fix(frontend): remote sync improvements, save all elements, block drop backend sync (Issues #9, #10, #11)
This commit is contained in:
+61
-18
@@ -310,36 +310,66 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
|
|||||||
|
|
||||||
// Only update local state if remote data differs AND local is not ahead of remote
|
// Only update local state if remote data differs AND local is not ahead of remote
|
||||||
// (prevents race condition where local element is overwritten by stale Yjs sync)
|
// (prevents race condition where local element is overwritten by stale Yjs sync)
|
||||||
|
// Merge: update local elements with remote version when properties differ, add remote-only elements
|
||||||
const localIds = new Set(elements.map(e => e.id));
|
const localIds = new Set(elements.map(e => e.id));
|
||||||
const remoteIds = new Set(collab.elements.map(e => e.id));
|
const remoteIds = new Set(collab.elements.map(e => e.id));
|
||||||
const localHasExtra = elements.some(e => !remoteIds.has(e.id));
|
const remoteMap = new Map(collab.elements.map(e => [e.id, e]));
|
||||||
const remoteHasExtra = collab.elements.some(e => !localIds.has(e.id));
|
let changed = false;
|
||||||
// Only sync from remote if remote has elements local doesn't have AND local doesn't have unsaved elements
|
|
||||||
if (remoteHasExtra && !localHasExtra) {
|
|
||||||
setElements(collab.elements);
|
|
||||||
} else if (remoteHasExtra && localHasExtra) {
|
|
||||||
// Merge: keep local elements, update any that changed remotely
|
|
||||||
const merged = elements.map(el => {
|
const merged = elements.map(el => {
|
||||||
const remote = collab.elements.find(e => e.id === el.id);
|
const remote = remoteMap.get(el.id);
|
||||||
return remote || el;
|
if (remote && JSON.stringify(remote) !== JSON.stringify(el)) {
|
||||||
|
changed = true;
|
||||||
|
return remote;
|
||||||
|
}
|
||||||
|
return el;
|
||||||
});
|
});
|
||||||
// Add remote-only elements
|
// Add remote-only elements
|
||||||
collab.elements.forEach(el => { if (!localIds.has(el.id)) merged.push(el); });
|
collab.elements.forEach(el => {
|
||||||
|
if (!localIds.has(el.id)) {
|
||||||
|
changed = true;
|
||||||
|
merged.push(el);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (changed) {
|
||||||
setElements(merged);
|
setElements(merged);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Never overwrite local layers with empty remote layers (prevents losing initialLayers)
|
// Never overwrite local layers with empty remote layers (prevents losing initialLayers)
|
||||||
if (collab.layers.length > 0) {
|
if (collab.layers.length > 0) {
|
||||||
const sameLayers = collab.layers.length === layers.length &&
|
// Compare by ID set, not by index ordering. Update if any layer properties differ.
|
||||||
collab.layers.every((l, i) => l.id === layers[i]?.id);
|
const localLayerMap = new Map(layers.map(l => [l.id, l]));
|
||||||
if (!sameLayers) setLayers(collab.layers);
|
let layersChanged = false;
|
||||||
|
const mergedLayers = collab.layers.map(rl => {
|
||||||
|
const local = localLayerMap.get(rl.id);
|
||||||
|
if (!local || JSON.stringify(local) !== JSON.stringify(rl)) {
|
||||||
|
layersChanged = true;
|
||||||
|
}
|
||||||
|
return rl;
|
||||||
|
});
|
||||||
|
// Also check if local has layers not in remote
|
||||||
|
if (!layersChanged && layers.length !== collab.layers.length) {
|
||||||
|
layersChanged = true;
|
||||||
|
}
|
||||||
|
if (layersChanged) setLayers(mergedLayers);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Never overwrite local blocks with empty remote blocks (prevents losing defaultBlocks)
|
// Never overwrite local blocks with empty remote blocks (prevents losing defaultBlocks)
|
||||||
if (collab.blocks.length > 0) {
|
if (collab.blocks.length > 0) {
|
||||||
const sameBlocks = collab.blocks.length === blocks.length &&
|
// Compare by ID set, not by index ordering. Update if any block properties differ.
|
||||||
collab.blocks.every((b, i) => b.id === blocks[i]?.id);
|
const localBlockMap = new Map(blocks.map(b => [b.id, b]));
|
||||||
if (!sameBlocks) setBlocks(collab.blocks);
|
let blocksChanged = false;
|
||||||
|
const mergedBlocks = collab.blocks.map(rb => {
|
||||||
|
const local = localBlockMap.get(rb.id);
|
||||||
|
if (!local || JSON.stringify(local) !== JSON.stringify(rb)) {
|
||||||
|
blocksChanged = true;
|
||||||
|
}
|
||||||
|
return rb;
|
||||||
|
});
|
||||||
|
// Also check if local has blocks not in remote
|
||||||
|
if (!blocksChanged && blocks.length !== collab.blocks.length) {
|
||||||
|
blocksChanged = true;
|
||||||
|
}
|
||||||
|
if (blocksChanged) setBlocks(mergedBlocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync groups from remote → local
|
// Sync groups from remote → local
|
||||||
@@ -574,8 +604,22 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
|
|||||||
if (instance) {
|
if (instance) {
|
||||||
setElements((prev) => [...prev, instance]);
|
setElements((prev) => [...prev, instance]);
|
||||||
setCommandHistory((prev) => [...prev, { prefix: '·', text: `Block platziert: ${blockId} bei (${x.toFixed(2)}, ${y.toFixed(2)})`, type: 'info' }]);
|
setCommandHistory((prev) => [...prev, { prefix: '·', text: `Block platziert: ${blockId} bei (${x.toFixed(2)}, ${y.toFixed(2)})`, type: 'info' }]);
|
||||||
|
// Push to Yjs CRDT for real-time sync
|
||||||
|
if (collab.status === 'connected') {
|
||||||
|
collab.setElement(instance);
|
||||||
}
|
}
|
||||||
}, [blocks, activeLayerId]);
|
// Save to backend
|
||||||
|
if (drawingId && token) {
|
||||||
|
setSavedStatus('Speichert…');
|
||||||
|
createElementTyped(token, drawingId, instance).then(() => {
|
||||||
|
setSavedStatus('gespeichert');
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error('Failed to save block instance:', err);
|
||||||
|
setSavedStatus('Fehler beim Speichern');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [blocks, activeLayerId, collab, drawingId, token]);
|
||||||
|
|
||||||
const handleSelectionChange = useCallback((selectedIds: string[]) => {
|
const handleSelectionChange = useCallback((selectedIds: string[]) => {
|
||||||
if (selectedIds.length === 1) {
|
if (selectedIds.length === 1) {
|
||||||
@@ -646,7 +690,6 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
|
|||||||
// Save elements to backend if drawingId exists
|
// Save elements to backend if drawingId exists
|
||||||
if (drawingId && token) {
|
if (drawingId && token) {
|
||||||
elements.forEach((el) => {
|
elements.forEach((el) => {
|
||||||
if (!el.id.startsWith('el-')) return;
|
|
||||||
updateElement(token, el.id, el).catch(() => {});
|
updateElement(token, el.id, el).catch(() => {});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user