From 287ad8859b6e6d90b68211ad74e56655c89aca1a Mon Sep 17 00:00:00 2001 From: Kilian Hofmann Date: Wed, 16 Jul 2025 12:10:01 +0200 Subject: [PATCH] Rebuilt Transition adn chart select --- TODO.md | 6 - browser/src/App.tsx | 38 ++- browser/src/components/Map.tsx | 260 ++++++------------ browser/src/components/Sidebar.tsx | 109 ++++++++ .../NavigraphAuth/NavigraphAuthContext.ts | 19 ++ .../NavigraphAuth/NavigraphAuthProvider.tsx} | 28 +- browser/src/types/index.d.ts | 9 + .../src/types/{types.d.ts => navdata.d.ts} | 0 8 files changed, 251 insertions(+), 218 deletions(-) create mode 100644 browser/src/components/Sidebar.tsx create mode 100644 browser/src/contexts/NavigraphAuth/NavigraphAuthContext.ts rename browser/src/{hooks/useNavigraphAuth.tsx => contexts/NavigraphAuth/NavigraphAuthProvider.tsx} (57%) create mode 100644 browser/src/types/index.d.ts rename browser/src/types/{types.d.ts => navdata.d.ts} (100%) diff --git a/TODO.md b/TODO.md index f2c2737..e69de29 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +0,0 @@ -Revise list of charts to only show those applicable to the selected procedure (so terminal itself and all transitions accompanied) -Revise image overlay - - Find center of full page - - Find center of georeferenced area - - Calculate skew parameters - - Skew geobounds \ No newline at end of file diff --git a/browser/src/App.tsx b/browser/src/App.tsx index 4bd634a..56123d7 100644 --- a/browser/src/App.tsx +++ b/browser/src/App.tsx @@ -3,7 +3,8 @@ import { QRCodeSVG } from 'qrcode.react'; import { useState } from 'react'; import { ProcedureSelect } from './components//ProcedureSelect'; import { Map } from './components/Map'; -import { useNavigraphAuth } from './hooks/useNavigraphAuth'; +import { Sidebar } from './components/Sidebar'; +import { useNavigraphAuth } from './contexts/NavigraphAuth/NavigraphAuthContext'; import Parser from './parser/parser'; const parser = await Parser.instance(); @@ -12,8 +13,10 @@ function App() { const [selectedAirport, setSelectedAirport] = useState(); const [selectedRunway, setSelectedRunway] = useState(); const [selectedTerminal, setSelectedTerminal] = useState(); - const [procedures, setProcedures] = useState<{ name: string; data: object }[]>([]); + const [transitions, setTransitions] = useState([]); const [params, setParams] = useState(null); + const [selectedTransition, setSelectedTransition] = useState(); + const [selectedChart, setSelectedChart] = useState(); const { user, signIn, initialized } = useNavigraphAuth(); @@ -21,7 +24,7 @@ function App() { return ( <> - {procedures.length === 0 ? ( + {transitions.length === 0 ? (
{!initialized &&
Loading...
} @@ -44,7 +47,7 @@ function App() { setSelectedRunway={setSelectedRunway} setSelectedTerminal={setSelectedTerminal} handleSelection={(selectedTransitions) => - setProcedures( + setTransitions( selectedTransitions.map((transition) => ({ name: transition, data: parser.parse(selectedRunway!, transition), @@ -56,16 +59,23 @@ function App() {
) : (
- {procedures.length > 0 && selectedAirport && selectedTerminal ? ( - { - setSelectedTerminal(undefined); - setProcedures([]); - }} - /> + {transitions.length > 0 && selectedAirport && selectedTerminal ? ( + <> + { + setSelectedTerminal(undefined); + setTransitions([]); + }} + /> + + ) : (

Error

)} diff --git a/browser/src/components/Map.tsx b/browser/src/components/Map.tsx index 08fcb06..218b3a9 100644 --- a/browser/src/components/Map.tsx +++ b/browser/src/components/Map.tsx @@ -1,197 +1,105 @@ -import BrowserImageManipulation from 'browser-image-manipulation'; -import { default as L, type LatLngBoundsExpression } from 'leaflet'; +import { default as L } from 'leaflet'; import 'leaflet-svg-shape-markers'; -import { type Chart } from 'navigraph/charts'; -import { createRef, useEffect, useState, type FC } from 'react'; +import { createRef, type FC } from 'react'; import { GeoJSON, ImageOverlay, MapContainer, TileLayer } from 'react-leaflet'; -import { charts } from '../lib/navigraph'; interface MapProps { airport: Airport; - terminal: Terminal; - procedures: { name: string; data: object }[]; - backAction: () => void; + chart: Chart | undefined; + procedure: Procedure | undefined; } -export const Map: FC = ({ airport, terminal, procedures, backAction }) => { - const [selectedProcedure, setSelectedProcedure] = useState(procedures[0]); - const [chartIndex, setChartIndex] = useState([]); - const [selectedChart, setSelectedChart] = useState<{ - data: string; - index_number: string; - bounds: LatLngBoundsExpression; - }>(); - +export const Map: FC = ({ airport, chart, procedure }) => { const mapRef = createRef(); const imageRef = createRef(); - useEffect(() => { - (async () => { - setChartIndex((await charts.getChartsIndex({ icao: airport.ICAO, version: 'STD' })) ?? []); - })(); - }, []); - return ( - <> - + { + _mapRef?.attributionControl.setPosition('topright'); + _mapRef?.zoomControl.setPosition('topright'); + mapRef.current = _mapRef; + }} + > + - { - _mapRef?.attributionControl.setPosition('topright'); - _mapRef?.zoomControl.setPosition('topright'); - mapRef.current = _mapRef; - }} - > - - - {selectedChart && selectedChart.bounds && ( - { - if (_imageRef) { - mapRef.current?.fitBounds(_imageRef.getBounds(), { - padding: [-50, -50], - }); - } - imageRef.current = _imageRef; - }} - /> - )} - - ({ - color: '#ff00ff', - stroke: true, - weight: 5, - opacity: 1, - dashArray: properties.isManual ? '20, 20' : undefined, - })} - filter={(feature) => feature.geometry.type !== 'Point'} - /> - { - if (properties.isFlyOver) - return L.shapeMarker(latlng, { - shape: 'triangle', - radius: 6, + {chart && chart.bounds && ( + { + if (_imageRef) { + mapRef.current?.fitBounds(_imageRef.getBounds(), { + padding: [-50, -50], }); - if (properties.isIntersection) return L.circleMarker(latlng, { radius: 6 }); - - return L.shapeMarker(latlng, { - shape: 'star-4', - radius: 10, - rotation: 45, - }); + } + imageRef.current = _imageRef; }} - onEachFeature={({ geometry, properties }, layer) => { - if (geometry.type === 'Point') { - layer.bindPopup( - `${properties.name}
+ /> + )} + + {procedure && ( + <> + ({ + color: '#ff00ff', + stroke: true, + weight: 5, + opacity: 1, + dashArray: properties.isManual ? '20, 20' : undefined, + })} + filter={(feature) => feature.geometry.type !== 'Point'} + /> + { + if (properties.isFlyOver) + return L.shapeMarker(latlng, { + shape: 'triangle', + radius: 6, + }); + if (properties.isIntersection) return L.circleMarker(latlng, { radius: 6 }); + + return L.shapeMarker(latlng, { + shape: 'star-4', + radius: 10, + rotation: 45, + }); + }} + onEachFeature={({ geometry, properties }, layer) => { + if (geometry.type === 'Point') { + layer.bindPopup( + `${properties.name}
${properties.altitude} ft
${properties.speed} kts
CNSTR: ${properties.altitudeConstraint ?? ''} ${properties.speedConstraint ?? ''}
` - ); - } - }} - filter={(feature) => feature.geometry.type === 'Point'} - /> -
- -
-
- Procedures: - {procedures.map((procedure) => ( - - ))} -
-
- Charts: -
- {chartIndex - .filter((chart) => chart.is_georeferenced) - .map((chart) => ( - - ))} -
-
-
- + ); + } + }} + filter={(feature) => feature.geometry.type === 'Point'} + /> + + )} +
); }; diff --git a/browser/src/components/Sidebar.tsx b/browser/src/components/Sidebar.tsx new file mode 100644 index 0000000..d544896 --- /dev/null +++ b/browser/src/components/Sidebar.tsx @@ -0,0 +1,109 @@ +import BrowserImageManipulation from 'browser-image-manipulation'; +import L from 'leaflet'; +import type { Chart as NGChart } from 'navigraph/charts'; +import { useEffect, useState, type Dispatch, type FC, type SetStateAction } from 'react'; +import { charts } from '../lib/navigraph'; + +interface SidebarProps { + airport: Airport; + terminal: Terminal; + transitions: Procedure[]; + transition: Procedure | undefined; + chart: Chart | undefined; + setTransition: Dispatch>; + setChart: Dispatch>; + backAction: () => void; +} + +export const Sidebar: FC = ({ + airport, + terminal, + transitions, + transition, + chart, + setTransition, + setChart, + backAction, +}) => { + const [chartIndex, setChartIndex] = useState([]); + + useEffect(() => { + (async () => { + setChartIndex((await charts.getChartsIndex({ icao: airport.ICAO, version: 'STD' })) ?? []); + })(); + }, [airport.ICAO]); + + return ( +
+ + +
+
+
+ Transitions for {terminal.FullName} +
+ {transitions.map((_procedure) => ( + + ))} +
+ +
+
Charts
+ {chartIndex + .filter((_chart) => _chart.is_georeferenced) + .map((_chart) => ( + + ))} +
+
+
+ ); +}; diff --git a/browser/src/contexts/NavigraphAuth/NavigraphAuthContext.ts b/browser/src/contexts/NavigraphAuth/NavigraphAuthContext.ts new file mode 100644 index 0000000..2215dc4 --- /dev/null +++ b/browser/src/contexts/NavigraphAuth/NavigraphAuthContext.ts @@ -0,0 +1,19 @@ +import { type User } from 'navigraph/auth'; +import { createContext, useContext } from 'react'; +import { auth } from '../../lib/navigraph'; + +interface NavigraphAuthContext { + initialized: boolean; + user: User | null; + signIn: typeof auth.signInWithDeviceFlow; +} + +export const authContext = createContext({ + initialized: false, + user: null, + signIn: () => Promise.reject('Not initialized'), +}); + +export const useNavigraphAuth = () => { + return useContext(authContext); +}; diff --git a/browser/src/hooks/useNavigraphAuth.tsx b/browser/src/contexts/NavigraphAuth/NavigraphAuthProvider.tsx similarity index 57% rename from browser/src/hooks/useNavigraphAuth.tsx rename to browser/src/contexts/NavigraphAuth/NavigraphAuthProvider.tsx index 64a8c8a..3a82f6e 100644 --- a/browser/src/hooks/useNavigraphAuth.tsx +++ b/browser/src/contexts/NavigraphAuth/NavigraphAuthProvider.tsx @@ -1,18 +1,7 @@ -import { type User } from 'navigraph/auth'; -import React, { createContext, useContext, useEffect, useState } from 'react'; -import { auth } from '../lib/navigraph'; - -interface NavigraphAuthContext { - initialized: boolean; - user: User | null; - signIn: typeof auth.signInWithDeviceFlow; -} - -const authContext = createContext({ - initialized: false, - user: null, - signIn: () => Promise.reject('Not initialized'), -}); +import type { User } from 'navigraph/auth'; +import { useEffect, useState } from 'react'; +import { auth } from '../../lib/navigraph'; +import { authContext } from './NavigraphAuthContext'; // Provider hook that creates auth object and handles state function useProvideAuth() { @@ -42,12 +31,7 @@ function useProvideAuth() { // Provider component that wraps your app and makes auth object // available to any child component that calls useAuth(). export function NavigraphAuthProvider({ children }: { children: React.ReactNode }) { - const auth = useProvideAuth(); - return {children}; + const _auth = useProvideAuth(); + return {children}; } -// Hook for child components to get the auth object -// and re-render when it changes. -export const useNavigraphAuth = () => { - return useContext(authContext); -}; diff --git a/browser/src/types/index.d.ts b/browser/src/types/index.d.ts new file mode 100644 index 0000000..959863d --- /dev/null +++ b/browser/src/types/index.d.ts @@ -0,0 +1,9 @@ +export declare global { + type Chart = { + data: string; + index_number: string; + bounds: LatLngBoundsExpression; + }; + + type Procedure = { name: string; data: object }; +} diff --git a/browser/src/types/types.d.ts b/browser/src/types/navdata.d.ts similarity index 100% rename from browser/src/types/types.d.ts rename to browser/src/types/navdata.d.ts