Files
Vito d4bb5c5ba9 feat(ui): add the tested opt-in Prompt Manager workspace shell (#198)
* docs(ui): prototype unified prompt management application

* docs(ui): add masonry and per-image tagging to application concept

* docs(ui): make gallery concept use continuous infinite scrolling

* feat(ui): introduce the tested application workspace shell

* fix(docs): preserve concept controls across cached navigation

* test(ci): emit Python tracebacks for native test crashes

* test(ui): cover workspace restoration across cached navigation
2026-10-17 10:36:30 -07:00

74 lines
3.1 KiB
JavaScript

const STORAGE_KEY = 'pm_workspace_theme';
const PREFERENCES = new Set(['follow', 'light', 'dark']);
const LIGHT_PALETTES = new Set(['light', 'solarized', 'github', 'arc']);
const DARK_PALETTES = new Set(['dark', 'obsidian', 'nord', 'dracula', 'gruvbox']);
/** Adapt the standalone page to ComfyUI without coupling the shared toolkit to HTTP. */
export function mountWorkspaceTheme({ root = document.body, scope, fetchImpl = globalThis.fetch, storage, win = window }) {
const documentElement = root.ownerDocument.documentElement;
const previousTheme = root.dataset.caTheme;
const previousScheme = documentElement.style.colorScheme;
const system = win.matchMedia('(prefers-color-scheme: dark)');
let preference = 'follow';
let hostTheme = null;
let request = null;
let sequence = 0;
try {
storage ??= win.localStorage;
const saved = storage?.getItem(STORAGE_KEY);
if (PREFERENCES.has(saved)) preference = saved;
} catch { /* Storage can be disabled while theme controls remain usable. */ }
function update() {
if (scope.signal.aborted) return;
const theme = preference === 'follow' ? hostTheme || (system.matches ? 'dark' : 'light') : preference;
root.dataset.caTheme = theme;
documentElement.style.colorScheme = theme;
}
async function refreshHost() {
if (scope.signal.aborted) return;
request?.abort();
const controller = new AbortController();
request = controller;
const current = ++sequence;
let next = null;
try {
const prefix = win.location.pathname.match(/^(.*)\/prompt_manager(?:\/|$)/)?.[1] || '';
const response = await fetchImpl(`${prefix}/api/settings/Comfy.ColorPalette`, { signal: controller.signal });
if (response.ok) {
const data = await response.json();
const value = typeof data === 'string' ? data : data?.value;
const name = typeof value === 'string' ? value.toLowerCase().trim() : '';
next = LIGHT_PALETTES.has(name) ? 'light' : DARK_PALETTES.has(name) ? 'dark' : null;
}
} catch { /* A standalone or unavailable host follows the system preference. */ }
if (scope.signal.aborted || current !== sequence) return;
hostTheme = next;
update();
}
scope.listen(system, 'change', update);
scope.listen(win, 'focus', refreshHost);
scope.cleanup(() => {
sequence++;
request?.abort();
if (previousTheme === undefined) delete root.dataset.caTheme;
else root.dataset.caTheme = previousTheme;
documentElement.style.colorScheme = previousScheme;
});
update();
const ready = refreshHost();
return {
ready,
get preference() { return preference; },
setPreference(value) {
if (scope.signal.aborted) return;
if (!PREFERENCES.has(value)) throw new TypeError('Unknown theme preference');
preference = value;
try { storage?.setItem(STORAGE_KEY, value); } catch { /* Keep the in-memory preference. */ }
update();
},
};
}