feat: Implement layout persistence for card positions and rotation, loading from and saving to the database.
This commit is contained in:
@@ -55,4 +55,5 @@ export const handleDragEnd = (store: LayoutStore, selection: string[]) => {
|
||||
card.finishDrag();
|
||||
}
|
||||
});
|
||||
store.saveLayout();
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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':
|
||||
|
||||
Reference in New Issue
Block a user