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 baf8a2b..ebe42a2 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 @@ -344,7 +344,17 @@
Scheduler is controlling the rig.
-
Interleaving: --
+ @@ -986,6 +996,10 @@
+ +
+
No activity yet.
+
+ + + + + + +
+
+ + +
+ +
+
+ Entry details + + + + + +
StartEndCenter freqPrimary bookmarkExtra channelsLabelInterleave (min)
+
@@ -1111,59 +1165,21 @@ - -
- -
-
Last Activity
-
No activity yet.
-
'; }); html += ""; @@ -328,10 +318,12 @@ } function wireBackgroundDecodeEvents() { - const addBtn = document.getElementById("background-decode-bookmark-add"); - if (addBtn && !addBtn._wired) { - addBtn._wired = true; - addBtn.addEventListener("click", addBookmark); + const filterInput = document.getElementById("bgd-bookmark-filter"); + if (filterInput && !filterInput._wired) { + filterInput._wired = true; + filterInput.addEventListener("input", function () { + renderBookmarkChecklist(filterInput.value); + }); } const saveBtn = document.getElementById("background-decode-save-btn"); 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 fe56d43..a564d5e 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 @@ -275,17 +275,36 @@ }; } - function schedulerInterleaveSummary(config) { - const state = schedulerInterleaveState(config); - if (state.activeEntries.length <= 1 || !(state.cycleMin > 0)) return "Interleaving: off"; - const activeName = schedulerEntryDisplayName(state.activeEntries[state.currentIndex]); - return "Interleaving: " + activeName + " · next switch in " + state.remainingSec + "s (" + state.cycleMin + " min cycle)"; - } - function renderSchedulerInterleaveStatus() { - const el = document.getElementById("scheduler-cycle-status"); - if (!el) return; - el.textContent = schedulerInterleaveSummary(currentConfig); + const wrap = document.getElementById("scheduler-cycle-status"); + if (!wrap) return; + + const state = schedulerInterleaveState(currentConfig); + const isActive = state.activeEntries.length > 1 && state.cycleMin > 0; + + wrap.style.display = isActive ? "" : "none"; + + if (isActive) { + var activeName = schedulerEntryDisplayName(state.activeEntries[state.currentIndex]); + var totalSlotSec = state.cycleMin > 0 + ? (state.cycleMin * 60) / state.activeEntries.length + : 0; + var elapsedPct = totalSlotSec > 0 + ? Math.min(100, Math.max(0, ((totalSlotSec - state.remainingSec) / totalSlotSec) * 100)) + : 0; + + var ringFill = document.getElementById("interleave-ring-fill"); + if (ringFill) ringFill.setAttribute("stroke-dashoffset", String(100 - elapsedPct)); + + var nameEl = document.getElementById("interleave-active-name"); + if (nameEl) nameEl.textContent = activeName; + + var countdownEl = document.getElementById("interleave-countdown"); + if (countdownEl) countdownEl.textContent = "next in " + state.remainingSec + "s · " + state.cycleMin + "m cycle"; + } + + // Also update the timeline needle if visible + renderTimelineNeedle(); renderSchedulerStepControls(); } @@ -459,7 +478,7 @@ } // ------------------------------------------------------------------------- - // Entry form (modal — mirrors bookmark add/edit modal) + // Entry form (inline card below Add Entry button) // ------------------------------------------------------------------------- function schOpenEntryForm(entry, idx) { schEntryEditIdx = (idx != null) ? idx : null; @@ -486,7 +505,7 @@ const wrap = document.getElementById("sch-entry-form-wrap"); if (wrap) { - wrap.style.display = "flex"; + wrap.style.display = "block"; if (startEl) startEl.focus(); } } @@ -552,6 +571,100 @@ renderTimespanEntries(); } + // ------------------------------------------------------------------------- + // 24h Timeline Bar + // ------------------------------------------------------------------------- + var TIMELINE_COLORS = ["#38bdf8", "#f59e0b", "#a78bfa", "#34d399", "#fb7185", "#60a5fa"]; + + function renderTimeline() { + var container = document.getElementById("scheduler-ts-timeline"); + if (!container) return; + var entries = currentConfig && Array.isArray(currentConfig.entries) ? currentConfig.entries : []; + if (entries.length === 0) { + container.innerHTML = ""; + return; + } + + var W = 1000; + var H = 62; + var BAR_Y = 6; + var BAR_H = 30; + var TICK_Y = BAR_Y + BAR_H + 2; + + var svg = ''; + + // Background bar + svg += ''; + + // Entry segments + entries.forEach(function (entry, idx) { + var start = Number(entry.start_min); + var end = Number(entry.end_min); + if (!Number.isFinite(start) || !Number.isFinite(end)) return; + var color = TIMELINE_COLORS[idx % TIMELINE_COLORS.length]; + + if (start === end) { + // All-day entry + svg += ''; + } else if (start < end) { + var x = (start / 1440) * W; + var w = ((end - start) / 1440) * W; + svg += ''; + } else { + // Wrap-around: two segments + var x1 = (start / 1440) * W; + var w1 = W - x1; + svg += ''; + var w2 = (end / 1440) * W; + svg += ''; + } + }); + + // Tick marks every 3 hours + for (var h = 0; h <= 24; h += 3) { + var tx = (h / 24) * W; + svg += ''; + if (h < 24) { + svg += '' + String(h).padStart(2, "0") + ''; + } + } + + // Current time needle + svg += '' + timelineNeedleSvg() + ''; + + svg += ''; + container.innerHTML = svg; + + // Wire click events on segments + container.querySelectorAll(".sch-timeline-seg").forEach(function (seg) { + seg.addEventListener("click", function () { + var i = parseInt(seg.getAttribute("data-idx"), 10); + var entry = currentConfig && currentConfig.entries ? currentConfig.entries[i] : null; + if (entry) schOpenEntryForm(entry, i); + }); + }); + } + + function timelineNeedleSvg() { + var info = schedulerUtcMinuteInfo(); + var nowMin = info.minuteOfDay + (info.secondOfMinute / 60); + var x = (nowMin / 1440) * 1000; + return '' + + ''; + } + + function renderTimelineNeedle() { + var g = document.getElementById("sch-timeline-needle-g"); + if (g) g.innerHTML = timelineNeedleSvg(); + } + // ------------------------------------------------------------------------- // TimeSpan entries table // ------------------------------------------------------------------------- @@ -598,6 +711,7 @@ removeEntry(parseInt(btn.dataset.idx, 10)); }); }); + renderTimeline(); } function bmName(id) { diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css index e6b7ad1..e676c7b 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css @@ -3814,8 +3814,7 @@ button:focus-visible, input:focus-visible, select:focus-visible { #sch-entry-form .bm-label { gap: 0.35rem; } -#bm-form-wrap, -#sch-entry-form-wrap { +#bm-form-wrap { position: fixed; inset: 0; z-index: 120; @@ -4539,6 +4538,187 @@ button:focus-visible, input:focus-visible, select:focus-visible { .bgd-status-state[data-state="no_supported_decoders"] { color: var(--accent-red); } +/* ── "Now Playing" status card (top of scheduler & bgd panels) ──── */ +.now-playing-card { + border-left: 3px solid var(--accent-green); + padding: 0.75rem 1rem; + margin-bottom: 1.25rem; + background: var(--bg-secondary); + border-radius: 0 0.5rem 0.5rem 0; + font-size: 0.9rem; +} +/* ── 24h Timeline Bar ─────────────────────────────────────────────── */ +.sch-timeline-wrap { + margin-bottom: 1rem; + overflow: hidden; + border-radius: 0.4rem; + border: 1px solid var(--border-light); + background: var(--bg-secondary); +} +.sch-timeline-wrap svg { + display: block; + width: 100%; + height: 62px; +} +.sch-timeline-seg { + cursor: pointer; + opacity: 0.82; + transition: opacity 0.12s; +} +.sch-timeline-seg:hover { + opacity: 1; +} +.sch-timeline-tick-label { + fill: var(--text-muted); + font-size: 9px; + font-family: inherit; +} +.sch-timeline-needle { + stroke: var(--accent-red); + stroke-width: 1.5; +} +.sch-timeline-needle-head { + fill: var(--accent-red); +} +/* Details toggle for entry table */ +.sch-ts-details { + margin-top: 0.5rem; +} +.sch-ts-details summary { + cursor: pointer; + font-size: 0.82rem; + color: var(--text-muted); + font-weight: 600; + padding: 0.3rem 0; + user-select: none; +} +/* ── Inline Entry Editor (replaces modal overlay) ─────────────────── */ +#sch-entry-form-wrap { + position: static; + z-index: auto; + display: none; + align-items: stretch; + justify-content: stretch; + background: none; + margin-bottom: 1rem; +} +#sch-entry-form-wrap .bm-form { + max-width: 100%; + width: 100%; + border: 1px solid var(--border-light); + border-radius: 0.5rem; + background: var(--bg-secondary); +} +/* ── Interleave Progress Ring ─────────────────────────────────────── */ +.interleave-ring-wrap { + display: flex; + align-items: center; + gap: 0.5rem; +} +.interleave-ring { + width: 32px; + height: 32px; + transform: rotate(-90deg); + flex-shrink: 0; +} +.interleave-ring-bg { + fill: none; + stroke: var(--border-light); + stroke-width: 3; +} +.interleave-ring-fill { + fill: none; + stroke: var(--accent-green); + stroke-width: 3; + stroke-linecap: round; + transition: stroke-dashoffset 0.9s linear; +} +.interleave-ring-text { + display: flex; + flex-direction: column; + gap: 0; + min-width: 0; +} +.interleave-ring-label { + font-size: 0.78rem; + font-weight: 600; + color: var(--text); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 14rem; +} +.interleave-ring-sub { + font-size: 0.7rem; + color: var(--text-muted); + opacity: 0.88; +} +/* ── Background Decode Checkbox List ──────────────────────────────── */ +.bgd-checklist-filter { + width: 100%; + margin-bottom: 0.5rem; + padding: 0.4rem 0.6rem; + font-size: 0.85rem; + background: var(--input-bg); + border: 1px solid var(--border); + border-radius: var(--radius, 4px); + color: var(--text); +} +.bgd-checklist-filter::placeholder { + color: var(--text-muted); + opacity: 0.7; +} +.bgd-checklist { + max-height: 16rem; + overflow-y: auto; + border: 1px solid var(--border-light); + border-radius: 0.4rem; + background: var(--bg-secondary); +} +.bgd-checklist-row { + display: flex; + align-items: center; + gap: 0.55rem; + padding: 0.45rem 0.65rem; + border-bottom: 1px solid var(--border-light); + cursor: pointer; + font-size: 0.85rem; + transition: background 0.1s; +} +.bgd-checklist-row:last-child { + border-bottom: none; +} +.bgd-checklist-row:hover { + background: color-mix(in srgb, var(--card-bg) 60%, transparent); +} +.bgd-checklist-row input[type="checkbox"] { + flex-shrink: 0; +} +.bgd-checklist-name { + font-weight: 600; + color: var(--text); +} +.bgd-checklist-meta { + color: var(--text-muted); + font-size: 0.78rem; + margin-left: auto; + white-space: nowrap; +} +.bgd-checklist-empty { + padding: 0.75rem; + text-align: center; + color: var(--text-muted); + font-size: 0.85rem; +} +/* ── SVG State Dot Badges ─────────────────────────────────────────── */ +.bgd-state-dot { + width: 8px; + height: 8px; + display: inline-block; + vertical-align: middle; + margin-right: 4px; + fill: currentColor; +} @media (max-width: 600px) { .channel-scheduler-controls { flex-direction: column; @@ -4587,6 +4767,13 @@ button:focus-visible, input:focus-visible, select:focus-visible { .bgd-add-row button { width: 100%; } + .interleave-ring-label { + max-width: 8rem; + } + .bgd-checklist-meta { + margin-left: 0; + font-size: 0.72rem; + } } /* ── SAT panel ──────────────────────────────────────────────────────── */ .sat-view-bar { display: flex; gap: 0; margin-bottom: 0.75rem; border-bottom: 1px solid var(--border); }