mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-23 08:58:11 -07:00
118 lines
3.3 KiB
JavaScript
118 lines
3.3 KiB
JavaScript
import { bindHold } from "../../utils/hold.js";
|
||
|
||
const SIDEBAR_ID = "ask-sidebar";
|
||
|
||
/** @typedef {import("../storage.js").ChatMeta} ChatMeta */
|
||
|
||
/**
|
||
* @typedef {Object} AskSidebarOptions
|
||
* @property {() => void} onNew
|
||
* @property {() => void} onToggle
|
||
* @property {(id: string) => void} onSelect
|
||
* @property {(id: string) => void} onRemove
|
||
*/
|
||
|
||
/**
|
||
* @param {string} label
|
||
* @param {string} glyph
|
||
*/
|
||
function createControl(label, glyph) {
|
||
const button = document.createElement("button");
|
||
const icon = document.createElement("span");
|
||
|
||
button.type = "button";
|
||
button.ariaLabel = label;
|
||
button.title = label;
|
||
icon.ariaHidden = "true";
|
||
icon.append(glyph);
|
||
button.append(icon);
|
||
return button;
|
||
}
|
||
|
||
/** @param {AskSidebarOptions} options */
|
||
export function createAskSidebar(options) {
|
||
const backdrop = document.createElement("button");
|
||
const aside = document.createElement("aside");
|
||
const chats = document.createElement("ol");
|
||
const controls = document.createElement("nav");
|
||
const newChat = createControl("New chat", "+");
|
||
const toggle = createControl("Show chats", "→");
|
||
let busy = false;
|
||
|
||
backdrop.type = "button";
|
||
backdrop.ariaLabel = "Close chats";
|
||
aside.id = SIDEBAR_ID;
|
||
chats.setAttribute("aria-label", "Chats");
|
||
controls.setAttribute("aria-label", "Chat controls");
|
||
toggle.setAttribute("aria-controls", SIDEBAR_ID);
|
||
toggle.setAttribute("aria-expanded", "false");
|
||
newChat.addEventListener("click", options.onNew);
|
||
toggle.addEventListener("click", options.onToggle);
|
||
controls.append(newChat, toggle);
|
||
aside.append(chats);
|
||
|
||
function syncDisabled() {
|
||
newChat.disabled = busy;
|
||
toggle.disabled = busy;
|
||
for (const button of chats.querySelectorAll("button")) {
|
||
button.disabled = busy;
|
||
}
|
||
}
|
||
|
||
return {
|
||
backdrop,
|
||
controls,
|
||
element: aside,
|
||
newChat,
|
||
toggle,
|
||
/** @param {boolean} value */
|
||
setBusy(value) {
|
||
busy = value;
|
||
syncDisabled();
|
||
},
|
||
/** @param {boolean} expanded */
|
||
setExpanded(expanded) {
|
||
const label = expanded ? "Hide chats" : "Show chats";
|
||
|
||
toggle.setAttribute("aria-expanded", String(expanded));
|
||
toggle.ariaLabel = label;
|
||
toggle.title = label;
|
||
},
|
||
/**
|
||
* @param {ChatMeta[]} items
|
||
* @param {string} activeId
|
||
*/
|
||
render(items, activeId) {
|
||
const scrollTop = chats.scrollTop;
|
||
const rows = items.filter((chat) => chat.messageCount > 0).map((chat) => {
|
||
const item = document.createElement("li");
|
||
const select = document.createElement("button");
|
||
const remove = document.createElement("button");
|
||
|
||
select.type = "button";
|
||
select.title = chat.title;
|
||
select.textContent = chat.title;
|
||
if (chat.id === activeId) select.setAttribute("aria-current", "page");
|
||
select.addEventListener("click", () => options.onSelect(chat.id));
|
||
|
||
remove.type = "button";
|
||
remove.ariaLabel = `Hold to delete ${chat.title}`;
|
||
remove.title = "Hold to delete chat";
|
||
const icon = document.createElement("span");
|
||
|
||
icon.ariaHidden = "true";
|
||
icon.append("×");
|
||
remove.append(icon);
|
||
bindHold(remove, () => options.onRemove(chat.id), 1_000);
|
||
|
||
item.append(select, remove);
|
||
return item;
|
||
});
|
||
|
||
chats.replaceChildren(...rows);
|
||
chats.scrollTop = scrollTop;
|
||
syncDisabled();
|
||
},
|
||
};
|
||
}
|