Skip to content

Commit 18733e8

Browse files
feat: amdb
1 parent a1a4cb8 commit 18733e8

File tree

10 files changed

+173
-9
lines changed

10 files changed

+173
-9
lines changed

examples/playground/global.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
declare global {
2+
interface ObjectConstructor {
3+
entries<Key extends PropertyKey, Value>(obj: Record<Key, Value>): [Key, Value][];
4+
}
5+
}

examples/playground/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"preview": "vite preview"
1111
},
1212
"dependencies": {
13+
"@navigraph/amdb": "*",
1314
"@navigraph/app": "*",
1415
"@navigraph/auth": "*",
1516
"@navigraph/charts": "*",

examples/playground/src/Root.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import App from "./pages/App"
77
import Auth from "./pages/Auth"
88
import Tiles from "./pages/Tiles"
99
import Charts from "./pages/Charts"
10+
import Amdb from "./pages/Amdb"
1011

1112
function Root() {
1213
useAppConfigLoader();
@@ -20,6 +21,7 @@ function Root() {
2021
<Route path="/auth" element={<Auth />} />
2122
<Route path="/tiles" element={<Tiles />} />
2223
<Route path="/charts" element={<Charts />} />
24+
<Route path="/amdb/*" element={<Amdb />} />
2325
</Routes>
2426
<Outlet />
2527
<MainWindow />
Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1-
export default function JsonView({ content }: { content: any }) {
1+
import clsx from "clsx";
2+
3+
interface Props {
4+
content: any;
5+
onClick?: () => void;
6+
}
7+
8+
export default function JsonView({ content, onClick }: Props) {
29
return (
3-
<div className="pane overflow-auto w-full no-scrollbar">
4-
<pre className="text-white text-xs">{JSON.stringify(content, null, 2)}</pre>
10+
<div className={clsx("pane overflow-auto w-full no-scrollbar", onClick && 'cursor-pointer')}>
11+
<pre className={clsx("text-white text-xs", onClick && 'hover:text-gray-50')}>{JSON.stringify(content, null, 2)}</pre>
512
</div>
613
)
714
}

examples/playground/src/components/Map.tsx

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { MapContainer, useMap, TileLayer, ImageOverlay } from "react-leaflet";
2-
import { useEffect, useMemo, useRef } from "react";
1+
import { MapContainer, useMap, TileLayer, ImageOverlay, GeoJSON, Popup } from "react-leaflet";
2+
import { memo, useEffect, useMemo, useRef } from "react";
33
import { LatLngBounds, Map } from "leaflet";
44
import { useRecoilState, useRecoilValue } from "recoil";
55
import { appState } from "../state/app";
@@ -16,6 +16,8 @@ import { useQuery } from "@tanstack/react-query";
1616
import { protectedPage } from "./protectedPage";
1717
import { TbCircleX } from "react-icons/tb";
1818
import Button from "./Button";
19+
import { AmdbLayerName } from "@navigraph/amdb";
20+
import { amdbLayersState } from "../state/amdb";
1921

2022
export function createPreset(source: NavigraphRasterSource, theme: NavigraphTheme, faa: boolean, tac: boolean): PresetConfig {
2123
if (source === 'WORLD') {
@@ -28,6 +30,27 @@ export function createPreset(source: NavigraphRasterSource, theme: NavigraphThem
2830
return { source, theme, type: faa ? 'FAA' : 'Navigraph' }
2931
}
3032

33+
const AmdbLayer = protectedPage<{ idarpt: string, layers: AmdbLayerName[] }, [Scope.AMDB]>(memo(({ idarpt, layers, amdb }) => {
34+
const { data } = useQuery({
35+
queryKey: ['amdb-data', idarpt, layers],
36+
queryFn: async () => {
37+
return amdb.getAmdbLayers({ icao: idarpt, include: ['aerodromereferencepoint', ...layers] })
38+
}
39+
})
40+
41+
if (!data) return null;
42+
43+
return (
44+
Object.entries(data).map(([layerName, data]) =>
45+
<GeoJSON data={data}>
46+
<Popup>
47+
{layerName}
48+
</Popup>
49+
</GeoJSON>
50+
)
51+
)
52+
}), [Scope.AMDB]);
53+
3154
const ChartOverlay = protectedPage(({ charts }) => {
3255
const theme = useRecoilValue(mapThemeState);
3356

@@ -130,6 +153,8 @@ export default function MapPane() {
130153
const app = useRecoilValue(appState);
131154
const user = useRecoilValue(userState);
132155

156+
const amdbLayers = useRecoilValue(amdbLayersState);
157+
133158
return (
134159
<div className='w-full'>
135160
<MapContainer center={[51.505, -0.09]} zoom={13} className='h-screen' zoomControl={false} ref={mapRef} whenReady={() => {
@@ -145,6 +170,7 @@ export default function MapPane() {
145170
/>
146171
)}
147172
<ChartOverlay />
173+
{amdbLayers.map((layer) => <AmdbLayer key={layer[0]} idarpt={layer[0]} layers={layer[1]} />)}
148174
</MapContainer>
149175
</div>
150176
)

examples/playground/src/components/SideBar.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import clsx from "clsx";
22
import { IconType } from "react-icons";
3-
import { FaGlobe, FaMap, FaUser } from "react-icons/fa";
3+
import { FaDatabase, FaGlobe, FaMap, FaUser } from "react-icons/fa";
44
import { MdOutlineSettings } from "react-icons/md";
55
import { NavLink } from "react-router-dom";
66
import { appState } from "../state/app";
@@ -39,6 +39,7 @@ export default function SideBar() {
3939
<SideBarLink path="/auth" icon={FaUser} disabled={!app}>Auth</SideBarLink>
4040
<SideBarLink path="/tiles" icon={FaGlobe} disabled={!user?.scope.includes(Scope.TILES)}>Tiles</SideBarLink>
4141
<SideBarLink path="/charts" icon={FaMap} disabled={!user?.scope.includes(Scope.CHARTS)}>Charts</SideBarLink>
42+
<SideBarLink path="/amdb" icon={FaDatabase} disabled={!user?.scope.includes(Scope.AMDB)}>AMDB</SideBarLink>
4243
</div >
4344
)
4445
}

examples/playground/src/components/protectedPage.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import { NavigraphAuth, User } from "@navigraph/auth";
66
import { Scope } from "@navigraph/app";
77
import { ReactNode } from "react";
88
import { getChartsAPI } from "@navigraph/charts";
9+
import { getAmdbAPI } from "@navigraph/amdb";
910

1011
interface PropsStruct {
1112
[Scope.CHARTS]: ReturnType<typeof getChartsAPI>
13+
[Scope.AMDB]: ReturnType<typeof getAmdbAPI>
1214
}
1315

1416
export function protectedPage<P extends {}, S extends Scope[]>(Component: (props: P & { auth: NavigraphAuth, user: User } & Pick<PropsStruct, Extract<S[number], keyof PropsStruct>>) => ReactNode, requiredScopes: S) {
@@ -25,8 +27,8 @@ export function protectedPage<P extends {}, S extends Scope[]>(Component: (props
2527
}
2628

2729
const charts = user.scope.includes(Scope.CHARTS) ? getChartsAPI() : undefined;
30+
const amdb = user.scope.includes(Scope.AMDB) ? getAmdbAPI() : undefined;
2831

29-
30-
return <Component user={user} auth={app.auth} charts={charts} {...props as unknown as any} />;
32+
return <Component user={user} auth={app.auth} amdb={amdb} charts={charts} {...props as unknown as any} />;
3133
};
3234
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import JsonView from "../components/JsonView";
2+
import { protectedPage } from "../components/protectedPage";
3+
import { Scope } from "@navigraph/app";
4+
import { useState } from "react";
5+
import { useQuery } from "@tanstack/react-query";
6+
import { FaMagnifyingGlass } from "react-icons/fa6";
7+
import { TextField } from "../components/TextField";
8+
import SpinningCircles from "react-loading-icons/dist/esm/components/spinning-circles";
9+
import { Link, Route, Routes, useParams } from "react-router-dom";
10+
import { allLayers, getAmdbAPI } from "@navigraph/amdb";
11+
import { useRecoilState } from "recoil";
12+
import { amdbLayersState } from "../state/amdb";
13+
import Button, { LargeButton } from "../components/Button";
14+
15+
function AmdbPage({ amdb }: { amdb: ReturnType<typeof getAmdbAPI> }) {
16+
const { idarpt } = useParams();
17+
18+
const { data: airport, isLoading } = useQuery({
19+
queryKey: ['amdb-search'],
20+
queryFn: async () => (await amdb.searchAmdb(idarpt!))?.find((airport) => airport.idarpt === idarpt!)
21+
});
22+
23+
const [layers, setLayers] = useRecoilState(amdbLayersState);
24+
25+
const airportLayers = layers.find((layers) => layers[0] === airport?.idarpt)?.[1];
26+
27+
if (isLoading) {
28+
return <SpinningCircles />
29+
}
30+
31+
if (!airport) {
32+
return <span>{idarpt} has no AMDB data or does not exist</span>
33+
}
34+
35+
return (
36+
<div className="px-3 flex flex-col items-center gap-3 min-h-0 pb-5">
37+
<h1>{airport.idarpt}/{airport.iata}</h1>
38+
<span className="text-sm">{airport.name}</span>
39+
40+
{airportLayers === undefined ?
41+
<LargeButton onClick={() => setLayers([...layers, [airport.idarpt, []]])}>Add to Map</LargeButton> :
42+
<>
43+
<div className="flex flex-col overflow-auto gap-3">
44+
<Button selected disabled onClick={() => null}>aerodromereferencepoint</Button>
45+
{allLayers.map((layer) => {
46+
if (layer === 'aerodromereferencepoint') return null;
47+
48+
return (
49+
<Button
50+
className="px-5"
51+
selected={airportLayers.includes(layer)}
52+
onClick={() => {
53+
if (airportLayers.includes(layer)) {
54+
setLayers([...layers.filter((x) => x[0] !== airport.idarpt), [airport.idarpt, airportLayers.filter((x) => x !== layer)]])
55+
} else {
56+
setLayers([...layers.filter((x) => x[0] !== airport.idarpt), [airport.idarpt, [...airportLayers, layer]]])
57+
}
58+
}}
59+
>
60+
{layer}
61+
</Button>
62+
)
63+
})}
64+
</div>
65+
<LargeButton onClick={() => setLayers(layers.filter((x) => x[0] !== airport.idarpt))}>Remove from Map</LargeButton>
66+
</>
67+
}
68+
69+
70+
</div >
71+
)
72+
}
73+
74+
function AmdbSearch({ amdb }: { amdb: ReturnType<typeof getAmdbAPI> }) {
75+
const [query, setQuery] = useState('');
76+
77+
const { data: response, isLoading } = useQuery({
78+
queryKey: ['amdb-search', query],
79+
queryFn: () => amdb.searchAmdb(query),
80+
enabled: !!query
81+
})
82+
83+
return (
84+
<>
85+
<TextField label="Query" icon={FaMagnifyingGlass} value={query} onChange={setQuery} />
86+
87+
{isLoading && <SpinningCircles />}
88+
{response &&
89+
<div className="overflow-auto space-y-3 px-3 self-stretch">
90+
{response && response.map((item) =>
91+
<Link to={`/amdb/${item.idarpt}`}>
92+
<JsonView onClick={() => null} content={item} />
93+
</Link>
94+
)}
95+
</div >
96+
}
97+
</>
98+
);
99+
}
100+
101+
const Amdb = protectedPage(({ amdb }) => {
102+
return (
103+
<div className="page-container flex flex-col gap-3 items-center">
104+
<h1>AMDB</h1>
105+
106+
<Routes>
107+
<Route index element={<AmdbSearch amdb={amdb} />} />
108+
<Route path=":idarpt" element={<AmdbPage amdb={amdb} />} />
109+
</Routes>
110+
</div>
111+
)
112+
}, [Scope.AMDB]);
113+
114+
export default Amdb;

examples/playground/src/pages/Charts.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { AirportInfo, Chart, getChartsAPI } from "@navigraph/charts";
88
import SpinningCircles from "react-loading-icons/dist/esm/components/spinning-circles";
99
import JsonView from "../components/JsonView";
1010
import SegmentControl from "../components/SegmentControl";
11-
import clsx from "clsx";
1211
import Button from "../components/Button";
1312
import { IoLayers } from "react-icons/io5";
1413
import { LuTableProperties } from "react-icons/lu";

examples/playground/src/state/amdb.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { AmdbLayerName } from "@navigraph/amdb";
2+
import { atom } from "recoil";
3+
4+
export const amdbLayersState = atom<[string, Exclude<AmdbLayerName, 'aerodromereferencepoint'>[]][]>({
5+
key: 'amdb-layers',
6+
default: []
7+
});

0 commit comments

Comments
 (0)