aboutsummaryrefslogtreecommitdiff
path: root/users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/weather.jsx
diff options
context:
space:
mode:
Diffstat (limited to 'users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/weather.jsx')
-rwxr-xr-xusers/ryan/modules/simple-bar/simple-bar-source/lib/components/data/weather.jsx214
1 files changed, 214 insertions, 0 deletions
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 @@
1import * as Uebersicht from "uebersicht";
2import * as DataWidget from "./data-widget.jsx";
3import * as DataWidgetLoader from "./data-widget-loader.jsx";
4import * as Icons from "../icons/icons.jsx";
5import * as Utils from "../../utils";
6import useWidgetRefresh from "../../hooks/use-widget-refresh";
7import useServerSocket from "../../hooks/use-server-socket";
8import { useSimpleBarContext } from "../simple-bar-context.jsx";
9
10export { weatherStyles as styles } from "../../styles/components/data/weather";
11
12const { React } = Uebersicht;
13
14const DEFAULT_REFRESH_FREQUENCY = 1000 * 60 * 30; // Default refresh frequency set to 30 minutes
15
16/**
17 * Weather widget component
18 */
19export const Widget = React.memo(() => {
20 const { displayIndex, settings, pushMissive } = useSimpleBarContext();
21 const { widgets, weatherWidgetOptions } = settings;
22 const { weatherWidget } = widgets;
23 const {
24 refreshFrequency,
25 customLocation,
26 unit,
27 hideLocation,
28 hideGradient,
29 showOnDisplay,
30 showIcon,
31 } = weatherWidgetOptions;
32
33 const refresh = React.useMemo(
34 () =>
35 Utils.getRefreshFrequency(refreshFrequency, DEFAULT_REFRESH_FREQUENCY),
36 [refreshFrequency],
37 );
38
39 const visible =
40 Utils.isVisibleOnDisplay(displayIndex, showOnDisplay) && weatherWidget;
41
42 const [state, setState] = React.useState();
43 const [loading, setLoading] = React.useState(visible);
44 const location = React.useRef(
45 visible && customLocation.length ? customLocation : undefined,
46 );
47
48 /**
49 * Resets the widget state and loading status
50 */
51 const resetWidget = () => {
52 setState(undefined);
53 setLoading(false);
54 };
55
56 /**
57 * Fetches weather data from wttr.in
58 */
59 const getWeather = React.useCallback(async () => {
60 if (!visible) return;
61 if (!location.current) {
62 const position = await Promise.race([getPosition(), Utils.timeout(5000)]);
63 if (!position) await getWeather();
64 const { city, zip } = position?.address || {};
65 location.current = zip || city;
66 if (!location.current) return setLoading(false);
67 }
68 try {
69 const result = await fetch(
70 `https://wttr.in/${location.current}?format=j1`,
71 );
72 const data = await result.json();
73 setState({ location: location.current, data });
74 } catch {
75 // eslint-disable-next-line no-console
76 console.error("Error while fetching weather")
77 }
78 setLoading(false);
79 }, [visible, location]);
80
81 useServerSocket("weather", visible, getWeather, resetWidget, setLoading);
82 useWidgetRefresh(visible, getWeather, refresh);
83
84 if (loading) return <DataWidgetLoader.Widget className="weather" />;
85 if (!state || !state.data.current_condition) return null;
86
87 const {
88 temp_C: tempC,
89 temp_F: tempF,
90 weatherDesc,
91 } = state.data.current_condition[0];
92 const temperature = unit === "C" ? tempC : tempF;
93 const wttrUnitParam = unit === "C" ? "?m" : "?u";
94
95 const description = weatherDesc[0].value;
96
97 const { astronomy } = state.data.weather[0];
98 const sunriseData = astronomy[0].sunrise.replace(" AM", "").split(":");
99 const sunsetData = astronomy[0].sunset.replace(" PM", "").split(":");
100
101 const now = new Date();
102 const nowIntervalStart = new Date();
103 nowIntervalStart.setHours(nowIntervalStart.getHours() - 1);
104 const nowIntervalStop = new Date();
105 nowIntervalStop.setHours(nowIntervalStop.getHours() + 1);
106 const sunriseTime = new Date();
107 sunriseTime.setHours(
108 parseInt(sunriseData[0], 10),
109 parseInt(sunriseData[1], 10),
110 0,
111 0,
112 );
113 const sunsetTime = new Date();
114 sunsetTime.setHours(
115 parseInt(sunsetData[0], 10) + 12,
116 parseInt(sunsetData[1], 10),
117 0,
118 0,
119 );
120
121 const atNight = sunriseTime >= now || now >= sunsetTime;
122
123 const Icon = getIcon(description, atNight);
124 const label = getLabel(state.location, temperature, unit, hideLocation);
125
126 const sunRising =
127 sunriseTime >= nowIntervalStart && sunriseTime <= nowIntervalStop;
128 const sunSetting =
129 sunsetTime >= nowIntervalStart && sunsetTime <= nowIntervalStop;
130
131 /**
132 * Handles right-click event to refresh weather data
133 * @param {Event} e - The event object
134 */
135 const onRightClick = (e) => {
136 Utils.clickEffect(e);
137 setLoading(true);
138 getWeather();
139 Utils.notification("Refreshing forecast from wttr.in...", pushMissive);
140 };
141
142 const classes = Utils.classNames("weather", {
143 "weather--sunrise": sunRising,
144 "weather--sunset": sunSetting,
145 });
146
147 return (
148 <DataWidget.Widget
149 classes={classes}
150 Icon={showIcon ? Icon : null}
151 href={`https://wttr.in/${state.location}${wttrUnitParam}`}
152 onClick={(e) => openWeather(e, pushMissive)}
153 onRightClick={onRightClick}
154 disableSlider
155 >
156 {!hideGradient && <div className="weather__gradient" />}
157 {label}
158 </DataWidget.Widget>
159 );
160});
161
162Widget.displayName = "Weather";
163
164/**
165 * Returns the appropriate weather icon based on the description and time of day
166 * @param {string} description - Weather description
167 * @param {boolean} atNight - Whether it is currently night time
168 * @returns {JSX.Element} - The weather icon component
169 */
170function getIcon(description, atNight) {
171 if (description.includes("fog") || description.includes("mist")) {
172 return Icons.Fog;
173 }
174 if (description.includes("storm")) return Icons.Storm;
175 if (description.includes("snow")) return Icons.Snow;
176 if (description.includes("rain")) return Icons.Rain;
177 if (description.includes("cloud")) return Icons.Cloud;
178 if (atNight) return Icons.Moon;
179 return Icons.Sun;
180}
181
182/**
183 * Returns the label for the weather widget
184 * @param {string} location - The location name
185 * @param {string} temperature - The temperature value
186 * @param {string} unit - The temperature unit (C or F)
187 * @param {boolean} hideLocation - Whether to hide the location name
188 * @returns {string} - The label text
189 */
190function getLabel(location, temperature, unit, hideLocation) {
191 if (!location) return "Fetching...";
192 if (hideLocation) return `${temperature}°${unit}`;
193 return `${location}, ${temperature}°${unit}`;
194}
195
196/**
197 * Opens the weather forecast in a new tab
198 * @param {Event} e - The event object
199 * @param {Function} pushMissive - Function to push notifications
200 */
201function openWeather(e, pushMissive) {
202 Utils.clickEffect(e);
203 Utils.notification("Opening forecast from wttr.in...", pushMissive);
204}
205
206/**
207 * Gets the current geographical position of the user
208 * @returns {Promise<GeolocationPosition>} - The position object
209 */
210async function getPosition() {
211 return new Promise((resolve) =>
212 navigator.geolocation.getCurrentPosition(resolve),
213 );
214}