@tscircuit/schematic-viewer 2.0.37 → 2.0.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +203 -191
- package/dist/index.js.map +1 -1
- package/lib/hooks/useSchematicGroupsOverlay.ts +275 -259
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -220,206 +220,218 @@ var GROUP_COLORS = [
|
|
|
220
220
|
var useSchematicGroupsOverlay = (options) => {
|
|
221
221
|
const { svgDivRef, circuitJson, circuitJsonKey, showGroups } = options;
|
|
222
222
|
useEffect3(() => {
|
|
223
|
-
if (
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
existingOverlays2.forEach((overlay) => overlay.remove());
|
|
229
|
-
}
|
|
230
|
-
return;
|
|
223
|
+
if (svgDivRef.current) {
|
|
224
|
+
const existingOverlays = svgDivRef.current.querySelectorAll(
|
|
225
|
+
".schematic-group-overlay"
|
|
226
|
+
);
|
|
227
|
+
existingOverlays.forEach((overlay) => overlay.remove());
|
|
231
228
|
}
|
|
232
|
-
|
|
233
|
-
if (!svg) {
|
|
229
|
+
if (!svgDivRef.current || !showGroups || !circuitJson || circuitJson.length === 0) {
|
|
234
230
|
return;
|
|
235
231
|
}
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
)
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
(
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
if (hasMeaningfulGroups) {
|
|
274
|
-
const groupMap = /* @__PURE__ */ new Map();
|
|
275
|
-
for (const comp of schematicComponents) {
|
|
276
|
-
const sourceComp = su3(circuitJson).source_component.get(
|
|
277
|
-
comp.source_component_id
|
|
232
|
+
const timeoutId = setTimeout(() => {
|
|
233
|
+
if (!svgDivRef.current) return;
|
|
234
|
+
const svg = svgDivRef.current.querySelector("svg");
|
|
235
|
+
if (!svg) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const existingOverlays = svg.querySelectorAll(".schematic-group-overlay");
|
|
239
|
+
existingOverlays.forEach((overlay) => overlay.remove());
|
|
240
|
+
try {
|
|
241
|
+
const sourceGroups = su3(circuitJson).source_group?.list().filter((x) => !!!x.is_subcircuit) || [];
|
|
242
|
+
const schematicComponents = su3(circuitJson).schematic_component?.list() || [];
|
|
243
|
+
const sourceGroupHierarchy = /* @__PURE__ */ new Map();
|
|
244
|
+
sourceGroups.forEach((group) => {
|
|
245
|
+
const groupWithParent = group;
|
|
246
|
+
if (groupWithParent.parent_source_group_id) {
|
|
247
|
+
const children = sourceGroupHierarchy.get(
|
|
248
|
+
groupWithParent.parent_source_group_id
|
|
249
|
+
) || [];
|
|
250
|
+
children.push(group.source_group_id);
|
|
251
|
+
sourceGroupHierarchy.set(
|
|
252
|
+
groupWithParent.parent_source_group_id,
|
|
253
|
+
children
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
const getAllDescendantSourceGroups = (sourceGroupId) => {
|
|
258
|
+
const descendants = [];
|
|
259
|
+
const children = sourceGroupHierarchy.get(sourceGroupId) || [];
|
|
260
|
+
for (const child of children) {
|
|
261
|
+
descendants.push(child);
|
|
262
|
+
descendants.push(...getAllDescendantSourceGroups(child));
|
|
263
|
+
}
|
|
264
|
+
return descendants;
|
|
265
|
+
};
|
|
266
|
+
const getGroupDepthLevel = (sourceGroupId) => {
|
|
267
|
+
const groupWithParent = sourceGroups.find(
|
|
268
|
+
(g) => g.source_group_id === sourceGroupId
|
|
278
269
|
);
|
|
279
|
-
if (
|
|
280
|
-
|
|
281
|
-
|
|
270
|
+
if (!groupWithParent?.parent_source_group_id) {
|
|
271
|
+
return 0;
|
|
272
|
+
}
|
|
273
|
+
return 1 + getGroupDepthLevel(groupWithParent.parent_source_group_id);
|
|
274
|
+
};
|
|
275
|
+
const hasMeaningfulGroups = sourceGroups.length > 0 && sourceGroups.some((group) => group.name && group.name.trim() !== "");
|
|
276
|
+
let groupsToRender = [];
|
|
277
|
+
if (hasMeaningfulGroups) {
|
|
278
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
279
|
+
for (const comp of schematicComponents) {
|
|
280
|
+
const sourceComp = su3(circuitJson).source_component.get(
|
|
281
|
+
comp.source_component_id
|
|
282
|
+
);
|
|
283
|
+
if (sourceComp?.source_group_id) {
|
|
284
|
+
if (!groupMap.has(sourceComp.source_group_id)) {
|
|
285
|
+
groupMap.set(sourceComp.source_group_id, []);
|
|
286
|
+
}
|
|
287
|
+
groupMap.get(sourceComp.source_group_id).push(comp);
|
|
282
288
|
}
|
|
283
|
-
groupMap.get(sourceComp.source_group_id).push(comp);
|
|
284
289
|
}
|
|
290
|
+
sourceGroups.forEach((group, index) => {
|
|
291
|
+
let groupComponents = groupMap.get(group.source_group_id) || [];
|
|
292
|
+
const descendantGroups = getAllDescendantSourceGroups(
|
|
293
|
+
group.source_group_id
|
|
294
|
+
);
|
|
295
|
+
for (const descendantGroupId of descendantGroups) {
|
|
296
|
+
const descendantComponents = groupMap.get(descendantGroupId) || [];
|
|
297
|
+
groupComponents = [...groupComponents, ...descendantComponents];
|
|
298
|
+
}
|
|
299
|
+
if (groupComponents.length > 0) {
|
|
300
|
+
const depthLevel = getGroupDepthLevel(group.source_group_id);
|
|
301
|
+
const hasChildren = getAllDescendantSourceGroups(group.source_group_id).length > 0;
|
|
302
|
+
if (group.name?.startsWith("unnamed_board")) return;
|
|
303
|
+
groupsToRender.push({
|
|
304
|
+
id: group.source_group_id,
|
|
305
|
+
name: group.name || `Group ${index + 1}`,
|
|
306
|
+
components: groupComponents,
|
|
307
|
+
color: GROUP_COLORS[index % GROUP_COLORS.length],
|
|
308
|
+
depthLevel,
|
|
309
|
+
hasChildren,
|
|
310
|
+
sourceGroupId: group.source_group_id
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
});
|
|
285
314
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
315
|
+
const viewBox = svg.viewBox.baseVal;
|
|
316
|
+
const svgRect = svg.getBoundingClientRect();
|
|
317
|
+
const scale = Math.min(
|
|
318
|
+
svgRect.width / viewBox.width,
|
|
319
|
+
svgRect.height / viewBox.height
|
|
320
|
+
) || 1;
|
|
321
|
+
groupsToRender.sort((a, b) => a.depthLevel - b.depthLevel);
|
|
322
|
+
groupsToRender.forEach((group) => {
|
|
323
|
+
if (group.components.length === 0) return;
|
|
324
|
+
const groupBounds = calculateGroupBounds(group.components, svg);
|
|
325
|
+
if (!groupBounds) return;
|
|
326
|
+
const basePadding = Math.max(
|
|
327
|
+
8,
|
|
328
|
+
Math.min(25, 15 / Math.max(scale, 0.3))
|
|
290
329
|
);
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
330
|
+
const hierarchyPadding = group.hasChildren ? basePadding * 0.6 : 0;
|
|
331
|
+
const totalPadding = basePadding + hierarchyPadding;
|
|
332
|
+
const baseStrokeWidth = Math.max(1, 2 / Math.max(scale, 0.5));
|
|
333
|
+
const strokeWidth = group.depthLevel === 0 ? baseStrokeWidth : baseStrokeWidth * 0.7;
|
|
334
|
+
const baseDashSize = Math.max(4, 8 / Math.max(scale, 0.5));
|
|
335
|
+
const dashMultiplier = group.hasChildren ? 1.3 : 1;
|
|
336
|
+
const dashSize = baseDashSize * dashMultiplier;
|
|
337
|
+
const gapSize = dashSize * 0.5;
|
|
338
|
+
const groupOverlay = document.createElementNS(
|
|
339
|
+
"http://www.w3.org/2000/svg",
|
|
340
|
+
"rect"
|
|
341
|
+
);
|
|
342
|
+
groupOverlay.setAttribute("class", "schematic-group-overlay");
|
|
343
|
+
groupOverlay.setAttribute(
|
|
344
|
+
"x",
|
|
345
|
+
(groupBounds.minX - totalPadding).toString()
|
|
346
|
+
);
|
|
347
|
+
groupOverlay.setAttribute(
|
|
348
|
+
"y",
|
|
349
|
+
(groupBounds.minY - totalPadding).toString()
|
|
350
|
+
);
|
|
351
|
+
groupOverlay.setAttribute(
|
|
352
|
+
"width",
|
|
353
|
+
(groupBounds.maxX - groupBounds.minX + totalPadding * 2).toString()
|
|
354
|
+
);
|
|
355
|
+
groupOverlay.setAttribute(
|
|
356
|
+
"height",
|
|
357
|
+
(groupBounds.maxY - groupBounds.minY + totalPadding * 2).toString()
|
|
358
|
+
);
|
|
359
|
+
groupOverlay.setAttribute("fill", "none");
|
|
360
|
+
groupOverlay.setAttribute("stroke", group.color);
|
|
361
|
+
groupOverlay.setAttribute("stroke-width", strokeWidth.toString());
|
|
362
|
+
groupOverlay.setAttribute(
|
|
363
|
+
"stroke-dasharray",
|
|
364
|
+
`${dashSize},${gapSize}`
|
|
365
|
+
);
|
|
366
|
+
groupOverlay.setAttribute("opacity", "0.8");
|
|
367
|
+
groupOverlay.setAttribute("rx", "4");
|
|
368
|
+
groupOverlay.setAttribute("ry", "4");
|
|
369
|
+
const baseFontSize = Math.max(
|
|
370
|
+
6,
|
|
371
|
+
Math.min(20, 14 / Math.max(scale, 0.2))
|
|
372
|
+
);
|
|
373
|
+
const fontSizeReduction = group.depthLevel === 0 || group.depthLevel === 1 ? 0 : group.depthLevel * 0.2;
|
|
374
|
+
const fontSize = baseFontSize * (1 - fontSizeReduction);
|
|
375
|
+
const labelPadding = Math.max(1, fontSize * 0.2);
|
|
376
|
+
const labelText = group.name;
|
|
377
|
+
const tempText = document.createElementNS(
|
|
378
|
+
"http://www.w3.org/2000/svg",
|
|
379
|
+
"text"
|
|
380
|
+
);
|
|
381
|
+
tempText.setAttribute("font-size", fontSize.toString());
|
|
382
|
+
tempText.setAttribute("font-family", "Arial, sans-serif");
|
|
383
|
+
tempText.textContent = labelText;
|
|
384
|
+
svg.appendChild(tempText);
|
|
385
|
+
const textBBox = tempText.getBBox();
|
|
386
|
+
svg.removeChild(tempText);
|
|
387
|
+
const labelWidth = textBBox.width + labelPadding * 2;
|
|
388
|
+
const labelHeight = fontSize + labelPadding * 2;
|
|
389
|
+
const labelX = groupBounds.minX - totalPadding;
|
|
390
|
+
const labelY = groupBounds.minY - totalPadding - labelHeight;
|
|
391
|
+
const labelBg = document.createElementNS(
|
|
392
|
+
"http://www.w3.org/2000/svg",
|
|
393
|
+
"rect"
|
|
394
|
+
);
|
|
395
|
+
labelBg.setAttribute("class", "schematic-group-overlay");
|
|
396
|
+
labelBg.setAttribute("x", labelX.toString());
|
|
397
|
+
labelBg.setAttribute("y", (labelY - labelHeight).toString());
|
|
398
|
+
labelBg.setAttribute("width", labelWidth.toString());
|
|
399
|
+
labelBg.setAttribute("height", labelHeight.toString());
|
|
400
|
+
labelBg.setAttribute("fill", "transparent");
|
|
401
|
+
labelBg.setAttribute("rx", "3");
|
|
402
|
+
labelBg.setAttribute("ry", "3");
|
|
403
|
+
const groupLabel = document.createElementNS(
|
|
404
|
+
"http://www.w3.org/2000/svg",
|
|
405
|
+
"text"
|
|
406
|
+
);
|
|
407
|
+
groupLabel.setAttribute("class", "schematic-group-overlay");
|
|
408
|
+
groupLabel.setAttribute("x", (labelX + labelPadding).toString());
|
|
409
|
+
groupLabel.setAttribute(
|
|
410
|
+
"y",
|
|
411
|
+
(labelY + labelHeight - labelPadding).toString()
|
|
412
|
+
);
|
|
413
|
+
groupLabel.setAttribute("fill", group.color);
|
|
414
|
+
groupLabel.setAttribute("font-size", fontSize.toString());
|
|
415
|
+
groupLabel.setAttribute("font-family", "Arial, sans-serif");
|
|
416
|
+
groupLabel.setAttribute(
|
|
417
|
+
"font-weight",
|
|
418
|
+
group.depthLevel === 0 ? "600" : "500"
|
|
419
|
+
);
|
|
420
|
+
groupLabel.setAttribute("stroke", group.color);
|
|
421
|
+
groupLabel.setAttribute(
|
|
422
|
+
"stroke-width",
|
|
423
|
+
Math.max(0.2, fontSize * 0.02).toString()
|
|
424
|
+
);
|
|
425
|
+
groupLabel.textContent = labelText;
|
|
426
|
+
svg.appendChild(groupOverlay);
|
|
427
|
+
svg.appendChild(labelBg);
|
|
428
|
+
svg.appendChild(groupLabel);
|
|
309
429
|
});
|
|
430
|
+
} catch (error) {
|
|
431
|
+
console.error("Error creating group overlays:", error);
|
|
310
432
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const scale = Math.min(
|
|
314
|
-
svgRect.width / viewBox.width,
|
|
315
|
-
svgRect.height / viewBox.height
|
|
316
|
-
) || 1;
|
|
317
|
-
groupsToRender.sort((a, b) => a.depthLevel - b.depthLevel);
|
|
318
|
-
groupsToRender.forEach((group) => {
|
|
319
|
-
if (group.components.length === 0) return;
|
|
320
|
-
const groupBounds = calculateGroupBounds(group.components, svg);
|
|
321
|
-
if (!groupBounds) return;
|
|
322
|
-
const basePadding = Math.max(8, Math.min(25, 15 / Math.max(scale, 0.3)));
|
|
323
|
-
const hierarchyPadding = group.hasChildren ? basePadding * 0.6 : 0;
|
|
324
|
-
const totalPadding = basePadding + hierarchyPadding;
|
|
325
|
-
const baseStrokeWidth = Math.max(1, 2 / Math.max(scale, 0.5));
|
|
326
|
-
const strokeWidth = group.depthLevel === 0 ? baseStrokeWidth : baseStrokeWidth * 0.7;
|
|
327
|
-
const baseDashSize = Math.max(4, 8 / Math.max(scale, 0.5));
|
|
328
|
-
const dashMultiplier = group.hasChildren ? 1.3 : 1;
|
|
329
|
-
const dashSize = baseDashSize * dashMultiplier;
|
|
330
|
-
const gapSize = dashSize * 0.5;
|
|
331
|
-
const groupOverlay = document.createElementNS(
|
|
332
|
-
"http://www.w3.org/2000/svg",
|
|
333
|
-
"rect"
|
|
334
|
-
);
|
|
335
|
-
groupOverlay.setAttribute("class", "schematic-group-overlay");
|
|
336
|
-
groupOverlay.setAttribute(
|
|
337
|
-
"x",
|
|
338
|
-
(groupBounds.minX - totalPadding).toString()
|
|
339
|
-
);
|
|
340
|
-
groupOverlay.setAttribute(
|
|
341
|
-
"y",
|
|
342
|
-
(groupBounds.minY - totalPadding).toString()
|
|
343
|
-
);
|
|
344
|
-
groupOverlay.setAttribute(
|
|
345
|
-
"width",
|
|
346
|
-
(groupBounds.maxX - groupBounds.minX + totalPadding * 2).toString()
|
|
347
|
-
);
|
|
348
|
-
groupOverlay.setAttribute(
|
|
349
|
-
"height",
|
|
350
|
-
(groupBounds.maxY - groupBounds.minY + totalPadding * 2).toString()
|
|
351
|
-
);
|
|
352
|
-
groupOverlay.setAttribute("fill", "none");
|
|
353
|
-
groupOverlay.setAttribute("stroke", group.color);
|
|
354
|
-
groupOverlay.setAttribute("stroke-width", strokeWidth.toString());
|
|
355
|
-
groupOverlay.setAttribute("stroke-dasharray", `${dashSize},${gapSize}`);
|
|
356
|
-
groupOverlay.setAttribute("opacity", "0.8");
|
|
357
|
-
groupOverlay.setAttribute("rx", "4");
|
|
358
|
-
groupOverlay.setAttribute("ry", "4");
|
|
359
|
-
const baseFontSize = Math.max(
|
|
360
|
-
6,
|
|
361
|
-
Math.min(20, 14 / Math.max(scale, 0.2))
|
|
362
|
-
);
|
|
363
|
-
const fontSizeReduction = group.depthLevel === 0 || group.depthLevel === 1 ? 0 : group.depthLevel * 0.2;
|
|
364
|
-
const fontSize = baseFontSize * (1 - fontSizeReduction);
|
|
365
|
-
const labelPadding = Math.max(1, fontSize * 0.2);
|
|
366
|
-
const labelText = group.name;
|
|
367
|
-
const tempText = document.createElementNS(
|
|
368
|
-
"http://www.w3.org/2000/svg",
|
|
369
|
-
"text"
|
|
370
|
-
);
|
|
371
|
-
tempText.setAttribute("font-size", fontSize.toString());
|
|
372
|
-
tempText.setAttribute("font-family", "Arial, sans-serif");
|
|
373
|
-
tempText.textContent = labelText;
|
|
374
|
-
svg.appendChild(tempText);
|
|
375
|
-
const textBBox = tempText.getBBox();
|
|
376
|
-
svg.removeChild(tempText);
|
|
377
|
-
const labelWidth = textBBox.width + labelPadding * 2;
|
|
378
|
-
const labelHeight = fontSize + labelPadding * 2;
|
|
379
|
-
const labelX = groupBounds.minX - totalPadding;
|
|
380
|
-
const labelY = groupBounds.minY - totalPadding - labelHeight;
|
|
381
|
-
const labelBg = document.createElementNS(
|
|
382
|
-
"http://www.w3.org/2000/svg",
|
|
383
|
-
"rect"
|
|
384
|
-
);
|
|
385
|
-
labelBg.setAttribute("class", "schematic-group-overlay");
|
|
386
|
-
labelBg.setAttribute("x", labelX.toString());
|
|
387
|
-
labelBg.setAttribute("y", (labelY - labelHeight).toString());
|
|
388
|
-
labelBg.setAttribute("width", labelWidth.toString());
|
|
389
|
-
labelBg.setAttribute("height", labelHeight.toString());
|
|
390
|
-
labelBg.setAttribute("fill", "transparent");
|
|
391
|
-
labelBg.setAttribute("rx", "3");
|
|
392
|
-
labelBg.setAttribute("ry", "3");
|
|
393
|
-
const groupLabel = document.createElementNS(
|
|
394
|
-
"http://www.w3.org/2000/svg",
|
|
395
|
-
"text"
|
|
396
|
-
);
|
|
397
|
-
groupLabel.setAttribute("class", "schematic-group-overlay");
|
|
398
|
-
groupLabel.setAttribute("x", (labelX + labelPadding).toString());
|
|
399
|
-
groupLabel.setAttribute(
|
|
400
|
-
"y",
|
|
401
|
-
(labelY + labelHeight - labelPadding).toString()
|
|
402
|
-
);
|
|
403
|
-
groupLabel.setAttribute("fill", group.color);
|
|
404
|
-
groupLabel.setAttribute("font-size", fontSize.toString());
|
|
405
|
-
groupLabel.setAttribute("font-family", "Arial, sans-serif");
|
|
406
|
-
groupLabel.setAttribute(
|
|
407
|
-
"font-weight",
|
|
408
|
-
group.depthLevel === 0 ? "600" : "500"
|
|
409
|
-
);
|
|
410
|
-
groupLabel.setAttribute("stroke", group.color);
|
|
411
|
-
groupLabel.setAttribute(
|
|
412
|
-
"stroke-width",
|
|
413
|
-
Math.max(0.2, fontSize * 0.02).toString()
|
|
414
|
-
);
|
|
415
|
-
groupLabel.textContent = labelText;
|
|
416
|
-
svg.appendChild(groupOverlay);
|
|
417
|
-
svg.appendChild(labelBg);
|
|
418
|
-
svg.appendChild(groupLabel);
|
|
419
|
-
});
|
|
420
|
-
} catch (error) {
|
|
421
|
-
console.error("Error creating group overlays:", error);
|
|
422
|
-
}
|
|
433
|
+
}, 10);
|
|
434
|
+
return () => clearTimeout(timeoutId);
|
|
423
435
|
}, [svgDivRef, circuitJsonKey, showGroups]);
|
|
424
436
|
};
|
|
425
437
|
function calculateGroupBounds(components, svg) {
|
|
@@ -834,7 +846,7 @@ import { su as su5 } from "@tscircuit/soup-util";
|
|
|
834
846
|
// package.json
|
|
835
847
|
var package_default = {
|
|
836
848
|
name: "@tscircuit/schematic-viewer",
|
|
837
|
-
version: "2.0.
|
|
849
|
+
version: "2.0.37",
|
|
838
850
|
main: "dist/index.js",
|
|
839
851
|
type: "module",
|
|
840
852
|
scripts: {
|