@tscircuit/schematic-viewer 1.4.1 → 1.4.3
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.css +7 -0
- package/dist/index.css.map +1 -0
- package/dist/index.js +125 -109
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/Schematic.tsx +13 -8
- package/src/pages/style.css +5 -0
- package/src/schematic-components/SVGPathComponent.tsx +68 -29
- package/src/schematic-components/SchematicChip.tsx +35 -55
- package/src/schematic-components/SchematicComponent.tsx +1 -1
- package/src/schematic-components/SchematicText.tsx +1 -1
- package/src/schematic-components/SchematicTrace.tsx +4 -5
- package/src/stories/bug-high-port-numbers.stories.tsx +6 -1
package/package.json
CHANGED
package/src/Schematic.tsx
CHANGED
|
@@ -2,17 +2,18 @@ import { useRenderedCircuit } from "@tscircuit/core"
|
|
|
2
2
|
import { findBoundsAndCenter } from "@tscircuit/soup-util"
|
|
3
3
|
import type { AnyCircuitElement } from "circuit-json"
|
|
4
4
|
import { useGlobalStore } from "lib/render-context"
|
|
5
|
-
import React
|
|
5
|
+
import type React from "react"
|
|
6
|
+
import { useCallback, useEffect, useRef, useState } from "react"
|
|
6
7
|
import { ErrorBoundary as TypedErrorBoundary } from "react-error-boundary"
|
|
7
8
|
import { SuperGrid, toMMSI } from "react-supergrid"
|
|
8
9
|
import useMeasure from "react-use-measure"
|
|
9
10
|
import { ContextProviders } from "schematic-components"
|
|
10
11
|
import { SchematicElement } from "schematic-components/SchematicElement"
|
|
11
12
|
import {
|
|
13
|
+
type Matrix,
|
|
12
14
|
applyToPoint,
|
|
13
15
|
compose,
|
|
14
16
|
inverse,
|
|
15
|
-
Matrix,
|
|
16
17
|
scale,
|
|
17
18
|
translate,
|
|
18
19
|
} from "transformation-matrix"
|
|
@@ -122,7 +123,7 @@ export const SchematicWithoutContext = ({
|
|
|
122
123
|
|
|
123
124
|
updateTransform(newTransform)
|
|
124
125
|
}
|
|
125
|
-
}, [elements,
|
|
126
|
+
}, [elements, updateTransform])
|
|
126
127
|
|
|
127
128
|
const handleMouseDown = useCallback((e: React.MouseEvent) => {
|
|
128
129
|
isDraggingRef.current = true
|
|
@@ -137,12 +138,16 @@ export const SchematicWithoutContext = ({
|
|
|
137
138
|
const dy = e.clientY - lastMousePosRef.current.y
|
|
138
139
|
lastMousePosRef.current = { x: e.clientX, y: e.clientY }
|
|
139
140
|
|
|
140
|
-
|
|
141
|
-
const
|
|
141
|
+
// Transform the mouse movement to world space
|
|
142
|
+
const inverseTransform = inverse(transformRef.current)
|
|
143
|
+
const dragStart = applyToPoint(inverseTransform, { x: 0, y: 0 })
|
|
144
|
+
const dragEnd = applyToPoint(inverseTransform, { x: dx, y: dy })
|
|
145
|
+
const worldDx = dragEnd.x - dragStart.x
|
|
146
|
+
const worldDy = dragEnd.y - dragStart.y
|
|
142
147
|
|
|
143
148
|
const newTransform = compose(
|
|
144
|
-
translate(dx * dragSensitivity, dy * dragSensitivity),
|
|
145
149
|
transformRef.current,
|
|
150
|
+
translate(worldDx, worldDy),
|
|
146
151
|
)
|
|
147
152
|
updateTransform(newTransform)
|
|
148
153
|
},
|
|
@@ -156,7 +161,7 @@ export const SchematicWithoutContext = ({
|
|
|
156
161
|
const handleWheel = useCallback(
|
|
157
162
|
(e: WheelEvent) => {
|
|
158
163
|
e.preventDefault()
|
|
159
|
-
const scaleMultiplier =
|
|
164
|
+
const scaleMultiplier = 0.999 ** e.deltaY
|
|
160
165
|
|
|
161
166
|
if (containerRef.current) {
|
|
162
167
|
const rect = containerRef.current.getBoundingClientRect()
|
|
@@ -228,7 +233,7 @@ export const SchematicWithoutContext = ({
|
|
|
228
233
|
transform={transformRef.current}
|
|
229
234
|
/>
|
|
230
235
|
{elements?.map((elm, i) => (
|
|
231
|
-
<ErrorBoundary key={
|
|
236
|
+
<ErrorBoundary key={`${elm}`} fallbackRender={fallbackRender(elm)}>
|
|
232
237
|
<SchematicElement
|
|
233
238
|
element={elm}
|
|
234
239
|
allElements={elements}
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
toSVG,
|
|
9
9
|
translate,
|
|
10
10
|
} from "transformation-matrix"
|
|
11
|
-
|
|
11
|
+
import "../pages/style.css"
|
|
12
12
|
interface PathProps {
|
|
13
13
|
type?: "path"
|
|
14
14
|
strokeWidth: number
|
|
@@ -27,7 +27,19 @@ interface CircleProps {
|
|
|
27
27
|
fill?: string
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
interface TextProps {
|
|
31
|
+
type: "text"
|
|
32
|
+
cx: number
|
|
33
|
+
cy: number
|
|
34
|
+
text: string
|
|
35
|
+
fontSize?: number
|
|
36
|
+
fill: string
|
|
37
|
+
anchor?: "start" | "middle" | "end"
|
|
38
|
+
rotation?: number
|
|
39
|
+
stroke?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type SVGElement = PathProps | CircleProps | TextProps
|
|
31
43
|
|
|
32
44
|
interface Props {
|
|
33
45
|
rotation: number
|
|
@@ -52,9 +64,10 @@ export const SVGPathComponent = ({
|
|
|
52
64
|
}: Props) => {
|
|
53
65
|
const ct = useGlobalStore((s) => s.camera_transform)
|
|
54
66
|
const pathBounds = getSVGPathBounds(
|
|
55
|
-
paths
|
|
67
|
+
paths
|
|
68
|
+
.filter((p): p is PathProps => p.type !== "circle" && p.type !== "text")
|
|
69
|
+
.map((p) => p.d),
|
|
56
70
|
)
|
|
57
|
-
|
|
58
71
|
const padding = { x: 0, y: 0 }
|
|
59
72
|
const absoluteCenter = applyToPoint(ct, center)
|
|
60
73
|
const innerSize = {
|
|
@@ -65,17 +78,13 @@ export const SVGPathComponent = ({
|
|
|
65
78
|
width: innerSize.width + padding.x * 2,
|
|
66
79
|
height: innerSize.height + padding.y * 2,
|
|
67
80
|
}
|
|
68
|
-
|
|
69
81
|
const [hovering, setHovering] = useState(false)
|
|
70
|
-
|
|
71
82
|
const svgLeft = absoluteCenter.x - fullSize.width / 2
|
|
72
83
|
const svgTop = absoluteCenter.y - fullSize.height / 2
|
|
73
|
-
|
|
74
84
|
const preferredRatio =
|
|
75
85
|
pathBounds.width === 0
|
|
76
86
|
? innerSize.height / pathBounds.height
|
|
77
87
|
: innerSize.width / pathBounds.width
|
|
78
|
-
|
|
79
88
|
const svgToScreen = compose(
|
|
80
89
|
scale(
|
|
81
90
|
pathBounds.width === 0
|
|
@@ -88,10 +97,15 @@ export const SVGPathComponent = ({
|
|
|
88
97
|
translate(-pathBounds.minX, -pathBounds.minY),
|
|
89
98
|
)
|
|
90
99
|
|
|
100
|
+
const baseFontSize = 0.15 // Fixed base font size in schematic units
|
|
101
|
+
|
|
91
102
|
return (
|
|
103
|
+
// biome-ignore lint/a11y/noSvgWithoutTitle: <explanation>
|
|
92
104
|
<svg
|
|
93
105
|
onMouseOver={() => setHovering(Boolean(hoverContent))}
|
|
106
|
+
onFocus={() => setHovering(Boolean(hoverContent))}
|
|
94
107
|
onMouseOut={() => setHovering(false)}
|
|
108
|
+
onBlur={() => setHovering(false)}
|
|
95
109
|
style={{
|
|
96
110
|
position: "absolute",
|
|
97
111
|
cursor: hovering ? "pointer" : undefined,
|
|
@@ -108,26 +122,51 @@ export const SVGPathComponent = ({
|
|
|
108
122
|
width={fullSize.width}
|
|
109
123
|
height={fullSize.height}
|
|
110
124
|
>
|
|
111
|
-
{paths.map((p, i) =>
|
|
112
|
-
p.type === "circle"
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
compose(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
{paths.map((p, i) => {
|
|
126
|
+
if (p.type === "circle") {
|
|
127
|
+
return (
|
|
128
|
+
<circle
|
|
129
|
+
key={`${p.type}-${i}`}
|
|
130
|
+
transform={toSVG(compose(scale(1, 1), svgToScreen))}
|
|
131
|
+
cx={p.cx}
|
|
132
|
+
cy={p.cy}
|
|
133
|
+
r={p.r}
|
|
134
|
+
fill={"none"}
|
|
135
|
+
strokeWidth={2.25 * (p.strokeWidth || 1)}
|
|
136
|
+
stroke={p.stroke || "red"}
|
|
137
|
+
/>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
if (p.type === "text") {
|
|
141
|
+
const transformedPos = applyToPoint(svgToScreen, { x: p.cx, y: p.cy })
|
|
142
|
+
const scaleFactor = fullSize.width / pathBounds.width || 1
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<g key={`${p.type}-${i}`}>
|
|
146
|
+
<text
|
|
147
|
+
className="schematic-text"
|
|
148
|
+
x={transformedPos.x}
|
|
149
|
+
y={transformedPos.y}
|
|
150
|
+
fill={p.fill}
|
|
151
|
+
fontSize={baseFontSize * scaleFactor}
|
|
152
|
+
textAnchor={p.anchor || "middle"}
|
|
153
|
+
dominantBaseline="middle"
|
|
154
|
+
transform={`scale(1,-1) rotate(${p.rotation || 0})`}
|
|
155
|
+
style={{
|
|
156
|
+
transformBox: "fill-box",
|
|
157
|
+
transformOrigin: "center",
|
|
158
|
+
}}
|
|
159
|
+
stroke={p.stroke}
|
|
160
|
+
>
|
|
161
|
+
{p.text}
|
|
162
|
+
</text>
|
|
163
|
+
</g>
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
// Handle the "path" type directly
|
|
167
|
+
return (
|
|
129
168
|
<path
|
|
130
|
-
key={i}
|
|
169
|
+
key={`${p.type}-${i}`}
|
|
131
170
|
transform={toSVG(svgToScreen)}
|
|
132
171
|
fill={p.fill ?? "none"}
|
|
133
172
|
strokeLinecap="round"
|
|
@@ -135,8 +174,8 @@ export const SVGPathComponent = ({
|
|
|
135
174
|
stroke={p.stroke || "red"}
|
|
136
175
|
d={p.d || ""}
|
|
137
176
|
/>
|
|
138
|
-
)
|
|
139
|
-
)}
|
|
177
|
+
)
|
|
178
|
+
})}
|
|
140
179
|
</svg>
|
|
141
180
|
)
|
|
142
181
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type {
|
|
2
2
|
AnyCircuitElement,
|
|
3
3
|
SchematicPort as OriginalSchematicPort,
|
|
4
4
|
SchematicComponent,
|
|
5
5
|
} from "circuit-json"
|
|
6
|
-
import * as Type from "lib/types"
|
|
6
|
+
import type * as Type from "lib/types"
|
|
7
7
|
import { colorMap } from "lib/utils/colors"
|
|
8
|
-
import React from "react"
|
|
8
|
+
import type React from "react"
|
|
9
9
|
import SVGPathComponent from "./SVGPathComponent"
|
|
10
10
|
import SchematicText from "./SchematicText"
|
|
11
11
|
|
|
@@ -37,7 +37,7 @@ export const SchematicChip: React.FC<Props> = ({
|
|
|
37
37
|
const chipHeight = size.height
|
|
38
38
|
|
|
39
39
|
const paths: Array<{
|
|
40
|
-
type
|
|
40
|
+
type: "path" | "circle" | "text"
|
|
41
41
|
strokeWidth: number
|
|
42
42
|
stroke: string
|
|
43
43
|
fill?: string
|
|
@@ -45,6 +45,9 @@ export const SchematicChip: React.FC<Props> = ({
|
|
|
45
45
|
cx?: number
|
|
46
46
|
cy?: number
|
|
47
47
|
r?: number
|
|
48
|
+
text?: string
|
|
49
|
+
anchor?: string
|
|
50
|
+
rotation?: number
|
|
48
51
|
}> = []
|
|
49
52
|
|
|
50
53
|
// Main chip rectangle
|
|
@@ -62,31 +65,23 @@ export const SchematicChip: React.FC<Props> = ({
|
|
|
62
65
|
item.schematic_component_id === schematic_component_id,
|
|
63
66
|
)
|
|
64
67
|
|
|
65
|
-
const portLength = 0.
|
|
66
|
-
const circleRadius = 0.
|
|
68
|
+
const portLength = 0.5
|
|
69
|
+
const circleRadius = 0.04
|
|
67
70
|
const labelOffset = 0.1
|
|
68
71
|
|
|
69
|
-
const
|
|
70
|
-
x: number
|
|
71
|
-
y: number
|
|
72
|
-
text: string
|
|
73
|
-
anchor: string
|
|
74
|
-
rotation: number
|
|
75
|
-
}> = []
|
|
76
|
-
|
|
77
|
-
schematicPorts.forEach((port) => {
|
|
72
|
+
for (const port of schematicPorts) {
|
|
78
73
|
const { side, pinNumber, distanceFromEdge } = port.center
|
|
79
|
-
let x = 0
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
let
|
|
84
|
-
|
|
74
|
+
let x = 0
|
|
75
|
+
let y = 0
|
|
76
|
+
let endX = 0
|
|
77
|
+
let endY = 0
|
|
78
|
+
let pinX = 0
|
|
79
|
+
let pinY = 0
|
|
85
80
|
let textAnchor = "middle"
|
|
86
81
|
let rotation = 0
|
|
87
82
|
|
|
88
83
|
if (side === "center") {
|
|
89
|
-
|
|
84
|
+
continue
|
|
90
85
|
}
|
|
91
86
|
|
|
92
87
|
switch (side) {
|
|
@@ -95,17 +90,17 @@ export const SchematicChip: React.FC<Props> = ({
|
|
|
95
90
|
y = -chipHeight / 2 + distanceFromEdge
|
|
96
91
|
endX = x - portLength
|
|
97
92
|
endY = y
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
textAnchor = "
|
|
93
|
+
pinX = x - portLength / 2
|
|
94
|
+
pinY = y + labelOffset
|
|
95
|
+
textAnchor = "middle"
|
|
101
96
|
break
|
|
102
97
|
case "right":
|
|
103
98
|
x = chipWidth / 2
|
|
104
99
|
y = chipHeight / 2 - distanceFromEdge
|
|
105
100
|
endX = x + portLength
|
|
106
101
|
endY = y
|
|
107
|
-
|
|
108
|
-
|
|
102
|
+
pinX = x + portLength / 2 - labelOffset
|
|
103
|
+
pinY = y + labelOffset
|
|
109
104
|
textAnchor = "start"
|
|
110
105
|
break
|
|
111
106
|
case "bottom":
|
|
@@ -113,8 +108,8 @@ export const SchematicChip: React.FC<Props> = ({
|
|
|
113
108
|
y = -chipHeight / 2
|
|
114
109
|
endX = x
|
|
115
110
|
endY = y - portLength
|
|
116
|
-
|
|
117
|
-
|
|
111
|
+
pinX = x - labelOffset
|
|
112
|
+
pinY = y - portLength / 2
|
|
118
113
|
rotation = -90
|
|
119
114
|
break
|
|
120
115
|
case "top":
|
|
@@ -122,8 +117,8 @@ export const SchematicChip: React.FC<Props> = ({
|
|
|
122
117
|
y = chipHeight / 2
|
|
123
118
|
endX = x
|
|
124
119
|
endY = y + portLength
|
|
125
|
-
|
|
126
|
-
|
|
120
|
+
pinX = x - labelOffset
|
|
121
|
+
pinY = y + portLength / 2
|
|
127
122
|
rotation = -90
|
|
128
123
|
break
|
|
129
124
|
}
|
|
@@ -142,22 +137,25 @@ export const SchematicChip: React.FC<Props> = ({
|
|
|
142
137
|
cx: endX,
|
|
143
138
|
cy: endY,
|
|
144
139
|
r: circleRadius,
|
|
145
|
-
strokeWidth: 0.
|
|
140
|
+
strokeWidth: 0.005,
|
|
146
141
|
stroke: colorMap.schematic.component_outline,
|
|
147
142
|
fill: colorMap.schematic.component_outline,
|
|
148
143
|
})
|
|
149
144
|
|
|
150
|
-
// Add pin
|
|
145
|
+
// Add pin number
|
|
151
146
|
if (pinNumber !== undefined) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
147
|
+
paths.push({
|
|
148
|
+
type: "text",
|
|
149
|
+
cx: pinX,
|
|
150
|
+
cy: pinY,
|
|
155
151
|
text: `${pinNumber}`,
|
|
156
152
|
anchor: textAnchor,
|
|
157
153
|
rotation: rotation,
|
|
154
|
+
strokeWidth: 0.005,
|
|
155
|
+
stroke: colorMap.schematic.pin_number,
|
|
158
156
|
})
|
|
159
157
|
}
|
|
160
|
-
}
|
|
158
|
+
}
|
|
161
159
|
|
|
162
160
|
return (
|
|
163
161
|
<>
|
|
@@ -167,24 +165,6 @@ export const SchematicChip: React.FC<Props> = ({
|
|
|
167
165
|
size={size}
|
|
168
166
|
paths={paths as any}
|
|
169
167
|
/>
|
|
170
|
-
{pinLabels.map((label, index) => (
|
|
171
|
-
<SchematicText
|
|
172
|
-
key={index}
|
|
173
|
-
schematic_text={{
|
|
174
|
-
anchor: label.anchor as any,
|
|
175
|
-
rotation: 0,
|
|
176
|
-
position: {
|
|
177
|
-
x: center.x + label.x,
|
|
178
|
-
y: center.y + label.y,
|
|
179
|
-
},
|
|
180
|
-
schematic_component_id: "SYNTHETIC",
|
|
181
|
-
schematic_text_id: `PIN_LABEL_${index}`,
|
|
182
|
-
text: label.text,
|
|
183
|
-
type: "schematic_text",
|
|
184
|
-
color: colorMap.schematic.pin_number,
|
|
185
|
-
}}
|
|
186
|
-
/>
|
|
187
|
-
))}
|
|
188
168
|
<SchematicText
|
|
189
169
|
schematic_text={{
|
|
190
170
|
anchor: "right",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SchematicText as SchematicTextType } from "circuit-json"
|
|
1
|
+
import type { SchematicText as SchematicTextType } from "circuit-json"
|
|
2
2
|
import { useGlobalStore } from "lib/render-context"
|
|
3
3
|
import useMeasure from "react-use-measure"
|
|
4
4
|
import { applyToPoint } from "transformation-matrix"
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import * as Type from "lib/types"
|
|
2
|
-
import
|
|
3
|
-
import Path from "svg-path-generator"
|
|
1
|
+
import type * as Type from "lib/types"
|
|
2
|
+
import { colorMap } from "lib/utils/colors"
|
|
4
3
|
import getSVGPathBounds from "lib/utils/get-svg-path-bounds"
|
|
4
|
+
import Path from "svg-path-generator"
|
|
5
5
|
import RenderError from "./RenderError"
|
|
6
|
-
import
|
|
7
|
-
import { colorMap } from "lib/utils/colors"
|
|
6
|
+
import SVGPathComponent from "./SVGPathComponent"
|
|
8
7
|
|
|
9
8
|
interface Props {
|
|
10
9
|
trace: {
|
|
@@ -26,8 +26,13 @@ export const BugHighPortNumbers = () => {
|
|
|
26
26
|
pins: [29],
|
|
27
27
|
direction: "left-to-right",
|
|
28
28
|
},
|
|
29
|
+
topSide: {
|
|
30
|
+
pins: [30],
|
|
31
|
+
direction: "left-to-right",
|
|
32
|
+
},
|
|
29
33
|
}}
|
|
30
|
-
schWidth={
|
|
34
|
+
schWidth={3}
|
|
35
|
+
schHeight={6}
|
|
31
36
|
footprint="ssop28Db"
|
|
32
37
|
pinLabels={{
|
|
33
38
|
"1": "TXD",
|