From 5191d50d422e28b17f024637657f7a535b52ebc3 Mon Sep 17 00:00:00 2001 From: Nils Schneider Date: Tue, 25 Nov 2025 12:11:42 +0100 Subject: [PATCH] refactor: Consolidate document view props into a shared interface and refine tag drop payload handling. --- frontend/src/desktop/DesktopWorkspace.tsx | 68 ++++++++----- frontend/src/documents/DocumentsGrid.tsx | 90 ++++-------------- frontend/src/documents/DocumentsList.tsx | 15 ++- .../src/documents/panel/DocumentsPanel.tsx | 95 +++++++------------ 4 files changed, 105 insertions(+), 163 deletions(-) diff --git a/frontend/src/desktop/DesktopWorkspace.tsx b/frontend/src/desktop/DesktopWorkspace.tsx index 9d4c60a..6ee5831 100644 --- a/frontend/src/desktop/DesktopWorkspace.tsx +++ b/frontend/src/desktop/DesktopWorkspace.tsx @@ -8,7 +8,7 @@ import React, { useSyncExternalStore, } from 'react'; import { resolveDocumentAssetUrl } from '../asset_manager'; -import type { EnsureAssetUrl, GetAsset } from '../asset_manager'; +import type { GetAsset } from '../asset_manager'; import { formatTransform } from '../utils/math'; import useDocumentDrag from './useDocumentDrag'; import PreviewZoomOverlay from '../detail/PreviewZoomOverlay'; @@ -118,18 +118,11 @@ type WorkspaceSnapshotState = { initialLoadDone: boolean; }; -interface DesktopWorkspaceProps { - entries?: DeskDocument[]; - onDocumentActivate?: (...args: unknown[]) => void; - onDocumentClick?: (...args: unknown[]) => void; - onDocumentTagDrop?: (...args: unknown[]) => void; - ensureAssetUrl?: EnsureAssetUrl; - getDocumentAsset?: GetAsset; - activeTagFilters?: Array; - tenantId?: Identifier | null; - viewId?: string | null; - documentLinks?: Map | null; - ensureDownloadUrl?: (docId: Identifier, options?: { force?: boolean }) => Promise; +import type { DocumentsViewProps } from '../documents/panel/DocumentsPanel'; + +interface DesktopWorkspaceProps extends DocumentsViewProps { + // Override entries if needed, or rely on DocumentsViewProps + // entries: DocumentsListEntry[]; } interface DesktopWorkspaceViewProps { @@ -209,7 +202,22 @@ const DesktopWorkspace: React.FC = ({ promoteSelectionOrder, } = useWorkspaceSelectionContext(); const items = useMemo( - () => (Array.isArray(entries) ? entries.filter((doc): doc is DeskDocument => Boolean(doc)) : []), + () => { + if (!Array.isArray(entries)) return []; + return entries.flatMap((entry: any) => { + if (!entry) return []; + + // Handle DocumentsListEntry + if ('type' in entry && entry.type === 'document' && entry.document) { + return [entry.document as DeskDocument]; + } + if ('type' in entry && entry.type === 'folder') { + return []; + } + + return []; + }); + }, [entries], ); @@ -938,6 +946,26 @@ function DesktopWorkspaceView({ markLayoutDirty, dragTransformsRef, }: DesktopWorkspaceViewProps) { + const handleDeskDocumentActivate = useCallback( + (docId: Identifier) => { + const doc = documentLookup.get(String(docId)); + if (doc && onDocumentActivate) { + onDocumentActivate(doc, undefined as any); + } + }, + [documentLookup, onDocumentActivate], + ); + + const handleDeskDocumentClick = useCallback( + (docId: Identifier, event: any) => { + const doc = documentLookup.get(String(docId)); + if (doc && onDocumentClick) { + onDocumentClick(doc, event); + } + }, + [documentLookup, onDocumentClick], + ); + const { handlePointerDown, handlePointerMove, handlePointerUp, handlePointerCancel } = useDocumentDrag({ engine, @@ -954,7 +982,7 @@ function DesktopWorkspaceView({ recalcVisibleDocIds, settings: dragSettings, containerRef, - onDocumentActivate, + onDocumentActivate: handleDeskDocumentActivate, selectedDocumentIds, markLayoutDirty, }) as { @@ -974,17 +1002,13 @@ function DesktopWorkspaceView({ handlePointerMove, handlePointerUp, handlePointerCancel, - onDocumentClick, + onDocumentClick: handleDeskDocumentClick, onDocumentStackSelect, onPromoteSelection, - onDocumentActivate, + onDocumentActivate: handleDeskDocumentActivate, selectedDocumentIds, openOverlayForDoc, - }) as { - getCardPointerHandlers: (doc: DeskDocument) => React.HTMLAttributes; - handleShellKeyDown: React.KeyboardEventHandler; - focusShell: () => void; - }; + }); useEffect(() => { focusShell(); diff --git a/frontend/src/documents/DocumentsGrid.tsx b/frontend/src/documents/DocumentsGrid.tsx index c942d91..202bc4a 100644 --- a/frontend/src/documents/DocumentsGrid.tsx +++ b/frontend/src/documents/DocumentsGrid.tsx @@ -1,86 +1,21 @@ import React, { useMemo } from 'react'; -import type { DragEvent, MouseEvent, RefObject } from 'react'; import { FolderIcon, CheckIcon, CloseIcon } from '../ui/icons'; import DocumentThumbnailImage from './DocumentThumbnailImage'; import CorrespondentLinks from './CorrespondentLinks'; import { getTagColorStyle } from '../utils/colors'; import { resolveCorrespondents } from './correspondents'; -import { writeTagTransferData } from './tagTransfer'; +import { writeTagTransferData, parseTagTransferPayload } from './tagTransfer'; import useInlineRename from './useInlineRename'; import { useWorkspaceSelectionContext } from '../app/WorkspaceSelectionContext'; -import type { Identifier } from '../types/identifiers'; -export interface FolderLike { - id?: Identifier | 'root'; - name?: string; -} +import type { + FolderLike, + DocumentLike, +} from './DocumentsList'; +import type { DocumentsViewProps } from './panel/DocumentsPanel'; -export interface DocumentTag { - id?: Identifier; - label?: string; - color?: string | null; -} - -export interface DocumentCorrespondent { - id?: Identifier; - name?: string; - count?: number; -} - -export interface DocumentLike { - id?: Identifier; - title?: string; - tags?: DocumentTag[] | null; - correspondents?: DocumentCorrespondent[] | null; -} - -export type FolderEntry = { - type: 'folder'; - id: Identifier | 'root'; - key: string; - folder: FolderLike; -}; - -export type DocumentEntry = { - type: 'document'; - id: Identifier; - key: string; - document: DocumentLike; -}; - -export type DocumentsGridEntry = FolderEntry | DocumentEntry; - -type FolderEventHandler = (folder: FolderLike, event: MouseEvent) => void; -type DocumentEventHandler = (document: DocumentLike, event: MouseEvent) => void; - -interface DocumentsGridProps { - entries: DocumentsGridEntry[]; - draggingDocumentIdsSet?: Set | null; - draggedFolderId?: Identifier | 'root' | null; - onFolderClick?: FolderEventHandler; - onFolderSelect?: (folderId: Identifier | 'root') => void; - onFolderDragOver?: (event: DragEvent, folderId: Identifier | 'root') => void; - onFolderDragLeave?: (event: DragEvent) => void; - onFolderDrop?: (event: DragEvent, folderId: Identifier | 'root') => void; - onFolderDragStart?: (event: DragEvent, folderId: Identifier | 'root') => void; - onFolderDragEnd?: (event: DragEvent) => void; - onFolderRename?: (folderId: Identifier | 'root', nextName: string) => Promise | boolean; - onDocumentClick?: DocumentEventHandler; - onDocumentActivate?: DocumentEventHandler; - onDocumentDragStart?: (event: DragEvent, document: DocumentLike) => void; - onDocumentDragEnd?: (event: DragEvent) => void; - onDocumentTagDragOver?: (event: DragEvent) => void; - onDocumentTagDragLeave?: (event: DragEvent) => void; - onDocumentTagDrop?: (event: DragEvent, documentId: Identifier) => void; - ensureAssetUrl?: (...args: any[]) => unknown; - getDocumentAsset?: (...args: any[]) => unknown; +interface DocumentsGridProps extends DocumentsViewProps { gridIconSize?: number; - tagLookupById?: Map | null; - onTagClick?: (tagId: Identifier) => void; - scrollRef?: RefObject; - onCorrespondentClick?: (correspondentId: Identifier) => void; - activeCorrespondentIdSet?: Set | null; - onDocumentRename?: (docId: Identifier, title: string) => Promise | boolean; } const DocumentsGrid: React.FC = ({ @@ -332,8 +267,15 @@ const DocumentsGrid: React.FC = ({ onDragOverCapture={(event) => onDocumentTagDragOver?.(event)} onDragLeave={onDocumentTagDragLeave} onDragLeaveCapture={onDocumentTagDragLeave} - onDrop={(event) => onDocumentTagDrop?.(event, doc.id)} - onDropCapture={(event) => onDocumentTagDrop?.(event, doc.id)} + onDrop={(event) => { + event.preventDefault(); + event.stopPropagation(); + const payload = parseTagTransferPayload(event); + if (payload && onDocumentTagDrop) { + onDocumentTagDrop(doc.id, payload); + } + }} + > ) => void; export type DocumentEventHandler = (document: DocumentLike, event: MouseEvent) => void; -const DocumentsList: React.FC = ({ +const DocumentsList: React.FC = ({ entries, draggingDocumentIdsSet, draggedFolderId, @@ -315,7 +315,14 @@ const DocumentsList: React.FC = ({ onDragEnd={(event) => onDocumentDragEnd?.(event)} onDragOver={onDocumentTagDragOver} onDragLeave={onDocumentTagDragLeave} - onDrop={(event) => onDocumentTagDrop?.(event, doc.id)} + onDrop={(event) => { + event.preventDefault(); + event.stopPropagation(); + const payload = parseTagTransferPayload(event); + if (payload && onDocumentTagDrop) { + onDocumentTagDrop(doc.id, payload); + } + }} > null; export type DocumentLinkLike = { url?: string | null; mimeType?: string | null }; -export interface DocumentsListProps { +export interface DocumentsViewProps { entries: DocumentsListEntry[]; draggingDocumentIdsSet?: Set | null; draggedFolderId?: Identifier | 'root' | null; @@ -56,25 +56,33 @@ export interface DocumentsListProps { getDocumentAsset?: (...args: any[]) => unknown; onFolderClick?: FolderEventHandler; onFolderSelect?: (folderId: Identifier | 'root') => void; - onFolderDragOver?: (event: DragEvent, folderId: Identifier | 'root') => void; - onFolderDragLeave?: (event: DragEvent) => void; - onFolderDrop?: (event: DragEvent, folderId: Identifier | 'root') => void; - onFolderDragStart?: (event: DragEvent, folderId: Identifier | 'root') => void; - onFolderDragEnd?: (event: DragEvent) => void; + onFolderDragOver?: (event: DragEvent, folderId: Identifier | 'root') => void; + onFolderDragLeave?: (event: DragEvent) => void; + onFolderDrop?: (event: DragEvent, folderId: Identifier | 'root') => void; + onFolderDragStart?: (event: DragEvent, folderId: Identifier | 'root') => void; + onFolderDragEnd?: (event: DragEvent) => void; onFolderRename?: (folderId: Identifier | 'root', nextName: string) => Promise | boolean; onDocumentClick?: DocumentEventHandler; onDocumentActivate?: DocumentEventHandler; - onDocumentDragStart?: (event: DragEvent, document: DocumentLike) => void; - onDocumentDragEnd?: (event: DragEvent) => void; - onDocumentTagDragOver?: (event: DragEvent) => void; - onDocumentTagDragLeave?: (event: DragEvent) => void; - onDocumentTagDrop?: (event: DragEvent, documentId: Identifier) => void; + onDocumentDragStart?: (event: DragEvent, document: DocumentLike) => void; + onDocumentDragEnd?: (event: DragEvent) => void; + onDocumentTagDragOver?: (event: DragEvent) => void; + onDocumentTagDragLeave?: (event: DragEvent) => void; + onDocumentTagDrop?: (documentId: Identifier, tag: any) => void; onDocumentRename?: (documentId: Identifier, nextTitle: string) => Promise | boolean; tagLookupById?: Map | null; onTagClick?: (tagId: Identifier) => void; onCorrespondentClick?: (correspondentId: Identifier) => void; activeCorrespondentIdSet?: Set | null; scrollRef?: RefObject; + // Desk specific (optional for now or handled via intersection) + tenantId?: Identifier | null; + viewId?: string | null; + documentLinks?: Map | null; + ensureDownloadUrl?: (docId: Identifier, options?: { force?: boolean }) => Promise; + onDocumentStackSelect?: (docIds: Identifier[], event?: any) => void; + onPromoteSelection?: (docId: Identifier | null) => void; + activeTagFilters?: Array; } const DocumentsPanelInner: React.FC = ({ @@ -777,53 +785,7 @@ const DocumentsPanelInner: React.FC = ({ [entries], ); - const handleListDocumentTagDrop = useCallback( - (event: React.DragEvent, docId: Identifier) => { - event.preventDefault(); - event.stopPropagation(); - - const payload = parseTagTransferPayload(event); - if (payload && onDocumentTagDrop) { - onDocumentTagDrop(docId, payload); - } - }, - [onDocumentTagDrop], - ); - - const deskWorkspaceProps = useMemo( - () => ({ - entries: rows, - onDocumentActivate, - onDocumentClick: onEntryPointer, - onDocumentStackSelect: handleDeskDocumentStackSelect, - onPromoteSelection: promoteSelectionOrder, - onDocumentTagDrop, - ensureAssetUrl, - getDocumentAsset, - activeTagFilters, - tenantId: currentTenantId, - viewId: deskViewId, - documentLinks: documentLinkMap, - ensureDownloadUrl, - }), - [ - rows, - onDocumentActivate, - onEntryPointer, - handleDeskDocumentStackSelect, - promoteSelectionOrder, - onDocumentTagDrop, - ensureAssetUrl, - getDocumentAsset, - activeTagFilters, - currentTenantId, - deskViewId, - documentLinkMap, - ensureDownloadUrl, - ], - ); - - const listProps: DocumentsListProps = { + const viewProps: DocumentsViewProps = { entries, draggingDocumentIdsSet: draggingSet, draggedFolderId, @@ -840,7 +802,7 @@ const DocumentsPanelInner: React.FC = ({ onDocumentDragEnd: handleDocumentDragEndLocal, onDocumentTagDragOver: handleDocumentTagDragOver, onDocumentTagDragLeave: handleDocumentTagDragLeave, - onDocumentTagDrop: handleListDocumentTagDrop, + onDocumentTagDrop, onDocumentRename, onFolderRename, ensureAssetUrl, @@ -850,6 +812,13 @@ const DocumentsPanelInner: React.FC = ({ onCorrespondentClick: toggleCorrespondentFilter, activeCorrespondentIdSet: activeCorrespondentIdSet, scrollRef, + // Desk specific + onDocumentStackSelect: handleDeskDocumentStackSelect, + onPromoteSelection: promoteSelectionOrder, + tenantId: currentTenantId, + viewId: deskViewId, + documentLinks: documentLinkMap, + ensureDownloadUrl, }; const renderBody = () => { @@ -874,12 +843,12 @@ const DocumentsPanelInner: React.FC = ({ switch (viewMode) { case 'desk': - return ; + return ; case 'grid': - return ; + return ; case 'list': default: - return ; + return ; } };