Settings.vue 14 KB

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