@tscircuit/schematic-viewer 2.0.56 → 2.0.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
- package/.github/workflows/bun-formatcheck.yml +0 -26
- package/.github/workflows/bun-pver-release.yml +0 -59
- package/.github/workflows/bun-typecheck.yml +0 -26
- package/.github/workflows/on-merge-inform-release-tracker.yml +0 -24
- package/CLAUDE.md +0 -1
- package/biome.json +0 -56
- package/bun.lockb +0 -0
- package/cosmos.config.json +0 -3
- package/cosmos.decorator.tsx +0 -3
- package/docs/circuit-to-svg-metadata.md +0 -151
- package/docs/dragndrop-spec.md +0 -39
- package/examples/example1-resistor-and-capacitor.fixture.tsx +0 -16
- package/examples/example10-groups-view-schematic-groups.fixture.tsx +0 -76
- package/examples/example11-automatic-grouping-view-schematic-groups.fixture.tsx +0 -109
- package/examples/example12-spice-boost-converter.fixture.tsx +0 -78
- package/examples/example13-disablegroups.fixture.tsx +0 -30
- package/examples/example14-schematic-component-click.fixture.tsx +0 -46
- package/examples/example15-analog-simulation-viewer.fixture.tsx +0 -145
- package/examples/example16-no-analog-simulation.fixture.tsx +0 -13
- package/examples/example17-schematic-ports.fixture.tsx +0 -49
- package/examples/example18-live-toggle.fixture.tsx +0 -60
- package/examples/example2-small-circuit.fixture.tsx +0 -48
- package/examples/example3-small-circuit-without-debug-grid.fixture.tsx +0 -44
- package/examples/example4-reset-edit-events.fixture.tsx +0 -57
- package/examples/example5-circuit-json-rerender.fixture.tsx +0 -110
- package/examples/example6-click-to-interact.fixture.tsx +0 -36
- package/examples/example7-schematic-viewer-fix-snapping.fixture.tsx +0 -123
- package/examples/example8-color-overrides.fixture.tsx +0 -52
- package/examples/example9-spice-rc-charging-voltage-divider.fixture.tsx +0 -77
- package/index.html +0 -12
- package/lib/components/AnalogSimulationViewer.tsx +0 -300
- package/lib/components/ControlledSchematicViewer.tsx +0 -40
- package/lib/components/EditIcon.tsx +0 -46
- package/lib/components/GridIcon.tsx +0 -45
- package/lib/components/MouseTracker.tsx +0 -257
- package/lib/components/SchematicComponentMouseTarget.tsx +0 -189
- package/lib/components/SchematicPortMouseTarget.tsx +0 -224
- package/lib/components/SchematicViewer.tsx +0 -583
- package/lib/components/SpiceIcon.tsx +0 -14
- package/lib/components/SpicePlot.tsx +0 -221
- package/lib/components/SpiceSimulationIcon.tsx +0 -32
- package/lib/components/SpiceSimulationOverlay.tsx +0 -250
- package/lib/components/ViewMenu.tsx +0 -218
- package/lib/components/ViewMenuIcon.tsx +0 -47
- package/lib/dev/render-to-circuit-json.ts +0 -8
- package/lib/hooks/use-resize-handling.ts +0 -35
- package/lib/hooks/useChangeSchematicComponentLocationsInSvg.ts +0 -117
- package/lib/hooks/useChangeSchematicTracesForMovedComponents.ts +0 -121
- package/lib/hooks/useComponentDragging.ts +0 -251
- package/lib/hooks/useLocalStorage.ts +0 -63
- package/lib/hooks/useMouseEventsOverBoundingBox.ts +0 -74
- package/lib/hooks/useSchematicGroupsOverlay.ts +0 -364
- package/lib/hooks/useSpiceSimulation.ts +0 -149
- package/lib/index.ts +0 -4
- package/lib/types/edit-events.ts +0 -16
- package/lib/types/eecircuit-engine.d.ts +0 -147
- package/lib/utils/debug.ts +0 -9
- package/lib/utils/get-component-offset-due-to-events.ts +0 -43
- package/lib/utils/spice-utils.ts +0 -128
- package/lib/utils/z-index-map.ts +0 -11
- package/lib/workers/spice-simulation.worker.ts +0 -51
- package/scripts/build-worker-blob-url.ts +0 -55
- package/src/main.tsx +0 -21
- package/tsconfig.json +0 -33
- package/tsup-webworker.config.ts +0 -13
- package/vite.config.js +0 -15
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { zIndexMap } from "../utils/z-index-map"
|
|
2
|
-
|
|
3
|
-
export const ViewMenuIcon = ({
|
|
4
|
-
onClick,
|
|
5
|
-
active,
|
|
6
|
-
}: { onClick: () => void; active: boolean }) => {
|
|
7
|
-
const handleInteraction = (e: React.MouseEvent | React.TouchEvent) => {
|
|
8
|
-
e.preventDefault()
|
|
9
|
-
onClick()
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
return (
|
|
13
|
-
<div
|
|
14
|
-
onClick={handleInteraction}
|
|
15
|
-
onTouchEnd={handleInteraction}
|
|
16
|
-
title={active ? "Hide view menu" : "Show view menu"}
|
|
17
|
-
style={{
|
|
18
|
-
position: "absolute",
|
|
19
|
-
top: "16px",
|
|
20
|
-
right: "16px",
|
|
21
|
-
backgroundColor: active ? "#4CAF50" : "#fff",
|
|
22
|
-
color: active ? "#fff" : "#000",
|
|
23
|
-
padding: "8px",
|
|
24
|
-
borderRadius: "4px",
|
|
25
|
-
cursor: "pointer",
|
|
26
|
-
boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
|
|
27
|
-
display: "flex",
|
|
28
|
-
alignItems: "center",
|
|
29
|
-
gap: "4px",
|
|
30
|
-
zIndex: zIndexMap.viewMenuIcon,
|
|
31
|
-
}}
|
|
32
|
-
>
|
|
33
|
-
<svg
|
|
34
|
-
width="16"
|
|
35
|
-
height="16"
|
|
36
|
-
viewBox="0 0 24 24"
|
|
37
|
-
fill="none"
|
|
38
|
-
stroke="currentColor"
|
|
39
|
-
strokeWidth="2"
|
|
40
|
-
>
|
|
41
|
-
<circle cx="12" cy="12" r="1" />
|
|
42
|
-
<circle cx="12" cy="5" r="1" />
|
|
43
|
-
<circle cx="12" cy="19" r="1" />
|
|
44
|
-
</svg>
|
|
45
|
-
</div>
|
|
46
|
-
)
|
|
47
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import * as Core from "@tscircuit/core"
|
|
2
|
-
import type { CircuitJson } from "circuit-json"
|
|
3
|
-
|
|
4
|
-
export const renderToCircuitJson = (board: React.ReactElement) => {
|
|
5
|
-
const circuit = new Core.Circuit()
|
|
6
|
-
circuit.add(board)
|
|
7
|
-
return circuit.getCircuitJson() as CircuitJson
|
|
8
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react"
|
|
2
|
-
|
|
3
|
-
export const useResizeHandling = (
|
|
4
|
-
containerRef: React.RefObject<HTMLElement>,
|
|
5
|
-
) => {
|
|
6
|
-
const [containerWidth, setContainerWidth] = useState(0)
|
|
7
|
-
const [containerHeight, setContainerHeight] = useState(0)
|
|
8
|
-
|
|
9
|
-
useEffect(() => {
|
|
10
|
-
if (!containerRef.current) return
|
|
11
|
-
|
|
12
|
-
const updateDimensions = () => {
|
|
13
|
-
const rect = containerRef.current?.getBoundingClientRect()
|
|
14
|
-
setContainerWidth(rect?.width || 0)
|
|
15
|
-
setContainerHeight(rect?.height || 0)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Set initial dimensions
|
|
19
|
-
updateDimensions()
|
|
20
|
-
|
|
21
|
-
// Add resize listener
|
|
22
|
-
const resizeObserver = new ResizeObserver(updateDimensions)
|
|
23
|
-
resizeObserver.observe(containerRef.current)
|
|
24
|
-
|
|
25
|
-
// Fallback to window resize
|
|
26
|
-
window.addEventListener("resize", updateDimensions)
|
|
27
|
-
|
|
28
|
-
return () => {
|
|
29
|
-
resizeObserver.disconnect()
|
|
30
|
-
window.removeEventListener("resize", updateDimensions)
|
|
31
|
-
}
|
|
32
|
-
}, [])
|
|
33
|
-
|
|
34
|
-
return { containerWidth, containerHeight }
|
|
35
|
-
}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { su } from "@tscircuit/soup-util"
|
|
2
|
-
import type {
|
|
3
|
-
ManualEditEvent,
|
|
4
|
-
EditSchematicComponentLocationEventWithElement,
|
|
5
|
-
} from "lib/types/edit-events"
|
|
6
|
-
import { type Matrix, compose, applyToPoint } from "transformation-matrix"
|
|
7
|
-
import { useEffect, useRef } from "react"
|
|
8
|
-
import { getComponentOffsetDueToEvents } from "lib/utils/get-component-offset-due-to-events"
|
|
9
|
-
import type { CircuitJson } from "circuit-json"
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* This hook automatically applies the edit events to the schematic components
|
|
13
|
-
* inside the svg div.
|
|
14
|
-
*
|
|
15
|
-
* Schematic components are "<g>" elements with a "data-circuit-json-type"
|
|
16
|
-
* attribute equal to "schematic_component", these elements also have a
|
|
17
|
-
* data-schematic-component-id attribute equal to the schematic_component_id
|
|
18
|
-
*/
|
|
19
|
-
export const useChangeSchematicComponentLocationsInSvg = ({
|
|
20
|
-
svgDivRef,
|
|
21
|
-
realToSvgProjection,
|
|
22
|
-
svgToScreenProjection,
|
|
23
|
-
activeEditEvent,
|
|
24
|
-
editEvents,
|
|
25
|
-
}: {
|
|
26
|
-
svgDivRef: React.RefObject<HTMLDivElement | null>
|
|
27
|
-
realToSvgProjection: Matrix
|
|
28
|
-
svgToScreenProjection: Matrix
|
|
29
|
-
activeEditEvent: EditSchematicComponentLocationEventWithElement | null
|
|
30
|
-
editEvents: ManualEditEvent[]
|
|
31
|
-
}) => {
|
|
32
|
-
// Keep track of the last known SVG content
|
|
33
|
-
const lastSvgContentRef = useRef<string | null>(null)
|
|
34
|
-
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
const svg = svgDivRef.current
|
|
37
|
-
if (!svg) return
|
|
38
|
-
|
|
39
|
-
// Create a MutationObserver to watch for changes in the div's content
|
|
40
|
-
const observer = new MutationObserver((mutations) => {
|
|
41
|
-
// Check if the SVG content has changed
|
|
42
|
-
const currentSvgContent = svg.innerHTML
|
|
43
|
-
if (currentSvgContent !== lastSvgContentRef.current) {
|
|
44
|
-
lastSvgContentRef.current = currentSvgContent
|
|
45
|
-
|
|
46
|
-
// Apply the transforms
|
|
47
|
-
applyTransforms()
|
|
48
|
-
}
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
// Function to apply transforms to components
|
|
52
|
-
const applyTransforms = () => {
|
|
53
|
-
const componentsThatHaveBeenMoved = new Set<string>()
|
|
54
|
-
for (const event of editEvents) {
|
|
55
|
-
if (
|
|
56
|
-
"edit_event_type" in event &&
|
|
57
|
-
event.edit_event_type === "edit_schematic_component_location"
|
|
58
|
-
) {
|
|
59
|
-
componentsThatHaveBeenMoved.add(event.schematic_component_id)
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
if (activeEditEvent) {
|
|
63
|
-
componentsThatHaveBeenMoved.add(activeEditEvent.schematic_component_id)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Reset all transforms
|
|
67
|
-
const allComponents = svg.querySelectorAll(
|
|
68
|
-
'[data-circuit-json-type="schematic_component"]',
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
for (const component of Array.from(allComponents)) {
|
|
72
|
-
const schematic_component_id = component.getAttribute(
|
|
73
|
-
"data-schematic-component-id",
|
|
74
|
-
)!
|
|
75
|
-
|
|
76
|
-
const offsetMm = getComponentOffsetDueToEvents({
|
|
77
|
-
editEvents: [
|
|
78
|
-
...editEvents,
|
|
79
|
-
...(activeEditEvent ? [activeEditEvent] : []),
|
|
80
|
-
],
|
|
81
|
-
schematic_component_id,
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
const offsetPx = {
|
|
85
|
-
x: offsetMm.x * realToSvgProjection.a,
|
|
86
|
-
y: offsetMm.y * realToSvgProjection.d,
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const style: any = (component as any).style
|
|
90
|
-
style.transform = `translate(${offsetPx.x}px, ${offsetPx.y}px)`
|
|
91
|
-
if (
|
|
92
|
-
activeEditEvent?.schematic_component_id === schematic_component_id
|
|
93
|
-
) {
|
|
94
|
-
style.outline = "solid 2px rgba(255,0,0,0.5)"
|
|
95
|
-
style.outlineOffset = "5px"
|
|
96
|
-
} else if (style.outline) {
|
|
97
|
-
style.outline = ""
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Start observing the div for changes
|
|
103
|
-
observer.observe(svg, {
|
|
104
|
-
childList: true, // Watch for changes to the child elements
|
|
105
|
-
subtree: false, // Watch for changes in the entire subtree
|
|
106
|
-
characterData: false, // Watch for changes to text content
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
// Apply transforms immediately on mount or when editEvents change
|
|
110
|
-
applyTransforms()
|
|
111
|
-
|
|
112
|
-
// Cleanup function
|
|
113
|
-
return () => {
|
|
114
|
-
observer.disconnect()
|
|
115
|
-
}
|
|
116
|
-
}, [svgDivRef, editEvents, activeEditEvent]) // Dependencies remain the same
|
|
117
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react"
|
|
2
|
-
import { su } from "@tscircuit/soup-util"
|
|
3
|
-
import type { ManualEditEvent } from "../types/edit-events"
|
|
4
|
-
import type { CircuitJson } from "circuit-json"
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* This hook makes traces dashed when their connected components are being moved
|
|
8
|
-
*/
|
|
9
|
-
export const useChangeSchematicTracesForMovedComponents = ({
|
|
10
|
-
svgDivRef,
|
|
11
|
-
circuitJson,
|
|
12
|
-
activeEditEvent,
|
|
13
|
-
editEvents,
|
|
14
|
-
}: {
|
|
15
|
-
svgDivRef: React.RefObject<HTMLDivElement | null>
|
|
16
|
-
circuitJson: CircuitJson
|
|
17
|
-
activeEditEvent: ManualEditEvent | null
|
|
18
|
-
editEvents: ManualEditEvent[]
|
|
19
|
-
}) => {
|
|
20
|
-
// Keep track of the last known SVG content
|
|
21
|
-
const lastSvgContentRef = useRef<string | null>(null)
|
|
22
|
-
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
const svg = svgDivRef.current
|
|
25
|
-
if (!svg) return
|
|
26
|
-
|
|
27
|
-
const updateTraceStyles = () => {
|
|
28
|
-
// Reset all traces to solid
|
|
29
|
-
const allTraces = svg.querySelectorAll(
|
|
30
|
-
'[data-circuit-json-type="schematic_trace"] path',
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
// Reset all traces to solid
|
|
34
|
-
for (const trace of Array.from(allTraces)) {
|
|
35
|
-
trace.setAttribute("stroke-dasharray", "0")
|
|
36
|
-
;(trace as any).style.animation = ""
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// If there's an active edit event, make connected traces dashed
|
|
40
|
-
for (const editEvent of [
|
|
41
|
-
...editEvents,
|
|
42
|
-
...(activeEditEvent ? [activeEditEvent] : []),
|
|
43
|
-
]) {
|
|
44
|
-
if (
|
|
45
|
-
"schematic_component_id" in editEvent &&
|
|
46
|
-
editEvent.edit_event_type === "edit_schematic_component_location"
|
|
47
|
-
) {
|
|
48
|
-
const sch_component = su(circuitJson).schematic_component.get(
|
|
49
|
-
editEvent.schematic_component_id,
|
|
50
|
-
)
|
|
51
|
-
if (!sch_component) return
|
|
52
|
-
|
|
53
|
-
const src_ports = su(circuitJson).source_port.list({
|
|
54
|
-
source_component_id: sch_component.source_component_id,
|
|
55
|
-
})
|
|
56
|
-
const src_port_ids = new Set(src_ports.map((sp) => sp.source_port_id))
|
|
57
|
-
const src_traces = su(circuitJson)
|
|
58
|
-
.source_trace.list()
|
|
59
|
-
.filter((st) =>
|
|
60
|
-
st.connected_source_port_ids?.some((spi: string) =>
|
|
61
|
-
src_port_ids.has(spi),
|
|
62
|
-
),
|
|
63
|
-
)
|
|
64
|
-
const src_trace_ids = new Set(
|
|
65
|
-
src_traces.map((st) => st.source_trace_id),
|
|
66
|
-
)
|
|
67
|
-
const schematic_traces = su(circuitJson)
|
|
68
|
-
.schematic_trace.list()
|
|
69
|
-
.filter((st) => src_trace_ids.has(st.source_trace_id!))
|
|
70
|
-
|
|
71
|
-
// Make the connected traces dashed
|
|
72
|
-
schematic_traces.forEach((trace) => {
|
|
73
|
-
const traceElements = svg.querySelectorAll(
|
|
74
|
-
`[data-schematic-trace-id="${trace.schematic_trace_id}"] path`,
|
|
75
|
-
)
|
|
76
|
-
for (const traceElement of Array.from(traceElements)) {
|
|
77
|
-
if (traceElement.getAttribute("class")?.includes("invisible"))
|
|
78
|
-
continue
|
|
79
|
-
traceElement.setAttribute("stroke-dasharray", "20,20")
|
|
80
|
-
;(traceElement as any).style.animation =
|
|
81
|
-
"dash-animation 350ms linear infinite, pulse-animation 900ms linear infinite"
|
|
82
|
-
|
|
83
|
-
if (!svg.querySelector("style#dash-animation")) {
|
|
84
|
-
const style = document.createElement("style")
|
|
85
|
-
style.id = "dash-animation"
|
|
86
|
-
style.textContent = `
|
|
87
|
-
@keyframes dash-animation {
|
|
88
|
-
to {
|
|
89
|
-
stroke-dashoffset: -40;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
@keyframes pulse-animation {
|
|
93
|
-
0% { opacity: 0.6; }
|
|
94
|
-
50% { opacity: 0.2; }
|
|
95
|
-
100% { opacity: 0.6; }
|
|
96
|
-
}
|
|
97
|
-
`
|
|
98
|
-
svg.appendChild(style)
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
})
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Apply styles immediately
|
|
107
|
-
updateTraceStyles()
|
|
108
|
-
|
|
109
|
-
// Cleanup function
|
|
110
|
-
const observer = new MutationObserver(updateTraceStyles)
|
|
111
|
-
observer.observe(svg, {
|
|
112
|
-
childList: true, // Watch for changes to the child elements
|
|
113
|
-
subtree: false, // Watch for changes in the entire subtree
|
|
114
|
-
characterData: false, // Watch for changes to text content
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
return () => {
|
|
118
|
-
observer.disconnect()
|
|
119
|
-
}
|
|
120
|
-
}, [svgDivRef, activeEditEvent, circuitJson, editEvents])
|
|
121
|
-
}
|
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
import { su } from "@tscircuit/soup-util"
|
|
2
|
-
import Debug from "lib/utils/debug"
|
|
3
|
-
import { getComponentOffsetDueToEvents } from "lib/utils/get-component-offset-due-to-events"
|
|
4
|
-
import { useCallback, useEffect, useRef, useState } from "react"
|
|
5
|
-
import { type Matrix, compose } from "transformation-matrix"
|
|
6
|
-
import type {
|
|
7
|
-
EditSchematicComponentLocationEventWithElement,
|
|
8
|
-
ManualEditEvent,
|
|
9
|
-
} from "../types/edit-events"
|
|
10
|
-
|
|
11
|
-
const debug = Debug.extend("useComponentDragging")
|
|
12
|
-
|
|
13
|
-
export const useComponentDragging = ({
|
|
14
|
-
onEditEvent,
|
|
15
|
-
editEvents = [],
|
|
16
|
-
circuitJson,
|
|
17
|
-
cancelDrag,
|
|
18
|
-
svgToScreenProjection,
|
|
19
|
-
realToSvgProjection,
|
|
20
|
-
enabled = false,
|
|
21
|
-
snapToGrid = false,
|
|
22
|
-
}: {
|
|
23
|
-
circuitJson: any[]
|
|
24
|
-
editEvents: ManualEditEvent[]
|
|
25
|
-
/** The projection returned from use-mouse-matrix-transform, indicating zoom on svg */
|
|
26
|
-
svgToScreenProjection: Matrix
|
|
27
|
-
/** The projection returned from circuit-to-svg, mm to svg */
|
|
28
|
-
realToSvgProjection: Matrix
|
|
29
|
-
onEditEvent?: (event: ManualEditEvent) => void
|
|
30
|
-
cancelDrag?: () => void
|
|
31
|
-
enabled?: boolean
|
|
32
|
-
snapToGrid?: boolean
|
|
33
|
-
}): {
|
|
34
|
-
handleMouseDown: (e: React.MouseEvent) => void
|
|
35
|
-
handleTouchStart: (e: React.TouchEvent) => void
|
|
36
|
-
isDragging: boolean
|
|
37
|
-
activeEditEvent: EditSchematicComponentLocationEventWithElement | null
|
|
38
|
-
} => {
|
|
39
|
-
const [activeEditEvent, setActiveEditEvent] =
|
|
40
|
-
useState<EditSchematicComponentLocationEventWithElement | null>(null)
|
|
41
|
-
const realToScreenProjection = compose(
|
|
42
|
-
realToSvgProjection,
|
|
43
|
-
svgToScreenProjection,
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Drag start position in screen space
|
|
48
|
-
*/
|
|
49
|
-
const dragStartPosRef = useRef<{
|
|
50
|
-
x: number
|
|
51
|
-
y: number
|
|
52
|
-
} | null>(null)
|
|
53
|
-
|
|
54
|
-
const activeEditEventRef =
|
|
55
|
-
useRef<EditSchematicComponentLocationEventWithElement | null>(null)
|
|
56
|
-
|
|
57
|
-
// Store the latest positions of components being tracked
|
|
58
|
-
const componentPositionsRef = useRef<Map<string, { x: number; y: number }>>(
|
|
59
|
-
new Map(),
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
// Update position map with the latest positions from edit events
|
|
63
|
-
useEffect(() => {
|
|
64
|
-
// Process completed edit events to track latest positions
|
|
65
|
-
editEvents.forEach((event) => {
|
|
66
|
-
if (
|
|
67
|
-
"edit_event_type" in event &&
|
|
68
|
-
event.edit_event_type === "edit_schematic_component_location" &&
|
|
69
|
-
!event.in_progress
|
|
70
|
-
) {
|
|
71
|
-
componentPositionsRef.current.set(event.schematic_component_id, {
|
|
72
|
-
...event.new_center,
|
|
73
|
-
})
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
}, [editEvents])
|
|
77
|
-
|
|
78
|
-
const startDrag = useCallback(
|
|
79
|
-
(clientX: number, clientY: number, target: Element) => {
|
|
80
|
-
if (!enabled) return false
|
|
81
|
-
|
|
82
|
-
const componentGroup = target.closest(
|
|
83
|
-
'[data-circuit-json-type="schematic_component"]',
|
|
84
|
-
)
|
|
85
|
-
if (!componentGroup) return false
|
|
86
|
-
|
|
87
|
-
const schematic_component_id = componentGroup.getAttribute(
|
|
88
|
-
"data-schematic-component-id",
|
|
89
|
-
)
|
|
90
|
-
if (!schematic_component_id) return false
|
|
91
|
-
|
|
92
|
-
if (cancelDrag) cancelDrag()
|
|
93
|
-
|
|
94
|
-
const schematic_component = su(circuitJson).schematic_component.get(
|
|
95
|
-
schematic_component_id,
|
|
96
|
-
)
|
|
97
|
-
if (!schematic_component) return false
|
|
98
|
-
|
|
99
|
-
dragStartPosRef.current = { x: clientX, y: clientY }
|
|
100
|
-
|
|
101
|
-
let current_position: { x: number; y: number }
|
|
102
|
-
const trackedPosition = componentPositionsRef.current.get(
|
|
103
|
-
schematic_component_id,
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
if (trackedPosition) {
|
|
107
|
-
current_position = { ...trackedPosition }
|
|
108
|
-
} else {
|
|
109
|
-
const editEventOffset = getComponentOffsetDueToEvents({
|
|
110
|
-
editEvents,
|
|
111
|
-
schematic_component_id: schematic_component_id,
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
current_position = {
|
|
115
|
-
x: schematic_component.center.x + editEventOffset.x,
|
|
116
|
-
y: schematic_component.center.y + editEventOffset.y,
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
componentPositionsRef.current.set(schematic_component_id, {
|
|
120
|
-
...current_position,
|
|
121
|
-
})
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const newEditEvent: EditSchematicComponentLocationEventWithElement = {
|
|
125
|
-
edit_event_id: Math.random().toString(36).substr(2, 9),
|
|
126
|
-
edit_event_type: "edit_schematic_component_location",
|
|
127
|
-
schematic_component_id: schematic_component_id,
|
|
128
|
-
original_center: current_position,
|
|
129
|
-
new_center: { ...current_position },
|
|
130
|
-
in_progress: true,
|
|
131
|
-
created_at: Date.now(),
|
|
132
|
-
_element: componentGroup as any,
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
activeEditEventRef.current = newEditEvent
|
|
136
|
-
setActiveEditEvent(newEditEvent)
|
|
137
|
-
return true
|
|
138
|
-
},
|
|
139
|
-
[cancelDrag, enabled, circuitJson, editEvents],
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
const handleMouseDown = useCallback(
|
|
143
|
-
(e: React.MouseEvent) => {
|
|
144
|
-
startDrag(e.clientX, e.clientY, e.target as Element)
|
|
145
|
-
},
|
|
146
|
-
[startDrag],
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
const handleTouchStart = useCallback(
|
|
150
|
-
(e: React.TouchEvent) => {
|
|
151
|
-
if (e.touches.length !== 1) return
|
|
152
|
-
const touch = e.touches[0]
|
|
153
|
-
if (startDrag(touch.clientX, touch.clientY, e.target as Element)) {
|
|
154
|
-
e.preventDefault()
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
[startDrag],
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
const updateDragPosition = useCallback(
|
|
161
|
-
(clientX: number, clientY: number) => {
|
|
162
|
-
if (!activeEditEventRef.current || !dragStartPosRef.current) return
|
|
163
|
-
|
|
164
|
-
const screenDelta = {
|
|
165
|
-
x: clientX - dragStartPosRef.current.x,
|
|
166
|
-
y: clientY - dragStartPosRef.current.y,
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const mmDelta = {
|
|
170
|
-
x: screenDelta.x / realToScreenProjection.a,
|
|
171
|
-
y: screenDelta.y / realToScreenProjection.d,
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
let newCenter = {
|
|
175
|
-
x: activeEditEventRef.current.original_center.x + mmDelta.x,
|
|
176
|
-
y: activeEditEventRef.current.original_center.y + mmDelta.y,
|
|
177
|
-
}
|
|
178
|
-
if (snapToGrid) {
|
|
179
|
-
const snap = (v: number) => Math.round(v * 10) / 10
|
|
180
|
-
newCenter = { x: snap(newCenter.x), y: snap(newCenter.y) }
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const newEditEvent = {
|
|
184
|
-
...activeEditEventRef.current,
|
|
185
|
-
new_center: newCenter,
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
activeEditEventRef.current = newEditEvent
|
|
189
|
-
setActiveEditEvent(newEditEvent)
|
|
190
|
-
},
|
|
191
|
-
[realToScreenProjection, snapToGrid],
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
const handleMouseMove = useCallback(
|
|
195
|
-
(e: MouseEvent) => updateDragPosition(e.clientX, e.clientY),
|
|
196
|
-
[updateDragPosition],
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
const handleTouchMove = useCallback(
|
|
200
|
-
(e: TouchEvent) => {
|
|
201
|
-
if (e.touches.length !== 1 || !activeEditEventRef.current) return
|
|
202
|
-
e.preventDefault()
|
|
203
|
-
const touch = e.touches[0]
|
|
204
|
-
updateDragPosition(touch.clientX, touch.clientY)
|
|
205
|
-
},
|
|
206
|
-
[updateDragPosition],
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
const endDrag = useCallback(() => {
|
|
210
|
-
if (!activeEditEventRef.current) return
|
|
211
|
-
const finalEvent = {
|
|
212
|
-
...activeEditEventRef.current,
|
|
213
|
-
in_progress: false,
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
componentPositionsRef.current.set(finalEvent.schematic_component_id, {
|
|
217
|
-
...finalEvent.new_center,
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
debug("endDrag calling onEditEvent with new edit event", {
|
|
221
|
-
newEditEvent: finalEvent,
|
|
222
|
-
})
|
|
223
|
-
if (onEditEvent) onEditEvent(finalEvent)
|
|
224
|
-
activeEditEventRef.current = null
|
|
225
|
-
dragStartPosRef.current = null
|
|
226
|
-
setActiveEditEvent(null)
|
|
227
|
-
}, [onEditEvent])
|
|
228
|
-
|
|
229
|
-
const handleMouseUp = useCallback(() => endDrag(), [endDrag])
|
|
230
|
-
const handleTouchEnd = useCallback(() => endDrag(), [endDrag])
|
|
231
|
-
|
|
232
|
-
useEffect(() => {
|
|
233
|
-
window.addEventListener("mousemove", handleMouseMove)
|
|
234
|
-
window.addEventListener("mouseup", handleMouseUp)
|
|
235
|
-
window.addEventListener("touchmove", handleTouchMove, { passive: false })
|
|
236
|
-
window.addEventListener("touchend", handleTouchEnd)
|
|
237
|
-
return () => {
|
|
238
|
-
window.removeEventListener("mousemove", handleMouseMove)
|
|
239
|
-
window.removeEventListener("mouseup", handleMouseUp)
|
|
240
|
-
window.removeEventListener("touchmove", handleTouchMove)
|
|
241
|
-
window.removeEventListener("touchend", handleTouchEnd)
|
|
242
|
-
}
|
|
243
|
-
}, [handleMouseMove, handleMouseUp, handleTouchMove, handleTouchEnd])
|
|
244
|
-
|
|
245
|
-
return {
|
|
246
|
-
handleMouseDown,
|
|
247
|
-
handleTouchStart,
|
|
248
|
-
isDragging: !!activeEditEventRef.current,
|
|
249
|
-
activeEditEvent: activeEditEvent,
|
|
250
|
-
}
|
|
251
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { useCallback } from "react"
|
|
2
|
-
|
|
3
|
-
export const STORAGE_KEYS = {
|
|
4
|
-
IS_SHOWING_SCHEMATIC_GROUPS: "schematic_viewer_show_groups",
|
|
5
|
-
} as const
|
|
6
|
-
|
|
7
|
-
export const getStoredBoolean = (
|
|
8
|
-
key: string,
|
|
9
|
-
defaultValue: boolean,
|
|
10
|
-
): boolean => {
|
|
11
|
-
if (typeof window === "undefined") return defaultValue
|
|
12
|
-
try {
|
|
13
|
-
const stored = localStorage.getItem(key)
|
|
14
|
-
return stored !== null ? JSON.parse(stored) : defaultValue
|
|
15
|
-
} catch {
|
|
16
|
-
return defaultValue
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export const setStoredBoolean = (key: string, value: boolean): void => {
|
|
21
|
-
if (typeof window === "undefined") return
|
|
22
|
-
try {
|
|
23
|
-
localStorage.setItem(key, JSON.stringify(value))
|
|
24
|
-
} catch {}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export const useLocalStorage = () => {
|
|
28
|
-
const getBoolean = useCallback(
|
|
29
|
-
(key: string, defaultValue: boolean): boolean => {
|
|
30
|
-
return getStoredBoolean(key, defaultValue)
|
|
31
|
-
},
|
|
32
|
-
[],
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
const setBoolean = useCallback((key: string, value: boolean): void => {
|
|
36
|
-
setStoredBoolean(key, value)
|
|
37
|
-
}, [])
|
|
38
|
-
|
|
39
|
-
return {
|
|
40
|
-
getBoolean,
|
|
41
|
-
setBoolean,
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export const useLocalStorageValue = (key: string, defaultValue: boolean) => {
|
|
46
|
-
const { getBoolean, setBoolean } = useLocalStorage()
|
|
47
|
-
|
|
48
|
-
const getValue = useCallback(() => {
|
|
49
|
-
return getBoolean(key, defaultValue)
|
|
50
|
-
}, [getBoolean, key, defaultValue])
|
|
51
|
-
|
|
52
|
-
const setValue = useCallback(
|
|
53
|
-
(value: boolean) => {
|
|
54
|
-
setBoolean(key, value)
|
|
55
|
-
},
|
|
56
|
-
[setBoolean, key],
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
getValue,
|
|
61
|
-
setValue,
|
|
62
|
-
}
|
|
63
|
-
}
|