@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
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// The Office view's art (fleet-office.js draws with it on a <canvas>). A skin draws a layout that fleet-office-model.js
|
|
2
|
-
// (desks) or fleet-office-zones.js (zones) made.
|
|
3
|
-
// pixel people, desks, lamps
|
|
4
|
-
//
|
|
5
|
-
//
|
|
2
|
+
// (desks) or fleet-office-zones.js (zones) made. Three skins are built in. Two draw everything procedurally — little
|
|
3
|
+
// pixel people, desks, lamps: "pixel" (the desks layout: a room per team, a desk per instance) and "studio" (the zones
|
|
4
|
+
// layout: work desks, a lounge, an alert corner and a nap corner; people walk to the area for their state). The third,
|
|
5
|
+
// "cozy" (#2631), is the zones layout on one picture shipped with the page (a warm pixel-art office), the same built-in
|
|
6
|
+
// people in it. Every drawing call goes through the OfficeSkin interface below, so a skin pack (pictures from an artist,
|
|
6
7
|
// format v1 in fleet-office-pack.js) can replace any part; a part a pack does not supply falls back to the built-in
|
|
7
|
-
// drawing.
|
|
8
|
+
// drawing. An admin uploads a pack (#2513, the host keeps it: src/web-office-skins.ts); loadPackSkin() reads it back —
|
|
9
|
+
// its manifest checked here again (checkSkinPack), its pictures by their content ids — and spriteSkin() draws it.
|
|
8
10
|
//
|
|
9
11
|
// ── The interface ──
|
|
10
12
|
//
|
|
@@ -22,16 +24,26 @@
|
|
|
22
24
|
// @property {(ctx, desk, look, env) => void} drawDeskFront desks: in front — the desk's front, its sign, folders, name tag
|
|
23
25
|
// @property {(ctx, L, env) => void} [drawBackground] zones: the whole 1280×720 background, areas and their names
|
|
24
26
|
// @property {(ctx, item, active, ms, env) => void} [drawFurniture] zones: one furniture item (active: activeWhen holds)
|
|
27
|
+
// @property {(ctx, L, env) => void} [drawForeground] zones: a pack's foreground over the whole 1280×720 scene, above
|
|
28
|
+
// the people, furniture and items in the air, below the crowd counts, name tags and bubbles
|
|
25
29
|
// @property {(ctx, x, y, look, env) => void} [drawTag] zones: a name tag under feet at (x, y)
|
|
30
|
+
// @property {(ctx, area, n, env) => void} [drawCrowd] zones: an area's "+n" (who found no spot in it)
|
|
26
31
|
// @property {(ctx, desk, look, ms, env) => void} drawCharacter the occupant in its pose at time `ms` (0: still); in
|
|
27
|
-
// zones `desk` is the character's box at its current position (its feet at desk.seat)
|
|
32
|
+
// zones `desk` is the character's box at its current position (its feet at desk.seat); walking, `look.heading` is
|
|
33
|
+
// the current step's { dx, dy }
|
|
34
|
+
// @property {(look, at) => {x, y, w, h}} [charBox] zones: the box drawCharacter covers with the feet at `at`
|
|
35
|
+
// ({ x, y, walking, heading }): what hover and click hit (fleet-office-zones.js personAt)
|
|
28
36
|
// @property {(ctx, prop, ms, env) => void} drawProp a traffic item in the air: { kind, hue, x, y, t }
|
|
29
37
|
// @property {(ctx, x, y, text, env, lift?) => void} drawBubble a speech bubble whose tail points at (x, y), its box `lift` px higher
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
38
|
+
// @property {(env) => string} matte #2540: the colour around the scene (the Office fills the panel; the scene is 16:9)
|
|
39
|
+
// @property {object|null} [backdrop] a pack's loaded background picture (the page samples its edges for the matte)
|
|
40
|
+
// The desks layout's back and front layers change only when the fleet (or the zoom) does: the renderer keeps them as
|
|
41
|
+
// pictures and draws the characters, props and bubbles each frame. In zones the background is kept; furniture, people (walking
|
|
42
|
+
// along the pack's waypoints), tags, props and bubbles are drawn each frame, furniture by depth around the people (depth "y":
|
|
43
|
+
// among them by base y), then the foreground, then the crowd counts, tags and bubbles.
|
|
44
|
+
import { t } from "/assets/app-i18n.js";
|
|
33
45
|
import { frameAt, nameHash } from "./fleet-office-model.js";
|
|
34
|
-
import { CHAR_DEPTH, LOGICAL } from "./fleet-office-pack.js";
|
|
46
|
+
import { CHAR_DEPTH, LOGICAL, checkSkinPack, own, packImages, poseStrip, stripBox, walkStrip } from "./fleet-office-pack.js";
|
|
35
47
|
|
|
36
48
|
export { nameHash };
|
|
37
49
|
|
|
@@ -212,14 +224,16 @@ function drawDeskFront(ctx, d, look, env) {
|
|
|
212
224
|
ctx.fillStyle = pal.text2; ctx.font = "600 8px system-ui, sans-serif"; ctx.textAlign = "center"; ctx.textBaseline = "middle";
|
|
213
225
|
ctx.fillText(fitText(ctx, env.labels.closed, 32), d.x + 48, d.y + 63);
|
|
214
226
|
}
|
|
215
|
-
// The name tag.
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
227
|
+
// The name tag (#2554: none when `notag` — a tile too small to read it; the page draws it again on hover).
|
|
228
|
+
if (!look.notag) {
|
|
229
|
+
ctx.fillStyle = pal.surface;
|
|
230
|
+
ctx.fillRect(d.x + 6, d.y + 78, d.w - 12, 16);
|
|
231
|
+
ctx.strokeStyle = look.match ? pal.focus : pal.border;
|
|
232
|
+
ctx.lineWidth = look.match ? 2 : 1;
|
|
233
|
+
ctx.strokeRect(d.x + 6.5, d.y + 78.5, d.w - 13, 15);
|
|
234
|
+
ctx.fillStyle = pal.text; ctx.font = "600 11px system-ui, sans-serif"; ctx.textAlign = "center"; ctx.textBaseline = "middle";
|
|
235
|
+
ctx.fillText(fitText(ctx, look.label, d.w - 18), d.x + d.w / 2, d.y + 86.5);
|
|
236
|
+
}
|
|
223
237
|
ctx.restore();
|
|
224
238
|
if (look.match) { ctx.save(); ctx.strokeStyle = pal.focus; ctx.lineWidth = 2; ctx.strokeRect(d.x + 2, d.y + 2, d.w - 4, d.h - 6); ctx.restore(); }
|
|
225
239
|
}
|
|
@@ -271,21 +285,58 @@ function drawBubble(ctx, x, y, text, env, lift = 0) {
|
|
|
271
285
|
ctx.restore();
|
|
272
286
|
}
|
|
273
287
|
|
|
288
|
+
/** A zones name tag's widest text (logical units): longer names are cut with an ellipsis. */
|
|
289
|
+
export const TAG_TEXT_W = 84;
|
|
290
|
+
const TAG_FONT = "600 10px system-ui, sans-serif";
|
|
291
|
+
/** The box a name tag under feet at (x, y) takes (what drawTag draws; bubbles keep clear of it, #2513). */
|
|
292
|
+
export function tagBox(ctx, x, y, label) {
|
|
293
|
+
ctx.save();
|
|
294
|
+
ctx.font = TAG_FONT;
|
|
295
|
+
const t = fitText(ctx, label, TAG_TEXT_W), w = Math.ceil((ctx.measureText ? ctx.measureText(t).width : t.length * 6) + 10);
|
|
296
|
+
ctx.restore();
|
|
297
|
+
return { x: Math.round(x - w / 2), y: y + 3, w, h: 14, text: t };
|
|
298
|
+
}
|
|
274
299
|
/** Zones: a name tag under someone's feet at (x, y). */
|
|
275
300
|
function drawTag(ctx, x, y, look, env) {
|
|
276
301
|
const { pal } = env;
|
|
277
302
|
ctx.save();
|
|
278
303
|
if (look.dim) ctx.globalAlpha = 0.35;
|
|
279
|
-
ctx.font =
|
|
280
|
-
const t = fitText(ctx, look.label,
|
|
304
|
+
ctx.font = TAG_FONT;
|
|
305
|
+
const t = fitText(ctx, look.label, TAG_TEXT_W), w = Math.ceil((ctx.measureText ? ctx.measureText(t).width : t.length * 6) + 10);
|
|
281
306
|
ctx.fillStyle = pal.surface; ctx.fillRect(Math.round(x - w / 2), y + 3, w, 14);
|
|
282
307
|
ctx.strokeStyle = look.match ? pal.focus : pal.border; ctx.lineWidth = look.match ? 2 : 1; ctx.strokeRect(Math.round(x - w / 2) + 0.5, y + 3.5, w - 1, 13);
|
|
283
308
|
ctx.fillStyle = pal.text; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(t, x, y + 10.5);
|
|
284
309
|
ctx.restore();
|
|
285
310
|
}
|
|
286
311
|
|
|
312
|
+
/**
|
|
313
|
+
* Zones: how many more want an area than it has room for — a chip in its bottom-right corner, inside its rect, so it
|
|
314
|
+
* never covers the area's name (#2513: "+42" over the nap corner's title).
|
|
315
|
+
*/
|
|
316
|
+
export function crowdChip(ctx, area, n) {
|
|
317
|
+
ctx.save();
|
|
318
|
+
ctx.font = "700 12px system-ui, sans-serif";
|
|
319
|
+
const text = `+${n}`, w = Math.ceil((ctx.measureText ? ctx.measureText(text).width : text.length * 7) + 12), h = 18, r = area.rect;
|
|
320
|
+
ctx.restore();
|
|
321
|
+
return { x: Math.round(r.x + r.w - w - 6), y: Math.round(r.y + r.h - h - 6), w, h, text };
|
|
322
|
+
}
|
|
323
|
+
function drawCrowd(ctx, area, n, env) {
|
|
324
|
+
if (!n) return;
|
|
325
|
+
const { pal } = env, b = crowdChip(ctx, area, n);
|
|
326
|
+
ctx.save();
|
|
327
|
+
ctx.fillStyle = pal.surface; ctx.fillRect(b.x, b.y, b.w, b.h);
|
|
328
|
+
ctx.strokeStyle = pal.border; ctx.lineWidth = 1; ctx.strokeRect(b.x + 0.5, b.y + 0.5, b.w - 1, b.h - 1);
|
|
329
|
+
ctx.fillStyle = pal.text; ctx.font = "700 12px system-ui, sans-serif"; ctx.textAlign = "center"; ctx.textBaseline = "middle";
|
|
330
|
+
ctx.fillText(b.text, b.x + b.w / 2, b.y + b.h / 2 + 0.5);
|
|
331
|
+
ctx.restore();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** The built-in character in zones covers this box around its feet (as zonesLayout's desk box). */
|
|
335
|
+
const charBox = (look, at) => ({ x: at.x - 24, y: at.y - 52, w: 48, h: 66 });
|
|
336
|
+
/** #2540: Desks' matte (around the scene): the page's background token, which the desks' floor is too (Studio has its own). */
|
|
337
|
+
const matte = (env) => env.pal.bg;
|
|
287
338
|
/** @type {OfficeSkin} */
|
|
288
|
-
export const pixelSkin = { id: "pixel", drawFloor, drawRoom, drawDeskBack, drawCharacter, drawDeskFront, drawProp, drawBubble, drawTag };
|
|
339
|
+
export const pixelSkin = { id: "pixel", drawFloor, drawRoom, drawDeskBack, drawCharacter, drawDeskFront, drawProp, drawBubble, drawTag, drawCrowd, charBox, matte };
|
|
289
340
|
|
|
290
341
|
// ── The built-in studio: the zones layout, drawn procedurally ──
|
|
291
342
|
|
|
@@ -327,7 +378,7 @@ const STUDIO_FURNITURE = [
|
|
|
327
378
|
].sort((a, b) => a.depth - b.depth);
|
|
328
379
|
/** The studio's pack: the same shape as a checked one (fleet-office-pack.js), furniture by `shape` instead of sprites. */
|
|
329
380
|
export const STUDIO_PACK = {
|
|
330
|
-
format: 1, id: "studio", scale: 1, areas: STUDIO_AREAS, door: { x: HALL_X, y: 704 }, furniture: STUDIO_FURNITURE, ...studioGraph(STUDIO_AREAS),
|
|
381
|
+
format: 1, id: "studio", scale: 1, areas: STUDIO_AREAS, door: { x: HALL_X, y: 704 }, furniture: STUDIO_FURNITURE, ...studioGraph(STUDIO_AREAS), straight: false,
|
|
331
382
|
stateZones: { working: "desks", idle: "lounge", needs: "alert", crashed: "alert", stuck: "alert", paused: "nap", stopped: "nap", default: "lounge" },
|
|
332
383
|
characters: {}, byInstance: {}, props: {}, bubbles: {},
|
|
333
384
|
};
|
|
@@ -346,8 +397,6 @@ function drawBackground(ctx, L, env) {
|
|
|
346
397
|
ctx.strokeStyle = pal.border; ctx.lineWidth = 2; ctx.strokeRect(r.x + 1, r.y + 1, r.w - 2, r.h - 2);
|
|
347
398
|
ctx.fillStyle = pal.text; ctx.font = "600 16px system-ui, sans-serif"; ctx.textBaseline = "top"; ctx.textAlign = "left";
|
|
348
399
|
ctx.fillText(fitText(ctx, env.labels.areas[a.name] || a.name, r.w - 60), r.x + 12, r.y + 10);
|
|
349
|
-
const crowd = L.crowd && L.crowd.get(a.name);
|
|
350
|
-
if (crowd) { ctx.fillStyle = pal.text2; ctx.textAlign = "right"; ctx.fillText(`+${crowd}`, r.x + r.w - 12, r.y + 10); }
|
|
351
400
|
ctx.restore();
|
|
352
401
|
}
|
|
353
402
|
}
|
|
@@ -396,9 +445,11 @@ function drawFurniture(ctx, f, active, ms, env) {
|
|
|
396
445
|
}
|
|
397
446
|
}
|
|
398
447
|
/** @type {OfficeSkin} */
|
|
399
|
-
|
|
448
|
+
/** #2540: the studio's matte is its hall floor (the scene's edge), so the panel around the scene reads as more of it. */
|
|
449
|
+
const studioMatte = (env) => (ZONE_FLOOR[env.mode] || ZONE_FLOOR.dark).hall;
|
|
450
|
+
export const studioSkin = { ...pixelSkin, id: "studio", pack: STUDIO_PACK, drawBackground, drawFurniture, matte: studioMatte };
|
|
400
451
|
|
|
401
|
-
// ── Skin packs
|
|
452
|
+
// ── Skin packs ──
|
|
402
453
|
|
|
403
454
|
/**
|
|
404
455
|
* An OfficeSkin from a checked pack (fleet-office-pack.js checkSkinPack) and its loaded pictures (`images`: src → a
|
|
@@ -406,20 +457,29 @@ export const studioSkin = { ...pixelSkin, id: "studio", pack: STUDIO_PACK, drawB
|
|
|
406
457
|
*/
|
|
407
458
|
export function spriteSkin(pack, images, base = pixelSkin) {
|
|
408
459
|
const img = (src) => (src && images.get(src)) || null;
|
|
409
|
-
|
|
460
|
+
// A strip's frame at its displayed size (dw × dh, unsmoothed), its anchor on (x, y); mirrored about the anchor.
|
|
461
|
+
const strip = (ctx, st, ms, x, y, frame = null, mirror = false) => {
|
|
410
462
|
const im = img(st && st.src);
|
|
411
463
|
if (!im) return false;
|
|
412
|
-
const f = frame ?? frameAt(ms, st.frames, st.fps),
|
|
464
|
+
const f = frame ?? frameAt(ms, st.frames, st.fps), b = stripBox(st, x, y, mirror);
|
|
413
465
|
ctx.save();
|
|
414
466
|
ctx.imageSmoothingEnabled = false;
|
|
415
|
-
ctx.drawImage(im, f * st.w, 0, st.w, st.h,
|
|
467
|
+
if (mirror) { ctx.translate(b.x + b.w, b.y); ctx.scale(-1, 1); ctx.drawImage(im, f * st.w, 0, st.w, st.h, 0, 0, b.w, b.h); }
|
|
468
|
+
else ctx.drawImage(im, f * st.w, 0, st.w, st.h, b.x, b.y, b.w, b.h);
|
|
416
469
|
ctx.restore();
|
|
417
470
|
return true;
|
|
418
471
|
};
|
|
472
|
+
const setOf = (look) => own(pack.characters, own(pack.byInstance, look.name)) || own(pack.characters, "default");
|
|
473
|
+
/** The strip a look shows (its pose; walking, by its heading), when its picture is loaded. */
|
|
474
|
+
const shown = (look, walking = look.pose === "walk") => {
|
|
475
|
+
const ps = poseStrip(setOf(look), walking ? "walk" : look.pose, look.heading);
|
|
476
|
+
return ps && img(ps.strip.src) ? ps : null;
|
|
477
|
+
};
|
|
419
478
|
return {
|
|
420
479
|
...base,
|
|
421
480
|
id: pack.id,
|
|
422
481
|
pack,
|
|
482
|
+
backdrop: img(pack.background),
|
|
423
483
|
drawRoom(ctx, room, env) {
|
|
424
484
|
const floor = img(pack.room.floor), wall = img(pack.room.wall);
|
|
425
485
|
if (!floor) { base.drawRoom(ctx, room, env); return; }
|
|
@@ -448,27 +508,151 @@ export function spriteSkin(pack, images, base = pixelSkin) {
|
|
|
448
508
|
if (!im) { drawBackground(ctx, L, env); return; }
|
|
449
509
|
ctx.save(); ctx.imageSmoothingEnabled = false; ctx.drawImage(im, 0, 0, LOGICAL.w, LOGICAL.h); ctx.restore();
|
|
450
510
|
},
|
|
511
|
+
drawForeground(ctx, L, env) {
|
|
512
|
+
const im = img(pack.foreground);
|
|
513
|
+
if (!im) return;
|
|
514
|
+
ctx.save(); ctx.imageSmoothingEnabled = false; ctx.drawImage(im, 0, 0, LOGICAL.w, LOGICAL.h); ctx.restore();
|
|
515
|
+
},
|
|
451
516
|
drawFurniture(ctx, f, active, ms, env) {
|
|
452
517
|
if (f.shape) { drawFurniture(ctx, f, active, ms, env); return; }
|
|
453
|
-
//
|
|
518
|
+
// An NPC walking (#2596): its walk strip for its heading (walkSide mirrored going left), when its picture is loaded.
|
|
519
|
+
const ws = walkStrip(f);
|
|
520
|
+
if (ws && strip(ctx, ws.strip, ms, f.x, f.y, null, ws.mirror)) return;
|
|
521
|
+
// Active: its activeSprite (or its strip) plays; otherwise its strip loops ("loop", #2570) or its first frame holds.
|
|
454
522
|
if (active) strip(ctx, f.activeSprite || f.sprite, ms, f.x, f.y);
|
|
523
|
+
else if (f.loop) strip(ctx, f.sprite, ms, f.x, f.y);
|
|
455
524
|
else strip(ctx, f.sprite, 0, f.x, f.y, 0);
|
|
456
525
|
},
|
|
457
526
|
drawCharacter(ctx, d, look, ms, env) {
|
|
458
527
|
if (look.pose === "away") return;
|
|
459
|
-
const
|
|
528
|
+
const ps = shown(look);
|
|
460
529
|
ctx.save();
|
|
461
530
|
if (look.dim) ctx.globalAlpha = 0.35;
|
|
462
|
-
const done = !!
|
|
531
|
+
const done = !!ps && strip(ctx, ps.strip, ms, d.seat.x, d.seat.y, null, ps.mirror);
|
|
463
532
|
ctx.restore();
|
|
464
533
|
if (!done) base.drawCharacter(ctx, d, look, ms, env);
|
|
465
534
|
},
|
|
466
|
-
|
|
535
|
+
charBox(look, at) {
|
|
536
|
+
const ps = shown({ ...look, heading: at.heading }, !!at.walking);
|
|
537
|
+
return ps ? stripBox(ps.strip, at.x, at.y, ps.mirror) : base.charBox(look, at);
|
|
538
|
+
},
|
|
539
|
+
// An item in the air: its sender's own (their set's "prop", #2562), else the pack's for its kind, else the built-in one.
|
|
540
|
+
drawProp(ctx, it, ms, env) {
|
|
541
|
+
const mine = it.from ? own(setOf({ name: it.from }) || {}, "prop") : null;
|
|
542
|
+
if (!strip(ctx, mine || own(pack.props, it.hue), ms, it.x, it.y)) base.drawProp(ctx, it, ms, env);
|
|
543
|
+
},
|
|
467
544
|
};
|
|
468
545
|
}
|
|
469
546
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
547
|
+
// ── The built-in cozy (#2631): the zones layout on a warm pixel-art office drawn as one picture, the built-in people ──
|
|
548
|
+
|
|
549
|
+
/** Its picture, shipped with the page (src/ui/shared/office/cozy.png, auth-api.ts ASSETS: public static, as the font). */
|
|
550
|
+
export const COZY_BACKGROUND = "/assets/office-cozy.png";
|
|
551
|
+
const pts = (...xy) => xy.map(([x, y]) => ({ x, y }));
|
|
552
|
+
// The seats are feet points on the picture: chairs in front of the desks, the sofa, the coffee table and machine, the
|
|
553
|
+
// beanbags (a sleeper's bottom), in front of the whiteboard. The rects put a crowd's first extra spots (fleet-office-zones.js
|
|
554
|
+
// overflowSpots: rows from the rect's top) on the aisles: under the first row of desks, in front of the sofa.
|
|
555
|
+
const COZY_AREAS = {
|
|
556
|
+
desks: { name: "desks", rect: { x: 130, y: 196, w: 430, h: 220 }, seats: pts([193, 238], [336, 238], [481, 238], [193, 354], [336, 354], [481, 354]) },
|
|
557
|
+
lounge: { name: "lounge", rect: { x: 700, y: 196, w: 476, h: 184 }, seats: pts([940, 240], [1025, 310], [1120, 305]) },
|
|
558
|
+
alert: { name: "alert", rect: { x: 900, y: 500, w: 220, h: 110 }, seats: pts([965, 570], [1045, 570]) },
|
|
559
|
+
nap: { name: "nap", rect: { x: 110, y: 470, w: 400, h: 150 }, seats: pts([180, 562], [300, 562], [422, 565]) },
|
|
560
|
+
};
|
|
561
|
+
/** Its walkable graph, on the picture's aisles, every edge across or up/down; each seat's nearest waypoint is in front of it. */
|
|
562
|
+
const COZY_WAYPOINTS = [
|
|
563
|
+
["door", 642, 640], ["hall", 642, 395], ["top", 642, 175],
|
|
564
|
+
// the desks: the aisle under the first row (y 250), the one under the second (y 395), the column beside them (x 560)
|
|
565
|
+
["d-east", 560, 395], ["d-up", 560, 250], ["d1-3", 481, 250], ["d1-2", 336, 250], ["d1-1", 193, 250],
|
|
566
|
+
["d2-3", 481, 395], ["d2-2", 336, 395], ["d2-x", 300, 395], ["d2-1", 193, 395],
|
|
567
|
+
// the beanbags: the aisle above them (y 460)
|
|
568
|
+
["n-x", 300, 460], ["n-1", 180, 460], ["n-3", 422, 460],
|
|
569
|
+
// the lounge: below the coffee table (y 340), up the sofa's west end; the coffee machine's column (x 1140)
|
|
570
|
+
["l-hall", 946, 395], ["l-mid", 946, 340], ["l-west", 860, 340], ["l-sofa", 860, 250], ["l-table", 1025, 340],
|
|
571
|
+
["e-hall", 1140, 395], ["e-coffee", 1140, 305],
|
|
572
|
+
// the whiteboard: in front of it (y 570), from the coffee machine's column
|
|
573
|
+
["a-east", 1140, 570], ["a-mid", 1005, 570],
|
|
574
|
+
].map(([id, x, y]) => ({ id, x, y }));
|
|
575
|
+
const COZY_EDGES = [
|
|
576
|
+
["door", "hall"], ["hall", "top"], ["hall", "d-east"], ["d-east", "d2-3"], ["d2-3", "d2-2"], ["d2-2", "d2-x"], ["d2-x", "d2-1"],
|
|
577
|
+
["d-east", "d-up"], ["d-up", "d1-3"], ["d1-3", "d1-2"], ["d1-2", "d1-1"], ["d2-x", "n-x"], ["n-x", "n-1"], ["n-x", "n-3"],
|
|
578
|
+
["hall", "l-hall"], ["l-hall", "l-mid"], ["l-mid", "l-west"], ["l-west", "l-sofa"], ["l-mid", "l-table"],
|
|
579
|
+
["l-hall", "e-hall"], ["e-hall", "e-coffee"], ["e-hall", "a-east"], ["a-east", "a-mid"],
|
|
580
|
+
];
|
|
581
|
+
/** The cozy's pack: the picture is the room; the one item is the notice board, its tag saying a pack can restyle it. */
|
|
582
|
+
export const COZY_PACK = {
|
|
583
|
+
format: 1, id: "cozy", scale: 1, background: COZY_BACKGROUND, areas: COZY_AREAS, door: { x: 642, y: 662 },
|
|
584
|
+
waypoints: COZY_WAYPOINTS, edges: COZY_EDGES, straight: true,
|
|
585
|
+
furniture: [{ id: "notice", x: 1211, y: 628, depth: 5, get label() { return t("orgo.cozyNotice"); } }],
|
|
586
|
+
stateZones: { working: "desks", idle: "lounge", needs: "alert", crashed: "alert", stuck: "alert", paused: "nap", stopped: "nap", default: "lounge" },
|
|
587
|
+
characters: {}, byInstance: {}, props: {}, bubbles: {},
|
|
588
|
+
};
|
|
589
|
+
/** Until its picture is in (loadCozySkin): the studio's floors under the cozy's areas. */
|
|
590
|
+
const cozyBare = spriteSkin(COZY_PACK, new Map(), studioSkin);
|
|
591
|
+
let cozyJob = null;
|
|
592
|
+
|
|
593
|
+
/** The skins built into this page; an uploaded pack is loaded by loadPackSkin. */
|
|
594
|
+
export const SKINS = new Map([["pixel", pixelSkin], ["studio", studioSkin], ["cozy", cozyBare]]);
|
|
595
|
+
export const BUILT_IN_SKINS = ["pixel", "studio", "cozy"];
|
|
596
|
+
/**
|
|
597
|
+
* #2631: the cozy's picture, loaded once; the skin drawn from it then takes the cozy's place in SKINS (skinFor). The
|
|
598
|
+
* skin, or null while it cannot load (the bare one stays; tried again next time).
|
|
599
|
+
*/
|
|
600
|
+
export function loadCozySkin({ image = defaultImage } = {}) {
|
|
601
|
+
if (SKINS.get("cozy") !== cozyBare) return Promise.resolve(SKINS.get("cozy"));
|
|
602
|
+
cozyJob ??= image(COZY_BACKGROUND).then((im) => {
|
|
603
|
+
cozyJob = null;
|
|
604
|
+
if (!im) return null;
|
|
605
|
+
const sk = spriteSkin(COZY_PACK, new Map([[COZY_BACKGROUND, im]]), studioSkin);
|
|
606
|
+
SKINS.set("cozy", sk);
|
|
607
|
+
return sk;
|
|
608
|
+
}, () => { cozyJob = null; return null; });
|
|
609
|
+
return cozyJob;
|
|
610
|
+
}
|
|
611
|
+
/** A skin by id: a built-in one, else a pack loaded here (`packs`: id → skin), else the default (pixel). */
|
|
612
|
+
export const skinFor = (id, packs = null) => SKINS.get(id) || (packs && packs.get(id)) || pixelSkin;
|
|
613
|
+
|
|
614
|
+
const loaded = new Map(); // `${id}@${sha}` → Promise<OfficeSkin | null>
|
|
615
|
+
// Room for every pack the host keeps (web-office-skins.ts SKIN_LIMITS.packs) and a few replaced ones (each a new sha).
|
|
616
|
+
const MAX_LOADED = 24;
|
|
617
|
+
const defaultImage = (url) => new Promise((resolve) => {
|
|
618
|
+
if (typeof Image !== "function") { resolve(null); return; }
|
|
619
|
+
const im = new Image();
|
|
620
|
+
im.onload = () => resolve(im);
|
|
621
|
+
im.onerror = () => resolve(null);
|
|
622
|
+
im.src = url;
|
|
623
|
+
});
|
|
624
|
+
/**
|
|
625
|
+
* An uploaded pack (GET /ui/office-skins/<id>?v=<sha>) as an OfficeSkin: its manifest checked (null when it is not a
|
|
626
|
+
* pack: the page then draws a built-in style and says so), its pictures loaded from their content ids (one that does
|
|
627
|
+
* not load is drawn by the built-in skin). Kept per id and sha, so a pack is fetched once. `url`: where a picture's path
|
|
628
|
+
* is read (a share link's page reads its own address, #2636).
|
|
629
|
+
*/
|
|
630
|
+
export function loadPackSkin(id, sha, { fetch: get = globalThis.fetch, image = defaultImage, url = (p) => p } = {}) {
|
|
631
|
+
const key = `${id}@${sha}`;
|
|
632
|
+
if (loaded.has(key)) return loaded.get(key);
|
|
633
|
+
const base = `/ui/office-skins/${encodeURIComponent(id)}`;
|
|
634
|
+
const job = (async () => {
|
|
635
|
+
try {
|
|
636
|
+
const r = await get(`${base}?v=${encodeURIComponent(sha)}`, { method: "GET", headers: { "Content-Type": "application/json" } });
|
|
637
|
+
if (!r || !r.ok) return null;
|
|
638
|
+
const d = await r.json();
|
|
639
|
+
const c = checkSkinPack(d && d.manifest, { kept: true });
|
|
640
|
+
if (!c.ok || c.pack.id !== id) return null;
|
|
641
|
+
const files = d.files && typeof d.files === "object" ? d.files : {};
|
|
642
|
+
const images = new Map();
|
|
643
|
+
await Promise.all(packImages(c.pack).map(async (src) => {
|
|
644
|
+
const fid = own(files, src);
|
|
645
|
+
if (typeof fid !== "string" || !/^[0-9a-f]{32}$/.test(fid)) return;
|
|
646
|
+
const im = await image(url(`${base}/img/${fid}.png`));
|
|
647
|
+
if (im) images.set(src, im);
|
|
648
|
+
}));
|
|
649
|
+
return spriteSkin(c.pack, images);
|
|
650
|
+
} catch { return null; }
|
|
651
|
+
})();
|
|
652
|
+
loaded.set(key, job);
|
|
653
|
+
if (loaded.size > MAX_LOADED) loaded.delete(loaded.keys().next().value);
|
|
654
|
+
job.then((skin) => { if (!skin) loaded.delete(key); }); // a failed read is tried again next time
|
|
655
|
+
return job;
|
|
656
|
+
}
|
|
473
657
|
/** The people's depth in zones (fleet-office-pack.js). */
|
|
474
658
|
export { CHAR_DEPTH };
|
|
@@ -2,14 +2,48 @@
|
|
|
2
2
|
// - the offices people made: the host's (GET / PUT /ui/offices, src/web-office-store.ts — one list for every viewer);
|
|
3
3
|
// what comes back is checked again here (fleet-office-model.js officesOk), never trusted;
|
|
4
4
|
// - this browser's own choices (localStorage; blocked storage: this page only, as the canvas's Canvas/List): which
|
|
5
|
-
// office is shown, the auto office's member filter, and its style (skin)
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// office is shown, the auto office's member filter, and its style (skin); #2540: the view (zoom and centre) and the
|
|
6
|
+
// fit mode (Fit or Fill) per office; #2554: whether All offices shows the lobby tile.
|
|
7
|
+
// - the skin packs an admin uploaded (#2513: GET /ui/office-skins, POST to upload one, DELETE to remove one;
|
|
8
|
+
// src/web-office-skins.ts). The office editor and the layout editor call saveOffices() with the whole list and the rev
|
|
9
|
+
// they read (a change in between answers 409).
|
|
8
10
|
import { AUTO_ID, filterOk, officesOk } from "./fleet-office-model.js";
|
|
11
|
+
import { fitModeOk, savedOk } from "./fleet-office-view.js";
|
|
9
12
|
|
|
10
|
-
const CHOICE_KEY = "agend_org_office", FILTER_KEY = "agend_org_office_filter", SKIN_KEY = "agend_org_office_skin";
|
|
13
|
+
const CHOICE_KEY = "agend_org_office", FILTER_KEY = "agend_org_office_filter", SKIN_KEY = "agend_org_office_skin", ASLEEP_KEY = "agend_org_office_hide_asleep", NIGHT_KEY = "agend_org_office_night";
|
|
14
|
+
/** #2554: "0" when All offices leaves the lobby tile out (default: shown). */
|
|
15
|
+
const LOBBY_KEY = "agend_org_office_lobby";
|
|
16
|
+
/** #2540: { "<office>@<style>": [k, cx, cy] (and a 1 after them: a fit) } — the last VIEWS_MAX views, the most recent last. */
|
|
17
|
+
const VIEW_KEY = "agend_org_office_view", VIEWS_MAX = 32;
|
|
18
|
+
/** #2540: { "<office>@<style>": "cover" } — the offices whose fit fills the panel (the rest: "contain"), the last VIEWS_MAX. */
|
|
19
|
+
const FIT_KEY = "agend_org_office_fit";
|
|
20
|
+
const PACK_ID = /^[A-Za-z0-9_-]{1,40}$/;
|
|
21
|
+
/** The host's list of skin packs, checked: [{ id, name, sha }] (anything odd dropped). */
|
|
22
|
+
export function skinListOk(d) {
|
|
23
|
+
const out = [], ids = new Set();
|
|
24
|
+
for (const p of Array.isArray(d && d.packs) ? d.packs.slice(0, 64) : []) {
|
|
25
|
+
if (!p || typeof p.id !== "string" || !PACK_ID.test(p.id) || ids.has(p.id) || typeof p.sha !== "string" || !/^[0-9a-f]{32}$/.test(p.sha)) continue;
|
|
26
|
+
ids.add(p.id);
|
|
27
|
+
out.push({ id: p.id, name: typeof p.name === "string" && p.name.trim() ? p.name.trim().slice(0, 60) : p.id, sha: p.sha });
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
11
31
|
|
|
12
32
|
/** `fetch`: the caller's (a lease's, so an answer after the person moved on is dropped); `storage`: default localStorage. */
|
|
33
|
+
/**
|
|
34
|
+
* Agents' acts as the host sends them (GET /ui/office-acts, the `office_acts` event), checked: the well-formed ones.
|
|
35
|
+
* `now`: the host's clock when it sent them — each end is moved onto this page's clock (they may differ).
|
|
36
|
+
*/
|
|
37
|
+
export function actsOk(list, now) {
|
|
38
|
+
const skew = typeof now === "number" && Number.isFinite(now) ? now - Date.now() : 0;
|
|
39
|
+
const str = (v) => typeof v === "string" && v.length > 0 && v.length <= 200, num = (v) => typeof v === "number" && Number.isFinite(v);
|
|
40
|
+
return (Array.isArray(list) ? list : []).filter((a) => a && typeof a === "object" && str(a.instance) && str(a.office) && num(a.until)
|
|
41
|
+
&& (a.x === undefined || num(a.x)) && (a.y === undefined || num(a.y)) && (a.person === undefined || str(a.person))
|
|
42
|
+
&& (a.say === undefined || (typeof a.say === "string" && a.say.length <= 40)) && (a.pose === undefined || ["idle", "working", "needs", "paused"].includes(a.pose)))
|
|
43
|
+
.map((a) => ({ instance: a.instance, office: a.office, until: a.until - skew, ...(num(a.since) ? { since: a.since } : {}), ...(str(a.state) ? { state: a.state } : {}),
|
|
44
|
+
...(num(a.x) && num(a.y) ? { x: a.x, y: a.y } : {}),
|
|
45
|
+
...(a.person ? { person: a.person } : {}), ...(a.say ? { say: a.say } : {}), ...(a.pose ? { pose: a.pose } : {}) }));
|
|
46
|
+
}
|
|
13
47
|
export function createOfficeStore({ fetch: get = globalThis.fetch, storage } = {}) {
|
|
14
48
|
const st = () => { try { return storage || globalThis.localStorage || null; } catch { return null; } };
|
|
15
49
|
const read = (k) => { try { const s = st(); return s ? s.getItem(k) : null; } catch { return null; } };
|
|
@@ -37,13 +71,84 @@ export function createOfficeStore({ fetch: get = globalThis.fetch, storage } = {
|
|
|
37
71
|
return r.ok ? { ok: true, rev: d.rev, offices: officesOk(d.offices) } : { ok: false, status: r.status, error: d.error || "offices_failed" };
|
|
38
72
|
} catch { return { ok: false, status: 0, error: "offices_failed" }; }
|
|
39
73
|
},
|
|
40
|
-
/**
|
|
74
|
+
/** #2578: what agents are doing in the offices now (office_set): their acts, or null when they could not be read. */
|
|
75
|
+
async acts() {
|
|
76
|
+
try {
|
|
77
|
+
const r = await get("/ui/office-acts", { method: "GET", headers });
|
|
78
|
+
if (!r || !r.ok) return null;
|
|
79
|
+
const d = await r.json();
|
|
80
|
+
return actsOk(d && d.acts, d && d.now);
|
|
81
|
+
} catch { return null; }
|
|
82
|
+
},
|
|
83
|
+
/** The skin packs the host keeps: { rev, packs: [{ id, name, sha }] }; null when they could not be read. */
|
|
84
|
+
async skins() {
|
|
85
|
+
try {
|
|
86
|
+
const r = await get("/ui/office-skins", { method: "GET", headers });
|
|
87
|
+
if (!r || !r.ok) return null;
|
|
88
|
+
const d = await r.json();
|
|
89
|
+
return { rev: typeof (d && d.rev) === "string" ? d.rev : "", packs: skinListOk(d) };
|
|
90
|
+
} catch { return null; }
|
|
91
|
+
},
|
|
92
|
+
/** Upload a pack (an admin session): { manifest, files: { name: base64 }, name? } → { ok, pack } or { ok: false, status, error, detail }. */
|
|
93
|
+
async uploadSkin(body) {
|
|
94
|
+
try {
|
|
95
|
+
const r = await get("/ui/office-skins", { method: "POST", headers, body: JSON.stringify(body) });
|
|
96
|
+
const d = await r.json().catch(() => ({}));
|
|
97
|
+
return r.ok ? { ok: true, pack: d.pack, rev: d.rev } : { ok: false, status: r.status, error: d.error || "skin_failed", detail: d.detail || "" };
|
|
98
|
+
} catch { return { ok: false, status: 0, error: "skin_failed", detail: "" }; }
|
|
99
|
+
},
|
|
100
|
+
/** Remove a pack (an admin session). */
|
|
101
|
+
async deleteSkin(id) {
|
|
102
|
+
try {
|
|
103
|
+
const r = await get(`/ui/office-skins/${encodeURIComponent(id)}`, { method: "DELETE", headers });
|
|
104
|
+
const d = await r.json().catch(() => ({}));
|
|
105
|
+
return r.ok ? { ok: true } : { ok: false, status: r.status, error: d.error || "skin_failed" };
|
|
106
|
+
} catch { return { ok: false, status: 0, error: "skin_failed" }; }
|
|
107
|
+
},
|
|
108
|
+
/**
|
|
109
|
+
* The office shown here: an id (one the host no longer has falls back to the auto office where it is used), or
|
|
110
|
+
* #2554's All offices (fleet-office-overview.js ALL_ID; with fewer than two offices stored: the auto office).
|
|
111
|
+
*/
|
|
41
112
|
choice: () => { const v = read(CHOICE_KEY); return typeof v === "string" && v && v.length <= 40 ? v : AUTO_ID; },
|
|
42
113
|
saveChoice: (id) => write(CHOICE_KEY, String(id)),
|
|
43
114
|
filter: () => { const s = read(FILTER_KEY); try { return filterOk(s ? JSON.parse(s) : null); } catch { return filterOk(null); } },
|
|
44
115
|
saveFilter: (f) => write(FILTER_KEY, JSON.stringify(filterOk(f))),
|
|
45
|
-
/** The whole fleet's style here: a skin id ("pixel": desks, "studio": zones); an unknown one is the default where used. */
|
|
46
|
-
skin: () => { const v = read(SKIN_KEY); return typeof v === "string" && /^[A-Za-z0-9_-]{1,40}$/.test(v) ? v : "
|
|
116
|
+
/** The whole fleet's style here: a skin id ("pixel": desks, "studio" and "cozy": zones); none chosen yet: cozy (#2631); an unknown one is the default where used. */
|
|
117
|
+
skin: () => { const v = read(SKIN_KEY); return typeof v === "string" && /^[A-Za-z0-9_-]{1,40}$/.test(v) ? v : "cozy"; },
|
|
47
118
|
saveSkin: (id) => write(SKIN_KEY, String(id)),
|
|
119
|
+
/** Whether paused and stopped people are left out of the Office here (default: shown). */
|
|
120
|
+
hideAsleep: () => read(ASLEEP_KEY) === "1",
|
|
121
|
+
saveHideAsleep: (v) => write(ASLEEP_KEY, v ? "1" : "0"),
|
|
122
|
+
/** #2568: whether the idle go to bed at night (00:00–06:00 on this device's clock) here (default: off). */
|
|
123
|
+
nightOn: () => read(NIGHT_KEY) === "1",
|
|
124
|
+
saveNightOn: (v) => write(NIGHT_KEY, v ? "1" : "0"),
|
|
125
|
+
/** #2554: whether All offices shows the lobby tile (the instances in no stored office) here (default: shown). */
|
|
126
|
+
lobbyTile: () => read(LOBBY_KEY) !== "0",
|
|
127
|
+
saveLobbyTile: (v) => write(LOBBY_KEY, v ? "1" : "0"),
|
|
128
|
+
/** The view kept for `key` (an office and its style): { k, cx, cy } (fleet-office-view.js savedOf), or null. */
|
|
129
|
+
view: (key) => {
|
|
130
|
+
try { const m = JSON.parse(read(VIEW_KEY) || "{}"); const a = m && Object.prototype.hasOwnProperty.call(m, key) ? m[key] : null;
|
|
131
|
+
return Array.isArray(a) ? savedOk({ k: a[0], cx: a[1], cy: a[2], fit: a[3] === 1 }) : null; } catch { return null; }
|
|
132
|
+
},
|
|
133
|
+
saveView: (key, v) => {
|
|
134
|
+
const ok = savedOk(v);
|
|
135
|
+
if (!ok || typeof key !== "string" || key.length > 100) return;
|
|
136
|
+
let m = {};
|
|
137
|
+
try { const o = JSON.parse(read(VIEW_KEY) || "{}"); if (o && typeof o === "object" && !Array.isArray(o)) m = o; } catch { m = {}; }
|
|
138
|
+
const r = (n) => Math.round(n * 1000) / 1000;
|
|
139
|
+
const next = Object.entries(m).filter(([k2, a]) => k2 !== key && Array.isArray(a)).slice(-(VIEWS_MAX - 1));
|
|
140
|
+
write(VIEW_KEY, JSON.stringify(Object.fromEntries([...next, [key, [r(ok.k), r(ok.cx), r(ok.cy), ...(ok.fit ? [1] : [])]]])));
|
|
141
|
+
},
|
|
142
|
+
/** The fit mode kept for `key`: "contain" (the default) or "cover" (Fill). */
|
|
143
|
+
fitMode: (key) => {
|
|
144
|
+
try { const m = JSON.parse(read(FIT_KEY) || "{}"); return fitModeOk(m && Object.prototype.hasOwnProperty.call(m, key) ? m[key] : null); } catch { return "contain"; }
|
|
145
|
+
},
|
|
146
|
+
saveFitMode: (key, mode) => {
|
|
147
|
+
if (typeof key !== "string" || key.length > 100) return;
|
|
148
|
+
let m = {};
|
|
149
|
+
try { const o = JSON.parse(read(FIT_KEY) || "{}"); if (o && typeof o === "object" && !Array.isArray(o)) m = o; } catch { m = {}; }
|
|
150
|
+
const next = Object.entries(m).filter(([k2, v]) => k2 !== key && v === "cover").slice(-(VIEWS_MAX - 1));
|
|
151
|
+
write(FIT_KEY, JSON.stringify(Object.fromEntries(fitModeOk(mode) === "cover" ? [...next, [key, "cover"]] : next)));
|
|
152
|
+
},
|
|
48
153
|
};
|
|
49
154
|
}
|