Compare commits
2
Commits
f95f3d0104
...
c073d03ffb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c073d03ffb | ||
|
|
730f129404 |
@@ -743,7 +743,7 @@ function elementById(id) {
|
||||
const element = document.getElementById(id);
|
||||
if (element) body.appendChild(element);
|
||||
});
|
||||
tray.appendChild(details);
|
||||
tray.insertBefore(details, document.getElementById("audio-controls"));
|
||||
api.applyLayout(savedLayoutName(), { persist: false });
|
||||
}
|
||||
}
|
||||
@@ -819,7 +819,9 @@ function elementById(id) {
|
||||
};
|
||||
const reflowOverflow = () => {
|
||||
const nav = document.querySelector(".tab-bar-nav");
|
||||
const bar = actions.closest(".tab-bar");
|
||||
nav?.classList.remove("nav-icons-only");
|
||||
bar?.classList.remove("bar-tight");
|
||||
overflowOrder.forEach((selector) => {
|
||||
const element = menu.querySelector(selector);
|
||||
if (element) actions.insertBefore(element, wrap);
|
||||
@@ -833,6 +835,7 @@ function elementById(id) {
|
||||
menu.appendChild(element);
|
||||
}
|
||||
if (nav && !barFits()) nav.classList.add("nav-icons-only");
|
||||
if (bar && !barFits()) bar.classList.add("bar-tight");
|
||||
wrap.hidden = menu.children.length === 0;
|
||||
if (wrap.hidden) closeMenu();
|
||||
};
|
||||
|
||||
@@ -372,34 +372,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<div class="vchan-picker" id="vchan-picker"></div>
|
||||
</div>
|
||||
</div>
|
||||
<details id="scheduler-controls" class="advanced-radio-controls scheduler-controls-section">
|
||||
<summary>Scheduler controls</summary>
|
||||
<div class="advanced-radio-body">
|
||||
<div class="scheduler-control-row" style="display:none">
|
||||
<div class="scheduler-release-wrap">
|
||||
<div class="scheduler-action-row">
|
||||
<div class="scheduler-step-controls">
|
||||
<button id="scheduler-prev-btn" type="button">Previous Entry</button>
|
||||
<button id="scheduler-next-btn" type="button">Next Entry</button>
|
||||
</div>
|
||||
<button id="scheduler-release-btn" type="button">Release to Scheduler</button>
|
||||
<div id="scheduler-cycle-status" class="interleave-ring-wrap" style="display:none;">
|
||||
<svg class="interleave-ring" viewBox="0 0 36 36">
|
||||
<circle class="interleave-ring-bg" cx="18" cy="18" r="15.915" />
|
||||
<circle class="interleave-ring-fill" id="interleave-ring-fill" cx="18" cy="18" r="15.915"
|
||||
stroke-dasharray="100" stroke-dashoffset="100" />
|
||||
</svg>
|
||||
<div class="interleave-ring-text">
|
||||
<div class="interleave-ring-label" id="interleave-active-name">--</div>
|
||||
<div class="interleave-ring-sub" id="interleave-countdown">--</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="scheduler-release-status" class="scheduler-release-status">Scheduler is controlling the rig.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<div class="full-row label-below-row">
|
||||
<div class="label"><span>Signal</span></div>
|
||||
<div class="signal" style="gap: 1rem;">
|
||||
@@ -448,6 +420,34 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<details id="scheduler-controls" class="advanced-radio-controls scheduler-controls-section">
|
||||
<summary>Scheduler controls</summary>
|
||||
<div class="advanced-radio-body">
|
||||
<div class="scheduler-control-row" style="display:none">
|
||||
<div class="scheduler-release-wrap">
|
||||
<div class="scheduler-action-row">
|
||||
<div class="scheduler-step-controls">
|
||||
<button id="scheduler-prev-btn" type="button">Previous Entry</button>
|
||||
<button id="scheduler-next-btn" type="button">Next Entry</button>
|
||||
</div>
|
||||
<button id="scheduler-release-btn" type="button">Release to Scheduler</button>
|
||||
<div id="scheduler-cycle-status" class="interleave-ring-wrap" style="display:none;">
|
||||
<svg class="interleave-ring" viewBox="0 0 36 36">
|
||||
<circle class="interleave-ring-bg" cx="18" cy="18" r="15.915" />
|
||||
<circle class="interleave-ring-fill" id="interleave-ring-fill" cx="18" cy="18" r="15.915"
|
||||
stroke-dasharray="100" stroke-dashoffset="100" />
|
||||
</svg>
|
||||
<div class="interleave-ring-text">
|
||||
<div class="interleave-ring-label" id="interleave-active-name">--</div>
|
||||
<div class="interleave-ring-sub" id="interleave-countdown">--</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="scheduler-release-status" class="scheduler-release-status">Scheduler is controlling the rig.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1652,6 +1652,27 @@ small { color: var(--text-muted); }
|
||||
.tab-bar-nav.nav-icons-only .tab .tab-more-icon { display: block; }
|
||||
.tab-bar-nav.nav-icons-only .tab { padding: 0.45rem 0.55rem; }
|
||||
}
|
||||
/* Last resort, past icons: something in the bar has to absorb the shortfall,
|
||||
and by default that is the tab strip — it may shrink below its content, so
|
||||
its tabs keep full width and run under the controls, unreachable. The
|
||||
identity block takes it instead, ellipsised: a clipped station name is still
|
||||
readable, a destination hidden under the controls is not. */
|
||||
.tab-bar.bar-tight .header-main {
|
||||
flex-shrink: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.tab-bar.bar-tight .header-text {
|
||||
min-width: 0;
|
||||
}
|
||||
.tab-bar.bar-tight .title,
|
||||
.tab-bar.bar-tight .subtitle {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.tab-bar.bar-tight .tab-bar-nav {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
/* The selected destination is boxed, not underlined — the same treatment the
|
||||
mobile bottom nav already used, so one navigation model reads the same at
|
||||
every width. The transparent border is on the base so switching tabs moves
|
||||
|
||||
@@ -317,7 +317,10 @@ function elementById<T extends HTMLElement>(id: string): T {
|
||||
const element = document.getElementById(id);
|
||||
if (element) body.appendChild(element);
|
||||
});
|
||||
tray.appendChild(details);
|
||||
// Ahead of the collapsibles the markup ships: the radio's own settings
|
||||
// come before audio and the scheduler, and appending would put the
|
||||
// section built here last whatever the markup says.
|
||||
tray.insertBefore(details, document.getElementById("audio-controls"));
|
||||
api.applyLayout(savedLayoutName(), { persist: false });
|
||||
}
|
||||
}
|
||||
@@ -423,9 +426,11 @@ function elementById<T extends HTMLElement>(id: string): T {
|
||||
};
|
||||
const reflowOverflow = () => {
|
||||
const nav = document.querySelector<HTMLElement>(".tab-bar-nav");
|
||||
const bar = actions.closest<HTMLElement>(".tab-bar");
|
||||
// Measure from the roomiest state every time, so the decision is a
|
||||
// function of the current widths alone and cannot ratchet.
|
||||
nav?.classList.remove("nav-icons-only");
|
||||
bar?.classList.remove("bar-tight");
|
||||
overflowOrder.forEach((selector) => {
|
||||
const element = menu.querySelector<HTMLElement>(selector);
|
||||
if (element) actions.insertBefore(element, wrap);
|
||||
@@ -444,6 +449,10 @@ function elementById<T extends HTMLElement>(id: string): T {
|
||||
// controls, so the destinations nearest the controls become unclickable.
|
||||
// Icon widths are fixed, so this always buys back the labels' width.
|
||||
if (nav && !barFits()) nav.classList.add("nav-icons-only");
|
||||
// Still short with the tabs down to icons: hand the squeeze to the
|
||||
// identity block, which can ellipsise, rather than to the strip, which
|
||||
// can only clip destinations out of reach.
|
||||
if (bar && !barFits()) bar.classList.add("bar-tight");
|
||||
wrap.hidden = menu.children.length === 0;
|
||||
if (wrap.hidden) closeMenu();
|
||||
};
|
||||
|
||||
@@ -23,6 +23,15 @@ try {
|
||||
await page.locator("summary", { hasText: "Audio controls" }).click();
|
||||
assert.equal(await page.locator("#rx-audio-btn").count(), 1);
|
||||
|
||||
// Section order in the tray. "Advanced radio controls" is built at runtime,
|
||||
// so it lands wherever ui-core puts it rather than where the markup says —
|
||||
// appending, as it once did, always left it last.
|
||||
const sections = await page.evaluate(() =>
|
||||
[...document.querySelectorAll(".controls-tray > details")].map((section) =>
|
||||
section.querySelector("summary").textContent.trim()));
|
||||
assert.deepEqual(sections, ["Advanced radio controls", "Audio controls", "Scheduler controls"],
|
||||
`tray sections are ${JSON.stringify(sections)}`);
|
||||
|
||||
// Mode is a button group over a hidden <select>, which stays the value a
|
||||
// dozen call sites and several plugins read. The click has to reach it, and
|
||||
// the select must not take part in layout while it does.
|
||||
@@ -213,16 +222,19 @@ try {
|
||||
assert.ok(menu.onTop, "menu is painted underneath the page");
|
||||
|
||||
// Wider text than this machine renders. The checks above passed on macOS
|
||||
// while CI, whose system font is wider, put the tab strip 9px into the
|
||||
// controls: the bar had run out of controls to move into the overflow menu
|
||||
// and the tabs kept their full width anyway. Scaling the bar's own text
|
||||
// reproduces that on any machine. The tabs carry their font size themselves,
|
||||
// so the parent size alone does not cascade to them.
|
||||
// while CI, whose system font is wider, put the tab strip into the controls:
|
||||
// the bar had run out of moves and the tabs kept their full width anyway.
|
||||
// Every piece of text in the bar is scaled, not just the tabs — the identity
|
||||
// block is what runs out of room first at 1100px, where the bookmark gutters
|
||||
// make the card narrower than it is at 900px.
|
||||
const scale = 1.9;
|
||||
await page.addStyleTag({ content: `
|
||||
.tab-bar { font-size: 160%; }
|
||||
.tab-bar .tab, .tab-bar select, .tab-bar button { font-size: 1.52rem; }
|
||||
.tab-bar .title { font-size: ${1.05 * scale}rem !important; }
|
||||
.tab-bar .subtitle { font-size: ${0.78 * scale}rem !important; }
|
||||
.tab-bar .tab { font-size: ${0.95 * scale}rem !important; }
|
||||
.tab-bar select, .tab-bar button { font-size: ${0.95 * scale}rem !important; }
|
||||
` });
|
||||
for (const width of [1440, 1280, 1100]) {
|
||||
for (const width of [1440, 1280, 1100, 900]) {
|
||||
await page.setViewportSize({ width, height: 900 });
|
||||
await page.waitForTimeout(250);
|
||||
const crowded = await page.evaluate(() => {
|
||||
|
||||
Reference in New Issue
Block a user