mirror of
https://github.com/netbymatt/ws4kp.git
synced 2026-04-17 17:19:30 -07:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4672d12d7 | ||
|
|
04ed3e0a52 | ||
|
|
58a337efbf | ||
|
|
249cbb93e6 | ||
|
|
888b35ea73 | ||
|
|
2a9e5b370e | ||
|
|
87d4155d71 | ||
|
|
3166dfad16 | ||
|
|
b6cceb5acf | ||
|
|
1303c55851 | ||
|
|
339d391110 | ||
|
|
b6e57e8a19 | ||
|
|
3743c45de6 | ||
|
|
b890b4e53d | ||
|
|
b07478f7ff | ||
|
|
8a25881d5b | ||
|
|
0743b9e2bb | ||
|
|
784c074e32 | ||
|
|
4840909098 | ||
|
|
03dfbc462b | ||
|
|
25291efff5 |
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',
|
||||||
|
|||||||
12
.vscode/launch.json
vendored
12
.vscode/launch.json
vendored
@@ -63,7 +63,17 @@
|
|||||||
"env": {
|
"env": {
|
||||||
"DIST": "1"
|
"DIST": "1"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"name": "Test",
|
||||||
|
"program": "${workspaceFolder}/tests/index.js",
|
||||||
|
"request": "launch",
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**"
|
||||||
|
],
|
||||||
|
"type": "node",
|
||||||
|
"outputCapture": "std"
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"compounds": [
|
"compounds": [
|
||||||
{
|
{
|
||||||
|
|||||||
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
12
index.js
12
index.js
@@ -31,17 +31,17 @@ const index = (req, res) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// debugging
|
// debugging
|
||||||
if (process.env?.DIST !== '1') {
|
if (process.env?.DIST === '1') {
|
||||||
// debugging
|
|
||||||
app.get('/index.html', index);
|
|
||||||
app.get('/', index);
|
|
||||||
app.get('*', express.static(path.join(__dirname, './server')));
|
|
||||||
} else {
|
|
||||||
// distribution
|
// distribution
|
||||||
app.use('/images', express.static(path.join(__dirname, './server/images')));
|
app.use('/images', express.static(path.join(__dirname, './server/images')));
|
||||||
app.use('/fonts', express.static(path.join(__dirname, './server/fonts')));
|
app.use('/fonts', express.static(path.join(__dirname, './server/fonts')));
|
||||||
app.use('/scripts', express.static(path.join(__dirname, './server/scripts')));
|
app.use('/scripts', express.static(path.join(__dirname, './server/scripts')));
|
||||||
app.use('/', express.static(path.join(__dirname, './dist')));
|
app.use('/', express.static(path.join(__dirname, './dist')));
|
||||||
|
} else {
|
||||||
|
// debugging
|
||||||
|
app.get('/index.html', index);
|
||||||
|
app.get('/', index);
|
||||||
|
app.get('*', express.static(path.join(__dirname, './server')));
|
||||||
}
|
}
|
||||||
|
|
||||||
const server = app.listen(port, () => {
|
const server = app.listen(port, () => {
|
||||||
|
|||||||
8309
package-lock.json
generated
8309
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "ws4kp",
|
"name": "ws4kp",
|
||||||
"version": "5.9.4",
|
"version": "5.9.8",
|
||||||
"description": "Welcome to the WeatherStar 4000+ project page!",
|
"description": "Welcome to the WeatherStar 4000+ project page!",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"build:css": "sass ./server/styles/scss/style.scss ./server/styles/compiled.css",
|
"build:css": "sass ./server/styles/scss/style.scss ./server/styles/compiled.css",
|
||||||
"lint": "eslint ./server/scripts/**",
|
"lint": "eslint ./server/scripts/**/*.mjs",
|
||||||
"lint:fix": "eslint --fix ./server/scripts/**"
|
"lint:fix": "eslint --fix ./server/scripts/**/*.mjs"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -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.19.0",
|
||||||
|
"eslint-plugin-unicorn": "^46.0.0",
|
||||||
"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,35 +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.getElementById('txtAddress').addEventListener('keydown', (key) => { if (key.code === 'Enter') formSubmit(); });
|
document.querySelector(TXT_ADDRESS_SELECTOR).addEventListener('keydown', (key) => { if (key.code === 'Enter') formSubmit(); });
|
||||||
document.getElementById('btnGetLatLng').addEventListener('click', () => 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(); });
|
||||||
|
|
||||||
$('#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',
|
||||||
@@ -75,7 +78,7 @@ const init = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const formSubmit = () => {
|
const formSubmit = () => {
|
||||||
const ac = $('#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;
|
||||||
};
|
};
|
||||||
@@ -85,7 +88,7 @@ const init = () => {
|
|||||||
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));
|
||||||
}
|
}
|
||||||
@@ -96,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');
|
||||||
@@ -112,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) => {
|
||||||
@@ -135,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.');
|
||||||
@@ -145,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
|
||||||
@@ -153,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()) {
|
||||||
@@ -171,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
|
||||||
@@ -189,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';
|
||||||
};
|
};
|
||||||
@@ -213,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';
|
||||||
};
|
};
|
||||||
@@ -232,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);
|
||||||
};
|
};
|
||||||
@@ -276,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);
|
||||||
@@ -286,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);
|
||||||
};
|
};
|
||||||
@@ -355,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')) {
|
||||||
@@ -371,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) => {
|
||||||
|
|||||||
@@ -30,8 +30,9 @@ class CurrentWeather extends WeatherDisplay {
|
|||||||
const filteredStations = weatherParameters.stations.filter((station) => station?.properties?.stationIdentifier?.length === 4 && !skipStations.includes(station.properties.stationIdentifier.slice(0, 1)));
|
const filteredStations = weatherParameters.stations.filter((station) => station?.properties?.stationIdentifier?.length === 4 && !skipStations.includes(station.properties.stationIdentifier.slice(0, 1)));
|
||||||
|
|
||||||
// Load the observations
|
// Load the observations
|
||||||
let observations; let
|
let observations;
|
||||||
station;
|
let station;
|
||||||
|
|
||||||
// station number counter
|
// station number counter
|
||||||
let stationNum = 0;
|
let stationNum = 0;
|
||||||
while (!observations && stationNum < filteredStations.length) {
|
while (!observations && stationNum < filteredStations.length) {
|
||||||
@@ -53,12 +54,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
|
||||||
@@ -71,7 +74,7 @@ class CurrentWeather extends WeatherDisplay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// we only get here if there was no error above
|
// we only get here if there was no error above
|
||||||
this.data = { ...observations, station };
|
this.data = parseData({ ...observations, station });
|
||||||
this.getDataCallback();
|
this.getDataCallback();
|
||||||
|
|
||||||
// stop here if we're disabled
|
// stop here if we're disabled
|
||||||
@@ -82,89 +85,37 @@ class CurrentWeather extends WeatherDisplay {
|
|||||||
this.setStatus(STATUS.loaded);
|
this.setStatus(STATUS.loaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
// format the data for use outside this function
|
|
||||||
parseData() {
|
|
||||||
if (!this.data) return false;
|
|
||||||
const data = {};
|
|
||||||
const observations = this.data.features[0].properties;
|
|
||||||
// values from api are provided in metric
|
|
||||||
data.observations = observations;
|
|
||||||
data.Temperature = Math.round(observations.temperature.value);
|
|
||||||
data.TemperatureUnit = 'C';
|
|
||||||
data.DewPoint = Math.round(observations.dewpoint.value);
|
|
||||||
data.Ceiling = Math.round(observations.cloudLayers[0]?.base?.value ?? 0);
|
|
||||||
data.CeilingUnit = 'm.';
|
|
||||||
data.Visibility = Math.round(observations.visibility.value / 1000);
|
|
||||||
data.VisibilityUnit = ' km.';
|
|
||||||
data.WindSpeed = Math.round(observations.windSpeed.value);
|
|
||||||
data.WindDirection = directionToNSEW(observations.windDirection.value);
|
|
||||||
data.Pressure = Math.round(observations.barometricPressure.value);
|
|
||||||
data.HeatIndex = Math.round(observations.heatIndex.value);
|
|
||||||
data.WindChill = Math.round(observations.windChill.value);
|
|
||||||
data.WindGust = Math.round(observations.windGust.value);
|
|
||||||
data.WindUnit = 'KPH';
|
|
||||||
data.Humidity = Math.round(observations.relativeHumidity.value);
|
|
||||||
data.Icon = getWeatherIconFromIconLink(observations.icon);
|
|
||||||
data.PressureDirection = '';
|
|
||||||
data.TextConditions = observations.textDescription;
|
|
||||||
data.station = this.data.station;
|
|
||||||
|
|
||||||
// difference since last measurement (pascals, looking for difference of more than 150)
|
|
||||||
const pressureDiff = (observations.barometricPressure.value - this.data.features[1].properties.barometricPressure.value);
|
|
||||||
if (pressureDiff > 150) data.PressureDirection = 'R';
|
|
||||||
if (pressureDiff < -150) data.PressureDirection = 'F';
|
|
||||||
|
|
||||||
data.Temperature = celsiusToFahrenheit(data.Temperature);
|
|
||||||
data.TemperatureUnit = 'F';
|
|
||||||
data.DewPoint = celsiusToFahrenheit(data.DewPoint);
|
|
||||||
data.Ceiling = Math.round(metersToFeet(data.Ceiling) / 100) * 100;
|
|
||||||
data.CeilingUnit = 'ft.';
|
|
||||||
data.Visibility = kilometersToMiles(observations.visibility.value / 1000);
|
|
||||||
data.VisibilityUnit = ' mi.';
|
|
||||||
data.WindSpeed = kphToMph(data.WindSpeed);
|
|
||||||
data.WindUnit = 'MPH';
|
|
||||||
data.Pressure = pascalToInHg(data.Pressure).toFixed(2);
|
|
||||||
data.HeatIndex = celsiusToFahrenheit(data.HeatIndex);
|
|
||||||
data.WindChill = celsiusToFahrenheit(data.WindChill);
|
|
||||||
data.WindGust = kphToMph(data.WindGust);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async drawCanvas() {
|
async drawCanvas() {
|
||||||
super.drawCanvas();
|
super.drawCanvas();
|
||||||
const fill = {};
|
|
||||||
// parse each time to deal with a change in units if necessary
|
|
||||||
const data = this.parseData();
|
|
||||||
|
|
||||||
fill.temp = data.Temperature + String.fromCharCode(176);
|
let condition = this.data.observations.textDescription;
|
||||||
|
if (condition.length > 15) {
|
||||||
let Conditions = data.observations.textDescription;
|
condition = shortConditions(condition);
|
||||||
if (Conditions.length > 15) {
|
|
||||||
Conditions = shortConditions(Conditions);
|
|
||||||
}
|
}
|
||||||
fill.condition = Conditions;
|
|
||||||
|
|
||||||
fill.wind = data.WindDirection.padEnd(3, '') + data.WindSpeed.toString().padStart(3, ' ');
|
const fill = {
|
||||||
if (data.WindGust) fill['wind-gusts'] = `Gusts to ${data.WindGust}`;
|
temp: this.data.Temperature + String.fromCharCode(176),
|
||||||
|
condition,
|
||||||
|
wind: this.data.WindDirection.padEnd(3, '') + this.data.WindSpeed.toString().padStart(3, ' '),
|
||||||
|
location: locationCleanup(this.data.station.properties.name).substr(0, 20),
|
||||||
|
humidity: `${this.data.Humidity}%`,
|
||||||
|
dewpoint: this.data.DewPoint + String.fromCharCode(176),
|
||||||
|
ceiling: (this.data.Ceiling === 0 ? 'Unlimited' : this.data.Ceiling + this.data.CeilingUnit),
|
||||||
|
visibility: this.data.Visibility + this.data.VisibilityUnit,
|
||||||
|
pressure: `${this.data.Pressure} ${this.data.PressureDirection}`,
|
||||||
|
icon: { type: 'img', src: this.data.Icon },
|
||||||
|
};
|
||||||
|
|
||||||
fill.location = locationCleanup(this.data.station.properties.name).substr(0, 20);
|
if (this.data.WindGust) fill['wind-gusts'] = `Gusts to ${this.data.WindGust}`;
|
||||||
|
|
||||||
fill.humidity = `${data.Humidity}%`;
|
if (this.data.observations.heatIndex.value && this.data.HeatIndex !== this.data.Temperature) {
|
||||||
fill.dewpoint = data.DewPoint + String.fromCharCode(176);
|
|
||||||
fill.ceiling = (data.Ceiling === 0 ? 'Unlimited' : data.Ceiling + data.CeilingUnit);
|
|
||||||
fill.visibility = data.Visibility + data.VisibilityUnit;
|
|
||||||
fill.pressure = `${data.Pressure} ${data.PressureDirection}`;
|
|
||||||
|
|
||||||
if (data.observations.heatIndex.value && data.HeatIndex !== data.Temperature) {
|
|
||||||
fill['heat-index-label'] = 'Heat Index:';
|
fill['heat-index-label'] = 'Heat Index:';
|
||||||
fill['heat-index'] = data.HeatIndex + String.fromCharCode(176);
|
fill['heat-index'] = this.data.HeatIndex + String.fromCharCode(176);
|
||||||
} else if (data.observations.windChill.value && data.WindChill !== '' && data.WindChill < data.Temperature) {
|
} else if (this.data.observations.windChill.value && this.data.WindChill !== '' && this.data.WindChill < this.data.Temperature) {
|
||||||
fill['heat-index-label'] = 'Wind Chill:';
|
fill['heat-index-label'] = 'Wind Chill:';
|
||||||
fill['heat-index'] = data.WindChill + String.fromCharCode(176);
|
fill['heat-index'] = this.data.WindChill + String.fromCharCode(176);
|
||||||
}
|
}
|
||||||
|
|
||||||
fill.icon = { type: 'img', src: data.Icon };
|
|
||||||
|
|
||||||
const area = this.elem.querySelector('.main');
|
const area = this.elem.querySelector('.main');
|
||||||
|
|
||||||
area.innerHTML = '';
|
area.innerHTML = '';
|
||||||
@@ -178,9 +129,9 @@ class CurrentWeather extends WeatherDisplay {
|
|||||||
async getCurrentWeather(stillWaiting) {
|
async getCurrentWeather(stillWaiting) {
|
||||||
if (stillWaiting) this.stillWaitingCallbacks.push(stillWaiting);
|
if (stillWaiting) this.stillWaitingCallbacks.push(stillWaiting);
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (this.data) resolve(this.parseData());
|
if (this.data) resolve(this.data);
|
||||||
// data not available, put it into the data callback queue
|
// data not available, put it into the data callback queue
|
||||||
this.getDataCallbacks.push(() => resolve(this.parseData()));
|
this.getDataCallbacks.push(() => resolve(this.data));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,6 +155,52 @@ const shortConditions = (_condition) => {
|
|||||||
return condition;
|
return condition;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// format the received data
|
||||||
|
const parseData = (data) => {
|
||||||
|
const observations = data.features[0].properties;
|
||||||
|
// values from api are provided in metric
|
||||||
|
data.observations = observations;
|
||||||
|
data.Temperature = Math.round(observations.temperature.value);
|
||||||
|
data.TemperatureUnit = 'C';
|
||||||
|
data.DewPoint = Math.round(observations.dewpoint.value);
|
||||||
|
data.Ceiling = Math.round(observations.cloudLayers[0]?.base?.value ?? 0);
|
||||||
|
data.CeilingUnit = 'm.';
|
||||||
|
data.Visibility = Math.round(observations.visibility.value / 1000);
|
||||||
|
data.VisibilityUnit = ' km.';
|
||||||
|
data.WindSpeed = Math.round(observations.windSpeed.value);
|
||||||
|
data.WindDirection = directionToNSEW(observations.windDirection.value);
|
||||||
|
data.Pressure = Math.round(observations.barometricPressure.value);
|
||||||
|
data.HeatIndex = Math.round(observations.heatIndex.value);
|
||||||
|
data.WindChill = Math.round(observations.windChill.value);
|
||||||
|
data.WindGust = Math.round(observations.windGust.value);
|
||||||
|
data.WindUnit = 'KPH';
|
||||||
|
data.Humidity = Math.round(observations.relativeHumidity.value);
|
||||||
|
data.Icon = getWeatherIconFromIconLink(observations.icon);
|
||||||
|
data.PressureDirection = '';
|
||||||
|
data.TextConditions = observations.textDescription;
|
||||||
|
|
||||||
|
// difference since last measurement (pascals, looking for difference of more than 150)
|
||||||
|
const pressureDiff = (observations.barometricPressure.value - data.features[1].properties.barometricPressure.value);
|
||||||
|
if (pressureDiff > 150) data.PressureDirection = 'R';
|
||||||
|
if (pressureDiff < -150) data.PressureDirection = 'F';
|
||||||
|
|
||||||
|
// convert to us units
|
||||||
|
data.Temperature = celsiusToFahrenheit(data.Temperature);
|
||||||
|
data.TemperatureUnit = 'F';
|
||||||
|
data.DewPoint = celsiusToFahrenheit(data.DewPoint);
|
||||||
|
data.Ceiling = Math.round(metersToFeet(data.Ceiling) / 100) * 100;
|
||||||
|
data.CeilingUnit = 'ft.';
|
||||||
|
data.Visibility = kilometersToMiles(observations.visibility.value / 1000);
|
||||||
|
data.VisibilityUnit = ' mi.';
|
||||||
|
data.WindSpeed = kphToMph(data.WindSpeed);
|
||||||
|
data.WindUnit = 'MPH';
|
||||||
|
data.Pressure = pascalToInHg(data.Pressure).toFixed(2);
|
||||||
|
data.HeatIndex = celsiusToFahrenheit(data.HeatIndex);
|
||||||
|
data.WindChill = celsiusToFahrenheit(data.WindChill);
|
||||||
|
data.WindGust = kphToMph(data.WindGust);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
const display = new CurrentWeather(1, 'current-weather');
|
const display = new CurrentWeather(1, 'current-weather');
|
||||||
registerDisplay(display);
|
registerDisplay(display);
|
||||||
|
|
||||||
|
|||||||
@@ -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,8 +52,11 @@ 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 = {
|
||||||
fill.date = Day.dayName;
|
icon: { type: 'img', src: Day.icon },
|
||||||
|
condition: Day.text,
|
||||||
|
date: Day.dayName,
|
||||||
|
};
|
||||||
|
|
||||||
const { low } = Day;
|
const { low } = Day;
|
||||||
if (low !== undefined) {
|
if (low !== undefined) {
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { DateTime } from '../vendor/auto/luxon.mjs';
|
|||||||
|
|
||||||
class HourlyGraph extends WeatherDisplay {
|
class HourlyGraph extends WeatherDisplay {
|
||||||
constructor(navId, elemId, defaultActive) {
|
constructor(navId, elemId, defaultActive) {
|
||||||
// special height and width for scrolling
|
|
||||||
super(navId, elemId, 'Hourly Graph', defaultActive);
|
super(navId, elemId, 'Hourly Graph', defaultActive);
|
||||||
|
|
||||||
// move the top right data into the correct location on load
|
// move the top right data into the correct location on load
|
||||||
|
|||||||
@@ -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':
|
||||||
@@ -119,10 +123,15 @@ const getWeatherRegionalIconFromIconLink = (link, _isNightTime) => {
|
|||||||
case 'tropical_storm':
|
case 'tropical_storm':
|
||||||
return addPath('Thunderstorm.gif');
|
return addPath('Thunderstorm.gif');
|
||||||
|
|
||||||
|
case 'wind':
|
||||||
case 'wind_few':
|
case 'wind_few':
|
||||||
case 'wind_sct':
|
case 'wind_sct':
|
||||||
case 'wind_bkn':
|
case 'wind_bkn':
|
||||||
case 'wind_ovc':
|
case 'wind_ovc':
|
||||||
|
case 'wind-n':
|
||||||
|
case 'wind_few-n':
|
||||||
|
case 'wind_bkn-n':
|
||||||
|
case 'wind_ovc-n':
|
||||||
return addPath('Wind.gif');
|
return addPath('Wind.gif');
|
||||||
|
|
||||||
case 'wind_skc':
|
case 'wind_skc':
|
||||||
@@ -155,7 +164,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];
|
||||||
|
|
||||||
@@ -203,6 +212,9 @@ const getWeatherIconFromIconLink = (link, _isNightTime) => {
|
|||||||
return addPath('CC_Fog.gif');
|
return addPath('CC_Fog.gif');
|
||||||
|
|
||||||
case 'rain_sleet':
|
case 'rain_sleet':
|
||||||
|
case 'rain_sleet-n':
|
||||||
|
case 'sleet':
|
||||||
|
case 'sleet-n':
|
||||||
return addPath('Sleet.gif');
|
return addPath('Sleet.gif');
|
||||||
|
|
||||||
case 'rain_showers':
|
case 'rain_showers':
|
||||||
@@ -238,6 +250,8 @@ const getWeatherIconFromIconLink = (link, _isNightTime) => {
|
|||||||
case 'snow_fzra-n':
|
case 'snow_fzra-n':
|
||||||
case 'fzra':
|
case 'fzra':
|
||||||
case 'fzra-n':
|
case 'fzra-n':
|
||||||
|
case 'rain_fzra':
|
||||||
|
case 'rain_fzra-n':
|
||||||
return addPath('CC_FreezingRain.gif');
|
return addPath('CC_FreezingRain.gif');
|
||||||
|
|
||||||
case 'snow_sleet':
|
case 'snow_sleet':
|
||||||
@@ -261,9 +275,10 @@ const getWeatherIconFromIconLink = (link, _isNightTime) => {
|
|||||||
case 'wind_sct':
|
case 'wind_sct':
|
||||||
case 'wind_bkn':
|
case 'wind_bkn':
|
||||||
case 'wind_ovc':
|
case 'wind_ovc':
|
||||||
return addPath('CC_Windy.gif');
|
|
||||||
|
|
||||||
case 'wind_skc':
|
case 'wind_skc':
|
||||||
|
case 'wind_few-n':
|
||||||
|
case 'wind_bkn-n':
|
||||||
|
case 'wind_ovc-n':
|
||||||
case 'wind_skc-n':
|
case 'wind_skc-n':
|
||||||
case 'wind_sct-n':
|
case 'wind_sct-n':
|
||||||
return addPath('CC_Windy.gif');
|
return addPath('CC_Windy.gif');
|
||||||
|
|||||||
@@ -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,7 +72,7 @@ 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.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;
|
||||||
@@ -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();
|
||||||
@@ -116,6 +112,12 @@ const updateStatus = (value) => {
|
|||||||
value.status = displays[firstDisplayIndex].status;
|
value.status = displays[firstDisplayIndex].status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if hazards data arrives after the firstDisplayIndex loads, then we need to hot wire this to the first display
|
||||||
|
if (value.id === 0 && value.status === STATUS.loaded && displays[0].timing.totalScreens === 0) {
|
||||||
|
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);
|
||||||
@@ -200,9 +202,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 +212,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 +269,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 +294,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 +311,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 +332,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 +364,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 +375,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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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));
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -53,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);
|
||||||
@@ -253,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();
|
||||||
}
|
}
|
||||||
@@ -377,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) => {
|
||||||
|
|||||||
1
tests/README.md
Normal file
1
tests/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Currently, tests take a different approach from typical unit testing. The test methodology loads several forecasts for different locations and logs them all to one logger so errors can be found such as missing icons, locations that do not have all of the necessary data or other changes that may occur between geographical locations.
|
||||||
42
tests/index.js
Normal file
42
tests/index.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
const puppeteer = require('puppeteer');
|
||||||
|
const { setTimeout } = require('node:timers/promises');
|
||||||
|
const { readFile } = require('fs/promises');
|
||||||
|
const messageFormatter = require('./messageformatter');
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const browser = await puppeteer.launch({
|
||||||
|
// headless: false,
|
||||||
|
slowMo: 10,
|
||||||
|
timeout: 10_000,
|
||||||
|
dumpio: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// get the list of locations
|
||||||
|
const LOCATIONS = JSON.parse(await readFile('./tests/locations.json'));
|
||||||
|
|
||||||
|
// get the page
|
||||||
|
const page = (await browser.pages())[0];
|
||||||
|
await page.goto('http://localhost:8080');
|
||||||
|
|
||||||
|
page.on('console', messageFormatter);
|
||||||
|
|
||||||
|
// run all the locations
|
||||||
|
for (let i = 0; i < LOCATIONS.length; i += 1) {
|
||||||
|
const location = LOCATIONS[i];
|
||||||
|
console.log(location);
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await tester(location, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
browser.close();
|
||||||
|
})();
|
||||||
|
|
||||||
|
const tester = async (location, page) => {
|
||||||
|
// Set the address
|
||||||
|
await page.type('#txtAddress', location);
|
||||||
|
await setTimeout(500);
|
||||||
|
// get the page
|
||||||
|
await page.click('#btnGetLatLng');
|
||||||
|
// wait for errors
|
||||||
|
await setTimeout(5000);
|
||||||
|
};
|
||||||
52
tests/locations.json
Normal file
52
tests/locations.json
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
[
|
||||||
|
"New York, New York",
|
||||||
|
"Los Angeles, California",
|
||||||
|
"Chicago, Illinois",
|
||||||
|
"Houston, Texas",
|
||||||
|
"Phoenix, Arizona",
|
||||||
|
"Philadelphia, Pennsylvania",
|
||||||
|
"San Antonio, Texas",
|
||||||
|
"San Diego, California",
|
||||||
|
"Dallas, Texas",
|
||||||
|
"San Jose, California",
|
||||||
|
"Austin, Texas",
|
||||||
|
"Jacksonville, Florida",
|
||||||
|
"Fort Worth, Texas",
|
||||||
|
"Columbus, Ohio",
|
||||||
|
"Charlotte, North Carolina",
|
||||||
|
"Indianapolis, Indiana",
|
||||||
|
"San Francisco, California",
|
||||||
|
"Seattle, Washington",
|
||||||
|
"Denver, Colorado",
|
||||||
|
"Nashville, Tennessee",
|
||||||
|
"Washington, District of Columbia",
|
||||||
|
"Oklahoma City, Oklahoma",
|
||||||
|
"Boston, Massachusetts",
|
||||||
|
"El Paso, Texas",
|
||||||
|
"Portland, Oregon",
|
||||||
|
"Las Vegas, Nevada",
|
||||||
|
"Memphis, Tennessee",
|
||||||
|
"Detroit, Michigan",
|
||||||
|
"Baltimore, Maryland",
|
||||||
|
"Milwaukee, Wisconsin",
|
||||||
|
"Albuquerque, New Mexico",
|
||||||
|
"Fresno, California",
|
||||||
|
"Tucson, Arizona",
|
||||||
|
"Sacramento, California",
|
||||||
|
"Mesa, Arizona",
|
||||||
|
"Kansas City, Missouri",
|
||||||
|
"Atlanta, Georgia",
|
||||||
|
"Omaha, Nebraska",
|
||||||
|
"Colorado Springs, Colorado",
|
||||||
|
"Raleigh, North Carolina",
|
||||||
|
"Long Beach, California",
|
||||||
|
"Virginia Beach, Virginia",
|
||||||
|
"Oakland, California",
|
||||||
|
"Miami, Florida",
|
||||||
|
"Minneapolis, Minnesota",
|
||||||
|
"Bakersfield, California",
|
||||||
|
"Tulsa, Oklahoma",
|
||||||
|
"Aurora, Colorado",
|
||||||
|
"Arlington, Texas",
|
||||||
|
"Wichita, Kansas"
|
||||||
|
]
|
||||||
28
tests/messageformatter.js
Normal file
28
tests/messageformatter.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
const chalk = require('chalk');
|
||||||
|
|
||||||
|
const describe = (jsHandle) => jsHandle.executionContext().evaluate(
|
||||||
|
// serialize |obj| however you want
|
||||||
|
(obj) => `OBJ: ${typeof obj}, ${obj}`,
|
||||||
|
jsHandle,
|
||||||
|
);
|
||||||
|
|
||||||
|
const colors = {
|
||||||
|
LOG: chalk.grey,
|
||||||
|
ERR: chalk.red,
|
||||||
|
WAR: chalk.yellow,
|
||||||
|
INF: chalk.cyan,
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatter = async (message) => {
|
||||||
|
const args = await Promise.all(message.args().map((arg) => describe(arg)));
|
||||||
|
// make ability to paint different console[types]
|
||||||
|
const type = message.type().substr(0, 3).toUpperCase();
|
||||||
|
const color = colors[type] || chalk.blue;
|
||||||
|
let text = '';
|
||||||
|
for (let i = 0; i < args.length; i += 1) {
|
||||||
|
text += `[${i}] ${args[i]} `;
|
||||||
|
}
|
||||||
|
console.log(color(`CONSOLE.${type}: ${message.text()}\n${text} `));
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = formatter;
|
||||||
1436
tests/package-lock.json
generated
Normal file
1436
tests/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
15
tests/package.json
Normal file
15
tests/package.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "ws4kp-tests",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Currently, tests take a different approach from typical unit testing. The test methodology loads several forecasts for different locations and logs them all to one logger so errors can be found such as missing icons, locations that do not have all of the necessary data or other changes that may occur between geographical locations.",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"chalk": "^4.0.0",
|
||||||
|
"puppeteer": "^19.5.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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