Files
nexus/public/assets/js/app.js
Lars Gebhardt-Kusche 73dae688ab
All checks were successful
Deploy / deploy-staging (push) Successful in 6s
Deploy / deploy-production (push) Has been skipped
popup
2026-04-27 02:07:34 +02:00

191 lines
6.7 KiB
JavaScript
Executable File

document.documentElement.classList.add('js');
function readThemePreference(key, fallback) {
try {
return localStorage.getItem(key) || fallback;
} catch (error) {
return fallback;
}
}
const moduleName = document.documentElement.dataset.module || '';
const mainThemeMode = readThemePreference('nexus.theme', 'day');
const mainThemeAccent = readThemePreference('nexus.accent', 'logo');
const moduleThemeMode = moduleName ? readThemePreference(`nexus.module.${moduleName}.theme`, 'inherit') : mainThemeMode;
const moduleThemeAccent = moduleName ? readThemePreference(`nexus.module.${moduleName}.accent`, 'inherit') : mainThemeAccent;
function normalizeThemeValue(value, allowed, fallback) {
return allowed.includes(value) ? value : fallback;
}
function storedTheme() {
const rawMode = moduleName ? readThemePreference(`nexus.module.${moduleName}.theme`, 'inherit') : readThemePreference('nexus.theme', 'day');
const rawAccent = moduleName ? readThemePreference(`nexus.module.${moduleName}.accent`, 'inherit') : readThemePreference('nexus.accent', 'logo');
const mode = moduleName ? normalizeThemeValue(rawMode, ['inherit', 'day', 'night'], 'inherit') : normalizeThemeValue(rawMode, ['day', 'night'], 'day');
const accent = moduleName ? normalizeThemeValue(rawAccent, ['inherit', 'logo', 'pink', 'cyan', 'orange', 'green'], 'inherit') : normalizeThemeValue(rawAccent, ['logo', 'pink', 'cyan', 'orange', 'green'], 'logo');
return { mode, accent };
}
function applyTheme(mode, accent, persist = true) {
const allowedModes = moduleName ? ['inherit', 'day', 'night'] : ['day', 'night'];
const allowedAccents = moduleName ? ['inherit', 'logo', 'pink', 'cyan', 'orange', 'green'] : ['logo', 'pink', 'cyan', 'orange', 'green'];
const normalizedMode = normalizeThemeValue(mode, allowedModes, moduleName ? 'inherit' : 'day');
const normalizedAccent = normalizeThemeValue(accent, allowedAccents, moduleName ? 'inherit' : 'logo');
const effectiveMode = normalizedMode === 'inherit' ? mainThemeMode : normalizedMode;
const effectiveAccent = normalizedAccent === 'inherit' ? mainThemeAccent : normalizedAccent;
document.documentElement.dataset.theme = effectiveMode;
document.documentElement.dataset.accent = effectiveAccent;
try {
if (persist) {
if (moduleName) {
if (normalizedMode === 'inherit') {
localStorage.removeItem(`nexus.module.${moduleName}.theme`);
} else {
localStorage.setItem(`nexus.module.${moduleName}.theme`, normalizedMode);
}
if (normalizedAccent === 'inherit') {
localStorage.removeItem(`nexus.module.${moduleName}.accent`);
} else {
localStorage.setItem(`nexus.module.${moduleName}.accent`, normalizedAccent);
}
} else {
localStorage.setItem('nexus.theme', normalizedMode);
localStorage.setItem('nexus.accent', normalizedAccent);
}
}
} catch (error) {
// Ignore blocked storage; the current page still receives the theme.
}
return { mode: normalizedMode, accent: normalizedAccent };
}
const initialTheme = applyTheme(moduleThemeMode, moduleThemeAccent, false);
const themeModeSelect = document.querySelector('[data-theme-mode]');
const themeAccentSelect = document.querySelector('[data-theme-accent]');
if (themeModeSelect) {
themeModeSelect.value = initialTheme.mode;
themeModeSelect.addEventListener('change', () => {
const current = storedTheme();
applyTheme(themeModeSelect.value, current.accent);
});
}
if (themeAccentSelect) {
themeAccentSelect.value = initialTheme.accent;
themeAccentSelect.addEventListener('change', () => {
const current = storedTheme();
applyTheme(current.mode, themeAccentSelect.value);
});
}
for (const element of document.querySelectorAll('[data-reveal]')) {
element.classList.add('reveal');
}
const setupTabs = document.querySelectorAll('[data-setup-tab-target]');
if (setupTabs.length > 0) {
const setupPanels = document.querySelectorAll('.setup-db-panel');
const setupControls = document.querySelectorAll('.setup-db-panel input, .setup-db-panel select, .setup-db-panel textarea');
for (const control of setupControls) {
if (control.required) {
control.dataset.setupRequired = 'true';
}
}
const activateSetupTab = (targetId) => {
for (const tab of setupTabs) {
const isActive = tab.dataset.setupTabTarget === targetId;
tab.classList.toggle('is-active', isActive);
tab.setAttribute('aria-selected', isActive ? 'true' : 'false');
}
for (const panel of setupPanels) {
const isActive = panel.id === targetId;
panel.hidden = !isActive;
for (const control of panel.querySelectorAll('input, select, textarea')) {
if (control.dataset.setupRequired === 'true') {
control.required = isActive;
}
}
}
};
for (const tab of setupTabs) {
tab.addEventListener('click', () => {
activateSetupTab(tab.dataset.setupTabTarget);
});
}
const activeSetupTab = document.querySelector('[data-setup-tab-target].is-active') || setupTabs[0];
activateSetupTab(activeSetupTab.dataset.setupTabTarget);
}
window.NexusModal = (() => {
const openModals = new Set();
const syncBodyState = () => {
document.body.classList.toggle('has-modal-open', openModals.size > 0);
};
const create = (root, options = {}) => {
if (!root) {
return null;
}
const modal = {
root,
isOpen: () => root.classList.contains('is-open'),
open() {
root.classList.add('is-open');
root.setAttribute('aria-hidden', 'false');
openModals.add(root);
syncBodyState();
if (typeof options.onOpen === 'function') {
options.onOpen();
}
const focusTarget = options.initialFocus ? root.querySelector(options.initialFocus) : null;
if (focusTarget instanceof HTMLElement) {
window.setTimeout(() => focusTarget.focus(), 20);
}
},
close() {
root.classList.remove('is-open');
root.setAttribute('aria-hidden', 'true');
openModals.delete(root);
syncBodyState();
if (typeof options.onClose === 'function') {
options.onClose();
}
},
};
if (!root.dataset.modalBound) {
root.addEventListener('click', (event) => {
if (event.target === root) {
modal.close();
}
});
root.dataset.modalBound = '1';
}
return modal;
};
document.addEventListener('keydown', (event) => {
if (event.key !== 'Escape') {
return;
}
const active = Array.from(openModals).pop();
if (!active) {
return;
}
active.classList.remove('is-open');
active.setAttribute('aria-hidden', 'true');
openModals.delete(active);
syncBodyState();
});
return { create };
})();