mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-09-14 13:19:01 +02:00
Merge remote-tracking branch 'origin/pr/559' into codex/pr-561-562-559
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { filterShipsByActiveFilters } from '@/components/map/shipFilters';
|
||||
|
||||
const nimitz = { name: 'USS Nimitz (CVN-68)', type: 'carrier' as const };
|
||||
const eclipse = { name: 'ECLIPSE', type: 'yacht' as const };
|
||||
const frigate = { name: 'HMS Diamond', type: 'military_vessel' as const };
|
||||
const ships = [nimitz, eclipse, frigate];
|
||||
|
||||
describe('filterShipsByActiveFilters', () => {
|
||||
it('returns the same array when no ship filter is set', () => {
|
||||
expect(filterShipsByActiveFilters(ships, undefined)).toBe(ships);
|
||||
expect(filterShipsByActiveFilters(ships, { ship_type: [] })).toBe(ships);
|
||||
expect(filterShipsByActiveFilters(ships, { commercial_airline: ['UAL'] })).toBe(ships);
|
||||
});
|
||||
|
||||
it('keeps carriers only when their own type is selected', () => {
|
||||
expect(filterShipsByActiveFilters(ships, { ship_type: ['yacht'] })).toEqual([eclipse]);
|
||||
expect(filterShipsByActiveFilters(ships, { ship_type: ['military_vessel'] })).toEqual([frigate]);
|
||||
expect(filterShipsByActiveFilters(ships, { ship_type: ['carrier', 'yacht'] })).toEqual([
|
||||
nimitz,
|
||||
eclipse,
|
||||
]);
|
||||
});
|
||||
|
||||
it('combines name and type filters', () => {
|
||||
expect(
|
||||
filterShipsByActiveFilters(ships, { ship_name: ['ECLIPSE'], ship_type: ['carrier'] }),
|
||||
).toEqual([]);
|
||||
expect(filterShipsByActiveFilters(ships, { ship_name: ['USS Nimitz (CVN-68)'] })).toEqual([
|
||||
nimitz,
|
||||
]);
|
||||
});
|
||||
|
||||
it('handles a missing ship array', () => {
|
||||
expect(filterShipsByActiveFilters(undefined, { ship_type: ['yacht'] })).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -152,6 +152,7 @@ import { useImperativeSource } from '@/components/map/hooks/useImperativeSource'
|
||||
import { useDynamicMapLayersWorker } from '@/components/map/hooks/useDynamicMapLayersWorker';
|
||||
import { useStaticMapLayersWorker } from '@/components/map/hooks/useStaticMapLayersWorker';
|
||||
import { applyDynamicLayerInterp } from '@/components/map/applyDynamicLayerInterp';
|
||||
import { filterShipsByActiveFilters } from '@/components/map/shipFilters';
|
||||
import {
|
||||
ClusterCountLabels,
|
||||
TrackedFlightLabels,
|
||||
@@ -1448,9 +1449,14 @@ const MaplibreViewer = ({
|
||||
const shipClusters = useClusterLabels(mapRef, 'ships-clusters-layer', shipsGeoJSON);
|
||||
const eqClusters = useClusterLabels(mapRef, 'eq-clusters-layer', earthquakesGeoJSON);
|
||||
|
||||
// Carriers bypass the worker, so apply the operator's vessel filters here.
|
||||
const carrierShips = useMemo(
|
||||
() => (activeLayers.ships_military ? filterShipsByActiveFilters(data?.ships, activeFilters) : []),
|
||||
[activeLayers.ships_military, data?.ships, activeFilters],
|
||||
);
|
||||
const carriersGeoJSON = useMemo(
|
||||
() => (activeLayers.ships_military ? buildCarriersGeoJSON(data?.ships) : null),
|
||||
[activeLayers.ships_military, data?.ships],
|
||||
() => (activeLayers.ships_military ? buildCarriersGeoJSON(carrierShips) : null),
|
||||
[activeLayers.ships_military, carrierShips],
|
||||
);
|
||||
|
||||
// SAR anomaly pins (Mode B) + AOI watchbox circles. AOIs render whenever
|
||||
@@ -4482,8 +4488,8 @@ const MaplibreViewer = ({
|
||||
)}
|
||||
|
||||
{/* HTML labels for carriers (orange names, with ESTIMATED badge for OSINT positions) */}
|
||||
{carriersGeoJSON && !selectedEntity && !isMapInteracting && data?.ships && (
|
||||
<CarrierLabels ships={data.ships} inView={inView} interpShip={interpShip} />
|
||||
{carriersGeoJSON && !selectedEntity && !isMapInteracting && carrierShips.length > 0 && (
|
||||
<CarrierLabels ships={carrierShips} inView={inView} interpShip={interpShip} />
|
||||
)}
|
||||
|
||||
{/* HTML labels for tracked yachts (pink owner names) */}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { classifyAircraft } from '@/utils/aircraftClassification';
|
||||
import type { Flight, Ship, SigintSignal } from '@/types/dashboard';
|
||||
import type { FlightLayerConfig } from '@/components/map/geoJSONBuilders';
|
||||
import { filterShipsByActiveFilters } from '@/components/map/shipFilters';
|
||||
|
||||
type BoundsTuple = [number, number, number, number];
|
||||
type FC = GeoJSON.FeatureCollection | null;
|
||||
@@ -523,16 +524,7 @@ function applyFilters(activeFilters: Record<string, string[]> | undefined) {
|
||||
}
|
||||
|
||||
// ── Ships ──
|
||||
let ships = dynamicData.ships;
|
||||
if (ships && (has('ship_name') || has('ship_type'))) {
|
||||
const nameSet = has('ship_name') ? set('ship_name') : null;
|
||||
const typeSet = has('ship_type') ? set('ship_type') : null;
|
||||
ships = ships.filter((s: any) => {
|
||||
if (nameSet && !nameSet.has(s.name)) return false;
|
||||
if (typeSet && !typeSet.has(s.type)) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
const ships = dynamicData.ships ? filterShipsByActiveFilters(dynamicData.ships, f) : dynamicData.ships;
|
||||
|
||||
return { commercial, private_, jets, military, tracked, ships };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { Ship } from '@/types/dashboard';
|
||||
|
||||
/**
|
||||
* Operator vessel filters from the Data Filters panel. Shared by the worker
|
||||
* (regular ship icons) and the main thread (carrier icons and labels) so every
|
||||
* ship source honours the same name / type selection.
|
||||
*/
|
||||
export function filterShipsByActiveFilters<T extends Pick<Ship, 'name' | 'type'>>(
|
||||
ships: T[] | undefined,
|
||||
activeFilters: Record<string, string[]> | undefined,
|
||||
): T[] {
|
||||
if (!ships) return [];
|
||||
const nameSet = activeFilters?.ship_name?.length ? new Set(activeFilters.ship_name) : null;
|
||||
const typeSet = activeFilters?.ship_type?.length ? new Set(activeFilters.ship_type) : null;
|
||||
if (!nameSet && !typeSet) return ships;
|
||||
return ships.filter((s) => {
|
||||
if (nameSet && !nameSet.has(s.name)) return false;
|
||||
if (typeSet && !typeSet.has(s.type)) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user