introduce a unified document viewer component.

This commit is contained in:
2025-12-04 02:21:11 +01:00
parent cbd463e1d9
commit f57bb9f6eb
6 changed files with 82 additions and 86 deletions
+3 -3
View File
@@ -116,8 +116,8 @@ export const useWorkspaceSurface = ({
<DocumentViewerPanel
variant="sidebar"
sidebarMode={sidebarMode}
onClosePanel={onClose}
onMaximizePanel={onOpenPreview}
onClose={onClose}
onMaximize={onOpenPreview}
tagOptions={tagOptions}
{...restDetailProps}
/>
@@ -194,7 +194,7 @@ export const useWorkspaceSurface = ({
ensurePreviewData={ensurePreviewData}
notifyApiError={notifyApiError}
sidebarToggle={sidebarToggle}
onClosePanel={closeDocumentPreview}
onClose={closeDocumentPreview}
resolveFolderPath={resolveFolderPath}
/>
);
+5 -30
View File
@@ -1,7 +1,6 @@
import React, { useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import PdfViewer from '../preview/PdfViewer';
import MediaViewer from '../preview/MediaViewer';
import UnifiedDocumentViewer from '../preview/UnifiedDocumentViewer';
import PanelHeader from '../ui/PanelHeader';
import { IconX, DownloadIcon, FileInfoIcon } from '../ui/icons';
@@ -14,16 +13,6 @@ interface PreviewZoomOverlayProps {
document?: Document | null;
}
type DisplayKind = 'image' | 'pdf';
const determineDisplayKind = (doc?: Document | null): DisplayKind => {
const type = doc?.mime_type?.toLowerCase?.() || '';
if (type.includes('pdf')) {
return 'pdf';
}
return 'image';
};
const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
open = false,
onClose,
@@ -41,7 +30,6 @@ const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
}
const downloadUrl = lastDocumentRef.current?.current_version?.download?.url;
const displayKind = determineDisplayKind(lastDocumentRef.current);
const documentTitle = lastDocumentRef.current?.title || undefined;
useEffect(() => {
@@ -172,23 +160,10 @@ const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
ref={scrollRef}
tabIndex={-1}
>
{displayKind === 'pdf' ? (
<PdfViewer
src={downloadUrl}
title={documentTitle}
className="preview-zoom__pdf-viewer"
viewportRef={scrollRef}
/>
) : (
<MediaViewer
src={downloadUrl}
alt={documentTitle}
className="preview-zoom__image"
draggable={false}
onClick={(e) => e.stopPropagation()}
mimeType={lastDocumentRef.current?.mime_type || undefined}
/>
)}
<UnifiedDocumentViewer
document={lastDocumentRef.current}
viewportRef={scrollRef}
/>
</div>
</div>
</div>
+7 -40
View File
@@ -1,8 +1,7 @@
import { useCallback, useMemo, useRef } from 'react';
import type { JSX } from 'react';
import DocumentInfoPanel from '../documents/DocumentInfoPanel';
import PdfViewer from './PdfViewer';
import MediaViewer from './MediaViewer';
import UnifiedDocumentViewer from './UnifiedDocumentViewer';
import type { Document } from '../types/documents';
@@ -33,7 +32,6 @@ interface DocumentViewerLayoutProps {
previewLoadingMessage?: string;
layoutMode?: LayoutMode;
}
const DocumentViewerLayout = ({
document,
summaryProps = {},
@@ -49,43 +47,12 @@ const DocumentViewerLayout = ({
const isStacked = layoutMode === 'stacked';
const viewportRef = useRef<HTMLDivElement>(null);
const previewContent = useMemo(() => {
if (!document) {
return null;
}
const downloadUrl = document.current_version?.download?.url;
if (!downloadUrl) {
return null;
}
const normalizedMimeType = (document.mime_type || '').toLowerCase();
const normalizedFilename = document.filename;
const isPdf = normalizedMimeType === 'application/pdf'
|| normalizedMimeType === 'application/x-pdf';
if (isPdf) {
const documentTitle = document.title;
return (
<PdfViewer
src={downloadUrl}
title={documentTitle ? `Preview of ${documentTitle}` : undefined}
viewportRef={viewportRef}
/>
);
}
return (
<MediaViewer
src={downloadUrl}
mimeType={normalizedMimeType}
filename={normalizedFilename}
alt={`Preview of ${document.title}`}
/>
);
}, [document, viewportRef]);
const previewContent = useMemo(() => (
<UnifiedDocumentViewer
document={document}
viewportRef={viewportRef}
/>
), [document, viewportRef]);
const renderViewportPane = useCallback(() => (
<div className="document-viewer__viewport" ref={viewportRef}>
+8 -8
View File
@@ -39,10 +39,10 @@ interface DocumentViewerPanelProps extends DocumentSummarySectionProps {
ensurePreviewData?: (docId: DocumentId, options?: { signal?: AbortSignal }) => Promise<Document | null>;
notifyApiError?: (error: unknown, fallbackMessage?: string) => void;
sidebarToggle?: ReactNode;
onClosePanel?: () => void;
onClose?: () => void;
resolveFolderPath?: (doc: Document | null) => Array<{ id?: string; name?: string }>;
variant?: 'viewer' | 'sidebar';
onMaximizePanel?: (args: { documentIds: Array<string> }) => void;
onMaximize?: (args: { documentIds: Array<string> }) => void;
sidebarMode?: SidebarMode;
}
@@ -95,10 +95,10 @@ const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({
ensurePreviewData,
notifyApiError,
sidebarToggle = null,
onClosePanel,
onClose,
resolveFolderPath,
variant = 'viewer',
onMaximizePanel,
onMaximize,
sidebarMode = 'overlay',
}) => {
const navigate = useNavigate();
@@ -299,7 +299,7 @@ const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({
canZoom: Boolean(document),
});
const maximizeButton = isSidebarVariant && onMaximizePanel
const maximizeButton = isSidebarVariant && onMaximize
? (
<button
type="button"
@@ -310,7 +310,7 @@ const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({
if (targetId == null) {
return;
}
onMaximizePanel?.({ documentIds: [targetId] });
onMaximize?.({ documentIds: [targetId] });
}}
aria-label="Maximize"
title="Maximize"
@@ -320,12 +320,12 @@ const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({
)
: null;
const closeButton = onClosePanel
const closeButton = onClose
? (
<button
type="button"
className="icon-button"
onClick={() => onClosePanel?.()}
onClick={() => onClose?.()}
aria-label="Close preview"
title="Close preview"
>
@@ -0,0 +1,57 @@
import React, { useMemo } from 'react';
import PdfViewer from './PdfViewer';
import MediaViewer from './MediaViewer';
import type { Document } from '../types/documents';
interface UnifiedDocumentViewerProps {
document: Document | null;
viewportRef?: React.RefObject<HTMLDivElement>;
}
const UnifiedDocumentViewer: React.FC<UnifiedDocumentViewerProps> = ({
document,
viewportRef,
}) => {
const content = useMemo(() => {
if (!document) {
return null;
}
const downloadUrl = document.current_version?.download?.url;
if (!downloadUrl) {
return null;
}
const normalizedMimeType = (document.mime_type || '').toLowerCase();
const normalizedFilename = document.filename;
const isPdf = normalizedMimeType === 'application/pdf'
|| normalizedMimeType === 'application/x-pdf';
if (isPdf) {
return (
<PdfViewer
src={downloadUrl}
title={document.title}
viewportRef={viewportRef}
/>
);
}
return (
<MediaViewer
src={downloadUrl}
mimeType={normalizedMimeType}
filename={normalizedFilename}
alt={document.title}
/>
);
}, [
document,
viewportRef,
]);
return content;
};
export default UnifiedDocumentViewer;
+2 -5
View File
@@ -32,7 +32,7 @@
--header-clearance: calc(2.5rem + 0.5rem);
}
.preview-zoom__image {
.preview-zoom__scroll .document-viewer__object--image {
width: auto;
height: auto;
box-shadow: 0 32px 120px var(--shadow-deep);
@@ -59,10 +59,7 @@
outline: none;
}
.preview-zoom__pdf-viewer {
.preview-zoom__scroll .document-viewer__object--pdf {
width: 100%;
height: 100%;
}