@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,263 +0,0 @@
|
|
|
1
|
-
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo, useState, useCallback } from "react";
|
|
3
|
-
import { useTranslation } from "react-i18next";
|
|
4
|
-
import { twMerge } from "tailwind-merge";
|
|
5
|
-
import { useReactFlow, MarkerType, ReactFlow, Background, BackgroundVariant, MiniMap } from "@xyflow/react";
|
|
6
|
-
import FlowBlockNode, { FLOW_INPUT_HANDLE_ID } from "./FlowBlockNode.js";
|
|
7
|
-
import FlowConnectionEdge from "./FlowConnectionEdge.js";
|
|
8
|
-
import { FLOW_BLOCK_DRAG_TYPE } from "./flowConstants.js";
|
|
9
|
-
import { useFlowBlockRegistry, useFlowEditorStore, useFlowEditor } from "./FlowEditorProvider.js";
|
|
10
|
-
import { canConnect, layoutFlowByLevels } from "./flowGraph.js";
|
|
11
|
-
import FlowZoomControls from "./FlowZoomControls.js";
|
|
12
|
-
import { useFlowLabels } from "./useFlowLabels.js";
|
|
13
|
-
import "@xyflow/react/dist/base.css";
|
|
14
|
-
/* empty css */
|
|
15
|
-
const ATTRIBUTION_POSITION = "bottom-left";
|
|
16
|
-
const NODE_TYPES = { flowBlock: FlowBlockNode };
|
|
17
|
-
const EDGE_TYPES = { flowConnection: FlowConnectionEdge };
|
|
18
|
-
const MINIMAP_SIZE = { width: 160, height: 104 };
|
|
19
|
-
const MINIMAP_MIN_NODES = 5;
|
|
20
|
-
const SNAP_GRID = [16, 16];
|
|
21
|
-
const PORTS = ["next", "true", "false", "error"];
|
|
22
|
-
const isPortId = (value) => PORTS.includes(value);
|
|
23
|
-
const worstSeverity = (issues) => {
|
|
24
|
-
if (issues.some((issue) => issue.severity === "error")) return "error";
|
|
25
|
-
if (issues.some((issue) => issue.severity === "warning")) return "warning";
|
|
26
|
-
return void 0;
|
|
27
|
-
};
|
|
28
|
-
const isTypingTarget = (target) => target instanceof HTMLElement && (target.isContentEditable || ["INPUT", "TEXTAREA", "SELECT"].includes(target.tagName));
|
|
29
|
-
const FlowCanvas = ({
|
|
30
|
-
issues = [],
|
|
31
|
-
runOverlay,
|
|
32
|
-
readOnly = false,
|
|
33
|
-
ariaLabel,
|
|
34
|
-
className,
|
|
35
|
-
controlsPosition,
|
|
36
|
-
children
|
|
37
|
-
}) => {
|
|
38
|
-
const { t } = useTranslation("ui");
|
|
39
|
-
const labels = useFlowLabels();
|
|
40
|
-
const registry = useFlowBlockRegistry();
|
|
41
|
-
const store = useFlowEditorStore();
|
|
42
|
-
const { screenToFlowPosition } = useReactFlow();
|
|
43
|
-
const graph = useFlowEditor((state) => state.graph);
|
|
44
|
-
const selectedNodeIds = useFlowEditor((state) => state.selectedNodeIds);
|
|
45
|
-
const selectedEdgeIds = useFlowEditor((state) => state.selectedEdgeIds);
|
|
46
|
-
const canUndo = useFlowEditor((state) => state.past.length > 0);
|
|
47
|
-
const canRedo = useFlowEditor((state) => state.future.length > 0);
|
|
48
|
-
const safeIssues = useMemo(() => Array.isArray(issues) ? issues : [], [issues]);
|
|
49
|
-
const [measuredSizes, setMeasuredSizes] = useState({});
|
|
50
|
-
const addableBlocks = useMemo(() => {
|
|
51
|
-
if (readOnly) return [];
|
|
52
|
-
return registry.list.filter((definition) => definition.category !== "trigger").filter(
|
|
53
|
-
(definition) => definition.maxInstances === void 0 || graph.nodes.filter((node) => node.type === definition.type).length < definition.maxInstances
|
|
54
|
-
).map((definition) => ({ type: definition.type, label: definition.label, icon: definition.icon }));
|
|
55
|
-
}, [registry, graph.nodes, readOnly]);
|
|
56
|
-
const nodes = useMemo(
|
|
57
|
-
() => graph.nodes.map((node) => {
|
|
58
|
-
const definition = registry.get(node.type);
|
|
59
|
-
const nodeIssues = safeIssues.filter((issue) => issue.nodeId === node.nodeId);
|
|
60
|
-
return {
|
|
61
|
-
id: node.nodeId,
|
|
62
|
-
type: "flowBlock",
|
|
63
|
-
position: node.position,
|
|
64
|
-
measured: measuredSizes[node.nodeId],
|
|
65
|
-
selected: selectedNodeIds.includes(node.nodeId),
|
|
66
|
-
draggable: !readOnly,
|
|
67
|
-
connectable: !readOnly,
|
|
68
|
-
data: {
|
|
69
|
-
icon: definition?.resolveIcon?.(node.config) ?? definition?.icon ?? "help",
|
|
70
|
-
title: node.label ?? definition?.resolveLabel?.(node.config) ?? definition?.label ?? node.type,
|
|
71
|
-
categoryLabel: definition ? labels.categories[definition.category] : "",
|
|
72
|
-
summary: definition ? definition.renderSummary(node.config) : labels.issueMessage({ code: "unknownBlock", severity: "error" }),
|
|
73
|
-
hasInput: definition ? definition.category !== "trigger" : true,
|
|
74
|
-
outputs: definition?.outputs ?? [],
|
|
75
|
-
connectedPorts: graph.edges.filter((edge) => edge.source === node.nodeId).map((edge) => edge.sourcePort),
|
|
76
|
-
portLabels: labels.ports,
|
|
77
|
-
addableBlocks,
|
|
78
|
-
onAddFromPort: readOnly ? void 0 : (port, type) => store.getState().addNodeFromPort(type, node.nodeId, port),
|
|
79
|
-
disabled: node.disabled,
|
|
80
|
-
issueSeverity: worstSeverity(nodeIssues),
|
|
81
|
-
issueCount: nodeIssues.length,
|
|
82
|
-
runStatus: runOverlay?.steps?.[node.nodeId]
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
}),
|
|
86
|
-
[
|
|
87
|
-
graph.nodes,
|
|
88
|
-
graph.edges,
|
|
89
|
-
registry,
|
|
90
|
-
safeIssues,
|
|
91
|
-
selectedNodeIds,
|
|
92
|
-
readOnly,
|
|
93
|
-
labels,
|
|
94
|
-
runOverlay,
|
|
95
|
-
measuredSizes,
|
|
96
|
-
addableBlocks,
|
|
97
|
-
store
|
|
98
|
-
]
|
|
99
|
-
);
|
|
100
|
-
const edges = useMemo(() => {
|
|
101
|
-
const traversed = new Set(runOverlay?.traversedEdgeIds ?? []);
|
|
102
|
-
return graph.edges.map((edge) => ({
|
|
103
|
-
id: edge.edgeId,
|
|
104
|
-
type: "flowConnection",
|
|
105
|
-
source: edge.source,
|
|
106
|
-
sourceHandle: edge.sourcePort,
|
|
107
|
-
target: edge.target,
|
|
108
|
-
targetHandle: FLOW_INPUT_HANDLE_ID,
|
|
109
|
-
selected: selectedEdgeIds.includes(edge.edgeId),
|
|
110
|
-
markerEnd: { type: MarkerType.ArrowClosed, width: 16, height: 16 },
|
|
111
|
-
data: {
|
|
112
|
-
label: edge.sourcePort === "next" ? void 0 : labels.ports[edge.sourcePort],
|
|
113
|
-
traversed: traversed.has(edge.edgeId),
|
|
114
|
-
issueSeverity: worstSeverity(safeIssues.filter((issue) => issue.edgeId === edge.edgeId))
|
|
115
|
-
}
|
|
116
|
-
}));
|
|
117
|
-
}, [graph.edges, selectedEdgeIds, runOverlay, labels, safeIssues]);
|
|
118
|
-
const handleNodesChange = useCallback(
|
|
119
|
-
(changes) => {
|
|
120
|
-
const positions = {};
|
|
121
|
-
const sizes = {};
|
|
122
|
-
let selection = null;
|
|
123
|
-
for (const change of changes) {
|
|
124
|
-
if (change.type === "dimensions" && change.dimensions) sizes[change.id] = change.dimensions;
|
|
125
|
-
if (change.type === "position" && change.position && !readOnly) positions[change.id] = change.position;
|
|
126
|
-
if (change.type === "select") {
|
|
127
|
-
const current = selection ?? store.getState().selectedNodeIds;
|
|
128
|
-
selection = change.selected ? [.../* @__PURE__ */ new Set([...current, change.id])] : current.filter((id) => id !== change.id);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
if (Object.keys(sizes).length > 0) setMeasuredSizes((current) => ({ ...current, ...sizes }));
|
|
132
|
-
if (Object.keys(positions).length > 0) store.getState().moveNodes(positions);
|
|
133
|
-
if (selection) store.getState().setSelection({ nodeIds: selection });
|
|
134
|
-
},
|
|
135
|
-
[store, readOnly]
|
|
136
|
-
);
|
|
137
|
-
const handleEdgesChange = useCallback(
|
|
138
|
-
(changes) => {
|
|
139
|
-
let selection = null;
|
|
140
|
-
for (const change of changes) {
|
|
141
|
-
if (change.type !== "select") continue;
|
|
142
|
-
const current = selection ?? store.getState().selectedEdgeIds;
|
|
143
|
-
selection = change.selected ? [.../* @__PURE__ */ new Set([...current, change.id])] : current.filter((id) => id !== change.id);
|
|
144
|
-
}
|
|
145
|
-
if (selection) store.getState().setSelection({ edgeIds: selection });
|
|
146
|
-
},
|
|
147
|
-
[store]
|
|
148
|
-
);
|
|
149
|
-
const handleConnect = useCallback(
|
|
150
|
-
(connection) => {
|
|
151
|
-
if (readOnly || !isPortId(connection.sourceHandle)) return;
|
|
152
|
-
store.getState().connect(connection.source, connection.sourceHandle, connection.target);
|
|
153
|
-
},
|
|
154
|
-
[store, readOnly]
|
|
155
|
-
);
|
|
156
|
-
const isValidConnection = useCallback(
|
|
157
|
-
(connection) => isPortId(connection.sourceHandle) && canConnect(store.getState().graph, registry, {
|
|
158
|
-
source: connection.source,
|
|
159
|
-
sourcePort: connection.sourceHandle,
|
|
160
|
-
target: connection.target
|
|
161
|
-
}),
|
|
162
|
-
[store, registry]
|
|
163
|
-
);
|
|
164
|
-
const handleDragOver = useCallback((event) => {
|
|
165
|
-
if (!event.dataTransfer.types.includes(FLOW_BLOCK_DRAG_TYPE)) return;
|
|
166
|
-
event.preventDefault();
|
|
167
|
-
event.dataTransfer.dropEffect = "copy";
|
|
168
|
-
}, []);
|
|
169
|
-
const handleDrop = useCallback(
|
|
170
|
-
(event) => {
|
|
171
|
-
const blockType = event.dataTransfer.getData(FLOW_BLOCK_DRAG_TYPE);
|
|
172
|
-
if (!blockType || readOnly) return;
|
|
173
|
-
event.preventDefault();
|
|
174
|
-
store.getState().addNode(blockType, screenToFlowPosition({ x: event.clientX, y: event.clientY }));
|
|
175
|
-
},
|
|
176
|
-
[store, readOnly, screenToFlowPosition]
|
|
177
|
-
);
|
|
178
|
-
const handleKeyDown = useCallback(
|
|
179
|
-
(event) => {
|
|
180
|
-
if (readOnly || isTypingTarget(event.target)) return;
|
|
181
|
-
const state = store.getState();
|
|
182
|
-
const withModifier = event.metaKey || event.ctrlKey;
|
|
183
|
-
const key = event.key.toLowerCase();
|
|
184
|
-
if (event.key === "Delete" || event.key === "Backspace") state.removeSelected();
|
|
185
|
-
else if (withModifier && key === "z" && event.shiftKey) state.redo();
|
|
186
|
-
else if (withModifier && key === "z") state.undo();
|
|
187
|
-
else if (withModifier && key === "y") state.redo();
|
|
188
|
-
else if (withModifier && key === "c") state.copySelected();
|
|
189
|
-
else if (withModifier && key === "v") state.paste();
|
|
190
|
-
else if (withModifier && key === "d") state.duplicateSelected();
|
|
191
|
-
else return;
|
|
192
|
-
event.preventDefault();
|
|
193
|
-
},
|
|
194
|
-
[store, readOnly]
|
|
195
|
-
);
|
|
196
|
-
const handleLayout = useCallback(() => {
|
|
197
|
-
const state = store.getState();
|
|
198
|
-
state.recordHistory();
|
|
199
|
-
state.moveNodes(layoutFlowByLevels(state.graph, registry));
|
|
200
|
-
}, [store, registry]);
|
|
201
|
-
return /* @__PURE__ */ jsxs(
|
|
202
|
-
"div",
|
|
203
|
-
{
|
|
204
|
-
role: "region",
|
|
205
|
-
"aria-label": ariaLabel ?? t("flow.canvas"),
|
|
206
|
-
tabIndex: 0,
|
|
207
|
-
onKeyDown: handleKeyDown,
|
|
208
|
-
className: twMerge(
|
|
209
|
-
"umode-flow relative h-full min-h-[24rem] w-full rounded-xl bg-canvas focus:outline-none",
|
|
210
|
-
className
|
|
211
|
-
),
|
|
212
|
-
children: [
|
|
213
|
-
/* @__PURE__ */ jsxs(
|
|
214
|
-
ReactFlow,
|
|
215
|
-
{
|
|
216
|
-
nodes,
|
|
217
|
-
edges,
|
|
218
|
-
nodeTypes: NODE_TYPES,
|
|
219
|
-
edgeTypes: EDGE_TYPES,
|
|
220
|
-
onNodesChange: handleNodesChange,
|
|
221
|
-
onEdgesChange: handleEdgesChange,
|
|
222
|
-
onConnect: handleConnect,
|
|
223
|
-
isValidConnection,
|
|
224
|
-
onNodeDragStart: () => store.getState().recordHistory(),
|
|
225
|
-
onPaneClick: () => store.getState().setSelection({ nodeIds: [], edgeIds: [] }),
|
|
226
|
-
onDragOver: handleDragOver,
|
|
227
|
-
onDrop: handleDrop,
|
|
228
|
-
deleteKeyCode: null,
|
|
229
|
-
nodesDraggable: !readOnly,
|
|
230
|
-
nodesConnectable: !readOnly,
|
|
231
|
-
snapToGrid: true,
|
|
232
|
-
snapGrid: SNAP_GRID,
|
|
233
|
-
minZoom: 0.25,
|
|
234
|
-
maxZoom: 2,
|
|
235
|
-
fitView: true,
|
|
236
|
-
fitViewOptions: { padding: 0.2, maxZoom: 1 },
|
|
237
|
-
attributionPosition: ATTRIBUTION_POSITION,
|
|
238
|
-
children: [
|
|
239
|
-
/* @__PURE__ */ jsx(Background, { variant: BackgroundVariant.Dots, gap: 16, size: 1 }),
|
|
240
|
-
nodes.length >= MINIMAP_MIN_NODES && /* @__PURE__ */ jsx(MiniMap, { pannable: true, zoomable: true, position: "bottom-right", style: MINIMAP_SIZE }),
|
|
241
|
-
/* @__PURE__ */ jsx(
|
|
242
|
-
FlowZoomControls,
|
|
243
|
-
{
|
|
244
|
-
onUndo: readOnly ? void 0 : () => store.getState().undo(),
|
|
245
|
-
onRedo: readOnly ? void 0 : () => store.getState().redo(),
|
|
246
|
-
canUndo,
|
|
247
|
-
canRedo,
|
|
248
|
-
onLayout: readOnly ? void 0 : handleLayout,
|
|
249
|
-
position: controlsPosition
|
|
250
|
-
}
|
|
251
|
-
)
|
|
252
|
-
]
|
|
253
|
-
}
|
|
254
|
-
),
|
|
255
|
-
children
|
|
256
|
-
]
|
|
257
|
-
}
|
|
258
|
-
);
|
|
259
|
-
};
|
|
260
|
-
export {
|
|
261
|
-
FlowCanvas as default
|
|
262
|
-
};
|
|
263
|
-
//# sourceMappingURL=FlowCanvas.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FlowCanvas.js","sources":["../../../../src/components/UI/Flow/FlowCanvas.tsx"],"sourcesContent":["import { DragEvent, KeyboardEvent, ReactNode, useCallback, useMemo, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { twMerge } from 'tailwind-merge';\n\nimport {\n Background,\n BackgroundVariant,\n type Connection,\n type EdgeChange,\n MarkerType,\n MiniMap,\n type NodeChange,\n type PanelPosition,\n ReactFlow,\n useReactFlow,\n} from '@xyflow/react';\n\nimport FlowBlockNode, { FLOW_INPUT_HANDLE_ID, type IFlowAddableBlock, type TFlowBlockNode } from './FlowBlockNode';\nimport FlowConnectionEdge, { type TFlowConnectionEdge } from './FlowConnectionEdge';\nimport { FLOW_BLOCK_DRAG_TYPE } from './flowConstants';\nimport { useFlowBlockRegistry, useFlowEditor, useFlowEditorStore } from './FlowEditorProvider';\nimport { canConnect, layoutFlowByLevels } from './flowGraph';\nimport type { IFlowIssue, IFlowPosition, IFlowRunOverlay, TFlowIssueSeverity, TFlowPortId } from './flowTypes';\nimport FlowZoomControls from './FlowZoomControls';\nimport { useFlowLabels } from './useFlowLabels';\n\nimport '@xyflow/react/dist/base.css';\nimport '../../../styles/flow.css';\n\nconst ATTRIBUTION_POSITION: PanelPosition = 'bottom-left';\nconst NODE_TYPES = { flowBlock: FlowBlockNode };\nconst EDGE_TYPES = { flowConnection: FlowConnectionEdge };\nconst MINIMAP_SIZE = { width: 160, height: 104 };\n/** A small flow fits on screen whole, where a minimap only covers blocks. */\nconst MINIMAP_MIN_NODES = 5;\nconst SNAP_GRID: [number, number] = [16, 16];\nconst PORTS: Array<TFlowPortId> = ['next', 'true', 'false', 'error'];\n\nexport interface IFlowCanvasProps {\n issues?: Array<IFlowIssue>;\n runOverlay?: IFlowRunOverlay | null;\n readOnly?: boolean;\n /** Accessible name of the canvas region, from `t()`. Defaults to the library label. */\n ariaLabel?: string;\n className?: string;\n /** Where the zoom and history controls sit; move them when floating chrome takes that corner. */\n controlsPosition?: PanelPosition;\n /** Floating chrome drawn over the canvas (toolbars, panels); position it with absolute classes. */\n children?: ReactNode;\n}\n\nconst isPortId = (value: string | null | undefined): value is TFlowPortId => PORTS.includes(value as TFlowPortId);\n\nconst worstSeverity = (issues: Array<IFlowIssue>): TFlowIssueSeverity | undefined => {\n if (issues.some((issue) => issue.severity === 'error')) return 'error';\n if (issues.some((issue) => issue.severity === 'warning')) return 'warning';\n return undefined;\n};\n\nconst isTypingTarget = (target: EventTarget | null): boolean =>\n target instanceof HTMLElement &&\n (target.isContentEditable || ['INPUT', 'TEXTAREA', 'SELECT'].includes(target.tagName));\n\n/**\n * The editing surface. Renders the graph held by `FlowEditorProvider` and turns every\n * gesture (drag, connect, select, delete, drop, shortcuts) into a store action, so undo\n * and the dirty flag see all of them.\n */\nconst FlowCanvas: React.FC<IFlowCanvasProps> = ({\n issues = [],\n runOverlay,\n readOnly = false,\n ariaLabel,\n className,\n controlsPosition,\n children,\n}) => {\n const { t } = useTranslation('ui');\n const labels = useFlowLabels();\n const registry = useFlowBlockRegistry();\n const store = useFlowEditorStore();\n const { screenToFlowPosition } = useReactFlow();\n\n const graph = useFlowEditor((state) => state.graph);\n const selectedNodeIds = useFlowEditor((state) => state.selectedNodeIds);\n const selectedEdgeIds = useFlowEditor((state) => state.selectedEdgeIds);\n const canUndo = useFlowEditor((state) => state.past.length > 0);\n const canRedo = useFlowEditor((state) => state.future.length > 0);\n\n const safeIssues = useMemo(() => (Array.isArray(issues) ? issues : []), [issues]);\n // Measured sizes are view state, not part of the flow: kept here so the minimap can draw the blocks.\n const [measuredSizes, setMeasuredSizes] = useState<Record<string, { width: number; height: number }>>({});\n\n const addableBlocks = useMemo<Array<IFlowAddableBlock>>(() => {\n if (readOnly) return [];\n return registry.list\n .filter((definition) => definition.category !== 'trigger')\n .filter(\n (definition) =>\n definition.maxInstances === undefined ||\n graph.nodes.filter((node) => node.type === definition.type).length < definition.maxInstances,\n )\n .map((definition) => ({ type: definition.type, label: definition.label, icon: definition.icon }));\n }, [registry, graph.nodes, readOnly]);\n\n const nodes = useMemo<Array<TFlowBlockNode>>(\n () =>\n graph.nodes.map((node) => {\n const definition = registry.get(node.type);\n const nodeIssues = safeIssues.filter((issue) => issue.nodeId === node.nodeId);\n return {\n id: node.nodeId,\n type: 'flowBlock',\n position: node.position,\n measured: measuredSizes[node.nodeId],\n selected: selectedNodeIds.includes(node.nodeId),\n draggable: !readOnly,\n connectable: !readOnly,\n data: {\n icon: definition?.resolveIcon?.(node.config) ?? definition?.icon ?? 'help',\n title: node.label ?? definition?.resolveLabel?.(node.config) ?? definition?.label ?? node.type,\n categoryLabel: definition ? labels.categories[definition.category] : '',\n summary: definition\n ? definition.renderSummary(node.config)\n : labels.issueMessage({ code: 'unknownBlock', severity: 'error' }),\n hasInput: definition ? definition.category !== 'trigger' : true,\n outputs: definition?.outputs ?? [],\n connectedPorts: graph.edges.filter((edge) => edge.source === node.nodeId).map((edge) => edge.sourcePort),\n portLabels: labels.ports,\n addableBlocks,\n onAddFromPort: readOnly\n ? undefined\n : (port, type) => store.getState().addNodeFromPort(type, node.nodeId, port),\n disabled: node.disabled,\n issueSeverity: worstSeverity(nodeIssues),\n issueCount: nodeIssues.length,\n runStatus: runOverlay?.steps?.[node.nodeId],\n },\n };\n }),\n [\n graph.nodes,\n graph.edges,\n registry,\n safeIssues,\n selectedNodeIds,\n readOnly,\n labels,\n runOverlay,\n measuredSizes,\n addableBlocks,\n store,\n ],\n );\n\n const edges = useMemo<Array<TFlowConnectionEdge>>(() => {\n const traversed = new Set(runOverlay?.traversedEdgeIds ?? []);\n return graph.edges.map((edge) => ({\n id: edge.edgeId,\n type: 'flowConnection',\n source: edge.source,\n sourceHandle: edge.sourcePort,\n target: edge.target,\n targetHandle: FLOW_INPUT_HANDLE_ID,\n selected: selectedEdgeIds.includes(edge.edgeId),\n markerEnd: { type: MarkerType.ArrowClosed, width: 16, height: 16 },\n data: {\n label: edge.sourcePort === 'next' ? undefined : labels.ports[edge.sourcePort],\n traversed: traversed.has(edge.edgeId),\n issueSeverity: worstSeverity(safeIssues.filter((issue) => issue.edgeId === edge.edgeId)),\n },\n }));\n }, [graph.edges, selectedEdgeIds, runOverlay, labels, safeIssues]);\n\n const handleNodesChange = useCallback(\n (changes: Array<NodeChange<TFlowBlockNode>>) => {\n const positions: Record<string, IFlowPosition> = {};\n const sizes: Record<string, { width: number; height: number }> = {};\n let selection: Array<string> | null = null;\n for (const change of changes) {\n if (change.type === 'dimensions' && change.dimensions) sizes[change.id] = change.dimensions;\n if (change.type === 'position' && change.position && !readOnly) positions[change.id] = change.position;\n if (change.type === 'select') {\n const current: Array<string> = selection ?? store.getState().selectedNodeIds;\n selection = change.selected\n ? [...new Set([...current, change.id])]\n : current.filter((id) => id !== change.id);\n }\n }\n if (Object.keys(sizes).length > 0) setMeasuredSizes((current) => ({ ...current, ...sizes }));\n if (Object.keys(positions).length > 0) store.getState().moveNodes(positions);\n if (selection) store.getState().setSelection({ nodeIds: selection });\n },\n [store, readOnly],\n );\n\n const handleEdgesChange = useCallback(\n (changes: Array<EdgeChange<TFlowConnectionEdge>>) => {\n let selection: Array<string> | null = null;\n for (const change of changes) {\n if (change.type !== 'select') continue;\n const current: Array<string> = selection ?? store.getState().selectedEdgeIds;\n selection = change.selected ? [...new Set([...current, change.id])] : current.filter((id) => id !== change.id);\n }\n if (selection) store.getState().setSelection({ edgeIds: selection });\n },\n [store],\n );\n\n const handleConnect = useCallback(\n (connection: Connection) => {\n if (readOnly || !isPortId(connection.sourceHandle)) return;\n store.getState().connect(connection.source, connection.sourceHandle, connection.target);\n },\n [store, readOnly],\n );\n\n const isValidConnection = useCallback(\n (connection: Connection | TFlowConnectionEdge) =>\n isPortId(connection.sourceHandle) &&\n canConnect(store.getState().graph, registry, {\n source: connection.source,\n sourcePort: connection.sourceHandle,\n target: connection.target,\n }),\n [store, registry],\n );\n\n const handleDragOver = useCallback((event: DragEvent<HTMLDivElement>) => {\n if (!event.dataTransfer.types.includes(FLOW_BLOCK_DRAG_TYPE)) return;\n event.preventDefault();\n event.dataTransfer.dropEffect = 'copy';\n }, []);\n\n const handleDrop = useCallback(\n (event: DragEvent<HTMLDivElement>) => {\n const blockType = event.dataTransfer.getData(FLOW_BLOCK_DRAG_TYPE);\n if (!blockType || readOnly) return;\n event.preventDefault();\n store.getState().addNode(blockType, screenToFlowPosition({ x: event.clientX, y: event.clientY }));\n },\n [store, readOnly, screenToFlowPosition],\n );\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLDivElement>) => {\n if (readOnly || isTypingTarget(event.target)) return;\n const state = store.getState();\n const withModifier = event.metaKey || event.ctrlKey;\n const key = event.key.toLowerCase();\n\n if (event.key === 'Delete' || event.key === 'Backspace') state.removeSelected();\n else if (withModifier && key === 'z' && event.shiftKey) state.redo();\n else if (withModifier && key === 'z') state.undo();\n else if (withModifier && key === 'y') state.redo();\n else if (withModifier && key === 'c') state.copySelected();\n else if (withModifier && key === 'v') state.paste();\n else if (withModifier && key === 'd') state.duplicateSelected();\n else return;\n event.preventDefault();\n },\n [store, readOnly],\n );\n\n const handleLayout = useCallback(() => {\n const state = store.getState();\n state.recordHistory();\n state.moveNodes(layoutFlowByLevels(state.graph, registry));\n }, [store, registry]);\n\n return (\n <div\n role=\"region\"\n aria-label={ariaLabel ?? t('flow.canvas')}\n tabIndex={0}\n onKeyDown={handleKeyDown}\n className={twMerge(\n 'umode-flow relative h-full min-h-[24rem] w-full rounded-xl bg-canvas focus:outline-none',\n className,\n )}\n >\n <ReactFlow<TFlowBlockNode, TFlowConnectionEdge>\n nodes={nodes}\n edges={edges}\n nodeTypes={NODE_TYPES}\n edgeTypes={EDGE_TYPES}\n onNodesChange={handleNodesChange}\n onEdgesChange={handleEdgesChange}\n onConnect={handleConnect}\n isValidConnection={isValidConnection}\n onNodeDragStart={() => store.getState().recordHistory()}\n onPaneClick={() => store.getState().setSelection({ nodeIds: [], edgeIds: [] })}\n onDragOver={handleDragOver}\n onDrop={handleDrop}\n deleteKeyCode={null}\n nodesDraggable={!readOnly}\n nodesConnectable={!readOnly}\n snapToGrid\n snapGrid={SNAP_GRID}\n minZoom={0.25}\n maxZoom={2}\n fitView\n fitViewOptions={{ padding: 0.2, maxZoom: 1 }}\n attributionPosition={ATTRIBUTION_POSITION}\n >\n <Background variant={BackgroundVariant.Dots} gap={16} size={1} />\n {nodes.length >= MINIMAP_MIN_NODES && (\n <MiniMap pannable zoomable position=\"bottom-right\" style={MINIMAP_SIZE} />\n )}\n <FlowZoomControls\n onUndo={readOnly ? undefined : () => store.getState().undo()}\n onRedo={readOnly ? undefined : () => store.getState().redo()}\n canUndo={canUndo}\n canRedo={canRedo}\n onLayout={readOnly ? undefined : handleLayout}\n position={controlsPosition}\n />\n </ReactFlow>\n {children}\n </div>\n );\n};\n\nexport default FlowCanvas;\n"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { Edge, EdgeProps } from '@xyflow/react';
|
|
2
|
-
import { TFlowIssueSeverity } from './flowTypes';
|
|
3
|
-
export interface IFlowConnectionEdgeData extends Record<string, unknown> {
|
|
4
|
-
label?: string;
|
|
5
|
-
traversed?: boolean;
|
|
6
|
-
issueSeverity?: TFlowIssueSeverity;
|
|
7
|
-
}
|
|
8
|
-
export type TFlowConnectionEdge = Edge<IFlowConnectionEdgeData, 'flowConnection'>;
|
|
9
|
-
declare const FlowConnectionEdge: React.FC<EdgeProps<TFlowConnectionEdge>>;
|
|
10
|
-
export default FlowConnectionEdge;
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { jsxs, Fragment, jsx } from "react/jsx-runtime";
|
|
2
|
-
import classNames from "classnames";
|
|
3
|
-
import { getSmoothStepPath, BaseEdge, EdgeLabelRenderer } from "@xyflow/react";
|
|
4
|
-
import OutlinedChip from "../Chips/OutlinedChip.js";
|
|
5
|
-
const FlowConnectionEdge = ({
|
|
6
|
-
id,
|
|
7
|
-
sourceX,
|
|
8
|
-
sourceY,
|
|
9
|
-
targetX,
|
|
10
|
-
targetY,
|
|
11
|
-
sourcePosition,
|
|
12
|
-
targetPosition,
|
|
13
|
-
data,
|
|
14
|
-
markerEnd,
|
|
15
|
-
selected
|
|
16
|
-
}) => {
|
|
17
|
-
const [path, labelX, labelY] = getSmoothStepPath({
|
|
18
|
-
sourceX,
|
|
19
|
-
sourceY,
|
|
20
|
-
targetX,
|
|
21
|
-
targetY,
|
|
22
|
-
sourcePosition,
|
|
23
|
-
targetPosition,
|
|
24
|
-
borderRadius: 12
|
|
25
|
-
});
|
|
26
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
27
|
-
/* @__PURE__ */ jsx(
|
|
28
|
-
BaseEdge,
|
|
29
|
-
{
|
|
30
|
-
id,
|
|
31
|
-
path,
|
|
32
|
-
markerEnd,
|
|
33
|
-
className: classNames("umode-flow-edge-path", {
|
|
34
|
-
"umode-flow-edge-path--selected": selected,
|
|
35
|
-
"umode-flow-edge-path--traversed": data?.traversed,
|
|
36
|
-
"umode-flow-edge-path--error": data?.issueSeverity === "error",
|
|
37
|
-
"umode-flow-edge-path--warning": data?.issueSeverity === "warning"
|
|
38
|
-
})
|
|
39
|
-
}
|
|
40
|
-
),
|
|
41
|
-
data?.label && /* @__PURE__ */ jsx(EdgeLabelRenderer, { children: /* @__PURE__ */ jsx(
|
|
42
|
-
"div",
|
|
43
|
-
{
|
|
44
|
-
className: "nodrag nopan pointer-events-none absolute",
|
|
45
|
-
style: { transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)` },
|
|
46
|
-
children: /* @__PURE__ */ jsx(OutlinedChip, { label: data.label, tone: "neutral", variant: "outlined", size: "xs", preserveLabelCase: true })
|
|
47
|
-
}
|
|
48
|
-
) })
|
|
49
|
-
] });
|
|
50
|
-
};
|
|
51
|
-
export {
|
|
52
|
-
FlowConnectionEdge as default
|
|
53
|
-
};
|
|
54
|
-
//# sourceMappingURL=FlowConnectionEdge.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FlowConnectionEdge.js","sources":["../../../../src/components/UI/Flow/FlowConnectionEdge.tsx"],"sourcesContent":["import classNames from 'classnames';\n\nimport { BaseEdge, type Edge, EdgeLabelRenderer, type EdgeProps, getSmoothStepPath } from '@xyflow/react';\n\nimport OutlinedChip from '../Chips/OutlinedChip';\n\nimport type { TFlowIssueSeverity } from './flowTypes';\n\nexport interface IFlowConnectionEdgeData extends Record<string, unknown> {\n label?: string;\n traversed?: boolean;\n issueSeverity?: TFlowIssueSeverity;\n}\n\nexport type TFlowConnectionEdge = Edge<IFlowConnectionEdgeData, 'flowConnection'>;\n\nconst FlowConnectionEdge: React.FC<EdgeProps<TFlowConnectionEdge>> = ({\n id,\n sourceX,\n sourceY,\n targetX,\n targetY,\n sourcePosition,\n targetPosition,\n data,\n markerEnd,\n selected,\n}) => {\n const [path, labelX, labelY] = getSmoothStepPath({\n sourceX,\n sourceY,\n targetX,\n targetY,\n sourcePosition,\n targetPosition,\n borderRadius: 12,\n });\n\n return (\n <>\n <BaseEdge\n id={id}\n path={path}\n markerEnd={markerEnd}\n className={classNames('umode-flow-edge-path', {\n 'umode-flow-edge-path--selected': selected,\n 'umode-flow-edge-path--traversed': data?.traversed,\n 'umode-flow-edge-path--error': data?.issueSeverity === 'error',\n 'umode-flow-edge-path--warning': data?.issueSeverity === 'warning',\n })}\n />\n {data?.label && (\n <EdgeLabelRenderer>\n <div\n className=\"nodrag nopan pointer-events-none absolute\"\n style={{ transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)` }}\n >\n <OutlinedChip label={data.label} tone=\"neutral\" variant=\"outlined\" size=\"xs\" preserveLabelCase />\n </div>\n </EdgeLabelRenderer>\n )}\n </>\n );\n};\n\nexport default FlowConnectionEdge;\n"],"names":[],"mappings":";;;;AAgBA,MAAM,qBAA+D,CAAC;AAAA,EACpE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,CAAC,MAAM,QAAQ,MAAM,IAAI,kBAAkB;AAAA,IAC/C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,EAAA,CACf;AAED,SACE,qBAAA,UAAA,EACE,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA,WAAW,WAAW,wBAAwB;AAAA,UAC5C,kCAAkC;AAAA,UAClC,mCAAmC,MAAM;AAAA,UACzC,+BAA+B,MAAM,kBAAkB;AAAA,UACvD,iCAAiC,MAAM,kBAAkB;AAAA,QAAA,CAC1D;AAAA,MAAA;AAAA,IAAA;AAAA,IAEF,MAAM,SACL,oBAAC,mBAAA,EACC,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAU;AAAA,QACV,OAAO,EAAE,WAAW,mCAAmC,MAAM,OAAO,MAAM,MAAA;AAAA,QAE1E,UAAA,oBAAC,cAAA,EAAa,OAAO,KAAK,OAAO,MAAK,WAAU,SAAQ,YAAW,MAAK,MAAK,mBAAiB,KAAA,CAAC;AAAA,MAAA;AAAA,IAAA,EACjG,CACF;AAAA,EAAA,GAEJ;AAEJ;"}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { ReactNode } from 'react';
|
|
2
|
-
import { IFlowBlockRegistry } from './flowBlockRegistry';
|
|
3
|
-
import { IFlowEditorState, TFlowEditorStore } from './flowEditorStore';
|
|
4
|
-
import { IFlowGraph } from './flowTypes';
|
|
5
|
-
interface IFlowEditorProviderProps {
|
|
6
|
-
/** Read once, when the provider mounts. Load a different flow with `replaceGraph` or a new `key`. */
|
|
7
|
-
initialGraph: IFlowGraph;
|
|
8
|
-
registry: IFlowBlockRegistry;
|
|
9
|
-
children: ReactNode;
|
|
10
|
-
}
|
|
11
|
-
/** Scopes one editor: its store, its block registry and the canvas viewport. */
|
|
12
|
-
declare const FlowEditorProvider: React.FC<IFlowEditorProviderProps>;
|
|
13
|
-
export declare const useFlowEditor: <TSelected>(selector: (state: IFlowEditorState) => TSelected) => TSelected;
|
|
14
|
-
/** The store itself, for reading state inside event handlers without subscribing. */
|
|
15
|
-
export declare const useFlowEditorStore: () => TFlowEditorStore;
|
|
16
|
-
export declare const useFlowBlockRegistry: () => IFlowBlockRegistry;
|
|
17
|
-
export default FlowEditorProvider;
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useState, useMemo, useEffect, useContext } from "react";
|
|
3
|
-
import { useStore } from "zustand";
|
|
4
|
-
import { ReactFlowProvider } from "@xyflow/react";
|
|
5
|
-
import { createFlowEditorStore } from "./flowEditorStore.js";
|
|
6
|
-
const FlowEditorContext = createContext(null);
|
|
7
|
-
const FlowEditorProvider = ({ initialGraph, registry, children }) => {
|
|
8
|
-
const [store] = useState(() => createFlowEditorStore(initialGraph, registry));
|
|
9
|
-
const contextValue = useMemo(() => ({ store, registry }), [store, registry]);
|
|
10
|
-
useEffect(() => store.getState().setRegistry(registry), [store, registry]);
|
|
11
|
-
return /* @__PURE__ */ jsx(FlowEditorContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(ReactFlowProvider, { children }) });
|
|
12
|
-
};
|
|
13
|
-
const useFlowEditorContext = () => {
|
|
14
|
-
const context = useContext(FlowEditorContext);
|
|
15
|
-
if (!context) throw new Error("Flow editor components must be rendered inside FlowEditorProvider");
|
|
16
|
-
return context;
|
|
17
|
-
};
|
|
18
|
-
const useFlowEditor = (selector) => useStore(useFlowEditorContext().store, selector);
|
|
19
|
-
const useFlowEditorStore = () => useFlowEditorContext().store;
|
|
20
|
-
const useFlowBlockRegistry = () => useFlowEditorContext().registry;
|
|
21
|
-
export {
|
|
22
|
-
FlowEditorProvider as default,
|
|
23
|
-
useFlowBlockRegistry,
|
|
24
|
-
useFlowEditor,
|
|
25
|
-
useFlowEditorStore
|
|
26
|
-
};
|
|
27
|
-
//# sourceMappingURL=FlowEditorProvider.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FlowEditorProvider.js","sources":["../../../../src/components/UI/Flow/FlowEditorProvider.tsx"],"sourcesContent":["import { createContext, ReactNode, useContext, useEffect, useMemo, useState } from 'react';\nimport { useStore } from 'zustand';\n\nimport { ReactFlowProvider } from '@xyflow/react';\n\nimport type { IFlowBlockRegistry } from './flowBlockRegistry';\nimport { createFlowEditorStore, IFlowEditorState, TFlowEditorStore } from './flowEditorStore';\nimport type { IFlowGraph } from './flowTypes';\n\ninterface IFlowEditorContextValue {\n store: TFlowEditorStore;\n registry: IFlowBlockRegistry;\n}\n\nconst FlowEditorContext = createContext<IFlowEditorContextValue | null>(null);\n\ninterface IFlowEditorProviderProps {\n /** Read once, when the provider mounts. Load a different flow with `replaceGraph` or a new `key`. */\n initialGraph: IFlowGraph;\n registry: IFlowBlockRegistry;\n children: ReactNode;\n}\n\n/** Scopes one editor: its store, its block registry and the canvas viewport. */\nconst FlowEditorProvider: React.FC<IFlowEditorProviderProps> = ({ initialGraph, registry, children }) => {\n const [store] = useState(() => createFlowEditorStore(initialGraph, registry));\n const contextValue = useMemo(() => ({ store, registry }), [store, registry]);\n\n useEffect(() => store.getState().setRegistry(registry), [store, registry]);\n\n return (\n <FlowEditorContext.Provider value={contextValue}>\n <ReactFlowProvider>{children}</ReactFlowProvider>\n </FlowEditorContext.Provider>\n );\n};\n\nconst useFlowEditorContext = (): IFlowEditorContextValue => {\n const context = useContext(FlowEditorContext);\n if (!context) throw new Error('Flow editor components must be rendered inside FlowEditorProvider');\n return context;\n};\n\nexport const useFlowEditor = <TSelected,>(selector: (state: IFlowEditorState) => TSelected): TSelected =>\n useStore(useFlowEditorContext().store, selector);\n\n/** The store itself, for reading state inside event handlers without subscribing. */\nexport const useFlowEditorStore = (): TFlowEditorStore => useFlowEditorContext().store;\n\nexport const useFlowBlockRegistry = (): IFlowBlockRegistry => useFlowEditorContext().registry;\n\nexport default FlowEditorProvider;\n"],"names":[],"mappings":";;;;;AAcA,MAAM,oBAAoB,cAA8C,IAAI;AAU5E,MAAM,qBAAyD,CAAC,EAAE,cAAc,UAAU,eAAe;AACvG,QAAM,CAAC,KAAK,IAAI,SAAS,MAAM,sBAAsB,cAAc,QAAQ,CAAC;AAC5E,QAAM,eAAe,QAAQ,OAAO,EAAE,OAAO,aAAa,CAAC,OAAO,QAAQ,CAAC;AAE3E,YAAU,MAAM,MAAM,WAAW,YAAY,QAAQ,GAAG,CAAC,OAAO,QAAQ,CAAC;AAEzE,SACE,oBAAC,kBAAkB,UAAlB,EAA2B,OAAO,cACjC,UAAA,oBAAC,mBAAA,EAAmB,SAAA,CAAS,EAAA,CAC/B;AAEJ;AAEA,MAAM,uBAAuB,MAA+B;AAC1D,QAAM,UAAU,WAAW,iBAAiB;AAC5C,MAAI,CAAC,QAAS,OAAM,IAAI,MAAM,mEAAmE;AACjG,SAAO;AACT;AAEO,MAAM,gBAAgB,CAAa,aACxC,SAAS,qBAAA,EAAuB,OAAO,QAAQ;AAG1C,MAAM,qBAAqB,MAAwB,uBAAuB;AAE1E,MAAM,uBAAuB,MAA0B,uBAAuB;"}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { IFlowIssue } from './flowTypes';
|
|
2
|
-
interface IFlowInspectorProps {
|
|
3
|
-
issues?: Array<IFlowIssue>;
|
|
4
|
-
readOnly?: boolean;
|
|
5
|
-
/** A floating panel opens with the selection instead of showing an empty state. */
|
|
6
|
-
hideWhenEmpty?: boolean;
|
|
7
|
-
className?: string;
|
|
8
|
-
}
|
|
9
|
-
/** The side panel of the selected block: its name, its own settings panel and its issues. */
|
|
10
|
-
declare const FlowInspector: React.FC<IFlowInspectorProps>;
|
|
11
|
-
export default FlowInspector;
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useTranslation } from "react-i18next";
|
|
3
|
-
import { twMerge } from "tailwind-merge";
|
|
4
|
-
import ControlledButton from "../../Form/ControlledButton.js";
|
|
5
|
-
import ControlledInput from "../../Form/ControlledInput.js";
|
|
6
|
-
import ControlledTooltip from "../ControlledTooltip.js";
|
|
7
|
-
import { MaterialSymbol } from "../MaterialSymbol/index.js";
|
|
8
|
-
import NoDataFound from "../NoDataFound/index.js";
|
|
9
|
-
import { useFlowBlockRegistry, useFlowEditorStore, useFlowEditor } from "./FlowEditorProvider.js";
|
|
10
|
-
import { useFlowLabels } from "./useFlowLabels.js";
|
|
11
|
-
const FlowInspector = ({
|
|
12
|
-
issues = [],
|
|
13
|
-
readOnly = false,
|
|
14
|
-
hideWhenEmpty = false,
|
|
15
|
-
className
|
|
16
|
-
}) => {
|
|
17
|
-
const { t } = useTranslation("ui");
|
|
18
|
-
const labels = useFlowLabels();
|
|
19
|
-
const registry = useFlowBlockRegistry();
|
|
20
|
-
const store = useFlowEditorStore();
|
|
21
|
-
const selectedNodeIds = useFlowEditor((state) => state.selectedNodeIds);
|
|
22
|
-
const selectedNode = useFlowEditor(
|
|
23
|
-
(state) => state.selectedNodeIds.length === 1 ? state.graph.nodes.find((node) => node.nodeId === state.selectedNodeIds[0]) : void 0
|
|
24
|
-
);
|
|
25
|
-
const shellClass = twMerge(
|
|
26
|
-
"flex h-full min-h-0 flex-col gap-y-3 rounded-xl border border-border bg-surface p-4",
|
|
27
|
-
className
|
|
28
|
-
);
|
|
29
|
-
if (!selectedNode || selectedNodeIds.length !== 1) {
|
|
30
|
-
if (hideWhenEmpty) return null;
|
|
31
|
-
return /* @__PURE__ */ jsx("aside", { className: shellClass, "aria-label": t("flow.inspector.title"), children: /* @__PURE__ */ jsx(NoDataFound, { size: "sm", variant: "waiting", message: t("flow.inspector.empty") }) });
|
|
32
|
-
}
|
|
33
|
-
const definition = registry.get(selectedNode.type);
|
|
34
|
-
const nodeIssues = (Array.isArray(issues) ? issues : []).filter((issue) => issue.nodeId === selectedNode.nodeId);
|
|
35
|
-
const ConfigPanel = definition?.ConfigPanel;
|
|
36
|
-
return /* @__PURE__ */ jsxs("aside", { className: shellClass, "aria-label": t("flow.inspector.title"), children: [
|
|
37
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-2", children: [
|
|
38
|
-
/* @__PURE__ */ jsx("span", { className: "flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground-muted", children: /* @__PURE__ */ jsx(
|
|
39
|
-
MaterialSymbol,
|
|
40
|
-
{
|
|
41
|
-
icon: definition?.resolveIcon?.(selectedNode.config) ?? definition?.icon ?? "help",
|
|
42
|
-
size: 18,
|
|
43
|
-
"aria-hidden": true
|
|
44
|
-
}
|
|
45
|
-
) }),
|
|
46
|
-
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col", children: [
|
|
47
|
-
definition && /* @__PURE__ */ jsx("span", { className: "text-xxs font-semibold uppercase tracking-wide text-foreground-subtle", children: labels.categories[definition.category] }),
|
|
48
|
-
/* @__PURE__ */ jsx("span", { className: "truncate text-sm font-semibold text-foreground", children: definition?.resolveLabel?.(selectedNode.config) ?? definition?.label ?? selectedNode.type })
|
|
49
|
-
] }),
|
|
50
|
-
/* @__PURE__ */ jsx(ControlledTooltip, { content: t("flow.inspector.close"), placement: "left", children: /* @__PURE__ */ jsx(
|
|
51
|
-
ControlledButton,
|
|
52
|
-
{
|
|
53
|
-
defaultHtmlButton: true,
|
|
54
|
-
onClick: () => store.getState().setSelection({ nodeIds: [], edgeIds: [] }),
|
|
55
|
-
className: "interactive ml-auto flex size-8 shrink-0 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",
|
|
56
|
-
children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: "close", size: 18, "aria-hidden": true })
|
|
57
|
-
}
|
|
58
|
-
) })
|
|
59
|
-
] }),
|
|
60
|
-
/* @__PURE__ */ jsx(
|
|
61
|
-
ControlledInput,
|
|
62
|
-
{
|
|
63
|
-
label: t("flow.inspector.label"),
|
|
64
|
-
value: selectedNode.label ?? "",
|
|
65
|
-
disabled: readOnly,
|
|
66
|
-
onChange: (event) => store.getState().updateNodeLabel(selectedNode.nodeId, event.target.value)
|
|
67
|
-
}
|
|
68
|
-
),
|
|
69
|
-
/* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-y-3 overflow-y-auto", children: [
|
|
70
|
-
ConfigPanel ? /* @__PURE__ */ jsx(
|
|
71
|
-
ConfigPanel,
|
|
72
|
-
{
|
|
73
|
-
config: selectedNode.config,
|
|
74
|
-
readOnly,
|
|
75
|
-
onChange: (next) => store.getState().updateNodeConfig(selectedNode.nodeId, next)
|
|
76
|
-
}
|
|
77
|
-
) : /* @__PURE__ */ jsx(NoDataFound, { compact: true, variant: "error", message: t("flow.inspector.unknownBlock") }),
|
|
78
|
-
nodeIssues.length > 0 && /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-y-1", children: nodeIssues.map((issue) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-x-2", children: [
|
|
79
|
-
/* @__PURE__ */ jsx(
|
|
80
|
-
MaterialSymbol,
|
|
81
|
-
{
|
|
82
|
-
icon: issue.severity === "error" ? "error" : "warning",
|
|
83
|
-
size: 16,
|
|
84
|
-
className: issue.severity === "error" ? "text-danger-emphasis" : "text-warning-emphasis",
|
|
85
|
-
"aria-hidden": true
|
|
86
|
-
}
|
|
87
|
-
),
|
|
88
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs text-foreground-muted", children: labels.issueMessage(issue) })
|
|
89
|
-
] }, `${issue.code}|${issue.edgeId ?? ""}`)) })
|
|
90
|
-
] }),
|
|
91
|
-
!readOnly && /* @__PURE__ */ jsxs("div", { className: "flex gap-x-2", children: [
|
|
92
|
-
definition?.category !== "trigger" && /* @__PURE__ */ jsxs(
|
|
93
|
-
ControlledButton,
|
|
94
|
-
{
|
|
95
|
-
size: "sm",
|
|
96
|
-
variant: "outlined",
|
|
97
|
-
tone: "neutral",
|
|
98
|
-
onClick: () => store.getState().duplicateSelected(),
|
|
99
|
-
children: [
|
|
100
|
-
/* @__PURE__ */ jsx(MaterialSymbol, { icon: "content_copy", size: 16, "aria-hidden": true }),
|
|
101
|
-
t("flow.inspector.duplicate")
|
|
102
|
-
]
|
|
103
|
-
}
|
|
104
|
-
),
|
|
105
|
-
/* @__PURE__ */ jsxs(
|
|
106
|
-
ControlledButton,
|
|
107
|
-
{
|
|
108
|
-
size: "sm",
|
|
109
|
-
variant: "outlined",
|
|
110
|
-
tone: "danger",
|
|
111
|
-
onClick: () => store.getState().removeSelected(),
|
|
112
|
-
children: [
|
|
113
|
-
/* @__PURE__ */ jsx(MaterialSymbol, { icon: "delete", size: 16, "aria-hidden": true }),
|
|
114
|
-
t("flow.inspector.delete")
|
|
115
|
-
]
|
|
116
|
-
}
|
|
117
|
-
)
|
|
118
|
-
] })
|
|
119
|
-
] });
|
|
120
|
-
};
|
|
121
|
-
export {
|
|
122
|
-
FlowInspector as default
|
|
123
|
-
};
|
|
124
|
-
//# sourceMappingURL=FlowInspector.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FlowInspector.js","sources":["../../../../src/components/UI/Flow/FlowInspector.tsx"],"sourcesContent":["import { useTranslation } from 'react-i18next';\nimport { twMerge } from 'tailwind-merge';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledInput from '@/components/Form/ControlledInput';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport NoDataFound from '@/components/UI/NoDataFound';\n\nimport { useFlowBlockRegistry, useFlowEditor, useFlowEditorStore } from './FlowEditorProvider';\nimport type { IFlowIssue } from './flowTypes';\nimport { useFlowLabels } from './useFlowLabels';\n\ninterface IFlowInspectorProps {\n issues?: Array<IFlowIssue>;\n readOnly?: boolean;\n /** A floating panel opens with the selection instead of showing an empty state. */\n hideWhenEmpty?: boolean;\n className?: string;\n}\n\n/** The side panel of the selected block: its name, its own settings panel and its issues. */\nconst FlowInspector: React.FC<IFlowInspectorProps> = ({\n issues = [],\n readOnly = false,\n hideWhenEmpty = false,\n className,\n}) => {\n const { t } = useTranslation('ui');\n const labels = useFlowLabels();\n const registry = useFlowBlockRegistry();\n const store = useFlowEditorStore();\n const selectedNodeIds = useFlowEditor((state) => state.selectedNodeIds);\n const selectedNode = useFlowEditor((state) =>\n state.selectedNodeIds.length === 1\n ? state.graph.nodes.find((node) => node.nodeId === state.selectedNodeIds[0])\n : undefined,\n );\n\n const shellClass = twMerge(\n 'flex h-full min-h-0 flex-col gap-y-3 rounded-xl border border-border bg-surface p-4',\n className,\n );\n\n if (!selectedNode || selectedNodeIds.length !== 1) {\n if (hideWhenEmpty) return null;\n return (\n <aside className={shellClass} aria-label={t('flow.inspector.title')}>\n <NoDataFound size=\"sm\" variant=\"waiting\" message={t('flow.inspector.empty')} />\n </aside>\n );\n }\n\n const definition = registry.get(selectedNode.type);\n const nodeIssues = (Array.isArray(issues) ? issues : []).filter((issue) => issue.nodeId === selectedNode.nodeId);\n const ConfigPanel = definition?.ConfigPanel;\n\n return (\n <aside className={shellClass} aria-label={t('flow.inspector.title')}>\n <div className=\"flex items-center gap-x-2\">\n <span className=\"flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground-muted\">\n <MaterialSymbol\n icon={definition?.resolveIcon?.(selectedNode.config) ?? definition?.icon ?? 'help'}\n size={18}\n aria-hidden\n />\n </span>\n <div className=\"flex min-w-0 flex-col\">\n {definition && (\n <span className=\"text-xxs font-semibold uppercase tracking-wide text-foreground-subtle\">\n {labels.categories[definition.category]}\n </span>\n )}\n <span className=\"truncate text-sm font-semibold text-foreground\">\n {definition?.resolveLabel?.(selectedNode.config) ?? definition?.label ?? selectedNode.type}\n </span>\n </div>\n <ControlledTooltip content={t('flow.inspector.close')} placement=\"left\">\n <ControlledButton\n defaultHtmlButton\n onClick={() => store.getState().setSelection({ nodeIds: [], edgeIds: [] })}\n className=\"interactive ml-auto flex size-8 shrink-0 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\"\n >\n <MaterialSymbol icon=\"close\" size={18} aria-hidden />\n </ControlledButton>\n </ControlledTooltip>\n </div>\n\n <ControlledInput\n label={t('flow.inspector.label')}\n value={selectedNode.label ?? ''}\n disabled={readOnly}\n onChange={(event) => store.getState().updateNodeLabel(selectedNode.nodeId, event.target.value)}\n />\n\n <div className=\"flex min-h-0 flex-1 flex-col gap-y-3 overflow-y-auto\">\n {ConfigPanel ? (\n <ConfigPanel\n config={selectedNode.config}\n readOnly={readOnly}\n onChange={(next) => store.getState().updateNodeConfig(selectedNode.nodeId, next)}\n />\n ) : (\n <NoDataFound compact variant=\"error\" message={t('flow.inspector.unknownBlock')} />\n )}\n\n {nodeIssues.length > 0 && (\n <ul className=\"flex flex-col gap-y-1\">\n {nodeIssues.map((issue) => (\n <li key={`${issue.code}|${issue.edgeId ?? ''}`} className=\"flex items-start gap-x-2\">\n <MaterialSymbol\n icon={issue.severity === 'error' ? 'error' : 'warning'}\n size={16}\n className={issue.severity === 'error' ? 'text-danger-emphasis' : 'text-warning-emphasis'}\n aria-hidden\n />\n <span className=\"text-xs text-foreground-muted\">{labels.issueMessage(issue)}</span>\n </li>\n ))}\n </ul>\n )}\n </div>\n\n {!readOnly && (\n <div className=\"flex gap-x-2\">\n {definition?.category !== 'trigger' && (\n <ControlledButton\n size=\"sm\"\n variant=\"outlined\"\n tone=\"neutral\"\n onClick={() => store.getState().duplicateSelected()}\n >\n <MaterialSymbol icon=\"content_copy\" size={16} aria-hidden />\n {t('flow.inspector.duplicate')}\n </ControlledButton>\n )}\n <ControlledButton\n size=\"sm\"\n variant=\"outlined\"\n tone=\"danger\"\n onClick={() => store.getState().removeSelected()}\n >\n <MaterialSymbol icon=\"delete\" size={16} aria-hidden />\n {t('flow.inspector.delete')}\n </ControlledButton>\n </div>\n )}\n </aside>\n );\n};\n\nexport default FlowInspector;\n"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { IFlowIssue } from './flowTypes';
|
|
2
|
-
interface IFlowIssueListProps {
|
|
3
|
-
issues: Array<IFlowIssue>;
|
|
4
|
-
className?: string;
|
|
5
|
-
}
|
|
6
|
-
/** Every issue of the flow; clicking one selects and centers the block it points to. */
|
|
7
|
-
declare const FlowIssueList: React.FC<IFlowIssueListProps>;
|
|
8
|
-
export default FlowIssueList;
|