diff --git a/lib/components/SchematicViewer.tsx b/lib/components/SchematicViewer.tsx index 51e782a..a41af5e 100644 --- a/lib/components/SchematicViewer.tsx +++ b/lib/components/SchematicViewer.tsx @@ -125,6 +125,8 @@ export const SchematicViewer = ({ }) const [isHoveringClickableComponent, setIsHoveringClickableComponent] = useState(false) + const [hoveredNetId, setHoveredNetId] = useState(null) + const hoveringComponentsRef = useRef>(new Set()) const handleComponentHoverChange = useCallback( @@ -154,6 +156,34 @@ export const SchematicViewer = ({ } }, [circuitJsonKey, circuitJson]) + const traceIdToNetId = useMemo(() => { + const map = new Map() + + try { + const soup = su(circuitJson) + const traces = soup.schematic_trace?.list?.() ?? [] + + for (const trace of traces as any[]) { + const t = trace as any + + // net id may be stored as net_id or netId + const netId = (t.net_id ?? t.netId) as string | undefined + + // schematic_trace_id is typed; id is a possible runtime alias + const traceId = + (t.schematic_trace_id as string | undefined) ?? + (t.id as string | undefined) + + if (!traceId || !netId) continue + map.set(traceId, netId) + } + } catch (err) { + console.error("Failed to derive traceIdToNetId map", err) + } + + return map + }, [circuitJsonKey, circuitJson]) + const handleTouchStart = (e: React.TouchEvent) => { const touch = e.touches[0] touchStartRef.current = { @@ -207,6 +237,7 @@ export const SchematicViewer = ({ }) const { containerWidth, containerHeight } = useResizeHandling(containerRef) + const svgString = useMemo(() => { if (!containerWidth || !containerHeight) return "" @@ -223,6 +254,33 @@ export const SchematicViewer = ({ }) }, [circuitJsonKey, containerWidth, containerHeight]) + const svgWithNetIds = useMemo(() => { + if (!svgString || typeof window === "undefined") return svgString + + try { + const doc = new DOMParser().parseFromString(svgString, "image/svg+xml") + + // Select all traces + const traces = doc.querySelectorAll("path.trace") + + // Assign each trace a net ID (fallback: use trace id itself) + traces.forEach((el, index) => { + const rawId = + el.getAttribute("data-schematic-trace-id") || + el.getAttribute("data-schematic-object-id") + + const netId = + traceIdToNetId.get(rawId || "") || rawId || `trace-${index}` + el.setAttribute("data-net-id", netId) + }) + + return new XMLSerializer().serializeToString(doc.documentElement) + } catch (err) { + console.error("Failed to add net ids", err) + return svgString + } + }, [svgString, traceIdToNetId]) + const containerBackgroundColor = useMemo(() => { const match = svgString.match( /]*style="[^"]*background-color:\s*([^;\"]+)/i, @@ -300,6 +358,54 @@ export const SchematicViewer = ({ handleComponentTouchStartRef.current = handleComponentTouchStart }, [handleComponentTouchStart]) + useEffect(() => { + const svgDiv = svgDivRef.current + if (!svgDiv) return + + const elements = Array.from( + svgDiv.querySelectorAll("[data-net-id]"), + ) + + function enter(this: SVGElement) { + const id = this.getAttribute("data-net-id") + if (id) setHoveredNetId(id) + } + + function leave(this: SVGElement) { + const id = this.getAttribute("data-net-id") + if (id) setHoveredNetId((prev) => (prev === id ? null : prev)) + } + + elements.forEach((el) => { + el.addEventListener("mouseenter", enter) + el.addEventListener("mouseleave", leave) + }) + + return () => { + elements.forEach((el) => { + el.removeEventListener("mouseenter", enter) + el.removeEventListener("mouseleave", leave) + }) + } + }, [svgWithNetIds]) + + useEffect(() => { + const svgDiv = svgDivRef.current + if (!svgDiv) return + + // Remove old highlights + svgDiv + .querySelectorAll("[data-net-id].trace-hover") + .forEach((el) => el.classList.remove("trace-hover")) + + if (!hoveredNetId) return + + // Highlight all traces sharing this net-id + svgDiv + .querySelectorAll(`[data-net-id="${hoveredNetId}"]`) + .forEach((el) => el.classList.add("trace-hover")) + }, [hoveredNetId]) + const svgDiv = useMemo( () => (
- dangerouslySetInnerHTML={{ __html: svgString }} + dangerouslySetInnerHTML={{ __html: svgWithNetIds }} /> ), [ - svgString, + svgWithNetIds, isInteractionEnabled, clickToInteractEnabled, editModeEnabled, @@ -342,6 +448,15 @@ export const SchematicViewer = ({ {`.schematic-component-clickable [data-schematic-component-id]:hover { cursor: pointer !important; }`} )} + +