@songsid/agend 2.3.2-beta.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/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 +11 -0
- package/dist/channel/adapters/discord.js +71 -14
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/mcp-tools.js +29 -2
- 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/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 +53 -10
- 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 +23 -0
- package/dist/daemon.js +106 -7
- 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/doctor.js +2 -1
- package/dist/doctor.js.map +1 -1
- package/dist/fleet-manager.d.ts +72 -0
- package/dist/fleet-manager.js +507 -89
- package/dist/fleet-manager.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 +24 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +24 -1
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +37 -9
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +44 -11
- package/dist/instance-config-impact.js +10 -5
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +19 -0
- package/dist/instance-lifecycle.js +55 -12
- 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 +88 -8
- 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/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 +26 -2
- package/dist/outbound-schemas.js +27 -2
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +7 -1
- 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 +2 -0
- package/dist/settings-api.js +41 -16
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +17 -14
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +7 -1
- 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/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 +1 -1
- package/dist/turn-reply-guard.js +1 -1
- package/dist/turn-reply-guard.js.map +1 -1
- package/dist/types.d.ts +28 -8
- package/dist/ui/backend-signin.js +63 -10
- package/dist/ui/chat-strings.js +2 -2
- package/dist/ui/fleet-office-editor.js +14 -9
- package/dist/ui/fleet-office-layout.js +70 -32
- package/dist/ui/fleet-office-model.js +6 -3
- package/dist/ui/fleet-office-overview.js +167 -0
- package/dist/ui/fleet-office-pack.d.ts +7 -2
- package/dist/ui/fleet-office-pack.js +119 -15
- package/dist/ui/fleet-office-skin.js +98 -23
- package/dist/ui/fleet-office-store.js +39 -5
- package/dist/ui/fleet-office-view.js +5 -4
- package/dist/ui/fleet-office-zones.js +172 -12
- package/dist/ui/fleet-office.js +547 -186
- package/dist/ui/fleet-usage.js +19 -15
- package/dist/ui/office-share-dialog.js +145 -0
- package/dist/ui/office-share-page.js +164 -0
- package/dist/ui/panel-chat.js +8 -1
- package/dist/ui/panel-log.js +2 -2
- package/dist/ui/panel-needs.js +4 -4
- package/dist/ui/panel-settings.js +37 -11
- package/dist/ui/settings-dialogs.js +8 -1
- package/dist/ui/settings-guests.js +17 -7
- package/dist/ui/settings-model.js +14 -6
- package/dist/ui/settings-registry.js +4 -0
- package/dist/ui/settings-strings.js +16 -2
- package/dist/ui/shared/app-i18n.js +4 -2
- package/dist/ui/shared/app-shell.js +14 -6
- package/dist/ui/shared/app-stream.js +2 -1
- package/dist/ui/shared/app.css +67 -2
- package/dist/ui/shared/office/cozy.png +0 -0
- package/dist/ui/shared/side-panel.js +95 -0
- 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 +18 -0
- package/dist/web-api.js +201 -0
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.js +1 -1
- 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-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 +3 -2
- package/dist/web-office-skins.js +3 -2
- package/dist/web-office-skins.js.map +1 -1
- package/dist/web-office-store.js +40 -7
- package/dist/web-office-store.js.map +1 -1
- package/dist/web-settings-patch.d.ts +2 -1
- package/dist/web-settings-patch.js +4 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/working-recovery.d.ts +2 -2
- package/dist/working-recovery.js +43 -8
- package/dist/working-recovery.js.map +1 -1
- package/dist/working-state-journal.d.ts +2 -0
- package/dist/working-state-journal.js +16 -1
- package/dist/working-state-journal.js.map +1 -1
- package/package.json +3 -2
- package/templates/windows-shortcuts.ps1 +155 -140
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
// session that started a sign-in gets its terminal link, access code and device code; every other admin page only that
|
|
6
6
|
// one runs, on which device, and may cancel it. The stream's `backend_login` event carries nothing — it only says to
|
|
7
7
|
// read again. Starting is two steps, as in chat: check (the server keeps what it found) and start (with its id).
|
|
8
|
-
import { html, useEffect, useState } from "/assets/app-html.js";
|
|
8
|
+
import { html, useEffect, useState, useRef } from "/assets/app-html.js";
|
|
9
9
|
import { t, register } from "/assets/app-i18n.js";
|
|
10
10
|
import { appStore, createStore, useStore } from "/assets/app-store.js";
|
|
11
11
|
import { closeDialog, shellStore, showDialog } from "/assets/app-shell.js";
|
|
12
12
|
import { Dialog } from "/assets/ui-dialog.js";
|
|
13
13
|
import { toast } from "/assets/ui-toast.js";
|
|
14
|
+
import { mountStructuredSignIns } from "./structured-signin.js";
|
|
14
15
|
|
|
15
16
|
register("signin", {
|
|
16
17
|
en: {
|
|
@@ -49,6 +50,8 @@ register("signin", {
|
|
|
49
50
|
notOffered: "A public link is not offered for this sign-in.",
|
|
50
51
|
failed: "That did not work.",
|
|
51
52
|
listTitle: "Sign-ins",
|
|
53
|
+
browserRunning: "Signing in to {0} on this machine",
|
|
54
|
+
consoleFallback: "Use console fallback",
|
|
52
55
|
listHint: "Each backend's login on this machine, and each credential profile an agent uses.",
|
|
53
56
|
stUsable: "signed in",
|
|
54
57
|
stNotSignedIn: "not signed in",
|
|
@@ -98,6 +101,8 @@ register("signin", {
|
|
|
98
101
|
notOffered: "這次登入不提供公開連結。",
|
|
99
102
|
failed: "沒有成功。",
|
|
100
103
|
listTitle: "登入",
|
|
104
|
+
browserRunning: "正在這台機器登入 {0}",
|
|
105
|
+
consoleFallback: "改用主控台備援",
|
|
101
106
|
listHint: "這台機器上每個 backend 的登入,以及代理使用的每個憑證設定檔。",
|
|
102
107
|
stUsable: "已登入",
|
|
103
108
|
stNotSignedIn: "未登入",
|
|
@@ -116,7 +121,7 @@ const tn = (k, ...v) => t(`signin.${k}`, ...v);
|
|
|
116
121
|
const shownOf = (backend, profile) => (profile ? tn("profile", backend, profile) : backend);
|
|
117
122
|
|
|
118
123
|
/** What this page knows: the current sign-in as this session may see it, and Settings' list. */
|
|
119
|
-
export const signInStore = createStore({ loaded: false, login: null, targets: [] });
|
|
124
|
+
export const signInStore = createStore({ loaded: false, login: null, targets: [], structured: null });
|
|
120
125
|
|
|
121
126
|
let reading = null, again = false;
|
|
122
127
|
/**
|
|
@@ -125,9 +130,13 @@ let reading = null, again = false;
|
|
|
125
130
|
*/
|
|
126
131
|
export function refreshSignIn() {
|
|
127
132
|
if (reading) { again = true; return reading; }
|
|
128
|
-
reading =
|
|
129
|
-
.then(
|
|
130
|
-
|
|
133
|
+
reading = Promise.all([
|
|
134
|
+
fetch("/ui/backend-login", { cache: "no-store" }).then(r => r.ok ? r.json() : null).catch(() => null),
|
|
135
|
+
fetch("/ui/browser-backend-signins", { cache: "no-store" }).then(r => r.ok ? r.json() : null).catch(() => null),
|
|
136
|
+
]).then(([b, structured]) => {
|
|
137
|
+
if (b) signInStore.set({ loaded: true, login: b.login || null, targets: Array.isArray(b.targets) ? b.targets : [] });
|
|
138
|
+
signInStore.set({ structured: structured && Array.isArray(structured.selected) && Array.isArray(structured.pending) ? structured : null }); return b;
|
|
139
|
+
})
|
|
131
140
|
.catch(() => null)
|
|
132
141
|
.finally(() => { reading = null; if (again) { again = false; void refreshSignIn(); } });
|
|
133
142
|
return reading;
|
|
@@ -140,13 +149,25 @@ export function installSignIn() {
|
|
|
140
149
|
installed = true;
|
|
141
150
|
const on = appStore.get().streamOn;
|
|
142
151
|
if (typeof on === "function") on("backend_login", () => { void refreshSignIn(); });
|
|
143
|
-
void refreshSignIn()
|
|
152
|
+
void refreshSignIn().then(() => {
|
|
153
|
+
// A navigation hint from the installer, never authority to start a CLI or cancel another writer.
|
|
154
|
+
const backends = installerSignInBackends(typeof location === "undefined" ? "" : location.search);
|
|
155
|
+
if (backends && signInStore.get().structured) showDialog(BrowserSignInDialog, { backends });
|
|
156
|
+
});
|
|
144
157
|
shellStore.set({ banner: SignInLine });
|
|
145
158
|
}
|
|
146
159
|
|
|
160
|
+
export function installerSignInBackends(search) {
|
|
161
|
+
const csv = new URLSearchParams(search).get("backend_signins");
|
|
162
|
+
if (!csv || csv.length > 100) return null;
|
|
163
|
+
const ids = csv.split(",");
|
|
164
|
+
return ids.length <= 4 && ids.every(b => ["kiro-cli", "claude-code", "codex", "antigravity"].includes(b)) ? [...new Set(ids)] : null;
|
|
165
|
+
}
|
|
166
|
+
|
|
147
167
|
/** Open the dialog for one login (a backend's default one, or a credential profile). */
|
|
148
168
|
export function openSignIn({ backend, profile }) {
|
|
149
|
-
|
|
169
|
+
if (!profile && ["kiro-cli", "claude-code", "codex"].includes(backend)) showDialog(BrowserSignInDialog, { backend });
|
|
170
|
+
else showDialog(SignInDialog, { backend, ...(profile ? { profile } : {}) });
|
|
150
171
|
}
|
|
151
172
|
|
|
152
173
|
/** Open the dialog on the running sign-in (its starter's view, or the cancel-only one). */
|
|
@@ -191,6 +212,32 @@ function Running({ login }) {
|
|
|
191
212
|
</div>`;
|
|
192
213
|
}
|
|
193
214
|
|
|
215
|
+
/** The same closed controls as first setup. Profile-specific logins retain their existing flow. */
|
|
216
|
+
export function BrowserSignInDialog({ backend, backends, onClose }) {
|
|
217
|
+
const root = useRef(null), close = onClose || closeDialog;
|
|
218
|
+
useEffect(() => {
|
|
219
|
+
let cards, last = "";
|
|
220
|
+
cards = mountStructuredSignIns(root.current, { endpoint: "/ui/browser-backend-signins", changed: next => {
|
|
221
|
+
if (backends) { if (backends.some(b => !next.selected.includes(b))) void cards.addBackends(backends); }
|
|
222
|
+
else if (!next.selected.includes(backend)) void cards.addBackend(backend);
|
|
223
|
+
const signature = JSON.stringify([next.login, next.pending]);
|
|
224
|
+
if (signature !== last) { last = signature; void refreshSignIn(); }
|
|
225
|
+
} });
|
|
226
|
+
return () => cards.dispose();
|
|
227
|
+
}, [backend, backends]);
|
|
228
|
+
const fallback = async (chosen = backend) => {
|
|
229
|
+
const result = await post("/ui/browser-backend-signins", { operation: "cancel" });
|
|
230
|
+
if (!result.ok) { toast(tn("failed")); return; }
|
|
231
|
+
showDialog(SignInDialog, { backend: chosen });
|
|
232
|
+
};
|
|
233
|
+
return html`<${Dialog} title=${backends ? tn("listTitle") : tn("title", backend)} onClose=${close}>
|
|
234
|
+
<div ref=${root}></div>
|
|
235
|
+
${backends ? backends.filter(b => b !== "antigravity").map(b => html`<button type="button" class="btn" onClick=${() => fallback(b)}>${b} · ${tn("consoleFallback")}</button>`) : null}
|
|
236
|
+
<div class="s-actions">${backend ? html`<button type="button" class="btn" onClick=${() => fallback(backend)}>${tn("consoleFallback")}</button>` : null}
|
|
237
|
+
<button type="button" class="btn" onClick=${close}>${tn("close")}</button></div>
|
|
238
|
+
</${Dialog}>`;
|
|
239
|
+
}
|
|
240
|
+
|
|
194
241
|
export function SignInDialog({ backend, profile, resume = false, onClose }) {
|
|
195
242
|
const s = useStore(signInStore);
|
|
196
243
|
const [step, setStep] = useState(resume ? { kind: "login" } : { kind: "checking" });
|
|
@@ -265,7 +312,12 @@ export function SignInDialog({ backend, profile, resume = false, onClose }) {
|
|
|
265
312
|
|
|
266
313
|
/** The line every admin page shows while a sign-in runs (the shell's banner slot). */
|
|
267
314
|
export function SignInLine() {
|
|
268
|
-
const { login } = useStore(signInStore);
|
|
315
|
+
const { login, structured } = useStore(signInStore);
|
|
316
|
+
const active = structured?.login;
|
|
317
|
+
if ((!login || login.phase !== "running") && active && active.phase !== "done" && !(active.phase === "failed" && active.reason)) {
|
|
318
|
+
return html`<div data-trust="status" class="conn op-line signin-line" role="status"><span>${tn("browserRunning", active.backend)}</span>
|
|
319
|
+
<button type="button" class="btn btn-sm" onClick=${() => openSignIn({ backend: active.backend })}>${tn("open")}</button></div>`;
|
|
320
|
+
}
|
|
269
321
|
if (!login || login.phase !== "running") return null;
|
|
270
322
|
return html`<div data-trust="status" class="conn op-line signin-line" role="status">
|
|
271
323
|
<span>${tn("running", shownOf(login.backend, login.profile), login.startedOn)}</span>
|
|
@@ -276,13 +328,14 @@ const STATE_KEY = { usable: "stUsable", not_signed_in: "stNotSignedIn", not_inst
|
|
|
276
328
|
|
|
277
329
|
/** Settings → Agents: each login on this machine, with Sign in where the dashboard can do it. */
|
|
278
330
|
export function SignInList() {
|
|
279
|
-
const { targets, loaded, login } = useStore(signInStore);
|
|
331
|
+
const { targets, loaded, login, structured } = useStore(signInStore);
|
|
280
332
|
// Not over the temporary public link: only the dashboard reached directly signs a backend in, and the link does not
|
|
281
333
|
// serve this read (it was a 404 in the console there, found by smoke-1939-authorize-defaults).
|
|
282
334
|
const offered = signInOffered();
|
|
283
335
|
useEffect(() => { if (offered) void refreshSignIn(); }, []);
|
|
284
336
|
if (!offered || !loaded || !targets.length) return null;
|
|
285
|
-
const
|
|
337
|
+
const active = structured?.login;
|
|
338
|
+
const running = (login && login.phase === "running") || (active && active.phase !== "done" && !(active.phase === "failed" && active.reason));
|
|
286
339
|
return html`<section class="s-group signin-list" aria-label=${tn("listTitle")}>
|
|
287
340
|
<div class="list-head"><span>${tn("listTitle")}</span></div>
|
|
288
341
|
<p class="sub">${tn("listHint")}</p>
|
package/dist/ui/chat-strings.js
CHANGED
|
@@ -116,7 +116,7 @@ register("chat", {
|
|
|
116
116
|
file_empty: "empty file", file_too_large: "over {0} MB", file_too_large_public: "over {0} MB — the most the public link carries in one upload", file_too_many: "at most {0} files per message", file_too_much: "the files together would be over {0} MB", removeFile: "Remove", putBack: "Put back",
|
|
117
117
|
noMessages: "Type a task below.",
|
|
118
118
|
pick: "Choose an agent to start chatting.", noAgents: "Create an agent to start chatting.", newAgent: "New agent", notFound: "No instance called “{0}”", notFoundHint: "It may have been deleted or renamed.", backToList: "Choose an agent",
|
|
119
|
-
details: "Instance details", start: "Start instance", stopInstance: "Stop instance", restart: "Restart instance", delete: "Delete instance…",
|
|
119
|
+
details: "Instance details", fullWidth: "Full width", start: "Start instance", stopInstance: "Stop instance", restart: "Restart instance", delete: "Delete instance…",
|
|
120
120
|
started: "{0} started", stoppedInst: "{0} stopped", restarted: "{0} restarted", instanceDeleted: "{0} was deleted",
|
|
121
121
|
deleteTitle: "Delete {0}?", deleteHelp: "This removes the instance from the fleet and stops it. Its working directory is kept. Type “delete {0}” to confirm.", deleteGo: "Delete", cancel: "Cancel",
|
|
122
122
|
loadFailed: "Failed to load", tour: "Chat tour", tourBtnTitle: "Show the chat tour on an agent's chat", tourNoAgent: "Choose an agent before opening the chat tour.",
|
|
@@ -255,7 +255,7 @@ register("chat", {
|
|
|
255
255
|
file_empty: "空檔案", file_too_large: "超過 {0} MB", file_too_large_public: "超過 {0} MB,這是公開連結單次上傳的上限", file_too_many: "每則訊息最多 {0} 個檔案", file_too_much: "檔案合計會超過 {0} MB", removeFile: "移除", putBack: "放回輸入框",
|
|
256
256
|
noMessages: "直接在下方輸入任務。",
|
|
257
257
|
pick: "選一個 Agent 開始聊天。", noAgents: "先建立一個 Agent,再開始聊天。", newAgent: "新增 Agent", notFound: "沒有叫「{0}」的 instance", notFoundHint: "它可能已被刪除或改名。", backToList: "選擇 Agent",
|
|
258
|
-
details: "Instance 詳細資料", start: "啟動 instance", stopInstance: "停止 instance", restart: "重新啟動 instance", delete: "刪除 instance…",
|
|
258
|
+
details: "Instance 詳細資料", fullWidth: "全寬", start: "啟動 instance", stopInstance: "停止 instance", restart: "重新啟動 instance", delete: "刪除 instance…",
|
|
259
259
|
started: "{0} 已啟動", stoppedInst: "{0} 已停止", restarted: "{0} 已重新啟動", instanceDeleted: "{0} 已刪除",
|
|
260
260
|
deleteTitle: "要刪除 {0} 嗎?", deleteHelp: "這會把 instance 從 fleet 移除並停止它,工作目錄會保留。請輸入「delete {0}」確認。", deleteGo: "刪除", cancel: "取消",
|
|
261
261
|
loadFailed: "載入失敗", tour: "聊天導覽", tourBtnTitle: "在 Agent 的聊天上開啟聊天導覽", tourNoAgent: "請先選一個 Agent,再開啟聊天導覽。",
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
// the public link or a read-only session (the host refuses them too).
|
|
9
9
|
import { html, useMemo, useState } from "/assets/app-html.js";
|
|
10
10
|
import { t, register } from "/assets/app-i18n.js";
|
|
11
|
-
import {
|
|
11
|
+
import { SidePanel } from "/assets/side-panel.js";
|
|
12
12
|
import { toast } from "/assets/ui-toast.js";
|
|
13
13
|
import { nodeText } from "./fleet-org.js";
|
|
14
|
+
import { BUILT_IN_SKINS } from "./fleet-office-skin.js";
|
|
14
15
|
|
|
15
16
|
register("orge", {
|
|
16
17
|
en: {
|
|
@@ -48,7 +49,7 @@ register("orge", {
|
|
|
48
49
|
});
|
|
49
50
|
const te = (k, ...v) => t(`orge.${k}`, ...v);
|
|
50
51
|
export const MAX_OFFICES = 20;
|
|
51
|
-
const STYLES =
|
|
52
|
+
const STYLES = BUILT_IN_SKINS;
|
|
52
53
|
|
|
53
54
|
/** A file's bytes as base64 (in slices: a large picture never goes through one huge string call). */
|
|
54
55
|
export function base64Of(bytes) {
|
|
@@ -147,8 +148,11 @@ export function OfficeEditor({ offices, rev, view, store, packs = [], ask = () =
|
|
|
147
148
|
// the rev under an older draft (a save would then overwrite what it never showed). After a 409 the person chooses
|
|
148
149
|
// (#2431): keep their edits (on the newer rev: Save then replaces what was saved meanwhile, knowingly) or load the
|
|
149
150
|
// newer list — never their draft thrown away unasked.
|
|
150
|
-
|
|
151
|
+
// `saved`: the list the draft is measured against (as read, or the newer one loaded) — the draft holds changes when
|
|
152
|
+
// it differs (#2585: leaving the page beside the docked editor then asks first).
|
|
153
|
+
const [base, setBase] = useState(() => ({ rev, saved: JSON.stringify(offices), draft: offices.map((o) => ({ ...o, members: [...o.members] })) }));
|
|
151
154
|
const draft = base.draft;
|
|
155
|
+
const dirty = JSON.stringify(draft) !== base.saved;
|
|
152
156
|
const setDraft = (next) => setBase((b) => ({ ...b, draft: typeof next === "function" ? next(b.draft) : next }));
|
|
153
157
|
const [open, setOpen] = useState(null); // the office whose members are shown
|
|
154
158
|
const [busy, setBusy] = useState(false);
|
|
@@ -157,14 +161,14 @@ export function OfficeEditor({ offices, rev, view, store, packs = [], ask = () =
|
|
|
157
161
|
const copy = (list) => list.map((o) => ({ ...o, members: [...o.members] }));
|
|
158
162
|
// The choice takes the snapshot in force when it is made: the stream may have read a newer one since the 409 (#2449).
|
|
159
163
|
const inForce = () => current() || conflict;
|
|
160
|
-
const keepMine = () => { const now = inForce(); setBase((b) => ({ rev: now.rev, draft: b.draft })); setConflict(null); setMsg(te("keptMine")); };
|
|
161
|
-
const loadTheirs = () => { const now = inForce(); setBase({ rev: now.rev, draft: copy(now.offices) }); setOpen(null); setConflict(null); setMsg(""); };
|
|
164
|
+
const keepMine = () => { const now = inForce(); setBase((b) => ({ rev: now.rev, saved: JSON.stringify(now.offices), draft: b.draft })); setConflict(null); setMsg(te("keptMine")); };
|
|
165
|
+
const loadTheirs = () => { const now = inForce(); setBase({ rev: now.rev, saved: JSON.stringify(now.offices), draft: copy(now.offices) }); setOpen(null); setConflict(null); setMsg(""); };
|
|
162
166
|
const everyone = useMemo(() => { const m = new Map(); for (const n of [...view.top, ...view.teams.flatMap((tm) => tm.nodes), ...view.rest.nodes]) if (!m.has(n.name)) m.set(n.name, n); return [...m.values()]; }, [view]);
|
|
163
167
|
const set = (id, patch) => setDraft((d) => d.map((o) => (o.id === id ? { ...o, ...patch } : o)));
|
|
164
168
|
const add = () => {
|
|
165
169
|
if (draft.length >= MAX_OFFICES) return;
|
|
166
170
|
const id = newOfficeId(new Set(draft.map((o) => o.id)));
|
|
167
|
-
setDraft((d) => [...d, { id, name: te("newName"), skin: "
|
|
171
|
+
setDraft((d) => [...d, { id, name: te("newName"), skin: "cozy", members: [], layout: null }]); // #2631: a new office is cozy
|
|
168
172
|
setOpen(id);
|
|
169
173
|
};
|
|
170
174
|
// Everything is frozen from Save until its answer (and a 409's re-read) is in (#2424 review): no edit made meanwhile
|
|
@@ -191,7 +195,8 @@ export function OfficeEditor({ offices, rev, view, store, packs = [], ask = () =
|
|
|
191
195
|
};
|
|
192
196
|
const actions = html`<button type="button" class="btn" onClick=${onClose} disabled=${busy}>${te("cancel")}</button>
|
|
193
197
|
<button type="button" class="btn btn-primary" id="oeSave" onClick=${save} disabled=${busy || !!conflict}>${te("save")}</button>`;
|
|
194
|
-
|
|
198
|
+
// #2585: docked on the right on a wide desk (the page beside it in use); the centred dialog elsewhere.
|
|
199
|
+
return html`<${SidePanel} title=${te("title")} onClose=${onClose} actions=${actions} busy=${busy} dirty=${dirty}>
|
|
195
200
|
<p class="note">${te("lead")}</p>
|
|
196
201
|
<fieldset class="oe-form" disabled=${busy}>
|
|
197
202
|
${draft.length ? null : html`<p class="note" id="oeNone">${te("none")}</p>`}
|
|
@@ -201,7 +206,7 @@ export function OfficeEditor({ offices, rev, view, store, packs = [], ask = () =
|
|
|
201
206
|
<input type="text" class="oe-name" maxlength="60" value=${o.name} onInput=${(e) => set(o.id, { name: e.target.value })} /></label>
|
|
202
207
|
<label class="oe-field"><span class="oe-label">${te("styleLabel")}</span>
|
|
203
208
|
<select class="tb-select oe-style" value=${o.skin} onChange=${(e) => set(o.id, { skin: e.target.value })}>
|
|
204
|
-
${STYLES.map((s) => html`<option key=${s} value=${s}>${t(`orgo.style_${s}`)}</option>`)}
|
|
209
|
+
${STYLES.filter((s) => !packs.some((pk) => pk.id === s)).map((s) => html`<option key=${s} value=${s}>${t(`orgo.style_${s}`)}</option>`)}
|
|
205
210
|
${packs.map((pk) => html`<option key=${`pack:${pk.id}`} value=${pk.id}>${pk.name}</option>`)}
|
|
206
211
|
${STYLES.includes(o.skin) || packs.some((pk) => pk.id === o.skin) ? null : html`<option key=${o.skin} value=${o.skin}>${te("styleUnknown", o.skin)}</option>`}</select></label>
|
|
207
212
|
<button type="button" class="btn btn-sm oe-members-btn" aria-expanded=${open === o.id ? "true" : "false"} onClick=${() => setOpen(open === o.id ? null : o.id)}>
|
|
@@ -220,5 +225,5 @@ export function OfficeEditor({ offices, rev, view, store, packs = [], ask = () =
|
|
|
220
225
|
<button type="button" class="btn btn-sm" id="oeKeepMine" onClick=${keepMine} disabled=${busy}>${te("keepMine")}</button>
|
|
221
226
|
<button type="button" class="btn btn-sm" id="oeLoadTheirs" onClick=${loadTheirs} disabled=${busy}>${te("loadTheirs")}</button>
|
|
222
227
|
</div>` : null}
|
|
223
|
-
</${
|
|
228
|
+
</${SidePanel}>`;
|
|
224
229
|
}
|
|
@@ -4,13 +4,18 @@
|
|
|
4
4
|
// overridesBetween), saved with the whole list and the rev read (PUT /ui/offices; a change made meanwhile is a 409).
|
|
5
5
|
// Reset goes back to the pack's own positions. A "show coordinates" overlay draws the 1280×720 logical grid.
|
|
6
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
|
+
//
|
|
7
11
|
// Every handle is a button for the keyboard too: Tab to it, the arrow keys move it (Shift: by 10), Delete removes a
|
|
8
12
|
// waypoint; the selected waypoint's corridors are listed with a way to join or unjoin each. Never offered to a guest,
|
|
9
13
|
// the public link or a read-only session (the host refuses their saves too).
|
|
10
14
|
//
|
|
11
15
|
// The pure part (handles, moving, adding and removing) is exported for tests; LayoutEditor is the overlay.
|
|
12
|
-
import { html, useEffect, useLayoutEffect, useRef, useState } from "/assets/app-html.js";
|
|
16
|
+
import { html, useEffect, useLayoutEffect, useMemo, useRef, useState } from "/assets/app-html.js";
|
|
13
17
|
import { t, register } from "/assets/app-i18n.js";
|
|
18
|
+
import { SidePanel } from "/assets/side-panel.js";
|
|
14
19
|
import { LOGICAL, overridesBetween, own } from "./fleet-office-pack.js";
|
|
15
20
|
|
|
16
21
|
register("orgl", {
|
|
@@ -18,7 +23,8 @@ register("orgl", {
|
|
|
18
23
|
edit: "Edit layout", title: "Edit layout", coords: "Show coordinates", addWaypoint: "Add waypoint", removeWaypoint: "Remove waypoint",
|
|
19
24
|
joinTo: "Join to", join: "Join", unjoin: "Unjoin {0}", corridors: "Corridors", none: "Nothing selected: pick a seat, an item, the door or a waypoint.",
|
|
20
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).",
|
|
21
|
-
seat: "Seat {0} in {1}", furniture: "Item {0}", door: "Door", waypoint: "Waypoint {0}", at: "{0} at {1}, {2}",
|
|
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",
|
|
22
28
|
changed: "Someone changed the offices meanwhile. Your layout is kept: Save again puts it on the current list.",
|
|
23
29
|
failed: "The layout was not saved ({0}).", rateLimited: "Too many saves. Try again in a minute.",
|
|
24
30
|
stale: "This office was removed or its style changed meanwhile: this layout can no longer be saved. Cancel closes it.",
|
|
@@ -27,7 +33,8 @@ register("orgl", {
|
|
|
27
33
|
edit: "編輯配置", title: "編輯配置", coords: "顯示座標", addWaypoint: "新增路徑點", removeWaypoint: "移除路徑點",
|
|
28
34
|
joinTo: "連到", join: "連接", unjoin: "取消連接 {0}", corridors: "走道", none: "尚未選取:請選座位、家具、門或路徑點。",
|
|
29
35
|
reset: "還原成 pack 的配置", save: "儲存配置", cancel: "取消", saved: "配置已儲存。", hint: "拖曳,或用方向鍵移動(Shift:一次 10)。",
|
|
30
|
-
seat: "{1} 的座位 {0}", furniture: "家具 {0}", door: "門", waypoint: "路徑點 {0}", at: "{0},位置 {1}, {2}",
|
|
36
|
+
seat: "{1} 的座位 {0}", furniture: "家具 {0}", night: "家具 {0}(夜間)", door: "門", waypoint: "路徑點 {0}", at: "{0},位置 {1}, {2}",
|
|
37
|
+
nightReset: "回到 pack 的夜間位置",
|
|
31
38
|
changed: "有人在這期間修改了辦公室。你的配置已保留:再按一次儲存,會套用到目前的清單。",
|
|
32
39
|
failed: "配置沒有儲存({0})。", rateLimited: "儲存太頻繁,請一分鐘後再試。",
|
|
33
40
|
stale: "這個辦公室在這期間被移除,或它的風格已改變:這份配置無法再儲存。按取消關閉。",
|
|
@@ -38,11 +45,21 @@ const tl = (k, ...v) => t(`orgl.${k}`, ...v);
|
|
|
38
45
|
// ── The pure part ──
|
|
39
46
|
|
|
40
47
|
const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, Math.round(v)));
|
|
41
|
-
/**
|
|
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
|
+
*/
|
|
42
54
|
export function handlesOf(pack) {
|
|
43
55
|
const out = [];
|
|
44
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 }));
|
|
45
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
|
+
}
|
|
46
63
|
out.push({ key: "door", kind: "door", label: tl("door"), x: pack.door.x, y: pack.door.y });
|
|
47
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 });
|
|
48
65
|
return out;
|
|
@@ -58,6 +75,7 @@ export function moveHandle(pack, key, x, y) {
|
|
|
58
75
|
return { ...pack, areas: { ...pack.areas, [a]: { ...area, seats: area.seats.map((s, j) => (j === i ? p : s)) } } };
|
|
59
76
|
}
|
|
60
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)) };
|
|
61
79
|
if (kind === "door") return { ...pack, door: inCanvas() };
|
|
62
80
|
if (kind === "wp") return { ...pack, waypoints: pack.waypoints.map((w) => (w.id === a ? { ...w, ...inCanvas() } : w)) };
|
|
63
81
|
return pack;
|
|
@@ -80,12 +98,23 @@ export function toggleEdge(pack, a, b) {
|
|
|
80
98
|
const has = (e) => (e[0] === a && e[1] === b) || (e[0] === b && e[1] === a);
|
|
81
99
|
return { ...pack, edges: pack.edges.some(has) ? pack.edges.filter((e) => !has(e)) : [...pack.edges, [a, b]] };
|
|
82
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
|
+
}
|
|
83
107
|
/** What Save keeps: the overrides between the pack and the edited layout (null: the pack's own). */
|
|
84
108
|
export const layoutOverrides = (base, edited) => overridesBetween(base, edited);
|
|
85
109
|
|
|
86
110
|
// ── The overlay ──
|
|
87
111
|
|
|
88
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`;
|
|
89
118
|
const KEY_STEP = { ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1] };
|
|
90
119
|
|
|
91
120
|
/**
|
|
@@ -96,7 +125,7 @@ const KEY_STEP = { ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], Arr
|
|
|
96
125
|
* moves (the canvas redraws it); `onSave(overrides | null)` → { ok } or { ok: false, message }; `onClose()`. `stale` (a message): the
|
|
97
126
|
* office or its style changed underneath the session — no handles, nothing to save, only Cancel.
|
|
98
127
|
*/
|
|
99
|
-
export function LayoutEditor({ base, start, place = null, stale = "", onChange, onSave, onClose }) {
|
|
128
|
+
export function LayoutEditor({ base, start, place = null, stale = "", dock = true, onChange, onSave, onClose }) {
|
|
100
129
|
const [edited, setEdited] = useState(start);
|
|
101
130
|
const [sel, setSel] = useState(null);
|
|
102
131
|
const [coords, setCoords] = useState(true);
|
|
@@ -130,6 +159,7 @@ export function LayoutEditor({ base, start, place = null, stale = "", onChange,
|
|
|
130
159
|
};
|
|
131
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}`); };
|
|
132
161
|
const wpSel = selected && selected.kind === "waypoint" ? selected.key.slice(3) : null;
|
|
162
|
+
const nightSel = selected && selected.kind === "night" ? selected.key.slice(6) : null;
|
|
133
163
|
const save = async () => {
|
|
134
164
|
if (busy) return;
|
|
135
165
|
setBusy(true); setMsg("");
|
|
@@ -149,49 +179,57 @@ export function LayoutEditor({ base, start, place = null, stale = "", onChange,
|
|
|
149
179
|
}
|
|
150
180
|
const at = new Map(edited.waypoints.map((w) => [w.id, w]));
|
|
151
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}>`;
|
|
152
190
|
if (stale) {
|
|
153
|
-
return html`<div class="ol" role="group" aria-label=${tl("title")}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
</div
|
|
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>`;
|
|
157
195
|
}
|
|
158
|
-
|
|
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}>
|
|
159
215
|
<div class="ol-overlay" ref=${overlay}>
|
|
160
216
|
<svg ref=${svg} class="ol-svg" viewBox=${`0 0 ${LOGICAL.w} ${LOGICAL.h}`} width="100%" height="100%"
|
|
161
217
|
onPointerMove=${move} onPointerUp=${up} onPointerCancel=${up} onPointerLeave=${up}>
|
|
162
218
|
${grid}
|
|
163
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} />`)}
|
|
164
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; })}
|
|
165
|
-
${handles.map((h) => html`<
|
|
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"
|
|
166
223
|
aria-label=${tl("at", h.label, h.x, h.y)} aria-pressed=${sel === h.key ? "true" : "false"}
|
|
167
224
|
onPointerDown=${down(h.key)} onKeyDown=${keyOn(h)} onFocus=${() => setSel(h.key)}>
|
|
168
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})`} />`
|
|
169
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)} />`
|
|
170
228
|
: html`<circle cx=${h.x} cy=${h.y} r=${h.kind === "seat" ? 9 : 7} />`}
|
|
171
229
|
${coords && sel === h.key ? html`<text class="ol-coord" x=${h.x + 12} y=${h.y - 10}>${h.x}, ${h.y}</text>` : null}
|
|
172
230
|
</g>`)}
|
|
173
231
|
</svg>
|
|
174
232
|
</div>
|
|
175
|
-
|
|
176
|
-
<label class="check"><input type="checkbox" class="ol-coords" checked=${coords} onChange=${() => setCoords((v) => !v)} /><span>${tl("coords")}</span></label>
|
|
177
|
-
<button type="button" class="btn btn-sm ol-add" onClick=${add} disabled=${busy}>${tl("addWaypoint")}</button>
|
|
178
|
-
<span class="muted ol-sel" aria-live="polite">${selected ? tl("at", selected.label, selected.x, selected.y) : tl("none")}</span>
|
|
179
|
-
${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>
|
|
180
|
-
<label class="oo-pick"><span class="oo-pick-label">${tl("joinTo")}</span>
|
|
181
|
-
<select class="tb-select ol-join-to" value=${joinTo} onChange=${(e) => setJoinTo(e.target.value)}>
|
|
182
|
-
<option value="">—</option>
|
|
183
|
-
${edited.waypoints.filter((w) => w.id !== wpSel).map((w) => html`<option key=${w.id} value=${w.id}>${w.id}</option>`)}
|
|
184
|
-
</select></label>
|
|
185
|
-
<button type="button" class="btn btn-sm ol-join" disabled=${busy || !joinTo || joinTo === wpSel} onClick=${() => { change(toggleEdge(edited, wpSel, joinTo)); setJoinTo(""); }}>${tl("join")}</button>
|
|
186
|
-
<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];
|
|
187
|
-
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}
|
|
188
|
-
<span class="ol-actions">
|
|
189
|
-
<button type="button" class="btn btn-sm ol-reset" onClick=${reset} disabled=${busy}>${tl("reset")}</button>
|
|
190
|
-
<button type="button" class="btn btn-sm ol-cancel" onClick=${onClose} disabled=${busy}>${tl("cancel")}</button>
|
|
191
|
-
<button type="button" class="btn btn-sm btn-primary ol-save" onClick=${save} disabled=${busy}>${tl("save")}</button>
|
|
192
|
-
</span>
|
|
193
|
-
<span class="muted oo-note">${tl("hint")}</span>
|
|
194
|
-
${msg ? html`<p class="note ol-msg" role="status">${msg}</p>` : null}
|
|
195
|
-
</div>
|
|
233
|
+
${panel(controls, actions, tail)}
|
|
196
234
|
</div>`;
|
|
197
235
|
}
|
|
@@ -199,7 +199,9 @@ export function autoLayout(rooms, { width = 960 } = {}) {
|
|
|
199
199
|
}
|
|
200
200
|
});
|
|
201
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;
|
|
202
|
-
|
|
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 };
|
|
203
205
|
}
|
|
204
206
|
|
|
205
207
|
/**
|
|
@@ -318,7 +320,8 @@ const MAX_SEEN = 2000;
|
|
|
318
320
|
* The items in the air. `sync(items, now, still)` takes fleet-traffic.js's store items: each new one (a message, a
|
|
319
321
|
* delegation's work, its report coming back) starts one trip from its sender's desk to its receiver's; a delegation with
|
|
320
322
|
* no report (stale) and anything seen before start none; `still` (reduced motion) starts none. `frame(now, L)` the
|
|
321
|
-
* 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.
|
|
322
325
|
*/
|
|
323
326
|
export function createFlights({ max = FLIGHT.MAX } = {}) {
|
|
324
327
|
const seen = new Set();
|
|
@@ -344,7 +347,7 @@ export function createFlights({ max = FLIGHT.MAX } = {}) {
|
|
|
344
347
|
if (!f.pts) return false;
|
|
345
348
|
const t = (now - f.start) / f.ms;
|
|
346
349
|
if (t >= 1) return false;
|
|
347
|
-
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 }); }
|
|
348
351
|
return true;
|
|
349
352
|
});
|
|
350
353
|
return out;
|