flowchart-sequence-designer 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +250 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -4
- package/dist/index.d.ts +20 -4
- package/dist/index.js +248 -43
- package/dist/index.js.map +1 -1
- package/dist/ui/index.cjs +2840 -1177
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.cts +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +2853 -1190
- package/dist/ui/index.js.map +1 -1
- package/package.json +7 -2
package/dist/ui/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/ui/DiagramEditor.tsx
|
|
2
|
-
import { useCallback as
|
|
2
|
+
import { useCallback as useCallback7, useEffect as useEffect10, useRef as useRef7, useState as useState12 } from "react";
|
|
3
3
|
|
|
4
4
|
// src/ui/Toolbar.tsx
|
|
5
5
|
import { useState as useState2 } from "react";
|
|
@@ -383,12 +383,37 @@ function arrowColor(isDark) {
|
|
|
383
383
|
}
|
|
384
384
|
function variantAccent(variant, isDark) {
|
|
385
385
|
if (variant === "question") {
|
|
386
|
-
return isDark ? {
|
|
386
|
+
return isDark ? {
|
|
387
|
+
color: ACCENT.amberDark,
|
|
388
|
+
fill: ACCENT.amberDarkLight,
|
|
389
|
+
border: ACCENT.amberDarkBorder,
|
|
390
|
+
glow: ACCENT.amberGlow
|
|
391
|
+
} : {
|
|
392
|
+
color: ACCENT.amber,
|
|
393
|
+
fill: ACCENT.amberLight,
|
|
394
|
+
border: ACCENT.amberBorder,
|
|
395
|
+
glow: ACCENT.amberGlow
|
|
396
|
+
};
|
|
387
397
|
}
|
|
388
398
|
if (variant === "journey") {
|
|
389
|
-
return isDark ? {
|
|
399
|
+
return isDark ? {
|
|
400
|
+
color: ACCENT.emeraldDark,
|
|
401
|
+
fill: ACCENT.emeraldDarkLight,
|
|
402
|
+
border: ACCENT.emeraldDarkBorder,
|
|
403
|
+
glow: ACCENT.emeraldGlow
|
|
404
|
+
} : {
|
|
405
|
+
color: ACCENT.emerald,
|
|
406
|
+
fill: ACCENT.emeraldLight,
|
|
407
|
+
border: "#6ee7b7",
|
|
408
|
+
glow: ACCENT.emeraldGlow
|
|
409
|
+
};
|
|
390
410
|
}
|
|
391
|
-
return isDark ? {
|
|
411
|
+
return isDark ? {
|
|
412
|
+
color: "#818cf8",
|
|
413
|
+
fill: "rgba(79,70,229,0.12)",
|
|
414
|
+
border: "rgba(79,70,229,0.3)",
|
|
415
|
+
glow: ACCENT.indigoGlow
|
|
416
|
+
} : { color: ACCENT.indigo, fill: "#f5f3ff", border: "#c7d2fe", glow: ACCENT.indigoGlow };
|
|
392
417
|
}
|
|
393
418
|
|
|
394
419
|
// src/ui/Toolbar.tsx
|
|
@@ -415,17 +440,19 @@ function Toolbar({ onExport, onImport, allowedExports, allowImport = true }) {
|
|
|
415
440
|
allowImport && onImport && /* @__PURE__ */ jsx2("button", { onClick: () => setImportOpen(true), "aria-label": "Import diagram", style: ghostBtn, children: "\u2191 Import" }),
|
|
416
441
|
formats.length > 0 && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
417
442
|
/* @__PURE__ */ jsx2("span", { style: { fontSize: 11, color: darkTheme.inputText, margin: "0 4px" }, children: "Export \u2192" }),
|
|
418
|
-
formats.map((f) => /* @__PURE__ */ jsx2(
|
|
443
|
+
formats.map((f) => /* @__PURE__ */ jsx2(
|
|
444
|
+
"button",
|
|
445
|
+
{
|
|
446
|
+
onClick: () => onExport(f.key),
|
|
447
|
+
"aria-label": `Export as ${f.label}`,
|
|
448
|
+
style: exportBtn,
|
|
449
|
+
children: f.label
|
|
450
|
+
},
|
|
451
|
+
f.key
|
|
452
|
+
))
|
|
419
453
|
] })
|
|
420
454
|
] }),
|
|
421
|
-
onImport && /* @__PURE__ */ jsx2(
|
|
422
|
-
ImportDialog,
|
|
423
|
-
{
|
|
424
|
-
open: importOpen,
|
|
425
|
-
onClose: () => setImportOpen(false),
|
|
426
|
-
onImport
|
|
427
|
-
}
|
|
428
|
-
)
|
|
455
|
+
onImport && /* @__PURE__ */ jsx2(ImportDialog, { open: importOpen, onClose: () => setImportOpen(false), onImport })
|
|
429
456
|
] });
|
|
430
457
|
}
|
|
431
458
|
var bar = {
|
|
@@ -517,7 +544,15 @@ var SHAPES = [
|
|
|
517
544
|
{ key: "circle", label: "Circle", icon: "\u25CB" },
|
|
518
545
|
{ key: "parallelogram", label: "I/O", icon: "\u25B1" }
|
|
519
546
|
];
|
|
520
|
-
function StepEditor({
|
|
547
|
+
function StepEditor({
|
|
548
|
+
nodeId,
|
|
549
|
+
model,
|
|
550
|
+
onModelChange,
|
|
551
|
+
variant = "flowchart",
|
|
552
|
+
isDark = false,
|
|
553
|
+
t,
|
|
554
|
+
acc
|
|
555
|
+
}) {
|
|
521
556
|
const isQuestion2 = variant === "question";
|
|
522
557
|
const branchTerm = isQuestion2 ? "Answer" : "Branch";
|
|
523
558
|
const tt = t ?? (isDark ? darkTheme : lightTheme);
|
|
@@ -551,27 +586,57 @@ function StepEditor({ nodeId, model, onModelChange, variant = "flowchart", isDar
|
|
|
551
586
|
const answers = node.metadata?.answers ?? [];
|
|
552
587
|
const commitLabel = () => {
|
|
553
588
|
if (label === node.label || !label.trim()) return;
|
|
554
|
-
onModelChange({
|
|
589
|
+
onModelChange({
|
|
590
|
+
...model,
|
|
591
|
+
nodes: model.nodes.map((n) => n.id === nodeId ? { ...n, label: label.trim() } : n)
|
|
592
|
+
});
|
|
555
593
|
};
|
|
556
594
|
const setShape = (shape) => {
|
|
557
|
-
onModelChange({
|
|
595
|
+
onModelChange({
|
|
596
|
+
...model,
|
|
597
|
+
nodes: model.nodes.map((n) => n.id === nodeId ? { ...n, shape } : n)
|
|
598
|
+
});
|
|
558
599
|
};
|
|
559
600
|
const removeEdge = (edgeId) => {
|
|
560
601
|
onModelChange({ ...model, edges: model.edges.filter((e) => e.id !== edgeId) });
|
|
561
602
|
};
|
|
562
603
|
const updateEdgeLabel = (edgeId, val) => {
|
|
563
|
-
onModelChange({
|
|
604
|
+
onModelChange({
|
|
605
|
+
...model,
|
|
606
|
+
edges: model.edges.map((e) => e.id === edgeId ? { ...e, label: val || void 0 } : e)
|
|
607
|
+
});
|
|
564
608
|
};
|
|
565
609
|
const addBranch = () => {
|
|
566
610
|
if (branchMode === "new") {
|
|
567
611
|
if (!branchLabel.trim()) return;
|
|
568
612
|
const newId = nextId("node", model.nodes);
|
|
569
|
-
const newNode = {
|
|
570
|
-
|
|
571
|
-
|
|
613
|
+
const newNode = {
|
|
614
|
+
id: newId,
|
|
615
|
+
label: branchLabel.trim(),
|
|
616
|
+
shape: "rectangle",
|
|
617
|
+
x: (node.x ?? 0) + 200,
|
|
618
|
+
y: (node.y ?? 0) + 20 + outEdges.length * 100
|
|
619
|
+
};
|
|
620
|
+
const newEdge = {
|
|
621
|
+
id: nextId("e", model.edges),
|
|
622
|
+
from: nodeId,
|
|
623
|
+
to: newId,
|
|
624
|
+
label: branchEdgeLabel.trim() || void 0
|
|
625
|
+
};
|
|
626
|
+
onModelChange({
|
|
627
|
+
...model,
|
|
628
|
+
nodes: [...model.nodes, newNode],
|
|
629
|
+
edges: [...model.edges, newEdge]
|
|
630
|
+
});
|
|
572
631
|
} else {
|
|
573
|
-
if (!branchTarget || model.edges.some((e) => e.from === nodeId && e.to === branchTarget))
|
|
574
|
-
|
|
632
|
+
if (!branchTarget || model.edges.some((e) => e.from === nodeId && e.to === branchTarget))
|
|
633
|
+
return;
|
|
634
|
+
const newEdge = {
|
|
635
|
+
id: nextId("e", model.edges),
|
|
636
|
+
from: nodeId,
|
|
637
|
+
to: branchTarget,
|
|
638
|
+
label: branchEdgeLabel.trim() || void 0
|
|
639
|
+
};
|
|
575
640
|
onModelChange({ ...model, edges: [...model.edges, newEdge] });
|
|
576
641
|
}
|
|
577
642
|
setBranchLabel("");
|
|
@@ -583,7 +648,12 @@ function StepEditor({ nodeId, model, onModelChange, variant = "flowchart", isDar
|
|
|
583
648
|
const trimmed = newAnswer.trim();
|
|
584
649
|
if (!trimmed || answers.includes(trimmed)) return;
|
|
585
650
|
const updated = [...answers, trimmed];
|
|
586
|
-
onModelChange({
|
|
651
|
+
onModelChange({
|
|
652
|
+
...model,
|
|
653
|
+
nodes: model.nodes.map(
|
|
654
|
+
(n) => n.id === nodeId ? { ...n, metadata: { ...n.metadata ?? {}, answers: updated } } : n
|
|
655
|
+
)
|
|
656
|
+
});
|
|
587
657
|
setNewAnswer("");
|
|
588
658
|
setAddingAnswer(false);
|
|
589
659
|
};
|
|
@@ -592,7 +662,9 @@ function StepEditor({ nodeId, model, onModelChange, variant = "flowchart", isDar
|
|
|
592
662
|
const updatedEdges = model.edges.filter((e) => !(e.from === nodeId && e.label === ans));
|
|
593
663
|
onModelChange({
|
|
594
664
|
...model,
|
|
595
|
-
nodes: model.nodes.map(
|
|
665
|
+
nodes: model.nodes.map(
|
|
666
|
+
(n) => n.id === nodeId ? { ...n, metadata: { ...n.metadata ?? {}, answers: updated } } : n
|
|
667
|
+
),
|
|
596
668
|
edges: updatedEdges
|
|
597
669
|
});
|
|
598
670
|
};
|
|
@@ -601,7 +673,12 @@ function StepEditor({ nodeId, model, onModelChange, variant = "flowchart", isDar
|
|
|
601
673
|
if (next < 0 || next >= answers.length) return;
|
|
602
674
|
const arr = [...answers];
|
|
603
675
|
[arr[idx], arr[next]] = [arr[next], arr[idx]];
|
|
604
|
-
onModelChange({
|
|
676
|
+
onModelChange({
|
|
677
|
+
...model,
|
|
678
|
+
nodes: model.nodes.map(
|
|
679
|
+
(n) => n.id === nodeId ? { ...n, metadata: { ...n.metadata ?? {}, answers: arr } } : n
|
|
680
|
+
)
|
|
681
|
+
});
|
|
605
682
|
};
|
|
606
683
|
const inputStyle = {
|
|
607
684
|
width: "100%",
|
|
@@ -656,167 +733,600 @@ function StepEditor({ nodeId, model, onModelChange, variant = "flowchart", isDar
|
|
|
656
733
|
fontFamily: "inherit",
|
|
657
734
|
transition: "background 0.15s, border-color 0.15s"
|
|
658
735
|
};
|
|
659
|
-
return /* @__PURE__ */ jsxs3(
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
/* @__PURE__ */ jsx3("span", { children: isQuestion2 ? "Question Editor" : variant === "journey" ? "Step Editor" : "Step Editor" })
|
|
675
|
-
] }),
|
|
676
|
-
/* @__PURE__ */ jsxs3("div", { style: { flex: 1, overflowY: "auto", display: "flex", flexDirection: "column" }, children: [
|
|
677
|
-
/* @__PURE__ */ jsxs3("section", { style: { padding: "14px 16px", borderBottom: `1px solid ${tt.sectionBorder}` }, children: [
|
|
678
|
-
/* @__PURE__ */ jsx3("label", { style: { display: "block", fontSize: 10, fontWeight: 700, color: tt.labelText, marginBottom: 8, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Name" }),
|
|
679
|
-
/* @__PURE__ */ jsx3(
|
|
680
|
-
"input",
|
|
736
|
+
return /* @__PURE__ */ jsxs3(
|
|
737
|
+
"div",
|
|
738
|
+
{
|
|
739
|
+
style: {
|
|
740
|
+
width: 272,
|
|
741
|
+
minWidth: 272,
|
|
742
|
+
background: tt.panelBg,
|
|
743
|
+
borderLeft: `1px solid ${tt.panelBorder}`,
|
|
744
|
+
display: "flex",
|
|
745
|
+
flexDirection: "column",
|
|
746
|
+
overflow: "hidden"
|
|
747
|
+
},
|
|
748
|
+
children: [
|
|
749
|
+
/* @__PURE__ */ jsxs3(
|
|
750
|
+
"div",
|
|
681
751
|
{
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
alignItems: "center",
|
|
700
|
-
gap: 4,
|
|
701
|
-
padding: "8px 6px",
|
|
702
|
-
borderRadius: 8,
|
|
703
|
-
cursor: "pointer",
|
|
704
|
-
transition: "all 0.15s",
|
|
705
|
-
background: active ? accentColor : tt.shapeBtnBg,
|
|
706
|
-
color: active ? "#fff" : tt.textSecondary,
|
|
707
|
-
border: active ? `1.5px solid ${accentColor}` : `1.5px solid ${tt.shapeBtnBorder}`
|
|
708
|
-
}, children: [
|
|
709
|
-
/* @__PURE__ */ jsx3("span", { style: { fontSize: 16, lineHeight: 1 }, children: s2.icon }),
|
|
710
|
-
/* @__PURE__ */ jsx3("span", { style: { fontSize: 11, fontWeight: 500 }, children: s2.label })
|
|
711
|
-
] }, s2.key);
|
|
712
|
-
}) })
|
|
713
|
-
] }),
|
|
714
|
-
isQuestion2 && /* @__PURE__ */ jsxs3("section", { style: { padding: "14px 16px", borderBottom: `1px solid ${tt.sectionBorder}`, flex: 1 }, children: [
|
|
715
|
-
/* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 10 }, children: [
|
|
716
|
-
/* @__PURE__ */ jsx3("label", { style: { display: "block", fontSize: 10, fontWeight: 700, color: tt.labelText, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Answers" }),
|
|
717
|
-
/* @__PURE__ */ jsx3("span", { style: { fontSize: 11, color: tt.textMuted, background: isDark ? "#0f172a" : "#f1f5f9", padding: "1px 7px", borderRadius: 99, fontWeight: 600 }, children: answers.length })
|
|
718
|
-
] }),
|
|
719
|
-
answers.length === 0 && !addingAnswer && /* @__PURE__ */ jsx3("div", { style: { fontSize: 12, color: tt.textMuted, textAlign: "center", padding: "16px 0", fontStyle: "italic" }, children: "No answers yet \u2014 add one below" }),
|
|
720
|
-
answers.map((ans, i) => {
|
|
721
|
-
const connected = model.edges.some((e) => e.from === nodeId && e.label === ans);
|
|
722
|
-
const targetNode = model.nodes.find((n) => {
|
|
723
|
-
const e = model.edges.find((ex) => ex.from === nodeId && ex.label === ans);
|
|
724
|
-
return e && n.id === e.to;
|
|
725
|
-
});
|
|
726
|
-
return /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "flex-start", gap: 0, marginBottom: 8, borderRadius: 12, border: `1px solid ${tt.cardBorder}`, overflow: "hidden", background: tt.cardBg, boxShadow: isDark ? "none" : "0 1px 2px rgba(15,23,42,0.04)" }, children: [
|
|
727
|
-
/* @__PURE__ */ jsx3("div", { style: { width: 4, alignSelf: "stretch", background: accentColor, flexShrink: 0 } }),
|
|
728
|
-
/* @__PURE__ */ jsxs3("div", { style: { flex: 1, minWidth: 0, padding: "8px 10px" }, children: [
|
|
729
|
-
/* @__PURE__ */ jsx3("div", { style: { fontSize: 12, fontWeight: 600, color: tt.textPrimary, marginBottom: connected ? 3 : 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: ans }),
|
|
730
|
-
connected && targetNode && /* @__PURE__ */ jsxs3("div", { style: { fontSize: 11, color: accentColor, opacity: 0.85 }, children: [
|
|
731
|
-
"\u2192 ",
|
|
732
|
-
targetNode.label
|
|
733
|
-
] }),
|
|
734
|
-
!connected && /* @__PURE__ */ jsx3("div", { style: { fontSize: 10, color: tt.textMuted, fontStyle: "italic" }, children: "drag port to connect" })
|
|
735
|
-
] }),
|
|
736
|
-
/* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", padding: "4px 2px", gap: 2 }, children: [
|
|
737
|
-
/* @__PURE__ */ jsx3("button", { onClick: () => moveAnswer(i, -1), disabled: i === 0, style: { background: "none", border: "none", color: tt.textMuted, cursor: "pointer", fontSize: 11, padding: "2px 4px", opacity: i === 0 ? 0.3 : 1 }, children: "\u2191" }),
|
|
738
|
-
/* @__PURE__ */ jsx3("button", { onClick: () => moveAnswer(i, 1), disabled: i === answers.length - 1, style: { background: "none", border: "none", color: tt.textMuted, cursor: "pointer", fontSize: 11, padding: "2px 4px", opacity: i === answers.length - 1 ? 0.3 : 1 }, children: "\u2193" })
|
|
739
|
-
] }),
|
|
740
|
-
/* @__PURE__ */ jsx3("button", { onClick: () => removeAnswer(ans), style: { background: "none", border: "none", color: tt.textMuted, cursor: "pointer", fontSize: 12, padding: "8px 10px", flexShrink: 0 }, title: "Remove", children: "\u2715" })
|
|
741
|
-
] }, ans + i);
|
|
742
|
-
}),
|
|
743
|
-
addingAnswer ? /* @__PURE__ */ jsxs3("div", { role: "group", "aria-label": "Add answer form", onKeyDown: (e) => {
|
|
744
|
-
if (e.key === "Escape") {
|
|
745
|
-
setAddingAnswer(false);
|
|
746
|
-
setNewAnswer("");
|
|
752
|
+
style: {
|
|
753
|
+
padding: "12px 16px",
|
|
754
|
+
fontWeight: 700,
|
|
755
|
+
fontSize: 12,
|
|
756
|
+
letterSpacing: 0.8,
|
|
757
|
+
textTransform: "uppercase",
|
|
758
|
+
color: accentColor,
|
|
759
|
+
borderBottom: `1px solid ${accentBorder}`,
|
|
760
|
+
background: accentLight,
|
|
761
|
+
display: "flex",
|
|
762
|
+
alignItems: "center",
|
|
763
|
+
gap: 8
|
|
764
|
+
},
|
|
765
|
+
children: [
|
|
766
|
+
/* @__PURE__ */ jsx3("div", { style: { width: 6, height: 6, borderRadius: "50%", background: accentColor } }),
|
|
767
|
+
/* @__PURE__ */ jsx3("span", { children: isQuestion2 ? "Question Editor" : variant === "journey" ? "Step Editor" : "Step Editor" })
|
|
768
|
+
]
|
|
747
769
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
/* @__PURE__ */ jsxs3("
|
|
751
|
-
/* @__PURE__ */ jsx3(
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
"\u2192 ",
|
|
779
|
-
target?.label ?? edge.to
|
|
780
|
-
] }),
|
|
781
|
-
/* @__PURE__ */ jsx3("input", { value: edge.label ?? "", onChange: (e) => updateEdgeLabel(edge.id, e.target.value), placeholder: "Edge label (optional)", style: { ...inputStyle, fontSize: 11, padding: "4px 8px" } })
|
|
782
|
-
] }),
|
|
783
|
-
/* @__PURE__ */ jsx3("button", { onClick: () => removeEdge(edge.id), style: { background: "none", border: "none", color: tt.textMuted, cursor: "pointer", fontSize: 12, padding: "8px 10px", flexShrink: 0 }, title: "Remove", children: "\u2715" })
|
|
784
|
-
] }, edge.id);
|
|
785
|
-
}),
|
|
786
|
-
addingBranch ? /* @__PURE__ */ jsxs3("div", { role: "group", "aria-label": "Add branch form", onKeyDown: (e) => {
|
|
787
|
-
if (e.key === "Escape") setAddingBranch(false);
|
|
788
|
-
}, style: { marginTop: 10, background: tt.addFormBg, borderRadius: 10, padding: 12, border: `1.5px solid ${accentBorder}` }, children: [
|
|
789
|
-
/* @__PURE__ */ jsx3("div", { style: { display: "flex", gap: 6, marginBottom: 10 }, children: ["new", "existing"].map((mode) => /* @__PURE__ */ jsx3("button", { onClick: () => setBranchMode(mode), style: {
|
|
790
|
-
flex: 1,
|
|
791
|
-
padding: "5px 0",
|
|
792
|
-
border: "none",
|
|
793
|
-
borderRadius: 6,
|
|
794
|
-
cursor: "pointer",
|
|
795
|
-
fontSize: 11,
|
|
796
|
-
fontWeight: 600,
|
|
797
|
-
background: branchMode === mode ? accentColor : tt.btnSecBg,
|
|
798
|
-
color: branchMode === mode ? "#fff" : tt.btnSecText
|
|
799
|
-
}, children: mode === "new" ? `+ New step` : "Existing step" }, mode)) }),
|
|
800
|
-
branchMode === "new" ? /* @__PURE__ */ jsx3("input", { autoFocus: true, value: branchLabel, onChange: (e) => setBranchLabel(e.target.value), onKeyDown: (e) => e.key === "Enter" && addBranch(), placeholder: "New step name\u2026", style: { ...inputStyle, marginBottom: 6 } }) : /* @__PURE__ */ jsxs3("select", { value: branchTarget, onChange: (e) => setBranchTarget(e.target.value), style: { ...inputStyle, marginBottom: 6, appearance: "none" }, children: [
|
|
801
|
-
/* @__PURE__ */ jsx3("option", { value: "", children: "Choose a step\u2026" }),
|
|
802
|
-
otherNodes.map((n) => /* @__PURE__ */ jsx3("option", { value: n.id, children: n.label }, n.id))
|
|
770
|
+
),
|
|
771
|
+
/* @__PURE__ */ jsxs3("div", { style: { flex: 1, overflowY: "auto", display: "flex", flexDirection: "column" }, children: [
|
|
772
|
+
/* @__PURE__ */ jsxs3("section", { style: { padding: "14px 16px", borderBottom: `1px solid ${tt.sectionBorder}` }, children: [
|
|
773
|
+
/* @__PURE__ */ jsx3(
|
|
774
|
+
"label",
|
|
775
|
+
{
|
|
776
|
+
style: {
|
|
777
|
+
display: "block",
|
|
778
|
+
fontSize: 10,
|
|
779
|
+
fontWeight: 700,
|
|
780
|
+
color: tt.labelText,
|
|
781
|
+
marginBottom: 8,
|
|
782
|
+
textTransform: "uppercase",
|
|
783
|
+
letterSpacing: 0.8
|
|
784
|
+
},
|
|
785
|
+
children: "Name"
|
|
786
|
+
}
|
|
787
|
+
),
|
|
788
|
+
/* @__PURE__ */ jsx3(
|
|
789
|
+
"input",
|
|
790
|
+
{
|
|
791
|
+
ref: inputRef,
|
|
792
|
+
value: label,
|
|
793
|
+
onChange: (e) => setLabel(e.target.value),
|
|
794
|
+
onBlur: commitLabel,
|
|
795
|
+
onKeyDown: (e) => e.key === "Enter" && commitLabel(),
|
|
796
|
+
style: inputStyle,
|
|
797
|
+
placeholder: "Step name\u2026"
|
|
798
|
+
}
|
|
799
|
+
)
|
|
803
800
|
] }),
|
|
804
|
-
/* @__PURE__ */
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
801
|
+
!isQuestion2 && /* @__PURE__ */ jsxs3("section", { style: { padding: "14px 16px", borderBottom: `1px solid ${tt.sectionBorder}` }, children: [
|
|
802
|
+
/* @__PURE__ */ jsx3(
|
|
803
|
+
"label",
|
|
804
|
+
{
|
|
805
|
+
style: {
|
|
806
|
+
display: "block",
|
|
807
|
+
fontSize: 10,
|
|
808
|
+
fontWeight: 700,
|
|
809
|
+
color: tt.labelText,
|
|
810
|
+
marginBottom: 8,
|
|
811
|
+
textTransform: "uppercase",
|
|
812
|
+
letterSpacing: 0.8
|
|
813
|
+
},
|
|
814
|
+
children: "Shape"
|
|
815
|
+
}
|
|
816
|
+
),
|
|
817
|
+
/* @__PURE__ */ jsx3("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 6 }, children: SHAPES.map((s2) => {
|
|
818
|
+
const active = (node.shape ?? "rectangle") === s2.key;
|
|
819
|
+
return /* @__PURE__ */ jsxs3(
|
|
820
|
+
"button",
|
|
821
|
+
{
|
|
822
|
+
onClick: () => setShape(s2.key),
|
|
823
|
+
"aria-pressed": active,
|
|
824
|
+
style: {
|
|
825
|
+
display: "flex",
|
|
826
|
+
flexDirection: "column",
|
|
827
|
+
alignItems: "center",
|
|
828
|
+
gap: 4,
|
|
829
|
+
padding: "8px 6px",
|
|
830
|
+
borderRadius: 8,
|
|
831
|
+
cursor: "pointer",
|
|
832
|
+
transition: "all 0.15s",
|
|
833
|
+
background: active ? accentColor : tt.shapeBtnBg,
|
|
834
|
+
color: active ? "#fff" : tt.textSecondary,
|
|
835
|
+
border: active ? `1.5px solid ${accentColor}` : `1.5px solid ${tt.shapeBtnBorder}`
|
|
836
|
+
},
|
|
837
|
+
children: [
|
|
838
|
+
/* @__PURE__ */ jsx3("span", { style: { fontSize: 16, lineHeight: 1 }, children: s2.icon }),
|
|
839
|
+
/* @__PURE__ */ jsx3("span", { style: { fontSize: 11, fontWeight: 500 }, children: s2.label })
|
|
840
|
+
]
|
|
841
|
+
},
|
|
842
|
+
s2.key
|
|
843
|
+
);
|
|
844
|
+
}) })
|
|
845
|
+
] }),
|
|
846
|
+
isQuestion2 && /* @__PURE__ */ jsxs3(
|
|
847
|
+
"section",
|
|
848
|
+
{
|
|
849
|
+
style: { padding: "14px 16px", borderBottom: `1px solid ${tt.sectionBorder}`, flex: 1 },
|
|
850
|
+
children: [
|
|
851
|
+
/* @__PURE__ */ jsxs3(
|
|
852
|
+
"div",
|
|
853
|
+
{
|
|
854
|
+
style: {
|
|
855
|
+
display: "flex",
|
|
856
|
+
alignItems: "center",
|
|
857
|
+
justifyContent: "space-between",
|
|
858
|
+
marginBottom: 10
|
|
859
|
+
},
|
|
860
|
+
children: [
|
|
861
|
+
/* @__PURE__ */ jsx3(
|
|
862
|
+
"label",
|
|
863
|
+
{
|
|
864
|
+
style: {
|
|
865
|
+
display: "block",
|
|
866
|
+
fontSize: 10,
|
|
867
|
+
fontWeight: 700,
|
|
868
|
+
color: tt.labelText,
|
|
869
|
+
textTransform: "uppercase",
|
|
870
|
+
letterSpacing: 0.8
|
|
871
|
+
},
|
|
872
|
+
children: "Answers"
|
|
873
|
+
}
|
|
874
|
+
),
|
|
875
|
+
/* @__PURE__ */ jsx3(
|
|
876
|
+
"span",
|
|
877
|
+
{
|
|
878
|
+
style: {
|
|
879
|
+
fontSize: 11,
|
|
880
|
+
color: tt.textMuted,
|
|
881
|
+
background: isDark ? "#0f172a" : "#f1f5f9",
|
|
882
|
+
padding: "1px 7px",
|
|
883
|
+
borderRadius: 99,
|
|
884
|
+
fontWeight: 600
|
|
885
|
+
},
|
|
886
|
+
children: answers.length
|
|
887
|
+
}
|
|
888
|
+
)
|
|
889
|
+
]
|
|
890
|
+
}
|
|
891
|
+
),
|
|
892
|
+
answers.length === 0 && !addingAnswer && /* @__PURE__ */ jsx3(
|
|
893
|
+
"div",
|
|
894
|
+
{
|
|
895
|
+
style: {
|
|
896
|
+
fontSize: 12,
|
|
897
|
+
color: tt.textMuted,
|
|
898
|
+
textAlign: "center",
|
|
899
|
+
padding: "16px 0",
|
|
900
|
+
fontStyle: "italic"
|
|
901
|
+
},
|
|
902
|
+
children: "No answers yet \u2014 add one below"
|
|
903
|
+
}
|
|
904
|
+
),
|
|
905
|
+
answers.map((ans, i) => {
|
|
906
|
+
const connected = model.edges.some((e) => e.from === nodeId && e.label === ans);
|
|
907
|
+
const targetNode = model.nodes.find((n) => {
|
|
908
|
+
const e = model.edges.find((ex) => ex.from === nodeId && ex.label === ans);
|
|
909
|
+
return e && n.id === e.to;
|
|
910
|
+
});
|
|
911
|
+
return /* @__PURE__ */ jsxs3(
|
|
912
|
+
"div",
|
|
913
|
+
{
|
|
914
|
+
style: {
|
|
915
|
+
display: "flex",
|
|
916
|
+
alignItems: "flex-start",
|
|
917
|
+
gap: 0,
|
|
918
|
+
marginBottom: 8,
|
|
919
|
+
borderRadius: 12,
|
|
920
|
+
border: `1px solid ${tt.cardBorder}`,
|
|
921
|
+
overflow: "hidden",
|
|
922
|
+
background: tt.cardBg,
|
|
923
|
+
boxShadow: isDark ? "none" : "0 1px 2px rgba(15,23,42,0.04)"
|
|
924
|
+
},
|
|
925
|
+
children: [
|
|
926
|
+
/* @__PURE__ */ jsx3(
|
|
927
|
+
"div",
|
|
928
|
+
{
|
|
929
|
+
style: {
|
|
930
|
+
width: 4,
|
|
931
|
+
alignSelf: "stretch",
|
|
932
|
+
background: accentColor,
|
|
933
|
+
flexShrink: 0
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
),
|
|
937
|
+
/* @__PURE__ */ jsxs3("div", { style: { flex: 1, minWidth: 0, padding: "8px 10px" }, children: [
|
|
938
|
+
/* @__PURE__ */ jsx3(
|
|
939
|
+
"div",
|
|
940
|
+
{
|
|
941
|
+
style: {
|
|
942
|
+
fontSize: 12,
|
|
943
|
+
fontWeight: 600,
|
|
944
|
+
color: tt.textPrimary,
|
|
945
|
+
marginBottom: connected ? 3 : 0,
|
|
946
|
+
overflow: "hidden",
|
|
947
|
+
textOverflow: "ellipsis",
|
|
948
|
+
whiteSpace: "nowrap"
|
|
949
|
+
},
|
|
950
|
+
children: ans
|
|
951
|
+
}
|
|
952
|
+
),
|
|
953
|
+
connected && targetNode && /* @__PURE__ */ jsxs3("div", { style: { fontSize: 11, color: accentColor, opacity: 0.85 }, children: [
|
|
954
|
+
"\u2192 ",
|
|
955
|
+
targetNode.label
|
|
956
|
+
] }),
|
|
957
|
+
!connected && /* @__PURE__ */ jsx3("div", { style: { fontSize: 10, color: tt.textMuted, fontStyle: "italic" }, children: "drag port to connect" })
|
|
958
|
+
] }),
|
|
959
|
+
/* @__PURE__ */ jsxs3(
|
|
960
|
+
"div",
|
|
961
|
+
{
|
|
962
|
+
style: { display: "flex", flexDirection: "column", padding: "4px 2px", gap: 2 },
|
|
963
|
+
children: [
|
|
964
|
+
/* @__PURE__ */ jsx3(
|
|
965
|
+
"button",
|
|
966
|
+
{
|
|
967
|
+
onClick: () => moveAnswer(i, -1),
|
|
968
|
+
disabled: i === 0,
|
|
969
|
+
style: {
|
|
970
|
+
background: "none",
|
|
971
|
+
border: "none",
|
|
972
|
+
color: tt.textMuted,
|
|
973
|
+
cursor: "pointer",
|
|
974
|
+
fontSize: 11,
|
|
975
|
+
padding: "2px 4px",
|
|
976
|
+
opacity: i === 0 ? 0.3 : 1
|
|
977
|
+
},
|
|
978
|
+
children: "\u2191"
|
|
979
|
+
}
|
|
980
|
+
),
|
|
981
|
+
/* @__PURE__ */ jsx3(
|
|
982
|
+
"button",
|
|
983
|
+
{
|
|
984
|
+
onClick: () => moveAnswer(i, 1),
|
|
985
|
+
disabled: i === answers.length - 1,
|
|
986
|
+
style: {
|
|
987
|
+
background: "none",
|
|
988
|
+
border: "none",
|
|
989
|
+
color: tt.textMuted,
|
|
990
|
+
cursor: "pointer",
|
|
991
|
+
fontSize: 11,
|
|
992
|
+
padding: "2px 4px",
|
|
993
|
+
opacity: i === answers.length - 1 ? 0.3 : 1
|
|
994
|
+
},
|
|
995
|
+
children: "\u2193"
|
|
996
|
+
}
|
|
997
|
+
)
|
|
998
|
+
]
|
|
999
|
+
}
|
|
1000
|
+
),
|
|
1001
|
+
/* @__PURE__ */ jsx3(
|
|
1002
|
+
"button",
|
|
1003
|
+
{
|
|
1004
|
+
onClick: () => removeAnswer(ans),
|
|
1005
|
+
style: {
|
|
1006
|
+
background: "none",
|
|
1007
|
+
border: "none",
|
|
1008
|
+
color: tt.textMuted,
|
|
1009
|
+
cursor: "pointer",
|
|
1010
|
+
fontSize: 12,
|
|
1011
|
+
padding: "8px 10px",
|
|
1012
|
+
flexShrink: 0
|
|
1013
|
+
},
|
|
1014
|
+
title: "Remove",
|
|
1015
|
+
children: "\u2715"
|
|
1016
|
+
}
|
|
1017
|
+
)
|
|
1018
|
+
]
|
|
1019
|
+
},
|
|
1020
|
+
ans + i
|
|
1021
|
+
);
|
|
1022
|
+
}),
|
|
1023
|
+
addingAnswer ? /* @__PURE__ */ jsxs3(
|
|
1024
|
+
"div",
|
|
1025
|
+
{
|
|
1026
|
+
role: "group",
|
|
1027
|
+
"aria-label": "Add answer form",
|
|
1028
|
+
onKeyDown: (e) => {
|
|
1029
|
+
if (e.key === "Escape") {
|
|
1030
|
+
setAddingAnswer(false);
|
|
1031
|
+
setNewAnswer("");
|
|
1032
|
+
}
|
|
1033
|
+
},
|
|
1034
|
+
style: {
|
|
1035
|
+
marginTop: 10,
|
|
1036
|
+
background: tt.addFormBg,
|
|
1037
|
+
borderRadius: 10,
|
|
1038
|
+
padding: 12,
|
|
1039
|
+
border: `1.5px solid ${accentBorder}`
|
|
1040
|
+
},
|
|
1041
|
+
children: [
|
|
1042
|
+
/* @__PURE__ */ jsx3(
|
|
1043
|
+
"input",
|
|
1044
|
+
{
|
|
1045
|
+
autoFocus: true,
|
|
1046
|
+
value: newAnswer,
|
|
1047
|
+
onChange: (e) => setNewAnswer(e.target.value),
|
|
1048
|
+
onKeyDown: (e) => e.key === "Enter" && addAnswer(),
|
|
1049
|
+
placeholder: "Answer text\u2026",
|
|
1050
|
+
style: { ...inputStyle, marginBottom: 8 }
|
|
1051
|
+
}
|
|
1052
|
+
),
|
|
1053
|
+
/* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: 6 }, children: [
|
|
1054
|
+
/* @__PURE__ */ jsx3("button", { onClick: addAnswer, style: addBtnStyle, children: "Add Answer" }),
|
|
1055
|
+
/* @__PURE__ */ jsx3(
|
|
1056
|
+
"button",
|
|
1057
|
+
{
|
|
1058
|
+
onClick: () => {
|
|
1059
|
+
setAddingAnswer(false);
|
|
1060
|
+
setNewAnswer("");
|
|
1061
|
+
},
|
|
1062
|
+
style: cancelBtnStyle,
|
|
1063
|
+
children: "Cancel"
|
|
1064
|
+
}
|
|
1065
|
+
)
|
|
1066
|
+
] })
|
|
1067
|
+
]
|
|
1068
|
+
}
|
|
1069
|
+
) : /* @__PURE__ */ jsxs3("button", { onClick: () => setAddingAnswer(true), style: addTriggerStyle, children: [
|
|
1070
|
+
/* @__PURE__ */ jsx3("span", { style: { fontSize: 16, lineHeight: 1 }, children: "+" }),
|
|
1071
|
+
" Add Answer"
|
|
1072
|
+
] }),
|
|
1073
|
+
answers.length > 0 && /* @__PURE__ */ jsxs3(
|
|
1074
|
+
"div",
|
|
1075
|
+
{
|
|
1076
|
+
style: {
|
|
1077
|
+
marginTop: 12,
|
|
1078
|
+
padding: "8px 10px",
|
|
1079
|
+
background: isDark ? "rgba(251,191,36,0.06)" : "#fef9f0",
|
|
1080
|
+
borderRadius: 8,
|
|
1081
|
+
border: `1px solid ${accentBorder}`
|
|
1082
|
+
},
|
|
1083
|
+
children: [
|
|
1084
|
+
/* @__PURE__ */ jsx3(
|
|
1085
|
+
"div",
|
|
1086
|
+
{
|
|
1087
|
+
style: {
|
|
1088
|
+
fontSize: 10,
|
|
1089
|
+
fontWeight: 700,
|
|
1090
|
+
color: accentColor,
|
|
1091
|
+
textTransform: "uppercase",
|
|
1092
|
+
letterSpacing: 0.6,
|
|
1093
|
+
marginBottom: 4
|
|
1094
|
+
},
|
|
1095
|
+
children: "How to connect"
|
|
1096
|
+
}
|
|
1097
|
+
),
|
|
1098
|
+
/* @__PURE__ */ jsx3("div", { style: { fontSize: 11, color: tt.textSecondary, lineHeight: 1.5 }, children: "Hover the question node on the canvas \u2014 drag an answer's port dot to any other node." })
|
|
1099
|
+
]
|
|
1100
|
+
}
|
|
1101
|
+
)
|
|
1102
|
+
]
|
|
1103
|
+
}
|
|
1104
|
+
),
|
|
1105
|
+
!isQuestion2 && /* @__PURE__ */ jsxs3(
|
|
1106
|
+
"section",
|
|
1107
|
+
{
|
|
1108
|
+
style: { padding: "14px 16px", borderBottom: `1px solid ${tt.sectionBorder}`, flex: 1 },
|
|
1109
|
+
children: [
|
|
1110
|
+
/* @__PURE__ */ jsxs3(
|
|
1111
|
+
"div",
|
|
1112
|
+
{
|
|
1113
|
+
style: {
|
|
1114
|
+
display: "flex",
|
|
1115
|
+
alignItems: "center",
|
|
1116
|
+
justifyContent: "space-between",
|
|
1117
|
+
marginBottom: 10
|
|
1118
|
+
},
|
|
1119
|
+
children: [
|
|
1120
|
+
/* @__PURE__ */ jsx3(
|
|
1121
|
+
"label",
|
|
1122
|
+
{
|
|
1123
|
+
style: {
|
|
1124
|
+
display: "block",
|
|
1125
|
+
fontSize: 10,
|
|
1126
|
+
fontWeight: 700,
|
|
1127
|
+
color: tt.labelText,
|
|
1128
|
+
textTransform: "uppercase",
|
|
1129
|
+
letterSpacing: 0.8
|
|
1130
|
+
},
|
|
1131
|
+
children: "Branches"
|
|
1132
|
+
}
|
|
1133
|
+
),
|
|
1134
|
+
/* @__PURE__ */ jsx3(
|
|
1135
|
+
"span",
|
|
1136
|
+
{
|
|
1137
|
+
style: {
|
|
1138
|
+
fontSize: 11,
|
|
1139
|
+
color: tt.textMuted,
|
|
1140
|
+
background: isDark ? "#0f172a" : "#f1f5f9",
|
|
1141
|
+
padding: "1px 7px",
|
|
1142
|
+
borderRadius: 99,
|
|
1143
|
+
fontWeight: 600
|
|
1144
|
+
},
|
|
1145
|
+
children: outEdges.length
|
|
1146
|
+
}
|
|
1147
|
+
)
|
|
1148
|
+
]
|
|
1149
|
+
}
|
|
1150
|
+
),
|
|
1151
|
+
outEdges.length === 0 && !addingBranch && /* @__PURE__ */ jsx3(
|
|
1152
|
+
"div",
|
|
1153
|
+
{
|
|
1154
|
+
style: {
|
|
1155
|
+
fontSize: 12,
|
|
1156
|
+
color: tt.textMuted,
|
|
1157
|
+
textAlign: "center",
|
|
1158
|
+
padding: "16px 0",
|
|
1159
|
+
fontStyle: "italic"
|
|
1160
|
+
},
|
|
1161
|
+
children: "No outgoing connections yet"
|
|
1162
|
+
}
|
|
1163
|
+
),
|
|
1164
|
+
outEdges.map((edge) => {
|
|
1165
|
+
const target = model.nodes.find((n) => n.id === edge.to);
|
|
1166
|
+
return /* @__PURE__ */ jsxs3(
|
|
1167
|
+
"div",
|
|
1168
|
+
{
|
|
1169
|
+
style: {
|
|
1170
|
+
display: "flex",
|
|
1171
|
+
alignItems: "flex-start",
|
|
1172
|
+
gap: 0,
|
|
1173
|
+
marginBottom: 8,
|
|
1174
|
+
borderRadius: 12,
|
|
1175
|
+
border: `1px solid ${tt.cardBorder}`,
|
|
1176
|
+
overflow: "hidden",
|
|
1177
|
+
background: tt.cardBg,
|
|
1178
|
+
boxShadow: isDark ? "none" : "0 1px 2px rgba(15,23,42,0.04)"
|
|
1179
|
+
},
|
|
1180
|
+
children: [
|
|
1181
|
+
/* @__PURE__ */ jsx3(
|
|
1182
|
+
"div",
|
|
1183
|
+
{
|
|
1184
|
+
style: {
|
|
1185
|
+
width: 4,
|
|
1186
|
+
alignSelf: "stretch",
|
|
1187
|
+
background: accentColor,
|
|
1188
|
+
flexShrink: 0
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
),
|
|
1192
|
+
/* @__PURE__ */ jsxs3("div", { style: { flex: 1, minWidth: 0, padding: "8px 10px" }, children: [
|
|
1193
|
+
/* @__PURE__ */ jsxs3(
|
|
1194
|
+
"div",
|
|
1195
|
+
{
|
|
1196
|
+
style: {
|
|
1197
|
+
fontSize: 12,
|
|
1198
|
+
fontWeight: 600,
|
|
1199
|
+
color: tt.textPrimary,
|
|
1200
|
+
marginBottom: 5,
|
|
1201
|
+
overflow: "hidden",
|
|
1202
|
+
textOverflow: "ellipsis",
|
|
1203
|
+
whiteSpace: "nowrap"
|
|
1204
|
+
},
|
|
1205
|
+
children: [
|
|
1206
|
+
"\u2192 ",
|
|
1207
|
+
target?.label ?? edge.to
|
|
1208
|
+
]
|
|
1209
|
+
}
|
|
1210
|
+
),
|
|
1211
|
+
/* @__PURE__ */ jsx3(
|
|
1212
|
+
"input",
|
|
1213
|
+
{
|
|
1214
|
+
value: edge.label ?? "",
|
|
1215
|
+
onChange: (e) => updateEdgeLabel(edge.id, e.target.value),
|
|
1216
|
+
placeholder: "Edge label (optional)",
|
|
1217
|
+
style: { ...inputStyle, fontSize: 11, padding: "4px 8px" }
|
|
1218
|
+
}
|
|
1219
|
+
)
|
|
1220
|
+
] }),
|
|
1221
|
+
/* @__PURE__ */ jsx3(
|
|
1222
|
+
"button",
|
|
1223
|
+
{
|
|
1224
|
+
onClick: () => removeEdge(edge.id),
|
|
1225
|
+
style: {
|
|
1226
|
+
background: "none",
|
|
1227
|
+
border: "none",
|
|
1228
|
+
color: tt.textMuted,
|
|
1229
|
+
cursor: "pointer",
|
|
1230
|
+
fontSize: 12,
|
|
1231
|
+
padding: "8px 10px",
|
|
1232
|
+
flexShrink: 0
|
|
1233
|
+
},
|
|
1234
|
+
title: "Remove",
|
|
1235
|
+
children: "\u2715"
|
|
1236
|
+
}
|
|
1237
|
+
)
|
|
1238
|
+
]
|
|
1239
|
+
},
|
|
1240
|
+
edge.id
|
|
1241
|
+
);
|
|
1242
|
+
}),
|
|
1243
|
+
addingBranch ? /* @__PURE__ */ jsxs3(
|
|
1244
|
+
"div",
|
|
1245
|
+
{
|
|
1246
|
+
role: "group",
|
|
1247
|
+
"aria-label": "Add branch form",
|
|
1248
|
+
onKeyDown: (e) => {
|
|
1249
|
+
if (e.key === "Escape") setAddingBranch(false);
|
|
1250
|
+
},
|
|
1251
|
+
style: {
|
|
1252
|
+
marginTop: 10,
|
|
1253
|
+
background: tt.addFormBg,
|
|
1254
|
+
borderRadius: 10,
|
|
1255
|
+
padding: 12,
|
|
1256
|
+
border: `1.5px solid ${accentBorder}`
|
|
1257
|
+
},
|
|
1258
|
+
children: [
|
|
1259
|
+
/* @__PURE__ */ jsx3("div", { style: { display: "flex", gap: 6, marginBottom: 10 }, children: ["new", "existing"].map((mode) => /* @__PURE__ */ jsx3(
|
|
1260
|
+
"button",
|
|
1261
|
+
{
|
|
1262
|
+
onClick: () => setBranchMode(mode),
|
|
1263
|
+
style: {
|
|
1264
|
+
flex: 1,
|
|
1265
|
+
padding: "5px 0",
|
|
1266
|
+
border: "none",
|
|
1267
|
+
borderRadius: 6,
|
|
1268
|
+
cursor: "pointer",
|
|
1269
|
+
fontSize: 11,
|
|
1270
|
+
fontWeight: 600,
|
|
1271
|
+
background: branchMode === mode ? accentColor : tt.btnSecBg,
|
|
1272
|
+
color: branchMode === mode ? "#fff" : tt.btnSecText
|
|
1273
|
+
},
|
|
1274
|
+
children: mode === "new" ? `+ New step` : "Existing step"
|
|
1275
|
+
},
|
|
1276
|
+
mode
|
|
1277
|
+
)) }),
|
|
1278
|
+
branchMode === "new" ? /* @__PURE__ */ jsx3(
|
|
1279
|
+
"input",
|
|
1280
|
+
{
|
|
1281
|
+
autoFocus: true,
|
|
1282
|
+
value: branchLabel,
|
|
1283
|
+
onChange: (e) => setBranchLabel(e.target.value),
|
|
1284
|
+
onKeyDown: (e) => e.key === "Enter" && addBranch(),
|
|
1285
|
+
placeholder: "New step name\u2026",
|
|
1286
|
+
style: { ...inputStyle, marginBottom: 6 }
|
|
1287
|
+
}
|
|
1288
|
+
) : /* @__PURE__ */ jsxs3(
|
|
1289
|
+
"select",
|
|
1290
|
+
{
|
|
1291
|
+
value: branchTarget,
|
|
1292
|
+
onChange: (e) => setBranchTarget(e.target.value),
|
|
1293
|
+
style: { ...inputStyle, marginBottom: 6, appearance: "none" },
|
|
1294
|
+
children: [
|
|
1295
|
+
/* @__PURE__ */ jsx3("option", { value: "", children: "Choose a step\u2026" }),
|
|
1296
|
+
otherNodes.map((n) => /* @__PURE__ */ jsx3("option", { value: n.id, children: n.label }, n.id))
|
|
1297
|
+
]
|
|
1298
|
+
}
|
|
1299
|
+
),
|
|
1300
|
+
/* @__PURE__ */ jsx3(
|
|
1301
|
+
"input",
|
|
1302
|
+
{
|
|
1303
|
+
value: branchEdgeLabel,
|
|
1304
|
+
onChange: (e) => setBranchEdgeLabel(e.target.value),
|
|
1305
|
+
placeholder: "Edge label (optional)",
|
|
1306
|
+
style: { ...inputStyle, marginBottom: 10 }
|
|
1307
|
+
}
|
|
1308
|
+
),
|
|
1309
|
+
/* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: 6 }, children: [
|
|
1310
|
+
/* @__PURE__ */ jsxs3("button", { onClick: addBranch, style: addBtnStyle, children: [
|
|
1311
|
+
"Add ",
|
|
1312
|
+
branchTerm
|
|
1313
|
+
] }),
|
|
1314
|
+
/* @__PURE__ */ jsx3("button", { onClick: () => setAddingBranch(false), style: cancelBtnStyle, children: "Cancel" })
|
|
1315
|
+
] })
|
|
1316
|
+
]
|
|
1317
|
+
}
|
|
1318
|
+
) : /* @__PURE__ */ jsxs3("button", { onClick: () => setAddingBranch(true), style: addTriggerStyle, children: [
|
|
1319
|
+
/* @__PURE__ */ jsx3("span", { style: { fontSize: 16, lineHeight: 1 }, children: "+" }),
|
|
1320
|
+
" Add ",
|
|
1321
|
+
branchTerm
|
|
1322
|
+
] })
|
|
1323
|
+
]
|
|
1324
|
+
}
|
|
1325
|
+
)
|
|
816
1326
|
] })
|
|
817
|
-
]
|
|
818
|
-
|
|
819
|
-
|
|
1327
|
+
]
|
|
1328
|
+
}
|
|
1329
|
+
);
|
|
820
1330
|
}
|
|
821
1331
|
|
|
822
1332
|
// src/ui/SequenceEditor.tsx
|
|
@@ -871,26 +1381,33 @@ function SequenceCanvas(props) {
|
|
|
871
1381
|
return next;
|
|
872
1382
|
}, [messages, drag]);
|
|
873
1383
|
if (actors.length === 0 && messages.length === 0) {
|
|
874
|
-
return /* @__PURE__ */ jsxs4(
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1384
|
+
return /* @__PURE__ */ jsxs4(
|
|
1385
|
+
"div",
|
|
1386
|
+
{
|
|
1387
|
+
style: {
|
|
1388
|
+
position: "absolute",
|
|
1389
|
+
inset: 0,
|
|
1390
|
+
display: "flex",
|
|
1391
|
+
flexDirection: "column",
|
|
1392
|
+
alignItems: "center",
|
|
1393
|
+
justifyContent: "center",
|
|
1394
|
+
gap: 10,
|
|
1395
|
+
color: t.textMuted,
|
|
1396
|
+
pointerEvents: "none"
|
|
1397
|
+
},
|
|
1398
|
+
children: [
|
|
1399
|
+
/* @__PURE__ */ jsx4("div", { style: { fontSize: 36, opacity: 0.15, color: t.textPrimary }, children: "\u2194" }),
|
|
1400
|
+
/* @__PURE__ */ jsxs4("div", { style: { fontSize: 13, fontWeight: 500 }, children: [
|
|
1401
|
+
"Click ",
|
|
1402
|
+
/* @__PURE__ */ jsx4("strong", { style: { color: INDIGO }, children: "+ Actor" }),
|
|
1403
|
+
" then",
|
|
1404
|
+
" ",
|
|
1405
|
+
/* @__PURE__ */ jsx4("strong", { style: { color: INDIGO }, children: "+ Message" }),
|
|
1406
|
+
" to start"
|
|
1407
|
+
] })
|
|
1408
|
+
]
|
|
1409
|
+
}
|
|
1410
|
+
);
|
|
894
1411
|
}
|
|
895
1412
|
return /* @__PURE__ */ jsxs4(
|
|
896
1413
|
"svg",
|
|
@@ -898,12 +1415,28 @@ function SequenceCanvas(props) {
|
|
|
898
1415
|
ref: svgRef,
|
|
899
1416
|
width: totalW,
|
|
900
1417
|
height: totalH,
|
|
901
|
-
style: {
|
|
1418
|
+
style: {
|
|
1419
|
+
display: "block",
|
|
1420
|
+
cursor: drag?.active ? "grabbing" : "default",
|
|
1421
|
+
userSelect: "none"
|
|
1422
|
+
},
|
|
902
1423
|
children: [
|
|
903
1424
|
/* @__PURE__ */ jsxs4("defs", { children: [
|
|
904
1425
|
/* @__PURE__ */ jsx4("pattern", { id: "seqdots", x: "0", y: "0", width: "24", height: "24", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ jsx4("circle", { cx: 12, cy: 12, r: 1.1, fill: t.dot }) }),
|
|
905
1426
|
/* @__PURE__ */ jsx4("filter", { id: "seqShadow", x: "-20%", y: "-20%", width: "140%", height: "140%", children: /* @__PURE__ */ jsx4("feDropShadow", { dx: 0, dy: 3, stdDeviation: 5, floodColor: shadowColor(isDark) }) }),
|
|
906
|
-
/* @__PURE__ */ jsx4(
|
|
1427
|
+
/* @__PURE__ */ jsx4(
|
|
1428
|
+
"marker",
|
|
1429
|
+
{
|
|
1430
|
+
id: "seqArrow",
|
|
1431
|
+
markerWidth: 9,
|
|
1432
|
+
markerHeight: 7,
|
|
1433
|
+
refX: 8.5,
|
|
1434
|
+
refY: 3.5,
|
|
1435
|
+
orient: "auto",
|
|
1436
|
+
markerUnits: "strokeWidth",
|
|
1437
|
+
children: /* @__PURE__ */ jsx4("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: t.arrow })
|
|
1438
|
+
}
|
|
1439
|
+
)
|
|
907
1440
|
] }),
|
|
908
1441
|
/* @__PURE__ */ jsx4("rect", { width: totalW, height: totalH, fill: "url(#seqdots)" }),
|
|
909
1442
|
actors.map((name) => {
|
|
@@ -952,8 +1485,28 @@ function SequenceCanvas(props) {
|
|
|
952
1485
|
opacity: isDark ? 0.18 : 0.6
|
|
953
1486
|
}
|
|
954
1487
|
),
|
|
955
|
-
/* @__PURE__ */ jsx4(
|
|
956
|
-
|
|
1488
|
+
/* @__PURE__ */ jsx4(
|
|
1489
|
+
"path",
|
|
1490
|
+
{
|
|
1491
|
+
d,
|
|
1492
|
+
fill: "none",
|
|
1493
|
+
stroke,
|
|
1494
|
+
strokeWidth: 1.5,
|
|
1495
|
+
strokeDasharray: dash,
|
|
1496
|
+
markerEnd: "url(#seqArrow)"
|
|
1497
|
+
}
|
|
1498
|
+
),
|
|
1499
|
+
/* @__PURE__ */ jsx4(
|
|
1500
|
+
"text",
|
|
1501
|
+
{
|
|
1502
|
+
x: startX + loopW + 8,
|
|
1503
|
+
y: loopY + 16,
|
|
1504
|
+
fontSize: 11,
|
|
1505
|
+
fill: selectedHere ? INDIGO : t.textPrimary,
|
|
1506
|
+
fontWeight: 500,
|
|
1507
|
+
children: msg.label
|
|
1508
|
+
}
|
|
1509
|
+
)
|
|
957
1510
|
] }, msg.id);
|
|
958
1511
|
}
|
|
959
1512
|
const labelX = (fromX + toX) / 2;
|
|
@@ -970,7 +1523,19 @@ function SequenceCanvas(props) {
|
|
|
970
1523
|
opacity: isDark ? 0.18 : 0.6
|
|
971
1524
|
}
|
|
972
1525
|
),
|
|
973
|
-
/* @__PURE__ */ jsx4(
|
|
1526
|
+
/* @__PURE__ */ jsx4(
|
|
1527
|
+
"line",
|
|
1528
|
+
{
|
|
1529
|
+
x1: fromX,
|
|
1530
|
+
y1: y,
|
|
1531
|
+
x2: toX,
|
|
1532
|
+
y2: y,
|
|
1533
|
+
stroke,
|
|
1534
|
+
strokeWidth: 1.5,
|
|
1535
|
+
strokeDasharray: dash,
|
|
1536
|
+
markerEnd: "url(#seqArrow)"
|
|
1537
|
+
}
|
|
1538
|
+
),
|
|
974
1539
|
/* @__PURE__ */ jsx4(
|
|
975
1540
|
"rect",
|
|
976
1541
|
{
|
|
@@ -984,7 +1549,18 @@ function SequenceCanvas(props) {
|
|
|
984
1549
|
strokeWidth: selectedHere ? 1.25 : 1
|
|
985
1550
|
}
|
|
986
1551
|
),
|
|
987
|
-
/* @__PURE__ */ jsx4(
|
|
1552
|
+
/* @__PURE__ */ jsx4(
|
|
1553
|
+
"text",
|
|
1554
|
+
{
|
|
1555
|
+
x: labelX,
|
|
1556
|
+
y: y - 5,
|
|
1557
|
+
textAnchor: "middle",
|
|
1558
|
+
fontSize: 11,
|
|
1559
|
+
fill: selectedHere ? INDIGO : t.textPrimary,
|
|
1560
|
+
fontWeight: 500,
|
|
1561
|
+
children: msg.label
|
|
1562
|
+
}
|
|
1563
|
+
)
|
|
988
1564
|
] }, msg.id);
|
|
989
1565
|
}),
|
|
990
1566
|
actors.map((name) => {
|
|
@@ -1324,7 +1900,9 @@ function computeLayout(model) {
|
|
|
1324
1900
|
const h = isQuestion(n, model.variant) ? questionNodeH(n.metadata?.answers ?? []) : NODE_H;
|
|
1325
1901
|
return { node: n, w, h };
|
|
1326
1902
|
});
|
|
1327
|
-
const allPositioned = sized.every(
|
|
1903
|
+
const allPositioned = sized.every(
|
|
1904
|
+
(s2) => typeof s2.node.x === "number" && typeof s2.node.y === "number"
|
|
1905
|
+
);
|
|
1328
1906
|
if (allPositioned) {
|
|
1329
1907
|
for (const s2 of sized) {
|
|
1330
1908
|
boxes.set(s2.node.id, { x: s2.node.x, y: s2.node.y, w: s2.w, h: s2.h });
|
|
@@ -1374,7 +1952,15 @@ function computeLayout(model) {
|
|
|
1374
1952
|
return boxes;
|
|
1375
1953
|
}
|
|
1376
1954
|
function escapeXML(s2) {
|
|
1377
|
-
return s2.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1955
|
+
return s2.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1956
|
+
}
|
|
1957
|
+
function sanitizeForSVG(s2) {
|
|
1958
|
+
let clean = s2;
|
|
1959
|
+
clean = clean.replace(/<\/?[a-zA-Z][^>]*>/g, "");
|
|
1960
|
+
clean = clean.replace(/\b(?:javascript|data|vbscript)\s*:/gi, "");
|
|
1961
|
+
clean = clean.replace(/\bon[a-z]+\s*=/gi, "");
|
|
1962
|
+
clean = clean.replace(/\x00/g, "");
|
|
1963
|
+
return escapeXML(clean);
|
|
1378
1964
|
}
|
|
1379
1965
|
var COLORS = {
|
|
1380
1966
|
bg: "#fafbfc",
|
|
@@ -1393,8 +1979,8 @@ function renderStandardNode(node, box) {
|
|
|
1393
1979
|
const cx = box.x + box.w / 2;
|
|
1394
1980
|
const cy = box.y + box.h / 2;
|
|
1395
1981
|
const shape = node.shape ?? "rectangle";
|
|
1396
|
-
const label = `<text x="${cx}" y="${cy + 4.5}" text-anchor="middle" font-family="ui-sans-serif,system-ui,-apple-system,sans-serif" font-size="13" font-weight="500" fill="${COLORS.text}">${
|
|
1397
|
-
let shapeEl
|
|
1982
|
+
const label = `<text x="${cx}" y="${cy + 4.5}" text-anchor="middle" font-family="ui-sans-serif,system-ui,-apple-system,sans-serif" font-size="13" font-weight="500" fill="${COLORS.text}">${sanitizeForSVG(node.label)}</text>`;
|
|
1983
|
+
let shapeEl;
|
|
1398
1984
|
if (shape === "diamond") {
|
|
1399
1985
|
const pts = `${cx},${box.y} ${box.x + box.w},${cy} ${cx},${box.y + box.h} ${box.x},${cy}`;
|
|
1400
1986
|
shapeEl = `<polygon points="${pts}" fill="${COLORS.nodeFill}" stroke="${COLORS.nodeStroke}" stroke-width="1.25" filter="url(#nodeShadow)"/>`;
|
|
@@ -1414,17 +2000,37 @@ function renderQuestionNode(node, box) {
|
|
|
1414
2000
|
const clipId = `qhdr-${node.id.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
1415
2001
|
const x = box.x, y = box.y, w = box.w, h = box.h;
|
|
1416
2002
|
const parts = [];
|
|
1417
|
-
parts.push(
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
parts.push(
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
parts.push(
|
|
1424
|
-
|
|
1425
|
-
|
|
2003
|
+
parts.push(
|
|
2004
|
+
`<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="14" fill="${COLORS.nodeFill}" stroke="${COLORS.amberLine}" stroke-width="1.5" filter="url(#nodeShadow)"/>`
|
|
2005
|
+
);
|
|
2006
|
+
parts.push(
|
|
2007
|
+
`<defs><clipPath id="${clipId}"><rect x="${x}" y="${y}" width="${w}" height="${Q_BASE_H}" rx="14"/></clipPath></defs>`
|
|
2008
|
+
);
|
|
2009
|
+
parts.push(
|
|
2010
|
+
`<rect x="${x}" y="${y}" width="${w}" height="${Q_BASE_H}" fill="${COLORS.amberSoft}" clip-path="url(#${clipId})"/>`
|
|
2011
|
+
);
|
|
2012
|
+
parts.push(
|
|
2013
|
+
`<rect x="${x}" y="${y}" width="4" height="${Q_BASE_H}" rx="2" fill="${COLORS.amber}"/>`
|
|
2014
|
+
);
|
|
2015
|
+
parts.push(
|
|
2016
|
+
`<rect x="${x + 12}" y="${y + 14}" width="28" height="28" rx="8" fill="${COLORS.amber}"/>`
|
|
2017
|
+
);
|
|
2018
|
+
parts.push(
|
|
2019
|
+
`<text x="${x + 26}" y="${y + 33}" text-anchor="middle" font-size="15" font-weight="900" fill="white">?</text>`
|
|
2020
|
+
);
|
|
2021
|
+
parts.push(
|
|
2022
|
+
`<text x="${x + 50}" y="${y + 27}" font-family="ui-sans-serif,system-ui,sans-serif" font-size="9" font-weight="700" fill="${COLORS.textSub}" letter-spacing="0.6">QUESTION</text>`
|
|
2023
|
+
);
|
|
2024
|
+
parts.push(
|
|
2025
|
+
`<text x="${x + 50}" y="${y + 42}" font-family="ui-sans-serif,system-ui,sans-serif" font-size="13" font-weight="700" fill="${COLORS.text}">${sanitizeForSVG(node.label)}</text>`
|
|
2026
|
+
);
|
|
2027
|
+
parts.push(
|
|
2028
|
+
`<line x1="${x}" y1="${y + Q_BASE_H}" x2="${x + w}" y2="${y + Q_BASE_H}" stroke="${COLORS.amberLine}" stroke-width="1"/>`
|
|
2029
|
+
);
|
|
1426
2030
|
if (answers.length === 0) {
|
|
1427
|
-
parts.push(
|
|
2031
|
+
parts.push(
|
|
2032
|
+
`<text x="${x + w / 2}" y="${y + Q_BASE_H + 22}" text-anchor="middle" font-size="10" fill="${COLORS.amber}" opacity="0.4" font-weight="600">No answers yet</text>`
|
|
2033
|
+
);
|
|
1428
2034
|
} else {
|
|
1429
2035
|
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
1430
2036
|
answers.forEach((ans, i) => {
|
|
@@ -1437,10 +2043,18 @@ function renderQuestionNode(node, box) {
|
|
|
1437
2043
|
const letter = i < 26 ? letters[i] : `${i + 1}`;
|
|
1438
2044
|
const maxChars = Math.max(2, Math.floor((cW - 20) / 7.5));
|
|
1439
2045
|
const displayAns = ans.length > maxChars ? ans.slice(0, maxChars - 1) + "\u2026" : ans;
|
|
1440
|
-
parts.push(
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
parts.push(
|
|
2046
|
+
parts.push(
|
|
2047
|
+
`<rect x="${cardX}" y="${cardY}" width="${cW}" height="${cardH}" rx="8" fill="${COLORS.amberCardBg}" stroke="${COLORS.amberLine}" stroke-width="1"/>`
|
|
2048
|
+
);
|
|
2049
|
+
parts.push(
|
|
2050
|
+
`<rect x="${cx - 11}" y="${cardY + 7}" width="22" height="22" rx="6" fill="#fef3c7"/>`
|
|
2051
|
+
);
|
|
2052
|
+
parts.push(
|
|
2053
|
+
`<text x="${cx}" y="${cardY + 22}" text-anchor="middle" font-size="10" font-weight="800" fill="${COLORS.amber}">${sanitizeForSVG(letter)}</text>`
|
|
2054
|
+
);
|
|
2055
|
+
parts.push(
|
|
2056
|
+
`<text x="${cx}" y="${cardY + 46}" text-anchor="middle" font-size="11" font-weight="500" fill="#374151" font-family="ui-sans-serif,system-ui,sans-serif">${sanitizeForSVG(displayAns)}</text>`
|
|
2057
|
+
);
|
|
1444
2058
|
});
|
|
1445
2059
|
}
|
|
1446
2060
|
return parts.join("");
|
|
@@ -1478,7 +2092,7 @@ function renderEdge(edge, boxes, variant, nodes) {
|
|
|
1478
2092
|
const midY = (y1 + y2) / 2;
|
|
1479
2093
|
const labelW = estimateTextW(edge.label, 7) + 14;
|
|
1480
2094
|
out += `<rect x="${midX - labelW / 2}" y="${midY - 11}" width="${labelW}" height="18" rx="9" fill="${COLORS.bg}" stroke="${COLORS.nodeStroke}" stroke-width="1"/>`;
|
|
1481
|
-
out += `<text x="${midX}" y="${midY + 2}" text-anchor="middle" font-family="ui-sans-serif,system-ui,sans-serif" font-size="11" fill="${COLORS.text}">${
|
|
2095
|
+
out += `<text x="${midX}" y="${midY + 2}" text-anchor="middle" font-family="ui-sans-serif,system-ui,sans-serif" font-size="11" fill="${COLORS.text}">${sanitizeForSVG(edge.label)}</text>`;
|
|
1482
2096
|
}
|
|
1483
2097
|
return out;
|
|
1484
2098
|
}
|
|
@@ -1504,7 +2118,7 @@ function toSVG(model) {
|
|
|
1504
2118
|
`</marker>`,
|
|
1505
2119
|
`</defs>`
|
|
1506
2120
|
].join("");
|
|
1507
|
-
const titleEl = model.title ? `<text x="${width / 2}" y="22" text-anchor="middle" font-family="ui-sans-serif,system-ui,sans-serif" font-size="15" font-weight="700" fill="${COLORS.text}">${
|
|
2121
|
+
const titleEl = model.title ? `<text x="${width / 2}" y="22" text-anchor="middle" font-family="ui-sans-serif,system-ui,sans-serif" font-size="15" font-weight="700" fill="${COLORS.text}">${sanitizeForSVG(model.title)}</text>` : "";
|
|
1508
2122
|
const edges = model.edges.map((e) => renderEdge(e, boxes, model.variant, model.nodes)).join("\n");
|
|
1509
2123
|
const nodes = model.nodes.map((n) => {
|
|
1510
2124
|
const b = boxes.get(n.id);
|
|
@@ -1521,7 +2135,9 @@ ${nodes}
|
|
|
1521
2135
|
}
|
|
1522
2136
|
async function toPNG(model) {
|
|
1523
2137
|
if (typeof document === "undefined") {
|
|
1524
|
-
throw new Error(
|
|
2138
|
+
throw new Error(
|
|
2139
|
+
"toPNG requires a browser environment. For Node/Bun server use, pipe toSVG() through @resvg/resvg-js."
|
|
2140
|
+
);
|
|
1525
2141
|
}
|
|
1526
2142
|
const svg = toSVG(model);
|
|
1527
2143
|
const blob = new Blob([svg], { type: "image/svg+xml" });
|
|
@@ -1537,7 +2153,10 @@ async function toPNG(model) {
|
|
|
1537
2153
|
ctx.scale(scale, scale);
|
|
1538
2154
|
ctx.drawImage(img, 0, 0);
|
|
1539
2155
|
URL.revokeObjectURL(url);
|
|
1540
|
-
canvas.toBlob(
|
|
2156
|
+
canvas.toBlob(
|
|
2157
|
+
(b) => b ? resolve(b) : reject(new Error("Canvas toBlob failed")),
|
|
2158
|
+
"image/png"
|
|
2159
|
+
);
|
|
1541
2160
|
};
|
|
1542
2161
|
img.onerror = () => {
|
|
1543
2162
|
URL.revokeObjectURL(url);
|
|
@@ -1549,40 +2168,43 @@ async function toPNG(model) {
|
|
|
1549
2168
|
|
|
1550
2169
|
// src/ui/hooks/useExporters.ts
|
|
1551
2170
|
function useExporters(model, onExport, filename = "diagram", onSuccess) {
|
|
1552
|
-
return useCallback2(
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
2171
|
+
return useCallback2(
|
|
2172
|
+
async (format) => {
|
|
2173
|
+
let content;
|
|
2174
|
+
switch (format) {
|
|
2175
|
+
case "mermaid":
|
|
2176
|
+
content = toMermaid(model);
|
|
2177
|
+
break;
|
|
2178
|
+
case "plantuml":
|
|
2179
|
+
content = toPlantUML(model);
|
|
2180
|
+
break;
|
|
2181
|
+
case "json":
|
|
2182
|
+
content = toJSON(model);
|
|
2183
|
+
break;
|
|
2184
|
+
case "svg":
|
|
2185
|
+
content = toSVG(model);
|
|
2186
|
+
break;
|
|
2187
|
+
case "png":
|
|
2188
|
+
content = await toPNG(model);
|
|
2189
|
+
break;
|
|
2190
|
+
default:
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
if (onExport) {
|
|
2194
|
+
onExport(format, content);
|
|
2195
|
+
onSuccess?.(`Exported as ${format.toUpperCase()}`);
|
|
1571
2196
|
return;
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
URL.revokeObjectURL(url);
|
|
1584
|
-
onSuccess?.(`Downloaded ${a.download}`);
|
|
1585
|
-
}, [model, onExport, filename, onSuccess]);
|
|
2197
|
+
}
|
|
2198
|
+
const url = content instanceof Blob ? URL.createObjectURL(content) : URL.createObjectURL(new Blob([content], { type: "text/plain" }));
|
|
2199
|
+
const a = document.createElement("a");
|
|
2200
|
+
a.href = url;
|
|
2201
|
+
a.download = `${filename}.${format === "plantuml" ? "puml" : format}`;
|
|
2202
|
+
a.click();
|
|
2203
|
+
URL.revokeObjectURL(url);
|
|
2204
|
+
onSuccess?.(`Downloaded ${a.download}`);
|
|
2205
|
+
},
|
|
2206
|
+
[model, onExport, filename, onSuccess]
|
|
2207
|
+
);
|
|
1586
2208
|
}
|
|
1587
2209
|
|
|
1588
2210
|
// src/ui/hooks/useImporter.ts
|
|
@@ -1599,7 +2221,15 @@ var Model = class _Model {
|
|
|
1599
2221
|
* @param variant Optional UI variant (flowchart models only).
|
|
1600
2222
|
*/
|
|
1601
2223
|
constructor(type, title, variant) {
|
|
1602
|
-
this.data = {
|
|
2224
|
+
this.data = {
|
|
2225
|
+
type,
|
|
2226
|
+
...variant ? { variant } : {},
|
|
2227
|
+
title,
|
|
2228
|
+
nodes: [],
|
|
2229
|
+
edges: [],
|
|
2230
|
+
actors: [],
|
|
2231
|
+
messages: []
|
|
2232
|
+
};
|
|
1603
2233
|
}
|
|
1604
2234
|
/**
|
|
1605
2235
|
* Rehydrate a `Model` from a previously serialized `DiagramModel`. The
|
|
@@ -1635,7 +2265,8 @@ var Model = class _Model {
|
|
|
1635
2265
|
updateNode(id, patch) {
|
|
1636
2266
|
const node = this.data.nodes.find((n) => n.id === id);
|
|
1637
2267
|
if (!node) throw new Error(`Node "${id}" not found`);
|
|
1638
|
-
|
|
2268
|
+
const { __proto__, constructor, ...safe } = patch;
|
|
2269
|
+
Object.assign(node, safe);
|
|
1639
2270
|
return this;
|
|
1640
2271
|
}
|
|
1641
2272
|
/**
|
|
@@ -1675,15 +2306,35 @@ var Model = class _Model {
|
|
|
1675
2306
|
const errors = [];
|
|
1676
2307
|
const nodeIds = /* @__PURE__ */ new Set();
|
|
1677
2308
|
for (const n of this.data.nodes) {
|
|
1678
|
-
if (nodeIds.has(n.id))
|
|
2309
|
+
if (nodeIds.has(n.id))
|
|
2310
|
+
errors.push({
|
|
2311
|
+
kind: "duplicate-node-id",
|
|
2312
|
+
id: n.id,
|
|
2313
|
+
message: `Duplicate node id "${n.id}"`
|
|
2314
|
+
});
|
|
1679
2315
|
nodeIds.add(n.id);
|
|
1680
2316
|
}
|
|
1681
2317
|
const edgeIds = /* @__PURE__ */ new Set();
|
|
1682
2318
|
for (const e of this.data.edges) {
|
|
1683
|
-
if (edgeIds.has(e.id))
|
|
2319
|
+
if (edgeIds.has(e.id))
|
|
2320
|
+
errors.push({
|
|
2321
|
+
kind: "duplicate-edge-id",
|
|
2322
|
+
id: e.id,
|
|
2323
|
+
message: `Duplicate edge id "${e.id}"`
|
|
2324
|
+
});
|
|
1684
2325
|
edgeIds.add(e.id);
|
|
1685
|
-
if (!nodeIds.has(e.from))
|
|
1686
|
-
|
|
2326
|
+
if (!nodeIds.has(e.from))
|
|
2327
|
+
errors.push({
|
|
2328
|
+
kind: "dangling-from",
|
|
2329
|
+
id: e.id,
|
|
2330
|
+
message: `Edge "${e.id}" references unknown source node "${e.from}"`
|
|
2331
|
+
});
|
|
2332
|
+
if (!nodeIds.has(e.to))
|
|
2333
|
+
errors.push({
|
|
2334
|
+
kind: "dangling-to",
|
|
2335
|
+
id: e.id,
|
|
2336
|
+
message: `Edge "${e.id}" references unknown target node "${e.to}"`
|
|
2337
|
+
});
|
|
1687
2338
|
}
|
|
1688
2339
|
return errors;
|
|
1689
2340
|
}
|
|
@@ -1717,6 +2368,25 @@ var Model = class _Model {
|
|
|
1717
2368
|
}
|
|
1718
2369
|
};
|
|
1719
2370
|
|
|
2371
|
+
// src/core/sanitize.ts
|
|
2372
|
+
var MAX_LABEL_LENGTH = 2e3;
|
|
2373
|
+
var MAX_NODES = 500;
|
|
2374
|
+
var MAX_EDGES = 2e3;
|
|
2375
|
+
var MAX_ACTORS = 100;
|
|
2376
|
+
var MAX_MESSAGES = 2e3;
|
|
2377
|
+
var MAX_IMPORT_LENGTH = 2 * 1024 * 1024;
|
|
2378
|
+
function sanitizeLabel(raw) {
|
|
2379
|
+
let s2 = raw;
|
|
2380
|
+
s2 = s2.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
|
|
2381
|
+
s2 = s2.replace(/<\/?[a-zA-Z][^>]*>/g, "");
|
|
2382
|
+
s2 = s2.replace(/\b(?:javascript|data|vbscript)\s*:/gi, "");
|
|
2383
|
+
s2 = s2.replace(/\bon[a-z]+\s*=/gi, "");
|
|
2384
|
+
if (s2.length > MAX_LABEL_LENGTH) {
|
|
2385
|
+
s2 = s2.slice(0, MAX_LABEL_LENGTH);
|
|
2386
|
+
}
|
|
2387
|
+
return s2;
|
|
2388
|
+
}
|
|
2389
|
+
|
|
1720
2390
|
// src/importers/mermaid.ts
|
|
1721
2391
|
function parseNodeDecl(raw) {
|
|
1722
2392
|
const patterns = [
|
|
@@ -1728,7 +2398,7 @@ function parseNodeDecl(raw) {
|
|
|
1728
2398
|
];
|
|
1729
2399
|
for (const [re, shape] of patterns) {
|
|
1730
2400
|
const m = raw.match(re);
|
|
1731
|
-
if (m) return { id: m[1], label: m[2].replace(/^["']|["']$/g, ""), shape };
|
|
2401
|
+
if (m) return { id: m[1], label: sanitizeLabel(m[2].replace(/^["']|["']$/g, "")), shape };
|
|
1732
2402
|
}
|
|
1733
2403
|
return null;
|
|
1734
2404
|
}
|
|
@@ -1743,8 +2413,11 @@ function parseFlowchart(lines) {
|
|
|
1743
2413
|
const model = new Model("flowchart");
|
|
1744
2414
|
const nodeMap = /* @__PURE__ */ new Map();
|
|
1745
2415
|
const groupStack = [];
|
|
2416
|
+
let edgeCount = 0;
|
|
1746
2417
|
const ensureNode = (id, group) => {
|
|
1747
2418
|
if (!nodeMap.has(id)) {
|
|
2419
|
+
if (nodeMap.size >= MAX_NODES)
|
|
2420
|
+
throw new Error(`Import aborted: diagram exceeds the maximum of ${MAX_NODES} nodes`);
|
|
1748
2421
|
nodeMap.set(id, true);
|
|
1749
2422
|
const metadata = group ? { group } : void 0;
|
|
1750
2423
|
model.addNode({ id, label: id, shape: "rectangle", ...metadata ? { metadata } : {} });
|
|
@@ -1753,7 +2426,8 @@ function parseFlowchart(lines) {
|
|
|
1753
2426
|
for (const line of lines) {
|
|
1754
2427
|
const trimmed = line.trim();
|
|
1755
2428
|
if (!trimmed) continue;
|
|
1756
|
-
if (trimmed.startsWith("%%") || trimmed.startsWith("graph") || trimmed.startsWith("flowchart") || trimmed.startsWith("click ") || trimmed.startsWith("classDef ") || trimmed.startsWith("class ") || trimmed.startsWith("style ") || trimmed.startsWith("linkStyle "))
|
|
2429
|
+
if (trimmed.startsWith("%%") || trimmed.startsWith("graph") || trimmed.startsWith("flowchart") || trimmed.startsWith("click ") || trimmed.startsWith("classDef ") || trimmed.startsWith("class ") || trimmed.startsWith("style ") || trimmed.startsWith("linkStyle "))
|
|
2430
|
+
continue;
|
|
1757
2431
|
const subgraphOpen = trimmed.match(/^subgraph\s+(\S+)/i);
|
|
1758
2432
|
if (subgraphOpen) {
|
|
1759
2433
|
groupStack.push(subgraphOpen[1]);
|
|
@@ -1769,12 +2443,15 @@ function parseFlowchart(lines) {
|
|
|
1769
2443
|
const fromRaw = edgeMatch[1].trim();
|
|
1770
2444
|
const connector = edgeMatch[2];
|
|
1771
2445
|
const label = edgeMatch[3]?.replace(/^["']|["']$/g, "");
|
|
2446
|
+
const sanitizedLabel = label ? sanitizeLabel(label) : void 0;
|
|
1772
2447
|
const toRaw = edgeMatch[4].trim();
|
|
1773
2448
|
const style = detectStyle(connector);
|
|
1774
2449
|
const arrowhead = detectArrowhead(connector);
|
|
1775
2450
|
const fromNode = parseNodeDecl(fromRaw);
|
|
1776
2451
|
const toNode = parseNodeDecl(toRaw);
|
|
1777
2452
|
if (fromNode && !nodeMap.has(fromNode.id)) {
|
|
2453
|
+
if (nodeMap.size >= MAX_NODES)
|
|
2454
|
+
throw new Error(`Import aborted: diagram exceeds the maximum of ${MAX_NODES} nodes`);
|
|
1778
2455
|
nodeMap.set(fromNode.id, true);
|
|
1779
2456
|
const metadata = currentGroup ? { group: currentGroup } : void 0;
|
|
1780
2457
|
model.addNode({ ...fromNode, ...metadata ? { metadata } : {} });
|
|
@@ -1782,19 +2459,24 @@ function parseFlowchart(lines) {
|
|
|
1782
2459
|
ensureNode(fromRaw.replace(/\W.*/, ""), currentGroup);
|
|
1783
2460
|
}
|
|
1784
2461
|
if (toNode && !nodeMap.has(toNode.id)) {
|
|
2462
|
+
if (nodeMap.size >= MAX_NODES)
|
|
2463
|
+
throw new Error(`Import aborted: diagram exceeds the maximum of ${MAX_NODES} nodes`);
|
|
1785
2464
|
nodeMap.set(toNode.id, true);
|
|
1786
2465
|
const metadata = currentGroup ? { group: currentGroup } : void 0;
|
|
1787
2466
|
model.addNode({ ...toNode, ...metadata ? { metadata } : {} });
|
|
1788
2467
|
} else if (!toNode) {
|
|
1789
2468
|
ensureNode(toRaw.replace(/\W.*/, ""), currentGroup);
|
|
1790
2469
|
}
|
|
2470
|
+
if (edgeCount >= MAX_EDGES)
|
|
2471
|
+
throw new Error(`Import aborted: diagram exceeds the maximum of ${MAX_EDGES} edges`);
|
|
2472
|
+
edgeCount++;
|
|
1791
2473
|
const fromId = fromNode?.id ?? fromRaw.replace(/\W.*/, "");
|
|
1792
2474
|
const toId = toNode?.id ?? toRaw.replace(/\W.*/, "");
|
|
1793
2475
|
model.addEdge({
|
|
1794
2476
|
id: nextId("e", model.toJSON().edges),
|
|
1795
2477
|
from: fromId,
|
|
1796
2478
|
to: toId,
|
|
1797
|
-
...
|
|
2479
|
+
...sanitizedLabel ? { label: sanitizedLabel } : {},
|
|
1798
2480
|
style,
|
|
1799
2481
|
...arrowhead === "none" ? { arrowhead } : {}
|
|
1800
2482
|
});
|
|
@@ -1802,6 +2484,8 @@ function parseFlowchart(lines) {
|
|
|
1802
2484
|
}
|
|
1803
2485
|
const nodeDecl = parseNodeDecl(trimmed);
|
|
1804
2486
|
if (nodeDecl && !nodeMap.has(nodeDecl.id)) {
|
|
2487
|
+
if (nodeMap.size >= MAX_NODES)
|
|
2488
|
+
throw new Error(`Import aborted: diagram exceeds the maximum of ${MAX_NODES} nodes`);
|
|
1805
2489
|
nodeMap.set(nodeDecl.id, true);
|
|
1806
2490
|
const metadata = currentGroup ? { group: currentGroup } : void 0;
|
|
1807
2491
|
model.addNode({ ...nodeDecl, ...metadata ? { metadata } : {} });
|
|
@@ -1811,34 +2495,56 @@ function parseFlowchart(lines) {
|
|
|
1811
2495
|
}
|
|
1812
2496
|
function parseSequence(lines, title) {
|
|
1813
2497
|
const model = new Model("sequence", title);
|
|
2498
|
+
let actorCount = 0;
|
|
2499
|
+
let messageCount = 0;
|
|
2500
|
+
const safeAddActor = (name) => {
|
|
2501
|
+
const safeName = sanitizeLabel(name);
|
|
2502
|
+
if (!model.toJSON().actors?.includes(safeName)) {
|
|
2503
|
+
if (actorCount >= MAX_ACTORS)
|
|
2504
|
+
throw new Error(`Import aborted: diagram exceeds the maximum of ${MAX_ACTORS} actors`);
|
|
2505
|
+
actorCount++;
|
|
2506
|
+
}
|
|
2507
|
+
model.addActor(safeName);
|
|
2508
|
+
return safeName;
|
|
2509
|
+
};
|
|
1814
2510
|
for (const line of lines) {
|
|
1815
2511
|
const trimmed = line.trim();
|
|
1816
2512
|
if (!trimmed || trimmed.startsWith("sequenceDiagram") || trimmed.startsWith("%%")) continue;
|
|
1817
2513
|
const participantMatch = trimmed.match(/^participant\s+(.+)$/i);
|
|
1818
2514
|
if (participantMatch) {
|
|
1819
|
-
|
|
2515
|
+
safeAddActor(participantMatch[1].trim());
|
|
1820
2516
|
continue;
|
|
1821
2517
|
}
|
|
1822
2518
|
const actorMatch = trimmed.match(/^actor\s+(.+)$/i);
|
|
1823
2519
|
if (actorMatch) {
|
|
1824
|
-
|
|
2520
|
+
safeAddActor(actorMatch[1].trim());
|
|
1825
2521
|
continue;
|
|
1826
2522
|
}
|
|
1827
2523
|
const msgMatch = trimmed.match(/^(.+?)\s*(-->>|->>|-->|->)\s*(.+?):\s*(.+)$/);
|
|
1828
2524
|
if (msgMatch) {
|
|
1829
|
-
const from = msgMatch[1].trim();
|
|
2525
|
+
const from = safeAddActor(msgMatch[1].trim());
|
|
1830
2526
|
const arrow = msgMatch[2];
|
|
1831
|
-
const to = msgMatch[3].trim();
|
|
1832
|
-
const label = msgMatch[4].trim();
|
|
1833
|
-
|
|
1834
|
-
|
|
2527
|
+
const to = safeAddActor(msgMatch[3].trim());
|
|
2528
|
+
const label = sanitizeLabel(msgMatch[4].trim());
|
|
2529
|
+
if (messageCount >= MAX_MESSAGES)
|
|
2530
|
+
throw new Error(`Import aborted: diagram exceeds the maximum of ${MAX_MESSAGES} messages`);
|
|
2531
|
+
messageCount++;
|
|
1835
2532
|
const messages = model.toJSON().messages ?? [];
|
|
1836
|
-
model.addMessage({
|
|
2533
|
+
model.addMessage({
|
|
2534
|
+
id: nextId("m", messages),
|
|
2535
|
+
from,
|
|
2536
|
+
to,
|
|
2537
|
+
label,
|
|
2538
|
+
style: arrow.startsWith("--") ? "dashed" : "solid"
|
|
2539
|
+
});
|
|
1837
2540
|
}
|
|
1838
2541
|
}
|
|
1839
2542
|
return model;
|
|
1840
2543
|
}
|
|
1841
2544
|
function fromMermaid(mermaid) {
|
|
2545
|
+
if (mermaid.length > MAX_IMPORT_LENGTH) {
|
|
2546
|
+
throw new Error(`Import aborted: input exceeds the maximum of ${MAX_IMPORT_LENGTH} characters`);
|
|
2547
|
+
}
|
|
1842
2548
|
const cleaned = mermaid.replace(/mermaid\.initialize\([\s\S]*?\)\s*;?/g, "");
|
|
1843
2549
|
const rawLines = cleaned.split("\n");
|
|
1844
2550
|
let startIdx = 0;
|
|
@@ -1870,10 +2576,74 @@ function fromMermaid(mermaid) {
|
|
|
1870
2576
|
}
|
|
1871
2577
|
|
|
1872
2578
|
// src/importers/json.ts
|
|
2579
|
+
function stripDangerousKeys(obj) {
|
|
2580
|
+
if (Array.isArray(obj)) return obj.map(stripDangerousKeys);
|
|
2581
|
+
if (obj !== null && typeof obj === "object") {
|
|
2582
|
+
const clean = {};
|
|
2583
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
2584
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
|
|
2585
|
+
clean[key] = stripDangerousKeys(val);
|
|
2586
|
+
}
|
|
2587
|
+
return clean;
|
|
2588
|
+
}
|
|
2589
|
+
return obj;
|
|
2590
|
+
}
|
|
1873
2591
|
function fromJSON(json) {
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
2592
|
+
if (typeof json === "string" && json.length > MAX_IMPORT_LENGTH) {
|
|
2593
|
+
throw new Error(`Import aborted: input exceeds the maximum of ${MAX_IMPORT_LENGTH} characters`);
|
|
2594
|
+
}
|
|
2595
|
+
const raw = typeof json === "string" ? JSON.parse(json) : json;
|
|
2596
|
+
const data = stripDangerousKeys(raw);
|
|
2597
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
2598
|
+
throw new Error("Invalid DiagramModel JSON: expected an object");
|
|
2599
|
+
}
|
|
2600
|
+
if (data.type !== "flowchart" && data.type !== "sequence") {
|
|
2601
|
+
throw new Error(`Invalid DiagramModel JSON: unknown type "${data.type}"`);
|
|
2602
|
+
}
|
|
2603
|
+
if (!Array.isArray(data.nodes) || !Array.isArray(data.edges)) {
|
|
2604
|
+
throw new Error("Invalid DiagramModel JSON: nodes and edges must be arrays");
|
|
2605
|
+
}
|
|
2606
|
+
if (data.nodes.length > MAX_NODES) {
|
|
2607
|
+
throw new Error(
|
|
2608
|
+
`Import aborted: diagram has ${data.nodes.length} nodes, maximum is ${MAX_NODES}`
|
|
2609
|
+
);
|
|
2610
|
+
}
|
|
2611
|
+
if (data.edges.length > MAX_EDGES) {
|
|
2612
|
+
throw new Error(
|
|
2613
|
+
`Import aborted: diagram has ${data.edges.length} edges, maximum is ${MAX_EDGES}`
|
|
2614
|
+
);
|
|
2615
|
+
}
|
|
2616
|
+
if (data.actors && data.actors.length > MAX_ACTORS) {
|
|
2617
|
+
throw new Error(
|
|
2618
|
+
`Import aborted: diagram has ${data.actors.length} actors, maximum is ${MAX_ACTORS}`
|
|
2619
|
+
);
|
|
2620
|
+
}
|
|
2621
|
+
if (data.messages && data.messages.length > MAX_MESSAGES) {
|
|
2622
|
+
throw new Error(
|
|
2623
|
+
`Import aborted: diagram has ${data.messages.length} messages, maximum is ${MAX_MESSAGES}`
|
|
2624
|
+
);
|
|
2625
|
+
}
|
|
2626
|
+
for (const node of data.nodes) {
|
|
2627
|
+
if (typeof node !== "object" || node === null || typeof node.id !== "string" || typeof node.label !== "string") {
|
|
2628
|
+
throw new Error("Invalid DiagramModel JSON: each node must have string id and label");
|
|
2629
|
+
}
|
|
2630
|
+
node.label = sanitizeLabel(node.label);
|
|
2631
|
+
}
|
|
2632
|
+
for (const edge of data.edges) {
|
|
2633
|
+
if (typeof edge !== "object" || edge === null || typeof edge.id !== "string" || typeof edge.from !== "string" || typeof edge.to !== "string") {
|
|
2634
|
+
throw new Error("Invalid DiagramModel JSON: each edge must have string id, from, and to");
|
|
2635
|
+
}
|
|
2636
|
+
if (edge.label) edge.label = sanitizeLabel(edge.label);
|
|
2637
|
+
}
|
|
2638
|
+
if (data.actors) {
|
|
2639
|
+
data.actors = data.actors.map((a) => typeof a === "string" ? sanitizeLabel(a) : a);
|
|
2640
|
+
}
|
|
2641
|
+
if (data.messages) {
|
|
2642
|
+
for (const msg of data.messages) {
|
|
2643
|
+
if (typeof msg === "object" && msg !== null && typeof msg.label === "string") {
|
|
2644
|
+
msg.label = sanitizeLabel(msg.label);
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
1877
2647
|
}
|
|
1878
2648
|
return Model.fromData(data);
|
|
1879
2649
|
}
|
|
@@ -1882,19 +2652,22 @@ function fromJSON(json) {
|
|
|
1882
2652
|
function useImporter(applyAndPush, options = {}) {
|
|
1883
2653
|
const { expectedType, transform, onSuccess, onError } = options;
|
|
1884
2654
|
const reportError = onError ?? ((msg) => alert(msg));
|
|
1885
|
-
return useCallback3(
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
2655
|
+
return useCallback3(
|
|
2656
|
+
(text) => {
|
|
2657
|
+
try {
|
|
2658
|
+
const parsed = text.trim().startsWith("{") ? fromJSON(text).toJSON() : fromMermaid(text).toJSON();
|
|
2659
|
+
if (expectedType && parsed.type !== expectedType) {
|
|
2660
|
+
reportError(`Imported diagram is not a ${expectedType} diagram.`);
|
|
2661
|
+
return;
|
|
2662
|
+
}
|
|
2663
|
+
applyAndPush(transform ? transform(parsed) : parsed);
|
|
2664
|
+
onSuccess?.("Diagram imported successfully");
|
|
2665
|
+
} catch (err) {
|
|
2666
|
+
reportError(`Import failed: ${err.message}`);
|
|
1891
2667
|
}
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
reportError(`Import failed: ${err.message}`);
|
|
1896
|
-
}
|
|
1897
|
-
}, [applyAndPush, expectedType, transform, onSuccess, onError]);
|
|
2668
|
+
},
|
|
2669
|
+
[applyAndPush, expectedType, transform, onSuccess, onError]
|
|
2670
|
+
);
|
|
1898
2671
|
}
|
|
1899
2672
|
|
|
1900
2673
|
// src/ui/hooks/useToast.ts
|
|
@@ -1932,7 +2705,10 @@ var containerStyle = {
|
|
|
1932
2705
|
zIndex: 9999,
|
|
1933
2706
|
pointerEvents: "none"
|
|
1934
2707
|
};
|
|
1935
|
-
function ToastContainer({
|
|
2708
|
+
function ToastContainer({
|
|
2709
|
+
toasts,
|
|
2710
|
+
onDismiss
|
|
2711
|
+
}) {
|
|
1936
2712
|
if (toasts.length === 0) return null;
|
|
1937
2713
|
return /* @__PURE__ */ jsx5("div", { style: containerStyle, children: toasts.map((t) => {
|
|
1938
2714
|
const c = TOAST_COLORS[t.type];
|
|
@@ -2163,7 +2939,10 @@ function SequenceEditor({
|
|
|
2163
2939
|
const historyRef = useRef3([ensureSequenceModel(initialModel)]);
|
|
2164
2940
|
const historyIdxRef = useRef3(0);
|
|
2165
2941
|
const svgRef = useRef3(null);
|
|
2166
|
-
const { t, isDark } = useEditorTheme(theme, themeOverrides, {
|
|
2942
|
+
const { t, isDark } = useEditorTheme(theme, themeOverrides, {
|
|
2943
|
+
light: lightTheme2,
|
|
2944
|
+
dark: darkTheme2
|
|
2945
|
+
});
|
|
2167
2946
|
const actors = model.actors ?? [];
|
|
2168
2947
|
const messages = model.messages ?? [];
|
|
2169
2948
|
const colW = useMemo3(() => {
|
|
@@ -2185,11 +2964,14 @@ function SequenceEditor({
|
|
|
2185
2964
|
historyRef.current = stack;
|
|
2186
2965
|
historyIdxRef.current = stack.length - 1;
|
|
2187
2966
|
}, []);
|
|
2188
|
-
const applyAndPush = useCallback5(
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2967
|
+
const applyAndPush = useCallback5(
|
|
2968
|
+
(m) => {
|
|
2969
|
+
setModel(m);
|
|
2970
|
+
onChange?.(m);
|
|
2971
|
+
pushHistory(m);
|
|
2972
|
+
},
|
|
2973
|
+
[onChange, pushHistory]
|
|
2974
|
+
);
|
|
2193
2975
|
const undo = useCallback5(() => {
|
|
2194
2976
|
if (historyIdxRef.current <= 0) return;
|
|
2195
2977
|
historyIdxRef.current--;
|
|
@@ -2234,7 +3016,10 @@ function SequenceEditor({
|
|
|
2234
3016
|
applyAndPush({
|
|
2235
3017
|
...model,
|
|
2236
3018
|
actors: [...actors, a, b],
|
|
2237
|
-
messages: [
|
|
3019
|
+
messages: [
|
|
3020
|
+
...messages,
|
|
3021
|
+
{ id: nextId("m", messages), from: a, to: b, label: "message", style: "solid" }
|
|
3022
|
+
]
|
|
2238
3023
|
});
|
|
2239
3024
|
return;
|
|
2240
3025
|
}
|
|
@@ -2242,7 +3027,10 @@ function SequenceEditor({
|
|
|
2242
3027
|
const to = actors[Math.min(1, actors.length - 1)] ?? from;
|
|
2243
3028
|
applyAndPush({
|
|
2244
3029
|
...model,
|
|
2245
|
-
messages: [
|
|
3030
|
+
messages: [
|
|
3031
|
+
...messages,
|
|
3032
|
+
{ id: nextId("m", messages), from, to, label: "message", style: "solid" }
|
|
3033
|
+
]
|
|
2246
3034
|
});
|
|
2247
3035
|
};
|
|
2248
3036
|
const updateMessage = (id, patch) => {
|
|
@@ -2255,37 +3043,57 @@ function SequenceEditor({
|
|
|
2255
3043
|
applyAndPush({ ...model, messages: messages.filter((m) => m.id !== id) });
|
|
2256
3044
|
if (selected === id) setSelected(null);
|
|
2257
3045
|
};
|
|
2258
|
-
const reorderMessage = useCallback5(
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
3046
|
+
const reorderMessage = useCallback5(
|
|
3047
|
+
(id, toIdx) => {
|
|
3048
|
+
const fromIdx = messages.findIndex((m) => m.id === id);
|
|
3049
|
+
if (fromIdx < 0 || toIdx === fromIdx) return;
|
|
3050
|
+
const next = messages.slice();
|
|
3051
|
+
const [moved] = next.splice(fromIdx, 1);
|
|
3052
|
+
next.splice(toIdx, 0, moved);
|
|
3053
|
+
applyAndPush({ ...model, messages: next });
|
|
3054
|
+
},
|
|
3055
|
+
[messages, model, applyAndPush]
|
|
3056
|
+
);
|
|
2266
3057
|
const keyCommands = [
|
|
2267
|
-
{
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
3058
|
+
{
|
|
3059
|
+
match: (e) => (e.ctrlKey || e.metaKey) && e.key === "z",
|
|
3060
|
+
run: () => {
|
|
3061
|
+
undo();
|
|
3062
|
+
return true;
|
|
3063
|
+
}
|
|
3064
|
+
},
|
|
3065
|
+
{
|
|
3066
|
+
match: (e) => (e.ctrlKey || e.metaKey) && (e.key === "y" || e.shiftKey && e.key === "z"),
|
|
3067
|
+
run: () => {
|
|
3068
|
+
redo();
|
|
3069
|
+
return true;
|
|
3070
|
+
}
|
|
3071
|
+
},
|
|
3072
|
+
{
|
|
3073
|
+
match: (e) => e.key === "Escape",
|
|
3074
|
+
run: () => {
|
|
3075
|
+
setSelected(null);
|
|
3076
|
+
setEditingId(null);
|
|
3077
|
+
return true;
|
|
3078
|
+
}
|
|
3079
|
+
},
|
|
3080
|
+
{
|
|
3081
|
+
match: (e) => (e.key === "Delete" || e.key === "Backspace") && !!selected,
|
|
3082
|
+
run: () => {
|
|
3083
|
+
removeMessage(selected);
|
|
3084
|
+
return true;
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
];
|
|
3088
|
+
useEditorKeyboard(keyCommands, [undo, redo, selected]);
|
|
3089
|
+
const handleExport = useExporters(
|
|
3090
|
+
model,
|
|
3091
|
+
onExport,
|
|
3092
|
+
"sequence",
|
|
3093
|
+
(msg) => showToast(msg, "success")
|
|
3094
|
+
);
|
|
3095
|
+
const handleImport = useImporter(applyAndPush, {
|
|
3096
|
+
expectedType: "sequence",
|
|
2289
3097
|
transform: ensureSequenceModel,
|
|
2290
3098
|
onSuccess: (msg) => showToast(msg, "success"),
|
|
2291
3099
|
onError: (msg) => showToast(msg, "error")
|
|
@@ -2328,17 +3136,22 @@ function SequenceEditor({
|
|
|
2328
3136
|
};
|
|
2329
3137
|
}, [drag, messages.length, reorderMessage]);
|
|
2330
3138
|
const selectedMsg = selected ? messages.find((m) => m.id === selected) : null;
|
|
2331
|
-
return /* @__PURE__ */ jsxs6(
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
3139
|
+
return /* @__PURE__ */ jsxs6(
|
|
3140
|
+
"div",
|
|
3141
|
+
{
|
|
3142
|
+
className: "fsd-seq-editor",
|
|
3143
|
+
style: {
|
|
3144
|
+
display: "flex",
|
|
3145
|
+
flexDirection: "column",
|
|
3146
|
+
height,
|
|
3147
|
+
width: "100%",
|
|
3148
|
+
fontFamily: "ui-sans-serif,system-ui,sans-serif",
|
|
3149
|
+
background: t.ctrlsBg,
|
|
3150
|
+
position: "relative"
|
|
3151
|
+
},
|
|
3152
|
+
children: [
|
|
3153
|
+
/* @__PURE__ */ jsx6(ToastContainer, { toasts, onDismiss: dismissToast }),
|
|
3154
|
+
/* @__PURE__ */ jsx6("style", { children: `
|
|
2342
3155
|
.fsd-seq-editor [role="button"]:focus-visible {
|
|
2343
3156
|
outline: 2px solid ${t.actorText};
|
|
2344
3157
|
outline-offset: 2px;
|
|
@@ -2350,139 +3163,202 @@ function SequenceEditor({
|
|
|
2350
3163
|
border-radius: 4px;
|
|
2351
3164
|
}
|
|
2352
3165
|
` }),
|
|
2353
|
-
/* @__PURE__ */ jsx6(Toolbar, { onExport: handleExport, onImport: allowImport ? handleImport : void 0, allowedExports, allowImport }),
|
|
2354
|
-
/* @__PURE__ */ jsxs6("div", { style: {
|
|
2355
|
-
display: "flex",
|
|
2356
|
-
gap: 8,
|
|
2357
|
-
padding: "7px 14px",
|
|
2358
|
-
background: t.ctrlsBg,
|
|
2359
|
-
borderBottom: `1px solid ${t.ctrlsBorder}`,
|
|
2360
|
-
alignItems: "center",
|
|
2361
|
-
flexWrap: "wrap"
|
|
2362
|
-
}, children: [
|
|
2363
|
-
/* @__PURE__ */ jsx6("button", { onClick: addActor, style: primaryBtn(), children: "+ Actor" }),
|
|
2364
|
-
/* @__PURE__ */ jsx6("button", { onClick: addMessage, style: primaryBtn(), children: "+ Message" }),
|
|
2365
|
-
/* @__PURE__ */ jsx6("div", { style: { width: 1, height: 18, background: t.ctrlsBorder, margin: "0 4px" } }),
|
|
2366
|
-
/* @__PURE__ */ jsx6("button", { onClick: undo, style: ghostBtn2(t), title: "Undo (Ctrl+Z)", children: "\u21B6" }),
|
|
2367
|
-
/* @__PURE__ */ jsx6("button", { onClick: redo, style: ghostBtn2(t), title: "Redo (Ctrl+Y)", children: "\u21B7" }),
|
|
2368
|
-
/* @__PURE__ */ jsxs6("span", { style: { marginLeft: "auto", fontSize: 11, color: t.textMuted }, children: [
|
|
2369
|
-
actors.length,
|
|
2370
|
-
" actor",
|
|
2371
|
-
actors.length === 1 ? "" : "s",
|
|
2372
|
-
" \xB7 ",
|
|
2373
|
-
messages.length,
|
|
2374
|
-
" message",
|
|
2375
|
-
messages.length === 1 ? "" : "s",
|
|
2376
|
-
" \xB7 drag a row to reorder"
|
|
2377
|
-
] })
|
|
2378
|
-
] }),
|
|
2379
|
-
/* @__PURE__ */ jsxs6("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
|
|
2380
|
-
/* @__PURE__ */ jsx6("div", { style: { flex: 1, overflow: "auto", background: t.canvas, position: "relative" }, children: /* @__PURE__ */ jsx6(
|
|
2381
|
-
SequenceCanvas,
|
|
2382
|
-
{
|
|
2383
|
-
model,
|
|
2384
|
-
actors,
|
|
2385
|
-
messages,
|
|
2386
|
-
t,
|
|
2387
|
-
isDark,
|
|
2388
|
-
colW,
|
|
2389
|
-
totalW,
|
|
2390
|
-
totalH,
|
|
2391
|
-
actorX,
|
|
2392
|
-
msgY,
|
|
2393
|
-
selected,
|
|
2394
|
-
editingId,
|
|
2395
|
-
setEditingId,
|
|
2396
|
-
drag,
|
|
2397
|
-
onRowMouseDown,
|
|
2398
|
-
renameActor,
|
|
2399
|
-
removeActor,
|
|
2400
|
-
svgRef
|
|
2401
|
-
}
|
|
2402
|
-
) }),
|
|
2403
|
-
selectedMsg && /* @__PURE__ */ jsxs6("div", { style: {
|
|
2404
|
-
width: 280,
|
|
2405
|
-
maxWidth: "40vw",
|
|
2406
|
-
flexShrink: 0,
|
|
2407
|
-
background: t.panelBg,
|
|
2408
|
-
borderLeft: `1px solid ${t.panelBorder}`,
|
|
2409
|
-
padding: "14px 16px",
|
|
2410
|
-
overflowY: "auto"
|
|
2411
|
-
}, children: [
|
|
2412
|
-
/* @__PURE__ */ jsx6("div", { style: { fontSize: 10, fontWeight: 700, color: t.textMuted, textTransform: "uppercase", letterSpacing: 0.7, marginBottom: 10 }, children: "Message" }),
|
|
2413
|
-
/* @__PURE__ */ jsx6(Label, { t, children: "Label" }),
|
|
2414
3166
|
/* @__PURE__ */ jsx6(
|
|
2415
|
-
|
|
3167
|
+
Toolbar,
|
|
2416
3168
|
{
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
if (editLabel && editLabel !== selectedMsg.label) updateMessage(selectedMsg.id, { label: editLabel });
|
|
2422
|
-
setEditLabel("");
|
|
2423
|
-
},
|
|
2424
|
-
onKeyDown: (e) => {
|
|
2425
|
-
if (e.key === "Enter") e.target.blur();
|
|
2426
|
-
},
|
|
2427
|
-
style: input(t)
|
|
3169
|
+
onExport: handleExport,
|
|
3170
|
+
onImport: allowImport ? handleImport : void 0,
|
|
3171
|
+
allowedExports,
|
|
3172
|
+
allowImport
|
|
2428
3173
|
}
|
|
2429
3174
|
),
|
|
2430
|
-
/* @__PURE__ */
|
|
2431
|
-
|
|
2432
|
-
/* @__PURE__ */ jsx6(Label, { t, children: "To" }),
|
|
2433
|
-
/* @__PURE__ */ jsx6("select", { value: selectedMsg.to, onChange: (e) => updateMessage(selectedMsg.id, { to: e.target.value }), style: input(t), children: actors.map((a) => /* @__PURE__ */ jsx6("option", { value: a, children: a }, a)) }),
|
|
2434
|
-
/* @__PURE__ */ jsx6(Label, { t, children: "Style" }),
|
|
2435
|
-
/* @__PURE__ */ jsx6("div", { style: { display: "flex", gap: 6 }, children: ["solid", "dashed"].map((s2) => /* @__PURE__ */ jsx6(
|
|
2436
|
-
"button",
|
|
3175
|
+
/* @__PURE__ */ jsxs6(
|
|
3176
|
+
"div",
|
|
2437
3177
|
{
|
|
2438
|
-
onClick: () => updateMessage(selectedMsg.id, { style: s2 }),
|
|
2439
3178
|
style: {
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
background:
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
fontWeight: 600,
|
|
2448
|
-
cursor: "pointer",
|
|
2449
|
-
fontFamily: "inherit"
|
|
3179
|
+
display: "flex",
|
|
3180
|
+
gap: 8,
|
|
3181
|
+
padding: "7px 14px",
|
|
3182
|
+
background: t.ctrlsBg,
|
|
3183
|
+
borderBottom: `1px solid ${t.ctrlsBorder}`,
|
|
3184
|
+
alignItems: "center",
|
|
3185
|
+
flexWrap: "wrap"
|
|
2450
3186
|
},
|
|
2451
|
-
children:
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
3187
|
+
children: [
|
|
3188
|
+
/* @__PURE__ */ jsx6("button", { onClick: addActor, style: primaryBtn(), children: "+ Actor" }),
|
|
3189
|
+
/* @__PURE__ */ jsx6("button", { onClick: addMessage, style: primaryBtn(), children: "+ Message" }),
|
|
3190
|
+
/* @__PURE__ */ jsx6("div", { style: { width: 1, height: 18, background: t.ctrlsBorder, margin: "0 4px" } }),
|
|
3191
|
+
/* @__PURE__ */ jsx6("button", { onClick: undo, style: ghostBtn2(t), title: "Undo (Ctrl+Z)", children: "\u21B6" }),
|
|
3192
|
+
/* @__PURE__ */ jsx6("button", { onClick: redo, style: ghostBtn2(t), title: "Redo (Ctrl+Y)", children: "\u21B7" }),
|
|
3193
|
+
/* @__PURE__ */ jsxs6("span", { style: { marginLeft: "auto", fontSize: 11, color: t.textMuted }, children: [
|
|
3194
|
+
actors.length,
|
|
3195
|
+
" actor",
|
|
3196
|
+
actors.length === 1 ? "" : "s",
|
|
3197
|
+
" \xB7 ",
|
|
3198
|
+
messages.length,
|
|
3199
|
+
" message",
|
|
3200
|
+
messages.length === 1 ? "" : "s",
|
|
3201
|
+
" \xB7 drag a row to reorder"
|
|
3202
|
+
] })
|
|
3203
|
+
]
|
|
3204
|
+
}
|
|
3205
|
+
),
|
|
3206
|
+
/* @__PURE__ */ jsxs6("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
|
|
3207
|
+
/* @__PURE__ */ jsx6("div", { style: { flex: 1, overflow: "auto", background: t.canvas, position: "relative" }, children: /* @__PURE__ */ jsx6(
|
|
3208
|
+
SequenceCanvas,
|
|
3209
|
+
{
|
|
3210
|
+
model,
|
|
3211
|
+
actors,
|
|
3212
|
+
messages,
|
|
3213
|
+
t,
|
|
3214
|
+
isDark,
|
|
3215
|
+
colW,
|
|
3216
|
+
totalW,
|
|
3217
|
+
totalH,
|
|
3218
|
+
actorX,
|
|
3219
|
+
msgY,
|
|
3220
|
+
selected,
|
|
3221
|
+
editingId,
|
|
3222
|
+
setEditingId,
|
|
3223
|
+
drag,
|
|
3224
|
+
onRowMouseDown,
|
|
3225
|
+
renameActor,
|
|
3226
|
+
removeActor,
|
|
3227
|
+
svgRef
|
|
3228
|
+
}
|
|
3229
|
+
) }),
|
|
3230
|
+
selectedMsg && /* @__PURE__ */ jsxs6(
|
|
3231
|
+
"div",
|
|
3232
|
+
{
|
|
3233
|
+
style: {
|
|
3234
|
+
width: 280,
|
|
3235
|
+
maxWidth: "40vw",
|
|
3236
|
+
flexShrink: 0,
|
|
3237
|
+
background: t.panelBg,
|
|
3238
|
+
borderLeft: `1px solid ${t.panelBorder}`,
|
|
3239
|
+
padding: "14px 16px",
|
|
3240
|
+
overflowY: "auto"
|
|
3241
|
+
},
|
|
3242
|
+
children: [
|
|
3243
|
+
/* @__PURE__ */ jsx6(
|
|
3244
|
+
"div",
|
|
3245
|
+
{
|
|
3246
|
+
style: {
|
|
3247
|
+
fontSize: 10,
|
|
3248
|
+
fontWeight: 700,
|
|
3249
|
+
color: t.textMuted,
|
|
3250
|
+
textTransform: "uppercase",
|
|
3251
|
+
letterSpacing: 0.7,
|
|
3252
|
+
marginBottom: 10
|
|
3253
|
+
},
|
|
3254
|
+
children: "Message"
|
|
3255
|
+
}
|
|
3256
|
+
),
|
|
3257
|
+
/* @__PURE__ */ jsx6(Label, { t, children: "Label" }),
|
|
3258
|
+
/* @__PURE__ */ jsx6(
|
|
3259
|
+
"input",
|
|
3260
|
+
{
|
|
3261
|
+
value: editLabel || selectedMsg.label,
|
|
3262
|
+
onChange: (e) => setEditLabel(e.target.value),
|
|
3263
|
+
onFocus: () => setEditLabel(selectedMsg.label),
|
|
3264
|
+
onBlur: () => {
|
|
3265
|
+
if (editLabel && editLabel !== selectedMsg.label)
|
|
3266
|
+
updateMessage(selectedMsg.id, { label: editLabel });
|
|
3267
|
+
setEditLabel("");
|
|
3268
|
+
},
|
|
3269
|
+
onKeyDown: (e) => {
|
|
3270
|
+
if (e.key === "Enter") e.target.blur();
|
|
3271
|
+
},
|
|
3272
|
+
style: input(t)
|
|
3273
|
+
}
|
|
3274
|
+
),
|
|
3275
|
+
/* @__PURE__ */ jsx6(Label, { t, children: "From" }),
|
|
3276
|
+
/* @__PURE__ */ jsx6(
|
|
3277
|
+
"select",
|
|
3278
|
+
{
|
|
3279
|
+
value: selectedMsg.from,
|
|
3280
|
+
onChange: (e) => updateMessage(selectedMsg.id, { from: e.target.value }),
|
|
3281
|
+
style: input(t),
|
|
3282
|
+
children: actors.map((a) => /* @__PURE__ */ jsx6("option", { value: a, children: a }, a))
|
|
3283
|
+
}
|
|
3284
|
+
),
|
|
3285
|
+
/* @__PURE__ */ jsx6(Label, { t, children: "To" }),
|
|
3286
|
+
/* @__PURE__ */ jsx6(
|
|
3287
|
+
"select",
|
|
3288
|
+
{
|
|
3289
|
+
value: selectedMsg.to,
|
|
3290
|
+
onChange: (e) => updateMessage(selectedMsg.id, { to: e.target.value }),
|
|
3291
|
+
style: input(t),
|
|
3292
|
+
children: actors.map((a) => /* @__PURE__ */ jsx6("option", { value: a, children: a }, a))
|
|
3293
|
+
}
|
|
3294
|
+
),
|
|
3295
|
+
/* @__PURE__ */ jsx6(Label, { t, children: "Style" }),
|
|
3296
|
+
/* @__PURE__ */ jsx6("div", { style: { display: "flex", gap: 6 }, children: ["solid", "dashed"].map((s2) => /* @__PURE__ */ jsx6(
|
|
3297
|
+
"button",
|
|
3298
|
+
{
|
|
3299
|
+
onClick: () => updateMessage(selectedMsg.id, { style: s2 }),
|
|
3300
|
+
style: {
|
|
3301
|
+
flex: 1,
|
|
3302
|
+
padding: "6px 10px",
|
|
3303
|
+
border: `1.5px solid ${selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ? INDIGO2 : t.inputBorder}`,
|
|
3304
|
+
background: selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ? INDIGO_SOFT2 : t.inputBg,
|
|
3305
|
+
color: selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ? INDIGO2 : t.textPrimary,
|
|
3306
|
+
borderRadius: 8,
|
|
3307
|
+
fontSize: 12,
|
|
3308
|
+
fontWeight: 600,
|
|
3309
|
+
cursor: "pointer",
|
|
3310
|
+
fontFamily: "inherit"
|
|
3311
|
+
},
|
|
3312
|
+
children: s2 === "solid" ? "\u2500\u2500 solid" : "\u2500 \u2500 dashed"
|
|
3313
|
+
},
|
|
3314
|
+
s2
|
|
3315
|
+
)) }),
|
|
3316
|
+
/* @__PURE__ */ jsx6("div", { style: { height: 14 } }),
|
|
3317
|
+
/* @__PURE__ */ jsx6(
|
|
3318
|
+
"button",
|
|
3319
|
+
{
|
|
3320
|
+
onClick: () => removeMessage(selectedMsg.id),
|
|
3321
|
+
style: {
|
|
3322
|
+
...ghostBtn2(t),
|
|
3323
|
+
width: "100%",
|
|
3324
|
+
color: "#ef4444",
|
|
3325
|
+
border: `1px solid ${isDark ? "#7f1d1d" : "#fca5a5"}`
|
|
3326
|
+
},
|
|
3327
|
+
children: "Delete message"
|
|
3328
|
+
}
|
|
3329
|
+
)
|
|
3330
|
+
]
|
|
3331
|
+
}
|
|
3332
|
+
)
|
|
3333
|
+
] }),
|
|
3334
|
+
/* @__PURE__ */ jsxs6(
|
|
3335
|
+
"div",
|
|
2458
3336
|
{
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
3337
|
+
style: {
|
|
3338
|
+
padding: "4px 14px",
|
|
3339
|
+
fontSize: 11,
|
|
3340
|
+
color: t.textMuted,
|
|
3341
|
+
background: t.canvas,
|
|
3342
|
+
borderTop: `1px solid ${t.ctrlsBorder}`,
|
|
3343
|
+
display: "flex",
|
|
3344
|
+
gap: 16
|
|
3345
|
+
},
|
|
3346
|
+
children: [
|
|
3347
|
+
/* @__PURE__ */ jsxs6("span", { children: [
|
|
3348
|
+
actors.length,
|
|
3349
|
+
" actors"
|
|
3350
|
+
] }),
|
|
3351
|
+
/* @__PURE__ */ jsxs6("span", { children: [
|
|
3352
|
+
messages.length,
|
|
3353
|
+
" messages"
|
|
3354
|
+
] }),
|
|
3355
|
+
/* @__PURE__ */ jsx6("span", { style: { marginLeft: "auto" }, children: "double-click actor to rename \xB7 drag a row to reorder" })
|
|
3356
|
+
]
|
|
2462
3357
|
}
|
|
2463
3358
|
)
|
|
2464
|
-
]
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
padding: "4px 14px",
|
|
2468
|
-
fontSize: 11,
|
|
2469
|
-
color: t.textMuted,
|
|
2470
|
-
background: t.canvas,
|
|
2471
|
-
borderTop: `1px solid ${t.ctrlsBorder}`,
|
|
2472
|
-
display: "flex",
|
|
2473
|
-
gap: 16
|
|
2474
|
-
}, children: [
|
|
2475
|
-
/* @__PURE__ */ jsxs6("span", { children: [
|
|
2476
|
-
actors.length,
|
|
2477
|
-
" actors"
|
|
2478
|
-
] }),
|
|
2479
|
-
/* @__PURE__ */ jsxs6("span", { children: [
|
|
2480
|
-
messages.length,
|
|
2481
|
-
" messages"
|
|
2482
|
-
] }),
|
|
2483
|
-
/* @__PURE__ */ jsx6("span", { style: { marginLeft: "auto" }, children: "double-click actor to rename \xB7 drag a row to reorder" })
|
|
2484
|
-
] })
|
|
2485
|
-
] });
|
|
3359
|
+
]
|
|
3360
|
+
}
|
|
3361
|
+
);
|
|
2486
3362
|
}
|
|
2487
3363
|
function primaryBtn() {
|
|
2488
3364
|
return {
|
|
@@ -2526,7 +3402,20 @@ function input(t) {
|
|
|
2526
3402
|
};
|
|
2527
3403
|
}
|
|
2528
3404
|
function Label({ t, children }) {
|
|
2529
|
-
return /* @__PURE__ */ jsx6(
|
|
3405
|
+
return /* @__PURE__ */ jsx6(
|
|
3406
|
+
"div",
|
|
3407
|
+
{
|
|
3408
|
+
style: {
|
|
3409
|
+
fontSize: 10,
|
|
3410
|
+
fontWeight: 700,
|
|
3411
|
+
color: t.textMuted,
|
|
3412
|
+
textTransform: "uppercase",
|
|
3413
|
+
letterSpacing: 0.6,
|
|
3414
|
+
marginBottom: 4
|
|
3415
|
+
},
|
|
3416
|
+
children
|
|
3417
|
+
}
|
|
3418
|
+
);
|
|
2530
3419
|
}
|
|
2531
3420
|
|
|
2532
3421
|
// src/ui/NodeNavigator.tsx
|
|
@@ -2558,163 +3447,286 @@ function NodeNavigator({
|
|
|
2558
3447
|
return "\u25AD";
|
|
2559
3448
|
}
|
|
2560
3449
|
};
|
|
2561
|
-
const filtered = model.nodes.filter(
|
|
2562
|
-
(n) => n.label.toLowerCase().includes(search.toLowerCase())
|
|
2563
|
-
);
|
|
3450
|
+
const filtered = model.nodes.filter((n) => n.label.toLowerCase().includes(search.toLowerCase()));
|
|
2564
3451
|
const inEdges = (id) => model.edges.filter((e) => e.to === id).length;
|
|
2565
3452
|
const outEdges = (id) => model.edges.filter((e) => e.from === id).length;
|
|
2566
3453
|
if (!open) {
|
|
2567
|
-
return /* @__PURE__ */ jsxs7(
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
3454
|
+
return /* @__PURE__ */ jsxs7(
|
|
3455
|
+
"div",
|
|
3456
|
+
{
|
|
3457
|
+
style: {
|
|
3458
|
+
width: 36,
|
|
3459
|
+
flexShrink: 0,
|
|
3460
|
+
background: t.panelBg,
|
|
3461
|
+
borderRight: `1px solid ${t.panelBorder}`,
|
|
3462
|
+
display: "flex",
|
|
3463
|
+
flexDirection: "column",
|
|
3464
|
+
alignItems: "center",
|
|
3465
|
+
paddingTop: 8,
|
|
3466
|
+
gap: 6
|
|
3467
|
+
},
|
|
3468
|
+
children: [
|
|
3469
|
+
/* @__PURE__ */ jsx7(
|
|
3470
|
+
"button",
|
|
3471
|
+
{
|
|
3472
|
+
onClick: onToggle,
|
|
3473
|
+
title: "Open node list",
|
|
3474
|
+
"aria-expanded": false,
|
|
3475
|
+
"aria-label": "Open node list",
|
|
3476
|
+
style: {
|
|
3477
|
+
background: "none",
|
|
3478
|
+
border: "none",
|
|
3479
|
+
cursor: "pointer",
|
|
3480
|
+
color: t.textMuted,
|
|
3481
|
+
padding: 6,
|
|
3482
|
+
borderRadius: 6,
|
|
3483
|
+
fontSize: 14,
|
|
3484
|
+
lineHeight: 1
|
|
3485
|
+
},
|
|
3486
|
+
children: "\u2630"
|
|
3487
|
+
}
|
|
3488
|
+
),
|
|
3489
|
+
/* @__PURE__ */ jsx7(
|
|
3490
|
+
"div",
|
|
3491
|
+
{
|
|
3492
|
+
style: {
|
|
3493
|
+
fontSize: 10,
|
|
3494
|
+
color: t.textMuted,
|
|
3495
|
+
fontWeight: 700,
|
|
3496
|
+
writingMode: "vertical-rl",
|
|
3497
|
+
transform: "rotate(180deg)",
|
|
3498
|
+
letterSpacing: 0.5
|
|
3499
|
+
},
|
|
3500
|
+
children: model.nodes.length
|
|
3501
|
+
}
|
|
3502
|
+
)
|
|
3503
|
+
]
|
|
3504
|
+
}
|
|
3505
|
+
);
|
|
2591
3506
|
}
|
|
2592
|
-
return /* @__PURE__ */ jsxs7(
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
flexShrink: 0
|
|
2608
|
-
}, children: [
|
|
2609
|
-
/* @__PURE__ */ jsxs7("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
|
|
2610
|
-
/* @__PURE__ */ jsx7("span", { style: { fontSize: 11, fontWeight: 700, color: t.textSecondary, textTransform: "uppercase", letterSpacing: 0.7 }, children: variant === "question" ? "Questions" : variant === "journey" ? "Steps" : "Nodes" }),
|
|
2611
|
-
/* @__PURE__ */ jsx7("span", { style: {
|
|
2612
|
-
fontSize: 10,
|
|
2613
|
-
fontWeight: 700,
|
|
2614
|
-
color: t.textMuted,
|
|
2615
|
-
background: isDark ? "#0f172a" : "#f1f5f9",
|
|
2616
|
-
padding: "1px 6px",
|
|
2617
|
-
borderRadius: 99
|
|
2618
|
-
}, children: model.nodes.length })
|
|
2619
|
-
] }),
|
|
2620
|
-
/* @__PURE__ */ jsx7(
|
|
2621
|
-
"button",
|
|
2622
|
-
{
|
|
2623
|
-
onClick: onToggle,
|
|
2624
|
-
style: { background: "none", border: "none", cursor: "pointer", color: t.textMuted, padding: "2px 4px", borderRadius: 4, fontSize: 13, lineHeight: 1 },
|
|
2625
|
-
title: "Collapse",
|
|
2626
|
-
"aria-expanded": true,
|
|
2627
|
-
"aria-label": "Collapse node list",
|
|
2628
|
-
children: "\u2039"
|
|
2629
|
-
}
|
|
2630
|
-
)
|
|
2631
|
-
] }),
|
|
2632
|
-
/* @__PURE__ */ jsx7("div", { style: { padding: "8px 10px", borderBottom: `1px solid ${t.sectionBorder}`, flexShrink: 0 }, children: /* @__PURE__ */ jsxs7("div", { style: { position: "relative" }, children: [
|
|
2633
|
-
/* @__PURE__ */ jsx7("span", { style: { position: "absolute", left: 8, top: "50%", transform: "translateY(-50%)", fontSize: 11, color: t.textMuted, pointerEvents: "none" }, children: "\u2315" }),
|
|
2634
|
-
/* @__PURE__ */ jsx7(
|
|
2635
|
-
"input",
|
|
2636
|
-
{
|
|
2637
|
-
value: search,
|
|
2638
|
-
onChange: (e) => setSearch(e.target.value),
|
|
2639
|
-
placeholder: "Search\u2026",
|
|
2640
|
-
style: {
|
|
2641
|
-
width: "100%",
|
|
2642
|
-
padding: "5px 8px 5px 24px",
|
|
2643
|
-
border: `1.5px solid ${t.inputBorder}`,
|
|
2644
|
-
borderRadius: 7,
|
|
2645
|
-
fontSize: 12,
|
|
2646
|
-
background: t.inputBg,
|
|
2647
|
-
color: t.inputText,
|
|
2648
|
-
outline: "none",
|
|
2649
|
-
boxSizing: "border-box",
|
|
2650
|
-
fontFamily: "inherit"
|
|
2651
|
-
}
|
|
2652
|
-
}
|
|
2653
|
-
)
|
|
2654
|
-
] }) }),
|
|
2655
|
-
/* @__PURE__ */ jsxs7("div", { style: { flex: 1, overflowY: "auto", padding: "6px 8px", display: "flex", flexDirection: "column", gap: 2 }, children: [
|
|
2656
|
-
filtered.length === 0 && /* @__PURE__ */ jsx7("div", { style: { textAlign: "center", padding: "20px 0", fontSize: 12, color: t.textMuted, fontStyle: "italic" }, children: model.nodes.length === 0 ? "No nodes yet" : "No matches" }),
|
|
2657
|
-
filtered.map((node, idx) => {
|
|
2658
|
-
const isSelected = selected === node.id;
|
|
2659
|
-
const answers = node.metadata?.answers ?? [];
|
|
2660
|
-
return /* @__PURE__ */ jsxs7(
|
|
2661
|
-
"button",
|
|
3507
|
+
return /* @__PURE__ */ jsxs7(
|
|
3508
|
+
"div",
|
|
3509
|
+
{
|
|
3510
|
+
style: {
|
|
3511
|
+
width: 216,
|
|
3512
|
+
flexShrink: 0,
|
|
3513
|
+
background: t.panelBg,
|
|
3514
|
+
borderRight: `1px solid ${t.panelBorder}`,
|
|
3515
|
+
display: "flex",
|
|
3516
|
+
flexDirection: "column",
|
|
3517
|
+
overflow: "hidden"
|
|
3518
|
+
},
|
|
3519
|
+
children: [
|
|
3520
|
+
/* @__PURE__ */ jsxs7(
|
|
3521
|
+
"div",
|
|
2662
3522
|
{
|
|
2663
|
-
onClick: () => onSelect(node.id),
|
|
2664
3523
|
style: {
|
|
2665
3524
|
display: "flex",
|
|
2666
3525
|
alignItems: "center",
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
background: isSelected ? acc.fill : "transparent",
|
|
2672
|
-
border: isSelected ? `1.5px solid ${acc.border}` : "1.5px solid transparent",
|
|
2673
|
-
borderRadius: 8,
|
|
2674
|
-
cursor: "pointer",
|
|
2675
|
-
fontFamily: "inherit",
|
|
2676
|
-
transition: "background 0.1s"
|
|
2677
|
-
},
|
|
2678
|
-
onMouseEnter: (e) => {
|
|
2679
|
-
if (!isSelected) e.currentTarget.style.background = isDark ? "#334155" : "#f1f5f9";
|
|
2680
|
-
},
|
|
2681
|
-
onMouseLeave: (e) => {
|
|
2682
|
-
if (!isSelected) e.currentTarget.style.background = "transparent";
|
|
3526
|
+
justifyContent: "space-between",
|
|
3527
|
+
padding: "10px 12px",
|
|
3528
|
+
borderBottom: `1px solid ${t.panelBorder}`,
|
|
3529
|
+
flexShrink: 0
|
|
2683
3530
|
},
|
|
2684
3531
|
children: [
|
|
2685
|
-
/* @__PURE__ */
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
/* @__PURE__ */ jsx7(
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
3532
|
+
/* @__PURE__ */ jsxs7("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
|
|
3533
|
+
/* @__PURE__ */ jsx7(
|
|
3534
|
+
"span",
|
|
3535
|
+
{
|
|
3536
|
+
style: {
|
|
3537
|
+
fontSize: 11,
|
|
3538
|
+
fontWeight: 700,
|
|
3539
|
+
color: t.textSecondary,
|
|
3540
|
+
textTransform: "uppercase",
|
|
3541
|
+
letterSpacing: 0.7
|
|
3542
|
+
},
|
|
3543
|
+
children: variant === "question" ? "Questions" : variant === "journey" ? "Steps" : "Nodes"
|
|
3544
|
+
}
|
|
3545
|
+
),
|
|
3546
|
+
/* @__PURE__ */ jsx7(
|
|
3547
|
+
"span",
|
|
3548
|
+
{
|
|
3549
|
+
style: {
|
|
3550
|
+
fontSize: 10,
|
|
3551
|
+
fontWeight: 700,
|
|
3552
|
+
color: t.textMuted,
|
|
3553
|
+
background: isDark ? "#0f172a" : "#f1f5f9",
|
|
3554
|
+
padding: "1px 6px",
|
|
3555
|
+
borderRadius: 99
|
|
3556
|
+
},
|
|
3557
|
+
children: model.nodes.length
|
|
3558
|
+
}
|
|
3559
|
+
)
|
|
2709
3560
|
] }),
|
|
2710
|
-
|
|
3561
|
+
/* @__PURE__ */ jsx7(
|
|
3562
|
+
"button",
|
|
3563
|
+
{
|
|
3564
|
+
onClick: onToggle,
|
|
3565
|
+
style: {
|
|
3566
|
+
background: "none",
|
|
3567
|
+
border: "none",
|
|
3568
|
+
cursor: "pointer",
|
|
3569
|
+
color: t.textMuted,
|
|
3570
|
+
padding: "2px 4px",
|
|
3571
|
+
borderRadius: 4,
|
|
3572
|
+
fontSize: 13,
|
|
3573
|
+
lineHeight: 1
|
|
3574
|
+
},
|
|
3575
|
+
title: "Collapse",
|
|
3576
|
+
"aria-expanded": true,
|
|
3577
|
+
"aria-label": "Collapse node list",
|
|
3578
|
+
children: "\u2039"
|
|
3579
|
+
}
|
|
3580
|
+
)
|
|
2711
3581
|
]
|
|
2712
|
-
}
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
3582
|
+
}
|
|
3583
|
+
),
|
|
3584
|
+
/* @__PURE__ */ jsx7(
|
|
3585
|
+
"div",
|
|
3586
|
+
{
|
|
3587
|
+
style: { padding: "8px 10px", borderBottom: `1px solid ${t.sectionBorder}`, flexShrink: 0 },
|
|
3588
|
+
children: /* @__PURE__ */ jsxs7("div", { style: { position: "relative" }, children: [
|
|
3589
|
+
/* @__PURE__ */ jsx7(
|
|
3590
|
+
"span",
|
|
3591
|
+
{
|
|
3592
|
+
style: {
|
|
3593
|
+
position: "absolute",
|
|
3594
|
+
left: 8,
|
|
3595
|
+
top: "50%",
|
|
3596
|
+
transform: "translateY(-50%)",
|
|
3597
|
+
fontSize: 11,
|
|
3598
|
+
color: t.textMuted,
|
|
3599
|
+
pointerEvents: "none"
|
|
3600
|
+
},
|
|
3601
|
+
children: "\u2315"
|
|
3602
|
+
}
|
|
3603
|
+
),
|
|
3604
|
+
/* @__PURE__ */ jsx7(
|
|
3605
|
+
"input",
|
|
3606
|
+
{
|
|
3607
|
+
value: search,
|
|
3608
|
+
onChange: (e) => setSearch(e.target.value),
|
|
3609
|
+
placeholder: "Search\u2026",
|
|
3610
|
+
style: {
|
|
3611
|
+
width: "100%",
|
|
3612
|
+
padding: "5px 8px 5px 24px",
|
|
3613
|
+
border: `1.5px solid ${t.inputBorder}`,
|
|
3614
|
+
borderRadius: 7,
|
|
3615
|
+
fontSize: 12,
|
|
3616
|
+
background: t.inputBg,
|
|
3617
|
+
color: t.inputText,
|
|
3618
|
+
outline: "none",
|
|
3619
|
+
boxSizing: "border-box",
|
|
3620
|
+
fontFamily: "inherit"
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
)
|
|
3624
|
+
] })
|
|
3625
|
+
}
|
|
3626
|
+
),
|
|
3627
|
+
/* @__PURE__ */ jsxs7(
|
|
3628
|
+
"div",
|
|
3629
|
+
{
|
|
3630
|
+
style: {
|
|
3631
|
+
flex: 1,
|
|
3632
|
+
overflowY: "auto",
|
|
3633
|
+
padding: "6px 8px",
|
|
3634
|
+
display: "flex",
|
|
3635
|
+
flexDirection: "column",
|
|
3636
|
+
gap: 2
|
|
3637
|
+
},
|
|
3638
|
+
children: [
|
|
3639
|
+
filtered.length === 0 && /* @__PURE__ */ jsx7(
|
|
3640
|
+
"div",
|
|
3641
|
+
{
|
|
3642
|
+
style: {
|
|
3643
|
+
textAlign: "center",
|
|
3644
|
+
padding: "20px 0",
|
|
3645
|
+
fontSize: 12,
|
|
3646
|
+
color: t.textMuted,
|
|
3647
|
+
fontStyle: "italic"
|
|
3648
|
+
},
|
|
3649
|
+
children: model.nodes.length === 0 ? "No nodes yet" : "No matches"
|
|
3650
|
+
}
|
|
3651
|
+
),
|
|
3652
|
+
filtered.map((node, idx) => {
|
|
3653
|
+
const isSelected = selected === node.id;
|
|
3654
|
+
const answers = node.metadata?.answers ?? [];
|
|
3655
|
+
return /* @__PURE__ */ jsxs7(
|
|
3656
|
+
"button",
|
|
3657
|
+
{
|
|
3658
|
+
onClick: () => onSelect(node.id),
|
|
3659
|
+
style: {
|
|
3660
|
+
display: "flex",
|
|
3661
|
+
alignItems: "center",
|
|
3662
|
+
gap: 8,
|
|
3663
|
+
width: "100%",
|
|
3664
|
+
padding: "7px 8px",
|
|
3665
|
+
textAlign: "left",
|
|
3666
|
+
background: isSelected ? acc.fill : "transparent",
|
|
3667
|
+
border: isSelected ? `1.5px solid ${acc.border}` : "1.5px solid transparent",
|
|
3668
|
+
borderRadius: 8,
|
|
3669
|
+
cursor: "pointer",
|
|
3670
|
+
fontFamily: "inherit",
|
|
3671
|
+
transition: "background 0.1s"
|
|
3672
|
+
},
|
|
3673
|
+
onMouseEnter: (e) => {
|
|
3674
|
+
if (!isSelected)
|
|
3675
|
+
e.currentTarget.style.background = isDark ? "#334155" : "#f1f5f9";
|
|
3676
|
+
},
|
|
3677
|
+
onMouseLeave: (e) => {
|
|
3678
|
+
if (!isSelected) e.currentTarget.style.background = "transparent";
|
|
3679
|
+
},
|
|
3680
|
+
children: [
|
|
3681
|
+
/* @__PURE__ */ jsx7(
|
|
3682
|
+
"div",
|
|
3683
|
+
{
|
|
3684
|
+
style: {
|
|
3685
|
+
width: 22,
|
|
3686
|
+
height: 22,
|
|
3687
|
+
borderRadius: 6,
|
|
3688
|
+
flexShrink: 0,
|
|
3689
|
+
background: isSelected ? acc.color : isDark ? "#334155" : "#e2e8f0",
|
|
3690
|
+
color: isSelected ? "#fff" : t.textMuted,
|
|
3691
|
+
display: "flex",
|
|
3692
|
+
alignItems: "center",
|
|
3693
|
+
justifyContent: "center",
|
|
3694
|
+
fontSize: variant === "journey" ? 9 : 11,
|
|
3695
|
+
fontWeight: 700
|
|
3696
|
+
},
|
|
3697
|
+
children: variant === "journey" ? idx + 1 : shapeIcon(node)
|
|
3698
|
+
}
|
|
3699
|
+
),
|
|
3700
|
+
/* @__PURE__ */ jsxs7("div", { style: { flex: 1, minWidth: 0 }, children: [
|
|
3701
|
+
/* @__PURE__ */ jsx7(
|
|
3702
|
+
"div",
|
|
3703
|
+
{
|
|
3704
|
+
style: {
|
|
3705
|
+
fontSize: 12,
|
|
3706
|
+
fontWeight: isSelected ? 600 : 400,
|
|
3707
|
+
color: isSelected ? acc.color : t.textPrimary,
|
|
3708
|
+
overflow: "hidden",
|
|
3709
|
+
textOverflow: "ellipsis",
|
|
3710
|
+
whiteSpace: "nowrap",
|
|
3711
|
+
lineHeight: 1.3
|
|
3712
|
+
},
|
|
3713
|
+
children: node.label
|
|
3714
|
+
}
|
|
3715
|
+
),
|
|
3716
|
+
/* @__PURE__ */ jsx7("div", { style: { fontSize: 10, color: t.textMuted, lineHeight: 1.2, marginTop: 1 }, children: variant === "question" ? `${answers.length} answer${answers.length !== 1 ? "s" : ""}` : `${inEdges(node.id)}\u2193 ${outEdges(node.id)}\u2192` })
|
|
3717
|
+
] }),
|
|
3718
|
+
isSelected && /* @__PURE__ */ jsx7("span", { style: { fontSize: 10, color: acc.color, flexShrink: 0 }, children: "\u25C9" })
|
|
3719
|
+
]
|
|
3720
|
+
},
|
|
3721
|
+
node.id
|
|
3722
|
+
);
|
|
3723
|
+
})
|
|
3724
|
+
]
|
|
3725
|
+
}
|
|
3726
|
+
)
|
|
3727
|
+
]
|
|
3728
|
+
}
|
|
3729
|
+
);
|
|
2718
3730
|
}
|
|
2719
3731
|
|
|
2720
3732
|
// src/ui/render.tsx
|
|
@@ -2789,18 +3801,55 @@ var STYLE_LABEL = { pointerEvents: "none", userSelect: "none" };
|
|
|
2789
3801
|
var STYLE_BLUR = { filter: "blur(4px)" };
|
|
2790
3802
|
var STYLE_EDGE_HIT = { cursor: "pointer" };
|
|
2791
3803
|
var STYLE_NO_EVENTS = { pointerEvents: "none" };
|
|
2792
|
-
var STYLE_PORT_HOVER = {
|
|
2793
|
-
|
|
3804
|
+
var STYLE_PORT_HOVER = {
|
|
3805
|
+
cursor: "crosshair",
|
|
3806
|
+
filter: "drop-shadow(0 1px 3px rgba(0,0,0,0.18))"
|
|
3807
|
+
};
|
|
3808
|
+
var STYLE_WAYPOINT = {
|
|
3809
|
+
cursor: "grab",
|
|
3810
|
+
filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.25))"
|
|
3811
|
+
};
|
|
2794
3812
|
var STYLE_EDGE_LABEL_HIT = { cursor: "text" };
|
|
2795
|
-
function NodeShape({
|
|
3813
|
+
function NodeShape({
|
|
3814
|
+
node,
|
|
3815
|
+
selected,
|
|
3816
|
+
variant,
|
|
3817
|
+
stepNumber,
|
|
3818
|
+
t,
|
|
3819
|
+
isDark,
|
|
3820
|
+
w
|
|
3821
|
+
}) {
|
|
2796
3822
|
const acc = variantAccent(variant, isDark);
|
|
2797
3823
|
const cx = w / 2, cy = NODE_H2 / 2;
|
|
2798
3824
|
const stroke = selected ? acc.color : t.nodeStroke;
|
|
2799
3825
|
const fill = selected ? t.nodeSelectedFill : t.nodeFill;
|
|
2800
3826
|
const sw = selected ? 1.75 : 1.25;
|
|
2801
3827
|
const glow = selected && /* @__PURE__ */ jsx8(Fragment2, { children: node.shape === "circle" ? /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
2802
|
-
/* @__PURE__ */ jsx8(
|
|
2803
|
-
|
|
3828
|
+
/* @__PURE__ */ jsx8(
|
|
3829
|
+
"circle",
|
|
3830
|
+
{
|
|
3831
|
+
cx,
|
|
3832
|
+
cy,
|
|
3833
|
+
r: NODE_H2 / 2 + 3,
|
|
3834
|
+
fill: "none",
|
|
3835
|
+
stroke: acc.color,
|
|
3836
|
+
strokeWidth: 6,
|
|
3837
|
+
opacity: 0.18,
|
|
3838
|
+
style: STYLE_BLUR
|
|
3839
|
+
}
|
|
3840
|
+
),
|
|
3841
|
+
/* @__PURE__ */ jsx8(
|
|
3842
|
+
"circle",
|
|
3843
|
+
{
|
|
3844
|
+
cx,
|
|
3845
|
+
cy,
|
|
3846
|
+
r: NODE_H2 / 2 + 1.5,
|
|
3847
|
+
fill: "none",
|
|
3848
|
+
stroke: acc.color,
|
|
3849
|
+
strokeWidth: 1,
|
|
3850
|
+
opacity: 0.55
|
|
3851
|
+
}
|
|
3852
|
+
)
|
|
2804
3853
|
] }) : node.shape === "diamond" ? /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
2805
3854
|
/* @__PURE__ */ jsx8(
|
|
2806
3855
|
"polygon",
|
|
@@ -2857,39 +3906,98 @@ function NodeShape({ node, selected, variant, stepNumber, t, isDark, w }) {
|
|
|
2857
3906
|
const badgeColor = isDark ? ACCENT.emeraldDark : ACCENT.emerald;
|
|
2858
3907
|
const badge = variant === "journey" && stepNumber !== void 0 && /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
2859
3908
|
/* @__PURE__ */ jsx8("circle", { cx: 14, cy: 14, r: 10, fill: badgeColor }),
|
|
2860
|
-
/* @__PURE__ */ jsx8(
|
|
3909
|
+
/* @__PURE__ */ jsx8(
|
|
3910
|
+
"text",
|
|
3911
|
+
{
|
|
3912
|
+
x: 14,
|
|
3913
|
+
y: 18,
|
|
3914
|
+
textAnchor: "middle",
|
|
3915
|
+
fontSize: 9,
|
|
3916
|
+
fill: "white",
|
|
3917
|
+
fontWeight: "700",
|
|
3918
|
+
style: STYLE_LABEL,
|
|
3919
|
+
children: stepNumber
|
|
3920
|
+
}
|
|
3921
|
+
)
|
|
2861
3922
|
] });
|
|
2862
3923
|
switch (node.shape) {
|
|
2863
3924
|
case "diamond": {
|
|
2864
3925
|
const pts = `${cx},0 ${w},${cy} ${cx},${NODE_H2} 0,${cy}`;
|
|
2865
3926
|
return /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
2866
3927
|
glow,
|
|
2867
|
-
/* @__PURE__ */ jsx8(
|
|
3928
|
+
/* @__PURE__ */ jsx8(
|
|
3929
|
+
"polygon",
|
|
3930
|
+
{
|
|
3931
|
+
points: pts,
|
|
3932
|
+
fill,
|
|
3933
|
+
stroke,
|
|
3934
|
+
strokeWidth: sw,
|
|
3935
|
+
filter: "url(#nodeShadow)"
|
|
3936
|
+
}
|
|
3937
|
+
),
|
|
2868
3938
|
badge
|
|
2869
3939
|
] });
|
|
2870
3940
|
}
|
|
2871
3941
|
case "circle":
|
|
2872
3942
|
return /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
2873
3943
|
glow,
|
|
2874
|
-
/* @__PURE__ */ jsx8(
|
|
3944
|
+
/* @__PURE__ */ jsx8(
|
|
3945
|
+
"circle",
|
|
3946
|
+
{
|
|
3947
|
+
cx,
|
|
3948
|
+
cy,
|
|
3949
|
+
r: NODE_H2 / 2 - 1,
|
|
3950
|
+
fill,
|
|
3951
|
+
stroke,
|
|
3952
|
+
strokeWidth: sw,
|
|
3953
|
+
filter: "url(#nodeShadow)"
|
|
3954
|
+
}
|
|
3955
|
+
),
|
|
2875
3956
|
badge
|
|
2876
3957
|
] });
|
|
2877
3958
|
case "parallelogram":
|
|
2878
3959
|
return /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
2879
3960
|
glow,
|
|
2880
|
-
/* @__PURE__ */ jsx8(
|
|
3961
|
+
/* @__PURE__ */ jsx8(
|
|
3962
|
+
"polygon",
|
|
3963
|
+
{
|
|
3964
|
+
points: `14,0 ${w},0 ${w - 14},${NODE_H2} 0,${NODE_H2}`,
|
|
3965
|
+
fill,
|
|
3966
|
+
stroke,
|
|
3967
|
+
strokeWidth: sw,
|
|
3968
|
+
filter: "url(#nodeShadow)"
|
|
3969
|
+
}
|
|
3970
|
+
),
|
|
2881
3971
|
badge
|
|
2882
3972
|
] });
|
|
2883
3973
|
default:
|
|
2884
3974
|
return /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
2885
3975
|
glow,
|
|
2886
|
-
/* @__PURE__ */ jsx8(
|
|
3976
|
+
/* @__PURE__ */ jsx8(
|
|
3977
|
+
"rect",
|
|
3978
|
+
{
|
|
3979
|
+
width: w,
|
|
3980
|
+
height: NODE_H2,
|
|
3981
|
+
rx: 14,
|
|
3982
|
+
fill,
|
|
3983
|
+
stroke,
|
|
3984
|
+
strokeWidth: sw,
|
|
3985
|
+
filter: "url(#nodeShadow)"
|
|
3986
|
+
}
|
|
3987
|
+
),
|
|
2887
3988
|
badge
|
|
2888
3989
|
] });
|
|
2889
3990
|
}
|
|
2890
3991
|
}
|
|
2891
3992
|
var ANSWER_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
2892
|
-
function QuestionNode({
|
|
3993
|
+
function QuestionNode({
|
|
3994
|
+
node,
|
|
3995
|
+
selected,
|
|
3996
|
+
edges,
|
|
3997
|
+
isDark,
|
|
3998
|
+
onAnswerPortDown,
|
|
3999
|
+
qW
|
|
4000
|
+
}) {
|
|
2893
4001
|
const answers = node.metadata?.answers ?? [];
|
|
2894
4002
|
const totalH = questionNodeH2(answers);
|
|
2895
4003
|
const amber = isDark ? ACCENT.amberDark : ACCENT.amber;
|
|
@@ -2937,27 +4045,91 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
2937
4045
|
] });
|
|
2938
4046
|
return /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
2939
4047
|
glow,
|
|
2940
|
-
/* @__PURE__ */ jsx8(
|
|
4048
|
+
/* @__PURE__ */ jsx8(
|
|
4049
|
+
"rect",
|
|
4050
|
+
{
|
|
4051
|
+
width: qW,
|
|
4052
|
+
height: totalH,
|
|
4053
|
+
rx: 14,
|
|
4054
|
+
fill: nodeBg,
|
|
4055
|
+
stroke: nodeBorder,
|
|
4056
|
+
strokeWidth: selected ? 2 : 1.5,
|
|
4057
|
+
filter: "url(#nodeShadow)"
|
|
4058
|
+
}
|
|
4059
|
+
),
|
|
2941
4060
|
/* @__PURE__ */ jsx8("clipPath", { id: `qhdr-${node.id}`, children: /* @__PURE__ */ jsx8("rect", { width: qW, height: Q_BASE_H2, rx: 14 }) }),
|
|
2942
4061
|
/* @__PURE__ */ jsx8("rect", { width: qW, height: Q_BASE_H2, fill: amberSoft, clipPath: `url(#qhdr-${node.id})` }),
|
|
2943
4062
|
/* @__PURE__ */ jsx8("rect", { x: 0, y: 0, width: 4, height: Q_BASE_H2, rx: 2, fill: amber }),
|
|
2944
4063
|
/* @__PURE__ */ jsx8("rect", { x: 12, y: 14, width: 28, height: 28, rx: 8, fill: amber }),
|
|
2945
|
-
/* @__PURE__ */ jsx8(
|
|
2946
|
-
/* @__PURE__ */ jsxs8(
|
|
4064
|
+
/* @__PURE__ */ jsx8(
|
|
2947
4065
|
"text",
|
|
2948
4066
|
{
|
|
4067
|
+
x: 26,
|
|
4068
|
+
y: 33,
|
|
4069
|
+
textAnchor: "middle",
|
|
4070
|
+
fontSize: 15,
|
|
4071
|
+
fontWeight: "900",
|
|
4072
|
+
fill: "white",
|
|
2949
4073
|
style: STYLE_LABEL,
|
|
2950
|
-
|
|
2951
|
-
children: [
|
|
2952
|
-
/* @__PURE__ */ jsx8("tspan", { x: 50, y: 27, fontSize: 9, fontWeight: 700, fill: textSub, letterSpacing: 0.6, textAnchor: "start", children: "QUESTION" }),
|
|
2953
|
-
/* @__PURE__ */ jsx8("tspan", { x: 50, dy: 15, fontSize: 13, fontWeight: 700, fill: selected ? amber : textMain, textAnchor: "start", children: node.label })
|
|
2954
|
-
]
|
|
4074
|
+
children: "?"
|
|
2955
4075
|
}
|
|
2956
4076
|
),
|
|
4077
|
+
/* @__PURE__ */ jsxs8("text", { style: STYLE_LABEL, fontFamily: "ui-sans-serif,system-ui,sans-serif", children: [
|
|
4078
|
+
/* @__PURE__ */ jsx8(
|
|
4079
|
+
"tspan",
|
|
4080
|
+
{
|
|
4081
|
+
x: 50,
|
|
4082
|
+
y: 27,
|
|
4083
|
+
fontSize: 9,
|
|
4084
|
+
fontWeight: 700,
|
|
4085
|
+
fill: textSub,
|
|
4086
|
+
letterSpacing: 0.6,
|
|
4087
|
+
textAnchor: "start",
|
|
4088
|
+
children: "QUESTION"
|
|
4089
|
+
}
|
|
4090
|
+
),
|
|
4091
|
+
/* @__PURE__ */ jsx8(
|
|
4092
|
+
"tspan",
|
|
4093
|
+
{
|
|
4094
|
+
x: 50,
|
|
4095
|
+
dy: 15,
|
|
4096
|
+
fontSize: 13,
|
|
4097
|
+
fontWeight: 700,
|
|
4098
|
+
fill: selected ? amber : textMain,
|
|
4099
|
+
textAnchor: "start",
|
|
4100
|
+
children: node.label
|
|
4101
|
+
}
|
|
4102
|
+
)
|
|
4103
|
+
] }),
|
|
2957
4104
|
/* @__PURE__ */ jsx8("line", { x1: 0, y1: Q_BASE_H2, x2: qW, y2: Q_BASE_H2, stroke: amberLine, strokeWidth: 1 }),
|
|
2958
4105
|
answers.length === 0 && /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
2959
|
-
/* @__PURE__ */ jsx8(
|
|
2960
|
-
|
|
4106
|
+
/* @__PURE__ */ jsx8(
|
|
4107
|
+
"text",
|
|
4108
|
+
{
|
|
4109
|
+
x: qW / 2,
|
|
4110
|
+
y: Q_BASE_H2 + 22,
|
|
4111
|
+
textAnchor: "middle",
|
|
4112
|
+
fontSize: 10,
|
|
4113
|
+
fill: amber,
|
|
4114
|
+
opacity: 0.4,
|
|
4115
|
+
fontWeight: 600,
|
|
4116
|
+
style: STYLE_LABEL,
|
|
4117
|
+
children: "No answers yet"
|
|
4118
|
+
}
|
|
4119
|
+
),
|
|
4120
|
+
/* @__PURE__ */ jsx8(
|
|
4121
|
+
"text",
|
|
4122
|
+
{
|
|
4123
|
+
x: qW / 2,
|
|
4124
|
+
y: Q_BASE_H2 + 36,
|
|
4125
|
+
textAnchor: "middle",
|
|
4126
|
+
fontSize: 9,
|
|
4127
|
+
fill: textSub,
|
|
4128
|
+
opacity: 0.7,
|
|
4129
|
+
style: STYLE_LABEL,
|
|
4130
|
+
children: "Open panel \u2192 Add Answer"
|
|
4131
|
+
}
|
|
4132
|
+
)
|
|
2961
4133
|
] }),
|
|
2962
4134
|
answers.map((ans, i) => {
|
|
2963
4135
|
const prevW = answers.slice(0, i).reduce((s2, a) => s2 + answerCardW2(a) + Q_CARD_PAD2, 0);
|
|
@@ -3051,7 +4223,22 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
3051
4223
|
})
|
|
3052
4224
|
] });
|
|
3053
4225
|
}
|
|
3054
|
-
function EdgeLine({
|
|
4226
|
+
function EdgeLine({
|
|
4227
|
+
edge,
|
|
4228
|
+
nodes,
|
|
4229
|
+
variant,
|
|
4230
|
+
t,
|
|
4231
|
+
isDark,
|
|
4232
|
+
acc,
|
|
4233
|
+
editing,
|
|
4234
|
+
editValue,
|
|
4235
|
+
onEditChange,
|
|
4236
|
+
onEditCommit,
|
|
4237
|
+
onEditCancel,
|
|
4238
|
+
onDoubleClick,
|
|
4239
|
+
onContextMenu,
|
|
4240
|
+
onWaypointDown
|
|
4241
|
+
}) {
|
|
3055
4242
|
const [hovered, setHovered] = useState8(false);
|
|
3056
4243
|
const from = nodes.find((n) => n.id === edge.from);
|
|
3057
4244
|
const to = nodes.find((n) => n.id === edge.to);
|
|
@@ -3206,7 +4393,7 @@ function EdgeLine({ edge, nodes, variant, t, isDark, acc, editing, editValue, on
|
|
|
3206
4393
|
}
|
|
3207
4394
|
|
|
3208
4395
|
// src/ui/Minimap.tsx
|
|
3209
|
-
import {
|
|
4396
|
+
import { useRef as useRef4 } from "react";
|
|
3210
4397
|
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
3211
4398
|
var W = 168;
|
|
3212
4399
|
var H = 112;
|
|
@@ -3255,13 +4442,13 @@ function Minimap({
|
|
|
3255
4442
|
x: (mx - offsetX) / scale,
|
|
3256
4443
|
y: (my - offsetY) / scale
|
|
3257
4444
|
});
|
|
3258
|
-
const panTo =
|
|
4445
|
+
const panTo = (e) => {
|
|
3259
4446
|
const rect = e.currentTarget.getBoundingClientRect();
|
|
3260
4447
|
const mx = e.clientX - rect.left;
|
|
3261
4448
|
const my = e.clientY - rect.top;
|
|
3262
4449
|
const { x, y } = unproject(mx, my);
|
|
3263
4450
|
onCenterOn(x, y);
|
|
3264
|
-
}
|
|
4451
|
+
};
|
|
3265
4452
|
const onMouseDown = (e) => {
|
|
3266
4453
|
e.stopPropagation();
|
|
3267
4454
|
dragRef.current = { active: true };
|
|
@@ -3448,29 +4635,106 @@ function ContextMenu({
|
|
|
3448
4635
|
fontFamily: "ui-sans-serif,system-ui,sans-serif"
|
|
3449
4636
|
},
|
|
3450
4637
|
children: edgeId ? /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
3451
|
-
/* @__PURE__ */ jsx10(
|
|
4638
|
+
/* @__PURE__ */ jsx10(
|
|
4639
|
+
"div",
|
|
4640
|
+
{
|
|
4641
|
+
style: {
|
|
4642
|
+
padding: "4px 14px 6px",
|
|
4643
|
+
fontSize: 10,
|
|
4644
|
+
fontWeight: 700,
|
|
4645
|
+
color: muted,
|
|
4646
|
+
textTransform: "uppercase",
|
|
4647
|
+
letterSpacing: 0.8
|
|
4648
|
+
},
|
|
4649
|
+
children: "Edge"
|
|
4650
|
+
}
|
|
4651
|
+
),
|
|
3452
4652
|
item("Rename label (dbl-click)", () => onEdgeRename?.()),
|
|
3453
4653
|
divider2,
|
|
3454
|
-
/* @__PURE__ */ jsx10(
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
4654
|
+
/* @__PURE__ */ jsx10(
|
|
4655
|
+
"div",
|
|
4656
|
+
{
|
|
4657
|
+
style: {
|
|
4658
|
+
padding: "4px 14px 2px",
|
|
4659
|
+
fontSize: 9,
|
|
4660
|
+
fontWeight: 700,
|
|
4661
|
+
color: muted,
|
|
4662
|
+
textTransform: "uppercase",
|
|
4663
|
+
letterSpacing: 0.8
|
|
4664
|
+
},
|
|
4665
|
+
children: "Style"
|
|
4666
|
+
}
|
|
4667
|
+
),
|
|
4668
|
+
item(
|
|
4669
|
+
`Solid${currentEdgeStyle === "solid" || !currentEdgeStyle ? " \u2713" : ""}`,
|
|
4670
|
+
() => onEdgeStyle?.("solid")
|
|
4671
|
+
),
|
|
4672
|
+
item(
|
|
4673
|
+
`Dashed${currentEdgeStyle === "dashed" ? " \u2713" : ""}`,
|
|
4674
|
+
() => onEdgeStyle?.("dashed")
|
|
4675
|
+
),
|
|
4676
|
+
item(
|
|
4677
|
+
`Dotted${currentEdgeStyle === "dotted" ? " \u2713" : ""}`,
|
|
4678
|
+
() => onEdgeStyle?.("dotted")
|
|
4679
|
+
),
|
|
3458
4680
|
divider2,
|
|
3459
|
-
/* @__PURE__ */ jsx10(
|
|
3460
|
-
|
|
4681
|
+
/* @__PURE__ */ jsx10(
|
|
4682
|
+
"div",
|
|
4683
|
+
{
|
|
4684
|
+
style: {
|
|
4685
|
+
padding: "4px 14px 2px",
|
|
4686
|
+
fontSize: 9,
|
|
4687
|
+
fontWeight: 700,
|
|
4688
|
+
color: muted,
|
|
4689
|
+
textTransform: "uppercase",
|
|
4690
|
+
letterSpacing: 0.8
|
|
4691
|
+
},
|
|
4692
|
+
children: "Arrowhead"
|
|
4693
|
+
}
|
|
4694
|
+
),
|
|
4695
|
+
item(
|
|
4696
|
+
`Arrow${currentEdgeArrow !== "none" ? " \u2713" : ""}`,
|
|
4697
|
+
() => onEdgeArrowhead?.("arrow")
|
|
4698
|
+
),
|
|
3461
4699
|
item(`None${currentEdgeArrow === "none" ? " \u2713" : ""}`, () => onEdgeArrowhead?.("none")),
|
|
3462
4700
|
divider2,
|
|
3463
4701
|
item("Reset routing", () => onEdgeResetRouting?.(), void 0, !edgeHasWaypoint),
|
|
3464
4702
|
item("Delete edge", () => onEdgeDelete?.(), "#ef4444")
|
|
3465
4703
|
] }) : nodeId ? /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
3466
|
-
/* @__PURE__ */ jsx10(
|
|
4704
|
+
/* @__PURE__ */ jsx10(
|
|
4705
|
+
"div",
|
|
4706
|
+
{
|
|
4707
|
+
style: {
|
|
4708
|
+
padding: "4px 14px 6px",
|
|
4709
|
+
fontSize: 10,
|
|
4710
|
+
fontWeight: 700,
|
|
4711
|
+
color: muted,
|
|
4712
|
+
textTransform: "uppercase",
|
|
4713
|
+
letterSpacing: 0.8
|
|
4714
|
+
},
|
|
4715
|
+
children: "Node"
|
|
4716
|
+
}
|
|
4717
|
+
),
|
|
3467
4718
|
item("Rename (dbl-click)", onRename),
|
|
3468
4719
|
item("Duplicate", onDuplicate),
|
|
3469
4720
|
item("Disconnect all edges", onDisconnect),
|
|
3470
4721
|
divider2,
|
|
3471
4722
|
item("Delete node", onDelete, "#ef4444")
|
|
3472
4723
|
] }) : /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
3473
|
-
/* @__PURE__ */ jsx10(
|
|
4724
|
+
/* @__PURE__ */ jsx10(
|
|
4725
|
+
"div",
|
|
4726
|
+
{
|
|
4727
|
+
style: {
|
|
4728
|
+
padding: "4px 14px 6px",
|
|
4729
|
+
fontSize: 10,
|
|
4730
|
+
fontWeight: 700,
|
|
4731
|
+
color: muted,
|
|
4732
|
+
textTransform: "uppercase",
|
|
4733
|
+
letterSpacing: 0.8
|
|
4734
|
+
},
|
|
4735
|
+
children: "Canvas"
|
|
4736
|
+
}
|
|
4737
|
+
),
|
|
3474
4738
|
item("Add node here", onAddNode, acc.color),
|
|
3475
4739
|
item("Re-center (Ctrl+0)", onReCenter),
|
|
3476
4740
|
divider2,
|
|
@@ -3487,8 +4751,20 @@ var STYLE_LABEL2 = { pointerEvents: "none", userSelect: "none" };
|
|
|
3487
4751
|
var STYLE_LIVE_PORT = { opacity: 0.85, pointerEvents: "none" };
|
|
3488
4752
|
var STYLE_NODE_GRAB = { cursor: "grab" };
|
|
3489
4753
|
var STYLE_NODE_GRABBING = { cursor: "grabbing" };
|
|
3490
|
-
var STYLE_PORT_VISIBLE = {
|
|
3491
|
-
|
|
4754
|
+
var STYLE_PORT_VISIBLE = {
|
|
4755
|
+
cursor: "crosshair",
|
|
4756
|
+
opacity: 1,
|
|
4757
|
+
transition: "opacity 0.15s",
|
|
4758
|
+
pointerEvents: "all",
|
|
4759
|
+
filter: "drop-shadow(0 1px 3px rgba(0,0,0,0.25))"
|
|
4760
|
+
};
|
|
4761
|
+
var STYLE_PORT_HIDDEN = {
|
|
4762
|
+
cursor: "crosshair",
|
|
4763
|
+
opacity: 0,
|
|
4764
|
+
transition: "opacity 0.15s",
|
|
4765
|
+
pointerEvents: "none",
|
|
4766
|
+
filter: "drop-shadow(0 1px 3px rgba(0,0,0,0.25))"
|
|
4767
|
+
};
|
|
3492
4768
|
function DiagramCanvas(props) {
|
|
3493
4769
|
const {
|
|
3494
4770
|
model,
|
|
@@ -3559,25 +4835,35 @@ function DiagramCanvas(props) {
|
|
|
3559
4835
|
onCtxEdgeDelete,
|
|
3560
4836
|
onCtxEdgeResetRouting
|
|
3561
4837
|
} = props;
|
|
3562
|
-
return /* @__PURE__ */ jsxs11(
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
4838
|
+
return /* @__PURE__ */ jsxs11(
|
|
4839
|
+
"div",
|
|
4840
|
+
{
|
|
4841
|
+
ref: containerRef,
|
|
4842
|
+
style: { flex: 1, overflow: "hidden", position: "relative", background: t.canvas },
|
|
4843
|
+
children: [
|
|
4844
|
+
/* @__PURE__ */ jsxs11(
|
|
4845
|
+
"svg",
|
|
4846
|
+
{
|
|
4847
|
+
ref: svgRef,
|
|
4848
|
+
width: "100%",
|
|
4849
|
+
height: "100%",
|
|
4850
|
+
role: "application",
|
|
4851
|
+
"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.`,
|
|
4852
|
+
tabIndex: 0,
|
|
4853
|
+
style: {
|
|
4854
|
+
display: "block",
|
|
4855
|
+
cursor: pan ? "grabbing" : drag ? "grabbing" : liveEdge ? "crosshair" : "default",
|
|
4856
|
+
userSelect: "none",
|
|
4857
|
+
outline: "none"
|
|
4858
|
+
},
|
|
4859
|
+
onMouseDown: onSvgMouseDown,
|
|
4860
|
+
onMouseMove,
|
|
4861
|
+
onMouseUp,
|
|
4862
|
+
onMouseLeave: onMouseUp,
|
|
4863
|
+
onContextMenu: onSvgContextMenu,
|
|
4864
|
+
children: [
|
|
4865
|
+
/* @__PURE__ */ jsxs11("defs", { children: [
|
|
4866
|
+
/* @__PURE__ */ jsx11("style", { children: reducedMotion ? `
|
|
3581
4867
|
.edge-flow { stroke-dasharray: 0; }
|
|
3582
4868
|
.edge-flow-amber { stroke-dasharray: 0; }
|
|
3583
4869
|
.edge-live { stroke-dasharray: 4 4; }
|
|
@@ -3588,221 +4874,355 @@ function DiagramCanvas(props) {
|
|
|
3588
4874
|
.edge-flow-amber { stroke-dasharray: 6 4; animation: edgeFlowFast 0.65s linear infinite; }
|
|
3589
4875
|
.edge-live { stroke-dasharray: 7 5; animation: edgeFlow 0.55s linear infinite; }
|
|
3590
4876
|
` }),
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
4877
|
+
/* @__PURE__ */ jsx11("pattern", { id: "dots", width: GRID, height: GRID, patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ jsx11("circle", { cx: GRID / 2, cy: GRID / 2, r: 1.1, fill: t.dot }) }),
|
|
4878
|
+
/* @__PURE__ */ jsx11("filter", { id: "nodeShadow", x: "-25%", y: "-25%", width: "150%", height: "160%", children: /* @__PURE__ */ jsx11("feDropShadow", { dx: "0", dy: "3", stdDeviation: "5", floodColor: shadowClr, floodOpacity: "1" }) }),
|
|
4879
|
+
/* @__PURE__ */ jsx11(
|
|
4880
|
+
"marker",
|
|
4881
|
+
{
|
|
4882
|
+
id: "arrowhead",
|
|
4883
|
+
markerWidth: "9",
|
|
4884
|
+
markerHeight: "7",
|
|
4885
|
+
refX: "8",
|
|
4886
|
+
refY: "3.5",
|
|
4887
|
+
orient: "auto",
|
|
4888
|
+
markerUnits: "strokeWidth",
|
|
4889
|
+
children: /* @__PURE__ */ jsx11("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: arrowClr })
|
|
4890
|
+
}
|
|
4891
|
+
),
|
|
4892
|
+
/* @__PURE__ */ jsx11(
|
|
4893
|
+
"marker",
|
|
4894
|
+
{
|
|
4895
|
+
id: "arrowAmber",
|
|
4896
|
+
markerWidth: "9",
|
|
4897
|
+
markerHeight: "7",
|
|
4898
|
+
refX: "8",
|
|
4899
|
+
refY: "3.5",
|
|
4900
|
+
orient: "auto",
|
|
4901
|
+
markerUnits: "strokeWidth",
|
|
4902
|
+
children: /* @__PURE__ */ jsx11("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: amberArrow })
|
|
4903
|
+
}
|
|
4904
|
+
),
|
|
4905
|
+
/* @__PURE__ */ jsx11(
|
|
4906
|
+
"marker",
|
|
4907
|
+
{
|
|
4908
|
+
id: "arrowLive",
|
|
4909
|
+
markerWidth: "9",
|
|
4910
|
+
markerHeight: "7",
|
|
4911
|
+
refX: "8",
|
|
4912
|
+
refY: "3.5",
|
|
4913
|
+
orient: "auto",
|
|
4914
|
+
markerUnits: "strokeWidth",
|
|
4915
|
+
children: /* @__PURE__ */ jsx11("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: acc.color })
|
|
4916
|
+
}
|
|
4917
|
+
)
|
|
4918
|
+
] }),
|
|
4919
|
+
/* @__PURE__ */ jsx11("rect", { width: "100%", height: "100%", fill: "url(#dots)", "data-bg": "1" }),
|
|
4920
|
+
/* @__PURE__ */ jsxs11("g", { transform: `translate(${transform.x},${transform.y}) scale(${transform.scale})`, children: [
|
|
4921
|
+
model.edges.map((e) => /* @__PURE__ */ jsx11(
|
|
4922
|
+
EdgeLine,
|
|
4923
|
+
{
|
|
4924
|
+
edge: e,
|
|
4925
|
+
nodes: model.nodes,
|
|
4926
|
+
variant,
|
|
4927
|
+
t,
|
|
4928
|
+
isDark,
|
|
4929
|
+
acc,
|
|
4930
|
+
editing: editingEdgeId === e.id,
|
|
4931
|
+
editValue: editEdgeLabel,
|
|
4932
|
+
onEditChange: setEditEdgeLabel,
|
|
4933
|
+
onEditCommit: commitEdgeEdit,
|
|
4934
|
+
onEditCancel: () => setEditingEdgeId(null),
|
|
4935
|
+
onDoubleClick: beginEditEdge,
|
|
4936
|
+
onContextMenu: onEdgeContextMenu,
|
|
4937
|
+
onWaypointDown: (ev, edgeId) => setWaypointDrag(edgeId)
|
|
4938
|
+
},
|
|
4939
|
+
e.id
|
|
4940
|
+
)),
|
|
4941
|
+
liveEdge && (() => {
|
|
4942
|
+
const d = bezierPath2(
|
|
4943
|
+
liveEdge.fromX,
|
|
4944
|
+
liveEdge.fromY,
|
|
4945
|
+
liveEdge.toX,
|
|
4946
|
+
liveEdge.toY,
|
|
4947
|
+
liveEdge.exitDir
|
|
4948
|
+
);
|
|
4949
|
+
return /* @__PURE__ */ jsx11(
|
|
4950
|
+
"path",
|
|
4951
|
+
{
|
|
4952
|
+
d,
|
|
4953
|
+
fill: "none",
|
|
4954
|
+
stroke: acc.color,
|
|
4955
|
+
strokeWidth: 2,
|
|
4956
|
+
strokeLinecap: "round",
|
|
4957
|
+
className: "edge-live",
|
|
4958
|
+
opacity: 0.8,
|
|
4959
|
+
markerEnd: "url(#arrowLive)"
|
|
4960
|
+
}
|
|
4961
|
+
);
|
|
4962
|
+
})(),
|
|
4963
|
+
alignGuides?.x && /* @__PURE__ */ jsx11(
|
|
4964
|
+
"line",
|
|
4965
|
+
{
|
|
4966
|
+
x1: alignGuides.x.pos,
|
|
4967
|
+
x2: alignGuides.x.pos,
|
|
4968
|
+
y1: alignGuides.x.minY,
|
|
4969
|
+
y2: alignGuides.x.maxY,
|
|
4970
|
+
stroke: acc.color,
|
|
4971
|
+
strokeWidth: 1 / transform.scale,
|
|
4972
|
+
strokeDasharray: `${4 / transform.scale} ${3 / transform.scale}`,
|
|
4973
|
+
opacity: 0.85,
|
|
4974
|
+
pointerEvents: "none"
|
|
4975
|
+
}
|
|
4976
|
+
),
|
|
4977
|
+
alignGuides?.y && /* @__PURE__ */ jsx11(
|
|
4978
|
+
"line",
|
|
4979
|
+
{
|
|
4980
|
+
y1: alignGuides.y.pos,
|
|
4981
|
+
y2: alignGuides.y.pos,
|
|
4982
|
+
x1: alignGuides.y.minX,
|
|
4983
|
+
x2: alignGuides.y.maxX,
|
|
4984
|
+
stroke: acc.color,
|
|
4985
|
+
strokeWidth: 1 / transform.scale,
|
|
4986
|
+
strokeDasharray: `${4 / transform.scale} ${3 / transform.scale}`,
|
|
4987
|
+
opacity: 0.85,
|
|
4988
|
+
pointerEvents: "none"
|
|
4989
|
+
}
|
|
4990
|
+
),
|
|
4991
|
+
model.nodes.map((node, idx) => {
|
|
4992
|
+
const isHovered = hoveredId === node.id;
|
|
4993
|
+
const isQuestion2 = variant === "question";
|
|
4994
|
+
const { w: nW } = nodeDims(node, variant);
|
|
4995
|
+
const isSelected = selectedSet.has(node.id);
|
|
4996
|
+
return /* @__PURE__ */ jsxs11(
|
|
4997
|
+
"g",
|
|
4998
|
+
{
|
|
4999
|
+
transform: `translate(${node.x ?? 0},${node.y ?? 0})`,
|
|
5000
|
+
role: "button",
|
|
5001
|
+
tabIndex: 0,
|
|
5002
|
+
"aria-label": `${variantLabel} ${variant === "journey" ? idx + 1 + ": " : ""}${node.label}${isSelected ? ", selected" : ""}`,
|
|
5003
|
+
style: drag?.nodeId === node.id ? STYLE_NODE_GRABBING : STYLE_NODE_GRAB,
|
|
5004
|
+
onMouseDown: (e) => onNodeMouseDown(e, node.id),
|
|
5005
|
+
onMouseUp: (e) => onNodeMouseUp(e, node.id),
|
|
5006
|
+
onDoubleClick: (e) => onNodeDblClick(e, node.id),
|
|
5007
|
+
onContextMenu: (e) => onNodeContextMenu(e, node.id),
|
|
5008
|
+
onMouseEnter: () => setHoveredId(node.id),
|
|
5009
|
+
onMouseLeave: () => setHoveredId(null),
|
|
5010
|
+
onFocus: () => setHoveredId(node.id),
|
|
5011
|
+
onBlur: () => setHoveredId(null),
|
|
5012
|
+
onKeyDown: (e) => {
|
|
5013
|
+
if (e.key === "F2" || e.key === "Enter" && !e.ctrlKey && !e.metaKey) {
|
|
5014
|
+
e.preventDefault();
|
|
5015
|
+
setEditingId(node.id);
|
|
5016
|
+
setEditLabel(node.label);
|
|
5017
|
+
}
|
|
5018
|
+
},
|
|
5019
|
+
children: [
|
|
5020
|
+
/* @__PURE__ */ jsx11("title", { children: `${variantLabel}: ${node.label}` }),
|
|
5021
|
+
isQuestion2 ? /* @__PURE__ */ jsx11(
|
|
5022
|
+
QuestionNode,
|
|
5023
|
+
{
|
|
5024
|
+
node,
|
|
5025
|
+
selected: isSelected,
|
|
5026
|
+
edges: model.edges,
|
|
5027
|
+
isDark,
|
|
5028
|
+
onAnswerPortDown,
|
|
5029
|
+
qW: nW
|
|
5030
|
+
}
|
|
5031
|
+
) : /* @__PURE__ */ jsxs11(Fragment4, { children: [
|
|
5032
|
+
/* @__PURE__ */ jsx11(
|
|
5033
|
+
NodeShape,
|
|
5034
|
+
{
|
|
5035
|
+
node,
|
|
5036
|
+
selected: isSelected,
|
|
5037
|
+
variant,
|
|
5038
|
+
stepNumber: variant === "journey" ? idx + 1 : void 0,
|
|
5039
|
+
t,
|
|
5040
|
+
isDark,
|
|
5041
|
+
w: nW
|
|
5042
|
+
}
|
|
5043
|
+
),
|
|
5044
|
+
editingId === node.id ? /* @__PURE__ */ jsx11("foreignObject", { x: 6, y: 6, width: nW - 12, height: NODE_H2 - 12, children: /* @__PURE__ */ jsx11(
|
|
5045
|
+
"input",
|
|
5046
|
+
{
|
|
5047
|
+
autoFocus: true,
|
|
5048
|
+
value: editLabel,
|
|
5049
|
+
onChange: (e) => setEditLabel(e.target.value),
|
|
5050
|
+
onBlur: commitEdit,
|
|
5051
|
+
onKeyDown: (e) => {
|
|
5052
|
+
if (e.key === "Enter") commitEdit();
|
|
5053
|
+
if (e.key === "Escape") setEditingId(null);
|
|
5054
|
+
},
|
|
5055
|
+
style: {
|
|
5056
|
+
width: "100%",
|
|
5057
|
+
height: "100%",
|
|
5058
|
+
border: "none",
|
|
5059
|
+
borderRadius: 6,
|
|
5060
|
+
outline: `2px solid ${acc.color}`,
|
|
5061
|
+
textAlign: "center",
|
|
5062
|
+
fontSize: 13,
|
|
5063
|
+
fontWeight: 500,
|
|
5064
|
+
background: t.inputBg,
|
|
5065
|
+
boxSizing: "border-box",
|
|
5066
|
+
padding: "0 6px",
|
|
5067
|
+
fontFamily: "inherit",
|
|
5068
|
+
color: t.inputText
|
|
5069
|
+
}
|
|
5070
|
+
}
|
|
5071
|
+
) }) : /* @__PURE__ */ jsx11(
|
|
5072
|
+
"text",
|
|
5073
|
+
{
|
|
5074
|
+
x: nW / 2,
|
|
5075
|
+
y: NODE_H2 / 2 + 5,
|
|
5076
|
+
textAnchor: "middle",
|
|
5077
|
+
fontSize: 13,
|
|
5078
|
+
fontWeight: "500",
|
|
5079
|
+
fontFamily: "ui-sans-serif,system-ui,sans-serif",
|
|
5080
|
+
fill: isSelected ? acc.color : t.textPrimary,
|
|
5081
|
+
style: STYLE_LABEL2,
|
|
5082
|
+
children: node.label
|
|
5083
|
+
}
|
|
5084
|
+
),
|
|
5085
|
+
/* @__PURE__ */ jsx11(
|
|
5086
|
+
"circle",
|
|
5087
|
+
{
|
|
5088
|
+
cx: nW / 2,
|
|
5089
|
+
cy: NODE_H2 + 1,
|
|
5090
|
+
r: portR,
|
|
5091
|
+
fill: acc.color,
|
|
5092
|
+
stroke: isDark ? "#0f172a" : "white",
|
|
5093
|
+
strokeWidth: 2,
|
|
5094
|
+
style: isHovered || isCoarse ? STYLE_PORT_VISIBLE : STYLE_PORT_HIDDEN,
|
|
5095
|
+
onMouseDown: (e) => onPortMouseDown(e, node.id)
|
|
5096
|
+
}
|
|
5097
|
+
)
|
|
5098
|
+
] }),
|
|
5099
|
+
liveEdge && liveEdge.fromId !== node.id && /* @__PURE__ */ jsx11(
|
|
5100
|
+
"circle",
|
|
5101
|
+
{
|
|
5102
|
+
cx: nW / 2,
|
|
5103
|
+
cy: -1,
|
|
5104
|
+
r: portR,
|
|
5105
|
+
fill: acc.color,
|
|
5106
|
+
stroke: isDark ? "#0f172a" : "white",
|
|
5107
|
+
strokeWidth: 2,
|
|
5108
|
+
style: STYLE_LIVE_PORT
|
|
5109
|
+
}
|
|
5110
|
+
)
|
|
5111
|
+
]
|
|
5112
|
+
},
|
|
5113
|
+
node.id
|
|
5114
|
+
);
|
|
5115
|
+
})
|
|
5116
|
+
] })
|
|
5117
|
+
]
|
|
3740
5118
|
}
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
5119
|
+
),
|
|
5120
|
+
boxSel && Math.abs(boxSel.cx - boxSel.sx) + Math.abs(boxSel.cy - boxSel.sy) > 4 && containerRef.current && (() => {
|
|
5121
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
5122
|
+
const left = Math.min(boxSel.sx, boxSel.cx) - rect.left;
|
|
5123
|
+
const top = Math.min(boxSel.sy, boxSel.cy) - rect.top;
|
|
5124
|
+
const w = Math.abs(boxSel.cx - boxSel.sx);
|
|
5125
|
+
const h = Math.abs(boxSel.cy - boxSel.sy);
|
|
5126
|
+
return /* @__PURE__ */ jsx11(
|
|
5127
|
+
"div",
|
|
5128
|
+
{
|
|
5129
|
+
style: {
|
|
5130
|
+
position: "absolute",
|
|
5131
|
+
left,
|
|
5132
|
+
top,
|
|
5133
|
+
width: w,
|
|
5134
|
+
height: h,
|
|
5135
|
+
border: `1px dashed ${acc.color}`,
|
|
5136
|
+
background: isDark ? "rgba(99,102,241,0.10)" : "rgba(99,102,241,0.08)",
|
|
5137
|
+
pointerEvents: "none",
|
|
5138
|
+
borderRadius: 4
|
|
5139
|
+
}
|
|
5140
|
+
}
|
|
5141
|
+
);
|
|
5142
|
+
})(),
|
|
5143
|
+
model.nodes.length === 0 && /* @__PURE__ */ jsxs11(
|
|
5144
|
+
"div",
|
|
5145
|
+
{
|
|
5146
|
+
style: {
|
|
5147
|
+
position: "absolute",
|
|
5148
|
+
inset: 0,
|
|
5149
|
+
display: "flex",
|
|
5150
|
+
flexDirection: "column",
|
|
5151
|
+
alignItems: "center",
|
|
5152
|
+
justifyContent: "center",
|
|
5153
|
+
pointerEvents: "none",
|
|
5154
|
+
gap: 8
|
|
5155
|
+
},
|
|
5156
|
+
children: [
|
|
5157
|
+
/* @__PURE__ */ jsx11("div", { style: { fontSize: 36, opacity: 0.1, color: t.textPrimary }, children: variant === "question" ? "?" : variant === "journey" ? "\u2197" : "\u2B21" }),
|
|
5158
|
+
/* @__PURE__ */ jsxs11("div", { style: { fontSize: 13, color: t.textMuted, fontWeight: 500 }, children: [
|
|
5159
|
+
"Click ",
|
|
5160
|
+
/* @__PURE__ */ jsxs11("strong", { style: { color: acc.color }, children: [
|
|
5161
|
+
"+ ",
|
|
5162
|
+
variantLabel
|
|
5163
|
+
] }),
|
|
5164
|
+
" to start"
|
|
5165
|
+
] })
|
|
5166
|
+
]
|
|
5167
|
+
}
|
|
5168
|
+
),
|
|
5169
|
+
model.nodes.length > 0 && viewport.w > 0 && /* @__PURE__ */ jsx11(
|
|
5170
|
+
Minimap,
|
|
5171
|
+
{
|
|
5172
|
+
model,
|
|
5173
|
+
viewportW: viewport.w,
|
|
5174
|
+
viewportH: viewport.h,
|
|
5175
|
+
transform,
|
|
5176
|
+
isDark,
|
|
5177
|
+
accentColor: acc.color,
|
|
5178
|
+
measureNode: (n) => nodeDims(n, variant),
|
|
5179
|
+
onCenterOn: (cx, cy) => {
|
|
5180
|
+
setTransform((tr) => ({
|
|
5181
|
+
...tr,
|
|
5182
|
+
x: viewport.w / 2 - cx * tr.scale,
|
|
5183
|
+
y: viewport.h / 2 - cy * tr.scale
|
|
5184
|
+
}));
|
|
5185
|
+
}
|
|
5186
|
+
}
|
|
5187
|
+
),
|
|
5188
|
+
ctxMenu && /* @__PURE__ */ jsx11(
|
|
5189
|
+
ContextMenu,
|
|
5190
|
+
{
|
|
5191
|
+
x: ctxMenu.x,
|
|
5192
|
+
y: ctxMenu.y,
|
|
5193
|
+
nodeId: ctxMenu.nodeId,
|
|
5194
|
+
edgeId: ctxMenu.edgeId,
|
|
5195
|
+
isDark,
|
|
5196
|
+
t,
|
|
5197
|
+
acc,
|
|
5198
|
+
canUndo: history.canUndo,
|
|
5199
|
+
canRedo: history.canRedo,
|
|
5200
|
+
onUndo: onCtxUndo,
|
|
5201
|
+
onRedo: onCtxRedo,
|
|
5202
|
+
onReCenter: onCtxReCenter,
|
|
5203
|
+
onAddNode: onCtxAddNode,
|
|
5204
|
+
onDuplicate: onCtxDuplicate,
|
|
5205
|
+
onRename: onCtxRename,
|
|
5206
|
+
onDelete: onCtxDelete,
|
|
5207
|
+
onDisconnect: onCtxDisconnect,
|
|
5208
|
+
currentEdgeStyle: ctxEdgeStyle,
|
|
5209
|
+
currentEdgeArrow: ctxEdgeArrow,
|
|
5210
|
+
edgeHasWaypoint: ctxEdgeHasWaypoint,
|
|
5211
|
+
onEdgeRename: onCtxEdgeRename,
|
|
5212
|
+
onEdgeStyle: onCtxEdgeStyle,
|
|
5213
|
+
onEdgeArrowhead: onCtxEdgeArrowhead,
|
|
5214
|
+
onEdgeDelete: onCtxEdgeDelete,
|
|
5215
|
+
onEdgeResetRouting: onCtxEdgeResetRouting,
|
|
5216
|
+
containerRef
|
|
5217
|
+
}
|
|
5218
|
+
)
|
|
5219
|
+
]
|
|
5220
|
+
}
|
|
5221
|
+
);
|
|
3802
5222
|
}
|
|
3803
5223
|
|
|
3804
5224
|
// src/ui/hooks/useHistory.ts
|
|
3805
|
-
import { useCallback as
|
|
5225
|
+
import { useCallback as useCallback6, useRef as useRef6, useState as useState10 } from "react";
|
|
3806
5226
|
var MAX_HISTORY = 80;
|
|
3807
5227
|
function useHistory(initial, onChange) {
|
|
3808
5228
|
const [state, setState] = useState10(initial);
|
|
@@ -3810,14 +5230,14 @@ function useHistory(initial, onChange) {
|
|
|
3810
5230
|
const idxRef = useRef6(0);
|
|
3811
5231
|
const [, setTick] = useState10(0);
|
|
3812
5232
|
const bump = () => setTick((n) => n + 1);
|
|
3813
|
-
const apply =
|
|
5233
|
+
const apply = useCallback6(
|
|
3814
5234
|
(next) => {
|
|
3815
5235
|
setState(next);
|
|
3816
5236
|
onChange?.(next);
|
|
3817
5237
|
},
|
|
3818
5238
|
[onChange]
|
|
3819
5239
|
);
|
|
3820
|
-
const applyAndPush =
|
|
5240
|
+
const applyAndPush = useCallback6(
|
|
3821
5241
|
(next) => {
|
|
3822
5242
|
const stack = stackRef.current.slice(0, idxRef.current + 1);
|
|
3823
5243
|
stack.push(next);
|
|
@@ -3830,7 +5250,7 @@ function useHistory(initial, onChange) {
|
|
|
3830
5250
|
},
|
|
3831
5251
|
[onChange]
|
|
3832
5252
|
);
|
|
3833
|
-
const undo =
|
|
5253
|
+
const undo = useCallback6(() => {
|
|
3834
5254
|
if (idxRef.current <= 0) return;
|
|
3835
5255
|
idxRef.current--;
|
|
3836
5256
|
const next = stackRef.current[idxRef.current];
|
|
@@ -3838,7 +5258,7 @@ function useHistory(initial, onChange) {
|
|
|
3838
5258
|
onChange?.(next);
|
|
3839
5259
|
bump();
|
|
3840
5260
|
}, [onChange]);
|
|
3841
|
-
const redo =
|
|
5261
|
+
const redo = useCallback6(() => {
|
|
3842
5262
|
if (idxRef.current >= stackRef.current.length - 1) return;
|
|
3843
5263
|
idxRef.current++;
|
|
3844
5264
|
const next = stackRef.current[idxRef.current];
|
|
@@ -3995,7 +5415,18 @@ function useCanvasTouch(ref, {
|
|
|
3995
5415
|
el.removeEventListener("touchend", onEnd);
|
|
3996
5416
|
el.removeEventListener("touchcancel", onEnd);
|
|
3997
5417
|
};
|
|
3998
|
-
}, [
|
|
5418
|
+
}, [
|
|
5419
|
+
ref,
|
|
5420
|
+
transform.scale,
|
|
5421
|
+
transform.x,
|
|
5422
|
+
transform.y,
|
|
5423
|
+
setTransform,
|
|
5424
|
+
onLongPress,
|
|
5425
|
+
minScale,
|
|
5426
|
+
maxScale,
|
|
5427
|
+
longPressMs,
|
|
5428
|
+
longPressSlop
|
|
5429
|
+
]);
|
|
3999
5430
|
}
|
|
4000
5431
|
|
|
4001
5432
|
// src/ui/hooks/useElementSize.ts
|
|
@@ -4109,7 +5540,17 @@ function nearestInDirection(fromX, fromY, dir, candidates) {
|
|
|
4109
5540
|
|
|
4110
5541
|
// src/ui/DiagramEditor.tsx
|
|
4111
5542
|
import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
4112
|
-
var STYLE_SR_ONLY = {
|
|
5543
|
+
var STYLE_SR_ONLY = {
|
|
5544
|
+
position: "absolute",
|
|
5545
|
+
width: 1,
|
|
5546
|
+
height: 1,
|
|
5547
|
+
padding: 0,
|
|
5548
|
+
margin: -1,
|
|
5549
|
+
overflow: "hidden",
|
|
5550
|
+
clip: "rect(0 0 0 0)",
|
|
5551
|
+
whiteSpace: "nowrap",
|
|
5552
|
+
border: 0
|
|
5553
|
+
};
|
|
4113
5554
|
var STYLE_FLEX_ROW = { flex: 1, display: "flex", overflow: "hidden" };
|
|
4114
5555
|
function DiagramEditor(props) {
|
|
4115
5556
|
if (props.initialModel?.type === "sequence") {
|
|
@@ -4141,7 +5582,7 @@ function FlowchartEditor({
|
|
|
4141
5582
|
themeOverrides
|
|
4142
5583
|
}) {
|
|
4143
5584
|
const base = initialModel ? { ...initialModel, variant: initialModel.variant ?? variant } : presetFlowchartModel(variant);
|
|
4144
|
-
const notify =
|
|
5585
|
+
const notify = useCallback7((m) => onChange?.(m), [onChange]);
|
|
4145
5586
|
const history = useHistory(base, notify);
|
|
4146
5587
|
const { state: model, apply: applyModel, applyAndPush, undo, redo } = history;
|
|
4147
5588
|
const { toasts, showToast, dismissToast } = useToast();
|
|
@@ -4156,16 +5597,16 @@ function FlowchartEditor({
|
|
|
4156
5597
|
const [waypointDrag, setWaypointDrag] = useState12(null);
|
|
4157
5598
|
const groupDragOriginsRef = useRef7(null);
|
|
4158
5599
|
const clipboardRef = useRef7(null);
|
|
4159
|
-
const selectOne =
|
|
5600
|
+
const selectOne = useCallback7((id) => {
|
|
4160
5601
|
setSelected(id);
|
|
4161
5602
|
setSelectedSet(id ? /* @__PURE__ */ new Set([id]) : /* @__PURE__ */ new Set());
|
|
4162
5603
|
}, []);
|
|
4163
|
-
const toggleSelect =
|
|
5604
|
+
const toggleSelect = useCallback7((id) => {
|
|
4164
5605
|
setSelectedSet((prev) => {
|
|
4165
5606
|
const next = new Set(prev);
|
|
4166
5607
|
if (next.has(id)) {
|
|
4167
5608
|
next.delete(id);
|
|
4168
|
-
const last = next.size ? Array.from(next)[next.size - 1] : null;
|
|
5609
|
+
const last = next.size ? Array.from(next)[next.size - 1] ?? null : null;
|
|
4169
5610
|
setSelected(last);
|
|
4170
5611
|
} else {
|
|
4171
5612
|
next.add(id);
|
|
@@ -4174,7 +5615,7 @@ function FlowchartEditor({
|
|
|
4174
5615
|
return next;
|
|
4175
5616
|
});
|
|
4176
5617
|
}, []);
|
|
4177
|
-
const clearSelection =
|
|
5618
|
+
const clearSelection = useCallback7(() => {
|
|
4178
5619
|
setSelected(null);
|
|
4179
5620
|
setSelectedSet(/* @__PURE__ */ new Set());
|
|
4180
5621
|
}, []);
|
|
@@ -4189,11 +5630,14 @@ function FlowchartEditor({
|
|
|
4189
5630
|
const svgRef = useRef7(null);
|
|
4190
5631
|
const containerRef = useRef7(null);
|
|
4191
5632
|
const reducedMotion = usePrefersReducedMotion();
|
|
4192
|
-
const { t, isDark } = useEditorTheme(theme, themeOverrides, {
|
|
5633
|
+
const { t, isDark } = useEditorTheme(theme, themeOverrides, {
|
|
5634
|
+
light: lightTheme,
|
|
5635
|
+
dark: darkTheme
|
|
5636
|
+
});
|
|
4193
5637
|
const isCoarse = useIsCoarsePointer();
|
|
4194
5638
|
const portR = isCoarse ? 9 : 6;
|
|
4195
5639
|
const viewport = useElementSize(svgRef);
|
|
4196
|
-
const reCenter =
|
|
5640
|
+
const reCenter = useCallback7(() => {
|
|
4197
5641
|
if (!svgRef.current) return;
|
|
4198
5642
|
const rect = svgRef.current.getBoundingClientRect();
|
|
4199
5643
|
const W2 = rect.width, H2 = rect.height;
|
|
@@ -4217,52 +5661,65 @@ function FlowchartEditor({
|
|
|
4217
5661
|
const cx = (minX + maxX) / 2, cy = (minY + maxY) / 2;
|
|
4218
5662
|
setTransform({ scale, x: W2 / 2 - cx * scale, y: H2 / 2 - cy * scale });
|
|
4219
5663
|
}, [model.nodes, variant]);
|
|
4220
|
-
const jumpToNode =
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
const
|
|
4240
|
-
|
|
4241
|
-
const
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
newEdges.push({ ...e, id: nextEdge(), from: idMap.get(e.from), to: idMap.get(e.to) });
|
|
5664
|
+
const jumpToNode = useCallback7(
|
|
5665
|
+
(nodeId) => {
|
|
5666
|
+
const node = model.nodes.find((n) => n.id === nodeId);
|
|
5667
|
+
if (!node || !svgRef.current) return;
|
|
5668
|
+
const rect = svgRef.current.getBoundingClientRect();
|
|
5669
|
+
const { w: nw, h: nh } = nodeDims(node, variant);
|
|
5670
|
+
const cx = (node.x ?? 0) + nw / 2;
|
|
5671
|
+
const cy = (node.y ?? 0) + nh / 2;
|
|
5672
|
+
const scale = Math.min(Math.max(transform.scale, 0.8), 1.4);
|
|
5673
|
+
setTransform({ scale, x: rect.width / 2 - cx * scale, y: rect.height / 2 - cy * scale });
|
|
5674
|
+
selectOne(nodeId);
|
|
5675
|
+
},
|
|
5676
|
+
[model.nodes, variant, transform.scale, selectOne]
|
|
5677
|
+
);
|
|
5678
|
+
const duplicateIds = useCallback7(
|
|
5679
|
+
(ids) => {
|
|
5680
|
+
if (ids.length === 0) return;
|
|
5681
|
+
const idSet = new Set(ids);
|
|
5682
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
5683
|
+
const nextNode = makeIdSource("node", model.nodes);
|
|
5684
|
+
const nextEdge = makeIdSource("e", model.edges);
|
|
5685
|
+
const newNodes = [];
|
|
5686
|
+
for (const oldId of ids) {
|
|
5687
|
+
const n = model.nodes.find((x) => x.id === oldId);
|
|
5688
|
+
if (!n) continue;
|
|
5689
|
+
const newId = nextNode();
|
|
5690
|
+
idMap.set(oldId, newId);
|
|
5691
|
+
newNodes.push({
|
|
5692
|
+
...n,
|
|
5693
|
+
id: newId,
|
|
5694
|
+
label: ids.length === 1 ? n.label + " (copy)" : n.label,
|
|
5695
|
+
x: (n.x ?? 0) + 32,
|
|
5696
|
+
y: (n.y ?? 0) + 32
|
|
5697
|
+
});
|
|
4255
5698
|
}
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
5699
|
+
const newEdges = [];
|
|
5700
|
+
for (const e of model.edges) {
|
|
5701
|
+
if (idSet.has(e.from) && idSet.has(e.to)) {
|
|
5702
|
+
newEdges.push({ ...e, id: nextEdge(), from: idMap.get(e.from), to: idMap.get(e.to) });
|
|
5703
|
+
}
|
|
5704
|
+
}
|
|
5705
|
+
const m = {
|
|
5706
|
+
...model,
|
|
5707
|
+
nodes: [...model.nodes, ...newNodes],
|
|
5708
|
+
edges: [...model.edges, ...newEdges]
|
|
5709
|
+
};
|
|
5710
|
+
applyAndPush(m);
|
|
5711
|
+
const newIds = newNodes.map((n) => n.id);
|
|
5712
|
+
setSelected(newIds[newIds.length - 1] ?? null);
|
|
5713
|
+
setSelectedSet(new Set(newIds));
|
|
5714
|
+
},
|
|
5715
|
+
[model, applyAndPush]
|
|
5716
|
+
);
|
|
5717
|
+
const duplicateNode = useCallback7(
|
|
5718
|
+
(nodeId) => {
|
|
5719
|
+
duplicateIds([nodeId]);
|
|
5720
|
+
},
|
|
5721
|
+
[duplicateIds]
|
|
5722
|
+
);
|
|
4266
5723
|
useEffect10(() => {
|
|
4267
5724
|
if (!ctxMenu) return;
|
|
4268
5725
|
const close = () => setCtxMenu(null);
|
|
@@ -4270,18 +5727,27 @@ function FlowchartEditor({
|
|
|
4270
5727
|
return () => window.removeEventListener("mousedown", close);
|
|
4271
5728
|
}, [ctxMenu]);
|
|
4272
5729
|
const keyCommands = [
|
|
4273
|
-
{
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
5730
|
+
{
|
|
5731
|
+
match: (e) => (e.ctrlKey || e.metaKey) && e.key === "z" && !e.shiftKey,
|
|
5732
|
+
run: () => {
|
|
5733
|
+
undo();
|
|
5734
|
+
return true;
|
|
5735
|
+
}
|
|
5736
|
+
},
|
|
5737
|
+
{
|
|
5738
|
+
match: (e) => (e.ctrlKey || e.metaKey) && (e.key === "y" || e.shiftKey && e.key === "z"),
|
|
5739
|
+
run: () => {
|
|
5740
|
+
redo();
|
|
5741
|
+
return true;
|
|
5742
|
+
}
|
|
5743
|
+
},
|
|
5744
|
+
{
|
|
5745
|
+
match: (e) => (e.ctrlKey || e.metaKey) && e.key === "0",
|
|
5746
|
+
run: () => {
|
|
5747
|
+
reCenter();
|
|
5748
|
+
return true;
|
|
5749
|
+
}
|
|
5750
|
+
},
|
|
4285
5751
|
{
|
|
4286
5752
|
match: (e) => (e.ctrlKey || e.metaKey) && (e.key === "d" || e.key === "D") && selectedSet.size > 0,
|
|
4287
5753
|
run: () => {
|
|
@@ -4321,12 +5787,18 @@ function FlowchartEditor({
|
|
|
4321
5787
|
from: idMap.get(ed.from) ?? ed.from,
|
|
4322
5788
|
to: idMap.get(ed.to) ?? ed.to
|
|
4323
5789
|
}));
|
|
4324
|
-
const m = {
|
|
5790
|
+
const m = {
|
|
5791
|
+
...model,
|
|
5792
|
+
nodes: [...model.nodes, ...newNodes],
|
|
5793
|
+
edges: [...model.edges, ...newEdges]
|
|
5794
|
+
};
|
|
4325
5795
|
applyAndPush(m);
|
|
4326
5796
|
const newIds = newNodes.map((n) => n.id);
|
|
4327
|
-
setSelected(newIds[newIds.length - 1]);
|
|
5797
|
+
setSelected(newIds[newIds.length - 1] ?? null);
|
|
4328
5798
|
setSelectedSet(new Set(newIds));
|
|
4329
|
-
setAnnouncement(
|
|
5799
|
+
setAnnouncement(
|
|
5800
|
+
`Pasted ${newIds.length} ${variantLabel.toLowerCase()}${newIds.length === 1 ? "" : "s"}.`
|
|
5801
|
+
);
|
|
4330
5802
|
return true;
|
|
4331
5803
|
}
|
|
4332
5804
|
},
|
|
@@ -4352,7 +5824,9 @@ function FlowchartEditor({
|
|
|
4352
5824
|
};
|
|
4353
5825
|
applyAndPush(updated);
|
|
4354
5826
|
clearSelection();
|
|
4355
|
-
setAnnouncement(
|
|
5827
|
+
setAnnouncement(
|
|
5828
|
+
`Deleted ${ids.size} ${variantLabel.toLowerCase()}${ids.size === 1 ? "" : "s"}.`
|
|
5829
|
+
);
|
|
4356
5830
|
return true;
|
|
4357
5831
|
}
|
|
4358
5832
|
},
|
|
@@ -4387,20 +5861,42 @@ function FlowchartEditor({
|
|
|
4387
5861
|
const ids = selectedSet;
|
|
4388
5862
|
const updated = {
|
|
4389
5863
|
...model,
|
|
4390
|
-
nodes: model.nodes.map(
|
|
5864
|
+
nodes: model.nodes.map(
|
|
5865
|
+
(n) => ids.has(n.id) ? { ...n, x: snap((n.x ?? 0) + dx), y: snap((n.y ?? 0) + dy) } : n
|
|
5866
|
+
)
|
|
4391
5867
|
};
|
|
4392
5868
|
applyAndPush(updated);
|
|
4393
5869
|
return true;
|
|
4394
5870
|
}
|
|
4395
5871
|
}
|
|
4396
5872
|
];
|
|
4397
|
-
useEditorKeyboard(keyCommands, [
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
5873
|
+
useEditorKeyboard(keyCommands, [
|
|
5874
|
+
undo,
|
|
5875
|
+
redo,
|
|
5876
|
+
reCenter,
|
|
5877
|
+
selected,
|
|
5878
|
+
selectedSet,
|
|
5879
|
+
ctxMenu,
|
|
5880
|
+
liveEdge,
|
|
5881
|
+
editingId,
|
|
5882
|
+
boxSel,
|
|
5883
|
+
model,
|
|
5884
|
+
applyAndPush,
|
|
5885
|
+
duplicateNode,
|
|
5886
|
+
clearSelection
|
|
5887
|
+
]);
|
|
5888
|
+
const toCanvas = useCallback7(
|
|
5889
|
+
(clientX, clientY) => {
|
|
5890
|
+
const rect = svgRef.current.getBoundingClientRect();
|
|
5891
|
+
return {
|
|
5892
|
+
x: (clientX - rect.left - transform.x) / transform.scale,
|
|
5893
|
+
y: (clientY - rect.top - transform.y) / transform.scale
|
|
5894
|
+
};
|
|
5895
|
+
},
|
|
5896
|
+
[transform]
|
|
5897
|
+
);
|
|
4402
5898
|
useCanvasWheel(svgRef, setTransform);
|
|
4403
|
-
const onCanvasLongPress =
|
|
5899
|
+
const onCanvasLongPress = useCallback7((x, y) => {
|
|
4404
5900
|
setCtxMenu({ x, y, nodeId: null });
|
|
4405
5901
|
}, []);
|
|
4406
5902
|
useCanvasTouch(svgRef, { transform, setTransform, onLongPress: onCanvasLongPress });
|
|
@@ -4409,13 +5905,28 @@ function FlowchartEditor({
|
|
|
4409
5905
|
const node = model.nodes.find((n) => n.id === nodeId);
|
|
4410
5906
|
const { x, y } = toCanvas(e.clientX, e.clientY);
|
|
4411
5907
|
const nW = nodeWidth2(node.label);
|
|
4412
|
-
setLiveEdge({
|
|
5908
|
+
setLiveEdge({
|
|
5909
|
+
fromId: nodeId,
|
|
5910
|
+
fromX: (node.x ?? 0) + nW / 2,
|
|
5911
|
+
fromY: (node.y ?? 0) + NODE_H2,
|
|
5912
|
+
exitDir: "bottom",
|
|
5913
|
+
toX: x,
|
|
5914
|
+
toY: y
|
|
5915
|
+
});
|
|
4413
5916
|
};
|
|
4414
5917
|
const onAnswerPortDown = (e, nodeId, answer, portXInNode, portYInNode) => {
|
|
4415
5918
|
e.stopPropagation();
|
|
4416
5919
|
const node = model.nodes.find((n) => n.id === nodeId);
|
|
4417
5920
|
const { x, y } = toCanvas(e.clientX, e.clientY);
|
|
4418
|
-
setLiveEdge({
|
|
5921
|
+
setLiveEdge({
|
|
5922
|
+
fromId: nodeId,
|
|
5923
|
+
fromX: (node.x ?? 0) + portXInNode,
|
|
5924
|
+
fromY: (node.y ?? 0) + portYInNode,
|
|
5925
|
+
exitDir: "bottom",
|
|
5926
|
+
answerLabel: answer,
|
|
5927
|
+
toX: x,
|
|
5928
|
+
toY: y
|
|
5929
|
+
});
|
|
4419
5930
|
};
|
|
4420
5931
|
const onNodeMouseDown = (e, id) => {
|
|
4421
5932
|
e.stopPropagation();
|
|
@@ -4442,7 +5953,11 @@ function FlowchartEditor({
|
|
|
4442
5953
|
selectOne(id);
|
|
4443
5954
|
groupDragOriginsRef.current = null;
|
|
4444
5955
|
}
|
|
4445
|
-
setDrag({
|
|
5956
|
+
setDrag({
|
|
5957
|
+
nodeId: id,
|
|
5958
|
+
ox: e.clientX - (transform.x + (node.x ?? 0) * transform.scale),
|
|
5959
|
+
oy: e.clientY - (transform.y + (node.y ?? 0) * transform.scale)
|
|
5960
|
+
});
|
|
4446
5961
|
};
|
|
4447
5962
|
const onNodeMouseUp = (e, targetId) => {
|
|
4448
5963
|
if (!liveEdge || liveEdge.fromId === targetId) return;
|
|
@@ -4452,12 +5967,27 @@ function FlowchartEditor({
|
|
|
4452
5967
|
if (label) {
|
|
4453
5968
|
const existing = model.edges.find((ex) => ex.from === liveEdge.fromId && ex.label === label);
|
|
4454
5969
|
if (existing) {
|
|
4455
|
-
updated = {
|
|
5970
|
+
updated = {
|
|
5971
|
+
...model,
|
|
5972
|
+
edges: model.edges.map((ex) => ex.id === existing.id ? { ...ex, to: targetId } : ex)
|
|
5973
|
+
};
|
|
4456
5974
|
} else {
|
|
4457
|
-
updated = {
|
|
5975
|
+
updated = {
|
|
5976
|
+
...model,
|
|
5977
|
+
edges: [
|
|
5978
|
+
...model.edges,
|
|
5979
|
+
{ id: nextId("e", model.edges), from: liveEdge.fromId, to: targetId, label }
|
|
5980
|
+
]
|
|
5981
|
+
};
|
|
4458
5982
|
}
|
|
4459
5983
|
} else {
|
|
4460
|
-
updated = {
|
|
5984
|
+
updated = {
|
|
5985
|
+
...model,
|
|
5986
|
+
edges: [
|
|
5987
|
+
...model.edges,
|
|
5988
|
+
{ id: nextId("e", model.edges), from: liveEdge.fromId, to: targetId }
|
|
5989
|
+
]
|
|
5990
|
+
};
|
|
4461
5991
|
}
|
|
4462
5992
|
applyAndPush(updated);
|
|
4463
5993
|
setLiveEdge(null);
|
|
@@ -4497,7 +6027,9 @@ function FlowchartEditor({
|
|
|
4497
6027
|
const wx = snap(x), wy = snap(y);
|
|
4498
6028
|
const updated = {
|
|
4499
6029
|
...model,
|
|
4500
|
-
edges: model.edges.map(
|
|
6030
|
+
edges: model.edges.map(
|
|
6031
|
+
(ed) => ed.id === waypointDrag ? { ...ed, waypoint: { x: wx, y: wy } } : ed
|
|
6032
|
+
)
|
|
4501
6033
|
};
|
|
4502
6034
|
applyModel(updated);
|
|
4503
6035
|
return;
|
|
@@ -4529,12 +6061,23 @@ function FlowchartEditor({
|
|
|
4529
6061
|
return { x: n.x ?? 0, y: n.y ?? 0, w: d.w, h: d.h };
|
|
4530
6062
|
});
|
|
4531
6063
|
const snapResult = findSiblingSnap({ x: dx, y: dy, w: dW, h: dH }, others);
|
|
4532
|
-
setAlignGuides(
|
|
4533
|
-
|
|
6064
|
+
setAlignGuides(
|
|
6065
|
+
snapResult.guideX || snapResult.guideY ? { x: snapResult.guideX, y: snapResult.guideY } : null
|
|
6066
|
+
);
|
|
6067
|
+
const updated = {
|
|
6068
|
+
...model,
|
|
6069
|
+
nodes: model.nodes.map(
|
|
6070
|
+
(n) => n.id === drag.nodeId ? { ...n, x: snapResult.x, y: snapResult.y } : n
|
|
6071
|
+
)
|
|
6072
|
+
};
|
|
4534
6073
|
applyModel(updated);
|
|
4535
6074
|
}
|
|
4536
6075
|
} else if (pan) {
|
|
4537
|
-
setTransform((tr) => ({
|
|
6076
|
+
setTransform((tr) => ({
|
|
6077
|
+
...tr,
|
|
6078
|
+
x: pan.tx + (e.clientX - pan.ox),
|
|
6079
|
+
y: pan.ty + (e.clientY - pan.oy)
|
|
6080
|
+
}));
|
|
4538
6081
|
} else if (boxSel) {
|
|
4539
6082
|
setBoxSel((b) => b ? { ...b, cx: e.clientX, cy: e.clientY } : null);
|
|
4540
6083
|
}
|
|
@@ -4560,7 +6103,7 @@ function FlowchartEditor({
|
|
|
4560
6103
|
}
|
|
4561
6104
|
const arr = Array.from(hits);
|
|
4562
6105
|
setSelectedSet(hits);
|
|
4563
|
-
setSelected(arr.length ? arr[arr.length - 1] : null);
|
|
6106
|
+
setSelected(arr.length ? arr[arr.length - 1] ?? null : null);
|
|
4564
6107
|
}
|
|
4565
6108
|
setBoxSel(null);
|
|
4566
6109
|
}
|
|
@@ -4583,7 +6126,10 @@ function FlowchartEditor({
|
|
|
4583
6126
|
};
|
|
4584
6127
|
const commitEdit = () => {
|
|
4585
6128
|
if (!editingId) return;
|
|
4586
|
-
const up = {
|
|
6129
|
+
const up = {
|
|
6130
|
+
...model,
|
|
6131
|
+
nodes: model.nodes.map((n) => n.id === editingId ? { ...n, label: editLabel } : n)
|
|
6132
|
+
};
|
|
4587
6133
|
applyAndPush(up);
|
|
4588
6134
|
setEditingId(null);
|
|
4589
6135
|
};
|
|
@@ -4592,20 +6138,28 @@ function FlowchartEditor({
|
|
|
4592
6138
|
const p = atCanvasPos ? { x: snap(atCanvasPos.x), y: snap(atCanvasPos.y) } : { x: snap(100 + Math.random() * 240), y: snap(100 + Math.random() * 180) };
|
|
4593
6139
|
const label = variant === "question" ? "New Question" : variant === "journey" ? `Step ${model.nodes.length + 1}` : "New Step";
|
|
4594
6140
|
const metadata = variant === "question" ? { answers: [] } : void 0;
|
|
4595
|
-
const updated = {
|
|
6141
|
+
const updated = {
|
|
6142
|
+
...model,
|
|
6143
|
+
nodes: [...model.nodes, { id, label, shape: "rectangle", metadata, ...p }]
|
|
6144
|
+
};
|
|
4596
6145
|
applyAndPush(updated);
|
|
4597
6146
|
selectOne(id);
|
|
4598
6147
|
setAnnouncement(`Added ${variantLabel.toLowerCase()} "${label}".`);
|
|
4599
6148
|
};
|
|
4600
6149
|
const deleteNode = (nodeId) => {
|
|
4601
6150
|
const node = model.nodes.find((n) => n.id === nodeId);
|
|
4602
|
-
const updated = {
|
|
6151
|
+
const updated = {
|
|
6152
|
+
...model,
|
|
6153
|
+
nodes: model.nodes.filter((n) => n.id !== nodeId),
|
|
6154
|
+
edges: model.edges.filter((e) => e.from !== nodeId && e.to !== nodeId)
|
|
6155
|
+
};
|
|
4603
6156
|
applyAndPush(updated);
|
|
4604
6157
|
if (selectedSet.has(nodeId)) {
|
|
4605
6158
|
const next = new Set(selectedSet);
|
|
4606
6159
|
next.delete(nodeId);
|
|
4607
6160
|
setSelectedSet(next);
|
|
4608
|
-
if (selected === nodeId)
|
|
6161
|
+
if (selected === nodeId)
|
|
6162
|
+
setSelected(next.size ? Array.from(next)[next.size - 1] ?? null : null);
|
|
4609
6163
|
}
|
|
4610
6164
|
if (node) setAnnouncement(`Deleted ${variantLabel.toLowerCase()} "${node.label}".`);
|
|
4611
6165
|
};
|
|
@@ -4637,7 +6191,9 @@ function FlowchartEditor({
|
|
|
4637
6191
|
const next = editEdgeLabel.trim();
|
|
4638
6192
|
const updated = {
|
|
4639
6193
|
...model,
|
|
4640
|
-
edges: model.edges.map(
|
|
6194
|
+
edges: model.edges.map(
|
|
6195
|
+
(e) => e.id === editingEdgeId ? { ...e, ...next ? { label: next } : { label: void 0 } } : e
|
|
6196
|
+
)
|
|
4641
6197
|
};
|
|
4642
6198
|
applyAndPush(updated);
|
|
4643
6199
|
setEditingEdgeId(null);
|
|
@@ -4648,11 +6204,17 @@ function FlowchartEditor({
|
|
|
4648
6204
|
setCtxMenu({ x: e.clientX, y: e.clientY, nodeId: null, edgeId });
|
|
4649
6205
|
};
|
|
4650
6206
|
const setEdgeStyle = (edgeId, style) => {
|
|
4651
|
-
const updated = {
|
|
6207
|
+
const updated = {
|
|
6208
|
+
...model,
|
|
6209
|
+
edges: model.edges.map((e) => e.id === edgeId ? { ...e, style } : e)
|
|
6210
|
+
};
|
|
4652
6211
|
applyAndPush(updated);
|
|
4653
6212
|
};
|
|
4654
6213
|
const setEdgeArrowhead = (edgeId, arrowhead) => {
|
|
4655
|
-
const updated = {
|
|
6214
|
+
const updated = {
|
|
6215
|
+
...model,
|
|
6216
|
+
edges: model.edges.map((e) => e.id === edgeId ? { ...e, arrowhead } : e)
|
|
6217
|
+
};
|
|
4656
6218
|
applyAndPush(updated);
|
|
4657
6219
|
};
|
|
4658
6220
|
const deleteEdge = (edgeId) => {
|
|
@@ -4672,14 +6234,17 @@ function FlowchartEditor({
|
|
|
4672
6234
|
applyAndPush(updated);
|
|
4673
6235
|
};
|
|
4674
6236
|
const handleExport = useExporters(model, onExport, "diagram", (msg) => showToast(msg, "success"));
|
|
4675
|
-
const positionFlowchartNodes =
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
6237
|
+
const positionFlowchartNodes = useCallback7(
|
|
6238
|
+
(m) => ({
|
|
6239
|
+
...m,
|
|
6240
|
+
nodes: m.nodes.map((n, i) => ({
|
|
6241
|
+
...n,
|
|
6242
|
+
x: n.x ?? snap(80 + i % 4 * 200),
|
|
6243
|
+
y: n.y ?? snap(80 + Math.floor(i / 4) * 140)
|
|
6244
|
+
}))
|
|
6245
|
+
}),
|
|
6246
|
+
[]
|
|
6247
|
+
);
|
|
4683
6248
|
const handleImport = useImporter(applyAndPush, {
|
|
4684
6249
|
transform: positionFlowchartNodes,
|
|
4685
6250
|
onSuccess: (msg) => showToast(msg, "success"),
|
|
@@ -4690,9 +6255,23 @@ function FlowchartEditor({
|
|
|
4690
6255
|
const shadowClr = shadowColor(isDark);
|
|
4691
6256
|
const arrowClr = arrowColor(isDark);
|
|
4692
6257
|
const amberArrow = isDark ? ACCENT.amberDark : ACCENT.amber;
|
|
4693
|
-
return /* @__PURE__ */ jsxs12(
|
|
4694
|
-
|
|
4695
|
-
|
|
6258
|
+
return /* @__PURE__ */ jsxs12(
|
|
6259
|
+
"div",
|
|
6260
|
+
{
|
|
6261
|
+
className: "fsd-editor",
|
|
6262
|
+
style: {
|
|
6263
|
+
display: "flex",
|
|
6264
|
+
flexDirection: "column",
|
|
6265
|
+
height,
|
|
6266
|
+
width: "100%",
|
|
6267
|
+
fontFamily: "ui-sans-serif,system-ui,sans-serif",
|
|
6268
|
+
boxSizing: "border-box",
|
|
6269
|
+
background: t.ctrlsBg,
|
|
6270
|
+
position: "relative"
|
|
6271
|
+
},
|
|
6272
|
+
children: [
|
|
6273
|
+
/* @__PURE__ */ jsx12(ToastContainer, { toasts, onDismiss: dismissToast }),
|
|
6274
|
+
/* @__PURE__ */ jsx12("style", { children: `
|
|
4696
6275
|
.fsd-editor button:focus-visible,
|
|
4697
6276
|
.fsd-editor input:focus-visible,
|
|
4698
6277
|
.fsd-editor textarea:focus-visible,
|
|
@@ -4711,209 +6290,293 @@ function FlowchartEditor({
|
|
|
4711
6290
|
outline-offset: -2px;
|
|
4712
6291
|
}
|
|
4713
6292
|
` }),
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
onNodeDblClick,
|
|
4795
|
-
onNodeContextMenu,
|
|
4796
|
-
onPortMouseDown,
|
|
4797
|
-
onAnswerPortDown,
|
|
4798
|
-
onSvgMouseDown,
|
|
4799
|
-
onMouseMove,
|
|
4800
|
-
onMouseUp,
|
|
4801
|
-
onSvgContextMenu,
|
|
4802
|
-
reducedMotion,
|
|
4803
|
-
isCoarse,
|
|
4804
|
-
portR,
|
|
4805
|
-
shadowClr,
|
|
4806
|
-
arrowClr,
|
|
4807
|
-
amberArrow,
|
|
4808
|
-
viewport,
|
|
4809
|
-
svgRef,
|
|
4810
|
-
containerRef,
|
|
4811
|
-
ctxMenu,
|
|
4812
|
-
history,
|
|
4813
|
-
ctxEdgeStyle: (ctxMenu?.edgeId ? model.edges.find((e) => e.id === ctxMenu.edgeId) : void 0)?.style ?? "solid",
|
|
4814
|
-
ctxEdgeArrow: (ctxMenu?.edgeId ? model.edges.find((e) => e.id === ctxMenu.edgeId) : void 0)?.arrowhead ?? "arrow",
|
|
4815
|
-
ctxEdgeHasWaypoint: !!(ctxMenu?.edgeId ? model.edges.find((e) => e.id === ctxMenu.edgeId) : void 0)?.waypoint,
|
|
4816
|
-
onCtxUndo: () => {
|
|
4817
|
-
undo();
|
|
4818
|
-
setCtxMenu(null);
|
|
4819
|
-
},
|
|
4820
|
-
onCtxRedo: () => {
|
|
4821
|
-
redo();
|
|
4822
|
-
setCtxMenu(null);
|
|
4823
|
-
},
|
|
4824
|
-
onCtxReCenter: () => {
|
|
4825
|
-
reCenter();
|
|
4826
|
-
setCtxMenu(null);
|
|
4827
|
-
},
|
|
4828
|
-
onCtxAddNode: () => {
|
|
4829
|
-
const rect = svgRef.current.getBoundingClientRect();
|
|
4830
|
-
const cx = (ctxMenu.x - rect.left - transform.x) / transform.scale;
|
|
4831
|
-
const cy = (ctxMenu.y - rect.top - transform.y) / transform.scale;
|
|
4832
|
-
addNode({ x: cx, y: cy });
|
|
4833
|
-
setCtxMenu(null);
|
|
4834
|
-
},
|
|
4835
|
-
onCtxDuplicate: () => {
|
|
4836
|
-
if (ctxMenu?.nodeId) {
|
|
4837
|
-
duplicateNode(ctxMenu.nodeId);
|
|
4838
|
-
setCtxMenu(null);
|
|
4839
|
-
}
|
|
4840
|
-
},
|
|
4841
|
-
onCtxRename: () => {
|
|
4842
|
-
if (ctxMenu?.nodeId) {
|
|
4843
|
-
const node = model.nodes.find((n) => n.id === ctxMenu.nodeId);
|
|
4844
|
-
setEditingId(ctxMenu.nodeId);
|
|
4845
|
-
setEditLabel(node.label);
|
|
4846
|
-
setCtxMenu(null);
|
|
4847
|
-
}
|
|
4848
|
-
},
|
|
4849
|
-
onCtxDelete: () => {
|
|
4850
|
-
if (ctxMenu?.nodeId) {
|
|
4851
|
-
deleteNode(ctxMenu.nodeId);
|
|
4852
|
-
setCtxMenu(null);
|
|
4853
|
-
}
|
|
4854
|
-
},
|
|
4855
|
-
onCtxDisconnect: () => {
|
|
4856
|
-
if (ctxMenu?.nodeId) {
|
|
4857
|
-
const m = { ...model, edges: model.edges.filter((e) => e.from !== ctxMenu.nodeId && e.to !== ctxMenu.nodeId) };
|
|
4858
|
-
applyAndPush(m);
|
|
4859
|
-
setCtxMenu(null);
|
|
4860
|
-
}
|
|
4861
|
-
},
|
|
4862
|
-
onCtxEdgeRename: () => {
|
|
4863
|
-
if (ctxMenu?.edgeId) {
|
|
4864
|
-
beginEditEdge(ctxMenu.edgeId);
|
|
4865
|
-
setCtxMenu(null);
|
|
4866
|
-
}
|
|
4867
|
-
},
|
|
4868
|
-
onCtxEdgeStyle: (s2) => {
|
|
4869
|
-
if (ctxMenu?.edgeId) {
|
|
4870
|
-
setEdgeStyle(ctxMenu.edgeId, s2);
|
|
4871
|
-
setCtxMenu(null);
|
|
4872
|
-
}
|
|
4873
|
-
},
|
|
4874
|
-
onCtxEdgeArrowhead: (a) => {
|
|
4875
|
-
if (ctxMenu?.edgeId) {
|
|
4876
|
-
setEdgeArrowhead(ctxMenu.edgeId, a);
|
|
4877
|
-
setCtxMenu(null);
|
|
4878
|
-
}
|
|
4879
|
-
},
|
|
4880
|
-
onCtxEdgeDelete: () => {
|
|
4881
|
-
if (ctxMenu?.edgeId) {
|
|
4882
|
-
deleteEdge(ctxMenu.edgeId);
|
|
4883
|
-
setCtxMenu(null);
|
|
6293
|
+
/* @__PURE__ */ jsx12("div", { role: "status", "aria-live": "polite", "aria-atomic": "true", style: STYLE_SR_ONLY, children: announcement }),
|
|
6294
|
+
/* @__PURE__ */ jsx12(
|
|
6295
|
+
Toolbar,
|
|
6296
|
+
{
|
|
6297
|
+
onExport: handleExport,
|
|
6298
|
+
onImport: allowImport ? handleImport : void 0,
|
|
6299
|
+
allowedExports,
|
|
6300
|
+
allowImport
|
|
6301
|
+
}
|
|
6302
|
+
),
|
|
6303
|
+
/* @__PURE__ */ jsxs12(
|
|
6304
|
+
"div",
|
|
6305
|
+
{
|
|
6306
|
+
style: {
|
|
6307
|
+
display: "flex",
|
|
6308
|
+
gap: 6,
|
|
6309
|
+
padding: "7px 14px",
|
|
6310
|
+
background: t.ctrlsBg,
|
|
6311
|
+
borderBottom: `1px solid ${t.ctrlsBorder}`,
|
|
6312
|
+
alignItems: "center",
|
|
6313
|
+
flexWrap: "wrap"
|
|
6314
|
+
},
|
|
6315
|
+
children: [
|
|
6316
|
+
/* @__PURE__ */ jsxs12("button", { onClick: () => addNode(), style: ctrlBtn(acc.color, isDark), children: [
|
|
6317
|
+
"+ ",
|
|
6318
|
+
variantLabel
|
|
6319
|
+
] }),
|
|
6320
|
+
selectedSet.size > 0 && /* @__PURE__ */ jsxs12(Fragment5, { children: [
|
|
6321
|
+
/* @__PURE__ */ jsx12("div", { style: { width: 1, height: 20, background: t.ctrlsBorder, margin: "0 2px" } }),
|
|
6322
|
+
/* @__PURE__ */ jsx12(
|
|
6323
|
+
"button",
|
|
6324
|
+
{
|
|
6325
|
+
onClick: deleteSelected,
|
|
6326
|
+
style: {
|
|
6327
|
+
...ctrlBtn("transparent", isDark),
|
|
6328
|
+
color: "#ef4444",
|
|
6329
|
+
border: `1px solid ${isDark ? "#7f1d1d" : "#fca5a5"}`
|
|
6330
|
+
},
|
|
6331
|
+
children: selectedSet.size > 1 ? `Delete (${selectedSet.size})` : "Delete"
|
|
6332
|
+
}
|
|
6333
|
+
)
|
|
6334
|
+
] }),
|
|
6335
|
+
liveEdge && /* @__PURE__ */ jsxs12("span", { style: { fontSize: 11, color: acc.color, fontWeight: 600, marginLeft: 6 }, children: [
|
|
6336
|
+
liveEdge.answerLabel ? `Routing "${liveEdge.answerLabel}" \u2192` : "Drop on a node to connect",
|
|
6337
|
+
/* @__PURE__ */ jsx12("span", { style: { fontWeight: 400, color: t.textMuted, marginLeft: 6 }, children: "release to cancel" })
|
|
6338
|
+
] }),
|
|
6339
|
+
/* @__PURE__ */ jsxs12("span", { style: { marginLeft: "auto", fontSize: 11, color: t.textMuted }, children: [
|
|
6340
|
+
variant === "question" ? "drag answer port to connect \xB7 " : "drag port dot \xB7 ",
|
|
6341
|
+
"scroll to zoom \xB7 drag to pan"
|
|
6342
|
+
] })
|
|
6343
|
+
]
|
|
6344
|
+
}
|
|
6345
|
+
),
|
|
6346
|
+
variant !== "flowchart" && /* @__PURE__ */ jsx12(
|
|
6347
|
+
"div",
|
|
6348
|
+
{
|
|
6349
|
+
style: {
|
|
6350
|
+
padding: "3px 14px",
|
|
6351
|
+
background: acc.fill,
|
|
6352
|
+
borderBottom: `1px solid ${acc.border}`,
|
|
6353
|
+
fontSize: 11,
|
|
6354
|
+
color: acc.color,
|
|
6355
|
+
fontWeight: 600
|
|
6356
|
+
},
|
|
6357
|
+
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"
|
|
6358
|
+
}
|
|
6359
|
+
),
|
|
6360
|
+
/* @__PURE__ */ jsxs12("div", { style: STYLE_FLEX_ROW, children: [
|
|
6361
|
+
/* @__PURE__ */ jsx12(
|
|
6362
|
+
NodeNavigator,
|
|
6363
|
+
{
|
|
6364
|
+
model,
|
|
6365
|
+
selected,
|
|
6366
|
+
variant,
|
|
6367
|
+
isDark,
|
|
6368
|
+
t,
|
|
6369
|
+
acc,
|
|
6370
|
+
open: navOpen,
|
|
6371
|
+
onToggle: () => setNavOpen((v) => !v),
|
|
6372
|
+
onSelect: jumpToNode
|
|
4884
6373
|
}
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
6374
|
+
),
|
|
6375
|
+
/* @__PURE__ */ jsx12(
|
|
6376
|
+
DiagramCanvas,
|
|
6377
|
+
{
|
|
6378
|
+
model,
|
|
6379
|
+
variant,
|
|
6380
|
+
variantLabel,
|
|
6381
|
+
t,
|
|
6382
|
+
isDark,
|
|
6383
|
+
acc,
|
|
6384
|
+
transform,
|
|
6385
|
+
setTransform,
|
|
6386
|
+
selected,
|
|
6387
|
+
selectedSet,
|
|
6388
|
+
hoveredId,
|
|
6389
|
+
setHoveredId,
|
|
6390
|
+
drag,
|
|
6391
|
+
pan,
|
|
6392
|
+
liveEdge,
|
|
6393
|
+
boxSel,
|
|
6394
|
+
alignGuides,
|
|
6395
|
+
editingEdgeId,
|
|
6396
|
+
editEdgeLabel,
|
|
6397
|
+
setEditEdgeLabel,
|
|
6398
|
+
commitEdgeEdit,
|
|
6399
|
+
setEditingEdgeId,
|
|
6400
|
+
beginEditEdge,
|
|
6401
|
+
onEdgeContextMenu,
|
|
6402
|
+
setWaypointDrag,
|
|
6403
|
+
editingId,
|
|
6404
|
+
editLabel,
|
|
6405
|
+
setEditLabel,
|
|
6406
|
+
commitEdit,
|
|
6407
|
+
setEditingId,
|
|
6408
|
+
onNodeMouseDown,
|
|
6409
|
+
onNodeMouseUp,
|
|
6410
|
+
onNodeDblClick,
|
|
6411
|
+
onNodeContextMenu,
|
|
6412
|
+
onPortMouseDown,
|
|
6413
|
+
onAnswerPortDown,
|
|
6414
|
+
onSvgMouseDown,
|
|
6415
|
+
onMouseMove,
|
|
6416
|
+
onMouseUp,
|
|
6417
|
+
onSvgContextMenu,
|
|
6418
|
+
reducedMotion,
|
|
6419
|
+
isCoarse,
|
|
6420
|
+
portR,
|
|
6421
|
+
shadowClr,
|
|
6422
|
+
arrowClr,
|
|
6423
|
+
amberArrow,
|
|
6424
|
+
viewport,
|
|
6425
|
+
svgRef,
|
|
6426
|
+
containerRef,
|
|
6427
|
+
ctxMenu,
|
|
6428
|
+
history,
|
|
6429
|
+
ctxEdgeStyle: (ctxMenu?.edgeId ? model.edges.find((e) => e.id === ctxMenu.edgeId) : void 0)?.style ?? "solid",
|
|
6430
|
+
ctxEdgeArrow: (ctxMenu?.edgeId ? model.edges.find((e) => e.id === ctxMenu.edgeId) : void 0)?.arrowhead ?? "arrow",
|
|
6431
|
+
ctxEdgeHasWaypoint: !!(ctxMenu?.edgeId ? model.edges.find((e) => e.id === ctxMenu.edgeId) : void 0)?.waypoint,
|
|
6432
|
+
onCtxUndo: () => {
|
|
6433
|
+
undo();
|
|
6434
|
+
setCtxMenu(null);
|
|
6435
|
+
},
|
|
6436
|
+
onCtxRedo: () => {
|
|
6437
|
+
redo();
|
|
6438
|
+
setCtxMenu(null);
|
|
6439
|
+
},
|
|
6440
|
+
onCtxReCenter: () => {
|
|
6441
|
+
reCenter();
|
|
6442
|
+
setCtxMenu(null);
|
|
6443
|
+
},
|
|
6444
|
+
onCtxAddNode: () => {
|
|
6445
|
+
const rect = svgRef.current.getBoundingClientRect();
|
|
6446
|
+
const cx = (ctxMenu.x - rect.left - transform.x) / transform.scale;
|
|
6447
|
+
const cy = (ctxMenu.y - rect.top - transform.y) / transform.scale;
|
|
6448
|
+
addNode({ x: cx, y: cy });
|
|
6449
|
+
setCtxMenu(null);
|
|
6450
|
+
},
|
|
6451
|
+
onCtxDuplicate: () => {
|
|
6452
|
+
if (ctxMenu?.nodeId) {
|
|
6453
|
+
duplicateNode(ctxMenu.nodeId);
|
|
6454
|
+
setCtxMenu(null);
|
|
6455
|
+
}
|
|
6456
|
+
},
|
|
6457
|
+
onCtxRename: () => {
|
|
6458
|
+
if (ctxMenu?.nodeId) {
|
|
6459
|
+
const node = model.nodes.find((n) => n.id === ctxMenu.nodeId);
|
|
6460
|
+
setEditingId(ctxMenu.nodeId);
|
|
6461
|
+
setEditLabel(node.label);
|
|
6462
|
+
setCtxMenu(null);
|
|
6463
|
+
}
|
|
6464
|
+
},
|
|
6465
|
+
onCtxDelete: () => {
|
|
6466
|
+
if (ctxMenu?.nodeId) {
|
|
6467
|
+
deleteNode(ctxMenu.nodeId);
|
|
6468
|
+
setCtxMenu(null);
|
|
6469
|
+
}
|
|
6470
|
+
},
|
|
6471
|
+
onCtxDisconnect: () => {
|
|
6472
|
+
if (ctxMenu?.nodeId) {
|
|
6473
|
+
const m = {
|
|
6474
|
+
...model,
|
|
6475
|
+
edges: model.edges.filter(
|
|
6476
|
+
(e) => e.from !== ctxMenu.nodeId && e.to !== ctxMenu.nodeId
|
|
6477
|
+
)
|
|
6478
|
+
};
|
|
6479
|
+
applyAndPush(m);
|
|
6480
|
+
setCtxMenu(null);
|
|
6481
|
+
}
|
|
6482
|
+
},
|
|
6483
|
+
onCtxEdgeRename: () => {
|
|
6484
|
+
if (ctxMenu?.edgeId) {
|
|
6485
|
+
beginEditEdge(ctxMenu.edgeId);
|
|
6486
|
+
setCtxMenu(null);
|
|
6487
|
+
}
|
|
6488
|
+
},
|
|
6489
|
+
onCtxEdgeStyle: (s2) => {
|
|
6490
|
+
if (ctxMenu?.edgeId) {
|
|
6491
|
+
setEdgeStyle(ctxMenu.edgeId, s2);
|
|
6492
|
+
setCtxMenu(null);
|
|
6493
|
+
}
|
|
6494
|
+
},
|
|
6495
|
+
onCtxEdgeArrowhead: (a) => {
|
|
6496
|
+
if (ctxMenu?.edgeId) {
|
|
6497
|
+
setEdgeArrowhead(ctxMenu.edgeId, a);
|
|
6498
|
+
setCtxMenu(null);
|
|
6499
|
+
}
|
|
6500
|
+
},
|
|
6501
|
+
onCtxEdgeDelete: () => {
|
|
6502
|
+
if (ctxMenu?.edgeId) {
|
|
6503
|
+
deleteEdge(ctxMenu.edgeId);
|
|
6504
|
+
setCtxMenu(null);
|
|
6505
|
+
}
|
|
6506
|
+
},
|
|
6507
|
+
onCtxEdgeResetRouting: () => {
|
|
6508
|
+
if (ctxMenu?.edgeId) {
|
|
6509
|
+
resetEdgeRouting(ctxMenu.edgeId);
|
|
6510
|
+
setCtxMenu(null);
|
|
6511
|
+
}
|
|
6512
|
+
}
|
|
4890
6513
|
}
|
|
6514
|
+
),
|
|
6515
|
+
selected && /* @__PURE__ */ jsx12(
|
|
6516
|
+
StepEditor,
|
|
6517
|
+
{
|
|
6518
|
+
nodeId: selected,
|
|
6519
|
+
model,
|
|
6520
|
+
onModelChange: (m) => {
|
|
6521
|
+
applyAndPush(m);
|
|
6522
|
+
},
|
|
6523
|
+
variant,
|
|
6524
|
+
isDark,
|
|
6525
|
+
t,
|
|
6526
|
+
acc
|
|
6527
|
+
},
|
|
6528
|
+
selected
|
|
6529
|
+
)
|
|
6530
|
+
] }),
|
|
6531
|
+
/* @__PURE__ */ jsxs12(
|
|
6532
|
+
"div",
|
|
6533
|
+
{
|
|
6534
|
+
style: {
|
|
6535
|
+
padding: "4px 14px",
|
|
6536
|
+
fontSize: 11,
|
|
6537
|
+
color: t.textMuted,
|
|
6538
|
+
background: t.statusBg,
|
|
6539
|
+
borderTop: `1px solid ${t.ctrlsBorder}`,
|
|
6540
|
+
display: "flex",
|
|
6541
|
+
gap: 16,
|
|
6542
|
+
flexWrap: "wrap",
|
|
6543
|
+
overflow: "hidden",
|
|
6544
|
+
maxHeight: 28
|
|
6545
|
+
},
|
|
6546
|
+
children: [
|
|
6547
|
+
/* @__PURE__ */ jsxs12("span", { children: [
|
|
6548
|
+
model.nodes.length,
|
|
6549
|
+
" ",
|
|
6550
|
+
variantLabel.toLowerCase(),
|
|
6551
|
+
"s"
|
|
6552
|
+
] }),
|
|
6553
|
+
/* @__PURE__ */ jsxs12("span", { children: [
|
|
6554
|
+
model.edges.length,
|
|
6555
|
+
" connections"
|
|
6556
|
+
] }),
|
|
6557
|
+
/* @__PURE__ */ jsxs12("span", { children: [
|
|
6558
|
+
Math.round(transform.scale * 100),
|
|
6559
|
+
"% zoom"
|
|
6560
|
+
] }),
|
|
6561
|
+
/* @__PURE__ */ jsx12(
|
|
6562
|
+
"span",
|
|
6563
|
+
{
|
|
6564
|
+
style: {
|
|
6565
|
+
marginLeft: "auto",
|
|
6566
|
+
whiteSpace: "nowrap",
|
|
6567
|
+
overflow: "hidden",
|
|
6568
|
+
textOverflow: "ellipsis"
|
|
6569
|
+
},
|
|
6570
|
+
children: "Ctrl+Z undo \xB7 Ctrl+Y redo \xB7 Ctrl+0 fit \xB7 Alt+Arrow traverse"
|
|
6571
|
+
}
|
|
6572
|
+
),
|
|
6573
|
+
selected && /* @__PURE__ */ jsx12("span", { style: { color: acc.color }, children: model.nodes.find((n) => n.id === selected)?.label })
|
|
6574
|
+
]
|
|
4891
6575
|
}
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
}, variant, isDark, t, acc }, selected)
|
|
4897
|
-
] }),
|
|
4898
|
-
/* @__PURE__ */ jsxs12("div", { style: { padding: "4px 14px", fontSize: 11, color: t.textMuted, background: t.statusBg, borderTop: `1px solid ${t.ctrlsBorder}`, display: "flex", gap: 16, flexWrap: "wrap", overflow: "hidden", maxHeight: 28 }, children: [
|
|
4899
|
-
/* @__PURE__ */ jsxs12("span", { children: [
|
|
4900
|
-
model.nodes.length,
|
|
4901
|
-
" ",
|
|
4902
|
-
variantLabel.toLowerCase(),
|
|
4903
|
-
"s"
|
|
4904
|
-
] }),
|
|
4905
|
-
/* @__PURE__ */ jsxs12("span", { children: [
|
|
4906
|
-
model.edges.length,
|
|
4907
|
-
" connections"
|
|
4908
|
-
] }),
|
|
4909
|
-
/* @__PURE__ */ jsxs12("span", { children: [
|
|
4910
|
-
Math.round(transform.scale * 100),
|
|
4911
|
-
"% zoom"
|
|
4912
|
-
] }),
|
|
4913
|
-
/* @__PURE__ */ jsx12("span", { style: { marginLeft: "auto", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: "Ctrl+Z undo \xB7 Ctrl+Y redo \xB7 Ctrl+0 fit \xB7 Alt+Arrow traverse" }),
|
|
4914
|
-
selected && /* @__PURE__ */ jsx12("span", { style: { color: acc.color }, children: model.nodes.find((n) => n.id === selected)?.label })
|
|
4915
|
-
] })
|
|
4916
|
-
] });
|
|
6576
|
+
)
|
|
6577
|
+
]
|
|
6578
|
+
}
|
|
6579
|
+
);
|
|
4917
6580
|
}
|
|
4918
6581
|
function ctrlBtn(accent, isDark) {
|
|
4919
6582
|
const isTransparent = accent === "transparent";
|