diff --git a/public/locales/en/common.json b/public/locales/en/common.json index ec5c39fd9..318b4f5cb 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -226,5 +226,63 @@ "homepagesearch": { "bookmark": "Bookmark", "service": "Service" + }, + "wmo": { + "0-day": "Sunny", + "0-night": "Clear", + "1-day": "Mainly Sunny", + "1-night": "Mainly Clear", + "2-day": "Partly Cloudy", + "2-night": "Partly Cloudy", + "3-day": "Cloudy", + "3-night": "Cloudy", + "45-day": "Foggy", + "45-night": "Foggy", + "48-day": "Foggy", + "48-night": "Foggy", + "51-day": "Light Drizzle", + "51-night": "Light Drizzle", + "53-day": "Drizzle", + "53-night": "Drizzle", + "55-day": "Heavy Drizzle", + "55-night": "Heavy Drizzle", + "56-day": "Light Freezing Drizzle", + "56-night": "Light Freezing Drizzle", + "57-day": "Freezing Drizzle", + "57-night": "Freezing Drizzle", + "61-day": "Light Rain", + "61-night": "Light Rain", + "63-day": "Rain", + "63-night": "Rain", + "65-day": "Heavy Rain", + "65-night": "Heavy Rain", + "66-day": "Freezing Rain", + "66-night": "Freezing Rain", + "67-day": "Freezing Rain", + "67-night": "Freezing Rain", + "71-day": "Light Snow", + "71-night": "Light Snow", + "73-day": "Snow", + "73-night": "Snow", + "75-day": "Heavy Snow", + "75-night": "Heavy Snow", + "77-day": "Snow Grains", + "77-night": "Snow Grains", + "80-day": "Light Showers", + "80-night": "Light Showers", + "81-day": "Showers", + "81-night": "Showers", + "82-day": "Heavy Showers", + "82-night": "Heavy Showers", + "85-day": "Snow Showers", + "85-night": "Snow Showers", + "86-day": "Snow Showers", + "86-night": "Snow Showers", + "95-day": "Thunderstorm", + "95-night": "Thunderstorm", + "96-day": "Thunderstorm With Hail", + "96-night": "Thunderstorm With Hail", + "99-day": "Thunderstorm With Hail", + "99-night": "Thunderstorm With Hail" } } diff --git a/src/components/widgets/openmeteo/icon.jsx b/src/components/widgets/openmeteo/icon.jsx new file mode 100644 index 000000000..a2b01ba19 --- /dev/null +++ b/src/components/widgets/openmeteo/icon.jsx @@ -0,0 +1,7 @@ +import mapIcon from "utils/weather/owm-condition-map"; + +export default function Icon({ condition, timeOfDay }) { + const IconComponent = mapIcon(condition, timeOfDay); + + return ; +} diff --git a/src/components/widgets/openmeteo/openmeteo.jsx b/src/components/widgets/openmeteo/openmeteo.jsx new file mode 100644 index 000000000..0d29aef53 --- /dev/null +++ b/src/components/widgets/openmeteo/openmeteo.jsx @@ -0,0 +1,130 @@ +import useSWR from "swr"; +import { useState } from "react"; +import { BiError } from "react-icons/bi"; +import { WiCloudDown } from "react-icons/wi"; +import { MdLocationDisabled, MdLocationSearching } from "react-icons/md"; +import { useTranslation } from "next-i18next"; + +import Icon from "./icon"; + +function Widget({ options }) { + const { t } = useTranslation(); + + const { data, error } = useSWR( + `/api/widgets/openmeteo?${new URLSearchParams({ ...options }).toString()}` + ); + + if (error || data?.error) { + return ( +
+
+
+ +
+ {t("widget.api_error")} + - +
+
+
+
+ ); + } + + if (!data) { + return ( +
+
+
+ +
+
+ {t("weather.updating")} + {t("weather.wait")} +
+
+
+ ); + } + + const unit = options.units === "metric" ? "celsius" : "fahrenheit"; + const timeOfDay = data.current_weather.time > data.daily.sunrise[0] && data.current_weather.time < data.daily.sunset[0] ? "day" : "night"; + + return ( +
+
+
+ +
+
+ + {options.label && `${options.label}, `} + {t("common.number", { + value: data.current_weather.temperature, + style: "unit", + unit, + })} + + {t(`wmo.${data.current_weather.weathercode}-${timeOfDay}`)} +
+
+
+ ); +} + +export default function OpenMeteo({ options }) { + const { t } = useTranslation(); + const [location, setLocation] = useState(false); + const [requesting, setRequesting] = useState(false); + + if (!location && options.latitude && options.longitude) { + setLocation({ latitude: options.latitude, longitude: options.longitude }); + } + + const requestLocation = () => { + setRequesting(true); + if (typeof window !== "undefined") { + navigator.geolocation.getCurrentPosition( + (position) => { + setLocation({ latitude: position.coords.latitude, longitude: position.coords.longitude }); + setRequesting(false); + }, + () => { + setRequesting(false); + }, + { + enableHighAccuracy: true, + maximumAge: 1000 * 60 * 60 * 3, + timeout: 1000 * 30, + } + ); + } + }; + + // if (!requesting && !location) requestLocation(); + + if (!location) { + return ( + + ); + } + + return ; +} diff --git a/src/components/widgets/widget.jsx b/src/components/widgets/widget.jsx index bd31ed934..86f79dfeb 100644 --- a/src/components/widgets/widget.jsx +++ b/src/components/widgets/widget.jsx @@ -12,6 +12,7 @@ const widgetMappings = { logo: dynamic(() => import("components/widgets/logo/logo"), { ssr: false }), unifi_console: dynamic(() => import("components/widgets/unifi_console/unifi_console")), glances: dynamic(() => import("components/widgets/glances/glances")), + openmeteo: dynamic(() => import("components/widgets/openmeteo/openmeteo")), }; export default function Widget({ widget }) { diff --git a/src/pages/api/widgets/openmeteo.js b/src/pages/api/widgets/openmeteo.js new file mode 100644 index 000000000..e79931cb6 --- /dev/null +++ b/src/pages/api/widgets/openmeteo.js @@ -0,0 +1,8 @@ +import cachedFetch from "utils/proxy/cached-fetch"; + +export default async function handler(req, res) { + const { latitude, longitude, units, cache } = req.query; + const degrees = units === "imperial" ? "fahrenheit" : "celsius"; + const apiUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&daily=sunrise,sunset¤t_weather=true&temperature_unit=${degrees}&timezone=auto`; + return res.send(await cachedFetch(apiUrl, cache)); +} \ No newline at end of file