EditStation.vue 29 KB

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