Settings.vue 14 KB

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