Files
RenewCMS/web/src/components/MediaUploadPanel.vue
T

184 lines
5.2 KiB
Vue

<script setup lang="ts">
import { t } from '@/i18n'
import MediaThumb from '@/components/MediaThumb.vue'
import type { UploadRow } from '@/composables/useMediaUpload'
import { formatUploadProgress } from '@/lib/imageUpload'
import { formatMediaDate, formatSize } from '@/lib/mediaFormat'
import {
ADDED_TEXT,
DUPLICATE_TITLE,
FAILED_TEXT,
FAILED_TITLE,
duplicateText,
rejectionText,
rejectionTitle,
type UploadHeader,
} from '@/lib/mediaUploadRows'
const BUTTON =
'h-11 gap-1.5 border border-accented bg-default px-2.5 text-xs text-highlighted ring-0 max-md:flex-1 md:h-7'
const PERCENT = 100
defineProps<{
rows: UploadRow[]
header: UploadHeader
}>()
const emit = defineEmits<{
'use-existing': [key: number]
'upload-anyway': [key: number]
retry: [key: number]
'cancel-row': [key: number]
'cancel-all': []
dismiss: []
}>()
function thumbItem(row: UploadRow) {
return {
url: row.previewUrl ?? '',
content_type: row.file.type,
sha256: String(row.tone),
}
}
function percent(row: UploadRow) {
return row.total > 0 ? Math.round((row.loaded / row.total) * PERCENT) : 0
}
</script>
<template>
<section
data-testid="media-upload-panel"
:aria-label="t('Uploads')"
class="flex flex-col border border-default bg-default"
>
<div class="flex items-center gap-3 px-3.5 py-3">
<div class="flex min-w-0 flex-1 flex-col gap-0.5">
<span data-testid="upload-title" class="font-semibold text-highlighted">{{
header.title
}}</span>
<span data-testid="upload-sub" class="text-muted">{{ header.sub }}</span>
</div>
<UButton
color="neutral"
variant="outline"
class="h-11 border border-accented bg-default px-3 text-xs text-highlighted ring-0 md:h-7"
@click="header.phase === 'working' ? emit('cancel-all') : emit('dismiss')"
>
{{ header.phase === 'working' ? t('Cancel all') : t('Dismiss') }}
</UButton>
</div>
<div
v-for="row in rows"
:key="row.key"
data-testid="upload-row"
:data-status="row.status"
class="flex items-start gap-3 border-t border-muted px-3.5 py-3"
>
<MediaThumb :item="thumbItem(row)" size="sm" class="size-10 border border-default" />
<div class="flex min-w-0 flex-1 flex-col gap-1.5">
<div class="flex justify-between gap-2">
<span class="truncate font-medium text-highlighted">{{ row.name }}</span>
<span class="whitespace-nowrap text-muted">{{ formatSize(row.size) }}</span>
</div>
<template v-if="row.status === 'waiting'">
<span class="text-muted">{{ t('Waiting') }}</span>
</template>
<template v-else-if="row.status === 'uploading'">
<span>{{ formatUploadProgress(row.loaded, row.total) }}</span>
<UProgress
:model-value="percent(row)"
size="sm"
:ui="{
base: 'rounded-none',
indicator: 'rounded-none',
}"
/>
</template>
<template v-else-if="row.status === 'added'">
<span class="flex items-center gap-1.5 text-success">
<UIcon name="i-lucide-circle-check" class="size-3.5 shrink-0" />
{{ t(ADDED_TEXT) }}
</span>
</template>
<template v-else-if="row.status === 'duplicate' && row.existing">
<span class="font-semibold text-highlighted">{{ t(DUPLICATE_TITLE) }}</span>
<span class="text-xs/5 text-toned">{{
duplicateText(row.existing, formatMediaDate)
}}</span>
<div class="flex flex-wrap gap-2">
<UButton
color="neutral"
variant="outline"
:class="BUTTON"
@click="emit('use-existing', row.key)"
>
{{ t('Use existing') }}
</UButton>
<UButton
color="neutral"
variant="outline"
:class="BUTTON"
@click="emit('upload-anyway', row.key)"
>
{{ t('Upload anyway') }}
</UButton>
</div>
</template>
<template v-else-if="row.status === 'rejected' && row.rejection">
<span class="flex items-center gap-1.5 font-semibold text-error">
<UIcon name="i-lucide-file-x" class="size-3.5 shrink-0" />
{{ rejectionTitle(row.rejection, row.name, row.size) }}
</span>
<span class="text-xs/5 text-toned">{{ rejectionText(row.rejection) }}</span>
</template>
<template v-else-if="row.status === 'failed'">
<span class="flex items-center gap-1.5 font-semibold text-error">
<UIcon name="i-lucide-circle-x" class="size-3.5 shrink-0" />
{{ t(FAILED_TITLE) }}
</span>
<span class="text-xs/5 text-toned">{{ t(FAILED_TEXT) }}</span>
<div class="flex">
<UButton
color="neutral"
variant="outline"
icon="i-lucide-rotate-cw"
:class="BUTTON"
:ui="{ leadingIcon: 'size-3.5' }"
@click="emit('retry', row.key)"
>
{{ t('Try again') }}
</UButton>
</div>
</template>
</div>
<UButton
v-if="row.status === 'uploading'"
color="neutral"
variant="ghost"
icon="i-lucide-x"
:aria-label="t('Cancel upload of {0}', [row.name])"
class="size-6 justify-center text-muted"
:ui="{ leadingIcon: 'size-3.5' }"
@click="emit('cancel-row', row.key)"
/>
</div>
<div
v-if="header.summary"
data-testid="upload-summary"
class="border-t border-default bg-elevated px-3.5 py-3 font-medium text-toned"
>
{{ header.summary }}
</div>
</section>
</template>