Merge fix/BUG-06-autopack-undo: Auto-Pack Undo via Snapshot

This commit is contained in:
ki
2026-08-29 17:50:22 +04:00
2 changed files with 114 additions and 4 deletions
+56 -4
View File
@@ -805,6 +805,21 @@
.toast.info .toast-icon { color: var(--info); }
.toast.warn .toast-icon { color: var(--warn); }
.toast-text { flex: 1; min-width: 0; word-break: break-word; }
.toast-action {
flex: 0 0 auto;
padding: 4px 10px;
background: var(--surface-3);
border: 1px solid var(--border-light);
color: var(--fg);
border-radius: 4px;
font-size: 0.85em;
font-family: inherit;
cursor: pointer;
pointer-events: auto; /* BUG-06: Klick auf Action-Button muss trotz Toast-Click-Handler gehen */
}
.toast-action:hover { background: var(--accent); color: white; border-color: var(--accent); }
.toast.success .toast-action { border-color: var(--success); color: var(--success); }
.toast.success .toast-action:hover { background: var(--success); color: var(--bg); }
@keyframes toastIn {
from { opacity: 0; transform: translateX(20px); }
to { opacity: 1; transform: translateX(0); }
@@ -1143,12 +1158,25 @@
// ============ Toast-Helper ============
// Aufruf: toast("Item gelöscht", "success" | "error" | "info" | "warn")
// Optional: toast(text, type, duration_ms) — duration 0 = manuell wegklicken
function toast(text, type = "info", duration = 3500) {
// Optional: toast(text, type, duration_ms, action={label, onClick})
// — Toast wird mit action-Button gerendert, Click auf den Button ruft onClick
// auf und dismissed den Toast.
function toast(text, type = "info", duration = 3500, action = null) {
const c = document.getElementById('toastContainer');
const el = document.createElement('div');
el.className = 'toast ' + type;
const iconChar = { success: '✓', error: '✕', info: 'ⓘ', warn: '⚠' }[type] || 'ⓘ';
el.innerHTML = `<div class="toast-icon">${iconChar}</div><div class="toast-text"></div>`;
if (action) {
el.innerHTML = `<div class="toast-icon">${iconChar}</div>
<div class="toast-text"></div>
<button class="toast-action">${action.label}</button>`;
el.querySelector('.toast-action').addEventListener('click', (e) => {
e.stopPropagation();
try { action.onClick(); } finally { remove(); }
});
} else {
el.innerHTML = `<div class="toast-icon">${iconChar}</div><div class="toast-text"></div>`;
}
el.querySelector('.toast-text').textContent = text;
let removed = false;
const remove = () => {
@@ -1703,17 +1731,41 @@
const ok = await modalConfirm({
icon: 'warn',
title: 'Alle Items neu anordnen?',
body: 'Auto-Pack sortiert alle Widgets nach Größe in den 4×4-Grid. Bestehende manuelle Positionen gehen verloren.',
body: 'Auto-Pack sortiert alle Widgets nach Größe in den 4×4-Grid. Bestehende manuelle Positionen gehen verloren. Du kannst den Schritt 10 Sekunden lang rückgängig machen.',
confirmText: 'Neu anordnen',
});
if (!ok) return;
// BUG-06: Snapshot der aktuellen Items in sessionStorage für Undo.
// server gibt neue Items zurück; wir können das alte Layout wiederherstellen.
const snapshot = JSON.stringify(layoutItems);
const r = await fetch('/api/layout/pack', { method: 'POST' });
const j = await r.json();
if (!j.ok) { toast('Auto-Pack fehlgeschlagen: ' + (j.error || '?'), 'error', 5000); return; }
layoutItems = j.items;
renderGrid();
debouncedSave();
toast('Layout automatisch angeordnet', 'success');
// BUG-06: Actionable Toast mit Undo-Button
toast('Layout automatisch angeordnet', 'success', 10000, {
label: 'Rückgängig',
onClick: async () => {
try {
const restored = JSON.parse(snapshot);
// restore via normalen Save-Endpunkt
const r2 = await fetch('/api/layout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items: restored }),
});
const j2 = await r2.json();
if (!j2.ok) { toast('Undo fehlgeschlagen: ' + (j2.error || '?'), 'error', 5000); return; }
layoutItems = j2.items;
renderGrid();
toast('Layout wiederhergestellt', 'info', 3000);
} catch (err) {
toast('Undo Fehler: ' + err.message, 'error', 5000);
}
}
});
}
// ============ Plugin-Configs ============