Frontend: Kacheln per Drag-and-drop umsortieren (Reihenfolge in localStorage)

This commit is contained in:
Joachim Hummel committed 2026-10-18 13:13:53 +02:00
1 parent a7a6f17d0e
commit 0b2837f0c3
1 file changed
+52 -1
+52 -1
View File
@@ -57,6 +57,10 @@ main{padding:22px;max-width:1800px;margin:0 auto}
.btn.ok:hover{box-shadow:0 4px 16px rgba(34,197,94,.45)}
.btn.alt{background:#222a3a;box-shadow:none;color:var(--fg);border:1px solid var(--line)}
.btn.alt:hover{border-color:var(--accent);color:var(--accent)}
.grid .cap{cursor:grab}
.grid .cap .btn{cursor:pointer}
.tile.dragging{opacity:.4}
.tile.dragover{border-color:var(--accent)}
.focus .tile.big .view{aspect-ratio:auto;height:min(75vh,56.25vw)}
.strip{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:16px;margin-top:20px}
.strip .cap{padding:8px 12px;font-size:13px}
@@ -232,6 +236,52 @@ function stopTile(o) {
function reloadTile(o) { stopTile(o); setTimeout(() => startTile(o), 300); }
const liveTiles = () => Object.values(tiles).filter(o => o.mode === "live");
const ORDER_KEY = "webcam-order";
function savedOrder() {
try { return JSON.parse(localStorage.getItem(ORDER_KEY)) || []; } catch (e) { return []; }
}
function sortBySaved(names) {
const saved = savedOrder();
const rank = n => { const i = saved.indexOf(n); return i < 0 ? saved.length : i; };
return names.map((n, i) => [n, i]).sort((a, b) => rank(a[0]) - rank(b[0]) || a[1] - b[1]).map(x => x[0]);
}
// Kacheln per Drag-and-drop an der Beschriftungsleiste umsortieren
function enableReorder(grid) {
let dragged = null;
grid.querySelectorAll(".tile").forEach(t => {
const cap = t.querySelector(".cap");
cap.draggable = true;
cap.addEventListener("dragstart", e => {
dragged = t;
t.classList.add("dragging");
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("text/plain", t.querySelector(".name").textContent);
e.dataTransfer.setDragImage(t, 20, 20);
});
cap.addEventListener("dragend", () => {
t.classList.remove("dragging");
grid.querySelectorAll(".dragover").forEach(x => x.classList.remove("dragover"));
dragged = null;
});
t.addEventListener("dragover", e => {
if (!dragged || dragged === t) return;
e.preventDefault();
t.classList.add("dragover");
});
t.addEventListener("dragleave", () => t.classList.remove("dragover"));
t.addEventListener("drop", e => {
e.preventDefault();
t.classList.remove("dragover");
if (!dragged || dragged === t) return;
const kids = [...grid.children];
grid.insertBefore(dragged, kids.indexOf(dragged) < kids.indexOf(t) ? t.nextSibling : t);
try {
localStorage.setItem(ORDER_KEY, JSON.stringify([...grid.children].map(c => c.querySelector(".name").textContent)));
} catch (err) {}
});
});
}
function build(cams) {
const names = cams.map(c => c.name);
const setupOf = n => cams.find(c => c.name === n).setup;
@@ -247,8 +297,9 @@ function build(cams) {
if (strip.children.length) app.append(strip);
} else {
const grid = el("div", "grid");
names.forEach(n => grid.append(makeTile(n, "live", setupOf(n))));
sortBySaved(names).forEach(n => grid.append(makeTile(n, "live", setupOf(n))));
app.append(grid);
enableReorder(grid);
}
}