mirror of
https://github.com/netbymatt/ws4kp.git
synced 2026-04-17 17:19:30 -07:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3166dfad16 | ||
|
|
b6cceb5acf | ||
|
|
1303c55851 | ||
|
|
339d391110 | ||
|
|
b6e57e8a19 | ||
|
|
3743c45de6 | ||
|
|
b890b4e53d | ||
|
|
b07478f7ff | ||
|
|
8a25881d5b | ||
|
|
0743b9e2bb | ||
|
|
784c074e32 | ||
|
|
4840909098 | ||
|
|
03dfbc462b | ||
|
|
25291efff5 | ||
|
|
dc77ba835c | ||
|
|
a440990696 | ||
|
|
fc4cbc1415 | ||
|
|
20ba3ddaac |
27
.eslintrc.js
27
.eslintrc.js
@@ -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
14
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"type": "npm",
|
||||||
|
"script": "lint",
|
||||||
|
"problemMatcher": [
|
||||||
|
"$eslint-stylish"
|
||||||
|
],
|
||||||
|
"label": "npm: lint",
|
||||||
|
"detail": "eslint ./server/scripts/**"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
2
dist/index.html
vendored
File diff suppressed because one or more lines are too long
2
dist/resources/ws.min.css
vendored
2
dist/resources/ws.min.css
vendored
File diff suppressed because one or more lines are too long
2
dist/resources/ws.min.js
vendored
2
dist/resources/ws.min.js
vendored
File diff suppressed because one or more lines are too long
1254
package-lock.json
generated
1254
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ws4kp",
|
"name": "ws4kp",
|
||||||
"version": "5.9.3",
|
"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": {}
|
|
||||||
}
|
}
|
||||||
|
|||||||
6
server/scripts/data/.eslintrc.js
Normal file
6
server/scripts/data/.eslintrc.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
rules: {
|
||||||
|
// unicorn
|
||||||
|
'unicorn/numeric-separators-style': 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -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,7 +192,7 @@ 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_2x.png';
|
img.src = 'images/nav/ic_fullscreen_exit_white_24dp_2x.png';
|
||||||
img.title = 'Exit fullscreen';
|
img.title = 'Exit fullscreen';
|
||||||
};
|
};
|
||||||
@@ -210,7 +216,7 @@ 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_2x.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,8 +290,8 @@ 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);
|
||||||
};
|
};
|
||||||
@@ -352,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')) {
|
||||||
@@ -368,7 +375,7 @@ 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) => {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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') {
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ class Hazards extends WeatherDisplay {
|
|||||||
|
|
||||||
// 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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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':
|
||||||
@@ -155,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];
|
||||||
|
|
||||||
|
|||||||
@@ -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'));
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -200,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();
|
||||||
@@ -212,15 +206,12 @@ 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) {
|
||||||
@@ -272,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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -297,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
|
||||||
@@ -314,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) => {
|
||||||
@@ -335,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)';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -367,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();
|
||||||
@@ -378,7 +369,7 @@ 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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -400,6 +391,8 @@ const registerRefreshData = (callback) => {
|
|||||||
loadTwcData.callback = callback;
|
loadTwcData.callback = callback;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const timeZone = () => weatherParameters.timeZone;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
updateStatus,
|
updateStatus,
|
||||||
displayNavMessage,
|
displayNavMessage,
|
||||||
@@ -415,4 +408,5 @@ export {
|
|||||||
latLonReceived,
|
latLonReceived,
|
||||||
stopAutoRefreshTimer,
|
stopAutoRefreshTimer,
|
||||||
registerRefreshData,
|
registerRefreshData,
|
||||||
|
timeZone,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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 : ` ${time}`;
|
const timePadded = time.length >= 8 ? time : ` ${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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
@@ -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 {
|
||||||
|
|||||||
@@ -60,13 +60,14 @@
|
|||||||
|
|
||||||
|
|
||||||
<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>
|
||||||
<div id="version" style="display:none">
|
<div id="version" style="display:none">
|
||||||
<%- version %>
|
<%- version %>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"Pngs",
|
"Pngs",
|
||||||
"PRECIP",
|
"PRECIP",
|
||||||
"rtrim",
|
"rtrim",
|
||||||
|
"sonarjs",
|
||||||
"T",
|
"T",
|
||||||
"T'storm",
|
"T'storm",
|
||||||
"uscomp",
|
"uscomp",
|
||||||
|
|||||||
Reference in New Issue
Block a user