Преглед на файлове

refactor: Use useForm in Report modal and other minor tweaks

Owen Diffey преди 1 година
родител
ревизия
d89a64379e

+ 4 - 1
frontend/src/classes/SocketHandler.class.ts

@@ -144,7 +144,10 @@ export default class SocketHandler {
 	on(
 		target: string,
 		cb: (...args: any[]) => any,
-		options?: EventListenerOptions
+		options?: EventListenerOptions & {
+			replaceable?: boolean;
+			modalUuid?: string;
+		}
 	) {
 		this.dispatcher.addEventListener(
 			target,

+ 8 - 5
frontend/src/components/modals/EditNews.vue

@@ -85,11 +85,14 @@ const { inputs, save, setOriginalValue } = useForm(
 			};
 			if (createNews.value) socket.dispatch("news.create", data, cb);
 			else socket.dispatch("news.update", newsId.value, data, cb);
-		} else if (status === "unchanged") new Toast(messages.unchanged);
-		else if (status === "error")
-			Object.values(messages).forEach(message => {
-				new Toast({ content: message, timeout: 8000 });
-			});
+		} else {
+			if (status === "unchanged") new Toast(messages.unchanged);
+			else if (status === "error")
+				Object.values(messages).forEach(message => {
+					new Toast({ content: message, timeout: 8000 });
+				});
+			resolve();
+		}
 	},
 	{
 		modalUuid: props.modalUuid

+ 7 - 2
frontend/src/components/modals/EditPlaylist/Tabs/Settings.vue

@@ -68,10 +68,12 @@ const {
 					} else reject(new Error(res.message));
 				}
 			);
-		else
+		else {
 			Object.values(messages).forEach(message => {
 				new Toast({ content: message, timeout: 8000 });
 			});
+			resolve();
+		}
 	},
 	{
 		modalUuid: props.modalUuid,
@@ -102,7 +104,10 @@ const {
 					} else reject(new Error(res.message));
 				}
 			);
-		else if (messages[status]) new Toast(messages[status]);
+		else {
+			if (messages[status]) new Toast(messages[status]);
+			resolve();
+		}
 	},
 	{
 		modalUuid: props.modalUuid,

+ 12 - 4
frontend/src/components/modals/EditUser.vue

@@ -61,10 +61,12 @@ const {
 					} else reject(new Error(res.message));
 				}
 			);
-		else
+		else {
 			Object.values(messages).forEach(message => {
 				new Toast({ content: message, timeout: 8000 });
 			});
+			resolve();
+		}
 	},
 	{
 		modalUuid: props.modalUuid,
@@ -108,10 +110,12 @@ const {
 					} else reject(new Error(res.message));
 				}
 			);
-		else
+		else {
 			Object.values(messages).forEach(message => {
 				new Toast({ content: message, timeout: 8000 });
 			});
+			resolve();
+		}
 	},
 	{
 		modalUuid: props.modalUuid,
@@ -140,10 +144,12 @@ const {
 					} else reject(new Error(res.message));
 				}
 			);
-		else
+		else {
 			Object.values(messages).forEach(message => {
 				new Toast({ content: message, timeout: 8000 });
 			});
+			resolve();
+		}
 	},
 	{
 		modalUuid: props.modalUuid,
@@ -182,10 +188,12 @@ const {
 					else reject(new Error(res.message));
 				}
 			);
-		else
+		else {
 			Object.values(messages).forEach(message => {
 				new Toast({ content: message, timeout: 8000 });
 			});
+			resolve();
+		}
 	},
 	{
 		modalUuid: props.modalUuid,

+ 3 - 1
frontend/src/components/modals/ManageStation/Settings.vue

@@ -100,10 +100,12 @@ const { inputs, save, setOriginalValue } = useForm(
 					} else reject(new Error(res.message));
 				}
 			);
-		} else
+		} else {
 			Object.values(messages).forEach(message => {
 				new Toast({ content: message, timeout: 8000 });
 			});
+			resolve();
+		}
 	},
 	{
 		modalUuid: props.modalUuid

+ 213 - 153
frontend/src/components/modals/Report.vue

@@ -1,10 +1,17 @@
 <script setup lang="ts">
-import { defineAsyncComponent, ref, onMounted, onBeforeUnmount } from "vue";
+import {
+	defineAsyncComponent,
+	ref,
+	onMounted,
+	onBeforeUnmount,
+	computed
+} from "vue";
 import Toast from "toasters";
 import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useModalsStore } from "@/stores/modals";
 import { useReportStore } from "@/stores/report";
+import { useForm } from "@/composables/useForm";
 
 const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
 const SongItem = defineAsyncComponent(
@@ -26,160 +33,195 @@ const { song } = storeToRefs(reportStore);
 const { openModal, closeCurrentModal } = useModalsStore();
 
 const existingReports = ref([]);
-const customIssues = ref([]);
-const predefinedCategories = ref([
+
+const { inputs, save } = useForm(
 	{
-		category: "video",
-		issues: [
-			{
-				enabled: false,
-				title: "Doesn't exist",
-				description: "",
-				showDescription: false
-			},
-			{
-				enabled: false,
-				title: "It's private",
-				description: "",
-				showDescription: false
-			},
-			{
-				enabled: false,
-				title: "It's not available in my country",
-				description: "",
-				showDescription: false
-			},
-			{
-				enabled: false,
-				title: "Unofficial",
-				description: "",
-				showDescription: false
+		video: {
+			value: {
+				category: "video",
+				issues: [
+					{
+						enabled: false,
+						title: "Doesn't exist",
+						description: "",
+						showDescription: false
+					},
+					{
+						enabled: false,
+						title: "It's private",
+						description: "",
+						showDescription: false
+					},
+					{
+						enabled: false,
+						title: "It's not available in my country",
+						description: "",
+						showDescription: false
+					},
+					{
+						enabled: false,
+						title: "Unofficial",
+						description: "",
+						showDescription: false
+					}
+				]
 			}
-		]
-	},
-	{
-		category: "title",
-		issues: [
-			{
-				enabled: false,
-				title: "Incorrect",
-				description: "",
-				showDescription: false
-			},
-			{
-				enabled: false,
-				title: "Inappropriate",
-				description: "",
-				showDescription: false
+		},
+		title: {
+			value: {
+				category: "title",
+				issues: [
+					{
+						enabled: false,
+						title: "Incorrect",
+						description: "",
+						showDescription: false
+					},
+					{
+						enabled: false,
+						title: "Inappropriate",
+						description: "",
+						showDescription: false
+					}
+				]
 			}
-		]
-	},
-	{
-		category: "duration",
-		issues: [
-			{
-				enabled: false,
-				title: "Skips too soon",
-				description: "",
-				showDescription: false
-			},
-			{
-				enabled: false,
-				title: "Skips too late",
-				description: "",
-				showDescription: false
-			},
-			{
-				enabled: false,
-				title: "Starts too soon",
-				description: "",
-				showDescription: false
-			},
-			{
-				enabled: false,
-				title: "Starts too late",
-				description: "",
-				showDescription: false
+		},
+		duration: {
+			value: {
+				category: "duration",
+				issues: [
+					{
+						enabled: false,
+						title: "Skips too soon",
+						description: "",
+						showDescription: false
+					},
+					{
+						enabled: false,
+						title: "Skips too late",
+						description: "",
+						showDescription: false
+					},
+					{
+						enabled: false,
+						title: "Starts too soon",
+						description: "",
+						showDescription: false
+					},
+					{
+						enabled: false,
+						title: "Starts too late",
+						description: "",
+						showDescription: false
+					}
+				]
 			}
-		]
-	},
-	{
-		category: "artists",
-		issues: [
-			{
-				enabled: false,
-				title: "Incorrect",
-				description: "",
-				showDescription: false
-			},
-			{
-				enabled: false,
-				title: "Inappropriate",
-				description: "",
-				showDescription: false
+		},
+		artists: {
+			value: {
+				category: "artists",
+				issues: [
+					{
+						enabled: false,
+						title: "Incorrect",
+						description: "",
+						showDescription: false
+					},
+					{
+						enabled: false,
+						title: "Inappropriate",
+						description: "",
+						showDescription: false
+					}
+				]
 			}
-		]
+		},
+		thumbnail: {
+			value: {
+				category: "thumbnail",
+				issues: [
+					{
+						enabled: false,
+						title: "Incorrect",
+						description: "",
+						showDescription: false
+					},
+					{
+						enabled: false,
+						title: "Inappropriate",
+						description: "",
+						showDescription: false
+					},
+					{
+						enabled: false,
+						title: "Doesn't exist",
+						description: "",
+						showDescription: false
+					}
+				]
+			}
+		},
+		custom: { value: [] }
+	},
+	({ status, messages, values }, resolve, reject) => {
+		if (status === "success") {
+			const issues: {
+				category: string;
+				title: string;
+				description?: string;
+			}[] = [];
+			Object.entries(values).forEach(([name, value]) => {
+				if (name === "custom")
+					value.forEach(issue => {
+						issues.push({ category: "custom", title: issue });
+					});
+				else
+					value.issues.forEach(issue => {
+						if (issue.enabled)
+							issues.push({
+								category: name,
+								title: issue.title,
+								description: issue.description
+							});
+					});
+			});
+			if (issues.length > 0)
+				socket.dispatch(
+					"reports.create",
+					{
+						issues,
+						youtubeId: song.value.youtubeId
+					},
+					res => {
+						if (res.status === "success") {
+							new Toast(res.message);
+							resolve();
+						} else reject(new Error(res.message));
+					}
+				);
+			else reject(new Error("Reports must have at least one issue"));
+		} else if (status === "unchanged")
+			reject(new Error("Reports must have at least one issue"));
+		else {
+			Object.values(messages).forEach(message => {
+				new Toast({ content: message, timeout: 8000 });
+			});
+			resolve();
+		}
 	},
 	{
-		category: "thumbnail",
-		issues: [
-			{
-				enabled: false,
-				title: "Incorrect",
-				description: "",
-				showDescription: false
-			},
-			{
-				enabled: false,
-				title: "Inappropriate",
-				description: "",
-				showDescription: false
-			},
-			{
-				enabled: false,
-				title: "Doesn't exist",
-				description: "",
-				showDescription: false
-			}
-		]
+		modalUuid: props.modalUuid
 	}
-]);
-
-const create = () => {
-	const issues = [];
-
-	// any predefined issues that are enabled
-	predefinedCategories.value.forEach(category =>
-		category.issues.forEach(issue => {
-			if (issue.enabled)
-				issues.push({
-					category: category.category,
-					title: issue.title,
-					description: issue.description
-				});
-		})
-	);
-
-	// any custom issues
-	customIssues.value.forEach(issue =>
-		issues.push({ category: "custom", title: issue })
-	);
-
-	if (issues.length === 0)
-		return new Toast("Reports must have at least one issue");
+);
 
-	return socket.dispatch(
-		"reports.create",
-		{
-			issues,
-			youtubeId: song.value.youtubeId
-		},
-		res => {
-			new Toast(res.message);
-			if (res.status === "success") closeCurrentModal();
-		}
-	);
-};
+const categories = computed(() =>
+	Object.entries(inputs.value)
+		.filter(([name]) => name !== "custom")
+		.map(input => {
+			const { category, issues } = input[1].value;
+			return { category, issues };
+		})
+);
 
 onMounted(() => {
 	socket.onConnect(() => {
@@ -204,6 +246,16 @@ onMounted(() => {
 			},
 			{ modalUuid: props.modalUuid }
 		);
+
+		socket.on(
+			"event:admin.report.removed",
+			res => {
+				existingReports.value = existingReports.value.filter(
+					report => report._id !== res.data.reportId
+				);
+			},
+			{ modalUuid: props.modalUuid }
+		);
 	});
 });
 
@@ -232,7 +284,7 @@ onBeforeUnmount(() => {
 
 						<div class="columns is-multiline">
 							<div
-								v-for="category in predefinedCategories"
+								v-for="category in categories"
 								class="column is-half"
 								:key="category.category"
 							>
@@ -301,7 +353,9 @@ onBeforeUnmount(() => {
 											class="button tab-actionable-button"
 											content="Add an issue that isn't listed"
 											v-tippy
-											@click="customIssues.push('')"
+											@click="
+												inputs.custom.value.push('')
+											"
 										>
 											<i
 												class="material-icons icon-with-button"
@@ -313,14 +367,17 @@ onBeforeUnmount(() => {
 
 									<div
 										class="custom-issue control is-grouped input-with-button"
-										v-for="(issue, index) in customIssues"
+										v-for="(issue, index) in inputs.custom
+											.value"
 										:key="index"
 									>
 										<p class="control is-expanded">
 											<input
 												type="text"
 												class="input"
-												v-model="customIssues[index]"
+												v-model="
+													inputs.custom.value[index]
+												"
 												placeholder="Provide information..."
 											/>
 										</p>
@@ -330,7 +387,7 @@ onBeforeUnmount(() => {
 												content="Remove custom issue"
 												v-tippy
 												@click="
-													customIssues.splice(
+													inputs.custom.value.splice(
 														index,
 														1
 													)
@@ -345,7 +402,7 @@ onBeforeUnmount(() => {
 
 									<p
 										id="no-issues-listed"
-										v-if="customIssues.length <= 0"
+										v-if="inputs.custom.value.length <= 0"
 									>
 										<em>
 											Add any issues that aren't listed
@@ -405,7 +462,10 @@ onBeforeUnmount(() => {
 				</div>
 			</template>
 			<template #footer>
-				<button class="button is-success" @click="create()">
+				<button
+					class="button is-success"
+					@click="save(closeCurrentModal)"
+				>
 					<i class="material-icons save-changes">done</i>
 					<span>&nbsp;Create</span>
 				</button>

+ 12 - 7
frontend/src/composables/useForm.ts

@@ -34,8 +34,8 @@ export const useForm = (
 					name,
 					{
 						...input,
-						originalValue: input.value,
-						errors: <string[]>[],
+						originalValue: JSON.parse(JSON.stringify(input.value)),
+						errors: [],
 						ref: ref(),
 						sourceChanged: false,
 						ignoreUnsaved: input.ignoreUnsaved === true,
@@ -48,7 +48,7 @@ export const useForm = (
 	);
 
 	const unsavedChanges = computed(() => {
-		const changed = <string[]>[];
+		const changed: string[] = [];
 		Object.entries(inputs.value).forEach(([name, input]) => {
 			if (
 				!input.ignoreUnsaved &&
@@ -61,7 +61,7 @@ export const useForm = (
 	});
 
 	const sourceChanged = computed(() => {
-		const _sourceChanged = <string[]>[];
+		const _sourceChanged: string[] = [];
 		Object.entries(inputs.value).forEach(([name, input]) => {
 			if (
 				input.sourceChanged &&
@@ -104,7 +104,7 @@ export const useForm = (
 	};
 
 	const validate = () => {
-		const invalid = <Record<string, string[]>>{};
+		const invalid: Record<string, string[]> = {};
 		Object.entries(inputs.value).forEach(([name, input]) => {
 			input.errors = [];
 			if (
@@ -153,8 +153,13 @@ export const useForm = (
 				});
 			else onSave();
 		} else if (errorCount === 0) {
-			useCallback("unchanged", { unchanged: "No changes to update" });
-			if (saveCb) saveCb();
+			useCallback("unchanged", { unchanged: "No changes have been made" })
+				.then(() => {
+					if (saveCb) saveCb();
+				})
+				.catch((err: Error) =>
+					useCallback("error", { error: err.message })
+				);
 		} else {
 			useCallback("error", {
 				...errors,

+ 28 - 6
frontend/src/tests/utils/utils.ts

@@ -10,7 +10,27 @@ const getConfig = async () => {
 	return config;
 };
 
-export const getWrapper = async (component, options?) => {
+export const getWrapper = async (
+	component: any,
+	options?: {
+		global?: {
+			plugins?: any[];
+			components?: { [key: string]: any };
+		};
+		usePinia?: boolean;
+		pinia?: {
+			stubActions?: boolean;
+		};
+		mockSocket?: boolean | { data?: any; executeDispatch?: boolean };
+		lofig?: any;
+		loginRequired?: boolean;
+		baseTemplate?: string;
+		attachTo?: HTMLElement | null;
+		beforeMount?: () => void;
+		onMount?: () => void;
+		afterMount?: () => void;
+	}
+) => {
 	const opts = options || {};
 
 	if (!opts.global) opts.global = {};
@@ -70,11 +90,13 @@ export const getWrapper = async (component, options?) => {
 		const websocketsStore = useWebsocketsStore();
 		await websocketsStore.createSocket();
 		await flushPromises();
-		if (opts.mockSocket.data)
-			websocketsStore.socket.data = opts.mockSocket.data;
-		if (typeof opts.mockSocket.executeDispatch !== "undefined")
-			websocketsStore.socket.executeDispatch =
-				opts.mockSocket.executeDispatch;
+		if (typeof opts.mockSocket === "object") {
+			if (opts.mockSocket.data)
+				websocketsStore.socket.data = opts.mockSocket.data;
+			if (typeof opts.mockSocket.executeDispatch !== "undefined")
+				websocketsStore.socket.executeDispatch =
+					opts.mockSocket.executeDispatch;
+		}
 		delete opts.mockSocket;
 	}
 

+ 3 - 2
musare.sh

@@ -395,6 +395,7 @@ case $1 in
 
     update)
         echo -e "${CYAN}Musare | Update${NC}"
+        musareshModified=$(git diff HEAD -- musare.sh)
         git fetch
         exitValue=$?
         if [[ ${exitValue} -gt 0 ]]; then
@@ -411,8 +412,8 @@ case $1 in
         bcChange=$(echo "${updated}" | grep "backend/config/template.json")
         if [[ ( $2 == "auto" && -z $dbChange && -z $fcChange && -z $bcChange && -z $musareshChange ) || -z $2 ]]; then
             if [[ -n $musareshChange && $(git diff @\{u\} -- musare.sh) != "" ]]; then
-                if [[ $(git diff HEAD -- musare.sh) != "" ]]; then
-                    echo -e "${RED}musare.sh has been modified, please reset or commit these changes and run the update command again to continue.${NC}"
+                if [[ $musareshModified != "" ]]; then
+                    echo -e "${RED}musare.sh has been modified, please reset these changes and run the update command again to continue.${NC}"
                 else
                     git checkout @\{u\} -- musare.sh
                     echo -e "${YELLOW}musare.sh has been updated, please run the update command again to continue.${NC}"