feat: v0.22.0 — ISS-022/025/027 hover route trace on job list, on-board rows and relocation panel
This commit is contained in:
+53
-6
@@ -8,7 +8,7 @@ import { DEFAULT_STYLE_ID, STYLES, STYLE_STORAGE_KEY, isStyleId, localAssetsAvai
|
||||
import type { GeoJsonFeatureCollection } from '../lib/airports';
|
||||
import type { StateSnapshot } from '../lib/game/state';
|
||||
import type { HoverTarget } from './hoverTypes';
|
||||
import { planesGeoJson, routesGeoJson, type AirportCoord, type PlaneFeatureProps } from '../lib/mapData';
|
||||
import { hoverRouteGeoJson, planesGeoJson, routesGeoJson, type AirportCoord, type PlaneFeatureProps } from '../lib/mapData';
|
||||
import { weatherFor } from '../lib/weather';
|
||||
import { approxLocalTime } from '../lib/localTime';
|
||||
|
||||
@@ -33,6 +33,11 @@ const ROUTES_LEGS_LAYER_ID = 'routes-legs';
|
||||
const PLANES_HIGHLIGHT_LAYER_ID = 'planes-highlight';
|
||||
const ROUTES_HIGHLIGHT_LAYER_ID = 'routes-highlight';
|
||||
const AIRPORTS_HIGHLIGHT_LAYER_ID = 'airports-highlight';
|
||||
// ISS-022/025/027 (v0.22.0): the hover route trace — its OWN source + top-most dashed line,
|
||||
// because the shared `routes` source only holds FLYING legs: an open package's or a grounded
|
||||
// relocation's planned route is never in it. applyHoverRef pushes data into it on every hover.
|
||||
const HOVER_ROUTES_SOURCE_ID = 'hover-routes';
|
||||
const HOVER_ROUTE_LAYER_ID = 'hover-route';
|
||||
|
||||
function escapeHtml(s: string): string {
|
||||
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
@@ -43,7 +48,8 @@ interface MapProps {
|
||||
// route lines. Null until the first successful poll.
|
||||
state?: StateSnapshot | null;
|
||||
// ISS-002 (v0.15.0): the side panels' hover target — highlights the matching plane
|
||||
// (+ its route leg if flying) or a package's endpoint airports. Null = nothing hovered.
|
||||
// (+ its route leg if flying) or a package's endpoint airports, and (ISS-022/025/027 v0.22.0)
|
||||
// draws the full great-circle trace between any hovered target's two endpoints. Null = nothing hovered.
|
||||
hovered?: HoverTarget | null;
|
||||
// ISS-010 (v0.18.0): push an action endpoint's full response snapshot into the shared
|
||||
// shell state — same callback RightPane receives, used by the airport popup's Relocate row.
|
||||
@@ -101,16 +107,39 @@ export default function FlightMap({ state, hovered, applySnapshot }: MapProps) {
|
||||
const map = mapRef.current;
|
||||
if (!map) return;
|
||||
const h = hoveredRef.current;
|
||||
// The empty-string sentinel matches nothing: no feature has id/icao ''.
|
||||
const planeId = h && h.kind === 'plane' ? h.id : '';
|
||||
// The empty-string sentinel matches nothing: no feature has id/icao ''. A package hover may
|
||||
// name its plane (ISS-025 on-board cargo rows), so that plane's halo stays lit too.
|
||||
let planeId = '';
|
||||
let fromIcao = '';
|
||||
let toIcao = '';
|
||||
if (h) {
|
||||
if (h.kind === 'plane') planeId = h.id;
|
||||
else if (h.kind === 'package') {
|
||||
planeId = h.planeId ?? '';
|
||||
fromIcao = h.originIcao;
|
||||
toIcao = h.destIcao;
|
||||
} else {
|
||||
// 'route' — the Relocate panel's planned flight: trace + target airport only.
|
||||
fromIcao = h.fromIcao;
|
||||
toIcao = h.toIcao;
|
||||
}
|
||||
}
|
||||
if (map.getLayer(PLANES_HIGHLIGHT_LAYER_ID)) {
|
||||
map.setFilter(PLANES_HIGHLIGHT_LAYER_ID, ['==', ['get', 'id'], planeId]);
|
||||
}
|
||||
if (map.getLayer(ROUTES_HIGHLIGHT_LAYER_ID)) {
|
||||
map.setFilter(ROUTES_HIGHLIGHT_LAYER_ID, ['==', ['get', 'planeId'], planeId]);
|
||||
}
|
||||
const originIcao = h && h.kind === 'package' ? h.originIcao : '';
|
||||
const destIcao = h && h.kind === 'package' ? h.destIcao : '';
|
||||
// ISS-022/025/027: the hover route trace — one great-circle line for any target carrying two
|
||||
// ICAOs (package origin→dest / relocate fromIcao→toIcao); empty when either endpoint is unknown.
|
||||
const hoverSource = map.getSource(HOVER_ROUTES_SOURCE_ID) as maplibregl.GeoJSONSource | undefined;
|
||||
if (hoverSource) {
|
||||
void hoverSource.setData(hoverRouteGeoJson(fromIcao, toIcao, airportsIndexRef.current ?? new Map()));
|
||||
}
|
||||
// Package hovers ring BOTH endpoints (v0.15.0 behavior); 'route' hovers ring the TARGET only —
|
||||
// the relocation's destination is what ISS-027 asks to see. Plane hovers ring nothing.
|
||||
const originIcao = h?.kind === 'package' ? fromIcao : '';
|
||||
const destIcao = h?.kind === 'package' || h?.kind === 'route' ? toIcao : '';
|
||||
if (map.getLayer(AIRPORTS_HIGHLIGHT_LAYER_ID)) {
|
||||
map.setFilter(
|
||||
AIRPORTS_HIGHLIGHT_LAYER_ID,
|
||||
@@ -384,6 +413,24 @@ export default function FlightMap({ state, hovered, applySnapshot }: MapProps) {
|
||||
},
|
||||
});
|
||||
}
|
||||
// ISS-022/025/027 (v0.22.0): the hover route trace — added LAST so it sits above every
|
||||
// other layer, including the highlight rings at its endpoints. Own source: the shared
|
||||
// `routes` source only holds flying legs, never a planned one. Empty until a hover sets data.
|
||||
if (!map.getSource(HOVER_ROUTES_SOURCE_ID)) {
|
||||
map.addSource(HOVER_ROUTES_SOURCE_ID, { type: 'geojson', data: { type: 'FeatureCollection', features: [] } });
|
||||
}
|
||||
if (!map.getLayer(HOVER_ROUTE_LAYER_ID)) {
|
||||
map.addLayer({
|
||||
id: HOVER_ROUTE_LAYER_ID,
|
||||
type: 'line',
|
||||
source: HOVER_ROUTES_SOURCE_ID,
|
||||
paint: {
|
||||
'line-color': '#facc15', // matches the airports-highlight ring — one "trace" color
|
||||
'line-width': 4,
|
||||
'line-dasharray': [2, 2], // dashed = planned route, distinct from solid flying legs
|
||||
},
|
||||
});
|
||||
}
|
||||
// Layer-scoped listeners survive setStyle (they filter by layer id at event
|
||||
// time), so off+on with the same reference re-wires without duplicating.
|
||||
map.off('click', AIRPORTS_LAYER_ID, onAirportClick);
|
||||
|
||||
Reference in New Issue
Block a user