diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 69d88305..b1fded09 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -218,5 +218,63 @@
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
+ },
+ "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 00000000..a2b01ba1
--- /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 00000000..0d29aef5
--- /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 bd31ed93..86f79dfe 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 00000000..e79931cb
--- /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