feat(fires): caption the confidence switch and name the slider "Fire intensity (FRP)"

The switch had no visible word saying what ALL / NOMINAL+ / HIGH refer to,
and "Min FRP" assumed the reader knows the acronym. The segmented row
descriptor gains an optional visible `label` (a caption above the group);
the fires row uses "Confidence" and "Fire intensity (FRP)", and the slider
value reads "≥ N MW" so the threshold is still clear. Locales updated.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
German KhokhlovandClaude Fable 5.1 committed 2026-10-27 18:50:03 +04:00
1 parent 22a87500c6
commit 1dad6b658a
9 files changed
+70 -18

No files matched your search

+3 -2
View File
@@ -3008,9 +3008,10 @@
"Every detection — many of the strongest fires are rated low confidence": "Todas las detecciones — muchos de los incendios más intensos tienen confianza baja",
"Nominal and high confidence detections": "Detecciones de confianza nominal y alta",
"High confidence detections only": "Solo detecciones de confianza alta",
"Min FRP": "FRP mín.",
"Fire intensity (FRP)": "Intensidad (FRP)",
"≥ {value} MW": "≥ {value} MW",
"Confidence": "Confianza",
"Minimum fire radiative power, megawatts": "Potencia radiativa mínima del fuego, en megavatios",
"{value} MW": "{value} MW",
"fire intensity filter|All": "Todo",
"Detection confidence": "Confianza de la detección",
"{shown} of {total} shown": "{shown} de {total} mostrados"
+3 -2
View File
@@ -3077,9 +3077,10 @@
"Every detection — many of the strongest fires are rated low confidence": "Все детекции — у многих сильнейших пожаров достоверность низкая",
"Nominal and high confidence detections": "Детекции средней и высокой достоверности",
"High confidence detections only": "Только детекции высокой достоверности",
"Min FRP": "Мин. FRP",
"Fire intensity (FRP)": "Интенсивность (FRP)",
"≥ {value} MW": "≥ {value} МВт",
"Confidence": "Достоверность",
"Minimum fire radiative power, megawatts": "Минимальная мощность излучения пожара, мегаватты",
"{value} MW": "{value} МВт",
"fire intensity filter|All": "Все",
"Detection confidence": "Достоверность детекции",
"{shown} of {total} shown": "показано {shown} из {total}"
+3 -2
View File
@@ -2870,9 +2870,10 @@
"Every detection — many of the strongest fires are rated low confidence": "所有检测——许多最强的火点置信度为低",
"Nominal and high confidence detections": "标称和高置信度的检测",
"High confidence detections only": "仅高置信度的检测",
"Min FRP": "最小 FRP",
"Fire intensity (FRP)": "火势强度 (FRP)",
"≥ {value} MW": "≥ {value} MW",
"Confidence": "置信度",
"Minimum fire radiative power, megawatts": "最小火辐射功率(兆瓦)",
"{value} MW": "{value} MW",
"fire intensity filter|All": "全部",
"Detection confidence": "检测置信度",
"{shown} of {total} shown": "显示 {shown} / {total}"
+2 -2
View File
@@ -121,7 +121,7 @@
"{value} KM",
"{value} KM/H",
"{value} KM/S",
"{value} MW",
"≥ {value} MW",
"ΔE",
"ΔN",
"▲ {frp} MW"
@@ -197,7 +197,7 @@
"{type} SPD: {speed} HDG: {heading}",
"{value} KM",
"{value} KM/S",
"{value} MW",
"≥ {value} MW",
"ΔE",
"ΔN",
"▲ {frp} MW"
+3 -2
View File
@@ -135,11 +135,11 @@ export function createQueries({
return {
slider: {
id: 'min-frp',
label: t('Min FRP'),
label: t('Fire intensity (FRP)'),
ariaLabel: t('Minimum fire radiative power, megawatts'),
valueText:
minFrp > 0
? t('{value} MW', { value: frpLabel(minFrp) })
? t('≥ {value} MW', { value: frpLabel(minFrp) })
: t('All', { context: 'fire intensity filter' }),
min: 0,
max: SLIDER_STEPS,
@@ -156,6 +156,7 @@ export function createQueries({
},
segmented: {
id: 'confidence',
label: t('Confidence'),
ariaLabel: t('Detection confidence'),
options: FIRMS_CONFIDENCE_MODES.map((mode) => ({
id: `confidence-${mode}`,
+22 -2
View File
@@ -104,8 +104,9 @@ export function syncSliderBlock(container, slider, { before = null } = {}) {
* Buttons carry `data-chip-id` like chips so the row's delegated click handler
* can resolve them; the caller matches `.data-toggle-segment`.
*
* Descriptor: `{ id, ariaLabel, options: [{ id, label, title, active,
* disabled, params }] }`.
* Descriptor: `{ id, label, ariaLabel, options: [{ id, label, title, active,
* disabled, params }] }` — `label` is an optional visible caption above the
* buttons (the group itself is a flex item, so the caption is a sibling).
* @param {HTMLElement} container The row's `.data-toggle-controls` node.
* @param {?object} segmented Descriptor, or null to remove the group.
* @param {{before?: ?Node}} [options] Insertion anchor for a new group.
@@ -121,19 +122,38 @@ export function syncSegmentedGroup(
const existing = [...container.children].find(
(node) => node.dataset?.segmentedId,
);
const existingCaption = [...container.children].find(
(node) => node.dataset?.segmentedCaptionFor,
);
if (!segmented) {
existing?.remove();
existingCaption?.remove();
return null;
}
let group = existing;
if (!group || group.dataset.segmentedId !== segmented.id) {
existing?.remove();
existingCaption?.remove();
group = document.createElement('div');
group.className = 'gev-segmented data-toggle-segmented';
group.dataset.segmentedId = segmented.id;
group.setAttribute('role', 'radiogroup');
container.insertBefore(group, before);
}
let caption = [...container.children].find(
(node) => node.dataset?.segmentedCaptionFor === segmented.id,
);
if (segmented.label) {
if (!caption) {
caption = document.createElement('span');
caption.className = 'data-toggle-segmented-label';
caption.dataset.segmentedCaptionFor = segmented.id;
container.insertBefore(caption, group);
}
set(caption, 'textContent', segmented.label);
} else {
caption?.remove();
}
if (segmented.ariaLabel) attribute(group, 'aria-label', segmented.ariaLabel);
const stale = new Map();
for (const node of [...group.children])
+20 -3
View File
@@ -24,6 +24,7 @@ const sliderDescriptor = (overrides = {}) => ({
const segmentedDescriptor = (active = 'all') => ({
id: 'confidence',
label: 'Confidence',
ariaLabel: 'Detection confidence',
options: ['all', 'nominal', 'high'].map((mode) => ({
id: `confidence-${mode}`,
@@ -96,6 +97,9 @@ test('the segmented group is a radiogroup whose active option reads checked, rec
assert.equal(group.getAttribute('role'), 'radiogroup');
assert.equal(group.getAttribute('aria-label'), 'Detection confidence');
assert.ok(group.className.split(' ').includes('gev-segmented'));
const caption = container.children[container.children.indexOf(group) - 1];
assert.equal(caption.className, 'data-toggle-segmented-label');
assert.equal(caption.textContent, 'Confidence');
const buttons = group.children;
assert.deepEqual(
buttons.map((b) => [
@@ -118,10 +122,18 @@ test('the segmented group is a radiogroup whose active option reads checked, rec
assert.equal(buttons[0].getAttribute('aria-checked'), 'false');
assert.equal(buttons[2].getAttribute('aria-checked'), 'true');
assert.ok(buttons[2].classList.contains('active'));
assert.equal(container.children.length, 2, 'caption and group');
syncSegmentedGroup(container, { ...segmentedDescriptor('high'), label: '' });
assert.equal(
container.querySelector('.data-toggle-segmented-label'),
null,
'an empty label removes the caption',
);
assert.equal(container.children.length, 1);
syncSegmentedGroup(container, null);
assert.equal(container.children.length, 0);
assert.equal(container.children.length, 0, 'null removes group and caption');
});
test('a layer row dispatches slider input and switch clicks as user params', async () => {
@@ -163,10 +175,15 @@ test('a layer row dispatches slider input and switch clicks as user params', asy
row.querySelector('.data-toggle-controls-info').textContent,
'12 of 40 shown',
);
// Order: switch, slider, info.
// Order: switch caption, switch, slider, info.
assert.deepEqual(
container.children.map((node) => node.className.split(' ')[0]),
['gev-segmented', 'data-toggle-slider', 'data-toggle-controls-info'],
[
'data-toggle-segmented-label',
'gev-segmented',
'data-toggle-slider',
'data-toggle-controls-info',
],
);
const input = row.querySelector('.data-toggle-slider-input');
+10
View File
@@ -1043,6 +1043,16 @@ html:not([lang='en']) .data-row-list-text {
max-width: 220px;
}
.data-toggle-segmented-label {
flex-basis: 100%;
padding-top: 2px;
font-family: var(--font-mono);
font-size: 8px;
letter-spacing: 1.2px;
color: var(--text-dim);
text-transform: uppercase;
}
.data-toggle-controls .data-toggle-segment {
min-height: 20px;
padding: 3px 6px;