yaml-flow 5.4.2 → 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 +6 -6
- package/browser/asset-integrity.json +10 -0
- 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 -1676
- 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 +561 -129
- package/browser/live-cards.schema.json +418 -132
- package/card-store.js +37 -0
- package/dist/batch/index.cjs +1 -108
- package/dist/batch/index.cjs.map +1 -1
- package/dist/batch/index.js +1 -106
- package/dist/batch/index.js.map +1 -1
- package/dist/board-live-cards-lib-Bg6EvCo5.d.cts +136 -0
- package/dist/board-live-cards-lib-jM2uYG1v.d.ts +136 -0
- package/dist/board-live-cards-public-CW5074xr.d.cts +318 -0
- package/dist/board-live-cards-public-hnZo0mAf.d.ts +318 -0
- package/dist/board-livegraph-runtime/index.cjs +2 -1671
- package/dist/board-livegraph-runtime/index.cjs.map +1 -1
- package/dist/board-livegraph-runtime/index.d.cts +12 -11
- package/dist/board-livegraph-runtime/index.d.ts +12 -11
- package/dist/board-livegraph-runtime/index.js +2 -1662
- package/dist/board-livegraph-runtime/index.js.map +1 -1
- package/dist/board-livegraph-runtime/jsonata-sync.cjs +7623 -0
- package/dist/card-compute/index.cjs +9 -7159
- package/dist/card-compute/index.cjs.map +1 -1
- package/dist/card-compute/index.d.cts +27 -1
- package/dist/card-compute/index.d.ts +27 -1
- package/dist/card-compute/index.js +9 -7145
- package/dist/card-compute/index.js.map +1 -1
- package/dist/card-compute/jsonata-sync.cjs +7623 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs +3 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs.map +1 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.d.cts +37 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.d.ts +37 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.js +3 -0
- package/dist/cli/browser-api/board-live-cards-browser-adapter.js.map +1 -0
- package/dist/cli/browser-api/card-store-browser-api.cjs +2 -0
- package/dist/cli/browser-api/card-store-browser-api.cjs.map +1 -0
- package/dist/cli/browser-api/card-store-browser-api.d.cts +26 -0
- package/dist/cli/browser-api/card-store-browser-api.d.ts +26 -0
- package/dist/cli/browser-api/card-store-browser-api.js +2 -0
- package/dist/cli/browser-api/card-store-browser-api.js.map +1 -0
- package/dist/cli/browser-api/jsonata-sync.cjs +7623 -0
- package/dist/cli/node/artifacts-store-cli.cjs +11 -0
- package/dist/cli/node/artifacts-store-cli.cjs.map +1 -0
- package/dist/cli/node/artifacts-store-cli.d.cts +8 -0
- package/dist/cli/node/artifacts-store-cli.d.ts +8 -0
- package/dist/cli/node/artifacts-store-cli.js +11 -0
- package/dist/cli/node/artifacts-store-cli.js.map +1 -0
- package/dist/cli/node/board-live-cards-cli.cjs +15 -0
- package/dist/cli/node/board-live-cards-cli.cjs.map +1 -0
- package/dist/cli/node/board-live-cards-cli.d.cts +20 -0
- package/dist/cli/node/board-live-cards-cli.d.ts +20 -0
- package/dist/cli/node/board-live-cards-cli.js +15 -0
- package/dist/cli/node/board-live-cards-cli.js.map +1 -0
- package/dist/cli/node/card-store-cli.cjs +8 -0
- package/dist/cli/node/card-store-cli.cjs.map +1 -0
- package/dist/cli/node/card-store-cli.d.cts +15 -0
- package/dist/cli/node/card-store-cli.d.ts +15 -0
- package/dist/cli/node/card-store-cli.js +8 -0
- package/dist/cli/node/card-store-cli.js.map +1 -0
- 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 +14 -0
- package/dist/cli/node/fs-board-adapter.cjs.map +1 -0
- package/dist/cli/node/fs-board-adapter.d.cts +204 -0
- package/dist/cli/node/fs-board-adapter.d.ts +204 -0
- package/dist/cli/node/fs-board-adapter.js +14 -0
- package/dist/cli/node/fs-board-adapter.js.map +1 -0
- package/dist/cli/node/jsonata-sync.cjs +7623 -0
- package/dist/cli/node/source-cli-task-executor.cjs +11 -0
- package/dist/cli/node/source-cli-task-executor.cjs.map +1 -0
- package/dist/cli/node/source-cli-task-executor.d.cts +1 -0
- package/dist/cli/node/source-cli-task-executor.d.ts +1 -0
- package/dist/cli/node/source-cli-task-executor.js +11 -0
- package/dist/cli/node/source-cli-task-executor.js.map +1 -0
- package/dist/config/index.cjs +1 -79
- package/dist/config/index.cjs.map +1 -1
- package/dist/config/index.js +1 -76
- package/dist/config/index.js.map +1 -1
- package/dist/continuous-event-graph/index.cjs +2 -2129
- package/dist/continuous-event-graph/index.cjs.map +1 -1
- package/dist/continuous-event-graph/index.d.cts +81 -5
- package/dist/continuous-event-graph/index.d.ts +81 -5
- package/dist/continuous-event-graph/index.js +2 -2088
- package/dist/continuous-event-graph/index.js.map +1 -1
- package/dist/continuous-event-graph/jsonata-sync.cjs +7623 -0
- package/dist/event-graph/index.cjs +22 -8292
- package/dist/event-graph/index.cjs.map +1 -1
- package/dist/event-graph/index.js +22 -8237
- package/dist/event-graph/index.js.map +1 -1
- package/dist/execution-refs.cjs +3 -0
- package/dist/execution-refs.cjs.map +1 -0
- package/dist/execution-refs.d.cts +260 -0
- package/dist/execution-refs.d.ts +260 -0
- package/dist/execution-refs.js +3 -0
- package/dist/execution-refs.js.map +1 -0
- package/dist/index.cjs +29 -13221
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -4
- package/dist/index.d.ts +2 -4
- package/dist/index.js +29 -13112
- package/dist/index.js.map +1 -1
- package/dist/inference/index.cjs +5 -617
- package/dist/inference/index.cjs.map +1 -1
- package/dist/inference/index.js +5 -610
- package/dist/inference/index.js.map +1 -1
- package/dist/jsonata-sync.cjs +7623 -0
- package/dist/{live-cards-bridge-x5XREkXm.d.cts → live-cards-bridge-BXbVTsna.d.cts} +27 -4
- package/dist/{live-cards-bridge-EQjytzI_.d.ts → live-cards-bridge-Ds28XR15.d.ts} +27 -4
- 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/index.cjs +11 -7129
- package/dist/step-machine/index.cjs.map +1 -1
- package/dist/step-machine/index.js +11 -7113
- package/dist/step-machine/index.js.map +1 -1
- 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 +10 -0
- package/dist/storage-refs.cjs.map +1 -0
- package/dist/storage-refs.d.cts +93 -0
- package/dist/storage-refs.d.ts +93 -0
- package/dist/storage-refs.js +10 -0
- package/dist/storage-refs.js.map +1 -0
- package/dist/stores/file.cjs +1 -114
- package/dist/stores/file.cjs.map +1 -1
- package/dist/stores/file.js +1 -112
- package/dist/stores/file.js.map +1 -1
- package/dist/stores/index.cjs +1 -231
- package/dist/stores/index.cjs.map +1 -1
- package/dist/stores/index.js +1 -227
- package/dist/stores/index.js.map +1 -1
- package/dist/stores/localStorage.cjs +1 -76
- package/dist/stores/localStorage.cjs.map +1 -1
- package/dist/stores/localStorage.js +1 -74
- package/dist/stores/localStorage.js.map +1 -1
- package/dist/stores/memory.cjs +1 -47
- package/dist/stores/memory.cjs.map +1 -1
- package/dist/stores/memory.js +1 -45
- package/dist/stores/memory.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 +291 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-fetch-prices.js +218 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-fetch-prices.py +201 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-http-test.js +357 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-inference-adapter.js +25 -16
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-public.js +552 -0
- 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 +366 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/--base-ref/.runtime-out +1 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/--base-ref/board-graph.json +32 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +70 -3
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +16 -11
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/init-board-cli.js +9 -8
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/poll-status-cli.js +49 -0
- 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 +9 -8
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/wait-completed-cli.js +12 -17
- 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 +107 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/add-cards.py +51 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/init-board.py +45 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/poll-status.py +71 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/reset-board-dir.py +36 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/inline-python-demo.flow.yaml +26 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/inline-python-handlers.py +39 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker-pycli.flow.yaml +80 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +36 -187
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.input.json +40 -34
- package/examples/cli/step-machine-cli/portfolio-tracker/run-inline-python-demo-pycli.py +43 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker-pycli.py +77 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +1 -2
- 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/agent-instructions.md +11 -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-chat-handler.js +14 -4
- package/examples/example-board/demo-server-config.json +1 -0
- package/examples/example-board/demo-server.js +366 -68
- package/examples/example-board/demo-shell-localstorage.html +774 -0
- package/examples/example-board/demo-shell-with-server.html +20 -37
- package/examples/example-board/demo-shell.html +5 -4
- package/examples/example-board/demo-task-executor.js +273 -275
- package/examples/index.html +0 -14
- package/examples/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +0 -1
- package/examples/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +1 -2
- package/package.json +46 -8
- package/schema/live-cards.schema.json +418 -132
- package/step-machine-cli.js +43 -310
- package/board-livecards-server-runtime.js +0 -1574
- package/browser/board-livecards-runtime-client.js +0 -263
- package/dist/cli/board-live-cards-cli.cjs +0 -10650
- package/dist/cli/board-live-cards-cli.cjs.map +0 -1
- package/dist/cli/board-live-cards-cli.d.cts +0 -179
- package/dist/cli/board-live-cards-cli.d.ts +0 -179
- package/dist/cli/board-live-cards-cli.js +0 -10598
- package/dist/cli/board-live-cards-cli.js.map +0 -1
- package/dist/journal-9HEgs7dU.d.ts +0 -28
- package/dist/journal-B-JCfQnh.d.cts +0 -28
- package/dist/schedule-Cszq9LYY.d.ts +0 -21
- package/dist/schedule-qWNL0RQh.d.cts +0 -21
- package/examples/browser/boards/portfolio-tracker/cards/holdings-table.json +0 -22
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-form.json +0 -16
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-risk-assessment.json +0 -28
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-value.json +0 -15
- package/examples/browser/boards/portfolio-tracker/cards/price-fetch.json +0 -15
- package/examples/browser/boards/portfolio-tracker/cards/rebalancing-strategy.json +0 -28
- package/examples/browser/boards/portfolio-tracker/fetch-prices.js +0 -43
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-task-executor.cjs +0 -96
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.bat +0 -7
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +0 -351
- 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 -674
package/browser/live-cards.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
// live-cards.js — LiveCards v3: Node-based Board/Canvas engine
|
|
2
2
|
//
|
|
3
3
|
// Schema: Each node has { id } required; all else optional.
|
|
4
|
-
// id, meta, card_data, requires, provides,
|
|
5
|
-
// Nodes with view render as cards; nodes with
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// shape (kind, url, mailbox, channel, model, ...) per executor.
|
|
10
|
-
// requires[] — upstream node IDs; engine subscribes automatically
|
|
4
|
+
// id, meta, card_data, requires, provides, view
|
|
5
|
+
// Nodes with view render as cards; nodes with no view but with source_defs declared on the
|
|
6
|
+
// underlying card definition render as source pills in canvas mode (source_defs are runtime-only;
|
|
7
|
+
// they are not interpreted here).
|
|
8
|
+
// requires[] — upstream provider tokens; engine subscribes automatically
|
|
11
9
|
// provides[] — [{ bindTo, src }] explicit downstream token bindings
|
|
10
|
+
// computed_values — derived values produced by the runtime; rendered as-is, never recomputed here
|
|
11
|
+
//
|
|
12
|
+
// Rendering contract: this module renders derived state only. View bind paths resolve to one of
|
|
13
|
+
// card_data | requires | computed_values | runtime_state. Raw fetched-source payloads stay in the
|
|
14
|
+
// runtime and never reach the Board.
|
|
12
15
|
//
|
|
13
16
|
// Uses Bootstrap 5 for layout/forms, optional Chart.js for charts.
|
|
14
|
-
// Uses CardCompute (card-compute.js) for declarative compute expressions.
|
|
15
17
|
//
|
|
16
18
|
// API:
|
|
17
19
|
// const engine = LiveCard.init({ resolve, onPatch, onPatchState, onRefresh, onAction, getChatMessages, markdown, sanitize, chartLib });
|
|
@@ -24,8 +26,15 @@
|
|
|
24
26
|
// engine.appendChatMessage(nodeId, role, text)
|
|
25
27
|
// engine.registerRenderer(name, fn)
|
|
26
28
|
//
|
|
27
|
-
//
|
|
28
|
-
// board.
|
|
29
|
+
// Reactive board (preferred): state in, view out. No destructive re-renders.
|
|
30
|
+
// const board = LiveCard.Board(engine, el, { initialState, getNodeIds, selectNode, mode?, canvas? });
|
|
31
|
+
// board.setState(nextState) — diff vs prev; per-node updates only
|
|
32
|
+
// board.destroy()
|
|
33
|
+
//
|
|
34
|
+
// Imperative core (advanced): direct node-list manipulation.
|
|
35
|
+
// const core = LiveCard.BoardCore(engine, el, { nodes, positions?, mode, canvas });
|
|
36
|
+
// core.add(node), core.remove(id), core.reorder(ids), core.updateNode(id, model)
|
|
37
|
+
// core.setMode('board'|'canvas'), core.setDevMode(flag), core.autoLayout(), core.clear(), core.destroy()
|
|
29
38
|
|
|
30
39
|
// eslint-disable-next-line no-unused-vars
|
|
31
40
|
var LiveCard = (function () {
|
|
@@ -98,6 +107,20 @@ var LiveCard = (function () {
|
|
|
98
107
|
.lc-gandalf-caret:hover { opacity:1; }
|
|
99
108
|
.lc-gandalf-card.lc-collapsed .lc-gandalf-caret { transform:rotate(-90deg); }
|
|
100
109
|
.lc-gandalf-card.lc-collapsed .card-body { display:none !important; }
|
|
110
|
+
.lc-token-row { display:flex; flex-wrap:wrap; gap:0.35rem; padding:0.2rem 0.5rem; background:transparent; align-items:center; justify-content:center; min-height:0; }
|
|
111
|
+
.lc-token-row-requires { border-bottom:none; padding-bottom:0.1rem; }
|
|
112
|
+
.lc-token-row-provides { border-top:none; padding-top:0.1rem; }
|
|
113
|
+
.lc-token-gem { display:inline-block; width:10px; height:10px; border-radius:50%; cursor:default; transition:transform .15s, box-shadow .15s; position:relative; }
|
|
114
|
+
.lc-token-gem:hover { transform:scale(1.5); box-shadow:0 0 4px rgba(0,0,0,0.3); z-index:5; }
|
|
115
|
+
.lc-token-gem-requires { background:var(--bs-secondary,#6c757d); border:1.5px solid var(--bs-secondary,#6c757d); }
|
|
116
|
+
.lc-token-gem-requires.lc-token-available { background:var(--bs-success,#198754); border-color:var(--bs-success,#198754); }
|
|
117
|
+
.lc-token-gem-provides { background:var(--bs-secondary,#6c757d); border:1.5px solid var(--bs-secondary,#6c757d); }
|
|
118
|
+
.lc-token-gem-provides.lc-token-available { background:var(--bs-success,#198754); border-color:var(--bs-success,#198754); }
|
|
119
|
+
.lc-running { animation:lc-running-pulse 2s ease-in-out infinite; position:relative; }
|
|
120
|
+
.lc-running::before { content:''; position:absolute; inset:-2px; border-radius:inherit; background:linear-gradient(90deg,transparent,rgba(13,110,253,.45),rgba(102,16,242,.4),rgba(13,110,253,.45),transparent); background-size:300% 100%; animation:lc-running-shimmer 2s linear infinite; z-index:-1; pointer-events:none; }
|
|
121
|
+
@keyframes lc-running-pulse { 0%,100%{ box-shadow:0 0 4px rgba(13,110,253,.15); } 50%{ box-shadow:0 0 14px 3px rgba(13,110,253,.35); } }
|
|
122
|
+
@keyframes lc-running-shimmer { 0%{ background-position:100% 0; } 100%{ background-position:-100% 0; } }
|
|
123
|
+
.lc-running .card-header { border-bottom-color:rgba(13,110,253,.35); }
|
|
101
124
|
@media (max-width:576px) {
|
|
102
125
|
.lc-metric-value { font-size:1.5rem; }
|
|
103
126
|
.lc-chart-wrap { min-height:150px; }
|
|
@@ -216,6 +239,30 @@ var LiveCard = (function () {
|
|
|
216
239
|
const _formState = {}; // stateKey → { baseValues, journal }
|
|
217
240
|
const _notesState = {}; // stateKey → { baseContent, journal|null }
|
|
218
241
|
const _todoState = {}; // stateKey → { currentState, pending } for todo dirty tracking
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Overlay a "Saving…" spinner over `el` while a patch is in-flight.
|
|
245
|
+
* The overlay is removed automatically on the next SSE re-render because
|
|
246
|
+
* every editable renderer does `el.innerHTML = …` on refresh.
|
|
247
|
+
*/
|
|
248
|
+
function _showSavingOverlay(el) {
|
|
249
|
+
// Ensure the container is a positioned ancestor so the overlay can fill it.
|
|
250
|
+
if (getComputedStyle(el).position === 'static') el.style.position = 'relative';
|
|
251
|
+
const overlay = document.createElement('div');
|
|
252
|
+
overlay.className = 'lc-saving-overlay';
|
|
253
|
+
overlay.setAttribute('aria-live', 'polite');
|
|
254
|
+
overlay.style.cssText = [
|
|
255
|
+
'position:absolute', 'inset:0',
|
|
256
|
+
'background:rgba(255,255,255,0.78)',
|
|
257
|
+
'display:flex', 'align-items:center', 'justify-content:center',
|
|
258
|
+
'gap:0.5rem', 'z-index:20', 'border-radius:inherit',
|
|
259
|
+
'pointer-events:all', // blocks all clicks on underlying inputs
|
|
260
|
+
].join(';');
|
|
261
|
+
overlay.innerHTML =
|
|
262
|
+
'<span class="spinner-border spinner-border-sm text-primary" role="status" aria-hidden="true"></span>' +
|
|
263
|
+
'<span class="text-primary fw-medium small">Saving…</span>';
|
|
264
|
+
el.appendChild(overlay);
|
|
265
|
+
}
|
|
219
266
|
const _renderers = {}; // kind → fn
|
|
220
267
|
const _nodeEls = {}; // nodeId → { container, resultEl, uid }
|
|
221
268
|
const _chatModal = {
|
|
@@ -261,11 +308,6 @@ var LiveCard = (function () {
|
|
|
261
308
|
return _cleanup[id];
|
|
262
309
|
}
|
|
263
310
|
|
|
264
|
-
function _runCompute() {
|
|
265
|
-
// Runtime payload is authoritative; UI never recomputes derived values.
|
|
266
|
-
return Promise.resolve();
|
|
267
|
-
}
|
|
268
|
-
|
|
269
311
|
function _ensureChatModal() {
|
|
270
312
|
if (_chatModal.backdrop) return;
|
|
271
313
|
|
|
@@ -753,11 +795,9 @@ var LiveCard = (function () {
|
|
|
753
795
|
const ns = {
|
|
754
796
|
card: node && node.card ? node.card : {},
|
|
755
797
|
card_data: node && node.card_data ? node.card_data : {},
|
|
756
|
-
fetched_sources: node && node.fetched_sources ? node.fetched_sources : {},
|
|
757
798
|
requires: node && node.requires ? node.requires : {},
|
|
758
799
|
computed_values: node && node.computed_values ? node.computed_values : {},
|
|
759
800
|
runtime_state: node && node.runtime_state ? node.runtime_state : {},
|
|
760
|
-
data_objects: node && node.data_objects ? node.data_objects : {},
|
|
761
801
|
};
|
|
762
802
|
|
|
763
803
|
if (!Object.prototype.hasOwnProperty.call(ns, root)) return undefined;
|
|
@@ -781,7 +821,29 @@ var LiveCard = (function () {
|
|
|
781
821
|
const requires = (node && node.card && Array.isArray(node.card.requires)) ? node.card.requires : [];
|
|
782
822
|
if (!requires.length) return;
|
|
783
823
|
const cleanup = _getCleanup(node.id);
|
|
784
|
-
|
|
824
|
+
|
|
825
|
+
// Resolve required tokens to upstream node IDs via provides declarations.
|
|
826
|
+
// Build a token→nodeId map from all nodes the engine knows about.
|
|
827
|
+
const tokenMap = {};
|
|
828
|
+
const allNodeIds = Object.keys(_subs).concat(Object.keys(_nodeEls));
|
|
829
|
+
allNodeIds.forEach(function(nid) {
|
|
830
|
+
const n = cfg.resolve(nid);
|
|
831
|
+
if (!n || !n.card) return;
|
|
832
|
+
var provides = (Array.isArray(n.card.provides) && n.card.provides.length)
|
|
833
|
+
? n.card.provides.map(function(p) { return typeof p === 'string' ? p : (p.bindTo || p); })
|
|
834
|
+
: [n.id];
|
|
835
|
+
provides.forEach(function(tok) { tokenMap[tok] = n.id; });
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
// Subscribe to each upstream provider node (deduplicated)
|
|
839
|
+
const seen = {};
|
|
840
|
+
const upIds = [];
|
|
841
|
+
requires.forEach(function(token) {
|
|
842
|
+
var srcId = tokenMap[token] || token; // fallback: treat token as nodeId
|
|
843
|
+
if (!seen[srcId]) { seen[srcId] = true; upIds.push(srcId); }
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
cleanup.unsubs = upIds.map(upId => subscribe(upId, () => {
|
|
785
847
|
const info = _nodeEls[node.id];
|
|
786
848
|
if (!info || !info.resultEl) return;
|
|
787
849
|
const updated = cfg.resolve(node.id);
|
|
@@ -1145,6 +1207,7 @@ var LiveCard = (function () {
|
|
|
1145
1207
|
const nextValues = getEffectiveValues();
|
|
1146
1208
|
cfg.onPatchState(node.id, { fieldValues: nextValues });
|
|
1147
1209
|
btn.textContent = 'Saving...';
|
|
1210
|
+
_showSavingOverlay(el);
|
|
1148
1211
|
}, { signal });
|
|
1149
1212
|
|
|
1150
1213
|
discardBtn.addEventListener('click', () => {
|
|
@@ -1223,6 +1286,7 @@ var LiveCard = (function () {
|
|
|
1223
1286
|
const nextValue = textarea.value;
|
|
1224
1287
|
cfg.onPatchState(node.id, { notes: nextValue });
|
|
1225
1288
|
saveBtn.textContent = 'Saving...';
|
|
1289
|
+
_showSavingOverlay(el);
|
|
1226
1290
|
}, { signal });
|
|
1227
1291
|
|
|
1228
1292
|
discardBtn.addEventListener('click', () => {
|
|
@@ -1307,6 +1371,7 @@ var LiveCard = (function () {
|
|
|
1307
1371
|
cfg.onPatchState(node.id, { fieldValues: rows });
|
|
1308
1372
|
const saveBtn = el.querySelector('.lc-et-save');
|
|
1309
1373
|
if (saveBtn) saveBtn.textContent = 'Saving...';
|
|
1374
|
+
_showSavingOverlay(el);
|
|
1310
1375
|
}
|
|
1311
1376
|
|
|
1312
1377
|
function commitDiscard() {
|
|
@@ -1925,11 +1990,11 @@ var LiveCard = (function () {
|
|
|
1925
1990
|
//
|
|
1926
1991
|
// Usage:
|
|
1927
1992
|
// { "kind": "ref",
|
|
1928
|
-
// "data": { "bind": "
|
|
1993
|
+
// "data": { "bind": "computed_values.proposed_trades",
|
|
1929
1994
|
// "viewBind": "card_data.display_mode",
|
|
1930
1995
|
// "fallbackKind": "table" } }
|
|
1931
1996
|
//
|
|
1932
|
-
// viewBind can point to any namespace: card_data,
|
|
1997
|
+
// viewBind can point to any namespace: card_data, requires, computed_values, runtime_state.
|
|
1933
1998
|
// If the resolved view object contains a "bind" sub-path, that overrides data.bind.
|
|
1934
1999
|
const _REF_KIND_WHITELIST = new Set([
|
|
1935
2000
|
'table','editable-table','chart','metric','list','badge',
|
|
@@ -2144,7 +2209,7 @@ var LiveCard = (function () {
|
|
|
2144
2209
|
if (node.card_data && node.card_data.status === 'error' && node.card_data.error) {
|
|
2145
2210
|
resultEl.innerHTML = `<div class="text-danger small fw-semibold">Refresh failed</div><pre class="text-muted small mt-1" style="white-space:pre-wrap">${_esc(node.card_data.error)}</pre>`;
|
|
2146
2211
|
} else {
|
|
2147
|
-
|
|
2212
|
+
_renderElements(node, resultEl);
|
|
2148
2213
|
}
|
|
2149
2214
|
|
|
2150
2215
|
// ---- Wire refresh ----
|
|
@@ -2239,7 +2304,7 @@ var LiveCard = (function () {
|
|
|
2239
2304
|
if (node.card_data.status === 'error' && node.card_data.error) {
|
|
2240
2305
|
info.resultEl.innerHTML = `<div class="text-danger small fw-semibold">Refresh failed</div><pre class="text-muted small mt-1" style="white-space:pre-wrap">${_esc(node.card_data.error)}</pre>`;
|
|
2241
2306
|
} else {
|
|
2242
|
-
|
|
2307
|
+
_renderElements(node, info.resultEl);
|
|
2243
2308
|
}
|
|
2244
2309
|
}
|
|
2245
2310
|
|
|
@@ -2318,10 +2383,11 @@ var LiveCard = (function () {
|
|
|
2318
2383
|
}
|
|
2319
2384
|
|
|
2320
2385
|
// ===========================================================================
|
|
2321
|
-
//
|
|
2386
|
+
// BoardCore — imperative grid (board) and DAG (canvas) modes.
|
|
2387
|
+
// Most callers should use Board (reactive wrapper) instead.
|
|
2322
2388
|
// ===========================================================================
|
|
2323
2389
|
|
|
2324
|
-
function
|
|
2390
|
+
function BoardCore(engine, containerEl, opts) {
|
|
2325
2391
|
opts = opts || {};
|
|
2326
2392
|
const mode = { current: opts.mode || 'board' };
|
|
2327
2393
|
const devMode = { current: opts.devMode || false };
|
|
@@ -2351,6 +2417,17 @@ var LiveCard = (function () {
|
|
|
2351
2417
|
};
|
|
2352
2418
|
const ac = new AbortController();
|
|
2353
2419
|
const signal = ac.signal;
|
|
2420
|
+
const _edges = []; // LeaderLine instances for canvas edges
|
|
2421
|
+
|
|
2422
|
+
// Edge style config (from canvas opts)
|
|
2423
|
+
const edgeOpts = co.edgeStyle || {};
|
|
2424
|
+
const edgeCfg = {
|
|
2425
|
+
color: edgeOpts.color || 'rgba(108, 117, 125, 0.6)',
|
|
2426
|
+
size: edgeOpts.size || 2,
|
|
2427
|
+
dash: edgeOpts.dash !== false,
|
|
2428
|
+
animation: edgeOpts.animation !== false,
|
|
2429
|
+
endPlug: edgeOpts.endPlug || 'arrow1',
|
|
2430
|
+
};
|
|
2354
2431
|
|
|
2355
2432
|
// DOM containers
|
|
2356
2433
|
const root = document.createElement('div');
|
|
@@ -2362,20 +2439,18 @@ var LiveCard = (function () {
|
|
|
2362
2439
|
|
|
2363
2440
|
const canvasEl = document.createElement('div');
|
|
2364
2441
|
canvasEl.className = 'lc-canvas';
|
|
2365
|
-
|
|
2366
|
-
const canvasOverflow = co.overflow || 'auto';
|
|
2367
|
-
canvasEl.style.cssText = 'position:relative;overflow:' + canvasOverflow + ';width:100%;height:' + canvasHeight + ';';
|
|
2442
|
+
canvasEl.style.cssText = 'position:relative;overflow:auto;width:100%;';
|
|
2368
2443
|
const canvasInner = document.createElement('div');
|
|
2369
2444
|
canvasInner.className = 'lc-canvas-inner';
|
|
2370
|
-
canvasInner.style.cssText = 'position:
|
|
2445
|
+
canvasInner.style.cssText = 'position:relative;transform-origin:0 0;min-width:100%;min-height:100%;';
|
|
2371
2446
|
canvasEl.appendChild(canvasInner);
|
|
2372
2447
|
|
|
2373
2448
|
// SVG overlay for edges
|
|
2374
2449
|
const svgEl = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
2375
2450
|
svgEl.setAttribute('class', 'lc-canvas-edges');
|
|
2376
|
-
svgEl.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;overflow:
|
|
2451
|
+
svgEl.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;overflow:hidden;z-index:0;';
|
|
2377
2452
|
const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
|
2378
|
-
defs.innerHTML = '<marker id="lc-arrow" viewBox="0 0 10 10" refX="
|
|
2453
|
+
defs.innerHTML = '<marker id="lc-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M 0 1 L 8 5 L 0 9 z" fill="rgba(108,117,125,0.55)"/></marker>';
|
|
2379
2454
|
svgEl.appendChild(defs);
|
|
2380
2455
|
canvasInner.appendChild(svgEl);
|
|
2381
2456
|
|
|
@@ -2384,10 +2459,16 @@ var LiveCard = (function () {
|
|
|
2384
2459
|
const s = document.createElement('style');
|
|
2385
2460
|
s.id = 'lc-board-css';
|
|
2386
2461
|
s.textContent = `
|
|
2387
|
-
.lc-canvas-card { position:absolute; min-width:${cvs.minWidth}px;
|
|
2462
|
+
.lc-canvas-card { position:absolute; min-width:${cvs.minWidth}px; cursor:grab; user-select:none; z-index:1; }
|
|
2388
2463
|
.lc-canvas-card.lc-dragging { cursor:grabbing; z-index:10; box-shadow:0 8px 24px rgba(0,0,0,0.18)!important; }
|
|
2389
|
-
.lc-canvas-card .card-body {
|
|
2390
|
-
.lc-canvas-
|
|
2464
|
+
.lc-canvas-card .card-body { overflow:hidden; }
|
|
2465
|
+
.lc-canvas-card.lc-resizing { cursor:nwse-resize; z-index:10; }
|
|
2466
|
+
.lc-resize-handle { position:absolute; bottom:0; right:0; width:14px; height:14px; cursor:nwse-resize; z-index:2; opacity:0.4; transition:opacity .15s; }
|
|
2467
|
+
.lc-resize-handle:hover { opacity:1; }
|
|
2468
|
+
.lc-resize-handle::after { content:''; position:absolute; bottom:3px; right:3px; width:8px; height:8px; border-right:2px solid var(--bs-secondary,#6c757d); border-bottom:2px solid var(--bs-secondary,#6c757d); }
|
|
2469
|
+
.lc-canvas-edges path.lc-edge-path { stroke:rgba(100,140,200,0.5); stroke-width:2; fill:none; stroke-linecap:round; }
|
|
2470
|
+
.lc-canvas-edges line { stroke:rgba(100,140,200,0.4); stroke-width:2; }
|
|
2471
|
+
@keyframes lc-edge-flow { to { stroke-dashoffset:-10; } }
|
|
2391
2472
|
.lc-source-node { position:absolute; cursor:grab; user-select:none; z-index:1; }
|
|
2392
2473
|
.lc-source-node.lc-dragging { cursor:grabbing; z-index:10; }
|
|
2393
2474
|
`;
|
|
@@ -2422,6 +2503,51 @@ var LiveCard = (function () {
|
|
|
2422
2503
|
return (node && node.card && Array.isArray(node.card.requires)) ? node.card.requires : [];
|
|
2423
2504
|
}
|
|
2424
2505
|
|
|
2506
|
+
/**
|
|
2507
|
+
* Returns tokens this node provides.
|
|
2508
|
+
* Explicit: card.provides[].bindTo
|
|
2509
|
+
* Implicit default: the node's own id (if no provides declared)
|
|
2510
|
+
*/
|
|
2511
|
+
function _getProvides(node) {
|
|
2512
|
+
if (!node || !node.card) return [node ? node.id : ''];
|
|
2513
|
+
if (Array.isArray(node.card.provides) && node.card.provides.length > 0) {
|
|
2514
|
+
return node.card.provides.map(function(p) { return (typeof p === 'string') ? p : (p.bindTo || p); });
|
|
2515
|
+
}
|
|
2516
|
+
// Default: node provides a token equal to its own id
|
|
2517
|
+
return [node.id];
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
/**
|
|
2521
|
+
* Build token → provider nodeId map from all nodes in the board.
|
|
2522
|
+
* Called before drawing edges so we can resolve requires tokens → source nodes.
|
|
2523
|
+
*/
|
|
2524
|
+
function _buildTokenMap() {
|
|
2525
|
+
var map = {};
|
|
2526
|
+
nodeList.forEach(function(node) {
|
|
2527
|
+
_getProvides(node).forEach(function(token) {
|
|
2528
|
+
map[token] = node.id;
|
|
2529
|
+
});
|
|
2530
|
+
});
|
|
2531
|
+
return map;
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
/**
|
|
2535
|
+
* Resolve required tokens to provider node IDs.
|
|
2536
|
+
* Returns deduplicated array of source node IDs for a given consumer node.
|
|
2537
|
+
*/
|
|
2538
|
+
function _resolveEdgeSources(node, tokenMap) {
|
|
2539
|
+
var sources = [];
|
|
2540
|
+
var seen = {};
|
|
2541
|
+
_getRequires(node).forEach(function(token) {
|
|
2542
|
+
var srcId = tokenMap[token];
|
|
2543
|
+
if (srcId && !seen[srcId]) {
|
|
2544
|
+
seen[srcId] = true;
|
|
2545
|
+
sources.push(srcId);
|
|
2546
|
+
}
|
|
2547
|
+
});
|
|
2548
|
+
return sources;
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2425
2551
|
function _showCardInspector(node) {
|
|
2426
2552
|
const modal = document.createElement('div');
|
|
2427
2553
|
modal.className = 'modal d-block';
|
|
@@ -2447,32 +2573,9 @@ var LiveCard = (function () {
|
|
|
2447
2573
|
|
|
2448
2574
|
const cardSection = document.createElement('div');
|
|
2449
2575
|
cardSection.className = 'mb-4';
|
|
2450
|
-
cardSection.innerHTML = '<h6 class="fw-semibold mb-2">Card
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
const editor = document.createElement('textarea');
|
|
2455
|
-
editor.className = 'form-control form-control-sm font-monospace';
|
|
2456
|
-
editor.rows = 16;
|
|
2457
|
-
editor.style.whiteSpace = 'pre';
|
|
2458
|
-
editor.value = JSON.stringify(editableCardObject, null, 2);
|
|
2459
|
-
|
|
2460
|
-
const editorHint = document.createElement('div');
|
|
2461
|
-
editorHint.className = 'small text-muted mt-2';
|
|
2462
|
-
editorHint.textContent = 'Edit JSON and click Submit to apply updates to this card.';
|
|
2463
|
-
|
|
2464
|
-
const editorError = document.createElement('div');
|
|
2465
|
-
editorError.className = 'small text-danger mt-1 d-none';
|
|
2466
|
-
|
|
2467
|
-
const submitBtn = document.createElement('button');
|
|
2468
|
-
submitBtn.type = 'button';
|
|
2469
|
-
submitBtn.className = 'btn btn-primary btn-sm mb-2';
|
|
2470
|
-
submitBtn.textContent = 'Submit';
|
|
2471
|
-
|
|
2472
|
-
cardSection.appendChild(submitBtn);
|
|
2473
|
-
cardSection.appendChild(editor);
|
|
2474
|
-
cardSection.appendChild(editorHint);
|
|
2475
|
-
cardSection.appendChild(editorError);
|
|
2576
|
+
cardSection.innerHTML = '<h6 class="fw-semibold mb-2">Card Definition (Read-only)</h6>';
|
|
2577
|
+
const cardDef = (node && node.card) ? node.card : {};
|
|
2578
|
+
cardSection.innerHTML += `<pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto; font-size: 12px; white-space: pre-wrap; word-wrap: break-word;">${_esc(JSON.stringify(cardDef, null, 2))}</pre>`;
|
|
2476
2579
|
body.appendChild(cardSection);
|
|
2477
2580
|
|
|
2478
2581
|
const computedSection = document.createElement('div');
|
|
@@ -2482,13 +2585,6 @@ var LiveCard = (function () {
|
|
|
2482
2585
|
computedSection.innerHTML += `<pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto; font-size: 12px; white-space: pre-wrap; word-wrap: break-word;">${_esc(JSON.stringify(computedValues, null, 2))}</pre>`;
|
|
2483
2586
|
body.appendChild(computedSection);
|
|
2484
2587
|
|
|
2485
|
-
const sourcesSection = document.createElement('div');
|
|
2486
|
-
sourcesSection.className = 'mb-4';
|
|
2487
|
-
sourcesSection.innerHTML = '<h6 class="fw-semibold mb-2">Fetched Sources (Read-only)</h6>';
|
|
2488
|
-
const sourcesData = node.fetched_sources || {};
|
|
2489
|
-
sourcesSection.innerHTML += `<pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto; font-size: 12px; white-space: pre-wrap; word-wrap: break-word;">${_esc(JSON.stringify(sourcesData, null, 2))}</pre>`;
|
|
2490
|
-
body.appendChild(sourcesSection);
|
|
2491
|
-
|
|
2492
2588
|
const requiresSection = document.createElement('div');
|
|
2493
2589
|
requiresSection.className = 'mb-4';
|
|
2494
2590
|
requiresSection.innerHTML = '<h6 class="fw-semibold mb-2">Requires (Read-only)</h6>';
|
|
@@ -2511,43 +2607,6 @@ var LiveCard = (function () {
|
|
|
2511
2607
|
closeBtn.textContent = 'Close';
|
|
2512
2608
|
closeBtn.addEventListener('click', closeModal);
|
|
2513
2609
|
|
|
2514
|
-
submitBtn.addEventListener('click', function () {
|
|
2515
|
-
editorError.classList.add('d-none');
|
|
2516
|
-
editorError.textContent = '';
|
|
2517
|
-
try {
|
|
2518
|
-
const parsed = JSON.parse(editor.value);
|
|
2519
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
2520
|
-
throw new Error('Card Object must be a JSON object.');
|
|
2521
|
-
}
|
|
2522
|
-
if (parsed.id && parsed.id !== node.id) {
|
|
2523
|
-
throw new Error('Changing card id is not supported in the inspector.');
|
|
2524
|
-
}
|
|
2525
|
-
|
|
2526
|
-
const fixedId = node.id;
|
|
2527
|
-
const preservedRuntime = {
|
|
2528
|
-
card_data: node.card_data,
|
|
2529
|
-
fetched_sources: node.fetched_sources,
|
|
2530
|
-
requires: node.requires,
|
|
2531
|
-
computed_values: node.computed_values,
|
|
2532
|
-
runtime_state: node.runtime_state,
|
|
2533
|
-
data_objects: node.data_objects,
|
|
2534
|
-
};
|
|
2535
|
-
node.card = parsed;
|
|
2536
|
-
node.id = fixedId;
|
|
2537
|
-
Object.assign(node, preservedRuntime);
|
|
2538
|
-
|
|
2539
|
-
engine.notify(node.id, { inspector: 'card-object-updated' });
|
|
2540
|
-
_render();
|
|
2541
|
-
|
|
2542
|
-
submitBtn.textContent = '✓ Saved';
|
|
2543
|
-
setTimeout(function () { submitBtn.textContent = 'Submit'; }, 1200);
|
|
2544
|
-
closeModal();
|
|
2545
|
-
} catch (err) {
|
|
2546
|
-
editorError.textContent = 'Invalid JSON: ' + String((err && err.message) || err);
|
|
2547
|
-
editorError.classList.remove('d-none');
|
|
2548
|
-
}
|
|
2549
|
-
});
|
|
2550
|
-
|
|
2551
2610
|
footer.appendChild(closeBtn);
|
|
2552
2611
|
content.appendChild(header);
|
|
2553
2612
|
content.appendChild(body);
|
|
@@ -2562,8 +2621,9 @@ var LiveCard = (function () {
|
|
|
2562
2621
|
const card = node && node.card ? node.card : {};
|
|
2563
2622
|
const isSimulation = card.meta && card.meta.simulation === true;
|
|
2564
2623
|
const isGandalfCard = card.meta && card.meta._gandalfCard === true;
|
|
2624
|
+
const isRunning = node && node.runtime_state && node.runtime_state.task_status === 'running';
|
|
2565
2625
|
const extraClass = isSimulation ? ' lc-simulation-card' : (isGandalfCard ? ' lc-gandalf-card' : '');
|
|
2566
|
-
wrap.className = 'card shadow-sm h-100' + extraClass;
|
|
2626
|
+
wrap.className = 'card shadow-sm h-100' + extraClass + (isRunning ? ' lc-running' : '');
|
|
2567
2627
|
const header = document.createElement('div');
|
|
2568
2628
|
header.className = 'card-header d-flex align-items-center gap-2 py-2';
|
|
2569
2629
|
const title = (card.meta && card.meta.title) || node.id;
|
|
@@ -2639,8 +2699,44 @@ var LiveCard = (function () {
|
|
|
2639
2699
|
|
|
2640
2700
|
const body = document.createElement('div');
|
|
2641
2701
|
body.className = 'card-body p-2';
|
|
2702
|
+
|
|
2703
|
+
// Token gem rows — requires gems above header, provides gems below body
|
|
2704
|
+
const requiresTokens = (card.requires && Array.isArray(card.requires)) ? card.requires : [];
|
|
2705
|
+
const providesTokens = (Array.isArray(card.provides) && card.provides.length)
|
|
2706
|
+
? card.provides.map(function(p) { return typeof p === 'string' ? p : (p.bindTo || p); })
|
|
2707
|
+
: [node.id];
|
|
2708
|
+
|
|
2709
|
+
// Requires gems — top of card (above header)
|
|
2710
|
+
if (requiresTokens.length) {
|
|
2711
|
+
const reqRow = document.createElement('div');
|
|
2712
|
+
reqRow.className = 'lc-token-row lc-token-row-requires';
|
|
2713
|
+
requiresTokens.forEach(function(token) {
|
|
2714
|
+
const gem = document.createElement('span');
|
|
2715
|
+
gem.className = 'lc-token-gem lc-token-gem-requires';
|
|
2716
|
+
gem.dataset.token = token;
|
|
2717
|
+
gem.title = token;
|
|
2718
|
+
reqRow.appendChild(gem);
|
|
2719
|
+
});
|
|
2720
|
+
wrap.appendChild(reqRow);
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2642
2723
|
wrap.appendChild(header);
|
|
2643
2724
|
wrap.appendChild(body);
|
|
2725
|
+
|
|
2726
|
+
// Provides gems — bottom of card (below body)
|
|
2727
|
+
if (providesTokens.length) {
|
|
2728
|
+
const provRow = document.createElement('div');
|
|
2729
|
+
provRow.className = 'lc-token-row lc-token-row-provides';
|
|
2730
|
+
providesTokens.forEach(function(token) {
|
|
2731
|
+
const gem = document.createElement('span');
|
|
2732
|
+
gem.className = 'lc-token-gem lc-token-gem-provides';
|
|
2733
|
+
gem.dataset.token = token;
|
|
2734
|
+
gem.title = token;
|
|
2735
|
+
provRow.appendChild(gem);
|
|
2736
|
+
});
|
|
2737
|
+
wrap.appendChild(provRow);
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2644
2740
|
return { wrap, header, body };
|
|
2645
2741
|
}
|
|
2646
2742
|
|
|
@@ -2661,7 +2757,23 @@ var LiveCard = (function () {
|
|
|
2661
2757
|
|
|
2662
2758
|
// ---- Board mode ----
|
|
2663
2759
|
|
|
2760
|
+
// Compute canvas inner size from card positions + padding
|
|
2761
|
+
function _fitCanvasToContent() {
|
|
2762
|
+
var pad = 100;
|
|
2763
|
+
var maxR = 0, maxB = 0;
|
|
2764
|
+
canvasInner.querySelectorAll('.lc-canvas-card,.lc-source-node').forEach(function(el) {
|
|
2765
|
+
var r = el.offsetLeft + el.offsetWidth;
|
|
2766
|
+
var b = el.offsetTop + el.offsetHeight;
|
|
2767
|
+
if (r > maxR) maxR = r;
|
|
2768
|
+
if (b > maxB) maxB = b;
|
|
2769
|
+
});
|
|
2770
|
+
canvasInner.style.width = (maxR + pad) + 'px';
|
|
2771
|
+
canvasInner.style.height = (maxB + pad) + 'px';
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2664
2774
|
function _renderBoard() {
|
|
2775
|
+
_destroyEdges();
|
|
2776
|
+
document.body.style.overflow = '';
|
|
2665
2777
|
root.innerHTML = '';
|
|
2666
2778
|
root.appendChild(gridEl);
|
|
2667
2779
|
gridEl.innerHTML = '';
|
|
@@ -2684,6 +2796,7 @@ var LiveCard = (function () {
|
|
|
2684
2796
|
nodeMap[node.id] = { node, colEl: col, bodyEl: body };
|
|
2685
2797
|
engine.render(node, body, { showChat });
|
|
2686
2798
|
});
|
|
2799
|
+
_updateTokenAvailability();
|
|
2687
2800
|
}
|
|
2688
2801
|
|
|
2689
2802
|
// ---- Canvas mode ----
|
|
@@ -2692,26 +2805,116 @@ var LiveCard = (function () {
|
|
|
2692
2805
|
canvasInner.style.transform = `translate(${cvs.panX}px,${cvs.panY}px) scale(${cvs.zoom})`;
|
|
2693
2806
|
}
|
|
2694
2807
|
|
|
2808
|
+
/**
|
|
2809
|
+
* Update token badge availability: a provides badge turns green when the
|
|
2810
|
+
* node has data; a requires badge turns green when the upstream provider
|
|
2811
|
+
* has data for that token.
|
|
2812
|
+
*/
|
|
2813
|
+
function _updateTokenAvailability() {
|
|
2814
|
+
var tokenMap = _buildTokenMap();
|
|
2815
|
+
// A node "has data" when card_data or computed_values is non-empty, or status is fresh/completed.
|
|
2816
|
+
var nodeHasData = {};
|
|
2817
|
+
nodeList.forEach(function(node) {
|
|
2818
|
+
var cd = node.card_data || (node.card && node.card.card_data);
|
|
2819
|
+
var cv = node.computed_values;
|
|
2820
|
+
var status = cd && cd.status;
|
|
2821
|
+
var hasOutput = (cd && Object.keys(cd).length > 0) || (cv && Object.keys(cv).length > 0);
|
|
2822
|
+
nodeHasData[node.id] = hasOutput || status === 'fresh' || status === 'completed';
|
|
2823
|
+
});
|
|
2824
|
+
|
|
2825
|
+
// Update all gem elements in root container
|
|
2826
|
+
var allGems = root.querySelectorAll('.lc-token-gem');
|
|
2827
|
+
allGems.forEach(function(gem) {
|
|
2828
|
+
var token = gem.dataset.token;
|
|
2829
|
+
if (!token) return;
|
|
2830
|
+
if (gem.classList.contains('lc-token-gem-provides')) {
|
|
2831
|
+
// The provides gem: green if this node has data
|
|
2832
|
+
var nodeEl = gem.closest('[data-node-id]');
|
|
2833
|
+
var nId = nodeEl && nodeEl.dataset.nodeId;
|
|
2834
|
+
gem.classList.toggle('lc-token-available', !!(nId && nodeHasData[nId]));
|
|
2835
|
+
} else if (gem.classList.contains('lc-token-gem-requires')) {
|
|
2836
|
+
// The requires gem: green if the upstream provider for this token has data
|
|
2837
|
+
var srcId = tokenMap[token];
|
|
2838
|
+
gem.classList.toggle('lc-token-available', !!(srcId && nodeHasData[srcId]));
|
|
2839
|
+
}
|
|
2840
|
+
});
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2843
|
+
function _destroyEdges() {
|
|
2844
|
+
_edges.forEach(function(line) { try { line.remove(); } catch(e) { /* noop */ } });
|
|
2845
|
+
_edges.length = 0;
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
function _repositionEdges() {
|
|
2849
|
+
_edges.forEach(function(line) { try { line.position(); } catch(e) { /* noop */ } });
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2695
2852
|
function _drawEdges() {
|
|
2696
|
-
|
|
2853
|
+
_destroyEdges();
|
|
2854
|
+
svgEl.querySelectorAll('line,path').forEach(function(el) { el.remove(); });
|
|
2697
2855
|
if (!cvs.edges) return;
|
|
2698
2856
|
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2857
|
+
// Build token → provider nodeId map
|
|
2858
|
+
var tokenMap = _buildTokenMap();
|
|
2859
|
+
|
|
2860
|
+
// SVG edges — rendered behind cards (z-index:0) for a clean look
|
|
2861
|
+
nodeList.forEach(function(node) {
|
|
2862
|
+
var tgtInfo = nodeMap[node.id];
|
|
2863
|
+
if (!tgtInfo || !tgtInfo.colEl) return;
|
|
2864
|
+
_getRequires(node).forEach(function(token) {
|
|
2865
|
+
var srcId = tokenMap[token];
|
|
2866
|
+
if (!srcId) return;
|
|
2867
|
+
var srcInfo = nodeMap[srcId];
|
|
2868
|
+
if (!srcInfo || !srcInfo.colEl) return;
|
|
2869
|
+
// Locate gems; fall back to card element if gem not found
|
|
2870
|
+
var srcGem = srcInfo.colEl.querySelector('.lc-token-gem-provides[data-token="' + token + '"]');
|
|
2871
|
+
var tgtGem = tgtInfo.colEl.querySelector('.lc-token-gem-requires[data-token="' + token + '"]');
|
|
2872
|
+
var sx, sy, tx, ty;
|
|
2873
|
+
var innerRect = canvasInner.getBoundingClientRect();
|
|
2874
|
+
if (srcGem) {
|
|
2875
|
+
var srcRect = srcGem.getBoundingClientRect();
|
|
2876
|
+
sx = (srcRect.left + srcRect.width / 2 - innerRect.left) / cvs.zoom;
|
|
2877
|
+
sy = (srcRect.bottom - innerRect.top) / cvs.zoom;
|
|
2878
|
+
} else {
|
|
2879
|
+
var sEl = srcInfo.colEl;
|
|
2880
|
+
sx = sEl.offsetLeft + sEl.offsetWidth / 2;
|
|
2881
|
+
sy = sEl.offsetTop + sEl.offsetHeight;
|
|
2882
|
+
}
|
|
2883
|
+
if (tgtGem) {
|
|
2884
|
+
var tgtRect = tgtGem.getBoundingClientRect();
|
|
2885
|
+
tx = (tgtRect.left + tgtRect.width / 2 - innerRect.left) / cvs.zoom;
|
|
2886
|
+
ty = (tgtRect.top - innerRect.top) / cvs.zoom;
|
|
2887
|
+
} else {
|
|
2888
|
+
var tEl = tgtInfo.colEl;
|
|
2889
|
+
tx = tEl.offsetLeft + tEl.offsetWidth / 2;
|
|
2890
|
+
ty = tEl.offsetTop;
|
|
2891
|
+
}
|
|
2892
|
+
// Route bezier curves around cards — offset control points outward
|
|
2893
|
+
var dx = tx - sx;
|
|
2894
|
+
var dy = ty - sy;
|
|
2895
|
+
var dist = Math.sqrt(dx * dx + dy * dy);
|
|
2896
|
+
var cpLen = Math.max(40, Math.min(dist * 0.4, 120));
|
|
2897
|
+
// Determine if src is roughly above, below, left, or right of target
|
|
2898
|
+
var absDx = Math.abs(dx);
|
|
2899
|
+
var absDy = Math.abs(dy);
|
|
2900
|
+
var cp1x, cp1y, cp2x, cp2y;
|
|
2901
|
+
if (absDy > absDx * 0.4) {
|
|
2902
|
+
// Mostly vertical — curve control points go straight down/up
|
|
2903
|
+
cp1x = sx; cp1y = sy + cpLen;
|
|
2904
|
+
cp2x = tx; cp2y = ty - cpLen;
|
|
2905
|
+
} else {
|
|
2906
|
+
// Mostly horizontal — swing control points outward to avoid overlapping cards
|
|
2907
|
+
var sideSign = dx > 0 ? 1 : -1;
|
|
2908
|
+
cp1x = sx + sideSign * cpLen; cp1y = sy + cpLen * 0.5;
|
|
2909
|
+
cp2x = tx - sideSign * cpLen; cp2y = ty - cpLen * 0.5;
|
|
2910
|
+
}
|
|
2911
|
+
var d = 'M ' + sx + ' ' + sy + ' C ' + cp1x + ' ' + cp1y + ', ' + cp2x + ' ' + cp2y + ', ' + tx + ' ' + ty;
|
|
2912
|
+
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
2913
|
+
path.setAttribute('d', d);
|
|
2914
|
+
path.setAttribute('fill', 'none');
|
|
2915
|
+
path.setAttribute('marker-end', 'url(#lc-arrow)');
|
|
2916
|
+
path.classList.add('lc-edge-path');
|
|
2917
|
+
svgEl.appendChild(path);
|
|
2715
2918
|
});
|
|
2716
2919
|
});
|
|
2717
2920
|
}
|
|
@@ -2736,7 +2939,8 @@ var LiveCard = (function () {
|
|
|
2736
2939
|
const dy = (e.clientY - startY) / cvs.zoom;
|
|
2737
2940
|
el.style.left = (origX + dx) + 'px';
|
|
2738
2941
|
el.style.top = (origY + dy) + 'px';
|
|
2739
|
-
|
|
2942
|
+
if (_edges.length) _repositionEdges();
|
|
2943
|
+
else _drawEdges();
|
|
2740
2944
|
}, { signal });
|
|
2741
2945
|
|
|
2742
2946
|
el.addEventListener('pointerup', () => {
|
|
@@ -2755,15 +2959,81 @@ var LiveCard = (function () {
|
|
|
2755
2959
|
node.card.view.layout.canvas.y = y;
|
|
2756
2960
|
}
|
|
2757
2961
|
engine.notify(node.id);
|
|
2758
|
-
|
|
2962
|
+
_fitCanvasToContent();
|
|
2963
|
+
if (_edges.length) _repositionEdges();
|
|
2964
|
+
else _drawEdges();
|
|
2965
|
+
}, { signal });
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
function _makeResizable(el, node) {
|
|
2969
|
+
const handle = document.createElement('div');
|
|
2970
|
+
handle.className = 'lc-resize-handle';
|
|
2971
|
+
el.appendChild(handle);
|
|
2972
|
+
el.style.overflow = 'visible';
|
|
2973
|
+
|
|
2974
|
+
let resizing = false, startX, startY, origW, origH;
|
|
2975
|
+
|
|
2976
|
+
handle.addEventListener('pointerdown', function(e) {
|
|
2977
|
+
if (e.button !== 0) return;
|
|
2978
|
+
e.stopPropagation();
|
|
2979
|
+
e.preventDefault();
|
|
2980
|
+
resizing = true;
|
|
2981
|
+
el.classList.add('lc-resizing');
|
|
2982
|
+
handle.setPointerCapture(e.pointerId);
|
|
2983
|
+
startX = e.clientX;
|
|
2984
|
+
startY = e.clientY;
|
|
2985
|
+
origW = el.offsetWidth;
|
|
2986
|
+
origH = el.offsetHeight;
|
|
2987
|
+
}, { signal });
|
|
2988
|
+
|
|
2989
|
+
handle.addEventListener('pointermove', function(e) {
|
|
2990
|
+
if (!resizing) return;
|
|
2991
|
+
const dw = (e.clientX - startX) / cvs.zoom;
|
|
2992
|
+
const dh = (e.clientY - startY) / cvs.zoom;
|
|
2993
|
+
const newW = Math.max(cvs.minWidth, origW + dw);
|
|
2994
|
+
const newH = Math.max(80, origH + dh);
|
|
2995
|
+
el.style.width = newW + 'px';
|
|
2996
|
+
el.style.height = newH + 'px';
|
|
2997
|
+
if (_edges.length) _repositionEdges();
|
|
2998
|
+
else _drawEdges();
|
|
2999
|
+
}, { signal });
|
|
3000
|
+
|
|
3001
|
+
handle.addEventListener('pointerup', function() {
|
|
3002
|
+
if (!resizing) return;
|
|
3003
|
+
resizing = false;
|
|
3004
|
+
el.classList.remove('lc-resizing');
|
|
3005
|
+
const w = el.offsetWidth;
|
|
3006
|
+
const h = el.offsetHeight;
|
|
3007
|
+
// Snap to grid
|
|
3008
|
+
const sw = cvs.snap > 1 ? Math.round(w / cvs.snap) * cvs.snap : w;
|
|
3009
|
+
const sh = cvs.snap > 1 ? Math.round(h / cvs.snap) * cvs.snap : h;
|
|
3010
|
+
el.style.width = sw + 'px';
|
|
3011
|
+
el.style.height = sh + 'px';
|
|
3012
|
+
// Persist dimensions
|
|
3013
|
+
_positions[node.id] = Object.assign(_positions[node.id] || {}, { w: sw, h: sh });
|
|
3014
|
+
if (node.card && node.card.view) {
|
|
3015
|
+
if (!node.card.view.layout) node.card.view.layout = {};
|
|
3016
|
+
if (!node.card.view.layout.canvas) node.card.view.layout.canvas = {};
|
|
3017
|
+
node.card.view.layout.canvas.w = sw;
|
|
3018
|
+
node.card.view.layout.canvas.h = sh;
|
|
3019
|
+
}
|
|
3020
|
+
engine.notify(node.id);
|
|
3021
|
+
_fitCanvasToContent();
|
|
3022
|
+
if (_edges.length) _repositionEdges();
|
|
3023
|
+
else _drawEdges();
|
|
2759
3024
|
}, { signal });
|
|
2760
3025
|
}
|
|
2761
3026
|
|
|
2762
3027
|
function _renderCanvas() {
|
|
3028
|
+
_destroyEdges();
|
|
3029
|
+
document.body.style.overflow = 'hidden';
|
|
2763
3030
|
root.innerHTML = '';
|
|
2764
3031
|
root.appendChild(canvasEl);
|
|
3032
|
+
// Fill remaining viewport height
|
|
3033
|
+
var top = canvasEl.getBoundingClientRect().top;
|
|
3034
|
+
canvasEl.style.height = co.height || ('calc(100vh - ' + top + 'px)');
|
|
2765
3035
|
canvasInner.querySelectorAll('.lc-canvas-card,.lc-source-node').forEach(el => el.remove());
|
|
2766
|
-
svgEl.querySelectorAll('line').forEach(
|
|
3036
|
+
svgEl.querySelectorAll('line,path').forEach(function(el) { el.remove(); });
|
|
2767
3037
|
_initPositions();
|
|
2768
3038
|
_applyTransform();
|
|
2769
3039
|
|
|
@@ -2788,6 +3058,7 @@ var LiveCard = (function () {
|
|
|
2788
3058
|
el.style.left = pos.x + 'px';
|
|
2789
3059
|
el.style.top = pos.y + 'px';
|
|
2790
3060
|
if (pos.w) el.style.width = pos.w + 'px';
|
|
3061
|
+
if (pos.h) el.style.height = pos.h + 'px';
|
|
2791
3062
|
|
|
2792
3063
|
const { wrap, body } = _buildCardWrapper(node);
|
|
2793
3064
|
while (wrap.firstChild) el.appendChild(wrap.firstChild);
|
|
@@ -2798,10 +3069,20 @@ var LiveCard = (function () {
|
|
|
2798
3069
|
nodeMap[node.id] = { node, colEl: el, bodyEl: body };
|
|
2799
3070
|
engine.render(node, body, { showChat: false });
|
|
2800
3071
|
_makeDraggable(el, node);
|
|
3072
|
+
_makeResizable(el, node);
|
|
2801
3073
|
}
|
|
2802
3074
|
});
|
|
2803
3075
|
|
|
2804
|
-
|
|
3076
|
+
_updateTokenAvailability();
|
|
3077
|
+
|
|
3078
|
+
// Fit canvas to content then draw edges
|
|
3079
|
+
requestAnimationFrame(function() {
|
|
3080
|
+
_fitCanvasToContent();
|
|
3081
|
+
_drawEdges();
|
|
3082
|
+
});
|
|
3083
|
+
|
|
3084
|
+
// Reposition LeaderLine edges on scroll
|
|
3085
|
+
canvasEl.addEventListener('scroll', function() { _repositionEdges(); }, { signal, passive: true });
|
|
2805
3086
|
|
|
2806
3087
|
// Pan: middle-click or Ctrl+drag on background
|
|
2807
3088
|
let panning = false, panStartX, panStartY, panOrigX, panOrigY;
|
|
@@ -2819,6 +3100,7 @@ var LiveCard = (function () {
|
|
|
2819
3100
|
cvs.panX = panOrigX + (e.clientX - panStartX);
|
|
2820
3101
|
cvs.panY = panOrigY + (e.clientY - panStartY);
|
|
2821
3102
|
_applyTransform();
|
|
3103
|
+
_repositionEdges();
|
|
2822
3104
|
}, { signal });
|
|
2823
3105
|
canvasEl.addEventListener('pointerup', () => { panning = false; }, { signal });
|
|
2824
3106
|
|
|
@@ -2829,6 +3111,7 @@ var LiveCard = (function () {
|
|
|
2829
3111
|
const delta = e.deltaY > 0 ? 0.9 : 1.1;
|
|
2830
3112
|
cvs.zoom = Math.min(cvs.zoomMax, Math.max(cvs.zoomMin, cvs.zoom * delta));
|
|
2831
3113
|
_applyTransform();
|
|
3114
|
+
_repositionEdges();
|
|
2832
3115
|
}, { signal, passive: false });
|
|
2833
3116
|
}
|
|
2834
3117
|
|
|
@@ -2840,11 +3123,12 @@ var LiveCard = (function () {
|
|
|
2840
3123
|
// ---- Auto-layout (topological L → R) ----
|
|
2841
3124
|
|
|
2842
3125
|
function autoLayout() {
|
|
3126
|
+
const tokenMap = _buildTokenMap();
|
|
2843
3127
|
const incoming = {};
|
|
2844
3128
|
const levels = {};
|
|
2845
3129
|
nodeList.forEach(n => { incoming[n.id] = []; levels[n.id] = 0; });
|
|
2846
3130
|
nodeList.forEach(n => {
|
|
2847
|
-
|
|
3131
|
+
_resolveEdgeSources(n, tokenMap).forEach(srcId => {
|
|
2848
3132
|
if (incoming[n.id]) incoming[n.id].push(srcId);
|
|
2849
3133
|
});
|
|
2850
3134
|
});
|
|
@@ -2907,9 +3191,44 @@ var LiveCard = (function () {
|
|
|
2907
3191
|
_render();
|
|
2908
3192
|
}
|
|
2909
3193
|
|
|
2910
|
-
|
|
3194
|
+
/**
|
|
3195
|
+
* Per-node update: replace runtime fields on the existing node object in place
|
|
3196
|
+
* and re-render only that node's body. Outer wrapper is rebuilt to pick up
|
|
3197
|
+
* status/badges, but the surrounding column element is reused so layout is stable.
|
|
3198
|
+
* Editable element state is preserved via journal overlays keyed by nodeId:bindPath.
|
|
3199
|
+
*/
|
|
3200
|
+
function updateNode(id, model) {
|
|
3201
|
+
const entry = nodeMap[id];
|
|
3202
|
+
if (!entry) throw new Error('updateNode: unknown node id ' + id);
|
|
3203
|
+
const node = entry.node;
|
|
3204
|
+
if (model && typeof model === 'object') {
|
|
3205
|
+
if (model.card !== undefined) node.card = model.card;
|
|
3206
|
+
if (model.card_data !== undefined) node.card_data = model.card_data;
|
|
3207
|
+
if (model.requires !== undefined) node.requires = model.requires;
|
|
3208
|
+
if (model.computed_values !== undefined) node.computed_values = model.computed_values;
|
|
3209
|
+
if (model.runtime_state !== undefined) node.runtime_state = model.runtime_state;
|
|
3210
|
+
}
|
|
3211
|
+
engine.destroy(id);
|
|
3212
|
+
if (mode.current === 'board') {
|
|
3213
|
+
const colEl = entry.colEl;
|
|
3214
|
+
colEl.innerHTML = '';
|
|
3215
|
+
const built = _buildCardWrapper(node);
|
|
3216
|
+
colEl.appendChild(built.wrap);
|
|
3217
|
+
nodeMap[id] = { node, colEl, bodyEl: built.body };
|
|
3218
|
+
engine.render(node, built.body, { showChat });
|
|
3219
|
+
} else {
|
|
3220
|
+
const el = entry.colEl;
|
|
3221
|
+
el.innerHTML = '';
|
|
3222
|
+
const built = _buildCardWrapper(node);
|
|
3223
|
+
while (built.wrap.firstChild) el.appendChild(built.wrap.firstChild);
|
|
3224
|
+
nodeMap[id] = { node, colEl: el, bodyEl: built.body };
|
|
3225
|
+
engine.render(node, built.body, { showChat: false });
|
|
3226
|
+
}
|
|
3227
|
+
_updateTokenAvailability();
|
|
3228
|
+
}
|
|
2911
3229
|
|
|
2912
3230
|
function clear() {
|
|
3231
|
+
_destroyEdges();
|
|
2913
3232
|
engine.destroyAll();
|
|
2914
3233
|
nodeList.length = 0;
|
|
2915
3234
|
Object.keys(nodeMap).forEach(k => delete nodeMap[k]);
|
|
@@ -2929,6 +3248,8 @@ var LiveCard = (function () {
|
|
|
2929
3248
|
}
|
|
2930
3249
|
|
|
2931
3250
|
function destroy() {
|
|
3251
|
+
_destroyEdges();
|
|
3252
|
+
document.body.style.overflow = '';
|
|
2932
3253
|
ac.abort();
|
|
2933
3254
|
engine.destroyAll();
|
|
2934
3255
|
nodeList.length = 0;
|
|
@@ -2947,7 +3268,7 @@ var LiveCard = (function () {
|
|
|
2947
3268
|
add,
|
|
2948
3269
|
remove,
|
|
2949
3270
|
reorder,
|
|
2950
|
-
|
|
3271
|
+
updateNode,
|
|
2951
3272
|
clear,
|
|
2952
3273
|
setMode,
|
|
2953
3274
|
setDevMode,
|
|
@@ -2960,9 +3281,120 @@ var LiveCard = (function () {
|
|
|
2960
3281
|
};
|
|
2961
3282
|
}
|
|
2962
3283
|
|
|
3284
|
+
// ===========================================================================
|
|
3285
|
+
// Board — reactive host. State in, view out. No destructive re-renders.
|
|
3286
|
+
// ===========================================================================
|
|
3287
|
+
|
|
3288
|
+
function Board(engine, containerEl, opts) {
|
|
3289
|
+
opts = opts || {};
|
|
3290
|
+
const initialState = opts.initialState;
|
|
3291
|
+
const getNodeIds = opts.getNodeIds;
|
|
3292
|
+
const selectNode = opts.selectNode;
|
|
3293
|
+
if (typeof getNodeIds !== 'function' || typeof selectNode !== 'function') {
|
|
3294
|
+
throw new Error('LiveCard.Board requires getNodeIds and selectNode functions');
|
|
3295
|
+
}
|
|
3296
|
+
|
|
3297
|
+
let state = initialState;
|
|
3298
|
+
const prevModelsById = {};
|
|
3299
|
+
const prevFingerprintsById = {};
|
|
3300
|
+
|
|
3301
|
+
function _stableStringify(v) {
|
|
3302
|
+
if (v == null || typeof v !== 'object') return JSON.stringify(v);
|
|
3303
|
+
if (Array.isArray(v)) return '[' + v.map(_stableStringify).join(',') + ']';
|
|
3304
|
+
const keys = Object.keys(v).sort();
|
|
3305
|
+
return '{' + keys.map(k => JSON.stringify(k) + ':' + _stableStringify(v[k])).join(',') + '}';
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
function _modelFingerprint(model) {
|
|
3309
|
+
if (!model || typeof model !== 'object') return 'null';
|
|
3310
|
+
return _stableStringify({
|
|
3311
|
+
card: model.card,
|
|
3312
|
+
card_data: model.card_data,
|
|
3313
|
+
requires: model.requires,
|
|
3314
|
+
computed_values: model.computed_values,
|
|
3315
|
+
runtime_state: model.runtime_state,
|
|
3316
|
+
});
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3319
|
+
const initialIds = getNodeIds(state);
|
|
3320
|
+
const initialNodes = initialIds.map(id => {
|
|
3321
|
+
const m = selectNode(state, id);
|
|
3322
|
+
prevModelsById[id] = m;
|
|
3323
|
+
prevFingerprintsById[id] = _modelFingerprint(m);
|
|
3324
|
+
return m;
|
|
3325
|
+
});
|
|
3326
|
+
|
|
3327
|
+
const coreOpts = {};
|
|
3328
|
+
Object.keys(opts).forEach(k => {
|
|
3329
|
+
if (k === 'initialState' || k === 'getNodeIds' || k === 'selectNode' || k === 'nodes') return;
|
|
3330
|
+
coreOpts[k] = opts[k];
|
|
3331
|
+
});
|
|
3332
|
+
coreOpts.nodes = initialNodes;
|
|
3333
|
+
|
|
3334
|
+
const core = BoardCore(engine, containerEl, coreOpts);
|
|
3335
|
+
|
|
3336
|
+
function _changed(prevFingerprint, nextFingerprint) {
|
|
3337
|
+
return prevFingerprint !== nextFingerprint;
|
|
3338
|
+
}
|
|
3339
|
+
|
|
3340
|
+
function setState(nextStateOrUpdater) {
|
|
3341
|
+
const nextState = (typeof nextStateOrUpdater === 'function')
|
|
3342
|
+
? nextStateOrUpdater(state)
|
|
3343
|
+
: nextStateOrUpdater;
|
|
3344
|
+
if (nextState === undefined) return;
|
|
3345
|
+
|
|
3346
|
+
state = nextState;
|
|
3347
|
+
const nextIds = getNodeIds(state);
|
|
3348
|
+
const nextSet = new Set(nextIds);
|
|
3349
|
+
|
|
3350
|
+
// Removals
|
|
3351
|
+
Object.keys(prevModelsById).forEach(id => {
|
|
3352
|
+
if (!nextSet.has(id)) {
|
|
3353
|
+
core.remove(id);
|
|
3354
|
+
delete prevModelsById[id];
|
|
3355
|
+
delete prevFingerprintsById[id];
|
|
3356
|
+
}
|
|
3357
|
+
});
|
|
3358
|
+
|
|
3359
|
+
// Additions and per-node updates
|
|
3360
|
+
nextIds.forEach(id => {
|
|
3361
|
+
const next = selectNode(state, id);
|
|
3362
|
+
const prev = prevModelsById[id];
|
|
3363
|
+
const nextFingerprint = _modelFingerprint(next);
|
|
3364
|
+
const prevFingerprint = prevFingerprintsById[id];
|
|
3365
|
+
if (!prev) {
|
|
3366
|
+
core.add(next);
|
|
3367
|
+
} else if (_changed(prevFingerprint, nextFingerprint)) {
|
|
3368
|
+
core.updateNode(id, next);
|
|
3369
|
+
}
|
|
3370
|
+
prevModelsById[id] = next;
|
|
3371
|
+
prevFingerprintsById[id] = nextFingerprint;
|
|
3372
|
+
});
|
|
3373
|
+
|
|
3374
|
+
// Reorder if id sequence differs
|
|
3375
|
+
const currentOrder = core.nodes.map(n => n.id);
|
|
3376
|
+
const orderDiffers = nextIds.length !== currentOrder.length
|
|
3377
|
+
|| nextIds.some((id, i) => id !== currentOrder[i]);
|
|
3378
|
+
if (orderDiffers) core.reorder(nextIds);
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3381
|
+
function destroy() {
|
|
3382
|
+
Object.keys(prevModelsById).forEach(k => delete prevModelsById[k]);
|
|
3383
|
+
Object.keys(prevFingerprintsById).forEach(k => delete prevFingerprintsById[k]);
|
|
3384
|
+
core.destroy();
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
return {
|
|
3388
|
+
setState,
|
|
3389
|
+
destroy,
|
|
3390
|
+
core,
|
|
3391
|
+
get state() { return state; },
|
|
3392
|
+
};
|
|
3393
|
+
}
|
|
3394
|
+
|
|
2963
3395
|
// ===========================================================================
|
|
2964
3396
|
// Module export
|
|
2965
3397
|
// ===========================================================================
|
|
2966
3398
|
|
|
2967
|
-
return { init, Board };
|
|
3399
|
+
return { init, Board, BoardCore };
|
|
2968
3400
|
})();
|