diff --git a/lib/components/SchematicViewer.tsx b/lib/components/SchematicViewer.tsx index ab4fd20..bdf86b5 100644 --- a/lib/components/SchematicViewer.tsx +++ b/lib/components/SchematicViewer.tsx @@ -1,10 +1,13 @@ +import { su } from "@tscircuit/soup-util" +import type { CircuitJson } from "circuit-json" import { - convertCircuitJsonToSchematicSvg, type ColorOverrides, + convertCircuitJsonToSchematicSvg, } from "circuit-to-svg" -import { su } from "@tscircuit/soup-util" import { useChangeSchematicComponentLocationsInSvg } from "lib/hooks/useChangeSchematicComponentLocationsInSvg" import { useChangeSchematicTracesForMovedComponents } from "lib/hooks/useChangeSchematicTracesForMovedComponents" +import { useHighlightConnectedSchematicTracesOnHover } from "lib/hooks/useHighlightConnectedSchematicTracesOnHover" +import { getStoredBoolean, setStoredBoolean } from "lib/hooks/useLocalStorage" import { useSchematicGroupsOverlay } from "lib/hooks/useSchematicGroupsOverlay" import { enableDebug } from "lib/utils/debug" import { useCallback, useEffect, useMemo, useRef, useState } from "react" @@ -16,21 +19,19 @@ import { import { useMouseMatrixTransform } from "use-mouse-matrix-transform" import { useResizeHandling } from "../hooks/use-resize-handling" import { useComponentDragging } from "../hooks/useComponentDragging" +import { useSpiceSimulation } from "../hooks/useSpiceSimulation" import type { ManualEditEvent } from "../types/edit-events" +import { getSpiceFromCircuitJson } from "../utils/spice-utils" +import { zIndexMap } from "../utils/z-index-map" import { EditIcon } from "./EditIcon" import { GridIcon } from "./GridIcon" -import { ViewMenuIcon } from "./ViewMenuIcon" -import { ViewMenu } from "./ViewMenu" -import type { CircuitJson } from "circuit-json" -import { SpiceSimulationIcon } from "./SpiceSimulationIcon" -import { SpiceSimulationOverlay } from "./SpiceSimulationOverlay" -import { zIndexMap } from "../utils/z-index-map" -import { useSpiceSimulation } from "../hooks/useSpiceSimulation" -import { getSpiceFromCircuitJson } from "../utils/spice-utils" -import { getStoredBoolean, setStoredBoolean } from "lib/hooks/useLocalStorage" import { MouseTracker } from "./MouseTracker" import { SchematicComponentMouseTarget } from "./SchematicComponentMouseTarget" import { SchematicPortMouseTarget } from "./SchematicPortMouseTarget" +import { SpiceSimulationIcon } from "./SpiceSimulationIcon" +import { SpiceSimulationOverlay } from "./SpiceSimulationOverlay" +import { ViewMenu } from "./ViewMenu" +import { ViewMenuIcon } from "./ViewMenuIcon" interface Props { circuitJson: CircuitJson @@ -345,6 +346,12 @@ export const SchematicViewer = ({ editEvents: editEventsWithUnappliedEditEvents, }) + useHighlightConnectedSchematicTracesOnHover({ + svgDivRef, + circuitJson, + circuitJsonKey, + }) + // Add group overlays when enabled useSchematicGroupsOverlay({ svgDivRef, @@ -398,14 +405,23 @@ export const SchematicViewer = ({ {onSchematicComponentClicked && ( )} {onSchematicPortClicked && ( )} +
{ + if (!sourceTrace) return null + + if (sourceTrace.source_net_id) + return `source_net:${sourceTrace.source_net_id}` + if (sourceTrace.subcircuit_connectivity_map_key) { + return `subcircuit:${sourceTrace.subcircuit_connectivity_map_key}` + } + if (sourceTrace.connected_source_port_ids?.length) { + return `ports:${[...sourceTrace.connected_source_port_ids].sort().join("|")}` + } + + return null +} + +export const useHighlightConnectedSchematicTracesOnHover = ({ + svgDivRef, + circuitJson, + circuitJsonKey, +}: { + svgDivRef: React.RefObject + circuitJson: CircuitJson + circuitJsonKey: string +}) => { + useEffect(() => { + const svgDiv = svgDivRef.current + if (!svgDiv) return + + const soup = su(circuitJson) + const sourceTraceToNetKey = new Map() + + for (const sourceTrace of soup.source_trace.list() as any[]) { + const sourceTraceId = sourceTrace.source_trace_id + const netKey = getSourceTraceNetKey(sourceTrace) + if (sourceTraceId && netKey) { + sourceTraceToNetKey.set(sourceTraceId, netKey) + } + } + + const schematicTraceIdToNetKey = new Map() + for (const schematicTrace of soup.schematic_trace.list() as any[]) { + const schematicTraceId = schematicTrace.schematic_trace_id + const sourceTraceId = schematicTrace.source_trace_id + const netKey = sourceTraceId + ? sourceTraceToNetKey.get(sourceTraceId) + : null + + if (schematicTraceId && netKey) { + schematicTraceIdToNetKey.set(schematicTraceId, netKey) + } + } + + const traceGroups = Array.from( + svgDiv.querySelectorAll( + '[data-circuit-json-type="schematic_trace"][data-schematic-trace-id]', + ), + ) + + const getTraceNetKey = (traceGroup: SVGGElement) => { + const traceId = traceGroup.getAttribute("data-schematic-trace-id") + return traceId ? schematicTraceIdToNetKey.get(traceId) : null + } + + const clearHover = () => { + for (const traceGroup of traceGroups) { + traceGroup.classList.remove(TRACE_HOVER_CLASS) + } + } + + const handlePointerEnter = (event: Event) => { + const traceGroup = event.currentTarget as SVGGElement + const hoveredNetKey = getTraceNetKey(traceGroup) + if (!hoveredNetKey) return + + for (const candidateTraceGroup of traceGroups) { + candidateTraceGroup.classList.toggle( + TRACE_HOVER_CLASS, + getTraceNetKey(candidateTraceGroup) === hoveredNetKey, + ) + } + } + + for (const traceGroup of traceGroups) { + traceGroup.addEventListener("pointerenter", handlePointerEnter) + traceGroup.addEventListener("pointerleave", clearHover) + } + + return () => { + for (const traceGroup of traceGroups) { + traceGroup.removeEventListener("pointerenter", handlePointerEnter) + traceGroup.removeEventListener("pointerleave", clearHover) + } + } + }, [svgDivRef, circuitJson, circuitJsonKey]) +}