mirror of
https://github.com/modrinth/code.git
synced 2026-08-02 06:05:54 +00:00
Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d05f2a10c | ||
|
|
7f07e66e4f | ||
|
|
5e0c2bba89 | ||
|
|
4878410415 | ||
|
|
deaf82d51a | ||
|
|
7e86afe082 | ||
|
|
db3b8f2099 | ||
|
|
2d1f1c3ab5 | ||
|
|
55d375fbd3 | ||
|
|
7fd1f988a0 | ||
|
|
b34aa593eb |
@@ -1,8 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { FolderSearchIcon, StarIcon, TrashIcon } from '@modrinth/assets'
|
||||
import {
|
||||
FolderSearchIcon,
|
||||
RotateCounterClockwiseIcon,
|
||||
SpinnerIcon,
|
||||
StarIcon,
|
||||
TrashIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
Combobox,
|
||||
type ComboboxOption,
|
||||
ConfirmModal,
|
||||
defineMessages,
|
||||
injectModrinthClient,
|
||||
@@ -12,6 +20,7 @@ import {
|
||||
type TableColumn,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { renderString } from '@modrinth/utils'
|
||||
import { useQueryClient } from '@tanstack/vue-query'
|
||||
import { computed, ref, useTemplateRef } from 'vue'
|
||||
|
||||
@@ -52,6 +61,10 @@ const messages = defineMessages({
|
||||
id: 'modpack-scan-modal.scanning',
|
||||
defaultMessage: 'Scanning...',
|
||||
},
|
||||
success: {
|
||||
id: 'modpack-scan-modal.success',
|
||||
defaultMessage: 'Success',
|
||||
},
|
||||
failed: {
|
||||
id: 'modpack-scan-modal.failed',
|
||||
defaultMessage: 'Failed',
|
||||
@@ -66,11 +79,40 @@ const messages = defineMessages({
|
||||
},
|
||||
scanError: {
|
||||
id: 'modpack-scan-modal.scan-error',
|
||||
defaultMessage: 'Some files failed to scan: {error}',
|
||||
defaultMessage: 'Some files failed to scan: \n\n{error}',
|
||||
},
|
||||
clearAllGroups: {
|
||||
id: 'modpack-scan-modal.clear-all-groups',
|
||||
defaultMessage: 'Clear All Groups',
|
||||
batchPlaceholder: {
|
||||
id: 'modpack-scan-modal.batch.placeholder',
|
||||
defaultMessage: 'Batch amount',
|
||||
},
|
||||
batchLabel: {
|
||||
id: 'modpack-scan-modal.batch.label',
|
||||
defaultMessage: '{amount} per batch',
|
||||
},
|
||||
deleteAllGroups: {
|
||||
id: 'project.settings.permissions.delete-all-groups',
|
||||
defaultMessage: 'Delete all groups',
|
||||
},
|
||||
deleteAllGroupsConfirmationTitle: {
|
||||
id: 'project.settings.permissions.delete-all-groups-confirmation.title',
|
||||
defaultMessage: 'Delete all attribution groups?',
|
||||
},
|
||||
deleteAllGroupsConfirmationDescription: {
|
||||
id: 'modpack-scan-modal.delete-all-groups-confirmation.description',
|
||||
defaultMessage:
|
||||
'This will permanently delete all attribution groups for this project and all files inside them. This action cannot be undone.',
|
||||
},
|
||||
deleteAllGroupsConfirmationProceed: {
|
||||
id: 'modpack-scan-modal.delete-all-groups.proceed',
|
||||
defaultMessage: 'Clear',
|
||||
},
|
||||
deleteAllGroupsSuccess: {
|
||||
id: 'modpack-scan-modal.delete-all-groups.success',
|
||||
defaultMessage: 'All groups cleared successfully.',
|
||||
},
|
||||
deleteAllGroupsError: {
|
||||
id: 'modpack-scan-modal.delete-all-groups.error',
|
||||
defaultMessage: 'Failed to clear all groups: {error}',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -98,23 +140,64 @@ const modalRef = useTemplateRef<InstanceType<typeof NewModal>>('modalRef')
|
||||
const clearModalRef = useTemplateRef<InstanceType<typeof ConfirmModal>>('clearModalRef')
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const rows = ref<ScanRow[]>([])
|
||||
const rows = ref<Record<string, ScanRow>>({})
|
||||
const isLoadingVersions = ref(false)
|
||||
const isScanning = ref(false)
|
||||
const isClearing = ref(false)
|
||||
const versionLoadError = ref<string | null>(null)
|
||||
const scanError = ref<string | null>(null)
|
||||
const requestId = ref(0)
|
||||
const scanRequestId = ref(0)
|
||||
|
||||
const DEFAULT_BATCH_AMOUNT = 10
|
||||
const batchAmountOptions: ComboboxOption<number>[] = [
|
||||
{ value: 1, label: '1' },
|
||||
{ value: 5, label: '5' },
|
||||
{ value: 10, label: '10' },
|
||||
{ value: 20, label: '20' },
|
||||
{ value: 50, label: '50' },
|
||||
]
|
||||
const batchAmountValues = batchAmountOptions.map((opt) => opt.value)
|
||||
const batchAmountCookie = useCookie<number>('moderation-modpack-scan-batch', {
|
||||
default: () => DEFAULT_BATCH_AMOUNT,
|
||||
maxAge: 60 * 60 * 24 * 365,
|
||||
sameSite: 'lax',
|
||||
path: '/',
|
||||
})
|
||||
const batchAmount = computed({
|
||||
get() {
|
||||
const value = Number(batchAmountCookie.value)
|
||||
return batchAmountValues.includes(value) ? value : DEFAULT_BATCH_AMOUNT
|
||||
},
|
||||
set(value: number) {
|
||||
batchAmountCookie.value = value
|
||||
},
|
||||
})
|
||||
|
||||
const columns = computed<TableColumn<ScanTableColumn>[]>(() => [
|
||||
{ key: 'filename', label: formatMessage(messages.packFileName), width: '60%' },
|
||||
{ key: 'newFiles', label: formatMessage(messages.newFiles), align: 'center', width: '20%' },
|
||||
{ key: 'newGroups', label: formatMessage(messages.newGroups), align: 'center', width: '20%' },
|
||||
])
|
||||
|
||||
const scannedCount = computed(() => rows.value.filter((row) => row.scan || row.error).length)
|
||||
const scannedCount = computed(
|
||||
() => Object.entries(rows.value).filter(([_, row]) => row.scan || row.error).length,
|
||||
)
|
||||
const isBusy = computed(() => isLoadingVersions.value || isScanning.value || isClearing.value)
|
||||
const titleButtonsDisabled = computed(() => isBusy.value || Object.keys(rows.value).length === 0)
|
||||
const rescanButtonsDisabled = computed(() => isLoadingVersions.value || isClearing.value)
|
||||
|
||||
const rowErrors = computed(() =>
|
||||
Object.entries(rows.value)
|
||||
.filter(([_, row]) => row.error)
|
||||
.map(([_, row]) => row),
|
||||
)
|
||||
|
||||
const rowScanError = computed(() => {
|
||||
if (rowErrors.value.length === 0) return undefined
|
||||
return formatMessage(messages.scanError, {
|
||||
error: rowErrors.value.map((r) => `\n - ${r.filename}`).join(''),
|
||||
})
|
||||
})
|
||||
|
||||
function getErrorMessage(error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
@@ -131,12 +214,27 @@ function getErrorMessage(error: unknown) {
|
||||
return String(error)
|
||||
}
|
||||
|
||||
async function runWithConcurrency<T>(
|
||||
items: T[],
|
||||
limit: number,
|
||||
task: (item: T) => Promise<void>,
|
||||
): Promise<void> {
|
||||
const queue = [...items]
|
||||
const workers = Array.from({ length: limit }, async () => {
|
||||
while (queue.length) {
|
||||
const item = queue.shift()
|
||||
if (item === undefined) return
|
||||
await task(item)
|
||||
}
|
||||
})
|
||||
await Promise.all(workers)
|
||||
}
|
||||
|
||||
async function fetchAllVersions() {
|
||||
const currentRequestId = ++requestId.value
|
||||
isLoadingVersions.value = true
|
||||
versionLoadError.value = null
|
||||
scanError.value = null
|
||||
rows.value = []
|
||||
rows.value = {}
|
||||
|
||||
try {
|
||||
const versions = await client.labrinth.versions_v2.getProjectVersions(props.project_id)
|
||||
@@ -144,17 +242,20 @@ async function fetchAllVersions() {
|
||||
return
|
||||
}
|
||||
|
||||
rows.value = versions
|
||||
const filteredVersions = versions
|
||||
.flatMap((version) => version.files)
|
||||
.filter((file): file is Labrinth.Versions.v2.VersionFile & { id: string } => Boolean(file.id))
|
||||
.map((file) => ({
|
||||
id: file.id,
|
||||
filename: file.filename,
|
||||
primary: file.primary,
|
||||
|
||||
for (const version of filteredVersions) {
|
||||
rows.value[version.id] = {
|
||||
id: version.id,
|
||||
filename: version.filename,
|
||||
primary: version.primary,
|
||||
isScanning: false,
|
||||
newFiles: undefined,
|
||||
newGroups: undefined,
|
||||
}))
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (currentRequestId === requestId.value) {
|
||||
versionLoadError.value = formatMessage(messages.loadVersionsError, {
|
||||
@@ -168,54 +269,45 @@ async function fetchAllVersions() {
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchScan(id: string) {
|
||||
rows.value[id].isScanning = true
|
||||
try {
|
||||
const scan = await client.labrinth.attribution_internal.scanFile(id)
|
||||
|
||||
rows.value[id].scan = scan
|
||||
rows.value[id].newFiles = scan.new_attribution_files
|
||||
rows.value[id].newGroups = scan.new_attribution_groups
|
||||
|
||||
rows.value[id].error = undefined
|
||||
} catch (error) {
|
||||
rows.value[id].error = getErrorMessage(error)
|
||||
} finally {
|
||||
rows.value[id].isScanning = false
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchAllScans() {
|
||||
if (isBusy.value) {
|
||||
return
|
||||
}
|
||||
if (isBusy.value) return
|
||||
|
||||
const currentScanRequestId = ++scanRequestId.value
|
||||
isScanning.value = true
|
||||
scanError.value = null
|
||||
rows.value = rows.value.map((row) => ({
|
||||
...row,
|
||||
scan: undefined,
|
||||
isScanning: false,
|
||||
error: undefined,
|
||||
newFiles: undefined,
|
||||
newGroups: undefined,
|
||||
}))
|
||||
|
||||
Object.entries(rows.value).map(([id, row]) => {
|
||||
rows.value[id] = {
|
||||
...row,
|
||||
scan: undefined,
|
||||
isScanning: false,
|
||||
error: undefined,
|
||||
newFiles: undefined,
|
||||
newGroups: undefined,
|
||||
}
|
||||
})
|
||||
|
||||
try {
|
||||
for (const row of rows.value) {
|
||||
if (currentScanRequestId !== scanRequestId.value) {
|
||||
return
|
||||
}
|
||||
|
||||
row.isScanning = true
|
||||
try {
|
||||
const scan = await client.labrinth.attribution_internal.scanFile(row.id)
|
||||
if (currentScanRequestId !== scanRequestId.value) {
|
||||
return
|
||||
}
|
||||
|
||||
row.scan = scan
|
||||
row.newFiles = scan.new_attribution_files
|
||||
row.newGroups = scan.new_attribution_groups
|
||||
} catch (error) {
|
||||
if (currentScanRequestId !== scanRequestId.value) {
|
||||
return
|
||||
}
|
||||
|
||||
row.error = getErrorMessage(error)
|
||||
scanError.value = formatMessage(messages.scanError, { error: row.error })
|
||||
} finally {
|
||||
row.isScanning = false
|
||||
}
|
||||
}
|
||||
await runWithConcurrency(Object.keys(rows.value), batchAmount.value, async (id: string) => {
|
||||
await fetchScan(id)
|
||||
})
|
||||
} finally {
|
||||
if (currentScanRequestId === scanRequestId.value) {
|
||||
isScanning.value = false
|
||||
}
|
||||
isScanning.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,8 +334,8 @@ async function clearAllGroups() {
|
||||
failed = true
|
||||
addNotification({
|
||||
type: 'error',
|
||||
title: 'An error occurred',
|
||||
text: `Failed to clear all groups: ${getErrorMessage(error)}`,
|
||||
title: formatMessage(messages.failed),
|
||||
text: formatMessage(messages.deleteAllGroupsError, { error: getErrorMessage(error) }),
|
||||
})
|
||||
} finally {
|
||||
isClearing.value = false
|
||||
@@ -252,8 +344,8 @@ async function clearAllGroups() {
|
||||
if (!failed) {
|
||||
addNotification({
|
||||
type: 'success',
|
||||
title: 'Success',
|
||||
text: 'All groups cleared successfully.',
|
||||
title: formatMessage(messages.success),
|
||||
text: formatMessage(messages.deleteAllGroupsSuccess),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -261,7 +353,7 @@ async function clearAllGroups() {
|
||||
function show() {
|
||||
scanRequestId.value++
|
||||
isScanning.value = false
|
||||
rows.value = []
|
||||
rows.value = {}
|
||||
void fetchAllVersions()
|
||||
modalRef.value?.show()
|
||||
}
|
||||
@@ -275,9 +367,9 @@ defineExpose({ show, hide })
|
||||
<template>
|
||||
<ConfirmModal
|
||||
ref="clearModalRef"
|
||||
title="Clear all permission groups?"
|
||||
description="This will clear **all** groups for this project. This action cannot be undone."
|
||||
proceed-label="Clear"
|
||||
:title="formatMessage(messages.deleteAllGroupsConfirmationTitle)"
|
||||
:description="formatMessage(messages.deleteAllGroupsConfirmationDescription)"
|
||||
:proceed-label="formatMessage(messages.deleteAllGroupsConfirmationProceed)"
|
||||
@proceed="clearAllGroups"
|
||||
/>
|
||||
|
||||
@@ -294,27 +386,43 @@ defineExpose({ show, hide })
|
||||
{{
|
||||
formatMessage(messages.title, {
|
||||
scanned: scannedCount,
|
||||
total: rows.length,
|
||||
total: Object.keys(rows).length,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<Combobox
|
||||
v-model="batchAmount"
|
||||
:options="batchAmountOptions"
|
||||
:disabled="titleButtonsDisabled"
|
||||
:placeholder="formatMessage(messages.batchPlaceholder)"
|
||||
>
|
||||
<template #selected>
|
||||
<span class="flex flex-row gap-2 align-middle font-semibold">
|
||||
<span class="truncate text-contrast">{{
|
||||
formatMessage(messages.batchLabel, { amount: batchAmount })
|
||||
}}</span>
|
||||
</span>
|
||||
</template>
|
||||
</Combobox>
|
||||
<ButtonStyled circular color="red" color-fill="none">
|
||||
<button
|
||||
v-tooltip="formatMessage(messages.clearAllGroups)"
|
||||
:disabled="isBusy || rows.length === 0"
|
||||
v-tooltip="formatMessage(messages.deleteAllGroups)"
|
||||
:disabled="titleButtonsDisabled"
|
||||
@click="showConfirmClearGroups"
|
||||
>
|
||||
<TrashIcon aria-hidden="true" />
|
||||
<TrashIcon v-if="!isClearing" aria-hidden="true" />
|
||||
<SpinnerIcon v-else class="animate-spin" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular>
|
||||
<button
|
||||
v-tooltip="formatMessage(messages.scanAllFiles)"
|
||||
:disabled="isBusy || rows.length === 0"
|
||||
:disabled="titleButtonsDisabled"
|
||||
@click="fetchAllScans"
|
||||
>
|
||||
<FolderSearchIcon aria-hidden="true" />
|
||||
<FolderSearchIcon v-if="!isScanning" aria-hidden="true" />
|
||||
<SpinnerIcon v-else class="animate-spin" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
@@ -323,14 +431,14 @@ defineExpose({ show, hide })
|
||||
|
||||
<div class="w-full">
|
||||
<div
|
||||
v-if="versionLoadError || scanError"
|
||||
class="mb-3 rounded-xl bg-highlight-red p-3 text-red"
|
||||
v-if="versionLoadError || rowScanError"
|
||||
class="mb-3 rounded-xl bg-highlight-red px-4 py-1 text-red"
|
||||
>
|
||||
{{ versionLoadError || scanError }}
|
||||
<div v-html="renderString((versionLoadError || rowScanError) ?? '')"></div>
|
||||
</div>
|
||||
<Table
|
||||
:columns="columns"
|
||||
:data="rows"
|
||||
:data="Object.entries(rows).map(([_, row]) => row)"
|
||||
row-key="id"
|
||||
:row-below-visible="
|
||||
(row) => Boolean(row.scan?.scanned_file_names && row.scan.scanned_file_names.length > 0)
|
||||
@@ -345,16 +453,36 @@ defineExpose({ show, hide })
|
||||
</template>
|
||||
<template #cell-newFiles="{ row }">
|
||||
<span v-if="row.isScanning">{{ formatMessage(messages.scanning) }}</span>
|
||||
<span v-else-if="row.error" v-tooltip="row.error" class="text-red">
|
||||
{{ formatMessage(messages.failed) }}
|
||||
<span v-else-if="row.error" v-tooltip="row.error" class="flex justify-center">
|
||||
<ButtonStyled
|
||||
class="justify-self-center"
|
||||
color="red"
|
||||
type="outlined"
|
||||
hover-color-fill="background"
|
||||
>
|
||||
<button :disabled="rescanButtonsDisabled" @click="() => fetchScan(row.id)">
|
||||
<RotateCounterClockwiseIcon />
|
||||
{{ formatMessage(messages.failed) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</span>
|
||||
<span v-else-if="row.scan">{{ row.scan.new_attribution_files }}</span>
|
||||
<span v-else>{{ formatMessage(messages.notScanned) }}</span>
|
||||
</template>
|
||||
<template #cell-newGroups="{ row }">
|
||||
<span v-if="row.isScanning">{{ formatMessage(messages.scanning) }}</span>
|
||||
<span v-else-if="row.error" v-tooltip="row.error" class="text-red">
|
||||
{{ formatMessage(messages.failed) }}
|
||||
<span v-else-if="row.error" v-tooltip="row.error" class="flex justify-center">
|
||||
<ButtonStyled
|
||||
class="justify-self-center"
|
||||
color="red"
|
||||
type="outlined"
|
||||
hover-color-fill="background"
|
||||
>
|
||||
<button :disabled="rescanButtonsDisabled" @click="() => fetchScan(row.id)">
|
||||
<RotateCounterClockwiseIcon />
|
||||
{{ formatMessage(messages.failed) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</span>
|
||||
<span v-else-if="row.scan">{{ row.scan.new_attribution_groups }}</span>
|
||||
<span v-else>{{ formatMessage(messages.notScanned) }}</span>
|
||||
|
||||
@@ -2972,9 +2972,6 @@
|
||||
"moderation.page.technicalReview": {
|
||||
"message": "Technische Überprüfung"
|
||||
},
|
||||
"modpack-scan-modal.clear-all-groups": {
|
||||
"message": "Alle Gruppen entfernen"
|
||||
},
|
||||
"modpack-scan-modal.failed": {
|
||||
"message": "Fehlgeschlagen"
|
||||
},
|
||||
|
||||
@@ -2972,9 +2972,6 @@
|
||||
"moderation.page.technicalReview": {
|
||||
"message": "Technische Überprüfung"
|
||||
},
|
||||
"modpack-scan-modal.clear-all-groups": {
|
||||
"message": "Alle Gruppen entfernen"
|
||||
},
|
||||
"modpack-scan-modal.failed": {
|
||||
"message": "Fehlgeschlagen"
|
||||
},
|
||||
|
||||
@@ -2972,8 +2972,23 @@
|
||||
"moderation.page.technicalReview": {
|
||||
"message": "Tech review"
|
||||
},
|
||||
"modpack-scan-modal.clear-all-groups": {
|
||||
"message": "Clear All Groups"
|
||||
"modpack-scan-modal.batch.label": {
|
||||
"message": "{amount} per batch"
|
||||
},
|
||||
"modpack-scan-modal.batch.placeholder": {
|
||||
"message": "Batch amount"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups-confirmation.description": {
|
||||
"message": "This will permanently delete all attribution groups for this project and all files inside them. This action cannot be undone."
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.error": {
|
||||
"message": "Failed to clear all groups: {error}"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.proceed": {
|
||||
"message": "Clear"
|
||||
},
|
||||
"modpack-scan-modal.delete-all-groups.success": {
|
||||
"message": "All groups cleared successfully."
|
||||
},
|
||||
"modpack-scan-modal.failed": {
|
||||
"message": "Failed"
|
||||
@@ -3006,11 +3021,14 @@
|
||||
"message": "Scan All Files"
|
||||
},
|
||||
"modpack-scan-modal.scan-error": {
|
||||
"message": "Some files failed to scan: {error}"
|
||||
"message": "Some files failed to scan: \n\n{error}"
|
||||
},
|
||||
"modpack-scan-modal.scanning": {
|
||||
"message": "Scanning..."
|
||||
},
|
||||
"modpack-scan-modal.success": {
|
||||
"message": "Success"
|
||||
},
|
||||
"modpack-scan-modal.title": {
|
||||
"message": "Modpack Scan ({scanned}/{total} Files)"
|
||||
},
|
||||
|
||||
@@ -2954,9 +2954,6 @@
|
||||
"moderation.page.technicalReview": {
|
||||
"message": "Revue technique"
|
||||
},
|
||||
"modpack-scan-modal.clear-all-groups": {
|
||||
"message": "Effacer tous les groupes"
|
||||
},
|
||||
"modpack-scan-modal.failed": {
|
||||
"message": "Échec"
|
||||
},
|
||||
|
||||
@@ -2957,9 +2957,6 @@
|
||||
"moderation.page.technicalReview": {
|
||||
"message": "Revisione tecnica"
|
||||
},
|
||||
"modpack-scan-modal.clear-all-groups": {
|
||||
"message": "Svuota gruppi"
|
||||
},
|
||||
"modpack-scan-modal.failed": {
|
||||
"message": "Errore"
|
||||
},
|
||||
|
||||
@@ -2942,9 +2942,6 @@
|
||||
"moderation.page.technicalReview": {
|
||||
"message": "기술 리뷰"
|
||||
},
|
||||
"modpack-scan-modal.clear-all-groups": {
|
||||
"message": "모든 그룹 삭제"
|
||||
},
|
||||
"modpack-scan-modal.failed": {
|
||||
"message": "실패"
|
||||
},
|
||||
|
||||
@@ -2939,9 +2939,6 @@
|
||||
"moderation.page.technicalReview": {
|
||||
"message": "Technische beoordeling"
|
||||
},
|
||||
"modpack-scan-modal.clear-all-groups": {
|
||||
"message": "Alle groepen wissen"
|
||||
},
|
||||
"modpack-scan-modal.failed": {
|
||||
"message": "Mislukt"
|
||||
},
|
||||
|
||||
@@ -2942,9 +2942,6 @@
|
||||
"moderation.page.technicalReview": {
|
||||
"message": "Revisão técnica"
|
||||
},
|
||||
"modpack-scan-modal.clear-all-groups": {
|
||||
"message": "Limpar todos os grupos"
|
||||
},
|
||||
"modpack-scan-modal.failed": {
|
||||
"message": "Falhou"
|
||||
},
|
||||
|
||||
@@ -2960,9 +2960,6 @@
|
||||
"moderation.page.technicalReview": {
|
||||
"message": "Техпроверка"
|
||||
},
|
||||
"modpack-scan-modal.clear-all-groups": {
|
||||
"message": "Очистить все группы"
|
||||
},
|
||||
"modpack-scan-modal.failed": {
|
||||
"message": "Ошибка"
|
||||
},
|
||||
|
||||
@@ -2936,9 +2936,6 @@
|
||||
"moderation.page.technicalReview": {
|
||||
"message": "Технічний огляд"
|
||||
},
|
||||
"modpack-scan-modal.clear-all-groups": {
|
||||
"message": "Очистити всі групи"
|
||||
},
|
||||
"modpack-scan-modal.failed": {
|
||||
"message": "Помилка"
|
||||
},
|
||||
|
||||
@@ -2969,9 +2969,6 @@
|
||||
"moderation.page.technicalReview": {
|
||||
"message": "技术审查"
|
||||
},
|
||||
"modpack-scan-modal.clear-all-groups": {
|
||||
"message": "清除所有分组"
|
||||
},
|
||||
"modpack-scan-modal.failed": {
|
||||
"message": "失败"
|
||||
},
|
||||
|
||||
@@ -2948,9 +2948,6 @@
|
||||
"moderation.page.technicalReview": {
|
||||
"message": "技術審查"
|
||||
},
|
||||
"modpack-scan-modal.clear-all-groups": {
|
||||
"message": "清除所有群組"
|
||||
},
|
||||
"modpack-scan-modal.failed": {
|
||||
"message": "失敗"
|
||||
},
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
<h2 id="messages" class="m-0 text-xl font-semibold text-contrast">
|
||||
{{ formatMessage(messages.threadSectionTitle) }}
|
||||
</h2>
|
||||
<div v-if="isStaff(currentMember?.user)" class="flex items-center gap-2">
|
||||
<div v-if="staff" class="flex items-center gap-2">
|
||||
<Toggle id="moderator-see-user-ui-toggle" v-model="moderatorSeeUserUi" small />
|
||||
<label for="moderator-see-user-ui-toggle"> Show member UI </label>
|
||||
</div>
|
||||
@@ -218,7 +218,10 @@ const {
|
||||
} = injectProjectPageContext()
|
||||
|
||||
const canAccess = computed(() => !!currentMember.value)
|
||||
const userFacingUiVisible = computed(() => !!currentMember.value && moderatorSeeUserUi.value)
|
||||
const staff = computed(() => isStaff(currentMember.value?.user))
|
||||
const userFacingUiVisible = computed(
|
||||
() => !!currentMember.value && (!staff.value || moderatorSeeUserUi.value),
|
||||
)
|
||||
|
||||
const approvedAdmonitionMessage = computed<MessageDescriptor | null>(() => {
|
||||
switch (project.value?.status) {
|
||||
|
||||
Reference in New Issue
Block a user