Merge branch 'view-ui'

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
abrown38andClaude Opus 5.5 committed 2026-10-09 15:17:49 +00:00
commit ca0f148b78
29 files changed
+623 -55

No files matched your search

+25 -7
View File
@@ -1,5 +1,5 @@
// Jarl web UI: router, layout and login.
import { h, mount, toast, errorBox, plural } from "./ui.js";
import { h, mount, toast, errorBox, plural, lastList } from "./ui.js";
import { wordmark, logo } from "./brand.js";
import { api, get, state, loadDraft, onChange, setUnauthorizedHandler, setImmediate, setCodePrompt } from "./api.js";
import * as totp from "./totp.js";
@@ -50,7 +50,7 @@ const navItems = [
];
const app = document.getElementById("app");
let mainEl, navEl, barEl, current = { key: "" };
let mainEl, navEl, barEl, current = { key: "" }, routeGen = 0;
function parseHash() {
const raw = location.hash.replace(/^#\/?/, "");
@@ -153,8 +153,7 @@ function layout() {
const header = h("header", { class: "top" },
h("a", { class: "brand", href: "#/", title: "Dashboard" },
logo(30),
wordmark({ rule: false, height: 19, cls: "brand-name" }),
h("span", { class: "brand-version" }, me.version)),
wordmark({ rule: false, height: 19, cls: "brand-name" })),
navEl,
h("div", { class: "spacer" }),
modeEl = h("span"),
@@ -162,7 +161,7 @@ function layout() {
h("a", { href: "#/account", class: "user", title: (me.permissions || []).join("\n") }, me.user.username),
me.admin ? h("a", { href: "#/users" }, "Users") : null,
h("button", { class: "small", onclick: logout }, "Log out"));
mount(app, header, barEl, mainEl);
mount(app, h("div", { class: "shell" }, header, barEl, mainEl, footer(me.build)));
modeHolder = modeEl;
renderNav();
renderBar();
@@ -170,6 +169,16 @@ function layout() {
let modeHolder;
// footer names the Jarl build: the release, or the commit for other
// builds, linked to it in the repository.
function footer(b) {
if (!b) return null;
const what = b.tag ? "release " + b.tag
: b.commit ? "commit " + b.commit + (b.modified ? ", with uncommitted changes" : "") : "version " + b.version;
return h("footer", { class: "site" }, "Powered by ",
h("a", { href: b.url, target: "_blank", rel: "noopener", title: "Jarl " + b.version + ", built from " + what }, "Jarl " + b.label));
}
function renderNav() {
mount(navEl, navItems.map(([key, href, label]) => h("a", { href, class: current.nav === key ? "active" : "" }, label)));
}
@@ -211,12 +220,21 @@ async function route() {
const params = {};
(r.params || []).forEach((p, i) => { params[p] = m[i + 1]; });
current = { key: path, nav: r.nav, page: r.page, params, query };
if (!r.params && r.nav) lastList[r.nav] = location.hash;
renderNav();
mount(mainEl, h("div", { class: "muted" }, "Loading…"));
// Keep the old page until the new one is drawn (switching a zone's
// tabs shouldn't blank the page); say "Loading…" only when it's slow.
// (Only while the new page has drawn nothing yet: pages that draw
// their frame first fill it in afterwards.)
const gen = ++routeGen;
const old = mainEl.firstChild;
const slow = setTimeout(() => { if (gen === routeGen && mainEl.firstChild === old) mount(mainEl, h("div", { class: "muted" }, "Loading…")); }, 300);
try {
await r.page.render(mainEl, params, query);
} catch (e) {
mount(mainEl, h("h1", null, "Something went wrong"), errorBox(e));
if (gen === routeGen) mount(mainEl, h("h1", null, "Something went wrong"), errorBox(e));
} finally {
clearTimeout(slow);
}
return;
}