import { useCallback, useMemo, useRef } from 'react'; import type { JSX } from 'react'; import DocumentInfoPanel from '../documents/DocumentInfoPanel'; import { DownloadIcon } from '../ui/icons'; import PdfViewer from './PdfViewer'; import { AUDIO_EXTENSIONS, VIDEO_EXTENSIONS } from '../constants/preview'; import type { Document } from '../types/documents'; interface DocumentLink { url?: string; mimeType?: string; filename?: string; } interface ContentTabConfig { id?: string; label?: string; enabled?: boolean; forceDisplay?: boolean; loadContent?: (options?: { signal?: AbortSignal }) => unknown | Promise; loadingMessage?: string; emptyMessage?: string; unavailableMessage?: string; errorMessage?: string; [key: string]: unknown; } type LayoutMode = 'split' | 'stacked' | (string & {}); interface DocumentViewerLayoutProps { document?: Document | null; documentLink?: DocumentLink | null; summaryProps?: Record; metadataPayload?: unknown; contentTabConfig?: ContentTabConfig | null; resetKey?: string | null; classNamePrefix?: string; defaultTabId?: string; infoPanelProps?: Record; previewLoadingMessage?: string; layoutMode?: LayoutMode; } const getFileExtension = (filename?: string | null) => { if (!filename) { return ''; } const match = filename.toLowerCase().match(/\.([a-z0-9]+)$/); return match ? match[1] : ''; }; const DocumentViewerLayout = ({ document, documentLink, summaryProps = {}, metadataPayload, contentTabConfig, resetKey, classNamePrefix = 'document-viewer', defaultTabId = 'details', infoPanelProps = {}, previewLoadingMessage = 'Preparing preview…', layoutMode = 'split', }: DocumentViewerLayoutProps): JSX.Element => { const isStacked = layoutMode === 'stacked'; const viewportRef = useRef(null); const previewContent = useMemo(() => { if (!document || !documentLink?.url) { return null; } const normalizedMimeType = (documentLink.mimeType || document.mime_type || '') .toLowerCase(); const normalizedFilename = documentLink.filename || document.filename || document.original_name || ''; const fileExtension = getFileExtension(normalizedFilename); const isImage = normalizedMimeType.startsWith('image/'); const isPdf = normalizedMimeType === 'application/pdf' || normalizedMimeType === 'application/x-pdf'; const isAudio = normalizedMimeType.startsWith('audio/') || AUDIO_EXTENSIONS.has(fileExtension); const isVideo = normalizedMimeType.startsWith('video/') || VIDEO_EXTENSIONS.has(fileExtension); const mediaLabel = document.title || normalizedFilename || 'Document preview'; if (isImage) { return ( {`Preview ); } if (isPdf) { const documentTitle = document.title || document.filename || document.original_name; return ( ); } if (isAudio) { return ( ); } if (isVideo) { return ( ); } const displayMimeType = document.mime_type || documentLink.mimeType || 'this file type'; const displayFilename = documentLink.filename || document.filename || document.original_name || 'download'; return (
Preview is not available for {displayMimeType} files.
{displayFilename}
Download
); }, [document, documentLink, viewportRef]); const renderViewportPane = useCallback(() => (
{!documentLink?.url ? (
{previewLoadingMessage}
) : ( previewContent )}
), [previewContent, documentLink?.url, previewLoadingMessage, viewportRef]); const viewportPane = renderViewportPane(); const stackedLeadingTabs = useMemo(() => ( isStacked ? [ { id: 'preview', label: 'Preview', render: () => renderViewportPane(), }, ] : [] ), [isStacked, renderViewportPane]); const resolvedDefaultTabId = isStacked ? 'preview' : defaultTabId; const summaryPlacement = 'tabs'; const tabsPlacement = 'bottom'; const summaryLayout = 'compact'; const detailsPane = (
); if (isStacked) { return detailsPane; } return ( <> {detailsPane} {viewportPane} ); }; export default DocumentViewerLayout;