Merge branch 'main' into prereq-10-osm-overlays

This commit is contained in:
Said Turksever authored and GitHub committed 2026-11-02 17:48:19 +00:00
commit bd7ef69843
77 files changed
+2443 -346

No files matched your search

+1
View File
@@ -174,6 +174,7 @@ const schemas = [
'cctv-panel',
'radio-panel',
'scene-panel',
'utilities-panel',
'pp-toggles',
'global-context-panel',
'weather-panel',
+2
View File
@@ -43,6 +43,7 @@ test('the complete Realtime tool payload pins the manifest-generated layer relea
// Globe rotation adds one motion; visual keyhole fit and explicit
// reset guidance/defaults update only move_camera and mode descriptions.
// S2 GhostMaps extends the generated layer enums.
// The Utilities panel (Directions, Draw, Import) adds one panel target.
// Panel adds named voice targets and timeout; shared Panel/Cockpit guidance
// is consolidated without changing either feature's arguments.
// Snow leaves the set_visual_style enum.
@@ -142,6 +143,7 @@ test('all legacy action arguments are byte-identical after removing the delibera
'voice-settings',
'voice-preview',
'provider-settings',
'utilities-panel',
].includes(id),
);
for (const tool of legacy) {
+21 -8
View File
@@ -99,6 +99,12 @@ const PANEL_ALIASES = new Map([
['global context', 'global-context-panel'],
['right context', 'global-context-panel'],
['context right panel', 'global-context-panel'],
['utilities', 'utilities-panel'],
['utilities panel', 'utilities-panel'],
['draw', 'utilities-panel'],
['drawing', 'utilities-panel'],
['import', 'utilities-panel'],
['import files', 'utilities-panel'],
['authoring', 'scene-panel'],
['scenes', 'scene-panel'],
['scene', 'scene-panel'],
@@ -122,6 +128,7 @@ const PANEL_IDS = new Set([
'cctv-panel',
'radio-panel',
'global-context-panel',
'utilities-panel',
'scene-panel',
'pp-toggles',
'weather-panel',
@@ -1053,15 +1060,18 @@ export function createGevActionRunner({
if (name === 'show_data_layers_menu') {
const layerId = normalizeLayerId(args.layerId || args.layer);
setPanelOpen(styleManager, 'data-panel', true);
const focusedLayer =
const row =
layerId && dataManager.layers.has(layerId)
? focusDataLayerRow(layerId)
? findDataLayerRow(layerId)
: null;
// Open the panel that holds the row: Directions lives in Utilities.
const panelId = row?.closest('[data-panel-id]')?.id || 'data-panel';
setPanelOpen(styleManager, panelId, true);
const focusedLayer = row ? focusDataLayerRow(row, layerId) : null;
return {
ok: true,
action: 'show_data_layers_menu',
panelId: 'data-panel',
panelId,
focusedLayer,
layers: dataManager
.getAll()
@@ -3197,11 +3207,14 @@ function normalizeCockpitAction(value) {
return null;
}
function focusDataLayerRow(layerId) {
const row = document.querySelector(
`#data-toggles [data-layer-id="${CSS.escape(layerId)}"]`,
function findDataLayerRow(layerId) {
const selector = `[data-layer-id="${CSS.escape(layerId)}"]`;
return document.querySelector(
`#data-toggles ${selector}, #utilities-toggles ${selector}`,
);
if (!row) return null;
}
function focusDataLayerRow(row, layerId) {
row.scrollIntoView({ block: 'center', behavior: 'smooth' });
row.classList.remove('gev-voice-focus');
void row.offsetWidth;
+78
View File
@@ -743,6 +743,61 @@ test('Data Layers voice inventory hides the Context coordinator while current-vi
assert.deepEqual(current.cockpit, { active: false, entryAllowed: true });
});
test('showing a layer opens the panel that holds its row: Directions lives in Utilities', async () => {
globalThis.window = globalThis.window || { clearTimeout, setTimeout, requestIdleCallback: null };
const previous = { document: globalThis.document, CSS: globalThis.CSS };
const focused = [];
const rowIn = (panelId) => ({
dataset: { layerName: panelId === 'utilities-panel' ? 'Directions' : 'Live Flights' },
classList: { add: (name) => focused.push(name), remove() {} },
scrollIntoView() {},
closest: (selector) => (selector === '[data-panel-id]' ? { id: panelId } : null),
});
const rows = { directions: rowIn('utilities-panel'), flights: rowIn('data-panel') };
// Each row is found only inside the list that really holds it.
const lists = { directions: '#utilities-toggles', flights: '#data-toggles' };
globalThis.CSS = { escape: (value) => value };
globalThis.document = {
querySelector: (selector) => {
for (const part of selector.split(',')) {
const [, list, id] = /^\s*(#[\w-]+) \[data-layer-id="([^"]+)"\]\s*$/.exec(part) || [];
if (id && lists[id] === list) return rows[id];
}
return null;
},
};
try {
const opened = [];
const layers = [
{ id: 'flights', name: 'Live Flights', enabled: false, showInTogglePanel: true, stats: {} },
{ id: 'directions', name: 'Directions', enabled: false, showInTogglePanel: true, stats: {} },
];
const dataManager = {
layers: new Map(layers.map((layer) => [layer.id, { module: {} }])),
getAll: () => layers,
};
const styleManager = {
setPanelCollapsed: (panelId, collapsed) => opened.push([panelId, collapsed]),
};
const viewer = {
clock: { onTick: { addEventListener: () => () => {} } },
scene: { canvas: { addEventListener() {}, removeEventListener() {} } },
camera: { moveEnd: { addEventListener() {} } },
};
const runner = createGevActionRunner({ viewer, styleManager, dataManager });
const directions = await runner('show_data_layers_menu', { layerId: 'directions' });
assert.equal(directions.panelId, 'utilities-panel');
assert.deepEqual(directions.focusedLayer, { id: 'directions', name: 'Directions' });
const flights = await runner('show_data_layers_menu', { layerId: 'flights' });
assert.equal(flights.panelId, 'data-panel');
assert.deepEqual(opened, [['utilities-panel', false], ['data-panel', false]]);
assert.deepEqual(focused, ['gev-voice-focus', 'gev-voice-focus']);
} finally {
globalThis.document = previous.document;
globalThis.CSS = previous.CSS;
}
});
test('generic layer visibility forwards cancellation and reports semantic failure', async () => {
globalThis.window = globalThis.window || { clearTimeout, setTimeout, requestIdleCallback: null };
const viewer = {
@@ -3954,3 +4009,26 @@ test('set_panel_open accepts the Authoring name for the scene panel', async () =
}
assert.deepEqual(calls.at(-1), ['scene-panel', false, { explicit: true }]);
});
test('set_panel_open reaches the Utilities panel by its name, Draw or Import', async () => {
const calls = [];
const runner = createGevActionRunner({
viewer: {
clock: { onTick: { addEventListener: () => () => {} } },
scene: { canvas: { addEventListener() {}, removeEventListener() {} } },
camera: {
moveEnd: { addEventListener() {} },
positionWC: Cesium.Cartesian3.fromDegrees(-97.7, 30.2, 1000),
},
},
styleManager: {
setPanelCollapsed: (...args) => calls.push(args),
},
dataManager: { layers: new Map(), isEnabled: () => false, getAll: () => [] },
});
for (const panel of ['utilities-panel', 'utilities', 'drawing', 'import']) {
const result = await runner('set_panel_open', { panelId: panel, open: true });
assert.equal(result.panelId, 'utilities-panel', panel);
}
assert.deepEqual(calls.at(-1), ['utilities-panel', false, { explicit: true }]);
});