mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Introduce toolbar panel rows
This commit is contained in:
parent
5819414e28
commit
2f56caf79c
3 changed files with 48 additions and 46 deletions
|
|
@ -257,12 +257,12 @@ function renderToolbarPanel(toolbar: HTMLElement, model: ToolbarViewModel, actio
|
|||
|
||||
function renderStatusPanel(parent: HTMLElement, model: ToolbarViewModel, actions: ToolbarActions): void {
|
||||
const statusItems = parent.createDiv({ cls: "codex-panel__status-panel-items", attr: { role: "menu" } });
|
||||
createToolbarPanelItem(statusItems, model.connectLabel, { onClick: actions.connect, className: "codex-panel__status-panel-item" });
|
||||
createToolbarPanelItem(statusItems, "Refresh diagnostics", {
|
||||
createToolbarPanelRow(statusItems, model.connectLabel, { onClick: actions.connect, className: "codex-panel__status-panel-item" });
|
||||
createToolbarPanelRow(statusItems, "Refresh diagnostics", {
|
||||
onClick: actions.refreshDiagnostics,
|
||||
className: "codex-panel__status-panel-item",
|
||||
});
|
||||
createToolbarPanelItem(statusItems, "Refresh thread list", {
|
||||
createToolbarPanelRow(statusItems, "Refresh thread list", {
|
||||
onClick: actions.refreshThreads,
|
||||
className: "codex-panel__status-panel-item",
|
||||
});
|
||||
|
|
@ -310,25 +310,21 @@ function renderConnectionDiagnostics(parent: HTMLElement, sections: ToolbarDiagn
|
|||
}
|
||||
|
||||
function renderRuntimePicker(parent: HTMLElement, model: ToolbarViewModel): void {
|
||||
const picker = parent.createDiv({ cls: "codex-panel__runtime-picker", attr: { role: "listbox", "aria-label": "Runtime controls" } });
|
||||
const picker = parent.createDiv({ cls: "codex-panel__runtime-picker", attr: { role: "listbox" } });
|
||||
picker.createDiv({ cls: "codex-panel__runtime-picker-label", text: "Reasoning effort" });
|
||||
for (const choice of model.effortChoices) {
|
||||
createToolbarPanelItem(picker, choice.label, { ...choice, className: "codex-panel__runtime-choice", highlightSelected: false });
|
||||
createToolbarPanelRow(picker, choice.label, { ...choice, className: "codex-panel__runtime-choice" });
|
||||
}
|
||||
picker.createDiv({ cls: "codex-panel__runtime-picker-label", text: "Model" });
|
||||
for (const choice of model.modelChoices) {
|
||||
createToolbarPanelItem(picker, choice.label, { ...choice, className: "codex-panel__runtime-choice", highlightSelected: false });
|
||||
createToolbarPanelRow(picker, choice.label, { ...choice, className: "codex-panel__runtime-choice" });
|
||||
}
|
||||
}
|
||||
|
||||
function renderThreadList(parent: HTMLElement, threads: ToolbarThreadRow[], actions: ToolbarActions): void {
|
||||
const threadsEl = parent.createDiv({ cls: "codex-panel__threads" });
|
||||
if (threads.length === 0) {
|
||||
const empty = threadsEl.createDiv({
|
||||
cls: "menu-item codex-panel__toolbar-panel-item codex-panel__thread codex-panel__thread--empty is-disabled",
|
||||
});
|
||||
empty.createSpan({ cls: "menu-item-icon codex-panel__toolbar-panel-check" });
|
||||
empty.createSpan({ cls: "menu-item-title codex-panel__toolbar-panel-label", text: "No threads" });
|
||||
createToolbarPanelRow(threadsEl, "No threads", { disabled: true, className: "codex-panel__thread codex-panel__thread--empty" });
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -341,7 +337,7 @@ function renderThreadList(parent: HTMLElement, threads: ToolbarThreadRow[], acti
|
|||
continue;
|
||||
}
|
||||
|
||||
createToolbarPanelItem(row, thread.title, {
|
||||
createToolbarPanelRow(row, thread.title, {
|
||||
selected: thread.selected,
|
||||
disabled: thread.disabled,
|
||||
title: `${thread.title}\n${thread.threadId}`,
|
||||
|
|
@ -374,7 +370,7 @@ function renderThreadList(parent: HTMLElement, threads: ToolbarThreadRow[], acti
|
|||
function renderThreadRenameRow(parent: HTMLElement, thread: ToolbarThreadRow, actions: ToolbarActions): void {
|
||||
const form = parent.createDiv({ cls: "menu-item codex-panel__toolbar-panel-item codex-panel__thread codex-panel__thread-rename" });
|
||||
form.createSpan({
|
||||
cls: "menu-item-icon codex-panel__toolbar-panel-check codex-panel__thread-rename-spacer",
|
||||
cls: "codex-panel__toolbar-panel-check codex-panel__thread-rename-spacer",
|
||||
attr: { "aria-hidden": "true" },
|
||||
});
|
||||
const field = form.createDiv({ cls: "codex-panel__thread-rename-field" });
|
||||
|
|
@ -431,7 +427,7 @@ function renderThreadRenameRow(parent: HTMLElement, thread: ToolbarThreadRow, ac
|
|||
};
|
||||
}
|
||||
|
||||
function createToolbarPanelItem(
|
||||
function createToolbarPanelRow(
|
||||
parent: HTMLElement,
|
||||
label: string,
|
||||
options: {
|
||||
|
|
@ -440,37 +436,34 @@ function createToolbarPanelItem(
|
|||
title?: string;
|
||||
meta?: string;
|
||||
className?: string;
|
||||
highlightSelected?: boolean;
|
||||
onClick?: () => void;
|
||||
} = {},
|
||||
): HTMLElement {
|
||||
const selected = Boolean(options.selected);
|
||||
const disabled = Boolean(options.disabled);
|
||||
const highlightSelected = options.highlightSelected ?? true;
|
||||
const item = parent.createEl("button", {
|
||||
cls: [
|
||||
"menu-item",
|
||||
"tappable",
|
||||
"codex-panel__toolbar-panel-item",
|
||||
options.className ?? "",
|
||||
selected && highlightSelected ? "selected is-selected" : "",
|
||||
disabled ? "is-disabled" : "",
|
||||
]
|
||||
const item = parent.createDiv({
|
||||
cls: ["codex-panel__toolbar-panel-item", options.className ?? "", selected ? "is-checked" : "", disabled ? "is-disabled" : ""]
|
||||
.filter(Boolean)
|
||||
.join(" "),
|
||||
attr: {
|
||||
type: "button",
|
||||
role: "button",
|
||||
tabindex: disabled ? "-1" : "0",
|
||||
title: options.title ?? label,
|
||||
"aria-disabled": disabled ? "true" : "false",
|
||||
"aria-selected": selected ? "true" : "false",
|
||||
},
|
||||
});
|
||||
item.disabled = disabled;
|
||||
item.onclick = () => {
|
||||
if (!disabled) options.onClick?.();
|
||||
};
|
||||
const check = item.createSpan({ cls: "menu-item-icon codex-panel__toolbar-panel-check" });
|
||||
item.onkeydown = (event) => {
|
||||
if (disabled || (event.key !== "Enter" && event.key !== " ")) return;
|
||||
event.preventDefault();
|
||||
options.onClick?.();
|
||||
};
|
||||
const check = item.createSpan({ cls: "codex-panel__toolbar-panel-check" });
|
||||
if (selected) setIcon(check, "check");
|
||||
item.createSpan({ cls: "menu-item-title codex-panel__toolbar-panel-label", text: label });
|
||||
item.createSpan({ cls: "codex-panel__toolbar-panel-label", text: label });
|
||||
if (options.meta) item.createSpan({ cls: "codex-panel__toolbar-panel-meta", text: options.meta });
|
||||
return item;
|
||||
}
|
||||
|
|
|
|||
39
styles.css
39
styles.css
|
|
@ -490,13 +490,13 @@
|
|||
gap: var(--codex-panel-toolbar-panel-row-gap);
|
||||
}
|
||||
|
||||
.codex-panel .menu-item.codex-panel__toolbar-panel-item {
|
||||
.codex-panel__toolbar-panel-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--size-4-2);
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: auto;
|
||||
min-height: var(--codex-panel-size-nav-item);
|
||||
padding: var(--size-4-1) var(--size-4-2);
|
||||
border: 0;
|
||||
border-radius: var(--radius-s);
|
||||
|
|
@ -509,12 +509,18 @@
|
|||
line-height: var(--codex-panel-toolbar-line-height);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transform: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.codex-panel__toolbar-panel-check {
|
||||
flex: 0 0 auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: var(--codex-panel-size-icon-s);
|
||||
width: var(--codex-panel-size-icon-s);
|
||||
height: var(--codex-panel-size-icon-s);
|
||||
color: var(--text-accent);
|
||||
}
|
||||
|
||||
|
|
@ -539,23 +545,28 @@
|
|||
font-size: var(--codex-panel-toolbar-meta-size);
|
||||
}
|
||||
|
||||
.codex-panel .menu-item.codex-panel__toolbar-panel-item:hover:not(:where(:disabled, .is-disabled)),
|
||||
.codex-panel .menu-item.codex-panel__toolbar-panel-item.selected,
|
||||
.codex-panel .menu-item.codex-panel__toolbar-panel-item.is-selected,
|
||||
.codex-panel .menu-item.codex-panel__toolbar-panel-item.is-active {
|
||||
.codex-panel__toolbar-panel-item:hover:not(:where(:disabled, .is-disabled)) {
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
.codex-panel .menu-item.codex-panel__toolbar-panel-item:hover:not(:where(:disabled, .is-disabled)) {
|
||||
background: var(--background-modifier-hover);
|
||||
box-shadow: none;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.codex-panel .menu-item.codex-panel__toolbar-panel-item.is-disabled {
|
||||
.codex-panel__toolbar-panel-item:focus,
|
||||
.codex-panel__toolbar-panel-item:focus-visible,
|
||||
.codex-panel__toolbar-panel-item:active {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.codex-panel__toolbar-panel-item.is-disabled {
|
||||
cursor: default;
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.codex-panel .menu-item.codex-panel__toolbar-panel-item:disabled {
|
||||
.codex-panel__toolbar-panel-item:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
|
@ -1123,16 +1134,14 @@
|
|||
grid-template-columns: minmax(0, 1fr) auto auto auto;
|
||||
}
|
||||
|
||||
.codex-panel .menu-item.codex-panel__thread:hover:not(:where(:disabled, .is-disabled)),
|
||||
.codex-panel .menu-item.codex-panel__thread.selected,
|
||||
.codex-panel .menu-item.codex-panel__thread.is-selected {
|
||||
.codex-panel__thread:hover:not(:where(:disabled, .is-disabled)),
|
||||
.codex-panel__thread.is-checked {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.codex-panel .menu-item.codex-panel__thread-rename {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
height: auto;
|
||||
min-height: var(--codex-panel-size-nav-item);
|
||||
padding-block: var(--size-4-1);
|
||||
cursor: text;
|
||||
|
|
|
|||
|
|
@ -1783,8 +1783,8 @@ describe("toolbar renderer decisions", () => {
|
|||
expect(parent.textContent).toContain("Refresh diagnostics");
|
||||
expect(parent.textContent).toContain("codex-cli/1.2.3");
|
||||
expect(parent.querySelector(".codex-panel__connection-diagnostics-row--error")?.textContent).toContain("model/list failed");
|
||||
[...parent.querySelectorAll<HTMLButtonElement>(".codex-panel__status-panel-item")]
|
||||
.find((button) => button.textContent.includes("Refresh diagnostics"))
|
||||
[...parent.querySelectorAll<HTMLElement>(".codex-panel__status-panel-item")]
|
||||
.find((item) => item.textContent.includes("Refresh diagnostics"))
|
||||
?.click();
|
||||
expect(refreshDiagnostics).toHaveBeenCalled();
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue