refactor: Consolidate document view props into a shared interface and refine tag drop payload handling.
This commit is contained in:
@@ -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<Identifier | null>;
|
||||
tenantId?: Identifier | null;
|
||||
viewId?: string | null;
|
||||
documentLinks?: Map<Identifier, unknown> | null;
|
||||
ensureDownloadUrl?: (docId: Identifier, options?: { force?: boolean }) => Promise<unknown>;
|
||||
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<DesktopWorkspaceProps> = ({
|
||||
promoteSelectionOrder,
|
||||
} = useWorkspaceSelectionContext();
|
||||
const items = useMemo<DeskDocument[]>(
|
||||
() => (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<HTMLDivElement>;
|
||||
handleShellKeyDown: React.KeyboardEventHandler<HTMLDivElement>;
|
||||
focusShell: () => void;
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
focusShell();
|
||||
|
||||
@@ -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<HTMLDivElement>) => void;
|
||||
type DocumentEventHandler = (document: DocumentLike, event: MouseEvent<HTMLDivElement>) => void;
|
||||
|
||||
interface DocumentsGridProps {
|
||||
entries: DocumentsGridEntry[];
|
||||
draggingDocumentIdsSet?: Set<Identifier> | null;
|
||||
draggedFolderId?: Identifier | 'root' | null;
|
||||
onFolderClick?: FolderEventHandler;
|
||||
onFolderSelect?: (folderId: Identifier | 'root') => void;
|
||||
onFolderDragOver?: (event: DragEvent<HTMLDivElement>, folderId: Identifier | 'root') => void;
|
||||
onFolderDragLeave?: (event: DragEvent<HTMLDivElement>) => void;
|
||||
onFolderDrop?: (event: DragEvent<HTMLDivElement>, folderId: Identifier | 'root') => void;
|
||||
onFolderDragStart?: (event: DragEvent<HTMLDivElement>, folderId: Identifier | 'root') => void;
|
||||
onFolderDragEnd?: (event: DragEvent<HTMLDivElement>) => void;
|
||||
onFolderRename?: (folderId: Identifier | 'root', nextName: string) => Promise<boolean> | boolean;
|
||||
onDocumentClick?: DocumentEventHandler;
|
||||
onDocumentActivate?: DocumentEventHandler;
|
||||
onDocumentDragStart?: (event: DragEvent<HTMLDivElement>, document: DocumentLike) => void;
|
||||
onDocumentDragEnd?: (event: DragEvent<HTMLDivElement>) => void;
|
||||
onDocumentTagDragOver?: (event: DragEvent<HTMLDivElement>) => void;
|
||||
onDocumentTagDragLeave?: (event: DragEvent<HTMLDivElement>) => void;
|
||||
onDocumentTagDrop?: (event: DragEvent<HTMLDivElement>, documentId: Identifier) => void;
|
||||
ensureAssetUrl?: (...args: any[]) => unknown;
|
||||
getDocumentAsset?: (...args: any[]) => unknown;
|
||||
interface DocumentsGridProps extends DocumentsViewProps {
|
||||
gridIconSize?: number;
|
||||
tagLookupById?: Map<Identifier, DocumentTag> | null;
|
||||
onTagClick?: (tagId: Identifier) => void;
|
||||
scrollRef?: RefObject<HTMLElement | null>;
|
||||
onCorrespondentClick?: (correspondentId: Identifier) => void;
|
||||
activeCorrespondentIdSet?: Set<Identifier> | null;
|
||||
onDocumentRename?: (docId: Identifier, title: string) => Promise<boolean> | boolean;
|
||||
}
|
||||
|
||||
const DocumentsGrid: React.FC<DocumentsGridProps> = ({
|
||||
@@ -332,8 +267,15 @@ const DocumentsGrid: React.FC<DocumentsGridProps> = ({
|
||||
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);
|
||||
}
|
||||
}}
|
||||
|
||||
>
|
||||
<DocumentThumbnailImage
|
||||
document={doc}
|
||||
|
||||
@@ -6,11 +6,11 @@ import { formatDate } from '../utils/date';
|
||||
import DocumentThumbnailImage from './DocumentThumbnailImage';
|
||||
import CorrespondentLinks from './CorrespondentLinks';
|
||||
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';
|
||||
import type { DocumentsListProps } from './panel/DocumentsPanel';
|
||||
import type { DocumentsViewProps } from './panel/DocumentsPanel';
|
||||
|
||||
export interface FolderLike {
|
||||
id?: Identifier | 'root';
|
||||
@@ -58,7 +58,7 @@ export type DocumentsListEntry = FolderEntry | DocumentEntry;
|
||||
export type FolderEventHandler = (folder: FolderLike, event: MouseEvent<HTMLTableRowElement>) => void;
|
||||
export type DocumentEventHandler = (document: DocumentLike, event: MouseEvent<HTMLTableRowElement>) => void;
|
||||
|
||||
const DocumentsList: React.FC<DocumentsListProps> = ({
|
||||
const DocumentsList: React.FC<DocumentsViewProps> = ({
|
||||
entries,
|
||||
draggingDocumentIdsSet,
|
||||
draggedFolderId,
|
||||
@@ -315,7 +315,14 @@ const DocumentsList: React.FC<DocumentsListProps> = ({
|
||||
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);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<td className="thumb-cell">
|
||||
<DocumentThumbnailImage
|
||||
|
||||
@@ -10,7 +10,7 @@ import type {
|
||||
DocumentTag,
|
||||
} from '../DocumentsList';
|
||||
import DesktopWorkspace from '../../desktop/DesktopWorkspace';
|
||||
import { isTagTransferEvent, parseTagTransferPayload } from '../tagTransfer';
|
||||
import { isTagTransferEvent } from '../tagTransfer';
|
||||
import PreviewZoomOverlay from '../../detail/PreviewZoomOverlay';
|
||||
import { isPointerModifierEvent, isPrimaryPointerEvent } from '../useEntryPointer';
|
||||
import {
|
||||
@@ -48,7 +48,7 @@ const defaultGetDocumentAsset = (_doc?: unknown, _type?: string) => null;
|
||||
|
||||
export type DocumentLinkLike = { url?: string | null; mimeType?: string | null };
|
||||
|
||||
export interface DocumentsListProps {
|
||||
export interface DocumentsViewProps {
|
||||
entries: DocumentsListEntry[];
|
||||
draggingDocumentIdsSet?: Set<Identifier> | 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<HTMLTableRowElement>, folderId: Identifier | 'root') => void;
|
||||
onFolderDragLeave?: (event: DragEvent<HTMLTableRowElement>) => void;
|
||||
onFolderDrop?: (event: DragEvent<HTMLTableRowElement>, folderId: Identifier | 'root') => void;
|
||||
onFolderDragStart?: (event: DragEvent<HTMLTableRowElement>, folderId: Identifier | 'root') => void;
|
||||
onFolderDragEnd?: (event: DragEvent<HTMLTableRowElement>) => void;
|
||||
onFolderDragOver?: (event: DragEvent<HTMLElement>, folderId: Identifier | 'root') => void;
|
||||
onFolderDragLeave?: (event: DragEvent<HTMLElement>) => void;
|
||||
onFolderDrop?: (event: DragEvent<HTMLElement>, folderId: Identifier | 'root') => void;
|
||||
onFolderDragStart?: (event: DragEvent<HTMLElement>, folderId: Identifier | 'root') => void;
|
||||
onFolderDragEnd?: (event: DragEvent<HTMLElement>) => void;
|
||||
onFolderRename?: (folderId: Identifier | 'root', nextName: string) => Promise<boolean> | boolean;
|
||||
onDocumentClick?: DocumentEventHandler;
|
||||
onDocumentActivate?: DocumentEventHandler;
|
||||
onDocumentDragStart?: (event: DragEvent<HTMLTableRowElement>, document: DocumentLike) => void;
|
||||
onDocumentDragEnd?: (event: DragEvent<HTMLTableRowElement>) => void;
|
||||
onDocumentTagDragOver?: (event: DragEvent<HTMLTableRowElement>) => void;
|
||||
onDocumentTagDragLeave?: (event: DragEvent<HTMLTableRowElement>) => void;
|
||||
onDocumentTagDrop?: (event: DragEvent<HTMLTableRowElement>, documentId: Identifier) => void;
|
||||
onDocumentDragStart?: (event: DragEvent<HTMLElement>, document: DocumentLike) => void;
|
||||
onDocumentDragEnd?: (event: DragEvent<HTMLElement>) => void;
|
||||
onDocumentTagDragOver?: (event: DragEvent<HTMLElement>) => void;
|
||||
onDocumentTagDragLeave?: (event: DragEvent<HTMLElement>) => void;
|
||||
onDocumentTagDrop?: (documentId: Identifier, tag: any) => void;
|
||||
onDocumentRename?: (documentId: Identifier, nextTitle: string) => Promise<boolean> | boolean;
|
||||
tagLookupById?: Map<Identifier, DocumentTag> | null;
|
||||
onTagClick?: (tagId: Identifier) => void;
|
||||
onCorrespondentClick?: (correspondentId: Identifier) => void;
|
||||
activeCorrespondentIdSet?: Set<Identifier> | null;
|
||||
scrollRef?: RefObject<HTMLElement | null>;
|
||||
// Desk specific (optional for now or handled via intersection)
|
||||
tenantId?: Identifier | null;
|
||||
viewId?: string | null;
|
||||
documentLinks?: Map<Identifier, any> | null;
|
||||
ensureDownloadUrl?: (docId: Identifier, options?: { force?: boolean }) => Promise<any>;
|
||||
onDocumentStackSelect?: (docIds: Identifier[], event?: any) => void;
|
||||
onPromoteSelection?: (docId: Identifier | null) => void;
|
||||
activeTagFilters?: Array<Identifier | null>;
|
||||
}
|
||||
|
||||
const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
||||
@@ -777,53 +785,7 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
||||
[entries],
|
||||
);
|
||||
|
||||
const handleListDocumentTagDrop = useCallback(
|
||||
(event: React.DragEvent<HTMLElement>, 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<DocumentsPanelInnerProps> = ({
|
||||
onDocumentDragEnd: handleDocumentDragEndLocal,
|
||||
onDocumentTagDragOver: handleDocumentTagDragOver,
|
||||
onDocumentTagDragLeave: handleDocumentTagDragLeave,
|
||||
onDocumentTagDrop: handleListDocumentTagDrop,
|
||||
onDocumentTagDrop,
|
||||
onDocumentRename,
|
||||
onFolderRename,
|
||||
ensureAssetUrl,
|
||||
@@ -850,6 +812,13 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
||||
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<DocumentsPanelInnerProps> = ({
|
||||
|
||||
switch (viewMode) {
|
||||
case 'desk':
|
||||
return <DesktopWorkspace {...deskWorkspaceProps} />;
|
||||
return <DesktopWorkspace {...viewProps} />;
|
||||
case 'grid':
|
||||
return <DocumentsGrid {...listProps} gridIconSize={gridIconSize} />;
|
||||
return <DocumentsGrid {...viewProps} gridIconSize={gridIconSize} />;
|
||||
case 'list':
|
||||
default:
|
||||
return <DocumentsList {...listProps} />;
|
||||
return <DocumentsList {...viewProps} />;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user