feat: refactor document opening/preview logic to support preview/sidepanel actions.

This commit is contained in:
2025-12-04 01:02:54 +01:00
parent 447b012e48
commit 8bb9f9950a
6 changed files with 37 additions and 24 deletions
+16 -3
View File
@@ -4,8 +4,8 @@ import { useNavigate } from 'react-router-dom';
import { import {
DocumentsFilterProvider, DocumentsFilterProvider,
} from '../documents/context/DocumentsFilterContext'; } from '../documents/context/DocumentsFilterContext';
import { PreviewProvider } from '../preview/PreviewContext'; import { PreviewProvider, usePreviewContext } from '../preview/PreviewContext';
import { DocumentOpenProvider } from '../context/DocumentOpenContext'; import { DocumentOpenProvider } from '../contexts/DocumentOpenContext';
import { useWorkspaceSurface } from './useWorkspaceSurface'; import { useWorkspaceSurface } from './useWorkspaceSurface';
import { DocumentsHeaderBreadcrumb } from '../documents/panel/DocumentsPanelHeader'; import { DocumentsHeaderBreadcrumb } from '../documents/panel/DocumentsPanelHeader';
import { SidebarProvider, useSidebarContext } from '../sidebar/SidebarContext'; import { SidebarProvider, useSidebarContext } from '../sidebar/SidebarContext';
@@ -24,8 +24,11 @@ const DocumentsRouteContent: React.FC = () => {
const { const {
sidebarSuppressed, sidebarSuppressed,
expandSidebar, expandSidebar,
openDetailPanel,
} = usePanelManager(); } = usePanelManager();
const { openPreview } = usePreviewContext();
const sidebarHidden = sidebarCollapsed || sidebarSuppressed; const sidebarHidden = sidebarCollapsed || sidebarSuppressed;
const handleHeaderBreadcrumbClick = useCallback((crumb: DocumentsHeaderBreadcrumb) => { const handleHeaderBreadcrumbClick = useCallback((crumb: DocumentsHeaderBreadcrumb) => {
@@ -40,6 +43,12 @@ const DocumentsRouteContent: React.FC = () => {
navigate(`/documents/${documentId}`); navigate(`/documents/${documentId}`);
}, [navigate]); }, [navigate]);
const handleOpenSidepanel = useCallback((docId: string) => {
if (openDetailPanel) {
openDetailPanel({ documentIds: [docId] });
}
}, [openDetailPanel]);
const documentsTablePropsWithNav = useMemo(() => ( const documentsTablePropsWithNav = useMemo(() => (
surfaceConfig.documentsTableProps surfaceConfig.documentsTableProps
? { ...surfaceConfig.documentsTableProps, onBreadcrumbNavigate: handleHeaderBreadcrumbClick } ? { ...surfaceConfig.documentsTableProps, onBreadcrumbNavigate: handleHeaderBreadcrumbClick }
@@ -84,7 +93,11 @@ const DocumentsRouteContent: React.FC = () => {
<PreviewProvider <PreviewProvider
onNavigate={handleDocumentNavigate} onNavigate={handleDocumentNavigate}
> >
<DocumentOpenProvider onOpenViewer={handleDocumentNavigate}> <DocumentOpenProvider
onOpenViewer={handleDocumentNavigate}
onOpenPreview={openPreview}
onOpenSidepanel={handleOpenSidepanel}
>
{content} {content}
</DocumentOpenProvider> </DocumentOpenProvider>
</PreviewProvider> </PreviewProvider>
@@ -1,6 +1,4 @@
import React, { createContext, useContext, useCallback } from 'react'; import React, { createContext, useContext, useCallback } from 'react';
import { usePreviewContext } from '../preview/PreviewContext';
import { useAppShell } from '../appShellContext';
import type { Document } from '../types/documents'; import type { Document } from '../types/documents';
import type { Identifier } from '../types/identifiers'; import type { Identifier } from '../types/identifiers';
@@ -20,25 +18,31 @@ export const useDocumentOpen = () => {
return context; return context;
}; };
export const DocumentOpenProvider: React.FC<{ children: React.ReactNode; onOpenViewer?: (docId: Identifier) => void }> = ({ children, onOpenViewer }) => { interface DocumentOpenProviderProps {
const { openPreview } = usePreviewContext(); children: React.ReactNode;
const appShell = useAppShell(); onOpenViewer?: (docId: Identifier) => void;
onOpenPreview?: (doc: Document) => void;
// We cast appShell.openDetailPanel because AppShellContext is loosely typed onOpenSidepanel?: (docId: Identifier) => void;
const openDetailPanel = appShell.openDetailPanel as ((args: { documentIds: Identifier[] }) => void) | undefined; }
export const DocumentOpenProvider: React.FC<DocumentOpenProviderProps> = ({
children,
onOpenViewer,
onOpenPreview,
onOpenSidepanel,
}) => {
const openDocument = useCallback((doc: Document, target: DocumentOpenTarget = 'preview') => { const openDocument = useCallback((doc: Document, target: DocumentOpenTarget = 'preview') => {
if (!doc) return; if (!doc) return;
switch (target) { switch (target) {
case 'preview': case 'preview':
openPreview(doc); if (onOpenPreview) {
onOpenPreview(doc);
}
break; break;
case 'sidepanel': case 'sidepanel':
if (openDetailPanel) { if (onOpenSidepanel) {
openDetailPanel({ documentIds: [doc.id] }); onOpenSidepanel(doc.id);
} else {
console.warn('openDetailPanel is not available in AppShellContext');
} }
break; break;
case 'viewer': case 'viewer':
@@ -47,7 +51,7 @@ export const DocumentOpenProvider: React.FC<{ children: React.ReactNode; onOpenV
} }
break; break;
} }
}, [openPreview, openDetailPanel, onOpenViewer]); }, [onOpenPreview, onOpenSidepanel, onOpenViewer]);
return ( return (
<DocumentOpenContext.Provider value={{ openDocument }}> <DocumentOpenContext.Provider value={{ openDocument }}>
+1 -1
View File
@@ -19,7 +19,7 @@ import { PointerTrackingProvider, usePointerTracking } from './PointerTrackingCo
import type { Identifier } from '../types/identifiers'; import type { Identifier } from '../types/identifiers';
import type { DocumentsListEntry, Document } from '../types/documents'; import type { DocumentsListEntry, Document } from '../types/documents';
import { useAppState } from '../app/appState'; import { useAppState } from '../app/appState';
import { useDocumentOpen } from '../context/DocumentOpenContext'; import { useDocumentOpen } from '../contexts/DocumentOpenContext';
type TagLike = { id?: Identifier | null; label?: string; color?: string | null } | null; type TagLike = { id?: Identifier | null; label?: string; color?: string | null } | null;
type OverlaySource = { url: string; alt?: string | null; mimeType?: string | null; }; type OverlaySource = { url: string; alt?: string | null; mimeType?: string | null; };
@@ -7,13 +7,9 @@ type FetchDocument = (id: DocumentId) => Promise<unknown>;
class DocumentsManager<T extends ManagedDocument = ManagedDocument> { class DocumentsManager<T extends ManagedDocument = ManagedDocument> {
private byId: Map<DocumentId, T>; private byId: Map<DocumentId, T>;
private fetcher?: FetchDocument; private fetcher?: FetchDocument;
private inflight: Map<DocumentId, Promise<T | null>>; private inflight: Map<DocumentId, Promise<T | null>>;
private listeners: Set<() => void>; private listeners: Set<() => void>;
private emitScheduled: boolean; private emitScheduled: boolean;
constructor( constructor(
@@ -1,7 +1,7 @@
import React, { type DragEvent } from 'react'; import React, { type DragEvent } from 'react';
import { parseTagTransferPayload } from '../tagTransfer'; import { parseTagTransferPayload } from '../tagTransfer';
import { createDocumentEntryKey } from '../../app/entryKey'; import { createDocumentEntryKey } from '../../app/entryKey';
import { useDocumentOpen } from '../../context/DocumentOpenContext'; import { useDocumentOpen } from '../../contexts/DocumentOpenContext';
import type { Document } from '../../types/documents'; import type { Document } from '../../types/documents';
import type { DocumentsViewProps } from '../panel/DocumentsPanel'; import type { DocumentsViewProps } from '../panel/DocumentsPanel';
import type { DocumentViewLogic } from './useDocumentViewLogic'; import type { DocumentViewLogic } from './useDocumentViewLogic';
@@ -1,7 +1,7 @@
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import type { DocumentsListEntry } from '../../types/documents'; import type { DocumentsListEntry } from '../../types/documents';
import { useWorkspaceSelectionContext } from '../../app/WorkspaceSelectionContext'; import { useWorkspaceSelectionContext } from '../../app/WorkspaceSelectionContext';
import { useDocumentOpen } from '../../context/DocumentOpenContext'; import { useDocumentOpen } from '../../contexts/DocumentOpenContext';
interface UseDocumentsNavigationProps { interface UseDocumentsNavigationProps {
entries: DocumentsListEntry[]; entries: DocumentsListEntry[];