From ee73baea49c81d6e8d8ab15da64186cdc8ea37b9 Mon Sep 17 00:00:00 2001 From: IMB11 Date: Wed, 16 Jul 2025 18:44:41 +0100 Subject: [PATCH 1/7] fix: handle identified files from the backend --- .../ui/moderation/ModpackPermissionsFlow.vue | 37 ++++++++++++++ .../ui/moderation/NewModerationChecklist.vue | 49 ++++++++++--------- packages/utils/types.ts | 25 ++++++++-- 3 files changed, 85 insertions(+), 26 deletions(-) diff --git a/apps/frontend/src/components/ui/moderation/ModpackPermissionsFlow.vue b/apps/frontend/src/components/ui/moderation/ModpackPermissionsFlow.vue index 124a49336f..def0d8c3fc 100644 --- a/apps/frontend/src/components/ui/moderation/ModpackPermissionsFlow.vue +++ b/apps/frontend/src/components/ui/moderation/ModpackPermissionsFlow.vue @@ -167,6 +167,7 @@ const props = defineProps<{ const emit = defineEmits<{ complete: []; "update:modelValue": [judgements: ModerationJudgements]; + "update:allFiles": [allFiles: ModerationModpackItem[]]; }>(); const persistedModPackData = useLocalStorage( @@ -252,6 +253,25 @@ async function fetchModPackData(): Promise { internal: true, })) as ModerationModpackResponse; const sortedData: ModerationModpackItem[] = [ + ...Object.entries(data.identified || {}) + .filter( + ([_, file]) => file.status !== "yes" && file.status !== "with-attribution-and-source", + ) + .map( + ([sha1, file]): ModerationModpackItem => ({ + sha1, + file_name: file.file_name, + type: "identified", + status: file.status, + approved: null, + ...(file.status === "unidentified" && { + proof: "", + url: "", + title: "", + }), + }), + ) + .sort((a, b) => a.file_name.localeCompare(b.file_name)), ...Object.entries(data.unknown_files || {}) .map( ([sha1, fileName]): ModerationUnknownModpackItem => ({ @@ -321,6 +341,21 @@ function goToPrevious(): void { } } +function emitAllFiles(): void { + if (modPackData.value) { + emit("update:allFiles", modPackData.value); + } +} + +watch( + modPackData, + (newValue) => { + persistedModPackData.value = newValue; + emitAllFiles(); + }, + { deep: true }, +); + function goToNext(): void { if (modPackData.value && currentIndex.value < modPackData.value.length) { currentIndex.value++; @@ -342,6 +377,7 @@ function setStatus(index: number, status: ModerationModpackPermissionApprovalTyp modPackData.value[index].approved = null; persistAll(); emit("update:modelValue", getJudgements()); + emitAllFiles(); } } @@ -350,6 +386,7 @@ function setApproval(index: number, approved: ModerationPermissionType["id"]): v modPackData.value[index].approved = approved; persistAll(); emit("update:modelValue", getJudgements()); + emitAllFiles(); } } diff --git a/apps/frontend/src/components/ui/moderation/NewModerationChecklist.vue b/apps/frontend/src/components/ui/moderation/NewModerationChecklist.vue index 1b1d0fb6db..e6a5b7b44d 100644 --- a/apps/frontend/src/components/ui/moderation/NewModerationChecklist.vue +++ b/apps/frontend/src/components/ui/moderation/NewModerationChecklist.vue @@ -64,6 +64,7 @@ v-model="modpackJudgements" :project-id="project.id" @complete="handleModpackPermissionsComplete" + @update:allFiles="allModpackFiles = $event" />
@@ -368,7 +369,12 @@ import { DropdownSelect, MarkdownEditor, } from "@modrinth/ui"; -import { type Project, renderHighlightedString, type ModerationJudgements } from "@modrinth/utils"; +import { + type Project, + renderHighlightedString, + type ModerationJudgements, + type ModerationModpackItem, +} from "@modrinth/utils"; import { computedAsync, useLocalStorage } from "@vueuse/core"; import type { Action, @@ -408,6 +414,7 @@ const futureProjectCount = computed(() => { const modpackPermissionsComplete = ref(false); const modpackJudgements = ref({}); +const allModpackFiles = ref([]); const isModpackPermissionsStage = computed(() => { return currentStageObj.value.id === "modpack-permissions"; }); @@ -441,6 +448,7 @@ function resetProgress() { localStorage.removeItem(`modpack-permissions-index-${props.project.id}`); modpackPermissionsComplete.value = false; modpackJudgements.value = {}; + allModpackFiles.value = []; initializeAllStages(); } @@ -1080,11 +1088,8 @@ async function generateMessage() { const baseMessage = await assembleFullMessage(); let fullMessage = baseMessage; - if ( - props.project.project_type === "modpack" && - Object.keys(modpackJudgements.value).length > 0 - ) { - const modpackMessage = generateModpackMessage(modpackJudgements.value); + if (props.project.project_type === "modpack" && allModpackFiles.value.length > 0) { + const modpackMessage = generateModpackMessage(allModpackFiles.value); if (modpackMessage) { fullMessage = baseMessage ? `${baseMessage}\n\n${modpackMessage}` : modpackMessage; } @@ -1117,25 +1122,25 @@ async function generateMessage() { } } -function generateModpackMessage(judgements: ModerationJudgements) { +function generateModpackMessage(allFiles: ModerationModpackItem[]) { const issues = []; - const attributeMods = []; - const noMods = []; - const permanentNoMods = []; - const unidentifiedMods = []; - - for (const [, judgement] of Object.entries(judgements)) { - if (judgement.status === "with-attribution") { - attributeMods.push(judgement.file_name); - } else if (judgement.status === "no") { - noMods.push(judgement.file_name); - } else if (judgement.status === "permanent-no") { - permanentNoMods.push(judgement.file_name); - } else if (judgement.status === "unidentified") { - unidentifiedMods.push(judgement.file_name); + const attributeMods: string[] = []; + const noMods: string[] = []; + const permanentNoMods: string[] = []; + const unidentifiedMods: string[] = []; + + allFiles.forEach((file) => { + if (file.status === "with-attribution" && !file.approved) { + attributeMods.push(file.file_name); + } else if (file.status === "no" && !file.approved) { + noMods.push(file.file_name); + } else if (file.status === "permanent-no") { + permanentNoMods.push(file.file_name); + } else if (file.status === "unidentified" && !file.approved) { + unidentifiedMods.push(file.file_name); } - } + }); if ( attributeMods.length > 0 || diff --git a/packages/utils/types.ts b/packages/utils/types.ts index 437fda2d68..a56bc3c569 100644 --- a/packages/utils/types.ts +++ b/packages/utils/types.ts @@ -315,7 +315,7 @@ export interface ModerationPermissionType { export interface ModerationBaseModpackItem { sha1: string file_name: string - type: 'unknown' | 'flame' + type: 'unknown' | 'flame' | 'identified' status: ModerationModpackPermissionApprovalType['id'] | null approved: ModerationPermissionType['id'] | null } @@ -334,9 +334,26 @@ export interface ModerationFlameModpackItem extends ModerationBaseModpackItem { url: string } -export type ModerationModpackItem = ModerationUnknownModpackItem | ModerationFlameModpackItem +export interface ModerationIdentifiedModpackItem extends ModerationBaseModpackItem { + type: 'identified' + proof?: string + url?: string + title?: string +} + +export type ModerationModpackItem = + | ModerationUnknownModpackItem + | ModerationFlameModpackItem + | ModerationIdentifiedModpackItem export interface ModerationModpackResponse { + identified?: Record< + string, + { + file_name: string + status: ModerationModpackPermissionApprovalType['id'] + } + > unknown_files?: Record flame_files?: Record< string, @@ -350,8 +367,8 @@ export interface ModerationModpackResponse { } export interface ModerationJudgement { - type: 'flame' | 'unknown' - status: string + type: 'flame' | 'unknown' | 'identified' + status: string | null id?: string link?: string title?: string From d326e2119749a661b4c5f2f768f61bc9b41773b1 Mon Sep 17 00:00:00 2001 From: IMB11 Date: Wed, 16 Jul 2025 18:48:35 +0100 Subject: [PATCH 2/7] fix: allFiles not being emitted after permissions flow completed --- .../src/components/ui/moderation/ModpackPermissionsFlow.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/frontend/src/components/ui/moderation/ModpackPermissionsFlow.vue b/apps/frontend/src/components/ui/moderation/ModpackPermissionsFlow.vue index def0d8c3fc..a24d5aad8e 100644 --- a/apps/frontend/src/components/ui/moderation/ModpackPermissionsFlow.vue +++ b/apps/frontend/src/components/ui/moderation/ModpackPermissionsFlow.vue @@ -363,6 +363,7 @@ function goToNext(): void { if (currentIndex.value >= modPackData.value.length) { const judgements = getJudgements(); emit("update:modelValue", judgements); + emitAllFiles(); emit("complete"); clearPersistedData(); } else { From 002a112629a806a8cce249d21f137ab13d1cc35c Mon Sep 17 00:00:00 2001 From: IMB11 Date: Wed, 16 Jul 2025 19:32:42 +0100 Subject: [PATCH 3/7] fix: properly handle identified projects --- .../ui/moderation/ModpackPermissionsFlow.vue | 32 +++++++++++-- .../ui/moderation/NewModerationChecklist.vue | 46 +++++++++---------- 2 files changed, 50 insertions(+), 28 deletions(-) diff --git a/apps/frontend/src/components/ui/moderation/ModpackPermissionsFlow.vue b/apps/frontend/src/components/ui/moderation/ModpackPermissionsFlow.vue index a24d5aad8e..0def8aa534 100644 --- a/apps/frontend/src/components/ui/moderation/ModpackPermissionsFlow.vue +++ b/apps/frontend/src/components/ui/moderation/ModpackPermissionsFlow.vue @@ -167,7 +167,9 @@ const props = defineProps<{ const emit = defineEmits<{ complete: []; "update:modelValue": [judgements: ModerationJudgements]; - "update:allFiles": [allFiles: ModerationModpackItem[]]; + "update:allFiles": [ + allFiles: { interactive: ModerationModpackItem[]; permanentNo: ModerationModpackItem[] }, + ]; }>(); const persistedModPackData = useLocalStorage( @@ -184,6 +186,7 @@ const persistedModPackData = useLocalStorage( const persistedIndex = useLocalStorage(`modpack-permissions-index-${props.projectId}`, 0); const modPackData = ref(null); +const permanentNoFiles = ref([]); const currentIndex = ref(0); const fileApprovalTypes: ModerationModpackPermissionApprovalType[] = [ @@ -252,10 +255,29 @@ async function fetchModPackData(): Promise { const data = (await useBaseFetch(`moderation/project/${props.projectId}`, { internal: true, })) as ModerationModpackResponse; + + const permanentNoItems: ModerationModpackItem[] = Object.entries(data.identified || {}) + .filter(([_, file]) => file.status === "permanent-no") + .map( + ([sha1, file]): ModerationModpackItem => ({ + sha1, + file_name: file.file_name, + type: "identified", + status: file.status, + approved: null, + }), + ) + .sort((a, b) => a.file_name.localeCompare(b.file_name)); + + permanentNoFiles.value = permanentNoItems; + const sortedData: ModerationModpackItem[] = [ ...Object.entries(data.identified || {}) .filter( - ([_, file]) => file.status !== "yes" && file.status !== "with-attribution-and-source", + ([_, file]) => + file.status !== "yes" && + file.status !== "with-attribution-and-source" && + file.status !== "permanent-no", ) .map( ([sha1, file]): ModerationModpackItem => ({ @@ -330,6 +352,7 @@ async function fetchModPackData(): Promise { } catch (error) { console.error("Failed to fetch modpack data:", error); modPackData.value = []; + permanentNoFiles.value = []; persistAll(); } } @@ -343,7 +366,10 @@ function goToPrevious(): void { function emitAllFiles(): void { if (modPackData.value) { - emit("update:allFiles", modPackData.value); + emit("update:allFiles", { + interactive: modPackData.value, + permanentNo: permanentNoFiles.value, + }); } } diff --git a/apps/frontend/src/components/ui/moderation/NewModerationChecklist.vue b/apps/frontend/src/components/ui/moderation/NewModerationChecklist.vue index e6a5b7b44d..cc74a8be9a 100644 --- a/apps/frontend/src/components/ui/moderation/NewModerationChecklist.vue +++ b/apps/frontend/src/components/ui/moderation/NewModerationChecklist.vue @@ -241,24 +241,6 @@
- - - - - - -