import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore, } from 'react'; import { resolveDocumentAssetUrl } from '../asset_manager'; import type { EnsureAssetUrl, GetAsset } from '../asset_manager'; import { formatTransform } from './math'; import useDocumentDrag from './useDocumentDrag'; import PreviewZoomOverlay from '../detail/PreviewZoomOverlay'; import { WorkspaceEngine, DESK_CANVAS_PADDING, DESK_DEFAULT_CANVAS_HEIGHT, DESK_DEFAULT_CANVAS_WIDTH, clampCardDimensions, computeFallbackCardSize, useWorkspaceSnapshot, } from './workspaceEngine'; import useDeskPointer from './pointer/useDeskPointer'; import useDeskTagInteractions from './tags/useDeskTagInteractions'; import DesktopDocumentCard from './DesktopDocumentCard'; import usePreviewMetadata from './hooks/usePreviewMetadata'; import '../styles/workspace/workspace-layout.css'; import '../styles/workspace/workspace-items.css'; import '../styles/workspace/workspace-cards.css'; import { useWorkspaceSelectionContext } from '../app/WorkspaceSelectionContext'; type Identifier = string | number; type TagLike = { id?: Identifier | null; label?: string; color?: string | null } | null; type DocumentLinkLike = { url?: string | null; mimeType?: string | null }; type OverlaySource = { url: string; alt?: string | null; mimeType?: string | null }; export interface DeskDocument { id?: Identifier | null; title?: string; tags?: TagLike[] | null; documentLink?: OverlaySource | null; [key: string]: unknown; } interface NavigatorSnapshot { url: string | null; alt?: string | null; width?: number | null; height?: number | null; } type OverlayOriginHint = { rotation?: number; scale?: number; width?: number; height?: number; }; interface OverlayOriginTransform { rotation: number; scaleX: number; scaleY: number; baseWidth: number; baseHeight: number; } interface OverlayDisplay { url: string; alt?: string | null; mimeType?: string | null; } interface DocumentSizeInfo { width: number; height: number; source?: 'snapshot' | 'metadata' | 'fallback'; } interface PreviewMetadataEntry { docId: string; width: number; height: number; } interface DragTransformOverride { centerX?: number; centerY?: number; rotation?: number; scale?: number; } interface DragSettings { canvasPadding: number; defaultCanvasWidth: number; defaultCanvasHeight: number; debugDrag?: boolean; } interface LayoutEntry { centerX: number; centerY: number; rotation: number; z: number; width?: number; height?: number; } type WorkspaceSnapshotState = { layout: Map; canvasSize: { width: number; height: number }; visibleDocIds: Set; draggingId: string | null; tagDropTargetId: string | null; pendingTagDocId: string | null; pendingRemovalTag: unknown; initialLoadDone: boolean; }; interface DesktopWorkspaceProps { entries?: DeskDocument[]; onDocumentActivate?: (...args: unknown[]) => void; onDocumentClick?: (...args: unknown[]) => void; onDocumentTagDrop?: (...args: unknown[]) => void; ensureAssetUrl?: EnsureAssetUrl; getDocumentAsset?: GetAsset; activeTagFilters?: Array; tenantId?: Identifier | null; viewId?: string | null; } interface DesktopWorkspaceViewProps { engine: WorkspaceEngine; items: DeskDocument[]; containerRef: React.RefObject; handleCanvasDragOver: (event: React.DragEvent) => void; handleCanvasDragLeave: (event: React.DragEvent) => void; handleCanvasDrop: (event: React.DragEvent) => void; ensureDocumentSize: (doc: DeskDocument | null) => DocumentSizeInfo | null; layoutSnapshot: Map; layoutRef: React.MutableRefObject>; dragTransformsRef: React.MutableRefObject>; itemRefs: React.MutableRefObject>; visibleDocIds: Set; draggingId: string | null; tagDropTargetId: string | null; pendingTagDocId: string | null; pendingRemovalTag: unknown; ensureAssetUrl?: DesktopWorkspaceProps['ensureAssetUrl']; getDocumentAsset?: DesktopWorkspaceProps['getDocumentAsset']; handleNavigatorSnapshot: (docId: Identifier | null, snapshot: NavigatorSnapshot | null) => void; activeTagSet: Set; handleTagDragEnterDoc: (...args: unknown[]) => void; handleTagDragOverDoc: (...args: unknown[]) => void; handleTagDragLeaveDoc: (...args: unknown[]) => void; handleTagDropOnDoc: (...args: unknown[]) => void; handleDocTagPointerDown: (...args: unknown[]) => void; handleDocTagDragStart: (...args: unknown[]) => void; handleDocTagDrag: (...args: unknown[]) => void; handleDocTagDragEnd: (...args: unknown[]) => void; overlayDisplay: OverlayDisplay | null; closeOverlay: () => void; overlayOriginRect: DOMRect | null; overlayOriginTransform: OverlayOriginTransform | null; overlayDocument: DeskDocument | null; onDocumentClick?: DesktopWorkspaceProps['onDocumentClick']; onDocumentStackSelect?: (docIds: Identifier[], event?: PointerEvent | MouseEvent | null) => void; onPromoteSelection?: (docId: Identifier | null) => void; documentLookup: Map; selectedDocumentIds: Identifier[]; onClearSelection: () => void; resolveBaseMetrics: (doc: DeskDocument | null, cardWidth: number, cardHeight: number) => { baseWidth: number; baseHeight: number; baseScale: number; }; bringToFront: (docId: Identifier | null) => void; setDraggingId: (value: string | null) => void; canvasSize: { width: number; height: number }; openOverlayForDoc: (docId: Identifier | null, originInfo?: OverlayOriginHint | null) => void; recalcVisibleDocIds: () => void; dragSettings: DragSettings; onDocumentActivate?: DesktopWorkspaceProps['onDocumentActivate']; markLayoutDirty: () => void; } const DEBUG_DRAG = false; const DEBUG_FOCUS = false; const defaultGetDocumentAsset: GetAsset = () => null; const DesktopWorkspace: React.FC = ({ entries = [], onDocumentActivate = null, onDocumentClick = null, onDocumentTagDrop = null, ensureAssetUrl = null, getDocumentAsset = defaultGetDocumentAsset, activeTagFilters = [], tenantId = null, viewId = 'default', documentLinks, ensureDownloadUrl, }) => { const { selectedDocumentIds, clearSelection, handleEntrySelection, promoteSelectionOrder, } = useWorkspaceSelectionContext(); const items = useMemo( () => (Array.isArray(entries) ? entries.filter((doc): doc is DeskDocument => Boolean(doc)) : []), [entries], ); const getDocRowKey = useCallback((id: Identifier | null) => (id != null ? `document:${id}` : null), []); const handleStackSelect = useCallback( (docIds: Identifier[], event?: PointerEvent | MouseEvent | null) => { if (!Array.isArray(docIds) || docIds.length === 0) { return; } const syntheticEvent = event || ({ metaKey: true, ctrlKey: true, preventDefault: () => {}, } as unknown as PointerEvent); docIds.forEach((id) => { const key = getDocRowKey(id); if (key) { handleEntrySelection(key, syntheticEvent); } }); }, [getDocRowKey, handleEntrySelection], ); const handlePromoteSelection = useCallback( (docId: Identifier | null) => { const key = getDocRowKey(docId); if (key && promoteSelectionOrder) { promoteSelectionOrder(key); } }, [getDocRowKey, promoteSelectionOrder], ); const allowLayoutPersistence = Boolean(tenantId && viewId && viewId.startsWith('folder:')); const documentLinkMap = documentLinks instanceof Map ? documentLinks : null; const containerRef = useRef(null); const itemRefs = useRef>(new Map()); const dragTransformsRef = useRef>(new Map()); const [overlayDocId, setOverlayDocId] = useState(null); const [overlayOriginRect, setOverlayOriginRect] = useState(null); const [overlayOriginTransform, setOverlayOriginTransform] = useState( null, ); const [overlaySource, setOverlaySource] = useState(null); const [, setPreviewSnapshots] = useState>(() => new Map()); const [docSizeVersion, setDocSizeVersion] = useState(0); const docSizeMapRef = useRef>(new Map()); const ensureDocumentSize = useCallback((doc: DeskDocument | null): DocumentSizeInfo | null => { if (!doc?.id) { return null; } return docSizeMapRef.current.get(String(doc.id)) || null; }, []); const previewMetadata = usePreviewMetadata(items, getDocumentAsset, ensureAssetUrl); const documentLookup = useMemo>(() => { const map = new Map(); items.forEach((doc) => { const key = doc?.id != null ? String(doc.id) : null; if (key) { map.set(key, doc); } }); return map; }, [items]); const engineRef = useRef(null); if (!engineRef.current) { engineRef.current = new WorkspaceEngine({ allowLayoutPersistence, tenantId, viewId, }); } const engine = engineRef.current as WorkspaceEngine; useEffect(() => { engine.updateConfig({ allowLayoutPersistence, tenantId, viewId }); }, [engine, allowLayoutPersistence, tenantId, viewId]); useEffect(() => { engine.setItems(items || []); }, [engine, items]); useEffect(() => { const map = new Map(); items.forEach((doc) => { const key = doc?.id != null ? String(doc.id) : null; if (key) { map.set(key, doc); } }); engine.setDocumentLookup(map); }, [engine, items]); useEffect(() => { engine.setEnsureDocumentSize(ensureDocumentSize); }, [engine, ensureDocumentSize]); const workspaceSnapshot = useWorkspaceSnapshot(engine, useSyncExternalStore) as WorkspaceSnapshotState; const { layout: layoutSnapshot, canvasSize, visibleDocIds, draggingId, tagDropTargetId, pendingTagDocId, pendingRemovalTag, initialLoadDone, } = workspaceSnapshot; useEffect(() => { const shouldWaitForPersisted = allowLayoutPersistence && !initialLoadDone; if (shouldWaitForPersisted) { return; } engine.ensureLayoutForItems(); }, [engine, docSizeVersion, initialLoadDone, items.length, allowLayoutPersistence]); useEffect(() => { engine.setItemRefs(itemRefs); }, [engine, itemRefs]); const layoutRef = useRef>(layoutSnapshot); layoutRef.current = engine.layout as Map; const bringToFront = useCallback((docId: Identifier | null) => { engine.bringToFront(docId); }, [engine]); const markLayoutDirty = useCallback(() => { engine.markLayoutDirty(); }, [engine]); const recalcVisibleDocIds = useCallback(() => { engine.recalcVisibleDocIds(); }, [engine]); const setDraggingId = useCallback((value: string | number | null) => { engine.setDraggingId(value); }, [engine]); const applySnapshotDimensions = useCallback((docKey: string, snapshot: NavigatorSnapshot | null) => { const width = Number(snapshot?.width); const height = Number(snapshot?.height); if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) { return; } const normalized = clampCardDimensions(width, height); if (!normalized) { return; } const existing = docSizeMapRef.current.get(docKey); if (existing && existing.width === normalized.width && existing.height === normalized.height) { return; } const next = new Map(docSizeMapRef.current); next.set(docKey, { ...normalized, source: 'snapshot' }); docSizeMapRef.current = next; setDocSizeVersion((value) => value + 1); }, []); const handleNavigatorSnapshot = useCallback( (docId: Identifier | null, snapshot: NavigatorSnapshot | null) => { const docKey = docId != null ? String(docId) : null; if (!docKey) { return; } setPreviewSnapshots((previous) => { const prevSnapshot = previous.get(docKey); if (!snapshot) { if (!previous.has(docKey)) { return previous; } const next = new Map(previous); next.delete(docKey); return next; } const next = new Map(previous); const sameSnapshot = prevSnapshot && prevSnapshot.url === snapshot.url && prevSnapshot.alt === snapshot.alt && prevSnapshot.width === snapshot.width && prevSnapshot.height === snapshot.height; if (sameSnapshot) { return previous; } next.set(docKey, snapshot); return next; }); if (snapshot) { applySnapshotDimensions(docKey, snapshot); } }, [applySnapshotDimensions], ); const activeTagSet = useMemo>(() => { if (!Array.isArray(activeTagFilters) || activeTagFilters.length === 0) { return new Set(); } const set = new Set(); activeTagFilters.forEach((id) => { if (id != null) { set.add(String(id)); } }); return set; }, [activeTagFilters]); useLayoutEffect(() => { const container = containerRef.current; if (!container) { engine.setCanvasSize({ width: 0, height: 0 }); return undefined; } const commitSize = () => { const rect = container.getBoundingClientRect(); const width = Math.floor(rect.width) || 0; const height = Math.floor(rect.height) || 0; engine.setCanvasSize({ width, height }); }; commitSize(); let rafId: number | null = null; const observer = new ResizeObserver(() => { if (rafId != null) return; rafId = requestAnimationFrame(() => { rafId = null; commitSize(); }); }); observer.observe(container); return () => { observer.disconnect(); if (rafId != null) { cancelAnimationFrame(rafId); } }; }, [engine]); const resolvePreviewDimensions = useCallback( (doc: DeskDocument | null): PreviewMetadataEntry | null => { if (!doc?.id) { return null; } return previewMetadata.get(String(doc.id)) || null; }, [previewMetadata], ); useEffect(() => { if (!ensureAssetUrl) { return; } visibleDocIds.forEach((docId) => { const doc = documentLookup.get(docId); if (!doc) { return; } resolveDocumentAssetUrl(doc, 'thumbnail', { ensureAssetUrl, getAsset: getDocumentAsset, }); }); }, [visibleDocIds, ensureAssetUrl, getDocumentAsset, documentLookup]); const requestCanvasFocus = useCallback(() => { const canvas = containerRef.current; if (!canvas?.focus) { return; } const focusTarget = () => { try { canvas.focus({ preventScroll: true }); } catch (error: unknown) { if (DEBUG_FOCUS) { void error; } } }; const raf = window.requestAnimationFrame; if (raf) { raf(() => focusTarget()); return; } setTimeout(() => { focusTarget(); }, 0); }, []); const tagInteractions = useDeskTagInteractions({ engine, onAssignTagToDocument: onDocumentTagDrop, requestCanvasFocus, }); const { handleTagDragEnterDoc, handleTagDragOverDoc, handleTagDragLeaveDoc, handleTagDropOnDoc, handleCanvasDragOver, handleCanvasDragLeave, handleCanvasDrop, handleDocTagPointerDown, handleDocTagDragStart, handleDocTagDrag, handleDocTagDragEnd, } = tagInteractions; useEffect(() => { const current = docSizeMapRef.current; const next = new Map(current); const itemKeys = new Set(items.filter((doc) => doc?.id != null).map((doc) => String(doc.id))); let changed = false; items.forEach((doc) => { if (!doc?.id) { return; } const key = String(doc.id); const existing = next.get(key) || null; const meta = previewMetadata.get(key); if (meta) { const normalized = clampCardDimensions(meta.width, meta.height); if (normalized) { if (existing?.source === 'snapshot') { return; } if (!existing || existing.width !== normalized.width || existing.height !== normalized.height || existing.source !== 'metadata') { next.set(key, { ...normalized, source: 'metadata' }); changed = true; } return; } } if (!existing) { const fallback = computeFallbackCardSize(key); if (fallback) { next.set(key, { ...fallback, source: 'fallback' }); changed = true; } } }); current.forEach((_, key) => { if (!itemKeys.has(key)) { next.delete(key); changed = true; } }); if (changed) { docSizeMapRef.current = next; setDocSizeVersion((value) => value + 1); } }, [items, previewMetadata]); useEffect(() => { let cancelled = false; if (!overlayDocId) { setOverlaySource(null); return () => { cancelled = true; }; } const doc = documentLookup.get(overlayDocId) || null; const docIdentifier = doc?.id ?? null; if (!docIdentifier || !doc) { setOverlaySource(null); return () => { cancelled = true; }; } const docMimeType = doc?.mime_type ?? null; const applyEntry = (entry?: DocumentLinkLike | null) => { if (!entry?.url) { setOverlaySource(null); return; } setOverlaySource({ url: entry.url, alt: doc.title, mimeType: docMimeType || undefined, }); }; const cachedEntry = documentLinkMap?.get(docIdentifier) || null; if (cachedEntry?.url) { applyEntry(cachedEntry); return () => { cancelled = true; }; } if (!ensureDownloadUrl) { setOverlaySource(null); return () => { cancelled = true; }; } ensureDownloadUrl(docIdentifier) .then((entry) => { if (cancelled) { return; } applyEntry(entry); }) .catch(() => { if (!cancelled) { setOverlaySource(null); } }); return () => { cancelled = true; }; }, [overlayDocId, documentLookup, documentLinkMap, ensureDownloadUrl]); const closeOverlay = useCallback(() => { setOverlayDocId(null); setOverlayOriginRect(null); setOverlayOriginTransform(null); setOverlaySource(null); }, []); useEffect(() => { if (overlayDocId && !documentLookup.has(overlayDocId)) { setOverlayDocId(null); setOverlayOriginRect(null); setOverlayOriginTransform(null); } }, [overlayDocId, documentLookup]); const resolveBaseMetrics = useCallback( (doc: DeskDocument | null, cardWidth: number, cardHeight: number) => { const previewDims = doc ? resolvePreviewDimensions(doc) : null; if (previewDims?.width && previewDims?.height) { const baseWidth = Math.max(previewDims.width, cardWidth); const baseHeight = Math.max(previewDims.height, cardHeight); const scaleX = cardWidth / baseWidth; const scaleY = cardHeight / baseHeight; const baseScale = Math.min(scaleX, scaleY, 1); return { baseWidth, baseHeight, baseScale: Number.isFinite(baseScale) && baseScale > 0 ? baseScale : 1, }; } return { baseWidth: cardWidth, baseHeight: cardHeight, baseScale: 1, }; }, [resolvePreviewDimensions], ); useEffect(() => { if (draggingId && !items.some((doc) => String(doc.id) === draggingId)) { setDraggingId(null); } }, [draggingId, items, setDraggingId]); const overlayDisplay = useMemo(() => { if (!overlaySource) { return null; } return overlaySource; }, [overlaySource]); const overlayDocument = useMemo(() => { if (!overlayDocId) { return null; } const baseDoc = documentLookup.get(String(overlayDocId)) || null; if (baseDoc && overlayDisplay?.url) { return { ...baseDoc, documentLink: overlayDisplay }; } return baseDoc; }, [documentLookup, overlayDisplay, overlayDocId]); const openOverlayForDoc = useCallback( (docId: Identifier | null, originInfo: OverlayOriginHint | null = null) => { if (!docId) { return; } const docKey = String(docId); const container = itemRefs.current.get(docKey); if (!container) { return; } const imageNode = container.querySelector('.desk-item__card img'); const rect = (imageNode || container).getBoundingClientRect(); if (!rect) { return; } let originTransform = null; if (originInfo) { const { rotation = 0, scale = 1, width: originWidth, height: originHeight } = originInfo; originTransform = { rotation, scaleX: scale, scaleY: scale, baseWidth: originWidth, baseHeight: originHeight, }; } if (!originTransform) { const entry = engine.getLayout(docKey); const doc = documentLookup.get(docKey) || null; const sizeInfo = ensureDocumentSize(doc); if (!sizeInfo) { return; } const { width: cardWidth, height: cardHeight } = sizeInfo; const { baseWidth, baseHeight, baseScale } = resolveBaseMetrics(doc, cardWidth, cardHeight); const effectiveWidth = baseWidth * baseScale; const effectiveHeight = baseHeight * baseScale; originTransform = { rotation: entry?.rotation ?? 0, scaleX: baseScale, scaleY: baseScale, baseWidth: Number.isFinite(effectiveWidth) && effectiveWidth > 0 ? effectiveWidth : cardWidth, baseHeight: Number.isFinite(effectiveHeight) && effectiveHeight > 0 ? effectiveHeight : cardHeight, }; } bringToFront(docId); setOverlayOriginRect(rect); setOverlayOriginTransform(originTransform); setOverlayDocId(docKey); }, [ bringToFront, itemRefs, setOverlayOriginTransform, ensureDocumentSize, resolveBaseMetrics, documentLookup, engine, ], ); const dragSettings = useMemo( () => ({ canvasPadding: DESK_CANVAS_PADDING, defaultCanvasWidth: DESK_DEFAULT_CANVAS_WIDTH, defaultCanvasHeight: DESK_DEFAULT_CANVAS_HEIGHT, debugDrag: DEBUG_DRAG, }), [], ); const viewProps = useMemo( () => ({ engine, items, containerRef, handleCanvasDragOver, handleCanvasDragLeave, handleCanvasDrop, ensureDocumentSize, layoutSnapshot, layoutRef, dragTransformsRef, itemRefs, visibleDocIds, draggingId, tagDropTargetId, pendingTagDocId, pendingRemovalTag, ensureAssetUrl, getDocumentAsset, handleNavigatorSnapshot, activeTagSet, handleTagDragEnterDoc, handleTagDragOverDoc, handleTagDragLeaveDoc, handleTagDropOnDoc, handleDocTagPointerDown, handleDocTagDragStart, handleDocTagDrag, handleDocTagDragEnd, overlayDisplay, closeOverlay, overlayOriginRect, overlayOriginTransform, overlayDocument, onDocumentClick, onDocumentStackSelect: handleStackSelect, onPromoteSelection: handlePromoteSelection, selectedDocumentIds, onClearSelection: clearSelection, documentLookup, resolveBaseMetrics, bringToFront, setDraggingId, canvasSize, openOverlayForDoc, recalcVisibleDocIds, dragSettings, onDocumentActivate, markLayoutDirty, }), [ activeTagSet, bringToFront, canvasSize, closeOverlay, containerRef, draggingId, dragSettings, engine, ensureAssetUrl, ensureDocumentSize, getDocumentAsset, dragTransformsRef, handleCanvasDragLeave, handleCanvasDragOver, handleCanvasDrop, handleDocTagDrag, handleDocTagDragEnd, handleDocTagDragStart, handleDocTagPointerDown, handleNavigatorSnapshot, handleTagDragEnterDoc, handleTagDragLeaveDoc, handleTagDragOverDoc, handleTagDropOnDoc, itemRefs, items, layoutRef, layoutSnapshot, onDocumentClick, openOverlayForDoc, overlayDisplay, overlayOriginRect, overlayOriginTransform, overlayDocument, documentLookup, pendingRemovalTag, pendingTagDocId, recalcVisibleDocIds, resolveBaseMetrics, setDraggingId, selectedDocumentIds, onDocumentActivate, markLayoutDirty, tagDropTargetId, visibleDocIds, ], ); return ; }; function DesktopWorkspaceView({ engine, items, containerRef, handleCanvasDragOver, handleCanvasDragLeave, handleCanvasDrop, ensureDocumentSize, layoutSnapshot, layoutRef, itemRefs, visibleDocIds, draggingId, tagDropTargetId, pendingTagDocId, pendingRemovalTag, ensureAssetUrl, getDocumentAsset, handleNavigatorSnapshot, activeTagSet, handleTagDragEnterDoc, handleTagDragOverDoc, handleTagDragLeaveDoc, handleTagDropOnDoc, handleDocTagPointerDown, handleDocTagDragStart, handleDocTagDrag, handleDocTagDragEnd, overlayDisplay, closeOverlay, overlayOriginRect, overlayOriginTransform, overlayDocument, onDocumentClick, onDocumentStackSelect, onPromoteSelection, selectedDocumentIds, onClearSelection, documentLookup, resolveBaseMetrics, bringToFront, setDraggingId, canvasSize, openOverlayForDoc, recalcVisibleDocIds, dragSettings, onDocumentActivate, markLayoutDirty, dragTransformsRef, }: DesktopWorkspaceViewProps) { const { handlePointerDown, handlePointerMove, handlePointerUp, handlePointerCancel } = useDocumentDrag({ engine, layoutRef, dragTransformsRef, itemRefs, documentLookup, ensureDocumentSize, resolveBaseMetrics, bringToFront, setDraggingId, canvasSize, openOverlayForDoc, recalcVisibleDocIds, settings: dragSettings, containerRef, onDocumentActivate, selectedDocumentIds, markLayoutDirty, }) as { handlePointerDown: React.PointerEventHandler; handlePointerMove: React.PointerEventHandler; handlePointerUp: React.PointerEventHandler; handlePointerCancel: React.PointerEventHandler; }; const { getCardPointerHandlers, handleShellKeyDown, focusShell } = useDeskPointer({ containerRef, items, layoutRef, ensureDocumentSize, activeTagSet, handlePointerDown, handlePointerMove, handlePointerUp, handlePointerCancel, onDocumentClick, onDocumentStackSelect, onPromoteSelection, onDocumentActivate, selectedDocumentIds, openOverlayForDoc, }) as { getCardPointerHandlers: (doc: DeskDocument) => React.HTMLAttributes; handleShellKeyDown: React.KeyboardEventHandler; focusShell: () => void; }; useEffect(() => { focusShell(); }, [focusShell]); useEffect(() => { if (selectedDocumentIds.length) { focusShell(); } }, [focusShell, selectedDocumentIds.length]); const allSizesReady = items.every((doc) => Boolean(ensureDocumentSize(doc))); return ( <>
{ if (event.target === event.currentTarget) { onClearSelection(); } focusShell(); }} >
{ if (event.target === event.currentTarget) { onClearSelection(); } focusShell(); }} > {!allSizesReady ? (

Loading previews…

) : items.length === 0 ? (

No documents to show here yet. Drop files to make this space come alive.

) : ( items.map((doc, index) => { const sizeInfo = ensureDocumentSize(doc); if (!sizeInfo) { return null; } const { width: cardWidth, height: cardHeight } = sizeInfo; const docKey = doc?.id != null ? String(doc.id) : null; const dragOverride = docKey ? dragTransformsRef.current.get(docKey) : null; const layout = docKey ? layoutRef.current.get(docKey) : null; if (!dragOverride && (!layout && (!docKey || !layoutSnapshot.has(docKey)))) { return null; } const centerX = dragOverride?.centerX ?? layout?.centerX; const centerY = dragOverride?.centerY ?? layout?.centerY; if (!Number.isFinite(centerX) || !Number.isFinite(centerY)) { return null; } const resolvedCenterX = centerX as number; const resolvedCenterY = centerY as number; const rotation = dragOverride?.rotation ?? layout?.rotation ?? 0; const scale = dragOverride?.scale ?? 1; const originX = resolvedCenterX - cardWidth / 2; const originY = resolvedCenterY - cardHeight / 2; const transform = formatTransform( Math.round(originX), Math.round(originY), rotation, scale, ); const style = { transform, zIndex: layout?.z ?? 1, width: Math.round(cardWidth), height: Math.round(cardHeight), }; const shouldLoad = docKey ? visibleDocIds.has(docKey) : false; const dragging = docKey ? draggingId === docKey : false; const docTagKeys = Array.isArray(doc?.tags) ? doc.tags .map((tag) => (tag?.id != null ? String(tag.id) : null)) .filter((id): id is string => Boolean(id)) : []; const matchesFilter = activeTagSet.size === 0 || docTagKeys.some((key) => activeTagSet.has(key)); const dropActive = docKey ? tagDropTargetId === docKey : false; const dropPending = docKey ? pendingTagDocId === docKey : false; const docId = doc?.id ?? null; const isSelected = docId != null ? selectedDocumentIds.includes(docId) : false; const docTagTokens = docTagKeys.join(' '); const cardPointerHandlers = getCardPointerHandlers(doc) as React.HTMLAttributes; const registerNode = (node: HTMLDivElement | null) => { if (!docKey) { return; } if (node) { itemRefs.current.set(docKey, node); } else { itemRefs.current.delete(docKey); } }; return ( ); }) )}
); } export default DesktopWorkspace;