Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 29 additions & 13 deletions lib/components/SchematicViewer.tsx
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -345,6 +346,12 @@ export const SchematicViewer = ({
editEvents: editEventsWithUnappliedEditEvents,
})

useHighlightConnectedSchematicTracesOnHover({
svgDivRef,
circuitJson,
circuitJsonKey,
})

// Add group overlays when enabled
useSchematicGroupsOverlay({
svgDivRef,
Expand Down Expand Up @@ -398,14 +405,23 @@ export const SchematicViewer = ({
<MouseTracker>
{onSchematicComponentClicked && (
<style>
{`.schematic-component-clickable [data-schematic-component-id]:hover { cursor: pointer !important; }`}
{
".schematic-component-clickable [data-schematic-component-id]:hover { cursor: pointer !important; }"
}
</style>
)}
{onSchematicPortClicked && (
<style>
{`[data-schematic-port-id]:hover { cursor: pointer !important; }`}
{"[data-schematic-port-id]:hover { cursor: pointer !important; }"}
</style>
)}
<style>
{`
[data-circuit-json-type="schematic_trace"].schematic-trace-net-hover path:not(.trace-invisible-hover-outline) {
stroke: #ff8c00 !important;
}
`}
</style>
<div
ref={containerRef}
style={{
Expand Down
101 changes: 101 additions & 0 deletions lib/hooks/useHighlightConnectedSchematicTracesOnHover.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { su } from "@tscircuit/soup-util"
import type { CircuitJson } from "circuit-json"
import { useEffect } from "react"

const TRACE_HOVER_CLASS = "schematic-trace-net-hover"

const getSourceTraceNetKey = (sourceTrace: any) => {
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<HTMLDivElement | null>
circuitJson: CircuitJson
circuitJsonKey: string
}) => {
useEffect(() => {
const svgDiv = svgDivRef.current
if (!svgDiv) return

const soup = su(circuitJson)
const sourceTraceToNetKey = new Map<string, string>()

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<string, string>()
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<SVGGElement>(
'[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])
}
Loading