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,658 @@
|
|
|
1
|
+
// ── Workflow App — Page Module ────────────────────────────────────────
|
|
2
|
+
// Visual workflow builder with drag-and-drop node editor.
|
|
3
|
+
// ES module: mount(container, ctx), unmount(container), onProjectChange(project)
|
|
4
|
+
|
|
5
|
+
import { escapeHtml } from '/shared-assets/ui-utils.js';
|
|
6
|
+
|
|
7
|
+
let _container = null;
|
|
8
|
+
let _builderModules = null;
|
|
9
|
+
let _builderMounted = false;
|
|
10
|
+
let _keydownHandler = null;
|
|
11
|
+
let _renderUnsubs = [];
|
|
12
|
+
|
|
13
|
+
// ── HTML ────────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
const BODY_HTML = `
|
|
16
|
+
<div class="wf-builder-layout" id="wf-builder-layout">
|
|
17
|
+
<div class="wb-toolbar" id="wb-toolbar"></div>
|
|
18
|
+
<div class="wb-main-layout">
|
|
19
|
+
<div class="wb-canvas-container" id="wb-canvas"></div>
|
|
20
|
+
<div class="wb-inspector" id="wb-inspector"></div>
|
|
21
|
+
</div>
|
|
22
|
+
<div id="wb-run-view"></div>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="modal-overlay hidden" id="wf-modal" role="dialog" aria-modal="true">
|
|
25
|
+
<div class="modal">
|
|
26
|
+
<div class="modal-header">
|
|
27
|
+
<h2 class="wf-modal-title"></h2>
|
|
28
|
+
<div class="modal-desc wf-modal-body"></div>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="modal-actions wf-modal-actions"></div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="wf-toast-container" id="wf-toast-container"></div>
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
function $(selector) {
|
|
39
|
+
return _container?.querySelector(selector) ?? null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const esc = escapeHtml;
|
|
43
|
+
|
|
44
|
+
// ── Modal / Toast helpers ───────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
function showModal(title, bodyHtml, buttons) {
|
|
47
|
+
return new Promise(resolve => {
|
|
48
|
+
const overlay = _container?.querySelector('.modal-overlay');
|
|
49
|
+
if (!overlay) { resolve(null); return; }
|
|
50
|
+
|
|
51
|
+
overlay.querySelector('.wf-modal-title').textContent = title;
|
|
52
|
+
overlay.querySelector('.wf-modal-body').innerHTML = bodyHtml;
|
|
53
|
+
const actionsEl = overlay.querySelector('.wf-modal-actions');
|
|
54
|
+
actionsEl.innerHTML = buttons.map(b =>
|
|
55
|
+
`<button class="${b.cls}" data-value="${b.value}">${b.label}</button>`
|
|
56
|
+
).join('');
|
|
57
|
+
|
|
58
|
+
overlay.classList.remove('hidden');
|
|
59
|
+
|
|
60
|
+
const ac = new AbortController();
|
|
61
|
+
const close = (value) => {
|
|
62
|
+
overlay.classList.add('hidden');
|
|
63
|
+
ac.abort();
|
|
64
|
+
resolve(value);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
overlay.addEventListener('click', (e) => {
|
|
68
|
+
if (e.target === overlay) close(null);
|
|
69
|
+
}, { signal: ac.signal });
|
|
70
|
+
|
|
71
|
+
actionsEl.querySelectorAll('button').forEach(btn => {
|
|
72
|
+
btn.addEventListener('click', () => close(btn.dataset.value), { signal: ac.signal });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
document.addEventListener('keydown', (e) => {
|
|
76
|
+
if (e.key === 'Escape') close(null);
|
|
77
|
+
}, { signal: ac.signal });
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function wfConfirm(title, message) {
|
|
82
|
+
const result = await showModal(title, `<p>${esc(message)}</p>`, [
|
|
83
|
+
{ label: 'Cancel', cls: 'btn btn-secondary', value: 'cancel' },
|
|
84
|
+
{ label: 'Delete', cls: 'btn btn-danger', value: 'confirm' },
|
|
85
|
+
]);
|
|
86
|
+
return result === 'confirm';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function wfAlert(title, bodyHtml) {
|
|
90
|
+
await showModal(title, bodyHtml, [
|
|
91
|
+
{ label: 'OK', cls: 'btn btn-primary', value: 'ok' },
|
|
92
|
+
]);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function wfToast(message, type = 'info') {
|
|
96
|
+
const container = _container?.querySelector('.wf-toast-container');
|
|
97
|
+
if (!container) return;
|
|
98
|
+
const toast = document.createElement('div');
|
|
99
|
+
toast.className = `wf-toast wf-toast-${type}`;
|
|
100
|
+
toast.textContent = message;
|
|
101
|
+
container.appendChild(toast);
|
|
102
|
+
setTimeout(() => {
|
|
103
|
+
toast.style.opacity = '0';
|
|
104
|
+
setTimeout(() => toast.remove(), 300);
|
|
105
|
+
}, 3000);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── Builder API ─────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
const API = '/api/workflow';
|
|
111
|
+
|
|
112
|
+
async function loadBuilderModules() {
|
|
113
|
+
if (_builderModules) return _builderModules;
|
|
114
|
+
|
|
115
|
+
const [
|
|
116
|
+
{ Inspector },
|
|
117
|
+
{ Toolbar },
|
|
118
|
+
{ WorkflowList },
|
|
119
|
+
{ RunView },
|
|
120
|
+
{ store },
|
|
121
|
+
{ WorkflowModel },
|
|
122
|
+
{ Canvas },
|
|
123
|
+
{ NodeRenderer },
|
|
124
|
+
{ EdgeRenderer },
|
|
125
|
+
{ DragManager },
|
|
126
|
+
{ HistoryManager },
|
|
127
|
+
{ NODE_TYPES },
|
|
128
|
+
] = await Promise.all([
|
|
129
|
+
import('./panels/inspector.js'),
|
|
130
|
+
import('./panels/toolbar.js'),
|
|
131
|
+
import('./panels/workflow-list.js'),
|
|
132
|
+
import('./panels/run-view.js'),
|
|
133
|
+
import('./state/store.js'),
|
|
134
|
+
import('./models/workflow-model.js'),
|
|
135
|
+
import('./editor/canvas.js'),
|
|
136
|
+
import('./editor/node-renderer.js'),
|
|
137
|
+
import('./editor/edge-renderer.js'),
|
|
138
|
+
import('./editor/drag-manager.js'),
|
|
139
|
+
import('./editor/history-manager.js'),
|
|
140
|
+
import('./models/node-types.js'),
|
|
141
|
+
]);
|
|
142
|
+
|
|
143
|
+
_builderModules = {
|
|
144
|
+
Inspector, Toolbar, WorkflowList, RunView,
|
|
145
|
+
store, WorkflowModel,
|
|
146
|
+
Canvas, NodeRenderer, EdgeRenderer, DragManager, HistoryManager, NODE_TYPES,
|
|
147
|
+
};
|
|
148
|
+
return _builderModules;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function mountBuilder() {
|
|
152
|
+
if (_builderMounted) return;
|
|
153
|
+
|
|
154
|
+
await loadBuilderModules();
|
|
155
|
+
_builderMounted = true;
|
|
156
|
+
|
|
157
|
+
showBuilderList();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function showBuilderList() {
|
|
161
|
+
if (!_builderModules || !_container) return;
|
|
162
|
+
const { WorkflowList, Toolbar, Inspector, RunView, Canvas, DragManager, HistoryManager } = _builderModules;
|
|
163
|
+
|
|
164
|
+
// Clean up render subscriptions
|
|
165
|
+
for (const unsub of _renderUnsubs) unsub();
|
|
166
|
+
_renderUnsubs = [];
|
|
167
|
+
|
|
168
|
+
Toolbar.unmount();
|
|
169
|
+
Inspector.unmount();
|
|
170
|
+
RunView.unmount();
|
|
171
|
+
Canvas.unmount();
|
|
172
|
+
DragManager.destroy();
|
|
173
|
+
HistoryManager.destroy();
|
|
174
|
+
|
|
175
|
+
const builderLayout = $('#wf-builder-layout');
|
|
176
|
+
if (!builderLayout) return;
|
|
177
|
+
|
|
178
|
+
builderLayout.innerHTML = `<div id="wb-workflow-list" style="display:flex;flex-direction:column;flex:1;overflow:hidden;"></div>`;
|
|
179
|
+
|
|
180
|
+
const listContainer = builderLayout.querySelector('#wb-workflow-list');
|
|
181
|
+
WorkflowList.mount(listContainer);
|
|
182
|
+
WorkflowList.setConfirm(wfConfirm);
|
|
183
|
+
WorkflowList.setToast(wfToast);
|
|
184
|
+
|
|
185
|
+
WorkflowList.onSelect((wf) => openWorkflowInEditor(wf));
|
|
186
|
+
WorkflowList.onNew(() => openWorkflowInEditor(null));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function addNodeAtPosition(type, x, y) {
|
|
190
|
+
if (!_builderModules) return;
|
|
191
|
+
const { store, WorkflowModel, NODE_TYPES } = _builderModules;
|
|
192
|
+
const wf = store.get('workflow');
|
|
193
|
+
const typeDef = NODE_TYPES[type];
|
|
194
|
+
const label = typeDef?.label ?? type;
|
|
195
|
+
WorkflowModel.addNode(type, label, { x, y });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function openWorkflowInEditor(wf) {
|
|
199
|
+
if (!_builderModules || !_container) return;
|
|
200
|
+
const {
|
|
201
|
+
Inspector, Toolbar, WorkflowList, RunView,
|
|
202
|
+
store, WorkflowModel,
|
|
203
|
+
Canvas, NodeRenderer, EdgeRenderer, DragManager, HistoryManager, NODE_TYPES,
|
|
204
|
+
} = _builderModules;
|
|
205
|
+
|
|
206
|
+
// Clean up render subscriptions from previous editor session
|
|
207
|
+
for (const unsub of _renderUnsubs) unsub();
|
|
208
|
+
_renderUnsubs = [];
|
|
209
|
+
|
|
210
|
+
WorkflowList.unmount();
|
|
211
|
+
Canvas.unmount();
|
|
212
|
+
DragManager.destroy();
|
|
213
|
+
HistoryManager.destroy();
|
|
214
|
+
|
|
215
|
+
if (wf) {
|
|
216
|
+
try {
|
|
217
|
+
const res = await fetch(`${API}/workflows/${wf.id}`);
|
|
218
|
+
if (res.ok) {
|
|
219
|
+
WorkflowModel.load(await res.json());
|
|
220
|
+
} else {
|
|
221
|
+
WorkflowModel.load(wf);
|
|
222
|
+
}
|
|
223
|
+
} catch {
|
|
224
|
+
WorkflowModel.load(wf);
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
WorkflowModel.load({
|
|
228
|
+
name: 'Untitled Workflow',
|
|
229
|
+
description: '',
|
|
230
|
+
nodes: [],
|
|
231
|
+
edges: [],
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const builderLayout = $('#wf-builder-layout');
|
|
236
|
+
if (!builderLayout) return;
|
|
237
|
+
|
|
238
|
+
builderLayout.innerHTML = `
|
|
239
|
+
<div class="wb-toolbar" id="wb-toolbar"></div>
|
|
240
|
+
<div class="wb-main-layout">
|
|
241
|
+
<div class="wb-canvas-container" id="wb-canvas"></div>
|
|
242
|
+
<div class="wb-inspector" id="wb-inspector"></div>
|
|
243
|
+
</div>
|
|
244
|
+
<div id="wb-run-view"></div>
|
|
245
|
+
`;
|
|
246
|
+
|
|
247
|
+
const canvasContainerEl = builderLayout.querySelector('#wb-canvas');
|
|
248
|
+
|
|
249
|
+
Toolbar.mount(builderLayout.querySelector('#wb-toolbar'));
|
|
250
|
+
Inspector.mount(builderLayout.querySelector('#wb-inspector'));
|
|
251
|
+
|
|
252
|
+
// Init undo/redo history and connect to toolbar
|
|
253
|
+
HistoryManager.init();
|
|
254
|
+
Toolbar.setHistoryManager(HistoryManager);
|
|
255
|
+
|
|
256
|
+
Toolbar.setHandlers({
|
|
257
|
+
onBack: () => showBuilderList(),
|
|
258
|
+
onSave: () => saveWorkflow(),
|
|
259
|
+
onAddStep: (nodeType) => {
|
|
260
|
+
// Add node of selected type at center of visible canvas
|
|
261
|
+
const canvasRoot = Canvas.getRootElement();
|
|
262
|
+
if (canvasRoot) {
|
|
263
|
+
const rect = canvasRoot.getBoundingClientRect();
|
|
264
|
+
const center = Canvas.screenToWorld(
|
|
265
|
+
rect.left + rect.width / 2,
|
|
266
|
+
rect.top + rect.height / 2
|
|
267
|
+
);
|
|
268
|
+
addNodeAtPosition(nodeType || 'step', center.x, center.y);
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
onExport: () => exportWorkflow(),
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Mount Canvas into the canvas container
|
|
275
|
+
Canvas.mount(canvasContainerEl);
|
|
276
|
+
|
|
277
|
+
// Render function — creates/updates nodes and edges on canvas
|
|
278
|
+
function renderGraph() {
|
|
279
|
+
const wfData = store.get('workflow');
|
|
280
|
+
if (!wfData) return;
|
|
281
|
+
|
|
282
|
+
const world = Canvas.getWorldElement();
|
|
283
|
+
const svgWorld = Canvas.getSvgElement();
|
|
284
|
+
if (!world || !svgWorld) return;
|
|
285
|
+
|
|
286
|
+
// Reconcile nodes: update existing, add new, remove old
|
|
287
|
+
const existingNodeEls = world.querySelectorAll('.wfb-node');
|
|
288
|
+
const existingMap = new Map();
|
|
289
|
+
for (const el of existingNodeEls) existingMap.set(el.dataset.nodeId, el);
|
|
290
|
+
|
|
291
|
+
const currentIds = new Set(wfData.nodes.map(n => n.id));
|
|
292
|
+
|
|
293
|
+
// Remove nodes that no longer exist
|
|
294
|
+
for (const [id, el] of existingMap) {
|
|
295
|
+
if (!currentIds.has(id)) el.remove();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Add or update nodes
|
|
299
|
+
for (const node of wfData.nodes) {
|
|
300
|
+
const existing = existingMap.get(node.id);
|
|
301
|
+
if (existing) {
|
|
302
|
+
NodeRenderer.updateNodeElement(existing, node);
|
|
303
|
+
} else {
|
|
304
|
+
const el = NodeRenderer.createNodeElement(node);
|
|
305
|
+
world.appendChild(el);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Update selection visuals
|
|
310
|
+
const selectedIds = store.get('selectedNodeIds') ?? new Set();
|
|
311
|
+
for (const node of wfData.nodes) {
|
|
312
|
+
const el = world.querySelector(`[data-node-id="${node.id}"]`);
|
|
313
|
+
if (el) NodeRenderer.setSelected(el, selectedIds.has(node.id));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Re-render all edges (simpler than reconciling)
|
|
317
|
+
const edgeEls = svgWorld.querySelectorAll('.wfb-edge');
|
|
318
|
+
for (const e of edgeEls) e.remove();
|
|
319
|
+
|
|
320
|
+
for (const edge of wfData.edges) {
|
|
321
|
+
const srcNode = wfData.nodes.find(n => n.id === edge.source);
|
|
322
|
+
const tgtNode = wfData.nodes.find(n => n.id === edge.target);
|
|
323
|
+
if (srcNode && tgtNode) {
|
|
324
|
+
const srcType = NODE_TYPES[srcNode.type];
|
|
325
|
+
const tgtType = NODE_TYPES[tgtNode.type];
|
|
326
|
+
const el = EdgeRenderer.createEdgePath(edge, srcNode, tgtNode, srcType, tgtType);
|
|
327
|
+
svgWorld.appendChild(el);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Update edge selection
|
|
332
|
+
const selectedEdgeIds = store.get('selectedEdgeIds') ?? new Set();
|
|
333
|
+
for (const edge of wfData.edges) {
|
|
334
|
+
const el = svgWorld.querySelector(`[data-edge-id="${edge.id}"]`);
|
|
335
|
+
if (el) EdgeRenderer.setSelected(el, selectedEdgeIds.has(edge.id));
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Subscribe to store changes for re-rendering
|
|
340
|
+
const unsubWorkflow = store.on('workflow', renderGraph);
|
|
341
|
+
const unsubSelection = store.on('selectedNodeIds', renderGraph);
|
|
342
|
+
const unsubEdgeSelection = store.on('selectedEdgeIds', renderGraph);
|
|
343
|
+
_renderUnsubs.push(unsubWorkflow, unsubSelection, unsubEdgeSelection);
|
|
344
|
+
|
|
345
|
+
// Initial render
|
|
346
|
+
renderGraph();
|
|
347
|
+
|
|
348
|
+
// Init drag manager for node moves and edge creation
|
|
349
|
+
DragManager.init(Canvas, Canvas.getWorldElement(), Canvas.getSvgElement());
|
|
350
|
+
|
|
351
|
+
// Double-click on canvas to create step
|
|
352
|
+
const canvasRoot = Canvas.getRootElement();
|
|
353
|
+
if (canvasRoot) {
|
|
354
|
+
canvasRoot.addEventListener('dblclick', (e) => {
|
|
355
|
+
// Don't create step if double-clicking on an existing node
|
|
356
|
+
if (e.target.closest('.wfb-node')) return;
|
|
357
|
+
const pos = Canvas.screenToWorld(e.clientX, e.clientY);
|
|
358
|
+
addNodeAtPosition('step', pos.x, pos.y);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// Click on canvas background to deselect, or on edge to select it
|
|
362
|
+
canvasRoot.addEventListener('pointerdown', (e) => {
|
|
363
|
+
if (e.button !== 0) return;
|
|
364
|
+
// Edge click — check SVG hit areas
|
|
365
|
+
const hitPath = e.target.closest?.('.wfb-edge-hit');
|
|
366
|
+
if (hitPath) {
|
|
367
|
+
const edgeGroup = hitPath.closest('.wfb-edge');
|
|
368
|
+
const edgeId = edgeGroup?.dataset?.edgeId;
|
|
369
|
+
if (edgeId) {
|
|
370
|
+
if (e.shiftKey) {
|
|
371
|
+
const sel = new Set(store.get('selectedEdgeIds') ?? []);
|
|
372
|
+
if (sel.has(edgeId)) sel.delete(edgeId); else sel.add(edgeId);
|
|
373
|
+
store.set('selectedEdgeIds', sel);
|
|
374
|
+
} else {
|
|
375
|
+
store.set('selectedEdgeIds', new Set([edgeId]));
|
|
376
|
+
store.set('selectedNodeIds', new Set());
|
|
377
|
+
}
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (!e.target.closest('.wfb-node') && !e.target.closest('.wfb-port')) {
|
|
382
|
+
store.set('selectedNodeIds', new Set());
|
|
383
|
+
store.set('selectedEdgeIds', new Set());
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async function saveWorkflow() {
|
|
390
|
+
if (!_builderModules) return;
|
|
391
|
+
const { WorkflowModel, Toolbar } = _builderModules;
|
|
392
|
+
|
|
393
|
+
const data = WorkflowModel.save();
|
|
394
|
+
if (!data) return;
|
|
395
|
+
|
|
396
|
+
try {
|
|
397
|
+
let res;
|
|
398
|
+
if (data.id) {
|
|
399
|
+
res = await fetch(`${API}/workflows/${data.id}`, {
|
|
400
|
+
method: 'PUT',
|
|
401
|
+
headers: { 'Content-Type': 'application/json' },
|
|
402
|
+
body: JSON.stringify(data),
|
|
403
|
+
});
|
|
404
|
+
if (res.status === 404) {
|
|
405
|
+
res = await fetch(`${API}/workflows`, {
|
|
406
|
+
method: 'POST',
|
|
407
|
+
headers: { 'Content-Type': 'application/json' },
|
|
408
|
+
body: JSON.stringify(data),
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
} else {
|
|
412
|
+
res = await fetch(`${API}/workflows`, {
|
|
413
|
+
method: 'POST',
|
|
414
|
+
headers: { 'Content-Type': 'application/json' },
|
|
415
|
+
body: JSON.stringify(data),
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (res.ok) {
|
|
420
|
+
const saved = await res.json();
|
|
421
|
+
WorkflowModel.load(saved);
|
|
422
|
+
Toolbar.setDirty(false);
|
|
423
|
+
wfToast('Workflow saved', 'success');
|
|
424
|
+
} else {
|
|
425
|
+
wfToast('Save failed', 'error');
|
|
426
|
+
}
|
|
427
|
+
} catch (e) {
|
|
428
|
+
console.error('Save failed:', e);
|
|
429
|
+
wfToast('Save failed: ' + e.message, 'error');
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async function exportWorkflow() {
|
|
434
|
+
if (!_builderModules) return;
|
|
435
|
+
const { WorkflowModel } = _builderModules;
|
|
436
|
+
|
|
437
|
+
const data = WorkflowModel.save();
|
|
438
|
+
if (!data || !data.nodes?.length) {
|
|
439
|
+
wfToast('No steps to export', 'error');
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Build ordered step list by following edges
|
|
444
|
+
const nodeMap = new Map(data.nodes.map(n => [n.id, n]));
|
|
445
|
+
const outEdges = new Map();
|
|
446
|
+
for (const e of data.edges) {
|
|
447
|
+
outEdges.set(e.source, e);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Find start nodes (no incoming edges)
|
|
451
|
+
const hasIncoming = new Set(data.edges.map(e => e.target));
|
|
452
|
+
const startNodes = data.nodes.filter(n => !hasIncoming.has(n.id));
|
|
453
|
+
|
|
454
|
+
let md = `# ${data.name}\n\n`;
|
|
455
|
+
if (data.description) md += `${data.description}\n\n`;
|
|
456
|
+
md += `---\n\n`;
|
|
457
|
+
|
|
458
|
+
// Walk graph from each start node
|
|
459
|
+
let stepNum = 1;
|
|
460
|
+
const visited = new Set();
|
|
461
|
+
|
|
462
|
+
function walkNode(nodeId) {
|
|
463
|
+
if (visited.has(nodeId)) return;
|
|
464
|
+
visited.add(nodeId);
|
|
465
|
+
|
|
466
|
+
const node = nodeMap.get(nodeId);
|
|
467
|
+
if (!node) return;
|
|
468
|
+
|
|
469
|
+
md += `## Step ${stepNum}: ${node.label}\n\n`;
|
|
470
|
+
stepNum++;
|
|
471
|
+
|
|
472
|
+
const instructions = node.config?.instructions;
|
|
473
|
+
const file = node.config?.instructionFile;
|
|
474
|
+
|
|
475
|
+
if (instructions) {
|
|
476
|
+
md += `${instructions}\n\n`;
|
|
477
|
+
}
|
|
478
|
+
if (file) {
|
|
479
|
+
md += `> Instructions file: \`${file}\`\n\n`;
|
|
480
|
+
}
|
|
481
|
+
if (!instructions && !file) {
|
|
482
|
+
md += `*(No instructions defined)*\n\n`;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Follow outgoing edge
|
|
486
|
+
const edge = outEdges.get(nodeId);
|
|
487
|
+
if (edge) walkNode(edge.target);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (startNodes.length === 0 && data.nodes.length > 0) {
|
|
491
|
+
// No clear start — just list all nodes
|
|
492
|
+
for (const node of data.nodes) walkNode(node.id);
|
|
493
|
+
} else {
|
|
494
|
+
for (const node of startNodes) walkNode(node.id);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Any unvisited nodes
|
|
498
|
+
for (const node of data.nodes) {
|
|
499
|
+
if (!visited.has(node.id)) walkNode(node.id);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Show in a modal with copy button
|
|
503
|
+
const escaped = md.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
504
|
+
const result = await showModal('Export Workflow',
|
|
505
|
+
`<pre style="max-height:300px;overflow:auto;padding:var(--df-space-3);background:var(--df-color-bg-raised);border:1px solid var(--df-color-border-default);border-radius:6px;font-size:var(--df-font-size-xs);white-space:pre-wrap;word-wrap:break-word;">${escaped}</pre>`,
|
|
506
|
+
[
|
|
507
|
+
{ label: 'Copy', cls: 'btn btn-primary', value: 'copy' },
|
|
508
|
+
{ label: 'Close', cls: 'btn btn-secondary', value: 'close' },
|
|
509
|
+
]
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
if (result === 'copy') {
|
|
513
|
+
try {
|
|
514
|
+
await navigator.clipboard.writeText(md);
|
|
515
|
+
wfToast('Copied to clipboard', 'success');
|
|
516
|
+
} catch {
|
|
517
|
+
wfToast('Copy failed', 'error');
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
function unmountBuilder() {
|
|
525
|
+
if (!_builderMounted || !_builderModules) return;
|
|
526
|
+
const { Inspector, Toolbar, WorkflowList, RunView, Canvas, DragManager, HistoryManager } = _builderModules;
|
|
527
|
+
|
|
528
|
+
// Clean up render subscriptions
|
|
529
|
+
for (const unsub of _renderUnsubs) unsub();
|
|
530
|
+
_renderUnsubs = [];
|
|
531
|
+
|
|
532
|
+
Toolbar.unmount();
|
|
533
|
+
Inspector.unmount();
|
|
534
|
+
WorkflowList.unmount();
|
|
535
|
+
RunView.unmount();
|
|
536
|
+
Canvas.unmount();
|
|
537
|
+
DragManager.destroy();
|
|
538
|
+
HistoryManager.destroy();
|
|
539
|
+
|
|
540
|
+
_builderMounted = false;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// ── Keyboard shortcuts ──────────────────────────────────────────────
|
|
544
|
+
|
|
545
|
+
function setupKeyboardShortcuts() {
|
|
546
|
+
_keydownHandler = async (e) => {
|
|
547
|
+
if (!_builderModules) return;
|
|
548
|
+
|
|
549
|
+
const { store, WorkflowModel } = _builderModules;
|
|
550
|
+
const ctrl = e.ctrlKey || e.metaKey;
|
|
551
|
+
|
|
552
|
+
// Ctrl+Z / Ctrl+Y / Ctrl+Shift+Z — handled by HistoryManager
|
|
553
|
+
|
|
554
|
+
if (ctrl && e.key === 's') {
|
|
555
|
+
e.preventDefault();
|
|
556
|
+
await saveWorkflow();
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (e.key === 'Delete' || e.key === 'Backspace') {
|
|
561
|
+
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') return;
|
|
562
|
+
e.preventDefault();
|
|
563
|
+
const selectedNodes = store.get('selectedNodeIds');
|
|
564
|
+
const selectedEdges = store.get('selectedEdgeIds');
|
|
565
|
+
if (selectedNodes?.size) {
|
|
566
|
+
for (const id of selectedNodes) WorkflowModel.removeNode(id);
|
|
567
|
+
}
|
|
568
|
+
if (selectedEdges?.size) {
|
|
569
|
+
for (const id of selectedEdges) WorkflowModel.removeEdge(id);
|
|
570
|
+
}
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (ctrl && e.key === 'a') {
|
|
575
|
+
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
|
576
|
+
e.preventDefault();
|
|
577
|
+
const wf = store.get('workflow');
|
|
578
|
+
if (wf?.nodes) {
|
|
579
|
+
store.set('selectedNodeIds', new Set(wf.nodes.map(n => n.id)));
|
|
580
|
+
}
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (ctrl && e.key === 'Enter') {
|
|
585
|
+
e.preventDefault();
|
|
586
|
+
await runWorkflow();
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (ctrl && e.key === '0') {
|
|
591
|
+
e.preventDefault();
|
|
592
|
+
store.set('zoom', 1);
|
|
593
|
+
store.set('panX', 0);
|
|
594
|
+
store.set('panY', 0);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (e.key === 'Escape') {
|
|
599
|
+
store.set('selectedNodeIds', new Set());
|
|
600
|
+
store.set('selectedEdgeIds', new Set());
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (e.key === '+' || e.key === '=') {
|
|
605
|
+
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
|
606
|
+
e.preventDefault();
|
|
607
|
+
store.set('zoom', Math.min(3, (store.get('zoom') ?? 1) + 0.1));
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
if (e.key === '-') {
|
|
612
|
+
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
|
613
|
+
e.preventDefault();
|
|
614
|
+
store.set('zoom', Math.max(0.25, (store.get('zoom') ?? 1) - 0.1));
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
document.addEventListener('keydown', _keydownHandler);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function teardownKeyboardShortcuts() {
|
|
623
|
+
if (_keydownHandler) {
|
|
624
|
+
document.removeEventListener('keydown', _keydownHandler);
|
|
625
|
+
_keydownHandler = null;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// ── Exports ──────────────────────────────────────────────────────────
|
|
630
|
+
|
|
631
|
+
export function mount(container, ctx) {
|
|
632
|
+
_container = container;
|
|
633
|
+
|
|
634
|
+
container.classList.add('page-workflow');
|
|
635
|
+
container.innerHTML = BODY_HTML;
|
|
636
|
+
|
|
637
|
+
setupKeyboardShortcuts();
|
|
638
|
+
mountBuilder();
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
export function unmount(container) {
|
|
642
|
+
unmountBuilder();
|
|
643
|
+
teardownKeyboardShortcuts();
|
|
644
|
+
|
|
645
|
+
container.classList.remove('page-workflow');
|
|
646
|
+
container.innerHTML = '';
|
|
647
|
+
|
|
648
|
+
_container = null;
|
|
649
|
+
_builderMounted = false;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
export function onProjectChange(project) {
|
|
653
|
+
// Refresh the builder list if it's showing
|
|
654
|
+
if (_builderModules) {
|
|
655
|
+
const { WorkflowList } = _builderModules;
|
|
656
|
+
WorkflowList.refresh?.();
|
|
657
|
+
}
|
|
658
|
+
}
|