This commit is contained in:
Matt Walsh 2022-12-06 16:14:56 -06:00
parent b71d696670
commit 6933e7b7f1
26 changed files with 600 additions and 873 deletions

View file

@ -10,6 +10,13 @@ module.exports = {
globals: { globals: {
Atomics: 'readonly', Atomics: 'readonly',
SharedArrayBuffer: 'readonly', SharedArrayBuffer: 'readonly',
StationInfo: 'readonly',
RegionalCities: 'readonly',
TravelCities: 'readonly',
NoSleep: 'readonly',
states: 'readonly',
SunCalc: 'readonly',
}, },
parserOptions: { parserOptions: {
ecmaVersion: 2021, ecmaVersion: 2021,

View file

@ -6796,13 +6796,6 @@ const StationInfo = {
lat: 39.4429, lat: 39.4429,
lon: -87.32207, lon: -87.32207,
}, },
KEYE: {
id: 'KEYE',
city: 'Indianapolis, Eagle Creek Airpark',
state: 'IN',
lat: 39.825,
lon: -86.29583,
},
KGSH: { KGSH: {
id: 'KGSH', id: 'KGSH',
city: 'Goshen, Goshen Municipal Airport', city: 'Goshen, Goshen Municipal Airport',
@ -19544,4 +19537,4 @@ const StationInfo = {
lon: -78.5016, lon: -78.5016,
}, },
}; };

View file

@ -6798,13 +6798,6 @@ const StationInfo = {
lat: 39.4429, lat: 39.4429,
lon: -87.32207, lon: -87.32207,
}, },
KEYE: {
id: 'KEYE',
city: 'Indianapolis, Eagle Creek Airpark',
state: 'IN',
lat: 39.825,
lon: -86.29583,
},
KGSH: { KGSH: {
id: 'KGSH', id: 'KGSH',
city: 'Goshen, Goshen Municipal Airport', city: 'Goshen, Goshen Municipal Airport',

View file

@ -1,21 +1,17 @@
import { UNITS } from './modules/config.mjs'; import { setUnits } from './modules/utils/units.mjs';
import { json } from './modules/utils/fetch.mjs'; import { json } from './modules/utils/fetch.mjs';
import noSleep from './modules/utils/nosleep.mjs';
import {
message as navMessage, isPlaying, resize, resetStatuses, latLonReceived, stopAutoRefreshTimer, registerRefreshData,
} from './modules/navigation.mjs';
/* globals NoSleep, states, navigation */
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
init(); init();
}); });
const overrides = {}; const overrides = {};
const AutoRefreshIntervalMs = 500;
const AutoRefreshTotalIntervalMs = 600000; // 10 min.
let AutoSelectQuery = false; let AutoSelectQuery = false;
let LastUpdate = null;
let AutoRefreshIntervalId = null;
let AutoRefreshCountMs = 0;
let FullScreenOverride = false; let FullScreenOverride = false;
const categories = [ const categories = [
@ -35,6 +31,8 @@ const init = () => {
e.target.select(); e.target.select();
}); });
registerRefreshData(LoadTwcData);
document.getElementById('NavigateMenu').addEventListener('click', btnNavigateMenuClick); document.getElementById('NavigateMenu').addEventListener('click', btnNavigateMenuClick);
document.getElementById('NavigateRefresh').addEventListener('click', btnNavigateRefreshClick); document.getElementById('NavigateRefresh').addEventListener('click', btnNavigateRefreshClick);
document.getElementById('NavigateNext').addEventListener('click', btnNavigateNextClick); document.getElementById('NavigateNext').addEventListener('click', btnNavigateNextClick);
@ -127,36 +125,15 @@ const init = () => {
const TwcUnits = localStorage.getItem('TwcUnits'); const TwcUnits = localStorage.getItem('TwcUnits');
if (!TwcUnits || TwcUnits === 'ENGLISH') { if (!TwcUnits || TwcUnits === 'ENGLISH') {
document.getElementById('radEnglish').checked = true; document.getElementById('radEnglish').checked = true;
navigation.message({ type: 'units', message: 'english' }); setUnits('english');
} else if (TwcUnits === 'METRIC') { } else if (TwcUnits === 'METRIC') {
document.getElementById('radMetric').checked = true; document.getElementById('radMetric').checked = true;
navigation.message({ type: 'units', message: 'metric' }); setUnits('metric');
} }
document.getElementById('radEnglish').addEventListener('change', changeUnits); document.getElementById('radEnglish').addEventListener('change', changeUnits);
document.getElementById('radMetric').addEventListener('change', changeUnits); document.getElementById('radMetric').addEventListener('change', changeUnits);
document.getElementById('chkAutoRefresh').addEventListener('change', (e) => {
const Checked = e.target.checked;
if (LastUpdate) {
if (Checked) {
StartAutoRefreshTimer();
} else {
StopAutoRefreshTimer();
}
}
localStorage.setItem('TwcAutoRefresh', Checked);
});
const TwcAutoRefresh = localStorage.getItem('TwcAutoRefresh');
if (!TwcAutoRefresh || TwcAutoRefresh === 'true') {
document.getElementById('chkAutoRefresh').checked = true;
} else {
document.getElementById('chkAutoRefresh').checked = false;
}
// swipe functionality // swipe functionality
document.getElementById('container').addEventListener('swiped-left', () => swipeCallBack('left')); document.getElementById('container').addEventListener('swiped-left', () => swipeCallBack('left'));
document.getElementById('container').addEventListener('swiped-right', () => swipeCallBack('right')); document.getElementById('container').addEventListener('swiped-right', () => swipeCallBack('right'));
@ -165,7 +142,6 @@ const init = () => {
const changeUnits = (e) => { const changeUnits = (e) => {
const Units = e.target.value; const Units = e.target.value;
localStorage.setItem('TwcUnits', Units); localStorage.setItem('TwcUnits', Units);
AssignLastUpdate();
postMessage('units', Units); postMessage('units', Units);
}; };
@ -207,7 +183,7 @@ const btnFullScreenClick = () => {
ExitFullscreen(); ExitFullscreen();
} }
if (navigation.isPlaying()) { if (isPlaying()) {
noSleep(true); noSleep(true);
} else { } else {
noSleep(false); noSleep(false);
@ -233,7 +209,7 @@ const EnterFullScreen = () => {
window.scrollTo(0, 0); window.scrollTo(0, 0);
FullScreenOverride = true; FullScreenOverride = true;
} }
navigation.resize(); resize();
UpdateFullScreenNavigate(); UpdateFullScreenNavigate();
// change hover text // change hover text
@ -257,7 +233,7 @@ const ExitFullscreen = () => {
} else if (document.msExitFullscreen) { } else if (document.msExitFullscreen) {
document.msExitFullscreen(); document.msExitFullscreen();
} }
navigation.resize(); resize();
// change hover text // change hover text
document.getElementById('ToggleFullScreen').title = 'Enter fullscreen'; document.getElementById('ToggleFullScreen').title = 'Enter fullscreen';
}; };
@ -277,11 +253,8 @@ const LoadTwcData = (_latLon) => {
if (!latLon) return; if (!latLon) return;
document.getElementById('txtAddress').blur(); document.getElementById('txtAddress').blur();
StopAutoRefreshTimer(); stopAutoRefreshTimer();
LastUpdate = null; latLonReceived(latLon);
AssignLastUpdate();
postMessage('latLon', latLon);
}; };
const swipeCallBack = (direction) => { const swipeCallBack = (direction) => {
@ -297,29 +270,8 @@ const swipeCallBack = (direction) => {
} }
}; };
const AssignLastUpdate = () => {
if (LastUpdate) {
switch (navigation.units()) {
case UNITS.english:
LastUpdate = LastUpdate.toLocaleString('en-US', {
weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', timeZoneName: 'short',
});
break;
default:
LastUpdate = LastUpdate.toLocaleString('en-GB', {
weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', timeZoneName: 'short',
});
break;
}
}
document.getElementById('spanLastRefresh').innerHTML = LastUpdate;
if (LastUpdate && document.getElementById('chkAutoRefresh').checked) StartAutoRefreshTimer();
};
const btnNavigateRefreshClick = () => { const btnNavigateRefreshClick = () => {
navigation.resetStatuses(); resetStatuses();
LoadTwcData(); LoadTwcData();
UpdateFullScreenNavigate(); UpdateFullScreenNavigate();
@ -410,77 +362,9 @@ const btnNavigatePlayClick = () => {
return false; return false;
}; };
// read and dispatch an event from the iframe
const message = (data) => {
const playButton = document.getElementById('NavigatePlay');
// dispatch event
if (!data.type) return;
switch (data.type) {
case 'loaded':
LastUpdate = new Date();
AssignLastUpdate();
break;
case 'weatherParameters':
populateWeatherParameters(data.message);
break;
case 'isPlaying':
localStorage.setItem('TwcPlay', navigation.isPlaying());
if (navigation.isPlaying()) {
noSleep(true);
playButton.title = 'Pause';
playButton.src = 'images/nav/ic_pause_white_24dp_1x.png';
} else {
noSleep(false);
playButton.title = 'Play';
playButton.src = 'images/nav/ic_play_arrow_white_24dp_1x.png';
}
break;
default:
console.error(`Unknown event '${data.eventType}`);
}
};
// post a message to the iframe // post a message to the iframe
const postMessage = (type, myMessage = {}) => { const postMessage = (type, myMessage = {}) => {
navigation.message({ type, message: myMessage }); navMessage({ type, message: myMessage });
};
const StartAutoRefreshTimer = () => {
// Ensure that any previous timer has already stopped.
// check if timer is running
if (AutoRefreshIntervalId) return;
// Reset the time elapsed.
AutoRefreshCountMs = 0;
const AutoRefreshTimer = () => {
// Increment the total time elapsed.
AutoRefreshCountMs += AutoRefreshIntervalMs;
// Display the count down.
let RemainingMs = (AutoRefreshTotalIntervalMs - AutoRefreshCountMs);
if (RemainingMs < 0) {
RemainingMs = 0;
}
const dt = new Date(RemainingMs);
document.getElementById('spanRefreshCountDown').innerHTML = `${dt.getMinutes() < 10 ? `0${dt.getMinutes()}` : dt.getMinutes()}:${dt.getSeconds() < 10 ? `0${dt.getSeconds()}` : dt.getSeconds()}`;
// Time has elapsed.
if (AutoRefreshCountMs >= AutoRefreshTotalIntervalMs && !navigation.isPlaying()) LoadTwcData();
};
AutoRefreshIntervalId = window.setInterval(AutoRefreshTimer, AutoRefreshIntervalMs);
AutoRefreshTimer();
};
const StopAutoRefreshTimer = () => {
if (AutoRefreshIntervalId) {
window.clearInterval(AutoRefreshIntervalId);
document.getElementById('spanRefreshCountDown').innerHTML = '--:--';
AutoRefreshIntervalId = null;
}
}; };
const btnGetGpsClick = async () => { const btnGetGpsClick = async () => {
@ -518,47 +402,3 @@ const btnGetGpsClick = async () => {
// Save the query // Save the query
localStorage.setItem('TwcQuery', TwcQuery); localStorage.setItem('TwcQuery', TwcQuery);
}; };
const populateWeatherParameters = (weatherParameters) => {
document.getElementById('spanCity').innerHTML = `${weatherParameters.city}, `;
document.getElementById('spanState').innerHTML = weatherParameters.state;
document.getElementById('spanStationId').innerHTML = weatherParameters.stationId;
document.getElementById('spanRadarId').innerHTML = weatherParameters.radarId;
document.getElementById('spanZoneId').innerHTML = weatherParameters.zoneId;
};
// track state of nosleep locally to avoid a null case error
// when nosleep.disable is called without first calling .enable
let wakeLock = false;
const noSleep = (enable = false) => {
// get a nosleep controller
if (!noSleep.controller) noSleep.controller = new NoSleep();
// don't call anything if the states match
if (wakeLock === enable) return false;
// store the value
wakeLock = enable;
// call the function
if (enable) return noSleep.controller.enable();
return noSleep.controller.disable();
};
const refreshCheck = () => {
// Time has elapsed.
if (AutoRefreshCountMs >= AutoRefreshTotalIntervalMs) {
LoadTwcData();
return true;
}
return false;
};
export {
init,
message,
refreshCheck,
};
window.index = {
init,
message,
refreshCheck,
};

View file

@ -3,8 +3,7 @@ import { loadImg, preloadImg } from './utils/image.mjs';
import { DateTime } from '../vendor/auto/luxon.mjs'; import { DateTime } from '../vendor/auto/luxon.mjs';
import STATUS from './status.mjs'; import STATUS from './status.mjs';
import WeatherDisplay from './weatherdisplay.mjs'; import WeatherDisplay from './weatherdisplay.mjs';
import { registerDisplay } from './navigation.mjs';
/* globals SunCalc */
class Almanac extends WeatherDisplay { class Almanac extends WeatherDisplay {
constructor(navId, elemId) { constructor(navId, elemId) {
@ -171,4 +170,8 @@ class Almanac extends WeatherDisplay {
} }
} }
export default Almanac; // register display
const display = new Almanac(7, 'almanac');
registerDisplay(display);
export default display.getSun.bind(display);

View file

@ -1,11 +0,0 @@
const UNITS = {
english: Symbol('english'),
metric: Symbol('metric'),
};
export {
// eslint-disable-next-line import/prefer-default-export
UNITS,
};
window.UNITS = UNITS;

View file

@ -1,15 +1,13 @@
// current weather conditions display // current weather conditions display
import STATUS from './status.mjs'; import STATUS from './status.mjs';
import { UNITS } from './config.mjs';
import { loadImg, preloadImg } from './utils/image.mjs'; import { loadImg, preloadImg } from './utils/image.mjs';
import { json } from './utils/fetch.mjs'; import { json } from './utils/fetch.mjs';
import { directionToNSEW } from './utils/calc.mjs'; import { directionToNSEW } from './utils/calc.mjs';
import * as units from './utils/units.mjs';
import { locationCleanup } from './utils/string.mjs'; import { locationCleanup } from './utils/string.mjs';
import { getWeatherIconFromIconLink } from './icons.mjs'; import { getWeatherIconFromIconLink } from './icons.mjs';
import WeatherDisplay from './weatherdisplay.mjs'; import WeatherDisplay from './weatherdisplay.mjs';
import { registerDisplay } from './navigation.mjs';
/* globals navigation */ import { getUnits, UNITS, convert } from './utils/units.mjs';
class CurrentWeather extends WeatherDisplay { class CurrentWeather extends WeatherDisplay {
constructor(navId, elemId) { constructor(navId, elemId) {
@ -100,20 +98,20 @@ class CurrentWeather extends WeatherDisplay {
if (pressureDiff > 150) data.PressureDirection = 'R'; if (pressureDiff > 150) data.PressureDirection = 'R';
if (pressureDiff < -150) data.PressureDirection = 'F'; if (pressureDiff < -150) data.PressureDirection = 'F';
if (navigation.units() === UNITS.english) { if (getUnits() === UNITS.english) {
data.Temperature = units.celsiusToFahrenheit(data.Temperature); data.Temperature = convert.celsiusToFahrenheit(data.Temperature);
data.TemperatureUnit = 'F'; data.TemperatureUnit = 'F';
data.DewPoint = units.celsiusToFahrenheit(data.DewPoint); data.DewPoint = convert.celsiusToFahrenheit(data.DewPoint);
data.Ceiling = Math.round(units.metersToFeet(data.Ceiling) / 100) * 100; data.Ceiling = Math.round(convert.metersToFeet(data.Ceiling) / 100) * 100;
data.CeilingUnit = 'ft.'; data.CeilingUnit = 'ft.';
data.Visibility = units.kilometersToMiles(observations.visibility.value / 1000); data.Visibility = convert.kilometersToMiles(observations.visibility.value / 1000);
data.VisibilityUnit = ' mi.'; data.VisibilityUnit = ' mi.';
data.WindSpeed = units.kphToMph(data.WindSpeed); data.WindSpeed = convert.kphToMph(data.WindSpeed);
data.WindUnit = 'MPH'; data.WindUnit = 'MPH';
data.Pressure = units.pascalToInHg(data.Pressure).toFixed(2); data.Pressure = convert.pascalToInHg(data.Pressure).toFixed(2);
data.HeatIndex = units.celsiusToFahrenheit(data.HeatIndex); data.HeatIndex = convert.celsiusToFahrenheit(data.HeatIndex);
data.WindChill = units.celsiusToFahrenheit(data.WindChill); data.WindChill = convert.celsiusToFahrenheit(data.WindChill);
data.WindGust = units.kphToMph(data.WindGust); data.WindGust = convert.kphToMph(data.WindGust);
} }
return data; return data;
} }
@ -191,4 +189,7 @@ class CurrentWeather extends WeatherDisplay {
} }
} }
export default CurrentWeather; const display = new CurrentWeather(0, 'current-weather');
registerDisplay(display);
export default display.getCurrentWeather.bind(display);

View file

@ -1,6 +1,7 @@
/* globals navigation */
import { locationCleanup } from './utils/string.mjs'; import { locationCleanup } from './utils/string.mjs';
import { elemForEach } from './utils/elem.mjs'; import { elemForEach } from './utils/elem.mjs';
import getCurrentWeather from './currentweather.mjs';
import { currentDisplay } from './navigation.mjs';
// constants // constants
const degree = String.fromCharCode(176); const degree = String.fromCharCode(176);
@ -24,12 +25,17 @@ const start = () => {
}; };
const stop = (reset) => { const stop = (reset) => {
if (interval) interval = clearInterval(interval);
if (reset) screenIndex = 0; if (reset) screenIndex = 0;
}; };
// increment interval, roll over // increment interval, roll over
const incrementInterval = () => { const incrementInterval = () => {
// test current screen
const display = currentDisplay();
if (!display?.okToDrawCurrentConditions) {
stop(display?.elemId === 'progress');
return;
}
screenIndex = (screenIndex + 1) % (screens.length); screenIndex = (screenIndex + 1) % (screens.length);
// draw new text // draw new text
drawScreen(); drawScreen();
@ -37,7 +43,7 @@ const incrementInterval = () => {
const drawScreen = async () => { const drawScreen = async () => {
// get the conditions // get the conditions
const data = await navigation.getCurrentWeather(); const data = await getCurrentWeather();
// nothing to do if there's no data yet // nothing to do if there's no data yet
if (!data) return; if (!data) return;
@ -93,8 +99,4 @@ const drawCondition = (text) => {
}); });
}; };
// return the api start();
export {
start,
stop,
};

View file

@ -2,15 +2,13 @@
// technically uses the same data as the local forecast, we'll let the browser do the caching of that // technically uses the same data as the local forecast, we'll let the browser do the caching of that
import STATUS from './status.mjs'; import STATUS from './status.mjs';
import { UNITS } from './config.mjs';
import { json } from './utils/fetch.mjs'; import { json } from './utils/fetch.mjs';
import { DateTime } from '../vendor/auto/luxon.mjs'; import { DateTime } from '../vendor/auto/luxon.mjs';
import { fahrenheitToCelsius } from './utils/units.mjs'; import { UNITS, getUnits, convert } from './utils/units.mjs';
import { getWeatherIconFromIconLink } from './icons.mjs'; import { getWeatherIconFromIconLink } from './icons.mjs';
import { preloadImg } from './utils/image.mjs'; import { preloadImg } from './utils/image.mjs';
import WeatherDisplay from './weatherdisplay.mjs'; import WeatherDisplay from './weatherdisplay.mjs';
import { registerDisplay } from './navigation.mjs';
/* globals navigation */
class ExtendedForecast extends WeatherDisplay { class ExtendedForecast extends WeatherDisplay {
constructor(navId, elemId) { constructor(navId, elemId) {
@ -26,7 +24,7 @@ class ExtendedForecast extends WeatherDisplay {
// request us or si units // request us or si units
let units = 'us'; let units = 'us';
if (navigation.units() === UNITS.metric) units = 'si'; if (getUnits() === UNITS.metric) units = 'si';
let forecast; let forecast;
try { try {
forecast = await json(weatherParameters.forecast, { forecast = await json(weatherParameters.forecast, {
@ -144,11 +142,11 @@ class ExtendedForecast extends WeatherDisplay {
let { low } = Day; let { low } = Day;
if (low !== undefined) { if (low !== undefined) {
if (navigation.units() === UNITS.metric) low = fahrenheitToCelsius(low); if (getUnits() === UNITS.metric) low = convert.fahrenheitToCelsius(low);
fill['value-lo'] = Math.round(low); fill['value-lo'] = Math.round(low);
} }
let { high } = Day; let { high } = Day;
if (navigation.units() === UNITS.metric) high = fahrenheitToCelsius(high); if (getUnits() === UNITS.metric) high = convert.fahrenheitToCelsius(high);
fill['value-hi'] = Math.round(high); fill['value-hi'] = Math.round(high);
fill.condition = Day.text; fill.condition = Day.text;
@ -167,4 +165,5 @@ class ExtendedForecast extends WeatherDisplay {
} }
} }
export default ExtendedForecast; // register display
registerDisplay(new ExtendedForecast(6, 'extended-forecast'));

View file

@ -1,14 +1,14 @@
// hourly forecast list // hourly forecast list
/* globals navigation */
import STATUS from './status.mjs'; import STATUS from './status.mjs';
import { DateTime, Interval, Duration } from '../vendor/auto/luxon.mjs'; import { DateTime, Interval, Duration } from '../vendor/auto/luxon.mjs';
import { json } from './utils/fetch.mjs'; import { json } from './utils/fetch.mjs';
import { UNITS } from './config.mjs'; import { convert, UNITS, getUnits } from './utils/units.mjs';
import * as units from './utils/units.mjs';
import { getHourlyIcon } from './icons.mjs'; import { getHourlyIcon } from './icons.mjs';
import { directionToNSEW } from './utils/calc.mjs'; import { directionToNSEW } from './utils/calc.mjs';
import WeatherDisplay from './weatherdisplay.mjs'; import WeatherDisplay from './weatherdisplay.mjs';
import { registerDisplay } from './navigation.mjs';
import getSun from './almanac.mjs';
class Hourly extends WeatherDisplay { class Hourly extends WeatherDisplay {
constructor(navId, elemId, defaultActive) { constructor(navId, elemId, defaultActive) {
@ -62,7 +62,7 @@ class Hourly extends WeatherDisplay {
const icons = await Hourly.determineIcon(skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed); const icons = await Hourly.determineIcon(skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed);
return temperature.map((val, idx) => { return temperature.map((val, idx) => {
if (navigation.units === UNITS.metric) { if (getUnits() === UNITS.metric) {
return { return {
temperature: temperature[idx], temperature: temperature[idx],
apparentTemperature: apparentTemperature[idx], apparentTemperature: apparentTemperature[idx],
@ -73,9 +73,9 @@ class Hourly extends WeatherDisplay {
} }
return { return {
temperature: units.celsiusToFahrenheit(temperature[idx]), temperature: convert.celsiusToFahrenheit(temperature[idx]),
apparentTemperature: units.celsiusToFahrenheit(apparentTemperature[idx]), apparentTemperature: convert.celsiusToFahrenheit(apparentTemperature[idx]),
windSpeed: units.kilometersToMiles(windSpeed[idx]), windSpeed: convert.kilometersToMiles(windSpeed[idx]),
windDirection: directionToNSEW(windDirection[idx]), windDirection: directionToNSEW(windDirection[idx]),
icon: icons[idx], icon: icons[idx],
}; };
@ -85,7 +85,7 @@ class Hourly extends WeatherDisplay {
// given forecast paramaters determine a suitable icon // given forecast paramaters determine a suitable icon
static async determineIcon(skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed) { static async determineIcon(skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed) {
const startOfHour = DateTime.local().startOf('hour'); const startOfHour = DateTime.local().startOf('hour');
const sunTimes = (await navigation.getSun()).sun; const sunTimes = (await getSun()).sun;
const overnight = Interval.fromDateTimes(DateTime.fromJSDate(sunTimes[0].sunset), DateTime.fromJSDate(sunTimes[1].sunrise)); const overnight = Interval.fromDateTimes(DateTime.fromJSDate(sunTimes[0].sunset), DateTime.fromJSDate(sunTimes[1].sunrise));
const tomorrowOvernight = DateTime.fromJSDate(sunTimes[1].sunset); const tomorrowOvernight = DateTime.fromJSDate(sunTimes[1].sunset);
return skyCover.map((val, idx) => { return skyCover.map((val, idx) => {
@ -198,4 +198,5 @@ class Hourly extends WeatherDisplay {
} }
} }
export default Hourly; // register display
registerDisplay(new Hourly(2, 'hourly'));

View file

@ -1,12 +1,11 @@
// current weather conditions display // current weather conditions display
/* globals navigation, StationInfo */
import { distance as calcDistance, directionToNSEW } from './utils/calc.mjs'; import { distance as calcDistance, directionToNSEW } from './utils/calc.mjs';
import { json } from './utils/fetch.mjs'; import { json } from './utils/fetch.mjs';
import STATUS from './status.mjs'; import STATUS from './status.mjs';
import { locationCleanup } from './utils/string.mjs'; import { locationCleanup } from './utils/string.mjs';
import { UNITS } from './config.mjs'; import { convert, UNITS, getUnits } from './utils/units.mjs';
import * as units from './utils/units.mjs';
import WeatherDisplay from './weatherdisplay.mjs'; import WeatherDisplay from './weatherdisplay.mjs';
import { registerDisplay } from './navigation.mjs';
class LatestObservations extends WeatherDisplay { class LatestObservations extends WeatherDisplay {
constructor(navId, elemId) { constructor(navId, elemId) {
@ -71,7 +70,7 @@ class LatestObservations extends WeatherDisplay {
// sort array by station name // sort array by station name
const sortedConditions = conditions.sort((a, b) => ((a.Name < b.Name) ? -1 : 1)); const sortedConditions = conditions.sort((a, b) => ((a.Name < b.Name) ? -1 : 1));
if (navigation.units() === UNITS.english) { if (getUnits() === UNITS.english) {
this.elem.querySelector('.column-headers .temp.english').classList.add('show'); this.elem.querySelector('.column-headers .temp.english').classList.add('show');
this.elem.querySelector('.column-headers .temp.metric').classList.remove('show'); this.elem.querySelector('.column-headers .temp.metric').classList.remove('show');
} else { } else {
@ -84,9 +83,9 @@ class LatestObservations extends WeatherDisplay {
let WindSpeed = condition.windSpeed.value; let WindSpeed = condition.windSpeed.value;
const windDirection = directionToNSEW(condition.windDirection.value); const windDirection = directionToNSEW(condition.windDirection.value);
if (navigation.units() === UNITS.english) { if (getUnits() === UNITS.english) {
Temperature = units.celsiusToFahrenheit(Temperature); Temperature = convert.celsiusToFahrenheit(Temperature);
WindSpeed = units.kphToMph(WindSpeed); WindSpeed = convert.kphToMph(WindSpeed);
} }
WindSpeed = Math.round(WindSpeed); WindSpeed = Math.round(WindSpeed);
Temperature = Math.round(Temperature); Temperature = Math.round(Temperature);
@ -132,3 +131,5 @@ class LatestObservations extends WeatherDisplay {
return condition; return condition;
} }
} }
// register display
registerDisplay(new LatestObservations(1, 'latest-observations'));

View file

@ -1,10 +1,10 @@
// display text based local forecast // display text based local forecast
/* globals navigation */
import STATUS from './status.mjs'; import STATUS from './status.mjs';
import { UNITS } from './config.mjs'; import { UNITS, getUnits } from './utils/units.mjs';
import { json } from './utils/fetch.mjs'; import { json } from './utils/fetch.mjs';
import WeatherDisplay from './weatherdisplay.mjs'; import WeatherDisplay from './weatherdisplay.mjs';
import { registerDisplay } from './navigation.mjs';
class LocalForecast extends WeatherDisplay { class LocalForecast extends WeatherDisplay {
constructor(navId, elemId) { constructor(navId, elemId) {
@ -33,7 +33,7 @@ class LocalForecast extends WeatherDisplay {
// process the text // process the text
let text = `${condition.DayName.toUpperCase()}...`; let text = `${condition.DayName.toUpperCase()}...`;
let conditionText = condition.Text; let conditionText = condition.Text;
if (navigation.units() === UNITS.metric) { if (getUnits() === UNITS.metric) {
conditionText = condition.TextC; conditionText = condition.TextC;
} }
text += conditionText.toUpperCase().replace('...', ' '); text += conditionText.toUpperCase().replace('...', ' ');
@ -63,7 +63,7 @@ class LocalForecast extends WeatherDisplay {
async getRawData(weatherParameters) { async getRawData(weatherParameters) {
// request us or si units // request us or si units
let units = 'us'; let units = 'us';
if (navigation.units() === UNITS.metric) units = 'si'; if (getUnits() === UNITS.metric) units = 'si';
try { try {
return await json(weatherParameters.forecast, { return await json(weatherParameters.forecast, {
data: { data: {
@ -97,3 +97,6 @@ class LocalForecast extends WeatherDisplay {
})); }));
} }
} }
// register display
registerDisplay(new LocalForecast(5, 'local-forecast'));

View file

@ -1,307 +0,0 @@
// navigation handles progress, next/previous and initial load messages from the parent frame
/* globals index, utils, StationInfo, STATUS, UNITS */
/* globals CurrentWeather, LatestObservations, TravelForecast, RegionalForecast, LocalForecast, ExtendedForecast, Almanac, Radar, Progress, Hourly */
document.addEventListener('DOMContentLoaded', () => {
navigation.init();
});
const navigation = (() => {
let displays = [];
let currentUnits;
let playing = false;
let progress;
const weatherParameters = {};
// current conditions and sunrise/sunset are made available from the display below
let currentWeather;
let almanac;
const init = async () => {
// set up resize handler
window.addEventListener('resize', resize);
currentUnits = UNITS.english;
};
const message = (data) => {
// dispatch event
if (!data.type) return;
switch (data.type) {
case 'latLon':
getWeather(data.message);
break;
case 'units':
setUnits(data.message);
break;
case 'navButton':
handleNavButton(data.message);
break;
default:
console.error(`Unknown event ${data.type}`);
}
};
const postMessage = (type, myMessage = {}) => {
index.message({ type, message: myMessage });
};
const getWeather = async (latLon) => {
// get initial weather data
const point = await utils.weather.getPoint(latLon.lat, latLon.lon);
// get stations
const stations = await utils.fetch.json(point.properties.observationStations);
const StationId = stations.features[0].properties.stationIdentifier;
let { city } = point.properties.relativeLocation.properties;
if (StationId in StationInfo) {
city = StationInfo[StationId].city;
[city] = city.split('/');
}
// populate the weather parameters
weatherParameters.latitude = latLon.lat;
weatherParameters.longitude = latLon.lon;
weatherParameters.zoneId = point.properties.forecastZone.substr(-6);
weatherParameters.radarId = point.properties.radarStation.substr(-3);
weatherParameters.stationId = StationId;
weatherParameters.weatherOffice = point.properties.cwa;
weatherParameters.city = city;
weatherParameters.state = point.properties.relativeLocation.properties.state;
weatherParameters.timeZone = point.properties.relativeLocation.properties.timeZone;
weatherParameters.forecast = point.properties.forecast;
weatherParameters.forecastGridData = point.properties.forecastGridData;
weatherParameters.stations = stations.features;
// update the main process for display purposes
postMessage('weatherParameters', weatherParameters);
// draw the progress canvas and hide others
hideAllCanvases();
document.getElementById('loading').style.display = 'none';
if (!progress) progress = new Progress(-1, 'progress');
await progress.drawCanvas();
progress.showCanvas();
// start loading canvases if necessary
if (displays.length === 0) {
currentWeather = new CurrentWeather(0, 'current-weather');
almanac = new Almanac(7, 'almanac');
displays = [
currentWeather,
new LatestObservations(1, 'latest-observations'),
new Hourly(2, 'hourly'),
new TravelForecast(3, 'travel', false), // not active by default
new RegionalForecast(4, 'regional-forecast'),
new LocalForecast(5, 'local-forecast'),
new ExtendedForecast(6, 'extended-forecast'),
almanac,
new Radar(8, 'radar'),
];
}
// call for new data on each display
displays.forEach((display) => display.getData(weatherParameters));
};
// receive a status update from a module {id, value}
const updateStatus = (value) => {
if (value.id < 0) return;
progress.drawCanvas(displays, countLoadedCanvases());
// if this is the first display and we're playing, load it up so it starts playing
if (isPlaying() && value.id === 0 && value.status === STATUS.loaded) {
navTo(msg.command.firstFrame);
}
// send loaded messaged to parent
if (countLoadedCanvases() < displays.length) return;
postMessage('loaded');
};
const countLoadedCanvases = () => displays.reduce((acc, display) => {
if (display.status !== STATUS.loading) return acc + 1;
return acc;
}, 0);
const hideAllCanvases = () => {
displays.forEach((display) => display.hideCanvas());
};
const units = () => currentUnits;
const setUnits = (_unit) => {
const unit = _unit.toLowerCase();
if (unit === 'english') {
currentUnits = UNITS.english;
} else {
currentUnits = UNITS.metric;
}
// TODO: refresh current screen
};
// is playing interface
const isPlaying = () => playing;
// navigation message constants
const msg = {
response: { // display to navigation
previous: Symbol('previous'), // already at first frame, calling function should switch to previous canvas
inProgress: Symbol('inProgress'), // have data to display, calling function should do nothing
next: Symbol('next'), // end of frames reached, calling function should switch to next canvas
},
command: { // navigation to display
firstFrame: Symbol('firstFrame'),
previousFrame: Symbol('previousFrame'),
nextFrame: Symbol('nextFrame'),
lastFrame: Symbol('lastFrame'), // used when navigating backwards from the begining of the next canvas
},
};
// receive navigation messages from displays
const displayNavMessage = (myMessage) => {
if (myMessage.type === msg.response.previous) loadDisplay(-1);
if (myMessage.type === msg.response.next) loadDisplay(1);
};
// navigate to next or previous
const navTo = (direction) => {
// test for a current display
const current = currentDisplay();
progress.hideCanvas();
if (!current) {
// special case for no active displays (typically on progress screen)
// find the first ready display
let firstDisplay;
let displayCount = 0;
do {
if (displays[displayCount].status === STATUS.loaded) firstDisplay = displays[displayCount];
displayCount += 1;
} while (!firstDisplay && displayCount < displays.length);
firstDisplay.navNext(msg.command.firstFrame);
return;
}
if (direction === msg.command.nextFrame) currentDisplay().navNext();
if (direction === msg.command.previousFrame) currentDisplay().navPrev();
};
// find the next or previous available display
const loadDisplay = (direction) => {
const totalDisplays = displays.length;
const curIdx = currentDisplayIndex();
let idx;
for (let i = 0; i < totalDisplays; i += 1) {
// convert form simple 0-10 to start at current display index +/-1 and wrap
idx = utils.calc.wrap(curIdx + (i + 1) * direction, totalDisplays);
if (displays[idx].status === STATUS.loaded) break;
}
// if new display index is less than current display a wrap occurred, test for reload timeout
if (idx <= curIdx) {
if (index.refreshCheck()) return;
}
const newDisplay = displays[idx];
// hide all displays
hideAllCanvases();
// show the new display and navigate to an appropriate display
if (direction < 0) newDisplay.showCanvas(msg.command.lastFrame);
if (direction > 0) newDisplay.showCanvas(msg.command.firstFrame);
};
// get the current display index or value
const currentDisplayIndex = () => {
const index = displays.findIndex((display) => display.isActive());
return index;
};
const currentDisplay = () => displays[currentDisplayIndex()];
const setPlaying = (newValue) => {
playing = newValue;
postMessage('isPlaying', playing);
// if we're playing and on the progress screen jump to the next screen
if (!progress) return;
if (playing && !currentDisplay()) navTo(msg.command.firstFrame);
};
// handle all navigation buttons
const handleNavButton = (button) => {
switch (button) {
case 'play':
setPlaying(true);
break;
case 'playToggle':
setPlaying(!playing);
break;
case 'stop':
setPlaying(false);
break;
case 'next':
setPlaying(false);
navTo(msg.command.nextFrame);
break;
case 'previous':
setPlaying(false);
navTo(msg.command.previousFrame);
break;
case 'menu':
setPlaying(false);
progress.showCanvas();
hideAllCanvases();
break;
default:
console.error(`Unknown navButton ${button}`);
}
};
// return the specificed display
const getDisplay = (index) => displays[index];
// get current conditions
const getCurrentWeather = () => {
if (!currentWeather) return false;
return currentWeather.getCurrentWeather();
};
// get sunrise/sunset
const getSun = () => {
if (!almanac) return false;
return almanac.getSun();
};
// resize the container on a page resize
const resize = () => {
const widthZoomPercent = window.innerWidth / 640;
const heightZoomPercent = window.innerHeight / 480;
const scale = Math.min(widthZoomPercent, heightZoomPercent);
if (scale < 1.0 || document.fullscreenElement) {
document.getElementById('container').style.zoom = scale;
} else {
document.getElementById('container').style.zoom = 1;
}
};
// reset all statuses to loading on all displays, used to keep the progress bar accurate during refresh
const resetStatuses = () => {
displays.forEach((display) => { display.status = STATUS.loading; });
};
return {
init,
message,
updateStatus,
units,
isPlaying,
displayNavMessage,
msg,
getDisplay,
getCurrentWeather,
getSun,
resize,
resetStatuses,
};
})();

View file

@ -0,0 +1,390 @@
// navigation handles progress, next/previous and initial load messages from the parent frame
import noSleep from './utils/nosleep.mjs';
import STATUS from './status.mjs';
import { wrap } from './utils/calc.mjs';
import { json } from './utils/fetch.mjs';
import { getPoint } from './utils/weather.mjs';
document.addEventListener('DOMContentLoaded', () => {
init();
});
const displays = [];
let playing = false;
let progress;
const weatherParameters = {};
// auto refresh
const AUTO_REFRESH_INTERVAL_MS = 500;
const AUTO_REFRESH_TIME_MS = 600000; // 10 min.
let AutoRefreshIntervalId = null;
let AutoRefreshCountMs = 0;
const init = async () => {
// set up resize handler
window.addEventListener('resize', resize);
// auto refresh
const TwcAutoRefresh = localStorage.getItem('TwcAutoRefresh');
if (!TwcAutoRefresh || TwcAutoRefresh === 'true') {
document.getElementById('chkAutoRefresh').checked = true;
} else {
document.getElementById('chkAutoRefresh').checked = false;
}
document.getElementById('chkAutoRefresh').addEventListener('change', autoRefreshChange);
};
const message = (data) => {
// dispatch event
if (!data.type) return;
switch (data.type) {
case 'navButton':
handleNavButton(data.message);
break;
default:
console.error(`Unknown event ${data.type}`);
}
};
const getWeather = async (latLon) => {
// get initial weather data
const point = await getPoint(latLon.lat, latLon.lon);
// get stations
const stations = await json(point.properties.observationStations);
const StationId = stations.features[0].properties.stationIdentifier;
let { city } = point.properties.relativeLocation.properties;
if (StationId in StationInfo) {
city = StationInfo[StationId].city;
[city] = city.split('/');
}
// populate the weather parameters
weatherParameters.latitude = latLon.lat;
weatherParameters.longitude = latLon.lon;
weatherParameters.zoneId = point.properties.forecastZone.substr(-6);
weatherParameters.radarId = point.properties.radarStation.substr(-3);
weatherParameters.stationId = StationId;
weatherParameters.weatherOffice = point.properties.cwa;
weatherParameters.city = city;
weatherParameters.state = point.properties.relativeLocation.properties.state;
weatherParameters.timeZone = point.properties.relativeLocation.properties.timeZone;
weatherParameters.forecast = point.properties.forecast;
weatherParameters.forecastGridData = point.properties.forecastGridData;
weatherParameters.stations = stations.features;
// update the main process for display purposes
populateWeatherParameters(weatherParameters);
// draw the progress canvas and hide others
hideAllCanvases();
document.getElementById('loading').style.display = 'none';
if (progress) {
await progress.drawCanvas();
progress.showCanvas();
}
// call for new data on each display
displays.forEach((display) => display.getData(weatherParameters));
};
// receive a status update from a module {id, value}
const updateStatus = (value) => {
if (value.id < 0) return;
if (!progress) return;
progress.drawCanvas(displays, countLoadedCanvases());
// if this is the first display and we're playing, load it up so it starts playing
if (isPlaying() && value.id === 0 && value.status === STATUS.loaded) {
navTo(msg.command.firstFrame);
}
// send loaded messaged to parent
if (countLoadedCanvases() < displays.length) return;
// everything loaded, set timestamps
AssignLastUpdate(new Date());
};
const countLoadedCanvases = () => displays.reduce((acc, display) => {
if (display.status !== STATUS.loading) return acc + 1;
return acc;
}, 0);
const hideAllCanvases = () => {
displays.forEach((display) => display.hideCanvas());
};
// is playing interface
const isPlaying = () => playing;
// navigation message constants
const msg = {
response: { // display to navigation
previous: Symbol('previous'), // already at first frame, calling function should switch to previous canvas
inProgress: Symbol('inProgress'), // have data to display, calling function should do nothing
next: Symbol('next'), // end of frames reached, calling function should switch to next canvas
},
command: { // navigation to display
firstFrame: Symbol('firstFrame'),
previousFrame: Symbol('previousFrame'),
nextFrame: Symbol('nextFrame'),
lastFrame: Symbol('lastFrame'), // used when navigating backwards from the begining of the next canvas
},
};
// receive navigation messages from displays
const displayNavMessage = (myMessage) => {
if (myMessage.type === msg.response.previous) loadDisplay(-1);
if (myMessage.type === msg.response.next) loadDisplay(1);
};
// navigate to next or previous
const navTo = (direction) => {
// test for a current display
const current = currentDisplay();
progress.hideCanvas();
if (!current) {
// special case for no active displays (typically on progress screen)
// find the first ready display
let firstDisplay;
let displayCount = 0;
do {
if (displays[displayCount].status === STATUS.loaded) firstDisplay = displays[displayCount];
displayCount += 1;
} while (!firstDisplay && displayCount < displays.length);
if (!firstDisplay) return;
firstDisplay.navNext(msg.command.firstFrame);
return;
}
if (direction === msg.command.nextFrame) currentDisplay().navNext();
if (direction === msg.command.previousFrame) currentDisplay().navPrev();
};
// find the next or previous available display
const loadDisplay = (direction) => {
const totalDisplays = displays.length;
const curIdx = currentDisplayIndex();
let idx;
for (let i = 0; i < totalDisplays; i += 1) {
// convert form simple 0-10 to start at current display index +/-1 and wrap
idx = wrap(curIdx + (i + 1) * direction, totalDisplays);
if (displays[idx].status === STATUS.loaded) break;
}
// if new display index is less than current display a wrap occurred, test for reload timeout
if (idx <= curIdx) {
if (refreshCheck()) return;
}
const newDisplay = displays[idx];
// hide all displays
hideAllCanvases();
// show the new display and navigate to an appropriate display
if (direction < 0) newDisplay.showCanvas(msg.command.lastFrame);
if (direction > 0) newDisplay.showCanvas(msg.command.firstFrame);
};
// get the current display index or value
const currentDisplayIndex = () => {
const index = displays.findIndex((display) => display.isActive());
return index;
};
const currentDisplay = () => displays[currentDisplayIndex()];
const setPlaying = (newValue) => {
playing = newValue;
const playButton = document.getElementById('NavigatePlay');
localStorage.setItem('TwcPlay', playing);
if (playing) {
noSleep(true);
playButton.title = 'Pause';
playButton.src = 'images/nav/ic_pause_white_24dp_1x.png';
} else {
noSleep(false);
playButton.title = 'Play';
playButton.src = 'images/nav/ic_play_arrow_white_24dp_1x.png';
}
// if we're playing and on the progress screen jump to the next screen
if (!progress) return;
if (playing && !currentDisplay()) navTo(msg.command.firstFrame);
};
// handle all navigation buttons
const handleNavButton = (button) => {
switch (button) {
case 'play':
setPlaying(true);
break;
case 'playToggle':
setPlaying(!playing);
break;
case 'stop':
setPlaying(false);
break;
case 'next':
setPlaying(false);
navTo(msg.command.nextFrame);
break;
case 'previous':
setPlaying(false);
navTo(msg.command.previousFrame);
break;
case 'menu':
setPlaying(false);
progress.showCanvas();
hideAllCanvases();
break;
default:
console.error(`Unknown navButton ${button}`);
}
};
// return the specificed display
const getDisplay = (index) => displays[index];
// resize the container on a page resize
const resize = () => {
const widthZoomPercent = window.innerWidth / 640;
const heightZoomPercent = window.innerHeight / 480;
const scale = Math.min(widthZoomPercent, heightZoomPercent);
if (scale < 1.0 || document.fullscreenElement) {
document.getElementById('container').style.zoom = scale;
} else {
document.getElementById('container').style.zoom = 1;
}
};
// reset all statuses to loading on all displays, used to keep the progress bar accurate during refresh
const resetStatuses = () => {
displays.forEach((display) => { display.status = STATUS.loading; });
};
// allow displays to register themselves
const registerDisplay = (display) => {
displays[display.navId] = display;
// generate checkboxes
const checkboxes = displays.map((d) => d.generateCheckbox()).filter((d) => d);
// write to page
const availableDisplays = document.getElementById('enabledDisplays');
availableDisplays.innerHTML = '';
availableDisplays.append(...checkboxes);
};
// special registration method for progress display
const registerProgress = (_progress) => {
progress = _progress;
};
const populateWeatherParameters = (params) => {
document.getElementById('spanCity').innerHTML = `${params.city}, `;
document.getElementById('spanState').innerHTML = params.state;
document.getElementById('spanStationId').innerHTML = params.stationId;
document.getElementById('spanRadarId').innerHTML = params.radarId;
document.getElementById('spanZoneId').innerHTML = params.zoneId;
};
const autoRefreshChange = (e) => {
const { checked } = e.target;
if (checked) {
startAutoRefreshTimer();
} else {
stopAutoRefreshTimer();
}
localStorage.setItem('TwcAutoRefresh', checked);
};
const AssignLastUpdate = (date) => {
if (date) {
document.getElementById('spanLastRefresh').innerHTML = date.toLocaleString('en-US', {
weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', timeZoneName: 'short',
});
if (document.getElementById('chkAutoRefresh').checked) startAutoRefreshTimer();
} else {
document.getElementById('spanLastRefresh').innerHTML = '(none)';
}
};
const latLonReceived = (data) => {
getWeather(data);
AssignLastUpdate(null);
};
const startAutoRefreshTimer = () => {
// Ensure that any previous timer has already stopped.
// check if timer is running
if (AutoRefreshIntervalId) return;
// Reset the time elapsed.
AutoRefreshCountMs = 0;
const AutoRefreshTimer = () => {
// Increment the total time elapsed.
AutoRefreshCountMs += AUTO_REFRESH_INTERVAL_MS;
// Display the count down.
let RemainingMs = (AUTO_REFRESH_TIME_MS - AutoRefreshCountMs);
if (RemainingMs < 0) {
RemainingMs = 0;
}
const dt = new Date(RemainingMs);
document.getElementById('spanRefreshCountDown').innerHTML = `${dt.getMinutes() < 10 ? `0${dt.getMinutes()}` : dt.getMinutes()}:${dt.getSeconds() < 10 ? `0${dt.getSeconds()}` : dt.getSeconds()}`;
// Time has elapsed.
if (AutoRefreshCountMs >= AUTO_REFRESH_TIME_MS && !isPlaying()) loadTwcData();
};
AutoRefreshIntervalId = window.setInterval(AutoRefreshTimer, AUTO_REFRESH_INTERVAL_MS);
AutoRefreshTimer();
};
const stopAutoRefreshTimer = () => {
if (AutoRefreshIntervalId) {
window.clearInterval(AutoRefreshIntervalId);
document.getElementById('spanRefreshCountDown').innerHTML = '--:--';
AutoRefreshIntervalId = null;
}
};
const refreshCheck = () => {
// Time has elapsed.
if (AutoRefreshCountMs >= AUTO_REFRESH_TIME_MS) {
loadTwcData();
return true;
}
return false;
};
const loadTwcData = () => {
if (loadTwcData.callback) loadTwcData.callback();
};
const registerRefreshData = (callback) => {
loadTwcData.callback = callback;
};
export {
updateStatus,
displayNavMessage,
resetStatuses,
isPlaying,
resize,
registerDisplay,
registerProgress,
currentDisplay,
getDisplay,
msg,
message,
latLonReceived,
stopAutoRefreshTimer,
registerRefreshData,
};

View file

@ -1,8 +1,10 @@
// regional forecast and observations // regional forecast and observations
/* globals navigation */
import { loadImg } from './utils/image.mjs'; import { loadImg } from './utils/image.mjs';
import STATUS from './status.mjs'; import STATUS from './status.mjs';
import WeatherDisplay from './weatherdisplay.mjs'; import WeatherDisplay from './weatherdisplay.mjs';
import {
registerProgress, message, getDisplay, msg,
} from './navigation.mjs';
class Progress extends WeatherDisplay { class Progress extends WeatherDisplay {
constructor(navId, elemId) { constructor(navId, elemId) {
@ -18,6 +20,8 @@ class Progress extends WeatherDisplay {
// setup event listener // setup event listener
this.elem.querySelector('.container').addEventListener('click', this.lineClick.bind(this)); this.elem.querySelector('.container').addEventListener('click', this.lineClick.bind(this));
this.okToDrawCurrentConditions = false;
} }
async drawCanvas(displays, loadedCount) { async drawCanvas(displays, loadedCount) {
@ -93,16 +97,16 @@ class Progress extends WeatherDisplay {
const index = +indexRaw; const index = +indexRaw;
// stop playing // stop playing
navigation.message('navButton'); message('navButton');
// use the y value to determine an index // use the y value to determine an index
const display = navigation.getDisplay(index); const display = getDisplay(index);
if (display && display.status === STATUS.loaded) { if (display && display.status === STATUS.loaded) {
display.showCanvas(navigation.msg.command.firstFrame); display.showCanvas(msg.command.firstFrame);
this.elem.classList.remove('show'); this.elem.classList.remove('show');
} }
} }
} }
export default Progress; // register our own display
const progress = new Progress(-1, 'progress');
window.Progress = Progress; registerProgress(progress);

View file

@ -5,11 +5,15 @@ import { loadImg } from './utils/image.mjs';
import { text } from './utils/fetch.mjs'; import { text } from './utils/fetch.mjs';
import { rewriteUrl } from './utils/cors.mjs'; import { rewriteUrl } from './utils/cors.mjs';
import WeatherDisplay from './weatherdisplay.mjs'; import WeatherDisplay from './weatherdisplay.mjs';
import { registerDisplay } from './navigation.mjs';
class Radar extends WeatherDisplay { class Radar extends WeatherDisplay {
constructor(navId, elemId) { constructor(navId, elemId) {
super(navId, elemId, 'Local Radar', true); super(navId, elemId, 'Local Radar', true);
this.okToDrawCurrentConditions = false;
this.okToDrawCurrentDateTime = false;
// set max images // set max images
this.dopplerRadarImageMax = 6; this.dopplerRadarImageMax = 6;
// update timing // update timing
@ -397,4 +401,5 @@ class Radar extends WeatherDisplay {
} }
} }
export default Radar; // register display
registerDisplay(new Radar(8, 'radar'));

View file

@ -1,16 +1,15 @@
// regional forecast and observations // regional forecast and observations
// type 0 = observations, 1 = first forecast, 2 = second forecast // type 0 = observations, 1 = first forecast, 2 = second forecast
/* globals navigation, StationInfo, RegionalCities */
import STATUS from './status.mjs'; import STATUS from './status.mjs';
import { UNITS } from './config.mjs';
import { distance as calcDistance } from './utils/calc.mjs'; import { distance as calcDistance } from './utils/calc.mjs';
import { json } from './utils/fetch.mjs'; import { json } from './utils/fetch.mjs';
import * as units from './utils/units.mjs'; import { convert, UNITS, getUnits } from './utils/units.mjs';
import { getWeatherRegionalIconFromIconLink } from './icons.mjs'; import { getWeatherRegionalIconFromIconLink } from './icons.mjs';
import { preloadImg } from './utils/image.mjs'; import { preloadImg } from './utils/image.mjs';
import { DateTime } from '../vendor/auto/luxon.mjs'; import { DateTime } from '../vendor/auto/luxon.mjs';
import WeatherDisplay from './weatherdisplay.mjs'; import WeatherDisplay from './weatherdisplay.mjs';
import { registerDisplay } from './navigation.mjs';
class RegionalForecast extends WeatherDisplay { class RegionalForecast extends WeatherDisplay {
constructor(navId, elemId) { constructor(navId, elemId) {
@ -88,7 +87,7 @@ class RegionalForecast extends WeatherDisplay {
// format the observation the same as the forecast // format the observation the same as the forecast
const regionalObservation = { const regionalObservation = {
daytime: !!observation.icon.match(/\/day\//), daytime: !!observation.icon.match(/\/day\//),
temperature: units.celsiusToFahrenheit(observation.temperature.value), temperature: convert.celsiusToFahrenheit(observation.temperature.value),
name: RegionalForecast.formatCity(city.city), name: RegionalForecast.formatCity(city.city),
icon: observation.icon, icon: observation.icon,
x: cityXY.x, x: cityXY.x,
@ -372,7 +371,7 @@ class RegionalForecast extends WeatherDisplay {
fill.icon = { type: 'img', src: getWeatherRegionalIconFromIconLink(period.icon, !period.daytime) }; fill.icon = { type: 'img', src: getWeatherRegionalIconFromIconLink(period.icon, !period.daytime) };
fill.city = period.name; fill.city = period.name;
let { temperature } = period; let { temperature } = period;
if (navigation.units() === UNITS.metric) temperature = Math.round(units.fahrenheitToCelsius(temperature)); if (getUnits() === UNITS.metric) temperature = Math.round(convert.fahrenheitToCelsius(temperature));
fill.temp = temperature; fill.temp = temperature;
const elem = this.fillTemplate('location', fill); const elem = this.fillTemplate('location', fill);
@ -390,4 +389,5 @@ class RegionalForecast extends WeatherDisplay {
} }
} }
export default RegionalForecast; // register display
registerDisplay(new RegionalForecast(4, 'regional-forecast'));

View file

@ -7,4 +7,3 @@ const STATUS = {
}; };
export default STATUS; export default STATUS;
window.STATUS = STATUS;

View file

@ -1,12 +1,11 @@
// travel forecast display // travel forecast display
/* globals navigation, TravelCities */
import STATUS from './status.mjs'; import STATUS from './status.mjs';
import { UNITS } from './config.mjs';
import { json } from './utils/fetch.mjs'; import { json } from './utils/fetch.mjs';
import { getWeatherRegionalIconFromIconLink } from './icons.mjs'; import { getWeatherRegionalIconFromIconLink } from './icons.mjs';
import { fahrenheitToCelsius } from './utils/units.mjs'; import { convert, UNITS, getUnits } from './utils/units.mjs';
import { DateTime } from '../vendor/auto/luxon.mjs'; import { DateTime } from '../vendor/auto/luxon.mjs';
import WeatherDisplay from './weatherdisplay.mjs'; import WeatherDisplay from './weatherdisplay.mjs';
import { registerDisplay } from './navigation.mjs';
class TravelForecast extends WeatherDisplay { class TravelForecast extends WeatherDisplay {
constructor(navId, elemId, defaultActive) { constructor(navId, elemId, defaultActive) {
@ -90,9 +89,9 @@ class TravelForecast extends WeatherDisplay {
// get temperatures and convert if necessary // get temperatures and convert if necessary
let { low, high } = city; let { low, high } = city;
if (navigation.units() === UNITS.metric) { if (getUnits() === UNITS.metric) {
low = fahrenheitToCelsius(low); low = convert.fahrenheitToCelsius(low);
high = fahrenheitToCelsius(high); high = convert.fahrenheitToCelsius(high);
} }
// convert to strings with no decimal // convert to strings with no decimal
@ -166,4 +165,5 @@ class TravelForecast extends WeatherDisplay {
} }
} }
export default TravelForecast; // register display, not active by default
registerDisplay(new TravelForecast(3, 'travel', false));

View file

@ -1,236 +0,0 @@
// radar utilities
// eslint-disable-next-line no-unused-vars
const utils = (() => {
// ****************************** weather data ********************************
const getPoint = async (lat, lon) => {
try {
return await json(`https://api.weather.gov/points/${lat},${lon}`);
} catch (e) {
console.log(`Unable to get point ${lat}, ${lon}`);
console.error(e);
return false;
}
};
// ****************************** load images *********************************
// load an image from a blob or url
const loadImg = (imgData, cors = false) => new Promise((resolve) => {
const img = new Image();
img.onload = (e) => {
resolve(e.target);
};
if (imgData instanceof Blob) {
img.src = window.URL.createObjectURL(imgData);
} else {
let url = imgData;
if (cors) url = rewriteUrl(imgData);
img.src = url;
}
});
// preload an image
// the goal is to get it in the browser's cache so it is available more quickly when the browser needs it
// a list of cached icons is used to avoid hitting the cache multiple times
const cachedImages = [];
const preload = (src) => {
if (cachedImages.includes(src)) return false;
blob(src);
// cachedImages.push(src);
return true;
};
// *********************************** unit conversions ***********************
Math.round2 = (value, decimals) => Number(`${Math.round(`${value}e${decimals}`)}e-${decimals}`);
const mphToKph = (Mph) => Math.round(Mph * 1.60934);
const kphToMph = (Kph) => Math.round(Kph / 1.60934);
const celsiusToFahrenheit = (Celsius) => Math.round((Celsius * 9) / 5 + 32);
const fahrenheitToCelsius = (Fahrenheit) => Math.round2((((Fahrenheit) - 32) * 5) / 9, 1);
const milesToKilometers = (Miles) => Math.round(Miles * 1.60934);
const kilometersToMiles = (Kilometers) => Math.round(Kilometers / 1.60934);
const feetToMeters = (Feet) => Math.round(Feet * 0.3048);
const metersToFeet = (Meters) => Math.round(Meters / 0.3048);
const inchesToCentimeters = (Inches) => Math.round2(Inches * 2.54, 2);
const pascalToInHg = (Pascal) => Math.round2(Pascal * 0.0002953, 2);
// ***************************** calculations **********************************
const relativeHumidity = (Temperature, DewPoint) => {
const T = Temperature;
const TD = DewPoint;
return Math.round(100 * (Math.exp((17.625 * TD) / (243.04 + TD)) / Math.exp((17.625 * T) / (243.04 + T))));
};
const heatIndex = (Temperature, RelativeHumidity) => {
const T = Temperature;
const RH = RelativeHumidity;
let HI = 0.5 * (T + 61.0 + ((T - 68.0) * 1.2) + (RH * 0.094));
let ADJUSTMENT;
if (T >= 80) {
HI = -42.379 + 2.04901523 * T + 10.14333127 * RH - 0.22475541 * T * RH - 0.00683783 * T * T - 0.05481717 * RH * RH + 0.00122874 * T * T * RH + 0.00085282 * T * RH * RH - 0.00000199 * T * T * RH * RH;
if (RH < 13 && (T > 80 && T < 112)) {
ADJUSTMENT = ((13 - RH) / 4) * Math.sqrt((17 - Math.abs(T - 95)) / 17);
HI -= ADJUSTMENT;
} else if (RH > 85 && (T > 80 && T < 87)) {
ADJUSTMENT = ((RH - 85) / 10) * ((87 - T) / 5);
HI += ADJUSTMENT;
}
}
if (HI < Temperature) {
HI = Temperature;
}
return Math.round(HI);
};
const windChill = (Temperature, WindSpeed) => {
if (WindSpeed === '0' || WindSpeed === 'Calm' || WindSpeed === 'NA') {
return '';
}
const T = Temperature;
const V = WindSpeed;
return Math.round(35.74 + (0.6215 * T) - (35.75 * (V ** 0.16)) + (0.4275 * T * (V ** 0.16)));
};
// wind direction
const directionToNSEW = (Direction) => {
const val = Math.floor((Direction / 22.5) + 0.5);
const arr = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'];
return arr[(val % 16)];
};
const distance = (x1, y1, x2, y2) => Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
// wrap a number to 0-m
const wrap = (x, m) => ((x % m) + m) % m;
// ********************************* strings *********************************************
const locationCleanup = (input) => {
// regexes to run
const regexes = [
// "Chicago / West Chicago", removes before slash
/^[A-Za-z ]+ \/ /,
// "Chicago/Waukegan" removes before slash
/^[A-Za-z ]+\//,
// "Chicago, Chicago O'hare" removes before comma
/^[A-Za-z ]+, /,
];
// run all regexes
return regexes.reduce((value, regex) => value.replace(regex, ''), input);
};
// ********************************* cors ********************************************
// rewrite some urls for local server
const rewriteUrl = (_url) => {
let url = _url;
url = url.replace('https://api.weather.gov/', window.location.href);
url = url.replace('https://www.cpc.ncep.noaa.gov/', window.location.href);
return url;
};
// ********************************* fetch ********************************************
const json = (url, params) => fetchAsync(url, 'json', params);
const text = (url, params) => fetchAsync(url, 'text', params);
const raw = (url, params) => fetchAsync(url, '', params);
const blob = (url, params) => fetchAsync(url, 'blob', params);
const fetchAsync = async (_url, responseType, _params = {}) => {
// combine default and provided parameters
const params = {
method: 'GET',
mode: 'cors',
type: 'GET',
..._params,
};
// build a url, including the rewrite for cors if necessary
let corsUrl = _url;
if (params.cors === true) corsUrl = rewriteUrl(_url);
const url = new URL(corsUrl, `${window.location.origin}/`);
// match the security protocol when not on localhost
url.protocol = window.location.hostname !== 'localhost' ? window.location.protocol : url.protocol;
// add parameters if necessary
if (params.data) {
Object.keys(params.data).forEach((key) => {
// get the value
const value = params.data[key];
// add to the url
url.searchParams.append(key, value);
});
}
// make the request
const response = await fetch(url, params);
// check for ok response
if (!response.ok) throw new Error(`Fetch error ${response.status} ${response.statusText} while fetching ${response.url}`);
// return the requested response
switch (responseType) {
case 'json':
return response.json();
case 'text':
return response.text();
case 'blob':
return response.blob();
default:
return response;
}
};
const elemForEach = (selector, callback) => {
[...document.querySelectorAll(selector)].forEach(callback);
};
// return an orderly object
return {
elem: {
forEach: elemForEach,
},
image: {
load: loadImg,
preload,
},
weather: {
getPoint,
},
units: {
mphToKph,
kphToMph,
celsiusToFahrenheit,
fahrenheitToCelsius,
milesToKilometers,
kilometersToMiles,
feetToMeters,
metersToFeet,
inchesToCentimeters,
pascalToInHg,
},
calc: {
relativeHumidity,
heatIndex,
windChill,
directionToNSEW,
distance,
wrap,
},
string: {
locationCleanup,
},
cors: {
rewriteUrl,
},
fetch: {
json,
text,
raw,
blob,
},
};
})();

View file

@ -0,0 +1,18 @@
// track state of nosleep locally to avoid a null case error
// when nosleep.disable is called without first calling .enable
let wakeLock = false;
const noSleep = (enable = false) => {
// get a nosleep controller
if (!noSleep.controller) noSleep.controller = new NoSleep();
// don't call anything if the states match
if (wakeLock === enable) return false;
// store the value
wakeLock = enable;
// call the function
if (enable) return noSleep.controller.enable();
return noSleep.controller.disable();
};
export default noSleep;

View file

@ -1,3 +1,23 @@
const UNITS = {
english: Symbol('english'),
metric: Symbol('metric'),
};
let currentUnits = UNITS.english;
const getUnits = () => currentUnits;
const setUnits = (_unit) => {
const unit = _unit.toLowerCase();
if (unit === 'english') {
currentUnits = UNITS.english;
} else {
currentUnits = UNITS.metric;
}
// TODO: refresh current screen
};
// *********************************** unit conversions ***********************
const round2 = (value, decimals) => Number(`${Math.round(`${value}e${decimals}`)}e-${decimals}`); const round2 = (value, decimals) => Number(`${Math.round(`${value}e${decimals}`)}e-${decimals}`);
const mphToKph = (Mph) => Math.round(Mph * 1.60934); const mphToKph = (Mph) => Math.round(Mph * 1.60934);
@ -11,7 +31,7 @@ const metersToFeet = (Meters) => Math.round(Meters / 0.3048);
const inchesToCentimeters = (Inches) => round2(Inches * 2.54, 2); const inchesToCentimeters = (Inches) => round2(Inches * 2.54, 2);
const pascalToInHg = (Pascal) => round2(Pascal * 0.0002953, 2); const pascalToInHg = (Pascal) => round2(Pascal * 0.0002953, 2);
export { const convert = {
mphToKph, mphToKph,
kphToMph, kphToMph,
celsiusToFahrenheit, celsiusToFahrenheit,
@ -23,3 +43,12 @@ export {
inchesToCentimeters, inchesToCentimeters,
pascalToInHg, pascalToInHg,
}; };
export {
getUnits,
setUnits,
UNITS,
convert,
};
export default getUnits;

View file

@ -0,0 +1,16 @@
import { json } from './fetch.mjs';
const getPoint = async (lat, lon) => {
try {
return await json(`https://api.weather.gov/points/${lat},${lon}`);
} catch (e) {
console.log(`Unable to get point ${lat}, ${lon}`);
console.error(e);
return false;
}
};
export {
// eslint-disable-next-line import/prefer-default-export
getPoint,
};

View file

@ -1,14 +1,15 @@
// base weather display class // base weather display class
/* globals navigation */
import STATUS from './status.mjs'; import STATUS from './status.mjs';
import * as currentWeatherScroll from './currentweatherscroll.mjs';
import { DateTime } from '../vendor/auto/luxon.mjs'; import { DateTime } from '../vendor/auto/luxon.mjs';
import { elemForEach } from './utils/elem.mjs'; import { elemForEach } from './utils/elem.mjs';
import {
msg, displayNavMessage, isPlaying, updateStatus,
} from './navigation.mjs';
class WeatherDisplay { class WeatherDisplay {
constructor(navId, elemId, name, defaultEnabled) { constructor(navId, elemId, name, defaultEnabled) {
// navId is used in messaging // navId is used in messaging and sort order
this.navId = navId; this.navId = navId;
this.elemId = undefined; this.elemId = undefined;
this.gifs = []; this.gifs = [];
@ -16,6 +17,9 @@ class WeatherDisplay {
this.loadingStatus = STATUS.loading; this.loadingStatus = STATUS.loading;
this.name = name ?? elemId; this.name = name ?? elemId;
this.getDataCallbacks = []; this.getDataCallbacks = [];
this.defaultEnabled = defaultEnabled;
this.okToDrawCurrentConditions = true;
this.okToDrawCurrentDateTime = true;
// default navigation timing // default navigation timing
this.timing = { this.timing = {
@ -29,7 +33,6 @@ class WeatherDisplay {
// store elemId once // store elemId once
this.storeElemId(elemId); this.storeElemId(elemId);
if (elemId !== 'progress') this.addCheckbox(defaultEnabled);
if (this.enabled) { if (this.enabled) {
this.setStatus(STATUS.loading); this.setStatus(STATUS.loading);
} else { } else {
@ -41,7 +44,10 @@ class WeatherDisplay {
this.loadTemplates(); this.loadTemplates();
} }
addCheckbox(defaultEnabled = true) { generateCheckbox(defaultEnabled = true) {
// no checkbox if progress
if (this.elemId === 'progress') return false;
// get the saved status of the checkbox // get the saved status of the checkbox
let savedStatus = window.localStorage.getItem(`${this.elemId}Enabled`); let savedStatus = window.localStorage.getItem(`${this.elemId}Enabled`);
if (savedStatus === null) savedStatus = defaultEnabled; if (savedStatus === null) savedStatus = defaultEnabled;
@ -60,8 +66,8 @@ class WeatherDisplay {
<input type="checkbox" value="true" id="${this.elemId}Enabled" name="${this.elemId}Enabled"${this.enabled ? ' checked' : ''}/> <input type="checkbox" value="true" id="${this.elemId}Enabled" name="${this.elemId}Enabled"${this.enabled ? ' checked' : ''}/>
${this.name}</label>`; ${this.name}</label>`;
checkbox.content.firstChild.addEventListener('change', (e) => this.checkboxChange(e)); checkbox.content.firstChild.addEventListener('change', (e) => this.checkboxChange(e));
const availableDisplays = document.getElementById('enabledDisplays');
availableDisplays.appendChild(checkbox.content.firstChild); return checkbox.content.firstChild;
} }
checkboxChange(e) { checkboxChange(e) {
@ -76,7 +82,7 @@ class WeatherDisplay {
// set data status and send update to navigation module // set data status and send update to navigation module
setStatus(value) { setStatus(value) {
this.status = value; this.status = value;
navigation.updateStatus({ updateStatus({
id: this.navId, id: this.navId,
status: this.status, status: this.status,
}); });
@ -131,39 +137,14 @@ class WeatherDisplay {
} }
finishDraw() { finishDraw() {
let OkToDrawCurrentConditions = true; // draw date and time
let OkToDrawCurrentDateTime = true; if (this.okToDrawCurrentDateTime) {
// let OkToDrawCustomScrollText = false; this.drawCurrentDateTime();
let bottom;
// visibility tests
// if (_ScrollText !== '') OkToDrawCustomScrollText = true;
if (this.elemId === 'progress') {
OkToDrawCurrentConditions = false;
}
if (this.elemId === 'radar') {
OkToDrawCurrentConditions = false;
OkToDrawCurrentDateTime = false;
}
if (this.elemId === 'hazards') {
bottom = true;
}
// draw functions
if (OkToDrawCurrentDateTime) {
this.drawCurrentDateTime(bottom);
// auto clock refresh // auto clock refresh
if (!this.dateTimeInterval) { if (!this.dateTimeInterval) {
setInterval(() => this.drawCurrentDateTime(bottom), 100); setInterval(() => this.drawCurrentDateTime(), 100);
} }
} }
if (OkToDrawCurrentConditions) {
currentWeatherScroll.start();
} else {
// cause a reset if the progress screen is displayed
currentWeatherScroll.stop(this.elemId === 'progress');
}
// TODO: add custom scroll text
// if (OkToDrawCustomScrollText) DrawCustomScrollText(WeatherParameters, context);
} }
drawCurrentDateTime() { drawCurrentDateTime() {
@ -192,8 +173,8 @@ class WeatherDisplay {
showCanvas(navCmd) { showCanvas(navCmd) {
// reset timing if enabled // reset timing if enabled
// if a nav command is present call it to set the screen index // if a nav command is present call it to set the screen index
if (navCmd === navigation.msg.command.firstFrame) this.navNext(navCmd); if (navCmd === msg.command.firstFrame) this.navNext(navCmd);
if (navCmd === navigation.msg.command.lastFrame) this.navPrev(navCmd); if (navCmd === msg.command.lastFrame) this.navPrev(navCmd);
this.startNavCount(); this.startNavCount();
@ -223,7 +204,7 @@ class WeatherDisplay {
// if the array forms are used totalScreens is overwritten by the size of the array // if the array forms are used totalScreens is overwritten by the size of the array
navBaseTime() { navBaseTime() {
// see if play is active and screen is active // see if play is active and screen is active
if (!navigation.isPlaying() || !this.isActive()) return; if (!isPlaying() || !this.isActive()) return;
// increment the base count // increment the base count
this.navBaseCount += 1; this.navBaseCount += 1;
@ -241,7 +222,7 @@ class WeatherDisplay {
// special cases for first and last frame // special cases for first and last frame
// must compare with false as nextScreenIndex could be 0 which is valid // must compare with false as nextScreenIndex could be 0 which is valid
if (nextScreenIndex === false) { if (nextScreenIndex === false) {
this.sendNavDisplayMessage(navigation.msg.response.next); this.sendNavDisplayMessage(msg.response.next);
return; return;
} }
@ -306,7 +287,7 @@ class WeatherDisplay {
// navigate to next screen // navigate to next screen
navNext(command) { navNext(command) {
// check for special 'first frame' command // check for special 'first frame' command
if (command === navigation.msg.command.firstFrame) { if (command === msg.command.firstFrame) {
this.resetNavBaseCount(); this.resetNavBaseCount();
} else { } else {
// set the base count to the next available frame // set the base count to the next available frame
@ -319,7 +300,7 @@ class WeatherDisplay {
// navigate to previous screen // navigate to previous screen
navPrev(command) { navPrev(command) {
// check for special 'last frame' command // check for special 'last frame' command
if (command === navigation.msg.command.lastFrame) { if (command === msg.command.lastFrame) {
this.navBaseCount = this.timing.fullDelay[this.timing.totalScreens - 1] - 1; this.navBaseCount = this.timing.fullDelay[this.timing.totalScreens - 1] - 1;
} else { } else {
// find the highest fullDelay that is less than the current base count // find the highest fullDelay that is less than the current base count
@ -329,7 +310,7 @@ class WeatherDisplay {
}, 0); }, 0);
// if the new base count is zero then we're already at the first screen // if the new base count is zero then we're already at the first screen
if (newBaseCount === 0 && this.navBaseCount === 0) { if (newBaseCount === 0 && this.navBaseCount === 0) {
this.sendNavDisplayMessage(navigation.msg.response.previous); this.sendNavDisplayMessage(msg.response.previous);
return; return;
} }
this.navBaseCount = newBaseCount; this.navBaseCount = newBaseCount;
@ -364,7 +345,7 @@ class WeatherDisplay {
} }
sendNavDisplayMessage(message) { sendNavDisplayMessage(message) {
navigation.displayNavMessage({ displayNavMessage({
id: this.navId, id: this.navId,
type: message, type: message,
}); });

View file

@ -7114,7 +7114,5 @@ function friendlyDateTime(dateTimeish) {
const VERSION = "3.1.0"; const VERSION = "3.1.0";
window.luxon = { DateTime, Duration, FixedOffsetZone, IANAZone, Info, Interval, InvalidZone, Settings, SystemZone, VERSION, Zone };
export { DateTime, Duration, FixedOffsetZone, IANAZone, Info, Interval, InvalidZone, Settings, SystemZone, VERSION, Zone }; export { DateTime, Duration, FixedOffsetZone, IANAZone, Info, Interval, InvalidZone, Settings, SystemZone, VERSION, Zone };
//# sourceMappingURL=luxon.js.map //# sourceMappingURL=luxon.js.map

View file

@ -27,11 +27,11 @@
<script type="text/javascript" src="scripts/vendor/jquery.autocomplete.min.js"></script> <script type="text/javascript" src="scripts/vendor/jquery.autocomplete.min.js"></script>
<script type="text/javascript" src="scripts/vendor/auto/nosleep.js"></script> <script type="text/javascript" src="scripts/vendor/auto/nosleep.js"></script>
<script type="text/javascript" src="scripts/vendor/auto/swiped-events.js"></script> <script type="text/javascript" src="scripts/vendor/auto/swiped-events.js"></script>
<script type="module" src="scripts/modules/navigation.mjs"></script>
<script type="module" src="scripts/modules/currentweatherscroll.mjs"></script>
<!-- to be removed--> <!-- to be removed-->
<script type="module" src="scripts/modules/config.mjs"></script>
<script type="module" src="scripts/modules/status.mjs"></script> <script type="module" src="scripts/modules/status.mjs"></script>
<script type="module" src="scripts/vendor/auto/luxon.mjs"></script>
<script type="module" src="scripts/modules/currentweather.mjs"></script> <script type="module" src="scripts/modules/currentweather.mjs"></script>
<script type="module" src="scripts/modules/almanac.mjs"></script> <script type="module" src="scripts/modules/almanac.mjs"></script>
<script type="module" src="scripts/modules/icons.mjs"></script> <script type="module" src="scripts/modules/icons.mjs"></script>
@ -54,8 +54,6 @@
<script type="text/javascript" src="scripts/data/stations.js"></script> <script type="text/javascript" src="scripts/data/stations.js"></script>
<script type="text/javascript" src="scripts/vendor/auto/suncalc.js"></script> <script type="text/javascript" src="scripts/vendor/auto/suncalc.js"></script>
<script type="text/javascript" src="scripts/modules/utilities.js"></script>
<script type="text/javascript" src="scripts/modules/navigation.js"></script>
<script type="text/javascript" src="scripts/custom.js?_=<%=production%>"></script> <script type="text/javascript" src="scripts/custom.js?_=<%=production%>"></script>
<% } %> <% } %>