@oharness/server 0.10.0 → 0.12.0
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/analytics.d.ts +32 -29
- package/dist/analytics.js +225 -278
- package/dist/analytics.js.map +1 -1
- package/dist/i18n.js +242 -12
- package/dist/i18n.js.map +1 -1
- package/dist/icons.d.ts +3 -0
- package/dist/icons.js +69 -3
- package/dist/icons.js.map +1 -1
- package/dist/model-order.js +1 -0
- package/dist/model-order.js.map +1 -1
- package/dist/page/client/account.js +140 -4
- package/dist/page/client/account.js.map +1 -1
- package/dist/page/client/base.js +42 -16
- package/dist/page/client/base.js.map +1 -1
- package/dist/page/client/browser.js +1 -1
- package/dist/page/client/composer.js +641 -47
- package/dist/page/client/composer.js.map +1 -1
- package/dist/page/client/dialogs.js +75 -0
- package/dist/page/client/dialogs.js.map +1 -1
- package/dist/page/client/events.js +25 -2
- package/dist/page/client/events.js.map +1 -1
- package/dist/page/client/index.js +4 -0
- package/dist/page/client/index.js.map +1 -1
- package/dist/page/client/panels.js +4 -4
- package/dist/page/client/ripple.d.ts +13 -0
- package/dist/page/client/ripple.js +247 -0
- package/dist/page/client/ripple.js.map +1 -0
- package/dist/page/client/routines.js +173 -41
- package/dist/page/client/routines.js.map +1 -1
- package/dist/page/client/shell.js +200 -54
- package/dist/page/client/shell.js.map +1 -1
- package/dist/page/client/state.js +7 -2
- package/dist/page/client/state.js.map +1 -1
- package/dist/page/client/terminal.js +34 -7
- package/dist/page/client/terminal.js.map +1 -1
- package/dist/page/client/theme.d.ts +17 -0
- package/dist/page/client/theme.js +365 -0
- package/dist/page/client/theme.js.map +1 -0
- package/dist/page/client/transcript.js +63 -7
- package/dist/page/client/transcript.js.map +1 -1
- package/dist/page/style.js +273 -22
- package/dist/page/style.js.map +1 -1
- package/dist/page.js +5 -5
- package/dist/page.js.map +1 -1
- package/dist/prefs.d.ts +17 -0
- package/dist/prefs.js +5 -0
- package/dist/prefs.js.map +1 -1
- package/dist/server.js +710 -45
- package/dist/server.js.map +1 -1
- package/dist/suggest.d.ts +42 -0
- package/dist/suggest.js +215 -0
- package/dist/suggest.js.map +1 -0
- package/dist/terminal.d.ts +35 -2
- package/dist/terminal.js +60 -4
- package/dist/terminal.js.map +1 -1
- package/dist/theme-assets.d.ts +39 -0
- package/dist/theme-assets.js +109 -0
- package/dist/theme-assets.js.map +1 -0
- package/dist/theme-store.d.ts +50 -0
- package/dist/theme-store.js +124 -0
- package/dist/theme-store.js.map +1 -0
- package/dist/theme-tools.d.ts +25 -0
- package/dist/theme-tools.js +243 -0
- package/dist/theme-tools.js.map +1 -0
- package/dist/tool-icons.d.ts +12 -0
- package/dist/tool-icons.js +16 -0
- package/dist/tool-icons.js.map +1 -0
- package/dist/turn-shots.d.ts +18 -2
- package/dist/turn-shots.js +10 -1
- package/dist/turn-shots.js.map +1 -1
- package/dist/user-theme.d.ts +143 -0
- package/dist/user-theme.js +510 -0
- package/dist/user-theme.js.map +1 -0
- package/package.json +2 -2
|
@@ -215,6 +215,21 @@ async function showModels() {
|
|
|
215
215
|
return go;
|
|
216
216
|
};
|
|
217
217
|
|
|
218
|
+
// What this vendor has cost lately, from this machine's own records — the
|
|
219
|
+
// question a pasted key leaves open, answered where the key is.
|
|
220
|
+
if (provider.spend30d >= 0.01) {
|
|
221
|
+
const spent = el("div", "muted small");
|
|
222
|
+
spent.appendChild(document.createTextNode(t("models.spend30d", { amount: moneyOf(Math.round(provider.spend30d * 100), "usd") }) + " "));
|
|
223
|
+
const more = el("button", "cn-inline", t("models.spendDetail"));
|
|
224
|
+
more.type = "button";
|
|
225
|
+
more.addEventListener("click", (e) => {
|
|
226
|
+
e.stopPropagation();
|
|
227
|
+
document.getElementById("nav-usage").click();
|
|
228
|
+
});
|
|
229
|
+
spent.appendChild(more);
|
|
230
|
+
cardBody.appendChild(spent);
|
|
231
|
+
}
|
|
232
|
+
|
|
218
233
|
if (provider.readiness === "no-account-system") {
|
|
219
234
|
cardBody.appendChild(el("div", "muted small", t("models.noAccounts")));
|
|
220
235
|
cardBody.appendChild(el("div", "muted small", t("models.noAccountsHow")));
|
|
@@ -229,6 +244,8 @@ async function showModels() {
|
|
|
229
244
|
cardBody.appendChild(signInButton());
|
|
230
245
|
} else if (provider.signIn && (!provider.configured || String(provider.source || "").startsWith("env:"))) {
|
|
231
246
|
cardBody.appendChild(signInButton());
|
|
247
|
+
} else if (provider.vendorSignIn && !provider.configured) {
|
|
248
|
+
cardBody.appendChild(vendorSignIn(provider, null, null));
|
|
232
249
|
}
|
|
233
250
|
|
|
234
251
|
// The key field, for every vendor that takes one — the account plans
|
|
@@ -236,7 +253,7 @@ async function showModels() {
|
|
|
236
253
|
// and spends the same balance, and CI needs one because it does not
|
|
237
254
|
// expire. This is where it belongs, next to every other vendor's key
|
|
238
255
|
// field, rather than inside the sign-in dialog.
|
|
239
|
-
if (!provider.configured || String(provider.source || "").startsWith("env:")) {
|
|
256
|
+
if (!keyless(provider) && (!provider.configured || String(provider.source || "").startsWith("env:"))) {
|
|
240
257
|
const row = el("div", "keyrow");
|
|
241
258
|
const field = document.createElement("input");
|
|
242
259
|
// type=password so the key is not left legible on a screen, and
|
|
@@ -467,6 +484,12 @@ function openConnect(providerId, thenModel) {
|
|
|
467
484
|
document.getElementById("cn-title").textContent = provider.signIn && provider.readiness !== "no-account-system"
|
|
468
485
|
? t("models.signIn")
|
|
469
486
|
: provider.label || provider.id;
|
|
487
|
+
// A vendor's own sign-in is that vendor's page the user is about to visit,
|
|
488
|
+
// so the dialog carries its mark: recognising the logo is most of what
|
|
489
|
+
// makes "sign in with" feel safe to click.
|
|
490
|
+
if (!provider.signIn && provider.vendorSignIn) {
|
|
491
|
+
document.getElementById("cn-title").prepend(providerMark(provider.id, provider.label || provider.id, "cn-title-mark"));
|
|
492
|
+
}
|
|
470
493
|
// What this dialog is answering. A credential that ran out and one that was
|
|
471
494
|
// never created both arrive here, and telling someone to "sign in" when what
|
|
472
495
|
// happened is "your session ended" leaves them wondering whether the first
|
|
@@ -594,6 +617,54 @@ function openConnect(providerId, thenModel) {
|
|
|
594
617
|
const siteButton = site.querySelector("button");
|
|
595
618
|
const orCode = el("div", "cn-note", ts("models.orCode"));
|
|
596
619
|
|
|
620
|
+
// A vendor's own sign-in, above the key field it makes optional — or, for a
|
|
621
|
+
// vendor with no key at all, in place of it.
|
|
622
|
+
const vendor = mode === "key" && provider.vendorSignIn
|
|
623
|
+
? vendorSignIn(
|
|
624
|
+
provider,
|
|
625
|
+
() => {
|
|
626
|
+
if (dialog.open) void done("signed in to " + provider.id + " — discovering its models…");
|
|
627
|
+
},
|
|
628
|
+
(reason) => {
|
|
629
|
+
hint.textContent = reason;
|
|
630
|
+
hint.hidden = false;
|
|
631
|
+
},
|
|
632
|
+
)
|
|
633
|
+
: null;
|
|
634
|
+
const noKey = keyless(provider);
|
|
635
|
+
const orKey = vendor && !noKey ? el("div", "cn-note", t("models.orPasteKey")) : null;
|
|
636
|
+
// What signing in hands over, said where the hesitation is: right under the
|
|
637
|
+
// button. Only when the sign-in is the whole dialog — beside a key field the
|
|
638
|
+
// key's own note says the same, and two padlocks in a row read as clutter.
|
|
639
|
+
let vendorLocal = null;
|
|
640
|
+
if (vendor && noKey) {
|
|
641
|
+
vendorLocal = el("div", "cn-note cn-local");
|
|
642
|
+
vendorLocal.appendChild(svgIcon("lock"));
|
|
643
|
+
const words = el("span");
|
|
644
|
+
words.appendChild(el("strong", null, t("models.vendorLocalLead", { provider: provider.label || provider.id })));
|
|
645
|
+
words.appendChild(document.createTextNode(t("models.vendorLocalRest")));
|
|
646
|
+
vendorLocal.appendChild(words);
|
|
647
|
+
}
|
|
648
|
+
// Someone with a Claude plan arrives here looking for a way to use it, and
|
|
649
|
+
// a key field alone says nothing to them. The plan works in Claude Code and
|
|
650
|
+
// not in other apps, so the honest answer is a door to Claude Code — their
|
|
651
|
+
// own install, in this project's terminal — rather than a login of ours.
|
|
652
|
+
let claudePlan = null;
|
|
653
|
+
if (provider.id === "anthropic" && currentCwd) {
|
|
654
|
+
claudePlan = el("div", "cn-note");
|
|
655
|
+
claudePlan.appendChild(document.createTextNode(t("models.claudePlan") + " "));
|
|
656
|
+
const open = el("button", "cn-inline", t("models.claudePlanOpen"));
|
|
657
|
+
open.type = "button";
|
|
658
|
+
open.onclick = () => {
|
|
659
|
+
dialog.close();
|
|
660
|
+
void openClaudeCode(currentCwd);
|
|
661
|
+
};
|
|
662
|
+
claudePlan.appendChild(open);
|
|
663
|
+
}
|
|
664
|
+
// The sign-in is the whole dialog then, and the × already closes it: a
|
|
665
|
+
// Cancel alone in the actions row only read as a button left behind.
|
|
666
|
+
dialog.classList.toggle("cn-keyless", noKey);
|
|
667
|
+
|
|
597
668
|
/**
|
|
598
669
|
* What the dialog opens focused.
|
|
599
670
|
*
|
|
@@ -606,7 +677,13 @@ function openConnect(providerId, thenModel) {
|
|
|
606
677
|
// no account system it is the one that fetches a key, which is the same
|
|
607
678
|
// shape of thing and deserves the same treatment. The field is where the
|
|
608
679
|
// cursor goes only when pasting is what is left.
|
|
609
|
-
const opener = mode === "account" && !flow
|
|
680
|
+
const opener = mode === "account" && !flow
|
|
681
|
+
? siteButton
|
|
682
|
+
: noAccounts
|
|
683
|
+
? keyButton
|
|
684
|
+
: vendor
|
|
685
|
+
? vendor.querySelector("button")
|
|
686
|
+
: undefined;
|
|
610
687
|
if (opener) opener.focus();
|
|
611
688
|
else field.focus();
|
|
612
689
|
};
|
|
@@ -665,7 +742,16 @@ function openConnect(providerId, thenModel) {
|
|
|
665
742
|
// button above the field: whichever control is the way in carries the
|
|
666
743
|
// colour, and the actions row holds the fallback. Two filled buttons make
|
|
667
744
|
// the reader choose by position, and position would pick the wrong one.
|
|
668
|
-
save.classList.toggle("approve", mode === "key" && !noAccounts);
|
|
745
|
+
save.classList.toggle("approve", mode === "key" && !noAccounts && !vendor);
|
|
746
|
+
// Nothing to paste for a vendor that takes no key: the sign-in above is
|
|
747
|
+
// the whole dialog, and a field beside it could only ever be refused.
|
|
748
|
+
field.hidden = noKey;
|
|
749
|
+
save.hidden = noKey;
|
|
750
|
+
if (noKey) {
|
|
751
|
+
why.hidden = true;
|
|
752
|
+
keptLocal.hidden = true;
|
|
753
|
+
docs.hidden = true;
|
|
754
|
+
}
|
|
669
755
|
field.value = "";
|
|
670
756
|
focusPrimary();
|
|
671
757
|
};
|
|
@@ -818,10 +904,14 @@ function openConnect(providerId, thenModel) {
|
|
|
818
904
|
// somebody expecting a sign-in would ask — and they are exactly who opens
|
|
819
905
|
// the mark.
|
|
820
906
|
...(noAccounts ? [keyLink(), el("div", "cn-note", t("models.orPasteKey"))] : []),
|
|
907
|
+
...(vendor ? [vendor] : []),
|
|
908
|
+
...(vendorLocal ? [vendorLocal] : []),
|
|
909
|
+
...(orKey ? [orKey] : []),
|
|
821
910
|
site,
|
|
822
911
|
orCode,
|
|
823
912
|
field,
|
|
824
913
|
keptLocal,
|
|
914
|
+
...(claudePlan ? [claudePlan] : []),
|
|
825
915
|
);
|
|
826
916
|
// Re-attached on every open: the actions row is markup, so a link left from
|
|
827
917
|
// the last vendor would otherwise point at their key page. The link is built
|
|
@@ -982,7 +1072,7 @@ async function loadAccount() {
|
|
|
982
1072
|
const name = body.name || body.email || t("account.signedIn");
|
|
983
1073
|
row.appendChild(avatarFor(name, body.avatar));
|
|
984
1074
|
row.appendChild(el("span", "who-name", name));
|
|
985
|
-
row.appendChild(svgIcon("
|
|
1075
|
+
row.appendChild(svgIcon("caret", "acct-caret"));
|
|
986
1076
|
|
|
987
1077
|
// The balance, once it can be read. Running out mid-turn is the failure this
|
|
988
1078
|
// prevents: a number that appears only in a paywall is a number nobody sees
|
|
@@ -1245,6 +1335,7 @@ async function loadAccount() {
|
|
|
1245
1335
|
cls: "accent",
|
|
1246
1336
|
}),
|
|
1247
1337
|
themeRow(),
|
|
1338
|
+
...customThemeRows(),
|
|
1248
1339
|
menuItem(t("account.signOut"), {
|
|
1249
1340
|
run: () => {
|
|
1250
1341
|
closeRowMenu();
|
|
@@ -1314,6 +1405,51 @@ function websiteSignIn(provider, onSignedIn, onUnavailable) {
|
|
|
1314
1405
|
return row;
|
|
1315
1406
|
}
|
|
1316
1407
|
|
|
1408
|
+
/**
|
|
1409
|
+
* A vendor whose only way in is its browser sign-in — ChatGPT, whose plan has
|
|
1410
|
+
* no key to paste. Offering a key field for it would be a dead end.
|
|
1411
|
+
*/
|
|
1412
|
+
function keyless(provider) {
|
|
1413
|
+
return Boolean(provider.vendorSignIn) && !(provider.envVars || []).length;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
/**
|
|
1417
|
+
* A vendor's own browser sign-in: Sign in with ChatGPT, OpenRouter.
|
|
1418
|
+
*
|
|
1419
|
+
* Like the website sign-in, the answer does not come back to the click. The
|
|
1420
|
+
* vendor redirects to a listener on this machine, the server finishes the
|
|
1421
|
+
* login there, and models_changed is what tells this page — so the waiter is
|
|
1422
|
+
* registered only once the tab has actually been opened.
|
|
1423
|
+
*/
|
|
1424
|
+
function vendorSignIn(provider, onSignedIn, onError) {
|
|
1425
|
+
const row = el("div", "keyrow");
|
|
1426
|
+
const go = el("button", "signin-site", provider.vendorSignIn);
|
|
1427
|
+
go.type = "button";
|
|
1428
|
+
go.addEventListener("click", (e) => {
|
|
1429
|
+
e.preventDefault();
|
|
1430
|
+
e.stopPropagation();
|
|
1431
|
+
go.disabled = true;
|
|
1432
|
+
void api("/signin/vendor", { provider: provider.id })
|
|
1433
|
+
.then((result) => {
|
|
1434
|
+
if (!result || result.error) {
|
|
1435
|
+
const message = (result && result.error) || "could not start the sign-in";
|
|
1436
|
+
if (onError) return void onError(message);
|
|
1437
|
+
return void addNotice(message, "err");
|
|
1438
|
+
}
|
|
1439
|
+
signedInWaiters.set(provider.id, onSignedIn || (() => {
|
|
1440
|
+
if (document.getElementById("nav-models").classList.contains("active")) void showModels();
|
|
1441
|
+
}));
|
|
1442
|
+
window.open(result.url, "_blank", "noopener");
|
|
1443
|
+
addNotice(t("models.signInOpened", { site: provider.label || provider.id }));
|
|
1444
|
+
})
|
|
1445
|
+
.finally(() => {
|
|
1446
|
+
go.disabled = false;
|
|
1447
|
+
});
|
|
1448
|
+
});
|
|
1449
|
+
row.appendChild(go);
|
|
1450
|
+
return row;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1317
1453
|
/**
|
|
1318
1454
|
* The sign-in itself, without a button attached to it.
|
|
1319
1455
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account.js","sourceRoot":"","sources":["../../../src/page/client/account.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"account.js","sourceRoot":"","sources":["../../../src/page/client/account.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA63EhC,CAAC"}
|
package/dist/page/client/base.js
CHANGED
|
@@ -117,23 +117,35 @@ const ICON_PATHS = {
|
|
|
117
117
|
themeDark: ["M13.2 9.6A5.6 5.6 0 0 1 6.4 2.8a5.6 5.6 0 1 0 6.8 6.8Z"],
|
|
118
118
|
themeSystemRects: [[1.8, 2.6, 12.4, 8.6, 1.6]],
|
|
119
119
|
themeLightCircles: [[8, 8, 3.1]],
|
|
120
|
+
// The add menu's marks all sit on one keyline, 2–14, the box the folder was
|
|
121
|
+
// redrawn onto: anything that runs out to 1.33 is the heaviest mark in the
|
|
122
|
+
// column, and anything drawn in 4.5–11.5 is the faintest.
|
|
123
|
+
//
|
|
120
124
|
// A page with its corner turned back, which is the one mark that reads as
|
|
121
125
|
// "file" at thirteen pixels without any lines of text inside it.
|
|
122
126
|
file: [
|
|
123
|
-
"M9.
|
|
124
|
-
"
|
|
125
|
-
"M9.
|
|
127
|
+
"M9.5 2H4.33A1.33 1.33 0 0 0 3 3.33v9.34A1.33 1.33 0 0 0 4.33 14h7.34" +
|
|
128
|
+
"A1.33 1.33 0 0 0 13 12.67V5.5Z",
|
|
129
|
+
"M9.5 2v3.5H13",
|
|
130
|
+
],
|
|
131
|
+
// A paperclip, for the menu row that attaches files or photos: the mark that
|
|
132
|
+
// row carries everywhere else, and one that covers a photo as well as a
|
|
133
|
+
// document. The chip an attachment becomes keeps the page above, since by
|
|
134
|
+
// then it is a file rather than the act of attaching one.
|
|
135
|
+
attach: [
|
|
136
|
+
"M10.4 4.4 5.35 9.55a1.2 1.2 0 0 0 1.7 1.7l5.05-5.15a2.4 2.4 0 1 0-3.4-3.4" +
|
|
137
|
+
"L3.67 7.83a3.6 3.6 0 1 0 5.1 5.1l5.03-5.13",
|
|
126
138
|
],
|
|
127
139
|
// Two pins and a body: a connector is the thing something else plugs into.
|
|
128
|
-
plug: ["M6
|
|
140
|
+
plug: ["M6 2v3", "M10 2v3", "M3.5 5h9v2.5a4.5 4.5 0 0 1-9 0Z", "M8 12v2"],
|
|
129
141
|
// Stacked plates, because a plugin arrives as a bundle of contributions
|
|
130
142
|
// rather than as one thing.
|
|
131
|
-
plugin: ["M8
|
|
143
|
+
plugin: ["M8 2 2 5l6 3 6-3Z", "M2 11l6 3 6-3", "M2 8l6 3 6-3"],
|
|
132
144
|
/* A bookmark: one path, and the only shape in this menu that is neither a
|
|
133
145
|
sheet nor a folder. The open book it replaced was two mirrored paths
|
|
134
146
|
meeting at a spine, which at the 13px these rows use closed into a blob —
|
|
135
147
|
two strokes a pixel apart read as one thick one. */
|
|
136
|
-
skill: ["
|
|
148
|
+
skill: ["M5.33 2h5.34A1.33 1.33 0 0 1 12 3.33V14l-4-3-4 3V3.33A1.33 1.33 0 0 1 5.33 2Z"],
|
|
137
149
|
/* A prompt caret: what a command line looks like anywhere else. */
|
|
138
150
|
command: ["M4 5.5 6.5 8 4 10.5", "M8.5 10.5H12"],
|
|
139
151
|
/* A bare chevron. The other back arrow has a shaft, which is right on a
|
|
@@ -144,6 +156,7 @@ const ICON_PATHS = {
|
|
|
144
156
|
close: ["M4.5 4.5l7 7", "M11.5 4.5l-7 7"],
|
|
145
157
|
/* Its counterpart, for adding one. */
|
|
146
158
|
plus: ["M8 3.5v9", "M3.5 8h9"],
|
|
159
|
+
check: ["M3.5 8.5l3 3 6-7"],
|
|
147
160
|
/* A picture: a frame, a hill and a sun — what "attach an image" looks like
|
|
148
161
|
everywhere else. */
|
|
149
162
|
image: ["M2.5 11.5 6 8l3 3 1.5-1.5 3 3"],
|
|
@@ -157,12 +170,17 @@ const ICON_PATHS = {
|
|
|
157
170
|
back: ["M12.67 8H3.33", "M7.33 12 3.33 8l4-4"],
|
|
158
171
|
// An arrow leaving a tray, for the upload that still goes to this machine.
|
|
159
172
|
upload: ["M8 10.67V2", "M4.67 5.33 8 2l3.33 3.33", "M2 10.67v2A1.33 1.33 0 0 0 3.33 14h9.34A1.33 1.33 0 0 0 14 12.67v-2"],
|
|
173
|
+
// The same tray with the arrow coming in: bringing in what another tool has.
|
|
174
|
+
import: ["M8 2v8.67", "M4.67 7.33 8 10.67l3.33-3.34", "M2 10.67v2A1.33 1.33 0 0 0 3.33 14h9.34A1.33 1.33 0 0 0 14 12.67v-2"],
|
|
160
175
|
// A trunk with a branch leaving it, which is what forking a turn does to the
|
|
161
176
|
// transcript: the arc lands on the new node rather than crossing it.
|
|
162
177
|
fork: ["M5 2.6v7.7", "M11 6a5.7 5.7 0 0 1-4.3 6"],
|
|
163
178
|
forkCircles: [[11, 4.3], [5, 12]],
|
|
164
|
-
// A trunk and the branch that leaves it for a tree of its own.
|
|
165
|
-
|
|
179
|
+
// A trunk and the branch that leaves it for a tree of its own. Three nodes
|
|
180
|
+
// where the fork has two: the trunk carries on past the point the branch
|
|
181
|
+
// left it, because a worktree is a second checkout, not a turn that moved.
|
|
182
|
+
worktree: ["M4.5 5.1v5.8", "M11.5 6.1a6.4 6.4 0 0 1-5.4 6.4"],
|
|
183
|
+
worktreeCircles: [[4.5, 3.5, 1.6], [4.5, 12.5, 1.6], [11.5, 4.5, 1.6]],
|
|
166
184
|
copy: [
|
|
167
185
|
"M6.67 5.33h6.66a1.33 1.33 0 0 1 1.34 1.34v6.66a1.33 1.33 0 0 1-1.34 1.34" +
|
|
168
186
|
"H6.67a1.33 1.33 0 0 1-1.34-1.34V6.67a1.33 1.33 0 0 1 1.34-1.34Z",
|
|
@@ -204,8 +222,8 @@ const ICON_PATHS = {
|
|
|
204
222
|
// Accept edits lets the pencil through.
|
|
205
223
|
modeEdits: ["M11.3 2.7 13.3 4.7 5.3 12.7H3.3v-2Z", "M9.8 4.2l2 2"],
|
|
206
224
|
// Plan is read-only: an eye, which looks and does not touch.
|
|
207
|
-
modePlan: ["
|
|
208
|
-
modePlanCircles: [[8, 8, 2.
|
|
225
|
+
modePlan: ["M2 8s2.3-4.8 6-4.8S14 8 14 8s-2.3 4.8-6 4.8S2 8 2 8Z"],
|
|
226
|
+
modePlanCircles: [[8, 8, 2.2]],
|
|
209
227
|
// Auto waves the routine through: a bolt.
|
|
210
228
|
modeAuto: ["M8.8 1.6 3.6 9h4l-.7 5.4L12.4 7h-4Z"],
|
|
211
229
|
// Bypass leaves the gate open: a padlock with its shackle swung clear.
|
|
@@ -218,9 +236,16 @@ const ICON_PATHS = {
|
|
|
218
236
|
// Goal mode: a target, rings closing on the point being aimed at.
|
|
219
237
|
goal: ["M8 8h.01"],
|
|
220
238
|
goalCircles: [[8, 8, 6], [8, 8, 3]],
|
|
221
|
-
// Computer use: a screen on its stand,
|
|
222
|
-
|
|
223
|
-
|
|
239
|
+
// Computer use: a screen on its stand, and the pointer that drives it. The
|
|
240
|
+
// pointer used to sit inside the screen at three and a half units, where it
|
|
241
|
+
// closed into a triangle and the whole mark read as a video's play button.
|
|
242
|
+
// Now it is full size and breaks out over the screen's corner, and the frame
|
|
243
|
+
// stops short of it so the two outlines never cross.
|
|
244
|
+
computer: [
|
|
245
|
+
"M8.5 10.5h-5A1.5 1.5 0 0 1 2 9V4A1.5 1.5 0 0 1 3.5 2.5h7A1.5 1.5 0 0 1 12 4v3",
|
|
246
|
+
"M7 10.5V13M5 13h4",
|
|
247
|
+
"M9.5 8.5l4.5 1.9-2 .6-.6 2Z",
|
|
248
|
+
],
|
|
224
249
|
chevronLeft: ["M10 3.5 5.5 8l4.5 4.5"],
|
|
225
250
|
chevronRight: ["M6 3.5 10.5 8 6 12.5"],
|
|
226
251
|
};
|
|
@@ -387,9 +412,10 @@ const api = (path, body, timeoutMs) =>
|
|
|
387
412
|
/**
|
|
388
413
|
* Record that a feature was used. Does nothing unless analytics are on.
|
|
389
414
|
*
|
|
390
|
-
* window.__TRACK
|
|
391
|
-
* see analytics.ts — so the calls
|
|
392
|
-
* has to remember which branch they
|
|
415
|
+
* window.__TRACK forwards to this server's /track once it has said analytics
|
|
416
|
+
* are on, and drops everything otherwise — see analytics.ts — so the calls
|
|
417
|
+
* below read the same either way and nobody has to remember which branch they
|
|
418
|
+
* are writing in. The server checks what arrives before anything leaves.
|
|
393
419
|
*
|
|
394
420
|
* There is deliberately no way to send text through here: every caller passes
|
|
395
421
|
* an event name and a handful of enumerable properties — which panel, whether
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.js","sourceRoot":"","sources":["../../../src/page/client/base.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"base.js","sourceRoot":"","sources":["../../../src/page/client/base.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwb7B,CAAC"}
|
|
@@ -578,7 +578,7 @@ async function browserReload() {
|
|
|
578
578
|
|
|
579
579
|
async function browserStop() {
|
|
580
580
|
if (!activeId || !browserView(activeId).running) return;
|
|
581
|
-
if (!
|
|
581
|
+
if (!(await askConfirm({ body: t("browser.stopConfirm"), ok: t("dialog.stop"), danger: true }))) return;
|
|
582
582
|
browserStopBtn.disabled = true;
|
|
583
583
|
const result = await api("/browser/close", { session: activeId });
|
|
584
584
|
if (result.error) {
|