2020-10-20 20:04:51 -05:00
|
|
|
// hourly forecast list
|
2022-11-22 16:19:10 -06:00
|
|
|
|
|
|
|
|
import STATUS from './status.mjs';
|
2026-04-07 14:57:23 -04:00
|
|
|
import { DateTime } from '../vendor/auto/luxon.mjs';
|
2025-10-22 00:22:29 +00:00
|
|
|
import { temperature as temperatureUnit, windSpeed as windUnit } from './utils/units.mjs';
|
2022-11-22 16:19:10 -06:00
|
|
|
import { directionToNSEW } from './utils/calc.mjs';
|
2022-11-22 16:29:10 -06:00
|
|
|
import WeatherDisplay from './weatherdisplay.mjs';
|
2025-02-25 09:44:24 -06:00
|
|
|
import { registerDisplay, timeZone } from './navigation.mjs';
|
2025-06-24 23:40:07 -04:00
|
|
|
import calculateScrollTiming from './utils/scroll-timing.mjs';
|
2026-04-07 14:57:23 -04:00
|
|
|
import { getSmallIconFromWmoCode } from './icons.mjs';
|
2020-10-20 20:04:51 -05:00
|
|
|
|
|
|
|
|
class Hourly extends WeatherDisplay {
|
|
|
|
|
constructor(navId, elemId, defaultActive) {
|
2022-11-21 21:50:22 -06:00
|
|
|
super(navId, elemId, 'Hourly Forecast', defaultActive);
|
2025-06-24 23:40:07 -04:00
|
|
|
this.scrollCache = {
|
|
|
|
|
displayHeight: 0,
|
|
|
|
|
contentHeight: 0,
|
|
|
|
|
maxOffset: 0,
|
|
|
|
|
hourlyLines: null,
|
|
|
|
|
};
|
2020-10-20 20:04:51 -05:00
|
|
|
}
|
|
|
|
|
|
2025-04-02 11:10:58 -05:00
|
|
|
async getData(weatherParameters, refresh) {
|
2025-04-02 16:45:11 -05:00
|
|
|
const superResponse = super.getData(weatherParameters, refresh);
|
2026-04-07 14:57:23 -04:00
|
|
|
this.data = parseForecast(this.weatherParameters);
|
2025-06-24 23:40:07 -04:00
|
|
|
|
2026-04-07 14:57:23 -04:00
|
|
|
if (!this.data?.length) {
|
2025-06-24 23:40:07 -04:00
|
|
|
if (this.isEnabled) this.setStatus(STATUS.failed);
|
|
|
|
|
this.getDataCallback(undefined);
|
2026-04-07 14:57:23 -04:00
|
|
|
return;
|
2025-06-24 23:40:07 -04:00
|
|
|
}
|
2026-04-07 14:57:23 -04:00
|
|
|
|
|
|
|
|
this.getDataCallback();
|
|
|
|
|
if (!superResponse) return;
|
|
|
|
|
|
|
|
|
|
this.setStatus(STATUS.loaded);
|
|
|
|
|
this.drawLongCanvas();
|
2020-10-20 20:04:51 -05:00
|
|
|
}
|
|
|
|
|
|
2020-10-29 16:44:28 -05:00
|
|
|
async drawLongCanvas() {
|
2022-07-29 16:12:42 -05:00
|
|
|
const list = this.elem.querySelector('.hourly-lines');
|
|
|
|
|
list.innerHTML = '';
|
2020-10-20 20:04:51 -05:00
|
|
|
|
2025-02-25 09:44:24 -06:00
|
|
|
const startingHour = DateTime.local().setZone(timeZone());
|
2026-01-17 11:42:26 -06:00
|
|
|
const shortData = this.data.slice(0, 24);
|
|
|
|
|
const lines = shortData.map((data, index) => {
|
2020-10-29 16:44:28 -05:00
|
|
|
const hour = startingHour.plus({ hours: index });
|
2026-04-07 14:57:23 -04:00
|
|
|
const fillValues = {
|
|
|
|
|
hour: hour.toLocaleString({ weekday: 'short', hour: 'numeric' }),
|
|
|
|
|
temp: data.temperature.toString().padStart(3),
|
|
|
|
|
like: data.apparentTemperature.toString().padStart(3),
|
|
|
|
|
wind: data.windSpeed > 0
|
|
|
|
|
? data.windDirection + (Array(6 - data.windDirection.length - Math.round(data.windSpeed).toString().length).join(' ')) + Math.round(data.windSpeed).toString()
|
|
|
|
|
: 'Calm',
|
|
|
|
|
icon: { type: 'img', src: data.icon },
|
|
|
|
|
};
|
2020-10-20 20:04:51 -05:00
|
|
|
|
2025-04-07 22:13:20 -05:00
|
|
|
const filledRow = this.fillTemplate('hourly-row', fillValues);
|
2026-01-17 11:42:26 -06:00
|
|
|
if (data.apparentTemperature < data.temperature) {
|
2025-04-07 22:13:20 -05:00
|
|
|
filledRow.querySelector('.like').classList.add('wind-chill');
|
2026-04-07 14:57:23 -04:00
|
|
|
} else if (data.apparentTemperature > data.temperature) {
|
2025-04-07 22:13:20 -05:00
|
|
|
filledRow.querySelector('.like').classList.add('heat-index');
|
|
|
|
|
}
|
|
|
|
|
return filledRow;
|
2022-07-29 16:12:42 -05:00
|
|
|
});
|
2020-10-20 20:04:51 -05:00
|
|
|
|
2022-07-29 16:12:42 -05:00
|
|
|
list.append(...lines);
|
2025-06-24 23:40:07 -04:00
|
|
|
this.setTiming(list);
|
2022-07-29 16:12:42 -05:00
|
|
|
}
|
2020-10-20 20:04:51 -05:00
|
|
|
|
2022-07-29 16:12:42 -05:00
|
|
|
drawCanvas() {
|
|
|
|
|
super.drawCanvas();
|
2020-10-20 20:04:51 -05:00
|
|
|
this.finishDraw();
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-29 16:12:42 -05:00
|
|
|
showCanvas() {
|
|
|
|
|
this.drawCanvas();
|
2020-10-20 20:04:51 -05:00
|
|
|
super.showCanvas();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
screenIndexChange() {
|
|
|
|
|
this.baseCountChange(this.navBaseCount);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
baseCountChange(count) {
|
2025-06-24 23:40:07 -04:00
|
|
|
const hourlyLines = this.elem.querySelector('.hourly-lines');
|
|
|
|
|
if (!hourlyLines) return;
|
|
|
|
|
|
|
|
|
|
if (this.scrollCache.hourlyLines !== hourlyLines || this.scrollCache.displayHeight === 0) {
|
|
|
|
|
this.scrollCache.displayHeight = this.elem.querySelector('.main').offsetHeight;
|
|
|
|
|
this.scrollCache.contentHeight = hourlyLines.offsetHeight;
|
|
|
|
|
this.scrollCache.maxOffset = Math.max(0, this.scrollCache.contentHeight - this.scrollCache.displayHeight);
|
|
|
|
|
this.scrollCache.hourlyLines = hourlyLines;
|
|
|
|
|
hourlyLines.style.willChange = 'transform';
|
|
|
|
|
hourlyLines.style.backfaceVisibility = 'hidden';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let offsetY = Math.min(this.scrollCache.maxOffset, (count - this.scrollTiming.initialCounts) * this.scrollTiming.pixelsPerCount);
|
2020-10-20 20:04:51 -05:00
|
|
|
if (offsetY < 0) offsetY = 0;
|
2025-06-24 23:40:07 -04:00
|
|
|
hourlyLines.style.transform = `translateY(-${Math.round(offsetY)}px)`;
|
2020-10-20 20:04:51 -05:00
|
|
|
}
|
|
|
|
|
|
2025-06-24 23:40:07 -04:00
|
|
|
async getHourlyData(stillWaiting) {
|
2022-12-12 13:53:33 -06:00
|
|
|
if (stillWaiting) this.stillWaitingCallbacks.push(stillWaiting);
|
2025-05-02 23:22:00 -05:00
|
|
|
this.setAutoReload();
|
2022-12-07 15:36:02 -06:00
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
if (this.data) resolve(this.data);
|
|
|
|
|
this.getDataCallbacks.push(() => resolve(this.data));
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-06-24 23:40:07 -04:00
|
|
|
|
|
|
|
|
setTiming(list) {
|
|
|
|
|
const container = this.elem.querySelector('.main');
|
|
|
|
|
const timingConfig = calculateScrollTiming(list, container);
|
|
|
|
|
this.timing.baseDelay = timingConfig.baseDelay;
|
|
|
|
|
this.timing.delay = timingConfig.delay;
|
|
|
|
|
this.scrollTiming = timingConfig.scrollTiming;
|
|
|
|
|
this.calcNavTiming();
|
|
|
|
|
}
|
2020-10-29 16:44:28 -05:00
|
|
|
}
|
2022-11-22 16:19:10 -06:00
|
|
|
|
2026-04-07 14:57:23 -04:00
|
|
|
const parseForecast = (weatherParameters) => {
|
2025-02-23 23:29:39 -06:00
|
|
|
const temperatureConverter = temperatureUnit();
|
2025-10-22 00:22:29 +00:00
|
|
|
const windConverter = windUnit();
|
2026-04-07 14:57:23 -04:00
|
|
|
const currentTime = new Date();
|
|
|
|
|
const todayKey = currentTime.toLocaleDateString('en-CA', { timeZone: weatherParameters.timeZone });
|
|
|
|
|
const tomorrowKey = DateTime.fromISO(todayKey).plus({ days: 1 }).toISODate();
|
|
|
|
|
const availableTimes = [
|
|
|
|
|
...(weatherParameters.forecast[todayKey]?.hours ?? []),
|
|
|
|
|
...(weatherParameters.forecast[tomorrowKey]?.hours ?? []),
|
|
|
|
|
];
|
|
|
|
|
if (!availableTimes.length) return [];
|
|
|
|
|
|
|
|
|
|
let closestIndex = 0;
|
|
|
|
|
let minDiff = Math.abs(new Date(availableTimes[0].time) - currentTime);
|
|
|
|
|
availableTimes.forEach((entry, index) => {
|
|
|
|
|
const diff = Math.abs(new Date(entry.time) - currentTime);
|
|
|
|
|
if (diff < minDiff) {
|
|
|
|
|
minDiff = diff;
|
|
|
|
|
closestIndex = index;
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-02-23 23:29:39 -06:00
|
|
|
|
2026-04-07 14:57:23 -04:00
|
|
|
return availableTimes.slice(closestIndex).map((hour) => ({
|
|
|
|
|
temperature: temperatureConverter(hour.temperature_2m),
|
2025-02-23 23:29:39 -06:00
|
|
|
temperatureUnit: temperatureConverter.units,
|
2026-04-07 14:57:23 -04:00
|
|
|
apparentTemperature: temperatureConverter(hour.apparent_temperature),
|
|
|
|
|
windSpeed: windConverter(hour.wind_speed_10m),
|
2025-10-22 00:22:29 +00:00
|
|
|
windUnit: windConverter.units,
|
2026-04-07 14:57:23 -04:00
|
|
|
windDirection: directionToNSEW(hour.wind_direction_10m ?? 0),
|
|
|
|
|
probabilityOfPrecipitation: Math.round(hour.precipitation_probability ?? 0),
|
|
|
|
|
skyCover: Math.round(hour.cloud_cover ?? 0),
|
|
|
|
|
icon: getSmallIconFromWmoCode(hour.weather_code, Boolean(hour.is_day)),
|
|
|
|
|
dewpoint: temperatureConverter(hour.dew_point_2m),
|
2022-12-09 13:51:51 -06:00
|
|
|
}));
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-07 14:57:23 -04:00
|
|
|
const display = new Hourly(3, 'hourly', true);
|
2022-12-07 15:36:02 -06:00
|
|
|
registerDisplay(display);
|
|
|
|
|
|
2025-06-24 23:40:07 -04:00
|
|
|
export default display.getHourlyData.bind(display);
|