fix: element creation fails with empty layer_id (FOREIGN KEY constraint)
Three fixes: 1. interaction/index.ts: Add layerId to created element in confirmDraw() 2. CanvasArea.tsx: Sync layers to LayerManager via layerManagerRef 3. App.tsx: Fallback to activeLayerId in handleElementCreated if layerId missing Root cause: LayerManager.activeLayerId was empty because layers were never synced from React state to the interaction layer LayerManager. New elements got layer_id="" which failed SQLite FOREIGN KEY constraint. Also includes Yjs sync race condition fix from previous commit.
This commit is contained in:
+24
-8
@@ -277,10 +277,25 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
|
|||||||
// Skip if collab data is empty (not yet loaded)
|
// Skip if collab data is empty (not yet loaded)
|
||||||
if (collab.elements.length === 0 && collab.layers.length === 0 && collab.blocks.length === 0) return;
|
if (collab.elements.length === 0 && collab.layers.length === 0 && collab.blocks.length === 0) return;
|
||||||
|
|
||||||
// Only update local state if remote data differs
|
// Only update local state if remote data differs AND local is not ahead of remote
|
||||||
const sameElements = collab.elements.length === elements.length &&
|
// (prevents race condition where local element is overwritten by stale Yjs sync)
|
||||||
collab.elements.every((el, i) => el.id === elements[i]?.id);
|
const localIds = new Set(elements.map(e => e.id));
|
||||||
if (!sameElements) setElements(collab.elements);
|
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 &&
|
const sameLayers = collab.layers.length === layers.length &&
|
||||||
collab.layers.every((l, i) => l.id === layers[i]?.id);
|
collab.layers.every((l, i) => l.id === layers[i]?.id);
|
||||||
@@ -293,8 +308,9 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
|
|||||||
}, [collab.elements, collab.layers, collab.blocks, collab.status]);
|
}, [collab.elements, collab.layers, collab.blocks, collab.status]);
|
||||||
|
|
||||||
const handleElementCreated = useCallback((el: CADElement) => {
|
const handleElementCreated = useCallback((el: CADElement) => {
|
||||||
|
const elWithLayer = el.layerId ? el : { ...el, layerId: activeLayerId };
|
||||||
setElements((prev) => {
|
setElements((prev) => {
|
||||||
const newElements = [...prev, el];
|
const newElements = [...prev, elWithLayer];
|
||||||
historyManagerRef.current.pushSnapshot({
|
historyManagerRef.current.pushSnapshot({
|
||||||
elements: newElements, layers, blocks, groups, bgConfig,
|
elements: newElements, layers, blocks, groups, bgConfig,
|
||||||
}, 'Element erstellt');
|
}, 'Element erstellt');
|
||||||
@@ -302,18 +318,18 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
|
|||||||
});
|
});
|
||||||
syncHistory();
|
syncHistory();
|
||||||
// Push to Yjs CRDT for real-time sync
|
// Push to Yjs CRDT for real-time sync
|
||||||
collab.setElement(el);
|
collab.setElement(elWithLayer);
|
||||||
// Save to backend
|
// Save to backend
|
||||||
if (drawingId && token) {
|
if (drawingId && token) {
|
||||||
setSavedStatus('Speichert…');
|
setSavedStatus('Speichert…');
|
||||||
createElementTyped(token, drawingId, el).then(() => {
|
createElementTyped(token, drawingId, elWithLayer).then(() => {
|
||||||
setSavedStatus('gespeichert');
|
setSavedStatus('gespeichert');
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.error('Failed to save element:', err);
|
console.error('Failed to save element:', err);
|
||||||
setSavedStatus('Fehler beim Speichern');
|
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[]) => {
|
const handleElementsDeleted = useCallback((ids: string[]) => {
|
||||||
setElements((prev) => {
|
setElements((prev) => {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ const CanvasArea: React.FC<CanvasAreaProps> = ({
|
|||||||
const renderEngineRef = useRef<RenderEngine | null>(null);
|
const renderEngineRef = useRef<RenderEngine | null>(null);
|
||||||
const interactionRef = useRef<InteractionEngine | null>(null);
|
const interactionRef = useRef<InteractionEngine | null>(null);
|
||||||
const spatialIndexRef = useRef<SpatialIndex | null>(null);
|
const spatialIndexRef = useRef<SpatialIndex | null>(null);
|
||||||
|
const layerManagerRef = useRef<LayerManager | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
@@ -48,6 +49,7 @@ const CanvasArea: React.FC<CanvasAreaProps> = ({
|
|||||||
renderEngineRef.current = renderEngine;
|
renderEngineRef.current = renderEngine;
|
||||||
interactionRef.current = interaction;
|
interactionRef.current = interaction;
|
||||||
spatialIndexRef.current = spatialIndex;
|
spatialIndexRef.current = spatialIndex;
|
||||||
|
layerManagerRef.current = layerManager;
|
||||||
|
|
||||||
interaction.setCallbacks({
|
interaction.setCallbacks({
|
||||||
onElementCreated,
|
onElementCreated,
|
||||||
@@ -68,6 +70,7 @@ const CanvasArea: React.FC<CanvasAreaProps> = ({
|
|||||||
renderEngineRef.current = null;
|
renderEngineRef.current = null;
|
||||||
interactionRef.current = null;
|
interactionRef.current = null;
|
||||||
spatialIndexRef.current = null;
|
spatialIndexRef.current = null;
|
||||||
|
layerManagerRef.current = null;
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
@@ -133,7 +136,12 @@ const CanvasArea: React.FC<CanvasAreaProps> = ({
|
|||||||
// Sync layers to LayerManager + render
|
// Sync layers to LayerManager + render
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const renderEngine = renderEngineRef.current;
|
const renderEngine = renderEngineRef.current;
|
||||||
|
const layerManager = layerManagerRef.current;
|
||||||
if (!renderEngine) return;
|
if (!renderEngine) return;
|
||||||
|
if (layerManager) {
|
||||||
|
layerManager.clear();
|
||||||
|
layers.forEach(l => layerManager.addLayer(l));
|
||||||
|
}
|
||||||
renderEngine.setLayers(layers);
|
renderEngine.setLayers(layers);
|
||||||
renderEngine.render();
|
renderEngine.render();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
|||||||
@@ -961,7 +961,7 @@ export class InteractionEngine {
|
|||||||
} else if (this.state.activeTool === 'revcloud' && this.state.points.length >= 2) {
|
} else if (this.state.activeTool === 'revcloud' && this.state.points.length >= 2) {
|
||||||
el = dimService.createRevCloud(this.state.points, layerId);
|
el = dimService.createRevCloud(this.state.points, layerId);
|
||||||
} else {
|
} else {
|
||||||
el = { ...this.state.previewElement, id: this.generateId() };
|
el = { ...this.state.previewElement, id: this.generateId(), layerId: layerId };
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onElementCreated?.(el);
|
this.onElementCreated?.(el);
|
||||||
|
|||||||
Reference in New Issue
Block a user