feat: Introduce new LayoutSystem and spatial workspace architecture design document, refactor document card and workspace to use it, and simplify pointer event handling.
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { globalLayout, LayoutItem } from './LayoutSystem';
|
||||
import { LayoutStore, LayoutCard } from './LayoutSystem';
|
||||
import PreviewZoomOverlay from '../detail/PreviewZoomOverlay';
|
||||
import DesktopDocumentCard from './DesktopDocumentCard';
|
||||
import usePreviewMetadata from './hooks/usePreviewMetadata';
|
||||
import useDeskTagInteractions from './tags/useDeskTagInteractions';
|
||||
import '../styles/workspace/workspace-layout.css';
|
||||
import '../styles/workspace/workspace-items.css';
|
||||
import '../styles/workspace/workspace-cards.css';
|
||||
@@ -42,7 +42,7 @@ interface DocumentSizeInfo {
|
||||
|
||||
// Fallback size computation
|
||||
const computeFallbackCardSize = (_doc: DeskDocument): DocumentSizeInfo => {
|
||||
return { width: 200, height: 280, source: 'fallback' };
|
||||
return { width: 200, height: 200, source: 'fallback' };
|
||||
};
|
||||
|
||||
export interface DesktopWorkspaceProps {
|
||||
@@ -51,7 +51,7 @@ export interface DesktopWorkspaceProps {
|
||||
getDocumentAsset?: (...args: any[]) => unknown;
|
||||
onDocumentActivate?: (doc: DeskDocument, event?: unknown) => void;
|
||||
onSelectionChange?: (selectedIds: Identifier[]) => void;
|
||||
layout?: any[];
|
||||
onDocumentTagDrop?: (docId: Identifier, tag: any) => void;
|
||||
}
|
||||
|
||||
const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
|
||||
@@ -60,10 +60,9 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
|
||||
getDocumentAsset,
|
||||
onDocumentActivate,
|
||||
onSelectionChange,
|
||||
layout: initialLayout,
|
||||
onDocumentTagDrop,
|
||||
}) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const itemRefs = useRef<Map<string, HTMLElement>>(new Map());
|
||||
|
||||
const items = useMemo(() => {
|
||||
return entries
|
||||
@@ -74,7 +73,8 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
|
||||
}, [entries]);
|
||||
|
||||
// Layout System Initialization
|
||||
const layoutRef = useRef<Map<string, LayoutItem>>(new Map());
|
||||
const layoutStore = useMemo(() => new LayoutStore(), []);
|
||||
const layoutRef = useRef<Map<string, LayoutCard>>(new Map());
|
||||
|
||||
// Selection Context
|
||||
const {
|
||||
@@ -101,33 +101,46 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
|
||||
|
||||
const metadataMap = usePreviewMetadata(items, getDocumentAsset, ensureAssetUrl);
|
||||
|
||||
const ensureDocumentSize = useCallback((doc: DeskDocument | null): DocumentSizeInfo | null => {
|
||||
if (!doc) return null;
|
||||
const ensureDocumentSize = useCallback((doc: DeskDocument): DocumentSizeInfo => {
|
||||
if (doc.id) {
|
||||
const meta = metadataMap.get(String(doc.id));
|
||||
if (meta) return { width: meta.width, height: meta.height, source: 'metadata' };
|
||||
if (meta && meta.width && meta.height)
|
||||
return { width: meta.width, height: meta.height, source: 'metadata' };
|
||||
}
|
||||
|
||||
return computeFallbackCardSize(doc);
|
||||
}, [metadataMap]);
|
||||
|
||||
// Initialize Layout System
|
||||
useLayoutEffect(() => {
|
||||
if (initialLayout) {
|
||||
// Seeding logic if needed, but registration happens in render loop
|
||||
}
|
||||
}, [initialLayout]);
|
||||
|
||||
// Sync LayoutStore to layoutRef
|
||||
useEffect(() => {
|
||||
const sync = () => {
|
||||
layoutRef.current = globalLayout.items;
|
||||
layoutRef.current = layoutStore.items;
|
||||
};
|
||||
sync();
|
||||
}, []);
|
||||
}, [layoutStore.items]);
|
||||
|
||||
const handleShellKeyDown = useCallback(() => { }, []);
|
||||
const focusShell = useCallback(() => { }, []);
|
||||
|
||||
// Tag Interactions
|
||||
const [pendingRemovalTag, setPendingRemovalTag] = useState<{ docId?: string; tagId?: string } | null>(null);
|
||||
const [tagDropTargetId, setTagDropTargetId] = useState<string | null>(null);
|
||||
const [pendingTagDocId, setPendingTagDocId] = useState<string | null>(null);
|
||||
|
||||
const tagEngine = useMemo(() => ({
|
||||
setPendingRemovalTag,
|
||||
setTagDropTargetId,
|
||||
setPendingTagDocId,
|
||||
}), []);
|
||||
|
||||
const tagInteractions = useDeskTagInteractions({
|
||||
engine: tagEngine,
|
||||
onAssignTagToDocument: (docId: string, tag: any) => {
|
||||
onDocumentTagDrop?.(docId, tag);
|
||||
},
|
||||
requestCanvasFocus: focusShell,
|
||||
});
|
||||
|
||||
// Overlay State
|
||||
const [overlayDisplay, setOverlayDisplay] = useState<OverlayDisplay | null>(null);
|
||||
const closeOverlay = useCallback(() => setOverlayDisplay(null), []);
|
||||
@@ -154,25 +167,10 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
|
||||
const isSelected = selectedDocumentIds.includes(docId);
|
||||
const size = ensureDocumentSize(doc);
|
||||
|
||||
// Register with LayoutStore
|
||||
const registerNode = (node: HTMLDivElement) => {
|
||||
if (node) {
|
||||
itemRefs.current.set(docId, node);
|
||||
const initItem = initialLayout?.find((i: any) => i.id === docId);
|
||||
globalLayout.initialize(docId, node, {
|
||||
x: initItem?.x,
|
||||
y: initItem?.y,
|
||||
rotation: initItem?.rotation,
|
||||
z: index,
|
||||
width: size?.width ?? 200,
|
||||
height: size?.height ?? 200
|
||||
});
|
||||
} else {
|
||||
itemRefs.current.delete(docId);
|
||||
}
|
||||
};
|
||||
|
||||
// const cardPointerHandlers = getCardPointerHandlers(doc);
|
||||
const layoutCard = layoutStore.initialize(docId, null, {
|
||||
width: size.width,
|
||||
height: size.height
|
||||
});
|
||||
|
||||
return (
|
||||
<DesktopDocumentCard
|
||||
@@ -187,8 +185,7 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
|
||||
}}
|
||||
shouldLoad={true}
|
||||
matchesFilter={true}
|
||||
tagTargetActive={false}
|
||||
tagTargetPending={false}
|
||||
|
||||
selected={isSelected}
|
||||
docTagTokens=""
|
||||
ensureAssetUrl={ensureAssetUrl}
|
||||
@@ -196,7 +193,18 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
|
||||
handleNavigatorSnapshot={() => { }}
|
||||
cardPointerHandlers={undefined}
|
||||
onDocumentActivate={onDocumentActivate}
|
||||
registerNode={registerNode}
|
||||
layoutCard={layoutCard}
|
||||
onTagDragEnter={tagInteractions.handleTagDragEnterDoc}
|
||||
onTagDragOver={tagInteractions.handleTagDragOverDoc}
|
||||
onTagDragLeave={tagInteractions.handleTagDragLeaveDoc}
|
||||
onTagDrop={tagInteractions.handleTagDropOnDoc}
|
||||
onDocTagPointerDown={tagInteractions.handleDocTagPointerDown}
|
||||
onDocTagDragStart={tagInteractions.handleDocTagDragStart}
|
||||
onDocTagDrag={tagInteractions.handleDocTagDrag}
|
||||
onDocTagDragEnd={tagInteractions.handleDocTagDragEnd}
|
||||
tagTargetActive={tagDropTargetId === docId}
|
||||
tagTargetPending={pendingTagDocId === docId}
|
||||
pendingRemovalTag={pendingRemovalTag}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user