@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,235 @@
|
|
|
1
|
+
// Fleet → Org chart → Office → Edit layout (#2513): an admin moves things on a stored office's zones skin — its seats,
|
|
2
|
+
// furniture, the door and the walkable graph's waypoints — and adds or removes waypoints and the corridors between
|
|
3
|
+
// them. The pack stays as it was uploaded: what differs from it is kept as the office's `overrides` (fleet-office-pack.js
|
|
4
|
+
// overridesBetween), saved with the whole list and the rev read (PUT /ui/offices; a change made meanwhile is a 409).
|
|
5
|
+
// Reset goes back to the pack's own positions. A "show coordinates" overlay draws the 1280×720 logical grid.
|
|
6
|
+
//
|
|
7
|
+
// #2665: an item with a `night` (#2568) has a night handle too — its night strip drawn faint on the canvas (as the
|
|
8
|
+
// seats' figures, #2558), a moon, and a thin line to its day place. One that sleeps in place shows it on its day place
|
|
9
|
+
// (and it follows the item); dragged away, the item has its own night place; its Reset puts back the pack's.
|
|
10
|
+
//
|
|
11
|
+
// Every handle is a button for the keyboard too: Tab to it, the arrow keys move it (Shift: by 10), Delete removes a
|
|
12
|
+
// waypoint; the selected waypoint's corridors are listed with a way to join or unjoin each. Never offered to a guest,
|
|
13
|
+
// the public link or a read-only session (the host refuses their saves too).
|
|
14
|
+
//
|
|
15
|
+
// The pure part (handles, moving, adding and removing) is exported for tests; LayoutEditor is the overlay.
|
|
16
|
+
import { html, useEffect, useLayoutEffect, useMemo, useRef, useState } from "/assets/app-html.js";
|
|
17
|
+
import { t, register } from "/assets/app-i18n.js";
|
|
18
|
+
import { SidePanel } from "/assets/side-panel.js";
|
|
19
|
+
import { LOGICAL, overridesBetween, own } from "./fleet-office-pack.js";
|
|
20
|
+
|
|
21
|
+
register("orgl", {
|
|
22
|
+
en: {
|
|
23
|
+
edit: "Edit layout", title: "Edit layout", coords: "Show coordinates", addWaypoint: "Add waypoint", removeWaypoint: "Remove waypoint",
|
|
24
|
+
joinTo: "Join to", join: "Join", unjoin: "Unjoin {0}", corridors: "Corridors", none: "Nothing selected: pick a seat, an item, the door or a waypoint.",
|
|
25
|
+
reset: "Reset to the pack's layout", save: "Save layout", cancel: "Cancel", saved: "Layout saved.", hint: "Drag, or use the arrow keys (Shift: by 10).",
|
|
26
|
+
seat: "Seat {0} in {1}", furniture: "Item {0}", night: "Item {0} (night)", door: "Door", waypoint: "Waypoint {0}", at: "{0} at {1}, {2}",
|
|
27
|
+
nightReset: "Back to the pack's night place",
|
|
28
|
+
changed: "Someone changed the offices meanwhile. Your layout is kept: Save again puts it on the current list.",
|
|
29
|
+
failed: "The layout was not saved ({0}).", rateLimited: "Too many saves. Try again in a minute.",
|
|
30
|
+
stale: "This office was removed or its style changed meanwhile: this layout can no longer be saved. Cancel closes it.",
|
|
31
|
+
},
|
|
32
|
+
"zh-TW": {
|
|
33
|
+
edit: "編輯配置", title: "編輯配置", coords: "顯示座標", addWaypoint: "新增路徑點", removeWaypoint: "移除路徑點",
|
|
34
|
+
joinTo: "連到", join: "連接", unjoin: "取消連接 {0}", corridors: "走道", none: "尚未選取:請選座位、家具、門或路徑點。",
|
|
35
|
+
reset: "還原成 pack 的配置", save: "儲存配置", cancel: "取消", saved: "配置已儲存。", hint: "拖曳,或用方向鍵移動(Shift:一次 10)。",
|
|
36
|
+
seat: "{1} 的座位 {0}", furniture: "家具 {0}", night: "家具 {0}(夜間)", door: "門", waypoint: "路徑點 {0}", at: "{0},位置 {1}, {2}",
|
|
37
|
+
nightReset: "回到 pack 的夜間位置",
|
|
38
|
+
changed: "有人在這期間修改了辦公室。你的配置已保留:再按一次儲存,會套用到目前的清單。",
|
|
39
|
+
failed: "配置沒有儲存({0})。", rateLimited: "儲存太頻繁,請一分鐘後再試。",
|
|
40
|
+
stale: "這個辦公室在這期間被移除,或它的風格已改變:這份配置無法再儲存。按取消關閉。",
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
const tl = (k, ...v) => t(`orgl.${k}`, ...v);
|
|
44
|
+
|
|
45
|
+
// ── The pure part ──
|
|
46
|
+
|
|
47
|
+
const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, Math.round(v)));
|
|
48
|
+
/** An item's night place (#2665): its own, or (in place) its day place; `own` whether it has one. */
|
|
49
|
+
const nightAt = (f) => (f.night.x !== null && f.night.x !== undefined && f.night.y !== null && f.night.y !== undefined ? { x: f.night.x, y: f.night.y, own: true } : { x: f.x, y: f.y, own: false });
|
|
50
|
+
/**
|
|
51
|
+
* Every handle on a pack: { key, kind, label, x, y } — seats by area, furniture, each item's night place (#2665: an
|
|
52
|
+
* item with a `night`; `day` its day place, `inPlace` while it sleeps there), the door, the waypoints.
|
|
53
|
+
*/
|
|
54
|
+
export function handlesOf(pack) {
|
|
55
|
+
const out = [];
|
|
56
|
+
for (const [name, a] of Object.entries(pack.areas)) a.seats.forEach((s, i) => out.push({ key: `seat:${name}:${i}`, kind: "seat", label: tl("seat", i + 1, name), x: s.x, y: s.y }));
|
|
57
|
+
for (const f of pack.furniture) out.push({ key: `furn:${f.id}`, kind: "furniture", label: tl("furniture", f.id), x: f.x, y: f.y });
|
|
58
|
+
for (const f of pack.furniture) {
|
|
59
|
+
if (!f.night) continue;
|
|
60
|
+
const n = nightAt(f);
|
|
61
|
+
out.push({ key: `night:${f.id}`, kind: "night", label: tl("night", f.id), x: n.x, y: n.y, day: { x: f.x, y: f.y }, inPlace: !n.own });
|
|
62
|
+
}
|
|
63
|
+
out.push({ key: "door", kind: "door", label: tl("door"), x: pack.door.x, y: pack.door.y });
|
|
64
|
+
for (const w of pack.waypoints) out.push({ key: `wp:${w.id}`, kind: "waypoint", label: tl("waypoint", w.id), x: w.x, y: w.y });
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
/** The pack with handle `key` at (x, y), rounded and kept in range: a seat inside its area, anything else on the canvas. */
|
|
68
|
+
export function moveHandle(pack, key, x, y) {
|
|
69
|
+
const [kind, a, b] = key.split(":");
|
|
70
|
+
const inCanvas = () => ({ x: clamp(x, 0, LOGICAL.w), y: clamp(y, 0, LOGICAL.h) });
|
|
71
|
+
if (kind === "seat") {
|
|
72
|
+
const area = own(pack.areas, a), i = Number(b);
|
|
73
|
+
if (!area || !area.seats[i]) return pack;
|
|
74
|
+
const r = area.rect, p = { x: clamp(x, Math.ceil(r.x), Math.floor(r.x + r.w)), y: clamp(y, Math.ceil(r.y), Math.floor(r.y + r.h)) };
|
|
75
|
+
return { ...pack, areas: { ...pack.areas, [a]: { ...area, seats: area.seats.map((s, j) => (j === i ? p : s)) } } };
|
|
76
|
+
}
|
|
77
|
+
if (kind === "furn") return { ...pack, furniture: pack.furniture.map((f) => (f.id === a ? { ...f, ...inCanvas() } : f)) };
|
|
78
|
+
if (kind === "night") return { ...pack, furniture: pack.furniture.map((f) => (f.id === a && f.night ? { ...f, night: { ...f.night, ...inCanvas() } } : f)) };
|
|
79
|
+
if (kind === "door") return { ...pack, door: inCanvas() };
|
|
80
|
+
if (kind === "wp") return { ...pack, waypoints: pack.waypoints.map((w) => (w.id === a ? { ...w, ...inCanvas() } : w)) };
|
|
81
|
+
return pack;
|
|
82
|
+
}
|
|
83
|
+
/** A new waypoint at (x, y), named wp-<n> (a name no other waypoint has); returns { pack, id }. */
|
|
84
|
+
export function addWaypoint(pack, x = LOGICAL.w / 2, y = LOGICAL.h / 2) {
|
|
85
|
+
const ids = new Set(pack.waypoints.map((w) => w.id));
|
|
86
|
+
let n = pack.waypoints.length + 1;
|
|
87
|
+
while (ids.has(`wp-${n}`)) n++;
|
|
88
|
+
const id = `wp-${n}`;
|
|
89
|
+
return { pack: { ...pack, waypoints: [...pack.waypoints, { id, x: clamp(x, 0, LOGICAL.w), y: clamp(y, 0, LOGICAL.h) }] }, id };
|
|
90
|
+
}
|
|
91
|
+
/** The pack without waypoint `id` and its corridors. */
|
|
92
|
+
export function removeWaypoint(pack, id) {
|
|
93
|
+
return { ...pack, waypoints: pack.waypoints.filter((w) => w.id !== id), edges: pack.edges.filter((e) => e[0] !== id && e[1] !== id) };
|
|
94
|
+
}
|
|
95
|
+
/** A corridor between `a` and `b` added, or removed when there is one (either way round). */
|
|
96
|
+
export function toggleEdge(pack, a, b) {
|
|
97
|
+
if (a === b || !pack.waypoints.some((w) => w.id === a) || !pack.waypoints.some((w) => w.id === b)) return pack;
|
|
98
|
+
const has = (e) => (e[0] === a && e[1] === b) || (e[0] === b && e[1] === a);
|
|
99
|
+
return { ...pack, edges: pack.edges.some(has) ? pack.edges.filter((e) => !has(e)) : [...pack.edges, [a, b]] };
|
|
100
|
+
}
|
|
101
|
+
/** #2665: item `id`'s night place back to the pack's (`base`): its own x/y, or in place again. */
|
|
102
|
+
export function resetNight(pack, base, id) {
|
|
103
|
+
const o = base.furniture.find((f) => f.id === id);
|
|
104
|
+
if (!o || !o.night) return pack;
|
|
105
|
+
return { ...pack, furniture: pack.furniture.map((f) => (f.id === id && f.night ? { ...f, night: { ...f.night, x: o.night.x, y: o.night.y } } : f)) };
|
|
106
|
+
}
|
|
107
|
+
/** What Save keeps: the overrides between the pack and the edited layout (null: the pack's own). */
|
|
108
|
+
export const layoutOverrides = (base, edited) => overridesBetween(base, edited);
|
|
109
|
+
|
|
110
|
+
// ── The overlay ──
|
|
111
|
+
|
|
112
|
+
const GRID = 80, LABEL = 160;
|
|
113
|
+
/**
|
|
114
|
+
* A night handle's moon (#2665): a crescent centred near (x, y). Night handles are drawn under the others: one in place
|
|
115
|
+
* is a wider ring round its item's square (the item stays grabbable), its moon at the ring's corner.
|
|
116
|
+
*/
|
|
117
|
+
const moonPath = (x, y) => `M ${x + 1} ${y - 5} A 5 5 0 1 0 ${x + 1} ${y + 5} A 4 4 0 1 1 ${x + 1} ${y - 5} Z`;
|
|
118
|
+
const KEY_STEP = { ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1] };
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The editor over the canvas: `base` the pack as uploaded; `start` the layout shown when it opens (the base with the
|
|
122
|
+
* office's overrides on it); `place(el)` puts the overlay over the scene wherever the view has it (#2540: panned and
|
|
123
|
+
* zoomed; the page sets its box through the CSSOM, never a style attribute: #1300) — a pointer is read back through
|
|
124
|
+
* the overlay's own box, so a drag lands on the logical point under it at any zoom; `onChange(edited)` as anything
|
|
125
|
+
* moves (the canvas redraws it); `onSave(overrides | null)` → { ok } or { ok: false, message }; `onClose()`. `stale` (a message): the
|
|
126
|
+
* office or its style changed underneath the session — no handles, nothing to save, only Cancel.
|
|
127
|
+
*/
|
|
128
|
+
export function LayoutEditor({ base, start, place = null, stale = "", dock = true, onChange, onSave, onClose }) {
|
|
129
|
+
const [edited, setEdited] = useState(start);
|
|
130
|
+
const [sel, setSel] = useState(null);
|
|
131
|
+
const [coords, setCoords] = useState(true);
|
|
132
|
+
const [busy, setBusy] = useState(false);
|
|
133
|
+
const [msg, setMsg] = useState("");
|
|
134
|
+
const [joinTo, setJoinTo] = useState("");
|
|
135
|
+
const svg = useRef(null), drag = useRef(null), overlay = useRef(null);
|
|
136
|
+
useLayoutEffect(() => { if (place) place(overlay.current); });
|
|
137
|
+
const change = (next) => { setEdited(next); onChange(next); };
|
|
138
|
+
useEffect(() => { onChange(start); }, []);
|
|
139
|
+
const handles = handlesOf(edited);
|
|
140
|
+
const selected = sel ? handles.find((h) => h.key === sel) || null : null;
|
|
141
|
+
const toLogical = (e) => {
|
|
142
|
+
const r = svg.current && svg.current.getBoundingClientRect ? svg.current.getBoundingClientRect() : null;
|
|
143
|
+
if (!r || !r.width || !r.height) return null;
|
|
144
|
+
return { x: ((e.clientX - r.left) / r.width) * LOGICAL.w, y: ((e.clientY - r.top) / r.height) * LOGICAL.h };
|
|
145
|
+
};
|
|
146
|
+
const down = (key) => (e) => {
|
|
147
|
+
if (busy) return;
|
|
148
|
+
setSel(key); drag.current = key;
|
|
149
|
+
try { e.currentTarget?.setPointerCapture?.(e.pointerId); } catch { /* not captured: moves still reach the svg */ }
|
|
150
|
+
};
|
|
151
|
+
const move = (e) => { if (!drag.current) return; const p = toLogical(e); if (p) change(moveHandle(edited, drag.current, p.x, p.y)); };
|
|
152
|
+
const up = () => { drag.current = null; };
|
|
153
|
+
const keyOn = (h) => (e) => {
|
|
154
|
+
if (busy) return;
|
|
155
|
+
const d = KEY_STEP[e.key];
|
|
156
|
+
if (d) { e.preventDefault(); const k = e.shiftKey ? 10 : 1; setSel(h.key); change(moveHandle(edited, h.key, h.x + d[0] * k, h.y + d[1] * k)); return; }
|
|
157
|
+
if ((e.key === "Delete" || e.key === "Backspace") && h.kind === "waypoint") { e.preventDefault(); change(removeWaypoint(edited, h.key.slice(3))); setSel(null); }
|
|
158
|
+
if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setSel(h.key); }
|
|
159
|
+
};
|
|
160
|
+
const add = () => { const at = selected ? { x: selected.x + 40, y: selected.y } : { x: LOGICAL.w / 2, y: LOGICAL.h / 2 }; const r = addWaypoint(edited, at.x, at.y); change(r.pack); setSel(`wp:${r.id}`); };
|
|
161
|
+
const wpSel = selected && selected.kind === "waypoint" ? selected.key.slice(3) : null;
|
|
162
|
+
const nightSel = selected && selected.kind === "night" ? selected.key.slice(6) : null;
|
|
163
|
+
const save = async () => {
|
|
164
|
+
if (busy) return;
|
|
165
|
+
setBusy(true); setMsg("");
|
|
166
|
+
try {
|
|
167
|
+
const r = await onSave(layoutOverrides(base, edited));
|
|
168
|
+
if (r && r.ok) { onClose(); return; }
|
|
169
|
+
setMsg(r && r.message ? r.message : tl("failed", "?"));
|
|
170
|
+
} finally { setBusy(false); }
|
|
171
|
+
};
|
|
172
|
+
const reset = () => { change(base); setSel(null); setMsg(""); };
|
|
173
|
+
const grid = [];
|
|
174
|
+
if (coords) {
|
|
175
|
+
for (let x = GRID; x < LOGICAL.w; x += GRID) grid.push(html`<line key=${`gx${x}`} class="ol-grid" x1=${x} y1="0" x2=${x} y2=${LOGICAL.h} />`);
|
|
176
|
+
for (let y = GRID; y < LOGICAL.h; y += GRID) grid.push(html`<line key=${`gy${y}`} class="ol-grid" x1="0" y1=${y} x2=${LOGICAL.w} y2=${y} />`);
|
|
177
|
+
for (let x = 0; x < LOGICAL.w; x += LABEL) grid.push(html`<text key=${`lx${x}`} class="ol-label" x=${x + 3} y="14">${x}</text>`);
|
|
178
|
+
for (let y = LABEL; y < LOGICAL.h; y += LABEL) grid.push(html`<text key=${`ly${y}`} class="ol-label" x="3" y=${y - 3}>${y}</text>`);
|
|
179
|
+
}
|
|
180
|
+
const at = new Map(edited.waypoints.map((w) => [w.id, w]));
|
|
181
|
+
const edgeKey = (e) => `${e[0]}\u0000${e[1]}`;
|
|
182
|
+
// #2585: the controls dock in the side panel on a wide desk (the canvas left whole for the handles), else they are
|
|
183
|
+
// the bar over the canvas as before. Esc (in the panel, or on the canvas's handles) is Cancel, never mid-save.
|
|
184
|
+
const esc = (e) => { if (e.key === "Escape" && !e.defaultPrevented && !busy) { e.preventDefault(); onClose(); } };
|
|
185
|
+
const dirty = useMemo(() => JSON.stringify(edited) !== JSON.stringify(start), [edited, start]);
|
|
186
|
+
const panel = (controls, actions, tail) => html`<${SidePanel} title=${tl("title")} dock=${dock} busy=${busy} dirty=${dirty} onClose=${onClose} actions=${actions}
|
|
187
|
+
fallback=${html`<div class="ol-bar">${controls}<span class="ol-actions">${actions}</span>${tail}</div>`}>
|
|
188
|
+
<div class="ol-side">${controls}${tail}</div>
|
|
189
|
+
</${SidePanel}>`;
|
|
190
|
+
if (stale) {
|
|
191
|
+
return html`<div class="ol" role="group" aria-label=${tl("title")} onKeyDown=${esc}>
|
|
192
|
+
${panel(null, html`<button type="button" class="btn btn-sm ol-cancel" onClick=${onClose} disabled=${busy}>${tl("cancel")}</button>`,
|
|
193
|
+
html`<p class="note ol-msg ol-stale" role="status">${stale}</p>`)}
|
|
194
|
+
</div>`;
|
|
195
|
+
}
|
|
196
|
+
const controls = html`<label class="check"><input type="checkbox" class="ol-coords" checked=${coords} onChange=${() => setCoords((v) => !v)} /><span>${tl("coords")}</span></label>
|
|
197
|
+
<button type="button" class="btn btn-sm ol-add" onClick=${add} disabled=${busy}>${tl("addWaypoint")}</button>
|
|
198
|
+
<span class="muted ol-sel" aria-live="polite">${selected ? tl("at", selected.label, selected.x, selected.y) : tl("none")}</span>
|
|
199
|
+
${nightSel ? html`<button type="button" class="btn btn-sm ol-night-reset" onClick=${() => change(resetNight(edited, base, nightSel))} disabled=${busy}>${tl("nightReset")}</button>` : null}
|
|
200
|
+
${wpSel ? html`<button type="button" class="btn btn-sm btn-danger ol-remove" onClick=${() => { change(removeWaypoint(edited, wpSel)); setSel(null); }} disabled=${busy}>${tl("removeWaypoint")}</button>
|
|
201
|
+
<label class="oo-pick"><span class="oo-pick-label">${tl("joinTo")}</span>
|
|
202
|
+
<select class="tb-select ol-join-to" value=${joinTo} onChange=${(e) => setJoinTo(e.target.value)}>
|
|
203
|
+
<option value="">—</option>
|
|
204
|
+
${edited.waypoints.filter((w) => w.id !== wpSel).map((w) => html`<option key=${w.id} value=${w.id}>${w.id}</option>`)}
|
|
205
|
+
</select></label>
|
|
206
|
+
<button type="button" class="btn btn-sm ol-join" disabled=${busy || !joinTo || joinTo === wpSel} onClick=${() => { change(toggleEdge(edited, wpSel, joinTo)); setJoinTo(""); }}>${tl("join")}</button>
|
|
207
|
+
<span class="ol-corridors">${edited.edges.filter((e) => e[0] === wpSel || e[1] === wpSel).map((e) => { const other = e[0] === wpSel ? e[1] : e[0];
|
|
208
|
+
return html`<button key=${edgeKey(e)} type="button" class="btn btn-sm ol-unjoin" data-other=${other} disabled=${busy} onClick=${() => change(toggleEdge(edited, wpSel, other))}>${tl("unjoin", other)}</button>`; })}</span>` : null}`;
|
|
209
|
+
const actions = html`<button type="button" class="btn btn-sm ol-reset" onClick=${reset} disabled=${busy}>${tl("reset")}</button>
|
|
210
|
+
<button type="button" class="btn btn-sm ol-cancel" onClick=${onClose} disabled=${busy}>${tl("cancel")}</button>
|
|
211
|
+
<button type="button" class="btn btn-sm btn-primary ol-save" onClick=${save} disabled=${busy}>${tl("save")}</button>`;
|
|
212
|
+
const tail = html`<span class="muted oo-note">${tl("hint")}</span>
|
|
213
|
+
${msg ? html`<p class="note ol-msg" role="status">${msg}</p>` : null}`;
|
|
214
|
+
return html`<div class="ol" role="group" aria-label=${tl("title")} onKeyDown=${esc}>
|
|
215
|
+
<div class="ol-overlay" ref=${overlay}>
|
|
216
|
+
<svg ref=${svg} class="ol-svg" viewBox=${`0 0 ${LOGICAL.w} ${LOGICAL.h}`} width="100%" height="100%"
|
|
217
|
+
onPointerMove=${move} onPointerUp=${up} onPointerCancel=${up} onPointerLeave=${up}>
|
|
218
|
+
${grid}
|
|
219
|
+
${Object.values(edited.areas).map((a) => html`<rect key=${`a:${a.name}`} class="ol-area" x=${a.rect.x} y=${a.rect.y} width=${a.rect.w} height=${a.rect.h} />`)}
|
|
220
|
+
${edited.edges.map((e) => { const p = at.get(e[0]), q = at.get(e[1]); return p && q ? html`<line key=${`e:${edgeKey(e)}`} class="ol-edge" x1=${p.x} y1=${p.y} x2=${q.x} y2=${q.y} />` : null; })}
|
|
221
|
+
${handles.filter((h) => h.kind === "night" && !h.inPlace).map((h) => html`<line key=${`nl:${h.key}`} class="ol-night-line" x1=${h.day.x} y1=${h.day.y} x2=${h.x} y2=${h.y} />`)}
|
|
222
|
+
${[...handles.filter((h) => h.kind === "night"), ...handles.filter((h) => h.kind !== "night")].map((h) => html`<g key=${h.key} class=${`ol-handle ol-${h.kind}${sel === h.key ? " on" : ""}`} data-handle=${h.key} tabindex="0" role="button"
|
|
223
|
+
aria-label=${tl("at", h.label, h.x, h.y)} aria-pressed=${sel === h.key ? "true" : "false"}
|
|
224
|
+
onPointerDown=${down(h.key)} onKeyDown=${keyOn(h)} onFocus=${() => setSel(h.key)}>
|
|
225
|
+
${h.kind === "door" ? html`<rect x=${h.x - 9} y=${h.y - 9} width="18" height="18" transform=${`rotate(45 ${h.x} ${h.y})`} />`
|
|
226
|
+
: h.kind === "furniture" ? html`<rect x=${h.x - 8} y=${h.y - 8} width="16" height="16" />`
|
|
227
|
+
: h.kind === "night" ? html`<circle cx=${h.x} cy=${h.y} r=${h.inPlace ? 15 : 8} /><path class="ol-moon" d=${h.inPlace ? moonPath(h.x + 10, h.y - 10) : moonPath(h.x, h.y)} />`
|
|
228
|
+
: html`<circle cx=${h.x} cy=${h.y} r=${h.kind === "seat" ? 9 : 7} />`}
|
|
229
|
+
${coords && sel === h.key ? html`<text class="ol-coord" x=${h.x + 12} y=${h.y - 10}>${h.x}, ${h.y}</text>` : null}
|
|
230
|
+
</g>`)}
|
|
231
|
+
</svg>
|
|
232
|
+
</div>
|
|
233
|
+
${panel(controls, actions, tail)}
|
|
234
|
+
</div>`;
|
|
235
|
+
}
|
|
@@ -6,14 +6,16 @@
|
|
|
6
6
|
// office the page makes itself (AUTO_ID, "the whole fleet", built from the teams as the canvas is), whose member
|
|
7
7
|
// filter is the team / people picker; any other office is the host's (GET /ui/offices, fleet-office-store.js),
|
|
8
8
|
// checked again here.
|
|
9
|
-
//
|
|
10
|
-
//
|
|
9
|
+
// The office editor (fleet-office-editor.js) adds, renames and deletes offices and picks who is in them; the layout
|
|
10
|
+
// editor (fleet-office-layout.js, #2513) moves things on a zones skin and keeps them as the office's `overrides`
|
|
11
|
+
// (fleet-office-pack.js) — both write the same model through the store's saveOffices().
|
|
11
12
|
// - The auto layout: a lobby (General and the instances in no team), then a room per team in fleet.yaml's order, in a
|
|
12
13
|
// grid that wraps at the stage's width (a phone gets narrower rooms); a desk per member; a corridor graph of waypoints
|
|
13
14
|
// (each desk's aisle, a spine down each room's left padding, its door, a corridor under each row of rooms and one down
|
|
14
15
|
// the left margin) that traffic walks along (bfsPath).
|
|
15
16
|
// - Poses come from the org chart's states (fleet-org.js ORG_STATES / orgState); nothing here invents a status.
|
|
16
17
|
import { hue } from "./fleet-traffic.js";
|
|
18
|
+
import { checkLayoutOverrides } from "./fleet-office-pack.js";
|
|
17
19
|
|
|
18
20
|
// ── Poses: the org chart's states, as a character at a desk ──
|
|
19
21
|
|
|
@@ -115,7 +117,8 @@ export function officeOk(x) {
|
|
|
115
117
|
if (!x || typeof x !== "object" || typeof x.id !== "string" || !ID.test(x.id) || x.id === AUTO_ID) return null;
|
|
116
118
|
const name = typeof x.name === "string" && x.name.trim() ? x.name.trim().slice(0, 60) : x.id;
|
|
117
119
|
const skin = typeof x.skin === "string" && ID.test(x.skin) ? x.skin : "pixel";
|
|
118
|
-
|
|
120
|
+
const ov = x.overrides === undefined ? null : checkLayoutOverrides(x.overrides);
|
|
121
|
+
return { id: x.id, name, skin, members: nameList(x.members), layout: layoutOk(x.layout), ...(ov && ov.ok ? { overrides: ov.overrides } : {}) };
|
|
119
122
|
}
|
|
120
123
|
/** A stored list of offices: the valid ones, ids unique, at most MAX_OFFICES. */
|
|
121
124
|
export function officesOk(list) {
|
|
@@ -196,7 +199,9 @@ export function autoLayout(rooms, { width = 960 } = {}) {
|
|
|
196
199
|
}
|
|
197
200
|
});
|
|
198
201
|
const right = out.length ? Math.max(...out.map((r) => r.x + r.w)) : 0, bottom = out.length ? Math.max(...out.map((r) => r.y + r.h)) : 0;
|
|
199
|
-
|
|
202
|
+
// As wide as its rooms (rows wrap at `width`): a scene narrower than the stage is centred in it (clampPan), never
|
|
203
|
+
// left at the stage's left edge with the rest of the stage empty (#2585 B).
|
|
204
|
+
return { rooms: out, waypoints, edges, width: out.length ? right + M : width, height: bottom + GAP / 2 + M };
|
|
200
205
|
}
|
|
201
206
|
|
|
202
207
|
/**
|
|
@@ -315,7 +320,8 @@ const MAX_SEEN = 2000;
|
|
|
315
320
|
* The items in the air. `sync(items, now, still)` takes fleet-traffic.js's store items: each new one (a message, a
|
|
316
321
|
* delegation's work, its report coming back) starts one trip from its sender's desk to its receiver's; a delegation with
|
|
317
322
|
* no report (stale) and anything seen before start none; `still` (reduced motion) starts none. `frame(now, L)` the
|
|
318
|
-
* trips under way on layout L: { id, hue, kind, x, y, t }
|
|
323
|
+
* trips under way on layout L: { id, hue, kind, from, x, y, t } (from: who sent it — a pack can draw it as theirs),
|
|
324
|
+
* finished ones dropped; a trip whose desks are not drawn goes.
|
|
319
325
|
*/
|
|
320
326
|
export function createFlights({ max = FLIGHT.MAX } = {}) {
|
|
321
327
|
const seen = new Set();
|
|
@@ -341,7 +347,7 @@ export function createFlights({ max = FLIGHT.MAX } = {}) {
|
|
|
341
347
|
if (!f.pts) return false;
|
|
342
348
|
const t = (now - f.start) / f.ms;
|
|
343
349
|
if (t >= 1) return false;
|
|
344
|
-
if (t >= 0) { const p = along(f.pts, t); out.push({ id: f.id, hue: f.hue, kind: f.kind, x: p.x, y: p.y, t }); }
|
|
350
|
+
if (t >= 0) { const p = along(f.pts, t); out.push({ id: f.id, hue: f.hue, kind: f.kind, from: f.from, x: p.x, y: p.y, t }); }
|
|
345
351
|
return true;
|
|
346
352
|
});
|
|
347
353
|
return out;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// #2554: the Office view's "All offices" — every stored office as a tile on one canvas, side by side, each drawn exactly
|
|
2
|
+
// as its own view would be (its style, its members, seats, poses, bubbles, walking), scaled into its tile. This module
|
|
3
|
+
// is the pure part (fleet-office.js draws it): who is in no office (the lobby tile), where the tiles go, what a point
|
|
4
|
+
// on the overview hits, which tiles are on screen, and the traffic that crosses between tiles.
|
|
5
|
+
//
|
|
6
|
+
// - Shown when at least two offices are stored (overviewOk); an id no stored office can have (ALL_ID: '*' is not an
|
|
7
|
+
// office id's character), kept per device as any office pick is.
|
|
8
|
+
// - The tiles: each as large as its office's drawn content — a zones scene its 1280×720, a desks office its rooms'
|
|
9
|
+
// bounds (cropToRooms: an office of three is small) — scaled down to at most CELL_W × CELL_H, never up; a LABEL band
|
|
10
|
+
// above each (the page's label sits there), GAP apart, M from the edge. Shelf packing in the stored order (the lobby
|
|
11
|
+
// last): a row takes tiles while they fit a row width, each row as tall as its tallest tile and centred across. The
|
|
12
|
+
// row width: whichever (of the widths a run of tiles makes) lets the whole overview show largest in the panel (Fit's
|
|
13
|
+
// contain); a tie keeps the narrower. The same input always gives the same picture.
|
|
14
|
+
// - Traffic between people in different tiles is a straight flight between their seats in overview coordinates
|
|
15
|
+
// (createCrossFlights); between two people in the same tile it is that tile's own (createFlights, along its corridors).
|
|
16
|
+
import { FLIGHT, along, desksOf, membersOf, pathLength } from "./fleet-office-model.js";
|
|
17
|
+
import { hue } from "./fleet-traffic.js";
|
|
18
|
+
|
|
19
|
+
/** The switcher's "All offices" value: '*' is never in a stored office's id (fleet-office-model.js officeOk). */
|
|
20
|
+
export const ALL_ID = "*all";
|
|
21
|
+
/** The lobby tile's key: never an office's id. */
|
|
22
|
+
export const LOBBY_TILE = "\u0000lobby";
|
|
23
|
+
/** The overview's geometry, in logical units. */
|
|
24
|
+
export const OV = { CELL_W: 1280, CELL_H: 720, LABEL: 56, GAP: 24, M: 24 };
|
|
25
|
+
|
|
26
|
+
/** Whether "All offices" is offered: at least two stored offices. */
|
|
27
|
+
export const overviewOk = (stored) => Array.isArray(stored) && stored.length >= 2;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The people in no stored office, in the org chart's order (General, the teams, the rest): the lobby tile's. An office
|
|
31
|
+
* with no members holds everyone (fleet-office-model.js membersOf), so then nobody is left for the lobby.
|
|
32
|
+
*/
|
|
33
|
+
export function lobbyNodes(stored, view) {
|
|
34
|
+
const placed = new Set((stored || []).flatMap((o) => membersOf(o, view).map((n) => n.name)));
|
|
35
|
+
const out = new Map();
|
|
36
|
+
for (const n of [...view.top, ...view.teams.flatMap((tm) => tm.nodes), ...view.rest.nodes]) if (!placed.has(n.name) && !out.has(n.name)) out.set(n.name, n);
|
|
37
|
+
return [...out.values()];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** A scene's scale in its tile: its own size, scaled down to fit a CELL_W × CELL_H cell (never up). */
|
|
41
|
+
export const tileScale = (w, h) => Math.min(1, OV.CELL_W / Math.max(1, w), OV.CELL_H / Math.max(1, h));
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A desks layout cropped to its rooms: everything moved so the rooms' bounds start at (0, 0), its size theirs (the
|
|
45
|
+
* corridors outside the rooms and the margins are not part of a tile). Unchanged when it has no rooms.
|
|
46
|
+
*/
|
|
47
|
+
export function cropToRooms(L) {
|
|
48
|
+
if (!L.rooms.length) return L;
|
|
49
|
+
const x0 = Math.min(...L.rooms.map((r) => r.x)), y0 = Math.min(...L.rooms.map((r) => r.y));
|
|
50
|
+
const x1 = Math.max(...L.rooms.map((r) => r.x + r.w)), y1 = Math.max(...L.rooms.map((r) => r.y + r.h));
|
|
51
|
+
const mv = (p) => ({ ...p, x: p.x - x0, y: p.y - y0 });
|
|
52
|
+
return { ...L, rooms: L.rooms.map((r) => ({ ...mv(r), desks: r.desks.map((d) => ({ ...mv(d), seat: mv(d.seat) })) })), waypoints: (L.waypoints || []).map(mv),
|
|
53
|
+
width: x1 - x0, height: y1 - y0 };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The tiles of `scenes` ([{ key, w, h }], in order) on shelves at most `rowW` wide (a tile wider than that has a row of
|
|
58
|
+
* its own): each { key, x, y, w, h, s (its scale), label: { x, y, w, h } (the band above it) }; and the overview's size.
|
|
59
|
+
* Each row is centred across the widest; its tiles stand at its top, under their labels.
|
|
60
|
+
*/
|
|
61
|
+
export function arrangeTiles(scenes, rowW) {
|
|
62
|
+
const { LABEL, GAP, M } = OV;
|
|
63
|
+
const rows = [];
|
|
64
|
+
let row = null;
|
|
65
|
+
for (const sc of scenes) {
|
|
66
|
+
const s = tileScale(sc.w, sc.h), it = { key: sc.key, s, w: Math.max(1, sc.w) * s, h: Math.max(1, sc.h) * s };
|
|
67
|
+
if (!row || row.w + GAP + it.w > rowW + 1e-6) { row = { items: [], w: -GAP, h: 0 }; rows.push(row); }
|
|
68
|
+
row.items.push(it); row.w += GAP + it.w; row.h = Math.max(row.h, it.h);
|
|
69
|
+
}
|
|
70
|
+
const inner = rows.length ? Math.max(...rows.map((r) => r.w)) : 0, tiles = [];
|
|
71
|
+
let y = M;
|
|
72
|
+
for (const r of rows) {
|
|
73
|
+
let x = M + (inner - r.w) / 2;
|
|
74
|
+
for (const it of r.items) {
|
|
75
|
+
tiles.push({ key: it.key, x, y: y + LABEL, w: it.w, h: it.h, s: it.s, label: { x, y, w: it.w, h: LABEL } });
|
|
76
|
+
x += it.w + GAP;
|
|
77
|
+
}
|
|
78
|
+
y += LABEL + r.h + GAP;
|
|
79
|
+
}
|
|
80
|
+
return { rows: rows.length, rowW, tiles, width: 2 * M + inner, height: rows.length ? y - GAP + M : 2 * M };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The tiles packed for a `size` panel: of the row widths a run of consecutive tiles makes, the one whose overview shows
|
|
85
|
+
* largest there (min of the two fits); a tie keeps the narrower.
|
|
86
|
+
*/
|
|
87
|
+
export function tileLayout(scenes, size) {
|
|
88
|
+
const ws = scenes.map((sc) => Math.max(1, sc.w) * tileScale(sc.w, sc.h)), cands = new Set();
|
|
89
|
+
for (let i = 0; i < ws.length; i++) { let run = -OV.GAP; for (let j = i; j < ws.length; j++) { run += OV.GAP + ws[j]; cands.add(run); } }
|
|
90
|
+
let best = arrangeTiles(scenes, 0), bestK = -1;
|
|
91
|
+
for (const rowW of [...cands].sort((a, b) => a - b)) {
|
|
92
|
+
const a = arrangeTiles(scenes, rowW), k = Math.min(size.w / a.width, size.h / a.height);
|
|
93
|
+
if (k > bestK + 1e-9) { best = a; bestK = k; }
|
|
94
|
+
}
|
|
95
|
+
return best;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The tile whose scene holds the overview point (x, y), or null. */
|
|
99
|
+
export const tileAt = (lay, x, y) => lay.tiles.find((t) => x >= t.x && x < t.x + t.w && y >= t.y && y < t.y + t.h) || null;
|
|
100
|
+
/** The tile whose label band holds the overview point, or null. */
|
|
101
|
+
export const labelAt = (lay, x, y) => lay.tiles.find((t) => x >= t.label.x && x < t.label.x + t.label.w && y >= t.label.y && y < t.label.y + t.label.h) || null;
|
|
102
|
+
/** Overview → the tile's own scene units, and back; a box in the scene → the overview. */
|
|
103
|
+
export const inTile = (t, x, y) => ({ x: (x - t.x) / t.s, y: (y - t.y) / t.s });
|
|
104
|
+
export const fromTile = (t, p) => ({ x: t.x + p.x * t.s, y: t.y + p.y * t.s });
|
|
105
|
+
export const tileBox = (t, b) => ({ x: t.x + b.x * t.s, y: t.y + b.y * t.s, w: b.w * t.s, h: b.h * t.s });
|
|
106
|
+
/** The tiles a view ({ k, x, y }) shows any of in a `size` viewport: the rest are not drawn. */
|
|
107
|
+
export function visibleTiles(lay, v, size) {
|
|
108
|
+
return lay.tiles.filter((t) => {
|
|
109
|
+
const x = v.x + t.x * v.k, y = v.y + t.y * v.k;
|
|
110
|
+
return x < size.w && x + t.w * v.k > 0 && y < size.h && y + t.h * v.k > 0;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* A straight flight between two people in different tiles: [the sender's seat, the receiver's] in overview units (the
|
|
116
|
+
* first tile each is in), or null — when either is drawn nowhere, or some tile holds both (that tile carries it along
|
|
117
|
+
* its corridors). `parts`: [{ tile, L }] in the tiles' order.
|
|
118
|
+
*/
|
|
119
|
+
export function crossPath(parts, from, to) {
|
|
120
|
+
let a = null, z = null;
|
|
121
|
+
for (const p of parts) {
|
|
122
|
+
const ds = desksOf(p.L), da = ds.find((d) => d.node.name === from), dz = ds.find((d) => d.node.name === to);
|
|
123
|
+
if (da && dz) return null;
|
|
124
|
+
if (da && !a) a = fromTile(p.tile, da.seat);
|
|
125
|
+
if (dz && !z) z = fromTile(p.tile, dz.seat);
|
|
126
|
+
}
|
|
127
|
+
return a && z ? [a, z] : null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const MAX_SEEN = 2000;
|
|
131
|
+
/**
|
|
132
|
+
* The items in the air between tiles — fleet-office-model.js createFlights's lifecycle (each new item once; none for a
|
|
133
|
+
* stale delegation or with reduced motion), on crossPath's straight line: `frame(now, parts)` → { id, hue, kind, from, x, y,
|
|
134
|
+
* t } in overview units; a trip with no crossing (both in one tile, or either gone) is dropped.
|
|
135
|
+
*/
|
|
136
|
+
export function createCrossFlights({ max = FLIGHT.MAX } = {}) {
|
|
137
|
+
const seen = new Set();
|
|
138
|
+
let trips = [];
|
|
139
|
+
return {
|
|
140
|
+
sync(items, now, still = false) {
|
|
141
|
+
let k = 0, started = 0;
|
|
142
|
+
for (const it of items || []) {
|
|
143
|
+
if (seen.has(it.id)) continue;
|
|
144
|
+
seen.add(it.id);
|
|
145
|
+
if (seen.size > MAX_SEEN) seen.delete(seen.values().next().value);
|
|
146
|
+
if (still || it.phase === "stale") continue;
|
|
147
|
+
trips.push({ id: it.id, from: it.from, to: it.to, kind: it.kind, hue: hue(it.kind), start: now + k++ * FLIGHT.STAGGER_MS, parts: null, pts: null, ms: 0 });
|
|
148
|
+
started++;
|
|
149
|
+
}
|
|
150
|
+
if (trips.length > max) trips = trips.slice(trips.length - max);
|
|
151
|
+
return started;
|
|
152
|
+
},
|
|
153
|
+
frame(now, parts) {
|
|
154
|
+
const out = [];
|
|
155
|
+
trips = trips.filter((f) => {
|
|
156
|
+
if (f.parts !== parts) { f.parts = parts; f.pts = crossPath(parts, f.from, f.to); f.ms = f.pts ? Math.min(FLIGHT.MAX_MS, Math.max(FLIGHT.MIN_MS, pathLength(f.pts) / FLIGHT.SPEED)) : 0; }
|
|
157
|
+
if (!f.pts) return false;
|
|
158
|
+
const t = (now - f.start) / f.ms;
|
|
159
|
+
if (t >= 1) return false;
|
|
160
|
+
if (t >= 0) { const p = along(f.pts, t); out.push({ id: f.id, hue: f.hue, kind: f.kind, from: f.from, x: p.x, y: p.y, t }); }
|
|
161
|
+
return true;
|
|
162
|
+
});
|
|
163
|
+
return out;
|
|
164
|
+
},
|
|
165
|
+
size: () => trips.length,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Types for fleet-office-pack.js: the page's module, which the host imports too (web-office-skins.ts,
|
|
2
|
+
// web-office-store.ts), so both check a skin pack and an office's layout overrides by the same rules.
|
|
3
|
+
/** `dw` × `dh`: its displayed size in logical units (w·scale × h·scale unless the manifest says otherwise). */
|
|
4
|
+
export interface PackStrip { src: string; frames: number; w: number; h: number; fps: number; ax: number; ay: number; dw: number; dh: number }
|
|
5
|
+
export interface PackPoint { x: number; y: number }
|
|
6
|
+
export interface PackArea { name: string; rect: { x: number; y: number; w: number; h: number }; seats: PackPoint[] }
|
|
7
|
+
/** `label` (#2574): its name tag, or null. */
|
|
8
|
+
export interface PackFurniture { id: string; sprite: PackStrip; x: number; y: number; depth: number | "y"; activeWhen: string[] | null; activeSprite: PackStrip | null; label: string | null }
|
|
9
|
+
export interface CheckedPack {
|
|
10
|
+
format: 1; id: string; scale: number; background?: string; foreground?: string; matte?: string;
|
|
11
|
+
areas: Record<string, PackArea>; stateZones: Record<string, string> | null; door: PackPoint;
|
|
12
|
+
waypoints: Array<{ id: string; x: number; y: number }>; edges: Array<[string, string]>;
|
|
13
|
+
furniture: PackFurniture[]; characters: Record<string, Record<string, PackStrip>>; byInstance: Record<string, string>;
|
|
14
|
+
props: Record<string, PackStrip>; bubbles: Record<string, Record<string, string[]>>;
|
|
15
|
+
room: { floor?: string; wall?: string }; desk: { back?: string; front?: string; lit?: string };
|
|
16
|
+
}
|
|
17
|
+
export interface LayoutOverrides {
|
|
18
|
+
pack: string;
|
|
19
|
+
seats?: Record<string, Array<[number, number]>>;
|
|
20
|
+
furniture?: Record<string, [number, number]>;
|
|
21
|
+
/** #2665: an item's night place (an item with a `night` only). */
|
|
22
|
+
furnitureNight?: Record<string, [number, number]>;
|
|
23
|
+
door?: [number, number];
|
|
24
|
+
waypoints?: Array<{ id: string; x: number; y: number }>;
|
|
25
|
+
edges?: Array<[string, string]>;
|
|
26
|
+
}
|
|
27
|
+
export const LOGICAL: { w: number; h: number };
|
|
28
|
+
export const CHAR_DEPTH: number;
|
|
29
|
+
export const PACK_STATES: string[];
|
|
30
|
+
export function own<T = unknown>(o: unknown, k: unknown): T | undefined;
|
|
31
|
+
export function inherited(k: unknown): boolean;
|
|
32
|
+
export function matteOk(v: unknown): v is string;
|
|
33
|
+
export function checkStrip(v: unknown, at?: string, scale?: number): PackStrip;
|
|
34
|
+
export function checkSkinPack(x: unknown, opts?: { kept?: boolean }): { ok: true; pack: CheckedPack } | { ok: false; error: string };
|
|
35
|
+
export function skinPackOk(x: unknown): CheckedPack | null;
|
|
36
|
+
export function packImages(pack: CheckedPack): string[];
|
|
37
|
+
export function packStrips(pack: CheckedPack): Array<{ at: string; strip: PackStrip }>;
|
|
38
|
+
export function checkLayoutOverrides(x: unknown): { ok: true; overrides: LayoutOverrides } | { ok: false; error: string };
|
|
39
|
+
export function applyOverrides(pack: CheckedPack, ov: LayoutOverrides | null | undefined): { ok: true; pack: CheckedPack } | { ok: false; error: string };
|
|
40
|
+
export function fitNight<T extends LayoutOverrides | null | undefined>(pack: CheckedPack, ov: T): T;
|
|
41
|
+
export function overridesBetween(pack: CheckedPack, edited: CheckedPack): LayoutOverrides | null;
|
|
42
|
+
export function poseStrip(set: Record<string, PackStrip> | null | undefined, pose: string, heading?: { dx: number; dy: number } | null): { strip: PackStrip; mirror: boolean } | null;
|
|
43
|
+
export function walkStrip(f: { walk?: Record<string, PackStrip> | null; walking?: boolean; heading?: { dx: number; dy: number } | null } | null | undefined): { strip: PackStrip; mirror: boolean } | null;
|
|
44
|
+
export function stripBox(st: PackStrip, x: number, y: number, mirror?: boolean): { x: number; y: number; w: number; h: number };
|