From 281f0ddbfeddb882fccfd5282ce4b95c338b3f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=27=C3=A9lectron=20rare?= <108685187+electron-rare@users.noreply.github.com> Date: Sun, 21 Jun 2026 09:46:35 +0200 Subject: [PATCH] feat(ui): routines tab editor --- tower/merlin_ui.html | 122 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 119 insertions(+), 3 deletions(-) diff --git a/tower/merlin_ui.html b/tower/merlin_ui.html index 2b9dc9c..eef9856 100644 --- a/tower/merlin_ui.html +++ b/tower/merlin_ui.html @@ -134,6 +134,7 @@ + @@ -185,6 +186,14 @@ + + +
+
chargement…
+
+ +
+
@@ -246,14 +255,17 @@ function humanSize(n) { } // ---------- tabs ---------- -const TABS = ["podcasts", "fichiers", "box"]; +const TABS = ["podcasts", "fichiers", "box", "routines", "transfers"]; function showTab(name) { TABS.forEach(function (t) { - $("panel-" + t).classList.toggle("active", t === name); - $("tab-" + t).classList.toggle("active", t === name); + const panel = $("panel-" + t); + const tab = $("tab-" + t); + if (panel) panel.classList.toggle("active", t === name); + if (tab) tab.classList.toggle("active", t === name); }); if (name === "fichiers") loadFiles(); if (name === "box") loadBox(); + if (name === "routines") loadRoutines(); } // ---------- Podcasts ---------- @@ -524,6 +536,110 @@ async function pushNow() { } } +// ---------- Routines ---------- +const EMOJI_PALETTE = ["☀️","🌙","👕","🥣","🦷","🎒","🧸","🛁","👖","🧦","🪥","📖","🚽","🍽️","💤","🧼","👟","🧥"]; +let ROUTINES = []; // editable in-memory copy of /api/routines + +function routineCard(r, ri) { + const card = document.createElement("div"); + card.className = "card"; + card.innerHTML = '

' + esc(r.emoji || "") + ' ' + esc(r.title || r.id) + '

' + + '
' + + '
'; + const host = card.querySelector(".steps-host"); + (r.steps || []).forEach(function (st, si) { host.appendChild(stepRow(ri, si)); }); + card.querySelector(".add-step").onclick = function () { + ROUTINES[ri].steps.push({ text: "", emoji: "🧸" }); + loadRoutines(true); + }; + return card; +} + +function stepRow(ri, si) { + const st = ROUTINES[ri].steps[si]; + const li = document.createElement("div"); + li.className = "row"; + li.style.borderTop = "1px solid var(--line)"; + li.style.padding = "8px 0"; + // emoji select + let opts = ""; + const inPalette = EMOJI_PALETTE.indexOf(st.emoji) !== -1; + EMOJI_PALETTE.forEach(function (e) { + opts += ''; + }); + if (!inPalette && st.emoji) opts += ''; + li.innerHTML = + '' + + '' + + '' + + '' + + ''; + li.querySelector(".st-text").oninput = function (e) { ROUTINES[ri].steps[si].text = e.target.value; }; + li.querySelector(".st-emoji").onchange = function (e) { ROUTINES[ri].steps[si].emoji = e.target.value; }; + li.querySelector(".st-up").onclick = function () { moveStep(ri, si, -1); }; + li.querySelector(".st-down").onclick = function () { moveStep(ri, si, 1); }; + li.querySelector(".st-del").onclick = function () { ROUTINES[ri].steps.splice(si, 1); loadRoutines(true); }; + return li; +} + +function moveStep(ri, si, d) { + const s = ROUTINES[ri].steps; + const j = si + d; + if (j < 0 || j >= s.length) return; + const t = s[si]; s[si] = s[j]; s[j] = t; + loadRoutines(true); +} + +async function loadRoutines(fromMemory) { + const box = $("routines"); + if (!fromMemory) { + box.innerHTML = '
chargement…
'; + try { + clearBanner(); + const data = await api("/api/routines"); + ROUTINES = (data && data.routines) || []; + } catch (e) { + box.innerHTML = '
Impossible de charger les routines.
'; + showBanner("Routines: " + e.message); + return; + } + } + box.innerHTML = ""; + ROUTINES.forEach(function (r, ri) { box.appendChild(routineCard(r, ri)); }); +} + +async function saveRoutines() { + const btn = $("routines-save"); + btn.disabled = true; + const old = btn.textContent; + btn.textContent = "Enregistrement…"; + try { + await api("/api/routines", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ routines: ROUTINES }), + }); + toast("Routines enregistrées — génération en cours…"); + // give the server a moment to regenerate assets, then push + setTimeout(async function () { + try { + const r = await api("/api/routines/push", { method: "POST" }); + if (r && r.started) toast("Envoi des routines démarré (" + (r.delta || 0) + " fichier(s))"); + else toast("Routines prêtes (rien à pousser ou box absente)"); + } catch (e) { + if (e.status === 409) toast("La box ne s'est jamais annoncée", true); + else { toast("Erreur push: " + e.message, true); showBanner("Push routines: " + e.message); } + } + }, 6000); + } catch (e) { + toast("Erreur: " + e.message, true); + showBanner("Enregistrer routines: " + e.message); + } finally { + btn.disabled = false; + btn.textContent = old; + } +} + // ---------- init ---------- window.addEventListener("DOMContentLoaded", function () { loadFeeds();