Finish all options sets
This commit is contained in:
28
modules/menus/calendar/weather/hourly/icon/index.js
Normal file
28
modules/menus/calendar/weather/hourly/icon/index.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import icons from "../../../../../icons/index.js";
|
||||
|
||||
export const HourlyIcon = (theWeather, getNextEpoch) => {
|
||||
return 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];
|
||||
}),
|
||||
});
|
||||
};
|
||||
44
modules/menus/calendar/weather/hourly/index.js
Normal file
44
modules/menus/calendar/weather/hourly/index.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import { HourlyIcon } from "./icon/index.js";
|
||||
import { HourlyTemp } from "./temperature/index.js";
|
||||
import { HourlyTime } from "./time/index.js";
|
||||
|
||||
export const Hourly = (theWeather) => {
|
||||
return 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: [
|
||||
HourlyTime(theWeather, getNextEpoch),
|
||||
HourlyIcon(theWeather, getNextEpoch),
|
||||
HourlyTemp(theWeather, getNextEpoch),
|
||||
],
|
||||
});
|
||||
}),
|
||||
});
|
||||
};
|
||||
27
modules/menus/calendar/weather/hourly/temperature/index.js
Normal file
27
modules/menus/calendar/weather/hourly/temperature/index.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import options from "options";
|
||||
|
||||
const { unit } = options.menus.clock.weather;
|
||||
|
||||
export const HourlyTemp = (theWeather, getNextEpoch) => {
|
||||
return Widget.Label({
|
||||
class_name: "hourly-weather-temp",
|
||||
label: Utils.merge(
|
||||
[theWeather.bind("value"), unit.bind("value")],
|
||||
(wthr, unt) => {
|
||||
if (!Object.keys(wthr).length) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
const nextEpoch = getNextEpoch(wthr);
|
||||
const weatherAtEpoch = wthr.forecast.forecastday[0].hour.find(
|
||||
(h) => h.time_epoch === nextEpoch,
|
||||
);
|
||||
|
||||
if (unt === "imperial") {
|
||||
return `${weatherAtEpoch ? Math.ceil(weatherAtEpoch.temp_f) : "-"}° F`;
|
||||
}
|
||||
return `${weatherAtEpoch ? Math.ceil(weatherAtEpoch.temp_c) : "-"}° C`;
|
||||
},
|
||||
),
|
||||
});
|
||||
};
|
||||
18
modules/menus/calendar/weather/hourly/time/index.js
Normal file
18
modules/menus/calendar/weather/hourly/time/index.js
Normal file
@@ -0,0 +1,18 @@
|
||||
export const HourlyTime = (theWeather, getNextEpoch) => {
|
||||
return 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}`;
|
||||
}),
|
||||
});
|
||||
};
|
||||
25
modules/menus/calendar/weather/icon/index.js
Normal file
25
modules/menus/calendar/weather/icon/index.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import icons from "../../../../icons/index.js";
|
||||
|
||||
export const TodayIcon = (theWeather) => {
|
||||
return 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];
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
||||
@@ -1,5 +1,10 @@
|
||||
import icons from "../../../icons/index.js";
|
||||
import { keyRing } from "../../../../../../Documents/Keys/keyList.js";
|
||||
import options from "options";
|
||||
import { TodayIcon } from "./icon/index.js";
|
||||
import { TodayStats } from "./stats/index.js";
|
||||
import { TodayTemperature } from "./temperature/index.js";
|
||||
import { Hourly } from "./hourly/index.js";
|
||||
|
||||
const { key, interval } = options.menus.clock.weather;
|
||||
|
||||
const defaultWeather = {
|
||||
location: {
|
||||
@@ -34,53 +39,31 @@ const defaultWeather = {
|
||||
|
||||
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;
|
||||
Utils.merge(
|
||||
[key.bind("value"), interval.bind("value")],
|
||||
(weatherKey, weatherInterval) => {
|
||||
Utils.interval(weatherInterval, () => {
|
||||
Utils.execAsync(
|
||||
`curl "https://api.weatherapi.com/v1/forecast.json?key=${weatherKey}&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,
|
||||
@@ -90,226 +73,15 @@ const WeatherWidget = () => {
|
||||
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}%`,
|
||||
),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
TodayIcon(theWeather),
|
||||
TodayTemperature(theWeather),
|
||||
TodayStats(theWeather),
|
||||
],
|
||||
}),
|
||||
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`;
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
}),
|
||||
}),
|
||||
Hourly(theWeather),
|
||||
],
|
||||
}));
|
||||
},
|
||||
|
||||
52
modules/menus/calendar/weather/stats/index.js
Normal file
52
modules/menus/calendar/weather/stats/index.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import options from "options";
|
||||
|
||||
const { unit } = options.menus.clock.weather;
|
||||
|
||||
export const TodayStats = (theWeather) => {
|
||||
return 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: Utils.merge(
|
||||
[theWeather.bind("value"), unit.bind("value")],
|
||||
(wthr, unt) => {
|
||||
if (unt === "imperial") {
|
||||
return `${Math.floor(wthr.current.wind_mph)} mph`;
|
||||
}
|
||||
return `${Math.floor(wthr.current.wind_kph)} kph`;
|
||||
},
|
||||
),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
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}%`,
|
||||
),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
||||
92
modules/menus/calendar/weather/temperature/index.js
Normal file
92
modules/menus/calendar/weather/temperature/index.js
Normal file
@@ -0,0 +1,92 @@
|
||||
import options from "options";
|
||||
const { unit } = options.menus.clock.weather;
|
||||
|
||||
export const TodayTemperature = (theWeather) => {
|
||||
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],
|
||||
};
|
||||
};
|
||||
|
||||
return 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: Utils.merge(
|
||||
[theWeather.bind("value"), unit.bind("value")],
|
||||
(wthr, unt) => {
|
||||
if (unt === "imperial") {
|
||||
return `${Math.ceil(wthr.current.temp_f)}° F`;
|
||||
} else {
|
||||
return `${Math.ceil(wthr.current.temp_c)}° C`;
|
||||
}
|
||||
},
|
||||
),
|
||||
}),
|
||||
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),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user