mirror of
https://github.com/modrinth/code.git
synced 2026-08-31 03:55:59 +00:00
Merge branch 'main' of github.com:modrinth/code into boris/dev-1205-trace-rules
# Conflicts: # apps/frontend/src/components/ui/moderation/GlobalDetailLocalTraceCard.vue # apps/frontend/src/pages/moderation.vue # apps/labrinth/.sqlx/query-06384971752b20c04d7a747777bf03a765ab60c2b029a47fcd3fe97c02ee5b2a.json # apps/labrinth/.sqlx/query-1551b022217df05490a01e715b3584d26550d01a1917d1af64fe236d63dd5005.json # apps/labrinth/.sqlx/query-33d4e31565ece4a99832cbfd0a39bc49666e9fc8a400a5c41660504205e7cac5.json # apps/labrinth/.sqlx/query-4680c4a59c6679f90e3b9e1a33ed1cb1fb60b93ffb79ba5b99e01ee0c14c991a.json # apps/labrinth/.sqlx/query-5a68c53bd00c08edf9dfb9ffdabc28f86cdd77cd06bbb10c9a8e5199509f9c4b.json # apps/labrinth/.sqlx/query-87d30e8802ebe69858141d0d918cafb5286f984cfe55ad1a7ac2219ec50539a4.json # apps/labrinth/.sqlx/query-aedd5d9fe052a9fa70e585f389c3b4915a88cb9c3ec54493a2a29582c80b5c76.json # apps/labrinth/.sqlx/query-befd8c75e7ed4621f5e99d88719c0fde9d617b6603b99b75cc18d1a02387bac4.json # apps/labrinth/.sqlx/query-c268a2a256f11a65449bd132a14f931c2e9776ffed87f4406bb5c2bc30087f5b.json # apps/labrinth/.sqlx/query-c40c2085202bc4486568fb24db9201239cefbe174f4a5cc4b8eb21a10268cd73.json # apps/labrinth/.sqlx/query-d37041da5452ecb82da506a83154cd66b78ebb9a1dbf0bb46e5bd6bb2b3a9bb9.json # apps/labrinth/.sqlx/query-d3d320af2786bf7908e65f5b7a35ade08110fb341d694ca0f25eecca2f5ad5bf.json # apps/labrinth/src/routes/internal/delphi/tech_review_sync.rs # apps/labrinth/src/routes/internal/moderation/tech_review/global.rs
This commit is contained in:
@@ -21,9 +21,9 @@ import {
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { type TechReviewContext, techReviewQuickReplies } from '@modrinth/moderation'
|
||||
import { Button, ButtonLink, IconButton, TeleportOverflowMenu } from '@modrinth/ui'
|
||||
import {
|
||||
Avatar,
|
||||
ButtonStyled,
|
||||
Collapsible,
|
||||
CollapsibleRegion,
|
||||
commonMessages,
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
NavTabs,
|
||||
OverflowMenu,
|
||||
type OverflowMenuOption,
|
||||
Toggle,
|
||||
useFormatBytes,
|
||||
@@ -120,29 +119,32 @@ const isLoadingStatusAction = ref(false)
|
||||
const projectStatusActions = computed<OverflowMenuOption[]>(() => [
|
||||
{
|
||||
id: 'approve',
|
||||
color: 'green',
|
||||
action: () => setStatus('approved'),
|
||||
label: 'Approve',
|
||||
tone: 'green',
|
||||
hoverFilled: true,
|
||||
action: () => setStatus('approved'),
|
||||
disabled: isStatusActionDisabled('approved'),
|
||||
},
|
||||
{
|
||||
id: 'withhold',
|
||||
color: 'orange',
|
||||
action: () => setStatus('withheld'),
|
||||
label: 'Withhold',
|
||||
tone: 'orange',
|
||||
hoverFilled: true,
|
||||
action: () => setStatus('withheld'),
|
||||
disabled: isStatusActionDisabled('withheld'),
|
||||
},
|
||||
{
|
||||
id: 'send-to-review',
|
||||
label: 'Send to review',
|
||||
action: () => setStatus('processing'),
|
||||
hoverFilled: true,
|
||||
disabled: isStatusActionDisabled('processing'),
|
||||
},
|
||||
{
|
||||
id: 'reject',
|
||||
color: 'red',
|
||||
action: () => setStatus('rejected'),
|
||||
label: 'Reject',
|
||||
tone: 'red',
|
||||
hoverFilled: true,
|
||||
action: () => setStatus('rejected'),
|
||||
disabled: isStatusActionDisabled('rejected'),
|
||||
},
|
||||
])
|
||||
@@ -886,6 +888,10 @@ async function updateDetailStatus(
|
||||
|
||||
updatingDetails.add(detailId)
|
||||
|
||||
const previousMarkedCount = selectedFile.value
|
||||
? getFileMarkedCount(selectedFile.value)
|
||||
: undefined
|
||||
|
||||
try {
|
||||
await updateIssueDetails([{ detail_id: detailId, verdict }])
|
||||
|
||||
@@ -910,7 +916,7 @@ async function updateDetailStatus(
|
||||
if (verdict !== 'pending' && selectedFile.value) {
|
||||
const markedCount = getFileMarkedCount(selectedFile.value)
|
||||
const totalCount = getFileDetailCount(selectedFile.value)
|
||||
if (markedCount === totalCount) {
|
||||
if (previousMarkedCount != markedCount && markedCount === totalCount) {
|
||||
backToFileList()
|
||||
}
|
||||
}
|
||||
@@ -968,6 +974,10 @@ async function updateGlobalDetailStatus(
|
||||
|
||||
updatingGlobalDetailKeys.add(detail.key)
|
||||
|
||||
const previousMarkedCount = selectedFile.value
|
||||
? getFileMarkedCount(selectedFile.value)
|
||||
: undefined
|
||||
|
||||
try {
|
||||
await updateGlobalIssueDetail(detail.key, verdict)
|
||||
|
||||
@@ -988,7 +998,7 @@ async function updateGlobalDetailStatus(
|
||||
if (verdict !== 'pending' && selectedFile.value) {
|
||||
const markedCount = getFileMarkedCount(selectedFile.value)
|
||||
const totalCount = getFileDetailCount(selectedFile.value)
|
||||
if (markedCount === totalCount) {
|
||||
if (previousMarkedCount != markedCount && markedCount === totalCount) {
|
||||
backToFileList()
|
||||
}
|
||||
}
|
||||
@@ -1387,8 +1397,10 @@ const canSubmitReview = computed(() => {
|
||||
if (totalIssues === 0) return true
|
||||
return allIssuesResolved.value
|
||||
})
|
||||
const hasSubmittedPassReview = ref(false)
|
||||
|
||||
async function handleSubmitReview(verdict: 'safe' | 'unsafe') {
|
||||
hasSubmittedPassReview.value = verdict === 'safe'
|
||||
const editorContent = threadViewRef.value?.getReplyContent() || ''
|
||||
|
||||
let message: string | undefined
|
||||
@@ -1529,42 +1541,65 @@ function copyId() {
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-base text-secondary">{{ formattedDate }}</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<ButtonStyled v-if="props.item.project.link_urls?.['source']?.url" circular>
|
||||
<a
|
||||
v-tooltip="'Open sources in new tab'"
|
||||
:href="props.item.project.link_urls?.['source']?.url"
|
||||
target="_blank"
|
||||
>
|
||||
<CodeIcon />
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular>
|
||||
<button v-tooltip="'Copy ID'" @click="copyId">
|
||||
<ClipboardCopyIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular>
|
||||
<a
|
||||
v-tooltip="'Open in new tab'"
|
||||
:href="`/moderation/technical-review/${props.item.project.id}`"
|
||||
target="_blank"
|
||||
>
|
||||
<ExternalIcon />
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<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>
|
||||
<IconButton v-tooltip="'Copy ID'" :label="'Copy ID'" @click="copyId">
|
||||
<ClipboardCopyIcon />
|
||||
</IconButton>
|
||||
<ButtonLink
|
||||
v-tooltip="'Open in new tab'"
|
||||
:href="`/moderation/technical-review/${props.item.project.id}`"
|
||||
target="_blank"
|
||||
class="!w-9 !rounded-full !px-0"
|
||||
>
|
||||
<ExternalIcon />
|
||||
</ButtonLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-px w-full bg-surface-5"></div>
|
||||
|
||||
<NavTabs
|
||||
mode="local"
|
||||
:links="navTabsLinks"
|
||||
:active-index="activeTabIndex"
|
||||
class="bg-surface-3! shadow-none!"
|
||||
@tab-click="handleTabClick"
|
||||
/>
|
||||
<div class="flex flex-row justify-between">
|
||||
<NavTabs
|
||||
mode="local"
|
||||
:links="navTabsLinks"
|
||||
:active-index="activeTabIndex"
|
||||
class="bg-surface-3! shadow-none!"
|
||||
@tab-click="handleTabClick"
|
||||
/>
|
||||
|
||||
<div v-if="selectedFile" class="flex flex-row items-end gap-2">
|
||||
<ButtonLink
|
||||
v-tooltip="`Download`"
|
||||
type="outlined"
|
||||
target="_blank"
|
||||
:href="selectedFile.download_url"
|
||||
:download="selectedFile.file_name"
|
||||
class="!bg-surface-2"
|
||||
aria-label="Download"
|
||||
>
|
||||
<DownloadIcon aria-hidden="true" />
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
v-tooltip="`Open in Slicer`"
|
||||
type="outlined"
|
||||
target="_blank"
|
||||
:href="`https://slicer.run/?url=${encodeURIComponent(selectedFile.download_url)}`"
|
||||
class="!bg-surface-2"
|
||||
aria-label="Open in Slicer"
|
||||
>
|
||||
<ExternalIcon aria-hidden="true" /> Open
|
||||
</ButtonLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-surface-3 bg-surface-2">
|
||||
@@ -1586,58 +1621,66 @@ function copyId() {
|
||||
@update-thread="handleThreadUpdate"
|
||||
>
|
||||
<template #additionalActions>
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
v-tooltip="!canSubmitReview ? 'There are still pending flags!' : undefined"
|
||||
:disabled="!canSubmitReview"
|
||||
@click="handleSubmitReview('safe')"
|
||||
>
|
||||
<ShieldCheckIcon /> Pass
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="red">
|
||||
<button
|
||||
v-tooltip="!canSubmitReview ? 'There are still pending flags!' : undefined"
|
||||
:disabled="!canSubmitReview"
|
||||
@click="handleSubmitReview('unsafe')"
|
||||
>
|
||||
<BugIcon /> Fail
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="standard">
|
||||
<OverflowMenu
|
||||
class="btn-dropdown-animation"
|
||||
:disabled="isLoadingStatusAction"
|
||||
:options="projectStatusActions"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="isLoadingStatusAction"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<ScaleIcon v-else aria-hidden="true" />
|
||||
Set Status
|
||||
<template #approve>
|
||||
<CheckIcon aria-hidden="true" />
|
||||
Approve
|
||||
</template>
|
||||
<template #withhold>
|
||||
<EyeOffIcon aria-hidden="true" />
|
||||
Withhold
|
||||
</template>
|
||||
<template #send-to-review>
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
Send to review
|
||||
</template>
|
||||
<template #reject>
|
||||
<XIcon aria-hidden="true" />
|
||||
Reject
|
||||
</template>
|
||||
</OverflowMenu>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled v-if="featureFlags.developerMode" type="outlined">
|
||||
<button @click="emit('showMaliciousSummary', unsafeFiles)">Debug</button>
|
||||
</ButtonStyled>
|
||||
<Button
|
||||
v-tooltip="
|
||||
!canSubmitReview
|
||||
? 'There are still pending flags!'
|
||||
: hasSubmittedPassReview
|
||||
? 'Project already passed!'
|
||||
: undefined
|
||||
"
|
||||
type="colored"
|
||||
color="brand"
|
||||
:disabled="!canSubmitReview || hasSubmittedPassReview"
|
||||
@click="handleSubmitReview('safe')"
|
||||
>
|
||||
<ShieldCheckIcon /> Pass
|
||||
</Button>
|
||||
<Button
|
||||
v-tooltip="!canSubmitReview ? 'There are still pending flags!' : undefined"
|
||||
type="colored"
|
||||
color="red"
|
||||
:disabled="!canSubmitReview"
|
||||
@click="handleSubmitReview('unsafe')"
|
||||
>
|
||||
<BugIcon /> Fail
|
||||
</Button>
|
||||
<TeleportOverflowMenu
|
||||
label="More options"
|
||||
class="btn-dropdown-animation !w-auto !rounded-xl !px-2.5"
|
||||
:disabled="isLoadingStatusAction"
|
||||
:options="projectStatusActions"
|
||||
>
|
||||
<SpinnerIcon
|
||||
v-if="isLoadingStatusAction"
|
||||
class="animate-spin"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<ScaleIcon v-else aria-hidden="true" />
|
||||
Set Status
|
||||
<template #approve>
|
||||
<CheckIcon aria-hidden="true" />
|
||||
Approve
|
||||
</template>
|
||||
<template #withhold>
|
||||
<EyeOffIcon aria-hidden="true" />
|
||||
Withhold
|
||||
</template>
|
||||
<template #send-to-review>
|
||||
<ScaleIcon aria-hidden="true" />
|
||||
Send to review
|
||||
</template>
|
||||
<template #reject>
|
||||
<XIcon aria-hidden="true" />
|
||||
Reject
|
||||
</template>
|
||||
</TeleportOverflowMenu>
|
||||
<Button
|
||||
v-if="featureFlags.developerMode"
|
||||
type="outlined"
|
||||
@click="emit('showMaliciousSummary', unsafeFiles)"
|
||||
>Debug</Button
|
||||
>
|
||||
</template>
|
||||
</ThreadView>
|
||||
</div>
|
||||
@@ -1708,19 +1751,25 @@ function copyId() {
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<ButtonStyled v-if="getFileDetailCount(file) > 0">
|
||||
<button @click="viewFileFlags(file)">Flags</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled type="outlined">
|
||||
<a
|
||||
:href="file.download_url"
|
||||
:title="`Download ${file.file_name}`"
|
||||
:download="file.file_name"
|
||||
tabindex="0"
|
||||
>
|
||||
<DownloadIcon /> Download
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<Button v-if="getFileDetailCount(file) > 0" @click="viewFileFlags(file)">Flags</Button>
|
||||
<ButtonLink
|
||||
type="outlined"
|
||||
:href="file.download_url"
|
||||
:title="`Download ${file.file_name}`"
|
||||
:download="file.file_name"
|
||||
tabindex="0"
|
||||
>
|
||||
<DownloadIcon />
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
v-tooltip="`Open in Slicer`"
|
||||
type="outlined"
|
||||
target="_blank"
|
||||
:href="`https://slicer.run/?url=${encodeURIComponent(file.download_url)}`"
|
||||
aria-label="Open in Slicer"
|
||||
>
|
||||
<ExternalIcon aria-hidden="true" /> Open
|
||||
</ButtonLink>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1875,14 +1924,14 @@ function copyId() {
|
||||
@click="toggleClass(classItem)"
|
||||
>
|
||||
<div class="my-auto flex items-center gap-2">
|
||||
<ButtonStyled type="transparent" circular>
|
||||
<button
|
||||
class="transition-transform"
|
||||
:class="{ 'rotate-180': expandedClasses.has(classItem.key) }"
|
||||
>
|
||||
<ChevronDownIcon class="h-5 w-5 text-contrast" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<IconButton
|
||||
type="quiet"
|
||||
label="Toggle details"
|
||||
class="transition-transform"
|
||||
:class="{ 'rotate-180': expandedClasses.has(classItem.key) }"
|
||||
>
|
||||
<ChevronDownIcon class="h-5 w-5 text-contrast" />
|
||||
</IconButton>
|
||||
|
||||
<IssueDetailPath
|
||||
:segments="[classItem.jar, classItem.filePath]"
|
||||
@@ -2100,16 +2149,16 @@ function copyId() {
|
||||
v-if="getHighlightedClassSource(classItem).length > 0"
|
||||
class="relative inset-0 overflow-hidden rounded-lg border border-solid border-surface-5 bg-surface-4"
|
||||
>
|
||||
<ButtonStyled circular type="transparent">
|
||||
<button
|
||||
v-tooltip="`Copy code`"
|
||||
class="absolute right-2 top-2 border-[1px]"
|
||||
@click="copyToClipboard(getClassDecompiledSource(classItem)!, classItem.key)"
|
||||
>
|
||||
<CopyIcon v-if="!showCopyFeedback.get(classItem.key)" />
|
||||
<CheckIcon v-else />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<IconButton
|
||||
v-tooltip="`Copy code`"
|
||||
type="quiet"
|
||||
:label="`Copy code`"
|
||||
class="!absolute right-2 top-2 border-[1px]"
|
||||
@click="copyToClipboard(getClassDecompiledSource(classItem)!, classItem.key)"
|
||||
>
|
||||
<CopyIcon v-if="!showCopyFeedback.get(classItem.key)" />
|
||||
<CheckIcon v-else />
|
||||
</IconButton>
|
||||
|
||||
<div class="overflow-x-auto bg-surface-3 py-3">
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user