yaml-flow 6.0.0 → 7.0.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/board-live-cards-cli.js +4 -4
- package/browser/asset-integrity.json +3 -3
- package/browser/board-livecards-client.js +2 -0
- package/browser/board-livecards-client.js.map +1 -0
- package/browser/board-livecards-localstorage.js +10 -0
- package/browser/board-livecards-localstorage.js.map +1 -0
- package/browser/board-livegraph-engine.js +2 -2
- package/browser/board-livegraph-engine.js.map +1 -1
- package/browser/card-compute.js +28 -28
- package/browser/compute-jsonata.js +5 -0
- package/browser/compute-jsonata.js.map +1 -0
- package/browser/live-cards.js +261 -150
- package/card-store.js +4 -4
- package/dist/{board-live-cards-public-CltXYgaY.d.cts → board-live-cards-public-CW5074xr.d.cts} +9 -5
- package/dist/{board-live-cards-public-f-E-FAyp.d.ts → board-live-cards-public-hnZo0mAf.d.ts} +9 -5
- package/dist/board-livegraph-runtime/index.cjs +2 -2
- package/dist/board-livegraph-runtime/index.cjs.map +1 -1
- package/dist/board-livegraph-runtime/index.d.cts +11 -9
- package/dist/board-livegraph-runtime/index.d.ts +11 -9
- package/dist/board-livegraph-runtime/index.js +2 -2
- package/dist/board-livegraph-runtime/index.js.map +1 -1
- package/dist/board-livegraph-runtime/jsonata-sync.cjs +37 -1
- package/dist/card-compute/index.cjs +4 -4
- package/dist/card-compute/index.cjs.map +1 -1
- package/dist/card-compute/index.d.cts +5 -1
- package/dist/card-compute/index.d.ts +5 -1
- package/dist/card-compute/index.js +4 -4
- package/dist/card-compute/index.js.map +1 -1
- package/dist/card-compute/jsonata-sync.cjs +37 -1
- package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs +2 -1
- package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs.map +1 -1
- package/dist/cli/browser-api/board-live-cards-browser-adapter.d.cts +27 -14
- package/dist/cli/browser-api/board-live-cards-browser-adapter.d.ts +27 -14
- package/dist/cli/browser-api/board-live-cards-browser-adapter.js +2 -1
- package/dist/cli/browser-api/board-live-cards-browser-adapter.js.map +1 -1
- package/dist/cli/browser-api/card-store-browser-api.cjs +1 -1
- package/dist/cli/browser-api/card-store-browser-api.cjs.map +1 -1
- package/dist/cli/browser-api/card-store-browser-api.js +1 -1
- package/dist/cli/browser-api/card-store-browser-api.js.map +1 -1
- package/dist/cli/browser-api/jsonata-sync.cjs +37 -1
- package/dist/cli/node/artifacts-store-cli.cjs +8 -8
- package/dist/cli/node/artifacts-store-cli.cjs.map +1 -1
- package/dist/cli/node/artifacts-store-cli.js +8 -8
- package/dist/cli/node/artifacts-store-cli.js.map +1 -1
- package/dist/cli/node/board-live-cards-cli.cjs +7 -7
- package/dist/cli/node/board-live-cards-cli.cjs.map +1 -1
- package/dist/cli/node/board-live-cards-cli.js +7 -7
- package/dist/cli/node/board-live-cards-cli.js.map +1 -1
- package/dist/cli/node/card-store-cli.cjs +5 -5
- package/dist/cli/node/card-store-cli.cjs.map +1 -1
- package/dist/cli/node/card-store-cli.js +5 -5
- package/dist/cli/node/card-store-cli.js.map +1 -1
- package/dist/cli/node/execution-adapter.cjs +3 -0
- package/dist/cli/node/execution-adapter.cjs.map +1 -0
- package/dist/cli/node/execution-adapter.d.cts +174 -0
- package/dist/cli/node/execution-adapter.d.ts +174 -0
- package/dist/cli/node/execution-adapter.js +3 -0
- package/dist/cli/node/execution-adapter.js.map +1 -0
- package/dist/cli/node/fs-board-adapter.cjs +7 -7
- package/dist/cli/node/fs-board-adapter.cjs.map +1 -1
- package/dist/cli/node/fs-board-adapter.d.cts +2 -2
- package/dist/cli/node/fs-board-adapter.d.ts +2 -2
- package/dist/cli/node/fs-board-adapter.js +7 -7
- package/dist/cli/node/fs-board-adapter.js.map +1 -1
- package/dist/cli/node/jsonata-sync.cjs +37 -1
- package/dist/cli/node/source-cli-task-executor.cjs +4 -4
- package/dist/cli/node/source-cli-task-executor.cjs.map +1 -1
- package/dist/cli/node/source-cli-task-executor.js +4 -4
- package/dist/cli/node/source-cli-task-executor.js.map +1 -1
- package/dist/continuous-event-graph/index.cjs +2 -2
- package/dist/continuous-event-graph/index.cjs.map +1 -1
- package/dist/continuous-event-graph/index.js +2 -2
- package/dist/continuous-event-graph/index.js.map +1 -1
- package/dist/continuous-event-graph/jsonata-sync.cjs +37 -1
- package/dist/execution-refs.cjs +2 -1
- package/dist/execution-refs.cjs.map +1 -1
- package/dist/execution-refs.d.cts +49 -11
- package/dist/execution-refs.d.ts +49 -11
- package/dist/execution-refs.js +2 -1
- package/dist/execution-refs.js.map +1 -1
- package/dist/index.cjs +10 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +10 -10
- package/dist/index.js.map +1 -1
- package/dist/jsonata-sync.cjs +37 -1
- package/dist/server-runtime/index.cjs +9 -0
- package/dist/server-runtime/index.cjs.map +1 -0
- package/dist/server-runtime/index.d.cts +31 -0
- package/dist/server-runtime/index.d.ts +31 -0
- package/dist/server-runtime/index.js +9 -0
- package/dist/server-runtime/index.js.map +1 -0
- package/dist/server-runtime/jsonata-sync.cjs +7623 -0
- package/dist/step-machine-public/index.cjs +2 -0
- package/dist/step-machine-public/index.cjs.map +1 -0
- package/dist/step-machine-public/index.d.cts +159 -0
- package/dist/step-machine-public/index.d.ts +159 -0
- package/dist/step-machine-public/index.js +2 -0
- package/dist/step-machine-public/index.js.map +1 -0
- package/dist/step-machine-public/jsonata-sync.cjs +7623 -0
- package/dist/storage-refs.cjs +2 -2
- package/dist/storage-refs.cjs.map +1 -1
- package/dist/storage-refs.d.cts +7 -6
- package/dist/storage-refs.d.ts +7 -6
- package/dist/storage-refs.js +2 -2
- package/dist/storage-refs.js.map +1 -1
- package/dist/types-B1ZRa4aI.d.ts +147 -0
- package/dist/types-BxEFcVK9.d.cts +147 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-t4.js +9 -10
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-http-test.js +357 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-public.js +9 -10
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-server.js +300 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-server.py +617 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-sse-worker.js +48 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.py +11 -10
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +19 -4
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +4 -8
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/init-board-cli.js +6 -10
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/poll-status-cli.js +8 -16
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/reset-board-dir-cli.js +2 -6
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/retrigger-cli.js +4 -8
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/status-cli.js +3 -7
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +4 -8
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/wait-completed-cli.js +7 -16
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/write-prices-cli.js +2 -6
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/_board_pycli.py +13 -3
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/add-cards.py +2 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/init-board.py +2 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/poll-status.py +2 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +20 -24
- package/examples/cli/step-machine-cli/portfolio-tracker/run-inline-python-demo-pycli.py +0 -3
- package/examples/cli/step-machine-demo/jsonata-init-board-cli.js +8 -13
- package/examples/cli/step-machine-demo/jsonata-init-board.flow.yaml +33 -9
- package/examples/cli/step-machine-demo/one-step-cli-only.flow.yaml +3 -1
- package/examples/cli/step-machine-demo/step2-double-cli.js +6 -12
- package/examples/cli/step-machine-demo/two-step-math.flow.yaml +66 -4
- package/examples/cli/step-machine-demo/two-step-mixed.flow.yaml +13 -5
- package/examples/example-board/cards/_index.json +47 -0
- package/examples/example-board/cards/card-market-prices.json +33 -9
- package/examples/example-board/cards/card-my-identity.json +30 -6
- package/examples/example-board/cards/card-portfolio-action.json +24 -6
- package/examples/example-board/cards/card-portfolio-intelligence.json +97 -0
- package/examples/example-board/cards/card-portfolio-risks.json +24 -6
- package/examples/example-board/cards/card-portfolio-value.json +38 -10
- package/examples/example-board/cards/card-portfolio.json +57 -13
- package/examples/example-board/cards/card-rebalance-impact.json +22 -6
- package/examples/example-board/cards/card-rebalance-sim.json +66 -15
- package/examples/example-board/demo-server.js +360 -69
- package/examples/example-board/demo-shell-localstorage.html +774 -0
- package/examples/example-board/demo-shell-with-server.html +18 -36
- package/examples/example-board/demo-shell.html +5 -4
- package/examples/example-board/demo-task-executor.js +217 -265
- package/package.json +15 -13
- package/step-machine-cli.js +43 -310
- package/board-livecards-server-runtime.js +0 -1513
- package/browser/board-livecards-runtime-client.js +0 -263
- package/dist/pycli/quickjs-board-runtime.global.js +0 -9
- package/dist/pycli/quickjs-board-runtime.global.js.map +0 -1
- package/dist/pycli/quickjs-step-machine-runtime.global.js +0 -5
- package/dist/pycli/quickjs-step-machine-runtime.global.js.map +0 -1
- package/examples/cli/step-machine-demo/two-step-math-handlers.js +0 -32
- package/examples/cli/step-machine-demo/two-step-mixed-handlers.js +0 -24
- package/examples/example-board/demo-shell-browser.html +0 -675
|
@@ -1,675 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
-
<title>Example Board Demo (Browser Runtime)</title>
|
|
7
|
-
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
|
8
|
-
<script src="https://cdn.jsdelivr.net/npm/jsonata/jsonata.min.js"></script>
|
|
9
|
-
<script src="https://cdn.jsdelivr.net/npm/leader-line/leader-line.min.js"></script>
|
|
10
|
-
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@6.0.0/browser/card-compute.js"></script>
|
|
11
|
-
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@6.0.0/browser/live-cards.js"></script>
|
|
12
|
-
<script src="https://cdn.jsdelivr.net/npm/yaml-flow@6.0.0/browser/board-livegraph-engine.js"></script>
|
|
13
|
-
</head>
|
|
14
|
-
<body class="bg-light">
|
|
15
|
-
<div class="container-fluid py-3">
|
|
16
|
-
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
|
|
17
|
-
<div>
|
|
18
|
-
<h1 class="h4 mb-0" id="boardTitle">Example Board (Browser Runtime)</h1>
|
|
19
|
-
<div class="small text-muted" id="boardDesc"></div>
|
|
20
|
-
</div>
|
|
21
|
-
<div class="d-flex align-items-center gap-2">
|
|
22
|
-
<a class="btn btn-sm btn-outline-secondary" href="demo-shell-with-server.html">Open Server Runtime Shell</a>
|
|
23
|
-
<button class="btn btn-sm btn-outline-primary" id="modeBoard">Board</button>
|
|
24
|
-
<button class="btn btn-sm btn-outline-primary" id="modeCanvas">Canvas</button>
|
|
25
|
-
<button class="btn btn-sm btn-outline-secondary" id="autoLayout">Auto Layout</button>
|
|
26
|
-
<button class="btn btn-sm btn-outline-dark" id="refreshAll">Refresh All</button>
|
|
27
|
-
<div class="form-check ms-2">
|
|
28
|
-
<input class="form-check-input" type="checkbox" id="devModeToggle" />
|
|
29
|
-
<label class="form-check-label" for="devModeToggle">Dev Mode</label>
|
|
30
|
-
</div>
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
33
|
-
|
|
34
|
-
<div class="alert alert-info small py-2 mb-3">
|
|
35
|
-
Browser runtime mode: source_defs are executed in-browser through an opaque task executor.
|
|
36
|
-
Card source definitions are treated as task-executor-owned metadata.
|
|
37
|
-
</div>
|
|
38
|
-
|
|
39
|
-
<div class="alert alert-secondary small py-2 mb-3">
|
|
40
|
-
Contract and decision notes live in <a href="./demo-shell.html#assumptions-and-tradeoffs">Assumptions and Tradeoffs</a>.
|
|
41
|
-
</div>
|
|
42
|
-
|
|
43
|
-
<details id="debugPanelDetails" class="card border-warning mb-3 d-none">
|
|
44
|
-
<summary class="card-header py-2 d-flex justify-content-between align-items-center" style="cursor: pointer;">
|
|
45
|
-
<strong class="small">Live Debug Panel</strong>
|
|
46
|
-
<span class="badge text-bg-warning">browser-runtime</span>
|
|
47
|
-
</summary>
|
|
48
|
-
<div class="card-body py-2">
|
|
49
|
-
<pre id="debugPanelBody" class="small mb-0" style="white-space: pre-wrap; max-height: 220px; overflow: auto;">Initializing debug panel...</pre>
|
|
50
|
-
</div>
|
|
51
|
-
</details>
|
|
52
|
-
|
|
53
|
-
<div id="boardRoot"></div>
|
|
54
|
-
</div>
|
|
55
|
-
|
|
56
|
-
<script>
|
|
57
|
-
(function () {
|
|
58
|
-
function failInit(message) {
|
|
59
|
-
const root = document.getElementById('boardRoot');
|
|
60
|
-
if (root) {
|
|
61
|
-
root.innerHTML = `<div class="alert alert-danger">${message}</div>`;
|
|
62
|
-
}
|
|
63
|
-
console.error(message);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Get exports from board-livegraph-engine
|
|
67
|
-
const LocalStorageService = (window.BoardLiveGraph && window.BoardLiveGraph.LocalStorageService);
|
|
68
|
-
const buildLiveCardModelsFromArtifacts = (window.BoardLiveGraph && window.BoardLiveGraph.buildLiveCardModelsFromArtifacts);
|
|
69
|
-
const buildBrowserArtifactsFromRuntime = (window.BoardLiveGraph && window.BoardLiveGraph.buildBrowserArtifactsFromRuntime);
|
|
70
|
-
if (!LocalStorageService || !buildLiveCardModelsFromArtifacts || !buildBrowserArtifactsFromRuntime) {
|
|
71
|
-
failInit('board-livegraph-engine.js not loaded. Run "npm run build:browser" first.');
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// ========================================================================
|
|
76
|
-
// Demo Setup — Portfolio Domain
|
|
77
|
-
// ========================================================================
|
|
78
|
-
const CARD_FILES = [
|
|
79
|
-
'card-portfolio.json',
|
|
80
|
-
'card-market-prices.json',
|
|
81
|
-
'card-portfolio-value.json',
|
|
82
|
-
'card-concentration.json',
|
|
83
|
-
'card-portfolio-risks.json',
|
|
84
|
-
'card-portfolio-action.json',
|
|
85
|
-
];
|
|
86
|
-
|
|
87
|
-
const QUOTES_SEED = {
|
|
88
|
-
quoteResponse: {
|
|
89
|
-
result: [
|
|
90
|
-
{ symbol: 'AAPL', shortName: 'Apple Inc.', regularMarketPrice: 180, regularMarketChange: 2.5, regularMarketChangePercent: 1.41 },
|
|
91
|
-
{ symbol: 'MSFT', shortName: 'Microsoft', regularMarketPrice: 420, regularMarketChange: -1.2, regularMarketChangePercent: -0.28 },
|
|
92
|
-
{ symbol: 'GOOGL', shortName: 'Alphabet', regularMarketPrice: 165, regularMarketChange: 0.8, regularMarketChangePercent: 0.49 },
|
|
93
|
-
{ symbol: 'TSLA', shortName: 'Tesla', regularMarketPrice: 250, regularMarketChange: -5.0, regularMarketChangePercent: -1.96 },
|
|
94
|
-
],
|
|
95
|
-
},
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const MOCK_ANALYSIS = {
|
|
99
|
-
mix: '- AAPL dominates at 36%\n- Tech-heavy portfolio',
|
|
100
|
-
pnl: '- Best: MSFT +35%\n- Worst: GOOGL -41%',
|
|
101
|
-
risks: '- AAPL: earnings risk\n- TSLA: high volatility',
|
|
102
|
-
action: '- Trim GOOGL — below cost basis and deteriorating momentum',
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const clone = (x) => JSON.parse(JSON.stringify(x));
|
|
106
|
-
const nowIso = () => new Date().toISOString();
|
|
107
|
-
const MOCK_FS_PREFIX = 'yf:demo-surface:cards:';
|
|
108
|
-
|
|
109
|
-
function safeJsonParse(raw, fallback) {
|
|
110
|
-
if (!raw) return fallback;
|
|
111
|
-
try {
|
|
112
|
-
return JSON.parse(raw);
|
|
113
|
-
} catch {
|
|
114
|
-
return fallback;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function mockFsKey(cardId, dirName) {
|
|
119
|
-
return `${MOCK_FS_PREFIX}${String(cardId)}:${String(dirName)}`;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function readMockDir(cardId, dirName) {
|
|
123
|
-
const key = mockFsKey(cardId, dirName);
|
|
124
|
-
const parsed = safeJsonParse(localStorage.getItem(key), []);
|
|
125
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function writeMockDir(cardId, dirName, items) {
|
|
129
|
-
localStorage.setItem(mockFsKey(cardId, dirName), JSON.stringify(Array.isArray(items) ? items : []));
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function toFileMetadataOnly(entry) {
|
|
133
|
-
if (!entry || typeof entry !== 'object') return null;
|
|
134
|
-
return {
|
|
135
|
-
name: entry.name || null,
|
|
136
|
-
stored_name: entry.stored_name || null,
|
|
137
|
-
size: typeof entry.size === 'number' ? entry.size : null,
|
|
138
|
-
mime_type: entry.mime_type || null,
|
|
139
|
-
path: entry.path || null,
|
|
140
|
-
uploaded_at: entry.uploaded_at || null,
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function normalizeDisplayFileName(name) {
|
|
145
|
-
const input = String(name || '').trim();
|
|
146
|
-
if (!input) return 'upload.bin';
|
|
147
|
-
const parts = input.split(/[/\\]/);
|
|
148
|
-
return parts[parts.length - 1] || 'upload.bin';
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function normalizeStem(rawStem) {
|
|
152
|
-
const normalized = String(rawStem || '')
|
|
153
|
-
.toLowerCase()
|
|
154
|
-
.replace(/\s+/g, '_')
|
|
155
|
-
.replace(/[^a-z0-9_-]/g, '_')
|
|
156
|
-
.replace(/_+/g, '_')
|
|
157
|
-
.replace(/^_+|_+$/g, '');
|
|
158
|
-
return normalized || 'file';
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function normalizeExt(rawExt) {
|
|
162
|
-
if (!rawExt || rawExt === '.') return '';
|
|
163
|
-
const extBody = String(rawExt).replace(/^\./, '').toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
164
|
-
return extBody ? `.${extBody}` : '';
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function parseLeadingSerial(fileName) {
|
|
168
|
-
const m = String(fileName || '').match(/^(\d+)[-_]/);
|
|
169
|
-
return m ? parseInt(m[1], 10) : 0;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function nextSerialFromNames(names) {
|
|
173
|
-
let maxSeen = 0;
|
|
174
|
-
for (const name of names) {
|
|
175
|
-
const serial = parseLeadingSerial(name);
|
|
176
|
-
if (Number.isFinite(serial) && serial > maxSeen) maxSeen = serial;
|
|
177
|
-
}
|
|
178
|
-
return maxSeen + 1;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function buildStoredFileName(cardId, fileName) {
|
|
182
|
-
const maxLen = 32;
|
|
183
|
-
const displayName = normalizeDisplayFileName(fileName);
|
|
184
|
-
const extRaw = displayName.includes('.') ? displayName.slice(displayName.lastIndexOf('.')) : '';
|
|
185
|
-
const ext = normalizeExt(extRaw);
|
|
186
|
-
const stemRaw = extRaw ? displayName.slice(0, -extRaw.length) : displayName;
|
|
187
|
-
const stemNorm = normalizeStem(stemRaw);
|
|
188
|
-
|
|
189
|
-
const existing = readPersistedFileMetadata(cardId);
|
|
190
|
-
const names = existing.map((f) => f && typeof f.stored_name === 'string' ? f.stored_name : '').filter(Boolean);
|
|
191
|
-
let serial = nextSerialFromNames(names);
|
|
192
|
-
|
|
193
|
-
while (true) {
|
|
194
|
-
const prefix = `${String(serial).padStart(3, '0')}-`;
|
|
195
|
-
let keepExt = ext;
|
|
196
|
-
let stemBudget = maxLen - prefix.length - keepExt.length;
|
|
197
|
-
if (stemBudget < 1) {
|
|
198
|
-
keepExt = '';
|
|
199
|
-
stemBudget = maxLen - prefix.length;
|
|
200
|
-
}
|
|
201
|
-
const stem = stemNorm.slice(0, Math.max(1, stemBudget));
|
|
202
|
-
let candidate = `${prefix}${stem}${keepExt}`;
|
|
203
|
-
if (candidate.length > maxLen) {
|
|
204
|
-
candidate = candidate.slice(0, maxLen).replace(/\.$/, '');
|
|
205
|
-
}
|
|
206
|
-
if (!names.includes(candidate)) return candidate;
|
|
207
|
-
serial += 1;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function nextChatPath(cardId, role) {
|
|
212
|
-
const chats = readMockDir(cardId, 'chats');
|
|
213
|
-
const names = chats
|
|
214
|
-
.map((entry) => {
|
|
215
|
-
if (!entry || typeof entry.path !== 'string') return '';
|
|
216
|
-
const parts = entry.path.split('/');
|
|
217
|
-
return parts[parts.length - 1] || '';
|
|
218
|
-
})
|
|
219
|
-
.filter(Boolean);
|
|
220
|
-
const serial = nextSerialFromNames(names);
|
|
221
|
-
const safeRole = String(role || 'system').toLowerCase().replace(/[^a-z0-9_-]/g, '_') || 'system';
|
|
222
|
-
return `${cardId}/chats/${String(serial).padStart(3, '0')}_${safeRole}.txt`;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function readPersistedFileMetadata(cardId) {
|
|
226
|
-
return readMockDir(cardId, 'files').map(toFileMetadataOnly).filter(Boolean);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function readPersistedChatMessages(cardId) {
|
|
230
|
-
return readMockDir(cardId, 'chats')
|
|
231
|
-
.map((entry) => {
|
|
232
|
-
if (!entry || typeof entry !== 'object') return null;
|
|
233
|
-
return {
|
|
234
|
-
role: typeof entry.role === 'string' ? entry.role : 'system',
|
|
235
|
-
text: typeof entry.text === 'string' ? entry.text : '',
|
|
236
|
-
files: Array.isArray(entry.files) ? entry.files.map(toFileMetadataOnly).filter(Boolean) : [],
|
|
237
|
-
at: typeof entry.at === 'string' ? entry.at : null,
|
|
238
|
-
};
|
|
239
|
-
})
|
|
240
|
-
.filter(Boolean);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
async function fileToBase64(file) {
|
|
244
|
-
if (!file || typeof file.arrayBuffer !== 'function') return null;
|
|
245
|
-
const buffer = await file.arrayBuffer();
|
|
246
|
-
const bytes = new Uint8Array(buffer);
|
|
247
|
-
let binary = '';
|
|
248
|
-
const chunkSize = 0x8000;
|
|
249
|
-
for (let i = 0; i < bytes.length; i += chunkSize) {
|
|
250
|
-
binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunkSize));
|
|
251
|
-
}
|
|
252
|
-
return btoa(binary);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
async function persistFileForCard(cardId, fileLike, now) {
|
|
256
|
-
if (!fileLike) return null;
|
|
257
|
-
|
|
258
|
-
if (typeof fileLike === 'object' && typeof fileLike.name === 'string' && typeof fileLike.arrayBuffer === 'function') {
|
|
259
|
-
const name = normalizeDisplayFileName(fileLike.name);
|
|
260
|
-
const storedName = buildStoredFileName(cardId, name);
|
|
261
|
-
const metadata = {
|
|
262
|
-
name,
|
|
263
|
-
stored_name: storedName,
|
|
264
|
-
size: typeof fileLike.size === 'number' ? fileLike.size : null,
|
|
265
|
-
mime_type: fileLike.type || 'application/octet-stream',
|
|
266
|
-
path: `${cardId}/files/${storedName}`,
|
|
267
|
-
uploaded_at: now,
|
|
268
|
-
};
|
|
269
|
-
const filesDir = readMockDir(cardId, 'files');
|
|
270
|
-
filesDir.push({ ...metadata, content_base64: await fileToBase64(fileLike) });
|
|
271
|
-
writeMockDir(cardId, 'files', filesDir);
|
|
272
|
-
return metadata;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (typeof fileLike === 'object' && typeof fileLike.name === 'string') {
|
|
276
|
-
const displayName = normalizeDisplayFileName(fileLike.name);
|
|
277
|
-
const storedName = fileLike.stored_name || buildStoredFileName(cardId, displayName);
|
|
278
|
-
const metadata = {
|
|
279
|
-
name: displayName,
|
|
280
|
-
stored_name: storedName,
|
|
281
|
-
size: typeof fileLike.size === 'number' ? fileLike.size : null,
|
|
282
|
-
mime_type: fileLike.mime_type || 'application/octet-stream',
|
|
283
|
-
path: fileLike.path || `${cardId}/files/${storedName}`,
|
|
284
|
-
uploaded_at: fileLike.uploaded_at || now,
|
|
285
|
-
};
|
|
286
|
-
const filesDir = readMockDir(cardId, 'files');
|
|
287
|
-
const knownPaths = new Set(filesDir.map((e) => e && e.path).filter(Boolean));
|
|
288
|
-
if (!knownPaths.has(metadata.path)) {
|
|
289
|
-
filesDir.push(metadata);
|
|
290
|
-
writeMockDir(cardId, 'files', filesDir);
|
|
291
|
-
}
|
|
292
|
-
return metadata;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return null;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function appendChatRecord(cardId, record) {
|
|
299
|
-
const chatsDir = readMockDir(cardId, 'chats');
|
|
300
|
-
const next = { ...(record || {}) };
|
|
301
|
-
if (!next.path) next.path = nextChatPath(cardId, next.role || 'system');
|
|
302
|
-
chatsDir.push(next);
|
|
303
|
-
writeMockDir(cardId, 'chats', chatsDir);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
function renderDebugPanel(runtimeState, artifacts) {
|
|
307
|
-
const el = document.getElementById('debugPanelBody');
|
|
308
|
-
if (!el) return;
|
|
309
|
-
|
|
310
|
-
const tasks = runtimeState && runtimeState.state && runtimeState.state.tasks
|
|
311
|
-
? runtimeState.state.tasks : {};
|
|
312
|
-
const availableOutputs = runtimeState && runtimeState.state && Array.isArray(runtimeState.state.availableOutputs)
|
|
313
|
-
? runtimeState.state.availableOutputs : [];
|
|
314
|
-
|
|
315
|
-
const byStatus = {};
|
|
316
|
-
for (const task of Object.values(tasks)) {
|
|
317
|
-
const status = task && task.status ? task.status : 'unknown';
|
|
318
|
-
byStatus[status] = (byStatus[status] || 0) + 1;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const cardRuntimeById = artifacts && artifacts.cardRuntimeById ? artifacts.cardRuntimeById : {};
|
|
322
|
-
|
|
323
|
-
const portfolioValue = cardRuntimeById['card-portfolio-value'] && cardRuntimeById['card-portfolio-value'].computed_values;
|
|
324
|
-
const totalValue = portfolioValue && portfolioValue.totalValue;
|
|
325
|
-
const totalGain = portfolioValue && portfolioValue.totalGain;
|
|
326
|
-
const positions = portfolioValue && portfolioValue.positions;
|
|
327
|
-
|
|
328
|
-
el.textContent = [
|
|
329
|
-
`updatedAt: ${new Date().toISOString()}`,
|
|
330
|
-
`statusCounts: ${JSON.stringify(byStatus)}`,
|
|
331
|
-
`availableOutputs(${availableOutputs.length}): ${availableOutputs.slice().sort().join(', ')}`,
|
|
332
|
-
`card-portfolio-value.totalValue: ${JSON.stringify(totalValue)}`,
|
|
333
|
-
`card-portfolio-value.totalGain: ${JSON.stringify(totalGain)}`,
|
|
334
|
-
`card-portfolio-value.positions.length: ${Array.isArray(positions) ? positions.length : 'n/a'}`,
|
|
335
|
-
].join('\n');
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
function hasCompatiblePersistedArtifacts(cardDefinitions, cardRuntimeById) {
|
|
339
|
-
if (!cardRuntimeById || typeof cardRuntimeById !== 'object') return false;
|
|
340
|
-
const cards = Array.isArray(cardDefinitions) ? cardDefinitions : [];
|
|
341
|
-
if (!cards.length) return false;
|
|
342
|
-
return cards.every((card) => {
|
|
343
|
-
const artifact = cardRuntimeById[card.id];
|
|
344
|
-
if (!artifact || typeof artifact !== 'object') return false;
|
|
345
|
-
if (card.source_defs && card.source_defs.length > 0) {
|
|
346
|
-
if (!(artifact.fetched_sources && typeof artifact.fetched_sources === 'object' && !Array.isArray(artifact.fetched_sources))) return false;
|
|
347
|
-
}
|
|
348
|
-
if (card.requires && card.requires.length > 0) {
|
|
349
|
-
if (!(artifact.requires && typeof artifact.requires === 'object' && !Array.isArray(artifact.requires))) return false;
|
|
350
|
-
}
|
|
351
|
-
return true;
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
async function loadBoardFromFiles() {
|
|
356
|
-
const boardMeta = {
|
|
357
|
-
name: 'Portfolio Tracker',
|
|
358
|
-
desc: 'Track your stock portfolio with live market prices and AI-powered risk analysis.',
|
|
359
|
-
};
|
|
360
|
-
const cards = await Promise.all(
|
|
361
|
-
CARD_FILES.map(name => fetch(`./cards/${name}`).then(r => r.json()))
|
|
362
|
-
);
|
|
363
|
-
|
|
364
|
-
for (const c of cards) {
|
|
365
|
-
c.card_data = c.card_data || {};
|
|
366
|
-
const persistedFiles = readPersistedFileMetadata(c.id);
|
|
367
|
-
if (persistedFiles.length > 0) c.card_data.files = persistedFiles;
|
|
368
|
-
const persistedChats = readPersistedChatMessages(c.id);
|
|
369
|
-
if (persistedChats.length > 0) c.card_data.messages = persistedChats;
|
|
370
|
-
LocalStorageService.writeCard(c.id, c);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
return { board: boardMeta, cards };
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
function makeMockSourceServer() {
|
|
377
|
-
let quoteData = clone(QUOTES_SEED);
|
|
378
|
-
let mockAnalysis = clone(MOCK_ANALYSIS);
|
|
379
|
-
|
|
380
|
-
return {
|
|
381
|
-
fetchSource: async function (card, sourceDef) {
|
|
382
|
-
// mock source with mock="quotes" → return mock quote data
|
|
383
|
-
if (sourceDef && sourceDef.mock === 'quotes') return clone(quoteData);
|
|
384
|
-
// copilot source → return mock analysis object
|
|
385
|
-
if (sourceDef && sourceDef.copilot) return clone(mockAnalysis);
|
|
386
|
-
return null;
|
|
387
|
-
},
|
|
388
|
-
setQuotes: function (nextQuotes) {
|
|
389
|
-
if (nextQuotes && typeof nextQuotes === 'object') quoteData = clone(nextQuotes);
|
|
390
|
-
},
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function replaceNodeInPlace(target, source) {
|
|
395
|
-
Object.keys(target).forEach(k => delete target[k]);
|
|
396
|
-
Object.assign(target, clone(source));
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
let board = null;
|
|
400
|
-
let runtime = null;
|
|
401
|
-
let runtimeUnsub = null;
|
|
402
|
-
let currentMode = 'canvas';
|
|
403
|
-
const nodesById = {};
|
|
404
|
-
|
|
405
|
-
function setDebugPanelVisibility(isDevModeOn) {
|
|
406
|
-
const details = document.getElementById('debugPanelDetails');
|
|
407
|
-
if (!details) return;
|
|
408
|
-
if (isDevModeOn) {
|
|
409
|
-
details.classList.remove('d-none');
|
|
410
|
-
} else {
|
|
411
|
-
details.open = false;
|
|
412
|
-
details.classList.add('d-none');
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
function toIsoTimeTag(iso) {
|
|
417
|
-
const d = new Date(iso);
|
|
418
|
-
const hh = String(d.getHours()).padStart(2, '0');
|
|
419
|
-
const mm = String(d.getMinutes()).padStart(2, '0');
|
|
420
|
-
const ss = String(d.getSeconds()).padStart(2, '0');
|
|
421
|
-
return `${hh}:${mm}:${ss}`;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
async function handleActionInBrowserRuntime(id, actionType, payload) {
|
|
425
|
-
const node = nodesById[id];
|
|
426
|
-
if (!node) return;
|
|
427
|
-
const now = nowIso();
|
|
428
|
-
|
|
429
|
-
if (actionType === 'chat-send') {
|
|
430
|
-
const text = payload && typeof payload.text === 'string' ? payload.text.trim() : '';
|
|
431
|
-
const rawFiles = Array.isArray(payload && payload.files) ? payload.files : [];
|
|
432
|
-
if (!text && rawFiles.length === 0) return;
|
|
433
|
-
|
|
434
|
-
const fileMetas = [];
|
|
435
|
-
for (const fileLike of rawFiles) {
|
|
436
|
-
const persisted = await persistFileForCard(id, fileLike, now);
|
|
437
|
-
if (persisted) fileMetas.push(persisted);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
appendChatRecord(id, { at: now, role: 'user', text, files: fileMetas });
|
|
441
|
-
|
|
442
|
-
fileMetas.forEach((fileMeta) => {
|
|
443
|
-
if (!fileMeta || !fileMeta.stored_name) return;
|
|
444
|
-
appendChatRecord(id, {
|
|
445
|
-
at: now,
|
|
446
|
-
role: 'system',
|
|
447
|
-
text: `File ${fileMeta.name || 'file'} uploaded as ${fileMeta.stored_name}.`,
|
|
448
|
-
files: [],
|
|
449
|
-
});
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
runtime.patchCardState(id, {
|
|
453
|
-
messages: readPersistedChatMessages(id),
|
|
454
|
-
lastInteractionAt: now,
|
|
455
|
-
});
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
if (actionType === 'action') {
|
|
460
|
-
const buttonId = payload && typeof payload.buttonId === 'string' ? payload.buttonId : '';
|
|
461
|
-
if (!buttonId) return;
|
|
462
|
-
runtime.patchCardState(id, {
|
|
463
|
-
lastAction: { buttonId, at: now },
|
|
464
|
-
lastActionText: `${buttonId} @ ${toIsoTimeTag(now)}`,
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
function syncBoardNodes(nextNodes) {
|
|
470
|
-
const existingIds = new Set(board ? board.nodes.map(n => n.id) : []);
|
|
471
|
-
const nextById = Object.fromEntries(nextNodes.map(n => [n.id, n]));
|
|
472
|
-
|
|
473
|
-
if (board) {
|
|
474
|
-
for (const id of existingIds) {
|
|
475
|
-
if (!nextById[id]) board.remove(id);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
for (const nextNode of nextNodes) {
|
|
480
|
-
const existing = nodesById[nextNode.id];
|
|
481
|
-
if (existing) replaceNodeInPlace(existing, nextNode);
|
|
482
|
-
else {
|
|
483
|
-
nodesById[nextNode.id] = clone(nextNode);
|
|
484
|
-
if (board) board.add(nodesById[nextNode.id]);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
if (board) board.refresh();
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
async function bootstrap() {
|
|
492
|
-
const loaded = await loadBoardFromFiles();
|
|
493
|
-
const mockServer = makeMockSourceServer();
|
|
494
|
-
|
|
495
|
-
const createBoardLiveGraphRuntime = (window.BoardLiveGraph && window.BoardLiveGraph.createBoardLiveGraphRuntime);
|
|
496
|
-
if (!createBoardLiveGraphRuntime) {
|
|
497
|
-
throw new Error('BoardLiveGraph runtime not loaded. Run "npm run build:browser" first.');
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
document.getElementById('boardTitle').textContent = loaded.board.name + ' (Browser Runtime)';
|
|
501
|
-
document.getElementById('boardDesc').textContent = loaded.board.desc;
|
|
502
|
-
|
|
503
|
-
const cardDefinitions = loaded.cards;
|
|
504
|
-
const cardDefsById = Object.fromEntries(cardDefinitions.map(c => [c.id, c]));
|
|
505
|
-
|
|
506
|
-
const deepSet = (obj, path, value) => {
|
|
507
|
-
if (!path) return;
|
|
508
|
-
const parts = String(path).split('.');
|
|
509
|
-
let cur = obj;
|
|
510
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
511
|
-
const k = parts[i];
|
|
512
|
-
if (!cur[k] || typeof cur[k] !== 'object') cur[k] = {};
|
|
513
|
-
cur = cur[k];
|
|
514
|
-
}
|
|
515
|
-
cur[parts[parts.length - 1]] = value;
|
|
516
|
-
};
|
|
517
|
-
|
|
518
|
-
runtime = createBoardLiveGraphRuntime(loaded.cards, {
|
|
519
|
-
taskExecutor: async function ({ card }) {
|
|
520
|
-
const out = {};
|
|
521
|
-
for (const sourceDef of (card.source_defs || [])) {
|
|
522
|
-
const payload = await mockServer.fetchSource(card, sourceDef);
|
|
523
|
-
if (payload !== null && payload !== undefined) out[sourceDef.bindTo] = payload;
|
|
524
|
-
}
|
|
525
|
-
return out;
|
|
526
|
-
},
|
|
527
|
-
});
|
|
528
|
-
|
|
529
|
-
// Restore previous runtime artifacts from localStorage
|
|
530
|
-
const cardRuntimeById = LocalStorageService.readAllComputedArtifacts(cardDefinitions.map(c => c.id));
|
|
531
|
-
let statusSnapshot = LocalStorageService.readStatusSnapshot();
|
|
532
|
-
|
|
533
|
-
const initialArtifacts = statusSnapshot && Object.keys(cardRuntimeById).length > 0 && hasCompatiblePersistedArtifacts(cardDefinitions, cardRuntimeById)
|
|
534
|
-
? { cardDefinitions, statusSnapshot, cardRuntimeById }
|
|
535
|
-
: buildBrowserArtifactsFromRuntime({
|
|
536
|
-
boardPath: 'browser',
|
|
537
|
-
cardDefinitions,
|
|
538
|
-
runtimeModels: runtime.getNodes(),
|
|
539
|
-
graphState: runtime.getState(),
|
|
540
|
-
});
|
|
541
|
-
|
|
542
|
-
const initialNodes = buildLiveCardModelsFromArtifacts(initialArtifacts);
|
|
543
|
-
for (const n of initialNodes) nodesById[n.id] = clone(n);
|
|
544
|
-
|
|
545
|
-
const engine = LiveCard.init({
|
|
546
|
-
resolve: (id) => nodesById[id],
|
|
547
|
-
onPatchState: (id, patch) => {
|
|
548
|
-
if (patch && Object.keys(patch).length === 1 && patch.fieldValues) {
|
|
549
|
-
const cardDef = cardDefsById[id];
|
|
550
|
-
let writeTo = null;
|
|
551
|
-
if (cardDef && cardDef.view && Array.isArray(cardDef.view.elements)) {
|
|
552
|
-
for (const elem of cardDef.view.elements) {
|
|
553
|
-
if (elem && elem.data && elem.data.writeTo) {
|
|
554
|
-
writeTo = elem.data.writeTo;
|
|
555
|
-
break;
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
const statePatch = {};
|
|
560
|
-
if (typeof writeTo === 'string' && writeTo.startsWith('card_data.')) {
|
|
561
|
-
deepSet(statePatch, writeTo.slice('card_data.'.length), patch.fieldValues);
|
|
562
|
-
} else {
|
|
563
|
-
Object.assign(statePatch, patch.fieldValues);
|
|
564
|
-
}
|
|
565
|
-
const cardInMem = cardDefsById[id];
|
|
566
|
-
if (cardInMem) {
|
|
567
|
-
deepSet(cardInMem, 'card_data', { ...cardInMem.card_data, ...statePatch });
|
|
568
|
-
LocalStorageService.writeCard(id, cardInMem);
|
|
569
|
-
}
|
|
570
|
-
runtime.patchCardState(id, statePatch);
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
runtime.patchCardState(id, patch || {});
|
|
575
|
-
},
|
|
576
|
-
onRefresh: (id) => runtime.retrigger(id),
|
|
577
|
-
onAction: (id, actionType, payload) => {
|
|
578
|
-
void handleActionInBrowserRuntime(id, actionType, payload || {});
|
|
579
|
-
},
|
|
580
|
-
getChatMessages: (id) => {
|
|
581
|
-
return readPersistedChatMessages(id).map((m) => ({
|
|
582
|
-
role: m && typeof m.role === 'string' ? m.role : 'system',
|
|
583
|
-
text: m && typeof m.text === 'string' ? m.text : '',
|
|
584
|
-
files: Array.isArray(m && m.files) ? m.files : [],
|
|
585
|
-
}));
|
|
586
|
-
},
|
|
587
|
-
});
|
|
588
|
-
|
|
589
|
-
board = LiveCard.Board(engine, document.getElementById('boardRoot'), {
|
|
590
|
-
nodes: Object.values(nodesById),
|
|
591
|
-
mode: currentMode,
|
|
592
|
-
canvas: { height: '72vh', overflow: 'auto' },
|
|
593
|
-
});
|
|
594
|
-
|
|
595
|
-
runtimeUnsub = runtime.subscribe(function () {
|
|
596
|
-
const runtimeState = runtime.getState();
|
|
597
|
-
const artifacts = buildBrowserArtifactsFromRuntime({
|
|
598
|
-
boardPath: 'browser',
|
|
599
|
-
cardDefinitions,
|
|
600
|
-
runtimeModels: runtime.getNodes(),
|
|
601
|
-
graphState: runtimeState,
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
for (const [, artifact] of Object.entries(artifacts.cardRuntimeById)) {
|
|
605
|
-
LocalStorageService.writeComputedArtifact(artifact);
|
|
606
|
-
}
|
|
607
|
-
LocalStorageService.writeStatusSnapshot(artifacts.statusSnapshot);
|
|
608
|
-
|
|
609
|
-
syncBoardNodes(buildLiveCardModelsFromArtifacts(artifacts));
|
|
610
|
-
renderDebugPanel(runtimeState, artifacts);
|
|
611
|
-
});
|
|
612
|
-
|
|
613
|
-
runtime.push({ type: 'inject-tokens', tokens: [], timestamp: nowIso() });
|
|
614
|
-
runtime.retriggerAll();
|
|
615
|
-
|
|
616
|
-
window.demoLiveGraph = {
|
|
617
|
-
mode: 'browser',
|
|
618
|
-
runtime,
|
|
619
|
-
setQuotes: function (nextQuotes) {
|
|
620
|
-
mockServer.setQuotes(nextQuotes);
|
|
621
|
-
runtime.retriggerAll();
|
|
622
|
-
},
|
|
623
|
-
clearLocalStorage: function () {
|
|
624
|
-
LocalStorageService.clear();
|
|
625
|
-
Object.keys(localStorage).forEach((key) => {
|
|
626
|
-
if (String(key).startsWith(MOCK_FS_PREFIX)) localStorage.removeItem(key);
|
|
627
|
-
});
|
|
628
|
-
window.location.reload();
|
|
629
|
-
},
|
|
630
|
-
};
|
|
631
|
-
|
|
632
|
-
document.getElementById('modeBoard').addEventListener('click', function () {
|
|
633
|
-
currentMode = 'board';
|
|
634
|
-
if (board) board.setMode('board');
|
|
635
|
-
});
|
|
636
|
-
|
|
637
|
-
document.getElementById('modeCanvas').addEventListener('click', function () {
|
|
638
|
-
currentMode = 'canvas';
|
|
639
|
-
if (board) board.setMode('canvas');
|
|
640
|
-
});
|
|
641
|
-
|
|
642
|
-
document.getElementById('autoLayout').addEventListener('click', function () {
|
|
643
|
-
if (board) {
|
|
644
|
-
board.setMode('canvas');
|
|
645
|
-
currentMode = 'canvas';
|
|
646
|
-
board.autoLayout();
|
|
647
|
-
}
|
|
648
|
-
});
|
|
649
|
-
|
|
650
|
-
document.getElementById('refreshAll').addEventListener('click', function () {
|
|
651
|
-
if (runtime) runtime.retriggerAll();
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
document.getElementById('devModeToggle').addEventListener('change', function () {
|
|
655
|
-
if (board) board.setDevMode(this.checked);
|
|
656
|
-
setDebugPanelVisibility(this.checked);
|
|
657
|
-
});
|
|
658
|
-
|
|
659
|
-
setDebugPanelVisibility(document.getElementById('devModeToggle').checked);
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
bootstrap().catch(function (err) {
|
|
663
|
-
console.error(err);
|
|
664
|
-
document.getElementById('boardRoot').innerHTML =
|
|
665
|
-
`<div class="alert alert-danger">Failed to start browser runtime demo: ${String(err && err.message || err)}</div>`;
|
|
666
|
-
});
|
|
667
|
-
|
|
668
|
-
window.addEventListener('beforeunload', function () {
|
|
669
|
-
if (runtimeUnsub) runtimeUnsub();
|
|
670
|
-
if (runtime) runtime.dispose();
|
|
671
|
-
});
|
|
672
|
-
})();
|
|
673
|
-
</script>
|
|
674
|
-
</body>
|
|
675
|
-
</html>
|