@songsid/agend 2.3.0-beta.12 → 2.3.0-beta.13
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/README.md +6 -0
- package/README.zh-TW.md +6 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.js +13 -4
- package/dist/config.js.map +1 -1
- package/dist/fleet-manager.d.ts +36 -1
- package/dist/fleet-manager.js +128 -30
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/fleet-config/SKILL.md +2 -0
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +20 -3
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +23 -5
- package/dist/outbound-schemas.d.ts +14 -1
- package/dist/outbound-schemas.js +8 -2
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +4 -0
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/restart-progress.d.ts +16 -1
- package/dist/restart-progress.js +74 -15
- package/dist/restart-progress.js.map +1 -1
- package/dist/settings-api.js +4 -1
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-confirmation.d.ts +3 -1
- package/dist/settings-confirmation.js +2 -0
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-effect.js +2 -1
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.d.ts +11 -0
- package/dist/settings-http-confirmation.js +10 -0
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/ui/chat-effects.js +10 -3
- package/dist/ui/chat-strings.js +2 -0
- package/dist/ui/effects-waker.js +70 -0
- package/dist/ui/fleet-canvas-layout.js +10 -4
- package/dist/ui/fleet-canvas.js +32 -2
- package/dist/ui/fleet-route-scene.js +207 -0
- package/dist/ui/fleet-route-worker.js +21 -0
- package/dist/ui/fleet-route.js +88 -0
- package/dist/ui/fleet-traffic.js +27 -11
- package/dist/ui/fleet-usage.js +35 -1
- package/dist/ui/panel-chat.js +43 -23
- package/dist/ui/panel-fleet.js +3 -3
- package/dist/ui/settings-appearance.js +2 -1
- package/dist/ui/settings-apply.js +3 -3
- package/dist/ui/settings-confirm.js +7 -2
- package/dist/ui/settings-owner-effects.js +6 -4
- package/dist/ui/shared/app-i18n.js +4 -0
- package/dist/ui/shared/app-prefs.js +12 -0
- package/dist/ui/shared/app-shell.js +38 -2
- package/dist/ui/shared/app-themes.js +116 -1
- package/dist/ui/shared/app.css +47 -3
- package/dist/ui/shared/card-strings.js +4 -0
- package/dist/ui/shared/identity-card.js +24 -6
- package/dist/ui/shared/identity-color.js +11 -7
- package/dist/ui/shared/ui-icons.js +2 -0
- package/dist/ui/shared/ui-menu.js +9 -3
- package/dist/ui/shared/web-authorize.js +47 -0
- package/dist/web-api.js +18 -1
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance.js +3 -1
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-effect.d.ts +1 -1
- package/dist/web-host-guard.d.ts +11 -0
- package/dist/web-host-guard.js +11 -0
- package/dist/web-host-guard.js.map +1 -1
- package/dist/web-libavoid.d.ts +14 -0
- package/dist/web-libavoid.js +46 -0
- package/dist/web-libavoid.js.map +1 -0
- package/dist/web-owner-css-store.d.ts +3 -2
- package/dist/web-owner-css-store.js +9 -4
- package/dist/web-owner-css-store.js.map +1 -1
- package/dist/web-prefs.js +13 -0
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-settings-tools.d.ts +5 -0
- package/dist/web-settings-tools.js +34 -6
- package/dist/web-settings-tools.js.map +1 -1
- package/dist/web-theme.d.ts +29 -0
- package/dist/web-theme.js +89 -1
- package/dist/web-theme.js.map +1 -1
- package/dist/yaml-shared-nodes.d.ts +25 -0
- package/dist/yaml-shared-nodes.js +185 -0
- package/dist/yaml-shared-nodes.js.map +1 -0
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings-http-confirmation.js","sourceRoot":"","sources":["../src/settings-http-confirmation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,IAAI,QAAQ,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AACrH,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAA8B,MAAM,sBAAsB,CAAC;AAC7G,OAAO,EAA6B,yBAAyB,EAA4B,MAAM,4BAA4B,CAAC;AAC5H,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACnF,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"settings-http-confirmation.js","sourceRoot":"","sources":["../src/settings-http-confirmation.ts"],"names":[],"mappings":"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"}
|
package/dist/ui/chat-effects.js
CHANGED
|
@@ -9,7 +9,7 @@ import { navigate } from "/assets/app-nav.js";
|
|
|
9
9
|
import { toast } from "/assets/ui-toast.js";
|
|
10
10
|
import { setThemesShown, themesShown, themesStore } from "/assets/app-themes.js";
|
|
11
11
|
import { setFollowsReducedMotion } from "/assets/fx-motion.js";
|
|
12
|
-
import { getAppEffects } from "./
|
|
12
|
+
import { getAppEffects, wakeEffects, effectsLoaded, onEffectsAwake } from "./effects-waker.js";
|
|
13
13
|
|
|
14
14
|
register("chatFx", {
|
|
15
15
|
en: { stock: "Off: the page layout could not be verified ({0}). Reload and report this code if it persists.", transport: "Off: the isolated effect frame could not start. Reload to try again.", enableHere: "Turn effects on here", playMotion: "Play effects anyway", leaveSafe: "Leave safe mode", viewerShow: "Show themes, effects and widgets here", clock: "Off: the page timing clock is unavailable. Reload to try again.", gatesUnknown: "Off: the playback conditions could not be verified.", resource: "Off: this effect could not be loaded or verified. Reload to try again.", frontQueue: "Waiting for a foreground slot.", consent: "This page has older playback information. Reload to use the isolated effect player.", previewMissing: "Off: the isolated effect frame is unavailable. Reload to try again.", unsupported: "Off: this browser lacks required Canvas Worker or layout support.", calibration: "Starting the effect.", baseline: "This page has older playback information. Reload to try again.", safetyUnknown: "Stopped: this device's safety history could not be verified. Reload to check it.", rawReason: "Playback blocked: {0}", missingRow: "Playback state is unavailable (runtime_unavailable).", region: "Off: this effect does not declare the chat region.", blocked: "Placed effects are blocked for this viewer:", deviceSetting: "Open device preferences", webSetting: "Open Web settings", reloadHint: "Reload page",
|
|
@@ -78,8 +78,13 @@ function ReasonAction({ reason, onClose = () => {} }) {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
const NO_PLAYBACK = () => {};
|
|
81
|
-
export function ChatEffects({ name, lease, element, store, open, onClose, onPlayback = NO_PLAYBACK }) {
|
|
81
|
+
export function ChatEffects({ name, lease, element, store, open, onClose, onPlayback = NO_PLAYBACK, active = true }) {
|
|
82
82
|
const cssLife = useStore(ownerCssLifecycle);
|
|
83
|
+
// #2193: `active` — this chat has placements (its status row says so). While the runtime sleeps, a chat with none
|
|
84
|
+
// attaches and reads nothing (the dialog says "No effects in this chat"); one with some wakes it and attaches once it
|
|
85
|
+
// runs. Once it runs (woken by anything — a Try card needs its chat attached), every chat attaches as before.
|
|
86
|
+
const [awake, setAwake] = useState(() => getAppEffects() !== null || effectsLoaded());
|
|
87
|
+
useEffect(() => (awake ? undefined : onEffectsAwake(() => setAwake(true))), [awake]);
|
|
83
88
|
useStore(themesStore); // Viewer-local off/on is visible immediately, independent of runtime callbacks.
|
|
84
89
|
// Which effects were running just before this dialog opened ("was running" is said in the past, never shown as
|
|
85
90
|
// current). The ⋯ menu that opens it is a barrier too, so "just before" is within RECENT_MS of the observed stop.
|
|
@@ -100,6 +105,8 @@ export function ChatEffects({ name, lease, element, store, open, onClose, onPlay
|
|
|
100
105
|
lease.hold(cleanup);
|
|
101
106
|
try {
|
|
102
107
|
setBusy(false); onClose(); setCanWrite(false); setState(null);
|
|
108
|
+
if (!active && !awake) { setState({ error: null, rows: [], effects: [], loading: false }); return cleanup; }
|
|
109
|
+
if (!awake) { void wakeEffects(); return cleanup; } // "Checking effects…" until it runs
|
|
103
110
|
const host = window, owner = getAppEffects(host);
|
|
104
111
|
const current = () => live && lease.current();
|
|
105
112
|
if (!owner) { setState({ error: null, rows: [], effects: [], unavailable: true, reason: typeof host.OffscreenCanvas !== "function" || typeof host.Worker !== "function" ? "unsupported_host" : "runtime_unavailable" }); return cleanup; }
|
|
@@ -132,7 +139,7 @@ export function ChatEffects({ name, lease, element, store, open, onClose, onPlay
|
|
|
132
139
|
if (lease.current()) setState({ error: "runtime_unavailable", rows: [], effects: [] });
|
|
133
140
|
return cleanup;
|
|
134
141
|
}
|
|
135
|
-
}, [name, lease]);
|
|
142
|
+
}, [name, lease, active, awake]);
|
|
136
143
|
const rows = state?.rows ?? [], effects = state?.effects ?? [];
|
|
137
144
|
if (open && !wasOpen.current) {
|
|
138
145
|
const now = performance.now();
|
package/dist/ui/chat-strings.js
CHANGED
|
@@ -122,6 +122,7 @@ register("chat", {
|
|
|
122
122
|
dNoActivity: "No activity in the last hour.", dWorking: "Working…",
|
|
123
123
|
// #1629: a person's control over an agent's own web theme.
|
|
124
124
|
themeLock: "Lock theme", themeUnlock: "Unlock theme", themeReset: "Reset theme…", themeResetGo: "Reset",
|
|
125
|
+
pageColours: "Page colours", pageAgentOn: "The agent's choice (on)", pageAgentOff: "The agent's choice (off)", pageWhole: "On the whole page", pageChatOnly: "Only its chat",
|
|
125
126
|
themeResetConfirm: "Reset {0}'s theme to the default? Its colours, picture and working label go; a lock stays.",
|
|
126
127
|
themeResetDone: "{0}'s theme was reset.", themeLocked: "{0}'s theme is locked: the agent can't change it.", themeUnlocked: "{0}'s theme is unlocked.",
|
|
127
128
|
themeFailed: "The theme was not changed ({0}).",
|
|
@@ -252,6 +253,7 @@ register("chat", {
|
|
|
252
253
|
dEditInSettings: "到設定編輯", dPause: "暫停", dWake: "喚醒", dPaused: "{0} 已暫停", dWoken: "{0} 正在喚醒", dActionFailed: "沒有成功,請再試一次。",
|
|
253
254
|
dNoActivity: "最近一小時沒有活動。", dWorking: "處理中…",
|
|
254
255
|
themeLock: "鎖定主題", themeUnlock: "解除主題鎖定", themeReset: "重設主題…", themeResetGo: "重設",
|
|
256
|
+
pageColours: "頁面配色", pageAgentOn: "依 agent 的設定(開)", pageAgentOff: "依 agent 的設定(關)", pageWhole: "整個頁面", pageChatOnly: "只在它的聊天",
|
|
255
257
|
themeResetConfirm: "要把 {0} 的主題重設為預設嗎?顏色、圖片和處理中文字都會移除;鎖定會保留。",
|
|
256
258
|
themeResetDone: "已重設 {0} 的主題。", themeLocked: "已鎖定 {0} 的主題:agent 無法變更。", themeUnlocked: "已解除 {0} 的主題鎖定。",
|
|
257
259
|
themeFailed: "主題沒有變更({0})。",
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// #2193: the web effects runtime stays unloaded until there is something it could play for this viewer. Before that,
|
|
2
|
+
// this module is all there is: a few store subscriptions comparing fields the page already has (no observer, listener,
|
|
3
|
+
// timer, layer or request). The runtime itself (app-effects.js and everything it imports) is the reviewed code,
|
|
4
|
+
// unchanged; it is imported and started here, once, the first time one of these holds:
|
|
5
|
+
// - the open chat has Canvas Worker placements: its status row's effect_chat_v (the hash of its placements, taken
|
|
6
|
+
// before the server's region filtering — so it can only wake for nothing, never miss something) is not the hash of
|
|
7
|
+
// an empty list;
|
|
8
|
+
// - the signed-in person has a personal effect slot chosen (fx.page/header/sidebar/box/cursor — app-prefs);
|
|
9
|
+
// - an agent's "Try" offer is shown (wakeEffects() from the chat), or Settings saved a personal effect.
|
|
10
|
+
// Once started it stays for the page: the runtime is one episode per window (a disposed one cannot start again).
|
|
11
|
+
// Owner CSS is not the runtime's: owner-css.js is installed by the app shell and works with the runtime never loaded.
|
|
12
|
+
import { appStore } from "/assets/app-store.js";
|
|
13
|
+
import { navStore } from "/assets/app-nav.js";
|
|
14
|
+
import { ownerEffectsChosen, onOwnerEffectsChanged } from "/assets/app-prefs.js";
|
|
15
|
+
|
|
16
|
+
/** effect_chat_v of a chat with no placements: sha256(JSON.stringify([])), first 32 hex (fleet-manager's status row). */
|
|
17
|
+
export const EMPTY_CHAT_EFFECTS = "4f53cda18c2baa0c0354bb5f9a3ecbe5";
|
|
18
|
+
|
|
19
|
+
let mod = null, waking = null, chatStore = null, started = false;
|
|
20
|
+
const awakeListeners = new Set();
|
|
21
|
+
|
|
22
|
+
/** Whether `name`'s chat has Canvas Worker placements (as the status rows say). */
|
|
23
|
+
export function chatHasEffects(name, app = appStore.get()) {
|
|
24
|
+
const v = name ? app.instances?.find(i => i.name === name)?.effect_chat_v : undefined;
|
|
25
|
+
return typeof v === "string" && /^[a-f0-9]{32}$/.test(v) && v !== EMPTY_CHAT_EFFECTS;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The running effects owner, or null while dormant (or when it could not start: as getAppEffects always was). */
|
|
29
|
+
export function getAppEffects(host) { return mod ? mod.getAppEffects(host) : null; }
|
|
30
|
+
|
|
31
|
+
/** Whether the runtime has been asked to start on this page. */
|
|
32
|
+
export function effectsAwake() { return waking !== null; }
|
|
33
|
+
/** Whether its module has loaded (started, or found it could not: getAppEffects() then says which). */
|
|
34
|
+
export function effectsLoaded() { return mod !== null; }
|
|
35
|
+
|
|
36
|
+
/** Called once the runtime has started (or immediately when it already has). Returns the unsubscribe. */
|
|
37
|
+
export function onEffectsAwake(fn) {
|
|
38
|
+
if (typeof fn !== "function") return () => {};
|
|
39
|
+
if (mod) { try { fn(); } catch { /* a listener's error is its own */ } return () => {}; }
|
|
40
|
+
awakeListeners.add(fn); return () => awakeListeners.delete(fn);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Load and start the runtime (once per page). Resolves to the owner, or null when it could not start. */
|
|
44
|
+
export function wakeEffects() {
|
|
45
|
+
if (waking) return waking;
|
|
46
|
+
waking = import("./app-effects.js").then(m => {
|
|
47
|
+
mod = m;
|
|
48
|
+
const owner = chatStore ? m.startAppEffects({ store: chatStore }) : null;
|
|
49
|
+
for (const fn of [...awakeListeners]) { try { fn(); } catch { /* the rest still hear */ } }
|
|
50
|
+
awakeListeners.clear();
|
|
51
|
+
return owner;
|
|
52
|
+
}, () => null);
|
|
53
|
+
return waking;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Wake when the open chat has placements or the person has a personal effect. */
|
|
57
|
+
function check() {
|
|
58
|
+
if (waking) return;
|
|
59
|
+
const route = navStore.get().route;
|
|
60
|
+
if ((route?.panel === "chat" && chatHasEffects(route.instance)) || ownerEffectsChosen()) void wakeEffects();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Boot (once per page, with the Chat store the runtime binds to). Starts nothing but the watch. */
|
|
64
|
+
export function startEffectsWaker({ store }) {
|
|
65
|
+
chatStore = store;
|
|
66
|
+
if (started) return;
|
|
67
|
+
started = true;
|
|
68
|
+
appStore.subscribe(check); navStore.subscribe(check); onOwnerEffectsChanged(check);
|
|
69
|
+
check();
|
|
70
|
+
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
// #1918: the org canvas's layout (fleet-canvas.js draws it; fleet-traffic.js routes lines through its empty lanes).
|
|
2
2
|
// Fixed slots, no layout engine: the same structure always gives the same picture.
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Sizes in canvas units. NW×NH a member card, LW×LH a General card; FOLDED a folded (or empty) team's height. GAP and
|
|
6
|
+
* PAD (#2182): the gutters between cards and a box's padding are the traffic lines' channels inside a box — wide
|
|
7
|
+
* enough that the router (fleet-route-scene.js) keeps a few lines apart in each; a phone's narrow packing keeps the
|
|
8
|
+
* tighter GAP_NARROW and PAD_NARROW, so General and the first team still open at a readable size. A layout says which
|
|
9
|
+
* it used (L.gap, L.pad).
|
|
10
|
+
*/
|
|
11
|
+
export const OC = { NW: 184, NH: 56, GAP: 28, PAD: 32, GAP_NARROW: 18, PAD_NARROW: 24, HEAD: 34, TEAM_GAP: 72, ROW_GAP: 96, ROW_MAX: 1400, ROW_MAX_NARROW: 460, LW: 232, LH: 68, TOP_Y: 20, BUS: 44, FOLDED: 64, TRUNK: -30 };
|
|
6
12
|
const colsFor = (n, narrow) => (n <= 1 ? 1 : n <= 4 || narrow ? 2 : 3);
|
|
7
13
|
/** The key a team folds by (per device): its name; the instances in no team have one of their own no team name can be. */
|
|
8
14
|
export const REST_KEY = "\u0000rest";
|
|
@@ -13,7 +19,7 @@ export const REST_KEY = "\u0000rest";
|
|
|
13
19
|
* size of the whole. `narrow`: a phone's packing.
|
|
14
20
|
*/
|
|
15
21
|
export function canvasLayout(view, folded = new Set(), { narrow = false, label = (s, n) => `${n} ${s}` } = {}) {
|
|
16
|
-
const { NW, NH, GAP, PAD
|
|
22
|
+
const { NW, NH, HEAD } = OC, GAP = narrow ? OC.GAP_NARROW : OC.GAP, PAD = narrow ? OC.PAD_NARROW : OC.PAD;
|
|
17
23
|
const groups = [...view.teams.map((tm) => ({ key: tm.name, name: tm.name, loose: false, nodes: tm.nodes, counts: tm.counts })),
|
|
18
24
|
...(view.rest.nodes.length ? [{ key: REST_KEY, name: null, loose: true, nodes: view.rest.nodes, counts: view.rest.counts }] : [])];
|
|
19
25
|
const rows = [];
|
|
@@ -49,7 +55,7 @@ export function canvasLayout(view, folded = new Set(), { narrow = false, label =
|
|
|
49
55
|
const width = Math.max(rowsW, topW);
|
|
50
56
|
const tops = view.top.map((node, i) => ({ node, key: `\u0000top/${node.name}`, x: width / 2 - topW / 2 + i * (OC.LW + GAP), y: OC.TOP_Y, w: OC.LW, h: OC.LH }));
|
|
51
57
|
const height = boxes.length ? y - OC.ROW_GAP : tops.length ? OC.TOP_Y + OC.LH : 0;
|
|
52
|
-
return { tops, boxes, width, height, tree: treeLines(tops, boxes) };
|
|
58
|
+
return { tops, boxes, width, height, tree: treeLines(tops, boxes), gap: GAP, pad: PAD };
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
/** A folded team's summary line height, and a label's width estimate (canvas units: wide for CJK, narrow otherwise). */
|
package/dist/ui/fleet-canvas.js
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
// change). The live state is the app store's (fleet-org.js buildOrgView), so the canvas moves with the stream.
|
|
16
16
|
// - Live traffic (fleet-traffic.js): the messages between instances, drawn between the team boxes and the cards (never
|
|
17
17
|
// hidden under a box, never across a name). Live on or off per device; the list says it in words ("Sending: …").
|
|
18
|
+
// #2182: routed around the cards and through the boxes' doors by libavoid in a worker (fleet-route-scene.js), each
|
|
19
|
+
// line from card to card; the lanes are the fallback.
|
|
18
20
|
import { html, useEffect, useLayoutEffect, useMemo, useRef, useState } from "/assets/app-html.js";
|
|
19
21
|
import { appStore } from "/assets/app-store.js";
|
|
20
22
|
import { t, register } from "/assets/app-i18n.js";
|
|
@@ -27,6 +29,8 @@ import { identityClasses, identityOf, paintIdentity, useIdentityColors } from "/
|
|
|
27
29
|
import { buildOrgView, OrgChart, OrgSummary } from "./fleet-org.js";
|
|
28
30
|
import { OC, REST_KEY, SUM_LINE, canvasLayout, openingRegion, summaryRows } from "./fleet-canvas-layout.js";
|
|
29
31
|
import { createTrafficStore, FAR_K, POLL_MS, sendingBy, trafficEdges } from "./fleet-traffic.js";
|
|
32
|
+
import { routeScene } from "./fleet-route-scene.js";
|
|
33
|
+
import { createRouteClient } from "./fleet-route.js";
|
|
30
34
|
|
|
31
35
|
register("orgc", {
|
|
32
36
|
en: {
|
|
@@ -201,7 +205,7 @@ function TrafficLayer({ edges, q }) {
|
|
|
201
205
|
const still = reducedMotion();
|
|
202
206
|
return html`<g class="oc-flows" aria-hidden="true">${edges.map((e) => {
|
|
203
207
|
const dim = q && !e.names.some((n) => n.toLowerCase().includes(q));
|
|
204
|
-
return html`<g key=${e.key} class=${`oc-fl${dim ? " dim" : ""}`}>
|
|
208
|
+
return html`<g key=${e.key} class=${`oc-fl${dim ? " dim" : ""}`} data-routed=${e.routed ? "1" : undefined}>
|
|
205
209
|
<path class="oc-flow-halo" d=${e.d} />
|
|
206
210
|
<path class=${`oc-flow h-${e.hue} ph-${e.phase}`} d=${e.d} marker-start=${`url(#oc-src-${MARKER(e)})`} marker-end=${`url(#oc-ah-${MARKER(e)})`} />
|
|
207
211
|
${e.phase === "live" && !still ? html`<circle class=${`oc-pulse h-${e.hue}`} r="4"><animateMotion dur=${`${Math.max(1.6, e.pts.length * 0.3)}s`} repeatCount="indefinite" path=${e.d} /></circle>` : null}
|
|
@@ -269,6 +273,32 @@ function useTraffic(lease, on) {
|
|
|
269
273
|
return useMemo(() => (on ? store.items() : []), [rev, on]);
|
|
270
274
|
}
|
|
271
275
|
|
|
276
|
+
/**
|
|
277
|
+
* #2182: the traffic lines, routed around the canvas by libavoid in a worker (fleet-route.js) once it answers; until
|
|
278
|
+
* then, and zoomed out (one line per team pair), along the lanes (fleet-traffic.js route()). An answer is kept while the
|
|
279
|
+
* layout it was routed on is the one drawn: a line it has not seen yet takes its lane until the next answer.
|
|
280
|
+
*/
|
|
281
|
+
let routeClient = null;
|
|
282
|
+
function useTrafficRoutes(traffic, L, far) {
|
|
283
|
+
const lanes = useMemo(() => (traffic ? trafficEdges(traffic, L, { far }) : []), [traffic, L, far]);
|
|
284
|
+
const scene = useMemo(() => (far || !lanes.length ? null : routeScene(L, lanes)), [lanes, L, far]);
|
|
285
|
+
const [answer, setAnswer] = useState(null); // { shapes, map }
|
|
286
|
+
// The layout as the router sees it: a status frame re-making the same layout keeps the answer (and asks nothing).
|
|
287
|
+
const shapes = scene ? JSON.stringify(scene.shapes) : null;
|
|
288
|
+
const ask = scene && scene.conns.length ? JSON.stringify(scene) : null;
|
|
289
|
+
useEffect(() => {
|
|
290
|
+
if (!ask) return undefined;
|
|
291
|
+
routeClient ??= createRouteClient();
|
|
292
|
+
if (!routeClient.available) return undefined;
|
|
293
|
+
let live = true;
|
|
294
|
+
routeClient.route(JSON.parse(ask)).then((map) => { if (live && map) setAnswer({ shapes, map }); });
|
|
295
|
+
return () => { live = false; };
|
|
296
|
+
}, [ask]);
|
|
297
|
+
useEffect(() => () => { routeClient?.close(); routeClient = null; }, []);
|
|
298
|
+
return useMemo(() => (answer && answer.shapes === shapes && traffic ? trafficEdges(traffic, L, { far, routed: answer.map }) : lanes),
|
|
299
|
+
[lanes, answer, shapes, L, far, traffic]);
|
|
300
|
+
}
|
|
301
|
+
|
|
272
302
|
/** The canvas body: `view` buildOrgView's answer; `folded` the folded teams' keys; `q` the search needle; `traffic` the live items (null: Live is off). */
|
|
273
303
|
function Canvas({ view, folded, onFold, q, jump, traffic }) {
|
|
274
304
|
const [narrow] = useState(() => typeof matchMedia === "function" && matchMedia("(max-width: 640px)").matches);
|
|
@@ -308,7 +338,7 @@ function Canvas({ view, folded, onFold, q, jump, traffic }) {
|
|
|
308
338
|
vp.current = narrow ? fitView(openingRegion(L), size(), { margin: 12, minK: 0.75 }) : fitView(whole(), size(), { widthOnly: true, maxK: 1, minK: 0.6 });
|
|
309
339
|
setFar(vp.current.k < FAR_K);
|
|
310
340
|
}, []);
|
|
311
|
-
const edges =
|
|
341
|
+
const edges = useTrafficRoutes(traffic, L, far);
|
|
312
342
|
// Preact owns the markup, not the transform: put it back after every render.
|
|
313
343
|
useLayoutEffect(apply);
|
|
314
344
|
// A search's Enter: the first match, unfolded (by the panel) and then brought into view — once. It waits for the
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// #2182: the org canvas's traffic lines routed around the canvas, not along fixed lanes. libavoid (Adaptagrams, through
|
|
2
|
+
// libavoid-js 0.4.5, LGPL-2.1-or-later, an npm dependency served unmodified from /ui/vendor/) routes each line between
|
|
3
|
+
// fixed obstacles; this module says what the obstacles are (pure: the worker, the page and the tests share it).
|
|
4
|
+
//
|
|
5
|
+
// - Obstacles: every card (General's and the members'), a folded team's box, each open box's header band (its name and
|
|
6
|
+
// count are drawn over the lines) and its border as wall pieces in front of its cards. The gaps between the pieces —
|
|
7
|
+
// the box's padding and the gutters between its rows and columns (L.pad, L.gap: wide enough for a few lines each)
|
|
8
|
+
// — are the only doors, so a line crosses a border at right angles, through a door, and never runs along it.
|
|
9
|
+
// - Ends: pins on each card's sides (several on the long sides, one on the short), facing out; a line ends on a card,
|
|
10
|
+
// never on a box. A card with more lines than pins gets more, so two lines never start at one point.
|
|
11
|
+
// - Separation: libavoid nudges lines that share a run ROUTE.NUDGE apart; the same two ends and kind are one line (×N)
|
|
12
|
+
// before they get here (fleet-traffic.js).
|
|
13
|
+
// A route that comes back otherwise (not orthogonal, not ending on its two shapes) is not drawn: that line keeps the
|
|
14
|
+
// lane route (fleet-traffic.js route()).
|
|
15
|
+
import { OC } from "./fleet-canvas-layout.js";
|
|
16
|
+
|
|
17
|
+
/** Buffer around every obstacle, the distance nudging keeps between lines, the cost of a bend, pins on a long side. */
|
|
18
|
+
export const ROUTE = { BUF: 5, NUDGE: 8, SEG: 60, PINS: 4, MAX_PINS: 64, MAX_POINTS: 64, MAX_SHAPES: 600, MAX_CONNS: 120 };
|
|
19
|
+
/** libavoid's ConnDirFlags: the way a line leaves a pin. */
|
|
20
|
+
const UP = 1, DOWN = 2, LEFT = 4, RIGHT = 8;
|
|
21
|
+
const PINS_PER_CARD = ROUTE.PINS * 2 + 2;
|
|
22
|
+
|
|
23
|
+
/** The shape a line's end is on: a card (General's or a member's) or a folded team's box. Null: not routed here. */
|
|
24
|
+
function endShape(end) {
|
|
25
|
+
if (!end) return null;
|
|
26
|
+
if (end.kind === "top" || end.kind === "cell") return { key: end.key, x: end.c.x, y: end.c.y, w: end.c.w, h: end.c.h };
|
|
27
|
+
if (end.kind === "box" && end.b.folded) return { key: end.key, x: end.b.x, y: end.b.y, w: end.b.w, h: end.b.h };
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The scene for layout `L` and the lines `lines` ([{ key, a, z }], a and z ends as fleet-traffic.js endOf gives them):
|
|
33
|
+
* { shapes: [{ x, y, w, h, pins }], conns: [{ key, a, z }], boxes: [{ x, y, w, h }] } — `pins` 0 none (an obstacle
|
|
34
|
+
* only), 1 the usual pins, more: that many on each long side (a busy card); every pin takes one line; `a`/`z` index
|
|
35
|
+
* shapes; `boxes` the open teams' outlines (separate() keeps lines off them). A line whose end is not a card or a folded box is left out (it keeps its lane).
|
|
36
|
+
*/
|
|
37
|
+
export function routeScene(L, lines) {
|
|
38
|
+
const shapes = [], boxes = [], at = new Map();
|
|
39
|
+
const add = (s, pins) => { shapes.push({ x: s.x, y: s.y, w: s.w, h: s.h, pins }); return shapes.length - 1; };
|
|
40
|
+
for (const c of L.tops) at.set(c.key, add(c, 1));
|
|
41
|
+
const { NW, NH, HEAD } = OC, PAD = L.pad ?? OC.PAD, GAP = L.gap ?? OC.GAP;
|
|
42
|
+
for (const b of L.boxes) {
|
|
43
|
+
if (b.folded) { at.set(`box:${b.key}`, add(b, 1)); continue; }
|
|
44
|
+
if (!b.cells.length) { add(b, 0); continue; }
|
|
45
|
+
boxes.push({ x: b.x, y: b.y, w: b.w, h: b.h });
|
|
46
|
+
for (const c of b.cells) at.set(c.key, add(c, 1));
|
|
47
|
+
add({ x: b.x - 1, y: b.y - 1, w: b.w + 2, h: HEAD + 1 }, 0);
|
|
48
|
+
const lines = Math.ceil(b.cells.length / b.cols);
|
|
49
|
+
for (let col = 0; col < b.cols; col++) add({ x: b.x + PAD + col * (NW + GAP), y: b.y + b.h - 1, w: NW, h: 2 }, 0);
|
|
50
|
+
for (const x of [b.x - 1, b.x + b.w - 1]) for (let row = 0; row < lines; row++) add({ x, y: b.y + HEAD + PAD + row * (NH + GAP), w: 2, h: NH }, 0);
|
|
51
|
+
}
|
|
52
|
+
const conns = [], demand = new Map();
|
|
53
|
+
for (const ln of lines) {
|
|
54
|
+
const a = endShape(ln.a), z = endShape(ln.z);
|
|
55
|
+
if (!a || !z || !at.has(a.key) || !at.has(z.key) || a.key === z.key) continue;
|
|
56
|
+
const ia = at.get(a.key), iz = at.get(z.key);
|
|
57
|
+
conns.push({ key: ln.key, a: ia, z: iz });
|
|
58
|
+
for (const i of [ia, iz]) demand.set(i, (demand.get(i) || 0) + 1);
|
|
59
|
+
}
|
|
60
|
+
// A line each: a shape with more lines than pins gets more pins on its long sides (two lines never leave one point:
|
|
61
|
+
// libavoid cannot nudge apart runs that start at the same point).
|
|
62
|
+
for (const [i, n] of demand) if (n > PINS_PER_CARD) shapes[i].pins = Math.min(ROUTE.MAX_PINS, Math.ceil((n - 2) / 2));
|
|
63
|
+
return { shapes, conns, boxes };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** A scene from anywhere else (the worker's message): only numbers in range, only indexes it has. Null: refused. */
|
|
67
|
+
export function sceneOk(s) {
|
|
68
|
+
if (!s || !Array.isArray(s.shapes) || !Array.isArray(s.conns) || !Array.isArray(s.boxes) || s.shapes.length > ROUTE.MAX_SHAPES || s.conns.length > ROUTE.MAX_CONNS
|
|
69
|
+
|| s.boxes.length > ROUTE.MAX_SHAPES) return null;
|
|
70
|
+
const num = (v) => typeof v === "number" && Number.isFinite(v) && Math.abs(v) < 1e6;
|
|
71
|
+
for (const b of s.boxes) if (!b || ![b.x, b.y, b.w, b.h].every(num) || b.w <= 0 || b.h <= 0) return null;
|
|
72
|
+
for (const sh of s.shapes) if (!sh || ![sh.x, sh.y, sh.w, sh.h].every(num) || sh.w <= 0 || sh.h <= 0 || !Number.isInteger(sh.pins) || sh.pins < 0 || sh.pins > ROUTE.MAX_PINS) return null;
|
|
73
|
+
for (const c of s.conns) {
|
|
74
|
+
if (!c || typeof c.key !== "string" || c.key.length > 2048) return null;
|
|
75
|
+
for (const i of [c.a, c.z]) if (!Number.isInteger(i) || i < 0 || i >= s.shapes.length || !s.shapes[i].pins) return null;
|
|
76
|
+
}
|
|
77
|
+
return s;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const onEdge = (p, s) => (Math.abs(p.x - s.x) < 0.5 || Math.abs(p.x - s.x - s.w) < 0.5) && p.y >= s.y - 0.5 && p.y <= s.y + s.h + 0.5
|
|
81
|
+
|| (Math.abs(p.y - s.y) < 0.5 || Math.abs(p.y - s.y - s.h) < 0.5) && p.x >= s.x - 0.5 && p.x <= s.x + s.w + 0.5;
|
|
82
|
+
/**
|
|
83
|
+
* A route for connection `c` of `scene`, or null: at most MAX_POINTS finite points, every segment horizontal or
|
|
84
|
+
* vertical, starting on its first shape's edge and ending on its second's.
|
|
85
|
+
*/
|
|
86
|
+
export function routeOk(pts, scene, c) {
|
|
87
|
+
if (!Array.isArray(pts) || pts.length < 2 || pts.length > ROUTE.MAX_POINTS) return null;
|
|
88
|
+
for (const p of pts) if (!p || !Number.isFinite(p.x) || !Number.isFinite(p.y)) return null;
|
|
89
|
+
for (let i = 1; i < pts.length; i++) if (pts[i].x !== pts[i - 1].x && pts[i].y !== pts[i - 1].y) return null;
|
|
90
|
+
if (!onEdge(pts[0], scene.shapes[c.a]) || !onEdge(pts[pts.length - 1], scene.shapes[c.z])) return null;
|
|
91
|
+
return pts.map((p) => ({ x: p.x, y: p.y }));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The worker's answer to a route request `m`: its scene checked (sceneOk) before libavoid sees any of it. */
|
|
95
|
+
export function answerRoute(A, m) {
|
|
96
|
+
const scene = sceneOk(m && m.scene);
|
|
97
|
+
return { type: "routes", id: m.id, routes: scene ? [...routeWith(A, scene)] : null };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* libavoid nudges lines that share a run apart, but not always: in a channel shared by many lines it packs them closer
|
|
102
|
+
* than ROUTE.NUDGE, and lines fanning out of one card can come back on the very same run. This pass moves the middle
|
|
103
|
+
* segments of such lines (never a line's first or last, which end on a pin) sideways to the nearest place at least
|
|
104
|
+
* SEPARATE from every parallel line along them — only where the moved segment and the two it joins stay clear of
|
|
105
|
+
* every shape's buffer and keep their direction. What cannot move stays as libavoid left it.
|
|
106
|
+
*/
|
|
107
|
+
export const SEPARATE = 6;
|
|
108
|
+
export function separate(routes, scene) {
|
|
109
|
+
const B = ROUTE.BUF - 0.5, OVER = 8;
|
|
110
|
+
const segsOf = (r, p) => p.slice(1).map((q, i) => ({ r, i, h: p[i].y === q.y }));
|
|
111
|
+
const coord = (p, s) => (s.h ? p[s.i].y : p[s.i].x);
|
|
112
|
+
const span = (p, s) => (s.h ? [Math.min(p[s.i].x, p[s.i + 1].x), Math.max(p[s.i].x, p[s.i + 1].x)] : [Math.min(p[s.i].y, p[s.i + 1].y), Math.max(p[s.i].y, p[s.i + 1].y)]);
|
|
113
|
+
/**
|
|
114
|
+
* A segment from a to b clear of every shape's buffer (but `own`, the card a line's first or last run starts on at its
|
|
115
|
+
* pin), and not along a box's border (through a door, say).
|
|
116
|
+
*/
|
|
117
|
+
const clear = (a, b, own = -1) => {
|
|
118
|
+
const x0 = Math.min(a.x, b.x), x1 = Math.max(a.x, b.x), y0 = Math.min(a.y, b.y), y1 = Math.max(a.y, b.y), h = y0 === y1;
|
|
119
|
+
return scene.shapes.every((sh, k) => k === own || x1 <= sh.x - B || x0 >= sh.x + sh.w + B || y1 <= sh.y - B || y0 >= sh.y + sh.h + B)
|
|
120
|
+
&& (scene.boxes ?? []).every((bx) => (h ? [bx.y, bx.y + bx.h].every((v) => Math.abs(v - y0) >= SEPARATE + 1) || Math.min(x1, bx.x + bx.w) - Math.max(x0, bx.x) <= 12
|
|
121
|
+
: [bx.x, bx.x + bx.w].every((v) => Math.abs(v - x0) >= SEPARATE + 1) || Math.min(y1, bx.y + bx.h) - Math.max(y0, bx.y) <= 12));
|
|
122
|
+
};
|
|
123
|
+
const keys = [...routes.keys()];
|
|
124
|
+
const connOf = new Map(scene.conns.map((c) => [c.key, c]));
|
|
125
|
+
const pts = keys.map((k) => routes.get(k).map((p) => ({ x: p.x, y: p.y })));
|
|
126
|
+
/** The segments of other lines (and this one's) parallel to segment s of line r, overlapping it, closer than SEPARATE. */
|
|
127
|
+
const near = (r, s, c, lo, hi) => {
|
|
128
|
+
const out = [];
|
|
129
|
+
pts.forEach((p, r2) => segsOf(r2, p).forEach((t) => {
|
|
130
|
+
if (t.h !== s.h || (r2 === r && t.i === s.i)) return;
|
|
131
|
+
const [l2, h2] = span(p, t);
|
|
132
|
+
if (Math.min(hi, h2) - Math.max(lo, l2) > OVER) out.push(coord(p, t));
|
|
133
|
+
}));
|
|
134
|
+
return out.filter((c2) => Math.abs(c2 - c) < SEPARATE);
|
|
135
|
+
};
|
|
136
|
+
for (let pass = 0; pass < 8; pass++) {
|
|
137
|
+
let moved = false;
|
|
138
|
+
pts.forEach((p, r) => segsOf(r, p).forEach((s) => {
|
|
139
|
+
if (s.i === 0 || s.i + 1 >= p.length - 1) return; // a first or last segment ends on a pin
|
|
140
|
+
const c = coord(p, s), [lo, hi] = span(p, s);
|
|
141
|
+
const close = near(r, s, c, lo, hi);
|
|
142
|
+
if (!close.length) return;
|
|
143
|
+
// Try the nearest places SEPARATE from the lines it is close to, both ways.
|
|
144
|
+
const tries = [...new Set(close.flatMap((c2) => [1, 2, 3, 4, 5, 6].flatMap((m) => [c2 + m * SEPARATE, c2 - m * SEPARATE])))].sort((u, v) => Math.abs(u - c) - Math.abs(v - c));
|
|
145
|
+
for (const c2 of tries) {
|
|
146
|
+
const a = { ...p[s.i] }, b = { ...p[s.i + 1] };
|
|
147
|
+
if (s.h) { a.y = c2; b.y = c2; } else { a.x = c2; b.x = c2; }
|
|
148
|
+
const prev = p[s.i - 1], next = p[s.i + 2];
|
|
149
|
+
// The joining segments keep their direction (never shrink through zero).
|
|
150
|
+
const dir = (u, v) => Math.sign(s.h ? v.y - u.y : v.x - u.x);
|
|
151
|
+
if (dir(prev, a) !== dir(prev, p[s.i]) || dir(b, next) !== dir(p[s.i + 1], next) || dir(prev, a) === 0 || dir(b, next) === 0) continue;
|
|
152
|
+
const c0 = connOf.get(keys[r]);
|
|
153
|
+
if (!clear(a, b) || !clear(prev, a, s.i - 1 === 0 && c0 ? c0.a : -1) || !clear(b, next, s.i + 2 === p.length - 1 && c0 ? c0.z : -1)) continue;
|
|
154
|
+
const old = [p[s.i], p[s.i + 1]];
|
|
155
|
+
p[s.i] = a; p[s.i + 1] = b;
|
|
156
|
+
const ok = !near(r, s, c2, lo, hi).length && [s.i - 1, s.i + 1].every((j) => { const t = { r, i: j, h: !s.h }; const [l3, h3] = span(p, t); return !near(r, t, coord(p, t), l3, h3).length; });
|
|
157
|
+
if (ok) { moved = true; break; }
|
|
158
|
+
[p[s.i], p[s.i + 1]] = old;
|
|
159
|
+
}
|
|
160
|
+
}));
|
|
161
|
+
if (!moved) break;
|
|
162
|
+
}
|
|
163
|
+
return new Map(keys.map((k, i) => [k, pts[i]]));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Route `scene` with libavoid instance `A` (libavoid-js 0.4.5's: its constants are numbers on `A`). A Map of
|
|
168
|
+
* connection key → points, for the routes that pass routeOk only.
|
|
169
|
+
*/
|
|
170
|
+
export function routeWith(A, scene) {
|
|
171
|
+
const r = new A.Router(A.OrthogonalRouting);
|
|
172
|
+
try {
|
|
173
|
+
r.setRoutingParameter(A.shapeBufferDistance, ROUTE.BUF);
|
|
174
|
+
r.setRoutingParameter(A.idealNudgingDistance, ROUTE.NUDGE);
|
|
175
|
+
r.setRoutingParameter(A.segmentPenalty, ROUTE.SEG);
|
|
176
|
+
r.setRoutingOption(A.nudgeOrthogonalSegmentsConnectedToShapes, true);
|
|
177
|
+
r.setRoutingOption(A.nudgeSharedPathsWithCommonEndPoint, true);
|
|
178
|
+
for (const [k, v] of Object.entries(ROUTE.EXTRA_OPT ?? {})) r.setRoutingOption(A[k], v);
|
|
179
|
+
for (const [k, v] of Object.entries(ROUTE.EXTRA_PARAM ?? {})) r.setRoutingParameter(A[k], v);
|
|
180
|
+
// The router owns its shapes, pins and connectors (A.destroy(r) frees them); the points, rectangles and ends it
|
|
181
|
+
// copies are freed here, or the wasm heap grows with every route.
|
|
182
|
+
const temp = (o) => { made.push(o); return o; }, made = [];
|
|
183
|
+
const refs = scene.shapes.map((s) => {
|
|
184
|
+
const ref = new A.ShapeRef(r, temp(new A.Rectangle(temp(new A.Point(s.x, s.y)), temp(new A.Point(s.x + s.w, s.y + s.h)))));
|
|
185
|
+
if (s.pins) {
|
|
186
|
+
const k = s.pins === 1 ? ROUTE.PINS : s.pins;
|
|
187
|
+
const pin = (x, y, dir) => new A.ShapeConnectionPin(ref, 1, x, y, true, 0, dir).setExclusive(true);
|
|
188
|
+
for (let i = 1; i <= k; i++) { pin(i / (k + 1), 0, UP); pin(i / (k + 1), 1, DOWN); }
|
|
189
|
+
pin(0, 0.5, LEFT); pin(1, 0.5, RIGHT);
|
|
190
|
+
}
|
|
191
|
+
return ref;
|
|
192
|
+
});
|
|
193
|
+
const conns = scene.conns.map((c) => new A.ConnRef(r, temp(new A.ConnEnd(refs[c.a], 1)), temp(new A.ConnEnd(refs[c.z], 1))));
|
|
194
|
+
for (const o of made.splice(0)) A.destroy(o);
|
|
195
|
+
r.processTransaction();
|
|
196
|
+
const out = new Map();
|
|
197
|
+
scene.conns.forEach((c, i) => {
|
|
198
|
+
const line = conns[i].displayRoute(), pts = [];
|
|
199
|
+
for (let k = 0; k < line.size() && k <= ROUTE.MAX_POINTS; k++) { const p = line.get_ps(k); pts.push({ x: p.x, y: p.y }); }
|
|
200
|
+
const ok = routeOk(pts, scene, c);
|
|
201
|
+
if (ok) out.set(c.key, ok);
|
|
202
|
+
});
|
|
203
|
+
return ROUTE.SEPARATE === false ? out : separate(out, scene);
|
|
204
|
+
} finally {
|
|
205
|
+
A.destroy(r);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// #2182: the worker that routes the org canvas's traffic lines (fleet-route-scene.js says what to route). It is served
|
|
2
|
+
// with a policy of its own (web-host-guard.ts ROUTE_WORKER_CSP): WebAssembly may run here, as it may not on the page,
|
|
3
|
+
// and it may reach nothing — the page fetches libavoid.wasm and hands the bytes over; they are instantiated from a
|
|
4
|
+
// blob: URL. It receives numbers and answers numbers; the page checks both.
|
|
5
|
+
import { AvoidLib } from "/ui/vendor/libavoid-0.4.5/libavoid.js";
|
|
6
|
+
import { answerRoute } from "./fleet-route-scene.js";
|
|
7
|
+
|
|
8
|
+
let ready = null;
|
|
9
|
+
self.onmessage = (ev) => {
|
|
10
|
+
const m = ev.data;
|
|
11
|
+
if (!m || typeof m !== "object") return;
|
|
12
|
+
if (m.type === "init" && !ready && m.wasm instanceof ArrayBuffer) {
|
|
13
|
+
const url = URL.createObjectURL(new Blob([m.wasm], { type: "application/wasm" }));
|
|
14
|
+
ready = AvoidLib.load(url).then(() => AvoidLib.getInstance()).finally(() => URL.revokeObjectURL(url));
|
|
15
|
+
ready.then(() => self.postMessage({ type: "ready" }), () => self.postMessage({ type: "failed" }));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (m.type === "route" && ready && Number.isInteger(m.id)) {
|
|
19
|
+
ready.then((A) => self.postMessage(answerRoute(A, m))).catch(() => self.postMessage({ type: "routes", id: m.id, routes: null }));
|
|
20
|
+
}
|
|
21
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// #2182: the page's side of the traffic router — one worker (fleet-route-worker.js), started the first time Live
|
|
2
|
+
// traffic is drawn, fed the libavoid.wasm bytes the page fetched. One scene at a time: a newer one replaces any still
|
|
3
|
+
// waiting (the older one's answer would be stale anyway). A worker that fails, or takes longer than TIMEOUT_MS, is
|
|
4
|
+
// ended; after FAIL_LIMIT failures in a row this page stops asking, and every line keeps its lane route (the caller's
|
|
5
|
+
// fallback for a null answer).
|
|
6
|
+
import { routeOk } from "./fleet-route-scene.js";
|
|
7
|
+
|
|
8
|
+
export const WORKER_URL = "/ui/js/fleet-route-worker.js";
|
|
9
|
+
export const WASM_URL = "/ui/vendor/libavoid-0.4.5/libavoid.wasm";
|
|
10
|
+
export const TIMEOUT_MS = 4000;
|
|
11
|
+
const FAIL_LIMIT = 2;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A client: route(scene) → a Promise of a Map (connection key → points, checked against the scene) or null (not
|
|
15
|
+
* routed: superseded, failed, unavailable). `deps` replaces Worker, fetch and the clock in tests.
|
|
16
|
+
*/
|
|
17
|
+
export function createRouteClient(deps = {}) {
|
|
18
|
+
const W = deps.Worker ?? (typeof Worker === "function" ? Worker : null);
|
|
19
|
+
const get = deps.fetch ?? ((...a) => fetch(...a));
|
|
20
|
+
const timers = deps.timers ?? { set: (f, ms) => setTimeout(f, ms), clear: (t) => clearTimeout(t) };
|
|
21
|
+
let worker = null, wasm = null, fails = 0, nextId = 1;
|
|
22
|
+
let busy = null, waiting = null; // { id, scene, resolve, timer }
|
|
23
|
+
const end = () => { try { worker?.terminate(); } catch { /* gone */ } worker = null; };
|
|
24
|
+
const fail = () => {
|
|
25
|
+
fails++;
|
|
26
|
+
end();
|
|
27
|
+
if (busy) { timers.clear(busy.timer); busy.resolve(null); busy = null; }
|
|
28
|
+
if (fails >= FAIL_LIMIT && waiting) { waiting.resolve(null); waiting = null; }
|
|
29
|
+
pump();
|
|
30
|
+
};
|
|
31
|
+
async function start(job) {
|
|
32
|
+
if (!wasm) {
|
|
33
|
+
const res = await get(WASM_URL, { credentials: "same-origin" });
|
|
34
|
+
if (!res.ok) throw new Error(`libavoid.wasm: ${res.status}`);
|
|
35
|
+
wasm = await res.arrayBuffer();
|
|
36
|
+
}
|
|
37
|
+
// #2194 review: closed, timed out or replaced while the .wasm was on its way — start nothing (a worker made now
|
|
38
|
+
// would be nobody's, and a later start would make another).
|
|
39
|
+
if (busy !== job || worker) return;
|
|
40
|
+
const w = new W(WORKER_URL, { type: "module" });
|
|
41
|
+
w.onmessage = (ev) => {
|
|
42
|
+
const m = ev.data;
|
|
43
|
+
if (!m || typeof m !== "object" || w !== worker) return;
|
|
44
|
+
if (m.type === "failed") { fail(); return; }
|
|
45
|
+
if (m.type !== "routes" || !busy || m.id !== busy.id) return;
|
|
46
|
+
const { scene, resolve, timer } = busy;
|
|
47
|
+
busy = null;
|
|
48
|
+
timers.clear(timer);
|
|
49
|
+
if (!Array.isArray(m.routes)) { fail(); resolve(null); return; }
|
|
50
|
+
fails = 0;
|
|
51
|
+
const out = new Map();
|
|
52
|
+
for (const pair of m.routes.slice(0, scene.conns.length)) {
|
|
53
|
+
if (!Array.isArray(pair) || typeof pair[0] !== "string") continue;
|
|
54
|
+
const c = scene.conns.find((x) => x.key === pair[0]);
|
|
55
|
+
const pts = c && routeOk(pair[1], scene, c);
|
|
56
|
+
if (pts) out.set(c.key, pts);
|
|
57
|
+
}
|
|
58
|
+
resolve(out);
|
|
59
|
+
pump();
|
|
60
|
+
};
|
|
61
|
+
w.onerror = (e) => { e?.preventDefault?.(); if (w === worker) fail(); };
|
|
62
|
+
worker = w;
|
|
63
|
+
w.postMessage({ type: "init", wasm: wasm.slice(0) });
|
|
64
|
+
}
|
|
65
|
+
function pump() {
|
|
66
|
+
if (busy || !waiting) return;
|
|
67
|
+
if (fails >= FAIL_LIMIT || !W) { waiting.resolve(null); waiting = null; return; }
|
|
68
|
+
busy = waiting; waiting = null;
|
|
69
|
+
const job = busy;
|
|
70
|
+
job.timer = timers.set(() => { if (busy === job) fail(); }, TIMEOUT_MS);
|
|
71
|
+
(worker ? Promise.resolve() : start(job)).then(() => {
|
|
72
|
+
if (busy === job) worker.postMessage({ type: "route", id: job.id, scene: job.scene });
|
|
73
|
+
}, () => { if (busy === job) fail(); });
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
route(scene) {
|
|
77
|
+
if (waiting) waiting.resolve(null);
|
|
78
|
+
return new Promise((resolve) => { waiting = { id: nextId++, scene, resolve, timer: null }; pump(); });
|
|
79
|
+
},
|
|
80
|
+
/** Off: the worker ended; what was waiting gets null. */
|
|
81
|
+
close() {
|
|
82
|
+
if (waiting) { waiting.resolve(null); waiting = null; }
|
|
83
|
+
if (busy) { timers.clear(busy.timer); busy.resolve(null); busy = null; }
|
|
84
|
+
end();
|
|
85
|
+
},
|
|
86
|
+
get available() { return !!W && fails < FAIL_LIMIT; },
|
|
87
|
+
};
|
|
88
|
+
}
|