From e4cfd3528276fcf12cda8c7214f6a08e85fe11c2 Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Tue, 10 Mar 2026 23:35:52 +0100 Subject: [PATCH] [feat](trx-frontend-http): per-entry interleave time in TimeSpan scheduler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each ScheduleEntry can now carry its own interleave_min, overriding the config-level default for that slot in the cycle. The cycle length is the sum of all active entries' effective durations (weighted), so entries with longer individual interleave times occupy proportionally more time. UI: "Interleave (min, optional)" input in the add-entry form; value shown in the entries table (displays "—" when using the config default). Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Stan Grams --- .../trx-frontend-http/assets/web/index.html | 5 +++- .../assets/web/plugins/scheduler.js | 9 +++++- .../trx-frontend-http/src/scheduler.rs | 28 +++++++++++++++---- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html index d7a1adc..b1f84e4 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html @@ -724,7 +724,7 @@ - +
StartEndBookmarkLabel
StartEndBookmarkLabelInterleave (min)
@@ -741,6 +741,9 @@ + diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/scheduler.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/scheduler.js index bea4949..cebe798 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/scheduler.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/scheduler.js @@ -277,11 +277,13 @@ : []; entries.forEach(function (entry, idx) { const tr = document.createElement("tr"); + const il = entry.interleave_min ? String(entry.interleave_min) + " min" : "—"; tr.innerHTML = '' + minToHHMM(entry.start_min) + '' + '' + minToHHMM(entry.end_min) + '' + '' + bmName(entry.bookmark_id) + '' + '' + escHtml(entry.label || "") + '' + + '' + il + '' + ''; tbody.appendChild(tr); }); @@ -330,12 +332,15 @@ const endEl = document.getElementById("scheduler-ts-end"); const bmEl = document.getElementById("scheduler-ts-bookmark"); const labelEl = document.getElementById("scheduler-ts-label"); + const ilEl = document.getElementById("scheduler-ts-entry-interleave"); if (!startEl || !endEl || !bmEl) return; const startMin = hhmmToMin(startEl.value); const endMin = hhmmToMin(endEl.value); const bmId = bmEl.value; const label = labelEl ? labelEl.value.trim() : ""; + const ilVal = ilEl ? parseInt(ilEl.value, 10) : NaN; + const entryInterleave = !isNaN(ilVal) && ilVal > 0 ? ilVal : null; if (!bmId) { alert("Please select a bookmark."); @@ -354,12 +359,14 @@ end_min: endMin, bookmark_id: bmId, label: label || null, + interleave_min: entryInterleave, }); startEl.value = ""; endEl.value = ""; - bmEl.value = ""; // reset select to first option + bmEl.value = ""; if (labelEl) labelEl.value = ""; + if (ilEl) ilEl.value = ""; renderTimespanEntries(); } diff --git a/src/trx-client/trx-frontend/trx-frontend-http/src/scheduler.rs b/src/trx-client/trx-frontend/trx-frontend-http/src/scheduler.rs index a64f6b9..d89790c 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/src/scheduler.rs +++ b/src/trx-client/trx-frontend/trx-frontend-http/src/scheduler.rs @@ -68,6 +68,11 @@ pub struct ScheduleEntry { pub bookmark_id: String, #[serde(default)] pub label: Option, + /// Per-entry interleave duration in minutes. Overrides the config-level + /// `interleave_min` when set. Allows each entry to occupy a differently + /// sized slice of the interleave cycle. + #[serde(default)] + pub interleave_min: Option, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] @@ -284,7 +289,7 @@ fn entry_is_active(entry: &ScheduleEntry, now_min: f64) -> bool { fn timespan_bookmark_id( entries: &[ScheduleEntry], now_min: f64, - interleave_min: Option, + default_interleave: Option, ) -> Option { let active: Vec<&ScheduleEntry> = entries .iter() @@ -295,11 +300,24 @@ fn timespan_bookmark_id( return None; } - // With interleaving and more than one active entry, pick by time slot. + // With interleaving and more than one active entry, use a weighted cycle. + // Each entry's effective duration is its own interleave_min, falling back + // to the config-level default. The cycle length is the sum of all durations. if active.len() > 1 { - if let Some(step) = interleave_min.filter(|&s| s > 0) { - let slot = (now_min as u64 / step as u64) as usize % active.len(); - return Some(active[slot].bookmark_id.clone()); + let durations: Vec = active + .iter() + .map(|e| e.interleave_min.or(default_interleave).unwrap_or(0)) + .collect(); + let cycle: u32 = durations.iter().sum(); + if cycle > 0 { + let pos = (now_min as u64) % (cycle as u64); + let mut cum = 0u64; + for (entry, &dur) in active.iter().zip(durations.iter()) { + cum += dur as u64; + if pos < cum { + return Some(entry.bookmark_id.clone()); + } + } } }