From 4673a5ea07c47a9ce564c94482ad2c5354c662f8 Mon Sep 17 00:00:00 2001 From: Ben Sherriff Date: Wed, 13 Sep 2023 23:35:22 -0400 Subject: [PATCH] Refactored and cleaned up code --- weather-ui/package.json | 10 +- weather-ui/src/app/layout.tsx | 38 ++-- weather-ui/src/app/page.tsx | 10 +- weather-ui/src/app/recoil-root-wrapper.tsx | 4 +- weather-ui/src/components/MetarMap.tsx | 197 ------------------ weather-ui/src/components/Metars/MapTiles.tsx | 133 ++++++++++++ .../src/components/Metars/MetarDialog.tsx | 71 +++++++ weather-ui/src/components/Metars/MetarMap.tsx | 23 ++ .../{Metar.tsx => Metars/index.tsx} | 7 +- weather-ui/src/components/Sidebar/Sidebar.css | 11 + weather-ui/src/components/Sidebar/index.tsx | 5 + weather-ui/src/components/Topbar/index.tsx | 27 +++ weather-ui/src/lib/AntdRegistry.tsx | 14 ++ weather-ui/styles/Home.module.css | 116 ----------- weather-ui/styles/globals.css | 14 +- weather-ui/tailwind.config.js | 5 +- 16 files changed, 332 insertions(+), 353 deletions(-) delete mode 100644 weather-ui/src/components/MetarMap.tsx create mode 100644 weather-ui/src/components/Metars/MapTiles.tsx create mode 100644 weather-ui/src/components/Metars/MetarDialog.tsx create mode 100644 weather-ui/src/components/Metars/MetarMap.tsx rename weather-ui/src/components/{Metar.tsx => Metars/index.tsx} (67%) create mode 100644 weather-ui/src/components/Sidebar/Sidebar.css create mode 100644 weather-ui/src/components/Sidebar/index.tsx create mode 100644 weather-ui/src/components/Topbar/index.tsx create mode 100644 weather-ui/src/lib/AntdRegistry.tsx delete mode 100755 weather-ui/styles/Home.module.css diff --git a/weather-ui/package.json b/weather-ui/package.json index 7bb983f..c2b07ca 100644 --- a/weather-ui/package.json +++ b/weather-ui/package.json @@ -9,18 +9,16 @@ "lint": "next lint" }, "dependencies": { - "@fortawesome/fontawesome-svg-core": "^6.4.2", - "@fortawesome/free-regular-svg-icons": "^6.4.2", - "@fortawesome/free-solid-svg-icons": "^6.4.2", - "@fortawesome/react-fontawesome": "^0.2.0", + "@ant-design/cssinjs": "^1.17.0", + "antd": "^5.9.0", "axios": "^1.4.0", "leaflet": "^1.9.4", "next": "^13.4.19", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-icons": "^4.11.0", "react-leaflet": "^4.2.1", - "recoil": "^0.7.7", - "xml-js": "^1.6.11" + "recoil": "^0.7.7" }, "devDependencies": { "@types/leaflet": "^1.9.3", diff --git a/weather-ui/src/app/layout.tsx b/weather-ui/src/app/layout.tsx index dc8e20a..c662172 100644 --- a/weather-ui/src/app/layout.tsx +++ b/weather-ui/src/app/layout.tsx @@ -1,31 +1,33 @@ import React from 'react'; import RecoilRootWrapper from '@app/recoil-root-wrapper'; - -import '@fortawesome/fontawesome-svg-core/styles.css'; -// Prevent fontawesome from adding its CSS since we did it manually above: -import { config } from '@fortawesome/fontawesome-svg-core'; -config.autoAddCss = false; +import Sidebar from '@/components/Sidebar'; +import Topbar from '@/components/Topbar'; import 'styles/globals.css'; -import Link from 'next/link'; - import 'styles/leaflet.css'; +import StyledComponentsRegistry from '@/lib/AntdRegistry'; +import { Inter } from 'next/font/google'; + +const inter = Inter({ subsets: ['latin'] }); + +export const metadata = { + title: 'Create Next App', + description: 'Generated by create next app', +}; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - + Aviation Weather - -
- -

Aviation Weather

- - - Profile - -
- {children} + + + + + + {children} + + ); diff --git a/weather-ui/src/app/page.tsx b/weather-ui/src/app/page.tsx index e65d194..5685dc2 100644 --- a/weather-ui/src/app/page.tsx +++ b/weather-ui/src/app/page.tsx @@ -1,12 +1,6 @@ import React from 'react'; -import Metar from '@/components/Metar'; +import Metar from '@/components/Metars'; export default function Page() { - return ( - <> -
- -
- - ); + return ; } diff --git a/weather-ui/src/app/recoil-root-wrapper.tsx b/weather-ui/src/app/recoil-root-wrapper.tsx index 10a2aea..363f3f6 100644 --- a/weather-ui/src/app/recoil-root-wrapper.tsx +++ b/weather-ui/src/app/recoil-root-wrapper.tsx @@ -4,5 +4,5 @@ import { RecoilRoot } from 'recoil'; import React, { ReactNode } from 'react'; export default function RecoilRootWrapper({ children }: { children: ReactNode }) { - return {children} -}; + return {children}; +} diff --git a/weather-ui/src/components/MetarMap.tsx b/weather-ui/src/components/MetarMap.tsx deleted file mode 100644 index c3e13be..0000000 --- a/weather-ui/src/components/MetarMap.tsx +++ /dev/null @@ -1,197 +0,0 @@ -'use client'; -import { getAirports } from '@/js/api/airport'; -import { Airport } from '@/js/api/airport.types'; -import { getMetars } from '@/js/api/metar'; -import { Metar } from '@/js/api/metar.types'; -import { faArrowsSpin, faLocationArrow, faLocationPin } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { DivIcon, LatLngBounds } from 'leaflet'; -import Link from 'next/link'; -import { useEffect, useState } from 'react'; -import ReactDOMServer from 'react-dom/server'; -import { MapContainer, Marker, Popup, TileLayer, Tooltip, useMap, useMapEvents } from 'react-leaflet'; - -export default function Map() { - return ( - - - - ); -} - -function MapTiles() { - const [airports, setAirports] = useState([]); - const [zoomLevel, setZoomLevel] = useState(8); - // const [dragging, setDragging] = useState(false); - const map = useMap(); - - const mapEvents = useMapEvents({ - zoomend: async () => { - setZoomLevel(mapEvents.getZoom()); - await updateAirports(mapEvents.getBounds()); - }, - movestart: () => { - // setDragging(true); - }, - moveend: async () => { - // setDragging(false); - await updateAirports(mapEvents.getBounds()); - } - }); - - async function updateAirports(bounds: LatLngBounds) { - const ne = bounds.getNorthEast(); - const sw = bounds.getSouthWest(); - const _airports = await getAirports({ - bounds: { - northEast: { lat: ne.lat, lon: ne.lng }, - southWest: { lat: sw.lat, lon: sw.lng } - }, - limit: 100, - page: 1 - }); - const metars = await getMetars(_airports); - metars.forEach((metar) => { - _airports.forEach((airport) => { - if (metar.station_id == airport.icao) { - airport.metar = metar; - } - }); - }); - setAirports(_airports); - } - - function metarBGColor(metar: Metar | undefined) { - if (metar?.flight_category == 'VFR') { - return 'bg-emerald-600'; - } else if (metar?.flight_category == 'MVFR') { - return 'bg-blue-600'; - } else if (metar?.flight_category == 'IFR') { - return 'bg-red-600'; - } else if (metar?.flight_category == 'LIFR') { - return 'bg-purple-600'; - } else { - return 'bg-black'; - } - } - - function metarTextColor(metar: Metar | undefined) { - if (metar?.flight_category == 'VFR') { - return 'text-emerald-700'; - } else if (metar?.flight_category == 'MVFR') { - return 'text-blue-700'; - } else if (metar?.flight_category == 'IFR') { - return 'text-red-700'; - } else if (metar?.flight_category == 'LIFR') { - return 'text-purple-700'; - } else { - return 'text-black/50'; - } - } - - function windColor(metar: Metar | undefined) { - if (Number(metar?.wind_speed_kt) <= 9) { - return 'bg-green-300'; - } else if (Number(metar?.wind_speed_kt) > 9) { - return 'bg-orange-300'; - } else if (Number(metar?.wind_speed_kt) > 12) { - return 'bg-red-300'; - } - } - - function iconSize() { - if (zoomLevel <= 4) { - return 'text-xs'; - } else if (zoomLevel <= 5) { - return 'text-sm'; - } else if (zoomLevel <= 6) { - return 'text-base'; - } else if (zoomLevel <= 7) { - return 'text-lg'; - } else if (zoomLevel <= 9) { - return 'text-2xl'; - } else if (zoomLevel <= 11) { - return 'text-3xl'; - } else if (zoomLevel >= 12) { - return 'text-4xl'; - } - } - - function icon(airport: Airport) { - return new DivIcon({ - html: ReactDOMServer.renderToString( - - ), - className: 'metar-marker-icon' - }); - } - - useEffect(() => { - updateAirports(map.getBounds()); - }, []); - - return ( - <> - - {airports.map((airport) => ( - - - {airport.icao} - {airport.full_name} - - -
- -

- {airport.icao} {airport.full_name} -

- -
-

{airport.metar?.raw_text}

-
- - {airport.metar?.flight_category ? airport.metar?.flight_category : 'UNKN'} - -
- - {airport.metar && airport.metar.wind_dir_degrees && Number(airport.metar.wind_dir_degrees) > 0 ? ( - - ) : ( - <> - )} - {airport.metar && airport.metar.wind_dir_degrees && airport.metar.wind_dir_degrees == 'VRB' ? ( - - ) : ( - <> - )} - {airport.metar?.wind_speed_kt != undefined && airport.metar?.wind_speed_kt > 0 - ? `${airport.metar?.wind_speed_kt} KT` - : 'CALM'} - -
-
-
-
-
- ))} - - ); -} diff --git a/weather-ui/src/components/Metars/MapTiles.tsx b/weather-ui/src/components/Metars/MapTiles.tsx new file mode 100644 index 0000000..ee4405b --- /dev/null +++ b/weather-ui/src/components/Metars/MapTiles.tsx @@ -0,0 +1,133 @@ +'use client'; + +import { getAirports } from '@/js/api/airport'; +import { Airport } from '@/js/api/airport.types'; +import { getMetars } from '@/js/api/metar'; +import { Metar } from '@/js/api/metar.types'; +import { FaLocationPin } from 'react-icons/fa6'; +import { DivIcon, LatLngBounds } from 'leaflet'; +import { useEffect, useState } from 'react'; +import ReactDOMServer from 'react-dom/server'; +import { Marker, TileLayer, Tooltip, useMap, useMapEvents } from 'react-leaflet'; +import MetarDialog from './MetarDialog'; + +export default function MapTiles() { + const [isOpen, setIsOpen] = useState(false); + const [airports, setAirports] = useState([]); + const [selectedAirport, setSelectedAirport] = useState(); + const [zoomLevel, setZoomLevel] = useState(8); + // const [dragging, setDragging] = useState(false); + const map = useMap(); + + const mapEvents = useMapEvents({ + zoomend: async () => { + setZoomLevel(mapEvents.getZoom()); + await updateAirports(mapEvents.getBounds()); + }, + movestart: () => { + // setDragging(true); + }, + moveend: async () => { + // setDragging(false); + await updateAirports(mapEvents.getBounds()); + } + }); + + function handleOpen(airport: Airport) { + setSelectedAirport(airport); + setIsOpen(true); + } + + async function updateAirports(bounds: LatLngBounds) { + const ne = bounds.getNorthEast(); + const sw = bounds.getSouthWest(); + const _airports = await getAirports({ + bounds: { + northEast: { lat: ne.lat, lon: ne.lng }, + southWest: { lat: sw.lat, lon: sw.lng } + }, + limit: 100, + page: 1 + }); + const metars = await getMetars(_airports); + metars.forEach((metar) => { + _airports.forEach((airport) => { + if (metar.station_id == airport.icao) { + airport.metar = metar; + } + }); + }); + setAirports(_airports); + } + + function metarTextColor(metar: Metar | undefined) { + if (metar?.flight_category == 'VFR') { + return 'text-emerald-700'; + } else if (metar?.flight_category == 'MVFR') { + return 'text-blue-700'; + } else if (metar?.flight_category == 'IFR') { + return 'text-red-700'; + } else if (metar?.flight_category == 'LIFR') { + return 'text-purple-700'; + } else { + return 'text-black/50'; + } + } + + function iconSize() { + if (zoomLevel <= 4) { + return 'text-xs'; + } else if (zoomLevel <= 5) { + return 'text-sm'; + } else if (zoomLevel <= 6) { + return 'text-base'; + } else if (zoomLevel <= 7) { + return 'text-lg'; + } else if (zoomLevel <= 9) { + return 'text-2xl'; + } else if (zoomLevel <= 11) { + return 'text-3xl'; + } else if (zoomLevel >= 12) { + return 'text-4xl'; + } + } + + function icon(airport: Airport) { + return new DivIcon({ + html: ReactDOMServer.renderToString( + + ), + className: 'metar-marker-icon' + }); + } + + useEffect(() => { + updateAirports(map.getBounds()); + }, []); + + return ( + <> + {selectedAirport && setIsOpen(false)} airport={selectedAirport} />} + + {airports.map((airport) => ( + handleOpen(airport) + }} + > + {!isOpen && ( + + {airport.icao} - {airport.full_name} + + )} + + ))} + + ); +} diff --git a/weather-ui/src/components/Metars/MetarDialog.tsx b/weather-ui/src/components/Metars/MetarDialog.tsx new file mode 100644 index 0000000..5a3e643 --- /dev/null +++ b/weather-ui/src/components/Metars/MetarDialog.tsx @@ -0,0 +1,71 @@ +import { Airport } from '@/js/api/airport.types'; +import { Metar } from '@/js/api/metar.types'; +import { FaArrowsSpin, FaLocationArrow } from 'react-icons/fa6'; +import { Modal } from 'antd'; + +interface MetarDialogProps { + airport: Airport; + isOpen: boolean; + onClose(): void; +} + +export default function MetarDialog({ airport, isOpen, onClose }: MetarDialogProps) { + function metarBGColor(metar: Metar | undefined) { + if (metar?.flight_category == 'VFR') { + return 'bg-emerald-600'; + } else if (metar?.flight_category == 'MVFR') { + return 'bg-blue-600'; + } else if (metar?.flight_category == 'IFR') { + return 'bg-red-600'; + } else if (metar?.flight_category == 'LIFR') { + return 'bg-purple-600'; + } else { + return 'bg-black'; + } + } + + function windColor(metar: Metar | undefined) { + if (Number(metar?.wind_speed_kt) <= 9) { + return 'bg-green-300'; + } else if (Number(metar?.wind_speed_kt) > 9) { + return 'bg-orange-300'; + } else if (Number(metar?.wind_speed_kt) > 12) { + return 'bg-red-300'; + } + } + return ( + +
+
+

{airport.metar?.raw_text}

+
+ + {airport.metar?.flight_category ? airport.metar?.flight_category : 'UNKN'} + +
+ + {airport.metar && airport.metar.wind_dir_degrees && Number(airport.metar.wind_dir_degrees) > 0 ? ( + + ) : ( + <> + )} + {airport.metar && airport.metar.wind_dir_degrees && airport.metar.wind_dir_degrees == 'VRB' ? ( + + ) : ( + <> + )} + {airport.metar?.wind_speed_kt != undefined && airport.metar?.wind_speed_kt > 0 + ? `${airport.metar?.wind_speed_kt} KT` + : 'CALM'} + +
+
+
+
+ ); +} diff --git a/weather-ui/src/components/Metars/MetarMap.tsx b/weather-ui/src/components/Metars/MetarMap.tsx new file mode 100644 index 0000000..4644a74 --- /dev/null +++ b/weather-ui/src/components/Metars/MetarMap.tsx @@ -0,0 +1,23 @@ +'use client'; + +import { MapContainer } from 'react-leaflet'; +import MapTiles from './MapTiles'; + +export default function Map({ className = '' }: { className?: string }) { + return ( + <> + + + + + ); +} diff --git a/weather-ui/src/components/Metar.tsx b/weather-ui/src/components/Metars/index.tsx similarity index 67% rename from weather-ui/src/components/Metar.tsx rename to weather-ui/src/components/Metars/index.tsx index 3ff5333..fd2abcd 100644 --- a/weather-ui/src/components/Metar.tsx +++ b/weather-ui/src/components/Metars/index.tsx @@ -1,8 +1,8 @@ import { Metar } from '@/js/api/metar.types'; import dynamic from 'next/dynamic'; -export default async function Metar() { - const Map = dynamic(() => import('@/components/MetarMap'), { +export default async function Metar({ className = '' }: { className?: string }) { + const Map = dynamic(() => import('@/components/Metars/MetarMap'), { loading: () => (
@@ -12,6 +12,5 @@ export default async function Metar() { ), ssr: false }); - - return ; + return ; } diff --git a/weather-ui/src/components/Sidebar/Sidebar.css b/weather-ui/src/components/Sidebar/Sidebar.css new file mode 100644 index 0000000..d5d086d --- /dev/null +++ b/weather-ui/src/components/Sidebar/Sidebar.css @@ -0,0 +1,11 @@ +.sidebar { + width: 62px; + height: 100%; + display: flex; + flex-direction: column; + + .option-group { + display: flex; + flex-direction: column; + } +} diff --git a/weather-ui/src/components/Sidebar/index.tsx b/weather-ui/src/components/Sidebar/index.tsx new file mode 100644 index 0000000..fe98409 --- /dev/null +++ b/weather-ui/src/components/Sidebar/index.tsx @@ -0,0 +1,5 @@ +import './Sidebar.css'; + +export default function Sidebar() { + return
; +} diff --git a/weather-ui/src/components/Topbar/index.tsx b/weather-ui/src/components/Topbar/index.tsx new file mode 100644 index 0000000..ab21317 --- /dev/null +++ b/weather-ui/src/components/Topbar/index.tsx @@ -0,0 +1,27 @@ +'use client'; + +import { Avatar } from 'antd'; +import Search from 'antd/es/input/Search'; +import { useRouter } from 'next/navigation'; +import { AiOutlineUser } from 'react-icons/ai'; + +export default function Topbar() { + const router = useRouter(); + + function onSearch(value: string) { + router.push(`/airports/${value}`); + } + + return ( + + ); +} diff --git a/weather-ui/src/lib/AntdRegistry.tsx b/weather-ui/src/lib/AntdRegistry.tsx new file mode 100644 index 0000000..289a635 --- /dev/null +++ b/weather-ui/src/lib/AntdRegistry.tsx @@ -0,0 +1,14 @@ +'use client'; + +import React from 'react'; +import { createCache, extractStyle, StyleProvider } from '@ant-design/cssinjs'; +import type Entity from '@ant-design/cssinjs/es/Cache'; +import { useServerInsertedHTML } from 'next/navigation'; + +const StyledComponentsRegistry = ({ children }: { children: React.ReactNode }) => { + const cache = React.useMemo(() => createCache(), [createCache]); + useServerInsertedHTML(() =>