frontend_nyla/src/components/ContentPreview/UrlContentPreview.tsx

348 lines
11 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useEffect } from 'react';
import { IoIosDownload } from 'react-icons/io';
import { Popup, PopupAction } from '../UiComponents/Popup/Popup';
import { useLanguage } from '../../providers/language/LanguageContext';
import { PdfRenderer, PdfJsRenderer, LoadingRenderer, ErrorRenderer } from './renderers';
import styles from './ContentPreview.module.css';
export interface UrlContentPreviewProps {
isOpen: boolean;
onClose: () => void;
url: string;
fileName: string;
mimeType?: string;
}
export function UrlContentPreview({
isOpen,
onClose,
url,
fileName,
mimeType = 'application/pdf'
}: UrlContentPreviewProps) {
const { t } = useLanguage();
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [hasLoaded, setHasLoaded] = useState(false);
const [warning, setWarning] = useState<string | null>(null);
const [showPdfAnyway, setShowPdfAnyway] = useState(false);
const [usePdfJs, setUsePdfJs] = useState(false);
// Reset state when modal opens/closes
useEffect(() => {
if (isOpen && url) {
setIsLoading(true);
setError(null);
setWarning(null);
setHasLoaded(false);
setShowPdfAnyway(false);
setUsePdfJs(false); // Start with iframe
} else {
setIsLoading(false);
setError(null);
setWarning(null);
setHasLoaded(false);
setShowPdfAnyway(false);
setUsePdfJs(false);
}
}, [isOpen, url]);
const handleDownload = () => {
try {
const link = document.createElement('a');
link.href = url;
link.download = fileName;
link.target = '_blank';
link.rel = 'noopener noreferrer';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} catch (err) {
console.error('Failed to download file:', err);
// Fallback: open in new tab
window.open(url, '_blank', 'noopener,noreferrer');
}
};
const handlePdfLoad = () => {
setIsLoading(false);
setHasLoaded(true);
setError(null);
};
const handlePdfError = () => {
// Try PDF.js as fallback instead of showing error immediately
if (!usePdfJs) {
console.log('Iframe failed, switching to PDF.js fallback');
setUsePdfJs(true);
setIsLoading(true); // Restart loading with PDF.js
setError(null);
setWarning(null);
return;
}
// If PDF.js also fails, show error
setIsLoading(false);
setError('Failed to load PDF. This might be due to CORS restrictions. You can try downloading the file or opening it in a new tab.');
setShowPdfAnyway(true);
};
const handleOpenInNewTab = () => {
window.open(url, '_blank', 'noopener,noreferrer');
};
// Set up progressive timeout for loading (schnellerer Fallback)
useEffect(() => {
if (isOpen && isLoading && !hasLoaded) {
// Schnellerer Timeout für externe PDFs: Warning after 3s, Error after 5s
const QUICK_TIMEOUT = 5000; // 5 Sekunden
const WARNING_TIMEOUT = 3000; // 3 Sekunden Warnung
const warningTimeout = setTimeout(() => {
if (isLoading && !hasLoaded) {
setWarning('PDF lädt langsam. Sie können es auch direkt herunterladen oder in einem neuen Tab öffnen.');
// Don't set isLoading to false - let it continue
}
}, WARNING_TIMEOUT);
const errorTimeout = setTimeout(() => {
if (isLoading && !hasLoaded && !usePdfJs) {
// Try PDF.js as fallback after 5 seconds
console.log('PDF loading timeout, switching to PDF.js fallback');
setUsePdfJs(true);
setIsLoading(true); // Restart loading with PDF.js
setWarning('PDF lädt langsam. Versuche alternative Anzeigemethode...');
} else if (isLoading && !hasLoaded && usePdfJs) {
// PDF.js also failed, show error
setShowPdfAnyway(true);
setError('PDF lädt langsam. Bitte verwenden Sie den Download-Button oder öffnen Sie es in einem neuen Tab.');
setIsLoading(false);
}
}, QUICK_TIMEOUT);
return () => {
clearTimeout(warningTimeout);
clearTimeout(errorTimeout);
};
}
}, [isOpen, isLoading, hasLoaded, usePdfJs]);
// Validate URL
useEffect(() => {
if (isOpen && url) {
try {
new URL(url);
} catch (e) {
setError('Invalid URL');
setIsLoading(false);
}
}
}, [isOpen, url]);
// Create action buttons for the popup header
const actions: PopupAction[] = [
{
label: String(''),
icon: <IoIosDownload />,
onClick: handleDownload,
disabled: false,
variant: 'success' as const
}
];
const renderPreview = () => {
// Show warning but continue loading
const showWarning = warning && !error;
// For PDF files, always try to show PDF (even if there's an error)
if (mimeType === 'application/pdf' && (hasLoaded || showPdfAnyway || !error)) {
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
{showWarning && (
<div style={{
padding: '0.75rem 1rem',
background: 'var(--color-warning-bg, #fef3c7)',
borderBottom: '1px solid var(--color-border, #e5e7eb)',
color: 'var(--color-warning-text, #92400e)',
fontSize: '0.875rem'
}}>
{warning}
</div>
)}
{error && (
<div style={{
padding: '0.75rem 1rem',
background: 'var(--color-error-bg, #fee2e2)',
borderBottom: '1px solid var(--color-border, #e5e7eb)',
color: 'var(--color-error-text, #991b1b)',
fontSize: '0.875rem'
}}>
{error}
<div style={{ display: 'flex', gap: '0.5rem', marginTop: '0.5rem', flexWrap: 'wrap' }}>
<button
onClick={handleOpenInNewTab}
className={styles.retryButton}
style={{
background: 'var(--color-primary, #3b82f6)',
fontSize: '0.75rem',
padding: '0.5rem 1rem'
}}
>
In neuem Tab öffnen
</button>
<button
onClick={handleDownload}
className={styles.retryButton}
style={{
background: 'var(--color-success, #10b981)',
fontSize: '0.75rem',
padding: '0.5rem 1rem'
}}
>
Download
</button>
</div>
</div>
)}
<div style={{ flex: 1, position: 'relative' }}>
<PdfRenderer
previewUrl={url}
fileName={fileName}
onError={handlePdfError}
onLoad={handlePdfLoad}
/>
</div>
</div>
);
}
// Show error only if we're not showing PDF anyway
if (error && !showPdfAnyway) {
return (
<div className={styles.errorContainer}>
<div className={styles.errorIcon}></div>
<p>{error}</p>
<div style={{ display: 'flex', gap: '1rem', marginTop: '1rem', flexWrap: 'wrap' }}>
<button
onClick={() => {
setError(null);
setWarning(null);
setIsLoading(true);
setHasLoaded(false);
setShowPdfAnyway(false);
setUsePdfJs(false); // Reset to iframe
}}
className={styles.retryButton}
>
{t('common.retry', 'Retry')}
</button>
<button
onClick={handleOpenInNewTab}
className={styles.retryButton}
style={{
background: 'var(--color-primary, #3b82f6)',
fontSize: '0.875rem',
padding: '0.625rem 1.25rem',
fontWeight: '500'
}}
>
In neuem Tab öffnen
</button>
<button
onClick={handleDownload}
className={styles.retryButton}
style={{
background: 'var(--color-success, #10b981)',
fontSize: '0.875rem',
padding: '0.625rem 1.25rem',
fontWeight: '500'
}}
>
Download File
</button>
</div>
</div>
);
}
if (isLoading && !hasLoaded && !showPdfAnyway) {
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
{warning && (
<div style={{
padding: '1rem',
background: 'var(--color-warning-bg, #fef3c7)',
borderBottom: '1px solid var(--color-border, #e5e7eb)',
color: 'var(--color-warning-text, #92400e)',
fontSize: '0.875rem',
marginBottom: '1rem'
}}>
{warning}
<div style={{ display: 'flex', gap: '0.75rem', marginTop: '0.75rem', flexWrap: 'wrap' }}>
<button
onClick={handleOpenInNewTab}
className={styles.retryButton}
style={{
background: 'var(--color-primary, #3b82f6)',
fontSize: '0.875rem',
padding: '0.625rem 1.25rem',
fontWeight: '500'
}}
>
In neuem Tab öffnen
</button>
<button
onClick={handleDownload}
className={styles.retryButton}
style={{
background: 'var(--color-success, #10b981)',
fontSize: '0.875rem',
padding: '0.625rem 1.25rem',
fontWeight: '500'
}}
>
Download
</button>
</div>
</div>
)}
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<LoadingRenderer />
</div>
</div>
);
}
// For other file types, show unsupported message
if (mimeType !== 'application/pdf') {
return (
<div className={styles.unsupportedContainer}>
<div className={styles.unsupportedIcon}>📄</div>
<div className={styles.fileName}>{fileName}</div>
<p>Preview not supported for this file type. Please download the file to view it.</p>
<button onClick={handleDownload} className={styles.retryButton}>
Download File
</button>
</div>
);
}
return null;
};
return (
<Popup
isOpen={isOpen}
onClose={onClose}
title={`${t('files.preview.title', 'Content Preview')}: ${fileName}`}
size="fullscreen"
className={styles.contentPreviewPopup}
actions={actions}
>
<div className={styles.previewContainer}>
{renderPreview()}
</div>
</Popup>
);
}
export default UrlContentPreview;