From 7957da9231eb28c2b2e747e45ac2aeb5893a1831 Mon Sep 17 00:00:00 2001 From: Soulhackzlol <52952716+Soulhackzlol@users.noreply.github.com> Date: Wed, 10 Jun 2026 14:02:14 +0200 Subject: [PATCH 1/3] feat(overlay): one overlay list, copy-a-URL flow (v0.1.6) Reworks the Overlay tab so every overlay is just a file with a URL. - Presets are pre-installed as real overlays on first run: one list, no preset/editable/legacy split or filter. A new `overlays_seeded` config flag keeps deleted presets from reappearing. - Per overlay: Copy URL, Edit in Studio, Duplicate, Delete. "+ New overlay" replaces the standalone Open Studio button; Studio save is Save (overwrite) + Save as new. - Auto-hide is a quick per-overlay toggle that appends ?autohide=off to the copied URL (no re-bake); the per-state Studio control stays the baked default. - Restore default overlays (and the factory reset) wipe Studio overlays and re-seed, with a confirmation that says so; legacy hand-written files are kept. Server: list_overlays reports an autohide flag; POST /overlays/seeded and /overlays/reset; config reset scope=all wipes Studio overlays. Seeded overlays are per-install runtime artifacts (gitignored). --- .gitignore | 10 +++ CHANGELOG.md | 27 ++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- src/config.rs | 21 ++++- src/web.rs | 135 ++++++++++++++++++++++++++++-- web/index.html | 217 +++++++++++++++++++++++-------------------------- 7 files changed, 289 insertions(+), 125 deletions(-) diff --git a/.gitignore b/.gitignore index 18597d1..3ef53f9 100644 --- a/.gitignore +++ b/.gitignore @@ -23,5 +23,15 @@ Thumbs.db .DS_Store desktop.ini +# Studio + seeded overlays are generated per-install at runtime (the +# dashboard bakes the built-in presets on first run). Only the shipped +# hand-written overlays are tracked; everything else here is local. +overlays/*.html +!overlays/minimal.html +!overlays/corner.html +!overlays/strip.html +!overlays/custom-template.html +overlays/*/ + # Misc *.log diff --git a/CHANGELOG.md b/CHANGELOG.md index 75a5542..c22d080 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,33 @@ All notable changes will land here. Format loosely follows Nothing yet. +## [0.1.6] - Overlay flow: one list, copy a URL + +Reworks the Overlay tab around a single idea: **everything is just an +overlay with a URL you copy into OBS.** + +**One list, presets pre-installed.** The old preset / editable / legacy +split (and its type filter) is gone. On first run the built-in presets +are baked to real overlay files, so they show up in the same list as +anything you make - each with a working URL, no hidden "materialize on +copy" step. A one-time `overlays_seeded` flag keeps deleted presets from +reappearing. + +**Three real actions.** Per overlay: **Copy URL**, **Edit in Studio**, +**Duplicate**, **Delete** (hand-written legacy files: Copy URL + Delete). +The standalone "Open Studio" button is replaced by **+ New overlay**. In +the Studio, save is now two clear buttons: **Save** (overwrite) and +**Save as new**. + +**Auto-hide as a quick toggle.** A per-overlay **Auto-hide when live** +switch; turning it off appends `?autohide=off` to the copied URL so the +overlay stays up, no re-bake. The per-state control in the Studio remains +the baked default. + +**Restore defaults.** A button (and the factory reset) wipes your Studio +overlays and reinstalls the built-in set, with a confirmation that says +so plainly. Legacy hand-written files are left untouched. + ## [0.1.5] - Overlay Studio (experimental) A no-code visual editor for building OBS browser-source overlays that diff --git a/Cargo.lock b/Cargo.lock index 24d292f..482bffb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -218,7 +218,7 @@ dependencies = [ [[package]] name = "instantclone" -version = "0.1.5" +version = "0.1.6" dependencies = [ "bytes", "flate2", diff --git a/Cargo.toml b/Cargo.toml index 13a576f..16f486d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "instantclone" -version = "0.1.5" +version = "0.1.6" edition = "2021" authors = ["s1moscs"] license = "GPL-3.0-only" diff --git a/src/config.rs b/src/config.rs index 1e0c1ea..68cccde 100644 --- a/src/config.rs +++ b/src/config.rs @@ -70,6 +70,12 @@ pub struct Settings { /// streamer hits Arm with a non-zero value) so "last used" sticks /// across sessions without a separate UI knob to maintain it. pub auto_arm_delay_ms: u32, + /// State: whether the built-in preset overlays have been seeded to the + /// overlays folder as real files yet. Set true after the dashboard bakes + /// them on first run, so deleting a seeded overlay doesn't bring it back. + /// Reset to false (and the overlays wiped) by "Restore default overlays" + /// and the factory reset, which re-seeds on the next dashboard load. + pub overlays_seeded: bool, } #[derive(Debug, Clone)] @@ -252,6 +258,9 @@ impl Settings { // auto_arm_on_connect without otherwise customising lands // on a familiar number. auto_arm_delay_ms: 15_000, + // Fresh install hasn't seeded the preset overlays yet; the + // dashboard does it on first load, then flips this true. + overlays_seeded: false, } } @@ -448,6 +457,9 @@ impl Settings { if self.auto_arm_delay_ms != 15_000 { writeln!(f, "auto_arm_delay_ms={}", self.auto_arm_delay_ms)?; } + if self.overlays_seeded { + writeln!(f, "overlays_seeded=true")?; + } for (i, p) in self.profiles.iter().enumerate() { writeln!(f, "profile.{}.name={}", i, p.name)?; writeln!(f, "profile.{}.delay_ms={}", i, p.delay_ms)?; @@ -524,6 +536,7 @@ impl Settings { "tracing_enabled" => self.tracing_enabled = value.parse().unwrap_or(false), "auto_arm_on_connect" => self.auto_arm_on_connect = value == "true", "auto_activate_when_ready" => self.auto_activate_when_ready = value == "true", + "overlays_seeded" => self.overlays_seeded = value == "true", "auto_arm_delay_ms" => { if let Ok(v) = value.parse() { self.auto_arm_delay_ms = v; @@ -700,7 +713,7 @@ impl Settings { } dests.push(']'); format!( - r#"{{"configured":{c},"ingest_port":{ip},"ingest_bind_all":{iba},"web_port":{wp},"web_bind_all":{wba},"buffer_mb":{bm},"buffer_path":{bp},"target_delay_ms":{td},"initial_delay_ms":{id},"obs_url":{ou},"discord_webhook_url":{dw},"webhook_set":{ws},"overlays_dir":{ov},"tracing_enabled":{te},"auto_arm_on_connect":{aaoc},"auto_activate_when_ready":{aawr},"auto_arm_delay_ms":{aadm},"destinations":{dests}}}"#, + r#"{{"configured":{c},"ingest_port":{ip},"ingest_bind_all":{iba},"web_port":{wp},"web_bind_all":{wba},"buffer_mb":{bm},"buffer_path":{bp},"target_delay_ms":{td},"initial_delay_ms":{id},"obs_url":{ou},"discord_webhook_url":{dw},"webhook_set":{ws},"overlays_dir":{ov},"tracing_enabled":{te},"auto_arm_on_connect":{aaoc},"auto_activate_when_ready":{aawr},"auto_arm_delay_ms":{aadm},"overlays_seeded":{os},"destinations":{dests}}}"#, c = self.configured, ip = self.ingest_port, iba = self.ingest_bind_all, @@ -718,6 +731,7 @@ impl Settings { aaoc = self.auto_arm_on_connect, aawr = self.auto_activate_when_ready, aadm = self.auto_arm_delay_ms, + os = self.overlays_seeded, dests = dests, ) } @@ -1286,6 +1300,7 @@ mod tests { s.auto_arm_on_connect = true; s.auto_activate_when_ready = true; s.auto_arm_delay_ms = 30_000; + s.overlays_seeded = true; // Need a destination so save() doesn't bail on the // configured-but-empty path. s.destinations.push(Destination { @@ -1315,6 +1330,10 @@ mod tests { loaded.auto_arm_delay_ms, 30_000, "auto_arm_delay_ms must survive round-trip" ); + assert!( + loaded.overlays_seeded, + "overlays_seeded must survive round-trip" + ); let _ = std::fs::remove_file(&path); } diff --git a/src/web.rs b/src/web.rs index e5ca92e..24fe5a9 100644 --- a/src/web.rs +++ b/src/web.rs @@ -264,8 +264,17 @@ async fn route( // Overlay Studio writes: POST /overlays/ saves a baked overlay, // POST /overlays//delete removes it. The slug is restricted to a // safe charset (no separators) so no path can escape overlays_dir. + // POST /overlays/seeded marks the built-in presets as installed; POST + // /overlays/reset wipes the Studio overlays and clears that flag so the + // dashboard re-seeds the defaults on its next load. if method == "POST" && bare_path.starts_with("/overlays/") { let rest = &bare_path["/overlays/".len()..]; + if rest == "seeded" { + return overlays_mark_seeded(settings, cfg_path); + } + if rest == "reset" { + return overlays_reset(settings, cfg_path); + } if let Some(slug) = rest.strip_suffix("/delete") { return overlay_delete(slug, settings); } @@ -1483,6 +1492,10 @@ async fn post_config_reset( // event-log tab match the "fresh install" feel. ctrl.arm_delay(0); ctrl.clear_logs(); + // Wipe the Studio overlays (from the still-current dir, before the + // send below swaps in defaults). The seeded flag is back to false in + // `next`, so the dashboard re-bakes the presets on its next load. + wipe_studio_overlays(&settings.borrow().overlays_dir); } ctrl.log(format!("config reset (scope={})", scope)); reconcile_obs_vod_files(&next, ctrl); @@ -2025,7 +2038,7 @@ fn generate_dest_id() -> String { /// ones are still listed and usable as browser sources. fn list_overlays(settings: &Arc>) -> String { let dir = settings.borrow().overlays_dir.clone(); - let mut items: Vec<(String, String, bool)> = Vec::new(); + let mut items: Vec<(String, String, bool, bool)> = Vec::new(); if let Ok(entries) = std::fs::read_dir(&dir) { for e in entries.flatten() { let fname = match e.file_name().into_string() { @@ -2043,21 +2056,25 @@ fn list_overlays(settings: &Arc>) -> String { // detect the ic-doc marker is cheap and keeps the list honest. let content = std::fs::read_to_string(e.path()).unwrap_or_default(); let studio = content.contains("S\ +
", + ) + .unwrap(); + // A hand-written legacy file: no marker, no auto-hide. + std::fs::write( + dir.join("legacy.html"), + "Lhi", + ) + .unwrap(); + + // The list reports studio + autohide per file. + let settings = settings_with_overlays_dir(&dir); + let listed = list_overlays(&settings); + assert!(listed.contains(r#""slug":"studio","name":"S","studio":true,"autohide":true"#)); + assert!(listed.contains(r#""slug":"legacy","name":"L","studio":false,"autohide":false"#)); + + // Restore-defaults wipes only the Studio (ic-doc) file; legacy stays. + wipe_studio_overlays(&dir); + assert!(!dir.join("studio.html").exists()); + assert!(dir.join("legacy.html").is_file()); + + let _ = std::fs::remove_dir_all(&dir); + } + #[test] fn extract_title_pulls_first_title() { assert_eq!( diff --git a/web/index.html b/web/index.html index 4ce6ce9..02d765f 100644 --- a/web/index.html +++ b/web/index.html @@ -629,6 +629,7 @@ .ovg-card-tag.editable{background:color-mix(in oklch,var(--accent) 16%,transparent);color:var(--accent)} .ovg-card-tag.preset{background:color-mix(in oklch,var(--live) 15%,transparent);color:var(--live)} .ovg-card-tag.legacy{background:rgba(255,255,255,.06);color:var(--fg-4)} +.ovg-empty{padding:26px 16px;text-align:center;color:var(--fg-4);font-size:13px;line-height:1.6} /* ── Overlay Studio ─────────────────────────────── */ .st-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap} @@ -1494,9 +1495,12 @@

New destination

On-stream overlay
-
A browser-source widget that shows your delay. Pick one, drop the URL into OBS - or open the Studio to design your own.
+
Browser-source widgets that show your delay. Pick one, copy its URL into OBS - or edit any in the Studio.
+
+
+ +
-
@@ -1515,7 +1519,6 @@

New destination

-
@@ -1556,12 +1559,12 @@

New destination

- + +
@@ -1627,7 +1634,10 @@

New destination

Behavior, network, buffer, notifications, diagnostics.
- +