diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 01c0d0b..09b8205 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -334,42 +334,36 @@ const CADEditor: React.FC = ({ projectId, token, onNavigateBack setElements(merged); } - // Never overwrite local layers with empty remote layers (prevents losing initialLayers) + // Merge layers: local-first, update changed from remote, append remote-only if (collab.layers.length > 0) { - // Compare by ID set, not by index ordering. Update if any layer properties differ. const localLayerMap = new Map(layers.map(l => [l.id, l])); - 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; + const remoteLayerMap = new Map(collab.layers.map(l => [l.id, l])); + const mergedLayers = layers.map(ll => { + const remote = remoteLayerMap.get(ll.id); + return remote ? (JSON.stringify(remote) !== JSON.stringify(ll) ? remote : ll) : ll; }); - // Also check if local has layers not in remote - if (!layersChanged && layers.length !== collab.layers.length) { - layersChanged = true; + collab.layers.forEach(rl => { + if (!localLayerMap.has(rl.id)) mergedLayers.push(rl); + }); + if (mergedLayers.length !== layers.length || mergedLayers.some((ml, i) => ml !== layers[i])) { + setLayers(mergedLayers); } - if (layersChanged) setLayers(mergedLayers); } - // Never overwrite local blocks with empty remote blocks (prevents losing defaultBlocks) + // Merge blocks: local-first, update changed from remote, append remote-only if (collab.blocks.length > 0) { - // Compare by ID set, not by index ordering. Update if any block properties differ. const localBlockMap = new Map(blocks.map(b => [b.id, b])); - 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; + const remoteBlockMap = new Map(collab.blocks.map(b => [b.id, b])); + const mergedBlocks = blocks.map(lb => { + const remote = remoteBlockMap.get(lb.id); + return remote ? (JSON.stringify(remote) !== JSON.stringify(lb) ? remote : lb) : lb; }); - // Also check if local has blocks not in remote - if (!blocksChanged && blocks.length !== collab.blocks.length) { - blocksChanged = true; + collab.blocks.forEach(rb => { + if (!localBlockMap.has(rb.id)) mergedBlocks.push(rb); + }); + if (mergedBlocks.length !== blocks.length || mergedBlocks.some((mb, i) => mb !== blocks[i])) { + setBlocks(mergedBlocks); } - if (blocksChanged) setBlocks(mergedBlocks); } // Sync groups from remote → local @@ -409,6 +403,20 @@ const CADEditor: React.FC = ({ projectId, token, onNavigateBack // eslint-disable-next-line react-hooks/exhaustive-deps }, [bgConfig, collab.status]); + // Sync local → remote: push local layers to Yjs when layers change + React.useEffect(() => { + if (collab.status !== 'connected') return; + for (const layer of layers) collab.setLayer(layer); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [layers, collab.status]); + + // Sync local → remote: push local blocks to Yjs when blocks change + React.useEffect(() => { + if (collab.status !== 'connected') return; + for (const block of blocks) collab.setBlock(block); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [blocks, collab.status]); + const handleElementCreated = useCallback((el: CADElement) => { const elWithLayer = el.layerId ? el : { ...el, layerId: activeLayerId }; setElements((prev) => {