feat: implement image preview
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue'
|
||||
import { CollectionObject } from '@DocumentsManager/models/collection'
|
||||
import { EntityObject } from '@DocumentsManager/models/entity'
|
||||
import { getFileIcon, formatSize } from '@/utils/fileHelpers'
|
||||
@@ -7,13 +8,30 @@ import FilePreviewPopover from '@/components/viewer/FilePreviewPopover.vue'
|
||||
|
||||
type PreviewSource = (item: { id: string; meta?: Record<string, unknown> }) => string | Blob | Promise<string | Blob>
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
collections: CollectionObject[]
|
||||
entities: EntityObject[]
|
||||
selectedIds: Set<string>
|
||||
previewSource?: PreviewSource
|
||||
}>()
|
||||
|
||||
// Thumbnails only support the plain-URL form of PreviewSource (what Documents
|
||||
// actually passes); Blob/Promise results just fall back to the file icon.
|
||||
const thumbnailErrors = reactive(new Set<string>())
|
||||
|
||||
function thumbnailUrl(entity: EntityObject): string | null {
|
||||
if (!props.previewSource) return null
|
||||
const result = props.previewSource({
|
||||
id: String(entity.identifier ?? ''),
|
||||
meta: { collection: entity.collection ? String(entity.collection) : null },
|
||||
})
|
||||
return typeof result === 'string' ? result : null
|
||||
}
|
||||
|
||||
function markThumbnailFailed(id: string) {
|
||||
thumbnailErrors.add(id)
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
'item-click': [item: CollectionObject | EntityObject, event: MouseEvent | KeyboardEvent]
|
||||
'open': [item: CollectionObject | EntityObject]
|
||||
@@ -74,7 +92,23 @@ const emit = defineEmits<{
|
||||
@show-details="emit('show-details', $event)"
|
||||
/>
|
||||
</div>
|
||||
<v-icon size="48" color="grey">{{ getFileIcon(entity) }}</v-icon>
|
||||
<div class="files-grid-thumbnail">
|
||||
<v-img
|
||||
v-if="thumbnailUrl(entity) && !thumbnailErrors.has(String(entity.identifier))"
|
||||
:src="thumbnailUrl(entity)!"
|
||||
:alt="entity.properties.label || 'File preview'"
|
||||
cover
|
||||
class="files-grid-thumbnail-img"
|
||||
@error="markThumbnailFailed(String(entity.identifier))"
|
||||
>
|
||||
<template #placeholder>
|
||||
<div class="d-flex fill-height align-center justify-center">
|
||||
<v-progress-circular indeterminate color="primary" size="20" width="2" />
|
||||
</div>
|
||||
</template>
|
||||
</v-img>
|
||||
<v-icon v-else size="48" color="grey">{{ getFileIcon(entity) }}</v-icon>
|
||||
</div>
|
||||
<div class="files-grid-label">{{ entity.properties.label || String(entity.identifier ?? '') }}</div>
|
||||
<div class="files-grid-size">{{ formatSize(entity.properties.size) }}</div>
|
||||
|
||||
@@ -136,6 +170,22 @@ const emit = defineEmits<{
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.files-grid-thumbnail {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background-color: rgb(var(--v-theme-surface-variant), 0.4);
|
||||
}
|
||||
|
||||
.files-grid-thumbnail-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.files-grid-label {
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
|
||||
Reference in New Issue
Block a user