diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 1d9ebd9..47c55bf 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -277,10 +277,25 @@ const CADEditor: React.FC = ({ projectId, token, onNavigateBack // Skip if collab data is empty (not yet loaded) if (collab.elements.length === 0 && collab.layers.length === 0 && collab.blocks.length === 0) return; - // Only update local state if remote data differs - const sameElements = collab.elements.length === elements.length && - collab.elements.every((el, i) => el.id === elements[i]?.id); - if (!sameElements) setElements(collab.elements); + // 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) + const localIds = new Set(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 remoteHasExtra = collab.elements.some(e => !localIds.has(e.id)); + // 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 remote = collab.elements.find(e => e.id === el.id); + return remote || el; + }); + // Add remote-only elements + collab.elements.forEach(el => { if (!localIds.has(el.id)) merged.push(el); }); + setElements(merged); + } const sameLayers = collab.layers.length === layers.length && collab.layers.every((l, i) => l.id === layers[i]?.id); @@ -293,8 +308,9 @@ const CADEditor: React.FC = ({ projectId, token, onNavigateBack }, [collab.elements, collab.layers, collab.blocks, collab.status]); const handleElementCreated = useCallback((el: CADElement) => { + const elWithLayer = el.layerId ? el : { ...el, layerId: activeLayerId }; setElements((prev) => { - const newElements = [...prev, el]; + const newElements = [...prev, elWithLayer]; historyManagerRef.current.pushSnapshot({ elements: newElements, layers, blocks, groups, bgConfig, }, 'Element erstellt'); @@ -302,18 +318,18 @@ const CADEditor: React.FC = ({ projectId, token, onNavigateBack }); syncHistory(); // Push to Yjs CRDT for real-time sync - collab.setElement(el); + collab.setElement(elWithLayer); // Save to backend if (drawingId && token) { setSavedStatus('Speichert…'); - createElementTyped(token, drawingId, el).then(() => { + createElementTyped(token, drawingId, elWithLayer).then(() => { setSavedStatus('gespeichert'); }).catch((err) => { console.error('Failed to save element:', err); setSavedStatus('Fehler beim Speichern'); }); } - }, [layers, blocks, groups, bgConfig, syncHistory, drawingId, token, collab]); + }, [layers, blocks, groups, bgConfig, syncHistory, drawingId, token, collab, activeLayerId]); const handleElementsDeleted = useCallback((ids: string[]) => { setElements((prev) => { diff --git a/frontend/src/components/CanvasArea.tsx b/frontend/src/components/CanvasArea.tsx index 5a80fb5..6c216e0 100644 --- a/frontend/src/components/CanvasArea.tsx +++ b/frontend/src/components/CanvasArea.tsx @@ -29,6 +29,7 @@ const CanvasArea: React.FC = ({ const renderEngineRef = useRef(null); const interactionRef = useRef(null); const spatialIndexRef = useRef(null); + const layerManagerRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; @@ -48,6 +49,7 @@ const CanvasArea: React.FC = ({ renderEngineRef.current = renderEngine; interactionRef.current = interaction; spatialIndexRef.current = spatialIndex; + layerManagerRef.current = layerManager; interaction.setCallbacks({ onElementCreated, @@ -68,6 +70,7 @@ const CanvasArea: React.FC = ({ renderEngineRef.current = null; interactionRef.current = null; spatialIndexRef.current = null; + layerManagerRef.current = null; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -133,7 +136,12 @@ const CanvasArea: React.FC = ({ // Sync layers to LayerManager + render useEffect(() => { const renderEngine = renderEngineRef.current; + const layerManager = layerManagerRef.current; if (!renderEngine) return; + if (layerManager) { + layerManager.clear(); + layers.forEach(l => layerManager.addLayer(l)); + } renderEngine.setLayers(layers); renderEngine.render(); // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/frontend/src/interaction/index.ts b/frontend/src/interaction/index.ts index a879712..54f4cda 100644 --- a/frontend/src/interaction/index.ts +++ b/frontend/src/interaction/index.ts @@ -961,7 +961,7 @@ export class InteractionEngine { } else if (this.state.activeTool === 'revcloud' && this.state.points.length >= 2) { el = dimService.createRevCloud(this.state.points, layerId); } else { - el = { ...this.state.previewElement, id: this.generateId() }; + el = { ...this.state.previewElement, id: this.generateId(), layerId: layerId }; } this.onElementCreated?.(el);