feat: media page T1
This commit is contained in:
1 parent
67d456baf6
commit
008b0dfbb9
46 files changed
+5742
-204
No files matched your search
@@ -0,0 +1,178 @@
|
||||
<script setup lang="ts">
|
||||
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="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' ? 'Cancel all' : '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">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" />
|
||||
{{ ADDED_TEXT }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template v-else-if="row.status === 'duplicate' && row.existing">
|
||||
<span class="font-semibold text-highlighted">{{ 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)"
|
||||
>
|
||||
Use existing
|
||||
</UButton>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
:class="BUTTON"
|
||||
@click="emit('upload-anyway', row.key)"
|
||||
>
|
||||
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" />
|
||||
{{ FAILED_TITLE }}
|
||||
</span>
|
||||
<span class="text-xs/5 text-toned">{{ 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)"
|
||||
>
|
||||
Try again
|
||||
</UButton>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<UButton
|
||||
v-if="row.status === 'uploading'"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
icon="i-lucide-x"
|
||||
:aria-label="`Cancel upload of ${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>
|
||||
Reference in new issue
Block a user