WIP: Vendoo 1.44.0 – UX Foundation & Guided Workspaces #29

Draft
Masterluke77 wants to merge 284 commits from release/1.44.0-ux-foundation-production-update-v1 into main
Showing only changes of commit abcf23bf0a - Show all commits
+201
View File
@@ -0,0 +1,201 @@
(() => {
'use strict';
const root = document.getElementById('settings');
if (!root || root.dataset.uxSettingsReady === '1') return;
root.dataset.uxSettingsReady = '1';
const storageKey = 'vendoo_settings_control_center_v1';
const panelButtons = [...root.querySelectorAll('[data-settings-panel]')];
const panels = [...root.querySelectorAll('[data-settings-panel-content]')];
const hero = root.querySelector('.settings-hero');
const shell = root.querySelector('.settings-shell');
if (!hero || !shell || !panelButtons.length || !panels.length) return;
const state = { query: '', lastPanel: 'general' };
try { Object.assign(state, JSON.parse(localStorage.getItem(storageKey) || '{}')); } catch {}
const create = (tag, className, text) => {
const element = document.createElement(tag);
if (className) element.className = className;
if (text !== undefined) element.textContent = text;
return element;
};
const command = create('section', 'ux-settings-command');
command.setAttribute('aria-label', 'Einstellungen durchsuchen und Systemstatus prüfen');
const searchWrap = create('label', 'ux-settings-search');
searchWrap.setAttribute('for', 'ux-settings-search-input');
const searchTitle = create('span', '', 'Wo möchtest du etwas ändern?');
const search = create('input');
search.id = 'ux-settings-search-input';
search.type = 'search';
search.placeholder = 'Zum Beispiel eBay, Theme, OpenRouter, FLUX oder Sprache';
search.autocomplete = 'off';
search.value = state.query || '';
searchWrap.append(searchTitle, search);
const result = create('div', 'ux-settings-search-result');
result.id = 'ux-settings-search-result';
result.setAttribute('role', 'status');
result.setAttribute('aria-live', 'polite');
const health = create('div', 'ux-settings-health');
health.setAttribute('aria-label', 'Systemstatus');
const healthItems = [
['ai', 'AI-Dienste', '#settings-ai-status-text'],
['marketplaces', 'Marktplätze', '#etsy-status-badge, #ebay-status-badge'],
['flux', 'Lokales FLUX', '#settings-flux-status-text, #local-flux-engine-title'],
['general', 'Netzwerk', '#public-base-url-status'],
];
const healthCards = healthItems.map(([panel, label, selector]) => {
const button = create('button', 'ux-settings-health-card');
button.type = 'button';
button.dataset.uxSettingsHealth = panel;
const dot = create('span', 'ux-settings-health-dot');
const copy = create('span');
copy.append(create('small', '', label), create('strong', '', 'Wird geprüft …'));
button.append(dot, copy);
button.addEventListener('click', () => activatePanel(panel, { focus: true }));
health.append(button);
return { button, dot, value: copy.querySelector('strong'), selector };
});
command.append(searchWrap, result, health);
hero.insertAdjacentElement('afterend', command);
const categoryGrid = create('section', 'ux-settings-category-grid');
categoryGrid.setAttribute('aria-label', 'Einstellungsbereiche');
const categoryCopy = {
general: ['Allgemein', 'Standards, Sprache, Netzwerk und Verkäuferhinweise'],
appearance: ['Darstellung', 'Theme, Dichte, Animationen und Kontrast'],
ai: ['AI & APIs', 'Text- und Bildprovider, Modelle und Sicherheitsoptionen'],
marketplaces: ['Marktplätze', 'eBay- und Etsy-Verbindungen kontrollieren'],
flux: ['Lokales FLUX', 'ComfyUI, Leistung, Installation und Wartung'],
};
panelButtons.forEach((navButton, index) => {
const panel = navButton.dataset.settingsPanel;
const [title, copy] = categoryCopy[panel] || [panel, 'Einstellungen öffnen'];
const card = create('button', 'ux-settings-category-card');
card.type = 'button';
card.dataset.uxSettingsCategory = panel;
card.setAttribute('aria-pressed', index === 0 ? 'true' : 'false');
card.append(create('span', 'ux-settings-category-index', String(index + 1).padStart(2, '0')),
create('strong', '', title), create('small', '', copy), create('b', '', 'Öffnen →'));
card.addEventListener('click', () => activatePanel(panel, { focus: true }));
categoryGrid.append(card);
});
command.insertAdjacentElement('afterend', categoryGrid);
const advancedSelectors = [
'.theme-admin-only', '.reference-control-card', '.local-flux-status-panel',
'.settings-disclosure', '.local-flux-install-options', '.local-flux-actions'
];
panels.forEach(panel => {
const advanced = advancedSelectors.flatMap(selector => [...panel.querySelectorAll(selector)]);
if (!advanced.length || panel.querySelector(':scope > .ux-settings-advanced')) return;
const details = create('details', 'ux-settings-advanced');
const summary = create('summary');
summary.append(create('strong', '', 'Erweiterte Einstellungen'), create('small', '', 'Technische und selten benötigte Optionen'));
details.append(summary);
advanced.forEach(element => details.append(element));
panel.append(details);
});
const normalize = value => String(value || '').toLocaleLowerCase('de-DE').normalize('NFD').replace(/[\u0300-\u036f]/g, '');
const searchable = panels.map(panel => ({
panel,
id: panel.dataset.settingsPanelContent,
text: normalize(panel.textContent),
targets: [...panel.querySelectorAll('label, .settings-card-head strong, .settings-panel-head h3')]
.map(element => ({ element, text: normalize(element.textContent) }))
}));
function activatePanel(id, { focus = false } = {}) {
const nav = panelButtons.find(button => button.dataset.settingsPanel === id);
if (!nav) return;
nav.click();
state.lastPanel = id;
try { localStorage.setItem(storageKey, JSON.stringify(state)); } catch {}
root.querySelectorAll('[data-ux-settings-category]').forEach(card => {
card.setAttribute('aria-pressed', card.dataset.uxSettingsCategory === id ? 'true' : 'false');
});
if (focus) {
const panel = panels.find(item => item.dataset.settingsPanelContent === id);
window.requestAnimationFrame(() => panel?.querySelector('input, select, textarea, button')?.focus({ preventScroll: true }));
}
}
function clearHighlights() {
root.querySelectorAll('.ux-settings-search-hit').forEach(element => element.classList.remove('ux-settings-search-hit'));
}
function applySearch() {
const query = normalize(search.value.trim());
state.query = search.value.trim();
try { localStorage.setItem(storageKey, JSON.stringify(state)); } catch {}
clearHighlights();
if (query.length < 2) {
result.textContent = query ? 'Bitte mindestens zwei Zeichen eingeben.' : 'Suche über alle Einstellungsbereiche.';
return;
}
const matches = searchable.filter(entry => entry.text.includes(query));
if (!matches.length) {
result.textContent = `Keine Einstellung zu „${search.value.trim()}“ gefunden.`;
return;
}
const first = matches[0];
activatePanel(first.id);
const target = first.targets.find(item => item.text.includes(query))?.element || first.panel.querySelector('.settings-panel-head');
target?.classList.add('ux-settings-search-hit');
target?.scrollIntoView({ behavior: document.documentElement.dataset.reducedMotion === 'true' ? 'auto' : 'smooth', block: 'center' });
result.textContent = `${matches.length} Bereich${matches.length === 1 ? '' : 'e'} gefunden · ${categoryCopy[first.id]?.[0] || first.id} geöffnet.`;
}
let searchTimer;
search.addEventListener('input', () => {
window.clearTimeout(searchTimer);
searchTimer = window.setTimeout(applySearch, 180);
});
search.addEventListener('keydown', event => {
if (event.key === 'Escape') {
search.value = '';
applySearch();
search.focus();
}
});
function statusTone(text) {
const value = normalize(text);
if (/fehler|nicht|ungultig|deaktiviert|fehlgeschlagen|nicht erreichbar/.test(value)) return 'danger';
if (/wird|pruf|unbekannt|noch keine|gestoppt/.test(value)) return 'warning';
if (/bereit|verbunden|aktiv|erreichbar|ok|gesetzt/.test(value)) return 'success';
return 'neutral';
}
function refreshHealth() {
healthCards.forEach(item => {
const sources = [...root.querySelectorAll(item.selector)].filter(element => element.textContent.trim());
const text = sources.map(element => element.textContent.trim()).join(' · ') || 'Noch nicht geprüft';
const tone = statusTone(text);
item.value.textContent = text.slice(0, 72);
item.button.dataset.tone = tone;
});
}
const observed = [
'#settings-ai-status-text', '#settings-flux-status-text', '#etsy-status-badge', '#ebay-status-badge',
'#local-flux-engine-title', '#public-base-url-status'
].flatMap(selector => [...root.querySelectorAll(selector)]);
const observer = new MutationObserver(() => window.requestAnimationFrame(refreshHealth));
observed.forEach(element => observer.observe(element, { childList: true, characterData: true, subtree: true, attributes: true, attributeFilter: ['class'] }));
panelButtons.forEach(button => button.addEventListener('click', () => activatePanel(button.dataset.settingsPanel)));
activatePanel(state.lastPanel || 'general');
refreshHealth();
applySearch();
window.addEventListener('beforeunload', () => observer.disconnect(), { once: true });
})();