@squawk/adsbtop 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -0
- package/dist/aircraft-state.d.ts +77 -0
- package/dist/aircraft-state.d.ts.map +1 -0
- package/dist/aircraft-state.js +87 -0
- package/dist/app.d.ts +41 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +166 -0
- package/dist/cli-args.d.ts +43 -0
- package/dist/cli-args.d.ts.map +1 -0
- package/dist/cli-args.js +150 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +17 -0
- package/dist/columns.d.ts +84 -0
- package/dist/columns.d.ts.map +1 -0
- package/dist/columns.js +203 -0
- package/dist/components/aircraft-table.d.ts +27 -0
- package/dist/components/aircraft-table.d.ts.map +1 -0
- package/dist/components/aircraft-table.js +83 -0
- package/dist/components/detail-view.d.ts +20 -0
- package/dist/components/detail-view.d.ts.map +1 -0
- package/dist/components/detail-view.js +14 -0
- package/dist/components/help-overlay.d.ts +4 -0
- package/dist/components/help-overlay.d.ts.map +1 -0
- package/dist/components/help-overlay.js +22 -0
- package/dist/components/hotkey-bar.d.ts +21 -0
- package/dist/components/hotkey-bar.d.ts.map +1 -0
- package/dist/components/hotkey-bar.js +28 -0
- package/dist/components/messages-panel.d.ts +27 -0
- package/dist/components/messages-panel.d.ts.map +1 -0
- package/dist/components/messages-panel.js +17 -0
- package/dist/components/search-bar.d.ts +19 -0
- package/dist/components/search-bar.d.ts.map +1 -0
- package/dist/components/search-bar.js +13 -0
- package/dist/components/status-header.d.ts +17 -0
- package/dist/components/status-header.d.ts.map +1 -0
- package/dist/components/status-header.js +14 -0
- package/dist/create-feed.d.ts +33 -0
- package/dist/create-feed.d.ts.map +1 -0
- package/dist/create-feed.js +40 -0
- package/dist/detail-fields.d.ts +23 -0
- package/dist/detail-fields.d.ts.map +1 -0
- package/dist/detail-fields.js +68 -0
- package/dist/format.d.ts +91 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +138 -0
- package/dist/location.d.ts +20 -0
- package/dist/location.d.ts.map +1 -0
- package/dist/location.js +29 -0
- package/dist/registration-cache.d.ts +23 -0
- package/dist/registration-cache.d.ts.map +1 -0
- package/dist/registration-cache.js +24 -0
- package/dist/search.d.ts +27 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +54 -0
- package/dist/selection.d.ts +25 -0
- package/dist/selection.d.ts.map +1 -0
- package/dist/selection.js +38 -0
- package/dist/sparkline.d.ts +26 -0
- package/dist/sparkline.d.ts.map +1 -0
- package/dist/sparkline.js +62 -0
- package/dist/status-line.d.ts +28 -0
- package/dist/status-line.d.ts.map +1 -0
- package/dist/status-line.js +18 -0
- package/dist/test-utils.d.ts +23 -0
- package/dist/test-utils.d.ts.map +1 -0
- package/dist/test-utils.js +35 -0
- package/dist/use-aircraft-feed.d.ts +28 -0
- package/dist/use-aircraft-feed.d.ts.map +1 -0
- package/dist/use-aircraft-feed.js +68 -0
- package/dist/use-icao-registry.d.ts +27 -0
- package/dist/use-icao-registry.d.ts.map +1 -0
- package/dist/use-icao-registry.js +37 -0
- package/package.json +58 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import type { StatusLineInfo } from '../status-line.js';
|
|
3
|
+
/** Props for {@link StatusHeader}. */
|
|
4
|
+
export interface StatusHeaderProps extends StatusLineInfo {
|
|
5
|
+
/** Whether the table is currently paused. */
|
|
6
|
+
paused: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Connection and activity summary shown above the aircraft table:
|
|
10
|
+
* source/host/port, tracked aircraft count, message rate, and time since the
|
|
11
|
+
* last update, followed by a blank line (same background) to give the table
|
|
12
|
+
* header row below it some breathing room.
|
|
13
|
+
*
|
|
14
|
+
* @param props - The connection/activity state to display.
|
|
15
|
+
*/
|
|
16
|
+
export declare function StatusHeader(props: StatusHeaderProps): ReactElement;
|
|
17
|
+
//# sourceMappingURL=status-header.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status-header.d.ts","sourceRoot":"","sources":["../../src/components/status-header.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAG1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,sCAAsC;AACtC,MAAM,WAAW,iBAAkB,SAAQ,cAAc;IACvD,6CAA6C;IAC7C,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,CAiBnE"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { formatStatusLine } from '../status-line.js';
|
|
4
|
+
/**
|
|
5
|
+
* Connection and activity summary shown above the aircraft table:
|
|
6
|
+
* source/host/port, tracked aircraft count, message rate, and time since the
|
|
7
|
+
* last update, followed by a blank line (same background) to give the table
|
|
8
|
+
* header row below it some breathing room.
|
|
9
|
+
*
|
|
10
|
+
* @param props - The connection/activity state to display.
|
|
11
|
+
*/
|
|
12
|
+
export function StatusHeader(props) {
|
|
13
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { width: "100%", backgroundColor: "blue", children: [_jsx(Text, { bold: true, color: "white", children: 'adsbtop ' }), _jsx(Text, { color: "white", children: formatStatusLine(props) }), props.paused ? (_jsx(Text, { bold: true, color: "yellow", children: ' PAUSED' })) : undefined] }), _jsx(Box, { width: "100%", backgroundColor: "blue", height: 1 })] }));
|
|
14
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createBeastAircraftFeed, createJsonAircraftFeed, createSbsAircraftFeed } from '@squawk/adsb-feed';
|
|
2
|
+
import type { AircraftFeed } from '@squawk/adsb-feed';
|
|
3
|
+
import type { CliOptions } from './cli-args.js';
|
|
4
|
+
/** The three feed factories `buildFeed` chooses between, injectable so tests can substitute fakes without touching real sockets/HTTP. */
|
|
5
|
+
export interface FeedFactories {
|
|
6
|
+
/** Factory for the `json` source. */
|
|
7
|
+
createJsonAircraftFeed: typeof createJsonAircraftFeed;
|
|
8
|
+
/** Factory for the `sbs` source. */
|
|
9
|
+
createSbsAircraftFeed: typeof createSbsAircraftFeed;
|
|
10
|
+
/** Factory for the `beast` source. */
|
|
11
|
+
createBeastAircraftFeed: typeof createBeastAircraftFeed;
|
|
12
|
+
}
|
|
13
|
+
/** The real `@squawk/adsb-feed` factories, used outside of tests. */
|
|
14
|
+
export declare const DEFAULT_FEED_FACTORIES: FeedFactories;
|
|
15
|
+
/**
|
|
16
|
+
* Builds the `aircraft.json` URL for the `json` source from `--host`/`--port`,
|
|
17
|
+
* used unless `--url` was passed explicitly.
|
|
18
|
+
*
|
|
19
|
+
* @param host - Station hostname or IP address.
|
|
20
|
+
* @param port - HTTP port serving `aircraft.json`.
|
|
21
|
+
* @returns The full endpoint URL.
|
|
22
|
+
*/
|
|
23
|
+
export declare function buildJsonUrl(host: string, port: number): string;
|
|
24
|
+
/**
|
|
25
|
+
* Constructs the `AircraftFeed` for the CLI's selected source and connection
|
|
26
|
+
* options.
|
|
27
|
+
*
|
|
28
|
+
* @param cli - Parsed, validated CLI options (must have `help: false`).
|
|
29
|
+
* @param factories - Feed factories to use; defaults to the real `@squawk/adsb-feed` factories.
|
|
30
|
+
* @returns An `AircraftFeed` ready to `start()`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function buildFeed(cli: CliOptions, factories?: FeedFactories): AircraftFeed;
|
|
33
|
+
//# sourceMappingURL=create-feed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-feed.d.ts","sourceRoot":"","sources":["../src/create-feed.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,qBAAqB,EACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,yIAAyI;AACzI,MAAM,WAAW,aAAa;IAC5B,qCAAqC;IACrC,sBAAsB,EAAE,OAAO,sBAAsB,CAAC;IACtD,oCAAoC;IACpC,qBAAqB,EAAE,OAAO,qBAAqB,CAAC;IACpD,sCAAsC;IACtC,uBAAuB,EAAE,OAAO,uBAAuB,CAAC;CACzD;AAED,qEAAqE;AACrE,eAAO,MAAM,sBAAsB,EAAE,aAIpC,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,UAAU,EACf,SAAS,GAAE,aAAsC,GAChD,YAAY,CAad"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createBeastAircraftFeed, createJsonAircraftFeed, createSbsAircraftFeed, } from '@squawk/adsb-feed';
|
|
2
|
+
/** The real `@squawk/adsb-feed` factories, used outside of tests. */
|
|
3
|
+
export const DEFAULT_FEED_FACTORIES = {
|
|
4
|
+
createJsonAircraftFeed,
|
|
5
|
+
createSbsAircraftFeed,
|
|
6
|
+
createBeastAircraftFeed,
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Builds the `aircraft.json` URL for the `json` source from `--host`/`--port`,
|
|
10
|
+
* used unless `--url` was passed explicitly.
|
|
11
|
+
*
|
|
12
|
+
* @param host - Station hostname or IP address.
|
|
13
|
+
* @param port - HTTP port serving `aircraft.json`.
|
|
14
|
+
* @returns The full endpoint URL.
|
|
15
|
+
*/
|
|
16
|
+
export function buildJsonUrl(host, port) {
|
|
17
|
+
return `http://${host}:${port}/data/aircraft.json`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Constructs the `AircraftFeed` for the CLI's selected source and connection
|
|
21
|
+
* options.
|
|
22
|
+
*
|
|
23
|
+
* @param cli - Parsed, validated CLI options (must have `help: false`).
|
|
24
|
+
* @param factories - Feed factories to use; defaults to the real `@squawk/adsb-feed` factories.
|
|
25
|
+
* @returns An `AircraftFeed` ready to `start()`.
|
|
26
|
+
*/
|
|
27
|
+
export function buildFeed(cli, factories = DEFAULT_FEED_FACTORIES) {
|
|
28
|
+
switch (cli.source) {
|
|
29
|
+
case 'json':
|
|
30
|
+
return factories.createJsonAircraftFeed({ url: cli.url ?? buildJsonUrl(cli.host, cli.port) });
|
|
31
|
+
case 'sbs':
|
|
32
|
+
return factories.createSbsAircraftFeed({ host: cli.host, port: cli.port });
|
|
33
|
+
case 'beast':
|
|
34
|
+
return factories.createBeastAircraftFeed({
|
|
35
|
+
host: cli.host,
|
|
36
|
+
port: cli.port,
|
|
37
|
+
...(cli.location !== undefined ? { receiverPosition: cli.location } : {}),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Aircraft, Coordinates } from '@squawk/types';
|
|
2
|
+
/** One labeled row in the aircraft detail view. */
|
|
3
|
+
export interface DetailField {
|
|
4
|
+
/** Row label. */
|
|
5
|
+
label: string;
|
|
6
|
+
/** Formatted value, or `'-'` if not populated by the current source. */
|
|
7
|
+
value: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Builds the full labeled field list for the `[Enter]/[D]etail` view: every
|
|
11
|
+
* `Aircraft` field, in a fixed order, formatted for display with `'-'` for
|
|
12
|
+
* anything the active source hasn't populated. Deliberately does not
|
|
13
|
+
* collapse baro/geo altitude or true/magnetic heading into one value the way
|
|
14
|
+
* the table's compact columns do - showing both is the point of a "full
|
|
15
|
+
* field dump" view.
|
|
16
|
+
*
|
|
17
|
+
* @param aircraft - The selected aircraft to build fields for.
|
|
18
|
+
* @param nowMs - Current time, for the "last seen" age.
|
|
19
|
+
* @param location - The configured receiver location (`--lat`/`--lon`), if any. Adds Distance/Bearing rows after Position when set; omitted entirely otherwise, matching the table's Dist/Brg columns.
|
|
20
|
+
* @returns Labeled rows in display order.
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildDetailFields(aircraft: Aircraft, nowMs: number, location: Coordinates | undefined): DetailField[];
|
|
23
|
+
//# sourceMappingURL=detail-fields.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detail-fields.d.ts","sourceRoot":"","sources":["../src/detail-fields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAiC,WAAW,EAAY,MAAM,eAAe,CAAC;AAYpG,mDAAmD;AACnD,MAAM,WAAW,WAAW;IAC1B,iBAAiB;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;CACf;AAgCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,WAAW,GAAG,SAAS,GAChC,WAAW,EAAE,CA8Bf"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { formatAge, formatBearing, formatDistance, formatGroundSpeed, formatOnGround, formatVerticalRate, } from './format.js';
|
|
2
|
+
import { bearingToAircraftDeg, distanceToAircraftNm } from './location.js';
|
|
3
|
+
function formatFeet(valueFt) {
|
|
4
|
+
return valueFt === undefined ? '-' : `${Math.round(valueFt)}ft`;
|
|
5
|
+
}
|
|
6
|
+
function formatKnots(valueKt) {
|
|
7
|
+
return valueKt === undefined ? '-' : `${Math.round(valueKt)}kt`;
|
|
8
|
+
}
|
|
9
|
+
function formatDegrees(valueDeg) {
|
|
10
|
+
return valueDeg === undefined ? '-' : `${Math.round(valueDeg)}°`;
|
|
11
|
+
}
|
|
12
|
+
function formatPosition(position) {
|
|
13
|
+
return position === undefined ? '-' : `${position.lat.toFixed(4)}, ${position.lon.toFixed(4)}`;
|
|
14
|
+
}
|
|
15
|
+
function formatRegistration(registration) {
|
|
16
|
+
if (registration === undefined) {
|
|
17
|
+
return '-';
|
|
18
|
+
}
|
|
19
|
+
const makeModel = [registration.make, registration.model]
|
|
20
|
+
.filter((part) => part !== undefined)
|
|
21
|
+
.join(' ');
|
|
22
|
+
return makeModel === '' ? registration.registration : `${registration.registration} ${makeModel}`;
|
|
23
|
+
}
|
|
24
|
+
function formatAirport(airport) {
|
|
25
|
+
return airport === undefined ? '-' : (airport.icao ?? airport.faaId);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Builds the full labeled field list for the `[Enter]/[D]etail` view: every
|
|
29
|
+
* `Aircraft` field, in a fixed order, formatted for display with `'-'` for
|
|
30
|
+
* anything the active source hasn't populated. Deliberately does not
|
|
31
|
+
* collapse baro/geo altitude or true/magnetic heading into one value the way
|
|
32
|
+
* the table's compact columns do - showing both is the point of a "full
|
|
33
|
+
* field dump" view.
|
|
34
|
+
*
|
|
35
|
+
* @param aircraft - The selected aircraft to build fields for.
|
|
36
|
+
* @param nowMs - Current time, for the "last seen" age.
|
|
37
|
+
* @param location - The configured receiver location (`--lat`/`--lon`), if any. Adds Distance/Bearing rows after Position when set; omitted entirely otherwise, matching the table's Dist/Brg columns.
|
|
38
|
+
* @returns Labeled rows in display order.
|
|
39
|
+
*/
|
|
40
|
+
export function buildDetailFields(aircraft, nowMs, location) {
|
|
41
|
+
const locationFields = location === undefined
|
|
42
|
+
? []
|
|
43
|
+
: [
|
|
44
|
+
{ label: 'Distance', value: formatDistance(distanceToAircraftNm(location, aircraft)) },
|
|
45
|
+
{ label: 'Bearing', value: formatBearing(bearingToAircraftDeg(location, aircraft)) },
|
|
46
|
+
];
|
|
47
|
+
return [
|
|
48
|
+
{ label: 'ICAO', value: aircraft.icaoHex },
|
|
49
|
+
{ label: 'Callsign', value: aircraft.callsign ?? '-' },
|
|
50
|
+
{ label: 'Squawk', value: aircraft.squawk ?? '-' },
|
|
51
|
+
{ label: 'Registration', value: formatRegistration(aircraft.registration) },
|
|
52
|
+
{ label: 'Position', value: formatPosition(aircraft.position) },
|
|
53
|
+
...locationFields,
|
|
54
|
+
{ label: 'Baro altitude', value: formatFeet(aircraft.position?.baroAltitudeFt) },
|
|
55
|
+
{ label: 'Geo altitude', value: formatFeet(aircraft.position?.geoAltitudeFt) },
|
|
56
|
+
{ label: 'Ground speed', value: formatGroundSpeed(aircraft) },
|
|
57
|
+
{ label: 'Indicated airspeed', value: formatKnots(aircraft.indicatedAirspeedKt) },
|
|
58
|
+
{ label: 'True airspeed', value: formatKnots(aircraft.trueAirspeedKt) },
|
|
59
|
+
{ label: 'True track', value: formatDegrees(aircraft.trueTrackDeg) },
|
|
60
|
+
{ label: 'Magnetic heading', value: formatDegrees(aircraft.magneticHeadingDeg) },
|
|
61
|
+
{ label: 'Vertical rate', value: formatVerticalRate(aircraft) },
|
|
62
|
+
{ label: 'On ground', value: formatOnGround(aircraft) },
|
|
63
|
+
{ label: 'Category', value: aircraft.category ?? '-' },
|
|
64
|
+
{ label: 'Origin', value: formatAirport(aircraft.origin) },
|
|
65
|
+
{ label: 'Destination', value: formatAirport(aircraft.destination) },
|
|
66
|
+
{ label: 'Last seen', value: `${formatAge(aircraft.lastSeenAt, nowMs)} ago` },
|
|
67
|
+
];
|
|
68
|
+
}
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Aircraft } from '@squawk/types';
|
|
2
|
+
import type { MessageLogEntry } from './aircraft-state.js';
|
|
3
|
+
/**
|
|
4
|
+
* Whether `squawk` is one of the three universally-reserved emergency codes
|
|
5
|
+
* (hijack/radio failure/general emergency). Works identically across all
|
|
6
|
+
* three feed sources, since `squawk` is populated uniformly by JSON, SBS,
|
|
7
|
+
* and Beast.
|
|
8
|
+
*
|
|
9
|
+
* @param squawk - The aircraft's current squawk code, if known.
|
|
10
|
+
* @returns True if `squawk` is 7500, 7600, or 7700.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isEmergencySquawk(squawk: string | undefined): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Formats an aircraft's altitude for table display: barometric altitude
|
|
15
|
+
* preferred, geometric (GNSS) altitude as a fallback when barometric is
|
|
16
|
+
* unavailable. Does not special-case on-ground aircraft - see {@link formatOnGround}
|
|
17
|
+
* for that indicator.
|
|
18
|
+
*
|
|
19
|
+
* @param aircraft - The aircraft to read altitude from.
|
|
20
|
+
* @returns The altitude in feet with a unit suffix, or `"-"` if neither field is populated.
|
|
21
|
+
*/
|
|
22
|
+
export declare function formatAltitude(aircraft: Aircraft): string;
|
|
23
|
+
/**
|
|
24
|
+
* Formats an aircraft's on-ground state for its own table column, separate
|
|
25
|
+
* from altitude. Deliberately blank (not e.g. "AIR") when airborne or
|
|
26
|
+
* unknown, so the column only draws attention when the indicator is true.
|
|
27
|
+
*
|
|
28
|
+
* @param aircraft - The aircraft to read on-ground status from.
|
|
29
|
+
* @returns `"GND"` if `onGround` is true, otherwise `"-"`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function formatOnGround(aircraft: Aircraft): string;
|
|
32
|
+
/**
|
|
33
|
+
* Formats an aircraft's heading for table display: true track preferred,
|
|
34
|
+
* magnetic heading as a fallback when true track is unavailable.
|
|
35
|
+
*
|
|
36
|
+
* @param aircraft - The aircraft to read heading from.
|
|
37
|
+
* @returns The heading in degrees with a trailing degree sign, or `"-"` if neither field is populated.
|
|
38
|
+
*/
|
|
39
|
+
export declare function formatHeading(aircraft: Aircraft): string;
|
|
40
|
+
/**
|
|
41
|
+
* Formats an aircraft's ground speed for table display.
|
|
42
|
+
*
|
|
43
|
+
* @param aircraft - The aircraft to read ground speed from.
|
|
44
|
+
* @returns The ground speed in knots with a unit suffix, or `"-"` if unavailable.
|
|
45
|
+
*/
|
|
46
|
+
export declare function formatGroundSpeed(aircraft: Aircraft): string;
|
|
47
|
+
/**
|
|
48
|
+
* Formats a great-circle distance for table display.
|
|
49
|
+
*
|
|
50
|
+
* @param distanceNm - Distance in nautical miles, or undefined if not computable.
|
|
51
|
+
* @returns The distance in nautical miles with a unit suffix, or `"-"` if undefined.
|
|
52
|
+
*/
|
|
53
|
+
export declare function formatDistance(distanceNm: number | undefined): string;
|
|
54
|
+
/**
|
|
55
|
+
* Formats a great-circle bearing for table display, matching {@link formatHeading}'s style.
|
|
56
|
+
*
|
|
57
|
+
* @param bearingDeg - Bearing in degrees true, or undefined if not computable.
|
|
58
|
+
* @returns The bearing in degrees with a trailing degree sign, or `"-"` if undefined.
|
|
59
|
+
*/
|
|
60
|
+
export declare function formatBearing(bearingDeg: number | undefined): string;
|
|
61
|
+
/**
|
|
62
|
+
* Formats an aircraft's vertical rate for table display, with an explicit
|
|
63
|
+
* `+` sign on climbs so climb/descend is visible without color.
|
|
64
|
+
*
|
|
65
|
+
* @param aircraft - The aircraft to read vertical rate from.
|
|
66
|
+
* @returns The vertical rate in feet per minute with a unit suffix, or `"-"` if unavailable.
|
|
67
|
+
*/
|
|
68
|
+
export declare function formatVerticalRate(aircraft: Aircraft): string;
|
|
69
|
+
/**
|
|
70
|
+
* Formats the time elapsed since `lastSeenAt` for the table's age column,
|
|
71
|
+
* escalating precision as the value grows: seconds, then minutes/seconds,
|
|
72
|
+
* then hours/minutes.
|
|
73
|
+
*
|
|
74
|
+
* @param lastSeenAt - Unix epoch ms the aircraft was last updated.
|
|
75
|
+
* @param nowMs - Unix epoch ms to measure elapsed time against, supplied by the caller so this stays pure and testable.
|
|
76
|
+
* @returns A short elapsed-time string, e.g. `"3s"`, `"1m05s"`, or `"2h03m"`.
|
|
77
|
+
*/
|
|
78
|
+
export declare function formatAge(lastSeenAt: number, nowMs: number): string;
|
|
79
|
+
/**
|
|
80
|
+
* Formats one {@link MessageLogEntry} for the `[M]essages` panel.
|
|
81
|
+
*
|
|
82
|
+
* The clock is rendered in UTC (`HH:MM:SS`) rather than local time so log
|
|
83
|
+
* output is deterministic in tests regardless of the runner's timezone -
|
|
84
|
+
* for a live-scrolling log the UTC/local distinction isn't meaningful to a
|
|
85
|
+
* user watching it update in real time.
|
|
86
|
+
*
|
|
87
|
+
* @param entry - The log entry to format.
|
|
88
|
+
* @returns A single display line, e.g. `"14:23:05 NEW A0B1C2 UAL123"`.
|
|
89
|
+
*/
|
|
90
|
+
export declare function formatMessageLogLine(entry: MessageLogEntry): string;
|
|
91
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAK3D;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAErE;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAGzD;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAEzD;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAGxD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAE5D;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAErE;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAEpE;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAQ7D;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAanE;AASD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAKnE"}
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/** Squawk codes that always indicate a declared emergency, regardless of source. */
|
|
2
|
+
const EMERGENCY_SQUAWKS = new Set(['7500', '7600', '7700']);
|
|
3
|
+
/**
|
|
4
|
+
* Whether `squawk` is one of the three universally-reserved emergency codes
|
|
5
|
+
* (hijack/radio failure/general emergency). Works identically across all
|
|
6
|
+
* three feed sources, since `squawk` is populated uniformly by JSON, SBS,
|
|
7
|
+
* and Beast.
|
|
8
|
+
*
|
|
9
|
+
* @param squawk - The aircraft's current squawk code, if known.
|
|
10
|
+
* @returns True if `squawk` is 7500, 7600, or 7700.
|
|
11
|
+
*/
|
|
12
|
+
export function isEmergencySquawk(squawk) {
|
|
13
|
+
return squawk !== undefined && EMERGENCY_SQUAWKS.has(squawk);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Formats an aircraft's altitude for table display: barometric altitude
|
|
17
|
+
* preferred, geometric (GNSS) altitude as a fallback when barometric is
|
|
18
|
+
* unavailable. Does not special-case on-ground aircraft - see {@link formatOnGround}
|
|
19
|
+
* for that indicator.
|
|
20
|
+
*
|
|
21
|
+
* @param aircraft - The aircraft to read altitude from.
|
|
22
|
+
* @returns The altitude in feet with a unit suffix, or `"-"` if neither field is populated.
|
|
23
|
+
*/
|
|
24
|
+
export function formatAltitude(aircraft) {
|
|
25
|
+
const altitudeFt = aircraft.position?.baroAltitudeFt ?? aircraft.position?.geoAltitudeFt;
|
|
26
|
+
return altitudeFt === undefined ? '-' : `${Math.round(altitudeFt)}ft`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Formats an aircraft's on-ground state for its own table column, separate
|
|
30
|
+
* from altitude. Deliberately blank (not e.g. "AIR") when airborne or
|
|
31
|
+
* unknown, so the column only draws attention when the indicator is true.
|
|
32
|
+
*
|
|
33
|
+
* @param aircraft - The aircraft to read on-ground status from.
|
|
34
|
+
* @returns `"GND"` if `onGround` is true, otherwise `"-"`.
|
|
35
|
+
*/
|
|
36
|
+
export function formatOnGround(aircraft) {
|
|
37
|
+
return aircraft.onGround === true ? 'GND' : '-';
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Formats an aircraft's heading for table display: true track preferred,
|
|
41
|
+
* magnetic heading as a fallback when true track is unavailable.
|
|
42
|
+
*
|
|
43
|
+
* @param aircraft - The aircraft to read heading from.
|
|
44
|
+
* @returns The heading in degrees with a trailing degree sign, or `"-"` if neither field is populated.
|
|
45
|
+
*/
|
|
46
|
+
export function formatHeading(aircraft) {
|
|
47
|
+
const headingDeg = aircraft.trueTrackDeg ?? aircraft.magneticHeadingDeg;
|
|
48
|
+
return headingDeg === undefined ? '-' : `${Math.round(headingDeg)}°`;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Formats an aircraft's ground speed for table display.
|
|
52
|
+
*
|
|
53
|
+
* @param aircraft - The aircraft to read ground speed from.
|
|
54
|
+
* @returns The ground speed in knots with a unit suffix, or `"-"` if unavailable.
|
|
55
|
+
*/
|
|
56
|
+
export function formatGroundSpeed(aircraft) {
|
|
57
|
+
return aircraft.groundSpeedKt === undefined ? '-' : `${Math.round(aircraft.groundSpeedKt)}kt`;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Formats a great-circle distance for table display.
|
|
61
|
+
*
|
|
62
|
+
* @param distanceNm - Distance in nautical miles, or undefined if not computable.
|
|
63
|
+
* @returns The distance in nautical miles with a unit suffix, or `"-"` if undefined.
|
|
64
|
+
*/
|
|
65
|
+
export function formatDistance(distanceNm) {
|
|
66
|
+
return distanceNm === undefined ? '-' : `${Math.round(distanceNm)}nm`;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Formats a great-circle bearing for table display, matching {@link formatHeading}'s style.
|
|
70
|
+
*
|
|
71
|
+
* @param bearingDeg - Bearing in degrees true, or undefined if not computable.
|
|
72
|
+
* @returns The bearing in degrees with a trailing degree sign, or `"-"` if undefined.
|
|
73
|
+
*/
|
|
74
|
+
export function formatBearing(bearingDeg) {
|
|
75
|
+
return bearingDeg === undefined ? '-' : `${Math.round(bearingDeg)}°`;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Formats an aircraft's vertical rate for table display, with an explicit
|
|
79
|
+
* `+` sign on climbs so climb/descend is visible without color.
|
|
80
|
+
*
|
|
81
|
+
* @param aircraft - The aircraft to read vertical rate from.
|
|
82
|
+
* @returns The vertical rate in feet per minute with a unit suffix, or `"-"` if unavailable.
|
|
83
|
+
*/
|
|
84
|
+
export function formatVerticalRate(aircraft) {
|
|
85
|
+
const rate = aircraft.verticalRateFtPerMin;
|
|
86
|
+
if (rate === undefined) {
|
|
87
|
+
return '-';
|
|
88
|
+
}
|
|
89
|
+
const rounded = Math.round(rate);
|
|
90
|
+
const sign = rounded > 0 ? '+' : '';
|
|
91
|
+
return `${sign}${rounded}fpm`;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Formats the time elapsed since `lastSeenAt` for the table's age column,
|
|
95
|
+
* escalating precision as the value grows: seconds, then minutes/seconds,
|
|
96
|
+
* then hours/minutes.
|
|
97
|
+
*
|
|
98
|
+
* @param lastSeenAt - Unix epoch ms the aircraft was last updated.
|
|
99
|
+
* @param nowMs - Unix epoch ms to measure elapsed time against, supplied by the caller so this stays pure and testable.
|
|
100
|
+
* @returns A short elapsed-time string, e.g. `"3s"`, `"1m05s"`, or `"2h03m"`.
|
|
101
|
+
*/
|
|
102
|
+
export function formatAge(lastSeenAt, nowMs) {
|
|
103
|
+
const elapsedSec = Math.max(0, Math.floor((nowMs - lastSeenAt) / 1000));
|
|
104
|
+
if (elapsedSec < 60) {
|
|
105
|
+
return `${elapsedSec}s`;
|
|
106
|
+
}
|
|
107
|
+
const minutes = Math.floor(elapsedSec / 60);
|
|
108
|
+
const seconds = elapsedSec % 60;
|
|
109
|
+
if (minutes < 60) {
|
|
110
|
+
return `${minutes}m${seconds.toString().padStart(2, '0')}s`;
|
|
111
|
+
}
|
|
112
|
+
const hours = Math.floor(minutes / 60);
|
|
113
|
+
const remainingMinutes = minutes % 60;
|
|
114
|
+
return `${hours}h${remainingMinutes.toString().padStart(2, '0')}m`;
|
|
115
|
+
}
|
|
116
|
+
/** Fixed-width label per {@link MessageLogEntry.type}, for column alignment in the messages panel. */
|
|
117
|
+
const MESSAGE_LOG_LABELS = {
|
|
118
|
+
new: 'NEW ',
|
|
119
|
+
update: 'UPDT',
|
|
120
|
+
lost: 'LOST',
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Formats one {@link MessageLogEntry} for the `[M]essages` panel.
|
|
124
|
+
*
|
|
125
|
+
* The clock is rendered in UTC (`HH:MM:SS`) rather than local time so log
|
|
126
|
+
* output is deterministic in tests regardless of the runner's timezone -
|
|
127
|
+
* for a live-scrolling log the UTC/local distinction isn't meaningful to a
|
|
128
|
+
* user watching it update in real time.
|
|
129
|
+
*
|
|
130
|
+
* @param entry - The log entry to format.
|
|
131
|
+
* @returns A single display line, e.g. `"14:23:05 NEW A0B1C2 UAL123"`.
|
|
132
|
+
*/
|
|
133
|
+
export function formatMessageLogLine(entry) {
|
|
134
|
+
const time = new Date(entry.at).toISOString().slice(11, 19);
|
|
135
|
+
const label = MESSAGE_LOG_LABELS[entry.type];
|
|
136
|
+
const callsign = entry.callsign ?? '-';
|
|
137
|
+
return `${time} ${label} ${entry.icaoHex} ${callsign}`;
|
|
138
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Aircraft, Coordinates } from '@squawk/types';
|
|
2
|
+
/**
|
|
3
|
+
* Distance in nautical miles from a fixed observer `location` to `aircraft`'s
|
|
4
|
+
* current position, for the Dist column.
|
|
5
|
+
*
|
|
6
|
+
* @param location - The configured receiver location (`--lat`/`--lon`).
|
|
7
|
+
* @param aircraft - The aircraft to measure distance to.
|
|
8
|
+
* @returns The great-circle distance in nautical miles, or undefined if the aircraft has no position yet.
|
|
9
|
+
*/
|
|
10
|
+
export declare function distanceToAircraftNm(location: Coordinates, aircraft: Aircraft): number | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Initial great-circle bearing in degrees true from a fixed observer
|
|
13
|
+
* `location` to `aircraft`'s current position, for the Brg column.
|
|
14
|
+
*
|
|
15
|
+
* @param location - The configured receiver location (`--lat`/`--lon`).
|
|
16
|
+
* @param aircraft - The aircraft to measure bearing to.
|
|
17
|
+
* @returns The bearing in degrees true (0-360), or undefined if the aircraft has no position yet.
|
|
18
|
+
*/
|
|
19
|
+
export declare function bearingToAircraftDeg(location: Coordinates, aircraft: Aircraft): number | undefined;
|
|
20
|
+
//# sourceMappingURL=location.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"location.d.ts","sourceRoot":"","sources":["../src/location.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE3D;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,WAAW,EACrB,QAAQ,EAAE,QAAQ,GACjB,MAAM,GAAG,SAAS,CAKpB;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,WAAW,EACrB,QAAQ,EAAE,QAAQ,GACjB,MAAM,GAAG,SAAS,CAKpB"}
|
package/dist/location.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { greatCircle } from '@squawk/geo';
|
|
2
|
+
/**
|
|
3
|
+
* Distance in nautical miles from a fixed observer `location` to `aircraft`'s
|
|
4
|
+
* current position, for the Dist column.
|
|
5
|
+
*
|
|
6
|
+
* @param location - The configured receiver location (`--lat`/`--lon`).
|
|
7
|
+
* @param aircraft - The aircraft to measure distance to.
|
|
8
|
+
* @returns The great-circle distance in nautical miles, or undefined if the aircraft has no position yet.
|
|
9
|
+
*/
|
|
10
|
+
export function distanceToAircraftNm(location, aircraft) {
|
|
11
|
+
const position = aircraft.position;
|
|
12
|
+
return position === undefined
|
|
13
|
+
? undefined
|
|
14
|
+
: greatCircle.distanceNm(location.lat, location.lon, position.lat, position.lon);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Initial great-circle bearing in degrees true from a fixed observer
|
|
18
|
+
* `location` to `aircraft`'s current position, for the Brg column.
|
|
19
|
+
*
|
|
20
|
+
* @param location - The configured receiver location (`--lat`/`--lon`).
|
|
21
|
+
* @param aircraft - The aircraft to measure bearing to.
|
|
22
|
+
* @returns The bearing in degrees true (0-360), or undefined if the aircraft has no position yet.
|
|
23
|
+
*/
|
|
24
|
+
export function bearingToAircraftDeg(location, aircraft) {
|
|
25
|
+
const position = aircraft.position;
|
|
26
|
+
return position === undefined
|
|
27
|
+
? undefined
|
|
28
|
+
: greatCircle.bearing(location.lat, location.lon, position.lat, position.lon);
|
|
29
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { IcaoRegistry } from '@squawk/icao-registry';
|
|
2
|
+
import type { Aircraft, AircraftRegistration } from '@squawk/types';
|
|
3
|
+
/**
|
|
4
|
+
* Per-`icaoHex` memoization cache for {@link enrichAircraftList} lookups.
|
|
5
|
+
* Stores `undefined` for a resolved miss too, so a hex with no registry
|
|
6
|
+
* match isn't looked up again on every render - an aircraft's registration
|
|
7
|
+
* never changes for the lifetime of a tracked session.
|
|
8
|
+
*/
|
|
9
|
+
export type RegistrationCache = Map<string, AircraftRegistration | undefined>;
|
|
10
|
+
/**
|
|
11
|
+
* Returns a new array with each aircraft's `registration` field populated
|
|
12
|
+
* from `registry`, memoizing lookups in `cache` by `icaoHex` so a given hex
|
|
13
|
+
* is only resolved once regardless of how many times this runs. `adsb-feed`
|
|
14
|
+
* never populates `Aircraft.registration` itself, so this is adsbtop's own
|
|
15
|
+
* enrichment step, applied client-side after the feed's own normalization.
|
|
16
|
+
*
|
|
17
|
+
* @param aircraft - The aircraft to enrich, in any order.
|
|
18
|
+
* @param registry - The loaded registry to query, or undefined while it's still loading - aircraft pass through unchanged in that case.
|
|
19
|
+
* @param cache - Mutated in place with any newly-resolved lookups.
|
|
20
|
+
* @returns A new array; aircraft with no registry match keep their original (unmodified) object reference.
|
|
21
|
+
*/
|
|
22
|
+
export declare function enrichAircraftList(aircraft: readonly Aircraft[], registry: IcaoRegistry | undefined, cache: RegistrationCache): Aircraft[];
|
|
23
|
+
//# sourceMappingURL=registration-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registration-cache.d.ts","sourceRoot":"","sources":["../src/registration-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAEpE;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,GAAG,CAAC,MAAM,EAAE,oBAAoB,GAAG,SAAS,CAAC,CAAC;AAE9E;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,SAAS,QAAQ,EAAE,EAC7B,QAAQ,EAAE,YAAY,GAAG,SAAS,EAClC,KAAK,EAAE,iBAAiB,GACvB,QAAQ,EAAE,CAWZ"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a new array with each aircraft's `registration` field populated
|
|
3
|
+
* from `registry`, memoizing lookups in `cache` by `icaoHex` so a given hex
|
|
4
|
+
* is only resolved once regardless of how many times this runs. `adsb-feed`
|
|
5
|
+
* never populates `Aircraft.registration` itself, so this is adsbtop's own
|
|
6
|
+
* enrichment step, applied client-side after the feed's own normalization.
|
|
7
|
+
*
|
|
8
|
+
* @param aircraft - The aircraft to enrich, in any order.
|
|
9
|
+
* @param registry - The loaded registry to query, or undefined while it's still loading - aircraft pass through unchanged in that case.
|
|
10
|
+
* @param cache - Mutated in place with any newly-resolved lookups.
|
|
11
|
+
* @returns A new array; aircraft with no registry match keep their original (unmodified) object reference.
|
|
12
|
+
*/
|
|
13
|
+
export function enrichAircraftList(aircraft, registry, cache) {
|
|
14
|
+
if (registry === undefined) {
|
|
15
|
+
return [...aircraft];
|
|
16
|
+
}
|
|
17
|
+
return aircraft.map((entry) => {
|
|
18
|
+
if (!cache.has(entry.icaoHex)) {
|
|
19
|
+
cache.set(entry.icaoHex, registry.lookup(entry.icaoHex));
|
|
20
|
+
}
|
|
21
|
+
const registration = cache.get(entry.icaoHex);
|
|
22
|
+
return registration === undefined ? entry : { ...entry, registration };
|
|
23
|
+
});
|
|
24
|
+
}
|
package/dist/search.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Aircraft } from '@squawk/types';
|
|
2
|
+
/**
|
|
3
|
+
* Whether `aircraft` matches `query` (case-insensitive substring match
|
|
4
|
+
* against ICAO hex, callsign, squawk, or N-number/registration). An empty or
|
|
5
|
+
* all-whitespace query never matches, so an unsubmitted search box selects
|
|
6
|
+
* nothing.
|
|
7
|
+
*
|
|
8
|
+
* @param aircraft - The aircraft to test.
|
|
9
|
+
* @param query - The search text.
|
|
10
|
+
* @returns True if `query` is a substring of the aircraft's hex, callsign, squawk, or registration.
|
|
11
|
+
*/
|
|
12
|
+
export declare function matchesSearch(aircraft: Aircraft, query: string): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Finds the next aircraft matching `query`, searching from just after
|
|
15
|
+
* `currentIcaoHex` in `direction` and wrapping around the ends of `aircraft`.
|
|
16
|
+
* Used both for the initial search submit (searching forward from whatever
|
|
17
|
+
* row is currently selected) and for cycling to the next/previous match with
|
|
18
|
+
* `[N]`/`[Shift+N]`.
|
|
19
|
+
*
|
|
20
|
+
* @param aircraft - The currently displayed aircraft, in display order.
|
|
21
|
+
* @param query - The search text.
|
|
22
|
+
* @param currentIcaoHex - The currently selected aircraft's ICAO hex, or undefined if none is selected.
|
|
23
|
+
* @param direction - 1 to search forward (next match), -1 to search backward (previous match).
|
|
24
|
+
* @returns The ICAO hex of the next match, or undefined if `query` matches nothing.
|
|
25
|
+
*/
|
|
26
|
+
export declare function findMatchIcaoHex(aircraft: readonly Aircraft[], query: string, currentIcaoHex: string | undefined, direction: 1 | -1): string | undefined;
|
|
27
|
+
//# sourceMappingURL=search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAWxE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,SAAS,QAAQ,EAAE,EAC7B,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC,GAChB,MAAM,GAAG,SAAS,CAwBpB"}
|
package/dist/search.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether `aircraft` matches `query` (case-insensitive substring match
|
|
3
|
+
* against ICAO hex, callsign, squawk, or N-number/registration). An empty or
|
|
4
|
+
* all-whitespace query never matches, so an unsubmitted search box selects
|
|
5
|
+
* nothing.
|
|
6
|
+
*
|
|
7
|
+
* @param aircraft - The aircraft to test.
|
|
8
|
+
* @param query - The search text.
|
|
9
|
+
* @returns True if `query` is a substring of the aircraft's hex, callsign, squawk, or registration.
|
|
10
|
+
*/
|
|
11
|
+
export function matchesSearch(aircraft, query) {
|
|
12
|
+
const normalized = query.trim().toLowerCase();
|
|
13
|
+
if (normalized === '') {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return (aircraft.icaoHex.toLowerCase().includes(normalized) ||
|
|
17
|
+
(aircraft.callsign?.toLowerCase().includes(normalized) ?? false) ||
|
|
18
|
+
(aircraft.squawk?.toLowerCase().includes(normalized) ?? false) ||
|
|
19
|
+
(aircraft.registration?.registration.toLowerCase().includes(normalized) ?? false));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Finds the next aircraft matching `query`, searching from just after
|
|
23
|
+
* `currentIcaoHex` in `direction` and wrapping around the ends of `aircraft`.
|
|
24
|
+
* Used both for the initial search submit (searching forward from whatever
|
|
25
|
+
* row is currently selected) and for cycling to the next/previous match with
|
|
26
|
+
* `[N]`/`[Shift+N]`.
|
|
27
|
+
*
|
|
28
|
+
* @param aircraft - The currently displayed aircraft, in display order.
|
|
29
|
+
* @param query - The search text.
|
|
30
|
+
* @param currentIcaoHex - The currently selected aircraft's ICAO hex, or undefined if none is selected.
|
|
31
|
+
* @param direction - 1 to search forward (next match), -1 to search backward (previous match).
|
|
32
|
+
* @returns The ICAO hex of the next match, or undefined if `query` matches nothing.
|
|
33
|
+
*/
|
|
34
|
+
export function findMatchIcaoHex(aircraft, query, currentIcaoHex, direction) {
|
|
35
|
+
const matchIndices = [];
|
|
36
|
+
aircraft.forEach((candidate, index) => {
|
|
37
|
+
if (matchesSearch(candidate, query)) {
|
|
38
|
+
matchIndices.push(index);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
const firstMatchIndex = matchIndices[0];
|
|
42
|
+
const lastMatchIndex = matchIndices[matchIndices.length - 1];
|
|
43
|
+
if (firstMatchIndex === undefined || lastMatchIndex === undefined) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
const currentIndex = currentIcaoHex === undefined ? -1 : aircraft.findIndex((a) => a.icaoHex === currentIcaoHex);
|
|
47
|
+
if (direction === 1) {
|
|
48
|
+
const next = matchIndices.find((index) => index > currentIndex) ?? firstMatchIndex;
|
|
49
|
+
return aircraft[next]?.icaoHex;
|
|
50
|
+
}
|
|
51
|
+
const reversedMatches = [...matchIndices].reverse();
|
|
52
|
+
const previous = reversedMatches.find((index) => index < currentIndex) ?? lastMatchIndex;
|
|
53
|
+
return aircraft[previous]?.icaoHex;
|
|
54
|
+
}
|