EditStation.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. <template>
  2. <modal title="Edit Station">
  3. <template v-slot:body>
  4. <label class="label">Name</label>
  5. <p class="control">
  6. <input
  7. v-model="editing.name"
  8. class="input"
  9. type="text"
  10. placeholder="Station Name"
  11. />
  12. </p>
  13. <label class="label">Display name</label>
  14. <p class="control">
  15. <input
  16. v-model="editing.displayName"
  17. class="input"
  18. type="text"
  19. placeholder="Station Display Name"
  20. />
  21. </p>
  22. <label class="label">Description</label>
  23. <p class="control">
  24. <input
  25. v-model="editing.description"
  26. class="input"
  27. type="text"
  28. placeholder="Station Description"
  29. />
  30. </p>
  31. <label class="label">Privacy</label>
  32. <p class="control">
  33. <span class="select">
  34. <select v-model="editing.privacy">
  35. <option value="public">Public</option>
  36. <option value="unlisted">Unlisted</option>
  37. <option value="private">Private</option>
  38. </select>
  39. </span>
  40. </p>
  41. <br />
  42. <p class="control" v-if="station.type === 'community'">
  43. <label class="checkbox party-mode-inner">
  44. <input v-model="editing.partyMode" type="checkbox" />
  45. &nbsp;Party mode
  46. </label>
  47. </p>
  48. <small v-if="station.type === 'community'"
  49. >With party mode enabled, people can add songs to a queue that
  50. plays. With party mode disabled you can play a private playlist
  51. on loop.</small
  52. >
  53. <br />
  54. <div v-if="station.type === 'community' && station.partyMode">
  55. <br />
  56. <br />
  57. <label class="label">Queue lock</label>
  58. <small v-if="station.partyMode"
  59. >With the queue locked, only owners (you) can add songs to
  60. the queue.</small
  61. >
  62. <br />
  63. <button
  64. v-if="!station.locked"
  65. class="button is-danger"
  66. @click="$parent.toggleLock()"
  67. >
  68. Lock the queue
  69. </button>
  70. <button
  71. v-if="station.locked"
  72. class="button is-success"
  73. @click="$parent.toggleLock()"
  74. >
  75. Unlock the queue
  76. </button>
  77. </div>
  78. <div
  79. v-if="station.type === 'official'"
  80. class="control is-grouped genre-wrapper"
  81. >
  82. <div class="sector">
  83. <p class="control has-addons">
  84. <input
  85. id="new-genre-edit"
  86. class="input"
  87. type="text"
  88. placeholder="Genre"
  89. @keyup.enter="addGenre()"
  90. />
  91. <a class="button is-info" href="#" @click="addGenre()"
  92. >Add genre</a
  93. >
  94. </p>
  95. <span
  96. v-for="(genre, index) in editing.genres"
  97. :key="index"
  98. class="tag is-info"
  99. >
  100. {{ genre }}
  101. <button
  102. class="delete is-info"
  103. @click="removeGenre(index)"
  104. />
  105. </span>
  106. </div>
  107. <div class="sector">
  108. <p class="control has-addons">
  109. <input
  110. id="new-blacklisted-genre-edit"
  111. class="input"
  112. type="text"
  113. placeholder="Blacklisted Genre"
  114. @keyup.enter="addBlacklistedGenre()"
  115. />
  116. <a
  117. class="button is-info"
  118. href="#"
  119. @click="addBlacklistedGenre()"
  120. >Add blacklisted genre</a
  121. >
  122. </p>
  123. <span
  124. v-for="(genre, index) in editing.blacklistedGenres"
  125. :key="index"
  126. class="tag is-info"
  127. >
  128. {{ genre }}
  129. <button
  130. class="delete is-info"
  131. @click="removeBlacklistedGenre(index)"
  132. />
  133. </span>
  134. </div>
  135. </div>
  136. </template>
  137. <template v-slot:footer>
  138. <button class="button is-success" v-on:click="update()">
  139. Update Settings
  140. </button>
  141. <button
  142. v-if="station.type === 'community'"
  143. class="button is-danger"
  144. @click="deleteStation()"
  145. >
  146. Delete station
  147. </button>
  148. </template>
  149. </modal>
  150. </template>
  151. <script>
  152. import { mapState } from "vuex";
  153. import { Toast } from "vue-roaster";
  154. import Modal from "../Modals/Modal.vue";
  155. import io from "../../io";
  156. import validation from "../../validation";
  157. export default {
  158. computed: mapState("admin/stations", {
  159. station: state => state.station,
  160. editing: state => state.editing
  161. }),
  162. mounted: function() {
  163. let _this = this;
  164. io.getSocket(socket => (_this.socket = socket));
  165. },
  166. methods: {
  167. update: function() {
  168. if (this.station.name !== this.editing.name) this.updateName();
  169. if (this.station.displayName !== this.editing.displayName)
  170. this.updateDisplayName();
  171. if (this.station.description !== this.editing.description)
  172. this.updateDescription();
  173. if (this.station.privacy !== this.editing.privacy)
  174. this.updatePrivacy();
  175. if (this.station.partyMode !== this.editing.partyMode)
  176. this.updatePartyMode();
  177. if (
  178. this.station.genres.toString() !==
  179. this.editing.genres.toString()
  180. )
  181. this.updateGenres();
  182. if (
  183. this.station.blacklistedGenres.toString() !==
  184. this.editing.blacklistedGenres.toString()
  185. )
  186. this.updateBlacklistedGenres();
  187. },
  188. updateName: function() {
  189. const name = this.editing.name;
  190. if (!validation.isLength(name, 2, 16))
  191. return Toast.methods.addToast(
  192. "Name must have between 2 and 16 characters.",
  193. 8000
  194. );
  195. if (!validation.regex.az09_.test(name))
  196. return Toast.methods.addToast(
  197. "Invalid name format. Allowed characters: a-z, 0-9 and _.",
  198. 8000
  199. );
  200. this.socket.emit(
  201. "stations.updateName",
  202. this.editing._id,
  203. name,
  204. res => {
  205. if (res.status === "success") {
  206. if (this.station) this.station.name = name;
  207. this.$parent.stations.forEach((station, index) => {
  208. if (station._id === this.editing._id)
  209. return (this.$parent.stations[
  210. index
  211. ].name = name);
  212. });
  213. }
  214. Toast.methods.addToast(res.message, 8000);
  215. }
  216. );
  217. },
  218. updateDisplayName: function() {
  219. const displayName = this.editing.displayName;
  220. if (!validation.isLength(displayName, 2, 32))
  221. return Toast.methods.addToast(
  222. "Display name must have between 2 and 32 characters.",
  223. 8000
  224. );
  225. if (!validation.regex.azAZ09_.test(displayName))
  226. return Toast.methods.addToast(
  227. "Invalid display name format. Allowed characters: a-z, A-Z, 0-9 and _.",
  228. 8000
  229. );
  230. this.socket.emit(
  231. "stations.updateDisplayName",
  232. this.editing._id,
  233. displayName,
  234. res => {
  235. if (res.status === "success") {
  236. if (this.station)
  237. this.station.displayName = displayName;
  238. this.$parent.stations.forEach((station, index) => {
  239. if (station._id === this.editing._id)
  240. return (this.$parent.stations[
  241. index
  242. ].displayName = displayName);
  243. });
  244. }
  245. Toast.methods.addToast(res.message, 8000);
  246. }
  247. );
  248. },
  249. updateDescription: function() {
  250. let _this = this;
  251. const description = this.editing.description;
  252. if (!validation.isLength(description, 2, 200))
  253. return Toast.methods.addToast(
  254. "Description must have between 2 and 200 characters.",
  255. 8000
  256. );
  257. let characters = description.split("");
  258. characters = characters.filter(character => {
  259. return character.charCodeAt(0) === 21328;
  260. });
  261. if (characters.length !== 0)
  262. return Toast.methods.addToast(
  263. "Invalid description format. Swastika's are not allowed.",
  264. 8000
  265. );
  266. this.socket.emit(
  267. "stations.updateDescription",
  268. this.editing._id,
  269. description,
  270. res => {
  271. if (res.status === "success") {
  272. if (_this.station)
  273. _this.station.description = description;
  274. _this.$parent.stations.forEach((station, index) => {
  275. if (station._id === station._id)
  276. return (_this.$parent.stations[
  277. index
  278. ].description = description);
  279. });
  280. return Toast.methods.addToast(res.message, 4000);
  281. }
  282. Toast.methods.addToast(res.message, 8000);
  283. }
  284. );
  285. },
  286. updatePrivacy: function() {
  287. let _this = this;
  288. this.socket.emit(
  289. "stations.updatePrivacy",
  290. this.editing._id,
  291. this.editing.privacy,
  292. res => {
  293. if (res.status === "success") {
  294. if (_this.station)
  295. _this.station.privacy = _this.editing.privacy;
  296. else {
  297. _this.$parent.stations.forEach((station, index) => {
  298. if (station._id === station._id)
  299. return (_this.$parent.stations[
  300. index
  301. ].privacy = _this.editing.privacy);
  302. });
  303. }
  304. return Toast.methods.addToast(res.message, 4000);
  305. }
  306. Toast.methods.addToast(res.message, 8000);
  307. }
  308. );
  309. },
  310. updateGenres: function() {
  311. let _this = this;
  312. this.socket.emit(
  313. "stations.updateGenres",
  314. this.editing._id,
  315. this.editing.genres,
  316. res => {
  317. if (res.status === "success") {
  318. let genres = JSON.parse(
  319. JSON.stringify(_this.editing.genres)
  320. );
  321. if (_this.station) _this.station.genres = genres;
  322. _this.$parent.stations.forEach((station, index) => {
  323. if (station._id === station._id)
  324. return (_this.$parent.stations[
  325. index
  326. ].genres = genres);
  327. });
  328. return Toast.methods.addToast(res.message, 4000);
  329. }
  330. Toast.methods.addToast(res.message, 8000);
  331. }
  332. );
  333. },
  334. updateBlacklistedGenres: function() {
  335. let _this = this;
  336. this.socket.emit(
  337. "stations.updateBlacklistedGenres",
  338. this.editing._id,
  339. this.editing.blacklistedGenres,
  340. res => {
  341. if (res.status === "success") {
  342. let blacklistedGenres = JSON.parse(
  343. JSON.stringify(_this.editing.blacklistedGenres)
  344. );
  345. if (_this.station)
  346. _this.station.blacklistedGenres = blacklistedGenres;
  347. _this.$parent.stations.forEach((station, index) => {
  348. if (station._id === station._id)
  349. return (_this.$parent.stations[
  350. index
  351. ].blacklistedGenres = blacklistedGenres);
  352. });
  353. return Toast.methods.addToast(res.message, 4000);
  354. }
  355. Toast.methods.addToast(res.message, 8000);
  356. }
  357. );
  358. },
  359. updatePartyMode: function() {
  360. let _this = this;
  361. this.socket.emit(
  362. "stations.updatePartyMode",
  363. this.editing._id,
  364. this.editing.partyMode,
  365. res => {
  366. if (res.status === "success") {
  367. if (_this.station)
  368. _this.station.partyMode = _this.editing.partyMode;
  369. _this.$parent.stations.forEach((station, index) => {
  370. if (station._id === station._id)
  371. return (_this.$parent.stations[
  372. index
  373. ].partyMode = _this.editing.partyMode);
  374. });
  375. return Toast.methods.addToast(res.message, 4000);
  376. }
  377. Toast.methods.addToast(res.message, 8000);
  378. }
  379. );
  380. },
  381. addGenre: function() {
  382. let genre = document
  383. .getElementById(`new-genre-edit`)
  384. .value.toLowerCase()
  385. .trim();
  386. if (this.editing.genres.indexOf(genre) !== -1)
  387. return Toast.methods.addToast("Genre already exists", 3000);
  388. if (genre) {
  389. this.editing.genres.push(genre);
  390. document.getElementById(`new-genre`).value = "";
  391. } else Toast.methods.addToast("Genre cannot be empty", 3000);
  392. },
  393. removeGenre: function(index) {
  394. this.editing.genres.splice(index, 1);
  395. },
  396. addBlacklistedGenre: function() {
  397. let genre = document
  398. .getElementById(`new-blacklisted-genre-edit`)
  399. .value.toLowerCase()
  400. .trim();
  401. if (this.editing.blacklistedGenres.indexOf(genre) !== -1)
  402. return Toast.methods.addToast("Genre already exists", 3000);
  403. if (genre) {
  404. this.editing.blacklistedGenres.push(genre);
  405. document.getElementById(`new-blacklisted-genre`).value = "";
  406. } else Toast.methods.addToast("Genre cannot be empty", 3000);
  407. },
  408. removeBlacklistedGenre: function(index) {
  409. this.editing.blacklistedGenres.splice(index, 1);
  410. },
  411. deleteStation: function() {
  412. this.socket.emit("stations.remove", this.editing._id, res => {
  413. Toast.methods.addToast(res.message, 8000);
  414. });
  415. }
  416. },
  417. components: { Modal }
  418. };
  419. </script>
  420. <style lang="scss" scoped>
  421. .controls {
  422. display: flex;
  423. a {
  424. display: flex;
  425. align-items: center;
  426. }
  427. }
  428. .table {
  429. margin-bottom: 0;
  430. }
  431. h5 {
  432. padding: 20px 0;
  433. }
  434. .party-mode-inner,
  435. .party-mode-outer {
  436. display: flex;
  437. align-items: center;
  438. }
  439. .select:after {
  440. border-color: #029ce3;
  441. }
  442. </style>