KI-AGENT: Dateivorschauen authentifiziert laden

This commit is contained in:
2026-09-07 20:10:18 +02:00
parent 2c7cd17e2b
commit dd063fd648
5 changed files with 126 additions and 18 deletions

View File

@@ -0,0 +1,84 @@
<script setup lang="ts">
import { onBeforeUnmount, ref, watch } from "vue"
const props = defineProps<{
fileId: string
path?: string | null
}>()
const { $api } = useNuxtApp()
const objectUrl = ref<string | null>(null)
const loadFailed = ref(false)
const isPdf = computed(() => String(props.path || "").toLowerCase().endsWith(".pdf"))
const revokeObjectUrl = () => {
if (!objectUrl.value) return
URL.revokeObjectURL(objectUrl.value)
objectUrl.value = null
}
const loadFile = async () => {
revokeObjectUrl()
loadFailed.value = false
try {
const blob = await $api<Blob>(`/api/files/content/${encodeURIComponent(props.fileId)}`, {
responseType: "blob"
})
objectUrl.value = URL.createObjectURL(blob)
} catch (error) {
loadFailed.value = true
console.error("Dateivorschau konnte nicht geladen werden:", error)
}
}
watch(() => props.fileId, loadFile, { immediate: true })
onBeforeUnmount(revokeObjectUrl)
</script>
<template>
<div class="authenticated-file-preview">
<iframe
v-if="objectUrl && isPdf"
:src="`${objectUrl}#toolbar=0&navpanes=0&scrollbar=0`"
title="PDF-Vorschau"
loading="lazy"
/>
<img v-else-if="objectUrl" :src="objectUrl" alt="Dateivorschau" />
<div v-else-if="loadFailed" class="preview-placeholder">
<UIcon name="i-heroicons-document" class="h-10 w-10" />
<span>Keine Vorschau verfügbar</span>
</div>
<USkeleton v-else class="h-full min-h-32 w-full" />
</div>
</template>
<style scoped>
.authenticated-file-preview,
.authenticated-file-preview iframe,
.authenticated-file-preview img {
width: 100%;
height: 100%;
}
.authenticated-file-preview iframe {
border: 0;
pointer-events: none;
}
.authenticated-file-preview img {
object-fit: contain;
}
.preview-placeholder {
display: flex;
min-height: 8rem;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.5rem;
color: rgb(107 114 128);
font-size: 0.875rem;
}
</style>

View File

@@ -38,16 +38,10 @@ const showFile = (file) => {
<template>
<div :id="`docDisplay-${documentData.id}`" class="documentListItem" @click="returnEmit ? $emit('clicked', documentData.id) : showFile(documentData)">
<iframe
:src="`${documentData.url}#toolbar=0&navpanes=0&scrollbar=0`"
<AuthenticatedFilePreview
:file-id="documentData.id"
:path="documentData.path"
class="previewEmbed"
v-if="documentData.path.toLowerCase().includes('pdf')"
loading="lazy"
/>
<img
v-else
alt=""
:src="documentData.url"
/>
<!-- TODO: Remove Scrollbar -->
<UTooltip class="w-full" :text="documentData.path.split('/')[documentData.path.split('/').length -1]">

View File

@@ -1,6 +1,7 @@
<script setup>
const toast = useToast()
const modal = useModal()
const fileService = useFiles()
const props = defineProps({
documentData: {
@@ -68,6 +69,15 @@ const archiveDocument = async () => {
await updateDocument({archived: true}, true)
}
const openDocument = async () => {
try {
await fileService.openFile(props.documentData.id)
} catch (error) {
console.error(error)
toast.add({title: "Datei konnte nicht geöffnet werden", color: "error"})
}
}
const getItemsBySelectedResource = async () => {
idToAssign.value = null
itemOptions.value = selectedResource.value?.entity
@@ -155,11 +165,11 @@ setup()
v-if="props.documentData.id && String(props.documentData.path || '').toLowerCase().includes('pdf')"
:file-id="props.documentData.id" />
<img
<AuthenticatedFilePreview
v-else
class="w-full"
:src="props.documentData.url"
alt=""
:file-id="props.documentData.id"
:path="props.documentData.path"
/>
</div>
@@ -173,10 +183,9 @@ setup()
/>
<UButton
:to="props.documentData.url"
@click="openDocument"
variant="outline"
icon="i-heroicons-arrow-top-right-on-square"
target="_blank"
>
Öffnen
</UButton>

View File

@@ -119,8 +119,9 @@ function isImage(file) {
v-else-if="activeFile && isImage(activeFile)"
class="p-4 flex justify-center"
>
<img
:src="activeFile.url"
<AuthenticatedFilePreview
:file-id="activeFile.id"
:path="activeFile.path"
class="max-w-full max-h-[80vh] rounded-lg shadow"
/>
</div>

View File

@@ -146,6 +146,26 @@ export const useFiles = () => {
}
const openFile = async (id: string) => {
const targetWindow = window.open("about:blank", "_blank")
try {
const blob = await downloadFile(id, undefined, true) as Blob
const objectUrl = URL.createObjectURL(blob)
if (targetWindow) {
targetWindow.location.replace(objectUrl)
} else {
window.open(objectUrl, "_blank")
}
window.setTimeout(() => URL.revokeObjectURL(objectUrl), 60_000)
} catch (error) {
targetWindow?.close()
throw error
}
}
const dataURLtoFile = (dataurl:string, filename:string) => {
let arr = dataurl.split(","),
//@ts-ignore
@@ -160,5 +180,5 @@ export const useFiles = () => {
}
return {uploadFiles, selectDocuments, selectSomeDocuments, selectDocument, downloadFile, dataURLtoFile}
return {uploadFiles, selectDocuments, selectSomeDocuments, selectDocument, downloadFile, openFile, dataURLtoFile}
}