Explorar o código

refactor: Moved openModal data intake from pinia stores to component props

Owen Diffey %!s(int64=2) %!d(string=hai) anos
pai
achega
ba83bb5838
Modificáronse 53 ficheiros con 266 adicións e 719 borrados
  1. 3 3
      frontend/src/App.vue
  2. 2 2
      frontend/src/components/ActivityItem.vue
  3. 2 1
      frontend/src/components/ModalManager.vue
  4. 7 7
      frontend/src/components/PlaylistTabBase.vue
  5. 3 3
      frontend/src/components/SongItem.vue
  6. 1 1
      frontend/src/components/StationInfoBox.vue
  7. 9 15
      frontend/src/components/modals/BulkActions.vue
  8. 5 14
      frontend/src/components/modals/Confirm.vue
  9. 1 1
      frontend/src/components/modals/CreatePlaylist.vue
  10. 4 13
      frontend/src/components/modals/CreateStation.vue
  11. 9 16
      frontend/src/components/modals/EditNews.vue
  12. 3 3
      frontend/src/components/modals/EditPlaylist/Tabs/AddSongs.vue
  13. 4 3
      frontend/src/components/modals/EditPlaylist/index.vue
  14. 18 29
      frontend/src/components/modals/EditSong/index.vue
  15. 20 40
      frontend/src/components/modals/EditUser.vue
  16. 6 3
      frontend/src/components/modals/ImportAlbum.vue
  17. 8 1
      frontend/src/components/modals/ManageStation/index.vue
  18. 3 7
      frontend/src/components/modals/RemoveAccount.vue
  19. 6 21
      frontend/src/components/modals/Report.vue
  20. 23 23
      frontend/src/components/modals/ViewApiRequest.vue
  21. 11 17
      frontend/src/components/modals/ViewPunishment.vue
  22. 10 16
      frontend/src/components/modals/ViewReport.vue
  23. 16 32
      frontend/src/components/modals/ViewYoutubeVideo.vue
  24. 4 13
      frontend/src/components/modals/WhatIsNew.vue
  25. 1 1
      frontend/src/composables/useForm.ts
  26. 2 2
      frontend/src/pages/Admin/News.vue
  27. 1 1
      frontend/src/pages/Admin/Playlists.vue
  28. 1 1
      frontend/src/pages/Admin/Reports.vue
  29. 11 25
      frontend/src/pages/Admin/Songs/Import.vue
  30. 23 36
      frontend/src/pages/Admin/Songs/index.vue
  31. 2 2
      frontend/src/pages/Admin/Stations.vue
  32. 1 1
      frontend/src/pages/Admin/Users/Punishments.vue
  33. 1 1
      frontend/src/pages/Admin/Users/index.vue
  34. 24 33
      frontend/src/pages/Admin/YouTube/Videos.vue
  35. 1 1
      frontend/src/pages/Admin/YouTube/index.vue
  36. 4 4
      frontend/src/pages/Home.vue
  37. 2 2
      frontend/src/pages/Profile/Tabs/Playlists.vue
  38. 1 1
      frontend/src/pages/Settings/Tabs/Account.vue
  39. 0 13
      frontend/src/stores/bulkActions.ts
  40. 0 25
      frontend/src/stores/confirm.ts
  41. 0 13
      frontend/src/stores/createStation.ts
  42. 0 17
      frontend/src/stores/editNews.ts
  43. 0 5
      frontend/src/stores/editPlaylist.ts
  44. 0 21
      frontend/src/stores/editUser.ts
  45. 0 4
      frontend/src/stores/importAlbum.ts
  46. 13 84
      frontend/src/stores/modals.ts
  47. 0 15
      frontend/src/stores/removeAccount.ts
  48. 0 16
      frontend/src/stores/report.ts
  49. 0 37
      frontend/src/stores/viewApiRequest.ts
  50. 0 27
      frontend/src/stores/viewPunishment.ts
  51. 0 15
      frontend/src/stores/viewReport.ts
  52. 0 16
      frontend/src/stores/viewYoutubeVideo.ts
  53. 0 16
      frontend/src/stores/whatIsNew.ts

+ 3 - 3
frontend/src/App.vue

@@ -209,7 +209,7 @@ onMounted(async () => {
 
 
 			if (news) {
 			if (news) {
 				if (newUser) {
 				if (newUser) {
-					openModal({ modal: "whatIsNew", data: { news } });
+					openModal({ modal: "whatIsNew", props: { news } });
 				} else if (localStorage.getItem("whatIsNew")) {
 				} else if (localStorage.getItem("whatIsNew")) {
 					if (
 					if (
 						parseInt(localStorage.getItem("whatIsNew") as string) <
 						parseInt(localStorage.getItem("whatIsNew") as string) <
@@ -217,7 +217,7 @@ onMounted(async () => {
 					) {
 					) {
 						openModal({
 						openModal({
 							modal: "whatIsNew",
 							modal: "whatIsNew",
-							data: { news }
+							props: { news }
 						});
 						});
 						localStorage.setItem(
 						localStorage.setItem(
 							"whatIsNew",
 							"whatIsNew",
@@ -233,7 +233,7 @@ onMounted(async () => {
 					)
 					)
 						openModal({
 						openModal({
 							modal: "whatIsNew",
 							modal: "whatIsNew",
-							data: { news }
+							props: { news }
 						});
 						});
 					localStorage.setItem(
 					localStorage.setItem(
 						"whatIsNew",
 						"whatIsNew",

+ 2 - 2
frontend/src/components/ActivityItem.vue

@@ -126,7 +126,7 @@ onMounted(() => {
 						@click="
 						@click="
 							openModal({
 							openModal({
 								modal: 'viewReport',
 								modal: 'viewReport',
-								data: { reportId: activity.payload.reportId }
+								props: { reportId: activity.payload.reportId }
 							})
 							})
 						"
 						"
 						>report</a
 						>report</a
@@ -139,7 +139,7 @@ onMounted(() => {
 						@click="
 						@click="
 							openModal({
 							openModal({
 								modal: 'editPlaylist',
 								modal: 'editPlaylist',
-								data: {
+								props: {
 									playlistId: activity.payload.playlistId
 									playlistId: activity.payload.playlistId
 								}
 								}
 							})
 							})

+ 2 - 1
frontend/src/components/ModalManager.vue

@@ -35,8 +35,9 @@ const modalComponents = shallowRef(
 	<div>
 	<div>
 		<div v-for="activeModalUuid in activeModals" :key="activeModalUuid">
 		<div v-for="activeModalUuid in activeModals" :key="activeModalUuid">
 			<component
 			<component
-				:is="modalComponents[modals[activeModalUuid]]"
+				:is="modalComponents[modals[activeModalUuid].modal]"
 				:modal-uuid="activeModalUuid"
 				:modal-uuid="activeModalUuid"
+				v-bind="modals[activeModalUuid].props"
 			/>
 			/>
 		</div>
 		</div>
 	</div>
 	</div>

+ 7 - 7
frontend/src/components/PlaylistTabBase.vue

@@ -492,7 +492,7 @@ onMounted(() => {
 								@click="
 								@click="
 									openModal({
 									openModal({
 										modal: 'editPlaylist',
 										modal: 'editPlaylist',
-										data: {
+										props: {
 											playlistId: featuredPlaylist._id
 											playlistId: featuredPlaylist._id
 										}
 										}
 									})
 									})
@@ -511,7 +511,7 @@ onMounted(() => {
 								@click="
 								@click="
 									openModal({
 									openModal({
 										modal: 'editPlaylist',
 										modal: 'editPlaylist',
-										data: {
+										props: {
 											playlistId: featuredPlaylist._id
 											playlistId: featuredPlaylist._id
 										}
 										}
 									})
 									})
@@ -676,7 +676,7 @@ onMounted(() => {
 								@click="
 								@click="
 									openModal({
 									openModal({
 										modal: 'editPlaylist',
 										modal: 'editPlaylist',
-										data: { playlistId: playlist._id }
+										props: { playlistId: playlist._id }
 									})
 									})
 								"
 								"
 								class="material-icons edit-icon"
 								class="material-icons edit-icon"
@@ -693,7 +693,7 @@ onMounted(() => {
 								@click="
 								@click="
 									openModal({
 									openModal({
 										modal: 'editPlaylist',
 										modal: 'editPlaylist',
-										data: { playlistId: playlist._id }
+										props: { playlistId: playlist._id }
 									})
 									})
 								"
 								"
 								class="material-icons edit-icon"
 								class="material-icons edit-icon"
@@ -758,7 +758,7 @@ onMounted(() => {
 								@click="
 								@click="
 									openModal({
 									openModal({
 										modal: 'editPlaylist',
 										modal: 'editPlaylist',
-										data: { playlistId: playlist._id }
+										props: { playlistId: playlist._id }
 									})
 									})
 								"
 								"
 								class="material-icons edit-icon"
 								class="material-icons edit-icon"
@@ -775,7 +775,7 @@ onMounted(() => {
 								@click="
 								@click="
 									openModal({
 									openModal({
 										modal: 'editPlaylist',
 										modal: 'editPlaylist',
-										data: { playlistId: playlist._id }
+										props: { playlistId: playlist._id }
 									})
 									})
 								"
 								"
 								class="material-icons edit-icon"
 								class="material-icons edit-icon"
@@ -949,7 +949,7 @@ onMounted(() => {
 										@click="
 										@click="
 											openModal({
 											openModal({
 												modal: 'editPlaylist',
 												modal: 'editPlaylist',
-												data: {
+												props: {
 													playlistId: element._id
 													playlistId: element._id
 												}
 												}
 											})
 											})

+ 3 - 3
frontend/src/components/SongItem.vue

@@ -93,14 +93,14 @@ const hoverTippy = () => {
 
 
 const report = song => {
 const report = song => {
 	hideTippyElements();
 	hideTippyElements();
-	openModal({ modal: "report", data: { song } });
+	openModal({ modal: "report", props: { song } });
 };
 };
 
 
 const edit = song => {
 const edit = song => {
 	hideTippyElements();
 	hideTippyElements();
 	openModal({
 	openModal({
 		modal: "editSong",
 		modal: "editSong",
-		data: { song }
+		props: { song }
 	});
 	});
 };
 };
 
 
@@ -204,7 +204,7 @@ onUnmounted(() => {
 								@click="
 								@click="
 									openModal({
 									openModal({
 										modal: 'viewYoutubeVideo',
 										modal: 'viewYoutubeVideo',
-										data: {
+										props: {
 											youtubeId: song.youtubeId
 											youtubeId: song.youtubeId
 										}
 										}
 									})
 									})

+ 1 - 1
frontend/src/components/StationInfoBox.vue

@@ -143,7 +143,7 @@ const unfavoriteStation = () => {
 				@click="
 				@click="
 					openModal({
 					openModal({
 						modal: 'manageStation',
 						modal: 'manageStation',
-						data: {
+						props: {
 							stationId: station._id,
 							stationId: station._id,
 							sector: 'station'
 							sector: 'station'
 						}
 						}

+ 9 - 15
frontend/src/components/modals/BulkActions.vue

@@ -1,10 +1,8 @@
 <script setup lang="ts">
 <script setup lang="ts">
 import { ref, defineAsyncComponent, onMounted, onBeforeUnmount } from "vue";
 import { ref, defineAsyncComponent, onMounted, onBeforeUnmount } from "vue";
 import Toast from "toasters";
 import Toast from "toasters";
-import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useLongJobsStore } from "@/stores/longJobs";
 import { useLongJobsStore } from "@/stores/longJobs";
-import { useBulkActionsStore } from "@/stores/bulkActions";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
 
 
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
@@ -13,7 +11,8 @@ const AutoSuggest = defineAsyncComponent(
 );
 );
 
 
 const props = defineProps({
 const props = defineProps({
-	modalUuid: { type: String, required: true }
+	modalUuid: { type: String, required: true },
+	type: { type: Object, required: true }
 });
 });
 
 
 const { closeCurrentModal } = useModalsStore();
 const { closeCurrentModal } = useModalsStore();
@@ -22,9 +21,6 @@ const { setJob } = useLongJobsStore();
 
 
 const { socket } = useWebsocketsStore();
 const { socket } = useWebsocketsStore();
 
 
-const bulkActionsStore = useBulkActionsStore(props);
-const { type } = storeToRefs(bulkActionsStore);
-
 const method = ref("add");
 const method = ref("add");
 const items = ref([]);
 const items = ref([]);
 const itemInput = ref();
 const itemInput = ref();
@@ -32,10 +28,10 @@ const allItems = ref([]);
 
 
 const addItem = () => {
 const addItem = () => {
 	if (!itemInput.value) return;
 	if (!itemInput.value) return;
-	if (type.value.regex && !type.value.regex.test(itemInput.value)) {
-		new Toast(`Invalid ${type.value.name} format.`);
+	if (props.type.regex && !props.type.regex.test(itemInput.value)) {
+		new Toast(`Invalid ${props.type.name} format.`);
 	} else if (items.value.includes(itemInput.value)) {
 	} else if (items.value.includes(itemInput.value)) {
-		new Toast(`Duplicate ${type.value.name} specified.`);
+		new Toast(`Duplicate ${props.type.name} specified.`);
 	} else {
 	} else {
 		items.value.push(itemInput.value);
 		items.value.push(itemInput.value);
 		itemInput.value = null;
 		itemInput.value = null;
@@ -51,10 +47,10 @@ const applyChanges = () => {
 	let title;
 	let title;
 
 
 	socket.dispatch(
 	socket.dispatch(
-		type.value.action,
+		props.type.action,
 		method.value,
 		method.value,
 		items.value,
 		items.value,
-		type.value.items,
+		props.type.items,
 		{
 		{
 			cb: () => {},
 			cb: () => {},
 			onProgress: res => {
 			onProgress: res => {
@@ -78,14 +74,12 @@ const applyChanges = () => {
 onBeforeUnmount(() => {
 onBeforeUnmount(() => {
 	itemInput.value = null;
 	itemInput.value = null;
 	items.value = [];
 	items.value = [];
-	// Delete the Pinia store that was created for this modal, after all other cleanup tasks are performed
-	bulkActionsStore.$dispose();
 });
 });
 
 
 onMounted(() => {
 onMounted(() => {
 	socket.onConnect(() => {
 	socket.onConnect(() => {
-		if (type.value.autosuggest && type.value.autosuggestDataAction)
-			socket.dispatch(type.value.autosuggestDataAction, res => {
+		if (props.type.autosuggest && props.type.autosuggestDataAction)
+			socket.dispatch(props.type.autosuggestDataAction, res => {
 				if (res.status === "success") {
 				if (res.status === "success") {
 					const { items } = res.data;
 					const { items } = res.data;
 					allItems.value = items;
 					allItems.value = items;

+ 5 - 14
frontend/src/components/modals/Confirm.vue

@@ -1,30 +1,21 @@
 <script setup lang="ts">
 <script setup lang="ts">
-import { defineAsyncComponent, onBeforeUnmount } from "vue";
-import { storeToRefs } from "pinia";
-import { useConfirmStore } from "@/stores/confirm";
+import { defineAsyncComponent } from "vue";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
 
 
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 
 
 const props = defineProps({
 const props = defineProps({
-	modalUuid: { type: String, required: true }
+	modalUuid: { type: String, required: true },
+	message: { type: String || Array, required: true },
+	onCompleted: { type: Function, required: true }
 });
 });
 
 
-const confirmStore = useConfirmStore(props);
-const { message } = storeToRefs(confirmStore);
-const { confirm } = confirmStore;
-
 const { closeCurrentModal } = useModalsStore();
 const { closeCurrentModal } = useModalsStore();
 
 
 const confirmAction = () => {
 const confirmAction = () => {
-	confirm();
+	props.onCompleted();
 	closeCurrentModal();
 	closeCurrentModal();
 };
 };
-
-onBeforeUnmount(() => {
-	// Delete the Pinia store that was created for this modal, after all other cleanup tasks are performed
-	confirmStore.$dispose();
-});
 </script>
 </script>
 
 
 <template>
 <template>

+ 1 - 1
frontend/src/components/modals/CreatePlaylist.vue

@@ -40,7 +40,7 @@ const createPlaylist = () => {
 			if (!window.addToPlaylistDropdown) {
 			if (!window.addToPlaylistDropdown) {
 				openModal({
 				openModal({
 					modal: "editPlaylist",
 					modal: "editPlaylist",
-					data: { playlistId: res.data.playlistId }
+					props: { playlistId: res.data.playlistId }
 				});
 				});
 			}
 			}
 		}
 		}

+ 4 - 13
frontend/src/components/modals/CreateStation.vue

@@ -1,23 +1,19 @@
 <script setup lang="ts">
 <script setup lang="ts">
-import { defineAsyncComponent, ref, onBeforeUnmount } from "vue";
+import { defineAsyncComponent, ref } from "vue";
 import Toast from "toasters";
 import Toast from "toasters";
-import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
-import { useCreateStationStore } from "@/stores/createStation";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
 import validation from "@/validation";
 import validation from "@/validation";
 
 
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 
 
 const props = defineProps({
 const props = defineProps({
-	modalUuid: { type: String, required: true }
+	modalUuid: { type: String, required: true },
+	official: { type: Boolean, default: false }
 });
 });
 
 
 const { socket } = useWebsocketsStore();
 const { socket } = useWebsocketsStore();
 
 
-const createStationStore = useCreateStationStore(props);
-const { official } = storeToRefs(createStationStore);
-
 const { closeCurrentModal } = useModalsStore();
 const { closeCurrentModal } = useModalsStore();
 
 
 const newStation = ref({
 const newStation = ref({
@@ -64,7 +60,7 @@ const submitModal = () => {
 		"stations.create",
 		"stations.create",
 		{
 		{
 			name,
 			name,
-			type: official.value ? "official" : "community",
+			type: props.official ? "official" : "community",
 			displayName,
 			displayName,
 			description
 			description
 		},
 		},
@@ -76,11 +72,6 @@ const submitModal = () => {
 		}
 		}
 	);
 	);
 };
 };
-
-onBeforeUnmount(() => {
-	// Delete the Pinia store that was created for this modal, after all other cleanup tasks are performed
-	createStationStore.$dispose();
-});
 </script>
 </script>
 
 
 <template>
 <template>

+ 9 - 16
frontend/src/components/modals/EditNews.vue

@@ -1,14 +1,12 @@
 <script setup lang="ts">
 <script setup lang="ts">
-import { defineAsyncComponent, ref, onMounted, onBeforeUnmount } from "vue";
+import { defineAsyncComponent, ref, onMounted } from "vue";
 import { marked } from "marked";
 import { marked } from "marked";
 import DOMPurify from "dompurify";
 import DOMPurify from "dompurify";
 import Toast from "toasters";
 import Toast from "toasters";
 import { formatDistance } from "date-fns";
 import { formatDistance } from "date-fns";
-import { storeToRefs } from "pinia";
 import { GetNewsResponse } from "@musare_types/actions/NewsActions";
 import { GetNewsResponse } from "@musare_types/actions/NewsActions";
 import { GenericResponse } from "@musare_types/actions/GenericActions";
 import { GenericResponse } from "@musare_types/actions/GenericActions";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
-import { useEditNewsStore } from "@/stores/editNews";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
 import { useForm } from "@/composables/useForm";
 import { useForm } from "@/composables/useForm";
 
 
@@ -21,14 +19,14 @@ const UserLink = defineAsyncComponent(
 );
 );
 
 
 const props = defineProps({
 const props = defineProps({
-	modalUuid: { type: String, required: true }
+	modalUuid: { type: String, required: true },
+	createNews: { type: Boolean, default: false },
+	newsId: { type: String, default: null },
+	sector: { type: String, default: "admin" }
 });
 });
 
 
 const { socket } = useWebsocketsStore();
 const { socket } = useWebsocketsStore();
 
 
-const editNewsStore = useEditNewsStore(props);
-const { createNews, newsId } = storeToRefs(editNewsStore);
-
 const { closeCurrentModal } = useModalsStore();
 const { closeCurrentModal } = useModalsStore();
 
 
 const createdBy = ref();
 const createdBy = ref();
@@ -83,8 +81,8 @@ const { inputs, save, setOriginalValue } = useForm(
 				if (res.status === "success") resolve();
 				if (res.status === "success") resolve();
 				else reject(new Error(res.message));
 				else reject(new Error(res.message));
 			};
 			};
-			if (createNews.value) socket.dispatch("news.create", data, cb);
-			else socket.dispatch("news.update", newsId.value, data, cb);
+			if (props.createNews) socket.dispatch("news.create", data, cb);
+			else socket.dispatch("news.update", props.newsId, data, cb);
 		} else {
 		} else {
 			if (status === "unchanged") new Toast(messages.unchanged);
 			if (status === "unchanged") new Toast(messages.unchanged);
 			else if (status === "error")
 			else if (status === "error")
@@ -99,11 +97,6 @@ const { inputs, save, setOriginalValue } = useForm(
 	}
 	}
 );
 );
 
 
-onBeforeUnmount(() => {
-	// Delete the Pinia store that was created for this modal, after all other cleanup tasks are performed
-	editNewsStore.$dispose();
-});
-
 onMounted(() => {
 onMounted(() => {
 	marked.use({
 	marked.use({
 		renderer: {
 		renderer: {
@@ -117,10 +110,10 @@ onMounted(() => {
 	});
 	});
 
 
 	socket.onConnect(() => {
 	socket.onConnect(() => {
-		if (newsId.value && !createNews.value) {
+		if (props.newsId && !props.createNews) {
 			socket.dispatch(
 			socket.dispatch(
 				`news.getNewsFromId`,
 				`news.getNewsFromId`,
-				newsId.value,
+				props.newsId,
 				(res: GetNewsResponse) => {
 				(res: GetNewsResponse) => {
 					if (res.status === "success") {
 					if (res.status === "success") {
 						setOriginalValue({
 						setOriginalValue({

+ 3 - 3
frontend/src/components/modals/EditPlaylist/Tabs/AddSongs.vue

@@ -17,7 +17,7 @@ const props = defineProps({
 });
 });
 
 
 const editPlaylistStore = useEditPlaylistStore(props);
 const editPlaylistStore = useEditPlaylistStore(props);
-const { playlistId, playlist } = storeToRefs(editPlaylistStore);
+const { playlist } = storeToRefs(editPlaylistStore);
 
 
 const sitename = ref("Musare");
 const sitename = ref("Musare");
 
 
@@ -141,7 +141,7 @@ onMounted(async () => {
 								v-tippy
 								v-tippy
 								@click="
 								@click="
 									addMusareSongToPlaylist(
 									addMusareSongToPlaylist(
-										playlistId,
+										playlist._id,
 										song.youtubeId,
 										song.youtubeId,
 										index
 										index
 									)
 									)
@@ -216,7 +216,7 @@ onMounted(async () => {
 								v-tippy
 								v-tippy
 								@click="
 								@click="
 									addYouTubeSongToPlaylist(
 									addYouTubeSongToPlaylist(
-										playlistId,
+										playlist._id,
 										result.id,
 										result.id,
 										index
 										index
 									)
 									)

+ 4 - 3
frontend/src/components/modals/EditPlaylist/index.vue

@@ -30,7 +30,8 @@ const QuickConfirm = defineAsyncComponent(
 );
 );
 
 
 const props = defineProps({
 const props = defineProps({
-	modalUuid: { type: String, required: true }
+	modalUuid: { type: String, required: true },
+	playlistId: { type: String, required: true }
 });
 });
 
 
 const { socket } = useWebsocketsStore();
 const { socket } = useWebsocketsStore();
@@ -54,7 +55,7 @@ const playlistSongs = computed({
 	}
 	}
 });
 });
 
 
-const { playlistId, tab, playlist } = storeToRefs(editPlaylistStore);
+const { tab, playlist } = storeToRefs(editPlaylistStore);
 const { setPlaylist, clearPlaylist, addSong, removeSong, repositionedSong } =
 const { setPlaylist, clearPlaylist, addSong, removeSong, repositionedSong } =
 	editPlaylistStore;
 	editPlaylistStore;
 
 
@@ -248,7 +249,7 @@ const clearAndRefillGenrePlaylist = () => {
 onMounted(() => {
 onMounted(() => {
 	socket.onConnect(() => {
 	socket.onConnect(() => {
 		gettingSongs.value = true;
 		gettingSongs.value = true;
-		socket.dispatch("playlists.getPlaylist", playlistId.value, res => {
+		socket.dispatch("playlists.getPlaylist", props.playlistId, res => {
 			if (res.status === "success") {
 			if (res.status === "success") {
 				setPlaylist(res.data.playlist);
 				setPlaylist(res.data.playlist);
 			} else new Toast(res.message);
 			} else new Toast(res.message);

+ 18 - 29
frontend/src/components/modals/EditSong/index.vue

@@ -50,7 +50,9 @@ const props = defineProps({
 		type: String,
 		type: String,
 		default: "modals/editSong/MODAL_UUID"
 		default: "modals/editSong/MODAL_UUID"
 	},
 	},
-	discogsAlbum: { type: Object, default: null }
+	discogsAlbum: { type: Object, default: null },
+	song: { type: Object, default: null },
+	songs: { type: Array, default: null }
 });
 });
 
 
 const editSongStore = useEditSongStore(props);
 const editSongStore = useEditSongStore(props);
@@ -454,25 +456,6 @@ const toggleMobileSidebar = () => {
 	sidebarMobileActive.value = !sidebarMobileActive.value;
 	sidebarMobileActive.value = !sidebarMobileActive.value;
 };
 };
 
 
-const handleConfirmed = ({ action, params }) => {
-	if (typeof action === "function") {
-		if (params) action(params);
-		else action();
-	}
-};
-
-const confirmAction = ({ message, action, params }) => {
-	openModal({
-		modal: "confirm",
-		data: {
-			message,
-			action,
-			params,
-			onCompleted: handleConfirmed
-		}
-	});
-};
-
 const onCloseOrNext = (next?: boolean): Promise<void> =>
 const onCloseOrNext = (next?: boolean): Promise<void> =>
 	new Promise(resolve => {
 	new Promise(resolve => {
 		const confirmReasons = [];
 		const confirmReasons = [];
@@ -503,10 +486,12 @@ const onCloseOrNext = (next?: boolean): Promise<void> =>
 		}
 		}
 
 
 		if (confirmReasons.length > 0)
 		if (confirmReasons.length > 0)
-			confirmAction({
-				message: confirmReasons,
-				action: resolve,
-				params: null
+			openModal({
+				modal: "confirm",
+				props: {
+					message: confirmReasons,
+					onCompleted: resolve
+				}
 			});
 			});
 		else resolve();
 		else resolve();
 	});
 	});
@@ -927,6 +912,8 @@ watch(
 );
 );
 
 
 onMounted(async () => {
 onMounted(async () => {
+	editSongStore.init({ song: props.song, songs: props.songs });
+
 	editSongStore.form = {
 	editSongStore.form = {
 		inputs,
 		inputs,
 		unsavedChanges,
 		unsavedChanges,
@@ -2317,11 +2304,13 @@ onBeforeUnmount(() => {
 							v-if="hasPermission('songs.remove')"
 							v-if="hasPermission('songs.remove')"
 							class="button is-danger icon-with-button material-icons"
 							class="button is-danger icon-with-button material-icons"
 							@click.prevent="
 							@click.prevent="
-								confirmAction({
-									message:
-										'Removing this song will remove it from all playlists and cause a ratings recalculation.',
-									action: remove,
-									params: song._id
+								openModal({
+									modal: 'confirm',
+									props: {
+										message:
+											'Removing this song will remove it from all playlists and cause a ratings recalculation.',
+										onCompleted: () => remove(song._id)
+									}
 								})
 								})
 							"
 							"
 							content="Delete Song"
 							content="Delete Song"

+ 20 - 40
frontend/src/components/modals/EditUser.vue

@@ -1,9 +1,7 @@
 <script setup lang="ts">
 <script setup lang="ts">
 import { defineAsyncComponent, watch, onMounted, onBeforeUnmount } from "vue";
 import { defineAsyncComponent, watch, onMounted, onBeforeUnmount } from "vue";
 import Toast from "toasters";
 import Toast from "toasters";
-import { storeToRefs } from "pinia";
 import validation from "@/validation";
 import validation from "@/validation";
-import { useEditUserStore } from "@/stores/editUser";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
 import { useUserAuthStore } from "@/stores/userAuth";
 import { useUserAuthStore } from "@/stores/userAuth";
@@ -15,16 +13,12 @@ const QuickConfirm = defineAsyncComponent(
 );
 );
 
 
 const props = defineProps({
 const props = defineProps({
-	modalUuid: { type: String, required: true }
+	modalUuid: { type: String, required: true },
+	userId: { type: String, required: true }
 });
 });
 
 
-const editUserStore = useEditUserStore(props);
-
 const { socket } = useWebsocketsStore();
 const { socket } = useWebsocketsStore();
 
 
-const { userId, user } = storeToRefs(editUserStore);
-const { setUser } = editUserStore;
-
 const { closeCurrentModal, preventCloseUnsaved } = useModalsStore();
 const { closeCurrentModal, preventCloseUnsaved } = useModalsStore();
 
 
 const { hasPermission } = useUserAuthStore();
 const { hasPermission } = useUserAuthStore();
@@ -37,7 +31,7 @@ const {
 } = useForm(
 } = useForm(
 	{
 	{
 		username: {
 		username: {
-			value: user.value.username,
+			value: "",
 			validate: value => {
 			validate: value => {
 				if (!validation.isLength(value, 2, 32))
 				if (!validation.isLength(value, 2, 32))
 					return "Username must have between 2 and 32 characters.";
 					return "Username must have between 2 and 32 characters.";
@@ -51,10 +45,9 @@ const {
 		if (status === "success")
 		if (status === "success")
 			socket.dispatch(
 			socket.dispatch(
 				"users.updateUsername",
 				"users.updateUsername",
-				user.value._id,
+				props.userId,
 				values.username,
 				values.username,
 				res => {
 				res => {
-					user.value.username = values.username;
 					if (res.status === "success") {
 					if (res.status === "success") {
 						resolve();
 						resolve();
 						new Toast(res.message);
 						new Toast(res.message);
@@ -100,10 +93,9 @@ const {
 		if (status === "success")
 		if (status === "success")
 			socket.dispatch(
 			socket.dispatch(
 				"users.updateEmail",
 				"users.updateEmail",
-				user.value._id,
+				props.userId,
 				values.email,
 				values.email,
 				res => {
 				res => {
-					user.value.email.address = values.email;
 					if (res.status === "success") {
 					if (res.status === "success") {
 						resolve();
 						resolve();
 						new Toast(res.message);
 						new Toast(res.message);
@@ -129,15 +121,14 @@ const {
 	save: saveRole,
 	save: saveRole,
 	setOriginalValue: setRole
 	setOriginalValue: setRole
 } = useForm(
 } = useForm(
-	{ role: user.value.role },
+	{ role: "" },
 	({ status, messages, values }, resolve, reject) => {
 	({ status, messages, values }, resolve, reject) => {
 		if (status === "success")
 		if (status === "success")
 			socket.dispatch(
 			socket.dispatch(
 				"users.updateRole",
 				"users.updateRole",
-				user.value._id,
+				props.userId,
 				values.role,
 				values.role,
 				res => {
 				res => {
-					user.value.role = values.role;
 					if (res.status === "success") {
 					if (res.status === "success") {
 						resolve();
 						resolve();
 						new Toast(res.message);
 						new Toast(res.message);
@@ -179,7 +170,7 @@ const {
 		if (status === "success")
 		if (status === "success")
 			socket.dispatch(
 			socket.dispatch(
 				"users.banUserById",
 				"users.banUserById",
-				user.value._id,
+				props.userId,
 				values.reason,
 				values.reason,
 				values.expiresAt,
 				values.expiresAt,
 				res => {
 				res => {
@@ -202,25 +193,25 @@ const {
 );
 );
 
 
 const resendVerificationEmail = () => {
 const resendVerificationEmail = () => {
-	socket.dispatch(`users.resendVerifyEmail`, user.value._id, res => {
+	socket.dispatch(`users.resendVerifyEmail`, props.userId, res => {
 		new Toast(res.message);
 		new Toast(res.message);
 	});
 	});
 };
 };
 
 
 const requestPasswordReset = () => {
 const requestPasswordReset = () => {
-	socket.dispatch(`users.adminRequestPasswordReset`, user.value._id, res => {
+	socket.dispatch(`users.adminRequestPasswordReset`, props.userId, res => {
 		new Toast(res.message);
 		new Toast(res.message);
 	});
 	});
 };
 };
 
 
 const removeAccount = () => {
 const removeAccount = () => {
-	socket.dispatch(`users.adminRemove`, user.value._id, res => {
+	socket.dispatch(`users.adminRemove`, props.userId, res => {
 		new Toast(res.message);
 		new Toast(res.message);
 	});
 	});
 };
 };
 
 
 const removeSessions = () => {
 const removeSessions = () => {
-	socket.dispatch(`users.removeSessions`, user.value._id, res => {
+	socket.dispatch(`users.removeSessions`, props.userId, res => {
 		new Toast(res.message);
 		new Toast(res.message);
 	});
 	});
 };
 };
@@ -231,17 +222,6 @@ watch(
 		if (!value) closeCurrentModal();
 		if (!value) closeCurrentModal();
 	}
 	}
 );
 );
-watch(user, (value, oldValue) => {
-	if (value.username !== oldValue.username)
-		setUsername({ username: value.username });
-	if (
-		value.email &&
-		(value.email.address !== (oldValue.email && oldValue.email.address) ||
-			!emailInputs.value.email.value)
-	)
-		setEmail({ email: value.email.address });
-	if (value.role !== oldValue.role) setRole({ role: value.role });
-});
 
 
 onMounted(() => {
 onMounted(() => {
 	preventCloseUnsaved[props.modalUuid] = () =>
 	preventCloseUnsaved[props.modalUuid] = () =>
@@ -252,16 +232,18 @@ onMounted(() => {
 		0;
 		0;
 
 
 	socket.onConnect(() => {
 	socket.onConnect(() => {
-		socket.dispatch(`users.getUserFromId`, userId.value, res => {
+		socket.dispatch(`users.getUserFromId`, props.userId, res => {
 			if (res.status === "success") {
 			if (res.status === "success") {
-				setUser(res.data);
+				setUsername({ username: res.data.username });
+				setEmail({ email: res.data.email.address });
+				setRole({ role: res.data.role });
 
 
-				socket.dispatch("apis.joinRoom", `edit-user.${userId.value}`);
+				socket.dispatch("apis.joinRoom", `edit-user.${props.userId}`);
 
 
 				socket.on(
 				socket.on(
 					"event:user.removed",
 					"event:user.removed",
 					res => {
 					res => {
-						if (res.data.userId === userId.value)
+						if (res.data.userId === props.userId)
 							closeCurrentModal();
 							closeCurrentModal();
 					},
 					},
 					{ modalUuid: props.modalUuid }
 					{ modalUuid: props.modalUuid }
@@ -276,16 +258,14 @@ onMounted(() => {
 
 
 onBeforeUnmount(() => {
 onBeforeUnmount(() => {
 	delete preventCloseUnsaved[props.modalUuid];
 	delete preventCloseUnsaved[props.modalUuid];
-	socket.dispatch("apis.leaveRoom", `edit-user.${userId.value}`, () => {});
-	// Delete the Pinia store that was created for this modal, after all other cleanup tasks are performed
-	editUserStore.$dispose();
+	socket.dispatch("apis.leaveRoom", `edit-user.${props.userId}`, () => {});
 });
 });
 </script>
 </script>
 
 
 <template>
 <template>
 	<div>
 	<div>
 		<modal title="Edit User">
 		<modal title="Edit User">
-			<template #body v-if="user && user._id">
+			<template #body>
 				<div class="section">
 				<div class="section">
 					<label class="label"> Change username </label>
 					<label class="label"> Change username </label>
 					<p class="control is-grouped">
 					<p class="control is-grouped">

+ 6 - 3
frontend/src/components/modals/ImportAlbum.vue

@@ -19,7 +19,8 @@ const SongItem = defineAsyncComponent(
 );
 );
 
 
 const props = defineProps({
 const props = defineProps({
-	modalUuid: { type: String, required: true }
+	modalUuid: { type: String, required: true },
+	songs: { type: Array, required: true }
 });
 });
 
 
 const { socket } = useWebsocketsStore();
 const { socket } = useWebsocketsStore();
@@ -117,7 +118,7 @@ const startEditingSongs = () => {
 	else {
 	else {
 		openModal({
 		openModal({
 			modal: "editSong",
 			modal: "editSong",
-			data: { songs: songsToEdit.value }
+			props: { songs: songsToEdit.value }
 		});
 		});
 	}
 	}
 };
 };
@@ -325,6 +326,8 @@ const updateTrackSong = updatedSong => {
 };
 };
 
 
 onMounted(() => {
 onMounted(() => {
+	setPlaylistSongs(props.songs);
+
 	preventCloseCbs[props.modalUuid] = (): Promise<void> =>
 	preventCloseCbs[props.modalUuid] = (): Promise<void> =>
 		new Promise(resolve => {
 		new Promise(resolve => {
 			const confirmReasons = [];
 			const confirmReasons = [];
@@ -343,7 +346,7 @@ onMounted(() => {
 			if (confirmReasons.length > 0)
 			if (confirmReasons.length > 0)
 				openModal({
 				openModal({
 					modal: "confirm",
 					modal: "confirm",
-					data: {
+					props: {
 						message: confirmReasons,
 						message: confirmReasons,
 						onCompleted: resolve
 						onCompleted: resolve
 					}
 					}

+ 8 - 1
frontend/src/components/modals/ManageStation/index.vue

@@ -31,7 +31,9 @@ const QuickConfirm = defineAsyncComponent(
 );
 );
 
 
 const props = defineProps({
 const props = defineProps({
-	modalUuid: { type: String, required: true }
+	modalUuid: { type: String, required: true },
+	stationId: { type: String, required: true },
+	sector: { type: String, default: "admin" }
 });
 });
 
 
 const tabs = ref([]);
 const tabs = ref([]);
@@ -147,6 +149,11 @@ watch(
 );
 );
 
 
 onMounted(() => {
 onMounted(() => {
+	manageStationStore.init({
+		stationId: props.stationId,
+		sector: props.sector
+	});
+
 	socket.onConnect(() => {
 	socket.onConnect(() => {
 		socket.dispatch(
 		socket.dispatch(
 			`stations.getStationById`,
 			`stations.getStationById`,

+ 3 - 7
frontend/src/components/modals/RemoveAccount.vue

@@ -2,10 +2,8 @@
 import { defineAsyncComponent, ref, onMounted } from "vue";
 import { defineAsyncComponent, ref, onMounted } from "vue";
 import { useRoute } from "vue-router";
 import { useRoute } from "vue-router";
 import Toast from "toasters";
 import Toast from "toasters";
-import { storeToRefs } from "pinia";
 import { useSettingsStore } from "@/stores/settings";
 import { useSettingsStore } from "@/stores/settings";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
-import { useRemoveAccountStore } from "@/stores/removeAccount";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
 
 
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
@@ -14,7 +12,8 @@ const QuickConfirm = defineAsyncComponent(
 );
 );
 
 
 const props = defineProps({
 const props = defineProps({
-	modalUuid: { type: String, required: true }
+	modalUuid: { type: String, required: true },
+	githubLinkConfirmed: { type: Boolean, default: false }
 });
 });
 
 
 const settingsStore = useSettingsStore();
 const settingsStore = useSettingsStore();
@@ -22,9 +21,6 @@ const route = useRoute();
 
 
 const { socket } = useWebsocketsStore();
 const { socket } = useWebsocketsStore();
 
 
-const removeAccountStore = useRemoveAccountStore(props);
-const { githubLinkConfirmed } = storeToRefs(removeAccountStore);
-
 const { isPasswordLinked, isGithubLinked } = settingsStore;
 const { isPasswordLinked, isGithubLinked } = settingsStore;
 
 
 const { closeCurrentModal } = useModalsStore();
 const { closeCurrentModal } = useModalsStore();
@@ -114,7 +110,7 @@ onMounted(async () => {
 		"siteSettings.githubAuthentication"
 		"siteSettings.githubAuthentication"
 	);
 	);
 
 
-	if (githubLinkConfirmed.value === true) confirmGithubLink();
+	if (props.githubLinkConfirmed === true) confirmGithubLink();
 });
 });
 </script>
 </script>
 
 

+ 6 - 21
frontend/src/components/modals/Report.vue

@@ -1,16 +1,8 @@
 <script setup lang="ts">
 <script setup lang="ts">
-import {
-	defineAsyncComponent,
-	ref,
-	onMounted,
-	onBeforeUnmount,
-	computed
-} from "vue";
+import { defineAsyncComponent, ref, onMounted, computed } from "vue";
 import Toast from "toasters";
 import Toast from "toasters";
-import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
-import { useReportStore } from "@/stores/report";
 import { useForm } from "@/composables/useForm";
 import { useForm } from "@/composables/useForm";
 
 
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
@@ -22,14 +14,12 @@ const ReportInfoItem = defineAsyncComponent(
 );
 );
 
 
 const props = defineProps({
 const props = defineProps({
-	modalUuid: { type: String, required: true }
+	modalUuid: { type: String, required: true },
+	song: { type: Object, required: true }
 });
 });
 
 
 const { socket } = useWebsocketsStore();
 const { socket } = useWebsocketsStore();
 
 
-const reportStore = useReportStore(props);
-const { song } = storeToRefs(reportStore);
-
 const { openModal, closeCurrentModal } = useModalsStore();
 const { openModal, closeCurrentModal } = useModalsStore();
 
 
 const existingReports = ref([]);
 const existingReports = ref([]);
@@ -190,7 +180,7 @@ const { inputs, save } = useForm(
 					"reports.create",
 					"reports.create",
 					{
 					{
 						issues,
 						issues,
-						youtubeId: song.value.youtubeId
+						youtubeId: props.song.youtubeId
 					},
 					},
 					res => {
 					res => {
 						if (res.status === "success") {
 						if (res.status === "success") {
@@ -225,7 +215,7 @@ const categories = computed(() =>
 
 
 onMounted(() => {
 onMounted(() => {
 	socket.onConnect(() => {
 	socket.onConnect(() => {
-		socket.dispatch("reports.myReportsForSong", song.value._id, res => {
+		socket.dispatch("reports.myReportsForSong", props.song._id, res => {
 			if (res.status === "success") {
 			if (res.status === "success") {
 				existingReports.value = res.data.reports;
 				existingReports.value = res.data.reports;
 				existingReports.value.forEach(report =>
 				existingReports.value.forEach(report =>
@@ -258,11 +248,6 @@ onMounted(() => {
 		);
 		);
 	});
 	});
 });
 });
-
-onBeforeUnmount(() => {
-	// Delete the Pinia store that was created for this modal, after all other cleanup tasks are performed
-	reportStore.$dispose();
-});
 </script>
 </script>
 
 
 <template>
 <template>
@@ -446,7 +431,7 @@ onBeforeUnmount(() => {
 											@click="
 											@click="
 												openModal({
 												openModal({
 													modal: 'viewReport',
 													modal: 'viewReport',
-													data: {
+													props: {
 														reportId: report._id
 														reportId: report._id
 													}
 													}
 												})
 												})

+ 23 - 23
frontend/src/components/modals/ViewApiRequest.vue

@@ -2,10 +2,8 @@
 import { defineAsyncComponent, ref, onMounted, onBeforeUnmount } from "vue";
 import { defineAsyncComponent, ref, onMounted, onBeforeUnmount } from "vue";
 import Toast from "toasters";
 import Toast from "toasters";
 import VueJsonPretty from "vue-json-pretty";
 import VueJsonPretty from "vue-json-pretty";
-import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
-import { useViewApiRequestStore } from "@/stores/viewApiRequest";
 import "vue-json-pretty/lib/styles.css";
 import "vue-json-pretty/lib/styles.css";
 
 
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
@@ -14,43 +12,47 @@ const QuickConfirm = defineAsyncComponent(
 );
 );
 
 
 const props = defineProps({
 const props = defineProps({
-	modalUuid: { type: String, required: true }
+	modalUuid: { type: String, required: true },
+	requestId: { type: String, required: true },
+	removeAction: { type: String, required: true }
 });
 });
 
 
 const { socket } = useWebsocketsStore();
 const { socket } = useWebsocketsStore();
 
 
-const viewApiRequestStore = useViewApiRequestStore(props);
-const { requestId, request, removeAction } = storeToRefs(viewApiRequestStore);
-const { viewApiRequest } = viewApiRequestStore;
-
 const { closeCurrentModal } = useModalsStore();
 const { closeCurrentModal } = useModalsStore();
 
 
 const loaded = ref(false);
 const loaded = ref(false);
+const request = ref({
+	_id: null,
+	url: null,
+	params: {},
+	results: [],
+	date: null,
+	quotaCost: null
+});
 
 
 const remove = () => {
 const remove = () => {
-	if (removeAction.value)
-		socket.dispatch(removeAction.value, requestId.value, res => {
-			if (res.status === "success") {
-				new Toast("API request successfully removed.");
-				closeCurrentModal();
-			} else {
-				new Toast("API request with that ID not found.");
-			}
-		});
+	socket.dispatch(props.removeAction, props.requestId, res => {
+		if (res.status === "success") {
+			new Toast("API request successfully removed.");
+			closeCurrentModal();
+		} else {
+			new Toast("API request with that ID not found.");
+		}
+	});
 };
 };
 
 
 onMounted(() => {
 onMounted(() => {
 	socket.onConnect(() => {
 	socket.onConnect(() => {
 		loaded.value = false;
 		loaded.value = false;
-		socket.dispatch("youtube.getApiRequest", requestId.value, res => {
+		socket.dispatch("youtube.getApiRequest", props.requestId, res => {
 			if (res.status === "success") {
 			if (res.status === "success") {
-				const { apiRequest } = res.data;
-				viewApiRequest(apiRequest);
+				request.value = res.data.apiRequest;
 				loaded.value = true;
 				loaded.value = true;
 
 
 				socket.dispatch(
 				socket.dispatch(
 					"apis.joinRoom",
 					"apis.joinRoom",
-					`view-api-request.${requestId.value}`
+					`view-api-request.${props.requestId}`
 				);
 				);
 
 
 				socket.on(
 				socket.on(
@@ -72,11 +74,9 @@ onMounted(() => {
 onBeforeUnmount(() => {
 onBeforeUnmount(() => {
 	socket.dispatch(
 	socket.dispatch(
 		"apis.leaveRoom",
 		"apis.leaveRoom",
-		`view-api-request.${requestId.value}`,
+		`view-api-request.${props.requestId}`,
 		() => {}
 		() => {}
 	);
 	);
-	// Delete the Pinia store that was created for this modal, after all other cleanup tasks are performed
-	viewApiRequestStore.$dispose();
 });
 });
 </script>
 </script>
 
 

+ 11 - 17
frontend/src/components/modals/ViewPunishment.vue

@@ -1,10 +1,8 @@
 <script setup lang="ts">
 <script setup lang="ts">
-import { defineAsyncComponent, onMounted, onBeforeUnmount } from "vue";
+import { defineAsyncComponent, ref, onMounted, onBeforeUnmount } from "vue";
 import Toast from "toasters";
 import Toast from "toasters";
-import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
-import { useViewPunishmentStore } from "@/stores/viewPunishment";
 
 
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const PunishmentItem = defineAsyncComponent(
 const PunishmentItem = defineAsyncComponent(
@@ -12,25 +10,24 @@ const PunishmentItem = defineAsyncComponent(
 );
 );
 
 
 const props = defineProps({
 const props = defineProps({
-	modalUuid: { type: String, required: true }
+	modalUuid: { type: String, required: true },
+	punishmentId: { type: String, required: true }
 });
 });
 
 
 const { socket } = useWebsocketsStore();
 const { socket } = useWebsocketsStore();
 
 
-const viewPunishmentStore = useViewPunishmentStore(props);
-const { punishmentId, punishment } = storeToRefs(viewPunishmentStore);
-const { viewPunishment } = viewPunishmentStore;
-
 const { closeCurrentModal } = useModalsStore();
 const { closeCurrentModal } = useModalsStore();
 
 
+const punishment = ref({});
+
 const deactivatePunishment = event => {
 const deactivatePunishment = event => {
 	event.preventDefault();
 	event.preventDefault();
 	socket.dispatch(
 	socket.dispatch(
 		"punishments.deactivatePunishment",
 		"punishments.deactivatePunishment",
-		punishmentId.value,
+		props.punishmentId,
 		res => {
 		res => {
 			if (res.status === "success") {
 			if (res.status === "success") {
-				viewPunishmentStore.deactivatePunishment();
+				punishment.value.active = false;
 			} else {
 			} else {
 				new Toast(res.message);
 				new Toast(res.message);
 			}
 			}
@@ -40,13 +37,13 @@ const deactivatePunishment = event => {
 
 
 onMounted(() => {
 onMounted(() => {
 	socket.onConnect(() => {
 	socket.onConnect(() => {
-		socket.dispatch(`punishments.findOne`, punishmentId.value, res => {
+		socket.dispatch(`punishments.findOne`, props.punishmentId, res => {
 			if (res.status === "success") {
 			if (res.status === "success") {
-				viewPunishment(res.data.punishment);
+				punishment.value = res.data.punishment;
 
 
 				socket.dispatch(
 				socket.dispatch(
 					"apis.joinRoom",
 					"apis.joinRoom",
-					`view-punishment.${punishmentId.value}`
+					`view-punishment.${props.punishmentId}`
 				);
 				);
 
 
 				socket.on(
 				socket.on(
@@ -67,12 +64,9 @@ onMounted(() => {
 onBeforeUnmount(() => {
 onBeforeUnmount(() => {
 	socket.dispatch(
 	socket.dispatch(
 		"apis.leaveRoom",
 		"apis.leaveRoom",
-		`view-punishment.${punishmentId.value}`,
+		`view-punishment.${props.punishmentId}`,
 		() => {}
 		() => {}
 	);
 	);
-
-	// Delete the Pinia store that was created for this modal, after all other cleanup tasks are performed
-	viewPunishmentStore.$dispose();
 });
 });
 </script>
 </script>
 
 

+ 10 - 16
frontend/src/components/modals/ViewReport.vue

@@ -7,10 +7,8 @@ import {
 	onBeforeUnmount
 	onBeforeUnmount
 } from "vue";
 } from "vue";
 import Toast from "toasters";
 import Toast from "toasters";
-import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useModalsStore } from "@/stores/modals";
 import { useModalsStore } from "@/stores/modals";
-import { useViewReportStore } from "@/stores/viewReport";
 import { useUserAuthStore } from "@/stores/userAuth";
 import { useUserAuthStore } from "@/stores/userAuth";
 import { useReports } from "@/composables/useReports";
 import { useReports } from "@/composables/useReports";
 import { Report } from "@/types/report";
 import { Report } from "@/types/report";
@@ -27,14 +25,12 @@ const QuickConfirm = defineAsyncComponent(
 );
 );
 
 
 const props = defineProps({
 const props = defineProps({
-	modalUuid: { type: String, required: true }
+	modalUuid: { type: String, required: true },
+	reportId: { type: String, required: true }
 });
 });
 
 
 const { socket } = useWebsocketsStore();
 const { socket } = useWebsocketsStore();
 
 
-const viewReportStore = useViewReportStore(props);
-const { reportId } = storeToRefs(viewReportStore);
-
 const { openModal, closeCurrentModal } = useModalsStore();
 const { openModal, closeCurrentModal } = useModalsStore();
 
 
 const { resolveReport, removeReport } = useReports();
 const { resolveReport, removeReport } = useReports();
@@ -54,21 +50,21 @@ const report = ref<Report>({});
 const song = ref();
 const song = ref();
 
 
 const resolve = value =>
 const resolve = value =>
-	resolveReport({ reportId: reportId.value, value })
+	resolveReport({ reportId: props.reportId, value })
 		.then((res: any) => {
 		.then((res: any) => {
 			if (res.status !== "success") new Toast(res.message);
 			if (res.status !== "success") new Toast(res.message);
 		})
 		})
 		.catch(err => new Toast(err.message));
 		.catch(err => new Toast(err.message));
 
 
 const remove = () =>
 const remove = () =>
-	removeReport(reportId.value)
+	removeReport(props.reportId)
 		.then((res: any) => {
 		.then((res: any) => {
 			if (res.status === "success") closeCurrentModal();
 			if (res.status === "success") closeCurrentModal();
 		})
 		})
 		.catch(err => new Toast(err.message));
 		.catch(err => new Toast(err.message));
 
 
 const toggleIssue = issueId => {
 const toggleIssue = issueId => {
-	socket.dispatch("reports.toggleIssue", reportId.value, issueId, res => {
+	socket.dispatch("reports.toggleIssue", props.reportId, issueId, res => {
 		if (res.status !== "success") new Toast(res.message);
 		if (res.status !== "success") new Toast(res.message);
 	});
 	});
 };
 };
@@ -76,7 +72,7 @@ const toggleIssue = issueId => {
 const openSong = () => {
 const openSong = () => {
 	openModal({
 	openModal({
 		modal: "editSong",
 		modal: "editSong",
-		data: { song: report.value.song }
+		props: { song: report.value.song }
 	});
 	});
 };
 };
 
 
@@ -89,13 +85,13 @@ watch(
 
 
 onMounted(() => {
 onMounted(() => {
 	socket.onConnect(() => {
 	socket.onConnect(() => {
-		socket.dispatch("reports.findOne", reportId.value, res => {
+		socket.dispatch("reports.findOne", props.reportId, res => {
 			if (res.status === "success") {
 			if (res.status === "success") {
 				report.value = res.data.report;
 				report.value = res.data.report;
 
 
 				socket.dispatch(
 				socket.dispatch(
 					"apis.joinRoom",
 					"apis.joinRoom",
-					`view-report.${reportId.value}`
+					`view-report.${props.reportId}`
 				);
 				);
 
 
 				socket.dispatch(
 				socket.dispatch(
@@ -132,7 +128,7 @@ onMounted(() => {
 				socket.on(
 				socket.on(
 					"event:admin.report.issue.toggled",
 					"event:admin.report.issue.toggled",
 					res => {
 					res => {
-						if (reportId.value === res.data.reportId) {
+						if (props.reportId === res.data.reportId) {
 							const issue = report.value.issues.find(
 							const issue = report.value.issues.find(
 								issue =>
 								issue =>
 									issue._id.toString() === res.data.issueId
 									issue._id.toString() === res.data.issueId
@@ -152,9 +148,7 @@ onMounted(() => {
 });
 });
 
 
 onBeforeUnmount(() => {
 onBeforeUnmount(() => {
-	socket.dispatch("apis.leaveRoom", `view-report.${reportId.value}`);
-	// Delete the Pinia store that was created for this modal, after all other cleanup tasks are performed
-	viewReportStore.$dispose();
+	socket.dispatch("apis.leaveRoom", `view-report.${props.reportId}`);
 });
 });
 </script>
 </script>
 
 

+ 16 - 32
frontend/src/components/modals/ViewYoutubeVideo.vue

@@ -16,7 +16,9 @@ const SongThumbnail = defineAsyncComponent(
 );
 );
 
 
 const props = defineProps({
 const props = defineProps({
-	modalUuid: { type: String, required: true }
+	modalUuid: { type: String, required: true },
+	videoId: { type: String, default: null },
+	youtubeId: { type: String, default: null }
 });
 });
 
 
 const interval = ref(null);
 const interval = ref(null);
@@ -30,9 +32,7 @@ const activityWatchVideoLastStartDuration = ref(0);
 
 
 const viewYoutubeVideoStore = useViewYoutubeVideoStore(props);
 const viewYoutubeVideoStore = useViewYoutubeVideoStore(props);
 const stationStore = useStationStore();
 const stationStore = useStationStore();
-const { videoId, youtubeId, video, player } = storeToRefs(
-	viewYoutubeVideoStore
-);
+const { video, player } = storeToRefs(viewYoutubeVideoStore);
 const {
 const {
 	updatePlayer,
 	updatePlayer,
 	stopVideo,
 	stopVideo,
@@ -51,7 +51,7 @@ const userAuthStore = useUserAuthStore();
 const { hasPermission } = userAuthStore;
 const { hasPermission } = userAuthStore;
 
 
 const remove = () => {
 const remove = () => {
-	socket.dispatch("youtube.removeVideos", videoId.value, res => {
+	socket.dispatch("youtube.removeVideos", video.value._id, res => {
 		if (res.status === "success") {
 		if (res.status === "success") {
 			new Toast("YouTube video successfully removed.");
 			new Toast("YouTube video successfully removed.");
 			closeCurrentModal();
 			closeCurrentModal();
@@ -61,25 +61,6 @@ const remove = () => {
 	});
 	});
 };
 };
 
 
-const handleConfirmed = ({ action, params }) => {
-	if (typeof action === "function") {
-		if (params) action(params);
-		else action();
-	}
-};
-
-const confirmAction = ({ message, action }) => {
-	openModal({
-		modal: "confirm",
-		data: {
-			message,
-			action,
-			params: null,
-			onCompleted: handleConfirmed
-		}
-	});
-};
-
 const seekTo = position => {
 const seekTo = position => {
 	pauseVideo(false);
 	pauseVideo(false);
 	player.value.player.seekTo(position);
 	player.value.player.seekTo(position);
@@ -222,7 +203,7 @@ onMounted(() => {
 		loaded.value = false;
 		loaded.value = false;
 		socket.dispatch(
 		socket.dispatch(
 			"youtube.getVideo",
 			"youtube.getVideo",
-			videoId.value || youtubeId.value,
+			props.videoId || props.youtubeId,
 			true,
 			true,
 			res => {
 			res => {
 				if (res.status === "success") {
 				if (res.status === "success") {
@@ -457,7 +438,7 @@ onMounted(() => {
 
 
 					socket.dispatch(
 					socket.dispatch(
 						"apis.joinRoom",
 						"apis.joinRoom",
-						`view-youtube-video.${videoId.value}`
+						`view-youtube-video.${video.value._id}`
 					);
 					);
 
 
 					socket.on(
 					socket.on(
@@ -491,7 +472,7 @@ onBeforeUnmount(() => {
 
 
 	socket.dispatch(
 	socket.dispatch(
 		"apis.leaveRoom",
 		"apis.leaveRoom",
-		`view-youtube-video.${videoId.value}`,
+		`view-youtube-video.${video.value._id}`,
 		() => {}
 		() => {}
 	);
 	);
 
 
@@ -720,7 +701,7 @@ onBeforeUnmount(() => {
 				"
 				"
 				class="button is-primary icon-with-button material-icons"
 				class="button is-primary icon-with-button material-icons"
 				@click.prevent="
 				@click.prevent="
-					openModal({ modal: 'editSong', data: { song: video } })
+					openModal({ modal: 'editSong', props: { song: video } })
 				"
 				"
 				content="Create/edit song from video"
 				content="Create/edit song from video"
 				v-tippy
 				v-tippy
@@ -732,10 +713,13 @@ onBeforeUnmount(() => {
 					v-if="hasPermission('youtube.removeVideos')"
 					v-if="hasPermission('youtube.removeVideos')"
 					class="button is-danger icon-with-button material-icons"
 					class="button is-danger icon-with-button material-icons"
 					@click.prevent="
 					@click.prevent="
-						confirmAction({
-							message:
-								'Removing this video will remove it from all playlists and cause a ratings recalculation.',
-							action: remove
+						openModal({
+							modal: 'confirm',
+							props: {
+								message:
+									'Removing this video will remove it from all playlists and cause a ratings recalculation.',
+								onCompleted: remove
+							}
 						})
 						})
 					"
 					"
 					content="Delete Video"
 					content="Delete Video"

+ 4 - 13
frontend/src/components/modals/WhatIsNew.vue

@@ -1,23 +1,19 @@
 <script setup lang="ts">
 <script setup lang="ts">
-import { defineAsyncComponent, onMounted, onBeforeUnmount } from "vue";
-import { storeToRefs } from "pinia";
+import { defineAsyncComponent, onMounted } from "vue";
 import { formatDistance } from "date-fns";
 import { formatDistance } from "date-fns";
 import { marked } from "marked";
 import { marked } from "marked";
 import dompurify from "dompurify";
 import dompurify from "dompurify";
-import { useWhatIsNewStore } from "@/stores/whatIsNew";
 
 
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const UserLink = defineAsyncComponent(
 const UserLink = defineAsyncComponent(
 	() => import("@/components/UserLink.vue")
 	() => import("@/components/UserLink.vue")
 );
 );
 
 
-const props = defineProps({
-	modalUuid: { type: String, required: true }
+defineProps({
+	modalUuid: { type: String, required: true },
+	news: { type: Object, required: true }
 });
 });
 
 
-const whatIsNewStore = useWhatIsNewStore(props);
-const { news } = storeToRefs(whatIsNewStore);
-
 onMounted(() => {
 onMounted(() => {
 	marked.use({
 	marked.use({
 		renderer: {
 		renderer: {
@@ -31,11 +27,6 @@ onMounted(() => {
 	});
 	});
 });
 });
 
 
-onBeforeUnmount(() => {
-	// Delete the Pinia store that was created for this modal, after all other cleanup tasks are performed
-	whatIsNewStore.$dispose();
-});
-
 const { sanitize } = dompurify;
 const { sanitize } = dompurify;
 </script>
 </script>
 
 

+ 1 - 1
frontend/src/composables/useForm.ts

@@ -145,7 +145,7 @@ export const useForm = (
 			if (sourceChanged.value.length > 0)
 			if (sourceChanged.value.length > 0)
 				openModal({
 				openModal({
 					modal: "confirm",
 					modal: "confirm",
-					data: {
+					props: {
 						message:
 						message:
 							"Updates have been made whilst you were making changes. Are you sure you want to continue?",
 							"Updates have been made whilst you were making changes. Are you sure you want to continue?",
 						onCompleted: onSave
 						onCompleted: onSave

+ 2 - 2
frontend/src/pages/Admin/News.vue

@@ -151,7 +151,7 @@ const remove = (id: string) => {
 					@click="
 					@click="
 						openModal({
 						openModal({
 							modal: 'editNews',
 							modal: 'editNews',
-							data: { createNews: true }
+							props: { createNews: true }
 						})
 						})
 					"
 					"
 				>
 				>
@@ -176,7 +176,7 @@ const remove = (id: string) => {
 						@click="
 						@click="
 							openModal({
 							openModal({
 								modal: 'editNews',
 								modal: 'editNews',
-								data: { newsId: slotProps.item._id }
+								props: { newsId: slotProps.item._id }
 							})
 							})
 						"
 						"
 						content="Edit News"
 						content="Edit News"

+ 1 - 1
frontend/src/pages/Admin/Playlists.vue

@@ -276,7 +276,7 @@ const formatTimeLong = length => utils.formatTimeLong(length);
 						@click="
 						@click="
 							openModal({
 							openModal({
 								modal: 'editPlaylist',
 								modal: 'editPlaylist',
-								data: { playlistId: slotProps.item._id }
+								props: { playlistId: slotProps.item._id }
 							})
 							})
 						"
 						"
 						:disabled="slotProps.item.removed"
 						:disabled="slotProps.item.removed"

+ 1 - 1
frontend/src/pages/Admin/Reports.vue

@@ -198,7 +198,7 @@ const getDateFormatted = createdAt => {
 						@click="
 						@click="
 							openModal({
 							openModal({
 								modal: 'viewReport',
 								modal: 'viewReport',
-								data: { reportId: slotProps.item._id }
+								props: { reportId: slotProps.item._id }
 							})
 							})
 						"
 						"
 						:disabled="slotProps.item.removed"
 						:disabled="slotProps.item.removed"

+ 11 - 25
frontend/src/pages/Admin/Songs/Import.vue

@@ -361,7 +361,7 @@ const importAlbum = youtubeIds => {
 		if (res.status === "success") {
 		if (res.status === "success") {
 			openModal({
 			openModal({
 				modal: "importAlbum",
 				modal: "importAlbum",
-				data: { songs: res.data.songs }
+				props: { songs: res.data.songs }
 			});
 			});
 		} else new Toast("Could not get songs.");
 		} else new Toast("Could not get songs.");
 	});
 	});
@@ -372,25 +372,6 @@ const removeImportJob = jobId => {
 		new Toast(res.message);
 		new Toast(res.message);
 	});
 	});
 };
 };
-
-const handleConfirmed = ({ action, params }) => {
-	if (typeof action === "function") {
-		if (params) action(params);
-		else action();
-	}
-};
-
-const confirmAction = ({ message, action, params }) => {
-	openModal({
-		modal: "confirm",
-		data: {
-			message,
-			action,
-			params,
-			onCompleted: handleConfirmed
-		}
-	});
-};
 </script>
 </script>
 
 
 <template>
 <template>
@@ -574,11 +555,16 @@ const confirmAction = ({ message, action, params }) => {
 									"
 									"
 									class="button is-danger icon-with-button material-icons"
 									class="button is-danger icon-with-button material-icons"
 									@click.prevent="
 									@click.prevent="
-										confirmAction({
-											message:
-												'Note: Removing an import will not remove any videos or songs.',
-											action: removeImportJob,
-											params: slotProps.item._id
+										openModal({
+											modal: 'confirm',
+											props: {
+												message:
+													'Note: Removing an import will not remove any videos or songs.',
+												onCompleted: () =>
+													removeImportJob(
+														slotProps.item._id
+													)
+											}
 										})
 										})
 									"
 									"
 									:disabled="
 									:disabled="

+ 23 - 36
frontend/src/pages/Admin/Songs/index.vue

@@ -323,14 +323,14 @@ const { openModal } = useModalsStore();
 const create = () => {
 const create = () => {
 	openModal({
 	openModal({
 		modal: "editSong",
 		modal: "editSong",
-		data: { song: { newSong: true } }
+		props: { song: { newSong: true } }
 	});
 	});
 };
 };
 
 
 const editOne = song => {
 const editOne = song => {
 	openModal({
 	openModal({
 		modal: "editSong",
 		modal: "editSong",
-		data: { song }
+		props: { song }
 	});
 	});
 };
 };
 
 
@@ -340,7 +340,7 @@ const editMany = selectedRows => {
 		const songs = selectedRows.map(row => ({
 		const songs = selectedRows.map(row => ({
 			youtubeId: row.youtubeId
 			youtubeId: row.youtubeId
 		}));
 		}));
-		openModal({ modal: "editSong", data: { songs } });
+		openModal({ modal: "editSong", props: { songs } });
 	}
 	}
 };
 };
 
 
@@ -414,7 +414,7 @@ const importAlbum = selectedRows => {
 		if (res.status === "success") {
 		if (res.status === "success") {
 			openModal({
 			openModal({
 				modal: "importAlbum",
 				modal: "importAlbum",
-				data: { songs: res.data.songs }
+				props: { songs: res.data.songs }
 			});
 			});
 		}
 		}
 	});
 	});
@@ -423,7 +423,7 @@ const importAlbum = selectedRows => {
 const setTags = selectedRows => {
 const setTags = selectedRows => {
 	openModal({
 	openModal({
 		modal: "bulkActions",
 		modal: "bulkActions",
-		data: {
+		props: {
 			type: {
 			type: {
 				name: "tags",
 				name: "tags",
 				action: "songs.editTags",
 				action: "songs.editTags",
@@ -439,7 +439,7 @@ const setTags = selectedRows => {
 const setArtists = selectedRows => {
 const setArtists = selectedRows => {
 	openModal({
 	openModal({
 		modal: "bulkActions",
 		modal: "bulkActions",
-		data: {
+		props: {
 			type: {
 			type: {
 				name: "artists",
 				name: "artists",
 				action: "songs.editArtists",
 				action: "songs.editArtists",
@@ -455,7 +455,7 @@ const setArtists = selectedRows => {
 const setGenres = selectedRows => {
 const setGenres = selectedRows => {
 	openModal({
 	openModal({
 		modal: "bulkActions",
 		modal: "bulkActions",
-		data: {
+		props: {
 			type: {
 			type: {
 				name: "genres",
 				name: "genres",
 				action: "songs.editGenres",
 				action: "songs.editGenres",
@@ -510,25 +510,6 @@ const getDateFormatted = createdAt => {
 	return `${year}-${month}-${day} ${hour}:${minute}`;
 	return `${year}-${month}-${day} ${hour}:${minute}`;
 };
 };
 
 
-const handleConfirmed = ({ action, params }) => {
-	if (typeof action === "function") {
-		if (params) action(params);
-		else action();
-	}
-};
-
-const confirmAction = ({ message, action, params }) => {
-	openModal({
-		modal: "confirm",
-		data: {
-			message,
-			action,
-			params,
-			onCompleted: handleConfirmed
-		}
-	});
-};
-
 onMounted(() => {
 onMounted(() => {
 	if (route.query.songId) {
 	if (route.query.songId) {
 		socket.dispatch("songs.getSongFromSongId", route.query.songId, res => {
 		socket.dispatch("songs.getSongFromSongId", route.query.songId, res => {
@@ -618,11 +599,14 @@ onMounted(() => {
 						v-if="hasPermission('songs.remove')"
 						v-if="hasPermission('songs.remove')"
 						class="button is-danger icon-with-button material-icons"
 						class="button is-danger icon-with-button material-icons"
 						@click.prevent="
 						@click.prevent="
-							confirmAction({
-								message:
-									'Removing this song will remove it from all playlists and cause a ratings recalculation.',
-								action: deleteOne,
-								params: slotProps.item._id
+							openModal({
+								modal: 'confirm',
+								props: {
+									message:
+										'Removing this song will remove it from all playlists and cause a ratings recalculation.',
+									onCompleted: () =>
+										deleteOne(slotProps.item._id)
+								}
 							})
 							})
 						"
 						"
 						:disabled="slotProps.item.removed"
 						:disabled="slotProps.item.removed"
@@ -792,11 +776,14 @@ onMounted(() => {
 						v-if="hasPermission('songs.remove')"
 						v-if="hasPermission('songs.remove')"
 						class="material-icons delete-icon"
 						class="material-icons delete-icon"
 						@click.prevent="
 						@click.prevent="
-							confirmAction({
-								message:
-									'Removing these songs will remove them from all playlists and cause a ratings recalculation.',
-								action: deleteMany,
-								params: slotProps.item
+							openModal({
+								modal: 'confirm',
+								props: {
+									message:
+										'Removing these songs will remove them from all playlists and cause a ratings recalculation.',
+									onCompleted: () =>
+										deleteMany(slotProps.item)
+								}
 							})
 							})
 						"
 						"
 						content="Delete Songs"
 						content="Delete Songs"

+ 2 - 2
frontend/src/pages/Admin/Stations.vue

@@ -332,7 +332,7 @@ const remove = stationId => {
 					@click="
 					@click="
 						openModal({
 						openModal({
 							modal: 'createStation',
 							modal: 'createStation',
-							data: { official: true }
+							props: { official: true }
 						})
 						})
 					"
 					"
 				>
 				>
@@ -356,7 +356,7 @@ const remove = stationId => {
 						@click="
 						@click="
 							openModal({
 							openModal({
 								modal: 'manageStation',
 								modal: 'manageStation',
-								data: {
+								props: {
 									stationId: slotProps.item._id,
 									stationId: slotProps.item._id,
 									sector: 'admin'
 									sector: 'admin'
 								}
 								}

+ 1 - 1
frontend/src/pages/Admin/Users/Punishments.vue

@@ -224,7 +224,7 @@ const deactivatePunishment = punishmentId => {
 						@click="
 						@click="
 							openModal({
 							openModal({
 								modal: 'viewPunishment',
 								modal: 'viewPunishment',
-								data: { punishmentId: slotProps.item._id }
+								props: { punishmentId: slotProps.item._id }
 							})
 							})
 						"
 						"
 						:disabled="slotProps.item.removed"
 						:disabled="slotProps.item.removed"

+ 1 - 1
frontend/src/pages/Admin/Users/index.vue

@@ -204,7 +204,7 @@ const { openModal } = useModalsStore();
 const { hasPermission } = useUserAuthStore();
 const { hasPermission } = useUserAuthStore();
 
 
 const edit = userId => {
 const edit = userId => {
-	openModal({ modal: "editUser", data: { userId } });
+	openModal({ modal: "editUser", props: { userId } });
 };
 };
 
 
 onMounted(() => {
 onMounted(() => {

+ 24 - 33
frontend/src/pages/Admin/YouTube/Videos.vue

@@ -208,7 +208,7 @@ const { openModal } = useModalsStore();
 const editOne = song => {
 const editOne = song => {
 	openModal({
 	openModal({
 		modal: "editSong",
 		modal: "editSong",
-		data: { song }
+		props: { song }
 	});
 	});
 };
 };
 
 
@@ -218,7 +218,7 @@ const editMany = selectedRows => {
 		const songs = selectedRows.map(row => ({
 		const songs = selectedRows.map(row => ({
 			youtubeId: row.youtubeId
 			youtubeId: row.youtubeId
 		}));
 		}));
-		openModal({ modal: "editSong", data: { songs } });
+		openModal({ modal: "editSong", props: { songs } });
 	}
 	}
 };
 };
 
 
@@ -228,7 +228,7 @@ const importAlbum = selectedRows => {
 		if (res.status === "success") {
 		if (res.status === "success") {
 			openModal({
 			openModal({
 				modal: "importAlbum",
 				modal: "importAlbum",
-				data: { songs: res.data.songs }
+				props: { songs: res.data.songs }
 			});
 			});
 		} else new Toast("Could not get songs.");
 		} else new Toast("Could not get songs.");
 	});
 	});
@@ -265,25 +265,6 @@ const getDateFormatted = createdAt => {
 	const minute = `${date.getMinutes()}`.padStart(2, "0");
 	const minute = `${date.getMinutes()}`.padStart(2, "0");
 	return `${year}-${month}-${day} ${hour}:${minute}`;
 	return `${year}-${month}-${day} ${hour}:${minute}`;
 };
 };
-
-const handleConfirmed = ({ action, params }) => {
-	if (typeof action === "function") {
-		if (params) action(params);
-		else action();
-	}
-};
-
-const confirmAction = ({ message, action, params }) => {
-	openModal({
-		modal: "confirm",
-		data: {
-			message,
-			action,
-			params,
-			onCompleted: handleConfirmed
-		}
-	});
-};
 </script>
 </script>
 
 
 <template>
 <template>
@@ -315,7 +296,7 @@ const confirmAction = ({ message, action, params }) => {
 						@click="
 						@click="
 							openModal({
 							openModal({
 								modal: 'viewYoutubeVideo',
 								modal: 'viewYoutubeVideo',
-								data: {
+								props: {
 									videoId: slotProps.item._id
 									videoId: slotProps.item._id
 								}
 								}
 							})
 							})
@@ -347,11 +328,14 @@ const confirmAction = ({ message, action, params }) => {
 						v-if="hasPermission('youtube.removeVideos')"
 						v-if="hasPermission('youtube.removeVideos')"
 						class="button is-danger icon-with-button material-icons"
 						class="button is-danger icon-with-button material-icons"
 						@click.prevent="
 						@click.prevent="
-							confirmAction({
-								message:
-									'Removing this video will remove it from all playlists and cause a ratings recalculation.',
-								action: removeVideos,
-								params: slotProps.item._id
+							openModal({
+								modal: 'confirm',
+								props: {
+									message:
+										'Removing this video will remove it from all playlists and cause a ratings recalculation.',
+									onCompleted: () =>
+										removeVideos(slotProps.item._id)
+								}
 							})
 							})
 						"
 						"
 						:disabled="slotProps.item.removed"
 						:disabled="slotProps.item.removed"
@@ -438,11 +422,18 @@ const confirmAction = ({ message, action, params }) => {
 						v-if="hasPermission('youtube.removeVideos')"
 						v-if="hasPermission('youtube.removeVideos')"
 						class="material-icons delete-icon"
 						class="material-icons delete-icon"
 						@click.prevent="
 						@click.prevent="
-							confirmAction({
-								message:
-									'Removing these videos will remove them from all playlists and cause a ratings recalculation.',
-								action: removeVideos,
-								params: slotProps.item.map(video => video._id)
+							openModal({
+								modal: 'confirm',
+								props: {
+									message:
+										'Removing these videos will remove them from all playlists and cause a ratings recalculation.',
+									onCompleted: () =>
+										removeVideos(
+											slotProps.item.map(
+												video => video._id
+											)
+										)
+								}
 							})
 							})
 						"
 						"
 						content="Delete Videos"
 						content="Delete Videos"

+ 1 - 1
frontend/src/pages/Admin/YouTube/index.vue

@@ -270,7 +270,7 @@ onMounted(() => {
 							@click="
 							@click="
 								openModal({
 								openModal({
 									modal: 'viewApiRequest',
 									modal: 'viewApiRequest',
-									data: {
+									props: {
 										requestId: slotProps.item._id,
 										requestId: slotProps.item._id,
 										removeAction:
 										removeAction:
 											'youtube.removeStoredApiRequest'
 											'youtube.removeStoredApiRequest'

+ 4 - 4
frontend/src/pages/Home.vue

@@ -451,7 +451,7 @@ onBeforeUnmount(() => {
 												@click.prevent="
 												@click.prevent="
 													openModal({
 													openModal({
 														modal: 'manageStation',
 														modal: 'manageStation',
-														data: {
+														props: {
 															stationId:
 															stationId:
 																element._id,
 																element._id,
 															sector: 'home'
 															sector: 'home'
@@ -469,7 +469,7 @@ onBeforeUnmount(() => {
 												@click.prevent="
 												@click.prevent="
 													openModal({
 													openModal({
 														modal: 'manageStation',
 														modal: 'manageStation',
-														data: {
+														props: {
 															stationId:
 															stationId:
 																element._id,
 																element._id,
 															sector: 'home'
 															sector: 'home'
@@ -732,7 +732,7 @@ onBeforeUnmount(() => {
 										@click.prevent="
 										@click.prevent="
 											openModal({
 											openModal({
 												modal: 'manageStation',
 												modal: 'manageStation',
-												data: {
+												props: {
 													stationId: station._id,
 													stationId: station._id,
 													sector: 'home'
 													sector: 'home'
 												}
 												}
@@ -749,7 +749,7 @@ onBeforeUnmount(() => {
 										@click.prevent="
 										@click.prevent="
 											openModal({
 											openModal({
 												modal: 'manageStation',
 												modal: 'manageStation',
-												data: {
+												props: {
 													stationId: station._id,
 													stationId: station._id,
 													sector: 'home'
 													sector: 'home'
 												}
 												}

+ 2 - 2
frontend/src/pages/Profile/Tabs/Playlists.vue

@@ -69,7 +69,7 @@ onMounted(() => {
 								@click="
 								@click="
 									openModal({
 									openModal({
 										modal: 'editPlaylist',
 										modal: 'editPlaylist',
-										data: { playlistId: element._id }
+										props: { playlistId: element._id }
 									})
 									})
 								"
 								"
 								class="material-icons edit-icon"
 								class="material-icons edit-icon"
@@ -82,7 +82,7 @@ onMounted(() => {
 								@click="
 								@click="
 									openModal({
 									openModal({
 										modal: 'editPlaylist',
 										modal: 'editPlaylist',
-										data: { playlistId: element._id }
+										props: { playlistId: element._id }
 									})
 									})
 								"
 								"
 								class="material-icons view-icon"
 								class="material-icons view-icon"

+ 1 - 1
frontend/src/pages/Settings/Tabs/Account.vue

@@ -149,7 +149,7 @@ onMounted(() => {
 	) {
 	) {
 		openModal({
 		openModal({
 			modal: "removeAccount",
 			modal: "removeAccount",
-			data: { githubLinkConfirmed: true }
+			props: { githubLinkConfirmed: true }
 		});
 		});
 	}
 	}
 });
 });

+ 0 - 13
frontend/src/stores/bulkActions.ts

@@ -1,13 +0,0 @@
-import { defineStore } from "pinia";
-
-export const useBulkActionsStore = ({ modalUuid }: { modalUuid: string }) =>
-	defineStore(`bulkActions-${modalUuid}`, {
-		state: () => ({
-			type: null
-		}),
-		actions: {
-			init({ type }) {
-				this.type = type;
-			}
-		}
-	})();

+ 0 - 25
frontend/src/stores/confirm.ts

@@ -1,25 +0,0 @@
-import { defineStore } from "pinia";
-
-export const useConfirmStore = ({ modalUuid }: { modalUuid: string }) =>
-	defineStore(`confirm-${modalUuid}`, {
-		state: () => ({
-			message: "",
-			onCompleted: null,
-			action: null,
-			params: null
-		}),
-		actions: {
-			init({ message, onCompleted, action, params }) {
-				this.message = message;
-				this.onCompleted = onCompleted;
-				this.action = action;
-				this.params = params;
-			},
-			confirm() {
-				this.onCompleted({
-					action: this.action,
-					params: this.params
-				});
-			}
-		}
-	})();

+ 0 - 13
frontend/src/stores/createStation.ts

@@ -1,13 +0,0 @@
-import { defineStore } from "pinia";
-
-export const useCreateStationStore = ({ modalUuid }: { modalUuid: string }) =>
-	defineStore(`createStation-${modalUuid}`, {
-		state: () => ({
-			official: false
-		}),
-		actions: {
-			init({ official }) {
-				this.official = !!official;
-			}
-		}
-	})();

+ 0 - 17
frontend/src/stores/editNews.ts

@@ -1,17 +0,0 @@
-import { defineStore } from "pinia";
-
-export const useEditNewsStore = ({ modalUuid }: { modalUuid: string }) =>
-	defineStore(`editNews-${modalUuid}`, {
-		state: () => ({
-			createNews: false,
-			newsId: null,
-			sector: "admin"
-		}),
-		actions: {
-			init({ createNews, newsId, sector }) {
-				if (createNews) this.createNews = createNews;
-				if (newsId) this.newsId = newsId;
-				if (sector) this.sector = sector;
-			}
-		}
-	})();

+ 0 - 5
frontend/src/stores/editPlaylist.ts

@@ -4,18 +4,13 @@ import { Playlist } from "@/types/playlist";
 export const useEditPlaylistStore = ({ modalUuid }: { modalUuid: string }) =>
 export const useEditPlaylistStore = ({ modalUuid }: { modalUuid: string }) =>
 	defineStore(`editPlaylist-${modalUuid}`, {
 	defineStore(`editPlaylist-${modalUuid}`, {
 		state: (): {
 		state: (): {
-			playlistId: string;
 			tab: string;
 			tab: string;
 			playlist: Playlist;
 			playlist: Playlist;
 		} => ({
 		} => ({
-			playlistId: null,
 			tab: "settings",
 			tab: "settings",
 			playlist: { songs: [] }
 			playlist: { songs: [] }
 		}),
 		}),
 		actions: {
 		actions: {
-			init({ playlistId }) {
-				this.playlistId = playlistId;
-			},
 			showTab(tab) {
 			showTab(tab) {
 				this.tab = tab;
 				this.tab = tab;
 			},
 			},

+ 0 - 21
frontend/src/stores/editUser.ts

@@ -1,21 +0,0 @@
-import { defineStore } from "pinia";
-import { User } from "@/types/user";
-
-export const useEditUserStore = ({ modalUuid }: { modalUuid: string }) =>
-	defineStore(`editUser-${modalUuid}`, {
-		state: (): {
-			userId: string;
-			user: User;
-		} => ({
-			userId: null,
-			user: {}
-		}),
-		actions: {
-			init({ userId }) {
-				this.userId = userId;
-			},
-			setUser(user) {
-				this.user = user;
-			}
-		}
-	})();

+ 0 - 4
frontend/src/stores/importAlbum.ts

@@ -34,10 +34,6 @@ export const useImportAlbumStore = ({ modalUuid }: { modalUuid: string }) =>
 			prefillDiscogs: false
 			prefillDiscogs: false
 		}),
 		}),
 		actions: {
 		actions: {
-			init({ songs }) {
-				this.originalPlaylistSongs = JSON.parse(JSON.stringify(songs));
-				this.playlistSongs = JSON.parse(JSON.stringify(songs));
-			},
 			showDiscogsTab(tab) {
 			showDiscogsTab(tab) {
 				this.discogsTab = tab;
 				this.discogsTab = tab;
 			},
 			},

+ 13 - 84
frontend/src/stores/modals.ts

@@ -3,26 +3,10 @@ import { defineStore } from "pinia";
 import utils from "@/utils";
 import utils from "@/utils";
 
 
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useWebsocketsStore } from "@/stores/websockets";
-import { useEditUserStore } from "@/stores/editUser";
-import { useEditSongStore } from "@/stores/editSong";
-import { useBulkActionsStore } from "@/stores/bulkActions";
-import { useConfirmStore } from "@/stores/confirm";
-import { useCreateStationStore } from "@/stores/createStation";
-import { useEditNewsStore } from "@/stores/editNews";
-import { useEditPlaylistStore } from "@/stores/editPlaylist";
-import { useImportAlbumStore } from "@/stores/importAlbum";
-import { useManageStationStore } from "@/stores/manageStation";
-import { useRemoveAccountStore } from "@/stores/removeAccount";
-import { useReportStore } from "@/stores/report";
-import { useViewApiRequestStore } from "@/stores/viewApiRequest";
-import { useViewPunishmentStore } from "@/stores/viewPunishment";
-import { useViewReportStore } from "@/stores/viewReport";
-import { useViewYoutubeVideoStore } from "@/stores/viewYoutubeVideo";
-import { useWhatIsNewStore } from "@/stores/whatIsNew";
 
 
 export const useModalsStore = defineStore("modals", {
 export const useModalsStore = defineStore("modals", {
 	state: (): {
 	state: (): {
-		modals: Record<string, string>;
+		modals: Record<string, { modal: string; props?: Record<string, any> }>;
 		activeModals: string[];
 		activeModals: string[];
 		preventCloseUnsaved: Record<string, () => boolean>;
 		preventCloseUnsaved: Record<string, () => boolean>;
 		preventCloseCbs: Record<string, () => Promise<void>>;
 		preventCloseCbs: Record<string, () => Promise<void>>;
@@ -36,7 +20,7 @@ export const useModalsStore = defineStore("modals", {
 		closeModal(uuid: string) {
 		closeModal(uuid: string) {
 			Object.entries(this.modals).forEach(([_uuid, modal]) => {
 			Object.entries(this.modals).forEach(([_uuid, modal]) => {
 				if (uuid === _uuid) {
 				if (uuid === _uuid) {
-					if (modal === "register")
+					if (modal.modal === "register")
 						lofig
 						lofig
 							.get("recaptcha.enabled")
 							.get("recaptcha.enabled")
 							.then((enabled: boolean) => {
 							.then((enabled: boolean) => {
@@ -61,7 +45,7 @@ export const useModalsStore = defineStore("modals", {
 					) {
 					) {
 						this.openModal({
 						this.openModal({
 							modal: "confirm",
 							modal: "confirm",
-							data: {
+							props: {
 								message:
 								message:
 									"You have unsaved changes. Are you sure you want to discard these changes and close the modal?",
 									"You have unsaved changes. Are you sure you want to discard these changes and close the modal?",
 								onCompleted: close
 								onCompleted: close
@@ -71,76 +55,21 @@ export const useModalsStore = defineStore("modals", {
 				}
 				}
 			});
 			});
 		},
 		},
-		openModal(dataOrModal: string | { modal: string; data?: any }) {
+		openModal(
+			propsOrModal:
+				| string
+				| { modal: string; props?: Record<string, any> }
+		) {
 			const uuid = utils.guid();
 			const uuid = utils.guid();
-
 			let modal;
 			let modal;
-			let data;
-			if (typeof dataOrModal === "string") modal = dataOrModal;
+			let props;
+			if (typeof propsOrModal === "string") modal = propsOrModal;
 			else {
 			else {
-				modal = dataOrModal.modal;
-				data = dataOrModal.data;
-			}
-			this.modals[uuid] = modal;
-
-			let store;
-			switch (modal) {
-				case "editUser":
-					store = useEditUserStore({ modalUuid: uuid });
-					break;
-				case "editSong":
-					store = useEditSongStore({ modalUuid: uuid });
-					break;
-				case "bulkActions":
-					store = useBulkActionsStore({ modalUuid: uuid });
-					break;
-				case "confirm":
-					store = useConfirmStore({ modalUuid: uuid });
-					break;
-				case "createStation":
-					store = useCreateStationStore({ modalUuid: uuid });
-					break;
-				case "editNews":
-					store = useEditNewsStore({ modalUuid: uuid });
-					break;
-				case "editPlaylist":
-					store = useEditPlaylistStore({ modalUuid: uuid });
-					break;
-				case "importAlbum":
-					store = useImportAlbumStore({ modalUuid: uuid });
-					break;
-				case "manageStation":
-					store = useManageStationStore({ modalUuid: uuid });
-					break;
-				case "removeAccount":
-					store = useRemoveAccountStore({ modalUuid: uuid });
-					break;
-				case "report":
-					store = useReportStore({ modalUuid: uuid });
-					break;
-				case "viewApiRequest":
-					store = useViewApiRequestStore({ modalUuid: uuid });
-					break;
-				case "viewPunishment":
-					store = useViewPunishmentStore({ modalUuid: uuid });
-					break;
-				case "viewReport":
-					store = useViewReportStore({ modalUuid: uuid });
-					break;
-				case "viewYoutubeVideo":
-					store = useViewYoutubeVideoStore({ modalUuid: uuid });
-					break;
-				case "whatIsNew":
-					store = useWhatIsNewStore({ modalUuid: uuid });
-					break;
-				default:
-					break;
+				modal = propsOrModal.modal;
+				props = propsOrModal.props;
 			}
 			}
-			if (store && typeof store.init === "function" && data)
-				store.init(data);
-
+			this.modals[uuid] = { modal, props };
 			this.activeModals.push(uuid);
 			this.activeModals.push(uuid);
-
 			return { uuid };
 			return { uuid };
 		},
 		},
 		closeCurrentModal() {
 		closeCurrentModal() {

+ 0 - 15
frontend/src/stores/removeAccount.ts

@@ -1,15 +0,0 @@
-import { defineStore } from "pinia";
-
-export const useRemoveAccountStore = ({ modalUuid }: { modalUuid: string }) =>
-	defineStore(`removeAccount-${modalUuid}`, {
-		state: (): {
-			githubLinkConfirmed: boolean;
-		} => ({
-			githubLinkConfirmed: false
-		}),
-		actions: {
-			init({ githubLinkConfirmed }) {
-				this.githubLinkConfirmed = !!githubLinkConfirmed;
-			}
-		}
-	})();

+ 0 - 16
frontend/src/stores/report.ts

@@ -1,16 +0,0 @@
-import { defineStore } from "pinia";
-import { Song } from "@/types/song";
-
-export const useReportStore = ({ modalUuid }: { modalUuid: string }) =>
-	defineStore(`report-${modalUuid}`, {
-		state: (): {
-			song: Song;
-		} => ({
-			song: {}
-		}),
-		actions: {
-			init({ song }) {
-				this.song = song;
-			}
-		}
-	})();

+ 0 - 37
frontend/src/stores/viewApiRequest.ts

@@ -1,37 +0,0 @@
-import { defineStore } from "pinia";
-
-export const useViewApiRequestStore = ({ modalUuid }: { modalUuid: string }) =>
-	defineStore(`viewApiRequest-${modalUuid}`, {
-		state: (): {
-			requestId: string;
-			request: {
-				_id: string;
-				url: string;
-				params: object;
-				results: any;
-				date: number;
-				quotaCost: number;
-			};
-			removeAction: string | null;
-		} => ({
-			requestId: null,
-			request: {
-				_id: null,
-				url: null,
-				params: {},
-				results: [],
-				date: null,
-				quotaCost: null
-			},
-			removeAction: null
-		}),
-		actions: {
-			init({ requestId, removeAction }) {
-				this.requestId = requestId;
-				this.removeAction = removeAction;
-			},
-			viewApiRequest(request) {
-				this.request = request;
-			}
-		}
-	})();

+ 0 - 27
frontend/src/stores/viewPunishment.ts

@@ -1,27 +0,0 @@
-import { defineStore } from "pinia";
-
-export const useViewPunishmentStore = ({ modalUuid }: { modalUuid: string }) =>
-	defineStore(`viewPunishment-${modalUuid}`, {
-		state: (): {
-			punishmentId: string;
-			punishment: {
-				_id: string;
-			};
-		} => ({
-			punishmentId: null,
-			punishment: {
-				_id: null
-			}
-		}),
-		actions: {
-			init({ punishmentId }: { punishmentId: string }) {
-				this.punishmentId = punishmentId;
-			},
-			viewPunishment(punishment) {
-				this.punishment = punishment;
-			},
-			deactivatePunishment() {
-				this.punishment.active = false;
-			}
-		}
-	})();

+ 0 - 15
frontend/src/stores/viewReport.ts

@@ -1,15 +0,0 @@
-import { defineStore } from "pinia";
-
-export const useViewReportStore = ({ modalUuid }: { modalUuid: string }) =>
-	defineStore(`viewReport-${modalUuid}`, {
-		state: (): {
-			reportId: string;
-		} => ({
-			reportId: null
-		}),
-		actions: {
-			init({ reportId }: { reportId: string }) {
-				this.reportId = reportId;
-			}
-		}
-	})();

+ 0 - 16
frontend/src/stores/viewYoutubeVideo.ts

@@ -7,8 +7,6 @@ export const useViewYoutubeVideoStore = ({
 }) =>
 }) =>
 	defineStore(`viewYoutubeVideo-${modalUuid}`, {
 	defineStore(`viewYoutubeVideo-${modalUuid}`, {
 		state: (): {
 		state: (): {
-			videoId: string;
-			youtubeId: string;
 			video: {
 			video: {
 				_id: string;
 				_id: string;
 				youtubeId: string;
 				youtubeId: string;
@@ -32,8 +30,6 @@ export const useViewYoutubeVideoStore = ({
 				showRateDropdown: boolean;
 				showRateDropdown: boolean;
 			};
 			};
 		} => ({
 		} => ({
-			videoId: null,
-			youtubeId: null,
 			video: {
 			video: {
 				_id: null,
 				_id: null,
 				youtubeId: null,
 				youtubeId: null,
@@ -58,19 +54,7 @@ export const useViewYoutubeVideoStore = ({
 			}
 			}
 		}),
 		}),
 		actions: {
 		actions: {
-			init({
-				videoId,
-				youtubeId
-			}: {
-				videoId: string;
-				youtubeId: string;
-			}) {
-				this.videoId = videoId;
-				this.youtubeId = youtubeId;
-			},
 			viewYoutubeVideo(video) {
 			viewYoutubeVideo(video) {
-				this.videoId = this.videoId || video._id;
-				this.youtubeId = video.youtubeId || video.youtubeId;
 				this.video = video;
 				this.video = video;
 			},
 			},
 			updatePlayer(player) {
 			updatePlayer(player) {

+ 0 - 16
frontend/src/stores/whatIsNew.ts

@@ -1,16 +0,0 @@
-import { NewsModel } from "@musare_types/models/News";
-import { defineStore } from "pinia";
-
-export const useWhatIsNewStore = ({ modalUuid }: { modalUuid: string }) =>
-	defineStore(`whatIsNew-${modalUuid}`, {
-		state: (): {
-			news: NewsModel;
-		} => ({
-			news: null
-		}),
-		actions: {
-			init({ news }: { news: NewsModel }) {
-				this.news = news;
-			}
-		}
-	})();