diff --git a/crates/aster-webui/src/main.rs b/crates/aster-webui/src/main.rs index 0956491..12069cf 100644 --- a/crates/aster-webui/src/main.rs +++ b/crates/aster-webui/src/main.rs @@ -1806,6 +1806,7 @@ const INDEX_HTML: &str = r##" setup: { switch_time: "10", custom_panel: false, specs_enabled: false, memes_enabled: false }, display: {}, system: {}, + dirty: false, }; const el = { gallery: document.getElementById("gallery"), @@ -1859,6 +1860,7 @@ const INDEX_HTML: &str = r##" const [item] = next.splice(index, 1); next.splice(target, 0, item); state.activeImages = next; + markDirty(); render(); } @@ -1868,9 +1870,25 @@ const INDEX_HTML: &str = r##" } else { state.activeImages = [...state.activeImages, name]; } + markDirty(); render(); } + function markDirty() { + state.dirty = true; + updateDirtyUi(); + } + + function clearDirty() { + state.dirty = false; + updateDirtyUi(); + } + + function updateDirtyUi() { + el.apply.textContent = state.dirty ? "Apply Rotation *" : "Apply Rotation"; + el.imageCountValue.textContent = `${state.activeImages.length}${state.dirty ? " *" : ""}`; + } + async function request(url, options = {}) { const res = await fetch(url, options); const data = await res.json().catch(() => ({})); @@ -1878,24 +1896,39 @@ const INDEX_HTML: &str = r##" return data; } - async function load() { + async function load(options = {}) { + const force = !!options.force; + const preserveDraft = !!options.preserveDraft; + if (state.dirty && !force) return; + + const draftActiveImages = [...state.activeImages]; const data = await request("/api/state"); state.images = data.images; - state.activeImages = data.active_images; - state.setup = data.setup; + state.activeImages = preserveDraft + ? draftActiveImages.filter((name) => data.images.some((image) => image.name === name)) + : data.active_images; + state.setup = preserveDraft + ? { + ...data.setup, + switch_time: el.switchTime.value || data.setup.switch_time || "10", + specs_enabled: el.specsEnabled.checked, + memes_enabled: el.memesEnabled.checked, + } + : data.setup; state.display = data.display || {}; state.system = data.system || {}; - el.switchTime.value = data.setup.switch_time || "10"; - el.specsEnabled.checked = !!data.setup.specs_enabled; - el.memesEnabled.checked = !!data.setup.memes_enabled; + if (!preserveDraft) { + el.switchTime.value = data.setup.switch_time || "10"; + el.specsEnabled.checked = !!data.setup.specs_enabled; + el.memesEnabled.checked = !!data.setup.memes_enabled; + } const enabledModes = [ - data.setup.specs_enabled ? "Specs" : "", - data.setup.memes_enabled ? "Memes" : "", + state.setup.specs_enabled ? "Specs" : "", + state.setup.memes_enabled ? "Memes" : "", ].filter(Boolean); - el.customPanelValue.textContent = data.setup.custom_panel + el.customPanelValue.textContent = state.setup.custom_panel ? (enabledModes.join(" + ") || "Active") : "Disabled"; - el.imageCountValue.textContent = String(data.active_images.length); el.displayStatusValue.textContent = state.display.connected ? `Connected (${state.display.mode || "native"})` : (state.display.native_enabled ? "Disconnected" : "Disabled"); @@ -1915,6 +1948,7 @@ const INDEX_HTML: &str = r##" el.uptimeValue.textContent = `Uptime ${state.system.uptime || "-"}`; el.tempCpuValue.textContent = `CPU ${state.system.temperature_cpu || "-"}`; el.tempGpuValue.textContent = `GPU ${state.system.temperature_gpu || "-"}`; + updateDirtyUi(); render(); } @@ -1926,7 +1960,7 @@ const INDEX_HTML: &str = r##" await request("/api/upload", { method: "POST", body: form }); event.target.value = ""; toast("Upload complete"); - await load(); + await load({ force: true, preserveDraft: state.dirty }); } async function apply() { @@ -1940,14 +1974,16 @@ const INDEX_HTML: &str = r##" memes_enabled: el.memesEnabled.checked, }), }); + clearDirty(); toast("Rotation updated"); - await load(); + await load({ force: true }); } async function disablePanels() { await request("/api/panels/disable", { method: "POST" }); + clearDirty(); toast("Custom panels disabled"); - await load(); + await load({ force: true }); } async function removeImage(name) { @@ -1956,8 +1992,10 @@ const INDEX_HTML: &str = r##" headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name }), }); + state.activeImages = state.activeImages.filter((item) => item !== name); + clearDirty(); toast("Image deleted"); - await load(); + await load({ force: true }); } function render() { @@ -2046,9 +2084,15 @@ const INDEX_HTML: &str = r##" el.upload.addEventListener("change", (event) => uploadFile(event).catch((err) => toast(err.message))); el.apply.addEventListener("click", () => apply().catch((err) => toast(err.message))); el.disable.addEventListener("click", () => disablePanels().catch((err) => toast(err.message))); - el.refresh.addEventListener("click", () => load().catch((err) => toast(err.message))); + el.refresh.addEventListener("click", () => { + clearDirty(); + load({ force: true }).catch((err) => toast(err.message)); + }); + el.switchTime.addEventListener("change", markDirty); + el.specsEnabled.addEventListener("change", markDirty); + el.memesEnabled.addEventListener("change", markDirty); - load().catch((err) => toast(err.message)); + load({ force: true }).catch((err) => toast(err.message)); setInterval(() => load().catch(() => {}), 5000);