additional eslint rules

This commit is contained in:
Matt Walsh 2023-01-06 14:39:39 -06:00
parent b890b4e53d
commit 3743c45de6
26 changed files with 1079 additions and 184 deletions

View file

@ -6,7 +6,10 @@ module.exports = {
node: true,
jquery: true,
},
extends: 'airbnb-base',
extends: [
'airbnb-base',
'plugin:sonarjs/recommended',
],
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly',
@ -21,6 +24,10 @@ module.exports = {
parserOptions: {
ecmaVersion: 2021,
},
plugins: [
'unicorn',
'sonarjs',
],
rules: {
indent: [
'error',
@ -60,6 +67,24 @@ module.exports = {
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: [
'*.min.js',

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

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

867
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -22,7 +22,11 @@
"devDependencies": {
"del": "^6.0.0",
"ejs": "^3.1.5",
"eslint": "^8.21.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-sonarjs": "^0.17.0",
"eslint-plugin-unicorn": "^45.0.2",
"express": "^4.17.1",
"gulp": "^4.0.2",
"gulp-concat": "^2.6.1",
@ -40,9 +44,6 @@
"suncalc": "^1.8.0",
"swiped-events": "^1.1.4",
"terser-webpack-plugin": "^5.3.6",
"webpack-stream": "^7.0.0",
"eslint": "^8.21.0",
"eslint-plugin-import": "^2.26.0"
},
"dependencies": {}
"webpack-stream": "^7.0.0"
}
}

View file

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

View file

@ -22,35 +22,38 @@ const categories = [
'Postal', 'Populated Place',
];
const category = categories.join(',');
const TXT_ADDRESS_SELECTOR = '#txtAddress';
const TOGGLE_FULL_SCREEN_SELECTOR = '#ToggleFullScreen';
const BNT_GET_GPS_SELECTOR = '#btnGetGps';
const init = () => {
document.getElementById('txtAddress').addEventListener('focus', (e) => {
document.querySelector(TXT_ADDRESS_SELECTOR).addEventListener('focus', (e) => {
e.target.select();
});
registerRefreshData(loadData);
document.getElementById('NavigateMenu').addEventListener('click', btnNavigateMenuClick);
document.getElementById('NavigateRefresh').addEventListener('click', btnNavigateRefreshClick);
document.getElementById('NavigateNext').addEventListener('click', btnNavigateNextClick);
document.getElementById('NavigatePrevious').addEventListener('click', btnNavigatePreviousClick);
document.getElementById('NavigatePlay').addEventListener('click', btnNavigatePlayClick);
document.getElementById('ToggleFullScreen').addEventListener('click', btnFullScreenClick);
const btnGetGps = document.getElementById('btnGetGps');
document.querySelector('#NavigateMenu').addEventListener('click', btnNavigateMenuClick);
document.querySelector('#NavigateRefresh').addEventListener('click', btnNavigateRefreshClick);
document.querySelector('#NavigateNext').addEventListener('click', btnNavigateNextClick);
document.querySelector('#NavigatePrevious').addEventListener('click', btnNavigatePreviousClick);
document.querySelector('#NavigatePlay').addEventListener('click', btnNavigatePlayClick);
document.querySelector(TOGGLE_FULL_SCREEN_SELECTOR).addEventListener('click', btnFullScreenClick);
const btnGetGps = document.querySelector(BNT_GET_GPS_SELECTOR);
btnGetGps.addEventListener('click', btnGetGpsClick);
if (!navigator.geolocation) btnGetGps.style.display = 'none';
document.getElementById('divTwc').addEventListener('click', () => {
document.querySelector('#divTwc').addEventListener('click', () => {
if (document.fullscreenElement) updateFullScreenNavigate();
});
document.getElementById('txtAddress').addEventListener('keydown', (key) => { if (key.code === 'Enter') formSubmit(); });
document.getElementById('btnGetLatLng').addEventListener('click', () => formSubmit());
document.querySelector(TXT_ADDRESS_SELECTOR).addEventListener('keydown', (key) => { if (key.code === 'Enter') formSubmit(); });
document.querySelector('#btnGetLatLng').addEventListener('click', () => formSubmit());
document.addEventListener('keydown', documentKeydown);
document.addEventListener('touchmove', (e) => { if (fullScreenOverride) e.preventDefault(); });
$('#txtAddress').devbridgeAutocomplete({
$(TXT_ADDRESS_SELECTOR).devbridgeAutocomplete({
serviceUrl: 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/suggest',
deferRequestBy: 300,
paramName: 'text',
@ -75,7 +78,7 @@ const init = () => {
});
const formSubmit = () => {
const ac = $('#txtAddress').devbridgeAutocomplete();
const ac = $(TXT_ADDRESS_SELECTOR).devbridgeAutocomplete();
if (ac.suggestions[0]) $(ac.suggestionsContainer.children[0]).trigger('click');
return false;
};
@ -85,7 +88,7 @@ const init = () => {
const latLon = localStorage.getItem('latLon');
const fromGPS = localStorage.getItem('latLonFromGPS');
if (query && latLon && !fromGPS) {
const txtAddress = document.getElementById('txtAddress');
const txtAddress = document.querySelector(TXT_ADDRESS_SELECTOR);
txtAddress.value = query;
loadData(JSON.parse(latLon));
}
@ -96,14 +99,14 @@ const init = () => {
const play = localStorage.getItem('play');
if (play === null || play === 'true') postMessage('navButton', 'play');
document.getElementById('btnClearQuery').addEventListener('click', () => {
document.getElementById('spanCity').innerHTML = '';
document.getElementById('spanState').innerHTML = '';
document.getElementById('spanStationId').innerHTML = '';
document.getElementById('spanRadarId').innerHTML = '';
document.getElementById('spanZoneId').innerHTML = '';
document.querySelector('#btnClearQuery').addEventListener('click', () => {
document.querySelector('#spanCity').innerHTML = '';
document.querySelector('#spanState').innerHTML = '';
document.querySelector('#spanStationId').innerHTML = '';
document.querySelector('#spanRadarId').innerHTML = '';
document.querySelector('#spanZoneId').innerHTML = '';
document.getElementById('chkAutoRefresh').checked = true;
document.querySelector('#chkAutoRefresh').checked = true;
localStorage.removeItem('autoRefresh');
localStorage.removeItem('play');
@ -112,12 +115,12 @@ const init = () => {
localStorage.removeItem('latLonQuery');
localStorage.removeItem('latLon');
localStorage.removeItem('latLonFromGPS');
document.getElementById('btnGetGps').classList.remove('active');
document.querySelector(BNT_GET_GPS_SELECTOR).classList.remove('active');
});
// swipe functionality
document.getElementById('container').addEventListener('swiped-left', () => swipeCallBack('left'));
document.getElementById('container').addEventListener('swiped-right', () => swipeCallBack('right'));
document.querySelector('#container').addEventListener('swiped-left', () => swipeCallBack('left'));
document.querySelector('#container').addEventListener('swiped-right', () => swipeCallBack('right'));
};
const autocompleteOnSelect = async (suggestion, elem) => {
@ -135,7 +138,7 @@ const autocompleteOnSelect = async (suggestion, elem) => {
const loc = data.locations[0];
if (loc) {
localStorage.removeItem('latLonFromGPS');
document.getElementById('btnGetGps').classList.remove('active');
document.querySelector(BNT_GET_GPS_SELECTOR).classList.remove('active');
doRedirectToGeometry(loc.feature.geometry);
} else {
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 latLon = { lat: round2(geom.y, 4), lon: round2(geom.x, 4) };
// 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));
// get the data
@ -153,10 +156,10 @@ const doRedirectToGeometry = (geom, haveDataCallback) => {
};
const btnFullScreenClick = () => {
if (!document.fullscreenElement) {
enterFullScreen();
} else {
if (document.fullscreenElement) {
exitFullscreen();
} else {
enterFullScreen();
}
if (isPlaying()) {
@ -171,7 +174,7 @@ const btnFullScreenClick = () => {
};
const enterFullScreen = () => {
const element = document.getElementById('divTwc');
const element = document.querySelector('#divTwc');
// Supports most browsers and their versions.
const requestMethod = element.requestFullScreen || element.webkitRequestFullScreen
@ -189,7 +192,7 @@ const enterFullScreen = () => {
updateFullScreenNavigate();
// 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.title = 'Exit fullscreen';
};
@ -213,7 +216,7 @@ const exitFullscreen = () => {
}
resize();
// 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.title = 'Enter fullscreen';
};
@ -232,7 +235,7 @@ const loadData = (_latLon, haveDataCallback) => {
// if there's no data stop
if (!latLon) return;
document.getElementById('txtAddress').blur();
document.querySelector(TXT_ADDRESS_SELECTOR).blur();
stopAutoRefreshTimer();
latLonReceived(latLon, haveDataCallback);
};
@ -276,8 +279,9 @@ let navigateFadeIntervalId = null;
const updateFullScreenNavigate = () => {
document.activeElement.blur();
document.getElementById('divTwcBottom').classList.remove('hidden');
document.getElementById('divTwcBottom').classList.add('visible');
const divTwcBottom = document.querySelector('#divTwcBottom');
divTwcBottom.classList.remove('hidden');
divTwcBottom.classList.add('visible');
if (navigateFadeIntervalId) {
clearTimeout(navigateFadeIntervalId);
@ -286,8 +290,8 @@ const updateFullScreenNavigate = () => {
navigateFadeIntervalId = setTimeout(() => {
if (document.fullscreenElement) {
document.getElementById('divTwcBottom').classList.remove('visible');
document.getElementById('divTwcBottom').classList.add('hidden');
divTwcBottom.classList.remove('visible');
divTwcBottom.classList.add('hidden');
}
}, 2000);
};
@ -355,7 +359,7 @@ const getPosition = async () => new Promise((resolve) => {
const btnGetGpsClick = async () => {
if (!navigator.geolocation) return;
const btn = document.getElementById('btnGetGps');
const btn = document.querySelector(BNT_GET_GPS_SELECTOR);
// toggle first
if (btn.classList.contains('active')) {
@ -371,7 +375,7 @@ const btnGetGpsClick = async () => {
const position = await getPosition();
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)}`;
doRedirectToGeometry({ y: latitude, x: longitude }, (point) => {

View file

@ -59,8 +59,8 @@ class CurrentWeather extends WeatherDisplay {
observations = undefined;
throw new Error(`Unable to get observations: ${station.properties.stationIdentifier}, trying next station`);
}
} catch (e) {
console.error(e);
} catch (error) {
console.error(error);
}
}
// test for data received

View file

@ -75,12 +75,10 @@ const screens = [
// wind
(data) => {
let text = '';
if (data.WindSpeed > 0) {
text = `Wind: ${data.WindDirection} ${data.WindSpeed} ${data.WindUnit}`;
} else {
text = 'Wind: Calm';
}
let text = data.WindSpeed > 0
? `Wind: ${data.WindDirection} ${data.WindSpeed} ${data.WindUnit}`
: 'Wind: Calm';
if (data.WindGust > 0) {
text += ` Gusts to ${data.WindGust}`;
}
@ -88,7 +86,10 @@ const screens = [
},
// 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

View file

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

View file

@ -40,9 +40,9 @@ class Hazards extends WeatherDisplay {
// show alert indicator
if (this.data.length > 0) alert.classList.add('show');
} catch (e) {
} catch (error) {
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);
// return undefined to other subscribers
this.getDataCallback(undefined);

View file

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

View file

@ -1,3 +1,4 @@
/* eslint-disable unicorn/consistent-function-scoping */
/* spell-checker: disable */
const getWeatherRegionalIconFromIconLink = (link, _isNightTime) => {
@ -8,7 +9,7 @@ const getWeatherRegionalIconFromIconLink = (link, _isNightTime) => {
// grab everything after the last slash ending at any of these: ?&,
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
const value = +(link.match(/,(\d{2,3})/) ?? [0, 100])[1];
@ -156,7 +157,7 @@ const getWeatherIconFromIconLink = (link, _isNightTime) => {
// grab everything after the last slash ending at any of these: ?&,
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
const value = +(link.match(/,(\d{2,3})/) ?? [0, 100])[1];

View file

@ -50,7 +50,7 @@ class LatestObservations extends WeatherDisplay {
this.data = actualConditions.slice(0, this.MaximumRegionalStations);
// test for at least one station
if (this.data.length < 1) {
if (this.data.length === 0) {
this.setStatus(STATUS.noData);
return;
}
@ -73,10 +73,12 @@ class LatestObservations extends WeatherDisplay {
const Temperature = Math.round(celsiusToFahrenheit(condition.temperature.value));
const WindSpeed = Math.round(kphToMph(condition.windSpeed.value));
const fill = {};
fill.location = locationCleanup(condition.city).substr(0, 14);
fill.temp = Temperature;
fill.weather = shortenCurrentConditions(condition.textDescription).substr(0, 9);
const fill = {
location: locationCleanup(condition.city).substr(0, 14),
temp: Temperature,
weather: shortenCurrentConditions(condition.textDescription).substr(0, 9),
};
if (WindSpeed > 0) {
fill.wind = windDirection + (Array(6 - windDirection.length - WindSpeed.toString().length).join(' ')) + WindSpeed.toString();
} else if (WindSpeed === 'NA') {
@ -128,7 +130,7 @@ const getStations = async (stations) => {
StationId: station.id,
city: station.city,
};
} catch (e) {
} catch (error) {
console.log(`Unable to get latest observations for ${station.id}`);
return false;
}

View file

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

View file

@ -16,7 +16,8 @@ const weatherParameters = {};
// auto refresh
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 AutoRefreshCountMs = 0;
@ -28,25 +29,19 @@ const init = async () => {
// auto refresh
const autoRefresh = localStorage.getItem('autoRefresh');
if (!autoRefresh || autoRefresh === 'true') {
document.getElementById('chkAutoRefresh').checked = true;
document.querySelector(CHK_AUTO_REFRESH_SELECTOR).checked = true;
} 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();
};
const message = (data) => {
// dispatch event
if (!data.type) return;
switch (data.type) {
case 'navButton':
handleNavButton(data.message);
break;
default:
console.error(`Unknown event ${data.type}`);
}
if (!data.type) return false;
if (data.type === 'navButton') return handleNavButton(data.message);
return console.error(`Unknown event ${data.type}`);
};
const getWeather = async (latLon, haveDataCallback) => {
@ -61,6 +56,7 @@ const getWeather = async (latLon, haveDataCallback) => {
const StationId = stations.features[0].properties.stationIdentifier;
let { city } = point.properties.relativeLocation.properties;
const { state } = point.properties.relativeLocation.properties;
if (StationId in StationInfo) {
city = StationInfo[StationId].city;
@ -76,7 +72,7 @@ const getWeather = async (latLon, haveDataCallback) => {
weatherParameters.stationId = StationId;
weatherParameters.weatherOffice = point.properties.cwa;
weatherParameters.city = city;
weatherParameters.state = point.properties.relativeLocation.properties.state;
weatherParameters.state = state;
weatherParameters.timeZone = point.properties.timeZone;
weatherParameters.forecast = point.properties.forecast;
weatherParameters.forecastGridData = point.properties.forecastGridData;
@ -87,7 +83,7 @@ const getWeather = async (latLon, haveDataCallback) => {
// draw the progress canvas and hide others
hideAllCanvases();
document.getElementById('loading').style.display = 'none';
document.querySelector('#loading').style.display = 'none';
if (progress) {
await progress.drawCanvas();
progress.showCanvas();
@ -200,9 +196,7 @@ const loadDisplay = (direction) => {
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 (idx <= curIdx) {
if (refreshCheck()) return;
}
if (idx <= curIdx && refreshCheck()) return;
const newDisplay = displays[idx];
// hide all displays
hideAllCanvases();
@ -212,15 +206,12 @@ const loadDisplay = (direction) => {
};
// get the current display index or value
const currentDisplayIndex = () => {
const index = displays.findIndex((display) => display.active);
return index;
};
const currentDisplayIndex = () => displays.findIndex((display) => display.active);
const currentDisplay = () => displays[currentDisplayIndex()];
const setPlaying = (newValue) => {
playing = newValue;
const playButton = document.getElementById('NavigatePlay');
const playButton = document.querySelector('#NavigatePlay');
localStorage.setItem('play', playing);
if (playing) {
@ -272,14 +263,14 @@ const getDisplay = (index) => displays[index];
// resize the container on a page 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 scale = Math.min(widthZoomPercent, heightZoomPercent);
if (scale < 1.0 || document.fullscreenElement) {
document.getElementById('container').style.zoom = scale;
document.querySelector('#container').style.zoom = scale;
} else {
document.getElementById('container').style.zoom = 1;
document.querySelector('#container').style.zoom = 1;
}
};
@ -297,7 +288,7 @@ const registerDisplay = (display) => {
};
const generateCheckboxes = () => {
const availableDisplays = document.getElementById('enabledDisplays');
const availableDisplays = document.querySelector('#enabledDisplays');
if (!availableDisplays) return;
// generate checkboxes
@ -314,11 +305,11 @@ const registerProgress = (_progress) => {
};
const populateWeatherParameters = (params) => {
document.getElementById('spanCity').innerHTML = `${params.city}, `;
document.getElementById('spanState').innerHTML = params.state;
document.getElementById('spanStationId').innerHTML = params.stationId;
document.getElementById('spanRadarId').innerHTML = params.radarId;
document.getElementById('spanZoneId').innerHTML = params.zoneId;
document.querySelector('#spanCity').innerHTML = `${params.city}, `;
document.querySelector('#spanState').innerHTML = params.state;
document.querySelector('#spanStationId').innerHTML = params.stationId;
document.querySelector('#spanRadarId').innerHTML = params.radarId;
document.querySelector('#spanZoneId').innerHTML = params.zoneId;
};
const autoRefreshChange = (e) => {
@ -335,12 +326,12 @@ const autoRefreshChange = (e) => {
const AssignLastUpdate = (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',
});
if (document.getElementById('chkAutoRefresh').checked) startAutoRefreshTimer();
if (document.querySelector(CHK_AUTO_REFRESH_SELECTOR).checked) startAutoRefreshTimer();
} else {
document.getElementById('spanLastRefresh').innerHTML = '(none)';
document.querySelector('#spanLastRefresh').innerHTML = '(none)';
}
};
@ -367,7 +358,7 @@ const startAutoRefreshTimer = () => {
RemainingMs = 0;
}
const dt = new Date(RemainingMs);
document.getElementById('spanRefreshCountDown').innerHTML = `${dt.getMinutes() < 10 ? `0${dt.getMinutes()}` : dt.getMinutes()}:${dt.getSeconds() < 10 ? `0${dt.getSeconds()}` : dt.getSeconds()}`;
document.querySelector('#spanRefreshCountDown').innerHTML = `${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}`;
// Time has elapsed.
if (AutoRefreshCountMs >= AUTO_REFRESH_TIME_MS && !isPlaying()) loadTwcData();
@ -378,7 +369,7 @@ const startAutoRefreshTimer = () => {
const stopAutoRefreshTimer = () => {
if (AutoRefreshIntervalId) {
window.clearInterval(AutoRefreshIntervalId);
document.getElementById('spanRefreshCountDown').innerHTML = '--:--';
document.querySelector('#spanRefreshCountDown').innerHTML = '--:--';
AutoRefreshIntervalId = null;
}
};

View file

@ -18,7 +18,7 @@ class Progress extends WeatherDisplay {
// setup event listener for dom-required initialization
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));
});
@ -36,9 +36,9 @@ class Progress extends WeatherDisplay {
if (!displays) return;
const lines = displays.map((display, index) => {
if (display.showOnProgress === false) return false;
const fill = {};
fill.name = display.name;
const fill = {
name: display.name,
};
const statusClass = calcStatusClass(display.status);

View file

@ -71,25 +71,24 @@ class Radar extends WeatherDisplay {
const lists = (await Promise.all(baseUrls.map(async (url) => {
try {
// get a list of available radars
const radarHtml = await text(url, { cors: true });
return radarHtml;
} catch (e) {
return text(url, { cors: true });
} catch (error) {
console.log('Unable to get list of radars');
console.error(e);
console.error(error);
this.setStatus(STATUS.failed);
return false;
}
}))).filter((d) => d);
// convert to an array of gif urls
const pngs = lists.map((html, htmlIdx) => {
const pngs = lists.flatMap((html, htmlIdx) => {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(html, 'text/html');
// add the base url
const base = xmlDoc.createElement('base');
base.href = baseUrls[htmlIdx];
xmlDoc.head.append(base);
const anchors = xmlDoc.getElementsByTagName('a');
const anchors = xmlDoc.querySelectorAll('a');
const urls = [];
Array.from(anchors).forEach((elem) => {
if (elem.innerHTML?.includes('.png') && elem.innerHTML?.includes('n0r_')) {
@ -97,7 +96,7 @@ class Radar extends WeatherDisplay {
}
});
return urls;
}).flat();
});
// get the last few images
const sortedPngs = pngs.sort((a, b) => (Date(a) < Date(b) ? -1 : 1));

View file

@ -28,9 +28,9 @@ const getRegionalObservation = async (point, city) => {
preloadImg(icon);
// return the observation
return observation.properties;
} catch (e) {
} catch (error) {
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;
}
};
@ -195,7 +195,7 @@ const getXYForCityHI = (City, MaxLatitude, MinLongitude) => {
};
// 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 {
buildForecast,

View file

@ -92,7 +92,7 @@ class RegionalForecast extends WeatherDisplay {
// format the observation the same as the forecast
const regionalObservation = {
daytime: !!observation.icon.match(/\/day\//),
daytime: !!/\/day\//.test(observation.icon),
temperature: celsiusToFahrenheit(observation.temperature.value),
name: utils.formatCity(city.city),
icon: observation.icon,
@ -113,9 +113,9 @@ class RegionalForecast extends WeatherDisplay {
utils.buildForecast(forecast.properties.periods[1], 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(e);
console.log(error);
return false;
}
}));
@ -159,11 +159,9 @@ class RegionalForecast extends WeatherDisplay {
const dayName = forecastDate.toLocaleString({ weekday: 'long' });
titleTop.innerHTML = 'Forecast for';
// draw the title
if (data[0][this.screenIndex].daytime) {
titleBottom.innerHTML = dayName;
} else {
titleBottom.innerHTML = `${dayName} Night`;
}
titleBottom.innerHTML = data[0][this.screenIndex].daytime
? dayName
: `${dayName} Night`;
}
// draw the map
@ -182,9 +180,11 @@ class RegionalForecast extends WeatherDisplay {
const { temperature } = period;
fill.temp = temperature;
const { x, y } = period;
const elem = this.fillTemplate('location', fill);
elem.style.left = `${period.x}px`;
elem.style.top = `${period.y}px`;
elem.style.left = `${x}px`;
elem.style.top = `${y}px`;
return elem;
});

View file

@ -45,9 +45,9 @@ class TravelForecast extends WeatherDisplay {
name: city.Name,
icon: getWeatherRegionalIconFromIconLink(forecast.properties.periods[todayShift].icon),
};
} catch (e) {
} catch (error) {
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 };
}
});
@ -77,10 +77,9 @@ class TravelForecast extends WeatherDisplay {
const lines = cities.map((city) => {
if (city.error) return false;
const fillValues = {};
// city name
fillValues.city = city;
const fillValues = {
city,
};
// check for forecast data
if (city.icon) {
@ -94,8 +93,9 @@ class TravelForecast extends WeatherDisplay {
fillValues.low = lowString;
fillValues.high = highString;
const { icon } = city;
fillValues.icon = { type: 'img', src: city.icon };
fillValues.icon = { type: 'img', src: icon };
} else {
fillValues.error = 'NO TRAVEL DATA AVAILABLE';
}

View file

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

View file

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

View file

@ -2,11 +2,11 @@
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 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 pascalToInHg = (Pascal) => round2(Pascal * 0.0002953, 2);
const pascalToInHg = (Pascal) => round2(Pascal * 0.000_295_3, 2);
export {
kphToMph,

View file

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

View file

@ -53,11 +53,7 @@ class WeatherDisplay {
// get the saved status of the checkbox
let savedStatus = window.localStorage.getItem(`display-enabled: ${this.elemId}`);
if (savedStatus === null) savedStatus = defaultEnabled;
if (savedStatus === 'true' || savedStatus === true) {
this.isEnabled = true;
} else {
this.isEnabled = false;
}
this.isEnabled = !!((savedStatus === 'true' || savedStatus === true));
// refresh (or initially store the state of the checkbox)
window.localStorage.setItem(`display-enabled: ${this.elemId}`, this.isEnabled);
@ -253,17 +249,13 @@ class WeatherDisplay {
if (nextScreenIndex === this.screenIndex) return;
// test for -1 (no screen displayed yet)
if (nextScreenIndex === -1) {
this.screenIndex = 0;
} else {
this.screenIndex = nextScreenIndex;
}
this.screenIndex = nextScreenIndex === -1 ? 0 : nextScreenIndex;
// call the appropriate screen index change method
if (!this.screenIndexChange) {
await this.drawCanvas();
} else {
if (this.screenIndexChange) {
this.screenIndexChange(this.screenIndex);
} else {
await this.drawCanvas();
}
this.showCanvas();
}
@ -377,7 +369,7 @@ class WeatherDisplay {
loadTemplates() {
this.templates = {};
this.elem = document.getElementById(`${this.elemId}-html`);
this.elem = document.querySelector(`#${this.elemId}-html`);
if (!this.elem) return;
const templates = this.elem.querySelectorAll('.template');
templates.forEach((template) => {

View file

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