@particle-academy/fancy-flow 0.5.4 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -0
- package/dist/engine.d.cts +2 -2
- package/dist/engine.d.ts +2 -2
- package/dist/index.cjs +283 -127
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +136 -13
- package/dist/index.d.ts +136 -13
- package/dist/index.js +288 -134
- package/dist/index.js.map +1 -1
- package/dist/registry/index.d.cts +3 -3
- package/dist/registry/index.d.ts +3 -3
- package/dist/runtime/index.d.cts +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/schema/index.d.cts +1 -1
- package/dist/schema/index.d.ts +1 -1
- package/dist/styles.css +68 -1
- package/dist/styles.css.map +1 -1
- package/dist/{types-TemTtb04.d.cts → types-BS3Gwnkq.d.cts} +1 -1
- package/dist/{types-TemTtb04.d.ts → types-BS3Gwnkq.d.ts} +1 -1
- package/dist/{types-D5RERHIP.d.cts → types-C0wdN6QX.d.cts} +1 -1
- package/dist/{types-BodwZiST.d.ts → types-DnMe9Vsf.d.ts} +1 -1
- package/dist/ux.d.cts +2 -2
- package/dist/ux.d.ts +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { useFlowState, useFlowRun, applyStatusesToNodes } from './chunk-QSSQRQN4.js';
|
|
2
2
|
export { applyStatusesToNodes, useFlowRun, useFlowState } from './chunk-QSSQRQN4.js';
|
|
3
3
|
export { runFlow } from './chunk-NVULCEDX.js';
|
|
4
|
-
import {
|
|
4
|
+
import { buildNodeTypes, registerBuiltinKinds } from './chunk-M2XKGQQL.js';
|
|
5
5
|
export { BUILTIN_KINDS, RegistryNode, buildNodeTypes, registerBuiltinKinds } from './chunk-M2XKGQQL.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { ReactFlowProvider, useReactFlow, addEdge, applyEdgeChanges, applyNodeChanges, Position, Handle, index, BackgroundVariant, Background, Controls, MiniMap } from './chunk-NF6NPY5N.js';
|
|
7
|
+
import { workflowToBlob, importWorkflow, exportWorkflow } from './chunk-BCXECQUC.js';
|
|
8
8
|
export { WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, exportWorkflow, importWorkflow, workflowToBlob } from './chunk-BCXECQUC.js';
|
|
9
|
-
import { onNodeKindsChanged, listNodeKinds,
|
|
9
|
+
import { onNodeKindsChanged, listNodeKinds, getNodeKind, defaultConfigFor, validateConfig, categoryAccent } from './chunk-WNVBXXOL.js';
|
|
10
10
|
export { categoryAccent, defaultConfigFor, getNodeKind, listNodeKinds, onNodeKindsChanged, registerNodeKind, validateConfig } from './chunk-WNVBXXOL.js';
|
|
11
|
-
import { memo,
|
|
12
|
-
import {
|
|
11
|
+
import { memo, createContext, forwardRef, useState, useEffect, useMemo, useCallback, useImperativeHandle, useRef, useContext } from 'react';
|
|
12
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
13
13
|
|
|
14
14
|
function NodeShellInner({
|
|
15
15
|
node,
|
|
@@ -508,16 +508,52 @@ function formatTime(at) {
|
|
|
508
508
|
const d = new Date(at);
|
|
509
509
|
return `${d.getMinutes().toString().padStart(2, "0")}:${d.getSeconds().toString().padStart(2, "0")}.${Math.floor(d.getMilliseconds() / 100)}`;
|
|
510
510
|
}
|
|
511
|
-
|
|
511
|
+
|
|
512
|
+
// src/components/FlowEditor/graph-ops.ts
|
|
513
|
+
function removeNodes(graph, ids) {
|
|
514
|
+
if (ids.length === 0) return graph;
|
|
515
|
+
const doomed = new Set(ids);
|
|
516
|
+
return {
|
|
517
|
+
nodes: graph.nodes.filter((n) => !doomed.has(n.id)),
|
|
518
|
+
edges: graph.edges.filter((e) => !doomed.has(e.source) && !doomed.has(e.target))
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
function removeEdges(edges, ids) {
|
|
522
|
+
if (ids.length === 0) return edges;
|
|
523
|
+
const doomed = new Set(ids);
|
|
524
|
+
return edges.filter((e) => !doomed.has(e.id));
|
|
525
|
+
}
|
|
526
|
+
function duplicateNode(node, id, offset = 40) {
|
|
527
|
+
return {
|
|
528
|
+
...node,
|
|
529
|
+
id,
|
|
530
|
+
position: { x: node.position.x + offset, y: node.position.y + offset },
|
|
531
|
+
// Deep copy so the clone's config edits don't mutate the original.
|
|
532
|
+
data: JSON.parse(JSON.stringify(node.data ?? {}))
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
var FlowEditorContext = createContext(null);
|
|
536
|
+
var FlowEditorProvider = FlowEditorContext.Provider;
|
|
537
|
+
function useFlowEditor() {
|
|
538
|
+
const api = useContext(FlowEditorContext);
|
|
539
|
+
if (api === null) {
|
|
540
|
+
throw new Error("useFlowEditor() must be called inside <FlowEditor>.");
|
|
541
|
+
}
|
|
542
|
+
return api;
|
|
543
|
+
}
|
|
544
|
+
function useFlowEditorOptional() {
|
|
545
|
+
return useContext(FlowEditorContext);
|
|
546
|
+
}
|
|
547
|
+
var FlowEditor = forwardRef(function FlowEditor2(props, ref) {
|
|
512
548
|
return /* @__PURE__ */ jsx(ReactFlowProvider, { children: /* @__PURE__ */ jsx(
|
|
513
549
|
"div",
|
|
514
550
|
{
|
|
515
551
|
className: ["ff-editor", props.className ?? ""].filter(Boolean).join(" "),
|
|
516
552
|
style: { height: props.height ?? 720, ...props.style },
|
|
517
|
-
children: /* @__PURE__ */ jsx(FlowEditorInner, { ...props })
|
|
553
|
+
children: /* @__PURE__ */ jsx(FlowEditorInner, { ...props, apiRef: ref })
|
|
518
554
|
}
|
|
519
555
|
) });
|
|
520
|
-
}
|
|
556
|
+
});
|
|
521
557
|
function FlowEditorInner({
|
|
522
558
|
initial = { nodes: [], edges: [] },
|
|
523
559
|
value,
|
|
@@ -527,10 +563,18 @@ function FlowEditorInner({
|
|
|
527
563
|
showPanel = true,
|
|
528
564
|
showFeed = true,
|
|
529
565
|
extraToolbar,
|
|
530
|
-
|
|
566
|
+
actions = [],
|
|
567
|
+
builtins = {},
|
|
568
|
+
slots = {},
|
|
569
|
+
canvasProps = {},
|
|
570
|
+
onChange,
|
|
571
|
+
onSelectionChange,
|
|
572
|
+
onDelete,
|
|
573
|
+
apiRef
|
|
531
574
|
}) {
|
|
532
575
|
const internal = useFlowState(initial);
|
|
533
576
|
const runner = useFlowRun();
|
|
577
|
+
const rf = useReactFlow();
|
|
534
578
|
const controlled = value !== void 0;
|
|
535
579
|
const flow = controlled ? makeControlledFlowAdapter(value, onChange) : internal;
|
|
536
580
|
const [, force] = useState(0);
|
|
@@ -542,29 +586,247 @@ function FlowEditorInner({
|
|
|
542
586
|
);
|
|
543
587
|
const [selectedId, setSelectedId] = useState(null);
|
|
544
588
|
const selected = useMemo(() => flow.nodes.find((n) => n.id === selectedId) ?? null, [flow.nodes, selectedId]);
|
|
589
|
+
useEffect(() => onSelectionChange?.(selected), [selected, onSelectionChange]);
|
|
545
590
|
const handleNodeClick = (_e, node) => setSelectedId(node.id);
|
|
591
|
+
const [menu, setMenu] = useState(null);
|
|
592
|
+
const closeMenu = useCallback(() => setMenu(null), []);
|
|
593
|
+
const handleNodeContextMenu = (event, node) => {
|
|
594
|
+
event.preventDefault();
|
|
595
|
+
setSelectedId(node.id);
|
|
596
|
+
setMenu({ x: event.clientX, y: event.clientY, nodeId: node.id });
|
|
597
|
+
};
|
|
598
|
+
useEffect(() => {
|
|
599
|
+
if (menu === null) return;
|
|
600
|
+
const close = () => setMenu(null);
|
|
601
|
+
const onKey = (e) => e.key === "Escape" && setMenu(null);
|
|
602
|
+
window.addEventListener("click", close);
|
|
603
|
+
window.addEventListener("scroll", close, true);
|
|
604
|
+
window.addEventListener("keydown", onKey);
|
|
605
|
+
return () => {
|
|
606
|
+
window.removeEventListener("click", close);
|
|
607
|
+
window.removeEventListener("scroll", close, true);
|
|
608
|
+
window.removeEventListener("keydown", onKey);
|
|
609
|
+
};
|
|
610
|
+
}, [menu]);
|
|
546
611
|
useEffect(() => {
|
|
547
612
|
if (!controlled) onChange?.({ nodes: flow.nodes, edges: flow.edges });
|
|
548
613
|
}, [flow.nodes, flow.edges, onChange, controlled]);
|
|
614
|
+
const addNode = useCallback(
|
|
615
|
+
(kindName, position) => {
|
|
616
|
+
const kind = getNodeKind(kindName);
|
|
617
|
+
if (!kind) return null;
|
|
618
|
+
const at = position ?? { x: 80, y: 80 };
|
|
619
|
+
const id = newNodeId();
|
|
620
|
+
flow.setNodes((all) => [
|
|
621
|
+
...all,
|
|
622
|
+
{
|
|
623
|
+
id,
|
|
624
|
+
type: kind.name,
|
|
625
|
+
position: at,
|
|
626
|
+
data: { kind: kind.name, label: kind.label, config: defaultConfigFor(kind) }
|
|
627
|
+
}
|
|
628
|
+
]);
|
|
629
|
+
setSelectedId(id);
|
|
630
|
+
return id;
|
|
631
|
+
},
|
|
632
|
+
[flow]
|
|
633
|
+
);
|
|
634
|
+
const deleteNodes = useCallback(
|
|
635
|
+
(ids) => {
|
|
636
|
+
if (ids.length === 0) return;
|
|
637
|
+
const doomed = new Set(ids);
|
|
638
|
+
flow.setNodes((all) => removeNodes({ nodes: all, edges: [] }, ids).nodes);
|
|
639
|
+
flow.setEdges((all) => removeNodes({ nodes: [], edges: all }, ids).edges);
|
|
640
|
+
setSelectedId((cur) => cur !== null && doomed.has(cur) ? null : cur);
|
|
641
|
+
onDelete?.(ids);
|
|
642
|
+
},
|
|
643
|
+
[flow, onDelete]
|
|
644
|
+
);
|
|
645
|
+
const api = useMemo(() => {
|
|
646
|
+
const toWorkflow = () => exportWorkflow({ nodes: flow.nodes, edges: flow.edges }, metadata);
|
|
647
|
+
return {
|
|
648
|
+
graph: { nodes: flow.nodes, edges: flow.edges },
|
|
649
|
+
nodes: flow.nodes,
|
|
650
|
+
edges: flow.edges,
|
|
651
|
+
selectedId,
|
|
652
|
+
selected,
|
|
653
|
+
running: runner.running,
|
|
654
|
+
statuses: runner.statuses,
|
|
655
|
+
select: setSelectedId,
|
|
656
|
+
addNode,
|
|
657
|
+
updateNode: (next) => flow.setNodes((all) => all.map((x) => x.id === next.id ? next : x)),
|
|
658
|
+
deleteNodes,
|
|
659
|
+
deleteSelected: () => deleteNodes(selectedId ? [selectedId] : []),
|
|
660
|
+
deleteEdges: (ids) => flow.setEdges((all) => removeEdges(all, ids)),
|
|
661
|
+
duplicateNode: (id) => {
|
|
662
|
+
const src = flow.nodes.find((n) => n.id === id);
|
|
663
|
+
if (!src) return null;
|
|
664
|
+
const copy = duplicateNode(src, newNodeId());
|
|
665
|
+
flow.setNodes((all) => [...all, copy]);
|
|
666
|
+
setSelectedId(copy.id);
|
|
667
|
+
return copy.id;
|
|
668
|
+
},
|
|
669
|
+
setGraph: (graph) => {
|
|
670
|
+
flow.setNodes(graph.nodes);
|
|
671
|
+
flow.setEdges(graph.edges);
|
|
672
|
+
},
|
|
673
|
+
run: () => runner.run({ nodes: flow.nodes, edges: flow.edges }, executors),
|
|
674
|
+
cancel: runner.cancel,
|
|
675
|
+
reset: runner.reset,
|
|
676
|
+
toWorkflow,
|
|
677
|
+
exportWorkflow: () => downloadWorkflow(toWorkflow(), metadata),
|
|
678
|
+
importWorkflow: () => pickWorkflow((graph) => {
|
|
679
|
+
flow.setNodes(graph.nodes);
|
|
680
|
+
flow.setEdges(graph.edges);
|
|
681
|
+
}),
|
|
682
|
+
fitView: () => rf.fitView({ padding: 0.2 })
|
|
683
|
+
};
|
|
684
|
+
}, [flow, selectedId, selected, runner, executors, metadata, addNode, deleteNodes, rf]);
|
|
685
|
+
useImperativeHandle(apiRef, () => api, [api]);
|
|
686
|
+
const dropHandlers = paletteDropHandlers((kindName, evt) => {
|
|
687
|
+
const point = rf.screenToFlowPosition({ x: evt.clientX, y: evt.clientY });
|
|
688
|
+
addNode(kindName, { x: point.x - 100, y: point.y - 30 });
|
|
689
|
+
});
|
|
690
|
+
const startActions = actions.filter((a) => a.placement === "start");
|
|
691
|
+
const endActions = actions.filter((a) => a.placement !== "start");
|
|
692
|
+
const toolbar = slots.toolbar ? slots.toolbar(api) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
693
|
+
startActions.map((a) => renderAction(a, api)),
|
|
694
|
+
builtins.run !== false && /* @__PURE__ */ jsx(FlowRunControls, { running: api.running, onRun: api.run, onCancel: api.cancel, onReset: api.reset }),
|
|
695
|
+
(builtins.delete !== false || builtins.export !== false || builtins.import !== false) && /* @__PURE__ */ jsx("span", { className: "ff-editor__sep" }),
|
|
696
|
+
builtins.delete !== false && /* @__PURE__ */ jsx(
|
|
697
|
+
"button",
|
|
698
|
+
{
|
|
699
|
+
className: "ff-editor__btn",
|
|
700
|
+
"data-action": "delete",
|
|
701
|
+
onClick: api.deleteSelected,
|
|
702
|
+
disabled: api.selected === null,
|
|
703
|
+
title: "Delete the selected node (Del / Backspace)",
|
|
704
|
+
children: "\u2715 Delete"
|
|
705
|
+
}
|
|
706
|
+
),
|
|
707
|
+
builtins.export !== false && /* @__PURE__ */ jsx("button", { className: "ff-editor__btn", "data-action": "export", onClick: api.exportWorkflow, children: "\u2193 Export" }),
|
|
708
|
+
builtins.import !== false && /* @__PURE__ */ jsx("button", { className: "ff-editor__btn", "data-action": "import", onClick: api.importWorkflow, children: "\u2191 Import" }),
|
|
709
|
+
endActions.map((a) => renderAction(a, api)),
|
|
710
|
+
extraToolbar,
|
|
711
|
+
builtins.count !== false && /* @__PURE__ */ jsxs("span", { className: "ff-editor__count", children: [
|
|
712
|
+
api.nodes.length,
|
|
713
|
+
" nodes \xB7 ",
|
|
714
|
+
api.edges.length,
|
|
715
|
+
" edges"
|
|
716
|
+
] })
|
|
717
|
+
] });
|
|
718
|
+
return /* @__PURE__ */ jsxs(FlowEditorProvider, { value: api, children: [
|
|
719
|
+
showPalette && (slots.palette ? slots.palette(api) : /* @__PURE__ */ jsx(NodePalette, { className: "ff-editor__palette" })),
|
|
720
|
+
/* @__PURE__ */ jsxs("div", { className: "ff-editor__main", ...dropHandlers, children: [
|
|
721
|
+
/* @__PURE__ */ jsx(
|
|
722
|
+
FlowCanvas,
|
|
723
|
+
{
|
|
724
|
+
nodes: renderedNodes,
|
|
725
|
+
edges: flow.edges,
|
|
726
|
+
nodeTypes,
|
|
727
|
+
onNodesChange: flow.onNodesChange,
|
|
728
|
+
onEdgesChange: flow.onEdgesChange,
|
|
729
|
+
onConnect: flow.onConnect,
|
|
730
|
+
onNodeClick: handleNodeClick,
|
|
731
|
+
onNodeContextMenu: builtins.contextMenu === false ? void 0 : handleNodeContextMenu,
|
|
732
|
+
onNodesDelete: (deleted) => onDelete?.(deleted.map((n) => n.id)),
|
|
733
|
+
deleteKeyCode: ["Delete", "Backspace"],
|
|
734
|
+
height: "100%",
|
|
735
|
+
toolbar,
|
|
736
|
+
...canvasProps
|
|
737
|
+
}
|
|
738
|
+
),
|
|
739
|
+
slots.empty && api.nodes.length === 0 && /* @__PURE__ */ jsx("div", { className: "ff-editor__empty", children: slots.empty(api) }),
|
|
740
|
+
menu !== null && builtins.contextMenu !== false && /* @__PURE__ */ jsx(
|
|
741
|
+
"div",
|
|
742
|
+
{
|
|
743
|
+
className: "ff-editor__ctx",
|
|
744
|
+
style: { top: menu.y, left: menu.x },
|
|
745
|
+
role: "menu",
|
|
746
|
+
onClick: (e) => e.stopPropagation(),
|
|
747
|
+
children: slots.contextMenu ? slots.contextMenu(api, menu.nodeId, closeMenu) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
748
|
+
/* @__PURE__ */ jsx(
|
|
749
|
+
"button",
|
|
750
|
+
{
|
|
751
|
+
type: "button",
|
|
752
|
+
role: "menuitem",
|
|
753
|
+
className: "ff-editor__ctx-item",
|
|
754
|
+
"data-action": "ctx-duplicate",
|
|
755
|
+
onClick: () => {
|
|
756
|
+
api.duplicateNode(menu.nodeId);
|
|
757
|
+
closeMenu();
|
|
758
|
+
},
|
|
759
|
+
children: "Duplicate"
|
|
760
|
+
}
|
|
761
|
+
),
|
|
762
|
+
/* @__PURE__ */ jsx(
|
|
763
|
+
"button",
|
|
764
|
+
{
|
|
765
|
+
type: "button",
|
|
766
|
+
role: "menuitem",
|
|
767
|
+
className: "ff-editor__ctx-item ff-editor__ctx-item--danger",
|
|
768
|
+
"data-action": "ctx-delete",
|
|
769
|
+
onClick: () => {
|
|
770
|
+
api.deleteNodes([menu.nodeId]);
|
|
771
|
+
closeMenu();
|
|
772
|
+
},
|
|
773
|
+
children: "Delete"
|
|
774
|
+
}
|
|
775
|
+
)
|
|
776
|
+
] })
|
|
777
|
+
}
|
|
778
|
+
),
|
|
779
|
+
showFeed && (slots.feed ? slots.feed(api) : /* @__PURE__ */ jsx(FlowRunFeed, { entries: runner.feed, className: "ff-editor__feed" }))
|
|
780
|
+
] }),
|
|
781
|
+
showPanel && (slots.panel ? slots.panel(api) : /* @__PURE__ */ jsxs("div", { className: "ff-editor__panel-wrap", children: [
|
|
782
|
+
/* @__PURE__ */ jsx(NodeConfigPanel, { className: "ff-editor__panel", node: api.selected, onChange: api.updateNode }),
|
|
783
|
+
slots.panelFooter && /* @__PURE__ */ jsx("div", { className: "ff-editor__panel-footer", children: slots.panelFooter(api) })
|
|
784
|
+
] }))
|
|
785
|
+
] });
|
|
786
|
+
}
|
|
787
|
+
function renderAction(action, api) {
|
|
788
|
+
if (action.visible && !action.visible(api)) return null;
|
|
789
|
+
const disabled = action.disabled ? action.disabled(api) : action.requiresSelection === true && api.selected === null;
|
|
549
790
|
return /* @__PURE__ */ jsx(
|
|
550
|
-
|
|
791
|
+
"button",
|
|
551
792
|
{
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
renderedNodes,
|
|
561
|
-
selected,
|
|
562
|
-
setSelectedId,
|
|
563
|
-
handleNodeClick,
|
|
564
|
-
extraToolbar
|
|
565
|
-
}
|
|
793
|
+
className: "ff-editor__btn",
|
|
794
|
+
"data-action": action.id,
|
|
795
|
+
title: action.title,
|
|
796
|
+
disabled,
|
|
797
|
+
onClick: () => action.onSelect(api),
|
|
798
|
+
children: action.label
|
|
799
|
+
},
|
|
800
|
+
action.id
|
|
566
801
|
);
|
|
567
802
|
}
|
|
803
|
+
function newNodeId() {
|
|
804
|
+
return `n_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`;
|
|
805
|
+
}
|
|
806
|
+
function downloadWorkflow(schema, metadata) {
|
|
807
|
+
const url = URL.createObjectURL(workflowToBlob(schema));
|
|
808
|
+
const a = document.createElement("a");
|
|
809
|
+
a.href = url;
|
|
810
|
+
a.download = `${metadata?.id ?? "workflow"}.json`;
|
|
811
|
+
a.click();
|
|
812
|
+
URL.revokeObjectURL(url);
|
|
813
|
+
}
|
|
814
|
+
function pickWorkflow(onLoad) {
|
|
815
|
+
const input = document.createElement("input");
|
|
816
|
+
input.type = "file";
|
|
817
|
+
input.accept = "application/json";
|
|
818
|
+
input.onchange = async () => {
|
|
819
|
+
const file = input.files?.[0];
|
|
820
|
+
if (!file) return;
|
|
821
|
+
try {
|
|
822
|
+
const result = importWorkflow(JSON.parse(await file.text()), { lenient: true });
|
|
823
|
+
onLoad(result.graph);
|
|
824
|
+
} catch (e) {
|
|
825
|
+
console.error("import failed", e);
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
input.click();
|
|
829
|
+
}
|
|
568
830
|
function makeControlledFlowAdapter(value, onChange) {
|
|
569
831
|
const apply = (next) => onChange?.(next);
|
|
570
832
|
return {
|
|
@@ -590,114 +852,6 @@ function makeControlledFlowAdapter(value, onChange) {
|
|
|
590
852
|
toGraph: () => value
|
|
591
853
|
};
|
|
592
854
|
}
|
|
593
|
-
function FlowEditorBody({
|
|
594
|
-
showPalette,
|
|
595
|
-
showPanel,
|
|
596
|
-
showFeed,
|
|
597
|
-
flow,
|
|
598
|
-
runner,
|
|
599
|
-
executors,
|
|
600
|
-
metadata,
|
|
601
|
-
nodeTypes,
|
|
602
|
-
renderedNodes,
|
|
603
|
-
selected,
|
|
604
|
-
setSelectedId,
|
|
605
|
-
handleNodeClick,
|
|
606
|
-
extraToolbar
|
|
607
|
-
}) {
|
|
608
|
-
const rf = useReactFlow();
|
|
609
|
-
const dropHandlers = paletteDropHandlers((kindName, evt) => {
|
|
610
|
-
const kind = getNodeKind(kindName);
|
|
611
|
-
if (!kind) return;
|
|
612
|
-
const point = rf.screenToFlowPosition({ x: evt.clientX, y: evt.clientY });
|
|
613
|
-
const id = `n_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`;
|
|
614
|
-
flow.setNodes((all) => [
|
|
615
|
-
...all,
|
|
616
|
-
{
|
|
617
|
-
id,
|
|
618
|
-
type: kind.name,
|
|
619
|
-
position: { x: point.x - 100, y: point.y - 30 },
|
|
620
|
-
data: { kind: kind.name, label: kind.label, config: defaultConfigFor(kind) }
|
|
621
|
-
}
|
|
622
|
-
]);
|
|
623
|
-
setSelectedId(id);
|
|
624
|
-
});
|
|
625
|
-
const doExport = () => {
|
|
626
|
-
const schema = exportWorkflow({ nodes: flow.nodes, edges: flow.edges }, metadata);
|
|
627
|
-
const url = URL.createObjectURL(workflowToBlob(schema));
|
|
628
|
-
const a = document.createElement("a");
|
|
629
|
-
a.href = url;
|
|
630
|
-
a.download = `${metadata?.id ?? "workflow"}.json`;
|
|
631
|
-
a.click();
|
|
632
|
-
URL.revokeObjectURL(url);
|
|
633
|
-
};
|
|
634
|
-
const doImport = () => {
|
|
635
|
-
const input = document.createElement("input");
|
|
636
|
-
input.type = "file";
|
|
637
|
-
input.accept = "application/json";
|
|
638
|
-
input.onchange = async () => {
|
|
639
|
-
const file = input.files?.[0];
|
|
640
|
-
if (!file) return;
|
|
641
|
-
const text = await file.text();
|
|
642
|
-
try {
|
|
643
|
-
const result = importWorkflow(JSON.parse(text), { lenient: true });
|
|
644
|
-
flow.setNodes(result.graph.nodes);
|
|
645
|
-
flow.setEdges(result.graph.edges);
|
|
646
|
-
} catch (e) {
|
|
647
|
-
console.error("import failed", e);
|
|
648
|
-
}
|
|
649
|
-
};
|
|
650
|
-
input.click();
|
|
651
|
-
};
|
|
652
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
653
|
-
showPalette && /* @__PURE__ */ jsx(NodePalette, { className: "ff-editor__palette" }),
|
|
654
|
-
/* @__PURE__ */ jsxs("div", { className: "ff-editor__main", ...dropHandlers, children: [
|
|
655
|
-
/* @__PURE__ */ jsx(
|
|
656
|
-
FlowCanvas,
|
|
657
|
-
{
|
|
658
|
-
nodes: renderedNodes,
|
|
659
|
-
edges: flow.edges,
|
|
660
|
-
nodeTypes,
|
|
661
|
-
onNodesChange: flow.onNodesChange,
|
|
662
|
-
onEdgesChange: flow.onEdgesChange,
|
|
663
|
-
onConnect: flow.onConnect,
|
|
664
|
-
onNodeClick: handleNodeClick,
|
|
665
|
-
height: "100%",
|
|
666
|
-
toolbar: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
667
|
-
/* @__PURE__ */ jsx(
|
|
668
|
-
FlowRunControls,
|
|
669
|
-
{
|
|
670
|
-
running: runner.running,
|
|
671
|
-
onRun: () => runner.run({ nodes: flow.nodes, edges: flow.edges }, executors),
|
|
672
|
-
onCancel: runner.cancel,
|
|
673
|
-
onReset: runner.reset
|
|
674
|
-
}
|
|
675
|
-
),
|
|
676
|
-
/* @__PURE__ */ jsx("span", { className: "ff-editor__sep" }),
|
|
677
|
-
/* @__PURE__ */ jsx("button", { className: "ff-editor__btn", onClick: doExport, children: "\u2193 Export" }),
|
|
678
|
-
/* @__PURE__ */ jsx("button", { className: "ff-editor__btn", onClick: doImport, children: "\u2191 Import" }),
|
|
679
|
-
extraToolbar,
|
|
680
|
-
/* @__PURE__ */ jsxs("span", { className: "ff-editor__count", children: [
|
|
681
|
-
flow.nodes.length,
|
|
682
|
-
" nodes \xB7 ",
|
|
683
|
-
flow.edges.length,
|
|
684
|
-
" edges"
|
|
685
|
-
] })
|
|
686
|
-
] })
|
|
687
|
-
}
|
|
688
|
-
),
|
|
689
|
-
showFeed && /* @__PURE__ */ jsx(FlowRunFeed, { entries: runner.feed, className: "ff-editor__feed" })
|
|
690
|
-
] }),
|
|
691
|
-
showPanel && /* @__PURE__ */ jsx(
|
|
692
|
-
NodeConfigPanel,
|
|
693
|
-
{
|
|
694
|
-
className: "ff-editor__panel",
|
|
695
|
-
node: selected,
|
|
696
|
-
onChange: (next) => flow.setNodes((all) => all.map((x) => x.id === next.id ? next : x))
|
|
697
|
-
}
|
|
698
|
-
)
|
|
699
|
-
] });
|
|
700
|
-
}
|
|
701
855
|
function defineNode(render) {
|
|
702
856
|
function Wrapped(props) {
|
|
703
857
|
return render({
|
|
@@ -732,6 +886,6 @@ function NodePort({ side, type, id, style, title, className }) {
|
|
|
732
886
|
// src/index.ts
|
|
733
887
|
registerBuiltinKinds();
|
|
734
888
|
|
|
735
|
-
export { ActionNode, ConfigFieldRenderer, DecisionNode, FlowCanvas, FlowEditor, FlowRunControls, FlowRunFeed, NodeConfigPanel, NodePalette, NodePort, NodeShell, NoteNode, OutputNode, SubgraphNode, TriggerNode, defaultNodeTypes, defineNode, paletteDropHandlers };
|
|
889
|
+
export { ActionNode, ConfigFieldRenderer, DecisionNode, FlowCanvas, FlowEditor, FlowRunControls, FlowRunFeed, NodeConfigPanel, NodePalette, NodePort, NodeShell, NoteNode, OutputNode, SubgraphNode, TriggerNode, defaultNodeTypes, defineNode, paletteDropHandlers, useFlowEditor, useFlowEditorOptional };
|
|
736
890
|
//# sourceMappingURL=index.js.map
|
|
737
891
|
//# sourceMappingURL=index.js.map
|