From d06784958d73159b5e4abafe5114804662114ed7 Mon Sep 17 00:00:00 2001 From: Ryan Schanzenbacher Date: Thu, 9 Jul 2026 22:15:53 -0400 Subject: move ubersicht modules in tree --- .../lib/components/data/weather.jsx | 214 +++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100755 users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/weather.jsx (limited to 'users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/weather.jsx') diff --git a/users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/weather.jsx b/users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/weather.jsx new file mode 100755 index 0000000..946fda1 --- /dev/null +++ b/users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/weather.jsx @@ -0,0 +1,214 @@ +import * as Uebersicht from "uebersicht"; +import * as DataWidget from "./data-widget.jsx"; +import * as DataWidgetLoader from "./data-widget-loader.jsx"; +import * as Icons from "../icons/icons.jsx"; +import * as Utils from "../../utils"; +import useWidgetRefresh from "../../hooks/use-widget-refresh"; +import useServerSocket from "../../hooks/use-server-socket"; +import { useSimpleBarContext } from "../simple-bar-context.jsx"; + +export { weatherStyles as styles } from "../../styles/components/data/weather"; + +const { React } = Uebersicht; + +const DEFAULT_REFRESH_FREQUENCY = 1000 * 60 * 30; // Default refresh frequency set to 30 minutes + +/** + * Weather widget component + */ +export const Widget = React.memo(() => { + const { displayIndex, settings, pushMissive } = useSimpleBarContext(); + const { widgets, weatherWidgetOptions } = settings; + const { weatherWidget } = widgets; + const { + refreshFrequency, + customLocation, + unit, + hideLocation, + hideGradient, + showOnDisplay, + showIcon, + } = weatherWidgetOptions; + + const refresh = React.useMemo( + () => + Utils.getRefreshFrequency(refreshFrequency, DEFAULT_REFRESH_FREQUENCY), + [refreshFrequency], + ); + + const visible = + Utils.isVisibleOnDisplay(displayIndex, showOnDisplay) && weatherWidget; + + const [state, setState] = React.useState(); + const [loading, setLoading] = React.useState(visible); + const location = React.useRef( + visible && customLocation.length ? customLocation : undefined, + ); + + /** + * Resets the widget state and loading status + */ + const resetWidget = () => { + setState(undefined); + setLoading(false); + }; + + /** + * Fetches weather data from wttr.in + */ + const getWeather = React.useCallback(async () => { + if (!visible) return; + if (!location.current) { + const position = await Promise.race([getPosition(), Utils.timeout(5000)]); + if (!position) await getWeather(); + const { city, zip } = position?.address || {}; + location.current = zip || city; + if (!location.current) return setLoading(false); + } + try { + const result = await fetch( + `https://wttr.in/${location.current}?format=j1`, + ); + const data = await result.json(); + setState({ location: location.current, data }); + } catch { + // eslint-disable-next-line no-console + console.error("Error while fetching weather") + } + setLoading(false); + }, [visible, location]); + + useServerSocket("weather", visible, getWeather, resetWidget, setLoading); + useWidgetRefresh(visible, getWeather, refresh); + + if (loading) return ; + if (!state || !state.data.current_condition) return null; + + const { + temp_C: tempC, + temp_F: tempF, + weatherDesc, + } = state.data.current_condition[0]; + const temperature = unit === "C" ? tempC : tempF; + const wttrUnitParam = unit === "C" ? "?m" : "?u"; + + const description = weatherDesc[0].value; + + const { astronomy } = state.data.weather[0]; + const sunriseData = astronomy[0].sunrise.replace(" AM", "").split(":"); + const sunsetData = astronomy[0].sunset.replace(" PM", "").split(":"); + + const now = new Date(); + const nowIntervalStart = new Date(); + nowIntervalStart.setHours(nowIntervalStart.getHours() - 1); + const nowIntervalStop = new Date(); + nowIntervalStop.setHours(nowIntervalStop.getHours() + 1); + const sunriseTime = new Date(); + sunriseTime.setHours( + parseInt(sunriseData[0], 10), + parseInt(sunriseData[1], 10), + 0, + 0, + ); + const sunsetTime = new Date(); + sunsetTime.setHours( + parseInt(sunsetData[0], 10) + 12, + parseInt(sunsetData[1], 10), + 0, + 0, + ); + + const atNight = sunriseTime >= now || now >= sunsetTime; + + const Icon = getIcon(description, atNight); + const label = getLabel(state.location, temperature, unit, hideLocation); + + const sunRising = + sunriseTime >= nowIntervalStart && sunriseTime <= nowIntervalStop; + const sunSetting = + sunsetTime >= nowIntervalStart && sunsetTime <= nowIntervalStop; + + /** + * Handles right-click event to refresh weather data + * @param {Event} e - The event object + */ + const onRightClick = (e) => { + Utils.clickEffect(e); + setLoading(true); + getWeather(); + Utils.notification("Refreshing forecast from wttr.in...", pushMissive); + }; + + const classes = Utils.classNames("weather", { + "weather--sunrise": sunRising, + "weather--sunset": sunSetting, + }); + + return ( + openWeather(e, pushMissive)} + onRightClick={onRightClick} + disableSlider + > + {!hideGradient &&
} + {label} + + ); +}); + +Widget.displayName = "Weather"; + +/** + * Returns the appropriate weather icon based on the description and time of day + * @param {string} description - Weather description + * @param {boolean} atNight - Whether it is currently night time + * @returns {JSX.Element} - The weather icon component + */ +function getIcon(description, atNight) { + if (description.includes("fog") || description.includes("mist")) { + return Icons.Fog; + } + if (description.includes("storm")) return Icons.Storm; + if (description.includes("snow")) return Icons.Snow; + if (description.includes("rain")) return Icons.Rain; + if (description.includes("cloud")) return Icons.Cloud; + if (atNight) return Icons.Moon; + return Icons.Sun; +} + +/** + * Returns the label for the weather widget + * @param {string} location - The location name + * @param {string} temperature - The temperature value + * @param {string} unit - The temperature unit (C or F) + * @param {boolean} hideLocation - Whether to hide the location name + * @returns {string} - The label text + */ +function getLabel(location, temperature, unit, hideLocation) { + if (!location) return "Fetching..."; + if (hideLocation) return `${temperature}°${unit}`; + return `${location}, ${temperature}°${unit}`; +} + +/** + * Opens the weather forecast in a new tab + * @param {Event} e - The event object + * @param {Function} pushMissive - Function to push notifications + */ +function openWeather(e, pushMissive) { + Utils.clickEffect(e); + Utils.notification("Opening forecast from wttr.in...", pushMissive); +} + +/** + * Gets the current geographical position of the user + * @returns {Promise} - The position object + */ +async function getPosition() { + return new Promise((resolve) => + navigator.geolocation.getCurrentPosition(resolve), + ); +} -- cgit v1.3