Move the task picker into the right pane and drop the switcher from it

This commit is contained in:
Ian Gulliver
2026-10-15 12:03:01 -07:00
parent cfa9699f4b
commit f896408310
8 changed files with 52 additions and 44 deletions
+7 -9
View File
@@ -1,11 +1,9 @@
import { post } from "./api.js";
import { errorText, h, replace, showAlert } from "./dom.js";
import { openSettings } from "./settings.js";
import { activeTasks, currentSession, findTask, focusedProject, focusProject, focusTab, focusTask, lastVisits, onEach, state } from "./state.js";
import { activeTasks, currentSession, focusedProject, focusProject, focusTab, focusTask, lastVisits, onEach, state } from "./state.js";
import { openStatus } from "./status.js";
export function mountNavbar(root) {
const crumb = h("button", { class: "crumb", title: "Switch task (⌘K)" });
crumb.onclick = openSwitcher;
const status = h("button", { class: "nav-link" }, "Status");
status.onclick = openStatus;
const settings = h("button", { class: "nav-link" }, "Settings");
@@ -14,16 +12,13 @@ export function mountNavbar(root) {
const connLabel = h("span", { class: "connection-label" }, "reconnecting…");
const me = h("span", { class: "me" });
const render = () => {
const task = state.selectedTaskId ? findTask(state.selectedTaskId) : undefined;
const project = focusedProject();
replace(crumb, task ? `${project?.name ?? ""} ▸ ${task.title}` : (project?.name ?? "No task"), " ▾");
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", "projects", "tasks", "selection", "focus", "connection"], render);
replace(root, h("a", { href: "#/", class: "brand" }, "byre"), crumb, h("span", { class: "spacer" }), status, settings, connLabel, conn, me);
onEach(["me", "connection"], render);
replace(root, h("a", { href: "#/", class: "brand" }, "byre"), h("span", { class: "spacer" }), status, settings, connLabel, conn, me);
render();
addEventListener("keydown", (e) => {
if (!(e.metaKey || e.ctrlKey) || document.querySelector("dialog[open]")) {
@@ -39,7 +34,7 @@ export function mountNavbar(root) {
}
});
}
function openSwitcher() {
export function openSwitcher() {
if (document.querySelector(".switcher")) {
return;
}
@@ -139,6 +134,9 @@ function openSwitcher() {
}
};
backdrop.onclick = close;
const anchor = document.querySelector(".task-picker").getBoundingClientRect();
box.style.top = `${anchor.bottom + 4}px`;
box.style.right = `${document.documentElement.clientWidth - anchor.right}px`;
document.body.append(backdrop, box);
render();
input.focus();
+1 -1
View File
File diff suppressed because one or more lines are too long
+12 -2
View File
@@ -4,7 +4,8 @@ import { h, replace } from "./dom.js";
import { mountFiles } from "./files.js";
import { mountInbox } from "./inbox.js";
import { mountLead } from "./lead.js";
import { focusTab, on, state, tabs } from "./state.js";
import { openSwitcher } from "./navbar.js";
import { findTask, focusedProject, focusTab, on, onEach, state, tabs } from "./state.js";
import { mountProject } from "./project.js";
import { mountTranscript } from "./task.js";
const views = {
@@ -22,8 +23,15 @@ export function leadPane() {
export function taskPane() {
const strip = h("nav", { class: "pane-tabs" });
const body = h("div", { class: "pane-body" });
const picker = h("button", { class: "task-picker", title: "Switch task (⌘K)" });
picker.onclick = openSwitcher;
let shown;
let dispose = () => { };
const renderPicker = () => {
const task = state.selectedTaskId ? findTask(state.selectedTaskId) : undefined;
const project = focusedProject();
replace(picker, task ? `${project?.name ?? ""} ▸ ${task.title}` : (project?.name ?? "No task"), " ▾");
};
const render = () => {
const first = state.focus.tabs.filter((t, i, all) => tabs.includes(t) && all.indexOf(t) === i);
const order = [...first, ...tabs.filter((t) => !first.includes(t))];
@@ -32,7 +40,7 @@ export function taskPane() {
const b = h("button", { class: t === tab ? "active" : "" }, views[t].label);
b.onclick = () => focusTab(t);
return b;
}));
}), h("span", { class: "spacer" }), picker);
if (tab === shown) {
return;
}
@@ -43,7 +51,9 @@ export function taskPane() {
dispose = views[tab].mount(root);
};
on("focus", render);
onEach(["projects", "tasks", "selection", "focus"], renderPicker);
render();
renderPicker();
const pane = h("section", { class: "pane", tabIndex: -1 }, strip, body);
trackPaneFocus(pane);
return pane;
+1 -1
View File
File diff suppressed because one or more lines are too long
+7 -11
View File
@@ -1,13 +1,10 @@
import { post } from "./api.js";
import { errorText, h, replace, showAlert } from "./dom.js";
import { openSettings } from "./settings.js";
import { activeTasks, currentSession, findTask, focusedProject, focusProject, focusTab, focusTask, lastVisits, onEach, state } from "./state.js";
import { activeTasks, currentSession, focusedProject, focusProject, focusTab, focusTask, lastVisits, onEach, state } from "./state.js";
import { openStatus } from "./status.js";
export function mountNavbar(root: HTMLElement): void {
const crumb = h("button", { class: "crumb", title: "Switch task (⌘K)" });
crumb.onclick = openSwitcher;
const status = h("button", { class: "nav-link" }, "Status");
status.onclick = openStatus;
@@ -19,18 +16,14 @@ export function mountNavbar(root: HTMLElement): void {
const me = h("span", { class: "me" });
const render = () => {
const task = state.selectedTaskId ? findTask(state.selectedTaskId) : undefined;
const project = focusedProject();
replace(crumb, task ? `${project?.name ?? ""} ▸ ${task.title}` : (project?.name ?? "No task"), " ▾");
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", "projects", "tasks", "selection", "focus", "connection"], render);
replace(root, h("a", { href: "#/", class: "brand" }, "byre"), crumb, h("span", { class: "spacer" }), status, settings, connLabel, conn, me);
onEach(["me", "connection"], render);
replace(root, h("a", { href: "#/", class: "brand" }, "byre"), h("span", { class: "spacer" }), status, settings, connLabel, conn, me);
render();
addEventListener("keydown", (e) => {
@@ -47,7 +40,7 @@ export function mountNavbar(root: HTMLElement): void {
});
}
function openSwitcher(): void {
export function openSwitcher(): void {
if (document.querySelector(".switcher")) {
return;
}
@@ -158,6 +151,9 @@ function openSwitcher(): void {
};
backdrop.onclick = close;
const anchor = document.querySelector(".task-picker")!.getBoundingClientRect();
box.style.top = `${anchor.bottom + 4}px`;
box.style.right = `${document.documentElement.clientWidth - anchor.right}px`;
document.body.append(backdrop, box);
render();
input.focus();
+14 -1
View File
@@ -4,7 +4,8 @@ import { h, replace } from "./dom.js";
import { mountFiles } from "./files.js";
import { mountInbox } from "./inbox.js";
import { mountLead } from "./lead.js";
import { focusTab, on, state, tabs } from "./state.js";
import { openSwitcher } from "./navbar.js";
import { findTask, focusedProject, focusTab, on, onEach, state, tabs } from "./state.js";
import { mountProject } from "./project.js";
import { mountTranscript } from "./task.js";
@@ -25,9 +26,17 @@ export function leadPane(): HTMLElement {
export function taskPane(): HTMLElement {
const strip = h("nav", { class: "pane-tabs" });
const body = h("div", { class: "pane-body" });
const picker = h("button", { class: "task-picker", title: "Switch task (⌘K)" });
picker.onclick = openSwitcher;
let shown: TaskTab | undefined;
let dispose: () => void = () => {};
const renderPicker = () => {
const task = state.selectedTaskId ? findTask(state.selectedTaskId) : undefined;
const project = focusedProject();
replace(picker, task ? `${project?.name ?? ""} ▸ ${task.title}` : (project?.name ?? "No task"), " ▾");
};
const render = () => {
const first = state.focus.tabs.filter((t, i, all) => tabs.includes(t) && all.indexOf(t) === i);
const order = [...first, ...tabs.filter((t) => !first.includes(t))];
@@ -39,6 +48,8 @@ export function taskPane(): HTMLElement {
b.onclick = () => focusTab(t);
return b;
}),
h("span", { class: "spacer" }),
picker,
);
if (tab === shown) {
return;
@@ -51,7 +62,9 @@ export function taskPane(): HTMLElement {
};
on("focus", render);
onEach(["projects", "tasks", "selection", "focus"], renderPicker);
render();
renderPicker();
const pane = h("section", { class: "pane", tabIndex: -1 }, strip, body);
trackPaneFocus(pane);
return pane;
+8 -17
View File
@@ -190,20 +190,6 @@ textarea {
text-decoration: none;
}
.crumb {
border-color: transparent;
background: transparent;
max-width: 50%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.crumb:hover:not(:disabled) {
background: var(--raised);
border-color: var(--border);
}
.spacer {
flex: 1;
}
@@ -288,6 +274,14 @@ textarea {
padding: 8px 10px 6px;
}
.pane-tabs .task-picker {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--text);
}
.pane-tabs button:hover:not(:disabled),
.range-tabs button:hover:not(:disabled),
.modes button:hover:not(:disabled) {
@@ -1509,9 +1503,6 @@ button.danger-primary {
.switcher {
position: fixed;
top: 15vh;
left: 50%;
translate: -50% 0;
width: min(560px, 90vw);
background: var(--raised);
border: 1px solid var(--border);