110 lines
4.1 KiB
TypeScript
110 lines
4.1 KiB
TypeScript
import type { MonitorViewport } from '../hooks/useMonitorData';
|
|
import { normalizeMonitorBounds } from '../domain/monitor';
|
|
|
|
export const MONITOR_RETURN_PARAM = 'monitorReturn';
|
|
|
|
export type MonitorRouteContext = {
|
|
mode: 'map' | 'list';
|
|
keyword: string;
|
|
protocol: string;
|
|
status: string;
|
|
selectedVin: string;
|
|
detailOpen: boolean;
|
|
viewport: MonitorViewport;
|
|
listOffset: number;
|
|
listLimit: number;
|
|
hasViewport: boolean;
|
|
};
|
|
|
|
const DEFAULT_CONTEXT: MonitorRouteContext = {
|
|
mode: 'map',
|
|
keyword: '',
|
|
protocol: '',
|
|
status: '',
|
|
selectedVin: '',
|
|
detailOpen: false,
|
|
viewport: { zoom: 5, bounds: '' },
|
|
listOffset: 0,
|
|
listLimit: 50,
|
|
hasViewport: false
|
|
};
|
|
|
|
function integer(value: string | null, fallback: number, minimum: number, maximum: number) {
|
|
const parsed = Number(value);
|
|
return Number.isInteger(parsed) && parsed >= minimum && parsed <= maximum ? parsed : fallback;
|
|
}
|
|
|
|
function decimal(value: string | null, fallback: number, minimum: number, maximum: number) {
|
|
const parsed = Number(value);
|
|
return Number.isFinite(parsed) && parsed >= minimum && parsed <= maximum ? parsed : fallback;
|
|
}
|
|
|
|
export function parseMonitorRouteContext(search: string | URLSearchParams): MonitorRouteContext {
|
|
const params = typeof search === 'string' ? new URLSearchParams(search) : search;
|
|
const selectedVin = params.get('selectedVin')?.trim().toUpperCase() ?? '';
|
|
const bounds = normalizeMonitorBounds(params.get('bounds'));
|
|
const hasViewport = params.has('zoom') || Boolean(bounds);
|
|
return {
|
|
mode: params.get('mode') === 'list' ? 'list' : 'map',
|
|
keyword: params.get('keyword')?.trim() ?? '',
|
|
protocol: params.get('protocol')?.trim() ?? '',
|
|
status: params.get('status')?.trim() ?? '',
|
|
selectedVin,
|
|
detailOpen: Boolean(selectedVin) && params.get('detail') !== 'collapsed',
|
|
viewport: {
|
|
zoom: decimal(params.get('zoom'), DEFAULT_CONTEXT.viewport.zoom, 3, 20),
|
|
bounds
|
|
},
|
|
listOffset: integer(params.get('listOffset'), 0, 0, 1_000_000),
|
|
listLimit: [20, 50, 100].includes(Number(params.get('listLimit'))) ? Number(params.get('listLimit')) : 50,
|
|
hasViewport
|
|
};
|
|
}
|
|
|
|
export function buildMonitorPath(context: Omit<MonitorRouteContext, 'hasViewport'> | MonitorRouteContext) {
|
|
const params = new URLSearchParams();
|
|
if (context.mode !== 'map') params.set('mode', context.mode);
|
|
if (context.keyword.trim()) params.set('keyword', context.keyword.trim());
|
|
if (context.protocol) params.set('protocol', context.protocol);
|
|
if (context.status) params.set('status', context.status);
|
|
if (context.selectedVin) {
|
|
params.set('selectedVin', context.selectedVin);
|
|
params.set('detail', context.detailOpen ? 'open' : 'collapsed');
|
|
}
|
|
params.set('zoom', String(Math.round(context.viewport.zoom * 100) / 100));
|
|
const bounds = normalizeMonitorBounds(context.viewport.bounds);
|
|
if (bounds) params.set('bounds', bounds);
|
|
if (context.listOffset) params.set('listOffset', String(context.listOffset));
|
|
if (context.listLimit !== 50) params.set('listLimit', String(context.listLimit));
|
|
const query = params.toString();
|
|
return query ? `/monitor?${query}` : '/monitor';
|
|
}
|
|
|
|
export function validatedMonitorReturn(value?: string | null) {
|
|
if (!value) return '';
|
|
try {
|
|
const parsed = new URL(value, 'https://vehicle-platform.invalid');
|
|
if (parsed.origin !== 'https://vehicle-platform.invalid' || parsed.pathname !== '/monitor') return '';
|
|
return buildMonitorPath(parseMonitorRouteContext(parsed.searchParams));
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
export function withMonitorReturn(path: string, monitorPath: string) {
|
|
const target = new URL(path, 'https://vehicle-platform.invalid');
|
|
const validated = validatedMonitorReturn(monitorPath);
|
|
if (validated) target.searchParams.set(MONITOR_RETURN_PARAM, validated);
|
|
return `${target.pathname}${target.search}${target.hash}`;
|
|
}
|
|
|
|
export function monitorReturnFromParams(params: URLSearchParams) {
|
|
return validatedMonitorReturn(params.get(MONITOR_RETURN_PARAM));
|
|
}
|
|
|
|
export function preserveMonitorReturn(params: URLSearchParams, monitorReturn: string) {
|
|
if (monitorReturn) params.set(MONITOR_RETURN_PARAM, monitorReturn);
|
|
else params.delete(MONITOR_RETURN_PARAM);
|
|
return params;
|
|
}
|