@goplasmatic/dataflow-ui 2.0.4 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,10 +17,12 @@ A React component library for visualizing and debugging [dataflow-rs](https://gi
17
17
  ## Features
18
18
 
19
19
  - **Workflow Visualization** - Interactive tree view of workflows, tasks, and conditions
20
- - **Execution Debugging** - Step-by-step execution trace visualization with change highlighting
20
+ - **Execution Debugging** - Step-by-step execution trace visualization with message snapshots
21
21
  - **JSONLogic Viewer** - Visual representation of JSONLogic expressions via [@goplasmatic/datalogic-ui](https://www.npmjs.com/package/@goplasmatic/datalogic-ui)
22
22
  - **Theme Support** - Light, dark, and system theme modes
23
23
  - **TypeScript** - Full type definitions included
24
+ - **Monaco Editor Integration** - JSON editing with syntax highlighting
25
+ - **Change Highlighting** - Visual diff of message changes at each step
24
26
 
25
27
  ## Installation
26
28
 
package/dist/index.cjs CHANGED
@@ -243,6 +243,21 @@ const FileJson = createLucideIcon("FileJson", [
243
243
  { d: "M14 18a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1 1 1 0 0 1-1-1v-1a1 1 0 0 0-1-1", key: "mpwhp6" }
244
244
  ]
245
245
  ]);
246
+ /**
247
+ * @license lucide-react v0.462.0 - ISC
248
+ *
249
+ * This source code is licensed under the ISC license.
250
+ * See the LICENSE file in the root directory of this source tree.
251
+ */
252
+ const Folder = createLucideIcon("Folder", [
253
+ [
254
+ "path",
255
+ {
256
+ d: "M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z",
257
+ key: "1kt360"
258
+ }
259
+ ]
260
+ ]);
246
261
  /**
247
262
  * @license lucide-react v0.462.0 - ISC
248
263
  *
@@ -19854,31 +19869,38 @@ function TreeNode({
19854
19869
  };
19855
19870
  const debugStateClass = debugState ? `df-tree-node-${debugState}` : "";
19856
19871
  const currentClass = isCurrent ? "df-tree-node-current-step" : "";
19857
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `df-tree-node ${debugStateClass} ${currentClass}`, children: [
19858
- /* @__PURE__ */ jsxRuntime.jsxs(
19859
- "div",
19860
- {
19861
- className: `df-tree-node-content ${isSelected ? "df-tree-node-selected" : ""}`,
19862
- style: { paddingLeft: `${level * 16 + 8}px` },
19863
- onClick: handleClick,
19864
- children: [
19865
- /* @__PURE__ */ jsxRuntime.jsx(
19866
- "span",
19867
- {
19868
- className: "df-tree-toggle",
19869
- onClick: hasChildren ? handleToggle : void 0,
19870
- style: { visibility: hasChildren ? "visible" : "hidden" },
19871
- children: isExpanded ? /* @__PURE__ */ jsxRuntime.jsx(ChevronDown, { size: 14 }) : /* @__PURE__ */ jsxRuntime.jsx(ChevronRight, { size: 14 })
19872
- }
19873
- ),
19874
- icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "df-tree-icon", style: iconColor ? { color: iconColor } : void 0, children: icon }),
19875
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "df-tree-label", children: label }),
19876
- debugState && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "df-tree-debug-indicator", children: /* @__PURE__ */ jsxRuntime.jsx(DebugStateIcon, { state: debugState, conditionResult }) })
19877
- ]
19878
- }
19879
- ),
19880
- isExpanded && hasChildren && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "df-tree-children", children: children2 })
19881
- ] });
19872
+ return /* @__PURE__ */ jsxRuntime.jsxs(
19873
+ "div",
19874
+ {
19875
+ className: `df-tree-node ${debugStateClass} ${currentClass}`,
19876
+ "data-current-step": isCurrent ? "true" : void 0,
19877
+ children: [
19878
+ /* @__PURE__ */ jsxRuntime.jsxs(
19879
+ "div",
19880
+ {
19881
+ className: `df-tree-node-content ${isSelected ? "df-tree-node-selected" : ""}`,
19882
+ style: { paddingLeft: `${level * 16 + 8}px` },
19883
+ onClick: handleClick,
19884
+ children: [
19885
+ /* @__PURE__ */ jsxRuntime.jsx(
19886
+ "span",
19887
+ {
19888
+ className: "df-tree-toggle",
19889
+ onClick: hasChildren ? handleToggle : void 0,
19890
+ style: { visibility: hasChildren ? "visible" : "hidden" },
19891
+ children: isExpanded ? /* @__PURE__ */ jsxRuntime.jsx(ChevronDown, { size: 14 }) : /* @__PURE__ */ jsxRuntime.jsx(ChevronRight, { size: 14 })
19892
+ }
19893
+ ),
19894
+ icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "df-tree-icon", style: iconColor ? { color: iconColor } : void 0, children: icon }),
19895
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "df-tree-label", children: label }),
19896
+ debugState && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "df-tree-debug-indicator", children: /* @__PURE__ */ jsxRuntime.jsx(DebugStateIcon, { state: debugState, conditionResult }) })
19897
+ ]
19898
+ }
19899
+ ),
19900
+ isExpanded && hasChildren && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "df-tree-children", children: children2 })
19901
+ ]
19902
+ }
19903
+ );
19882
19904
  }
19883
19905
  const TREE_COLORS = {
19884
19906
  workflow: "#0078d4",
@@ -19891,8 +19913,10 @@ const TREE_COLORS = {
19891
19913
  // VSCode teal/green
19892
19914
  validation: "#ce9178",
19893
19915
  // VSCode orange
19894
- tasks: "#9d9d9d"
19916
+ tasks: "#9d9d9d",
19895
19917
  // VSCode gray
19918
+ folder: "#dcb67a"
19919
+ // VSCode folder yellow/gold
19896
19920
  };
19897
19921
  function TaskNode({
19898
19922
  task,
@@ -20091,20 +20115,177 @@ function WorkflowNode({
20091
20115
  }
20092
20116
  );
20093
20117
  }
20118
+ function FolderNode({
20119
+ folder,
20120
+ level,
20121
+ selection: selection2,
20122
+ onSelect,
20123
+ expandedNodes,
20124
+ toggleNode,
20125
+ debugMode = false
20126
+ }) {
20127
+ const folderId = `folder-${folder.fullPath}`;
20128
+ const isExpanded = expandedNodes.has(folderId);
20129
+ const hasChildren = folder.folders.size > 0 || folder.workflows.length > 0;
20130
+ const sortedFolders = Array.from(folder.folders.values()).sort(
20131
+ (a, b) => a.name.localeCompare(b.name)
20132
+ );
20133
+ return /* @__PURE__ */ jsxRuntime.jsxs(
20134
+ TreeNode,
20135
+ {
20136
+ label: `${folder.name} (${folder.totalWorkflowCount})`,
20137
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Folder, { size: 14 }),
20138
+ iconColor: TREE_COLORS.folder,
20139
+ isExpanded,
20140
+ hasChildren,
20141
+ level,
20142
+ onToggle: () => toggleNode(folderId),
20143
+ onClick: () => toggleNode(folderId),
20144
+ children: [
20145
+ sortedFolders.map((childFolder) => /* @__PURE__ */ jsxRuntime.jsx(
20146
+ FolderNode,
20147
+ {
20148
+ folder: childFolder,
20149
+ level: level + 1,
20150
+ selection: selection2,
20151
+ onSelect,
20152
+ expandedNodes,
20153
+ toggleNode,
20154
+ debugMode
20155
+ },
20156
+ childFolder.fullPath
20157
+ )),
20158
+ folder.workflows.map((workflow) => /* @__PURE__ */ jsxRuntime.jsx(
20159
+ WorkflowNode,
20160
+ {
20161
+ workflow,
20162
+ level: level + 1,
20163
+ selection: selection2,
20164
+ onSelect,
20165
+ expandedNodes,
20166
+ toggleNode,
20167
+ debugMode
20168
+ },
20169
+ workflow.id
20170
+ ))
20171
+ ]
20172
+ }
20173
+ );
20174
+ }
20175
+ function parsePath(path) {
20176
+ if (!path) return [];
20177
+ const trimmed = path.replace(/^\/+|\/+$/g, "");
20178
+ if (!trimmed) return [];
20179
+ return trimmed.split("/").filter((segment) => segment.length > 0);
20180
+ }
20181
+ function createFolderNode(name, fullPath) {
20182
+ return {
20183
+ name,
20184
+ fullPath,
20185
+ folders: /* @__PURE__ */ new Map(),
20186
+ workflows: [],
20187
+ totalWorkflowCount: 0
20188
+ };
20189
+ }
20190
+ function calculateTotalCount(node) {
20191
+ let count = node.workflows.length;
20192
+ for (const child of node.folders.values()) {
20193
+ count += calculateTotalCount(child);
20194
+ }
20195
+ node.totalWorkflowCount = count;
20196
+ return count;
20197
+ }
20198
+ function buildFolderTree(workflows) {
20199
+ const tree = {
20200
+ folders: /* @__PURE__ */ new Map(),
20201
+ workflows: []
20202
+ };
20203
+ const sortedWorkflows = [...workflows].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
20204
+ for (const workflow of sortedWorkflows) {
20205
+ const segments = parsePath(workflow.path);
20206
+ if (segments.length === 0) {
20207
+ tree.workflows.push(workflow);
20208
+ } else {
20209
+ let currentLevel = tree.folders;
20210
+ let currentPath = "";
20211
+ for (let i = 0; i < segments.length; i++) {
20212
+ const segment = segments[i];
20213
+ currentPath = currentPath ? `${currentPath}/${segment}` : segment;
20214
+ if (!currentLevel.has(segment)) {
20215
+ currentLevel.set(segment, createFolderNode(segment, currentPath));
20216
+ }
20217
+ const folder = currentLevel.get(segment);
20218
+ if (i === segments.length - 1) {
20219
+ folder.workflows.push(workflow);
20220
+ } else {
20221
+ currentLevel = folder.folders;
20222
+ }
20223
+ }
20224
+ }
20225
+ }
20226
+ for (const folder of tree.folders.values()) {
20227
+ calculateTotalCount(folder);
20228
+ }
20229
+ return tree;
20230
+ }
20231
+ function getFirstLevelFolderIds(tree) {
20232
+ return Array.from(tree.folders.keys()).map((name) => `folder-${name}`);
20233
+ }
20234
+ function getParentFolderIds(path) {
20235
+ const segments = parsePath(path);
20236
+ if (segments.length === 0) return [];
20237
+ const ids = [];
20238
+ let currentPath = "";
20239
+ for (const segment of segments) {
20240
+ currentPath = currentPath ? `${currentPath}/${segment}` : segment;
20241
+ ids.push(`folder-${currentPath}`);
20242
+ }
20243
+ return ids;
20244
+ }
20094
20245
  function TreeView({ workflows, selection: selection2, onSelect, debugMode = false }) {
20095
20246
  const debuggerContext = useDebugger();
20096
20247
  const effectiveDebugContext = debugMode ? debuggerContext : null;
20248
+ const folderTree = require$$0.useMemo(() => buildFolderTree(workflows), [workflows]);
20249
+ const sortedRootFolders = require$$0.useMemo(() => {
20250
+ return Array.from(folderTree.folders.values()).sort(
20251
+ (a, b) => a.name.localeCompare(b.name)
20252
+ );
20253
+ }, [folderTree]);
20254
+ const rootWorkflows = folderTree.workflows;
20097
20255
  const [expandedNodes, setExpandedNodes] = require$$0.useState(() => {
20098
20256
  const initial = /* @__PURE__ */ new Set(["workflows-root"]);
20099
- if (workflows.length > 0) {
20100
- initial.add(`workflow-${workflows[0].id}`);
20101
- }
20257
+ getFirstLevelFolderIds(folderTree).forEach((id2) => initial.add(id2));
20102
20258
  return initial;
20103
20259
  });
20104
- const sortedWorkflows = require$$0.useMemo(() => {
20105
- return [...workflows].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
20106
- }, [workflows]);
20260
+ require$$0.useEffect(() => {
20261
+ setExpandedNodes((prev) => {
20262
+ const next = new Set(prev);
20263
+ next.add("workflows-root");
20264
+ getFirstLevelFolderIds(folderTree).forEach((id2) => next.add(id2));
20265
+ return next;
20266
+ });
20267
+ }, [folderTree]);
20268
+ require$$0.useEffect(() => {
20269
+ const allWorkflows = [...folderTree.workflows];
20270
+ function collectWorkflows(folders) {
20271
+ for (const folder of folders.values()) {
20272
+ allWorkflows.push(...folder.workflows);
20273
+ collectWorkflows(folder.folders);
20274
+ }
20275
+ }
20276
+ collectWorkflows(folderTree.folders);
20277
+ if (allWorkflows.length > 0) {
20278
+ allWorkflows.sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
20279
+ setExpandedNodes((prev) => {
20280
+ const next = new Set(prev);
20281
+ next.add(`workflow-${allWorkflows[0].id}`);
20282
+ getParentFolderIds(allWorkflows[0].path).forEach((id2) => next.add(id2));
20283
+ return next;
20284
+ });
20285
+ }
20286
+ }, [folderTree]);
20107
20287
  const lastSelectedRef = require$$0.useRef(null);
20288
+ const treeContainerRef = require$$0.useRef(null);
20108
20289
  require$$0.useEffect(() => {
20109
20290
  var _a, _b;
20110
20291
  if (!debugMode || !(effectiveDebugContext == null ? void 0 : effectiveDebugContext.currentStep) || effectiveDebugContext.state.currentStepIndex < 0) {
@@ -20114,9 +20295,13 @@ function TreeView({ workflows, selection: selection2, onSelect, debugMode = fals
20114
20295
  if (((_a = lastSelectedRef.current) == null ? void 0 : _a.workflowId) === workflow_id && ((_b = lastSelectedRef.current) == null ? void 0 : _b.taskId) === task_id) {
20115
20296
  return;
20116
20297
  }
20298
+ const workflow = workflows.find((w) => w.id === workflow_id);
20117
20299
  setExpandedNodes((prev) => {
20118
20300
  const next = new Set(prev);
20119
20301
  next.add("workflows-root");
20302
+ if (workflow == null ? void 0 : workflow.path) {
20303
+ getParentFolderIds(workflow.path).forEach((id2) => next.add(id2));
20304
+ }
20120
20305
  next.add(`workflow-${workflow_id}`);
20121
20306
  next.add(`tasks-${workflow_id}`);
20122
20307
  if (task_id) {
@@ -20125,7 +20310,6 @@ function TreeView({ workflows, selection: selection2, onSelect, debugMode = fals
20125
20310
  return next;
20126
20311
  });
20127
20312
  if (task_id) {
20128
- const workflow = workflows.find((w) => w.id === workflow_id);
20129
20313
  const task = workflow == null ? void 0 : workflow.tasks.find((t) => t.id === task_id);
20130
20314
  if (workflow && task) {
20131
20315
  lastSelectedRef.current = { workflowId: workflow_id, taskId: task_id };
@@ -20134,6 +20318,16 @@ function TreeView({ workflows, selection: selection2, onSelect, debugMode = fals
20134
20318
  } else {
20135
20319
  lastSelectedRef.current = { workflowId: workflow_id };
20136
20320
  }
20321
+ setTimeout(() => {
20322
+ var _a2;
20323
+ const currentStepElement = (_a2 = treeContainerRef.current) == null ? void 0 : _a2.querySelector('[data-current-step="true"]');
20324
+ if (currentStepElement) {
20325
+ currentStepElement.scrollIntoView({
20326
+ behavior: "smooth",
20327
+ block: "nearest"
20328
+ });
20329
+ }
20330
+ }, 50);
20137
20331
  }, [debugMode, effectiveDebugContext == null ? void 0 : effectiveDebugContext.currentStep, effectiveDebugContext == null ? void 0 : effectiveDebugContext.state.currentStepIndex, workflows, onSelect]);
20138
20332
  const toggleNode = (id2) => {
20139
20333
  setExpandedNodes((prev) => {
@@ -20147,30 +20341,46 @@ function TreeView({ workflows, selection: selection2, onSelect, debugMode = fals
20147
20341
  });
20148
20342
  };
20149
20343
  const isRootExpanded = expandedNodes.has("workflows-root");
20150
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `df-tree-view ${debugMode ? "df-tree-view-debug" : ""}`, children: /* @__PURE__ */ jsxRuntime.jsx(
20344
+ const totalWorkflowCount = workflows.length;
20345
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: treeContainerRef, className: `df-tree-view ${debugMode ? "df-tree-view-debug" : ""}`, children: /* @__PURE__ */ jsxRuntime.jsxs(
20151
20346
  TreeNode,
20152
20347
  {
20153
20348
  label: "Workflows",
20154
20349
  icon: /* @__PURE__ */ jsxRuntime.jsx(Layers, { size: 14 }),
20155
20350
  iconColor: TREE_COLORS.workflow,
20156
20351
  isExpanded: isRootExpanded,
20157
- hasChildren: sortedWorkflows.length > 0,
20352
+ hasChildren: totalWorkflowCount > 0,
20158
20353
  level: 0,
20159
20354
  onToggle: () => toggleNode("workflows-root"),
20160
20355
  onClick: () => toggleNode("workflows-root"),
20161
- children: sortedWorkflows.map((workflow) => /* @__PURE__ */ jsxRuntime.jsx(
20162
- WorkflowNode,
20163
- {
20164
- workflow,
20165
- level: 1,
20166
- selection: selection2,
20167
- onSelect,
20168
- expandedNodes,
20169
- toggleNode,
20170
- debugMode
20171
- },
20172
- workflow.id
20173
- ))
20356
+ children: [
20357
+ sortedRootFolders.map((folder) => /* @__PURE__ */ jsxRuntime.jsx(
20358
+ FolderNode,
20359
+ {
20360
+ folder,
20361
+ level: 1,
20362
+ selection: selection2,
20363
+ onSelect,
20364
+ expandedNodes,
20365
+ toggleNode,
20366
+ debugMode
20367
+ },
20368
+ folder.fullPath
20369
+ )),
20370
+ rootWorkflows.map((workflow) => /* @__PURE__ */ jsxRuntime.jsx(
20371
+ WorkflowNode,
20372
+ {
20373
+ workflow,
20374
+ level: 1,
20375
+ selection: selection2,
20376
+ onSelect,
20377
+ expandedNodes,
20378
+ toggleNode,
20379
+ debugMode
20380
+ },
20381
+ workflow.id
20382
+ ))
20383
+ ]
20174
20384
  }
20175
20385
  ) });
20176
20386
  }
package/dist/index.d.ts CHANGED
@@ -670,6 +670,8 @@ export declare interface Workflow {
670
670
  priority?: number;
671
671
  /** Optional description */
672
672
  description?: string;
673
+ /** Optional folder path for grouping (e.g., "orders/processing") */
674
+ path?: string;
673
675
  /** JSONLogic condition (evaluated against metadata only) */
674
676
  condition?: JsonLogicValue;
675
677
  /** Tasks in this workflow */
package/dist/index.js CHANGED
@@ -241,6 +241,21 @@ const FileJson = createLucideIcon("FileJson", [
241
241
  { d: "M14 18a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1 1 1 0 0 1-1-1v-1a1 1 0 0 0-1-1", key: "mpwhp6" }
242
242
  ]
243
243
  ]);
244
+ /**
245
+ * @license lucide-react v0.462.0 - ISC
246
+ *
247
+ * This source code is licensed under the ISC license.
248
+ * See the LICENSE file in the root directory of this source tree.
249
+ */
250
+ const Folder = createLucideIcon("Folder", [
251
+ [
252
+ "path",
253
+ {
254
+ d: "M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z",
255
+ key: "1kt360"
256
+ }
257
+ ]
258
+ ]);
244
259
  /**
245
260
  * @license lucide-react v0.462.0 - ISC
246
261
  *
@@ -19852,31 +19867,38 @@ function TreeNode({
19852
19867
  };
19853
19868
  const debugStateClass = debugState ? `df-tree-node-${debugState}` : "";
19854
19869
  const currentClass = isCurrent ? "df-tree-node-current-step" : "";
19855
- return /* @__PURE__ */ jsxs("div", { className: `df-tree-node ${debugStateClass} ${currentClass}`, children: [
19856
- /* @__PURE__ */ jsxs(
19857
- "div",
19858
- {
19859
- className: `df-tree-node-content ${isSelected ? "df-tree-node-selected" : ""}`,
19860
- style: { paddingLeft: `${level * 16 + 8}px` },
19861
- onClick: handleClick,
19862
- children: [
19863
- /* @__PURE__ */ jsx(
19864
- "span",
19865
- {
19866
- className: "df-tree-toggle",
19867
- onClick: hasChildren ? handleToggle : void 0,
19868
- style: { visibility: hasChildren ? "visible" : "hidden" },
19869
- children: isExpanded ? /* @__PURE__ */ jsx(ChevronDown, { size: 14 }) : /* @__PURE__ */ jsx(ChevronRight, { size: 14 })
19870
- }
19871
- ),
19872
- icon && /* @__PURE__ */ jsx("span", { className: "df-tree-icon", style: iconColor ? { color: iconColor } : void 0, children: icon }),
19873
- /* @__PURE__ */ jsx("span", { className: "df-tree-label", children: label }),
19874
- debugState && /* @__PURE__ */ jsx("span", { className: "df-tree-debug-indicator", children: /* @__PURE__ */ jsx(DebugStateIcon, { state: debugState, conditionResult }) })
19875
- ]
19876
- }
19877
- ),
19878
- isExpanded && hasChildren && /* @__PURE__ */ jsx("div", { className: "df-tree-children", children: children2 })
19879
- ] });
19870
+ return /* @__PURE__ */ jsxs(
19871
+ "div",
19872
+ {
19873
+ className: `df-tree-node ${debugStateClass} ${currentClass}`,
19874
+ "data-current-step": isCurrent ? "true" : void 0,
19875
+ children: [
19876
+ /* @__PURE__ */ jsxs(
19877
+ "div",
19878
+ {
19879
+ className: `df-tree-node-content ${isSelected ? "df-tree-node-selected" : ""}`,
19880
+ style: { paddingLeft: `${level * 16 + 8}px` },
19881
+ onClick: handleClick,
19882
+ children: [
19883
+ /* @__PURE__ */ jsx(
19884
+ "span",
19885
+ {
19886
+ className: "df-tree-toggle",
19887
+ onClick: hasChildren ? handleToggle : void 0,
19888
+ style: { visibility: hasChildren ? "visible" : "hidden" },
19889
+ children: isExpanded ? /* @__PURE__ */ jsx(ChevronDown, { size: 14 }) : /* @__PURE__ */ jsx(ChevronRight, { size: 14 })
19890
+ }
19891
+ ),
19892
+ icon && /* @__PURE__ */ jsx("span", { className: "df-tree-icon", style: iconColor ? { color: iconColor } : void 0, children: icon }),
19893
+ /* @__PURE__ */ jsx("span", { className: "df-tree-label", children: label }),
19894
+ debugState && /* @__PURE__ */ jsx("span", { className: "df-tree-debug-indicator", children: /* @__PURE__ */ jsx(DebugStateIcon, { state: debugState, conditionResult }) })
19895
+ ]
19896
+ }
19897
+ ),
19898
+ isExpanded && hasChildren && /* @__PURE__ */ jsx("div", { className: "df-tree-children", children: children2 })
19899
+ ]
19900
+ }
19901
+ );
19880
19902
  }
19881
19903
  const TREE_COLORS = {
19882
19904
  workflow: "#0078d4",
@@ -19889,8 +19911,10 @@ const TREE_COLORS = {
19889
19911
  // VSCode teal/green
19890
19912
  validation: "#ce9178",
19891
19913
  // VSCode orange
19892
- tasks: "#9d9d9d"
19914
+ tasks: "#9d9d9d",
19893
19915
  // VSCode gray
19916
+ folder: "#dcb67a"
19917
+ // VSCode folder yellow/gold
19894
19918
  };
19895
19919
  function TaskNode({
19896
19920
  task,
@@ -20089,20 +20113,177 @@ function WorkflowNode({
20089
20113
  }
20090
20114
  );
20091
20115
  }
20116
+ function FolderNode({
20117
+ folder,
20118
+ level,
20119
+ selection: selection2,
20120
+ onSelect,
20121
+ expandedNodes,
20122
+ toggleNode,
20123
+ debugMode = false
20124
+ }) {
20125
+ const folderId = `folder-${folder.fullPath}`;
20126
+ const isExpanded = expandedNodes.has(folderId);
20127
+ const hasChildren = folder.folders.size > 0 || folder.workflows.length > 0;
20128
+ const sortedFolders = Array.from(folder.folders.values()).sort(
20129
+ (a, b) => a.name.localeCompare(b.name)
20130
+ );
20131
+ return /* @__PURE__ */ jsxs(
20132
+ TreeNode,
20133
+ {
20134
+ label: `${folder.name} (${folder.totalWorkflowCount})`,
20135
+ icon: /* @__PURE__ */ jsx(Folder, { size: 14 }),
20136
+ iconColor: TREE_COLORS.folder,
20137
+ isExpanded,
20138
+ hasChildren,
20139
+ level,
20140
+ onToggle: () => toggleNode(folderId),
20141
+ onClick: () => toggleNode(folderId),
20142
+ children: [
20143
+ sortedFolders.map((childFolder) => /* @__PURE__ */ jsx(
20144
+ FolderNode,
20145
+ {
20146
+ folder: childFolder,
20147
+ level: level + 1,
20148
+ selection: selection2,
20149
+ onSelect,
20150
+ expandedNodes,
20151
+ toggleNode,
20152
+ debugMode
20153
+ },
20154
+ childFolder.fullPath
20155
+ )),
20156
+ folder.workflows.map((workflow) => /* @__PURE__ */ jsx(
20157
+ WorkflowNode,
20158
+ {
20159
+ workflow,
20160
+ level: level + 1,
20161
+ selection: selection2,
20162
+ onSelect,
20163
+ expandedNodes,
20164
+ toggleNode,
20165
+ debugMode
20166
+ },
20167
+ workflow.id
20168
+ ))
20169
+ ]
20170
+ }
20171
+ );
20172
+ }
20173
+ function parsePath(path) {
20174
+ if (!path) return [];
20175
+ const trimmed = path.replace(/^\/+|\/+$/g, "");
20176
+ if (!trimmed) return [];
20177
+ return trimmed.split("/").filter((segment) => segment.length > 0);
20178
+ }
20179
+ function createFolderNode(name, fullPath) {
20180
+ return {
20181
+ name,
20182
+ fullPath,
20183
+ folders: /* @__PURE__ */ new Map(),
20184
+ workflows: [],
20185
+ totalWorkflowCount: 0
20186
+ };
20187
+ }
20188
+ function calculateTotalCount(node) {
20189
+ let count = node.workflows.length;
20190
+ for (const child of node.folders.values()) {
20191
+ count += calculateTotalCount(child);
20192
+ }
20193
+ node.totalWorkflowCount = count;
20194
+ return count;
20195
+ }
20196
+ function buildFolderTree(workflows) {
20197
+ const tree = {
20198
+ folders: /* @__PURE__ */ new Map(),
20199
+ workflows: []
20200
+ };
20201
+ const sortedWorkflows = [...workflows].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
20202
+ for (const workflow of sortedWorkflows) {
20203
+ const segments = parsePath(workflow.path);
20204
+ if (segments.length === 0) {
20205
+ tree.workflows.push(workflow);
20206
+ } else {
20207
+ let currentLevel = tree.folders;
20208
+ let currentPath = "";
20209
+ for (let i = 0; i < segments.length; i++) {
20210
+ const segment = segments[i];
20211
+ currentPath = currentPath ? `${currentPath}/${segment}` : segment;
20212
+ if (!currentLevel.has(segment)) {
20213
+ currentLevel.set(segment, createFolderNode(segment, currentPath));
20214
+ }
20215
+ const folder = currentLevel.get(segment);
20216
+ if (i === segments.length - 1) {
20217
+ folder.workflows.push(workflow);
20218
+ } else {
20219
+ currentLevel = folder.folders;
20220
+ }
20221
+ }
20222
+ }
20223
+ }
20224
+ for (const folder of tree.folders.values()) {
20225
+ calculateTotalCount(folder);
20226
+ }
20227
+ return tree;
20228
+ }
20229
+ function getFirstLevelFolderIds(tree) {
20230
+ return Array.from(tree.folders.keys()).map((name) => `folder-${name}`);
20231
+ }
20232
+ function getParentFolderIds(path) {
20233
+ const segments = parsePath(path);
20234
+ if (segments.length === 0) return [];
20235
+ const ids = [];
20236
+ let currentPath = "";
20237
+ for (const segment of segments) {
20238
+ currentPath = currentPath ? `${currentPath}/${segment}` : segment;
20239
+ ids.push(`folder-${currentPath}`);
20240
+ }
20241
+ return ids;
20242
+ }
20092
20243
  function TreeView({ workflows, selection: selection2, onSelect, debugMode = false }) {
20093
20244
  const debuggerContext = useDebugger();
20094
20245
  const effectiveDebugContext = debugMode ? debuggerContext : null;
20246
+ const folderTree = useMemo(() => buildFolderTree(workflows), [workflows]);
20247
+ const sortedRootFolders = useMemo(() => {
20248
+ return Array.from(folderTree.folders.values()).sort(
20249
+ (a, b) => a.name.localeCompare(b.name)
20250
+ );
20251
+ }, [folderTree]);
20252
+ const rootWorkflows = folderTree.workflows;
20095
20253
  const [expandedNodes, setExpandedNodes] = useState(() => {
20096
20254
  const initial = /* @__PURE__ */ new Set(["workflows-root"]);
20097
- if (workflows.length > 0) {
20098
- initial.add(`workflow-${workflows[0].id}`);
20099
- }
20255
+ getFirstLevelFolderIds(folderTree).forEach((id2) => initial.add(id2));
20100
20256
  return initial;
20101
20257
  });
20102
- const sortedWorkflows = useMemo(() => {
20103
- return [...workflows].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
20104
- }, [workflows]);
20258
+ useEffect(() => {
20259
+ setExpandedNodes((prev) => {
20260
+ const next = new Set(prev);
20261
+ next.add("workflows-root");
20262
+ getFirstLevelFolderIds(folderTree).forEach((id2) => next.add(id2));
20263
+ return next;
20264
+ });
20265
+ }, [folderTree]);
20266
+ useEffect(() => {
20267
+ const allWorkflows = [...folderTree.workflows];
20268
+ function collectWorkflows(folders) {
20269
+ for (const folder of folders.values()) {
20270
+ allWorkflows.push(...folder.workflows);
20271
+ collectWorkflows(folder.folders);
20272
+ }
20273
+ }
20274
+ collectWorkflows(folderTree.folders);
20275
+ if (allWorkflows.length > 0) {
20276
+ allWorkflows.sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
20277
+ setExpandedNodes((prev) => {
20278
+ const next = new Set(prev);
20279
+ next.add(`workflow-${allWorkflows[0].id}`);
20280
+ getParentFolderIds(allWorkflows[0].path).forEach((id2) => next.add(id2));
20281
+ return next;
20282
+ });
20283
+ }
20284
+ }, [folderTree]);
20105
20285
  const lastSelectedRef = useRef(null);
20286
+ const treeContainerRef = useRef(null);
20106
20287
  useEffect(() => {
20107
20288
  var _a, _b;
20108
20289
  if (!debugMode || !(effectiveDebugContext == null ? void 0 : effectiveDebugContext.currentStep) || effectiveDebugContext.state.currentStepIndex < 0) {
@@ -20112,9 +20293,13 @@ function TreeView({ workflows, selection: selection2, onSelect, debugMode = fals
20112
20293
  if (((_a = lastSelectedRef.current) == null ? void 0 : _a.workflowId) === workflow_id && ((_b = lastSelectedRef.current) == null ? void 0 : _b.taskId) === task_id) {
20113
20294
  return;
20114
20295
  }
20296
+ const workflow = workflows.find((w) => w.id === workflow_id);
20115
20297
  setExpandedNodes((prev) => {
20116
20298
  const next = new Set(prev);
20117
20299
  next.add("workflows-root");
20300
+ if (workflow == null ? void 0 : workflow.path) {
20301
+ getParentFolderIds(workflow.path).forEach((id2) => next.add(id2));
20302
+ }
20118
20303
  next.add(`workflow-${workflow_id}`);
20119
20304
  next.add(`tasks-${workflow_id}`);
20120
20305
  if (task_id) {
@@ -20123,7 +20308,6 @@ function TreeView({ workflows, selection: selection2, onSelect, debugMode = fals
20123
20308
  return next;
20124
20309
  });
20125
20310
  if (task_id) {
20126
- const workflow = workflows.find((w) => w.id === workflow_id);
20127
20311
  const task = workflow == null ? void 0 : workflow.tasks.find((t) => t.id === task_id);
20128
20312
  if (workflow && task) {
20129
20313
  lastSelectedRef.current = { workflowId: workflow_id, taskId: task_id };
@@ -20132,6 +20316,16 @@ function TreeView({ workflows, selection: selection2, onSelect, debugMode = fals
20132
20316
  } else {
20133
20317
  lastSelectedRef.current = { workflowId: workflow_id };
20134
20318
  }
20319
+ setTimeout(() => {
20320
+ var _a2;
20321
+ const currentStepElement = (_a2 = treeContainerRef.current) == null ? void 0 : _a2.querySelector('[data-current-step="true"]');
20322
+ if (currentStepElement) {
20323
+ currentStepElement.scrollIntoView({
20324
+ behavior: "smooth",
20325
+ block: "nearest"
20326
+ });
20327
+ }
20328
+ }, 50);
20135
20329
  }, [debugMode, effectiveDebugContext == null ? void 0 : effectiveDebugContext.currentStep, effectiveDebugContext == null ? void 0 : effectiveDebugContext.state.currentStepIndex, workflows, onSelect]);
20136
20330
  const toggleNode = (id2) => {
20137
20331
  setExpandedNodes((prev) => {
@@ -20145,30 +20339,46 @@ function TreeView({ workflows, selection: selection2, onSelect, debugMode = fals
20145
20339
  });
20146
20340
  };
20147
20341
  const isRootExpanded = expandedNodes.has("workflows-root");
20148
- return /* @__PURE__ */ jsx("div", { className: `df-tree-view ${debugMode ? "df-tree-view-debug" : ""}`, children: /* @__PURE__ */ jsx(
20342
+ const totalWorkflowCount = workflows.length;
20343
+ return /* @__PURE__ */ jsx("div", { ref: treeContainerRef, className: `df-tree-view ${debugMode ? "df-tree-view-debug" : ""}`, children: /* @__PURE__ */ jsxs(
20149
20344
  TreeNode,
20150
20345
  {
20151
20346
  label: "Workflows",
20152
20347
  icon: /* @__PURE__ */ jsx(Layers, { size: 14 }),
20153
20348
  iconColor: TREE_COLORS.workflow,
20154
20349
  isExpanded: isRootExpanded,
20155
- hasChildren: sortedWorkflows.length > 0,
20350
+ hasChildren: totalWorkflowCount > 0,
20156
20351
  level: 0,
20157
20352
  onToggle: () => toggleNode("workflows-root"),
20158
20353
  onClick: () => toggleNode("workflows-root"),
20159
- children: sortedWorkflows.map((workflow) => /* @__PURE__ */ jsx(
20160
- WorkflowNode,
20161
- {
20162
- workflow,
20163
- level: 1,
20164
- selection: selection2,
20165
- onSelect,
20166
- expandedNodes,
20167
- toggleNode,
20168
- debugMode
20169
- },
20170
- workflow.id
20171
- ))
20354
+ children: [
20355
+ sortedRootFolders.map((folder) => /* @__PURE__ */ jsx(
20356
+ FolderNode,
20357
+ {
20358
+ folder,
20359
+ level: 1,
20360
+ selection: selection2,
20361
+ onSelect,
20362
+ expandedNodes,
20363
+ toggleNode,
20364
+ debugMode
20365
+ },
20366
+ folder.fullPath
20367
+ )),
20368
+ rootWorkflows.map((workflow) => /* @__PURE__ */ jsx(
20369
+ WorkflowNode,
20370
+ {
20371
+ workflow,
20372
+ level: 1,
20373
+ selection: selection2,
20374
+ onSelect,
20375
+ expandedNodes,
20376
+ toggleNode,
20377
+ debugMode
20378
+ },
20379
+ workflow.id
20380
+ ))
20381
+ ]
20172
20382
  }
20173
20383
  ) });
20174
20384
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goplasmatic/dataflow-ui",
3
- "version": "2.0.4",
3
+ "version": "2.0.5",
4
4
  "type": "module",
5
5
  "description": "React visualization library for dataflow-rs workflow engine",
6
6
  "author": "Plasmatic Engineering <shankar@goplasmatic.io>",
@@ -53,7 +53,7 @@
53
53
  },
54
54
  "dependencies": {
55
55
  "@goplasmatic/dataflow-wasm": "^2.0.4",
56
- "@goplasmatic/datalogic-ui": "^4.0.9",
56
+ "@goplasmatic/datalogic-ui": "^4.0.11",
57
57
  "@monaco-editor/react": "^4.7.0",
58
58
  "@xyflow/react": "^12.0.0",
59
59
  "lucide-react": "^0.462.0"
@@ -67,6 +67,8 @@
67
67
  "react-dom": "^18.3.1",
68
68
  "typescript": "^5.7.3",
69
69
  "vite": "^6.0.7",
70
- "vite-plugin-dts": "^4.4.0"
70
+ "vite-plugin-dts": "^4.4.0",
71
+ "vite-plugin-top-level-await": "^1.6.0",
72
+ "vite-plugin-wasm": "^3.5.0"
71
73
  }
72
74
  }