Compare commits

...
Author SHA1 Message Date
Thomas Camlong 726f126d94 feat(weather): add humidity display from Open-Meteo API
Adds relative_humidity_2m to the daily forecast API request and
displays it in both the single-day weather view and the forecast
hover cards.

Closes #6428
2026-07-21 16:12:24 +02:00
Thomas Camlong 0163e50fe3 feat(weather): add humidity display from Open-Meteo API
Adds relative_humidity_2m to the daily forecast API request and
displays it in both the single-day weather view and the forecast
hover cards.

Closes #6428
2026-07-21 16:11:51 +02:00
4 changed files with 20 additions and 3 deletions

No files matched your search

+4 -1
View File
@@ -9,7 +9,7 @@ export const weatherRequestHandler = createWidgetRequestHandler({
async requestAsync(input: { latitude: number; longitude: number }) { async requestAsync(input: { latitude: number; longitude: number }) {
const res = await withTimeoutAsync(async (signal) => { const res = await withTimeoutAsync(async (signal) => {
return await fetchWithTrustedCertificatesAsync( return await fetchWithTrustedCertificatesAsync(
`https://api.open-meteo.com/v1/forecast?latitude=${input.latitude}&longitude=${input.longitude}&daily=weathercode,temperature_2m_max,temperature_2m_min,sunrise,sunset,wind_speed_10m_max,wind_gusts_10m_max&current_weather=true&timezone=auto`, `https://api.open-meteo.com/v1/forecast?latitude=${input.latitude}&longitude=${input.longitude}&daily=weathercode,temperature_2m_max,temperature_2m_min,sunrise,sunset,wind_speed_10m_max,wind_gusts_10m_max,relative_humidity_2m_mean&current_weather=true&timezone=auto`,
{ signal }, { signal },
); );
}); });
@@ -27,6 +27,7 @@ export const weatherRequestHandler = createWidgetRequestHandler({
sunset: weather.daily.sunset[index], sunset: weather.daily.sunset[index],
maxWindSpeed: weather.daily.wind_speed_10m_max[index], maxWindSpeed: weather.daily.wind_speed_10m_max[index],
maxWindGusts: weather.daily.wind_gusts_10m_max[index], maxWindGusts: weather.daily.wind_gusts_10m_max[index],
humidity: weather.daily.relative_humidity_2m_mean[index],
}; };
}), }),
} satisfies Weather; } satisfies Weather;
@@ -48,6 +49,7 @@ const atLocationOutput = z.object({
sunset: z.array(z.string()), sunset: z.array(z.string()),
wind_speed_10m_max: z.array(z.number()), wind_speed_10m_max: z.array(z.number()),
wind_gusts_10m_max: z.array(z.number()), wind_gusts_10m_max: z.array(z.number()),
relative_humidity_2m_mean: z.array(z.number()),
}), }),
}); });
@@ -66,5 +68,6 @@ export interface Weather {
sunset: string | undefined; sunset: string | undefined;
maxWindSpeed: number | undefined; maxWindSpeed: number | undefined;
maxWindGusts: number | undefined; maxWindGusts: number | undefined;
humidity: number | undefined;
}[]; }[];
} }
+2 -1
View File
@@ -2257,7 +2257,8 @@
"sunrise": "Sunrise", "sunrise": "Sunrise",
"sunset": "Sunset", "sunset": "Sunset",
"maxWindSpeed": "Max wind speed: {maxWindSpeed} {unit}", "maxWindSpeed": "Max wind speed: {maxWindSpeed} {unit}",
"maxWindGusts": "Max wind gusts: {maxWindGusts} {unit}" "maxWindGusts": "Max wind gusts: {maxWindGusts} {unit}",
"humidity": "Humidity: {humidity}%"
}, },
"kind": { "kind": {
"clear": "Clear", "clear": "Clear",
+8 -1
View File
@@ -1,7 +1,7 @@
"use client"; "use client";
import { Box, Group, HoverCard, Stack, Text } from "@mantine/core"; import { Box, Group, HoverCard, Stack, Text } from "@mantine/core";
import { IconArrowDownRight, IconArrowUpRight, IconMapPin, IconWind } from "@tabler/icons-react"; import { IconArrowDownRight, IconArrowUpRight, IconDroplets, IconMapPin, IconWind } from "@tabler/icons-react";
import combineClasses from "clsx"; import combineClasses from "clsx";
import dayjs from "dayjs"; import dayjs from "dayjs";
@@ -87,6 +87,12 @@ const DailyWeather = ({ options, weather }: WeatherProps) => {
</Text> </Text>
</Group> </Group>
)} )}
{weather.daily[0]?.humidity !== undefined && (
<Group className="weather-humidity-group" wrap="nowrap" gap="xs">
<IconDroplets size={16} />
<Text fz={16}>{t("dailyForecast.humidity", { humidity: weather.daily[0].humidity })}</Text>
</Group>
)}
<Group className="weather-max-min-temp-group" wrap="nowrap" gap="sm"> <Group className="weather-max-min-temp-group" wrap="nowrap" gap="sm">
<Group gap="xs" wrap="nowrap"> <Group gap="xs" wrap="nowrap">
<IconArrowUpRight size={16} /> <IconArrowUpRight size={16} />
@@ -204,6 +210,7 @@ function Forecast({ weather, options }: WeatherProps) {
sunset={dayjs(dayWeather.sunset).format("HH:mm")} sunset={dayjs(dayWeather.sunset).format("HH:mm")}
maxWindSpeed={dayWeather.maxWindSpeed} maxWindSpeed={dayWeather.maxWindSpeed}
maxWindGusts={dayWeather.maxWindGusts} maxWindGusts={dayWeather.maxWindGusts}
humidity={dayWeather.humidity}
/> />
</HoverCard.Dropdown> </HoverCard.Dropdown>
</HoverCard> </HoverCard>
+6
View File
@@ -5,6 +5,7 @@ import {
IconCloudRain, IconCloudRain,
IconCloudSnow, IconCloudSnow,
IconCloudStorm, IconCloudStorm,
IconDroplets,
IconMoon, IconMoon,
IconQuestionMark, IconQuestionMark,
IconSnowflake, IconSnowflake,
@@ -51,6 +52,7 @@ interface WeatherDescriptionProps {
sunset?: string; sunset?: string;
maxWindSpeed?: number; maxWindSpeed?: number;
maxWindGusts?: number; maxWindGusts?: number;
humidity?: number;
} }
/** /**
@@ -78,6 +80,7 @@ export const WeatherDescription = ({
sunset, sunset,
maxWindSpeed, maxWindSpeed,
maxWindGusts, maxWindGusts,
humidity,
}: WeatherDescriptionProps) => { }: WeatherDescriptionProps) => {
const t = useScopedI18n("widget.weather"); const t = useScopedI18n("widget.weather");
const tCommon = useScopedI18n("common"); const tCommon = useScopedI18n("common");
@@ -97,6 +100,9 @@ export const WeatherDescription = ({
<List.Item icon={<IconTemperatureMinus size={15} />}>{`${tCommon("information.min")}: ${minTemp}`}</List.Item> <List.Item icon={<IconTemperatureMinus size={15} />}>{`${tCommon("information.min")}: ${minTemp}`}</List.Item>
<List.Item icon={<IconSun size={15} />}>{`${t("dailyForecast.sunrise")}: ${sunrise}`}</List.Item> <List.Item icon={<IconSun size={15} />}>{`${t("dailyForecast.sunrise")}: ${sunrise}`}</List.Item>
<List.Item icon={<IconMoon size={15} />}>{`${t("dailyForecast.sunset")}: ${sunset}`}</List.Item> <List.Item icon={<IconMoon size={15} />}>{`${t("dailyForecast.sunset")}: ${sunset}`}</List.Item>
{humidity !== undefined && (
<List.Item icon={<IconDroplets size={15} />}>{t("dailyForecast.humidity", { humidity })}</List.Item>
)}
{maxWindSpeed !== undefined && ( {maxWindSpeed !== undefined && (
<List.Item icon={<IconWind size={15} />}> <List.Item icon={<IconWind size={15} />}>
{t("dailyForecast.maxWindSpeed", { {t("dailyForecast.maxWindSpeed", {