Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
142 changes: 54 additions & 88 deletions apps/frontend/src/components/ui/moderation/ModerationTechRevCard.vue
Original file line number Diff line number Diff line change
@@ -1,33 +1,24 @@
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import {
CheckIcon,
CodeIcon,
DownloadIcon,
ExternalIcon,
TimerIcon,
VersionIcon,
} from '@modrinth/assets'
import { CheckIcon, CodeIcon, ExternalIcon, TimerIcon, VersionIcon } from '@modrinth/assets'
import {
Avatar,
ButtonLink,
CopyCode,
CopyLinkButton,
getProjectTypeIcon,
NavTabs,
useFormatBytes,
} from '@modrinth/ui'
import { capitalizeString, formatProjectType } from '@modrinth/utils'
import { computed, provide, ref, watch } from 'vue'

import type { UnsafeFile } from '~/components/ui/moderation/MaliciousSummaryModal.vue'
import {
getFileHighestSeverity,
getHighestSeverity,
getSeverityBadgeColor,
getVersionLabel,
getVersionPageHref,
severityOrder,
} from '~/components/ui/moderation/tech-review/helpers'
import TechRevFileActions from '~/components/ui/moderation/tech-review/TechRevFileActions.vue'
import TechRevFileDetailTab from '~/components/ui/moderation/tech-review/TechRevFileDetailTab.vue'
import TechRevFilesTab from '~/components/ui/moderation/tech-review/TechRevFilesTab.vue'
import TechRevThreadTab from '~/components/ui/moderation/tech-review/TechRevThreadTab.vue'
Expand All @@ -51,8 +42,6 @@ const props = defineProps<{
disableCollapsing?: boolean
}>()

const formatBytes = useFormatBytes()

const emit = defineEmits<{
refetch: []
loadIssueSources: [issueIds: string[]]
Expand Down Expand Up @@ -90,14 +79,11 @@ watch(selectedFile, (newFile) => {
}
})

const highestSeverity = computed(() => {
let highest: Labrinth.TechReview.Internal.DelphiSeverity = 'low'
for (const report of props.item.reports) {
const severity = getFileHighestSeverity(report)
if (severityOrder[severity] > severityOrder[highest]) highest = severity
}
return highest
})
const highestSeverity = computed(() =>
getHighestSeverity(
props.item.reports.flatMap((report) => report.issues.flatMap((issue) => issue.details)),
),
)

const navTabsLinks = computed(() => {
const links = tabs.map((tab) => ({
Expand Down Expand Up @@ -189,9 +175,9 @@ watch(
class="shadow-card overflow-hidden rounded-2xl border border-solid border-surface-4 bg-surface-3"
>
<div
class="flex flex-col gap-4 border-0 border-b border-solid border-surface-4 bg-surface-3 p-4"
class="flex flex-col gap-3 border-0 border-b border-solid border-surface-4 bg-surface-3 p-4 pb-3"
>
<div class="flex items-start justify-between">
<div class="flex flex-wrap items-start justify-between">
<div class="flex items-center gap-3">
<NuxtLink
:to="`/${item.project.project_types[0]}/${item.project.slug ?? item.project.id}`"
Expand All @@ -206,7 +192,7 @@ watch(
</NuxtLink>

<div class="flex flex-col gap-1.5">
<div class="flex items-center gap-2">
<div class="flex flex-wrap items-center gap-2">
<NuxtLink
:to="`/${item.project.project_types[0]}/${item.project.slug ?? item.project.id}`"
target="_blank"
Expand Down Expand Up @@ -275,79 +261,59 @@ watch(
</div>
</div>

<div class="flex items-center gap-3">
<span class="text-base text-secondary">{{ formattedDate }}</span>
<div class="flex items-center gap-2">
<ButtonLink
v-if="props.item.project.link_urls?.['source']?.url"
v-tooltip="'Open sources in new tab'"
:href="props.item.project.link_urls?.['source']?.url"
target="_blank"
class="!w-9 !rounded-full !px-0"
>
<CodeIcon />
</ButtonLink>
<CopyCode v-tooltip="'Copy project ID'" :text="item.project.id" />
<CopyLinkButton
copy-label="Copy project link"
:url="`https://modrinth.com/moderation/technical-review/${props.item.project.id}`"
/>
<ButtonLink
v-tooltip="'Open tech review in new tab'"
:href="`/moderation/technical-review/${props.item.project.id}`"
target="_blank"
circular
icon-only
>
<ExternalIcon />
</ButtonLink>
<div class="flex flex-col items-end gap-2">
<div class="flex flex-wrap items-center justify-end gap-3">
<span class="text-base text-secondary">{{ formattedDate }}</span>
<div class="flex items-center gap-2">
<ButtonLink
v-if="props.item.project.link_urls?.['source']?.url"
v-tooltip="'Open sources in new tab'"
:href="props.item.project.link_urls?.['source']?.url"
target="_blank"
circular
icon-only
>
<CodeIcon />
</ButtonLink>
<CopyLinkButton
copy-label="Copy project link"
:url="`https://modrinth.com/moderation/technical-review/${props.item.project.id}`"
/>
<ButtonLink
v-tooltip="'Open tech review in new tab'"
:href="`/moderation/technical-review/${props.item.project.id}`"
target="_blank"
circular
icon-only
>
<ExternalIcon />
</ButtonLink>
</div>
</div>
<CopyCode v-tooltip="'Copy project ID'" :text="item.project.id" />
</div>
</div>
<div class="flex flex-row justify-between">
<div class="flex flex-row flex-wrap justify-between">
<NavTabs
mode="local"
:links="navTabsLinks"
:active-index="activeTabIndex"
class="border border-solid border-surface-4 bg-surface-2"
@tab-click="handleTabClick"
/>

<div v-if="currentTab === 'File' && selectedFile" class="flex flex-row items-end gap-2">
<ButtonLink
type="outlined"
target="_blank"
:href="getVersionPageHref(item.project, selectedFile.version_id)"
class="!bg-surface-2"
:aria-label="`Open version ${getVersionLabel(selectedFile)}`"
>
<VersionIcon aria-hidden="true" />
{{ getVersionLabel(selectedFile) }}
</ButtonLink>
<ButtonLink
type="outlined"
:target="selectedFile.file_id"
:href="`https://slicer.run/?url=${encodeURIComponent(selectedFile.download_url)}`"
class="!bg-surface-2"
aria-label="Open in Slicer"
>
<ExternalIcon aria-hidden="true" /> Slicer
</ButtonLink>
<ButtonLink
v-tooltip="
`Download ${selectedFile.file_name} (${formatBytes(selectedFile.file_size)})`
"
type="outlined"
target="_blank"
:href="selectedFile.download_url"
:download="selectedFile.file_name"
class="!bg-surface-2"
aria-label="Download"
icon-only
circular
>
<DownloadIcon aria-hidden="true" />
</ButtonLink>
<div v-if="currentTab === 'File' && selectedFile" class="flex items-center gap-2">
<div class="flex items-center gap-1">
<ButtonLink
v-tooltip="'View version'"
type="outlined"
target="_blank"
:href="getVersionPageHref(item.project, selectedFile.version_id)"
>
<VersionIcon aria-hidden="true" />
{{ selectedFile.version_number }}
</ButtonLink>
</div>
<TechRevFileActions :file="selectedFile" />
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<script setup lang="ts">
import { moderationSettings, type SettingDefinition } from '@modrinth/moderation'
import { Combobox, Toggle } from '@modrinth/ui'
import { Button, Combobox, Input, Toggle } from '@modrinth/ui'

const localhostHostname = 'localhost:3000'

const flattenedSettings = Object.entries(moderationSettings).reduce(
(acc, [group, settings]) => {
Expand Down Expand Up @@ -37,25 +39,26 @@ const displayedSettings = ref<{ [name: string]: SettingDefinition[] }>(flattened
<template>
<div v-for="[name, page] of Object.entries(displayedSettings)" :key="name" class="universal-card">
<h2 class="text-2xl">{{ name }}</h2>
<div class="flex flex-col gap-2">
<div class="flex flex-col gap-3">
<div
v-for="setting in page"
:key="setting.id"
class="flex flex-row flex-wrap items-center justify-between gap-2"
>
<label class="flex-1">
<span class="block font-semibold text-contrast">{{ setting.title }}</span>
<span class="mb-1 block font-semibold text-contrast">{{ setting.title }}</span>
<span class="block text-secondary">{{ setting.description }}</span>
<span class="block text-secondary">
<span class="mt-1 block text-secondary">
Default:
<span
class="font-semibold"
class="font-medium"
:class="{
'text-red': setting.type === 'toggle' && !setting.default,
'text-green': setting.type === 'toggle' && setting.default,
'italic opacity-50': setting.default === null,
}"
>{{ setting.default }}</span
>
>{{ setting.default ?? '(none)' }}
</span>
</span>
</label>

Expand All @@ -72,15 +75,23 @@ const displayedSettings = ref<{ [name: string]: SettingDefinition[] }>(flattened
class="!w-1/4"
@update:model-value="(value) => configuredSettings.set(setting, value)"
/>
<input
v-if="setting.type === 'string'"
type="text"
:value="configuredSettings.get(setting)"
class="input !w-1/4"
@input="
(event) => configuredSettings.set(setting, (event.target as HTMLInputElement).value)
"
/>
<div v-if="setting.type === 'string'" class="flex !w-1/4 flex-col items-start gap-2">
<Input
type="text"
:model-value="configuredSettings.get(setting) ?? ''"
@update:model-value="(value) => configuredSettings.set(setting, String(value ?? ''))"
/>
<Button
v-if="setting.id === 'alternative-hostname'"
type="outlined"
size="sm"
class="shrink-0"
:disabled="configuredSettings.get(setting) === localhostHostname"
@click="configuredSettings.set(setting, localhostHostname)"
>
Set to {{ localhostHostname }}
</Button>
</div>
</div>
</div>
</div>
Expand Down
Loading
Loading