@songsid/agend 2.3.3-beta.4 → 2.3.3-beta.6
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/dist/agent-cli.js +13 -112
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +0 -1
- package/dist/agent-endpoint.js +1 -9
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.d.ts +3 -6
- package/dist/agent-whats-new.js +7 -18
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +0 -7
- package/dist/auth-api.js +1 -15
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/claude-code.js +4 -2
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex.d.ts +1 -0
- package/dist/backend/codex.js +106 -15
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/grok.js +57 -11
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/types.d.ts +35 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/bundled-skills.d.ts +4 -6
- package/dist/bundled-skills.js +10 -14
- package/dist/bundled-skills.js.map +1 -1
- package/dist/channel/mcp-server.js +5 -5
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.d.ts +1 -3
- package/dist/channel/mcp-tools.js +4 -58
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +6 -3
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.d.ts +17 -3
- package/dist/daemon.js +76 -107
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard-live.d.ts +1 -1
- package/dist/dashboard-live.js +3 -3
- package/dist/dashboard-live.js.map +1 -1
- package/dist/dashboard-permissions.d.ts +1 -1
- package/dist/dashboard-permissions.js +1 -3
- package/dist/dashboard-permissions.js.map +1 -1
- package/dist/fleet-manager.d.ts +31 -44
- package/dist/fleet-manager.js +94 -227
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +11 -1
- package/dist/general-knowledge/skills/session-management/SKILL.md +6 -0
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +13 -204
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +26 -63
- package/dist/instance-lifecycle.d.ts +6 -0
- package/dist/instance-lifecycle.js +20 -4
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +14 -4
- package/dist/locale.js.map +1 -1
- package/dist/outbound-handlers.js +19 -7
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +0 -62
- package/dist/outbound-schemas.js +3 -42
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +0 -9
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +2 -0
- package/dist/public-web-link.js +5 -0
- package/dist/public-web-link.js.map +1 -1
- package/dist/search/search-docs.d.ts +1 -1
- package/dist/search/search-docs.js +2 -2
- package/dist/search/search-docs.js.map +1 -1
- package/dist/settings-api.d.ts +6 -0
- package/dist/settings-api.js +14 -2
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +3 -1
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-confirmation.d.ts +17 -3
- package/dist/settings-confirmation.js +8 -5
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-http-confirmation.js +31 -1
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/tool-permissions.js +3 -10
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +27 -7
- package/dist/topic-commands.js +94 -36
- package/dist/topic-commands.js.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/ui/chat-ambient.js +4 -4
- package/dist/ui/chat-commands.js +3 -2
- package/dist/ui/chat-store.js +4 -1
- package/dist/ui/chat-strings.js +12 -46
- package/dist/ui/chat-thread.js +51 -59
- package/dist/ui/fleet-office-skin.js +37 -14
- package/dist/ui/fleet-office.js +31 -7
- package/dist/ui/html-attachment.js +22 -8
- package/dist/ui/panel-chat.js +21 -50
- package/dist/ui/preview-panel.js +12 -21
- package/dist/ui/preview.js +10 -3
- package/dist/ui/search-dialog.js +30 -14
- package/dist/ui/settings-appearance.js +1 -3
- package/dist/ui/settings-confirm.js +18 -0
- package/dist/ui/settings-guests.js +13 -4
- package/dist/ui/settings-learn.js +1 -1
- package/dist/ui/settings-mods.js +5 -12
- package/dist/ui/settings-registry.js +0 -1
- package/dist/ui/settings-strings.js +6 -6
- package/dist/ui/shared/app-i18n.js +13 -2
- package/dist/ui/shared/app-prefs.js +17 -17
- package/dist/ui/shared/app-shell.js +47 -9
- package/dist/ui/shared/app-store.js +6 -2
- package/dist/ui/shared/app.css +19 -36
- package/dist/ui/shared/app.js +5 -4
- package/dist/ui/shared/instance-nav.js +9 -9
- package/dist/ui/shared/instance-switch.js +8 -2
- package/dist/ui/shared/panel-view.js +22 -8
- package/dist/ui/shared/view-strings.js +6 -0
- package/dist/usage/usage-api.d.ts +3 -0
- package/dist/usage/usage-api.js +5 -1
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/view-api.d.ts +2 -0
- package/dist/view-api.js +64 -0
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +9 -17
- package/dist/web-api.js +255 -371
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-tools.d.ts +0 -2
- package/dist/web-appearance-tools.js +2 -5
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance-view.d.ts +0 -6
- package/dist/web-appearance-view.js +0 -1
- package/dist/web-appearance-view.js.map +1 -1
- package/dist/web-appearance.d.ts +14 -7
- package/dist/web-appearance.js +5 -6
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-asset-parts.d.ts +1 -17
- package/dist/web-asset-parts.js +1 -15
- package/dist/web-asset-parts.js.map +1 -1
- package/dist/web-auth.js +1 -7
- package/dist/web-auth.js.map +1 -1
- package/dist/web-chat-history.d.ts +3 -7
- package/dist/web-chat-history.js +10 -17
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +9 -1
- package/dist/web-commands.js +13 -0
- package/dist/web-commands.js.map +1 -1
- package/dist/web-experimental.d.ts +2 -11
- package/dist/web-experimental.js +8 -36
- package/dist/web-experimental.js.map +1 -1
- package/dist/web-guest-access.d.ts +40 -2
- package/dist/web-guest-access.js +50 -14
- package/dist/web-guest-access.js.map +1 -1
- package/dist/web-mod-bundle.d.ts +2 -45
- package/dist/web-mod-bundle.js +15 -63
- package/dist/web-mod-bundle.js.map +1 -1
- package/dist/web-mod-store.d.ts +3 -48
- package/dist/web-mod-store.js +30 -97
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-prefs.d.ts +13 -0
- package/dist/web-prefs.js +38 -0
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +6 -13
- package/dist/web-preview.js +12 -23
- package/dist/web-preview.js.map +1 -1
- package/dist/web-session.d.ts +11 -3
- package/dist/web-session.js +22 -11
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +1 -0
- package/dist/web-settings-patch.js +6 -3
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +0 -13
- package/dist/web-settings-tools.js +12 -84
- package/dist/web-settings-tools.js.map +1 -1
- package/package.json +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +0 -376
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +0 -672
- package/dist/ui/app-effects.js +0 -251
- package/dist/ui/chat-effect-posters.js +0 -42
- package/dist/ui/chat-effects.js +0 -167
- package/dist/ui/effect-trial-controller.js +0 -124
- package/dist/ui/effect-trial-offer.js +0 -2
- package/dist/ui/effect-trial-panel.js +0 -40
- package/dist/ui/effects-waker.js +0 -2
- package/dist/ui/settings-effects.js +0 -202
- package/dist/ui/settings-owner-effects.js +0 -82
- package/dist/ui/shared/effect-app-runtime.js +0 -299
- package/dist/ui/shared/effect-baseline.js +0 -64
- package/dist/ui/shared/effect-chat-runtime.js +0 -162
- package/dist/ui/shared/effect-cursor.js +0 -126
- package/dist/ui/shared/effect-display-metadata.js +0 -39
- package/dist/ui/shared/effect-frame.js +0 -146
- package/dist/ui/shared/effect-geometry.js +0 -137
- package/dist/ui/shared/effect-host-style.js +0 -10
- package/dist/ui/shared/effect-owner-regions.js +0 -74
- package/dist/ui/shared/effect-owner-source.js +0 -122
- package/dist/ui/shared/effect-policy.js +0 -151
- package/dist/ui/shared/effect-poster-reader.js +0 -98
- package/dist/ui/shared/effect-posters.js +0 -117
- package/dist/ui/shared/effect-profiler.js +0 -184
- package/dist/ui/shared/effect-profiles.js +0 -65
- package/dist/ui/shared/effect-registry.js +0 -545
- package/dist/ui/shared/effect-runtime-loader.js +0 -236
- package/dist/ui/shared/effect-safety.js +0 -153
- package/dist/ui/shared/effect-stock-snapshot.js +0 -43
- package/dist/ui/shared/effect-stock-trust.js +0 -217
- package/dist/ui/shared/effect-trial-shadow.js +0 -62
- package/dist/ui/shared/effect-trial-source.js +0 -103
- package/dist/ui/shared/effect-trial-stage.js +0 -170
- package/dist/ui/shared/effect-trial.css +0 -40
- package/dist/ui/shared/feature-flags.js +0 -127
- package/dist/ui/shared/owner-effect-prefs.js +0 -117
- package/dist/web-effect-bootstrap.d.ts +0 -12
- package/dist/web-effect-bootstrap.js +0 -49
- package/dist/web-effect-bootstrap.js.map +0 -1
- package/dist/web-effect-bundle.d.ts +0 -28
- package/dist/web-effect-bundle.js +0 -171
- package/dist/web-effect-bundle.js.map +0 -1
- package/dist/web-effect-canonical.d.ts +0 -8
- package/dist/web-effect-canonical.js +0 -27
- package/dist/web-effect-canonical.js.map +0 -1
- package/dist/web-effect-library.d.ts +0 -114
- package/dist/web-effect-library.js +0 -530
- package/dist/web-effect-library.js.map +0 -1
- package/dist/web-effect-playback.d.ts +0 -30
- package/dist/web-effect-playback.js +0 -71
- package/dist/web-effect-playback.js.map +0 -1
- package/dist/web-effect-poster.d.ts +0 -8
- package/dist/web-effect-poster.js +0 -52
- package/dist/web-effect-poster.js.map +0 -1
- package/dist/web-effect-profiles.d.ts +0 -118
- package/dist/web-effect-profiles.js +0 -181
- package/dist/web-effect-profiles.js.map +0 -1
- package/dist/web-effect-service.d.ts +0 -93
- package/dist/web-effect-service.js +0 -335
- package/dist/web-effect-service.js.map +0 -1
- package/dist/web-effect-tools.d.ts +0 -48
- package/dist/web-effect-tools.js +0 -129
- package/dist/web-effect-tools.js.map +0 -1
- package/dist/web-effect-trials.d.ts +0 -86
- package/dist/web-effect-trials.js +0 -204
- package/dist/web-effect-trials.js.map +0 -1
- package/dist/web-effect-upload.d.ts +0 -27
- package/dist/web-effect-upload.js +0 -185
- package/dist/web-effect-upload.js.map +0 -1
- package/dist/web-effect-worker.d.ts +0 -8
- package/dist/web-effect-worker.js +0 -170
- package/dist/web-effect-worker.js.map +0 -1
- package/dist/web-effect.d.ts +0 -103
- package/dist/web-effect.js +0 -153
- package/dist/web-effect.js.map +0 -1
- package/dist/web-experimental-http.d.ts +0 -2
- package/dist/web-experimental-http.js +0 -12
- package/dist/web-experimental-http.js.map +0 -1
- package/dist/web-mod-agent.d.ts +0 -50
- package/dist/web-mod-agent.js +0 -207
- package/dist/web-mod-agent.js.map +0 -1
package/dist/ui/chat-thread.js
CHANGED
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
// Unrelated updates (a new message, another message's tick) stop nothing (#1306 §6.2, #1408 §4).
|
|
9
9
|
import "./chat-render.js";
|
|
10
10
|
import "./preview.js";
|
|
11
|
-
import {
|
|
12
|
-
import { loadHtmlAttachment } from "./html-attachment.js";
|
|
11
|
+
import { HTML_PREVIEW_MAX, loadHtmlAttachment, sizeLabel } from "./html-attachment.js";
|
|
13
12
|
import { chatTime, chatTimeFull, onDayChange } from "./chat-time.js";
|
|
14
13
|
import { textLinkSegments } from "/assets/app-route.js";
|
|
15
14
|
|
|
@@ -24,6 +23,12 @@ export const TEXT_PREVIEW_LINES = 200;
|
|
|
24
23
|
export const TEXT_PREVIEW_NODES = 4000;
|
|
25
24
|
/** A csv/tsv wider than this is shown as text, as Markdown tables are capped at 20 columns. */
|
|
26
25
|
export const TEXT_PREVIEW_COLUMNS = 50;
|
|
26
|
+
/**
|
|
27
|
+
* #2863: the most a text card reads (bytes). It stays 1 MiB while an HTML preview goes to 8 MiB (and 50 MiB on
|
|
28
|
+
* "Preview anyway"): a text card reads, parses and formats the file on this page's own thread (Markdown, highlighting,
|
|
29
|
+
* a CSV table; Show all draws every line), where an HTML preview runs in its own frame. A bigger file is a download.
|
|
30
|
+
*/
|
|
31
|
+
export const TEXT_CARD_MAX = 1024 * 1024;
|
|
27
32
|
const P = () => globalThis.AgendPreview;
|
|
28
33
|
/** #1672: a status line longer than this (lines, or characters) is folded until it is opened. */
|
|
29
34
|
export const STATUS_FOLD_LINES = 6;
|
|
@@ -120,7 +125,7 @@ export function createThread(list, scroller, opts) {
|
|
|
120
125
|
if (el.textContent !== v) el.textContent = v;
|
|
121
126
|
}
|
|
122
127
|
// #1770: "today" ends at the fleet's midnight; the labels are rewritten then (and on every render), while there are any.
|
|
123
|
-
let offDay = null
|
|
128
|
+
let offDay = null;
|
|
124
129
|
function armDay() {
|
|
125
130
|
if (nodes.size && !offDay) offDay = onDayChange(() => { for (const v of nodes.values()) timeLabel(v.node); });
|
|
126
131
|
else if (!nodes.size && offDay) { offDay(); offDay = null; }
|
|
@@ -133,8 +138,6 @@ export function createThread(list, scroller, opts) {
|
|
|
133
138
|
// is kept by: it is written into the node (timeLabels) — crossing midnight relabels in place and reloads no preview.
|
|
134
139
|
const time = x.ts ? `<span class="time" data-ts="${escAttr(x.ts)}" title="${escAttr(chatTimeFull(x.ts))}"></span>` : `<span class="time"></span>`;
|
|
135
140
|
if (x.outgoing) return outgoingHtml(x);
|
|
136
|
-
const trial = readCanvasTrialMessage(x);
|
|
137
|
-
if (trial) return canvasWorkerEnabled() ? trialHtml(trial, time, x) : "";
|
|
138
141
|
if (x.role === "status") return statusHtml(x, time);
|
|
139
142
|
const u = opts.isUser(x);
|
|
140
143
|
// renderMarkdown escapes the whole text before it adds any tag of its own (chat-render.js).
|
|
@@ -213,26 +216,7 @@ export function createThread(list, scroller, opts) {
|
|
|
213
216
|
? `<button type="button" class="click-link" data-act="showButtons" data-arg="${escAttr(of)}" title="${escAttr(tr("chat.clickShow"))}">${inner}</button>`
|
|
214
217
|
: `<span class="click-text">${inner}</span>`}</div>`;
|
|
215
218
|
}
|
|
216
|
-
|
|
217
|
-
const trialReasons = new Set(["checking", "localAdmin", "chatChanged", "busy", "safe", "effectsOff", "reducedMotion", "saveData", "unsupported", "transport", "inactive", "dialog", "expired"]);
|
|
218
|
-
function trialStatus(message) {
|
|
219
|
-
const reason = opts.effectTrialStatus?.(message);
|
|
220
|
-
return reason === "ready" || trialReasons.has(reason) ? reason : opts.canTryEffect?.(message) === true ? "ready" : "localAdmin";
|
|
221
|
-
}
|
|
222
|
-
function trialHtml(offer, time, message) {
|
|
223
|
-
const reason = trialNow() >= offer.expiresAt ? "expired" : PUBLIC_LINK() ? "localAdmin" : trialStatus(message);
|
|
224
|
-
const rawFailure = opts.effectTrialFailure?.(message), failure = typeof rawFailure === "string" ? rawFailure.replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 80) : null;
|
|
225
|
-
const available = reason === "ready", fix = !PUBLIC_LINK() && !available && reason !== "expired" && reason !== "checking" && typeof opts.fixEffectTrial === "function";
|
|
226
|
-
const label = available ? "chat.effectTrialStart" : reason === "busy" ? "chat.effectTrialStopOther" : reason === "localAdmin" ? "chat.effectTrialSignIn" : reason === "reducedMotion" ? "chat.effectTrialPlayMotion" : "chat.effectTrialSettings";
|
|
227
|
-
const button = available || fix ? `<button type="button" class="btn btn-sm effect-trial-start" data-act="effectTrial" data-arg="${escAttr(msgKey(message))}">${escAttr(tr(label))}</button>` : "";
|
|
228
|
-
return `<div class="msg status-line"><div class="meta">${time}</div><div class="body"><div class="effect-trial-offer" data-trust="thread-control"><strong>${escAttr(tr("chat.effectTrial"))}: ${escAttr(offer.title)}</strong><div>${escAttr(tr(available ? "chat.effectTrialLocal" : "chat.effectTrialReason_" + reason))}</div>${failure ? `<div class="effect-trial-failure" role="status">${escAttr(tr("chat.effectTrialStopped"))}: ${escAttr(failure)}</div>` : ""}${button}</div></div></div>`;
|
|
229
|
-
}
|
|
230
|
-
function armTrials() {
|
|
231
|
-
if (trialTimer !== null) clearTimeout(trialTimer); trialTimer = null;
|
|
232
|
-
if (!canvasWorkerEnabled()) return;
|
|
233
|
-
const now = trialNow(), next = msgs.map(x => readCanvasTrialMessage(x)).filter(x => x && x.expiresAt > now).reduce((n, x) => Math.min(n, x.expiresAt), Infinity);
|
|
234
|
-
if (Number.isFinite(next)) trialTimer = setTimeout(() => { trialTimer = null; render(msgs); }, Math.max(1, Math.min(2147483647, next - now)));
|
|
235
|
-
}
|
|
219
|
+
|
|
236
220
|
/**
|
|
237
221
|
* #1672: a status line (another instance's message, a daemon notice) is muted, read-only plain text — never Markdown,
|
|
238
222
|
* a preview card, a button or a person's bubble. Checked before isUser, which reads the sender: a fleet name need not
|
|
@@ -428,7 +412,7 @@ export function createThread(list, scroller, opts) {
|
|
|
428
412
|
if (reading) return;
|
|
429
413
|
if (text !== null) { body.hidden = !body.hidden; show.textContent = tr(body.hidden ? "chat.tcShow" : "chat.tcHide"); return; }
|
|
430
414
|
const mine = reading = ++reads; note.textContent = tr("chat.pvLoading");
|
|
431
|
-
const r = await loadHtmlAttachment({ id: att.id, name: att.name, size: att.size });
|
|
415
|
+
const r = await loadHtmlAttachment({ id: att.id, name: att.name, size: att.size }, undefined, { ask: TEXT_CARD_MAX, max: TEXT_CARD_MAX });
|
|
432
416
|
if (reading !== mine) return; // dropped (its chat left meanwhile): a new click reads again
|
|
433
417
|
reading = 0;
|
|
434
418
|
if (!ph.isConnected) return; // the card left the page while it was read: nothing to draw
|
|
@@ -484,6 +468,8 @@ export function createThread(list, scroller, opts) {
|
|
|
484
468
|
const head = el("div", "pv-head"); head.setAttribute("data-trust", "thread-control");
|
|
485
469
|
const label = el("span", "pv-label", att ? `${tr("chat.pvHtml")} · ${att.name}` : tr("chat.pvHtml"));
|
|
486
470
|
const run = el("button", "btn btn-sm pv-run", tr("chat.pvPreview")); run.type = "button";
|
|
471
|
+
// #2863: a large file (over 8 MB) is previewed only on this second click, in the card or the panel as first asked.
|
|
472
|
+
const anywayB = el("button", "btn btn-sm pv-anyway", tr("chat.pvAnyway")); anywayB.type = "button"; anywayB.hidden = true;
|
|
487
473
|
const stopB = el("button", "btn btn-sm pv-stop", tr("chat.pvStop")); stopB.type = "button"; stopB.hidden = true;
|
|
488
474
|
// An attachment already has its download link (the file itself) right above the card.
|
|
489
475
|
const dl = att ? null : el("button", "btn btn-sm btn-ghost pv-dl", tr("chat.pvDownload"));
|
|
@@ -496,7 +482,7 @@ export function createThread(list, scroller, opts) {
|
|
|
496
482
|
const optB = el("button", "chip-btn pv-opt"); optB.type = "button";
|
|
497
483
|
const neverB = el("button", "chip-btn pv-never"); neverB.type = "button";
|
|
498
484
|
menu.append(sum, optB, neverB);
|
|
499
|
-
head.append(label, run, stopB, ...(toPanel ? [toPanel] : []), ...(dl ? [dl] : []), menu);
|
|
485
|
+
head.append(label, run, anywayB, stopB, ...(toPanel ? [toPanel] : []), ...(dl ? [dl] : []), menu);
|
|
500
486
|
const note = el("div", "pv-note"); note.setAttribute("data-trust", "thread-control");
|
|
501
487
|
// #1568: the banner, with its "I understand" (hidden until the next sign-in; the frame's border stays).
|
|
502
488
|
const banner = el("div", "pv-banner"); banner.setAttribute("data-trust", "thread-control"); banner.hidden = true;
|
|
@@ -508,45 +494,64 @@ export function createThread(list, scroller, opts) {
|
|
|
508
494
|
// Reading an attachment: one read per card at a time (a second click while it reads does nothing), and a read
|
|
509
495
|
// whose card was stopped, replaced or left meanwhile is dropped (`reading` moved on, or the card left the page).
|
|
510
496
|
let reading = 0, reads = 0;
|
|
497
|
+
// #2863: where "Preview anyway" runs it ("card" | "panel") and what the card says meanwhile; null: not asked.
|
|
498
|
+
// `tooBig`: over the 50 MB ceiling (listed, or read) — the card says so and offers only the file's download.
|
|
499
|
+
let large = null, largeNote = "", tooBig = !!att && typeof att.size === "number" && att.size > HTML_PREVIEW_MAX;
|
|
511
500
|
const refresh = (reason) => {
|
|
512
501
|
const a = P().availability();
|
|
513
502
|
const going = state === "starting" || state === "running";
|
|
514
503
|
const inPanel = !!panel && panel.shown() === key; // #1481: the panel shows this block; the card points there
|
|
515
|
-
|
|
516
|
-
if (
|
|
504
|
+
if (going || reading) large = null;
|
|
505
|
+
if (reason == null && large) reason = largeNote;
|
|
506
|
+
if (reason == null && tooBig) reason = tr("chat.pvAttOver");
|
|
507
|
+
run.hidden = !a.ok || going || inPanel || !!reading || !!large || tooBig;
|
|
508
|
+
anywayB.hidden = !a.ok || !large;
|
|
509
|
+
if (toPanel) toPanel.hidden = inPanel || !!reading || tooBig;
|
|
517
510
|
stopB.hidden = !going && !reading;
|
|
518
511
|
banner.hidden = !going || P().bannerAcked();
|
|
519
512
|
note.textContent = reason != null ? reason : reading ? tr("chat.pvLoading") : going ? (state === "starting" ? tr("chat.pvStarting") : "") : inPanel ? tr("chat.pvInPanel") : a.ok ? "" : a.reason;
|
|
520
513
|
optB.textContent = P().optedIn() ? tr("chat.pvDisallow") : tr("chat.pvAllow");
|
|
521
514
|
neverB.textContent = P().never() ? tr("chat.pvNeverUndo") : tr("chat.pvNever");
|
|
522
515
|
};
|
|
523
|
-
/**
|
|
524
|
-
const
|
|
516
|
+
/** Over 8 MB: the card asks, and "Preview anyway" runs it where `target` ("card" | "panel") says. */
|
|
517
|
+
const askLarge = (target, reason) => { large = target; largeNote = reason; refresh(reason); };
|
|
518
|
+
/**
|
|
519
|
+
* The HTML, then `then(code)`: at once for a fence or an attachment already read; otherwise read it first — a large
|
|
520
|
+
* one only once `anyway` (its listed size asks before anything is fetched).
|
|
521
|
+
*/
|
|
522
|
+
const withCode = async (then, target = "card", anyway = false) => {
|
|
525
523
|
if (code != null) { then(code); return; }
|
|
526
524
|
if (reading) return;
|
|
527
525
|
const mine = reading = ++reads;
|
|
528
526
|
refresh();
|
|
529
|
-
const r = await loadHtmlAttachment(att);
|
|
527
|
+
const r = await loadHtmlAttachment(att, undefined, { anyway });
|
|
530
528
|
if (reading !== mine || !ph.isConnected) return;
|
|
531
529
|
reading = 0;
|
|
530
|
+
if (!r.ok && r.reason === "large") { askLarge(target, trf("chat.pvLarge", sizeLabel(r.size))); return; }
|
|
531
|
+
if (!r.ok && r.reason === "over") { tooBig = true; refresh(); return; }
|
|
532
532
|
if (!r.ok) { refresh(attReason(r.reason)); return; }
|
|
533
533
|
code = r.code;
|
|
534
534
|
refresh();
|
|
535
535
|
then(code);
|
|
536
536
|
};
|
|
537
|
-
const ui = { state: (name, reason) => {
|
|
537
|
+
const ui = { state: (name, reason) => {
|
|
538
|
+
state = name === "starting" || name === "running" ? name : "idle";
|
|
539
|
+
if (name === "large") { askLarge("card", reason || ""); return; }
|
|
540
|
+
large = null; refresh(reason || null);
|
|
541
|
+
} };
|
|
538
542
|
// Nothing is read for Preview on a device that does not allow previews: the button is not even shown there.
|
|
539
543
|
// A Preview click is newer than any Open in panel still reading (#1704): it claims the panel's turn too — and, its
|
|
540
544
|
// own HTML read, starts only if no newer click claimed meanwhile and no preview was started since (the panel's own
|
|
541
545
|
// Preview): a late read never undoes a newer choice. A close is neither, so it does not drop this click.
|
|
542
|
-
|
|
546
|
+
const preview = (anyway) => {
|
|
543
547
|
if (!P().availability().ok || reading) return; // this card already reading: that click stays the one
|
|
544
548
|
const mine = panel ? (panel.claim(), panel.claims()) : 0, started = P().startCount();
|
|
545
549
|
withCode((c) => {
|
|
546
550
|
if ((panel && panel.claims() !== mine) || P().startCount() !== started) { refresh(); return; }
|
|
547
|
-
P().start(key, holder, c, ui);
|
|
548
|
-
});
|
|
551
|
+
if (anyway) P().start(key, holder, c, ui, { anyway: true }); else P().start(key, holder, c, ui);
|
|
552
|
+
}, "card", anyway);
|
|
549
553
|
};
|
|
554
|
+
run.onclick = () => preview(false);
|
|
550
555
|
stopB.onclick = () => { if (reading) { reading = 0; refresh(); } else P().stop(key, "stopped", ""); };
|
|
551
556
|
// #1704: this click runs it in the panel, as Preview there would — a running preview moves (it stops here and
|
|
552
557
|
// starts there), an idle one starts there. Whether it can run is the panel's to say: a device that has not allowed
|
|
@@ -554,15 +559,18 @@ export function createThread(list, scroller, opts) {
|
|
|
554
559
|
// An attachment's HTML is read first. The click claims the panel at once, so a newer click (even one still reading)
|
|
555
560
|
// wins: if the panel changed (another click, block, close) or any preview started meanwhile, this one is dropped —
|
|
556
561
|
// a late read never undoes a newer click.
|
|
557
|
-
|
|
562
|
+
const openInPanel = (anyway) => {
|
|
558
563
|
if (reading) return; // this card already reading: that click stays the one
|
|
559
564
|
if (P().running(key)) P().stop(key, "stopped", "");
|
|
560
565
|
const at = panel.claim(), started = P().startCount();
|
|
561
566
|
withCode((c) => {
|
|
562
567
|
if (panel.version() !== at || P().startCount() !== started) { refresh(); return; }
|
|
563
|
-
|
|
564
|
-
|
|
568
|
+
large = null; refresh();
|
|
569
|
+
panel.open({ key, instance: x.instance, msgKey: msgKey(x), ...(att ? { att } : { n: source.n }), code: c, sender: x.sender, ts: x.ts, ...(anyway ? { anyway: true } : {}) }, { run: true });
|
|
570
|
+
}, "panel", anyway);
|
|
565
571
|
};
|
|
572
|
+
if (toPanel) toPanel.onclick = () => openInPanel(false);
|
|
573
|
+
anywayB.onclick = () => { const where = large; large = null; if (where === "panel" && toPanel) openInPanel(true); else preview(true); };
|
|
566
574
|
if (dl) dl.onclick = () => opts.download(code);
|
|
567
575
|
ack.onclick = () => P().ackBanner(); // every card and the panel hear it (onChange)
|
|
568
576
|
optB.onclick = () => { menu.open = false; opts.setPreviewOptIn(!P().optedIn()); };
|
|
@@ -668,7 +676,6 @@ export function createThread(list, scroller, opts) {
|
|
|
668
676
|
function render(next, renderOpts = {}) {
|
|
669
677
|
const stick = pinned || nearBottom();
|
|
670
678
|
msgs = next || [];
|
|
671
|
-
armTrials();
|
|
672
679
|
if (!msgs.length) {
|
|
673
680
|
for (const v of nodes.values()) stopIn(v.node);
|
|
674
681
|
nodes.clear();
|
|
@@ -693,11 +700,7 @@ export function createThread(list, scroller, opts) {
|
|
|
693
700
|
}
|
|
694
701
|
/** Put the messages from startIndex() on in the list (nodes reused when unchanged). Returns how many are new. */
|
|
695
702
|
function draw(drawOpts) {
|
|
696
|
-
const shown = msgs.slice(startIndex())
|
|
697
|
-
// Volatile host offer records are hidden off and while their on-only parser is loading.
|
|
698
|
-
const hostOffer = message?.role === "status" && message.sender === "agend" && Object.hasOwn(message, "effectTrial") && message.effectTrial != null;
|
|
699
|
-
return !hostOffer || canvasWorkerEnabled() && readCanvasTrialMessage(message) !== undefined;
|
|
700
|
-
});
|
|
703
|
+
const shown = msgs.slice(startIndex());
|
|
701
704
|
const keep = new Set(shown.map(msgKey));
|
|
702
705
|
for (const [k, v] of nodes) if (!keep.has(k)) { stopIn(v.node); v.node.remove(); nodes.delete(k); }
|
|
703
706
|
let prev = null, added = 0;
|
|
@@ -711,7 +714,7 @@ export function createThread(list, scroller, opts) {
|
|
|
711
714
|
const stopped = have ? stopIn(have.node) : [];
|
|
712
715
|
const node = msgNode(html, x, stopped);
|
|
713
716
|
if (have) have.node.replaceWith(node); else added++;
|
|
714
|
-
have = { html, node, x
|
|
717
|
+
have = { html, node, x }; nodes.set(key, have);
|
|
715
718
|
} else { have.x = x; timeLabel(have.node); }
|
|
716
719
|
const want = prev ? prev.nextSibling : list.firstChild;
|
|
717
720
|
// A node already in the list is being moved: its preview stops first. A new node (still in its template) has none.
|
|
@@ -811,13 +814,6 @@ export function createThread(list, scroller, opts) {
|
|
|
811
814
|
else if (act === "replyButton") {
|
|
812
815
|
const m = /^([0-9a-f]{32}):(\d{1,2})$/.exec(el.dataset.arg || "");
|
|
813
816
|
if (m && opts.clickReplyButton) opts.clickReplyButton(m[1], Number(m[2]));
|
|
814
|
-
} else if (act === "effectTrial") {
|
|
815
|
-
// Only the current host-produced keyed card and a real local click can request admission. No token is read from DOM.
|
|
816
|
-
const key = el.dataset.arg, row = msgs.find(x => msgKey(x) === key), owner = nodes.get(key), node = owner?.node, offer = readCanvasTrialMessage(row);
|
|
817
|
-
if (PUBLIC_LINK() || e.defaultPrevented || e.isTrusted !== true || owner?.trialButton !== el || el.className !== "btn btn-sm effect-trial-start" || !node?.contains(el) || el.closest(".effect-trial-offer")?.getAttribute("data-trust") !== "thread-control"
|
|
818
|
-
|| !offer || trialNow() >= offer.expiresAt) return;
|
|
819
|
-
if (trialStatus(row) === "ready" && opts.canTryEffect?.(row) === true) opts.startEffectTrial?.(row);
|
|
820
|
-
else opts.fixEffectTrial?.(row, trialStatus(row));
|
|
821
817
|
} else if (act === "showButtons") { const x = msgs.find(y => y.buttons && y.buttons.id === el.dataset.arg); if (x) reveal(msgKey(x)); }
|
|
822
818
|
else if (act === "reply") { const x = msgs.find(y => msgKey(y) === el.dataset.arg); if (x) opts.startReply?.(x); }
|
|
823
819
|
else if (act === "jumpQuote") void jumpTo(el.dataset.arg);
|
|
@@ -841,7 +837,7 @@ export function createThread(list, scroller, opts) {
|
|
|
841
837
|
/** The thread goes (the chat left, or another instance opened): every preview in it stops first. */
|
|
842
838
|
/**
|
|
843
839
|
* #2053: kept for a later visit — the chat left as it always did (every preview stops: "leave"), but its drawn nodes
|
|
844
|
-
* stay. Nothing of it runs meanwhile: no timer (day labels, polls,
|
|
840
|
+
* stay. Nothing of it runs meanwhile: no timer (day labels, polls, filling in), no size observer; the
|
|
845
841
|
* caller drops its subscriptions. Shown again: attach, then render — only what changed is drawn.
|
|
846
842
|
*/
|
|
847
843
|
function park() {
|
|
@@ -850,7 +846,6 @@ export function createThread(list, scroller, opts) {
|
|
|
850
846
|
if (P()) P().stopAll("leave");
|
|
851
847
|
for (const node of list.querySelectorAll(".html-card[data-att], .text-card[data-att]")) { const f = cardDrop.get(node); if (f) f(); }
|
|
852
848
|
if (ro) { ro.disconnect(); ro = null; }
|
|
853
|
-
if (trialTimer !== null) clearTimeout(trialTimer); trialTimer = null;
|
|
854
849
|
if (growFrame !== null && typeof cancelAnimationFrame === "function") cancelAnimationFrame(growFrame);
|
|
855
850
|
if (growTimer !== null) clearTimeout(growTimer);
|
|
856
851
|
growTimer = growFrame = null;
|
|
@@ -868,12 +863,9 @@ export function createThread(list, scroller, opts) {
|
|
|
868
863
|
if (ro) { ro.observe(list); ro.observe(sc); }
|
|
869
864
|
}
|
|
870
865
|
/** `own`: only this thread's previews stop (a thread let go while another chat is in view); else every one, as leaving. */
|
|
871
|
-
const redrawFeature = () => { if (!disposed && !parked) render(msgs); };
|
|
872
|
-
const featureOff = onCanvasWorkerChange(redrawFeature), metadataOff = onCanvasMetadataChange(redrawFeature);
|
|
873
866
|
function dispose(own = false) {
|
|
874
|
-
disposed = true;
|
|
867
|
+
disposed = true;
|
|
875
868
|
if (!parked) watchReader(scroller, false);
|
|
876
|
-
if (trialTimer !== null) clearTimeout(trialTimer); trialTimer = null;
|
|
877
869
|
if (ro) ro.disconnect();
|
|
878
870
|
if (P()) { if (own) P().stopIn(list); else P().stopAll("leave"); }
|
|
879
871
|
nodes.clear();
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
// @property {(ctx, item, active, ms, env) => void} [drawFurniture] zones: one furniture item (active: activeWhen holds)
|
|
28
28
|
// @property {(ctx, L, env) => void} [drawForeground] zones: a pack's foreground over the whole 1280×720 scene, above
|
|
29
29
|
// the people, furniture and items in the air, below the crowd counts, name tags and bubbles
|
|
30
|
-
// @property {(ctx, x, y, look, env) => void} [drawTag] zones: a name tag under feet at (x, y)
|
|
30
|
+
// @property {(ctx, x, y, look, env, compact?) => void} [drawTag] zones: a name tag under feet at (x, y) (compact: #2860)
|
|
31
31
|
// @property {(ctx, area, n, env) => void} [drawCrowd] zones: an area's "+n" (who found no spot in it)
|
|
32
32
|
// @property {(ctx, desk, look, ms, env) => void} drawCharacter the occupant in its pose at time `ms` (0: still); in
|
|
33
33
|
// zones `desk` is the character's box at its current position (its feet at desk.seat); walking, `look.heading` is
|
|
@@ -293,21 +293,44 @@ function drawBubble(ctx, x, y, text, env, lift = 0) {
|
|
|
293
293
|
/** A zones name tag's widest text (logical units): longer names are cut with an ellipsis. */
|
|
294
294
|
export const TAG_TEXT_W = 84;
|
|
295
295
|
const TAG_FONT = "600 10px system-ui, sans-serif";
|
|
296
|
+
/**
|
|
297
|
+
* #2860: the compact tag a crowded area keeps on (it used to show tags on hover only, #2513): a smaller font, a smaller
|
|
298
|
+
* plate, a shorter cut, the same level badge.
|
|
299
|
+
*/
|
|
300
|
+
const COMPACT = { font: "500 9px system-ui, sans-serif", textW: 48, pad: 6, top: 2, h: 11 };
|
|
301
|
+
const NORMAL = { font: TAG_FONT, textW: TAG_TEXT_W, pad: 10, top: 3, h: 14 };
|
|
296
302
|
/** The box a name tag under feet at (x, y) takes (what drawTag draws; bubbles keep clear of it, #2513). */
|
|
297
|
-
export function tagBox(ctx, x, y, label) {
|
|
303
|
+
export function tagBox(ctx, x, y, label, compact = false) {
|
|
304
|
+
const k = compact ? COMPACT : NORMAL;
|
|
305
|
+
ctx.save();
|
|
306
|
+
ctx.font = k.font;
|
|
307
|
+
const t = fitText(ctx, label, k.textW), w = Math.ceil((ctx.measureText ? ctx.measureText(t).width : t.length * 6) + k.pad);
|
|
308
|
+
ctx.restore();
|
|
309
|
+
return { x: Math.round(x - w / 2), y: y + k.top, w, h: k.h, text: t };
|
|
310
|
+
}
|
|
311
|
+
/** #2860: what a tag covers with its level badge: on a full tag's top-left corner (half outside); left of a compact one. */
|
|
312
|
+
export function tagFootprint(ctx, box, badge, compact = false) {
|
|
313
|
+
if (!badge) return box;
|
|
314
|
+
if (compact) { const w = badgeWidth(ctx, badge) + 1; return { x: box.x - w, y: box.y, w: box.w + w, h: box.h }; }
|
|
315
|
+
return { x: box.x - 10, y: box.y - 7, w: box.w + 10, h: box.h + 7 };
|
|
316
|
+
}
|
|
317
|
+
function badgeWidth(ctx, badge) {
|
|
318
|
+
if (badge.mark) return 8;
|
|
298
319
|
ctx.save();
|
|
299
|
-
ctx.font =
|
|
300
|
-
const
|
|
320
|
+
ctx.font = BADGE_FONT;
|
|
321
|
+
const w = Math.max(12, Math.ceil((ctx.measureText ? ctx.measureText(badge.text).width : badge.text.length * 5) + 6));
|
|
301
322
|
ctx.restore();
|
|
302
|
-
return
|
|
323
|
+
return w;
|
|
303
324
|
}
|
|
304
325
|
/**
|
|
305
326
|
* #2661: a level badge on a name tag's top-left corner (x, y: the tag's corner), overlapping its edge like an app's
|
|
306
327
|
* badge — it takes none of the tag's width. `badge`: { text, tier } (fleet-office-levels.js badgeOf), or { mark } for a
|
|
307
328
|
* pinned title that hides the number: a small diamond.
|
|
308
329
|
*/
|
|
309
|
-
export function drawBadge(ctx, x, y, badge, pal) {
|
|
330
|
+
export function drawBadge(ctx, x, y, badge, pal, inline = false) {
|
|
310
331
|
if (!badge) return;
|
|
332
|
+
// #2860 inline (a compact tag): (x, y) is the tag's left edge and middle; the badge sits just left of it, whole.
|
|
333
|
+
if (inline) { const w = badgeWidth(ctx, badge); x = x - 1 - w + (badge.mark ? w / 2 : w / 2 - 3); y = y + (badge.mark ? 0 : 1); }
|
|
311
334
|
const c = tierColours(badge.tier, pal);
|
|
312
335
|
ctx.save();
|
|
313
336
|
ctx.globalAlpha = 1;
|
|
@@ -331,18 +354,18 @@ export function drawBadge(ctx, x, y, badge, pal) {
|
|
|
331
354
|
}
|
|
332
355
|
const BADGE_FONT = "700 8px system-ui, sans-serif";
|
|
333
356
|
|
|
334
|
-
/** Zones: a name tag under someone's feet at (x, y). */
|
|
335
|
-
function drawTag(ctx, x, y, look, env) {
|
|
357
|
+
/** Zones: a name tag under someone's feet at (x, y); `compact` (#2860): the smaller one a crowded area keeps on. */
|
|
358
|
+
function drawTag(ctx, x, y, look, env, compact = false) {
|
|
336
359
|
const { pal } = env;
|
|
360
|
+
const b = tagBox(ctx, x, y, look.label, compact);
|
|
337
361
|
ctx.save();
|
|
338
362
|
if (look.dim) ctx.globalAlpha = 0.35;
|
|
339
|
-
ctx.font = TAG_FONT;
|
|
340
|
-
|
|
341
|
-
ctx.
|
|
342
|
-
ctx.
|
|
343
|
-
ctx.fillStyle = pal.text; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(t, x, y + 10.5);
|
|
363
|
+
ctx.font = compact ? COMPACT.font : TAG_FONT;
|
|
364
|
+
ctx.fillStyle = pal.surface; ctx.fillRect(b.x, b.y, b.w, b.h);
|
|
365
|
+
ctx.strokeStyle = look.match ? pal.focus : pal.border; ctx.lineWidth = look.match ? 2 : 1; ctx.strokeRect(b.x + 0.5, b.y + 0.5, b.w - 1, b.h - 1);
|
|
366
|
+
ctx.fillStyle = pal.text; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(b.text, x, b.y + b.h / 2 + 0.5);
|
|
344
367
|
ctx.restore();
|
|
345
|
-
if (look.badge) drawBadge(ctx,
|
|
368
|
+
if (look.badge) compact ? drawBadge(ctx, b.x, b.y + b.h / 2, look.badge, pal, true) : drawBadge(ctx, b.x, b.y, look.badge, pal);
|
|
346
369
|
}
|
|
347
370
|
|
|
348
371
|
/**
|
package/dist/ui/fleet-office.js
CHANGED
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
// any part — an office names it as its style; one this page cannot load is drawn as Desks, and says so.
|
|
34
34
|
// - Crowds (#2513): an area with more people than seats packs the rest into a tighter grid, then counts who is left
|
|
35
35
|
// ("+N", in its corner, never over its name); there idle and asleep people say nothing, at most a few speak (calls
|
|
36
|
-
// first, then work), no bubble covers a name tag, and the tags
|
|
37
|
-
// (per browser) leaves the sleepers out.
|
|
36
|
+
// first, then work), no bubble covers a name tag, and the tags are compact (#2860; one that would touch another shows
|
|
37
|
+
// on hover or focus only). "Hide paused and stopped" (per browser) leaves the sleepers out.
|
|
38
38
|
// - Share… (#2636; an admin, on the dashboard reached directly or over the public link): a link anyone can open to watch an office live, read-only,
|
|
39
39
|
// until it ends (office-share-dialog.js; the page it opens is office-share-page.js).
|
|
40
40
|
// - Edit layout (an admin, a stored office on a zones skin): fleet-office-layout.js, saved as the office's overrides.
|
|
@@ -64,7 +64,7 @@ import { nodeText, stateWords } from "./fleet-org.js";
|
|
|
64
64
|
import { hits, needle } from "./fleet-canvas-layout.js";
|
|
65
65
|
import { AUTO_ID, LOBBY, autoLayout, backingScale, coatOf, createFlights, customLayout, deskAt, desksOf, filterOk, lampOf, layoutOk, lightOf, membersOf,
|
|
66
66
|
officePose, officeRooms, signOf, workOn } from "./fleet-office-model.js";
|
|
67
|
-
import { BUILT_IN_SKINS, CHAR_DEPTH, DEFAULT_PAL, bubbleBox, loadCozySkin, loadPackSkin, modeOf, skinFor, tagBox } from "./fleet-office-skin.js";
|
|
67
|
+
import { BUILT_IN_SKINS, CHAR_DEPTH, DEFAULT_PAL, bubbleBox, loadCozySkin, loadPackSkin, modeOf, skinFor, tagBox, tagFootprint } from "./fleet-office-skin.js";
|
|
68
68
|
import { CALLING, activeFurniture, bubbleAt, hiddenBy, bubbleRank, NIGHT, WANDER, createMail, createWalkers, isNight, linesFor, npcKey, personAt, placeBubbles, thinBubbles, walkTargets, zonesLayout } from "./fleet-office-zones.js";
|
|
69
69
|
import { actsOk, createOfficeStore } from "./fleet-office-store.js";
|
|
70
70
|
import { badgeOf, fixedOf } from "./fleet-office-levels.js";
|
|
@@ -131,6 +131,8 @@ const deviceRatio = () => Math.max(0.5, Math.min(4, Number(globalThis.devicePixe
|
|
|
131
131
|
const SIZE0 = { w: 960, h: 600 };
|
|
132
132
|
/** #2554: All offices' Fit keeps this many CSS px clear round the tiles (the zoom controls' column and a margin). */
|
|
133
133
|
const FIT_PAD = 60;
|
|
134
|
+
/** #2860: how far below its place a crowded area's compact tag drops when a neighbour's is in the way (a tag's height + 1). */
|
|
135
|
+
const COMPACT_ROW = 12;
|
|
134
136
|
/** #2554: in All offices a tile's name tags show (always) from this many CSS px a unit up; below it on hover only. */
|
|
135
137
|
export const TAG_MIN_K = 0.75;
|
|
136
138
|
const PAN_KEYS = { ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1] };
|
|
@@ -418,13 +420,19 @@ export function drawPart(ctx, p, a, ox, oy, ms, still, hot, env, tagsOff = false
|
|
|
418
420
|
if (sk.drawForeground) sk.drawForeground(ctx, p.L, env);
|
|
419
421
|
// Who found no spot in an area: a count in its corner (never over its name).
|
|
420
422
|
for (const [ar, n] of p.L.crowd || []) if (p.L.areas[ar] && sk.drawCrowd) sk.drawCrowd(ctx, p.L.areas[ar], n, env);
|
|
421
|
-
// In an area over capacity
|
|
423
|
+
// In an area over capacity (#2513: a crowd's full tags covered each other) everyone keeps a compact tag (#2860:
|
|
424
|
+
// in Cozy, the default, most of a fleet is in a crowd, and hover-only hid names and levels); one that would touch
|
|
425
|
+
// a tag already drawn, its badge included, is left to hover or focus. The full tags go first.
|
|
422
426
|
const over = p.L.over || new Set();
|
|
427
|
+
const crowded = (q) => over.has(q.d.area) && q.d.key !== hot && !q.look.match;
|
|
423
428
|
const tagged = (q) => !((tagsOff || over.has(q.d.area)) && q.d.key !== hot && !q.look.match);
|
|
429
|
+
const taken = [];
|
|
430
|
+
const touches = (b) => taken.some((o) => b.x < o.x + o.w && o.x < b.x + b.w && b.y < o.y + o.h && o.y < b.y + b.h);
|
|
424
431
|
for (const q of people) {
|
|
425
432
|
if (live.hidden.has(q.look.name) || !tagged(q)) continue;
|
|
426
433
|
sk.drawTag(ctx, q.at.x, q.at.y, q.look, env);
|
|
427
|
-
|
|
434
|
+
const box = tagBox(ctx, q.at.x, q.at.y, q.look.label);
|
|
435
|
+
tags.push(box); taken.push(tagFootprint(ctx, box, q.look.badge));
|
|
428
436
|
}
|
|
429
437
|
// A furniture item's own name (#2574: an NPC drawn as furniture), tagged like a person; none on a tile too small to read.
|
|
430
438
|
// #2698: an item hiding its people carries their tags too, in one row at its tag point (its own first; each keeps
|
|
@@ -434,7 +442,10 @@ export function drawPart(ctx, p, a, ox, oy, ms, still, hot, env, tagsOff = false
|
|
|
434
442
|
for (const f of furniture) {
|
|
435
443
|
const on = people.filter((q) => live.hidden.get(q.look.name) === f.id);
|
|
436
444
|
if (!on.length) {
|
|
437
|
-
if (!tagsOff && f.label) {
|
|
445
|
+
if (!tagsOff && f.label) {
|
|
446
|
+
const nl = npcLook(p, f), box = tagBox(ctx, f.x, f.y, nl.label);
|
|
447
|
+
sk.drawTag(ctx, f.x, f.y, nl, env); tags.push(box); taken.push(tagFootprint(ctx, box, nl.badge));
|
|
448
|
+
}
|
|
438
449
|
continue;
|
|
439
450
|
}
|
|
440
451
|
const order = new Map((f.activeFor || []).map((n, i) => [n, i]));
|
|
@@ -447,11 +458,24 @@ export function drawPart(ctx, p, a, ox, oy, ms, still, hot, env, tagsOff = false
|
|
|
447
458
|
if (q ? !tagged(q) : tagsOff) continue;
|
|
448
459
|
sk.drawTag(ctx, r.x, f.y, r.look, env);
|
|
449
460
|
const box = tagBox(ctx, r.x, f.y, r.look.label);
|
|
450
|
-
tags.push(box);
|
|
461
|
+
tags.push(box); taken.push(tagFootprint(ctx, box, r.look.badge));
|
|
451
462
|
if (q) held.push({ name: q.look.name, id: f.id, box });
|
|
452
463
|
}
|
|
453
464
|
}
|
|
454
465
|
p.heldTags = held;
|
|
466
|
+
// The crowd's compact tags go last, clear of every full tag above: people's, an item's own name and the row it
|
|
467
|
+
// carries (#2862 review). A crowd's grid is tighter than a compact tag is wide (OVERFLOW.STEPS down to 32): a
|
|
468
|
+
// neighbour's tag goes a row lower (COMPACT_ROW) rather than out, so a row of people reads as two staggered rows.
|
|
469
|
+
if (!tagsOff) for (const q of people) {
|
|
470
|
+
if (live.hidden.has(q.look.name) || !crowded(q)) continue;
|
|
471
|
+
for (const dy of [0, COMPACT_ROW]) {
|
|
472
|
+
const box = tagBox(ctx, q.at.x, q.at.y + dy, q.look.label, true), foot = tagFootprint(ctx, box, q.look.badge, true);
|
|
473
|
+
if (touches(foot)) continue;
|
|
474
|
+
sk.drawTag(ctx, q.at.x, q.at.y + dy, q.look, env, true);
|
|
475
|
+
tags.push(box); taken.push(foot);
|
|
476
|
+
break;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
455
479
|
} else {
|
|
456
480
|
for (const d of p.desks) { const look = p.looks.get(d.key); sk.drawCharacter(ctx, d, actPosed(p, look), ms, env); speakers.push({ look, x: d.x + 58, y: d.y + 12, area: p.roomOf.get(d.key) ?? null }); }
|
|
457
481
|
blit(p.layers.front);
|
|
@@ -3,24 +3,38 @@
|
|
|
3
3
|
// card. What runs is then handed to the preview frame exactly like a block's (AgendPreview.start): the file URL itself
|
|
4
4
|
// is never opened or framed on the dashboard's origin.
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
|
|
6
|
+
/**
|
|
7
|
+
* #2863: the sizes a preview takes (UTF-8 bytes of the HTML), the same as a block's (preview.js LIMITS): up to ASK it
|
|
8
|
+
* runs at once; above it the person is asked first ("Preview anyway?"); above MAX there is no preview, only the
|
|
9
|
+
* download. MAX is the frame's own ceiling (web-preview.ts PREVIEW_HTML_MAX_BYTES).
|
|
10
|
+
*/
|
|
11
|
+
export const HTML_PREVIEW_ASK = 8 * 1024 * 1024;
|
|
12
|
+
export const HTML_PREVIEW_MAX = 50 * 1024 * 1024;
|
|
8
13
|
|
|
9
14
|
/**
|
|
10
|
-
* Read an attachment's HTML: { ok: true, code } or { ok: false, reason } with reason "
|
|
11
|
-
*
|
|
12
|
-
*
|
|
15
|
+
* Read an attachment's HTML: { ok: true, code } or { ok: false, reason } with reason "large" (above `ask` and not
|
|
16
|
+
* `anyway`: nothing is fetched when its listed size says so; `size` is what it is), "over" (above `max`: its listed size
|
|
17
|
+
* is checked before anything is fetched, the bytes again after), "gone" (the fleet no longer serves that id — the file
|
|
18
|
+
* changed, or the fleet restarted), or "failed". The text card reads with its own, single cap (ask = max).
|
|
13
19
|
*/
|
|
14
|
-
export async function loadHtmlAttachment(att, fetchImpl = globalThis.fetch) {
|
|
20
|
+
export async function loadHtmlAttachment(att, fetchImpl = globalThis.fetch, { ask = HTML_PREVIEW_ASK, max = HTML_PREVIEW_MAX, anyway = false } = {}) {
|
|
15
21
|
if (!att || typeof att.id !== "string" || !/^[0-9a-f]{32}$/.test(att.id)) return { ok: false, reason: "failed" };
|
|
16
|
-
if (typeof att.size === "number" && att.size >
|
|
22
|
+
if (typeof att.size === "number" && att.size > max) return { ok: false, reason: "over" };
|
|
23
|
+
if (typeof att.size === "number" && att.size > ask && !anyway) return { ok: false, reason: "large", size: att.size };
|
|
17
24
|
let res;
|
|
18
25
|
try { res = await fetchImpl(`/ui/file/${att.id}`, { credentials: "same-origin", cache: "no-store" }); }
|
|
19
26
|
catch { return { ok: false, reason: "failed" }; }
|
|
20
27
|
if (!res || !res.ok) return { ok: false, reason: res && res.status === 404 ? "gone" : "failed" };
|
|
21
28
|
let bytes;
|
|
22
29
|
try { bytes = await res.arrayBuffer(); } catch { return { ok: false, reason: "failed" }; }
|
|
23
|
-
if (bytes.byteLength >
|
|
30
|
+
if (bytes.byteLength > max) return { ok: false, reason: "over" };
|
|
31
|
+
if (bytes.byteLength > ask && !anyway) return { ok: false, reason: "large", size: bytes.byteLength };
|
|
24
32
|
// UTF-8, as a block's text is; a BOM is dropped.
|
|
25
33
|
return { ok: true, code: new TextDecoder("utf-8").decode(bytes) };
|
|
26
34
|
}
|
|
35
|
+
|
|
36
|
+
/** #2863: a size as the person reads it: "12.3 MB" (1 MB = 1024 × 1024 bytes, as the caps are). */
|
|
37
|
+
export function sizeLabel(bytes) {
|
|
38
|
+
const mb = bytes / (1024 * 1024);
|
|
39
|
+
return `${mb >= 10 ? Math.round(mb) : Math.round(mb * 10) / 10} MB`;
|
|
40
|
+
}
|