refactor: begin spatial workspace architecture refactor with design document and updated pointer event handling for document activation.

This commit is contained in:
2025-11-27 01:57:38 +01:00
parent 82243b4170
commit 68ed888172
7 changed files with 31 additions and 35 deletions
+2 -2
View File
@@ -31,7 +31,7 @@ interface DesktopDocumentCardProps {
getDocumentAsset?: (...args: any[]) => unknown; getDocumentAsset?: (...args: any[]) => unknown;
handleNavigatorSnapshot?: (...args: any[]) => void; handleNavigatorSnapshot?: (...args: any[]) => void;
cardPointerHandlers?: React.HTMLAttributes<HTMLDivElement>; cardPointerHandlers?: React.HTMLAttributes<HTMLDivElement>;
onDocumentActivate?: (id: string) => void; onDocumentActivate?: (id: string, event?: any) => void;
onTagDragEnter?: (event: React.DragEvent<HTMLDivElement>, docId: DocumentId) => void; onTagDragEnter?: (event: React.DragEvent<HTMLDivElement>, docId: DocumentId) => void;
onTagDragOver?: (event: React.DragEvent<HTMLDivElement>, docId: DocumentId) => void; onTagDragOver?: (event: React.DragEvent<HTMLDivElement>, docId: DocumentId) => void;
onTagDragLeave?: (event: React.DragEvent<HTMLDivElement>, docId: DocumentId) => void; onTagDragLeave?: (event: React.DragEvent<HTMLDivElement>, docId: DocumentId) => void;
@@ -116,7 +116,7 @@ const DesktopDocumentCard: React.FC<DesktopDocumentCardProps> = ({
onKeyDown={(event) => { onKeyDown={(event) => {
if (event.key === 'Enter' || event.key === ' ') { if (event.key === 'Enter' || event.key === ' ') {
preventAll(event); preventAll(event);
onDocumentActivate?.(doc.id); onDocumentActivate?.(doc.id, event);
} }
}} }}
> >
+1 -1
View File
@@ -31,7 +31,7 @@ interface DesktopPreviewCardProps {
doc: Document | null; doc: Document | null;
title?: string; title?: string;
ensureAssetUrl?: EnsureAssetUrl | null; ensureAssetUrl?: EnsureAssetUrl | null;
getDocumentAsset: GetDocumentAsset; getDocumentAsset?: GetDocumentAsset;
onNavigatorSnapshot?: (docId: Identifier, snapshot: NavigatorSnapshot | null) => void; onNavigatorSnapshot?: (docId: Identifier, snapshot: NavigatorSnapshot | null) => void;
shouldLoad?: boolean; shouldLoad?: boolean;
} }
+2 -2
View File
@@ -57,7 +57,7 @@ export interface DesktopWorkspaceProps {
const DesktopDocumentContainer: React.FC<React.ComponentProps<typeof DesktopDocumentCard> & { const DesktopDocumentContainer: React.FC<React.ComponentProps<typeof DesktopDocumentCard> & {
onSelect: (ids: string[], extend?: boolean) => void; onSelect: (ids: string[], extend?: boolean) => void;
onDeselect: (ids: string[]) => void; onDeselect: (ids: string[]) => void;
onDocumentActivate?: (id: string) => void; onDocumentActivate?: (id: string, event?: any) => void;
selection: string[]; selection: string[];
}> = React.memo((props) => { }> = React.memo((props) => {
const { layoutCard, selected, onSelect, onDeselect, onDocumentActivate, selection } = props; const { layoutCard, selected, onSelect, onDeselect, onDocumentActivate, selection } = props;
@@ -280,7 +280,7 @@ const DesktopWorkspaceContent: React.FC<DesktopWorkspaceProps> = ({
ensureAssetUrl={ensureAssetUrl} ensureAssetUrl={ensureAssetUrl}
getDocumentAsset={getDocumentAsset} getDocumentAsset={getDocumentAsset}
handleNavigatorSnapshot={() => { }} handleNavigatorSnapshot={() => { }}
onDocumentActivate={(id) => { onDocumentActivate?.({ id } as DeskDocument) }} onDocumentActivate={(_id, event) => { onDocumentActivate?.(doc, event) }}
layoutCard={layoutCard} layoutCard={layoutCard}
onTagDragEnter={tagInteractions.handleTagDragEnterDoc} onTagDragEnter={tagInteractions.handleTagDragEnterDoc}
onTagDragOver={tagInteractions.handleTagDragOverDoc} onTagDragOver={tagInteractions.handleTagDragOverDoc}
+10 -7
View File
@@ -14,11 +14,12 @@ export const useCardPointer = (
selection: string[], selection: string[],
onSelect: (ids: string[], extend?: boolean) => void, onSelect: (ids: string[], extend?: boolean) => void,
onDeselect: (ids: string[]) => void, onDeselect: (ids: string[]) => void,
onDocumentActivate?: (id: string) => void onDocumentActivate?: (id: string, event?: React.PointerEvent) => void
) => { ) => {
const [state, setState] = React.useState<PointerState>('idle'); const [state, setState] = React.useState<PointerState>('idle');
const initialPosition = useRef<{ x: number, y: number } | null>(null); const initialPosition = useRef<{ x: number, y: number } | null>(null);
const lastPosition = useRef<{ x: number, y: number } | null>(null); const lastPosition = useRef<{ x: number, y: number } | null>(null);
const lastClickTime = useRef<number>(0);
const longPressTimer = useRef<ReturnType<typeof setTimeout> | null>(null); const longPressTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
@@ -37,8 +38,8 @@ export const useCardPointer = (
}, [state]); }, [state]);
const onPointerDown = useCallback((e: React.PointerEvent) => { const onPointerDown = useCallback((e: React.PointerEvent) => {
// Only left click // Allow left (0) and middle (1) click
if (e.button !== 0) return; if (e.button !== 0 && e.button !== 1) return;
e.preventDefault(); e.preventDefault();
@@ -201,13 +202,15 @@ export const useCardPointer = (
if (state === 'drag' || state === 'drag-start') { if (state === 'drag' || state === 'drag-start') {
handleDragEnd(card.store, selection, e.pointerId); handleDragEnd(card.store, selection, e.pointerId);
} else if (state === 'click') { } else if (state === 'click') {
if (isSelected) { const now = Date.now();
const isUnobstructed = card.isUnobstructed(); if (now - lastClickTime.current < 300) {
onDocumentActivate?.(card.id, e);
}
lastClickTime.current = now;
if (isSelected) {
if (hasModifier) { if (hasModifier) {
onDeselect([card.id]); onDeselect([card.id]);
} else if (isUnobstructed) {
onDocumentActivate?.(card.id);
} }
} else { } else {
onSelect([card.id], hasModifier); onSelect([card.id], hasModifier);
@@ -1,5 +1,6 @@
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 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';
@@ -14,7 +15,6 @@ export const useDocumentItemLogic = (props: UseDocumentItemLogicProps) => {
doc, doc,
viewLogic, viewLogic,
draggingDocumentIdsSet, draggingDocumentIdsSet,
onDocumentClick,
onDocumentActivate, onDocumentActivate,
onDocumentDragStart, onDocumentDragStart,
onDocumentDragEnd, onDocumentDragEnd,
@@ -37,6 +37,7 @@ export const useDocumentItemLogic = (props: UseDocumentItemLogicProps) => {
savingId: savingDocumentId, savingId: savingDocumentId,
attachInputRef: attachDocumentInputRef, attachInputRef: attachDocumentInputRef,
}, },
handleEntrySelection,
} = viewLogic; } = viewLogic;
const isSelected = selectedDocumentIdsSet?.has(doc.id); const isSelected = selectedDocumentIdsSet?.has(doc.id);
@@ -50,7 +51,10 @@ export const useDocumentItemLogic = (props: UseDocumentItemLogicProps) => {
const allowInlineDocumentEdit = onDocumentRename && isSelected && totalSelectionCount === 1; const allowInlineDocumentEdit = onDocumentRename && isSelected && totalSelectionCount === 1;
const handlers = { const handlers = {
onClick: (event: React.MouseEvent) => onDocumentClick?.(doc, event), onClick: (event: React.MouseEvent) => {
const key = createDocumentEntryKey(doc.id);
handleEntrySelection(key, event);
},
onDoubleClick: (event: React.MouseEvent) => onDocumentActivate?.(doc, event), onDoubleClick: (event: React.MouseEvent) => onDocumentActivate?.(doc, event),
onDragStart: (event: DragEvent<HTMLElement>) => onDocumentDragStart?.(event, doc), onDragStart: (event: DragEvent<HTMLElement>) => onDocumentDragStart?.(event, doc),
onDragEnd: (event: DragEvent<HTMLElement>) => onDocumentDragEnd?.(event), onDragEnd: (event: DragEvent<HTMLElement>) => onDocumentDragEnd?.(event),
@@ -16,6 +16,7 @@ export const useDocumentViewLogic = ({
selectedDocumentIds, selectedDocumentIds,
selectedFolderIds, selectedFolderIds,
clearSelection, clearSelection,
handleEntrySelection,
} = useWorkspaceSelectionContext(); } = useWorkspaceSelectionContext();
const selectedDocumentIdsSet = useMemo(() => new Set(selectedDocumentIds), [selectedDocumentIds]); const selectedDocumentIdsSet = useMemo(() => new Set(selectedDocumentIds), [selectedDocumentIds]);
@@ -42,6 +43,7 @@ export const useDocumentViewLogic = ({
selectedDocumentIdsSet, selectedDocumentIdsSet,
selectedFolderIdsSet, selectedFolderIdsSet,
clearSelection, clearSelection,
handleEntrySelection,
totalSelectionCount, totalSelectionCount,
documentRename, documentRename,
folderRename, folderRename,
@@ -42,8 +42,6 @@ interface DocumentsPanelProps extends DocumentsPanelInnerProps {
selectionValue: WorkspaceSelectionValue; selectionValue: WorkspaceSelectionValue;
} }
const defaultGetDocumentAsset = (_doc?: unknown, _type?: string) => null;
export type DocumentLinkLike = { url?: string | null; mimeType?: string | null }; export type DocumentLinkLike = { url?: string | null; mimeType?: string | null };
export interface DocumentsViewProps { export interface DocumentsViewProps {
@@ -60,8 +58,7 @@ export interface DocumentsViewProps {
onFolderDragStart?: (event: DragEvent<HTMLElement>, folderId: Identifier | 'root') => void; onFolderDragStart?: (event: DragEvent<HTMLElement>, folderId: Identifier | 'root') => void;
onFolderDragEnd?: (event: DragEvent<HTMLElement>) => void; onFolderDragEnd?: (event: DragEvent<HTMLElement>) => void;
onFolderRename?: (folderId: Identifier | 'root', nextName: string) => Promise<boolean> | boolean; onFolderRename?: (folderId: Identifier | 'root', nextName: string) => Promise<boolean> | boolean;
onDocumentClick?: DocumentEventHandler; onDocumentOpen?: DocumentEventHandler;
onDocumentActivate?: DocumentEventHandler;
onDocumentDragStart?: (event: DragEvent<HTMLElement>, document: Document) => void; onDocumentDragStart?: (event: DragEvent<HTMLElement>, document: Document) => void;
onDocumentDragEnd?: (event: DragEvent<HTMLElement>) => void; onDocumentDragEnd?: (event: DragEvent<HTMLElement>) => void;
onDocumentTagDragOver?: (event: DragEvent<HTMLElement>) => void; onDocumentTagDragOver?: (event: DragEvent<HTMLElement>) => void;
@@ -105,7 +102,7 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
tagLookupById, tagLookupById,
activeCorrespondentIds = [], activeCorrespondentIds = [],
ensureAssetUrl = null, ensureAssetUrl = null,
getDocumentAsset = defaultGetDocumentAsset, getDocumentAsset,
isSearchLoading = false, isSearchLoading = false,
viewMode = 'list', viewMode = 'list',
onViewModeChange, onViewModeChange,
@@ -345,29 +342,19 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
[isTagDragEvent], [isTagDragEvent],
); );
const handleDocumentClick = useCallback(
(doc, event) => {
if (!doc || suppressDocumentClickRef.current || !onEntryPointer) {
return;
}
onEntryPointer(
{ type: EntryType.document, id: doc.id, key: `document:${doc.id}`, document: doc },
event,
);
},
[onEntryPointer],
);
const handleDocumentActivate = useCallback( const handleDocumentActivate = useCallback(
(doc, event?: React.MouseEvent | KeyboardEvent | null) => { (doc, event?: React.MouseEvent | KeyboardEvent | null) => {
if (!doc) { if (!doc) {
return; return;
} }
if (event && (event.altKey || (event.button === 1))) {
// Handle Preview (Alt+Click or Middle Click)
if (event && (event.altKey || ((event as React.MouseEvent).button === 1))) {
openPreview(doc); openPreview(doc);
return; return;
} }
// Handle Activation (Double Click, Enter, or explicit call)
if (onDocumentActivate) { if (onDocumentActivate) {
onDocumentActivate(doc); onDocumentActivate(doc);
} }
@@ -435,7 +422,7 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
onFolderDragStart, onFolderDragStart,
onFolderDragEnd, onFolderDragEnd,
onFolderRename, onFolderRename,
onDocumentClick: handleDocumentClick,
onDocumentActivate: handleDocumentActivate, onDocumentActivate: handleDocumentActivate,
onDocumentDragStart: handleDocumentDragStartLocal, onDocumentDragStart: handleDocumentDragStartLocal,
onDocumentDragEnd: handleDocumentDragEndLocal, onDocumentDragEnd: handleDocumentDragEndLocal,