diff --git a/frontend/src/components/reportEditor/EditorCanvas.tsx b/frontend/src/components/reportEditor/EditorCanvas.tsx index 0894864..62d5a54 100644 --- a/frontend/src/components/reportEditor/EditorCanvas.tsx +++ b/frontend/src/components/reportEditor/EditorCanvas.tsx @@ -103,6 +103,7 @@ const EditorCanvas = forwardRef( const isTransformingRef = useRef(false); // True while user is actively dragging/scaling const guideLinesRef = useRef([]); const loadedImagesRef = useRef>(new Map()); // elementId -> loaded src + const loadingImagesRef = useRef>(new Set()); // elementIds currently being loaded const templateElementsRef = useRef(template.elements); // Ref for accessing current elements in callbacks // Keep templateElementsRef in sync @@ -1005,6 +1006,15 @@ const EditorCanvas = forwardRef( const canvas = fabricRef.current; + // Clean up loadedImagesRef for elements that no longer exist + const currentElementIds = new Set(template.elements.map((e) => e.id)); + for (const elementId of loadedImagesRef.current.keys()) { + if (!currentElementIds.has(elementId)) { + loadedImagesRef.current.delete(elementId); + loadingImagesRef.current.delete(elementId); + } + } + // Find image elements that need loading template.elements.forEach((element) => { if (element.type !== "image" || !element.imageSettings?.src) return; @@ -1053,24 +1063,55 @@ const EditorCanvas = forwardRef( return; } + // Skip if this image is already being loaded + if (loadingImagesRef.current.has(element.id)) { + return; + } + if (!existingObj) return; + // Mark as loading to prevent duplicate loads + loadingImagesRef.current.add(element.id); + + // Capture element data for async callback + const elementId = element.id; + const elementType = element.type; + // Load the image const imgElement = new Image(); imgElement.crossOrigin = "anonymous"; imgElement.onload = () => { + // Remove from loading set + loadingImagesRef.current.delete(elementId); + if (!fabricRef.current) return; - const pos = element.position; - const imgSettings = element.imageSettings!; + // Re-fetch current element data from template (might have changed during load) + const currentElement = templateElementsRef.current.find( + (el) => el.id === elementId, + ); + if (!currentElement || currentElement.type !== "image") return; + + // Re-fetch the current object from the map (might have been recreated) + const currentObj = elementsMapRef.current.get(elementId); + if (!currentObj) return; + + // Skip if already a FabricImage with correct src (another load completed first) + if ( + currentObj instanceof fabric.FabricImage && + loadedImagesRef.current.get(elementId) === currentSrc + ) { + return; + } + + const pos = currentElement.position; + const imgSettings = currentElement.imageSettings!; const targetWidth = mmToPx(pos.width) * zoom; const targetHeight = mmToPx(pos.height) * zoom; const imgNaturalWidth = imgElement.naturalWidth; const imgNaturalHeight = imgElement.naturalHeight; // Simple approach: scale image to fit the target dimensions - // The fitMode affects how we calculate initial dimensions when first adding, - // but once placed, the user's resize takes precedence const scaleX = targetWidth / imgNaturalWidth; const scaleY = targetHeight / imgNaturalHeight; @@ -1083,33 +1124,35 @@ const EditorCanvas = forwardRef( opacity: imgSettings.opacity ?? 1, flipX: imgSettings.flipHorizontal || false, flipY: imgSettings.flipVertical || false, - hasControls: !element.locked, - hasBorders: !element.locked, - lockMovementX: element.locked, - lockMovementY: element.locked, - lockRotation: element.locked, - lockScalingX: element.locked, - lockScalingY: element.locked, + hasControls: !currentElement.locked, + hasBorders: !currentElement.locked, + lockMovementX: currentElement.locked, + lockMovementY: currentElement.locked, + lockRotation: currentElement.locked, + lockScalingX: currentElement.locked, + lockScalingY: currentElement.locked, }); (fabricImage as FabricObjectWithData).data = { - id: element.id, - type: element.type, + id: elementId, + type: elementType, }; - // Remove placeholder and add real image - canvas.remove(existingObj); + // Remove the current placeholder/old image and add the new one + canvas.remove(currentObj); canvas.add(fabricImage); elementsMapRef.current.set( - element.id, + elementId, fabricImage as FabricObjectWithData, ); - loadedImagesRef.current.set(element.id, currentSrc); + loadedImagesRef.current.set(elementId, currentSrc); canvas.renderAll(); }; imgElement.onerror = () => { - console.error(`Failed to load image for element ${element.id}`); + // Remove from loading set on error too + loadingImagesRef.current.delete(elementId); + console.error(`Failed to load image for element ${elementId}`); }; imgElement.src = currentSrc; }); diff --git a/src/Apollinare.API/apollinare.db-shm b/src/Apollinare.API/apollinare.db-shm index 4c022c7..fe9ac28 100644 Binary files a/src/Apollinare.API/apollinare.db-shm and b/src/Apollinare.API/apollinare.db-shm differ diff --git a/src/Apollinare.API/apollinare.db-wal b/src/Apollinare.API/apollinare.db-wal index 803f733..e69de29 100644 Binary files a/src/Apollinare.API/apollinare.db-wal and b/src/Apollinare.API/apollinare.db-wal differ