From 18a5440a7340281ddc4d1dd86c7c7114251588ea Mon Sep 17 00:00:00 2001 From: Alexander Harding <2166114+aeharding@users.noreply.github.com> Date: Wed, 2 Oct 2024 18:46:11 -0500 Subject: [PATCH 1/8] First attempt --- src/features/outlook/Day.tsx | 47 ++++++++++ src/features/outlook/Outlook.tsx | 11 +++ src/features/outlook/OutlookRow.tsx | 115 ++++++++++++++++++++++++ src/features/outlook/OutlookTable.tsx | 102 +++++++++++++++++++++ src/features/outlook/WindSpeed.tsx | 38 ++++++++ src/features/rap/cells/WindSpeed.tsx | 4 +- src/features/rap/extra/Extra.tsx | 68 +++++++++----- src/features/weather/header/Weather.tsx | 4 +- src/features/weather/header/Wind.tsx | 2 +- src/features/weather/weatherSlice.ts | 2 +- src/services/openMeteo.ts | 59 ++++++------ 11 files changed, 398 insertions(+), 54 deletions(-) create mode 100644 src/features/outlook/Day.tsx create mode 100644 src/features/outlook/Outlook.tsx create mode 100644 src/features/outlook/OutlookRow.tsx create mode 100644 src/features/outlook/OutlookTable.tsx create mode 100644 src/features/outlook/WindSpeed.tsx diff --git a/src/features/outlook/Day.tsx b/src/features/outlook/Day.tsx new file mode 100644 index 0000000..eb560a7 --- /dev/null +++ b/src/features/outlook/Day.tsx @@ -0,0 +1,47 @@ +import styled from "@emotion/styled"; +import { formatInTimeZone } from "date-fns-tz"; +import { useAppSelector } from "../../hooks"; +import { timeZoneSelector } from "../weather/weatherSlice"; + +const Table = styled.table` + width: 100%; + + padding: 0; + border-collapse: collapse; + border: none; +`; + +const THead = styled.thead` + position: sticky; + top: 0; + z-index: 1; + background: var(--bg-bottom-sheet); +`; + +const DayLabelCell = styled.th` + text-align: start; + padding: 8px 16px; +`; + +interface DayProps { + date: Date; + hours: React.ReactNode[]; +} + +export default function Day({ hours, date }: DayProps) { + const timeZone = useAppSelector(timeZoneSelector); + if (!timeZone) throw new Error("timeZone needed"); + + return ( + + + + + {formatInTimeZone(date, timeZone, "eeee, LLL d")} + + + + {hours} +
+ ); +} diff --git a/src/features/outlook/Outlook.tsx b/src/features/outlook/Outlook.tsx new file mode 100644 index 0000000..f759a2e --- /dev/null +++ b/src/features/outlook/Outlook.tsx @@ -0,0 +1,11 @@ +import { useAppSelector } from "../../hooks"; +import OutlookTable from "./OutlookTable"; + +export default function Outlook() { + const weather = useAppSelector((state) => state.weather.weather); + + if (weather === "failed") return; + if (!weather || weather === "pending") return; + + return ; +} diff --git a/src/features/outlook/OutlookRow.tsx b/src/features/outlook/OutlookRow.tsx new file mode 100644 index 0000000..956305c --- /dev/null +++ b/src/features/outlook/OutlookRow.tsx @@ -0,0 +1,115 @@ +import { formatInTimeZone } from "date-fns-tz"; +import { timeZoneSelector } from "../weather/weatherSlice"; +import { useAppSelector } from "../../hooks"; +import styled from "@emotion/styled"; +import WindIndicator from "../rap/WindIndicator"; +import WindSpeed from "./WindSpeed"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faMoon, faSun } from "@fortawesome/pro-duotone-svg-icons"; +import chroma from "chroma-js"; +import { getCompositeWindValue } from "../weather/header/Wind"; +import SunCalc from "suncalc"; + +export const windColorScale = chroma + .scale([ + "#00FF0099", + "#00FF0099", + "#eaff0099", + "#FFA50099", + "#FF000099", + "#FF000099", + "#FF10F0", + "#AD2AFF", + "white", + ]) + .domain([0, 5.56, 18, 25.93, 55.56, 64.82, 138.9, 185.2, 296.32]) + .mode("lab"); + +const Row = styled.tr<{ speed: number; color: string; day: boolean }>` + height: 50px; + + border-bottom: 1px solid #77777715; + + position: relative; + // https://github.com/w3c/csswg-drafts/issues/1899#issuecomment-338773780 + transform: scale(1); + + &::after { + content: ""; + position: absolute; + z-index: -1; + height: 50px; + inset: 0; + background: linear-gradient( + in oklab 90deg, + ${({ color }) => color}, + ${({ color }) => color} ${({ speed }) => speed}%, + ${({ day }) => (day ? "#ffffff0a" : "transparent")} + calc(${({ speed }) => speed}% + 80px) + ); + } +`; + +const TimeCell = styled.td` + text-align: center; + vertical-align: middle; +`; + +const Time = styled.div` + display: inline-block; + padding: 3px 5px; + font-size: 12px; + background: var(--bg-bottom-sheet); + border-radius: 16px; +`; + +interface OutlookRowProps { + hour: Date; + windDirection: number; + windSpeed: number; + windGust: number; +} + +export default function OutlookRow({ + hour, + windDirection, + windSpeed, + windGust, +}: OutlookRowProps) { + const timeZone = useAppSelector(timeZoneSelector); + if (!timeZone) throw new Error("timeZone needed"); + + const coordinates = useAppSelector((state) => state.weather.coordinates); + if (!coordinates) throw new Error("coordinates not found"); + + const time = formatInTimeZone(hour, timeZone, "hha"); + + const compositeSpeed = getCompositeWindValue(windSpeed, windGust) * 0.7; + + const color = (() => { + const [r, g, b, a] = windColorScale(compositeSpeed).rgba(); + + return `color(display-p3 ${r / 255} ${g / 255} ${b / 255} / ${a})`; + })(); + + const isDay = + SunCalc.getPosition(hour, coordinates.lat, coordinates.lon).altitude > 0; + + return ( + + + + + + + + + + + + + + 47deg + + ); +} diff --git a/src/features/outlook/OutlookTable.tsx b/src/features/outlook/OutlookTable.tsx new file mode 100644 index 0000000..83756eb --- /dev/null +++ b/src/features/outlook/OutlookTable.tsx @@ -0,0 +1,102 @@ +import { addDays, eachHourOfInterval, startOfDay } from "date-fns"; +import { findValue, NWSWeather } from "../../services/nwsWeather"; +import { Weather } from "../weather/weatherSlice"; +import { OpenMeteoWeather } from "../../services/openMeteo"; +import { useMemo } from "react"; +import OutlookRow from "./OutlookRow"; +import compact from "lodash/fp/compact"; +import styled from "@emotion/styled"; +import Day from "./Day"; + +const Rows = styled.div``; + +interface OutlookTableProps { + weather: Weather; +} + +function getOutlook( + mapFn: (hour: Date, index: number) => React.ReactNode | undefined, +) { + const hours = eachHourOfInterval({ + start: new Date(), + end: addDays(new Date(), 7), + }); + + const data = compact( + hours.map((hour, index) => ({ node: mapFn(hour, index), hour })), + ); + + return Object.entries( + Object.groupBy(data, ({ hour }) => startOfDay(hour).getTime()), + ).map(([timeStr, hours]) => ({ + date: new Date(+timeStr), + hours: hours!.map(({ node }) => node), + })); +} + +export default function OutlookTable({ weather }: OutlookTableProps) { + const rows = (() => { + if ("properties" in weather) return ; + return ; + })(); + + return {rows}; +} + +function NWSOutlookRows({ weather }: { weather: NWSWeather }) { + const days = useMemo( + () => + getOutlook((hour, index) => { + const windDirection = findValue( + hour, + weather.properties.windDirection, + )?.value; + const windSpeed = findValue(hour, weather.properties.windSpeed)?.value; + const windGust = findValue(hour, weather.properties.windGust)?.value; + + if (windDirection == null) return; + if (windSpeed == null) return; + if (windGust == null) return; + + return ( + + ); + }), + [weather], + ); + + return days.map(({ date, hours }, index) => ( + + )); +} +function OpenMeteoOutlookRows({ weather }: { weather: OpenMeteoWeather }) { + const days = useMemo( + () => + getOutlook((hour, index) => { + const data = weather.byUnixTimestamp[hour.getTime() / 1_000]; + + if (!data) return; + + return ( + + ); + }), + [weather], + ); + + return days.map(({ date, hours }, index) => ( + + )); +} diff --git a/src/features/outlook/WindSpeed.tsx b/src/features/outlook/WindSpeed.tsx new file mode 100644 index 0000000..352dcb3 --- /dev/null +++ b/src/features/outlook/WindSpeed.tsx @@ -0,0 +1,38 @@ +import styled from "@emotion/styled"; +import { formatWind } from "../../helpers/taf"; +import { useAppSelector } from "../../hooks"; +import { SpeedUnit } from "metar-taf-parser"; + +const Speed = styled.div` + word-spacing: -2px; +`; +const Gust = styled.div` + font-size: 12px; + word-spacing: -2px; +`; + +interface WindSpeedProps { + speed: number; + gust: number; +} + +export default function WindSpeed({ speed, gust }: WindSpeedProps) { + const speedUnit = useAppSelector((state) => state.user.speedUnit); + const speedFormatted = formatWind( + speed, + SpeedUnit.KilometersPerHour, + speedUnit, + ); + const gustFormatted = formatWind( + gust, + SpeedUnit.KilometersPerHour, + speedUnit, + ); + + return ( + <> + {speedFormatted} + max {gustFormatted} + + ); +} diff --git a/src/features/rap/cells/WindSpeed.tsx b/src/features/rap/cells/WindSpeed.tsx index b0870ac..3071d2c 100644 --- a/src/features/rap/cells/WindSpeed.tsx +++ b/src/features/rap/cells/WindSpeed.tsx @@ -8,7 +8,7 @@ import { useMemo } from "react"; import { useAppSelector } from "../../../hooks"; import { SpeedUnit } from "../extra/settings/settingEnums"; -const colorScale = chroma +export const windColorScale = chroma .scale([ "#00FF00", "#00FF00", @@ -27,7 +27,7 @@ const colorScale = chroma const WindSpeedContainer = styled.div<{ speed: number; shear: boolean }>` position: relative; - ${({ speed }) => outputP3ColorFromLab(colorScale(speed).lab())}; + ${({ speed }) => outputP3ColorFromLab(windColorScale(speed).lab())}; ${({ shear }) => shear && diff --git a/src/features/rap/extra/Extra.tsx b/src/features/rap/extra/Extra.tsx index 2c3c37f..6213eb1 100644 --- a/src/features/rap/extra/Extra.tsx +++ b/src/features/rap/extra/Extra.tsx @@ -2,6 +2,7 @@ import React, { lazy, Suspense } from "react"; import styled from "@emotion/styled"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { + faCalendarAlt, faCog, faFileAlt, faLongArrowRight, @@ -19,6 +20,7 @@ import InstallPrompt from "../../install/InstallPrompt"; import { isAfter } from "date-fns"; import Spinner from "../../../shared/Spinner"; import { useTranslation } from "react-i18next"; +import Outlook from "../../outlook/Outlook"; const ReportMetadata = lazy(() => import("./reportMetadata/ReportMetadata")); @@ -37,6 +39,15 @@ const Container = styled.div` margin: 0 auto; `; +const Line = styled.div` + display: flex; + gap: 1rem; + + > * { + flex: 1; + } +`; + export default function Extra() { const { t } = useTranslation(); const weather = useAppSelector((state) => state.weather.weather); @@ -63,6 +74,17 @@ export default function Extra() { + + {t("Daily Forecast")} + + } + title={t("Daily Forecast")} + > + + + {discussion !== "not-available" ? ( ) : undefined} - - {t("Report Metadata")} - - } - title={t("Report Metadata")} - > - }> - - - + + + {t("Metadata")} + + } + title={t("Report Metadata")} + > + }> + + + - - {t("Settings")} - - } - title={t("Settings")} - > - - + + {t("Settings")} + + } + title={t("Settings")} + > + + + ); } diff --git a/src/features/weather/header/Weather.tsx b/src/features/weather/header/Weather.tsx index 5f83ad6..7686187 100644 --- a/src/features/weather/header/Weather.tsx +++ b/src/features/weather/header/Weather.tsx @@ -5,7 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useMemo } from "react"; import { outputP3ColorFromRGB } from "../../../helpers/colors"; import { findValue } from "../../../services/nwsWeather"; -import { WeatherResult as NWSWeatherResult } from "../weatherSlice"; +import { WeatherResult } from "../weatherSlice"; import { keyframes } from "@emotion/css"; import { css } from "@emotion/react"; import NWSWeather from "./NWSWeather"; @@ -84,7 +84,7 @@ export const WeatherIcon = styled(FontAwesomeIcon, { interface WeatherProps { date: string; - weather: NWSWeatherResult | undefined; + weather: WeatherResult | undefined; } export default function Weather({ date, weather }: WeatherProps) { diff --git a/src/features/weather/header/Wind.tsx b/src/features/weather/header/Wind.tsx index 5f8e077..7d76fd6 100644 --- a/src/features/weather/header/Wind.tsx +++ b/src/features/weather/header/Wind.tsx @@ -137,6 +137,6 @@ function toMph(speed: number): number { /** * @returns A "composite" wind value for gusts+sustained, similar to temperature "real feel" */ -function getCompositeWindValue(speed: number, gust: number): number { +export function getCompositeWindValue(speed: number, gust: number): number { return Math.max((gust - speed) * 2.5, speed, gust); } diff --git a/src/features/weather/weatherSlice.ts b/src/features/weather/weatherSlice.ts index aa38e55..5a1b2f1 100644 --- a/src/features/weather/weatherSlice.ts +++ b/src/features/weather/weatherSlice.ts @@ -18,7 +18,7 @@ import { AxiosError } from "axios"; const UPDATE_INTERVAL_MINUTES = 30; -type Weather = nwsWeather.NWSWeather | openMeteo.OpenMeteoWeather; +export type Weather = nwsWeather.NWSWeather | openMeteo.OpenMeteoWeather; export type WeatherResult = // component has requested a weather, to be batched in next bulk request diff --git a/src/services/openMeteo.ts b/src/services/openMeteo.ts index e103489..0789994 100644 --- a/src/services/openMeteo.ts +++ b/src/services/openMeteo.ts @@ -19,10 +19,10 @@ const PRESSURE_ALTITUDES = [ const PRESSURE_ALTITUDE_METRICS = [ "temperature", - "windspeed", - "winddirection", + "wind_speed", + "wind_direction", "geopotential_height", - "relativehumidity", + "relative_humidity", ] as const; /** @@ -30,31 +30,33 @@ const PRESSURE_ALTITUDE_METRICS = [ */ const AGL_ALTITUDES = [80, 120, 180] as const; -const AGL_METRICS = ["windspeed", "winddirection", "temperature"] as const; +const AGL_METRICS = ["wind_speed", "wind_direction", "temperature"] as const; const SPECIAL_ALOFT_VARIABLES = [ "cape", + "convective_inhibition", "temperature_2m", - "dewpoint_2m", - "relativehumidity_2m", + "dew_point_2m", + "relative_humidity_2m", "surface_pressure", "pressure_msl", // No temperature_10m, so have to break it out from AGL_ALTITUDES // (will fudge it and use temperature_2m) - "winddirection_10m", - "windspeed_10m", - "windgusts_10m", + "wind_direction_10m", + "wind_speed_10m", + "wind_gusts_10m", ] as const; const WEATHER_VARIABLES = [ "precipitation_probability", - "weathercode", - "cloudcover", + "weather_code", + "cloud_cover", - "windspeed_10m", - "windgusts_10m", + "wind_speed_10m", + "wind_gusts_10m", + "wind_direction_10m", ] as const; type HourlyPressureParams = @@ -84,6 +86,7 @@ interface OpenMeteoWeatherHour { /** kph */ windGust: number; cloudCover: number; + windDirection: number; } export async function getWeather( @@ -131,10 +134,11 @@ function convertOpenMeteoToWeather( openMeteoResponse.hourly.time.map((_, index) => ({ precipitationChance: openMeteoResponse.hourly.precipitation_probability[index], - weather: openMeteoResponse.hourly.weathercode[index], - windSpeed: openMeteoResponse.hourly.windspeed_10m[index], - windGust: openMeteoResponse.hourly.windgusts_10m[index], - cloudCover: openMeteoResponse.hourly.cloudcover[index], + weather: openMeteoResponse.hourly.weather_code[index], + windSpeed: openMeteoResponse.hourly.wind_speed_10m[index], + windGust: openMeteoResponse.hourly.wind_gusts_10m[index], + windDirection: openMeteoResponse.hourly.wind_direction_10m[index], + cloudCover: openMeteoResponse.hourly.cloud_cover[index], })), ), }; @@ -226,14 +230,16 @@ function convertOpenMeteoToWindsAloft( index ], windSpeedInKph: - openMeteoResponse.hourly[`windspeed_${pressureAltitude}hPa`][index], + openMeteoResponse.hourly[`wind_speed_${pressureAltitude}hPa`][index], windDirectionInDeg: - openMeteoResponse.hourly[`winddirection_${pressureAltitude}hPa`][index], + openMeteoResponse.hourly[`wind_direction_${pressureAltitude}hPa`][ + index + ], temperatureInC: openMeteoResponse.hourly[`temperature_${pressureAltitude}hPa`][index], pressure: pressureAltitude, dewpointInC: velitherm.dewPoint( - openMeteoResponse.hourly[`relativehumidity_${pressureAltitude}hPa`][ + openMeteoResponse.hourly[`relative_humidity_${pressureAltitude}hPa`][ index ], openMeteoResponse.hourly[`temperature_${pressureAltitude}hPa`][index], @@ -270,14 +276,15 @@ function convertOpenMeteoToWindsAloft( return { date: new Date(time * 1_000).toISOString(), cape: openMeteoResponse.hourly.cape[index], + cin: openMeteoResponse.hourly.convective_inhibition[index], altitudes: [ { altitudeInM: openMeteoResponse.elevation, - windSpeedInKph: openMeteoResponse.hourly.windspeed_10m[index], + windSpeedInKph: openMeteoResponse.hourly.wind_speed_10m[index], windDirectionInDeg: - openMeteoResponse.hourly.winddirection_10m[index], + openMeteoResponse.hourly.wind_direction_10m[index], temperatureInC: openMeteoResponse.hourly.temperature_2m[index], - dewpointInC: openMeteoResponse.hourly.dewpoint_2m[index], + dewpointInC: openMeteoResponse.hourly.dew_point_2m[index], pressure: Math.round( openMeteoResponse.hourly.surface_pressure[index], ), @@ -287,9 +294,9 @@ function convertOpenMeteoToWindsAloft( AGL_ALTITUDES.map((agl) => ({ altitudeInM: openMeteoResponse.elevation + agl, windSpeedInKph: - openMeteoResponse.hourly[`windspeed_${agl}m`][index], + openMeteoResponse.hourly[`wind_speed_${agl}m`][index], windDirectionInDeg: - openMeteoResponse.hourly[`winddirection_${agl}m`][index], + openMeteoResponse.hourly[`wind_direction_${agl}m`][index], temperatureInC: openMeteoResponse.hourly[`temperature_${agl}m`][index], dewpointInC: velitherm.dewPoint( @@ -297,7 +304,7 @@ function convertOpenMeteoToWindsAloft( agl, openMeteoResponse.hourly[`surface_pressure`][index], openMeteoResponse.hourly[`temperature_${agl}m`][index], - openMeteoResponse.hourly[`relativehumidity_2m`][index], + openMeteoResponse.hourly[`relative_humidity_2m`][index], ), openMeteoResponse.hourly[`temperature_${agl}m`][index], ), From 719a8dfca0e68d3217e377a28416a414baaee1d3 Mon Sep 17 00:00:00 2001 From: Alexander Harding <2166114+aeharding@users.noreply.github.com> Date: Fri, 4 Oct 2024 14:22:57 -0500 Subject: [PATCH 2/8] More shit --- src/features/outlook/Day.tsx | 4 +- src/features/outlook/OutlookRow.tsx | 60 ++++++++++++++++++--------- src/features/outlook/OutlookTable.tsx | 7 ++++ src/features/outlook/WindSpeed.tsx | 23 +++++++--- src/features/rap/extra/Extra.tsx | 4 +- src/features/weather/header/Wind.tsx | 4 +- src/services/nwsWeather.ts | 1 + src/services/openMeteo.ts | 3 ++ 8 files changed, 75 insertions(+), 31 deletions(-) diff --git a/src/features/outlook/Day.tsx b/src/features/outlook/Day.tsx index eb560a7..4c96dc3 100644 --- a/src/features/outlook/Day.tsx +++ b/src/features/outlook/Day.tsx @@ -9,6 +9,8 @@ const Table = styled.table` padding: 0; border-collapse: collapse; border: none; + + text-align: center; `; const THead = styled.thead` @@ -36,7 +38,7 @@ export default function Day({ hours, date }: DayProps) { - + {formatInTimeZone(date, timeZone, "eeee, LLL d")} diff --git a/src/features/outlook/OutlookRow.tsx b/src/features/outlook/OutlookRow.tsx index 956305c..b3ddb43 100644 --- a/src/features/outlook/OutlookRow.tsx +++ b/src/features/outlook/OutlookRow.tsx @@ -9,6 +9,10 @@ import { faMoon, faSun } from "@fortawesome/pro-duotone-svg-icons"; import chroma from "chroma-js"; import { getCompositeWindValue } from "../weather/header/Wind"; import SunCalc from "suncalc"; +import { TemperatureText } from "../rap/cells/Temperature"; +import { Aside } from "../rap/cells/Altitude"; +import { TemperatureUnit } from "../rap/extra/settings/settingEnums"; +import { cToF } from "../weather/aviation/DetailedAviationReport"; export const windColorScale = chroma .scale([ @@ -26,11 +30,11 @@ export const windColorScale = chroma .mode("lab"); const Row = styled.tr<{ speed: number; color: string; day: boolean }>` - height: 50px; - border-bottom: 1px solid #77777715; - position: relative; + background: ${({ day }) => (day ? "#ffffff07" : "transparent")}; + + /* position: relative; // https://github.com/w3c/csswg-drafts/issues/1899#issuecomment-338773780 transform: scale(1); @@ -47,20 +51,11 @@ const Row = styled.tr<{ speed: number; color: string; day: boolean }>` ${({ day }) => (day ? "#ffffff0a" : "transparent")} calc(${({ speed }) => speed}% + 80px) ); - } + } */ `; const TimeCell = styled.td` - text-align: center; - vertical-align: middle; -`; - -const Time = styled.div` - display: inline-block; - padding: 3px 5px; font-size: 12px; - background: var(--bg-bottom-sheet); - border-radius: 16px; `; interface OutlookRowProps { @@ -68,6 +63,7 @@ interface OutlookRowProps { windDirection: number; windSpeed: number; windGust: number; + temperature: number; } export default function OutlookRow({ @@ -75,6 +71,7 @@ export default function OutlookRow({ windDirection, windSpeed, windGust, + temperature: inCelsius, }: OutlookRowProps) { const timeZone = useAppSelector(timeZoneSelector); if (!timeZone) throw new Error("timeZone needed"); @@ -82,6 +79,8 @@ export default function OutlookRow({ const coordinates = useAppSelector((state) => state.weather.coordinates); if (!coordinates) throw new Error("coordinates not found"); + const temperatureUnit = useAppSelector((state) => state.user.temperatureUnit); + const time = formatInTimeZone(hour, timeZone, "hha"); const compositeSpeed = getCompositeWindValue(windSpeed, windGust) * 0.7; @@ -95,21 +94,42 @@ export default function OutlookRow({ const isDay = SunCalc.getPosition(hour, coordinates.lat, coordinates.lon).altitude > 0; + const temperatureUnitLabel = (() => { + switch (temperatureUnit) { + case TemperatureUnit.Celsius: + return "C"; + case TemperatureUnit.Fahrenheit: + return "F"; + } + })(); + + const temperature = (() => { + switch (temperatureUnit) { + case TemperatureUnit.Celsius: + return inCelsius; + case TemperatureUnit.Fahrenheit: + return cToF(inCelsius); + } + })(); + return ( - - - + {time} + - - + - {hours} + {hours}
- + + + {Math.round(temperature)} {" "} + + - + + 47deg ); } diff --git a/src/features/outlook/OutlookTable.tsx b/src/features/outlook/OutlookTable.tsx index 83756eb..5a27d38 100644 --- a/src/features/outlook/OutlookTable.tsx +++ b/src/features/outlook/OutlookTable.tsx @@ -53,10 +53,15 @@ function NWSOutlookRows({ weather }: { weather: NWSWeather }) { )?.value; const windSpeed = findValue(hour, weather.properties.windSpeed)?.value; const windGust = findValue(hour, weather.properties.windGust)?.value; + const temperature = findValue( + hour, + weather.properties.temperature, + )?.value; if (windDirection == null) return; if (windSpeed == null) return; if (windGust == null) return; + if (temperature == null) return; return ( ); }), @@ -90,6 +96,7 @@ function OpenMeteoOutlookRows({ weather }: { weather: OpenMeteoWeather }) { windDirection={data.windDirection} windSpeed={data.windSpeed} windGust={data.windGust} + temperature={data.temperature} /> ); }), diff --git a/src/features/outlook/WindSpeed.tsx b/src/features/outlook/WindSpeed.tsx index 352dcb3..ca9522b 100644 --- a/src/features/outlook/WindSpeed.tsx +++ b/src/features/outlook/WindSpeed.tsx @@ -2,15 +2,17 @@ import styled from "@emotion/styled"; import { formatWind } from "../../helpers/taf"; import { useAppSelector } from "../../hooks"; import { SpeedUnit } from "metar-taf-parser"; +import { toMph, WindIcon } from "../weather/header/Wind"; +import { HeaderType } from "../weather/WeatherHeader"; +import { faWindsock } from "@fortawesome/pro-duotone-svg-icons"; const Speed = styled.div` word-spacing: -2px; `; -const Gust = styled.div` - font-size: 12px; - word-spacing: -2px; -`; +const StyledWindIcon = styled(WindIcon)` + margin-right: 12px; +`; interface WindSpeedProps { speed: number; gust: number; @@ -22,17 +24,26 @@ export default function WindSpeed({ speed, gust }: WindSpeedProps) { speed, SpeedUnit.KilometersPerHour, speedUnit, + false, ); const gustFormatted = formatWind( gust, SpeedUnit.KilometersPerHour, speedUnit, + false, ); return ( <> - {speedFormatted} - max {gustFormatted} + + + {speedFormatted}G{gustFormatted} + ); } diff --git a/src/features/rap/extra/Extra.tsx b/src/features/rap/extra/Extra.tsx index 6213eb1..0ae6b32 100644 --- a/src/features/rap/extra/Extra.tsx +++ b/src/features/rap/extra/Extra.tsx @@ -77,10 +77,10 @@ export default function Extra() { - {t("Daily Forecast")} + {t("Extended Forecast")} } - title={t("Daily Forecast")} + title={t("Extended Forecast")} > diff --git a/src/features/weather/header/Wind.tsx b/src/features/weather/header/Wind.tsx index 7d76fd6..61d95a2 100644 --- a/src/features/weather/header/Wind.tsx +++ b/src/features/weather/header/Wind.tsx @@ -21,7 +21,7 @@ const colorScale = chroma .scale(["#ffffff66", "#ffffff", "#fffb00", "#ff0000"]) .domain([10, 14, 16, 18]); -const WindIcon = styled(FontAwesomeIcon, { +export const WindIcon = styled(FontAwesomeIcon, { shouldForwardProp: (prop) => prop !== "headerType", })<{ headerType: HeaderType; @@ -130,7 +130,7 @@ export default function Wind({ headerType, date, weather }: WindProps) { ); } -function toMph(speed: number): number { +export function toMph(speed: number): number { return speed * 0.621371; } diff --git a/src/services/nwsWeather.ts b/src/services/nwsWeather.ts index 744c28f..053bc17 100644 --- a/src/services/nwsWeather.ts +++ b/src/services/nwsWeather.ts @@ -19,6 +19,7 @@ export interface NWSWeather extends Coordinates { windSpeed: Property; windGust: Property; windDirection: Property; + temperature: Property; /** * The NWS office, like "MKX" diff --git a/src/services/openMeteo.ts b/src/services/openMeteo.ts index 0789994..4b17d3e 100644 --- a/src/services/openMeteo.ts +++ b/src/services/openMeteo.ts @@ -53,6 +53,7 @@ const WEATHER_VARIABLES = [ "precipitation_probability", "weather_code", "cloud_cover", + "temperature", "wind_speed_10m", "wind_gusts_10m", @@ -87,6 +88,7 @@ interface OpenMeteoWeatherHour { windGust: number; cloudCover: number; windDirection: number; + temperature: number; } export async function getWeather( @@ -139,6 +141,7 @@ function convertOpenMeteoToWeather( windGust: openMeteoResponse.hourly.wind_gusts_10m[index], windDirection: openMeteoResponse.hourly.wind_direction_10m[index], cloudCover: openMeteoResponse.hourly.cloud_cover[index], + temperature: openMeteoResponse.hourly.temperature[index], })), ), }; From 9ea5306ca40a4f2b6df3dd4726d19f9cbaaae56b Mon Sep 17 00:00:00 2001 From: Alexander Harding <2166114+aeharding@users.noreply.github.com> Date: Sun, 6 Oct 2024 12:51:27 -0500 Subject: [PATCH 3/8] Fix layout, international support TODO translations, units? --- src/features/outlook/Day.tsx | 2 +- src/features/outlook/OutlookRow.tsx | 14 ++++++++++---- src/features/weather/weatherSlice.ts | 29 ++++++++++++---------------- src/services/openMeteo.ts | 18 +++++++---------- 4 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/features/outlook/Day.tsx b/src/features/outlook/Day.tsx index 4c96dc3..f30c99c 100644 --- a/src/features/outlook/Day.tsx +++ b/src/features/outlook/Day.tsx @@ -43,7 +43,7 @@ export default function Day({ hours, date }: DayProps) {
); } diff --git a/src/features/outlook/OutlookRow.tsx b/src/features/outlook/OutlookRow.tsx index b3ddb43..7d1045a 100644 --- a/src/features/outlook/OutlookRow.tsx +++ b/src/features/outlook/OutlookRow.tsx @@ -30,6 +30,12 @@ export const windColorScale = chroma .mode("lab"); const Row = styled.tr<{ speed: number; color: string; day: boolean }>` + display: flex; + + > * { + flex: 1; + } + border-bottom: 1px solid #77777715; background: ${({ day }) => (day ? "#ffffff07" : "transparent")}; @@ -114,8 +120,8 @@ export default function OutlookRow({ return ( - {time} - + {time} + @@ -123,13 +129,13 @@ export default function OutlookRow({ {Math.round(temperature)} {" "} + - + - ); } diff --git a/src/features/weather/weatherSlice.ts b/src/features/weather/weatherSlice.ts index 5a1b2f1..4a1f070 100644 --- a/src/features/weather/weatherSlice.ts +++ b/src/features/weather/weatherSlice.ts @@ -585,10 +585,10 @@ export const getWeather = let windsAloft, weather, elevationInM; try { - ({ windsAloft, weather, elevationInM } = await openMeteo.getWindsAloft( - lat, - lon, - )); + [{ windsAloft, elevationInM }, weather] = await Promise.all([ + openMeteo.getWindsAloft(lat, lon), + openMeteo.getWeather(lat, lon), + ]); } catch (error) { if (!isStale()) { dispatch(windsAloftFailed()); @@ -614,14 +614,14 @@ export const getWeather = if (isStale()) return; if (!windsAloft) return; // pending - const { elevation, weather } = windsAloft; + const { elevation } = windsAloft; if (elevation == null) loadElevation(); else dispatch(elevationReceived(elevation)); await Promise.all([ loadNWSAlerts(), - loadWeatherAndDiscussion(weather), + loadWeatherAndDiscussion(), loadAviationWeather(), loadAviationAlerts(), ]); @@ -629,7 +629,6 @@ export const getWeather = async function loadWindsAloft(): Promise< | { elevation?: number; - weather?: Weather; } | undefined > { @@ -661,16 +660,13 @@ export const getWeather = try { // It would be nice in the future to intelligently choose an API // instead of trial and error (and, it would be faster) - const { windsAloft, weather } = await openMeteo.getWindsAloft( - lat, - lon, - ); + const { windsAloft } = await openMeteo.getWindsAloft(lat, lon); if (isStale()) return; dispatch(windsAloftReceived(windsAloft)); - return { elevation: windsAloft.elevationInM, weather }; + return { elevation: windsAloft.elevationInM }; } catch (error) { if (!isStale()) dispatch(windsAloftFailed()); @@ -679,7 +675,7 @@ export const getWeather = } } - async function loadPointData(fallbackWeather?: Weather) { + async function loadPointData() { if (getState().weather.weather === "pending") return; dispatch(weatherLoading()); if (getState().weather.weather !== "pending") return; @@ -731,8 +727,7 @@ export const getWeather = // Likely Mexico or Canada // We still need the timezone, so try to fall back anyways - const weather = - fallbackWeather ?? (await openMeteo.getWeather(lat, lon)); + const weather = await openMeteo.getWeather(lat, lon); if (isStale()) return; @@ -855,8 +850,8 @@ export const getWeather = } } - async function loadWeatherAndDiscussion(fallbackWeather?: Weather) { - const gridId = await loadPointData(fallbackWeather); + async function loadWeatherAndDiscussion() { + const gridId = await loadPointData(); if (isStale()) return; diff --git a/src/services/openMeteo.ts b/src/services/openMeteo.ts index 4b17d3e..dcef612 100644 --- a/src/services/openMeteo.ts +++ b/src/services/openMeteo.ts @@ -8,7 +8,8 @@ import { notEmpty } from "../helpers/array"; import zipObject from "lodash/zipObject"; import * as velitherm from "velitherm"; -const FORECAST_DAYS = 2; +const FORECAST_DAYS = 7; +const FORECAST_DAYS_WINDS_ALOFT = 2; /** * in hPa @@ -115,15 +116,13 @@ export async function getWindsAloft( longitude: number, ): Promise<{ windsAloft: WindsAloftReport; - weather: OpenMeteoWeather; elevationInM: number; }> { - const openMeteoResponse = await getOpenMeteoWindsAloft(latitude, longitude); + const aloft = await getOpenMeteoWindsAloft(latitude, longitude); return { - windsAloft: interpolate(convertOpenMeteoToWindsAloft(openMeteoResponse)), - weather: convertOpenMeteoToWeather(openMeteoResponse), - elevationInM: openMeteoResponse.elevation, + windsAloft: interpolate(convertOpenMeteoToWindsAloft(aloft)), + elevationInM: aloft.elevation, }; } @@ -212,12 +211,9 @@ async function getOpenMeteoWindsAloft( params: { latitude, longitude, - forecast_days: FORECAST_DAYS, + forecast_days: FORECAST_DAYS_WINDS_ALOFT, timeformat: "unixtime", - hourly: [ - ...generateWindsAloftParams(), - ...generateWeatherParams(), - ].join(","), + hourly: generateWindsAloftParams().join(","), }, }) ).data; From 823c40e3219a7d36308e564d610f0d20051b6c22 Mon Sep 17 00:00:00 2001 From: Alexander Harding <2166114+aeharding@users.noreply.github.com> Date: Sun, 6 Oct 2024 13:04:41 -0500 Subject: [PATCH 4/8] Layout tweaks --- src/features/rap/extra/Extra.tsx | 15 ++++++++++++++- src/locales/de.json | 4 +++- src/locales/en.json | 4 +++- src/locales/es.json | 4 +++- src/locales/fr.json | 4 +++- src/locales/nl.json | 4 +++- 6 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/features/rap/extra/Extra.tsx b/src/features/rap/extra/Extra.tsx index 0ae6b32..41368cd 100644 --- a/src/features/rap/extra/Extra.tsx +++ b/src/features/rap/extra/Extra.tsx @@ -45,6 +45,7 @@ const Line = styled.div` > * { flex: 1; + min-width: 0; } `; @@ -149,6 +150,8 @@ const ItemContainer = styled("div", { transition: 100ms linear; transition-property: opacity, filter; + container-type: inline-size; + ${({ loading }) => loading && css` @@ -171,6 +174,7 @@ const IconContainer = styled.div<{ height: 1.85rem; position: relative; margin-right: 1rem; + flex-shrink: 0; &::before { content: ""; @@ -217,6 +221,15 @@ const IconContainer = styled.div<{ const RightArrow = styled(FontAwesomeIcon)` margin-left: auto; opacity: 0.5; + + @container (max-width: 140px) { + display: none; + } +`; + +const Text = styled.span` + text-overflow: ellipsis; + overflow: hidden; `; interface IconProps { @@ -245,7 +258,7 @@ function Item({ {!loading ? : } - {children} + {children} ); diff --git a/src/locales/de.json b/src/locales/de.json index 093111a..51f5dcf 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -42,5 +42,7 @@ "Lapse rate value information": "Temperaturabnahme <0>{{lapseRate}}", "Spread": "Differenz", "Dewpt": "Taupkt", - "Relative humidity acronym": "LF" + "Relative humidity acronym": "LF", + "Extended Forecast": "Erweiterte Vorhersage", + "Metadata": "Metadaten" } diff --git a/src/locales/en.json b/src/locales/en.json index b9db444..3d1a358 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -42,5 +42,7 @@ "Lapse rate value information": "Lapse rate <0>{{lapseRate}}", "Spread": "Spread", "Dewpt": "Dewpt", - "Relative humidity acronym": "RH" + "Relative humidity acronym": "RH", + "Extended Forecast": "Extended Forecast", + "Metadata": "Metadata" } diff --git a/src/locales/es.json b/src/locales/es.json index 7fde8c3..42b31bb 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -42,5 +42,7 @@ "Lapse rate value information": "Tasa de enfriamiento adiabático <0>{{lapseRate}}", "Spread": "Diferencia", "Dewpt": "Écart", - "Relative humidity acronym": "HR" + "Relative humidity acronym": "HR", + "Extended Forecast": "Pronóstico Extendido", + "Metadata": "Metadatos" } diff --git a/src/locales/fr.json b/src/locales/fr.json index 1e26bc3..2dcc9cc 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -42,5 +42,7 @@ "Lapse rate value information": "Valeur du gradient adiabatique <0>{{lapseRate}}", "Spread": "Écart", "Dewpt": "P. rosée", - "Relative humidity acronym": "HR" + "Relative humidity acronym": "HR", + "Extended Forecast": "Prévisions Étendues", + "Metadata": "Métadonnées" } diff --git a/src/locales/nl.json b/src/locales/nl.json index fe7d2cd..d4ce2fc 100644 --- a/src/locales/nl.json +++ b/src/locales/nl.json @@ -42,5 +42,7 @@ "Lapse rate value information": "Dalingsnelheid <0>{{lapseRate}}", "Spread": "Verschil", "Dewpt": "Dauwpnt", - "Relative humidity acronym": "RV" + "Relative humidity acronym": "RV", + "Extended Forecast": "Uitgebreide Voorspelling", + "Metadata": "Metadata" } From eee6f0c36e0a74aa3e1c61ba657a9cec3555ac94 Mon Sep 17 00:00:00 2001 From: Alexander Harding <2166114+aeharding@users.noreply.github.com> Date: Sun, 6 Oct 2024 13:15:05 -0500 Subject: [PATCH 5/8] More tweaks TODO: Weather icon --- src/features/outlook/OutlookRow.tsx | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/features/outlook/OutlookRow.tsx b/src/features/outlook/OutlookRow.tsx index 7d1045a..61719e5 100644 --- a/src/features/outlook/OutlookRow.tsx +++ b/src/features/outlook/OutlookRow.tsx @@ -11,7 +11,10 @@ import { getCompositeWindValue } from "../weather/header/Wind"; import SunCalc from "suncalc"; import { TemperatureText } from "../rap/cells/Temperature"; import { Aside } from "../rap/cells/Altitude"; -import { TemperatureUnit } from "../rap/extra/settings/settingEnums"; +import { + TemperatureUnit, + TimeFormat, +} from "../rap/extra/settings/settingEnums"; import { cToF } from "../weather/aviation/DetailedAviationReport"; export const windColorScale = chroma @@ -62,6 +65,9 @@ const Row = styled.tr<{ speed: number; color: string; day: boolean }>` const TimeCell = styled.td` font-size: 12px; + display: flex; + align-items: center; + justify-content: center; `; interface OutlookRowProps { @@ -79,6 +85,8 @@ export default function OutlookRow({ windGust, temperature: inCelsius, }: OutlookRowProps) { + const timeFormat = useAppSelector((state) => state.user.timeFormat); + const timeZone = useAppSelector(timeZoneSelector); if (!timeZone) throw new Error("timeZone needed"); @@ -87,7 +95,7 @@ export default function OutlookRow({ const temperatureUnit = useAppSelector((state) => state.user.temperatureUnit); - const time = formatInTimeZone(hour, timeZone, "hha"); + const time = formatInTimeZone(hour, timeZone, timeFormatString(timeFormat)); const compositeSpeed = getCompositeWindValue(windSpeed, windGust) * 0.7; @@ -139,3 +147,12 @@ export default function OutlookRow({ ); } + +function timeFormatString(timeFormat: TimeFormat): string { + switch (timeFormat) { + case TimeFormat.Twelve: + return "hha"; + case TimeFormat.TwentyFour: + return "HHmm"; + } +} From 15152d32977875d7fdd4b5ac4eddef86a2e88a90 Mon Sep 17 00:00:00 2001 From: Alexander Harding <2166114+aeharding@users.noreply.github.com> Date: Sun, 6 Oct 2024 13:22:12 -0500 Subject: [PATCH 6/8] Remove commented --- src/features/outlook/OutlookRow.tsx | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/features/outlook/OutlookRow.tsx b/src/features/outlook/OutlookRow.tsx index 61719e5..c36d9e2 100644 --- a/src/features/outlook/OutlookRow.tsx +++ b/src/features/outlook/OutlookRow.tsx @@ -42,25 +42,6 @@ const Row = styled.tr<{ speed: number; color: string; day: boolean }>` border-bottom: 1px solid #77777715; background: ${({ day }) => (day ? "#ffffff07" : "transparent")}; - - /* position: relative; - // https://github.com/w3c/csswg-drafts/issues/1899#issuecomment-338773780 - transform: scale(1); - - &::after { - content: ""; - position: absolute; - z-index: -1; - height: 50px; - inset: 0; - background: linear-gradient( - in oklab 90deg, - ${({ color }) => color}, - ${({ color }) => color} ${({ speed }) => speed}%, - ${({ day }) => (day ? "#ffffff0a" : "transparent")} - calc(${({ speed }) => speed}% + 80px) - ); - } */ `; const TimeCell = styled.td` From bd4901a84f1bd55401b41e0e8c637e6925253a2b Mon Sep 17 00:00:00 2001 From: Alexander Harding <2166114+aeharding@users.noreply.github.com> Date: Sun, 6 Oct 2024 21:23:07 -0500 Subject: [PATCH 7/8] Improvements --- src/features/outlook/Day.tsx | 3 +- src/features/outlook/OutlookRow.tsx | 81 +++++++++++++--------- src/features/outlook/OutlookTable.tsx | 8 +++ src/features/weather/header/NWSWeather.tsx | 48 ++++++++----- src/features/weather/header/WMOWeather.tsx | 33 ++++++--- src/features/weather/header/Weather.tsx | 18 +++-- src/shared/Tooltip.tsx | 8 ++- 7 files changed, 136 insertions(+), 63 deletions(-) diff --git a/src/features/outlook/Day.tsx b/src/features/outlook/Day.tsx index f30c99c..dc24155 100644 --- a/src/features/outlook/Day.tsx +++ b/src/features/outlook/Day.tsx @@ -16,6 +16,7 @@ const Table = styled.table` const THead = styled.thead` position: sticky; top: 0; + transform: translateY(-0.5px); z-index: 1; background: var(--bg-bottom-sheet); `; @@ -38,7 +39,7 @@ export default function Day({ hours, date }: DayProps) { - + {formatInTimeZone(date, timeZone, "eeee, LLL d")} diff --git a/src/features/outlook/OutlookRow.tsx b/src/features/outlook/OutlookRow.tsx index c36d9e2..4a6633c 100644 --- a/src/features/outlook/OutlookRow.tsx +++ b/src/features/outlook/OutlookRow.tsx @@ -4,10 +4,15 @@ import { useAppSelector } from "../../hooks"; import styled from "@emotion/styled"; import WindIndicator from "../rap/WindIndicator"; import WindSpeed from "./WindSpeed"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faMoon, faSun } from "@fortawesome/pro-duotone-svg-icons"; -import chroma from "chroma-js"; -import { getCompositeWindValue } from "../weather/header/Wind"; +import { + faCloudMoon, + faClouds, + faCloudsMoon, + faCloudsSun, + faCloudSun, + faMoon, + faSun, +} from "@fortawesome/pro-duotone-svg-icons"; import SunCalc from "suncalc"; import { TemperatureText } from "../rap/cells/Temperature"; import { Aside } from "../rap/cells/Altitude"; @@ -16,23 +21,11 @@ import { TimeFormat, } from "../rap/extra/settings/settingEnums"; import { cToF } from "../weather/aviation/DetailedAviationReport"; +import { Observations } from "../weather/header/Weather"; +import { NWSWeatherObservation } from "../../services/nwsWeather"; +import { IconProp } from "@fortawesome/fontawesome-svg-core"; -export const windColorScale = chroma - .scale([ - "#00FF0099", - "#00FF0099", - "#eaff0099", - "#FFA50099", - "#FF000099", - "#FF000099", - "#FF10F0", - "#AD2AFF", - "white", - ]) - .domain([0, 5.56, 18, 25.93, 55.56, 64.82, 138.9, 185.2, 296.32]) - .mode("lab"); - -const Row = styled.tr<{ speed: number; color: string; day: boolean }>` +const Row = styled.tr<{ day: boolean }>` display: flex; > * { @@ -51,12 +44,19 @@ const TimeCell = styled.td` justify-content: center; `; +const StyledObservations = styled(Observations)` + font-size: 1em; + margin-right: 0; +`; + interface OutlookRowProps { hour: Date; windDirection: number; windSpeed: number; windGust: number; temperature: number; + observations: NWSWeatherObservation[] | number; + skyCover: number; } export default function OutlookRow({ @@ -65,6 +65,8 @@ export default function OutlookRow({ windSpeed, windGust, temperature: inCelsius, + observations, + skyCover, }: OutlookRowProps) { const timeFormat = useAppSelector((state) => state.user.timeFormat); @@ -78,14 +80,6 @@ export default function OutlookRow({ const time = formatInTimeZone(hour, timeZone, timeFormatString(timeFormat)); - const compositeSpeed = getCompositeWindValue(windSpeed, windGust) * 0.7; - - const color = (() => { - const [r, g, b, a] = windColorScale(compositeSpeed).rgba(); - - return `color(display-p3 ${r / 255} ${g / 255} ${b / 255} / ${a})`; - })(); - const isDay = SunCalc.getPosition(hour, coordinates.lat, coordinates.lon).altitude > 0; @@ -107,11 +101,23 @@ export default function OutlookRow({ } })(); + console.log(observations); + return ( - + {time} -
- + + @@ -137,3 +143,16 @@ function timeFormatString(timeFormat: TimeFormat): string { return "HHmm"; } } + +function getDefaultIcon(skyCover: number, isDay: boolean): IconProp { + switch (true) { + case skyCover < 20: + return isDay ? faSun : faMoon; + case skyCover < 60: + return isDay ? faCloudSun : faCloudMoon; + case skyCover < 80: + return isDay ? faCloudsSun : faCloudsMoon; + default: + return faClouds; + } +} diff --git a/src/features/outlook/OutlookTable.tsx b/src/features/outlook/OutlookTable.tsx index 5a27d38..6d65bad 100644 --- a/src/features/outlook/OutlookTable.tsx +++ b/src/features/outlook/OutlookTable.tsx @@ -57,11 +57,15 @@ function NWSOutlookRows({ weather }: { weather: NWSWeather }) { hour, weather.properties.temperature, )?.value; + const observations = findValue(hour, weather.properties.weather)?.value; + const skyCover = findValue(hour, weather.properties.skyCover)?.value; if (windDirection == null) return; if (windSpeed == null) return; if (windGust == null) return; if (temperature == null) return; + if (observations == null) return; + if (skyCover == null) return; return ( ); }), @@ -97,6 +103,8 @@ function OpenMeteoOutlookRows({ weather }: { weather: OpenMeteoWeather }) { windSpeed={data.windSpeed} windGust={data.windGust} temperature={data.temperature} + observations={data.weather} + skyCover={data.cloudCover} /> ); }), diff --git a/src/features/weather/header/NWSWeather.tsx b/src/features/weather/header/NWSWeather.tsx index 37c4e3f..d3afc32 100644 --- a/src/features/weather/header/NWSWeather.tsx +++ b/src/features/weather/header/NWSWeather.tsx @@ -24,16 +24,24 @@ const Flex = styled.div` interface NWSWeatherProps { observations: NWSWeatherObservation[]; + defaultIcon?: IconProp; + className?: string; } -export default function NWSWeather({ observations }: NWSWeatherProps) { +export default function NWSWeather({ + observations, + defaultIcon, + ...rest +}: NWSWeatherProps) { const observation: NWSWeatherObservation | undefined = observations.find(({ weather }) => weather === "thunderstorms") || observations[0]; - if (!observation) return <>; + if (!observation) return renderWithIcon(defaultIcon); function renderTooltip() { + if (observations.every(({ weather }) => !weather)) return; + return capitalize( observations .map((observation) => @@ -45,21 +53,29 @@ export default function NWSWeather({ observations }: NWSWeatherProps) { const icon = findIconFor(observation); - if (!icon) return <>; + if (!icon) return renderWithIcon(defaultIcon); + + return renderWithIcon(icon); + + function renderWithIcon(icon: IconProp | undefined) { + if (!icon) return <>; - return ( - - - - - - ); + return ( + + + + + + ); + } } function findIconFor(observation: NWSWeatherObservation): IconProp | undefined { diff --git a/src/features/weather/header/WMOWeather.tsx b/src/features/weather/header/WMOWeather.tsx index 1a50241..31447aa 100644 --- a/src/features/weather/header/WMOWeather.tsx +++ b/src/features/weather/header/WMOWeather.tsx @@ -8,6 +8,7 @@ import { import { faSnowflake } from "@fortawesome/pro-light-svg-icons"; import { convertTitleCaseToSpaces } from "../../../helpers/string"; import Tooltip from "../../../shared/Tooltip"; +import { IconProp } from "@fortawesome/fontawesome-svg-core"; const Flex = styled.div` display: flex; @@ -47,9 +48,15 @@ enum WMOWeatherCode { interface WMOWeatherCodeProps { wmoCode: WMOWeatherCode; + defaultIcon?: IconProp; + className?: string; } -export default function WMOWeather({ wmoCode }: WMOWeatherCodeProps) { +export default function WMOWeather({ + wmoCode, + defaultIcon, + ...rest +}: WMOWeatherCodeProps) { const icon = (() => { switch (wmoCode) { case WMOWeatherCode.LightRainShowers: @@ -82,13 +89,21 @@ export default function WMOWeather({ wmoCode }: WMOWeatherCodeProps) { } })(); - if (!icon) return <>; + if (!icon) return renderWithIcon(defaultIcon); - return ( - convertTitleCaseToSpaces(WMOWeatherCode[wmoCode])}> - - - - - ); + return renderWithIcon(icon); + + function renderWithIcon(icon: IconProp | undefined) { + if (!icon) return <>; + + return ( + convertTitleCaseToSpaces(WMOWeatherCode[wmoCode])} + > + + + + + ); + } } diff --git a/src/features/weather/header/Weather.tsx b/src/features/weather/header/Weather.tsx index 7686187..a14dd32 100644 --- a/src/features/weather/header/Weather.tsx +++ b/src/features/weather/header/Weather.tsx @@ -4,12 +4,13 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useMemo } from "react"; import { outputP3ColorFromRGB } from "../../../helpers/colors"; -import { findValue } from "../../../services/nwsWeather"; +import { findValue, NWSWeatherObservation } from "../../../services/nwsWeather"; import { WeatherResult } from "../weatherSlice"; import { keyframes } from "@emotion/css"; import { css } from "@emotion/react"; import NWSWeather from "./NWSWeather"; import WMOWeather from "./WMOWeather"; +import { IconProp } from "@fortawesome/fontawesome-svg-core"; const thunderAnimate = keyframes` 0% { @@ -103,8 +104,17 @@ export default function Weather({ date, weather }: WeatherProps) { if (!observations) return <>; - if (Array.isArray(observations)) - return ; + return ; +} + +interface ObservationsProps { + data: number | NWSWeatherObservation[]; + defaultIcon?: IconProp | undefined; + className?: string; +} + +export function Observations({ data, ...rest }: ObservationsProps) { + if (Array.isArray(data)) return ; - return ; + return ; } diff --git a/src/shared/Tooltip.tsx b/src/shared/Tooltip.tsx index c3dbcfc..297c853 100644 --- a/src/shared/Tooltip.tsx +++ b/src/shared/Tooltip.tsx @@ -52,7 +52,7 @@ export const TooltipContainer = styled.div<{ interactive: boolean }>` interface TooltipProps { children?: React.ReactNode; - contents: () => React.ReactNode; + contents: () => React.ReactNode | undefined; mouseOnly?: boolean; interactive?: boolean; offset?: number; @@ -110,6 +110,10 @@ export default function Tooltip({ }); }, [isMounted]); + const renderedContent = contents(); + + if (!renderedContent) return children; + return ( <> to determine if tooltip is open */ interactive={interactive ?? false} > - {contents()} + {renderedContent} From 14c6efb32af97e7c62c9ae056006f353de46421b Mon Sep 17 00:00:00 2001 From: Alexander Harding <2166114+aeharding@users.noreply.github.com> Date: Sun, 6 Oct 2024 21:30:09 -0500 Subject: [PATCH 8/8] do not export --- src/features/rap/cells/WindSpeed.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/rap/cells/WindSpeed.tsx b/src/features/rap/cells/WindSpeed.tsx index 3071d2c..b0870ac 100644 --- a/src/features/rap/cells/WindSpeed.tsx +++ b/src/features/rap/cells/WindSpeed.tsx @@ -8,7 +8,7 @@ import { useMemo } from "react"; import { useAppSelector } from "../../../hooks"; import { SpeedUnit } from "../extra/settings/settingEnums"; -export const windColorScale = chroma +const colorScale = chroma .scale([ "#00FF00", "#00FF00", @@ -27,7 +27,7 @@ export const windColorScale = chroma const WindSpeedContainer = styled.div<{ speed: number; shear: boolean }>` position: relative; - ${({ speed }) => outputP3ColorFromLab(windColorScale(speed).lab())}; + ${({ speed }) => outputP3ColorFromLab(colorScale(speed).lab())}; ${({ shear }) => shear &&