refactor: begin spatial workspace architecture refactor with design document and updated pointer event handling for document activation.
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user