Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ import type {
} from "@modrinth/utils";
import { ButtonStyled } from "@modrinth/ui";
import { ref, computed, watch, onMounted } from "vue";
import { useLocalStorage } from "@vueuse/core";
import { useLocalStorage, useSessionStorage } from "@vueuse/core";

const props = defineProps<{
projectId: string;
Expand All @@ -182,7 +182,26 @@ const persistedModPackData = useLocalStorage<ModerationModpackItem[] | null>(

const persistedIndex = useLocalStorage<number>(`modpack-permissions-index-${props.projectId}`, 0);

const modPackData = ref<ModerationModpackItem[] | null>(null);
const modPackData = useSessionStorage<ModerationModpackItem[] | null>(
`modpack-permissions-data-${props.projectId}`,
null,
{
serializer: {
read: (v: any) => (v ? JSON.parse(v) : null),
write: (v: any) => JSON.stringify(v),
},
},
);
const permanentNoFiles = useSessionStorage<ModerationModpackItem[]>(
`modpack-permissions-permanent-no-${props.projectId}`,
[],
{
serializer: {
read: (v: any) => (v ? JSON.parse(v) : []),
write: (v: any) => JSON.stringify(v),
},
},
);
const currentIndex = ref(0);

const fileApprovalTypes: ModerationModpackPermissionApprovalType[] = [
Expand Down Expand Up @@ -251,7 +270,45 @@ async function fetchModPackData(): Promise<void> {
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.status !== "permanent-no",
)
.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 => ({
Expand Down Expand Up @@ -310,6 +367,7 @@ async function fetchModPackData(): Promise<void> {
} catch (error) {
console.error("Failed to fetch modpack data:", error);
modPackData.value = [];
permanentNoFiles.value = [];
persistAll();
}
}
Expand All @@ -321,6 +379,14 @@ function goToPrevious(): void {
}
}

watch(
modPackData,
(newValue) => {
persistedModPackData.value = newValue;
},
{ deep: true },
);

function goToNext(): void {
if (modPackData.value && currentIndex.value < modPackData.value.length) {
currentIndex.value++;
Expand Down Expand Up @@ -406,6 +472,20 @@ watch(
}
},
);

function getModpackFiles(): {
interactive: ModerationModpackItem[];
permanentNo: ModerationModpackItem[];
} {
return {
interactive: modPackData.value || [],
permanentNo: permanentNoFiles.value,
};
}

defineExpose({
getModpackFiles,
});
</script>

<style scoped>
Expand Down
136 changes: 78 additions & 58 deletions apps/frontend/src/components/ui/moderation/NewModerationChecklist.vue
Original file line number Diff line number Diff line change
Expand Up @@ -240,24 +240,6 @@
</div>

<div v-else-if="generatedMessage" class="flex items-center gap-2">
<OverflowMenu :options="stageOptions" class="bg-transparent p-0">
<ButtonStyled circular>
<button v-tooltip="`Stages`">
<ListBulletedIcon />
</button>
</ButtonStyled>

<template
v-for="opt in stageOptions.filter(
(opt) => 'id' in opt && 'text' in opt && 'icon' in opt,
)"
#[opt.id]
:key="opt.id"
>
<component :is="opt.icon" v-if="opt.icon" class="mr-2" />
{{ opt.text }}
</template>
</OverflowMenu>
<ButtonStyled>
<button @click="goBackToStages">
<LeftArrowIcon aria-hidden="true" />
Expand Down Expand Up @@ -368,21 +350,26 @@ 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,
MultiSelectChipsAction,
DropdownAction,
ButtonAction,
ToggleAction,
ConditionalButtonAction,
Stage,
import {
type Action,
type MultiSelectChipsAction,
type DropdownAction,
type ButtonAction,
type ToggleAction,
type ConditionalButtonAction,
type Stage,
finalPermissionMessages,
} from "@modrinth/moderation";
import * as prettier from "prettier";
import ModpackPermissionsFlow from "./ModpackPermissionsFlow.vue";
import KeybindsModal from "./ChecklistKeybindsModal.vue";
import { finalPermissionMessages } from "@modrinth/moderation/data/modpack-permissions-stage";
import prettier from "prettier";

const keybindsModal = ref<InstanceType<typeof KeybindsModal>>();

Expand Down Expand Up @@ -823,6 +810,31 @@ const isAnyVisibleInputs = computed(() => {
});
});

function getModpackFilesFromStorage(): {
interactive: ModerationModpackItem[];
permanentNo: ModerationModpackItem[];
} {
try {
const sessionData = sessionStorage.getItem(`modpack-permissions-data-${props.project.id}`);
const interactive = sessionData ? (JSON.parse(sessionData) as ModerationModpackItem[]) : [];

const permanentNoData = sessionStorage.getItem(
`modpack-permissions-permanent-no-${props.project.id}`,
);
const permanentNo = permanentNoData
? (JSON.parse(permanentNoData) as ModerationModpackItem[])
: [];

return {
interactive: interactive || [],
permanentNo: permanentNo || [],
};
} catch (error) {
console.warn("Failed to parse session storage modpack data:", error);
return { interactive: [], permanentNo: [] };
}
}

async function assembleFullMessage() {
const messageParts: MessagePart[] = [];

Expand Down Expand Up @@ -1092,13 +1104,14 @@ 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 (modpackMessage) {
fullMessage = baseMessage ? `${baseMessage}\n\n${modpackMessage}` : modpackMessage;
if (props.project.project_type === "modpack") {
const modpackFilesData = getModpackFilesFromStorage();

if (modpackFilesData.interactive.length > 0 || modpackFilesData.permanentNo.length > 0) {
const modpackMessage = generateModpackMessage(modpackFilesData);
if (modpackMessage) {
fullMessage = baseMessage ? `${baseMessage}\n\n${modpackMessage}` : modpackMessage;
}
}
}

Expand Down Expand Up @@ -1129,25 +1142,32 @@ async function generateMessage() {
}
}

function generateModpackMessage(judgements: ModerationJudgements) {
function generateModpackMessage(allFiles: {
interactive: ModerationModpackItem[];
permanentNo: 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.interactive.forEach((file) => {
if (file.status === "unidentified") {
if (file.approved === "no") {
unidentifiedMods.push(file.file_name);
}
} else if (file.status === "with-attribution" && file.approved === "no") {
attributeMods.push(file.file_name);
} else if (file.status === "no" && file.approved === "no") {
noMods.push(file.file_name);
}
}
});

allFiles.permanentNo.forEach((file) => {
permanentNoMods.push(file.file_name);
});

if (
attributeMods.length > 0 ||
Expand All @@ -1157,6 +1177,12 @@ function generateModpackMessage(judgements: ModerationJudgements) {
) {
issues.push("## Copyrighted content");

if (unidentifiedMods.length > 0) {
issues.push(
`${finalPermissionMessages.unidentified}\n${unidentifiedMods.map((mod) => `- ${mod}`).join("\n")}`,
);
}

if (attributeMods.length > 0) {
issues.push(
`${finalPermissionMessages["with-attribution"]}\n${attributeMods.map((mod) => `- ${mod}`).join("\n")}`,
Expand All @@ -1172,12 +1198,6 @@ function generateModpackMessage(judgements: ModerationJudgements) {
`${finalPermissionMessages["permanent-no"]}\n${permanentNoMods.map((mod) => `- ${mod}`).join("\n")}`,
);
}

if (unidentifiedMods.length > 0) {
issues.push(
`${finalPermissionMessages["unidentified"]}\n${unidentifiedMods.map((mod) => `- ${mod}`).join("\n")}`,
);
}
}

return issues.join("\n\n");
Expand Down
1 change: 1 addition & 0 deletions packages/moderation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './types/messages'
export * from './types/stage'
export * from './types/keybinds'
export * from './utils'
export { finalPermissionMessages } from './data/modpack-permissions-stage'

export { default as checklist } from './data/checklist'
export { default as keybinds } from './data/keybinds'
25 changes: 21 additions & 4 deletions packages/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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<string, string>
flame_files?: Record<
string,
Expand All @@ -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
Expand Down