@umodeapporg/ui 0.23.0 → 0.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/umode-ui.css +0 -105
- package/dist/components/Form/ControlledSelect.js +5 -3
- package/dist/components/Form/ControlledSelect.js.map +1 -1
- package/dist/locales/en/ui.json.d.ts +0 -67
- package/dist/locales/en/ui.json.js +0 -3
- package/dist/locales/en/ui.json.js.map +1 -1
- package/dist/locales/index.d.ts +0 -134
- package/dist/locales/pt/ui.json.d.ts +0 -67
- package/dist/locales/pt/ui.json.js +0 -3
- package/dist/locales/pt/ui.json.js.map +1 -1
- package/package.json +1 -10
- package/dist/components/UI/Flow/FlowBlockNode.d.ts +0 -35
- package/dist/components/UI/Flow/FlowBlockNode.js +0 -84
- package/dist/components/UI/Flow/FlowBlockNode.js.map +0 -1
- package/dist/components/UI/Flow/FlowCanvas.d.ts +0 -22
- package/dist/components/UI/Flow/FlowCanvas.js +0 -263
- package/dist/components/UI/Flow/FlowCanvas.js.map +0 -1
- package/dist/components/UI/Flow/FlowConnectionEdge.d.ts +0 -10
- package/dist/components/UI/Flow/FlowConnectionEdge.js +0 -54
- package/dist/components/UI/Flow/FlowConnectionEdge.js.map +0 -1
- package/dist/components/UI/Flow/FlowEditorProvider.d.ts +0 -17
- package/dist/components/UI/Flow/FlowEditorProvider.js +0 -27
- package/dist/components/UI/Flow/FlowEditorProvider.js.map +0 -1
- package/dist/components/UI/Flow/FlowInspector.d.ts +0 -11
- package/dist/components/UI/Flow/FlowInspector.js +0 -124
- package/dist/components/UI/Flow/FlowInspector.js.map +0 -1
- package/dist/components/UI/Flow/FlowIssueList.d.ts +0 -8
- package/dist/components/UI/Flow/FlowIssueList.js +0 -55
- package/dist/components/UI/Flow/FlowIssueList.js.map +0 -1
- package/dist/components/UI/Flow/FlowLaneMap.d.ts +0 -30
- package/dist/components/UI/Flow/FlowLaneMap.js +0 -206
- package/dist/components/UI/Flow/FlowLaneMap.js.map +0 -1
- package/dist/components/UI/Flow/FlowNodeShell.d.ts +0 -20
- package/dist/components/UI/Flow/FlowNodeShell.js +0 -81
- package/dist/components/UI/Flow/FlowNodeShell.js.map +0 -1
- package/dist/components/UI/Flow/FlowPalette.d.ts +0 -7
- package/dist/components/UI/Flow/FlowPalette.js +0 -90
- package/dist/components/UI/Flow/FlowPalette.js.map +0 -1
- package/dist/components/UI/Flow/FlowResizablePanel.d.ts +0 -20
- package/dist/components/UI/Flow/FlowResizablePanel.js +0 -117
- package/dist/components/UI/Flow/FlowResizablePanel.js.map +0 -1
- package/dist/components/UI/Flow/FlowZoomControls.d.ts +0 -12
- package/dist/components/UI/Flow/FlowZoomControls.js +0 -53
- package/dist/components/UI/Flow/FlowZoomControls.js.map +0 -1
- package/dist/components/UI/Flow/flowBlockRegistry.d.ts +0 -12
- package/dist/components/UI/Flow/flowBlockRegistry.js +0 -15
- package/dist/components/UI/Flow/flowBlockRegistry.js.map +0 -1
- package/dist/components/UI/Flow/flowConstants.d.ts +0 -2
- package/dist/components/UI/Flow/flowConstants.js +0 -5
- package/dist/components/UI/Flow/flowConstants.js.map +0 -1
- package/dist/components/UI/Flow/flowEditorStore.d.ts +0 -44
- package/dist/components/UI/Flow/flowEditorStore.js +0 -258
- package/dist/components/UI/Flow/flowEditorStore.js.map +0 -1
- package/dist/components/UI/Flow/flowGraph.d.ts +0 -32
- package/dist/components/UI/Flow/flowGraph.js +0 -156
- package/dist/components/UI/Flow/flowGraph.js.map +0 -1
- package/dist/components/UI/Flow/flowTypes.d.ts +0 -74
- package/dist/components/UI/Flow/flowTypes.js +0 -2
- package/dist/components/UI/Flow/flowTypes.js.map +0 -1
- package/dist/components/UI/Flow/index.d.ts +0 -14
- package/dist/components/UI/Flow/index.js +0 -43
- package/dist/components/UI/Flow/index.js.map +0 -1
- package/dist/components/UI/Flow/useFlowLabels.d.ts +0 -10
- package/dist/components/UI/Flow/useFlowLabels.js +0 -51
- package/dist/components/UI/Flow/useFlowLabels.js.map +0 -1
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useState, useRef, useEffect } from "react";
|
|
3
|
-
import classNames from "classnames";
|
|
4
|
-
import { useTranslation } from "react-i18next";
|
|
5
|
-
import { twMerge } from "tailwind-merge";
|
|
6
|
-
import ControlledButton from "../../Form/ControlledButton.js";
|
|
7
|
-
import ControlledTooltip from "../ControlledTooltip.js";
|
|
8
|
-
import { writeUiPreference, readUiPreference } from "../../../helpers/uiPreferences.js";
|
|
9
|
-
const PANEL_WIDTHS_KEY = "umode-flow-panel-widths";
|
|
10
|
-
const KEYBOARD_RESIZE_STEP = 24;
|
|
11
|
-
const readStoredWidths = () => readUiPreference(PANEL_WIDTHS_KEY) ?? {};
|
|
12
|
-
const FlowResizablePanel = ({
|
|
13
|
-
handleSide,
|
|
14
|
-
defaultWidth,
|
|
15
|
-
minWidth,
|
|
16
|
-
maxWidth,
|
|
17
|
-
storageKey,
|
|
18
|
-
className,
|
|
19
|
-
children
|
|
20
|
-
}) => {
|
|
21
|
-
const { t } = useTranslation("ui");
|
|
22
|
-
const clampWidth = useCallback(
|
|
23
|
-
(width2) => Math.min(maxWidth, Math.max(minWidth, Math.round(width2))),
|
|
24
|
-
[minWidth, maxWidth]
|
|
25
|
-
);
|
|
26
|
-
const [width, setWidth] = useState(() => {
|
|
27
|
-
const stored = storageKey ? readStoredWidths()[storageKey] : void 0;
|
|
28
|
-
return clampWidth(typeof stored === "number" ? stored : defaultWidth);
|
|
29
|
-
});
|
|
30
|
-
const [isResizing, setIsResizing] = useState(false);
|
|
31
|
-
const resizeStartRef = useRef(null);
|
|
32
|
-
const widthRef = useRef(width);
|
|
33
|
-
widthRef.current = width;
|
|
34
|
-
const persistWidth = useCallback(
|
|
35
|
-
(nextWidth) => {
|
|
36
|
-
if (!storageKey) return;
|
|
37
|
-
writeUiPreference(PANEL_WIDTHS_KEY, { ...readStoredWidths(), [storageKey]: nextWidth });
|
|
38
|
-
},
|
|
39
|
-
[storageKey]
|
|
40
|
-
);
|
|
41
|
-
useEffect(() => setWidth((current) => clampWidth(current)), [clampWidth]);
|
|
42
|
-
const handlePointerDown = (event) => {
|
|
43
|
-
event.preventDefault();
|
|
44
|
-
resizeStartRef.current = { pointerX: event.clientX, width: widthRef.current };
|
|
45
|
-
setIsResizing(true);
|
|
46
|
-
event.currentTarget.setPointerCapture(event.pointerId);
|
|
47
|
-
};
|
|
48
|
-
const handlePointerMove = (event) => {
|
|
49
|
-
const resizeStart = resizeStartRef.current;
|
|
50
|
-
if (!resizeStart) return;
|
|
51
|
-
const pointerDelta = event.clientX - resizeStart.pointerX;
|
|
52
|
-
setWidth(clampWidth(resizeStart.width + (handleSide === "right" ? pointerDelta : -pointerDelta)));
|
|
53
|
-
};
|
|
54
|
-
const handlePointerEnd = (event) => {
|
|
55
|
-
if (!resizeStartRef.current) return;
|
|
56
|
-
resizeStartRef.current = null;
|
|
57
|
-
setIsResizing(false);
|
|
58
|
-
persistWidth(widthRef.current);
|
|
59
|
-
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
|
60
|
-
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
const handleKeyDown = (event) => {
|
|
64
|
-
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
|
|
65
|
-
event.preventDefault();
|
|
66
|
-
const pointerDirection = event.key === "ArrowRight" ? 1 : -1;
|
|
67
|
-
const nextWidth = clampWidth(
|
|
68
|
-
widthRef.current + (handleSide === "right" ? pointerDirection : -pointerDirection) * KEYBOARD_RESIZE_STEP
|
|
69
|
-
);
|
|
70
|
-
setWidth(nextWidth);
|
|
71
|
-
persistWidth(nextWidth);
|
|
72
|
-
};
|
|
73
|
-
const handleReset = () => {
|
|
74
|
-
const nextWidth = clampWidth(defaultWidth);
|
|
75
|
-
setWidth(nextWidth);
|
|
76
|
-
persistWidth(nextWidth);
|
|
77
|
-
};
|
|
78
|
-
return /* @__PURE__ */ jsxs(
|
|
79
|
-
"div",
|
|
80
|
-
{
|
|
81
|
-
className: twMerge("relative w-full shrink-0 lg:w-[var(--flow-panel-width)]", className),
|
|
82
|
-
style: { "--flow-panel-width": `${width}px` },
|
|
83
|
-
children: [
|
|
84
|
-
children,
|
|
85
|
-
/* @__PURE__ */ jsx(ControlledTooltip, { content: t("flow.resizePanel"), placement: handleSide === "right" ? "right" : "left", children: /* @__PURE__ */ jsx(
|
|
86
|
-
ControlledButton,
|
|
87
|
-
{
|
|
88
|
-
defaultHtmlButton: true,
|
|
89
|
-
onPointerDown: handlePointerDown,
|
|
90
|
-
onPointerMove: handlePointerMove,
|
|
91
|
-
onPointerUp: handlePointerEnd,
|
|
92
|
-
onPointerCancel: handlePointerEnd,
|
|
93
|
-
onKeyDown: handleKeyDown,
|
|
94
|
-
onDoubleClick: handleReset,
|
|
95
|
-
className: classNames(
|
|
96
|
-
"group/resize absolute inset-y-0 z-10 hidden w-3 cursor-col-resize touch-none select-none items-center justify-center focus:outline-none lg:flex",
|
|
97
|
-
handleSide === "right" ? "-right-3" : "-left-3"
|
|
98
|
-
),
|
|
99
|
-
children: /* @__PURE__ */ jsx(
|
|
100
|
-
"span",
|
|
101
|
-
{
|
|
102
|
-
className: classNames(
|
|
103
|
-
"h-full w-0.5 rounded-full transition-colors duration-base",
|
|
104
|
-
isResizing ? "bg-brand-hover" : "bg-transparent group-hover/resize:bg-brand-hover"
|
|
105
|
-
)
|
|
106
|
-
}
|
|
107
|
-
)
|
|
108
|
-
}
|
|
109
|
-
) })
|
|
110
|
-
]
|
|
111
|
-
}
|
|
112
|
-
);
|
|
113
|
-
};
|
|
114
|
-
export {
|
|
115
|
-
FlowResizablePanel as default
|
|
116
|
-
};
|
|
117
|
-
//# sourceMappingURL=FlowResizablePanel.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FlowResizablePanel.js","sources":["../../../../src/components/UI/Flow/FlowResizablePanel.tsx"],"sourcesContent":["import { CSSProperties, ReactNode, useCallback, useEffect, useRef, useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\nimport { twMerge } from 'tailwind-merge';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { readUiPreference, writeUiPreference } from '@/helpers/uiPreferences';\n\nconst PANEL_WIDTHS_KEY = 'umode-flow-panel-widths';\nconst KEYBOARD_RESIZE_STEP = 24;\n\nexport interface IFlowResizablePanelProps {\n /** Which edge carries the drag handle: the one that faces the canvas. */\n handleSide: 'left' | 'right';\n /** Widths in px, like any pointer-driven geometry. */\n defaultWidth: number;\n minWidth: number;\n maxWidth: number;\n /** Remembers the width the user chose, per panel, across visits. */\n storageKey?: string;\n className?: string;\n children: ReactNode;\n}\n\nconst readStoredWidths = (): Record<string, number> => readUiPreference<Record<string, number>>(PANEL_WIDTHS_KEY) ?? {};\n\n/**\n * A side panel of the flow editor that the user can widen or narrow by dragging the edge facing\n * the canvas (arrow keys on the focused handle, double click resets). Below `lg` it takes the full\n * width and the handle hides: stacked under the canvas there is nothing to resize against.\n */\nconst FlowResizablePanel: React.FC<IFlowResizablePanelProps> = ({\n handleSide,\n defaultWidth,\n minWidth,\n maxWidth,\n storageKey,\n className,\n children,\n}) => {\n const { t } = useTranslation('ui');\n const clampWidth = useCallback(\n (width: number) => Math.min(maxWidth, Math.max(minWidth, Math.round(width))),\n [minWidth, maxWidth],\n );\n const [width, setWidth] = useState(() => {\n const stored = storageKey ? readStoredWidths()[storageKey] : undefined;\n return clampWidth(typeof stored === 'number' ? stored : defaultWidth);\n });\n const [isResizing, setIsResizing] = useState(false);\n const resizeStartRef = useRef<{ pointerX: number; width: number } | null>(null);\n const widthRef = useRef(width);\n widthRef.current = width;\n\n const persistWidth = useCallback(\n (nextWidth: number) => {\n if (!storageKey) return;\n writeUiPreference(PANEL_WIDTHS_KEY, { ...readStoredWidths(), [storageKey]: nextWidth });\n },\n [storageKey],\n );\n\n useEffect(() => setWidth((current) => clampWidth(current)), [clampWidth]);\n\n const handlePointerDown = (event: React.PointerEvent<HTMLButtonElement>) => {\n event.preventDefault();\n resizeStartRef.current = { pointerX: event.clientX, width: widthRef.current };\n setIsResizing(true);\n event.currentTarget.setPointerCapture(event.pointerId);\n };\n\n const handlePointerMove = (event: React.PointerEvent<HTMLButtonElement>) => {\n const resizeStart = resizeStartRef.current;\n if (!resizeStart) return;\n const pointerDelta = event.clientX - resizeStart.pointerX;\n setWidth(clampWidth(resizeStart.width + (handleSide === 'right' ? pointerDelta : -pointerDelta)));\n };\n\n const handlePointerEnd = (event: React.PointerEvent<HTMLButtonElement>) => {\n if (!resizeStartRef.current) return;\n resizeStartRef.current = null;\n setIsResizing(false);\n persistWidth(widthRef.current);\n if (event.currentTarget.hasPointerCapture(event.pointerId)) {\n event.currentTarget.releasePointerCapture(event.pointerId);\n }\n };\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {\n if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;\n event.preventDefault();\n const pointerDirection = event.key === 'ArrowRight' ? 1 : -1;\n const nextWidth = clampWidth(\n widthRef.current + (handleSide === 'right' ? pointerDirection : -pointerDirection) * KEYBOARD_RESIZE_STEP,\n );\n setWidth(nextWidth);\n persistWidth(nextWidth);\n };\n\n const handleReset = () => {\n const nextWidth = clampWidth(defaultWidth);\n setWidth(nextWidth);\n persistWidth(nextWidth);\n };\n\n return (\n <div\n className={twMerge('relative w-full shrink-0 lg:w-[var(--flow-panel-width)]', className)}\n style={{ '--flow-panel-width': `${width}px` } as CSSProperties}\n >\n {children}\n <ControlledTooltip content={t('flow.resizePanel')} placement={handleSide === 'right' ? 'right' : 'left'}>\n <ControlledButton\n defaultHtmlButton\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerEnd}\n onPointerCancel={handlePointerEnd}\n onKeyDown={handleKeyDown}\n onDoubleClick={handleReset}\n className={classNames(\n 'group/resize absolute inset-y-0 z-10 hidden w-3 cursor-col-resize touch-none select-none items-center justify-center focus:outline-none lg:flex',\n handleSide === 'right' ? '-right-3' : '-left-3',\n )}\n >\n <span\n className={classNames(\n 'h-full w-0.5 rounded-full transition-colors duration-base',\n isResizing ? 'bg-brand-hover' : 'bg-transparent group-hover/resize:bg-brand-hover',\n )}\n />\n </ControlledButton>\n </ControlledTooltip>\n </div>\n );\n};\n\nexport default FlowResizablePanel;\n"],"names":["width"],"mappings":"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"}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { PanelPosition } from '@xyflow/react';
|
|
2
|
-
interface IFlowZoomControlsProps {
|
|
3
|
-
onUndo?: () => void;
|
|
4
|
-
onRedo?: () => void;
|
|
5
|
-
canUndo?: boolean;
|
|
6
|
-
canRedo?: boolean;
|
|
7
|
-
onLayout?: () => void;
|
|
8
|
-
position?: PanelPosition;
|
|
9
|
-
}
|
|
10
|
-
/** Zoom, fit and history controls drawn with the library's own buttons. */
|
|
11
|
-
declare const FlowZoomControls: React.FC<IFlowZoomControlsProps>;
|
|
12
|
-
export default FlowZoomControls;
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useTranslation } from "react-i18next";
|
|
3
|
-
import ControlledButton from "../../Form/ControlledButton.js";
|
|
4
|
-
import ControlledTooltip from "../ControlledTooltip.js";
|
|
5
|
-
import { MaterialSymbol } from "../MaterialSymbol/index.js";
|
|
6
|
-
import { useReactFlow, Panel } from "@xyflow/react";
|
|
7
|
-
const ControlButton = ({ icon, label, onClick, disabled }) => /* @__PURE__ */ jsx(ControlledTooltip, { content: label, placement: "right", children: /* @__PURE__ */ jsx(
|
|
8
|
-
ControlledButton,
|
|
9
|
-
{
|
|
10
|
-
defaultHtmlButton: true,
|
|
11
|
-
onClick,
|
|
12
|
-
disabled,
|
|
13
|
-
className: "interactive flex size-8 items-center justify-center rounded-lg text-foreground-muted hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-40",
|
|
14
|
-
children: /* @__PURE__ */ jsx(MaterialSymbol, { icon, size: 18, "aria-hidden": true })
|
|
15
|
-
}
|
|
16
|
-
) });
|
|
17
|
-
const FlowZoomControls = ({
|
|
18
|
-
onUndo,
|
|
19
|
-
onRedo,
|
|
20
|
-
canUndo,
|
|
21
|
-
canRedo,
|
|
22
|
-
onLayout,
|
|
23
|
-
position = "top-left"
|
|
24
|
-
}) => {
|
|
25
|
-
const { t } = useTranslation("ui");
|
|
26
|
-
const { zoomIn, zoomOut, fitView } = useReactFlow();
|
|
27
|
-
return /* @__PURE__ */ jsxs(
|
|
28
|
-
Panel,
|
|
29
|
-
{
|
|
30
|
-
position,
|
|
31
|
-
className: "flex flex-col gap-y-1 rounded-xl border border-border bg-surface p-1 shadow-sm",
|
|
32
|
-
children: [
|
|
33
|
-
/* @__PURE__ */ jsx(ControlButton, { icon: "add", label: t("flow.controls.zoomIn"), onClick: () => void zoomIn() }),
|
|
34
|
-
/* @__PURE__ */ jsx(ControlButton, { icon: "remove", label: t("flow.controls.zoomOut"), onClick: () => void zoomOut() }),
|
|
35
|
-
/* @__PURE__ */ jsx(
|
|
36
|
-
ControlButton,
|
|
37
|
-
{
|
|
38
|
-
icon: "fit_screen",
|
|
39
|
-
label: t("flow.controls.fitView"),
|
|
40
|
-
onClick: () => void fitView({ padding: 0.2 })
|
|
41
|
-
}
|
|
42
|
-
),
|
|
43
|
-
onLayout && /* @__PURE__ */ jsx(ControlButton, { icon: "account_tree", label: t("flow.controls.layout"), onClick: onLayout }),
|
|
44
|
-
onUndo && /* @__PURE__ */ jsx(ControlButton, { icon: "undo", label: t("flow.controls.undo"), onClick: onUndo, disabled: !canUndo }),
|
|
45
|
-
onRedo && /* @__PURE__ */ jsx(ControlButton, { icon: "redo", label: t("flow.controls.redo"), onClick: onRedo, disabled: !canRedo })
|
|
46
|
-
]
|
|
47
|
-
}
|
|
48
|
-
);
|
|
49
|
-
};
|
|
50
|
-
export {
|
|
51
|
-
FlowZoomControls as default
|
|
52
|
-
};
|
|
53
|
-
//# sourceMappingURL=FlowZoomControls.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FlowZoomControls.js","sources":["../../../../src/components/UI/Flow/FlowZoomControls.tsx"],"sourcesContent":["import { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol, SymbolCodepoints } from '@/components/UI/MaterialSymbol';\nimport { Panel, type PanelPosition, useReactFlow } from '@xyflow/react';\n\ninterface IFlowZoomControlsProps {\n onUndo?: () => void;\n onRedo?: () => void;\n canUndo?: boolean;\n canRedo?: boolean;\n onLayout?: () => void;\n position?: PanelPosition;\n}\n\ninterface IControlButtonProps {\n icon: SymbolCodepoints;\n label: string;\n onClick: () => void;\n disabled?: boolean;\n}\n\nconst ControlButton: React.FC<IControlButtonProps> = ({ icon, label, onClick, disabled }) => (\n <ControlledTooltip content={label} placement=\"right\">\n <ControlledButton\n defaultHtmlButton\n onClick={onClick}\n disabled={disabled}\n className=\"interactive flex size-8 items-center justify-center rounded-lg text-foreground-muted hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-40\"\n >\n <MaterialSymbol icon={icon} size={18} aria-hidden />\n </ControlledButton>\n </ControlledTooltip>\n);\n\n/** Zoom, fit and history controls drawn with the library's own buttons. */\nconst FlowZoomControls: React.FC<IFlowZoomControlsProps> = ({\n onUndo,\n onRedo,\n canUndo,\n canRedo,\n onLayout,\n position = 'top-left',\n}) => {\n const { t } = useTranslation('ui');\n const { zoomIn, zoomOut, fitView } = useReactFlow();\n\n return (\n <Panel\n position={position}\n className=\"flex flex-col gap-y-1 rounded-xl border border-border bg-surface p-1 shadow-sm\"\n >\n <ControlButton icon=\"add\" label={t('flow.controls.zoomIn')} onClick={() => void zoomIn()} />\n <ControlButton icon=\"remove\" label={t('flow.controls.zoomOut')} onClick={() => void zoomOut()} />\n <ControlButton\n icon=\"fit_screen\"\n label={t('flow.controls.fitView')}\n onClick={() => void fitView({ padding: 0.2 })}\n />\n {onLayout && <ControlButton icon=\"account_tree\" label={t('flow.controls.layout')} onClick={onLayout} />}\n {onUndo && <ControlButton icon=\"undo\" label={t('flow.controls.undo')} onClick={onUndo} disabled={!canUndo} />}\n {onRedo && <ControlButton icon=\"redo\" label={t('flow.controls.redo')} onClick={onRedo} disabled={!canRedo} />}\n </Panel>\n );\n};\n\nexport default FlowZoomControls;\n"],"names":[],"mappings":";;;;;;AAuBA,MAAM,gBAA+C,CAAC,EAAE,MAAM,OAAO,SAAS,SAAA,MAC5E,oBAAC,mBAAA,EAAkB,SAAS,OAAO,WAAU,SAC3C,UAAA;AAAA,EAAC;AAAA,EAAA;AAAA,IACC,mBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA,WAAU;AAAA,IAEV,8BAAC,gBAAA,EAAe,MAAY,MAAM,IAAI,eAAW,KAAA,CAAC;AAAA,EAAA;AACpD,GACF;AAIF,MAAM,mBAAqD,CAAC;AAAA,EAC1D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AACb,MAAM;AACJ,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AACjC,QAAM,EAAE,QAAQ,SAAS,QAAA,IAAY,aAAA;AAErC,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,WAAU;AAAA,MAEV,UAAA;AAAA,QAAA,oBAAC,eAAA,EAAc,MAAK,OAAM,OAAO,EAAE,sBAAsB,GAAG,SAAS,MAAM,KAAK,OAAA,EAAO,CAAG;AAAA,QAC1F,oBAAC,eAAA,EAAc,MAAK,UAAS,OAAO,EAAE,uBAAuB,GAAG,SAAS,MAAM,KAAK,QAAA,EAAQ,CAAG;AAAA,QAC/F;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,OAAO,EAAE,uBAAuB;AAAA,YAChC,SAAS,MAAM,KAAK,QAAQ,EAAE,SAAS,KAAK;AAAA,UAAA;AAAA,QAAA;AAAA,QAE7C,YAAY,oBAAC,eAAA,EAAc,MAAK,gBAAe,OAAO,EAAE,sBAAsB,GAAG,SAAS,SAAA,CAAU;AAAA,QACpG,UAAU,oBAAC,eAAA,EAAc,MAAK,QAAO,OAAO,EAAE,oBAAoB,GAAG,SAAS,QAAQ,UAAU,CAAC,QAAA,CAAS;AAAA,QAC1G,UAAU,oBAAC,eAAA,EAAc,MAAK,QAAO,OAAO,EAAE,oBAAoB,GAAG,SAAS,QAAQ,UAAU,CAAC,QAAA,CAAS;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGjH;"}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { IFlowBlockDefinition, TFlowBlockCategory } from './flowTypes';
|
|
2
|
-
export interface IFlowBlockRegistry {
|
|
3
|
-
list: Array<IFlowBlockDefinition>;
|
|
4
|
-
get: (type: string) => IFlowBlockDefinition | undefined;
|
|
5
|
-
byCategory: (category: TFlowBlockCategory) => Array<IFlowBlockDefinition>;
|
|
6
|
-
}
|
|
7
|
-
/**
|
|
8
|
-
* Erases the config type so blocks with different configs live in one registry. The
|
|
9
|
-
* canvas only hands each block the config it created, so the cast never crosses blocks.
|
|
10
|
-
*/
|
|
11
|
-
export declare const defineFlowBlock: <TConfig>(definition: IFlowBlockDefinition<TConfig>) => IFlowBlockDefinition;
|
|
12
|
-
export declare const createFlowBlockRegistry: (definitions: Array<IFlowBlockDefinition>) => IFlowBlockRegistry;
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
const defineFlowBlock = (definition) => definition;
|
|
2
|
-
const createFlowBlockRegistry = (definitions) => {
|
|
3
|
-
const safeDefinitions = Array.isArray(definitions) ? definitions : [];
|
|
4
|
-
const byType = new Map(safeDefinitions.map((definition) => [definition.type, definition]));
|
|
5
|
-
return {
|
|
6
|
-
list: safeDefinitions,
|
|
7
|
-
get: (type) => byType.get(type),
|
|
8
|
-
byCategory: (category) => safeDefinitions.filter((definition) => definition.category === category)
|
|
9
|
-
};
|
|
10
|
-
};
|
|
11
|
-
export {
|
|
12
|
-
createFlowBlockRegistry,
|
|
13
|
-
defineFlowBlock
|
|
14
|
-
};
|
|
15
|
-
//# sourceMappingURL=flowBlockRegistry.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"flowBlockRegistry.js","sources":["../../../../src/components/UI/Flow/flowBlockRegistry.ts"],"sourcesContent":["import type { IFlowBlockDefinition, TFlowBlockCategory } from './flowTypes';\n\nexport interface IFlowBlockRegistry {\n list: Array<IFlowBlockDefinition>;\n get: (type: string) => IFlowBlockDefinition | undefined;\n byCategory: (category: TFlowBlockCategory) => Array<IFlowBlockDefinition>;\n}\n\n/**\n * Erases the config type so blocks with different configs live in one registry. The\n * canvas only hands each block the config it created, so the cast never crosses blocks.\n */\nexport const defineFlowBlock = <TConfig>(definition: IFlowBlockDefinition<TConfig>): IFlowBlockDefinition =>\n definition as unknown as IFlowBlockDefinition;\n\nexport const createFlowBlockRegistry = (definitions: Array<IFlowBlockDefinition>): IFlowBlockRegistry => {\n const safeDefinitions = Array.isArray(definitions) ? definitions : [];\n const byType = new Map(safeDefinitions.map((definition) => [definition.type, definition]));\n\n return {\n list: safeDefinitions,\n get: (type) => byType.get(type),\n byCategory: (category) => safeDefinitions.filter((definition) => definition.category === category),\n };\n};\n"],"names":[],"mappings":"AAYO,MAAM,kBAAkB,CAAU,eACvC;AAEK,MAAM,0BAA0B,CAAC,gBAAiE;AACvG,QAAM,kBAAkB,MAAM,QAAQ,WAAW,IAAI,cAAc,CAAA;AACnE,QAAM,SAAS,IAAI,IAAI,gBAAgB,IAAI,CAAC,eAAe,CAAC,WAAW,MAAM,UAAU,CAAC,CAAC;AAEzF,SAAO;AAAA,IACL,MAAM;AAAA,IACN,KAAK,CAAC,SAAS,OAAO,IAAI,IAAI;AAAA,IAC9B,YAAY,CAAC,aAAa,gBAAgB,OAAO,CAAC,eAAe,WAAW,aAAa,QAAQ;AAAA,EAAA;AAErG;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"flowConstants.js","sources":["../../../../src/components/UI/Flow/flowConstants.ts"],"sourcesContent":["/** The MIME type a palette item puts on the drag so the canvas knows which block was dropped. */\nexport const FLOW_BLOCK_DRAG_TYPE = 'application/x-umode-flow-block';\n"],"names":[],"mappings":"AACO,MAAM,uBAAuB;"}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { StoreApi } from 'zustand/vanilla';
|
|
2
|
-
import { IFlowBlockRegistry } from './flowBlockRegistry';
|
|
3
|
-
import { IFlowGraph, IFlowNode, IFlowPosition, TFlowPortId } from './flowTypes';
|
|
4
|
-
export interface IFlowEditorState {
|
|
5
|
-
graph: IFlowGraph;
|
|
6
|
-
selectedNodeIds: Array<string>;
|
|
7
|
-
selectedEdgeIds: Array<string>;
|
|
8
|
-
past: Array<IFlowGraph>;
|
|
9
|
-
future: Array<IFlowGraph>;
|
|
10
|
-
clipboard: Array<IFlowNode>;
|
|
11
|
-
isDirty: boolean;
|
|
12
|
-
replaceGraph: (graph: IFlowGraph, options?: {
|
|
13
|
-
keepHistory?: boolean;
|
|
14
|
-
}) => void;
|
|
15
|
-
markClean: () => void;
|
|
16
|
-
/** Snapshot before a gesture that changes the graph in many small steps (a drag). */
|
|
17
|
-
recordHistory: () => void;
|
|
18
|
-
addNode: (type: string, position?: IFlowPosition) => string | null;
|
|
19
|
-
/** Adds a block right of `sourceNodeId` and wires `sourcePort` to it in one undo step. */
|
|
20
|
-
addNodeFromPort: (type: string, sourceNodeId: string, sourcePort: TFlowPortId) => string | null;
|
|
21
|
-
updateNodeConfig: (nodeId: string, config: unknown) => void;
|
|
22
|
-
updateNodeLabel: (nodeId: string, label: string) => void;
|
|
23
|
-
moveNodes: (positions: Record<string, IFlowPosition>) => void;
|
|
24
|
-
connect: (source: string, sourcePort: TFlowPortId, target: string) => boolean;
|
|
25
|
-
removeEdges: (edgeIds: Array<string>) => void;
|
|
26
|
-
removeSelected: () => void;
|
|
27
|
-
duplicateSelected: () => void;
|
|
28
|
-
copySelected: () => void;
|
|
29
|
-
paste: () => void;
|
|
30
|
-
setSelection: (selection: {
|
|
31
|
-
nodeIds?: Array<string>;
|
|
32
|
-
edgeIds?: Array<string>;
|
|
33
|
-
}) => void;
|
|
34
|
-
undo: () => void;
|
|
35
|
-
redo: () => void;
|
|
36
|
-
/** The host may rebuild its registry (labels after a language switch); gestures always read the latest. */
|
|
37
|
-
setRegistry: (registry: IFlowBlockRegistry) => void;
|
|
38
|
-
}
|
|
39
|
-
export type TFlowEditorStore = StoreApi<IFlowEditorState>;
|
|
40
|
-
/**
|
|
41
|
-
* One store per editor instance (two canvases on a page never share state). The graph
|
|
42
|
-
* is the source of truth; the canvas only renders it and reports gestures back.
|
|
43
|
-
*/
|
|
44
|
-
export declare const createFlowEditorStore: (initialGraph: IFlowGraph, registry: IFlowBlockRegistry) => TFlowEditorStore;
|
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
import { createStore } from "zustand/vanilla";
|
|
2
|
-
import { generateUUID } from "../../../helpers/utilityHelpers.js";
|
|
3
|
-
import { canConnect, FLOW_SIBLING_GAP, FLOW_LEVEL_GAP, layoutFlowByLevels } from "./flowGraph.js";
|
|
4
|
-
const HISTORY_LIMIT = 50;
|
|
5
|
-
const PASTE_OFFSET = 32;
|
|
6
|
-
const COALESCE_WINDOW_MS = 1e3;
|
|
7
|
-
const isPosition = (position) => typeof position?.x === "number" && typeof position?.y === "number";
|
|
8
|
-
const normalizeGraph = (graph, registry) => {
|
|
9
|
-
const normalized = {
|
|
10
|
-
nodes: Array.isArray(graph?.nodes) ? graph.nodes : [],
|
|
11
|
-
edges: Array.isArray(graph?.edges) ? graph.edges : []
|
|
12
|
-
};
|
|
13
|
-
if (normalized.nodes.every((node) => isPosition(node.position))) return normalized;
|
|
14
|
-
const layout = layoutFlowByLevels(normalized, registry);
|
|
15
|
-
return {
|
|
16
|
-
...normalized,
|
|
17
|
-
nodes: normalized.nodes.map(
|
|
18
|
-
(node) => isPosition(node.position) ? node : { ...node, position: layout[node.nodeId] ?? { x: 0, y: 0 } }
|
|
19
|
-
)
|
|
20
|
-
};
|
|
21
|
-
};
|
|
22
|
-
const withoutNodes = (graph, nodeIds) => ({
|
|
23
|
-
nodes: graph.nodes.filter((node) => !nodeIds.has(node.nodeId)),
|
|
24
|
-
edges: graph.edges.filter((edge) => !nodeIds.has(edge.source) && !nodeIds.has(edge.target))
|
|
25
|
-
});
|
|
26
|
-
const isPositionTaken = (graph, position) => graph.nodes.some(
|
|
27
|
-
(node) => Math.abs(node.position.x - position.x) < FLOW_LEVEL_GAP && Math.abs(node.position.y - position.y) < FLOW_SIBLING_GAP
|
|
28
|
-
);
|
|
29
|
-
const nextFreePosition = (graph, selectedNodeIds) => {
|
|
30
|
-
if (graph.nodes.length === 0) return { x: 0, y: 0 };
|
|
31
|
-
const anchor = graph.nodes.find((node) => node.nodeId === selectedNodeIds[0]) ?? graph.nodes.reduce((current, node) => node.position.x > current.position.x ? node : current);
|
|
32
|
-
let candidate = { x: anchor.position.x + FLOW_LEVEL_GAP, y: anchor.position.y };
|
|
33
|
-
while (isPositionTaken(graph, candidate)) candidate = { x: candidate.x, y: candidate.y + FLOW_SIBLING_GAP };
|
|
34
|
-
return candidate;
|
|
35
|
-
};
|
|
36
|
-
const createFlowEditorStore = (initialGraph, registry) => createStore()((set, get) => {
|
|
37
|
-
let currentRegistry = registry;
|
|
38
|
-
let lastCoalesce = null;
|
|
39
|
-
const commit = (next, selection, coalesceKey) => {
|
|
40
|
-
const { graph, past } = get();
|
|
41
|
-
const now = Date.now();
|
|
42
|
-
const extendsLastStep = coalesceKey !== void 0 && lastCoalesce?.key === coalesceKey && now - lastCoalesce.at < COALESCE_WINDOW_MS;
|
|
43
|
-
lastCoalesce = coalesceKey === void 0 ? null : { key: coalesceKey, at: now };
|
|
44
|
-
set({
|
|
45
|
-
graph: next,
|
|
46
|
-
past: extendsLastStep ? past : [...past, graph].slice(-HISTORY_LIMIT),
|
|
47
|
-
future: [],
|
|
48
|
-
isDirty: true,
|
|
49
|
-
...selection?.nodeIds ? { selectedNodeIds: selection.nodeIds } : {},
|
|
50
|
-
...selection?.edgeIds ? { selectedEdgeIds: selection.edgeIds } : {}
|
|
51
|
-
});
|
|
52
|
-
};
|
|
53
|
-
const cloneNodes = (nodes, offset) => {
|
|
54
|
-
const instancesByType = /* @__PURE__ */ new Map();
|
|
55
|
-
get().graph.nodes.forEach((node) => instancesByType.set(node.type, (instancesByType.get(node.type) ?? 0) + 1));
|
|
56
|
-
return nodes.filter((node) => {
|
|
57
|
-
const definition = currentRegistry.get(node.type);
|
|
58
|
-
if (!definition || definition.category === "trigger") return false;
|
|
59
|
-
const instances = instancesByType.get(node.type) ?? 0;
|
|
60
|
-
if (definition.maxInstances !== void 0 && instances >= definition.maxInstances) return false;
|
|
61
|
-
instancesByType.set(node.type, instances + 1);
|
|
62
|
-
return true;
|
|
63
|
-
}).map((node) => ({
|
|
64
|
-
...node,
|
|
65
|
-
nodeId: generateUUID(),
|
|
66
|
-
config: structuredClone(node.config),
|
|
67
|
-
position: { x: node.position.x + offset, y: node.position.y + offset }
|
|
68
|
-
}));
|
|
69
|
-
};
|
|
70
|
-
return {
|
|
71
|
-
graph: normalizeGraph(initialGraph, registry),
|
|
72
|
-
selectedNodeIds: [],
|
|
73
|
-
selectedEdgeIds: [],
|
|
74
|
-
past: [],
|
|
75
|
-
future: [],
|
|
76
|
-
clipboard: [],
|
|
77
|
-
isDirty: false,
|
|
78
|
-
replaceGraph: (graph, options) => {
|
|
79
|
-
set((state) => ({
|
|
80
|
-
graph: normalizeGraph(graph, currentRegistry),
|
|
81
|
-
past: options?.keepHistory ? [...state.past, state.graph].slice(-HISTORY_LIMIT) : [],
|
|
82
|
-
future: [],
|
|
83
|
-
selectedNodeIds: [],
|
|
84
|
-
selectedEdgeIds: [],
|
|
85
|
-
isDirty: Boolean(options?.keepHistory)
|
|
86
|
-
}));
|
|
87
|
-
},
|
|
88
|
-
markClean: () => set({ isDirty: false }),
|
|
89
|
-
recordHistory: () => {
|
|
90
|
-
const { graph, past } = get();
|
|
91
|
-
set({ past: [...past, graph].slice(-HISTORY_LIMIT), future: [] });
|
|
92
|
-
},
|
|
93
|
-
addNode: (type, position) => {
|
|
94
|
-
const definition = currentRegistry.get(type);
|
|
95
|
-
if (!definition) return null;
|
|
96
|
-
const { graph, selectedNodeIds } = get();
|
|
97
|
-
const instances = graph.nodes.filter((node2) => node2.type === type).length;
|
|
98
|
-
if (definition.maxInstances !== void 0 && instances >= definition.maxInstances) return null;
|
|
99
|
-
const node = {
|
|
100
|
-
nodeId: generateUUID(),
|
|
101
|
-
type,
|
|
102
|
-
typeVersion: definition.typeVersion,
|
|
103
|
-
config: definition.createDefaultConfig(),
|
|
104
|
-
position: position ?? nextFreePosition(graph, selectedNodeIds)
|
|
105
|
-
};
|
|
106
|
-
commit({ ...graph, nodes: [...graph.nodes, node] }, { nodeIds: [node.nodeId], edgeIds: [] });
|
|
107
|
-
return node.nodeId;
|
|
108
|
-
},
|
|
109
|
-
addNodeFromPort: (type, sourceNodeId, sourcePort) => {
|
|
110
|
-
const definition = currentRegistry.get(type);
|
|
111
|
-
const { graph } = get();
|
|
112
|
-
const source = graph.nodes.find((node2) => node2.nodeId === sourceNodeId);
|
|
113
|
-
if (!definition || !source) return null;
|
|
114
|
-
const instances = graph.nodes.filter((node2) => node2.type === type).length;
|
|
115
|
-
if (definition.maxInstances !== void 0 && instances >= definition.maxInstances) return null;
|
|
116
|
-
if (graph.edges.some((edge) => edge.source === sourceNodeId && edge.sourcePort === sourcePort)) return null;
|
|
117
|
-
const outputs = currentRegistry.get(source.type)?.outputs ?? [];
|
|
118
|
-
const portIndex = Math.max(0, outputs.indexOf(sourcePort));
|
|
119
|
-
let position = {
|
|
120
|
-
x: source.position.x + FLOW_LEVEL_GAP,
|
|
121
|
-
y: source.position.y + (portIndex - (outputs.length - 1) / 2) * FLOW_SIBLING_GAP
|
|
122
|
-
};
|
|
123
|
-
while (isPositionTaken(graph, position)) position = { x: position.x, y: position.y + FLOW_SIBLING_GAP };
|
|
124
|
-
const node = {
|
|
125
|
-
nodeId: generateUUID(),
|
|
126
|
-
type,
|
|
127
|
-
typeVersion: definition.typeVersion,
|
|
128
|
-
config: definition.createDefaultConfig(),
|
|
129
|
-
position
|
|
130
|
-
};
|
|
131
|
-
const nodes = [...graph.nodes, node];
|
|
132
|
-
const wires = canConnect({ ...graph, nodes }, currentRegistry, { source: sourceNodeId, sourcePort, target: node.nodeId });
|
|
133
|
-
const edges = wires ? [...graph.edges, { edgeId: generateUUID(), source: sourceNodeId, sourcePort, target: node.nodeId }] : graph.edges;
|
|
134
|
-
commit({ nodes, edges }, { nodeIds: [node.nodeId], edgeIds: [] });
|
|
135
|
-
return node.nodeId;
|
|
136
|
-
},
|
|
137
|
-
updateNodeConfig: (nodeId, config) => {
|
|
138
|
-
const { graph } = get();
|
|
139
|
-
commit(
|
|
140
|
-
{ ...graph, nodes: graph.nodes.map((node) => node.nodeId === nodeId ? { ...node, config } : node) },
|
|
141
|
-
void 0,
|
|
142
|
-
`config:${nodeId}`
|
|
143
|
-
);
|
|
144
|
-
},
|
|
145
|
-
updateNodeLabel: (nodeId, label) => {
|
|
146
|
-
const { graph } = get();
|
|
147
|
-
const hasText = label.trim().length > 0;
|
|
148
|
-
commit(
|
|
149
|
-
{
|
|
150
|
-
...graph,
|
|
151
|
-
nodes: graph.nodes.map(
|
|
152
|
-
(node) => node.nodeId === nodeId ? { ...node, label: hasText ? label : void 0 } : node
|
|
153
|
-
)
|
|
154
|
-
},
|
|
155
|
-
void 0,
|
|
156
|
-
`label:${nodeId}`
|
|
157
|
-
);
|
|
158
|
-
},
|
|
159
|
-
moveNodes: (positions) => {
|
|
160
|
-
const { graph } = get();
|
|
161
|
-
set({
|
|
162
|
-
graph: {
|
|
163
|
-
...graph,
|
|
164
|
-
nodes: graph.nodes.map(
|
|
165
|
-
(node) => positions[node.nodeId] ? { ...node, position: positions[node.nodeId] } : node
|
|
166
|
-
)
|
|
167
|
-
},
|
|
168
|
-
isDirty: true
|
|
169
|
-
});
|
|
170
|
-
},
|
|
171
|
-
connect: (source, sourcePort, target) => {
|
|
172
|
-
const { graph } = get();
|
|
173
|
-
if (!canConnect(graph, currentRegistry, { source, sourcePort, target })) return false;
|
|
174
|
-
const edge = { edgeId: generateUUID(), source, sourcePort, target };
|
|
175
|
-
commit({ ...graph, edges: [...graph.edges, edge] });
|
|
176
|
-
return true;
|
|
177
|
-
},
|
|
178
|
-
removeEdges: (edgeIds) => {
|
|
179
|
-
if (edgeIds.length === 0) return;
|
|
180
|
-
const { graph } = get();
|
|
181
|
-
const removed = new Set(edgeIds);
|
|
182
|
-
commit({ ...graph, edges: graph.edges.filter((edge) => !removed.has(edge.edgeId)) }, { edgeIds: [] });
|
|
183
|
-
},
|
|
184
|
-
removeSelected: () => {
|
|
185
|
-
const { graph, selectedNodeIds, selectedEdgeIds } = get();
|
|
186
|
-
if (selectedNodeIds.length === 0 && selectedEdgeIds.length === 0) return;
|
|
187
|
-
const removedEdges = new Set(selectedEdgeIds);
|
|
188
|
-
const next = withoutNodes(
|
|
189
|
-
{ ...graph, edges: graph.edges.filter((edge) => !removedEdges.has(edge.edgeId)) },
|
|
190
|
-
new Set(selectedNodeIds)
|
|
191
|
-
);
|
|
192
|
-
commit(next, { nodeIds: [], edgeIds: [] });
|
|
193
|
-
},
|
|
194
|
-
duplicateSelected: () => {
|
|
195
|
-
const { graph, selectedNodeIds } = get();
|
|
196
|
-
const selected = graph.nodes.filter((node) => selectedNodeIds.includes(node.nodeId));
|
|
197
|
-
const copies = cloneNodes(selected, PASTE_OFFSET);
|
|
198
|
-
if (copies.length === 0) return;
|
|
199
|
-
commit(
|
|
200
|
-
{ ...graph, nodes: [...graph.nodes, ...copies] },
|
|
201
|
-
{ nodeIds: copies.map((node) => node.nodeId), edgeIds: [] }
|
|
202
|
-
);
|
|
203
|
-
},
|
|
204
|
-
copySelected: () => {
|
|
205
|
-
const { graph, selectedNodeIds } = get();
|
|
206
|
-
set({ clipboard: graph.nodes.filter((node) => selectedNodeIds.includes(node.nodeId)) });
|
|
207
|
-
},
|
|
208
|
-
paste: () => {
|
|
209
|
-
const { graph, clipboard } = get();
|
|
210
|
-
const copies = cloneNodes(clipboard, PASTE_OFFSET);
|
|
211
|
-
if (copies.length === 0) return;
|
|
212
|
-
commit(
|
|
213
|
-
{ ...graph, nodes: [...graph.nodes, ...copies] },
|
|
214
|
-
{ nodeIds: copies.map((node) => node.nodeId), edgeIds: [] }
|
|
215
|
-
);
|
|
216
|
-
set({ clipboard: copies });
|
|
217
|
-
},
|
|
218
|
-
setSelection: ({ nodeIds, edgeIds }) => {
|
|
219
|
-
set((state) => ({
|
|
220
|
-
selectedNodeIds: nodeIds ?? state.selectedNodeIds,
|
|
221
|
-
selectedEdgeIds: edgeIds ?? state.selectedEdgeIds
|
|
222
|
-
}));
|
|
223
|
-
},
|
|
224
|
-
undo: () => {
|
|
225
|
-
const { graph, past, future } = get();
|
|
226
|
-
const previous = past[past.length - 1];
|
|
227
|
-
if (!previous) return;
|
|
228
|
-
set({
|
|
229
|
-
graph: previous,
|
|
230
|
-
past: past.slice(0, -1),
|
|
231
|
-
future: [graph, ...future].slice(0, HISTORY_LIMIT),
|
|
232
|
-
selectedNodeIds: [],
|
|
233
|
-
selectedEdgeIds: [],
|
|
234
|
-
isDirty: true
|
|
235
|
-
});
|
|
236
|
-
},
|
|
237
|
-
redo: () => {
|
|
238
|
-
const { graph, past, future } = get();
|
|
239
|
-
const following = future[0];
|
|
240
|
-
if (!following) return;
|
|
241
|
-
set({
|
|
242
|
-
graph: following,
|
|
243
|
-
past: [...past, graph].slice(-HISTORY_LIMIT),
|
|
244
|
-
future: future.slice(1),
|
|
245
|
-
selectedNodeIds: [],
|
|
246
|
-
selectedEdgeIds: [],
|
|
247
|
-
isDirty: true
|
|
248
|
-
});
|
|
249
|
-
},
|
|
250
|
-
setRegistry: (nextRegistry) => {
|
|
251
|
-
currentRegistry = nextRegistry;
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
});
|
|
255
|
-
export {
|
|
256
|
-
createFlowEditorStore
|
|
257
|
-
};
|
|
258
|
-
//# sourceMappingURL=flowEditorStore.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"flowEditorStore.js","sources":["../../../../src/components/UI/Flow/flowEditorStore.ts"],"sourcesContent":["import { createStore, type StoreApi } from 'zustand/vanilla';\n\nimport { generateUUID } from '@/helpers/utilityHelpers';\n\nimport type { IFlowBlockRegistry } from './flowBlockRegistry';\nimport { canConnect, FLOW_LEVEL_GAP, FLOW_SIBLING_GAP, layoutFlowByLevels } from './flowGraph';\nimport type { IFlowEdge, IFlowGraph, IFlowNode, IFlowPosition, TFlowPortId } from './flowTypes';\n\nconst HISTORY_LIMIT = 50;\nconst PASTE_OFFSET = 32;\n/** Edits to the same field within this window share one undo step (typing a name is one change). */\nconst COALESCE_WINDOW_MS = 1000;\n\nexport interface IFlowEditorState {\n graph: IFlowGraph;\n selectedNodeIds: Array<string>;\n selectedEdgeIds: Array<string>;\n past: Array<IFlowGraph>;\n future: Array<IFlowGraph>;\n clipboard: Array<IFlowNode>;\n isDirty: boolean;\n\n replaceGraph: (graph: IFlowGraph, options?: { keepHistory?: boolean }) => void;\n markClean: () => void;\n /** Snapshot before a gesture that changes the graph in many small steps (a drag). */\n recordHistory: () => void;\n addNode: (type: string, position?: IFlowPosition) => string | null;\n /** Adds a block right of `sourceNodeId` and wires `sourcePort` to it in one undo step. */\n addNodeFromPort: (type: string, sourceNodeId: string, sourcePort: TFlowPortId) => string | null;\n updateNodeConfig: (nodeId: string, config: unknown) => void;\n updateNodeLabel: (nodeId: string, label: string) => void;\n moveNodes: (positions: Record<string, IFlowPosition>) => void;\n connect: (source: string, sourcePort: TFlowPortId, target: string) => boolean;\n removeEdges: (edgeIds: Array<string>) => void;\n removeSelected: () => void;\n duplicateSelected: () => void;\n copySelected: () => void;\n paste: () => void;\n setSelection: (selection: { nodeIds?: Array<string>; edgeIds?: Array<string> }) => void;\n undo: () => void;\n redo: () => void;\n /** The host may rebuild its registry (labels after a language switch); gestures always read the latest. */\n setRegistry: (registry: IFlowBlockRegistry) => void;\n}\n\nexport type TFlowEditorStore = StoreApi<IFlowEditorState>;\n\nconst isPosition = (position: unknown): position is IFlowPosition =>\n typeof (position as IFlowPosition | undefined)?.x === 'number' &&\n typeof (position as IFlowPosition | undefined)?.y === 'number';\n\n/** Coerces a stored graph; a block saved without a position is placed by the automatic layout. */\nconst normalizeGraph = (graph: IFlowGraph | null | undefined, registry: IFlowBlockRegistry): IFlowGraph => {\n const normalized: IFlowGraph = {\n nodes: Array.isArray(graph?.nodes) ? graph.nodes : [],\n edges: Array.isArray(graph?.edges) ? graph.edges : [],\n };\n if (normalized.nodes.every((node) => isPosition(node.position))) return normalized;\n\n const layout = layoutFlowByLevels(normalized, registry);\n return {\n ...normalized,\n nodes: normalized.nodes.map((node) =>\n isPosition(node.position) ? node : { ...node, position: layout[node.nodeId] ?? { x: 0, y: 0 } },\n ),\n };\n};\n\nconst withoutNodes = (graph: IFlowGraph, nodeIds: Set<string>): IFlowGraph => ({\n nodes: graph.nodes.filter((node) => !nodeIds.has(node.nodeId)),\n edges: graph.edges.filter((edge) => !nodeIds.has(edge.source) && !nodeIds.has(edge.target)),\n});\n\nconst isPositionTaken = (graph: IFlowGraph, position: IFlowPosition): boolean =>\n graph.nodes.some(\n (node) =>\n Math.abs(node.position.x - position.x) < FLOW_LEVEL_GAP &&\n Math.abs(node.position.y - position.y) < FLOW_SIBLING_GAP,\n );\n\n/** Right of the selected block (or the rightmost one), stepping down the column until the slot is free. */\nconst nextFreePosition = (graph: IFlowGraph, selectedNodeIds: Array<string>): IFlowPosition => {\n if (graph.nodes.length === 0) return { x: 0, y: 0 };\n const anchor =\n graph.nodes.find((node) => node.nodeId === selectedNodeIds[0]) ??\n graph.nodes.reduce((current, node) => (node.position.x > current.position.x ? node : current));\n let candidate = { x: anchor.position.x + FLOW_LEVEL_GAP, y: anchor.position.y };\n while (isPositionTaken(graph, candidate)) candidate = { x: candidate.x, y: candidate.y + FLOW_SIBLING_GAP };\n return candidate;\n};\n\n/**\n * One store per editor instance (two canvases on a page never share state). The graph\n * is the source of truth; the canvas only renders it and reports gestures back.\n */\nexport const createFlowEditorStore = (initialGraph: IFlowGraph, registry: IFlowBlockRegistry): TFlowEditorStore =>\n createStore<IFlowEditorState>()((set, get) => {\n let currentRegistry = registry;\n let lastCoalesce: { key: string; at: number } | null = null;\n\n const commit = (\n next: IFlowGraph,\n selection?: { nodeIds?: Array<string>; edgeIds?: Array<string> },\n coalesceKey?: string,\n ) => {\n const { graph, past } = get();\n const now = Date.now();\n const extendsLastStep =\n coalesceKey !== undefined && lastCoalesce?.key === coalesceKey && now - lastCoalesce.at < COALESCE_WINDOW_MS;\n lastCoalesce = coalesceKey === undefined ? null : { key: coalesceKey, at: now };\n set({\n graph: next,\n past: extendsLastStep ? past : [...past, graph].slice(-HISTORY_LIMIT),\n future: [],\n isDirty: true,\n ...(selection?.nodeIds ? { selectedNodeIds: selection.nodeIds } : {}),\n ...(selection?.edgeIds ? { selectedEdgeIds: selection.edgeIds } : {}),\n });\n };\n\n const cloneNodes = (nodes: Array<IFlowNode>, offset: number): Array<IFlowNode> => {\n const instancesByType = new Map<string, number>();\n get().graph.nodes.forEach((node) => instancesByType.set(node.type, (instancesByType.get(node.type) ?? 0) + 1));\n return nodes\n .filter((node) => {\n const definition = currentRegistry.get(node.type);\n if (!definition || definition.category === 'trigger') return false;\n const instances = instancesByType.get(node.type) ?? 0;\n if (definition.maxInstances !== undefined && instances >= definition.maxInstances) return false;\n instancesByType.set(node.type, instances + 1);\n return true;\n })\n .map((node) => ({\n ...node,\n nodeId: generateUUID(),\n config: structuredClone(node.config),\n position: { x: node.position.x + offset, y: node.position.y + offset },\n }));\n };\n\n return {\n graph: normalizeGraph(initialGraph, registry),\n selectedNodeIds: [],\n selectedEdgeIds: [],\n past: [],\n future: [],\n clipboard: [],\n isDirty: false,\n\n replaceGraph: (graph, options) => {\n set((state) => ({\n graph: normalizeGraph(graph, currentRegistry),\n past: options?.keepHistory ? [...state.past, state.graph].slice(-HISTORY_LIMIT) : [],\n future: [],\n selectedNodeIds: [],\n selectedEdgeIds: [],\n isDirty: Boolean(options?.keepHistory),\n }));\n },\n\n markClean: () => set({ isDirty: false }),\n\n recordHistory: () => {\n const { graph, past } = get();\n set({ past: [...past, graph].slice(-HISTORY_LIMIT), future: [] });\n },\n\n addNode: (type, position) => {\n const definition = currentRegistry.get(type);\n if (!definition) return null;\n\n const { graph, selectedNodeIds } = get();\n const instances = graph.nodes.filter((node) => node.type === type).length;\n if (definition.maxInstances !== undefined && instances >= definition.maxInstances) return null;\n\n const node: IFlowNode = {\n nodeId: generateUUID(),\n type,\n typeVersion: definition.typeVersion,\n config: definition.createDefaultConfig(),\n position: position ?? nextFreePosition(graph, selectedNodeIds),\n };\n commit({ ...graph, nodes: [...graph.nodes, node] }, { nodeIds: [node.nodeId], edgeIds: [] });\n return node.nodeId;\n },\n\n addNodeFromPort: (type, sourceNodeId, sourcePort) => {\n const definition = currentRegistry.get(type);\n const { graph } = get();\n const source = graph.nodes.find((node) => node.nodeId === sourceNodeId);\n if (!definition || !source) return null;\n\n const instances = graph.nodes.filter((node) => node.type === type).length;\n if (definition.maxInstances !== undefined && instances >= definition.maxInstances) return null;\n if (graph.edges.some((edge) => edge.source === sourceNodeId && edge.sourcePort === sourcePort)) return null;\n\n const outputs = currentRegistry.get(source.type)?.outputs ?? [];\n const portIndex = Math.max(0, outputs.indexOf(sourcePort));\n let position = {\n x: source.position.x + FLOW_LEVEL_GAP,\n y: source.position.y + (portIndex - (outputs.length - 1) / 2) * FLOW_SIBLING_GAP,\n };\n while (isPositionTaken(graph, position)) position = { x: position.x, y: position.y + FLOW_SIBLING_GAP };\n\n const node: IFlowNode = {\n nodeId: generateUUID(),\n type,\n typeVersion: definition.typeVersion,\n config: definition.createDefaultConfig(),\n position,\n };\n const nodes = [...graph.nodes, node];\n const wires = canConnect({ ...graph, nodes }, currentRegistry, { source: sourceNodeId, sourcePort, target: node.nodeId });\n const edges = wires\n ? [...graph.edges, { edgeId: generateUUID(), source: sourceNodeId, sourcePort, target: node.nodeId }]\n : graph.edges;\n commit({ nodes, edges }, { nodeIds: [node.nodeId], edgeIds: [] });\n return node.nodeId;\n },\n\n updateNodeConfig: (nodeId, config) => {\n const { graph } = get();\n commit(\n { ...graph, nodes: graph.nodes.map((node) => (node.nodeId === nodeId ? { ...node, config } : node)) },\n undefined,\n `config:${nodeId}`,\n );\n },\n\n updateNodeLabel: (nodeId, label) => {\n const { graph } = get();\n const hasText = label.trim().length > 0;\n commit(\n {\n ...graph,\n nodes: graph.nodes.map((node) =>\n node.nodeId === nodeId ? { ...node, label: hasText ? label : undefined } : node,\n ),\n },\n undefined,\n `label:${nodeId}`,\n );\n },\n\n moveNodes: (positions) => {\n const { graph } = get();\n set({\n graph: {\n ...graph,\n nodes: graph.nodes.map((node) =>\n positions[node.nodeId] ? { ...node, position: positions[node.nodeId] } : node,\n ),\n },\n isDirty: true,\n });\n },\n\n connect: (source, sourcePort, target) => {\n const { graph } = get();\n if (!canConnect(graph, currentRegistry, { source, sourcePort, target })) return false;\n const edge: IFlowEdge = { edgeId: generateUUID(), source, sourcePort, target };\n commit({ ...graph, edges: [...graph.edges, edge] });\n return true;\n },\n\n removeEdges: (edgeIds) => {\n if (edgeIds.length === 0) return;\n const { graph } = get();\n const removed = new Set(edgeIds);\n commit({ ...graph, edges: graph.edges.filter((edge) => !removed.has(edge.edgeId)) }, { edgeIds: [] });\n },\n\n removeSelected: () => {\n const { graph, selectedNodeIds, selectedEdgeIds } = get();\n if (selectedNodeIds.length === 0 && selectedEdgeIds.length === 0) return;\n const removedEdges = new Set(selectedEdgeIds);\n const next = withoutNodes(\n { ...graph, edges: graph.edges.filter((edge) => !removedEdges.has(edge.edgeId)) },\n new Set(selectedNodeIds),\n );\n commit(next, { nodeIds: [], edgeIds: [] });\n },\n\n duplicateSelected: () => {\n const { graph, selectedNodeIds } = get();\n const selected = graph.nodes.filter((node) => selectedNodeIds.includes(node.nodeId));\n const copies = cloneNodes(selected, PASTE_OFFSET);\n if (copies.length === 0) return;\n commit(\n { ...graph, nodes: [...graph.nodes, ...copies] },\n { nodeIds: copies.map((node) => node.nodeId), edgeIds: [] },\n );\n },\n\n copySelected: () => {\n const { graph, selectedNodeIds } = get();\n set({ clipboard: graph.nodes.filter((node) => selectedNodeIds.includes(node.nodeId)) });\n },\n\n paste: () => {\n const { graph, clipboard } = get();\n const copies = cloneNodes(clipboard, PASTE_OFFSET);\n if (copies.length === 0) return;\n commit(\n { ...graph, nodes: [...graph.nodes, ...copies] },\n { nodeIds: copies.map((node) => node.nodeId), edgeIds: [] },\n );\n set({ clipboard: copies });\n },\n\n setSelection: ({ nodeIds, edgeIds }) => {\n set((state) => ({\n selectedNodeIds: nodeIds ?? state.selectedNodeIds,\n selectedEdgeIds: edgeIds ?? state.selectedEdgeIds,\n }));\n },\n\n undo: () => {\n const { graph, past, future } = get();\n const previous = past[past.length - 1];\n if (!previous) return;\n set({\n graph: previous,\n past: past.slice(0, -1),\n future: [graph, ...future].slice(0, HISTORY_LIMIT),\n selectedNodeIds: [],\n selectedEdgeIds: [],\n isDirty: true,\n });\n },\n\n redo: () => {\n const { graph, past, future } = get();\n const following = future[0];\n if (!following) return;\n set({\n graph: following,\n past: [...past, graph].slice(-HISTORY_LIMIT),\n future: future.slice(1),\n selectedNodeIds: [],\n selectedEdgeIds: [],\n isDirty: true,\n });\n },\n\n setRegistry: (nextRegistry) => {\n currentRegistry = nextRegistry;\n },\n };\n });\n"],"names":["node"],"mappings":"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"}
|