flowchart-sequence-designer 1.0.0 → 1.0.1
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/CHANGELOG.md +13 -0
- package/dist/ui/index.cjs +1418 -1183
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.js +1354 -1119
- package/dist/ui/index.js.map +1 -1
- package/package.json +1 -1
package/dist/ui/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/ui/DiagramEditor.tsx
|
|
2
|
-
import { useCallback as useCallback7, useEffect as
|
|
2
|
+
import { useCallback as useCallback7, useEffect as useEffect10, useRef as useRef7, useState as useState11 } from "react";
|
|
3
3
|
|
|
4
4
|
// src/ui/Toolbar.tsx
|
|
5
5
|
import { useState as useState2 } from "react";
|
|
@@ -375,6 +375,12 @@ var ACCENT = {
|
|
|
375
375
|
emeraldDarkLight: "rgba(16,185,129,0.12)",
|
|
376
376
|
emeraldDarkBorder: "rgba(16,185,129,0.3)"
|
|
377
377
|
};
|
|
378
|
+
function shadowColor(isDark) {
|
|
379
|
+
return isDark ? "rgba(0,0,0,0.55)" : "rgba(15,23,42,0.09)";
|
|
380
|
+
}
|
|
381
|
+
function arrowColor(isDark) {
|
|
382
|
+
return isDark ? "#64748b" : "#94a3b8";
|
|
383
|
+
}
|
|
378
384
|
function variantAccent(variant, isDark) {
|
|
379
385
|
if (variant === "question") {
|
|
380
386
|
return isDark ? { color: ACCENT.amberDark, fill: ACCENT.amberDarkLight, border: ACCENT.amberDarkBorder, glow: ACCENT.amberGlow } : { color: ACCENT.amber, fill: ACCENT.amberLight, border: ACCENT.amberBorder, glow: ACCENT.amberGlow };
|
|
@@ -807,10 +813,270 @@ function StepEditor({ nodeId, model, onModelChange, variant = "flowchart", isDar
|
|
|
807
813
|
}
|
|
808
814
|
|
|
809
815
|
// src/ui/SequenceEditor.tsx
|
|
810
|
-
import { useCallback as useCallback4, useEffect as
|
|
816
|
+
import { useCallback as useCallback4, useEffect as useEffect5, useMemo as useMemo3, useRef as useRef3, useState as useState5 } from "react";
|
|
811
817
|
|
|
812
|
-
// src/ui/
|
|
818
|
+
// src/ui/SequenceCanvas.tsx
|
|
813
819
|
import { useMemo } from "react";
|
|
820
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
821
|
+
var HEADER_H = 64;
|
|
822
|
+
var HEADER_PAD = 24;
|
|
823
|
+
var ROW_H = 64;
|
|
824
|
+
var SIDE_PAD = 40;
|
|
825
|
+
var INDIGO = "#4f46e5";
|
|
826
|
+
var INDIGO_SOFT = "#eef2ff";
|
|
827
|
+
var STYLE_SEQ_GRAB = { cursor: "grab" };
|
|
828
|
+
var STYLE_SEQ_GRABBING = { cursor: "grabbing" };
|
|
829
|
+
var STYLE_SEQ_ACTOR_TEXT = { cursor: "pointer", userSelect: "none" };
|
|
830
|
+
var STYLE_SEQ_REMOVE_BTN = { cursor: "pointer" };
|
|
831
|
+
var STYLE_SEQ_REMOVE_ICON = { pointerEvents: "none", userSelect: "none" };
|
|
832
|
+
var STYLE_SEQ_DRAGGING = { opacity: 0.85 };
|
|
833
|
+
function estimateW(text, pxPerChar = 7) {
|
|
834
|
+
return text.length * pxPerChar;
|
|
835
|
+
}
|
|
836
|
+
function SequenceCanvas(props) {
|
|
837
|
+
const {
|
|
838
|
+
model,
|
|
839
|
+
actors,
|
|
840
|
+
messages,
|
|
841
|
+
t,
|
|
842
|
+
isDark,
|
|
843
|
+
colW,
|
|
844
|
+
totalW,
|
|
845
|
+
totalH,
|
|
846
|
+
actorX,
|
|
847
|
+
msgY,
|
|
848
|
+
selected,
|
|
849
|
+
editingId,
|
|
850
|
+
setEditingId,
|
|
851
|
+
drag,
|
|
852
|
+
onRowMouseDown,
|
|
853
|
+
renameActor,
|
|
854
|
+
removeActor,
|
|
855
|
+
svgRef
|
|
856
|
+
} = props;
|
|
857
|
+
const visualMessages = useMemo(() => {
|
|
858
|
+
if (!drag?.active) return messages;
|
|
859
|
+
const idx = messages.findIndex((m) => m.id === drag.id);
|
|
860
|
+
if (idx < 0) return messages;
|
|
861
|
+
const next = messages.slice();
|
|
862
|
+
const [moved] = next.splice(idx, 1);
|
|
863
|
+
next.splice(drag.targetIdx, 0, moved);
|
|
864
|
+
return next;
|
|
865
|
+
}, [messages, drag]);
|
|
866
|
+
if (actors.length === 0 && messages.length === 0) {
|
|
867
|
+
return /* @__PURE__ */ jsxs4("div", { style: {
|
|
868
|
+
position: "absolute",
|
|
869
|
+
inset: 0,
|
|
870
|
+
display: "flex",
|
|
871
|
+
flexDirection: "column",
|
|
872
|
+
alignItems: "center",
|
|
873
|
+
justifyContent: "center",
|
|
874
|
+
gap: 10,
|
|
875
|
+
color: t.textMuted,
|
|
876
|
+
pointerEvents: "none"
|
|
877
|
+
}, children: [
|
|
878
|
+
/* @__PURE__ */ jsx4("div", { style: { fontSize: 36, opacity: 0.15, color: t.textPrimary }, children: "\u2194" }),
|
|
879
|
+
/* @__PURE__ */ jsxs4("div", { style: { fontSize: 13, fontWeight: 500 }, children: [
|
|
880
|
+
"Click ",
|
|
881
|
+
/* @__PURE__ */ jsx4("strong", { style: { color: INDIGO }, children: "+ Actor" }),
|
|
882
|
+
" then ",
|
|
883
|
+
/* @__PURE__ */ jsx4("strong", { style: { color: INDIGO }, children: "+ Message" }),
|
|
884
|
+
" to start"
|
|
885
|
+
] })
|
|
886
|
+
] });
|
|
887
|
+
}
|
|
888
|
+
return /* @__PURE__ */ jsxs4(
|
|
889
|
+
"svg",
|
|
890
|
+
{
|
|
891
|
+
ref: svgRef,
|
|
892
|
+
width: totalW,
|
|
893
|
+
height: totalH,
|
|
894
|
+
style: { display: "block", cursor: drag?.active ? "grabbing" : "default", userSelect: "none" },
|
|
895
|
+
children: [
|
|
896
|
+
/* @__PURE__ */ jsxs4("defs", { children: [
|
|
897
|
+
/* @__PURE__ */ jsx4("pattern", { id: "seqdots", x: "0", y: "0", width: "24", height: "24", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ jsx4("circle", { cx: 12, cy: 12, r: 1.1, fill: t.dot }) }),
|
|
898
|
+
/* @__PURE__ */ jsx4("filter", { id: "seqShadow", x: "-20%", y: "-20%", width: "140%", height: "140%", children: /* @__PURE__ */ jsx4("feDropShadow", { dx: 0, dy: 3, stdDeviation: 5, floodColor: shadowColor(isDark) }) }),
|
|
899
|
+
/* @__PURE__ */ jsx4("marker", { id: "seqArrow", markerWidth: 9, markerHeight: 7, refX: 8.5, refY: 3.5, orient: "auto", markerUnits: "strokeWidth", children: /* @__PURE__ */ jsx4("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: t.arrow }) })
|
|
900
|
+
] }),
|
|
901
|
+
/* @__PURE__ */ jsx4("rect", { width: totalW, height: totalH, fill: "url(#seqdots)" }),
|
|
902
|
+
actors.map((name) => {
|
|
903
|
+
const x = actorX(name);
|
|
904
|
+
const top = HEADER_PAD + HEADER_H;
|
|
905
|
+
return /* @__PURE__ */ jsx4(
|
|
906
|
+
"line",
|
|
907
|
+
{
|
|
908
|
+
x1: x,
|
|
909
|
+
x2: x,
|
|
910
|
+
y1: top + 4,
|
|
911
|
+
y2: totalH - 24,
|
|
912
|
+
stroke: t.lifeline,
|
|
913
|
+
strokeWidth: 1.25,
|
|
914
|
+
strokeDasharray: "5 5"
|
|
915
|
+
},
|
|
916
|
+
`life-${name}`
|
|
917
|
+
);
|
|
918
|
+
}),
|
|
919
|
+
visualMessages.map((msg, idx) => {
|
|
920
|
+
const y = msgY(idx);
|
|
921
|
+
const fromX = actorX(msg.from);
|
|
922
|
+
const toX = actorX(msg.to);
|
|
923
|
+
const selectedHere = selected === msg.id;
|
|
924
|
+
const isDragging = drag?.active && drag.id === msg.id;
|
|
925
|
+
const isSelf = msg.from === msg.to;
|
|
926
|
+
const stroke = selectedHere ? INDIGO : t.arrow;
|
|
927
|
+
const dash = msg.style === "dashed" ? "6,4" : void 0;
|
|
928
|
+
const cursorStyle = drag?.active ? STYLE_SEQ_GRABBING : STYLE_SEQ_GRAB;
|
|
929
|
+
const groupStyle = isDragging ? { ...cursorStyle, ...STYLE_SEQ_DRAGGING } : cursorStyle;
|
|
930
|
+
if (isSelf) {
|
|
931
|
+
const startX = fromX;
|
|
932
|
+
const loopW = 36;
|
|
933
|
+
const loopY = y - 6;
|
|
934
|
+
const d = `M ${startX} ${loopY} C ${startX + loopW} ${loopY}, ${startX + loopW} ${loopY + 24}, ${startX} ${loopY + 24}`;
|
|
935
|
+
return /* @__PURE__ */ jsxs4("g", { onMouseDown: (e) => onRowMouseDown(e, msg.id), style: groupStyle, children: [
|
|
936
|
+
(selectedHere || isDragging) && /* @__PURE__ */ jsx4(
|
|
937
|
+
"rect",
|
|
938
|
+
{
|
|
939
|
+
x: SIDE_PAD - 8,
|
|
940
|
+
y: y - 22,
|
|
941
|
+
width: totalW - (SIDE_PAD - 8) * 2,
|
|
942
|
+
height: ROW_H - 12,
|
|
943
|
+
rx: 10,
|
|
944
|
+
fill: INDIGO_SOFT,
|
|
945
|
+
opacity: isDark ? 0.18 : 0.6
|
|
946
|
+
}
|
|
947
|
+
),
|
|
948
|
+
/* @__PURE__ */ jsx4("path", { d, fill: "none", stroke, strokeWidth: 1.5, strokeDasharray: dash, markerEnd: "url(#seqArrow)" }),
|
|
949
|
+
/* @__PURE__ */ jsx4("text", { x: startX + loopW + 8, y: loopY + 16, fontSize: 11, fill: selectedHere ? INDIGO : t.textPrimary, fontWeight: 500, children: msg.label })
|
|
950
|
+
] }, msg.id);
|
|
951
|
+
}
|
|
952
|
+
const labelX = (fromX + toX) / 2;
|
|
953
|
+
return /* @__PURE__ */ jsxs4("g", { onMouseDown: (e) => onRowMouseDown(e, msg.id), style: groupStyle, children: [
|
|
954
|
+
(selectedHere || isDragging) && /* @__PURE__ */ jsx4(
|
|
955
|
+
"rect",
|
|
956
|
+
{
|
|
957
|
+
x: SIDE_PAD - 8,
|
|
958
|
+
y: y - 22,
|
|
959
|
+
width: totalW - (SIDE_PAD - 8) * 2,
|
|
960
|
+
height: ROW_H - 12,
|
|
961
|
+
rx: 10,
|
|
962
|
+
fill: INDIGO_SOFT,
|
|
963
|
+
opacity: isDark ? 0.18 : 0.6
|
|
964
|
+
}
|
|
965
|
+
),
|
|
966
|
+
/* @__PURE__ */ jsx4("line", { x1: fromX, y1: y, x2: toX, y2: y, stroke, strokeWidth: 1.5, strokeDasharray: dash, markerEnd: "url(#seqArrow)" }),
|
|
967
|
+
/* @__PURE__ */ jsx4(
|
|
968
|
+
"rect",
|
|
969
|
+
{
|
|
970
|
+
x: labelX - estimateW(msg.label) / 2 - 6,
|
|
971
|
+
y: y - 18,
|
|
972
|
+
width: estimateW(msg.label) + 12,
|
|
973
|
+
height: 18,
|
|
974
|
+
rx: 6,
|
|
975
|
+
fill: t.canvas,
|
|
976
|
+
stroke: selectedHere ? INDIGO : t.cardBorder,
|
|
977
|
+
strokeWidth: selectedHere ? 1.25 : 1
|
|
978
|
+
}
|
|
979
|
+
),
|
|
980
|
+
/* @__PURE__ */ jsx4("text", { x: labelX, y: y - 5, textAnchor: "middle", fontSize: 11, fill: selectedHere ? INDIGO : t.textPrimary, fontWeight: 500, children: msg.label })
|
|
981
|
+
] }, msg.id);
|
|
982
|
+
}),
|
|
983
|
+
actors.map((name) => {
|
|
984
|
+
const x = actorX(name);
|
|
985
|
+
const w = colW - 24;
|
|
986
|
+
return /* @__PURE__ */ jsxs4("g", { children: [
|
|
987
|
+
/* @__PURE__ */ jsx4(
|
|
988
|
+
"rect",
|
|
989
|
+
{
|
|
990
|
+
x: x - w / 2,
|
|
991
|
+
y: HEADER_PAD,
|
|
992
|
+
width: w,
|
|
993
|
+
height: HEADER_H,
|
|
994
|
+
rx: 12,
|
|
995
|
+
fill: t.actorFill,
|
|
996
|
+
stroke: t.actorStroke,
|
|
997
|
+
strokeWidth: 1.25,
|
|
998
|
+
filter: "url(#seqShadow)"
|
|
999
|
+
}
|
|
1000
|
+
),
|
|
1001
|
+
editingId === name ? /* @__PURE__ */ jsx4("foreignObject", { x: x - w / 2 + 8, y: HEADER_PAD + 16, width: w - 16, height: 32, children: /* @__PURE__ */ jsx4(
|
|
1002
|
+
"input",
|
|
1003
|
+
{
|
|
1004
|
+
autoFocus: true,
|
|
1005
|
+
defaultValue: name,
|
|
1006
|
+
onBlur: (e) => {
|
|
1007
|
+
renameActor(name, e.currentTarget.value.trim());
|
|
1008
|
+
setEditingId(null);
|
|
1009
|
+
},
|
|
1010
|
+
onKeyDown: (e) => {
|
|
1011
|
+
if (e.key === "Enter") {
|
|
1012
|
+
renameActor(name, e.target.value.trim());
|
|
1013
|
+
setEditingId(null);
|
|
1014
|
+
}
|
|
1015
|
+
if (e.key === "Escape") setEditingId(null);
|
|
1016
|
+
},
|
|
1017
|
+
style: {
|
|
1018
|
+
width: "100%",
|
|
1019
|
+
height: "100%",
|
|
1020
|
+
border: "none",
|
|
1021
|
+
borderRadius: 6,
|
|
1022
|
+
outline: `2px solid ${INDIGO}`,
|
|
1023
|
+
textAlign: "center",
|
|
1024
|
+
fontSize: 13,
|
|
1025
|
+
fontWeight: 600,
|
|
1026
|
+
background: t.inputBg,
|
|
1027
|
+
color: t.inputText,
|
|
1028
|
+
boxSizing: "border-box",
|
|
1029
|
+
padding: "0 6px",
|
|
1030
|
+
fontFamily: "inherit"
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
) }) : /* @__PURE__ */ jsx4(
|
|
1034
|
+
"text",
|
|
1035
|
+
{
|
|
1036
|
+
x,
|
|
1037
|
+
y: HEADER_PAD + HEADER_H / 2 + 4,
|
|
1038
|
+
textAnchor: "middle",
|
|
1039
|
+
fontSize: 13,
|
|
1040
|
+
fontWeight: 700,
|
|
1041
|
+
fill: t.actorText,
|
|
1042
|
+
style: STYLE_SEQ_ACTOR_TEXT,
|
|
1043
|
+
onDoubleClick: () => setEditingId(name),
|
|
1044
|
+
children: name
|
|
1045
|
+
}
|
|
1046
|
+
),
|
|
1047
|
+
/* @__PURE__ */ jsx4(
|
|
1048
|
+
"circle",
|
|
1049
|
+
{
|
|
1050
|
+
cx: x + w / 2 - 12,
|
|
1051
|
+
cy: HEADER_PAD + 14,
|
|
1052
|
+
r: 9,
|
|
1053
|
+
fill: "transparent",
|
|
1054
|
+
style: STYLE_SEQ_REMOVE_BTN,
|
|
1055
|
+
onClick: () => removeActor(name),
|
|
1056
|
+
children: /* @__PURE__ */ jsx4("title", { children: "Remove actor" })
|
|
1057
|
+
}
|
|
1058
|
+
),
|
|
1059
|
+
/* @__PURE__ */ jsx4(
|
|
1060
|
+
"text",
|
|
1061
|
+
{
|
|
1062
|
+
x: x + w / 2 - 12,
|
|
1063
|
+
y: HEADER_PAD + 18,
|
|
1064
|
+
textAnchor: "middle",
|
|
1065
|
+
fontSize: 12,
|
|
1066
|
+
fill: t.textMuted,
|
|
1067
|
+
style: STYLE_SEQ_REMOVE_ICON,
|
|
1068
|
+
children: "\xD7"
|
|
1069
|
+
}
|
|
1070
|
+
)
|
|
1071
|
+
] }, `hdr-${name}`);
|
|
1072
|
+
})
|
|
1073
|
+
]
|
|
1074
|
+
}
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// src/ui/hooks/useEditorTheme.ts
|
|
1079
|
+
import { useMemo as useMemo2 } from "react";
|
|
814
1080
|
|
|
815
1081
|
// src/ui/hooks/useSystemTheme.ts
|
|
816
1082
|
import { useEffect as useEffect3, useState as useState4 } from "react";
|
|
@@ -857,7 +1123,7 @@ function usePrefersReducedMotion() {
|
|
|
857
1123
|
// src/ui/hooks/useEditorTheme.ts
|
|
858
1124
|
function useEditorTheme(theme, overrides, palettes) {
|
|
859
1125
|
const isDark = useIsDark(theme);
|
|
860
|
-
const t =
|
|
1126
|
+
const t = useMemo2(
|
|
861
1127
|
() => ({ ...isDark ? palettes.dark : palettes.light, ...overrides ?? {} }),
|
|
862
1128
|
// palettes is a stable module-level constant in every caller, so it is
|
|
863
1129
|
// deliberately omitted from the dep array to keep the memo key tight.
|
|
@@ -1695,10 +1961,33 @@ function cloneModel(m) {
|
|
|
1695
1961
|
};
|
|
1696
1962
|
}
|
|
1697
1963
|
|
|
1964
|
+
// src/ui/hooks/useEditorKeyboard.ts
|
|
1965
|
+
import { useEffect as useEffect4 } from "react";
|
|
1966
|
+
var isInput = (e) => {
|
|
1967
|
+
const tgt = e.target;
|
|
1968
|
+
return !!(tgt && (tgt.tagName === "INPUT" || tgt.tagName === "TEXTAREA" || tgt.isContentEditable));
|
|
1969
|
+
};
|
|
1970
|
+
function useEditorKeyboard(commands, deps) {
|
|
1971
|
+
useEffect4(() => {
|
|
1972
|
+
const onKey = (e) => {
|
|
1973
|
+
if (isInput(e)) return;
|
|
1974
|
+
for (const cmd of commands) {
|
|
1975
|
+
if (cmd.match(e)) {
|
|
1976
|
+
const handled = cmd.run(e);
|
|
1977
|
+
if (handled) e.preventDefault();
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
};
|
|
1982
|
+
window.addEventListener("keydown", onKey);
|
|
1983
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
1984
|
+
}, deps);
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1698
1987
|
// src/ui/SequenceEditor.tsx
|
|
1699
|
-
import { jsx as
|
|
1700
|
-
var
|
|
1701
|
-
var
|
|
1988
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1989
|
+
var INDIGO2 = "#4f46e5";
|
|
1990
|
+
var INDIGO_SOFT2 = "#eef2ff";
|
|
1702
1991
|
var lightTheme2 = {
|
|
1703
1992
|
canvas: "#fafbfc",
|
|
1704
1993
|
dot: "#dbe3ee",
|
|
@@ -1741,11 +2030,11 @@ var darkTheme2 = {
|
|
|
1741
2030
|
actorStroke: "rgba(99,102,241,0.45)",
|
|
1742
2031
|
actorText: "#a5b4fc"
|
|
1743
2032
|
};
|
|
1744
|
-
var
|
|
1745
|
-
var
|
|
2033
|
+
var HEADER_H2 = 64;
|
|
2034
|
+
var HEADER_PAD2 = 24;
|
|
1746
2035
|
var COL_MIN = 160;
|
|
1747
|
-
var
|
|
1748
|
-
var
|
|
2036
|
+
var ROW_H2 = 64;
|
|
2037
|
+
var SIDE_PAD2 = 40;
|
|
1749
2038
|
var DRAG_THRESHOLD = 5;
|
|
1750
2039
|
function ensureSequenceModel(m) {
|
|
1751
2040
|
if (m && m.type === "sequence") {
|
|
@@ -1774,18 +2063,18 @@ function SequenceEditor({
|
|
|
1774
2063
|
const { t, isDark } = useEditorTheme(theme, themeOverrides, { light: lightTheme2, dark: darkTheme2 });
|
|
1775
2064
|
const actors = model.actors ?? [];
|
|
1776
2065
|
const messages = model.messages ?? [];
|
|
1777
|
-
const colW =
|
|
2066
|
+
const colW = useMemo3(() => {
|
|
1778
2067
|
const longest = actors.reduce((m, a) => Math.max(m, a.length), 6);
|
|
1779
2068
|
return Math.max(COL_MIN, longest * 9 + 40);
|
|
1780
2069
|
}, [actors]);
|
|
1781
|
-
const totalW =
|
|
1782
|
-
const totalH =
|
|
2070
|
+
const totalW = SIDE_PAD2 * 2 + Math.max(1, actors.length) * colW;
|
|
2071
|
+
const totalH = HEADER_PAD2 + HEADER_H2 + 32 + messages.length * ROW_H2 + 48;
|
|
1783
2072
|
const actorX = (name) => {
|
|
1784
2073
|
const idx = actors.indexOf(name);
|
|
1785
|
-
if (idx < 0) return
|
|
1786
|
-
return
|
|
2074
|
+
if (idx < 0) return SIDE_PAD2 + colW / 2;
|
|
2075
|
+
return SIDE_PAD2 + idx * colW + colW / 2;
|
|
1787
2076
|
};
|
|
1788
|
-
const msgY = (idx) =>
|
|
2077
|
+
const msgY = (idx) => HEADER_PAD2 + HEADER_H2 + 40 + idx * ROW_H2;
|
|
1789
2078
|
const pushHistory = useCallback4((m) => {
|
|
1790
2079
|
const stack = historyRef.current.slice(0, historyIdxRef.current + 1);
|
|
1791
2080
|
stack.push(m);
|
|
@@ -1871,34 +2160,26 @@ function SequenceEditor({
|
|
|
1871
2160
|
next.splice(toIdx, 0, moved);
|
|
1872
2161
|
applyAndPush({ ...model, messages: next });
|
|
1873
2162
|
}, [messages, model, applyAndPush]);
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
if ((e.key === "Delete" || e.key === "Backspace") && selected) {
|
|
1895
|
-
e.preventDefault();
|
|
1896
|
-
removeMessage(selected);
|
|
1897
|
-
}
|
|
1898
|
-
};
|
|
1899
|
-
window.addEventListener("keydown", onKey);
|
|
1900
|
-
return () => window.removeEventListener("keydown", onKey);
|
|
1901
|
-
}, [undo, redo, selected]);
|
|
2163
|
+
const keyCommands = [
|
|
2164
|
+
{ match: (e) => (e.ctrlKey || e.metaKey) && e.key === "z", run: () => {
|
|
2165
|
+
undo();
|
|
2166
|
+
return true;
|
|
2167
|
+
} },
|
|
2168
|
+
{ match: (e) => (e.ctrlKey || e.metaKey) && (e.key === "y" || e.shiftKey && e.key === "z"), run: () => {
|
|
2169
|
+
redo();
|
|
2170
|
+
return true;
|
|
2171
|
+
} },
|
|
2172
|
+
{ match: (e) => e.key === "Escape", run: () => {
|
|
2173
|
+
setSelected(null);
|
|
2174
|
+
setEditingId(null);
|
|
2175
|
+
return true;
|
|
2176
|
+
} },
|
|
2177
|
+
{ match: (e) => (e.key === "Delete" || e.key === "Backspace") && !!selected, run: () => {
|
|
2178
|
+
removeMessage(selected);
|
|
2179
|
+
return true;
|
|
2180
|
+
} }
|
|
2181
|
+
];
|
|
2182
|
+
useEditorKeyboard(keyCommands, [undo, redo, selected]);
|
|
1902
2183
|
const handleExport = useExporters(model, onExport, "sequence");
|
|
1903
2184
|
const handleImport = useImporter(applyAndPush, {
|
|
1904
2185
|
expectedType: "sequence",
|
|
@@ -1913,9 +2194,9 @@ function SequenceEditor({
|
|
|
1913
2194
|
setSelected(id);
|
|
1914
2195
|
setDrag({ id, startY: e.clientY, originalIdx: idx, targetIdx: idx, active: false });
|
|
1915
2196
|
};
|
|
1916
|
-
|
|
2197
|
+
useEffect5(() => {
|
|
1917
2198
|
if (!drag) return;
|
|
1918
|
-
const baseY =
|
|
2199
|
+
const baseY = HEADER_PAD2 + HEADER_H2 + 40;
|
|
1919
2200
|
const onMove = (ev) => {
|
|
1920
2201
|
const dy = ev.clientY - drag.startY;
|
|
1921
2202
|
if (!drag.active && Math.abs(dy) < DRAG_THRESHOLD) return;
|
|
@@ -1923,7 +2204,7 @@ function SequenceEditor({
|
|
|
1923
2204
|
if (!svg) return;
|
|
1924
2205
|
const rect = svg.getBoundingClientRect();
|
|
1925
2206
|
const yInSvg = ev.clientY - rect.top;
|
|
1926
|
-
const raw = Math.floor((yInSvg - baseY +
|
|
2207
|
+
const raw = Math.floor((yInSvg - baseY + ROW_H2 / 2) / ROW_H2);
|
|
1927
2208
|
const next = Math.max(0, Math.min(messages.length - 1, raw));
|
|
1928
2209
|
if (next === drag.targetIdx && drag.active) return;
|
|
1929
2210
|
setDrag({ ...drag, active: true, targetIdx: next });
|
|
@@ -1941,17 +2222,8 @@ function SequenceEditor({
|
|
|
1941
2222
|
window.removeEventListener("mouseup", onUp);
|
|
1942
2223
|
};
|
|
1943
2224
|
}, [drag, messages.length, reorderMessage]);
|
|
1944
|
-
const visualMessages = useMemo2(() => {
|
|
1945
|
-
if (!drag?.active) return messages;
|
|
1946
|
-
const idx = messages.findIndex((m) => m.id === drag.id);
|
|
1947
|
-
if (idx < 0) return messages;
|
|
1948
|
-
const next = messages.slice();
|
|
1949
|
-
const [moved] = next.splice(idx, 1);
|
|
1950
|
-
next.splice(drag.targetIdx, 0, moved);
|
|
1951
|
-
return next;
|
|
1952
|
-
}, [messages, drag]);
|
|
1953
2225
|
const selectedMsg = selected ? messages.find((m) => m.id === selected) : null;
|
|
1954
|
-
return /* @__PURE__ */
|
|
2226
|
+
return /* @__PURE__ */ jsxs5("div", { style: {
|
|
1955
2227
|
display: "flex",
|
|
1956
2228
|
flexDirection: "column",
|
|
1957
2229
|
height,
|
|
@@ -1959,8 +2231,8 @@ function SequenceEditor({
|
|
|
1959
2231
|
fontFamily: "ui-sans-serif,system-ui,sans-serif",
|
|
1960
2232
|
background: t.ctrlsBg
|
|
1961
2233
|
}, children: [
|
|
1962
|
-
/* @__PURE__ */
|
|
1963
|
-
/* @__PURE__ */
|
|
2234
|
+
/* @__PURE__ */ jsx5(Toolbar, { onExport: handleExport, onImport: allowImport ? handleImport : void 0, allowedExports, allowImport }),
|
|
2235
|
+
/* @__PURE__ */ jsxs5("div", { style: {
|
|
1964
2236
|
display: "flex",
|
|
1965
2237
|
gap: 8,
|
|
1966
2238
|
padding: "7px 14px",
|
|
@@ -1969,12 +2241,12 @@ function SequenceEditor({
|
|
|
1969
2241
|
alignItems: "center",
|
|
1970
2242
|
flexWrap: "wrap"
|
|
1971
2243
|
}, children: [
|
|
1972
|
-
/* @__PURE__ */
|
|
1973
|
-
/* @__PURE__ */
|
|
1974
|
-
/* @__PURE__ */
|
|
1975
|
-
/* @__PURE__ */
|
|
1976
|
-
/* @__PURE__ */
|
|
1977
|
-
/* @__PURE__ */
|
|
2244
|
+
/* @__PURE__ */ jsx5("button", { onClick: addActor, style: primaryBtn(), children: "+ Actor" }),
|
|
2245
|
+
/* @__PURE__ */ jsx5("button", { onClick: addMessage, style: primaryBtn(), children: "+ Message" }),
|
|
2246
|
+
/* @__PURE__ */ jsx5("div", { style: { width: 1, height: 18, background: t.ctrlsBorder, margin: "0 4px" } }),
|
|
2247
|
+
/* @__PURE__ */ jsx5("button", { onClick: undo, style: ghostBtn2(t), title: "Undo (Ctrl+Z)", children: "\u21B6" }),
|
|
2248
|
+
/* @__PURE__ */ jsx5("button", { onClick: redo, style: ghostBtn2(t), title: "Redo (Ctrl+Y)", children: "\u21B7" }),
|
|
2249
|
+
/* @__PURE__ */ jsxs5("span", { style: { marginLeft: "auto", fontSize: 11, color: t.textMuted }, children: [
|
|
1978
2250
|
actors.length,
|
|
1979
2251
|
" actor",
|
|
1980
2252
|
actors.length === 1 ? "" : "s",
|
|
@@ -1985,215 +2257,31 @@ function SequenceEditor({
|
|
|
1985
2257
|
" \xB7 drag a row to reorder"
|
|
1986
2258
|
] })
|
|
1987
2259
|
] }),
|
|
1988
|
-
/* @__PURE__ */
|
|
1989
|
-
/* @__PURE__ */
|
|
1990
|
-
|
|
1991
|
-
inset: 0,
|
|
1992
|
-
display: "flex",
|
|
1993
|
-
flexDirection: "column",
|
|
1994
|
-
alignItems: "center",
|
|
1995
|
-
justifyContent: "center",
|
|
1996
|
-
gap: 10,
|
|
1997
|
-
color: t.textMuted,
|
|
1998
|
-
pointerEvents: "none"
|
|
1999
|
-
}, children: [
|
|
2000
|
-
/* @__PURE__ */ jsx4("div", { style: { fontSize: 36, opacity: 0.15, color: t.textPrimary }, children: "\u2194" }),
|
|
2001
|
-
/* @__PURE__ */ jsxs4("div", { style: { fontSize: 13, fontWeight: 500 }, children: [
|
|
2002
|
-
"Click ",
|
|
2003
|
-
/* @__PURE__ */ jsx4("strong", { style: { color: INDIGO }, children: "+ Actor" }),
|
|
2004
|
-
" then ",
|
|
2005
|
-
/* @__PURE__ */ jsx4("strong", { style: { color: INDIGO }, children: "+ Message" }),
|
|
2006
|
-
" to start"
|
|
2007
|
-
] })
|
|
2008
|
-
] }) : /* @__PURE__ */ jsxs4(
|
|
2009
|
-
"svg",
|
|
2260
|
+
/* @__PURE__ */ jsxs5("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
|
|
2261
|
+
/* @__PURE__ */ jsx5("div", { style: { flex: 1, overflow: "auto", background: t.canvas, position: "relative" }, children: /* @__PURE__ */ jsx5(
|
|
2262
|
+
SequenceCanvas,
|
|
2010
2263
|
{
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
x2: x,
|
|
2030
|
-
y1: top + 4,
|
|
2031
|
-
y2: totalH - 24,
|
|
2032
|
-
stroke: t.lifeline,
|
|
2033
|
-
strokeWidth: 1.25,
|
|
2034
|
-
strokeDasharray: "5 5"
|
|
2035
|
-
},
|
|
2036
|
-
`life-${name}`
|
|
2037
|
-
);
|
|
2038
|
-
}),
|
|
2039
|
-
visualMessages.map((msg, idx) => {
|
|
2040
|
-
const y = msgY(idx);
|
|
2041
|
-
const fromX = actorX(msg.from);
|
|
2042
|
-
const toX = actorX(msg.to);
|
|
2043
|
-
const selectedHere = selected === msg.id;
|
|
2044
|
-
const isDragging = drag?.active && drag.id === msg.id;
|
|
2045
|
-
const isSelf = msg.from === msg.to;
|
|
2046
|
-
const stroke = selectedHere ? INDIGO : t.arrow;
|
|
2047
|
-
const dash = msg.style === "dashed" ? "6,4" : void 0;
|
|
2048
|
-
const cursor = drag?.active ? "grabbing" : "grab";
|
|
2049
|
-
const groupOpacity = isDragging ? 0.85 : 1;
|
|
2050
|
-
if (isSelf) {
|
|
2051
|
-
const startX = fromX;
|
|
2052
|
-
const loopW = 36;
|
|
2053
|
-
const loopY = y - 6;
|
|
2054
|
-
const d = `M ${startX} ${loopY} C ${startX + loopW} ${loopY}, ${startX + loopW} ${loopY + 24}, ${startX} ${loopY + 24}`;
|
|
2055
|
-
return /* @__PURE__ */ jsxs4("g", { onMouseDown: (e) => onRowMouseDown(e, msg.id), style: { cursor, opacity: groupOpacity }, children: [
|
|
2056
|
-
(selectedHere || isDragging) && /* @__PURE__ */ jsx4(
|
|
2057
|
-
"rect",
|
|
2058
|
-
{
|
|
2059
|
-
x: SIDE_PAD - 8,
|
|
2060
|
-
y: y - 22,
|
|
2061
|
-
width: totalW - (SIDE_PAD - 8) * 2,
|
|
2062
|
-
height: ROW_H - 12,
|
|
2063
|
-
rx: 10,
|
|
2064
|
-
fill: INDIGO_SOFT,
|
|
2065
|
-
opacity: isDark ? 0.18 : 0.6
|
|
2066
|
-
}
|
|
2067
|
-
),
|
|
2068
|
-
/* @__PURE__ */ jsx4("path", { d, fill: "none", stroke, strokeWidth: 1.5, strokeDasharray: dash, markerEnd: "url(#seqArrow)" }),
|
|
2069
|
-
/* @__PURE__ */ jsx4("text", { x: startX + loopW + 8, y: loopY + 16, fontSize: 11, fill: selectedHere ? INDIGO : t.textPrimary, fontWeight: 500, children: msg.label })
|
|
2070
|
-
] }, msg.id);
|
|
2071
|
-
}
|
|
2072
|
-
const labelX = (fromX + toX) / 2;
|
|
2073
|
-
return /* @__PURE__ */ jsxs4("g", { onMouseDown: (e) => onRowMouseDown(e, msg.id), style: { cursor, opacity: groupOpacity }, children: [
|
|
2074
|
-
(selectedHere || isDragging) && /* @__PURE__ */ jsx4(
|
|
2075
|
-
"rect",
|
|
2076
|
-
{
|
|
2077
|
-
x: SIDE_PAD - 8,
|
|
2078
|
-
y: y - 22,
|
|
2079
|
-
width: totalW - (SIDE_PAD - 8) * 2,
|
|
2080
|
-
height: ROW_H - 12,
|
|
2081
|
-
rx: 10,
|
|
2082
|
-
fill: INDIGO_SOFT,
|
|
2083
|
-
opacity: isDark ? 0.18 : 0.6
|
|
2084
|
-
}
|
|
2085
|
-
),
|
|
2086
|
-
/* @__PURE__ */ jsx4("line", { x1: fromX, y1: y, x2: toX, y2: y, stroke, strokeWidth: 1.5, strokeDasharray: dash, markerEnd: "url(#seqArrow)" }),
|
|
2087
|
-
/* @__PURE__ */ jsx4(
|
|
2088
|
-
"rect",
|
|
2089
|
-
{
|
|
2090
|
-
x: labelX - estimateW(msg.label) / 2 - 6,
|
|
2091
|
-
y: y - 18,
|
|
2092
|
-
width: estimateW(msg.label) + 12,
|
|
2093
|
-
height: 18,
|
|
2094
|
-
rx: 6,
|
|
2095
|
-
fill: t.canvas,
|
|
2096
|
-
stroke: selectedHere ? INDIGO : t.cardBorder,
|
|
2097
|
-
strokeWidth: selectedHere ? 1.25 : 1
|
|
2098
|
-
}
|
|
2099
|
-
),
|
|
2100
|
-
/* @__PURE__ */ jsx4("text", { x: labelX, y: y - 5, textAnchor: "middle", fontSize: 11, fill: selectedHere ? INDIGO : t.textPrimary, fontWeight: 500, children: msg.label })
|
|
2101
|
-
] }, msg.id);
|
|
2102
|
-
}),
|
|
2103
|
-
actors.map((name) => {
|
|
2104
|
-
const x = actorX(name);
|
|
2105
|
-
const w = colW - 24;
|
|
2106
|
-
return /* @__PURE__ */ jsxs4("g", { children: [
|
|
2107
|
-
/* @__PURE__ */ jsx4(
|
|
2108
|
-
"rect",
|
|
2109
|
-
{
|
|
2110
|
-
x: x - w / 2,
|
|
2111
|
-
y: HEADER_PAD,
|
|
2112
|
-
width: w,
|
|
2113
|
-
height: HEADER_H,
|
|
2114
|
-
rx: 12,
|
|
2115
|
-
fill: t.actorFill,
|
|
2116
|
-
stroke: t.actorStroke,
|
|
2117
|
-
strokeWidth: 1.25,
|
|
2118
|
-
filter: "url(#seqShadow)"
|
|
2119
|
-
}
|
|
2120
|
-
),
|
|
2121
|
-
editingId === name ? /* @__PURE__ */ jsx4("foreignObject", { x: x - w / 2 + 8, y: HEADER_PAD + 16, width: w - 16, height: 32, children: /* @__PURE__ */ jsx4(
|
|
2122
|
-
"input",
|
|
2123
|
-
{
|
|
2124
|
-
autoFocus: true,
|
|
2125
|
-
defaultValue: name,
|
|
2126
|
-
onBlur: (e) => {
|
|
2127
|
-
renameActor(name, e.currentTarget.value.trim());
|
|
2128
|
-
setEditingId(null);
|
|
2129
|
-
},
|
|
2130
|
-
onKeyDown: (e) => {
|
|
2131
|
-
if (e.key === "Enter") {
|
|
2132
|
-
renameActor(name, e.target.value.trim());
|
|
2133
|
-
setEditingId(null);
|
|
2134
|
-
}
|
|
2135
|
-
if (e.key === "Escape") setEditingId(null);
|
|
2136
|
-
},
|
|
2137
|
-
style: {
|
|
2138
|
-
width: "100%",
|
|
2139
|
-
height: "100%",
|
|
2140
|
-
border: "none",
|
|
2141
|
-
borderRadius: 6,
|
|
2142
|
-
outline: `2px solid ${INDIGO}`,
|
|
2143
|
-
textAlign: "center",
|
|
2144
|
-
fontSize: 13,
|
|
2145
|
-
fontWeight: 600,
|
|
2146
|
-
background: t.inputBg,
|
|
2147
|
-
color: t.inputText,
|
|
2148
|
-
boxSizing: "border-box",
|
|
2149
|
-
padding: "0 6px",
|
|
2150
|
-
fontFamily: "inherit"
|
|
2151
|
-
}
|
|
2152
|
-
}
|
|
2153
|
-
) }) : /* @__PURE__ */ jsx4(
|
|
2154
|
-
"text",
|
|
2155
|
-
{
|
|
2156
|
-
x,
|
|
2157
|
-
y: HEADER_PAD + HEADER_H / 2 + 4,
|
|
2158
|
-
textAnchor: "middle",
|
|
2159
|
-
fontSize: 13,
|
|
2160
|
-
fontWeight: 700,
|
|
2161
|
-
fill: t.actorText,
|
|
2162
|
-
style: { cursor: "pointer", userSelect: "none" },
|
|
2163
|
-
onDoubleClick: () => setEditingId(name),
|
|
2164
|
-
children: name
|
|
2165
|
-
}
|
|
2166
|
-
),
|
|
2167
|
-
/* @__PURE__ */ jsx4(
|
|
2168
|
-
"circle",
|
|
2169
|
-
{
|
|
2170
|
-
cx: x + w / 2 - 12,
|
|
2171
|
-
cy: HEADER_PAD + 14,
|
|
2172
|
-
r: 9,
|
|
2173
|
-
fill: "transparent",
|
|
2174
|
-
style: { cursor: "pointer" },
|
|
2175
|
-
onClick: () => removeActor(name),
|
|
2176
|
-
children: /* @__PURE__ */ jsx4("title", { children: "Remove actor" })
|
|
2177
|
-
}
|
|
2178
|
-
),
|
|
2179
|
-
/* @__PURE__ */ jsx4(
|
|
2180
|
-
"text",
|
|
2181
|
-
{
|
|
2182
|
-
x: x + w / 2 - 12,
|
|
2183
|
-
y: HEADER_PAD + 18,
|
|
2184
|
-
textAnchor: "middle",
|
|
2185
|
-
fontSize: 12,
|
|
2186
|
-
fill: t.textMuted,
|
|
2187
|
-
style: { pointerEvents: "none", userSelect: "none" },
|
|
2188
|
-
children: "\xD7"
|
|
2189
|
-
}
|
|
2190
|
-
)
|
|
2191
|
-
] }, `hdr-${name}`);
|
|
2192
|
-
})
|
|
2193
|
-
]
|
|
2264
|
+
model,
|
|
2265
|
+
actors,
|
|
2266
|
+
messages,
|
|
2267
|
+
t,
|
|
2268
|
+
isDark,
|
|
2269
|
+
colW,
|
|
2270
|
+
totalW,
|
|
2271
|
+
totalH,
|
|
2272
|
+
actorX,
|
|
2273
|
+
msgY,
|
|
2274
|
+
selected,
|
|
2275
|
+
editingId,
|
|
2276
|
+
setEditingId,
|
|
2277
|
+
drag,
|
|
2278
|
+
onRowMouseDown,
|
|
2279
|
+
renameActor,
|
|
2280
|
+
removeActor,
|
|
2281
|
+
svgRef
|
|
2194
2282
|
}
|
|
2195
2283
|
) }),
|
|
2196
|
-
selectedMsg && /* @__PURE__ */
|
|
2284
|
+
selectedMsg && /* @__PURE__ */ jsxs5("div", { style: {
|
|
2197
2285
|
width: 280,
|
|
2198
2286
|
flexShrink: 0,
|
|
2199
2287
|
background: t.panelBg,
|
|
@@ -2201,9 +2289,9 @@ function SequenceEditor({
|
|
|
2201
2289
|
padding: "14px 16px",
|
|
2202
2290
|
overflowY: "auto"
|
|
2203
2291
|
}, children: [
|
|
2204
|
-
/* @__PURE__ */
|
|
2205
|
-
/* @__PURE__ */
|
|
2206
|
-
/* @__PURE__ */
|
|
2292
|
+
/* @__PURE__ */ jsx5("div", { style: { fontSize: 10, fontWeight: 700, color: t.textMuted, textTransform: "uppercase", letterSpacing: 0.7, marginBottom: 10 }, children: "Message" }),
|
|
2293
|
+
/* @__PURE__ */ jsx5(Label, { t, children: "Label" }),
|
|
2294
|
+
/* @__PURE__ */ jsx5(
|
|
2207
2295
|
"input",
|
|
2208
2296
|
{
|
|
2209
2297
|
value: editLabel || selectedMsg.label,
|
|
@@ -2219,21 +2307,21 @@ function SequenceEditor({
|
|
|
2219
2307
|
style: input(t)
|
|
2220
2308
|
}
|
|
2221
2309
|
),
|
|
2222
|
-
/* @__PURE__ */
|
|
2223
|
-
/* @__PURE__ */
|
|
2224
|
-
/* @__PURE__ */
|
|
2225
|
-
/* @__PURE__ */
|
|
2226
|
-
/* @__PURE__ */
|
|
2227
|
-
/* @__PURE__ */
|
|
2310
|
+
/* @__PURE__ */ jsx5(Label, { t, children: "From" }),
|
|
2311
|
+
/* @__PURE__ */ jsx5("select", { value: selectedMsg.from, onChange: (e) => updateMessage(selectedMsg.id, { from: e.target.value }), style: input(t), children: actors.map((a) => /* @__PURE__ */ jsx5("option", { value: a, children: a }, a)) }),
|
|
2312
|
+
/* @__PURE__ */ jsx5(Label, { t, children: "To" }),
|
|
2313
|
+
/* @__PURE__ */ jsx5("select", { value: selectedMsg.to, onChange: (e) => updateMessage(selectedMsg.id, { to: e.target.value }), style: input(t), children: actors.map((a) => /* @__PURE__ */ jsx5("option", { value: a, children: a }, a)) }),
|
|
2314
|
+
/* @__PURE__ */ jsx5(Label, { t, children: "Style" }),
|
|
2315
|
+
/* @__PURE__ */ jsx5("div", { style: { display: "flex", gap: 6 }, children: ["solid", "dashed"].map((s2) => /* @__PURE__ */ jsx5(
|
|
2228
2316
|
"button",
|
|
2229
2317
|
{
|
|
2230
2318
|
onClick: () => updateMessage(selectedMsg.id, { style: s2 }),
|
|
2231
2319
|
style: {
|
|
2232
2320
|
flex: 1,
|
|
2233
2321
|
padding: "6px 10px",
|
|
2234
|
-
border: `1.5px solid ${selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ?
|
|
2235
|
-
background: selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ?
|
|
2236
|
-
color: selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ?
|
|
2322
|
+
border: `1.5px solid ${selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ? INDIGO2 : t.inputBorder}`,
|
|
2323
|
+
background: selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ? INDIGO_SOFT2 : t.inputBg,
|
|
2324
|
+
color: selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ? INDIGO2 : t.textPrimary,
|
|
2237
2325
|
borderRadius: 8,
|
|
2238
2326
|
fontSize: 12,
|
|
2239
2327
|
fontWeight: 600,
|
|
@@ -2244,8 +2332,8 @@ function SequenceEditor({
|
|
|
2244
2332
|
},
|
|
2245
2333
|
s2
|
|
2246
2334
|
)) }),
|
|
2247
|
-
/* @__PURE__ */
|
|
2248
|
-
/* @__PURE__ */
|
|
2335
|
+
/* @__PURE__ */ jsx5("div", { style: { height: 14 } }),
|
|
2336
|
+
/* @__PURE__ */ jsx5(
|
|
2249
2337
|
"button",
|
|
2250
2338
|
{
|
|
2251
2339
|
onClick: () => removeMessage(selectedMsg.id),
|
|
@@ -2255,7 +2343,7 @@ function SequenceEditor({
|
|
|
2255
2343
|
)
|
|
2256
2344
|
] })
|
|
2257
2345
|
] }),
|
|
2258
|
-
/* @__PURE__ */
|
|
2346
|
+
/* @__PURE__ */ jsxs5("div", { style: {
|
|
2259
2347
|
padding: "4px 14px",
|
|
2260
2348
|
fontSize: 11,
|
|
2261
2349
|
color: t.textMuted,
|
|
@@ -2264,25 +2352,22 @@ function SequenceEditor({
|
|
|
2264
2352
|
display: "flex",
|
|
2265
2353
|
gap: 16
|
|
2266
2354
|
}, children: [
|
|
2267
|
-
/* @__PURE__ */
|
|
2355
|
+
/* @__PURE__ */ jsxs5("span", { children: [
|
|
2268
2356
|
actors.length,
|
|
2269
2357
|
" actors"
|
|
2270
2358
|
] }),
|
|
2271
|
-
/* @__PURE__ */
|
|
2359
|
+
/* @__PURE__ */ jsxs5("span", { children: [
|
|
2272
2360
|
messages.length,
|
|
2273
2361
|
" messages"
|
|
2274
2362
|
] }),
|
|
2275
|
-
/* @__PURE__ */
|
|
2363
|
+
/* @__PURE__ */ jsx5("span", { style: { marginLeft: "auto" }, children: "double-click actor to rename \xB7 drag a row to reorder" })
|
|
2276
2364
|
] })
|
|
2277
2365
|
] });
|
|
2278
2366
|
}
|
|
2279
|
-
function estimateW(text, pxPerChar = 7) {
|
|
2280
|
-
return text.length * pxPerChar;
|
|
2281
|
-
}
|
|
2282
2367
|
function primaryBtn() {
|
|
2283
2368
|
return {
|
|
2284
2369
|
padding: "6px 12px",
|
|
2285
|
-
background:
|
|
2370
|
+
background: INDIGO2,
|
|
2286
2371
|
color: "#fff",
|
|
2287
2372
|
border: "none",
|
|
2288
2373
|
borderRadius: 8,
|
|
@@ -2321,180 +2406,37 @@ function input(t) {
|
|
|
2321
2406
|
};
|
|
2322
2407
|
}
|
|
2323
2408
|
function Label({ t, children }) {
|
|
2324
|
-
return /* @__PURE__ */
|
|
2409
|
+
return /* @__PURE__ */ jsx5("div", { style: { fontSize: 10, fontWeight: 700, color: t.textMuted, textTransform: "uppercase", letterSpacing: 0.6, marginBottom: 4 }, children });
|
|
2325
2410
|
}
|
|
2326
2411
|
|
|
2327
|
-
// src/ui/
|
|
2328
|
-
import {
|
|
2329
|
-
import { jsx as
|
|
2330
|
-
|
|
2331
|
-
var H = 112;
|
|
2332
|
-
var PAD = 18;
|
|
2333
|
-
function Minimap({
|
|
2412
|
+
// src/ui/NodeNavigator.tsx
|
|
2413
|
+
import { useState as useState6 } from "react";
|
|
2414
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2415
|
+
function NodeNavigator({
|
|
2334
2416
|
model,
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
transform,
|
|
2338
|
-
measureNode,
|
|
2339
|
-
onCenterOn,
|
|
2417
|
+
selected,
|
|
2418
|
+
variant,
|
|
2340
2419
|
isDark,
|
|
2341
|
-
|
|
2420
|
+
t,
|
|
2421
|
+
acc,
|
|
2422
|
+
open,
|
|
2423
|
+
onToggle,
|
|
2424
|
+
onSelect
|
|
2342
2425
|
}) {
|
|
2343
|
-
const
|
|
2344
|
-
const
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
maxX = Math.max(maxX, b.x + b.w);
|
|
2358
|
-
maxY = Math.max(maxY, b.y + b.h);
|
|
2359
|
-
}
|
|
2360
|
-
minX = Math.min(minX, vx);
|
|
2361
|
-
minY = Math.min(minY, vy);
|
|
2362
|
-
maxX = Math.max(maxX, vx + vw);
|
|
2363
|
-
maxY = Math.max(maxY, vy + vh);
|
|
2364
|
-
const contentW = Math.max(1, maxX - minX);
|
|
2365
|
-
const contentH = Math.max(1, maxY - minY);
|
|
2366
|
-
const scale = Math.min((W - PAD * 2) / contentW, (H - PAD * 2) / contentH);
|
|
2367
|
-
const offsetX = (W - contentW * scale) / 2 - minX * scale;
|
|
2368
|
-
const offsetY = (H - contentH * scale) / 2 - minY * scale;
|
|
2369
|
-
const project = (x, y) => ({
|
|
2370
|
-
x: offsetX + x * scale,
|
|
2371
|
-
y: offsetY + y * scale
|
|
2372
|
-
});
|
|
2373
|
-
const unproject = (mx, my) => ({
|
|
2374
|
-
x: (mx - offsetX) / scale,
|
|
2375
|
-
y: (my - offsetY) / scale
|
|
2376
|
-
});
|
|
2377
|
-
const panTo = useCallback5((e) => {
|
|
2378
|
-
const rect = e.currentTarget.getBoundingClientRect();
|
|
2379
|
-
const mx = e.clientX - rect.left;
|
|
2380
|
-
const my = e.clientY - rect.top;
|
|
2381
|
-
const { x, y } = unproject(mx, my);
|
|
2382
|
-
onCenterOn(x, y);
|
|
2383
|
-
}, [onCenterOn, scale, offsetX, offsetY]);
|
|
2384
|
-
const onMouseDown = (e) => {
|
|
2385
|
-
e.stopPropagation();
|
|
2386
|
-
dragRef.current = { active: true };
|
|
2387
|
-
panTo(e);
|
|
2388
|
-
};
|
|
2389
|
-
const onMouseMove = (e) => {
|
|
2390
|
-
if (!dragRef.current?.active) return;
|
|
2391
|
-
panTo(e);
|
|
2392
|
-
};
|
|
2393
|
-
const onMouseUp = () => {
|
|
2394
|
-
dragRef.current = null;
|
|
2395
|
-
};
|
|
2396
|
-
const bg = isDark ? "rgba(15,23,42,0.92)" : "rgba(255,255,255,0.94)";
|
|
2397
|
-
const border = isDark ? "#334155" : "#e2e8f0";
|
|
2398
|
-
const nodeFill = isDark ? "#475569" : "#cbd5e1";
|
|
2399
|
-
const viewStroke = accentColor;
|
|
2400
|
-
const viewFill = `${accentColor}22`;
|
|
2401
|
-
const vp1 = project(vx, vy);
|
|
2402
|
-
const vp2 = project(vx + vw, vy + vh);
|
|
2403
|
-
const vpRect = {
|
|
2404
|
-
x: Math.max(0, Math.min(W, vp1.x)),
|
|
2405
|
-
y: Math.max(0, Math.min(H, vp1.y)),
|
|
2406
|
-
w: Math.max(2, Math.min(W, vp2.x) - Math.max(0, vp1.x)),
|
|
2407
|
-
h: Math.max(2, Math.min(H, vp2.y) - Math.max(0, vp1.y))
|
|
2408
|
-
};
|
|
2409
|
-
return /* @__PURE__ */ jsx5(
|
|
2410
|
-
"div",
|
|
2411
|
-
{
|
|
2412
|
-
style: {
|
|
2413
|
-
position: "absolute",
|
|
2414
|
-
bottom: 14,
|
|
2415
|
-
right: 14,
|
|
2416
|
-
background: bg,
|
|
2417
|
-
border: `1px solid ${border}`,
|
|
2418
|
-
borderRadius: 10,
|
|
2419
|
-
padding: 6,
|
|
2420
|
-
boxShadow: isDark ? "0 8px 20px rgba(0,0,0,0.45)" : "0 6px 18px rgba(15,23,42,0.08)",
|
|
2421
|
-
backdropFilter: "blur(6px)"
|
|
2422
|
-
},
|
|
2423
|
-
children: /* @__PURE__ */ jsxs5(
|
|
2424
|
-
"svg",
|
|
2425
|
-
{
|
|
2426
|
-
width: W,
|
|
2427
|
-
height: H,
|
|
2428
|
-
style: { display: "block", cursor: "grab", borderRadius: 6 },
|
|
2429
|
-
onMouseDown,
|
|
2430
|
-
onMouseMove,
|
|
2431
|
-
onMouseUp,
|
|
2432
|
-
onMouseLeave: onMouseUp,
|
|
2433
|
-
children: [
|
|
2434
|
-
/* @__PURE__ */ jsx5("rect", { width: W, height: H, rx: 6, fill: isDark ? "#0f172a" : "#fafbfc" }),
|
|
2435
|
-
boxes.map((b) => {
|
|
2436
|
-
const p = project(b.x, b.y);
|
|
2437
|
-
return /* @__PURE__ */ jsx5(
|
|
2438
|
-
"rect",
|
|
2439
|
-
{
|
|
2440
|
-
x: p.x,
|
|
2441
|
-
y: p.y,
|
|
2442
|
-
width: Math.max(2, b.w * scale),
|
|
2443
|
-
height: Math.max(2, b.h * scale),
|
|
2444
|
-
rx: 2,
|
|
2445
|
-
fill: nodeFill
|
|
2446
|
-
},
|
|
2447
|
-
b.id
|
|
2448
|
-
);
|
|
2449
|
-
}),
|
|
2450
|
-
/* @__PURE__ */ jsx5(
|
|
2451
|
-
"rect",
|
|
2452
|
-
{
|
|
2453
|
-
x: vpRect.x,
|
|
2454
|
-
y: vpRect.y,
|
|
2455
|
-
width: vpRect.w,
|
|
2456
|
-
height: vpRect.h,
|
|
2457
|
-
rx: 3,
|
|
2458
|
-
fill: viewFill,
|
|
2459
|
-
stroke: viewStroke,
|
|
2460
|
-
strokeWidth: 1.25
|
|
2461
|
-
}
|
|
2462
|
-
)
|
|
2463
|
-
]
|
|
2464
|
-
}
|
|
2465
|
-
)
|
|
2466
|
-
}
|
|
2467
|
-
);
|
|
2468
|
-
}
|
|
2469
|
-
|
|
2470
|
-
// src/ui/NodeNavigator.tsx
|
|
2471
|
-
import { useState as useState6 } from "react";
|
|
2472
|
-
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2473
|
-
function NodeNavigator({
|
|
2474
|
-
model,
|
|
2475
|
-
selected,
|
|
2476
|
-
variant,
|
|
2477
|
-
isDark,
|
|
2478
|
-
t,
|
|
2479
|
-
acc,
|
|
2480
|
-
open,
|
|
2481
|
-
onToggle,
|
|
2482
|
-
onSelect
|
|
2483
|
-
}) {
|
|
2484
|
-
const [search, setSearch] = useState6("");
|
|
2485
|
-
const shapeIcon = (node) => {
|
|
2486
|
-
if (variant === "question") return "?";
|
|
2487
|
-
if (variant === "journey") return "\u2197";
|
|
2488
|
-
switch (node.shape) {
|
|
2489
|
-
case "diamond":
|
|
2490
|
-
return "\u25C7";
|
|
2491
|
-
case "circle":
|
|
2492
|
-
return "\u25CB";
|
|
2493
|
-
case "parallelogram":
|
|
2494
|
-
return "\u25B1";
|
|
2495
|
-
default:
|
|
2496
|
-
return "\u25AD";
|
|
2497
|
-
}
|
|
2426
|
+
const [search, setSearch] = useState6("");
|
|
2427
|
+
const shapeIcon = (node) => {
|
|
2428
|
+
if (variant === "question") return "?";
|
|
2429
|
+
if (variant === "journey") return "\u2197";
|
|
2430
|
+
switch (node.shape) {
|
|
2431
|
+
case "diamond":
|
|
2432
|
+
return "\u25C7";
|
|
2433
|
+
case "circle":
|
|
2434
|
+
return "\u25CB";
|
|
2435
|
+
case "parallelogram":
|
|
2436
|
+
return "\u25B1";
|
|
2437
|
+
default:
|
|
2438
|
+
return "\u25AD";
|
|
2439
|
+
}
|
|
2498
2440
|
};
|
|
2499
2441
|
const filtered = model.nodes.filter(
|
|
2500
2442
|
(n) => n.label.toLowerCase().includes(search.toLowerCase())
|
|
@@ -2651,139 +2593,8 @@ function NodeNavigator({
|
|
|
2651
2593
|
] });
|
|
2652
2594
|
}
|
|
2653
2595
|
|
|
2654
|
-
// src/ui/ContextMenu.tsx
|
|
2655
|
-
import { useEffect as useEffect5, useRef as useRef5, useState as useState7 } from "react";
|
|
2656
|
-
import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2657
|
-
function ContextMenu({
|
|
2658
|
-
x,
|
|
2659
|
-
y,
|
|
2660
|
-
nodeId,
|
|
2661
|
-
edgeId,
|
|
2662
|
-
isDark,
|
|
2663
|
-
t,
|
|
2664
|
-
acc,
|
|
2665
|
-
canUndo,
|
|
2666
|
-
canRedo,
|
|
2667
|
-
onUndo,
|
|
2668
|
-
onRedo,
|
|
2669
|
-
onReCenter,
|
|
2670
|
-
onAddNode,
|
|
2671
|
-
onDuplicate,
|
|
2672
|
-
onRename,
|
|
2673
|
-
onDelete,
|
|
2674
|
-
onDisconnect,
|
|
2675
|
-
onEdgeRename,
|
|
2676
|
-
onEdgeStyle,
|
|
2677
|
-
onEdgeArrowhead,
|
|
2678
|
-
onEdgeDelete,
|
|
2679
|
-
onEdgeResetRouting,
|
|
2680
|
-
currentEdgeStyle,
|
|
2681
|
-
currentEdgeArrow,
|
|
2682
|
-
edgeHasWaypoint,
|
|
2683
|
-
containerRef
|
|
2684
|
-
}) {
|
|
2685
|
-
const menuRef = useRef5(null);
|
|
2686
|
-
const [pos, setPos] = useState7({ x, y });
|
|
2687
|
-
useEffect5(() => {
|
|
2688
|
-
if (!menuRef.current || !containerRef.current) return;
|
|
2689
|
-
const m = menuRef.current.getBoundingClientRect();
|
|
2690
|
-
const c = containerRef.current.getBoundingClientRect();
|
|
2691
|
-
let nx = x, ny = y;
|
|
2692
|
-
if (nx + m.width > c.right - 8) nx = x - m.width;
|
|
2693
|
-
if (ny + m.height > c.bottom - 8) ny = y - m.height;
|
|
2694
|
-
setPos({ x: nx, y: ny });
|
|
2695
|
-
}, [x, y, containerRef]);
|
|
2696
|
-
const bg = isDark ? "#1e293b" : "#ffffff";
|
|
2697
|
-
const border = isDark ? "#334155" : "#e2e8f0";
|
|
2698
|
-
const hoverBg = isDark ? "#334155" : "#f1f5f9";
|
|
2699
|
-
const dividerColor = isDark ? "#334155" : "#f1f5f9";
|
|
2700
|
-
const text = t.textPrimary;
|
|
2701
|
-
const muted = t.textMuted;
|
|
2702
|
-
const item = (label, onClick, color, disabled) => /* @__PURE__ */ jsx7(
|
|
2703
|
-
"button",
|
|
2704
|
-
{
|
|
2705
|
-
onClick: disabled ? void 0 : onClick,
|
|
2706
|
-
style: {
|
|
2707
|
-
display: "flex",
|
|
2708
|
-
alignItems: "center",
|
|
2709
|
-
gap: 10,
|
|
2710
|
-
width: "100%",
|
|
2711
|
-
padding: "7px 14px",
|
|
2712
|
-
background: "none",
|
|
2713
|
-
border: "none",
|
|
2714
|
-
textAlign: "left",
|
|
2715
|
-
cursor: disabled ? "default" : "pointer",
|
|
2716
|
-
fontSize: 12,
|
|
2717
|
-
fontFamily: "ui-sans-serif,system-ui,sans-serif",
|
|
2718
|
-
color: disabled ? muted : color ?? text,
|
|
2719
|
-
opacity: disabled ? 0.4 : 1,
|
|
2720
|
-
borderRadius: 6
|
|
2721
|
-
},
|
|
2722
|
-
onMouseEnter: (e) => {
|
|
2723
|
-
if (!disabled) e.currentTarget.style.background = hoverBg;
|
|
2724
|
-
},
|
|
2725
|
-
onMouseLeave: (e) => {
|
|
2726
|
-
e.currentTarget.style.background = "none";
|
|
2727
|
-
},
|
|
2728
|
-
children: label
|
|
2729
|
-
},
|
|
2730
|
-
label
|
|
2731
|
-
);
|
|
2732
|
-
const divider2 = /* @__PURE__ */ jsx7("div", { style: { height: 1, background: dividerColor, margin: "4px 0" } });
|
|
2733
|
-
return /* @__PURE__ */ jsx7(
|
|
2734
|
-
"div",
|
|
2735
|
-
{
|
|
2736
|
-
ref: menuRef,
|
|
2737
|
-
onMouseDown: (e) => e.stopPropagation(),
|
|
2738
|
-
style: {
|
|
2739
|
-
position: "fixed",
|
|
2740
|
-
left: pos.x,
|
|
2741
|
-
top: pos.y,
|
|
2742
|
-
zIndex: 9999,
|
|
2743
|
-
background: bg,
|
|
2744
|
-
border: `1px solid ${border}`,
|
|
2745
|
-
borderRadius: 10,
|
|
2746
|
-
padding: "5px 0",
|
|
2747
|
-
minWidth: 180,
|
|
2748
|
-
boxShadow: isDark ? "0 8px 32px rgba(0,0,0,0.5)" : "0 8px 32px rgba(0,0,0,0.12)",
|
|
2749
|
-
fontFamily: "ui-sans-serif,system-ui,sans-serif"
|
|
2750
|
-
},
|
|
2751
|
-
children: edgeId ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
2752
|
-
/* @__PURE__ */ jsx7("div", { style: { padding: "4px 14px 6px", fontSize: 10, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Edge" }),
|
|
2753
|
-
item("Rename label (dbl-click)", () => onEdgeRename?.()),
|
|
2754
|
-
divider2,
|
|
2755
|
-
/* @__PURE__ */ jsx7("div", { style: { padding: "4px 14px 2px", fontSize: 9, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Style" }),
|
|
2756
|
-
item(`Solid${currentEdgeStyle === "solid" || !currentEdgeStyle ? " \u2713" : ""}`, () => onEdgeStyle?.("solid")),
|
|
2757
|
-
item(`Dashed${currentEdgeStyle === "dashed" ? " \u2713" : ""}`, () => onEdgeStyle?.("dashed")),
|
|
2758
|
-
item(`Dotted${currentEdgeStyle === "dotted" ? " \u2713" : ""}`, () => onEdgeStyle?.("dotted")),
|
|
2759
|
-
divider2,
|
|
2760
|
-
/* @__PURE__ */ jsx7("div", { style: { padding: "4px 14px 2px", fontSize: 9, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Arrowhead" }),
|
|
2761
|
-
item(`Arrow${currentEdgeArrow !== "none" ? " \u2713" : ""}`, () => onEdgeArrowhead?.("arrow")),
|
|
2762
|
-
item(`None${currentEdgeArrow === "none" ? " \u2713" : ""}`, () => onEdgeArrowhead?.("none")),
|
|
2763
|
-
divider2,
|
|
2764
|
-
item("Reset routing", () => onEdgeResetRouting?.(), void 0, !edgeHasWaypoint),
|
|
2765
|
-
item("Delete edge", () => onEdgeDelete?.(), "#ef4444")
|
|
2766
|
-
] }) : nodeId ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
2767
|
-
/* @__PURE__ */ jsx7("div", { style: { padding: "4px 14px 6px", fontSize: 10, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Node" }),
|
|
2768
|
-
item("Rename (dbl-click)", onRename),
|
|
2769
|
-
item("Duplicate", onDuplicate),
|
|
2770
|
-
item("Disconnect all edges", onDisconnect),
|
|
2771
|
-
divider2,
|
|
2772
|
-
item("Delete node", onDelete, "#ef4444")
|
|
2773
|
-
] }) : /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
2774
|
-
/* @__PURE__ */ jsx7("div", { style: { padding: "4px 14px 6px", fontSize: 10, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Canvas" }),
|
|
2775
|
-
item("Add node here", onAddNode, acc.color),
|
|
2776
|
-
item("Re-center (Ctrl+0)", onReCenter),
|
|
2777
|
-
divider2,
|
|
2778
|
-
item("Undo (Ctrl+Z)", onUndo, void 0, !canUndo),
|
|
2779
|
-
item("Redo (Ctrl+Y)", onRedo, void 0, !canRedo)
|
|
2780
|
-
] })
|
|
2781
|
-
}
|
|
2782
|
-
);
|
|
2783
|
-
}
|
|
2784
|
-
|
|
2785
2596
|
// src/ui/render.tsx
|
|
2786
|
-
import { useState as
|
|
2597
|
+
import { useState as useState7 } from "react";
|
|
2787
2598
|
|
|
2788
2599
|
// src/ui/layout.ts
|
|
2789
2600
|
var NODE_H2 = 48;
|
|
@@ -2849,7 +2660,7 @@ function bezierPathVia(x1, y1, wx, wy, x2, y2) {
|
|
|
2849
2660
|
}
|
|
2850
2661
|
|
|
2851
2662
|
// src/ui/render.tsx
|
|
2852
|
-
import { Fragment as
|
|
2663
|
+
import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2853
2664
|
var STYLE_LABEL = { pointerEvents: "none", userSelect: "none" };
|
|
2854
2665
|
var STYLE_BLUR = { filter: "blur(4px)" };
|
|
2855
2666
|
var STYLE_EDGE_HIT = { cursor: "pointer" };
|
|
@@ -2863,11 +2674,11 @@ function NodeShape({ node, selected, variant, stepNumber, t, isDark, w }) {
|
|
|
2863
2674
|
const stroke = selected ? acc.color : t.nodeStroke;
|
|
2864
2675
|
const fill = selected ? t.nodeSelectedFill : t.nodeFill;
|
|
2865
2676
|
const sw = selected ? 1.75 : 1.25;
|
|
2866
|
-
const glow = selected && /* @__PURE__ */
|
|
2867
|
-
/* @__PURE__ */
|
|
2868
|
-
/* @__PURE__ */
|
|
2869
|
-
] }) : node.shape === "diamond" ? /* @__PURE__ */
|
|
2870
|
-
/* @__PURE__ */
|
|
2677
|
+
const glow = selected && /* @__PURE__ */ jsx7(Fragment2, { children: node.shape === "circle" ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
2678
|
+
/* @__PURE__ */ jsx7("circle", { cx, cy, r: NODE_H2 / 2 + 3, fill: "none", stroke: acc.color, strokeWidth: 6, opacity: 0.18, style: STYLE_BLUR }),
|
|
2679
|
+
/* @__PURE__ */ jsx7("circle", { cx, cy, r: NODE_H2 / 2 + 1.5, fill: "none", stroke: acc.color, strokeWidth: 1, opacity: 0.55 })
|
|
2680
|
+
] }) : node.shape === "diamond" ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
2681
|
+
/* @__PURE__ */ jsx7(
|
|
2871
2682
|
"polygon",
|
|
2872
2683
|
{
|
|
2873
2684
|
points: `${cx},${-5} ${w + 5},${cy} ${cx},${NODE_H2 + 5} ${-5},${cy}`,
|
|
@@ -2878,7 +2689,7 @@ function NodeShape({ node, selected, variant, stepNumber, t, isDark, w }) {
|
|
|
2878
2689
|
style: STYLE_BLUR
|
|
2879
2690
|
}
|
|
2880
2691
|
),
|
|
2881
|
-
/* @__PURE__ */
|
|
2692
|
+
/* @__PURE__ */ jsx7(
|
|
2882
2693
|
"polygon",
|
|
2883
2694
|
{
|
|
2884
2695
|
points: `${cx},${-2} ${w + 2},${cy} ${cx},${NODE_H2 + 2} ${-2},${cy}`,
|
|
@@ -2888,8 +2699,8 @@ function NodeShape({ node, selected, variant, stepNumber, t, isDark, w }) {
|
|
|
2888
2699
|
opacity: 0.55
|
|
2889
2700
|
}
|
|
2890
2701
|
)
|
|
2891
|
-
] }) : /* @__PURE__ */
|
|
2892
|
-
/* @__PURE__ */
|
|
2702
|
+
] }) : /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
2703
|
+
/* @__PURE__ */ jsx7(
|
|
2893
2704
|
"rect",
|
|
2894
2705
|
{
|
|
2895
2706
|
x: -4,
|
|
@@ -2904,7 +2715,7 @@ function NodeShape({ node, selected, variant, stepNumber, t, isDark, w }) {
|
|
|
2904
2715
|
style: STYLE_BLUR
|
|
2905
2716
|
}
|
|
2906
2717
|
),
|
|
2907
|
-
/* @__PURE__ */
|
|
2718
|
+
/* @__PURE__ */ jsx7(
|
|
2908
2719
|
"rect",
|
|
2909
2720
|
{
|
|
2910
2721
|
x: -1.5,
|
|
@@ -2920,35 +2731,35 @@ function NodeShape({ node, selected, variant, stepNumber, t, isDark, w }) {
|
|
|
2920
2731
|
)
|
|
2921
2732
|
] }) });
|
|
2922
2733
|
const badgeColor = isDark ? ACCENT.emeraldDark : ACCENT.emerald;
|
|
2923
|
-
const badge = variant === "journey" && stepNumber !== void 0 && /* @__PURE__ */
|
|
2924
|
-
/* @__PURE__ */
|
|
2925
|
-
/* @__PURE__ */
|
|
2734
|
+
const badge = variant === "journey" && stepNumber !== void 0 && /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
2735
|
+
/* @__PURE__ */ jsx7("circle", { cx: 14, cy: 14, r: 10, fill: badgeColor }),
|
|
2736
|
+
/* @__PURE__ */ jsx7("text", { x: 14, y: 18, textAnchor: "middle", fontSize: 9, fill: "white", fontWeight: "700", style: STYLE_LABEL, children: stepNumber })
|
|
2926
2737
|
] });
|
|
2927
2738
|
switch (node.shape) {
|
|
2928
2739
|
case "diamond": {
|
|
2929
2740
|
const pts = `${cx},0 ${w},${cy} ${cx},${NODE_H2} 0,${cy}`;
|
|
2930
|
-
return /* @__PURE__ */
|
|
2741
|
+
return /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
2931
2742
|
glow,
|
|
2932
|
-
/* @__PURE__ */
|
|
2743
|
+
/* @__PURE__ */ jsx7("polygon", { points: pts, fill, stroke, strokeWidth: sw, filter: "url(#nodeShadow)" }),
|
|
2933
2744
|
badge
|
|
2934
2745
|
] });
|
|
2935
2746
|
}
|
|
2936
2747
|
case "circle":
|
|
2937
|
-
return /* @__PURE__ */
|
|
2748
|
+
return /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
2938
2749
|
glow,
|
|
2939
|
-
/* @__PURE__ */
|
|
2750
|
+
/* @__PURE__ */ jsx7("circle", { cx, cy, r: NODE_H2 / 2 - 1, fill, stroke, strokeWidth: sw, filter: "url(#nodeShadow)" }),
|
|
2940
2751
|
badge
|
|
2941
2752
|
] });
|
|
2942
2753
|
case "parallelogram":
|
|
2943
|
-
return /* @__PURE__ */
|
|
2754
|
+
return /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
2944
2755
|
glow,
|
|
2945
|
-
/* @__PURE__ */
|
|
2756
|
+
/* @__PURE__ */ jsx7("polygon", { points: `14,0 ${w},0 ${w - 14},${NODE_H2} 0,${NODE_H2}`, fill, stroke, strokeWidth: sw, filter: "url(#nodeShadow)" }),
|
|
2946
2757
|
badge
|
|
2947
2758
|
] });
|
|
2948
2759
|
default:
|
|
2949
|
-
return /* @__PURE__ */
|
|
2760
|
+
return /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
2950
2761
|
glow,
|
|
2951
|
-
/* @__PURE__ */
|
|
2762
|
+
/* @__PURE__ */ jsx7("rect", { width: w, height: NODE_H2, rx: 14, fill, stroke, strokeWidth: sw, filter: "url(#nodeShadow)" }),
|
|
2952
2763
|
badge
|
|
2953
2764
|
] });
|
|
2954
2765
|
}
|
|
@@ -2969,8 +2780,8 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
2969
2780
|
const textSub = isDark ? "#64748b" : "#94a3b8";
|
|
2970
2781
|
const textAns = isDark ? "#cbd5e1" : "#374151";
|
|
2971
2782
|
const portRowY = Q_BASE_H2 + Q_ANS_ROW_H2 - 8;
|
|
2972
|
-
const glow = selected && /* @__PURE__ */
|
|
2973
|
-
/* @__PURE__ */
|
|
2783
|
+
const glow = selected && /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
2784
|
+
/* @__PURE__ */ jsx7(
|
|
2974
2785
|
"rect",
|
|
2975
2786
|
{
|
|
2976
2787
|
x: -4,
|
|
@@ -2985,7 +2796,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
2985
2796
|
style: STYLE_BLUR
|
|
2986
2797
|
}
|
|
2987
2798
|
),
|
|
2988
|
-
/* @__PURE__ */
|
|
2799
|
+
/* @__PURE__ */ jsx7(
|
|
2989
2800
|
"rect",
|
|
2990
2801
|
{
|
|
2991
2802
|
x: -1.5,
|
|
@@ -3000,29 +2811,29 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
3000
2811
|
}
|
|
3001
2812
|
)
|
|
3002
2813
|
] });
|
|
3003
|
-
return /* @__PURE__ */
|
|
2814
|
+
return /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
3004
2815
|
glow,
|
|
3005
|
-
/* @__PURE__ */
|
|
3006
|
-
/* @__PURE__ */
|
|
3007
|
-
/* @__PURE__ */
|
|
3008
|
-
/* @__PURE__ */
|
|
3009
|
-
/* @__PURE__ */
|
|
3010
|
-
/* @__PURE__ */
|
|
3011
|
-
/* @__PURE__ */
|
|
2816
|
+
/* @__PURE__ */ jsx7("rect", { width: qW, height: totalH, rx: 14, fill: nodeBg, stroke: nodeBorder, strokeWidth: selected ? 2 : 1.5, filter: "url(#nodeShadow)" }),
|
|
2817
|
+
/* @__PURE__ */ jsx7("clipPath", { id: `qhdr-${node.id}`, children: /* @__PURE__ */ jsx7("rect", { width: qW, height: Q_BASE_H2, rx: 14 }) }),
|
|
2818
|
+
/* @__PURE__ */ jsx7("rect", { width: qW, height: Q_BASE_H2, fill: amberSoft, clipPath: `url(#qhdr-${node.id})` }),
|
|
2819
|
+
/* @__PURE__ */ jsx7("rect", { x: 0, y: 0, width: 4, height: Q_BASE_H2, rx: 2, fill: amber }),
|
|
2820
|
+
/* @__PURE__ */ jsx7("rect", { x: 12, y: 14, width: 28, height: 28, rx: 8, fill: amber }),
|
|
2821
|
+
/* @__PURE__ */ jsx7("text", { x: 26, y: 33, textAnchor: "middle", fontSize: 15, fontWeight: "900", fill: "white", style: STYLE_LABEL, children: "?" }),
|
|
2822
|
+
/* @__PURE__ */ jsxs7(
|
|
3012
2823
|
"text",
|
|
3013
2824
|
{
|
|
3014
2825
|
style: STYLE_LABEL,
|
|
3015
2826
|
fontFamily: "ui-sans-serif,system-ui,sans-serif",
|
|
3016
2827
|
children: [
|
|
3017
|
-
/* @__PURE__ */
|
|
3018
|
-
/* @__PURE__ */
|
|
2828
|
+
/* @__PURE__ */ jsx7("tspan", { x: 50, y: 27, fontSize: 9, fontWeight: 700, fill: textSub, letterSpacing: 0.6, textAnchor: "start", children: "QUESTION" }),
|
|
2829
|
+
/* @__PURE__ */ jsx7("tspan", { x: 50, dy: 15, fontSize: 13, fontWeight: 700, fill: selected ? amber : textMain, textAnchor: "start", children: node.label })
|
|
3019
2830
|
]
|
|
3020
2831
|
}
|
|
3021
2832
|
),
|
|
3022
|
-
/* @__PURE__ */
|
|
3023
|
-
answers.length === 0 && /* @__PURE__ */
|
|
3024
|
-
/* @__PURE__ */
|
|
3025
|
-
/* @__PURE__ */
|
|
2833
|
+
/* @__PURE__ */ jsx7("line", { x1: 0, y1: Q_BASE_H2, x2: qW, y2: Q_BASE_H2, stroke: amberLine, strokeWidth: 1 }),
|
|
2834
|
+
answers.length === 0 && /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
2835
|
+
/* @__PURE__ */ jsx7("text", { x: qW / 2, y: Q_BASE_H2 + 22, textAnchor: "middle", fontSize: 10, fill: amber, opacity: 0.4, fontWeight: 600, style: STYLE_LABEL, children: "No answers yet" }),
|
|
2836
|
+
/* @__PURE__ */ jsx7("text", { x: qW / 2, y: Q_BASE_H2 + 36, textAnchor: "middle", fontSize: 9, fill: textSub, opacity: 0.7, style: STYLE_LABEL, children: "Open panel \u2192 Add Answer" })
|
|
3026
2837
|
] }),
|
|
3027
2838
|
answers.map((ans, i) => {
|
|
3028
2839
|
const prevW = answers.slice(0, i).reduce((s2, a) => s2 + answerCardW2(a) + Q_CARD_PAD2, 0);
|
|
@@ -3035,8 +2846,8 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
3035
2846
|
const letter = i < 26 ? ANSWER_LETTERS[i] : `${i + 1}`;
|
|
3036
2847
|
const maxChars = Math.max(2, Math.floor((cW - 20) / 7.5));
|
|
3037
2848
|
const displayAns = ans.length > maxChars ? ans.slice(0, maxChars - 1) + "\u2026" : ans;
|
|
3038
|
-
return /* @__PURE__ */
|
|
3039
|
-
/* @__PURE__ */
|
|
2849
|
+
return /* @__PURE__ */ jsxs7("g", { children: [
|
|
2850
|
+
/* @__PURE__ */ jsx7(
|
|
3040
2851
|
"rect",
|
|
3041
2852
|
{
|
|
3042
2853
|
x: cardX,
|
|
@@ -3049,7 +2860,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
3049
2860
|
strokeWidth: connected ? 1.5 : 1
|
|
3050
2861
|
}
|
|
3051
2862
|
),
|
|
3052
|
-
/* @__PURE__ */
|
|
2863
|
+
/* @__PURE__ */ jsx7(
|
|
3053
2864
|
"rect",
|
|
3054
2865
|
{
|
|
3055
2866
|
x: cx - 11,
|
|
@@ -3060,7 +2871,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
3060
2871
|
fill: connected ? amber : isDark ? "#1e293b" : "#fef3c7"
|
|
3061
2872
|
}
|
|
3062
2873
|
),
|
|
3063
|
-
/* @__PURE__ */
|
|
2874
|
+
/* @__PURE__ */ jsx7(
|
|
3064
2875
|
"text",
|
|
3065
2876
|
{
|
|
3066
2877
|
x: cx,
|
|
@@ -3073,7 +2884,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
3073
2884
|
children: letter
|
|
3074
2885
|
}
|
|
3075
2886
|
),
|
|
3076
|
-
/* @__PURE__ */
|
|
2887
|
+
/* @__PURE__ */ jsx7(
|
|
3077
2888
|
"text",
|
|
3078
2889
|
{
|
|
3079
2890
|
x: cx,
|
|
@@ -3087,7 +2898,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
3087
2898
|
children: displayAns
|
|
3088
2899
|
}
|
|
3089
2900
|
),
|
|
3090
|
-
/* @__PURE__ */
|
|
2901
|
+
/* @__PURE__ */ jsx7(
|
|
3091
2902
|
"circle",
|
|
3092
2903
|
{
|
|
3093
2904
|
cx,
|
|
@@ -3100,7 +2911,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
3100
2911
|
onMouseDown: (e) => onAnswerPortDown(e, node.id, ans, cx, portRowY)
|
|
3101
2912
|
}
|
|
3102
2913
|
),
|
|
3103
|
-
/* @__PURE__ */
|
|
2914
|
+
/* @__PURE__ */ jsx7(
|
|
3104
2915
|
"path",
|
|
3105
2916
|
{
|
|
3106
2917
|
d: `M ${cx - 3} ${portRowY - 2} L ${cx} ${portRowY + 2} L ${cx + 3} ${portRowY - 2}`,
|
|
@@ -3117,7 +2928,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
3117
2928
|
] });
|
|
3118
2929
|
}
|
|
3119
2930
|
function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, onEditChange, onEditCommit, onEditCancel, onDoubleClick, onContextMenu, onWaypointDown }) {
|
|
3120
|
-
const [hovered, setHovered] =
|
|
2931
|
+
const [hovered, setHovered] = useState7(false);
|
|
3121
2932
|
const from = nodes.find((n) => n.id === edge.from);
|
|
3122
2933
|
const to = nodes.find((n) => n.id === edge.to);
|
|
3123
2934
|
if (!from || !to) return null;
|
|
@@ -3156,7 +2967,7 @@ function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, on
|
|
|
3156
2967
|
const labelW = edge.label ? Math.max(60, Math.ceil(estimateTextW2(edge.label, 7) + 18)) : 60;
|
|
3157
2968
|
const showHandle = !!onWaypointDown && (hovered || !!wp);
|
|
3158
2969
|
const flowClass = dash ? void 0 : isAmber ? "edge-flow-amber" : "edge-flow";
|
|
3159
|
-
return /* @__PURE__ */
|
|
2970
|
+
return /* @__PURE__ */ jsxs7(
|
|
3160
2971
|
"g",
|
|
3161
2972
|
{
|
|
3162
2973
|
onDoubleClick: (e) => {
|
|
@@ -3169,8 +2980,8 @@ function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, on
|
|
|
3169
2980
|
onMouseEnter: () => setHovered(true),
|
|
3170
2981
|
onMouseLeave: () => setHovered(false),
|
|
3171
2982
|
children: [
|
|
3172
|
-
/* @__PURE__ */
|
|
3173
|
-
/* @__PURE__ */
|
|
2983
|
+
/* @__PURE__ */ jsx7("path", { d, fill: "none", stroke: "transparent", strokeWidth: 14, style: STYLE_EDGE_HIT }),
|
|
2984
|
+
/* @__PURE__ */ jsx7(
|
|
3174
2985
|
"path",
|
|
3175
2986
|
{
|
|
3176
2987
|
d,
|
|
@@ -3185,7 +2996,7 @@ function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, on
|
|
|
3185
2996
|
style: STYLE_NO_EVENTS
|
|
3186
2997
|
}
|
|
3187
2998
|
),
|
|
3188
|
-
showHandle && /* @__PURE__ */
|
|
2999
|
+
showHandle && /* @__PURE__ */ jsx7(
|
|
3189
3000
|
"circle",
|
|
3190
3001
|
{
|
|
3191
3002
|
cx: hx,
|
|
@@ -3201,7 +3012,7 @@ function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, on
|
|
|
3201
3012
|
}
|
|
3202
3013
|
}
|
|
3203
3014
|
),
|
|
3204
|
-
editing && !isAmber ? /* @__PURE__ */
|
|
3015
|
+
editing && !isAmber ? /* @__PURE__ */ jsx7("foreignObject", { x: mx - labelW / 2, y: my - 12, width: labelW, height: 22, children: /* @__PURE__ */ jsx7(
|
|
3205
3016
|
"input",
|
|
3206
3017
|
{
|
|
3207
3018
|
autoFocus: true,
|
|
@@ -3213,61 +3024,645 @@ function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, on
|
|
|
3213
3024
|
e.preventDefault();
|
|
3214
3025
|
onEditCommit?.();
|
|
3215
3026
|
}
|
|
3216
|
-
if (e.key === "Escape") {
|
|
3217
|
-
e.preventDefault();
|
|
3218
|
-
onEditCancel?.();
|
|
3027
|
+
if (e.key === "Escape") {
|
|
3028
|
+
e.preventDefault();
|
|
3029
|
+
onEditCancel?.();
|
|
3030
|
+
}
|
|
3031
|
+
},
|
|
3032
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
3033
|
+
style: {
|
|
3034
|
+
width: "100%",
|
|
3035
|
+
height: "100%",
|
|
3036
|
+
border: "none",
|
|
3037
|
+
borderRadius: 6,
|
|
3038
|
+
outline: `2px solid ${acc.color}`,
|
|
3039
|
+
textAlign: "center",
|
|
3040
|
+
fontSize: 10,
|
|
3041
|
+
fontWeight: 500,
|
|
3042
|
+
background: t.inputBg,
|
|
3043
|
+
color: t.inputText,
|
|
3044
|
+
boxSizing: "border-box",
|
|
3045
|
+
padding: "0 6px",
|
|
3046
|
+
fontFamily: "inherit"
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
) }) : edge.label && !isAmber ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
3050
|
+
/* @__PURE__ */ jsx7(
|
|
3051
|
+
"rect",
|
|
3052
|
+
{
|
|
3053
|
+
x: mx - labelW / 2,
|
|
3054
|
+
y: my - 11,
|
|
3055
|
+
width: labelW,
|
|
3056
|
+
height: 19,
|
|
3057
|
+
rx: 5,
|
|
3058
|
+
fill: t.panelBg,
|
|
3059
|
+
stroke: t.cardBorder,
|
|
3060
|
+
strokeWidth: 1,
|
|
3061
|
+
style: STYLE_EDGE_LABEL_HIT
|
|
3062
|
+
}
|
|
3063
|
+
),
|
|
3064
|
+
/* @__PURE__ */ jsx7(
|
|
3065
|
+
"text",
|
|
3066
|
+
{
|
|
3067
|
+
x: mx,
|
|
3068
|
+
y: my + 4,
|
|
3069
|
+
textAnchor: "middle",
|
|
3070
|
+
fontSize: 10,
|
|
3071
|
+
fill: t.textSecondary,
|
|
3072
|
+
fontFamily: "ui-sans-serif,system-ui,sans-serif",
|
|
3073
|
+
fontWeight: "500",
|
|
3074
|
+
style: STYLE_LABEL,
|
|
3075
|
+
children: edge.label
|
|
3076
|
+
}
|
|
3077
|
+
)
|
|
3078
|
+
] }) : null
|
|
3079
|
+
]
|
|
3080
|
+
}
|
|
3081
|
+
);
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
// src/ui/Minimap.tsx
|
|
3085
|
+
import { useCallback as useCallback5, useRef as useRef4 } from "react";
|
|
3086
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
3087
|
+
var W = 168;
|
|
3088
|
+
var H = 112;
|
|
3089
|
+
var PAD = 18;
|
|
3090
|
+
function Minimap({
|
|
3091
|
+
model,
|
|
3092
|
+
viewportW,
|
|
3093
|
+
viewportH,
|
|
3094
|
+
transform,
|
|
3095
|
+
measureNode,
|
|
3096
|
+
onCenterOn,
|
|
3097
|
+
isDark,
|
|
3098
|
+
accentColor
|
|
3099
|
+
}) {
|
|
3100
|
+
const dragRef = useRef4(null);
|
|
3101
|
+
const boxes = model.nodes.map((n) => {
|
|
3102
|
+
const { w, h } = measureNode(n);
|
|
3103
|
+
return { id: n.id, x: n.x ?? 0, y: n.y ?? 0, w, h };
|
|
3104
|
+
});
|
|
3105
|
+
if (boxes.length === 0) return null;
|
|
3106
|
+
const vx = -transform.x / transform.scale;
|
|
3107
|
+
const vy = -transform.y / transform.scale;
|
|
3108
|
+
const vw = viewportW / transform.scale;
|
|
3109
|
+
const vh = viewportH / transform.scale;
|
|
3110
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
3111
|
+
for (const b of boxes) {
|
|
3112
|
+
minX = Math.min(minX, b.x);
|
|
3113
|
+
minY = Math.min(minY, b.y);
|
|
3114
|
+
maxX = Math.max(maxX, b.x + b.w);
|
|
3115
|
+
maxY = Math.max(maxY, b.y + b.h);
|
|
3116
|
+
}
|
|
3117
|
+
minX = Math.min(minX, vx);
|
|
3118
|
+
minY = Math.min(minY, vy);
|
|
3119
|
+
maxX = Math.max(maxX, vx + vw);
|
|
3120
|
+
maxY = Math.max(maxY, vy + vh);
|
|
3121
|
+
const contentW = Math.max(1, maxX - minX);
|
|
3122
|
+
const contentH = Math.max(1, maxY - minY);
|
|
3123
|
+
const scale = Math.min((W - PAD * 2) / contentW, (H - PAD * 2) / contentH);
|
|
3124
|
+
const offsetX = (W - contentW * scale) / 2 - minX * scale;
|
|
3125
|
+
const offsetY = (H - contentH * scale) / 2 - minY * scale;
|
|
3126
|
+
const project = (x, y) => ({
|
|
3127
|
+
x: offsetX + x * scale,
|
|
3128
|
+
y: offsetY + y * scale
|
|
3129
|
+
});
|
|
3130
|
+
const unproject = (mx, my) => ({
|
|
3131
|
+
x: (mx - offsetX) / scale,
|
|
3132
|
+
y: (my - offsetY) / scale
|
|
3133
|
+
});
|
|
3134
|
+
const panTo = useCallback5((e) => {
|
|
3135
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
3136
|
+
const mx = e.clientX - rect.left;
|
|
3137
|
+
const my = e.clientY - rect.top;
|
|
3138
|
+
const { x, y } = unproject(mx, my);
|
|
3139
|
+
onCenterOn(x, y);
|
|
3140
|
+
}, [onCenterOn, scale, offsetX, offsetY]);
|
|
3141
|
+
const onMouseDown = (e) => {
|
|
3142
|
+
e.stopPropagation();
|
|
3143
|
+
dragRef.current = { active: true };
|
|
3144
|
+
panTo(e);
|
|
3145
|
+
};
|
|
3146
|
+
const onMouseMove = (e) => {
|
|
3147
|
+
if (!dragRef.current?.active) return;
|
|
3148
|
+
panTo(e);
|
|
3149
|
+
};
|
|
3150
|
+
const onMouseUp = () => {
|
|
3151
|
+
dragRef.current = null;
|
|
3152
|
+
};
|
|
3153
|
+
const bg = isDark ? "rgba(15,23,42,0.92)" : "rgba(255,255,255,0.94)";
|
|
3154
|
+
const border = isDark ? "#334155" : "#e2e8f0";
|
|
3155
|
+
const nodeFill = isDark ? "#475569" : "#cbd5e1";
|
|
3156
|
+
const viewStroke = accentColor;
|
|
3157
|
+
const viewFill = `${accentColor}22`;
|
|
3158
|
+
const vp1 = project(vx, vy);
|
|
3159
|
+
const vp2 = project(vx + vw, vy + vh);
|
|
3160
|
+
const vpRect = {
|
|
3161
|
+
x: Math.max(0, Math.min(W, vp1.x)),
|
|
3162
|
+
y: Math.max(0, Math.min(H, vp1.y)),
|
|
3163
|
+
w: Math.max(2, Math.min(W, vp2.x) - Math.max(0, vp1.x)),
|
|
3164
|
+
h: Math.max(2, Math.min(H, vp2.y) - Math.max(0, vp1.y))
|
|
3165
|
+
};
|
|
3166
|
+
return /* @__PURE__ */ jsx8(
|
|
3167
|
+
"div",
|
|
3168
|
+
{
|
|
3169
|
+
style: {
|
|
3170
|
+
position: "absolute",
|
|
3171
|
+
bottom: 14,
|
|
3172
|
+
right: 14,
|
|
3173
|
+
background: bg,
|
|
3174
|
+
border: `1px solid ${border}`,
|
|
3175
|
+
borderRadius: 10,
|
|
3176
|
+
padding: 6,
|
|
3177
|
+
boxShadow: isDark ? "0 8px 20px rgba(0,0,0,0.45)" : "0 6px 18px rgba(15,23,42,0.08)",
|
|
3178
|
+
backdropFilter: "blur(6px)"
|
|
3179
|
+
},
|
|
3180
|
+
children: /* @__PURE__ */ jsxs8(
|
|
3181
|
+
"svg",
|
|
3182
|
+
{
|
|
3183
|
+
width: W,
|
|
3184
|
+
height: H,
|
|
3185
|
+
style: { display: "block", cursor: "grab", borderRadius: 6 },
|
|
3186
|
+
onMouseDown,
|
|
3187
|
+
onMouseMove,
|
|
3188
|
+
onMouseUp,
|
|
3189
|
+
onMouseLeave: onMouseUp,
|
|
3190
|
+
children: [
|
|
3191
|
+
/* @__PURE__ */ jsx8("rect", { width: W, height: H, rx: 6, fill: isDark ? "#0f172a" : "#fafbfc" }),
|
|
3192
|
+
boxes.map((b) => {
|
|
3193
|
+
const p = project(b.x, b.y);
|
|
3194
|
+
return /* @__PURE__ */ jsx8(
|
|
3195
|
+
"rect",
|
|
3196
|
+
{
|
|
3197
|
+
x: p.x,
|
|
3198
|
+
y: p.y,
|
|
3199
|
+
width: Math.max(2, b.w * scale),
|
|
3200
|
+
height: Math.max(2, b.h * scale),
|
|
3201
|
+
rx: 2,
|
|
3202
|
+
fill: nodeFill
|
|
3203
|
+
},
|
|
3204
|
+
b.id
|
|
3205
|
+
);
|
|
3206
|
+
}),
|
|
3207
|
+
/* @__PURE__ */ jsx8(
|
|
3208
|
+
"rect",
|
|
3209
|
+
{
|
|
3210
|
+
x: vpRect.x,
|
|
3211
|
+
y: vpRect.y,
|
|
3212
|
+
width: vpRect.w,
|
|
3213
|
+
height: vpRect.h,
|
|
3214
|
+
rx: 3,
|
|
3215
|
+
fill: viewFill,
|
|
3216
|
+
stroke: viewStroke,
|
|
3217
|
+
strokeWidth: 1.25
|
|
3218
|
+
}
|
|
3219
|
+
)
|
|
3220
|
+
]
|
|
3221
|
+
}
|
|
3222
|
+
)
|
|
3223
|
+
}
|
|
3224
|
+
);
|
|
3225
|
+
}
|
|
3226
|
+
|
|
3227
|
+
// src/ui/ContextMenu.tsx
|
|
3228
|
+
import { useEffect as useEffect6, useRef as useRef5, useState as useState8 } from "react";
|
|
3229
|
+
import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
3230
|
+
function ContextMenu({
|
|
3231
|
+
x,
|
|
3232
|
+
y,
|
|
3233
|
+
nodeId,
|
|
3234
|
+
edgeId,
|
|
3235
|
+
isDark,
|
|
3236
|
+
t,
|
|
3237
|
+
acc,
|
|
3238
|
+
canUndo,
|
|
3239
|
+
canRedo,
|
|
3240
|
+
onUndo,
|
|
3241
|
+
onRedo,
|
|
3242
|
+
onReCenter,
|
|
3243
|
+
onAddNode,
|
|
3244
|
+
onDuplicate,
|
|
3245
|
+
onRename,
|
|
3246
|
+
onDelete,
|
|
3247
|
+
onDisconnect,
|
|
3248
|
+
onEdgeRename,
|
|
3249
|
+
onEdgeStyle,
|
|
3250
|
+
onEdgeArrowhead,
|
|
3251
|
+
onEdgeDelete,
|
|
3252
|
+
onEdgeResetRouting,
|
|
3253
|
+
currentEdgeStyle,
|
|
3254
|
+
currentEdgeArrow,
|
|
3255
|
+
edgeHasWaypoint,
|
|
3256
|
+
containerRef
|
|
3257
|
+
}) {
|
|
3258
|
+
const menuRef = useRef5(null);
|
|
3259
|
+
const [pos, setPos] = useState8({ x, y });
|
|
3260
|
+
useEffect6(() => {
|
|
3261
|
+
if (!menuRef.current || !containerRef.current) return;
|
|
3262
|
+
const m = menuRef.current.getBoundingClientRect();
|
|
3263
|
+
const c = containerRef.current.getBoundingClientRect();
|
|
3264
|
+
let nx = x, ny = y;
|
|
3265
|
+
if (nx + m.width > c.right - 8) nx = x - m.width;
|
|
3266
|
+
if (ny + m.height > c.bottom - 8) ny = y - m.height;
|
|
3267
|
+
setPos({ x: nx, y: ny });
|
|
3268
|
+
}, [x, y, containerRef]);
|
|
3269
|
+
const bg = isDark ? "#1e293b" : "#ffffff";
|
|
3270
|
+
const border = isDark ? "#334155" : "#e2e8f0";
|
|
3271
|
+
const hoverBg = isDark ? "#334155" : "#f1f5f9";
|
|
3272
|
+
const dividerColor = isDark ? "#334155" : "#f1f5f9";
|
|
3273
|
+
const text = t.textPrimary;
|
|
3274
|
+
const muted = t.textMuted;
|
|
3275
|
+
const item = (label, onClick, color, disabled) => /* @__PURE__ */ jsx9(
|
|
3276
|
+
"button",
|
|
3277
|
+
{
|
|
3278
|
+
onClick: disabled ? void 0 : onClick,
|
|
3279
|
+
style: {
|
|
3280
|
+
display: "flex",
|
|
3281
|
+
alignItems: "center",
|
|
3282
|
+
gap: 10,
|
|
3283
|
+
width: "100%",
|
|
3284
|
+
padding: "7px 14px",
|
|
3285
|
+
background: "none",
|
|
3286
|
+
border: "none",
|
|
3287
|
+
textAlign: "left",
|
|
3288
|
+
cursor: disabled ? "default" : "pointer",
|
|
3289
|
+
fontSize: 12,
|
|
3290
|
+
fontFamily: "ui-sans-serif,system-ui,sans-serif",
|
|
3291
|
+
color: disabled ? muted : color ?? text,
|
|
3292
|
+
opacity: disabled ? 0.4 : 1,
|
|
3293
|
+
borderRadius: 6
|
|
3294
|
+
},
|
|
3295
|
+
onMouseEnter: (e) => {
|
|
3296
|
+
if (!disabled) e.currentTarget.style.background = hoverBg;
|
|
3297
|
+
},
|
|
3298
|
+
onMouseLeave: (e) => {
|
|
3299
|
+
e.currentTarget.style.background = "none";
|
|
3300
|
+
},
|
|
3301
|
+
children: label
|
|
3302
|
+
},
|
|
3303
|
+
label
|
|
3304
|
+
);
|
|
3305
|
+
const divider2 = /* @__PURE__ */ jsx9("div", { style: { height: 1, background: dividerColor, margin: "4px 0" } });
|
|
3306
|
+
return /* @__PURE__ */ jsx9(
|
|
3307
|
+
"div",
|
|
3308
|
+
{
|
|
3309
|
+
ref: menuRef,
|
|
3310
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
3311
|
+
style: {
|
|
3312
|
+
position: "fixed",
|
|
3313
|
+
left: pos.x,
|
|
3314
|
+
top: pos.y,
|
|
3315
|
+
zIndex: 9999,
|
|
3316
|
+
background: bg,
|
|
3317
|
+
border: `1px solid ${border}`,
|
|
3318
|
+
borderRadius: 10,
|
|
3319
|
+
padding: "5px 0",
|
|
3320
|
+
minWidth: 180,
|
|
3321
|
+
boxShadow: isDark ? "0 8px 32px rgba(0,0,0,0.5)" : "0 8px 32px rgba(0,0,0,0.12)",
|
|
3322
|
+
fontFamily: "ui-sans-serif,system-ui,sans-serif"
|
|
3323
|
+
},
|
|
3324
|
+
children: edgeId ? /* @__PURE__ */ jsxs9(Fragment3, { children: [
|
|
3325
|
+
/* @__PURE__ */ jsx9("div", { style: { padding: "4px 14px 6px", fontSize: 10, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Edge" }),
|
|
3326
|
+
item("Rename label (dbl-click)", () => onEdgeRename?.()),
|
|
3327
|
+
divider2,
|
|
3328
|
+
/* @__PURE__ */ jsx9("div", { style: { padding: "4px 14px 2px", fontSize: 9, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Style" }),
|
|
3329
|
+
item(`Solid${currentEdgeStyle === "solid" || !currentEdgeStyle ? " \u2713" : ""}`, () => onEdgeStyle?.("solid")),
|
|
3330
|
+
item(`Dashed${currentEdgeStyle === "dashed" ? " \u2713" : ""}`, () => onEdgeStyle?.("dashed")),
|
|
3331
|
+
item(`Dotted${currentEdgeStyle === "dotted" ? " \u2713" : ""}`, () => onEdgeStyle?.("dotted")),
|
|
3332
|
+
divider2,
|
|
3333
|
+
/* @__PURE__ */ jsx9("div", { style: { padding: "4px 14px 2px", fontSize: 9, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Arrowhead" }),
|
|
3334
|
+
item(`Arrow${currentEdgeArrow !== "none" ? " \u2713" : ""}`, () => onEdgeArrowhead?.("arrow")),
|
|
3335
|
+
item(`None${currentEdgeArrow === "none" ? " \u2713" : ""}`, () => onEdgeArrowhead?.("none")),
|
|
3336
|
+
divider2,
|
|
3337
|
+
item("Reset routing", () => onEdgeResetRouting?.(), void 0, !edgeHasWaypoint),
|
|
3338
|
+
item("Delete edge", () => onEdgeDelete?.(), "#ef4444")
|
|
3339
|
+
] }) : nodeId ? /* @__PURE__ */ jsxs9(Fragment3, { children: [
|
|
3340
|
+
/* @__PURE__ */ jsx9("div", { style: { padding: "4px 14px 6px", fontSize: 10, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Node" }),
|
|
3341
|
+
item("Rename (dbl-click)", onRename),
|
|
3342
|
+
item("Duplicate", onDuplicate),
|
|
3343
|
+
item("Disconnect all edges", onDisconnect),
|
|
3344
|
+
divider2,
|
|
3345
|
+
item("Delete node", onDelete, "#ef4444")
|
|
3346
|
+
] }) : /* @__PURE__ */ jsxs9(Fragment3, { children: [
|
|
3347
|
+
/* @__PURE__ */ jsx9("div", { style: { padding: "4px 14px 6px", fontSize: 10, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Canvas" }),
|
|
3348
|
+
item("Add node here", onAddNode, acc.color),
|
|
3349
|
+
item("Re-center (Ctrl+0)", onReCenter),
|
|
3350
|
+
divider2,
|
|
3351
|
+
item("Undo (Ctrl+Z)", onUndo, void 0, !canUndo),
|
|
3352
|
+
item("Redo (Ctrl+Y)", onRedo, void 0, !canRedo)
|
|
3353
|
+
] })
|
|
3354
|
+
}
|
|
3355
|
+
);
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3358
|
+
// src/ui/DiagramCanvas.tsx
|
|
3359
|
+
import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3360
|
+
var STYLE_LABEL2 = { pointerEvents: "none", userSelect: "none" };
|
|
3361
|
+
var STYLE_LIVE_PORT = { opacity: 0.85, pointerEvents: "none" };
|
|
3362
|
+
var STYLE_NODE_GRAB = { cursor: "grab" };
|
|
3363
|
+
var STYLE_NODE_GRABBING = { cursor: "grabbing" };
|
|
3364
|
+
var STYLE_PORT_VISIBLE = { cursor: "crosshair", opacity: 1, transition: "opacity 0.15s", pointerEvents: "all", filter: "drop-shadow(0 1px 3px rgba(0,0,0,0.25))" };
|
|
3365
|
+
var STYLE_PORT_HIDDEN = { cursor: "crosshair", opacity: 0, transition: "opacity 0.15s", pointerEvents: "none", filter: "drop-shadow(0 1px 3px rgba(0,0,0,0.25))" };
|
|
3366
|
+
function DiagramCanvas(props) {
|
|
3367
|
+
const {
|
|
3368
|
+
model,
|
|
3369
|
+
variant,
|
|
3370
|
+
variantLabel,
|
|
3371
|
+
t,
|
|
3372
|
+
isDark,
|
|
3373
|
+
acc,
|
|
3374
|
+
transform,
|
|
3375
|
+
setTransform,
|
|
3376
|
+
selected,
|
|
3377
|
+
selectedSet,
|
|
3378
|
+
hoveredId,
|
|
3379
|
+
setHoveredId,
|
|
3380
|
+
drag,
|
|
3381
|
+
pan,
|
|
3382
|
+
liveEdge,
|
|
3383
|
+
boxSel,
|
|
3384
|
+
alignGuides,
|
|
3385
|
+
editingEdgeId,
|
|
3386
|
+
editEdgeLabel,
|
|
3387
|
+
setEditEdgeLabel,
|
|
3388
|
+
commitEdgeEdit,
|
|
3389
|
+
setEditingEdgeId,
|
|
3390
|
+
beginEditEdge,
|
|
3391
|
+
onEdgeContextMenu,
|
|
3392
|
+
setWaypointDrag,
|
|
3393
|
+
editingId,
|
|
3394
|
+
editLabel,
|
|
3395
|
+
setEditLabel,
|
|
3396
|
+
commitEdit,
|
|
3397
|
+
setEditingId,
|
|
3398
|
+
onNodeMouseDown,
|
|
3399
|
+
onNodeMouseUp,
|
|
3400
|
+
onNodeDblClick,
|
|
3401
|
+
onNodeContextMenu,
|
|
3402
|
+
onPortMouseDown,
|
|
3403
|
+
onAnswerPortDown,
|
|
3404
|
+
onSvgMouseDown,
|
|
3405
|
+
onMouseMove,
|
|
3406
|
+
onMouseUp,
|
|
3407
|
+
onSvgContextMenu,
|
|
3408
|
+
reducedMotion,
|
|
3409
|
+
isCoarse,
|
|
3410
|
+
portR,
|
|
3411
|
+
shadowClr,
|
|
3412
|
+
arrowClr,
|
|
3413
|
+
amberArrow,
|
|
3414
|
+
viewport,
|
|
3415
|
+
svgRef,
|
|
3416
|
+
containerRef,
|
|
3417
|
+
ctxMenu,
|
|
3418
|
+
history,
|
|
3419
|
+
onCtxUndo,
|
|
3420
|
+
onCtxRedo,
|
|
3421
|
+
onCtxReCenter,
|
|
3422
|
+
onCtxAddNode,
|
|
3423
|
+
onCtxDuplicate,
|
|
3424
|
+
onCtxRename,
|
|
3425
|
+
onCtxDelete,
|
|
3426
|
+
onCtxDisconnect,
|
|
3427
|
+
ctxEdgeStyle,
|
|
3428
|
+
ctxEdgeArrow,
|
|
3429
|
+
ctxEdgeHasWaypoint,
|
|
3430
|
+
onCtxEdgeRename,
|
|
3431
|
+
onCtxEdgeStyle,
|
|
3432
|
+
onCtxEdgeArrowhead,
|
|
3433
|
+
onCtxEdgeDelete,
|
|
3434
|
+
onCtxEdgeResetRouting
|
|
3435
|
+
} = props;
|
|
3436
|
+
return /* @__PURE__ */ jsxs10("div", { ref: containerRef, style: { flex: 1, overflow: "hidden", position: "relative", background: t.canvas }, children: [
|
|
3437
|
+
/* @__PURE__ */ jsxs10(
|
|
3438
|
+
"svg",
|
|
3439
|
+
{
|
|
3440
|
+
ref: svgRef,
|
|
3441
|
+
width: "100%",
|
|
3442
|
+
height: "100%",
|
|
3443
|
+
role: "application",
|
|
3444
|
+
"aria-label": `${variantLabel} diagram editor. ${model.nodes.length} ${variantLabel.toLowerCase()}s, ${model.edges.length} connections. Scroll to zoom, drag to pan, click a ${variantLabel.toLowerCase()} to select.`,
|
|
3445
|
+
tabIndex: 0,
|
|
3446
|
+
style: { display: "block", cursor: pan ? "grabbing" : drag ? "grabbing" : liveEdge ? "crosshair" : "default", userSelect: "none", outline: "none" },
|
|
3447
|
+
onMouseDown: onSvgMouseDown,
|
|
3448
|
+
onMouseMove,
|
|
3449
|
+
onMouseUp,
|
|
3450
|
+
onMouseLeave: onMouseUp,
|
|
3451
|
+
onContextMenu: onSvgContextMenu,
|
|
3452
|
+
children: [
|
|
3453
|
+
/* @__PURE__ */ jsxs10("defs", { children: [
|
|
3454
|
+
/* @__PURE__ */ jsx10("style", { children: reducedMotion ? `
|
|
3455
|
+
.edge-flow { stroke-dasharray: 0; }
|
|
3456
|
+
.edge-flow-amber { stroke-dasharray: 0; }
|
|
3457
|
+
.edge-live { stroke-dasharray: 4 4; }
|
|
3458
|
+
` : `
|
|
3459
|
+
@keyframes edgeFlow { to { stroke-dashoffset: -13; } }
|
|
3460
|
+
@keyframes edgeFlowFast { to { stroke-dashoffset: -13; } }
|
|
3461
|
+
.edge-flow { stroke-dasharray: 8 5; animation: edgeFlow 0.9s linear infinite; }
|
|
3462
|
+
.edge-flow-amber { stroke-dasharray: 6 4; animation: edgeFlowFast 0.65s linear infinite; }
|
|
3463
|
+
.edge-live { stroke-dasharray: 7 5; animation: edgeFlow 0.55s linear infinite; }
|
|
3464
|
+
` }),
|
|
3465
|
+
/* @__PURE__ */ jsx10("pattern", { id: "dots", width: GRID, height: GRID, patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ jsx10("circle", { cx: GRID / 2, cy: GRID / 2, r: 1.1, fill: t.dot }) }),
|
|
3466
|
+
/* @__PURE__ */ jsx10("filter", { id: "nodeShadow", x: "-25%", y: "-25%", width: "150%", height: "160%", children: /* @__PURE__ */ jsx10("feDropShadow", { dx: "0", dy: "3", stdDeviation: "5", floodColor: shadowClr, floodOpacity: "1" }) }),
|
|
3467
|
+
/* @__PURE__ */ jsx10("marker", { id: "arrowhead", markerWidth: "9", markerHeight: "7", refX: "8", refY: "3.5", orient: "auto", markerUnits: "strokeWidth", children: /* @__PURE__ */ jsx10("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: arrowClr }) }),
|
|
3468
|
+
/* @__PURE__ */ jsx10("marker", { id: "arrowAmber", markerWidth: "9", markerHeight: "7", refX: "8", refY: "3.5", orient: "auto", markerUnits: "strokeWidth", children: /* @__PURE__ */ jsx10("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: amberArrow }) }),
|
|
3469
|
+
/* @__PURE__ */ jsx10("marker", { id: "arrowLive", markerWidth: "9", markerHeight: "7", refX: "8", refY: "3.5", orient: "auto", markerUnits: "strokeWidth", children: /* @__PURE__ */ jsx10("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: acc.color }) })
|
|
3470
|
+
] }),
|
|
3471
|
+
/* @__PURE__ */ jsx10("rect", { width: "100%", height: "100%", fill: "url(#dots)", "data-bg": "1" }),
|
|
3472
|
+
/* @__PURE__ */ jsxs10("g", { transform: `translate(${transform.x},${transform.y}) scale(${transform.scale})`, children: [
|
|
3473
|
+
model.edges.map((e) => /* @__PURE__ */ jsx10(
|
|
3474
|
+
EdgeLine,
|
|
3475
|
+
{
|
|
3476
|
+
edge: e,
|
|
3477
|
+
nodes: model.nodes,
|
|
3478
|
+
variant,
|
|
3479
|
+
t,
|
|
3480
|
+
isDark,
|
|
3481
|
+
acc,
|
|
3482
|
+
editing: editingEdgeId === e.id,
|
|
3483
|
+
editValue: editEdgeLabel,
|
|
3484
|
+
onEditChange: setEditEdgeLabel,
|
|
3485
|
+
onEditCommit: commitEdgeEdit,
|
|
3486
|
+
onEditCancel: () => setEditingEdgeId(null),
|
|
3487
|
+
onDoubleClick: beginEditEdge,
|
|
3488
|
+
onContextMenu: onEdgeContextMenu,
|
|
3489
|
+
onWaypointDown: (ev, edgeId) => setWaypointDrag(edgeId)
|
|
3490
|
+
},
|
|
3491
|
+
e.id
|
|
3492
|
+
)),
|
|
3493
|
+
liveEdge && (() => {
|
|
3494
|
+
const d = bezierPath2(liveEdge.fromX, liveEdge.fromY, liveEdge.toX, liveEdge.toY, liveEdge.exitDir);
|
|
3495
|
+
return /* @__PURE__ */ jsx10("path", { d, fill: "none", stroke: acc.color, strokeWidth: 2, strokeLinecap: "round", className: "edge-live", opacity: 0.8, markerEnd: "url(#arrowLive)" });
|
|
3496
|
+
})(),
|
|
3497
|
+
alignGuides?.x && /* @__PURE__ */ jsx10(
|
|
3498
|
+
"line",
|
|
3499
|
+
{
|
|
3500
|
+
x1: alignGuides.x.pos,
|
|
3501
|
+
x2: alignGuides.x.pos,
|
|
3502
|
+
y1: alignGuides.x.minY,
|
|
3503
|
+
y2: alignGuides.x.maxY,
|
|
3504
|
+
stroke: acc.color,
|
|
3505
|
+
strokeWidth: 1 / transform.scale,
|
|
3506
|
+
strokeDasharray: `${4 / transform.scale} ${3 / transform.scale}`,
|
|
3507
|
+
opacity: 0.85,
|
|
3508
|
+
pointerEvents: "none"
|
|
3509
|
+
}
|
|
3510
|
+
),
|
|
3511
|
+
alignGuides?.y && /* @__PURE__ */ jsx10(
|
|
3512
|
+
"line",
|
|
3513
|
+
{
|
|
3514
|
+
y1: alignGuides.y.pos,
|
|
3515
|
+
y2: alignGuides.y.pos,
|
|
3516
|
+
x1: alignGuides.y.minX,
|
|
3517
|
+
x2: alignGuides.y.maxX,
|
|
3518
|
+
stroke: acc.color,
|
|
3519
|
+
strokeWidth: 1 / transform.scale,
|
|
3520
|
+
strokeDasharray: `${4 / transform.scale} ${3 / transform.scale}`,
|
|
3521
|
+
opacity: 0.85,
|
|
3522
|
+
pointerEvents: "none"
|
|
3219
3523
|
}
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3524
|
+
),
|
|
3525
|
+
model.nodes.map((node, idx) => {
|
|
3526
|
+
const isHovered = hoveredId === node.id;
|
|
3527
|
+
const isQuestion2 = variant === "question";
|
|
3528
|
+
const { w: nW } = nodeDims(node, variant);
|
|
3529
|
+
const isSelected = selectedSet.has(node.id);
|
|
3530
|
+
return /* @__PURE__ */ jsxs10(
|
|
3531
|
+
"g",
|
|
3532
|
+
{
|
|
3533
|
+
transform: `translate(${node.x ?? 0},${node.y ?? 0})`,
|
|
3534
|
+
role: "button",
|
|
3535
|
+
"aria-label": `${variantLabel} ${variant === "journey" ? idx + 1 + ": " : ""}${node.label}${isSelected ? ", selected" : ""}`,
|
|
3536
|
+
style: drag?.nodeId === node.id ? STYLE_NODE_GRABBING : STYLE_NODE_GRAB,
|
|
3537
|
+
onMouseDown: (e) => onNodeMouseDown(e, node.id),
|
|
3538
|
+
onMouseUp: (e) => onNodeMouseUp(e, node.id),
|
|
3539
|
+
onDoubleClick: (e) => onNodeDblClick(e, node.id),
|
|
3540
|
+
onContextMenu: (e) => onNodeContextMenu(e, node.id),
|
|
3541
|
+
onMouseEnter: () => setHoveredId(node.id),
|
|
3542
|
+
onMouseLeave: () => setHoveredId(null),
|
|
3543
|
+
children: [
|
|
3544
|
+
/* @__PURE__ */ jsx10("title", { children: `${variantLabel}: ${node.label}` }),
|
|
3545
|
+
isQuestion2 ? /* @__PURE__ */ jsx10(QuestionNode, { node, selected: isSelected, edges: model.edges, isDark, onAnswerPortDown, qW: nW }) : /* @__PURE__ */ jsxs10(Fragment4, { children: [
|
|
3546
|
+
/* @__PURE__ */ jsx10(NodeShape, { node, selected: isSelected, variant, stepNumber: variant === "journey" ? idx + 1 : void 0, t, isDark, w: nW }),
|
|
3547
|
+
editingId === node.id ? /* @__PURE__ */ jsx10("foreignObject", { x: 6, y: 6, width: nW - 12, height: NODE_H2 - 12, children: /* @__PURE__ */ jsx10(
|
|
3548
|
+
"input",
|
|
3549
|
+
{
|
|
3550
|
+
autoFocus: true,
|
|
3551
|
+
value: editLabel,
|
|
3552
|
+
onChange: (e) => setEditLabel(e.target.value),
|
|
3553
|
+
onBlur: commitEdit,
|
|
3554
|
+
onKeyDown: (e) => {
|
|
3555
|
+
if (e.key === "Enter") commitEdit();
|
|
3556
|
+
if (e.key === "Escape") setEditingId(null);
|
|
3557
|
+
},
|
|
3558
|
+
style: { width: "100%", height: "100%", border: "none", borderRadius: 6, outline: `2px solid ${acc.color}`, textAlign: "center", fontSize: 13, fontWeight: 500, background: t.inputBg, boxSizing: "border-box", padding: "0 6px", fontFamily: "inherit", color: t.inputText }
|
|
3559
|
+
}
|
|
3560
|
+
) }) : /* @__PURE__ */ jsx10("text", { x: nW / 2, y: NODE_H2 / 2 + 5, textAnchor: "middle", fontSize: 13, fontWeight: "500", fontFamily: "ui-sans-serif,system-ui,sans-serif", fill: isSelected ? acc.color : t.textPrimary, style: STYLE_LABEL2, children: node.label }),
|
|
3561
|
+
/* @__PURE__ */ jsx10(
|
|
3562
|
+
"circle",
|
|
3563
|
+
{
|
|
3564
|
+
cx: nW / 2,
|
|
3565
|
+
cy: NODE_H2 + 1,
|
|
3566
|
+
r: portR,
|
|
3567
|
+
fill: acc.color,
|
|
3568
|
+
stroke: isDark ? "#0f172a" : "white",
|
|
3569
|
+
strokeWidth: 2,
|
|
3570
|
+
style: isHovered || isCoarse ? STYLE_PORT_VISIBLE : STYLE_PORT_HIDDEN,
|
|
3571
|
+
onMouseDown: (e) => onPortMouseDown(e, node.id)
|
|
3572
|
+
}
|
|
3573
|
+
)
|
|
3574
|
+
] }),
|
|
3575
|
+
liveEdge && liveEdge.fromId !== node.id && /* @__PURE__ */ jsx10("circle", { cx: nW / 2, cy: -1, r: portR, fill: acc.color, stroke: isDark ? "#0f172a" : "white", strokeWidth: 2, style: STYLE_LIVE_PORT })
|
|
3576
|
+
]
|
|
3577
|
+
},
|
|
3578
|
+
node.id
|
|
3579
|
+
);
|
|
3580
|
+
})
|
|
3581
|
+
] })
|
|
3582
|
+
]
|
|
3583
|
+
}
|
|
3584
|
+
),
|
|
3585
|
+
boxSel && Math.abs(boxSel.cx - boxSel.sx) + Math.abs(boxSel.cy - boxSel.sy) > 4 && containerRef.current && (() => {
|
|
3586
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
3587
|
+
const left = Math.min(boxSel.sx, boxSel.cx) - rect.left;
|
|
3588
|
+
const top = Math.min(boxSel.sy, boxSel.cy) - rect.top;
|
|
3589
|
+
const w = Math.abs(boxSel.cx - boxSel.sx);
|
|
3590
|
+
const h = Math.abs(boxSel.cy - boxSel.sy);
|
|
3591
|
+
return /* @__PURE__ */ jsx10(
|
|
3592
|
+
"div",
|
|
3593
|
+
{
|
|
3594
|
+
style: {
|
|
3595
|
+
position: "absolute",
|
|
3596
|
+
left,
|
|
3597
|
+
top,
|
|
3598
|
+
width: w,
|
|
3599
|
+
height: h,
|
|
3600
|
+
border: `1px dashed ${acc.color}`,
|
|
3601
|
+
background: isDark ? "rgba(99,102,241,0.10)" : "rgba(99,102,241,0.08)",
|
|
3602
|
+
pointerEvents: "none",
|
|
3603
|
+
borderRadius: 4
|
|
3237
3604
|
}
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3605
|
+
}
|
|
3606
|
+
);
|
|
3607
|
+
})(),
|
|
3608
|
+
model.nodes.length === 0 && /* @__PURE__ */ jsxs10("div", { style: { position: "absolute", inset: 0, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", pointerEvents: "none", gap: 8 }, children: [
|
|
3609
|
+
/* @__PURE__ */ jsx10("div", { style: { fontSize: 36, opacity: 0.1, color: t.textPrimary }, children: variant === "question" ? "?" : variant === "journey" ? "\u2197" : "\u2B21" }),
|
|
3610
|
+
/* @__PURE__ */ jsxs10("div", { style: { fontSize: 13, color: t.textMuted, fontWeight: 500 }, children: [
|
|
3611
|
+
"Click ",
|
|
3612
|
+
/* @__PURE__ */ jsxs10("strong", { style: { color: acc.color }, children: [
|
|
3613
|
+
"+ ",
|
|
3614
|
+
variantLabel
|
|
3615
|
+
] }),
|
|
3616
|
+
" to start"
|
|
3617
|
+
] })
|
|
3618
|
+
] }),
|
|
3619
|
+
model.nodes.length > 0 && viewport.w > 0 && /* @__PURE__ */ jsx10(
|
|
3620
|
+
Minimap,
|
|
3621
|
+
{
|
|
3622
|
+
model,
|
|
3623
|
+
viewportW: viewport.w,
|
|
3624
|
+
viewportH: viewport.h,
|
|
3625
|
+
transform,
|
|
3626
|
+
isDark,
|
|
3627
|
+
accentColor: acc.color,
|
|
3628
|
+
measureNode: (n) => nodeDims(n, variant),
|
|
3629
|
+
onCenterOn: (cx, cy) => {
|
|
3630
|
+
setTransform((tr) => ({ ...tr, x: viewport.w / 2 - cx * tr.scale, y: viewport.h / 2 - cy * tr.scale }));
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
),
|
|
3634
|
+
ctxMenu && /* @__PURE__ */ jsx10(
|
|
3635
|
+
ContextMenu,
|
|
3636
|
+
{
|
|
3637
|
+
x: ctxMenu.x,
|
|
3638
|
+
y: ctxMenu.y,
|
|
3639
|
+
nodeId: ctxMenu.nodeId,
|
|
3640
|
+
edgeId: ctxMenu.edgeId,
|
|
3641
|
+
isDark,
|
|
3642
|
+
t,
|
|
3643
|
+
acc,
|
|
3644
|
+
canUndo: history.canUndo,
|
|
3645
|
+
canRedo: history.canRedo,
|
|
3646
|
+
onUndo: onCtxUndo,
|
|
3647
|
+
onRedo: onCtxRedo,
|
|
3648
|
+
onReCenter: onCtxReCenter,
|
|
3649
|
+
onAddNode: onCtxAddNode,
|
|
3650
|
+
onDuplicate: onCtxDuplicate,
|
|
3651
|
+
onRename: onCtxRename,
|
|
3652
|
+
onDelete: onCtxDelete,
|
|
3653
|
+
onDisconnect: onCtxDisconnect,
|
|
3654
|
+
currentEdgeStyle: ctxEdgeStyle,
|
|
3655
|
+
currentEdgeArrow: ctxEdgeArrow,
|
|
3656
|
+
edgeHasWaypoint: ctxEdgeHasWaypoint,
|
|
3657
|
+
onEdgeRename: onCtxEdgeRename,
|
|
3658
|
+
onEdgeStyle: onCtxEdgeStyle,
|
|
3659
|
+
onEdgeArrowhead: onCtxEdgeArrowhead,
|
|
3660
|
+
onEdgeDelete: onCtxEdgeDelete,
|
|
3661
|
+
onEdgeResetRouting: onCtxEdgeResetRouting,
|
|
3662
|
+
containerRef
|
|
3663
|
+
}
|
|
3664
|
+
)
|
|
3665
|
+
] });
|
|
3271
3666
|
}
|
|
3272
3667
|
|
|
3273
3668
|
// src/ui/hooks/useHistory.ts
|
|
@@ -3327,10 +3722,10 @@ function useHistory(initial, onChange) {
|
|
|
3327
3722
|
}
|
|
3328
3723
|
|
|
3329
3724
|
// src/ui/hooks/useCanvasWheel.ts
|
|
3330
|
-
import { useEffect as
|
|
3725
|
+
import { useEffect as useEffect7 } from "react";
|
|
3331
3726
|
function useCanvasWheel(ref, setTransform, options = {}) {
|
|
3332
3727
|
const { min = 0.15, max = 3, factor = 0.1 } = options;
|
|
3333
|
-
|
|
3728
|
+
useEffect7(() => {
|
|
3334
3729
|
const el = ref.current;
|
|
3335
3730
|
if (!el) return;
|
|
3336
3731
|
const onWheel = (e) => {
|
|
@@ -3354,7 +3749,7 @@ function useCanvasWheel(ref, setTransform, options = {}) {
|
|
|
3354
3749
|
}
|
|
3355
3750
|
|
|
3356
3751
|
// src/ui/hooks/useCanvasTouch.ts
|
|
3357
|
-
import { useEffect as
|
|
3752
|
+
import { useEffect as useEffect8 } from "react";
|
|
3358
3753
|
function useCanvasTouch(ref, {
|
|
3359
3754
|
transform,
|
|
3360
3755
|
setTransform,
|
|
@@ -3364,7 +3759,7 @@ function useCanvasTouch(ref, {
|
|
|
3364
3759
|
longPressMs = 550,
|
|
3365
3760
|
longPressSlop = 8
|
|
3366
3761
|
}) {
|
|
3367
|
-
|
|
3762
|
+
useEffect8(() => {
|
|
3368
3763
|
const el = ref.current;
|
|
3369
3764
|
if (!el) return;
|
|
3370
3765
|
let touchPan = null;
|
|
@@ -3468,10 +3863,10 @@ function useCanvasTouch(ref, {
|
|
|
3468
3863
|
}
|
|
3469
3864
|
|
|
3470
3865
|
// src/ui/hooks/useElementSize.ts
|
|
3471
|
-
import { useEffect as
|
|
3866
|
+
import { useEffect as useEffect9, useState as useState10 } from "react";
|
|
3472
3867
|
function useElementSize(ref) {
|
|
3473
3868
|
const [size, setSize] = useState10({ w: 0, h: 0 });
|
|
3474
|
-
|
|
3869
|
+
useEffect9(() => {
|
|
3475
3870
|
const el = ref.current;
|
|
3476
3871
|
if (!el || typeof ResizeObserver === "undefined") return;
|
|
3477
3872
|
const measure = () => {
|
|
@@ -3577,14 +3972,12 @@ function nearestInDirection(fromX, fromY, dir, candidates) {
|
|
|
3577
3972
|
}
|
|
3578
3973
|
|
|
3579
3974
|
// src/ui/DiagramEditor.tsx
|
|
3580
|
-
import { Fragment as
|
|
3581
|
-
var STYLE_LABEL2 = { pointerEvents: "none", userSelect: "none" };
|
|
3582
|
-
var STYLE_LIVE_PORT = { opacity: 0.85, pointerEvents: "none" };
|
|
3975
|
+
import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3583
3976
|
var STYLE_SR_ONLY = { position: "absolute", width: 1, height: 1, padding: 0, margin: -1, overflow: "hidden", clip: "rect(0 0 0 0)", whiteSpace: "nowrap", border: 0 };
|
|
3584
3977
|
var STYLE_FLEX_ROW = { flex: 1, display: "flex", overflow: "hidden" };
|
|
3585
3978
|
function DiagramEditor(props) {
|
|
3586
3979
|
if (props.initialModel?.type === "sequence") {
|
|
3587
|
-
return /* @__PURE__ */
|
|
3980
|
+
return /* @__PURE__ */ jsx11(
|
|
3588
3981
|
SequenceEditor,
|
|
3589
3982
|
{
|
|
3590
3983
|
initialModel: props.initialModel,
|
|
@@ -3598,7 +3991,7 @@ function DiagramEditor(props) {
|
|
|
3598
3991
|
}
|
|
3599
3992
|
);
|
|
3600
3993
|
}
|
|
3601
|
-
return /* @__PURE__ */
|
|
3994
|
+
return /* @__PURE__ */ jsx11(FlowchartEditor, { ...props });
|
|
3602
3995
|
}
|
|
3603
3996
|
function FlowchartEditor({
|
|
3604
3997
|
initialModel,
|
|
@@ -3733,89 +4126,87 @@ function FlowchartEditor({
|
|
|
3733
4126
|
const duplicateNode = useCallback7((nodeId) => {
|
|
3734
4127
|
duplicateIds([nodeId]);
|
|
3735
4128
|
}, [duplicateIds]);
|
|
3736
|
-
|
|
4129
|
+
useEffect10(() => {
|
|
3737
4130
|
if (!ctxMenu) return;
|
|
3738
4131
|
const close = () => setCtxMenu(null);
|
|
3739
4132
|
window.addEventListener("mousedown", close);
|
|
3740
4133
|
return () => window.removeEventListener("mousedown", close);
|
|
3741
4134
|
}, [ctxMenu]);
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
return;
|
|
3761
|
-
}
|
|
3762
|
-
if (ctrl && (e.key === "d" || e.key === "D")) {
|
|
3763
|
-
if (selectedSet.size > 0) {
|
|
3764
|
-
e.preventDefault();
|
|
3765
|
-
duplicateIds(Array.from(selectedSet));
|
|
3766
|
-
}
|
|
3767
|
-
return;
|
|
4135
|
+
const keyCommands = [
|
|
4136
|
+
{ match: (e) => (e.ctrlKey || e.metaKey) && e.key === "z" && !e.shiftKey, run: () => {
|
|
4137
|
+
undo();
|
|
4138
|
+
return true;
|
|
4139
|
+
} },
|
|
4140
|
+
{ match: (e) => (e.ctrlKey || e.metaKey) && (e.key === "y" || e.shiftKey && e.key === "z"), run: () => {
|
|
4141
|
+
redo();
|
|
4142
|
+
return true;
|
|
4143
|
+
} },
|
|
4144
|
+
{ match: (e) => (e.ctrlKey || e.metaKey) && e.key === "0", run: () => {
|
|
4145
|
+
reCenter();
|
|
4146
|
+
return true;
|
|
4147
|
+
} },
|
|
4148
|
+
{
|
|
4149
|
+
match: (e) => (e.ctrlKey || e.metaKey) && (e.key === "d" || e.key === "D") && selectedSet.size > 0,
|
|
4150
|
+
run: () => {
|
|
4151
|
+
duplicateIds(Array.from(selectedSet));
|
|
4152
|
+
return true;
|
|
3768
4153
|
}
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
}
|
|
3779
|
-
}
|
|
3780
|
-
return;
|
|
4154
|
+
},
|
|
4155
|
+
{
|
|
4156
|
+
match: (e) => (e.ctrlKey || e.metaKey) && (e.key === "c" || e.key === "C") && selectedSet.size > 0,
|
|
4157
|
+
run: () => {
|
|
4158
|
+
const ids = new Set(selectedSet);
|
|
4159
|
+
const nodes = model.nodes.filter((n) => ids.has(n.id));
|
|
4160
|
+
const edges = model.edges.filter((ed) => ids.has(ed.from) && ids.has(ed.to));
|
|
4161
|
+
clipboardRef.current = {
|
|
4162
|
+
nodes: nodes.map((n) => ({ ...n })),
|
|
4163
|
+
edges: edges.map((ed) => ({ ...ed }))
|
|
4164
|
+
};
|
|
4165
|
+
return true;
|
|
3781
4166
|
}
|
|
3782
|
-
|
|
4167
|
+
},
|
|
4168
|
+
{
|
|
4169
|
+
match: (e) => (e.ctrlKey || e.metaKey) && (e.key === "v" || e.key === "V"),
|
|
4170
|
+
run: () => {
|
|
3783
4171
|
const clip = clipboardRef.current;
|
|
3784
|
-
if (clip
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
const
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
}
|
|
3807
|
-
return;
|
|
4172
|
+
if (!clip || clip.nodes.length === 0) return false;
|
|
4173
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
4174
|
+
const nextNode = makeIdSource("node", model.nodes);
|
|
4175
|
+
const nextEdge = makeIdSource("e", model.edges);
|
|
4176
|
+
const newNodes = clip.nodes.map((n) => {
|
|
4177
|
+
const newId = nextNode();
|
|
4178
|
+
idMap.set(n.id, newId);
|
|
4179
|
+
return { ...n, id: newId, x: (n.x ?? 0) + 24, y: (n.y ?? 0) + 24 };
|
|
4180
|
+
});
|
|
4181
|
+
const newEdges = clip.edges.map((ed) => ({
|
|
4182
|
+
...ed,
|
|
4183
|
+
id: nextEdge(),
|
|
4184
|
+
from: idMap.get(ed.from) ?? ed.from,
|
|
4185
|
+
to: idMap.get(ed.to) ?? ed.to
|
|
4186
|
+
}));
|
|
4187
|
+
const m = { ...model, nodes: [...model.nodes, ...newNodes], edges: [...model.edges, ...newEdges] };
|
|
4188
|
+
applyAndPush(m);
|
|
4189
|
+
const newIds = newNodes.map((n) => n.id);
|
|
4190
|
+
setSelected(newIds[newIds.length - 1]);
|
|
4191
|
+
setSelectedSet(new Set(newIds));
|
|
4192
|
+
setAnnouncement(`Pasted ${newIds.length} ${variantLabel.toLowerCase()}${newIds.length === 1 ? "" : "s"}.`);
|
|
4193
|
+
return true;
|
|
3808
4194
|
}
|
|
3809
|
-
|
|
4195
|
+
},
|
|
4196
|
+
{
|
|
4197
|
+
match: (e) => e.key === "Escape",
|
|
4198
|
+
run: () => {
|
|
3810
4199
|
if (ctxMenu) setCtxMenu(null);
|
|
3811
4200
|
if (liveEdge) setLiveEdge(null);
|
|
3812
4201
|
if (editingId) setEditingId(null);
|
|
3813
4202
|
if (boxSel) setBoxSel(null);
|
|
3814
4203
|
if (selectedSet.size > 0) clearSelection();
|
|
3815
|
-
return;
|
|
4204
|
+
return true;
|
|
3816
4205
|
}
|
|
3817
|
-
|
|
3818
|
-
|
|
4206
|
+
},
|
|
4207
|
+
{
|
|
4208
|
+
match: (e) => (e.key === "Delete" || e.key === "Backspace") && selectedSet.size > 0,
|
|
4209
|
+
run: () => {
|
|
3819
4210
|
const ids = new Set(selectedSet);
|
|
3820
4211
|
const updated = {
|
|
3821
4212
|
...model,
|
|
@@ -3825,29 +4216,34 @@ function FlowchartEditor({
|
|
|
3825
4216
|
applyAndPush(updated);
|
|
3826
4217
|
clearSelection();
|
|
3827
4218
|
setAnnouncement(`Deleted ${ids.size} ${variantLabel.toLowerCase()}${ids.size === 1 ? "" : "s"}.`);
|
|
3828
|
-
return;
|
|
4219
|
+
return true;
|
|
3829
4220
|
}
|
|
3830
|
-
|
|
4221
|
+
},
|
|
4222
|
+
{
|
|
4223
|
+
match: (e) => selectedSet.size > 0 && e.altKey && !!selected && (e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "ArrowLeft" || e.key === "ArrowRight"),
|
|
4224
|
+
run: (e) => {
|
|
3831
4225
|
const dirKey = e.key === "ArrowLeft" ? "left" : e.key === "ArrowRight" ? "right" : e.key === "ArrowUp" ? "up" : "down";
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
const
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
selectOne(nextId2);
|
|
3846
|
-
setAnnouncement(`Selected ${model.nodes.find((n) => n.id === nextId2)?.label ?? ""}.`);
|
|
3847
|
-
}
|
|
3848
|
-
return;
|
|
4226
|
+
const origin = model.nodes.find((n) => n.id === selected);
|
|
4227
|
+
if (!origin) return false;
|
|
4228
|
+
const od = nodeDims(origin, variant);
|
|
4229
|
+
const ox = (origin.x ?? 0) + od.w / 2;
|
|
4230
|
+
const oy = (origin.y ?? 0) + od.h / 2;
|
|
4231
|
+
const candidates = model.nodes.filter((n) => n.id !== selected).map((n) => {
|
|
4232
|
+
const d = nodeDims(n, variant);
|
|
4233
|
+
return { id: n.id, x: (n.x ?? 0) + d.w / 2, y: (n.y ?? 0) + d.h / 2 };
|
|
4234
|
+
});
|
|
4235
|
+
const nextNodeId = nearestInDirection(ox, oy, dirKey, candidates);
|
|
4236
|
+
if (nextNodeId) {
|
|
4237
|
+
selectOne(nextNodeId);
|
|
4238
|
+
setAnnouncement(`Selected ${model.nodes.find((n) => n.id === nextNodeId)?.label ?? ""}.`);
|
|
3849
4239
|
}
|
|
3850
|
-
|
|
4240
|
+
return true;
|
|
4241
|
+
}
|
|
4242
|
+
},
|
|
4243
|
+
{
|
|
4244
|
+
match: (e) => selectedSet.size > 0 && (e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "ArrowLeft" || e.key === "ArrowRight"),
|
|
4245
|
+
run: (e) => {
|
|
4246
|
+
const dirKey = e.key === "ArrowLeft" ? "left" : e.key === "ArrowRight" ? "right" : e.key === "ArrowUp" ? "up" : "down";
|
|
3851
4247
|
const step = e.shiftKey ? GRID * 4 : GRID;
|
|
3852
4248
|
const dx = dirKey === "left" ? -step : dirKey === "right" ? step : 0;
|
|
3853
4249
|
const dy = dirKey === "up" ? -step : dirKey === "down" ? step : 0;
|
|
@@ -3857,11 +4253,11 @@ function FlowchartEditor({
|
|
|
3857
4253
|
nodes: model.nodes.map((n) => ids.has(n.id) ? { ...n, x: snap((n.x ?? 0) + dx), y: snap((n.y ?? 0) + dy) } : n)
|
|
3858
4254
|
};
|
|
3859
4255
|
applyAndPush(updated);
|
|
4256
|
+
return true;
|
|
3860
4257
|
}
|
|
3861
|
-
}
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
}, [undo, redo, reCenter, selected, selectedSet, ctxMenu, liveEdge, editingId, boxSel, model, applyAndPush, duplicateNode, clearSelection]);
|
|
4258
|
+
}
|
|
4259
|
+
];
|
|
4260
|
+
useEditorKeyboard(keyCommands, [undo, redo, reCenter, selected, selectedSet, ctxMenu, liveEdge, editingId, boxSel, model, applyAndPush, duplicateNode, clearSelection]);
|
|
3865
4261
|
const toCanvas = useCallback7((clientX, clientY) => {
|
|
3866
4262
|
const rect = svgRef.current.getBoundingClientRect();
|
|
3867
4263
|
return { x: (clientX - rect.left - transform.x) / transform.scale, y: (clientY - rect.top - transform.y) / transform.scale };
|
|
@@ -4150,11 +4546,11 @@ function FlowchartEditor({
|
|
|
4150
4546
|
const handleImport = useImporter(applyAndPush, { transform: positionFlowchartNodes });
|
|
4151
4547
|
const acc = variantAccent(variant, isDark);
|
|
4152
4548
|
const variantLabel = variant === "question" ? "Question" : variant === "journey" ? "Step" : "Node";
|
|
4153
|
-
const
|
|
4154
|
-
const
|
|
4549
|
+
const shadowClr = shadowColor(isDark);
|
|
4550
|
+
const arrowClr = arrowColor(isDark);
|
|
4155
4551
|
const amberArrow = isDark ? ACCENT.amberDark : ACCENT.amber;
|
|
4156
|
-
return /* @__PURE__ */
|
|
4157
|
-
/* @__PURE__ */
|
|
4552
|
+
return /* @__PURE__ */ jsxs11("div", { className: "fsd-editor", style: { display: "flex", flexDirection: "column", height, width: "100%", fontFamily: "ui-sans-serif,system-ui,sans-serif", boxSizing: "border-box", background: t.ctrlsBg }, children: [
|
|
4553
|
+
/* @__PURE__ */ jsx11("style", { children: `
|
|
4158
4554
|
.fsd-editor button:focus-visible,
|
|
4159
4555
|
.fsd-editor input:focus-visible,
|
|
4160
4556
|
.fsd-editor textarea:focus-visible,
|
|
@@ -4169,7 +4565,7 @@ function FlowchartEditor({
|
|
|
4169
4565
|
outline-offset: -2px;
|
|
4170
4566
|
}
|
|
4171
4567
|
` }),
|
|
4172
|
-
/* @__PURE__ */
|
|
4568
|
+
/* @__PURE__ */ jsx11(
|
|
4173
4569
|
"div",
|
|
4174
4570
|
{
|
|
4175
4571
|
role: "status",
|
|
@@ -4179,28 +4575,28 @@ function FlowchartEditor({
|
|
|
4179
4575
|
children: announcement
|
|
4180
4576
|
}
|
|
4181
4577
|
),
|
|
4182
|
-
/* @__PURE__ */
|
|
4183
|
-
/* @__PURE__ */
|
|
4184
|
-
/* @__PURE__ */
|
|
4578
|
+
/* @__PURE__ */ jsx11(Toolbar, { onExport: handleExport, onImport: allowImport ? handleImport : void 0, allowedExports, allowImport }),
|
|
4579
|
+
/* @__PURE__ */ jsxs11("div", { style: { display: "flex", gap: 6, padding: "7px 14px", background: t.ctrlsBg, borderBottom: `1px solid ${t.ctrlsBorder}`, alignItems: "center", flexWrap: "wrap" }, children: [
|
|
4580
|
+
/* @__PURE__ */ jsxs11("button", { onClick: () => addNode(), style: ctrlBtn(acc.color, isDark), children: [
|
|
4185
4581
|
"+ ",
|
|
4186
4582
|
variantLabel
|
|
4187
4583
|
] }),
|
|
4188
|
-
selectedSet.size > 0 && /* @__PURE__ */
|
|
4189
|
-
/* @__PURE__ */
|
|
4190
|
-
/* @__PURE__ */
|
|
4584
|
+
selectedSet.size > 0 && /* @__PURE__ */ jsxs11(Fragment5, { children: [
|
|
4585
|
+
/* @__PURE__ */ jsx11("div", { style: { width: 1, height: 20, background: t.ctrlsBorder, margin: "0 2px" } }),
|
|
4586
|
+
/* @__PURE__ */ jsx11("button", { onClick: deleteSelected, style: { ...ctrlBtn("transparent", isDark), color: "#ef4444", border: `1px solid ${isDark ? "#7f1d1d" : "#fca5a5"}` }, children: selectedSet.size > 1 ? `Delete (${selectedSet.size})` : "Delete" })
|
|
4191
4587
|
] }),
|
|
4192
|
-
liveEdge && /* @__PURE__ */
|
|
4588
|
+
liveEdge && /* @__PURE__ */ jsxs11("span", { style: { fontSize: 11, color: acc.color, fontWeight: 600, marginLeft: 6 }, children: [
|
|
4193
4589
|
liveEdge.answerLabel ? `Routing "${liveEdge.answerLabel}" \u2192` : "Drop on a node to connect",
|
|
4194
|
-
/* @__PURE__ */
|
|
4590
|
+
/* @__PURE__ */ jsx11("span", { style: { fontWeight: 400, color: t.textMuted, marginLeft: 6 }, children: "release to cancel" })
|
|
4195
4591
|
] }),
|
|
4196
|
-
/* @__PURE__ */
|
|
4592
|
+
/* @__PURE__ */ jsxs11("span", { style: { marginLeft: "auto", fontSize: 11, color: t.textMuted }, children: [
|
|
4197
4593
|
variant === "question" ? "drag answer port to connect \xB7 " : "drag port dot \xB7 ",
|
|
4198
4594
|
"scroll to zoom \xB7 drag to pan"
|
|
4199
4595
|
] })
|
|
4200
4596
|
] }),
|
|
4201
|
-
variant !== "flowchart" && /* @__PURE__ */
|
|
4202
|
-
/* @__PURE__ */
|
|
4203
|
-
/* @__PURE__ */
|
|
4597
|
+
variant !== "flowchart" && /* @__PURE__ */ jsx11("div", { style: { padding: "3px 14px", background: acc.fill, borderBottom: `1px solid ${acc.border}`, fontSize: 11, color: acc.color, fontWeight: 600 }, children: variant === "question" ? "? Question Flow \u2014 add answers in the panel, drag their port to connect" : "\u2197 Journey Map \u2014 numbered steps, drag port to sequence" }),
|
|
4598
|
+
/* @__PURE__ */ jsxs11("div", { style: STYLE_FLEX_ROW, children: [
|
|
4599
|
+
/* @__PURE__ */ jsx11(
|
|
4204
4600
|
NodeNavigator,
|
|
4205
4601
|
{
|
|
4206
4602
|
model,
|
|
@@ -4214,323 +4610,162 @@ function FlowchartEditor({
|
|
|
4214
4610
|
onSelect: jumpToNode
|
|
4215
4611
|
}
|
|
4216
4612
|
),
|
|
4217
|
-
/* @__PURE__ */
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
x1: alignGuides.y.minX,
|
|
4298
|
-
x2: alignGuides.y.maxX,
|
|
4299
|
-
stroke: acc.color,
|
|
4300
|
-
strokeWidth: 1 / transform.scale,
|
|
4301
|
-
strokeDasharray: `${4 / transform.scale} ${3 / transform.scale}`,
|
|
4302
|
-
opacity: 0.85,
|
|
4303
|
-
pointerEvents: "none"
|
|
4304
|
-
}
|
|
4305
|
-
),
|
|
4306
|
-
model.nodes.map((node, idx) => {
|
|
4307
|
-
const isHovered = hoveredId === node.id;
|
|
4308
|
-
const isQuestion2 = variant === "question";
|
|
4309
|
-
const { w: nW, h: nH } = nodeDims(node, variant);
|
|
4310
|
-
const isSelected = selectedSet.has(node.id);
|
|
4311
|
-
return /* @__PURE__ */ jsxs9(
|
|
4312
|
-
"g",
|
|
4313
|
-
{
|
|
4314
|
-
transform: `translate(${node.x ?? 0},${node.y ?? 0})`,
|
|
4315
|
-
role: "button",
|
|
4316
|
-
"aria-label": `${variantLabel} ${variant === "journey" ? idx + 1 + ": " : ""}${node.label}${isSelected ? ", selected" : ""}`,
|
|
4317
|
-
style: { cursor: drag?.nodeId === node.id ? "grabbing" : "grab" },
|
|
4318
|
-
onMouseDown: (e) => onNodeMouseDown(e, node.id),
|
|
4319
|
-
onMouseUp: (e) => onNodeMouseUp(e, node.id),
|
|
4320
|
-
onDoubleClick: (e) => onNodeDblClick(e, node.id),
|
|
4321
|
-
onContextMenu: (e) => onNodeContextMenu(e, node.id),
|
|
4322
|
-
onMouseEnter: () => setHoveredId(node.id),
|
|
4323
|
-
onMouseLeave: () => setHoveredId(null),
|
|
4324
|
-
children: [
|
|
4325
|
-
/* @__PURE__ */ jsx9("title", { children: `${variantLabel}: ${node.label}` }),
|
|
4326
|
-
isQuestion2 ? /* @__PURE__ */ jsx9(QuestionNode, { node, selected: isSelected, edges: model.edges, isDark, onAnswerPortDown, qW: nW }) : /* @__PURE__ */ jsxs9(Fragment4, { children: [
|
|
4327
|
-
/* @__PURE__ */ jsx9(NodeShape, { node, selected: isSelected, variant, stepNumber: variant === "journey" ? idx + 1 : void 0, t, isDark, w: nW }),
|
|
4328
|
-
editingId === node.id ? /* @__PURE__ */ jsx9("foreignObject", { x: 6, y: 6, width: nW - 12, height: NODE_H2 - 12, children: /* @__PURE__ */ jsx9(
|
|
4329
|
-
"input",
|
|
4330
|
-
{
|
|
4331
|
-
autoFocus: true,
|
|
4332
|
-
value: editLabel,
|
|
4333
|
-
onChange: (e) => setEditLabel(e.target.value),
|
|
4334
|
-
onBlur: commitEdit,
|
|
4335
|
-
onKeyDown: (e) => {
|
|
4336
|
-
if (e.key === "Enter") commitEdit();
|
|
4337
|
-
if (e.key === "Escape") setEditingId(null);
|
|
4338
|
-
},
|
|
4339
|
-
style: { width: "100%", height: "100%", border: "none", borderRadius: 6, outline: `2px solid ${acc.color}`, textAlign: "center", fontSize: 13, fontWeight: 500, background: t.inputBg, boxSizing: "border-box", padding: "0 6px", fontFamily: "inherit", color: t.inputText }
|
|
4340
|
-
}
|
|
4341
|
-
) }) : /* @__PURE__ */ jsx9("text", { x: nW / 2, y: NODE_H2 / 2 + 5, textAnchor: "middle", fontSize: 13, fontWeight: "500", fontFamily: "ui-sans-serif,system-ui,sans-serif", fill: isSelected ? acc.color : t.textPrimary, style: STYLE_LABEL2, children: node.label }),
|
|
4342
|
-
/* @__PURE__ */ jsx9(
|
|
4343
|
-
"circle",
|
|
4344
|
-
{
|
|
4345
|
-
cx: nW / 2,
|
|
4346
|
-
cy: NODE_H2 + 1,
|
|
4347
|
-
r: portR,
|
|
4348
|
-
fill: acc.color,
|
|
4349
|
-
stroke: isDark ? "#0f172a" : "white",
|
|
4350
|
-
strokeWidth: 2,
|
|
4351
|
-
style: { cursor: "crosshair", opacity: isHovered || isCoarse ? 1 : 0, transition: "opacity 0.15s", pointerEvents: isHovered || isCoarse ? "all" : "none", filter: "drop-shadow(0 1px 3px rgba(0,0,0,0.25))" },
|
|
4352
|
-
onMouseDown: (e) => onPortMouseDown(e, node.id)
|
|
4353
|
-
}
|
|
4354
|
-
)
|
|
4355
|
-
] }),
|
|
4356
|
-
liveEdge && liveEdge.fromId !== node.id && /* @__PURE__ */ jsx9("circle", { cx: nW / 2, cy: -1, r: portR, fill: acc.color, stroke: isDark ? "#0f172a" : "white", strokeWidth: 2, style: STYLE_LIVE_PORT })
|
|
4357
|
-
]
|
|
4358
|
-
},
|
|
4359
|
-
node.id
|
|
4360
|
-
);
|
|
4361
|
-
})
|
|
4362
|
-
] })
|
|
4363
|
-
]
|
|
4364
|
-
}
|
|
4365
|
-
),
|
|
4366
|
-
boxSel && Math.abs(boxSel.cx - boxSel.sx) + Math.abs(boxSel.cy - boxSel.sy) > 4 && containerRef.current && (() => {
|
|
4367
|
-
const rect = containerRef.current.getBoundingClientRect();
|
|
4368
|
-
const left = Math.min(boxSel.sx, boxSel.cx) - rect.left;
|
|
4369
|
-
const top = Math.min(boxSel.sy, boxSel.cy) - rect.top;
|
|
4370
|
-
const w = Math.abs(boxSel.cx - boxSel.sx);
|
|
4371
|
-
const h = Math.abs(boxSel.cy - boxSel.sy);
|
|
4372
|
-
return /* @__PURE__ */ jsx9(
|
|
4373
|
-
"div",
|
|
4374
|
-
{
|
|
4375
|
-
style: {
|
|
4376
|
-
position: "absolute",
|
|
4377
|
-
left,
|
|
4378
|
-
top,
|
|
4379
|
-
width: w,
|
|
4380
|
-
height: h,
|
|
4381
|
-
border: `1px dashed ${acc.color}`,
|
|
4382
|
-
background: isDark ? "rgba(99,102,241,0.10)" : "rgba(99,102,241,0.08)",
|
|
4383
|
-
pointerEvents: "none",
|
|
4384
|
-
borderRadius: 4
|
|
4385
|
-
}
|
|
4613
|
+
/* @__PURE__ */ jsx11(
|
|
4614
|
+
DiagramCanvas,
|
|
4615
|
+
{
|
|
4616
|
+
model,
|
|
4617
|
+
variant,
|
|
4618
|
+
variantLabel,
|
|
4619
|
+
t,
|
|
4620
|
+
isDark,
|
|
4621
|
+
acc,
|
|
4622
|
+
transform,
|
|
4623
|
+
setTransform,
|
|
4624
|
+
selected,
|
|
4625
|
+
selectedSet,
|
|
4626
|
+
hoveredId,
|
|
4627
|
+
setHoveredId,
|
|
4628
|
+
drag,
|
|
4629
|
+
pan,
|
|
4630
|
+
liveEdge,
|
|
4631
|
+
boxSel,
|
|
4632
|
+
alignGuides,
|
|
4633
|
+
editingEdgeId,
|
|
4634
|
+
editEdgeLabel,
|
|
4635
|
+
setEditEdgeLabel,
|
|
4636
|
+
commitEdgeEdit,
|
|
4637
|
+
setEditingEdgeId,
|
|
4638
|
+
beginEditEdge,
|
|
4639
|
+
onEdgeContextMenu,
|
|
4640
|
+
setWaypointDrag,
|
|
4641
|
+
editingId,
|
|
4642
|
+
editLabel,
|
|
4643
|
+
setEditLabel,
|
|
4644
|
+
commitEdit,
|
|
4645
|
+
setEditingId,
|
|
4646
|
+
onNodeMouseDown,
|
|
4647
|
+
onNodeMouseUp,
|
|
4648
|
+
onNodeDblClick,
|
|
4649
|
+
onNodeContextMenu,
|
|
4650
|
+
onPortMouseDown,
|
|
4651
|
+
onAnswerPortDown,
|
|
4652
|
+
onSvgMouseDown,
|
|
4653
|
+
onMouseMove,
|
|
4654
|
+
onMouseUp,
|
|
4655
|
+
onSvgContextMenu,
|
|
4656
|
+
reducedMotion,
|
|
4657
|
+
isCoarse,
|
|
4658
|
+
portR,
|
|
4659
|
+
shadowClr,
|
|
4660
|
+
arrowClr,
|
|
4661
|
+
amberArrow,
|
|
4662
|
+
viewport,
|
|
4663
|
+
svgRef,
|
|
4664
|
+
containerRef,
|
|
4665
|
+
ctxMenu,
|
|
4666
|
+
history,
|
|
4667
|
+
ctxEdgeStyle: (ctxMenu?.edgeId ? model.edges.find((e) => e.id === ctxMenu.edgeId) : void 0)?.style ?? "solid",
|
|
4668
|
+
ctxEdgeArrow: (ctxMenu?.edgeId ? model.edges.find((e) => e.id === ctxMenu.edgeId) : void 0)?.arrowhead ?? "arrow",
|
|
4669
|
+
ctxEdgeHasWaypoint: !!(ctxMenu?.edgeId ? model.edges.find((e) => e.id === ctxMenu.edgeId) : void 0)?.waypoint,
|
|
4670
|
+
onCtxUndo: () => {
|
|
4671
|
+
undo();
|
|
4672
|
+
setCtxMenu(null);
|
|
4673
|
+
},
|
|
4674
|
+
onCtxRedo: () => {
|
|
4675
|
+
redo();
|
|
4676
|
+
setCtxMenu(null);
|
|
4677
|
+
},
|
|
4678
|
+
onCtxReCenter: () => {
|
|
4679
|
+
reCenter();
|
|
4680
|
+
setCtxMenu(null);
|
|
4681
|
+
},
|
|
4682
|
+
onCtxAddNode: () => {
|
|
4683
|
+
const rect = svgRef.current.getBoundingClientRect();
|
|
4684
|
+
const cx = (ctxMenu.x - rect.left - transform.x) / transform.scale;
|
|
4685
|
+
const cy = (ctxMenu.y - rect.top - transform.y) / transform.scale;
|
|
4686
|
+
addNode({ x: cx, y: cy });
|
|
4687
|
+
setCtxMenu(null);
|
|
4688
|
+
},
|
|
4689
|
+
onCtxDuplicate: () => {
|
|
4690
|
+
if (ctxMenu?.nodeId) {
|
|
4691
|
+
duplicateNode(ctxMenu.nodeId);
|
|
4692
|
+
setCtxMenu(null);
|
|
4386
4693
|
}
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
"+ ",
|
|
4395
|
-
variantLabel
|
|
4396
|
-
] }),
|
|
4397
|
-
" to start"
|
|
4398
|
-
] })
|
|
4399
|
-
] }),
|
|
4400
|
-
model.nodes.length > 0 && viewport.w > 0 && /* @__PURE__ */ jsx9(
|
|
4401
|
-
Minimap,
|
|
4402
|
-
{
|
|
4403
|
-
model,
|
|
4404
|
-
viewportW: viewport.w,
|
|
4405
|
-
viewportH: viewport.h,
|
|
4406
|
-
transform,
|
|
4407
|
-
isDark,
|
|
4408
|
-
accentColor: acc.color,
|
|
4409
|
-
measureNode: (n) => nodeDims(n, variant),
|
|
4410
|
-
onCenterOn: (cx, cy) => {
|
|
4411
|
-
setTransform((tr) => ({ ...tr, x: viewport.w / 2 - cx * tr.scale, y: viewport.h / 2 - cy * tr.scale }));
|
|
4694
|
+
},
|
|
4695
|
+
onCtxRename: () => {
|
|
4696
|
+
if (ctxMenu?.nodeId) {
|
|
4697
|
+
const node = model.nodes.find((n) => n.id === ctxMenu.nodeId);
|
|
4698
|
+
setEditingId(ctxMenu.nodeId);
|
|
4699
|
+
setEditLabel(node.label);
|
|
4700
|
+
setCtxMenu(null);
|
|
4412
4701
|
}
|
|
4413
|
-
}
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
ContextMenu,
|
|
4419
|
-
{
|
|
4420
|
-
x: ctxMenu.x,
|
|
4421
|
-
y: ctxMenu.y,
|
|
4422
|
-
nodeId: ctxMenu.nodeId,
|
|
4423
|
-
edgeId: ctxMenu.edgeId,
|
|
4424
|
-
isDark,
|
|
4425
|
-
t,
|
|
4426
|
-
acc,
|
|
4427
|
-
canUndo: history.canUndo,
|
|
4428
|
-
canRedo: history.canRedo,
|
|
4429
|
-
onUndo: () => {
|
|
4430
|
-
undo();
|
|
4431
|
-
setCtxMenu(null);
|
|
4432
|
-
},
|
|
4433
|
-
onRedo: () => {
|
|
4434
|
-
redo();
|
|
4435
|
-
setCtxMenu(null);
|
|
4436
|
-
},
|
|
4437
|
-
onReCenter: () => {
|
|
4438
|
-
reCenter();
|
|
4439
|
-
setCtxMenu(null);
|
|
4440
|
-
},
|
|
4441
|
-
onAddNode: () => {
|
|
4442
|
-
const rect = svgRef.current.getBoundingClientRect();
|
|
4443
|
-
const cx = (ctxMenu.x - rect.left - transform.x) / transform.scale;
|
|
4444
|
-
const cy = (ctxMenu.y - rect.top - transform.y) / transform.scale;
|
|
4445
|
-
addNode({ x: cx, y: cy });
|
|
4446
|
-
setCtxMenu(null);
|
|
4447
|
-
},
|
|
4448
|
-
onDuplicate: () => {
|
|
4449
|
-
if (ctxMenu.nodeId) {
|
|
4450
|
-
duplicateNode(ctxMenu.nodeId);
|
|
4451
|
-
setCtxMenu(null);
|
|
4452
|
-
}
|
|
4453
|
-
},
|
|
4454
|
-
onRename: () => {
|
|
4455
|
-
if (ctxMenu.nodeId) {
|
|
4456
|
-
const node = model.nodes.find((n) => n.id === ctxMenu.nodeId);
|
|
4457
|
-
setEditingId(ctxMenu.nodeId);
|
|
4458
|
-
setEditLabel(node.label);
|
|
4459
|
-
setCtxMenu(null);
|
|
4460
|
-
}
|
|
4461
|
-
},
|
|
4462
|
-
onDelete: () => {
|
|
4463
|
-
if (ctxMenu.nodeId) {
|
|
4464
|
-
deleteNode(ctxMenu.nodeId);
|
|
4465
|
-
setCtxMenu(null);
|
|
4466
|
-
}
|
|
4467
|
-
},
|
|
4468
|
-
onDisconnect: () => {
|
|
4469
|
-
if (ctxMenu.nodeId) {
|
|
4470
|
-
const m = { ...model, edges: model.edges.filter((e) => e.from !== ctxMenu.nodeId && e.to !== ctxMenu.nodeId) };
|
|
4471
|
-
applyAndPush(m);
|
|
4472
|
-
setCtxMenu(null);
|
|
4473
|
-
}
|
|
4474
|
-
},
|
|
4475
|
-
currentEdgeStyle: ctxEdge?.style ?? "solid",
|
|
4476
|
-
currentEdgeArrow: ctxEdge?.arrowhead ?? "arrow",
|
|
4477
|
-
edgeHasWaypoint: !!ctxEdge?.waypoint,
|
|
4478
|
-
onEdgeRename: () => {
|
|
4479
|
-
if (ctxMenu.edgeId) {
|
|
4480
|
-
beginEditEdge(ctxMenu.edgeId);
|
|
4481
|
-
setCtxMenu(null);
|
|
4482
|
-
}
|
|
4483
|
-
},
|
|
4484
|
-
onEdgeStyle: (s2) => {
|
|
4485
|
-
if (ctxMenu.edgeId) {
|
|
4486
|
-
setEdgeStyle(ctxMenu.edgeId, s2);
|
|
4487
|
-
setCtxMenu(null);
|
|
4488
|
-
}
|
|
4489
|
-
},
|
|
4490
|
-
onEdgeArrowhead: (a) => {
|
|
4491
|
-
if (ctxMenu.edgeId) {
|
|
4492
|
-
setEdgeArrowhead(ctxMenu.edgeId, a);
|
|
4493
|
-
setCtxMenu(null);
|
|
4494
|
-
}
|
|
4495
|
-
},
|
|
4496
|
-
onEdgeDelete: () => {
|
|
4497
|
-
if (ctxMenu.edgeId) {
|
|
4498
|
-
deleteEdge(ctxMenu.edgeId);
|
|
4499
|
-
setCtxMenu(null);
|
|
4500
|
-
}
|
|
4501
|
-
},
|
|
4502
|
-
onEdgeResetRouting: () => {
|
|
4503
|
-
if (ctxMenu.edgeId) {
|
|
4504
|
-
resetEdgeRouting(ctxMenu.edgeId);
|
|
4505
|
-
setCtxMenu(null);
|
|
4506
|
-
}
|
|
4507
|
-
},
|
|
4508
|
-
containerRef
|
|
4702
|
+
},
|
|
4703
|
+
onCtxDelete: () => {
|
|
4704
|
+
if (ctxMenu?.nodeId) {
|
|
4705
|
+
deleteNode(ctxMenu.nodeId);
|
|
4706
|
+
setCtxMenu(null);
|
|
4509
4707
|
}
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4708
|
+
},
|
|
4709
|
+
onCtxDisconnect: () => {
|
|
4710
|
+
if (ctxMenu?.nodeId) {
|
|
4711
|
+
const m = { ...model, edges: model.edges.filter((e) => e.from !== ctxMenu.nodeId && e.to !== ctxMenu.nodeId) };
|
|
4712
|
+
applyAndPush(m);
|
|
4713
|
+
setCtxMenu(null);
|
|
4714
|
+
}
|
|
4715
|
+
},
|
|
4716
|
+
onCtxEdgeRename: () => {
|
|
4717
|
+
if (ctxMenu?.edgeId) {
|
|
4718
|
+
beginEditEdge(ctxMenu.edgeId);
|
|
4719
|
+
setCtxMenu(null);
|
|
4720
|
+
}
|
|
4721
|
+
},
|
|
4722
|
+
onCtxEdgeStyle: (s2) => {
|
|
4723
|
+
if (ctxMenu?.edgeId) {
|
|
4724
|
+
setEdgeStyle(ctxMenu.edgeId, s2);
|
|
4725
|
+
setCtxMenu(null);
|
|
4726
|
+
}
|
|
4727
|
+
},
|
|
4728
|
+
onCtxEdgeArrowhead: (a) => {
|
|
4729
|
+
if (ctxMenu?.edgeId) {
|
|
4730
|
+
setEdgeArrowhead(ctxMenu.edgeId, a);
|
|
4731
|
+
setCtxMenu(null);
|
|
4732
|
+
}
|
|
4733
|
+
},
|
|
4734
|
+
onCtxEdgeDelete: () => {
|
|
4735
|
+
if (ctxMenu?.edgeId) {
|
|
4736
|
+
deleteEdge(ctxMenu.edgeId);
|
|
4737
|
+
setCtxMenu(null);
|
|
4738
|
+
}
|
|
4739
|
+
},
|
|
4740
|
+
onCtxEdgeResetRouting: () => {
|
|
4741
|
+
if (ctxMenu?.edgeId) {
|
|
4742
|
+
resetEdgeRouting(ctxMenu.edgeId);
|
|
4743
|
+
setCtxMenu(null);
|
|
4744
|
+
}
|
|
4745
|
+
}
|
|
4746
|
+
}
|
|
4747
|
+
),
|
|
4748
|
+
selected && /* @__PURE__ */ jsx11(StepEditor, { nodeId: selected, model, onModelChange: (m) => {
|
|
4514
4749
|
applyAndPush(m);
|
|
4515
4750
|
}, variant, isDark, t, acc }, selected)
|
|
4516
4751
|
] }),
|
|
4517
|
-
/* @__PURE__ */
|
|
4518
|
-
/* @__PURE__ */
|
|
4752
|
+
/* @__PURE__ */ jsxs11("div", { style: { padding: "4px 14px", fontSize: 11, color: t.textMuted, background: t.statusBg, borderTop: `1px solid ${t.ctrlsBorder}`, display: "flex", gap: 16 }, children: [
|
|
4753
|
+
/* @__PURE__ */ jsxs11("span", { children: [
|
|
4519
4754
|
model.nodes.length,
|
|
4520
4755
|
" ",
|
|
4521
4756
|
variantLabel.toLowerCase(),
|
|
4522
4757
|
"s"
|
|
4523
4758
|
] }),
|
|
4524
|
-
/* @__PURE__ */
|
|
4759
|
+
/* @__PURE__ */ jsxs11("span", { children: [
|
|
4525
4760
|
model.edges.length,
|
|
4526
4761
|
" connections"
|
|
4527
4762
|
] }),
|
|
4528
|
-
/* @__PURE__ */
|
|
4763
|
+
/* @__PURE__ */ jsxs11("span", { children: [
|
|
4529
4764
|
Math.round(transform.scale * 100),
|
|
4530
4765
|
"% zoom"
|
|
4531
4766
|
] }),
|
|
4532
|
-
/* @__PURE__ */
|
|
4533
|
-
selected && /* @__PURE__ */
|
|
4767
|
+
/* @__PURE__ */ jsx11("span", { style: { marginLeft: "auto" }, children: "Ctrl+Z undo \xB7 Ctrl+Y redo \xB7 Ctrl+0 fit \xB7 Alt+Arrow traverse" }),
|
|
4768
|
+
selected && /* @__PURE__ */ jsx11("span", { style: { color: acc.color }, children: model.nodes.find((n) => n.id === selected)?.label })
|
|
4534
4769
|
] })
|
|
4535
4770
|
] });
|
|
4536
4771
|
}
|