diff --git a/templates/index.html b/templates/index.html index ab7d1fe..0314035 100644 --- a/templates/index.html +++ b/templates/index.html @@ -330,6 +330,38 @@ display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-bottom: 16px; } + /* BUG-05: Save-Status-Indikator (ersetzt den redundanten Speichern-Button) */ + .save-indicator { + display: inline-flex; align-items: center; gap: 6px; + margin-left: auto; /* rechtsbündig im toolbar */ + padding: 6px 10px; + border-radius: 6px; + font-size: 0.82em; + color: var(--fg-muted); + transition: color 0.15s ease; + } + .save-indicator .save-dot { + width: 8px; height: 8px; border-radius: 50%; + background: var(--ok); + box-shadow: 0 0 6px rgba(74, 222, 128, 0.5); + transition: background 0.15s ease, box-shadow 0.15s ease; + } + /* Zustände */ + .save-indicator[data-state="idle"] .save-dot { background: var(--ok); } + .save-indicator[data-state="pending"] .save-dot { + background: var(--warn); animation: savePulse 1s ease-in-out infinite; + } + .save-indicator[data-state="saving"] .save-dot { + background: var(--info); animation: savePulse 0.6s ease-in-out infinite; + } + .save-indicator[data-state="error"] .save-dot { + background: var(--alert); box-shadow: 0 0 6px rgba(248, 113, 113, 0.5); + } + .save-indicator[data-state="error"] { color: var(--alert); } + @keyframes savePulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.35; } + } .add-form { display: flex; gap: 8px; align-items: center; background: var(--surface-2); @@ -1014,7 +1046,12 @@ - + +
+ + Gespeichert +
@@ -1244,8 +1281,50 @@ updateClock(); // ============ DRAG & DROP LAYOUT EDITOR ============ + async function saveLayout() { + // BUG-05: Status-Indikator statt redundantem "Speichern"-Button. + // Status-Lebenszyklus: + // pending → saving → idle (success) | error + setSaveState('saving'); + const r = await fetch('/api/layout', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ items: layoutItems }), + }); + const j = await r.json(); + if (!j.ok) { + setSaveState('error', 'Fehler: ' + (j.error || '?')); + toast('Layout speichern fehlgeschlagen: ' + (j.error || '?'), 'error', 5000); + return; + } + layoutItems = j.items; + renderGrid(); + setTimeout(refreshPreview, 500); + setSaveState('idle', 'Gespeichert'); + // nicht mehr länger toasten — der Indicator reicht + } + + // BUG-05: Save-Status-Indikator Helper + function setSaveState(state, label) { + const el = document.getElementById('saveIndicator'); + if (!el) return; + el.dataset.state = state; + const lbl = el.querySelector('.save-label'); + if (lbl && label) lbl.textContent = label; + // Idle-Reset: nach kurzer Anzeige "Gespeichert" zurück + if (state === 'saving' && !label) { + const lblEl = el.querySelector('.save-label'); + if (lblEl) lblEl.textContent = 'Speichern…'; + } + if (state === 'idle' && !label) { + const lblEl = el.querySelector('.save-label'); + if (lblEl) lblEl.textContent = 'Gespeichert'; + } + } + // BUG-05: debouncedSave setzt pending-Status let saveTimer = null; function debouncedSave() { + setSaveState('pending', 'Ungespeichert…'); if (saveTimer) clearTimeout(saveTimer); saveTimer = setTimeout(saveLayout, 500); } @@ -1608,19 +1687,7 @@ renderGrid(); toast('Widget hinzugefügt', 'success', 2000); } - async function saveLayout() { - const r = await fetch('/api/layout', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ items: layoutItems }), - }); - const j = await r.json(); - if (!j.ok) { toast('Layout speichern fehlgeschlagen: ' + (j.error || '?'), 'error', 5000); return; } - layoutItems = j.items; - renderGrid(); - setTimeout(refreshPreview, 500); - toast('Layout gespeichert', 'success'); - } + // BUG-05: alte saveLayout entfernt — siehe oben (mit setSaveState). async function packAll() { const ok = await modalConfirm({ icon: 'warn', diff --git a/tests/test_save_indicator.js b/tests/test_save_indicator.js new file mode 100644 index 0000000..8d901ca --- /dev/null +++ b/tests/test_save_indicator.js @@ -0,0 +1,74 @@ +// BUG-05 Test: Speichern-Button entfernt, Save-Indicator hinzugefügt. +// +// Erwartung: +// 1) KEIN