diff --git a/ui/src/App.tsx b/ui/src/App.tsx index fed944c..1e41568 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -13,7 +13,9 @@ import { Airport } from '@lib/airport.types.ts'; import AirportDrawer from '@components/AirportDrawer.tsx'; import { getWeatherMapUrl } from '@lib/rainViewer.ts'; import Cookies from 'js-cookie'; +// import { createRoot } from 'react-dom/client'; import { UnstyledButton } from '@mantine/core'; +import { IconBuildingAirport, IconRadar } from '@tabler/icons-react'; // Fix Leaflet's default icon path issues with Webpack // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error @@ -41,6 +43,63 @@ const layerMap: LayerInfo[] = [ const defaultZoom = 6; const defaultCenter: L.LatLngExpression = [38.944444, -77.455833]; +// function CustomControl({ toggleRadar, showRadar, toggleShowNoMetar, showNoMetar }) { +// const map = useMap(); +// +// useEffect(() => { +// const CustomLeafletControl = L.Control.extend({ +// options: { position: 'bottomright' }, +// onAdd: function () { +// // Create a container for the control +// const container = L.DomUtil.create('div', 'leaflet-bar custom-control'); +// +// // Radar button +// const radarButton = L.DomUtil.create('button', 'control-button radar-button', container); +// // radarButton.innerHTML = 'Radar'; +// // if (showRadar) { +// // radarButton.classList.add('active'); +// // } +// const radarRoot = createRoot(radarButton); +// radarRoot.render( +// +// ); +// // Prevent click events from propagating to the map +// L.DomEvent.disableClickPropagation(radarButton); +// L.DomEvent.on(radarButton, 'click', (e) => { +// L.DomEvent.stopPropagation(e); +// toggleRadar(); +// }); +// +// // Airports (no METARs) button +// const airportButton = L.DomUtil.create('button', 'control-button', container); +// airportButton.innerHTML = 'Airports'; +// if (showNoMetar) { +// airportButton.classList.add('active'); +// } +// L.DomEvent.disableClickPropagation(airportButton); +// L.DomEvent.on(airportButton, 'click', (e) => { +// L.DomEvent.stopPropagation(e); +// toggleShowNoMetar(); +// }); +// +// return container; +// } +// }); +// +// const customControl = new CustomLeafletControl(); +// customControl.addTo(map); +// +// // Remove control on cleanup +// return () => { +// customControl.remove(); +// }; +// }, [map, toggleRadar, toggleShowNoMetar, showRadar, showNoMetar]); +// +// return null; +// } + function App() { const [airport, setAirport] = useState(null); const [rainViewerUrl, setRainViewerUrl] = useState(null); @@ -117,20 +176,26 @@ function App() { + {/**/} - U + - R + diff --git a/ui/src/components/AirportDrawer.tsx b/ui/src/components/AirportDrawer.tsx index c3e416e..cbd6391 100644 --- a/ui/src/components/AirportDrawer.tsx +++ b/ui/src/components/AirportDrawer.tsx @@ -1,7 +1,7 @@ -import { Box, Drawer, Group, Tabs, TabsList, Text, Tooltip } from '@mantine/core'; +import { Badge, Box, Divider, Drawer, Group, Tabs, TabsList, Text, Tooltip } from '@mantine/core'; import { Airport, AirportCategory } from '@lib/airport.types.ts'; import { getMarkerColor, Metar } from '@lib/metar.types.ts'; -import { useEffect, useState } from 'react'; +import { forwardRef, useEffect, useState } from 'react'; import { getMetars } from '@lib/metar.ts'; import { useMediaQuery } from '@mantine/hooks'; @@ -43,56 +43,103 @@ export default function AirportDrawer({ const metarColor = getMarkerColor(metar?.flight_category || 'UNKN'); return ( - setAirport(null)} - title={airport.name} withinPortal zIndex={1000} styles={{ root: { padding: 0, margin: 0, width: 0, height: 0 } }} padding='md' size={isMobile ? '100%' : 'md'} position='left' - withOverlay={false} closeOnClickOutside={false} > - - {metar && metar.flight_category && ( - - {metar.flight_category} - - - - - )} - - - Info - Weather - - - {airport.latest_metar && ( - - )} - - - + + + {airport.name} + + + + + {metar && metar.flight_category && ( + + + {metar.flight_category} + + {/*{metar.flight_category}*/} + + + + + )} + + + Info + Weather + + + {airport.latest_metar && ( + + )} + + + + + ); } function AirportInfo({ airport }: { airport: Airport }) { return (
- ICAO: {airport.icao} - Category: {airportCategoryToText(airport.category)} +
+
+ + ICAO + + + {airport.icao} + +
+
+ + IATA + + + {airport.iata} + +
+
+ + Local + + + {airport.local} + +
+
+ + Category + + + {airportCategoryToText(airport.category)} + +
+
+
); } @@ -121,18 +168,20 @@ function airportCategoryToText(category: AirportCategory): string { } } -function TimeSince({ date }: { date: string }) { - const inputDate = new Date(date); - // @ts-expect-error doing arithmetic with dates - const seconds = Math.floor((new Date() - inputDate) / 1000); +const TimeSince = forwardRef( + ({ date }, ref) => { + const inputDate = new Date(date); + // @ts-expect-error doing arithmetic with dates + const seconds = Math.floor((new Date() - inputDate) / 1000); - if (seconds < 60) { - const content = seconds + (seconds === 1 ? " second ago" : " seconds ago"); - return {content}; - } else { - const minutes = Math.floor(seconds / 60); - const content = minutes + (minutes === 1 ? " minute ago" : " minutes ago"); - // If more than 60 minutes have passed, set the text color to yellow - return = 60 ? '#fca903' : undefined }}>{content}; + if (seconds < 60) { + const content = seconds + (seconds === 1 ? " second ago" : " seconds ago"); + return {content}; + } else { + const minutes = Math.floor(seconds / 60); + const content = minutes + (minutes === 1 ? " minute ago" : " minutes ago"); + // If more than 60 minutes have passed, set the text color to yellow + return = 60 ? '#fca903' : undefined, userSelect: 'none' }}>{content}; + } } -} +); diff --git a/ui/src/components/AirportMarker.tsx b/ui/src/components/AirportMarker.tsx index 8624e3e..6ce6f64 100644 --- a/ui/src/components/AirportMarker.tsx +++ b/ui/src/components/AirportMarker.tsx @@ -28,10 +28,10 @@ export default function AirportMarker({ eventHandlers={{ click: () => setAirport(airport), mouseover: () => markerRef.current?.openPopup(), - mouseout: () => markerRef.current?.closePopup() + mouseout: () => markerRef.current?.closePopup(), }} > - + {airport.icao} - {airport.name} @@ -47,7 +47,7 @@ function createCustomIcon(airport: Airport, selectedLayer: LayerInfo): L.DivIcon height: 14px; border-radius: 50%; border: 2px solid black; - background-color: ${selectedLayer.markerOutline}; + background-color: white; display: flex; align-items: center; justify-content: center; diff --git a/ui/src/components/Header/Header.module.css b/ui/src/components/Header/Header.module.css index cbc0595..c4bef94 100644 --- a/ui/src/components/Header/Header.module.css +++ b/ui/src/components/Header/Header.module.css @@ -5,6 +5,10 @@ border-bottom: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4)); } +.user { + user-select: none; +} + .inner { height: 56px; display: flex; diff --git a/ui/src/components/Header/HeaderUser.tsx b/ui/src/components/Header/HeaderUser.tsx index 2249e9c..08dc95e 100644 --- a/ui/src/components/Header/HeaderUser.tsx +++ b/ui/src/components/Header/HeaderUser.tsx @@ -19,7 +19,7 @@ export default function HeaderUser({ user, profilePicture, logout }: HeaderUserP -
+
{user.first_name} {user.last_name} diff --git a/ui/src/components/Header/index.tsx b/ui/src/components/Header/index.tsx index 6153af9..b65d02c 100644 --- a/ui/src/components/Header/index.tsx +++ b/ui/src/components/Header/index.tsx @@ -1,5 +1,5 @@ -import { Avatar, Box, Burger, Button, Group, Text } from '@mantine/core'; -import { useDisclosure, useToggle } from '@mantine/hooks'; +import { Autocomplete, Avatar, Box, Burger, Button, Group, Text } from '@mantine/core'; +import { useDisclosure, useMediaQuery, useToggle } from '@mantine/hooks'; import classes from './Header.module.css'; import { HeaderModal } from '@components/Header/HeaderModal.tsx'; import { notifications } from '@mantine/notifications'; @@ -18,6 +18,7 @@ export function Header() { const { user, setUser } = useUserContext(); const [opened, { toggle }] = useDisclosure(false); const [modalType, modalToggle] = useToggle([undefined, 'login', 'register', 'reset']); + const isMobile = useMediaQuery('(max-width: 768px)'); // const [active, setActive] = useState(links[0].link); // const navItems = links.map((link) => ( @@ -130,28 +131,33 @@ export function Header() {
- - Aviation Data + Aviation Data {/**/} {/* {navItems}*/} {/**/} - - {user ? ( - - ) : ( - - - - - )} - + {!isMobile && ( + + + {user ? ( + + ) : ( + + + + + )} + + )} + {isMobile && ( + + )}
diff --git a/ui/vite.config.ts b/ui/vite.config.ts index 8baea26..911c70b 100644 --- a/ui/vite.config.ts +++ b/ui/vite.config.ts @@ -11,6 +11,7 @@ export default defineConfig({ '@assets': path.resolve(__dirname, './src/assets'), '@components': path.resolve(__dirname, './src/components'), '@lib': path.resolve(__dirname, './src/lib'), + '@tabler/icons-react': '@tabler/icons-react/dist/esm/icons/index.mjs', } }, server: {