@songsid/agend 2.3.1 → 2.3.2-beta.10
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/THIRD_PARTY_NOTICES.md +40 -0
- package/dist/agent-cli.js +21 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +5 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/antigravity-usage-scan.d.ts +58 -0
- package/dist/antigravity-usage-scan.js +257 -0
- package/dist/antigravity-usage-scan.js.map +1 -0
- package/dist/antigravity-usage-worker.d.ts +1 -0
- package/dist/antigravity-usage-worker.js +10 -0
- package/dist/antigravity-usage-worker.js.map +1 -0
- package/dist/auth-api.js +4 -0
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/authority-state.d.ts +4 -0
- package/dist/authority/authority-state.js +16 -10
- package/dist/authority/authority-state.js.map +1 -1
- package/dist/authority/session-authorizations.d.ts +7 -1
- package/dist/authority/session-authorizations.js +6 -4
- package/dist/authority/session-authorizations.js.map +1 -1
- package/dist/authority/web-authority.d.ts +19 -6
- package/dist/authority/web-authority.js +22 -8
- package/dist/authority/web-authority.js.map +1 -1
- package/dist/backend/kiro-identity.d.ts +64 -0
- package/dist/backend/kiro-identity.js +101 -0
- package/dist/backend/kiro-identity.js.map +1 -1
- package/dist/backend/kiro-retired-model.d.ts +62 -0
- package/dist/backend/kiro-retired-model.js +160 -0
- package/dist/backend/kiro-retired-model.js.map +1 -0
- package/dist/backend/kiro.d.ts +71 -2
- package/dist/backend/kiro.js +352 -11
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/types.d.ts +59 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-signin-guide.d.ts +1 -0
- package/dist/backend-signin-guide.js +9 -0
- package/dist/backend-signin-guide.js.map +1 -0
- package/dist/backend-signin-pending.d.ts +10 -0
- package/dist/backend-signin-pending.js +44 -0
- package/dist/backend-signin-pending.js.map +1 -0
- package/dist/browser-backend-signins.d.ts +56 -0
- package/dist/browser-backend-signins.js +189 -0
- package/dist/browser-backend-signins.js.map +1 -0
- package/dist/cache-ledger.d.ts +38 -2
- package/dist/cache-ledger.js +133 -8
- package/dist/cache-ledger.js.map +1 -1
- package/dist/cache-prices.d.ts +2 -1
- package/dist/cache-prices.js +5 -2
- package/dist/cache-prices.js.map +1 -1
- package/dist/cache-service.d.ts +68 -0
- package/dist/cache-service.js +355 -13
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +13 -0
- package/dist/channel/adapters/discord.js +103 -16
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/mcp-tools.js +34 -7
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/tool-router.js +2 -1
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/channel/types.d.ts +11 -0
- package/dist/classic-channel-manager.d.ts +11 -0
- package/dist/classic-channel-manager.js +39 -1
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +21 -2
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.d.ts +7 -1
- package/dist/config-validator.js +92 -12
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +19 -0
- package/dist/config.js +31 -1
- package/dist/config.js.map +1 -1
- package/dist/cpu-quota.d.ts +23 -0
- package/dist/cpu-quota.js +86 -0
- package/dist/cpu-quota.js.map +1 -0
- package/dist/daemon.d.ts +57 -3
- package/dist/daemon.js +330 -50
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard-permissions.d.ts +20 -6
- package/dist/dashboard-permissions.js +58 -9
- package/dist/dashboard-permissions.js.map +1 -1
- package/dist/delivery-outbox.d.ts +12 -1
- package/dist/delivery-outbox.js +67 -15
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/doctor.d.ts +3 -0
- package/dist/doctor.js +11 -1
- package/dist/doctor.js.map +1 -1
- package/dist/event-log.d.ts +5 -0
- package/dist/event-log.js +13 -0
- package/dist/event-log.js.map +1 -1
- package/dist/fleet-manager.d.ts +212 -0
- package/dist/fleet-manager.js +914 -111
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-process-handlers.d.ts +1 -0
- package/dist/fleet-process-handlers.js +9 -1
- package/dist/fleet-process-handlers.js.map +1 -1
- package/dist/forged-envelope-scan.d.ts +9 -0
- package/dist/forged-envelope-scan.js +30 -5
- package/dist/forged-envelope-scan.js.map +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +5 -2
- package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +14 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +28 -5
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +31 -8
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +138 -59
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +145 -61
- package/dist/guest-seen-people.d.ts +38 -0
- package/dist/guest-seen-people.js +78 -0
- package/dist/guest-seen-people.js.map +1 -0
- package/dist/instance-config-impact.js +14 -6
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +20 -0
- package/dist/instance-lifecycle.js +71 -13
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/kiro-db-reader.d.ts +5 -0
- package/dist/kiro-db-reader.js +18 -0
- package/dist/kiro-db-reader.js.map +1 -1
- package/dist/kiro-engine-status.d.ts +2 -0
- package/dist/kiro-engine-status.js +9 -0
- package/dist/kiro-engine-status.js.map +1 -1
- package/dist/kiro-migrate.d.ts +49 -0
- package/dist/kiro-migrate.js +105 -0
- package/dist/kiro-migrate.js.map +1 -0
- package/dist/kiro-transcript-lane.d.ts +2 -0
- package/dist/kiro-transcript-lane.js.map +1 -1
- package/dist/kiro-transcript-worker.js +1 -0
- package/dist/kiro-transcript-worker.js.map +1 -1
- package/dist/kiro-tui-forensic.d.ts +7 -0
- package/dist/kiro-tui-forensic.js +110 -0
- package/dist/kiro-tui-forensic.js.map +1 -0
- package/dist/kiro-usage-run.d.ts +7 -0
- package/dist/kiro-usage-run.js +39 -0
- package/dist/kiro-usage-run.js.map +1 -0
- package/dist/kiro-usage-scan.d.ts +49 -0
- package/dist/kiro-usage-scan.js +223 -0
- package/dist/kiro-usage-scan.js.map +1 -0
- package/dist/kiro-usage-worker.d.ts +1 -0
- package/dist/kiro-usage-worker.js +10 -0
- package/dist/kiro-usage-worker.js.map +1 -0
- package/dist/kiro-usage.d.ts +58 -0
- package/dist/kiro-usage.js +310 -0
- package/dist/kiro-usage.js.map +1 -0
- package/dist/locale.js +120 -12
- package/dist/locale.js.map +1 -1
- package/dist/log-service.js +1 -1
- package/dist/log-service.js.map +1 -1
- package/dist/login-url.d.ts +4 -0
- package/dist/login-url.js +22 -0
- package/dist/login-url.js.map +1 -0
- package/dist/mac-auto-restore.d.ts +117 -0
- package/dist/mac-auto-restore.js +294 -0
- package/dist/mac-auto-restore.js.map +1 -0
- package/dist/needs-you-hub.d.ts +2 -1
- package/dist/needs-you-hub.js +1 -1
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you.d.ts +8 -2
- package/dist/needs-you.js +6 -0
- package/dist/needs-you.js.map +1 -1
- package/dist/notice-delivery.d.ts +16 -0
- package/dist/notice-delivery.js +35 -0
- package/dist/notice-delivery.js.map +1 -0
- package/dist/office-share-page.d.ts +16 -0
- package/dist/office-share-page.js +85 -0
- package/dist/office-share-page.js.map +1 -0
- package/dist/office-share-service.d.ts +91 -0
- package/dist/office-share-service.js +176 -0
- package/dist/office-share-service.js.map +1 -0
- package/dist/office-share.d.ts +258 -0
- package/dist/office-share.js +549 -0
- package/dist/office-share.js.map +1 -0
- package/dist/opencode-usage-scan.d.ts +28 -0
- package/dist/opencode-usage-scan.js +92 -0
- package/dist/opencode-usage-scan.js.map +1 -0
- package/dist/opencode-usage-worker.d.ts +1 -0
- package/dist/opencode-usage-worker.js +10 -0
- package/dist/opencode-usage-worker.js.map +1 -0
- package/dist/outbound-handlers.js +22 -2
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +28 -2
- package/dist/outbound-schemas.js +29 -2
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +16 -2
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/quickstart-api.js +13 -2
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.js +2 -1
- package/dist/quickstart.js.map +1 -1
- package/dist/remote-card-answer.d.ts +83 -0
- package/dist/remote-card-answer.js +208 -0
- package/dist/remote-card-answer.js.map +1 -0
- package/dist/reply-triggers.d.ts +2 -0
- package/dist/reply-triggers.js +3 -0
- package/dist/reply-triggers.js.map +1 -0
- package/dist/settings-api.d.ts +28 -0
- package/dist/settings-api.js +148 -17
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +54 -17
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +12 -3
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-instance-patch.js +4 -1
- package/dist/settings-instance-patch.js.map +1 -1
- package/dist/setup-form.d.ts +1 -1
- package/dist/setup-form.js +55 -6
- package/dist/setup-form.js.map +1 -1
- package/dist/setup-host.d.ts +9 -0
- package/dist/setup-host.js +127 -6
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-signin-assets.d.ts +2 -0
- package/dist/setup-signin-assets.js +6 -0
- package/dist/setup-signin-assets.js.map +1 -0
- package/dist/setup-wizard.js +2 -0
- package/dist/setup-wizard.js.map +1 -1
- package/dist/start-attempt.d.ts +9 -5
- package/dist/start-attempt.js +27 -8
- package/dist/start-attempt.js.map +1 -1
- package/dist/start-failure-chat.d.ts +4 -0
- package/dist/start-failure-chat.js +23 -6
- package/dist/start-failure-chat.js.map +1 -1
- package/dist/start-failure-notice.d.ts +4 -0
- package/dist/start-failure-notice.js +8 -0
- package/dist/start-failure-notice.js.map +1 -1
- package/dist/start-failure.d.ts +25 -0
- package/dist/start-failure.js +75 -2
- package/dist/start-failure.js.map +1 -1
- package/dist/structured-signin-transport.d.ts +8 -0
- package/dist/structured-signin-transport.js +79 -0
- package/dist/structured-signin-transport.js.map +1 -0
- package/dist/structured-signin.d.ts +131 -0
- package/dist/structured-signin.js +388 -0
- package/dist/structured-signin.js.map +1 -0
- package/dist/tool-permissions.js +12 -0
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.js +6 -2
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-sources.d.ts +26 -2
- package/dist/transcript-sources.js +103 -40
- package/dist/transcript-sources.js.map +1 -1
- package/dist/turn-reply-guard.d.ts +25 -0
- package/dist/turn-reply-guard.js +111 -12
- package/dist/turn-reply-guard.js.map +1 -1
- package/dist/types.d.ts +46 -9
- package/dist/ui/backend-signin.js +68 -12
- package/dist/ui/chat-ambient.js +1 -1
- package/dist/ui/chat-effects.js +4 -4
- package/dist/ui/chat-render.js +5 -2
- package/dist/ui/chat-strings.js +4 -4
- package/dist/ui/chat-thread.js +2 -2
- package/dist/ui/first-run.js +2 -2
- package/dist/ui/fleet-canvas-view.js +114 -0
- package/dist/ui/fleet-canvas.js +15 -77
- package/dist/ui/fleet-office-editor.js +94 -14
- package/dist/ui/fleet-office-layout.js +235 -0
- package/dist/ui/fleet-office-model.js +12 -6
- package/dist/ui/fleet-office-overview.js +167 -0
- package/dist/ui/fleet-office-pack.d.ts +44 -0
- package/dist/ui/fleet-office-pack.js +352 -37
- package/dist/ui/fleet-office-skin.js +220 -36
- package/dist/ui/fleet-office-store.js +112 -7
- package/dist/ui/fleet-office-view.js +155 -0
- package/dist/ui/fleet-office-zones.js +264 -42
- package/dist/ui/fleet-office.js +957 -160
- package/dist/ui/fleet-usage.js +26 -21
- package/dist/ui/office-share-dialog.js +145 -0
- package/dist/ui/office-share-page.js +164 -0
- package/dist/ui/panel-chat.js +9 -2
- package/dist/ui/panel-details.js +2 -2
- package/dist/ui/panel-fleet.js +11 -77
- package/dist/ui/panel-log.js +4 -4
- package/dist/ui/panel-needs.js +4 -4
- package/dist/ui/panel-settings.js +608 -167
- package/dist/ui/search-dialog.js +26 -7
- package/dist/ui/settings-appearance.js +82 -29
- package/dist/ui/settings-apply.js +42 -7
- package/dist/ui/settings-dialogs.js +76 -23
- package/dist/ui/settings-draft.js +51 -0
- package/dist/ui/settings-guests.js +255 -79
- package/dist/ui/settings-learn.js +2 -2
- package/dist/ui/settings-model.js +16 -6
- package/dist/ui/settings-registry.js +146 -0
- package/dist/ui/settings-strings.js +116 -44
- package/dist/ui/settings-web-authority.js +34 -23
- package/dist/ui/shared/app-i18n.js +18 -2
- package/dist/ui/shared/app-nav.js +10 -5
- package/dist/ui/shared/app-prefs.js +80 -2
- package/dist/ui/shared/app-route.js +39 -5
- package/dist/ui/shared/app-shell.js +73 -10
- package/dist/ui/shared/app-stream.js +3 -1
- package/dist/ui/shared/app.css +307 -17
- package/dist/ui/shared/app.js +3 -0
- package/dist/ui/shared/effect-stock-trust.js +1 -1
- package/dist/ui/shared/identity-color.js +4 -4
- package/dist/ui/shared/office/cozy.png +0 -0
- package/dist/ui/shared/side-panel.js +95 -0
- package/dist/ui/shared/signin.js +2 -2
- package/dist/ui/shared/ui-dialog.js +6 -1
- package/dist/ui/shared/ui-icons.js +2 -0
- package/dist/ui/shared/web-authorize.js +13 -12
- package/dist/ui/structured-signin.js +115 -0
- package/dist/usage-report.d.ts +20 -0
- package/dist/usage-report.js +54 -17
- package/dist/usage-report.js.map +1 -1
- package/dist/usage-rollup.d.ts +9 -1
- package/dist/usage-rollup.js +20 -4
- package/dist/usage-rollup.js.map +1 -1
- package/dist/web-api.d.ts +26 -2
- package/dist/web-api.js +361 -5
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.js +8 -2
- package/dist/web-auth.js.map +1 -1
- package/dist/web-backend-login.d.ts +1 -7
- package/dist/web-backend-login.js +2 -24
- package/dist/web-backend-login.js.map +1 -1
- package/dist/web-experimental.d.ts +1 -1
- package/dist/web-experimental.js +1 -1
- package/dist/web-experimental.js.map +1 -1
- package/dist/web-mod-agent.js +2 -2
- package/dist/web-mod-agent.js.map +1 -1
- package/dist/web-office-acts.d.ts +173 -0
- package/dist/web-office-acts.js +285 -0
- package/dist/web-office-acts.js.map +1 -0
- package/dist/web-office-skins.d.ts +109 -0
- package/dist/web-office-skins.js +299 -0
- package/dist/web-office-skins.js.map +1 -0
- package/dist/web-office-store.d.ts +8 -2
- package/dist/web-office-store.js +77 -14
- package/dist/web-office-store.js.map +1 -1
- package/dist/web-settings-patch.d.ts +8 -1
- package/dist/web-settings-patch.js +65 -5
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-shell-routes.d.ts +4 -2
- package/dist/web-shell-routes.js +17 -7
- package/dist/web-shell-routes.js.map +1 -1
- package/dist/working-recovery.d.ts +104 -0
- package/dist/working-recovery.js +298 -0
- package/dist/working-recovery.js.map +1 -0
- package/dist/working-state-journal.d.ts +5 -1
- package/dist/working-state-journal.js +31 -3
- package/dist/working-state-journal.js.map +1 -1
- package/package.json +3 -2
- package/templates/windows-shortcuts.ps1 +155 -140
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// #2540: the Office view's canvas controls — the pure part (fleet-office.js applies it). The office fills the Fleet
|
|
2
|
+
// panel; its scene (the zones' 1280×720 logical canvas, or the desks layout) is panned and zoomed like the org chart's
|
|
3
|
+
// canvas (fleet-canvas-view.js: the same view { k, x, y }, gestures, wheel and keys), with these of its own:
|
|
4
|
+
//
|
|
5
|
+
// - Pixel levels for the steps. + and − step to the next pixel level (one logical unit a whole number of device px:
|
|
6
|
+
// k·dpr = 1, 2, 3, …; below one device px a unit by 1.25); the canvas always draws unsmoothed (nearest neighbour).
|
|
7
|
+
// The wheel and a pinch zoom continuously, and Fit takes whatever scale fills the panel.
|
|
8
|
+
// - Fit: two modes, kept per office (fleet-office-store.js). "contain" (the default): the largest scale at which the
|
|
9
|
+
// whole scene shows — non-integer, so the scene fills the panel's width or height. "cover" (Fill): the smallest
|
|
10
|
+
// scale that fills both; what overflows is cropped and stays a pan away. Either is centred.
|
|
11
|
+
// - The view never loses the scene (clampPan): along an axis where the scene is larger than the panel it covers the
|
|
12
|
+
// panel (no gap at an edge); where it is smaller it is centred on that axis (the user: the canvas's middle is the
|
|
13
|
+
// scene's), on opening, Fit, any zoom and a resize alike.
|
|
14
|
+
//
|
|
15
|
+
// Around the scene is a matte: the pack's `matte` (fleet-office-pack.js), else the colour of the background's edge
|
|
16
|
+
// pixels (sampled once per style: edgeMatte), else the skin's own (Studio's hall floor; Desks': the dashboard's
|
|
17
|
+
// background). It paints the whole canvas outside the scene (and the stage behind it), never the page's grey.
|
|
18
|
+
import { clampK, pinchTo, zoomTo } from "./fleet-canvas-view.js";
|
|
19
|
+
import { CANVAS_MAX } from "./fleet-office-model.js";
|
|
20
|
+
|
|
21
|
+
/** The zoom's range, in CSS px per logical unit. */
|
|
22
|
+
export const OK_MIN = 0.1, OK_MAX = 8;
|
|
23
|
+
const EPS = 1e-6;
|
|
24
|
+
const clampO = (k) => clampK(k, OK_MIN, OK_MAX);
|
|
25
|
+
/** The highest pixel level within the range at this ratio (at least 1). */
|
|
26
|
+
const topLevel = (dpr) => Math.max(1, Math.floor(OK_MAX * dpr + EPS));
|
|
27
|
+
|
|
28
|
+
/** `k` on its pixel level: k·dpr rounded to a whole number when it is 1 or more; as it is (in range) below that. */
|
|
29
|
+
export function snapK(k, dpr = 1) {
|
|
30
|
+
const c = clampO(k), n = c * dpr;
|
|
31
|
+
if (n < 1 - EPS) return c;
|
|
32
|
+
return Math.min(topLevel(dpr), Math.max(1, Math.round(n))) / dpr;
|
|
33
|
+
}
|
|
34
|
+
/** Whether `k` is a pixel level (a whole number of device px per logical unit). */
|
|
35
|
+
export const isPixelK = (k, dpr = 1) => k * dpr >= 1 - EPS && Math.abs(k * dpr - Math.round(k * dpr)) < 1e-6;
|
|
36
|
+
/** One step in (`dir` > 0) or out: the next pixel level; below one device pixel a unit, by 1.25. */
|
|
37
|
+
export function stepK(k, dir, dpr = 1) {
|
|
38
|
+
const n = k * dpr;
|
|
39
|
+
if (dir > 0) {
|
|
40
|
+
if (n >= 1 - EPS) return Math.min(topLevel(dpr), Math.floor(n + EPS) + 1) / dpr;
|
|
41
|
+
return n * 1.25 >= 1 - EPS ? 1 / dpr : clampO(k * 1.25);
|
|
42
|
+
}
|
|
43
|
+
if (n > 1 + EPS) return Math.max(1, Math.ceil(n - EPS) - 1) / dpr;
|
|
44
|
+
return clampO(k * 0.8);
|
|
45
|
+
}
|
|
46
|
+
/** The view at scale `k` (in range, continuous: the wheel's) about the viewport point (cx, cy). */
|
|
47
|
+
export const zoomOffice = (v, k, cx, cy) => zoomTo(v, clampO(k), cx, cy);
|
|
48
|
+
/** A pinch step: `raw` the zoom the pinch has reached (continuous); the world point under the old midpoint follows. */
|
|
49
|
+
export const pinchOffice = (v, from, to, raw) => pinchTo(v, from, to, clampO(raw));
|
|
50
|
+
|
|
51
|
+
/** The fit modes: "contain" (the whole scene shows) and "cover" (the panel is filled, the overflow cropped). */
|
|
52
|
+
export const FIT_MODES = ["contain", "cover"];
|
|
53
|
+
export const fitModeOk = (m) => (m === "cover" ? "cover" : "contain");
|
|
54
|
+
/** The scale at which a `w`×`h` scene fits a `size` viewport: contain (the whole scene) or cover (the whole panel). */
|
|
55
|
+
export function fitK(w, h, size, mode = "contain", margin = 0) {
|
|
56
|
+
const sx = (size.w - 2 * margin) / Math.max(1, w), sy = (size.h - 2 * margin) / Math.max(1, h);
|
|
57
|
+
return clampO(fitModeOk(mode) === "cover" ? Math.max(sx, sy) : Math.min(sx, sy));
|
|
58
|
+
}
|
|
59
|
+
/** The scene at fitK, centred. */
|
|
60
|
+
export function fitScene(w, h, size, mode = "contain", margin = 0) {
|
|
61
|
+
const k = fitK(w, h, size, mode, margin);
|
|
62
|
+
return { k, x: (size.w - w * k) / 2, y: (size.h - h * k) / 2 };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The desks layout's scale when it opens: the pixel level nearest one CSS px a unit (1 on an ordinary or a 2× screen,
|
|
66
|
+
* 4/3 at 150 %, 0.8 at 125 %); the layout wraps at the panel's width over it, so it opens as wide as the panel.
|
|
67
|
+
*/
|
|
68
|
+
export const desksK = (dpr = 1) => Math.max(1, Math.round(dpr)) / dpr;
|
|
69
|
+
/** The view a scene opens on with nothing kept: zones fitted (the office's mode), desks from the top left at desksK (centred by clampPan on an axis where it is smaller). */
|
|
70
|
+
export const openingView = (w, h, size, dpr, zones, mode = "contain") => (zones ? fitScene(w, h, size, mode) : { k: desksK(dpr), x: 0, y: 0 });
|
|
71
|
+
/** One axis of clampPan: offset `o` of a scene `s` long in a viewport `n` long. */
|
|
72
|
+
const clampAxis = (o, s, n) => (s >= n ? Math.min(0, Math.max(n - s, o)) : (n - s) / 2);
|
|
73
|
+
/**
|
|
74
|
+
* The view kept on the scene: along an axis where the scene is at least the viewport it covers the viewport (no gap at
|
|
75
|
+
* either edge); where it is smaller it is centred on that axis. Nothing can be scrolled off-screen.
|
|
76
|
+
*/
|
|
77
|
+
export function clampPan(v, w, h, size) {
|
|
78
|
+
return { k: v.k, x: clampAxis(v.x, w * v.k, size.w), y: clampAxis(v.y, h * v.k, size.h) };
|
|
79
|
+
}
|
|
80
|
+
/** The zoom as a person reads it: "150%". */
|
|
81
|
+
export const zoomLabel = (k) => `${Math.round(k * 100)}%`;
|
|
82
|
+
/**
|
|
83
|
+
* The overview's box in a `size` stage: the scene's aspect, about a seventh of the stage's width (120–280 px, at most
|
|
84
|
+
* a third of its height), `margin` px in from the bottom right corner — always inside the stage.
|
|
85
|
+
*/
|
|
86
|
+
export function miniBox(size, w, h, margin = 12) {
|
|
87
|
+
const ar = Math.max(1, w) / Math.max(1, h), roomW = Math.max(0, size.w - 2 * margin), roomH = Math.max(0, size.h - 2 * margin);
|
|
88
|
+
let bw = Math.min(280, Math.max(120, Math.round(size.w / 7)), roomW), bh = bw / ar;
|
|
89
|
+
const maxH = Math.min(roomH, Math.max(60, size.h / 3));
|
|
90
|
+
if (bh > maxH) { bh = maxH; bw = bh * ar; }
|
|
91
|
+
bw = Math.floor(bw); bh = Math.floor(bh);
|
|
92
|
+
return { x: size.w - margin - bw, y: size.h - margin - bh, w: bw, h: bh };
|
|
93
|
+
}
|
|
94
|
+
/** The view's frame on the overview (logical units): the part of the scene in view (never outside it). */
|
|
95
|
+
export function miniFrame(v, size, w, h) {
|
|
96
|
+
const x0 = Math.max(0, -v.x / v.k), y0 = Math.max(0, -v.y / v.k);
|
|
97
|
+
const x1 = Math.min(w, (size.w - v.x) / v.k), y1 = Math.min(h, (size.h - v.y) / v.k);
|
|
98
|
+
return { x: x0, y: y0, width: Math.max(0, x1 - x0), height: Math.max(0, y1 - y0) };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ── Kept per device, per office: the zoom and the logical point in the middle (the viewport's size may differ) ──
|
|
102
|
+
|
|
103
|
+
/** What is kept of a view: { k, cx, cy }, and `fit: true` when it is a fit (it is fitted again, to the size then). */
|
|
104
|
+
export const savedOf = (v, size, fit = false) => ({ k: v.k, cx: (size.w / 2 - v.x) / v.k, cy: (size.h / 2 - v.y) / v.k, ...(fit ? { fit: true } : {}) });
|
|
105
|
+
/** A kept view, checked: { k, cx, cy } with finite numbers (k in range), else null. */
|
|
106
|
+
export function savedOk(x) {
|
|
107
|
+
if (!x || typeof x !== "object") return null;
|
|
108
|
+
const { k, cx, cy } = x;
|
|
109
|
+
if (![k, cx, cy].every((n) => typeof n === "number" && Number.isFinite(n)) || k <= 0) return null;
|
|
110
|
+
if (Math.abs(cx) > 1e6 || Math.abs(cy) > 1e6) return null;
|
|
111
|
+
return { k: clampO(k), cx, cy, ...(x.fit === true ? { fit: true } : {}) };
|
|
112
|
+
}
|
|
113
|
+
/** The view a kept one gives in a `size` viewport: the same zoom, the same logical point in the middle. */
|
|
114
|
+
export function viewFromSaved(s, size) {
|
|
115
|
+
const k = clampO(s.k);
|
|
116
|
+
return { k, x: size.w / 2 - s.cx * k, y: size.h / 2 - s.cy * k };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── Drawing ──
|
|
120
|
+
|
|
121
|
+
/** The cached layers' budget (device px): beyond it they are drawn coarser and scaled up by a whole number. */
|
|
122
|
+
export const LAYER_MAX = { side: CANVAS_MAX.side, area: 8 * 1024 * 1024 };
|
|
123
|
+
/**
|
|
124
|
+
* The layers' resolution (layer px per logical unit) for a view at `a` device px a unit and a `w`×`h` scene: `a`
|
|
125
|
+
* itself (the layer is blitted 1:1), or — over the budget — `a` divided by the smallest whole number that fits it,
|
|
126
|
+
* so the blit is still an integer scale-up (nearest neighbour: crisp).
|
|
127
|
+
*/
|
|
128
|
+
export function layerScale(a, w, h, max = LAYER_MAX) {
|
|
129
|
+
const cap = Math.min(max.side / Math.max(1, w), max.side / Math.max(1, h), Math.sqrt(max.area / Math.max(1, w * h)));
|
|
130
|
+
if (a <= cap) return a;
|
|
131
|
+
return a / Math.ceil(a / cap - EPS);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The matte from a background's edge pixels: `strips` are RGBA pixel arrays (the top and bottom rows, the left and
|
|
135
|
+
* right columns); the mean of their opaque pixels as #rrggbb, or null when none is opaque.
|
|
136
|
+
*/
|
|
137
|
+
export function edgeMatte(strips) {
|
|
138
|
+
let r = 0, g = 0, b = 0, n = 0;
|
|
139
|
+
for (const s of strips) {
|
|
140
|
+
if (!s || typeof s.length !== "number") continue;
|
|
141
|
+
for (let i = 0; i + 3 < s.length; i += 4) { if (s[i + 3] < 128) continue; r += s[i]; g += s[i + 1]; b += s[i + 2]; n++; }
|
|
142
|
+
}
|
|
143
|
+
if (!n) return null;
|
|
144
|
+
const hx = (v) => Math.round(v / n).toString(16).padStart(2, "0");
|
|
145
|
+
return `#${hx(r)}${hx(g)}${hx(b)}`;
|
|
146
|
+
}
|
|
147
|
+
/** The edge strips of a canvas (its 2d context's pixels), for edgeMatte; null when they cannot be read. */
|
|
148
|
+
export function edgeStrips(c) {
|
|
149
|
+
try {
|
|
150
|
+
const g = c && typeof c.getContext === "function" ? c.getContext("2d") : null;
|
|
151
|
+
if (!g || typeof g.getImageData !== "function" || !(c.width > 0) || !(c.height > 0)) return null;
|
|
152
|
+
const w = c.width, h = c.height;
|
|
153
|
+
return [g.getImageData(0, 0, w, 1).data, g.getImageData(0, h - 1, w, 1).data, g.getImageData(0, 0, 1, h).data, g.getImageData(w - 1, 0, 1, h).data];
|
|
154
|
+
} catch { return null; } // a tainted or lost canvas: the skin's matte
|
|
155
|
+
}
|
|
@@ -2,27 +2,40 @@
|
|
|
2
2
|
// fleet-office-pack.js). Each instance goes to the area for its state now and takes a seat slot there; when its state
|
|
3
3
|
// changes it walks (along the pack's waypoints) to the new area. Pure — fleet-office.js draws it. Also here: what a
|
|
4
4
|
// character says in its speech bubble, and which furniture is active.
|
|
5
|
-
import { LOGICAL } from "./fleet-office-pack.js";
|
|
6
|
-
import { nameHash, pathLength, along, routeVia } from "./fleet-office-model.js";
|
|
5
|
+
import { LOGICAL, own } from "./fleet-office-pack.js";
|
|
6
|
+
import { FLIGHT, nameHash, pathLength, along, routeVia } from "./fleet-office-model.js";
|
|
7
|
+
import { hue } from "./fleet-traffic.js";
|
|
7
8
|
|
|
8
9
|
/** The area for a state: the pack's own, else its "default", else its first area. */
|
|
9
10
|
export function zoneOf(pack, state) {
|
|
10
11
|
const z = pack.stateZones || {};
|
|
11
|
-
return z
|
|
12
|
+
return own(z, state) || own(z, "default") || Object.keys(pack.areas)[0];
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
/**
|
|
16
|
+
* How far apart overflow spots are, and how far they stay from the area's seats (logical units); TITLE: the band at
|
|
17
|
+
* the top of an area its name is written in, kept clear. A crowded area packs tighter (STEPS, #2513: 70 instances, 42
|
|
18
|
+
* idle, piled up in the lounge) before anyone is left to the "+N" count.
|
|
19
|
+
*/
|
|
20
|
+
export const OVERFLOW = { STEP: 56, CLEAR: 40, EDGE: 24, TITLE: 30, STEPS: [56, 46, 38, 32] };
|
|
21
|
+
function spotsAt(area, step) {
|
|
22
|
+
const out = [], { x, y, w, h } = area.rect, clear = step === OVERFLOW.STEP ? OVERFLOW.CLEAR : Math.round(step * 0.72);
|
|
23
|
+
for (let sy = y + OVERFLOW.EDGE + OVERFLOW.TITLE; sy <= y + h - OVERFLOW.EDGE; sy += step) {
|
|
24
|
+
for (let sx = x + OVERFLOW.EDGE; sx <= x + w - OVERFLOW.EDGE; sx += step) {
|
|
25
|
+
if (area.seats.every((s) => Math.hypot(s.x - sx, s.y - sy) >= clear)) out.push({ x: sx, y: sy });
|
|
22
26
|
}
|
|
23
27
|
}
|
|
24
28
|
return out;
|
|
25
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* An area's overflow spots, for when its seats are taken: a tidy grid inside its rect, clear of its seats and its
|
|
32
|
+
* title, in reading order. `need`: how many are wanted — the widest spacing that fits them all, else the tightest.
|
|
33
|
+
*/
|
|
34
|
+
export function overflowSpots(area, need = 0) {
|
|
35
|
+
let spots = [];
|
|
36
|
+
for (const step of OVERFLOW.STEPS) { spots = spotsAt(area, step); if (spots.length >= need) return spots; }
|
|
37
|
+
return spots;
|
|
38
|
+
}
|
|
26
39
|
|
|
27
40
|
/**
|
|
28
41
|
* Seats for everyone: `wants` [{ name, area }] in the fleet's order; `prev` what the last call gave (name → { area,
|
|
@@ -31,51 +44,224 @@ export function overflowSpots(area) {
|
|
|
31
44
|
* Returns { seats: Map name → { area, slot, x, y }, crowd: Map }.
|
|
32
45
|
*/
|
|
33
46
|
export function assignSeats(pack, wants, prev = new Map()) {
|
|
34
|
-
const seats = new Map(), crowd = new Map(), taken = new Map(), spill = new Map();
|
|
35
|
-
const
|
|
36
|
-
const
|
|
47
|
+
const seats = new Map(), crowd = new Map(), taken = new Map(), spill = new Map(), demand = new Map();
|
|
48
|
+
const areaOf = (a) => own(pack.areas, a);
|
|
49
|
+
for (const w of wants) demand.set(w.area, (demand.get(w.area) || 0) + 1);
|
|
50
|
+
const spotsOf = (area) => {
|
|
51
|
+
if (!spill.has(area)) spill.set(area, overflowSpots(areaOf(area), Math.max(0, (demand.get(area) || 0) - areaOf(area).seats.length)));
|
|
52
|
+
return spill.get(area);
|
|
53
|
+
};
|
|
54
|
+
const pos = (area, slot) => (slot >= 0 ? areaOf(area).seats[slot] : spotsOf(area)[-slot - 1]);
|
|
37
55
|
const usedIn = (area) => { if (!taken.has(area)) taken.set(area, new Set()); return taken.get(area); };
|
|
38
56
|
const give = (name, area, slot) => { const p = pos(area, slot); usedIn(area).add(slot); seats.set(name, { area, slot, x: p.x, y: p.y }); };
|
|
39
57
|
for (const w of wants) {
|
|
40
58
|
const p = prev.get(w.name);
|
|
41
|
-
if (!p || p.area !== w.area || !
|
|
59
|
+
if (!p || p.area !== w.area || !areaOf(w.area) || !pos(w.area, p.slot) || usedIn(w.area).has(p.slot)) continue;
|
|
42
60
|
give(w.name, w.area, p.slot);
|
|
43
61
|
}
|
|
44
62
|
for (const w of wants) {
|
|
45
|
-
if (seats.has(w.name) || !
|
|
46
|
-
const used = usedIn(w.area), area =
|
|
63
|
+
if (seats.has(w.name) || !areaOf(w.area)) continue;
|
|
64
|
+
const used = usedIn(w.area), area = areaOf(w.area);
|
|
47
65
|
let slot = area.seats.findIndex((_, i) => !used.has(i));
|
|
48
66
|
if (slot < 0) { const j = spotsOf(w.area).findIndex((_, i) => !used.has(-i - 1)); slot = j < 0 ? null : -j - 1; }
|
|
49
67
|
if (slot === null) crowd.set(w.area, (crowd.get(w.area) || 0) + 1);
|
|
50
68
|
else give(w.name, w.area, slot);
|
|
51
69
|
}
|
|
52
|
-
|
|
70
|
+
// Over capacity: more people want the area than it has seats (their bubbles and tags are thinned, fleet-office.js).
|
|
71
|
+
const over = new Set([...demand].filter(([a, n]) => areaOf(a) && n > areaOf(a).seats.length).map(([a]) => a));
|
|
72
|
+
return { seats, crowd, over };
|
|
53
73
|
}
|
|
54
74
|
|
|
55
75
|
/**
|
|
56
76
|
* The zones layout for `nodes` (buildOrgView's) on a pack: everyone but the missing at their seat. The same shape as
|
|
57
|
-
* the desks layout (rooms → desks with a seat), in logical units, plus `mode: "zones"`, the pack's areas and graph
|
|
58
|
-
* seats given (`seats`, the next call's `prev`) and `
|
|
77
|
+
* the desks layout (rooms → desks with a seat), in logical units, plus `mode: "zones"`, the pack's areas and graph (and
|
|
78
|
+
* its `straight`, #2634), the seats given (`seats`, the next call's `prev`), `crowd` and `over` (the areas over capacity).
|
|
59
79
|
*/
|
|
60
|
-
export function zonesLayout(pack, nodes, prev = new Map()) {
|
|
61
|
-
|
|
62
|
-
const
|
|
80
|
+
export function zonesLayout(pack, nodes, prev = new Map(), { night = false } = {}) {
|
|
81
|
+
// At night (#2568) the idle go to bed: the area asleep people go to while it has a free seat; the rest doze where they are.
|
|
82
|
+
const bed = zoneOf(pack, "paused"), beds = (own(pack.areas, bed)?.seats || []).length, sleepers = new Set(), inBed = new Set();
|
|
83
|
+
let used = nodes.filter((n) => n.state !== "idle" && zoneOf(pack, n.state) === bed).length;
|
|
84
|
+
if (night) for (const n of nodes) if (n.state === "idle") { sleepers.add(n.name); if (used < beds) { used++; inBed.add(n.name); } }
|
|
85
|
+
const wants = nodes.filter((n) => n.state !== "missing").map((n) => ({ name: n.name, area: inBed.has(n.name) ? bed : zoneOf(pack, n.state) }));
|
|
86
|
+
const { seats, crowd, over } = assignSeats(pack, wants, prev);
|
|
63
87
|
const desks = nodes.filter((n) => seats.has(n.name)).map((n) => {
|
|
64
88
|
const s = seats.get(n.name);
|
|
65
|
-
return { key: `zone/${n.name}`, node: n, x: s.x - 24, y: s.y - 52, w: 48, h: 66, seat: { x: s.x, y: s.y }, wp: null, area: s.area, slot: s.slot };
|
|
89
|
+
return { key: `zone/${n.name}`, node: n, x: s.x - 24, y: s.y - 52, w: 48, h: 66, seat: { x: s.x, y: s.y }, wp: null, area: s.area, slot: s.slot, night: sleepers.has(n.name) };
|
|
66
90
|
});
|
|
67
91
|
return { mode: "zones", rooms: [{ key: "\u0000zones", name: null, lobby: false, x: 0, y: 0, w: LOGICAL.w, h: LOGICAL.h, desks }], areas: pack.areas,
|
|
68
|
-
waypoints: pack.waypoints || [], edges: pack.edges || [], door: pack.door, width: LOGICAL.w, height: LOGICAL.h, seats, crowd };
|
|
92
|
+
waypoints: pack.waypoints || [], edges: pack.edges || [], straight: pack.straight === true, door: pack.door, width: LOGICAL.w, height: LOGICAL.h, seats, crowd, over };
|
|
69
93
|
}
|
|
70
94
|
|
|
71
95
|
// ── Walking ──
|
|
72
96
|
|
|
73
97
|
/** Logical units per ms (crossing the canvas takes about six seconds). */
|
|
74
98
|
export const WALK = { SPEED: 0.2 };
|
|
99
|
+
/** The step of a polyline a fraction `t` (0–1) of the way along it: { dx, dy } (its direction), or null (no length). */
|
|
100
|
+
export function headingAt(pts, t) {
|
|
101
|
+
const total = pathLength(pts);
|
|
102
|
+
if (!total) return null;
|
|
103
|
+
let left = Math.max(0, Math.min(1, t)) * total;
|
|
104
|
+
for (let i = 1; i < pts.length; i++) {
|
|
105
|
+
const dx = pts[i].x - pts[i - 1].x, dy = pts[i].y - pts[i - 1].y, l = Math.hypot(dx, dy);
|
|
106
|
+
if (l > 0 && (left <= l || i === pts.length - 1)) return { dx, dy };
|
|
107
|
+
left -= l;
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* #2634: a polyline with each diagonal leg made an L — across first, then up or down — so a character set's three
|
|
113
|
+
* walking strips always fit; legs already across or up/down are kept, and no point repeats (no zero-length leg).
|
|
114
|
+
*/
|
|
115
|
+
export function straighten(pts) {
|
|
116
|
+
const out = [];
|
|
117
|
+
const add = (x, y) => { const l = out[out.length - 1]; if (!l || l.x !== x || l.y !== y) out.push({ x, y }); };
|
|
118
|
+
for (const p of pts) { const l = out[out.length - 1]; if (l && l.x !== p.x && l.y !== p.y) add(p.x, l.y); add(p.x, p.y); }
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
// ── Night (#2568) ──
|
|
122
|
+
|
|
123
|
+
/** Bedtime on the viewer's clock: from FROM o'clock to TO o'clock the idle sleep (anyone with work still gets up for it). */
|
|
124
|
+
export const NIGHT = { FROM: 0, TO: 6, TICK: 60000 };
|
|
125
|
+
export const isNight = (d = new Date()) => { const h = d.getHours(); return NIGHT.FROM <= NIGHT.TO ? h >= NIGHT.FROM && h < NIGHT.TO : h >= NIGHT.FROM || h < NIGHT.TO; };
|
|
126
|
+
|
|
127
|
+
// ── Wandering (#2560) ──
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* An idle person now and then strolls to a spot near their seat and back, so a quiet room is not a row of statues:
|
|
131
|
+
* every PERIOD ms (each person's own phase, from their name) they are AWAY ms at a waypoint within REACH of the seat —
|
|
132
|
+
* one no seat of the office is on (taken or free), picked anew each time — then walk back. Pure: the page asks every TICK ms and walks the change.
|
|
133
|
+
*/
|
|
134
|
+
export const WANDER = { PERIOD: 40000, AWAY: 8000, REACH: 220, TICK: 1000 };
|
|
135
|
+
/** Where `name`, seated at `seat`, should be at `t` (ms) while idle: a spot near the seat, or the seat. */
|
|
136
|
+
export function wanderTarget(name, seat, graph, t, seats = []) {
|
|
137
|
+
const near = (graph.waypoints || []).filter((w) => Math.hypot(w.x - seat.x, w.y - seat.y) <= WANDER.REACH
|
|
138
|
+
&& !seats.some((s) => Math.hypot(w.x - s.x, w.y - s.y) < 4));
|
|
139
|
+
if (!near.length) return seat;
|
|
140
|
+
const h = nameHash(name), k = t + (h % WANDER.PERIOD), turn = Math.floor(k / WANDER.PERIOD);
|
|
141
|
+
if (k - turn * WANDER.PERIOD >= WANDER.AWAY) return seat;
|
|
142
|
+
const w = near[nameHash(`${name}\u0000${turn}`) % near.length];
|
|
143
|
+
return { x: w.x, y: w.y };
|
|
144
|
+
}
|
|
145
|
+
/** An NPC drawn as furniture (#2596: an item with `walk`): its walker's name — never a person's, and its own phase. */
|
|
146
|
+
export const npcKey = (f) => `furniture/${f.id}`;
|
|
147
|
+
/** Everyone's walk target: their seat, or — idle and `wander` on — wanderTarget's spot (one person a spot: whoever
|
|
148
|
+
* would share it stays seated this time). And, `wander` on, each item of `furniture` with a `walk` (#2596): its spot
|
|
149
|
+
* (x, y), or a stroll near it as an idle person's (by npcKey) — none at `night` for one with a night place (asleep there). */
|
|
150
|
+
export function walkTargets(desks, graph, t, wander = true, furniture = [], night = false) {
|
|
151
|
+
// Every seat of the office (anyone's, taken or not: someone may sit down on it any moment), not only the ones in use.
|
|
152
|
+
// And each walking NPC's spot (#2596 review): its home, where it stands outside its stroll and goes back to whenever
|
|
153
|
+
// its stroll would collide, so nobody else's stroll may end there — no two walkers ever share a target.
|
|
154
|
+
const homes = wander ? furniture.filter((f) => f.walk && !(night && f.night)).map((f) => ({ x: f.x, y: f.y })) : [];
|
|
155
|
+
const seats = [...Object.values(graph.areas || {}).flatMap((a) => a.seats || []), ...desks.map((d) => d.seat), ...homes];
|
|
156
|
+
const used = new Set(homes.map((h) => `${h.x},${h.y}`));
|
|
157
|
+
const stroll = (name, home) => {
|
|
158
|
+
const p = wanderTarget(name, home, graph, t, seats), k = `${p.x},${p.y}`;
|
|
159
|
+
if (p === home || used.has(k)) return home;
|
|
160
|
+
used.add(k);
|
|
161
|
+
return p;
|
|
162
|
+
};
|
|
163
|
+
const out = new Map(desks.map((d) => [d.node.name, !wander || d.node.state !== "idle" || d.night ? d.seat : stroll(d.node.name, d.seat)]));
|
|
164
|
+
if (wander) for (const f of furniture) if (f.walk && !(night && f.night)) out.set(npcKey(f), stroll(npcKey(f), { x: f.x, y: f.y }));
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ── Mail from another office (#2564) ──
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Something sent to a person here by someone who is not (in another office, or in none): it flies in at the door and
|
|
172
|
+
* lands on the floor near the middle of the room; the person walks over, picks it up and walks back to their seat.
|
|
173
|
+
* MAX_WAIT: a letter no one picks up is gone after it; GAP: letters waiting side by side; SEARCH: how far from the
|
|
174
|
+
* middle a landing spot is looked for when no waypoint will do; ASLEEP: who does not get up
|
|
175
|
+
* for it (it waits for them to wake, up to MAX_WAIT).
|
|
176
|
+
*/
|
|
177
|
+
export const MAIL = { MAX_WAIT: 60000, GAP: 16, SEARCH: 320, ASLEEP: new Set(["paused", "stopped"]), MAX: 20 };
|
|
178
|
+
/**
|
|
179
|
+
* Where the `n`th waiting letter lands: `n` GAPs along from the waypoint nearest the room's middle — the nearest whose
|
|
180
|
+
* spot (offset included) no seat of the office is on, taken or free (someone may sit there any moment; #2561 likewise);
|
|
181
|
+
* with none, the nearest point round the middle (rings GAP·2 apart, inside the room) that no seat is on; with none of
|
|
182
|
+
* those either, null — the letter is not delivered here (never on a seat).
|
|
183
|
+
*/
|
|
184
|
+
export function dropPoint(L, n = 0) {
|
|
185
|
+
const mid = { x: L.width / 2, y: L.height * 0.55 }, d = (p, q) => Math.hypot(p.x - q.x, p.y - q.y);
|
|
186
|
+
const seats = [...Object.values(L.areas || {}).flatMap((a) => a.seats || []), ...desksIn(L).map((q) => q.seat)];
|
|
187
|
+
const onSeat = (p) => seats.some((q) => d(p, q) < 4);
|
|
188
|
+
const spots = (L.waypoints || []).map((w) => ({ x: Math.round(w.x + n * MAIL.GAP), y: Math.round(w.y), from: w }))
|
|
189
|
+
.filter((p) => !onSeat(p.from) && !onSeat(p)).sort((p, q) => d(p.from, mid) - d(q.from, mid));
|
|
190
|
+
if (spots.length) return { x: spots[0].x, y: spots[0].y };
|
|
191
|
+
const inRoom = (p) => p.x >= 0 && p.y >= 0 && p.x <= L.width && p.y <= L.height;
|
|
192
|
+
for (let r = 0; r <= MAIL.SEARCH; r += 2 * MAIL.GAP) {
|
|
193
|
+
for (let k = 0, m = r ? 16 : 1; k < m; k++) {
|
|
194
|
+
const a = (2 * Math.PI * k) / m, p = { x: Math.round(mid.x + r * Math.cos(a) + n * MAIL.GAP), y: Math.round(mid.y + r * Math.sin(a)) };
|
|
195
|
+
if (inRoom(p) && !onSeat(p)) return p;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
const desksIn = (L) => (L.rooms || []).flatMap((r) => r.desks || []).filter((d) => d.node);
|
|
201
|
+
/**
|
|
202
|
+
* The letters of one office. `sync(items, now, L, still)`: each new traffic item to someone drawn here from someone
|
|
203
|
+
* who is not becomes a letter (none with reduced motion, nor for a stale delegation; a zones office only).
|
|
204
|
+
* `frame(now, L, walkers)` → what to draw, { id, hue, kind, from, x, y, t } (t: its flight, 0 on the floor), picking
|
|
205
|
+
* up the letters whose person stands on them, and dropping those gone too long or whose person left. `targets(L)`:
|
|
206
|
+
* name → where to walk to pick one up (the first waiting for them; none while they are asleep). `prime(items)`: mark as
|
|
207
|
+
* seen what was in the air before this office was shown (a new office's or style's mail starts empty). `size()`.
|
|
208
|
+
*/
|
|
209
|
+
export function createMail({ max = MAIL.MAX } = {}) {
|
|
210
|
+
const seen = new Set();
|
|
211
|
+
let letters = [];
|
|
212
|
+
const here = (L, name) => desksIn(L).find((d) => d.node.name === name) || null;
|
|
213
|
+
return {
|
|
214
|
+
sync(items, now, L, still = false) {
|
|
215
|
+
let started = 0;
|
|
216
|
+
for (const it of items || []) {
|
|
217
|
+
if (seen.has(it.id)) continue;
|
|
218
|
+
seen.add(it.id);
|
|
219
|
+
if (seen.size > 2000) seen.delete(seen.values().next().value);
|
|
220
|
+
if (still || it.phase === "stale" || !L || L.mode !== "zones" || !here(L, it.to) || here(L, it.from)) continue;
|
|
221
|
+
const drop = dropPoint(L, letters.filter((l) => l.to !== it.to).length % 4);
|
|
222
|
+
if (!drop) continue; // nowhere off the seats: not delivered here
|
|
223
|
+
const pts = [{ x: L.door.x, y: L.door.y }, drop];
|
|
224
|
+
letters.push({ id: it.id, from: it.from, to: it.to, kind: it.kind, hue: hue(it.kind), start: now, drop, pts,
|
|
225
|
+
ms: Math.min(FLIGHT.MAX_MS, Math.max(FLIGHT.MIN_MS, pathLength(pts) / FLIGHT.SPEED)) });
|
|
226
|
+
started++;
|
|
227
|
+
}
|
|
228
|
+
if (letters.length > max) letters = letters.slice(letters.length - max);
|
|
229
|
+
return started;
|
|
230
|
+
},
|
|
231
|
+
frame(now, L, walkers) {
|
|
232
|
+
const out = [];
|
|
233
|
+
letters = letters.filter((l) => {
|
|
234
|
+
if (!here(L, l.to)) return false;
|
|
235
|
+
const t = (now - l.start) / l.ms;
|
|
236
|
+
if (t < 1) { if (t >= 0) { const p = along(l.pts, t); out.push({ id: l.id, hue: l.hue, kind: l.kind, from: l.from, x: p.x, y: p.y, t }); } return true; }
|
|
237
|
+
if (now - l.start - l.ms > MAIL.MAX_WAIT) return false;
|
|
238
|
+
const w = walkers && walkers.at(l.to, now);
|
|
239
|
+
if (w && !w.walking && Math.hypot(w.x - l.drop.x, w.y - l.drop.y) < 4) return false; // picked up
|
|
240
|
+
out.push({ id: l.id, hue: l.hue, kind: l.kind, from: l.from, x: l.drop.x, y: l.drop.y, t: 0 });
|
|
241
|
+
return true;
|
|
242
|
+
});
|
|
243
|
+
return out;
|
|
244
|
+
},
|
|
245
|
+
targets(L, now) {
|
|
246
|
+
const out = new Map();
|
|
247
|
+
for (const l of letters) {
|
|
248
|
+
if (out.has(l.to) || now - l.start < l.ms) continue;
|
|
249
|
+
const d = here(L, l.to);
|
|
250
|
+
if (d && !MAIL.ASLEEP.has(d.node.state) && !d.night) out.set(l.to, l.drop); // asleep, or in bed for the night (#2568)
|
|
251
|
+
}
|
|
252
|
+
return out;
|
|
253
|
+
},
|
|
254
|
+
/** What is already in the traffic when this office's mail starts (#2564): seen, so it is not delivered again here. */
|
|
255
|
+
prime(items) { for (const it of items || []) seen.add(it.id); },
|
|
256
|
+
size: () => letters.length,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
75
260
|
/**
|
|
76
261
|
* Where each character is. `sync(targets, now, graph, still)`: targets name → { x, y }; someone new is already there;
|
|
77
|
-
* someone whose target moved walks from where they are now along the graph (routeVia
|
|
78
|
-
* `still` (reduced motion); someone gone is forgotten. `at(name, now)` → { x, y, walking }
|
|
262
|
+
* someone whose target moved walks from where they are now along the graph (routeVia; each diagonal leg an L when the
|
|
263
|
+
* graph is `straight`, #2634) — or is there at once when `still` (reduced motion); someone gone is forgotten. `at(name, now)` → { x, y, walking } and, while walking, `heading`: the
|
|
264
|
+
* current step's { dx, dy }, which picks a pack's walkDown / walkUp / walkSide strip; `moving(now)`.
|
|
79
265
|
*/
|
|
80
266
|
export function createWalkers() {
|
|
81
267
|
const by = new Map();
|
|
@@ -84,7 +270,7 @@ export function createWalkers() {
|
|
|
84
270
|
const t = (now - w.start) / w.ms;
|
|
85
271
|
if (t >= 1) return { x: w.to.x, y: w.to.y, walking: false };
|
|
86
272
|
const p = along(w.pts, Math.max(0, t));
|
|
87
|
-
return { x: p.x, y: p.y, walking: true };
|
|
273
|
+
return { x: p.x, y: p.y, walking: true, heading: headingAt(w.pts, Math.max(0, t)) };
|
|
88
274
|
};
|
|
89
275
|
return {
|
|
90
276
|
sync(targets, now, graph, still = false) {
|
|
@@ -93,7 +279,8 @@ export function createWalkers() {
|
|
|
93
279
|
const w = by.get(name);
|
|
94
280
|
if (!w || still) { if (!w || w.to.x !== to.x || w.to.y !== to.y) by.set(name, { pts: null, to }); continue; }
|
|
95
281
|
if (w.to.x === to.x && w.to.y === to.y) continue;
|
|
96
|
-
const here = posAt(w, now),
|
|
282
|
+
const here = posAt(w, now), via = routeVia(graph, { x: here.x, y: here.y }, { x: to.x, y: to.y });
|
|
283
|
+
const pts = graph.straight ? straighten(via) : via; // #2634: timed along the longer way
|
|
97
284
|
by.set(name, { pts, start: now, ms: Math.max(1, pathLength(pts) / WALK.SPEED), to });
|
|
98
285
|
}
|
|
99
286
|
},
|
|
@@ -104,14 +291,17 @@ export function createWalkers() {
|
|
|
104
291
|
|
|
105
292
|
/**
|
|
106
293
|
* The person at (x, y) in layout px as the scene draws them (#2431 review): each desk's box moved to where its person is
|
|
107
|
-
* now (`posOf(desk)`: a walker's position; null: at the seat), the one drawn last (the nearest) tried first
|
|
294
|
+
* now (`posOf(desk)`: a walker's position; null: at the seat), the one drawn last (the nearest) tried first — the same
|
|
295
|
+
* order the scene draws them in. `boxOf(desk, at)` (#2513 review): the box the skin draws that person in there (an
|
|
296
|
+
* uploaded character's strip, anchor and size, in its current pose and walking direction); null: the desk's own box.
|
|
108
297
|
*/
|
|
109
|
-
export function personAt(desks, x, y, posOf) {
|
|
110
|
-
const people = desks.filter((d) => d.node).map((d) => {
|
|
298
|
+
export function personAt(desks, x, y, posOf, boxOf = null) {
|
|
299
|
+
const people = desks.filter((d) => d.node).map((d) => ({ d, at: posOf(d) || { ...d.seat, walking: false, heading: null } }))
|
|
111
300
|
.sort((a, b) => a.at.y - b.at.y);
|
|
112
301
|
for (let i = people.length - 1; i >= 0; i--) {
|
|
113
|
-
const { d,
|
|
114
|
-
|
|
302
|
+
const { d, at } = people[i];
|
|
303
|
+
const b = (boxOf && boxOf(d, at)) || { x: d.x + at.x - d.seat.x, y: d.y + at.y - d.seat.y, w: d.w, h: d.h };
|
|
304
|
+
if (x >= b.x && x < b.x + b.w && y >= b.y && y < b.y + b.h) return d;
|
|
115
305
|
}
|
|
116
306
|
return null;
|
|
117
307
|
}
|
|
@@ -143,11 +333,13 @@ export function bubbleAt(name, state, ms, lines) {
|
|
|
143
333
|
* { key, calling, shown, box, tail? } with `box` the bubble's whole box ({ x, y, w, h }, for its whole line, so it never
|
|
144
334
|
* moves while typing out) and `tail` the point its tail reaches: the strip from the box down to it is kept clear too. The ones calling for the person go first (they always show), then the one that started earliest
|
|
145
335
|
* (the most typed out) — one already showing keeps its place. Each takes its spot, or one row up (`lift`) when that is
|
|
146
|
-
* free; else it waits for another round. Returns the placed ones, each with its `dy` (0 or -lift).
|
|
336
|
+
* free; else it waits for another round. Returns the placed ones, each with its `dy` (0 or -lift). `rank` (optional,
|
|
337
|
+
* bubbleRank): a lower one goes first (else: calling 0, the rest 2); `blocked`: boxes no bubble may cover (name tags).
|
|
147
338
|
*/
|
|
148
|
-
export function placeBubbles(items, lift = 22) {
|
|
149
|
-
const
|
|
150
|
-
const
|
|
339
|
+
export function placeBubbles(items, lift = 22, blocked = []) {
|
|
340
|
+
const rank = (it) => (it.rank ?? (it.calling ? 0 : 2));
|
|
341
|
+
const order = [...items].sort((a, b) => (rank(a) - rank(b)) || (b.shown - a.shown) || (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
|
|
342
|
+
const taken = [...blocked], out = [];
|
|
151
343
|
const hits = (r) => taken.some((t) => r.x < t.x + t.w && t.x < r.x + r.w && r.y < t.y + t.h && t.y < r.y + r.h);
|
|
152
344
|
for (const it of order) {
|
|
153
345
|
for (const dy of [0, -lift]) {
|
|
@@ -161,17 +353,47 @@ export function placeBubbles(items, lift = 22) {
|
|
|
161
353
|
return out;
|
|
162
354
|
}
|
|
163
355
|
|
|
356
|
+
/** Who speaks first when bubbles compete: calling for the person, then working, then everyone else. */
|
|
357
|
+
export const bubbleRank = (state) => (CALLING.has(state) ? 0 : state === "working" ? 1 : 2);
|
|
358
|
+
/** At most this many bubbles in an area over capacity (#2513). */
|
|
359
|
+
export const CROWD_BUBBLES = 3;
|
|
360
|
+
/** States whose bubbles are left out in an area over capacity (the chatter; a call or work still speaks). */
|
|
361
|
+
const QUIET_IN_CROWD = new Set(["idle", "paused", "stopped", "starting", "queued"]);
|
|
362
|
+
/**
|
|
363
|
+
* The bubbles a frame may show (#2513): in an area over capacity (`over`), none for idle or asleep people and at most
|
|
364
|
+
* CROWD_BUBBLES, the highest ranked first (bubbleRank); elsewhere all of them. `items`: { area, state, shown, key, … }.
|
|
365
|
+
*/
|
|
366
|
+
export function thinBubbles(items, over) {
|
|
367
|
+
const byArea = new Map(), out = [];
|
|
368
|
+
for (const it of items) {
|
|
369
|
+
if (!over || !over.has(it.area)) { out.push(it); continue; }
|
|
370
|
+
if (QUIET_IN_CROWD.has(it.state)) continue;
|
|
371
|
+
if (!byArea.has(it.area)) byArea.set(it.area, []);
|
|
372
|
+
byArea.get(it.area).push(it);
|
|
373
|
+
}
|
|
374
|
+
for (const list of byArea.values()) {
|
|
375
|
+
list.sort((a, b) => (bubbleRank(a.state) - bubbleRank(b.state)) || (b.shown - a.shown) || (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
|
|
376
|
+
out.push(...list.slice(0, CROWD_BUBBLES));
|
|
377
|
+
}
|
|
378
|
+
return out;
|
|
379
|
+
}
|
|
380
|
+
|
|
164
381
|
/** The lines for a state: the skin's (in the page's language), else the page's own. */
|
|
165
382
|
export function linesFor(state, lang, pack, defaults) {
|
|
166
|
-
const
|
|
167
|
-
return
|
|
383
|
+
const mine = pack && own(own(pack.bubbles, lang), state);
|
|
384
|
+
return Array.isArray(mine) && mine.length ? mine : own(defaults, state) || [];
|
|
168
385
|
}
|
|
169
386
|
|
|
170
387
|
// ── Furniture ──
|
|
171
388
|
|
|
172
389
|
/** The furniture active now: an item whose `activeWhen` names a state someone shown is in. */
|
|
173
|
-
export function activeFurniture(pack, states) {
|
|
390
|
+
export function activeFurniture(pack, states, byName = new Map()) {
|
|
174
391
|
const on = new Set();
|
|
175
|
-
for (const f of (pack && pack.furniture) || [])
|
|
392
|
+
for (const f of (pack && pack.furniture) || []) {
|
|
393
|
+
if (!f.activeWhen) continue;
|
|
394
|
+
// activeFor (#2570): only those people's states count; else anyone's shown.
|
|
395
|
+
const hit = f.activeFor ? f.activeFor.some((n) => byName.has(n) && f.activeWhen.includes(byName.get(n))) : f.activeWhen.some((s) => states.has(s));
|
|
396
|
+
if (hit) on.add(f.id);
|
|
397
|
+
}
|
|
176
398
|
return on;
|
|
177
399
|
}
|