feat: Replaced document preview with a new fullscreen viewer
This commit is contained in:
@@ -16,7 +16,7 @@ import {
|
||||
sortCorrespondents,
|
||||
} from './components/DocumentSummarySection';
|
||||
import DocumentDownloadLink from '../documents/components/DocumentDownloadLink';
|
||||
import { usePreviewContext } from './PreviewContext';
|
||||
import { useFullscreenPreviewContext } from './FullscreenPreviewContext';
|
||||
import type { DocumentSummarySectionProps } from './components/DocumentSummarySection';
|
||||
import { extractDocumentMetadataPayload } from './logic/documentSummary';
|
||||
import { resolveDocumentAssetUrl } from '../lib/assets/AssetManager';
|
||||
@@ -218,14 +218,14 @@ const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({
|
||||
[hasOcr, loadOcrContent],
|
||||
);
|
||||
|
||||
const { openPreview } = usePreviewContext();
|
||||
const { openFullscreenPreview } = useFullscreenPreviewContext();
|
||||
|
||||
const handleZoomOpen = useCallback(() => {
|
||||
if (!document) {
|
||||
return;
|
||||
}
|
||||
openPreview(document);
|
||||
}, [document, openPreview]);
|
||||
openFullscreenPreview(document);
|
||||
}, [document, openFullscreenPreview]);
|
||||
|
||||
const panelRef = useRef<HTMLDivElement | HTMLFormElement | HTMLElement | null>(null);
|
||||
const isStackedLayout = useViewerLayoutMode(panelRef, document?.id);
|
||||
|
||||
+19
-19
@@ -1,33 +1,33 @@
|
||||
import React, { useState, useCallback, useMemo, useRef } from 'react';
|
||||
import PreviewZoomOverlay from './components/PreviewZoomOverlay';
|
||||
import FullscreenPreviewOverlay from './components/FullscreenPreviewOverlay';
|
||||
import type { Document } from '../types/documents';
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
import { createSafeContext } from '../utils/createSafeContext';
|
||||
|
||||
interface PreviewContextType {
|
||||
openPreview: (doc: Document) => void;
|
||||
closePreview: () => void;
|
||||
interface FullscreenPreviewContextType {
|
||||
openFullscreenPreview: (doc: Document) => void;
|
||||
closeFullscreenPreview: () => void;
|
||||
}
|
||||
|
||||
const [PreviewContext, usePreviewContext] = createSafeContext<PreviewContextType>('Preview');
|
||||
const [FullscreenPreviewContext, useFullscreenPreviewContext] = createSafeContext<FullscreenPreviewContextType>('FullscreenPreview');
|
||||
|
||||
interface PreviewProviderProps {
|
||||
interface FullscreenPreviewProviderProps {
|
||||
children: React.ReactNode;
|
||||
onNavigate?: (documentId: Identifier) => void;
|
||||
}
|
||||
|
||||
export const PreviewProvider: React.FC<PreviewProviderProps> = ({ children, onNavigate }) => {
|
||||
export const FullscreenPreviewProvider: React.FC<FullscreenPreviewProviderProps> = ({ children, onNavigate }) => {
|
||||
const [previewDoc, setPreviewDoc] = useState<Document | null>(null);
|
||||
const lastFocusedElement = useRef<HTMLElement | null>(null);
|
||||
|
||||
const openPreview = useCallback((doc: Document) => {
|
||||
const openFullscreenPreview = useCallback((doc: Document) => {
|
||||
if (!lastFocusedElement.current) {
|
||||
lastFocusedElement.current = document.activeElement as HTMLElement;
|
||||
}
|
||||
setPreviewDoc(doc);
|
||||
}, []);
|
||||
|
||||
const closePreview = useCallback(() => {
|
||||
const closeFullscreenPreview = useCallback(() => {
|
||||
setPreviewDoc(null);
|
||||
if (lastFocusedElement.current) {
|
||||
lastFocusedElement.current.focus();
|
||||
@@ -38,26 +38,26 @@ export const PreviewProvider: React.FC<PreviewProviderProps> = ({ children, onNa
|
||||
const handleMaximize = useCallback(() => {
|
||||
if (previewDoc && onNavigate) {
|
||||
onNavigate(previewDoc.id);
|
||||
closePreview();
|
||||
closeFullscreenPreview();
|
||||
}
|
||||
}, [previewDoc, onNavigate, closePreview]);
|
||||
}, [previewDoc, onNavigate, closeFullscreenPreview]);
|
||||
|
||||
const value = useMemo(() => ({
|
||||
openPreview,
|
||||
closePreview,
|
||||
}), [openPreview, closePreview]);
|
||||
openFullscreenPreview,
|
||||
closeFullscreenPreview,
|
||||
}), [openFullscreenPreview, closeFullscreenPreview]);
|
||||
|
||||
return (
|
||||
<PreviewContext.Provider value={value}>
|
||||
<FullscreenPreviewContext.Provider value={value}>
|
||||
{children}
|
||||
<PreviewZoomOverlay
|
||||
<FullscreenPreviewOverlay
|
||||
open={Boolean(previewDoc)}
|
||||
onClose={closePreview}
|
||||
onClose={closeFullscreenPreview}
|
||||
onMaximize={onNavigate ? handleMaximize : undefined}
|
||||
document={previewDoc}
|
||||
/>
|
||||
</PreviewContext.Provider>
|
||||
</FullscreenPreviewContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export { usePreviewContext };
|
||||
export { useFullscreenPreviewContext };
|
||||
+7
-7
@@ -7,14 +7,14 @@ import { IconX, FileInfoIcon } from '../../components/icons';
|
||||
|
||||
import type { Document } from '../../types/documents';
|
||||
|
||||
interface PreviewZoomOverlayProps {
|
||||
interface FullscreenPreviewOverlayProps {
|
||||
open?: boolean;
|
||||
onClose: () => void;
|
||||
onMaximize?: () => void;
|
||||
document?: Document | null;
|
||||
}
|
||||
|
||||
const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
|
||||
const FullscreenPreviewOverlay: React.FC<FullscreenPreviewOverlayProps> = ({
|
||||
open = false,
|
||||
onClose,
|
||||
onMaximize,
|
||||
@@ -81,12 +81,12 @@ const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const stageClassName = 'preview-zoom__stage';
|
||||
const containerClassName = 'preview-zoom__scroll';
|
||||
const stageClassName = 'fullscreen-preview__stage';
|
||||
const containerClassName = 'fullscreen-preview__scroll';
|
||||
|
||||
const backdropClassName = [
|
||||
'preview-zoom-backdrop',
|
||||
isBackdropVisible ? 'preview-zoom-backdrop--visible' : '',
|
||||
'fullscreen-preview-backdrop',
|
||||
isBackdropVisible ? 'fullscreen-preview-backdrop--visible' : '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
@@ -160,4 +160,4 @@ const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default PreviewZoomOverlay;
|
||||
export default FullscreenPreviewOverlay;
|
||||
@@ -1,4 +1,4 @@
|
||||
.preview-zoom-backdrop {
|
||||
.fullscreen-preview-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: var(--overlay-backdrop);
|
||||
@@ -14,13 +14,13 @@
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.preview-zoom-backdrop--visible {
|
||||
.fullscreen-preview-backdrop--visible {
|
||||
opacity: 1;
|
||||
background: var(--overlay-backdrop);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.preview-zoom__stage {
|
||||
.fullscreen-preview__stage {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -32,7 +32,7 @@
|
||||
--header-clearance: calc(2.5rem + 0.5rem);
|
||||
}
|
||||
|
||||
.preview-zoom__scroll .document-viewer__object--image {
|
||||
.fullscreen-preview__scroll .document-viewer__object--image {
|
||||
width: auto;
|
||||
height: auto;
|
||||
box-shadow: 0 32px 120px var(--shadow-deep);
|
||||
@@ -42,7 +42,7 @@
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.preview-zoom__scroll {
|
||||
.fullscreen-preview__scroll {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -55,21 +55,21 @@
|
||||
--pdf-viewer-stack-padding: var(--preview-padding);
|
||||
}
|
||||
|
||||
.preview-zoom__scroll:focus {
|
||||
.fullscreen-preview__scroll:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.preview-zoom__scroll .document-viewer__object--pdf {
|
||||
.fullscreen-preview__scroll .document-viewer__object--pdf {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.preview-zoom__stage .pdf-viewer__page-wrapper {
|
||||
.fullscreen-preview__stage .pdf-viewer__page-wrapper {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.preview-zoom__stage .pdf-viewer__page-wrapper::after {
|
||||
.fullscreen-preview__stage .pdf-viewer__page-wrapper::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@@ -80,7 +80,7 @@
|
||||
}
|
||||
|
||||
/* Header positioning for preview zoom overlay */
|
||||
.preview-zoom-backdrop .panel-header--dark {
|
||||
.fullscreen-preview-backdrop .panel-header--dark {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@@ -94,6 +94,6 @@
|
||||
}
|
||||
|
||||
/* Prevent header clicks from closing backdrop */
|
||||
.preview-zoom-backdrop .panel-header--dark * {
|
||||
.fullscreen-preview-backdrop .panel-header--dark * {
|
||||
pointer-events: auto;
|
||||
}
|
||||
@@ -22,7 +22,7 @@ interface UseDetailWorkspaceArgs {
|
||||
folderNodes: Map<Identifier | 'root', FolderNode>;
|
||||
detailPanelControlRef: MutableRefObject<{ open?: (documentId: Identifier) => void; close?: () => void } | null>;
|
||||
detailFolderFetchRef: MutableRefObject<Set<Identifier | 'root'>>;
|
||||
openDocumentPreview?: (args: { documentIds: Identifier[] }) => void;
|
||||
openDocumentViewer?: (args: { documentIds: Identifier[] }) => void;
|
||||
handleDocumentTitleUpdate?: (docId: Identifier, title: string) => Promise<boolean> | boolean;
|
||||
handleDocumentIssuedUpdate?: (docId: Identifier, issued: number | null) => Promise<boolean> | boolean;
|
||||
handleDocumentTagAdd?: (doc: Document, value: string, context?: { option?: unknown }) => void;
|
||||
@@ -54,7 +54,7 @@ const useDetailWorkspace = ({
|
||||
folderNodes,
|
||||
detailPanelControlRef,
|
||||
detailFolderFetchRef,
|
||||
openDocumentPreview,
|
||||
openDocumentViewer,
|
||||
handleDocumentTitleUpdate,
|
||||
handleDocumentIssuedUpdate,
|
||||
handleDocumentTagAdd,
|
||||
@@ -176,7 +176,7 @@ const useDetailWorkspace = ({
|
||||
correspondentLookupById,
|
||||
onTagAdd: handleDetailTagAdd,
|
||||
onTagRemove: handleDetailTagRemove,
|
||||
onOpenPreview: openDocumentPreview,
|
||||
onOpenViewer: openDocumentViewer,
|
||||
onUpdateTitle: handleDocumentTitleUpdate,
|
||||
onUpdateIssued: handleDocumentIssuedUpdate,
|
||||
ensureAssetUrl,
|
||||
|
||||
Reference in New Issue
Block a user