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