Vue.component('tt-fullscreen-viewer', { props: { item: { type: Object, required: true }, url: { type: String, default: null }, items: { type: Array, default: () => [] }, initialIndex: { type: Number, default: -1 }, }, data: () => ({ currentItem: null, currentImageIndex: -1, zoom: 1, pan: { x: 0, y: 0 }, isPanning: false, panStart: { x: 0, y: 0 }, lastPinchDist: 0, isLoading: true, isStandalone: false, }), computed: { contentSrc() { if (this.url) { return this.url; } if (this.currentItem?.fileId) { return `/File/show?id=${this.currentItem.fileId}`; } return null; }, isViewingImage() { return this.currentItem && this.isImage(this.currentItem); }, imageTransformStyle() { return { transform: `translate(${this.pan.x}px, ${this.pan.y}px) scale(${this.zoom})`, cursor: this.isPanning ? 'grabbing' : 'grab', transition: this.isPanning ? 'none' : 'transform 0.2s', }; }, downloadUrl() { return this.currentItem?.fileId ? `/File/download?id=${this.currentItem.fileId}` : '#'; } }, watch: { currentItem(newItem) { if (this.isPdf(newItem) && this.isStandalone) { this.$nextTick(() => { this.renderPdfWithJs(this.contentSrc); }); } } }, methods: { isImage: file => file?.mimetype?.startsWith('image/') || file?.mimetype === 'application/octet-stream', isPdf: file => file?.mimetype === 'application/pdf', onContentLoad() { this.isLoading = false; }, closeViewer() { this.$emit('close'); }, navigateImage(direction) { const newIndex = this.currentImageIndex + direction; if (newIndex >= 0 && newIndex < this.items.length) { this.isLoading = true; this.currentImageIndex = newIndex; this.currentItem = this.items[newIndex]; this.resetZoomAndPan(); } }, handleKeyDown(e) { e.stopPropagation(); if (!this.currentItem) return; switch (e.key) { case 'Escape': this.closeViewer(); break; case 'ArrowLeft': this.isViewingImage && this.navigateImage(-1); break; case 'ArrowRight': this.isViewingImage && this.navigateImage(1); break; } }, resetZoomAndPan() { this.zoom = 1; this.pan = { x: 0, y: 0 }; this.isPanning = false; }, handleWheel(e) { if (!this.isViewingImage) return; e.preventDefault(); const scaleFactor = e.deltaY > 0 ? -0.2 : 0.2; this.zoom = Math.max(1, Math.min(this.zoom + scaleFactor, 5)); }, onPanStart(e) { if (this.zoom <= 1) return; e.preventDefault(); this.isPanning = true; this.panStart.x = e.clientX - this.pan.x; this.panStart.y = e.clientY - this.pan.y; }, onPanMove(e) { if (!this.isPanning) return; this.pan.x = e.clientX - this.panStart.x; this.pan.y = e.clientY - this.panStart.y; }, onPanEnd() { this.isPanning = false; }, onTouchStart(e) {}, onTouchMove(e) {}, onTouchEnd(e) {}, loadPdfJsScript() { return new Promise((resolve, reject) => { if (document.getElementById('pdfjs-script')) { if (window.pdfjsLib) { resolve(); } else { document.getElementById('pdfjs-script').addEventListener('load', () => resolve()); document.getElementById('pdfjs-script').addEventListener('error', (e) => reject(e)); } return; } const script = document.createElement('script'); script.id = 'pdfjs-script'; script.type = 'module'; script.src = 'https://unpkg.com/pdfjs-dist@5.4.54/build/pdf.mjs'; // CDN URL script.onload = () => resolve(); script.onerror = (err) => { console.error("Failed to load PDF.js script.", err); reject(new Error("PDF.js script could not be loaded.")); }; document.head.appendChild(script); }); }, async renderPdfWithJs(url) { if (!url) return; this.isLoading = true; try { await this.loadPdfJsScript(); pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@5.4.54/build/pdf.worker.min.mjs'; const pdf = await pdfjsLib.getDocument(url).promise; const page = await pdf.getPage(1); const canvas = this.$refs.pdfCanvas; if (!canvas) return; const container = canvas.parentElement; if (!container) return; const context = canvas.getContext('2d'); const unscaledViewport = page.getViewport({ scale: 1 }); const scale = Math.min( container.clientWidth / unscaledViewport.width, container.clientHeight / unscaledViewport.height ); const viewport = page.getViewport({ scale: scale }); canvas.height = viewport.height; canvas.width = viewport.width; await page.render({ canvasContext: context, viewport: viewport }).promise; } catch (error) { console.error('Error rendering PDF with PDF.js:', error); } finally { this.onContentLoad(); } }, }, created() { this.currentItem = this.item; this.currentImageIndex = this.initialIndex; if (typeof window !== 'undefined' && window.matchMedia) { this.isStandalone = window.matchMedia('(display-mode: standalone)').matches; } }, mounted() { document.body.style.overflow = 'hidden'; this.$nextTick(() => { this.$refs.viewer?.focus(); if (this.isPdf(this.currentItem) && this.isStandalone) { this.renderPdfWithJs(this.contentSrc); } }); }, beforeDestroy() { document.body.style.overflow = ''; }, template: `
`, }) Vue.component('tt-file-gallery', { props: { files: {type: Array, default: () => []}, editMode: {type: Boolean, default: false}, deleteMode: {type: Boolean, default: false}, selectable: {type: Boolean, default: false}, }, data: () => ({ fullscreenItem: null, editingFile: null, selectedFiles: [], missingFileIds: new Set(), }), computed: { imageFiles() { return this.files.filter(this.isImage); }, }, methods: { isImage: file => file.mimetype?.startsWith('image/') || file.mimetype === 'application/octet-stream', isPdf: file => file.mimetype === 'application/pdf', getFileIcon(file) { const extension = file.fileName?.split('.').pop().toLowerCase(); switch (extension) { case 'doc': case 'docx': return 'fas fa-file-word text-primary'; case 'xls': case 'xlsx': return 'fas fa-file-excel text-success'; case 'zip': case 'rar': return 'fas fa-file-archive text-warning'; default: return 'fas fa-file text-secondary'; } }, toggleSelection(fileId) { if (!this.selectable) return; const index = this.selectedFiles.indexOf(fileId); if (index > -1) this.selectedFiles.splice(index, 1); else this.selectedFiles.push(fileId); this.$emit('selection-changed', this.selectedFiles); }, handleImageError(file) { if (!file || !file.id) return; this.missingFileIds.add(file.id); this.$forceUpdate(); // Force a re-render as Vue might not detect the Set change }, isMissing(file) { return this.missingFileIds.has(file.id); }, openViewer(file) { if (this.isMissing(file) || this.editingFile) return; this.fullscreenItem = file; }, closeViewer() { this.fullscreenItem = null; }, startEdit(file, event) { event?.stopPropagation(); this.editingFile = {...file}; }, cancelEdit() { this.editingFile = null; }, saveEdit(event) { event?.stopPropagation(); this.$emit('update-file', this.editingFile); this.editingFile = null; }, deleteFile(file, event) { event?.stopPropagation(); if (confirm(`Sind Sie sicher, dass Sie die Datei "${file.fileName}" löschen möchten?`)) { this.$emit('delete-file', file); } }, }, template: `
Hochgeladene Dokumente
Keine Dokumente vorhanden.
` });