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.cjs
CHANGED
|
@@ -415,12 +415,37 @@ function arrowColor(isDark) {
|
|
|
415
415
|
}
|
|
416
416
|
function variantAccent(variant, isDark) {
|
|
417
417
|
if (variant === "question") {
|
|
418
|
-
return isDark ? {
|
|
418
|
+
return isDark ? {
|
|
419
|
+
color: ACCENT.amberDark,
|
|
420
|
+
fill: ACCENT.amberDarkLight,
|
|
421
|
+
border: ACCENT.amberDarkBorder,
|
|
422
|
+
glow: ACCENT.amberGlow
|
|
423
|
+
} : {
|
|
424
|
+
color: ACCENT.amber,
|
|
425
|
+
fill: ACCENT.amberLight,
|
|
426
|
+
border: ACCENT.amberBorder,
|
|
427
|
+
glow: ACCENT.amberGlow
|
|
428
|
+
};
|
|
419
429
|
}
|
|
420
430
|
if (variant === "journey") {
|
|
421
|
-
return isDark ? {
|
|
431
|
+
return isDark ? {
|
|
432
|
+
color: ACCENT.emeraldDark,
|
|
433
|
+
fill: ACCENT.emeraldDarkLight,
|
|
434
|
+
border: ACCENT.emeraldDarkBorder,
|
|
435
|
+
glow: ACCENT.emeraldGlow
|
|
436
|
+
} : {
|
|
437
|
+
color: ACCENT.emerald,
|
|
438
|
+
fill: ACCENT.emeraldLight,
|
|
439
|
+
border: "#6ee7b7",
|
|
440
|
+
glow: ACCENT.emeraldGlow
|
|
441
|
+
};
|
|
422
442
|
}
|
|
423
|
-
return isDark ? {
|
|
443
|
+
return isDark ? {
|
|
444
|
+
color: "#818cf8",
|
|
445
|
+
fill: "rgba(79,70,229,0.12)",
|
|
446
|
+
border: "rgba(79,70,229,0.3)",
|
|
447
|
+
glow: ACCENT.indigoGlow
|
|
448
|
+
} : { color: ACCENT.indigo, fill: "#f5f3ff", border: "#c7d2fe", glow: ACCENT.indigoGlow };
|
|
424
449
|
}
|
|
425
450
|
|
|
426
451
|
// src/ui/Toolbar.tsx
|
|
@@ -447,17 +472,19 @@ function Toolbar({ onExport, onImport, allowedExports, allowImport = true }) {
|
|
|
447
472
|
allowImport && onImport && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => setImportOpen(true), "aria-label": "Import diagram", style: ghostBtn, children: "\u2191 Import" }),
|
|
448
473
|
formats.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
449
474
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 11, color: darkTheme.inputText, margin: "0 4px" }, children: "Export \u2192" }),
|
|
450
|
-
formats.map((f) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
475
|
+
formats.map((f) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
476
|
+
"button",
|
|
477
|
+
{
|
|
478
|
+
onClick: () => onExport(f.key),
|
|
479
|
+
"aria-label": `Export as ${f.label}`,
|
|
480
|
+
style: exportBtn,
|
|
481
|
+
children: f.label
|
|
482
|
+
},
|
|
483
|
+
f.key
|
|
484
|
+
))
|
|
451
485
|
] })
|
|
452
486
|
] }),
|
|
453
|
-
onImport && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
454
|
-
ImportDialog,
|
|
455
|
-
{
|
|
456
|
-
open: importOpen,
|
|
457
|
-
onClose: () => setImportOpen(false),
|
|
458
|
-
onImport
|
|
459
|
-
}
|
|
460
|
-
)
|
|
487
|
+
onImport && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ImportDialog, { open: importOpen, onClose: () => setImportOpen(false), onImport })
|
|
461
488
|
] });
|
|
462
489
|
}
|
|
463
490
|
var bar = {
|
|
@@ -549,7 +576,15 @@ var SHAPES = [
|
|
|
549
576
|
{ key: "circle", label: "Circle", icon: "\u25CB" },
|
|
550
577
|
{ key: "parallelogram", label: "I/O", icon: "\u25B1" }
|
|
551
578
|
];
|
|
552
|
-
function StepEditor({
|
|
579
|
+
function StepEditor({
|
|
580
|
+
nodeId,
|
|
581
|
+
model,
|
|
582
|
+
onModelChange,
|
|
583
|
+
variant = "flowchart",
|
|
584
|
+
isDark = false,
|
|
585
|
+
t,
|
|
586
|
+
acc
|
|
587
|
+
}) {
|
|
553
588
|
const isQuestion2 = variant === "question";
|
|
554
589
|
const branchTerm = isQuestion2 ? "Answer" : "Branch";
|
|
555
590
|
const tt = t ?? (isDark ? darkTheme : lightTheme);
|
|
@@ -583,27 +618,57 @@ function StepEditor({ nodeId, model, onModelChange, variant = "flowchart", isDar
|
|
|
583
618
|
const answers = node.metadata?.answers ?? [];
|
|
584
619
|
const commitLabel = () => {
|
|
585
620
|
if (label === node.label || !label.trim()) return;
|
|
586
|
-
onModelChange({
|
|
621
|
+
onModelChange({
|
|
622
|
+
...model,
|
|
623
|
+
nodes: model.nodes.map((n) => n.id === nodeId ? { ...n, label: label.trim() } : n)
|
|
624
|
+
});
|
|
587
625
|
};
|
|
588
626
|
const setShape = (shape) => {
|
|
589
|
-
onModelChange({
|
|
627
|
+
onModelChange({
|
|
628
|
+
...model,
|
|
629
|
+
nodes: model.nodes.map((n) => n.id === nodeId ? { ...n, shape } : n)
|
|
630
|
+
});
|
|
590
631
|
};
|
|
591
632
|
const removeEdge = (edgeId) => {
|
|
592
633
|
onModelChange({ ...model, edges: model.edges.filter((e) => e.id !== edgeId) });
|
|
593
634
|
};
|
|
594
635
|
const updateEdgeLabel = (edgeId, val) => {
|
|
595
|
-
onModelChange({
|
|
636
|
+
onModelChange({
|
|
637
|
+
...model,
|
|
638
|
+
edges: model.edges.map((e) => e.id === edgeId ? { ...e, label: val || void 0 } : e)
|
|
639
|
+
});
|
|
596
640
|
};
|
|
597
641
|
const addBranch = () => {
|
|
598
642
|
if (branchMode === "new") {
|
|
599
643
|
if (!branchLabel.trim()) return;
|
|
600
644
|
const newId = nextId("node", model.nodes);
|
|
601
|
-
const newNode = {
|
|
602
|
-
|
|
603
|
-
|
|
645
|
+
const newNode = {
|
|
646
|
+
id: newId,
|
|
647
|
+
label: branchLabel.trim(),
|
|
648
|
+
shape: "rectangle",
|
|
649
|
+
x: (node.x ?? 0) + 200,
|
|
650
|
+
y: (node.y ?? 0) + 20 + outEdges.length * 100
|
|
651
|
+
};
|
|
652
|
+
const newEdge = {
|
|
653
|
+
id: nextId("e", model.edges),
|
|
654
|
+
from: nodeId,
|
|
655
|
+
to: newId,
|
|
656
|
+
label: branchEdgeLabel.trim() || void 0
|
|
657
|
+
};
|
|
658
|
+
onModelChange({
|
|
659
|
+
...model,
|
|
660
|
+
nodes: [...model.nodes, newNode],
|
|
661
|
+
edges: [...model.edges, newEdge]
|
|
662
|
+
});
|
|
604
663
|
} else {
|
|
605
|
-
if (!branchTarget || model.edges.some((e) => e.from === nodeId && e.to === branchTarget))
|
|
606
|
-
|
|
664
|
+
if (!branchTarget || model.edges.some((e) => e.from === nodeId && e.to === branchTarget))
|
|
665
|
+
return;
|
|
666
|
+
const newEdge = {
|
|
667
|
+
id: nextId("e", model.edges),
|
|
668
|
+
from: nodeId,
|
|
669
|
+
to: branchTarget,
|
|
670
|
+
label: branchEdgeLabel.trim() || void 0
|
|
671
|
+
};
|
|
607
672
|
onModelChange({ ...model, edges: [...model.edges, newEdge] });
|
|
608
673
|
}
|
|
609
674
|
setBranchLabel("");
|
|
@@ -615,7 +680,12 @@ function StepEditor({ nodeId, model, onModelChange, variant = "flowchart", isDar
|
|
|
615
680
|
const trimmed = newAnswer.trim();
|
|
616
681
|
if (!trimmed || answers.includes(trimmed)) return;
|
|
617
682
|
const updated = [...answers, trimmed];
|
|
618
|
-
onModelChange({
|
|
683
|
+
onModelChange({
|
|
684
|
+
...model,
|
|
685
|
+
nodes: model.nodes.map(
|
|
686
|
+
(n) => n.id === nodeId ? { ...n, metadata: { ...n.metadata ?? {}, answers: updated } } : n
|
|
687
|
+
)
|
|
688
|
+
});
|
|
619
689
|
setNewAnswer("");
|
|
620
690
|
setAddingAnswer(false);
|
|
621
691
|
};
|
|
@@ -624,7 +694,9 @@ function StepEditor({ nodeId, model, onModelChange, variant = "flowchart", isDar
|
|
|
624
694
|
const updatedEdges = model.edges.filter((e) => !(e.from === nodeId && e.label === ans));
|
|
625
695
|
onModelChange({
|
|
626
696
|
...model,
|
|
627
|
-
nodes: model.nodes.map(
|
|
697
|
+
nodes: model.nodes.map(
|
|
698
|
+
(n) => n.id === nodeId ? { ...n, metadata: { ...n.metadata ?? {}, answers: updated } } : n
|
|
699
|
+
),
|
|
628
700
|
edges: updatedEdges
|
|
629
701
|
});
|
|
630
702
|
};
|
|
@@ -633,7 +705,12 @@ function StepEditor({ nodeId, model, onModelChange, variant = "flowchart", isDar
|
|
|
633
705
|
if (next < 0 || next >= answers.length) return;
|
|
634
706
|
const arr = [...answers];
|
|
635
707
|
[arr[idx], arr[next]] = [arr[next], arr[idx]];
|
|
636
|
-
onModelChange({
|
|
708
|
+
onModelChange({
|
|
709
|
+
...model,
|
|
710
|
+
nodes: model.nodes.map(
|
|
711
|
+
(n) => n.id === nodeId ? { ...n, metadata: { ...n.metadata ?? {}, answers: arr } } : n
|
|
712
|
+
)
|
|
713
|
+
});
|
|
637
714
|
};
|
|
638
715
|
const inputStyle = {
|
|
639
716
|
width: "100%",
|
|
@@ -688,167 +765,600 @@ function StepEditor({ nodeId, model, onModelChange, variant = "flowchart", isDar
|
|
|
688
765
|
fontFamily: "inherit",
|
|
689
766
|
transition: "background 0.15s, border-color 0.15s"
|
|
690
767
|
};
|
|
691
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: isQuestion2 ? "Question Editor" : variant === "journey" ? "Step Editor" : "Step Editor" })
|
|
707
|
-
] }),
|
|
708
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { flex: 1, overflowY: "auto", display: "flex", flexDirection: "column" }, children: [
|
|
709
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { style: { padding: "14px 16px", borderBottom: `1px solid ${tt.sectionBorder}` }, children: [
|
|
710
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { style: { display: "block", fontSize: 10, fontWeight: 700, color: tt.labelText, marginBottom: 8, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Name" }),
|
|
711
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
712
|
-
"input",
|
|
768
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
769
|
+
"div",
|
|
770
|
+
{
|
|
771
|
+
style: {
|
|
772
|
+
width: 272,
|
|
773
|
+
minWidth: 272,
|
|
774
|
+
background: tt.panelBg,
|
|
775
|
+
borderLeft: `1px solid ${tt.panelBorder}`,
|
|
776
|
+
display: "flex",
|
|
777
|
+
flexDirection: "column",
|
|
778
|
+
overflow: "hidden"
|
|
779
|
+
},
|
|
780
|
+
children: [
|
|
781
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
782
|
+
"div",
|
|
713
783
|
{
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
alignItems: "center",
|
|
732
|
-
gap: 4,
|
|
733
|
-
padding: "8px 6px",
|
|
734
|
-
borderRadius: 8,
|
|
735
|
-
cursor: "pointer",
|
|
736
|
-
transition: "all 0.15s",
|
|
737
|
-
background: active ? accentColor : tt.shapeBtnBg,
|
|
738
|
-
color: active ? "#fff" : tt.textSecondary,
|
|
739
|
-
border: active ? `1.5px solid ${accentColor}` : `1.5px solid ${tt.shapeBtnBorder}`
|
|
740
|
-
}, children: [
|
|
741
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { fontSize: 16, lineHeight: 1 }, children: s2.icon }),
|
|
742
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { fontSize: 11, fontWeight: 500 }, children: s2.label })
|
|
743
|
-
] }, s2.key);
|
|
744
|
-
}) })
|
|
745
|
-
] }),
|
|
746
|
-
isQuestion2 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { style: { padding: "14px 16px", borderBottom: `1px solid ${tt.sectionBorder}`, flex: 1 }, children: [
|
|
747
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 10 }, children: [
|
|
748
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { style: { display: "block", fontSize: 10, fontWeight: 700, color: tt.labelText, textTransform: "uppercase", letterSpacing: 0.8 }, children: "Answers" }),
|
|
749
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { fontSize: 11, color: tt.textMuted, background: isDark ? "#0f172a" : "#f1f5f9", padding: "1px 7px", borderRadius: 99, fontWeight: 600 }, children: answers.length })
|
|
750
|
-
] }),
|
|
751
|
-
answers.length === 0 && !addingAnswer && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { fontSize: 12, color: tt.textMuted, textAlign: "center", padding: "16px 0", fontStyle: "italic" }, children: "No answers yet \u2014 add one below" }),
|
|
752
|
-
answers.map((ans, i) => {
|
|
753
|
-
const connected = model.edges.some((e) => e.from === nodeId && e.label === ans);
|
|
754
|
-
const targetNode = model.nodes.find((n) => {
|
|
755
|
-
const e = model.edges.find((ex) => ex.from === nodeId && ex.label === ans);
|
|
756
|
-
return e && n.id === e.to;
|
|
757
|
-
});
|
|
758
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("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: [
|
|
759
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { width: 4, alignSelf: "stretch", background: accentColor, flexShrink: 0 } }),
|
|
760
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { flex: 1, minWidth: 0, padding: "8px 10px" }, children: [
|
|
761
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { fontSize: 12, fontWeight: 600, color: tt.textPrimary, marginBottom: connected ? 3 : 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: ans }),
|
|
762
|
-
connected && targetNode && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { fontSize: 11, color: accentColor, opacity: 0.85 }, children: [
|
|
763
|
-
"\u2192 ",
|
|
764
|
-
targetNode.label
|
|
765
|
-
] }),
|
|
766
|
-
!connected && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { fontSize: 10, color: tt.textMuted, fontStyle: "italic" }, children: "drag port to connect" })
|
|
767
|
-
] }),
|
|
768
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", flexDirection: "column", padding: "4px 2px", gap: 2 }, children: [
|
|
769
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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" }),
|
|
770
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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" })
|
|
771
|
-
] }),
|
|
772
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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" })
|
|
773
|
-
] }, ans + i);
|
|
774
|
-
}),
|
|
775
|
-
addingAnswer ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { role: "group", "aria-label": "Add answer form", onKeyDown: (e) => {
|
|
776
|
-
if (e.key === "Escape") {
|
|
777
|
-
setAddingAnswer(false);
|
|
778
|
-
setNewAnswer("");
|
|
784
|
+
style: {
|
|
785
|
+
padding: "12px 16px",
|
|
786
|
+
fontWeight: 700,
|
|
787
|
+
fontSize: 12,
|
|
788
|
+
letterSpacing: 0.8,
|
|
789
|
+
textTransform: "uppercase",
|
|
790
|
+
color: accentColor,
|
|
791
|
+
borderBottom: `1px solid ${accentBorder}`,
|
|
792
|
+
background: accentLight,
|
|
793
|
+
display: "flex",
|
|
794
|
+
alignItems: "center",
|
|
795
|
+
gap: 8
|
|
796
|
+
},
|
|
797
|
+
children: [
|
|
798
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { width: 6, height: 6, borderRadius: "50%", background: accentColor } }),
|
|
799
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: isQuestion2 ? "Question Editor" : variant === "journey" ? "Step Editor" : "Step Editor" })
|
|
800
|
+
]
|
|
779
801
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("
|
|
783
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
"\u2192 ",
|
|
811
|
-
target?.label ?? edge.to
|
|
812
|
-
] }),
|
|
813
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("input", { value: edge.label ?? "", onChange: (e) => updateEdgeLabel(edge.id, e.target.value), placeholder: "Edge label (optional)", style: { ...inputStyle, fontSize: 11, padding: "4px 8px" } })
|
|
814
|
-
] }),
|
|
815
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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" })
|
|
816
|
-
] }, edge.id);
|
|
817
|
-
}),
|
|
818
|
-
addingBranch ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { role: "group", "aria-label": "Add branch form", onKeyDown: (e) => {
|
|
819
|
-
if (e.key === "Escape") setAddingBranch(false);
|
|
820
|
-
}, style: { marginTop: 10, background: tt.addFormBg, borderRadius: 10, padding: 12, border: `1.5px solid ${accentBorder}` }, children: [
|
|
821
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { display: "flex", gap: 6, marginBottom: 10 }, children: ["new", "existing"].map((mode) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { onClick: () => setBranchMode(mode), style: {
|
|
822
|
-
flex: 1,
|
|
823
|
-
padding: "5px 0",
|
|
824
|
-
border: "none",
|
|
825
|
-
borderRadius: 6,
|
|
826
|
-
cursor: "pointer",
|
|
827
|
-
fontSize: 11,
|
|
828
|
-
fontWeight: 600,
|
|
829
|
-
background: branchMode === mode ? accentColor : tt.btnSecBg,
|
|
830
|
-
color: branchMode === mode ? "#fff" : tt.btnSecText
|
|
831
|
-
}, children: mode === "new" ? `+ New step` : "Existing step" }, mode)) }),
|
|
832
|
-
branchMode === "new" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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__ */ (0, import_jsx_runtime3.jsxs)("select", { value: branchTarget, onChange: (e) => setBranchTarget(e.target.value), style: { ...inputStyle, marginBottom: 6, appearance: "none" }, children: [
|
|
833
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: "Choose a step\u2026" }),
|
|
834
|
-
otherNodes.map((n) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: n.id, children: n.label }, n.id))
|
|
802
|
+
),
|
|
803
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { flex: 1, overflowY: "auto", display: "flex", flexDirection: "column" }, children: [
|
|
804
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { style: { padding: "14px 16px", borderBottom: `1px solid ${tt.sectionBorder}` }, children: [
|
|
805
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
806
|
+
"label",
|
|
807
|
+
{
|
|
808
|
+
style: {
|
|
809
|
+
display: "block",
|
|
810
|
+
fontSize: 10,
|
|
811
|
+
fontWeight: 700,
|
|
812
|
+
color: tt.labelText,
|
|
813
|
+
marginBottom: 8,
|
|
814
|
+
textTransform: "uppercase",
|
|
815
|
+
letterSpacing: 0.8
|
|
816
|
+
},
|
|
817
|
+
children: "Name"
|
|
818
|
+
}
|
|
819
|
+
),
|
|
820
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
821
|
+
"input",
|
|
822
|
+
{
|
|
823
|
+
ref: inputRef,
|
|
824
|
+
value: label,
|
|
825
|
+
onChange: (e) => setLabel(e.target.value),
|
|
826
|
+
onBlur: commitLabel,
|
|
827
|
+
onKeyDown: (e) => e.key === "Enter" && commitLabel(),
|
|
828
|
+
style: inputStyle,
|
|
829
|
+
placeholder: "Step name\u2026"
|
|
830
|
+
}
|
|
831
|
+
)
|
|
835
832
|
] }),
|
|
836
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
833
|
+
!isQuestion2 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { style: { padding: "14px 16px", borderBottom: `1px solid ${tt.sectionBorder}` }, children: [
|
|
834
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
835
|
+
"label",
|
|
836
|
+
{
|
|
837
|
+
style: {
|
|
838
|
+
display: "block",
|
|
839
|
+
fontSize: 10,
|
|
840
|
+
fontWeight: 700,
|
|
841
|
+
color: tt.labelText,
|
|
842
|
+
marginBottom: 8,
|
|
843
|
+
textTransform: "uppercase",
|
|
844
|
+
letterSpacing: 0.8
|
|
845
|
+
},
|
|
846
|
+
children: "Shape"
|
|
847
|
+
}
|
|
848
|
+
),
|
|
849
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 6 }, children: SHAPES.map((s2) => {
|
|
850
|
+
const active = (node.shape ?? "rectangle") === s2.key;
|
|
851
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
852
|
+
"button",
|
|
853
|
+
{
|
|
854
|
+
onClick: () => setShape(s2.key),
|
|
855
|
+
"aria-pressed": active,
|
|
856
|
+
style: {
|
|
857
|
+
display: "flex",
|
|
858
|
+
flexDirection: "column",
|
|
859
|
+
alignItems: "center",
|
|
860
|
+
gap: 4,
|
|
861
|
+
padding: "8px 6px",
|
|
862
|
+
borderRadius: 8,
|
|
863
|
+
cursor: "pointer",
|
|
864
|
+
transition: "all 0.15s",
|
|
865
|
+
background: active ? accentColor : tt.shapeBtnBg,
|
|
866
|
+
color: active ? "#fff" : tt.textSecondary,
|
|
867
|
+
border: active ? `1.5px solid ${accentColor}` : `1.5px solid ${tt.shapeBtnBorder}`
|
|
868
|
+
},
|
|
869
|
+
children: [
|
|
870
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { fontSize: 16, lineHeight: 1 }, children: s2.icon }),
|
|
871
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { fontSize: 11, fontWeight: 500 }, children: s2.label })
|
|
872
|
+
]
|
|
873
|
+
},
|
|
874
|
+
s2.key
|
|
875
|
+
);
|
|
876
|
+
}) })
|
|
877
|
+
] }),
|
|
878
|
+
isQuestion2 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
879
|
+
"section",
|
|
880
|
+
{
|
|
881
|
+
style: { padding: "14px 16px", borderBottom: `1px solid ${tt.sectionBorder}`, flex: 1 },
|
|
882
|
+
children: [
|
|
883
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
884
|
+
"div",
|
|
885
|
+
{
|
|
886
|
+
style: {
|
|
887
|
+
display: "flex",
|
|
888
|
+
alignItems: "center",
|
|
889
|
+
justifyContent: "space-between",
|
|
890
|
+
marginBottom: 10
|
|
891
|
+
},
|
|
892
|
+
children: [
|
|
893
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
894
|
+
"label",
|
|
895
|
+
{
|
|
896
|
+
style: {
|
|
897
|
+
display: "block",
|
|
898
|
+
fontSize: 10,
|
|
899
|
+
fontWeight: 700,
|
|
900
|
+
color: tt.labelText,
|
|
901
|
+
textTransform: "uppercase",
|
|
902
|
+
letterSpacing: 0.8
|
|
903
|
+
},
|
|
904
|
+
children: "Answers"
|
|
905
|
+
}
|
|
906
|
+
),
|
|
907
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
908
|
+
"span",
|
|
909
|
+
{
|
|
910
|
+
style: {
|
|
911
|
+
fontSize: 11,
|
|
912
|
+
color: tt.textMuted,
|
|
913
|
+
background: isDark ? "#0f172a" : "#f1f5f9",
|
|
914
|
+
padding: "1px 7px",
|
|
915
|
+
borderRadius: 99,
|
|
916
|
+
fontWeight: 600
|
|
917
|
+
},
|
|
918
|
+
children: answers.length
|
|
919
|
+
}
|
|
920
|
+
)
|
|
921
|
+
]
|
|
922
|
+
}
|
|
923
|
+
),
|
|
924
|
+
answers.length === 0 && !addingAnswer && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
925
|
+
"div",
|
|
926
|
+
{
|
|
927
|
+
style: {
|
|
928
|
+
fontSize: 12,
|
|
929
|
+
color: tt.textMuted,
|
|
930
|
+
textAlign: "center",
|
|
931
|
+
padding: "16px 0",
|
|
932
|
+
fontStyle: "italic"
|
|
933
|
+
},
|
|
934
|
+
children: "No answers yet \u2014 add one below"
|
|
935
|
+
}
|
|
936
|
+
),
|
|
937
|
+
answers.map((ans, i) => {
|
|
938
|
+
const connected = model.edges.some((e) => e.from === nodeId && e.label === ans);
|
|
939
|
+
const targetNode = model.nodes.find((n) => {
|
|
940
|
+
const e = model.edges.find((ex) => ex.from === nodeId && ex.label === ans);
|
|
941
|
+
return e && n.id === e.to;
|
|
942
|
+
});
|
|
943
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
944
|
+
"div",
|
|
945
|
+
{
|
|
946
|
+
style: {
|
|
947
|
+
display: "flex",
|
|
948
|
+
alignItems: "flex-start",
|
|
949
|
+
gap: 0,
|
|
950
|
+
marginBottom: 8,
|
|
951
|
+
borderRadius: 12,
|
|
952
|
+
border: `1px solid ${tt.cardBorder}`,
|
|
953
|
+
overflow: "hidden",
|
|
954
|
+
background: tt.cardBg,
|
|
955
|
+
boxShadow: isDark ? "none" : "0 1px 2px rgba(15,23,42,0.04)"
|
|
956
|
+
},
|
|
957
|
+
children: [
|
|
958
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
959
|
+
"div",
|
|
960
|
+
{
|
|
961
|
+
style: {
|
|
962
|
+
width: 4,
|
|
963
|
+
alignSelf: "stretch",
|
|
964
|
+
background: accentColor,
|
|
965
|
+
flexShrink: 0
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
),
|
|
969
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { flex: 1, minWidth: 0, padding: "8px 10px" }, children: [
|
|
970
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
971
|
+
"div",
|
|
972
|
+
{
|
|
973
|
+
style: {
|
|
974
|
+
fontSize: 12,
|
|
975
|
+
fontWeight: 600,
|
|
976
|
+
color: tt.textPrimary,
|
|
977
|
+
marginBottom: connected ? 3 : 0,
|
|
978
|
+
overflow: "hidden",
|
|
979
|
+
textOverflow: "ellipsis",
|
|
980
|
+
whiteSpace: "nowrap"
|
|
981
|
+
},
|
|
982
|
+
children: ans
|
|
983
|
+
}
|
|
984
|
+
),
|
|
985
|
+
connected && targetNode && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { fontSize: 11, color: accentColor, opacity: 0.85 }, children: [
|
|
986
|
+
"\u2192 ",
|
|
987
|
+
targetNode.label
|
|
988
|
+
] }),
|
|
989
|
+
!connected && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { fontSize: 10, color: tt.textMuted, fontStyle: "italic" }, children: "drag port to connect" })
|
|
990
|
+
] }),
|
|
991
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
992
|
+
"div",
|
|
993
|
+
{
|
|
994
|
+
style: { display: "flex", flexDirection: "column", padding: "4px 2px", gap: 2 },
|
|
995
|
+
children: [
|
|
996
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
997
|
+
"button",
|
|
998
|
+
{
|
|
999
|
+
onClick: () => moveAnswer(i, -1),
|
|
1000
|
+
disabled: i === 0,
|
|
1001
|
+
style: {
|
|
1002
|
+
background: "none",
|
|
1003
|
+
border: "none",
|
|
1004
|
+
color: tt.textMuted,
|
|
1005
|
+
cursor: "pointer",
|
|
1006
|
+
fontSize: 11,
|
|
1007
|
+
padding: "2px 4px",
|
|
1008
|
+
opacity: i === 0 ? 0.3 : 1
|
|
1009
|
+
},
|
|
1010
|
+
children: "\u2191"
|
|
1011
|
+
}
|
|
1012
|
+
),
|
|
1013
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1014
|
+
"button",
|
|
1015
|
+
{
|
|
1016
|
+
onClick: () => moveAnswer(i, 1),
|
|
1017
|
+
disabled: i === answers.length - 1,
|
|
1018
|
+
style: {
|
|
1019
|
+
background: "none",
|
|
1020
|
+
border: "none",
|
|
1021
|
+
color: tt.textMuted,
|
|
1022
|
+
cursor: "pointer",
|
|
1023
|
+
fontSize: 11,
|
|
1024
|
+
padding: "2px 4px",
|
|
1025
|
+
opacity: i === answers.length - 1 ? 0.3 : 1
|
|
1026
|
+
},
|
|
1027
|
+
children: "\u2193"
|
|
1028
|
+
}
|
|
1029
|
+
)
|
|
1030
|
+
]
|
|
1031
|
+
}
|
|
1032
|
+
),
|
|
1033
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1034
|
+
"button",
|
|
1035
|
+
{
|
|
1036
|
+
onClick: () => removeAnswer(ans),
|
|
1037
|
+
style: {
|
|
1038
|
+
background: "none",
|
|
1039
|
+
border: "none",
|
|
1040
|
+
color: tt.textMuted,
|
|
1041
|
+
cursor: "pointer",
|
|
1042
|
+
fontSize: 12,
|
|
1043
|
+
padding: "8px 10px",
|
|
1044
|
+
flexShrink: 0
|
|
1045
|
+
},
|
|
1046
|
+
title: "Remove",
|
|
1047
|
+
children: "\u2715"
|
|
1048
|
+
}
|
|
1049
|
+
)
|
|
1050
|
+
]
|
|
1051
|
+
},
|
|
1052
|
+
ans + i
|
|
1053
|
+
);
|
|
1054
|
+
}),
|
|
1055
|
+
addingAnswer ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1056
|
+
"div",
|
|
1057
|
+
{
|
|
1058
|
+
role: "group",
|
|
1059
|
+
"aria-label": "Add answer form",
|
|
1060
|
+
onKeyDown: (e) => {
|
|
1061
|
+
if (e.key === "Escape") {
|
|
1062
|
+
setAddingAnswer(false);
|
|
1063
|
+
setNewAnswer("");
|
|
1064
|
+
}
|
|
1065
|
+
},
|
|
1066
|
+
style: {
|
|
1067
|
+
marginTop: 10,
|
|
1068
|
+
background: tt.addFormBg,
|
|
1069
|
+
borderRadius: 10,
|
|
1070
|
+
padding: 12,
|
|
1071
|
+
border: `1.5px solid ${accentBorder}`
|
|
1072
|
+
},
|
|
1073
|
+
children: [
|
|
1074
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1075
|
+
"input",
|
|
1076
|
+
{
|
|
1077
|
+
autoFocus: true,
|
|
1078
|
+
value: newAnswer,
|
|
1079
|
+
onChange: (e) => setNewAnswer(e.target.value),
|
|
1080
|
+
onKeyDown: (e) => e.key === "Enter" && addAnswer(),
|
|
1081
|
+
placeholder: "Answer text\u2026",
|
|
1082
|
+
style: { ...inputStyle, marginBottom: 8 }
|
|
1083
|
+
}
|
|
1084
|
+
),
|
|
1085
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", gap: 6 }, children: [
|
|
1086
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { onClick: addAnswer, style: addBtnStyle, children: "Add Answer" }),
|
|
1087
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1088
|
+
"button",
|
|
1089
|
+
{
|
|
1090
|
+
onClick: () => {
|
|
1091
|
+
setAddingAnswer(false);
|
|
1092
|
+
setNewAnswer("");
|
|
1093
|
+
},
|
|
1094
|
+
style: cancelBtnStyle,
|
|
1095
|
+
children: "Cancel"
|
|
1096
|
+
}
|
|
1097
|
+
)
|
|
1098
|
+
] })
|
|
1099
|
+
]
|
|
1100
|
+
}
|
|
1101
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("button", { onClick: () => setAddingAnswer(true), style: addTriggerStyle, children: [
|
|
1102
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { fontSize: 16, lineHeight: 1 }, children: "+" }),
|
|
1103
|
+
" Add Answer"
|
|
1104
|
+
] }),
|
|
1105
|
+
answers.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1106
|
+
"div",
|
|
1107
|
+
{
|
|
1108
|
+
style: {
|
|
1109
|
+
marginTop: 12,
|
|
1110
|
+
padding: "8px 10px",
|
|
1111
|
+
background: isDark ? "rgba(251,191,36,0.06)" : "#fef9f0",
|
|
1112
|
+
borderRadius: 8,
|
|
1113
|
+
border: `1px solid ${accentBorder}`
|
|
1114
|
+
},
|
|
1115
|
+
children: [
|
|
1116
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1117
|
+
"div",
|
|
1118
|
+
{
|
|
1119
|
+
style: {
|
|
1120
|
+
fontSize: 10,
|
|
1121
|
+
fontWeight: 700,
|
|
1122
|
+
color: accentColor,
|
|
1123
|
+
textTransform: "uppercase",
|
|
1124
|
+
letterSpacing: 0.6,
|
|
1125
|
+
marginBottom: 4
|
|
1126
|
+
},
|
|
1127
|
+
children: "How to connect"
|
|
1128
|
+
}
|
|
1129
|
+
),
|
|
1130
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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." })
|
|
1131
|
+
]
|
|
1132
|
+
}
|
|
1133
|
+
)
|
|
1134
|
+
]
|
|
1135
|
+
}
|
|
1136
|
+
),
|
|
1137
|
+
!isQuestion2 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1138
|
+
"section",
|
|
1139
|
+
{
|
|
1140
|
+
style: { padding: "14px 16px", borderBottom: `1px solid ${tt.sectionBorder}`, flex: 1 },
|
|
1141
|
+
children: [
|
|
1142
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1143
|
+
"div",
|
|
1144
|
+
{
|
|
1145
|
+
style: {
|
|
1146
|
+
display: "flex",
|
|
1147
|
+
alignItems: "center",
|
|
1148
|
+
justifyContent: "space-between",
|
|
1149
|
+
marginBottom: 10
|
|
1150
|
+
},
|
|
1151
|
+
children: [
|
|
1152
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1153
|
+
"label",
|
|
1154
|
+
{
|
|
1155
|
+
style: {
|
|
1156
|
+
display: "block",
|
|
1157
|
+
fontSize: 10,
|
|
1158
|
+
fontWeight: 700,
|
|
1159
|
+
color: tt.labelText,
|
|
1160
|
+
textTransform: "uppercase",
|
|
1161
|
+
letterSpacing: 0.8
|
|
1162
|
+
},
|
|
1163
|
+
children: "Branches"
|
|
1164
|
+
}
|
|
1165
|
+
),
|
|
1166
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1167
|
+
"span",
|
|
1168
|
+
{
|
|
1169
|
+
style: {
|
|
1170
|
+
fontSize: 11,
|
|
1171
|
+
color: tt.textMuted,
|
|
1172
|
+
background: isDark ? "#0f172a" : "#f1f5f9",
|
|
1173
|
+
padding: "1px 7px",
|
|
1174
|
+
borderRadius: 99,
|
|
1175
|
+
fontWeight: 600
|
|
1176
|
+
},
|
|
1177
|
+
children: outEdges.length
|
|
1178
|
+
}
|
|
1179
|
+
)
|
|
1180
|
+
]
|
|
1181
|
+
}
|
|
1182
|
+
),
|
|
1183
|
+
outEdges.length === 0 && !addingBranch && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1184
|
+
"div",
|
|
1185
|
+
{
|
|
1186
|
+
style: {
|
|
1187
|
+
fontSize: 12,
|
|
1188
|
+
color: tt.textMuted,
|
|
1189
|
+
textAlign: "center",
|
|
1190
|
+
padding: "16px 0",
|
|
1191
|
+
fontStyle: "italic"
|
|
1192
|
+
},
|
|
1193
|
+
children: "No outgoing connections yet"
|
|
1194
|
+
}
|
|
1195
|
+
),
|
|
1196
|
+
outEdges.map((edge) => {
|
|
1197
|
+
const target = model.nodes.find((n) => n.id === edge.to);
|
|
1198
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1199
|
+
"div",
|
|
1200
|
+
{
|
|
1201
|
+
style: {
|
|
1202
|
+
display: "flex",
|
|
1203
|
+
alignItems: "flex-start",
|
|
1204
|
+
gap: 0,
|
|
1205
|
+
marginBottom: 8,
|
|
1206
|
+
borderRadius: 12,
|
|
1207
|
+
border: `1px solid ${tt.cardBorder}`,
|
|
1208
|
+
overflow: "hidden",
|
|
1209
|
+
background: tt.cardBg,
|
|
1210
|
+
boxShadow: isDark ? "none" : "0 1px 2px rgba(15,23,42,0.04)"
|
|
1211
|
+
},
|
|
1212
|
+
children: [
|
|
1213
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1214
|
+
"div",
|
|
1215
|
+
{
|
|
1216
|
+
style: {
|
|
1217
|
+
width: 4,
|
|
1218
|
+
alignSelf: "stretch",
|
|
1219
|
+
background: accentColor,
|
|
1220
|
+
flexShrink: 0
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
),
|
|
1224
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { flex: 1, minWidth: 0, padding: "8px 10px" }, children: [
|
|
1225
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1226
|
+
"div",
|
|
1227
|
+
{
|
|
1228
|
+
style: {
|
|
1229
|
+
fontSize: 12,
|
|
1230
|
+
fontWeight: 600,
|
|
1231
|
+
color: tt.textPrimary,
|
|
1232
|
+
marginBottom: 5,
|
|
1233
|
+
overflow: "hidden",
|
|
1234
|
+
textOverflow: "ellipsis",
|
|
1235
|
+
whiteSpace: "nowrap"
|
|
1236
|
+
},
|
|
1237
|
+
children: [
|
|
1238
|
+
"\u2192 ",
|
|
1239
|
+
target?.label ?? edge.to
|
|
1240
|
+
]
|
|
1241
|
+
}
|
|
1242
|
+
),
|
|
1243
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1244
|
+
"input",
|
|
1245
|
+
{
|
|
1246
|
+
value: edge.label ?? "",
|
|
1247
|
+
onChange: (e) => updateEdgeLabel(edge.id, e.target.value),
|
|
1248
|
+
placeholder: "Edge label (optional)",
|
|
1249
|
+
style: { ...inputStyle, fontSize: 11, padding: "4px 8px" }
|
|
1250
|
+
}
|
|
1251
|
+
)
|
|
1252
|
+
] }),
|
|
1253
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1254
|
+
"button",
|
|
1255
|
+
{
|
|
1256
|
+
onClick: () => removeEdge(edge.id),
|
|
1257
|
+
style: {
|
|
1258
|
+
background: "none",
|
|
1259
|
+
border: "none",
|
|
1260
|
+
color: tt.textMuted,
|
|
1261
|
+
cursor: "pointer",
|
|
1262
|
+
fontSize: 12,
|
|
1263
|
+
padding: "8px 10px",
|
|
1264
|
+
flexShrink: 0
|
|
1265
|
+
},
|
|
1266
|
+
title: "Remove",
|
|
1267
|
+
children: "\u2715"
|
|
1268
|
+
}
|
|
1269
|
+
)
|
|
1270
|
+
]
|
|
1271
|
+
},
|
|
1272
|
+
edge.id
|
|
1273
|
+
);
|
|
1274
|
+
}),
|
|
1275
|
+
addingBranch ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1276
|
+
"div",
|
|
1277
|
+
{
|
|
1278
|
+
role: "group",
|
|
1279
|
+
"aria-label": "Add branch form",
|
|
1280
|
+
onKeyDown: (e) => {
|
|
1281
|
+
if (e.key === "Escape") setAddingBranch(false);
|
|
1282
|
+
},
|
|
1283
|
+
style: {
|
|
1284
|
+
marginTop: 10,
|
|
1285
|
+
background: tt.addFormBg,
|
|
1286
|
+
borderRadius: 10,
|
|
1287
|
+
padding: 12,
|
|
1288
|
+
border: `1.5px solid ${accentBorder}`
|
|
1289
|
+
},
|
|
1290
|
+
children: [
|
|
1291
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { display: "flex", gap: 6, marginBottom: 10 }, children: ["new", "existing"].map((mode) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1292
|
+
"button",
|
|
1293
|
+
{
|
|
1294
|
+
onClick: () => setBranchMode(mode),
|
|
1295
|
+
style: {
|
|
1296
|
+
flex: 1,
|
|
1297
|
+
padding: "5px 0",
|
|
1298
|
+
border: "none",
|
|
1299
|
+
borderRadius: 6,
|
|
1300
|
+
cursor: "pointer",
|
|
1301
|
+
fontSize: 11,
|
|
1302
|
+
fontWeight: 600,
|
|
1303
|
+
background: branchMode === mode ? accentColor : tt.btnSecBg,
|
|
1304
|
+
color: branchMode === mode ? "#fff" : tt.btnSecText
|
|
1305
|
+
},
|
|
1306
|
+
children: mode === "new" ? `+ New step` : "Existing step"
|
|
1307
|
+
},
|
|
1308
|
+
mode
|
|
1309
|
+
)) }),
|
|
1310
|
+
branchMode === "new" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1311
|
+
"input",
|
|
1312
|
+
{
|
|
1313
|
+
autoFocus: true,
|
|
1314
|
+
value: branchLabel,
|
|
1315
|
+
onChange: (e) => setBranchLabel(e.target.value),
|
|
1316
|
+
onKeyDown: (e) => e.key === "Enter" && addBranch(),
|
|
1317
|
+
placeholder: "New step name\u2026",
|
|
1318
|
+
style: { ...inputStyle, marginBottom: 6 }
|
|
1319
|
+
}
|
|
1320
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1321
|
+
"select",
|
|
1322
|
+
{
|
|
1323
|
+
value: branchTarget,
|
|
1324
|
+
onChange: (e) => setBranchTarget(e.target.value),
|
|
1325
|
+
style: { ...inputStyle, marginBottom: 6, appearance: "none" },
|
|
1326
|
+
children: [
|
|
1327
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: "Choose a step\u2026" }),
|
|
1328
|
+
otherNodes.map((n) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: n.id, children: n.label }, n.id))
|
|
1329
|
+
]
|
|
1330
|
+
}
|
|
1331
|
+
),
|
|
1332
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1333
|
+
"input",
|
|
1334
|
+
{
|
|
1335
|
+
value: branchEdgeLabel,
|
|
1336
|
+
onChange: (e) => setBranchEdgeLabel(e.target.value),
|
|
1337
|
+
placeholder: "Edge label (optional)",
|
|
1338
|
+
style: { ...inputStyle, marginBottom: 10 }
|
|
1339
|
+
}
|
|
1340
|
+
),
|
|
1341
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", gap: 6 }, children: [
|
|
1342
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("button", { onClick: addBranch, style: addBtnStyle, children: [
|
|
1343
|
+
"Add ",
|
|
1344
|
+
branchTerm
|
|
1345
|
+
] }),
|
|
1346
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { onClick: () => setAddingBranch(false), style: cancelBtnStyle, children: "Cancel" })
|
|
1347
|
+
] })
|
|
1348
|
+
]
|
|
1349
|
+
}
|
|
1350
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("button", { onClick: () => setAddingBranch(true), style: addTriggerStyle, children: [
|
|
1351
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { fontSize: 16, lineHeight: 1 }, children: "+" }),
|
|
1352
|
+
" Add ",
|
|
1353
|
+
branchTerm
|
|
1354
|
+
] })
|
|
1355
|
+
]
|
|
1356
|
+
}
|
|
1357
|
+
)
|
|
848
1358
|
] })
|
|
849
|
-
]
|
|
850
|
-
|
|
851
|
-
|
|
1359
|
+
]
|
|
1360
|
+
}
|
|
1361
|
+
);
|
|
852
1362
|
}
|
|
853
1363
|
|
|
854
1364
|
// src/ui/SequenceEditor.tsx
|
|
@@ -903,26 +1413,33 @@ function SequenceCanvas(props) {
|
|
|
903
1413
|
return next;
|
|
904
1414
|
}, [messages, drag]);
|
|
905
1415
|
if (actors.length === 0 && messages.length === 0) {
|
|
906
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1416
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1417
|
+
"div",
|
|
1418
|
+
{
|
|
1419
|
+
style: {
|
|
1420
|
+
position: "absolute",
|
|
1421
|
+
inset: 0,
|
|
1422
|
+
display: "flex",
|
|
1423
|
+
flexDirection: "column",
|
|
1424
|
+
alignItems: "center",
|
|
1425
|
+
justifyContent: "center",
|
|
1426
|
+
gap: 10,
|
|
1427
|
+
color: t.textMuted,
|
|
1428
|
+
pointerEvents: "none"
|
|
1429
|
+
},
|
|
1430
|
+
children: [
|
|
1431
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { fontSize: 36, opacity: 0.15, color: t.textPrimary }, children: "\u2194" }),
|
|
1432
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { fontSize: 13, fontWeight: 500 }, children: [
|
|
1433
|
+
"Click ",
|
|
1434
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { style: { color: INDIGO }, children: "+ Actor" }),
|
|
1435
|
+
" then",
|
|
1436
|
+
" ",
|
|
1437
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { style: { color: INDIGO }, children: "+ Message" }),
|
|
1438
|
+
" to start"
|
|
1439
|
+
] })
|
|
1440
|
+
]
|
|
1441
|
+
}
|
|
1442
|
+
);
|
|
926
1443
|
}
|
|
927
1444
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
928
1445
|
"svg",
|
|
@@ -930,12 +1447,28 @@ function SequenceCanvas(props) {
|
|
|
930
1447
|
ref: svgRef,
|
|
931
1448
|
width: totalW,
|
|
932
1449
|
height: totalH,
|
|
933
|
-
style: {
|
|
1450
|
+
style: {
|
|
1451
|
+
display: "block",
|
|
1452
|
+
cursor: drag?.active ? "grabbing" : "default",
|
|
1453
|
+
userSelect: "none"
|
|
1454
|
+
},
|
|
934
1455
|
children: [
|
|
935
1456
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("defs", { children: [
|
|
936
1457
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("pattern", { id: "seqdots", x: "0", y: "0", width: "24", height: "24", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("circle", { cx: 12, cy: 12, r: 1.1, fill: t.dot }) }),
|
|
937
1458
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("filter", { id: "seqShadow", x: "-20%", y: "-20%", width: "140%", height: "140%", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("feDropShadow", { dx: 0, dy: 3, stdDeviation: 5, floodColor: shadowColor(isDark) }) }),
|
|
938
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1459
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1460
|
+
"marker",
|
|
1461
|
+
{
|
|
1462
|
+
id: "seqArrow",
|
|
1463
|
+
markerWidth: 9,
|
|
1464
|
+
markerHeight: 7,
|
|
1465
|
+
refX: 8.5,
|
|
1466
|
+
refY: 3.5,
|
|
1467
|
+
orient: "auto",
|
|
1468
|
+
markerUnits: "strokeWidth",
|
|
1469
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: t.arrow })
|
|
1470
|
+
}
|
|
1471
|
+
)
|
|
939
1472
|
] }),
|
|
940
1473
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { width: totalW, height: totalH, fill: "url(#seqdots)" }),
|
|
941
1474
|
actors.map((name) => {
|
|
@@ -984,8 +1517,28 @@ function SequenceCanvas(props) {
|
|
|
984
1517
|
opacity: isDark ? 0.18 : 0.6
|
|
985
1518
|
}
|
|
986
1519
|
),
|
|
987
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
988
|
-
|
|
1520
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1521
|
+
"path",
|
|
1522
|
+
{
|
|
1523
|
+
d,
|
|
1524
|
+
fill: "none",
|
|
1525
|
+
stroke,
|
|
1526
|
+
strokeWidth: 1.5,
|
|
1527
|
+
strokeDasharray: dash,
|
|
1528
|
+
markerEnd: "url(#seqArrow)"
|
|
1529
|
+
}
|
|
1530
|
+
),
|
|
1531
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1532
|
+
"text",
|
|
1533
|
+
{
|
|
1534
|
+
x: startX + loopW + 8,
|
|
1535
|
+
y: loopY + 16,
|
|
1536
|
+
fontSize: 11,
|
|
1537
|
+
fill: selectedHere ? INDIGO : t.textPrimary,
|
|
1538
|
+
fontWeight: 500,
|
|
1539
|
+
children: msg.label
|
|
1540
|
+
}
|
|
1541
|
+
)
|
|
989
1542
|
] }, msg.id);
|
|
990
1543
|
}
|
|
991
1544
|
const labelX = (fromX + toX) / 2;
|
|
@@ -1002,7 +1555,19 @@ function SequenceCanvas(props) {
|
|
|
1002
1555
|
opacity: isDark ? 0.18 : 0.6
|
|
1003
1556
|
}
|
|
1004
1557
|
),
|
|
1005
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1558
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1559
|
+
"line",
|
|
1560
|
+
{
|
|
1561
|
+
x1: fromX,
|
|
1562
|
+
y1: y,
|
|
1563
|
+
x2: toX,
|
|
1564
|
+
y2: y,
|
|
1565
|
+
stroke,
|
|
1566
|
+
strokeWidth: 1.5,
|
|
1567
|
+
strokeDasharray: dash,
|
|
1568
|
+
markerEnd: "url(#seqArrow)"
|
|
1569
|
+
}
|
|
1570
|
+
),
|
|
1006
1571
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1007
1572
|
"rect",
|
|
1008
1573
|
{
|
|
@@ -1016,7 +1581,18 @@ function SequenceCanvas(props) {
|
|
|
1016
1581
|
strokeWidth: selectedHere ? 1.25 : 1
|
|
1017
1582
|
}
|
|
1018
1583
|
),
|
|
1019
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1584
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1585
|
+
"text",
|
|
1586
|
+
{
|
|
1587
|
+
x: labelX,
|
|
1588
|
+
y: y - 5,
|
|
1589
|
+
textAnchor: "middle",
|
|
1590
|
+
fontSize: 11,
|
|
1591
|
+
fill: selectedHere ? INDIGO : t.textPrimary,
|
|
1592
|
+
fontWeight: 500,
|
|
1593
|
+
children: msg.label
|
|
1594
|
+
}
|
|
1595
|
+
)
|
|
1020
1596
|
] }, msg.id);
|
|
1021
1597
|
}),
|
|
1022
1598
|
actors.map((name) => {
|
|
@@ -1356,7 +1932,9 @@ function computeLayout(model) {
|
|
|
1356
1932
|
const h = isQuestion(n, model.variant) ? questionNodeH(n.metadata?.answers ?? []) : NODE_H;
|
|
1357
1933
|
return { node: n, w, h };
|
|
1358
1934
|
});
|
|
1359
|
-
const allPositioned = sized.every(
|
|
1935
|
+
const allPositioned = sized.every(
|
|
1936
|
+
(s2) => typeof s2.node.x === "number" && typeof s2.node.y === "number"
|
|
1937
|
+
);
|
|
1360
1938
|
if (allPositioned) {
|
|
1361
1939
|
for (const s2 of sized) {
|
|
1362
1940
|
boxes.set(s2.node.id, { x: s2.node.x, y: s2.node.y, w: s2.w, h: s2.h });
|
|
@@ -1406,7 +1984,15 @@ function computeLayout(model) {
|
|
|
1406
1984
|
return boxes;
|
|
1407
1985
|
}
|
|
1408
1986
|
function escapeXML(s2) {
|
|
1409
|
-
return s2.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1987
|
+
return s2.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1988
|
+
}
|
|
1989
|
+
function sanitizeForSVG(s2) {
|
|
1990
|
+
let clean = s2;
|
|
1991
|
+
clean = clean.replace(/<\/?[a-zA-Z][^>]*>/g, "");
|
|
1992
|
+
clean = clean.replace(/\b(?:javascript|data|vbscript)\s*:/gi, "");
|
|
1993
|
+
clean = clean.replace(/\bon[a-z]+\s*=/gi, "");
|
|
1994
|
+
clean = clean.replace(/\x00/g, "");
|
|
1995
|
+
return escapeXML(clean);
|
|
1410
1996
|
}
|
|
1411
1997
|
var COLORS = {
|
|
1412
1998
|
bg: "#fafbfc",
|
|
@@ -1425,8 +2011,8 @@ function renderStandardNode(node, box) {
|
|
|
1425
2011
|
const cx = box.x + box.w / 2;
|
|
1426
2012
|
const cy = box.y + box.h / 2;
|
|
1427
2013
|
const shape = node.shape ?? "rectangle";
|
|
1428
|
-
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}">${
|
|
1429
|
-
let shapeEl
|
|
2014
|
+
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>`;
|
|
2015
|
+
let shapeEl;
|
|
1430
2016
|
if (shape === "diamond") {
|
|
1431
2017
|
const pts = `${cx},${box.y} ${box.x + box.w},${cy} ${cx},${box.y + box.h} ${box.x},${cy}`;
|
|
1432
2018
|
shapeEl = `<polygon points="${pts}" fill="${COLORS.nodeFill}" stroke="${COLORS.nodeStroke}" stroke-width="1.25" filter="url(#nodeShadow)"/>`;
|
|
@@ -1446,17 +2032,37 @@ function renderQuestionNode(node, box) {
|
|
|
1446
2032
|
const clipId = `qhdr-${node.id.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
1447
2033
|
const x = box.x, y = box.y, w = box.w, h = box.h;
|
|
1448
2034
|
const parts = [];
|
|
1449
|
-
parts.push(
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
parts.push(
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
parts.push(
|
|
1456
|
-
|
|
1457
|
-
|
|
2035
|
+
parts.push(
|
|
2036
|
+
`<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="14" fill="${COLORS.nodeFill}" stroke="${COLORS.amberLine}" stroke-width="1.5" filter="url(#nodeShadow)"/>`
|
|
2037
|
+
);
|
|
2038
|
+
parts.push(
|
|
2039
|
+
`<defs><clipPath id="${clipId}"><rect x="${x}" y="${y}" width="${w}" height="${Q_BASE_H}" rx="14"/></clipPath></defs>`
|
|
2040
|
+
);
|
|
2041
|
+
parts.push(
|
|
2042
|
+
`<rect x="${x}" y="${y}" width="${w}" height="${Q_BASE_H}" fill="${COLORS.amberSoft}" clip-path="url(#${clipId})"/>`
|
|
2043
|
+
);
|
|
2044
|
+
parts.push(
|
|
2045
|
+
`<rect x="${x}" y="${y}" width="4" height="${Q_BASE_H}" rx="2" fill="${COLORS.amber}"/>`
|
|
2046
|
+
);
|
|
2047
|
+
parts.push(
|
|
2048
|
+
`<rect x="${x + 12}" y="${y + 14}" width="28" height="28" rx="8" fill="${COLORS.amber}"/>`
|
|
2049
|
+
);
|
|
2050
|
+
parts.push(
|
|
2051
|
+
`<text x="${x + 26}" y="${y + 33}" text-anchor="middle" font-size="15" font-weight="900" fill="white">?</text>`
|
|
2052
|
+
);
|
|
2053
|
+
parts.push(
|
|
2054
|
+
`<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>`
|
|
2055
|
+
);
|
|
2056
|
+
parts.push(
|
|
2057
|
+
`<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>`
|
|
2058
|
+
);
|
|
2059
|
+
parts.push(
|
|
2060
|
+
`<line x1="${x}" y1="${y + Q_BASE_H}" x2="${x + w}" y2="${y + Q_BASE_H}" stroke="${COLORS.amberLine}" stroke-width="1"/>`
|
|
2061
|
+
);
|
|
1458
2062
|
if (answers.length === 0) {
|
|
1459
|
-
parts.push(
|
|
2063
|
+
parts.push(
|
|
2064
|
+
`<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>`
|
|
2065
|
+
);
|
|
1460
2066
|
} else {
|
|
1461
2067
|
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
1462
2068
|
answers.forEach((ans, i) => {
|
|
@@ -1469,10 +2075,18 @@ function renderQuestionNode(node, box) {
|
|
|
1469
2075
|
const letter = i < 26 ? letters[i] : `${i + 1}`;
|
|
1470
2076
|
const maxChars = Math.max(2, Math.floor((cW - 20) / 7.5));
|
|
1471
2077
|
const displayAns = ans.length > maxChars ? ans.slice(0, maxChars - 1) + "\u2026" : ans;
|
|
1472
|
-
parts.push(
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
parts.push(
|
|
2078
|
+
parts.push(
|
|
2079
|
+
`<rect x="${cardX}" y="${cardY}" width="${cW}" height="${cardH}" rx="8" fill="${COLORS.amberCardBg}" stroke="${COLORS.amberLine}" stroke-width="1"/>`
|
|
2080
|
+
);
|
|
2081
|
+
parts.push(
|
|
2082
|
+
`<rect x="${cx - 11}" y="${cardY + 7}" width="22" height="22" rx="6" fill="#fef3c7"/>`
|
|
2083
|
+
);
|
|
2084
|
+
parts.push(
|
|
2085
|
+
`<text x="${cx}" y="${cardY + 22}" text-anchor="middle" font-size="10" font-weight="800" fill="${COLORS.amber}">${sanitizeForSVG(letter)}</text>`
|
|
2086
|
+
);
|
|
2087
|
+
parts.push(
|
|
2088
|
+
`<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>`
|
|
2089
|
+
);
|
|
1476
2090
|
});
|
|
1477
2091
|
}
|
|
1478
2092
|
return parts.join("");
|
|
@@ -1510,7 +2124,7 @@ function renderEdge(edge, boxes, variant, nodes) {
|
|
|
1510
2124
|
const midY = (y1 + y2) / 2;
|
|
1511
2125
|
const labelW = estimateTextW(edge.label, 7) + 14;
|
|
1512
2126
|
out += `<rect x="${midX - labelW / 2}" y="${midY - 11}" width="${labelW}" height="18" rx="9" fill="${COLORS.bg}" stroke="${COLORS.nodeStroke}" stroke-width="1"/>`;
|
|
1513
|
-
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}">${
|
|
2127
|
+
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>`;
|
|
1514
2128
|
}
|
|
1515
2129
|
return out;
|
|
1516
2130
|
}
|
|
@@ -1536,7 +2150,7 @@ function toSVG(model) {
|
|
|
1536
2150
|
`</marker>`,
|
|
1537
2151
|
`</defs>`
|
|
1538
2152
|
].join("");
|
|
1539
|
-
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}">${
|
|
2153
|
+
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>` : "";
|
|
1540
2154
|
const edges = model.edges.map((e) => renderEdge(e, boxes, model.variant, model.nodes)).join("\n");
|
|
1541
2155
|
const nodes = model.nodes.map((n) => {
|
|
1542
2156
|
const b = boxes.get(n.id);
|
|
@@ -1553,7 +2167,9 @@ ${nodes}
|
|
|
1553
2167
|
}
|
|
1554
2168
|
async function toPNG(model) {
|
|
1555
2169
|
if (typeof document === "undefined") {
|
|
1556
|
-
throw new Error(
|
|
2170
|
+
throw new Error(
|
|
2171
|
+
"toPNG requires a browser environment. For Node/Bun server use, pipe toSVG() through @resvg/resvg-js."
|
|
2172
|
+
);
|
|
1557
2173
|
}
|
|
1558
2174
|
const svg = toSVG(model);
|
|
1559
2175
|
const blob = new Blob([svg], { type: "image/svg+xml" });
|
|
@@ -1569,7 +2185,10 @@ async function toPNG(model) {
|
|
|
1569
2185
|
ctx.scale(scale, scale);
|
|
1570
2186
|
ctx.drawImage(img, 0, 0);
|
|
1571
2187
|
URL.revokeObjectURL(url);
|
|
1572
|
-
canvas.toBlob(
|
|
2188
|
+
canvas.toBlob(
|
|
2189
|
+
(b) => b ? resolve(b) : reject(new Error("Canvas toBlob failed")),
|
|
2190
|
+
"image/png"
|
|
2191
|
+
);
|
|
1573
2192
|
};
|
|
1574
2193
|
img.onerror = () => {
|
|
1575
2194
|
URL.revokeObjectURL(url);
|
|
@@ -1581,40 +2200,43 @@ async function toPNG(model) {
|
|
|
1581
2200
|
|
|
1582
2201
|
// src/ui/hooks/useExporters.ts
|
|
1583
2202
|
function useExporters(model, onExport, filename = "diagram", onSuccess) {
|
|
1584
|
-
return (0, import_react7.useCallback)(
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
2203
|
+
return (0, import_react7.useCallback)(
|
|
2204
|
+
async (format) => {
|
|
2205
|
+
let content;
|
|
2206
|
+
switch (format) {
|
|
2207
|
+
case "mermaid":
|
|
2208
|
+
content = toMermaid(model);
|
|
2209
|
+
break;
|
|
2210
|
+
case "plantuml":
|
|
2211
|
+
content = toPlantUML(model);
|
|
2212
|
+
break;
|
|
2213
|
+
case "json":
|
|
2214
|
+
content = toJSON(model);
|
|
2215
|
+
break;
|
|
2216
|
+
case "svg":
|
|
2217
|
+
content = toSVG(model);
|
|
2218
|
+
break;
|
|
2219
|
+
case "png":
|
|
2220
|
+
content = await toPNG(model);
|
|
2221
|
+
break;
|
|
2222
|
+
default:
|
|
2223
|
+
return;
|
|
2224
|
+
}
|
|
2225
|
+
if (onExport) {
|
|
2226
|
+
onExport(format, content);
|
|
2227
|
+
onSuccess?.(`Exported as ${format.toUpperCase()}`);
|
|
1603
2228
|
return;
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
URL.revokeObjectURL(url);
|
|
1616
|
-
onSuccess?.(`Downloaded ${a.download}`);
|
|
1617
|
-
}, [model, onExport, filename, onSuccess]);
|
|
2229
|
+
}
|
|
2230
|
+
const url = content instanceof Blob ? URL.createObjectURL(content) : URL.createObjectURL(new Blob([content], { type: "text/plain" }));
|
|
2231
|
+
const a = document.createElement("a");
|
|
2232
|
+
a.href = url;
|
|
2233
|
+
a.download = `${filename}.${format === "plantuml" ? "puml" : format}`;
|
|
2234
|
+
a.click();
|
|
2235
|
+
URL.revokeObjectURL(url);
|
|
2236
|
+
onSuccess?.(`Downloaded ${a.download}`);
|
|
2237
|
+
},
|
|
2238
|
+
[model, onExport, filename, onSuccess]
|
|
2239
|
+
);
|
|
1618
2240
|
}
|
|
1619
2241
|
|
|
1620
2242
|
// src/ui/hooks/useImporter.ts
|
|
@@ -1631,7 +2253,15 @@ var Model = class _Model {
|
|
|
1631
2253
|
* @param variant Optional UI variant (flowchart models only).
|
|
1632
2254
|
*/
|
|
1633
2255
|
constructor(type, title, variant) {
|
|
1634
|
-
this.data = {
|
|
2256
|
+
this.data = {
|
|
2257
|
+
type,
|
|
2258
|
+
...variant ? { variant } : {},
|
|
2259
|
+
title,
|
|
2260
|
+
nodes: [],
|
|
2261
|
+
edges: [],
|
|
2262
|
+
actors: [],
|
|
2263
|
+
messages: []
|
|
2264
|
+
};
|
|
1635
2265
|
}
|
|
1636
2266
|
/**
|
|
1637
2267
|
* Rehydrate a `Model` from a previously serialized `DiagramModel`. The
|
|
@@ -1667,7 +2297,8 @@ var Model = class _Model {
|
|
|
1667
2297
|
updateNode(id, patch) {
|
|
1668
2298
|
const node = this.data.nodes.find((n) => n.id === id);
|
|
1669
2299
|
if (!node) throw new Error(`Node "${id}" not found`);
|
|
1670
|
-
|
|
2300
|
+
const { __proto__, constructor, ...safe } = patch;
|
|
2301
|
+
Object.assign(node, safe);
|
|
1671
2302
|
return this;
|
|
1672
2303
|
}
|
|
1673
2304
|
/**
|
|
@@ -1707,15 +2338,35 @@ var Model = class _Model {
|
|
|
1707
2338
|
const errors = [];
|
|
1708
2339
|
const nodeIds = /* @__PURE__ */ new Set();
|
|
1709
2340
|
for (const n of this.data.nodes) {
|
|
1710
|
-
if (nodeIds.has(n.id))
|
|
2341
|
+
if (nodeIds.has(n.id))
|
|
2342
|
+
errors.push({
|
|
2343
|
+
kind: "duplicate-node-id",
|
|
2344
|
+
id: n.id,
|
|
2345
|
+
message: `Duplicate node id "${n.id}"`
|
|
2346
|
+
});
|
|
1711
2347
|
nodeIds.add(n.id);
|
|
1712
2348
|
}
|
|
1713
2349
|
const edgeIds = /* @__PURE__ */ new Set();
|
|
1714
2350
|
for (const e of this.data.edges) {
|
|
1715
|
-
if (edgeIds.has(e.id))
|
|
2351
|
+
if (edgeIds.has(e.id))
|
|
2352
|
+
errors.push({
|
|
2353
|
+
kind: "duplicate-edge-id",
|
|
2354
|
+
id: e.id,
|
|
2355
|
+
message: `Duplicate edge id "${e.id}"`
|
|
2356
|
+
});
|
|
1716
2357
|
edgeIds.add(e.id);
|
|
1717
|
-
if (!nodeIds.has(e.from))
|
|
1718
|
-
|
|
2358
|
+
if (!nodeIds.has(e.from))
|
|
2359
|
+
errors.push({
|
|
2360
|
+
kind: "dangling-from",
|
|
2361
|
+
id: e.id,
|
|
2362
|
+
message: `Edge "${e.id}" references unknown source node "${e.from}"`
|
|
2363
|
+
});
|
|
2364
|
+
if (!nodeIds.has(e.to))
|
|
2365
|
+
errors.push({
|
|
2366
|
+
kind: "dangling-to",
|
|
2367
|
+
id: e.id,
|
|
2368
|
+
message: `Edge "${e.id}" references unknown target node "${e.to}"`
|
|
2369
|
+
});
|
|
1719
2370
|
}
|
|
1720
2371
|
return errors;
|
|
1721
2372
|
}
|
|
@@ -1749,6 +2400,25 @@ var Model = class _Model {
|
|
|
1749
2400
|
}
|
|
1750
2401
|
};
|
|
1751
2402
|
|
|
2403
|
+
// src/core/sanitize.ts
|
|
2404
|
+
var MAX_LABEL_LENGTH = 2e3;
|
|
2405
|
+
var MAX_NODES = 500;
|
|
2406
|
+
var MAX_EDGES = 2e3;
|
|
2407
|
+
var MAX_ACTORS = 100;
|
|
2408
|
+
var MAX_MESSAGES = 2e3;
|
|
2409
|
+
var MAX_IMPORT_LENGTH = 2 * 1024 * 1024;
|
|
2410
|
+
function sanitizeLabel(raw) {
|
|
2411
|
+
let s2 = raw;
|
|
2412
|
+
s2 = s2.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
|
|
2413
|
+
s2 = s2.replace(/<\/?[a-zA-Z][^>]*>/g, "");
|
|
2414
|
+
s2 = s2.replace(/\b(?:javascript|data|vbscript)\s*:/gi, "");
|
|
2415
|
+
s2 = s2.replace(/\bon[a-z]+\s*=/gi, "");
|
|
2416
|
+
if (s2.length > MAX_LABEL_LENGTH) {
|
|
2417
|
+
s2 = s2.slice(0, MAX_LABEL_LENGTH);
|
|
2418
|
+
}
|
|
2419
|
+
return s2;
|
|
2420
|
+
}
|
|
2421
|
+
|
|
1752
2422
|
// src/importers/mermaid.ts
|
|
1753
2423
|
function parseNodeDecl(raw) {
|
|
1754
2424
|
const patterns = [
|
|
@@ -1760,7 +2430,7 @@ function parseNodeDecl(raw) {
|
|
|
1760
2430
|
];
|
|
1761
2431
|
for (const [re, shape] of patterns) {
|
|
1762
2432
|
const m = raw.match(re);
|
|
1763
|
-
if (m) return { id: m[1], label: m[2].replace(/^["']|["']$/g, ""), shape };
|
|
2433
|
+
if (m) return { id: m[1], label: sanitizeLabel(m[2].replace(/^["']|["']$/g, "")), shape };
|
|
1764
2434
|
}
|
|
1765
2435
|
return null;
|
|
1766
2436
|
}
|
|
@@ -1775,8 +2445,11 @@ function parseFlowchart(lines) {
|
|
|
1775
2445
|
const model = new Model("flowchart");
|
|
1776
2446
|
const nodeMap = /* @__PURE__ */ new Map();
|
|
1777
2447
|
const groupStack = [];
|
|
2448
|
+
let edgeCount = 0;
|
|
1778
2449
|
const ensureNode = (id, group) => {
|
|
1779
2450
|
if (!nodeMap.has(id)) {
|
|
2451
|
+
if (nodeMap.size >= MAX_NODES)
|
|
2452
|
+
throw new Error(`Import aborted: diagram exceeds the maximum of ${MAX_NODES} nodes`);
|
|
1780
2453
|
nodeMap.set(id, true);
|
|
1781
2454
|
const metadata = group ? { group } : void 0;
|
|
1782
2455
|
model.addNode({ id, label: id, shape: "rectangle", ...metadata ? { metadata } : {} });
|
|
@@ -1785,7 +2458,8 @@ function parseFlowchart(lines) {
|
|
|
1785
2458
|
for (const line of lines) {
|
|
1786
2459
|
const trimmed = line.trim();
|
|
1787
2460
|
if (!trimmed) continue;
|
|
1788
|
-
if (trimmed.startsWith("%%") || trimmed.startsWith("graph") || trimmed.startsWith("flowchart") || trimmed.startsWith("click ") || trimmed.startsWith("classDef ") || trimmed.startsWith("class ") || trimmed.startsWith("style ") || trimmed.startsWith("linkStyle "))
|
|
2461
|
+
if (trimmed.startsWith("%%") || trimmed.startsWith("graph") || trimmed.startsWith("flowchart") || trimmed.startsWith("click ") || trimmed.startsWith("classDef ") || trimmed.startsWith("class ") || trimmed.startsWith("style ") || trimmed.startsWith("linkStyle "))
|
|
2462
|
+
continue;
|
|
1789
2463
|
const subgraphOpen = trimmed.match(/^subgraph\s+(\S+)/i);
|
|
1790
2464
|
if (subgraphOpen) {
|
|
1791
2465
|
groupStack.push(subgraphOpen[1]);
|
|
@@ -1801,12 +2475,15 @@ function parseFlowchart(lines) {
|
|
|
1801
2475
|
const fromRaw = edgeMatch[1].trim();
|
|
1802
2476
|
const connector = edgeMatch[2];
|
|
1803
2477
|
const label = edgeMatch[3]?.replace(/^["']|["']$/g, "");
|
|
2478
|
+
const sanitizedLabel = label ? sanitizeLabel(label) : void 0;
|
|
1804
2479
|
const toRaw = edgeMatch[4].trim();
|
|
1805
2480
|
const style = detectStyle(connector);
|
|
1806
2481
|
const arrowhead = detectArrowhead(connector);
|
|
1807
2482
|
const fromNode = parseNodeDecl(fromRaw);
|
|
1808
2483
|
const toNode = parseNodeDecl(toRaw);
|
|
1809
2484
|
if (fromNode && !nodeMap.has(fromNode.id)) {
|
|
2485
|
+
if (nodeMap.size >= MAX_NODES)
|
|
2486
|
+
throw new Error(`Import aborted: diagram exceeds the maximum of ${MAX_NODES} nodes`);
|
|
1810
2487
|
nodeMap.set(fromNode.id, true);
|
|
1811
2488
|
const metadata = currentGroup ? { group: currentGroup } : void 0;
|
|
1812
2489
|
model.addNode({ ...fromNode, ...metadata ? { metadata } : {} });
|
|
@@ -1814,19 +2491,24 @@ function parseFlowchart(lines) {
|
|
|
1814
2491
|
ensureNode(fromRaw.replace(/\W.*/, ""), currentGroup);
|
|
1815
2492
|
}
|
|
1816
2493
|
if (toNode && !nodeMap.has(toNode.id)) {
|
|
2494
|
+
if (nodeMap.size >= MAX_NODES)
|
|
2495
|
+
throw new Error(`Import aborted: diagram exceeds the maximum of ${MAX_NODES} nodes`);
|
|
1817
2496
|
nodeMap.set(toNode.id, true);
|
|
1818
2497
|
const metadata = currentGroup ? { group: currentGroup } : void 0;
|
|
1819
2498
|
model.addNode({ ...toNode, ...metadata ? { metadata } : {} });
|
|
1820
2499
|
} else if (!toNode) {
|
|
1821
2500
|
ensureNode(toRaw.replace(/\W.*/, ""), currentGroup);
|
|
1822
2501
|
}
|
|
2502
|
+
if (edgeCount >= MAX_EDGES)
|
|
2503
|
+
throw new Error(`Import aborted: diagram exceeds the maximum of ${MAX_EDGES} edges`);
|
|
2504
|
+
edgeCount++;
|
|
1823
2505
|
const fromId = fromNode?.id ?? fromRaw.replace(/\W.*/, "");
|
|
1824
2506
|
const toId = toNode?.id ?? toRaw.replace(/\W.*/, "");
|
|
1825
2507
|
model.addEdge({
|
|
1826
2508
|
id: nextId("e", model.toJSON().edges),
|
|
1827
2509
|
from: fromId,
|
|
1828
2510
|
to: toId,
|
|
1829
|
-
...
|
|
2511
|
+
...sanitizedLabel ? { label: sanitizedLabel } : {},
|
|
1830
2512
|
style,
|
|
1831
2513
|
...arrowhead === "none" ? { arrowhead } : {}
|
|
1832
2514
|
});
|
|
@@ -1834,6 +2516,8 @@ function parseFlowchart(lines) {
|
|
|
1834
2516
|
}
|
|
1835
2517
|
const nodeDecl = parseNodeDecl(trimmed);
|
|
1836
2518
|
if (nodeDecl && !nodeMap.has(nodeDecl.id)) {
|
|
2519
|
+
if (nodeMap.size >= MAX_NODES)
|
|
2520
|
+
throw new Error(`Import aborted: diagram exceeds the maximum of ${MAX_NODES} nodes`);
|
|
1837
2521
|
nodeMap.set(nodeDecl.id, true);
|
|
1838
2522
|
const metadata = currentGroup ? { group: currentGroup } : void 0;
|
|
1839
2523
|
model.addNode({ ...nodeDecl, ...metadata ? { metadata } : {} });
|
|
@@ -1843,34 +2527,56 @@ function parseFlowchart(lines) {
|
|
|
1843
2527
|
}
|
|
1844
2528
|
function parseSequence(lines, title) {
|
|
1845
2529
|
const model = new Model("sequence", title);
|
|
2530
|
+
let actorCount = 0;
|
|
2531
|
+
let messageCount = 0;
|
|
2532
|
+
const safeAddActor = (name) => {
|
|
2533
|
+
const safeName = sanitizeLabel(name);
|
|
2534
|
+
if (!model.toJSON().actors?.includes(safeName)) {
|
|
2535
|
+
if (actorCount >= MAX_ACTORS)
|
|
2536
|
+
throw new Error(`Import aborted: diagram exceeds the maximum of ${MAX_ACTORS} actors`);
|
|
2537
|
+
actorCount++;
|
|
2538
|
+
}
|
|
2539
|
+
model.addActor(safeName);
|
|
2540
|
+
return safeName;
|
|
2541
|
+
};
|
|
1846
2542
|
for (const line of lines) {
|
|
1847
2543
|
const trimmed = line.trim();
|
|
1848
2544
|
if (!trimmed || trimmed.startsWith("sequenceDiagram") || trimmed.startsWith("%%")) continue;
|
|
1849
2545
|
const participantMatch = trimmed.match(/^participant\s+(.+)$/i);
|
|
1850
2546
|
if (participantMatch) {
|
|
1851
|
-
|
|
2547
|
+
safeAddActor(participantMatch[1].trim());
|
|
1852
2548
|
continue;
|
|
1853
2549
|
}
|
|
1854
2550
|
const actorMatch = trimmed.match(/^actor\s+(.+)$/i);
|
|
1855
2551
|
if (actorMatch) {
|
|
1856
|
-
|
|
2552
|
+
safeAddActor(actorMatch[1].trim());
|
|
1857
2553
|
continue;
|
|
1858
2554
|
}
|
|
1859
2555
|
const msgMatch = trimmed.match(/^(.+?)\s*(-->>|->>|-->|->)\s*(.+?):\s*(.+)$/);
|
|
1860
2556
|
if (msgMatch) {
|
|
1861
|
-
const from = msgMatch[1].trim();
|
|
2557
|
+
const from = safeAddActor(msgMatch[1].trim());
|
|
1862
2558
|
const arrow = msgMatch[2];
|
|
1863
|
-
const to = msgMatch[3].trim();
|
|
1864
|
-
const label = msgMatch[4].trim();
|
|
1865
|
-
|
|
1866
|
-
|
|
2559
|
+
const to = safeAddActor(msgMatch[3].trim());
|
|
2560
|
+
const label = sanitizeLabel(msgMatch[4].trim());
|
|
2561
|
+
if (messageCount >= MAX_MESSAGES)
|
|
2562
|
+
throw new Error(`Import aborted: diagram exceeds the maximum of ${MAX_MESSAGES} messages`);
|
|
2563
|
+
messageCount++;
|
|
1867
2564
|
const messages = model.toJSON().messages ?? [];
|
|
1868
|
-
model.addMessage({
|
|
2565
|
+
model.addMessage({
|
|
2566
|
+
id: nextId("m", messages),
|
|
2567
|
+
from,
|
|
2568
|
+
to,
|
|
2569
|
+
label,
|
|
2570
|
+
style: arrow.startsWith("--") ? "dashed" : "solid"
|
|
2571
|
+
});
|
|
1869
2572
|
}
|
|
1870
2573
|
}
|
|
1871
2574
|
return model;
|
|
1872
2575
|
}
|
|
1873
2576
|
function fromMermaid(mermaid) {
|
|
2577
|
+
if (mermaid.length > MAX_IMPORT_LENGTH) {
|
|
2578
|
+
throw new Error(`Import aborted: input exceeds the maximum of ${MAX_IMPORT_LENGTH} characters`);
|
|
2579
|
+
}
|
|
1874
2580
|
const cleaned = mermaid.replace(/mermaid\.initialize\([\s\S]*?\)\s*;?/g, "");
|
|
1875
2581
|
const rawLines = cleaned.split("\n");
|
|
1876
2582
|
let startIdx = 0;
|
|
@@ -1902,10 +2608,74 @@ function fromMermaid(mermaid) {
|
|
|
1902
2608
|
}
|
|
1903
2609
|
|
|
1904
2610
|
// src/importers/json.ts
|
|
2611
|
+
function stripDangerousKeys(obj) {
|
|
2612
|
+
if (Array.isArray(obj)) return obj.map(stripDangerousKeys);
|
|
2613
|
+
if (obj !== null && typeof obj === "object") {
|
|
2614
|
+
const clean = {};
|
|
2615
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
2616
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
|
|
2617
|
+
clean[key] = stripDangerousKeys(val);
|
|
2618
|
+
}
|
|
2619
|
+
return clean;
|
|
2620
|
+
}
|
|
2621
|
+
return obj;
|
|
2622
|
+
}
|
|
1905
2623
|
function fromJSON(json) {
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
2624
|
+
if (typeof json === "string" && json.length > MAX_IMPORT_LENGTH) {
|
|
2625
|
+
throw new Error(`Import aborted: input exceeds the maximum of ${MAX_IMPORT_LENGTH} characters`);
|
|
2626
|
+
}
|
|
2627
|
+
const raw = typeof json === "string" ? JSON.parse(json) : json;
|
|
2628
|
+
const data = stripDangerousKeys(raw);
|
|
2629
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
2630
|
+
throw new Error("Invalid DiagramModel JSON: expected an object");
|
|
2631
|
+
}
|
|
2632
|
+
if (data.type !== "flowchart" && data.type !== "sequence") {
|
|
2633
|
+
throw new Error(`Invalid DiagramModel JSON: unknown type "${data.type}"`);
|
|
2634
|
+
}
|
|
2635
|
+
if (!Array.isArray(data.nodes) || !Array.isArray(data.edges)) {
|
|
2636
|
+
throw new Error("Invalid DiagramModel JSON: nodes and edges must be arrays");
|
|
2637
|
+
}
|
|
2638
|
+
if (data.nodes.length > MAX_NODES) {
|
|
2639
|
+
throw new Error(
|
|
2640
|
+
`Import aborted: diagram has ${data.nodes.length} nodes, maximum is ${MAX_NODES}`
|
|
2641
|
+
);
|
|
2642
|
+
}
|
|
2643
|
+
if (data.edges.length > MAX_EDGES) {
|
|
2644
|
+
throw new Error(
|
|
2645
|
+
`Import aborted: diagram has ${data.edges.length} edges, maximum is ${MAX_EDGES}`
|
|
2646
|
+
);
|
|
2647
|
+
}
|
|
2648
|
+
if (data.actors && data.actors.length > MAX_ACTORS) {
|
|
2649
|
+
throw new Error(
|
|
2650
|
+
`Import aborted: diagram has ${data.actors.length} actors, maximum is ${MAX_ACTORS}`
|
|
2651
|
+
);
|
|
2652
|
+
}
|
|
2653
|
+
if (data.messages && data.messages.length > MAX_MESSAGES) {
|
|
2654
|
+
throw new Error(
|
|
2655
|
+
`Import aborted: diagram has ${data.messages.length} messages, maximum is ${MAX_MESSAGES}`
|
|
2656
|
+
);
|
|
2657
|
+
}
|
|
2658
|
+
for (const node of data.nodes) {
|
|
2659
|
+
if (typeof node !== "object" || node === null || typeof node.id !== "string" || typeof node.label !== "string") {
|
|
2660
|
+
throw new Error("Invalid DiagramModel JSON: each node must have string id and label");
|
|
2661
|
+
}
|
|
2662
|
+
node.label = sanitizeLabel(node.label);
|
|
2663
|
+
}
|
|
2664
|
+
for (const edge of data.edges) {
|
|
2665
|
+
if (typeof edge !== "object" || edge === null || typeof edge.id !== "string" || typeof edge.from !== "string" || typeof edge.to !== "string") {
|
|
2666
|
+
throw new Error("Invalid DiagramModel JSON: each edge must have string id, from, and to");
|
|
2667
|
+
}
|
|
2668
|
+
if (edge.label) edge.label = sanitizeLabel(edge.label);
|
|
2669
|
+
}
|
|
2670
|
+
if (data.actors) {
|
|
2671
|
+
data.actors = data.actors.map((a) => typeof a === "string" ? sanitizeLabel(a) : a);
|
|
2672
|
+
}
|
|
2673
|
+
if (data.messages) {
|
|
2674
|
+
for (const msg of data.messages) {
|
|
2675
|
+
if (typeof msg === "object" && msg !== null && typeof msg.label === "string") {
|
|
2676
|
+
msg.label = sanitizeLabel(msg.label);
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
1909
2679
|
}
|
|
1910
2680
|
return Model.fromData(data);
|
|
1911
2681
|
}
|
|
@@ -1914,19 +2684,22 @@ function fromJSON(json) {
|
|
|
1914
2684
|
function useImporter(applyAndPush, options = {}) {
|
|
1915
2685
|
const { expectedType, transform, onSuccess, onError } = options;
|
|
1916
2686
|
const reportError = onError ?? ((msg) => alert(msg));
|
|
1917
|
-
return (0, import_react8.useCallback)(
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
2687
|
+
return (0, import_react8.useCallback)(
|
|
2688
|
+
(text) => {
|
|
2689
|
+
try {
|
|
2690
|
+
const parsed = text.trim().startsWith("{") ? fromJSON(text).toJSON() : fromMermaid(text).toJSON();
|
|
2691
|
+
if (expectedType && parsed.type !== expectedType) {
|
|
2692
|
+
reportError(`Imported diagram is not a ${expectedType} diagram.`);
|
|
2693
|
+
return;
|
|
2694
|
+
}
|
|
2695
|
+
applyAndPush(transform ? transform(parsed) : parsed);
|
|
2696
|
+
onSuccess?.("Diagram imported successfully");
|
|
2697
|
+
} catch (err) {
|
|
2698
|
+
reportError(`Import failed: ${err.message}`);
|
|
1923
2699
|
}
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
reportError(`Import failed: ${err.message}`);
|
|
1928
|
-
}
|
|
1929
|
-
}, [applyAndPush, expectedType, transform, onSuccess, onError]);
|
|
2700
|
+
},
|
|
2701
|
+
[applyAndPush, expectedType, transform, onSuccess, onError]
|
|
2702
|
+
);
|
|
1930
2703
|
}
|
|
1931
2704
|
|
|
1932
2705
|
// src/ui/hooks/useToast.ts
|
|
@@ -1964,7 +2737,10 @@ var containerStyle = {
|
|
|
1964
2737
|
zIndex: 9999,
|
|
1965
2738
|
pointerEvents: "none"
|
|
1966
2739
|
};
|
|
1967
|
-
function ToastContainer({
|
|
2740
|
+
function ToastContainer({
|
|
2741
|
+
toasts,
|
|
2742
|
+
onDismiss
|
|
2743
|
+
}) {
|
|
1968
2744
|
if (toasts.length === 0) return null;
|
|
1969
2745
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: containerStyle, children: toasts.map((t) => {
|
|
1970
2746
|
const c = TOAST_COLORS[t.type];
|
|
@@ -2195,7 +2971,10 @@ function SequenceEditor({
|
|
|
2195
2971
|
const historyRef = (0, import_react11.useRef)([ensureSequenceModel(initialModel)]);
|
|
2196
2972
|
const historyIdxRef = (0, import_react11.useRef)(0);
|
|
2197
2973
|
const svgRef = (0, import_react11.useRef)(null);
|
|
2198
|
-
const { t, isDark } = useEditorTheme(theme, themeOverrides, {
|
|
2974
|
+
const { t, isDark } = useEditorTheme(theme, themeOverrides, {
|
|
2975
|
+
light: lightTheme2,
|
|
2976
|
+
dark: darkTheme2
|
|
2977
|
+
});
|
|
2199
2978
|
const actors = model.actors ?? [];
|
|
2200
2979
|
const messages = model.messages ?? [];
|
|
2201
2980
|
const colW = (0, import_react11.useMemo)(() => {
|
|
@@ -2217,11 +2996,14 @@ function SequenceEditor({
|
|
|
2217
2996
|
historyRef.current = stack;
|
|
2218
2997
|
historyIdxRef.current = stack.length - 1;
|
|
2219
2998
|
}, []);
|
|
2220
|
-
const applyAndPush = (0, import_react11.useCallback)(
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2999
|
+
const applyAndPush = (0, import_react11.useCallback)(
|
|
3000
|
+
(m) => {
|
|
3001
|
+
setModel(m);
|
|
3002
|
+
onChange?.(m);
|
|
3003
|
+
pushHistory(m);
|
|
3004
|
+
},
|
|
3005
|
+
[onChange, pushHistory]
|
|
3006
|
+
);
|
|
2225
3007
|
const undo = (0, import_react11.useCallback)(() => {
|
|
2226
3008
|
if (historyIdxRef.current <= 0) return;
|
|
2227
3009
|
historyIdxRef.current--;
|
|
@@ -2266,7 +3048,10 @@ function SequenceEditor({
|
|
|
2266
3048
|
applyAndPush({
|
|
2267
3049
|
...model,
|
|
2268
3050
|
actors: [...actors, a, b],
|
|
2269
|
-
messages: [
|
|
3051
|
+
messages: [
|
|
3052
|
+
...messages,
|
|
3053
|
+
{ id: nextId("m", messages), from: a, to: b, label: "message", style: "solid" }
|
|
3054
|
+
]
|
|
2270
3055
|
});
|
|
2271
3056
|
return;
|
|
2272
3057
|
}
|
|
@@ -2274,7 +3059,10 @@ function SequenceEditor({
|
|
|
2274
3059
|
const to = actors[Math.min(1, actors.length - 1)] ?? from;
|
|
2275
3060
|
applyAndPush({
|
|
2276
3061
|
...model,
|
|
2277
|
-
messages: [
|
|
3062
|
+
messages: [
|
|
3063
|
+
...messages,
|
|
3064
|
+
{ id: nextId("m", messages), from, to, label: "message", style: "solid" }
|
|
3065
|
+
]
|
|
2278
3066
|
});
|
|
2279
3067
|
};
|
|
2280
3068
|
const updateMessage = (id, patch) => {
|
|
@@ -2287,37 +3075,57 @@ function SequenceEditor({
|
|
|
2287
3075
|
applyAndPush({ ...model, messages: messages.filter((m) => m.id !== id) });
|
|
2288
3076
|
if (selected === id) setSelected(null);
|
|
2289
3077
|
};
|
|
2290
|
-
const reorderMessage = (0, import_react11.useCallback)(
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
3078
|
+
const reorderMessage = (0, import_react11.useCallback)(
|
|
3079
|
+
(id, toIdx) => {
|
|
3080
|
+
const fromIdx = messages.findIndex((m) => m.id === id);
|
|
3081
|
+
if (fromIdx < 0 || toIdx === fromIdx) return;
|
|
3082
|
+
const next = messages.slice();
|
|
3083
|
+
const [moved] = next.splice(fromIdx, 1);
|
|
3084
|
+
next.splice(toIdx, 0, moved);
|
|
3085
|
+
applyAndPush({ ...model, messages: next });
|
|
3086
|
+
},
|
|
3087
|
+
[messages, model, applyAndPush]
|
|
3088
|
+
);
|
|
2298
3089
|
const keyCommands = [
|
|
2299
|
-
{
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
3090
|
+
{
|
|
3091
|
+
match: (e) => (e.ctrlKey || e.metaKey) && e.key === "z",
|
|
3092
|
+
run: () => {
|
|
3093
|
+
undo();
|
|
3094
|
+
return true;
|
|
3095
|
+
}
|
|
3096
|
+
},
|
|
3097
|
+
{
|
|
3098
|
+
match: (e) => (e.ctrlKey || e.metaKey) && (e.key === "y" || e.shiftKey && e.key === "z"),
|
|
3099
|
+
run: () => {
|
|
3100
|
+
redo();
|
|
3101
|
+
return true;
|
|
3102
|
+
}
|
|
3103
|
+
},
|
|
3104
|
+
{
|
|
3105
|
+
match: (e) => e.key === "Escape",
|
|
3106
|
+
run: () => {
|
|
3107
|
+
setSelected(null);
|
|
3108
|
+
setEditingId(null);
|
|
3109
|
+
return true;
|
|
3110
|
+
}
|
|
3111
|
+
},
|
|
3112
|
+
{
|
|
3113
|
+
match: (e) => (e.key === "Delete" || e.key === "Backspace") && !!selected,
|
|
3114
|
+
run: () => {
|
|
3115
|
+
removeMessage(selected);
|
|
3116
|
+
return true;
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
];
|
|
3120
|
+
useEditorKeyboard(keyCommands, [undo, redo, selected]);
|
|
3121
|
+
const handleExport = useExporters(
|
|
3122
|
+
model,
|
|
3123
|
+
onExport,
|
|
3124
|
+
"sequence",
|
|
3125
|
+
(msg) => showToast(msg, "success")
|
|
3126
|
+
);
|
|
3127
|
+
const handleImport = useImporter(applyAndPush, {
|
|
3128
|
+
expectedType: "sequence",
|
|
2321
3129
|
transform: ensureSequenceModel,
|
|
2322
3130
|
onSuccess: (msg) => showToast(msg, "success"),
|
|
2323
3131
|
onError: (msg) => showToast(msg, "error")
|
|
@@ -2360,17 +3168,22 @@ function SequenceEditor({
|
|
|
2360
3168
|
};
|
|
2361
3169
|
}, [drag, messages.length, reorderMessage]);
|
|
2362
3170
|
const selectedMsg = selected ? messages.find((m) => m.id === selected) : null;
|
|
2363
|
-
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
3171
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
3172
|
+
"div",
|
|
3173
|
+
{
|
|
3174
|
+
className: "fsd-seq-editor",
|
|
3175
|
+
style: {
|
|
3176
|
+
display: "flex",
|
|
3177
|
+
flexDirection: "column",
|
|
3178
|
+
height,
|
|
3179
|
+
width: "100%",
|
|
3180
|
+
fontFamily: "ui-sans-serif,system-ui,sans-serif",
|
|
3181
|
+
background: t.ctrlsBg,
|
|
3182
|
+
position: "relative"
|
|
3183
|
+
},
|
|
3184
|
+
children: [
|
|
3185
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ToastContainer, { toasts, onDismiss: dismissToast }),
|
|
3186
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("style", { children: `
|
|
2374
3187
|
.fsd-seq-editor [role="button"]:focus-visible {
|
|
2375
3188
|
outline: 2px solid ${t.actorText};
|
|
2376
3189
|
outline-offset: 2px;
|
|
@@ -2382,139 +3195,202 @@ function SequenceEditor({
|
|
|
2382
3195
|
border-radius: 4px;
|
|
2383
3196
|
}
|
|
2384
3197
|
` }),
|
|
2385
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Toolbar, { onExport: handleExport, onImport: allowImport ? handleImport : void 0, allowedExports, allowImport }),
|
|
2386
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: {
|
|
2387
|
-
display: "flex",
|
|
2388
|
-
gap: 8,
|
|
2389
|
-
padding: "7px 14px",
|
|
2390
|
-
background: t.ctrlsBg,
|
|
2391
|
-
borderBottom: `1px solid ${t.ctrlsBorder}`,
|
|
2392
|
-
alignItems: "center",
|
|
2393
|
-
flexWrap: "wrap"
|
|
2394
|
-
}, children: [
|
|
2395
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { onClick: addActor, style: primaryBtn(), children: "+ Actor" }),
|
|
2396
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { onClick: addMessage, style: primaryBtn(), children: "+ Message" }),
|
|
2397
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { width: 1, height: 18, background: t.ctrlsBorder, margin: "0 4px" } }),
|
|
2398
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { onClick: undo, style: ghostBtn2(t), title: "Undo (Ctrl+Z)", children: "\u21B6" }),
|
|
2399
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { onClick: redo, style: ghostBtn2(t), title: "Redo (Ctrl+Y)", children: "\u21B7" }),
|
|
2400
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: { marginLeft: "auto", fontSize: 11, color: t.textMuted }, children: [
|
|
2401
|
-
actors.length,
|
|
2402
|
-
" actor",
|
|
2403
|
-
actors.length === 1 ? "" : "s",
|
|
2404
|
-
" \xB7 ",
|
|
2405
|
-
messages.length,
|
|
2406
|
-
" message",
|
|
2407
|
-
messages.length === 1 ? "" : "s",
|
|
2408
|
-
" \xB7 drag a row to reorder"
|
|
2409
|
-
] })
|
|
2410
|
-
] }),
|
|
2411
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
|
|
2412
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { flex: 1, overflow: "auto", background: t.canvas, position: "relative" }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2413
|
-
SequenceCanvas,
|
|
2414
|
-
{
|
|
2415
|
-
model,
|
|
2416
|
-
actors,
|
|
2417
|
-
messages,
|
|
2418
|
-
t,
|
|
2419
|
-
isDark,
|
|
2420
|
-
colW,
|
|
2421
|
-
totalW,
|
|
2422
|
-
totalH,
|
|
2423
|
-
actorX,
|
|
2424
|
-
msgY,
|
|
2425
|
-
selected,
|
|
2426
|
-
editingId,
|
|
2427
|
-
setEditingId,
|
|
2428
|
-
drag,
|
|
2429
|
-
onRowMouseDown,
|
|
2430
|
-
renameActor,
|
|
2431
|
-
removeActor,
|
|
2432
|
-
svgRef
|
|
2433
|
-
}
|
|
2434
|
-
) }),
|
|
2435
|
-
selectedMsg && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: {
|
|
2436
|
-
width: 280,
|
|
2437
|
-
maxWidth: "40vw",
|
|
2438
|
-
flexShrink: 0,
|
|
2439
|
-
background: t.panelBg,
|
|
2440
|
-
borderLeft: `1px solid ${t.panelBorder}`,
|
|
2441
|
-
padding: "14px 16px",
|
|
2442
|
-
overflowY: "auto"
|
|
2443
|
-
}, children: [
|
|
2444
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { fontSize: 10, fontWeight: 700, color: t.textMuted, textTransform: "uppercase", letterSpacing: 0.7, marginBottom: 10 }, children: "Message" }),
|
|
2445
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Label, { t, children: "Label" }),
|
|
2446
3198
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2447
|
-
|
|
3199
|
+
Toolbar,
|
|
2448
3200
|
{
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
if (editLabel && editLabel !== selectedMsg.label) updateMessage(selectedMsg.id, { label: editLabel });
|
|
2454
|
-
setEditLabel("");
|
|
2455
|
-
},
|
|
2456
|
-
onKeyDown: (e) => {
|
|
2457
|
-
if (e.key === "Enter") e.target.blur();
|
|
2458
|
-
},
|
|
2459
|
-
style: input(t)
|
|
3201
|
+
onExport: handleExport,
|
|
3202
|
+
onImport: allowImport ? handleImport : void 0,
|
|
3203
|
+
allowedExports,
|
|
3204
|
+
allowImport
|
|
2460
3205
|
}
|
|
2461
3206
|
),
|
|
2462
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.
|
|
2463
|
-
|
|
2464
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Label, { t, children: "To" }),
|
|
2465
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("select", { value: selectedMsg.to, onChange: (e) => updateMessage(selectedMsg.id, { to: e.target.value }), style: input(t), children: actors.map((a) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: a, children: a }, a)) }),
|
|
2466
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Label, { t, children: "Style" }),
|
|
2467
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { display: "flex", gap: 6 }, children: ["solid", "dashed"].map((s2) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2468
|
-
"button",
|
|
3207
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
3208
|
+
"div",
|
|
2469
3209
|
{
|
|
2470
|
-
onClick: () => updateMessage(selectedMsg.id, { style: s2 }),
|
|
2471
3210
|
style: {
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
background:
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
fontWeight: 600,
|
|
2480
|
-
cursor: "pointer",
|
|
2481
|
-
fontFamily: "inherit"
|
|
3211
|
+
display: "flex",
|
|
3212
|
+
gap: 8,
|
|
3213
|
+
padding: "7px 14px",
|
|
3214
|
+
background: t.ctrlsBg,
|
|
3215
|
+
borderBottom: `1px solid ${t.ctrlsBorder}`,
|
|
3216
|
+
alignItems: "center",
|
|
3217
|
+
flexWrap: "wrap"
|
|
2482
3218
|
},
|
|
2483
|
-
children:
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
3219
|
+
children: [
|
|
3220
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { onClick: addActor, style: primaryBtn(), children: "+ Actor" }),
|
|
3221
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { onClick: addMessage, style: primaryBtn(), children: "+ Message" }),
|
|
3222
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { width: 1, height: 18, background: t.ctrlsBorder, margin: "0 4px" } }),
|
|
3223
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { onClick: undo, style: ghostBtn2(t), title: "Undo (Ctrl+Z)", children: "\u21B6" }),
|
|
3224
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { onClick: redo, style: ghostBtn2(t), title: "Redo (Ctrl+Y)", children: "\u21B7" }),
|
|
3225
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: { marginLeft: "auto", fontSize: 11, color: t.textMuted }, children: [
|
|
3226
|
+
actors.length,
|
|
3227
|
+
" actor",
|
|
3228
|
+
actors.length === 1 ? "" : "s",
|
|
3229
|
+
" \xB7 ",
|
|
3230
|
+
messages.length,
|
|
3231
|
+
" message",
|
|
3232
|
+
messages.length === 1 ? "" : "s",
|
|
3233
|
+
" \xB7 drag a row to reorder"
|
|
3234
|
+
] })
|
|
3235
|
+
]
|
|
3236
|
+
}
|
|
3237
|
+
),
|
|
3238
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
|
|
3239
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { flex: 1, overflow: "auto", background: t.canvas, position: "relative" }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3240
|
+
SequenceCanvas,
|
|
3241
|
+
{
|
|
3242
|
+
model,
|
|
3243
|
+
actors,
|
|
3244
|
+
messages,
|
|
3245
|
+
t,
|
|
3246
|
+
isDark,
|
|
3247
|
+
colW,
|
|
3248
|
+
totalW,
|
|
3249
|
+
totalH,
|
|
3250
|
+
actorX,
|
|
3251
|
+
msgY,
|
|
3252
|
+
selected,
|
|
3253
|
+
editingId,
|
|
3254
|
+
setEditingId,
|
|
3255
|
+
drag,
|
|
3256
|
+
onRowMouseDown,
|
|
3257
|
+
renameActor,
|
|
3258
|
+
removeActor,
|
|
3259
|
+
svgRef
|
|
3260
|
+
}
|
|
3261
|
+
) }),
|
|
3262
|
+
selectedMsg && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
3263
|
+
"div",
|
|
3264
|
+
{
|
|
3265
|
+
style: {
|
|
3266
|
+
width: 280,
|
|
3267
|
+
maxWidth: "40vw",
|
|
3268
|
+
flexShrink: 0,
|
|
3269
|
+
background: t.panelBg,
|
|
3270
|
+
borderLeft: `1px solid ${t.panelBorder}`,
|
|
3271
|
+
padding: "14px 16px",
|
|
3272
|
+
overflowY: "auto"
|
|
3273
|
+
},
|
|
3274
|
+
children: [
|
|
3275
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3276
|
+
"div",
|
|
3277
|
+
{
|
|
3278
|
+
style: {
|
|
3279
|
+
fontSize: 10,
|
|
3280
|
+
fontWeight: 700,
|
|
3281
|
+
color: t.textMuted,
|
|
3282
|
+
textTransform: "uppercase",
|
|
3283
|
+
letterSpacing: 0.7,
|
|
3284
|
+
marginBottom: 10
|
|
3285
|
+
},
|
|
3286
|
+
children: "Message"
|
|
3287
|
+
}
|
|
3288
|
+
),
|
|
3289
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Label, { t, children: "Label" }),
|
|
3290
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3291
|
+
"input",
|
|
3292
|
+
{
|
|
3293
|
+
value: editLabel || selectedMsg.label,
|
|
3294
|
+
onChange: (e) => setEditLabel(e.target.value),
|
|
3295
|
+
onFocus: () => setEditLabel(selectedMsg.label),
|
|
3296
|
+
onBlur: () => {
|
|
3297
|
+
if (editLabel && editLabel !== selectedMsg.label)
|
|
3298
|
+
updateMessage(selectedMsg.id, { label: editLabel });
|
|
3299
|
+
setEditLabel("");
|
|
3300
|
+
},
|
|
3301
|
+
onKeyDown: (e) => {
|
|
3302
|
+
if (e.key === "Enter") e.target.blur();
|
|
3303
|
+
},
|
|
3304
|
+
style: input(t)
|
|
3305
|
+
}
|
|
3306
|
+
),
|
|
3307
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Label, { t, children: "From" }),
|
|
3308
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3309
|
+
"select",
|
|
3310
|
+
{
|
|
3311
|
+
value: selectedMsg.from,
|
|
3312
|
+
onChange: (e) => updateMessage(selectedMsg.id, { from: e.target.value }),
|
|
3313
|
+
style: input(t),
|
|
3314
|
+
children: actors.map((a) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: a, children: a }, a))
|
|
3315
|
+
}
|
|
3316
|
+
),
|
|
3317
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Label, { t, children: "To" }),
|
|
3318
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3319
|
+
"select",
|
|
3320
|
+
{
|
|
3321
|
+
value: selectedMsg.to,
|
|
3322
|
+
onChange: (e) => updateMessage(selectedMsg.id, { to: e.target.value }),
|
|
3323
|
+
style: input(t),
|
|
3324
|
+
children: actors.map((a) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: a, children: a }, a))
|
|
3325
|
+
}
|
|
3326
|
+
),
|
|
3327
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Label, { t, children: "Style" }),
|
|
3328
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { display: "flex", gap: 6 }, children: ["solid", "dashed"].map((s2) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3329
|
+
"button",
|
|
3330
|
+
{
|
|
3331
|
+
onClick: () => updateMessage(selectedMsg.id, { style: s2 }),
|
|
3332
|
+
style: {
|
|
3333
|
+
flex: 1,
|
|
3334
|
+
padding: "6px 10px",
|
|
3335
|
+
border: `1.5px solid ${selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ? INDIGO2 : t.inputBorder}`,
|
|
3336
|
+
background: selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ? INDIGO_SOFT2 : t.inputBg,
|
|
3337
|
+
color: selectedMsg.style === s2 || !selectedMsg.style && s2 === "solid" ? INDIGO2 : t.textPrimary,
|
|
3338
|
+
borderRadius: 8,
|
|
3339
|
+
fontSize: 12,
|
|
3340
|
+
fontWeight: 600,
|
|
3341
|
+
cursor: "pointer",
|
|
3342
|
+
fontFamily: "inherit"
|
|
3343
|
+
},
|
|
3344
|
+
children: s2 === "solid" ? "\u2500\u2500 solid" : "\u2500 \u2500 dashed"
|
|
3345
|
+
},
|
|
3346
|
+
s2
|
|
3347
|
+
)) }),
|
|
3348
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { height: 14 } }),
|
|
3349
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3350
|
+
"button",
|
|
3351
|
+
{
|
|
3352
|
+
onClick: () => removeMessage(selectedMsg.id),
|
|
3353
|
+
style: {
|
|
3354
|
+
...ghostBtn2(t),
|
|
3355
|
+
width: "100%",
|
|
3356
|
+
color: "#ef4444",
|
|
3357
|
+
border: `1px solid ${isDark ? "#7f1d1d" : "#fca5a5"}`
|
|
3358
|
+
},
|
|
3359
|
+
children: "Delete message"
|
|
3360
|
+
}
|
|
3361
|
+
)
|
|
3362
|
+
]
|
|
3363
|
+
}
|
|
3364
|
+
)
|
|
3365
|
+
] }),
|
|
3366
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
3367
|
+
"div",
|
|
2490
3368
|
{
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
3369
|
+
style: {
|
|
3370
|
+
padding: "4px 14px",
|
|
3371
|
+
fontSize: 11,
|
|
3372
|
+
color: t.textMuted,
|
|
3373
|
+
background: t.canvas,
|
|
3374
|
+
borderTop: `1px solid ${t.ctrlsBorder}`,
|
|
3375
|
+
display: "flex",
|
|
3376
|
+
gap: 16
|
|
3377
|
+
},
|
|
3378
|
+
children: [
|
|
3379
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
|
|
3380
|
+
actors.length,
|
|
3381
|
+
" actors"
|
|
3382
|
+
] }),
|
|
3383
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
|
|
3384
|
+
messages.length,
|
|
3385
|
+
" messages"
|
|
3386
|
+
] }),
|
|
3387
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { marginLeft: "auto" }, children: "double-click actor to rename \xB7 drag a row to reorder" })
|
|
3388
|
+
]
|
|
2494
3389
|
}
|
|
2495
3390
|
)
|
|
2496
|
-
]
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
padding: "4px 14px",
|
|
2500
|
-
fontSize: 11,
|
|
2501
|
-
color: t.textMuted,
|
|
2502
|
-
background: t.canvas,
|
|
2503
|
-
borderTop: `1px solid ${t.ctrlsBorder}`,
|
|
2504
|
-
display: "flex",
|
|
2505
|
-
gap: 16
|
|
2506
|
-
}, children: [
|
|
2507
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
|
|
2508
|
-
actors.length,
|
|
2509
|
-
" actors"
|
|
2510
|
-
] }),
|
|
2511
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
|
|
2512
|
-
messages.length,
|
|
2513
|
-
" messages"
|
|
2514
|
-
] }),
|
|
2515
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { marginLeft: "auto" }, children: "double-click actor to rename \xB7 drag a row to reorder" })
|
|
2516
|
-
] })
|
|
2517
|
-
] });
|
|
3391
|
+
]
|
|
3392
|
+
}
|
|
3393
|
+
);
|
|
2518
3394
|
}
|
|
2519
3395
|
function primaryBtn() {
|
|
2520
3396
|
return {
|
|
@@ -2558,7 +3434,20 @@ function input(t) {
|
|
|
2558
3434
|
};
|
|
2559
3435
|
}
|
|
2560
3436
|
function Label({ t, children }) {
|
|
2561
|
-
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3437
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3438
|
+
"div",
|
|
3439
|
+
{
|
|
3440
|
+
style: {
|
|
3441
|
+
fontSize: 10,
|
|
3442
|
+
fontWeight: 700,
|
|
3443
|
+
color: t.textMuted,
|
|
3444
|
+
textTransform: "uppercase",
|
|
3445
|
+
letterSpacing: 0.6,
|
|
3446
|
+
marginBottom: 4
|
|
3447
|
+
},
|
|
3448
|
+
children
|
|
3449
|
+
}
|
|
3450
|
+
);
|
|
2562
3451
|
}
|
|
2563
3452
|
|
|
2564
3453
|
// src/ui/NodeNavigator.tsx
|
|
@@ -2590,163 +3479,286 @@ function NodeNavigator({
|
|
|
2590
3479
|
return "\u25AD";
|
|
2591
3480
|
}
|
|
2592
3481
|
};
|
|
2593
|
-
const filtered = model.nodes.filter(
|
|
2594
|
-
(n) => n.label.toLowerCase().includes(search.toLowerCase())
|
|
2595
|
-
);
|
|
3482
|
+
const filtered = model.nodes.filter((n) => n.label.toLowerCase().includes(search.toLowerCase()));
|
|
2596
3483
|
const inEdges = (id) => model.edges.filter((e) => e.to === id).length;
|
|
2597
3484
|
const outEdges = (id) => model.edges.filter((e) => e.from === id).length;
|
|
2598
3485
|
if (!open) {
|
|
2599
|
-
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
3486
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
3487
|
+
"div",
|
|
3488
|
+
{
|
|
3489
|
+
style: {
|
|
3490
|
+
width: 36,
|
|
3491
|
+
flexShrink: 0,
|
|
3492
|
+
background: t.panelBg,
|
|
3493
|
+
borderRight: `1px solid ${t.panelBorder}`,
|
|
3494
|
+
display: "flex",
|
|
3495
|
+
flexDirection: "column",
|
|
3496
|
+
alignItems: "center",
|
|
3497
|
+
paddingTop: 8,
|
|
3498
|
+
gap: 6
|
|
3499
|
+
},
|
|
3500
|
+
children: [
|
|
3501
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3502
|
+
"button",
|
|
3503
|
+
{
|
|
3504
|
+
onClick: onToggle,
|
|
3505
|
+
title: "Open node list",
|
|
3506
|
+
"aria-expanded": false,
|
|
3507
|
+
"aria-label": "Open node list",
|
|
3508
|
+
style: {
|
|
3509
|
+
background: "none",
|
|
3510
|
+
border: "none",
|
|
3511
|
+
cursor: "pointer",
|
|
3512
|
+
color: t.textMuted,
|
|
3513
|
+
padding: 6,
|
|
3514
|
+
borderRadius: 6,
|
|
3515
|
+
fontSize: 14,
|
|
3516
|
+
lineHeight: 1
|
|
3517
|
+
},
|
|
3518
|
+
children: "\u2630"
|
|
3519
|
+
}
|
|
3520
|
+
),
|
|
3521
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3522
|
+
"div",
|
|
3523
|
+
{
|
|
3524
|
+
style: {
|
|
3525
|
+
fontSize: 10,
|
|
3526
|
+
color: t.textMuted,
|
|
3527
|
+
fontWeight: 700,
|
|
3528
|
+
writingMode: "vertical-rl",
|
|
3529
|
+
transform: "rotate(180deg)",
|
|
3530
|
+
letterSpacing: 0.5
|
|
3531
|
+
},
|
|
3532
|
+
children: model.nodes.length
|
|
3533
|
+
}
|
|
3534
|
+
)
|
|
3535
|
+
]
|
|
3536
|
+
}
|
|
3537
|
+
);
|
|
2623
3538
|
}
|
|
2624
|
-
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
flexShrink: 0
|
|
2640
|
-
}, children: [
|
|
2641
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
|
|
2642
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { fontSize: 11, fontWeight: 700, color: t.textSecondary, textTransform: "uppercase", letterSpacing: 0.7 }, children: variant === "question" ? "Questions" : variant === "journey" ? "Steps" : "Nodes" }),
|
|
2643
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: {
|
|
2644
|
-
fontSize: 10,
|
|
2645
|
-
fontWeight: 700,
|
|
2646
|
-
color: t.textMuted,
|
|
2647
|
-
background: isDark ? "#0f172a" : "#f1f5f9",
|
|
2648
|
-
padding: "1px 6px",
|
|
2649
|
-
borderRadius: 99
|
|
2650
|
-
}, children: model.nodes.length })
|
|
2651
|
-
] }),
|
|
2652
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2653
|
-
"button",
|
|
2654
|
-
{
|
|
2655
|
-
onClick: onToggle,
|
|
2656
|
-
style: { background: "none", border: "none", cursor: "pointer", color: t.textMuted, padding: "2px 4px", borderRadius: 4, fontSize: 13, lineHeight: 1 },
|
|
2657
|
-
title: "Collapse",
|
|
2658
|
-
"aria-expanded": true,
|
|
2659
|
-
"aria-label": "Collapse node list",
|
|
2660
|
-
children: "\u2039"
|
|
2661
|
-
}
|
|
2662
|
-
)
|
|
2663
|
-
] }),
|
|
2664
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { padding: "8px 10px", borderBottom: `1px solid ${t.sectionBorder}`, flexShrink: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { position: "relative" }, children: [
|
|
2665
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { position: "absolute", left: 8, top: "50%", transform: "translateY(-50%)", fontSize: 11, color: t.textMuted, pointerEvents: "none" }, children: "\u2315" }),
|
|
2666
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2667
|
-
"input",
|
|
2668
|
-
{
|
|
2669
|
-
value: search,
|
|
2670
|
-
onChange: (e) => setSearch(e.target.value),
|
|
2671
|
-
placeholder: "Search\u2026",
|
|
2672
|
-
style: {
|
|
2673
|
-
width: "100%",
|
|
2674
|
-
padding: "5px 8px 5px 24px",
|
|
2675
|
-
border: `1.5px solid ${t.inputBorder}`,
|
|
2676
|
-
borderRadius: 7,
|
|
2677
|
-
fontSize: 12,
|
|
2678
|
-
background: t.inputBg,
|
|
2679
|
-
color: t.inputText,
|
|
2680
|
-
outline: "none",
|
|
2681
|
-
boxSizing: "border-box",
|
|
2682
|
-
fontFamily: "inherit"
|
|
2683
|
-
}
|
|
2684
|
-
}
|
|
2685
|
-
)
|
|
2686
|
-
] }) }),
|
|
2687
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { flex: 1, overflowY: "auto", padding: "6px 8px", display: "flex", flexDirection: "column", gap: 2 }, children: [
|
|
2688
|
-
filtered.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { textAlign: "center", padding: "20px 0", fontSize: 12, color: t.textMuted, fontStyle: "italic" }, children: model.nodes.length === 0 ? "No nodes yet" : "No matches" }),
|
|
2689
|
-
filtered.map((node, idx) => {
|
|
2690
|
-
const isSelected = selected === node.id;
|
|
2691
|
-
const answers = node.metadata?.answers ?? [];
|
|
2692
|
-
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2693
|
-
"button",
|
|
3539
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
3540
|
+
"div",
|
|
3541
|
+
{
|
|
3542
|
+
style: {
|
|
3543
|
+
width: 216,
|
|
3544
|
+
flexShrink: 0,
|
|
3545
|
+
background: t.panelBg,
|
|
3546
|
+
borderRight: `1px solid ${t.panelBorder}`,
|
|
3547
|
+
display: "flex",
|
|
3548
|
+
flexDirection: "column",
|
|
3549
|
+
overflow: "hidden"
|
|
3550
|
+
},
|
|
3551
|
+
children: [
|
|
3552
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
3553
|
+
"div",
|
|
2694
3554
|
{
|
|
2695
|
-
onClick: () => onSelect(node.id),
|
|
2696
3555
|
style: {
|
|
2697
3556
|
display: "flex",
|
|
2698
3557
|
alignItems: "center",
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
background: isSelected ? acc.fill : "transparent",
|
|
2704
|
-
border: isSelected ? `1.5px solid ${acc.border}` : "1.5px solid transparent",
|
|
2705
|
-
borderRadius: 8,
|
|
2706
|
-
cursor: "pointer",
|
|
2707
|
-
fontFamily: "inherit",
|
|
2708
|
-
transition: "background 0.1s"
|
|
2709
|
-
},
|
|
2710
|
-
onMouseEnter: (e) => {
|
|
2711
|
-
if (!isSelected) e.currentTarget.style.background = isDark ? "#334155" : "#f1f5f9";
|
|
2712
|
-
},
|
|
2713
|
-
onMouseLeave: (e) => {
|
|
2714
|
-
if (!isSelected) e.currentTarget.style.background = "transparent";
|
|
3558
|
+
justifyContent: "space-between",
|
|
3559
|
+
padding: "10px 12px",
|
|
3560
|
+
borderBottom: `1px solid ${t.panelBorder}`,
|
|
3561
|
+
flexShrink: 0
|
|
2715
3562
|
},
|
|
2716
3563
|
children: [
|
|
2717
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
3564
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
|
|
3565
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3566
|
+
"span",
|
|
3567
|
+
{
|
|
3568
|
+
style: {
|
|
3569
|
+
fontSize: 11,
|
|
3570
|
+
fontWeight: 700,
|
|
3571
|
+
color: t.textSecondary,
|
|
3572
|
+
textTransform: "uppercase",
|
|
3573
|
+
letterSpacing: 0.7
|
|
3574
|
+
},
|
|
3575
|
+
children: variant === "question" ? "Questions" : variant === "journey" ? "Steps" : "Nodes"
|
|
3576
|
+
}
|
|
3577
|
+
),
|
|
3578
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3579
|
+
"span",
|
|
3580
|
+
{
|
|
3581
|
+
style: {
|
|
3582
|
+
fontSize: 10,
|
|
3583
|
+
fontWeight: 700,
|
|
3584
|
+
color: t.textMuted,
|
|
3585
|
+
background: isDark ? "#0f172a" : "#f1f5f9",
|
|
3586
|
+
padding: "1px 6px",
|
|
3587
|
+
borderRadius: 99
|
|
3588
|
+
},
|
|
3589
|
+
children: model.nodes.length
|
|
3590
|
+
}
|
|
3591
|
+
)
|
|
2741
3592
|
] }),
|
|
2742
|
-
|
|
3593
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3594
|
+
"button",
|
|
3595
|
+
{
|
|
3596
|
+
onClick: onToggle,
|
|
3597
|
+
style: {
|
|
3598
|
+
background: "none",
|
|
3599
|
+
border: "none",
|
|
3600
|
+
cursor: "pointer",
|
|
3601
|
+
color: t.textMuted,
|
|
3602
|
+
padding: "2px 4px",
|
|
3603
|
+
borderRadius: 4,
|
|
3604
|
+
fontSize: 13,
|
|
3605
|
+
lineHeight: 1
|
|
3606
|
+
},
|
|
3607
|
+
title: "Collapse",
|
|
3608
|
+
"aria-expanded": true,
|
|
3609
|
+
"aria-label": "Collapse node list",
|
|
3610
|
+
children: "\u2039"
|
|
3611
|
+
}
|
|
3612
|
+
)
|
|
2743
3613
|
]
|
|
2744
|
-
}
|
|
2745
|
-
|
|
2746
|
-
)
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
3614
|
+
}
|
|
3615
|
+
),
|
|
3616
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3617
|
+
"div",
|
|
3618
|
+
{
|
|
3619
|
+
style: { padding: "8px 10px", borderBottom: `1px solid ${t.sectionBorder}`, flexShrink: 0 },
|
|
3620
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { position: "relative" }, children: [
|
|
3621
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3622
|
+
"span",
|
|
3623
|
+
{
|
|
3624
|
+
style: {
|
|
3625
|
+
position: "absolute",
|
|
3626
|
+
left: 8,
|
|
3627
|
+
top: "50%",
|
|
3628
|
+
transform: "translateY(-50%)",
|
|
3629
|
+
fontSize: 11,
|
|
3630
|
+
color: t.textMuted,
|
|
3631
|
+
pointerEvents: "none"
|
|
3632
|
+
},
|
|
3633
|
+
children: "\u2315"
|
|
3634
|
+
}
|
|
3635
|
+
),
|
|
3636
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3637
|
+
"input",
|
|
3638
|
+
{
|
|
3639
|
+
value: search,
|
|
3640
|
+
onChange: (e) => setSearch(e.target.value),
|
|
3641
|
+
placeholder: "Search\u2026",
|
|
3642
|
+
style: {
|
|
3643
|
+
width: "100%",
|
|
3644
|
+
padding: "5px 8px 5px 24px",
|
|
3645
|
+
border: `1.5px solid ${t.inputBorder}`,
|
|
3646
|
+
borderRadius: 7,
|
|
3647
|
+
fontSize: 12,
|
|
3648
|
+
background: t.inputBg,
|
|
3649
|
+
color: t.inputText,
|
|
3650
|
+
outline: "none",
|
|
3651
|
+
boxSizing: "border-box",
|
|
3652
|
+
fontFamily: "inherit"
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
)
|
|
3656
|
+
] })
|
|
3657
|
+
}
|
|
3658
|
+
),
|
|
3659
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
3660
|
+
"div",
|
|
3661
|
+
{
|
|
3662
|
+
style: {
|
|
3663
|
+
flex: 1,
|
|
3664
|
+
overflowY: "auto",
|
|
3665
|
+
padding: "6px 8px",
|
|
3666
|
+
display: "flex",
|
|
3667
|
+
flexDirection: "column",
|
|
3668
|
+
gap: 2
|
|
3669
|
+
},
|
|
3670
|
+
children: [
|
|
3671
|
+
filtered.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3672
|
+
"div",
|
|
3673
|
+
{
|
|
3674
|
+
style: {
|
|
3675
|
+
textAlign: "center",
|
|
3676
|
+
padding: "20px 0",
|
|
3677
|
+
fontSize: 12,
|
|
3678
|
+
color: t.textMuted,
|
|
3679
|
+
fontStyle: "italic"
|
|
3680
|
+
},
|
|
3681
|
+
children: model.nodes.length === 0 ? "No nodes yet" : "No matches"
|
|
3682
|
+
}
|
|
3683
|
+
),
|
|
3684
|
+
filtered.map((node, idx) => {
|
|
3685
|
+
const isSelected = selected === node.id;
|
|
3686
|
+
const answers = node.metadata?.answers ?? [];
|
|
3687
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
3688
|
+
"button",
|
|
3689
|
+
{
|
|
3690
|
+
onClick: () => onSelect(node.id),
|
|
3691
|
+
style: {
|
|
3692
|
+
display: "flex",
|
|
3693
|
+
alignItems: "center",
|
|
3694
|
+
gap: 8,
|
|
3695
|
+
width: "100%",
|
|
3696
|
+
padding: "7px 8px",
|
|
3697
|
+
textAlign: "left",
|
|
3698
|
+
background: isSelected ? acc.fill : "transparent",
|
|
3699
|
+
border: isSelected ? `1.5px solid ${acc.border}` : "1.5px solid transparent",
|
|
3700
|
+
borderRadius: 8,
|
|
3701
|
+
cursor: "pointer",
|
|
3702
|
+
fontFamily: "inherit",
|
|
3703
|
+
transition: "background 0.1s"
|
|
3704
|
+
},
|
|
3705
|
+
onMouseEnter: (e) => {
|
|
3706
|
+
if (!isSelected)
|
|
3707
|
+
e.currentTarget.style.background = isDark ? "#334155" : "#f1f5f9";
|
|
3708
|
+
},
|
|
3709
|
+
onMouseLeave: (e) => {
|
|
3710
|
+
if (!isSelected) e.currentTarget.style.background = "transparent";
|
|
3711
|
+
},
|
|
3712
|
+
children: [
|
|
3713
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3714
|
+
"div",
|
|
3715
|
+
{
|
|
3716
|
+
style: {
|
|
3717
|
+
width: 22,
|
|
3718
|
+
height: 22,
|
|
3719
|
+
borderRadius: 6,
|
|
3720
|
+
flexShrink: 0,
|
|
3721
|
+
background: isSelected ? acc.color : isDark ? "#334155" : "#e2e8f0",
|
|
3722
|
+
color: isSelected ? "#fff" : t.textMuted,
|
|
3723
|
+
display: "flex",
|
|
3724
|
+
alignItems: "center",
|
|
3725
|
+
justifyContent: "center",
|
|
3726
|
+
fontSize: variant === "journey" ? 9 : 11,
|
|
3727
|
+
fontWeight: 700
|
|
3728
|
+
},
|
|
3729
|
+
children: variant === "journey" ? idx + 1 : shapeIcon(node)
|
|
3730
|
+
}
|
|
3731
|
+
),
|
|
3732
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
|
|
3733
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3734
|
+
"div",
|
|
3735
|
+
{
|
|
3736
|
+
style: {
|
|
3737
|
+
fontSize: 12,
|
|
3738
|
+
fontWeight: isSelected ? 600 : 400,
|
|
3739
|
+
color: isSelected ? acc.color : t.textPrimary,
|
|
3740
|
+
overflow: "hidden",
|
|
3741
|
+
textOverflow: "ellipsis",
|
|
3742
|
+
whiteSpace: "nowrap",
|
|
3743
|
+
lineHeight: 1.3
|
|
3744
|
+
},
|
|
3745
|
+
children: node.label
|
|
3746
|
+
}
|
|
3747
|
+
),
|
|
3748
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("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` })
|
|
3749
|
+
] }),
|
|
3750
|
+
isSelected && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: { fontSize: 10, color: acc.color, flexShrink: 0 }, children: "\u25C9" })
|
|
3751
|
+
]
|
|
3752
|
+
},
|
|
3753
|
+
node.id
|
|
3754
|
+
);
|
|
3755
|
+
})
|
|
3756
|
+
]
|
|
3757
|
+
}
|
|
3758
|
+
)
|
|
3759
|
+
]
|
|
3760
|
+
}
|
|
3761
|
+
);
|
|
2750
3762
|
}
|
|
2751
3763
|
|
|
2752
3764
|
// src/ui/render.tsx
|
|
@@ -2821,18 +3833,55 @@ var STYLE_LABEL = { pointerEvents: "none", userSelect: "none" };
|
|
|
2821
3833
|
var STYLE_BLUR = { filter: "blur(4px)" };
|
|
2822
3834
|
var STYLE_EDGE_HIT = { cursor: "pointer" };
|
|
2823
3835
|
var STYLE_NO_EVENTS = { pointerEvents: "none" };
|
|
2824
|
-
var STYLE_PORT_HOVER = {
|
|
2825
|
-
|
|
3836
|
+
var STYLE_PORT_HOVER = {
|
|
3837
|
+
cursor: "crosshair",
|
|
3838
|
+
filter: "drop-shadow(0 1px 3px rgba(0,0,0,0.18))"
|
|
3839
|
+
};
|
|
3840
|
+
var STYLE_WAYPOINT = {
|
|
3841
|
+
cursor: "grab",
|
|
3842
|
+
filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.25))"
|
|
3843
|
+
};
|
|
2826
3844
|
var STYLE_EDGE_LABEL_HIT = { cursor: "text" };
|
|
2827
|
-
function NodeShape({
|
|
3845
|
+
function NodeShape({
|
|
3846
|
+
node,
|
|
3847
|
+
selected,
|
|
3848
|
+
variant,
|
|
3849
|
+
stepNumber,
|
|
3850
|
+
t,
|
|
3851
|
+
isDark,
|
|
3852
|
+
w
|
|
3853
|
+
}) {
|
|
2828
3854
|
const acc = variantAccent(variant, isDark);
|
|
2829
3855
|
const cx = w / 2, cy = NODE_H2 / 2;
|
|
2830
3856
|
const stroke = selected ? acc.color : t.nodeStroke;
|
|
2831
3857
|
const fill = selected ? t.nodeSelectedFill : t.nodeFill;
|
|
2832
3858
|
const sw = selected ? 1.75 : 1.25;
|
|
2833
3859
|
const glow = selected && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_jsx_runtime8.Fragment, { children: node.shape === "circle" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
2834
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2835
|
-
|
|
3860
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
3861
|
+
"circle",
|
|
3862
|
+
{
|
|
3863
|
+
cx,
|
|
3864
|
+
cy,
|
|
3865
|
+
r: NODE_H2 / 2 + 3,
|
|
3866
|
+
fill: "none",
|
|
3867
|
+
stroke: acc.color,
|
|
3868
|
+
strokeWidth: 6,
|
|
3869
|
+
opacity: 0.18,
|
|
3870
|
+
style: STYLE_BLUR
|
|
3871
|
+
}
|
|
3872
|
+
),
|
|
3873
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
3874
|
+
"circle",
|
|
3875
|
+
{
|
|
3876
|
+
cx,
|
|
3877
|
+
cy,
|
|
3878
|
+
r: NODE_H2 / 2 + 1.5,
|
|
3879
|
+
fill: "none",
|
|
3880
|
+
stroke: acc.color,
|
|
3881
|
+
strokeWidth: 1,
|
|
3882
|
+
opacity: 0.55
|
|
3883
|
+
}
|
|
3884
|
+
)
|
|
2836
3885
|
] }) : node.shape === "diamond" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
2837
3886
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2838
3887
|
"polygon",
|
|
@@ -2889,39 +3938,98 @@ function NodeShape({ node, selected, variant, stepNumber, t, isDark, w }) {
|
|
|
2889
3938
|
const badgeColor = isDark ? ACCENT.emeraldDark : ACCENT.emerald;
|
|
2890
3939
|
const badge = variant === "journey" && stepNumber !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
2891
3940
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: 14, cy: 14, r: 10, fill: badgeColor }),
|
|
2892
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
3941
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
3942
|
+
"text",
|
|
3943
|
+
{
|
|
3944
|
+
x: 14,
|
|
3945
|
+
y: 18,
|
|
3946
|
+
textAnchor: "middle",
|
|
3947
|
+
fontSize: 9,
|
|
3948
|
+
fill: "white",
|
|
3949
|
+
fontWeight: "700",
|
|
3950
|
+
style: STYLE_LABEL,
|
|
3951
|
+
children: stepNumber
|
|
3952
|
+
}
|
|
3953
|
+
)
|
|
2893
3954
|
] });
|
|
2894
3955
|
switch (node.shape) {
|
|
2895
3956
|
case "diamond": {
|
|
2896
3957
|
const pts = `${cx},0 ${w},${cy} ${cx},${NODE_H2} 0,${cy}`;
|
|
2897
3958
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
2898
3959
|
glow,
|
|
2899
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
3960
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
3961
|
+
"polygon",
|
|
3962
|
+
{
|
|
3963
|
+
points: pts,
|
|
3964
|
+
fill,
|
|
3965
|
+
stroke,
|
|
3966
|
+
strokeWidth: sw,
|
|
3967
|
+
filter: "url(#nodeShadow)"
|
|
3968
|
+
}
|
|
3969
|
+
),
|
|
2900
3970
|
badge
|
|
2901
3971
|
] });
|
|
2902
3972
|
}
|
|
2903
3973
|
case "circle":
|
|
2904
3974
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
2905
3975
|
glow,
|
|
2906
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
3976
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
3977
|
+
"circle",
|
|
3978
|
+
{
|
|
3979
|
+
cx,
|
|
3980
|
+
cy,
|
|
3981
|
+
r: NODE_H2 / 2 - 1,
|
|
3982
|
+
fill,
|
|
3983
|
+
stroke,
|
|
3984
|
+
strokeWidth: sw,
|
|
3985
|
+
filter: "url(#nodeShadow)"
|
|
3986
|
+
}
|
|
3987
|
+
),
|
|
2907
3988
|
badge
|
|
2908
3989
|
] });
|
|
2909
3990
|
case "parallelogram":
|
|
2910
3991
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
2911
3992
|
glow,
|
|
2912
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
3993
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
3994
|
+
"polygon",
|
|
3995
|
+
{
|
|
3996
|
+
points: `14,0 ${w},0 ${w - 14},${NODE_H2} 0,${NODE_H2}`,
|
|
3997
|
+
fill,
|
|
3998
|
+
stroke,
|
|
3999
|
+
strokeWidth: sw,
|
|
4000
|
+
filter: "url(#nodeShadow)"
|
|
4001
|
+
}
|
|
4002
|
+
),
|
|
2913
4003
|
badge
|
|
2914
4004
|
] });
|
|
2915
4005
|
default:
|
|
2916
4006
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
2917
4007
|
glow,
|
|
2918
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
4008
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
4009
|
+
"rect",
|
|
4010
|
+
{
|
|
4011
|
+
width: w,
|
|
4012
|
+
height: NODE_H2,
|
|
4013
|
+
rx: 14,
|
|
4014
|
+
fill,
|
|
4015
|
+
stroke,
|
|
4016
|
+
strokeWidth: sw,
|
|
4017
|
+
filter: "url(#nodeShadow)"
|
|
4018
|
+
}
|
|
4019
|
+
),
|
|
2919
4020
|
badge
|
|
2920
4021
|
] });
|
|
2921
4022
|
}
|
|
2922
4023
|
}
|
|
2923
4024
|
var ANSWER_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
2924
|
-
function QuestionNode({
|
|
4025
|
+
function QuestionNode({
|
|
4026
|
+
node,
|
|
4027
|
+
selected,
|
|
4028
|
+
edges,
|
|
4029
|
+
isDark,
|
|
4030
|
+
onAnswerPortDown,
|
|
4031
|
+
qW
|
|
4032
|
+
}) {
|
|
2925
4033
|
const answers = node.metadata?.answers ?? [];
|
|
2926
4034
|
const totalH = questionNodeH2(answers);
|
|
2927
4035
|
const amber = isDark ? ACCENT.amberDark : ACCENT.amber;
|
|
@@ -2969,27 +4077,91 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
2969
4077
|
] });
|
|
2970
4078
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
2971
4079
|
glow,
|
|
2972
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
4080
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
4081
|
+
"rect",
|
|
4082
|
+
{
|
|
4083
|
+
width: qW,
|
|
4084
|
+
height: totalH,
|
|
4085
|
+
rx: 14,
|
|
4086
|
+
fill: nodeBg,
|
|
4087
|
+
stroke: nodeBorder,
|
|
4088
|
+
strokeWidth: selected ? 2 : 1.5,
|
|
4089
|
+
filter: "url(#nodeShadow)"
|
|
4090
|
+
}
|
|
4091
|
+
),
|
|
2973
4092
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("clipPath", { id: `qhdr-${node.id}`, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("rect", { width: qW, height: Q_BASE_H2, rx: 14 }) }),
|
|
2974
4093
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("rect", { width: qW, height: Q_BASE_H2, fill: amberSoft, clipPath: `url(#qhdr-${node.id})` }),
|
|
2975
4094
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("rect", { x: 0, y: 0, width: 4, height: Q_BASE_H2, rx: 2, fill: amber }),
|
|
2976
4095
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("rect", { x: 12, y: 14, width: 28, height: 28, rx: 8, fill: amber }),
|
|
2977
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2978
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
4096
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2979
4097
|
"text",
|
|
2980
4098
|
{
|
|
4099
|
+
x: 26,
|
|
4100
|
+
y: 33,
|
|
4101
|
+
textAnchor: "middle",
|
|
4102
|
+
fontSize: 15,
|
|
4103
|
+
fontWeight: "900",
|
|
4104
|
+
fill: "white",
|
|
2981
4105
|
style: STYLE_LABEL,
|
|
2982
|
-
|
|
2983
|
-
children: [
|
|
2984
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("tspan", { x: 50, y: 27, fontSize: 9, fontWeight: 700, fill: textSub, letterSpacing: 0.6, textAnchor: "start", children: "QUESTION" }),
|
|
2985
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("tspan", { x: 50, dy: 15, fontSize: 13, fontWeight: 700, fill: selected ? amber : textMain, textAnchor: "start", children: node.label })
|
|
2986
|
-
]
|
|
4106
|
+
children: "?"
|
|
2987
4107
|
}
|
|
2988
4108
|
),
|
|
4109
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("text", { style: STYLE_LABEL, fontFamily: "ui-sans-serif,system-ui,sans-serif", children: [
|
|
4110
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
4111
|
+
"tspan",
|
|
4112
|
+
{
|
|
4113
|
+
x: 50,
|
|
4114
|
+
y: 27,
|
|
4115
|
+
fontSize: 9,
|
|
4116
|
+
fontWeight: 700,
|
|
4117
|
+
fill: textSub,
|
|
4118
|
+
letterSpacing: 0.6,
|
|
4119
|
+
textAnchor: "start",
|
|
4120
|
+
children: "QUESTION"
|
|
4121
|
+
}
|
|
4122
|
+
),
|
|
4123
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
4124
|
+
"tspan",
|
|
4125
|
+
{
|
|
4126
|
+
x: 50,
|
|
4127
|
+
dy: 15,
|
|
4128
|
+
fontSize: 13,
|
|
4129
|
+
fontWeight: 700,
|
|
4130
|
+
fill: selected ? amber : textMain,
|
|
4131
|
+
textAnchor: "start",
|
|
4132
|
+
children: node.label
|
|
4133
|
+
}
|
|
4134
|
+
)
|
|
4135
|
+
] }),
|
|
2989
4136
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("line", { x1: 0, y1: Q_BASE_H2, x2: qW, y2: Q_BASE_H2, stroke: amberLine, strokeWidth: 1 }),
|
|
2990
4137
|
answers.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
2991
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2992
|
-
|
|
4138
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
4139
|
+
"text",
|
|
4140
|
+
{
|
|
4141
|
+
x: qW / 2,
|
|
4142
|
+
y: Q_BASE_H2 + 22,
|
|
4143
|
+
textAnchor: "middle",
|
|
4144
|
+
fontSize: 10,
|
|
4145
|
+
fill: amber,
|
|
4146
|
+
opacity: 0.4,
|
|
4147
|
+
fontWeight: 600,
|
|
4148
|
+
style: STYLE_LABEL,
|
|
4149
|
+
children: "No answers yet"
|
|
4150
|
+
}
|
|
4151
|
+
),
|
|
4152
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
4153
|
+
"text",
|
|
4154
|
+
{
|
|
4155
|
+
x: qW / 2,
|
|
4156
|
+
y: Q_BASE_H2 + 36,
|
|
4157
|
+
textAnchor: "middle",
|
|
4158
|
+
fontSize: 9,
|
|
4159
|
+
fill: textSub,
|
|
4160
|
+
opacity: 0.7,
|
|
4161
|
+
style: STYLE_LABEL,
|
|
4162
|
+
children: "Open panel \u2192 Add Answer"
|
|
4163
|
+
}
|
|
4164
|
+
)
|
|
2993
4165
|
] }),
|
|
2994
4166
|
answers.map((ans, i) => {
|
|
2995
4167
|
const prevW = answers.slice(0, i).reduce((s2, a) => s2 + answerCardW2(a) + Q_CARD_PAD2, 0);
|
|
@@ -3083,7 +4255,22 @@ function QuestionNode({ node, selected, edges, isDark, onAnswerPortDown, qW }) {
|
|
|
3083
4255
|
})
|
|
3084
4256
|
] });
|
|
3085
4257
|
}
|
|
3086
|
-
function EdgeLine({
|
|
4258
|
+
function EdgeLine({
|
|
4259
|
+
edge,
|
|
4260
|
+
nodes,
|
|
4261
|
+
variant,
|
|
4262
|
+
t,
|
|
4263
|
+
isDark,
|
|
4264
|
+
acc,
|
|
4265
|
+
editing,
|
|
4266
|
+
editValue,
|
|
4267
|
+
onEditChange,
|
|
4268
|
+
onEditCommit,
|
|
4269
|
+
onEditCancel,
|
|
4270
|
+
onDoubleClick,
|
|
4271
|
+
onContextMenu,
|
|
4272
|
+
onWaypointDown
|
|
4273
|
+
}) {
|
|
3087
4274
|
const [hovered, setHovered] = (0, import_react13.useState)(false);
|
|
3088
4275
|
const from = nodes.find((n) => n.id === edge.from);
|
|
3089
4276
|
const to = nodes.find((n) => n.id === edge.to);
|
|
@@ -3287,13 +4474,13 @@ function Minimap({
|
|
|
3287
4474
|
x: (mx - offsetX) / scale,
|
|
3288
4475
|
y: (my - offsetY) / scale
|
|
3289
4476
|
});
|
|
3290
|
-
const panTo = (
|
|
4477
|
+
const panTo = (e) => {
|
|
3291
4478
|
const rect = e.currentTarget.getBoundingClientRect();
|
|
3292
4479
|
const mx = e.clientX - rect.left;
|
|
3293
4480
|
const my = e.clientY - rect.top;
|
|
3294
4481
|
const { x, y } = unproject(mx, my);
|
|
3295
4482
|
onCenterOn(x, y);
|
|
3296
|
-
}
|
|
4483
|
+
};
|
|
3297
4484
|
const onMouseDown = (e) => {
|
|
3298
4485
|
e.stopPropagation();
|
|
3299
4486
|
dragRef.current = { active: true };
|
|
@@ -3480,29 +4667,106 @@ function ContextMenu({
|
|
|
3480
4667
|
fontFamily: "ui-sans-serif,system-ui,sans-serif"
|
|
3481
4668
|
},
|
|
3482
4669
|
children: edgeId ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
|
|
3483
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
4670
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
4671
|
+
"div",
|
|
4672
|
+
{
|
|
4673
|
+
style: {
|
|
4674
|
+
padding: "4px 14px 6px",
|
|
4675
|
+
fontSize: 10,
|
|
4676
|
+
fontWeight: 700,
|
|
4677
|
+
color: muted,
|
|
4678
|
+
textTransform: "uppercase",
|
|
4679
|
+
letterSpacing: 0.8
|
|
4680
|
+
},
|
|
4681
|
+
children: "Edge"
|
|
4682
|
+
}
|
|
4683
|
+
),
|
|
3484
4684
|
item("Rename label (dbl-click)", () => onEdgeRename?.()),
|
|
3485
4685
|
divider2,
|
|
3486
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
4686
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
4687
|
+
"div",
|
|
4688
|
+
{
|
|
4689
|
+
style: {
|
|
4690
|
+
padding: "4px 14px 2px",
|
|
4691
|
+
fontSize: 9,
|
|
4692
|
+
fontWeight: 700,
|
|
4693
|
+
color: muted,
|
|
4694
|
+
textTransform: "uppercase",
|
|
4695
|
+
letterSpacing: 0.8
|
|
4696
|
+
},
|
|
4697
|
+
children: "Style"
|
|
4698
|
+
}
|
|
4699
|
+
),
|
|
4700
|
+
item(
|
|
4701
|
+
`Solid${currentEdgeStyle === "solid" || !currentEdgeStyle ? " \u2713" : ""}`,
|
|
4702
|
+
() => onEdgeStyle?.("solid")
|
|
4703
|
+
),
|
|
4704
|
+
item(
|
|
4705
|
+
`Dashed${currentEdgeStyle === "dashed" ? " \u2713" : ""}`,
|
|
4706
|
+
() => onEdgeStyle?.("dashed")
|
|
4707
|
+
),
|
|
4708
|
+
item(
|
|
4709
|
+
`Dotted${currentEdgeStyle === "dotted" ? " \u2713" : ""}`,
|
|
4710
|
+
() => onEdgeStyle?.("dotted")
|
|
4711
|
+
),
|
|
3490
4712
|
divider2,
|
|
3491
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
3492
|
-
|
|
4713
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
4714
|
+
"div",
|
|
4715
|
+
{
|
|
4716
|
+
style: {
|
|
4717
|
+
padding: "4px 14px 2px",
|
|
4718
|
+
fontSize: 9,
|
|
4719
|
+
fontWeight: 700,
|
|
4720
|
+
color: muted,
|
|
4721
|
+
textTransform: "uppercase",
|
|
4722
|
+
letterSpacing: 0.8
|
|
4723
|
+
},
|
|
4724
|
+
children: "Arrowhead"
|
|
4725
|
+
}
|
|
4726
|
+
),
|
|
4727
|
+
item(
|
|
4728
|
+
`Arrow${currentEdgeArrow !== "none" ? " \u2713" : ""}`,
|
|
4729
|
+
() => onEdgeArrowhead?.("arrow")
|
|
4730
|
+
),
|
|
3493
4731
|
item(`None${currentEdgeArrow === "none" ? " \u2713" : ""}`, () => onEdgeArrowhead?.("none")),
|
|
3494
4732
|
divider2,
|
|
3495
4733
|
item("Reset routing", () => onEdgeResetRouting?.(), void 0, !edgeHasWaypoint),
|
|
3496
4734
|
item("Delete edge", () => onEdgeDelete?.(), "#ef4444")
|
|
3497
4735
|
] }) : nodeId ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
|
|
3498
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
4736
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
4737
|
+
"div",
|
|
4738
|
+
{
|
|
4739
|
+
style: {
|
|
4740
|
+
padding: "4px 14px 6px",
|
|
4741
|
+
fontSize: 10,
|
|
4742
|
+
fontWeight: 700,
|
|
4743
|
+
color: muted,
|
|
4744
|
+
textTransform: "uppercase",
|
|
4745
|
+
letterSpacing: 0.8
|
|
4746
|
+
},
|
|
4747
|
+
children: "Node"
|
|
4748
|
+
}
|
|
4749
|
+
),
|
|
3499
4750
|
item("Rename (dbl-click)", onRename),
|
|
3500
4751
|
item("Duplicate", onDuplicate),
|
|
3501
4752
|
item("Disconnect all edges", onDisconnect),
|
|
3502
4753
|
divider2,
|
|
3503
4754
|
item("Delete node", onDelete, "#ef4444")
|
|
3504
4755
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
|
|
3505
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
4756
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
4757
|
+
"div",
|
|
4758
|
+
{
|
|
4759
|
+
style: {
|
|
4760
|
+
padding: "4px 14px 6px",
|
|
4761
|
+
fontSize: 10,
|
|
4762
|
+
fontWeight: 700,
|
|
4763
|
+
color: muted,
|
|
4764
|
+
textTransform: "uppercase",
|
|
4765
|
+
letterSpacing: 0.8
|
|
4766
|
+
},
|
|
4767
|
+
children: "Canvas"
|
|
4768
|
+
}
|
|
4769
|
+
),
|
|
3506
4770
|
item("Add node here", onAddNode, acc.color),
|
|
3507
4771
|
item("Re-center (Ctrl+0)", onReCenter),
|
|
3508
4772
|
divider2,
|
|
@@ -3519,8 +4783,20 @@ var STYLE_LABEL2 = { pointerEvents: "none", userSelect: "none" };
|
|
|
3519
4783
|
var STYLE_LIVE_PORT = { opacity: 0.85, pointerEvents: "none" };
|
|
3520
4784
|
var STYLE_NODE_GRAB = { cursor: "grab" };
|
|
3521
4785
|
var STYLE_NODE_GRABBING = { cursor: "grabbing" };
|
|
3522
|
-
var STYLE_PORT_VISIBLE = {
|
|
3523
|
-
|
|
4786
|
+
var STYLE_PORT_VISIBLE = {
|
|
4787
|
+
cursor: "crosshair",
|
|
4788
|
+
opacity: 1,
|
|
4789
|
+
transition: "opacity 0.15s",
|
|
4790
|
+
pointerEvents: "all",
|
|
4791
|
+
filter: "drop-shadow(0 1px 3px rgba(0,0,0,0.25))"
|
|
4792
|
+
};
|
|
4793
|
+
var STYLE_PORT_HIDDEN = {
|
|
4794
|
+
cursor: "crosshair",
|
|
4795
|
+
opacity: 0,
|
|
4796
|
+
transition: "opacity 0.15s",
|
|
4797
|
+
pointerEvents: "none",
|
|
4798
|
+
filter: "drop-shadow(0 1px 3px rgba(0,0,0,0.25))"
|
|
4799
|
+
};
|
|
3524
4800
|
function DiagramCanvas(props) {
|
|
3525
4801
|
const {
|
|
3526
4802
|
model,
|
|
@@ -3591,25 +4867,35 @@ function DiagramCanvas(props) {
|
|
|
3591
4867
|
onCtxEdgeDelete,
|
|
3592
4868
|
onCtxEdgeResetRouting
|
|
3593
4869
|
} = props;
|
|
3594
|
-
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
4870
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
4871
|
+
"div",
|
|
4872
|
+
{
|
|
4873
|
+
ref: containerRef,
|
|
4874
|
+
style: { flex: 1, overflow: "hidden", position: "relative", background: t.canvas },
|
|
4875
|
+
children: [
|
|
4876
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
4877
|
+
"svg",
|
|
4878
|
+
{
|
|
4879
|
+
ref: svgRef,
|
|
4880
|
+
width: "100%",
|
|
4881
|
+
height: "100%",
|
|
4882
|
+
role: "application",
|
|
4883
|
+
"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.`,
|
|
4884
|
+
tabIndex: 0,
|
|
4885
|
+
style: {
|
|
4886
|
+
display: "block",
|
|
4887
|
+
cursor: pan ? "grabbing" : drag ? "grabbing" : liveEdge ? "crosshair" : "default",
|
|
4888
|
+
userSelect: "none",
|
|
4889
|
+
outline: "none"
|
|
4890
|
+
},
|
|
4891
|
+
onMouseDown: onSvgMouseDown,
|
|
4892
|
+
onMouseMove,
|
|
4893
|
+
onMouseUp,
|
|
4894
|
+
onMouseLeave: onMouseUp,
|
|
4895
|
+
onContextMenu: onSvgContextMenu,
|
|
4896
|
+
children: [
|
|
4897
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("defs", { children: [
|
|
4898
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("style", { children: reducedMotion ? `
|
|
3613
4899
|
.edge-flow { stroke-dasharray: 0; }
|
|
3614
4900
|
.edge-flow-amber { stroke-dasharray: 0; }
|
|
3615
4901
|
.edge-live { stroke-dasharray: 4 4; }
|
|
@@ -3620,217 +4906,351 @@ function DiagramCanvas(props) {
|
|
|
3620
4906
|
.edge-flow-amber { stroke-dasharray: 6 4; animation: edgeFlowFast 0.65s linear infinite; }
|
|
3621
4907
|
.edge-live { stroke-dasharray: 7 5; animation: edgeFlow 0.55s linear infinite; }
|
|
3622
4908
|
` }),
|
|
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
|
-
|
|
3740
|
-
|
|
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
|
-
|
|
4909
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("pattern", { id: "dots", width: GRID, height: GRID, patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("circle", { cx: GRID / 2, cy: GRID / 2, r: 1.1, fill: t.dot }) }),
|
|
4910
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("filter", { id: "nodeShadow", x: "-25%", y: "-25%", width: "150%", height: "160%", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("feDropShadow", { dx: "0", dy: "3", stdDeviation: "5", floodColor: shadowClr, floodOpacity: "1" }) }),
|
|
4911
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
4912
|
+
"marker",
|
|
4913
|
+
{
|
|
4914
|
+
id: "arrowhead",
|
|
4915
|
+
markerWidth: "9",
|
|
4916
|
+
markerHeight: "7",
|
|
4917
|
+
refX: "8",
|
|
4918
|
+
refY: "3.5",
|
|
4919
|
+
orient: "auto",
|
|
4920
|
+
markerUnits: "strokeWidth",
|
|
4921
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: arrowClr })
|
|
4922
|
+
}
|
|
4923
|
+
),
|
|
4924
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
4925
|
+
"marker",
|
|
4926
|
+
{
|
|
4927
|
+
id: "arrowAmber",
|
|
4928
|
+
markerWidth: "9",
|
|
4929
|
+
markerHeight: "7",
|
|
4930
|
+
refX: "8",
|
|
4931
|
+
refY: "3.5",
|
|
4932
|
+
orient: "auto",
|
|
4933
|
+
markerUnits: "strokeWidth",
|
|
4934
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: amberArrow })
|
|
4935
|
+
}
|
|
4936
|
+
),
|
|
4937
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
4938
|
+
"marker",
|
|
4939
|
+
{
|
|
4940
|
+
id: "arrowLive",
|
|
4941
|
+
markerWidth: "9",
|
|
4942
|
+
markerHeight: "7",
|
|
4943
|
+
refX: "8",
|
|
4944
|
+
refY: "3.5",
|
|
4945
|
+
orient: "auto",
|
|
4946
|
+
markerUnits: "strokeWidth",
|
|
4947
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "M0,0.5 L9,3.5 L0,6.5 L2.2,3.5 Z", fill: acc.color })
|
|
4948
|
+
}
|
|
4949
|
+
)
|
|
4950
|
+
] }),
|
|
4951
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("rect", { width: "100%", height: "100%", fill: "url(#dots)", "data-bg": "1" }),
|
|
4952
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("g", { transform: `translate(${transform.x},${transform.y}) scale(${transform.scale})`, children: [
|
|
4953
|
+
model.edges.map((e) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
4954
|
+
EdgeLine,
|
|
4955
|
+
{
|
|
4956
|
+
edge: e,
|
|
4957
|
+
nodes: model.nodes,
|
|
4958
|
+
variant,
|
|
4959
|
+
t,
|
|
4960
|
+
isDark,
|
|
4961
|
+
acc,
|
|
4962
|
+
editing: editingEdgeId === e.id,
|
|
4963
|
+
editValue: editEdgeLabel,
|
|
4964
|
+
onEditChange: setEditEdgeLabel,
|
|
4965
|
+
onEditCommit: commitEdgeEdit,
|
|
4966
|
+
onEditCancel: () => setEditingEdgeId(null),
|
|
4967
|
+
onDoubleClick: beginEditEdge,
|
|
4968
|
+
onContextMenu: onEdgeContextMenu,
|
|
4969
|
+
onWaypointDown: (ev, edgeId) => setWaypointDrag(edgeId)
|
|
4970
|
+
},
|
|
4971
|
+
e.id
|
|
4972
|
+
)),
|
|
4973
|
+
liveEdge && (() => {
|
|
4974
|
+
const d = bezierPath2(
|
|
4975
|
+
liveEdge.fromX,
|
|
4976
|
+
liveEdge.fromY,
|
|
4977
|
+
liveEdge.toX,
|
|
4978
|
+
liveEdge.toY,
|
|
4979
|
+
liveEdge.exitDir
|
|
4980
|
+
);
|
|
4981
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
4982
|
+
"path",
|
|
4983
|
+
{
|
|
4984
|
+
d,
|
|
4985
|
+
fill: "none",
|
|
4986
|
+
stroke: acc.color,
|
|
4987
|
+
strokeWidth: 2,
|
|
4988
|
+
strokeLinecap: "round",
|
|
4989
|
+
className: "edge-live",
|
|
4990
|
+
opacity: 0.8,
|
|
4991
|
+
markerEnd: "url(#arrowLive)"
|
|
4992
|
+
}
|
|
4993
|
+
);
|
|
4994
|
+
})(),
|
|
4995
|
+
alignGuides?.x && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
4996
|
+
"line",
|
|
4997
|
+
{
|
|
4998
|
+
x1: alignGuides.x.pos,
|
|
4999
|
+
x2: alignGuides.x.pos,
|
|
5000
|
+
y1: alignGuides.x.minY,
|
|
5001
|
+
y2: alignGuides.x.maxY,
|
|
5002
|
+
stroke: acc.color,
|
|
5003
|
+
strokeWidth: 1 / transform.scale,
|
|
5004
|
+
strokeDasharray: `${4 / transform.scale} ${3 / transform.scale}`,
|
|
5005
|
+
opacity: 0.85,
|
|
5006
|
+
pointerEvents: "none"
|
|
5007
|
+
}
|
|
5008
|
+
),
|
|
5009
|
+
alignGuides?.y && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
5010
|
+
"line",
|
|
5011
|
+
{
|
|
5012
|
+
y1: alignGuides.y.pos,
|
|
5013
|
+
y2: alignGuides.y.pos,
|
|
5014
|
+
x1: alignGuides.y.minX,
|
|
5015
|
+
x2: alignGuides.y.maxX,
|
|
5016
|
+
stroke: acc.color,
|
|
5017
|
+
strokeWidth: 1 / transform.scale,
|
|
5018
|
+
strokeDasharray: `${4 / transform.scale} ${3 / transform.scale}`,
|
|
5019
|
+
opacity: 0.85,
|
|
5020
|
+
pointerEvents: "none"
|
|
5021
|
+
}
|
|
5022
|
+
),
|
|
5023
|
+
model.nodes.map((node, idx) => {
|
|
5024
|
+
const isHovered = hoveredId === node.id;
|
|
5025
|
+
const isQuestion2 = variant === "question";
|
|
5026
|
+
const { w: nW } = nodeDims(node, variant);
|
|
5027
|
+
const isSelected = selectedSet.has(node.id);
|
|
5028
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
5029
|
+
"g",
|
|
5030
|
+
{
|
|
5031
|
+
transform: `translate(${node.x ?? 0},${node.y ?? 0})`,
|
|
5032
|
+
role: "button",
|
|
5033
|
+
tabIndex: 0,
|
|
5034
|
+
"aria-label": `${variantLabel} ${variant === "journey" ? idx + 1 + ": " : ""}${node.label}${isSelected ? ", selected" : ""}`,
|
|
5035
|
+
style: drag?.nodeId === node.id ? STYLE_NODE_GRABBING : STYLE_NODE_GRAB,
|
|
5036
|
+
onMouseDown: (e) => onNodeMouseDown(e, node.id),
|
|
5037
|
+
onMouseUp: (e) => onNodeMouseUp(e, node.id),
|
|
5038
|
+
onDoubleClick: (e) => onNodeDblClick(e, node.id),
|
|
5039
|
+
onContextMenu: (e) => onNodeContextMenu(e, node.id),
|
|
5040
|
+
onMouseEnter: () => setHoveredId(node.id),
|
|
5041
|
+
onMouseLeave: () => setHoveredId(null),
|
|
5042
|
+
onFocus: () => setHoveredId(node.id),
|
|
5043
|
+
onBlur: () => setHoveredId(null),
|
|
5044
|
+
onKeyDown: (e) => {
|
|
5045
|
+
if (e.key === "F2" || e.key === "Enter" && !e.ctrlKey && !e.metaKey) {
|
|
5046
|
+
e.preventDefault();
|
|
5047
|
+
setEditingId(node.id);
|
|
5048
|
+
setEditLabel(node.label);
|
|
5049
|
+
}
|
|
5050
|
+
},
|
|
5051
|
+
children: [
|
|
5052
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("title", { children: `${variantLabel}: ${node.label}` }),
|
|
5053
|
+
isQuestion2 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
5054
|
+
QuestionNode,
|
|
5055
|
+
{
|
|
5056
|
+
node,
|
|
5057
|
+
selected: isSelected,
|
|
5058
|
+
edges: model.edges,
|
|
5059
|
+
isDark,
|
|
5060
|
+
onAnswerPortDown,
|
|
5061
|
+
qW: nW
|
|
5062
|
+
}
|
|
5063
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
|
|
5064
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
5065
|
+
NodeShape,
|
|
5066
|
+
{
|
|
5067
|
+
node,
|
|
5068
|
+
selected: isSelected,
|
|
5069
|
+
variant,
|
|
5070
|
+
stepNumber: variant === "journey" ? idx + 1 : void 0,
|
|
5071
|
+
t,
|
|
5072
|
+
isDark,
|
|
5073
|
+
w: nW
|
|
5074
|
+
}
|
|
5075
|
+
),
|
|
5076
|
+
editingId === node.id ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("foreignObject", { x: 6, y: 6, width: nW - 12, height: NODE_H2 - 12, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
5077
|
+
"input",
|
|
5078
|
+
{
|
|
5079
|
+
autoFocus: true,
|
|
5080
|
+
value: editLabel,
|
|
5081
|
+
onChange: (e) => setEditLabel(e.target.value),
|
|
5082
|
+
onBlur: commitEdit,
|
|
5083
|
+
onKeyDown: (e) => {
|
|
5084
|
+
if (e.key === "Enter") commitEdit();
|
|
5085
|
+
if (e.key === "Escape") setEditingId(null);
|
|
5086
|
+
},
|
|
5087
|
+
style: {
|
|
5088
|
+
width: "100%",
|
|
5089
|
+
height: "100%",
|
|
5090
|
+
border: "none",
|
|
5091
|
+
borderRadius: 6,
|
|
5092
|
+
outline: `2px solid ${acc.color}`,
|
|
5093
|
+
textAlign: "center",
|
|
5094
|
+
fontSize: 13,
|
|
5095
|
+
fontWeight: 500,
|
|
5096
|
+
background: t.inputBg,
|
|
5097
|
+
boxSizing: "border-box",
|
|
5098
|
+
padding: "0 6px",
|
|
5099
|
+
fontFamily: "inherit",
|
|
5100
|
+
color: t.inputText
|
|
5101
|
+
}
|
|
5102
|
+
}
|
|
5103
|
+
) }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
5104
|
+
"text",
|
|
5105
|
+
{
|
|
5106
|
+
x: nW / 2,
|
|
5107
|
+
y: NODE_H2 / 2 + 5,
|
|
5108
|
+
textAnchor: "middle",
|
|
5109
|
+
fontSize: 13,
|
|
5110
|
+
fontWeight: "500",
|
|
5111
|
+
fontFamily: "ui-sans-serif,system-ui,sans-serif",
|
|
5112
|
+
fill: isSelected ? acc.color : t.textPrimary,
|
|
5113
|
+
style: STYLE_LABEL2,
|
|
5114
|
+
children: node.label
|
|
5115
|
+
}
|
|
5116
|
+
),
|
|
5117
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
5118
|
+
"circle",
|
|
5119
|
+
{
|
|
5120
|
+
cx: nW / 2,
|
|
5121
|
+
cy: NODE_H2 + 1,
|
|
5122
|
+
r: portR,
|
|
5123
|
+
fill: acc.color,
|
|
5124
|
+
stroke: isDark ? "#0f172a" : "white",
|
|
5125
|
+
strokeWidth: 2,
|
|
5126
|
+
style: isHovered || isCoarse ? STYLE_PORT_VISIBLE : STYLE_PORT_HIDDEN,
|
|
5127
|
+
onMouseDown: (e) => onPortMouseDown(e, node.id)
|
|
5128
|
+
}
|
|
5129
|
+
)
|
|
5130
|
+
] }),
|
|
5131
|
+
liveEdge && liveEdge.fromId !== node.id && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
5132
|
+
"circle",
|
|
5133
|
+
{
|
|
5134
|
+
cx: nW / 2,
|
|
5135
|
+
cy: -1,
|
|
5136
|
+
r: portR,
|
|
5137
|
+
fill: acc.color,
|
|
5138
|
+
stroke: isDark ? "#0f172a" : "white",
|
|
5139
|
+
strokeWidth: 2,
|
|
5140
|
+
style: STYLE_LIVE_PORT
|
|
5141
|
+
}
|
|
5142
|
+
)
|
|
5143
|
+
]
|
|
5144
|
+
},
|
|
5145
|
+
node.id
|
|
5146
|
+
);
|
|
5147
|
+
})
|
|
5148
|
+
] })
|
|
5149
|
+
]
|
|
3772
5150
|
}
|
|
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
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
5151
|
+
),
|
|
5152
|
+
boxSel && Math.abs(boxSel.cx - boxSel.sx) + Math.abs(boxSel.cy - boxSel.sy) > 4 && containerRef.current && (() => {
|
|
5153
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
5154
|
+
const left = Math.min(boxSel.sx, boxSel.cx) - rect.left;
|
|
5155
|
+
const top = Math.min(boxSel.sy, boxSel.cy) - rect.top;
|
|
5156
|
+
const w = Math.abs(boxSel.cx - boxSel.sx);
|
|
5157
|
+
const h = Math.abs(boxSel.cy - boxSel.sy);
|
|
5158
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
5159
|
+
"div",
|
|
5160
|
+
{
|
|
5161
|
+
style: {
|
|
5162
|
+
position: "absolute",
|
|
5163
|
+
left,
|
|
5164
|
+
top,
|
|
5165
|
+
width: w,
|
|
5166
|
+
height: h,
|
|
5167
|
+
border: `1px dashed ${acc.color}`,
|
|
5168
|
+
background: isDark ? "rgba(99,102,241,0.10)" : "rgba(99,102,241,0.08)",
|
|
5169
|
+
pointerEvents: "none",
|
|
5170
|
+
borderRadius: 4
|
|
5171
|
+
}
|
|
5172
|
+
}
|
|
5173
|
+
);
|
|
5174
|
+
})(),
|
|
5175
|
+
model.nodes.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
5176
|
+
"div",
|
|
5177
|
+
{
|
|
5178
|
+
style: {
|
|
5179
|
+
position: "absolute",
|
|
5180
|
+
inset: 0,
|
|
5181
|
+
display: "flex",
|
|
5182
|
+
flexDirection: "column",
|
|
5183
|
+
alignItems: "center",
|
|
5184
|
+
justifyContent: "center",
|
|
5185
|
+
pointerEvents: "none",
|
|
5186
|
+
gap: 8
|
|
5187
|
+
},
|
|
5188
|
+
children: [
|
|
5189
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { fontSize: 36, opacity: 0.1, color: t.textPrimary }, children: variant === "question" ? "?" : variant === "journey" ? "\u2197" : "\u2B21" }),
|
|
5190
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { fontSize: 13, color: t.textMuted, fontWeight: 500 }, children: [
|
|
5191
|
+
"Click ",
|
|
5192
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("strong", { style: { color: acc.color }, children: [
|
|
5193
|
+
"+ ",
|
|
5194
|
+
variantLabel
|
|
5195
|
+
] }),
|
|
5196
|
+
" to start"
|
|
5197
|
+
] })
|
|
5198
|
+
]
|
|
5199
|
+
}
|
|
5200
|
+
),
|
|
5201
|
+
model.nodes.length > 0 && viewport.w > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
5202
|
+
Minimap,
|
|
5203
|
+
{
|
|
5204
|
+
model,
|
|
5205
|
+
viewportW: viewport.w,
|
|
5206
|
+
viewportH: viewport.h,
|
|
5207
|
+
transform,
|
|
5208
|
+
isDark,
|
|
5209
|
+
accentColor: acc.color,
|
|
5210
|
+
measureNode: (n) => nodeDims(n, variant),
|
|
5211
|
+
onCenterOn: (cx, cy) => {
|
|
5212
|
+
setTransform((tr) => ({
|
|
5213
|
+
...tr,
|
|
5214
|
+
x: viewport.w / 2 - cx * tr.scale,
|
|
5215
|
+
y: viewport.h / 2 - cy * tr.scale
|
|
5216
|
+
}));
|
|
5217
|
+
}
|
|
5218
|
+
}
|
|
5219
|
+
),
|
|
5220
|
+
ctxMenu && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
5221
|
+
ContextMenu,
|
|
5222
|
+
{
|
|
5223
|
+
x: ctxMenu.x,
|
|
5224
|
+
y: ctxMenu.y,
|
|
5225
|
+
nodeId: ctxMenu.nodeId,
|
|
5226
|
+
edgeId: ctxMenu.edgeId,
|
|
5227
|
+
isDark,
|
|
5228
|
+
t,
|
|
5229
|
+
acc,
|
|
5230
|
+
canUndo: history.canUndo,
|
|
5231
|
+
canRedo: history.canRedo,
|
|
5232
|
+
onUndo: onCtxUndo,
|
|
5233
|
+
onRedo: onCtxRedo,
|
|
5234
|
+
onReCenter: onCtxReCenter,
|
|
5235
|
+
onAddNode: onCtxAddNode,
|
|
5236
|
+
onDuplicate: onCtxDuplicate,
|
|
5237
|
+
onRename: onCtxRename,
|
|
5238
|
+
onDelete: onCtxDelete,
|
|
5239
|
+
onDisconnect: onCtxDisconnect,
|
|
5240
|
+
currentEdgeStyle: ctxEdgeStyle,
|
|
5241
|
+
currentEdgeArrow: ctxEdgeArrow,
|
|
5242
|
+
edgeHasWaypoint: ctxEdgeHasWaypoint,
|
|
5243
|
+
onEdgeRename: onCtxEdgeRename,
|
|
5244
|
+
onEdgeStyle: onCtxEdgeStyle,
|
|
5245
|
+
onEdgeArrowhead: onCtxEdgeArrowhead,
|
|
5246
|
+
onEdgeDelete: onCtxEdgeDelete,
|
|
5247
|
+
onEdgeResetRouting: onCtxEdgeResetRouting,
|
|
5248
|
+
containerRef
|
|
5249
|
+
}
|
|
5250
|
+
)
|
|
5251
|
+
]
|
|
5252
|
+
}
|
|
5253
|
+
);
|
|
3834
5254
|
}
|
|
3835
5255
|
|
|
3836
5256
|
// src/ui/hooks/useHistory.ts
|
|
@@ -4027,7 +5447,18 @@ function useCanvasTouch(ref, {
|
|
|
4027
5447
|
el.removeEventListener("touchend", onEnd);
|
|
4028
5448
|
el.removeEventListener("touchcancel", onEnd);
|
|
4029
5449
|
};
|
|
4030
|
-
}, [
|
|
5450
|
+
}, [
|
|
5451
|
+
ref,
|
|
5452
|
+
transform.scale,
|
|
5453
|
+
transform.x,
|
|
5454
|
+
transform.y,
|
|
5455
|
+
setTransform,
|
|
5456
|
+
onLongPress,
|
|
5457
|
+
minScale,
|
|
5458
|
+
maxScale,
|
|
5459
|
+
longPressMs,
|
|
5460
|
+
longPressSlop
|
|
5461
|
+
]);
|
|
4031
5462
|
}
|
|
4032
5463
|
|
|
4033
5464
|
// src/ui/hooks/useElementSize.ts
|
|
@@ -4141,7 +5572,17 @@ function nearestInDirection(fromX, fromY, dir, candidates) {
|
|
|
4141
5572
|
|
|
4142
5573
|
// src/ui/DiagramEditor.tsx
|
|
4143
5574
|
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
4144
|
-
var STYLE_SR_ONLY = {
|
|
5575
|
+
var STYLE_SR_ONLY = {
|
|
5576
|
+
position: "absolute",
|
|
5577
|
+
width: 1,
|
|
5578
|
+
height: 1,
|
|
5579
|
+
padding: 0,
|
|
5580
|
+
margin: -1,
|
|
5581
|
+
overflow: "hidden",
|
|
5582
|
+
clip: "rect(0 0 0 0)",
|
|
5583
|
+
whiteSpace: "nowrap",
|
|
5584
|
+
border: 0
|
|
5585
|
+
};
|
|
4145
5586
|
var STYLE_FLEX_ROW = { flex: 1, display: "flex", overflow: "hidden" };
|
|
4146
5587
|
function DiagramEditor(props) {
|
|
4147
5588
|
if (props.initialModel?.type === "sequence") {
|
|
@@ -4197,7 +5638,7 @@ function FlowchartEditor({
|
|
|
4197
5638
|
const next = new Set(prev);
|
|
4198
5639
|
if (next.has(id)) {
|
|
4199
5640
|
next.delete(id);
|
|
4200
|
-
const last = next.size ? Array.from(next)[next.size - 1] : null;
|
|
5641
|
+
const last = next.size ? Array.from(next)[next.size - 1] ?? null : null;
|
|
4201
5642
|
setSelected(last);
|
|
4202
5643
|
} else {
|
|
4203
5644
|
next.add(id);
|
|
@@ -4221,7 +5662,10 @@ function FlowchartEditor({
|
|
|
4221
5662
|
const svgRef = (0, import_react20.useRef)(null);
|
|
4222
5663
|
const containerRef = (0, import_react20.useRef)(null);
|
|
4223
5664
|
const reducedMotion = usePrefersReducedMotion();
|
|
4224
|
-
const { t, isDark } = useEditorTheme(theme, themeOverrides, {
|
|
5665
|
+
const { t, isDark } = useEditorTheme(theme, themeOverrides, {
|
|
5666
|
+
light: lightTheme,
|
|
5667
|
+
dark: darkTheme
|
|
5668
|
+
});
|
|
4225
5669
|
const isCoarse = useIsCoarsePointer();
|
|
4226
5670
|
const portR = isCoarse ? 9 : 6;
|
|
4227
5671
|
const viewport = useElementSize(svgRef);
|
|
@@ -4249,52 +5693,65 @@ function FlowchartEditor({
|
|
|
4249
5693
|
const cx = (minX + maxX) / 2, cy = (minY + maxY) / 2;
|
|
4250
5694
|
setTransform({ scale, x: W2 / 2 - cx * scale, y: H2 / 2 - cy * scale });
|
|
4251
5695
|
}, [model.nodes, variant]);
|
|
4252
|
-
const jumpToNode = (0, import_react20.useCallback)(
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
const
|
|
4272
|
-
|
|
4273
|
-
const
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
newEdges.push({ ...e, id: nextEdge(), from: idMap.get(e.from), to: idMap.get(e.to) });
|
|
5696
|
+
const jumpToNode = (0, import_react20.useCallback)(
|
|
5697
|
+
(nodeId) => {
|
|
5698
|
+
const node = model.nodes.find((n) => n.id === nodeId);
|
|
5699
|
+
if (!node || !svgRef.current) return;
|
|
5700
|
+
const rect = svgRef.current.getBoundingClientRect();
|
|
5701
|
+
const { w: nw, h: nh } = nodeDims(node, variant);
|
|
5702
|
+
const cx = (node.x ?? 0) + nw / 2;
|
|
5703
|
+
const cy = (node.y ?? 0) + nh / 2;
|
|
5704
|
+
const scale = Math.min(Math.max(transform.scale, 0.8), 1.4);
|
|
5705
|
+
setTransform({ scale, x: rect.width / 2 - cx * scale, y: rect.height / 2 - cy * scale });
|
|
5706
|
+
selectOne(nodeId);
|
|
5707
|
+
},
|
|
5708
|
+
[model.nodes, variant, transform.scale, selectOne]
|
|
5709
|
+
);
|
|
5710
|
+
const duplicateIds = (0, import_react20.useCallback)(
|
|
5711
|
+
(ids) => {
|
|
5712
|
+
if (ids.length === 0) return;
|
|
5713
|
+
const idSet = new Set(ids);
|
|
5714
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
5715
|
+
const nextNode = makeIdSource("node", model.nodes);
|
|
5716
|
+
const nextEdge = makeIdSource("e", model.edges);
|
|
5717
|
+
const newNodes = [];
|
|
5718
|
+
for (const oldId of ids) {
|
|
5719
|
+
const n = model.nodes.find((x) => x.id === oldId);
|
|
5720
|
+
if (!n) continue;
|
|
5721
|
+
const newId = nextNode();
|
|
5722
|
+
idMap.set(oldId, newId);
|
|
5723
|
+
newNodes.push({
|
|
5724
|
+
...n,
|
|
5725
|
+
id: newId,
|
|
5726
|
+
label: ids.length === 1 ? n.label + " (copy)" : n.label,
|
|
5727
|
+
x: (n.x ?? 0) + 32,
|
|
5728
|
+
y: (n.y ?? 0) + 32
|
|
5729
|
+
});
|
|
4287
5730
|
}
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
5731
|
+
const newEdges = [];
|
|
5732
|
+
for (const e of model.edges) {
|
|
5733
|
+
if (idSet.has(e.from) && idSet.has(e.to)) {
|
|
5734
|
+
newEdges.push({ ...e, id: nextEdge(), from: idMap.get(e.from), to: idMap.get(e.to) });
|
|
5735
|
+
}
|
|
5736
|
+
}
|
|
5737
|
+
const m = {
|
|
5738
|
+
...model,
|
|
5739
|
+
nodes: [...model.nodes, ...newNodes],
|
|
5740
|
+
edges: [...model.edges, ...newEdges]
|
|
5741
|
+
};
|
|
5742
|
+
applyAndPush(m);
|
|
5743
|
+
const newIds = newNodes.map((n) => n.id);
|
|
5744
|
+
setSelected(newIds[newIds.length - 1] ?? null);
|
|
5745
|
+
setSelectedSet(new Set(newIds));
|
|
5746
|
+
},
|
|
5747
|
+
[model, applyAndPush]
|
|
5748
|
+
);
|
|
5749
|
+
const duplicateNode = (0, import_react20.useCallback)(
|
|
5750
|
+
(nodeId) => {
|
|
5751
|
+
duplicateIds([nodeId]);
|
|
5752
|
+
},
|
|
5753
|
+
[duplicateIds]
|
|
5754
|
+
);
|
|
4298
5755
|
(0, import_react20.useEffect)(() => {
|
|
4299
5756
|
if (!ctxMenu) return;
|
|
4300
5757
|
const close = () => setCtxMenu(null);
|
|
@@ -4302,18 +5759,27 @@ function FlowchartEditor({
|
|
|
4302
5759
|
return () => window.removeEventListener("mousedown", close);
|
|
4303
5760
|
}, [ctxMenu]);
|
|
4304
5761
|
const keyCommands = [
|
|
4305
|
-
{
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
5762
|
+
{
|
|
5763
|
+
match: (e) => (e.ctrlKey || e.metaKey) && e.key === "z" && !e.shiftKey,
|
|
5764
|
+
run: () => {
|
|
5765
|
+
undo();
|
|
5766
|
+
return true;
|
|
5767
|
+
}
|
|
5768
|
+
},
|
|
5769
|
+
{
|
|
5770
|
+
match: (e) => (e.ctrlKey || e.metaKey) && (e.key === "y" || e.shiftKey && e.key === "z"),
|
|
5771
|
+
run: () => {
|
|
5772
|
+
redo();
|
|
5773
|
+
return true;
|
|
5774
|
+
}
|
|
5775
|
+
},
|
|
5776
|
+
{
|
|
5777
|
+
match: (e) => (e.ctrlKey || e.metaKey) && e.key === "0",
|
|
5778
|
+
run: () => {
|
|
5779
|
+
reCenter();
|
|
5780
|
+
return true;
|
|
5781
|
+
}
|
|
5782
|
+
},
|
|
4317
5783
|
{
|
|
4318
5784
|
match: (e) => (e.ctrlKey || e.metaKey) && (e.key === "d" || e.key === "D") && selectedSet.size > 0,
|
|
4319
5785
|
run: () => {
|
|
@@ -4353,12 +5819,18 @@ function FlowchartEditor({
|
|
|
4353
5819
|
from: idMap.get(ed.from) ?? ed.from,
|
|
4354
5820
|
to: idMap.get(ed.to) ?? ed.to
|
|
4355
5821
|
}));
|
|
4356
|
-
const m = {
|
|
5822
|
+
const m = {
|
|
5823
|
+
...model,
|
|
5824
|
+
nodes: [...model.nodes, ...newNodes],
|
|
5825
|
+
edges: [...model.edges, ...newEdges]
|
|
5826
|
+
};
|
|
4357
5827
|
applyAndPush(m);
|
|
4358
5828
|
const newIds = newNodes.map((n) => n.id);
|
|
4359
|
-
setSelected(newIds[newIds.length - 1]);
|
|
5829
|
+
setSelected(newIds[newIds.length - 1] ?? null);
|
|
4360
5830
|
setSelectedSet(new Set(newIds));
|
|
4361
|
-
setAnnouncement(
|
|
5831
|
+
setAnnouncement(
|
|
5832
|
+
`Pasted ${newIds.length} ${variantLabel.toLowerCase()}${newIds.length === 1 ? "" : "s"}.`
|
|
5833
|
+
);
|
|
4362
5834
|
return true;
|
|
4363
5835
|
}
|
|
4364
5836
|
},
|
|
@@ -4384,7 +5856,9 @@ function FlowchartEditor({
|
|
|
4384
5856
|
};
|
|
4385
5857
|
applyAndPush(updated);
|
|
4386
5858
|
clearSelection();
|
|
4387
|
-
setAnnouncement(
|
|
5859
|
+
setAnnouncement(
|
|
5860
|
+
`Deleted ${ids.size} ${variantLabel.toLowerCase()}${ids.size === 1 ? "" : "s"}.`
|
|
5861
|
+
);
|
|
4388
5862
|
return true;
|
|
4389
5863
|
}
|
|
4390
5864
|
},
|
|
@@ -4419,18 +5893,40 @@ function FlowchartEditor({
|
|
|
4419
5893
|
const ids = selectedSet;
|
|
4420
5894
|
const updated = {
|
|
4421
5895
|
...model,
|
|
4422
|
-
nodes: model.nodes.map(
|
|
5896
|
+
nodes: model.nodes.map(
|
|
5897
|
+
(n) => ids.has(n.id) ? { ...n, x: snap((n.x ?? 0) + dx), y: snap((n.y ?? 0) + dy) } : n
|
|
5898
|
+
)
|
|
4423
5899
|
};
|
|
4424
5900
|
applyAndPush(updated);
|
|
4425
5901
|
return true;
|
|
4426
5902
|
}
|
|
4427
5903
|
}
|
|
4428
5904
|
];
|
|
4429
|
-
useEditorKeyboard(keyCommands, [
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
5905
|
+
useEditorKeyboard(keyCommands, [
|
|
5906
|
+
undo,
|
|
5907
|
+
redo,
|
|
5908
|
+
reCenter,
|
|
5909
|
+
selected,
|
|
5910
|
+
selectedSet,
|
|
5911
|
+
ctxMenu,
|
|
5912
|
+
liveEdge,
|
|
5913
|
+
editingId,
|
|
5914
|
+
boxSel,
|
|
5915
|
+
model,
|
|
5916
|
+
applyAndPush,
|
|
5917
|
+
duplicateNode,
|
|
5918
|
+
clearSelection
|
|
5919
|
+
]);
|
|
5920
|
+
const toCanvas = (0, import_react20.useCallback)(
|
|
5921
|
+
(clientX, clientY) => {
|
|
5922
|
+
const rect = svgRef.current.getBoundingClientRect();
|
|
5923
|
+
return {
|
|
5924
|
+
x: (clientX - rect.left - transform.x) / transform.scale,
|
|
5925
|
+
y: (clientY - rect.top - transform.y) / transform.scale
|
|
5926
|
+
};
|
|
5927
|
+
},
|
|
5928
|
+
[transform]
|
|
5929
|
+
);
|
|
4434
5930
|
useCanvasWheel(svgRef, setTransform);
|
|
4435
5931
|
const onCanvasLongPress = (0, import_react20.useCallback)((x, y) => {
|
|
4436
5932
|
setCtxMenu({ x, y, nodeId: null });
|
|
@@ -4441,13 +5937,28 @@ function FlowchartEditor({
|
|
|
4441
5937
|
const node = model.nodes.find((n) => n.id === nodeId);
|
|
4442
5938
|
const { x, y } = toCanvas(e.clientX, e.clientY);
|
|
4443
5939
|
const nW = nodeWidth2(node.label);
|
|
4444
|
-
setLiveEdge({
|
|
5940
|
+
setLiveEdge({
|
|
5941
|
+
fromId: nodeId,
|
|
5942
|
+
fromX: (node.x ?? 0) + nW / 2,
|
|
5943
|
+
fromY: (node.y ?? 0) + NODE_H2,
|
|
5944
|
+
exitDir: "bottom",
|
|
5945
|
+
toX: x,
|
|
5946
|
+
toY: y
|
|
5947
|
+
});
|
|
4445
5948
|
};
|
|
4446
5949
|
const onAnswerPortDown = (e, nodeId, answer, portXInNode, portYInNode) => {
|
|
4447
5950
|
e.stopPropagation();
|
|
4448
5951
|
const node = model.nodes.find((n) => n.id === nodeId);
|
|
4449
5952
|
const { x, y } = toCanvas(e.clientX, e.clientY);
|
|
4450
|
-
setLiveEdge({
|
|
5953
|
+
setLiveEdge({
|
|
5954
|
+
fromId: nodeId,
|
|
5955
|
+
fromX: (node.x ?? 0) + portXInNode,
|
|
5956
|
+
fromY: (node.y ?? 0) + portYInNode,
|
|
5957
|
+
exitDir: "bottom",
|
|
5958
|
+
answerLabel: answer,
|
|
5959
|
+
toX: x,
|
|
5960
|
+
toY: y
|
|
5961
|
+
});
|
|
4451
5962
|
};
|
|
4452
5963
|
const onNodeMouseDown = (e, id) => {
|
|
4453
5964
|
e.stopPropagation();
|
|
@@ -4474,7 +5985,11 @@ function FlowchartEditor({
|
|
|
4474
5985
|
selectOne(id);
|
|
4475
5986
|
groupDragOriginsRef.current = null;
|
|
4476
5987
|
}
|
|
4477
|
-
setDrag({
|
|
5988
|
+
setDrag({
|
|
5989
|
+
nodeId: id,
|
|
5990
|
+
ox: e.clientX - (transform.x + (node.x ?? 0) * transform.scale),
|
|
5991
|
+
oy: e.clientY - (transform.y + (node.y ?? 0) * transform.scale)
|
|
5992
|
+
});
|
|
4478
5993
|
};
|
|
4479
5994
|
const onNodeMouseUp = (e, targetId) => {
|
|
4480
5995
|
if (!liveEdge || liveEdge.fromId === targetId) return;
|
|
@@ -4484,12 +5999,27 @@ function FlowchartEditor({
|
|
|
4484
5999
|
if (label) {
|
|
4485
6000
|
const existing = model.edges.find((ex) => ex.from === liveEdge.fromId && ex.label === label);
|
|
4486
6001
|
if (existing) {
|
|
4487
|
-
updated = {
|
|
6002
|
+
updated = {
|
|
6003
|
+
...model,
|
|
6004
|
+
edges: model.edges.map((ex) => ex.id === existing.id ? { ...ex, to: targetId } : ex)
|
|
6005
|
+
};
|
|
4488
6006
|
} else {
|
|
4489
|
-
updated = {
|
|
6007
|
+
updated = {
|
|
6008
|
+
...model,
|
|
6009
|
+
edges: [
|
|
6010
|
+
...model.edges,
|
|
6011
|
+
{ id: nextId("e", model.edges), from: liveEdge.fromId, to: targetId, label }
|
|
6012
|
+
]
|
|
6013
|
+
};
|
|
4490
6014
|
}
|
|
4491
6015
|
} else {
|
|
4492
|
-
updated = {
|
|
6016
|
+
updated = {
|
|
6017
|
+
...model,
|
|
6018
|
+
edges: [
|
|
6019
|
+
...model.edges,
|
|
6020
|
+
{ id: nextId("e", model.edges), from: liveEdge.fromId, to: targetId }
|
|
6021
|
+
]
|
|
6022
|
+
};
|
|
4493
6023
|
}
|
|
4494
6024
|
applyAndPush(updated);
|
|
4495
6025
|
setLiveEdge(null);
|
|
@@ -4529,7 +6059,9 @@ function FlowchartEditor({
|
|
|
4529
6059
|
const wx = snap(x), wy = snap(y);
|
|
4530
6060
|
const updated = {
|
|
4531
6061
|
...model,
|
|
4532
|
-
edges: model.edges.map(
|
|
6062
|
+
edges: model.edges.map(
|
|
6063
|
+
(ed) => ed.id === waypointDrag ? { ...ed, waypoint: { x: wx, y: wy } } : ed
|
|
6064
|
+
)
|
|
4533
6065
|
};
|
|
4534
6066
|
applyModel(updated);
|
|
4535
6067
|
return;
|
|
@@ -4561,12 +6093,23 @@ function FlowchartEditor({
|
|
|
4561
6093
|
return { x: n.x ?? 0, y: n.y ?? 0, w: d.w, h: d.h };
|
|
4562
6094
|
});
|
|
4563
6095
|
const snapResult = findSiblingSnap({ x: dx, y: dy, w: dW, h: dH }, others);
|
|
4564
|
-
setAlignGuides(
|
|
4565
|
-
|
|
6096
|
+
setAlignGuides(
|
|
6097
|
+
snapResult.guideX || snapResult.guideY ? { x: snapResult.guideX, y: snapResult.guideY } : null
|
|
6098
|
+
);
|
|
6099
|
+
const updated = {
|
|
6100
|
+
...model,
|
|
6101
|
+
nodes: model.nodes.map(
|
|
6102
|
+
(n) => n.id === drag.nodeId ? { ...n, x: snapResult.x, y: snapResult.y } : n
|
|
6103
|
+
)
|
|
6104
|
+
};
|
|
4566
6105
|
applyModel(updated);
|
|
4567
6106
|
}
|
|
4568
6107
|
} else if (pan) {
|
|
4569
|
-
setTransform((tr) => ({
|
|
6108
|
+
setTransform((tr) => ({
|
|
6109
|
+
...tr,
|
|
6110
|
+
x: pan.tx + (e.clientX - pan.ox),
|
|
6111
|
+
y: pan.ty + (e.clientY - pan.oy)
|
|
6112
|
+
}));
|
|
4570
6113
|
} else if (boxSel) {
|
|
4571
6114
|
setBoxSel((b) => b ? { ...b, cx: e.clientX, cy: e.clientY } : null);
|
|
4572
6115
|
}
|
|
@@ -4592,7 +6135,7 @@ function FlowchartEditor({
|
|
|
4592
6135
|
}
|
|
4593
6136
|
const arr = Array.from(hits);
|
|
4594
6137
|
setSelectedSet(hits);
|
|
4595
|
-
setSelected(arr.length ? arr[arr.length - 1] : null);
|
|
6138
|
+
setSelected(arr.length ? arr[arr.length - 1] ?? null : null);
|
|
4596
6139
|
}
|
|
4597
6140
|
setBoxSel(null);
|
|
4598
6141
|
}
|
|
@@ -4615,7 +6158,10 @@ function FlowchartEditor({
|
|
|
4615
6158
|
};
|
|
4616
6159
|
const commitEdit = () => {
|
|
4617
6160
|
if (!editingId) return;
|
|
4618
|
-
const up = {
|
|
6161
|
+
const up = {
|
|
6162
|
+
...model,
|
|
6163
|
+
nodes: model.nodes.map((n) => n.id === editingId ? { ...n, label: editLabel } : n)
|
|
6164
|
+
};
|
|
4619
6165
|
applyAndPush(up);
|
|
4620
6166
|
setEditingId(null);
|
|
4621
6167
|
};
|
|
@@ -4624,20 +6170,28 @@ function FlowchartEditor({
|
|
|
4624
6170
|
const p = atCanvasPos ? { x: snap(atCanvasPos.x), y: snap(atCanvasPos.y) } : { x: snap(100 + Math.random() * 240), y: snap(100 + Math.random() * 180) };
|
|
4625
6171
|
const label = variant === "question" ? "New Question" : variant === "journey" ? `Step ${model.nodes.length + 1}` : "New Step";
|
|
4626
6172
|
const metadata = variant === "question" ? { answers: [] } : void 0;
|
|
4627
|
-
const updated = {
|
|
6173
|
+
const updated = {
|
|
6174
|
+
...model,
|
|
6175
|
+
nodes: [...model.nodes, { id, label, shape: "rectangle", metadata, ...p }]
|
|
6176
|
+
};
|
|
4628
6177
|
applyAndPush(updated);
|
|
4629
6178
|
selectOne(id);
|
|
4630
6179
|
setAnnouncement(`Added ${variantLabel.toLowerCase()} "${label}".`);
|
|
4631
6180
|
};
|
|
4632
6181
|
const deleteNode = (nodeId) => {
|
|
4633
6182
|
const node = model.nodes.find((n) => n.id === nodeId);
|
|
4634
|
-
const updated = {
|
|
6183
|
+
const updated = {
|
|
6184
|
+
...model,
|
|
6185
|
+
nodes: model.nodes.filter((n) => n.id !== nodeId),
|
|
6186
|
+
edges: model.edges.filter((e) => e.from !== nodeId && e.to !== nodeId)
|
|
6187
|
+
};
|
|
4635
6188
|
applyAndPush(updated);
|
|
4636
6189
|
if (selectedSet.has(nodeId)) {
|
|
4637
6190
|
const next = new Set(selectedSet);
|
|
4638
6191
|
next.delete(nodeId);
|
|
4639
6192
|
setSelectedSet(next);
|
|
4640
|
-
if (selected === nodeId)
|
|
6193
|
+
if (selected === nodeId)
|
|
6194
|
+
setSelected(next.size ? Array.from(next)[next.size - 1] ?? null : null);
|
|
4641
6195
|
}
|
|
4642
6196
|
if (node) setAnnouncement(`Deleted ${variantLabel.toLowerCase()} "${node.label}".`);
|
|
4643
6197
|
};
|
|
@@ -4669,7 +6223,9 @@ function FlowchartEditor({
|
|
|
4669
6223
|
const next = editEdgeLabel.trim();
|
|
4670
6224
|
const updated = {
|
|
4671
6225
|
...model,
|
|
4672
|
-
edges: model.edges.map(
|
|
6226
|
+
edges: model.edges.map(
|
|
6227
|
+
(e) => e.id === editingEdgeId ? { ...e, ...next ? { label: next } : { label: void 0 } } : e
|
|
6228
|
+
)
|
|
4673
6229
|
};
|
|
4674
6230
|
applyAndPush(updated);
|
|
4675
6231
|
setEditingEdgeId(null);
|
|
@@ -4680,11 +6236,17 @@ function FlowchartEditor({
|
|
|
4680
6236
|
setCtxMenu({ x: e.clientX, y: e.clientY, nodeId: null, edgeId });
|
|
4681
6237
|
};
|
|
4682
6238
|
const setEdgeStyle = (edgeId, style) => {
|
|
4683
|
-
const updated = {
|
|
6239
|
+
const updated = {
|
|
6240
|
+
...model,
|
|
6241
|
+
edges: model.edges.map((e) => e.id === edgeId ? { ...e, style } : e)
|
|
6242
|
+
};
|
|
4684
6243
|
applyAndPush(updated);
|
|
4685
6244
|
};
|
|
4686
6245
|
const setEdgeArrowhead = (edgeId, arrowhead) => {
|
|
4687
|
-
const updated = {
|
|
6246
|
+
const updated = {
|
|
6247
|
+
...model,
|
|
6248
|
+
edges: model.edges.map((e) => e.id === edgeId ? { ...e, arrowhead } : e)
|
|
6249
|
+
};
|
|
4688
6250
|
applyAndPush(updated);
|
|
4689
6251
|
};
|
|
4690
6252
|
const deleteEdge = (edgeId) => {
|
|
@@ -4704,14 +6266,17 @@ function FlowchartEditor({
|
|
|
4704
6266
|
applyAndPush(updated);
|
|
4705
6267
|
};
|
|
4706
6268
|
const handleExport = useExporters(model, onExport, "diagram", (msg) => showToast(msg, "success"));
|
|
4707
|
-
const positionFlowchartNodes = (0, import_react20.useCallback)(
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
6269
|
+
const positionFlowchartNodes = (0, import_react20.useCallback)(
|
|
6270
|
+
(m) => ({
|
|
6271
|
+
...m,
|
|
6272
|
+
nodes: m.nodes.map((n, i) => ({
|
|
6273
|
+
...n,
|
|
6274
|
+
x: n.x ?? snap(80 + i % 4 * 200),
|
|
6275
|
+
y: n.y ?? snap(80 + Math.floor(i / 4) * 140)
|
|
6276
|
+
}))
|
|
6277
|
+
}),
|
|
6278
|
+
[]
|
|
6279
|
+
);
|
|
4715
6280
|
const handleImport = useImporter(applyAndPush, {
|
|
4716
6281
|
transform: positionFlowchartNodes,
|
|
4717
6282
|
onSuccess: (msg) => showToast(msg, "success"),
|
|
@@ -4722,9 +6287,23 @@ function FlowchartEditor({
|
|
|
4722
6287
|
const shadowClr = shadowColor(isDark);
|
|
4723
6288
|
const arrowClr = arrowColor(isDark);
|
|
4724
6289
|
const amberArrow = isDark ? ACCENT.amberDark : ACCENT.amber;
|
|
4725
|
-
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
4726
|
-
|
|
4727
|
-
|
|
6290
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
6291
|
+
"div",
|
|
6292
|
+
{
|
|
6293
|
+
className: "fsd-editor",
|
|
6294
|
+
style: {
|
|
6295
|
+
display: "flex",
|
|
6296
|
+
flexDirection: "column",
|
|
6297
|
+
height,
|
|
6298
|
+
width: "100%",
|
|
6299
|
+
fontFamily: "ui-sans-serif,system-ui,sans-serif",
|
|
6300
|
+
boxSizing: "border-box",
|
|
6301
|
+
background: t.ctrlsBg,
|
|
6302
|
+
position: "relative"
|
|
6303
|
+
},
|
|
6304
|
+
children: [
|
|
6305
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ToastContainer, { toasts, onDismiss: dismissToast }),
|
|
6306
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("style", { children: `
|
|
4728
6307
|
.fsd-editor button:focus-visible,
|
|
4729
6308
|
.fsd-editor input:focus-visible,
|
|
4730
6309
|
.fsd-editor textarea:focus-visible,
|
|
@@ -4743,209 +6322,293 @@ function FlowchartEditor({
|
|
|
4743
6322
|
outline-offset: -2px;
|
|
4744
6323
|
}
|
|
4745
6324
|
` }),
|
|
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
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
onNodeDblClick,
|
|
4827
|
-
onNodeContextMenu,
|
|
4828
|
-
onPortMouseDown,
|
|
4829
|
-
onAnswerPortDown,
|
|
4830
|
-
onSvgMouseDown,
|
|
4831
|
-
onMouseMove,
|
|
4832
|
-
onMouseUp,
|
|
4833
|
-
onSvgContextMenu,
|
|
4834
|
-
reducedMotion,
|
|
4835
|
-
isCoarse,
|
|
4836
|
-
portR,
|
|
4837
|
-
shadowClr,
|
|
4838
|
-
arrowClr,
|
|
4839
|
-
amberArrow,
|
|
4840
|
-
viewport,
|
|
4841
|
-
svgRef,
|
|
4842
|
-
containerRef,
|
|
4843
|
-
ctxMenu,
|
|
4844
|
-
history,
|
|
4845
|
-
ctxEdgeStyle: (ctxMenu?.edgeId ? model.edges.find((e) => e.id === ctxMenu.edgeId) : void 0)?.style ?? "solid",
|
|
4846
|
-
ctxEdgeArrow: (ctxMenu?.edgeId ? model.edges.find((e) => e.id === ctxMenu.edgeId) : void 0)?.arrowhead ?? "arrow",
|
|
4847
|
-
ctxEdgeHasWaypoint: !!(ctxMenu?.edgeId ? model.edges.find((e) => e.id === ctxMenu.edgeId) : void 0)?.waypoint,
|
|
4848
|
-
onCtxUndo: () => {
|
|
4849
|
-
undo();
|
|
4850
|
-
setCtxMenu(null);
|
|
4851
|
-
},
|
|
4852
|
-
onCtxRedo: () => {
|
|
4853
|
-
redo();
|
|
4854
|
-
setCtxMenu(null);
|
|
4855
|
-
},
|
|
4856
|
-
onCtxReCenter: () => {
|
|
4857
|
-
reCenter();
|
|
4858
|
-
setCtxMenu(null);
|
|
4859
|
-
},
|
|
4860
|
-
onCtxAddNode: () => {
|
|
4861
|
-
const rect = svgRef.current.getBoundingClientRect();
|
|
4862
|
-
const cx = (ctxMenu.x - rect.left - transform.x) / transform.scale;
|
|
4863
|
-
const cy = (ctxMenu.y - rect.top - transform.y) / transform.scale;
|
|
4864
|
-
addNode({ x: cx, y: cy });
|
|
4865
|
-
setCtxMenu(null);
|
|
4866
|
-
},
|
|
4867
|
-
onCtxDuplicate: () => {
|
|
4868
|
-
if (ctxMenu?.nodeId) {
|
|
4869
|
-
duplicateNode(ctxMenu.nodeId);
|
|
4870
|
-
setCtxMenu(null);
|
|
4871
|
-
}
|
|
4872
|
-
},
|
|
4873
|
-
onCtxRename: () => {
|
|
4874
|
-
if (ctxMenu?.nodeId) {
|
|
4875
|
-
const node = model.nodes.find((n) => n.id === ctxMenu.nodeId);
|
|
4876
|
-
setEditingId(ctxMenu.nodeId);
|
|
4877
|
-
setEditLabel(node.label);
|
|
4878
|
-
setCtxMenu(null);
|
|
4879
|
-
}
|
|
4880
|
-
},
|
|
4881
|
-
onCtxDelete: () => {
|
|
4882
|
-
if (ctxMenu?.nodeId) {
|
|
4883
|
-
deleteNode(ctxMenu.nodeId);
|
|
4884
|
-
setCtxMenu(null);
|
|
4885
|
-
}
|
|
4886
|
-
},
|
|
4887
|
-
onCtxDisconnect: () => {
|
|
4888
|
-
if (ctxMenu?.nodeId) {
|
|
4889
|
-
const m = { ...model, edges: model.edges.filter((e) => e.from !== ctxMenu.nodeId && e.to !== ctxMenu.nodeId) };
|
|
4890
|
-
applyAndPush(m);
|
|
4891
|
-
setCtxMenu(null);
|
|
4892
|
-
}
|
|
4893
|
-
},
|
|
4894
|
-
onCtxEdgeRename: () => {
|
|
4895
|
-
if (ctxMenu?.edgeId) {
|
|
4896
|
-
beginEditEdge(ctxMenu.edgeId);
|
|
4897
|
-
setCtxMenu(null);
|
|
4898
|
-
}
|
|
4899
|
-
},
|
|
4900
|
-
onCtxEdgeStyle: (s2) => {
|
|
4901
|
-
if (ctxMenu?.edgeId) {
|
|
4902
|
-
setEdgeStyle(ctxMenu.edgeId, s2);
|
|
4903
|
-
setCtxMenu(null);
|
|
4904
|
-
}
|
|
4905
|
-
},
|
|
4906
|
-
onCtxEdgeArrowhead: (a) => {
|
|
4907
|
-
if (ctxMenu?.edgeId) {
|
|
4908
|
-
setEdgeArrowhead(ctxMenu.edgeId, a);
|
|
4909
|
-
setCtxMenu(null);
|
|
4910
|
-
}
|
|
4911
|
-
},
|
|
4912
|
-
onCtxEdgeDelete: () => {
|
|
4913
|
-
if (ctxMenu?.edgeId) {
|
|
4914
|
-
deleteEdge(ctxMenu.edgeId);
|
|
4915
|
-
setCtxMenu(null);
|
|
6325
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { role: "status", "aria-live": "polite", "aria-atomic": "true", style: STYLE_SR_ONLY, children: announcement }),
|
|
6326
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
6327
|
+
Toolbar,
|
|
6328
|
+
{
|
|
6329
|
+
onExport: handleExport,
|
|
6330
|
+
onImport: allowImport ? handleImport : void 0,
|
|
6331
|
+
allowedExports,
|
|
6332
|
+
allowImport
|
|
6333
|
+
}
|
|
6334
|
+
),
|
|
6335
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
6336
|
+
"div",
|
|
6337
|
+
{
|
|
6338
|
+
style: {
|
|
6339
|
+
display: "flex",
|
|
6340
|
+
gap: 6,
|
|
6341
|
+
padding: "7px 14px",
|
|
6342
|
+
background: t.ctrlsBg,
|
|
6343
|
+
borderBottom: `1px solid ${t.ctrlsBorder}`,
|
|
6344
|
+
alignItems: "center",
|
|
6345
|
+
flexWrap: "wrap"
|
|
6346
|
+
},
|
|
6347
|
+
children: [
|
|
6348
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("button", { onClick: () => addNode(), style: ctrlBtn(acc.color, isDark), children: [
|
|
6349
|
+
"+ ",
|
|
6350
|
+
variantLabel
|
|
6351
|
+
] }),
|
|
6352
|
+
selectedSet.size > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
|
|
6353
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: { width: 1, height: 20, background: t.ctrlsBorder, margin: "0 2px" } }),
|
|
6354
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
6355
|
+
"button",
|
|
6356
|
+
{
|
|
6357
|
+
onClick: deleteSelected,
|
|
6358
|
+
style: {
|
|
6359
|
+
...ctrlBtn("transparent", isDark),
|
|
6360
|
+
color: "#ef4444",
|
|
6361
|
+
border: `1px solid ${isDark ? "#7f1d1d" : "#fca5a5"}`
|
|
6362
|
+
},
|
|
6363
|
+
children: selectedSet.size > 1 ? `Delete (${selectedSet.size})` : "Delete"
|
|
6364
|
+
}
|
|
6365
|
+
)
|
|
6366
|
+
] }),
|
|
6367
|
+
liveEdge && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { style: { fontSize: 11, color: acc.color, fontWeight: 600, marginLeft: 6 }, children: [
|
|
6368
|
+
liveEdge.answerLabel ? `Routing "${liveEdge.answerLabel}" \u2192` : "Drop on a node to connect",
|
|
6369
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: { fontWeight: 400, color: t.textMuted, marginLeft: 6 }, children: "release to cancel" })
|
|
6370
|
+
] }),
|
|
6371
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { style: { marginLeft: "auto", fontSize: 11, color: t.textMuted }, children: [
|
|
6372
|
+
variant === "question" ? "drag answer port to connect \xB7 " : "drag port dot \xB7 ",
|
|
6373
|
+
"scroll to zoom \xB7 drag to pan"
|
|
6374
|
+
] })
|
|
6375
|
+
]
|
|
6376
|
+
}
|
|
6377
|
+
),
|
|
6378
|
+
variant !== "flowchart" && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
6379
|
+
"div",
|
|
6380
|
+
{
|
|
6381
|
+
style: {
|
|
6382
|
+
padding: "3px 14px",
|
|
6383
|
+
background: acc.fill,
|
|
6384
|
+
borderBottom: `1px solid ${acc.border}`,
|
|
6385
|
+
fontSize: 11,
|
|
6386
|
+
color: acc.color,
|
|
6387
|
+
fontWeight: 600
|
|
6388
|
+
},
|
|
6389
|
+
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"
|
|
6390
|
+
}
|
|
6391
|
+
),
|
|
6392
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: STYLE_FLEX_ROW, children: [
|
|
6393
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
6394
|
+
NodeNavigator,
|
|
6395
|
+
{
|
|
6396
|
+
model,
|
|
6397
|
+
selected,
|
|
6398
|
+
variant,
|
|
6399
|
+
isDark,
|
|
6400
|
+
t,
|
|
6401
|
+
acc,
|
|
6402
|
+
open: navOpen,
|
|
6403
|
+
onToggle: () => setNavOpen((v) => !v),
|
|
6404
|
+
onSelect: jumpToNode
|
|
4916
6405
|
}
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
6406
|
+
),
|
|
6407
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
6408
|
+
DiagramCanvas,
|
|
6409
|
+
{
|
|
6410
|
+
model,
|
|
6411
|
+
variant,
|
|
6412
|
+
variantLabel,
|
|
6413
|
+
t,
|
|
6414
|
+
isDark,
|
|
6415
|
+
acc,
|
|
6416
|
+
transform,
|
|
6417
|
+
setTransform,
|
|
6418
|
+
selected,
|
|
6419
|
+
selectedSet,
|
|
6420
|
+
hoveredId,
|
|
6421
|
+
setHoveredId,
|
|
6422
|
+
drag,
|
|
6423
|
+
pan,
|
|
6424
|
+
liveEdge,
|
|
6425
|
+
boxSel,
|
|
6426
|
+
alignGuides,
|
|
6427
|
+
editingEdgeId,
|
|
6428
|
+
editEdgeLabel,
|
|
6429
|
+
setEditEdgeLabel,
|
|
6430
|
+
commitEdgeEdit,
|
|
6431
|
+
setEditingEdgeId,
|
|
6432
|
+
beginEditEdge,
|
|
6433
|
+
onEdgeContextMenu,
|
|
6434
|
+
setWaypointDrag,
|
|
6435
|
+
editingId,
|
|
6436
|
+
editLabel,
|
|
6437
|
+
setEditLabel,
|
|
6438
|
+
commitEdit,
|
|
6439
|
+
setEditingId,
|
|
6440
|
+
onNodeMouseDown,
|
|
6441
|
+
onNodeMouseUp,
|
|
6442
|
+
onNodeDblClick,
|
|
6443
|
+
onNodeContextMenu,
|
|
6444
|
+
onPortMouseDown,
|
|
6445
|
+
onAnswerPortDown,
|
|
6446
|
+
onSvgMouseDown,
|
|
6447
|
+
onMouseMove,
|
|
6448
|
+
onMouseUp,
|
|
6449
|
+
onSvgContextMenu,
|
|
6450
|
+
reducedMotion,
|
|
6451
|
+
isCoarse,
|
|
6452
|
+
portR,
|
|
6453
|
+
shadowClr,
|
|
6454
|
+
arrowClr,
|
|
6455
|
+
amberArrow,
|
|
6456
|
+
viewport,
|
|
6457
|
+
svgRef,
|
|
6458
|
+
containerRef,
|
|
6459
|
+
ctxMenu,
|
|
6460
|
+
history,
|
|
6461
|
+
ctxEdgeStyle: (ctxMenu?.edgeId ? model.edges.find((e) => e.id === ctxMenu.edgeId) : void 0)?.style ?? "solid",
|
|
6462
|
+
ctxEdgeArrow: (ctxMenu?.edgeId ? model.edges.find((e) => e.id === ctxMenu.edgeId) : void 0)?.arrowhead ?? "arrow",
|
|
6463
|
+
ctxEdgeHasWaypoint: !!(ctxMenu?.edgeId ? model.edges.find((e) => e.id === ctxMenu.edgeId) : void 0)?.waypoint,
|
|
6464
|
+
onCtxUndo: () => {
|
|
6465
|
+
undo();
|
|
6466
|
+
setCtxMenu(null);
|
|
6467
|
+
},
|
|
6468
|
+
onCtxRedo: () => {
|
|
6469
|
+
redo();
|
|
6470
|
+
setCtxMenu(null);
|
|
6471
|
+
},
|
|
6472
|
+
onCtxReCenter: () => {
|
|
6473
|
+
reCenter();
|
|
6474
|
+
setCtxMenu(null);
|
|
6475
|
+
},
|
|
6476
|
+
onCtxAddNode: () => {
|
|
6477
|
+
const rect = svgRef.current.getBoundingClientRect();
|
|
6478
|
+
const cx = (ctxMenu.x - rect.left - transform.x) / transform.scale;
|
|
6479
|
+
const cy = (ctxMenu.y - rect.top - transform.y) / transform.scale;
|
|
6480
|
+
addNode({ x: cx, y: cy });
|
|
6481
|
+
setCtxMenu(null);
|
|
6482
|
+
},
|
|
6483
|
+
onCtxDuplicate: () => {
|
|
6484
|
+
if (ctxMenu?.nodeId) {
|
|
6485
|
+
duplicateNode(ctxMenu.nodeId);
|
|
6486
|
+
setCtxMenu(null);
|
|
6487
|
+
}
|
|
6488
|
+
},
|
|
6489
|
+
onCtxRename: () => {
|
|
6490
|
+
if (ctxMenu?.nodeId) {
|
|
6491
|
+
const node = model.nodes.find((n) => n.id === ctxMenu.nodeId);
|
|
6492
|
+
setEditingId(ctxMenu.nodeId);
|
|
6493
|
+
setEditLabel(node.label);
|
|
6494
|
+
setCtxMenu(null);
|
|
6495
|
+
}
|
|
6496
|
+
},
|
|
6497
|
+
onCtxDelete: () => {
|
|
6498
|
+
if (ctxMenu?.nodeId) {
|
|
6499
|
+
deleteNode(ctxMenu.nodeId);
|
|
6500
|
+
setCtxMenu(null);
|
|
6501
|
+
}
|
|
6502
|
+
},
|
|
6503
|
+
onCtxDisconnect: () => {
|
|
6504
|
+
if (ctxMenu?.nodeId) {
|
|
6505
|
+
const m = {
|
|
6506
|
+
...model,
|
|
6507
|
+
edges: model.edges.filter(
|
|
6508
|
+
(e) => e.from !== ctxMenu.nodeId && e.to !== ctxMenu.nodeId
|
|
6509
|
+
)
|
|
6510
|
+
};
|
|
6511
|
+
applyAndPush(m);
|
|
6512
|
+
setCtxMenu(null);
|
|
6513
|
+
}
|
|
6514
|
+
},
|
|
6515
|
+
onCtxEdgeRename: () => {
|
|
6516
|
+
if (ctxMenu?.edgeId) {
|
|
6517
|
+
beginEditEdge(ctxMenu.edgeId);
|
|
6518
|
+
setCtxMenu(null);
|
|
6519
|
+
}
|
|
6520
|
+
},
|
|
6521
|
+
onCtxEdgeStyle: (s2) => {
|
|
6522
|
+
if (ctxMenu?.edgeId) {
|
|
6523
|
+
setEdgeStyle(ctxMenu.edgeId, s2);
|
|
6524
|
+
setCtxMenu(null);
|
|
6525
|
+
}
|
|
6526
|
+
},
|
|
6527
|
+
onCtxEdgeArrowhead: (a) => {
|
|
6528
|
+
if (ctxMenu?.edgeId) {
|
|
6529
|
+
setEdgeArrowhead(ctxMenu.edgeId, a);
|
|
6530
|
+
setCtxMenu(null);
|
|
6531
|
+
}
|
|
6532
|
+
},
|
|
6533
|
+
onCtxEdgeDelete: () => {
|
|
6534
|
+
if (ctxMenu?.edgeId) {
|
|
6535
|
+
deleteEdge(ctxMenu.edgeId);
|
|
6536
|
+
setCtxMenu(null);
|
|
6537
|
+
}
|
|
6538
|
+
},
|
|
6539
|
+
onCtxEdgeResetRouting: () => {
|
|
6540
|
+
if (ctxMenu?.edgeId) {
|
|
6541
|
+
resetEdgeRouting(ctxMenu.edgeId);
|
|
6542
|
+
setCtxMenu(null);
|
|
6543
|
+
}
|
|
6544
|
+
}
|
|
4922
6545
|
}
|
|
6546
|
+
),
|
|
6547
|
+
selected && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
6548
|
+
StepEditor,
|
|
6549
|
+
{
|
|
6550
|
+
nodeId: selected,
|
|
6551
|
+
model,
|
|
6552
|
+
onModelChange: (m) => {
|
|
6553
|
+
applyAndPush(m);
|
|
6554
|
+
},
|
|
6555
|
+
variant,
|
|
6556
|
+
isDark,
|
|
6557
|
+
t,
|
|
6558
|
+
acc
|
|
6559
|
+
},
|
|
6560
|
+
selected
|
|
6561
|
+
)
|
|
6562
|
+
] }),
|
|
6563
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
6564
|
+
"div",
|
|
6565
|
+
{
|
|
6566
|
+
style: {
|
|
6567
|
+
padding: "4px 14px",
|
|
6568
|
+
fontSize: 11,
|
|
6569
|
+
color: t.textMuted,
|
|
6570
|
+
background: t.statusBg,
|
|
6571
|
+
borderTop: `1px solid ${t.ctrlsBorder}`,
|
|
6572
|
+
display: "flex",
|
|
6573
|
+
gap: 16,
|
|
6574
|
+
flexWrap: "wrap",
|
|
6575
|
+
overflow: "hidden",
|
|
6576
|
+
maxHeight: 28
|
|
6577
|
+
},
|
|
6578
|
+
children: [
|
|
6579
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { children: [
|
|
6580
|
+
model.nodes.length,
|
|
6581
|
+
" ",
|
|
6582
|
+
variantLabel.toLowerCase(),
|
|
6583
|
+
"s"
|
|
6584
|
+
] }),
|
|
6585
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { children: [
|
|
6586
|
+
model.edges.length,
|
|
6587
|
+
" connections"
|
|
6588
|
+
] }),
|
|
6589
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { children: [
|
|
6590
|
+
Math.round(transform.scale * 100),
|
|
6591
|
+
"% zoom"
|
|
6592
|
+
] }),
|
|
6593
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
6594
|
+
"span",
|
|
6595
|
+
{
|
|
6596
|
+
style: {
|
|
6597
|
+
marginLeft: "auto",
|
|
6598
|
+
whiteSpace: "nowrap",
|
|
6599
|
+
overflow: "hidden",
|
|
6600
|
+
textOverflow: "ellipsis"
|
|
6601
|
+
},
|
|
6602
|
+
children: "Ctrl+Z undo \xB7 Ctrl+Y redo \xB7 Ctrl+0 fit \xB7 Alt+Arrow traverse"
|
|
6603
|
+
}
|
|
6604
|
+
),
|
|
6605
|
+
selected && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: { color: acc.color }, children: model.nodes.find((n) => n.id === selected)?.label })
|
|
6606
|
+
]
|
|
4923
6607
|
}
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
}, variant, isDark, t, acc }, selected)
|
|
4929
|
-
] }),
|
|
4930
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("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: [
|
|
4931
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { children: [
|
|
4932
|
-
model.nodes.length,
|
|
4933
|
-
" ",
|
|
4934
|
-
variantLabel.toLowerCase(),
|
|
4935
|
-
"s"
|
|
4936
|
-
] }),
|
|
4937
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { children: [
|
|
4938
|
-
model.edges.length,
|
|
4939
|
-
" connections"
|
|
4940
|
-
] }),
|
|
4941
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { children: [
|
|
4942
|
-
Math.round(transform.scale * 100),
|
|
4943
|
-
"% zoom"
|
|
4944
|
-
] }),
|
|
4945
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("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" }),
|
|
4946
|
-
selected && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: { color: acc.color }, children: model.nodes.find((n) => n.id === selected)?.label })
|
|
4947
|
-
] })
|
|
4948
|
-
] });
|
|
6608
|
+
)
|
|
6609
|
+
]
|
|
6610
|
+
}
|
|
6611
|
+
);
|
|
4949
6612
|
}
|
|
4950
6613
|
function ctrlBtn(accent, isDark) {
|
|
4951
6614
|
const isTransparent = accent === "transparent";
|