typescript
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user