@particle-academy/react-fancy 2.9.0 → 3.0.0
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/README.md +20 -0
- package/dist/index.cjs +1 -1293
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -270
- package/dist/index.d.ts +40 -270
- package/dist/index.js +2 -1001
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/diagram.serializers-6RPUO46U.js +0 -273
- package/dist/diagram.serializers-6RPUO46U.js.map +0 -1
- package/docs/Canvas.md +0 -105
- package/docs/Diagram.md +0 -119
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { forwardRef, createContext, useId, useRef, useEffect, useState, useCallb
|
|
|
2
2
|
import { clsx } from 'clsx';
|
|
3
3
|
import { twMerge } from 'tailwind-merge';
|
|
4
4
|
import * as LucideIcons from 'lucide-react';
|
|
5
|
-
import { X, ChevronUp, ChevronDown, ChevronLeft, ChevronRight, Search, Menu, File, Upload, PanelLeftOpen, PanelLeftClose,
|
|
5
|
+
import { X, ChevronUp, ChevronDown, ChevronLeft, ChevronRight, Search, Menu, File, Upload, PanelLeftOpen, PanelLeftClose, Check, XCircle, AlertTriangle, Info } from 'lucide-react';
|
|
6
6
|
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
7
7
|
import { createPortal } from 'react-dom';
|
|
8
8
|
import { marked } from 'marked';
|
|
@@ -11454,1005 +11454,6 @@ var Kanban = Object.assign(KanbanRoot, {
|
|
|
11454
11454
|
Card: KanbanCard,
|
|
11455
11455
|
ColumnHandle: KanbanColumnHandle
|
|
11456
11456
|
});
|
|
11457
|
-
var CanvasContext = createContext(null);
|
|
11458
|
-
function useCanvas() {
|
|
11459
|
-
const ctx = useContext(CanvasContext);
|
|
11460
|
-
if (!ctx) throw new Error("useCanvas must be used within a Canvas component");
|
|
11461
|
-
return ctx;
|
|
11462
|
-
}
|
|
11463
|
-
function CanvasNode({ children, id, x, y, draggable, onPositionChange, className, style }) {
|
|
11464
|
-
const { registerNode, unregisterNode, viewport, gridSize, snapToGrid } = useCanvas();
|
|
11465
|
-
const nodeRef = useRef(null);
|
|
11466
|
-
const isDragging = useRef(false);
|
|
11467
|
-
const dragStart = useRef({ mouseX: 0, mouseY: 0, nodeX: 0, nodeY: 0 });
|
|
11468
|
-
useEffect(() => {
|
|
11469
|
-
const el = nodeRef.current;
|
|
11470
|
-
if (!el) return;
|
|
11471
|
-
const updateRect = () => {
|
|
11472
|
-
registerNode(id, { x, y, width: el.offsetWidth, height: el.offsetHeight });
|
|
11473
|
-
};
|
|
11474
|
-
updateRect();
|
|
11475
|
-
const observer = new ResizeObserver(updateRect);
|
|
11476
|
-
observer.observe(el);
|
|
11477
|
-
return () => {
|
|
11478
|
-
observer.disconnect();
|
|
11479
|
-
unregisterNode(id);
|
|
11480
|
-
};
|
|
11481
|
-
}, [id, x, y, registerNode, unregisterNode]);
|
|
11482
|
-
const handlePointerDown = useCallback(
|
|
11483
|
-
(e) => {
|
|
11484
|
-
if (!draggable || e.button !== 0) return;
|
|
11485
|
-
e.stopPropagation();
|
|
11486
|
-
isDragging.current = true;
|
|
11487
|
-
dragStart.current = { mouseX: e.clientX, mouseY: e.clientY, nodeX: x, nodeY: y };
|
|
11488
|
-
e.target.setPointerCapture(e.pointerId);
|
|
11489
|
-
},
|
|
11490
|
-
[draggable, x, y]
|
|
11491
|
-
);
|
|
11492
|
-
const handlePointerMove = useCallback(
|
|
11493
|
-
(e) => {
|
|
11494
|
-
if (!isDragging.current) return;
|
|
11495
|
-
const dx = (e.clientX - dragStart.current.mouseX) / viewport.zoom;
|
|
11496
|
-
const dy = (e.clientY - dragStart.current.mouseY) / viewport.zoom;
|
|
11497
|
-
let nx = dragStart.current.nodeX + dx;
|
|
11498
|
-
let ny = dragStart.current.nodeY + dy;
|
|
11499
|
-
if (snapToGrid && gridSize > 0) {
|
|
11500
|
-
nx = Math.round(nx / gridSize) * gridSize;
|
|
11501
|
-
ny = Math.round(ny / gridSize) * gridSize;
|
|
11502
|
-
}
|
|
11503
|
-
onPositionChange?.(nx, ny);
|
|
11504
|
-
},
|
|
11505
|
-
[viewport.zoom, onPositionChange, snapToGrid, gridSize]
|
|
11506
|
-
);
|
|
11507
|
-
const handlePointerUp = useCallback(() => {
|
|
11508
|
-
isDragging.current = false;
|
|
11509
|
-
}, []);
|
|
11510
|
-
return /* @__PURE__ */ jsx(
|
|
11511
|
-
"div",
|
|
11512
|
-
{
|
|
11513
|
-
ref: nodeRef,
|
|
11514
|
-
"data-react-fancy-canvas-node": "",
|
|
11515
|
-
"data-node-id": id,
|
|
11516
|
-
className: cn("absolute", draggable && "cursor-grab active:cursor-grabbing", className),
|
|
11517
|
-
style: { left: x, top: y, ...style },
|
|
11518
|
-
onPointerDown: handlePointerDown,
|
|
11519
|
-
onPointerMove: handlePointerMove,
|
|
11520
|
-
onPointerUp: handlePointerUp,
|
|
11521
|
-
children
|
|
11522
|
-
}
|
|
11523
|
-
);
|
|
11524
|
-
}
|
|
11525
|
-
CanvasNode.displayName = "CanvasNode";
|
|
11526
|
-
|
|
11527
|
-
// src/components/Canvas/canvas.utils.ts
|
|
11528
|
-
function getAnchorPoint(rect, anchor, otherRect) {
|
|
11529
|
-
const cx = rect.x + rect.width / 2;
|
|
11530
|
-
const cy = rect.y + rect.height / 2;
|
|
11531
|
-
if (anchor === "auto" && otherRect) {
|
|
11532
|
-
const ocx = otherRect.x + otherRect.width / 2;
|
|
11533
|
-
const ocy = otherRect.y + otherRect.height / 2;
|
|
11534
|
-
const dx = ocx - cx;
|
|
11535
|
-
const dy = ocy - cy;
|
|
11536
|
-
if (Math.abs(dx) > Math.abs(dy)) {
|
|
11537
|
-
return dx > 0 ? { x: rect.x + rect.width, y: cy } : { x: rect.x, y: cy };
|
|
11538
|
-
}
|
|
11539
|
-
return dy > 0 ? { x: cx, y: rect.y + rect.height } : { x: cx, y: rect.y };
|
|
11540
|
-
}
|
|
11541
|
-
switch (anchor) {
|
|
11542
|
-
case "top":
|
|
11543
|
-
return { x: cx, y: rect.y };
|
|
11544
|
-
case "bottom":
|
|
11545
|
-
return { x: cx, y: rect.y + rect.height };
|
|
11546
|
-
case "left":
|
|
11547
|
-
return { x: rect.x, y: cy };
|
|
11548
|
-
case "right":
|
|
11549
|
-
return { x: rect.x + rect.width, y: cy };
|
|
11550
|
-
case "center":
|
|
11551
|
-
return { x: cx, y: cy };
|
|
11552
|
-
default:
|
|
11553
|
-
return { x: cx, y: cy };
|
|
11554
|
-
}
|
|
11555
|
-
}
|
|
11556
|
-
function bezierPath(from, to) {
|
|
11557
|
-
const dx = Math.abs(to.x - from.x);
|
|
11558
|
-
const dy = Math.abs(to.y - from.y);
|
|
11559
|
-
if (dx > dy) {
|
|
11560
|
-
const offset2 = dx * 0.5;
|
|
11561
|
-
const cp1x = from.x + (to.x > from.x ? offset2 : -offset2);
|
|
11562
|
-
const cp2x = to.x + (to.x > from.x ? -offset2 : offset2);
|
|
11563
|
-
return `M${from.x},${from.y} C${cp1x},${from.y} ${cp2x},${to.y} ${to.x},${to.y}`;
|
|
11564
|
-
}
|
|
11565
|
-
const offset = Math.max(dy * 0.5, 30);
|
|
11566
|
-
const cp1y = from.y + (to.y > from.y ? offset : -offset);
|
|
11567
|
-
const cp2y = to.y + (to.y > from.y ? -offset : offset);
|
|
11568
|
-
return `M${from.x},${from.y} C${from.x},${cp1y} ${to.x},${cp2y} ${to.x},${to.y}`;
|
|
11569
|
-
}
|
|
11570
|
-
function stepPath(from, to) {
|
|
11571
|
-
const midX = (from.x + to.x) / 2;
|
|
11572
|
-
return `M${from.x},${from.y} H${midX} V${to.y} H${to.x}`;
|
|
11573
|
-
}
|
|
11574
|
-
function straightPath(from, to) {
|
|
11575
|
-
return `M${from.x},${from.y} L${to.x},${to.y}`;
|
|
11576
|
-
}
|
|
11577
|
-
function getEdgePath(from, to, curve = "bezier") {
|
|
11578
|
-
switch (curve) {
|
|
11579
|
-
case "bezier":
|
|
11580
|
-
return bezierPath(from, to);
|
|
11581
|
-
case "step":
|
|
11582
|
-
return stepPath(from, to);
|
|
11583
|
-
case "straight":
|
|
11584
|
-
return straightPath(from, to);
|
|
11585
|
-
}
|
|
11586
|
-
}
|
|
11587
|
-
function CanvasEdge({
|
|
11588
|
-
from,
|
|
11589
|
-
to,
|
|
11590
|
-
fromAnchor = "auto",
|
|
11591
|
-
toAnchor = "auto",
|
|
11592
|
-
curve = "bezier",
|
|
11593
|
-
color = "currentColor",
|
|
11594
|
-
strokeWidth = 2,
|
|
11595
|
-
dashed = false,
|
|
11596
|
-
animated = false,
|
|
11597
|
-
label,
|
|
11598
|
-
className,
|
|
11599
|
-
markerStart,
|
|
11600
|
-
markerEnd
|
|
11601
|
-
}) {
|
|
11602
|
-
const { nodeRects, registryVersion } = useCanvas();
|
|
11603
|
-
const path = useMemo(() => {
|
|
11604
|
-
const fromRect = nodeRects.get(from);
|
|
11605
|
-
const toRect = nodeRects.get(to);
|
|
11606
|
-
if (!fromRect || !toRect) return null;
|
|
11607
|
-
const fromPt = getAnchorPoint(fromRect, fromAnchor, toRect);
|
|
11608
|
-
const toPt = getAnchorPoint(toRect, toAnchor, fromRect);
|
|
11609
|
-
return {
|
|
11610
|
-
d: getEdgePath(fromPt, toPt, curve),
|
|
11611
|
-
midX: (fromPt.x + toPt.x) / 2,
|
|
11612
|
-
midY: (fromPt.y + toPt.y) / 2
|
|
11613
|
-
};
|
|
11614
|
-
}, [from, to, fromAnchor, toAnchor, curve, nodeRects, registryVersion]);
|
|
11615
|
-
if (!path) return null;
|
|
11616
|
-
return /* @__PURE__ */ jsxs("g", { "data-react-fancy-canvas-edge": "", className: cn("text-zinc-300 dark:text-zinc-600", className), children: [
|
|
11617
|
-
/* @__PURE__ */ jsx(
|
|
11618
|
-
"path",
|
|
11619
|
-
{
|
|
11620
|
-
d: path.d,
|
|
11621
|
-
fill: "none",
|
|
11622
|
-
stroke: color,
|
|
11623
|
-
strokeWidth,
|
|
11624
|
-
strokeDasharray: dashed ? "6 4" : void 0,
|
|
11625
|
-
markerStart: markerStart ? `url(#${markerStart})` : void 0,
|
|
11626
|
-
markerEnd: markerEnd ? `url(#${markerEnd})` : void 0,
|
|
11627
|
-
className: animated ? "animate-[dash_1s_linear_infinite]" : "",
|
|
11628
|
-
style: animated ? { strokeDasharray: "8 4" } : void 0
|
|
11629
|
-
}
|
|
11630
|
-
),
|
|
11631
|
-
label && /* @__PURE__ */ jsx("foreignObject", { x: path.midX - 40, y: path.midY - 12, width: 80, height: 24, children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center text-xs text-zinc-500", children: label }) })
|
|
11632
|
-
] });
|
|
11633
|
-
}
|
|
11634
|
-
CanvasEdge.displayName = "CanvasEdge";
|
|
11635
|
-
function CanvasMinimap({ width = 150, height = 100, className }) {
|
|
11636
|
-
const { nodeRects, registryVersion, viewport } = useCanvas();
|
|
11637
|
-
const bounds = useMemo(() => {
|
|
11638
|
-
if (nodeRects.size === 0) return { minX: 0, minY: 0, maxX: 500, maxY: 300 };
|
|
11639
|
-
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
11640
|
-
nodeRects.forEach((r) => {
|
|
11641
|
-
minX = Math.min(minX, r.x);
|
|
11642
|
-
minY = Math.min(minY, r.y);
|
|
11643
|
-
maxX = Math.max(maxX, r.x + r.width);
|
|
11644
|
-
maxY = Math.max(maxY, r.y + r.height);
|
|
11645
|
-
});
|
|
11646
|
-
const padding = 50;
|
|
11647
|
-
return { minX: minX - padding, minY: minY - padding, maxX: maxX + padding, maxY: maxY + padding };
|
|
11648
|
-
}, [nodeRects, registryVersion]);
|
|
11649
|
-
const scaleX = width / (bounds.maxX - bounds.minX || 1);
|
|
11650
|
-
const scaleY = height / (bounds.maxY - bounds.minY || 1);
|
|
11651
|
-
const scale = Math.min(scaleX, scaleY);
|
|
11652
|
-
return /* @__PURE__ */ jsx(
|
|
11653
|
-
"div",
|
|
11654
|
-
{
|
|
11655
|
-
"data-react-fancy-canvas-minimap": "",
|
|
11656
|
-
className: cn(
|
|
11657
|
-
"absolute right-3 bottom-3 overflow-hidden rounded-lg border border-zinc-200 bg-white/90 dark:border-zinc-700 dark:bg-zinc-900/90",
|
|
11658
|
-
className
|
|
11659
|
-
),
|
|
11660
|
-
style: { width, height },
|
|
11661
|
-
children: /* @__PURE__ */ jsxs("svg", { width, height, children: [
|
|
11662
|
-
Array.from(nodeRects.entries()).map(([id, rect]) => /* @__PURE__ */ jsx(
|
|
11663
|
-
"rect",
|
|
11664
|
-
{
|
|
11665
|
-
x: (rect.x - bounds.minX) * scale,
|
|
11666
|
-
y: (rect.y - bounds.minY) * scale,
|
|
11667
|
-
width: Math.max(rect.width * scale, 4),
|
|
11668
|
-
height: Math.max(rect.height * scale, 3),
|
|
11669
|
-
rx: 1,
|
|
11670
|
-
className: "fill-blue-400/60"
|
|
11671
|
-
},
|
|
11672
|
-
id
|
|
11673
|
-
)),
|
|
11674
|
-
/* @__PURE__ */ jsx(
|
|
11675
|
-
"rect",
|
|
11676
|
-
{
|
|
11677
|
-
x: (-viewport.panX / viewport.zoom - bounds.minX) * scale,
|
|
11678
|
-
y: (-viewport.panY / viewport.zoom - bounds.minY) * scale,
|
|
11679
|
-
width: (width / viewport.zoom / scale > 0 ? width / viewport.zoom : width) * scale / (bounds.maxX - bounds.minX || 1) * (bounds.maxX - bounds.minX),
|
|
11680
|
-
height: (height / viewport.zoom / scale > 0 ? height / viewport.zoom : height) * scale / (bounds.maxY - bounds.minY || 1) * (bounds.maxY - bounds.minY),
|
|
11681
|
-
fill: "none",
|
|
11682
|
-
stroke: "currentColor",
|
|
11683
|
-
strokeWidth: 1,
|
|
11684
|
-
className: "text-blue-500"
|
|
11685
|
-
}
|
|
11686
|
-
)
|
|
11687
|
-
] })
|
|
11688
|
-
}
|
|
11689
|
-
);
|
|
11690
|
-
}
|
|
11691
|
-
CanvasMinimap.displayName = "CanvasMinimap";
|
|
11692
|
-
function CanvasControls({
|
|
11693
|
-
className,
|
|
11694
|
-
showZoomIn = true,
|
|
11695
|
-
showZoomOut = true,
|
|
11696
|
-
showReset = true,
|
|
11697
|
-
showFitAll = true
|
|
11698
|
-
}) {
|
|
11699
|
-
const { setViewport, nodeRects, containerRef } = useCanvas();
|
|
11700
|
-
const zoomIn = () => setViewport((v) => ({ ...v, zoom: Math.min(3, v.zoom * 1.25) }));
|
|
11701
|
-
const zoomOut = () => setViewport((v) => ({ ...v, zoom: Math.max(0.1, v.zoom / 1.25) }));
|
|
11702
|
-
const reset = () => setViewport({ panX: 0, panY: 0, zoom: 1 });
|
|
11703
|
-
const fitAll = () => {
|
|
11704
|
-
const container = containerRef.current;
|
|
11705
|
-
if (!container || nodeRects.size === 0) return reset();
|
|
11706
|
-
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
11707
|
-
nodeRects.forEach((r) => {
|
|
11708
|
-
minX = Math.min(minX, r.x);
|
|
11709
|
-
minY = Math.min(minY, r.y);
|
|
11710
|
-
maxX = Math.max(maxX, r.x + r.width);
|
|
11711
|
-
maxY = Math.max(maxY, r.y + r.height);
|
|
11712
|
-
});
|
|
11713
|
-
const padding = 40;
|
|
11714
|
-
const contentW = maxX - minX + padding * 2;
|
|
11715
|
-
const contentH = maxY - minY + padding * 2;
|
|
11716
|
-
const cw = container.clientWidth;
|
|
11717
|
-
const ch = container.clientHeight;
|
|
11718
|
-
const zoom = Math.min(cw / contentW, ch / contentH, 1.5);
|
|
11719
|
-
const panX = (cw - contentW * zoom) / 2 - minX * zoom + padding * zoom;
|
|
11720
|
-
const panY = (ch - contentH * zoom) / 2 - minY * zoom + padding * zoom;
|
|
11721
|
-
setViewport({ panX, panY, zoom });
|
|
11722
|
-
};
|
|
11723
|
-
const btnClass = "flex h-8 w-8 items-center justify-center rounded-md text-zinc-500 hover:bg-zinc-100 hover:text-zinc-700 dark:hover:bg-zinc-800 dark:hover:text-zinc-300 transition-colors";
|
|
11724
|
-
return /* @__PURE__ */ jsxs(
|
|
11725
|
-
"div",
|
|
11726
|
-
{
|
|
11727
|
-
"data-react-fancy-canvas-controls": "",
|
|
11728
|
-
className: cn(
|
|
11729
|
-
"absolute bottom-3 left-3 flex gap-1 rounded-lg border border-zinc-200 bg-white/90 p-1 shadow-sm dark:border-zinc-700 dark:bg-zinc-900/90",
|
|
11730
|
-
className
|
|
11731
|
-
),
|
|
11732
|
-
children: [
|
|
11733
|
-
showZoomIn && /* @__PURE__ */ jsx("button", { type: "button", onClick: zoomIn, className: btnClass, "aria-label": "Zoom in", children: /* @__PURE__ */ jsx(ZoomIn, { size: 16 }) }),
|
|
11734
|
-
showZoomOut && /* @__PURE__ */ jsx("button", { type: "button", onClick: zoomOut, className: btnClass, "aria-label": "Zoom out", children: /* @__PURE__ */ jsx(ZoomOut, { size: 16 }) }),
|
|
11735
|
-
showReset && /* @__PURE__ */ jsx("button", { type: "button", onClick: reset, className: btnClass, "aria-label": "Reset view", children: /* @__PURE__ */ jsx(RotateCcw, { size: 16 }) }),
|
|
11736
|
-
showFitAll && /* @__PURE__ */ jsx("button", { type: "button", onClick: fitAll, className: btnClass, "aria-label": "Fit all", children: /* @__PURE__ */ jsx(Maximize, { size: 16 }) })
|
|
11737
|
-
]
|
|
11738
|
-
}
|
|
11739
|
-
);
|
|
11740
|
-
}
|
|
11741
|
-
CanvasControls.displayName = "CanvasControls";
|
|
11742
|
-
var DEFAULT_VIEWPORT = { panX: 0, panY: 0, zoom: 1 };
|
|
11743
|
-
function CanvasRoot({
|
|
11744
|
-
children,
|
|
11745
|
-
viewport: controlledViewport,
|
|
11746
|
-
defaultViewport = DEFAULT_VIEWPORT,
|
|
11747
|
-
onViewportChange,
|
|
11748
|
-
minZoom = 0.1,
|
|
11749
|
-
maxZoom = 3,
|
|
11750
|
-
pannable = true,
|
|
11751
|
-
zoomable = true,
|
|
11752
|
-
showGrid = false,
|
|
11753
|
-
gridStyle = "dots",
|
|
11754
|
-
gridSize = 20,
|
|
11755
|
-
gridColor = "rgb(161 161 170 / 0.3)",
|
|
11756
|
-
snapToGrid = false,
|
|
11757
|
-
fitOnMount = false,
|
|
11758
|
-
className,
|
|
11759
|
-
style
|
|
11760
|
-
}) {
|
|
11761
|
-
const containerRef = useRef(null);
|
|
11762
|
-
const [viewport, setViewport] = useControllableState(controlledViewport, defaultViewport, onViewportChange);
|
|
11763
|
-
const { registerNode, unregisterNode, nodeRects, version: registryVersion } = useNodeRegistry();
|
|
11764
|
-
const { containerProps } = usePanZoom({
|
|
11765
|
-
viewport,
|
|
11766
|
-
setViewport,
|
|
11767
|
-
minZoom,
|
|
11768
|
-
maxZoom,
|
|
11769
|
-
pannable,
|
|
11770
|
-
zoomable,
|
|
11771
|
-
containerRef
|
|
11772
|
-
});
|
|
11773
|
-
const ctx = useMemo(
|
|
11774
|
-
() => ({ viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion, containerRef, gridSize, snapToGrid }),
|
|
11775
|
-
[viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion, gridSize, snapToGrid]
|
|
11776
|
-
);
|
|
11777
|
-
const hasFitted = useRef(false);
|
|
11778
|
-
useEffect(() => {
|
|
11779
|
-
if (!fitOnMount || hasFitted.current || nodeRects.size === 0) return;
|
|
11780
|
-
const container = containerRef.current;
|
|
11781
|
-
if (!container || container.clientWidth === 0) return;
|
|
11782
|
-
hasFitted.current = true;
|
|
11783
|
-
requestAnimationFrame(() => {
|
|
11784
|
-
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
11785
|
-
nodeRects.forEach((r) => {
|
|
11786
|
-
minX = Math.min(minX, r.x);
|
|
11787
|
-
minY = Math.min(minY, r.y);
|
|
11788
|
-
maxX = Math.max(maxX, r.x + r.width);
|
|
11789
|
-
maxY = Math.max(maxY, r.y + r.height);
|
|
11790
|
-
});
|
|
11791
|
-
const padding = 40;
|
|
11792
|
-
const contentW = maxX - minX + padding * 2;
|
|
11793
|
-
const contentH = maxY - minY + padding * 2;
|
|
11794
|
-
const cw = container.clientWidth;
|
|
11795
|
-
const ch = container.clientHeight;
|
|
11796
|
-
const zoom = Math.min(cw / contentW, ch / contentH, 1.5);
|
|
11797
|
-
const panX = (cw - contentW * zoom) / 2 - minX * zoom + padding * zoom;
|
|
11798
|
-
const panY = (ch - contentH * zoom) / 2 - minY * zoom + padding * zoom;
|
|
11799
|
-
setViewport({ panX, panY, zoom });
|
|
11800
|
-
});
|
|
11801
|
-
}, [fitOnMount, nodeRects, registryVersion, setViewport]);
|
|
11802
|
-
const edges = [];
|
|
11803
|
-
const others = [];
|
|
11804
|
-
const overlays = [];
|
|
11805
|
-
Children.forEach(children, (child) => {
|
|
11806
|
-
const el = child;
|
|
11807
|
-
if (!el || !el.type) return;
|
|
11808
|
-
const elType = el.type;
|
|
11809
|
-
if (elType === CanvasEdge || elType?._isCanvasEdge) {
|
|
11810
|
-
edges.push(el);
|
|
11811
|
-
} else if (elType === CanvasMinimap || elType === CanvasControls) {
|
|
11812
|
-
overlays.push(el);
|
|
11813
|
-
} else {
|
|
11814
|
-
others.push(el);
|
|
11815
|
-
}
|
|
11816
|
-
});
|
|
11817
|
-
return /* @__PURE__ */ jsx(CanvasContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxs(
|
|
11818
|
-
"div",
|
|
11819
|
-
{
|
|
11820
|
-
ref: containerRef,
|
|
11821
|
-
"data-react-fancy-canvas": "",
|
|
11822
|
-
className: cn("relative overflow-hidden", className),
|
|
11823
|
-
style: { touchAction: "none", ...style },
|
|
11824
|
-
...containerProps,
|
|
11825
|
-
children: [
|
|
11826
|
-
/* @__PURE__ */ jsx(
|
|
11827
|
-
"div",
|
|
11828
|
-
{
|
|
11829
|
-
"data-canvas-bg": "",
|
|
11830
|
-
className: "absolute inset-0",
|
|
11831
|
-
style: showGrid && gridStyle !== "none" ? gridStyle === "lines" ? {
|
|
11832
|
-
backgroundImage: `linear-gradient(to right, ${gridColor} 1px, transparent 1px), linear-gradient(to bottom, ${gridColor} 1px, transparent 1px)`,
|
|
11833
|
-
backgroundSize: `${gridSize * viewport.zoom}px ${gridSize * viewport.zoom}px`,
|
|
11834
|
-
backgroundPosition: `${viewport.panX}px ${viewport.panY}px`
|
|
11835
|
-
} : {
|
|
11836
|
-
backgroundImage: `radial-gradient(circle, ${gridColor} 1px, transparent 1px)`,
|
|
11837
|
-
backgroundSize: `${gridSize * viewport.zoom}px ${gridSize * viewport.zoom}px`,
|
|
11838
|
-
backgroundPosition: `${viewport.panX}px ${viewport.panY}px`
|
|
11839
|
-
} : void 0
|
|
11840
|
-
}
|
|
11841
|
-
),
|
|
11842
|
-
/* @__PURE__ */ jsx(
|
|
11843
|
-
"div",
|
|
11844
|
-
{
|
|
11845
|
-
className: "absolute origin-top-left",
|
|
11846
|
-
style: {
|
|
11847
|
-
transform: `translate(${viewport.panX}px, ${viewport.panY}px) scale(${viewport.zoom})`
|
|
11848
|
-
},
|
|
11849
|
-
children: others
|
|
11850
|
-
}
|
|
11851
|
-
),
|
|
11852
|
-
/* @__PURE__ */ jsxs(
|
|
11853
|
-
"svg",
|
|
11854
|
-
{
|
|
11855
|
-
className: "pointer-events-none absolute inset-0 h-full w-full",
|
|
11856
|
-
style: {
|
|
11857
|
-
transform: `translate(${viewport.panX}px, ${viewport.panY}px) scale(${viewport.zoom})`,
|
|
11858
|
-
transformOrigin: "0 0"
|
|
11859
|
-
},
|
|
11860
|
-
children: [
|
|
11861
|
-
/* @__PURE__ */ jsxs("defs", { children: [
|
|
11862
|
-
/* @__PURE__ */ jsx("marker", { id: "canvas-arrow", viewBox: "0 0 10 10", refX: "10", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto", children: /* @__PURE__ */ jsx("path", { d: "M0,0 L10,5 L0,10 Z", fill: "#71717a" }) }),
|
|
11863
|
-
/* @__PURE__ */ jsx("marker", { id: "canvas-circle", viewBox: "0 0 10 10", refX: "5", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto", children: /* @__PURE__ */ jsx("circle", { cx: "5", cy: "5", r: "3.5", fill: "#71717a" }) }),
|
|
11864
|
-
/* @__PURE__ */ jsx("marker", { id: "canvas-diamond", viewBox: "0 0 12 12", refX: "6", refY: "6", markerWidth: "10", markerHeight: "10", orient: "auto", children: /* @__PURE__ */ jsx("polygon", { points: "6,0 12,6 6,12 0,6", fill: "none", stroke: "#71717a", strokeWidth: "1.5" }) }),
|
|
11865
|
-
/* @__PURE__ */ jsx("marker", { id: "canvas-one", viewBox: "0 0 2 16", refX: "1", refY: "8", markerWidth: "2", markerHeight: "14", orient: "auto", children: /* @__PURE__ */ jsx("line", { x1: "1", y1: "0", x2: "1", y2: "16", stroke: "#71717a", strokeWidth: "2" }) }),
|
|
11866
|
-
/* @__PURE__ */ jsxs("marker", { id: "canvas-crow-foot", viewBox: "0 0 16 16", refX: "16", refY: "8", markerWidth: "14", markerHeight: "14", orient: "auto", children: [
|
|
11867
|
-
/* @__PURE__ */ jsx("line", { x1: "16", y1: "8", x2: "0", y2: "0", stroke: "#71717a", strokeWidth: "2", strokeLinecap: "round" }),
|
|
11868
|
-
/* @__PURE__ */ jsx("line", { x1: "16", y1: "8", x2: "0", y2: "8", stroke: "#71717a", strokeWidth: "2", strokeLinecap: "round" }),
|
|
11869
|
-
/* @__PURE__ */ jsx("line", { x1: "16", y1: "8", x2: "0", y2: "16", stroke: "#71717a", strokeWidth: "2", strokeLinecap: "round" }),
|
|
11870
|
-
/* @__PURE__ */ jsx("line", { x1: "16", y1: "0", x2: "16", y2: "16", stroke: "#71717a", strokeWidth: "2", strokeLinecap: "round" })
|
|
11871
|
-
] })
|
|
11872
|
-
] }),
|
|
11873
|
-
edges
|
|
11874
|
-
]
|
|
11875
|
-
}
|
|
11876
|
-
),
|
|
11877
|
-
overlays
|
|
11878
|
-
]
|
|
11879
|
-
}
|
|
11880
|
-
) });
|
|
11881
|
-
}
|
|
11882
|
-
var Canvas = Object.assign(CanvasRoot, {
|
|
11883
|
-
Node: CanvasNode,
|
|
11884
|
-
Edge: CanvasEdge,
|
|
11885
|
-
Minimap: CanvasMinimap,
|
|
11886
|
-
Controls: CanvasControls
|
|
11887
|
-
});
|
|
11888
|
-
var DiagramContext = createContext(null);
|
|
11889
|
-
function useDiagram() {
|
|
11890
|
-
const ctx = useContext(DiagramContext);
|
|
11891
|
-
if (!ctx) {
|
|
11892
|
-
throw new Error("useDiagram must be used within a <Diagram> component");
|
|
11893
|
-
}
|
|
11894
|
-
return ctx;
|
|
11895
|
-
}
|
|
11896
|
-
function DiagramField({
|
|
11897
|
-
name,
|
|
11898
|
-
type,
|
|
11899
|
-
primary = false,
|
|
11900
|
-
foreign = false,
|
|
11901
|
-
nullable = false,
|
|
11902
|
-
className
|
|
11903
|
-
}) {
|
|
11904
|
-
return /* @__PURE__ */ jsxs(
|
|
11905
|
-
"div",
|
|
11906
|
-
{
|
|
11907
|
-
"data-react-fancy-diagram-field": "",
|
|
11908
|
-
className: cn(
|
|
11909
|
-
"flex items-center justify-between gap-2 border-t border-zinc-200 px-3 py-1 text-sm dark:border-zinc-700",
|
|
11910
|
-
className
|
|
11911
|
-
),
|
|
11912
|
-
children: [
|
|
11913
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
11914
|
-
primary && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded bg-blue-100 px-1 py-0.5 text-[10px] font-semibold leading-none text-blue-700 dark:bg-blue-900/50 dark:text-blue-300", children: "PK" }),
|
|
11915
|
-
foreign && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded bg-amber-100 px-1 py-0.5 text-[10px] font-semibold leading-none text-amber-700 dark:bg-amber-900/50 dark:text-amber-300", children: "FK" }),
|
|
11916
|
-
/* @__PURE__ */ jsx("span", { className: "text-zinc-800 dark:text-zinc-200", children: name })
|
|
11917
|
-
] }),
|
|
11918
|
-
type && /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-xs text-zinc-400 dark:text-zinc-500", children: [
|
|
11919
|
-
type,
|
|
11920
|
-
nullable && "?"
|
|
11921
|
-
] })
|
|
11922
|
-
]
|
|
11923
|
-
}
|
|
11924
|
-
);
|
|
11925
|
-
}
|
|
11926
|
-
DiagramField.displayName = "DiagramField";
|
|
11927
|
-
function DiagramEntity({
|
|
11928
|
-
children,
|
|
11929
|
-
id: idProp,
|
|
11930
|
-
name,
|
|
11931
|
-
x = 0,
|
|
11932
|
-
y = 0,
|
|
11933
|
-
color = "bg-blue-600 dark:bg-blue-500",
|
|
11934
|
-
draggable,
|
|
11935
|
-
onPositionChange,
|
|
11936
|
-
className
|
|
11937
|
-
}) {
|
|
11938
|
-
const id = idProp ?? name;
|
|
11939
|
-
const fields = [];
|
|
11940
|
-
const other = [];
|
|
11941
|
-
Children.forEach(children, (child) => {
|
|
11942
|
-
const el = child;
|
|
11943
|
-
if (!el || !el.type) return;
|
|
11944
|
-
if (el.type === DiagramField) {
|
|
11945
|
-
fields.push(el);
|
|
11946
|
-
} else {
|
|
11947
|
-
other.push(el);
|
|
11948
|
-
}
|
|
11949
|
-
});
|
|
11950
|
-
return /* @__PURE__ */ jsx(Canvas.Node, { id, x, y, draggable, onPositionChange, children: /* @__PURE__ */ jsxs(
|
|
11951
|
-
"div",
|
|
11952
|
-
{
|
|
11953
|
-
"data-react-fancy-diagram-entity": "",
|
|
11954
|
-
"data-entity-id": id,
|
|
11955
|
-
className: cn(
|
|
11956
|
-
"w-[220px] overflow-hidden rounded-lg border border-zinc-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-800",
|
|
11957
|
-
className
|
|
11958
|
-
),
|
|
11959
|
-
children: [
|
|
11960
|
-
/* @__PURE__ */ jsx(
|
|
11961
|
-
"div",
|
|
11962
|
-
{
|
|
11963
|
-
className: cn(
|
|
11964
|
-
"px-3 py-2 text-sm font-semibold text-white",
|
|
11965
|
-
color
|
|
11966
|
-
),
|
|
11967
|
-
children: name
|
|
11968
|
-
}
|
|
11969
|
-
),
|
|
11970
|
-
fields.length > 0 && /* @__PURE__ */ jsx("div", { children: fields }),
|
|
11971
|
-
other
|
|
11972
|
-
]
|
|
11973
|
-
}
|
|
11974
|
-
) });
|
|
11975
|
-
}
|
|
11976
|
-
DiagramEntity.displayName = "DiagramEntity";
|
|
11977
|
-
var HEADER_HEIGHT = 36;
|
|
11978
|
-
var FIELD_HEIGHT = 29;
|
|
11979
|
-
var SYMBOL_SIZE = 12;
|
|
11980
|
-
function oneSymbol(pt, direction) {
|
|
11981
|
-
const s = SYMBOL_SIZE * 0.6;
|
|
11982
|
-
switch (direction) {
|
|
11983
|
-
case "left":
|
|
11984
|
-
case "right":
|
|
11985
|
-
return `M${pt.x},${pt.y - s} L${pt.x},${pt.y + s}`;
|
|
11986
|
-
case "up":
|
|
11987
|
-
case "down":
|
|
11988
|
-
return `M${pt.x - s},${pt.y} L${pt.x + s},${pt.y}`;
|
|
11989
|
-
}
|
|
11990
|
-
}
|
|
11991
|
-
function crowFootSymbol(pt, direction) {
|
|
11992
|
-
const s = SYMBOL_SIZE;
|
|
11993
|
-
const spread = s * 0.8;
|
|
11994
|
-
let tip;
|
|
11995
|
-
switch (direction) {
|
|
11996
|
-
case "right":
|
|
11997
|
-
tip = { x: pt.x - s, y: pt.y };
|
|
11998
|
-
return [
|
|
11999
|
-
`M${pt.x},${pt.y - spread} L${tip.x},${tip.y}`,
|
|
12000
|
-
`M${pt.x},${pt.y} L${tip.x},${tip.y}`,
|
|
12001
|
-
`M${pt.x},${pt.y + spread} L${tip.x},${tip.y}`,
|
|
12002
|
-
// bar at entity edge
|
|
12003
|
-
`M${pt.x},${pt.y - spread} L${pt.x},${pt.y + spread}`
|
|
12004
|
-
].join(" ");
|
|
12005
|
-
case "left":
|
|
12006
|
-
tip = { x: pt.x + s, y: pt.y };
|
|
12007
|
-
return [
|
|
12008
|
-
`M${pt.x},${pt.y - spread} L${tip.x},${tip.y}`,
|
|
12009
|
-
`M${pt.x},${pt.y} L${tip.x},${tip.y}`,
|
|
12010
|
-
`M${pt.x},${pt.y + spread} L${tip.x},${tip.y}`,
|
|
12011
|
-
`M${pt.x},${pt.y - spread} L${pt.x},${pt.y + spread}`
|
|
12012
|
-
].join(" ");
|
|
12013
|
-
case "down":
|
|
12014
|
-
tip = { x: pt.x, y: pt.y - s };
|
|
12015
|
-
return [
|
|
12016
|
-
`M${pt.x - spread},${pt.y} L${tip.x},${tip.y}`,
|
|
12017
|
-
`M${pt.x},${pt.y} L${tip.x},${tip.y}`,
|
|
12018
|
-
`M${pt.x + spread},${pt.y} L${tip.x},${tip.y}`,
|
|
12019
|
-
`M${pt.x - spread},${pt.y} L${pt.x + spread},${pt.y}`
|
|
12020
|
-
].join(" ");
|
|
12021
|
-
case "up":
|
|
12022
|
-
tip = { x: pt.x, y: pt.y + s };
|
|
12023
|
-
return [
|
|
12024
|
-
`M${pt.x - spread},${pt.y} L${tip.x},${tip.y}`,
|
|
12025
|
-
`M${pt.x},${pt.y} L${tip.x},${tip.y}`,
|
|
12026
|
-
`M${pt.x + spread},${pt.y} L${tip.x},${tip.y}`,
|
|
12027
|
-
`M${pt.x - spread},${pt.y} L${pt.x + spread},${pt.y}`
|
|
12028
|
-
].join(" ");
|
|
12029
|
-
}
|
|
12030
|
-
}
|
|
12031
|
-
function getSymbolPath(type, end, pt, direction) {
|
|
12032
|
-
const side = end === "start" ? type.split("-to-")[0] : type.split("-to-")[1];
|
|
12033
|
-
if (side === "one") return oneSymbol(pt, direction);
|
|
12034
|
-
if (side === "many") return crowFootSymbol(pt, direction);
|
|
12035
|
-
return null;
|
|
12036
|
-
}
|
|
12037
|
-
function DiagramRelation({
|
|
12038
|
-
from,
|
|
12039
|
-
to,
|
|
12040
|
-
fromField: fromFieldProp,
|
|
12041
|
-
toField: toFieldProp,
|
|
12042
|
-
type,
|
|
12043
|
-
label
|
|
12044
|
-
}) {
|
|
12045
|
-
const { nodeRects, registryVersion } = useCanvas();
|
|
12046
|
-
const { schema } = useDiagram();
|
|
12047
|
-
const result = useMemo(() => {
|
|
12048
|
-
const fromRect = nodeRects.get(from);
|
|
12049
|
-
const toRect = nodeRects.get(to);
|
|
12050
|
-
if (!fromRect || !toRect) return null;
|
|
12051
|
-
const fromEntity = schema.entities.find((e) => (e.id ?? e.name) === from);
|
|
12052
|
-
const toEntity = schema.entities.find((e) => (e.id ?? e.name) === to);
|
|
12053
|
-
let fromFieldIdx = -1;
|
|
12054
|
-
let toFieldIdx = -1;
|
|
12055
|
-
if (fromFieldProp && fromEntity?.fields) {
|
|
12056
|
-
fromFieldIdx = fromEntity.fields.findIndex((f) => f.name === fromFieldProp);
|
|
12057
|
-
} else if (fromEntity?.fields) {
|
|
12058
|
-
fromFieldIdx = fromEntity.fields.findIndex((f) => f.primary);
|
|
12059
|
-
}
|
|
12060
|
-
if (toFieldProp && toEntity?.fields) {
|
|
12061
|
-
toFieldIdx = toEntity.fields.findIndex((f) => f.name === toFieldProp);
|
|
12062
|
-
} else if (toEntity?.fields) {
|
|
12063
|
-
const fromName = (fromEntity?.name ?? from).toLowerCase();
|
|
12064
|
-
toFieldIdx = toEntity.fields.findIndex(
|
|
12065
|
-
(f) => f.foreign && (f.name === `${fromName}_id` || f.name === `${fromName}Id`)
|
|
12066
|
-
);
|
|
12067
|
-
if (toFieldIdx === -1) {
|
|
12068
|
-
toFieldIdx = toEntity.fields.findIndex((f) => f.foreign);
|
|
12069
|
-
}
|
|
12070
|
-
}
|
|
12071
|
-
const fromFieldY = fromFieldIdx >= 0 ? HEADER_HEIGHT + fromFieldIdx * FIELD_HEIGHT + FIELD_HEIGHT / 2 : fromRect.height / 2;
|
|
12072
|
-
const toFieldY = toFieldIdx >= 0 ? HEADER_HEIGHT + toFieldIdx * FIELD_HEIGHT + FIELD_HEIGHT / 2 : toRect.height / 2;
|
|
12073
|
-
const fromCx = fromRect.x + fromRect.width / 2;
|
|
12074
|
-
const toCx = toRect.x + toRect.width / 2;
|
|
12075
|
-
let fromPt, toPt;
|
|
12076
|
-
let fromDir;
|
|
12077
|
-
let toDir;
|
|
12078
|
-
if (fromCx <= toCx) {
|
|
12079
|
-
fromPt = { x: fromRect.x + fromRect.width, y: fromRect.y + fromFieldY };
|
|
12080
|
-
toPt = { x: toRect.x, y: toRect.y + toFieldY };
|
|
12081
|
-
fromDir = "right";
|
|
12082
|
-
toDir = "left";
|
|
12083
|
-
} else {
|
|
12084
|
-
fromPt = { x: fromRect.x, y: fromRect.y + fromFieldY };
|
|
12085
|
-
toPt = { x: toRect.x + toRect.width, y: toRect.y + toFieldY };
|
|
12086
|
-
fromDir = "left";
|
|
12087
|
-
toDir = "right";
|
|
12088
|
-
}
|
|
12089
|
-
const offsetFrom = { ...fromPt };
|
|
12090
|
-
const offsetTo = { ...toPt };
|
|
12091
|
-
if (fromDir === "right") offsetFrom.x += SYMBOL_SIZE;
|
|
12092
|
-
else offsetFrom.x -= SYMBOL_SIZE;
|
|
12093
|
-
if (toDir === "left") offsetTo.x -= SYMBOL_SIZE;
|
|
12094
|
-
else offsetTo.x += SYMBOL_SIZE;
|
|
12095
|
-
const adx = Math.abs(offsetTo.x - offsetFrom.x);
|
|
12096
|
-
const ady = Math.abs(offsetTo.y - offsetFrom.y);
|
|
12097
|
-
const off = Math.max(adx * 0.4, ady * 0.25, 40);
|
|
12098
|
-
const cp1x = offsetFrom.x + (fromDir === "right" ? off : -off);
|
|
12099
|
-
const cp2x = offsetTo.x + (toDir === "left" ? -off : off);
|
|
12100
|
-
const linePath = `M${offsetFrom.x},${offsetFrom.y} C${cp1x},${offsetFrom.y} ${cp2x},${offsetTo.y} ${offsetTo.x},${offsetTo.y}`;
|
|
12101
|
-
const startSymbol = getSymbolPath(type, "start", fromPt, fromDir);
|
|
12102
|
-
const endSymbol = getSymbolPath(type, "end", toPt, toDir);
|
|
12103
|
-
return { linePath, startSymbol, endSymbol, midX: (offsetFrom.x + offsetTo.x) / 2, midY: (offsetFrom.y + offsetTo.y) / 2 };
|
|
12104
|
-
}, [from, to, fromFieldProp, toFieldProp, type, schema, nodeRects, registryVersion]);
|
|
12105
|
-
if (!result) return null;
|
|
12106
|
-
return /* @__PURE__ */ jsxs("g", { "data-react-fancy-diagram-relation": "", children: [
|
|
12107
|
-
/* @__PURE__ */ jsx("path", { d: result.linePath, fill: "none", stroke: "#71717a", strokeWidth: 2 }),
|
|
12108
|
-
result.startSymbol && /* @__PURE__ */ jsx("path", { d: result.startSymbol, fill: "none", stroke: "#71717a", strokeWidth: 2 }),
|
|
12109
|
-
result.endSymbol && /* @__PURE__ */ jsx("path", { d: result.endSymbol, fill: "none", stroke: "#71717a", strokeWidth: 2 }),
|
|
12110
|
-
label && /* @__PURE__ */ jsx("foreignObject", { x: result.midX - 40, y: result.midY - 12, width: 80, height: 24, children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center text-xs text-zinc-500", children: label }) })
|
|
12111
|
-
] });
|
|
12112
|
-
}
|
|
12113
|
-
DiagramRelation._isCanvasEdge = true;
|
|
12114
|
-
DiagramRelation.displayName = "DiagramRelation";
|
|
12115
|
-
var FORMAT_LABELS = {
|
|
12116
|
-
erd: "ERD",
|
|
12117
|
-
uml: "UML",
|
|
12118
|
-
dfd: "DFD"
|
|
12119
|
-
};
|
|
12120
|
-
var FORMAT_EXTENSIONS = {
|
|
12121
|
-
erd: "erd",
|
|
12122
|
-
uml: "puml",
|
|
12123
|
-
dfd: "dfd"
|
|
12124
|
-
};
|
|
12125
|
-
function DiagramToolbar({ className }) {
|
|
12126
|
-
const { schema, downloadableRef, importableRef, exportFormats, onImport } = useDiagram();
|
|
12127
|
-
const fileInputRef = useRef(null);
|
|
12128
|
-
const canDownload = downloadableRef.current;
|
|
12129
|
-
const canImport = importableRef.current;
|
|
12130
|
-
const handleDownload = useCallback(
|
|
12131
|
-
async (format) => {
|
|
12132
|
-
const { serializeToERD, serializeToUML, serializeToDFD } = await import('./diagram.serializers-6RPUO46U.js');
|
|
12133
|
-
let content;
|
|
12134
|
-
switch (format) {
|
|
12135
|
-
case "erd":
|
|
12136
|
-
content = serializeToERD(schema);
|
|
12137
|
-
break;
|
|
12138
|
-
case "uml":
|
|
12139
|
-
content = serializeToUML(schema);
|
|
12140
|
-
break;
|
|
12141
|
-
case "dfd":
|
|
12142
|
-
content = serializeToDFD(schema);
|
|
12143
|
-
break;
|
|
12144
|
-
}
|
|
12145
|
-
const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
|
|
12146
|
-
const url = URL.createObjectURL(blob);
|
|
12147
|
-
const a = document.createElement("a");
|
|
12148
|
-
a.href = url;
|
|
12149
|
-
a.download = `diagram.${FORMAT_EXTENSIONS[format]}`;
|
|
12150
|
-
document.body.appendChild(a);
|
|
12151
|
-
a.click();
|
|
12152
|
-
document.body.removeChild(a);
|
|
12153
|
-
URL.revokeObjectURL(url);
|
|
12154
|
-
},
|
|
12155
|
-
[schema]
|
|
12156
|
-
);
|
|
12157
|
-
const handleFileChange = useCallback(
|
|
12158
|
-
async (e) => {
|
|
12159
|
-
const file = e.target.files?.[0];
|
|
12160
|
-
if (!file || !onImport) return;
|
|
12161
|
-
const text = await file.text();
|
|
12162
|
-
const ext = file.name.split(".").pop()?.toLowerCase();
|
|
12163
|
-
const { deserializeSchema } = await import('./diagram.serializers-6RPUO46U.js');
|
|
12164
|
-
let format = "erd";
|
|
12165
|
-
if (ext === "puml" || ext === "uml") format = "uml";
|
|
12166
|
-
else if (ext === "dfd") format = "dfd";
|
|
12167
|
-
const parsed = deserializeSchema(text, format);
|
|
12168
|
-
onImport(parsed);
|
|
12169
|
-
if (fileInputRef.current) {
|
|
12170
|
-
fileInputRef.current.value = "";
|
|
12171
|
-
}
|
|
12172
|
-
},
|
|
12173
|
-
[onImport]
|
|
12174
|
-
);
|
|
12175
|
-
if (!canDownload && !canImport) return null;
|
|
12176
|
-
return /* @__PURE__ */ jsxs(
|
|
12177
|
-
"div",
|
|
12178
|
-
{
|
|
12179
|
-
"data-react-fancy-diagram-toolbar": "",
|
|
12180
|
-
className: cn(
|
|
12181
|
-
"absolute right-3 top-3 z-10 flex items-center gap-1 rounded-lg border border-zinc-200 bg-white/90 p-1 shadow-sm backdrop-blur-sm dark:border-zinc-700 dark:bg-zinc-800/90",
|
|
12182
|
-
className
|
|
12183
|
-
),
|
|
12184
|
-
children: [
|
|
12185
|
-
canDownload && exportFormats.map((format) => /* @__PURE__ */ jsx(
|
|
12186
|
-
"button",
|
|
12187
|
-
{
|
|
12188
|
-
type: "button",
|
|
12189
|
-
onClick: () => handleDownload(format),
|
|
12190
|
-
className: "rounded px-2 py-1 text-xs font-medium text-zinc-600 transition-colors hover:bg-zinc-100 hover:text-zinc-900 dark:text-zinc-400 dark:hover:bg-zinc-700 dark:hover:text-zinc-200",
|
|
12191
|
-
children: FORMAT_LABELS[format]
|
|
12192
|
-
},
|
|
12193
|
-
format
|
|
12194
|
-
)),
|
|
12195
|
-
canImport && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
12196
|
-
/* @__PURE__ */ jsx(
|
|
12197
|
-
"button",
|
|
12198
|
-
{
|
|
12199
|
-
type: "button",
|
|
12200
|
-
onClick: () => fileInputRef.current?.click(),
|
|
12201
|
-
className: "rounded px-2 py-1 text-xs font-medium text-zinc-600 transition-colors hover:bg-zinc-100 hover:text-zinc-900 dark:text-zinc-400 dark:hover:bg-zinc-700 dark:hover:text-zinc-200",
|
|
12202
|
-
children: "Import"
|
|
12203
|
-
}
|
|
12204
|
-
),
|
|
12205
|
-
/* @__PURE__ */ jsx(
|
|
12206
|
-
"input",
|
|
12207
|
-
{
|
|
12208
|
-
ref: fileInputRef,
|
|
12209
|
-
type: "file",
|
|
12210
|
-
accept: ".erd,.puml,.uml,.dfd,.txt",
|
|
12211
|
-
className: "hidden",
|
|
12212
|
-
onChange: handleFileChange
|
|
12213
|
-
}
|
|
12214
|
-
)
|
|
12215
|
-
] })
|
|
12216
|
-
]
|
|
12217
|
-
}
|
|
12218
|
-
);
|
|
12219
|
-
}
|
|
12220
|
-
DiagramToolbar.displayName = "DiagramToolbar";
|
|
12221
|
-
|
|
12222
|
-
// src/components/Diagram/diagram.layout.ts
|
|
12223
|
-
var ENTITY_WIDTH = 220;
|
|
12224
|
-
var HEADER_HEIGHT2 = 40;
|
|
12225
|
-
var FIELD_HEIGHT2 = 28;
|
|
12226
|
-
var HORIZONTAL_GAP = 80;
|
|
12227
|
-
var VERTICAL_GAP = 60;
|
|
12228
|
-
function getEntityHeight(fieldCount) {
|
|
12229
|
-
return HEADER_HEIGHT2 + Math.max(fieldCount, 1) * FIELD_HEIGHT2;
|
|
12230
|
-
}
|
|
12231
|
-
function resolveEntityId(entity) {
|
|
12232
|
-
return entity.id ?? entity.name;
|
|
12233
|
-
}
|
|
12234
|
-
function computeDiagramLayout(schema) {
|
|
12235
|
-
const positions = /* @__PURE__ */ new Map();
|
|
12236
|
-
const entityIds = new Set(schema.entities.map(resolveEntityId));
|
|
12237
|
-
const incoming = /* @__PURE__ */ new Map();
|
|
12238
|
-
for (const id of entityIds) {
|
|
12239
|
-
incoming.set(id, /* @__PURE__ */ new Set());
|
|
12240
|
-
}
|
|
12241
|
-
for (const rel of schema.relations) {
|
|
12242
|
-
if (entityIds.has(rel.from) && entityIds.has(rel.to)) {
|
|
12243
|
-
incoming.get(rel.to).add(rel.from);
|
|
12244
|
-
}
|
|
12245
|
-
}
|
|
12246
|
-
const rowAssignment = /* @__PURE__ */ new Map();
|
|
12247
|
-
const assigned = /* @__PURE__ */ new Set();
|
|
12248
|
-
const queue = [];
|
|
12249
|
-
for (const id of entityIds) {
|
|
12250
|
-
if (incoming.get(id).size === 0) {
|
|
12251
|
-
rowAssignment.set(id, 0);
|
|
12252
|
-
assigned.add(id);
|
|
12253
|
-
queue.push(id);
|
|
12254
|
-
}
|
|
12255
|
-
}
|
|
12256
|
-
if (queue.length === 0 && entityIds.size > 0) {
|
|
12257
|
-
const firstId = resolveEntityId(schema.entities[0]);
|
|
12258
|
-
rowAssignment.set(firstId, 0);
|
|
12259
|
-
assigned.add(firstId);
|
|
12260
|
-
queue.push(firstId);
|
|
12261
|
-
}
|
|
12262
|
-
const outgoing = /* @__PURE__ */ new Map();
|
|
12263
|
-
for (const id of entityIds) {
|
|
12264
|
-
outgoing.set(id, []);
|
|
12265
|
-
}
|
|
12266
|
-
for (const rel of schema.relations) {
|
|
12267
|
-
if (entityIds.has(rel.from) && entityIds.has(rel.to)) {
|
|
12268
|
-
outgoing.get(rel.from).push(rel.to);
|
|
12269
|
-
}
|
|
12270
|
-
}
|
|
12271
|
-
let head = 0;
|
|
12272
|
-
while (head < queue.length) {
|
|
12273
|
-
const current = queue[head++];
|
|
12274
|
-
const currentRow = rowAssignment.get(current);
|
|
12275
|
-
for (const neighbor of outgoing.get(current) ?? []) {
|
|
12276
|
-
if (!assigned.has(neighbor)) {
|
|
12277
|
-
rowAssignment.set(neighbor, currentRow + 1);
|
|
12278
|
-
assigned.add(neighbor);
|
|
12279
|
-
queue.push(neighbor);
|
|
12280
|
-
}
|
|
12281
|
-
}
|
|
12282
|
-
}
|
|
12283
|
-
for (const id of entityIds) {
|
|
12284
|
-
if (!assigned.has(id)) {
|
|
12285
|
-
rowAssignment.set(id, 0);
|
|
12286
|
-
}
|
|
12287
|
-
}
|
|
12288
|
-
const rows = /* @__PURE__ */ new Map();
|
|
12289
|
-
for (const [id, row] of rowAssignment) {
|
|
12290
|
-
if (!rows.has(row)) {
|
|
12291
|
-
rows.set(row, []);
|
|
12292
|
-
}
|
|
12293
|
-
rows.get(row).push(id);
|
|
12294
|
-
}
|
|
12295
|
-
const fieldCounts = /* @__PURE__ */ new Map();
|
|
12296
|
-
for (const entity of schema.entities) {
|
|
12297
|
-
fieldCounts.set(resolveEntityId(entity), entity.fields?.length ?? 0);
|
|
12298
|
-
}
|
|
12299
|
-
const sortedRows = Array.from(rows.keys()).sort((a, b) => a - b);
|
|
12300
|
-
let currentY = 0;
|
|
12301
|
-
for (const rowIndex of sortedRows) {
|
|
12302
|
-
const rowEntities = rows.get(rowIndex);
|
|
12303
|
-
const totalWidth = rowEntities.length * ENTITY_WIDTH + (rowEntities.length - 1) * HORIZONTAL_GAP;
|
|
12304
|
-
const startX = -totalWidth / 2 + ENTITY_WIDTH / 2;
|
|
12305
|
-
let maxHeight = 0;
|
|
12306
|
-
for (let i = 0; i < rowEntities.length; i++) {
|
|
12307
|
-
const entityId = rowEntities[i];
|
|
12308
|
-
const x = startX + i * (ENTITY_WIDTH + HORIZONTAL_GAP) - ENTITY_WIDTH / 2;
|
|
12309
|
-
positions.set(entityId, { x, y: currentY });
|
|
12310
|
-
const height = getEntityHeight(fieldCounts.get(entityId) ?? 0);
|
|
12311
|
-
if (height > maxHeight) {
|
|
12312
|
-
maxHeight = height;
|
|
12313
|
-
}
|
|
12314
|
-
}
|
|
12315
|
-
currentY += maxHeight + VERTICAL_GAP;
|
|
12316
|
-
}
|
|
12317
|
-
return positions;
|
|
12318
|
-
}
|
|
12319
|
-
function DiagramRoot({
|
|
12320
|
-
children,
|
|
12321
|
-
schema,
|
|
12322
|
-
type = "general",
|
|
12323
|
-
viewport,
|
|
12324
|
-
defaultViewport,
|
|
12325
|
-
onViewportChange,
|
|
12326
|
-
downloadable = false,
|
|
12327
|
-
importable = false,
|
|
12328
|
-
exportFormats = ["erd"],
|
|
12329
|
-
onImport,
|
|
12330
|
-
minimap = false,
|
|
12331
|
-
className
|
|
12332
|
-
}) {
|
|
12333
|
-
const downloadableRef = useRef(downloadable);
|
|
12334
|
-
const importableRef = useRef(importable);
|
|
12335
|
-
const normalizedSchema = useMemo(() => {
|
|
12336
|
-
if (!schema) return { entities: [], relations: [] };
|
|
12337
|
-
const entities = schema.entities.map((e) => ({
|
|
12338
|
-
...e,
|
|
12339
|
-
id: e.id ?? e.name
|
|
12340
|
-
}));
|
|
12341
|
-
const relations = schema.relations.map((r, i) => ({
|
|
12342
|
-
...r,
|
|
12343
|
-
id: r.id ?? `rel-${i}`
|
|
12344
|
-
}));
|
|
12345
|
-
return { entities, relations };
|
|
12346
|
-
}, [schema]);
|
|
12347
|
-
const initialPositions = useMemo(() => {
|
|
12348
|
-
if (normalizedSchema.entities.length === 0) return /* @__PURE__ */ new Map();
|
|
12349
|
-
const layout = computeDiagramLayout(normalizedSchema);
|
|
12350
|
-
const positions = /* @__PURE__ */ new Map();
|
|
12351
|
-
for (const entity of normalizedSchema.entities) {
|
|
12352
|
-
if (entity.x !== void 0 && entity.y !== void 0) {
|
|
12353
|
-
positions.set(entity.id, { x: entity.x, y: entity.y });
|
|
12354
|
-
} else {
|
|
12355
|
-
const pos = layout.get(entity.id);
|
|
12356
|
-
positions.set(entity.id, pos ?? { x: 0, y: 0 });
|
|
12357
|
-
}
|
|
12358
|
-
}
|
|
12359
|
-
return positions;
|
|
12360
|
-
}, [normalizedSchema]);
|
|
12361
|
-
const computedDefaultViewport = useMemo(() => {
|
|
12362
|
-
if (defaultViewport) return defaultViewport;
|
|
12363
|
-
if (initialPositions.size === 0) return { panX: 0, panY: 0, zoom: 1 };
|
|
12364
|
-
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
12365
|
-
initialPositions.forEach((pos) => {
|
|
12366
|
-
minX = Math.min(minX, pos.x);
|
|
12367
|
-
minY = Math.min(minY, pos.y);
|
|
12368
|
-
maxX = Math.max(maxX, pos.x + 220);
|
|
12369
|
-
maxY = Math.max(maxY, pos.y + 200);
|
|
12370
|
-
});
|
|
12371
|
-
const padding = 40;
|
|
12372
|
-
const panX = -minX + padding;
|
|
12373
|
-
const panY = -minY + padding;
|
|
12374
|
-
return { panX, panY, zoom: 1 };
|
|
12375
|
-
}, [defaultViewport, initialPositions]);
|
|
12376
|
-
const [entityPositions, setEntityPositions] = useState(initialPositions);
|
|
12377
|
-
const handleEntityMove = useCallback((entityId, x, y) => {
|
|
12378
|
-
setEntityPositions((prev) => {
|
|
12379
|
-
const next = new Map(prev);
|
|
12380
|
-
next.set(entityId, { x, y });
|
|
12381
|
-
return next;
|
|
12382
|
-
});
|
|
12383
|
-
}, []);
|
|
12384
|
-
const ctx = useMemo(
|
|
12385
|
-
() => ({
|
|
12386
|
-
diagramType: type,
|
|
12387
|
-
schema: normalizedSchema,
|
|
12388
|
-
downloadableRef,
|
|
12389
|
-
importableRef,
|
|
12390
|
-
exportFormats,
|
|
12391
|
-
onImport
|
|
12392
|
-
}),
|
|
12393
|
-
[type, normalizedSchema, exportFormats, onImport]
|
|
12394
|
-
);
|
|
12395
|
-
return /* @__PURE__ */ jsx(DiagramContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx("div", { "data-react-fancy-diagram": "", className: "relative h-full w-full", children: /* @__PURE__ */ jsxs(
|
|
12396
|
-
Canvas,
|
|
12397
|
-
{
|
|
12398
|
-
viewport,
|
|
12399
|
-
defaultViewport: computedDefaultViewport,
|
|
12400
|
-
onViewportChange,
|
|
12401
|
-
showGrid: true,
|
|
12402
|
-
fitOnMount: true,
|
|
12403
|
-
className: cn("h-full w-full", className),
|
|
12404
|
-
children: [
|
|
12405
|
-
normalizedSchema.entities.map((entity) => {
|
|
12406
|
-
const pos = entityPositions.get(entity.id) ?? { x: 0, y: 0 };
|
|
12407
|
-
return /* @__PURE__ */ jsx(
|
|
12408
|
-
DiagramEntity,
|
|
12409
|
-
{
|
|
12410
|
-
id: entity.id,
|
|
12411
|
-
name: entity.name,
|
|
12412
|
-
x: pos.x,
|
|
12413
|
-
y: pos.y,
|
|
12414
|
-
draggable: true,
|
|
12415
|
-
onPositionChange: (nx, ny) => handleEntityMove(entity.id, nx, ny),
|
|
12416
|
-
children: entity.fields?.map((field) => /* @__PURE__ */ jsx(
|
|
12417
|
-
DiagramField,
|
|
12418
|
-
{
|
|
12419
|
-
name: field.name,
|
|
12420
|
-
type: field.type,
|
|
12421
|
-
primary: field.primary,
|
|
12422
|
-
foreign: field.foreign,
|
|
12423
|
-
nullable: field.nullable
|
|
12424
|
-
},
|
|
12425
|
-
field.name
|
|
12426
|
-
))
|
|
12427
|
-
},
|
|
12428
|
-
entity.id
|
|
12429
|
-
);
|
|
12430
|
-
}),
|
|
12431
|
-
normalizedSchema.relations.map((rel) => /* @__PURE__ */ jsx(
|
|
12432
|
-
DiagramRelation,
|
|
12433
|
-
{
|
|
12434
|
-
from: rel.from,
|
|
12435
|
-
to: rel.to,
|
|
12436
|
-
fromField: rel.fromField,
|
|
12437
|
-
toField: rel.toField,
|
|
12438
|
-
type: rel.type,
|
|
12439
|
-
label: rel.label
|
|
12440
|
-
},
|
|
12441
|
-
rel.id
|
|
12442
|
-
)),
|
|
12443
|
-
children,
|
|
12444
|
-
/* @__PURE__ */ jsx(Canvas.Controls, {}),
|
|
12445
|
-
minimap && /* @__PURE__ */ jsx(Canvas.Minimap, {})
|
|
12446
|
-
]
|
|
12447
|
-
}
|
|
12448
|
-
) }) });
|
|
12449
|
-
}
|
|
12450
|
-
var Diagram = Object.assign(DiagramRoot, {
|
|
12451
|
-
Entity: DiagramEntity,
|
|
12452
|
-
Field: DiagramField,
|
|
12453
|
-
Relation: DiagramRelation,
|
|
12454
|
-
Toolbar: DiagramToolbar
|
|
12455
|
-
});
|
|
12456
11457
|
var TreeNavContext = createContext(null);
|
|
12457
11458
|
function useTreeNav() {
|
|
12458
11459
|
const ctx = useContext(TreeNavContext);
|
|
@@ -12819,6 +11820,6 @@ var TreeNav = Object.assign(TreeNavRoot, {
|
|
|
12819
11820
|
Node: TreeNode
|
|
12820
11821
|
});
|
|
12821
11822
|
|
|
12822
|
-
export { Accordion, AccordionPanel, AccordionPanelContent, AccordionPanelSection, AccordionPanelTrigger, Action, Autocomplete, Avatar, Badge, Brand, Breadcrumbs, Calendar, Callout,
|
|
11823
|
+
export { Accordion, AccordionPanel, AccordionPanelContent, AccordionPanelSection, AccordionPanelTrigger, Action, Autocomplete, Avatar, Badge, Brand, Breadcrumbs, Calendar, Callout, Card, Carousel, Chart, Checkbox, CheckboxGroup, ColorPicker, Command, Composer, ContentRenderer, ContextMenu, DatePicker, Dropdown, EMOJI_CATEGORY_ORDER, EMOJI_DATA, EMOJI_ENTRIES, Editor, Emoji, EmojiSelect, Field, FileUpload, Heading, Icon, Input, Kanban, Menu2 as Menu, MobileMenu, Modal, MultiSwitch, Navbar, OtpInput, Pagination, Pillbox, Popover, Portal, Profile, Progress, RadioGroup, SKIN_TONES, Select, Separator, Sidebar, Skeleton, Slider, Switch, Table, Tabs, Text, Textarea, TimePicker, Timeline, Toast, Tooltip, TreeNav, applyTone, cn, configureIcons, find, hasSkinTones, registerExtension, registerExtensions, registerIconSet, registerIcons, resolve, sanitizeHref, sanitizeHtml, search, skinTones, useAccordion, useAccordionPanel, useAccordionSection, useAnimation, useCarousel, useCommand, useContextMenu, useControllableState, useDropdown, useEditor, useEscapeKey, useFileUpload, useFloatingPosition, useFocusTrap, useId12 as useId, useKanban, useMenu, useMobileMenu, useModal, useNavbar, useNodeRegistry, useOutsideClick, usePanZoom, usePopover, useSidebar, useTabs, useToast, useTreeNav };
|
|
12823
11824
|
//# sourceMappingURL=index.js.map
|
|
12824
11825
|
//# sourceMappingURL=index.js.map
|