fix: NodeGraph crashed on render — stale sourceImageB64 reference
The multi-source refactor dropped the global sourceImageB64 prop but left two references in the eyedropper-sample useEffect, which threw a ReferenceError on first render and broke the entire Pipeline view. Replaced the single-canvas eyedropper machinery with a per-Source-node canvas Map keyed by node id. The effect now iterates every Source in the graph, decodes its current `nodePreviews[id]` b64 into a hidden canvas (skipping already-decoded ones via a `_b64` cache key), and prunes entries for Sources that have been deleted. sampleColorAt() looks up the canvas matching the clicked Source's id. Verified: npm run build clean.
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user