diff --git a/frontend/src/DesktopWorkspace.jsx b/frontend/src/DesktopWorkspace.jsx index 4afc6fa..6c50014 100644 --- a/frontend/src/DesktopWorkspace.jsx +++ b/frontend/src/DesktopWorkspace.jsx @@ -24,6 +24,7 @@ import { parseTagTransferPayload, writeTagTransferData, } from './documents/tagTransfer'; +import { fetchLayoutRecords, upsertLayoutRecords } from './desk/db'; import './DesktopWorkspace.css'; const CANVAS_PADDING = 24; @@ -628,58 +629,7 @@ const DesktopWorkspace = ({ documentLookupRef.current = documentLookup; }, [documentLookup]); - const storageKey = useMemo(() => { - if (!tenantId || !viewId) { - return null; - } - const normalizedViewId = encodeURIComponent(String(viewId)); - return `papercrate.desk-layout.${tenantId}.${normalizedViewId}`; - }, [tenantId, viewId]); - - const initialPersistedLayout = useMemo(() => { - if (!storageKey || typeof window === 'undefined') { - return new Map(); - } - try { - const raw = window.localStorage.getItem(storageKey); - if (!raw) { - return new Map(); - } - const parsed = JSON.parse(raw); - if (!parsed || typeof parsed !== 'object') { - return new Map(); - } - const map = new Map(); - Object.entries(parsed).forEach(([docId, value]) => { - if (!value || typeof value !== 'object') { - return; - } - const centerX = Number(value.centerX); - const centerY = Number(value.centerY); - if (!Number.isFinite(centerX) || !Number.isFinite(centerY)) { - return; - } - const rotation = Number.isFinite(Number(value.rotation)) ? Number(value.rotation) : 0; - const z = Number.isFinite(Number(value.z)) ? Number(value.z) : undefined; - map.set(String(docId), { - centerX, - centerY, - rotation, - z, - }); - }); - return map; - } catch (error) { - console.warn('[desk] Failed to parse persisted layout', error); - return new Map(); - } - }, [storageKey]); - - const persistedLayoutRef = useRef(initialPersistedLayout); - - useEffect(() => { - persistedLayoutRef.current = initialPersistedLayout; - }, [initialPersistedLayout]); + const persistedLayoutRef = useRef(new Map()); const markLayoutDirty = useCallback(() => { layoutDirtyRef.current = true; @@ -1167,8 +1117,8 @@ const recalcVisibleDocIds = useCallback(() => { ]); const persistLayoutSnapshot = useCallback( - (snapshot, force = false) => { - if (!storageKey || typeof window === 'undefined') { + async (snapshot, force = false) => { + if (!tenantId || !viewId) { return; } if (!force && !layoutDirtyRef.current) { @@ -1190,21 +1140,25 @@ const recalcVisibleDocIds = useCallback(() => { merged.set(docId, { centerX, centerY, rotation, z }); }); - const payload = {}; + persistedLayoutRef.current = merged; + + const records = []; merged.forEach((entry, docId) => { if (!docId || !entry) { return; } - payload[docId] = entry; + records.push({ + documentId: docId, + centerX: entry.centerX, + centerY: entry.centerY, + rotation: entry.rotation ?? 0, + zIndex: entry.z ?? 0, + }); }); - try { - window.localStorage.setItem(storageKey, JSON.stringify(payload)); - persistedLayoutRef.current = merged; - } catch (error) { - console.warn('[desk] Failed to persist desk layout', error); - } + + await upsertLayoutRecords({ tenantId, viewId, entries: records }); }, - [storageKey], + [tenantId, viewId], ); const syncLayoutSnapshot = useCallback((force = false) => { @@ -1250,6 +1204,47 @@ const recalcVisibleDocIds = useCallback(() => { observer.observe(container); return () => observer.disconnect(); }, []); + useEffect(() => { + if (!tenantId || !viewId) { + return; + } + let cancelled = false; + + const loadLayouts = async () => { + const records = await fetchLayoutRecords({ tenantId, viewId }); + if (cancelled) { + return; + } + const map = new Map(); + records.forEach((record) => { + if (!record || !record.documentId) { + return; + } + map.set(String(record.documentId), { + centerX: Number(record.centerX) || 0, + centerY: Number(record.centerY) || 0, + rotation: Number(record.rotation) || 0, + z: Number(record.zIndex) || 0, + }); + }); + persistedLayoutRef.current = map; + layoutDirtyRef.current = false; + if (records.length) { + zCounterRef.current = Math.max( + zCounterRef.current, + ...records.map((r) => Number(r.zIndex) || 0), + ); + } + setDocSizeVersion((value) => value + 1); + }; + + loadLayouts(); + + return () => { + cancelled = true; + }; + }, [tenantId, viewId]); + useLayoutEffect(() => { if (!containerRef.current || !canvasSize.width || !canvasSize.height) { return; @@ -1291,9 +1286,7 @@ const recalcVisibleDocIds = useCallback(() => { const persisted = docKey ? persistedLayoutRef.current.get(docKey) : null; let existing = previous.get(doc.id) || null; if (persisted) { - existing = existing - ? { ...existing, ...persisted } - : { ...persisted }; + existing = existing ? { ...existing, ...persisted } : { ...persisted }; } if (existing) { const defaultCenterX = (minCenterX + maxCenterX) / 2; @@ -1349,7 +1342,6 @@ const recalcVisibleDocIds = useCallback(() => { syncLayoutSnapshot, recalcVisibleDocIds, ]); - useEffect(() => { recalcVisibleDocIds(); }, [recalcVisibleDocIds, items.length, canvasSize.width, canvasSize.height, docSizeVersion]);