devglide 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +338 -0
- package/bin/claude-md-template.js +94 -0
- package/bin/devglide.js +387 -0
- package/package.json +85 -0
- package/pnpm-workspace.yaml +3 -0
- package/src/apps/coder/.turbo/turbo-lint.log +5 -0
- package/src/apps/coder/package.json +16 -0
- package/src/apps/coder/public/favicon.svg +7 -0
- package/src/apps/coder/public/page.css +275 -0
- package/src/apps/coder/public/page.js +528 -0
- package/src/apps/coder/server.js +3 -0
- package/src/apps/documentation/public/page.css +597 -0
- package/src/apps/documentation/public/page.js +609 -0
- package/src/apps/kanban/.turbo/turbo-lint.log +97 -0
- package/src/apps/kanban/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/kanban/package.json +32 -0
- package/src/apps/kanban/public/favicon.svg +7 -0
- package/src/apps/kanban/public/page.css +1010 -0
- package/src/apps/kanban/public/page.js +1730 -0
- package/src/apps/kanban/public/vendor/marked.min.js +6 -0
- package/src/apps/kanban/public/vendor/sortable.min.js +2 -0
- package/src/apps/kanban/src/db.ts +319 -0
- package/src/apps/kanban/src/index.ts +14 -0
- package/src/apps/kanban/src/mcp-helpers.test.ts +88 -0
- package/src/apps/kanban/src/mcp-helpers.ts +60 -0
- package/src/apps/kanban/src/mcp.ts +59 -0
- package/src/apps/kanban/src/routes/attachments.ts +161 -0
- package/src/apps/kanban/src/routes/features.ts +233 -0
- package/src/apps/kanban/src/routes/issues.ts +373 -0
- package/src/apps/kanban/src/tools/feature-tools.ts +164 -0
- package/src/apps/kanban/src/tools/item-tools.ts +307 -0
- package/src/apps/kanban/src/tools/versioned-entry-tools.ts +72 -0
- package/src/apps/kanban/tsconfig.check.json +9 -0
- package/src/apps/kanban/tsconfig.json +9 -0
- package/src/apps/keymap/.turbo/turbo-lint.log +5 -0
- package/src/apps/keymap/package.json +16 -0
- package/src/apps/keymap/public/page.css +275 -0
- package/src/apps/keymap/public/page.js +294 -0
- package/src/apps/keymap/server.js +25 -0
- package/src/apps/log/.turbo/turbo-build.log +5 -0
- package/src/apps/log/.turbo/turbo-lint.log +45 -0
- package/src/apps/log/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/log/node_modules/.bin/tsc +21 -0
- package/src/apps/log/node_modules/.bin/tsserver +21 -0
- package/src/apps/log/node_modules/.bin/tsx +21 -0
- package/src/apps/log/package.json +36 -0
- package/src/apps/log/public/console-sniffer.js +221 -0
- package/src/apps/log/public/favicon.svg +7 -0
- package/src/apps/log/public/page.css +322 -0
- package/src/apps/log/public/page.js +463 -0
- package/src/apps/log/src/index.ts +9 -0
- package/src/apps/log/src/mcp.ts +122 -0
- package/src/apps/log/src/routes/log.ts +333 -0
- package/src/apps/log/src/routes/status.ts +25 -0
- package/src/apps/log/src/server-sniffer.ts +118 -0
- package/src/apps/log/src/services/file-patterns.ts +39 -0
- package/src/apps/log/src/services/file-tailer.ts +228 -0
- package/src/apps/log/src/services/line-parser.ts +94 -0
- package/src/apps/log/src/services/log-writer.ts +39 -0
- package/src/apps/log/tsconfig.json +8 -0
- package/src/apps/prompts/.turbo/turbo-build.log +5 -0
- package/src/apps/prompts/.turbo/turbo-lint.log +24 -0
- package/src/apps/prompts/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/prompts/mcp.ts +175 -0
- package/src/apps/prompts/node_modules/.bin/tsc +21 -0
- package/src/apps/prompts/node_modules/.bin/tsserver +21 -0
- package/src/apps/prompts/node_modules/.bin/tsx +21 -0
- package/src/apps/prompts/package.json +25 -0
- package/src/apps/prompts/public/page.css +315 -0
- package/src/apps/prompts/public/page.js +541 -0
- package/src/apps/prompts/services/prompt-store.ts +212 -0
- package/src/apps/prompts/src/index.ts +9 -0
- package/src/apps/prompts/tsconfig.json +8 -0
- package/src/apps/prompts/types.ts +27 -0
- package/src/apps/shell/.turbo/turbo-build.log +5 -0
- package/src/apps/shell/.turbo/turbo-lint.log +34 -0
- package/src/apps/shell/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/shell/package.json +35 -0
- package/src/apps/shell/public/favicon.svg +7 -0
- package/src/apps/shell/public/page.css +407 -0
- package/src/apps/shell/public/page.js +1577 -0
- package/src/apps/shell/src/index.ts +150 -0
- package/src/apps/shell/src/mcp.ts +398 -0
- package/src/apps/shell/src/shell-types.ts +41 -0
- package/src/apps/shell/tsconfig.json +8 -0
- package/src/apps/test/.turbo/turbo-build.log +5 -0
- package/src/apps/test/.turbo/turbo-lint.log +27 -0
- package/src/apps/test/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/test/node_modules/.bin/tsc +21 -0
- package/src/apps/test/node_modules/.bin/tsserver +21 -0
- package/src/apps/test/node_modules/.bin/tsx +21 -0
- package/src/apps/test/node_modules/.bin/uuid +21 -0
- package/src/apps/test/package.json +35 -0
- package/src/apps/test/public/favicon.svg +7 -0
- package/src/apps/test/public/page.css +499 -0
- package/src/apps/test/public/page.js +417 -0
- package/src/apps/test/public/scenario-runner.js +450 -0
- package/src/apps/test/src/index.ts +9 -0
- package/src/apps/test/src/mcp.ts +192 -0
- package/src/apps/test/src/routes/trigger.ts +285 -0
- package/src/apps/test/src/services/scenario-broadcaster.ts +60 -0
- package/src/apps/test/src/services/scenario-manager.ts +361 -0
- package/src/apps/test/src/services/scenario-store.ts +145 -0
- package/src/apps/test/tsconfig.json +8 -0
- package/src/apps/vocabulary/.turbo/turbo-build.log +5 -0
- package/src/apps/vocabulary/.turbo/turbo-lint.log +25 -0
- package/src/apps/vocabulary/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/vocabulary/mcp.ts +173 -0
- package/src/apps/vocabulary/node_modules/.bin/tsc +21 -0
- package/src/apps/vocabulary/node_modules/.bin/tsserver +21 -0
- package/src/apps/vocabulary/node_modules/.bin/tsx +21 -0
- package/src/apps/vocabulary/package.json +25 -0
- package/src/apps/vocabulary/public/page.css +247 -0
- package/src/apps/vocabulary/public/page.js +444 -0
- package/src/apps/vocabulary/services/vocabulary-store.ts +179 -0
- package/src/apps/vocabulary/src/index.ts +10 -0
- package/src/apps/vocabulary/tsconfig.json +8 -0
- package/src/apps/vocabulary/types.ts +22 -0
- package/src/apps/voice/.turbo/turbo-build.log +5 -0
- package/src/apps/voice/.turbo/turbo-lint.log +43 -0
- package/src/apps/voice/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/voice/node_modules/.bin/openai +21 -0
- package/src/apps/voice/node_modules/.bin/tsc +21 -0
- package/src/apps/voice/node_modules/.bin/tsserver +21 -0
- package/src/apps/voice/node_modules/.bin/tsx +21 -0
- package/src/apps/voice/package.json +35 -0
- package/src/apps/voice/public/favicon.svg +7 -0
- package/src/apps/voice/public/page.css +388 -0
- package/src/apps/voice/public/page.js +718 -0
- package/src/apps/voice/src/index.ts +10 -0
- package/src/apps/voice/src/mcp.ts +70 -0
- package/src/apps/voice/src/providers/index.ts +85 -0
- package/src/apps/voice/src/providers/openai-compatible.ts +94 -0
- package/src/apps/voice/src/providers/types.ts +27 -0
- package/src/apps/voice/src/routes/config.ts +118 -0
- package/src/apps/voice/src/routes/transcribe.ts +90 -0
- package/src/apps/voice/src/services/config-store.ts +129 -0
- package/src/apps/voice/src/services/stats.ts +108 -0
- package/src/apps/voice/src/transcribe.ts +11 -0
- package/src/apps/voice/src/utils/mime.ts +16 -0
- package/src/apps/voice/tsconfig.json +8 -0
- package/src/apps/workflow/.turbo/turbo-build.log +5 -0
- package/src/apps/workflow/.turbo/turbo-lint.log +96 -0
- package/src/apps/workflow/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/workflow/engine/executors/decision-executor.ts +87 -0
- package/src/apps/workflow/engine/executors/file-executor.ts +90 -0
- package/src/apps/workflow/engine/executors/git-executor.ts +137 -0
- package/src/apps/workflow/engine/executors/http-executor.ts +65 -0
- package/src/apps/workflow/engine/executors/index.ts +28 -0
- package/src/apps/workflow/engine/executors/kanban-executor.ts +154 -0
- package/src/apps/workflow/engine/executors/llm-executor.ts +46 -0
- package/src/apps/workflow/engine/executors/log-executor.ts +62 -0
- package/src/apps/workflow/engine/executors/loop-executor.ts +14 -0
- package/src/apps/workflow/engine/executors/shell-executor.ts +107 -0
- package/src/apps/workflow/engine/executors/sub-workflow-executor.ts +61 -0
- package/src/apps/workflow/engine/executors/test-executor.ts +73 -0
- package/src/apps/workflow/engine/executors/trigger-executor.ts +39 -0
- package/src/apps/workflow/engine/expression-evaluator.ts +117 -0
- package/src/apps/workflow/engine/graph-runner.ts +438 -0
- package/src/apps/workflow/engine/node-executor.ts +104 -0
- package/src/apps/workflow/engine/node-registry.ts +15 -0
- package/src/apps/workflow/engine/variable-resolver.ts +109 -0
- package/src/apps/workflow/mcp.ts +223 -0
- package/src/apps/workflow/node_modules/.bin/tsc +21 -0
- package/src/apps/workflow/node_modules/.bin/tsserver +21 -0
- package/src/apps/workflow/node_modules/.bin/tsx +21 -0
- package/src/apps/workflow/package.json +25 -0
- package/src/apps/workflow/public/editor/canvas.js +366 -0
- package/src/apps/workflow/public/editor/drag-manager.js +326 -0
- package/src/apps/workflow/public/editor/edge-renderer.js +235 -0
- package/src/apps/workflow/public/editor/history-manager.js +147 -0
- package/src/apps/workflow/public/editor/layout-engine.js +159 -0
- package/src/apps/workflow/public/editor/node-renderer.js +199 -0
- package/src/apps/workflow/public/editor/selection-manager.js +193 -0
- package/src/apps/workflow/public/favicon.svg +7 -0
- package/src/apps/workflow/public/models/node-types.js +300 -0
- package/src/apps/workflow/public/models/workflow-model.js +257 -0
- package/src/apps/workflow/public/page.css +406 -0
- package/src/apps/workflow/public/page.js +658 -0
- package/src/apps/workflow/public/panels/inspector.js +360 -0
- package/src/apps/workflow/public/panels/palette.js +106 -0
- package/src/apps/workflow/public/panels/run-view.js +275 -0
- package/src/apps/workflow/public/panels/toolbar.js +232 -0
- package/src/apps/workflow/public/panels/workflow-list.js +237 -0
- package/src/apps/workflow/public/state/store.js +47 -0
- package/src/apps/workflow/services/custom-node-loader.ts +48 -0
- package/src/apps/workflow/services/legacy-converter.ts +72 -0
- package/src/apps/workflow/services/run-manager.ts +190 -0
- package/src/apps/workflow/services/workflow-store.ts +424 -0
- package/src/apps/workflow/services/workflow-validator.test.ts +103 -0
- package/src/apps/workflow/services/workflow-validator.ts +98 -0
- package/src/apps/workflow/src/index.ts +10 -0
- package/src/apps/workflow/templates/ci-pipeline.json +18 -0
- package/src/apps/workflow/templates/code-review.json +22 -0
- package/src/apps/workflow/templates/kanban-testing.json +24 -0
- package/src/apps/workflow/tsconfig.json +8 -0
- package/src/apps/workflow/types.ts +268 -0
- package/src/packages/auth-middleware.ts +14 -0
- package/src/packages/design-tokens/.turbo/turbo-build.log +10 -0
- package/src/packages/design-tokens/STYLEGUIDE.md +414 -0
- package/src/packages/design-tokens/build.js +413 -0
- package/src/packages/design-tokens/demo/index.html +1367 -0
- package/src/packages/design-tokens/demo/proposition-a.html +717 -0
- package/src/packages/design-tokens/demo/proposition-b.html +1239 -0
- package/src/packages/design-tokens/demo/proposition-c.html +1049 -0
- package/src/packages/design-tokens/dist/tailwind-preset.js +115 -0
- package/src/packages/design-tokens/dist/tokens.css +345 -0
- package/src/packages/design-tokens/dist/tokens.d.ts +229 -0
- package/src/packages/design-tokens/dist/tokens.js +386 -0
- package/src/packages/design-tokens/package.json +25 -0
- package/src/packages/design-tokens/tokens.json +228 -0
- package/src/packages/devtools-middleware.ts +22 -0
- package/src/packages/eslint-config/index.js +63 -0
- package/src/packages/eslint-config/node_modules/.bin/eslint +21 -0
- package/src/packages/eslint-config/package.json +18 -0
- package/src/packages/json-file-store.ts +232 -0
- package/src/packages/mcp-utils/.turbo/turbo-build.log +5 -0
- package/src/packages/mcp-utils/dist/index.d.ts +33 -0
- package/src/packages/mcp-utils/dist/index.d.ts.map +1 -0
- package/src/packages/mcp-utils/dist/index.js +126 -0
- package/src/packages/mcp-utils/dist/index.js.map +1 -0
- package/src/packages/mcp-utils/node_modules/.bin/tsc +21 -0
- package/src/packages/mcp-utils/node_modules/.bin/tsserver +21 -0
- package/src/packages/mcp-utils/package.json +32 -0
- package/src/packages/mcp-utils/src/index.ts +171 -0
- package/src/packages/mcp-utils/tsconfig.json +9 -0
- package/src/packages/paths.ts +18 -0
- package/src/packages/project-context/index.js +55 -0
- package/src/packages/project-context/package.json +13 -0
- package/src/packages/project-store.ts +127 -0
- package/src/packages/server-sniffer.ts +132 -0
- package/src/packages/shared-assets/favicon.svg +7 -0
- package/src/packages/shared-assets/keymap-registry.js +512 -0
- package/src/packages/shared-assets/logo.svg +6 -0
- package/src/packages/shared-assets/package.json +11 -0
- package/src/packages/shared-assets/ui-utils.js +48 -0
- package/src/packages/shared-assets/voice-widget.d.ts +37 -0
- package/src/packages/shared-assets/voice-widget.js +695 -0
- package/src/packages/shared-types/.turbo/turbo-build.log +5 -0
- package/src/packages/shared-types/dist/index.d.ts +39 -0
- package/src/packages/shared-types/dist/index.d.ts.map +1 -0
- package/src/packages/shared-types/node_modules/.bin/tsc +21 -0
- package/src/packages/shared-types/node_modules/.bin/tsserver +21 -0
- package/src/packages/shared-types/package.json +25 -0
- package/src/packages/shared-types/src/index.ts +41 -0
- package/src/packages/shared-types/tsconfig.json +11 -0
- package/src/packages/tsconfig/base.json +15 -0
- package/src/packages/tsconfig/next.json +14 -0
- package/src/packages/tsconfig/node.json +11 -0
- package/src/packages/tsconfig/package.json +10 -0
- package/turbo.json +25 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
// ── Workflow Editor — Dynamic Property Panel ────────────────────────────
|
|
2
|
+
// Right sidebar showing properties of the selected node or the workflow.
|
|
3
|
+
|
|
4
|
+
import { store } from '../state/store.js';
|
|
5
|
+
import { NODE_TYPES } from '../models/node-types.js';
|
|
6
|
+
import { WorkflowModel } from '../models/workflow-model.js';
|
|
7
|
+
|
|
8
|
+
let _container = null;
|
|
9
|
+
let _unsubs = [];
|
|
10
|
+
|
|
11
|
+
function esc(s) {
|
|
12
|
+
return String(s).replace(/&/g, '&').replace(/"/g, '"')
|
|
13
|
+
.replace(/</g, '<').replace(/>/g, '>');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ── Field renderer ───────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
function renderField(field, config) {
|
|
19
|
+
const val = config[field.key] ?? '';
|
|
20
|
+
const id = `wb-cfg-${field.key}`;
|
|
21
|
+
|
|
22
|
+
switch (field.type) {
|
|
23
|
+
case 'select':
|
|
24
|
+
return `
|
|
25
|
+
<div class="wb-inspector-field">
|
|
26
|
+
<div class="wb-inspector-label">${esc(field.label)}</div>
|
|
27
|
+
<select id="${id}">
|
|
28
|
+
${(field.options ?? []).map(o =>
|
|
29
|
+
`<option value="${esc(o)}"${val === o ? ' selected' : ''}>${esc(o)}</option>`
|
|
30
|
+
).join('')}
|
|
31
|
+
</select>
|
|
32
|
+
</div>`;
|
|
33
|
+
|
|
34
|
+
case 'textarea':
|
|
35
|
+
return `
|
|
36
|
+
<div class="wb-inspector-field">
|
|
37
|
+
<div class="wb-inspector-label">${esc(field.label)}</div>
|
|
38
|
+
<textarea id="${id}" rows="4" placeholder="${esc(field.label)}">${esc(val)}</textarea>
|
|
39
|
+
</div>`;
|
|
40
|
+
|
|
41
|
+
case 'checkbox':
|
|
42
|
+
return `
|
|
43
|
+
<div class="wb-inspector-field">
|
|
44
|
+
<label style="display:flex;align-items:center;gap:var(--df-space-2);cursor:pointer;">
|
|
45
|
+
<input type="checkbox" id="${id}"${val ? ' checked' : ''} />
|
|
46
|
+
<span>${esc(field.label)}</span>
|
|
47
|
+
</label>
|
|
48
|
+
</div>`;
|
|
49
|
+
|
|
50
|
+
case 'number':
|
|
51
|
+
return `
|
|
52
|
+
<div class="wb-inspector-field">
|
|
53
|
+
<div class="wb-inspector-label">${esc(field.label)}</div>
|
|
54
|
+
<input type="number" id="${id}" value="${esc(val)}" />
|
|
55
|
+
</div>`;
|
|
56
|
+
|
|
57
|
+
default: // 'text'
|
|
58
|
+
return `
|
|
59
|
+
<div class="wb-inspector-field">
|
|
60
|
+
<div class="wb-inspector-label">${esc(field.label)}</div>
|
|
61
|
+
<input type="text" id="${id}" value="${esc(val)}" placeholder="${esc(field.label)}" />
|
|
62
|
+
</div>`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── Node inspector ──────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
function renderNodeInspector(node) {
|
|
69
|
+
const typeDef = NODE_TYPES[node.type];
|
|
70
|
+
const config = node.config || {};
|
|
71
|
+
const fields = typeDef?.configFields ?? [];
|
|
72
|
+
|
|
73
|
+
let html = `
|
|
74
|
+
<div class="wb-inspector-header">${esc(typeDef?.label ?? node.type)} Properties</div>
|
|
75
|
+
<div class="wb-inspector-field">
|
|
76
|
+
<div class="wb-inspector-label">Label</div>
|
|
77
|
+
<input type="text" id="wb-node-label" value="${esc(node.label)}" />
|
|
78
|
+
</div>`;
|
|
79
|
+
|
|
80
|
+
for (const field of fields) {
|
|
81
|
+
if (field.showWhen) {
|
|
82
|
+
const [key, val] = Object.entries(field.showWhen)[0];
|
|
83
|
+
if (config[key] !== val) continue;
|
|
84
|
+
}
|
|
85
|
+
html += renderField(field, config);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Decision port editor
|
|
89
|
+
if (node.type === 'decision') {
|
|
90
|
+
const ports = Array.isArray(config.ports) ? config.ports : [];
|
|
91
|
+
html += `
|
|
92
|
+
<div class="wb-inspector-field">
|
|
93
|
+
<div class="wb-inspector-label" style="display:flex;justify-content:space-between;align-items:center;">
|
|
94
|
+
<span>Output Ports</span>
|
|
95
|
+
<button id="wb-add-port" style="font-size:var(--df-font-size-xs);padding:2px 8px;cursor:pointer;">+ Add</button>
|
|
96
|
+
</div>
|
|
97
|
+
<div id="wb-port-list" style="display:flex;flex-direction:column;gap:var(--df-space-1);margin-top:var(--df-space-1);">`;
|
|
98
|
+
for (let i = 0; i < ports.length; i++) {
|
|
99
|
+
const p = ports[i];
|
|
100
|
+
html += `
|
|
101
|
+
<div class="wb-port-row" style="display:flex;gap:var(--df-space-1);align-items:center;" data-port-idx="${i}">
|
|
102
|
+
<input type="text" class="wb-port-label" value="${esc(p.label ?? '')}"
|
|
103
|
+
placeholder="Label" style="flex:1;min-width:0;" />
|
|
104
|
+
<input type="text" class="wb-port-condition" value="${esc(p.condition ?? '')}"
|
|
105
|
+
placeholder="Condition" style="flex:1;min-width:0;" />
|
|
106
|
+
<button class="wb-remove-port" data-port-idx="${i}"
|
|
107
|
+
style="padding:2px 6px;cursor:pointer;flex-shrink:0;">×</button>
|
|
108
|
+
</div>`;
|
|
109
|
+
}
|
|
110
|
+
if (ports.length === 0) {
|
|
111
|
+
html += `<div style="font-size:var(--df-font-size-xs);color:var(--df-color-text-muted);">No ports — add at least one to create branches</div>`;
|
|
112
|
+
}
|
|
113
|
+
html += `
|
|
114
|
+
</div>
|
|
115
|
+
</div>`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// For non-step types, show instructions at the bottom as an optional field
|
|
119
|
+
if (node.type !== 'step' && fields.length > 0) {
|
|
120
|
+
html += `
|
|
121
|
+
<div class="wb-inspector-field">
|
|
122
|
+
<div class="wb-inspector-label">Instructions</div>
|
|
123
|
+
<textarea id="wb-node-instructions" rows="3"
|
|
124
|
+
placeholder="Optional AI instructions for this node">${esc(config.instructions ?? '')}</textarea>
|
|
125
|
+
</div>`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return html;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ── Workflow inspector (no selection) ───────────────────────────────────
|
|
132
|
+
|
|
133
|
+
function renderWorkflowInspector() {
|
|
134
|
+
const wf = store.get('workflow');
|
|
135
|
+
if (!wf) {
|
|
136
|
+
return `
|
|
137
|
+
<div class="wb-inspector-header">Workflow</div>
|
|
138
|
+
<div class="wb-inspector-field" style="color:var(--df-color-text-muted);font-size:var(--df-font-size-xs);">
|
|
139
|
+
No workflow loaded
|
|
140
|
+
</div>`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const nodeCount = wf.nodes?.length ?? 0;
|
|
144
|
+
const edgeCount = wf.edges?.length ?? 0;
|
|
145
|
+
|
|
146
|
+
return `
|
|
147
|
+
<div class="wb-inspector-header">Workflow Properties</div>
|
|
148
|
+
<div class="wb-inspector-field">
|
|
149
|
+
<div class="wb-inspector-label">Name</div>
|
|
150
|
+
<input type="text" id="wb-wf-name" value="${esc(wf.name)}" />
|
|
151
|
+
</div>
|
|
152
|
+
<div class="wb-inspector-field">
|
|
153
|
+
<div class="wb-inspector-label">Description</div>
|
|
154
|
+
<textarea id="wb-wf-description" rows="3" placeholder="Workflow description">${esc((wf.description ?? '').replace(/\\n/g, '\n'))}</textarea>
|
|
155
|
+
</div>
|
|
156
|
+
<div class="wb-inspector-field">
|
|
157
|
+
<div class="wb-inspector-label">Stats</div>
|
|
158
|
+
<div style="font-size:var(--df-font-size-xs);color:var(--df-color-text-muted);">
|
|
159
|
+
${nodeCount} node${nodeCount !== 1 ? 's' : ''} · ${edgeCount} edge${edgeCount !== 1 ? 's' : ''}
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
<div class="wb-inspector-field">
|
|
163
|
+
<div class="wb-inspector-label">Scope</div>
|
|
164
|
+
<div style="font-size:var(--df-font-size-xs);color:var(--df-color-text-muted);">
|
|
165
|
+
${wf.global ? 'Global — visible in all projects' : 'Project-scoped'}
|
|
166
|
+
</div>
|
|
167
|
+
</div>`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── Event wiring ────────────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
function bindNodeEvents(nodeId) {
|
|
173
|
+
if (!_container) return;
|
|
174
|
+
|
|
175
|
+
const node = WorkflowModel.getNode(nodeId);
|
|
176
|
+
if (!node) return;
|
|
177
|
+
|
|
178
|
+
// Label (always present)
|
|
179
|
+
const labelInput = _container.querySelector('#wb-node-label');
|
|
180
|
+
if (labelInput) {
|
|
181
|
+
labelInput.addEventListener('input', () => {
|
|
182
|
+
WorkflowModel.updateNode(nodeId, { label: labelInput.value });
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Dynamic config fields
|
|
187
|
+
const typeDef = NODE_TYPES[node.type];
|
|
188
|
+
const fields = typeDef?.configFields ?? [];
|
|
189
|
+
for (const field of fields) {
|
|
190
|
+
const el = _container.querySelector(`#wb-cfg-${field.key}`);
|
|
191
|
+
if (!el) continue;
|
|
192
|
+
|
|
193
|
+
const eventType = field.type === 'select' ? 'change' : 'input';
|
|
194
|
+
el.addEventListener(eventType, () => {
|
|
195
|
+
let value;
|
|
196
|
+
if (field.type === 'checkbox') {
|
|
197
|
+
value = el.checked;
|
|
198
|
+
} else if (field.type === 'number') {
|
|
199
|
+
value = el.value === '' ? undefined : Number(el.value);
|
|
200
|
+
} else {
|
|
201
|
+
value = el.value;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
WorkflowModel.updateNode(nodeId, { config: { [field.key]: value } });
|
|
205
|
+
|
|
206
|
+
// Re-render if a field with showWhen dependents changed — other fields
|
|
207
|
+
// may need to appear or disappear.
|
|
208
|
+
const hasDependents = fields.some(f => f.showWhen && f.showWhen[field.key] !== undefined);
|
|
209
|
+
if (hasDependents) {
|
|
210
|
+
// Defer so the store update settles first
|
|
211
|
+
requestAnimationFrame(() => renderForNode(WorkflowModel.getNode(nodeId)));
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Decision port editor
|
|
217
|
+
if (node.type === 'decision') {
|
|
218
|
+
const addBtn = _container.querySelector('#wb-add-port');
|
|
219
|
+
if (addBtn) {
|
|
220
|
+
addBtn.addEventListener('click', () => {
|
|
221
|
+
const current = WorkflowModel.getNode(nodeId);
|
|
222
|
+
const ports = Array.isArray(current.config.ports) ? [...current.config.ports] : [];
|
|
223
|
+
const id = `port-${crypto.randomUUID().slice(0, 8)}`;
|
|
224
|
+
ports.push({ id, label: '', condition: '' });
|
|
225
|
+
WorkflowModel.updateNode(nodeId, { config: { ports } });
|
|
226
|
+
requestAnimationFrame(() => renderForNode(WorkflowModel.getNode(nodeId)));
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
for (const removeBtn of _container.querySelectorAll('.wb-remove-port')) {
|
|
231
|
+
removeBtn.addEventListener('click', () => {
|
|
232
|
+
const idx = parseInt(removeBtn.dataset.portIdx, 10);
|
|
233
|
+
const current = WorkflowModel.getNode(nodeId);
|
|
234
|
+
const ports = Array.isArray(current.config.ports) ? [...current.config.ports] : [];
|
|
235
|
+
ports.splice(idx, 1);
|
|
236
|
+
WorkflowModel.updateNode(nodeId, { config: { ports } });
|
|
237
|
+
requestAnimationFrame(() => renderForNode(WorkflowModel.getNode(nodeId)));
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
for (const row of _container.querySelectorAll('.wb-port-row')) {
|
|
242
|
+
const idx = parseInt(row.dataset.portIdx, 10);
|
|
243
|
+
const labelInput = row.querySelector('.wb-port-label');
|
|
244
|
+
const condInput = row.querySelector('.wb-port-condition');
|
|
245
|
+
|
|
246
|
+
const updatePort = () => {
|
|
247
|
+
const current = WorkflowModel.getNode(nodeId);
|
|
248
|
+
const ports = Array.isArray(current.config.ports) ? [...current.config.ports] : [];
|
|
249
|
+
if (ports[idx]) {
|
|
250
|
+
ports[idx] = { ...ports[idx], label: labelInput.value, condition: condInput.value };
|
|
251
|
+
WorkflowModel.updateNode(nodeId, { config: { ports } });
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
if (labelInput) labelInput.addEventListener('input', updatePort);
|
|
256
|
+
if (condInput) condInput.addEventListener('input', updatePort);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Instructions (present for non-step typed nodes)
|
|
261
|
+
const instructionsInput = _container.querySelector('#wb-node-instructions');
|
|
262
|
+
if (instructionsInput) {
|
|
263
|
+
instructionsInput.addEventListener('input', () => {
|
|
264
|
+
WorkflowModel.updateNode(nodeId, { config: { instructions: instructionsInput.value } });
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function bindWorkflowEvents() {
|
|
270
|
+
if (!_container) return;
|
|
271
|
+
|
|
272
|
+
const nameInput = _container.querySelector('#wb-wf-name');
|
|
273
|
+
if (nameInput) {
|
|
274
|
+
nameInput.addEventListener('input', () => {
|
|
275
|
+
const wf = store.get('workflow');
|
|
276
|
+
if (wf) {
|
|
277
|
+
wf.name = nameInput.value;
|
|
278
|
+
store.set('isDirty', true);
|
|
279
|
+
store.set('workflow', wf);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const descInput = _container.querySelector('#wb-wf-description');
|
|
285
|
+
if (descInput) {
|
|
286
|
+
descInput.addEventListener('input', () => {
|
|
287
|
+
const wf = store.get('workflow');
|
|
288
|
+
if (wf) {
|
|
289
|
+
wf.description = descInput.value;
|
|
290
|
+
store.set('isDirty', true);
|
|
291
|
+
store.set('workflow', wf);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function renderForNode(node) {
|
|
298
|
+
if (!_container) return;
|
|
299
|
+
_container.innerHTML = renderNodeInspector(node);
|
|
300
|
+
bindNodeEvents(node.id);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function render() {
|
|
304
|
+
if (!_container) return;
|
|
305
|
+
|
|
306
|
+
// Skip re-render while a field inside the inspector is focused — the user
|
|
307
|
+
// is actively typing and re-rendering would destroy the focused element.
|
|
308
|
+
if (_container.contains(document.activeElement)) return;
|
|
309
|
+
|
|
310
|
+
const selectedIds = store.get('selectedNodeIds');
|
|
311
|
+
if (selectedIds && selectedIds.size === 1) {
|
|
312
|
+
const nodeId = [...selectedIds][0];
|
|
313
|
+
const node = WorkflowModel.getNode(nodeId);
|
|
314
|
+
if (node) {
|
|
315
|
+
renderForNode(node);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// No node selected — show workflow properties
|
|
321
|
+
_container.innerHTML = renderWorkflowInspector();
|
|
322
|
+
bindWorkflowEvents();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ── Exports ─────────────────────────────────────────────────────────────
|
|
326
|
+
|
|
327
|
+
export const Inspector = {
|
|
328
|
+
/**
|
|
329
|
+
* Mount the inspector panel into a container and subscribe to selection changes.
|
|
330
|
+
* @param {HTMLElement} container
|
|
331
|
+
*/
|
|
332
|
+
mount(container) {
|
|
333
|
+
_container = container;
|
|
334
|
+
render();
|
|
335
|
+
|
|
336
|
+
_unsubs.push(store.on('selectedNodeIds', () => render()));
|
|
337
|
+
_unsubs.push(store.on('workflow', () => {
|
|
338
|
+
// Only re-render workflow panel if no node is selected
|
|
339
|
+
const sel = store.get('selectedNodeIds');
|
|
340
|
+
if (!sel || sel.size === 0) render();
|
|
341
|
+
}));
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Unmount and clean up subscriptions.
|
|
346
|
+
*/
|
|
347
|
+
unmount() {
|
|
348
|
+
for (const unsub of _unsubs) unsub();
|
|
349
|
+
_unsubs = [];
|
|
350
|
+
if (_container) _container.innerHTML = '';
|
|
351
|
+
_container = null;
|
|
352
|
+
},
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Force re-render for current selection.
|
|
356
|
+
*/
|
|
357
|
+
refresh() {
|
|
358
|
+
render();
|
|
359
|
+
},
|
|
360
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// ── Workflow Editor — Block Catalog Sidebar ─────────────────────────────
|
|
2
|
+
// Left panel showing categorized, draggable node blocks.
|
|
3
|
+
|
|
4
|
+
import { NODE_TYPES, NODE_CATEGORIES } from '../models/node-types.js';
|
|
5
|
+
|
|
6
|
+
let _container = null;
|
|
7
|
+
let _dragCb = null;
|
|
8
|
+
let _cleanup = [];
|
|
9
|
+
|
|
10
|
+
function esc(s) {
|
|
11
|
+
return String(s).replace(/&/g, '&').replace(/"/g, '"')
|
|
12
|
+
.replace(/</g, '<').replace(/>/g, '>');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function renderPalette() {
|
|
16
|
+
if (!_container) return;
|
|
17
|
+
|
|
18
|
+
let html = '';
|
|
19
|
+
for (const cat of NODE_CATEGORIES) {
|
|
20
|
+
const entries = Object.entries(NODE_TYPES).filter(([, def]) => def.category === cat.id);
|
|
21
|
+
if (!entries.length) continue;
|
|
22
|
+
|
|
23
|
+
html += `<div class="wb-palette-category">${esc(cat.label)}</div>`;
|
|
24
|
+
|
|
25
|
+
for (const [typeKey, def] of entries) {
|
|
26
|
+
html += `
|
|
27
|
+
<div class="wb-palette-item" data-node-type="${esc(typeKey)}" draggable="true">
|
|
28
|
+
<span class="wb-palette-item-icon">${def.icon}</span>
|
|
29
|
+
<span>${esc(def.label)}</span>
|
|
30
|
+
</div>`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_container.innerHTML = html;
|
|
35
|
+
bindDrag();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function bindDrag() {
|
|
39
|
+
if (!_container) return;
|
|
40
|
+
|
|
41
|
+
const items = _container.querySelectorAll('.wb-palette-item');
|
|
42
|
+
for (const item of items) {
|
|
43
|
+
const onPointerDown = (e) => {
|
|
44
|
+
const typeKey = item.dataset.nodeType;
|
|
45
|
+
const def = NODE_TYPES[typeKey];
|
|
46
|
+
if (!def || !_dragCb) return;
|
|
47
|
+
|
|
48
|
+
_dragCb({
|
|
49
|
+
type: typeKey,
|
|
50
|
+
label: def.label,
|
|
51
|
+
icon: def.icon,
|
|
52
|
+
color: def.color,
|
|
53
|
+
originX: e.clientX,
|
|
54
|
+
originY: e.clientY,
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const onDragStart = (e) => {
|
|
59
|
+
const typeKey = item.dataset.nodeType;
|
|
60
|
+
const def = NODE_TYPES[typeKey];
|
|
61
|
+
if (!def) return;
|
|
62
|
+
e.dataTransfer.setData('application/x-wb-node', JSON.stringify({
|
|
63
|
+
type: typeKey,
|
|
64
|
+
label: def.label,
|
|
65
|
+
}));
|
|
66
|
+
e.dataTransfer.effectAllowed = 'copy';
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
item.addEventListener('pointerdown', onPointerDown);
|
|
70
|
+
item.addEventListener('dragstart', onDragStart);
|
|
71
|
+
_cleanup.push(() => {
|
|
72
|
+
item.removeEventListener('pointerdown', onPointerDown);
|
|
73
|
+
item.removeEventListener('dragstart', onDragStart);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const Palette = {
|
|
79
|
+
/**
|
|
80
|
+
* Render the palette into the given container element.
|
|
81
|
+
* @param {HTMLElement} container
|
|
82
|
+
*/
|
|
83
|
+
mount(container) {
|
|
84
|
+
_container = container;
|
|
85
|
+
renderPalette();
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Tear down the palette and clean up listeners.
|
|
90
|
+
*/
|
|
91
|
+
unmount() {
|
|
92
|
+
for (const fn of _cleanup) fn();
|
|
93
|
+
_cleanup = [];
|
|
94
|
+
if (_container) _container.innerHTML = '';
|
|
95
|
+
_container = null;
|
|
96
|
+
_dragCb = null;
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Register callback invoked when the user begins dragging a block.
|
|
101
|
+
* @param {function} callback - Receives { type, label, icon, color, originX, originY }
|
|
102
|
+
*/
|
|
103
|
+
onDragStart(callback) {
|
|
104
|
+
_dragCb = callback;
|
|
105
|
+
},
|
|
106
|
+
};
|