mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
refactor(ui): remove the action button and status badge builders
The Vue views apply the gev-action-button and gev-status-badge classes themselves, so the DOM builders have no users left. The docs describe the views instead.
This commit is contained in:
1 parent
9d9eb267c5
commit
2ffd7a4afd
7 files changed
+19
-199
No files matched your search
+10
-14
@@ -372,12 +372,10 @@ facade's asynchronous teardown can yield.
|
|||||||
|
|
||||||
## Ordinary action buttons
|
## Ordinary action buttons
|
||||||
|
|
||||||
`src/ui/actionButton.js` is a native, listener-free view helper used by Scene
|
The scene panels view and the CCTV panel draw their action buttons with the shared
|
||||||
presentation and sharing-toolbar owners. It receives translated text and explicit
|
`gev-action-button` classes in `src/ui/styles/action-button.css`. Feature
|
||||||
presentation/accessibility properties. Feature controllers own action dispatch,
|
controllers own action dispatch, state, persistence and cleanup, and the views
|
||||||
state, persistence and cleanup. Static Scenes/CCTV templates use the same CSS
|
preserve IDs and layout hooks.
|
||||||
surface and preserve IDs and layout hooks. The helper imports no application,
|
|
||||||
renderer, storage or services; it belongs to the consuming package closures.
|
|
||||||
|
|
||||||
## Display controls
|
## Display controls
|
||||||
|
|
||||||
@@ -759,11 +757,9 @@ field rules separate, so Radio's palette remains an intentional exception.
|
|||||||
|
|
||||||
### Read-only status badges
|
### Read-only status badges
|
||||||
|
|
||||||
`src/ui/statusBadge.js` constructs/updates ordinary badges without listeners,
|
Views draw read-only badges with the `gev-status-badge` classes, without
|
||||||
status inference or live-region policy. It consumes translated text and controlled
|
listeners, status inference or live-region policy. Appearance variants share
|
||||||
visibility; appearance variants share CSS rather than mapping machine states to
|
CSS rather than mapping machine states to universal severity. CCTV owns
|
||||||
universal severity. CCTV owns frame/source and calibration rules and their data
|
frame/source and calibration rules and their data attributes, while Weather
|
||||||
attributes, while Weather owns coverage labels. The keyed rail-card owner keeps
|
owns coverage labels. The rail card keeps node identity, header focus,
|
||||||
node identity, header focus, disclosure and teardown. The shared adapter never
|
disclosure and teardown, and does not announce every card update.
|
||||||
creates another state store or announces all card updates. A Vue view can replace
|
|
||||||
this adapter and retain these presentation inputs and styles.
|
|
||||||
+9
-14
@@ -1460,11 +1460,9 @@ boundaries preserve the current Tactical/Cyber appearance and native behavior.
|
|||||||
## Ordinary status badges
|
## Ordinary status badges
|
||||||
|
|
||||||
CCTV source/frame labels, calibration provenance and Weather coverage labels use
|
CCTV source/frame labels, calibration provenance and Weather coverage labels use
|
||||||
`ui/statusBadge.js` and the `gev-status-badge` CSS surface. The listener-free
|
the `gev-status-badge` CSS surface, drawn by the CCTV view and the rail card from
|
||||||
adapter accepts already-translated `label` and `hidden` values; pill, box and plain
|
already-translated labels; pill, box and plain appearances describe
|
||||||
appearances describe shape/density only. Static CCTV markup uses the same classes
|
shape/density only. Empty coverage remains hidden.
|
||||||
as generated rail-card badges. Repeated identical updates do not rewrite text or
|
|
||||||
replace nodes. Empty coverage remains hidden.
|
|
||||||
|
|
||||||
Feature owners retain the status rules, machine-state attributes, translations
|
Feature owners retain the status rules, machine-state attributes, translations
|
||||||
and announcement policy. CCTV frame acquisition (`idle/loading/error/ready`)
|
and announcement policy. CCTV frame acquisition (`idle/loading/error/ready`)
|
||||||
@@ -1474,18 +1472,16 @@ prior poses; unsaved edits override the label until save/reset. Coverage labels
|
|||||||
remain geographic scope, not health claims. Existing colors and the source
|
remain geographic scope, not health claims. Existing colors and the source
|
||||||
badge's polite live region are preserved; coverage and calibration do not gain
|
badge's polite live region are preserved; coverage and calibration do not gain
|
||||||
live regions. Interactive layer toggles, progress indicators and world overlays
|
live regions. Interactive layer toggles, progress indicators and world overlays
|
||||||
retain their specialized ownership. Vue can reuse the view contract and CSS while
|
retain their specialized ownership. Browser acceptance:
|
||||||
replacing DOM construction. Browser acceptance:
|
|
||||||
`QA_BASE_URL=http://127.0.0.1:4189 node scripts/qa-status-badges.mjs`.
|
`QA_BASE_URL=http://127.0.0.1:4189 node scripts/qa-status-badges.mjs`.
|
||||||
|
|
||||||
## Ordinary action buttons
|
## Ordinary action buttons
|
||||||
|
|
||||||
Scene panel actions, shot LOAD/DEL actions, the EDIT DETAILS / SHARE SCENE
|
Scene panel actions, shot LOAD/DEL actions, the EDIT DETAILS / SHARE SCENE
|
||||||
bar and one-shot CCTV actions use `ui/actionButton.js` and the shared
|
bar and one-shot CCTV actions use the shared `gev-action-button` surface in
|
||||||
`gev-action-button` surface in `ui/styles/action-button.css`. Static templates
|
`ui/styles/action-button.css`, with default/danger variants and
|
||||||
apply the same classes as generated buttons. The native builder accepts translated
|
default/compact sizes, drawn by the scene panels view (`LayerPanelsView.vue`,
|
||||||
text, default/danger variants, default/compact sizes and disabled/accessibility
|
`SceneShotList.vue`) and the CCTV panel. Feature
|
||||||
properties; it installs no listeners and owns no application state. Feature
|
|
||||||
controllers retain callbacks, enable/disable rules, confirmation prompts,
|
controllers retain callbacks, enable/disable rules, confirmation prompts,
|
||||||
persistence and teardown. The smaller shot actions retain their existing sizing
|
persistence and teardown. The smaller shot actions retain their existing sizing
|
||||||
and hover treatment. Feature CSS still owns panel layout and translated wrapping.
|
and hover treatment. Feature CSS still owns panel layout and translated wrapping.
|
||||||
@@ -1493,8 +1489,7 @@ and hover treatment. Feature CSS still owns panel layout and translated wrapping
|
|||||||
CCTV enable, coverage, auto-hop, projection and adjustment toggles, editable
|
CCTV enable, coverage, auto-hop, projection and adjustment toggles, editable
|
||||||
calibration-value chips and the scene authoring dialogs retain their specialized
|
calibration-value chips and the scene authoring dialogs retain their specialized
|
||||||
controls. Existing `scene-btn` hooks remain compatible with Street Level and
|
controls. Existing `scene-btn` hooks remain compatible with Street Level and
|
||||||
legacy toggles. The shared CSS, explicit input/action boundary and behavior tests
|
legacy toggles.
|
||||||
can carry to a Vue view while Vue replaces native DOM construction.
|
|
||||||
Browser acceptance: `QA_BASE_URL=http://127.0.0.1:4179 node scripts/qa-action-buttons.mjs`;
|
Browser acceptance: `QA_BASE_URL=http://127.0.0.1:4179 node scripts/qa-action-buttons.mjs`;
|
||||||
`qa-scene-controls.mjs` also covers real Scene authoring/playback with native Space capture.
|
`qa-scene-controls.mjs` also covers real Scene authoring/playback with native Space capture.
|
||||||
|
|
||||||
|
|||||||
@@ -1093,7 +1093,6 @@
|
|||||||
"server/providers/overpass/requestAdmission.test.mjs",
|
"server/providers/overpass/requestAdmission.test.mjs",
|
||||||
"src/tooling/trackArrivalObservation.test.mjs",
|
"src/tooling/trackArrivalObservation.test.mjs",
|
||||||
"src/ui/styles/status-badge.css",
|
"src/ui/styles/status-badge.css",
|
||||||
"src/ui/statusBadge.test.mjs",
|
|
||||||
"src/ui/cctvBadges.test.mjs",
|
"src/ui/cctvBadges.test.mjs",
|
||||||
"scripts/qa-status-badges.mjs",
|
"scripts/qa-status-badges.mjs",
|
||||||
"src/dockerTrustedPeers.test.mjs",
|
"src/dockerTrustedPeers.test.mjs",
|
||||||
|
|||||||
@@ -609,7 +609,6 @@
|
|||||||
"src/ui/unitLabels.js",
|
"src/ui/unitLabels.js",
|
||||||
"src/ui/keyGuidance.js",
|
"src/ui/keyGuidance.js",
|
||||||
"src/ui/statusText.js",
|
"src/ui/statusText.js",
|
||||||
"src/ui/statusBadge.js",
|
|
||||||
"src/ui/rail/RailChips.vue",
|
"src/ui/rail/RailChips.vue",
|
||||||
"src/ui/rail/RailRowList.vue",
|
"src/ui/rail/RailRowList.vue",
|
||||||
"src/ui/rail/RailCardSetting.vue",
|
"src/ui/rail/RailCardSetting.vue",
|
||||||
@@ -695,7 +694,6 @@
|
|||||||
"src/i18n/locales/zh-CN.json",
|
"src/i18n/locales/zh-CN.json",
|
||||||
"src/i18n/locales/zh-Hant.json",
|
"src/i18n/locales/zh-Hant.json",
|
||||||
"src/ui/unitLabels.js",
|
"src/ui/unitLabels.js",
|
||||||
"src/ui/statusBadge.js",
|
|
||||||
"src/ui/vueRuntime.js",
|
"src/ui/vueRuntime.js",
|
||||||
"src/ui/stores/cctv.js"
|
"src/ui/stores/cctv.js"
|
||||||
],
|
],
|
||||||
@@ -787,7 +785,6 @@
|
|||||||
"src/ui/stores/scenes.js",
|
"src/ui/stores/scenes.js",
|
||||||
"src/ui/views/SceneOptions.vue",
|
"src/ui/views/SceneOptions.vue",
|
||||||
"src/ui/views/SceneShotList.vue",
|
"src/ui/views/SceneShotList.vue",
|
||||||
"src/ui/actionButton.js",
|
|
||||||
"src/i18n/index.js",
|
"src/i18n/index.js",
|
||||||
"src/i18n/locale.js",
|
"src/i18n/locale.js",
|
||||||
"src/i18n/locales/de.json",
|
"src/i18n/locales/de.json",
|
||||||
@@ -931,7 +928,6 @@
|
|||||||
"src/ui/stores/scenes.js",
|
"src/ui/stores/scenes.js",
|
||||||
"src/ui/views/SceneOptions.vue",
|
"src/ui/views/SceneOptions.vue",
|
||||||
"src/ui/views/SceneShotList.vue",
|
"src/ui/views/SceneShotList.vue",
|
||||||
"src/ui/actionButton.js",
|
|
||||||
"src/ui/scenes.js",
|
"src/ui/scenes.js",
|
||||||
"src/ui/shareFollow.js",
|
"src/ui/shareFollow.js",
|
||||||
"src/share/follow.js",
|
"src/share/follow.js",
|
||||||
@@ -977,7 +973,6 @@
|
|||||||
"src/ui/keyGuidance.js",
|
"src/ui/keyGuidance.js",
|
||||||
"src/ui/layerNames.js",
|
"src/ui/layerNames.js",
|
||||||
"src/ui/statusText.js",
|
"src/ui/statusText.js",
|
||||||
"src/ui/statusBadge.js",
|
|
||||||
"src/scopeMaskPolicy.js",
|
"src/scopeMaskPolicy.js",
|
||||||
"src/visualStylePolicy.js",
|
"src/visualStylePolicy.js",
|
||||||
"src/ui/stores/panels.js",
|
"src/ui/stores/panels.js",
|
||||||
@@ -1090,7 +1085,6 @@
|
|||||||
"src/ui/stores/scenes.js",
|
"src/ui/stores/scenes.js",
|
||||||
"src/ui/views/SceneOptions.vue",
|
"src/ui/views/SceneOptions.vue",
|
||||||
"src/ui/views/SceneShotList.vue",
|
"src/ui/views/SceneShotList.vue",
|
||||||
"src/ui/actionButton.js",
|
|
||||||
"src/contextModePolicy.js",
|
"src/contextModePolicy.js",
|
||||||
"src/director/clock.js",
|
"src/director/clock.js",
|
||||||
"src/director/timeline.js",
|
"src/director/timeline.js",
|
||||||
@@ -2641,7 +2635,6 @@
|
|||||||
"src/ui/stores/scenes.js",
|
"src/ui/stores/scenes.js",
|
||||||
"src/ui/views/SceneOptions.vue",
|
"src/ui/views/SceneOptions.vue",
|
||||||
"src/ui/views/SceneShotList.vue",
|
"src/ui/views/SceneShotList.vue",
|
||||||
"src/ui/actionButton.js",
|
|
||||||
"src/ui/scenes.js",
|
"src/ui/scenes.js",
|
||||||
"src/ui/shareFollow.js",
|
"src/ui/shareFollow.js",
|
||||||
"src/ui/shareRestoration.js",
|
"src/ui/shareRestoration.js",
|
||||||
@@ -2956,7 +2949,6 @@
|
|||||||
"src/voice/geminiSession.js",
|
"src/voice/geminiSession.js",
|
||||||
"src/voice/geminiAudio.js",
|
"src/voice/geminiAudio.js",
|
||||||
"src/voice/geminiCaptureWorklet.js",
|
"src/voice/geminiCaptureWorklet.js",
|
||||||
"src/ui/statusBadge.js",
|
|
||||||
"src/sceneCommandRelease.js",
|
"src/sceneCommandRelease.js",
|
||||||
"src/scopeMaskPolicy.js",
|
"src/scopeMaskPolicy.js",
|
||||||
"src/visualStylePolicy.js",
|
"src/visualStylePolicy.js",
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
/**
|
|
||||||
* Create an ordinary native action button. Static templates use the same CSS
|
|
||||||
* classes. Labels are already translated; feature owners retain callbacks,
|
|
||||||
* disabled-state updates and listener cleanup. Construction binds no listeners.
|
|
||||||
* @param {object} options
|
|
||||||
* @param {Document} [options.documentRef] Document that owns the button.
|
|
||||||
* @param {string} options.label Visible text, never interpreted as markup.
|
|
||||||
* @param {string} [options.className] Additional feature/layout hooks.
|
|
||||||
* @param {'default'|'danger'} [options.variant]
|
|
||||||
* @param {'default'|'compact'} [options.size]
|
|
||||||
* @param {boolean} [options.disabled]
|
|
||||||
* @param {string} [options.ariaLabel] Accessible name when visible text needs context.
|
|
||||||
* @param {string} [options.title]
|
|
||||||
* @returns {HTMLButtonElement}
|
|
||||||
*/
|
|
||||||
export function createActionButton({
|
|
||||||
documentRef = globalThis.document,
|
|
||||||
label,
|
|
||||||
className = '',
|
|
||||||
variant = 'default',
|
|
||||||
size = 'default',
|
|
||||||
disabled = false,
|
|
||||||
ariaLabel,
|
|
||||||
title,
|
|
||||||
}) {
|
|
||||||
const button = documentRef.createElement('button');
|
|
||||||
button.type = 'button';
|
|
||||||
button.className = [
|
|
||||||
'gev-action-button',
|
|
||||||
variant === 'danger' && 'gev-action-button--danger',
|
|
||||||
size === 'compact' && 'gev-action-button--compact',
|
|
||||||
className,
|
|
||||||
]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(' ');
|
|
||||||
button.textContent = label;
|
|
||||||
button.disabled = disabled;
|
|
||||||
if (ariaLabel !== undefined) button.setAttribute('aria-label', ariaLabel);
|
|
||||||
if (title !== undefined) button.title = title;
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
/**
|
|
||||||
* Ordinary read-only badge view. Labels are already translated. Feature owners
|
|
||||||
* retain status derivation, machine-state attributes and announcement policy.
|
|
||||||
* No listeners, timers, state inference or automatic live regions are installed.
|
|
||||||
* Vue can replace this DOM adapter while retaining the label/hidden contract
|
|
||||||
* and the gev-status-badge CSS surface.
|
|
||||||
* @param {object} options
|
|
||||||
* @param {Document} [options.documentRef]
|
|
||||||
* @param {string} [options.label]
|
|
||||||
* @param {'pill'|'box'|'plain'} [options.appearance]
|
|
||||||
* @param {string} [options.className] Feature/layout hooks.
|
|
||||||
* @param {boolean} [options.hidden]
|
|
||||||
* @returns {HTMLSpanElement}
|
|
||||||
*/
|
|
||||||
export function createStatusBadge({
|
|
||||||
documentRef = globalThis.document,
|
|
||||||
label = '',
|
|
||||||
appearance = 'pill',
|
|
||||||
className = '',
|
|
||||||
hidden = false,
|
|
||||||
} = {}) {
|
|
||||||
const badge = documentRef.createElement('span');
|
|
||||||
badge.className = [
|
|
||||||
'gev-status-badge',
|
|
||||||
appearance === 'box' && 'gev-status-badge--box',
|
|
||||||
appearance === 'plain' && 'gev-status-badge--plain',
|
|
||||||
className,
|
|
||||||
]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(' ');
|
|
||||||
updateStatusBadge(badge, { label, hidden });
|
|
||||||
return badge;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Paint controlled text/visibility without replacing nodes or redundant writes. */
|
|
||||||
export function updateStatusBadge(badge, { label, hidden = false }) {
|
|
||||||
if (badge.textContent !== label) badge.textContent = label;
|
|
||||||
if (badge.hidden !== hidden) badge.hidden = hidden;
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
import '../testSupport/registerDom.mjs';
|
|
||||||
import test from 'node:test';
|
|
||||||
import assert from 'node:assert/strict';
|
|
||||||
import { assertSameNode } from '../testSupport/domAssert.mjs';
|
|
||||||
import { createStatusBadge, updateStatusBadge } from './statusBadge.js';
|
|
||||||
import { createRailCards } from './railCards.js';
|
|
||||||
import { railDocumentFixture as railFixture } from './railTestFixture.mjs';
|
|
||||||
|
|
||||||
test('badges paint literal translated text without installing announcement or interaction semantics', () => {
|
|
||||||
const { document } = railFixture();
|
|
||||||
for (const appearance of ['pill', 'box', 'plain']) {
|
|
||||||
const badge = createStatusBadge({
|
|
||||||
documentRef: document,
|
|
||||||
appearance,
|
|
||||||
className: 'feature-hook',
|
|
||||||
label: '<img src=x onerror=alert(1)> · НЕИЗВЕСТНО',
|
|
||||||
});
|
|
||||||
assert.equal(badge.tagName, 'SPAN');
|
|
||||||
assert.equal(
|
|
||||||
badge.textContent,
|
|
||||||
'<img src=x onerror=alert(1)> · НЕИЗВЕСТНО',
|
|
||||||
);
|
|
||||||
assert.equal(badge.children.length, 0);
|
|
||||||
assert.equal(badge.classList.contains('gev-status-badge'), true);
|
|
||||||
assert.equal(badge.classList.contains('feature-hook'), true);
|
|
||||||
assert.equal(badge.getAttribute('role'), null);
|
|
||||||
assert.equal(badge.getAttribute('aria-live'), null);
|
|
||||||
assert.equal(badge.getAttribute('tabindex'), null);
|
|
||||||
assert.equal(
|
|
||||||
badge.classList.contains(`gev-status-badge--${appearance}`),
|
|
||||||
appearance !== 'pill',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('painting preserves feature state, live regions and focus with zero identical writes', () => {
|
|
||||||
const f = railFixture();
|
|
||||||
const { document } = f;
|
|
||||||
const badge = createStatusBadge({ documentRef: document, hidden: true });
|
|
||||||
f.container.appendChild(badge);
|
|
||||||
badge.setAttribute('role', 'status');
|
|
||||||
badge.setAttribute('aria-live', 'polite');
|
|
||||||
badge.dataset.frameState = 'ready';
|
|
||||||
const focusOwner = document.createElement('button');
|
|
||||||
document.body.appendChild(focusOwner);
|
|
||||||
focusOwner.focus();
|
|
||||||
updateStatusBadge(badge, { label: 'SNAPSHOT · DEGRADED' });
|
|
||||||
assert.equal(badge.hidden, false);
|
|
||||||
f.writes();
|
|
||||||
updateStatusBadge(badge, { label: 'SNAPSHOT · DEGRADED' });
|
|
||||||
assert.equal(f.writes(), 0);
|
|
||||||
assert.equal(badge.getAttribute('role'), 'status');
|
|
||||||
assert.equal(badge.getAttribute('aria-live'), 'polite');
|
|
||||||
assert.equal(badge.dataset.frameState, 'ready');
|
|
||||||
assertSameNode(document.activeElement, focusOwner);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('coverage badges keep keyed identity, hide empty coverage and never claim feed health', () => {
|
|
||||||
const f = railFixture();
|
|
||||||
const view = createRailCards({ ...f, badgeClassName: 'weather-coverage' });
|
|
||||||
const card = { id: 'radar', title: 'Rain radar', badge: 'CONUS', blocks: [] };
|
|
||||||
view.update([card]);
|
|
||||||
const badge = f.find((node) => node.classList.contains('gev-status-badge'));
|
|
||||||
const header = f.container.children[0].children[0];
|
|
||||||
header.focus();
|
|
||||||
assert.equal(badge.classList.contains('gev-status-badge--plain'), true);
|
|
||||||
assert.equal(badge.classList.contains('weather-coverage'), true);
|
|
||||||
view.update([{ ...card, badge: '' }]);
|
|
||||||
assert.equal(badge.hidden, true);
|
|
||||||
view.update([{ ...card, badge: 'Global · 1° grid' }]);
|
|
||||||
assert.equal(badge.hidden, false);
|
|
||||||
assert.equal(badge.textContent, 'Global · 1° grid');
|
|
||||||
assert.equal(
|
|
||||||
f.find((node) => node.classList.contains('gev-status-badge')),
|
|
||||||
badge,
|
|
||||||
);
|
|
||||||
assert.deepEqual({ ...badge.dataset }, {});
|
|
||||||
assertSameNode(f.document.activeElement, header);
|
|
||||||
view.destroy();
|
|
||||||
view.update([card]);
|
|
||||||
assert.equal(f.container.children.length, 0);
|
|
||||||
});
|
|
||||||
Reference in new issue
Block a user