321 lines
11 KiB
JavaScript
321 lines
11 KiB
JavaScript
import icons from "../../../icons/index.js";
|
|
import { keyRing } from "../../../../../../Documents/Keys/keyList.js";
|
|
|
|
const defaultWeather = {
|
|
location: {
|
|
localtime_epoch: 1719471600,
|
|
},
|
|
current: {
|
|
temp_f: 0,
|
|
wind_mph: 0,
|
|
condition: {
|
|
text: "Clear",
|
|
},
|
|
},
|
|
forecast: {
|
|
forecastday: [
|
|
{
|
|
day: {
|
|
daily_chance_of_rain: 0,
|
|
},
|
|
hour: [
|
|
{
|
|
time_epoch: 1719471600,
|
|
temp_f: 0,
|
|
condition: {
|
|
text: "Clear",
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
const theWeather = Variable(defaultWeather);
|
|
|
|
const getIcon = (fahren) => {
|
|
const icons = {
|
|
100: "",
|
|
75: "",
|
|
50: "",
|
|
25: "",
|
|
0: "",
|
|
};
|
|
const colors = {
|
|
100: "weather-color red",
|
|
75: "weather-color orange",
|
|
50: "weather-color lavender",
|
|
25: "weather-color blue",
|
|
0: "weather-color sky",
|
|
};
|
|
|
|
const threshold =
|
|
fahren < 0
|
|
? 0
|
|
: [100, 75, 50, 25, 0].find((threshold) => threshold <= fahren);
|
|
|
|
return {
|
|
icon: icons[threshold],
|
|
color: colors[threshold],
|
|
};
|
|
};
|
|
|
|
const WeatherWidget = () => {
|
|
return Widget.Box({
|
|
class_name: "calendar-menu-item-container weather",
|
|
child: Widget.Box({
|
|
class_name: "weather-container-box",
|
|
setup: (self) => {
|
|
Utils.interval(60 * 1000, () => {
|
|
Utils.execAsync(
|
|
`curl "https://api.weatherapi.com/v1/forecast.json?key=${keyRing.weatherapi}&q=93722&days=1&aqi=no&alerts=no"`,
|
|
)
|
|
.then((res) => {
|
|
if (typeof res === "string") {
|
|
theWeather.value = JSON.parse(res);
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
console.error(`Failed to fetch weather: ${err}`);
|
|
theWeather.value = defaultWeather;
|
|
});
|
|
});
|
|
|
|
return (self.child = Widget.Box({
|
|
vertical: true,
|
|
hexpand: true,
|
|
children: [
|
|
Widget.Box({
|
|
class_name: "calendar-menu-weather today",
|
|
hexpand: true,
|
|
children: [
|
|
Widget.Box({
|
|
vpack: "center",
|
|
hpack: "start",
|
|
class_name: "calendar-menu-weather today icon container",
|
|
children: [
|
|
Widget.Icon({
|
|
class_name: "calendar-menu-weather today icon",
|
|
icon: theWeather.bind("value").as((v) => {
|
|
let iconQuery = v.current.condition.text
|
|
.trim()
|
|
.toLowerCase()
|
|
.replaceAll(" ", "_")
|
|
|
|
if (!v.current.isDay && iconQuery === "partly_cloudy") {
|
|
iconQuery = "partly_cloudy_night";
|
|
}
|
|
return icons.weather[iconQuery];
|
|
}),
|
|
}),
|
|
],
|
|
}),
|
|
Widget.Box({
|
|
hpack: "center",
|
|
vpack: "center",
|
|
vertical: true,
|
|
children: [
|
|
Widget.Box({
|
|
hexpand: true,
|
|
vpack: "center",
|
|
class_name: "calendar-menu-weather today temp container",
|
|
vertical: false,
|
|
children: [
|
|
Widget.Box({
|
|
hexpand: true,
|
|
hpack: "center",
|
|
children: [
|
|
Widget.Label({
|
|
class_name:
|
|
"calendar-menu-weather today temp label",
|
|
label: theWeather
|
|
.bind("value")
|
|
.as((v) => `${Math.ceil(v.current.temp_f)}° F`),
|
|
}),
|
|
Widget.Label({
|
|
class_name: theWeather
|
|
.bind("value")
|
|
.as(
|
|
(v) =>
|
|
`calendar-menu-weather today temp label icon ${getIcon(Math.ceil(v.current.temp_f)).color}`,
|
|
),
|
|
label: theWeather
|
|
.bind("value")
|
|
.as(
|
|
(v) =>
|
|
getIcon(Math.ceil(v.current.temp_f)).icon,
|
|
),
|
|
}),
|
|
],
|
|
}),
|
|
],
|
|
}),
|
|
Widget.Box({
|
|
hpack: "center",
|
|
child: Widget.Label({
|
|
max_width_chars: 17,
|
|
truncate: "end",
|
|
lines: 2,
|
|
class_name: theWeather
|
|
.bind("value")
|
|
.as(
|
|
(v) =>
|
|
`calendar-menu-weather today condition label ${getIcon(Math.ceil(v.current.temp_f)).color}`,
|
|
),
|
|
label: theWeather
|
|
.bind("value")
|
|
.as((v) => v.current.condition.text),
|
|
}),
|
|
}),
|
|
],
|
|
}),
|
|
Widget.Box({
|
|
class_name: "calendar-menu-weather today stats container",
|
|
hpack: "end",
|
|
vpack: "center",
|
|
vertical: true,
|
|
children: [
|
|
Widget.Box({
|
|
class_name: "weather wind",
|
|
children: [
|
|
Widget.Label({
|
|
class_name: "weather wind icon",
|
|
label: "",
|
|
}),
|
|
Widget.Label({
|
|
class_name: "weather wind label",
|
|
label: theWeather
|
|
.bind("value")
|
|
.as((v) => `${Math.floor(v.current.wind_mph)} mph`),
|
|
}),
|
|
],
|
|
}),
|
|
Widget.Box({
|
|
class_name: "weather precip",
|
|
children: [
|
|
Widget.Label({
|
|
class_name: "weather precip icon",
|
|
label: "",
|
|
}),
|
|
Widget.Label({
|
|
class_name: "weather precip label",
|
|
label: theWeather
|
|
.bind("value")
|
|
.as(
|
|
(v) =>
|
|
`${v.forecast.forecastday[0].day.daily_chance_of_rain}%`,
|
|
),
|
|
}),
|
|
],
|
|
}),
|
|
],
|
|
}),
|
|
],
|
|
}),
|
|
Widget.Separator({
|
|
class_name: "menu-separator weather",
|
|
}),
|
|
Widget.Box({
|
|
vertical: false,
|
|
hexpand: true,
|
|
hpack: "fill",
|
|
class_name: "hourly-weather-container",
|
|
children: [1, 2, 3, 4].map((hoursFromNow) => {
|
|
const getNextEpoch = (wthr) => {
|
|
const currentEpoch = wthr.location.localtime_epoch;
|
|
const epochAtHourStart = currentEpoch - (currentEpoch % 3600);
|
|
let nextEpoch = 3600 * hoursFromNow + epochAtHourStart;
|
|
|
|
const curHour = new Date(currentEpoch * 1000).getHours();
|
|
|
|
/*
|
|
* NOTE: Since the API is only capable of showing the current day; if
|
|
* the hours left in the day are less than 4 (aka spilling into the next day),
|
|
* then rewind to contain the prediction within the current day.
|
|
*/
|
|
if (curHour > 19) {
|
|
const hoursToRewind = curHour - 19;
|
|
nextEpoch =
|
|
3600 * hoursFromNow +
|
|
epochAtHourStart -
|
|
hoursToRewind * 3600;
|
|
}
|
|
return nextEpoch;
|
|
};
|
|
|
|
return Widget.Box({
|
|
class_name: "hourly-weather-item",
|
|
hexpand: true,
|
|
vertical: true,
|
|
children: [
|
|
Widget.Label({
|
|
class_name: "hourly-weather-time",
|
|
label: theWeather.bind("value").as((w) => {
|
|
if (!Object.keys(w).length) {
|
|
return "-";
|
|
}
|
|
|
|
const nextEpoch = getNextEpoch(w);
|
|
const dateAtEpoch = new Date(nextEpoch * 1000);
|
|
let hours = dateAtEpoch.getHours();
|
|
const ampm = hours >= 12 ? "PM" : "AM";
|
|
hours = hours % 12 || 12;
|
|
|
|
return `${hours}${ampm}`;
|
|
}),
|
|
}),
|
|
Widget.Icon({
|
|
class_name: "hourly-weather-icon",
|
|
icon: theWeather.bind("value").as((w) => {
|
|
if (!Object.keys(w).length) {
|
|
return "-";
|
|
}
|
|
|
|
const nextEpoch = getNextEpoch(w);
|
|
const weatherAtEpoch =
|
|
w.forecast.forecastday[0].hour.find(
|
|
(h) => h.time_epoch === nextEpoch,
|
|
);
|
|
|
|
let iconQuery = weatherAtEpoch?.condition.text
|
|
.trim()
|
|
.toLowerCase()
|
|
.replaceAll(" ", "_")
|
|
|
|
if (!weatherAtEpoch?.isDay && iconQuery === "partly_cloudy") {
|
|
iconQuery = "partly_cloudy_night";
|
|
}
|
|
|
|
return icons.weather[iconQuery];
|
|
}),
|
|
}),
|
|
Widget.Label({
|
|
class_name: "hourly-weather-temp",
|
|
label: theWeather.bind("value").as((w) => {
|
|
if (!Object.keys(w).length) {
|
|
return "-";
|
|
}
|
|
|
|
const nextEpoch = getNextEpoch(w);
|
|
const weatherAtEpoch =
|
|
w.forecast.forecastday[0].hour.find(
|
|
(h) => h.time_epoch === nextEpoch,
|
|
);
|
|
|
|
return `${weatherAtEpoch ? Math.ceil(weatherAtEpoch.temp_f) : "-"}° F`;
|
|
}),
|
|
}),
|
|
],
|
|
});
|
|
}),
|
|
}),
|
|
],
|
|
}));
|
|
},
|
|
}),
|
|
});
|
|
};
|
|
|
|
export { WeatherWidget };
|