diff --git a/frontend/src/app/DocumentsRoute.tsx b/frontend/src/app/DocumentsRoute.tsx index efd6028..f1b0ade 100644 --- a/frontend/src/app/DocumentsRoute.tsx +++ b/frontend/src/app/DocumentsRoute.tsx @@ -5,6 +5,7 @@ import { DocumentsFilterProvider, } from '../documents/context/DocumentsFilterContext'; import { PreviewProvider } from '../preview/PreviewContext'; +import { DocumentOpenProvider } from '../context/DocumentOpenContext'; import { useWorkspaceSurface } from './useWorkspaceSurface'; import { DocumentsHeaderBreadcrumb } from '../documents/panel/DocumentsPanelHeader'; import { SidebarProvider, useSidebarContext } from '../sidebar/SidebarContext'; @@ -83,7 +84,9 @@ const DocumentsRouteContent: React.FC = () => { - {content} + + {content} + ); diff --git a/frontend/src/context/DocumentOpenContext.tsx b/frontend/src/context/DocumentOpenContext.tsx new file mode 100644 index 0000000..692ac2d --- /dev/null +++ b/frontend/src/context/DocumentOpenContext.tsx @@ -0,0 +1,57 @@ +import React, { createContext, useContext, useCallback } from 'react'; +import { usePreviewContext } from '../preview/PreviewContext'; +import { useAppShell } from '../appShellContext'; +import type { Document } from '../types/documents'; +import type { Identifier } from '../types/identifiers'; + +export type DocumentOpenTarget = 'preview' | 'sidepanel' | 'viewer'; + +export interface DocumentOpenContextValue { + openDocument: (doc: Document, target?: DocumentOpenTarget) => void; +} + +const DocumentOpenContext = createContext(null); + +export const useDocumentOpen = () => { + const context = useContext(DocumentOpenContext); + if (!context) { + throw new Error('useDocumentOpen must be used within a DocumentOpenProvider'); + } + return context; +}; + +export const DocumentOpenProvider: React.FC<{ children: React.ReactNode; onOpenViewer?: (docId: Identifier) => void }> = ({ children, onOpenViewer }) => { + const { openPreview } = usePreviewContext(); + const appShell = useAppShell(); + + // We cast appShell.openDetailPanel because AppShellContext is loosely typed + const openDetailPanel = appShell.openDetailPanel as ((args: { documentIds: Identifier[] }) => void) | undefined; + + const openDocument = useCallback((doc: Document, target: DocumentOpenTarget = 'preview') => { + if (!doc) return; + + switch (target) { + case 'preview': + openPreview(doc); + break; + case 'sidepanel': + if (openDetailPanel) { + openDetailPanel({ documentIds: [doc.id] }); + } else { + console.warn('openDetailPanel is not available in AppShellContext'); + } + break; + case 'viewer': + if (onOpenViewer) { + onOpenViewer(doc.id); + } + break; + } + }, [openPreview, openDetailPanel, onOpenViewer]); + + return ( + + {children} + + ); +}; diff --git a/frontend/src/desktop/DesktopWorkspace.tsx b/frontend/src/desktop/DesktopWorkspace.tsx index 85b7968..6c6f474 100644 --- a/frontend/src/desktop/DesktopWorkspace.tsx +++ b/frontend/src/desktop/DesktopWorkspace.tsx @@ -18,8 +18,8 @@ import { createDocumentEntryKey } from '../app/entryKey'; import { PointerTrackingProvider, usePointerTracking } from './PointerTrackingContext'; import type { Identifier } from '../types/identifiers'; import type { DocumentsListEntry, Document } from '../types/documents'; -import { usePreviewContext } from '../preview/PreviewContext'; import { useAppState } from '../app/appState'; +import { useDocumentOpen } from '../context/DocumentOpenContext'; type TagLike = { id?: Identifier | null; label?: string; color?: string | null } | null; type OverlaySource = { url: string; alt?: string | null; mimeType?: string | null; }; @@ -48,7 +48,6 @@ export interface DesktopWorkspaceProps { entries: DocumentsListEntry[]; ensureAssetUrl?: (...args: any[]) => Promise; getDocumentAsset?: (...args: any[]) => unknown; - onDocumentActivate?: (doc: DeskDocument, event?: unknown) => void; onSelectionChange?: (selectedIds: Identifier[]) => void; onDocumentTagAttach?: (docId: Identifier, tagId: Identifier) => void; onDocumentTagDetach?: (docId: Identifier, tagId: Identifier) => void; @@ -83,14 +82,13 @@ const DesktopWorkspaceContent: React.FC = ({ entries, ensureAssetUrl, getDocumentAsset, - onDocumentActivate, onSelectionChange, onDocumentTagAttach, onDocumentTagDetach, viewId, defaultCardSize = 200, }) => { - const { openPreview } = usePreviewContext(); + const { openDocument } = useDocumentOpen(); const { tenant } = useAppState(); const tenantId = tenant?.id as Identifier; const { addPointer, removePointer } = usePointerTracking(); @@ -240,7 +238,7 @@ const DesktopWorkspaceContent: React.FC = ({ const doc = items.find(i => String(i.id) === lastId); if (doc) { e.preventDefault(); - openPreview(doc); + openDocument(doc, 'preview'); return; } } @@ -352,7 +350,7 @@ const DesktopWorkspaceContent: React.FC = ({ window.addEventListener('keydown', handleWindowKeyDown); return () => window.removeEventListener('keydown', handleWindowKeyDown); - }, [selectedDocumentIds, items, openPreview, layoutStore, handleSelectionChange]); + }, [selectedDocumentIds, items, openDocument, layoutStore, handleSelectionChange]); return ( <> @@ -424,7 +422,10 @@ const DesktopWorkspaceContent: React.FC = ({ ensureAssetUrl={ensureAssetUrl} getDocumentAsset={getDocumentAsset} handleNavigatorSnapshot={() => { }} - onDocumentActivate={(_id, event) => { onDocumentActivate?.(doc, event) }} + onDocumentActivate={(_id, event) => { + const isPreview = event && ((event as any).altKey || (event as any).button === 1); + openDocument(doc, isPreview ? 'preview' : 'sidepanel'); + }} layoutCard={layoutCard} onTagDragEnter={tagInteractions.handleTagDragEnterDoc} onTagDragOver={tagInteractions.handleTagDragOverDoc} diff --git a/frontend/src/documents/DocumentsView.tsx b/frontend/src/documents/DocumentsView.tsx index a95f855..ad219c4 100644 --- a/frontend/src/documents/DocumentsView.tsx +++ b/frontend/src/documents/DocumentsView.tsx @@ -3,7 +3,6 @@ import { useDocumentViewLogic, DocumentViewLogic } from './hooks/useDocumentView import { useDocumentsNavigation } from './hooks/useDocumentsNavigation'; import { useWorkspaceSelectionContext } from '../app/WorkspaceSelectionContext'; import { usePanelManager } from '../app/PanelManagerContext'; -import { usePreviewContext } from '../preview/PreviewContext'; import DocumentsListRow from './components/DocumentsListRow'; import DocumentsGridCard from './components/DocumentsGridCard'; import DocumentsListContainer from './components/DocumentsListContainer'; @@ -24,7 +23,6 @@ const AbstractDocumentsView = void; chil ...props }: AbstractDocumentsViewProps) => { const { entries, onDocumentRename, onFolderRename, onFolderSelect, scrollRef, viewId } = props; - const { openPreview } = usePreviewContext(); const viewLogic = useDocumentViewLogic({ onDocumentRename, onFolderRename, @@ -32,7 +30,6 @@ const AbstractDocumentsView = void; chil const { handleKeyDown, handleFocus } = useDocumentsNavigation({ entries, onFolderSelect, - onPreview: openPreview, viewMode: props.viewMode, scrollRef: props.scrollRef, }); @@ -105,7 +102,6 @@ const AbstractDocumentsView = void; chil entry={entry} viewLogic={viewLogic} {...props} - onPreview={openPreview} /> ))} diff --git a/frontend/src/documents/hooks/useDocumentItemLogic.ts b/frontend/src/documents/hooks/useDocumentItemLogic.ts index 90deca2..5a4ae9a 100644 --- a/frontend/src/documents/hooks/useDocumentItemLogic.ts +++ b/frontend/src/documents/hooks/useDocumentItemLogic.ts @@ -1,6 +1,7 @@ import React, { type DragEvent } from 'react'; import { parseTagTransferPayload } from '../tagTransfer'; import { createDocumentEntryKey } from '../../app/entryKey'; +import { useDocumentOpen } from '../../context/DocumentOpenContext'; import type { Document } from '../../types/documents'; import type { DocumentsViewProps } from '../panel/DocumentsPanel'; import type { DocumentViewLogic } from './useDocumentViewLogic'; @@ -17,7 +18,6 @@ export const useDocumentItemLogic = (props: UseDocumentItemLogicProps) => { doc, viewLogic, draggingDocumentIdsSet, - onDocumentActivate, onDocumentDragStart, onDocumentDragEnd, onDocumentTagDragStart, @@ -54,6 +54,8 @@ export const useDocumentItemLogic = (props: UseDocumentItemLogicProps) => { isEditingDoc && trimmedDocumentDraft.length > 0 && trimmedDocumentDraft !== doc.title; const allowInlineDocumentEdit = onDocumentRename && isSelected && totalSelectionCount === 1; + const { openDocument } = useDocumentOpen(); + const handlers = { onClick: (event: React.MouseEvent) => { if (props.onEntryPointer) { @@ -63,7 +65,10 @@ export const useDocumentItemLogic = (props: UseDocumentItemLogicProps) => { handleEntrySelection(key, event); } }, - onDoubleClick: (event: React.MouseEvent) => onDocumentActivate?.(doc, event), + onDoubleClick: (event: React.MouseEvent) => { + const isPreview = event && (event.altKey || event.button === 1); + openDocument(doc, isPreview ? 'preview' : 'sidepanel'); + }, onDragStart: (event: DragEvent) => onDocumentDragStart?.(event, doc), onDragEnd: (event: DragEvent) => onDocumentDragEnd?.(event), onDragOver: (event: DragEvent) => onDocumentTagDragOver?.(event, doc.id), diff --git a/frontend/src/documents/hooks/useDocumentsNavigation.ts b/frontend/src/documents/hooks/useDocumentsNavigation.ts index 684f30c..99812d9 100644 --- a/frontend/src/documents/hooks/useDocumentsNavigation.ts +++ b/frontend/src/documents/hooks/useDocumentsNavigation.ts @@ -1,11 +1,11 @@ import React, { useCallback, useMemo } from 'react'; import type { DocumentsListEntry } from '../../types/documents'; import { useWorkspaceSelectionContext } from '../../app/WorkspaceSelectionContext'; +import { useDocumentOpen } from '../../context/DocumentOpenContext'; interface UseDocumentsNavigationProps { entries: DocumentsListEntry[]; onFolderSelect?: (folderId: string) => void; - onPreview?: (doc: any) => void; viewMode?: string; scrollRef?: React.RefObject; } @@ -13,7 +13,6 @@ interface UseDocumentsNavigationProps { export const useDocumentsNavigation = ({ entries, onFolderSelect, - onPreview, viewMode, scrollRef, }: UseDocumentsNavigationProps) => { @@ -25,6 +24,8 @@ export const useDocumentsNavigation = ({ applySelection, } = useWorkspaceSelectionContext(); + const { openDocument } = useDocumentOpen(); + const navigableRows = useMemo( () => entries.map((entry) => ({ key: entry.key, type: entry.type, id: entry.id })), [entries], @@ -109,7 +110,8 @@ export const useDocumentsNavigation = ({ } else { const entry = getEntryByKey(activeRow.key); if (entry && entry.type === 'document') { - onPreview?.(entry.document); + const isPreview = key === ' ' || key === 'Space' || key === 'Spacebar'; + openDocument(entry.document, isPreview ? 'preview' : 'sidepanel'); } } } @@ -175,7 +177,7 @@ export const useDocumentsNavigation = ({ navigableRows, onFolderSelect, selectedEntries, - onPreview, + openDocument, handleEntrySelection, setFocusedEntryKey, viewMode, diff --git a/frontend/src/documents/hooks/useDocumentsPanelProps.ts b/frontend/src/documents/hooks/useDocumentsPanelProps.ts index 4bee8c6..a05ee60 100644 --- a/frontend/src/documents/hooks/useDocumentsPanelProps.ts +++ b/frontend/src/documents/hooks/useDocumentsPanelProps.ts @@ -52,7 +52,6 @@ export interface UseDocumentsPanelPropsArgs { clearDocumentSelection?: () => void; handleDeleteSelection?: () => void; handleEntryPointerCore?: (...args: unknown[]) => void; - onDocumentActivate?: (docId: Identifier | null, metadata?: unknown) => void; tags?: unknown[]; correspondents?: unknown[]; documentLookup?: unknown; @@ -105,7 +104,6 @@ const useDocumentsPanelProps = (props: UseDocumentsPanelPropsArgs) => { handleDocumentsViewModeChange, handleDeleteSelection, handleEntryPointerCore, - onDocumentActivate, tags, correspondents, documentLookup, @@ -159,7 +157,6 @@ const useDocumentsPanelProps = (props: UseDocumentsPanelPropsArgs) => { onViewModeChange: handleDocumentsViewModeChange, onDeleteSelection: handleDeleteSelection, onEntryPointer: handleEntryPointerCore, - onDocumentActivate, tags, correspondents, documentLookup, @@ -206,7 +203,6 @@ const useDocumentsPanelProps = (props: UseDocumentsPanelPropsArgs) => { handleFolderDragEnd, handleFolderDragStart, handleFolderRename, - onDocumentActivate, moveDocumentsToFolder, refreshCurrentFolder, searchLoading, diff --git a/frontend/src/documents/panel/DocumentsPanel.tsx b/frontend/src/documents/panel/DocumentsPanel.tsx index d6325dd..2fd501a 100644 --- a/frontend/src/documents/panel/DocumentsPanel.tsx +++ b/frontend/src/documents/panel/DocumentsPanel.tsx @@ -15,7 +15,6 @@ import type { } from '../../types/documents'; import DesktopWorkspace from '../../desktop/DesktopWorkspace'; import { isTagTransferEvent } from '../tagTransfer'; -import { usePreviewContext } from '../../preview/PreviewContext'; import { isPointerModifierEvent, isPrimaryPointerEvent } from '../useEntryPointer'; import { WorkspaceSelectionProvider, @@ -65,7 +64,6 @@ export interface DocumentsViewProps { onFolderDragStart?: (event: DragEvent, folderId: Identifier | 'root') => void; onFolderDragEnd?: (event: DragEvent) => void; onFolderRename?: (folderId: Identifier | 'root', nextName: string) => Promise | boolean; - onDocumentActivate?: (doc: Document, event?: unknown) => void; onDocumentDragStart?: (event: DragEvent, document: Document) => void; onDocumentDragEnd?: (event: DragEvent) => void; onDocumentTagDragStart?: (event: DragEvent, docId: Identifier, tagId: Identifier) => void; @@ -106,7 +104,6 @@ const DocumentsPanelInner: React.FC = ({ onDocumentDragEnd, onDocumentRename, onEntryPointer = null, - onDocumentActivate = null, tagLookupById, activeCorrespondentIds = [], ensureAssetUrl = null, @@ -317,8 +314,6 @@ const DocumentsPanelInner: React.FC = ({ const isGridView = viewMode === 'grid'; const isDeskView = viewMode === 'desk'; - const { openPreview } = usePreviewContext(); - const isTagDragEvent = useCallback((event) => isTagTransferEvent(event), []); const draggingTagRef = useRef<{ docId: Identifier; tagId: Identifier } | null>(null); @@ -365,26 +360,6 @@ const DocumentsPanelInner: React.FC = ({ [isTagDragEvent], ); - const handleDocumentActivate = useCallback( - (doc, event?: React.MouseEvent | KeyboardEvent | null) => { - if (!doc) { - return; - } - - // Handle Preview (Alt+Click or Middle Click) - if (event && (event.altKey || ((event as React.MouseEvent).button === 1))) { - openPreview(doc); - return; - } - - // Handle Activation (Double Click, Enter, or explicit call) - if (onDocumentActivate) { - onDocumentActivate(doc); - } - }, - [onDocumentActivate, openPreview], - ); - const handleFolderClick = useCallback( (folder, event) => { if (!folder) { @@ -445,7 +420,6 @@ const DocumentsPanelInner: React.FC = ({ onFolderDragStart, onFolderDragEnd, onFolderRename, - onDocumentActivate: handleDocumentActivate, onDocumentDragStart: handleDocumentDragStartLocal, onDocumentDragEnd: handleDocumentDragEndLocal, onDocumentTagDragStart: handleDocumentTagDragStart, diff --git a/frontend/src/hooks/documents/useDocumentsWorkspace.ts b/frontend/src/hooks/documents/useDocumentsWorkspace.ts index 84b12cc..8c1ce1e 100644 --- a/frontend/src/hooks/documents/useDocumentsWorkspace.ts +++ b/frontend/src/hooks/documents/useDocumentsWorkspace.ts @@ -1069,7 +1069,6 @@ const useDocumentsWorkspace = ({ detailPanelProps, detailPanelOpen, openDetailPanel, - inspectDocument, previewActive, previewWorkspaceDocument, resolveFolderPath, @@ -1178,7 +1177,6 @@ const useDocumentsWorkspace = ({ clearDocumentSelection, handleDeleteSelection, handleEntryPointerCore, - onDocumentActivate: inspectDocument, tags, correspondents, documentLookup,