feat: Replaced document preview with a new fullscreen viewer
This commit is contained in:
@@ -4,7 +4,7 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
import {
|
import {
|
||||||
DocumentsFilterProvider,
|
DocumentsFilterProvider,
|
||||||
} from '../documents/context/DocumentsFilterContext';
|
} from '../documents/context/DocumentsFilterContext';
|
||||||
import { PreviewProvider, usePreviewContext } from '../viewer/PreviewContext';
|
import { FullscreenPreviewProvider, useFullscreenPreviewContext } from '../viewer/FullscreenPreviewContext';
|
||||||
import { DocumentOpenProvider } from '../lib/context/DocumentOpenContext';
|
import { DocumentOpenProvider } from '../lib/context/DocumentOpenContext';
|
||||||
import { useWorkspaceSurface } from './useWorkspaceSurface';
|
import { useWorkspaceSurface } from './useWorkspaceSurface';
|
||||||
import { SidebarProvider, useSidebarContext } from '../sidebar/SidebarContext';
|
import { SidebarProvider, useSidebarContext } from '../sidebar/SidebarContext';
|
||||||
@@ -16,7 +16,7 @@ const DocumentsInner: React.FC<{
|
|||||||
surfaceConfig: any;
|
surfaceConfig: any;
|
||||||
onNavigate: (documentId: string) => void;
|
onNavigate: (documentId: string) => void;
|
||||||
}> = ({ surfaceConfig, onNavigate }) => {
|
}> = ({ surfaceConfig, onNavigate }) => {
|
||||||
const { openPreview } = usePreviewContext();
|
const { openFullscreenPreview } = useFullscreenPreviewContext();
|
||||||
const { collapsed: sidebarCollapsed } = useSidebarContext();
|
const { collapsed: sidebarCollapsed } = useSidebarContext();
|
||||||
const {
|
const {
|
||||||
sidebarSuppressed,
|
sidebarSuppressed,
|
||||||
@@ -68,7 +68,7 @@ const DocumentsInner: React.FC<{
|
|||||||
return (
|
return (
|
||||||
<DocumentOpenProvider
|
<DocumentOpenProvider
|
||||||
onOpenViewer={onNavigate}
|
onOpenViewer={onNavigate}
|
||||||
onOpenPreview={openPreview}
|
onOpenFullscreenPreview={openFullscreenPreview}
|
||||||
onOpenDetailPanel={openDetailPanel}
|
onOpenDetailPanel={openDetailPanel}
|
||||||
>
|
>
|
||||||
{renderSurface()}
|
{renderSurface()}
|
||||||
@@ -89,14 +89,14 @@ const DocumentsRouteContent: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DocumentsFilterProvider value={documentsFilter}>
|
<DocumentsFilterProvider value={documentsFilter}>
|
||||||
<PreviewProvider
|
<FullscreenPreviewProvider
|
||||||
onNavigate={handleDocumentNavigate}
|
onNavigate={handleDocumentNavigate}
|
||||||
>
|
>
|
||||||
<DocumentsInner
|
<DocumentsInner
|
||||||
surfaceConfig={surfaceConfig}
|
surfaceConfig={surfaceConfig}
|
||||||
onNavigate={handleDocumentNavigate}
|
onNavigate={handleDocumentNavigate}
|
||||||
/>
|
/>
|
||||||
</PreviewProvider>
|
</FullscreenPreviewProvider>
|
||||||
</DocumentsFilterProvider>
|
</DocumentsFilterProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ type FolderId = DocumentId | 'root';
|
|||||||
import type { Document } from '../types/documents';
|
import type { Document } from '../types/documents';
|
||||||
import useNotifyApiError from '../hooks/useNotifyApiError';
|
import useNotifyApiError from '../hooks/useNotifyApiError';
|
||||||
|
|
||||||
interface UseDocumentPreviewArgs {
|
interface UseDocumentViewerArgs {
|
||||||
routeDocumentId?: DocumentId | null;
|
routeDocumentId?: DocumentId | null;
|
||||||
documentsManager: {
|
documentsManager: {
|
||||||
getById: (id: DocumentId) => Document | null;
|
getById: (id: DocumentId) => Document | null;
|
||||||
@@ -28,36 +28,36 @@ interface UseDocumentPreviewArgs {
|
|||||||
open?: (args?: { documentIds?: DocumentId[] }) => void;
|
open?: (args?: { documentIds?: DocumentId[] }) => void;
|
||||||
close?: () => void;
|
close?: () => void;
|
||||||
} | null>;
|
} | null>;
|
||||||
setActivePreviewId: Dispatch<SetStateAction<DocumentId | null>>;
|
setActiveViewerId: Dispatch<SetStateAction<DocumentId | null>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UseDocumentPreviewResult {
|
interface UseDocumentViewerResult {
|
||||||
ensurePreviewData: (documentId: DocumentId) => Promise<Document | null>;
|
ensureViewerData: (documentId: DocumentId) => Promise<Document | null>;
|
||||||
openDocumentPreview: (documentId: DocumentId, options?: { replace?: boolean }) => void;
|
openDocumentViewer: (documentId: DocumentId, options?: { replace?: boolean }) => void;
|
||||||
closeDocumentPreview: (folderId?: FolderId) => void;
|
closeDocumentViewer: (folderId?: FolderId) => void;
|
||||||
resetPreviewState: () => void;
|
resetViewerState: () => void;
|
||||||
previewWorkspaceDocument: Document | null;
|
viewerWorkspaceDocument: Document | null;
|
||||||
previewActive: boolean;
|
viewerActive: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useDocumentPreview = ({
|
const useDocumentViewer = ({
|
||||||
routeDocumentId,
|
routeDocumentId,
|
||||||
documentsManager,
|
documentsManager,
|
||||||
selectedFolder,
|
selectedFolder,
|
||||||
locationPathname,
|
locationPathname,
|
||||||
locationSearch,
|
locationSearch,
|
||||||
detailPanelControlRef,
|
detailPanelControlRef,
|
||||||
setActivePreviewId,
|
setActiveViewerId,
|
||||||
}: UseDocumentPreviewArgs): UseDocumentPreviewResult => {
|
}: UseDocumentViewerArgs): UseDocumentViewerResult => {
|
||||||
const previewReturnPathRef = useRef<string | null>(null);
|
const viewerReturnPathRef = useRef<string | null>(null);
|
||||||
const notifyApiError = useNotifyApiError();
|
const notifyApiError = useNotifyApiError();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const resetPreviewState = useCallback(() => {
|
const resetViewerState = useCallback(() => {
|
||||||
previewReturnPathRef.current = null;
|
viewerReturnPathRef.current = null;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const ensurePreviewData = useCallback(
|
const ensureViewerData = useCallback(
|
||||||
async (documentId: DocumentId): Promise<Document | null> => {
|
async (documentId: DocumentId): Promise<Document | null> => {
|
||||||
if (!documentId) return null;
|
if (!documentId) return null;
|
||||||
|
|
||||||
@@ -67,35 +67,35 @@ const useDocumentPreview = ({
|
|||||||
throw new Error('Document metadata unavailable.');
|
throw new Error('Document metadata unavailable.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!previewReturnPathRef.current) {
|
if (!viewerReturnPathRef.current) {
|
||||||
const fallbackFolderId = doc?.folder_id || 'root';
|
const fallbackFolderId = doc?.folder_id || 'root';
|
||||||
previewReturnPathRef.current =
|
viewerReturnPathRef.current =
|
||||||
fallbackFolderId === 'root' ? '/documents' : `/documents/folder/${fallbackFolderId}`;
|
fallbackFolderId === 'root' ? '/documents' : `/documents/folder/${fallbackFolderId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
setActivePreviewId(documentId);
|
setActiveViewerId(documentId);
|
||||||
return doc;
|
return doc;
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
documentsManager,
|
documentsManager,
|
||||||
setActivePreviewId,
|
setActiveViewerId,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const openDocumentPreview = useCallback(
|
const openDocumentViewer = useCallback(
|
||||||
(documentId: DocumentId, { replace = false }: { replace?: boolean } = {}) => {
|
(documentId: DocumentId, { replace = false }: { replace?: boolean } = {}) => {
|
||||||
if (!documentId) return;
|
if (!documentId) return;
|
||||||
detailPanelControlRef.current?.close?.();
|
detailPanelControlRef.current?.close?.();
|
||||||
previewReturnPathRef.current = `${locationPathname}${locationSearch}`;
|
viewerReturnPathRef.current = `${locationPathname}${locationSearch}`;
|
||||||
navigate(`/documents/${documentId}`, { replace });
|
navigate(`/documents/${documentId}`, { replace });
|
||||||
},
|
},
|
||||||
[navigate, locationPathname, locationSearch, detailPanelControlRef],
|
[navigate, locationPathname, locationSearch, detailPanelControlRef],
|
||||||
);
|
);
|
||||||
|
|
||||||
const closeDocumentPreview = useCallback(
|
const closeDocumentViewer = useCallback(
|
||||||
(folderId?: FolderId) => {
|
(folderId?: FolderId) => {
|
||||||
const fallbackPath = previewReturnPathRef.current;
|
const fallbackPath = viewerReturnPathRef.current;
|
||||||
previewReturnPathRef.current = null;
|
viewerReturnPathRef.current = null;
|
||||||
|
|
||||||
if (fallbackPath) {
|
if (fallbackPath) {
|
||||||
navigate(fallbackPath, { replace: false });
|
navigate(fallbackPath, { replace: false });
|
||||||
@@ -116,36 +116,36 @@ const useDocumentPreview = ({
|
|||||||
|
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
|
|
||||||
ensurePreviewData(routeDocumentId).catch((error) => {
|
ensureViewerData(routeDocumentId).catch((error) => {
|
||||||
if (cancelled) {
|
if (cancelled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
notifyApiError(error, 'Failed to open document preview.');
|
notifyApiError(error, 'Failed to open document preview.');
|
||||||
closeDocumentPreview();
|
closeDocumentViewer();
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
};
|
};
|
||||||
}, [routeDocumentId, ensurePreviewData, notifyApiError, closeDocumentPreview]);
|
}, [routeDocumentId, ensureViewerData, notifyApiError, closeDocumentViewer]);
|
||||||
|
|
||||||
const previewWorkspaceDocument = useMemo(() => {
|
const viewerWorkspaceDocument = useMemo(() => {
|
||||||
if (!routeDocumentId) {
|
if (!routeDocumentId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return documentsManager.getById(routeDocumentId);
|
return documentsManager.getById(routeDocumentId);
|
||||||
}, [routeDocumentId, documentsManager]);
|
}, [routeDocumentId, documentsManager]);
|
||||||
|
|
||||||
const previewActive = Boolean(routeDocumentId && previewWorkspaceDocument);
|
const viewerActive = Boolean(routeDocumentId && viewerWorkspaceDocument);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ensurePreviewData,
|
ensureViewerData,
|
||||||
openDocumentPreview,
|
openDocumentViewer,
|
||||||
closeDocumentPreview,
|
closeDocumentViewer,
|
||||||
resetPreviewState,
|
resetViewerState,
|
||||||
previewWorkspaceDocument,
|
viewerWorkspaceDocument,
|
||||||
previewActive,
|
viewerActive,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useDocumentPreview;
|
export default useDocumentViewer;
|
||||||
@@ -30,10 +30,10 @@ const useDocumentsShell = (): DocumentsShellView => {
|
|||||||
detailPanelOpen: Boolean(shell.detailPanel?.detailPanelOpen),
|
detailPanelOpen: Boolean(shell.detailPanel?.detailPanelOpen),
|
||||||
openDetailPanel: shell.detailPanel?.openDetailPanel as WorkspaceSurfaceConfig['openDetailPanel'],
|
openDetailPanel: shell.detailPanel?.openDetailPanel as WorkspaceSurfaceConfig['openDetailPanel'],
|
||||||
closeDetailPanel: shell.detailPanel?.closeDetailPanel as WorkspaceSurfaceConfig['closeDetailPanel'],
|
closeDetailPanel: shell.detailPanel?.closeDetailPanel as WorkspaceSurfaceConfig['closeDetailPanel'],
|
||||||
previewWorkspaceDocument: shell.preview?.previewWorkspaceDocument,
|
viewerWorkspaceDocument: shell.preview?.viewerWorkspaceDocument,
|
||||||
previewDocumentId: (shell.preview?.previewDocumentId as Identifier) ?? null,
|
viewerDocumentId: (shell.preview?.viewerDocumentId as Identifier) ?? null,
|
||||||
closeDocumentPreview: shell.preview?.closeDocumentPreview as WorkspaceSurfaceConfig['closeDocumentPreview'],
|
closeDocumentViewer: shell.preview?.closeDocumentViewer as WorkspaceSurfaceConfig['closeDocumentViewer'],
|
||||||
ensurePreviewData: shell.preview?.ensurePreviewData as WorkspaceSurfaceConfig['ensurePreviewData'],
|
ensureViewerData: shell.preview?.ensureViewerData as WorkspaceSurfaceConfig['ensureViewerData'],
|
||||||
ensureAssetUrl: shell.preview?.ensureAssetUrl as WorkspaceSurfaceConfig['ensureAssetUrl'],
|
ensureAssetUrl: shell.preview?.ensureAssetUrl as WorkspaceSurfaceConfig['ensureAssetUrl'],
|
||||||
getDocumentAsset: shell.preview?.getDocumentAsset as WorkspaceSurfaceConfig['getDocumentAsset'],
|
getDocumentAsset: shell.preview?.getDocumentAsset as WorkspaceSurfaceConfig['getDocumentAsset'],
|
||||||
notifyApiError: shell.ui?.notifyApiError as WorkspaceSurfaceConfig['notifyApiError'],
|
notifyApiError: shell.ui?.notifyApiError as WorkspaceSurfaceConfig['notifyApiError'],
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ type EnsureAssetUrl = (
|
|||||||
asset: unknown,
|
asset: unknown,
|
||||||
options?: Record<string, unknown>,
|
options?: Record<string, unknown>,
|
||||||
) => Promise<unknown> | void;
|
) => Promise<unknown> | void;
|
||||||
type EnsurePreviewData = (docId: Identifier, options?: Record<string, unknown>) => Promise<unknown>;
|
type EnsureViewerData = (docId: Identifier, options?: Record<string, unknown>) => Promise<unknown>;
|
||||||
type GetDocumentAsset = (document: unknown, assetType: string) => unknown;
|
type GetDocumentAsset = (document: unknown, assetType: string) => unknown;
|
||||||
type NotifyApiError = (error: unknown, fallbackMessage?: string) => void;
|
type NotifyApiError = (error: unknown, fallbackMessage?: string) => void;
|
||||||
|
|
||||||
type DocumentsTableProps = ComponentProps<typeof DocumentsPanel> | null;
|
type DocumentsTableProps = ComponentProps<typeof DocumentsPanel> | null;
|
||||||
type DetailPanelProps = (ComponentProps<typeof DocumentViewerPanel> & {
|
type DetailPanelProps = (ComponentProps<typeof DocumentViewerPanel> & {
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
onOpenPreview?: (args: { documentIds: Array<string> }) => void;
|
onOpenViewer?: (args: { documentIds: Array<string> }) => void;
|
||||||
folderNodes?: Map<Identifier | 'root', unknown>;
|
folderNodes?: Map<Identifier | 'root', unknown>;
|
||||||
ensureFolderData?: (
|
ensureFolderData?: (
|
||||||
folderId: Identifier | 'root',
|
folderId: Identifier | 'root',
|
||||||
@@ -35,13 +35,13 @@ export interface UseWorkspaceSurfaceArgs {
|
|||||||
documentsTableProps?: DocumentsTableProps;
|
documentsTableProps?: DocumentsTableProps;
|
||||||
detailPanelProps?: DetailPanelProps;
|
detailPanelProps?: DetailPanelProps;
|
||||||
detailPanelOpen?: boolean;
|
detailPanelOpen?: boolean;
|
||||||
previewWorkspaceDocument?: unknown;
|
viewerWorkspaceDocument?: unknown;
|
||||||
previewDocumentId?: Identifier | null;
|
viewerDocumentId?: Identifier | null;
|
||||||
ensureAssetUrl?: EnsureAssetUrl;
|
ensureAssetUrl?: EnsureAssetUrl;
|
||||||
ensurePreviewData?: EnsurePreviewData;
|
ensureViewerData?: EnsureViewerData;
|
||||||
getDocumentAsset?: GetDocumentAsset;
|
getDocumentAsset?: GetDocumentAsset;
|
||||||
notifyApiError?: NotifyApiError;
|
notifyApiError?: NotifyApiError;
|
||||||
closeDocumentPreview?: () => void;
|
closeDocumentViewer?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UseWorkspaceSurfaceResult {
|
interface UseWorkspaceSurfaceResult {
|
||||||
@@ -54,13 +54,13 @@ export const useWorkspaceSurface = ({
|
|||||||
documentsTableProps,
|
documentsTableProps,
|
||||||
detailPanelProps,
|
detailPanelProps,
|
||||||
detailPanelOpen = false,
|
detailPanelOpen = false,
|
||||||
previewWorkspaceDocument,
|
viewerWorkspaceDocument,
|
||||||
previewDocumentId,
|
viewerDocumentId,
|
||||||
ensureAssetUrl,
|
ensureAssetUrl,
|
||||||
ensurePreviewData,
|
ensureViewerData,
|
||||||
getDocumentAsset,
|
getDocumentAsset,
|
||||||
notifyApiError,
|
notifyApiError,
|
||||||
closeDocumentPreview,
|
closeDocumentViewer,
|
||||||
}: UseWorkspaceSurfaceArgs): UseWorkspaceSurfaceResult => {
|
}: UseWorkspaceSurfaceArgs): UseWorkspaceSurfaceResult => {
|
||||||
const { registerDetailCloseHandler, setDetailActive } = usePanelManager();
|
const { registerDetailCloseHandler, setDetailActive } = usePanelManager();
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ export const useWorkspaceSurface = ({
|
|||||||
? (() => {
|
? (() => {
|
||||||
const {
|
const {
|
||||||
onClose,
|
onClose,
|
||||||
onOpenPreview,
|
onOpenViewer,
|
||||||
tags: tagOptions,
|
tags: tagOptions,
|
||||||
folderNodes,
|
folderNodes,
|
||||||
ensureFolderData,
|
ensureFolderData,
|
||||||
@@ -129,7 +129,7 @@ export const useWorkspaceSurface = ({
|
|||||||
variant="sidebar"
|
variant="sidebar"
|
||||||
sidebarMode={sidebarMode}
|
sidebarMode={sidebarMode}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onMaximize={onOpenPreview}
|
onMaximize={onOpenViewer}
|
||||||
tagOptions={tagOptions}
|
tagOptions={tagOptions}
|
||||||
{...restDetailProps}
|
{...restDetailProps}
|
||||||
/>
|
/>
|
||||||
@@ -164,10 +164,10 @@ export const useWorkspaceSurface = ({
|
|||||||
detailPanelProps,
|
detailPanelProps,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const showPreviewWorkspace = Boolean(previewDocumentId);
|
const showViewerWorkspace = Boolean(viewerDocumentId);
|
||||||
|
|
||||||
const previewSurface = useMemo<WorkspaceSurface>(() => {
|
const viewerSurface = useMemo<WorkspaceSurface>(() => {
|
||||||
if (!showPreviewWorkspace) {
|
if (!showViewerWorkspace) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,8 +191,8 @@ export const useWorkspaceSurface = ({
|
|||||||
|
|
||||||
const viewer = (
|
const viewer = (
|
||||||
<DocumentViewerPanel
|
<DocumentViewerPanel
|
||||||
document={previewWorkspaceDocument || null}
|
document={viewerWorkspaceDocument || null}
|
||||||
hydrateDocument={ensurePreviewData}
|
hydrateDocument={ensureViewerData}
|
||||||
tagLookupById={tagLookupById}
|
tagLookupById={tagLookupById}
|
||||||
tagOptions={tagOptions}
|
tagOptions={tagOptions}
|
||||||
onTagAdd={onTagAdd}
|
onTagAdd={onTagAdd}
|
||||||
@@ -205,10 +205,10 @@ export const useWorkspaceSurface = ({
|
|||||||
onUpdateIssued={onUpdateIssued}
|
onUpdateIssued={onUpdateIssued}
|
||||||
ensureAssetUrl={ensureAssetUrl}
|
ensureAssetUrl={ensureAssetUrl}
|
||||||
getDocumentAsset={getDocumentAsset}
|
getDocumentAsset={getDocumentAsset}
|
||||||
ensurePreviewData={ensurePreviewData}
|
ensurePreviewData={ensureViewerData}
|
||||||
notifyApiError={notifyApiError}
|
notifyApiError={notifyApiError}
|
||||||
sidebarToggle={sidebarToggle}
|
sidebarToggle={sidebarToggle}
|
||||||
onClose={closeDocumentPreview}
|
onClose={closeDocumentViewer}
|
||||||
resolveFolderPath={resolveFolderPath}
|
resolveFolderPath={resolveFolderPath}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -223,23 +223,23 @@ export const useWorkspaceSurface = ({
|
|||||||
|
|
||||||
return { content, detail: null, detailMode: null };
|
return { content, detail: null, detailMode: null };
|
||||||
}, [
|
}, [
|
||||||
showPreviewWorkspace,
|
showViewerWorkspace,
|
||||||
previewWorkspaceDocument,
|
viewerWorkspaceDocument,
|
||||||
ensurePreviewData,
|
ensureViewerData,
|
||||||
ensureAssetUrl,
|
ensureAssetUrl,
|
||||||
getDocumentAsset,
|
getDocumentAsset,
|
||||||
notifyApiError,
|
notifyApiError,
|
||||||
renderSidebarToggle,
|
renderSidebarToggle,
|
||||||
closeDocumentPreview,
|
closeDocumentViewer,
|
||||||
detailPanelProps,
|
detailPanelProps,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const surface = useMemo<WorkspaceSurface>(() => {
|
const surface = useMemo<WorkspaceSurface>(() => {
|
||||||
if (showPreviewWorkspace) {
|
if (showViewerWorkspace) {
|
||||||
return previewSurface;
|
return viewerSurface;
|
||||||
}
|
}
|
||||||
return documentsSurface;
|
return documentsSurface;
|
||||||
}, [showPreviewWorkspace, previewSurface, documentsSurface]);
|
}, [showViewerWorkspace, viewerSurface, documentsSurface]);
|
||||||
|
|
||||||
return { surface };
|
return { surface };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ interface UseDocumentMutationsArgs {
|
|||||||
selectionState: SelectionState;
|
selectionState: SelectionState;
|
||||||
tagsState: TagsState;
|
tagsState: TagsState;
|
||||||
correspondentsState: CorrespondentsState;
|
correspondentsState: CorrespondentsState;
|
||||||
closeDocumentPreview: () => void;
|
closeDocumentViewer: () => void;
|
||||||
previewDocumentId?: DocumentId | null;
|
viewerDocumentId?: DocumentId | null;
|
||||||
resolveTargetDocumentIds: (ids?: Identifier[] | null) => Identifier[];
|
resolveTargetDocumentIds: (ids?: Identifier[] | null) => Identifier[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,8 +103,8 @@ const useDocumentMutations = ({
|
|||||||
selectionState,
|
selectionState,
|
||||||
tagsState,
|
tagsState,
|
||||||
correspondentsState,
|
correspondentsState,
|
||||||
closeDocumentPreview,
|
closeDocumentViewer,
|
||||||
previewDocumentId,
|
viewerDocumentId,
|
||||||
resolveTargetDocumentIds,
|
resolveTargetDocumentIds,
|
||||||
}: UseDocumentMutationsArgs): UseDocumentMutationsResult => {
|
}: UseDocumentMutationsArgs): UseDocumentMutationsResult => {
|
||||||
const { showToast } = useStatusToast();
|
const { showToast } = useStatusToast();
|
||||||
@@ -140,14 +140,14 @@ const useDocumentMutations = ({
|
|||||||
await queueDocumentReanalysis(documentId);
|
await queueDocumentReanalysis(documentId);
|
||||||
showToast('Analysis queued.', 'info');
|
showToast('Analysis queued.', 'info');
|
||||||
// Close preview if it's the current one to allow refresh?
|
// Close preview if it's the current one to allow refresh?
|
||||||
if (previewDocumentId === documentId) {
|
if (viewerDocumentId === documentId) {
|
||||||
closeDocumentPreview();
|
closeDocumentViewer();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifyApiError(error, 'Failed to queue analysis.');
|
notifyApiError(error, 'Failed to queue analysis.');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[closeDocumentPreview, notifyApiError, previewDocumentId, showToast],
|
[closeDocumentViewer, notifyApiError, viewerDocumentId, showToast],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDocumentsDelete = useCallback(
|
const handleDocumentsDelete = useCallback(
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ import { useAppDispatch, useAppState } from '../../lib/store/appState';
|
|||||||
import { listFolderContents } from '../../lib/api/apiClient';
|
import { listFolderContents } from '../../lib/api/apiClient';
|
||||||
import { useApi } from '../../lib/context/ApiContext';
|
import { useApi } from '../../lib/context/ApiContext';
|
||||||
import { useWorkspaceSelection } from '../../app/useWorkspaceSelection';
|
import { useWorkspaceSelection } from '../../app/useWorkspaceSelection';
|
||||||
import useDocumentPreview from '../../app/useDocumentPreview';
|
import useDocumentViewer from '../../app/useDocumentViewer';
|
||||||
import type { DocumentId, FolderNodeId, Identifier } from '../../types/identifiers';
|
import type { DocumentId, FolderNodeId, Identifier } from '../../types/identifiers';
|
||||||
import type { Document } from '../../types/documents';
|
import type { Document } from '../../types/documents';
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ const useDocumentsWorkspace = ({
|
|||||||
const docMatch = matchPath('/documents/:documentId', location.pathname);
|
const docMatch = matchPath('/documents/:documentId', location.pathname);
|
||||||
const routeFolderId = folderMatch?.params?.folderId || null;
|
const routeFolderId = folderMatch?.params?.folderId || null;
|
||||||
const routeDocumentId = docMatch?.params?.documentId || null;
|
const routeDocumentId = docMatch?.params?.documentId || null;
|
||||||
const previewDocumentId = routeDocumentId;
|
const viewerDocumentId = routeDocumentId;
|
||||||
|
|
||||||
const handleBreadcrumbNavigate = useCallback((crumb: { id?: Identifier | string } | null) => {
|
const handleBreadcrumbNavigate = useCallback((crumb: { id?: Identifier | string } | null) => {
|
||||||
if (!crumb || !crumb.id) {
|
if (!crumb || !crumb.id) {
|
||||||
@@ -157,7 +157,7 @@ const useDocumentsWorkspace = ({
|
|||||||
|
|
||||||
const [draggedDocumentIds, setDraggedDocumentIds] = useState<DocumentId[]>([]);
|
const [draggedDocumentIds, setDraggedDocumentIds] = useState<DocumentId[]>([]);
|
||||||
const [draggedFolderId, setDraggedFolderId] = useState<FolderNodeId | null>(null);
|
const [draggedFolderId, setDraggedFolderId] = useState<FolderNodeId | null>(null);
|
||||||
const [activePreviewId, setActivePreviewId] = useState<DocumentId | null>(routeDocumentId || null);
|
const [activeViewerId, setActiveViewerId] = useState<DocumentId | null>(routeDocumentId || null);
|
||||||
const shellRef = useRef(null);
|
const shellRef = useRef(null);
|
||||||
const assetManagerRef = useRef(null);
|
const assetManagerRef = useRef(null);
|
||||||
if (!assetManagerRef.current) {
|
if (!assetManagerRef.current) {
|
||||||
@@ -402,7 +402,7 @@ const useDocumentsWorkspace = ({
|
|||||||
documentsManager,
|
documentsManager,
|
||||||
});
|
});
|
||||||
|
|
||||||
const documentsFilter = documentsSearch.documentsFilterValue;
|
|
||||||
const showingSearchResults = documentsSearch.searchResultIds !== null;
|
const showingSearchResults = documentsSearch.searchResultIds !== null;
|
||||||
|
|
||||||
// Live Filter: Ensure we only show documents that actually belong to the current folder.
|
// Live Filter: Ensure we only show documents that actually belong to the current folder.
|
||||||
@@ -432,30 +432,30 @@ const useDocumentsWorkspace = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
openDocumentPreview,
|
openDocumentViewer,
|
||||||
closeDocumentPreview,
|
closeDocumentViewer,
|
||||||
resetPreviewState,
|
resetViewerState,
|
||||||
previewWorkspaceDocument,
|
viewerWorkspaceDocument,
|
||||||
previewActive,
|
viewerActive,
|
||||||
} = useDocumentPreview({
|
} = useDocumentViewer({
|
||||||
routeDocumentId: previewDocumentId,
|
routeDocumentId: viewerDocumentId,
|
||||||
documentsManager,
|
documentsManager,
|
||||||
selectedFolder,
|
selectedFolder,
|
||||||
locationPathname: location.pathname,
|
locationPathname: location.pathname,
|
||||||
locationSearch: location.search,
|
locationSearch: location.search,
|
||||||
detailPanelControlRef,
|
detailPanelControlRef,
|
||||||
setActivePreviewId,
|
setActiveViewerId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const openDocumentPreviewForDetail = useCallback(
|
const openDocumentViewerForDetail = useCallback(
|
||||||
({ documentIds }: { documentIds?: Identifier[] } = {}) => {
|
({ documentIds }: { documentIds?: Identifier[] } = {}) => {
|
||||||
const targetId = documentIds?.find((value): value is Identifier => value != null);
|
const targetId = documentIds?.find((value): value is Identifier => value != null);
|
||||||
if (targetId == null) {
|
if (targetId == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
openDocumentPreview(targetId, { replace: true });
|
openDocumentViewer(targetId, { replace: true });
|
||||||
},
|
},
|
||||||
[openDocumentPreview],
|
[openDocumentViewer],
|
||||||
);
|
);
|
||||||
|
|
||||||
const getDocumentAsset = useCallback((doc, type) => {
|
const getDocumentAsset = useCallback((doc, type) => {
|
||||||
@@ -475,8 +475,8 @@ const useDocumentsWorkspace = ({
|
|||||||
selectionAnchorRef,
|
selectionAnchorRef,
|
||||||
setFocusedDocumentId,
|
setFocusedDocumentId,
|
||||||
selectedDocumentIds,
|
selectedDocumentIds,
|
||||||
activePreviewId,
|
activeViewerId,
|
||||||
setActivePreviewId,
|
setActiveViewerId,
|
||||||
selectionInitializedRef,
|
selectionInitializedRef,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -573,10 +573,10 @@ const useDocumentsWorkspace = ({
|
|||||||
documentsSearch.setSearchQuery('');
|
documentsSearch.setSearchQuery('');
|
||||||
documentsSearch.setActiveTagFilters([]);
|
documentsSearch.setActiveTagFilters([]);
|
||||||
documentsSearch.setActiveCorrespondentFilters([]);
|
documentsSearch.setActiveCorrespondentFilters([]);
|
||||||
setActivePreviewId(null);
|
setActiveViewerId(null);
|
||||||
detailPanelControlRef.current.close();
|
detailPanelControlRef.current.close();
|
||||||
assetManager.reset();
|
assetManager.reset();
|
||||||
resetPreviewState();
|
resetViewerState();
|
||||||
upload.resetUploadsState();
|
upload.resetUploadsState();
|
||||||
upload.clearUploadQueue();
|
upload.clearUploadQueue();
|
||||||
|
|
||||||
@@ -598,8 +598,8 @@ const useDocumentsWorkspace = ({
|
|||||||
setDraggedDocumentIds,
|
setDraggedDocumentIds,
|
||||||
setDraggedFolderId,
|
setDraggedFolderId,
|
||||||
documentsSearch,
|
documentsSearch,
|
||||||
setActivePreviewId,
|
setActiveViewerId,
|
||||||
resetPreviewState,
|
resetViewerState,
|
||||||
upload,
|
upload,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -631,8 +631,8 @@ const useDocumentsWorkspace = ({
|
|||||||
...correspondentsState,
|
...correspondentsState,
|
||||||
correspondentManager,
|
correspondentManager,
|
||||||
},
|
},
|
||||||
closeDocumentPreview,
|
closeDocumentViewer,
|
||||||
previewDocumentId,
|
viewerDocumentId,
|
||||||
resolveTargetDocumentIds,
|
resolveTargetDocumentIds,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -693,7 +693,7 @@ const useDocumentsWorkspace = ({
|
|||||||
selectionAnchorRef,
|
selectionAnchorRef,
|
||||||
promoteSelectionOrderRaw,
|
promoteSelectionOrderRaw,
|
||||||
setFocusedDocumentId,
|
setFocusedDocumentId,
|
||||||
setActivePreviewId,
|
setActiveViewerId,
|
||||||
clearSelection,
|
clearSelection,
|
||||||
focusedDocumentId,
|
focusedDocumentId,
|
||||||
setFocusedEntryKey,
|
setFocusedEntryKey,
|
||||||
@@ -884,12 +884,12 @@ const useDocumentsWorkspace = ({
|
|||||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||||
}, [settingsOpen]);
|
}, [settingsOpen]);
|
||||||
|
|
||||||
const detailPanel = useDetailWorkspace({
|
const detailWorkspace = useDetailWorkspace({
|
||||||
documentLookup,
|
documentLookup,
|
||||||
folderNodes,
|
folderNodes,
|
||||||
detailPanelControlRef,
|
detailPanelControlRef,
|
||||||
detailFolderFetchRef,
|
detailFolderFetchRef,
|
||||||
openDocumentPreview: openDocumentPreviewForDetail,
|
openDocumentViewer: openDocumentViewerForDetail,
|
||||||
handleDocumentTitleUpdate,
|
handleDocumentTitleUpdate,
|
||||||
handleDocumentIssuedUpdate,
|
handleDocumentIssuedUpdate,
|
||||||
handleDocumentTagAdd,
|
handleDocumentTagAdd,
|
||||||
@@ -906,6 +906,8 @@ const useDocumentsWorkspace = ({
|
|||||||
resolveFolderPath,
|
resolveFolderPath,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleEntryPointerCore = useEntryPointerCore({
|
const handleEntryPointerCore = useEntryPointerCore({
|
||||||
onSelectEntry: (entry, event, { rowKey, modifierClick, primaryClick }) => {
|
onSelectEntry: (entry, event, { rowKey, modifierClick, primaryClick }) => {
|
||||||
const { type, id } = entry;
|
const { type, id } = entry;
|
||||||
@@ -979,17 +981,18 @@ const useDocumentsWorkspace = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const preview = {
|
const preview = {
|
||||||
previewActive,
|
openDocumentViewerForDetail,
|
||||||
previewWorkspaceDocument,
|
viewerActive,
|
||||||
previewDocumentId,
|
viewerWorkspaceDocument,
|
||||||
closeDocumentPreview,
|
viewerDocumentId,
|
||||||
|
closeDocumentViewer,
|
||||||
|
selectionContext,
|
||||||
ensureAssetUrl,
|
ensureAssetUrl,
|
||||||
getDocumentAsset,
|
getDocumentAsset,
|
||||||
};
|
};
|
||||||
|
|
||||||
const search = {
|
const search = {
|
||||||
searchQuery: documentsSearch.searchQuery,
|
searchQuery: documentsSearch.searchQuery,
|
||||||
documentsFilter,
|
|
||||||
searchLoading: documentsSearch.searchLoading,
|
searchLoading: documentsSearch.searchLoading,
|
||||||
documentsViewMode,
|
documentsViewMode,
|
||||||
handleDocumentsViewModeChange,
|
handleDocumentsViewModeChange,
|
||||||
@@ -999,6 +1002,7 @@ const useDocumentsWorkspace = ({
|
|||||||
handleDocumentsSortDirectionToggle,
|
handleDocumentsSortDirectionToggle,
|
||||||
searchResultIds: documentsSearch.searchResultIds,
|
searchResultIds: documentsSearch.searchResultIds,
|
||||||
documents: viewDocuments,
|
documents: viewDocuments,
|
||||||
|
documentsFilter: documentsSearch.documentsFilterValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
const folderTree = {
|
const folderTree = {
|
||||||
@@ -1043,7 +1047,7 @@ const useDocumentsWorkspace = ({
|
|||||||
correspondents,
|
correspondents,
|
||||||
passkeys,
|
passkeys,
|
||||||
preview,
|
preview,
|
||||||
detailPanel,
|
detailPanel: detailWorkspace,
|
||||||
search,
|
search,
|
||||||
folderTree,
|
folderTree,
|
||||||
selection,
|
selection,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ interface UseDocumentsSelectionOptions {
|
|||||||
selectionAnchorRef: { current: string | null };
|
selectionAnchorRef: { current: string | null };
|
||||||
promoteSelectionOrderRaw: (id: DocumentId) => void;
|
promoteSelectionOrderRaw: (id: DocumentId) => void;
|
||||||
setFocusedDocumentId: (id: DocumentId | null) => void;
|
setFocusedDocumentId: (id: DocumentId | null) => void;
|
||||||
setActivePreviewId: (id: DocumentId | null) => void;
|
setActiveViewerId: (id: DocumentId | null) => void;
|
||||||
clearSelection: () => void;
|
clearSelection: () => void;
|
||||||
focusedDocumentId: DocumentId | null;
|
focusedDocumentId: DocumentId | null;
|
||||||
setFocusedEntryKey: Dispatch<SetStateAction<string | null>>;
|
setFocusedEntryKey: Dispatch<SetStateAction<string | null>>;
|
||||||
@@ -46,7 +46,7 @@ const useDocumentsSelection = ({
|
|||||||
selectionAnchorRef,
|
selectionAnchorRef,
|
||||||
promoteSelectionOrderRaw,
|
promoteSelectionOrderRaw,
|
||||||
setFocusedDocumentId,
|
setFocusedDocumentId,
|
||||||
setActivePreviewId,
|
setActiveViewerId,
|
||||||
clearSelection,
|
clearSelection,
|
||||||
focusedDocumentId,
|
focusedDocumentId,
|
||||||
setFocusedEntryKey,
|
setFocusedEntryKey,
|
||||||
@@ -92,9 +92,9 @@ const useDocumentsSelection = ({
|
|||||||
selectionAnchorRef.current = rowKey;
|
selectionAnchorRef.current = rowKey;
|
||||||
}
|
}
|
||||||
setFocusedDocumentId(docId);
|
setFocusedDocumentId(docId);
|
||||||
setActivePreviewId(docId);
|
setActiveViewerId(docId);
|
||||||
},
|
},
|
||||||
[promoteSelectionOrderRaw, selectionAnchorRef, setFocusedDocumentId, setActivePreviewId],
|
[promoteSelectionOrderRaw, selectionAnchorRef, setFocusedDocumentId, setActiveViewerId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const clearDocumentSelection = useCallback(() => {
|
const clearDocumentSelection = useCallback(() => {
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ interface UseWorkspaceSelectionSyncArgs {
|
|||||||
selectionAnchorRef: MutableRefObject<Identifier | string | null>;
|
selectionAnchorRef: MutableRefObject<Identifier | string | null>;
|
||||||
setFocusedDocumentId: (id: Identifier | null) => void;
|
setFocusedDocumentId: (id: Identifier | null) => void;
|
||||||
selectedDocumentIds: Identifier[];
|
selectedDocumentIds: Identifier[];
|
||||||
activePreviewId: Identifier | null;
|
activeViewerId: Identifier | null;
|
||||||
setActivePreviewId: (id: Identifier | null) => void;
|
setActiveViewerId: (id: Identifier | null) => void;
|
||||||
selectionInitializedRef: MutableRefObject<boolean>;
|
selectionInitializedRef: MutableRefObject<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,8 +25,8 @@ const useWorkspaceSelectionSync = ({
|
|||||||
selectionAnchorRef,
|
selectionAnchorRef,
|
||||||
setFocusedDocumentId,
|
setFocusedDocumentId,
|
||||||
selectedDocumentIds,
|
selectedDocumentIds,
|
||||||
activePreviewId,
|
activeViewerId,
|
||||||
setActivePreviewId,
|
setActiveViewerId,
|
||||||
selectionInitializedRef,
|
selectionInitializedRef,
|
||||||
}: UseWorkspaceSelectionSyncArgs) => {
|
}: UseWorkspaceSelectionSyncArgs) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -52,11 +52,11 @@ const useWorkspaceSelectionSync = ({
|
|||||||
if (!selectedDocumentIds.length) {
|
if (!selectedDocumentIds.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!selectedDocumentIds.includes(activePreviewId as Identifier)) {
|
if (!selectedDocumentIds.includes(activeViewerId as Identifier)) {
|
||||||
setActivePreviewId(selectedDocumentIds[selectedDocumentIds.length - 1]);
|
setActiveViewerId(selectedDocumentIds[selectedDocumentIds.length - 1]);
|
||||||
}
|
}
|
||||||
selectionInitializedRef.current = true;
|
selectionInitializedRef.current = true;
|
||||||
}, [selectedDocumentIds, activePreviewId, selectionInitializedRef, setActivePreviewId]);
|
}, [selectedDocumentIds, activeViewerId, selectionInitializedRef, setActiveViewerId]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useWorkspaceSelectionSync;
|
export default useWorkspaceSelectionSync;
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ const [DocumentOpenContext, useDocumentOpen] = createSafeContext<DocumentOpenCon
|
|||||||
interface DocumentOpenProviderProps {
|
interface DocumentOpenProviderProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
onOpenViewer?: (docId: Identifier) => void;
|
onOpenViewer?: (docId: Identifier) => void;
|
||||||
onOpenPreview?: (doc: Document) => void;
|
onOpenFullscreenPreview?: (doc: Document) => void;
|
||||||
onOpenDetailPanel?: (docId: Identifier) => void;
|
onOpenDetailPanel?: (docId: Identifier) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DocumentOpenProvider: React.FC<DocumentOpenProviderProps> = ({
|
export const DocumentOpenProvider: React.FC<DocumentOpenProviderProps> = ({
|
||||||
children,
|
children,
|
||||||
onOpenViewer,
|
onOpenViewer,
|
||||||
onOpenPreview,
|
onOpenFullscreenPreview,
|
||||||
onOpenDetailPanel,
|
onOpenDetailPanel,
|
||||||
}) => {
|
}) => {
|
||||||
const openDocument = useCallback((doc: Document, intent: DocumentOpenIntent = 'preview') => {
|
const openDocument = useCallback((doc: Document, intent: DocumentOpenIntent = 'preview') => {
|
||||||
@@ -29,8 +29,8 @@ export const DocumentOpenProvider: React.FC<DocumentOpenProviderProps> = ({
|
|||||||
|
|
||||||
switch (intent) {
|
switch (intent) {
|
||||||
case 'preview':
|
case 'preview':
|
||||||
if (onOpenPreview) {
|
if (onOpenFullscreenPreview) {
|
||||||
onOpenPreview(doc);
|
onOpenFullscreenPreview(doc);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'inspect':
|
case 'inspect':
|
||||||
@@ -51,7 +51,7 @@ export const DocumentOpenProvider: React.FC<DocumentOpenProviderProps> = ({
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}, [onOpenPreview, onOpenDetailPanel, onOpenViewer]);
|
}, [onOpenFullscreenPreview, onOpenDetailPanel, onOpenViewer]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DocumentOpenContext.Provider value={{ openDocument }}>
|
<DocumentOpenContext.Provider value={{ openDocument }}>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
@import './base/controls.css';
|
@import './base/controls.css';
|
||||||
@import './layout/panel-header-controls.css';
|
@import './layout/panel-header-controls.css';
|
||||||
@import './components/resize-handle.css';
|
@import './components/resize-handle.css';
|
||||||
@import '../viewer/preview-zoom.css';
|
@import '../viewer/fullscreen-preview.css';
|
||||||
@import './base/iconography.css';
|
@import './base/iconography.css';
|
||||||
@import '../documents/styles/controls.css';
|
@import '../documents/styles/controls.css';
|
||||||
@import './layout/structure.css';
|
@import './layout/structure.css';
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
sortCorrespondents,
|
sortCorrespondents,
|
||||||
} from './components/DocumentSummarySection';
|
} from './components/DocumentSummarySection';
|
||||||
import DocumentDownloadLink from '../documents/components/DocumentDownloadLink';
|
import DocumentDownloadLink from '../documents/components/DocumentDownloadLink';
|
||||||
import { usePreviewContext } from './PreviewContext';
|
import { useFullscreenPreviewContext } from './FullscreenPreviewContext';
|
||||||
import type { DocumentSummarySectionProps } from './components/DocumentSummarySection';
|
import type { DocumentSummarySectionProps } from './components/DocumentSummarySection';
|
||||||
import { extractDocumentMetadataPayload } from './logic/documentSummary';
|
import { extractDocumentMetadataPayload } from './logic/documentSummary';
|
||||||
import { resolveDocumentAssetUrl } from '../lib/assets/AssetManager';
|
import { resolveDocumentAssetUrl } from '../lib/assets/AssetManager';
|
||||||
@@ -218,14 +218,14 @@ const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({
|
|||||||
[hasOcr, loadOcrContent],
|
[hasOcr, loadOcrContent],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { openPreview } = usePreviewContext();
|
const { openFullscreenPreview } = useFullscreenPreviewContext();
|
||||||
|
|
||||||
const handleZoomOpen = useCallback(() => {
|
const handleZoomOpen = useCallback(() => {
|
||||||
if (!document) {
|
if (!document) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
openPreview(document);
|
openFullscreenPreview(document);
|
||||||
}, [document, openPreview]);
|
}, [document, openFullscreenPreview]);
|
||||||
|
|
||||||
const panelRef = useRef<HTMLDivElement | HTMLFormElement | HTMLElement | null>(null);
|
const panelRef = useRef<HTMLDivElement | HTMLFormElement | HTMLElement | null>(null);
|
||||||
const isStackedLayout = useViewerLayoutMode(panelRef, document?.id);
|
const isStackedLayout = useViewerLayoutMode(panelRef, document?.id);
|
||||||
|
|||||||
+19
-19
@@ -1,33 +1,33 @@
|
|||||||
import React, { useState, useCallback, useMemo, useRef } from 'react';
|
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 { Document } from '../types/documents';
|
||||||
import type { Identifier } from '../types/identifiers';
|
import type { Identifier } from '../types/identifiers';
|
||||||
import { createSafeContext } from '../utils/createSafeContext';
|
import { createSafeContext } from '../utils/createSafeContext';
|
||||||
|
|
||||||
interface PreviewContextType {
|
interface FullscreenPreviewContextType {
|
||||||
openPreview: (doc: Document) => void;
|
openFullscreenPreview: (doc: Document) => void;
|
||||||
closePreview: () => void;
|
closeFullscreenPreview: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [PreviewContext, usePreviewContext] = createSafeContext<PreviewContextType>('Preview');
|
const [FullscreenPreviewContext, useFullscreenPreviewContext] = createSafeContext<FullscreenPreviewContextType>('FullscreenPreview');
|
||||||
|
|
||||||
interface PreviewProviderProps {
|
interface FullscreenPreviewProviderProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
onNavigate?: (documentId: Identifier) => void;
|
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 [previewDoc, setPreviewDoc] = useState<Document | null>(null);
|
||||||
const lastFocusedElement = useRef<HTMLElement | null>(null);
|
const lastFocusedElement = useRef<HTMLElement | null>(null);
|
||||||
|
|
||||||
const openPreview = useCallback((doc: Document) => {
|
const openFullscreenPreview = useCallback((doc: Document) => {
|
||||||
if (!lastFocusedElement.current) {
|
if (!lastFocusedElement.current) {
|
||||||
lastFocusedElement.current = document.activeElement as HTMLElement;
|
lastFocusedElement.current = document.activeElement as HTMLElement;
|
||||||
}
|
}
|
||||||
setPreviewDoc(doc);
|
setPreviewDoc(doc);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const closePreview = useCallback(() => {
|
const closeFullscreenPreview = useCallback(() => {
|
||||||
setPreviewDoc(null);
|
setPreviewDoc(null);
|
||||||
if (lastFocusedElement.current) {
|
if (lastFocusedElement.current) {
|
||||||
lastFocusedElement.current.focus();
|
lastFocusedElement.current.focus();
|
||||||
@@ -38,26 +38,26 @@ export const PreviewProvider: React.FC<PreviewProviderProps> = ({ children, onNa
|
|||||||
const handleMaximize = useCallback(() => {
|
const handleMaximize = useCallback(() => {
|
||||||
if (previewDoc && onNavigate) {
|
if (previewDoc && onNavigate) {
|
||||||
onNavigate(previewDoc.id);
|
onNavigate(previewDoc.id);
|
||||||
closePreview();
|
closeFullscreenPreview();
|
||||||
}
|
}
|
||||||
}, [previewDoc, onNavigate, closePreview]);
|
}, [previewDoc, onNavigate, closeFullscreenPreview]);
|
||||||
|
|
||||||
const value = useMemo(() => ({
|
const value = useMemo(() => ({
|
||||||
openPreview,
|
openFullscreenPreview,
|
||||||
closePreview,
|
closeFullscreenPreview,
|
||||||
}), [openPreview, closePreview]);
|
}), [openFullscreenPreview, closeFullscreenPreview]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PreviewContext.Provider value={value}>
|
<FullscreenPreviewContext.Provider value={value}>
|
||||||
{children}
|
{children}
|
||||||
<PreviewZoomOverlay
|
<FullscreenPreviewOverlay
|
||||||
open={Boolean(previewDoc)}
|
open={Boolean(previewDoc)}
|
||||||
onClose={closePreview}
|
onClose={closeFullscreenPreview}
|
||||||
onMaximize={onNavigate ? handleMaximize : undefined}
|
onMaximize={onNavigate ? handleMaximize : undefined}
|
||||||
document={previewDoc}
|
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';
|
import type { Document } from '../../types/documents';
|
||||||
|
|
||||||
interface PreviewZoomOverlayProps {
|
interface FullscreenPreviewOverlayProps {
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onMaximize?: () => void;
|
onMaximize?: () => void;
|
||||||
document?: Document | null;
|
document?: Document | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
|
const FullscreenPreviewOverlay: React.FC<FullscreenPreviewOverlayProps> = ({
|
||||||
open = false,
|
open = false,
|
||||||
onClose,
|
onClose,
|
||||||
onMaximize,
|
onMaximize,
|
||||||
@@ -81,12 +81,12 @@ const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const stageClassName = 'preview-zoom__stage';
|
const stageClassName = 'fullscreen-preview__stage';
|
||||||
const containerClassName = 'preview-zoom__scroll';
|
const containerClassName = 'fullscreen-preview__scroll';
|
||||||
|
|
||||||
const backdropClassName = [
|
const backdropClassName = [
|
||||||
'preview-zoom-backdrop',
|
'fullscreen-preview-backdrop',
|
||||||
isBackdropVisible ? 'preview-zoom-backdrop--visible' : '',
|
isBackdropVisible ? 'fullscreen-preview-backdrop--visible' : '',
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(' ');
|
.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;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
background: var(--overlay-backdrop);
|
background: var(--overlay-backdrop);
|
||||||
@@ -14,13 +14,13 @@
|
|||||||
-webkit-backdrop-filter: blur(8px);
|
-webkit-backdrop-filter: blur(8px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-zoom-backdrop--visible {
|
.fullscreen-preview-backdrop--visible {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
background: var(--overlay-backdrop);
|
background: var(--overlay-backdrop);
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-zoom__stage {
|
.fullscreen-preview__stage {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
--header-clearance: calc(2.5rem + 0.5rem);
|
--header-clearance: calc(2.5rem + 0.5rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-zoom__scroll .document-viewer__object--image {
|
.fullscreen-preview__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);
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
touch-action: manipulation;
|
touch-action: manipulation;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-zoom__scroll {
|
.fullscreen-preview__scroll {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -55,21 +55,21 @@
|
|||||||
--pdf-viewer-stack-padding: var(--preview-padding);
|
--pdf-viewer-stack-padding: var(--preview-padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-zoom__scroll:focus {
|
.fullscreen-preview__scroll:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-zoom__scroll .document-viewer__object--pdf {
|
.fullscreen-preview__scroll .document-viewer__object--pdf {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-zoom__stage .pdf-viewer__page-wrapper {
|
.fullscreen-preview__stage .pdf-viewer__page-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-zoom__stage .pdf-viewer__page-wrapper::after {
|
.fullscreen-preview__stage .pdf-viewer__page-wrapper::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Header positioning for preview zoom overlay */
|
/* Header positioning for preview zoom overlay */
|
||||||
.preview-zoom-backdrop .panel-header--dark {
|
.fullscreen-preview-backdrop .panel-header--dark {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -94,6 +94,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Prevent header clicks from closing backdrop */
|
/* Prevent header clicks from closing backdrop */
|
||||||
.preview-zoom-backdrop .panel-header--dark * {
|
.fullscreen-preview-backdrop .panel-header--dark * {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ interface UseDetailWorkspaceArgs {
|
|||||||
folderNodes: Map<Identifier | 'root', FolderNode>;
|
folderNodes: Map<Identifier | 'root', FolderNode>;
|
||||||
detailPanelControlRef: MutableRefObject<{ open?: (documentId: Identifier) => void; close?: () => void } | null>;
|
detailPanelControlRef: MutableRefObject<{ open?: (documentId: Identifier) => void; close?: () => void } | null>;
|
||||||
detailFolderFetchRef: MutableRefObject<Set<Identifier | 'root'>>;
|
detailFolderFetchRef: MutableRefObject<Set<Identifier | 'root'>>;
|
||||||
openDocumentPreview?: (args: { documentIds: Identifier[] }) => void;
|
openDocumentViewer?: (args: { documentIds: Identifier[] }) => void;
|
||||||
handleDocumentTitleUpdate?: (docId: Identifier, title: string) => Promise<boolean> | boolean;
|
handleDocumentTitleUpdate?: (docId: Identifier, title: string) => Promise<boolean> | boolean;
|
||||||
handleDocumentIssuedUpdate?: (docId: Identifier, issued: number | null) => Promise<boolean> | boolean;
|
handleDocumentIssuedUpdate?: (docId: Identifier, issued: number | null) => Promise<boolean> | boolean;
|
||||||
handleDocumentTagAdd?: (doc: Document, value: string, context?: { option?: unknown }) => void;
|
handleDocumentTagAdd?: (doc: Document, value: string, context?: { option?: unknown }) => void;
|
||||||
@@ -54,7 +54,7 @@ const useDetailWorkspace = ({
|
|||||||
folderNodes,
|
folderNodes,
|
||||||
detailPanelControlRef,
|
detailPanelControlRef,
|
||||||
detailFolderFetchRef,
|
detailFolderFetchRef,
|
||||||
openDocumentPreview,
|
openDocumentViewer,
|
||||||
handleDocumentTitleUpdate,
|
handleDocumentTitleUpdate,
|
||||||
handleDocumentIssuedUpdate,
|
handleDocumentIssuedUpdate,
|
||||||
handleDocumentTagAdd,
|
handleDocumentTagAdd,
|
||||||
@@ -176,7 +176,7 @@ const useDetailWorkspace = ({
|
|||||||
correspondentLookupById,
|
correspondentLookupById,
|
||||||
onTagAdd: handleDetailTagAdd,
|
onTagAdd: handleDetailTagAdd,
|
||||||
onTagRemove: handleDetailTagRemove,
|
onTagRemove: handleDetailTagRemove,
|
||||||
onOpenPreview: openDocumentPreview,
|
onOpenViewer: openDocumentViewer,
|
||||||
onUpdateTitle: handleDocumentTitleUpdate,
|
onUpdateTitle: handleDocumentTitleUpdate,
|
||||||
onUpdateIssued: handleDocumentIssuedUpdate,
|
onUpdateIssued: handleDocumentIssuedUpdate,
|
||||||
ensureAssetUrl,
|
ensureAssetUrl,
|
||||||
|
|||||||
Reference in New Issue
Block a user