From bf2c2f982cabaeb0d4e29de0e4b6f8753abb2358 Mon Sep 17 00:00:00 2001 From: Ida Dittrich Date: Mon, 8 Sep 2025 16:46:59 +0200 Subject: [PATCH] added file preview --- .../Dateien/DateienUploadModal.module.css | 0 src/components/Dateien/DateienUploadModal.tsx | 0 .../FilePreview/FilePreview.module.css | 91 ++++++++- src/components/FilePreview/FilePreview.tsx | 41 +++- .../FilePreview/renderers/PdfRenderer.tsx | 40 +++- .../FilePreview/renderers/TextRenderer.tsx | 24 ++- src/hooks/useApi.ts | 6 + src/hooks/useFiles.ts | 177 +++++++++++++++++- src/locales/de.ts | 2 + src/locales/en.ts | 2 + src/locales/fr.ts | 2 + 11 files changed, 368 insertions(+), 17 deletions(-) delete mode 100644 src/components/Dateien/DateienUploadModal.module.css delete mode 100644 src/components/Dateien/DateienUploadModal.tsx diff --git a/src/components/Dateien/DateienUploadModal.module.css b/src/components/Dateien/DateienUploadModal.module.css deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/Dateien/DateienUploadModal.tsx b/src/components/Dateien/DateienUploadModal.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/FilePreview/FilePreview.module.css b/src/components/FilePreview/FilePreview.module.css index 085ef17..ce25139 100644 --- a/src/components/FilePreview/FilePreview.module.css +++ b/src/components/FilePreview/FilePreview.module.css @@ -305,6 +305,9 @@ display: flex; align-items: flex-start; box-sizing: border-box; + word-wrap: break-word; + white-space: pre-wrap; + width: 100%; } .jsonTableValue { @@ -325,6 +328,9 @@ background: transparent; line-height: 1.4; width: 100%; + + word-wrap: break-word; + white-space: -wrap; } .jsonValue { @@ -347,7 +353,8 @@ font-weight: 600; font-family: 'Fira Code', 'Monaco', 'Cascadia Code', 'Roboto Mono', 'Courier New', monospace; font-size: 13px; - white-space: nowrap !important; + word-wrap: break-word; + white-space: pre-wrap; } .jsonValueNumber { @@ -750,3 +757,85 @@ } } +/* Text Container Styles */ +.textContainer { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + background: var(--color-background); + border-radius: 8px; + overflow: hidden; +} + +.textHeader { + padding: 1rem; + border-bottom: 1px solid var(--color-border); + background: var(--color-background); +} + +.textTitle { + font-size: 1.1rem; + font-weight: 600; + color: var(--color-text); + margin-bottom: 0.5rem; + display: block; +} + +.warningMessage { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem; + background: var(--color-background); + border: 1px solid var(--color-secondary); + border-radius: 6px; + margin-top: 0.5rem; + width: 100%; + text-align: center; + justify-content: center; +} + +.warningIcon { + display: flex; + align-items: center; + justify-content: center; + font-size: 1.2rem; + color: var(--color-secondary); + flex-shrink: 0; + margin-top: 0.1rem; + text-align: center; + justify-content: center; +} + +.warningText { + color: var(--color-text); + font-size: 0.9rem; + line-height: 1.4; + font-weight: 500; +} + +.textPreview { + flex: 1; + padding: 1rem; + margin: 0; + background: var(--color-background); + overflow: auto; + font-family: 'Fira Code', 'Monaco', 'Cascadia Code', 'Roboto Mono', 'Courier New', monospace; + font-size: 13px; + line-height: 1.4; + color: var(--color-text); + white-space: pre-wrap; + word-wrap: break-word; +} + +.textCode { + background: transparent; + color: var(--color-text); + font-family: inherit; + font-size: inherit; + line-height: inherit; + white-space: inherit; + word-wrap: inherit; +} + diff --git a/src/components/FilePreview/FilePreview.tsx b/src/components/FilePreview/FilePreview.tsx index f9ec3c3..32946a8 100644 --- a/src/components/FilePreview/FilePreview.tsx +++ b/src/components/FilePreview/FilePreview.tsx @@ -2,7 +2,6 @@ import { useState, useEffect } from 'react'; import { IoIosDownload, IoIosCopy } from 'react-icons/io'; - import { Popup, PopupAction } from '../Popup/Popup'; import { useLanguage } from '../../contexts/LanguageContext'; import { useFileOperations } from '../../hooks/useFiles'; @@ -68,13 +67,16 @@ export function FilePreview({ try { setError(null); setPreviewContent(null); - const result = await handleFilePreview(fileId, fileName); + const result = await handleFilePreview(fileId, fileName, mimeType); - if (result.success && result.previewUrl) { - setPreviewUrl(result.previewUrl); + if (result.success) { + if (result.previewUrl) { + setPreviewUrl(result.previewUrl); + } if (result.decodedContent) { setPreviewContent(result.decodedContent); } + // If it's text content but MIME type says PDF, we'll handle it in renderPreview } else { setError(result.error || 'Failed to load preview'); } @@ -115,9 +117,12 @@ export function FilePreview({ const isPreviewing = previewingFiles.has(fileId); const hasError = error || previewError; + // Check if this is a corrupted PDF (text content instead of PDF) + const isCorruptedPdf = mimeType === 'application/pdf' && previewContent && !previewUrl; + // Create action buttons for the popup header const actions: PopupAction[] = [ - // Copy Content Button - only show for text-based files (exclude PDFs) + // Copy Content Button - only show for text-based files (exclude PDFs) or corrupted PDFs ...(mimeType !== 'application/pdf' && (mimeType?.startsWith('text/') || mimeType === 'application/json' || previewContent) ? [{ label: copySuccess ? t('files.preview.copied', 'Copied!') : t(''), icon: copySuccess ? '✓' : , @@ -126,18 +131,31 @@ export function FilePreview({ variant: 'primary' as const }] : []), - // Download Button - { + // Download Button - hide for corrupted PDFs + ...(isCorruptedPdf ? [] : [{ label: String(''), icon: downloadingFiles.has(fileId) ? undefined : , onClick: handleDownloadFile, disabled: downloadingFiles.has(fileId), loading: downloadingFiles.has(fileId), variant: 'success' as const - } + }]) ]; const renderPreview = () => { + // Handle text content in PDF files (corrupted files) - check this first + if (previewContent && !previewUrl && mimeType === 'application/pdf') { + console.log('🔍 FilePreview: Rendering corrupted PDF with text content'); + return ( + setError('Failed to load PDF preview')} + /> + ); + } + if (!previewUrl) { if (isPreviewing) { return ; @@ -200,9 +218,16 @@ export function FilePreview({ case 'application': if (mimeType === 'application/pdf') { + console.log('🔍 FilePreview passing normal PDF to PdfRenderer:', { + previewUrl, + previewContent: previewContent ? `${previewContent.substring(0, 50)}...` : null, + fileName, + mimeType + }); return ( setError('Failed to load PDF preview')} /> diff --git a/src/components/FilePreview/renderers/PdfRenderer.tsx b/src/components/FilePreview/renderers/PdfRenderer.tsx index bb7b189..ba15ca3 100644 --- a/src/components/FilePreview/renderers/PdfRenderer.tsx +++ b/src/components/FilePreview/renderers/PdfRenderer.tsx @@ -1,19 +1,53 @@ +import { IoIosWarning } from 'react-icons/io'; +import { useLanguage } from '../../../contexts/LanguageContext'; import styles from '../FilePreview.module.css'; interface PdfRendererProps { - previewUrl: string; + previewUrl?: string; + previewContent?: string; fileName: string; onError: () => void; } -export function PdfRenderer({ previewUrl, fileName, onError }: PdfRendererProps) { +export function PdfRenderer({ previewUrl, previewContent, fileName, onError }: PdfRendererProps) { + const { t } = useLanguage(); + + + const handleLoad = () => { + console.log('📄 PDF iframe loaded successfully:', { previewUrl, fileName }); + }; + + const handleError = (event: React.SyntheticEvent) => { + console.error('❌ PDF iframe failed to load:', { previewUrl, fileName, event }); + onError(); + }; + + // Handle corrupted PDF files (text content instead of PDF) + if (previewContent && !previewUrl) { + console.log('📄 Rendering corrupted PDF warning'); + return ( +
+
+
+ + + {t('files.preview.pdfFileCorrupted', 'This file appears to be corrupted. It has a PDF extension but contains text content. Please re-upload the file if possible.')} + +
+
+
+ ); + } + + // Normal PDF rendering return (