New Weather API
All checks were successful
Deploy to production / build (push) Successful in 11s

This commit is contained in:
Sean Edmunds
2026-07-13 14:30:56 -05:00
parent f14fee945b
commit 3d5a2dd88a
20 changed files with 847 additions and 1025 deletions

632
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"@gfazioli/mantine-text-animate": "^4.0.6",
"@gfazioli/mantine-text-animate": "^4.0.7",
"@hello-pangea/dnd": "^18.0.1",
"@mantine/core": "^9.4.1",
"@mantine/dates": "^9.4.1",
@@ -23,6 +23,7 @@
"@tanstack/react-table": "^8.21.3",
"idb-keyval": "^6.3.0",
"jotai": "^2.20.1",
"openmeteo": "^1.2.3",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"react-fontpicker-ts": "^1.3.0",

View File

@@ -1,27 +1,63 @@
import { Box, Card, Group, Stack, Text } from '@mantine/core';
import { useCommonHooks, useSunStateAPI } from 'hooks';
import { useCommonHooks } from 'hooks';
import { FaLongArrowAltDown, FaLongArrowAltUp, FaWind } from 'react-icons/fa';
import { WiBarometer, WiHumidity, WiSunrise, WiSunset } from 'react-icons/wi';
import { textClasses } from 'styles';
import type { CurrentWeather as CW, SunState } from 'types';
import type { MeteoWeather } from 'types';
import WeatherFeaturePill from './WeatherFeaturePill';
import WeatherIcon from './WeatherIcon';
export default function CurrentWeather({ current }: { current: CW }) {
const { currentLocation, isMobile, theme, textShadowStyle } = useCommonHooks();
const { data: sunStateData } = useSunStateAPI({
lat: currentLocation.lat,
lon: currentLocation.lon,
});
const WeatherCodes: { [key: number]: string } = {
0: 'Clear sky',
const sunStates: SunState[] =
sunStateData?.results.map((item: SunState) => {
return {
date: item.date,
sunrise: item.sunrise,
sunset: item.sunset,
};
}) || [];
1: 'Mainly clear',
2: 'Partly cloudy',
3: 'Overcast',
45: 'Fog',
48: 'Depositing rime fog',
51: 'Drizzle: Light',
53: 'Drizzle: Moderate',
55: 'Drizzle: Dense',
56: 'Freezing Drizzle: Light',
57: 'Freezing Drizzle: Dense',
61: 'Rain: Slight',
63: 'Rain: Moderate',
65: 'Rain: Heavy',
66: 'Freezing Rain: Light',
67: 'Freezing Rain: Heavy',
71: 'Snowfall: Slight',
73: 'Snowfall: Moderate',
75: 'Snowfall: Heavy',
77: 'Snow grains',
80: 'Rain showers: Slight',
81: 'Rain showers: Moderate',
82: 'Rain showers: Violent',
85: 'Snow showers: Slight',
86: 'Snow showers: Heavy',
95: 'Thunderstorm: Slight or moderate',
96: 'Thunderstorm with slight hail',
99: 'Thunderstorm with heavy hail',
} as const;
export default function CurrentWeather({
current,
}: {
current: MeteoWeather | undefined;
}) {
const { isMobile, theme, textShadowStyle } = useCommonHooks();
const sunrise = new Date(current?.daily.sunrise[0] || 0).toLocaleTimeString();
const sunset = new Date(current?.daily.sunset[0] || 0).toLocaleTimeString();
return (
<>
@@ -71,25 +107,11 @@ export default function CurrentWeather({ current }: { current: CW }) {
opacity: 0.5,
}}
>
<WeatherIcon
size={'lg'}
textDescription={`${
current.textDescription.includes('and')
? current.textDescription.substring(
0,
current.textDescription.indexOf('and'),
)
: current.textDescription.includes('then')
? current.textDescription.substring(
0,
current.textDescription.indexOf('then'),
)
: current.textDescription
}`}
/>
<WeatherIcon size={'lg'} code={current.current.weather_code} />
</Box>
<Text fz={isMobile ? '5rem' : '7rem'} fw={400}>
{current.temperature}°<sup style={{ fontSize: '3rem' }}>F</sup>
{current.current.temperature_2m?.toFixed(0)}°
<sup style={{ fontSize: '3rem' }}>F</sup>
</Text>
<Box
style={{
@@ -108,7 +130,11 @@ export default function CurrentWeather({ current }: { current: CW }) {
/>
</Group>
<Group
justify={current.textDescription.length > 25 ? 'flex-start' : 'center'}
justify={
WeatherCodes[Number(current.current.weather_code)].length > 25
? 'flex-start'
: 'center'
}
style={{
width: '100%',
maxWidth: '180px',
@@ -120,13 +146,15 @@ export default function CurrentWeather({ current }: { current: CW }) {
>
<Text
className={
current.textDescription.length > 25
WeatherCodes[Number(current.current.weather_code)].length > 25
? textClasses.autoScrollText
: undefined
}
classNames={textClasses}
>
{current.temperature === 69 ? 'Nice' : current.textDescription}
{current.current.temperature_2m?.toFixed(0) === '69'
? 'Nice'
: WeatherCodes[Number(current.current.weather_code)]}
</Text>
</Group>
</Stack>
@@ -134,8 +162,9 @@ export default function CurrentWeather({ current }: { current: CW }) {
leftSide={<Text>Feels like </Text>}
rightSide={
<Text>
{current.heatIndex ?? current.windChill ?? current.temperature}°
<sup>F</sup>
{current.current.apparent_temperature?.toFixed(0) ??
current.current.temperature_2m?.toFixed(0)}
°<sup>F</sup>
</Text>
}
highlight='right'
@@ -164,7 +193,7 @@ export default function CurrentWeather({ current }: { current: CW }) {
}
rightSide={
<Text>
{current.highTemp}°<sup>F</sup>
{current.daily.temperature_2m_max?.[0].toFixed(0)}°<sup>F</sup>
</Text>
}
highlight='right'
@@ -178,7 +207,7 @@ export default function CurrentWeather({ current }: { current: CW }) {
}
rightSide={
<Text>
{current.lowTemp}°<sup>F</sup>
{current.daily.temperature_2m_min?.[0].toFixed(0)}°<sup>F</sup>
</Text>
}
highlight='right'
@@ -205,7 +234,7 @@ export default function CurrentWeather({ current }: { current: CW }) {
Humidity
</Group>
}
rightSide={<Text>{current.humidity}%</Text>}
rightSide={<Text>{current.current.relative_humidity_2m}%</Text>}
highlight='right'
/>
<WeatherFeaturePill
@@ -215,7 +244,9 @@ export default function CurrentWeather({ current }: { current: CW }) {
Wind
</Group>
}
rightSide={<Text>{current.windSpeed}mph</Text>}
rightSide={
<Text>{current.current.wind_speed_10m?.toFixed(0)}mph</Text>
}
highlight='right'
/>
<WeatherFeaturePill
@@ -225,7 +256,9 @@ export default function CurrentWeather({ current }: { current: CW }) {
Pressure
</Group>
}
rightSide={<Text>{current.pressure} in</Text>}
rightSide={
<Text>{current.current.surface_pressure?.toFixed(0)} mbar</Text>
}
highlight='right'
/>
</Group>
@@ -257,7 +290,7 @@ export default function CurrentWeather({ current }: { current: CW }) {
/>
</Group>
}
rightSide={<Text>{sunStates[0]?.sunrise}</Text>}
rightSide={<Text>{sunrise}</Text>}
highlight='left'
/>
<WeatherFeaturePill
@@ -274,7 +307,7 @@ export default function CurrentWeather({ current }: { current: CW }) {
/>
</Group>
}
rightSide={<Text>{sunStates[0]?.sunset}</Text>}
rightSide={<Text>{sunset}</Text>}
highlight='left'
/>
</Group>

View File

@@ -1,17 +1,78 @@
import { Card, Group, SimpleGrid, Stack, Text } from '@mantine/core';
import { useCommonHooks } from 'hooks';
import { textClasses } from 'styles';
import type { ExtendedForecast } from 'types';
import type { Daily } from 'types';
import WeatherIcon from './WeatherIcon';
export default function ExtendedForecastCard({
forecast,
}: {
forecast: ExtendedForecast[];
}) {
const WeatherCodes: { [key: number]: string } = {
0: 'Clear sky',
1: 'Mainly clear',
2: 'Partly cloudy',
3: 'Overcast',
45: 'Fog',
48: 'Depositing rime fog',
51: 'Drizzle: Light',
53: 'Drizzle: Moderate',
55: 'Drizzle: Dense',
56: 'Freezing Drizzle: Light',
57: 'Freezing Drizzle: Dense',
61: 'Rain: Slight',
63: 'Rain: Moderate',
65: 'Rain: Heavy',
66: 'Freezing Rain: Light',
67: 'Freezing Rain: Heavy',
71: 'Snowfall: Slight',
73: 'Snowfall: Moderate',
75: 'Snowfall: Heavy',
77: 'Snow grains',
80: 'Rain showers: Slight',
81: 'Rain showers: Moderate',
82: 'Rain showers: Violent',
85: 'Snow showers: Slight',
86: 'Snow showers: Heavy',
95: 'Thunderstorm: Slight or moderate',
96: 'Thunderstorm with slight hail',
99: 'Thunderstorm with heavy hail',
} as const;
export default function ExtendedForecastCard({ daily }: { daily: Daily | undefined }) {
const { isMobile, theme } = useCommonHooks();
const day = forecast.slice(2, 14).filter((item: ExtendedForecast) => item.isDaytime);
const night = forecast.slice(2, 14).filter((item: ExtendedForecast) => !item.isDaytime);
const forecast = daily?.time.map((_: any, index: number) => {
const time = new Date(daily.time[index]);
time.setMinutes(daily.time[index].getTimezoneOffset() + time.getMinutes());
return {
apparentTemperatureMax: daily.apparent_temperature_max?.[index],
apparentTemperatureMin: daily.apparent_temperature_min?.[index],
daylightDuration: daily.daylight_duration?.[index],
precipitationHours: daily.precipitation_hours?.[index],
precipitationProbability: daily.precipitation_probability_max?.[index],
precipitationSum: daily.precipitation_sum?.[index],
sunrise: daily.sunrise[index],
sunset: daily.sunset[index],
sunshineDuration: daily.sunshine_duration?.[index],
temperatureMax: daily.temperature_2m_max?.[index],
temperatureMin: daily.temperature_2m_min?.[index],
time: time,
unIndexClearSkyMax: daily.uv_index_clear_sky_max?.[index],
uvIndexMax: daily.uv_index_max?.[index],
weatherCode: daily.weather_code?.[index],
windDirection: daily.wind_direction_10m_dominant?.[index],
windGusts: daily.wind_gusts_10m_max?.[index],
windSpeed: daily.wind_speed_10m_max?.[index],
};
});
return (
<Card
w={'100%'}
@@ -37,13 +98,7 @@ export default function ExtendedForecastCard({
Extended Forecast
</Text>
<SimpleGrid w={'100%'} pt={'.5rem'} cols={isMobile ? 3 : 6}>
{day.map((item: ExtendedForecast, index: number) => {
const short = item.shortForecast.includes('And')
? item.shortForecast.substring(0, item.shortForecast.indexOf('And'))
: item.shortForecast.includes('then')
? item.shortForecast.substring(0, item.shortForecast.indexOf('then'))
: item.shortForecast;
{forecast?.slice(1, 7).map((item: any) => {
return (
<Stack
key={item.name}
@@ -55,7 +110,11 @@ export default function ExtendedForecastCard({
}}
>
<Group
justify={item.name.length > 15 ? 'flex-start' : 'center'}
justify={
item.time.toLocaleDateString('en-US', { weekday: 'long' }).length > 15
? 'flex-start'
: 'center'
}
style={{
width: '100%',
whiteSpace: 'nowrap',
@@ -66,16 +125,20 @@ export default function ExtendedForecastCard({
>
<Text
className={
item.name.length > 15 ? textClasses.autoScrollText : undefined
item.time.toLocaleDateString('en-US', { weekday: 'long' }).length > 15
? textClasses.autoScrollText
: undefined
}
classNames={textClasses}
>
{item.name}
{item.time.toLocaleDateString('en-US', { weekday: 'long' })}
</Text>
</Group>
<WeatherIcon size='md' textDescription={short} />
<WeatherIcon size='md' code={item.weatherCode} />
<Group
justify={short.length > 15 ? 'flex-start' : 'center'}
justify={
WeatherCodes[item.weatherCode].length > 15 ? 'flex-start' : 'center'
}
style={{
width: '100%',
whiteSpace: 'nowrap',
@@ -85,15 +148,18 @@ export default function ExtendedForecastCard({
}}
>
<Text
className={short.length > 15 ? textClasses.autoScrollText : undefined}
className={
WeatherCodes[item.weatherCode].length > 15
? textClasses.autoScrollText
: undefined
}
classNames={textClasses}
>
{short}
{WeatherCodes[item.weatherCode]}
</Text>
</Group>
<Text>
{item.temperature}°{item.temperatureUnit}/{night[index].temperature}°
{item.temperatureUnit}
{item.temperatureMax.toFixed(0)}°F/{item.temperatureMin.toFixed(0)}°F
</Text>
</Stack>
);

View File

@@ -11,16 +11,31 @@ import {
XAxis,
YAxis,
} from 'recharts';
import type { HourlyForecast } from 'types';
import type { Hourly } from 'types';
import WeatherTooltip from './WeatherTooltip';
export default function WeatherGraphs({
hourlyForecast,
}: {
hourlyForecast: HourlyForecast[];
}) {
export default function WeatherGraphs({ hourly }: { hourly: Hourly | undefined }) {
const colorPrecipitation = 'colorPrecipitation';
const colorHumidity = 'colorHumidity';
const later = new Date();
const now = new Date();
later.setDate(now.getDate() + 1);
const hourlyForecast = hourly?.time
?.map((_: any, index: number) => {
return {
dewpoint: hourly.dew_point_2m?.[index].toFixed(0),
isDaytime: hourly.is_day?.[index],
name: hourly.time[index],
startTime: hourly.time[index],
endTime: hourly.time[index + 1],
probabilityOfPrecipitation: hourly.precipitation_probability?.[index],
temperature: hourly.temperature_2m?.[index].toFixed(0),
relativeHumidity: hourly.relative_humidity_2m?.[index].toFixed(0),
};
})
.filter((hour: any) => hour.startTime > now && hour.startTime < later);
return (
<Stack
style={{
@@ -34,7 +49,7 @@ export default function WeatherGraphs({
<XAxis
type='category'
stroke={'var(--mantine-color-primary-0)'}
dataKey={(data: HourlyForecast) => {
dataKey={(data) => {
return new Date(data.startTime).toLocaleTimeString('en-US', {
hour: 'numeric',
minute: 'numeric',
@@ -49,7 +64,7 @@ export default function WeatherGraphs({
}}
/>
<CartesianGrid stroke='#f5f5f522' />
<Tooltip content={<WeatherTooltip />} />
<Tooltip wrapperStyle={{ zIndex: 100 }} content={<WeatherTooltip />} />
<Line
unit={'°F'}
type='monotone'
@@ -67,9 +82,7 @@ export default function WeatherGraphs({
unit={'°F'}
type='monotone'
name='Dew Point'
dataKey={(data: HourlyForecast) => {
return (data.dewpoint.value * 9) / 5 + 32;
}}
dataKey='dewpoint'
stroke={'var(--mantine-color-accent-0)'}
strokeWidth={2}
dot={{
@@ -78,7 +91,7 @@ export default function WeatherGraphs({
fill: 'var(--mantine-color-accent-0)',
}}
/>
<Legend style={{ background: 'var(--mantine-color-search-0)' }} />
<Legend wrapperStyle={{ zIndex: 0, position: 'absolute' }} />
</ComposedChart>
</ResponsiveContainer>
<ResponsiveContainer width={'100%'} height={150}>
@@ -112,7 +125,7 @@ export default function WeatherGraphs({
<XAxis
type='category'
stroke={'var(--mantine-color-primary-0)'}
dataKey={(data: HourlyForecast) => {
dataKey={(data) => {
return new Date(data.startTime).toLocaleTimeString('en-US', {
hour: 'numeric',
minute: 'numeric',
@@ -121,14 +134,12 @@ export default function WeatherGraphs({
/>
<YAxis stroke={'var(--mantine-color-primary-0)'} unit={'%'} domain={[0, 100]} />
<CartesianGrid stroke='#f5f5f522' />
<Tooltip content={<WeatherTooltip />} />
<Tooltip wrapperStyle={{ zIndex: 100 }} content={<WeatherTooltip />} />
<Area
unit={'%'}
type='monotone'
name='Humidity'
dataKey={(data: HourlyForecast) => {
return data.relativeHumidity.value;
}}
dataKey={'relativeHumidity'}
stroke={'var(--mantine-color-primary-0)'}
strokeWidth={2}
fill={`url(#${colorPrecipitation})`}
@@ -142,9 +153,7 @@ export default function WeatherGraphs({
unit={'%'}
type='monotone'
name='Precipitation'
dataKey={(data: HourlyForecast) => {
return data.probabilityOfPrecipitation.value;
}}
dataKey={'probabilityOfPrecipitation'}
stroke={'var(--mantine-color-accent-0)'}
strokeWidth={2}
fill={`url(#${colorHumidity})`}
@@ -154,7 +163,7 @@ export default function WeatherGraphs({
fill: 'var(--mantine-color-accent-0)',
}}
/>
<Legend style={{ background: 'var(--mantine-color-search-0)' }} />
<Legend wrapperStyle={{ zIndex: 0, position: 'absolute' }} />
</AreaChart>
</ResponsiveContainer>
</Stack>

View File

@@ -1,5 +1,4 @@
import { Group, Image } from '@mantine/core';
import { FaArrowRight } from 'react-icons/fa';
import { Image } from '@mantine/core';
import CloudyIcon from '../../assets/weather/cloudy.svg';
import HazeIcon from '../../assets/weather/haze.svg';
import HeavyRainIcon from '../../assets/weather/heavy-rain.svg';
@@ -9,168 +8,166 @@ import SleetIcon from '../../assets/weather/sleet.svg';
import SnowIcon from '../../assets/weather/snow.svg';
import SunnyIcon from '../../assets/weather/sunny.svg';
import ThunderstormIcon from '../../assets/weather/thunderstorm.svg';
import WindyIcon from '../../assets/weather/windy.svg';
export default function WeatherIcon({
size,
textDescription,
code,
}: {
size: string;
textDescription: string;
code: number | undefined;
}) {
const icon = textDescription.includes('then')
? [
textDescription.substring(0, textDescription.trim().indexOf('then')),
textDescription.substring(textDescription.trim().indexOf('then') + 4).trim(),
]
: [textDescription.trim()];
const lookupSource = (icon: any) => {
switch (icon.trim()) {
case 'Ash':
const lookupSource = (code: any) => {
switch (code) {
case 0:
return {
src: HazeIcon,
alt: 'HazeIcon',
src: SunnyIcon,
alt: 'SunnyIcon',
};
case 'Broken Clouds':
case 1:
return {
src: PartlyCloudyIcon,
alt: 'PartlyCloudyIcon',
};
case 'Chance Rain Showers':
case 2:
return {
src: RainIcon,
alt: 'RainIcon',
src: PartlyCloudyIcon,
alt: 'PartlyCloudyIcon',
};
case 'Chance Showers':
return {
src: RainIcon,
alt: 'RainIcon',
};
case 'Slight Chance Showers':
return {
src: RainIcon,
alt: 'RainIcon',
};
case 'Cloudy':
case 3:
return {
src: CloudyIcon,
alt: 'CloudyIcon',
};
case 'Dust':
case 45:
return {
src: HazeIcon,
alt: 'HazeIcon',
};
case 'Drizzle':
case 48:
return {
src: HazeIcon,
alt: 'HazeIcon',
};
case 51:
return {
src: RainIcon,
alt: 'RainIcon',
};
case 'Fog':
case 53:
return {
src: HazeIcon,
alt: 'HazeIcon',
src: RainIcon,
alt: 'RainIcon',
};
case 'Freezing Rain':
case 55:
return {
src: RainIcon,
alt: 'RainIcon',
};
case 56:
return {
src: SleetIcon,
alt: 'SleetIcon',
};
case 'Haze':
case 57:
return {
src: HazeIcon,
alt: 'HazeIcon',
src: SleetIcon,
alt: 'SleetIcon',
};
case 'Heavy Rain':
case 61:
return {
src: RainIcon,
alt: 'RainIcon',
};
case 63:
return {
src: RainIcon,
alt: 'RainIcon',
};
case 65:
return {
src: RainIcon,
alt: 'RainIcon',
};
case 66:
return {
src: SleetIcon,
alt: 'SleetIcon',
};
case 67:
return {
src: SleetIcon,
alt: 'SleetIcon',
};
case 71:
return {
src: SnowIcon,
alt: 'SnowIcon',
};
case 73:
return {
src: SnowIcon,
alt: 'SnowIcon',
};
case 75:
return {
src: SnowIcon,
alt: 'SnowIcon',
};
case 77:
return {
src: SnowIcon,
alt: 'SnowIcon',
};
case 80:
return {
src: HeavyRainIcon,
alt: 'HeavyRainIcon',
};
case 'Heavy Snow':
case 81:
return {
src: HeavyRainIcon,
alt: 'HeavyRainIcon',
};
case 82:
return {
src: HeavyRainIcon,
alt: 'HeavyRainIcon',
};
case 85:
return {
src: SnowIcon,
alt: 'SnowIcon',
};
case 'Light Rain':
return {
src: RainIcon,
alt: 'RainIcon',
};
case 'Mist':
return {
src: HazeIcon,
alt: 'HazeIcon',
};
case 'Moderate Rain':
return {
src: RainIcon,
alt: 'RainIcon',
};
case 'Mostly Cloudy':
return {
src: CloudyIcon,
alt: 'CloudyIcon',
};
case 'Overcast Clouds':
return {
src: PartlyCloudyIcon,
alt: 'PartlyCloudyIcon',
};
case 'Patchy Fog':
return {
src: HazeIcon,
alt: 'HazeIcon',
};
case 'Partly Sunny':
return {
src: PartlyCloudyIcon,
alt: 'PartlyCloudyIcon',
};
case 'Rain':
return {
src: RainIcon,
alt: 'RainIcon',
};
case 'Scattered Clouds':
return {
src: PartlyCloudyIcon,
alt: 'PartlyCloudyIcon',
};
case 'Showers':
return {
src: RainIcon,
alt: 'RainIcon',
};
case 'Sleet':
return {
src: SleetIcon,
alt: 'SleetIcon',
};
case 'Smoke':
return {
src: HazeIcon,
alt: 'HazeIcon',
};
case 'Snow':
case 86:
return {
src: SnowIcon,
alt: 'SnowIcon',
};
case 'Squall':
return {
src: WindyIcon,
alt: 'WindyIcon',
};
case 'Thunderstorm':
case 95:
return {
src: ThunderstormIcon,
alt: 'ThunderstormIcon',
};
case 'Tornado':
case 96:
return {
src: WindyIcon,
alt: 'WindyIcon',
src: ThunderstormIcon,
alt: 'ThunderstormIcon',
};
case 99:
return {
src: ThunderstormIcon,
alt: 'ThunderstormIcon',
};
default:
@@ -182,61 +179,14 @@ export default function WeatherIcon({
};
return (
<>
{icon.length === 1 ? (
<Image
style={
size === 'lg'
? { width: '150px', height: '150px' }
: { width: '75px', height: '75px' }
}
src={lookupSource(icon[0]).src}
alt={lookupSource(icon[0]).alt}
/>
) : (
<Group
pos={'relative'}
style={
size === 'lg'
? { width: '150px', height: '150px' }
: { width: '75px', height: '75px' }
}
align='center'
justify='center'
>
<Image
style={{
mask: 'linear-gradient(105deg, rgba(0, 0, 0, 1) 52%, rgba(0, 0, 0, 0) 56%)',
}}
pos={'absolute'}
left={-8}
src={lookupSource(icon[0]).src}
alt={lookupSource(icon[0]).alt}
/>
<Image
style={{
mask: 'linear-gradient(105deg, rgba(0, 0, 0, 0) 44%, rgba(0, 0, 0, 1) 48%)',
}}
pos={'absolute'}
right={-8}
src={lookupSource(icon[1]).src}
alt={lookupSource(icon[1]).alt}
/>
<FaArrowRight
size={28}
style={{
color: 'var(--mantine-color-background-0)',
position: 'absolute',
stroke: 'var(--mantine-color-primary-0)',
strokeWidth: 16,
backgroundColor:
'color-mix(in srgb, var(--mantine-color-accent-0), transparent 20%)',
borderRadius: '50%',
padding: '5px',
}}
/>
</Group>
)}
</>
<Image
style={
size === 'lg'
? { width: '150px', height: '150px' }
: { width: '75px', height: '75px' }
}
src={lookupSource(code).src}
alt={lookupSource(code).alt}
/>
);
}

View File

@@ -1,34 +1,23 @@
import { Center, Group, Modal, Stack, Text } from '@mantine/core';
import { useCommonHooks, useWeatherAPI } from 'hooks';
import { useCommonHooks } from 'hooks';
import { modalClasses } from 'styles';
import type { CurrentWeather as CW, ExtendedForecast, HourlyForecast } from 'types';
import type { MeteoWeather } from 'types';
import CurrentWeather from './CurrentWeather';
import ExtendedForecastCard from './ExtendedForecastCard';
import WeatherGraphs from './WeatherGraphs';
export function WeatherModal({
current,
extendedForecast,
weatherModalOpened,
handleResetSearch,
closeWeatherModal,
}: {
current: CW;
extendedForecast: ExtendedForecast[];
current: MeteoWeather | undefined;
weatherModalOpened: boolean;
handleResetSearch: () => void;
closeWeatherModal: () => void;
}) {
const { isMobile, currentUser, currentLocation } = useCommonHooks();
const { data: hourly } = useWeatherAPI({
endpoint: 'hourly',
office: currentLocation.office,
x: currentLocation.x,
y: currentLocation.y,
});
const hourlyForecast: HourlyForecast[] =
hourly?.properties?.periods?.slice(1, 25) || [];
const { isMobile, currentUser } = useCommonHooks();
return (
<Modal.Root
@@ -68,8 +57,8 @@ export function WeatherModal({
<Center w={'100%'}>
<Stack w={'100%'}>
<CurrentWeather current={current} />
<WeatherGraphs hourlyForecast={hourlyForecast} />
<ExtendedForecastCard forecast={extendedForecast} />
<WeatherGraphs hourly={current?.hourly} />
<ExtendedForecastCard daily={current?.daily} />
</Stack>
</Center>
</Modal.Body>

View File

@@ -10,6 +10,9 @@ function WeatherTooltip({ payload, active }: any) {
borderRadius: '8px',
}}
>
<Text size='md' c='var(--mantine-color-primary-0)'>
{payload[0]?.payload.startTime?.toLocaleString()}
</Text>
{payload.map((item: any) => (
<Text
key={item.name}

View File

@@ -1,8 +1,7 @@
export {
useMutationAPI,
useOpenMeteoAPI,
useOSMAPI,
useQueryAPI,
useSunStateAPI,
useWeatherAPI,
} from './useAPI';
export { useCommonHooks } from './useCommonHooks';

View File

@@ -1,6 +1,7 @@
import { Text } from '@mantine/core';
import { notifications } from '@mantine/notifications';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { fetchWeatherApi } from 'openmeteo';
import { FaCheck } from 'react-icons/fa';
import { VscLoading } from 'react-icons/vsc';
@@ -14,12 +15,195 @@ const headers: HeadersInit = {
'Sec-Fetch-Mode': 'no-cors',
'Sec-Fetch-Site': 'same-site',
};
const callEndpoint = async (url: string) => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`${response.status} ${response.statusText}`);
}
return await response.json();
const callWeatherEndpoint = async (lat: number, lon: number) => {
const weatherParams = {
latitude: lat,
longitude: lon,
daily: [
'sunrise',
'sunset',
'daylight_duration',
'sunshine_duration',
'uv_index_max',
'uv_index_clear_sky_max',
'temperature_2m_max',
'temperature_2m_min',
'apparent_temperature_max',
'apparent_temperature_min',
'wind_speed_10m_max',
'wind_gusts_10m_max',
'wind_direction_10m_dominant',
'precipitation_sum',
'precipitation_hours',
'precipitation_probability_max',
'weather_code',
],
hourly: [
'temperature_2m',
'relative_humidity_2m',
'dew_point_2m',
'apparent_temperature',
'precipitation_probability',
'precipitation',
'rain',
'showers',
'snowfall',
'snow_depth',
'weather_code',
'pressure_msl',
'surface_pressure',
'cloud_cover',
'cloud_cover_low',
'cloud_cover_mid',
'cloud_cover_high',
'visibility',
'evapotranspiration',
'et0_fao_evapotranspiration',
'vapour_pressure_deficit',
'wind_speed_10m',
'wind_direction_10m',
'wind_gusts_10m',
'uv_index',
'uv_index_clear_sky',
'is_day',
'sunshine_duration',
],
current: [
'temperature_2m',
'relative_humidity_2m',
'apparent_temperature',
'is_day',
'wind_speed_10m',
'wind_direction_10m',
'wind_gusts_10m',
'precipitation',
'rain',
'showers',
'snowfall',
'surface_pressure',
'pressure_msl',
'cloud_cover',
'weather_code',
],
wind_speed_unit: 'mph',
temperature_unit: 'fahrenheit',
precipitation_unit: 'inch',
};
const url = 'https://api.open-meteo.com/v1/forecast';
const responses = await fetchWeatherApi(url, weatherParams);
const response = responses[0];
const current = response ? response.current() : null;
const hourly = response ? response.hourly() : null;
const daily = response ? response.daily() : null;
const sunrise = daily?.variables(0);
const sunset = daily?.variables(1);
const utcOffsetSeconds = response.utcOffsetSeconds();
const weatherData = {
current: {
time: new Date((Number(current?.time()) + utcOffsetSeconds) * 1000),
temperature_2m: current?.variables(0)?.value(),
relative_humidity_2m: current?.variables(1)?.value(),
apparent_temperature: current?.variables(2)?.value(),
is_day: current?.variables(3)?.value(),
wind_speed_10m: current?.variables(4)?.value(),
wind_direction_10m: current?.variables(5)?.value(),
wind_gusts_10m: current?.variables(6)?.value(),
precipitation: current?.variables(7)?.value(),
rain: current?.variables(8)?.value(),
showers: current?.variables(9)?.value(),
snowfall: current?.variables(10)?.value(),
surface_pressure: current?.variables(11)?.value(),
pressure_msl: current?.variables(12)?.value(),
cloud_cover: current?.variables(13)?.value(),
weather_code: current?.variables(14)?.value(),
},
hourly: {
time: Array.from(
{
length:
(Number(hourly?.timeEnd()) - Number(hourly?.time())) /
Number(hourly?.interval()),
},
(_, i) =>
new Date(
(Number(hourly?.time()) + i * Number(hourly?.interval()) + utcOffsetSeconds) *
1000,
),
),
temperature_2m: hourly?.variables(0)?.valuesArray(),
relative_humidity_2m: hourly?.variables(1)?.valuesArray(),
dew_point_2m: hourly?.variables(2)?.valuesArray(),
apparent_temperature: hourly?.variables(3)?.valuesArray(),
precipitation_probability: hourly?.variables(4)?.valuesArray(),
precipitation: hourly?.variables(5)?.valuesArray(),
rain: hourly?.variables(6)?.valuesArray(),
showers: hourly?.variables(7)?.valuesArray(),
snowfall: hourly?.variables(8)?.valuesArray(),
snow_depth: hourly?.variables(9)?.valuesArray(),
weather_code: hourly?.variables(10)?.valuesArray(),
pressure_msl: hourly?.variables(11)?.valuesArray(),
surface_pressure: hourly?.variables(12)?.valuesArray(),
cloud_cover: hourly?.variables(13)?.valuesArray(),
cloud_cover_low: hourly?.variables(14)?.valuesArray(),
cloud_cover_mid: hourly?.variables(15)?.valuesArray(),
cloud_cover_high: hourly?.variables(16)?.valuesArray(),
visibility: hourly?.variables(17)?.valuesArray(),
evapotranspiration: hourly?.variables(18)?.valuesArray(),
et0_fao_evapotranspiration: hourly?.variables(19)?.valuesArray(),
vapour_pressure_deficit: hourly?.variables(20)?.valuesArray(),
wind_speed_10m: hourly?.variables(21)?.valuesArray(),
wind_direction_10m: hourly?.variables(22)?.valuesArray(),
wind_gusts_10m: hourly?.variables(23)?.valuesArray(),
uv_index: hourly?.variables(24)?.valuesArray(),
uv_index_clear_sky: hourly?.variables(25)?.valuesArray(),
is_day: hourly?.variables(26)?.valuesArray(),
sunshine_duration: hourly?.variables(27)?.valuesArray(),
},
daily: {
time: Array.from(
{
length:
(Number(daily?.timeEnd()) - Number(daily?.time())) /
Number(daily?.interval()),
},
(_, i) =>
new Date(
(Number(daily?.time()) + i * Number(daily?.interval()) + utcOffsetSeconds) *
1000,
),
),
// Map Int64 values to according structure
sunrise: [new Array(sunrise?.valuesInt64Length())].map(
(_, i) => new Date((Number(sunrise?.valuesInt64(i)) + utcOffsetSeconds) * 1000),
),
// Map Int64 values to according structure
sunset: [new Array(sunset?.valuesInt64Length())].map(
(_, i) => new Date((Number(sunset?.valuesInt64(i)) + utcOffsetSeconds) * 1000),
),
daylight_duration: daily?.variables(2)?.valuesArray(),
sunshine_duration: daily?.variables(3)?.valuesArray(),
uv_index_max: daily?.variables(4)?.valuesArray(),
uv_index_clear_sky_max: daily?.variables(5)?.valuesArray(),
temperature_2m_max: daily?.variables(6)?.valuesArray(),
temperature_2m_min: daily?.variables(7)?.valuesArray(),
apparent_temperature_max: daily?.variables(8)?.valuesArray(),
apparent_temperature_min: daily?.variables(9)?.valuesArray(),
wind_speed_10m_max: daily?.variables(10)?.valuesArray(),
wind_gusts_10m_max: daily?.variables(11)?.valuesArray(),
wind_direction_10m_dominant: daily?.variables(12)?.valuesArray(),
precipitation_sum: daily?.variables(13)?.valuesArray(),
precipitation_hours: daily?.variables(14)?.valuesArray(),
precipitation_probability_max: daily?.variables(15)?.valuesArray(),
weather_code: daily?.variables(16)?.valuesArray(),
},
};
return weatherData;
};
export function useQueryAPI({ endpoint }: { endpoint: string }) {
const callEndpoint = async (endpoint: string) => {
@@ -143,54 +327,10 @@ export function useMutationAPI() {
});
}
export function useWeatherAPI({
endpoint,
lat,
lon,
office,
station,
x,
y,
}: {
endpoint: string;
lat?: number;
lon?: number;
office?: string;
station?: string;
x?: number;
y?: number;
}) {
let url = '';
if (endpoint === 'baseline') {
url = `https://api.weather.gov/gridpoints/${office}/${x},${y}`;
} else if (endpoint === 'current') {
url = `https://api.weather.gov/stations/${station}/observations`;
} else if (endpoint === 'forecast') {
url = `https://api.weather.gov/gridpoints/${office}/${x},${y}/forecast`;
} else if (endpoint === 'hourly') {
url = `https://api.weather.gov/gridpoints/${office}/${x},${y}/forecast/hourly`;
} else if (endpoint === 'points') {
url = `https://api.weather.gov/points/${lat},${lon}`;
} else if (endpoint === 'stations') {
url = `https://api.weather.gov/gridpoints/${office}/${x},${y}/stations`;
}
export function useOpenMeteoAPI({ lat, lon }: { lat: number; lon: number }) {
return useQuery({
queryKey: ['GET', url],
queryFn: () => callEndpoint(url),
});
}
export function useSunStateAPI({ lat, lon }: { lat: number; lon: number }) {
const date = new Date();
const today = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
date.setDate(date.getDate() + 1);
const tomorrow = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
const url = `https://api.sunrisesunset.io/json?lat=${lat}&lng=${lon}&date_start=${today}&date_end=${tomorrow}`;
return useQuery({
queryKey: ['GET', url],
queryFn: () => callEndpoint(url),
queryKey: ['GET', 'weather'],
queryFn: () => callWeatherEndpoint(lat, lon),
});
}

View File

@@ -108,10 +108,6 @@ export function useCommonHooks() {
const [currentLocation, setCurrentLocation] = useLocalStorage<Location>({
key: 'location',
defaultValue: {
office: 'HGX',
station: 'KHOU',
x: 65,
y: 96,
lat: 29.750950134386066,
lon: -95.36220654860034,
},

View File

@@ -1,15 +1,10 @@
import { Box, Grid, Stack } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import CurrentWeather from 'components/WeatherModal/CurrentWeather';
import { useCommonHooks, useOSMAPI, useQueryAPI, useWeatherAPI } from 'hooks';
import { useCommonHooks, useOpenMeteoAPI, useOSMAPI, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react';
import { stackClasses } from 'styles';
import type {
CurrentWeather as CW,
ExtendedForecast,
Message,
PersonalData,
} from 'types';
import type { Message, MeteoWeather, PersonalData } from 'types';
import CurrentWeather from '../../components/WeatherModal/CurrentWeather';
import {
Applications,
Bookmarks,
@@ -48,73 +43,19 @@ export function Homepage() {
const { data: weatherMetadata } = useOSMAPI({
address: personalData?.[0]?.physicalAddress,
});
const { data: points } = useWeatherAPI({
endpoint: 'points',
const { data: meteoWeather } = useOpenMeteoAPI({
lat: weatherMetadata?.[0].lat ?? currentLocation.lat,
lon: weatherMetadata?.[0].lon ?? currentLocation.lon,
});
const { data: basicWeather } = useWeatherAPI({
endpoint: 'baseline',
office: points?.properties.gridId ?? currentLocation.office,
x: points?.properties.gridX ?? currentLocation.x,
y: points?.properties.gridY ?? currentLocation.y,
});
const { data: stations } = useWeatherAPI({
endpoint: 'stations',
office: points?.properties.gridId ?? currentLocation.office,
x: points?.properties.gridX ?? currentLocation.x,
y: points?.properties.gridY ?? currentLocation.y,
});
const { data: weather } = useWeatherAPI({
endpoint: 'current',
station:
stations?.features[0].properties.stationIdentifier ?? currentLocation.station,
});
const { data: forecast } = useWeatherAPI({
endpoint: 'forecast',
office: points?.properties.gridId ?? currentLocation.office,
x: points?.properties.gridX ?? currentLocation.x,
y: points?.properties.gridY ?? currentLocation.y,
});
const personal: PersonalData = personalData?.[0];
const message: Message = messageData?.[0] || {};
const [displayedGreeting, setDisplayedGreeting] = useState<string | undefined>('');
const [current, setCurrent] = useState<CW>({
icon: '',
textDescription: '',
temperature: 0,
highTemp: 0,
lowTemp: 0,
precipitation: 'N/A',
humidity: 0,
heatIndex: null,
windChill: null,
windSpeed: 0,
windDirection: 0,
pressure: 0,
});
const [meteoCurrent, setMeteoCurrent] = useState<MeteoWeather>();
const [providerModalOpened, { open: openProviderModal, close: closeProviderModal }] =
useDisclosure(false);
const extendedForecast = forecast?.properties?.periods.map(
(item: ExtendedForecast) => ({
detailedForecast: item.detailedForecast,
isDaytime: item.isDaytime,
name: item.name,
shortForecast: item.shortForecast,
temperature: item.temperature,
temperatureUnit: item.temperatureUnit,
}),
) || [
{
detailedForecast: '',
isDaytime: true,
name: '',
shortForecast: '',
temperature: 0,
temperatureUnit: 'F',
},
];
const [weatherModalOpened, { open: openWeatherModal, close: closeWeatherModal }] =
useDisclosure(false);
@@ -142,59 +83,16 @@ export function Homepage() {
});
}
}, [message, personal]);
useEffect(() => {
if (weather !== undefined && basicWeather !== undefined) {
setCurrent({
icon: weather.features[0]?.properties.icon?.replace('medium', 'large') ?? '',
textDescription: weather.features[0]?.properties.textDescription,
temperature: Math.round(
(weather.features?.find((feature: any) => feature.properties.temperature.value)
.properties.temperature.value *
9) /
5 +
32,
),
highTemp: Math.round(
(basicWeather.properties.maxTemperature.values[0].value * 9) / 5 + 32,
),
lowTemp: Math.round(
(basicWeather.properties.minTemperature.values[0].value * 9) / 5 + 32,
),
precipitation:
weather.features[0]?.properties?.precipitationLastHour?.value ?? 'N/A',
humidity: Math.round(weather.features[0]?.properties?.relativeHumidity?.value),
heatIndex:
basicWeather.properties.heatIndex?.values[0].value === null
? null
: Math.round(
(basicWeather.properties.heatIndex.values[0].value * 9) / 5 + 32,
),
windChill:
basicWeather.properties.windChill?.values[0].value === null
? null
: Math.round(
(basicWeather.properties.windChill.values[0].value * 9) / 5 + 32,
),
windSpeed: Math.round(weather.features[0]?.properties.windSpeed.value / 1.60934),
windDirection: weather.features[0]?.properties.windDirection.value,
pressure: Math.round(
weather.features[0]?.properties.barometricPressure.value / 3386.38,
),
});
}
}, [weather, basicWeather]);
useEffect(() => {
if (forecast) {
if (meteoWeather) {
setMeteoCurrent(meteoWeather);
setCurrentLocation({
office: points?.properties.gridId,
station: stations?.features[0].properties.stationIdentifier,
x: points?.properties.gridX,
y: points?.properties.gridY,
lat: weatherMetadata?.[0].lat,
lon: weatherMetadata?.[0].lon,
});
}
}, [forecast]);
}, [meteoWeather]);
return (
<Stack
@@ -206,8 +104,7 @@ export function Homepage() {
{appFilter.length > 0 || bookmarkFilter.length > 0 ? (
<>
<Search
current={current}
extendedForecast={extendedForecast}
current={meteoCurrent}
weatherModalOpened={weatherModalOpened}
closeWeatherModal={closeWeatherModal}
openWeatherModal={openWeatherModal}
@@ -220,7 +117,7 @@ export function Homepage() {
<Header
greet={displayedGreeting}
date={date}
current={current}
current={meteoCurrent}
openWeatherModal={openWeatherModal}
/>
<Applications contentType={appFilter} />
@@ -231,12 +128,11 @@ export function Homepage() {
<Stack align='center'>
<LargeHeader greet={displayedGreeting} date={date} />
<Grid grow columns={24} pos={'absolute'} bottom={'1rem'}>
<CurrentWeather current={current} />
{meteoCurrent && <CurrentWeather current={meteoCurrent} />}
</Grid>
</Stack>
<Search
current={current}
extendedForecast={extendedForecast}
current={meteoCurrent}
weatherModalOpened={weatherModalOpened}
closeWeatherModal={closeWeatherModal}
openWeatherModal={openWeatherModal}

View File

@@ -2,7 +2,7 @@ import { Group, Stack, Text } from '@mantine/core';
import { useCommonHooks } from 'hooks';
import { useEffect, useState } from 'react';
import { textClasses } from 'styles';
import type { CurrentWeather } from 'types';
import type { MeteoWeather } from 'types';
export function Header({
greet,
@@ -12,7 +12,7 @@ export function Header({
}: {
greet: string | undefined;
date: string;
current: CurrentWeather;
current: MeteoWeather | undefined;
openWeatherModal: () => void;
}) {
const [time, setTime] = useState(new Date());
@@ -101,7 +101,7 @@ export function Header({
textShadow: theme.textShadowsEnabled ? textShadowStyle : undefined,
}}
>
{current.temperature}°F
{current?.current.temperature_2m?.toFixed(0)}°F
</Text>
<Text
className={textClasses.dateTime}
@@ -112,7 +112,8 @@ export function Header({
textShadow: theme.textShadowsEnabled ? textShadowStyle : undefined,
}}
>
{current.highTemp}°F/{current.lowTemp}°F
{current?.daily.temperature_2m_max?.[0].toFixed(0)}°F/
{current?.daily.temperature_2m_min?.[0].toFixed(0)}°F
</Text>
</Group>
<Text

View File

@@ -18,9 +18,8 @@ import { selectClasses } from 'styles';
import type {
Application,
Bookmark,
CurrentWeather,
ExtendedForecast,
GuacamoleClient,
MeteoWeather,
PersonalData,
Provider,
SearchHistoryItem,
@@ -32,7 +31,6 @@ import RenderItem from './components/RenderItem';
export function Search({
current,
extendedForecast,
weatherModalOpened,
closeWeatherModal,
openWeatherModal,
@@ -42,8 +40,7 @@ export function Search({
openProviderModal,
closeProviderModal,
}: {
current: CurrentWeather;
extendedForecast: ExtendedForecast[];
current: MeteoWeather | undefined;
weatherModalOpened: boolean;
closeWeatherModal: () => void;
openWeatherModal: () => void;
@@ -647,7 +644,6 @@ export function Search({
{weatherModalOpened && (
<WeatherModal
current={current}
extendedForecast={extendedForecast}
weatherModalOpened={weatherModalOpened}
handleResetSearch={handleResetSearch}
closeWeatherModal={closeWeatherModal}

View File

@@ -11,10 +11,9 @@ export type { Theme, ThemePlaylist } from './theme';
export { ThemeProperties } from './theme';
export type { User } from './user';
export type {
CurrentWeather,
ExtendedForecast,
HourlyForecast,
OutlookWeather,
Daily,
Hourly,
MeteoWeather,
SunState,
} from './weather';
export type { WolClient } from './wolClient';

View File

@@ -1,8 +1,4 @@
export interface Location {
office: string;
station: string;
x: number;
y: number;
lat: number;
lon: number;
}

View File

@@ -1,73 +1,83 @@
export interface CurrentWeather {
icon: string;
textDescription: string;
temperature: number;
highTemp: number;
lowTemp: number;
precipitation: string;
humidity: number;
heatIndex: number | null;
windChill: number | null;
windSpeed: number;
windDirection: number;
pressure: number;
}
export interface OutlookWeather {
name: string;
icon: string;
isDayTime: boolean;
temperature: number;
precipitation: number;
windSpeed: string;
windDirection: string;
}
export interface ExtendedForecast {
detailedForecast: string;
isDaytime: boolean;
name: string;
shortForecast: string;
temperature: number;
temperatureUnit: string;
}
export interface HourlyForecast {
detailedForecast: string;
dewpoint: DewPoint;
endTime: string;
icon: string;
isDaytime: boolean;
name: string;
number: number;
probabilityOfPrecipitation: ProbabilityOfPrecipitation;
relativeHumidity: RelativeHumidity;
shortForecast: string;
startTime: string;
temperature: number;
temperatureTrend: string;
temperatureUnit: string;
windDirection: string;
windSpeed: string;
}
export interface ProbabilityOfPrecipitation {
unitCode: string;
value: number;
}
export interface RelativeHumidity {
unitCode: string;
value: number;
}
export interface DewPoint {
unitCode: string;
value: number;
}
export interface SunState {
date: string;
sunrise: string;
sunset: string;
}
export interface MeteoWeather {
current: Current;
hourly: Hourly;
daily: Daily;
}
export interface Current {
time: Date;
temperature_2m: number | undefined;
relative_humidity_2m: number | undefined;
apparent_temperature: number | undefined;
is_day: number | undefined;
wind_speed_10m: number | undefined;
wind_direction_10m: number | undefined;
wind_gusts_10m: number | undefined;
precipitation: number | undefined;
rain: number | undefined;
showers: number | undefined;
snowfall: number | undefined;
surface_pressure: number | undefined;
pressure_msl: number | undefined;
cloud_cover: number | undefined;
weather_code: number | undefined;
}
export interface Hourly {
apparent_temperature: Float32Array<ArrayBufferLike> | null | undefined;
cloud_cover: Float32Array<ArrayBufferLike> | null | undefined;
cloud_cover_high: Float32Array<ArrayBufferLike> | null | undefined;
cloud_cover_mid: Float32Array<ArrayBufferLike> | null | undefined;
cloud_cover_low: Float32Array<ArrayBufferLike> | null | undefined;
dew_point_2m: Float32Array<ArrayBufferLike> | null | undefined;
et0_fao_evapotranspiration: Float32Array<ArrayBufferLike> | null | undefined;
evapotranspiration: Float32Array<ArrayBufferLike> | null | undefined;
is_day: Float32Array<ArrayBufferLike> | null | undefined;
precipitation: Float32Array<ArrayBufferLike> | null | undefined;
precipitation_probability: Float32Array<ArrayBufferLike> | null | undefined;
pressure_msl: Float32Array<ArrayBufferLike> | null | undefined;
rain: Float32Array<ArrayBufferLike> | null | undefined;
relative_humidity_2m: Float32Array<ArrayBufferLike> | null | undefined;
showers: Float32Array<ArrayBufferLike> | null | undefined;
snow_depth: Float32Array<ArrayBufferLike> | null | undefined;
snowfall: Float32Array<ArrayBufferLike> | null | undefined;
sunshine_duration: Float32Array<ArrayBufferLike> | null | undefined;
surface_pressure: Float32Array<ArrayBufferLike> | null | undefined;
temperature_2m: Float32Array<ArrayBufferLike> | null | undefined;
time: Date[];
uv_index_clear_sky: Float32Array<ArrayBufferLike> | null | undefined;
uv_index: Float32Array<ArrayBufferLike> | null | undefined;
vapour_pressure_deficit: Float32Array<ArrayBufferLike> | null | undefined;
visibility: Float32Array<ArrayBufferLike> | null | undefined;
weather_code: Float32Array<ArrayBufferLike> | null | undefined;
wind_direction_10m: Float32Array<ArrayBufferLike> | null | undefined;
wind_gusts_10m: Float32Array<ArrayBufferLike> | null | undefined;
wind_speed_10m: Float32Array<ArrayBufferLike> | null | undefined;
}
export interface Daily {
apparent_temperature_max: Float32Array<ArrayBufferLike> | null | undefined;
apparent_temperature_min: Float32Array<ArrayBufferLike> | null | undefined;
daylight_duration: Float32Array<ArrayBufferLike> | null | undefined;
precipitation_hours: Float32Array<ArrayBufferLike> | null | undefined;
precipitation_probability_max: Float32Array<ArrayBufferLike> | null | undefined;
precipitation_sum: Float32Array<ArrayBufferLike> | null | undefined;
sunrise: Date[];
sunset: Date[];
sunshine_duration: Float32Array<ArrayBufferLike> | null | undefined;
temperature_2m_max: Float32Array<ArrayBufferLike> | null | undefined;
temperature_2m_min: Float32Array<ArrayBufferLike> | null | undefined;
time: Date[];
uv_index_clear_sky_max: Float32Array<ArrayBufferLike> | null | undefined;
uv_index_max: Float32Array<ArrayBufferLike> | null | undefined;
weather_code: Float32Array<ArrayBufferLike> | null | undefined;
wind_direction_10m_dominant: Float32Array<ArrayBufferLike> | null | undefined;
wind_gusts_10m_max: Float32Array<ArrayBufferLike> | null | undefined;
wind_speed_10m_max: Float32Array<ArrayBufferLike> | null | undefined;
}

View File

@@ -1,26 +0,0 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "es2023",
"lib": ["ES2023", "DOM"],
"module": "esnext",
"types": ["vite/client"],
"skipLibCheck": true,
"composite": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": false,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": false,
"jsx": "react-jsx",
/* Linting */
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}

View File

@@ -1,17 +1,16 @@
{
"compilerOptions": {
"target": "ES2020",
"target": "ES2022",
"useDefineForClassFields": true,
"module": "nodenext",
"module": "ES2022",
"skipLibCheck": true,
"rootDir": "./src",
"allowJs": false,
"lib": ["ESNext", "DOM"],
"allowSyntheticDefaultImports": true,
"types": ["vite/client", "vitest"],
/* Bundler mode */
"moduleResolution": "nodenext",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
@@ -25,17 +24,13 @@
"noFallthroughCasesInSwitch": true,
"paths": {
"components": ["./components/index.ts"],
"contexts": ["./contexts/index.ts"],
"hooks": ["./hooks/index.ts"],
"pages": ["./pages/index.ts"],
"styles": ["./styles/index.ts"],
"types": ["./types/index.ts"]
"components": ["./src/components/index.ts"],
"contexts": ["./src/contexts/index.ts"],
"hooks": ["./src/hooks/index.ts"],
"pages": ["./src/pages/index.ts"],
"styles": ["./src/styles/index.ts"],
"types": ["./src/types/index.ts"]
}
},
"include": ["src"],
"references": [
{ "path": "./tsconfig.node.json" },
{ "path": "./tsconfig.app.json" }
]
}

View File

@@ -1,25 +0,0 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "es2023",
"lib": ["ES2023"],
"module": "esnext",
"types": ["node"],
"skipLibCheck": true,
"composite": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": false,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": false,
/* Linting */
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true
},
"include": ["vite.config.ts"]
}