Skip to content
Snippets Groups Projects
Commit 8a71b3bf authored by Evy Storozhenko's avatar Evy Storozhenko
Browse files

add weather panel based on open-meteo

parent cfb1d00a
No related branches found
No related tags found
No related merge requests found
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
"@types/react-dom": "^18.2.6", "@types/react-dom": "^18.2.6",
"framer-motion": "^10.16.16", "framer-motion": "^10.16.16",
"javascript-time-ago": "^2.5.9", "javascript-time-ago": "^2.5.9",
"openmeteo": "^1.1.3",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-error-boundary": "^4.0.11", "react-error-boundary": "^4.0.11",
...@@ -3223,6 +3224,22 @@ ...@@ -3223,6 +3224,22 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/@openmeteo/sdk": {
"version": "1.11.4",
"resolved": "https://registry.npmjs.org/@openmeteo/sdk/-/sdk-1.11.4.tgz",
"integrity": "sha512-9So53FQMP+27rPzsJXisH6Ks4pow1Unr6mIk4yM6RD2BeWk0Xibd7Sj3UzYrBAeDf7fTzBvLisbhA9cgzs85fw==",
"dependencies": {
"flatbuffers": "^24.3.25"
},
"engines": {
"node": ">=12.0"
}
},
"node_modules/@openmeteo/sdk/node_modules/flatbuffers": {
"version": "24.3.25",
"resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-24.3.25.tgz",
"integrity": "sha512-3HDgPbgiwWMI9zVB7VYBHaMrbOO7Gm0v+yD2FV/sCKj+9NDeVL7BOBYUuhWAQGKWOzBo8S9WdMvV0eixO233XQ=="
},
"node_modules/@phosphor-icons/react": { "node_modules/@phosphor-icons/react": {
"version": "2.0.15", "version": "2.0.15",
"resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.0.15.tgz", "resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.0.15.tgz",
...@@ -8067,6 +8084,11 @@ ...@@ -8067,6 +8084,11 @@
"node": "^10.12.0 || >=12.0.0" "node": "^10.12.0 || >=12.0.0"
} }
}, },
"node_modules/flatbuffers": {
"version": "23.5.26",
"resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-23.5.26.tgz",
"integrity": "sha512-vE+SI9vrJDwi1oETtTIFldC/o9GsVKRM+s6EL0nQgxXlYV1Vc4Tk30hj4xGICftInKQKj1F3up2n8UbIVobISQ=="
},
"node_modules/flatted": { "node_modules/flatted": {
"version": "3.2.7", "version": "3.2.7",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
...@@ -12463,6 +12485,18 @@ ...@@ -12463,6 +12485,18 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/openmeteo": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/openmeteo/-/openmeteo-1.1.3.tgz",
"integrity": "sha512-f4ycmEA9rnhH4mPkDSLAuZ9dCu33nZ2bP7pLUJmZp2YYAuIDrGDIWU/VXQPmG48l5s+vw0VcnH9+CMqL57oADQ==",
"dependencies": {
"@openmeteo/sdk": "^1.4.0",
"flatbuffers": "^23.5.26"
},
"engines": {
"node": ">=12.0"
}
},
"node_modules/optionator": { "node_modules/optionator": {
"version": "0.9.1", "version": "0.9.1",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
...@@ -19511,6 +19545,21 @@ ...@@ -19511,6 +19545,21 @@
"fastq": "^1.6.0" "fastq": "^1.6.0"
} }
}, },
"@openmeteo/sdk": {
"version": "1.11.4",
"resolved": "https://registry.npmjs.org/@openmeteo/sdk/-/sdk-1.11.4.tgz",
"integrity": "sha512-9So53FQMP+27rPzsJXisH6Ks4pow1Unr6mIk4yM6RD2BeWk0Xibd7Sj3UzYrBAeDf7fTzBvLisbhA9cgzs85fw==",
"requires": {
"flatbuffers": "^24.3.25"
},
"dependencies": {
"flatbuffers": {
"version": "24.3.25",
"resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-24.3.25.tgz",
"integrity": "sha512-3HDgPbgiwWMI9zVB7VYBHaMrbOO7Gm0v+yD2FV/sCKj+9NDeVL7BOBYUuhWAQGKWOzBo8S9WdMvV0eixO233XQ=="
}
}
},
"@phosphor-icons/react": { "@phosphor-icons/react": {
"version": "2.0.15", "version": "2.0.15",
"resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.0.15.tgz", "resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.0.15.tgz",
...@@ -23083,6 +23132,11 @@ ...@@ -23083,6 +23132,11 @@
"rimraf": "^3.0.2" "rimraf": "^3.0.2"
} }
}, },
"flatbuffers": {
"version": "23.5.26",
"resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-23.5.26.tgz",
"integrity": "sha512-vE+SI9vrJDwi1oETtTIFldC/o9GsVKRM+s6EL0nQgxXlYV1Vc4Tk30hj4xGICftInKQKj1F3up2n8UbIVobISQ=="
},
"flatted": { "flatted": {
"version": "3.2.7", "version": "3.2.7",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
...@@ -26215,6 +26269,15 @@ ...@@ -26215,6 +26269,15 @@
"is-wsl": "^2.2.0" "is-wsl": "^2.2.0"
} }
}, },
"openmeteo": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/openmeteo/-/openmeteo-1.1.3.tgz",
"integrity": "sha512-f4ycmEA9rnhH4mPkDSLAuZ9dCu33nZ2bP7pLUJmZp2YYAuIDrGDIWU/VXQPmG48l5s+vw0VcnH9+CMqL57oADQ==",
"requires": {
"@openmeteo/sdk": "^1.4.0",
"flatbuffers": "^23.5.26"
}
},
"optionator": { "optionator": {
"version": "0.9.1", "version": "0.9.1",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
...@@ -63,17 +63,30 @@ ...@@ -63,17 +63,30 @@
} }
}, },
{ {
"type": "callout", "type": "wetter",
"position": { "position": {
"x": 9, "x": 9,
"y": 3, "y": 3,
"w": 16, "w": 8,
"h": 2
},
"config": {
"latitude": 51.5149,
"longitude": 7.466
}
},
{
"type": "callout",
"position": {
"x": 17,
"y": 3,
"w": 8,
"h": 2 "h": 2
}, },
"config": { "config": {
"type": "warning", "type": "warning",
"title": "Achtung, Testbetrieb", "title": "Achtung, Testbetrieb",
"description": "Der neue Infoscreen ist da! Bitte melde Fehler an die Admins unter root@oh14.de." "description": "Bitte melde Fehler an die Admins unter root@oh14.de."
} }
}, },
{ {
......
import { useEffect, useState } from 'react';
import PanelWrapper from '../../meta/PanelWrapper';
import PanelTitle from '../../meta/PanelTitle';
import PanelContent from '../../meta/PanelContent';
import { Cloud, CloudFog, CloudLightning, CloudRain, CloudSnow, CloudSun, Icon, Sun } from '@phosphor-icons/react';
import { fetchWeatherApi } from 'openmeteo';
export type WetterPanelDefinition = {
latitude: number,
longitude: number,
}
const WetterPanel = (props: { definition: WetterPanelDefinition }) => {
const [temperature, setTemperature] = useState<number>(0);
const [weatherCode, setWeatherCode] = useState<number>(0);
// TODO: how long will it rain
useEffect(() => {
// this function will be called every hour
const update = async () => {
// query open-meteo (https://github.com/open-meteo/typescript)
const params = {
latitude: [props.definition.latitude],
longitude: [props.definition.longitude],
current: 'temperature_2m,precipitation,weather_code,rain,showers',
forecast_days: 1,
}
const url = 'https://api.open-meteo.com/v1/forecast';
const currentWeather = (await fetchWeatherApi(url, params))[0].current()!;
setTemperature(Math.round(currentWeather.variables(0)!.value()))
setWeatherCode(currentWeather.variables(1)!.value())
}
// call it manually the first time
update();
const interval = setInterval(update, 1000 * 60 * 60);
return () => {
// clear up old handle in case this component is cleaned up
clearInterval(interval)
}
});
const renderWeather = (weatherCode: number, temperature: number) => {
const [WeatherIcon, text] = wcToIconText(weatherCode)!;
return (<div className='clex-1 flex flex-row gap-2 items-center'>
<WeatherIcon size={32} />
<p>{text} <span className='text-gray-400'>{temperature}°C</span></p>
</div>);
};
return (
<PanelWrapper className={"relative"}>
<PanelTitle title={"Wetter"} />
<PanelContent>
<div className={"flex flex-row gap-4 items-center"}>
{renderWeather(weatherCode, temperature)}
</div>
</PanelContent>
</PanelWrapper>
);
};
/**
* Take a weather code and give an icon and text for the weather
* @param weather_code weather code (see https://open-meteo.com/en/docs)
* @returns Tuple of Icon and text or undefined
*/
function wcToIconText(weather_code: number): [Icon, string] | undefined {
switch (true) {
case weather_code === 0: return [Sun, "Sonnig"]
case weather_code <= 2: return [CloudSun, "Bewölkt"]
case weather_code <= 3: return [Cloud, "Bedeckt"]
case weather_code <= 48: return [CloudFog, "Nebel"]
case weather_code <= 67: return [CloudRain, "Regen"]
case weather_code <= 77: return [CloudSnow, "Schneefall"]
case weather_code <= 82: return [CloudRain, "Starker Regen"]
case weather_code <= 86: return [CloudSnow, "Starker Schneefall"]
case weather_code <= 99: return [CloudLightning, "Gewitter"]
default: return undefined
}
}
export default WetterPanel;
...@@ -11,6 +11,7 @@ import BildPanel from "./Bild/BildPanel"; ...@@ -11,6 +11,7 @@ import BildPanel from "./Bild/BildPanel";
import MensaplanPanel from "./Mensaplan/MensaplanPanel"; import MensaplanPanel from "./Mensaplan/MensaplanPanel";
import CalloutPanel from "./Callout/CalloutPanel"; import CalloutPanel from "./Callout/CalloutPanel";
import GremiumPanel from "./Gremium/GremiumPanel"; import GremiumPanel from "./Gremium/GremiumPanel";
import WetterPanel from "./Wetter/WetterPanel";
/* /*
* First, please claim a unique id for your panel here. Convention is that it is all lowercase, in snake-case to be * First, please claim a unique id for your panel here. Convention is that it is all lowercase, in snake-case to be
...@@ -30,6 +31,7 @@ export const PanelRenderers: {[panelType: string]: React.FC<any & {definition: P ...@@ -30,6 +31,7 @@ export const PanelRenderers: {[panelType: string]: React.FC<any & {definition: P
"mensaplan": MensaplanPanel, "mensaplan": MensaplanPanel,
"callout": CalloutPanel, "callout": CalloutPanel,
"gremium": GremiumPanel, "gremium": GremiumPanel,
"wetter": WetterPanel,
"placeholder": () => ( "placeholder": () => (
<PanelWrapper className={"flex flex-col items-center justify-center text-zinc-400"}> <PanelWrapper className={"flex flex-col items-center justify-center text-zinc-400"}>
Dieses Panel wird noch entwickelt Dieses Panel wird noch entwickelt
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment