import type { JSX } from "@char/aftercare/jsx-runtime"; import { playing } from "../audio/transport.ts"; import { type Project, TONICS, UNTITLED } from "../project/model.ts"; import { commit, createProject, deleteProject, history, openProject, project, projectIndex, redo, undo, } from "../project/store.ts"; import { MODES } from "../theory/key.ts"; import { rekey } from "../project/harmony.ts"; import { exportMidi, exportProject, importingTheoryTab, importMidi, importProject, importTheoryTab, togglePlay, } from "./actions.ts"; import { chooseFile, watch } from "./dom.tsx"; const pretty = (s: string) => s.replace("#", "♯").replace("b", "♭"); const MODE_OPTIONS = Object.entries(MODES).map(([id, { label }]): [string, string] => [ id, label, ]); const options = (entries: [value: string, label: string][]) => entries.map(([value, label]) => ); /** a control bound to a project field: shows the current value, commits changes */ function bind( el: E, read: (p: Project) => string, write: (d: Project, value: string) => void, { history = true } = {}, ): E { watch(el, [project], () => { if (document.activeElement !== el) el.value = read(project.get()); }); el.addEventListener("change", () => { commit(d => write(d, el.value), { history }); if (el instanceof HTMLSelectElement) el.blur(); }); return el; } /** the things you do to a project as a whole, in a menu under a button */ function projectMenu() { const item = (label: string, run: () => void) => ( ); const menu = ( ) as HTMLDivElement; const toggle = ( ) as HTMLButtonElement; menu.id = "project-menu"; // opens under its button menu.addEventListener("beforetoggle", e => { if ((e as ToggleEvent).newState !== "open") return; const at = toggle.getBoundingClientRect(); Object.assign(menu.style, { left: `${at.left}px`, top: `${at.bottom + 4}px` }); }); // a press outside only closes it. the browser would close it too, but not when the press // redraws what's under it (as clicking the timeline does), and the press would still edit addEventListener( "pointerdown", e => { const target = e.target as Node; if (!menu.matches(":popover-open") || menu.contains(target) || toggle.contains(target)) return; e.stopPropagation(); e.preventDefault(); menu.hidePopover(); }, true, ); return [toggle, menu]; } export function Toolbar(): JSX.Element { const projects = ( commit(d => void (d.loop = loop.checked), { history: false })} /> ) as HTMLInputElement; watch(loop, [project], () => (loop.checked = project.get().loop)); return (
{projects} {projectMenu()}
{bind( () as HTMLSelectElement, p => p.key.tonic, (d, v) => rekey(d, { ...d.key, tonic: v }), )} {bind( () as HTMLSelectElement, p => p.key.mode, (d, v) => rekey(d, { ...d.key, mode: v as Project["key"]["mode"] }), )}
); }