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.cjs
CHANGED
|
@@ -31,7 +31,7 @@ __export(ui_exports, {
|
|
|
31
31
|
module.exports = __toCommonJS(ui_exports);
|
|
32
32
|
|
|
33
33
|
// src/ui/DiagramEditor.tsx
|
|
34
|
-
var
|
|
34
|
+
var import_react19 = require("react");
|
|
35
35
|
|
|
36
36
|
// src/ui/Toolbar.tsx
|
|
37
37
|
var import_react2 = require("react");
|
|
@@ -407,6 +407,12 @@ var ACCENT = {
|
|
|
407
407
|
emeraldDarkLight: "rgba(16,185,129,0.12)",
|
|
408
408
|
emeraldDarkBorder: "rgba(16,185,129,0.3)"
|
|
409
409
|
};
|
|
410
|
+
function shadowColor(isDark) {
|
|
411
|
+
return isDark ? "rgba(0,0,0,0.55)" : "rgba(15,23,42,0.09)";
|
|
412
|
+
}
|
|
413
|
+
function arrowColor(isDark) {
|
|
414
|
+
return isDark ? "#64748b" : "#94a3b8";
|
|
415
|
+
}
|
|
410
416
|
function variantAccent(variant, isDark) {
|
|
411
417
|
if (variant === "question") {
|
|
412
418
|
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 };
|
|
@@ -839,18 +845,278 @@ function StepEditor({ nodeId, model, onModelChange, variant = "flowchart", isDar
|
|
|
839
845
|
}
|
|
840
846
|
|
|
841
847
|
// src/ui/SequenceEditor.tsx
|
|
842
|
-
var
|
|
848
|
+
var import_react10 = require("react");
|
|
849
|
+
|
|
850
|
+
// src/ui/SequenceCanvas.tsx
|
|
851
|
+
var import_react4 = require("react");
|
|
852
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
853
|
+
var HEADER_H = 64;
|
|
854
|
+
var HEADER_PAD = 24;
|
|
855
|
+
var ROW_H = 64;
|
|
856
|
+
var SIDE_PAD = 40;
|
|
857
|
+
var INDIGO = "#4f46e5";
|
|
858
|
+
var INDIGO_SOFT = "#eef2ff";
|
|
859
|
+
var STYLE_SEQ_GRAB = { cursor: "grab" };
|
|
860
|
+
var STYLE_SEQ_GRABBING = { cursor: "grabbing" };
|
|
861
|
+
var STYLE_SEQ_ACTOR_TEXT = { cursor: "pointer", userSelect: "none" };
|
|
862
|
+
var STYLE_SEQ_REMOVE_BTN = { cursor: "pointer" };
|
|
863
|
+
var STYLE_SEQ_REMOVE_ICON = { pointerEvents: "none", userSelect: "none" };
|
|
864
|
+
var STYLE_SEQ_DRAGGING = { opacity: 0.85 };
|
|
865
|
+
function estimateW(text, pxPerChar = 7) {
|
|
866
|
+
return text.length * pxPerChar;
|
|
867
|
+
}
|
|
868
|
+
function SequenceCanvas(props) {
|
|
869
|
+
const {
|
|
870
|
+
model,
|
|
871
|
+
actors,
|
|
872
|
+
messages,
|
|
873
|
+
t,
|
|
874
|
+
isDark,
|
|
875
|
+
colW,
|
|
876
|
+
totalW,
|
|
877
|
+
totalH,
|
|
878
|
+
actorX,
|
|
879
|
+
msgY,
|
|
880
|
+
selected,
|
|
881
|
+
editingId,
|
|
882
|
+
setEditingId,
|
|
883
|
+
drag,
|
|
884
|
+
onRowMouseDown,
|
|
885
|
+
renameActor,
|
|
886
|
+
removeActor,
|
|
887
|
+
svgRef
|
|
888
|
+
} = props;
|
|
889
|
+
const visualMessages = (0, import_react4.useMemo)(() => {
|
|
890
|
+
if (!drag?.active) return messages;
|
|
891
|
+
const idx = messages.findIndex((m) => m.id === drag.id);
|
|
892
|
+
if (idx < 0) return messages;
|
|
893
|
+
const next = messages.slice();
|
|
894
|
+
const [moved] = next.splice(idx, 1);
|
|
895
|
+
next.splice(drag.targetIdx, 0, moved);
|
|
896
|
+
return next;
|
|
897
|
+
}, [messages, drag]);
|
|
898
|
+
if (actors.length === 0 && messages.length === 0) {
|
|
899
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: {
|
|
900
|
+
position: "absolute",
|
|
901
|
+
inset: 0,
|
|
902
|
+
display: "flex",
|
|
903
|
+
flexDirection: "column",
|
|
904
|
+
alignItems: "center",
|
|
905
|
+
justifyContent: "center",
|
|
906
|
+
gap: 10,
|
|
907
|
+
color: t.textMuted,
|
|
908
|
+
pointerEvents: "none"
|
|
909
|
+
}, children: [
|
|
910
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: 36, opacity: 0.15, color: t.textPrimary }, children: "\u2194" }),
|
|
911
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { fontSize: 13, fontWeight: 500 }, children: [
|
|
912
|
+
"Click ",
|
|
913
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { style: { color: INDIGO }, children: "+ Actor" }),
|
|
914
|
+
" then ",
|
|
915
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { style: { color: INDIGO }, children: "+ Message" }),
|
|
916
|
+
" to start"
|
|
917
|
+
] })
|
|
918
|
+
] });
|
|
919
|
+
}
|
|
920
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
921
|
+
"svg",
|
|
922
|
+
{
|
|
923
|
+
ref: svgRef,
|
|
924
|
+
width: totalW,
|
|
925
|
+
height: totalH,
|
|
926
|
+
style: { display: "block", cursor: drag?.active ? "grabbing" : "default", userSelect: "none" },
|
|
927
|
+
children: [
|
|
928
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("defs", { children: [
|
|
929
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("pattern", { id: "seqdots", x: "0", y: "0", width: "24", height: "24", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("circle", { cx: 12, cy: 12, r: 1.1, fill: t.dot }) }),
|
|
930
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("filter", { id: "seqShadow", x: "-20%", y: "-20%", width: "140%", height: "140%", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("feDropShadow", { dx: 0, dy: 3, stdDeviation: 5, floodColor: shadowColor(isDark) }) }),
|
|
931
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("marker", { id: "seqArrow", markerWidth: 9, markerHeight: 7, refX: 8.5, refY: 3.5, orient: "auto", markerUnits: "strokeWidth", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: t.arrow }) })
|
|
932
|
+
] }),
|
|
933
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { width: totalW, height: totalH, fill: "url(#seqdots)" }),
|
|
934
|
+
actors.map((name) => {
|
|
935
|
+
const x = actorX(name);
|
|
936
|
+
const top = HEADER_PAD + HEADER_H;
|
|
937
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
938
|
+
"line",
|
|
939
|
+
{
|
|
940
|
+
x1: x,
|
|
941
|
+
x2: x,
|
|
942
|
+
y1: top + 4,
|
|
943
|
+
y2: totalH - 24,
|
|
944
|
+
stroke: t.lifeline,
|
|
945
|
+
strokeWidth: 1.25,
|
|
946
|
+
strokeDasharray: "5 5"
|
|
947
|
+
},
|
|
948
|
+
`life-${name}`
|
|
949
|
+
);
|
|
950
|
+
}),
|
|
951
|
+
visualMessages.map((msg, idx) => {
|
|
952
|
+
const y = msgY(idx);
|
|
953
|
+
const fromX = actorX(msg.from);
|
|
954
|
+
const toX = actorX(msg.to);
|
|
955
|
+
const selectedHere = selected === msg.id;
|
|
956
|
+
const isDragging = drag?.active && drag.id === msg.id;
|
|
957
|
+
const isSelf = msg.from === msg.to;
|
|
958
|
+
const stroke = selectedHere ? INDIGO : t.arrow;
|
|
959
|
+
const dash = msg.style === "dashed" ? "6,4" : void 0;
|
|
960
|
+
const cursorStyle = drag?.active ? STYLE_SEQ_GRABBING : STYLE_SEQ_GRAB;
|
|
961
|
+
const groupStyle = isDragging ? { ...cursorStyle, ...STYLE_SEQ_DRAGGING } : cursorStyle;
|
|
962
|
+
if (isSelf) {
|
|
963
|
+
const startX = fromX;
|
|
964
|
+
const loopW = 36;
|
|
965
|
+
const loopY = y - 6;
|
|
966
|
+
const d = `M ${startX} ${loopY} C ${startX + loopW} ${loopY}, ${startX + loopW} ${loopY + 24}, ${startX} ${loopY + 24}`;
|
|
967
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { onMouseDown: (e) => onRowMouseDown(e, msg.id), style: groupStyle, children: [
|
|
968
|
+
(selectedHere || isDragging) && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
969
|
+
"rect",
|
|
970
|
+
{
|
|
971
|
+
x: SIDE_PAD - 8,
|
|
972
|
+
y: y - 22,
|
|
973
|
+
width: totalW - (SIDE_PAD - 8) * 2,
|
|
974
|
+
height: ROW_H - 12,
|
|
975
|
+
rx: 10,
|
|
976
|
+
fill: INDIGO_SOFT,
|
|
977
|
+
opacity: isDark ? 0.18 : 0.6
|
|
978
|
+
}
|
|
979
|
+
),
|
|
980
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d, fill: "none", stroke, strokeWidth: 1.5, strokeDasharray: dash, markerEnd: "url(#seqArrow)" }),
|
|
981
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("text", { x: startX + loopW + 8, y: loopY + 16, fontSize: 11, fill: selectedHere ? INDIGO : t.textPrimary, fontWeight: 500, children: msg.label })
|
|
982
|
+
] }, msg.id);
|
|
983
|
+
}
|
|
984
|
+
const labelX = (fromX + toX) / 2;
|
|
985
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { onMouseDown: (e) => onRowMouseDown(e, msg.id), style: groupStyle, children: [
|
|
986
|
+
(selectedHere || isDragging) && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
987
|
+
"rect",
|
|
988
|
+
{
|
|
989
|
+
x: SIDE_PAD - 8,
|
|
990
|
+
y: y - 22,
|
|
991
|
+
width: totalW - (SIDE_PAD - 8) * 2,
|
|
992
|
+
height: ROW_H - 12,
|
|
993
|
+
rx: 10,
|
|
994
|
+
fill: INDIGO_SOFT,
|
|
995
|
+
opacity: isDark ? 0.18 : 0.6
|
|
996
|
+
}
|
|
997
|
+
),
|
|
998
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: fromX, y1: y, x2: toX, y2: y, stroke, strokeWidth: 1.5, strokeDasharray: dash, markerEnd: "url(#seqArrow)" }),
|
|
999
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1000
|
+
"rect",
|
|
1001
|
+
{
|
|
1002
|
+
x: labelX - estimateW(msg.label) / 2 - 6,
|
|
1003
|
+
y: y - 18,
|
|
1004
|
+
width: estimateW(msg.label) + 12,
|
|
1005
|
+
height: 18,
|
|
1006
|
+
rx: 6,
|
|
1007
|
+
fill: t.canvas,
|
|
1008
|
+
stroke: selectedHere ? INDIGO : t.cardBorder,
|
|
1009
|
+
strokeWidth: selectedHere ? 1.25 : 1
|
|
1010
|
+
}
|
|
1011
|
+
),
|
|
1012
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("text", { x: labelX, y: y - 5, textAnchor: "middle", fontSize: 11, fill: selectedHere ? INDIGO : t.textPrimary, fontWeight: 500, children: msg.label })
|
|
1013
|
+
] }, msg.id);
|
|
1014
|
+
}),
|
|
1015
|
+
actors.map((name) => {
|
|
1016
|
+
const x = actorX(name);
|
|
1017
|
+
const w = colW - 24;
|
|
1018
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { children: [
|
|
1019
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1020
|
+
"rect",
|
|
1021
|
+
{
|
|
1022
|
+
x: x - w / 2,
|
|
1023
|
+
y: HEADER_PAD,
|
|
1024
|
+
width: w,
|
|
1025
|
+
height: HEADER_H,
|
|
1026
|
+
rx: 12,
|
|
1027
|
+
fill: t.actorFill,
|
|
1028
|
+
stroke: t.actorStroke,
|
|
1029
|
+
strokeWidth: 1.25,
|
|
1030
|
+
filter: "url(#seqShadow)"
|
|
1031
|
+
}
|
|
1032
|
+
),
|
|
1033
|
+
editingId === name ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("foreignObject", { x: x - w / 2 + 8, y: HEADER_PAD + 16, width: w - 16, height: 32, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1034
|
+
"input",
|
|
1035
|
+
{
|
|
1036
|
+
autoFocus: true,
|
|
1037
|
+
defaultValue: name,
|
|
1038
|
+
onBlur: (e) => {
|
|
1039
|
+
renameActor(name, e.currentTarget.value.trim());
|
|
1040
|
+
setEditingId(null);
|
|
1041
|
+
},
|
|
1042
|
+
onKeyDown: (e) => {
|
|
1043
|
+
if (e.key === "Enter") {
|
|
1044
|
+
renameActor(name, e.target.value.trim());
|
|
1045
|
+
setEditingId(null);
|
|
1046
|
+
}
|
|
1047
|
+
if (e.key === "Escape") setEditingId(null);
|
|
1048
|
+
},
|
|
1049
|
+
style: {
|
|
1050
|
+
width: "100%",
|
|
1051
|
+
height: "100%",
|
|
1052
|
+
border: "none",
|
|
1053
|
+
borderRadius: 6,
|
|
1054
|
+
outline: `2px solid ${INDIGO}`,
|
|
1055
|
+
textAlign: "center",
|
|
1056
|
+
fontSize: 13,
|
|
1057
|
+
fontWeight: 600,
|
|
1058
|
+
background: t.inputBg,
|
|
1059
|
+
color: t.inputText,
|
|
1060
|
+
boxSizing: "border-box",
|
|
1061
|
+
padding: "0 6px",
|
|
1062
|
+
fontFamily: "inherit"
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1066
|
+
"text",
|
|
1067
|
+
{
|
|
1068
|
+
x,
|
|
1069
|
+
y: HEADER_PAD + HEADER_H / 2 + 4,
|
|
1070
|
+
textAnchor: "middle",
|
|
1071
|
+
fontSize: 13,
|
|
1072
|
+
fontWeight: 700,
|
|
1073
|
+
fill: t.actorText,
|
|
1074
|
+
style: STYLE_SEQ_ACTOR_TEXT,
|
|
1075
|
+
onDoubleClick: () => setEditingId(name),
|
|
1076
|
+
children: name
|
|
1077
|
+
}
|
|
1078
|
+
),
|
|
1079
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1080
|
+
"circle",
|
|
1081
|
+
{
|
|
1082
|
+
cx: x + w / 2 - 12,
|
|
1083
|
+
cy: HEADER_PAD + 14,
|
|
1084
|
+
r: 9,
|
|
1085
|
+
fill: "transparent",
|
|
1086
|
+
style: STYLE_SEQ_REMOVE_BTN,
|
|
1087
|
+
onClick: () => removeActor(name),
|
|
1088
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("title", { children: "Remove actor" })
|
|
1089
|
+
}
|
|
1090
|
+
),
|
|
1091
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1092
|
+
"text",
|
|
1093
|
+
{
|
|
1094
|
+
x: x + w / 2 - 12,
|
|
1095
|
+
y: HEADER_PAD + 18,
|
|
1096
|
+
textAnchor: "middle",
|
|
1097
|
+
fontSize: 12,
|
|
1098
|
+
fill: t.textMuted,
|
|
1099
|
+
style: STYLE_SEQ_REMOVE_ICON,
|
|
1100
|
+
children: "\xD7"
|
|
1101
|
+
}
|
|
1102
|
+
)
|
|
1103
|
+
] }, `hdr-${name}`);
|
|
1104
|
+
})
|
|
1105
|
+
]
|
|
1106
|
+
}
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
843
1109
|
|
|
844
1110
|
// src/ui/hooks/useEditorTheme.ts
|
|
845
|
-
var
|
|
1111
|
+
var import_react6 = require("react");
|
|
846
1112
|
|
|
847
1113
|
// src/ui/hooks/useSystemTheme.ts
|
|
848
|
-
var
|
|
1114
|
+
var import_react5 = require("react");
|
|
849
1115
|
function useIsDark(theme) {
|
|
850
|
-
const [sysDark, setSysDark] = (0,
|
|
1116
|
+
const [sysDark, setSysDark] = (0, import_react5.useState)(
|
|
851
1117
|
() => typeof window !== "undefined" ? window.matchMedia("(prefers-color-scheme: dark)").matches : false
|
|
852
1118
|
);
|
|
853
|
-
(0,
|
|
1119
|
+
(0, import_react5.useEffect)(() => {
|
|
854
1120
|
if (theme !== "auto" || typeof window === "undefined") return;
|
|
855
1121
|
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
856
1122
|
const handler = (e) => setSysDark(e.matches);
|
|
@@ -860,10 +1126,10 @@ function useIsDark(theme) {
|
|
|
860
1126
|
return theme === "dark" || theme === "auto" && sysDark;
|
|
861
1127
|
}
|
|
862
1128
|
function useIsCoarsePointer() {
|
|
863
|
-
const [coarse, setCoarse] = (0,
|
|
1129
|
+
const [coarse, setCoarse] = (0, import_react5.useState)(
|
|
864
1130
|
() => typeof window !== "undefined" ? window.matchMedia("(pointer: coarse)").matches : false
|
|
865
1131
|
);
|
|
866
|
-
(0,
|
|
1132
|
+
(0, import_react5.useEffect)(() => {
|
|
867
1133
|
if (typeof window === "undefined") return;
|
|
868
1134
|
const mq = window.matchMedia("(pointer: coarse)");
|
|
869
1135
|
const handler = (e) => setCoarse(e.matches);
|
|
@@ -873,10 +1139,10 @@ function useIsCoarsePointer() {
|
|
|
873
1139
|
return coarse;
|
|
874
1140
|
}
|
|
875
1141
|
function usePrefersReducedMotion() {
|
|
876
|
-
const [reduced, setReduced] = (0,
|
|
1142
|
+
const [reduced, setReduced] = (0, import_react5.useState)(
|
|
877
1143
|
() => typeof window !== "undefined" ? window.matchMedia("(prefers-reduced-motion: reduce)").matches : false
|
|
878
1144
|
);
|
|
879
|
-
(0,
|
|
1145
|
+
(0, import_react5.useEffect)(() => {
|
|
880
1146
|
if (typeof window === "undefined") return;
|
|
881
1147
|
const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
882
1148
|
const handler = (e) => setReduced(e.matches);
|
|
@@ -889,7 +1155,7 @@ function usePrefersReducedMotion() {
|
|
|
889
1155
|
// src/ui/hooks/useEditorTheme.ts
|
|
890
1156
|
function useEditorTheme(theme, overrides, palettes) {
|
|
891
1157
|
const isDark = useIsDark(theme);
|
|
892
|
-
const t = (0,
|
|
1158
|
+
const t = (0, import_react6.useMemo)(
|
|
893
1159
|
() => ({ ...isDark ? palettes.dark : palettes.light, ...overrides ?? {} }),
|
|
894
1160
|
// palettes is a stable module-level constant in every caller, so it is
|
|
895
1161
|
// deliberately omitted from the dep array to keep the memo key tight.
|
|
@@ -900,7 +1166,7 @@ function useEditorTheme(theme, overrides, palettes) {
|
|
|
900
1166
|
}
|
|
901
1167
|
|
|
902
1168
|
// src/ui/hooks/useExporters.ts
|
|
903
|
-
var
|
|
1169
|
+
var import_react7 = require("react");
|
|
904
1170
|
|
|
905
1171
|
// src/exporters/mermaid.ts
|
|
906
1172
|
var SHAPE_OPEN = {
|
|
@@ -1287,7 +1553,7 @@ async function toPNG(model) {
|
|
|
1287
1553
|
|
|
1288
1554
|
// src/ui/hooks/useExporters.ts
|
|
1289
1555
|
function useExporters(model, onExport, filename = "diagram") {
|
|
1290
|
-
return (0,
|
|
1556
|
+
return (0, import_react7.useCallback)(async (format) => {
|
|
1291
1557
|
let content;
|
|
1292
1558
|
switch (format) {
|
|
1293
1559
|
case "mermaid":
|
|
@@ -1322,7 +1588,7 @@ function useExporters(model, onExport, filename = "diagram") {
|
|
|
1322
1588
|
}
|
|
1323
1589
|
|
|
1324
1590
|
// src/ui/hooks/useImporter.ts
|
|
1325
|
-
var
|
|
1591
|
+
var import_react8 = require("react");
|
|
1326
1592
|
|
|
1327
1593
|
// src/core/model.ts
|
|
1328
1594
|
var Model = class _Model {
|
|
@@ -1617,7 +1883,7 @@ function fromJSON(json) {
|
|
|
1617
1883
|
// src/ui/hooks/useImporter.ts
|
|
1618
1884
|
function useImporter(applyAndPush, options = {}) {
|
|
1619
1885
|
const { expectedType, transform } = options;
|
|
1620
|
-
return (0,
|
|
1886
|
+
return (0, import_react8.useCallback)((text) => {
|
|
1621
1887
|
try {
|
|
1622
1888
|
const parsed = text.trim().startsWith("{") ? fromJSON(text).toJSON() : fromMermaid(text).toJSON();
|
|
1623
1889
|
if (expectedType && parsed.type !== expectedType) {
|
|
@@ -1727,10 +1993,33 @@ function cloneModel(m) {
|
|
|
1727
1993
|
};
|
|
1728
1994
|
}
|
|
1729
1995
|
|
|
1996
|
+
// src/ui/hooks/useEditorKeyboard.ts
|
|
1997
|
+
var import_react9 = require("react");
|
|
1998
|
+
var isInput = (e) => {
|
|
1999
|
+
const tgt = e.target;
|
|
2000
|
+
return !!(tgt && (tgt.tagName === "INPUT" || tgt.tagName === "TEXTAREA" || tgt.isContentEditable));
|
|
2001
|
+
};
|
|
2002
|
+
function useEditorKeyboard(commands, deps) {
|
|
2003
|
+
(0, import_react9.useEffect)(() => {
|
|
2004
|
+
const onKey = (e) => {
|
|
2005
|
+
if (isInput(e)) return;
|
|
2006
|
+
for (const cmd of commands) {
|
|
2007
|
+
if (cmd.match(e)) {
|
|
2008
|
+
const handled = cmd.run(e);
|
|
2009
|
+
if (handled) e.preventDefault();
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
};
|
|
2014
|
+
window.addEventListener("keydown", onKey);
|
|
2015
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
2016
|
+
}, deps);
|
|
2017
|
+
}
|
|
2018
|
+
|
|
1730
2019
|
// src/ui/SequenceEditor.tsx
|
|
1731
|
-
var
|
|
1732
|
-
var
|
|
1733
|
-
var
|
|
2020
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
2021
|
+
var INDIGO2 = "#4f46e5";
|
|
2022
|
+
var INDIGO_SOFT2 = "#eef2ff";
|
|
1734
2023
|
var lightTheme2 = {
|
|
1735
2024
|
canvas: "#fafbfc",
|
|
1736
2025
|
dot: "#dbe3ee",
|
|
@@ -1773,11 +2062,11 @@ var darkTheme2 = {
|
|
|
1773
2062
|
actorStroke: "rgba(99,102,241,0.45)",
|
|
1774
2063
|
actorText: "#a5b4fc"
|
|
1775
2064
|
};
|
|
1776
|
-
var
|
|
1777
|
-
var
|
|
2065
|
+
var HEADER_H2 = 64;
|
|
2066
|
+
var HEADER_PAD2 = 24;
|
|
1778
2067
|
var COL_MIN = 160;
|
|
1779
|
-
var
|
|
1780
|
-
var
|
|
2068
|
+
var ROW_H2 = 64;
|
|
2069
|
+
var SIDE_PAD2 = 40;
|
|
1781
2070
|
var DRAG_THRESHOLD = 5;
|
|
1782
2071
|
function ensureSequenceModel(m) {
|
|
1783
2072
|
if (m && m.type === "sequence") {
|
|
@@ -1795,49 +2084,49 @@ function SequenceEditor({
|
|
|
1795
2084
|
theme = "auto",
|
|
1796
2085
|
themeOverrides
|
|
1797
2086
|
}) {
|
|
1798
|
-
const [model, setModel] = (0,
|
|
1799
|
-
const [selected, setSelected] = (0,
|
|
1800
|
-
const [drag, setDrag] = (0,
|
|
1801
|
-
const [editingId, setEditingId] = (0,
|
|
1802
|
-
const [editLabel, setEditLabel] = (0,
|
|
1803
|
-
const historyRef = (0,
|
|
1804
|
-
const historyIdxRef = (0,
|
|
1805
|
-
const svgRef = (0,
|
|
2087
|
+
const [model, setModel] = (0, import_react10.useState)(() => ensureSequenceModel(initialModel));
|
|
2088
|
+
const [selected, setSelected] = (0, import_react10.useState)(null);
|
|
2089
|
+
const [drag, setDrag] = (0, import_react10.useState)(null);
|
|
2090
|
+
const [editingId, setEditingId] = (0, import_react10.useState)(null);
|
|
2091
|
+
const [editLabel, setEditLabel] = (0, import_react10.useState)("");
|
|
2092
|
+
const historyRef = (0, import_react10.useRef)([ensureSequenceModel(initialModel)]);
|
|
2093
|
+
const historyIdxRef = (0, import_react10.useRef)(0);
|
|
2094
|
+
const svgRef = (0, import_react10.useRef)(null);
|
|
1806
2095
|
const { t, isDark } = useEditorTheme(theme, themeOverrides, { light: lightTheme2, dark: darkTheme2 });
|
|
1807
2096
|
const actors = model.actors ?? [];
|
|
1808
2097
|
const messages = model.messages ?? [];
|
|
1809
|
-
const colW = (0,
|
|
2098
|
+
const colW = (0, import_react10.useMemo)(() => {
|
|
1810
2099
|
const longest = actors.reduce((m, a) => Math.max(m, a.length), 6);
|
|
1811
2100
|
return Math.max(COL_MIN, longest * 9 + 40);
|
|
1812
2101
|
}, [actors]);
|
|
1813
|
-
const totalW =
|
|
1814
|
-
const totalH =
|
|
2102
|
+
const totalW = SIDE_PAD2 * 2 + Math.max(1, actors.length) * colW;
|
|
2103
|
+
const totalH = HEADER_PAD2 + HEADER_H2 + 32 + messages.length * ROW_H2 + 48;
|
|
1815
2104
|
const actorX = (name) => {
|
|
1816
2105
|
const idx = actors.indexOf(name);
|
|
1817
|
-
if (idx < 0) return
|
|
1818
|
-
return
|
|
2106
|
+
if (idx < 0) return SIDE_PAD2 + colW / 2;
|
|
2107
|
+
return SIDE_PAD2 + idx * colW + colW / 2;
|
|
1819
2108
|
};
|
|
1820
|
-
const msgY = (idx) =>
|
|
1821
|
-
const pushHistory = (0,
|
|
2109
|
+
const msgY = (idx) => HEADER_PAD2 + HEADER_H2 + 40 + idx * ROW_H2;
|
|
2110
|
+
const pushHistory = (0, import_react10.useCallback)((m) => {
|
|
1822
2111
|
const stack = historyRef.current.slice(0, historyIdxRef.current + 1);
|
|
1823
2112
|
stack.push(m);
|
|
1824
2113
|
if (stack.length > 80) stack.shift();
|
|
1825
2114
|
historyRef.current = stack;
|
|
1826
2115
|
historyIdxRef.current = stack.length - 1;
|
|
1827
2116
|
}, []);
|
|
1828
|
-
const applyAndPush = (0,
|
|
2117
|
+
const applyAndPush = (0, import_react10.useCallback)((m) => {
|
|
1829
2118
|
setModel(m);
|
|
1830
2119
|
onChange?.(m);
|
|
1831
2120
|
pushHistory(m);
|
|
1832
2121
|
}, [onChange, pushHistory]);
|
|
1833
|
-
const undo = (0,
|
|
2122
|
+
const undo = (0, import_react10.useCallback)(() => {
|
|
1834
2123
|
if (historyIdxRef.current <= 0) return;
|
|
1835
2124
|
historyIdxRef.current--;
|
|
1836
2125
|
const m = historyRef.current[historyIdxRef.current];
|
|
1837
2126
|
setModel(m);
|
|
1838
2127
|
onChange?.(m);
|
|
1839
2128
|
}, [onChange]);
|
|
1840
|
-
const redo = (0,
|
|
2129
|
+
const redo = (0, import_react10.useCallback)(() => {
|
|
1841
2130
|
if (historyIdxRef.current >= historyRef.current.length - 1) return;
|
|
1842
2131
|
historyIdxRef.current++;
|
|
1843
2132
|
const m = historyRef.current[historyIdxRef.current];
|
|
@@ -1895,7 +2184,7 @@ function SequenceEditor({
|
|
|
1895
2184
|
applyAndPush({ ...model, messages: messages.filter((m) => m.id !== id) });
|
|
1896
2185
|
if (selected === id) setSelected(null);
|
|
1897
2186
|
};
|
|
1898
|
-
const reorderMessage = (0,
|
|
2187
|
+
const reorderMessage = (0, import_react10.useCallback)((id, toIdx) => {
|
|
1899
2188
|
const fromIdx = messages.findIndex((m) => m.id === id);
|
|
1900
2189
|
if (fromIdx < 0 || toIdx === fromIdx) return;
|
|
1901
2190
|
const next = messages.slice();
|
|
@@ -1903,34 +2192,26 @@ function SequenceEditor({
|
|
|
1903
2192
|
next.splice(toIdx, 0, moved);
|
|
1904
2193
|
applyAndPush({ ...model, messages: next });
|
|
1905
2194
|
}, [messages, model, applyAndPush]);
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
if ((e.key === "Delete" || e.key === "Backspace") && selected) {
|
|
1927
|
-
e.preventDefault();
|
|
1928
|
-
removeMessage(selected);
|
|
1929
|
-
}
|
|
1930
|
-
};
|
|
1931
|
-
window.addEventListener("keydown", onKey);
|
|
1932
|
-
return () => window.removeEventListener("keydown", onKey);
|
|
1933
|
-
}, [undo, redo, selected]);
|
|
2195
|
+
const keyCommands = [
|
|
2196
|
+
{ match: (e) => (e.ctrlKey || e.metaKey) && e.key === "z", run: () => {
|
|
2197
|
+
undo();
|
|
2198
|
+
return true;
|
|
2199
|
+
} },
|
|
2200
|
+
{ match: (e) => (e.ctrlKey || e.metaKey) && (e.key === "y" || e.shiftKey && e.key === "z"), run: () => {
|
|
2201
|
+
redo();
|
|
2202
|
+
return true;
|
|
2203
|
+
} },
|
|
2204
|
+
{ match: (e) => e.key === "Escape", run: () => {
|
|
2205
|
+
setSelected(null);
|
|
2206
|
+
setEditingId(null);
|
|
2207
|
+
return true;
|
|
2208
|
+
} },
|
|
2209
|
+
{ match: (e) => (e.key === "Delete" || e.key === "Backspace") && !!selected, run: () => {
|
|
2210
|
+
removeMessage(selected);
|
|
2211
|
+
return true;
|
|
2212
|
+
} }
|
|
2213
|
+
];
|
|
2214
|
+
useEditorKeyboard(keyCommands, [undo, redo, selected]);
|
|
1934
2215
|
const handleExport = useExporters(model, onExport, "sequence");
|
|
1935
2216
|
const handleImport = useImporter(applyAndPush, {
|
|
1936
2217
|
expectedType: "sequence",
|
|
@@ -1945,9 +2226,9 @@ function SequenceEditor({
|
|
|
1945
2226
|
setSelected(id);
|
|
1946
2227
|
setDrag({ id, startY: e.clientY, originalIdx: idx, targetIdx: idx, active: false });
|
|
1947
2228
|
};
|
|
1948
|
-
(0,
|
|
2229
|
+
(0, import_react10.useEffect)(() => {
|
|
1949
2230
|
if (!drag) return;
|
|
1950
|
-
const baseY =
|
|
2231
|
+
const baseY = HEADER_PAD2 + HEADER_H2 + 40;
|
|
1951
2232
|
const onMove = (ev) => {
|
|
1952
2233
|
const dy = ev.clientY - drag.startY;
|
|
1953
2234
|
if (!drag.active && Math.abs(dy) < DRAG_THRESHOLD) return;
|
|
@@ -1955,7 +2236,7 @@ function SequenceEditor({
|
|
|
1955
2236
|
if (!svg) return;
|
|
1956
2237
|
const rect = svg.getBoundingClientRect();
|
|
1957
2238
|
const yInSvg = ev.clientY - rect.top;
|
|
1958
|
-
const raw = Math.floor((yInSvg - baseY +
|
|
2239
|
+
const raw = Math.floor((yInSvg - baseY + ROW_H2 / 2) / ROW_H2);
|
|
1959
2240
|
const next = Math.max(0, Math.min(messages.length - 1, raw));
|
|
1960
2241
|
if (next === drag.targetIdx && drag.active) return;
|
|
1961
2242
|
setDrag({ ...drag, active: true, targetIdx: next });
|
|
@@ -1973,17 +2254,8 @@ function SequenceEditor({
|
|
|
1973
2254
|
window.removeEventListener("mouseup", onUp);
|
|
1974
2255
|
};
|
|
1975
2256
|
}, [drag, messages.length, reorderMessage]);
|
|
1976
|
-
const visualMessages = (0, import_react8.useMemo)(() => {
|
|
1977
|
-
if (!drag?.active) return messages;
|
|
1978
|
-
const idx = messages.findIndex((m) => m.id === drag.id);
|
|
1979
|
-
if (idx < 0) return messages;
|
|
1980
|
-
const next = messages.slice();
|
|
1981
|
-
const [moved] = next.splice(idx, 1);
|
|
1982
|
-
next.splice(drag.targetIdx, 0, moved);
|
|
1983
|
-
return next;
|
|
1984
|
-
}, [messages, drag]);
|
|
1985
2257
|
const selectedMsg = selected ? messages.find((m) => m.id === selected) : null;
|
|
1986
|
-
return /* @__PURE__ */ (0,
|
|
2258
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: {
|
|
1987
2259
|
display: "flex",
|
|
1988
2260
|
flexDirection: "column",
|
|
1989
2261
|
height,
|
|
@@ -1991,8 +2263,8 @@ function SequenceEditor({
|
|
|
1991
2263
|
fontFamily: "ui-sans-serif,system-ui,sans-serif",
|
|
1992
2264
|
background: t.ctrlsBg
|
|
1993
2265
|
}, children: [
|
|
1994
|
-
/* @__PURE__ */ (0,
|
|
1995
|
-
/* @__PURE__ */ (0,
|
|
2266
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Toolbar, { onExport: handleExport, onImport: allowImport ? handleImport : void 0, allowedExports, allowImport }),
|
|
2267
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: {
|
|
1996
2268
|
display: "flex",
|
|
1997
2269
|
gap: 8,
|
|
1998
2270
|
padding: "7px 14px",
|
|
@@ -2001,12 +2273,12 @@ function SequenceEditor({
|
|
|
2001
2273
|
alignItems: "center",
|
|
2002
2274
|
flexWrap: "wrap"
|
|
2003
2275
|
}, children: [
|
|
2004
|
-
/* @__PURE__ */ (0,
|
|
2005
|
-
/* @__PURE__ */ (0,
|
|
2006
|
-
/* @__PURE__ */ (0,
|
|
2007
|
-
/* @__PURE__ */ (0,
|
|
2008
|
-
/* @__PURE__ */ (0,
|
|
2009
|
-
/* @__PURE__ */ (0,
|
|
2276
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { onClick: addActor, style: primaryBtn(), children: "+ Actor" }),
|
|
2277
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { onClick: addMessage, style: primaryBtn(), children: "+ Message" }),
|
|
2278
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { width: 1, height: 18, background: t.ctrlsBorder, margin: "0 4px" } }),
|
|
2279
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { onClick: undo, style: ghostBtn2(t), title: "Undo (Ctrl+Z)", children: "\u21B6" }),
|
|
2280
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { onClick: redo, style: ghostBtn2(t), title: "Redo (Ctrl+Y)", children: "\u21B7" }),
|
|
2281
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { style: { marginLeft: "auto", fontSize: 11, color: t.textMuted }, children: [
|
|
2010
2282
|
actors.length,
|
|
2011
2283
|
" actor",
|
|
2012
2284
|
actors.length === 1 ? "" : "s",
|
|
@@ -2017,215 +2289,31 @@ function SequenceEditor({
|
|
|
2017
2289
|
" \xB7 drag a row to reorder"
|
|
2018
2290
|
] })
|
|
2019
2291
|
] }),
|
|
2020
|
-
/* @__PURE__ */ (0,
|
|
2021
|
-
/* @__PURE__ */ (0,
|
|
2022
|
-
|
|
2023
|
-
inset: 0,
|
|
2024
|
-
display: "flex",
|
|
2025
|
-
flexDirection: "column",
|
|
2026
|
-
alignItems: "center",
|
|
2027
|
-
justifyContent: "center",
|
|
2028
|
-
gap: 10,
|
|
2029
|
-
color: t.textMuted,
|
|
2030
|
-
pointerEvents: "none"
|
|
2031
|
-
}, children: [
|
|
2032
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: 36, opacity: 0.15, color: t.textPrimary }, children: "\u2194" }),
|
|
2033
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { fontSize: 13, fontWeight: 500 }, children: [
|
|
2034
|
-
"Click ",
|
|
2035
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { style: { color: INDIGO }, children: "+ Actor" }),
|
|
2036
|
-
" then ",
|
|
2037
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { style: { color: INDIGO }, children: "+ Message" }),
|
|
2038
|
-
" to start"
|
|
2039
|
-
] })
|
|
2040
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
2041
|
-
"svg",
|
|
2292
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
|
|
2293
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { flex: 1, overflow: "auto", background: t.canvas, position: "relative" }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2294
|
+
SequenceCanvas,
|
|
2042
2295
|
{
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
x2: x,
|
|
2062
|
-
y1: top + 4,
|
|
2063
|
-
y2: totalH - 24,
|
|
2064
|
-
stroke: t.lifeline,
|
|
2065
|
-
strokeWidth: 1.25,
|
|
2066
|
-
strokeDasharray: "5 5"
|
|
2067
|
-
},
|
|
2068
|
-
`life-${name}`
|
|
2069
|
-
);
|
|
2070
|
-
}),
|
|
2071
|
-
visualMessages.map((msg, idx) => {
|
|
2072
|
-
const y = msgY(idx);
|
|
2073
|
-
const fromX = actorX(msg.from);
|
|
2074
|
-
const toX = actorX(msg.to);
|
|
2075
|
-
const selectedHere = selected === msg.id;
|
|
2076
|
-
const isDragging = drag?.active && drag.id === msg.id;
|
|
2077
|
-
const isSelf = msg.from === msg.to;
|
|
2078
|
-
const stroke = selectedHere ? INDIGO : t.arrow;
|
|
2079
|
-
const dash = msg.style === "dashed" ? "6,4" : void 0;
|
|
2080
|
-
const cursor = drag?.active ? "grabbing" : "grab";
|
|
2081
|
-
const groupOpacity = isDragging ? 0.85 : 1;
|
|
2082
|
-
if (isSelf) {
|
|
2083
|
-
const startX = fromX;
|
|
2084
|
-
const loopW = 36;
|
|
2085
|
-
const loopY = y - 6;
|
|
2086
|
-
const d = `M ${startX} ${loopY} C ${startX + loopW} ${loopY}, ${startX + loopW} ${loopY + 24}, ${startX} ${loopY + 24}`;
|
|
2087
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { onMouseDown: (e) => onRowMouseDown(e, msg.id), style: { cursor, opacity: groupOpacity }, children: [
|
|
2088
|
-
(selectedHere || isDragging) && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2089
|
-
"rect",
|
|
2090
|
-
{
|
|
2091
|
-
x: SIDE_PAD - 8,
|
|
2092
|
-
y: y - 22,
|
|
2093
|
-
width: totalW - (SIDE_PAD - 8) * 2,
|
|
2094
|
-
height: ROW_H - 12,
|
|
2095
|
-
rx: 10,
|
|
2096
|
-
fill: INDIGO_SOFT,
|
|
2097
|
-
opacity: isDark ? 0.18 : 0.6
|
|
2098
|
-
}
|
|
2099
|
-
),
|
|
2100
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d, fill: "none", stroke, strokeWidth: 1.5, strokeDasharray: dash, markerEnd: "url(#seqArrow)" }),
|
|
2101
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("text", { x: startX + loopW + 8, y: loopY + 16, fontSize: 11, fill: selectedHere ? INDIGO : t.textPrimary, fontWeight: 500, children: msg.label })
|
|
2102
|
-
] }, msg.id);
|
|
2103
|
-
}
|
|
2104
|
-
const labelX = (fromX + toX) / 2;
|
|
2105
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { onMouseDown: (e) => onRowMouseDown(e, msg.id), style: { cursor, opacity: groupOpacity }, children: [
|
|
2106
|
-
(selectedHere || isDragging) && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2107
|
-
"rect",
|
|
2108
|
-
{
|
|
2109
|
-
x: SIDE_PAD - 8,
|
|
2110
|
-
y: y - 22,
|
|
2111
|
-
width: totalW - (SIDE_PAD - 8) * 2,
|
|
2112
|
-
height: ROW_H - 12,
|
|
2113
|
-
rx: 10,
|
|
2114
|
-
fill: INDIGO_SOFT,
|
|
2115
|
-
opacity: isDark ? 0.18 : 0.6
|
|
2116
|
-
}
|
|
2117
|
-
),
|
|
2118
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: fromX, y1: y, x2: toX, y2: y, stroke, strokeWidth: 1.5, strokeDasharray: dash, markerEnd: "url(#seqArrow)" }),
|
|
2119
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2120
|
-
"rect",
|
|
2121
|
-
{
|
|
2122
|
-
x: labelX - estimateW(msg.label) / 2 - 6,
|
|
2123
|
-
y: y - 18,
|
|
2124
|
-
width: estimateW(msg.label) + 12,
|
|
2125
|
-
height: 18,
|
|
2126
|
-
rx: 6,
|
|
2127
|
-
fill: t.canvas,
|
|
2128
|
-
stroke: selectedHere ? INDIGO : t.cardBorder,
|
|
2129
|
-
strokeWidth: selectedHere ? 1.25 : 1
|
|
2130
|
-
}
|
|
2131
|
-
),
|
|
2132
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("text", { x: labelX, y: y - 5, textAnchor: "middle", fontSize: 11, fill: selectedHere ? INDIGO : t.textPrimary, fontWeight: 500, children: msg.label })
|
|
2133
|
-
] }, msg.id);
|
|
2134
|
-
}),
|
|
2135
|
-
actors.map((name) => {
|
|
2136
|
-
const x = actorX(name);
|
|
2137
|
-
const w = colW - 24;
|
|
2138
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("g", { children: [
|
|
2139
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2140
|
-
"rect",
|
|
2141
|
-
{
|
|
2142
|
-
x: x - w / 2,
|
|
2143
|
-
y: HEADER_PAD,
|
|
2144
|
-
width: w,
|
|
2145
|
-
height: HEADER_H,
|
|
2146
|
-
rx: 12,
|
|
2147
|
-
fill: t.actorFill,
|
|
2148
|
-
stroke: t.actorStroke,
|
|
2149
|
-
strokeWidth: 1.25,
|
|
2150
|
-
filter: "url(#seqShadow)"
|
|
2151
|
-
}
|
|
2152
|
-
),
|
|
2153
|
-
editingId === name ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("foreignObject", { x: x - w / 2 + 8, y: HEADER_PAD + 16, width: w - 16, height: 32, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2154
|
-
"input",
|
|
2155
|
-
{
|
|
2156
|
-
autoFocus: true,
|
|
2157
|
-
defaultValue: name,
|
|
2158
|
-
onBlur: (e) => {
|
|
2159
|
-
renameActor(name, e.currentTarget.value.trim());
|
|
2160
|
-
setEditingId(null);
|
|
2161
|
-
},
|
|
2162
|
-
onKeyDown: (e) => {
|
|
2163
|
-
if (e.key === "Enter") {
|
|
2164
|
-
renameActor(name, e.target.value.trim());
|
|
2165
|
-
setEditingId(null);
|
|
2166
|
-
}
|
|
2167
|
-
if (e.key === "Escape") setEditingId(null);
|
|
2168
|
-
},
|
|
2169
|
-
style: {
|
|
2170
|
-
width: "100%",
|
|
2171
|
-
height: "100%",
|
|
2172
|
-
border: "none",
|
|
2173
|
-
borderRadius: 6,
|
|
2174
|
-
outline: `2px solid ${INDIGO}`,
|
|
2175
|
-
textAlign: "center",
|
|
2176
|
-
fontSize: 13,
|
|
2177
|
-
fontWeight: 600,
|
|
2178
|
-
background: t.inputBg,
|
|
2179
|
-
color: t.inputText,
|
|
2180
|
-
boxSizing: "border-box",
|
|
2181
|
-
padding: "0 6px",
|
|
2182
|
-
fontFamily: "inherit"
|
|
2183
|
-
}
|
|
2184
|
-
}
|
|
2185
|
-
) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2186
|
-
"text",
|
|
2187
|
-
{
|
|
2188
|
-
x,
|
|
2189
|
-
y: HEADER_PAD + HEADER_H / 2 + 4,
|
|
2190
|
-
textAnchor: "middle",
|
|
2191
|
-
fontSize: 13,
|
|
2192
|
-
fontWeight: 700,
|
|
2193
|
-
fill: t.actorText,
|
|
2194
|
-
style: { cursor: "pointer", userSelect: "none" },
|
|
2195
|
-
onDoubleClick: () => setEditingId(name),
|
|
2196
|
-
children: name
|
|
2197
|
-
}
|
|
2198
|
-
),
|
|
2199
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2200
|
-
"circle",
|
|
2201
|
-
{
|
|
2202
|
-
cx: x + w / 2 - 12,
|
|
2203
|
-
cy: HEADER_PAD + 14,
|
|
2204
|
-
r: 9,
|
|
2205
|
-
fill: "transparent",
|
|
2206
|
-
style: { cursor: "pointer" },
|
|
2207
|
-
onClick: () => removeActor(name),
|
|
2208
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("title", { children: "Remove actor" })
|
|
2209
|
-
}
|
|
2210
|
-
),
|
|
2211
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2212
|
-
"text",
|
|
2213
|
-
{
|
|
2214
|
-
x: x + w / 2 - 12,
|
|
2215
|
-
y: HEADER_PAD + 18,
|
|
2216
|
-
textAnchor: "middle",
|
|
2217
|
-
fontSize: 12,
|
|
2218
|
-
fill: t.textMuted,
|
|
2219
|
-
style: { pointerEvents: "none", userSelect: "none" },
|
|
2220
|
-
children: "\xD7"
|
|
2221
|
-
}
|
|
2222
|
-
)
|
|
2223
|
-
] }, `hdr-${name}`);
|
|
2224
|
-
})
|
|
2225
|
-
]
|
|
2296
|
+
model,
|
|
2297
|
+
actors,
|
|
2298
|
+
messages,
|
|
2299
|
+
t,
|
|
2300
|
+
isDark,
|
|
2301
|
+
colW,
|
|
2302
|
+
totalW,
|
|
2303
|
+
totalH,
|
|
2304
|
+
actorX,
|
|
2305
|
+
msgY,
|
|
2306
|
+
selected,
|
|
2307
|
+
editingId,
|
|
2308
|
+
setEditingId,
|
|
2309
|
+
drag,
|
|
2310
|
+
onRowMouseDown,
|
|
2311
|
+
renameActor,
|
|
2312
|
+
removeActor,
|
|
2313
|
+
svgRef
|
|
2226
2314
|
}
|
|
2227
2315
|
) }),
|
|
2228
|
-
selectedMsg && /* @__PURE__ */ (0,
|
|
2316
|
+
selectedMsg && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: {
|
|
2229
2317
|
width: 280,
|
|
2230
2318
|
flexShrink: 0,
|
|
2231
2319
|
background: t.panelBg,
|
|
@@ -2233,9 +2321,9 @@ function SequenceEditor({
|
|
|
2233
2321
|
padding: "14px 16px",
|
|
2234
2322
|
overflowY: "auto"
|
|
2235
2323
|
}, children: [
|
|
2236
|
-
/* @__PURE__ */ (0,
|
|
2237
|
-
/* @__PURE__ */ (0,
|
|
2238
|
-
/* @__PURE__ */ (0,
|
|
2324
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { fontSize: 10, fontWeight: 700, color: t.textMuted, textTransform: "uppercase", letterSpacing: 0.7, marginBottom: 10 }, children: "Message" }),
|
|
2325
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Label, { t, children: "Label" }),
|
|
2326
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2239
2327
|
"input",
|
|
2240
2328
|
{
|
|
2241
2329
|
value: editLabel || selectedMsg.label,
|
|
@@ -2251,21 +2339,21 @@ function SequenceEditor({
|
|
|
2251
2339
|
style: input(t)
|
|
2252
2340
|
}
|
|
2253
2341
|
),
|
|
2254
|
-
/* @__PURE__ */ (0,
|
|
2255
|
-
/* @__PURE__ */ (0,
|
|
2256
|
-
/* @__PURE__ */ (0,
|
|
2257
|
-
/* @__PURE__ */ (0,
|
|
2258
|
-
/* @__PURE__ */ (0,
|
|
2259
|
-
/* @__PURE__ */ (0,
|
|
2342
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Label, { t, children: "From" }),
|
|
2343
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("select", { value: selectedMsg.from, onChange: (e) => updateMessage(selectedMsg.id, { from: e.target.value }), style: input(t), children: actors.map((a) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: a, children: a }, a)) }),
|
|
2344
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Label, { t, children: "To" }),
|
|
2345
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("select", { value: selectedMsg.to, onChange: (e) => updateMessage(selectedMsg.id, { to: e.target.value }), style: input(t), children: actors.map((a) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: a, children: a }, a)) }),
|
|
2346
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Label, { t, children: "Style" }),
|
|
2347
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { display: "flex", gap: 6 }, children: ["solid", "dashed"].map((s2) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2260
2348
|
"button",
|
|
2261
2349
|
{
|
|
2262
2350
|
onClick: () => updateMessage(selectedMsg.id, { style: s2 }),
|
|
2263
2351
|
style: {
|
|
2264
2352
|
flex: 1,
|
|
2265
2353
|
padding: "6px 10px",
|
|
2266
|
-
border: `1.5px solid ${selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ?
|
|
2267
|
-
background: selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ?
|
|
2268
|
-
color: selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ?
|
|
2354
|
+
border: `1.5px solid ${selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ? INDIGO2 : t.inputBorder}`,
|
|
2355
|
+
background: selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ? INDIGO_SOFT2 : t.inputBg,
|
|
2356
|
+
color: selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ? INDIGO2 : t.textPrimary,
|
|
2269
2357
|
borderRadius: 8,
|
|
2270
2358
|
fontSize: 12,
|
|
2271
2359
|
fontWeight: 600,
|
|
@@ -2276,8 +2364,8 @@ function SequenceEditor({
|
|
|
2276
2364
|
},
|
|
2277
2365
|
s2
|
|
2278
2366
|
)) }),
|
|
2279
|
-
/* @__PURE__ */ (0,
|
|
2280
|
-
/* @__PURE__ */ (0,
|
|
2367
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { height: 14 } }),
|
|
2368
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2281
2369
|
"button",
|
|
2282
2370
|
{
|
|
2283
2371
|
onClick: () => removeMessage(selectedMsg.id),
|
|
@@ -2287,7 +2375,7 @@ function SequenceEditor({
|
|
|
2287
2375
|
)
|
|
2288
2376
|
] })
|
|
2289
2377
|
] }),
|
|
2290
|
-
/* @__PURE__ */ (0,
|
|
2378
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: {
|
|
2291
2379
|
padding: "4px 14px",
|
|
2292
2380
|
fontSize: 11,
|
|
2293
2381
|
color: t.textMuted,
|
|
@@ -2296,25 +2384,22 @@ function SequenceEditor({
|
|
|
2296
2384
|
display: "flex",
|
|
2297
2385
|
gap: 16
|
|
2298
2386
|
}, children: [
|
|
2299
|
-
/* @__PURE__ */ (0,
|
|
2387
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { children: [
|
|
2300
2388
|
actors.length,
|
|
2301
2389
|
" actors"
|
|
2302
2390
|
] }),
|
|
2303
|
-
/* @__PURE__ */ (0,
|
|
2391
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { children: [
|
|
2304
2392
|
messages.length,
|
|
2305
2393
|
" messages"
|
|
2306
2394
|
] }),
|
|
2307
|
-
/* @__PURE__ */ (0,
|
|
2395
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { marginLeft: "auto" }, children: "double-click actor to rename \xB7 drag a row to reorder" })
|
|
2308
2396
|
] })
|
|
2309
2397
|
] });
|
|
2310
2398
|
}
|
|
2311
|
-
function estimateW(text, pxPerChar = 7) {
|
|
2312
|
-
return text.length * pxPerChar;
|
|
2313
|
-
}
|
|
2314
2399
|
function primaryBtn() {
|
|
2315
2400
|
return {
|
|
2316
2401
|
padding: "6px 12px",
|
|
2317
|
-
background:
|
|
2402
|
+
background: INDIGO2,
|
|
2318
2403
|
color: "#fff",
|
|
2319
2404
|
border: "none",
|
|
2320
2405
|
borderRadius: 8,
|
|
@@ -2353,180 +2438,37 @@ function input(t) {
|
|
|
2353
2438
|
};
|
|
2354
2439
|
}
|
|
2355
2440
|
function Label({ t, children }) {
|
|
2356
|
-
return /* @__PURE__ */ (0,
|
|
2441
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { fontSize: 10, fontWeight: 700, color: t.textMuted, textTransform: "uppercase", letterSpacing: 0.6, marginBottom: 4 }, children });
|
|
2357
2442
|
}
|
|
2358
2443
|
|
|
2359
|
-
// src/ui/
|
|
2360
|
-
var
|
|
2361
|
-
var
|
|
2362
|
-
|
|
2363
|
-
var H = 112;
|
|
2364
|
-
var PAD = 18;
|
|
2365
|
-
function Minimap({
|
|
2444
|
+
// src/ui/NodeNavigator.tsx
|
|
2445
|
+
var import_react11 = require("react");
|
|
2446
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
2447
|
+
function NodeNavigator({
|
|
2366
2448
|
model,
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
transform,
|
|
2370
|
-
measureNode,
|
|
2371
|
-
onCenterOn,
|
|
2449
|
+
selected,
|
|
2450
|
+
variant,
|
|
2372
2451
|
isDark,
|
|
2373
|
-
|
|
2452
|
+
t,
|
|
2453
|
+
acc,
|
|
2454
|
+
open,
|
|
2455
|
+
onToggle,
|
|
2456
|
+
onSelect
|
|
2374
2457
|
}) {
|
|
2375
|
-
const
|
|
2376
|
-
const
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
maxX = Math.max(maxX, b.x + b.w);
|
|
2390
|
-
maxY = Math.max(maxY, b.y + b.h);
|
|
2391
|
-
}
|
|
2392
|
-
minX = Math.min(minX, vx);
|
|
2393
|
-
minY = Math.min(minY, vy);
|
|
2394
|
-
maxX = Math.max(maxX, vx + vw);
|
|
2395
|
-
maxY = Math.max(maxY, vy + vh);
|
|
2396
|
-
const contentW = Math.max(1, maxX - minX);
|
|
2397
|
-
const contentH = Math.max(1, maxY - minY);
|
|
2398
|
-
const scale = Math.min((W - PAD * 2) / contentW, (H - PAD * 2) / contentH);
|
|
2399
|
-
const offsetX = (W - contentW * scale) / 2 - minX * scale;
|
|
2400
|
-
const offsetY = (H - contentH * scale) / 2 - minY * scale;
|
|
2401
|
-
const project = (x, y) => ({
|
|
2402
|
-
x: offsetX + x * scale,
|
|
2403
|
-
y: offsetY + y * scale
|
|
2404
|
-
});
|
|
2405
|
-
const unproject = (mx, my) => ({
|
|
2406
|
-
x: (mx - offsetX) / scale,
|
|
2407
|
-
y: (my - offsetY) / scale
|
|
2408
|
-
});
|
|
2409
|
-
const panTo = (0, import_react9.useCallback)((e) => {
|
|
2410
|
-
const rect = e.currentTarget.getBoundingClientRect();
|
|
2411
|
-
const mx = e.clientX - rect.left;
|
|
2412
|
-
const my = e.clientY - rect.top;
|
|
2413
|
-
const { x, y } = unproject(mx, my);
|
|
2414
|
-
onCenterOn(x, y);
|
|
2415
|
-
}, [onCenterOn, scale, offsetX, offsetY]);
|
|
2416
|
-
const onMouseDown = (e) => {
|
|
2417
|
-
e.stopPropagation();
|
|
2418
|
-
dragRef.current = { active: true };
|
|
2419
|
-
panTo(e);
|
|
2420
|
-
};
|
|
2421
|
-
const onMouseMove = (e) => {
|
|
2422
|
-
if (!dragRef.current?.active) return;
|
|
2423
|
-
panTo(e);
|
|
2424
|
-
};
|
|
2425
|
-
const onMouseUp = () => {
|
|
2426
|
-
dragRef.current = null;
|
|
2427
|
-
};
|
|
2428
|
-
const bg = isDark ? "rgba(15,23,42,0.92)" : "rgba(255,255,255,0.94)";
|
|
2429
|
-
const border = isDark ? "#334155" : "#e2e8f0";
|
|
2430
|
-
const nodeFill = isDark ? "#475569" : "#cbd5e1";
|
|
2431
|
-
const viewStroke = accentColor;
|
|
2432
|
-
const viewFill = `${accentColor}22`;
|
|
2433
|
-
const vp1 = project(vx, vy);
|
|
2434
|
-
const vp2 = project(vx + vw, vy + vh);
|
|
2435
|
-
const vpRect = {
|
|
2436
|
-
x: Math.max(0, Math.min(W, vp1.x)),
|
|
2437
|
-
y: Math.max(0, Math.min(H, vp1.y)),
|
|
2438
|
-
w: Math.max(2, Math.min(W, vp2.x) - Math.max(0, vp1.x)),
|
|
2439
|
-
h: Math.max(2, Math.min(H, vp2.y) - Math.max(0, vp1.y))
|
|
2440
|
-
};
|
|
2441
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2442
|
-
"div",
|
|
2443
|
-
{
|
|
2444
|
-
style: {
|
|
2445
|
-
position: "absolute",
|
|
2446
|
-
bottom: 14,
|
|
2447
|
-
right: 14,
|
|
2448
|
-
background: bg,
|
|
2449
|
-
border: `1px solid ${border}`,
|
|
2450
|
-
borderRadius: 10,
|
|
2451
|
-
padding: 6,
|
|
2452
|
-
boxShadow: isDark ? "0 8px 20px rgba(0,0,0,0.45)" : "0 6px 18px rgba(15,23,42,0.08)",
|
|
2453
|
-
backdropFilter: "blur(6px)"
|
|
2454
|
-
},
|
|
2455
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
2456
|
-
"svg",
|
|
2457
|
-
{
|
|
2458
|
-
width: W,
|
|
2459
|
-
height: H,
|
|
2460
|
-
style: { display: "block", cursor: "grab", borderRadius: 6 },
|
|
2461
|
-
onMouseDown,
|
|
2462
|
-
onMouseMove,
|
|
2463
|
-
onMouseUp,
|
|
2464
|
-
onMouseLeave: onMouseUp,
|
|
2465
|
-
children: [
|
|
2466
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("rect", { width: W, height: H, rx: 6, fill: isDark ? "#0f172a" : "#fafbfc" }),
|
|
2467
|
-
boxes.map((b) => {
|
|
2468
|
-
const p = project(b.x, b.y);
|
|
2469
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2470
|
-
"rect",
|
|
2471
|
-
{
|
|
2472
|
-
x: p.x,
|
|
2473
|
-
y: p.y,
|
|
2474
|
-
width: Math.max(2, b.w * scale),
|
|
2475
|
-
height: Math.max(2, b.h * scale),
|
|
2476
|
-
rx: 2,
|
|
2477
|
-
fill: nodeFill
|
|
2478
|
-
},
|
|
2479
|
-
b.id
|
|
2480
|
-
);
|
|
2481
|
-
}),
|
|
2482
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2483
|
-
"rect",
|
|
2484
|
-
{
|
|
2485
|
-
x: vpRect.x,
|
|
2486
|
-
y: vpRect.y,
|
|
2487
|
-
width: vpRect.w,
|
|
2488
|
-
height: vpRect.h,
|
|
2489
|
-
rx: 3,
|
|
2490
|
-
fill: viewFill,
|
|
2491
|
-
stroke: viewStroke,
|
|
2492
|
-
strokeWidth: 1.25
|
|
2493
|
-
}
|
|
2494
|
-
)
|
|
2495
|
-
]
|
|
2496
|
-
}
|
|
2497
|
-
)
|
|
2498
|
-
}
|
|
2499
|
-
);
|
|
2500
|
-
}
|
|
2501
|
-
|
|
2502
|
-
// src/ui/NodeNavigator.tsx
|
|
2503
|
-
var import_react10 = require("react");
|
|
2504
|
-
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
2505
|
-
function NodeNavigator({
|
|
2506
|
-
model,
|
|
2507
|
-
selected,
|
|
2508
|
-
variant,
|
|
2509
|
-
isDark,
|
|
2510
|
-
t,
|
|
2511
|
-
acc,
|
|
2512
|
-
open,
|
|
2513
|
-
onToggle,
|
|
2514
|
-
onSelect
|
|
2515
|
-
}) {
|
|
2516
|
-
const [search, setSearch] = (0, import_react10.useState)("");
|
|
2517
|
-
const shapeIcon = (node) => {
|
|
2518
|
-
if (variant === "question") return "?";
|
|
2519
|
-
if (variant === "journey") return "\u2197";
|
|
2520
|
-
switch (node.shape) {
|
|
2521
|
-
case "diamond":
|
|
2522
|
-
return "\u25C7";
|
|
2523
|
-
case "circle":
|
|
2524
|
-
return "\u25CB";
|
|
2525
|
-
case "parallelogram":
|
|
2526
|
-
return "\u25B1";
|
|
2527
|
-
default:
|
|
2528
|
-
return "\u25AD";
|
|
2529
|
-
}
|
|
2458
|
+
const [search, setSearch] = (0, import_react11.useState)("");
|
|
2459
|
+
const shapeIcon = (node) => {
|
|
2460
|
+
if (variant === "question") return "?";
|
|
2461
|
+
if (variant === "journey") return "\u2197";
|
|
2462
|
+
switch (node.shape) {
|
|
2463
|
+
case "diamond":
|
|
2464
|
+
return "\u25C7";
|
|
2465
|
+
case "circle":
|
|
2466
|
+
return "\u25CB";
|
|
2467
|
+
case "parallelogram":
|
|
2468
|
+
return "\u25B1";
|
|
2469
|
+
default:
|
|
2470
|
+
return "\u25AD";
|
|
2471
|
+
}
|
|
2530
2472
|
};
|
|
2531
2473
|
const filtered = model.nodes.filter(
|
|
2532
2474
|
(n) => n.label.toLowerCase().includes(search.toLowerCase())
|
|
@@ -2683,137 +2625,6 @@ function NodeNavigator({
|
|
|
2683
2625
|
] });
|
|
2684
2626
|
}
|
|
2685
2627
|
|
|
2686
|
-
// src/ui/ContextMenu.tsx
|
|
2687
|
-
var import_react11 = require("react");
|
|
2688
|
-
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
2689
|
-
function ContextMenu({
|
|
2690
|
-
x,
|
|
2691
|
-
y,
|
|
2692
|
-
nodeId,
|
|
2693
|
-
edgeId,
|
|
2694
|
-
isDark,
|
|
2695
|
-
t,
|
|
2696
|
-
acc,
|
|
2697
|
-
canUndo,
|
|
2698
|
-
canRedo,
|
|
2699
|
-
onUndo,
|
|
2700
|
-
onRedo,
|
|
2701
|
-
onReCenter,
|
|
2702
|
-
onAddNode,
|
|
2703
|
-
onDuplicate,
|
|
2704
|
-
onRename,
|
|
2705
|
-
onDelete,
|
|
2706
|
-
onDisconnect,
|
|
2707
|
-
onEdgeRename,
|
|
2708
|
-
onEdgeStyle,
|
|
2709
|
-
onEdgeArrowhead,
|
|
2710
|
-
onEdgeDelete,
|
|
2711
|
-
onEdgeResetRouting,
|
|
2712
|
-
currentEdgeStyle,
|
|
2713
|
-
currentEdgeArrow,
|
|
2714
|
-
edgeHasWaypoint,
|
|
2715
|
-
containerRef
|
|
2716
|
-
}) {
|
|
2717
|
-
const menuRef = (0, import_react11.useRef)(null);
|
|
2718
|
-
const [pos, setPos] = (0, import_react11.useState)({ x, y });
|
|
2719
|
-
(0, import_react11.useEffect)(() => {
|
|
2720
|
-
if (!menuRef.current || !containerRef.current) return;
|
|
2721
|
-
const m = menuRef.current.getBoundingClientRect();
|
|
2722
|
-
const c = containerRef.current.getBoundingClientRect();
|
|
2723
|
-
let nx = x, ny = y;
|
|
2724
|
-
if (nx + m.width > c.right - 8) nx = x - m.width;
|
|
2725
|
-
if (ny + m.height > c.bottom - 8) ny = y - m.height;
|
|
2726
|
-
setPos({ x: nx, y: ny });
|
|
2727
|
-
}, [x, y, containerRef]);
|
|
2728
|
-
const bg = isDark ? "#1e293b" : "#ffffff";
|
|
2729
|
-
const border = isDark ? "#334155" : "#e2e8f0";
|
|
2730
|
-
const hoverBg = isDark ? "#334155" : "#f1f5f9";
|
|
2731
|
-
const dividerColor = isDark ? "#334155" : "#f1f5f9";
|
|
2732
|
-
const text = t.textPrimary;
|
|
2733
|
-
const muted = t.textMuted;
|
|
2734
|
-
const item = (label, onClick, color, disabled) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2735
|
-
"button",
|
|
2736
|
-
{
|
|
2737
|
-
onClick: disabled ? void 0 : onClick,
|
|
2738
|
-
style: {
|
|
2739
|
-
display: "flex",
|
|
2740
|
-
alignItems: "center",
|
|
2741
|
-
gap: 10,
|
|
2742
|
-
width: "100%",
|
|
2743
|
-
padding: "7px 14px",
|
|
2744
|
-
background: "none",
|
|
2745
|
-
border: "none",
|
|
2746
|
-
textAlign: "left",
|
|
2747
|
-
cursor: disabled ? "default" : "pointer",
|
|
2748
|
-
fontSize: 12,
|
|
2749
|
-
fontFamily: "ui-sans-serif,system-ui,sans-serif",
|
|
2750
|
-
color: disabled ? muted : color ?? text,
|
|
2751
|
-
opacity: disabled ? 0.4 : 1,
|
|
2752
|
-
borderRadius: 6
|
|
2753
|
-
},
|
|
2754
|
-
onMouseEnter: (e) => {
|
|
2755
|
-
if (!disabled) e.currentTarget.style.background = hoverBg;
|
|
2756
|
-
},
|
|
2757
|
-
onMouseLeave: (e) => {
|
|
2758
|
-
e.currentTarget.style.background = "none";
|
|
2759
|
-
},
|
|
2760
|
-
children: label
|
|
2761
|
-
},
|
|
2762
|
-
label
|
|
2763
|
-
);
|
|
2764
|
-
const divider2 = /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { height: 1, background: dividerColor, margin: "4px 0" } });
|
|
2765
|
-
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2766
|
-
"div",
|
|
2767
|
-
{
|
|
2768
|
-
ref: menuRef,
|
|
2769
|
-
onMouseDown: (e) => e.stopPropagation(),
|
|
2770
|
-
style: {
|
|
2771
|
-
position: "fixed",
|
|
2772
|
-
left: pos.x,
|
|
2773
|
-
top: pos.y,
|
|
2774
|
-
zIndex: 9999,
|
|
2775
|
-
background: bg,
|
|
2776
|
-
border: `1px solid ${border}`,
|
|
2777
|
-
borderRadius: 10,
|
|
2778
|
-
padding: "5px 0",
|
|
2779
|
-
minWidth: 180,
|
|
2780
|
-
boxShadow: isDark ? "0 8px 32px rgba(0,0,0,0.5)" : "0 8px 32px rgba(0,0,0,0.12)",
|
|
2781
|
-
fontFamily: "ui-sans-serif,system-ui,sans-serif"
|
|
2782
|
-
},
|
|
2783
|
-
children: edgeId ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
2784
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { padding: "4px 14px 6px", fontSize: 10, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Edge" }),
|
|
2785
|
-
item("Rename label (dbl-click)", () => onEdgeRename?.()),
|
|
2786
|
-
divider2,
|
|
2787
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { padding: "4px 14px 2px", fontSize: 9, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Style" }),
|
|
2788
|
-
item(`Solid${currentEdgeStyle === "solid" || !currentEdgeStyle ? " \u2713" : ""}`, () => onEdgeStyle?.("solid")),
|
|
2789
|
-
item(`Dashed${currentEdgeStyle === "dashed" ? " \u2713" : ""}`, () => onEdgeStyle?.("dashed")),
|
|
2790
|
-
item(`Dotted${currentEdgeStyle === "dotted" ? " \u2713" : ""}`, () => onEdgeStyle?.("dotted")),
|
|
2791
|
-
divider2,
|
|
2792
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { padding: "4px 14px 2px", fontSize: 9, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Arrowhead" }),
|
|
2793
|
-
item(`Arrow${currentEdgeArrow !== "none" ? " \u2713" : ""}`, () => onEdgeArrowhead?.("arrow")),
|
|
2794
|
-
item(`None${currentEdgeArrow === "none" ? " \u2713" : ""}`, () => onEdgeArrowhead?.("none")),
|
|
2795
|
-
divider2,
|
|
2796
|
-
item("Reset routing", () => onEdgeResetRouting?.(), void 0, !edgeHasWaypoint),
|
|
2797
|
-
item("Delete edge", () => onEdgeDelete?.(), "#ef4444")
|
|
2798
|
-
] }) : nodeId ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
2799
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { padding: "4px 14px 6px", fontSize: 10, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Node" }),
|
|
2800
|
-
item("Rename (dbl-click)", onRename),
|
|
2801
|
-
item("Duplicate", onDuplicate),
|
|
2802
|
-
item("Disconnect all edges", onDisconnect),
|
|
2803
|
-
divider2,
|
|
2804
|
-
item("Delete node", onDelete, "#ef4444")
|
|
2805
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
2806
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { padding: "4px 14px 6px", fontSize: 10, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Canvas" }),
|
|
2807
|
-
item("Add node here", onAddNode, acc.color),
|
|
2808
|
-
item("Re-center (Ctrl+0)", onReCenter),
|
|
2809
|
-
divider2,
|
|
2810
|
-
item("Undo (Ctrl+Z)", onUndo, void 0, !canUndo),
|
|
2811
|
-
item("Redo (Ctrl+Y)", onRedo, void 0, !canRedo)
|
|
2812
|
-
] })
|
|
2813
|
-
}
|
|
2814
|
-
);
|
|
2815
|
-
}
|
|
2816
|
-
|
|
2817
2628
|
// src/ui/render.tsx
|
|
2818
2629
|
var import_react12 = require("react");
|
|
2819
2630
|
|
|
@@ -2881,7 +2692,7 @@ function bezierPathVia(x1, y1, wx, wy, x2, y2) {
|
|
|
2881
2692
|
}
|
|
2882
2693
|
|
|
2883
2694
|
// src/ui/render.tsx
|
|
2884
|
-
var
|
|
2695
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
2885
2696
|
var STYLE_LABEL = { pointerEvents: "none", userSelect: "none" };
|
|
2886
2697
|
var STYLE_BLUR = { filter: "blur(4px)" };
|
|
2887
2698
|
var STYLE_EDGE_HIT = { cursor: "pointer" };
|
|
@@ -2895,11 +2706,11 @@ function NodeShape({ node, selected, variant, stepNumber, t, isDark, w }) {
|
|
|
2895
2706
|
const stroke = selected ? acc.color : t.nodeStroke;
|
|
2896
2707
|
const fill = selected ? t.nodeSelectedFill : t.nodeFill;
|
|
2897
2708
|
const sw = selected ? 1.75 : 1.25;
|
|
2898
|
-
const glow = selected && /* @__PURE__ */ (0,
|
|
2899
|
-
/* @__PURE__ */ (0,
|
|
2900
|
-
/* @__PURE__ */ (0,
|
|
2901
|
-
] }) : node.shape === "diamond" ? /* @__PURE__ */ (0,
|
|
2902
|
-
/* @__PURE__ */ (0,
|
|
2709
|
+
const glow = selected && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: node.shape === "circle" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
2710
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx, cy, r: NODE_H2 / 2 + 3, fill: "none", stroke: acc.color, strokeWidth: 6, opacity: 0.18, style: STYLE_BLUR }),
|
|
2711
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx, cy, r: NODE_H2 / 2 + 1.5, fill: "none", stroke: acc.color, strokeWidth: 1, opacity: 0.55 })
|
|
2712
|
+
] }) : node.shape === "diamond" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
2713
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2903
2714
|
"polygon",
|
|
2904
2715
|
{
|
|
2905
2716
|
points: `${cx},${-5} ${w + 5},${cy} ${cx},${NODE_H2 + 5} ${-5},${cy}`,
|
|
@@ -2910,7 +2721,7 @@ function NodeShape({ node, selected, variant, stepNumber, t, isDark, w }) {
|
|
|
2910
2721
|
style: STYLE_BLUR
|
|
2911
2722
|
}
|
|
2912
2723
|
),
|
|
2913
|
-
/* @__PURE__ */ (0,
|
|
2724
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2914
2725
|
"polygon",
|
|
2915
2726
|
{
|
|
2916
2727
|
points: `${cx},${-2} ${w + 2},${cy} ${cx},${NODE_H2 + 2} ${-2},${cy}`,
|
|
@@ -2920,8 +2731,8 @@ function NodeShape({ node, selected, variant, stepNumber, t, isDark, w }) {
|
|
|
2920
2731
|
opacity: 0.55
|
|
2921
2732
|
}
|
|
2922
2733
|
)
|
|
2923
|
-
] }) : /* @__PURE__ */ (0,
|
|
2924
|
-
/* @__PURE__ */ (0,
|
|
2734
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
2735
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2925
2736
|
"rect",
|
|
2926
2737
|
{
|
|
2927
2738
|
x: -4,
|
|
@@ -2936,7 +2747,7 @@ function NodeShape({ node, selected, variant, stepNumber, t, isDark, w }) {
|
|
|
2936
2747
|
style: STYLE_BLUR
|
|
2937
2748
|
}
|
|
2938
2749
|
),
|
|
2939
|
-
/* @__PURE__ */ (0,
|
|
2750
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2940
2751
|
"rect",
|
|
2941
2752
|
{
|
|
2942
2753
|
x: -1.5,
|
|
@@ -2952,35 +2763,35 @@ function NodeShape({ node, selected, variant, stepNumber, t, isDark, w }) {
|
|
|
2952
2763
|
)
|
|
2953
2764
|
] }) });
|
|
2954
2765
|
const badgeColor = isDark ? ACCENT.emeraldDark : ACCENT.emerald;
|
|
2955
|
-
const badge = variant === "journey" && stepNumber !== void 0 && /* @__PURE__ */ (0,
|
|
2956
|
-
/* @__PURE__ */ (0,
|
|
2957
|
-
/* @__PURE__ */ (0,
|
|
2766
|
+
const badge = variant === "journey" && stepNumber !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
2767
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: 14, cy: 14, r: 10, fill: badgeColor }),
|
|
2768
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("text", { x: 14, y: 18, textAnchor: "middle", fontSize: 9, fill: "white", fontWeight: "700", style: STYLE_LABEL, children: stepNumber })
|
|
2958
2769
|
] });
|
|
2959
2770
|
switch (node.shape) {
|
|
2960
2771
|
case "diamond": {
|
|
2961
2772
|
const pts = `${cx},0 ${w},${cy} ${cx},${NODE_H2} 0,${cy}`;
|
|
2962
|
-
return /* @__PURE__ */ (0,
|
|
2773
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
2963
2774
|
glow,
|
|
2964
|
-
/* @__PURE__ */ (0,
|
|
2775
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("polygon", { points: pts, fill, stroke, strokeWidth: sw, filter: "url(#nodeShadow)" }),
|
|
2965
2776
|
badge
|
|
2966
2777
|
] });
|
|
2967
2778
|
}
|
|
2968
2779
|
case "circle":
|
|
2969
|
-
return /* @__PURE__ */ (0,
|
|
2780
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
2970
2781
|
glow,
|
|
2971
|
-
/* @__PURE__ */ (0,
|
|
2782
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx, cy, r: NODE_H2 / 2 - 1, fill, stroke, strokeWidth: sw, filter: "url(#nodeShadow)" }),
|
|
2972
2783
|
badge
|
|
2973
2784
|
] });
|
|
2974
2785
|
case "parallelogram":
|
|
2975
|
-
return /* @__PURE__ */ (0,
|
|
2786
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
2976
2787
|
glow,
|
|
2977
|
-
/* @__PURE__ */ (0,
|
|
2788
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("polygon", { points: `14,0 ${w},0 ${w - 14},${NODE_H2} 0,${NODE_H2}`, fill, stroke, strokeWidth: sw, filter: "url(#nodeShadow)" }),
|
|
2978
2789
|
badge
|
|
2979
2790
|
] });
|
|
2980
2791
|
default:
|
|
2981
|
-
return /* @__PURE__ */ (0,
|
|
2792
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
2982
2793
|
glow,
|
|
2983
|
-
/* @__PURE__ */ (0,
|
|
2794
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { width: w, height: NODE_H2, rx: 14, fill, stroke, strokeWidth: sw, filter: "url(#nodeShadow)" }),
|
|
2984
2795
|
badge
|
|
2985
2796
|
] });
|
|
2986
2797
|
}
|
|
@@ -3001,8 +2812,8 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
3001
2812
|
const textSub = isDark ? "#64748b" : "#94a3b8";
|
|
3002
2813
|
const textAns = isDark ? "#cbd5e1" : "#374151";
|
|
3003
2814
|
const portRowY = Q_BASE_H2 + Q_ANS_ROW_H2 - 8;
|
|
3004
|
-
const glow = selected && /* @__PURE__ */ (0,
|
|
3005
|
-
/* @__PURE__ */ (0,
|
|
2815
|
+
const glow = selected && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
2816
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3006
2817
|
"rect",
|
|
3007
2818
|
{
|
|
3008
2819
|
x: -4,
|
|
@@ -3017,7 +2828,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
3017
2828
|
style: STYLE_BLUR
|
|
3018
2829
|
}
|
|
3019
2830
|
),
|
|
3020
|
-
/* @__PURE__ */ (0,
|
|
2831
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3021
2832
|
"rect",
|
|
3022
2833
|
{
|
|
3023
2834
|
x: -1.5,
|
|
@@ -3032,29 +2843,29 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
3032
2843
|
}
|
|
3033
2844
|
)
|
|
3034
2845
|
] });
|
|
3035
|
-
return /* @__PURE__ */ (0,
|
|
2846
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
3036
2847
|
glow,
|
|
3037
|
-
/* @__PURE__ */ (0,
|
|
3038
|
-
/* @__PURE__ */ (0,
|
|
3039
|
-
/* @__PURE__ */ (0,
|
|
3040
|
-
/* @__PURE__ */ (0,
|
|
3041
|
-
/* @__PURE__ */ (0,
|
|
3042
|
-
/* @__PURE__ */ (0,
|
|
3043
|
-
/* @__PURE__ */ (0,
|
|
2848
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { width: qW, height: totalH, rx: 14, fill: nodeBg, stroke: nodeBorder, strokeWidth: selected ? 2 : 1.5, filter: "url(#nodeShadow)" }),
|
|
2849
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("clipPath", { id: `qhdr-${node.id}`, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { width: qW, height: Q_BASE_H2, rx: 14 }) }),
|
|
2850
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { width: qW, height: Q_BASE_H2, fill: amberSoft, clipPath: `url(#qhdr-${node.id})` }),
|
|
2851
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { x: 0, y: 0, width: 4, height: Q_BASE_H2, rx: 2, fill: amber }),
|
|
2852
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { x: 12, y: 14, width: 28, height: 28, rx: 8, fill: amber }),
|
|
2853
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("text", { x: 26, y: 33, textAnchor: "middle", fontSize: 15, fontWeight: "900", fill: "white", style: STYLE_LABEL, children: "?" }),
|
|
2854
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
3044
2855
|
"text",
|
|
3045
2856
|
{
|
|
3046
2857
|
style: STYLE_LABEL,
|
|
3047
2858
|
fontFamily: "ui-sans-serif,system-ui,sans-serif",
|
|
3048
2859
|
children: [
|
|
3049
|
-
/* @__PURE__ */ (0,
|
|
3050
|
-
/* @__PURE__ */ (0,
|
|
2860
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tspan", { x: 50, y: 27, fontSize: 9, fontWeight: 700, fill: textSub, letterSpacing: 0.6, textAnchor: "start", children: "QUESTION" }),
|
|
2861
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tspan", { x: 50, dy: 15, fontSize: 13, fontWeight: 700, fill: selected ? amber : textMain, textAnchor: "start", children: node.label })
|
|
3051
2862
|
]
|
|
3052
2863
|
}
|
|
3053
2864
|
),
|
|
3054
|
-
/* @__PURE__ */ (0,
|
|
3055
|
-
answers.length === 0 && /* @__PURE__ */ (0,
|
|
3056
|
-
/* @__PURE__ */ (0,
|
|
3057
|
-
/* @__PURE__ */ (0,
|
|
2865
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("line", { x1: 0, y1: Q_BASE_H2, x2: qW, y2: Q_BASE_H2, stroke: amberLine, strokeWidth: 1 }),
|
|
2866
|
+
answers.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
2867
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("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" }),
|
|
2868
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("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" })
|
|
3058
2869
|
] }),
|
|
3059
2870
|
answers.map((ans, i) => {
|
|
3060
2871
|
const prevW = answers.slice(0, i).reduce((s2, a) => s2 + answerCardW2(a) + Q_CARD_PAD2, 0);
|
|
@@ -3067,8 +2878,8 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
3067
2878
|
const letter = i < 26 ? ANSWER_LETTERS[i] : `${i + 1}`;
|
|
3068
2879
|
const maxChars = Math.max(2, Math.floor((cW - 20) / 7.5));
|
|
3069
2880
|
const displayAns = ans.length > maxChars ? ans.slice(0, maxChars - 1) + "\u2026" : ans;
|
|
3070
|
-
return /* @__PURE__ */ (0,
|
|
3071
|
-
/* @__PURE__ */ (0,
|
|
2881
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("g", { children: [
|
|
2882
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3072
2883
|
"rect",
|
|
3073
2884
|
{
|
|
3074
2885
|
x: cardX,
|
|
@@ -3081,7 +2892,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
3081
2892
|
strokeWidth: connected ? 1.5 : 1
|
|
3082
2893
|
}
|
|
3083
2894
|
),
|
|
3084
|
-
/* @__PURE__ */ (0,
|
|
2895
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3085
2896
|
"rect",
|
|
3086
2897
|
{
|
|
3087
2898
|
x: cx - 11,
|
|
@@ -3092,7 +2903,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
3092
2903
|
fill: connected ? amber : isDark ? "#1e293b" : "#fef3c7"
|
|
3093
2904
|
}
|
|
3094
2905
|
),
|
|
3095
|
-
/* @__PURE__ */ (0,
|
|
2906
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3096
2907
|
"text",
|
|
3097
2908
|
{
|
|
3098
2909
|
x: cx,
|
|
@@ -3105,7 +2916,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
3105
2916
|
children: letter
|
|
3106
2917
|
}
|
|
3107
2918
|
),
|
|
3108
|
-
/* @__PURE__ */ (0,
|
|
2919
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3109
2920
|
"text",
|
|
3110
2921
|
{
|
|
3111
2922
|
x: cx,
|
|
@@ -3119,7 +2930,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
3119
2930
|
children: displayAns
|
|
3120
2931
|
}
|
|
3121
2932
|
),
|
|
3122
|
-
/* @__PURE__ */ (0,
|
|
2933
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3123
2934
|
"circle",
|
|
3124
2935
|
{
|
|
3125
2936
|
cx,
|
|
@@ -3132,7 +2943,7 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
3132
2943
|
onMouseDown: (e) => onAnswerPortDown(e, node.id, ans, cx, portRowY)
|
|
3133
2944
|
}
|
|
3134
2945
|
),
|
|
3135
|
-
/* @__PURE__ */ (0,
|
|
2946
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3136
2947
|
"path",
|
|
3137
2948
|
{
|
|
3138
2949
|
d: `M ${cx - 3} ${portRowY - 2} L ${cx} ${portRowY + 2} L ${cx + 3} ${portRowY - 2}`,
|
|
@@ -3188,7 +2999,7 @@ function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, on
|
|
|
3188
2999
|
const labelW = edge.label ? Math.max(60, Math.ceil(estimateTextW2(edge.label, 7) + 18)) : 60;
|
|
3189
3000
|
const showHandle = !!onWaypointDown && (hovered || !!wp);
|
|
3190
3001
|
const flowClass = dash ? void 0 : isAmber ? "edge-flow-amber" : "edge-flow";
|
|
3191
|
-
return /* @__PURE__ */ (0,
|
|
3002
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
3192
3003
|
"g",
|
|
3193
3004
|
{
|
|
3194
3005
|
onDoubleClick: (e) => {
|
|
@@ -3201,8 +3012,8 @@ function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, on
|
|
|
3201
3012
|
onMouseEnter: () => setHovered(true),
|
|
3202
3013
|
onMouseLeave: () => setHovered(false),
|
|
3203
3014
|
children: [
|
|
3204
|
-
/* @__PURE__ */ (0,
|
|
3205
|
-
/* @__PURE__ */ (0,
|
|
3015
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d, fill: "none", stroke: "transparent", strokeWidth: 14, style: STYLE_EDGE_HIT }),
|
|
3016
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3206
3017
|
"path",
|
|
3207
3018
|
{
|
|
3208
3019
|
d,
|
|
@@ -3217,7 +3028,7 @@ function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, on
|
|
|
3217
3028
|
style: STYLE_NO_EVENTS
|
|
3218
3029
|
}
|
|
3219
3030
|
),
|
|
3220
|
-
showHandle && /* @__PURE__ */ (0,
|
|
3031
|
+
showHandle && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3221
3032
|
"circle",
|
|
3222
3033
|
{
|
|
3223
3034
|
cx: hx,
|
|
@@ -3233,7 +3044,7 @@ function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, on
|
|
|
3233
3044
|
}
|
|
3234
3045
|
}
|
|
3235
3046
|
),
|
|
3236
|
-
editing && !isAmber ? /* @__PURE__ */ (0,
|
|
3047
|
+
editing && !isAmber ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("foreignObject", { x: mx - labelW / 2, y: my - 12, width: labelW, height: 22, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3237
3048
|
"input",
|
|
3238
3049
|
{
|
|
3239
3050
|
autoFocus: true,
|
|
@@ -3245,80 +3056,664 @@ function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, on
|
|
|
3245
3056
|
e.preventDefault();
|
|
3246
3057
|
onEditCommit?.();
|
|
3247
3058
|
}
|
|
3248
|
-
if (e.key === "Escape") {
|
|
3249
|
-
e.preventDefault();
|
|
3250
|
-
onEditCancel?.();
|
|
3059
|
+
if (e.key === "Escape") {
|
|
3060
|
+
e.preventDefault();
|
|
3061
|
+
onEditCancel?.();
|
|
3062
|
+
}
|
|
3063
|
+
},
|
|
3064
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
3065
|
+
style: {
|
|
3066
|
+
width: "100%",
|
|
3067
|
+
height: "100%",
|
|
3068
|
+
border: "none",
|
|
3069
|
+
borderRadius: 6,
|
|
3070
|
+
outline: `2px solid ${acc.color}`,
|
|
3071
|
+
textAlign: "center",
|
|
3072
|
+
fontSize: 10,
|
|
3073
|
+
fontWeight: 500,
|
|
3074
|
+
background: t.inputBg,
|
|
3075
|
+
color: t.inputText,
|
|
3076
|
+
boxSizing: "border-box",
|
|
3077
|
+
padding: "0 6px",
|
|
3078
|
+
fontFamily: "inherit"
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
) }) : edge.label && !isAmber ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
3082
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3083
|
+
"rect",
|
|
3084
|
+
{
|
|
3085
|
+
x: mx - labelW / 2,
|
|
3086
|
+
y: my - 11,
|
|
3087
|
+
width: labelW,
|
|
3088
|
+
height: 19,
|
|
3089
|
+
rx: 5,
|
|
3090
|
+
fill: t.panelBg,
|
|
3091
|
+
stroke: t.cardBorder,
|
|
3092
|
+
strokeWidth: 1,
|
|
3093
|
+
style: STYLE_EDGE_LABEL_HIT
|
|
3094
|
+
}
|
|
3095
|
+
),
|
|
3096
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3097
|
+
"text",
|
|
3098
|
+
{
|
|
3099
|
+
x: mx,
|
|
3100
|
+
y: my + 4,
|
|
3101
|
+
textAnchor: "middle",
|
|
3102
|
+
fontSize: 10,
|
|
3103
|
+
fill: t.textSecondary,
|
|
3104
|
+
fontFamily: "ui-sans-serif,system-ui,sans-serif",
|
|
3105
|
+
fontWeight: "500",
|
|
3106
|
+
style: STYLE_LABEL,
|
|
3107
|
+
children: edge.label
|
|
3108
|
+
}
|
|
3109
|
+
)
|
|
3110
|
+
] }) : null
|
|
3111
|
+
]
|
|
3112
|
+
}
|
|
3113
|
+
);
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
// src/ui/Minimap.tsx
|
|
3117
|
+
var import_react13 = require("react");
|
|
3118
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
3119
|
+
var W = 168;
|
|
3120
|
+
var H = 112;
|
|
3121
|
+
var PAD = 18;
|
|
3122
|
+
function Minimap({
|
|
3123
|
+
model,
|
|
3124
|
+
viewportW,
|
|
3125
|
+
viewportH,
|
|
3126
|
+
transform,
|
|
3127
|
+
measureNode,
|
|
3128
|
+
onCenterOn,
|
|
3129
|
+
isDark,
|
|
3130
|
+
accentColor
|
|
3131
|
+
}) {
|
|
3132
|
+
const dragRef = (0, import_react13.useRef)(null);
|
|
3133
|
+
const boxes = model.nodes.map((n) => {
|
|
3134
|
+
const { w, h } = measureNode(n);
|
|
3135
|
+
return { id: n.id, x: n.x ?? 0, y: n.y ?? 0, w, h };
|
|
3136
|
+
});
|
|
3137
|
+
if (boxes.length === 0) return null;
|
|
3138
|
+
const vx = -transform.x / transform.scale;
|
|
3139
|
+
const vy = -transform.y / transform.scale;
|
|
3140
|
+
const vw = viewportW / transform.scale;
|
|
3141
|
+
const vh = viewportH / transform.scale;
|
|
3142
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
3143
|
+
for (const b of boxes) {
|
|
3144
|
+
minX = Math.min(minX, b.x);
|
|
3145
|
+
minY = Math.min(minY, b.y);
|
|
3146
|
+
maxX = Math.max(maxX, b.x + b.w);
|
|
3147
|
+
maxY = Math.max(maxY, b.y + b.h);
|
|
3148
|
+
}
|
|
3149
|
+
minX = Math.min(minX, vx);
|
|
3150
|
+
minY = Math.min(minY, vy);
|
|
3151
|
+
maxX = Math.max(maxX, vx + vw);
|
|
3152
|
+
maxY = Math.max(maxY, vy + vh);
|
|
3153
|
+
const contentW = Math.max(1, maxX - minX);
|
|
3154
|
+
const contentH = Math.max(1, maxY - minY);
|
|
3155
|
+
const scale = Math.min((W - PAD * 2) / contentW, (H - PAD * 2) / contentH);
|
|
3156
|
+
const offsetX = (W - contentW * scale) / 2 - minX * scale;
|
|
3157
|
+
const offsetY = (H - contentH * scale) / 2 - minY * scale;
|
|
3158
|
+
const project = (x, y) => ({
|
|
3159
|
+
x: offsetX + x * scale,
|
|
3160
|
+
y: offsetY + y * scale
|
|
3161
|
+
});
|
|
3162
|
+
const unproject = (mx, my) => ({
|
|
3163
|
+
x: (mx - offsetX) / scale,
|
|
3164
|
+
y: (my - offsetY) / scale
|
|
3165
|
+
});
|
|
3166
|
+
const panTo = (0, import_react13.useCallback)((e) => {
|
|
3167
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
3168
|
+
const mx = e.clientX - rect.left;
|
|
3169
|
+
const my = e.clientY - rect.top;
|
|
3170
|
+
const { x, y } = unproject(mx, my);
|
|
3171
|
+
onCenterOn(x, y);
|
|
3172
|
+
}, [onCenterOn, scale, offsetX, offsetY]);
|
|
3173
|
+
const onMouseDown = (e) => {
|
|
3174
|
+
e.stopPropagation();
|
|
3175
|
+
dragRef.current = { active: true };
|
|
3176
|
+
panTo(e);
|
|
3177
|
+
};
|
|
3178
|
+
const onMouseMove = (e) => {
|
|
3179
|
+
if (!dragRef.current?.active) return;
|
|
3180
|
+
panTo(e);
|
|
3181
|
+
};
|
|
3182
|
+
const onMouseUp = () => {
|
|
3183
|
+
dragRef.current = null;
|
|
3184
|
+
};
|
|
3185
|
+
const bg = isDark ? "rgba(15,23,42,0.92)" : "rgba(255,255,255,0.94)";
|
|
3186
|
+
const border = isDark ? "#334155" : "#e2e8f0";
|
|
3187
|
+
const nodeFill = isDark ? "#475569" : "#cbd5e1";
|
|
3188
|
+
const viewStroke = accentColor;
|
|
3189
|
+
const viewFill = `${accentColor}22`;
|
|
3190
|
+
const vp1 = project(vx, vy);
|
|
3191
|
+
const vp2 = project(vx + vw, vy + vh);
|
|
3192
|
+
const vpRect = {
|
|
3193
|
+
x: Math.max(0, Math.min(W, vp1.x)),
|
|
3194
|
+
y: Math.max(0, Math.min(H, vp1.y)),
|
|
3195
|
+
w: Math.max(2, Math.min(W, vp2.x) - Math.max(0, vp1.x)),
|
|
3196
|
+
h: Math.max(2, Math.min(H, vp2.y) - Math.max(0, vp1.y))
|
|
3197
|
+
};
|
|
3198
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
3199
|
+
"div",
|
|
3200
|
+
{
|
|
3201
|
+
style: {
|
|
3202
|
+
position: "absolute",
|
|
3203
|
+
bottom: 14,
|
|
3204
|
+
right: 14,
|
|
3205
|
+
background: bg,
|
|
3206
|
+
border: `1px solid ${border}`,
|
|
3207
|
+
borderRadius: 10,
|
|
3208
|
+
padding: 6,
|
|
3209
|
+
boxShadow: isDark ? "0 8px 20px rgba(0,0,0,0.45)" : "0 6px 18px rgba(15,23,42,0.08)",
|
|
3210
|
+
backdropFilter: "blur(6px)"
|
|
3211
|
+
},
|
|
3212
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
3213
|
+
"svg",
|
|
3214
|
+
{
|
|
3215
|
+
width: W,
|
|
3216
|
+
height: H,
|
|
3217
|
+
style: { display: "block", cursor: "grab", borderRadius: 6 },
|
|
3218
|
+
onMouseDown,
|
|
3219
|
+
onMouseMove,
|
|
3220
|
+
onMouseUp,
|
|
3221
|
+
onMouseLeave: onMouseUp,
|
|
3222
|
+
children: [
|
|
3223
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("rect", { width: W, height: H, rx: 6, fill: isDark ? "#0f172a" : "#fafbfc" }),
|
|
3224
|
+
boxes.map((b) => {
|
|
3225
|
+
const p = project(b.x, b.y);
|
|
3226
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
3227
|
+
"rect",
|
|
3228
|
+
{
|
|
3229
|
+
x: p.x,
|
|
3230
|
+
y: p.y,
|
|
3231
|
+
width: Math.max(2, b.w * scale),
|
|
3232
|
+
height: Math.max(2, b.h * scale),
|
|
3233
|
+
rx: 2,
|
|
3234
|
+
fill: nodeFill
|
|
3235
|
+
},
|
|
3236
|
+
b.id
|
|
3237
|
+
);
|
|
3238
|
+
}),
|
|
3239
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
3240
|
+
"rect",
|
|
3241
|
+
{
|
|
3242
|
+
x: vpRect.x,
|
|
3243
|
+
y: vpRect.y,
|
|
3244
|
+
width: vpRect.w,
|
|
3245
|
+
height: vpRect.h,
|
|
3246
|
+
rx: 3,
|
|
3247
|
+
fill: viewFill,
|
|
3248
|
+
stroke: viewStroke,
|
|
3249
|
+
strokeWidth: 1.25
|
|
3250
|
+
}
|
|
3251
|
+
)
|
|
3252
|
+
]
|
|
3253
|
+
}
|
|
3254
|
+
)
|
|
3255
|
+
}
|
|
3256
|
+
);
|
|
3257
|
+
}
|
|
3258
|
+
|
|
3259
|
+
// src/ui/ContextMenu.tsx
|
|
3260
|
+
var import_react14 = require("react");
|
|
3261
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
3262
|
+
function ContextMenu({
|
|
3263
|
+
x,
|
|
3264
|
+
y,
|
|
3265
|
+
nodeId,
|
|
3266
|
+
edgeId,
|
|
3267
|
+
isDark,
|
|
3268
|
+
t,
|
|
3269
|
+
acc,
|
|
3270
|
+
canUndo,
|
|
3271
|
+
canRedo,
|
|
3272
|
+
onUndo,
|
|
3273
|
+
onRedo,
|
|
3274
|
+
onReCenter,
|
|
3275
|
+
onAddNode,
|
|
3276
|
+
onDuplicate,
|
|
3277
|
+
onRename,
|
|
3278
|
+
onDelete,
|
|
3279
|
+
onDisconnect,
|
|
3280
|
+
onEdgeRename,
|
|
3281
|
+
onEdgeStyle,
|
|
3282
|
+
onEdgeArrowhead,
|
|
3283
|
+
onEdgeDelete,
|
|
3284
|
+
onEdgeResetRouting,
|
|
3285
|
+
currentEdgeStyle,
|
|
3286
|
+
currentEdgeArrow,
|
|
3287
|
+
edgeHasWaypoint,
|
|
3288
|
+
containerRef
|
|
3289
|
+
}) {
|
|
3290
|
+
const menuRef = (0, import_react14.useRef)(null);
|
|
3291
|
+
const [pos, setPos] = (0, import_react14.useState)({ x, y });
|
|
3292
|
+
(0, import_react14.useEffect)(() => {
|
|
3293
|
+
if (!menuRef.current || !containerRef.current) return;
|
|
3294
|
+
const m = menuRef.current.getBoundingClientRect();
|
|
3295
|
+
const c = containerRef.current.getBoundingClientRect();
|
|
3296
|
+
let nx = x, ny = y;
|
|
3297
|
+
if (nx + m.width > c.right - 8) nx = x - m.width;
|
|
3298
|
+
if (ny + m.height > c.bottom - 8) ny = y - m.height;
|
|
3299
|
+
setPos({ x: nx, y: ny });
|
|
3300
|
+
}, [x, y, containerRef]);
|
|
3301
|
+
const bg = isDark ? "#1e293b" : "#ffffff";
|
|
3302
|
+
const border = isDark ? "#334155" : "#e2e8f0";
|
|
3303
|
+
const hoverBg = isDark ? "#334155" : "#f1f5f9";
|
|
3304
|
+
const dividerColor = isDark ? "#334155" : "#f1f5f9";
|
|
3305
|
+
const text = t.textPrimary;
|
|
3306
|
+
const muted = t.textMuted;
|
|
3307
|
+
const item = (label, onClick, color, disabled) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
3308
|
+
"button",
|
|
3309
|
+
{
|
|
3310
|
+
onClick: disabled ? void 0 : onClick,
|
|
3311
|
+
style: {
|
|
3312
|
+
display: "flex",
|
|
3313
|
+
alignItems: "center",
|
|
3314
|
+
gap: 10,
|
|
3315
|
+
width: "100%",
|
|
3316
|
+
padding: "7px 14px",
|
|
3317
|
+
background: "none",
|
|
3318
|
+
border: "none",
|
|
3319
|
+
textAlign: "left",
|
|
3320
|
+
cursor: disabled ? "default" : "pointer",
|
|
3321
|
+
fontSize: 12,
|
|
3322
|
+
fontFamily: "ui-sans-serif,system-ui,sans-serif",
|
|
3323
|
+
color: disabled ? muted : color ?? text,
|
|
3324
|
+
opacity: disabled ? 0.4 : 1,
|
|
3325
|
+
borderRadius: 6
|
|
3326
|
+
},
|
|
3327
|
+
onMouseEnter: (e) => {
|
|
3328
|
+
if (!disabled) e.currentTarget.style.background = hoverBg;
|
|
3329
|
+
},
|
|
3330
|
+
onMouseLeave: (e) => {
|
|
3331
|
+
e.currentTarget.style.background = "none";
|
|
3332
|
+
},
|
|
3333
|
+
children: label
|
|
3334
|
+
},
|
|
3335
|
+
label
|
|
3336
|
+
);
|
|
3337
|
+
const divider2 = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { height: 1, background: dividerColor, margin: "4px 0" } });
|
|
3338
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
3339
|
+
"div",
|
|
3340
|
+
{
|
|
3341
|
+
ref: menuRef,
|
|
3342
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
3343
|
+
style: {
|
|
3344
|
+
position: "fixed",
|
|
3345
|
+
left: pos.x,
|
|
3346
|
+
top: pos.y,
|
|
3347
|
+
zIndex: 9999,
|
|
3348
|
+
background: bg,
|
|
3349
|
+
border: `1px solid ${border}`,
|
|
3350
|
+
borderRadius: 10,
|
|
3351
|
+
padding: "5px 0",
|
|
3352
|
+
minWidth: 180,
|
|
3353
|
+
boxShadow: isDark ? "0 8px 32px rgba(0,0,0,0.5)" : "0 8px 32px rgba(0,0,0,0.12)",
|
|
3354
|
+
fontFamily: "ui-sans-serif,system-ui,sans-serif"
|
|
3355
|
+
},
|
|
3356
|
+
children: edgeId ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
|
|
3357
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { padding: "4px 14px 6px", fontSize: 10, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Edge" }),
|
|
3358
|
+
item("Rename label (dbl-click)", () => onEdgeRename?.()),
|
|
3359
|
+
divider2,
|
|
3360
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { padding: "4px 14px 2px", fontSize: 9, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Style" }),
|
|
3361
|
+
item(`Solid${currentEdgeStyle === "solid" || !currentEdgeStyle ? " \u2713" : ""}`, () => onEdgeStyle?.("solid")),
|
|
3362
|
+
item(`Dashed${currentEdgeStyle === "dashed" ? " \u2713" : ""}`, () => onEdgeStyle?.("dashed")),
|
|
3363
|
+
item(`Dotted${currentEdgeStyle === "dotted" ? " \u2713" : ""}`, () => onEdgeStyle?.("dotted")),
|
|
3364
|
+
divider2,
|
|
3365
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { padding: "4px 14px 2px", fontSize: 9, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Arrowhead" }),
|
|
3366
|
+
item(`Arrow${currentEdgeArrow !== "none" ? " \u2713" : ""}`, () => onEdgeArrowhead?.("arrow")),
|
|
3367
|
+
item(`None${currentEdgeArrow === "none" ? " \u2713" : ""}`, () => onEdgeArrowhead?.("none")),
|
|
3368
|
+
divider2,
|
|
3369
|
+
item("Reset routing", () => onEdgeResetRouting?.(), void 0, !edgeHasWaypoint),
|
|
3370
|
+
item("Delete edge", () => onEdgeDelete?.(), "#ef4444")
|
|
3371
|
+
] }) : nodeId ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
|
|
3372
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { padding: "4px 14px 6px", fontSize: 10, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Node" }),
|
|
3373
|
+
item("Rename (dbl-click)", onRename),
|
|
3374
|
+
item("Duplicate", onDuplicate),
|
|
3375
|
+
item("Disconnect all edges", onDisconnect),
|
|
3376
|
+
divider2,
|
|
3377
|
+
item("Delete node", onDelete, "#ef4444")
|
|
3378
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
|
|
3379
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { padding: "4px 14px 6px", fontSize: 10, fontWeight: 700, color: muted, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Canvas" }),
|
|
3380
|
+
item("Add node here", onAddNode, acc.color),
|
|
3381
|
+
item("Re-center (Ctrl+0)", onReCenter),
|
|
3382
|
+
divider2,
|
|
3383
|
+
item("Undo (Ctrl+Z)", onUndo, void 0, !canUndo),
|
|
3384
|
+
item("Redo (Ctrl+Y)", onRedo, void 0, !canRedo)
|
|
3385
|
+
] })
|
|
3386
|
+
}
|
|
3387
|
+
);
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3390
|
+
// src/ui/DiagramCanvas.tsx
|
|
3391
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
3392
|
+
var STYLE_LABEL2 = { pointerEvents: "none", userSelect: "none" };
|
|
3393
|
+
var STYLE_LIVE_PORT = { opacity: 0.85, pointerEvents: "none" };
|
|
3394
|
+
var STYLE_NODE_GRAB = { cursor: "grab" };
|
|
3395
|
+
var STYLE_NODE_GRABBING = { cursor: "grabbing" };
|
|
3396
|
+
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))" };
|
|
3397
|
+
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))" };
|
|
3398
|
+
function DiagramCanvas(props) {
|
|
3399
|
+
const {
|
|
3400
|
+
model,
|
|
3401
|
+
variant,
|
|
3402
|
+
variantLabel,
|
|
3403
|
+
t,
|
|
3404
|
+
isDark,
|
|
3405
|
+
acc,
|
|
3406
|
+
transform,
|
|
3407
|
+
setTransform,
|
|
3408
|
+
selected,
|
|
3409
|
+
selectedSet,
|
|
3410
|
+
hoveredId,
|
|
3411
|
+
setHoveredId,
|
|
3412
|
+
drag,
|
|
3413
|
+
pan,
|
|
3414
|
+
liveEdge,
|
|
3415
|
+
boxSel,
|
|
3416
|
+
alignGuides,
|
|
3417
|
+
editingEdgeId,
|
|
3418
|
+
editEdgeLabel,
|
|
3419
|
+
setEditEdgeLabel,
|
|
3420
|
+
commitEdgeEdit,
|
|
3421
|
+
setEditingEdgeId,
|
|
3422
|
+
beginEditEdge,
|
|
3423
|
+
onEdgeContextMenu,
|
|
3424
|
+
setWaypointDrag,
|
|
3425
|
+
editingId,
|
|
3426
|
+
editLabel,
|
|
3427
|
+
setEditLabel,
|
|
3428
|
+
commitEdit,
|
|
3429
|
+
setEditingId,
|
|
3430
|
+
onNodeMouseDown,
|
|
3431
|
+
onNodeMouseUp,
|
|
3432
|
+
onNodeDblClick,
|
|
3433
|
+
onNodeContextMenu,
|
|
3434
|
+
onPortMouseDown,
|
|
3435
|
+
onAnswerPortDown,
|
|
3436
|
+
onSvgMouseDown,
|
|
3437
|
+
onMouseMove,
|
|
3438
|
+
onMouseUp,
|
|
3439
|
+
onSvgContextMenu,
|
|
3440
|
+
reducedMotion,
|
|
3441
|
+
isCoarse,
|
|
3442
|
+
portR,
|
|
3443
|
+
shadowClr,
|
|
3444
|
+
arrowClr,
|
|
3445
|
+
amberArrow,
|
|
3446
|
+
viewport,
|
|
3447
|
+
svgRef,
|
|
3448
|
+
containerRef,
|
|
3449
|
+
ctxMenu,
|
|
3450
|
+
history,
|
|
3451
|
+
onCtxUndo,
|
|
3452
|
+
onCtxRedo,
|
|
3453
|
+
onCtxReCenter,
|
|
3454
|
+
onCtxAddNode,
|
|
3455
|
+
onCtxDuplicate,
|
|
3456
|
+
onCtxRename,
|
|
3457
|
+
onCtxDelete,
|
|
3458
|
+
onCtxDisconnect,
|
|
3459
|
+
ctxEdgeStyle,
|
|
3460
|
+
ctxEdgeArrow,
|
|
3461
|
+
ctxEdgeHasWaypoint,
|
|
3462
|
+
onCtxEdgeRename,
|
|
3463
|
+
onCtxEdgeStyle,
|
|
3464
|
+
onCtxEdgeArrowhead,
|
|
3465
|
+
onCtxEdgeDelete,
|
|
3466
|
+
onCtxEdgeResetRouting
|
|
3467
|
+
} = props;
|
|
3468
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { ref: containerRef, style: { flex: 1, overflow: "hidden", position: "relative", background: t.canvas }, children: [
|
|
3469
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
3470
|
+
"svg",
|
|
3471
|
+
{
|
|
3472
|
+
ref: svgRef,
|
|
3473
|
+
width: "100%",
|
|
3474
|
+
height: "100%",
|
|
3475
|
+
role: "application",
|
|
3476
|
+
"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.`,
|
|
3477
|
+
tabIndex: 0,
|
|
3478
|
+
style: { display: "block", cursor: pan ? "grabbing" : drag ? "grabbing" : liveEdge ? "crosshair" : "default", userSelect: "none", outline: "none" },
|
|
3479
|
+
onMouseDown: onSvgMouseDown,
|
|
3480
|
+
onMouseMove,
|
|
3481
|
+
onMouseUp,
|
|
3482
|
+
onMouseLeave: onMouseUp,
|
|
3483
|
+
onContextMenu: onSvgContextMenu,
|
|
3484
|
+
children: [
|
|
3485
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("defs", { children: [
|
|
3486
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("style", { children: reducedMotion ? `
|
|
3487
|
+
.edge-flow { stroke-dasharray: 0; }
|
|
3488
|
+
.edge-flow-amber { stroke-dasharray: 0; }
|
|
3489
|
+
.edge-live { stroke-dasharray: 4 4; }
|
|
3490
|
+
` : `
|
|
3491
|
+
@keyframes edgeFlow { to { stroke-dashoffset: -13; } }
|
|
3492
|
+
@keyframes edgeFlowFast { to { stroke-dashoffset: -13; } }
|
|
3493
|
+
.edge-flow { stroke-dasharray: 8 5; animation: edgeFlow 0.9s linear infinite; }
|
|
3494
|
+
.edge-flow-amber { stroke-dasharray: 6 4; animation: edgeFlowFast 0.65s linear infinite; }
|
|
3495
|
+
.edge-live { stroke-dasharray: 7 5; animation: edgeFlow 0.55s linear infinite; }
|
|
3496
|
+
` }),
|
|
3497
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("pattern", { id: "dots", width: GRID, height: GRID, patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("circle", { cx: GRID / 2, cy: GRID / 2, r: 1.1, fill: t.dot }) }),
|
|
3498
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("filter", { id: "nodeShadow", x: "-25%", y: "-25%", width: "150%", height: "160%", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("feDropShadow", { dx: "0", dy: "3", stdDeviation: "5", floodColor: shadowClr, floodOpacity: "1" }) }),
|
|
3499
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("marker", { id: "arrowhead", markerWidth: "9", markerHeight: "7", refX: "8", refY: "3.5", orient: "auto", markerUnits: "strokeWidth", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: arrowClr }) }),
|
|
3500
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("marker", { id: "arrowAmber", markerWidth: "9", markerHeight: "7", refX: "8", refY: "3.5", orient: "auto", markerUnits: "strokeWidth", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: amberArrow }) }),
|
|
3501
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("marker", { id: "arrowLive", markerWidth: "9", markerHeight: "7", refX: "8", refY: "3.5", orient: "auto", markerUnits: "strokeWidth", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: acc.color }) })
|
|
3502
|
+
] }),
|
|
3503
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("rect", { width: "100%", height: "100%", fill: "url(#dots)", "data-bg": "1" }),
|
|
3504
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("g", { transform: `translate(${transform.x},${transform.y}) scale(${transform.scale})`, children: [
|
|
3505
|
+
model.edges.map((e) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
3506
|
+
EdgeLine,
|
|
3507
|
+
{
|
|
3508
|
+
edge: e,
|
|
3509
|
+
nodes: model.nodes,
|
|
3510
|
+
variant,
|
|
3511
|
+
t,
|
|
3512
|
+
isDark,
|
|
3513
|
+
acc,
|
|
3514
|
+
editing: editingEdgeId === e.id,
|
|
3515
|
+
editValue: editEdgeLabel,
|
|
3516
|
+
onEditChange: setEditEdgeLabel,
|
|
3517
|
+
onEditCommit: commitEdgeEdit,
|
|
3518
|
+
onEditCancel: () => setEditingEdgeId(null),
|
|
3519
|
+
onDoubleClick: beginEditEdge,
|
|
3520
|
+
onContextMenu: onEdgeContextMenu,
|
|
3521
|
+
onWaypointDown: (ev, edgeId) => setWaypointDrag(edgeId)
|
|
3522
|
+
},
|
|
3523
|
+
e.id
|
|
3524
|
+
)),
|
|
3525
|
+
liveEdge && (() => {
|
|
3526
|
+
const d = bezierPath2(liveEdge.fromX, liveEdge.fromY, liveEdge.toX, liveEdge.toY, liveEdge.exitDir);
|
|
3527
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d, fill: "none", stroke: acc.color, strokeWidth: 2, strokeLinecap: "round", className: "edge-live", opacity: 0.8, markerEnd: "url(#arrowLive)" });
|
|
3528
|
+
})(),
|
|
3529
|
+
alignGuides?.x && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
3530
|
+
"line",
|
|
3531
|
+
{
|
|
3532
|
+
x1: alignGuides.x.pos,
|
|
3533
|
+
x2: alignGuides.x.pos,
|
|
3534
|
+
y1: alignGuides.x.minY,
|
|
3535
|
+
y2: alignGuides.x.maxY,
|
|
3536
|
+
stroke: acc.color,
|
|
3537
|
+
strokeWidth: 1 / transform.scale,
|
|
3538
|
+
strokeDasharray: `${4 / transform.scale} ${3 / transform.scale}`,
|
|
3539
|
+
opacity: 0.85,
|
|
3540
|
+
pointerEvents: "none"
|
|
3541
|
+
}
|
|
3542
|
+
),
|
|
3543
|
+
alignGuides?.y && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
3544
|
+
"line",
|
|
3545
|
+
{
|
|
3546
|
+
y1: alignGuides.y.pos,
|
|
3547
|
+
y2: alignGuides.y.pos,
|
|
3548
|
+
x1: alignGuides.y.minX,
|
|
3549
|
+
x2: alignGuides.y.maxX,
|
|
3550
|
+
stroke: acc.color,
|
|
3551
|
+
strokeWidth: 1 / transform.scale,
|
|
3552
|
+
strokeDasharray: `${4 / transform.scale} ${3 / transform.scale}`,
|
|
3553
|
+
opacity: 0.85,
|
|
3554
|
+
pointerEvents: "none"
|
|
3251
3555
|
}
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3556
|
+
),
|
|
3557
|
+
model.nodes.map((node, idx) => {
|
|
3558
|
+
const isHovered = hoveredId === node.id;
|
|
3559
|
+
const isQuestion2 = variant === "question";
|
|
3560
|
+
const { w: nW } = nodeDims(node, variant);
|
|
3561
|
+
const isSelected = selectedSet.has(node.id);
|
|
3562
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
3563
|
+
"g",
|
|
3564
|
+
{
|
|
3565
|
+
transform: `translate(${node.x ?? 0},${node.y ?? 0})`,
|
|
3566
|
+
role: "button",
|
|
3567
|
+
"aria-label": `${variantLabel} ${variant === "journey" ? idx + 1 + ": " : ""}${node.label}${isSelected ? ", selected" : ""}`,
|
|
3568
|
+
style: drag?.nodeId === node.id ? STYLE_NODE_GRABBING : STYLE_NODE_GRAB,
|
|
3569
|
+
onMouseDown: (e) => onNodeMouseDown(e, node.id),
|
|
3570
|
+
onMouseUp: (e) => onNodeMouseUp(e, node.id),
|
|
3571
|
+
onDoubleClick: (e) => onNodeDblClick(e, node.id),
|
|
3572
|
+
onContextMenu: (e) => onNodeContextMenu(e, node.id),
|
|
3573
|
+
onMouseEnter: () => setHoveredId(node.id),
|
|
3574
|
+
onMouseLeave: () => setHoveredId(null),
|
|
3575
|
+
children: [
|
|
3576
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("title", { children: `${variantLabel}: ${node.label}` }),
|
|
3577
|
+
isQuestion2 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(QuestionNode, { node, selected: isSelected, edges: model.edges, isDark, onAnswerPortDown, qW: nW }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
|
|
3578
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(NodeShape, { node, selected: isSelected, variant, stepNumber: variant === "journey" ? idx + 1 : void 0, t, isDark, w: nW }),
|
|
3579
|
+
editingId === node.id ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("foreignObject", { x: 6, y: 6, width: nW - 12, height: NODE_H2 - 12, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
3580
|
+
"input",
|
|
3581
|
+
{
|
|
3582
|
+
autoFocus: true,
|
|
3583
|
+
value: editLabel,
|
|
3584
|
+
onChange: (e) => setEditLabel(e.target.value),
|
|
3585
|
+
onBlur: commitEdit,
|
|
3586
|
+
onKeyDown: (e) => {
|
|
3587
|
+
if (e.key === "Enter") commitEdit();
|
|
3588
|
+
if (e.key === "Escape") setEditingId(null);
|
|
3589
|
+
},
|
|
3590
|
+
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 }
|
|
3591
|
+
}
|
|
3592
|
+
) }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("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 }),
|
|
3593
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
3594
|
+
"circle",
|
|
3595
|
+
{
|
|
3596
|
+
cx: nW / 2,
|
|
3597
|
+
cy: NODE_H2 + 1,
|
|
3598
|
+
r: portR,
|
|
3599
|
+
fill: acc.color,
|
|
3600
|
+
stroke: isDark ? "#0f172a" : "white",
|
|
3601
|
+
strokeWidth: 2,
|
|
3602
|
+
style: isHovered || isCoarse ? STYLE_PORT_VISIBLE : STYLE_PORT_HIDDEN,
|
|
3603
|
+
onMouseDown: (e) => onPortMouseDown(e, node.id)
|
|
3604
|
+
}
|
|
3605
|
+
)
|
|
3606
|
+
] }),
|
|
3607
|
+
liveEdge && liveEdge.fromId !== node.id && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("circle", { cx: nW / 2, cy: -1, r: portR, fill: acc.color, stroke: isDark ? "#0f172a" : "white", strokeWidth: 2, style: STYLE_LIVE_PORT })
|
|
3608
|
+
]
|
|
3609
|
+
},
|
|
3610
|
+
node.id
|
|
3611
|
+
);
|
|
3612
|
+
})
|
|
3613
|
+
] })
|
|
3614
|
+
]
|
|
3615
|
+
}
|
|
3616
|
+
),
|
|
3617
|
+
boxSel && Math.abs(boxSel.cx - boxSel.sx) + Math.abs(boxSel.cy - boxSel.sy) > 4 && containerRef.current && (() => {
|
|
3618
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
3619
|
+
const left = Math.min(boxSel.sx, boxSel.cx) - rect.left;
|
|
3620
|
+
const top = Math.min(boxSel.sy, boxSel.cy) - rect.top;
|
|
3621
|
+
const w = Math.abs(boxSel.cx - boxSel.sx);
|
|
3622
|
+
const h = Math.abs(boxSel.cy - boxSel.sy);
|
|
3623
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
3624
|
+
"div",
|
|
3625
|
+
{
|
|
3626
|
+
style: {
|
|
3627
|
+
position: "absolute",
|
|
3628
|
+
left,
|
|
3629
|
+
top,
|
|
3630
|
+
width: w,
|
|
3631
|
+
height: h,
|
|
3632
|
+
border: `1px dashed ${acc.color}`,
|
|
3633
|
+
background: isDark ? "rgba(99,102,241,0.10)" : "rgba(99,102,241,0.08)",
|
|
3634
|
+
pointerEvents: "none",
|
|
3635
|
+
borderRadius: 4
|
|
3269
3636
|
}
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3637
|
+
}
|
|
3638
|
+
);
|
|
3639
|
+
})(),
|
|
3640
|
+
model.nodes.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { position: "absolute", inset: 0, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", pointerEvents: "none", gap: 8 }, children: [
|
|
3641
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { fontSize: 36, opacity: 0.1, color: t.textPrimary }, children: variant === "question" ? "?" : variant === "journey" ? "\u2197" : "\u2B21" }),
|
|
3642
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { fontSize: 13, color: t.textMuted, fontWeight: 500 }, children: [
|
|
3643
|
+
"Click ",
|
|
3644
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("strong", { style: { color: acc.color }, children: [
|
|
3645
|
+
"+ ",
|
|
3646
|
+
variantLabel
|
|
3647
|
+
] }),
|
|
3648
|
+
" to start"
|
|
3649
|
+
] })
|
|
3650
|
+
] }),
|
|
3651
|
+
model.nodes.length > 0 && viewport.w > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
3652
|
+
Minimap,
|
|
3653
|
+
{
|
|
3654
|
+
model,
|
|
3655
|
+
viewportW: viewport.w,
|
|
3656
|
+
viewportH: viewport.h,
|
|
3657
|
+
transform,
|
|
3658
|
+
isDark,
|
|
3659
|
+
accentColor: acc.color,
|
|
3660
|
+
measureNode: (n) => nodeDims(n, variant),
|
|
3661
|
+
onCenterOn: (cx, cy) => {
|
|
3662
|
+
setTransform((tr) => ({ ...tr, x: viewport.w / 2 - cx * tr.scale, y: viewport.h / 2 - cy * tr.scale }));
|
|
3663
|
+
}
|
|
3664
|
+
}
|
|
3665
|
+
),
|
|
3666
|
+
ctxMenu && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
3667
|
+
ContextMenu,
|
|
3668
|
+
{
|
|
3669
|
+
x: ctxMenu.x,
|
|
3670
|
+
y: ctxMenu.y,
|
|
3671
|
+
nodeId: ctxMenu.nodeId,
|
|
3672
|
+
edgeId: ctxMenu.edgeId,
|
|
3673
|
+
isDark,
|
|
3674
|
+
t,
|
|
3675
|
+
acc,
|
|
3676
|
+
canUndo: history.canUndo,
|
|
3677
|
+
canRedo: history.canRedo,
|
|
3678
|
+
onUndo: onCtxUndo,
|
|
3679
|
+
onRedo: onCtxRedo,
|
|
3680
|
+
onReCenter: onCtxReCenter,
|
|
3681
|
+
onAddNode: onCtxAddNode,
|
|
3682
|
+
onDuplicate: onCtxDuplicate,
|
|
3683
|
+
onRename: onCtxRename,
|
|
3684
|
+
onDelete: onCtxDelete,
|
|
3685
|
+
onDisconnect: onCtxDisconnect,
|
|
3686
|
+
currentEdgeStyle: ctxEdgeStyle,
|
|
3687
|
+
currentEdgeArrow: ctxEdgeArrow,
|
|
3688
|
+
edgeHasWaypoint: ctxEdgeHasWaypoint,
|
|
3689
|
+
onEdgeRename: onCtxEdgeRename,
|
|
3690
|
+
onEdgeStyle: onCtxEdgeStyle,
|
|
3691
|
+
onEdgeArrowhead: onCtxEdgeArrowhead,
|
|
3692
|
+
onEdgeDelete: onCtxEdgeDelete,
|
|
3693
|
+
onEdgeResetRouting: onCtxEdgeResetRouting,
|
|
3694
|
+
containerRef
|
|
3695
|
+
}
|
|
3696
|
+
)
|
|
3697
|
+
] });
|
|
3303
3698
|
}
|
|
3304
3699
|
|
|
3305
3700
|
// src/ui/hooks/useHistory.ts
|
|
3306
|
-
var
|
|
3701
|
+
var import_react15 = require("react");
|
|
3307
3702
|
var MAX_HISTORY = 80;
|
|
3308
3703
|
function useHistory(initial, onChange) {
|
|
3309
|
-
const [state, setState] = (0,
|
|
3310
|
-
const stackRef = (0,
|
|
3311
|
-
const idxRef = (0,
|
|
3312
|
-
const [, setTick] = (0,
|
|
3704
|
+
const [state, setState] = (0, import_react15.useState)(initial);
|
|
3705
|
+
const stackRef = (0, import_react15.useRef)([initial]);
|
|
3706
|
+
const idxRef = (0, import_react15.useRef)(0);
|
|
3707
|
+
const [, setTick] = (0, import_react15.useState)(0);
|
|
3313
3708
|
const bump = () => setTick((n) => n + 1);
|
|
3314
|
-
const apply = (0,
|
|
3709
|
+
const apply = (0, import_react15.useCallback)(
|
|
3315
3710
|
(next) => {
|
|
3316
3711
|
setState(next);
|
|
3317
3712
|
onChange?.(next);
|
|
3318
3713
|
},
|
|
3319
3714
|
[onChange]
|
|
3320
3715
|
);
|
|
3321
|
-
const applyAndPush = (0,
|
|
3716
|
+
const applyAndPush = (0, import_react15.useCallback)(
|
|
3322
3717
|
(next) => {
|
|
3323
3718
|
const stack = stackRef.current.slice(0, idxRef.current + 1);
|
|
3324
3719
|
stack.push(next);
|
|
@@ -3331,7 +3726,7 @@ function useHistory(initial, onChange) {
|
|
|
3331
3726
|
},
|
|
3332
3727
|
[onChange]
|
|
3333
3728
|
);
|
|
3334
|
-
const undo = (0,
|
|
3729
|
+
const undo = (0, import_react15.useCallback)(() => {
|
|
3335
3730
|
if (idxRef.current <= 0) return;
|
|
3336
3731
|
idxRef.current--;
|
|
3337
3732
|
const next = stackRef.current[idxRef.current];
|
|
@@ -3339,7 +3734,7 @@ function useHistory(initial, onChange) {
|
|
|
3339
3734
|
onChange?.(next);
|
|
3340
3735
|
bump();
|
|
3341
3736
|
}, [onChange]);
|
|
3342
|
-
const redo = (0,
|
|
3737
|
+
const redo = (0, import_react15.useCallback)(() => {
|
|
3343
3738
|
if (idxRef.current >= stackRef.current.length - 1) return;
|
|
3344
3739
|
idxRef.current++;
|
|
3345
3740
|
const next = stackRef.current[idxRef.current];
|
|
@@ -3359,10 +3754,10 @@ function useHistory(initial, onChange) {
|
|
|
3359
3754
|
}
|
|
3360
3755
|
|
|
3361
3756
|
// src/ui/hooks/useCanvasWheel.ts
|
|
3362
|
-
var
|
|
3757
|
+
var import_react16 = require("react");
|
|
3363
3758
|
function useCanvasWheel(ref, setTransform, options = {}) {
|
|
3364
3759
|
const { min = 0.15, max = 3, factor = 0.1 } = options;
|
|
3365
|
-
(0,
|
|
3760
|
+
(0, import_react16.useEffect)(() => {
|
|
3366
3761
|
const el = ref.current;
|
|
3367
3762
|
if (!el) return;
|
|
3368
3763
|
const onWheel = (e) => {
|
|
@@ -3386,7 +3781,7 @@ function useCanvasWheel(ref, setTransform, options = {}) {
|
|
|
3386
3781
|
}
|
|
3387
3782
|
|
|
3388
3783
|
// src/ui/hooks/useCanvasTouch.ts
|
|
3389
|
-
var
|
|
3784
|
+
var import_react17 = require("react");
|
|
3390
3785
|
function useCanvasTouch(ref, {
|
|
3391
3786
|
transform,
|
|
3392
3787
|
setTransform,
|
|
@@ -3396,7 +3791,7 @@ function useCanvasTouch(ref, {
|
|
|
3396
3791
|
longPressMs = 550,
|
|
3397
3792
|
longPressSlop = 8
|
|
3398
3793
|
}) {
|
|
3399
|
-
(0,
|
|
3794
|
+
(0, import_react17.useEffect)(() => {
|
|
3400
3795
|
const el = ref.current;
|
|
3401
3796
|
if (!el) return;
|
|
3402
3797
|
let touchPan = null;
|
|
@@ -3500,10 +3895,10 @@ function useCanvasTouch(ref, {
|
|
|
3500
3895
|
}
|
|
3501
3896
|
|
|
3502
3897
|
// src/ui/hooks/useElementSize.ts
|
|
3503
|
-
var
|
|
3898
|
+
var import_react18 = require("react");
|
|
3504
3899
|
function useElementSize(ref) {
|
|
3505
|
-
const [size, setSize] = (0,
|
|
3506
|
-
(0,
|
|
3900
|
+
const [size, setSize] = (0, import_react18.useState)({ w: 0, h: 0 });
|
|
3901
|
+
(0, import_react18.useEffect)(() => {
|
|
3507
3902
|
const el = ref.current;
|
|
3508
3903
|
if (!el || typeof ResizeObserver === "undefined") return;
|
|
3509
3904
|
const measure = () => {
|
|
@@ -3609,14 +4004,12 @@ function nearestInDirection(fromX, fromY, dir, candidates) {
|
|
|
3609
4004
|
}
|
|
3610
4005
|
|
|
3611
4006
|
// src/ui/DiagramEditor.tsx
|
|
3612
|
-
var
|
|
3613
|
-
var STYLE_LABEL2 = { pointerEvents: "none", userSelect: "none" };
|
|
3614
|
-
var STYLE_LIVE_PORT = { opacity: 0.85, pointerEvents: "none" };
|
|
4007
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
3615
4008
|
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 };
|
|
3616
4009
|
var STYLE_FLEX_ROW = { flex: 1, display: "flex", overflow: "hidden" };
|
|
3617
4010
|
function DiagramEditor(props) {
|
|
3618
4011
|
if (props.initialModel?.type === "sequence") {
|
|
3619
|
-
return /* @__PURE__ */ (0,
|
|
4012
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3620
4013
|
SequenceEditor,
|
|
3621
4014
|
{
|
|
3622
4015
|
initialModel: props.initialModel,
|
|
@@ -3630,7 +4023,7 @@ function DiagramEditor(props) {
|
|
|
3630
4023
|
}
|
|
3631
4024
|
);
|
|
3632
4025
|
}
|
|
3633
|
-
return /* @__PURE__ */ (0,
|
|
4026
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(FlowchartEditor, { ...props });
|
|
3634
4027
|
}
|
|
3635
4028
|
function FlowchartEditor({
|
|
3636
4029
|
initialModel,
|
|
@@ -3644,25 +4037,25 @@ function FlowchartEditor({
|
|
|
3644
4037
|
themeOverrides
|
|
3645
4038
|
}) {
|
|
3646
4039
|
const base = initialModel ? { ...initialModel, variant: initialModel.variant ?? variant } : presetFlowchartModel(variant);
|
|
3647
|
-
const notify = (0,
|
|
4040
|
+
const notify = (0, import_react19.useCallback)((m) => onChange?.(m), [onChange]);
|
|
3648
4041
|
const history = useHistory(base, notify);
|
|
3649
4042
|
const { state: model, apply: applyModel, applyAndPush, undo, redo } = history;
|
|
3650
|
-
const [transform, setTransform] = (0,
|
|
3651
|
-
const [selected, setSelected] = (0,
|
|
3652
|
-
const [selectedSet, setSelectedSet] = (0,
|
|
3653
|
-
const [drag, setDrag] = (0,
|
|
3654
|
-
const [pan, setPan] = (0,
|
|
3655
|
-
const [boxSel, setBoxSel] = (0,
|
|
3656
|
-
const [liveEdge, setLiveEdge] = (0,
|
|
3657
|
-
const [alignGuides, setAlignGuides] = (0,
|
|
3658
|
-
const [waypointDrag, setWaypointDrag] = (0,
|
|
3659
|
-
const groupDragOriginsRef = (0,
|
|
3660
|
-
const clipboardRef = (0,
|
|
3661
|
-
const selectOne = (0,
|
|
4043
|
+
const [transform, setTransform] = (0, import_react19.useState)({ x: 60, y: 60, scale: 1 });
|
|
4044
|
+
const [selected, setSelected] = (0, import_react19.useState)(null);
|
|
4045
|
+
const [selectedSet, setSelectedSet] = (0, import_react19.useState)(() => /* @__PURE__ */ new Set());
|
|
4046
|
+
const [drag, setDrag] = (0, import_react19.useState)(null);
|
|
4047
|
+
const [pan, setPan] = (0, import_react19.useState)(null);
|
|
4048
|
+
const [boxSel, setBoxSel] = (0, import_react19.useState)(null);
|
|
4049
|
+
const [liveEdge, setLiveEdge] = (0, import_react19.useState)(null);
|
|
4050
|
+
const [alignGuides, setAlignGuides] = (0, import_react19.useState)(null);
|
|
4051
|
+
const [waypointDrag, setWaypointDrag] = (0, import_react19.useState)(null);
|
|
4052
|
+
const groupDragOriginsRef = (0, import_react19.useRef)(null);
|
|
4053
|
+
const clipboardRef = (0, import_react19.useRef)(null);
|
|
4054
|
+
const selectOne = (0, import_react19.useCallback)((id) => {
|
|
3662
4055
|
setSelected(id);
|
|
3663
4056
|
setSelectedSet(id ? /* @__PURE__ */ new Set([id]) : /* @__PURE__ */ new Set());
|
|
3664
4057
|
}, []);
|
|
3665
|
-
const toggleSelect = (0,
|
|
4058
|
+
const toggleSelect = (0, import_react19.useCallback)((id) => {
|
|
3666
4059
|
setSelectedSet((prev) => {
|
|
3667
4060
|
const next = new Set(prev);
|
|
3668
4061
|
if (next.has(id)) {
|
|
@@ -3676,26 +4069,26 @@ function FlowchartEditor({
|
|
|
3676
4069
|
return next;
|
|
3677
4070
|
});
|
|
3678
4071
|
}, []);
|
|
3679
|
-
const clearSelection = (0,
|
|
4072
|
+
const clearSelection = (0, import_react19.useCallback)(() => {
|
|
3680
4073
|
setSelected(null);
|
|
3681
4074
|
setSelectedSet(/* @__PURE__ */ new Set());
|
|
3682
4075
|
}, []);
|
|
3683
|
-
const [editingId, setEditingId] = (0,
|
|
3684
|
-
const [editLabel, setEditLabel] = (0,
|
|
3685
|
-
const [editingEdgeId, setEditingEdgeId] = (0,
|
|
3686
|
-
const [editEdgeLabel, setEditEdgeLabel] = (0,
|
|
3687
|
-
const [hoveredId, setHoveredId] = (0,
|
|
3688
|
-
const [ctxMenu, setCtxMenu] = (0,
|
|
3689
|
-
const [navOpen, setNavOpen] = (0,
|
|
3690
|
-
const [announcement, setAnnouncement] = (0,
|
|
3691
|
-
const svgRef = (0,
|
|
3692
|
-
const containerRef = (0,
|
|
4076
|
+
const [editingId, setEditingId] = (0, import_react19.useState)(null);
|
|
4077
|
+
const [editLabel, setEditLabel] = (0, import_react19.useState)("");
|
|
4078
|
+
const [editingEdgeId, setEditingEdgeId] = (0, import_react19.useState)(null);
|
|
4079
|
+
const [editEdgeLabel, setEditEdgeLabel] = (0, import_react19.useState)("");
|
|
4080
|
+
const [hoveredId, setHoveredId] = (0, import_react19.useState)(null);
|
|
4081
|
+
const [ctxMenu, setCtxMenu] = (0, import_react19.useState)(null);
|
|
4082
|
+
const [navOpen, setNavOpen] = (0, import_react19.useState)(true);
|
|
4083
|
+
const [announcement, setAnnouncement] = (0, import_react19.useState)("");
|
|
4084
|
+
const svgRef = (0, import_react19.useRef)(null);
|
|
4085
|
+
const containerRef = (0, import_react19.useRef)(null);
|
|
3693
4086
|
const reducedMotion = usePrefersReducedMotion();
|
|
3694
4087
|
const { t, isDark } = useEditorTheme(theme, themeOverrides, { light: lightTheme, dark: darkTheme });
|
|
3695
4088
|
const isCoarse = useIsCoarsePointer();
|
|
3696
4089
|
const portR = isCoarse ? 9 : 6;
|
|
3697
4090
|
const viewport = useElementSize(svgRef);
|
|
3698
|
-
const reCenter = (0,
|
|
4091
|
+
const reCenter = (0, import_react19.useCallback)(() => {
|
|
3699
4092
|
if (!svgRef.current) return;
|
|
3700
4093
|
const rect = svgRef.current.getBoundingClientRect();
|
|
3701
4094
|
const W2 = rect.width, H2 = rect.height;
|
|
@@ -3719,7 +4112,7 @@ function FlowchartEditor({
|
|
|
3719
4112
|
const cx = (minX + maxX) / 2, cy = (minY + maxY) / 2;
|
|
3720
4113
|
setTransform({ scale, x: W2 / 2 - cx * scale, y: H2 / 2 - cy * scale });
|
|
3721
4114
|
}, [model.nodes, variant]);
|
|
3722
|
-
const jumpToNode = (0,
|
|
4115
|
+
const jumpToNode = (0, import_react19.useCallback)((nodeId) => {
|
|
3723
4116
|
const node = model.nodes.find((n) => n.id === nodeId);
|
|
3724
4117
|
if (!node || !svgRef.current) return;
|
|
3725
4118
|
const rect = svgRef.current.getBoundingClientRect();
|
|
@@ -3730,7 +4123,7 @@ function FlowchartEditor({
|
|
|
3730
4123
|
setTransform({ scale, x: rect.width / 2 - cx * scale, y: rect.height / 2 - cy * scale });
|
|
3731
4124
|
selectOne(nodeId);
|
|
3732
4125
|
}, [model.nodes, variant, transform.scale, selectOne]);
|
|
3733
|
-
const duplicateIds = (0,
|
|
4126
|
+
const duplicateIds = (0, import_react19.useCallback)((ids) => {
|
|
3734
4127
|
if (ids.length === 0) return;
|
|
3735
4128
|
const idSet = new Set(ids);
|
|
3736
4129
|
const idMap = /* @__PURE__ */ new Map();
|
|
@@ -3762,92 +4155,90 @@ function FlowchartEditor({
|
|
|
3762
4155
|
setSelected(newIds[newIds.length - 1] ?? null);
|
|
3763
4156
|
setSelectedSet(new Set(newIds));
|
|
3764
4157
|
}, [model, applyAndPush]);
|
|
3765
|
-
const duplicateNode = (0,
|
|
4158
|
+
const duplicateNode = (0, import_react19.useCallback)((nodeId) => {
|
|
3766
4159
|
duplicateIds([nodeId]);
|
|
3767
4160
|
}, [duplicateIds]);
|
|
3768
|
-
(0,
|
|
4161
|
+
(0, import_react19.useEffect)(() => {
|
|
3769
4162
|
if (!ctxMenu) return;
|
|
3770
4163
|
const close = () => setCtxMenu(null);
|
|
3771
4164
|
window.addEventListener("mousedown", close);
|
|
3772
4165
|
return () => window.removeEventListener("mousedown", close);
|
|
3773
4166
|
}, [ctxMenu]);
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
return;
|
|
3793
|
-
}
|
|
3794
|
-
if (ctrl && (e.key === "d" || e.key === "D")) {
|
|
3795
|
-
if (selectedSet.size > 0) {
|
|
3796
|
-
e.preventDefault();
|
|
3797
|
-
duplicateIds(Array.from(selectedSet));
|
|
3798
|
-
}
|
|
3799
|
-
return;
|
|
4167
|
+
const keyCommands = [
|
|
4168
|
+
{ match: (e) => (e.ctrlKey || e.metaKey) && e.key === "z" && !e.shiftKey, run: () => {
|
|
4169
|
+
undo();
|
|
4170
|
+
return true;
|
|
4171
|
+
} },
|
|
4172
|
+
{ match: (e) => (e.ctrlKey || e.metaKey) && (e.key === "y" || e.shiftKey && e.key === "z"), run: () => {
|
|
4173
|
+
redo();
|
|
4174
|
+
return true;
|
|
4175
|
+
} },
|
|
4176
|
+
{ match: (e) => (e.ctrlKey || e.metaKey) && e.key === "0", run: () => {
|
|
4177
|
+
reCenter();
|
|
4178
|
+
return true;
|
|
4179
|
+
} },
|
|
4180
|
+
{
|
|
4181
|
+
match: (e) => (e.ctrlKey || e.metaKey) && (e.key === "d" || e.key === "D") && selectedSet.size > 0,
|
|
4182
|
+
run: () => {
|
|
4183
|
+
duplicateIds(Array.from(selectedSet));
|
|
4184
|
+
return true;
|
|
3800
4185
|
}
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
}
|
|
3811
|
-
}
|
|
3812
|
-
return;
|
|
4186
|
+
},
|
|
4187
|
+
{
|
|
4188
|
+
match: (e) => (e.ctrlKey || e.metaKey) && (e.key === "c" || e.key === "C") && selectedSet.size > 0,
|
|
4189
|
+
run: () => {
|
|
4190
|
+
const ids = new Set(selectedSet);
|
|
4191
|
+
const nodes = model.nodes.filter((n) => ids.has(n.id));
|
|
4192
|
+
const edges = model.edges.filter((ed) => ids.has(ed.from) && ids.has(ed.to));
|
|
4193
|
+
clipboardRef.current = {
|
|
4194
|
+
nodes: nodes.map((n) => ({ ...n })),
|
|
4195
|
+
edges: edges.map((ed) => ({ ...ed }))
|
|
4196
|
+
};
|
|
4197
|
+
return true;
|
|
3813
4198
|
}
|
|
3814
|
-
|
|
4199
|
+
},
|
|
4200
|
+
{
|
|
4201
|
+
match: (e) => (e.ctrlKey || e.metaKey) && (e.key === "v" || e.key === "V"),
|
|
4202
|
+
run: () => {
|
|
3815
4203
|
const clip = clipboardRef.current;
|
|
3816
|
-
if (clip
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
const
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
}
|
|
3839
|
-
return;
|
|
4204
|
+
if (!clip || clip.nodes.length === 0) return false;
|
|
4205
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
4206
|
+
const nextNode = makeIdSource("node", model.nodes);
|
|
4207
|
+
const nextEdge = makeIdSource("e", model.edges);
|
|
4208
|
+
const newNodes = clip.nodes.map((n) => {
|
|
4209
|
+
const newId = nextNode();
|
|
4210
|
+
idMap.set(n.id, newId);
|
|
4211
|
+
return { ...n, id: newId, x: (n.x ?? 0) + 24, y: (n.y ?? 0) + 24 };
|
|
4212
|
+
});
|
|
4213
|
+
const newEdges = clip.edges.map((ed) => ({
|
|
4214
|
+
...ed,
|
|
4215
|
+
id: nextEdge(),
|
|
4216
|
+
from: idMap.get(ed.from) ?? ed.from,
|
|
4217
|
+
to: idMap.get(ed.to) ?? ed.to
|
|
4218
|
+
}));
|
|
4219
|
+
const m = { ...model, nodes: [...model.nodes, ...newNodes], edges: [...model.edges, ...newEdges] };
|
|
4220
|
+
applyAndPush(m);
|
|
4221
|
+
const newIds = newNodes.map((n) => n.id);
|
|
4222
|
+
setSelected(newIds[newIds.length - 1]);
|
|
4223
|
+
setSelectedSet(new Set(newIds));
|
|
4224
|
+
setAnnouncement(`Pasted ${newIds.length} ${variantLabel.toLowerCase()}${newIds.length === 1 ? "" : "s"}.`);
|
|
4225
|
+
return true;
|
|
3840
4226
|
}
|
|
3841
|
-
|
|
4227
|
+
},
|
|
4228
|
+
{
|
|
4229
|
+
match: (e) => e.key === "Escape",
|
|
4230
|
+
run: () => {
|
|
3842
4231
|
if (ctxMenu) setCtxMenu(null);
|
|
3843
4232
|
if (liveEdge) setLiveEdge(null);
|
|
3844
4233
|
if (editingId) setEditingId(null);
|
|
3845
4234
|
if (boxSel) setBoxSel(null);
|
|
3846
4235
|
if (selectedSet.size > 0) clearSelection();
|
|
3847
|
-
return;
|
|
4236
|
+
return true;
|
|
3848
4237
|
}
|
|
3849
|
-
|
|
3850
|
-
|
|
4238
|
+
},
|
|
4239
|
+
{
|
|
4240
|
+
match: (e) => (e.key === "Delete" || e.key === "Backspace") && selectedSet.size > 0,
|
|
4241
|
+
run: () => {
|
|
3851
4242
|
const ids = new Set(selectedSet);
|
|
3852
4243
|
const updated = {
|
|
3853
4244
|
...model,
|
|
@@ -3857,29 +4248,34 @@ function FlowchartEditor({
|
|
|
3857
4248
|
applyAndPush(updated);
|
|
3858
4249
|
clearSelection();
|
|
3859
4250
|
setAnnouncement(`Deleted ${ids.size} ${variantLabel.toLowerCase()}${ids.size === 1 ? "" : "s"}.`);
|
|
3860
|
-
return;
|
|
4251
|
+
return true;
|
|
3861
4252
|
}
|
|
3862
|
-
|
|
4253
|
+
},
|
|
4254
|
+
{
|
|
4255
|
+
match: (e) => selectedSet.size > 0 && e.altKey && !!selected && (e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "ArrowLeft" || e.key === "ArrowRight"),
|
|
4256
|
+
run: (e) => {
|
|
3863
4257
|
const dirKey = e.key === "ArrowLeft" ? "left" : e.key === "ArrowRight" ? "right" : e.key === "ArrowUp" ? "up" : "down";
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
const
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
selectOne(nextId2);
|
|
3878
|
-
setAnnouncement(`Selected ${model.nodes.find((n) => n.id === nextId2)?.label ?? ""}.`);
|
|
3879
|
-
}
|
|
3880
|
-
return;
|
|
4258
|
+
const origin = model.nodes.find((n) => n.id === selected);
|
|
4259
|
+
if (!origin) return false;
|
|
4260
|
+
const od = nodeDims(origin, variant);
|
|
4261
|
+
const ox = (origin.x ?? 0) + od.w / 2;
|
|
4262
|
+
const oy = (origin.y ?? 0) + od.h / 2;
|
|
4263
|
+
const candidates = model.nodes.filter((n) => n.id !== selected).map((n) => {
|
|
4264
|
+
const d = nodeDims(n, variant);
|
|
4265
|
+
return { id: n.id, x: (n.x ?? 0) + d.w / 2, y: (n.y ?? 0) + d.h / 2 };
|
|
4266
|
+
});
|
|
4267
|
+
const nextNodeId = nearestInDirection(ox, oy, dirKey, candidates);
|
|
4268
|
+
if (nextNodeId) {
|
|
4269
|
+
selectOne(nextNodeId);
|
|
4270
|
+
setAnnouncement(`Selected ${model.nodes.find((n) => n.id === nextNodeId)?.label ?? ""}.`);
|
|
3881
4271
|
}
|
|
3882
|
-
|
|
4272
|
+
return true;
|
|
4273
|
+
}
|
|
4274
|
+
},
|
|
4275
|
+
{
|
|
4276
|
+
match: (e) => selectedSet.size > 0 && (e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "ArrowLeft" || e.key === "ArrowRight"),
|
|
4277
|
+
run: (e) => {
|
|
4278
|
+
const dirKey = e.key === "ArrowLeft" ? "left" : e.key === "ArrowRight" ? "right" : e.key === "ArrowUp" ? "up" : "down";
|
|
3883
4279
|
const step = e.shiftKey ? GRID * 4 : GRID;
|
|
3884
4280
|
const dx = dirKey === "left" ? -step : dirKey === "right" ? step : 0;
|
|
3885
4281
|
const dy = dirKey === "up" ? -step : dirKey === "down" ? step : 0;
|
|
@@ -3889,17 +4285,17 @@ function FlowchartEditor({
|
|
|
3889
4285
|
nodes: model.nodes.map((n) => ids.has(n.id) ? { ...n, x: snap((n.x ?? 0) + dx), y: snap((n.y ?? 0) + dy) } : n)
|
|
3890
4286
|
};
|
|
3891
4287
|
applyAndPush(updated);
|
|
4288
|
+
return true;
|
|
3892
4289
|
}
|
|
3893
|
-
}
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
const toCanvas = (0, import_react17.useCallback)((clientX, clientY) => {
|
|
4290
|
+
}
|
|
4291
|
+
];
|
|
4292
|
+
useEditorKeyboard(keyCommands, [undo, redo, reCenter, selected, selectedSet, ctxMenu, liveEdge, editingId, boxSel, model, applyAndPush, duplicateNode, clearSelection]);
|
|
4293
|
+
const toCanvas = (0, import_react19.useCallback)((clientX, clientY) => {
|
|
3898
4294
|
const rect = svgRef.current.getBoundingClientRect();
|
|
3899
4295
|
return { x: (clientX - rect.left - transform.x) / transform.scale, y: (clientY - rect.top - transform.y) / transform.scale };
|
|
3900
4296
|
}, [transform]);
|
|
3901
4297
|
useCanvasWheel(svgRef, setTransform);
|
|
3902
|
-
const onCanvasLongPress = (0,
|
|
4298
|
+
const onCanvasLongPress = (0, import_react19.useCallback)((x, y) => {
|
|
3903
4299
|
setCtxMenu({ x, y, nodeId: null });
|
|
3904
4300
|
}, []);
|
|
3905
4301
|
useCanvasTouch(svgRef, { transform, setTransform, onLongPress: onCanvasLongPress });
|
|
@@ -4171,7 +4567,7 @@ function FlowchartEditor({
|
|
|
4171
4567
|
applyAndPush(updated);
|
|
4172
4568
|
};
|
|
4173
4569
|
const handleExport = useExporters(model, onExport, "diagram");
|
|
4174
|
-
const positionFlowchartNodes = (0,
|
|
4570
|
+
const positionFlowchartNodes = (0, import_react19.useCallback)((m) => ({
|
|
4175
4571
|
...m,
|
|
4176
4572
|
nodes: m.nodes.map((n, i) => ({
|
|
4177
4573
|
...n,
|
|
@@ -4182,11 +4578,11 @@ function FlowchartEditor({
|
|
|
4182
4578
|
const handleImport = useImporter(applyAndPush, { transform: positionFlowchartNodes });
|
|
4183
4579
|
const acc = variantAccent(variant, isDark);
|
|
4184
4580
|
const variantLabel = variant === "question" ? "Question" : variant === "journey" ? "Step" : "Node";
|
|
4185
|
-
const
|
|
4186
|
-
const
|
|
4581
|
+
const shadowClr = shadowColor(isDark);
|
|
4582
|
+
const arrowClr = arrowColor(isDark);
|
|
4187
4583
|
const amberArrow = isDark ? ACCENT.amberDark : ACCENT.amber;
|
|
4188
|
-
return /* @__PURE__ */ (0,
|
|
4189
|
-
/* @__PURE__ */ (0,
|
|
4584
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("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: [
|
|
4585
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("style", { children: `
|
|
4190
4586
|
.fsd-editor button:focus-visible,
|
|
4191
4587
|
.fsd-editor input:focus-visible,
|
|
4192
4588
|
.fsd-editor textarea:focus-visible,
|
|
@@ -4201,7 +4597,7 @@ function FlowchartEditor({
|
|
|
4201
4597
|
outline-offset: -2px;
|
|
4202
4598
|
}
|
|
4203
4599
|
` }),
|
|
4204
|
-
/* @__PURE__ */ (0,
|
|
4600
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
4205
4601
|
"div",
|
|
4206
4602
|
{
|
|
4207
4603
|
role: "status",
|
|
@@ -4211,28 +4607,28 @@ function FlowchartEditor({
|
|
|
4211
4607
|
children: announcement
|
|
4212
4608
|
}
|
|
4213
4609
|
),
|
|
4214
|
-
/* @__PURE__ */ (0,
|
|
4215
|
-
/* @__PURE__ */ (0,
|
|
4216
|
-
/* @__PURE__ */ (0,
|
|
4610
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Toolbar, { onExport: handleExport, onImport: allowImport ? handleImport : void 0, allowedExports, allowImport }),
|
|
4611
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { display: "flex", gap: 6, padding: "7px 14px", background: t.ctrlsBg, borderBottom: `1px solid ${t.ctrlsBorder}`, alignItems: "center", flexWrap: "wrap" }, children: [
|
|
4612
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("button", { onClick: () => addNode(), style: ctrlBtn(acc.color, isDark), children: [
|
|
4217
4613
|
"+ ",
|
|
4218
4614
|
variantLabel
|
|
4219
4615
|
] }),
|
|
4220
|
-
selectedSet.size > 0 && /* @__PURE__ */ (0,
|
|
4221
|
-
/* @__PURE__ */ (0,
|
|
4222
|
-
/* @__PURE__ */ (0,
|
|
4616
|
+
selectedSet.size > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
|
|
4617
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { width: 1, height: 20, background: t.ctrlsBorder, margin: "0 2px" } }),
|
|
4618
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { onClick: deleteSelected, style: { ...ctrlBtn("transparent", isDark), color: "#ef4444", border: `1px solid ${isDark ? "#7f1d1d" : "#fca5a5"}` }, children: selectedSet.size > 1 ? `Delete (${selectedSet.size})` : "Delete" })
|
|
4223
4619
|
] }),
|
|
4224
|
-
liveEdge && /* @__PURE__ */ (0,
|
|
4620
|
+
liveEdge && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { style: { fontSize: 11, color: acc.color, fontWeight: 600, marginLeft: 6 }, children: [
|
|
4225
4621
|
liveEdge.answerLabel ? `Routing "${liveEdge.answerLabel}" \u2192` : "Drop on a node to connect",
|
|
4226
|
-
/* @__PURE__ */ (0,
|
|
4622
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: { fontWeight: 400, color: t.textMuted, marginLeft: 6 }, children: "release to cancel" })
|
|
4227
4623
|
] }),
|
|
4228
|
-
/* @__PURE__ */ (0,
|
|
4624
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { style: { marginLeft: "auto", fontSize: 11, color: t.textMuted }, children: [
|
|
4229
4625
|
variant === "question" ? "drag answer port to connect \xB7 " : "drag port dot \xB7 ",
|
|
4230
4626
|
"scroll to zoom \xB7 drag to pan"
|
|
4231
4627
|
] })
|
|
4232
4628
|
] }),
|
|
4233
|
-
variant !== "flowchart" && /* @__PURE__ */ (0,
|
|
4234
|
-
/* @__PURE__ */ (0,
|
|
4235
|
-
/* @__PURE__ */ (0,
|
|
4629
|
+
variant !== "flowchart" && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("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" }),
|
|
4630
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: STYLE_FLEX_ROW, children: [
|
|
4631
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
4236
4632
|
NodeNavigator,
|
|
4237
4633
|
{
|
|
4238
4634
|
model,
|
|
@@ -4246,323 +4642,162 @@ function FlowchartEditor({
|
|
|
4246
4642
|
onSelect: jumpToNode
|
|
4247
4643
|
}
|
|
4248
4644
|
),
|
|
4249
|
-
/* @__PURE__ */ (0,
|
|
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
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
x1: alignGuides.y.minX,
|
|
4330
|
-
x2: alignGuides.y.maxX,
|
|
4331
|
-
stroke: acc.color,
|
|
4332
|
-
strokeWidth: 1 / transform.scale,
|
|
4333
|
-
strokeDasharray: `${4 / transform.scale} ${3 / transform.scale}`,
|
|
4334
|
-
opacity: 0.85,
|
|
4335
|
-
pointerEvents: "none"
|
|
4336
|
-
}
|
|
4337
|
-
),
|
|
4338
|
-
model.nodes.map((node, idx) => {
|
|
4339
|
-
const isHovered = hoveredId === node.id;
|
|
4340
|
-
const isQuestion2 = variant === "question";
|
|
4341
|
-
const { w: nW, h: nH } = nodeDims(node, variant);
|
|
4342
|
-
const isSelected = selectedSet.has(node.id);
|
|
4343
|
-
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
4344
|
-
"g",
|
|
4345
|
-
{
|
|
4346
|
-
transform: `translate(${node.x ?? 0},${node.y ?? 0})`,
|
|
4347
|
-
role: "button",
|
|
4348
|
-
"aria-label": `${variantLabel} ${variant === "journey" ? idx + 1 + ": " : ""}${node.label}${isSelected ? ", selected" : ""}`,
|
|
4349
|
-
style: { cursor: drag?.nodeId === node.id ? "grabbing" : "grab" },
|
|
4350
|
-
onMouseDown: (e) => onNodeMouseDown(e, node.id),
|
|
4351
|
-
onMouseUp: (e) => onNodeMouseUp(e, node.id),
|
|
4352
|
-
onDoubleClick: (e) => onNodeDblClick(e, node.id),
|
|
4353
|
-
onContextMenu: (e) => onNodeContextMenu(e, node.id),
|
|
4354
|
-
onMouseEnter: () => setHoveredId(node.id),
|
|
4355
|
-
onMouseLeave: () => setHoveredId(null),
|
|
4356
|
-
children: [
|
|
4357
|
-
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("title", { children: `${variantLabel}: ${node.label}` }),
|
|
4358
|
-
isQuestion2 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(QuestionNode, { node, selected: isSelected, edges: model.edges, isDark, onAnswerPortDown, qW: nW }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
|
|
4359
|
-
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(NodeShape, { node, selected: isSelected, variant, stepNumber: variant === "journey" ? idx + 1 : void 0, t, isDark, w: nW }),
|
|
4360
|
-
editingId === node.id ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("foreignObject", { x: 6, y: 6, width: nW - 12, height: NODE_H2 - 12, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
4361
|
-
"input",
|
|
4362
|
-
{
|
|
4363
|
-
autoFocus: true,
|
|
4364
|
-
value: editLabel,
|
|
4365
|
-
onChange: (e) => setEditLabel(e.target.value),
|
|
4366
|
-
onBlur: commitEdit,
|
|
4367
|
-
onKeyDown: (e) => {
|
|
4368
|
-
if (e.key === "Enter") commitEdit();
|
|
4369
|
-
if (e.key === "Escape") setEditingId(null);
|
|
4370
|
-
},
|
|
4371
|
-
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 }
|
|
4372
|
-
}
|
|
4373
|
-
) }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("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 }),
|
|
4374
|
-
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
4375
|
-
"circle",
|
|
4376
|
-
{
|
|
4377
|
-
cx: nW / 2,
|
|
4378
|
-
cy: NODE_H2 + 1,
|
|
4379
|
-
r: portR,
|
|
4380
|
-
fill: acc.color,
|
|
4381
|
-
stroke: isDark ? "#0f172a" : "white",
|
|
4382
|
-
strokeWidth: 2,
|
|
4383
|
-
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))" },
|
|
4384
|
-
onMouseDown: (e) => onPortMouseDown(e, node.id)
|
|
4385
|
-
}
|
|
4386
|
-
)
|
|
4387
|
-
] }),
|
|
4388
|
-
liveEdge && liveEdge.fromId !== node.id && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: nW / 2, cy: -1, r: portR, fill: acc.color, stroke: isDark ? "#0f172a" : "white", strokeWidth: 2, style: STYLE_LIVE_PORT })
|
|
4389
|
-
]
|
|
4390
|
-
},
|
|
4391
|
-
node.id
|
|
4392
|
-
);
|
|
4393
|
-
})
|
|
4394
|
-
] })
|
|
4395
|
-
]
|
|
4396
|
-
}
|
|
4397
|
-
),
|
|
4398
|
-
boxSel && Math.abs(boxSel.cx - boxSel.sx) + Math.abs(boxSel.cy - boxSel.sy) > 4 && containerRef.current && (() => {
|
|
4399
|
-
const rect = containerRef.current.getBoundingClientRect();
|
|
4400
|
-
const left = Math.min(boxSel.sx, boxSel.cx) - rect.left;
|
|
4401
|
-
const top = Math.min(boxSel.sy, boxSel.cy) - rect.top;
|
|
4402
|
-
const w = Math.abs(boxSel.cx - boxSel.sx);
|
|
4403
|
-
const h = Math.abs(boxSel.cy - boxSel.sy);
|
|
4404
|
-
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
4405
|
-
"div",
|
|
4406
|
-
{
|
|
4407
|
-
style: {
|
|
4408
|
-
position: "absolute",
|
|
4409
|
-
left,
|
|
4410
|
-
top,
|
|
4411
|
-
width: w,
|
|
4412
|
-
height: h,
|
|
4413
|
-
border: `1px dashed ${acc.color}`,
|
|
4414
|
-
background: isDark ? "rgba(99,102,241,0.10)" : "rgba(99,102,241,0.08)",
|
|
4415
|
-
pointerEvents: "none",
|
|
4416
|
-
borderRadius: 4
|
|
4417
|
-
}
|
|
4645
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
4646
|
+
DiagramCanvas,
|
|
4647
|
+
{
|
|
4648
|
+
model,
|
|
4649
|
+
variant,
|
|
4650
|
+
variantLabel,
|
|
4651
|
+
t,
|
|
4652
|
+
isDark,
|
|
4653
|
+
acc,
|
|
4654
|
+
transform,
|
|
4655
|
+
setTransform,
|
|
4656
|
+
selected,
|
|
4657
|
+
selectedSet,
|
|
4658
|
+
hoveredId,
|
|
4659
|
+
setHoveredId,
|
|
4660
|
+
drag,
|
|
4661
|
+
pan,
|
|
4662
|
+
liveEdge,
|
|
4663
|
+
boxSel,
|
|
4664
|
+
alignGuides,
|
|
4665
|
+
editingEdgeId,
|
|
4666
|
+
editEdgeLabel,
|
|
4667
|
+
setEditEdgeLabel,
|
|
4668
|
+
commitEdgeEdit,
|
|
4669
|
+
setEditingEdgeId,
|
|
4670
|
+
beginEditEdge,
|
|
4671
|
+
onEdgeContextMenu,
|
|
4672
|
+
setWaypointDrag,
|
|
4673
|
+
editingId,
|
|
4674
|
+
editLabel,
|
|
4675
|
+
setEditLabel,
|
|
4676
|
+
commitEdit,
|
|
4677
|
+
setEditingId,
|
|
4678
|
+
onNodeMouseDown,
|
|
4679
|
+
onNodeMouseUp,
|
|
4680
|
+
onNodeDblClick,
|
|
4681
|
+
onNodeContextMenu,
|
|
4682
|
+
onPortMouseDown,
|
|
4683
|
+
onAnswerPortDown,
|
|
4684
|
+
onSvgMouseDown,
|
|
4685
|
+
onMouseMove,
|
|
4686
|
+
onMouseUp,
|
|
4687
|
+
onSvgContextMenu,
|
|
4688
|
+
reducedMotion,
|
|
4689
|
+
isCoarse,
|
|
4690
|
+
portR,
|
|
4691
|
+
shadowClr,
|
|
4692
|
+
arrowClr,
|
|
4693
|
+
amberArrow,
|
|
4694
|
+
viewport,
|
|
4695
|
+
svgRef,
|
|
4696
|
+
containerRef,
|
|
4697
|
+
ctxMenu,
|
|
4698
|
+
history,
|
|
4699
|
+
ctxEdgeStyle: (ctxMenu?.edgeId ? model.edges.find((e) => e.id === ctxMenu.edgeId) : void 0)?.style ?? "solid",
|
|
4700
|
+
ctxEdgeArrow: (ctxMenu?.edgeId ? model.edges.find((e) => e.id === ctxMenu.edgeId) : void 0)?.arrowhead ?? "arrow",
|
|
4701
|
+
ctxEdgeHasWaypoint: !!(ctxMenu?.edgeId ? model.edges.find((e) => e.id === ctxMenu.edgeId) : void 0)?.waypoint,
|
|
4702
|
+
onCtxUndo: () => {
|
|
4703
|
+
undo();
|
|
4704
|
+
setCtxMenu(null);
|
|
4705
|
+
},
|
|
4706
|
+
onCtxRedo: () => {
|
|
4707
|
+
redo();
|
|
4708
|
+
setCtxMenu(null);
|
|
4709
|
+
},
|
|
4710
|
+
onCtxReCenter: () => {
|
|
4711
|
+
reCenter();
|
|
4712
|
+
setCtxMenu(null);
|
|
4713
|
+
},
|
|
4714
|
+
onCtxAddNode: () => {
|
|
4715
|
+
const rect = svgRef.current.getBoundingClientRect();
|
|
4716
|
+
const cx = (ctxMenu.x - rect.left - transform.x) / transform.scale;
|
|
4717
|
+
const cy = (ctxMenu.y - rect.top - transform.y) / transform.scale;
|
|
4718
|
+
addNode({ x: cx, y: cy });
|
|
4719
|
+
setCtxMenu(null);
|
|
4720
|
+
},
|
|
4721
|
+
onCtxDuplicate: () => {
|
|
4722
|
+
if (ctxMenu?.nodeId) {
|
|
4723
|
+
duplicateNode(ctxMenu.nodeId);
|
|
4724
|
+
setCtxMenu(null);
|
|
4418
4725
|
}
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
"+ ",
|
|
4427
|
-
variantLabel
|
|
4428
|
-
] }),
|
|
4429
|
-
" to start"
|
|
4430
|
-
] })
|
|
4431
|
-
] }),
|
|
4432
|
-
model.nodes.length > 0 && viewport.w > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
4433
|
-
Minimap,
|
|
4434
|
-
{
|
|
4435
|
-
model,
|
|
4436
|
-
viewportW: viewport.w,
|
|
4437
|
-
viewportH: viewport.h,
|
|
4438
|
-
transform,
|
|
4439
|
-
isDark,
|
|
4440
|
-
accentColor: acc.color,
|
|
4441
|
-
measureNode: (n) => nodeDims(n, variant),
|
|
4442
|
-
onCenterOn: (cx, cy) => {
|
|
4443
|
-
setTransform((tr) => ({ ...tr, x: viewport.w / 2 - cx * tr.scale, y: viewport.h / 2 - cy * tr.scale }));
|
|
4726
|
+
},
|
|
4727
|
+
onCtxRename: () => {
|
|
4728
|
+
if (ctxMenu?.nodeId) {
|
|
4729
|
+
const node = model.nodes.find((n) => n.id === ctxMenu.nodeId);
|
|
4730
|
+
setEditingId(ctxMenu.nodeId);
|
|
4731
|
+
setEditLabel(node.label);
|
|
4732
|
+
setCtxMenu(null);
|
|
4444
4733
|
}
|
|
4445
|
-
}
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
ContextMenu,
|
|
4451
|
-
{
|
|
4452
|
-
x: ctxMenu.x,
|
|
4453
|
-
y: ctxMenu.y,
|
|
4454
|
-
nodeId: ctxMenu.nodeId,
|
|
4455
|
-
edgeId: ctxMenu.edgeId,
|
|
4456
|
-
isDark,
|
|
4457
|
-
t,
|
|
4458
|
-
acc,
|
|
4459
|
-
canUndo: history.canUndo,
|
|
4460
|
-
canRedo: history.canRedo,
|
|
4461
|
-
onUndo: () => {
|
|
4462
|
-
undo();
|
|
4463
|
-
setCtxMenu(null);
|
|
4464
|
-
},
|
|
4465
|
-
onRedo: () => {
|
|
4466
|
-
redo();
|
|
4467
|
-
setCtxMenu(null);
|
|
4468
|
-
},
|
|
4469
|
-
onReCenter: () => {
|
|
4470
|
-
reCenter();
|
|
4471
|
-
setCtxMenu(null);
|
|
4472
|
-
},
|
|
4473
|
-
onAddNode: () => {
|
|
4474
|
-
const rect = svgRef.current.getBoundingClientRect();
|
|
4475
|
-
const cx = (ctxMenu.x - rect.left - transform.x) / transform.scale;
|
|
4476
|
-
const cy = (ctxMenu.y - rect.top - transform.y) / transform.scale;
|
|
4477
|
-
addNode({ x: cx, y: cy });
|
|
4478
|
-
setCtxMenu(null);
|
|
4479
|
-
},
|
|
4480
|
-
onDuplicate: () => {
|
|
4481
|
-
if (ctxMenu.nodeId) {
|
|
4482
|
-
duplicateNode(ctxMenu.nodeId);
|
|
4483
|
-
setCtxMenu(null);
|
|
4484
|
-
}
|
|
4485
|
-
},
|
|
4486
|
-
onRename: () => {
|
|
4487
|
-
if (ctxMenu.nodeId) {
|
|
4488
|
-
const node = model.nodes.find((n) => n.id === ctxMenu.nodeId);
|
|
4489
|
-
setEditingId(ctxMenu.nodeId);
|
|
4490
|
-
setEditLabel(node.label);
|
|
4491
|
-
setCtxMenu(null);
|
|
4492
|
-
}
|
|
4493
|
-
},
|
|
4494
|
-
onDelete: () => {
|
|
4495
|
-
if (ctxMenu.nodeId) {
|
|
4496
|
-
deleteNode(ctxMenu.nodeId);
|
|
4497
|
-
setCtxMenu(null);
|
|
4498
|
-
}
|
|
4499
|
-
},
|
|
4500
|
-
onDisconnect: () => {
|
|
4501
|
-
if (ctxMenu.nodeId) {
|
|
4502
|
-
const m = { ...model, edges: model.edges.filter((e) => e.from !== ctxMenu.nodeId && e.to !== ctxMenu.nodeId) };
|
|
4503
|
-
applyAndPush(m);
|
|
4504
|
-
setCtxMenu(null);
|
|
4505
|
-
}
|
|
4506
|
-
},
|
|
4507
|
-
currentEdgeStyle: ctxEdge?.style ?? "solid",
|
|
4508
|
-
currentEdgeArrow: ctxEdge?.arrowhead ?? "arrow",
|
|
4509
|
-
edgeHasWaypoint: !!ctxEdge?.waypoint,
|
|
4510
|
-
onEdgeRename: () => {
|
|
4511
|
-
if (ctxMenu.edgeId) {
|
|
4512
|
-
beginEditEdge(ctxMenu.edgeId);
|
|
4513
|
-
setCtxMenu(null);
|
|
4514
|
-
}
|
|
4515
|
-
},
|
|
4516
|
-
onEdgeStyle: (s2) => {
|
|
4517
|
-
if (ctxMenu.edgeId) {
|
|
4518
|
-
setEdgeStyle(ctxMenu.edgeId, s2);
|
|
4519
|
-
setCtxMenu(null);
|
|
4520
|
-
}
|
|
4521
|
-
},
|
|
4522
|
-
onEdgeArrowhead: (a) => {
|
|
4523
|
-
if (ctxMenu.edgeId) {
|
|
4524
|
-
setEdgeArrowhead(ctxMenu.edgeId, a);
|
|
4525
|
-
setCtxMenu(null);
|
|
4526
|
-
}
|
|
4527
|
-
},
|
|
4528
|
-
onEdgeDelete: () => {
|
|
4529
|
-
if (ctxMenu.edgeId) {
|
|
4530
|
-
deleteEdge(ctxMenu.edgeId);
|
|
4531
|
-
setCtxMenu(null);
|
|
4532
|
-
}
|
|
4533
|
-
},
|
|
4534
|
-
onEdgeResetRouting: () => {
|
|
4535
|
-
if (ctxMenu.edgeId) {
|
|
4536
|
-
resetEdgeRouting(ctxMenu.edgeId);
|
|
4537
|
-
setCtxMenu(null);
|
|
4538
|
-
}
|
|
4539
|
-
},
|
|
4540
|
-
containerRef
|
|
4734
|
+
},
|
|
4735
|
+
onCtxDelete: () => {
|
|
4736
|
+
if (ctxMenu?.nodeId) {
|
|
4737
|
+
deleteNode(ctxMenu.nodeId);
|
|
4738
|
+
setCtxMenu(null);
|
|
4541
4739
|
}
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4740
|
+
},
|
|
4741
|
+
onCtxDisconnect: () => {
|
|
4742
|
+
if (ctxMenu?.nodeId) {
|
|
4743
|
+
const m = { ...model, edges: model.edges.filter((e) => e.from !== ctxMenu.nodeId && e.to !== ctxMenu.nodeId) };
|
|
4744
|
+
applyAndPush(m);
|
|
4745
|
+
setCtxMenu(null);
|
|
4746
|
+
}
|
|
4747
|
+
},
|
|
4748
|
+
onCtxEdgeRename: () => {
|
|
4749
|
+
if (ctxMenu?.edgeId) {
|
|
4750
|
+
beginEditEdge(ctxMenu.edgeId);
|
|
4751
|
+
setCtxMenu(null);
|
|
4752
|
+
}
|
|
4753
|
+
},
|
|
4754
|
+
onCtxEdgeStyle: (s2) => {
|
|
4755
|
+
if (ctxMenu?.edgeId) {
|
|
4756
|
+
setEdgeStyle(ctxMenu.edgeId, s2);
|
|
4757
|
+
setCtxMenu(null);
|
|
4758
|
+
}
|
|
4759
|
+
},
|
|
4760
|
+
onCtxEdgeArrowhead: (a) => {
|
|
4761
|
+
if (ctxMenu?.edgeId) {
|
|
4762
|
+
setEdgeArrowhead(ctxMenu.edgeId, a);
|
|
4763
|
+
setCtxMenu(null);
|
|
4764
|
+
}
|
|
4765
|
+
},
|
|
4766
|
+
onCtxEdgeDelete: () => {
|
|
4767
|
+
if (ctxMenu?.edgeId) {
|
|
4768
|
+
deleteEdge(ctxMenu.edgeId);
|
|
4769
|
+
setCtxMenu(null);
|
|
4770
|
+
}
|
|
4771
|
+
},
|
|
4772
|
+
onCtxEdgeResetRouting: () => {
|
|
4773
|
+
if (ctxMenu?.edgeId) {
|
|
4774
|
+
resetEdgeRouting(ctxMenu.edgeId);
|
|
4775
|
+
setCtxMenu(null);
|
|
4776
|
+
}
|
|
4777
|
+
}
|
|
4778
|
+
}
|
|
4779
|
+
),
|
|
4780
|
+
selected && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(StepEditor, { nodeId: selected, model, onModelChange: (m) => {
|
|
4546
4781
|
applyAndPush(m);
|
|
4547
4782
|
}, variant, isDark, t, acc }, selected)
|
|
4548
4783
|
] }),
|
|
4549
|
-
/* @__PURE__ */ (0,
|
|
4550
|
-
/* @__PURE__ */ (0,
|
|
4784
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { padding: "4px 14px", fontSize: 11, color: t.textMuted, background: t.statusBg, borderTop: `1px solid ${t.ctrlsBorder}`, display: "flex", gap: 16 }, children: [
|
|
4785
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { children: [
|
|
4551
4786
|
model.nodes.length,
|
|
4552
4787
|
" ",
|
|
4553
4788
|
variantLabel.toLowerCase(),
|
|
4554
4789
|
"s"
|
|
4555
4790
|
] }),
|
|
4556
|
-
/* @__PURE__ */ (0,
|
|
4791
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { children: [
|
|
4557
4792
|
model.edges.length,
|
|
4558
4793
|
" connections"
|
|
4559
4794
|
] }),
|
|
4560
|
-
/* @__PURE__ */ (0,
|
|
4795
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { children: [
|
|
4561
4796
|
Math.round(transform.scale * 100),
|
|
4562
4797
|
"% zoom"
|
|
4563
4798
|
] }),
|
|
4564
|
-
/* @__PURE__ */ (0,
|
|
4565
|
-
selected && /* @__PURE__ */ (0,
|
|
4799
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: { marginLeft: "auto" }, children: "Ctrl+Z undo \xB7 Ctrl+Y redo \xB7 Ctrl+0 fit \xB7 Alt+Arrow traverse" }),
|
|
4800
|
+
selected && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: { color: acc.color }, children: model.nodes.find((n) => n.id === selected)?.label })
|
|
4566
4801
|
] })
|
|
4567
4802
|
] });
|
|
4568
4803
|
}
|