@umodeapporg/ui 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/assets/umode-ui.css +0 -5
  2. package/dist/components/Form/ControlledSelect.js +5 -3
  3. package/dist/components/Form/ControlledSelect.js.map +1 -1
  4. package/dist/components/UI/Flow/FlowCanvas.d.ts +4 -5
  5. package/dist/components/UI/Flow/FlowCanvas.js +58 -69
  6. package/dist/components/UI/Flow/FlowCanvas.js.map +1 -1
  7. package/dist/components/UI/Flow/FlowConnectionEdge.d.ts +8 -0
  8. package/dist/components/UI/Flow/FlowConnectionEdge.js +64 -3
  9. package/dist/components/UI/Flow/FlowConnectionEdge.js.map +1 -1
  10. package/dist/components/UI/Flow/FlowInspector.d.ts +1 -1
  11. package/dist/components/UI/Flow/FlowInspector.js.map +1 -1
  12. package/dist/components/UI/Flow/FlowLaneMap.js +2 -2
  13. package/dist/components/UI/Flow/FlowLaneMap.js.map +1 -1
  14. package/dist/components/UI/Flow/FlowResizablePanel.d.ts +1 -1
  15. package/dist/components/UI/Flow/FlowResizablePanel.js.map +1 -1
  16. package/dist/components/UI/Flow/flowBlockRegistry.d.ts +2 -2
  17. package/dist/components/UI/Flow/flowBlockRegistry.js +8 -3
  18. package/dist/components/UI/Flow/flowBlockRegistry.js.map +1 -1
  19. package/dist/components/UI/Flow/flowEditorStore.d.ts +7 -2
  20. package/dist/components/UI/Flow/flowEditorStore.js +56 -13
  21. package/dist/components/UI/Flow/flowEditorStore.js.map +1 -1
  22. package/dist/components/UI/Flow/flowGraph.d.ts +0 -1
  23. package/dist/components/UI/Flow/flowGraph.js +3 -4
  24. package/dist/components/UI/Flow/flowGraph.js.map +1 -1
  25. package/dist/components/UI/Flow/flowTypes.d.ts +2 -4
  26. package/dist/components/UI/Flow/index.d.ts +2 -4
  27. package/dist/components/UI/Flow/index.js +5 -9
  28. package/dist/components/UI/Flow/index.js.map +1 -1
  29. package/dist/locales/en/ui.json.d.ts +3 -6
  30. package/dist/locales/en/ui.json.js +1 -1
  31. package/dist/locales/index.d.ts +6 -12
  32. package/dist/locales/pt/ui.json.d.ts +3 -6
  33. package/dist/locales/pt/ui.json.js +1 -1
  34. package/package.json +1 -1
  35. package/dist/components/UI/Flow/FlowPalette.d.ts +0 -7
  36. package/dist/components/UI/Flow/FlowPalette.js +0 -90
  37. package/dist/components/UI/Flow/FlowPalette.js.map +0 -1
  38. package/dist/components/UI/Flow/flowConstants.d.ts +0 -2
  39. package/dist/components/UI/Flow/flowConstants.js +0 -5
  40. package/dist/components/UI/Flow/flowConstants.js.map +0 -1
@@ -1,6 +1,7 @@
1
1
  import { createStore } from "zustand/vanilla";
2
2
  import { generateUUID } from "../../../helpers/utilityHelpers.js";
3
- import { canConnect, FLOW_SIBLING_GAP, FLOW_LEVEL_GAP, layoutFlowByLevels } from "./flowGraph.js";
3
+ import { flowOutputsOf } from "./flowBlockRegistry.js";
4
+ import { canConnect, reachableFrom, FLOW_LEVEL_GAP, FLOW_SIBLING_GAP, layoutFlowByLevels } from "./flowGraph.js";
4
5
  const HISTORY_LIMIT = 50;
5
6
  const PASTE_OFFSET = 32;
6
7
  const COALESCE_WINDOW_MS = 1e3;
@@ -90,7 +91,7 @@ const createFlowEditorStore = (initialGraph, registry) => createStore()((set, ge
90
91
  const { graph, past } = get();
91
92
  set({ past: [...past, graph].slice(-HISTORY_LIMIT), future: [] });
92
93
  },
93
- addNode: (type, position) => {
94
+ addNode: (type) => {
94
95
  const definition = currentRegistry.get(type);
95
96
  if (!definition) return null;
96
97
  const { graph, selectedNodeIds } = get();
@@ -101,7 +102,7 @@ const createFlowEditorStore = (initialGraph, registry) => createStore()((set, ge
101
102
  type,
102
103
  typeVersion: definition.typeVersion,
103
104
  config: definition.createDefaultConfig(),
104
- position: position ?? nextFreePosition(graph, selectedNodeIds)
105
+ position: nextFreePosition(graph, selectedNodeIds)
105
106
  };
106
107
  commit({ ...graph, nodes: [...graph.nodes, node] }, { nodeIds: [node.nodeId], edgeIds: [] });
107
108
  return node.nodeId;
@@ -114,8 +115,9 @@ const createFlowEditorStore = (initialGraph, registry) => createStore()((set, ge
114
115
  const instances = graph.nodes.filter((node2) => node2.type === type).length;
115
116
  if (definition.maxInstances !== void 0 && instances >= definition.maxInstances) return null;
116
117
  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));
118
+ const outputs = flowOutputsOf(currentRegistry.get(source.type), source.config);
119
+ if (!outputs.includes(sourcePort)) return null;
120
+ const portIndex = outputs.indexOf(sourcePort);
119
121
  let position = {
120
122
  x: source.position.x + FLOW_LEVEL_GAP,
121
123
  y: source.position.y + (portIndex - (outputs.length - 1) / 2) * FLOW_SIBLING_GAP
@@ -129,15 +131,62 @@ const createFlowEditorStore = (initialGraph, registry) => createStore()((set, ge
129
131
  position
130
132
  };
131
133
  const nodes = [...graph.nodes, node];
132
- const wires = canConnect({ ...graph, nodes }, currentRegistry, { source: sourceNodeId, sourcePort, target: node.nodeId });
134
+ const wires = canConnect({ ...graph, nodes }, currentRegistry, {
135
+ source: sourceNodeId,
136
+ sourcePort,
137
+ target: node.nodeId
138
+ });
133
139
  const edges = wires ? [...graph.edges, { edgeId: generateUUID(), source: sourceNodeId, sourcePort, target: node.nodeId }] : graph.edges;
134
140
  commit({ nodes, edges }, { nodeIds: [node.nodeId], edgeIds: [] });
135
141
  return node.nodeId;
136
142
  },
143
+ insertNodeOnEdge: (type, edgeId) => {
144
+ const definition = currentRegistry.get(type);
145
+ const { graph } = get();
146
+ const edge = graph.edges.find((candidate) => candidate.edgeId === edgeId);
147
+ const target = edge ? graph.nodes.find((node2) => node2.nodeId === edge.target) : void 0;
148
+ const config = definition?.createDefaultConfig();
149
+ const continuationPort = definition ? flowOutputsOf(definition, config)[0] : void 0;
150
+ if (!definition || !edge || !target || !continuationPort || definition.category === "trigger") return null;
151
+ const instances = graph.nodes.filter((node2) => node2.type === type).length;
152
+ if (definition.maxInstances !== void 0 && instances >= definition.maxInstances) return null;
153
+ const shifted = reachableFrom(graph, target.nodeId);
154
+ const node = {
155
+ nodeId: generateUUID(),
156
+ type,
157
+ typeVersion: definition.typeVersion,
158
+ config,
159
+ position: { ...target.position }
160
+ };
161
+ const nodes = [
162
+ ...graph.nodes.map(
163
+ (current) => shifted.has(current.nodeId) ? { ...current, position: { x: current.position.x + FLOW_LEVEL_GAP, y: current.position.y } } : current
164
+ ),
165
+ node
166
+ ];
167
+ const withoutEdge = graph.edges.filter((current) => current.edgeId !== edgeId);
168
+ const incoming = { ...edge, target: node.nodeId };
169
+ const outgoing = {
170
+ edgeId: generateUUID(),
171
+ source: node.nodeId,
172
+ sourcePort: continuationPort,
173
+ target: target.nodeId
174
+ };
175
+ const wiresIn = canConnect({ nodes, edges: withoutEdge }, currentRegistry, incoming);
176
+ const wiresOut = canConnect({ nodes, edges: [...withoutEdge, incoming] }, currentRegistry, outgoing);
177
+ if (!wiresIn || !wiresOut) return null;
178
+ commit({ nodes, edges: [...withoutEdge, incoming, outgoing] }, { nodeIds: [node.nodeId], edgeIds: [] });
179
+ return node.nodeId;
180
+ },
137
181
  updateNodeConfig: (nodeId, config) => {
138
182
  const { graph } = get();
183
+ const node = graph.nodes.find((candidate) => candidate.nodeId === nodeId);
184
+ const outputs = node ? flowOutputsOf(currentRegistry.get(node.type), config) : [];
139
185
  commit(
140
- { ...graph, nodes: graph.nodes.map((node) => node.nodeId === nodeId ? { ...node, config } : node) },
186
+ {
187
+ nodes: graph.nodes.map((current) => current.nodeId === nodeId ? { ...current, config } : current),
188
+ edges: graph.edges.filter((edge) => edge.source !== nodeId || outputs.includes(edge.sourcePort))
189
+ },
141
190
  void 0,
142
191
  `config:${nodeId}`
143
192
  );
@@ -175,12 +224,6 @@ const createFlowEditorStore = (initialGraph, registry) => createStore()((set, ge
175
224
  commit({ ...graph, edges: [...graph.edges, edge] });
176
225
  return true;
177
226
  },
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
227
  removeSelected: () => {
185
228
  const { graph, selectedNodeIds, selectedEdgeIds } = get();
186
229
  if (selectedNodeIds.length === 0 && selectedEdgeIds.length === 0) return;
@@ -1 +1 @@
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":";;;AAQA,MAAM,gBAAgB;AACtB,MAAM,eAAe;AAErB,MAAM,qBAAqB;AAoC3B,MAAM,aAAa,CAAC,aAClB,OAAQ,UAAwC,MAAM,YACtD,OAAQ,UAAwC,MAAM;AAGxD,MAAM,iBAAiB,CAAC,OAAsC,aAA6C;AACzG,QAAM,aAAyB;AAAA,IAC7B,OAAO,MAAM,QAAQ,OAAO,KAAK,IAAI,MAAM,QAAQ,CAAA;AAAA,IACnD,OAAO,MAAM,QAAQ,OAAO,KAAK,IAAI,MAAM,QAAQ,CAAA;AAAA,EAAC;AAEtD,MAAI,WAAW,MAAM,MAAM,CAAC,SAAS,WAAW,KAAK,QAAQ,CAAC,EAAG,QAAO;AAExE,QAAM,SAAS,mBAAmB,YAAY,QAAQ;AACtD,SAAO;AAAA,IACL,GAAG;AAAA,IACH,OAAO,WAAW,MAAM;AAAA,MAAI,CAAC,SAC3B,WAAW,KAAK,QAAQ,IAAI,OAAO,EAAE,GAAG,MAAM,UAAU,OAAO,KAAK,MAAM,KAAK,EAAE,GAAG,GAAG,GAAG,IAAE;AAAA,IAAE;AAAA,EAChG;AAEJ;AAEA,MAAM,eAAe,CAAC,OAAmB,aAAsC;AAAA,EAC7E,OAAO,MAAM,MAAM,OAAO,CAAC,SAAS,CAAC,QAAQ,IAAI,KAAK,MAAM,CAAC;AAAA,EAC7D,OAAO,MAAM,MAAM,OAAO,CAAC,SAAS,CAAC,QAAQ,IAAI,KAAK,MAAM,KAAK,CAAC,QAAQ,IAAI,KAAK,MAAM,CAAC;AAC5F;AAEA,MAAM,kBAAkB,CAAC,OAAmB,aAC1C,MAAM,MAAM;AAAA,EACV,CAAC,SACC,KAAK,IAAI,KAAK,SAAS,IAAI,SAAS,CAAC,IAAI,kBACzC,KAAK,IAAI,KAAK,SAAS,IAAI,SAAS,CAAC,IAAI;AAC7C;AAGF,MAAM,mBAAmB,CAAC,OAAmB,oBAAkD;AAC7F,MAAI,MAAM,MAAM,WAAW,UAAU,EAAE,GAAG,GAAG,GAAG,EAAA;AAChD,QAAM,SACJ,MAAM,MAAM,KAAK,CAAC,SAAS,KAAK,WAAW,gBAAgB,CAAC,CAAC,KAC7D,MAAM,MAAM,OAAO,CAAC,SAAS,SAAU,KAAK,SAAS,IAAI,QAAQ,SAAS,IAAI,OAAO,OAAQ;AAC/F,MAAI,YAAY,EAAE,GAAG,OAAO,SAAS,IAAI,gBAAgB,GAAG,OAAO,SAAS,EAAA;AAC5E,SAAO,gBAAgB,OAAO,SAAS,EAAG,aAAY,EAAE,GAAG,UAAU,GAAG,GAAG,UAAU,IAAI,iBAAA;AACzF,SAAO;AACT;AAMO,MAAM,wBAAwB,CAAC,cAA0B,aAC9D,cAAgC,CAAC,KAAK,QAAQ;AAC5C,MAAI,kBAAkB;AACtB,MAAI,eAAmD;AAEvD,QAAM,SAAS,CACb,MACA,WACA,gBACG;AACH,UAAM,EAAE,OAAO,KAAA,IAAS,IAAA;AACxB,UAAM,MAAM,KAAK,IAAA;AACjB,UAAM,kBACJ,gBAAgB,UAAa,cAAc,QAAQ,eAAe,MAAM,aAAa,KAAK;AAC5F,mBAAe,gBAAgB,SAAY,OAAO,EAAE,KAAK,aAAa,IAAI,IAAA;AAC1E,QAAI;AAAA,MACF,OAAO;AAAA,MACP,MAAM,kBAAkB,OAAO,CAAC,GAAG,MAAM,KAAK,EAAE,MAAM,CAAC,aAAa;AAAA,MACpE,QAAQ,CAAA;AAAA,MACR,SAAS;AAAA,MACT,GAAI,WAAW,UAAU,EAAE,iBAAiB,UAAU,QAAA,IAAY,CAAA;AAAA,MAClE,GAAI,WAAW,UAAU,EAAE,iBAAiB,UAAU,QAAA,IAAY,CAAA;AAAA,IAAC,CACpE;AAAA,EACH;AAEA,QAAM,aAAa,CAAC,OAAyB,WAAqC;AAChF,UAAM,sCAAsB,IAAA;AAC5B,QAAA,EAAM,MAAM,MAAM,QAAQ,CAAC,SAAS,gBAAgB,IAAI,KAAK,OAAO,gBAAgB,IAAI,KAAK,IAAI,KAAK,KAAK,CAAC,CAAC;AAC7G,WAAO,MACJ,OAAO,CAAC,SAAS;AAChB,YAAM,aAAa,gBAAgB,IAAI,KAAK,IAAI;AAChD,UAAI,CAAC,cAAc,WAAW,aAAa,UAAW,QAAO;AAC7D,YAAM,YAAY,gBAAgB,IAAI,KAAK,IAAI,KAAK;AACpD,UAAI,WAAW,iBAAiB,UAAa,aAAa,WAAW,aAAc,QAAO;AAC1F,sBAAgB,IAAI,KAAK,MAAM,YAAY,CAAC;AAC5C,aAAO;AAAA,IACT,CAAC,EACA,IAAI,CAAC,UAAU;AAAA,MACd,GAAG;AAAA,MACH,QAAQ,aAAA;AAAA,MACR,QAAQ,gBAAgB,KAAK,MAAM;AAAA,MACnC,UAAU,EAAE,GAAG,KAAK,SAAS,IAAI,QAAQ,GAAG,KAAK,SAAS,IAAI,OAAA;AAAA,IAAO,EACrE;AAAA,EACN;AAEA,SAAO;AAAA,IACL,OAAO,eAAe,cAAc,QAAQ;AAAA,IAC5C,iBAAiB,CAAA;AAAA,IACjB,iBAAiB,CAAA;AAAA,IACjB,MAAM,CAAA;AAAA,IACN,QAAQ,CAAA;AAAA,IACR,WAAW,CAAA;AAAA,IACX,SAAS;AAAA,IAET,cAAc,CAAC,OAAO,YAAY;AAChC,UAAI,CAAC,WAAW;AAAA,QACd,OAAO,eAAe,OAAO,eAAe;AAAA,QAC5C,MAAM,SAAS,cAAc,CAAC,GAAG,MAAM,MAAM,MAAM,KAAK,EAAE,MAAM,CAAC,aAAa,IAAI,CAAA;AAAA,QAClF,QAAQ,CAAA;AAAA,QACR,iBAAiB,CAAA;AAAA,QACjB,iBAAiB,CAAA;AAAA,QACjB,SAAS,QAAQ,SAAS,WAAW;AAAA,MAAA,EACrC;AAAA,IACJ;AAAA,IAEA,WAAW,MAAM,IAAI,EAAE,SAAS,OAAO;AAAA,IAEvC,eAAe,MAAM;AACnB,YAAM,EAAE,OAAO,KAAA,IAAS,IAAA;AACxB,UAAI,EAAE,MAAM,CAAC,GAAG,MAAM,KAAK,EAAE,MAAM,CAAC,aAAa,GAAG,QAAQ,CAAA,GAAI;AAAA,IAClE;AAAA,IAEA,SAAS,CAAC,MAAM,aAAa;AAC3B,YAAM,aAAa,gBAAgB,IAAI,IAAI;AAC3C,UAAI,CAAC,WAAY,QAAO;AAExB,YAAM,EAAE,OAAO,gBAAA,IAAoB,IAAA;AACnC,YAAM,YAAY,MAAM,MAAM,OAAO,CAACA,UAASA,MAAK,SAAS,IAAI,EAAE;AACnE,UAAI,WAAW,iBAAiB,UAAa,aAAa,WAAW,aAAc,QAAO;AAE1F,YAAM,OAAkB;AAAA,QACtB,QAAQ,aAAA;AAAA,QACR;AAAA,QACA,aAAa,WAAW;AAAA,QACxB,QAAQ,WAAW,oBAAA;AAAA,QACnB,UAAU,YAAY,iBAAiB,OAAO,eAAe;AAAA,MAAA;AAE/D,aAAO,EAAE,GAAG,OAAO,OAAO,CAAC,GAAG,MAAM,OAAO,IAAI,EAAA,GAAK,EAAE,SAAS,CAAC,KAAK,MAAM,GAAG,SAAS,CAAA,GAAI;AAC3F,aAAO,KAAK;AAAA,IACd;AAAA,IAEA,iBAAiB,CAAC,MAAM,cAAc,eAAe;AACnD,YAAM,aAAa,gBAAgB,IAAI,IAAI;AAC3C,YAAM,EAAE,MAAA,IAAU,IAAA;AAClB,YAAM,SAAS,MAAM,MAAM,KAAK,CAACA,UAASA,MAAK,WAAW,YAAY;AACtE,UAAI,CAAC,cAAc,CAAC,OAAQ,QAAO;AAEnC,YAAM,YAAY,MAAM,MAAM,OAAO,CAACA,UAASA,MAAK,SAAS,IAAI,EAAE;AACnE,UAAI,WAAW,iBAAiB,UAAa,aAAa,WAAW,aAAc,QAAO;AAC1F,UAAI,MAAM,MAAM,KAAK,CAAC,SAAS,KAAK,WAAW,gBAAgB,KAAK,eAAe,UAAU,EAAG,QAAO;AAEvG,YAAM,UAAU,gBAAgB,IAAI,OAAO,IAAI,GAAG,WAAW,CAAA;AAC7D,YAAM,YAAY,KAAK,IAAI,GAAG,QAAQ,QAAQ,UAAU,CAAC;AACzD,UAAI,WAAW;AAAA,QACb,GAAG,OAAO,SAAS,IAAI;AAAA,QACvB,GAAG,OAAO,SAAS,KAAK,aAAa,QAAQ,SAAS,KAAK,KAAK;AAAA,MAAA;AAElE,aAAO,gBAAgB,OAAO,QAAQ,EAAG,YAAW,EAAE,GAAG,SAAS,GAAG,GAAG,SAAS,IAAI,iBAAA;AAErF,YAAM,OAAkB;AAAA,QACtB,QAAQ,aAAA;AAAA,QACR;AAAA,QACA,aAAa,WAAW;AAAA,QACxB,QAAQ,WAAW,oBAAA;AAAA,QACnB;AAAA,MAAA;AAEF,YAAM,QAAQ,CAAC,GAAG,MAAM,OAAO,IAAI;AACnC,YAAM,QAAQ,WAAW,EAAE,GAAG,OAAO,MAAA,GAAS,iBAAiB,EAAE,QAAQ,cAAc,YAAY,QAAQ,KAAK,QAAQ;AACxH,YAAM,QAAQ,QACV,CAAC,GAAG,MAAM,OAAO,EAAE,QAAQ,aAAA,GAAgB,QAAQ,cAAc,YAAY,QAAQ,KAAK,OAAA,CAAQ,IAClG,MAAM;AACV,aAAO,EAAE,OAAO,MAAA,GAAS,EAAE,SAAS,CAAC,KAAK,MAAM,GAAG,SAAS,CAAA,GAAI;AAChE,aAAO,KAAK;AAAA,IACd;AAAA,IAEA,kBAAkB,CAAC,QAAQ,WAAW;AACpC,YAAM,EAAE,MAAA,IAAU,IAAA;AAClB;AAAA,QACE,EAAE,GAAG,OAAO,OAAO,MAAM,MAAM,IAAI,CAAC,SAAU,KAAK,WAAW,SAAS,EAAE,GAAG,MAAM,OAAA,IAAW,IAAK,EAAA;AAAA,QAClG;AAAA,QACA,UAAU,MAAM;AAAA,MAAA;AAAA,IAEpB;AAAA,IAEA,iBAAiB,CAAC,QAAQ,UAAU;AAClC,YAAM,EAAE,MAAA,IAAU,IAAA;AAClB,YAAM,UAAU,MAAM,KAAA,EAAO,SAAS;AACtC;AAAA,QACE;AAAA,UACE,GAAG;AAAA,UACH,OAAO,MAAM,MAAM;AAAA,YAAI,CAAC,SACtB,KAAK,WAAW,SAAS,EAAE,GAAG,MAAM,OAAO,UAAU,QAAQ,OAAA,IAAc;AAAA,UAAA;AAAA,QAC7E;AAAA,QAEF;AAAA,QACA,SAAS,MAAM;AAAA,MAAA;AAAA,IAEnB;AAAA,IAEA,WAAW,CAAC,cAAc;AACxB,YAAM,EAAE,MAAA,IAAU,IAAA;AAClB,UAAI;AAAA,QACF,OAAO;AAAA,UACL,GAAG;AAAA,UACH,OAAO,MAAM,MAAM;AAAA,YAAI,CAAC,SACtB,UAAU,KAAK,MAAM,IAAI,EAAE,GAAG,MAAM,UAAU,UAAU,KAAK,MAAM,MAAM;AAAA,UAAA;AAAA,QAC3E;AAAA,QAEF,SAAS;AAAA,MAAA,CACV;AAAA,IACH;AAAA,IAEA,SAAS,CAAC,QAAQ,YAAY,WAAW;AACvC,YAAM,EAAE,MAAA,IAAU,IAAA;AAClB,UAAI,CAAC,WAAW,OAAO,iBAAiB,EAAE,QAAQ,YAAY,QAAQ,EAAG,QAAO;AAChF,YAAM,OAAkB,EAAE,QAAQ,gBAAgB,QAAQ,YAAY,OAAA;AACtE,aAAO,EAAE,GAAG,OAAO,OAAO,CAAC,GAAG,MAAM,OAAO,IAAI,GAAG;AAClD,aAAO;AAAA,IACT;AAAA,IAEA,aAAa,CAAC,YAAY;AACxB,UAAI,QAAQ,WAAW,EAAG;AAC1B,YAAM,EAAE,MAAA,IAAU,IAAA;AAClB,YAAM,UAAU,IAAI,IAAI,OAAO;AAC/B,aAAO,EAAE,GAAG,OAAO,OAAO,MAAM,MAAM,OAAO,CAAC,SAAS,CAAC,QAAQ,IAAI,KAAK,MAAM,CAAC,EAAA,GAAK,EAAE,SAAS,CAAA,GAAI;AAAA,IACtG;AAAA,IAEA,gBAAgB,MAAM;AACpB,YAAM,EAAE,OAAO,iBAAiB,gBAAA,IAAoB,IAAA;AACpD,UAAI,gBAAgB,WAAW,KAAK,gBAAgB,WAAW,EAAG;AAClE,YAAM,eAAe,IAAI,IAAI,eAAe;AAC5C,YAAM,OAAO;AAAA,QACX,EAAE,GAAG,OAAO,OAAO,MAAM,MAAM,OAAO,CAAC,SAAS,CAAC,aAAa,IAAI,KAAK,MAAM,CAAC,EAAA;AAAA,QAC9E,IAAI,IAAI,eAAe;AAAA,MAAA;AAEzB,aAAO,MAAM,EAAE,SAAS,CAAA,GAAI,SAAS,CAAA,GAAI;AAAA,IAC3C;AAAA,IAEA,mBAAmB,MAAM;AACvB,YAAM,EAAE,OAAO,gBAAA,IAAoB,IAAA;AACnC,YAAM,WAAW,MAAM,MAAM,OAAO,CAAC,SAAS,gBAAgB,SAAS,KAAK,MAAM,CAAC;AACnF,YAAM,SAAS,WAAW,UAAU,YAAY;AAChD,UAAI,OAAO,WAAW,EAAG;AACzB;AAAA,QACE,EAAE,GAAG,OAAO,OAAO,CAAC,GAAG,MAAM,OAAO,GAAG,MAAM,EAAA;AAAA,QAC7C,EAAE,SAAS,OAAO,IAAI,CAAC,SAAS,KAAK,MAAM,GAAG,SAAS,CAAA,EAAC;AAAA,MAAE;AAAA,IAE9D;AAAA,IAEA,cAAc,MAAM;AAClB,YAAM,EAAE,OAAO,gBAAA,IAAoB,IAAA;AACnC,UAAI,EAAE,WAAW,MAAM,MAAM,OAAO,CAAC,SAAS,gBAAgB,SAAS,KAAK,MAAM,CAAC,GAAG;AAAA,IACxF;AAAA,IAEA,OAAO,MAAM;AACX,YAAM,EAAE,OAAO,UAAA,IAAc,IAAA;AAC7B,YAAM,SAAS,WAAW,WAAW,YAAY;AACjD,UAAI,OAAO,WAAW,EAAG;AACzB;AAAA,QACE,EAAE,GAAG,OAAO,OAAO,CAAC,GAAG,MAAM,OAAO,GAAG,MAAM,EAAA;AAAA,QAC7C,EAAE,SAAS,OAAO,IAAI,CAAC,SAAS,KAAK,MAAM,GAAG,SAAS,CAAA,EAAC;AAAA,MAAE;AAE5D,UAAI,EAAE,WAAW,QAAQ;AAAA,IAC3B;AAAA,IAEA,cAAc,CAAC,EAAE,SAAS,cAAc;AACtC,UAAI,CAAC,WAAW;AAAA,QACd,iBAAiB,WAAW,MAAM;AAAA,QAClC,iBAAiB,WAAW,MAAM;AAAA,MAAA,EAClC;AAAA,IACJ;AAAA,IAEA,MAAM,MAAM;AACV,YAAM,EAAE,OAAO,MAAM,OAAA,IAAW,IAAA;AAChC,YAAM,WAAW,KAAK,KAAK,SAAS,CAAC;AACrC,UAAI,CAAC,SAAU;AACf,UAAI;AAAA,QACF,OAAO;AAAA,QACP,MAAM,KAAK,MAAM,GAAG,EAAE;AAAA,QACtB,QAAQ,CAAC,OAAO,GAAG,MAAM,EAAE,MAAM,GAAG,aAAa;AAAA,QACjD,iBAAiB,CAAA;AAAA,QACjB,iBAAiB,CAAA;AAAA,QACjB,SAAS;AAAA,MAAA,CACV;AAAA,IACH;AAAA,IAEA,MAAM,MAAM;AACV,YAAM,EAAE,OAAO,MAAM,OAAA,IAAW,IAAA;AAChC,YAAM,YAAY,OAAO,CAAC;AAC1B,UAAI,CAAC,UAAW;AAChB,UAAI;AAAA,QACF,OAAO;AAAA,QACP,MAAM,CAAC,GAAG,MAAM,KAAK,EAAE,MAAM,CAAC,aAAa;AAAA,QAC3C,QAAQ,OAAO,MAAM,CAAC;AAAA,QACtB,iBAAiB,CAAA;AAAA,QACjB,iBAAiB,CAAA;AAAA,QACjB,SAAS;AAAA,MAAA,CACV;AAAA,IACH;AAAA,IAEA,aAAa,CAAC,iBAAiB;AAC7B,wBAAkB;AAAA,IACpB;AAAA,EAAA;AAEJ,CAAC;"}
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 { flowOutputsOf, type IFlowBlockRegistry } from './flowBlockRegistry';\nimport { canConnect, FLOW_LEVEL_GAP, FLOW_SIBLING_GAP, layoutFlowByLevels, reachableFrom } 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) => 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 /**\n * Puts a block in the middle of an edge: the edge now ends at the new block, whose first output\n * leads to the old target. The old target and everything after it move one column right, so the\n * new block takes the place it left. One undo step.\n */\n insertNodeOnEdge: (type: string, edgeId: string) => 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 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) => {\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: 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 = flowOutputsOf(currentRegistry.get(source.type), source.config);\n if (!outputs.includes(sourcePort)) return null;\n const portIndex = 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, {\n source: sourceNodeId,\n sourcePort,\n target: node.nodeId,\n });\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 insertNodeOnEdge: (type, edgeId) => {\n const definition = currentRegistry.get(type);\n const { graph } = get();\n const edge = graph.edges.find((candidate) => candidate.edgeId === edgeId);\n const target = edge ? graph.nodes.find((node) => node.nodeId === edge.target) : undefined;\n const config = definition?.createDefaultConfig();\n const continuationPort = definition ? flowOutputsOf(definition, config)[0] : undefined;\n if (!definition || !edge || !target || !continuationPort || definition.category === 'trigger') 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\n const shifted = reachableFrom(graph, target.nodeId);\n const node: IFlowNode = {\n nodeId: generateUUID(),\n type,\n typeVersion: definition.typeVersion,\n config,\n position: { ...target.position },\n };\n const nodes = [\n ...graph.nodes.map((current) =>\n shifted.has(current.nodeId)\n ? { ...current, position: { x: current.position.x + FLOW_LEVEL_GAP, y: current.position.y } }\n : current,\n ),\n node,\n ];\n const withoutEdge = graph.edges.filter((current) => current.edgeId !== edgeId);\n const incoming: IFlowEdge = { ...edge, target: node.nodeId };\n const outgoing: IFlowEdge = {\n edgeId: generateUUID(),\n source: node.nodeId,\n sourcePort: continuationPort,\n target: target.nodeId,\n };\n\n const wiresIn = canConnect({ nodes, edges: withoutEdge }, currentRegistry, incoming);\n const wiresOut = canConnect({ nodes, edges: [...withoutEdge, incoming] }, currentRegistry, outgoing);\n if (!wiresIn || !wiresOut) return null;\n\n commit({ nodes, edges: [...withoutEdge, incoming, outgoing] }, { nodeIds: [node.nodeId], edgeIds: [] });\n return node.nodeId;\n },\n\n updateNodeConfig: (nodeId, config) => {\n const { graph } = get();\n const node = graph.nodes.find((candidate) => candidate.nodeId === nodeId);\n const outputs = node ? flowOutputsOf(currentRegistry.get(node.type), config) : [];\n commit(\n {\n nodes: graph.nodes.map((current) => (current.nodeId === nodeId ? { ...current, config } : current)),\n edges: graph.edges.filter((edge) => edge.source !== nodeId || outputs.includes(edge.sourcePort)),\n },\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 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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"}
@@ -29,4 +29,3 @@ export declare const hasBlockingIssues: (issues: Array<IFlowIssue>) => boolean;
29
29
  * implicitly.
30
30
  */
31
31
  export declare const layoutFlowByLevels: (graph: IFlowGraph, registry: IFlowBlockRegistry) => Record<string, IFlowPosition>;
32
- export declare const hasSavedPositions: (graph: IFlowGraph) => boolean;
@@ -1,3 +1,4 @@
1
+ import { flowOutputsOf } from "./flowBlockRegistry.js";
1
2
  const FLOW_LEVEL_GAP = 336;
2
3
  const FLOW_SIBLING_GAP = 176;
3
4
  const nodesOf = (graph) => Array.isArray(graph?.nodes) ? graph.nodes : [];
@@ -57,7 +58,7 @@ const canConnect = (graph, registry, candidate) => {
57
58
  if (!sourceNode || !targetNode) return false;
58
59
  const sourceDefinition = registry.get(sourceNode.type);
59
60
  const targetDefinition = registry.get(targetNode.type);
60
- if (!sourceDefinition?.outputs.includes(sourcePort)) return false;
61
+ if (!flowOutputsOf(sourceDefinition, sourceNode.config).includes(sourcePort)) return false;
61
62
  if (!targetDefinition || targetDefinition.category === "trigger") return false;
62
63
  const edges = edgesOf(graph);
63
64
  if (edges.some((edge) => edge.source === source && edge.sourcePort === sourcePort)) return false;
@@ -103,7 +104,7 @@ const validateFlowGraph = (graph, registry) => {
103
104
  );
104
105
  const cycle = findCycle(graph);
105
106
  if (cycle) cycle.forEach((nodeId) => issues.push({ code: "cycle", severity: "error", nodeId }));
106
- nodes.filter((node) => registry.get(node.type)?.outputs.includes("true")).filter((node) => !edges.some((edge) => edge.source === node.nodeId && edge.sourcePort === "true")).forEach((node) => issues.push({ code: "missingTrueBranch", severity: "warning", nodeId: node.nodeId }));
107
+ nodes.filter((node) => flowOutputsOf(registry.get(node.type), node.config).includes("true")).filter((node) => !edges.some((edge) => edge.source === node.nodeId && edge.sourcePort === "true")).forEach((node) => issues.push({ code: "missingTrueBranch", severity: "warning", nodeId: node.nodeId }));
107
108
  return issues;
108
109
  };
109
110
  const hasBlockingIssues = (issues) => (issues ?? []).some((issue) => issue.severity === "error");
@@ -140,14 +141,12 @@ const layoutFlowByLevels = (graph, registry) => {
140
141
  });
141
142
  return positions;
142
143
  };
143
- const hasSavedPositions = (graph) => nodesOf(graph).some((node) => node.position && (node.position.x !== 0 || node.position.y !== 0));
144
144
  export {
145
145
  FLOW_LEVEL_GAP,
146
146
  FLOW_SIBLING_GAP,
147
147
  canConnect,
148
148
  findCycle,
149
149
  hasBlockingIssues,
150
- hasSavedPositions,
151
150
  layoutFlowByLevels,
152
151
  reachableFrom,
153
152
  triggerNodesOf,
@@ -1 +1 @@
1
- {"version":3,"file":"flowGraph.js","sources":["../../../../src/components/UI/Flow/flowGraph.ts"],"sourcesContent":["import type { IFlowBlockRegistry } from './flowBlockRegistry';\nimport type { IFlowEdge, IFlowGraph, IFlowIssue, IFlowNode, IFlowPosition, TFlowPortId } from './flowTypes';\n\n/** The flow reads left to right: one level per column, siblings stacked in the column. */\nexport const FLOW_LEVEL_GAP = 336;\nexport const FLOW_SIBLING_GAP = 176;\n\nconst nodesOf = (graph: IFlowGraph): Array<IFlowNode> => (Array.isArray(graph?.nodes) ? graph.nodes : []);\nconst edgesOf = (graph: IFlowGraph): Array<IFlowEdge> => (Array.isArray(graph?.edges) ? graph.edges : []);\n\nconst outgoingByNode = (graph: IFlowGraph): Map<string, Array<IFlowEdge>> => {\n const outgoing = new Map<string, Array<IFlowEdge>>();\n for (const edge of edgesOf(graph)) {\n outgoing.set(edge.source, [...(outgoing.get(edge.source) ?? []), edge]);\n }\n return outgoing;\n};\n\nexport const triggerNodesOf = (graph: IFlowGraph, registry: IFlowBlockRegistry): Array<IFlowNode> =>\n nodesOf(graph).filter((node) => registry.get(node.type)?.category === 'trigger');\n\n/** Node ids reachable from `startNodeId` following edges, the start included. */\nexport const reachableFrom = (graph: IFlowGraph, startNodeId: string): Set<string> => {\n const outgoing = outgoingByNode(graph);\n const visited = new Set<string>();\n const pending = [startNodeId];\n while (pending.length > 0) {\n const nodeId = pending.pop();\n if (nodeId === undefined || visited.has(nodeId)) continue;\n visited.add(nodeId);\n for (const edge of outgoing.get(nodeId) ?? []) pending.push(edge.target);\n }\n return visited;\n};\n\n/** The node ids of the first cycle found, in order, or null when the graph has none. */\nexport const findCycle = (graph: IFlowGraph): Array<string> | null => {\n const outgoing = outgoingByNode(graph);\n const state = new Map<string, 'visiting' | 'done'>();\n const path: Array<string> = [];\n\n const visit = (nodeId: string): Array<string> | null => {\n state.set(nodeId, 'visiting');\n path.push(nodeId);\n for (const edge of outgoing.get(nodeId) ?? []) {\n const targetState = state.get(edge.target);\n if (targetState === 'visiting') return path.slice(path.indexOf(edge.target));\n if (targetState === undefined) {\n const cycle = visit(edge.target);\n if (cycle) return cycle;\n }\n }\n path.pop();\n state.set(nodeId, 'done');\n return null;\n };\n\n for (const node of nodesOf(graph)) {\n if (state.has(node.nodeId)) continue;\n const cycle = visit(node.nodeId);\n if (cycle) return cycle;\n }\n return null;\n};\n\nexport interface IFlowConnectionCandidate {\n source: string;\n sourcePort: TFlowPortId;\n target: string;\n}\n\n/**\n * The V0 shape is a tree with no fan-out: one input per block, one edge per output\n * port, no edge into a trigger and no cycle. Keeping this rule here lets the canvas\n * refuse a bad connection while it is being drawn, not only when publishing.\n */\nexport const canConnect = (\n graph: IFlowGraph,\n registry: IFlowBlockRegistry,\n candidate: IFlowConnectionCandidate,\n): boolean => {\n const { source, sourcePort, target } = candidate;\n if (!source || !target || source === target) return false;\n\n const nodes = nodesOf(graph);\n const sourceNode = nodes.find((node) => node.nodeId === source);\n const targetNode = nodes.find((node) => node.nodeId === target);\n if (!sourceNode || !targetNode) return false;\n\n const sourceDefinition = registry.get(sourceNode.type);\n const targetDefinition = registry.get(targetNode.type);\n if (!sourceDefinition?.outputs.includes(sourcePort)) return false;\n if (!targetDefinition || targetDefinition.category === 'trigger') return false;\n\n const edges = edgesOf(graph);\n if (edges.some((edge) => edge.source === source && edge.sourcePort === sourcePort)) return false;\n if (edges.some((edge) => edge.target === target)) return false;\n\n return !reachableFrom(graph, target).has(source);\n};\n\n/** The structural rules of the V0 shape, plus each block's own `validate`. */\nexport const validateFlowGraph = (graph: IFlowGraph, registry: IFlowBlockRegistry): Array<IFlowIssue> => {\n const issues: Array<IFlowIssue> = [];\n const nodes = nodesOf(graph);\n const edges = edgesOf(graph);\n\n for (const node of nodes) {\n const definition = registry.get(node.type);\n if (!definition) {\n issues.push({ code: 'unknownBlock', severity: 'error', nodeId: node.nodeId });\n continue;\n }\n for (const blockIssue of definition.validate(node.config) ?? []) {\n issues.push({ ...blockIssue, nodeId: node.nodeId });\n }\n }\n\n const triggers = triggerNodesOf(graph, registry);\n if (triggers.length === 0) issues.push({ code: 'missingTrigger', severity: 'error' });\n triggers.slice(1).forEach((trigger) => {\n issues.push({ code: 'multipleTriggers', severity: 'error', nodeId: trigger.nodeId });\n });\n\n const firstTrigger = triggers[0];\n if (firstTrigger) {\n const reachable = reachableFrom(graph, firstTrigger.nodeId);\n nodes\n .filter((node) => !reachable.has(node.nodeId) && registry.get(node.type)?.category !== 'trigger')\n .forEach((node) => issues.push({ code: 'unreachableNode', severity: 'error', nodeId: node.nodeId }));\n }\n\n const inputCount = new Map<string, number>();\n const portCount = new Map<string, number>();\n for (const edge of edges) {\n inputCount.set(edge.target, (inputCount.get(edge.target) ?? 0) + 1);\n const portKey = `${edge.source}|${edge.sourcePort}`;\n portCount.set(portKey, (portCount.get(portKey) ?? 0) + 1);\n }\n inputCount.forEach((count, nodeId) => {\n if (count > 1) issues.push({ code: 'multipleInputs', severity: 'error', nodeId });\n });\n edges\n .filter((edge) => (portCount.get(`${edge.source}|${edge.sourcePort}`) ?? 0) > 1)\n .forEach((edge) =>\n issues.push({ code: 'portFanOut', severity: 'error', edgeId: edge.edgeId, nodeId: edge.source }),\n );\n\n const cycle = findCycle(graph);\n if (cycle) cycle.forEach((nodeId) => issues.push({ code: 'cycle', severity: 'error', nodeId }));\n\n nodes\n .filter((node) => registry.get(node.type)?.outputs.includes('true'))\n .filter((node) => !edges.some((edge) => edge.source === node.nodeId && edge.sourcePort === 'true'))\n .forEach((node) => issues.push({ code: 'missingTrueBranch', severity: 'warning', nodeId: node.nodeId }));\n\n return issues;\n};\n\nexport const hasBlockingIssues = (issues: Array<IFlowIssue>): boolean =>\n (issues ?? []).some((issue) => issue.severity === 'error');\n\n/**\n * Lays the tree out by levels from the trigger: each level one column to the right,\n * siblings stacked in the column. Used only for a flow with no saved positions (one born from a rule) or when\n * the user asks for it; a position the user arranged is data and is never overwritten\n * implicitly.\n */\nexport const layoutFlowByLevels = (graph: IFlowGraph, registry: IFlowBlockRegistry): Record<string, IFlowPosition> => {\n const nodes = nodesOf(graph);\n const outgoing = outgoingByNode(graph);\n const levelOf = new Map<string, number>();\n const roots = triggerNodesOf(graph, registry);\n const queue: Array<string> = roots.map((root) => root.nodeId);\n roots.forEach((root) => levelOf.set(root.nodeId, 0));\n\n while (queue.length > 0) {\n const nodeId = queue.shift();\n if (nodeId === undefined) continue;\n const level = levelOf.get(nodeId) ?? 0;\n for (const edge of outgoing.get(nodeId) ?? []) {\n if (levelOf.has(edge.target)) continue;\n levelOf.set(edge.target, level + 1);\n queue.push(edge.target);\n }\n }\n\n const deepestLevel = Math.max(-1, ...Array.from(levelOf.values()));\n nodes.filter((node) => !levelOf.has(node.nodeId)).forEach((node) => levelOf.set(node.nodeId, deepestLevel + 1));\n\n const nodesByLevel = new Map<number, Array<string>>();\n nodes.forEach((node) => {\n const level = levelOf.get(node.nodeId) ?? 0;\n nodesByLevel.set(level, [...(nodesByLevel.get(level) ?? []), node.nodeId]);\n });\n\n const positions: Record<string, IFlowPosition> = {};\n nodesByLevel.forEach((nodeIds, level) => {\n const columnHeight = (nodeIds.length - 1) * FLOW_SIBLING_GAP;\n nodeIds.forEach((nodeId, index) => {\n positions[nodeId] = { x: level * FLOW_LEVEL_GAP, y: index * FLOW_SIBLING_GAP - columnHeight / 2 };\n });\n });\n return positions;\n};\n\nexport const hasSavedPositions = (graph: IFlowGraph): boolean =>\n nodesOf(graph).some((node) => node.position && (node.position.x !== 0 || node.position.y !== 0));\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"flowGraph.js","sources":["../../../../src/components/UI/Flow/flowGraph.ts"],"sourcesContent":["import { flowOutputsOf, type IFlowBlockRegistry } from './flowBlockRegistry';\nimport type { IFlowEdge, IFlowGraph, IFlowIssue, IFlowNode, IFlowPosition, TFlowPortId } from './flowTypes';\n\n/** The flow reads left to right: one level per column, siblings stacked in the column. */\nexport const FLOW_LEVEL_GAP = 336;\nexport const FLOW_SIBLING_GAP = 176;\n\nconst nodesOf = (graph: IFlowGraph): Array<IFlowNode> => (Array.isArray(graph?.nodes) ? graph.nodes : []);\nconst edgesOf = (graph: IFlowGraph): Array<IFlowEdge> => (Array.isArray(graph?.edges) ? graph.edges : []);\n\nconst outgoingByNode = (graph: IFlowGraph): Map<string, Array<IFlowEdge>> => {\n const outgoing = new Map<string, Array<IFlowEdge>>();\n for (const edge of edgesOf(graph)) {\n outgoing.set(edge.source, [...(outgoing.get(edge.source) ?? []), edge]);\n }\n return outgoing;\n};\n\nexport const triggerNodesOf = (graph: IFlowGraph, registry: IFlowBlockRegistry): Array<IFlowNode> =>\n nodesOf(graph).filter((node) => registry.get(node.type)?.category === 'trigger');\n\n/** Node ids reachable from `startNodeId` following edges, the start included. */\nexport const reachableFrom = (graph: IFlowGraph, startNodeId: string): Set<string> => {\n const outgoing = outgoingByNode(graph);\n const visited = new Set<string>();\n const pending = [startNodeId];\n while (pending.length > 0) {\n const nodeId = pending.pop();\n if (nodeId === undefined || visited.has(nodeId)) continue;\n visited.add(nodeId);\n for (const edge of outgoing.get(nodeId) ?? []) pending.push(edge.target);\n }\n return visited;\n};\n\n/** The node ids of the first cycle found, in order, or null when the graph has none. */\nexport const findCycle = (graph: IFlowGraph): Array<string> | null => {\n const outgoing = outgoingByNode(graph);\n const state = new Map<string, 'visiting' | 'done'>();\n const path: Array<string> = [];\n\n const visit = (nodeId: string): Array<string> | null => {\n state.set(nodeId, 'visiting');\n path.push(nodeId);\n for (const edge of outgoing.get(nodeId) ?? []) {\n const targetState = state.get(edge.target);\n if (targetState === 'visiting') return path.slice(path.indexOf(edge.target));\n if (targetState === undefined) {\n const cycle = visit(edge.target);\n if (cycle) return cycle;\n }\n }\n path.pop();\n state.set(nodeId, 'done');\n return null;\n };\n\n for (const node of nodesOf(graph)) {\n if (state.has(node.nodeId)) continue;\n const cycle = visit(node.nodeId);\n if (cycle) return cycle;\n }\n return null;\n};\n\nexport interface IFlowConnectionCandidate {\n source: string;\n sourcePort: TFlowPortId;\n target: string;\n}\n\n/**\n * The V0 shape is a tree with no fan-out: one input per block, one edge per output\n * port, no edge into a trigger and no cycle. Keeping this rule here lets the canvas\n * refuse a bad connection while it is being drawn, not only when publishing.\n */\nexport const canConnect = (\n graph: IFlowGraph,\n registry: IFlowBlockRegistry,\n candidate: IFlowConnectionCandidate,\n): boolean => {\n const { source, sourcePort, target } = candidate;\n if (!source || !target || source === target) return false;\n\n const nodes = nodesOf(graph);\n const sourceNode = nodes.find((node) => node.nodeId === source);\n const targetNode = nodes.find((node) => node.nodeId === target);\n if (!sourceNode || !targetNode) return false;\n\n const sourceDefinition = registry.get(sourceNode.type);\n const targetDefinition = registry.get(targetNode.type);\n if (!flowOutputsOf(sourceDefinition, sourceNode.config).includes(sourcePort)) return false;\n if (!targetDefinition || targetDefinition.category === 'trigger') return false;\n\n const edges = edgesOf(graph);\n if (edges.some((edge) => edge.source === source && edge.sourcePort === sourcePort)) return false;\n if (edges.some((edge) => edge.target === target)) return false;\n\n return !reachableFrom(graph, target).has(source);\n};\n\n/** The structural rules of the V0 shape, plus each block's own `validate`. */\nexport const validateFlowGraph = (graph: IFlowGraph, registry: IFlowBlockRegistry): Array<IFlowIssue> => {\n const issues: Array<IFlowIssue> = [];\n const nodes = nodesOf(graph);\n const edges = edgesOf(graph);\n\n for (const node of nodes) {\n const definition = registry.get(node.type);\n if (!definition) {\n issues.push({ code: 'unknownBlock', severity: 'error', nodeId: node.nodeId });\n continue;\n }\n for (const blockIssue of definition.validate(node.config) ?? []) {\n issues.push({ ...blockIssue, nodeId: node.nodeId });\n }\n }\n\n const triggers = triggerNodesOf(graph, registry);\n if (triggers.length === 0) issues.push({ code: 'missingTrigger', severity: 'error' });\n triggers.slice(1).forEach((trigger) => {\n issues.push({ code: 'multipleTriggers', severity: 'error', nodeId: trigger.nodeId });\n });\n\n const firstTrigger = triggers[0];\n if (firstTrigger) {\n const reachable = reachableFrom(graph, firstTrigger.nodeId);\n nodes\n .filter((node) => !reachable.has(node.nodeId) && registry.get(node.type)?.category !== 'trigger')\n .forEach((node) => issues.push({ code: 'unreachableNode', severity: 'error', nodeId: node.nodeId }));\n }\n\n const inputCount = new Map<string, number>();\n const portCount = new Map<string, number>();\n for (const edge of edges) {\n inputCount.set(edge.target, (inputCount.get(edge.target) ?? 0) + 1);\n const portKey = `${edge.source}|${edge.sourcePort}`;\n portCount.set(portKey, (portCount.get(portKey) ?? 0) + 1);\n }\n inputCount.forEach((count, nodeId) => {\n if (count > 1) issues.push({ code: 'multipleInputs', severity: 'error', nodeId });\n });\n edges\n .filter((edge) => (portCount.get(`${edge.source}|${edge.sourcePort}`) ?? 0) > 1)\n .forEach((edge) =>\n issues.push({ code: 'portFanOut', severity: 'error', edgeId: edge.edgeId, nodeId: edge.source }),\n );\n\n const cycle = findCycle(graph);\n if (cycle) cycle.forEach((nodeId) => issues.push({ code: 'cycle', severity: 'error', nodeId }));\n\n nodes\n .filter((node) => flowOutputsOf(registry.get(node.type), node.config).includes('true'))\n .filter((node) => !edges.some((edge) => edge.source === node.nodeId && edge.sourcePort === 'true'))\n .forEach((node) => issues.push({ code: 'missingTrueBranch', severity: 'warning', nodeId: node.nodeId }));\n\n return issues;\n};\n\nexport const hasBlockingIssues = (issues: Array<IFlowIssue>): boolean =>\n (issues ?? []).some((issue) => issue.severity === 'error');\n\n/**\n * Lays the tree out by levels from the trigger: each level one column to the right,\n * siblings stacked in the column. Used only for a flow with no saved positions (one born from a rule) or when\n * the user asks for it; a position the user arranged is data and is never overwritten\n * implicitly.\n */\nexport const layoutFlowByLevels = (graph: IFlowGraph, registry: IFlowBlockRegistry): Record<string, IFlowPosition> => {\n const nodes = nodesOf(graph);\n const outgoing = outgoingByNode(graph);\n const levelOf = new Map<string, number>();\n const roots = triggerNodesOf(graph, registry);\n const queue: Array<string> = roots.map((root) => root.nodeId);\n roots.forEach((root) => levelOf.set(root.nodeId, 0));\n\n while (queue.length > 0) {\n const nodeId = queue.shift();\n if (nodeId === undefined) continue;\n const level = levelOf.get(nodeId) ?? 0;\n for (const edge of outgoing.get(nodeId) ?? []) {\n if (levelOf.has(edge.target)) continue;\n levelOf.set(edge.target, level + 1);\n queue.push(edge.target);\n }\n }\n\n const deepestLevel = Math.max(-1, ...Array.from(levelOf.values()));\n nodes.filter((node) => !levelOf.has(node.nodeId)).forEach((node) => levelOf.set(node.nodeId, deepestLevel + 1));\n\n const nodesByLevel = new Map<number, Array<string>>();\n nodes.forEach((node) => {\n const level = levelOf.get(node.nodeId) ?? 0;\n nodesByLevel.set(level, [...(nodesByLevel.get(level) ?? []), node.nodeId]);\n });\n\n const positions: Record<string, IFlowPosition> = {};\n nodesByLevel.forEach((nodeIds, level) => {\n const columnHeight = (nodeIds.length - 1) * FLOW_SIBLING_GAP;\n nodeIds.forEach((nodeId, index) => {\n positions[nodeId] = { x: level * FLOW_LEVEL_GAP, y: index * FLOW_SIBLING_GAP - columnHeight / 2 };\n });\n });\n return positions;\n};\n"],"names":[],"mappings":"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"}
@@ -56,19 +56,17 @@ export interface IFlowBlockDefinition<TConfig = unknown> {
56
56
  icon: SymbolCodepoints;
57
57
  /** Translated by the host. */
58
58
  label: string;
59
- description?: string;
60
59
  outputs: Array<TFlowPortId>;
60
+ /** Ports that depend on the config, e.g. an error path the user turns on inside the block. Defaults to `outputs`. */
61
+ resolveOutputs?: (config: TConfig) => Array<TFlowPortId>;
61
62
  /** How many of this block a flow may hold; omit for no limit. */
62
63
  maxInstances?: number;
63
64
  createDefaultConfig: () => TConfig;
64
- migrateConfig?: (config: unknown, fromVersion: number) => TConfig;
65
65
  ConfigPanel: ComponentType<IFlowBlockConfigPanelProps<TConfig>>;
66
66
  /** When one block stands for several kinds (chosen inside it), the icon and name of the chosen kind. */
67
67
  resolveIcon?: (config: TConfig) => SymbolCodepoints | undefined;
68
68
  resolveLabel?: (config: TConfig) => string | undefined;
69
69
  /** One line inside the block on the canvas. */
70
70
  renderSummary: (config: TConfig) => ReactNode;
71
- /** This block's piece of the "When..., if..., then..." sentence. */
72
- describe: (config: TConfig) => string;
73
71
  validate: (config: TConfig) => Array<TFlowBlockIssue>;
74
72
  }
@@ -1,14 +1,12 @@
1
- export { createFlowBlockRegistry, defineFlowBlock, type IFlowBlockRegistry } from './flowBlockRegistry';
1
+ export { createFlowBlockRegistry, defineFlowBlock, flowOutputsOf, type IFlowBlockRegistry } from './flowBlockRegistry';
2
2
  export { default as FlowCanvas, type IFlowCanvasProps } from './FlowCanvas';
3
- export { FLOW_BLOCK_DRAG_TYPE } from './flowConstants';
4
3
  export { default as FlowEditorProvider, useFlowBlockRegistry, useFlowEditor, useFlowEditorStore, } from './FlowEditorProvider';
5
4
  export { createFlowEditorStore, type IFlowEditorState, type TFlowEditorStore } from './flowEditorStore';
6
- export { canConnect, findCycle, FLOW_LEVEL_GAP, FLOW_SIBLING_GAP, hasBlockingIssues, hasSavedPositions, layoutFlowByLevels, reachableFrom, triggerNodesOf, validateFlowGraph, } from './flowGraph';
5
+ export { canConnect, findCycle, FLOW_LEVEL_GAP, FLOW_SIBLING_GAP, hasBlockingIssues, layoutFlowByLevels, reachableFrom, triggerNodesOf, validateFlowGraph, } from './flowGraph';
7
6
  export { default as FlowInspector } from './FlowInspector';
8
7
  export { default as FlowIssueList } from './FlowIssueList';
9
8
  export { default as FlowLaneMap, type IFlowArc, type IFlowLane, type TFlowArcTone } from './FlowLaneMap';
10
9
  export { default as FlowNodeShell, type IFlowNodeShellProps } from './FlowNodeShell';
11
- export { default as FlowPalette } from './FlowPalette';
12
10
  export { default as FlowResizablePanel, type IFlowResizablePanelProps } from './FlowResizablePanel';
13
11
  export type * from './flowTypes';
14
12
  export { FLOW_RUN_STATUS_TONE, useFlowLabels } from './useFlowLabels';
@@ -1,18 +1,15 @@
1
- import { createFlowBlockRegistry, defineFlowBlock } from "./flowBlockRegistry.js";
1
+ import { createFlowBlockRegistry, defineFlowBlock, flowOutputsOf } from "./flowBlockRegistry.js";
2
2
  import { default as default2 } from "./FlowCanvas.js";
3
- import { FLOW_BLOCK_DRAG_TYPE } from "./flowConstants.js";
4
3
  import { default as default3, useFlowBlockRegistry, useFlowEditor, useFlowEditorStore } from "./FlowEditorProvider.js";
5
4
  import { createFlowEditorStore } from "./flowEditorStore.js";
6
- import { FLOW_LEVEL_GAP, FLOW_SIBLING_GAP, canConnect, findCycle, hasBlockingIssues, hasSavedPositions, layoutFlowByLevels, reachableFrom, triggerNodesOf, validateFlowGraph } from "./flowGraph.js";
5
+ import { FLOW_LEVEL_GAP, FLOW_SIBLING_GAP, canConnect, findCycle, hasBlockingIssues, layoutFlowByLevels, reachableFrom, triggerNodesOf, validateFlowGraph } from "./flowGraph.js";
7
6
  import { default as default4 } from "./FlowInspector.js";
8
7
  import { default as default5 } from "./FlowIssueList.js";
9
8
  import { default as default6 } from "./FlowLaneMap.js";
10
9
  import { default as default7 } from "./FlowNodeShell.js";
11
- import { default as default8 } from "./FlowPalette.js";
12
- import { default as default9 } from "./FlowResizablePanel.js";
10
+ import { default as default8 } from "./FlowResizablePanel.js";
13
11
  import { FLOW_RUN_STATUS_TONE, useFlowLabels } from "./useFlowLabels.js";
14
12
  export {
15
- FLOW_BLOCK_DRAG_TYPE,
16
13
  FLOW_LEVEL_GAP,
17
14
  FLOW_RUN_STATUS_TONE,
18
15
  FLOW_SIBLING_GAP,
@@ -22,15 +19,14 @@ export {
22
19
  default5 as FlowIssueList,
23
20
  default6 as FlowLaneMap,
24
21
  default7 as FlowNodeShell,
25
- default8 as FlowPalette,
26
- default9 as FlowResizablePanel,
22
+ default8 as FlowResizablePanel,
27
23
  canConnect,
28
24
  createFlowBlockRegistry,
29
25
  createFlowEditorStore,
30
26
  defineFlowBlock,
31
27
  findCycle,
28
+ flowOutputsOf,
32
29
  hasBlockingIssues,
33
- hasSavedPositions,
34
30
  layoutFlowByLevels,
35
31
  reachableFrom,
36
32
  triggerNodesOf,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;"}
@@ -230,12 +230,6 @@ declare const _default: {
230
230
  "condition": "Conditions",
231
231
  "action": "Actions"
232
232
  },
233
- "palette": {
234
- "title": "Blocks",
235
- "search": "Search block",
236
- "empty": "No block found",
237
- "limitReached": "This flow already has the maximum of this block"
238
- },
239
233
  "inspector": {
240
234
  "title": "Block settings",
241
235
  "empty": "Select a block to configure it",
@@ -273,6 +267,9 @@ declare const _default: {
273
267
  "redo": "Redo",
274
268
  "layout": "Arrange blocks"
275
269
  },
270
+ "edge": {
271
+ "insertBlock": "Insert a block here"
272
+ },
276
273
  "node": {
277
274
  "disabled": "Disabled",
278
275
  "addBlock": "Add the next block",
@@ -12,7 +12,7 @@ const detailsTable = { "size": "Size", "quantity": "Qty" };
12
12
  const errorPage = { "message": "This screen could not load", "headline": "Something unexpected happened while building the page. Refreshing usually fixes it. If it happens again, contact support and tell them what you were doing.", "backToStart": "Back to start" };
13
13
  const filters = { "activeFilters": "Applied filters", "appliedCount_one": "{{count}} filter applied", "appliedCount_other": "{{count}} filters applied", "clearAll": "Clear all", "noFields": "No filters available for this list.", "noOptions": "No options found.", "optionsError": "Could not load the options for {{field}}.", "rangeMax": "Maximum", "rangeMin": "Minimum", "rangeSeparator": "to", "searchOptions": "Search options" };
14
14
  const filtersDrawer = { "selectedFilters": "Selected filters:", "pendingChanges": "Pending changes", "discardChangesTitle": "Discard changes?", "discardChangesMessage": "You have unapplied filter changes. Discard them?", "keepEditing": "Keep editing", "discard": "Discard" };
15
- const flow = { "canvas": "Flow editor", "resizePanel": "Drag to adjust the width. Double click resets", "ports": { "next": "Next", "true": "Yes", "false": "No", "error": "Error" }, "categories": { "trigger": "Triggers", "condition": "Conditions", "action": "Actions" }, "palette": { "title": "Blocks", "search": "Search block", "empty": "No block found", "limitReached": "This flow already has the maximum of this block" }, "inspector": { "title": "Block settings", "empty": "Select a block to configure it", "label": "Block name", "delete": "Delete block", "duplicate": "Duplicate block", "unknownBlock": "This block type no longer exists", "close": "Close" }, "issues": { "title": "Issues", "none": "No issues", "missingTrigger": "The flow needs a trigger", "multipleTriggers": "A flow can have only one trigger", "unknownBlock": "Block of a type that no longer exists", "unreachableNode": "Loose block: no path from the trigger reaches it", "multipleInputs": "Block with more than one input", "portFanOut": "An output can connect to one block only", "cycle": "The blocks form a cycle", "missingTrueBranch": "The condition has no yes path" }, "run": { "passed": "Passed", "failed": "Did not pass", "skipped": "Skipped", "wouldRun": "Would run", "error": "Error", "blocked": "Blocked" }, "controls": { "zoomIn": "Zoom in", "zoomOut": "Zoom out", "fitView": "Fit view", "undo": "Undo", "redo": "Redo", "layout": "Arrange blocks" }, "node": { "disabled": "Disabled", "addBlock": "Add the next block", "issueCount_one": "{{count}} issue", "issueCount_other": "{{count}} issues" }, "laneMap": { "label": "Flow map" } };
15
+ const flow = { "canvas": "Flow editor", "resizePanel": "Drag to adjust the width. Double click resets", "ports": { "next": "Next", "true": "Yes", "false": "No", "error": "Error" }, "categories": { "trigger": "Triggers", "condition": "Conditions", "action": "Actions" }, "inspector": { "title": "Block settings", "empty": "Select a block to configure it", "label": "Block name", "delete": "Delete block", "duplicate": "Duplicate block", "unknownBlock": "This block type no longer exists", "close": "Close" }, "issues": { "title": "Issues", "none": "No issues", "missingTrigger": "The flow needs a trigger", "multipleTriggers": "A flow can have only one trigger", "unknownBlock": "Block of a type that no longer exists", "unreachableNode": "Loose block: no path from the trigger reaches it", "multipleInputs": "Block with more than one input", "portFanOut": "An output can connect to one block only", "cycle": "The blocks form a cycle", "missingTrueBranch": "The condition has no yes path" }, "run": { "passed": "Passed", "failed": "Did not pass", "skipped": "Skipped", "wouldRun": "Would run", "error": "Error", "blocked": "Blocked" }, "controls": { "zoomIn": "Zoom in", "zoomOut": "Zoom out", "fitView": "Fit view", "undo": "Undo", "redo": "Redo", "layout": "Arrange blocks" }, "edge": { "insertBlock": "Insert a block here" }, "node": { "disabled": "Disabled", "addBlock": "Add the next block", "issueCount_one": "{{count}} issue", "issueCount_other": "{{count}} issues" }, "laneMap": { "label": "Flow map" } };
16
16
  const followUp = { "messages": "Messages", "chat": { "loadingMessages": "Loading messages", "placeholder": "Write a message...", "invalidMessage": "Please enter a valid message", "noMessagesYet": "No messages yet", "startConversation": "Start a conversation by adding the first message", "loadError": "Messages could not be loaded", "sendFailed": "The message could not be sent. Try again.", "sendFailedInline": "Failed to send", "resend": "Resend", "discard": "Discard", "today": "Today", "yesterday": "Yesterday", "typingOne": "{{name}} is typing...", "typingMany": "{{count}} people are typing...", "someone": "Someone", "you": "You", "newMessages": "New messages", "backToLatest": "Back to latest", "deletedMessage": "Message deleted", "edited": "edited", "readBy": "Read by {{names}}", "copied": "Message copied", "replyingTo": "Replying to {{name}}", "attachmentFallback": "Attachment", "editTitle": "Edit message", "deleteTitle": "Delete message?", "deleteSubtitle": "The message will be shown as deleted for every participant", "sendHint": "Enter sends, Shift+Enter adds a line break", "formatting": "Formatting" }, "actions": { "reply": "Reply", "copy": "Copy text", "edit": "Edit", "delete": "Delete" }, "attachments": { "attach": "Attach", "pickFile": "File", "pickImage": "Image", "takePhoto": "Take photo", "dropHere": "Drop files to attach", "tooMany": "Limit of {{max}} attachments per message", "tooLarge": "The file {{name}} exceeds the 50 MB limit", "uploadFailed": "Upload failed", "uploading": "Uploading attachments...", "imagePreview": "Preview image", "showTranscript": "Show transcript", "hideTranscript": "Hide transcript" }, "voice": { "record": "Record audio", "recording": "Recording", "stop": "Finish recording", "cancel": "Cancel recording", "rerecord": "Record again", "attach": "Add to message", "discard": "Discard", "transcriptLabel": "Transcript", "transcriptPlaceholder": "The speech transcript appears here and can be edited" }, "camera": { "title": "Take photo", "unavailable": "Camera unavailable", "unavailableHint": "Check the browser camera permission", "switch": "Switch camera", "capture": "Capture photo" }, "search": { "open": "Search in conversation", "placeholder": "Search messages", "noResults": "No messages found", "error": "Messages could not be searched" } };
17
17
  const generatedByAI = { "tooltip": "Generated by AI" };
18
18
  const imageAiAdjust = { "action": "Adjust with AI", "title": "Adjust image with AI", "subtitle": "Describe the adjustment. The AI changes only what you ask and preserves the rest of the image.", "original": "Original", "result": "Result", "resultHint": "Describe the adjustment and click Generate to see the result here.", "promptLabel": "Desired adjustment", "promptPlaceholder": "E.g. remove the background and make it white, smooth the sleeve creases, brighten the lighting", "generate": "Generate", "generating": "Generating the image...", "tryAgain": "Generate another", "useResult": "Use this image", "errorGenerate": "Could not adjust the image with AI", "errorSave": "Could not save the adjusted image", "errorRefused": "The AI could not make this adjustment: {{reason}}", "mask": { "start": "Mark the area", "stop": "Use the whole image", "hint": "Paint the area the AI may change. The rest of the image is kept." }, "verification": { "changedMoreThanRequested": "The AI changed more than requested. Check the areas outside the adjustment before using it.", "colorMismatch": "The garment color ({{dominant}}) differs from the requested color ({{target}})." } };
@@ -232,12 +232,6 @@ export declare const uiLocales: {
232
232
  condition: string;
233
233
  action: string;
234
234
  };
235
- palette: {
236
- title: string;
237
- search: string;
238
- empty: string;
239
- limitReached: string;
240
- };
241
235
  inspector: {
242
236
  title: string;
243
237
  empty: string;
@@ -275,6 +269,9 @@ export declare const uiLocales: {
275
269
  redo: string;
276
270
  layout: string;
277
271
  };
272
+ edge: {
273
+ insertBlock: string;
274
+ };
278
275
  node: {
279
276
  disabled: string;
280
277
  addBlock: string;
@@ -853,12 +850,6 @@ export declare const uiLocales: {
853
850
  condition: string;
854
851
  action: string;
855
852
  };
856
- palette: {
857
- title: string;
858
- search: string;
859
- empty: string;
860
- limitReached: string;
861
- };
862
853
  inspector: {
863
854
  title: string;
864
855
  empty: string;
@@ -896,6 +887,9 @@ export declare const uiLocales: {
896
887
  redo: string;
897
888
  layout: string;
898
889
  };
890
+ edge: {
891
+ insertBlock: string;
892
+ };
899
893
  node: {
900
894
  disabled: string;
901
895
  addBlock: string;
@@ -230,12 +230,6 @@ declare const _default: {
230
230
  "condition": "Condições",
231
231
  "action": "Ações"
232
232
  },
233
- "palette": {
234
- "title": "Blocos",
235
- "search": "Buscar bloco",
236
- "empty": "Nenhum bloco encontrado",
237
- "limitReached": "Este fluxo já tem o máximo deste bloco"
238
- },
239
233
  "inspector": {
240
234
  "title": "Configuração do bloco",
241
235
  "empty": "Selecione um bloco para configurar",
@@ -273,6 +267,9 @@ declare const _default: {
273
267
  "redo": "Refazer",
274
268
  "layout": "Organizar blocos"
275
269
  },
270
+ "edge": {
271
+ "insertBlock": "Inserir um bloco aqui"
272
+ },
276
273
  "node": {
277
274
  "disabled": "Desativado",
278
275
  "addBlock": "Adicionar o próximo bloco",
@@ -12,7 +12,7 @@ const detailsTable = { "size": "Tam", "quantity": "Qtd" };
12
12
  const errorPage = { "message": "Esta tela não conseguiu carregar", "headline": "Algo inesperado aconteceu ao montar a página. Atualizar costuma resolver. Se acontecer de novo, fale com o suporte contando o que você estava fazendo.", "backToStart": "Voltar para o início" };
13
13
  const filters = { "activeFilters": "Filtros aplicados", "appliedCount_one": "{{count}} filtro aplicado", "appliedCount_other": "{{count}} filtros aplicados", "clearAll": "Limpar tudo", "noFields": "Nenhum filtro disponível para esta listagem.", "noOptions": "Nenhuma opção encontrada.", "optionsError": "Não foi possível carregar as opções de {{field}}.", "rangeMax": "Máximo", "rangeMin": "Mínimo", "rangeSeparator": "até", "searchOptions": "Buscar opções" };
14
14
  const filtersDrawer = { "selectedFilters": "Filtros Selecionados:", "pendingChanges": "Alterações pendentes", "discardChangesTitle": "Descartar alterações?", "discardChangesMessage": "Você tem alterações de filtro não aplicadas. Deseja descartá-las?", "keepEditing": "Continuar editando", "discard": "Descartar" };
15
- const flow = { "canvas": "Editor de fluxo", "resizePanel": "Arraste para ajustar a largura. Clique duplo restaura", "ports": { "next": "Próximo", "true": "Sim", "false": "Não", "error": "Erro" }, "categories": { "trigger": "Gatilhos", "condition": "Condições", "action": "Ações" }, "palette": { "title": "Blocos", "search": "Buscar bloco", "empty": "Nenhum bloco encontrado", "limitReached": "Este fluxo já tem o máximo deste bloco" }, "inspector": { "title": "Configuração do bloco", "empty": "Selecione um bloco para configurar", "label": "Nome do bloco", "delete": "Excluir bloco", "duplicate": "Duplicar bloco", "unknownBlock": "Este tipo de bloco não existe mais", "close": "Fechar" }, "issues": { "title": "Pendências", "none": "Nenhuma pendência", "missingTrigger": "O fluxo precisa de um gatilho", "multipleTriggers": "O fluxo só pode ter um gatilho", "unknownBlock": "Bloco de um tipo que não existe mais", "unreachableNode": "Bloco solto: nenhum caminho do gatilho chega até ele", "multipleInputs": "Bloco com mais de uma entrada", "portFanOut": "Uma saída só pode ligar a um bloco", "cycle": "Os blocos formam um ciclo", "missingTrueBranch": "A condição não tem o caminho do sim" }, "run": { "passed": "Passou", "failed": "Não passou", "skipped": "Pulado", "wouldRun": "Executaria", "error": "Erro", "blocked": "Bloqueado" }, "controls": { "zoomIn": "Aproximar", "zoomOut": "Afastar", "fitView": "Enquadrar", "undo": "Desfazer", "redo": "Refazer", "layout": "Organizar blocos" }, "node": { "disabled": "Desativado", "addBlock": "Adicionar o próximo bloco", "issueCount_one": "{{count}} pendência", "issueCount_other": "{{count}} pendências" }, "laneMap": { "label": "Mapa do fluxo" } };
15
+ const flow = { "canvas": "Editor de fluxo", "resizePanel": "Arraste para ajustar a largura. Clique duplo restaura", "ports": { "next": "Próximo", "true": "Sim", "false": "Não", "error": "Erro" }, "categories": { "trigger": "Gatilhos", "condition": "Condições", "action": "Ações" }, "inspector": { "title": "Configuração do bloco", "empty": "Selecione um bloco para configurar", "label": "Nome do bloco", "delete": "Excluir bloco", "duplicate": "Duplicar bloco", "unknownBlock": "Este tipo de bloco não existe mais", "close": "Fechar" }, "issues": { "title": "Pendências", "none": "Nenhuma pendência", "missingTrigger": "O fluxo precisa de um gatilho", "multipleTriggers": "O fluxo só pode ter um gatilho", "unknownBlock": "Bloco de um tipo que não existe mais", "unreachableNode": "Bloco solto: nenhum caminho do gatilho chega até ele", "multipleInputs": "Bloco com mais de uma entrada", "portFanOut": "Uma saída só pode ligar a um bloco", "cycle": "Os blocos formam um ciclo", "missingTrueBranch": "A condição não tem o caminho do sim" }, "run": { "passed": "Passou", "failed": "Não passou", "skipped": "Pulado", "wouldRun": "Executaria", "error": "Erro", "blocked": "Bloqueado" }, "controls": { "zoomIn": "Aproximar", "zoomOut": "Afastar", "fitView": "Enquadrar", "undo": "Desfazer", "redo": "Refazer", "layout": "Organizar blocos" }, "edge": { "insertBlock": "Inserir um bloco aqui" }, "node": { "disabled": "Desativado", "addBlock": "Adicionar o próximo bloco", "issueCount_one": "{{count}} pendência", "issueCount_other": "{{count}} pendências" }, "laneMap": { "label": "Mapa do fluxo" } };
16
16
  const followUp = { "messages": "Mensagens", "chat": { "loadingMessages": "Carregando mensagens", "placeholder": "Escrever mensagem...", "invalidMessage": "Por favor, insira uma mensagem válida", "noMessagesYet": "Nenhuma mensagem por enquanto", "startConversation": "Inicie uma conversa adicionando a primeira mensagem", "loadError": "Não foi possível carregar as mensagens", "sendFailed": "Não foi possível enviar a mensagem. Tente novamente.", "sendFailedInline": "Falha no envio", "resend": "Reenviar", "discard": "Descartar", "today": "Hoje", "yesterday": "Ontem", "typingOne": "{{name}} está digitando...", "typingMany": "{{count}} pessoas estão digitando...", "someone": "Alguém", "you": "Você", "newMessages": "Novas mensagens", "backToLatest": "Voltar para as recentes", "deletedMessage": "Mensagem apagada", "edited": "editada", "readBy": "Lida por {{names}}", "copied": "Mensagem copiada", "replyingTo": "Respondendo a {{name}}", "attachmentFallback": "Anexo", "editTitle": "Editar mensagem", "deleteTitle": "Apagar mensagem?", "deleteSubtitle": "A mensagem será marcada como apagada para todos os participantes", "sendHint": "Enter envia, Shift+Enter quebra a linha", "formatting": "Formatação" }, "actions": { "reply": "Responder", "copy": "Copiar texto", "edit": "Editar", "delete": "Apagar" }, "attachments": { "attach": "Anexar", "pickFile": "Arquivo", "pickImage": "Imagem", "takePhoto": "Tirar foto", "dropHere": "Solte os arquivos para anexar", "tooMany": "Limite de {{max}} anexos por mensagem", "tooLarge": "O arquivo {{name}} passa do limite de 50 MB", "uploadFailed": "Falha no upload", "uploading": "Enviando anexos...", "imagePreview": "Visualizar imagem", "showTranscript": "Ver transcrição", "hideTranscript": "Ocultar transcrição" }, "voice": { "record": "Gravar áudio", "recording": "Gravando", "stop": "Concluir gravação", "cancel": "Cancelar gravação", "rerecord": "Regravar", "attach": "Adicionar ao envio", "discard": "Descartar", "transcriptLabel": "Transcrição", "transcriptPlaceholder": "A transcrição da fala aparece aqui e pode ser editada" }, "camera": { "title": "Tirar foto", "unavailable": "Câmera indisponível", "unavailableHint": "Verifique a permissão de câmera do navegador", "switch": "Alternar câmera", "capture": "Capturar foto" }, "search": { "open": "Buscar na conversa", "placeholder": "Buscar mensagens", "noResults": "Nenhuma mensagem encontrada", "error": "Não foi possível buscar as mensagens" } };
17
17
  const generatedByAI = { "tooltip": "Gerado por IA" };
18
18
  const imageAiAdjust = { "action": "Ajustar com IA", "title": "Ajustar imagem com IA", "subtitle": "Descreva o ajuste. A IA altera só o que você pedir e preserva o resto da imagem.", "original": "Original", "result": "Resultado", "resultHint": "Descreva o ajuste e clique em Gerar para ver o resultado aqui.", "promptLabel": "Ajuste desejado", "promptPlaceholder": "Ex.: remover o fundo e deixar branco, tirar os vincos da manga, clarear a iluminação", "generate": "Gerar", "generating": "Gerando a imagem...", "tryAgain": "Gerar outra", "useResult": "Usar esta imagem", "errorGenerate": "Não foi possível ajustar a imagem com IA", "errorSave": "Não foi possível salvar a imagem ajustada", "errorRefused": "A IA não conseguiu fazer este ajuste: {{reason}}", "mask": { "start": "Marcar a área", "stop": "Usar a imagem toda", "hint": "Pinte a área que a IA pode alterar. O resto da imagem é mantido." }, "verification": { "changedMoreThanRequested": "A IA mudou mais do que o pedido. Confira as áreas fora do ajuste antes de usar.", "colorMismatch": "A cor da peça ({{dominant}}) ficou diferente da cor pedida ({{target}})." } };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umodeapporg/ui",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -1,7 +0,0 @@
1
- interface IFlowPaletteProps {
2
- readOnly?: boolean;
3
- className?: string;
4
- }
5
- /** The blocks a flow can use, grouped by category. Drag one onto the canvas, or click to add it below the selection. */
6
- declare const FlowPalette: React.FC<IFlowPaletteProps>;
7
- export default FlowPalette;