diff --git a/src-frontend/src/components/NodeGraph.jsx b/src-frontend/src/components/NodeGraph.jsx index 0752f448..e0198c04 100644 --- a/src-frontend/src/components/NodeGraph.jsx +++ b/src-frontend/src/components/NodeGraph.jsx @@ -131,19 +131,34 @@ export default function NodeGraph({ graph, onChange, nodePreviews, nodeWidth = 2 const sampleNodeIdRef = useRef(null) sampleNodeIdRef.current = sampleNodeId - // Offscreen canvas holding decoded source image pixels for eyedropper sampling - const sourceSampleCanvasRef = useRef(null) + // Per-Source offscreen canvases for eyedropper sampling. Each Source + // node's preview b64 is decoded once into a hidden canvas keyed by node + // id; sampleColorAt() looks up the canvas matching the clicked Source. + const sourceSampleCanvasesRef = useRef(new Map()) useEffect(() => { - if (!sourceImageB64) { sourceSampleCanvasRef.current = null; return } - const img = new Image() - img.onload = () => { - const c = document.createElement('canvas') - c.width = img.naturalWidth; c.height = img.naturalHeight - c.getContext('2d').drawImage(img, 0, 0) - sourceSampleCanvasRef.current = c + const map = sourceSampleCanvasesRef.current + const sourceNodes = (graph?.nodes ?? []).filter(n => n.kind === 'Source') + // Drop entries for Sources that no longer exist. + for (const id of Array.from(map.keys())) { + if (!sourceNodes.some(n => n.id === id)) map.delete(id) } - img.src = `data:image/jpeg;base64,${sourceImageB64}` - }, [sourceImageB64]) + // Decode each Source's current preview b64 into a fresh canvas. + for (const n of sourceNodes) { + const b64 = nodePreviews?.[n.id] + if (!b64) { map.delete(n.id); continue } + const existing = map.get(n.id) + if (existing && existing._b64 === b64) continue // already decoded + const img = new Image() + img.onload = () => { + const c = document.createElement('canvas') + c.width = img.naturalWidth; c.height = img.naturalHeight + c.getContext('2d').drawImage(img, 0, 0) + c._b64 = b64 + map.set(n.id, c) + } + img.src = `data:image/jpeg;base64,${b64}` + } + }, [graph, nodePreviews]) // ── Wheel zoom — zoom to cursor, computed from refs to handle rapid scroll ── const onWheel = useCallback(e => { @@ -351,7 +366,7 @@ export default function NodeGraph({ graph, onChange, nodePreviews, nodeWidth = 2 // Sample a pixel from the source image at the given click position (screen coords). function sampleColorAt(e, sourceNode) { - const c = sourceSampleCanvasRef.current + const c = sourceSampleCanvasesRef.current.get(sourceNode.id) if (!c) return const r = canvasRef.current.getBoundingClientRect() const wx = (e.clientX - r.left - panRef.current.x) / zoomRef.current