@tscircuit/schematic-viewer 2.0.13 → 2.0.15
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/bun.lockb +0 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +267 -224
- package/dist/index.js.map +1 -1
- package/examples/example7-schematic-viewer-fix-snapping.fixture.tsx +121 -0
- package/lib/components/SchematicViewer.tsx +44 -16
- package/lib/dev/render-to-circuit-json.ts +2 -1
- package/lib/hooks/useComponentDragging.ts +61 -21
- package/package.json +3 -3
package/bun.lockb
CHANGED
|
Binary file
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ManualEditEvent } from '@tscircuit/props';
|
|
3
|
+
import { CircuitJson } from 'circuit-json';
|
|
3
4
|
|
|
4
5
|
interface Props {
|
|
5
|
-
circuitJson:
|
|
6
|
+
circuitJson: CircuitJson;
|
|
6
7
|
containerStyle?: React.CSSProperties;
|
|
7
8
|
editEvents?: ManualEditEvent[];
|
|
8
9
|
onEditEvent?: (event: ManualEditEvent) => void;
|
|
@@ -12,6 +13,6 @@ interface Props {
|
|
|
12
13
|
debug?: boolean;
|
|
13
14
|
clickToInteractEnabled?: boolean;
|
|
14
15
|
}
|
|
15
|
-
declare const SchematicViewer: ({ circuitJson, containerStyle, editEvents, onEditEvent, defaultEditMode, debugGrid, editingEnabled, debug, clickToInteractEnabled, }: Props) => react_jsx_runtime.JSX.Element;
|
|
16
|
+
declare const SchematicViewer: ({ circuitJson, containerStyle, editEvents: unappliedEditEvents, onEditEvent, defaultEditMode, debugGrid, editingEnabled, debug, clickToInteractEnabled, }: Props) => react_jsx_runtime.JSX.Element;
|
|
16
17
|
|
|
17
18
|
export { SchematicViewer };
|
package/dist/index.js
CHANGED
|
@@ -1,81 +1,10 @@
|
|
|
1
1
|
// lib/components/SchematicViewer.tsx
|
|
2
|
-
import { useMouseMatrixTransform } from "use-mouse-matrix-transform";
|
|
3
2
|
import { convertCircuitJsonToSchematicSvg } from "circuit-to-svg";
|
|
4
|
-
import { useMemo, useRef as useRef4, useState as useState3 } from "react";
|
|
5
|
-
|
|
6
|
-
// lib/components/EditIcon.tsx
|
|
7
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
-
var EditIcon = ({
|
|
9
|
-
onClick,
|
|
10
|
-
active
|
|
11
|
-
}) => {
|
|
12
|
-
return /* @__PURE__ */ jsx(
|
|
13
|
-
"div",
|
|
14
|
-
{
|
|
15
|
-
onClick,
|
|
16
|
-
style: {
|
|
17
|
-
position: "absolute",
|
|
18
|
-
top: "16px",
|
|
19
|
-
right: "16px",
|
|
20
|
-
backgroundColor: active ? "#4CAF50" : "#fff",
|
|
21
|
-
color: active ? "#fff" : "#000",
|
|
22
|
-
padding: "8px",
|
|
23
|
-
borderRadius: "4px",
|
|
24
|
-
cursor: "pointer",
|
|
25
|
-
boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
|
|
26
|
-
display: "flex",
|
|
27
|
-
alignItems: "center",
|
|
28
|
-
gap: "4px",
|
|
29
|
-
zIndex: 1e3
|
|
30
|
-
},
|
|
31
|
-
children: /* @__PURE__ */ jsxs(
|
|
32
|
-
"svg",
|
|
33
|
-
{
|
|
34
|
-
width: "16",
|
|
35
|
-
height: "16",
|
|
36
|
-
viewBox: "0 0 24 24",
|
|
37
|
-
fill: "none",
|
|
38
|
-
stroke: "currentColor",
|
|
39
|
-
strokeWidth: "2",
|
|
40
|
-
children: [
|
|
41
|
-
/* @__PURE__ */ jsx("path", { d: "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" }),
|
|
42
|
-
/* @__PURE__ */ jsx("path", { d: "M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" })
|
|
43
|
-
]
|
|
44
|
-
}
|
|
45
|
-
)
|
|
46
|
-
}
|
|
47
|
-
);
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
// lib/hooks/use-resize-handling.ts
|
|
51
|
-
import { useEffect, useState } from "react";
|
|
52
|
-
var useResizeHandling = (containerRef) => {
|
|
53
|
-
const [containerWidth, setContainerWidth] = useState(0);
|
|
54
|
-
const [containerHeight, setContainerHeight] = useState(0);
|
|
55
|
-
useEffect(() => {
|
|
56
|
-
if (!containerRef.current) return;
|
|
57
|
-
const updateDimensions = () => {
|
|
58
|
-
const rect = containerRef.current?.getBoundingClientRect();
|
|
59
|
-
setContainerWidth(rect?.width || 0);
|
|
60
|
-
setContainerHeight(rect?.height || 0);
|
|
61
|
-
};
|
|
62
|
-
updateDimensions();
|
|
63
|
-
const resizeObserver = new ResizeObserver(updateDimensions);
|
|
64
|
-
resizeObserver.observe(containerRef.current);
|
|
65
|
-
window.addEventListener("resize", updateDimensions);
|
|
66
|
-
return () => {
|
|
67
|
-
resizeObserver.disconnect();
|
|
68
|
-
window.removeEventListener("resize", updateDimensions);
|
|
69
|
-
};
|
|
70
|
-
}, []);
|
|
71
|
-
return { containerWidth, containerHeight };
|
|
72
|
-
};
|
|
73
3
|
|
|
74
|
-
// lib/hooks/
|
|
75
|
-
import
|
|
76
|
-
import
|
|
77
|
-
|
|
78
|
-
} from "transformation-matrix";
|
|
4
|
+
// lib/hooks/useChangeSchematicComponentLocationsInSvg.ts
|
|
5
|
+
import "@tscircuit/soup-util";
|
|
6
|
+
import "transformation-matrix";
|
|
7
|
+
import { useEffect, useRef } from "react";
|
|
79
8
|
|
|
80
9
|
// lib/utils/get-component-offset-due-to-events.ts
|
|
81
10
|
var getComponentOffsetDueToEvents = ({
|
|
@@ -99,141 +28,7 @@ var getComponentOffsetDueToEvents = ({
|
|
|
99
28
|
};
|
|
100
29
|
};
|
|
101
30
|
|
|
102
|
-
// lib/hooks/useComponentDragging.ts
|
|
103
|
-
import { su } from "@tscircuit/soup-util";
|
|
104
|
-
|
|
105
|
-
// lib/utils/debug.ts
|
|
106
|
-
import Debug from "debug";
|
|
107
|
-
var debug = Debug("schematic-viewer");
|
|
108
|
-
var enableDebug = () => {
|
|
109
|
-
Debug.enable("schematic-viewer*");
|
|
110
|
-
};
|
|
111
|
-
var debug_default = debug;
|
|
112
|
-
|
|
113
|
-
// lib/hooks/useComponentDragging.ts
|
|
114
|
-
var debug2 = debug_default.extend("useComponentDragging");
|
|
115
|
-
var useComponentDragging = ({
|
|
116
|
-
onEditEvent,
|
|
117
|
-
editEvents = [],
|
|
118
|
-
circuitJson,
|
|
119
|
-
cancelDrag,
|
|
120
|
-
svgToScreenProjection,
|
|
121
|
-
realToSvgProjection,
|
|
122
|
-
enabled = false
|
|
123
|
-
}) => {
|
|
124
|
-
const [activeEditEvent, setActiveEditEvent] = useState2(null);
|
|
125
|
-
const realToScreenProjection = compose(
|
|
126
|
-
realToSvgProjection,
|
|
127
|
-
svgToScreenProjection
|
|
128
|
-
);
|
|
129
|
-
const dragStartPosRef = useRef(null);
|
|
130
|
-
const activeEditEventRef = useRef(null);
|
|
131
|
-
const handleMouseDown = useCallback(
|
|
132
|
-
(e) => {
|
|
133
|
-
if (!enabled) return;
|
|
134
|
-
const target = e.target;
|
|
135
|
-
const componentGroup = target.closest(
|
|
136
|
-
'[data-circuit-json-type="schematic_component"]'
|
|
137
|
-
);
|
|
138
|
-
if (!componentGroup) return;
|
|
139
|
-
const schematic_component_id = componentGroup.getAttribute(
|
|
140
|
-
"data-schematic-component-id"
|
|
141
|
-
);
|
|
142
|
-
if (!schematic_component_id) return;
|
|
143
|
-
if (cancelDrag) cancelDrag();
|
|
144
|
-
const schematic_component = su(circuitJson).schematic_component.get(
|
|
145
|
-
schematic_component_id
|
|
146
|
-
);
|
|
147
|
-
if (!schematic_component) return;
|
|
148
|
-
const editEventOffset = getComponentOffsetDueToEvents({
|
|
149
|
-
editEvents,
|
|
150
|
-
schematic_component_id
|
|
151
|
-
});
|
|
152
|
-
dragStartPosRef.current = {
|
|
153
|
-
x: e.clientX,
|
|
154
|
-
y: e.clientY
|
|
155
|
-
};
|
|
156
|
-
const original_center = {
|
|
157
|
-
x: schematic_component.center.x + editEventOffset.x,
|
|
158
|
-
y: schematic_component.center.y + editEventOffset.y
|
|
159
|
-
};
|
|
160
|
-
const newEditEvent = {
|
|
161
|
-
edit_event_id: Math.random().toString(36).substr(2, 9),
|
|
162
|
-
edit_event_type: "edit_schematic_component_location",
|
|
163
|
-
schematic_component_id,
|
|
164
|
-
original_center,
|
|
165
|
-
new_center: { ...original_center },
|
|
166
|
-
in_progress: true,
|
|
167
|
-
created_at: Date.now(),
|
|
168
|
-
_element: componentGroup
|
|
169
|
-
};
|
|
170
|
-
activeEditEventRef.current = newEditEvent;
|
|
171
|
-
},
|
|
172
|
-
[cancelDrag, enabled, circuitJson]
|
|
173
|
-
);
|
|
174
|
-
const handleMouseMove = useCallback(
|
|
175
|
-
(e) => {
|
|
176
|
-
if (!activeEditEventRef.current || !dragStartPosRef.current) return;
|
|
177
|
-
const screenDelta = {
|
|
178
|
-
x: e.clientX - dragStartPosRef.current.x,
|
|
179
|
-
y: e.clientY - dragStartPosRef.current.y
|
|
180
|
-
};
|
|
181
|
-
const mmDelta = {
|
|
182
|
-
x: screenDelta.x / realToScreenProjection.a,
|
|
183
|
-
y: screenDelta.y / realToScreenProjection.d
|
|
184
|
-
};
|
|
185
|
-
const newEditEvent = {
|
|
186
|
-
...activeEditEventRef.current,
|
|
187
|
-
new_center: {
|
|
188
|
-
x: activeEditEventRef.current.original_center.x + mmDelta.x,
|
|
189
|
-
y: activeEditEventRef.current.original_center.y + mmDelta.y
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
activeEditEventRef.current = newEditEvent;
|
|
193
|
-
setActiveEditEvent(newEditEvent);
|
|
194
|
-
},
|
|
195
|
-
[realToScreenProjection]
|
|
196
|
-
);
|
|
197
|
-
const handleMouseUp = useCallback(() => {
|
|
198
|
-
if (!activeEditEventRef.current) return;
|
|
199
|
-
const finalEvent = {
|
|
200
|
-
...activeEditEventRef.current,
|
|
201
|
-
in_progress: false
|
|
202
|
-
};
|
|
203
|
-
debug2("handleMouseUp calling onEditEvent with new edit event", {
|
|
204
|
-
newEditEvent: finalEvent
|
|
205
|
-
});
|
|
206
|
-
if (onEditEvent) onEditEvent(finalEvent);
|
|
207
|
-
activeEditEventRef.current = null;
|
|
208
|
-
dragStartPosRef.current = null;
|
|
209
|
-
setActiveEditEvent(null);
|
|
210
|
-
}, [onEditEvent]);
|
|
211
|
-
useEffect2(() => {
|
|
212
|
-
window.addEventListener("mousemove", handleMouseMove);
|
|
213
|
-
window.addEventListener("mouseup", handleMouseUp);
|
|
214
|
-
return () => {
|
|
215
|
-
window.removeEventListener("mousemove", handleMouseMove);
|
|
216
|
-
window.removeEventListener("mouseup", handleMouseUp);
|
|
217
|
-
};
|
|
218
|
-
}, [handleMouseMove, handleMouseUp]);
|
|
219
|
-
return {
|
|
220
|
-
handleMouseDown,
|
|
221
|
-
isDragging: !!activeEditEventRef.current,
|
|
222
|
-
activeEditEvent
|
|
223
|
-
};
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
// lib/components/SchematicViewer.tsx
|
|
227
|
-
import {
|
|
228
|
-
identity,
|
|
229
|
-
fromString,
|
|
230
|
-
toString as transformToString
|
|
231
|
-
} from "transformation-matrix";
|
|
232
|
-
|
|
233
31
|
// lib/hooks/useChangeSchematicComponentLocationsInSvg.ts
|
|
234
|
-
import "@tscircuit/soup-util";
|
|
235
|
-
import "transformation-matrix";
|
|
236
|
-
import { useEffect as useEffect3, useRef as useRef2 } from "react";
|
|
237
32
|
var useChangeSchematicComponentLocationsInSvg = ({
|
|
238
33
|
svgDivRef,
|
|
239
34
|
realToSvgProjection,
|
|
@@ -241,8 +36,8 @@ var useChangeSchematicComponentLocationsInSvg = ({
|
|
|
241
36
|
activeEditEvent,
|
|
242
37
|
editEvents
|
|
243
38
|
}) => {
|
|
244
|
-
const lastSvgContentRef =
|
|
245
|
-
|
|
39
|
+
const lastSvgContentRef = useRef(null);
|
|
40
|
+
useEffect(() => {
|
|
246
41
|
const svg = svgDivRef.current;
|
|
247
42
|
if (!svg) return;
|
|
248
43
|
const observer = new MutationObserver((mutations) => {
|
|
@@ -306,16 +101,16 @@ var useChangeSchematicComponentLocationsInSvg = ({
|
|
|
306
101
|
};
|
|
307
102
|
|
|
308
103
|
// lib/hooks/useChangeSchematicTracesForMovedComponents.ts
|
|
309
|
-
import { useEffect as
|
|
310
|
-
import { su as
|
|
104
|
+
import { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
105
|
+
import { su as su2 } from "@tscircuit/soup-util";
|
|
311
106
|
var useChangeSchematicTracesForMovedComponents = ({
|
|
312
107
|
svgDivRef,
|
|
313
108
|
circuitJson,
|
|
314
109
|
activeEditEvent,
|
|
315
110
|
editEvents
|
|
316
111
|
}) => {
|
|
317
|
-
const lastSvgContentRef =
|
|
318
|
-
|
|
112
|
+
const lastSvgContentRef = useRef2(null);
|
|
113
|
+
useEffect2(() => {
|
|
319
114
|
const svg = svgDivRef.current;
|
|
320
115
|
if (!svg) return;
|
|
321
116
|
const updateTraceStyles = () => {
|
|
@@ -331,15 +126,15 @@ var useChangeSchematicTracesForMovedComponents = ({
|
|
|
331
126
|
...activeEditEvent ? [activeEditEvent] : []
|
|
332
127
|
]) {
|
|
333
128
|
if ("schematic_component_id" in editEvent && editEvent.edit_event_type === "edit_schematic_component_location") {
|
|
334
|
-
const sch_component =
|
|
129
|
+
const sch_component = su2(circuitJson).schematic_component.get(
|
|
335
130
|
editEvent.schematic_component_id
|
|
336
131
|
);
|
|
337
132
|
if (!sch_component) return;
|
|
338
|
-
const src_ports =
|
|
133
|
+
const src_ports = su2(circuitJson).source_port.list({
|
|
339
134
|
source_component_id: sch_component.source_component_id
|
|
340
135
|
});
|
|
341
136
|
const src_port_ids = new Set(src_ports.map((sp) => sp.source_port_id));
|
|
342
|
-
const src_traces =
|
|
137
|
+
const src_traces = su2(circuitJson).source_trace.list().filter(
|
|
343
138
|
(st) => st.connected_source_port_ids?.some(
|
|
344
139
|
(spi) => src_port_ids.has(spi)
|
|
345
140
|
)
|
|
@@ -347,7 +142,7 @@ var useChangeSchematicTracesForMovedComponents = ({
|
|
|
347
142
|
const src_trace_ids = new Set(
|
|
348
143
|
src_traces.map((st) => st.source_trace_id)
|
|
349
144
|
);
|
|
350
|
-
const schematic_traces =
|
|
145
|
+
const schematic_traces = su2(circuitJson).schematic_trace.list().filter((st) => src_trace_ids.has(st.source_trace_id));
|
|
351
146
|
schematic_traces.forEach((trace) => {
|
|
352
147
|
const traceElements = svg.querySelectorAll(
|
|
353
148
|
`[data-schematic-trace-id="${trace.schematic_trace_id}"] path`
|
|
@@ -395,12 +190,238 @@ var useChangeSchematicTracesForMovedComponents = ({
|
|
|
395
190
|
}, [svgDivRef, activeEditEvent, circuitJson, editEvents]);
|
|
396
191
|
};
|
|
397
192
|
|
|
193
|
+
// lib/utils/debug.ts
|
|
194
|
+
import Debug from "debug";
|
|
195
|
+
var debug = Debug("schematic-viewer");
|
|
196
|
+
var enableDebug = () => {
|
|
197
|
+
Debug.enable("schematic-viewer*");
|
|
198
|
+
};
|
|
199
|
+
var debug_default = debug;
|
|
200
|
+
|
|
201
|
+
// lib/components/SchematicViewer.tsx
|
|
202
|
+
import { useEffect as useEffect5, useMemo, useRef as useRef4, useState as useState3 } from "react";
|
|
203
|
+
import {
|
|
204
|
+
fromString,
|
|
205
|
+
identity,
|
|
206
|
+
toString as transformToString
|
|
207
|
+
} from "transformation-matrix";
|
|
208
|
+
import { useMouseMatrixTransform } from "use-mouse-matrix-transform";
|
|
209
|
+
|
|
210
|
+
// lib/hooks/use-resize-handling.ts
|
|
211
|
+
import { useEffect as useEffect3, useState } from "react";
|
|
212
|
+
var useResizeHandling = (containerRef) => {
|
|
213
|
+
const [containerWidth, setContainerWidth] = useState(0);
|
|
214
|
+
const [containerHeight, setContainerHeight] = useState(0);
|
|
215
|
+
useEffect3(() => {
|
|
216
|
+
if (!containerRef.current) return;
|
|
217
|
+
const updateDimensions = () => {
|
|
218
|
+
const rect = containerRef.current?.getBoundingClientRect();
|
|
219
|
+
setContainerWidth(rect?.width || 0);
|
|
220
|
+
setContainerHeight(rect?.height || 0);
|
|
221
|
+
};
|
|
222
|
+
updateDimensions();
|
|
223
|
+
const resizeObserver = new ResizeObserver(updateDimensions);
|
|
224
|
+
resizeObserver.observe(containerRef.current);
|
|
225
|
+
window.addEventListener("resize", updateDimensions);
|
|
226
|
+
return () => {
|
|
227
|
+
resizeObserver.disconnect();
|
|
228
|
+
window.removeEventListener("resize", updateDimensions);
|
|
229
|
+
};
|
|
230
|
+
}, []);
|
|
231
|
+
return { containerWidth, containerHeight };
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// lib/hooks/useComponentDragging.ts
|
|
235
|
+
import { su as su3 } from "@tscircuit/soup-util";
|
|
236
|
+
import { useCallback, useEffect as useEffect4, useRef as useRef3, useState as useState2 } from "react";
|
|
237
|
+
import {
|
|
238
|
+
compose as compose2
|
|
239
|
+
} from "transformation-matrix";
|
|
240
|
+
var debug2 = debug_default.extend("useComponentDragging");
|
|
241
|
+
var useComponentDragging = ({
|
|
242
|
+
onEditEvent,
|
|
243
|
+
editEvents = [],
|
|
244
|
+
circuitJson,
|
|
245
|
+
cancelDrag,
|
|
246
|
+
svgToScreenProjection,
|
|
247
|
+
realToSvgProjection,
|
|
248
|
+
enabled = false
|
|
249
|
+
}) => {
|
|
250
|
+
const [activeEditEvent, setActiveEditEvent] = useState2(null);
|
|
251
|
+
const realToScreenProjection = compose2(
|
|
252
|
+
realToSvgProjection,
|
|
253
|
+
svgToScreenProjection
|
|
254
|
+
);
|
|
255
|
+
const dragStartPosRef = useRef3(null);
|
|
256
|
+
const activeEditEventRef = useRef3(null);
|
|
257
|
+
const componentPositionsRef = useRef3(/* @__PURE__ */ new Map());
|
|
258
|
+
useEffect4(() => {
|
|
259
|
+
editEvents.forEach((event) => {
|
|
260
|
+
if ("edit_event_type" in event && event.edit_event_type === "edit_schematic_component_location" && !event.in_progress) {
|
|
261
|
+
componentPositionsRef.current.set(
|
|
262
|
+
event.schematic_component_id,
|
|
263
|
+
{ ...event.new_center }
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
}, [editEvents]);
|
|
268
|
+
const handleMouseDown = useCallback(
|
|
269
|
+
(e) => {
|
|
270
|
+
if (!enabled) return;
|
|
271
|
+
const target = e.target;
|
|
272
|
+
const componentGroup = target.closest(
|
|
273
|
+
'[data-circuit-json-type="schematic_component"]'
|
|
274
|
+
);
|
|
275
|
+
if (!componentGroup) return;
|
|
276
|
+
const schematic_component_id = componentGroup.getAttribute(
|
|
277
|
+
"data-schematic-component-id"
|
|
278
|
+
);
|
|
279
|
+
if (!schematic_component_id) return;
|
|
280
|
+
if (cancelDrag) cancelDrag();
|
|
281
|
+
const schematic_component = su3(circuitJson).schematic_component.get(
|
|
282
|
+
schematic_component_id
|
|
283
|
+
);
|
|
284
|
+
if (!schematic_component) return;
|
|
285
|
+
dragStartPosRef.current = {
|
|
286
|
+
x: e.clientX,
|
|
287
|
+
y: e.clientY
|
|
288
|
+
};
|
|
289
|
+
let current_position;
|
|
290
|
+
const trackedPosition = componentPositionsRef.current.get(schematic_component_id);
|
|
291
|
+
if (trackedPosition) {
|
|
292
|
+
current_position = { ...trackedPosition };
|
|
293
|
+
} else {
|
|
294
|
+
const editEventOffset = getComponentOffsetDueToEvents({
|
|
295
|
+
editEvents,
|
|
296
|
+
schematic_component_id
|
|
297
|
+
});
|
|
298
|
+
current_position = {
|
|
299
|
+
x: schematic_component.center.x + editEventOffset.x,
|
|
300
|
+
y: schematic_component.center.y + editEventOffset.y
|
|
301
|
+
};
|
|
302
|
+
componentPositionsRef.current.set(schematic_component_id, { ...current_position });
|
|
303
|
+
}
|
|
304
|
+
const newEditEvent = {
|
|
305
|
+
edit_event_id: Math.random().toString(36).substr(2, 9),
|
|
306
|
+
edit_event_type: "edit_schematic_component_location",
|
|
307
|
+
schematic_component_id,
|
|
308
|
+
original_center: current_position,
|
|
309
|
+
new_center: { ...current_position },
|
|
310
|
+
in_progress: true,
|
|
311
|
+
created_at: Date.now(),
|
|
312
|
+
_element: componentGroup
|
|
313
|
+
};
|
|
314
|
+
activeEditEventRef.current = newEditEvent;
|
|
315
|
+
setActiveEditEvent(newEditEvent);
|
|
316
|
+
},
|
|
317
|
+
[cancelDrag, enabled, circuitJson, editEvents]
|
|
318
|
+
);
|
|
319
|
+
const handleMouseMove = useCallback(
|
|
320
|
+
(e) => {
|
|
321
|
+
if (!activeEditEventRef.current || !dragStartPosRef.current) return;
|
|
322
|
+
const screenDelta = {
|
|
323
|
+
x: e.clientX - dragStartPosRef.current.x,
|
|
324
|
+
y: e.clientY - dragStartPosRef.current.y
|
|
325
|
+
};
|
|
326
|
+
const mmDelta = {
|
|
327
|
+
x: screenDelta.x / realToScreenProjection.a,
|
|
328
|
+
y: screenDelta.y / realToScreenProjection.d
|
|
329
|
+
};
|
|
330
|
+
const newEditEvent = {
|
|
331
|
+
...activeEditEventRef.current,
|
|
332
|
+
new_center: {
|
|
333
|
+
x: activeEditEventRef.current.original_center.x + mmDelta.x,
|
|
334
|
+
y: activeEditEventRef.current.original_center.y + mmDelta.y
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
activeEditEventRef.current = newEditEvent;
|
|
338
|
+
setActiveEditEvent(newEditEvent);
|
|
339
|
+
},
|
|
340
|
+
[realToScreenProjection]
|
|
341
|
+
);
|
|
342
|
+
const handleMouseUp = useCallback(() => {
|
|
343
|
+
if (!activeEditEventRef.current) return;
|
|
344
|
+
const finalEvent = {
|
|
345
|
+
...activeEditEventRef.current,
|
|
346
|
+
in_progress: false
|
|
347
|
+
};
|
|
348
|
+
componentPositionsRef.current.set(
|
|
349
|
+
finalEvent.schematic_component_id,
|
|
350
|
+
{ ...finalEvent.new_center }
|
|
351
|
+
);
|
|
352
|
+
debug2("handleMouseUp calling onEditEvent with new edit event", {
|
|
353
|
+
newEditEvent: finalEvent
|
|
354
|
+
});
|
|
355
|
+
if (onEditEvent) onEditEvent(finalEvent);
|
|
356
|
+
activeEditEventRef.current = null;
|
|
357
|
+
dragStartPosRef.current = null;
|
|
358
|
+
setActiveEditEvent(null);
|
|
359
|
+
}, [onEditEvent]);
|
|
360
|
+
useEffect4(() => {
|
|
361
|
+
window.addEventListener("mousemove", handleMouseMove);
|
|
362
|
+
window.addEventListener("mouseup", handleMouseUp);
|
|
363
|
+
return () => {
|
|
364
|
+
window.removeEventListener("mousemove", handleMouseMove);
|
|
365
|
+
window.removeEventListener("mouseup", handleMouseUp);
|
|
366
|
+
};
|
|
367
|
+
}, [handleMouseMove, handleMouseUp]);
|
|
368
|
+
return {
|
|
369
|
+
handleMouseDown,
|
|
370
|
+
isDragging: !!activeEditEventRef.current,
|
|
371
|
+
activeEditEvent
|
|
372
|
+
};
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
// lib/components/EditIcon.tsx
|
|
376
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
377
|
+
var EditIcon = ({
|
|
378
|
+
onClick,
|
|
379
|
+
active
|
|
380
|
+
}) => {
|
|
381
|
+
return /* @__PURE__ */ jsx(
|
|
382
|
+
"div",
|
|
383
|
+
{
|
|
384
|
+
onClick,
|
|
385
|
+
style: {
|
|
386
|
+
position: "absolute",
|
|
387
|
+
top: "16px",
|
|
388
|
+
right: "16px",
|
|
389
|
+
backgroundColor: active ? "#4CAF50" : "#fff",
|
|
390
|
+
color: active ? "#fff" : "#000",
|
|
391
|
+
padding: "8px",
|
|
392
|
+
borderRadius: "4px",
|
|
393
|
+
cursor: "pointer",
|
|
394
|
+
boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
|
|
395
|
+
display: "flex",
|
|
396
|
+
alignItems: "center",
|
|
397
|
+
gap: "4px",
|
|
398
|
+
zIndex: 1e3
|
|
399
|
+
},
|
|
400
|
+
children: /* @__PURE__ */ jsxs(
|
|
401
|
+
"svg",
|
|
402
|
+
{
|
|
403
|
+
width: "16",
|
|
404
|
+
height: "16",
|
|
405
|
+
viewBox: "0 0 24 24",
|
|
406
|
+
fill: "none",
|
|
407
|
+
stroke: "currentColor",
|
|
408
|
+
strokeWidth: "2",
|
|
409
|
+
children: [
|
|
410
|
+
/* @__PURE__ */ jsx("path", { d: "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" }),
|
|
411
|
+
/* @__PURE__ */ jsx("path", { d: "M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" })
|
|
412
|
+
]
|
|
413
|
+
}
|
|
414
|
+
)
|
|
415
|
+
}
|
|
416
|
+
);
|
|
417
|
+
};
|
|
418
|
+
|
|
398
419
|
// lib/components/SchematicViewer.tsx
|
|
399
420
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
400
421
|
var SchematicViewer = ({
|
|
401
422
|
circuitJson,
|
|
402
423
|
containerStyle,
|
|
403
|
-
editEvents = [],
|
|
424
|
+
editEvents: unappliedEditEvents = [],
|
|
404
425
|
onEditEvent,
|
|
405
426
|
defaultEditMode = false,
|
|
406
427
|
debugGrid = false,
|
|
@@ -416,6 +437,19 @@ var SchematicViewer = ({
|
|
|
416
437
|
!clickToInteractEnabled
|
|
417
438
|
);
|
|
418
439
|
const svgDivRef = useRef4(null);
|
|
440
|
+
const [internalEditEvents, setInternalEditEvents] = useState3([]);
|
|
441
|
+
const circuitJsonRef = useRef4(circuitJson);
|
|
442
|
+
const getCircuitHash = (circuitJson2) => {
|
|
443
|
+
return `${circuitJson2?.length || 0}_${circuitJson2?.editCount || 0}`;
|
|
444
|
+
};
|
|
445
|
+
useEffect5(() => {
|
|
446
|
+
const circuitHash = getCircuitHash(circuitJson);
|
|
447
|
+
const circuitHashRef = getCircuitHash(circuitJsonRef.current);
|
|
448
|
+
if (circuitHash !== circuitHashRef) {
|
|
449
|
+
setInternalEditEvents([]);
|
|
450
|
+
circuitJsonRef.current = circuitJson;
|
|
451
|
+
}
|
|
452
|
+
}, [circuitJson]);
|
|
419
453
|
const {
|
|
420
454
|
ref: containerRef,
|
|
421
455
|
cancelDrag,
|
|
@@ -452,20 +486,29 @@ var SchematicViewer = ({
|
|
|
452
486
|
return identity();
|
|
453
487
|
}
|
|
454
488
|
}, [svgString]);
|
|
489
|
+
const handleEditEvent = (event) => {
|
|
490
|
+
setInternalEditEvents((prev) => [...prev, event]);
|
|
491
|
+
if (onEditEvent) {
|
|
492
|
+
onEditEvent(event);
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
const editEventsWithUnappliedEditEvents = useMemo(() => {
|
|
496
|
+
return [...unappliedEditEvents, ...internalEditEvents];
|
|
497
|
+
}, [unappliedEditEvents, internalEditEvents]);
|
|
455
498
|
const { handleMouseDown, isDragging, activeEditEvent } = useComponentDragging(
|
|
456
499
|
{
|
|
457
|
-
onEditEvent,
|
|
500
|
+
onEditEvent: handleEditEvent,
|
|
458
501
|
cancelDrag,
|
|
459
502
|
realToSvgProjection,
|
|
460
503
|
svgToScreenProjection,
|
|
461
504
|
circuitJson,
|
|
462
|
-
editEvents,
|
|
505
|
+
editEvents: editEventsWithUnappliedEditEvents,
|
|
463
506
|
enabled: editModeEnabled && isInteractionEnabled
|
|
464
507
|
}
|
|
465
508
|
);
|
|
466
509
|
useChangeSchematicComponentLocationsInSvg({
|
|
467
510
|
svgDivRef,
|
|
468
|
-
editEvents,
|
|
511
|
+
editEvents: editEventsWithUnappliedEditEvents,
|
|
469
512
|
realToSvgProjection,
|
|
470
513
|
svgToScreenProjection,
|
|
471
514
|
activeEditEvent
|
|
@@ -474,7 +517,7 @@ var SchematicViewer = ({
|
|
|
474
517
|
svgDivRef,
|
|
475
518
|
circuitJson,
|
|
476
519
|
activeEditEvent,
|
|
477
|
-
editEvents
|
|
520
|
+
editEvents: editEventsWithUnappliedEditEvents
|
|
478
521
|
});
|
|
479
522
|
const svgDiv = useMemo(
|
|
480
523
|
() => /* @__PURE__ */ jsx2(
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../lib/components/SchematicViewer.tsx","../lib/components/EditIcon.tsx","../lib/hooks/use-resize-handling.ts","../lib/hooks/useComponentDragging.ts","../lib/utils/get-component-offset-due-to-events.ts","../lib/utils/debug.ts","../lib/hooks/useChangeSchematicComponentLocationsInSvg.ts","../lib/hooks/useChangeSchematicTracesForMovedComponents.ts"],"sourcesContent":["import { useMouseMatrixTransform } from \"use-mouse-matrix-transform\"\nimport { convertCircuitJsonToSchematicSvg } from \"circuit-to-svg\"\nimport { useMemo, useRef, useState } from \"react\"\nimport { EditIcon } from \"./EditIcon\"\nimport { useResizeHandling } from \"../hooks/use-resize-handling\"\nimport { useComponentDragging } from \"../hooks/useComponentDragging\"\nimport type { ManualEditEvent } from \"../types/edit-events\"\nimport {\n identity,\n fromString,\n toString as transformToString,\n} from \"transformation-matrix\"\nimport { useChangeSchematicComponentLocationsInSvg } from \"lib/hooks/useChangeSchematicComponentLocationsInSvg\"\nimport { useChangeSchematicTracesForMovedComponents } from \"lib/hooks/useChangeSchematicTracesForMovedComponents\"\nimport type { CircuitJson } from \"circuit-json\"\nimport { enableDebug } from \"lib/utils/debug\"\n\ninterface Props {\n circuitJson: any[]\n containerStyle?: React.CSSProperties\n editEvents?: ManualEditEvent[]\n onEditEvent?: (event: ManualEditEvent) => void\n defaultEditMode?: boolean\n debugGrid?: boolean\n editingEnabled?: boolean\n debug?: boolean\n clickToInteractEnabled?: boolean\n}\n\nexport const SchematicViewer = ({\n circuitJson,\n containerStyle,\n editEvents = [],\n onEditEvent,\n defaultEditMode = false,\n debugGrid = false,\n editingEnabled = false,\n debug = false,\n clickToInteractEnabled = false,\n}: Props) => {\n if (debug) {\n enableDebug()\n }\n const [editModeEnabled, setEditModeEnabled] = useState(defaultEditMode)\n const [isInteractionEnabled, setIsInteractionEnabled] = useState<boolean>(\n !clickToInteractEnabled,\n )\n const svgDivRef = useRef<HTMLDivElement>(null)\n\n const {\n ref: containerRef,\n cancelDrag,\n transform: svgToScreenProjection,\n } = useMouseMatrixTransform({\n onSetTransform(transform) {\n if (!svgDivRef.current) return\n svgDivRef.current.style.transform = transformToString(transform)\n },\n // @ts-ignore disabled is a valid prop but not typed\n enabled: isInteractionEnabled,\n })\n\n const { containerWidth, containerHeight } = useResizeHandling(containerRef)\n const svgString = useMemo(() => {\n if (!containerWidth || !containerHeight) return \"\"\n\n return convertCircuitJsonToSchematicSvg(circuitJson as any, {\n width: containerWidth,\n height: containerHeight || 720,\n grid: !debugGrid\n ? undefined\n : {\n cellSize: 1,\n labelCells: true,\n },\n })\n }, [circuitJson, containerWidth, containerHeight])\n\n const realToSvgProjection = useMemo(() => {\n if (!svgString) return identity()\n const transformString = svgString.match(\n /data-real-to-screen-transform=\"([^\"]+)\"/,\n )?.[1]!\n\n try {\n return fromString(transformString)\n } catch (e) {\n console.error(e)\n return identity()\n }\n }, [svgString])\n\n const { handleMouseDown, isDragging, activeEditEvent } = useComponentDragging(\n {\n onEditEvent,\n cancelDrag,\n realToSvgProjection,\n svgToScreenProjection,\n circuitJson,\n editEvents,\n enabled: editModeEnabled && isInteractionEnabled,\n },\n )\n\n useChangeSchematicComponentLocationsInSvg({\n svgDivRef,\n editEvents,\n realToSvgProjection,\n svgToScreenProjection,\n activeEditEvent,\n })\n\n useChangeSchematicTracesForMovedComponents({\n svgDivRef,\n circuitJson,\n activeEditEvent,\n editEvents,\n })\n\n const svgDiv = useMemo(\n () => (\n <div\n ref={svgDivRef}\n style={{\n pointerEvents: clickToInteractEnabled\n ? isInteractionEnabled\n ? \"auto\"\n : \"none\"\n : \"auto\",\n transformOrigin: \"0 0\",\n }}\n // biome-ignore lint/security/noDangerouslySetInnerHtml: <explanation>\n dangerouslySetInnerHTML={{ __html: svgString }}\n />\n ),\n [svgString, isInteractionEnabled, clickToInteractEnabled],\n )\n\n return (\n <div\n ref={containerRef}\n style={{\n position: \"relative\",\n backgroundColor: \"#F5F1ED\",\n overflow: \"hidden\",\n cursor: isDragging\n ? \"grabbing\"\n : clickToInteractEnabled && !isInteractionEnabled\n ? \"pointer\"\n : \"grab\",\n minHeight: \"300px\",\n ...containerStyle,\n }}\n onMouseDown={(e) => {\n if (clickToInteractEnabled && !isInteractionEnabled) {\n e.preventDefault()\n e.stopPropagation()\n return\n }\n handleMouseDown(e)\n }}\n onMouseDownCapture={(e) => {\n if (clickToInteractEnabled && !isInteractionEnabled) {\n e.preventDefault()\n e.stopPropagation()\n return\n }\n }}\n >\n {!isInteractionEnabled && clickToInteractEnabled && (\n <div\n onClick={(e) => {\n e.preventDefault()\n e.stopPropagation()\n setIsInteractionEnabled(true)\n }}\n style={{\n position: \"absolute\",\n inset: 0,\n cursor: \"pointer\",\n zIndex: 10,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n pointerEvents: \"all\",\n }}\n >\n <div\n style={{\n backgroundColor: \"rgba(0, 0, 0, 0.8)\",\n color: \"white\",\n padding: \"12px 24px\",\n borderRadius: \"8px\",\n fontSize: \"16px\",\n fontFamily: \"sans-serif\",\n pointerEvents: \"none\",\n }}\n >\n Click to Interact\n </div>\n </div>\n )}\n {editingEnabled && (\n <EditIcon\n active={editModeEnabled}\n onClick={() => setEditModeEnabled(!editModeEnabled)}\n />\n )}\n {svgDiv}\n </div>\n )\n}\n","export const EditIcon = ({\n onClick,\n active,\n}: { onClick: () => void; active: boolean }) => {\n return (\n <div\n onClick={onClick}\n style={{\n position: \"absolute\",\n top: \"16px\",\n right: \"16px\",\n backgroundColor: active ? \"#4CAF50\" : \"#fff\",\n color: active ? \"#fff\" : \"#000\",\n padding: \"8px\",\n borderRadius: \"4px\",\n cursor: \"pointer\",\n boxShadow: \"0 2px 4px rgba(0,0,0,0.1)\",\n display: \"flex\",\n alignItems: \"center\",\n gap: \"4px\",\n zIndex: 1000,\n }}\n >\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n >\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\" />\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\" />\n </svg>\n </div>\n )\n}\n","import { useEffect, useState } from \"react\"\n\nexport const useResizeHandling = (\n containerRef: React.RefObject<HTMLElement>,\n) => {\n const [containerWidth, setContainerWidth] = useState(0)\n const [containerHeight, setContainerHeight] = useState(0)\n\n useEffect(() => {\n if (!containerRef.current) return\n\n const updateDimensions = () => {\n const rect = containerRef.current?.getBoundingClientRect()\n setContainerWidth(rect?.width || 0)\n setContainerHeight(rect?.height || 0)\n }\n\n // Set initial dimensions\n updateDimensions()\n\n // Add resize listener\n const resizeObserver = new ResizeObserver(updateDimensions)\n resizeObserver.observe(containerRef.current)\n\n // Fallback to window resize\n window.addEventListener(\"resize\", updateDimensions)\n\n return () => {\n resizeObserver.disconnect()\n window.removeEventListener(\"resize\", updateDimensions)\n }\n }, [])\n\n return { containerWidth, containerHeight }\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\"\nimport type {\n EditSchematicComponentLocationEventWithElement,\n ManualEditEvent,\n} from \"../types/edit-events\"\nimport {\n type Matrix,\n applyToPoint,\n inverse,\n compose,\n} from \"transformation-matrix\"\nimport { getComponentOffsetDueToEvents } from \"lib/utils/get-component-offset-due-to-events\"\nimport type { CircuitJson } from \"circuit-json\"\nimport { su } from \"@tscircuit/soup-util\"\nimport Debug from \"lib/utils/debug\"\n\nconst debug = Debug.extend(\"useComponentDragging\")\n\nexport const useComponentDragging = ({\n onEditEvent,\n editEvents = [],\n circuitJson,\n cancelDrag,\n svgToScreenProjection,\n realToSvgProjection,\n enabled = false,\n}: {\n circuitJson: any[]\n editEvents: ManualEditEvent[]\n /** The projection returned from use-mouse-matrix-transform, indicating zoom on svg */\n svgToScreenProjection: Matrix\n /** The projection returned from circuit-to-svg, mm to svg */\n realToSvgProjection: Matrix\n onEditEvent?: (event: ManualEditEvent) => void\n cancelDrag?: () => void\n enabled?: boolean\n}): {\n handleMouseDown: (e: React.MouseEvent) => void\n isDragging: boolean\n activeEditEvent: EditSchematicComponentLocationEventWithElement | null\n} => {\n const [activeEditEvent, setActiveEditEvent] =\n useState<EditSchematicComponentLocationEventWithElement | null>(null)\n const realToScreenProjection = compose(\n realToSvgProjection,\n svgToScreenProjection,\n )\n\n /**\n * Drag start position in screen space\n */\n const dragStartPosRef = useRef<{\n x: number\n y: number\n } | null>(null)\n\n const activeEditEventRef =\n useRef<EditSchematicComponentLocationEventWithElement | null>(null)\n\n const handleMouseDown = useCallback(\n (e: React.MouseEvent) => {\n if (!enabled) return\n\n const target = e.target as SVGElement\n const componentGroup = target.closest(\n '[data-circuit-json-type=\"schematic_component\"]',\n )\n if (!componentGroup) return\n\n const schematic_component_id = componentGroup.getAttribute(\n \"data-schematic-component-id\",\n )\n if (!schematic_component_id) return\n\n if (cancelDrag) cancelDrag()\n\n const schematic_component = su(circuitJson).schematic_component.get(\n schematic_component_id,\n )\n if (!schematic_component) return\n const editEventOffset = getComponentOffsetDueToEvents({\n editEvents,\n schematic_component_id: schematic_component_id,\n })\n\n dragStartPosRef.current = {\n x: e.clientX,\n y: e.clientY,\n }\n\n const original_center = {\n x: schematic_component.center.x + editEventOffset.x,\n y: schematic_component.center.y + editEventOffset.y,\n }\n\n const newEditEvent: EditSchematicComponentLocationEventWithElement = {\n edit_event_id: Math.random().toString(36).substr(2, 9),\n edit_event_type: \"edit_schematic_component_location\",\n schematic_component_id: schematic_component_id,\n original_center,\n new_center: { ...original_center },\n in_progress: true,\n created_at: Date.now(),\n _element: componentGroup as any,\n }\n\n activeEditEventRef.current = newEditEvent\n },\n [cancelDrag, enabled, circuitJson],\n )\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (!activeEditEventRef.current || !dragStartPosRef.current) return\n\n const screenDelta = {\n x: e.clientX - dragStartPosRef.current.x,\n y: e.clientY - dragStartPosRef.current.y,\n }\n\n const mmDelta = {\n x: screenDelta.x / realToScreenProjection.a,\n y: screenDelta.y / realToScreenProjection.d,\n }\n\n const newEditEvent = {\n ...activeEditEventRef.current,\n new_center: {\n x: activeEditEventRef.current.original_center.x + mmDelta.x,\n y: activeEditEventRef.current.original_center.y + mmDelta.y,\n },\n }\n\n activeEditEventRef.current = newEditEvent\n setActiveEditEvent(newEditEvent)\n },\n [realToScreenProjection],\n )\n\n const handleMouseUp = useCallback(() => {\n if (!activeEditEventRef.current) return\n const finalEvent = {\n ...activeEditEventRef.current,\n in_progress: false,\n }\n debug(\"handleMouseUp calling onEditEvent with new edit event\", {\n newEditEvent: finalEvent,\n })\n if (onEditEvent) onEditEvent(finalEvent)\n activeEditEventRef.current = null\n dragStartPosRef.current = null\n setActiveEditEvent(null)\n }, [onEditEvent])\n\n useEffect(() => {\n window.addEventListener(\"mousemove\", handleMouseMove)\n window.addEventListener(\"mouseup\", handleMouseUp)\n return () => {\n window.removeEventListener(\"mousemove\", handleMouseMove)\n window.removeEventListener(\"mouseup\", handleMouseUp)\n }\n }, [handleMouseMove, handleMouseUp])\n\n return {\n handleMouseDown,\n isDragging: !!activeEditEventRef.current,\n activeEditEvent: activeEditEvent,\n }\n}\n","import type {\n EditSchematicComponentLocationEvent,\n EditSchematicComponentLocationEventWithElement,\n ManualEditEvent,\n} from \"lib/types/edit-events\"\n\n/**\n * Returns the total offset of a component due to a set of edit events in\n * mm\n */\nexport const getComponentOffsetDueToEvents = ({\n editEvents,\n schematic_component_id,\n}: {\n editEvents: ManualEditEvent[]\n schematic_component_id: string\n}) => {\n const editEventsForComponent: EditSchematicComponentLocationEvent[] =\n editEvents\n .filter(\n (event) =>\n \"schematic_component_id\" in event &&\n event.schematic_component_id === schematic_component_id,\n )\n .filter(\n (event) =>\n \"edit_event_type\" in event &&\n event.edit_event_type === \"edit_schematic_component_location\",\n )\n\n const totalOffsetX = editEventsForComponent.reduce((acc, event) => {\n return acc + event.new_center.x - event.original_center.x\n }, 0)\n\n const totalOffsetY = editEventsForComponent.reduce((acc, event) => {\n return acc + event.new_center.y - event.original_center.y\n }, 0)\n\n return {\n x: totalOffsetX,\n y: totalOffsetY,\n }\n}\n","import Debug from \"debug\"\n\nexport const debug = Debug(\"schematic-viewer\")\n\nexport const enableDebug = () => {\n Debug.enable(\"schematic-viewer*\")\n}\n\nexport default debug\n","import { su } from \"@tscircuit/soup-util\"\nimport type {\n ManualEditEvent,\n EditSchematicComponentLocationEventWithElement,\n} from \"lib/types/edit-events\"\nimport { type Matrix, compose, applyToPoint } from \"transformation-matrix\"\nimport { useEffect, useRef } from \"react\"\nimport { getComponentOffsetDueToEvents } from \"lib/utils/get-component-offset-due-to-events\"\nimport type { CircuitJson } from \"circuit-json\"\n\n/**\n * This hook automatically applies the edit events to the schematic components\n * inside the svg div.\n *\n * Schematic components are \"<g>\" elements with a \"data-circuit-json-type\"\n * attribute equal to \"schematic_component\", these elements also have a\n * data-schematic-component-id attribute equal to the schematic_component_id\n */\nexport const useChangeSchematicComponentLocationsInSvg = ({\n svgDivRef,\n realToSvgProjection,\n svgToScreenProjection,\n activeEditEvent,\n editEvents,\n}: {\n svgDivRef: React.RefObject<HTMLDivElement | null>\n realToSvgProjection: Matrix\n svgToScreenProjection: Matrix\n activeEditEvent: EditSchematicComponentLocationEventWithElement | null\n editEvents: ManualEditEvent[]\n}) => {\n // Keep track of the last known SVG content\n const lastSvgContentRef = useRef<string | null>(null)\n\n useEffect(() => {\n const svg = svgDivRef.current\n if (!svg) return\n\n // Create a MutationObserver to watch for changes in the div's content\n const observer = new MutationObserver((mutations) => {\n // Check if the SVG content has changed\n const currentSvgContent = svg.innerHTML\n if (currentSvgContent !== lastSvgContentRef.current) {\n lastSvgContentRef.current = currentSvgContent\n\n // Apply the transforms\n applyTransforms()\n }\n })\n\n // Function to apply transforms to components\n const applyTransforms = () => {\n const componentsThatHaveBeenMoved = new Set<string>()\n for (const event of editEvents) {\n if (\n \"edit_event_type\" in event &&\n event.edit_event_type === \"edit_schematic_component_location\"\n ) {\n componentsThatHaveBeenMoved.add(event.schematic_component_id)\n }\n }\n if (activeEditEvent) {\n componentsThatHaveBeenMoved.add(activeEditEvent.schematic_component_id)\n }\n\n // Reset all transforms\n const allComponents = svg.querySelectorAll(\n '[data-circuit-json-type=\"schematic_component\"]',\n )\n\n for (const component of Array.from(allComponents)) {\n const schematic_component_id = component.getAttribute(\n \"data-schematic-component-id\",\n )!\n\n const offsetMm = getComponentOffsetDueToEvents({\n editEvents: [\n ...editEvents,\n ...(activeEditEvent ? [activeEditEvent] : []),\n ],\n schematic_component_id,\n })\n\n const offsetPx = {\n x: offsetMm.x * realToSvgProjection.a,\n y: offsetMm.y * realToSvgProjection.d,\n }\n\n const style: any = (component as any).style\n style.transform = `translate(${offsetPx.x}px, ${offsetPx.y}px)`\n if (\n activeEditEvent?.schematic_component_id === schematic_component_id\n ) {\n style.outline = \"solid 2px rgba(255,0,0,0.5)\"\n style.outlineOffset = \"5px\"\n } else if (style.outline) {\n style.outline = \"\"\n }\n }\n }\n\n // Start observing the div for changes\n observer.observe(svg, {\n childList: true, // Watch for changes to the child elements\n subtree: false, // Watch for changes in the entire subtree\n characterData: false, // Watch for changes to text content\n })\n\n // Apply transforms immediately on mount or when editEvents change\n applyTransforms()\n\n // Cleanup function\n return () => {\n observer.disconnect()\n }\n }, [svgDivRef, editEvents, activeEditEvent]) // Dependencies remain the same\n}\n","import { useEffect, useRef } from \"react\"\nimport { su } from \"@tscircuit/soup-util\"\nimport type { ManualEditEvent } from \"../types/edit-events\"\nimport type { CircuitJson } from \"circuit-json\"\n\n/**\n * This hook makes traces dashed when their connected components are being moved\n */\nexport const useChangeSchematicTracesForMovedComponents = ({\n svgDivRef,\n circuitJson,\n activeEditEvent,\n editEvents,\n}: {\n svgDivRef: React.RefObject<HTMLDivElement | null>\n circuitJson: CircuitJson\n activeEditEvent: ManualEditEvent | null\n editEvents: ManualEditEvent[]\n}) => {\n // Keep track of the last known SVG content\n const lastSvgContentRef = useRef<string | null>(null)\n\n useEffect(() => {\n const svg = svgDivRef.current\n if (!svg) return\n\n const updateTraceStyles = () => {\n // Reset all traces to solid\n const allTraces = svg.querySelectorAll(\n '[data-circuit-json-type=\"schematic_trace\"] path',\n )\n\n // Reset all traces to solid\n for (const trace of Array.from(allTraces)) {\n trace.setAttribute(\"stroke-dasharray\", \"0\")\n ;(trace as any).style.animation = \"\"\n }\n\n // If there's an active edit event, make connected traces dashed\n for (const editEvent of [\n ...editEvents,\n ...(activeEditEvent ? [activeEditEvent] : []),\n ]) {\n if (\n \"schematic_component_id\" in editEvent &&\n editEvent.edit_event_type === \"edit_schematic_component_location\"\n ) {\n const sch_component = su(circuitJson).schematic_component.get(\n editEvent.schematic_component_id,\n )\n if (!sch_component) return\n\n const src_ports = su(circuitJson).source_port.list({\n source_component_id: sch_component.source_component_id,\n })\n const src_port_ids = new Set(src_ports.map((sp) => sp.source_port_id))\n const src_traces = su(circuitJson)\n .source_trace.list()\n .filter((st) =>\n st.connected_source_port_ids?.some((spi) =>\n src_port_ids.has(spi),\n ),\n )\n const src_trace_ids = new Set(\n src_traces.map((st) => st.source_trace_id),\n )\n const schematic_traces = su(circuitJson)\n .schematic_trace.list()\n .filter((st) => src_trace_ids.has(st.source_trace_id))\n\n // Make the connected traces dashed\n schematic_traces.forEach((trace) => {\n const traceElements = svg.querySelectorAll(\n `[data-schematic-trace-id=\"${trace.schematic_trace_id}\"] path`,\n )\n for (const traceElement of Array.from(traceElements)) {\n if (traceElement.getAttribute(\"class\")?.includes(\"invisible\"))\n continue\n traceElement.setAttribute(\"stroke-dasharray\", \"20,20\")\n ;(traceElement as any).style.animation =\n \"dash-animation 350ms linear infinite, pulse-animation 900ms linear infinite\"\n\n if (!svg.querySelector(\"style#dash-animation\")) {\n const style = document.createElement(\"style\")\n style.id = \"dash-animation\"\n style.textContent = `\n @keyframes dash-animation {\n to {\n stroke-dashoffset: -40;\n }\n }\n @keyframes pulse-animation {\n 0% { opacity: 0.6; }\n 50% { opacity: 0.2; }\n 100% { opacity: 0.6; }\n }\n `\n svg.appendChild(style)\n }\n }\n })\n }\n }\n }\n\n // Apply styles immediately\n updateTraceStyles()\n\n // Cleanup function\n const observer = new MutationObserver(updateTraceStyles)\n observer.observe(svg, {\n childList: true, // Watch for changes to the child elements\n subtree: false, // Watch for changes in the entire subtree\n characterData: false, // Watch for changes to text content\n })\n\n return () => {\n observer.disconnect()\n }\n }, [svgDivRef, activeEditEvent, circuitJson, editEvents])\n}\n"],"mappings":"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","names":["useRef","useState","useEffect","useState","debug","useState","useEffect","useEffect","useRef","useRef","useEffect","useEffect","useRef","su","jsx","jsxs","debug","useState","useRef"]}
|
|
1
|
+
{"version":3,"sources":["../lib/components/SchematicViewer.tsx","../lib/hooks/useChangeSchematicComponentLocationsInSvg.ts","../lib/utils/get-component-offset-due-to-events.ts","../lib/hooks/useChangeSchematicTracesForMovedComponents.ts","../lib/utils/debug.ts","../lib/hooks/use-resize-handling.ts","../lib/hooks/useComponentDragging.ts","../lib/components/EditIcon.tsx"],"sourcesContent":["import { convertCircuitJsonToSchematicSvg } from \"circuit-to-svg\"\nimport { useChangeSchematicComponentLocationsInSvg } from \"lib/hooks/useChangeSchematicComponentLocationsInSvg\"\nimport { useChangeSchematicTracesForMovedComponents } from \"lib/hooks/useChangeSchematicTracesForMovedComponents\"\nimport { enableDebug } from \"lib/utils/debug\"\nimport { useEffect, useMemo, useRef, useState } from \"react\"\nimport {\n fromString,\n identity,\n toString as transformToString,\n} from \"transformation-matrix\"\nimport { useMouseMatrixTransform } from \"use-mouse-matrix-transform\"\nimport { useResizeHandling } from \"../hooks/use-resize-handling\"\nimport { useComponentDragging } from \"../hooks/useComponentDragging\"\nimport type { ManualEditEvent } from \"../types/edit-events\"\nimport { EditIcon } from \"./EditIcon\"\nimport type { CircuitJson } from \"circuit-json\"\n\ninterface Props {\n circuitJson: CircuitJson\n containerStyle?: React.CSSProperties\n editEvents?: ManualEditEvent[]\n onEditEvent?: (event: ManualEditEvent) => void\n defaultEditMode?: boolean\n debugGrid?: boolean\n editingEnabled?: boolean\n debug?: boolean\n clickToInteractEnabled?: boolean\n}\n\nexport const SchematicViewer = ({\n circuitJson,\n containerStyle,\n editEvents: unappliedEditEvents = [],\n onEditEvent,\n defaultEditMode = false,\n debugGrid = false,\n editingEnabled = false,\n debug = false,\n clickToInteractEnabled = false,\n}: Props) => {\n if (debug) {\n enableDebug()\n }\n const [editModeEnabled, setEditModeEnabled] = useState(defaultEditMode)\n const [isInteractionEnabled, setIsInteractionEnabled] = useState<boolean>(\n !clickToInteractEnabled,\n )\n const svgDivRef = useRef<HTMLDivElement>(null)\n\n const [internalEditEvents, setInternalEditEvents] = useState<ManualEditEvent[]>([])\n const circuitJsonRef = useRef<CircuitJson>(circuitJson)\n\n const getCircuitHash = (circuitJson: CircuitJson) => {\n return `${circuitJson?.length || 0}_${(circuitJson as any)?.editCount || 0}`\n }\n\n useEffect(() => {\n const circuitHash = getCircuitHash(circuitJson)\n const circuitHashRef = getCircuitHash(circuitJsonRef.current)\n\n if (circuitHash !== circuitHashRef) {\n setInternalEditEvents([])\n circuitJsonRef.current = circuitJson\n }\n }, [circuitJson])\n\n const {\n ref: containerRef,\n cancelDrag,\n transform: svgToScreenProjection,\n } = useMouseMatrixTransform({\n onSetTransform(transform) {\n if (!svgDivRef.current) return\n svgDivRef.current.style.transform = transformToString(transform)\n },\n // @ts-ignore disabled is a valid prop but not typed\n enabled: isInteractionEnabled,\n })\n\n const { containerWidth, containerHeight } = useResizeHandling(containerRef)\n const svgString = useMemo(() => {\n if (!containerWidth || !containerHeight) return \"\"\n\n return convertCircuitJsonToSchematicSvg(circuitJson as any, {\n width: containerWidth,\n height: containerHeight || 720,\n grid: !debugGrid\n ? undefined\n : {\n cellSize: 1,\n labelCells: true,\n },\n })\n }, [circuitJson, containerWidth, containerHeight])\n\n const realToSvgProjection = useMemo(() => {\n if (!svgString) return identity()\n const transformString = svgString.match(\n /data-real-to-screen-transform=\"([^\"]+)\"/,\n )?.[1]!\n\n try {\n return fromString(transformString)\n } catch (e) {\n console.error(e)\n return identity()\n }\n }, [svgString])\n\n const handleEditEvent = (event: ManualEditEvent) => {\n setInternalEditEvents((prev) => [...prev, event])\n if (onEditEvent) {\n onEditEvent(event)\n }\n }\n\n const editEventsWithUnappliedEditEvents = useMemo(() => {\n return [...unappliedEditEvents, ...internalEditEvents]\n }, [unappliedEditEvents, internalEditEvents])\n\n const { handleMouseDown, isDragging, activeEditEvent } = useComponentDragging(\n {\n onEditEvent: handleEditEvent,\n cancelDrag,\n realToSvgProjection,\n svgToScreenProjection,\n circuitJson,\n editEvents: editEventsWithUnappliedEditEvents,\n enabled: editModeEnabled && isInteractionEnabled,\n },\n )\n\n useChangeSchematicComponentLocationsInSvg({\n svgDivRef,\n editEvents: editEventsWithUnappliedEditEvents,\n realToSvgProjection,\n svgToScreenProjection,\n activeEditEvent,\n })\n\n useChangeSchematicTracesForMovedComponents({\n svgDivRef,\n circuitJson,\n activeEditEvent,\n editEvents: editEventsWithUnappliedEditEvents,\n })\n\n const svgDiv = useMemo(\n () => (\n <div\n ref={svgDivRef}\n style={{\n pointerEvents: clickToInteractEnabled\n ? isInteractionEnabled\n ? \"auto\"\n : \"none\"\n : \"auto\",\n transformOrigin: \"0 0\",\n }}\n // biome-ignore lint/security/noDangerouslySetInnerHtml: <explanation>\n dangerouslySetInnerHTML={{ __html: svgString }}\n />\n ),\n [svgString, isInteractionEnabled, clickToInteractEnabled],\n )\n\n return (\n <div\n ref={containerRef}\n style={{\n position: \"relative\",\n backgroundColor: \"#F5F1ED\",\n overflow: \"hidden\",\n cursor: isDragging\n ? \"grabbing\"\n : clickToInteractEnabled && !isInteractionEnabled\n ? \"pointer\"\n : \"grab\",\n minHeight: \"300px\",\n ...containerStyle,\n }}\n onMouseDown={(e) => {\n if (clickToInteractEnabled && !isInteractionEnabled) {\n e.preventDefault()\n e.stopPropagation()\n return\n }\n handleMouseDown(e)\n }}\n onMouseDownCapture={(e) => {\n if (clickToInteractEnabled && !isInteractionEnabled) {\n e.preventDefault()\n e.stopPropagation()\n return\n }\n }}\n >\n {!isInteractionEnabled && clickToInteractEnabled && (\n <div\n onClick={(e) => {\n e.preventDefault()\n e.stopPropagation()\n setIsInteractionEnabled(true)\n }}\n style={{\n position: \"absolute\",\n inset: 0,\n cursor: \"pointer\",\n zIndex: 10,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n pointerEvents: \"all\",\n }}\n >\n <div\n style={{\n backgroundColor: \"rgba(0, 0, 0, 0.8)\",\n color: \"white\",\n padding: \"12px 24px\",\n borderRadius: \"8px\",\n fontSize: \"16px\",\n fontFamily: \"sans-serif\",\n pointerEvents: \"none\",\n }}\n >\n Click to Interact\n </div>\n </div>\n )}\n {editingEnabled && (\n <EditIcon\n active={editModeEnabled}\n onClick={() => setEditModeEnabled(!editModeEnabled)}\n />\n )}\n {svgDiv}\n </div>\n )\n}\n","import { su } from \"@tscircuit/soup-util\"\nimport type {\n ManualEditEvent,\n EditSchematicComponentLocationEventWithElement,\n} from \"lib/types/edit-events\"\nimport { type Matrix, compose, applyToPoint } from \"transformation-matrix\"\nimport { useEffect, useRef } from \"react\"\nimport { getComponentOffsetDueToEvents } from \"lib/utils/get-component-offset-due-to-events\"\nimport type { CircuitJson } from \"circuit-json\"\n\n/**\n * This hook automatically applies the edit events to the schematic components\n * inside the svg div.\n *\n * Schematic components are \"<g>\" elements with a \"data-circuit-json-type\"\n * attribute equal to \"schematic_component\", these elements also have a\n * data-schematic-component-id attribute equal to the schematic_component_id\n */\nexport const useChangeSchematicComponentLocationsInSvg = ({\n svgDivRef,\n realToSvgProjection,\n svgToScreenProjection,\n activeEditEvent,\n editEvents,\n}: {\n svgDivRef: React.RefObject<HTMLDivElement | null>\n realToSvgProjection: Matrix\n svgToScreenProjection: Matrix\n activeEditEvent: EditSchematicComponentLocationEventWithElement | null\n editEvents: ManualEditEvent[]\n}) => {\n // Keep track of the last known SVG content\n const lastSvgContentRef = useRef<string | null>(null)\n\n useEffect(() => {\n const svg = svgDivRef.current\n if (!svg) return\n\n // Create a MutationObserver to watch for changes in the div's content\n const observer = new MutationObserver((mutations) => {\n // Check if the SVG content has changed\n const currentSvgContent = svg.innerHTML\n if (currentSvgContent !== lastSvgContentRef.current) {\n lastSvgContentRef.current = currentSvgContent\n\n // Apply the transforms\n applyTransforms()\n }\n })\n\n // Function to apply transforms to components\n const applyTransforms = () => {\n const componentsThatHaveBeenMoved = new Set<string>()\n for (const event of editEvents) {\n if (\n \"edit_event_type\" in event &&\n event.edit_event_type === \"edit_schematic_component_location\"\n ) {\n componentsThatHaveBeenMoved.add(event.schematic_component_id)\n }\n }\n if (activeEditEvent) {\n componentsThatHaveBeenMoved.add(activeEditEvent.schematic_component_id)\n }\n\n // Reset all transforms\n const allComponents = svg.querySelectorAll(\n '[data-circuit-json-type=\"schematic_component\"]',\n )\n\n for (const component of Array.from(allComponents)) {\n const schematic_component_id = component.getAttribute(\n \"data-schematic-component-id\",\n )!\n\n const offsetMm = getComponentOffsetDueToEvents({\n editEvents: [\n ...editEvents,\n ...(activeEditEvent ? [activeEditEvent] : []),\n ],\n schematic_component_id,\n })\n\n const offsetPx = {\n x: offsetMm.x * realToSvgProjection.a,\n y: offsetMm.y * realToSvgProjection.d,\n }\n\n const style: any = (component as any).style\n style.transform = `translate(${offsetPx.x}px, ${offsetPx.y}px)`\n if (\n activeEditEvent?.schematic_component_id === schematic_component_id\n ) {\n style.outline = \"solid 2px rgba(255,0,0,0.5)\"\n style.outlineOffset = \"5px\"\n } else if (style.outline) {\n style.outline = \"\"\n }\n }\n }\n\n // Start observing the div for changes\n observer.observe(svg, {\n childList: true, // Watch for changes to the child elements\n subtree: false, // Watch for changes in the entire subtree\n characterData: false, // Watch for changes to text content\n })\n\n // Apply transforms immediately on mount or when editEvents change\n applyTransforms()\n\n // Cleanup function\n return () => {\n observer.disconnect()\n }\n }, [svgDivRef, editEvents, activeEditEvent]) // Dependencies remain the same\n}\n","import type {\n EditSchematicComponentLocationEvent,\n EditSchematicComponentLocationEventWithElement,\n ManualEditEvent,\n} from \"lib/types/edit-events\"\n\n/**\n * Returns the total offset of a component due to a set of edit events in\n * mm\n */\nexport const getComponentOffsetDueToEvents = ({\n editEvents,\n schematic_component_id,\n}: {\n editEvents: ManualEditEvent[]\n schematic_component_id: string\n}) => {\n const editEventsForComponent: EditSchematicComponentLocationEvent[] =\n editEvents\n .filter(\n (event) =>\n \"schematic_component_id\" in event &&\n event.schematic_component_id === schematic_component_id,\n )\n .filter(\n (event) =>\n \"edit_event_type\" in event &&\n event.edit_event_type === \"edit_schematic_component_location\",\n )\n\n const totalOffsetX = editEventsForComponent.reduce((acc, event) => {\n return acc + event.new_center.x - event.original_center.x\n }, 0)\n\n const totalOffsetY = editEventsForComponent.reduce((acc, event) => {\n return acc + event.new_center.y - event.original_center.y\n }, 0)\n\n return {\n x: totalOffsetX,\n y: totalOffsetY,\n }\n}\n","import { useEffect, useRef } from \"react\"\nimport { su } from \"@tscircuit/soup-util\"\nimport type { ManualEditEvent } from \"../types/edit-events\"\nimport type { CircuitJson } from \"circuit-json\"\n\n/**\n * This hook makes traces dashed when their connected components are being moved\n */\nexport const useChangeSchematicTracesForMovedComponents = ({\n svgDivRef,\n circuitJson,\n activeEditEvent,\n editEvents,\n}: {\n svgDivRef: React.RefObject<HTMLDivElement | null>\n circuitJson: CircuitJson\n activeEditEvent: ManualEditEvent | null\n editEvents: ManualEditEvent[]\n}) => {\n // Keep track of the last known SVG content\n const lastSvgContentRef = useRef<string | null>(null)\n\n useEffect(() => {\n const svg = svgDivRef.current\n if (!svg) return\n\n const updateTraceStyles = () => {\n // Reset all traces to solid\n const allTraces = svg.querySelectorAll(\n '[data-circuit-json-type=\"schematic_trace\"] path',\n )\n\n // Reset all traces to solid\n for (const trace of Array.from(allTraces)) {\n trace.setAttribute(\"stroke-dasharray\", \"0\")\n ;(trace as any).style.animation = \"\"\n }\n\n // If there's an active edit event, make connected traces dashed\n for (const editEvent of [\n ...editEvents,\n ...(activeEditEvent ? [activeEditEvent] : []),\n ]) {\n if (\n \"schematic_component_id\" in editEvent &&\n editEvent.edit_event_type === \"edit_schematic_component_location\"\n ) {\n const sch_component = su(circuitJson).schematic_component.get(\n editEvent.schematic_component_id,\n )\n if (!sch_component) return\n\n const src_ports = su(circuitJson).source_port.list({\n source_component_id: sch_component.source_component_id,\n })\n const src_port_ids = new Set(src_ports.map((sp) => sp.source_port_id))\n const src_traces = su(circuitJson)\n .source_trace.list()\n .filter((st) =>\n st.connected_source_port_ids?.some((spi) =>\n src_port_ids.has(spi),\n ),\n )\n const src_trace_ids = new Set(\n src_traces.map((st) => st.source_trace_id),\n )\n const schematic_traces = su(circuitJson)\n .schematic_trace.list()\n .filter((st) => src_trace_ids.has(st.source_trace_id))\n\n // Make the connected traces dashed\n schematic_traces.forEach((trace) => {\n const traceElements = svg.querySelectorAll(\n `[data-schematic-trace-id=\"${trace.schematic_trace_id}\"] path`,\n )\n for (const traceElement of Array.from(traceElements)) {\n if (traceElement.getAttribute(\"class\")?.includes(\"invisible\"))\n continue\n traceElement.setAttribute(\"stroke-dasharray\", \"20,20\")\n ;(traceElement as any).style.animation =\n \"dash-animation 350ms linear infinite, pulse-animation 900ms linear infinite\"\n\n if (!svg.querySelector(\"style#dash-animation\")) {\n const style = document.createElement(\"style\")\n style.id = \"dash-animation\"\n style.textContent = `\n @keyframes dash-animation {\n to {\n stroke-dashoffset: -40;\n }\n }\n @keyframes pulse-animation {\n 0% { opacity: 0.6; }\n 50% { opacity: 0.2; }\n 100% { opacity: 0.6; }\n }\n `\n svg.appendChild(style)\n }\n }\n })\n }\n }\n }\n\n // Apply styles immediately\n updateTraceStyles()\n\n // Cleanup function\n const observer = new MutationObserver(updateTraceStyles)\n observer.observe(svg, {\n childList: true, // Watch for changes to the child elements\n subtree: false, // Watch for changes in the entire subtree\n characterData: false, // Watch for changes to text content\n })\n\n return () => {\n observer.disconnect()\n }\n }, [svgDivRef, activeEditEvent, circuitJson, editEvents])\n}\n","import Debug from \"debug\"\n\nexport const debug = Debug(\"schematic-viewer\")\n\nexport const enableDebug = () => {\n Debug.enable(\"schematic-viewer*\")\n}\n\nexport default debug\n","import { useEffect, useState } from \"react\"\n\nexport const useResizeHandling = (\n containerRef: React.RefObject<HTMLElement>,\n) => {\n const [containerWidth, setContainerWidth] = useState(0)\n const [containerHeight, setContainerHeight] = useState(0)\n\n useEffect(() => {\n if (!containerRef.current) return\n\n const updateDimensions = () => {\n const rect = containerRef.current?.getBoundingClientRect()\n setContainerWidth(rect?.width || 0)\n setContainerHeight(rect?.height || 0)\n }\n\n // Set initial dimensions\n updateDimensions()\n\n // Add resize listener\n const resizeObserver = new ResizeObserver(updateDimensions)\n resizeObserver.observe(containerRef.current)\n\n // Fallback to window resize\n window.addEventListener(\"resize\", updateDimensions)\n\n return () => {\n resizeObserver.disconnect()\n window.removeEventListener(\"resize\", updateDimensions)\n }\n }, [])\n\n return { containerWidth, containerHeight }\n}\n","import { su } from \"@tscircuit/soup-util\"\nimport Debug from \"lib/utils/debug\"\nimport { getComponentOffsetDueToEvents } from \"lib/utils/get-component-offset-due-to-events\"\nimport { useCallback, useEffect, useRef, useState } from \"react\"\nimport {\n type Matrix,\n compose\n} from \"transformation-matrix\"\nimport type {\n EditSchematicComponentLocationEventWithElement,\n ManualEditEvent,\n} from \"../types/edit-events\"\n\nconst debug = Debug.extend(\"useComponentDragging\")\n\nexport const useComponentDragging = ({\n onEditEvent,\n editEvents = [],\n circuitJson,\n cancelDrag,\n svgToScreenProjection,\n realToSvgProjection,\n enabled = false,\n}: {\n circuitJson: any[]\n editEvents: ManualEditEvent[]\n /** The projection returned from use-mouse-matrix-transform, indicating zoom on svg */\n svgToScreenProjection: Matrix\n /** The projection returned from circuit-to-svg, mm to svg */\n realToSvgProjection: Matrix\n onEditEvent?: (event: ManualEditEvent) => void\n cancelDrag?: () => void\n enabled?: boolean\n}): {\n handleMouseDown: (e: React.MouseEvent) => void\n isDragging: boolean\n activeEditEvent: EditSchematicComponentLocationEventWithElement | null\n} => {\n const [activeEditEvent, setActiveEditEvent] =\n useState<EditSchematicComponentLocationEventWithElement | null>(null)\n const realToScreenProjection = compose(\n realToSvgProjection,\n svgToScreenProjection,\n )\n\n /**\n * Drag start position in screen space\n */\n const dragStartPosRef = useRef<{\n x: number\n y: number\n } | null>(null)\n\n const activeEditEventRef =\n useRef<EditSchematicComponentLocationEventWithElement | null>(null)\n\n // Store the latest positions of components being tracked\n const componentPositionsRef = useRef<Map<string, {x: number, y: number}>>(new Map())\n \n // Update position map with the latest positions from edit events\n useEffect(() => {\n // Process completed edit events to track latest positions\n editEvents.forEach(event => {\n if (\n \"edit_event_type\" in event && \n event.edit_event_type === \"edit_schematic_component_location\" && \n !event.in_progress\n ) {\n componentPositionsRef.current.set(\n event.schematic_component_id, \n {...event.new_center}\n )\n }\n })\n }, [editEvents])\n\n const handleMouseDown = useCallback(\n (e: React.MouseEvent) => {\n if (!enabled) return\n\n const target = e.target as SVGElement\n const componentGroup = target.closest(\n '[data-circuit-json-type=\"schematic_component\"]',\n )\n if (!componentGroup) return\n\n const schematic_component_id = componentGroup.getAttribute(\n \"data-schematic-component-id\",\n )\n if (!schematic_component_id) return\n\n if (cancelDrag) cancelDrag()\n\n const schematic_component = su(circuitJson).schematic_component.get(\n schematic_component_id,\n )\n if (!schematic_component) return\n \n dragStartPosRef.current = {\n x: e.clientX,\n y: e.clientY,\n }\n\n // Get the current position of the component\n // Check if we're already tracking this component\n let current_position: {x: number, y: number}\n const trackedPosition = componentPositionsRef.current.get(schematic_component_id)\n \n if (trackedPosition) {\n // Use the tracked position from previous edits\n current_position = {...trackedPosition}\n } else {\n // Calculate position based on component data and edit events\n const editEventOffset = getComponentOffsetDueToEvents({\n editEvents,\n schematic_component_id: schematic_component_id,\n })\n \n current_position = {\n x: schematic_component.center.x + editEventOffset.x,\n y: schematic_component.center.y + editEventOffset.y,\n }\n \n // Store this initial position\n componentPositionsRef.current.set(schematic_component_id, {...current_position})\n }\n\n const newEditEvent: EditSchematicComponentLocationEventWithElement = {\n edit_event_id: Math.random().toString(36).substr(2, 9),\n edit_event_type: \"edit_schematic_component_location\",\n schematic_component_id: schematic_component_id,\n original_center: current_position,\n new_center: { ...current_position },\n in_progress: true,\n created_at: Date.now(),\n _element: componentGroup as any,\n }\n\n activeEditEventRef.current = newEditEvent\n setActiveEditEvent(newEditEvent)\n },\n [cancelDrag, enabled, circuitJson, editEvents],\n )\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (!activeEditEventRef.current || !dragStartPosRef.current) return\n\n const screenDelta = {\n x: e.clientX - dragStartPosRef.current.x,\n y: e.clientY - dragStartPosRef.current.y,\n }\n\n const mmDelta = {\n x: screenDelta.x / realToScreenProjection.a,\n y: screenDelta.y / realToScreenProjection.d,\n }\n\n const newEditEvent = {\n ...activeEditEventRef.current,\n new_center: {\n x: activeEditEventRef.current.original_center.x + mmDelta.x,\n y: activeEditEventRef.current.original_center.y + mmDelta.y,\n },\n }\n\n activeEditEventRef.current = newEditEvent\n setActiveEditEvent(newEditEvent)\n },\n [realToScreenProjection],\n )\n\n const handleMouseUp = useCallback(() => {\n if (!activeEditEventRef.current) return\n const finalEvent = {\n ...activeEditEventRef.current,\n in_progress: false,\n }\n \n // Update our stored position for this component\n componentPositionsRef.current.set(\n finalEvent.schematic_component_id, \n {...finalEvent.new_center}\n )\n \n debug(\"handleMouseUp calling onEditEvent with new edit event\", {\n newEditEvent: finalEvent,\n })\n if (onEditEvent) onEditEvent(finalEvent)\n activeEditEventRef.current = null\n dragStartPosRef.current = null\n setActiveEditEvent(null)\n }, [onEditEvent])\n\n useEffect(() => {\n window.addEventListener(\"mousemove\", handleMouseMove)\n window.addEventListener(\"mouseup\", handleMouseUp)\n return () => {\n window.removeEventListener(\"mousemove\", handleMouseMove)\n window.removeEventListener(\"mouseup\", handleMouseUp)\n }\n }, [handleMouseMove, handleMouseUp])\n\n return {\n handleMouseDown,\n isDragging: !!activeEditEventRef.current,\n activeEditEvent: activeEditEvent,\n }\n}\n","export const EditIcon = ({\n onClick,\n active,\n}: { onClick: () => void; active: boolean }) => {\n return (\n <div\n onClick={onClick}\n style={{\n position: \"absolute\",\n top: \"16px\",\n right: \"16px\",\n backgroundColor: active ? \"#4CAF50\" : \"#fff\",\n color: active ? \"#fff\" : \"#000\",\n padding: \"8px\",\n borderRadius: \"4px\",\n cursor: \"pointer\",\n boxShadow: \"0 2px 4px rgba(0,0,0,0.1)\",\n display: \"flex\",\n alignItems: \"center\",\n gap: \"4px\",\n zIndex: 1000,\n }}\n >\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n >\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\" />\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\" />\n </svg>\n </div>\n )\n}\n"],"mappings":"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","names":["useEffect","useRef","su","useEffect","useRef","useState","useEffect","su","useEffect","useRef","useState","compose","debug","su","jsx","jsxs","debug","useState","useRef","circuitJson","useEffect"]}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { applyEditEventsToManualEditsFile } from "@tscircuit/core"
|
|
2
|
+
import type { CircuitJson } from "circuit-json"
|
|
3
|
+
import { renderToCircuitJson } from "lib/dev/render-to-circuit-json"
|
|
4
|
+
import { SchematicViewer } from "lib/index"
|
|
5
|
+
import type { ManualEditEvent } from "lib/types/edit-events"
|
|
6
|
+
import { useEffect, useState } from "react"
|
|
7
|
+
import type { ManualEditsFile } from "@tscircuit/props"
|
|
8
|
+
|
|
9
|
+
export default () => {
|
|
10
|
+
const [editEvents, setEditEvents] = useState<ManualEditEvent[]>([])
|
|
11
|
+
const [circuitJson, setCircuitJson] = useState<CircuitJson | null>(null)
|
|
12
|
+
const [manualEdits, setManualEdits] = useState<ManualEditsFile>({
|
|
13
|
+
schematic_placements: [{
|
|
14
|
+
center: {
|
|
15
|
+
x: 2,
|
|
16
|
+
y: 3,
|
|
17
|
+
},
|
|
18
|
+
relative_to: "group_center",
|
|
19
|
+
selector: "C1",
|
|
20
|
+
}],
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const rerenderCircuitJson = () => {
|
|
24
|
+
setCircuitJson(
|
|
25
|
+
renderToCircuitJson(
|
|
26
|
+
<board width="10mm" height="10mm" manualEdits={manualEdits}>
|
|
27
|
+
<resistor name="R1" resistance={1000} />
|
|
28
|
+
<capacitor name="C1" capacitance="1uF" />
|
|
29
|
+
<trace from=".R1 .pin2" to=".C1 .pin1" />
|
|
30
|
+
</board>,
|
|
31
|
+
) as any,
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
rerenderCircuitJson()
|
|
37
|
+
},[])
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
// Apply edit events to manual edits for persistence between renders
|
|
41
|
+
const updatedManualEdits = applyEditEventsToManualEditsFile({
|
|
42
|
+
circuitJson: circuitJson ?? [] as any,
|
|
43
|
+
editEvents,
|
|
44
|
+
manualEditsFile: manualEdits,
|
|
45
|
+
})
|
|
46
|
+
setManualEdits(updatedManualEdits)
|
|
47
|
+
}, [editEvents, circuitJson])
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div style={{ position: "relative", height: "100%" }}>
|
|
51
|
+
<button
|
|
52
|
+
type="button"
|
|
53
|
+
onClick={rerenderCircuitJson}
|
|
54
|
+
style={{
|
|
55
|
+
position: "absolute",
|
|
56
|
+
top: "16px",
|
|
57
|
+
right: "64px",
|
|
58
|
+
zIndex: 1001,
|
|
59
|
+
backgroundColor: "#f44336",
|
|
60
|
+
color: "#fff",
|
|
61
|
+
padding: "8px",
|
|
62
|
+
borderRadius: "4px",
|
|
63
|
+
cursor: "pointer",
|
|
64
|
+
border: "none",
|
|
65
|
+
boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
Rerender Circuit JSON
|
|
69
|
+
</button>
|
|
70
|
+
<button
|
|
71
|
+
type="button"
|
|
72
|
+
onClick={() => {
|
|
73
|
+
setEditEvents([])
|
|
74
|
+
}}
|
|
75
|
+
style={{
|
|
76
|
+
position: "absolute",
|
|
77
|
+
top: "16px",
|
|
78
|
+
right: "220px",
|
|
79
|
+
zIndex: 1001,
|
|
80
|
+
backgroundColor: "#2196f3",
|
|
81
|
+
color: "#fff",
|
|
82
|
+
padding: "8px",
|
|
83
|
+
borderRadius: "4px",
|
|
84
|
+
cursor: "pointer",
|
|
85
|
+
border: "none",
|
|
86
|
+
boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
Reset Edits
|
|
90
|
+
</button>
|
|
91
|
+
<pre
|
|
92
|
+
style={{
|
|
93
|
+
position: "absolute",
|
|
94
|
+
top: "64px",
|
|
95
|
+
right: "64px",
|
|
96
|
+
zIndex: 1001,
|
|
97
|
+
backgroundColor: "#fff",
|
|
98
|
+
padding: "12px",
|
|
99
|
+
borderRadius: "4px",
|
|
100
|
+
border: "1px solid #ccc",
|
|
101
|
+
boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
|
|
102
|
+
maxHeight: "300px",
|
|
103
|
+
overflowY: "auto",
|
|
104
|
+
fontSize: "12px",
|
|
105
|
+
fontFamily: "monospace",
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
{JSON.stringify(editEvents, null, 2)}
|
|
109
|
+
</pre>
|
|
110
|
+
<SchematicViewer
|
|
111
|
+
onEditEvent={(event) => {
|
|
112
|
+
setEditEvents((prev) => [...prev, event])
|
|
113
|
+
}}
|
|
114
|
+
circuitJson={circuitJson ?? []}
|
|
115
|
+
containerStyle={{ height: "100%" }}
|
|
116
|
+
debugGrid
|
|
117
|
+
editingEnabled
|
|
118
|
+
/>
|
|
119
|
+
</div>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { useMouseMatrixTransform } from "use-mouse-matrix-transform"
|
|
2
1
|
import { convertCircuitJsonToSchematicSvg } from "circuit-to-svg"
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import type { ManualEditEvent } from "../types/edit-events"
|
|
2
|
+
import { useChangeSchematicComponentLocationsInSvg } from "lib/hooks/useChangeSchematicComponentLocationsInSvg"
|
|
3
|
+
import { useChangeSchematicTracesForMovedComponents } from "lib/hooks/useChangeSchematicTracesForMovedComponents"
|
|
4
|
+
import { enableDebug } from "lib/utils/debug"
|
|
5
|
+
import { useEffect, useMemo, useRef, useState } from "react"
|
|
8
6
|
import {
|
|
9
|
-
identity,
|
|
10
7
|
fromString,
|
|
8
|
+
identity,
|
|
11
9
|
toString as transformToString,
|
|
12
10
|
} from "transformation-matrix"
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
11
|
+
import { useMouseMatrixTransform } from "use-mouse-matrix-transform"
|
|
12
|
+
import { useResizeHandling } from "../hooks/use-resize-handling"
|
|
13
|
+
import { useComponentDragging } from "../hooks/useComponentDragging"
|
|
14
|
+
import type { ManualEditEvent } from "../types/edit-events"
|
|
15
|
+
import { EditIcon } from "./EditIcon"
|
|
15
16
|
import type { CircuitJson } from "circuit-json"
|
|
16
|
-
import { enableDebug } from "lib/utils/debug"
|
|
17
17
|
|
|
18
18
|
interface Props {
|
|
19
|
-
circuitJson:
|
|
19
|
+
circuitJson: CircuitJson
|
|
20
20
|
containerStyle?: React.CSSProperties
|
|
21
21
|
editEvents?: ManualEditEvent[]
|
|
22
22
|
onEditEvent?: (event: ManualEditEvent) => void
|
|
@@ -30,7 +30,7 @@ interface Props {
|
|
|
30
30
|
export const SchematicViewer = ({
|
|
31
31
|
circuitJson,
|
|
32
32
|
containerStyle,
|
|
33
|
-
editEvents = [],
|
|
33
|
+
editEvents: unappliedEditEvents = [],
|
|
34
34
|
onEditEvent,
|
|
35
35
|
defaultEditMode = false,
|
|
36
36
|
debugGrid = false,
|
|
@@ -47,6 +47,23 @@ export const SchematicViewer = ({
|
|
|
47
47
|
)
|
|
48
48
|
const svgDivRef = useRef<HTMLDivElement>(null)
|
|
49
49
|
|
|
50
|
+
const [internalEditEvents, setInternalEditEvents] = useState<ManualEditEvent[]>([])
|
|
51
|
+
const circuitJsonRef = useRef<CircuitJson>(circuitJson)
|
|
52
|
+
|
|
53
|
+
const getCircuitHash = (circuitJson: CircuitJson) => {
|
|
54
|
+
return `${circuitJson?.length || 0}_${(circuitJson as any)?.editCount || 0}`
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const circuitHash = getCircuitHash(circuitJson)
|
|
59
|
+
const circuitHashRef = getCircuitHash(circuitJsonRef.current)
|
|
60
|
+
|
|
61
|
+
if (circuitHash !== circuitHashRef) {
|
|
62
|
+
setInternalEditEvents([])
|
|
63
|
+
circuitJsonRef.current = circuitJson
|
|
64
|
+
}
|
|
65
|
+
}, [circuitJson])
|
|
66
|
+
|
|
50
67
|
const {
|
|
51
68
|
ref: containerRef,
|
|
52
69
|
cancelDrag,
|
|
@@ -90,21 +107,32 @@ export const SchematicViewer = ({
|
|
|
90
107
|
}
|
|
91
108
|
}, [svgString])
|
|
92
109
|
|
|
110
|
+
const handleEditEvent = (event: ManualEditEvent) => {
|
|
111
|
+
setInternalEditEvents((prev) => [...prev, event])
|
|
112
|
+
if (onEditEvent) {
|
|
113
|
+
onEditEvent(event)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const editEventsWithUnappliedEditEvents = useMemo(() => {
|
|
118
|
+
return [...unappliedEditEvents, ...internalEditEvents]
|
|
119
|
+
}, [unappliedEditEvents, internalEditEvents])
|
|
120
|
+
|
|
93
121
|
const { handleMouseDown, isDragging, activeEditEvent } = useComponentDragging(
|
|
94
122
|
{
|
|
95
|
-
onEditEvent,
|
|
123
|
+
onEditEvent: handleEditEvent,
|
|
96
124
|
cancelDrag,
|
|
97
125
|
realToSvgProjection,
|
|
98
126
|
svgToScreenProjection,
|
|
99
127
|
circuitJson,
|
|
100
|
-
editEvents,
|
|
128
|
+
editEvents: editEventsWithUnappliedEditEvents,
|
|
101
129
|
enabled: editModeEnabled && isInteractionEnabled,
|
|
102
130
|
},
|
|
103
131
|
)
|
|
104
132
|
|
|
105
133
|
useChangeSchematicComponentLocationsInSvg({
|
|
106
134
|
svgDivRef,
|
|
107
|
-
editEvents,
|
|
135
|
+
editEvents: editEventsWithUnappliedEditEvents,
|
|
108
136
|
realToSvgProjection,
|
|
109
137
|
svgToScreenProjection,
|
|
110
138
|
activeEditEvent,
|
|
@@ -114,7 +142,7 @@ export const SchematicViewer = ({
|
|
|
114
142
|
svgDivRef,
|
|
115
143
|
circuitJson,
|
|
116
144
|
activeEditEvent,
|
|
117
|
-
editEvents,
|
|
145
|
+
editEvents: editEventsWithUnappliedEditEvents,
|
|
118
146
|
})
|
|
119
147
|
|
|
120
148
|
const svgDiv = useMemo(
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as Core from "@tscircuit/core"
|
|
2
|
+
import type { CircuitJson } from "circuit-json"
|
|
2
3
|
|
|
3
4
|
export const renderToCircuitJson = (board: React.ReactElement) => {
|
|
4
5
|
const circuit = new Core.Circuit()
|
|
5
6
|
circuit.add(board)
|
|
6
|
-
return circuit.getCircuitJson()
|
|
7
|
+
return circuit.getCircuitJson() as CircuitJson
|
|
7
8
|
}
|
|
@@ -1,18 +1,15 @@
|
|
|
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"
|
|
1
4
|
import { useCallback, useEffect, useRef, useState } from "react"
|
|
5
|
+
import {
|
|
6
|
+
type Matrix,
|
|
7
|
+
compose
|
|
8
|
+
} from "transformation-matrix"
|
|
2
9
|
import type {
|
|
3
10
|
EditSchematicComponentLocationEventWithElement,
|
|
4
11
|
ManualEditEvent,
|
|
5
12
|
} from "../types/edit-events"
|
|
6
|
-
import {
|
|
7
|
-
type Matrix,
|
|
8
|
-
applyToPoint,
|
|
9
|
-
inverse,
|
|
10
|
-
compose,
|
|
11
|
-
} from "transformation-matrix"
|
|
12
|
-
import { getComponentOffsetDueToEvents } from "lib/utils/get-component-offset-due-to-events"
|
|
13
|
-
import type { CircuitJson } from "circuit-json"
|
|
14
|
-
import { su } from "@tscircuit/soup-util"
|
|
15
|
-
import Debug from "lib/utils/debug"
|
|
16
13
|
|
|
17
14
|
const debug = Debug.extend("useComponentDragging")
|
|
18
15
|
|
|
@@ -57,6 +54,26 @@ export const useComponentDragging = ({
|
|
|
57
54
|
const activeEditEventRef =
|
|
58
55
|
useRef<EditSchematicComponentLocationEventWithElement | null>(null)
|
|
59
56
|
|
|
57
|
+
// Store the latest positions of components being tracked
|
|
58
|
+
const componentPositionsRef = useRef<Map<string, {x: number, y: number}>>(new Map())
|
|
59
|
+
|
|
60
|
+
// Update position map with the latest positions from edit events
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
// Process completed edit events to track latest positions
|
|
63
|
+
editEvents.forEach(event => {
|
|
64
|
+
if (
|
|
65
|
+
"edit_event_type" in event &&
|
|
66
|
+
event.edit_event_type === "edit_schematic_component_location" &&
|
|
67
|
+
!event.in_progress
|
|
68
|
+
) {
|
|
69
|
+
componentPositionsRef.current.set(
|
|
70
|
+
event.schematic_component_id,
|
|
71
|
+
{...event.new_center}
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
}, [editEvents])
|
|
76
|
+
|
|
60
77
|
const handleMouseDown = useCallback(
|
|
61
78
|
(e: React.MouseEvent) => {
|
|
62
79
|
if (!enabled) return
|
|
@@ -78,35 +95,51 @@ export const useComponentDragging = ({
|
|
|
78
95
|
schematic_component_id,
|
|
79
96
|
)
|
|
80
97
|
if (!schematic_component) return
|
|
81
|
-
|
|
82
|
-
editEvents,
|
|
83
|
-
schematic_component_id: schematic_component_id,
|
|
84
|
-
})
|
|
85
|
-
|
|
98
|
+
|
|
86
99
|
dragStartPosRef.current = {
|
|
87
100
|
x: e.clientX,
|
|
88
101
|
y: e.clientY,
|
|
89
102
|
}
|
|
90
103
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
104
|
+
// Get the current position of the component
|
|
105
|
+
// Check if we're already tracking this component
|
|
106
|
+
let current_position: {x: number, y: number}
|
|
107
|
+
const trackedPosition = componentPositionsRef.current.get(schematic_component_id)
|
|
108
|
+
|
|
109
|
+
if (trackedPosition) {
|
|
110
|
+
// Use the tracked position from previous edits
|
|
111
|
+
current_position = {...trackedPosition}
|
|
112
|
+
} else {
|
|
113
|
+
// Calculate position based on component data and edit events
|
|
114
|
+
const editEventOffset = getComponentOffsetDueToEvents({
|
|
115
|
+
editEvents,
|
|
116
|
+
schematic_component_id: schematic_component_id,
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
current_position = {
|
|
120
|
+
x: schematic_component.center.x + editEventOffset.x,
|
|
121
|
+
y: schematic_component.center.y + editEventOffset.y,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Store this initial position
|
|
125
|
+
componentPositionsRef.current.set(schematic_component_id, {...current_position})
|
|
94
126
|
}
|
|
95
127
|
|
|
96
128
|
const newEditEvent: EditSchematicComponentLocationEventWithElement = {
|
|
97
129
|
edit_event_id: Math.random().toString(36).substr(2, 9),
|
|
98
130
|
edit_event_type: "edit_schematic_component_location",
|
|
99
131
|
schematic_component_id: schematic_component_id,
|
|
100
|
-
original_center,
|
|
101
|
-
new_center: { ...
|
|
132
|
+
original_center: current_position,
|
|
133
|
+
new_center: { ...current_position },
|
|
102
134
|
in_progress: true,
|
|
103
135
|
created_at: Date.now(),
|
|
104
136
|
_element: componentGroup as any,
|
|
105
137
|
}
|
|
106
138
|
|
|
107
139
|
activeEditEventRef.current = newEditEvent
|
|
140
|
+
setActiveEditEvent(newEditEvent)
|
|
108
141
|
},
|
|
109
|
-
[cancelDrag, enabled, circuitJson],
|
|
142
|
+
[cancelDrag, enabled, circuitJson, editEvents],
|
|
110
143
|
)
|
|
111
144
|
|
|
112
145
|
const handleMouseMove = useCallback(
|
|
@@ -143,6 +176,13 @@ export const useComponentDragging = ({
|
|
|
143
176
|
...activeEditEventRef.current,
|
|
144
177
|
in_progress: false,
|
|
145
178
|
}
|
|
179
|
+
|
|
180
|
+
// Update our stored position for this component
|
|
181
|
+
componentPositionsRef.current.set(
|
|
182
|
+
finalEvent.schematic_component_id,
|
|
183
|
+
{...finalEvent.new_center}
|
|
184
|
+
)
|
|
185
|
+
|
|
146
186
|
debug("handleMouseUp calling onEditEvent with new edit event", {
|
|
147
187
|
newEditEvent: finalEvent,
|
|
148
188
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/schematic-viewer",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.15",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"@biomejs/biome": "^1.9.4",
|
|
16
16
|
"@tscircuit/core": "^0.0.362",
|
|
17
|
-
"@tscircuit/props": "^0.0.
|
|
17
|
+
"@tscircuit/props": "^0.0.172",
|
|
18
18
|
"@types/bun": "latest",
|
|
19
19
|
"@types/debug": "^4.1.12",
|
|
20
20
|
"@types/react": "^19.0.1",
|
|
21
21
|
"@types/react-dom": "^19.0.2",
|
|
22
22
|
"@vitejs/plugin-react": "^4.3.4",
|
|
23
|
-
"circuit-json": "^0.0.
|
|
23
|
+
"circuit-json": "^0.0.154",
|
|
24
24
|
"circuit-to-svg": "^0.0.96",
|
|
25
25
|
"react": "18",
|
|
26
26
|
"react-cosmos": "^6.2.1",
|