Settings.vue 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. <script setup lang="ts">
  2. import Toast from "toasters";
  3. import { storeToRefs } from "pinia";
  4. import { onBeforeUnmount, onMounted, watch } from "vue";
  5. import validation from "@/validation";
  6. import { useWebsocketsStore } from "@/stores/websockets";
  7. import { useUserAuthStore } from "@/stores/userAuth";
  8. import { useEditPlaylistStore } from "@/stores/editPlaylist";
  9. import { useModalsStore } from "@/stores/modals";
  10. import { useForm } from "@/composables/useForm";
  11. const props = defineProps({
  12. modalUuid: { type: String, required: true }
  13. });
  14. const userAuthStore = useUserAuthStore();
  15. const { loggedIn, userId } = storeToRefs(userAuthStore);
  16. const { hasPermission } = userAuthStore;
  17. const { socket } = useWebsocketsStore();
  18. const editPlaylistStore = useEditPlaylistStore(props);
  19. const { playlist } = storeToRefs(editPlaylistStore);
  20. const { preventCloseUnsaved } = useModalsStore();
  21. const isOwner = () =>
  22. loggedIn.value && userId.value === playlist.value.createdBy;
  23. const isEditable = permission =>
  24. ((playlist.value.type === "user" ||
  25. playlist.value.type === "user-liked" ||
  26. playlist.value.type === "user-disliked") &&
  27. (isOwner() || hasPermission(permission))) ||
  28. (playlist.value.type === "genre" &&
  29. permission === "playlists.update.privacy" &&
  30. hasPermission(permission));
  31. const {
  32. inputs: displayNameInputs,
  33. unsavedChanges: displayNameUnsaved,
  34. save: saveDisplayName,
  35. setOriginalValue: setDisplayName
  36. } = useForm(
  37. {
  38. displayName: {
  39. value: playlist.value.displayName,
  40. validate: value => {
  41. if (!validation.isLength(value, 2, 32)) {
  42. const err =
  43. "Display name must have between 2 and 32 characters.";
  44. new Toast(err);
  45. return err;
  46. }
  47. if (!validation.regex.ascii.test(value)) {
  48. const err =
  49. "Invalid display name format. Only ASCII characters are allowed.";
  50. new Toast(err);
  51. return err;
  52. }
  53. return true;
  54. }
  55. }
  56. },
  57. (status, message, values) =>
  58. new Promise((resolve, reject) => {
  59. if (status === "success")
  60. socket.dispatch(
  61. "playlists.updateDisplayName",
  62. playlist.value._id,
  63. values.displayName,
  64. res => {
  65. playlist.value.displayName = values.displayName;
  66. if (res.status === "success") {
  67. resolve();
  68. new Toast(res.message);
  69. } else reject(new Error(res.message));
  70. }
  71. );
  72. else new Toast(message);
  73. }),
  74. {
  75. modalUuid: props.modalUuid,
  76. preventCloseUnsaved: false
  77. }
  78. );
  79. const {
  80. inputs: privacyInputs,
  81. unsavedChanges: privacyUnsaved,
  82. save: savePrivacy,
  83. setOriginalValue: setPrivacy
  84. } = useForm(
  85. { privacy: playlist.value.privacy },
  86. (status, message, values) =>
  87. new Promise((resolve, reject) => {
  88. if (status === "success")
  89. socket.dispatch(
  90. playlist.value.type === "genre"
  91. ? "playlists.updatePrivacyAdmin"
  92. : "playlists.updatePrivacy",
  93. playlist.value._id,
  94. values.privacy,
  95. res => {
  96. playlist.value.privacy = values.privacy;
  97. if (res.status === "success") {
  98. resolve();
  99. new Toast(res.message);
  100. } else reject(new Error(res.message));
  101. }
  102. );
  103. else new Toast(message);
  104. }),
  105. {
  106. modalUuid: props.modalUuid,
  107. preventCloseUnsaved: false
  108. }
  109. );
  110. watch(playlist, (value, oldValue) => {
  111. if (value.displayName !== oldValue.displayName)
  112. setDisplayName({ displayName: value.displayName });
  113. if (value.privacy !== oldValue.privacy)
  114. setPrivacy({ privacy: value.privacy });
  115. });
  116. onMounted(() => {
  117. preventCloseUnsaved[props.modalUuid] = () =>
  118. displayNameUnsaved.value.length + privacyUnsaved.value.length > 0;
  119. });
  120. onBeforeUnmount(() => {
  121. delete preventCloseUnsaved[props.modalUuid];
  122. });
  123. </script>
  124. <template>
  125. <div class="settings-tab section">
  126. <div
  127. v-if="
  128. isEditable('playlists.update.displayName') &&
  129. !(
  130. playlist.type === 'user-liked' ||
  131. playlist.type === 'user-disliked'
  132. )
  133. "
  134. >
  135. <label class="label"> Change display name </label>
  136. <div class="control is-grouped input-with-button">
  137. <p class="control is-expanded">
  138. <input
  139. v-model="displayNameInputs['displayName'].value"
  140. class="input"
  141. type="text"
  142. placeholder="Playlist Display Name"
  143. @keyup.enter="saveDisplayName()"
  144. />
  145. </p>
  146. <p class="control">
  147. <button
  148. class="button is-info"
  149. @click.prevent="saveDisplayName()"
  150. >
  151. Rename
  152. </button>
  153. </p>
  154. </div>
  155. </div>
  156. <div v-if="isEditable('playlists.update.privacy')">
  157. <label class="label"> Change privacy </label>
  158. <div class="control is-grouped input-with-button">
  159. <div class="control is-expanded select">
  160. <select v-model="privacyInputs['privacy'].value">
  161. <option value="private">Private</option>
  162. <option value="public">Public</option>
  163. </select>
  164. </div>
  165. <p class="control">
  166. <button
  167. class="button is-info"
  168. @click.prevent="savePrivacy()"
  169. >
  170. Update Privacy
  171. </button>
  172. </p>
  173. </div>
  174. </div>
  175. </div>
  176. </template>
  177. <style lang="less" scoped>
  178. @media screen and (max-width: 1300px) {
  179. .section {
  180. max-width: 100% !important;
  181. }
  182. }
  183. </style>