Compare commits

..

37 Commits

Author SHA1 Message Date
Matt Walsh
3166dfad16 5.9.7 2023-01-10 14:12:32 -06:00
Matt Walsh
b6cceb5acf radar scroll height 2023-01-10 14:12:22 -06:00
Matt Walsh
1303c55851 freezing rain icon 2023-01-10 14:07:48 -06:00
Matt Walsh
339d391110 code cleanup 2023-01-06 16:26:19 -06:00
Matt Walsh
b6e57e8a19 reduse use of .reduce 2023-01-06 16:18:33 -06:00
Matt Walsh
3743c45de6 additional eslint rules 2023-01-06 14:39:39 -06:00
Matt Walsh
b890b4e53d update dependencies 2023-01-05 14:48:21 -06:00
Matt Walsh
b07478f7ff capture dist 2023-01-05 14:26:58 -06:00
Matt Walsh
8a25881d5b 5.9.6 2023-01-05 14:19:43 -06:00
Matt Walsh
0743b9e2bb filter current conditions for missing icon or current conditions 2023-01-05 14:19:33 -06:00
Matt Walsh
784c074e32 capture dist 2022-12-22 14:48:05 -06:00
Matt Walsh
4840909098 5.9.5 2022-12-22 14:47:08 -06:00
Matt Walsh
03dfbc462b better latest observation get data routine 2022-12-22 14:46:58 -06:00
Matt Walsh
25291efff5 capture dist 2022-12-21 16:20:54 -06:00
Matt Walsh
dc77ba835c 5.9.4 2022-12-21 16:20:37 -06:00
Matt Walsh
a440990696 update top form html and css 2022-12-21 16:20:31 -06:00
Matt Walsh
fc4cbc1415 fix time zones close #21 2022-12-21 15:17:50 -06:00
Matt Walsh
20ba3ddaac capture dist 2022-12-21 14:52:03 -06:00
Matt Walsh
4205155f96 5.9.3 2022-12-21 14:51:04 -06:00
Matt Walsh
f4101f06cc fix hazards failed to load auto play issue 2022-12-21 14:50:56 -06:00
Matt Walsh
4c3fcfc358 correct hazard scroll time 2022-12-21 14:44:36 -06:00
Matt Walsh
366f527aee auto play when hazard is present 2022-12-21 14:05:14 -06:00
Matt Walsh
797b4d32fa capture dist 2022-12-19 15:24:34 -06:00
Matt Walsh
5092076050 5.9.2 2022-12-19 15:24:12 -06:00
Matt Walsh
5d891fb38f switch to 2x image sizes 2022-12-19 15:21:38 -06:00
Matt Walsh
97e0fda709 key navigation 2022-12-19 11:48:59 -06:00
Matt Walsh
7cf9dd6466 almanac delivers data when disabled 2022-12-19 11:27:02 -06:00
Matt Walsh
a44bd866ed regional forecast icon blizzard 2022-12-19 11:17:30 -06:00
Matt Walsh
21ef7f476a auto refresh fix 2022-12-19 11:15:48 -06:00
Matt Walsh
c5b715d631 checkbox label colors 2022-12-19 10:17:12 -06:00
Matt Walsh
dfd9facc79 Merge branch 'main' of github.com:netbymatt/ws4kp 2022-12-19 10:14:37 -06:00
Matt Walsh
5b926a358e no hazards, blizzard 2022-12-19 10:14:33 -06:00
Matt Walsh
ba1fbd7088 capture dist 2022-12-14 21:49:07 -06:00
Matt Walsh
f82980ed09 5.9.1 2022-12-14 21:47:35 -06:00
Matt Walsh
af17b3c690 progress light mode colors 2022-12-14 21:47:27 -06:00
Matt Walsh
2c394c2e4a correct gulp build 2022-12-14 16:31:11 -06:00
Matt Walsh
97f8eda236 capture dist 2022-12-14 16:29:09 -06:00
39 changed files with 2001 additions and 580 deletions

View File

@@ -6,7 +6,10 @@ module.exports = {
node: true, node: true,
jquery: true, jquery: true,
}, },
extends: 'airbnb-base', extends: [
'airbnb-base',
'plugin:sonarjs/recommended',
],
globals: { globals: {
Atomics: 'readonly', Atomics: 'readonly',
SharedArrayBuffer: 'readonly', SharedArrayBuffer: 'readonly',
@@ -21,6 +24,10 @@ module.exports = {
parserOptions: { parserOptions: {
ecmaVersion: 2021, ecmaVersion: 2021,
}, },
plugins: [
'unicorn',
'sonarjs',
],
rules: { rules: {
indent: [ indent: [
'error', 'error',
@@ -60,6 +67,24 @@ module.exports = {
json: 'always', json: 'always',
}, },
], ],
// unicorn
'unicorn/numeric-separators-style': 'error',
'unicorn/prefer-query-selector': 'error',
'unicorn/catch-error-name': 'error',
'unicorn/no-negated-condition': 'error',
'unicorn/better-regex': 'error',
'unicorn/consistent-function-scoping': 'error',
'unicorn/prefer-array-flat-map': 'error',
'unicorn/prefer-array-find': 'error',
'unicorn/prefer-regexp-test': 'error',
'unicorn/consistent-destructuring': 'error',
'unicorn/prefer-date-now': 'error',
'unicorn/prefer-ternary': 'error',
'unicorn/prefer-dom-node-append': 'error',
'unicorn/explicit-length-check': 'error',
'unicorn/prefer-at': 'error',
// sonarjs
'sonarjs/cognitive-complexity': 0,
}, },
ignorePatterns: [ ignorePatterns: [
'*.min.js', '*.min.js',

14
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,14 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "lint",
"problemMatcher": [
"$eslint-stylish"
],
"label": "npm: lint",
"detail": "eslint ./server/scripts/**"
}
]
}

View File

@@ -11,7 +11,7 @@ module.exports = (req, res) => {
// add out-going headers // add out-going headers
const headers = {}; const headers = {};
headers['user-agent'] = '(WeatherStar 4000+, ws4000@netbymatt.com)'; headers['user-agent'] = '(WeatherStar 4000+, ws4000@netbymatt.com)';
headers['accept'] = req.headers.accept; headers.accept = req.headers.accept;
// get query paramaters if the exist // get query paramaters if the exist
const queryParams = Object.keys(req.query).reduce((acc, key) => { const queryParams = Object.keys(req.query).reduce((acc, key) => {
@@ -20,16 +20,16 @@ module.exports = (req, res) => {
// add the paramter to the resulting object // add the paramter to the resulting object
acc[key] = req.query[key]; acc[key] = req.query[key];
return acc; return acc;
},{}); }, {});
let query = queryString.encode(queryParams); let query = queryString.encode(queryParams);
if (query.length > 0) query = '?' + query; if (query.length > 0) query = `?${query}`;
// get the page // get the page
https.get('https://www.cpc.ncep.noaa.gov/' + req.path + query, { https.get(`https://www.cpc.ncep.noaa.gov/${req.path}${query}`, {
headers, headers,
}, getRes => { }, (getRes) => {
// pull some info // pull some info
const {statusCode} = getRes; const { statusCode } = getRes;
// pass the status code through // pass the status code through
res.status(statusCode); res.status(statusCode);
@@ -38,8 +38,7 @@ module.exports = (req, res) => {
res.header('last-modified', getRes.headers['last-modified']); res.header('last-modified', getRes.headers['last-modified']);
// pipe to response // pipe to response
getRes.pipe(res); getRes.pipe(res);
}).on('error', (e) => {
}).on('error', e=>{
console.error(e); console.error(e);
}); });
}; };

View File

@@ -11,7 +11,7 @@ module.exports = (req, res) => {
// add out-going headers // add out-going headers
const headers = {}; const headers = {};
headers['user-agent'] = '(WeatherStar 4000+, ws4000@netbymatt.com)'; headers['user-agent'] = '(WeatherStar 4000+, ws4000@netbymatt.com)';
headers['accept'] = req.headers.accept; headers.accept = req.headers.accept;
// get query paramaters if the exist // get query paramaters if the exist
const queryParams = Object.keys(req.query).reduce((acc, key) => { const queryParams = Object.keys(req.query).reduce((acc, key) => {
@@ -20,16 +20,16 @@ module.exports = (req, res) => {
// add the paramter to the resulting object // add the paramter to the resulting object
acc[key] = req.query[key]; acc[key] = req.query[key];
return acc; return acc;
},{}); }, {});
let query = queryString.encode(queryParams); let query = queryString.encode(queryParams);
if (query.length > 0) query = '?' + query; if (query.length > 0) query = `?${query}`;
// get the page // get the page
https.get('https://radar.weather.gov' + req.path + query, { https.get(`https://radar.weather.gov${req.path}${query}`, {
headers, headers,
}, getRes => { }, (getRes) => {
// pull some info // pull some info
const {statusCode} = getRes; const { statusCode } = getRes;
// pass the status code through // pass the status code through
res.status(statusCode); res.status(statusCode);
@@ -38,8 +38,7 @@ module.exports = (req, res) => {
res.header('last-modified', getRes.headers['last-modified']); res.header('last-modified', getRes.headers['last-modified']);
// pipe to response // pipe to response
getRes.pipe(res); getRes.pipe(res);
}).on('error', (e) => {
}).on('error', e=>{
console.error(e); console.error(e);
}); });
}; };

2
dist/index.html vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -71,6 +71,7 @@ gulp.task('compress_js_vendor', () => gulp.src(jsVendorSources)
const mjsSources = [ const mjsSources = [
'server/scripts/modules/currentweatherscroll.mjs', 'server/scripts/modules/currentweatherscroll.mjs',
'server/scripts/modules/hazards.mjs',
'server/scripts/modules/currentweather.mjs', 'server/scripts/modules/currentweather.mjs',
'server/scripts/modules/almanac.mjs', 'server/scripts/modules/almanac.mjs',
'server/scripts/modules/icons.mjs', 'server/scripts/modules/icons.mjs',

1746
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "ws4kp", "name": "ws4kp",
"version": "5.9.0", "version": "5.9.7",
"description": "Welcome to the WeatherStar 4000+ project page!", "description": "Welcome to the WeatherStar 4000+ project page!",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@@ -22,7 +22,11 @@
"devDependencies": { "devDependencies": {
"del": "^6.0.0", "del": "^6.0.0",
"ejs": "^3.1.5", "ejs": "^3.1.5",
"eslint": "^8.21.0",
"eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-sonarjs": "^0.17.0",
"eslint-plugin-unicorn": "^45.0.2",
"express": "^4.17.1", "express": "^4.17.1",
"gulp": "^4.0.2", "gulp": "^4.0.2",
"gulp-concat": "^2.6.1", "gulp-concat": "^2.6.1",
@@ -40,9 +44,6 @@
"suncalc": "^1.8.0", "suncalc": "^1.8.0",
"swiped-events": "^1.1.4", "swiped-events": "^1.1.4",
"terser-webpack-plugin": "^5.3.6", "terser-webpack-plugin": "^5.3.6",
"webpack-stream": "^7.0.0", "webpack-stream": "^7.0.0"
"eslint": "^8.21.0", }
"eslint-plugin-import": "^2.26.0"
},
"dependencies": {}
} }

View File

@@ -0,0 +1,6 @@
module.exports = {
rules: {
// unicorn
'unicorn/numeric-separators-style': 0,
},
};

View File

@@ -22,32 +22,38 @@ const categories = [
'Postal', 'Populated Place', 'Postal', 'Populated Place',
]; ];
const category = categories.join(','); const category = categories.join(',');
const TXT_ADDRESS_SELECTOR = '#txtAddress';
const TOGGLE_FULL_SCREEN_SELECTOR = '#ToggleFullScreen';
const BNT_GET_GPS_SELECTOR = '#btnGetGps';
const init = () => { const init = () => {
document.getElementById('txtAddress').addEventListener('focus', (e) => { document.querySelector(TXT_ADDRESS_SELECTOR).addEventListener('focus', (e) => {
e.target.select(); e.target.select();
}); });
registerRefreshData(loadData); registerRefreshData(loadData);
document.getElementById('NavigateMenu').addEventListener('click', btnNavigateMenuClick); document.querySelector('#NavigateMenu').addEventListener('click', btnNavigateMenuClick);
document.getElementById('NavigateRefresh').addEventListener('click', btnNavigateRefreshClick); document.querySelector('#NavigateRefresh').addEventListener('click', btnNavigateRefreshClick);
document.getElementById('NavigateNext').addEventListener('click', btnNavigateNextClick); document.querySelector('#NavigateNext').addEventListener('click', btnNavigateNextClick);
document.getElementById('NavigatePrevious').addEventListener('click', btnNavigatePreviousClick); document.querySelector('#NavigatePrevious').addEventListener('click', btnNavigatePreviousClick);
document.getElementById('NavigatePlay').addEventListener('click', btnNavigatePlayClick); document.querySelector('#NavigatePlay').addEventListener('click', btnNavigatePlayClick);
document.getElementById('ToggleFullScreen').addEventListener('click', btnFullScreenClick); document.querySelector(TOGGLE_FULL_SCREEN_SELECTOR).addEventListener('click', btnFullScreenClick);
const btnGetGps = document.getElementById('btnGetGps'); const btnGetGps = document.querySelector(BNT_GET_GPS_SELECTOR);
btnGetGps.addEventListener('click', btnGetGpsClick); btnGetGps.addEventListener('click', btnGetGpsClick);
if (!navigator.geolocation) btnGetGps.style.display = 'none'; if (!navigator.geolocation) btnGetGps.style.display = 'none';
document.getElementById('divTwc').addEventListener('click', () => { document.querySelector('#divTwc').addEventListener('click', () => {
if (document.fullscreenElement) updateFullScreenNavigate(); if (document.fullscreenElement) updateFullScreenNavigate();
}); });
document.querySelector(TXT_ADDRESS_SELECTOR).addEventListener('keydown', (key) => { if (key.code === 'Enter') formSubmit(); });
document.querySelector('#btnGetLatLng').addEventListener('click', () => formSubmit());
document.addEventListener('keydown', documentKeydown); document.addEventListener('keydown', documentKeydown);
document.addEventListener('touchmove', (e) => { if (fullScreenOverride) e.preventDefault(); }); document.addEventListener('touchmove', (e) => { if (fullScreenOverride) e.preventDefault(); });
$('#frmGetLatLng #txtAddress').devbridgeAutocomplete({ $(TXT_ADDRESS_SELECTOR).devbridgeAutocomplete({
serviceUrl: 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/suggest', serviceUrl: 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/suggest',
deferRequestBy: 300, deferRequestBy: 300,
paramName: 'text', paramName: 'text',
@@ -71,18 +77,18 @@ const init = () => {
width: 490, width: 490,
}); });
$('#frmGetLatLng').on('submit', () => { const formSubmit = () => {
const ac = $('#frmGetLatLng #txtAddress').devbridgeAutocomplete(); const ac = $(TXT_ADDRESS_SELECTOR).devbridgeAutocomplete();
if (ac.suggestions[0]) $(ac.suggestionsContainer.children[0]).trigger('click'); if (ac.suggestions[0]) $(ac.suggestionsContainer.children[0]).trigger('click');
return false; return false;
}); };
// Auto load the previous query // Auto load the previous query
const query = localStorage.getItem('latLonQuery'); const query = localStorage.getItem('latLonQuery');
const latLon = localStorage.getItem('latLon'); const latLon = localStorage.getItem('latLon');
const fromGPS = localStorage.getItem('latLonFromGPS'); const fromGPS = localStorage.getItem('latLonFromGPS');
if (query && latLon && !fromGPS) { if (query && latLon && !fromGPS) {
const txtAddress = document.getElementById('txtAddress'); const txtAddress = document.querySelector(TXT_ADDRESS_SELECTOR);
txtAddress.value = query; txtAddress.value = query;
loadData(JSON.parse(latLon)); loadData(JSON.parse(latLon));
} }
@@ -93,14 +99,14 @@ const init = () => {
const play = localStorage.getItem('play'); const play = localStorage.getItem('play');
if (play === null || play === 'true') postMessage('navButton', 'play'); if (play === null || play === 'true') postMessage('navButton', 'play');
document.getElementById('btnClearQuery').addEventListener('click', () => { document.querySelector('#btnClearQuery').addEventListener('click', () => {
document.getElementById('spanCity').innerHTML = ''; document.querySelector('#spanCity').innerHTML = '';
document.getElementById('spanState').innerHTML = ''; document.querySelector('#spanState').innerHTML = '';
document.getElementById('spanStationId').innerHTML = ''; document.querySelector('#spanStationId').innerHTML = '';
document.getElementById('spanRadarId').innerHTML = ''; document.querySelector('#spanRadarId').innerHTML = '';
document.getElementById('spanZoneId').innerHTML = ''; document.querySelector('#spanZoneId').innerHTML = '';
document.getElementById('chkAutoRefresh').checked = true; document.querySelector('#chkAutoRefresh').checked = true;
localStorage.removeItem('autoRefresh'); localStorage.removeItem('autoRefresh');
localStorage.removeItem('play'); localStorage.removeItem('play');
@@ -109,12 +115,12 @@ const init = () => {
localStorage.removeItem('latLonQuery'); localStorage.removeItem('latLonQuery');
localStorage.removeItem('latLon'); localStorage.removeItem('latLon');
localStorage.removeItem('latLonFromGPS'); localStorage.removeItem('latLonFromGPS');
document.getElementById('btnGetGps').classList.remove('active'); document.querySelector(BNT_GET_GPS_SELECTOR).classList.remove('active');
}); });
// swipe functionality // swipe functionality
document.getElementById('container').addEventListener('swiped-left', () => swipeCallBack('left')); document.querySelector('#container').addEventListener('swiped-left', () => swipeCallBack('left'));
document.getElementById('container').addEventListener('swiped-right', () => swipeCallBack('right')); document.querySelector('#container').addEventListener('swiped-right', () => swipeCallBack('right'));
}; };
const autocompleteOnSelect = async (suggestion, elem) => { const autocompleteOnSelect = async (suggestion, elem) => {
@@ -132,7 +138,7 @@ const autocompleteOnSelect = async (suggestion, elem) => {
const loc = data.locations[0]; const loc = data.locations[0];
if (loc) { if (loc) {
localStorage.removeItem('latLonFromGPS'); localStorage.removeItem('latLonFromGPS');
document.getElementById('btnGetGps').classList.remove('active'); document.querySelector(BNT_GET_GPS_SELECTOR).classList.remove('active');
doRedirectToGeometry(loc.feature.geometry); doRedirectToGeometry(loc.feature.geometry);
} else { } else {
console.error('An unexpected error occurred. Please try a different search string.'); console.error('An unexpected error occurred. Please try a different search string.');
@@ -142,7 +148,7 @@ const autocompleteOnSelect = async (suggestion, elem) => {
const doRedirectToGeometry = (geom, haveDataCallback) => { const doRedirectToGeometry = (geom, haveDataCallback) => {
const latLon = { lat: round2(geom.y, 4), lon: round2(geom.x, 4) }; const latLon = { lat: round2(geom.y, 4), lon: round2(geom.x, 4) };
// Save the query // Save the query
localStorage.setItem('latLonQuery', document.getElementById('txtAddress').value); localStorage.setItem('latLonQuery', document.querySelector(TXT_ADDRESS_SELECTOR).value);
localStorage.setItem('latLon', JSON.stringify(latLon)); localStorage.setItem('latLon', JSON.stringify(latLon));
// get the data // get the data
@@ -150,10 +156,10 @@ const doRedirectToGeometry = (geom, haveDataCallback) => {
}; };
const btnFullScreenClick = () => { const btnFullScreenClick = () => {
if (!document.fullscreenElement) { if (document.fullscreenElement) {
enterFullScreen();
} else {
exitFullscreen(); exitFullscreen();
} else {
enterFullScreen();
} }
if (isPlaying()) { if (isPlaying()) {
@@ -168,7 +174,7 @@ const btnFullScreenClick = () => {
}; };
const enterFullScreen = () => { const enterFullScreen = () => {
const element = document.getElementById('divTwc'); const element = document.querySelector('#divTwc');
// Supports most browsers and their versions. // Supports most browsers and their versions.
const requestMethod = element.requestFullScreen || element.webkitRequestFullScreen const requestMethod = element.requestFullScreen || element.webkitRequestFullScreen
@@ -186,8 +192,8 @@ const enterFullScreen = () => {
updateFullScreenNavigate(); updateFullScreenNavigate();
// change hover text and image // change hover text and image
const img = document.getElementById('ToggleFullScreen'); const img = document.querySelector(TOGGLE_FULL_SCREEN_SELECTOR);
img.src = 'images/nav/ic_fullscreen_exit_white_24dp_1x.png'; img.src = 'images/nav/ic_fullscreen_exit_white_24dp_2x.png';
img.title = 'Exit fullscreen'; img.title = 'Exit fullscreen';
}; };
@@ -210,8 +216,8 @@ const exitFullscreen = () => {
} }
resize(); resize();
// change hover text and image // change hover text and image
const img = document.getElementById('ToggleFullScreen'); const img = document.querySelector(TOGGLE_FULL_SCREEN_SELECTOR);
img.src = 'images/nav/ic_fullscreen_white_24dp_1x.png'; img.src = 'images/nav/ic_fullscreen_white_24dp_2x.png';
img.title = 'Enter fullscreen'; img.title = 'Enter fullscreen';
}; };
@@ -229,7 +235,7 @@ const loadData = (_latLon, haveDataCallback) => {
// if there's no data stop // if there's no data stop
if (!latLon) return; if (!latLon) return;
document.getElementById('txtAddress').blur(); document.querySelector(TXT_ADDRESS_SELECTOR).blur();
stopAutoRefreshTimer(); stopAutoRefreshTimer();
latLonReceived(latLon, haveDataCallback); latLonReceived(latLon, haveDataCallback);
}; };
@@ -273,8 +279,9 @@ let navigateFadeIntervalId = null;
const updateFullScreenNavigate = () => { const updateFullScreenNavigate = () => {
document.activeElement.blur(); document.activeElement.blur();
document.getElementById('divTwcBottom').classList.remove('hidden'); const divTwcBottom = document.querySelector('#divTwcBottom');
document.getElementById('divTwcBottom').classList.add('visible'); divTwcBottom.classList.remove('hidden');
divTwcBottom.classList.add('visible');
if (navigateFadeIntervalId) { if (navigateFadeIntervalId) {
clearTimeout(navigateFadeIntervalId); clearTimeout(navigateFadeIntervalId);
@@ -283,44 +290,48 @@ const updateFullScreenNavigate = () => {
navigateFadeIntervalId = setTimeout(() => { navigateFadeIntervalId = setTimeout(() => {
if (document.fullscreenElement) { if (document.fullscreenElement) {
document.getElementById('divTwcBottom').classList.remove('visible'); divTwcBottom.classList.remove('visible');
document.getElementById('divTwcBottom').classList.add('hidden'); divTwcBottom.classList.add('hidden');
} }
}, 2000); }, 2000);
}; };
const documentKeydown = (e) => { const documentKeydown = (e) => {
const code = (e.keyCode || e.which); const { key } = e;
// 200ms repeat
if ((Date.now() - documentKeydown.lastButton ?? 0) < 200) return false;
documentKeydown.lastButton = Date.now();
if (document.fullscreenElement || document.activeElement === document.body) { if (document.fullscreenElement || document.activeElement === document.body) {
switch (code) { switch (key) {
case 32: // Space case ' ': // Space
// don't scroll
e.preventDefault();
btnNavigatePlayClick(); btnNavigatePlayClick();
return false; return false;
case 39: // Right Arrow case 'ArrowRight':
case 34: // Page Down case 'PageDown':
// don't scroll
e.preventDefault();
btnNavigateNextClick(); btnNavigateNextClick();
return false; return false;
case 37: // Left Arrow case 'ArrowLeft':
case 33: // Page Up case 'PageUp':
// don't scroll
e.preventDefault();
btnNavigatePreviousClick(); btnNavigatePreviousClick();
return false; return false;
case 36: // Home case 'ArrowUp': // Home
e.preventDefault();
btnNavigateMenuClick(); btnNavigateMenuClick();
return false; return false;
case 48: // Restart case '0': // "O" Restart
btnNavigateRefreshClick(); btnNavigateRefreshClick();
return false; return false;
case 70: // F case 'F':
case 'f':
btnFullScreenClick(); btnFullScreenClick();
return false; return false;
@@ -348,7 +359,7 @@ const getPosition = async () => new Promise((resolve) => {
const btnGetGpsClick = async () => { const btnGetGpsClick = async () => {
if (!navigator.geolocation) return; if (!navigator.geolocation) return;
const btn = document.getElementById('btnGetGps'); const btn = document.querySelector(BNT_GET_GPS_SELECTOR);
// toggle first // toggle first
if (btn.classList.contains('active')) { if (btn.classList.contains('active')) {
@@ -364,11 +375,10 @@ const btnGetGpsClick = async () => {
const position = await getPosition(); const position = await getPosition();
const { latitude, longitude } = position.coords; const { latitude, longitude } = position.coords;
const txtAddress = document.getElementById('txtAddress'); const txtAddress = document.querySelector(TXT_ADDRESS_SELECTOR);
txtAddress.value = `${round2(latitude, 4)}, ${round2(longitude, 4)}`; txtAddress.value = `${round2(latitude, 4)}, ${round2(longitude, 4)}`;
doRedirectToGeometry({ y: latitude, x: longitude }, (point) => { doRedirectToGeometry({ y: latitude, x: longitude }, (point) => {
console.log(point);
const location = point.properties.relativeLocation.properties; const location = point.properties.relativeLocation.properties;
// Save the query // Save the query
const query = `${location.city}, ${location.state}`; const query = `${location.city}, ${location.state}`;

View File

@@ -22,7 +22,7 @@ class Almanac extends WeatherDisplay {
} }
async getData(_weatherParameters) { async getData(_weatherParameters) {
if (!super.getData(_weatherParameters)) return; const superResponse = super.getData(_weatherParameters);
const weatherParameters = _weatherParameters ?? this.weatherParameters; const weatherParameters = _weatherParameters ?? this.weatherParameters;
// get sun/moon data // get sun/moon data
@@ -33,11 +33,13 @@ class Almanac extends WeatherDisplay {
sun, sun,
moon, moon,
}; };
// update status
this.setStatus(STATUS.loaded);
// share data // share data
this.getDataCallback(); this.getDataCallback();
if (!superResponse) return;
// update status
this.setStatus(STATUS.loaded);
} }
calcSunMoonData(weatherParameters) { calcSunMoonData(weatherParameters) {

View File

@@ -53,12 +53,14 @@ class CurrentWeather extends WeatherDisplay {
// test data quality // test data quality
if (observations.features[0].properties.temperature.value === null if (observations.features[0].properties.temperature.value === null
|| observations.features[0].properties.windSpeed.value === null || observations.features[0].properties.windSpeed.value === null
|| observations.features[0].properties.textDescription === null) { || observations.features[0].properties.textDescription === null
|| observations.features[0].properties.textDescription === ''
|| observations.features[0].properties.icon === null) {
observations = undefined; observations = undefined;
throw new Error(`Unable to get observations: ${station.properties.stationIdentifier}, trying next station`); throw new Error(`Unable to get observations: ${station.properties.stationIdentifier}, trying next station`);
} }
} catch (e) { } catch (error) {
console.error(e); console.error(error);
} }
} }
// test for data received // test for data received

View File

@@ -43,7 +43,7 @@ const incrementInterval = () => {
const drawScreen = async () => { const drawScreen = async () => {
// get the conditions // get the conditions
const data = await getCurrentWeather(() => this.stillWaiting()); 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;
@@ -75,12 +75,10 @@ const screens = [
// wind // wind
(data) => { (data) => {
let text = ''; let text = data.WindSpeed > 0
if (data.WindSpeed > 0) { ? `Wind: ${data.WindDirection} ${data.WindSpeed} ${data.WindUnit}`
text = `Wind: ${data.WindDirection} ${data.WindSpeed} ${data.WindUnit}`; : 'Wind: Calm';
} else {
text = 'Wind: Calm';
}
if (data.WindGust > 0) { if (data.WindGust > 0) {
text += ` Gusts to ${data.WindGust}`; text += ` Gusts to ${data.WindGust}`;
} }
@@ -88,7 +86,10 @@ const screens = [
}, },
// visibility // visibility
(data) => `Visib: ${data.Visibility} ${data.VisibilityUnit} Ceiling: ${data.Ceiling === 0 ? 'Unlimited' : `${data.Ceiling} ${data.CeilingUnit}`}`, (data) => {
const distance = `${data.Ceiling} ${data.CeilingUnit}`;
return `Visib: ${data.Visibility} ${data.VisibilityUnit} Ceiling: ${data.Ceiling === 0 ? 'Unlimited' : distance}`;
},
]; ];
// internal draw function with preset parameters // internal draw function with preset parameters

View File

@@ -31,9 +31,9 @@ class ExtendedForecast extends WeatherDisplay {
retryCount: 3, retryCount: 3,
stillWaiting: () => this.stillWaiting(), stillWaiting: () => this.stillWaiting(),
}); });
} catch (e) { } catch (error) {
console.error('Unable to get extended forecast'); console.error('Unable to get extended forecast');
console.error(e.status, e.responseJSON); console.error(error.status, error.responseJSON);
this.setStatus(STATUS.failed); this.setStatus(STATUS.failed);
return; return;
} }
@@ -52,7 +52,10 @@ class ExtendedForecast extends WeatherDisplay {
// create each day template // create each day template
const days = forecast.map((Day) => { const days = forecast.map((Day) => {
const fill = {}; const fill = {
icon: { type: 'img', src: Day.icon },
condition: Day.text,
};
fill.date = Day.dayName; fill.date = Day.dayName;
const { low } = Day; const { low } = Day;
@@ -61,10 +64,6 @@ class ExtendedForecast extends WeatherDisplay {
} }
const { high } = Day; const { high } = Day;
fill['value-hi'] = Math.round(high); fill['value-hi'] = Math.round(high);
fill.condition = Day.text;
// draw the icon
fill.icon = { type: 'img', src: Day.icon };
// return the filled template // return the filled template
return this.fillTemplate('day', fill); return this.fillTemplate('day', fill);
@@ -123,13 +122,13 @@ const parse = (fullForecast) => {
const shortenExtendedForecastText = (long) => { const shortenExtendedForecastText = (long) => {
const regexList = [ const regexList = [
[/ and /ig, ' '], [/ and /gi, ' '],
[/Slight /ig, ''], [/slight /gi, ''],
[/Chance /ig, ''], [/chance /gi, ''],
[/Very /ig, ''], [/very /gi, ''],
[/Patchy /ig, ''], [/patchy /gi, ''],
[/Areas /ig, ''], [/areas /gi, ''],
[/Dense /ig, ''], [/dense /gi, ''],
[/Thunderstorm/g, 'T\'Storm'], [/Thunderstorm/g, 'T\'Storm'],
]; ];
// run all regexes // run all regexes
@@ -144,10 +143,10 @@ const shortenExtendedForecastText = (long) => {
let short1 = conditions[0].substr(0, 10); let short1 = conditions[0].substr(0, 10);
let short2 = ''; let short2 = '';
if (conditions[1]) { if (conditions[1]) {
if (!short1.endsWith('.')) { if (short1.endsWith('.')) {
short2 = conditions[1].substr(0, 10);
} else {
short1 = short1.replace(/\./, ''); short1 = short1.replace(/\./, '');
} else {
short2 = conditions[1].substr(0, 10);
} }
if (short2 === 'Blowing') { if (short2 === 'Blowing') {

View File

@@ -35,13 +35,14 @@ class Hazards extends WeatherDisplay {
const alerts = await json(url, { retryCount: 3, stillWaiting: () => this.stillWaiting() }); const alerts = await json(url, { retryCount: 3, stillWaiting: () => this.stillWaiting() });
const unsortedAlerts = alerts.features ?? []; const unsortedAlerts = alerts.features ?? [];
const sortedAlerts = unsortedAlerts.sort((a, b) => (hazardLevels[b.properties.severity] ?? 0) - (hazardLevels[a.properties.severity] ?? 0)); const sortedAlerts = unsortedAlerts.sort((a, b) => (hazardLevels[b.properties.severity] ?? 0) - (hazardLevels[a.properties.severity] ?? 0));
this.data = sortedAlerts; const filteredAlerts = sortedAlerts.filter((hazard) => hazard.properties.severity !== 'Unknown');
this.data = filteredAlerts;
// show alert indicator // show alert indicator
if (this.data.length > 0) alert.classList.add('show'); if (this.data.length > 0) alert.classList.add('show');
} catch (e) { } catch (error) {
console.error('Get hourly forecast failed'); console.error('Get hourly forecast failed');
console.error(e.status, e.responseJSON); console.error(error.status, error.responseJSON);
if (this.isEnabled) this.setStatus(STATUS.failed); if (this.isEnabled) this.setStatus(STATUS.failed);
// return undefined to other subscribers // return undefined to other subscribers
this.getDataCallback(undefined); this.getDataCallback(undefined);
@@ -50,7 +51,10 @@ class Hazards extends WeatherDisplay {
this.getDataCallback(); this.getDataCallback();
if (!superResult) return; if (!superResult) {
this.setStatus(STATUS.loaded);
return;
}
this.drawLongCanvas(); this.drawLongCanvas();
} }
@@ -71,7 +75,9 @@ class Hazards extends WeatherDisplay {
// no alerts, skip this display by setting timing to zero // no alerts, skip this display by setting timing to zero
if (lines.length === 0) { if (lines.length === 0) {
this.setStatus(STATUS.loaded);
this.timing.totalScreens = 0; this.timing.totalScreens = 0;
this.setStatus(STATUS.loaded);
return; return;
} }
@@ -79,13 +85,13 @@ class Hazards extends WeatherDisplay {
// set up the timing // set up the timing
this.timing.baseDelay = 20; this.timing.baseDelay = 20;
// 24 hours = 6 pages // 24 hours = 6 pages
const pages = Math.ceil(list.scrollHeight / 390); // first page is already displayed, last page doesn't happen const pages = Math.max(Math.ceil(list.scrollHeight / 400) - 3, 1);
const timingStep = 75 * 4; const timingStep = 400;
this.timing.delay = [150 + timingStep]; this.timing.delay = [150 + timingStep];
// add additional pages // add additional pages
for (let i = 0; i < pages; i += 1) this.timing.delay.push(timingStep); for (let i = 0; i < pages; i += 1) this.timing.delay.push(timingStep);
// add the final 3 second delay // add the final 3 second delay
this.timing.delay.push(150); this.timing.delay.push(250);
this.calcNavTiming(); this.calcNavTiming();
this.setStatus(STATUS.loaded); this.setStatus(STATUS.loaded);
} }

View File

@@ -34,9 +34,9 @@ class Hourly extends WeatherDisplay {
try { try {
// get the forecast // get the forecast
forecast = await json(weatherParameters.forecastGridData, { retryCount: 3, stillWaiting: () => this.stillWaiting() }); forecast = await json(weatherParameters.forecastGridData, { retryCount: 3, stillWaiting: () => this.stillWaiting() });
} catch (e) { } catch (error) {
console.error('Get hourly forecast failed'); console.error('Get hourly forecast failed');
console.error(e.status, e.responseJSON); console.error(error.status, error.responseJSON);
if (this.isEnabled) this.setStatus(STATUS.failed); if (this.isEnabled) this.setStatus(STATUS.failed);
// return undefined to other subscribers // return undefined to other subscribers
this.getDataCallback(undefined); this.getDataCallback(undefined);
@@ -183,7 +183,7 @@ const expand = (data) => {
result.push(item.value); // push data array result.push(item.value); // push data array
} // timestamp is after now } // timestamp is after now
// increment start time by 1 hour // increment start time by 1 hour
startTime += 3600000; startTime += 3_600_000;
} while (startTime < endTime && result.length < 24); } while (startTime < endTime && result.length < 24);
}); // for each value }); // for each value

View File

@@ -1,3 +1,4 @@
/* eslint-disable unicorn/consistent-function-scoping */
/* spell-checker: disable */ /* spell-checker: disable */
const getWeatherRegionalIconFromIconLink = (link, _isNightTime) => { const getWeatherRegionalIconFromIconLink = (link, _isNightTime) => {
@@ -8,7 +9,7 @@ const getWeatherRegionalIconFromIconLink = (link, _isNightTime) => {
// grab everything after the last slash ending at any of these: ?&, // grab everything after the last slash ending at any of these: ?&,
const afterLastSlash = link.toLowerCase().match(/[^/]+$/)[0]; const afterLastSlash = link.toLowerCase().match(/[^/]+$/)[0];
let conditionName = afterLastSlash.match(/(.*?)[,?&.]/)[1]; let conditionName = afterLastSlash.match(/(.*?)[&,.?]/)[1];
// using probability as a crude heavy/light indication where possible // using probability as a crude heavy/light indication where possible
const value = +(link.match(/,(\d{2,3})/) ?? [0, 100])[1]; const value = +(link.match(/,(\d{2,3})/) ?? [0, 100])[1];
@@ -87,6 +88,7 @@ const getWeatherRegionalIconFromIconLink = (link, _isNightTime) => {
return addPath('Light-Snow.gif'); return addPath('Light-Snow.gif');
case 'rain_snow': case 'rain_snow':
case 'rain_snow-n':
return addPath('Rain-Snow-1992.gif'); return addPath('Rain-Snow-1992.gif');
case 'snow_fzra': case 'snow_fzra':
@@ -95,6 +97,8 @@ const getWeatherRegionalIconFromIconLink = (link, _isNightTime) => {
case 'fzra': case 'fzra':
case 'fzra-n': case 'fzra-n':
case 'rain_fzra':
case 'rain_fzra-n':
return addPath('Freezing-Rain-1992.gif'); return addPath('Freezing-Rain-1992.gif');
case 'snow_sleet': case 'snow_sleet':
@@ -133,6 +137,7 @@ const getWeatherRegionalIconFromIconLink = (link, _isNightTime) => {
return addPath('Clear-Wind-1994.gif'); return addPath('Clear-Wind-1994.gif');
case 'blizzard': case 'blizzard':
case 'blizzard-n':
return addPath('Blowing Snow.gif'); return addPath('Blowing Snow.gif');
case 'cold': case 'cold':
@@ -154,7 +159,7 @@ const getWeatherIconFromIconLink = (link, _isNightTime) => {
// grab everything after the last slash ending at any of these: ?&, // grab everything after the last slash ending at any of these: ?&,
const afterLastSlash = link.toLowerCase().match(/[^/]+$/)[0]; const afterLastSlash = link.toLowerCase().match(/[^/]+$/)[0];
let conditionName = afterLastSlash.match(/(.*?)[,?&.]/)[1]; let conditionName = afterLastSlash.match(/(.*?)[&,.?]/)[1];
// using probability as a crude heavy/light indication where possible // using probability as a crude heavy/light indication where possible
const value = +(link.match(/,(\d{2,3})/) ?? [0, 100])[1]; const value = +(link.match(/,(\d{2,3})/) ?? [0, 100])[1];
@@ -268,6 +273,7 @@ const getWeatherIconFromIconLink = (link, _isNightTime) => {
return addPath('CC_Windy.gif'); return addPath('CC_Windy.gif');
case 'blizzard': case 'blizzard':
case 'blizzard-n':
return addPath('Blowing-Snow.gif'); return addPath('Blowing-Snow.gif');
default: default:

View File

@@ -32,31 +32,25 @@ class LatestObservations extends WeatherDisplay {
const regionalStations = sortedStations.slice(0, 30); const regionalStations = sortedStations.slice(0, 30);
// get data for regional stations // get data for regional stations
const allConditions = await Promise.all(regionalStations.map(async (station) => { // get first 7 stations
try { const actualConditions = [];
const data = await json(`https://api.weather.gov/stations/${station.id}/observations/latest`, { retryCount: 3, stillWaiting: () => this.stillWaiting() }); let lastStation = Math.min(regionalStations.length, 7);
// test for temperature, weather and wind values present let firstStation = 0;
if (data.properties.temperature.value === null while (actualConditions.length < 7 && (lastStation) <= regionalStations.length) {
|| data.properties.textDescription === '' // eslint-disable-next-line no-await-in-loop
|| data.properties.windSpeed.value === null) return false; const someStations = await getStations(regionalStations.slice(firstStation, lastStation));
// format the return values
return { actualConditions.push(...someStations);
...data.properties, // update counters
StationId: station.id, firstStation += lastStation;
city: station.city, lastStation = Math.min(regionalStations.length + 1, firstStation + 7 - actualConditions.length);
}; }
} catch (e) {
console.log(`Unable to get latest observations for ${station.id}`);
return false;
}
}));
// remove and stations that did not return data
const actualConditions = allConditions.filter((condition) => condition);
// cut down to the maximum of 7 // cut down to the maximum of 7
this.data = actualConditions.slice(0, this.MaximumRegionalStations); this.data = actualConditions.slice(0, this.MaximumRegionalStations);
// test for at least one station // test for at least one station
if (this.data.length < 1) { if (this.data.length === 0) {
this.setStatus(STATUS.noData); this.setStatus(STATUS.noData);
return; return;
} }
@@ -79,10 +73,12 @@ class LatestObservations extends WeatherDisplay {
const Temperature = Math.round(celsiusToFahrenheit(condition.temperature.value)); const Temperature = Math.round(celsiusToFahrenheit(condition.temperature.value));
const WindSpeed = Math.round(kphToMph(condition.windSpeed.value)); const WindSpeed = Math.round(kphToMph(condition.windSpeed.value));
const fill = {}; const fill = {
fill.location = locationCleanup(condition.city).substr(0, 14); location: locationCleanup(condition.city).substr(0, 14),
fill.temp = Temperature; temp: Temperature,
fill.weather = shortenCurrentConditions(condition.textDescription).substr(0, 9); weather: shortenCurrentConditions(condition.textDescription).substr(0, 9),
};
if (WindSpeed > 0) { if (WindSpeed > 0) {
fill.wind = windDirection + (Array(6 - windDirection.length - WindSpeed.toString().length).join(' ')) + WindSpeed.toString(); fill.wind = windDirection + (Array(6 - windDirection.length - WindSpeed.toString().length).join(' ')) + WindSpeed.toString();
} else if (WindSpeed === 'NA') { } else if (WindSpeed === 'NA') {
@@ -119,5 +115,28 @@ const shortenCurrentConditions = (_condition) => {
condition = condition.replace(/ with /, '/'); condition = condition.replace(/ with /, '/');
return condition; return condition;
}; };
const getStations = async (stations) => {
const stationData = await Promise.all(stations.map(async (station) => {
try {
const data = await json(`https://api.weather.gov/stations/${station.id}/observations/latest`, { retryCount: 1, stillWaiting: () => this.stillWaiting() });
// test for temperature, weather and wind values present
if (data.properties.temperature.value === null
|| data.properties.textDescription === ''
|| data.properties.windSpeed.value === null) return false;
// format the return values
return {
...data.properties,
StationId: station.id,
city: station.city,
};
} catch (error) {
console.log(`Unable to get latest observations for ${station.id}`);
return false;
}
}));
// filter false (no data or other error)
return stationData.filter((d) => d);
};
// register display // register display
registerDisplay(new LatestObservations(2, 'latest-observations')); registerDisplay(new LatestObservations(2, 'latest-observations'));

View File

@@ -66,9 +66,9 @@ class LocalForecast extends WeatherDisplay {
retryCount: 3, retryCount: 3,
stillWaiting: () => this.stillWaiting(), stillWaiting: () => this.stillWaiting(),
}); });
} catch (e) { } catch (error) {
console.error(`GetWeatherForecast failed: ${weatherParameters.forecast}`); console.error(`GetWeatherForecast failed: ${weatherParameters.forecast}`);
console.error(e.status, e.responseJSON); console.error(error.status, error.responseJSON);
this.setStatus(STATUS.failed); this.setStatus(STATUS.failed);
return false; return false;
} }

View File

@@ -16,7 +16,8 @@ const weatherParameters = {};
// auto refresh // auto refresh
const AUTO_REFRESH_INTERVAL_MS = 500; const AUTO_REFRESH_INTERVAL_MS = 500;
const AUTO_REFRESH_TIME_MS = 600000; // 10 min. const AUTO_REFRESH_TIME_MS = 600_000; // 10 min.
const CHK_AUTO_REFRESH_SELECTOR = '#chkAutoRefresh';
let AutoRefreshIntervalId = null; let AutoRefreshIntervalId = null;
let AutoRefreshCountMs = 0; let AutoRefreshCountMs = 0;
@@ -28,25 +29,19 @@ const init = async () => {
// auto refresh // auto refresh
const autoRefresh = localStorage.getItem('autoRefresh'); const autoRefresh = localStorage.getItem('autoRefresh');
if (!autoRefresh || autoRefresh === 'true') { if (!autoRefresh || autoRefresh === 'true') {
document.getElementById('chkAutoRefresh').checked = true; document.querySelector(CHK_AUTO_REFRESH_SELECTOR).checked = true;
} else { } else {
document.getElementById('chkAutoRefresh').checked = false; document.querySelector(CHK_AUTO_REFRESH_SELECTOR).checked = false;
} }
document.getElementById('chkAutoRefresh').addEventListener('change', autoRefreshChange); document.querySelector(CHK_AUTO_REFRESH_SELECTOR).addEventListener('change', autoRefreshChange);
generateCheckboxes(); generateCheckboxes();
}; };
const message = (data) => { const message = (data) => {
// dispatch event // dispatch event
if (!data.type) return; if (!data.type) return false;
switch (data.type) { if (data.type === 'navButton') return handleNavButton(data.message);
case 'navButton': return console.error(`Unknown event ${data.type}`);
handleNavButton(data.message);
break;
default:
console.error(`Unknown event ${data.type}`);
}
}; };
const getWeather = async (latLon, haveDataCallback) => { const getWeather = async (latLon, haveDataCallback) => {
@@ -61,6 +56,7 @@ const getWeather = async (latLon, haveDataCallback) => {
const StationId = stations.features[0].properties.stationIdentifier; const StationId = stations.features[0].properties.stationIdentifier;
let { city } = point.properties.relativeLocation.properties; let { city } = point.properties.relativeLocation.properties;
const { state } = point.properties.relativeLocation.properties;
if (StationId in StationInfo) { if (StationId in StationInfo) {
city = StationInfo[StationId].city; city = StationInfo[StationId].city;
@@ -76,8 +72,8 @@ const getWeather = async (latLon, haveDataCallback) => {
weatherParameters.stationId = StationId; weatherParameters.stationId = StationId;
weatherParameters.weatherOffice = point.properties.cwa; weatherParameters.weatherOffice = point.properties.cwa;
weatherParameters.city = city; weatherParameters.city = city;
weatherParameters.state = point.properties.relativeLocation.properties.state; weatherParameters.state = state;
weatherParameters.timeZone = point.properties.relativeLocation.properties.timeZone; weatherParameters.timeZone = point.properties.timeZone;
weatherParameters.forecast = point.properties.forecast; weatherParameters.forecast = point.properties.forecast;
weatherParameters.forecastGridData = point.properties.forecastGridData; weatherParameters.forecastGridData = point.properties.forecastGridData;
weatherParameters.stations = stations.features; weatherParameters.stations = stations.features;
@@ -87,7 +83,7 @@ const getWeather = async (latLon, haveDataCallback) => {
// draw the progress canvas and hide others // draw the progress canvas and hide others
hideAllCanvases(); hideAllCanvases();
document.getElementById('loading').style.display = 'none'; document.querySelector('#loading').style.display = 'none';
if (progress) { if (progress) {
await progress.drawCanvas(); await progress.drawCanvas();
progress.showCanvas(); progress.showCanvas();
@@ -109,6 +105,13 @@ const updateStatus = (value) => {
// calculate first enabled display // calculate first enabled display
const firstDisplayIndex = displays.findIndex((display) => display.enabled && display.timing.totalScreens > 0); const firstDisplayIndex = displays.findIndex((display) => display.enabled && display.timing.totalScreens > 0);
// value.id = 0 is hazards, if they fail to load hot-wire a new value.id to the current display to see if it needs to be loaded
// typically this plays out as current conditions loads, then hazards fails.
if (value.id === 0 && (value.status === STATUS.failed || value.status === STATUS.retrying)) {
value.id = firstDisplayIndex;
value.status = displays[firstDisplayIndex].status;
}
// if this is the first display and we're playing, load it up so it starts playing // if this is the first display and we're playing, load it up so it starts playing
if (isPlaying() && value.id === firstDisplayIndex && value.status === STATUS.loaded) { if (isPlaying() && value.id === firstDisplayIndex && value.status === STATUS.loaded) {
navTo(msg.command.firstFrame); navTo(msg.command.firstFrame);
@@ -175,6 +178,7 @@ const navTo = (direction) => {
if (!firstDisplay) return; if (!firstDisplay) return;
firstDisplay.navNext(msg.command.firstFrame); firstDisplay.navNext(msg.command.firstFrame);
firstDisplay.showCanvas();
return; return;
} }
if (direction === msg.command.nextFrame) currentDisplay().navNext(); if (direction === msg.command.nextFrame) currentDisplay().navNext();
@@ -192,9 +196,7 @@ const loadDisplay = (direction) => {
if (displays[idx].status === STATUS.loaded && displays[idx].timing.totalScreens > 0) break; if (displays[idx].status === STATUS.loaded && displays[idx].timing.totalScreens > 0) break;
} }
// if new display index is less than current display a wrap occurred, test for reload timeout // if new display index is less than current display a wrap occurred, test for reload timeout
if (idx <= curIdx) { if (idx <= curIdx && refreshCheck()) return;
if (refreshCheck()) return;
}
const newDisplay = displays[idx]; const newDisplay = displays[idx];
// hide all displays // hide all displays
hideAllCanvases(); hideAllCanvases();
@@ -204,25 +206,22 @@ const loadDisplay = (direction) => {
}; };
// get the current display index or value // get the current display index or value
const currentDisplayIndex = () => { const currentDisplayIndex = () => displays.findIndex((display) => display.active);
const index = displays.findIndex((display) => display.active);
return index;
};
const currentDisplay = () => displays[currentDisplayIndex()]; const currentDisplay = () => displays[currentDisplayIndex()];
const setPlaying = (newValue) => { const setPlaying = (newValue) => {
playing = newValue; playing = newValue;
const playButton = document.getElementById('NavigatePlay'); const playButton = document.querySelector('#NavigatePlay');
localStorage.setItem('play', playing); localStorage.setItem('play', playing);
if (playing) { if (playing) {
noSleep(true); noSleep(true);
playButton.title = 'Pause'; playButton.title = 'Pause';
playButton.src = 'images/nav/ic_pause_white_24dp_1x.png'; playButton.src = 'images/nav/ic_pause_white_24dp_2x.png';
} else { } else {
noSleep(false); noSleep(false);
playButton.title = 'Play'; playButton.title = 'Play';
playButton.src = 'images/nav/ic_play_arrow_white_24dp_1x.png'; playButton.src = 'images/nav/ic_play_arrow_white_24dp_2x.png';
} }
// if we're playing and on the progress screen jump to the next screen // if we're playing and on the progress screen jump to the next screen
if (!progress) return; if (!progress) return;
@@ -264,14 +263,14 @@ const getDisplay = (index) => displays[index];
// resize the container on a page resize // resize the container on a page resize
const resize = () => { const resize = () => {
const widthZoomPercent = (document.getElementById('divTwcBottom').getBoundingClientRect().width) / 640; const widthZoomPercent = (document.querySelector('#divTwcBottom').getBoundingClientRect().width) / 640;
const heightZoomPercent = (window.innerHeight) / 480; const heightZoomPercent = (window.innerHeight) / 480;
const scale = Math.min(widthZoomPercent, heightZoomPercent); const scale = Math.min(widthZoomPercent, heightZoomPercent);
if (scale < 1.0 || document.fullscreenElement) { if (scale < 1.0 || document.fullscreenElement) {
document.getElementById('container').style.zoom = scale; document.querySelector('#container').style.zoom = scale;
} else { } else {
document.getElementById('container').style.zoom = 1; document.querySelector('#container').style.zoom = 1;
} }
}; };
@@ -289,7 +288,7 @@ const registerDisplay = (display) => {
}; };
const generateCheckboxes = () => { const generateCheckboxes = () => {
const availableDisplays = document.getElementById('enabledDisplays'); const availableDisplays = document.querySelector('#enabledDisplays');
if (!availableDisplays) return; if (!availableDisplays) return;
// generate checkboxes // generate checkboxes
@@ -306,11 +305,11 @@ const registerProgress = (_progress) => {
}; };
const populateWeatherParameters = (params) => { const populateWeatherParameters = (params) => {
document.getElementById('spanCity').innerHTML = `${params.city}, `; document.querySelector('#spanCity').innerHTML = `${params.city}, `;
document.getElementById('spanState').innerHTML = params.state; document.querySelector('#spanState').innerHTML = params.state;
document.getElementById('spanStationId').innerHTML = params.stationId; document.querySelector('#spanStationId').innerHTML = params.stationId;
document.getElementById('spanRadarId').innerHTML = params.radarId; document.querySelector('#spanRadarId').innerHTML = params.radarId;
document.getElementById('spanZoneId').innerHTML = params.zoneId; document.querySelector('#spanZoneId').innerHTML = params.zoneId;
}; };
const autoRefreshChange = (e) => { const autoRefreshChange = (e) => {
@@ -327,12 +326,12 @@ const autoRefreshChange = (e) => {
const AssignLastUpdate = (date) => { const AssignLastUpdate = (date) => {
if (date) { if (date) {
document.getElementById('spanLastRefresh').innerHTML = date.toLocaleString('en-US', { document.querySelector('#spanLastRefresh').innerHTML = date.toLocaleString('en-US', {
weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', timeZoneName: 'short', weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', timeZoneName: 'short',
}); });
if (document.getElementById('chkAutoRefresh').checked) startAutoRefreshTimer(); if (document.querySelector(CHK_AUTO_REFRESH_SELECTOR).checked) startAutoRefreshTimer();
} else { } else {
document.getElementById('spanLastRefresh').innerHTML = '(none)'; document.querySelector('#spanLastRefresh').innerHTML = '(none)';
} }
}; };
@@ -359,7 +358,7 @@ const startAutoRefreshTimer = () => {
RemainingMs = 0; RemainingMs = 0;
} }
const dt = new Date(RemainingMs); 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()}`; document.querySelector('#spanRefreshCountDown').innerHTML = `${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}`;
// Time has elapsed. // Time has elapsed.
if (AutoRefreshCountMs >= AUTO_REFRESH_TIME_MS && !isPlaying()) loadTwcData(); if (AutoRefreshCountMs >= AUTO_REFRESH_TIME_MS && !isPlaying()) loadTwcData();
@@ -370,14 +369,14 @@ const startAutoRefreshTimer = () => {
const stopAutoRefreshTimer = () => { const stopAutoRefreshTimer = () => {
if (AutoRefreshIntervalId) { if (AutoRefreshIntervalId) {
window.clearInterval(AutoRefreshIntervalId); window.clearInterval(AutoRefreshIntervalId);
document.getElementById('spanRefreshCountDown').innerHTML = '--:--'; document.querySelector('#spanRefreshCountDown').innerHTML = '--:--';
AutoRefreshIntervalId = null; AutoRefreshIntervalId = null;
} }
}; };
const refreshCheck = () => { const refreshCheck = () => {
// Time has elapsed. // Time has elapsed.
if (AutoRefreshCountMs >= AUTO_REFRESH_TIME_MS) { if (AutoRefreshCountMs >= AUTO_REFRESH_TIME_MS && isPlaying()) {
loadTwcData(); loadTwcData();
return true; return true;
} }
@@ -392,6 +391,8 @@ const registerRefreshData = (callback) => {
loadTwcData.callback = callback; loadTwcData.callback = callback;
}; };
const timeZone = () => weatherParameters.timeZone;
export { export {
updateStatus, updateStatus,
displayNavMessage, displayNavMessage,
@@ -407,4 +408,5 @@ export {
latLonReceived, latLonReceived,
stopAutoRefreshTimer, stopAutoRefreshTimer,
registerRefreshData, registerRefreshData,
timeZone,
}; };

View File

@@ -18,7 +18,7 @@ class Progress extends WeatherDisplay {
// setup event listener for dom-required initialization // setup event listener for dom-required initialization
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
this.version = document.getElementById('version').innerHTML; this.version = document.querySelector('#version').innerHTML;
this.elem.querySelector('.container').addEventListener('click', this.lineClick.bind(this)); this.elem.querySelector('.container').addEventListener('click', this.lineClick.bind(this));
}); });
@@ -36,9 +36,9 @@ class Progress extends WeatherDisplay {
if (!displays) return; if (!displays) return;
const lines = displays.map((display, index) => { const lines = displays.map((display, index) => {
if (display.showOnProgress === false) return false; if (display.showOnProgress === false) return false;
const fill = {}; const fill = {
name: display.name,
fill.name = display.name; };
const statusClass = calcStatusClass(display.status); const statusClass = calcStatusClass(display.status);

View File

@@ -5,7 +5,7 @@ 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'; import { registerDisplay, timeZone } from './navigation.mjs';
import * as utils from './radar-utils.mjs'; import * as utils from './radar-utils.mjs';
class Radar extends WeatherDisplay { class Radar extends WeatherDisplay {
@@ -71,25 +71,24 @@ class Radar extends WeatherDisplay {
const lists = (await Promise.all(baseUrls.map(async (url) => { const lists = (await Promise.all(baseUrls.map(async (url) => {
try { try {
// get a list of available radars // get a list of available radars
const radarHtml = await text(url, { cors: true }); return text(url, { cors: true });
return radarHtml; } catch (error) {
} catch (e) {
console.log('Unable to get list of radars'); console.log('Unable to get list of radars');
console.error(e); console.error(error);
this.setStatus(STATUS.failed); this.setStatus(STATUS.failed);
return false; return false;
} }
}))).filter((d) => d); }))).filter((d) => d);
// convert to an array of gif urls // convert to an array of gif urls
const pngs = lists.map((html, htmlIdx) => { const pngs = lists.flatMap((html, htmlIdx) => {
const parser = new DOMParser(); const parser = new DOMParser();
const xmlDoc = parser.parseFromString(html, 'text/html'); const xmlDoc = parser.parseFromString(html, 'text/html');
// add the base url // add the base url
const base = xmlDoc.createElement('base'); const base = xmlDoc.createElement('base');
base.href = baseUrls[htmlIdx]; base.href = baseUrls[htmlIdx];
xmlDoc.head.append(base); xmlDoc.head.append(base);
const anchors = xmlDoc.getElementsByTagName('a'); const anchors = xmlDoc.querySelectorAll('a');
const urls = []; const urls = [];
Array.from(anchors).forEach((elem) => { Array.from(anchors).forEach((elem) => {
if (elem.innerHTML?.includes('.png') && elem.innerHTML?.includes('n0r_')) { if (elem.innerHTML?.includes('.png') && elem.innerHTML?.includes('n0r_')) {
@@ -97,7 +96,7 @@ class Radar extends WeatherDisplay {
} }
}); });
return urls; return urls;
}).flat(); });
// get the last few images // get the last few images
const sortedPngs = pngs.sort((a, b) => (Date(a) < Date(b) ? -1 : 1)); const sortedPngs = pngs.sort((a, b) => (Date(a) < Date(b) ? -1 : 1));
@@ -159,7 +158,7 @@ class Radar extends WeatherDisplay {
zone: 'UTC', zone: 'UTC',
}).setZone(); }).setZone();
} else { } else {
time = DateTime.fromHTTP(response.headers.get('last-modified')).setZone(); time = DateTime.fromHTTP(response.headers.get('last-modified')).setZone(timeZone());
} }
// assign to an html image element // assign to an html image element
@@ -214,8 +213,12 @@ class Radar extends WeatherDisplay {
const timePadded = time.length >= 8 ? time : `&nbsp;${time}`; const timePadded = time.length >= 8 ? time : `&nbsp;${time}`;
this.elem.querySelector('.header .right .time').innerHTML = timePadded; this.elem.querySelector('.header .right .time').innerHTML = timePadded;
// get image offset calculation
// is slides slightly because of scaling so we have to take a measurement from the rendered page
const actualFrameHeight = this.elem.querySelector('.frame').getBoundingClientRect().height;
// scroll to image // scroll to image
this.elem.querySelector('.scroll-area').style.top = `${-this.screenIndex * 371}px`; this.elem.querySelector('.scroll-area').style.top = `${-this.screenIndex * actualFrameHeight}px`;
this.finishDraw(); this.finishDraw();
} }

View File

@@ -23,12 +23,14 @@ const getRegionalObservation = async (point, city) => {
const observation = await json(`${station}/observations/latest`); const observation = await json(`${station}/observations/latest`);
// preload the image // preload the image
if (!observation.properties.icon) return false; if (!observation.properties.icon) return false;
preloadImg(getWeatherRegionalIconFromIconLink(observation.properties.icon, !observation.properties.daytime)); const icon = getWeatherRegionalIconFromIconLink(observation.properties.icon, !observation.properties.daytime);
if (!icon) return false;
preloadImg(icon);
// return the observation // return the observation
return observation.properties; return observation.properties;
} catch (e) { } catch (error) {
console.log(`Unable to get regional observations for ${city.Name ?? city.city}`); console.log(`Unable to get regional observations for ${city.Name ?? city.city}`);
console.error(e.status, e.responseJSON); console.error(error.status, error.responseJSON);
return false; return false;
} }
}; };
@@ -193,7 +195,7 @@ const getXYForCityHI = (City, MaxLatitude, MinLongitude) => {
}; };
// to fit on the map, remove anything after punctuation and then limit to 15 characters // to fit on the map, remove anything after punctuation and then limit to 15 characters
const formatCity = (city) => city.match(/[^-;/\\,]*/)[0].substr(0, 12); const formatCity = (city) => city.match(/[^,/;\\-]*/)[0].substr(0, 12);
export { export {
buildForecast, buildForecast,

View File

@@ -87,9 +87,12 @@ class RegionalForecast extends WeatherDisplay {
// wait for the regional observation if it's not done yet // wait for the regional observation if it's not done yet
const observation = await observationPromise; const observation = await observationPromise;
if (!observation) return false;
// 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: !!/\/day\//.test(observation.icon),
temperature: celsiusToFahrenheit(observation.temperature.value), temperature: celsiusToFahrenheit(observation.temperature.value),
name: utils.formatCity(city.city), name: utils.formatCity(city.city),
icon: observation.icon, icon: observation.icon,
@@ -110,9 +113,9 @@ class RegionalForecast extends WeatherDisplay {
utils.buildForecast(forecast.properties.periods[1], city, cityXY), utils.buildForecast(forecast.properties.periods[1], city, cityXY),
utils.buildForecast(forecast.properties.periods[2], city, cityXY), utils.buildForecast(forecast.properties.periods[2], city, cityXY),
]; ];
} catch (e) { } catch (error) {
console.log(`No regional forecast data for '${city.name ?? city.city}'`); console.log(`No regional forecast data for '${city.name ?? city.city}'`);
console.log(e); console.log(error);
return false; return false;
} }
})); }));
@@ -156,11 +159,9 @@ class RegionalForecast extends WeatherDisplay {
const dayName = forecastDate.toLocaleString({ weekday: 'long' }); const dayName = forecastDate.toLocaleString({ weekday: 'long' });
titleTop.innerHTML = 'Forecast for'; titleTop.innerHTML = 'Forecast for';
// draw the title // draw the title
if (data[0][this.screenIndex].daytime) { titleBottom.innerHTML = data[0][this.screenIndex].daytime
titleBottom.innerHTML = dayName; ? dayName
} else { : `${dayName} Night`;
titleBottom.innerHTML = `${dayName} Night`;
}
} }
// draw the map // draw the map
@@ -179,9 +180,11 @@ class RegionalForecast extends WeatherDisplay {
const { temperature } = period; const { temperature } = period;
fill.temp = temperature; fill.temp = temperature;
const { x, y } = period;
const elem = this.fillTemplate('location', fill); const elem = this.fillTemplate('location', fill);
elem.style.left = `${period.x}px`; elem.style.left = `${x}px`;
elem.style.top = `${period.y}px`; elem.style.top = `${y}px`;
return elem; return elem;
}); });

View File

@@ -45,9 +45,9 @@ class TravelForecast extends WeatherDisplay {
name: city.Name, name: city.Name,
icon: getWeatherRegionalIconFromIconLink(forecast.properties.periods[todayShift].icon), icon: getWeatherRegionalIconFromIconLink(forecast.properties.periods[todayShift].icon),
}; };
} catch (e) { } catch (error) {
console.error(`GetTravelWeather for ${city.Name} failed`); console.error(`GetTravelWeather for ${city.Name} failed`);
console.error(e.status, e.responseJSON); console.error(error.status, error.responseJSON);
return { name: city.Name, error: true }; return { name: city.Name, error: true };
} }
}); });
@@ -57,7 +57,7 @@ class TravelForecast extends WeatherDisplay {
this.data = forecasts; this.data = forecasts;
// test for some data available in at least one forecast // test for some data available in at least one forecast
const hasData = this.data.reduce((acc, forecast) => acc || forecast.high, false); const hasData = this.data.some((forecast) => forecast.high);
if (!hasData) { if (!hasData) {
this.setStatus(STATUS.noData); this.setStatus(STATUS.noData);
return; return;
@@ -77,10 +77,9 @@ class TravelForecast extends WeatherDisplay {
const lines = cities.map((city) => { const lines = cities.map((city) => {
if (city.error) return false; if (city.error) return false;
const fillValues = {}; const fillValues = {
city,
// city name };
fillValues.city = city;
// check for forecast data // check for forecast data
if (city.icon) { if (city.icon) {
@@ -94,8 +93,9 @@ class TravelForecast extends WeatherDisplay {
fillValues.low = lowString; fillValues.low = lowString;
fillValues.high = highString; fillValues.high = highString;
const { icon } = city;
fillValues.icon = { type: 'img', src: city.icon }; fillValues.icon = { type: 'img', src: icon };
} else { } else {
fillValues.error = 'NO TRAVEL DATA AVAILABLE'; fillValues.error = 'NO TRAVEL DATA AVAILABLE';
} }

View File

@@ -21,7 +21,7 @@ const fetchAsync = async (_url, responseType, _params = {}) => {
if (params.cors === true) corsUrl = rewriteUrl(_url); if (params.cors === true) corsUrl = rewriteUrl(_url);
const url = new URL(corsUrl, `${window.location.origin}/`); const url = new URL(corsUrl, `${window.location.origin}/`);
// match the security protocol when not on localhost // match the security protocol when not on localhost
url.protocol = window.location.hostname !== 'localhost' ? window.location.protocol : url.protocol; url.protocol = window.location.hostname === 'localhost' ? url.protocol : window.location.protocol;
// add parameters if necessary // add parameters if necessary
if (params.data) { if (params.data) {
Object.keys(params.data).forEach((key) => { Object.keys(params.data).forEach((key) => {
@@ -73,7 +73,7 @@ const doFetch = (url, params) => new Promise((resolve, reject) => {
// out of retries // out of retries
return resolve(response); return resolve(response);
}) })
.catch((e) => reject(e)); .catch((error) => reject(error));
}); });
const delay = (time, func, ...args) => new Promise((resolve) => { const delay = (time, func, ...args) => new Promise((resolve) => {
@@ -87,8 +87,8 @@ const retryDelay = (retryNumber) => {
case 1: return 1000; case 1: return 1000;
case 2: return 2000; case 2: return 2000;
case 3: return 5000; case 3: return 5000;
case 4: return 10000; case 4: return 10_000;
default: return 30000; default: return 30_000;
} }
}; };

View File

@@ -2,11 +2,11 @@ const locationCleanup = (input) => {
// regexes to run // regexes to run
const regexes = [ const regexes = [
// "Chicago / West Chicago", removes before slash // "Chicago / West Chicago", removes before slash
/^[A-Za-z ]+ \/ /, /^[ A-Za-z]+ \/ /,
// "Chicago/Waukegan" removes before slash // "Chicago/Waukegan" removes before slash
/^[A-Za-z ]+\//, /^[ A-Za-z]+\//,
// "Chicago, Chicago O'hare" removes before comma // "Chicago, Chicago O'hare" removes before comma
/^[A-Za-z ]+, /, /^[ A-Za-z]+, /,
]; ];
// run all regexes // run all regexes

View File

@@ -2,11 +2,11 @@
const round2 = (value, decimals) => Math.trunc(value * 10 ** decimals) / 10 ** decimals; const round2 = (value, decimals) => Math.trunc(value * 10 ** decimals) / 10 ** decimals;
const kphToMph = (Kph) => Math.round(Kph / 1.60934); const kphToMph = (Kph) => Math.round(Kph / 1.609_34);
const celsiusToFahrenheit = (Celsius) => Math.round((Celsius * 9) / 5 + 32); const celsiusToFahrenheit = (Celsius) => Math.round((Celsius * 9) / 5 + 32);
const kilometersToMiles = (Kilometers) => Math.round(Kilometers / 1.60934); const kilometersToMiles = (Kilometers) => Math.round(Kilometers / 1.609_34);
const metersToFeet = (Meters) => Math.round(Meters / 0.3048); const metersToFeet = (Meters) => Math.round(Meters / 0.3048);
const pascalToInHg = (Pascal) => round2(Pascal * 0.0002953, 2); const pascalToInHg = (Pascal) => round2(Pascal * 0.000_295_3, 2);
export { export {
kphToMph, kphToMph,

View File

@@ -3,9 +3,9 @@ import { json } from './fetch.mjs';
const getPoint = async (lat, lon) => { const getPoint = async (lat, lon) => {
try { try {
return await json(`https://api.weather.gov/points/${lat},${lon}`); return await json(`https://api.weather.gov/points/${lat},${lon}`);
} catch (e) { } catch (error) {
console.log(`Unable to get point ${lat}, ${lon}`); console.log(`Unable to get point ${lat}, ${lon}`);
console.error(e); console.error(error);
return false; return false;
} }
}; };

View File

@@ -2,9 +2,8 @@
import STATUS, { calcStatusClass, statusClasses } from './status.mjs'; import STATUS, { calcStatusClass, statusClasses } from './status.mjs';
import { DateTime } from '../vendor/auto/luxon.mjs'; import { DateTime } from '../vendor/auto/luxon.mjs';
import { elemForEach } from './utils/elem.mjs';
import { import {
msg, displayNavMessage, isPlaying, updateStatus, msg, displayNavMessage, isPlaying, updateStatus, timeZone,
} from './navigation.mjs'; } from './navigation.mjs';
class WeatherDisplay { class WeatherDisplay {
@@ -54,11 +53,7 @@ class WeatherDisplay {
// get the saved status of the checkbox // get the saved status of the checkbox
let savedStatus = window.localStorage.getItem(`display-enabled: ${this.elemId}`); let savedStatus = window.localStorage.getItem(`display-enabled: ${this.elemId}`);
if (savedStatus === null) savedStatus = defaultEnabled; if (savedStatus === null) savedStatus = defaultEnabled;
if (savedStatus === 'true' || savedStatus === true) { this.isEnabled = !!((savedStatus === 'true' || savedStatus === true));
this.isEnabled = true;
} else {
this.isEnabled = false;
}
// refresh (or initially store the state of the checkbox) // refresh (or initially store the state of the checkbox)
window.localStorage.setItem(`display-enabled: ${this.elemId}`, this.isEnabled); window.localStorage.setItem(`display-enabled: ${this.elemId}`, this.isEnabled);
@@ -173,20 +168,22 @@ class WeatherDisplay {
// only draw if canvas is active to conserve battery // only draw if canvas is active to conserve battery
if (!this.active) return; if (!this.active) return;
// Get the current date and time. // Get the current date and time.
const now = DateTime.local(); const now = DateTime.local().setZone(timeZone());
// time = "11:35:08 PM"; // time = "11:35:08 PM";
const time = now.toLocaleString(DateTime.TIME_WITH_SECONDS).padStart(11, ' '); const time = now.toLocaleString(DateTime.TIME_WITH_SECONDS).padStart(11, ' ');
const date = now.toFormat(' ccc LLL ') + now.day.toString().padStart(2, ' ');
if (this.lastTime !== time) { const dateElem = this.elem.querySelector('.date-time.date');
elemForEach('.date-time.time', (elem) => { elem.innerHTML = time.toUpperCase(); }); const timeElem = this.elem.querySelector('.date-time.time');
if (timeElem && this.lastTime !== time) {
timeElem.innerHTML = time.toUpperCase();
} }
this.lastTime = time; this.lastTime = time;
const date = now.toFormat(' ccc LLL ') + now.day.toString().padStart(2, ' '); if (dateElem && this.lastDate !== date) {
dateElem.innerHTML = date.toUpperCase();
if (this.lastDate !== date) {
elemForEach('.date-time.date', (elem) => { elem.innerHTML = date.toUpperCase(); });
} }
this.lastDate = date; this.lastDate = date;
} }
@@ -252,17 +249,13 @@ class WeatherDisplay {
if (nextScreenIndex === this.screenIndex) return; if (nextScreenIndex === this.screenIndex) return;
// test for -1 (no screen displayed yet) // test for -1 (no screen displayed yet)
if (nextScreenIndex === -1) { this.screenIndex = nextScreenIndex === -1 ? 0 : nextScreenIndex;
this.screenIndex = 0;
} else {
this.screenIndex = nextScreenIndex;
}
// call the appropriate screen index change method // call the appropriate screen index change method
if (!this.screenIndexChange) { if (this.screenIndexChange) {
await this.drawCanvas();
} else {
this.screenIndexChange(this.screenIndex); this.screenIndexChange(this.screenIndex);
} else {
await this.drawCanvas();
} }
this.showCanvas(); this.showCanvas();
} }
@@ -376,7 +369,7 @@ class WeatherDisplay {
loadTemplates() { loadTemplates() {
this.templates = {}; this.templates = {};
this.elem = document.getElementById(`${this.elemId}-html`); this.elem = document.querySelector(`#${this.elemId}-html`);
if (!this.elem) return; if (!this.elem) return;
const templates = this.elem.querySelectorAll('.template'); const templates = this.elem.querySelectorAll('.template');
templates.forEach((template) => { templates.forEach((template) => {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -20,72 +20,83 @@ body {
} }
} }
input, #divQuery {
button { max-width: 640px;
font-family: "Star4000";
}
#imgGetGps { .buttons {
height: 13px; display: inline-block;
vertical-align: middle; width: 150px;
} text-align: right;
#txtAddress { #imgGetGps {
width: 490px; height: 13px;
font-size: 16pt; vertical-align: middle;
max-width: calc(100% - 8px);
@media (prefers-color-scheme: dark) {
background-color: #000000;
color: white;
border: 1px solid darkgray;
}
}
#btnGetGps,
#btnGetLatLng,
#btnClearQuery {
font-size: 16pt;
@media (prefers-color-scheme: dark) {
background-color: #000000;
color: white;
}
border: 1px solid darkgray;
}
#btnGetGps {
img {
&.dark {
display: none;
@media (prefers-color-scheme: dark) {
display: inline-block;
}
} }
&.light { button {
font-size: 16pt;
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
display: none; background-color: #000000;
color: white;
}
border: 1px solid darkgray;
}
#btnGetGps {
img {
&.dark {
display: none;
@media (prefers-color-scheme: dark) {
display: inline-block;
}
}
&.light {
@media (prefers-color-scheme: dark) {
display: none;
}
}
}
&.active {
background-color: black;
@media (prefers-color-scheme: dark) {
background-color: white;
}
img {
filter: invert(1);
}
} }
} }
} }
&.active { input,
background-color: black; button {
font-family: "Star4000";
}
#txtAddress {
width: calc(100% - 170px);
max-width: 490px;
font-size: 16pt;
min-width: 200px;
display: inline-block;
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
background-color: white; background-color: #000000;
} color: white;
border: 1px solid darkgray;
img {
filter: invert(1);
} }
} }
}
}
.autocomplete-suggestions { .autocomplete-suggestions {
background-color: #ffffff; background-color: #ffffff;
border: 1px solid #000000; border: 1px solid #000000;
@@ -93,19 +104,19 @@ button {
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
background-color: #000000; background-color: #000000;
} }
}
.autocomplete-suggestion { .autocomplete-suggestion {
/*padding: 2px 5px;*/ /*padding: 2px 5px;*/
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
font-size: 16pt; font-size: 16pt;
} }
.autocomplete-selected { .autocomplete-selected {
background-color: #0000ff; background-color: #0000ff;
color: #ffffff; color: #ffffff;
}
} }
#divTwc { #divTwc {
@@ -213,8 +224,7 @@ button {
text-align: right; text-align: right;
} }
#imgPause1x, #imgPause1x {
#imgPause2x {
visibility: hidden; visibility: hidden;
position: absolute; position: absolute;
} }
@@ -322,6 +332,31 @@ button {
color: white; color: white;
} }
@media (prefers-color-scheme: light) {
.loading,
.retrying {
color: hsl(60, 100%, 30%);
}
.press-here {
color: black;
cursor: pointer;
}
.failed {
color: hsl(0, 100%, 30%);
}
.no-data {
color: hsl(0, 0%, 30%);
}
.disabled {
color: hsl(0, 0%, 30%);
}
}
label { label {
display: block; display: block;
max-width: 300px; max-width: 300px;
@@ -338,7 +373,7 @@ button {
} }
#divTwcBottom img { #divTwcBottom img {
zoom: 150%; zoom: 75%;
} }
#divTwc:fullscreen { #divTwc:fullscreen {

View File

@@ -60,19 +60,15 @@
<div id="divQuery"> <div id="divQuery">
<form id="frmGetLatLng"> <input id="txtAddress" type="text" value="" placeholder="Zip or City, State" />
<input id="txtAddress" type="text" value="" placeholder="Zip or City, State" /><button id="btnGetGps" type="button" title="Get GPS Location"><img src="images/nav/ic_gps_fixed_black_18dp_1x.png" class="light"/><img src="images/nav/ic_gps_fixed_white_18dp_1x.png" class="dark"/></button> <div class="buttons">
<input id="btnGetLatLng" type="submit" value="GO" /> <button id="btnGetGps" type="button" title="Get GPS Location"><img src="images/nav/ic_gps_fixed_black_18dp_1x.png" class="light"/>
<input id="btnClearQuery" type="reset" value="Reset" /> <img src="images/nav/ic_gps_fixed_white_18dp_1x.png" class="dark"/>
</form> </button>
<div id="divLat"></div> <button id="btnGetLatLng" type="submit">GO</button>
<div id="divLng"></div> <button id="btnClearQuery" type="reset">Reset</button>
</div>
</div> </div>
<br />
<img id="imgPause1x" src="images/nav/ic_pause_white_24dp_1x.png" />
<img id="imgPause2x" src="images/nav/ic_pause_white_24dp_2x.png" />
<div id="version" style="display:none"> <div id="version" style="display:none">
<%- version %> <%- version %>
</div> </div>
@@ -125,16 +121,16 @@
</div> </div>
<div id="divTwcBottom"> <div id="divTwcBottom">
<div id="divTwcBottomLeft"> <div id="divTwcBottomLeft">
<img id="NavigateMenu" class="navButton" src="images/nav/ic_menu_white_24dp_1x.png" title="Menu" /> <img id="NavigateMenu" class="navButton" src="images/nav/ic_menu_white_24dp_2x.png" title="Menu" />
<img id="NavigatePrevious" class="navButton" src="images/nav/ic_skip_previous_white_24dp_1x.png" title="Previous" /> <img id="NavigatePrevious" class="navButton" src="images/nav/ic_skip_previous_white_24dp_2x.png" title="Previous" />
<img id="NavigateNext" class="navButton" src="images/nav/ic_skip_next_white_24dp_1x.png" title="Next" /> <img id="NavigateNext" class="navButton" src="images/nav/ic_skip_next_white_24dp_2x.png" title="Next" />
<img id="NavigatePlay" class="navButton" src="images/nav/ic_play_arrow_white_24dp_1x.png" title="Play" /> <img id="NavigatePlay" class="navButton" src="images/nav/ic_play_arrow_white_24dp_2x.png" title="Play" />
</div> </div>
<div id="divTwcBottomMiddle"> <div id="divTwcBottomMiddle">
<img id="NavigateRefresh" class="navButton" src="images/nav/ic_refresh_white_24dp_1x.png" title="Refresh" /> <img id="NavigateRefresh" class="navButton" src="images/nav/ic_refresh_white_24dp_2x.png" title="Refresh" />
</div> </div>
<div id="divTwcBottomRight"> <div id="divTwcBottomRight">
<img id="ToggleFullScreen" class="navButton" src="images/nav/ic_fullscreen_white_24dp_1x.png" title="Enter Fullscreen" /> <img id="ToggleFullScreen" class="navButton" src="images/nav/ic_fullscreen_white_24dp_2x.png" title="Enter Fullscreen" />
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,4 +1,4 @@
<%- include('header.ejs', {titleDual:{ top: 'Current' , bottom: 'Conditions' }, noaaLogo: true}) %> <%- include('header.ejs', {titleDual:{ top: 'Current' , bottom: 'Conditions' }, noaaLogo: true, hasTime: true}) %>
<div class="main has-scroll has-box current-weather"> <div class="main has-scroll has-box current-weather">
<div class="weather template"> <div class="weather template">
<div class="left col"> <div class="left col">

View File

@@ -1,4 +1,4 @@
<%- include('header.ejs', {titleDual:{ top: 'Latest' , bottom: 'Observations' }, noaaLogo: true }) %> <%- include('header.ejs', {titleDual:{ top: 'Latest' , bottom: 'Observations' }, noaaLogo: true, hasTime: true }) %>
<div class="main has-scroll latest-observations has-box"> <div class="main has-scroll latest-observations has-box">
<div class="container"> <div class="container">
<div class="column-headers"> <div class="column-headers">

View File

@@ -27,6 +27,7 @@
"Pngs", "Pngs",
"PRECIP", "PRECIP",
"rtrim", "rtrim",
"sonarjs",
"T", "T",
"T'storm", "T'storm",
"uscomp", "uscomp",