A bare glyph character had no framing at all; filterList's icon now gets a coloured chip, drawn from a fixed tone (accent/danger/warn/good/neutral) -- never a raw colour from data, the same rule el()'s style refusal already holds everywhere else. Each Settings section picks one. The sidebar was the filterList element's small dropdown-list shape (a 240px-capped box with its own border, floating in a large pane) rather than a real full-height sidebar; .cx-filter-full opts a host into the pane version without changing the compact one pipeline_window's own node picker still uses. Round one had a per-row hint line under the icon+label, which doesn't exist in v4's own sidebar and broke alignment the moment a hint wrapped (icon and label baseline-aligned against a hint floating as an independent sibling rather than stacked under its own label). Fixed the real bug -- label+hint are a column now, not loose flex siblings -- then removed the hint entirely to match v4: a section's subtitle belongs in its own head once picked, not repeated under every row whether picked or not. About is centred -- mark, name, version/branch -- rather than a plain settings-row list, the one section built fresh rather than mounting an existing window.
136 lines
5.4 KiB
JavaScript
136 lines
5.4 KiB
JavaScript
// One home for everything that is a PREFERENCE about the app or the pipeline,
|
|
// rather than an edit to the project on screen -- replacing five separate
|
|
// menu items (Models and pipeline, Updates, Node packs, Log, Temp files) that
|
|
// nothing tied together and nothing let you search.
|
|
//
|
|
// Every section is MOUNTED here, in place, beside the same always-visible
|
|
// nav list -- picking "Node packs" does not leave this window any more than
|
|
// clicking a different System Settings pane leaves System Settings. Each of
|
|
// pipeline_window.js/updates.js/packs.js/logwindow.js/tempfiles.js already
|
|
// had its content built separately from its own modal chrome (a `body`
|
|
// region.stack, footer buttons set on the modal handle); their `mount()`
|
|
// exports are that same content, taking `setFooter`/`close` from whoever is
|
|
// hosting them instead of owning a modal directly. Their `open()` exports
|
|
// are unchanged for anyone opening one standalone.
|
|
|
|
import { composer } from "../composer/composer.js";
|
|
|
|
/**
|
|
* createSettingsWindow({ sections, gitStatus }) -> { open, close }
|
|
*
|
|
* `sections`: [{ id, title, subtitle, keywords, icon,
|
|
* mount({ setFooter, close }) -> handle }].
|
|
* `mount()` returns a composer element handle (its own `destroy()` is the
|
|
* cleanup, called when another section is picked or the window closes).
|
|
* `setFooter`/`close` act on this window's own modal chrome -- a section
|
|
* does not know or care whether it is standing alone or hosted here.
|
|
*
|
|
* `gitStatus` is injected, not imported, for the same reason every other
|
|
* shell window takes its network calls as props: testable without a server,
|
|
* and unable to reach for one that was not handed to it.
|
|
*
|
|
* `current` lives HERE, per call, not at module scope: a module-level
|
|
* singleton would let a second independent settings window (there is only
|
|
* ever one in this app today, but nothing enforced that) silently redraw
|
|
* the FIRST one's modal instead of opening its own.
|
|
*/
|
|
export function createSettingsWindow({ sections = [], gitStatus } = {}) {
|
|
const all = [aboutSection(gitStatus), ...sections];
|
|
let current = null;
|
|
|
|
function open(id) {
|
|
if (current) { current.show(id); return current; }
|
|
|
|
let activeCleanup = null;
|
|
const contentStack = composer.region.stack({ gap: "sm", fill: true });
|
|
|
|
function showSection(pick) {
|
|
const spec = all.find((s) => s.id === pick) || all[0];
|
|
if (!spec) return;
|
|
if (activeCleanup) { activeCleanup.destroy?.(); activeCleanup = null; }
|
|
// Cleared before mounting: a section that sets no footer of its own
|
|
// (About) must not go on showing the PREVIOUS section's buttons.
|
|
modal.setFooter({});
|
|
const content = spec.mount({
|
|
setFooter: (f) => modal.setFooter(f),
|
|
close: () => modal.close("done"),
|
|
});
|
|
activeCleanup = content;
|
|
contentStack.set([
|
|
composer.header.md({ text: spec.title }),
|
|
spec.subtitle ? composer.hint.default({ text: spec.subtitle }) : null,
|
|
content,
|
|
]);
|
|
nav.setValue(spec.id);
|
|
}
|
|
|
|
const nav = composer.filterList.md({
|
|
// No hint line here -- an icon and a name, the way v4's own sidebar
|
|
// reads. What a section IS goes in its own head once picked, not
|
|
// repeated under every row whether picked or not.
|
|
items: all.map((s) => ({
|
|
id: s.id, label: s.title, icon: s.icon, keywords: s.keywords,
|
|
tone: s.tone || "neutral",
|
|
})),
|
|
value: id,
|
|
onChange: showSection,
|
|
placeholder: "Search settings",
|
|
});
|
|
// A whole pane, not the compact dropdown-style list this element is
|
|
// usually a small part of.
|
|
nav.node.classList.add("cx-filter-full");
|
|
|
|
const body = composer.splitPane.h({ panes: [nav, contentStack], size: 25, min: 15, label: "Settings" });
|
|
const modal = composer.modal.generic({
|
|
title: "Settings", size: "xl", body,
|
|
onClose: () => {
|
|
if (activeCleanup) { activeCleanup.destroy?.(); activeCleanup = null; }
|
|
if (current === handle) current = null;
|
|
},
|
|
});
|
|
|
|
const handle = { node: modal.node, close: () => modal.close(), show: showSection };
|
|
current = handle;
|
|
showSection(id || (all[0] && all[0].id));
|
|
return handle;
|
|
}
|
|
|
|
function close() {
|
|
if (current) current.close();
|
|
}
|
|
|
|
return { open, close };
|
|
}
|
|
|
|
/** Version and branch -- the one thing everyone with the window open at all
|
|
* might be there to check, so it is built in rather than one more click away. */
|
|
function aboutSection(gitStatus) {
|
|
return {
|
|
id: "about", title: "About FunPack",
|
|
subtitle: "The version and branch running right now.",
|
|
keywords: "about version commit branch",
|
|
icon: "◉", tone: "neutral",
|
|
mount() {
|
|
const stack = composer.region.stack({ gap: "md" });
|
|
stack.node.classList.add("cx-about");
|
|
const rows = composer.region.stack({ gap: "none" });
|
|
const row = (label, value) => composer.settingsRow.default({
|
|
label, control: composer.text.sm({ text: value }) });
|
|
rows.set([row("Version", "…"), row("Branch", "…")]);
|
|
stack.set([
|
|
composer.text.lg({ text: "◉" }),
|
|
composer.header.lg({ text: "FunPack" }),
|
|
rows,
|
|
]);
|
|
if (gitStatus) {
|
|
gitStatus().then((s) => {
|
|
rows.set([row("Version", s.version || "unknown"), row("Branch", s.branch || "?")]);
|
|
}).catch(() => {
|
|
rows.set([composer.hint.default({ text: "Could not read version information." })]);
|
|
});
|
|
}
|
|
return stack;
|
|
},
|
|
};
|
|
}
|