Files
DemonGatanjieu-Anomalous_Mo…/web/modules/ui_update_guide.js
T
DemonGatanjieu 77ae7ce888 fix(ui): keep update guide manual and clean sidebar styles
Keep a persistent header guide button, restore upward label transitions, remove duplicate CSS and obsolete notice events, and move replay styling to CSS.

Document proportional UX checks and interruption recovery checkpoints. Validated update_guide.mjs, 48-module parse/link check, diff check, and live ComfyUI manual open, Escape, replay, Help and sidebar keyboard labels. No backend behavior changed; mobile and alternate themes were not exercised.
2026-09-16 11:27:19 +08:00

103 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { CURRENT_UPDATE_GUIDE, validateUpdateGuide } from './update_guide_data.js';
import { i18n, translate as t } from './locales.js';
import { createViewScope } from './ui_lifecycle.js';
import { text } from './ui_dom.js';
const acknowledgedThisSession = new Set();
let activeGuide = null;
const storageKey = id => `anomalous_update_guide_seen:${id}`;
export function hasAcknowledged(id = CURRENT_UPDATE_GUIDE.id) {
if (acknowledgedThisSession.has(id)) return true;
try { return localStorage.getItem(storageKey(id)) === '1'; }
catch (error) { return false; } // Storage may be unavailable; session state still works.
}
export function closeUpdateGuide(owner, acknowledge = false) {
if (!activeGuide || activeGuide.owner !== owner) return;
const current = activeGuide;
activeGuide = null;
if (acknowledge) {
acknowledgedThisSession.add(current.id);
try { localStorage.setItem(storageKey(current.id), '1'); }
catch (error) { console.warn('[AMB] Update guide dismissal is saved for this session only.', error); }
}
current.scope.dispose();
}
/** Opened only by explicit user action in the header or Help. No graph or file writes. */
export function showUpdateGuide(owner, { force = false, guide = CURRENT_UPDATE_GUIDE } = {}) {
if (!owner.modal?.classList.contains('visible')) return false;
if (!validateUpdateGuide(guide, i18n)) {
console.warn('[AMB] Invalid update guide configuration; guide skipped.');
return false;
}
if (activeGuide?.owner === owner && activeGuide.id === guide.id) return false;
if (!force && hasAcknowledged(guide.id)) return false;
if (activeGuide) closeUpdateGuide(activeGuide.owner);
const scope = createViewScope();
const previousFocus = document.activeElement;
const dialog = document.createElement('dialog');
dialog.className = 'anomalous-update-guide';
dialog.setAttribute('aria-labelledby', 'anomalous-update-guide-title');
dialog.setAttribute('aria-describedby', 'anomalous-update-guide-body');
activeGuide = { owner, id: guide.id, scope };
scope.onDispose(() => {
if (dialog.open) dialog.close();
dialog.remove();
if (previousFocus?.isConnected) previousFocus.focus({ preventScroll: true });
});
const header = text(dialog, 'div', '', 'anomalous-update-guide-header');
text(header, 'span', t('updateGuideTitle'));
const dismiss = text(header, 'button', '×', 'anomalous-update-guide-close');
dismiss.type = 'button';
dismiss.setAttribute('aria-label', t('close'));
dismiss.onclick = () => closeUpdateGuide(owner, true);
const icon = text(dialog, 'div', '', 'anomalous-update-guide-icon');
icon.setAttribute('aria-hidden', 'true');
const title = text(dialog, 'h2', '');
title.id = 'anomalous-update-guide-title';
const body = text(dialog, 'p', '');
body.id = 'anomalous-update-guide-body';
const progress = text(dialog, 'p', '', 'anomalous-update-guide-progress');
progress.setAttribute('aria-live', 'polite');
const footer = text(dialog, 'div', '', 'anomalous-update-guide-footer');
const skip = text(footer, 'button', t('updateGuideSkip'), 'anomalous-btn-ghost');
const back = text(footer, 'button', t('updateGuideBack'), 'anomalous-btn-ghost');
const next = text(footer, 'button', '', 'anomalous-btn-primary');
for (const button of [skip, back, next]) button.type = 'button';
let index = 0;
const render = () => {
const step = guide.steps[index];
icon.textContent = step.icon;
title.textContent = t(step.titleKey);
body.textContent = t(step.bodyKey);
progress.textContent = t('updateGuideProgress', { current: index + 1, total: guide.steps.length });
back.disabled = index === 0;
next.textContent = t(index === guide.steps.length - 1 ? 'updateGuideDone' : 'updateGuideNext');
};
skip.onclick = () => closeUpdateGuide(owner, true);
back.onclick = () => { if (index > 0) { index--; render(); } };
next.onclick = () => {
if (index === guide.steps.length - 1) closeUpdateGuide(owner, true);
else { index++; render(); }
};
scope.listen(dialog, 'keydown', event => {
if (event.key === 'Escape') {
event.preventDefault();
event.stopPropagation();
closeUpdateGuide(owner, true);
}
});
scope.listen(dialog, 'cancel', event => { event.preventDefault(); closeUpdateGuide(owner, true); });
scope.listen(dialog, 'close', () => closeUpdateGuide(owner));
scope.listen(dialog, 'click', event => { if (event.target === dialog) closeUpdateGuide(owner, true); });
render();
document.body.appendChild(dialog);
dialog.showModal();
next.focus();
return true;
}