modular
This commit is contained in:
parent
b71d696670
commit
6933e7b7f1
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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,
|
|
||||||
};
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
|
||||||
};
|
|
||||||
|
|
|
@ -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'));
|
||||||
|
|
|
@ -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'));
|
||||||
|
|
|
@ -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'));
|
||||||
|
|
|
@ -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'));
|
||||||
|
|
|
@ -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,
|
|
||||||
};
|
|
||||||
})();
|
|
390
server/scripts/modules/navigation.mjs
Normal file
390
server/scripts/modules/navigation.mjs
Normal 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,
|
||||||
|
};
|
|
@ -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);
|
||||||
|
|
|
@ -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'));
|
||||||
|
|
|
@ -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'));
|
||||||
|
|
|
@ -7,4 +7,3 @@ const STATUS = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default STATUS;
|
export default STATUS;
|
||||||
window.STATUS = STATUS;
|
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
})();
|
|
18
server/scripts/modules/utils/nosleep.mjs
Normal file
18
server/scripts/modules/utils/nosleep.mjs
Normal 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;
|
|
@ -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;
|
||||||
|
|
16
server/scripts/modules/utils/weather.mjs
Normal file
16
server/scripts/modules/utils/weather.mjs
Normal 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,
|
||||||
|
};
|
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
2
server/scripts/vendor/auto/luxon.mjs
vendored
2
server/scripts/vendor/auto/luxon.mjs
vendored
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
Loading…
Reference in a new issue