feat: Implement layout persistence for card positions and rotation, loading from and saving to the database.

This commit is contained in:
2025-11-26 20:24:00 +01:00
parent 6157a4f571
commit 39e3ac2a87
4 changed files with 92 additions and 6 deletions
+1
View File
@@ -55,4 +55,5 @@ export const handleDragEnd = (store: LayoutStore, selection: string[]) => {
card.finishDrag();
}
});
store.saveLayout();
};
+10
View File
@@ -54,6 +54,8 @@ export interface DesktopWorkspaceProps {
onDocumentActivate?: (doc: DeskDocument, event?: unknown) => void;
onSelectionChange?: (selectedIds: Identifier[]) => void;
onDocumentTagDrop?: (docId: Identifier, tag: any) => void;
tenantId?: Identifier | null;
viewId?: string | null;
}
// Wrapper to handle hooks per card
@@ -84,6 +86,8 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
onDocumentActivate,
onSelectionChange,
onDocumentTagDrop,
tenantId,
viewId,
}) => {
const containerRef = useRef<HTMLDivElement>(null);
@@ -99,6 +103,12 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
const layoutStore = useMemo(() => new LayoutStore(), []);
const layoutRef = useRef<Map<string, LayoutCard>>(new Map());
useEffect(() => {
if (tenantId && viewId) {
layoutStore.loadLayout(String(tenantId), viewId);
}
}, [layoutStore, tenantId, viewId]);
// Selection Context
const {
selectedDocumentIds,
+80 -5
View File
@@ -1,4 +1,5 @@
import { constrainDimensions } from './utils/layoutUtils';
import { fetchLayoutRecords, upsertLayoutRecords } from './db';
export interface LayoutCardState {
id: string;
@@ -225,6 +226,10 @@ export class LayoutStore {
items = new Map<string, LayoutCard>();
zCounter = 100;
private savedLayouts = new Map<string, { x: number, y: number, rotation: number, z: number }>();
private tenantId: string | null = null;
private viewId: string | null = null;
initialize(id: string, ref: HTMLElement | null, config: {
width: number;
height: number;
@@ -238,10 +243,27 @@ export class LayoutStore {
);
if (!card) {
// Apply defaults if not provided
const x = Math.random() * 500;
const y = Math.random() * 500;
const rotation = Math.random() * 10 - 5;
// Check for saved layout
const saved = this.savedLayouts.get(id);
let x, y, rotation, z;
if (saved) {
x = saved.x;
y = saved.y;
rotation = saved.rotation;
z = saved.z;
// Ensure zCounter is higher than any loaded z
if (z >= this.zCounter) {
this.zCounter = z + 1;
}
} else {
// Apply defaults if not provided
x = Math.random() * 500;
y = Math.random() * 500;
rotation = Math.random() * 10 - 5;
z = this.zCounter++;
}
card = new LayoutCard(id, this, {
x,
@@ -249,7 +271,7 @@ export class LayoutStore {
rotation,
width,
height,
z: this.zCounter++
z
}, ref);
this.items.set(id, card);
} else {
@@ -319,4 +341,57 @@ export class LayoutStore {
getSnapshot() {
return Array.from(this.items.values()).map(card => card.toSnapshot());
}
async loadLayout(tenantId: string, viewId: string) {
this.tenantId = tenantId;
this.viewId = viewId;
const records = await fetchLayoutRecords({ tenantId, viewId });
this.savedLayouts.clear();
let maxZ = this.zCounter;
for (const record of records) {
if (record.documentId && record.centerX !== undefined && record.centerY !== undefined) {
this.savedLayouts.set(record.documentId, {
x: record.centerX,
y: record.centerY,
rotation: record.rotation || 0,
z: record.zIndex || 0
});
if (record.zIndex && record.zIndex >= maxZ) {
maxZ = record.zIndex + 1;
}
}
}
this.zCounter = maxZ;
// Apply to existing items if any (though usually this runs before items are created)
for (const [id, card] of this.items) {
const saved = this.savedLayouts.get(id);
if (saved) {
card.update(saved);
}
}
}
async saveLayout() {
if (!this.tenantId || !this.viewId) return;
const entries = Array.from(this.items.values()).map(card => ({
documentId: card.id,
centerX: card.x,
centerY: card.y,
rotation: card.rotation,
zIndex: card.z,
updatedAt: Date.now()
}));
await upsertLayoutRecords({
tenantId: this.tenantId,
viewId: this.viewId,
entries
});
}
}
@@ -839,7 +839,7 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
switch (viewMode) {
case 'desk':
return <DesktopWorkspace {...viewProps} />;
return <DesktopWorkspace {...viewProps} tenantId={currentTenantId} viewId={deskViewId} />;
case 'grid':
return <DocumentsGrid {...viewProps} gridIconSize={gridIconSize} />;
case 'list':