introduce a unified document viewer component.
This commit is contained in:
@@ -116,8 +116,8 @@ export const useWorkspaceSurface = ({
|
|||||||
<DocumentViewerPanel
|
<DocumentViewerPanel
|
||||||
variant="sidebar"
|
variant="sidebar"
|
||||||
sidebarMode={sidebarMode}
|
sidebarMode={sidebarMode}
|
||||||
onClosePanel={onClose}
|
onClose={onClose}
|
||||||
onMaximizePanel={onOpenPreview}
|
onMaximize={onOpenPreview}
|
||||||
tagOptions={tagOptions}
|
tagOptions={tagOptions}
|
||||||
{...restDetailProps}
|
{...restDetailProps}
|
||||||
/>
|
/>
|
||||||
@@ -194,7 +194,7 @@ export const useWorkspaceSurface = ({
|
|||||||
ensurePreviewData={ensurePreviewData}
|
ensurePreviewData={ensurePreviewData}
|
||||||
notifyApiError={notifyApiError}
|
notifyApiError={notifyApiError}
|
||||||
sidebarToggle={sidebarToggle}
|
sidebarToggle={sidebarToggle}
|
||||||
onClosePanel={closeDocumentPreview}
|
onClose={closeDocumentPreview}
|
||||||
resolveFolderPath={resolveFolderPath}
|
resolveFolderPath={resolveFolderPath}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import PdfViewer from '../preview/PdfViewer';
|
import UnifiedDocumentViewer from '../preview/UnifiedDocumentViewer';
|
||||||
import MediaViewer from '../preview/MediaViewer';
|
|
||||||
import PanelHeader from '../ui/PanelHeader';
|
import PanelHeader from '../ui/PanelHeader';
|
||||||
import { IconX, DownloadIcon, FileInfoIcon } from '../ui/icons';
|
import { IconX, DownloadIcon, FileInfoIcon } from '../ui/icons';
|
||||||
|
|
||||||
@@ -14,16 +13,6 @@ interface PreviewZoomOverlayProps {
|
|||||||
document?: Document | null;
|
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> = ({
|
const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
|
||||||
open = false,
|
open = false,
|
||||||
onClose,
|
onClose,
|
||||||
@@ -41,7 +30,6 @@ const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const downloadUrl = lastDocumentRef.current?.current_version?.download?.url;
|
const downloadUrl = lastDocumentRef.current?.current_version?.download?.url;
|
||||||
const displayKind = determineDisplayKind(lastDocumentRef.current);
|
|
||||||
const documentTitle = lastDocumentRef.current?.title || undefined;
|
const documentTitle = lastDocumentRef.current?.title || undefined;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -172,23 +160,10 @@ const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
|
|||||||
ref={scrollRef}
|
ref={scrollRef}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
>
|
>
|
||||||
{displayKind === 'pdf' ? (
|
<UnifiedDocumentViewer
|
||||||
<PdfViewer
|
document={lastDocumentRef.current}
|
||||||
src={downloadUrl}
|
viewportRef={scrollRef}
|
||||||
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}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { useCallback, useMemo, useRef } from 'react';
|
import { useCallback, useMemo, useRef } from 'react';
|
||||||
import type { JSX } from 'react';
|
import type { JSX } from 'react';
|
||||||
import DocumentInfoPanel from '../documents/DocumentInfoPanel';
|
import DocumentInfoPanel from '../documents/DocumentInfoPanel';
|
||||||
import PdfViewer from './PdfViewer';
|
import UnifiedDocumentViewer from './UnifiedDocumentViewer';
|
||||||
import MediaViewer from './MediaViewer';
|
|
||||||
|
|
||||||
import type { Document } from '../types/documents';
|
import type { Document } from '../types/documents';
|
||||||
|
|
||||||
@@ -33,7 +32,6 @@ interface DocumentViewerLayoutProps {
|
|||||||
previewLoadingMessage?: string;
|
previewLoadingMessage?: string;
|
||||||
layoutMode?: LayoutMode;
|
layoutMode?: LayoutMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DocumentViewerLayout = ({
|
const DocumentViewerLayout = ({
|
||||||
document,
|
document,
|
||||||
summaryProps = {},
|
summaryProps = {},
|
||||||
@@ -49,43 +47,12 @@ const DocumentViewerLayout = ({
|
|||||||
const isStacked = layoutMode === 'stacked';
|
const isStacked = layoutMode === 'stacked';
|
||||||
const viewportRef = useRef<HTMLDivElement>(null);
|
const viewportRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const previewContent = useMemo(() => {
|
const previewContent = useMemo(() => (
|
||||||
if (!document) {
|
<UnifiedDocumentViewer
|
||||||
return null;
|
document={document}
|
||||||
}
|
viewportRef={viewportRef}
|
||||||
|
/>
|
||||||
const downloadUrl = document.current_version?.download?.url;
|
), [document, viewportRef]);
|
||||||
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 renderViewportPane = useCallback(() => (
|
const renderViewportPane = useCallback(() => (
|
||||||
<div className="document-viewer__viewport" ref={viewportRef}>
|
<div className="document-viewer__viewport" ref={viewportRef}>
|
||||||
|
|||||||
@@ -39,10 +39,10 @@ interface DocumentViewerPanelProps extends DocumentSummarySectionProps {
|
|||||||
ensurePreviewData?: (docId: DocumentId, options?: { signal?: AbortSignal }) => Promise<Document | null>;
|
ensurePreviewData?: (docId: DocumentId, options?: { signal?: AbortSignal }) => Promise<Document | null>;
|
||||||
notifyApiError?: (error: unknown, fallbackMessage?: string) => void;
|
notifyApiError?: (error: unknown, fallbackMessage?: string) => void;
|
||||||
sidebarToggle?: ReactNode;
|
sidebarToggle?: ReactNode;
|
||||||
onClosePanel?: () => void;
|
onClose?: () => void;
|
||||||
resolveFolderPath?: (doc: Document | null) => Array<{ id?: string; name?: string }>;
|
resolveFolderPath?: (doc: Document | null) => Array<{ id?: string; name?: string }>;
|
||||||
variant?: 'viewer' | 'sidebar';
|
variant?: 'viewer' | 'sidebar';
|
||||||
onMaximizePanel?: (args: { documentIds: Array<string> }) => void;
|
onMaximize?: (args: { documentIds: Array<string> }) => void;
|
||||||
sidebarMode?: SidebarMode;
|
sidebarMode?: SidebarMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,10 +95,10 @@ const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({
|
|||||||
ensurePreviewData,
|
ensurePreviewData,
|
||||||
notifyApiError,
|
notifyApiError,
|
||||||
sidebarToggle = null,
|
sidebarToggle = null,
|
||||||
onClosePanel,
|
onClose,
|
||||||
resolveFolderPath,
|
resolveFolderPath,
|
||||||
variant = 'viewer',
|
variant = 'viewer',
|
||||||
onMaximizePanel,
|
onMaximize,
|
||||||
sidebarMode = 'overlay',
|
sidebarMode = 'overlay',
|
||||||
}) => {
|
}) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -299,7 +299,7 @@ const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({
|
|||||||
canZoom: Boolean(document),
|
canZoom: Boolean(document),
|
||||||
});
|
});
|
||||||
|
|
||||||
const maximizeButton = isSidebarVariant && onMaximizePanel
|
const maximizeButton = isSidebarVariant && onMaximize
|
||||||
? (
|
? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -310,7 +310,7 @@ const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({
|
|||||||
if (targetId == null) {
|
if (targetId == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onMaximizePanel?.({ documentIds: [targetId] });
|
onMaximize?.({ documentIds: [targetId] });
|
||||||
}}
|
}}
|
||||||
aria-label="Maximize"
|
aria-label="Maximize"
|
||||||
title="Maximize"
|
title="Maximize"
|
||||||
@@ -320,12 +320,12 @@ const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({
|
|||||||
)
|
)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const closeButton = onClosePanel
|
const closeButton = onClose
|
||||||
? (
|
? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="icon-button"
|
className="icon-button"
|
||||||
onClick={() => onClosePanel?.()}
|
onClick={() => onClose?.()}
|
||||||
aria-label="Close preview"
|
aria-label="Close preview"
|
||||||
title="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;
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
--header-clearance: calc(2.5rem + 0.5rem);
|
--header-clearance: calc(2.5rem + 0.5rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-zoom__image {
|
.preview-zoom__scroll .document-viewer__object--image {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
box-shadow: 0 32px 120px var(--shadow-deep);
|
box-shadow: 0 32px 120px var(--shadow-deep);
|
||||||
@@ -59,10 +59,7 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.preview-zoom__scroll .document-viewer__object--pdf {
|
||||||
|
|
||||||
|
|
||||||
.preview-zoom__pdf-viewer {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user