Browse Source

Openmeteo Widget

Michael Shamoon 2 years ago
parent
commit
e1a62a69f6

+ 58 - 0
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"
     }
 }

+ 7 - 0
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 <IconComponent className="w-10 h-10 text-theme-800 dark:text-theme-200" />;
+}

+ 130 - 0
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 (
+      <div className="flex flex-col justify-center first:ml-0 ml-4 mr-2">
+        <div className="flex flex-row items-center justify-end">
+          <div className="flex flex-col items-center">
+            <BiError className="w-8 h-8 text-theme-800 dark:text-theme-200" />
+            <div className="flex flex-col ml-3 text-left">
+              <span className="text-theme-800 dark:text-theme-200 text-sm">{t("widget.api_error")}</span>
+              <span className="text-theme-800 dark:text-theme-200 text-xs">-</span>
+            </div>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  if (!data) {
+    return (
+      <div className="flex flex-col justify-center first:ml-0 ml-4 mr-2">
+        <div className="flex flex-row items-center justify-end">
+          <div className="flex flex-col items-center">
+            <WiCloudDown className="w-8 h-8 text-theme-800 dark:text-theme-200" />
+          </div>
+          <div className="flex flex-col ml-3 text-left">
+            <span className="text-theme-800 dark:text-theme-200 text-sm">{t("weather.updating")}</span>
+            <span className="text-theme-800 dark:text-theme-200 text-xs">{t("weather.wait")}</span>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  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 (
+    <div className="flex flex-col justify-center first:ml-0 ml-4 mr-2">
+      <div className="flex flex-row items-center justify-end">
+        <div className="flex flex-col items-center">
+          <Icon condition={data.current_weather.weathercode} timeOfDay={timeOfDay} />
+        </div>
+        <div className="flex flex-col ml-3 text-left">
+          <span className="text-theme-800 dark:text-theme-200 text-sm">
+            {options.label && `${options.label}, `}
+            {t("common.number", {
+              value: data.current_weather.temperature,
+              style: "unit",
+              unit,
+            })}
+          </span>
+          <span className="text-theme-800 dark:text-theme-200 text-xs">{t(`wmo.${data.current_weather.weathercode}-${timeOfDay}`)}</span>
+        </div>
+      </div>
+    </div>
+  );
+}
+
+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 (
+      <button
+        type="button"
+        onClick={() => requestLocation()}
+        className="flex flex-col justify-center first:ml-0 ml-4 mr-2"
+      >
+        <div className="flex flex-row items-center justify-end">
+          <div className="flex flex-col items-center">
+            {requesting ? (
+              <MdLocationSearching className="w-6 h-6 text-theme-800 dark:text-theme-200 animate-pulse" />
+            ) : (
+              <MdLocationDisabled className="w-6 h-6 text-theme-800 dark:text-theme-200" />
+            )}
+          </div>
+          <div className="flex flex-col ml-3 text-left">
+            <span className="text-theme-800 dark:text-theme-200 text-sm">{t("weather.current")}</span>
+            <span className="text-theme-800 dark:text-theme-200 text-xs">{t("weather.allow")}</span>
+          </div>
+        </div>
+      </button>
+    );
+  }
+
+  return <Widget options={{ ...location, ...options }} />;
+}

+ 1 - 0
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 }) {

+ 8 - 0
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&current_weather=true&temperature_unit=${degrees}&timezone=auto`;
+  return res.send(await cachedFetch(apiUrl, cache));
+}