This commit is contained in:
Andras Schmelczer 2026-05-31 13:17:11 +01:00
parent c995f12f8b
commit 8dc939d761
44 changed files with 3540 additions and 2159478 deletions

View file

@ -50,16 +50,26 @@ import {
} from './poi-distance-filter';
import { dedupeTravelTimeEntries } from './travel-params';
import { isOverlayId, type OverlayId } from './overlays';
import { CRIME_TYPES, isCrimeTypeValue } from './crime-types';
import { isBasemapId, type BasemapId } from './basemaps';
import {
DEFAULT_COLOR_OPACITY,
colorOpacityToPercent,
normalizeColorOpacity,
} from './color-opacity';
const POI_NONE_PARAM = '__none';
const CRIME_TYPES_NONE_PARAM = '__none';
const CRIME_OVERLAY_ID: OverlayId = 'crime-hotspots';
export interface UrlState {
viewState: ViewState;
filters: FeatureFilters;
poiCategories: Set<string>;
overlays: Set<OverlayId>;
crimeTypes?: Set<string>;
basemap: BasemapId;
colorOpacity: number;
tab: 'properties' | 'area';
travelTime?: TravelTimeInitial;
postcode?: string;
@ -216,6 +226,7 @@ export function parseUrlState(): UrlState {
poiCategories: new Set(),
overlays: new Set(),
basemap: 'standard',
colorOpacity: DEFAULT_COLOR_OPACITY,
tab: 'area',
};
@ -256,18 +267,36 @@ export function parseUrlState(): UrlState {
result.overlays = new Set(overlayParams.filter(isOverlayId));
}
// Crime-type filter: repeated `crimeType` params, or the `__none` sentinel.
// Absent means "all types" (handled downstream), so leave it undefined.
const crimeTypeParams = params.getAll('crimeType');
if (crimeTypeParams.length > 0) {
result.crimeTypes = crimeTypeParams.includes(CRIME_TYPES_NONE_PARAM)
? new Set()
: new Set(crimeTypeParams.filter(isCrimeTypeValue));
}
const basemap = params.get('basemap');
if (basemap && isBasemapId(basemap)) {
result.basemap = basemap;
}
const colorOpacity = params.get('colorOpacity');
if (colorOpacity != null) {
const colorOpacityPercent = Number(colorOpacity);
if (!isNaN(colorOpacityPercent)) {
result.colorOpacity = normalizeColorOpacity(colorOpacityPercent / 100);
}
}
// Tab: full name
const tab = params.get('tab');
if (tab === 'properties' || tab === 'area') {
result.tab = tab;
}
// Navigate-to-postcode: one-time param for opening a saved property
// Selected postcode. This is also accepted as the historical one-time
// navigate-to-postcode param used by saved-property links.
const pc = params.get('pc');
if (pc) {
result.postcode = pc;
@ -329,7 +358,10 @@ export function stateToParams(
travelTimeEntries?: TravelTimeEntry[],
share?: string,
selectedOverlays?: Set<OverlayId>,
basemap?: BasemapId
basemap?: BasemapId,
selectedCrimeTypes?: Set<string>,
selectedPostcode?: string,
colorOpacity?: number
): URLSearchParams {
const params = new URLSearchParams();
@ -412,16 +444,40 @@ export function stateToParams(
params.set('tab', 'properties');
}
if (selectedPostcode) {
params.set('pc', selectedPostcode);
}
if (selectedOverlays) {
for (const overlay of selectedOverlays) {
params.append('overlay', overlay);
}
}
// Crime-type selection only matters while the crime overlay is on. Emit it
// only when it deviates from the default (all types selected) to keep URLs
// clean; a fully-deselected state uses an explicit sentinel.
if (selectedOverlays?.has(CRIME_OVERLAY_ID) && selectedCrimeTypes) {
if (selectedCrimeTypes.size === 0) {
params.append('crimeType', CRIME_TYPES_NONE_PARAM);
} else if (selectedCrimeTypes.size < CRIME_TYPES.length) {
for (const crimeType of selectedCrimeTypes) {
params.append('crimeType', crimeType);
}
}
}
if (basemap && basemap !== 'standard') {
params.set('basemap', basemap);
}
if (colorOpacity != null) {
const normalizedColorOpacity = normalizeColorOpacity(colorOpacity);
if (normalizedColorOpacity !== DEFAULT_COLOR_OPACITY) {
params.set('colorOpacity', String(colorOpacityToPercent(normalizedColorOpacity)));
}
}
// Travel time: repeated `tt` params
if (travelTimeEntries) {
for (const entry of dedupeTravelTimeEntries(travelTimeEntries)) {