[feat](trx-frontend-http): add frequency layers display for virtual channels
Render virtual channels as absolutely-positioned layer strips inside a shared relative container (#vchan-freq-layers). Layers are sorted by frequency ascending so higher-frequency channels receive a higher z-index and sit on top by default. Hovering any layer temporarily assigns it the maximum z-index to bring it to the front; leaving restores the original stacking order. Each layer is offset by 11 px vertically so all channels remain visible as a staggered card stack. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -262,6 +262,7 @@
|
|||||||
<div class="full-row label-below-row" id="vchan-row" style="display:none;">
|
<div class="full-row label-below-row" id="vchan-row" style="display:none;">
|
||||||
<div class="label"><span>Channels</span></div>
|
<div class="label"><span>Channels</span></div>
|
||||||
<div class="vchan-picker" id="vchan-picker"></div>
|
<div class="vchan-picker" id="vchan-picker"></div>
|
||||||
|
<div class="vchan-freq-layers" id="vchan-freq-layers"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="full-row label-below-row">
|
<div class="full-row label-below-row">
|
||||||
<div class="label"><span>Signal</span></div>
|
<div class="label"><span>Signal</span></div>
|
||||||
|
|||||||
@@ -49,6 +49,50 @@ function vchanHandleChannels(data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function vchanRenderLayers() {
|
||||||
|
const container = document.getElementById("vchan-freq-layers");
|
||||||
|
if (!container) return;
|
||||||
|
container.innerHTML = "";
|
||||||
|
|
||||||
|
if (vchanChannels.length === 0) {
|
||||||
|
container.style.height = "0";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by frequency ascending so higher-frequency channels get higher z-index.
|
||||||
|
const sorted = [...vchanChannels].sort((a, b) => a.freq_hz - b.freq_hz);
|
||||||
|
|
||||||
|
const LAYER_H_PX = 32;
|
||||||
|
const STEP_PX = 11; // vertical offset between layers so each peeks below the next
|
||||||
|
const totalH = LAYER_H_PX + (sorted.length - 1) * STEP_PX;
|
||||||
|
container.style.height = totalH + "px";
|
||||||
|
|
||||||
|
sorted.forEach((ch, i) => {
|
||||||
|
const layer = document.createElement("div");
|
||||||
|
layer.className = "vchan-freq-layer";
|
||||||
|
if (ch.id === vchanActiveId) layer.classList.add("active");
|
||||||
|
|
||||||
|
layer.style.top = (i * STEP_PX) + "px";
|
||||||
|
// Higher frequency → higher index → higher z-index (sits on top by default).
|
||||||
|
const defaultZ = i + 1;
|
||||||
|
layer.style.zIndex = defaultZ;
|
||||||
|
|
||||||
|
layer.textContent = `${ch.index}: ${vchanFmtFreq(ch.freq_hz)} ${ch.mode}`;
|
||||||
|
layer.title = `Ch ${ch.index}: ${vchanFmtFreq(ch.freq_hz)} ${ch.mode} · ${ch.subscribers} subscriber${ch.subscribers !== 1 ? "s" : ""}`;
|
||||||
|
|
||||||
|
// Bring hovered layer to the front; restore on leave.
|
||||||
|
const maxZ = sorted.length + 10;
|
||||||
|
layer.addEventListener("mouseenter", () => { layer.style.zIndex = maxZ; });
|
||||||
|
layer.addEventListener("mouseleave", () => { layer.style.zIndex = defaultZ; });
|
||||||
|
|
||||||
|
layer.addEventListener("click", () => {
|
||||||
|
if (ch.id !== vchanActiveId) vchanSubscribe(ch.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
container.appendChild(layer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function vchanRender() {
|
function vchanRender() {
|
||||||
const picker = document.getElementById("vchan-picker");
|
const picker = document.getElementById("vchan-picker");
|
||||||
if (!picker) return;
|
if (!picker) return;
|
||||||
@@ -93,6 +137,7 @@ function vchanRender() {
|
|||||||
addBtn.addEventListener("click", vchanAllocate);
|
addBtn.addEventListener("click", vchanAllocate);
|
||||||
picker.appendChild(addBtn);
|
picker.appendChild(addBtn);
|
||||||
|
|
||||||
|
vchanRenderLayers();
|
||||||
vchanSyncAccentUI();
|
vchanSyncAccentUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -392,6 +392,44 @@ input.status-input, select.status-input { width: 100%; padding: 0.45rem 0.5rem;
|
|||||||
border-color: var(--vchan-color);
|
border-color: var(--vchan-color);
|
||||||
box-shadow: inset 3px 0 0 var(--vchan-color);
|
box-shadow: inset 3px 0 0 var(--vchan-color);
|
||||||
}
|
}
|
||||||
|
/* Frequency layers: absolutely-positioned channel strips stacked by frequency */
|
||||||
|
.vchan-freq-layers {
|
||||||
|
position: relative;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
/* height is set dynamically by vchanRenderLayers() */
|
||||||
|
}
|
||||||
|
.vchan-freq-layer {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 2rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 0.6rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--input-bg);
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
transition: border-color 0.12s, box-shadow 0.12s;
|
||||||
|
}
|
||||||
|
.vchan-freq-layer:hover {
|
||||||
|
border-color: var(--vchan-color);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
.vchan-freq-layer.active {
|
||||||
|
background: var(--btn-bg);
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: 600;
|
||||||
|
border-color: var(--vchan-color);
|
||||||
|
box-shadow: inset 3px 0 0 var(--vchan-color);
|
||||||
|
}
|
||||||
/* Applied to #freq and #spectrum-bw-input when on a virtual channel */
|
/* Applied to #freq and #spectrum-bw-input when on a virtual channel */
|
||||||
.vchan-ch-active {
|
.vchan-ch-active {
|
||||||
border-color: var(--vchan-color) !important;
|
border-color: var(--vchan-color) !important;
|
||||||
|
|||||||
Reference in New Issue
Block a user