Settings.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  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. v-if="station.type === 'community'"
  147. class="button-wrapper"
  148. theme="addToPlaylist"
  149. interactive="true"
  150. placement="bottom"
  151. trigger="click"
  152. append-to="parent"
  153. >
  154. <template #trigger>
  155. <button
  156. :class="{
  157. blue: !station.partyMode,
  158. yellow: station.partyMode
  159. }"
  160. >
  161. <i class="material-icons">{{
  162. station.partyMode
  163. ? "emoji_people"
  164. : "playlist_play"
  165. }}</i>
  166. {{ station.partyMode ? "Party" : "Playlist" }}
  167. </button>
  168. </template>
  169. <button
  170. class="blue"
  171. v-if="station.partyMode"
  172. @click="updatePartyMode(false)"
  173. >
  174. <i class="material-icons">playlist_play</i>
  175. Playlist
  176. </button>
  177. <button
  178. class="yellow"
  179. v-if="!station.partyMode"
  180. @click="updatePartyMode(true)"
  181. >
  182. <i class="material-icons">emoji_people</i>
  183. Party
  184. </button>
  185. </tippy>
  186. <div v-else class="button-wrapper">
  187. <button
  188. class="blue"
  189. content="Can not be changed on official stations."
  190. v-tippy="{ theme: 'info' }"
  191. >
  192. <i class="material-icons">playlist_play</i>
  193. Playlist
  194. </button>
  195. </div>
  196. </div>
  197. <div v-if="!station.partyMode" class="small-section">
  198. <label class="label">Play Mode</label>
  199. <tippy
  200. v-if="station.type === 'community'"
  201. class="button-wrapper"
  202. theme="addToPlaylist"
  203. interactive="true"
  204. placement="bottom"
  205. trigger="click"
  206. append-to="parent"
  207. >
  208. <template #trigger>
  209. <button class="blue">
  210. <i class="material-icons">{{
  211. station.playMode === "random"
  212. ? "shuffle"
  213. : "format_list_numbered"
  214. }}</i>
  215. {{
  216. station.playMode === "random"
  217. ? "Random"
  218. : "Sequential"
  219. }}
  220. </button>
  221. </template>
  222. <button
  223. class="blue"
  224. v-if="station.playMode === 'sequential'"
  225. @click="updatePlayMode('random')"
  226. >
  227. <i class="material-icons">shuffle</i>
  228. Random
  229. </button>
  230. <button
  231. class="blue"
  232. v-if="station.playMode === 'random'"
  233. @click="updatePlayMode('sequential')"
  234. >
  235. <i class="material-icons">format_list_numbered</i>
  236. Sequential
  237. </button>
  238. </tippy>
  239. <div v-else class="button-wrapper">
  240. <button
  241. class="blue"
  242. content="Can not be changed on official stations."
  243. v-tippy="{ theme: 'info' }"
  244. >
  245. <i class="material-icons">shuffle</i>
  246. Random
  247. </button>
  248. </div>
  249. </div>
  250. <div
  251. v-if="
  252. station.type === 'community' && station.partyMode === true
  253. "
  254. class="small-section"
  255. >
  256. <label class="label">Queue lock</label>
  257. <tippy
  258. class="button-wrapper"
  259. theme="addToPlaylist"
  260. interactive="true"
  261. placement="bottom"
  262. trigger="click"
  263. append-to="parent"
  264. >
  265. <template #trigger>
  266. <button
  267. :class="{
  268. green: station.locked,
  269. red: !station.locked
  270. }"
  271. >
  272. <i class="material-icons">{{
  273. station.locked ? "lock" : "lock_open"
  274. }}</i>
  275. {{ station.locked ? "Locked" : "Unlocked" }}
  276. </button>
  277. </template>
  278. <button
  279. class="green"
  280. v-if="!station.locked"
  281. @click="updateQueueLock(true)"
  282. >
  283. <i class="material-icons">lock</i>
  284. Locked
  285. </button>
  286. <button
  287. class="red"
  288. v-if="station.locked"
  289. @click="updateQueueLock(false)"
  290. >
  291. <i class="material-icons">lock_open</i>
  292. Unlocked
  293. </button>
  294. </tippy>
  295. </div>
  296. </div>
  297. </div>
  298. </template>
  299. <script>
  300. import { mapState, mapGetters } from "vuex";
  301. import Toast from "toasters";
  302. import validation from "@/validation";
  303. export default {
  304. data() {
  305. return {
  306. privacyButtons: {
  307. public: {
  308. style: "green",
  309. iconName: "public"
  310. },
  311. private: {
  312. style: "red",
  313. iconName: "lock"
  314. },
  315. unlisted: {
  316. style: "orange",
  317. iconName: "link"
  318. }
  319. }
  320. };
  321. },
  322. computed: {
  323. ...mapState("modals/manageStation", {
  324. station: state => state.station,
  325. originalStation: state => state.originalStation
  326. }),
  327. ...mapGetters({
  328. socket: "websockets/getSocket"
  329. })
  330. },
  331. methods: {
  332. updateName() {
  333. if (this.originalStation.name !== this.station.name) {
  334. const { name } = this.station;
  335. if (!validation.isLength(name, 2, 16)) {
  336. new Toast("Name must have between 2 and 16 characters.");
  337. } else if (!validation.regex.az09_.test(name)) {
  338. new Toast(
  339. "Invalid name format. Allowed characters: a-z, 0-9 and _."
  340. );
  341. } else {
  342. this.socket.dispatch(
  343. "stations.updateName",
  344. this.station._id,
  345. name,
  346. res => {
  347. new Toast(res.message);
  348. if (res.status === "success") {
  349. this.station.name = name;
  350. this.originalStation.name = name;
  351. }
  352. }
  353. );
  354. }
  355. } else {
  356. new Toast("Please make a change before saving.");
  357. }
  358. },
  359. updateDisplayName() {
  360. if (this.originalStation.displayName !== this.station.displayName) {
  361. const { displayName } = this.station;
  362. if (!validation.isLength(displayName, 2, 32)) {
  363. new Toast(
  364. "Display name must have between 2 and 32 characters."
  365. );
  366. } else if (!validation.regex.ascii.test(displayName)) {
  367. new Toast(
  368. "Invalid display name format. Only ASCII characters are allowed."
  369. );
  370. } else {
  371. this.socket.dispatch(
  372. "stations.updateDisplayName",
  373. this.station._id,
  374. displayName,
  375. res => {
  376. new Toast(res.message);
  377. if (res.status === "success") {
  378. this.station.displayName = displayName;
  379. this.originalStation.displayName = displayName;
  380. }
  381. }
  382. );
  383. }
  384. } else {
  385. new Toast("Please make a change before saving.");
  386. }
  387. },
  388. updateDescription() {
  389. if (this.originalStation.description !== this.station.description) {
  390. const { description } = this.station;
  391. const characters = description.split("").filter(character => {
  392. return character.charCodeAt(0) === 21328;
  393. });
  394. if (!validation.isLength(description, 2, 200)) {
  395. new Toast(
  396. "Description must have between 2 and 200 characters."
  397. );
  398. } else if (characters.length !== 0) {
  399. new Toast("Invalid description format.");
  400. } else {
  401. this.socket.dispatch(
  402. "stations.updateDescription",
  403. this.station._id,
  404. description,
  405. res => {
  406. new Toast(res.message);
  407. if (res.status === "success") {
  408. this.station.description = description;
  409. this.originalStation.description = description;
  410. }
  411. }
  412. );
  413. }
  414. } else {
  415. new Toast("Please make a change before saving.");
  416. }
  417. },
  418. updateTheme(theme) {
  419. if (this.station.theme !== theme) {
  420. this.socket.dispatch(
  421. "stations.updateTheme",
  422. this.station._id,
  423. theme,
  424. res => {
  425. new Toast(res.message);
  426. if (res.status === "success") {
  427. this.station.theme = theme;
  428. this.originalStation.theme = theme;
  429. }
  430. }
  431. );
  432. }
  433. },
  434. updatePrivacy(privacy) {
  435. if (this.station.privacy !== privacy) {
  436. this.socket.dispatch(
  437. "stations.updatePrivacy",
  438. this.station._id,
  439. privacy,
  440. res => {
  441. new Toast(res.message);
  442. if (res.status === "success") {
  443. this.station.privacy = privacy;
  444. this.originalStation.privacy = privacy;
  445. }
  446. }
  447. );
  448. }
  449. },
  450. updatePartyMode(partyMode) {
  451. if (this.station.partyMode !== partyMode) {
  452. this.socket.dispatch(
  453. "stations.updatePartyMode",
  454. this.station._id,
  455. partyMode,
  456. res => {
  457. new Toast(res.message);
  458. if (res.status === "success") {
  459. this.station.partyMode = partyMode;
  460. this.originalStation.partyMode = partyMode;
  461. }
  462. }
  463. );
  464. }
  465. },
  466. updatePlayMode(playMode) {
  467. if (this.station.playMode !== playMode) {
  468. this.socket.dispatch(
  469. "stations.updatePlayMode",
  470. this.station._id,
  471. playMode,
  472. res => {
  473. new Toast(res.message);
  474. if (res.status === "success") {
  475. this.station.playMode = playMode;
  476. this.originalStation.playMode = playMode;
  477. }
  478. }
  479. );
  480. }
  481. },
  482. updateQueueLock(locked) {
  483. if (this.station.locked !== locked) {
  484. this.socket.dispatch(
  485. "stations.toggleLock",
  486. this.station._id,
  487. res => {
  488. if (res.status === "success") {
  489. if (this.originalStation) {
  490. this.station.locked = res.data.locked;
  491. this.originalStation.locked = res.data.locked;
  492. }
  493. new Toast(
  494. `Toggled queue lock successfully to ${res.data.locked}`
  495. );
  496. } else {
  497. new Toast("Failed to toggle queue lock.");
  498. }
  499. }
  500. );
  501. }
  502. }
  503. }
  504. };
  505. </script>
  506. <style lang="scss" scoped>
  507. .station-settings {
  508. .settings-buttons {
  509. display: flex;
  510. justify-content: center;
  511. flex-wrap: wrap;
  512. .small-section {
  513. width: calc(50% - 10px);
  514. min-width: 150px;
  515. margin: 5px auto;
  516. }
  517. }
  518. .button-wrapper {
  519. display: flex;
  520. flex-direction: column;
  521. button {
  522. width: 100%;
  523. height: 36px;
  524. border: 0;
  525. border-radius: 3px;
  526. font-size: 18px;
  527. color: var(--white);
  528. box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.25);
  529. display: block;
  530. text-align: center;
  531. justify-content: center;
  532. display: inline-flex;
  533. -ms-flex-align: center;
  534. align-items: center;
  535. -moz-user-select: none;
  536. user-select: none;
  537. cursor: pointer;
  538. padding: 0;
  539. text-transform: capitalize;
  540. &.red {
  541. background-color: var(--red);
  542. }
  543. &.green {
  544. background-color: var(--green);
  545. }
  546. &.blue {
  547. background-color: var(--blue);
  548. }
  549. &.orange {
  550. background-color: var(--orange);
  551. }
  552. &.yellow {
  553. background-color: var(--yellow);
  554. }
  555. &.purple {
  556. background-color: var(--purple);
  557. }
  558. &.teal {
  559. background-color: var(--teal);
  560. }
  561. i {
  562. font-size: 20px;
  563. margin-right: 4px;
  564. }
  565. }
  566. }
  567. }
  568. </style>