189 lines
5.0 KiB
TypeScript
189 lines
5.0 KiB
TypeScript
import { useCallback, useMemo } from 'react';
|
|
import type { JSX } from 'react';
|
|
import DocumentInfoPanel from '../documents/DocumentInfoPanel';
|
|
import { DownloadIcon } from '../ui/icons';
|
|
|
|
interface DocumentLike {
|
|
id?: string | number;
|
|
title?: string;
|
|
content_type?: string;
|
|
filename?: string;
|
|
original_name?: string;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
interface PreviewEntry {
|
|
url?: string;
|
|
contentType?: string;
|
|
filename?: string;
|
|
}
|
|
|
|
interface ContentTabConfig {
|
|
id?: string;
|
|
label?: string;
|
|
enabled?: boolean;
|
|
forceDisplay?: boolean;
|
|
loadContent?: (options?: { signal?: AbortSignal }) => unknown | Promise<unknown>;
|
|
loadingMessage?: string;
|
|
emptyMessage?: string;
|
|
unavailableMessage?: string;
|
|
errorMessage?: string;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
type LayoutMode = 'split' | 'stacked' | (string & {});
|
|
|
|
interface DocumentViewerLayoutProps {
|
|
document?: DocumentLike | null;
|
|
previewEntry?: PreviewEntry | null;
|
|
summaryProps?: Record<string, unknown>;
|
|
metadataPayload?: unknown;
|
|
contentTabConfig?: ContentTabConfig | null;
|
|
resetKey?: string | number | null;
|
|
classNamePrefix?: string;
|
|
defaultTabId?: string;
|
|
infoPanelProps?: Record<string, unknown>;
|
|
previewLoadingMessage?: string;
|
|
layoutMode?: LayoutMode;
|
|
}
|
|
|
|
const DocumentViewerLayout = ({
|
|
document,
|
|
previewEntry,
|
|
summaryProps = {},
|
|
metadataPayload,
|
|
contentTabConfig,
|
|
resetKey,
|
|
classNamePrefix = 'document-viewer',
|
|
defaultTabId = 'details',
|
|
infoPanelProps = {},
|
|
previewLoadingMessage = 'Preparing preview…',
|
|
layoutMode = 'split',
|
|
}: DocumentViewerLayoutProps): JSX.Element => {
|
|
const isStacked = layoutMode === 'stacked';
|
|
|
|
const previewContent = useMemo(() => {
|
|
if (!document || !previewEntry?.url) {
|
|
return null;
|
|
}
|
|
|
|
const normalizedContentType = (previewEntry.contentType
|
|
|| document.content_type
|
|
|| '')
|
|
.toLowerCase();
|
|
const isImage = normalizedContentType.startsWith('image/');
|
|
const isPdf = normalizedContentType === 'application/pdf'
|
|
|| normalizedContentType === 'application/x-pdf';
|
|
|
|
if (isImage) {
|
|
return (
|
|
<img
|
|
src={previewEntry.url}
|
|
alt={`Preview of ${document.title}`}
|
|
className="document-viewer__object document-viewer__object--image"
|
|
draggable={false}
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (isPdf) {
|
|
return (
|
|
<iframe
|
|
src={previewEntry.url}
|
|
title={`Preview of ${document.title}`}
|
|
className="document-viewer__object"
|
|
/>
|
|
);
|
|
}
|
|
|
|
const displayContentType = document.content_type || previewEntry.contentType || 'this file type';
|
|
const displayFilename = previewEntry.filename
|
|
|| document.filename
|
|
|| document.original_name
|
|
|| 'download';
|
|
|
|
return (
|
|
<div className="document-viewer__unsupported">
|
|
<div className="document-viewer__unsupported-message">
|
|
Preview is not available for {displayContentType} files.
|
|
</div>
|
|
<div className="document-viewer__unsupported-filename">{displayFilename}</div>
|
|
<a
|
|
className="button-link document-viewer__unsupported-download"
|
|
href={previewEntry.url}
|
|
download={displayFilename}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<DownloadIcon />
|
|
<span>Download</span>
|
|
</a>
|
|
</div>
|
|
);
|
|
}, [previewEntry, document]);
|
|
|
|
const renderViewportPane = useCallback(() => (
|
|
<div className="document-viewer__viewport">
|
|
{!previewEntry?.url ? (
|
|
<div className="document-viewer__message">{previewLoadingMessage}</div>
|
|
) : (
|
|
previewContent
|
|
)}
|
|
</div>
|
|
), [previewEntry?.url, previewLoadingMessage, previewContent]);
|
|
|
|
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 = (
|
|
<div className="document-viewer__details-pane">
|
|
<div className="document-viewer__details">
|
|
<DocumentInfoPanel
|
|
document={document}
|
|
summaryProps={summaryProps}
|
|
metadataPayload={metadataPayload}
|
|
contentConfig={contentTabConfig}
|
|
defaultTabId={resolvedDefaultTabId}
|
|
classNamePrefix={classNamePrefix}
|
|
hideTabNavWhenSingle={false}
|
|
resetKey={resetKey || document?.id}
|
|
summaryPlacement={summaryPlacement}
|
|
summaryLayout={summaryLayout}
|
|
leadingTabs={stackedLeadingTabs}
|
|
tabsPlacement={tabsPlacement}
|
|
{...infoPanelProps}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
if (isStacked) {
|
|
return detailsPane;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{detailsPane}
|
|
{viewportPane}
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default DocumentViewerLayout;
|