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), ); }