Settings.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. <template>
  2. <div class="station-settings">
  3. <label class="label">Name</label>
  4. <div class="control is-grouped input-with-button">
  5. <p class="control is-expanded">
  6. <input class="input" type="text" v-model="station.name" />
  7. </p>
  8. <p class="control">
  9. <a class="button is-info" @click.prevent="updateName()">Save</a>
  10. </p>
  11. </div>
  12. <label class="label">Display Name</label>
  13. <div class="control is-grouped input-with-button">
  14. <p class="control is-expanded">
  15. <input
  16. class="input"
  17. type="text"
  18. v-model="station.displayName"
  19. />
  20. </p>
  21. <p class="control">
  22. <a class="button is-info" @click.prevent="updateDisplayName()"
  23. >Save</a
  24. >
  25. </p>
  26. </div>
  27. <label class="label">Description</label>
  28. <div class="control is-grouped input-with-button">
  29. <p class="control is-expanded">
  30. <input
  31. class="input"
  32. type="text"
  33. v-model="station.description"
  34. />
  35. </p>
  36. <p class="control">
  37. <a class="button is-info" @click.prevent="updateDescription()"
  38. >Save</a
  39. >
  40. </p>
  41. </div>
  42. <div class="settings-buttons">
  43. <div class="small-section">
  44. <label class="label">Theme</label>
  45. <tippy
  46. class="button-wrapper"
  47. theme="addToPlaylist"
  48. interactive="true"
  49. placement="bottom"
  50. trigger="click"
  51. append-to="parent"
  52. >
  53. <template #trigger>
  54. <button :class="station.theme">
  55. <i class="material-icons">palette</i>
  56. {{ station.theme }}
  57. </button>
  58. </template>
  59. <button
  60. class="blue"
  61. v-if="station.theme !== 'blue'"
  62. @click="updateTheme('blue')"
  63. >
  64. <i class="material-icons">palette</i>
  65. Blue
  66. </button>
  67. <button
  68. class="purple"
  69. v-if="station.theme !== 'purple'"
  70. @click="updateTheme('purple')"
  71. >
  72. <i class="material-icons">palette</i>
  73. Purple
  74. </button>
  75. <button
  76. class="teal"
  77. v-if="station.theme !== 'teal'"
  78. @click="updateTheme('teal')"
  79. >
  80. <i class="material-icons">palette</i>
  81. Teal
  82. </button>
  83. <button
  84. class="orange"
  85. v-if="station.theme !== 'orange'"
  86. @click="updateTheme('orange')"
  87. >
  88. <i class="material-icons">palette</i>
  89. Orange
  90. </button>
  91. </tippy>
  92. </div>
  93. <div class="small-section">
  94. <label class="label">Privacy</label>
  95. <tippy
  96. class="button-wrapper"
  97. theme="addToPlaylist"
  98. interactive="true"
  99. placement="bottom"
  100. trigger="click"
  101. append-to="parent"
  102. >
  103. <template #trigger>
  104. <button :class="privacyButtons[station.privacy].style">
  105. <i class="material-icons">{{
  106. privacyButtons[station.privacy].iconName
  107. }}</i>
  108. {{ station.privacy }}
  109. </button>
  110. </template>
  111. <button
  112. class="green"
  113. v-if="station.privacy !== 'public'"
  114. @click="updatePrivacy('public')"
  115. >
  116. <i class="material-icons">{{
  117. privacyButtons["public"].iconName
  118. }}</i>
  119. Public
  120. </button>
  121. <button
  122. class="orange"
  123. v-if="station.privacy !== 'unlisted'"
  124. @click="updatePrivacy('unlisted')"
  125. >
  126. <i class="material-icons">{{
  127. privacyButtons["unlisted"].iconName
  128. }}</i>
  129. Unlisted
  130. </button>
  131. <button
  132. class="red"
  133. v-if="station.privacy !== 'private'"
  134. @click="updatePrivacy('private')"
  135. >
  136. <i class="material-icons">{{
  137. privacyButtons["private"].iconName
  138. }}</i>
  139. Private
  140. </button>
  141. </tippy>
  142. </div>
  143. <div class="small-section">
  144. <label class="label">Station Mode</label>
  145. <tippy
  146. class="button-wrapper"
  147. theme="addToPlaylist"
  148. interactive="true"
  149. placement="bottom"
  150. trigger="click"
  151. append-to="parent"
  152. >
  153. <template #trigger>
  154. <button
  155. :class="{
  156. blue: !station.partyMode,
  157. yellow: station.partyMode
  158. }"
  159. >
  160. <i class="material-icons">{{
  161. station.partyMode
  162. ? "emoji_people"
  163. : "playlist_play"
  164. }}</i>
  165. {{ station.partyMode ? "Party" : "Playlist" }}
  166. </button>
  167. </template>
  168. <button
  169. class="blue"
  170. v-if="station.partyMode"
  171. @click="updatePartyMode(false)"
  172. >
  173. <i class="material-icons">playlist_play</i>
  174. Playlist
  175. </button>
  176. <button
  177. class="yellow"
  178. v-if="!station.partyMode"
  179. @click="updatePartyMode(true)"
  180. >
  181. <i class="material-icons">emoji_people</i>
  182. Party
  183. </button>
  184. </tippy>
  185. </div>
  186. <div v-if="!station.partyMode" class="small-section">
  187. <label class="label">Play Mode</label>
  188. <tippy
  189. class="button-wrapper"
  190. theme="addToPlaylist"
  191. interactive="true"
  192. placement="bottom"
  193. trigger="click"
  194. append-to="parent"
  195. >
  196. <template #trigger>
  197. <button class="blue">
  198. <i class="material-icons">{{
  199. station.playMode === "random"
  200. ? "shuffle"
  201. : "format_list_numbered"
  202. }}</i>
  203. {{
  204. station.playMode === "random"
  205. ? "Random"
  206. : "Sequential"
  207. }}
  208. </button>
  209. </template>
  210. <button
  211. class="blue"
  212. v-if="station.playMode === 'sequential'"
  213. @click="updatePlayMode('random')"
  214. >
  215. <i class="material-icons">shuffle</i>
  216. Random
  217. </button>
  218. <button
  219. class="blue"
  220. v-if="station.playMode === 'random'"
  221. @click="updatePlayMode('sequential')"
  222. >
  223. <i class="material-icons">format_list_numbered</i>
  224. Sequential
  225. </button>
  226. </tippy>
  227. </div>
  228. <div
  229. v-if="
  230. station.type === 'community' && station.partyMode === true
  231. "
  232. class="small-section"
  233. >
  234. <label class="label">Queue lock</label>
  235. <tippy
  236. class="button-wrapper"
  237. theme="addToPlaylist"
  238. interactive="true"
  239. placement="bottom"
  240. trigger="click"
  241. append-to="parent"
  242. >
  243. <template #trigger>
  244. <button
  245. :class="{
  246. green: station.locked,
  247. red: !station.locked
  248. }"
  249. >
  250. <i class="material-icons">{{
  251. station.locked ? "lock" : "lock_open"
  252. }}</i>
  253. {{ station.locked ? "Locked" : "Unlocked" }}
  254. </button>
  255. </template>
  256. <button
  257. class="green"
  258. v-if="!station.locked"
  259. @click="updateQueueLock(true)"
  260. >
  261. <i class="material-icons">lock</i>
  262. Locked
  263. </button>
  264. <button
  265. class="red"
  266. v-if="station.locked"
  267. @click="updateQueueLock(false)"
  268. >
  269. <i class="material-icons">lock_open</i>
  270. Unlocked
  271. </button>
  272. </tippy>
  273. </div>
  274. </div>
  275. </div>
  276. </template>
  277. <script>
  278. import { mapState, mapGetters } from "vuex";
  279. import Toast from "toasters";
  280. import validation from "@/validation";
  281. export default {
  282. data() {
  283. return {
  284. privacyButtons: {
  285. public: {
  286. style: "green",
  287. iconName: "public"
  288. },
  289. private: {
  290. style: "red",
  291. iconName: "lock"
  292. },
  293. unlisted: {
  294. style: "orange",
  295. iconName: "link"
  296. }
  297. }
  298. };
  299. },
  300. computed: {
  301. ...mapState("modals/editStation", {
  302. station: state => state.station,
  303. originalStation: state => state.originalStation
  304. }),
  305. ...mapGetters({
  306. socket: "websockets/getSocket"
  307. })
  308. },
  309. methods: {
  310. updateName() {
  311. if (this.originalStation.name !== this.station.name) {
  312. const { name } = this.station;
  313. if (!validation.isLength(name, 2, 16)) {
  314. new Toast("Name must have between 2 and 16 characters.");
  315. } else if (!validation.regex.az09_.test(name)) {
  316. new Toast(
  317. "Invalid name format. Allowed characters: a-z, 0-9 and _."
  318. );
  319. } else {
  320. this.socket.dispatch(
  321. "stations.updateName",
  322. this.station._id,
  323. name,
  324. res => {
  325. new Toast(res.message);
  326. if (res.status === "success") {
  327. this.station.name = name;
  328. this.originalStation.name = name;
  329. }
  330. }
  331. );
  332. }
  333. } else {
  334. new Toast("Please make a change before saving.");
  335. }
  336. },
  337. updateDisplayName() {
  338. if (this.originalStation.displayName !== this.station.displayName) {
  339. const { displayName } = this.station;
  340. if (!validation.isLength(displayName, 2, 32)) {
  341. new Toast(
  342. "Display name must have between 2 and 32 characters."
  343. );
  344. } else if (!validation.regex.ascii.test(displayName)) {
  345. new Toast(
  346. "Invalid display name format. Only ASCII characters are allowed."
  347. );
  348. } else {
  349. this.socket.dispatch(
  350. "stations.updateDisplayName",
  351. this.station._id,
  352. displayName,
  353. res => {
  354. new Toast(res.message);
  355. if (res.status === "success") {
  356. this.station.displayName = displayName;
  357. this.originalStation.displayName = displayName;
  358. }
  359. }
  360. );
  361. }
  362. } else {
  363. new Toast("Please make a change before saving.");
  364. }
  365. },
  366. updateDescription() {
  367. if (this.originalStation.description !== this.station.description) {
  368. const { description } = this.station;
  369. const characters = description.split("").filter(character => {
  370. return character.charCodeAt(0) === 21328;
  371. });
  372. if (!validation.isLength(description, 2, 200)) {
  373. new Toast(
  374. "Description must have between 2 and 200 characters."
  375. );
  376. } else if (characters.length !== 0) {
  377. new Toast("Invalid description format.");
  378. } else {
  379. this.socket.dispatch(
  380. "stations.updateDescription",
  381. this.station._id,
  382. description,
  383. res => {
  384. new Toast(res.message);
  385. if (res.status === "success") {
  386. this.station.description = description;
  387. this.originalStation.description = description;
  388. }
  389. }
  390. );
  391. }
  392. } else {
  393. new Toast("Please make a change before saving.");
  394. }
  395. },
  396. updateTheme(theme) {
  397. if (this.station.theme !== theme) {
  398. this.socket.dispatch(
  399. "stations.updateTheme",
  400. this.station._id,
  401. theme,
  402. res => {
  403. new Toast(res.message);
  404. if (res.status === "success") {
  405. this.station.theme = theme;
  406. this.originalStation.theme = theme;
  407. }
  408. }
  409. );
  410. }
  411. },
  412. updatePrivacy(privacy) {
  413. if (this.station.privacy !== privacy) {
  414. this.socket.dispatch(
  415. "stations.updatePrivacy",
  416. this.station._id,
  417. privacy,
  418. res => {
  419. new Toast(res.message);
  420. if (res.status === "success") {
  421. this.station.privacy = privacy;
  422. this.originalStation.privacy = privacy;
  423. }
  424. }
  425. );
  426. }
  427. },
  428. updatePartyMode(partyMode) {
  429. if (this.station.partyMode !== partyMode) {
  430. this.socket.dispatch(
  431. "stations.updatePartyMode",
  432. this.station._id,
  433. partyMode,
  434. res => {
  435. new Toast(res.message);
  436. if (res.status === "success") {
  437. this.station.partyMode = partyMode;
  438. this.originalStation.partyMode = partyMode;
  439. }
  440. }
  441. );
  442. }
  443. },
  444. updatePlayMode(playMode) {
  445. if (this.station.playMode !== playMode) {
  446. this.socket.dispatch(
  447. "stations.updatePlayMode",
  448. this.station._id,
  449. playMode,
  450. res => {
  451. new Toast(res.message);
  452. if (res.status === "success") {
  453. this.station.playMode = playMode;
  454. this.originalStation.playMode = playMode;
  455. }
  456. }
  457. );
  458. }
  459. },
  460. updateQueueLock(locked) {
  461. if (this.station.locked !== locked) {
  462. this.socket.dispatch(
  463. "stations.toggleLock",
  464. this.station._id,
  465. res => {
  466. if (res.status === "success") {
  467. if (this.originalStation) {
  468. this.station.locked = res.data.locked;
  469. this.originalStation.locked = res.data.locked;
  470. }
  471. new Toast(
  472. `Toggled queue lock successfully to ${res.data.locked}`
  473. );
  474. } else {
  475. new Toast("Failed to toggle queue lock.");
  476. }
  477. }
  478. );
  479. }
  480. }
  481. }
  482. };
  483. </script>
  484. <style lang="scss" scoped>
  485. .station-settings {
  486. .settings-buttons {
  487. display: flex;
  488. justify-content: center;
  489. flex-wrap: wrap;
  490. .small-section {
  491. width: calc(50% - 10px);
  492. min-width: 150px;
  493. margin: 5px auto;
  494. }
  495. }
  496. .button-wrapper {
  497. display: flex;
  498. flex-direction: column;
  499. button {
  500. width: 100%;
  501. height: 36px;
  502. border: 0;
  503. border-radius: 3px;
  504. font-size: 18px;
  505. color: var(--white);
  506. box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.25);
  507. display: block;
  508. text-align: center;
  509. justify-content: center;
  510. display: inline-flex;
  511. -ms-flex-align: center;
  512. align-items: center;
  513. -moz-user-select: none;
  514. user-select: none;
  515. cursor: pointer;
  516. padding: 0;
  517. text-transform: capitalize;
  518. &.red {
  519. background-color: var(--red);
  520. }
  521. &.green {
  522. background-color: var(--green);
  523. }
  524. &.blue {
  525. background-color: var(--blue);
  526. }
  527. &.orange {
  528. background-color: var(--orange);
  529. }
  530. &.yellow {
  531. background-color: var(--yellow);
  532. }
  533. &.purple {
  534. background-color: var(--purple);
  535. }
  536. &.teal {
  537. background-color: var(--teal);
  538. }
  539. i {
  540. font-size: 20px;
  541. margin-right: 4px;
  542. }
  543. }
  544. }
  545. }
  546. </style>