* 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
74 lines
3.1 KiB
JavaScript
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();
|
|
},
|
|
};
|
|
}
|