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;
handleNavigatorSnapshot?: (...args: any[]) => void;
cardPointerHandlers?: React.HTMLAttributes<HTMLDivElement>;
onDocumentActivate?: (id: string) => void;
onDocumentActivate?: (id: string, event?: any) => void;
onTagDragEnter?: (event: React.DragEvent<HTMLDivElement>, docId: DocumentId) => void;
onTagDragOver?: (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) => {
if (event.key === 'Enter' || event.key === ' ') {
preventAll(event);
onDocumentActivate?.(doc.id);
onDocumentActivate?.(doc.id, event);
}
}}
>
+1 -1
View File
@@ -31,7 +31,7 @@ interface DesktopPreviewCardProps {
doc: Document | null;
title?: string;
ensureAssetUrl?: EnsureAssetUrl | null;
getDocumentAsset: GetDocumentAsset;
getDocumentAsset?: GetDocumentAsset;
onNavigatorSnapshot?: (docId: Identifier, snapshot: NavigatorSnapshot | null) => void;
shouldLoad?: boolean;
}
+2 -2
View File
@@ -57,7 +57,7 @@ export interface DesktopWorkspaceProps {
const DesktopDocumentContainer: React.FC<React.ComponentProps<typeof DesktopDocumentCard> & {
onSelect: (ids: string[], extend?: boolean) => void;
onDeselect: (ids: string[]) => void;
onDocumentActivate?: (id: string) => void;
onDocumentActivate?: (id: string, event?: any) => void;
selection: string[];
}> = React.memo((props) => {
const { layoutCard, selected, onSelect, onDeselect, onDocumentActivate, selection } = props;
@@ -280,7 +280,7 @@ const DesktopWorkspaceContent: React.FC<DesktopWorkspaceProps> = ({
ensureAssetUrl={ensureAssetUrl}
getDocumentAsset={getDocumentAsset}
handleNavigatorSnapshot={() => { }}
onDocumentActivate={(id) => { onDocumentActivate?.({ id } as DeskDocument) }}
onDocumentActivate={(_id, event) => { onDocumentActivate?.(doc, event) }}
layoutCard={layoutCard}
onTagDragEnter={tagInteractions.handleTagDragEnterDoc}
onTagDragOver={tagInteractions.handleTagDragOverDoc}
+10 -7
View File
@@ -14,11 +14,12 @@ export const useCardPointer = (
selection: string[],
onSelect: (ids: string[], extend?: boolean) => void,
onDeselect: (ids: string[]) => void,
onDocumentActivate?: (id: string) => void
onDocumentActivate?: (id: string, event?: React.PointerEvent) => void
) => {
const [state, setState] = React.useState<PointerState>('idle');
const initialPosition = 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);
@@ -37,8 +38,8 @@ export const useCardPointer = (
}, [state]);
const onPointerDown = useCallback((e: React.PointerEvent) => {
// Only left click
if (e.button !== 0) return;
// Allow left (0) and middle (1) click
if (e.button !== 0 && e.button !== 1) return;
e.preventDefault();
@@ -201,13 +202,15 @@ export const useCardPointer = (
if (state === 'drag' || state === 'drag-start') {
handleDragEnd(card.store, selection, e.pointerId);
} else if (state === 'click') {
if (isSelected) {
const isUnobstructed = card.isUnobstructed();
const now = Date.now();
if (now - lastClickTime.current < 300) {
onDocumentActivate?.(card.id, e);
}
lastClickTime.current = now;
if (isSelected) {
if (hasModifier) {
onDeselect([card.id]);
} else if (isUnobstructed) {
onDocumentActivate?.(card.id);
}
} else {
onSelect([card.id], hasModifier);
@@ -1,5 +1,6 @@
import React, { type DragEvent } from 'react';
import { parseTagTransferPayload } from '../tagTransfer';
import { createDocumentEntryKey } from '../../app/entryKey';
import type { Document } from '../../types/documents';
import type { DocumentsViewProps } from '../panel/DocumentsPanel';
import type { DocumentViewLogic } from './useDocumentViewLogic';
@@ -14,7 +15,6 @@ export const useDocumentItemLogic = (props: UseDocumentItemLogicProps) => {
doc,
viewLogic,
draggingDocumentIdsSet,
onDocumentClick,
onDocumentActivate,
onDocumentDragStart,
onDocumentDragEnd,
@@ -37,6 +37,7 @@ export const useDocumentItemLogic = (props: UseDocumentItemLogicProps) => {
savingId: savingDocumentId,
attachInputRef: attachDocumentInputRef,
},
handleEntrySelection,
} = viewLogic;
const isSelected = selectedDocumentIdsSet?.has(doc.id);
@@ -50,7 +51,10 @@ export const useDocumentItemLogic = (props: UseDocumentItemLogicProps) => {
const allowInlineDocumentEdit = onDocumentRename && isSelected && totalSelectionCount === 1;
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),
onDragStart: (event: DragEvent<HTMLElement>) => onDocumentDragStart?.(event, doc),
onDragEnd: (event: DragEvent<HTMLElement>) => onDocumentDragEnd?.(event),
@@ -16,6 +16,7 @@ export const useDocumentViewLogic = ({
selectedDocumentIds,
selectedFolderIds,
clearSelection,
handleEntrySelection,
} = useWorkspaceSelectionContext();
const selectedDocumentIdsSet = useMemo(() => new Set(selectedDocumentIds), [selectedDocumentIds]);
@@ -42,6 +43,7 @@ export const useDocumentViewLogic = ({
selectedDocumentIdsSet,
selectedFolderIdsSet,
clearSelection,
handleEntrySelection,
totalSelectionCount,
documentRename,
folderRename,
@@ -42,8 +42,6 @@ interface DocumentsPanelProps extends DocumentsPanelInnerProps {
selectionValue: WorkspaceSelectionValue;
}
const defaultGetDocumentAsset = (_doc?: unknown, _type?: string) => null;
export type DocumentLinkLike = { url?: string | null; mimeType?: string | null };
export interface DocumentsViewProps {
@@ -60,8 +58,7 @@ export interface DocumentsViewProps {
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;
onDocumentOpen?: DocumentEventHandler;
onDocumentDragStart?: (event: DragEvent<HTMLElement>, document: Document) => void;
onDocumentDragEnd?: (event: DragEvent<HTMLElement>) => void;
onDocumentTagDragOver?: (event: DragEvent<HTMLElement>) => void;
@@ -105,7 +102,7 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
tagLookupById,
activeCorrespondentIds = [],
ensureAssetUrl = null,
getDocumentAsset = defaultGetDocumentAsset,
getDocumentAsset,
isSearchLoading = false,
viewMode = 'list',
onViewModeChange,
@@ -345,29 +342,19 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
[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(
(doc, event?: React.MouseEvent | KeyboardEvent | null) => {
if (!doc) {
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);
return;
}
// Handle Activation (Double Click, Enter, or explicit call)
if (onDocumentActivate) {
onDocumentActivate(doc);
}
@@ -435,7 +422,7 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
onFolderDragStart,
onFolderDragEnd,
onFolderRename,
onDocumentClick: handleDocumentClick,
onDocumentActivate: handleDocumentActivate,
onDocumentDragStart: handleDocumentDragStartLocal,
onDocumentDragEnd: handleDocumentDragEndLocal,