Status
This commit is contained in:
+3
-4
@@ -1,8 +1,8 @@
|
||||
import { get, put, subscribe } from "./api.js";
|
||||
import { errorText, h, replace, showAlert } from "./dom.js";
|
||||
import { mountNavbar } from "./navbar.js";
|
||||
import { leadPane, taskPane } from "./panes.js";
|
||||
import { handleEvent, loadFocus, loadInbox, loadLeadSessions, loadMe, loadProjects, notify, on, selectTask, setFocus, startLeadStream, state, tabs } from "./state.js";
|
||||
import { bindShortcuts } from "./switcher.js";
|
||||
|
||||
function identityBanner(root: HTMLElement): void {
|
||||
const render = () => {
|
||||
@@ -49,7 +49,6 @@ function route(): void {
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const navbar = h("header", { class: "navbar" });
|
||||
const banner = h("div", { class: "banner", hidden: true });
|
||||
|
||||
const status = await get("/api/admin/status");
|
||||
@@ -61,8 +60,8 @@ async function main(): Promise<void> {
|
||||
}
|
||||
startLeadStream();
|
||||
|
||||
replace(document.body, navbar, banner, h("div", { class: "panes" }, leadPane(), taskPane()));
|
||||
mountNavbar(navbar);
|
||||
replace(document.body, banner, h("div", { class: "panes" }, leadPane(), taskPane()));
|
||||
bindShortcuts();
|
||||
identityBanner(banner);
|
||||
|
||||
addEventListener(
|
||||
|
||||
@@ -109,28 +109,6 @@ function modal(content: string | Node[], buttons: { label: string; value: string
|
||||
});
|
||||
}
|
||||
|
||||
export function openPanel(title: string, mount: (root: HTMLElement) => () => void): void {
|
||||
if (document.querySelector("dialog.panel[open]")) {
|
||||
return;
|
||||
}
|
||||
const close = h("button", { type: "button", class: "panel-close", title: "Close (Esc)" }, "×");
|
||||
const body = h("div", { class: "panel-body" });
|
||||
const dialog = h("dialog", { class: "modal panel" }, h("header", { class: "panel-head" }, h("h2", {}, title), close), body);
|
||||
close.onclick = () => dialog.close();
|
||||
dialog.onclick = (e) => {
|
||||
if (e.target === dialog || (e.target as Element).closest("a[href^='#']")) {
|
||||
dialog.close();
|
||||
}
|
||||
};
|
||||
document.body.append(dialog);
|
||||
const dispose = mount(body);
|
||||
dialog.onclose = () => {
|
||||
dispose();
|
||||
dialog.remove();
|
||||
};
|
||||
dialog.showModal();
|
||||
}
|
||||
|
||||
export async function showAlert(text: string): Promise<void> {
|
||||
await modal(text, [{ label: "OK", value: "ok", class: "primary" }]);
|
||||
}
|
||||
|
||||
+70
-4
@@ -4,11 +4,13 @@ import { h, replace } from "./dom.js";
|
||||
import { mountFiles } from "./files.js";
|
||||
import { mountInbox } from "./inbox.js";
|
||||
import { mountLead } from "./lead.js";
|
||||
import { openSwitcher } from "./navbar.js";
|
||||
import { findTask, focusedProject, focusTab, on, onEach, state, tabs } from "./state.js";
|
||||
import { mountPreview } from "./preview.js";
|
||||
import { mountProject } from "./project.js";
|
||||
import { isReviewEvent, latestFindingCount, mountReviews } from "./reviews.js";
|
||||
import { mountSettings } from "./settings.js";
|
||||
import { mountStatus } from "./status.js";
|
||||
import { openSwitcher } from "./switcher.js";
|
||||
import { mountTranscript } from "./task.js";
|
||||
import { mountTerminal } from "./terminal.js";
|
||||
|
||||
@@ -23,10 +25,74 @@ const views: Record<TaskTab, { label: string; mount: (root: HTMLElement) => () =
|
||||
project: { label: "Project", mount: mountProject },
|
||||
};
|
||||
|
||||
type LeftTab = "lead" | "status" | "settings";
|
||||
|
||||
const leftViews: Record<Exclude<LeftTab, "lead">, { label: string; mount: (root: HTMLElement) => () => void }> = {
|
||||
status: { label: "Status", mount: mountStatus },
|
||||
settings: { label: "Settings", mount: mountSettings },
|
||||
};
|
||||
|
||||
let showLeft: (tab: LeftTab) => void = () => {};
|
||||
|
||||
export function focusLead(): void {
|
||||
showLeft("lead");
|
||||
document.querySelector<HTMLTextAreaElement>(".view-lead .composer textarea")?.focus();
|
||||
}
|
||||
|
||||
export function leadPane(): HTMLElement {
|
||||
const root = h("div", { class: "view view-lead" });
|
||||
mountLead(root);
|
||||
return h("section", { class: "pane" }, h("nav", { class: "pane-tabs" }, h("button", { class: "active" }, "Lead")), h("div", { class: "pane-body" }, root));
|
||||
const lead = h("div", { class: "view view-lead" });
|
||||
mountLead(lead);
|
||||
let other = h("div", { class: "view", hidden: true });
|
||||
const buttons: Record<LeftTab, HTMLButtonElement> = {
|
||||
lead: h("button", {}, "Lead"),
|
||||
status: h("button", {}, leftViews.status.label),
|
||||
settings: h("button", {}, leftViews.settings.label),
|
||||
};
|
||||
let shown: LeftTab | undefined;
|
||||
let dispose: () => void = () => {};
|
||||
|
||||
showLeft = (tab) => {
|
||||
if (tab === shown) {
|
||||
return;
|
||||
}
|
||||
dispose();
|
||||
dispose = () => {};
|
||||
shown = tab;
|
||||
for (const [t, button] of Object.entries(buttons)) {
|
||||
button.classList.toggle("active", t === tab);
|
||||
}
|
||||
lead.hidden = tab !== "lead";
|
||||
const root = h("div", { class: tab === "lead" ? "view" : `view view-${tab}`, hidden: tab === "lead" });
|
||||
other.replaceWith(root);
|
||||
other = root;
|
||||
if (tab === "lead") {
|
||||
return;
|
||||
}
|
||||
dispose = leftViews[tab].mount(root);
|
||||
};
|
||||
for (const [t, button] of Object.entries(buttons)) {
|
||||
button.onclick = () => showLeft(t as LeftTab);
|
||||
}
|
||||
|
||||
const conn = h("span", { class: "connection down", title: "connecting" });
|
||||
const connLabel = h("span", { class: "connection-label" }, "reconnecting…");
|
||||
const me = h("span", { class: "me" });
|
||||
const renderConn = () => {
|
||||
conn.className = state.connected ? "connection up" : "connection down";
|
||||
conn.title = state.connected ? "live" : "reconnecting";
|
||||
connLabel.hidden = state.connected;
|
||||
replace(me, state.me?.display_name ?? "");
|
||||
};
|
||||
onEach(["me", "connection"], renderConn);
|
||||
renderConn();
|
||||
showLeft("lead");
|
||||
|
||||
return h(
|
||||
"section",
|
||||
{ class: "pane" },
|
||||
h("nav", { class: "pane-tabs" }, buttons.lead, buttons.status, buttons.settings, h("span", { class: "pane-status" }, connLabel, conn, me)),
|
||||
h("div", { class: "pane-body" }, lead, other),
|
||||
);
|
||||
}
|
||||
|
||||
export function taskPane(): HTMLElement {
|
||||
|
||||
+2
-6
@@ -1,5 +1,5 @@
|
||||
import { put, type NotificationPrefs } from "./api.js";
|
||||
import { errorText, h, openPanel, replace, showAlert } from "./dom.js";
|
||||
import { errorText, h, replace, showAlert } from "./dom.js";
|
||||
import { on, state } from "./state.js";
|
||||
|
||||
const kinds: { key: keyof NotificationPrefs; label: string }[] = [
|
||||
@@ -10,11 +10,7 @@ const kinds: { key: keyof NotificationPrefs; label: string }[] = [
|
||||
{ key: "rate_limits", label: "Rate limits" },
|
||||
];
|
||||
|
||||
export function openSettings(): void {
|
||||
openPanel("Settings", mountSettings);
|
||||
}
|
||||
|
||||
function mountSettings(root: HTMLElement): () => void {
|
||||
export function mountSettings(root: HTMLElement): () => void {
|
||||
const device = h("div", { class: "push-device" });
|
||||
const prefs = h("fieldset", { class: "push-prefs" });
|
||||
|
||||
|
||||
+2
-6
@@ -1,15 +1,11 @@
|
||||
import { get, type AgentProcess, type ByreEvent, type Status, type TurnRef, type Zombie } from "./api.js";
|
||||
import { ago, errorText, h, openPanel, replace } from "./dom.js";
|
||||
import { ago, errorText, h, replace } from "./dom.js";
|
||||
import { findTask, on, onEach, state } from "./state.js";
|
||||
import { taskStyle } from "./taskcolor.js";
|
||||
|
||||
const refreshKinds = new Set(["session.created", "session.state", "task.created", "task.merged", "task.discarded", "project.created", "admin.health"]);
|
||||
|
||||
export function openStatus(): void {
|
||||
openPanel("Status", mountStatus);
|
||||
}
|
||||
|
||||
function mountStatus(root: HTMLElement): () => void {
|
||||
export function mountStatus(root: HTMLElement): () => void {
|
||||
let status: Status | undefined;
|
||||
let error = "";
|
||||
let inFlight = false;
|
||||
|
||||
@@ -1,33 +1,11 @@
|
||||
import { post } from "./api.js";
|
||||
import { errorText, h, replace, showAlert } from "./dom.js";
|
||||
import { openSettings } from "./settings.js";
|
||||
import { focusLead } from "./panes.js";
|
||||
import { activeTasks, currentSession, focusedProject, focusProject, focusTab, focusTask, lastVisits, onEach, state } from "./state.js";
|
||||
import { openStatus } from "./status.js";
|
||||
import { confirmMerge } from "./task.js";
|
||||
import { taskStyle } from "./taskcolor.js";
|
||||
|
||||
export function mountNavbar(root: HTMLElement): void {
|
||||
const status = h("button", { class: "nav-link" }, "Status");
|
||||
status.onclick = openStatus;
|
||||
|
||||
const settings = h("button", { class: "nav-link" }, "Settings");
|
||||
settings.onclick = openSettings;
|
||||
|
||||
const conn = h("span", { class: "connection down", title: "connecting" });
|
||||
const connLabel = h("span", { class: "connection-label" }, "reconnecting…");
|
||||
const me = h("span", { class: "me" });
|
||||
|
||||
const render = () => {
|
||||
conn.className = state.connected ? "connection up" : "connection down";
|
||||
conn.title = state.connected ? "live" : "reconnecting";
|
||||
connLabel.hidden = state.connected;
|
||||
replace(me, state.me?.display_name ?? "");
|
||||
};
|
||||
|
||||
onEach(["me", "connection"], render);
|
||||
replace(root, h("a", { href: "#/", class: "brand" }, "byre"), h("span", { class: "spacer" }), status, settings, connLabel, conn, me);
|
||||
render();
|
||||
|
||||
export function bindShortcuts(): void {
|
||||
addEventListener("keydown", (e) => {
|
||||
if (!(e.metaKey || e.ctrlKey) || document.querySelector("dialog[open]")) {
|
||||
return;
|
||||
@@ -40,7 +18,7 @@ export function mountNavbar(root: HTMLElement): void {
|
||||
focusTab("project");
|
||||
} else if (e.key === "l") {
|
||||
e.preventDefault();
|
||||
document.querySelector<HTMLTextAreaElement>(".view-lead .composer textarea")?.focus();
|
||||
focusLead();
|
||||
} else if (e.key === "s") {
|
||||
e.preventDefault();
|
||||
if (state.selectedTaskId) {
|
||||
Reference in New Issue
Block a user