EditStation.vue 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428
  1. <template>
  2. <modal title="Edit Station" class="edit-station-modal">
  3. <template #body>
  4. <div class="custom-modal-body" v-if="station && station._id">
  5. <!-- Station Preferences -->
  6. <div class="section left-section">
  7. <div class="col col-2">
  8. <div>
  9. <label class="label">Name</label>
  10. <p class="control">
  11. <input
  12. class="input"
  13. type="text"
  14. v-model="station.name"
  15. />
  16. </p>
  17. </div>
  18. <div>
  19. <label class="label">Display name</label>
  20. <p class="control">
  21. <input
  22. class="input"
  23. type="text"
  24. v-model="station.displayName"
  25. />
  26. </p>
  27. </div>
  28. </div>
  29. <div class="col col-1">
  30. <div>
  31. <label class="label">Description</label>
  32. <p class="control">
  33. <input
  34. class="input"
  35. type="text"
  36. v-model="station.description"
  37. />
  38. </p>
  39. </div>
  40. </div>
  41. <div
  42. class="col col-2"
  43. v-if="station.type === 'official' && station.genres"
  44. >
  45. <div>
  46. <label class="label">Genre(s)</label>
  47. <p class="control has-addons">
  48. <input
  49. class="input"
  50. type="text"
  51. id="new-genre"
  52. v-model="genreInputValue"
  53. @blur="blurGenreInput()"
  54. @focus="focusGenreInput()"
  55. @keydown="keydownGenreInput()"
  56. @keyup.enter="addTag('genres')"
  57. />
  58. <button
  59. class="button is-info add-button blue"
  60. @click="addTag('genres')"
  61. >
  62. <i class="material-icons">add</i>
  63. </button>
  64. </p>
  65. <div
  66. class="autosuggest-container"
  67. v-if="
  68. (genreInputFocussed ||
  69. genreAutosuggestContainerFocussed) &&
  70. genreAutosuggestItems.length > 0
  71. "
  72. @mouseover="focusGenreContainer()"
  73. @mouseleave="blurGenreContainer()"
  74. >
  75. <span
  76. class="autosuggest-item"
  77. tabindex="0"
  78. @click="selectGenreAutosuggest(item)"
  79. v-for="(item,
  80. index) in genreAutosuggestItems"
  81. :key="index"
  82. >{{ item }}</span
  83. >
  84. </div>
  85. <div class="list-container">
  86. <div
  87. class="list-item"
  88. v-for="(genre, index) in station.genres"
  89. :key="index"
  90. >
  91. <div
  92. class="list-item-circle blue"
  93. @click="removeTag('genres', index)"
  94. >
  95. <i class="material-icons">close</i>
  96. </div>
  97. <p>{{ genre }}</p>
  98. </div>
  99. </div>
  100. </div>
  101. <div>
  102. <label class="label">Blacklist genre(s)</label>
  103. <p class="control has-addons">
  104. <input
  105. class="input"
  106. type="text"
  107. v-model="blacklistGenreInputValue"
  108. @blur="blurBlacklistGenreInput()"
  109. @focus="focusBlacklistGenreInput()"
  110. @keydown="keydownBlacklistGenreInput()"
  111. @keyup.enter="addTag('blacklist-genres')"
  112. />
  113. <button
  114. class="button is-info add-button red"
  115. @click="addTag('blacklist-genres')"
  116. >
  117. <i class="material-icons">add</i>
  118. </button>
  119. </p>
  120. <div
  121. class="autosuggest-container"
  122. v-if="
  123. (blacklistGenreInputFocussed ||
  124. blacklistGenreAutosuggestContainerFocussed) &&
  125. blacklistGenreAutosuggestItems.length >
  126. 0
  127. "
  128. @mouseover="focusBlacklistGenreContainer()"
  129. @mouseleave="blurBlacklistGenreContainer()"
  130. >
  131. <span
  132. class="autosuggest-item"
  133. tabindex="0"
  134. @click="
  135. selectBlacklistGenreAutosuggest(item)
  136. "
  137. v-for="(item,
  138. index) in blacklistGenreAutosuggestItems"
  139. :key="index"
  140. >{{ item }}</span
  141. >
  142. </div>
  143. <div class="list-container">
  144. <div
  145. class="list-item"
  146. v-for="(genre,
  147. index) in station.blacklistedGenres"
  148. :key="index"
  149. >
  150. <div
  151. class="list-item-circle red"
  152. @click="
  153. removeTag('blacklist-genres', index)
  154. "
  155. >
  156. <i class="material-icons">close</i>
  157. </div>
  158. <p>{{ genre }}</p>
  159. </div>
  160. </div>
  161. </div>
  162. </div>
  163. <!-- Choose a playlist -->
  164. <div
  165. v-if="
  166. station.type === 'community' &&
  167. !station.partyMode &&
  168. playlists.length > 0
  169. "
  170. >
  171. <hr style="margin: 10px 0 20px 0" />
  172. <h4 class="section-title">Choose a playlist</h4>
  173. <p class="section-description">
  174. Choose one of your playlists to add to the queue.
  175. </p>
  176. <br />
  177. <div id="playlists">
  178. <playlist-item
  179. :playlist="playlist"
  180. v-for="(playlist, index) in playlists"
  181. :key="index"
  182. >
  183. <div slot="actions">
  184. <a
  185. class="button is-danger"
  186. href="#"
  187. @click="deselectPlaylist()"
  188. v-if="isPlaylistSelected(playlist._id)"
  189. >
  190. <i
  191. class="material-icons icon-with-button"
  192. >stop</i
  193. >
  194. Stop playing
  195. </a>
  196. <a
  197. class="button is-success"
  198. href="#"
  199. @click="selectPlaylist(playlist._id)"
  200. v-else
  201. ><i
  202. class="material-icons icon-with-button"
  203. >play_arrow</i
  204. >Play in queue
  205. </a>
  206. </div>
  207. </playlist-item>
  208. </div>
  209. </div>
  210. </div>
  211. <!-- Buttons changing the privacy settings -->
  212. <div class="section right-section">
  213. <div>
  214. <label class="label">Privacy</label>
  215. <div
  216. @mouseenter="privacyDropdownActive = true"
  217. @mouseleave="privacyDropdownActive = false"
  218. class="button-wrapper"
  219. >
  220. <button
  221. :class="privacyButtons[station.privacy].style"
  222. style="text-transform: capitalize"
  223. @click="updatePrivacyLocal(station.privacy)"
  224. >
  225. <i class="material-icons">{{
  226. privacyButtons[station.privacy].iconName
  227. }}</i>
  228. {{ station.privacy }}
  229. </button>
  230. <transition name="slide-down">
  231. <button
  232. class="green"
  233. v-if="
  234. privacyDropdownActive &&
  235. station.privacy !== 'public'
  236. "
  237. @click="updatePrivacyLocal('public')"
  238. >
  239. <i class="material-icons">{{
  240. privacyButtons["public"].iconName
  241. }}</i>
  242. Public
  243. </button>
  244. </transition>
  245. <transition name="slide-down">
  246. <button
  247. class="orange"
  248. v-if="
  249. privacyDropdownActive &&
  250. station.privacy !== 'unlisted'
  251. "
  252. @click="updatePrivacyLocal('unlisted')"
  253. >
  254. <i class="material-icons">{{
  255. privacyButtons["unlisted"].iconName
  256. }}</i>
  257. Unlisted
  258. </button>
  259. </transition>
  260. <transition name="slide-down">
  261. <button
  262. class="red"
  263. v-if="
  264. privacyDropdownActive &&
  265. station.privacy !== 'private'
  266. "
  267. @click="updatePrivacyLocal('private')"
  268. >
  269. <i class="material-icons">{{
  270. privacyButtons["private"].iconName
  271. }}</i>
  272. Private
  273. </button>
  274. </transition>
  275. </div>
  276. </div>
  277. <!-- Buttons changing the mode of the station -->
  278. <div v-if="station.type === 'community'">
  279. <label class="label">Mode</label>
  280. <div
  281. @mouseenter="modeDropdownActive = true"
  282. @mouseleave="modeDropdownActive = false"
  283. class="button-wrapper"
  284. >
  285. <button
  286. :class="{
  287. blue: !station.partyMode,
  288. yellow: station.partyMode
  289. }"
  290. @click="
  291. station.partyMode
  292. ? updatePartyModeLocal(true)
  293. : updatePartyModeLocal(false)
  294. "
  295. >
  296. <i class="material-icons">{{
  297. station.partyMode
  298. ? "emoji_people"
  299. : "playlist_play"
  300. }}</i>
  301. {{ station.partyMode ? "Party" : "Playlist" }}
  302. </button>
  303. <transition name="slide-down">
  304. <button
  305. class="blue"
  306. v-if="
  307. modeDropdownActive && station.partyMode
  308. "
  309. @click="updatePartyModeLocal(false)"
  310. >
  311. <i class="material-icons">playlist_play</i>
  312. Playlist
  313. </button>
  314. </transition>
  315. <transition name="slide-down">
  316. <button
  317. class="yellow"
  318. v-if="
  319. modeDropdownActive && !station.partyMode
  320. "
  321. @click="updatePartyModeLocal(true)"
  322. >
  323. <i class="material-icons">emoji_people</i>
  324. Party
  325. </button>
  326. </transition>
  327. </div>
  328. </div>
  329. <div
  330. v-if="
  331. station.type === 'community' &&
  332. station.partyMode === true
  333. "
  334. >
  335. <label class="label">Queue lock</label>
  336. <div
  337. @mouseenter="queueLockDropdownActive = true"
  338. @mouseleave="queueLockDropdownActive = false"
  339. class="button-wrapper"
  340. >
  341. <button
  342. :class="{
  343. green: station.locked,
  344. red: !station.locked
  345. }"
  346. @click="
  347. station.locked
  348. ? updateQueueLockLocal(true)
  349. : updateQueueLockLocal(false)
  350. "
  351. >
  352. <i class="material-icons">{{
  353. station.locked ? "lock" : "lock_open"
  354. }}</i>
  355. {{ station.locked ? "Locked" : "Unlocked" }}
  356. </button>
  357. <transition name="slide-down">
  358. <button
  359. class="green"
  360. v-if="
  361. queueLockDropdownActive &&
  362. !station.locked
  363. "
  364. @click="updateQueueLockLocal(true)"
  365. >
  366. <i class="material-icons">lock</i>
  367. Locked
  368. </button>
  369. </transition>
  370. <transition name="slide-down">
  371. <button
  372. class="red"
  373. v-if="
  374. queueLockDropdownActive &&
  375. station.locked
  376. "
  377. @click="updateQueueLockLocal(false)"
  378. >
  379. <i class="material-icons">lock_open</i>
  380. Unlocked
  381. </button>
  382. </transition>
  383. </div>
  384. </div>
  385. <div>
  386. <label class="label">Theme</label>
  387. <div
  388. @mouseenter="themeDropdownActive = true"
  389. @mouseleave="themeDropdownActive = false"
  390. class="button-wrapper"
  391. >
  392. <button
  393. style="text-transform: capitalize"
  394. :class="station.theme"
  395. @click="updateThemeLocal(station.theme)"
  396. >
  397. <i class="material-icons">palette</i>
  398. {{ station.theme }}
  399. </button>
  400. <transition name="slide-down">
  401. <button
  402. class="blue"
  403. v-if="
  404. themeDropdownActive &&
  405. station.theme !== 'blue'
  406. "
  407. @click="updateThemeLocal('blue')"
  408. >
  409. <i class="material-icons">palette</i>
  410. Blue
  411. </button>
  412. </transition>
  413. <transition name="slide-down">
  414. <button
  415. class="purple"
  416. v-if="
  417. themeDropdownActive &&
  418. station.theme !== 'purple'
  419. "
  420. @click="updateThemeLocal('purple')"
  421. >
  422. <i class="material-icons">palette</i>
  423. Purple
  424. </button>
  425. </transition>
  426. <transition name="slide-down">
  427. <button
  428. class="teal"
  429. v-if="
  430. themeDropdownActive &&
  431. station.theme !== 'teal'
  432. "
  433. @click="updateThemeLocal('teal')"
  434. >
  435. <i class="material-icons">palette</i>
  436. Teal
  437. </button>
  438. </transition>
  439. <transition name="slide-down">
  440. <button
  441. class="orange"
  442. v-if="
  443. themeDropdownActive &&
  444. station.theme !== 'orange'
  445. "
  446. @click="updateThemeLocal('orange')"
  447. >
  448. <i class="material-icons">palette</i>
  449. Orange
  450. </button>
  451. </transition>
  452. </div>
  453. </div>
  454. </div>
  455. </div>
  456. </template>
  457. <template #footer>
  458. <button class="button is-success" @click="update()">
  459. Update Settings
  460. </button>
  461. <button
  462. v-if="station.type === 'community'"
  463. class="button is-danger"
  464. @click="deleteStation()"
  465. >
  466. Delete station
  467. </button>
  468. </template>
  469. </modal>
  470. </template>
  471. <script>
  472. import { mapState, mapActions } from "vuex";
  473. import Toast from "toasters";
  474. import PlaylistItem from "../ui/PlaylistItem.vue";
  475. import Modal from "../Modal.vue";
  476. import io from "../../io";
  477. import validation from "../../validation";
  478. export default {
  479. components: { Modal, PlaylistItem },
  480. props: {
  481. stationId: { type: String, default: "" },
  482. sector: { type: String, default: "admin" }
  483. },
  484. data() {
  485. return {
  486. genreInputValue: "",
  487. genreInputFocussed: false,
  488. genreAutosuggestContainerFocussed: false,
  489. keydownGenreInputTimeout: 0,
  490. genreAutosuggestItems: [],
  491. blacklistGenreInputValue: "",
  492. blacklistGenreInputFocussed: false,
  493. blacklistGenreAutosuggestContainerFocussed: false,
  494. blacklistKeydownGenreInputTimeout: 0,
  495. blacklistGenreAutosuggestItems: [],
  496. privacyDropdownActive: false,
  497. modeDropdownActive: false,
  498. queueLockDropdownActive: false,
  499. themeDropdownActive: false,
  500. genres: [
  501. "Blues",
  502. "Country",
  503. "Disco",
  504. "Funk",
  505. "Hip-Hop",
  506. "Jazz",
  507. "Metal",
  508. "Oldies",
  509. "Other",
  510. "Pop",
  511. "Rap",
  512. "Reggae",
  513. "Rock",
  514. "Techno",
  515. "Trance",
  516. "Classical",
  517. "Instrumental",
  518. "House",
  519. "Electronic",
  520. "Christian Rap",
  521. "Lo-Fi",
  522. "Musical",
  523. "Rock 'n' Roll",
  524. "Opera",
  525. "Drum & Bass",
  526. "Club-House",
  527. "Indie",
  528. "Heavy Metal",
  529. "Christian rock",
  530. "Dubstep"
  531. ],
  532. privacyButtons: {
  533. public: {
  534. style: "green",
  535. iconName: "public"
  536. },
  537. private: {
  538. style: "red",
  539. iconName: "lock"
  540. },
  541. unlisted: {
  542. style: "orange",
  543. iconName: "link"
  544. }
  545. },
  546. playlists: []
  547. };
  548. },
  549. computed: {
  550. // ...mapState("admin/stations", {
  551. // stations: state => state.stations
  552. // }),
  553. ...mapState("editStationModal", {
  554. station: state => state.station,
  555. originalStation: state => state.originalStation
  556. })
  557. },
  558. mounted() {
  559. io.getSocket(socket => {
  560. this.socket = socket;
  561. this.socket.emit(`stations.getStationById`, this.stationId, res => {
  562. if (res.status === "success") {
  563. const { station } = res;
  564. // this.song = { ...song };
  565. // if (this.song.discogs === undefined)
  566. // this.song.discogs = null;
  567. this.editStation(station);
  568. // this.songDataLoaded = true;
  569. this.station.genres = JSON.parse(
  570. JSON.stringify(this.station.genres)
  571. );
  572. this.station.blacklistedGenres = JSON.parse(
  573. JSON.stringify(this.station.blacklistedGenres)
  574. );
  575. } else {
  576. new Toast({
  577. content: "Station with that ID not found",
  578. timeout: 3000
  579. });
  580. this.closeModal({
  581. sector: this.sector,
  582. modal: "editStation"
  583. });
  584. }
  585. });
  586. this.socket.emit("playlists.indexMyPlaylists", true, res => {
  587. if (res.status === "success") this.playlists = res.data;
  588. });
  589. this.socket.on("event:playlist.create", playlist => {
  590. this.playlists.push(playlist);
  591. });
  592. this.socket.on("event:playlist.delete", playlistId => {
  593. this.playlists.forEach((playlist, index) => {
  594. if (playlist._id === playlistId) {
  595. this.playlists.splice(index, 1);
  596. }
  597. });
  598. });
  599. this.socket.on("event:playlist.addSong", data => {
  600. this.playlists.forEach((playlist, index) => {
  601. if (playlist._id === data.playlistId) {
  602. this.playlists[index].songs.push(data.song);
  603. }
  604. });
  605. });
  606. this.socket.on("event:playlist.removeSong", data => {
  607. this.playlists.forEach((playlist, index) => {
  608. if (playlist._id === data.playlistId) {
  609. this.playlists[index].songs.forEach((song, index2) => {
  610. if (song.songId === data.songId) {
  611. this.playlists[index].songs.splice(index2, 1);
  612. }
  613. });
  614. }
  615. });
  616. });
  617. this.socket.on("event:playlist.updateDisplayName", data => {
  618. this.playlists.forEach((playlist, index) => {
  619. if (playlist._id === data.playlistId) {
  620. this.playlists[index].displayName = data.displayName;
  621. }
  622. });
  623. });
  624. return socket;
  625. });
  626. },
  627. methods: {
  628. isPlaylistSelected(id) {
  629. // TODO Also change this once it changes for a station
  630. if (
  631. this.originalStation &&
  632. this.originalStation.privatePlaylist === id
  633. )
  634. return true;
  635. return false;
  636. },
  637. selectPlaylist(playlistId) {
  638. this.socket.emit(
  639. "stations.selectPrivatePlaylist",
  640. this.originalStation._id,
  641. playlistId,
  642. res => {
  643. if (res.status === "failure")
  644. return new Toast({
  645. content: res.message,
  646. timeout: 8000
  647. });
  648. return new Toast({ content: res.message, timeout: 4000 });
  649. }
  650. );
  651. },
  652. deselectPlaylist() {
  653. this.socket.emit(
  654. "stations.deselectPrivatePlaylist",
  655. this.originalStation._id,
  656. res => {
  657. if (res.status === "failure")
  658. return new Toast({
  659. content: res.message,
  660. timeout: 8000
  661. });
  662. return new Toast({ content: res.message, timeout: 4000 });
  663. }
  664. );
  665. },
  666. update() {
  667. if (this.originalStation.name !== this.station.name)
  668. this.updateName();
  669. if (this.originalStation.displayName !== this.station.displayName)
  670. this.updateDisplayName();
  671. if (this.originalStation.description !== this.station.description)
  672. this.updateDescription();
  673. if (this.originalStation.privacy !== this.station.privacy)
  674. this.updatePrivacy();
  675. if (
  676. this.originalStation.type === "community" &&
  677. this.originalStation.partyMode !== this.station.partyMode
  678. )
  679. this.updatePartyMode();
  680. if (
  681. this.originalStation.type === "community" &&
  682. this.station.partyMode &&
  683. this.originalStation.locked !== this.station.locked
  684. )
  685. this.updateQueueLock();
  686. if (
  687. this.originalStation.genres.toString() !==
  688. this.station.genres.toString()
  689. )
  690. this.updateGenres();
  691. if (
  692. this.originalStation.blacklistedGenres.toString() !==
  693. this.station.blacklistedGenres.toString()
  694. )
  695. this.updateBlacklistedGenres();
  696. if (this.originalStation.theme !== this.station.theme)
  697. this.updateTheme();
  698. },
  699. updateName() {
  700. const { name } = this.station;
  701. if (!validation.isLength(name, 2, 16))
  702. return new Toast({
  703. content: "Name must have between 2 and 16 characters.",
  704. timeout: 8000
  705. });
  706. if (!validation.regex.az09_.test(name))
  707. return new Toast({
  708. content:
  709. "Invalid name format. Allowed characters: a-z, 0-9 and _.",
  710. timeout: 8000
  711. });
  712. return this.socket.emit(
  713. "stations.updateName",
  714. this.station._id,
  715. name,
  716. res => {
  717. if (res.status === "success") {
  718. if (this.originalStation)
  719. this.originalStation.name = name;
  720. else {
  721. this.originalStations.forEach((station, index) => {
  722. if (station._id === this.station._id) {
  723. this.originalStations[index].name = name;
  724. return name;
  725. }
  726. return false;
  727. });
  728. }
  729. }
  730. new Toast({ content: res.message, timeout: 8000 });
  731. }
  732. );
  733. },
  734. updateDisplayName() {
  735. const { displayName } = this.station;
  736. if (!validation.isLength(displayName, 2, 32))
  737. return new Toast({
  738. content:
  739. "Display name must have between 2 and 32 characters.",
  740. timeout: 8000
  741. });
  742. if (!validation.regex.ascii.test(displayName))
  743. return new Toast({
  744. content:
  745. "Invalid display name format. Only ASCII characters are allowed.",
  746. timeout: 8000
  747. });
  748. return this.socket.emit(
  749. "stations.updateDisplayName",
  750. this.station._id,
  751. displayName,
  752. res => {
  753. if (res.status === "success") {
  754. if (this.originalStation)
  755. this.originalStation.displayName = displayName;
  756. else {
  757. this.originalStations.forEach((station, index) => {
  758. if (station._id === this.station._id) {
  759. this.originalStations[
  760. index
  761. ].displayName = displayName;
  762. return displayName;
  763. }
  764. return false;
  765. });
  766. }
  767. }
  768. new Toast({ content: res.message, timeout: 8000 });
  769. }
  770. );
  771. },
  772. updateDescription() {
  773. const { description } = this.station;
  774. if (!validation.isLength(description, 2, 200))
  775. return new Toast({
  776. content:
  777. "Description must have between 2 and 200 characters.",
  778. timeout: 8000
  779. });
  780. let characters = description.split("");
  781. characters = characters.filter(character => {
  782. return character.charCodeAt(0) === 21328;
  783. });
  784. if (characters.length !== 0)
  785. return new Toast({
  786. content: "Invalid description format.",
  787. timeout: 8000
  788. });
  789. return this.socket.emit(
  790. "stations.updateDescription",
  791. this.station._id,
  792. description,
  793. res => {
  794. if (res.status === "success") {
  795. if (this.originalStation)
  796. this.originalStation.description = description;
  797. else {
  798. this.originalStations.forEach((station, index) => {
  799. if (station._id === this.station._id) {
  800. this.originalStations[
  801. index
  802. ].description = description;
  803. return description;
  804. }
  805. return false;
  806. });
  807. }
  808. return new Toast({
  809. content: res.message,
  810. timeout: 4000
  811. });
  812. }
  813. return new Toast({ content: res.message, timeout: 8000 });
  814. }
  815. );
  816. },
  817. updatePrivacyLocal(privacy) {
  818. if (this.station.privacy === privacy) return;
  819. this.station.privacy = privacy;
  820. this.privacyDropdownActive = false;
  821. },
  822. updatePrivacy() {
  823. this.socket.emit(
  824. "stations.updatePrivacy",
  825. this.station._id,
  826. this.station.privacy,
  827. res => {
  828. if (res.status === "success") {
  829. if (this.originalStation)
  830. this.originalStation.privacy = this.station.privacy;
  831. else {
  832. this.originalStations.forEach((station, index) => {
  833. if (station._id === this.station._id) {
  834. this.originalStations[
  835. index
  836. ].privacy = this.station.privacy;
  837. return this.station.privacy;
  838. }
  839. return false;
  840. });
  841. }
  842. return new Toast({
  843. content: res.message,
  844. timeout: 4000
  845. });
  846. }
  847. return new Toast({ content: res.message, timeout: 8000 });
  848. }
  849. );
  850. },
  851. updateGenres() {
  852. this.socket.emit(
  853. "stations.updateGenres",
  854. this.station._id,
  855. this.station.genres,
  856. res => {
  857. if (res.status === "success") {
  858. const genres = JSON.parse(
  859. JSON.stringify(this.station.genres)
  860. );
  861. if (this.originalStation)
  862. this.originalStation.genres = genres;
  863. this.originalStations.forEach((station, index) => {
  864. if (station._id === this.station._id) {
  865. this.originalStations[index].genres = genres;
  866. return genres;
  867. }
  868. return false;
  869. });
  870. return new Toast({
  871. content: res.message,
  872. timeout: 4000
  873. });
  874. }
  875. return new Toast({ content: res.message, timeout: 8000 });
  876. }
  877. );
  878. },
  879. updateBlacklistedGenres() {
  880. this.socket.emit(
  881. "stations.updateBlacklistedGenres",
  882. this.station._id,
  883. this.station.blacklistedGenres,
  884. res => {
  885. if (res.status === "success") {
  886. const blacklistedGenres = JSON.parse(
  887. JSON.stringify(this.station.blacklistedGenres)
  888. );
  889. if (this.originalStation)
  890. this.originalStation.blacklistedGenres = blacklistedGenres;
  891. this.originalStations.forEach((station, index) => {
  892. if (station._id === this.station._id) {
  893. this.originalStations[
  894. index
  895. ].blacklistedGenres = blacklistedGenres;
  896. return blacklistedGenres;
  897. }
  898. return false;
  899. });
  900. return new Toast({
  901. content: res.message,
  902. timeout: 4000
  903. });
  904. }
  905. return new Toast({ content: res.message, timeout: 8000 });
  906. }
  907. );
  908. },
  909. updatePartyModeLocal(partyMode) {
  910. if (this.station.partyMode === partyMode) return;
  911. this.station.partyMode = partyMode;
  912. this.modeDropdownActive = false;
  913. },
  914. updatePartyMode() {
  915. this.socket.emit(
  916. "stations.updatePartyMode",
  917. this.station._id,
  918. this.station.partyMode,
  919. res => {
  920. if (res.status === "success") {
  921. if (this.originalStation)
  922. this.originalStation.partyMode = this.station.partyMode;
  923. // if (this.originalStation)
  924. // this.originalStation.partyMode = this.station.partyMode;
  925. // this.originalStations.forEach((station, index) => {
  926. // if (station._id === this.station._id) {
  927. // this.originalStations[
  928. // index
  929. // ].partyMode = this.station.partyMode;
  930. // return this.station.partyMode;
  931. // }
  932. // return false;
  933. // });
  934. return new Toast({
  935. content: res.message,
  936. timeout: 4000
  937. });
  938. }
  939. return new Toast({ content: res.message, timeout: 8000 });
  940. }
  941. );
  942. },
  943. updateQueueLockLocal(locked) {
  944. if (this.station.locked === locked) return;
  945. this.station.locked = locked;
  946. this.queueLockDropdownActive = false;
  947. },
  948. updateQueueLock() {
  949. this.socket.emit("stations.toggleLock", this.station._id, res => {
  950. console.log(res);
  951. if (res.status === "success") {
  952. if (this.originalStation)
  953. this.originalStation.locked = res.data;
  954. return new Toast({
  955. content: `Toggled queue lock succesfully to ${res.data}`,
  956. timeout: 4000
  957. });
  958. }
  959. return new Toast({
  960. content: "Failed to toggle queue lock.",
  961. timeout: 8000
  962. });
  963. });
  964. },
  965. updateThemeLocal(theme) {
  966. if (this.station.theme === theme) return;
  967. this.station.theme = theme;
  968. this.themeDropdownActive = false;
  969. },
  970. updateTheme() {
  971. this.socket.emit(
  972. "stations.updateTheme",
  973. this.station._id,
  974. this.station.theme,
  975. res => {
  976. if (res.status === "success") {
  977. if (this.originalStation)
  978. this.originalStation.theme = this.station.theme;
  979. else {
  980. this.originalStations.forEach((station, index) => {
  981. if (station._id === this.station._id) {
  982. this.originalStations[
  983. index
  984. ].theme = this.station.theme;
  985. return this.station.theme;
  986. }
  987. return false;
  988. });
  989. }
  990. return new Toast({
  991. content: res.message,
  992. timeout: 4000
  993. });
  994. }
  995. return new Toast({ content: res.message, timeout: 8000 });
  996. }
  997. );
  998. },
  999. deleteStation() {
  1000. this.socket.emit("stations.remove", this.station._id, res => {
  1001. if (res.status === "success")
  1002. this.closeModal({
  1003. sector: "station",
  1004. modal: "editStation"
  1005. });
  1006. return new Toast({ content: res.message, timeout: 8000 });
  1007. });
  1008. },
  1009. blurGenreInput() {
  1010. this.genreInputFocussed = false;
  1011. },
  1012. focusGenreInput() {
  1013. this.genreInputFocussed = true;
  1014. },
  1015. keydownGenreInput() {
  1016. clearTimeout(this.keydownGenreInputTimeout);
  1017. this.keydownGenreInputTimeout = setTimeout(() => {
  1018. if (this.genreInputValue.length > 1) {
  1019. this.genreAutosuggestItems = this.genres.filter(genre => {
  1020. return genre
  1021. .toLowerCase()
  1022. .startsWith(this.genreInputValue.toLowerCase());
  1023. });
  1024. } else this.genreAutosuggestItems = [];
  1025. }, 1000);
  1026. },
  1027. focusGenreContainer() {
  1028. this.genreAutosuggestContainerFocussed = true;
  1029. },
  1030. blurGenreContainer() {
  1031. this.genreAutosuggestContainerFocussed = false;
  1032. },
  1033. selectGenreAutosuggest(value) {
  1034. this.genreInputValue = value;
  1035. },
  1036. blurBlacklistGenreInput() {
  1037. this.blacklistGenreInputFocussed = false;
  1038. },
  1039. focusBlacklistGenreInput() {
  1040. this.blacklistGenreInputFocussed = true;
  1041. },
  1042. keydownBlacklistGenreInput() {
  1043. clearTimeout(this.keydownBlacklistGenreInputTimeout);
  1044. this.keydownBlacklistGenreInputTimeout = setTimeout(() => {
  1045. if (this.blacklistGenreInputValue.length > 1) {
  1046. this.blacklistGenreAutosuggestItems = this.genres.filter(
  1047. genre => {
  1048. return genre
  1049. .toLowerCase()
  1050. .startsWith(
  1051. this.blacklistGenreInputValue.toLowerCase()
  1052. );
  1053. }
  1054. );
  1055. } else this.blacklistGenreAutosuggestItems = [];
  1056. }, 1000);
  1057. },
  1058. focusBlacklistGenreContainer() {
  1059. this.blacklistGenreAutosuggestContainerFocussed = true;
  1060. },
  1061. blurBlacklistGenreContainer() {
  1062. this.blacklistGenreAutosuggestContainerFocussed = false;
  1063. },
  1064. selectBlacklistGenreAutosuggest(value) {
  1065. this.blacklistGenreInputValue = value;
  1066. },
  1067. addTag(type) {
  1068. if (type === "genres") {
  1069. const genre = this.genreInputValue.toLowerCase().trim();
  1070. if (this.station.genres.indexOf(genre) !== -1)
  1071. return new Toast({
  1072. content: "Genre already exists",
  1073. timeout: 3000
  1074. });
  1075. if (genre) {
  1076. this.station.genres.push(genre);
  1077. this.genreInputValue = "";
  1078. return false;
  1079. }
  1080. return new Toast({
  1081. content: "Genre cannot be empty",
  1082. timeout: 3000
  1083. });
  1084. }
  1085. if (type === "blacklist-genres") {
  1086. const genre = this.blacklistGenreInputValue
  1087. .toLowerCase()
  1088. .trim();
  1089. if (this.station.blacklistedGenres.indexOf(genre) !== -1)
  1090. return new Toast({
  1091. content: "Blacklist genre already exists",
  1092. timeout: 3000
  1093. });
  1094. if (genre) {
  1095. this.station.blacklistedGenres.push(genre);
  1096. this.blacklistGenreInputValue = "";
  1097. return false;
  1098. }
  1099. return new Toast({
  1100. content: "Blacklist genre cannot be empty",
  1101. timeout: 3000
  1102. });
  1103. }
  1104. return false;
  1105. },
  1106. removeTag(type, index) {
  1107. if (type === "genres") this.station.genres.splice(index, 1);
  1108. else if (type === "blacklist-genres")
  1109. this.station.blacklistedGenres.splice(index, 1);
  1110. },
  1111. ...mapActions("editStationModal", ["editStation"]),
  1112. ...mapActions("modals", ["closeModal"])
  1113. }
  1114. };
  1115. </script>
  1116. <style lang="scss" scoped>
  1117. @import "../../styles/global.scss";
  1118. .night-mode {
  1119. .modal-card,
  1120. .modal-card-head,
  1121. .modal-card-body,
  1122. .modal-card-foot {
  1123. background-color: $night-mode-bg-secondary;
  1124. }
  1125. .section {
  1126. background-color: $night-mode-bg-secondary !important;
  1127. border: 0 !important;
  1128. }
  1129. .label,
  1130. p,
  1131. strong {
  1132. color: $night-mode-text;
  1133. }
  1134. }
  1135. .modal-card-title {
  1136. text-align: center;
  1137. margin-left: 24px;
  1138. }
  1139. .custom-modal-body {
  1140. padding: 16px;
  1141. display: flex;
  1142. }
  1143. .section {
  1144. border: 1px solid #a3e0ff;
  1145. background-color: #f4f4f4;
  1146. border-radius: 5px;
  1147. padding: 16px;
  1148. }
  1149. .left-section {
  1150. width: 595px;
  1151. display: grid;
  1152. gap: 16px;
  1153. grid-template-rows: min-content min-content auto;
  1154. .control {
  1155. input {
  1156. width: 100%;
  1157. height: 36px;
  1158. }
  1159. .add-button {
  1160. width: 32px;
  1161. &.blue {
  1162. background-color: $musare-blue !important;
  1163. }
  1164. &.red {
  1165. background-color: $red !important;
  1166. }
  1167. i {
  1168. font-size: 32px;
  1169. }
  1170. }
  1171. }
  1172. .col {
  1173. > div {
  1174. position: relative;
  1175. }
  1176. }
  1177. .list-item-circle {
  1178. width: 16px;
  1179. height: 16px;
  1180. border-radius: 8px;
  1181. cursor: pointer;
  1182. margin-right: 8px;
  1183. float: left;
  1184. -webkit-touch-callout: none;
  1185. -webkit-user-select: none;
  1186. -khtml-user-select: none;
  1187. -moz-user-select: none;
  1188. -ms-user-select: none;
  1189. user-select: none;
  1190. &.blue {
  1191. background-color: $musare-blue;
  1192. i {
  1193. color: $musare-blue;
  1194. }
  1195. }
  1196. &.red {
  1197. background-color: $red;
  1198. i {
  1199. color: $red;
  1200. }
  1201. }
  1202. i {
  1203. font-size: 14px;
  1204. margin-left: 1px;
  1205. }
  1206. }
  1207. .list-item-circle:hover,
  1208. .list-item-circle:focus {
  1209. i {
  1210. color: white;
  1211. }
  1212. }
  1213. .list-item > p {
  1214. line-height: 16px;
  1215. word-wrap: break-word;
  1216. width: calc(100% - 24px);
  1217. left: 24px;
  1218. float: left;
  1219. margin-bottom: 8px;
  1220. }
  1221. .list-item:last-child > p {
  1222. margin-bottom: 0;
  1223. }
  1224. .autosuggest-container {
  1225. position: absolute;
  1226. background: white;
  1227. width: calc(100% + 1px);
  1228. top: 57px;
  1229. z-index: 200;
  1230. overflow: auto;
  1231. max-height: 100%;
  1232. clear: both;
  1233. .autosuggest-item {
  1234. padding: 8px;
  1235. display: block;
  1236. border: 1px solid #dbdbdb;
  1237. margin-top: -1px;
  1238. line-height: 16px;
  1239. cursor: pointer;
  1240. -webkit-user-select: none;
  1241. -ms-user-select: none;
  1242. -moz-user-select: none;
  1243. user-select: none;
  1244. }
  1245. .autosuggest-item:hover,
  1246. .autosuggest-item:focus {
  1247. background-color: #eee;
  1248. }
  1249. .autosuggest-item:first-child {
  1250. border-top: none;
  1251. }
  1252. .autosuggest-item:last-child {
  1253. border-radius: 0 0 3px 3px;
  1254. }
  1255. }
  1256. }
  1257. .right-section {
  1258. width: 157px;
  1259. min-height: 375px;
  1260. margin-left: 16px;
  1261. display: grid;
  1262. gap: 16px;
  1263. grid-template-rows: min-content min-content min-content;
  1264. .button-wrapper {
  1265. display: flex;
  1266. flex-direction: column;
  1267. }
  1268. button {
  1269. width: 100%;
  1270. height: 36px;
  1271. border: 0;
  1272. border-radius: 3px;
  1273. font-size: 18px;
  1274. color: white;
  1275. box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.25);
  1276. display: block;
  1277. text-align: center;
  1278. justify-content: center;
  1279. display: inline-flex;
  1280. -ms-flex-align: center;
  1281. align-items: center;
  1282. -moz-user-select: none;
  1283. user-select: none;
  1284. cursor: pointer;
  1285. margin-bottom: 10px;
  1286. padding: 0;
  1287. &.red {
  1288. background-color: $red;
  1289. }
  1290. &.green {
  1291. background-color: $green;
  1292. }
  1293. &.blue {
  1294. background-color: $musare-blue;
  1295. }
  1296. &.orange {
  1297. background-color: $light-orange;
  1298. }
  1299. &.yellow {
  1300. background-color: $yellow;
  1301. }
  1302. i {
  1303. font-size: 20px;
  1304. margin-right: 4px;
  1305. }
  1306. }
  1307. }
  1308. .col {
  1309. display: grid;
  1310. grid-column-gap: 16px;
  1311. }
  1312. .col-1 {
  1313. grid-template-columns: auto;
  1314. }
  1315. .col-2 {
  1316. grid-template-columns: auto auto;
  1317. }
  1318. .slide-down-enter-active {
  1319. transition: transform 0.25s;
  1320. }
  1321. .slide-down-enter {
  1322. transform: translateY(-10px);
  1323. }
  1324. #playlists {
  1325. overflow: auto;
  1326. .playlist:not(:last-of-type) {
  1327. margin-bottom: 10px;
  1328. }
  1329. .button {
  1330. width: 148px;
  1331. }
  1332. }
  1333. .modal-card {
  1334. overflow: auto;
  1335. }
  1336. .modal-card-body {
  1337. overflow: unset;
  1338. }
  1339. </style>