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 @@
+
+
+
+
+
+
+
+
@@ -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();