persistence
This commit is contained in:
@@ -583,6 +583,7 @@ const DesktopWorkspace = ({
|
||||
onClearSelection = null,
|
||||
helpOpen = false,
|
||||
onHelpClose = null,
|
||||
tenantId = null,
|
||||
}) => {
|
||||
const items = useMemo(() => (searchResults ? searchResults : documents), [documents, searchResults]);
|
||||
|
||||
@@ -591,6 +592,7 @@ const DesktopWorkspace = ({
|
||||
const layoutRef = useRef(new Map());
|
||||
const itemRefs = useRef(new Map());
|
||||
const zCounterRef = useRef(10);
|
||||
const layoutDirtyRef = useRef(false);
|
||||
|
||||
const [layoutSnapshot, setLayoutSnapshot] = useState(() => new Map());
|
||||
|
||||
@@ -626,6 +628,62 @@ const DesktopWorkspace = ({
|
||||
documentLookupRef.current = documentLookup;
|
||||
}, [documentLookup]);
|
||||
|
||||
const storageKey = useMemo(() => {
|
||||
if (!tenantId) {
|
||||
return null;
|
||||
}
|
||||
return `papercrate.desk-layout.${tenantId}`;
|
||||
}, [tenantId]);
|
||||
|
||||
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 markLayoutDirty = useCallback(() => {
|
||||
layoutDirtyRef.current = true;
|
||||
}, []);
|
||||
|
||||
const applySnapshotDimensions = useCallback((docKey, snapshot) => {
|
||||
const width = Number(snapshot?.width);
|
||||
const height = Number(snapshot?.height);
|
||||
@@ -1107,9 +1165,49 @@ const recalcVisibleDocIds = useCallback(() => {
|
||||
documentLookup,
|
||||
]);
|
||||
|
||||
const syncLayoutSnapshot = useCallback(() => {
|
||||
setLayoutSnapshot(new Map(layoutRef.current));
|
||||
}, []);
|
||||
const persistLayoutSnapshot = useCallback(
|
||||
(snapshot, force = false) => {
|
||||
if (!storageKey || typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
if (!force && !layoutDirtyRef.current) {
|
||||
return;
|
||||
}
|
||||
layoutDirtyRef.current = false;
|
||||
const payload = {};
|
||||
snapshot.forEach((entry, docId) => {
|
||||
if (!docId || !entry) {
|
||||
return;
|
||||
}
|
||||
const centerX = Number(entry.centerX);
|
||||
const centerY = Number(entry.centerY);
|
||||
if (!Number.isFinite(centerX) || !Number.isFinite(centerY)) {
|
||||
return;
|
||||
}
|
||||
payload[docId] = {
|
||||
centerX,
|
||||
centerY,
|
||||
rotation: Number.isFinite(Number(entry.rotation)) ? Number(entry.rotation) : 0,
|
||||
z: Number.isFinite(Number(entry.z)) ? Number(entry.z) : undefined,
|
||||
};
|
||||
});
|
||||
try {
|
||||
window.localStorage.setItem(storageKey, JSON.stringify(payload));
|
||||
persistedLayoutRef.current = new Map(
|
||||
Object.entries(payload).map(([id, value]) => [id, value]),
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn('[desk] Failed to persist desk layout', error);
|
||||
}
|
||||
},
|
||||
[storageKey],
|
||||
);
|
||||
|
||||
const syncLayoutSnapshot = useCallback((force = false) => {
|
||||
const snapshot = new Map(layoutRef.current);
|
||||
setLayoutSnapshot(snapshot);
|
||||
persistLayoutSnapshot(snapshot, force);
|
||||
}, [persistLayoutSnapshot]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const container = containerRef.current;
|
||||
@@ -1185,7 +1283,14 @@ const syncLayoutSnapshot = useCallback(() => {
|
||||
const minCenterY = CANVAS_PADDING + halfHeight;
|
||||
const maxCenterY = Math.max(minCenterY, canvasHeight - CANVAS_PADDING - halfHeight);
|
||||
|
||||
const existing = previous.get(doc.id);
|
||||
const docKey = doc?.id != null ? String(doc.id) : null;
|
||||
const persisted = docKey ? persistedLayoutRef.current.get(docKey) : null;
|
||||
let existing = previous.get(doc.id) || null;
|
||||
if (persisted) {
|
||||
existing = existing
|
||||
? { ...existing, ...persisted }
|
||||
: { ...persisted };
|
||||
}
|
||||
if (existing) {
|
||||
const defaultCenterX = (minCenterX + maxCenterX) / 2;
|
||||
const defaultCenterY = (minCenterY + maxCenterY) / 2;
|
||||
@@ -1258,10 +1363,11 @@ const syncLayoutSnapshot = useCallback(() => {
|
||||
if (!entry) return;
|
||||
const updated = { ...entry, z: zCounterRef.current };
|
||||
layoutRef.current.set(docId, updated);
|
||||
markLayoutDirty();
|
||||
syncLayoutSnapshot();
|
||||
recalcVisibleDocIds();
|
||||
},
|
||||
[syncLayoutSnapshot, recalcVisibleDocIds],
|
||||
[syncLayoutSnapshot, recalcVisibleDocIds, markLayoutDirty],
|
||||
);
|
||||
|
||||
const openOverlayForDoc = useCallback(
|
||||
@@ -1740,6 +1846,7 @@ const syncLayoutSnapshot = useCallback(() => {
|
||||
closeOverlay,
|
||||
overlayOriginRect,
|
||||
overlayOriginTransform,
|
||||
markLayoutDirty,
|
||||
selectedDocumentIds,
|
||||
onClearSelection,
|
||||
}),
|
||||
@@ -1789,6 +1896,7 @@ const syncLayoutSnapshot = useCallback(() => {
|
||||
selectedDocumentIds,
|
||||
onClearSelection,
|
||||
onDocumentStackSelect,
|
||||
markLayoutDirty,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user