Open views naming the retired Snow style on Normal

createView threw on style 'snow', so an embed host or a
show_in_gods_eye_view call holding a view from before Snow was retired
was refused outright, camera and layers included. viewFromParams read
style=snow as the recipient's own style, while the app opened the same
link on Normal.

Both now read a retired style as Normal; truly unknown names still
throw. The retired list moves to a browser-free module shared by the
view API, the share codec and visual settings.
This commit is contained in:
Said Turksever committed 2026-10-29 11:34:19 +00:00
1 parent 2b5770c6a2
commit 3b6111be06
10 files changed
+56 -19

No files matched your search

+14
View File
@@ -146,6 +146,12 @@ test('an embedded page takes views only from its parent and answers it', async (
id: 3,
view: { camera: { lat: 1, lon: 2 }, layers: ['flights'] },
});
// A view kept from before Snow was retired still applies, on Normal.
send(parent, {
type: 'gev:view',
id: 4,
view: { camera: { lat: 1, lon: 2 }, style: 'snow' },
});
await new Promise((resolve) => setTimeout(resolve, 10));
assert.deepEqual(posted[1], {
type: 'gev:view-applied',
@@ -161,6 +167,13 @@ test('an embedded page takes views only from its parent and answers it', async (
name === 'set_layer_visibility' && args.layerId === 'flights',
),
);
assert.equal(posted[3].id, 4);
assert.equal(posted[3].ok, true);
assert.ok(
calls.some(
([name, args]) => name === 'set_visual_style' && args.style === 'normal',
),
);
assert.equal(posted.filter((message) => message.id === 1).length, 0);
// Ready names no data and goes to any parent; answers go only to the
// origin that asked.
@@ -168,6 +181,7 @@ test('an embedded page takes views only from its parent and answers it', async (
'*',
'https://host.example',
'https://host.example',
'https://host.example',
]);
remove();
});
+1 -1
View File
@@ -31,9 +31,9 @@ import {
} from '../data/detectionPolicy.js';
import { clampScopeFeatherPct } from '../scopeMaskPolicy.js';
import { VIEW_STYLES } from '../view/index.js';
import { RETIRED_STYLES } from '../visualStylePolicy.js';
import {
GLOBAL_POST_DEFAULTS,
RETIRED_STYLES,
STYLE_PRESET_DEFAULTS,
STYLES,
} from '../ui/visualPresets.js';
-6
View File
@@ -17,12 +17,6 @@ export const STYLES = {
noir: noirShader,
};
/**
* Styles the app no longer offers. A share link, scene or shot that names one
* opens on Normal instead of keeping whatever style was already showing.
*/
export const RETIRED_STYLES = Object.freeze(['snow']);
/**
* The tactical detection look: Dense at 75%.
*
+2 -1
View File
@@ -1,6 +1,7 @@
import { clampScopeFeatherPct } from '../scopeMaskPolicy.js';
import { syncScopeControls } from './displayControls.js';
import { RETIRED_STYLES, STYLE_STATUS_LABELS } from './visualPresets.js';
import { STYLE_STATUS_LABELS } from './visualPresets.js';
import { RETIRED_STYLES } from '../visualStylePolicy.js';
import { syncSegmentedControl } from './segmentedControl.js';
import { t } from '../i18n/index.js';
import { UiLifetime } from './uiLifetime.js';
+7 -3
View File
@@ -11,6 +11,7 @@ import {
decodeLayerStateParams,
encodeLayerStateParams,
} from '../data/layerState.js';
import { RETIRED_STYLES } from '../visualStylePolicy.js';
/** Visual styles by internal name, with the name share links use. */
export const STYLE_URL_NAMES = Object.freeze({
@@ -253,6 +254,7 @@ export function createView({
} = {}) {
if (!Number.isFinite(camera?.lat) || !Number.isFinite(camera?.lon))
throw new TypeError('A view needs a camera lat and lon');
if (RETIRED_STYLES.includes(style)) style = 'normal';
if (style !== null && !VIEW_STYLES.includes(style))
throw new TypeError(`Unknown view style: ${style}`);
if (map !== null && !VIEW_MAPS.includes(map))
@@ -369,9 +371,11 @@ export function viewFromParams(params) {
pitch_deg: number('pitch'),
},
layers: state?.enabledLayerIds ?? [],
style: Object.hasOwn(STYLES_BY_URL_NAME, params.get('style'))
? STYLES_BY_URL_NAME[params.get('style')]
: null,
style: RETIRED_STYLES.includes(params.get('style'))
? 'normal'
: Object.hasOwn(STYLES_BY_URL_NAME, params.get('style'))
? STYLES_BY_URL_NAME[params.get('style')]
: null,
map: VIEW_MAPS.includes(map) ? map : null,
follow,
annotations: annotationsFromParams(params),
+8
View File
@@ -127,6 +127,14 @@ test('annotations travel in the link, keeping only what the app draws', async ()
assert.equal(viewToParams(many).has('an'), false);
});
test('a view naming the retired Snow style opens on Normal', () => {
const camera = { lat: 1, lon: 2 };
assert.equal(createView({ camera, style: 'snow' }).style, 'normal');
const view = viewFromParams(new URLSearchParams('lat=1&lon=2&style=snow'));
assert.equal(view.style, 'normal');
assert.equal(viewToParams(view).get('style'), 'normal');
});
test('inherited and unknown style keys do not corrupt a restored view', () => {
for (const style of [
'constructor',
+7
View File
@@ -0,0 +1,7 @@
/**
* Browser-free style vocabulary shared by views, share links and the UI.
* Styles the app no longer offers, by the name share links used: a view,
* link, scene or shot that names one opens on Normal instead of keeping
* whatever style was already showing.
*/
export const RETIRED_STYLES = Object.freeze(['snow']);