1
0

Settings.vue 13 KB

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