@redvars/peacock 3.5.1 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BaseButton-DuASuVth.js → BaseButton-BNFAYn-S.js} +2 -2
- package/dist/{BaseButton-DuASuVth.js.map → BaseButton-BNFAYn-S.js.map} +1 -1
- package/dist/BaseInput-14YmcfK7.js +27 -0
- package/dist/BaseInput-14YmcfK7.js.map +1 -0
- package/dist/banner.js +2 -3
- package/dist/banner.js.map +1 -1
- package/dist/{button-DouvOfEU.js → button-colors-Ccys3hvS.js} +5 -294
- package/dist/button-colors-Ccys3hvS.js.map +1 -0
- package/dist/button-group.js +226 -6
- package/dist/button-group.js.map +1 -1
- package/dist/button.js +294 -8
- package/dist/button.js.map +1 -1
- package/dist/calendar-column-view.js +634 -0
- package/dist/calendar-column-view.js.map +1 -0
- package/dist/calendar-event-BrQ_SEKD.js +199 -0
- package/dist/calendar-event-BrQ_SEKD.js.map +1 -0
- package/dist/calendar-month-view.js +376 -0
- package/dist/calendar-month-view.js.map +1 -0
- package/dist/calendar.js +339 -0
- package/dist/calendar.js.map +1 -0
- package/dist/canvas.js +361 -0
- package/dist/canvas.js.map +1 -0
- package/dist/cb-compound-expression.js +125 -0
- package/dist/cb-compound-expression.js.map +1 -0
- package/dist/cb-divider.js +150 -0
- package/dist/cb-divider.js.map +1 -0
- package/dist/cb-expression.js +75 -0
- package/dist/cb-expression.js.map +1 -0
- package/dist/cb-predicate.js +137 -0
- package/dist/cb-predicate.js.map +1 -0
- package/dist/code-editor.js +2 -1
- package/dist/code-editor.js.map +1 -1
- package/dist/condition-builder.js +58 -0
- package/dist/condition-builder.js.map +1 -0
- package/dist/custom-elements-jsdocs.json +7976 -4294
- package/dist/custom-elements.json +14358 -7589
- package/dist/dropdown-button.js +216 -0
- package/dist/dropdown-button.js.map +1 -0
- package/dist/event-manager-D-QCmUgR.js +113 -0
- package/dist/event-manager-D-QCmUgR.js.map +1 -0
- package/dist/fab.js +1 -1
- package/dist/flow-designer-dZnLJOQT.js +1656 -0
- package/dist/flow-designer-dZnLJOQT.js.map +1 -0
- package/dist/flow-designer-node-XMe-jlKg.js +548 -0
- package/dist/flow-designer-node-XMe-jlKg.js.map +1 -0
- package/dist/flow-designer-node.js +4 -0
- package/dist/flow-designer-node.js.map +1 -0
- package/dist/flow-designer.js +16 -0
- package/dist/flow-designer.js.map +1 -0
- package/dist/html-editor.js +358 -0
- package/dist/html-editor.js.map +1 -0
- package/dist/icon-button-CK1ZuE-2.js +247 -0
- package/dist/icon-button-CK1ZuE-2.js.map +1 -0
- package/dist/index.js +29 -6
- package/dist/index.js.map +1 -1
- package/dist/{is-dark-mode-DicqGkCJ.js → is-dark-mode-DOcaw4Yq.js} +2 -27
- package/dist/is-dark-mode-DOcaw4Yq.js.map +1 -0
- package/dist/modal.js +418 -0
- package/dist/modal.js.map +1 -0
- package/dist/{navigation-rail-Lxetd5-Z.js → navigation-rail-DyO0oAZU.js} +306 -2197
- package/dist/navigation-rail-DyO0oAZU.js.map +1 -0
- package/dist/notification-manager.js +268 -0
- package/dist/notification-manager.js.map +1 -0
- package/dist/peacock-loader.js +84 -8
- package/dist/peacock-loader.js.map +1 -1
- package/dist/popover-NC7b1lTq.js +1971 -0
- package/dist/popover-NC7b1lTq.js.map +1 -0
- package/dist/popover-content.js +125 -0
- package/dist/popover-content.js.map +1 -0
- package/dist/popover.js +4 -0
- package/dist/popover.js.map +1 -0
- package/dist/split-button.js +388 -0
- package/dist/split-button.js.map +1 -0
- package/dist/src/__controllers/floating-controller.d.ts +35 -0
- package/dist/src/calendar/base-event.d.ts +10 -0
- package/dist/src/calendar/calendar-column-view.d.ts +41 -0
- package/dist/src/calendar/calendar-event.d.ts +7 -0
- package/dist/src/calendar/calendar-month-view.d.ts +31 -0
- package/dist/src/calendar/calendar.d.ts +65 -0
- package/dist/src/calendar/event-manager.d.ts +17 -0
- package/dist/src/calendar/index.d.ts +4 -0
- package/dist/src/calendar/types.d.ts +13 -0
- package/dist/src/calendar/utils.d.ts +31 -0
- package/dist/src/canvas/canvas.d.ts +92 -0
- package/dist/src/canvas/index.d.ts +2 -0
- package/dist/src/condition-builder/cb-compound-expression.d.ts +31 -0
- package/dist/src/condition-builder/cb-divider.d.ts +26 -0
- package/dist/src/condition-builder/cb-expression.d.ts +31 -0
- package/dist/src/condition-builder/cb-predicate.d.ts +30 -0
- package/dist/src/condition-builder/condition-builder.d.ts +27 -0
- package/dist/src/condition-builder/index.d.ts +5 -0
- package/dist/src/dropdown-button/dropdown-button.d.ts +68 -0
- package/dist/src/dropdown-button/index.d.ts +1 -0
- package/dist/src/flow-designer/commands.d.ts +66 -0
- package/dist/src/flow-designer/flow-designer-node.d.ts +46 -0
- package/dist/src/flow-designer/flow-designer.d.ts +133 -0
- package/dist/src/flow-designer/index.d.ts +7 -0
- package/dist/src/flow-designer/layout.d.ts +30 -0
- package/dist/src/flow-designer/types.d.ts +142 -0
- package/dist/src/flow-designer/validation.d.ts +43 -0
- package/dist/src/flow-designer/workflow-utils.d.ts +40 -0
- package/dist/src/html-editor/html-editor.d.ts +56 -0
- package/dist/src/html-editor/index.d.ts +2 -0
- package/dist/src/index.d.ts +13 -0
- package/dist/src/menu/menu/menu.d.ts +5 -7
- package/dist/src/menu/menu-item/menu-item.d.ts +14 -13
- package/dist/src/modal/index.d.ts +1 -0
- package/dist/src/modal/modal.d.ts +63 -0
- package/dist/src/notification-manager/index.d.ts +1 -0
- package/dist/src/notification-manager/notification-manager.d.ts +44 -0
- package/dist/src/popover/index.d.ts +2 -0
- package/dist/src/popover/popover-content.d.ts +29 -0
- package/dist/src/popover/popover.d.ts +62 -0
- package/dist/src/split-button/index.d.ts +1 -0
- package/dist/src/split-button/split-button.d.ts +72 -0
- package/dist/src/tooltip/tooltip.d.ts +2 -15
- package/dist/test/flow-designer.test.d.ts +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -2
- package/readme.md +2 -2
- package/src/__controllers/floating-controller.ts +237 -0
- package/src/banner/banner.scss +2 -3
- package/src/button/button/button.ts +1 -0
- package/src/calendar/base-event.ts +49 -0
- package/src/calendar/calendar-column-view.scss +326 -0
- package/src/calendar/calendar-column-view.ts +392 -0
- package/src/calendar/calendar-event.ts +20 -0
- package/src/calendar/calendar-month-view.scss +192 -0
- package/src/calendar/calendar-month-view.ts +244 -0
- package/src/calendar/calendar.scss +71 -0
- package/src/calendar/calendar.ts +298 -0
- package/src/calendar/event-manager.ts +117 -0
- package/src/calendar/index.ts +4 -0
- package/src/calendar/types.ts +14 -0
- package/src/calendar/utils.ts +180 -0
- package/src/canvas/canvas.scss +60 -0
- package/src/canvas/canvas.ts +391 -0
- package/src/canvas/index.ts +2 -0
- package/src/condition-builder/cb-compound-expression.scss +37 -0
- package/src/condition-builder/cb-compound-expression.ts +80 -0
- package/src/condition-builder/cb-divider.scss +93 -0
- package/src/condition-builder/cb-divider.ts +56 -0
- package/src/condition-builder/cb-expression.scss +14 -0
- package/src/condition-builder/cb-expression.ts +49 -0
- package/src/condition-builder/cb-predicate.scss +35 -0
- package/src/condition-builder/cb-predicate.ts +102 -0
- package/src/condition-builder/condition-builder.scss +13 -0
- package/src/condition-builder/condition-builder.ts +38 -0
- package/src/condition-builder/index.ts +5 -0
- package/src/dropdown-button/demo/index.html +110 -0
- package/src/dropdown-button/dropdown-button.scss +22 -0
- package/src/dropdown-button/dropdown-button.ts +206 -0
- package/src/dropdown-button/index.ts +1 -0
- package/src/flow-designer/DEMO.md +239 -0
- package/src/flow-designer/commands.ts +278 -0
- package/src/flow-designer/flow-designer-node.ts +172 -0
- package/src/flow-designer/flow-designer.scss +457 -0
- package/src/flow-designer/flow-designer.ts +611 -0
- package/src/flow-designer/index.ts +41 -0
- package/src/flow-designer/layout.ts +357 -0
- package/src/flow-designer/types.ts +166 -0
- package/src/flow-designer/validation.ts +284 -0
- package/src/flow-designer/workflow-utils.ts +282 -0
- package/src/html-editor/html-editor.scss +146 -0
- package/src/html-editor/html-editor.ts +276 -0
- package/src/html-editor/index.ts +3 -0
- package/src/index.ts +25 -0
- package/src/menu/menu/menu.scss +2 -2
- package/src/menu/menu/menu.ts +91 -101
- package/src/menu/menu-item/menu-item.scss +4 -0
- package/src/menu/menu-item/menu-item.ts +82 -78
- package/src/modal/index.ts +1 -0
- package/src/modal/modal.scss +206 -0
- package/src/modal/modal.ts +201 -0
- package/src/notification-manager/index.ts +1 -0
- package/src/notification-manager/notification-manager.scss +113 -0
- package/src/notification-manager/notification-manager.ts +199 -0
- package/src/peacock-loader.ts +71 -0
- package/src/popover/index.ts +2 -0
- package/src/popover/popover-content.scss +69 -0
- package/src/popover/popover-content.ts +51 -0
- package/src/popover/popover.scss +7 -0
- package/src/popover/popover.ts +170 -0
- package/src/split-button/index.ts +1 -0
- package/src/split-button/split-button-colors.scss +56 -0
- package/src/split-button/split-button-sizes.scss +28 -0
- package/src/split-button/split-button.scss +79 -0
- package/src/split-button/split-button.ts +236 -0
- package/src/table/table.ts +2 -2
- package/src/tooltip/tooltip.scss +4 -3
- package/src/tooltip/tooltip.ts +46 -104
- package/dist/button-DouvOfEU.js.map +0 -1
- package/dist/button-group-CEdMwvJJ.js +0 -464
- package/dist/button-group-CEdMwvJJ.js.map +0 -1
- package/dist/is-dark-mode-DicqGkCJ.js.map +0 -1
- package/dist/navigation-rail-Lxetd5-Z.js.map +0 -1
- package/dist/src/menu/menu/MenuSurfaceController.d.ts +0 -18
- package/src/menu/menu/MenuSurfaceController.ts +0 -61
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# Flow Designer Component Demo
|
|
2
|
+
|
|
3
|
+
## Basic Usage
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<wc-flow-designer id="designer"></wc-flow-designer>
|
|
7
|
+
|
|
8
|
+
<script type="module">
|
|
9
|
+
import { FlowDesigner, Workflow } from '@redvars/peacock';
|
|
10
|
+
|
|
11
|
+
// Define a workflow
|
|
12
|
+
const workflow = {
|
|
13
|
+
workflow_id: 'demo_workflow',
|
|
14
|
+
nodes: {
|
|
15
|
+
id: 'start',
|
|
16
|
+
type: 'trigger',
|
|
17
|
+
label: 'Start Process',
|
|
18
|
+
children: [
|
|
19
|
+
{
|
|
20
|
+
id: 'validate',
|
|
21
|
+
type: 'decision',
|
|
22
|
+
label: 'Data Valid?',
|
|
23
|
+
branches: {
|
|
24
|
+
yes: [
|
|
25
|
+
{
|
|
26
|
+
id: 'process',
|
|
27
|
+
type: 'action',
|
|
28
|
+
label: 'Process Data',
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
no: [
|
|
32
|
+
{
|
|
33
|
+
id: 'error',
|
|
34
|
+
type: 'action',
|
|
35
|
+
label: 'Log Error',
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Set workflow on component
|
|
45
|
+
const designer = document.getElementById('designer');
|
|
46
|
+
designer.workflow = workflow;
|
|
47
|
+
|
|
48
|
+
// Listen for changes
|
|
49
|
+
designer.addEventListener('workflow-changed', (e) => {
|
|
50
|
+
console.log('Workflow changed:', e.detail.type);
|
|
51
|
+
console.log('Updated workflow:', e.detail.workflow);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Add a new node
|
|
55
|
+
const newNode = {
|
|
56
|
+
id: 'new_action',
|
|
57
|
+
type: 'action',
|
|
58
|
+
label: 'New Action'
|
|
59
|
+
};
|
|
60
|
+
designer.addNode(newNode, 'start');
|
|
61
|
+
|
|
62
|
+
// Undo/Redo
|
|
63
|
+
if (designer.canUndo()) {
|
|
64
|
+
designer.undo();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (designer.canRedo()) {
|
|
68
|
+
designer.redo();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Validate
|
|
72
|
+
designer.validate();
|
|
73
|
+
|
|
74
|
+
// Export
|
|
75
|
+
const json = designer.exportWorkflow();
|
|
76
|
+
</script>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Customizing Node Templates
|
|
80
|
+
|
|
81
|
+
```html
|
|
82
|
+
<wc-flow-designer id="designer">
|
|
83
|
+
<!-- Custom trigger node template -->
|
|
84
|
+
<div slot="trigger-header" style="color: green; font-weight: bold;">
|
|
85
|
+
🚀 Custom Trigger
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<!-- Custom action node template -->
|
|
89
|
+
<div slot="action-header" style="color: blue;">
|
|
90
|
+
⚙️ Custom Action
|
|
91
|
+
</div>
|
|
92
|
+
<p slot="action-body">Custom action content here</p>
|
|
93
|
+
|
|
94
|
+
<!-- Custom decision template -->
|
|
95
|
+
<div slot="decision-header" style="color: orange;">
|
|
96
|
+
❓ Custom Decision
|
|
97
|
+
</div>
|
|
98
|
+
</wc-flow-designer>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Workflow Structure
|
|
102
|
+
|
|
103
|
+
The component supports complex workflows:
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
{
|
|
107
|
+
workflow_id: 'complex_workflow',
|
|
108
|
+
nodes: {
|
|
109
|
+
id: 'trigger_1',
|
|
110
|
+
type: 'trigger',
|
|
111
|
+
label: 'Input Received',
|
|
112
|
+
|
|
113
|
+
// Sequential flow
|
|
114
|
+
children: [
|
|
115
|
+
{
|
|
116
|
+
id: 'loop_1',
|
|
117
|
+
type: 'loop_start',
|
|
118
|
+
label: 'Begin Loop',
|
|
119
|
+
|
|
120
|
+
children: [
|
|
121
|
+
{
|
|
122
|
+
id: 'decision_1',
|
|
123
|
+
type: 'decision',
|
|
124
|
+
label: 'Check Condition',
|
|
125
|
+
|
|
126
|
+
// Conditional branching
|
|
127
|
+
branches: {
|
|
128
|
+
yes: [
|
|
129
|
+
{
|
|
130
|
+
id: 'fork_1',
|
|
131
|
+
type: 'fork',
|
|
132
|
+
label: 'Parallel Tasks',
|
|
133
|
+
|
|
134
|
+
// Parallel execution
|
|
135
|
+
tasks: [
|
|
136
|
+
{ id: 'task_1', type: 'action', label: 'Task A' },
|
|
137
|
+
{ id: 'task_2', type: 'action', label: 'Task B' }
|
|
138
|
+
],
|
|
139
|
+
|
|
140
|
+
// Join point
|
|
141
|
+
join: {
|
|
142
|
+
id: 'join_1',
|
|
143
|
+
type: 'join',
|
|
144
|
+
label: 'Merge',
|
|
145
|
+
children: [
|
|
146
|
+
{
|
|
147
|
+
id: 'loop_end_1',
|
|
148
|
+
type: 'loop_end',
|
|
149
|
+
label: 'Loop Back',
|
|
150
|
+
target_id: 'loop_1'
|
|
151
|
+
}
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
no: [
|
|
157
|
+
{ id: 'skip', type: 'action', label: 'Skip' }
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Available Node Types
|
|
169
|
+
|
|
170
|
+
- **trigger** - Start of workflow
|
|
171
|
+
- **action** - Perform an action
|
|
172
|
+
- **decision** - Branch on condition (yes/no)
|
|
173
|
+
- **loop_start** - Begin loop
|
|
174
|
+
- **loop_end** - End loop (loop_start target)
|
|
175
|
+
- **fork** - Parallel execution start
|
|
176
|
+
- **join** - Parallel execution end
|
|
177
|
+
|
|
178
|
+
## Events
|
|
179
|
+
|
|
180
|
+
- `workflow-changed` - Emitted when workflow is modified
|
|
181
|
+
- `node-selected` - Emitted when node is clicked
|
|
182
|
+
- `node-edited` - Emitted when node is edited
|
|
183
|
+
- `node-deleted` - Emitted when node is deleted
|
|
184
|
+
- `validation-result` - Emitted when validate() is called
|
|
185
|
+
|
|
186
|
+
## Properties
|
|
187
|
+
|
|
188
|
+
- `workflow: Workflow` - Current workflow definition
|
|
189
|
+
- `readonly: boolean` - Read-only mode (default: false)
|
|
190
|
+
- `disabled: boolean` - Disabled state (default: false)
|
|
191
|
+
- `showValidation: boolean` - Show validation errors (default: false)
|
|
192
|
+
|
|
193
|
+
## Methods
|
|
194
|
+
|
|
195
|
+
- `addNode(node, parentId, connectionType?, branchKey?)` - Add node
|
|
196
|
+
- `deleteNode(nodeId)` - Delete node
|
|
197
|
+
- `editNode(nodeId, updates)` - Edit node
|
|
198
|
+
- `moveNode(nodeId, newParentId, newIndex, connectionType?, branchKey?)` - Move node
|
|
199
|
+
- `undo()` - Undo last operation
|
|
200
|
+
- `redo()` - Redo last undone operation
|
|
201
|
+
- `canUndo()` - Check if undo available
|
|
202
|
+
- `canRedo()` - Check if redo available
|
|
203
|
+
- `validate()` - Validate workflow
|
|
204
|
+
- `exportWorkflow()` - Export as JSON
|
|
205
|
+
|
|
206
|
+
## Validation
|
|
207
|
+
|
|
208
|
+
The component automatically validates workflows after each change:
|
|
209
|
+
|
|
210
|
+
- ✅ Detects circular loops
|
|
211
|
+
- ✅ Detects orphaned nodes
|
|
212
|
+
- ✅ Validates decision branches (yes/no)
|
|
213
|
+
- ✅ Validates fork/join pairs
|
|
214
|
+
- ✅ Validates loop targets
|
|
215
|
+
|
|
216
|
+
Invalid workflows will prompt user confirmation before accepting changes.
|
|
217
|
+
|
|
218
|
+
## Styling
|
|
219
|
+
|
|
220
|
+
Custom CSS properties available:
|
|
221
|
+
|
|
222
|
+
```css
|
|
223
|
+
wc-flow-designer {
|
|
224
|
+
--flow-designer-height: 600px;
|
|
225
|
+
--flow-designer-border-color: var(--color-outline-variant);
|
|
226
|
+
--flow-designer-background: var(--color-surface);
|
|
227
|
+
--flow-designer-border-radius: var(--shape-corner-medium);
|
|
228
|
+
--flow-designer-action-bar-bg: var(--color-surface-container);
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Keyboard Shortcuts
|
|
233
|
+
|
|
234
|
+
- **Ctrl+Z** / **Cmd+Z** - Undo
|
|
235
|
+
- **Ctrl+Y** / **Cmd+Y** - Redo
|
|
236
|
+
- **Delete** - Delete selected node
|
|
237
|
+
- **Enter** / **Space** - Select/activate node
|
|
238
|
+
- **Click + Drag** - Pan canvas
|
|
239
|
+
- **Scroll** - Zoom in/out (via toolbar)
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import type { Workflow, WorkflowNode, WorkflowCommand } from './types.js';
|
|
2
|
+
import {
|
|
3
|
+
findNodeById,
|
|
4
|
+
cloneWorkflow,
|
|
5
|
+
removeNodeById,
|
|
6
|
+
insertNodeIntoWorkflow,
|
|
7
|
+
} from './workflow-utils.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Add Node Command
|
|
11
|
+
*/
|
|
12
|
+
export class AddNodeCommand implements WorkflowCommand {
|
|
13
|
+
description = 'Add node';
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
private nodeToAdd: WorkflowNode,
|
|
17
|
+
private parentNodeId: string,
|
|
18
|
+
private connectionType: 'child' | 'branch' | 'task' = 'child',
|
|
19
|
+
private branchKey?: string
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
execute(workflow: Workflow): Workflow {
|
|
23
|
+
const result = cloneWorkflow(workflow);
|
|
24
|
+
const parent = findNodeById(result.nodes, this.parentNodeId);
|
|
25
|
+
if (!parent) return workflow; // Validation in parent component
|
|
26
|
+
|
|
27
|
+
insertNodeIntoWorkflow(parent, this.nodeToAdd, this.connectionType, this.branchKey);
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
undo(workflow: Workflow): Workflow {
|
|
32
|
+
const result = cloneWorkflow(workflow);
|
|
33
|
+
removeNodeById(result.nodes, this.nodeToAdd.id);
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Delete Node Command
|
|
40
|
+
*/
|
|
41
|
+
export class DeleteNodeCommand implements WorkflowCommand {
|
|
42
|
+
description = 'Delete node';
|
|
43
|
+
private deletedNode: WorkflowNode | null = null;
|
|
44
|
+
private parentReference: {
|
|
45
|
+
parentId: string;
|
|
46
|
+
connectionType: 'child' | 'branch' | 'task';
|
|
47
|
+
branchKey?: string;
|
|
48
|
+
} | null = null;
|
|
49
|
+
|
|
50
|
+
constructor(
|
|
51
|
+
private nodeId: string,
|
|
52
|
+
workflow?: Workflow
|
|
53
|
+
) {
|
|
54
|
+
if (workflow) {
|
|
55
|
+
this.captureNodeContext(workflow);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private captureNodeContext(workflow: Workflow) {
|
|
60
|
+
const node = findNodeById(workflow.nodes, this.nodeId);
|
|
61
|
+
if (!node) return;
|
|
62
|
+
this.deletedNode = cloneWorkflow({ workflow_id: '', nodes: node }).nodes;
|
|
63
|
+
|
|
64
|
+
// Find parent reference
|
|
65
|
+
this.findParentReference(workflow.nodes);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private findParentReference(node: WorkflowNode) {
|
|
69
|
+
if (node.children) {
|
|
70
|
+
const idx = node.children.findIndex((n) => n.id === this.nodeId);
|
|
71
|
+
if (idx !== -1) {
|
|
72
|
+
this.parentReference = {
|
|
73
|
+
parentId: node.id,
|
|
74
|
+
connectionType: 'child',
|
|
75
|
+
};
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
for (const child of node.children) {
|
|
79
|
+
this.findParentReference(child);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (node.tasks) {
|
|
84
|
+
const idx = node.tasks.findIndex((n) => n.id === this.nodeId);
|
|
85
|
+
if (idx !== -1) {
|
|
86
|
+
this.parentReference = {
|
|
87
|
+
parentId: node.id,
|
|
88
|
+
connectionType: 'task',
|
|
89
|
+
};
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
for (const task of node.tasks) {
|
|
93
|
+
this.findParentReference(task);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (node.branches) {
|
|
98
|
+
for (const [branchKey, branchNodes] of Object.entries(node.branches)) {
|
|
99
|
+
const idx = branchNodes.findIndex((n) => n.id === this.nodeId);
|
|
100
|
+
if (idx !== -1) {
|
|
101
|
+
this.parentReference = {
|
|
102
|
+
parentId: node.id,
|
|
103
|
+
connectionType: 'branch',
|
|
104
|
+
branchKey,
|
|
105
|
+
};
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
for (const branchNode of branchNodes) {
|
|
109
|
+
this.findParentReference(branchNode);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
execute(workflow: Workflow): Workflow {
|
|
116
|
+
const result = cloneWorkflow(workflow);
|
|
117
|
+
removeNodeById(result.nodes, this.nodeId);
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
undo(workflow: Workflow): Workflow {
|
|
122
|
+
if (!this.deletedNode || !this.parentReference) return workflow;
|
|
123
|
+
const result = cloneWorkflow(workflow);
|
|
124
|
+
const parent = findNodeById(result.nodes, this.parentReference.parentId);
|
|
125
|
+
if (!parent) return workflow;
|
|
126
|
+
|
|
127
|
+
insertNodeIntoWorkflow(
|
|
128
|
+
parent,
|
|
129
|
+
this.deletedNode,
|
|
130
|
+
this.parentReference.connectionType,
|
|
131
|
+
this.parentReference.branchKey
|
|
132
|
+
);
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Edit Node Command
|
|
139
|
+
*/
|
|
140
|
+
export class EditNodeCommand implements WorkflowCommand {
|
|
141
|
+
description = 'Edit node';
|
|
142
|
+
private previousState: Record<string, any> = {};
|
|
143
|
+
|
|
144
|
+
constructor(
|
|
145
|
+
private nodeId: string,
|
|
146
|
+
private updates: Partial<WorkflowNode>,
|
|
147
|
+
workflow?: Workflow
|
|
148
|
+
) {
|
|
149
|
+
if (workflow) {
|
|
150
|
+
const node = findNodeById(workflow.nodes, nodeId);
|
|
151
|
+
if (node) {
|
|
152
|
+
// Store only edited fields
|
|
153
|
+
Object.keys(updates).forEach((key) => {
|
|
154
|
+
this.previousState[key] = (node as Record<string, any>)[key];
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
execute(workflow: Workflow): Workflow {
|
|
161
|
+
const result = cloneWorkflow(workflow);
|
|
162
|
+
const node = findNodeById(result.nodes, this.nodeId);
|
|
163
|
+
if (!node) return workflow;
|
|
164
|
+
|
|
165
|
+
Object.assign(node, this.updates);
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
undo(workflow: Workflow): Workflow {
|
|
170
|
+
const result = cloneWorkflow(workflow);
|
|
171
|
+
const node = findNodeById(result.nodes, this.nodeId);
|
|
172
|
+
if (!node) return workflow;
|
|
173
|
+
|
|
174
|
+
Object.assign(node, this.previousState);
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Move Node Command - reorder in array or change parent
|
|
181
|
+
*/
|
|
182
|
+
export class MoveNodeCommand implements WorkflowCommand {
|
|
183
|
+
description = 'Move node';
|
|
184
|
+
private previousState: {
|
|
185
|
+
parentId: string;
|
|
186
|
+
index: number;
|
|
187
|
+
connectionType: 'child' | 'branch' | 'task';
|
|
188
|
+
branchKey?: string;
|
|
189
|
+
} | null = null;
|
|
190
|
+
|
|
191
|
+
constructor(
|
|
192
|
+
private nodeId: string,
|
|
193
|
+
private newParentId: string,
|
|
194
|
+
private newIndex: number,
|
|
195
|
+
private newConnectionType: 'child' | 'branch' | 'task' = 'child',
|
|
196
|
+
private newBranchKey?: string,
|
|
197
|
+
workflow?: Workflow
|
|
198
|
+
) {
|
|
199
|
+
if (workflow) {
|
|
200
|
+
this.captureCurrentPosition(workflow);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private captureCurrentPosition(workflow: Workflow) {
|
|
205
|
+
// Store current parent/position for undo
|
|
206
|
+
// Implementation depends on finding current parent location
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
execute(workflow: Workflow): Workflow {
|
|
210
|
+
// Remove from old parent, insert at new parent
|
|
211
|
+
let result = cloneWorkflow(workflow);
|
|
212
|
+
result.nodes = removeNodeById(result.nodes, this.nodeId);
|
|
213
|
+
const newParent = findNodeById(result.nodes, this.newParentId);
|
|
214
|
+
if (!newParent) return workflow;
|
|
215
|
+
|
|
216
|
+
const node = findNodeById(workflow.nodes, this.nodeId);
|
|
217
|
+
if (!node) return workflow;
|
|
218
|
+
|
|
219
|
+
insertNodeIntoWorkflow(
|
|
220
|
+
newParent,
|
|
221
|
+
node,
|
|
222
|
+
this.newConnectionType,
|
|
223
|
+
this.newBranchKey
|
|
224
|
+
);
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
undo(workflow: Workflow): Workflow {
|
|
229
|
+
// Restore to previous position
|
|
230
|
+
if (!this.previousState) return workflow;
|
|
231
|
+
let result = cloneWorkflow(workflow);
|
|
232
|
+
result.nodes = removeNodeById(result.nodes, this.nodeId);
|
|
233
|
+
const prevParent = findNodeById(result.nodes, this.previousState.parentId);
|
|
234
|
+
if (!prevParent) return workflow;
|
|
235
|
+
|
|
236
|
+
const node = findNodeById(workflow.nodes, this.nodeId);
|
|
237
|
+
if (!node) return workflow;
|
|
238
|
+
|
|
239
|
+
insertNodeIntoWorkflow(
|
|
240
|
+
prevParent,
|
|
241
|
+
node,
|
|
242
|
+
this.previousState.connectionType,
|
|
243
|
+
this.previousState.branchKey
|
|
244
|
+
);
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Batch Command - combine multiple commands into one undo/redo step
|
|
251
|
+
*/
|
|
252
|
+
export class BatchCommand implements WorkflowCommand {
|
|
253
|
+
description: string;
|
|
254
|
+
|
|
255
|
+
constructor(
|
|
256
|
+
private commands: WorkflowCommand[],
|
|
257
|
+
description: string = `Batch operation (${commands.length} steps)`
|
|
258
|
+
) {
|
|
259
|
+
this.description = description;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
execute(workflow: Workflow): Workflow {
|
|
263
|
+
let result = workflow;
|
|
264
|
+
for (const command of this.commands) {
|
|
265
|
+
result = command.execute(result);
|
|
266
|
+
}
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
undo(workflow: Workflow): Workflow {
|
|
271
|
+
let result = workflow;
|
|
272
|
+
// Execute commands in reverse order to undo
|
|
273
|
+
for (let i = this.commands.length - 1; i >= 0; i--) {
|
|
274
|
+
result = this.commands[i].undo(result);
|
|
275
|
+
}
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { html, LitElement, nothing } from 'lit';
|
|
2
|
+
import { property, state } from 'lit/decorators.js';
|
|
3
|
+
import IndividualComponent from '@/IndividualComponent.js';
|
|
4
|
+
import styles from './flow-designer.scss';
|
|
5
|
+
import type { WorkflowNode } from './types.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Individual node component for flow designer
|
|
9
|
+
* Renders a single workflow node with customizable slot templates
|
|
10
|
+
*
|
|
11
|
+
* @tag wc-flow-designer-node
|
|
12
|
+
* @rawTag flow-designer-node
|
|
13
|
+
* @wip true
|
|
14
|
+
*/
|
|
15
|
+
@IndividualComponent
|
|
16
|
+
export class FlowDesignerNode extends LitElement {
|
|
17
|
+
static styles = [styles];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The workflow node to render
|
|
21
|
+
*/
|
|
22
|
+
@property({ type: Object })
|
|
23
|
+
node: WorkflowNode = { id: '', type: 'action', label: '' };
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whether this node is currently selected
|
|
27
|
+
*/
|
|
28
|
+
@property({ type: Boolean, reflect: true, attribute: 'selected' })
|
|
29
|
+
isSelected: boolean = false;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Whether this node is in edit mode
|
|
33
|
+
*/
|
|
34
|
+
@property({ type: Boolean, reflect: true, attribute: 'editing' })
|
|
35
|
+
isEditing: boolean = false;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Whether this node is disabled
|
|
39
|
+
*/
|
|
40
|
+
@property({ type: Boolean, reflect: true })
|
|
41
|
+
disabled: boolean = false;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Whether to show the delete button
|
|
45
|
+
*/
|
|
46
|
+
@property({ type: Boolean, attribute: 'show-delete' })
|
|
47
|
+
showDelete: boolean = true;
|
|
48
|
+
|
|
49
|
+
private _handleClick = () => {
|
|
50
|
+
this.dispatchEvent(
|
|
51
|
+
new CustomEvent('node-click', {
|
|
52
|
+
detail: { nodeId: this.node.id },
|
|
53
|
+
bubbles: true,
|
|
54
|
+
composed: true,
|
|
55
|
+
})
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
private _handleDoubleClick = () => {
|
|
60
|
+
this.dispatchEvent(
|
|
61
|
+
new CustomEvent('node-edit-start', {
|
|
62
|
+
detail: { nodeId: this.node.id },
|
|
63
|
+
bubbles: true,
|
|
64
|
+
composed: true,
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
private _handleDelete = (e: Event) => {
|
|
70
|
+
e.stopPropagation();
|
|
71
|
+
if (confirm(`Delete "${this.node.label}"?`)) {
|
|
72
|
+
this.dispatchEvent(
|
|
73
|
+
new CustomEvent('node-delete', {
|
|
74
|
+
detail: { nodeId: this.node.id },
|
|
75
|
+
bubbles: true,
|
|
76
|
+
composed: true,
|
|
77
|
+
})
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
private _handleMouseEnter = () => {
|
|
83
|
+
// Node hover state handled via CSS
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
private _handleMouseLeave = () => {
|
|
87
|
+
// Node hover state handled via CSS
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
render() {
|
|
91
|
+
const { node, isSelected, isEditing, disabled } = this;
|
|
92
|
+
const nodeType = node.type || 'action';
|
|
93
|
+
|
|
94
|
+
return html`
|
|
95
|
+
<div
|
|
96
|
+
class="node-card ${nodeType}"
|
|
97
|
+
?selected=${isSelected}
|
|
98
|
+
?editing=${isEditing}
|
|
99
|
+
?disabled=${disabled}
|
|
100
|
+
@click=${this._handleClick}
|
|
101
|
+
@dblclick=${this._handleDoubleClick}
|
|
102
|
+
role="button"
|
|
103
|
+
tabindex="0"
|
|
104
|
+
@keydown=${(e: KeyboardEvent) => {
|
|
105
|
+
if (e.key === 'Enter' || e.key === ' ') this._handleClick();
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
<!-- Customizable header slot -->
|
|
109
|
+
<slot name="${nodeType}-header">
|
|
110
|
+
${this._renderDefaultHeader()}
|
|
111
|
+
</slot>
|
|
112
|
+
|
|
113
|
+
<!-- Customizable body slot -->
|
|
114
|
+
<slot name="${nodeType}-body">${this._renderDefaultBody()}</slot>
|
|
115
|
+
|
|
116
|
+
<!-- Action buttons -->
|
|
117
|
+
${this.isEditing
|
|
118
|
+
? html`
|
|
119
|
+
<div class="node-actions">
|
|
120
|
+
<button class="btn-sm" @click=${this._handleDelete}>
|
|
121
|
+
Delete
|
|
122
|
+
</button>
|
|
123
|
+
</div>
|
|
124
|
+
`
|
|
125
|
+
: nothing}
|
|
126
|
+
</div>
|
|
127
|
+
`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private _renderDefaultHeader() {
|
|
131
|
+
const { node } = this;
|
|
132
|
+
const iconMap: Record<string, string> = {
|
|
133
|
+
trigger: 'play-circle',
|
|
134
|
+
action: 'check-circle',
|
|
135
|
+
decision: 'help-circle',
|
|
136
|
+
loop_start: 'repeat',
|
|
137
|
+
loop_end: 'repeat',
|
|
138
|
+
fork: 'git-branch',
|
|
139
|
+
join: 'git-merge',
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const icon = iconMap[node.type] || 'activity';
|
|
143
|
+
|
|
144
|
+
return html`
|
|
145
|
+
<div class="node-header">
|
|
146
|
+
<wc-icon name=${icon} class="node-icon"></wc-icon>
|
|
147
|
+
<span class="node-title">${node.label}</span>
|
|
148
|
+
</div>
|
|
149
|
+
`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private _renderDefaultBody() {
|
|
153
|
+
const { node } = this;
|
|
154
|
+
return html`
|
|
155
|
+
<div class="node-body">
|
|
156
|
+
${node.description
|
|
157
|
+
? html`<p class="node-description">${node.description}</p>`
|
|
158
|
+
: nothing}
|
|
159
|
+
<div class="node-metadata">
|
|
160
|
+
<span class="node-type-tag">${node.type}</span>
|
|
161
|
+
${node.id ? html`<span class="node-id">${node.id}</span>` : nothing}
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
`;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
declare global {
|
|
169
|
+
interface HTMLElementTagNameMap {
|
|
170
|
+
'wc-flow-designer-node': FlowDesignerNode;
|
|
171
|
+
}
|
|
172
|
+
}
|