change to airbnb eslint plugin

This commit is contained in:
Matt Walsh 2020-10-29 16:44:28 -05:00
parent b7967fca05
commit dd98daf0c2
31 changed files with 1179 additions and 790 deletions

View file

@ -1,35 +1,39 @@
module.exports = {
'env': {
'browser': true,
'commonjs': true,
'es6': true,
'node': true,
'jquery': true,
env: {
browser: true,
commonjs: true,
es6: true,
node: true,
jquery: true,
},
'extends': 'eslint:recommended',
'globals': {
'Atomics': 'readonly',
'SharedArrayBuffer': 'readonly'
extends: 'airbnb-base',
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly',
},
'parserOptions': {
'ecmaVersion': 2018
parserOptions: {
ecmaVersion: 2020,
},
'rules': {
'indent': [
rules: {
indent: [
'error',
'tab'
'tab',
],
'no-tabs': 0,
'no-use-before-define': 0,
'no-console': 0,
'linebreak-style': [
'error',
'unix'
'unix',
],
'quotes': [
'max-len': 0,
quotes: [
'error',
'single'
'single',
],
'semi': [
semi: [
'error',
'always'
'always',
],
'no-prototype-builtins': 0,
'comma-dangle': ['error', 'always-multiline'],
@ -37,14 +41,14 @@ module.exports = {
'default-case': ['error'],
'default-param-last': ['error'],
'dot-location': ['error', 'property'],
'eqeqeq': ['error'],
eqeqeq: ['error'],
'no-eval': ['error'],
'no-eq-null': ['error'],
'no-floating-decimal': ['error'],
'no-trailing-spaces': ['error'],
'brace-style': [2, '1tbs', { 'allowSingleLine': true }],
'brace-style': [2, '1tbs', { allowSingleLine: true }],
},
'ignorePatterns': [
'*.min.js'
ignorePatterns: [
'*.min.js',
],
};

View file

@ -1,4 +1,4 @@
const _StationInfo = {
const StationInfo = {
MADC: {
id: 'MADC',
city: 'Durango Complex',

2
dist/index.html vendored
View file

@ -1 +1 @@
<!DOCTYPE html><html lang="en" xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="utf-8"><link rel="preload" href="fonts/Star4000.woff" as="font" crossorigin="anonymous"><link rel="preload" href="fonts/Star 4 Radar.woff" as="font" crossorigin="anonymous"><link rel="preload" href="fonts/Star4000 Extended.woff" as="font" crossorigin="anonymous"><link rel="preload" href="fonts/Star4000 Large Compressed.woff" as="font" crossorigin="anonymous"><link rel="preload" href="fonts/Star4000 Large.woff" as="font" crossorigin="anonymous"><link rel="preload" href="fonts/Star4000 Small.woff" as="font" crossorigin="anonymous"><title>WeatherStar 4000+</title><meta name="description" content="Web based WeatherStar 4000 simulator that reports current and forecast weather conditions plus a few extras!"><meta name="keywords" content="WeatherStar 4000+"><meta name="author" content="Matt Walsh"><meta name="application-name" content="WeatherStar 4000+"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"><link rel="manifest" href="manifest.json"><link rel="icon" href="images/Logo192.png"><link rel="stylesheet" type="text/css" href="resources/ws.min.css?_=3.7.1"><script type="text/javascript" src="resources/data.min.js?_=3.7.1"></script><script type="text/javascript" src="resources/ws.min.js?_=3.7.1"></script></head><body><div id="divQuery"><form id="frmGetLatLng"><input id="txtAddress" type="text" value="" placeholder="Zip or City, State"><button id="btnGetGps" type="button" title="Get GPS Location"><img id="imgGetGps" src="images/nav/ic_gps_fixed_black_18dp_1x.png"></button> <input id="btnGetLatLng" type="submit" value="GO"> <input id="btnClearQuery" type="reset" value="Reset"></form><div id="divLat"></div><div id="divLng"></div></div><br><img id="imgPause1x" src="images/nav/ic_pause_white_24dp_1x.png"> <img id="imgPause2x" src="images/nav/ic_pause_white_24dp_2x.png"><div id="version" style="display:none">3.7.1</div><div id="divTwc"><div id="container"><div id="loading" width="640" height="480"><div><div class="title">WeatherStar 4000+</div><div class="instructions">Enter your location above to continue</div></div></div></div><div id="divTwcBottom"><div id="divTwcBottomLeft"><img id="NavigateMenu" class="navButton" src="images/nav/ic_menu_white_24dp_1x.png" title="Menu"> <img id="NavigatePrevious" class="navButton" src="images/nav/ic_skip_previous_white_24dp_1x.png" title="Previous"> <img id="NavigateNext" class="navButton" src="images/nav/ic_skip_next_white_24dp_1x.png" title="Next"> <img id="NavigatePlay" class="navButton" src="images/nav/ic_play_arrow_white_24dp_1x.png" title="Play"></div><div id="divTwcBottomMiddle"><img id="NavigateRefresh" class="navButton" src="images/nav/ic_refresh_white_24dp_1x.png" title="Refresh"></div><div id="divTwcBottomRight"><img id="ToggleFullScreen" class="navButton" src="images/nav/ic_fullscreen_exit_white_24dp_1x.png" title="Exit Fullscreen"></div></div></div><br><div class="info"><a href="https://github.com/netbymatt/ws4kp#weatherstar-4000">More information</a></div><div class="heading">Selected displays</div><div id="enabledDisplays"></div><div id="divInfo">Location: <span id="spanCity"></span> <span id="spanState"></span><br>Station Id: <span id="spanStationId"></span><br>Radar Id: <span id="spanRadarId"></span><br>Zone Id: <span id="spanZoneId"></span><br></div><div id="divRefresh">Last Update: <span id="spanLastRefresh">(None)</span><br><input id="chkAutoRefresh" name="chkAutoRefresh" type="checkbox"><label id="lblRefreshCountDown" for="chkAutoRefresh">Auto Refresh: <span id="spanRefreshCountDown">--:--</span></label></div><div id="divUnits">Units: <input id="radEnglish" name="radUnits" type="radio" value="ENGLISH"><label for="radEnglish">English</label> <input id="radMetric" name="radUnits" type="radio" value="METRIC"><label for="radMetric">Metric</label></div></body></html>
<!DOCTYPE html><html lang="en" xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="utf-8"><link rel="preload" href="fonts/Star4000.woff" as="font" crossorigin="anonymous"><link rel="preload" href="fonts/Star 4 Radar.woff" as="font" crossorigin="anonymous"><link rel="preload" href="fonts/Star4000 Extended.woff" as="font" crossorigin="anonymous"><link rel="preload" href="fonts/Star4000 Large Compressed.woff" as="font" crossorigin="anonymous"><link rel="preload" href="fonts/Star4000 Large.woff" as="font" crossorigin="anonymous"><link rel="preload" href="fonts/Star4000 Small.woff" as="font" crossorigin="anonymous"><title>WeatherStar 4000+</title><meta name="description" content="Web based WeatherStar 4000 simulator that reports current and forecast weather conditions plus a few extras!"><meta name="keywords" content="WeatherStar 4000+"><meta name="author" content="Matt Walsh"><meta name="application-name" content="WeatherStar 4000+"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"><link rel="manifest" href="manifest.json"><link rel="icon" href="images/Logo192.png"><link rel="stylesheet" type="text/css" href="resources/ws.min.css?_=3.8.0"><script type="text/javascript" src="resources/data.min.js?_=3.8.0"></script><script type="text/javascript" src="resources/ws.min.js?_=3.8.0"></script></head><body><div id="divQuery"><form id="frmGetLatLng"><input id="txtAddress" type="text" value="" placeholder="Zip or City, State"><button id="btnGetGps" type="button" title="Get GPS Location"><img id="imgGetGps" src="images/nav/ic_gps_fixed_black_18dp_1x.png"></button> <input id="btnGetLatLng" type="submit" value="GO"> <input id="btnClearQuery" type="reset" value="Reset"></form><div id="divLat"></div><div id="divLng"></div></div><br><img id="imgPause1x" src="images/nav/ic_pause_white_24dp_1x.png"> <img id="imgPause2x" src="images/nav/ic_pause_white_24dp_2x.png"><div id="version" style="display:none">3.8.0</div><div id="divTwc"><div id="container"><div id="loading" width="640" height="480"><div><div class="title">WeatherStar 4000+</div><div class="instructions">Enter your location above to continue</div></div></div></div><div id="divTwcBottom"><div id="divTwcBottomLeft"><img id="NavigateMenu" class="navButton" src="images/nav/ic_menu_white_24dp_1x.png" title="Menu"> <img id="NavigatePrevious" class="navButton" src="images/nav/ic_skip_previous_white_24dp_1x.png" title="Previous"> <img id="NavigateNext" class="navButton" src="images/nav/ic_skip_next_white_24dp_1x.png" title="Next"> <img id="NavigatePlay" class="navButton" src="images/nav/ic_play_arrow_white_24dp_1x.png" title="Play"></div><div id="divTwcBottomMiddle"><img id="NavigateRefresh" class="navButton" src="images/nav/ic_refresh_white_24dp_1x.png" title="Refresh"></div><div id="divTwcBottomRight"><img id="ToggleFullScreen" class="navButton" src="images/nav/ic_fullscreen_exit_white_24dp_1x.png" title="Exit Fullscreen"></div></div></div><br><div class="info"><a href="https://github.com/netbymatt/ws4kp#weatherstar-4000">More information</a></div><div class="heading">Selected displays</div><div id="enabledDisplays"></div><div id="divInfo">Location: <span id="spanCity"></span> <span id="spanState"></span><br>Station Id: <span id="spanStationId"></span><br>Radar Id: <span id="spanRadarId"></span><br>Zone Id: <span id="spanZoneId"></span><br></div><div id="divRefresh">Last Update: <span id="spanLastRefresh">(None)</span><br><input id="chkAutoRefresh" name="chkAutoRefresh" type="checkbox"><label id="lblRefreshCountDown" for="chkAutoRefresh">Auto Refresh: <span id="spanRefreshCountDown">--:--</span></label></div><div id="divUnits">Units: <input id="radEnglish" name="radUnits" type="radio" value="ENGLISH"><label for="radEnglish">English</label> <input id="radMetric" name="radUnits" type="radio" value="METRIC"><label for="radMetric">Metric</label></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

562
package-lock.json generated
View file

@ -44,9 +44,9 @@
}
},
"@eslint/eslintrc": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz",
"integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==",
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.1.tgz",
"integrity": "sha512-XRUeBZ5zBWLYgSANMpThFddrZZkEbGHgUdt5UJjZfnlN9BGCiUBrf+nvbRupSjMvqzwnQN0qwCmOxITt1cfywA==",
"dev": true,
"requires": {
"ajv": "^6.12.4",
@ -104,26 +104,10 @@
"fastq": "^1.6.0"
}
},
"@types/glob": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz",
"integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==",
"dev": true,
"requires": {
"@types/minimatch": "*",
"@types/node": "*"
}
},
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
"integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
"dev": true
},
"@types/node": {
"version": "14.6.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.4.tgz",
"integrity": "sha512-Wk7nG1JSaMfMpoMJDKUsWYugliB2Vy55pdjLpmLixeyMi7HizW2I/9QoxsPCkXl3dO+ZOVqPumKaDUv5zJu2uQ==",
"@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
"dev": true
},
"accepts": {
@ -308,6 +292,17 @@
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
"dev": true
},
"array-includes": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz",
"integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.0",
"is-string": "^1.0.5"
}
},
"array-initial": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz",
@ -380,6 +375,16 @@
"integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
"dev": true
},
"array.prototype.flat": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz",
"integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.0-next.1"
}
},
"assign-symbols": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
@ -985,6 +990,18 @@
}
}
},
"confusing-browser-globals": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz",
"integrity": "sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA==",
"dev": true
},
"contains-path": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz",
"integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=",
"dev": true
},
"content-disposition": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
@ -1118,7 +1135,6 @@
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
"dev": true,
"requires": {
"object-keys": "^1.0.12"
}
@ -1165,18 +1181,18 @@
}
},
"del": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/del/-/del-5.1.0.tgz",
"integrity": "sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==",
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz",
"integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==",
"dev": true,
"requires": {
"globby": "^10.0.1",
"graceful-fs": "^4.2.2",
"globby": "^11.0.1",
"graceful-fs": "^4.2.4",
"is-glob": "^4.0.1",
"is-path-cwd": "^2.2.0",
"is-path-inside": "^3.0.1",
"p-map": "^3.0.0",
"rimraf": "^3.0.0",
"is-path-inside": "^3.0.2",
"p-map": "^4.0.0",
"rimraf": "^3.0.2",
"slash": "^3.0.0"
},
"dependencies": {
@ -1317,6 +1333,66 @@
"is-arrayish": "^0.2.1"
}
},
"es-abstract": {
"version": "1.17.7",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz",
"integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==",
"dev": true,
"requires": {
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1",
"is-callable": "^1.2.2",
"is-regex": "^1.1.1",
"object-inspect": "^1.8.0",
"object-keys": "^1.1.1",
"object.assign": "^4.1.1",
"string.prototype.trimend": "^1.0.1",
"string.prototype.trimstart": "^1.0.1"
},
"dependencies": {
"es-abstract": {
"version": "1.18.0-next.1",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz",
"integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==",
"requires": {
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1",
"is-callable": "^1.2.2",
"is-negative-zero": "^2.0.0",
"is-regex": "^1.1.1",
"object-inspect": "^1.8.0",
"object-keys": "^1.1.1",
"object.assign": "^4.1.1",
"string.prototype.trimend": "^1.0.1",
"string.prototype.trimstart": "^1.0.1"
}
},
"object.assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz",
"integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==",
"requires": {
"define-properties": "^1.1.3",
"has-symbols": "^1.0.1",
"object-keys": "^1.1.1"
}
}
}
},
"es-to-primitive": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
"integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
"requires": {
"is-callable": "^1.1.4",
"is-date-object": "^1.0.1",
"is-symbol": "^1.0.2"
}
},
"es5-ext": {
"version": "0.10.53",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz",
@ -1389,13 +1465,13 @@
"dev": true
},
"eslint": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.11.0.tgz",
"integrity": "sha512-G9+qtYVCHaDi1ZuWzBsOWo2wSwd70TXnU6UHA3cTYHp7gCTXZcpggWFoUVAMRarg68qtPoNfFbzPh+VdOgmwmw==",
"version": "7.12.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.12.1.tgz",
"integrity": "sha512-HlMTEdr/LicJfN08LB3nM1rRYliDXOmfoO4vj39xN6BLpFzF00hbwBoqHk8UcJ2M/3nlARZWy/mslvGEuZFvsg==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"@eslint/eslintrc": "^0.1.3",
"@eslint/eslintrc": "^0.2.1",
"ajv": "^6.10.0",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
@ -1450,6 +1526,127 @@
}
}
},
"eslint-config-airbnb-base": {
"version": "14.2.0",
"resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.0.tgz",
"integrity": "sha512-Snswd5oC6nJaevs3nZoLSTvGJBvzTfnBqOIArkf3cbyTyq9UD79wOk8s+RiL6bhca0p/eRO6veczhf6A/7Jy8Q==",
"dev": true,
"requires": {
"confusing-browser-globals": "^1.0.9",
"object.assign": "^4.1.0",
"object.entries": "^1.1.2"
}
},
"eslint-import-resolver-node": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz",
"integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==",
"dev": true,
"requires": {
"debug": "^2.6.9",
"resolve": "^1.13.1"
}
},
"eslint-module-utils": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz",
"integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==",
"dev": true,
"requires": {
"debug": "^2.6.9",
"pkg-dir": "^2.0.0"
}
},
"eslint-plugin-import": {
"version": "2.22.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz",
"integrity": "sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw==",
"dev": true,
"requires": {
"array-includes": "^3.1.1",
"array.prototype.flat": "^1.2.3",
"contains-path": "^0.1.0",
"debug": "^2.6.9",
"doctrine": "1.5.0",
"eslint-import-resolver-node": "^0.3.4",
"eslint-module-utils": "^2.6.0",
"has": "^1.0.3",
"minimatch": "^3.0.4",
"object.values": "^1.1.1",
"read-pkg-up": "^2.0.0",
"resolve": "^1.17.0",
"tsconfig-paths": "^3.9.0"
},
"dependencies": {
"doctrine": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
"integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=",
"dev": true,
"requires": {
"esutils": "^2.0.2",
"isarray": "^1.0.0"
}
},
"find-up": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
"integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
"dev": true,
"requires": {
"locate-path": "^2.0.0"
}
},
"load-json-file": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
"integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
"dev": true,
"requires": {
"graceful-fs": "^4.1.2",
"parse-json": "^2.2.0",
"pify": "^2.0.0",
"strip-bom": "^3.0.0"
}
},
"path-type": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
"integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=",
"dev": true,
"requires": {
"pify": "^2.0.0"
}
},
"read-pkg": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
"integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=",
"dev": true,
"requires": {
"load-json-file": "^2.0.0",
"normalize-package-data": "^2.3.2",
"path-type": "^2.0.0"
}
},
"read-pkg-up": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
"integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=",
"dev": true,
"requires": {
"find-up": "^2.0.0",
"read-pkg": "^2.0.0"
}
},
"strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
"dev": true
}
}
},
"eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
@ -1862,9 +2059,9 @@
"dev": true
},
"fastq": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz",
"integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==",
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.9.0.tgz",
"integrity": "sha512-i7FVWL8HhVY+CTkwFxkN2mk3h+787ixS5S63eb78diVRc1MCssarHq3W5cj0av7YDSwmaV928RNag+U1etRQ7w==",
"dev": true,
"requires": {
"reusify": "^1.0.4"
@ -2073,8 +2270,7 @@
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"functional-red-black-tree": {
"version": "1.0.1",
@ -2216,18 +2412,16 @@
}
},
"globby": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz",
"integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==",
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz",
"integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==",
"dev": true,
"requires": {
"@types/glob": "^7.1.1",
"array-union": "^2.1.0",
"dir-glob": "^3.0.1",
"fast-glob": "^3.0.3",
"glob": "^7.1.3",
"ignore": "^5.1.1",
"merge2": "^1.2.3",
"fast-glob": "^3.1.1",
"ignore": "^5.1.4",
"merge2": "^1.3.0",
"slash": "^3.0.0"
},
"dependencies": {
@ -2452,6 +2646,14 @@
"glogg": "^1.0.0"
}
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"requires": {
"function-bind": "^1.1.1"
}
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@ -2461,8 +2663,7 @@
"has-symbols": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
"integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==",
"dev": true
"integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg=="
},
"has-value": {
"version": "1.0.0",
@ -2715,6 +2916,11 @@
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
"dev": true
},
"is-callable": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz",
"integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA=="
},
"is-data-descriptor": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
@ -2735,6 +2941,11 @@
}
}
},
"is-date-object": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz",
"integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g=="
},
"is-descriptor": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
@ -2787,6 +2998,11 @@
"integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=",
"dev": true
},
"is-negative-zero": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz",
"integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE="
},
"is-number": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
@ -2834,6 +3050,14 @@
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"dev": true
},
"is-regex": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz",
"integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==",
"requires": {
"has-symbols": "^1.0.1"
}
},
"is-relative": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz",
@ -2849,6 +3073,20 @@
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
"dev": true
},
"is-string": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz",
"integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==",
"dev": true
},
"is-symbol": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
"integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==",
"requires": {
"has-symbols": "^1.0.1"
}
},
"is-unc-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz",
@ -2965,6 +3203,15 @@
"integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
"dev": true
},
"json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"dev": true,
"requires": {
"minimist": "^1.2.0"
}
},
"just-debounce": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz",
@ -3053,6 +3300,24 @@
"strip-bom": "^2.0.0"
}
},
"locate-path": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
"integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
"dev": true,
"requires": {
"p-locate": "^2.0.0",
"path-exists": "^3.0.0"
},
"dependencies": {
"path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
"dev": true
}
}
},
"lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
@ -3396,11 +3661,15 @@
}
}
},
"object-inspect": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz",
"integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA=="
},
"object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"dev": true
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
},
"object-visit": {
"version": "1.0.1",
@ -3435,6 +3704,17 @@
"isobject": "^3.0.0"
}
},
"object.entries": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz",
"integrity": "sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.5",
"has": "^1.0.3"
}
},
"object.map": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz",
@ -3464,6 +3744,18 @@
"make-iterator": "^1.0.0"
}
},
"object.values": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz",
"integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.0-next.1",
"function-bind": "^1.1.1",
"has": "^1.0.3"
}
},
"on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
@ -3514,15 +3806,39 @@
"lcid": "^1.0.0"
}
},
"p-limit": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
"integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
"dev": true,
"requires": {
"p-try": "^1.0.0"
}
},
"p-locate": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
"integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
"dev": true,
"requires": {
"p-limit": "^1.1.0"
}
},
"p-map": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz",
"integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
"integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
"dev": true,
"requires": {
"aggregate-error": "^3.0.0"
}
},
"p-try": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
"integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
"dev": true
},
"param-case": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
@ -3686,6 +4002,26 @@
"pinkie": "^2.0.0"
}
},
"pkg-dir": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
"integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=",
"dev": true,
"requires": {
"find-up": "^2.1.0"
},
"dependencies": {
"find-up": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
"integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
"dev": true,
"requires": {
"locate-path": "^2.0.0"
}
}
}
},
"plugin-error": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz",
@ -4014,9 +4350,9 @@
}
},
"run-parallel": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz",
"integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==",
"version": "1.1.10",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz",
"integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==",
"dev": true
},
"safe-buffer": {
@ -4463,6 +4799,88 @@
}
}
},
"string.prototype.trimend": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.2.tgz",
"integrity": "sha512-8oAG/hi14Z4nOVP0z6mdiVZ/wqjDtWSLygMigTzAb+7aPEDTleeFf+WrF+alzecxIRkckkJVn+dTlwzJXORATw==",
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.18.0-next.1"
},
"dependencies": {
"es-abstract": {
"version": "1.18.0-next.1",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz",
"integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==",
"requires": {
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1",
"is-callable": "^1.2.2",
"is-negative-zero": "^2.0.0",
"is-regex": "^1.1.1",
"object-inspect": "^1.8.0",
"object-keys": "^1.1.1",
"object.assign": "^4.1.1",
"string.prototype.trimend": "^1.0.1",
"string.prototype.trimstart": "^1.0.1"
}
},
"object.assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz",
"integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==",
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.18.0-next.0",
"has-symbols": "^1.0.1",
"object-keys": "^1.1.1"
}
}
}
},
"string.prototype.trimstart": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.2.tgz",
"integrity": "sha512-7F6CdBTl5zyu30BJFdzSTlSlLPwODC23Od+iLoVH8X6+3fvDPPuBVVj9iaB1GOsSTSIgVfsfm27R2FGrAPznWg==",
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.18.0-next.1"
},
"dependencies": {
"es-abstract": {
"version": "1.18.0-next.1",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz",
"integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==",
"requires": {
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1",
"is-callable": "^1.2.2",
"is-negative-zero": "^2.0.0",
"is-regex": "^1.1.1",
"object-inspect": "^1.8.0",
"object-keys": "^1.1.1",
"object.assign": "^4.1.1",
"string.prototype.trimend": "^1.0.1",
"string.prototype.trimstart": "^1.0.1"
}
},
"object.assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz",
"integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==",
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.18.0-next.0",
"has-symbols": "^1.0.1",
"object-keys": "^1.1.1"
}
}
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@ -4672,6 +5090,26 @@
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
"dev": true
},
"tsconfig-paths": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz",
"integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==",
"dev": true,
"requires": {
"@types/json5": "^0.0.29",
"json5": "^1.0.1",
"minimist": "^1.2.0",
"strip-bom": "^3.0.0"
},
"dependencies": {
"strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
"dev": true
}
}
},
"type": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
@ -4915,9 +5353,9 @@
"dev": true
},
"v8-compile-cache": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz",
"integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz",
"integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==",
"dev": true
},
"v8flags": {

View file

@ -17,9 +17,11 @@
},
"homepage": "https://github.com/netbymatt/ws4kp#readme",
"devDependencies": {
"del": "^5.1.0",
"del": "^6.0.0",
"ejs": "^3.1.5",
"eslint": "^7.11.0",
"eslint": "^7.12.1",
"eslint-config-airbnb-base": "^14.2.0",
"eslint-plugin-import": "^2.22.1",
"express": "^4.17.1",
"gulp": "^4.0.2",
"gulp-clean-css": "^4.3.0",

View file

@ -2,3 +2,4 @@
[1020/120229.281:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
[1020/192548.246:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
[1026/100236.922:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
[1029/153152.790:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)

View file

@ -1,5 +1,5 @@
// eslint-disable-next-line no-unused-vars
const _RegionalCities = [
const RegionalCities = [
{
city: 'Atlanta',
lat: 33.749,

View file

@ -1,60 +1,59 @@
// eslint-disable-next-line no-unused-vars
const states = (() => {
const stateList = {
'Arizona': 'AZ',
'Alabama': 'AL',
'Alaska': 'AK',
'Arkansas': 'AR',
'California': 'CA',
'Colorado': 'CO',
'Connecticut': 'CT',
'Delaware': 'DE',
'Florida': 'FL',
'Georgia': 'GA',
'Hawaii': 'HI',
'Idaho': 'ID',
'Illinois': 'IL',
'Indiana': 'IN',
'Iowa': 'IA',
'Kansas': 'KS',
'Kentucky': 'KY',
'Louisiana': 'LA',
'Maine': 'ME',
'Maryland': 'MD',
'Massachusetts': 'MA',
'Michigan': 'MI',
'Minnesota': 'MN',
'Mississippi': 'MS',
'Missouri': 'MO',
'Montana': 'MT',
'Nebraska': 'NE',
'Nevada': 'NV',
Arizona: 'AZ',
Alabama: 'AL',
Alaska: 'AK',
Arkansas: 'AR',
California: 'CA',
Colorado: 'CO',
Connecticut: 'CT',
Delaware: 'DE',
Florida: 'FL',
Georgia: 'GA',
Hawaii: 'HI',
Idaho: 'ID',
Illinois: 'IL',
Indiana: 'IN',
Iowa: 'IA',
Kansas: 'KS',
Kentucky: 'KY',
Louisiana: 'LA',
Maine: 'ME',
Maryland: 'MD',
Massachusetts: 'MA',
Michigan: 'MI',
Minnesota: 'MN',
Mississippi: 'MS',
Missouri: 'MO',
Montana: 'MT',
Nebraska: 'NE',
Nevada: 'NV',
'New Hampshire': 'NH',
'New Jersey': 'NJ',
'New Mexico': 'NM',
'New York': 'NY',
'North Carolina': 'NC',
'North Dakota': 'ND',
'Ohio': 'OH',
'Oklahoma': 'OK',
'Oregon': 'OR',
'Pennsylvania': 'PA',
Ohio: 'OH',
Oklahoma: 'OK',
Oregon: 'OR',
Pennsylvania: 'PA',
'Rhode Island': 'RI',
'South Carolina': 'SC',
'South Dakota': 'SD',
'Tennessee': 'TN',
'Texas': 'TX',
'Utah': 'UT',
'Vermont': 'VT',
'Virginia': 'VA',
'Washington': 'WA',
Tennessee: 'TN',
Texas: 'TX',
Utah: 'UT',
Vermont: 'VT',
Virginia: 'VA',
Washington: 'WA',
'West Virginia': 'WV',
'Wisconsin': 'WI',
'Wyoming': 'WY',
Wisconsin: 'WI',
Wyoming: 'WY',
};
return {
getTwoDigitCode: (stateFullName) => stateList[stateFullName],
};
})();

View file

@ -1,4 +1,6 @@
const _StationInfo = {
// cspell: disable
// eslint-disable-next-line no-unused-vars
const StationInfo = {
MADC: {
id: 'MADC',
city: 'Durango Complex',

View file

@ -1,30 +1,5 @@
//Atlanta
//Boston
//Chicago
//Cleveland
//Dallas
//Denver
//Detroit
//Hartford
//Houston
//Indianapolis
//Los Angeles
//Miami
//Minneapolis
//New York
//Norfolk
//Orlando
//Philadelphia
//Pittsburgh
//St. Louis
//San Francisco
//Seattle
//Syracuse
//Tampa
//Washington DC
// eslint-disable-next-line no-unused-vars
const _TravelCities = [
const TravelCities = [
{
Name: 'Atlanta',
Latitude: 33.749,
@ -146,4 +121,3 @@ const _TravelCities = [
Longitude: -77.0364,
},
];

View file

@ -1,4 +1,3 @@
'use strict';
/* globals NoSleep, states, navigation, UNITS, utils */
document.addEventListener('DOMContentLoaded', () => {
index.init();
@ -8,17 +7,16 @@ const index = (() => {
const overrides = {
// '32899, Orlando, Florida, USA': { x: -80.6774, y: 28.6143 },
};
const _AutoRefreshIntervalMs = 500;
const _AutoRefreshTotalIntervalMs = 600000; // 10 min.
const _NoSleep = new NoSleep();
const AutoRefreshIntervalMs = 500;
const AutoRefreshTotalIntervalMs = 600000; // 10 min.
let _AutoSelectQuery = false;
let AutoSelectQuery = false;
let _LastUpdate = null;
let _AutoRefreshIntervalId = null;
let _AutoRefreshCountMs = 0;
let LastUpdate = null;
let AutoRefreshIntervalId = null;
let AutoRefreshCountMs = 0;
let _FullScreenOverride = false;
let FullScreenOverride = false;
const categories = [
'Land Features',
@ -37,20 +35,20 @@ const index = (() => {
e.target.select();
});
document.getElementById('NavigateMenu').addEventListener('click', btnNavigateMenu_click);
document.getElementById('NavigateRefresh').addEventListener('click', btnNavigateRefresh_click);
document.getElementById('NavigateNext').addEventListener('click', btnNavigateNext_click);
document.getElementById('NavigatePrevious').addEventListener('click', btnNavigatePrevious_click);
document.getElementById('NavigatePlay').addEventListener('click', btnNavigatePlay_click);
document.getElementById('ToggleFullScreen').addEventListener('click', btnFullScreen_click);
document.getElementById('btnGetGps').addEventListener('click', btnGetGps_click);
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);
document.getElementById('btnGetGps').addEventListener('click', btnGetGpsClick);
document.getElementById('divTwc').addEventListener('click', () => {
if (document.fullscreenElement) UpdateFullScreenNavigate();
});
document.addEventListener('keydown', document_keydown);
document.addEventListener('touchmove', e => { if (_FullScreenOverride) e.preventDefault(); });
document.addEventListener('keydown', documentKeydown);
document.addEventListener('touchmove', (e) => { if (FullScreenOverride) e.preventDefault(); });
$('#frmGetLatLng #txtAddress').devbridgeAutocomplete({
serviceUrl: 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/suggest',
@ -58,26 +56,24 @@ const index = (() => {
paramName: 'text',
params: {
f: 'json',
countryCode: 'USA', //'USA,PRI,VIR,GUM,ASM',
countryCode: 'USA', // 'USA,PRI,VIR,GUM,ASM',
category: cats,
maxSuggestions: 10,
},
dataType: 'json',
transformResult: (response) => {
if (_AutoSelectQuery) {
_AutoSelectQuery = false;
if (AutoSelectQuery) {
AutoSelectQuery = false;
window.setTimeout(() => {
$(ac.suggestionsContainer.children[0]).click();
}, 1);
}
return {
suggestions: $.map(response.suggestions, function (i) {
return {
suggestions: $.map(response.suggestions, (i) => ({
value: i.text,
data: i.magicKey,
};
}),
})),
};
},
minChars: 3,
@ -96,7 +92,7 @@ const index = (() => {
// Auto load the previous query
const TwcQuery = localStorage.getItem('TwcQuery');
if (TwcQuery) {
_AutoSelectQuery = true;
AutoSelectQuery = true;
const txtAddress = document.getElementById('txtAddress');
txtAddress.value = TwcQuery;
txtAddress.blur();
@ -141,7 +137,7 @@ const index = (() => {
document.getElementById('chkAutoRefresh').addEventListener('change', (e) => {
const Checked = e.target.checked;
if (_LastUpdate) {
if (LastUpdate) {
if (Checked) {
StartAutoRefreshTimer();
} else {
@ -162,12 +158,10 @@ const index = (() => {
// swipe functionality
document.getElementById('container').addEventListener('swiped-left', () => swipeCallBack('left'));
document.getElementById('container').addEventListener('swiped-right', () => swipeCallBack('right'));
};
const changeUnits = (e) => {
const Units = e.target.value;
e;
localStorage.setItem('TwcUnits', Units);
AssignLastUpdate();
postMessage('units', Units);
@ -192,20 +186,19 @@ const index = (() => {
if (loc) {
doRedirectToGeometry(loc.feature.geometry);
} else {
alert('An unexpected error occurred. Please try a different search string.');
console.error('An unexpected error occurred. Please try a different search string.');
}
}
};
const doRedirectToGeometry = (geom) => {
const latLon = {lat:Math.round2(geom.y, 4), lon:Math.round2(geom.x, 4)};
const latLon = { lat: Math.round2(geom.y, 4), lon: Math.round2(geom.x, 4) };
LoadTwcData(latLon);
// Save the query
localStorage.setItem('TwcQuery', document.getElementById('txtAddress').value);
};
const btnFullScreen_click = () => {
const btnFullScreenClick = () => {
if (!document.fullscreenElement) {
EnterFullScreen();
} else {
@ -213,9 +206,9 @@ const index = (() => {
}
if (navigation.isPlaying()) {
noSleepEnable();
noSleep(true);
} else {
noSleepDisable();
noSleep(false);
}
UpdateFullScreenNavigate();
@ -227,7 +220,8 @@ const index = (() => {
const element = document.getElementById('divTwc');
// Supports most browsers and their versions.
const requestMethod = element.requestFullScreen || element.webkitRequestFullScreen || element.mozRequestFullScreen || element.msRequestFullscreen;
const requestMethod = element.requestFullScreen || element.webkitRequestFullScreen
|| element.mozRequestFullScreen || element.msRequestFullscreen;
if (requestMethod) {
// Native full screen.
@ -235,7 +229,7 @@ const index = (() => {
} else {
// iOS doesn't support FullScreen API.
window.scrollTo(0, 0);
_FullScreenOverride = true;
FullScreenOverride = true;
}
UpdateFullScreenNavigate();
@ -244,8 +238,8 @@ const index = (() => {
const ExitFullscreen = () => {
// exit full-screen
if (_FullScreenOverride) {
_FullScreenOverride = false;
if (FullScreenOverride) {
FullScreenOverride = false;
}
if (document.exitFullscreen) {
@ -260,7 +254,7 @@ const index = (() => {
}
};
const btnNavigateMenu_click = () => {
const btnNavigateMenuClick = () => {
postMessage('navButton', 'menu');
UpdateFullScreenNavigate();
return false;
@ -270,134 +264,133 @@ const index = (() => {
// if latlon is provided store it locally
if (_latLon) LoadTwcData.latLon = _latLon;
// get the data
const latLon = LoadTwcData.latLon;
const { latLon } = LoadTwcData;
// if there's no data stop
if (!latLon) return;
document.getElementById('txtAddress').blur();
StopAutoRefreshTimer();
_LastUpdate = null;
LastUpdate = null;
AssignLastUpdate();
postMessage('latLon', latLon);
};
const swipeCallBack = (direction) => {
switch (direction) {
case 'left':
btnNavigateNext_click();
btnNavigateNextClick();
break;
case 'right':
default:
btnNavigatePrevious_click();
btnNavigatePreviousClick();
break;
}
};
const AssignLastUpdate = () => {
let LastUpdate = '(None)';
if (_LastUpdate) {
if (LastUpdate) {
switch (navigation.units()) {
case UNITS.english:
LastUpdate = _LastUpdate.toLocaleString('en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', timeZoneName: 'short' });
LastUpdate = LastUpdate.toLocaleString('en-US', {
weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', timeZoneName: 'short',
});
break;
default:
LastUpdate = _LastUpdate.toLocaleString('en-GB', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', timeZoneName: 'short' });
LastUpdate = LastUpdate.toLocaleString('en-GB', {
weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', timeZoneName: 'short',
});
break;
}
}
document.getElementById('spanLastRefresh').innerHTML = LastUpdate;
if (_LastUpdate && document.getElementById('chkAutoRefresh').checked) StartAutoRefreshTimer();
if (LastUpdate && document.getElementById('chkAutoRefresh').checked) StartAutoRefreshTimer();
};
const btnNavigateRefresh_click = () => {
const btnNavigateRefreshClick = () => {
LoadTwcData();
UpdateFullScreenNavigate();
return false;
};
const btnNavigateNext_click = () => {
const btnNavigateNextClick = () => {
postMessage('navButton', 'next');
UpdateFullScreenNavigate();
return false;
};
const btnNavigatePrevious_click = () => {
const btnNavigatePreviousClick = () => {
postMessage('navButton', 'previous');
UpdateFullScreenNavigate();
return false;
};
let _NavigateFadeIntervalId = null;
let NavigateFadeIntervalId = null;
const UpdateFullScreenNavigate = () => {
document.activeElement.blur();
document.getElementById('divTwcBottom').classList.remove('hidden');
document.getElementById('divTwcBottom').classList.add('visible');
if (_NavigateFadeIntervalId) {
clearTimeout(_NavigateFadeIntervalId);
_NavigateFadeIntervalId = null;
if (NavigateFadeIntervalId) {
clearTimeout(NavigateFadeIntervalId);
NavigateFadeIntervalId = null;
}
_NavigateFadeIntervalId = setTimeout(() => {
NavigateFadeIntervalId = setTimeout(() => {
if (document.fullscreenElement) {
document.getElementById('divTwcBottom').classList.remove('visible');
document.getElementById('divTwcBottom').classList.add('hidden');
}
}, 2000);
};
const document_keydown = (e) => {
const documentKeydown = (e) => {
const code = (e.keyCode || e.which);
if (document.fullscreenElement || document.activeElement === document.body) {
switch (code) {
case 32: // Space
btnNavigatePlay_click();
btnNavigatePlayClick();
return false;
case 39: // Right Arrow
case 34: // Page Down
btnNavigateNext_click();
btnNavigateNextClick();
return false;
case 37: // Left Arrow
case 33: // Page Up
btnNavigatePrevious_click();
btnNavigatePreviousClick();
return false;
case 36: // Home
btnNavigateMenu_click();
btnNavigateMenuClick();
return false;
case 48: // Restart
btnNavigateRefresh_click();
btnNavigateRefreshClick();
return false;
case 70: // F
btnFullScreen_click();
btnFullScreenClick();
return false;
default:
}
}
return false;
};
Math.round2 = (value, decimals) => Number(Math.round(value + 'e' + decimals) + 'e-' + decimals);
Math.round2 = (value, decimals) => Number(`${Math.round(`${value}e${decimals}`)}e-${decimals}`);
const btnNavigatePlay_click = () => {
const btnNavigatePlayClick = () => {
postMessage('navButton', 'playToggle');
UpdateFullScreenNavigate();
@ -411,7 +404,7 @@ const index = (() => {
if (!data.type) return;
switch (data.type) {
case 'loaded':
_LastUpdate = new Date();
LastUpdate = new Date();
AssignLastUpdate();
break;
@ -422,79 +415,74 @@ const index = (() => {
case 'isPlaying':
localStorage.setItem('TwcPlay', navigation.isPlaying());
if (navigation.isPlaying()) {
noSleepEnable();
noSleep(true);
playButton.title = 'Pause';
playButton.src = 'images/nav/ic_pause_white_24dp_1x.png';
} else {
noSleepDisable();
noSleep(false);
playButton.title = 'Play';
playButton.src = 'images/nav/ic_play_arrow_white_24dp_1x.png';
}
break;
default:
console.error(`Unknown event '${data.eventType}`);
}
};
// post a message to the iframe
const postMessage = (type, message = {}) => {
navigation.message({type, message});
const postMessage = (type, myMessage = {}) => {
navigation.message({ type, message: myMessage });
};
const StartAutoRefreshTimer = () => {
// Ensure that any previous timer has already stopped.
// check if timer is running
if (_AutoRefreshIntervalId) return;
if (AutoRefreshIntervalId) return;
// Reset the time elapsed.
_AutoRefreshCountMs = 0;
AutoRefreshCountMs = 0;
const AutoRefreshTimer = () => {
// Increment the total time elapsed.
_AutoRefreshCountMs += _AutoRefreshIntervalMs;
AutoRefreshCountMs += AutoRefreshIntervalMs;
// Display the count down.
let RemainingMs = (_AutoRefreshTotalIntervalMs - _AutoRefreshCountMs);
let RemainingMs = (AutoRefreshTotalIntervalMs - AutoRefreshCountMs);
if (RemainingMs < 0) {
RemainingMs = 0;
}
const dt = new Date(RemainingMs);
document.getElementById('spanRefreshCountDown').innerHTML = (dt.getMinutes() < 10 ? '0' + dt.getMinutes() : dt.getMinutes()) + ':' + (dt.getSeconds() < 10 ? '0' + dt.getSeconds() : dt.getSeconds());
document.getElementById('spanRefreshCountDown').innerHTML = `${dt.getMinutes() < 10 ? `0${dt.getMinutes()}` : dt.getMinutes()}:${dt.getSeconds() < 10 ? `0${dt.getSeconds()}` : dt.getSeconds()}`;
// Time has elapsed.
if (_AutoRefreshCountMs >= _AutoRefreshTotalIntervalMs) LoadTwcData();
if (AutoRefreshCountMs >= AutoRefreshTotalIntervalMs) LoadTwcData();
};
_AutoRefreshIntervalId = window.setInterval(AutoRefreshTimer, _AutoRefreshIntervalMs);
AutoRefreshIntervalId = window.setInterval(AutoRefreshTimer, AutoRefreshIntervalMs);
AutoRefreshTimer();
};
const StopAutoRefreshTimer = () => {
if (_AutoRefreshIntervalId) {
window.clearInterval(_AutoRefreshIntervalId);
if (AutoRefreshIntervalId) {
window.clearInterval(AutoRefreshIntervalId);
document.getElementById('spanRefreshCountDown').innerHTML = '--:--';
_AutoRefreshIntervalId = null;
AutoRefreshIntervalId = null;
}
};
const btnGetGps_click = async () => {
const btnGetGpsClick = async () => {
if (!navigator.geolocation) return;
const position = await (() => {
return new Promise(resolve => {
const position = await (() => new Promise((resolve) => {
navigator.geolocation.getCurrentPosition(resolve);
});
})();
const latitude = position.coords.latitude;
const longitude = position.coords.longitude;
}))();
const { latitude, longitude } = position.coords;
let data;
try {
data = await utils.fetch.json('https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/reverseGeocode', {
data: {
location: longitude + ',' + latitude,
location: `${longitude},${latitude}`,
distance: 1000, // Find location up to 1 KM.
f: 'json',
},
@ -504,7 +492,7 @@ const index = (() => {
console.error(e.status, e.responseJSONe);
}
const ZipCode = data.address.Postal;
const City = data.address.City;
const { City } = data.address;
const State = states.getTwoDigitCode(data.address.Region);
const Country = data.address.CountryCode;
const TwcQuery = `${ZipCode}, ${City}, ${State}, ${Country}`;
@ -519,29 +507,30 @@ const index = (() => {
};
const populateWeatherParameters = (weatherParameters) => {
document.getElementById('spanCity').innerHTML = weatherParameters.city + ', ';
document.getElementById('spanCity').innerHTML = `${weatherParameters.city}, `;
document.getElementById('spanState').innerHTML = weatherParameters.state;
document.getElementById('spanStationId').innerHTML = weatherParameters.stationId;
document.getElementById('spanRadarId').innerHTML = weatherParameters.radarId;
document.getElementById('spanZoneId').innerHTML = weatherParameters.zoneId;
};
// track state of nosleep locally to avoid a null case error when nosleep.disable is called without first calling .enable
// track state of nosleep locally to avoid a null case error
// when nosleep.disable is called without first calling .enable
let wakeLock = false;
const noSleepEnable = () => {
_NoSleep.enable();
wakeLock = true;
};
const noSleepDisable = () => {
if (!wakeLock) return;
_NoSleep.disable();
wakeLock = false;
const noSleep = (enable = false) => {
// get a nosleep controller
if (!noSleep.controller) noSleep.controller = new NoSleep();
// don't call anything if the states match
if (wakeLock === enable) return false;
// store the value
wakeLock = enable;
// call the function
if (enable) return noSleep.controller.enable();
return noSleep.controller.disable();
};
return {
init,
message,
};
})();

View file

@ -4,8 +4,8 @@
// eslint-disable-next-line no-unused-vars
class Almanac extends WeatherDisplay {
constructor(navId,elemId) {
super(navId,elemId,'Almanac');
constructor(navId, elemId) {
super(navId, elemId, 'Almanac');
// pre-load background images (returns promises)
this.backgroundImage0 = utils.image.load('images/BackGround3_1.png');
@ -20,12 +20,11 @@ class Almanac extends WeatherDisplay {
];
this.timing.totalScreens = 2;
}
async getData(weatherParameters) {
super.getData(weatherParameters);
if (!weatherParameters) weatherParameters = this.weatherParameters;
async getData(_weatherParameters) {
super.getData(_weatherParameters);
const weatherParameters = _weatherParameters ?? this.weatherParameters;
// get images for outlook
const imagePromises = [
@ -34,12 +33,12 @@ class Almanac extends WeatherDisplay {
];
// get sun/moon data
const {sun, moon} = this.calcSunMoonData(weatherParameters);
const { sun, moon } = this.calcSunMoonData(weatherParameters);
// process images for outlook
const [outlookTemp, outlookPrecip] = await Promise.all(imagePromises);
const outlook = this.parseOutlooks(weatherParameters.latitude, weatherParameters.longitude, outlookTemp, outlookPrecip);
const outlook = Almanac.parseOutlooks(weatherParameters.latitude, weatherParameters.longitude, outlookTemp, outlookPrecip);
// store the data
this.data = {
@ -52,28 +51,27 @@ class Almanac extends WeatherDisplay {
// share data
this.getDataCallback();
}
calcSunMoonData(weatherParameters) {
const {DateTime} = luxon;
const { DateTime } = luxon;
const sun = [
SunCalc.getTimes(new Date(), weatherParameters.latitude, weatherParameters.longitude),
SunCalc.getTimes(DateTime.local().plus({days:1}).toJSDate(), weatherParameters.latitude, weatherParameters.longitude),
SunCalc.getTimes(DateTime.local().plus({ days: 1 }).toJSDate(), weatherParameters.latitude, weatherParameters.longitude),
];
// brute force the moon phases by scanning the next 30 days
const moon = [];
// start with yesterday
let moonDate = DateTime.local().minus({days:1});
let phase = SunCalc.getMoonIllumination(moonDate.toJSDate()).phase;
let moonDate = DateTime.local().minus({ days: 1 });
let { phase } = SunCalc.getMoonIllumination(moonDate.toJSDate());
let iterations = 0;
do {
// get yesterday's moon info
const lastPhase = phase;
// calculate new values
moonDate = moonDate.plus({days:1});
moonDate = moonDate.plus({ days: 1 });
phase = SunCalc.getMoonIllumination(moonDate.toJSDate()).phase;
// check for 4 cases
if (lastPhase < 0.25 && phase >= 0.25) moon.push(this.getMoonTransition(0.25, 'First', moonDate));
@ -82,7 +80,7 @@ class Almanac extends WeatherDisplay {
if (lastPhase > phase) moon.push(this.getMoonTransition(0.00, 'New', moonDate));
// stop after 30 days or 4 moon phases
iterations++;
iterations += 1;
} while (iterations <= 30 && moon.length < 4);
return {
@ -94,19 +92,19 @@ class Almanac extends WeatherDisplay {
// get moon transition from one phase to the next by drilling down by hours, minutes and seconds
getMoonTransition(threshold, phaseName, start, iteration = 0) {
let moonDate = start;
let phase = SunCalc.getMoonIllumination(moonDate.toJSDate()).phase;
let { phase } = SunCalc.getMoonIllumination(moonDate.toJSDate());
let iterations = 0;
const step = {
hours: iteration === 0 ? -1:0,
minutes: iteration === 1 ? 1:0,
seconds: iteration === 2 ? -1:0,
milliseconds: iteration === 3 ? 1:0,
hours: iteration === 0 ? -1 : 0,
minutes: iteration === 1 ? 1 : 0,
seconds: iteration === 2 ? -1 : 0,
milliseconds: iteration === 3 ? 1 : 0,
};
// increasing test
let test = (lastPhase,phase,threshold) => lastPhase < threshold && phase >= threshold;
let test = (lastPhase, testPhase) => lastPhase < threshold && testPhase >= threshold;
// decreasing test
if (iteration%2===0) test = (lastPhase,phase,threshold) => lastPhase > threshold && phase <= threshold;
if (iteration % 2 === 0) test = (lastPhase, testPhase) => lastPhase > threshold && testPhase <= threshold;
do {
// store last phase
@ -117,42 +115,42 @@ class Almanac extends WeatherDisplay {
// wrap phases > 0.9 to -0.1 for ease of detection
if (phase > 0.9) phase -= 1.0;
// compare
if (test(lastPhase, phase, threshold)) {
if (test(lastPhase, phase)) {
// last iteration is three, return value
if (iteration >= 3) break;
// iterate recursively
return this.getMoonTransition(threshold, phaseName, moonDate, iteration+1);
return this.getMoonTransition(threshold, phaseName, moonDate, iteration + 1);
}
iterations++;
iterations += 1;
} while (iterations < 1000);
return {phase: phaseName, date: moonDate};
return { phase: phaseName, date: moonDate };
}
// use the color of the pixel to determine the outlook
parseOutlooks(lat, lon, temp, precip) {
const {DateTime} = luxon;
static parseOutlooks(lat, lon, temp, precip) {
const { DateTime } = luxon;
const month = DateTime.local();
const thisMonth = month.toLocaleString({month: 'short'});
const nextMonth = month.plus({months: 1}).toLocaleString({month: 'short'});
const thisMonth = month.toLocaleString({ month: 'short' });
const nextMonth = month.plus({ months: 1 }).toLocaleString({ month: 'short' });
// draw the images on the canvases
const tempContext = utils.image.drawLocalCanvas(temp);
const precipContext = utils.image.drawLocalCanvas(precip);
// get the color from each canvas
const tempColor = this.getOutlookColor(lat, lon, tempContext);
const precipColor = this.getOutlookColor(lat, lon, precipContext);
const tempColor = Almanac.getOutlookColor(lat, lon, tempContext);
const precipColor = Almanac.getOutlookColor(lat, lon, precipContext);
return {
thisMonth,
nextMonth,
temperature: this.getOutlookTemperatureIndicator(tempColor),
precipitation: this.getOutlookPrecipitationIndicator(precipColor),
temperature: Almanac.getOutlookTemperatureIndicator(tempColor),
precipitation: Almanac.getOutlookPrecipitationIndicator(precipColor),
};
}
getOutlookColor (lat, lon, context) {
static getOutlookColor(lat, lon, context) {
let x = 0;
let y = 0;
@ -195,11 +193,11 @@ class Almanac extends WeatherDisplay {
// Determine if there is any "non-white" colors around the area.
// Search a 16x16 region.
for (let colorX = x - 8; colorX <= x + 8; colorX++) {
for (let colorY = y - 8; colorY <= y + 8; colorY++) {
const pixelColor = this.getPixelColor(context, colorX, colorY);
if ((pixelColor.r !== 0 && pixelColor.g !== 0 && pixelColor.b !== 0) ||
(pixelColor.r !== 255 && pixelColor.g !== 255 && pixelColor.b !== 255)) {
for (let colorX = x - 8; colorX <= x + 8; colorX += 1) {
for (let colorY = y - 8; colorY <= y + 8; colorY += 1) {
const pixelColor = Almanac.getPixelColor(context, colorX, colorY);
if ((pixelColor.r !== 0 && pixelColor.g !== 0 && pixelColor.b !== 0)
|| (pixelColor.r !== 255 && pixelColor.g !== 255 && pixelColor.b !== 255)) {
return pixelColor;
}
}
@ -209,7 +207,7 @@ class Almanac extends WeatherDisplay {
}
// get rgb values of a pixel
getPixelColor (context, x, y) {
static getPixelColor(context, x, y) {
const pixelData = context.getImageData(x, y, 1, 1).data;
return {
r: pixelData[0],
@ -219,33 +217,31 @@ class Almanac extends WeatherDisplay {
}
// get temperature outlook from color
getOutlookTemperatureIndicator(pixelColor) {
static getOutlookTemperatureIndicator(pixelColor) {
if (pixelColor.b > pixelColor.r) {
return 'Below Normal';
} else if (pixelColor.r > pixelColor.b) {
} if (pixelColor.r > pixelColor.b) {
return 'Above Normal';
} else {
return 'Normal';
}
return 'Normal';
}
// get precipitation outlook from color
getOutlookPrecipitationIndicator (pixelColor) {
static getOutlookPrecipitationIndicator(pixelColor) {
if (pixelColor.g > pixelColor.r) {
return 'Above Normal';
} else if (pixelColor.r > pixelColor.g) {
} if (pixelColor.r > pixelColor.g) {
return 'Below Normal';
} else {
return 'Normal';
}
return 'Normal';
}
async drawCanvas() {
super.drawCanvas();
const info = this.data;
const {DateTime} = luxon;
const { DateTime } = luxon;
const Today = DateTime.local();
const Tomorrow = Today.plus({days: 1});
const Tomorrow = Today.plus({ days: 1 });
// extract moon images
const [FullMoonImage, LastMoonImage, NewMoonImage, FirstMoonImage] = await Promise.all(this.moonImages);
@ -261,8 +257,8 @@ class Almanac extends WeatherDisplay {
draw.titleText(this.context, 'Almanac', 'Astronomical');
draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 320, 120, Today.toLocaleString({weekday: 'long'}), 2, 'center');
draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 500, 120, Tomorrow.toLocaleString({weekday: 'long'}), 2, 'center');
draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 320, 120, Today.toLocaleString({ weekday: 'long' }), 2, 'center');
draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 500, 120, Tomorrow.toLocaleString({ weekday: 'long' }), 2, 'center');
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 70, 150, 'Sunrise:', 2);
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 270, 150, DateTime.fromJSDate(info.sun[0].sunrise).toLocaleString(DateTime.TIME_SIMPLE).toLowerCase(), 2);
@ -274,12 +270,11 @@ class Almanac extends WeatherDisplay {
draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 70, 220, 'Moon Data:', 2);
info.moon.forEach((MoonPhase, Index) => {
const date = MoonPhase.date.toLocaleString({month: 'short', day: 'numeric'});
const date = MoonPhase.date.toLocaleString({ month: 'short', day: 'numeric' });
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 120+Index*130, 260, MoonPhase.phase, 2, 'center');
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 120+Index*130, 390, date, 2, 'center');
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 120 + Index * 130, 260, MoonPhase.phase, 2, 'center');
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 120 + Index * 130, 390, date, 2, 'center');
const image = (() => {
switch (MoonPhase.phase) {
@ -294,11 +289,11 @@ class Almanac extends WeatherDisplay {
return FirstMoonImage;
}
})();
this.context.drawImage(image, 75+Index*130, 270);
this.context.drawImage(image, 75 + Index * 130, 270);
});
break;
case 1:
case 1: {
this.context.drawImage(await this.backgroundImage1, 0, 0);
draw.horizontalGradientSingle(this.context, 0, 30, 500, 90, draw.topColor1, draw.topColor2);
draw.triangle(this.context, 'rgb(28, 10, 87)', 500, 30, 450, 90, 500, 90);
@ -309,14 +304,15 @@ class Almanac extends WeatherDisplay {
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 320, 180, '30 Day Outlook', 2, 'center');
var DateRange = 'MID-' + info.outlook.thisMonth.toUpperCase() + ' TO MID-' + info.outlook.nextMonth.toUpperCase();
const DateRange = `MID-${info.outlook.thisMonth.toUpperCase()} TO MID-${info.outlook.nextMonth.toUpperCase()}`;
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 320, 220, DateRange, 2, 'center');
var Temperature = info.outlook.temperature;
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 70, 300, 'Temperatures: ' + Temperature, 2);
const Temperature = info.outlook.temperature;
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 70, 300, `Temperatures: ${Temperature}`, 2);
var Precipitation = info.outlook.precipitation;
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 70, 380, 'Precipitation: ' + Precipitation, 2);
const Precipitation = info.outlook.precipitation;
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 70, 380, `Precipitation: ${Precipitation}`, 2);
}
}
this.finishDraw();

View file

@ -3,27 +3,28 @@
// eslint-disable-next-line no-unused-vars
class CurrentWeather extends WeatherDisplay {
constructor(navId,elemId) {
super(navId,elemId,'Current Conditions');
constructor(navId, elemId) {
super(navId, elemId, 'Current Conditions');
// pre-load background image (returns promise)
this.backgroundImage = utils.image.load('images/BackGround1_1.png');
}
async getData(weatherParameters) {
super.getData(weatherParameters);
if (!weatherParameters) weatherParameters = this.weatherParameters;
async getData(_weatherParameters) {
super.getData(_weatherParameters);
const weatherParameters = _weatherParameters ?? this.weatherParameters;
// Load the observations
let observations, station;
let observations; let
station;
// station number counter
let stationNum = 0;
while (!observations && stationNum < weatherParameters.stations.length) {
// get the station
station = weatherParameters.stations[stationNum];
stationNum++;
stationNum += 1;
try {
// station observations
observations = await utils.fetch.json(`${station.id}/observations`,{
observations = await utils.fetch.json(`${station.id}/observations`, {
cors: true,
data: {
limit: 2,
@ -31,9 +32,9 @@ class CurrentWeather extends WeatherDisplay {
});
// test data quality
if (observations.features[0].properties.temperature.value === null ||
observations.features[0].properties.windSpeed.value === null ||
observations.features[0].properties.textDescription === null) {
if (observations.features[0].properties.temperature.value === null
|| observations.features[0].properties.windSpeed.value === null
|| observations.features[0].properties.textDescription === null) {
observations = undefined;
throw new Error(`Unable to get observations: ${station.properties.stationIdentifier}, trying next station`);
}
@ -53,7 +54,7 @@ class CurrentWeather extends WeatherDisplay {
utils.image.preload(icons.getWeatherIconFromIconLink(observations.features[0].properties.icon));
// we only get here if there was no error above
this.data = Object.assign({}, observations, {station: station});
this.data = { ...observations, station };
this.setStatus(STATUS.loaded);
this.getDataCallback();
@ -71,7 +72,7 @@ class CurrentWeather extends WeatherDisplay {
data.DewPoint = Math.round(observations.dewpoint.value);
data.Ceiling = Math.round(observations.cloudLayers[0].base.value);
data.CeilingUnit = 'm.';
data.Visibility = Math.round(observations.visibility.value/1000);
data.Visibility = Math.round(observations.visibility.value / 1000);
data.VisibilityUnit = ' km.';
data.WindSpeed = Math.round(observations.windSpeed.value);
data.WindDirection = utils.calc.directionToNSEW(observations.windDirection.value);
@ -95,9 +96,9 @@ class CurrentWeather extends WeatherDisplay {
data.Temperature = utils.units.celsiusToFahrenheit(data.Temperature);
data.TemperatureUnit = 'F';
data.DewPoint = utils.units.celsiusToFahrenheit(data.DewPoint);
data.Ceiling = Math.round(utils.units.metersToFeet(data.Ceiling)/100)*100;
data.Ceiling = Math.round(utils.units.metersToFeet(data.Ceiling) / 100) * 100;
data.CeilingUnit = 'ft.';
data.Visibility = utils.units.kilometersToMiles(observations.visibility.value/1000);
data.Visibility = utils.units.kilometersToMiles(observations.visibility.value / 1000);
data.VisibilityUnit = ' mi.';
data.WindSpeed = utils.units.kphToMph(data.WindSpeed);
data.WindUnit = 'MPH';
@ -109,7 +110,7 @@ class CurrentWeather extends WeatherDisplay {
return data;
}
async drawCanvas () {
async drawCanvas() {
super.drawCanvas();
// parse each time to deal with a change in units if necessary
const data = this.parseData();
@ -131,14 +132,14 @@ class CurrentWeather extends WeatherDisplay {
draw.text(this.context, 'Star4000 Extended', '24pt', '#FFFFFF', 195, 170, Conditions, 2, 'center');
draw.text(this.context, 'Star4000 Extended', '24pt', '#FFFFFF', 80, 330, 'Wind:', 2);
draw.text(this.context, 'Star4000 Extended', '24pt', '#FFFFFF', 300, 330, data.WindDirection + ' ' + data.WindSpeed, 2, 'right');
draw.text(this.context, 'Star4000 Extended', '24pt', '#FFFFFF', 300, 330, `${data.WindDirection} ${data.WindSpeed}`, 2, 'right');
if (data.WindGust) draw.text(this.context, 'Star4000 Extended', '24pt', '#FFFFFF', 80, 375, 'Gusts to ' + data.WindGust, 2);
if (data.WindGust) draw.text(this.context, 'Star4000 Extended', '24pt', '#FFFFFF', 80, 375, `Gusts to ${data.WindGust}`, 2);
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFF00', 315, 120, this.data.station.properties.name.substr(0, 20), 2);
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 340, 165, 'Humidity:', 2);
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 560, 165, data.Humidity + '%', 2, 'right');
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 560, 165, `${data.Humidity}%`, 2, 'right');
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 340, 205, 'Dewpoint:', 2);
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 560, 205, data.DewPoint + String.fromCharCode(176), 2, 'right');
@ -213,7 +214,8 @@ class CurrentWeather extends WeatherDisplay {
});
}
shortConditions(condition) {
static shortConditions(_condition) {
let condition = _condition;
condition = condition.replace(/Light/g, 'L');
condition = condition.replace(/Heavy/g, 'H');
condition = condition.replace(/Partly/g, 'P');
@ -230,5 +232,4 @@ class CurrentWeather extends WeatherDisplay {
condition = condition.replace(/ with /g, '/');
return condition;
}
}

View file

@ -1,5 +1,3 @@
'use strict';
/* globals draw, navigation */
// eslint-disable-next-line no-unused-vars
@ -10,7 +8,6 @@ const currentWeatherScroll = (() => {
// local variables
let context; // currently active context
let blankDrawArea; // original state of context
let station;
let interval;
let screenIndex = 0;
@ -36,7 +33,6 @@ const currentWeatherScroll = (() => {
// draw the data
drawScreen();
};
const stop = (reset) => {
@ -53,7 +49,7 @@ const currentWeatherScroll = (() => {
// increment interval, roll over
const incrementInterval = () => {
screenIndex = (screenIndex+1)%(screens.length);
screenIndex = (screenIndex + 1) % (screens.length);
// draw new text
drawScreen();
};
@ -74,7 +70,7 @@ const currentWeatherScroll = (() => {
// the "screens" are stored in an array for easy addition and removal
const screens = [
// station name
(data) => `Conditions at ${data.station.properties.name.substr(0,20)}`,
(data) => `Conditions at ${data.station.properties.name.substr(0, 20)}`,
// temperature
(data) => {
@ -108,7 +104,7 @@ const currentWeatherScroll = (() => {
},
// visibility
(data) => `Visib: ${data.Visibility} ${data.VisibilityUnit} Ceiling: ${data.Ceiling===0?'Unlimited':`${data.Ceiling} ${data.CeilingUnit}`}`,
(data) => `Visib: ${data.Visibility} ${data.VisibilityUnit} Ceiling: ${data.Ceiling === 0 ? 'Unlimited' : `${data.Ceiling} ${data.CeilingUnit}`}`,
];
// internal draw function with preset parameters

View file

@ -46,17 +46,17 @@ const draw = (() => {
}
};
const text = (context, font, size, color, x, y, text, shadow = 0, align = 'start') => {
const text = (context, font, size, color, x, y, myText, shadow = 0, align = 'start') => {
context.textAlign = align;
context.font = size + ` '${font}'`;
context.font = `${size} '${font}'`;
context.shadowColor = '#000000';
context.shadowOffsetX = shadow;
context.shadowOffsetY = shadow;
context.strokeStyle = '#000000';
context.lineWidth = 2;
context.strokeText(text, x, y);
context.strokeText(myText, x, y);
context.fillStyle = color;
context.fillText(text, x, y);
context.fillText(myText, x, y);
context.fillStyle = '';
context.strokeStyle = '';
context.shadowOffsetX = 0;

View file

@ -5,8 +5,8 @@
// eslint-disable-next-line no-unused-vars
class ExtendedForecast extends WeatherDisplay {
constructor(navId,elemId) {
super(navId,elemId,'Extended Forecast');
constructor(navId, elemId) {
super(navId, elemId, 'Extended Forecast');
// set timings
this.timing.totalScreens = 2;
@ -15,17 +15,16 @@ class ExtendedForecast extends WeatherDisplay {
this.backgroundImage = utils.image.load('images/BackGround2_1.png');
}
async getData(weatherParameters) {
super.getData(weatherParameters);
if (!weatherParameters) weatherParameters = this.weatherParameters;
async getData(_weatherParameters) {
super.getData(_weatherParameters);
const weatherParameters = _weatherParameters ?? this.weatherParameters;
// request us or si units
let units = 'us';
if (navigation.units() === UNITS.metric) units = 'si';
let forecast;
try {
forecast = await utils.fetch.json(weatherParameters.forecast,{
forecast = await utils.fetch.json(weatherParameters.forecast, {
data: {
units,
},
@ -37,32 +36,36 @@ class ExtendedForecast extends WeatherDisplay {
return;
}
// we only get here if there was no error above
this.data = this.parseExtendedForecast(forecast.properties.periods);
this.data = ExtendedForecast.parse(forecast.properties.periods);
this.screenIndex = 0;
this.setStatus(STATUS.loaded);
}
// the api provides the forecast in 12 hour increments, flatten to day increments with high and low temperatures
parseExtendedForecast(fullForecast) {
static parse(fullForecast) {
// create a list of days starting with today
const _Days = [0, 1, 2, 3, 4, 5, 6];
const Days = [0, 1, 2, 3, 4, 5, 6];
const dates = _Days.map(shift => {
const date = luxon.DateTime.local().startOf('day').plus({days:shift});
return date.toLocaleString({weekday: 'short'});
const dates = Days.map((shift) => {
const date = luxon.DateTime.local().startOf('day').plus({ days: shift });
return date.toLocaleString({ weekday: 'short' });
});
// track the destination forecast index
let destIndex = 0;
const forecast = [];
fullForecast.forEach(period => {
fullForecast.forEach((period) => {
// create the destination object if necessary
if (!forecast[destIndex]) forecast.push({dayName:'', low: undefined, high: undefined, text: undefined, icon: undefined});
if (!forecast[destIndex]) {
forecast.push({
dayName: '', low: undefined, high: undefined, text: undefined, icon: undefined,
});
}
// get the object to modify/populate
const fDay = forecast[destIndex];
// high temperature will always be last in the source array so it will overwrite the low values assigned below
fDay.icon = icons.getWeatherIconFromIconLink(period.icon);
fDay.text = this.shortenExtendedForecastText(period.shortForecast);
fDay.text = ExtendedForecast.shortenExtendedForecastText(period.shortForecast);
fDay.dayName = dates[destIndex];
// preload the icon
@ -71,7 +74,7 @@ class ExtendedForecast extends WeatherDisplay {
if (period.isDaytime) {
// day time is the high temperature
fDay.high = period.temperature;
destIndex++;
destIndex += 1;
} else {
// low temperature
fDay.low = period.temperature;
@ -81,7 +84,7 @@ class ExtendedForecast extends WeatherDisplay {
return forecast;
}
shortenExtendedForecastText(long) {
static shortenExtendedForecastText(long) {
let short = long;
short = short.replace(/ and /g, ' ');
short = short.replace(/Slight /g, '');
@ -112,7 +115,7 @@ class ExtendedForecast extends WeatherDisplay {
}
short = short1;
if (short2 !== '') {
short += ' ' + short2;
short += ` ${short2}`;
}
return [short, short1, short2];
@ -123,7 +126,7 @@ class ExtendedForecast extends WeatherDisplay {
// determine bounds
// grab the first three or second set of three array elements
const forecast = this.data.slice(0+3*this.screenIndex, 3+this.screenIndex*3);
const forecast = this.data.slice(0 + 3 * this.screenIndex, 3 + this.screenIndex * 3);
const backgroundImage = await this.backgroundImage;
@ -138,27 +141,27 @@ class ExtendedForecast extends WeatherDisplay {
draw.titleText(this.context, 'Extended', 'Forecast');
await Promise.all(forecast.map(async (Day, Index) => {
const offset = Index*195;
draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 100+offset, 135, Day.dayName.toUpperCase(), 2);
draw.text(this.context, 'Star4000', '24pt', '#8080FF', 85+offset, 345, 'Lo', 2, 'center');
draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 165+offset, 345, 'Hi', 2, 'center');
let low = Day.low;
const offset = Index * 195;
draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 100 + offset, 135, Day.dayName.toUpperCase(), 2);
draw.text(this.context, 'Star4000', '24pt', '#8080FF', 85 + offset, 345, 'Lo', 2, 'center');
draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 165 + offset, 345, 'Hi', 2, 'center');
let { low } = Day;
if (low !== undefined) {
if (navigation.units() === UNITS.metric) low = utils.units.rahrenheitToCelsius(low);
draw.text(this.context, 'Star4000 Large', '24pt', '#FFFFFF', 85+offset, 385, low, 2, 'center');
if (navigation.units() === UNITS.metric) low = utils.units.fahrenheitToCelsius(low);
draw.text(this.context, 'Star4000 Large', '24pt', '#FFFFFF', 85 + offset, 385, low, 2, 'center');
}
let high = Day.high;
if (navigation.units() === UNITS.metric) high = utils.units.rahrenheitToCelsius(high);
draw.text(this.context, 'Star4000 Large', '24pt', '#FFFFFF', 165+offset, 385, high, 2, 'center');
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 120+offset, 270, Day.text[1], 2, 'center');
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 120+offset, 310, Day.text[2], 2, 'center');
let { high } = Day;
if (navigation.units() === UNITS.metric) high = utils.units.fahrenheitToCelsius(high);
draw.text(this.context, 'Star4000 Large', '24pt', '#FFFFFF', 165 + offset, 385, high, 2, 'center');
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 120 + offset, 270, Day.text[1], 2, 'center');
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 120 + offset, 310, Day.text[2], 2, 'center');
// draw the icon
this.gifs.push(await utils.image.superGifAsync({
src: Day.icon,
auto_play: true,
canvas: this.canvas,
x: 70 + Index*195,
x: 70 + Index * 195,
y: 150,
max_height: 75,
}));

View file

@ -16,10 +16,10 @@ class Hourly extends WeatherDisplay {
this.timing.baseDelay = 20;
// 24 hours = 6 pages
const pages = 4; // first page is already displayed, last page doesn't happen
const timingStep = this.hourHeight*4;
this.timing.delay = [150+timingStep];
const timingStep = this.hourHeight * 4;
this.timing.delay = [150 + timingStep];
// add additional pages
for (let i = 0; i < pages; i++) this.timing.delay.push(timingStep);
for (let i = 0; i < pages; i += 1) this.timing.delay.push(timingStep);
// add the final 3 second delay
this.timing.delay.push(150);
}
@ -37,34 +37,36 @@ class Hourly extends WeatherDisplay {
this.setStatus(STATUS.failed);
}
this.data = await this.parseForecast(forecast.properties);
this.data = await Hourly.parseForecast(forecast.properties);
this.setStatus(STATUS.loaded);
this.drawLongCanvas();
}
// extract specific values from forecast and format as an array
async parseForecast(data) {
const temperature = this.expand(data.temperature.values);
const apparentTemperature = this.expand(data.apparentTemperature.values);
const windSpeed = this.expand(data.windSpeed.values);
const windDirection = this.expand(data.windDirection.values);
const skyCover = this.expand(data.skyCover.values); // cloud icon
const weather = this.expand(data.weather.values); // fog icon
const iceAccumulation = this.expand(data.iceAccumulation.values); // ice icon
const probabilityOfPrecipitation = this.expand(data.probabilityOfPrecipitation.values); // rain icon
const snowfallAmount = this.expand(data.snowfallAmount.values); // snow icon
static async parseForecast(data) {
const temperature = Hourly.expand(data.temperature.values);
const apparentTemperature = Hourly.expand(data.apparentTemperature.values);
const windSpeed = Hourly.expand(data.windSpeed.values);
const windDirection = Hourly.expand(data.windDirection.values);
const skyCover = Hourly.expand(data.skyCover.values); // cloud icon
const weather = Hourly.expand(data.weather.values); // fog icon
const iceAccumulation = Hourly.expand(data.iceAccumulation.values); // ice icon
const probabilityOfPrecipitation = Hourly.expand(data.probabilityOfPrecipitation.values); // rain icon
const snowfallAmount = Hourly.expand(data.snowfallAmount.values); // snow icon
const icons = await this.determineIcon(skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed);
const icons = await Hourly.determineIcon(skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed);
return temperature.map((val, idx) => {
if (navigation.units === UNITS.metric) return {
if (navigation.units === UNITS.metric) {
return {
temperature: temperature[idx],
apparentTemperature: apparentTemperature[idx],
windSpeed: windSpeed[idx],
windDirection: utils.calc.directionToNSEW(windDirection[idx]),
icon: icons[idx],
};
}
return {
temperature: utils.units.celsiusToFahrenheit(temperature[idx]),
@ -73,30 +75,29 @@ class Hourly extends WeatherDisplay {
windDirection: utils.calc.directionToNSEW(windDirection[idx]),
icon: icons[idx],
};
});
}
// given forecast paramaters determine a suitable icon
async determineIcon(skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed) {
static async determineIcon(skyCover, weather, iceAccumulation, probabilityOfPrecipitation, snowfallAmount, windSpeed) {
const startOfHour = luxon.DateTime.local().startOf('hour');
const sunTimes = (await navigation.getSun()).sun;
const overnight = luxon.Interval.fromDateTimes(luxon.DateTime.fromJSDate(sunTimes[0].sunset), luxon.DateTime.fromJSDate(sunTimes[1].sunrise));
const tomorrowOvernight = luxon.DateTime.fromJSDate(sunTimes[1].sunset);
return skyCover.map((val, idx) => {
const hour = startOfHour.plus({hours: idx});
const hour = startOfHour.plus({ hours: idx });
const isNight = overnight.contains(hour) || (hour > tomorrowOvernight);
return icons.getHourlyIcon(skyCover[idx], weather[idx], iceAccumulation[idx], probabilityOfPrecipitation[idx], snowfallAmount[idx], windSpeed[idx], isNight);
});
}
// expand a set of values with durations to an hour-by-hour array
expand(data) {
static expand(data) {
const startOfHour = luxon.DateTime.utc().startOf('hour').toMillis();
const result = []; // resulting expanded values
data.forEach(item => {
data.forEach((item) => {
let startTime = Date.parse(item.validTime.substr(0, item.validTime.indexOf('/')));
const duration = luxon.Duration.fromISO(item.validTime.substr(item.validTime.indexOf('/')+1)).shiftTo('milliseconds').values.milliseconds;
const duration = luxon.Duration.fromISO(item.validTime.substr(item.validTime.indexOf('/') + 1)).shiftTo('milliseconds').values.milliseconds;
const endTime = startTime + duration;
// loop through duration at one hour intervals
do {
@ -105,39 +106,39 @@ class Hourly extends WeatherDisplay {
result.push(item.value); // push data array
} // timestamp is after now
// increment start time by 1 hour
startTime = startTime + 3600000;
startTime += 3600000;
} while (startTime < endTime && result.length < 24);
}); // for each value
return result;
}
async drawLongCanvas () {
async drawLongCanvas() {
// create the "long" canvas if necessary
if (!this.longCanvas) {
this.longCanvas = document.createElement('canvas');
this.longCanvas.width = 640;
this.longCanvas.height = 24*this.hourHeight;
this.longCanvas.height = 24 * this.hourHeight;
this.longContext = this.longCanvas.getContext('2d');
this.longCanvasGifs = [];
}
// stop all gifs
this.longCanvasGifs.forEach(gif => gif.pause());
this.longCanvasGifs.forEach((gif) => gif.pause());
// delete the gifs
this.longCanvasGifs.length = 0;
// clean up existing gifs
this.gifs.forEach(gif => gif.pause());
this.gifs.forEach((gif) => gif.pause());
// delete the gifs
this.gifs.length = 0;
this.longContext.clearRect(0,0,this.longCanvas.width,this.longCanvas.height);
this.longContext.clearRect(0, 0, this.longCanvas.width, this.longCanvas.height);
// draw the "long" canvas with all cities
draw.box(this.longContext, 'rgb(35, 50, 112)', 0, 0, 640, 24*this.hourHeight);
draw.box(this.longContext, 'rgb(35, 50, 112)', 0, 0, 640, 24 * this.hourHeight);
for (let i = 0; i <= 4; i++) {
for (let i = 0; i <= 4; i += 1) {
const y = i * 346;
draw.horizontalGradient(this.longContext, 0, y, 640, y + 346, '#102080', '#001040');
}
@ -146,14 +147,13 @@ class Hourly extends WeatherDisplay {
await Promise.all(this.data.map(async (data, index) => {
// calculate base y value
const y = 50+this.hourHeight*index;
const y = 50 + this.hourHeight * index;
// hour
const hour = startingHour.plus({hours: index});
const formattedHour = hour.toLocaleString({weekday: 'short', hour: 'numeric'});
const hour = startingHour.plus({ hours: index });
const formattedHour = hour.toLocaleString({ weekday: 'short', hour: 'numeric' });
draw.text(this.longContext, 'Star4000 Large Compressed', '24pt', '#FFFF00', 80, y, formattedHour, 2);
// temperatures, convert to strings with no decimal
const temperature = Math.round(data.temperature).toString().padStart(3);
const feelsLike = Math.round(data.apparentTemperature).toString().padStart(3);
@ -177,8 +177,6 @@ class Hourly extends WeatherDisplay {
y: y - 35,
max_width: 47,
}));
}));
}
@ -221,7 +219,7 @@ class Hourly extends WeatherDisplay {
const longCanvas = this.getLongCanvas();
// calculate scroll offset and don't go past end
let offsetY = Math.min(longCanvas.height-289, (count-150));
let offsetY = Math.min(longCanvas.height - 289, (count - 150));
// don't let offset go negative
if (offsetY < 0) offsetY = 0;
@ -230,15 +228,15 @@ class Hourly extends WeatherDisplay {
this.context.drawImage(longCanvas, 0, offsetY, 640, 289, 0, 110, 640, 289);
}
getTravelCitiesDayName(cities) {
const {DateTime} = luxon;
static getTravelCitiesDayName(cities) {
const { DateTime } = luxon;
// effectively returns early on the first found date
return cities.reduce((dayName, city) => {
if (city && dayName === '') {
// today or tomorrow
const day = DateTime.local().plus({days: (city.today)?0:1});
const day = DateTime.local().plus({ days: (city.today) ? 0 : 1 });
// return the day
return day.toLocaleString({weekday: 'long'});
return day.toLocaleString({ weekday: 'long' });
}
return dayName;
}, '');

View file

@ -1,11 +1,9 @@
'use strict';
/* spell-checker: disable */
// eslint-disable-next-line no-unused-vars
const icons = (() => {
const getWeatherRegionalIconFromIconLink = (link, isNightTime) => {
const getWeatherRegionalIconFromIconLink = (link, _isNightTime) => {
// extract day or night if not provided
if (isNightTime === undefined) isNightTime = link.indexOf('/night/') >=0;
const isNightTime = _isNightTime ?? link.indexOf('/night/') >= 0;
// internal function to add path to returned icon
const addPath = (icon) => `images/r/${icon}`;
@ -16,12 +14,11 @@ const icons = (() => {
// if a 'DualImage' is captured, adjust to just the j parameter
if (conditionName === 'dualimage') {
const match = link.match(/&j=(.*)&/);
conditionName = match[1];
[, conditionName] = match;
}
// find the icon
switch (conditionName + (isNightTime?'-n':'')) {
switch (conditionName + (isNightTime ? '-n' : '')) {
case 'skc':
case 'hot':
case 'haze':
@ -132,11 +129,11 @@ const icons = (() => {
}
};
const getWeatherIconFromIconLink = function (link, isNightTime = false) {
const getWeatherIconFromIconLink = (link, _isNightTime) => {
// internal function to add path to returned icon
const addPath = (icon) => `images/${icon}`;
// extract day or night if not provided
if (isNightTime === undefined) isNightTime = link.indexOf('/night/') >=0;
const isNightTime = _isNightTime ?? link.indexOf('/night/') >= 0;
// grab everything after the last slash ending at any of these: ?&,
const afterLastSlash = link.toLowerCase().match(/[^/]+$/)[0];
@ -145,12 +142,11 @@ const icons = (() => {
// if a 'DualImage' is captured, adjust to just the j parameter
if (conditionName === 'dualimage') {
const match = link.match(/&j=(.*)&/);
conditionName = match[1];
[, conditionName] = match;
}
// find the icon
switch (conditionName + (isNightTime?'-n':'')) {
switch (conditionName + (isNightTime ? '-n' : '')) {
case 'skc':
case 'hot':
case 'haze':
@ -266,8 +262,7 @@ const icons = (() => {
let wind = false;
// test the phenomenon for various value if it is provided.
weather.forEach(phenomenon => {
console.log(phenomenon.weather);
weather.forEach((phenomenon) => {
if (!phenomenon.weather) return;
if (phenomenon.weather.toLowerCase().includes('thunder')) thunder = true;
if (phenomenon.weather.toLowerCase().includes('snow')) snow = true;
@ -284,7 +279,7 @@ const icons = (() => {
}
if ((snowfallAmount > 0 || snow) && thunder) return addPath('ThunderSnow.gif');
if (snowfallAmount > 0 || snow) return addPath('Light-Snow.gif');
if (thunder) return(addPath('Thunderstorm.gif'));
if (thunder) return (addPath('Thunderstorm.gif'));
if (probabilityOfPrecipitation > 70) return addPath('Rain-1992.gif');
if (probabilityOfPrecipitation > 50) return addPath('Shower.gif');
if (probabilityOfPrecipitation > 30) {

View file

@ -1,10 +1,10 @@
// current weather conditions display
/* globals WeatherDisplay, utils, STATUS, UNITS, draw, navigation, _StationInfo */
/* globals WeatherDisplay, utils, STATUS, UNITS, draw, navigation, StationInfo */
// eslint-disable-next-line no-unused-vars
class LatestObservations extends WeatherDisplay {
constructor(navId,elemId) {
super(navId,elemId,'Latest Observations');
constructor(navId, elemId) {
super(navId, elemId, 'Latest Observations');
// pre-load background image (returns promise)
this.backgroundImage = utils.image.load('images/BackGround1_1.png');
@ -12,44 +12,45 @@ class LatestObservations extends WeatherDisplay {
this.MaximumRegionalStations = 7;
}
async getData(weatherParameters) {
super.getData(weatherParameters);
if (!weatherParameters) weatherParameters = this.weatherParameters;
async getData(_weatherParameters) {
super.getData(_weatherParameters);
const weatherParameters = _weatherParameters ?? this.weatherParameters;
// calculate distance to each station
const stationsByDistance = Object.keys(_StationInfo).map(key => {
const station = _StationInfo[key];
const stationsByDistance = Object.keys(StationInfo).map((key) => {
const station = StationInfo[key];
const distance = utils.calc.distance(station.lat, station.lon, weatherParameters.latitude, weatherParameters.longitude);
return Object.assign({}, station, {distance});
return { ...station, distance };
});
// sort the stations by distance
const sortedStations = stationsByDistance.sort((a,b) => a.distance - b.distance);
const sortedStations = stationsByDistance.sort((a, b) => a.distance - b.distance);
// try up to 30 regional stations
const regionalStations = sortedStations.slice(0,30);
const regionalStations = sortedStations.slice(0, 30);
// get data for regional stations
const allConditions = await Promise.all(regionalStations.map(async station => {
const allConditions = await Promise.all(regionalStations.map(async (station) => {
try {
const data = await utils.fetch.json(`https://api.weather.gov/stations/${station.id}/observations/latest`);
// test for temperature, weather and wind values present
if (data.properties.temperature.value === null ||
data.properties.textDescription === '' ||
data.properties.windSpeed.value === null) return;
if (data.properties.temperature.value === null
|| data.properties.textDescription === ''
|| data.properties.windSpeed.value === null) return false;
// format the return values
return Object.assign({}, data.properties, {
return {
...data.properties,
StationId: station.id,
city: station.city,
});
};
} catch (e) {
console.log(`Unable to get latest observations for ${station.id}`);
return;
return false;
}
}));
// remove and stations that did not return data
const actualConditions = allConditions.filter(condition => condition);
const actualConditions = allConditions.filter((condition) => condition);
// 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
if (this.data.length < 1) {
@ -64,7 +65,7 @@ class LatestObservations extends WeatherDisplay {
const conditions = this.data;
// sort array by station name
const sortedConditions = conditions.sort((a,b) => ((a.Name < b.Name) ? -1 : ((a.Name > b.Name) ? 1 : 0)));
const sortedConditions = conditions.sort((a, b) => ((a.Name < b.Name) ? -1 : 1));
this.context.drawImage(await this.backgroundImage, 0, 0);
draw.horizontalGradientSingle(this.context, 0, 30, 500, 90, draw.topColor1, draw.topColor2);
@ -75,9 +76,9 @@ class LatestObservations extends WeatherDisplay {
draw.titleText(this.context, 'Latest', 'Observations');
if (navigation.units() === UNITS.english) {
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFFFF', 295, 105, String.fromCharCode(176) + 'F', 2);
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFFFF', 295, 105, `${String.fromCharCode(176)}F`, 2);
} else {
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFFFF', 295, 105, String.fromCharCode(176) + 'C', 2);
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFFFF', 295, 105, `${String.fromCharCode(176)}C`, 2);
}
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFFFF', 345, 105, 'WEATHER', 2);
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFFFF', 495, 105, 'WIND', 2);
@ -95,7 +96,7 @@ class LatestObservations extends WeatherDisplay {
}
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 65, y, condition.city.substr(0, 14), 2);
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 345, y, this.shortenCurrentConditions(condition.textDescription).substr(0, 9), 2);
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 345, y, LatestObservations.shortenCurrentConditions(condition.textDescription).substr(0, 9), 2);
if (WindSpeed > 0) {
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 495, y, windDirection + (Array(6 - windDirection.length - WindSpeed.toString().length).join(' ')) + WindSpeed.toString(), 2);
@ -113,7 +114,8 @@ class LatestObservations extends WeatherDisplay {
this.finishDraw();
}
shortenCurrentConditions(condition) {
static shortenCurrentConditions(_condition) {
let condition = _condition;
condition = condition.replace(/Light/, 'L');
condition = condition.replace(/Heavy/, 'H');
condition = condition.replace(/Partly/, 'P');

View file

@ -1,23 +1,22 @@
// display text based local forecast
/* globals WeatherDisplay, utils, STATUS, UNITS, draw, navigation*/
/* globals WeatherDisplay, utils, STATUS, UNITS, draw, navigation */
// eslint-disable-next-line no-unused-vars
class LocalForecast extends WeatherDisplay {
constructor(navId,elemId) {
super(navId,elemId, 'Local Forecast');
constructor(navId, elemId) {
super(navId, elemId, 'Local Forecast');
// set timings
this.timing.baseDelay= 5000;
this.timing.baseDelay = 5000;
// pre-load background image (returns promise)
this.backgroundImage = utils.image.load('images/BackGround1_1.png');
}
async getData(weatherParameters) {
super.getData(weatherParameters);
if (!weatherParameters) weatherParameters = this.weatherParameters;
async getData(_weatherParameters) {
super.getData(_weatherParameters);
const weatherParameters = _weatherParameters ?? this.weatherParameters;
// get raw data
const rawData = await this.getRawData(weatherParameters);
@ -27,7 +26,7 @@ class LocalForecast extends WeatherDisplay {
return;
}
// parse raw data
const conditions = this.parseLocalForecast(rawData);
const conditions = LocalForecast.parse(rawData);
// split this forecast into the correct number of screens
const maxRows = 7;
@ -36,9 +35,9 @@ class LocalForecast extends WeatherDisplay {
this.screenTexts = [];
// read each text
conditions.forEach(condition => {
conditions.forEach((condition) => {
// process the text
let text = condition.DayName.toUpperCase() + '...';
let text = `${condition.DayName.toUpperCase()}...`;
let conditionText = condition.Text;
if (navigation.units() === UNITS.metric) {
conditionText = condition.TextC;
@ -52,15 +51,13 @@ class LocalForecast extends WeatherDisplay {
const maxRowCount = maxRows;
let rowCount = 0;
// if (PrependAlert) {
// ScreenText = LocalForecastScreenTexts[LocalForecastScreenTexts.length - 1];
// rowCount = ScreenText.split('\n').length - 1;
// }
for (let i = 0; i <= lineCount - 1; i++) {
if (lines[i] === '') continue;
for (let i = 0; i <= lineCount - 1; i += 1) {
if (lines[i] !== '') {
if (rowCount > maxRowCount - 1) {
// if (PrependAlert) {
// LocalForecastScreenTexts[LocalForecastScreenTexts.length - 1] = ScreenText;
@ -72,8 +69,9 @@ class LocalForecast extends WeatherDisplay {
rowCount = 0;
}
ScreenText += lines[i] + '\n';
rowCount++;
ScreenText += `${lines[i]}\n`;
rowCount += 1;
}
}
// if (PrependAlert) {
// this.screenTexts[this.screenTexts.length - 1] = ScreenText;
@ -99,7 +97,6 @@ class LocalForecast extends WeatherDisplay {
units,
},
});
} catch (e) {
console.error(`GetWeatherForecast failed: ${weatherParameters.forecast}`);
console.error(e.status, e.responseJSON);
@ -125,15 +122,15 @@ class LocalForecast extends WeatherDisplay {
draw.box(this.context, 'rgb(33, 40, 90)', 65, 105, 505, 280);
// Draw the text.
this.screenTexts[this.screenIndex].split('\n').forEach((text, index) => {
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 75, 140+40*index, text, 2);
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 75, 140 + 40 * index, text, 2);
});
this.finishDraw();
}
// format the forecast
parseLocalForecast (forecast) {
static parse(forecast) {
// only use the first 6 lines
return forecast.properties.periods.slice(0,6).map(text => ({
return forecast.properties.periods.slice(0, 6).map((text) => ({
// format day and text
DayName: text.name.toUpperCase(),
Text: text.detailedForecast,

View file

@ -1,6 +1,5 @@
'use strict';
// navigation handles progress, next/previous and initial load messages from the parent frame
/* globals index, utils, _StationInfo, STATUS */
/* globals index, utils, StationInfo, STATUS */
/* globals CurrentWeather, LatestObservations, TravelForecast, RegionalForecast, LocalForecast, ExtendedForecast, Almanac, Radar, Progress, Hourly */
document.addEventListener('DOMContentLoaded', () => {
@ -13,11 +12,11 @@ const UNITS = {
};
const navigation = (() => {
let weatherParameters = {};
let displays = [];
let currentUnits = UNITS.english;
let playing = false;
let progress;
const weatherParameters = {};
// current conditions and sunrise/sunset are made available from the display below
let currentWeather;
@ -46,11 +45,10 @@ const navigation = (() => {
default:
console.error(`Unknown event ${data.type}`);
}
};
const postMessage = (type, message = {}) => {
index.message({type, message});
const postMessage = (type, myMessage = {}) => {
index.message({ type, message: myMessage });
};
const getWeather = async (latLon) => {
@ -62,11 +60,11 @@ const navigation = (() => {
const StationId = stations.features[0].properties.stationIdentifier;
let city = point.properties.relativeLocation.properties.city;
let { city } = point.properties.relativeLocation.properties;
if (StationId in _StationInfo) {
city = _StationInfo[StationId].city;
city = city.split('/')[0];
if (StationId in StationInfo) {
city = StationInfo[StationId].city;
[city] = city.split('/');
}
// populate the weather parameters
@ -89,13 +87,13 @@ const navigation = (() => {
// draw the progress canvas and hide others
hideAllCanvases();
document.getElementById('loading').style.display = 'none';
progress = new Progress(-1,'progress');
progress = new Progress(-1, 'progress');
await progress.drawCanvas();
progress.showCanvas();
// start loading canvases if necessary
if (displays.length === 0) {
currentWeather = new CurrentWeather(0,'currentWeather');
currentWeather = new CurrentWeather(0, 'currentWeather');
almanac = new Almanac(7, 'almanac');
displays = [
currentWeather,
@ -110,8 +108,7 @@ const navigation = (() => {
];
}
// call for new data on each display
displays.forEach(display => display.getData(weatherParameters));
displays.forEach((display) => display.getData(weatherParameters));
};
// receive a status update from a module {id, value}
@ -130,12 +127,12 @@ const navigation = (() => {
};
const countLoadedCanvases = () => displays.reduce((acc, display) => {
if (display.status !== STATUS.loading) return acc+1;
if (display.status !== STATUS.loading) return acc + 1;
return acc;
},0);
}, 0);
const hideAllCanvases = () => {
displays.forEach(display => display.hideCanvas());
displays.forEach((display) => display.hideCanvas());
};
const units = () => currentUnits;
@ -168,9 +165,9 @@ const navigation = (() => {
};
// receive navigation messages from displays
const displayNavMessage = (message) => {
if (message.type === msg.response.previous) loadDisplay(-1);
if (message.type === msg.response.next) loadDisplay(1);
const displayNavMessage = (myMessage) => {
if (myMessage.type === msg.response.previous) loadDisplay(-1);
if (myMessage.type === msg.response.next) loadDisplay(1);
};
// navigate to next or previous
@ -192,9 +189,9 @@ const navigation = (() => {
const totalDisplays = displays.length;
const curIdx = currentDisplayIndex();
let idx;
for (let i = 0; i < totalDisplays; i++) {
for (let i = 0; i < totalDisplays; i += 1) {
// convert form simple 0-10 to start at current display index +/-1 and wrap
idx = utils.calc.wrap(curIdx+(i+1)*direction,totalDisplays);
idx = utils.calc.wrap(curIdx + (i + 1) * direction, totalDisplays);
if (displays[idx].status === STATUS.loaded) break;
}
const newDisplay = displays[idx];
@ -207,12 +204,10 @@ const navigation = (() => {
// get the current display index or value
const currentDisplayIndex = () => {
let index = displays.findIndex(display=>display.isActive());
const index = displays.findIndex((display) => display.isActive());
return index;
};
const currentDisplay = () => {
return displays[currentDisplayIndex()];
};
const currentDisplay = () => displays[currentDisplayIndex()];
const setPlaying = (newValue) => {
playing = newValue;

View file

@ -4,7 +4,7 @@
// eslint-disable-next-line no-unused-vars
class Progress extends WeatherDisplay {
constructor(navId,elemId) {
constructor(navId, elemId) {
super(navId, elemId);
// pre-load background image (returns promise)
@ -34,18 +34,17 @@ class Progress extends WeatherDisplay {
draw.horizontalGradientSingle(this.context, 584, 90, 640, 399, draw.sideColor1, draw.sideColor2);
draw.horizontalGradientSingle(this.context, 0, 30, 500, 90, draw.topColor1, draw.topColor2);
draw.triangle(this.context, 'rgb(28, 10, 87)', 500, 30, 450, 90, 500, 90);
draw.titleText(this.context, 'WeatherStar', '4000+ ' + this.version);
draw.titleText(this.context, 'WeatherStar', `4000+ ${this.version}`);
}
this.finishDraw();
// if no displays provided just draw the backgrounds (above)
if (!displays) return;
displays.forEach((display, idx) => {
const y = 120 + idx*29;
const y = 120 + idx * 29;
const dots = Array(120 - Math.floor(display.name.length * 2.5)).join('.');
draw.text(this.context, 'Star4000 Extended', '19pt', '#ffffff', 70, y, display.name + dots, 2);
let statusText;
let statusColor;
switch (display.status) {
@ -77,24 +76,21 @@ class Progress extends WeatherDisplay {
// Erase any dots that spill into the status text.
this.context.drawImage(backgroundImage, 475, y - 20, 165, 30, 475, y - 20, 165, 30);
draw.text(this.context, 'Star4000 Extended', '19pt', statusColor, 565, y, statusText, 2, 'end');
});
// calculate loaded percent
const loadedPercent = (loadedCount/displays.length);
const loadedPercent = (loadedCount / displays.length);
if (loadedPercent < 1.0) {
// Draw a box for the progress.
draw.box(this.context, '#000000', 51, 428, 534, 22);
draw.box(this.context, '#ffffff', 53, 430, 530, 18);
// update the progress gif
draw.box(this.context, '#1d7fff', 55, 432, 526*loadedPercent, 14);
draw.box(this.context, '#1d7fff', 55, 432, 526 * loadedPercent, 14);
} else {
// restore the background
this.context.drawImage(backgroundImage, 51, 428, 534, 22, 51, 428, 534, 22);
}
}
canvasClick(e) {
@ -106,9 +102,9 @@ class Progress extends WeatherDisplay {
if (x < 440 || x > 570) return;
// stop playing
navigation.message('navButton', stop);
navigation.message('navButton');
// use the y value to determine an index
const index = Math.floor((y-100)/29);
const index = Math.floor((y - 100) / 29);
const display = navigation.getDisplay(index);
if (display && display.status === STATUS.loaded) {
display.showCanvas(navigation.msg.command.firstFrame);

View file

@ -3,42 +3,42 @@
// eslint-disable-next-line no-unused-vars
class Radar extends WeatherDisplay {
constructor(navId,elemId) {
super(navId,elemId,'Local Radar');
constructor(navId, elemId) {
super(navId, elemId, 'Local Radar');
// set max images
this.dopplerRadarImageMax = 6;
// update timing
this.timing.baseDelay = 350;
this.timing.delay = [
{time: 4, si: 5},
{time: 1, si: 0},
{time: 1, si: 1},
{time: 1, si: 2},
{time: 1, si: 3},
{time: 1, si: 4},
{time: 4, si: 5},
{time: 1, si: 0},
{time: 1, si: 1},
{time: 1, si: 2},
{time: 1, si: 3},
{time: 1, si: 4},
{time: 4, si: 5},
{time: 1, si: 0},
{time: 1, si: 1},
{time: 1, si: 2},
{time: 1, si: 3},
{time: 1, si: 4},
{time: 12, si: 5},
{ time: 4, si: 5 },
{ time: 1, si: 0 },
{ time: 1, si: 1 },
{ time: 1, si: 2 },
{ time: 1, si: 3 },
{ time: 1, si: 4 },
{ time: 4, si: 5 },
{ time: 1, si: 0 },
{ time: 1, si: 1 },
{ time: 1, si: 2 },
{ time: 1, si: 3 },
{ time: 1, si: 4 },
{ time: 4, si: 5 },
{ time: 1, si: 0 },
{ time: 1, si: 1 },
{ time: 1, si: 2 },
{ time: 1, si: 3 },
{ time: 1, si: 4 },
{ time: 12, si: 5 },
];
// pre-load background image (returns promise)
this.backgroundImage = utils.image.load('images/BackGround4_1.png');
}
async getData(weatherParameters) {
super.getData(weatherParameters);
if (!weatherParameters) weatherParameters = this.weatherParameters;
async getData(_weatherParameters) {
super.getData(_weatherParameters);
const weatherParameters = _weatherParameters ?? this.weatherParameters;
// ALASKA ISN'T SUPPORTED!
if (weatherParameters.state === 'AK') {
@ -47,7 +47,7 @@ class Radar extends WeatherDisplay {
}
// date and time parsing
const {DateTime} = luxon;
const { DateTime } = luxon;
// get the base map
let src = 'images/4000RadarMap2.jpg';
@ -59,9 +59,9 @@ class Radar extends WeatherDisplay {
let radarHtml;
try {
// get a list of available radars
radarHtml = await utils.fetch.text(baseUrl, {cors: true});
radarHtml = await utils.fetch.text(baseUrl, { cors: true });
} catch (e) {
console.log('Unable to get list of radars');
console.error('Unable to get list of radars');
console.error(e);
this.setStatus(STATUS.failed);
return;
@ -72,17 +72,17 @@ class Radar extends WeatherDisplay {
const xmlDoc = parser.parseFromString(radarHtml, 'text/html');
const anchors = xmlDoc.getElementsByTagName('a');
const gifs = [];
for (let idx in anchors) {
gifs.push(anchors[idx].innerHTML);
}
Object.values(anchors).forEach((a) => {
gifs.push(a.innerHTML);
});
// filter for selected urls
let filter = /Conus_\d/;
if (weatherParameters.state === 'HI') filter = /hawaii_\d/;
// get the last few images
const urlsFull = gifs.filter(gif => gif && gif.match(filter));
const urls = urlsFull.slice(-(this.dopplerRadarImageMax-1));
const urlsFull = gifs.filter((gif) => gif && gif.match(filter));
const urls = urlsFull.slice(-(this.dopplerRadarImageMax - 1));
// add additional 'latest.gif'
if (weatherParameters.state !== 'HI') urls.push('latest_radaronly.gif');
@ -97,13 +97,13 @@ class Radar extends WeatherDisplay {
if (weatherParameters.state === 'HI') {
width = 600;
height = 571;
sourceXY = this.getXYFromLatitudeLongitudeHI(weatherParameters.latitude, weatherParameters.longitude, offsetX, offsetY);
sourceXY = Radar.getXYFromLatitudeLongitudeHI(weatherParameters.latitude, weatherParameters.longitude, offsetX, offsetY);
} else {
width = 2550;
height = 1600;
offsetX *= 2;
offsetY *= 2;
sourceXY = this.getXYFromLatitudeLongitudeDoppler(weatherParameters.latitude, weatherParameters.longitude, offsetX, offsetY);
sourceXY = Radar.getXYFromLatitudeLongitudeDoppler(weatherParameters.latitude, weatherParameters.longitude, offsetX, offsetY);
}
// create working context for manipulation
@ -116,7 +116,7 @@ class Radar extends WeatherDisplay {
// calculate radar offsets
let radarOffsetX = 117;
let radarOffsetY = 60;
let radarSourceXY = this.getXYFromLatitudeLongitudeDoppler(weatherParameters.latitude, weatherParameters.longitude, offsetX, offsetY);
let radarSourceXY = Radar.getXYFromLatitudeLongitudeDoppler(weatherParameters.latitude, weatherParameters.longitude, offsetX, offsetY);
let radarSourceX = radarSourceXY.x / 2;
let radarSourceY = radarSourceXY.y / 2;
@ -176,7 +176,7 @@ class Radar extends WeatherDisplay {
}
// get the base map
context.drawImage(await this.baseMap, sourceXY.x, sourceXY.y, offsetX*2, offsetY*2, 0, 0, 640, 367);
context.drawImage(await this.baseMap, sourceXY.x, sourceXY.y, offsetX * 2, offsetY * 2, 0, 0, 640, 367);
// crop the radar image
const cropCanvas = document.createElement('canvas');
@ -186,10 +186,10 @@ class Radar extends WeatherDisplay {
cropContext.imageSmoothingEnabled = false;
cropContext.drawImage(workingCanvas, radarSourceX, radarSourceY, (radarOffsetX * 2), (radarOffsetY * 2.33), 0, 0, 640, 367);
// clean the image
this.removeDopplerRadarImageNoise(cropContext);
Radar.removeDopplerRadarImageNoise(cropContext);
// merge the radar and map
this.mergeDopplerRadarImage(context, cropContext);
Radar.mergeDopplerRadarImage(context, cropContext);
return {
canvas,
@ -199,9 +199,9 @@ class Radar extends WeatherDisplay {
// set max length
this.timing.totalScreens = radarInfo.length;
// store the images
this.data = radarInfo.map(radar=>radar.canvas);
this.data = radarInfo.map((radar) => radar.canvas);
this.times = radarInfo.map(radar=>radar.time);
this.times = radarInfo.map((radar) => radar.time);
this.setStatus(STATUS.loaded);
}
@ -209,7 +209,7 @@ class Radar extends WeatherDisplay {
super.drawCanvas();
if (this.screenIndex === -1) return;
this.context.drawImage(await this.backgroundImage, 0, 0);
const {DateTime} = luxon;
const { DateTime } = luxon;
// Title
draw.text(this.context, 'Arial', 'bold 28pt', '#ffffff', 155, 60, 'Local', 2);
draw.text(this.context, 'Arial', 'bold 28pt', '#ffffff', 155, 95, 'Radar', 2);
@ -237,7 +237,7 @@ class Radar extends WeatherDisplay {
}
// utility latitude/pixel conversions
getXYFromLatitudeLongitude (Latitude, Longitude, OffsetX, OffsetY, state) {
getXYFromLatitudeLongitude(Latitude, Longitude, OffsetX, OffsetY, state) {
if (state === 'HI') return this.getXYFromLatitudeLongitudeHI(...arguments);
let y = 0;
let x = 0;
@ -265,7 +265,7 @@ class Radar extends WeatherDisplay {
return { x, y };
}
getXYFromLatitudeLongitudeHI(Latitude, Longitude, OffsetX, OffsetY) {
static getXYFromLatitudeLongitudeHI(Latitude, Longitude, OffsetX, OffsetY) {
let y = 0;
let x = 0;
const ImgHeight = 571;
@ -292,7 +292,7 @@ class Radar extends WeatherDisplay {
return { x, y };
}
getXYFromLatitudeLongitudeDoppler (Latitude, Longitude, OffsetX, OffsetY) {
static getXYFromLatitudeLongitudeDoppler(Latitude, Longitude, OffsetX, OffsetY) {
let y = 0;
let x = 0;
const ImgHeight = 3200;
@ -319,7 +319,7 @@ class Radar extends WeatherDisplay {
return { x: x * 2, y: y * 2 };
}
removeDopplerRadarImageNoise (RadarContext) {
static removeDopplerRadarImageNoise(RadarContext) {
const RadarImageData = RadarContext.getImageData(0, 0, RadarContext.canvas.width, RadarContext.canvas.height);
// examine every pixel,
@ -329,7 +329,7 @@ class Radar extends WeatherDisplay {
// i + 1 = green
// i + 2 = blue
// i + 3 = alpha (0 = transparent, 255 = opaque)
let [R, G, B, A] = RadarImageData.data.slice(i,i+4);
let [R, G, B, A] = RadarImageData.data.slice(i, i + 4);
// is this pixel the old rgb?
if ((R === 1 && G === 159 && B === 244)
@ -402,13 +402,13 @@ class Radar extends WeatherDisplay {
RadarContext.putImageData(RadarImageData, 0, 0);
}
mergeDopplerRadarImage (mapContext, radarContext) {
var mapImageData = mapContext.getImageData(0, 0, mapContext.canvas.width, mapContext.canvas.height);
var radarImageData = radarContext.getImageData(0, 0, radarContext.canvas.width, radarContext.canvas.height);
static mergeDopplerRadarImage(mapContext, radarContext) {
const mapImageData = mapContext.getImageData(0, 0, mapContext.canvas.width, mapContext.canvas.height);
const radarImageData = radarContext.getImageData(0, 0, radarContext.canvas.width, radarContext.canvas.height);
// examine every pixel,
// change any old rgb to the new-rgb
for (var i = 0; i < radarImageData.data.length; i += 4) {
for (let i = 0; i < radarImageData.data.length; i += 4) {
// i + 0 = red
// i + 1 = green
// i + 2 = blue

View file

@ -1,12 +1,12 @@
// regional forecast and observations
// type 0 = observations, 1 = first forecast, 2 = second forecast
/* globals WeatherDisplay, utils, STATUS, icons, UNITS, draw, navigation, luxon, _StationInfo, _RegionalCities */
/* globals WeatherDisplay, utils, STATUS, icons, UNITS, draw, navigation, luxon, StationInfo, RegionalCities */
// eslint-disable-next-line no-unused-vars
class RegionalForecast extends WeatherDisplay {
constructor(navId,elemId) {
super(navId,elemId,'Regional Forecast');
constructor(navId, elemId) {
super(navId, elemId, 'Regional Forecast');
// pre-load background image (returns promise)
this.backgroundImage = utils.image.load('images/BackGround5_1.png');
@ -15,9 +15,9 @@ class RegionalForecast extends WeatherDisplay {
this.timing.totalScreens = 3;
}
async getData(weatherParameters) {
super.getData(weatherParameters);
if (!weatherParameters) weatherParameters = this.weatherParameters;
async getData(_weatherParameters) {
super.getData(_weatherParameters);
const weatherParameters = _weatherParameters ?? this.weatherParameters;
// pre-load the base map (returns promise)
let src = 'images/Basemap2.png';
@ -44,35 +44,35 @@ class RegionalForecast extends WeatherDisplay {
if (weatherParameters.state === 'HI') targetDistance = 1;
// make station info into an array
const stationInfoArray = Object.keys(_StationInfo).map(key => Object.assign({}, _StationInfo[key], {targetDistance}));
const stationInfoArray = Object.values(StationInfo).map((value) => ({ ...value, targetDistance }));
// combine regional cities with station info for additional stations
// stations are intentionally after cities to allow cities priority when drawing the map
const combinedCities = [..._RegionalCities, ...stationInfoArray];
const combinedCities = [...RegionalCities, ...stationInfoArray];
// Determine which cities are within the max/min latitude/longitude.
const regionalCities = [];
combinedCities.forEach(city => {
if (city.lat > minMaxLatLon.minLat && city.lat < minMaxLatLon.maxLat &&
city.lon > minMaxLatLon.minLon && city.lon < minMaxLatLon.maxLon - 1) {
// default to 1 for cities loaded from _RegionalCities, use value calculate above for remaining stations
const targetDistance = city.targetDistance || 1;
combinedCities.forEach((city) => {
if (city.lat > minMaxLatLon.minLat && city.lat < minMaxLatLon.maxLat
&& city.lon > minMaxLatLon.minLon && city.lon < minMaxLatLon.maxLon - 1) {
// default to 1 for cities loaded from RegionalCities, use value calculate above for remaining stations
const targetDist = city.targetDistance || 1;
// Only add the city as long as it isn't within set distance degree of any other city already in the array.
const okToAddCity = regionalCities.reduce((acc, testCity) => {
const distance = utils.calc.distance(city.lon, city.lat, testCity.lon, testCity.lat);
return acc && distance >= targetDistance;
return acc && distance >= targetDist;
}, true);
if (okToAddCity) regionalCities.push(city);
}
});
// get regional forecasts and observations (the two are intertwined due to the design of api.weather.gov)
const regionalForecastPromises = regionalCities.map(async city => {
const regionalForecastPromises = regionalCities.map(async (city) => {
try {
// get the point first, then break down into forecast and observations
const point = await utils.weather.getPoint(city.lat, city.lon);
// start off the observation task
const observationPromise = this.getRegionalObservation(point, city);
const observationPromise = RegionalForecast.getRegionalObservation(point, city);
const forecast = await utils.fetch.json(point.properties.forecast);
@ -85,7 +85,7 @@ class RegionalForecast extends WeatherDisplay {
const regionalObservation = {
daytime: !!observation.icon.match(/\/day\//),
temperature: utils.units.celsiusToFahrenheit(observation.temperature.value),
name: this.formatCity(city.city),
name: RegionalForecast.formatCity(city.city),
icon: observation.icon,
x: cityXY.x,
y: cityXY.y,
@ -101,8 +101,8 @@ class RegionalForecast extends WeatherDisplay {
// always skip the first forecast index because it's what's going on right now
return [
regionalObservation,
this.buildForecast(forecast.properties.periods[1], city, cityXY),
this.buildForecast(forecast.properties.periods[2], city, cityXY),
RegionalForecast.buildForecast(forecast.properties.periods[1], city, cityXY),
RegionalForecast.buildForecast(forecast.properties.periods[2], city, cityXY),
];
} catch (e) {
console.log(`No regional forecast data for '${city.name}'`);
@ -114,7 +114,7 @@ class RegionalForecast extends WeatherDisplay {
// wait for the forecasts
const regionalDataAll = await Promise.all(regionalForecastPromises);
// filter out any false (unavailable data)
const regionalData = regionalDataAll.filter(data => data);
const regionalData = regionalDataAll.filter((data) => data);
// test for data present
if (regionalData.length === 0) {
@ -132,11 +132,11 @@ class RegionalForecast extends WeatherDisplay {
this.setStatus(STATUS.loaded);
}
buildForecast (forecast, city, cityXY) {
static buildForecast(forecast, city, cityXY) {
return {
daytime: forecast.isDaytime,
temperature: forecast.temperature||0,
name: this.formatCity(city.city),
temperature: forecast.temperature || 0,
name: RegionalForecast.formatCity(city.city),
icon: forecast.icon,
x: cityXY.x,
y: cityXY.y,
@ -144,7 +144,7 @@ class RegionalForecast extends WeatherDisplay {
};
}
async getRegionalObservation (point, city) {
static async getRegionalObservation(point, city) {
try {
// get stations
const stations = await utils.fetch.json(point.properties.observationStations);
@ -165,9 +165,9 @@ class RegionalForecast extends WeatherDisplay {
}
// utility latitude/pixel conversions
getXYFromLatitudeLongitude (Latitude, Longitude, OffsetX, OffsetY, state) {
if (state === 'AK') return this.getXYFromLatitudeLongitudeAK(...arguments);
if (state === 'HI') return this.getXYFromLatitudeLongitudeHI(...arguments);
getXYFromLatitudeLongitude(Latitude, Longitude, OffsetX, OffsetY, state) {
if (state === 'AK') return this.getXYFromLatitudeLongitudeAK(Latitude, Longitude, OffsetX, OffsetY);
if (state === 'HI') return this.getXYFromLatitudeLongitudeHI(Latitude, Longitude, OffsetX, OffsetY);
let y = 0;
let x = 0;
const ImgHeight = 1600;
@ -194,7 +194,7 @@ class RegionalForecast extends WeatherDisplay {
return { x, y };
}
getXYFromLatitudeLongitudeAK (Latitude, Longitude, OffsetX, OffsetY) {
static getXYFromLatitudeLongitudeAK(Latitude, Longitude, OffsetX, OffsetY) {
let y = 0;
let x = 0;
const ImgHeight = 1142;
@ -221,7 +221,7 @@ class RegionalForecast extends WeatherDisplay {
return { x, y };
}
getXYFromLatitudeLongitudeHI (Latitude, Longitude, OffsetX, OffsetY) {
static getXYFromLatitudeLongitudeHI(Latitude, Longitude, OffsetX, OffsetY) {
let y = 0;
let x = 0;
const ImgHeight = 571;
@ -248,38 +248,44 @@ class RegionalForecast extends WeatherDisplay {
return { x, y };
}
getMinMaxLatitudeLongitude (X, Y, OffsetX, OffsetY, state) {
if (state === 'AK') return this.getMinMaxLatitudeLongitudeAK(...arguments);
if (state === 'HI') return this.getMinMaxLatitudeLongitudeHI(...arguments);
getMinMaxLatitudeLongitude(X, Y, OffsetX, OffsetY, state) {
if (state === 'AK') return this.getMinMaxLatitudeLongitudeAK(X, Y, OffsetX, OffsetY);
if (state === 'HI') return this.getMinMaxLatitudeLongitudeHI(X, Y, OffsetX, OffsetY);
const maxLat = ((Y / 55.2) - 50.5) * -1;
const minLat = (((Y + (OffsetY * 2)) / 55.2) - 50.5) * -1;
const minLon = (((X * -1) / 41.775) + 127.5) * -1;
const maxLon = ((((X + (OffsetX * 2)) * -1) / 41.775) + 127.5) * -1;
return { minLat, maxLat, minLon, maxLon };
return {
minLat, maxLat, minLon, maxLon,
};
}
getMinMaxLatitudeLongitudeAK (X, Y, OffsetX, OffsetY) {
static getMinMaxLatitudeLongitudeAK(X, Y, OffsetX, OffsetY) {
const maxLat = ((Y / 56) - 73.0) * -1;
const minLat = (((Y + (OffsetY * 2)) / 56) - 73.0) * -1;
const minLon = (((X * -1) / 25) + 175.0) * -1;
const maxLon = ((((X + (OffsetX * 2)) * -1) / 25) + 175.0) * -1;
return { minLat, maxLat, minLon, maxLon };
return {
minLat, maxLat, minLon, maxLon,
};
}
getMinMaxLatitudeLongitudeHI (X, Y, OffsetX, OffsetY) {
static getMinMaxLatitudeLongitudeHI(X, Y, OffsetX, OffsetY) {
const maxLat = ((Y / 55.2) - 25) * -1;
const minLat = (((Y + (OffsetY * 2)) / 55.2) - 25) * -1;
const minLon = (((X * -1) / 41.775) + 164.5) * -1;
const maxLon = ((((X + (OffsetX * 2)) * -1) / 41.775) + 164.5) * -1;
return { minLat, maxLat, minLon, maxLon };
return {
minLat, maxLat, minLon, maxLon,
};
}
getXYForCity (City, MaxLatitude, MinLongitude, state) {
if (state === 'AK') this.getXYForCityAK(...arguments);
if (state === 'HI') this.getXYForCityHI(...arguments);
getXYForCity(City, MaxLatitude, MinLongitude, state) {
if (state === 'AK') this.getXYForCityAK(City, MaxLatitude, MinLongitude);
if (state === 'HI') this.getXYForCityHI(City, MaxLatitude, MinLongitude);
let x = (City.lon - MinLongitude) * 57;
let y = (MaxLatitude - City.lat) * 70;
@ -292,7 +298,7 @@ class RegionalForecast extends WeatherDisplay {
return { x, y };
}
getXYForCityAK (City, MaxLatitude, MinLongitude) {
static getXYForCityAK(City, MaxLatitude, MinLongitude) {
let x = (City.lon - MinLongitude) * 37;
let y = (MaxLatitude - City.lat) * 70;
@ -304,7 +310,7 @@ class RegionalForecast extends WeatherDisplay {
return { x, y };
}
getXYForCityHI (City, MaxLatitude, MinLongitude) {
static getXYForCityHI(City, MaxLatitude, MinLongitude) {
let x = (City.lon - MinLongitude) * 57;
let y = (MaxLatitude - City.lat) * 70;
@ -318,19 +324,19 @@ class RegionalForecast extends WeatherDisplay {
}
// to fit on the map, remove anything after punctuation and then limit to 15 characters
formatCity(city) {
return city.match(/[^-;/\\,]*/)[0].substr(0,12);
static formatCity(city) {
return city.match(/[^-;/\\,]*/)[0].substr(0, 12);
}
async drawCanvas() {
super.drawCanvas();
// break up data into useful values
const {regionalData: data, sourceXY, offsetXY} = this.data;
const { regionalData: data, sourceXY, offsetXY } = this.data;
// fixed offset for all y values when drawing to the map
const mapYOff = 90;
const {DateTime} = luxon;
const { DateTime } = luxon;
// draw the header graphics
this.context.drawImage(await this.backgroundImage, 0, 0);
draw.horizontalGradientSingle(this.context, 0, 30, 500, 90, draw.topColor1, draw.topColor2);
@ -340,21 +346,21 @@ class RegionalForecast extends WeatherDisplay {
if (this.screenIndex === 0) {
draw.titleText(this.context, 'Regional', 'Observations');
} else {
let forecastDate = DateTime.fromISO(data[0][this.screenIndex].time);
const forecastDate = DateTime.fromISO(data[0][this.screenIndex].time);
// get the name of the day
const dayName = forecastDate.toLocaleString({weekday: 'long'});
const dayName = forecastDate.toLocaleString({ weekday: 'long' });
// draw the title
if (data[0][this.screenIndex].daytime) {
draw.titleText(this.context, 'Forecast for', dayName);
} else {
draw.titleText(this.context, 'Forecast for', dayName + ' Night');
draw.titleText(this.context, 'Forecast for', `${dayName} Night`);
}
}
// draw the map
this.context.drawImage(await this.baseMap, sourceXY.x, sourceXY.y, (offsetXY.x * 2), (offsetXY.y * 2), 0, mapYOff, 640, 312);
await Promise.all(data.map(async city => {
await Promise.all(data.map(async (city) => {
const period = city[this.screenIndex];
// draw the icon if possible
const icon = icons.getWeatherRegionalIconFromIconLink(period.icon, !period.daytime);
@ -365,20 +371,19 @@ class RegionalForecast extends WeatherDisplay {
auto_play: true,
canvas: this.canvas,
x: period.x,
y: period.y - 15+mapYOff,
y: period.y - 15 + mapYOff,
}));
}
// City Name
draw.text(this.context, 'Star4000', '20px', '#ffffff', period.x - 40, period.y - 15+mapYOff, period.name, 2);
draw.text(this.context, 'Star4000', '20px', '#ffffff', period.x - 40, period.y - 15 + mapYOff, period.name, 2);
// Temperature
let temperature = period.temperature;
let { temperature } = period;
if (navigation.units() === UNITS.metric) temperature = Math.round(utils.units.fahrenheitToCelsius(temperature));
draw.text(this.context, 'Star4000 Large Compressed', '28px', '#ffff00', period.x - (temperature.toString().length * 15), period.y + 20+mapYOff, temperature, 2);
draw.text(this.context, 'Star4000 Large Compressed', '28px', '#ffff00', period.x - (temperature.toString().length * 15), period.y + 20 + mapYOff, temperature, 2);
}));
this.finishDraw();
}
}

View file

@ -1,5 +1,5 @@
// travel forecast display
/* globals WeatherDisplay, utils, STATUS, UNITS, draw, navigation, icons, luxon, _TravelCities */
/* globals WeatherDisplay, utils, STATUS, UNITS, draw, navigation, icons, luxon, TravelCities */
// eslint-disable-next-line no-unused-vars
class TravelForecast extends WeatherDisplay {
@ -15,15 +15,15 @@ class TravelForecast extends WeatherDisplay {
// set up the timing
this.timing.baseDelay = 20;
// page sizes are 4 cities, calculate the number of pages necessary plus overflow
const pagesFloat = _TravelCities.length/4;
const pagesFloat = TravelCities.length / 4;
const pages = Math.floor(pagesFloat) - 2; // first page is already displayed, last page doesn't happen
const extra = pages%1;
const timingStep = this.cityHeight*4;
this.timing.delay = [150+timingStep];
const extra = pages % 1;
const timingStep = this.cityHeight * 4;
this.timing.delay = [150 + timingStep];
// add additional pages
for (let i = 0; i < pages; i++) this.timing.delay.push(timingStep);
for (let i = 0; i < pages; i += 1) this.timing.delay.push(timingStep);
// add the extra (not exactly 4 pages portion)
if (extra !== 0) this.timing.delay.push(Math.round(this.extra*this.cityHeight));
if (extra !== 0) this.timing.delay.push(Math.round(this.extra * this.cityHeight));
// add the final 3 second delay
this.timing.delay.push(150);
}
@ -31,25 +31,25 @@ class TravelForecast extends WeatherDisplay {
async getData() {
// super checks for enabled
if (!super.getData()) return;
const forecastPromises = _TravelCities.map(async city => {
const forecastPromises = TravelCities.map(async (city) => {
try {
// get point then forecast
const point = await utils.weather.getPoint(city.Latitude, city.Longitude);
const forecast = await utils.fetch.json(point.properties.forecast);
// determine today or tomorrow (shift periods by 1 if tomorrow)
const todayShift = forecast.properties.periods[0].isDaytime? 0:1;
const todayShift = forecast.properties.periods[0].isDaytime ? 0 : 1;
// return a pared-down forecast
return {
today: todayShift === 0,
high: forecast.properties.periods[todayShift].temperature,
low: forecast.properties.periods[todayShift+1].temperature,
low: forecast.properties.periods[todayShift + 1].temperature,
name: city.Name,
icon: icons.getWeatherRegionalIconFromIconLink(forecast.properties.periods[todayShift].icon),
};
} catch (e) {
console.error(`GetTravelWeather for ${city.Name} failed`);
console.error(e.status, e.responseJSON);
return {name: city.Name};
return { name: city.Name };
}
});
@ -58,7 +58,7 @@ class TravelForecast extends WeatherDisplay {
this.data = forecasts;
// 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.reduce((acc, forecast) => acc || forecast.high, false);
if (!hasData) {
this.setStatus(STATUS.noData);
return;
@ -68,7 +68,7 @@ class TravelForecast extends WeatherDisplay {
this.drawLongCanvas();
}
async drawLongCanvas () {
async drawLongCanvas() {
// create the "long" canvas if necessary
if (!this.longCanvas) {
this.longCanvas = document.createElement('canvas');
@ -79,7 +79,7 @@ class TravelForecast extends WeatherDisplay {
}
// stop all gifs
this.longCanvasGifs.forEach(gif => gif.pause());
this.longCanvasGifs.forEach((gif) => gif.pause());
// delete the gifs
this.longCanvasGifs.length = 0;
@ -87,23 +87,23 @@ class TravelForecast extends WeatherDisplay {
const cities = this.data;
// clean up existing gifs
this.gifs.forEach(gif => gif.pause());
this.gifs.forEach((gif) => gif.pause());
// delete the gifs
this.gifs.length = 0;
this.longContext.clearRect(0,0,this.longCanvas.width,this.longCanvas.height);
this.longContext.clearRect(0, 0, this.longCanvas.width, this.longCanvas.height);
// draw the "long" canvas with all cities
draw.box(this.longContext, 'rgb(35, 50, 112)', 0, 0, 640, _TravelCities.length*this.cityHeight);
draw.box(this.longContext, 'rgb(35, 50, 112)', 0, 0, 640, TravelCities.length * this.cityHeight);
for (let i = 0; i <= 4; i++) {
for (let i = 0; i <= 4; i += 1) {
const y = i * 346;
draw.horizontalGradient(this.longContext, 0, y, 640, y + 346, '#102080', '#001040');
}
await Promise.all(cities.map(async (city, index) => {
// calculate base y value
const y = 50+this.cityHeight*index;
const y = 50 + this.cityHeight * index;
// city name
draw.text(this.longContext, 'Star4000 Large Compressed', '24pt', '#FFFF00', 80, y, city.name, 2);
@ -111,7 +111,7 @@ class TravelForecast extends WeatherDisplay {
// check for forecast data
if (city.icon) {
// get temperatures and convert if necessary
let {low, high} = city;
let { low, high } = city;
if (navigation.units() === UNITS.metric) {
low = utils.units.fahrenheitToCelsius(low);
@ -140,7 +140,6 @@ class TravelForecast extends WeatherDisplay {
draw.text(this.longContext, 'Star4000 Small', '24pt', '#FFFFFF', 400, y - 18, 'NO TRAVEL', 2);
draw.text(this.longContext, 'Star4000 Small', '24pt', '#FFFFFF', 400, y, 'DATA AVAILABLE', 2);
}
}));
}
@ -157,7 +156,7 @@ class TravelForecast extends WeatherDisplay {
draw.horizontalGradientSingle(this.context, 0, 30, 500, 90, draw.topColor1, draw.topColor2);
draw.triangle(this.context, 'rgb(28, 10, 87)', 500, 30, 450, 90, 500, 90);
draw.titleText(this.context, 'Travel Forecast', 'For ' + this.getTravelCitiesDayName(cities));
draw.titleText(this.context, 'Travel Forecast', `For ${TravelForecast.getTravelCitiesDayName(cities)}`);
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFF00', 455, 105, 'LOW', 2);
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFF00', 510, 105, 'HIGH', 2);
@ -185,7 +184,7 @@ class TravelForecast extends WeatherDisplay {
const longCanvas = this.getLongCanvas();
// calculate scroll offset and don't go past end
let offsetY = Math.min(longCanvas.height-289, (count-150));
let offsetY = Math.min(longCanvas.height - 289, (count - 150));
// don't let offset go negative
if (offsetY < 0) offsetY = 0;
@ -194,15 +193,15 @@ class TravelForecast extends WeatherDisplay {
this.context.drawImage(longCanvas, 0, offsetY, 640, 289, 0, 110, 640, 289);
}
getTravelCitiesDayName(cities) {
const {DateTime} = luxon;
static getTravelCitiesDayName(cities) {
const { DateTime } = luxon;
// effectively returns early on the first found date
return cities.reduce((dayName, city) => {
if (city && dayName === '') {
// today or tomorrow
const day = DateTime.local().plus({days: (city.today)?0:1});
const day = DateTime.local().plus({ days: (city.today) ? 0 : 1 });
// return the day
return day.toLocaleString({weekday: 'long'});
return day.toLocaleString({ weekday: 'long' });
}
return dayName;
}, '');

View file

@ -1,4 +1,3 @@
'use strict';
// radar utilities
/* globals SuperGif */
@ -17,8 +16,7 @@ const utils = (() => {
// ****************************** load images *********************************
// load an image from a blob or url
const loadImg = (imgData, cors = false) => {
return new Promise(resolve => {
const loadImg = (imgData, cors = false) => new Promise((resolve) => {
const img = new Image();
img.onload = (e) => {
resolve(e.target);
@ -31,15 +29,12 @@ const utils = (() => {
img.src = url;
}
});
};
// async version of SuperGif
const superGifAsync = (e) => {
return new Promise(resolve => {
const superGifAsync = (e) => new Promise((resolve) => {
const gif = new SuperGif(e);
gif.load(() => resolve(gif));
});
};
// preload an image
// the goal is to get it in the browser's cache so it is available more quickly when the browser needs it
@ -65,24 +60,24 @@ const utils = (() => {
context.imageSmoothingEnabled = false;
// draw the image
context.drawImage(img, 0,0);
context.drawImage(img, 0, 0);
return context;
};
// *********************************** unit conversions ***********************
Math.round2 = (value, decimals) => Number(Math.round(value + 'e' + decimals) + 'e-' + decimals);
Math.round2 = (value, decimals) => Number(`${Math.round(`${value}e${decimals}`)}e-${decimals}`);
const mphToKph = (Mph) => Math.round(Mph * 1.60934);
const kphToMph = (Kph) => Math.round(Kph / 1.60934);
const celsiusToFahrenheit = (Celsius) => Math.round(Celsius * 9 / 5 + 32);
const fahrenheitToCelsius = (Fahrenheit) => Math.round2(((Fahrenheit) - 32) * 5 / 9, 1);
const celsiusToFahrenheit = (Celsius) => Math.round((Celsius * 9) / 5 + 32);
const fahrenheitToCelsius = (Fahrenheit) => Math.round2((((Fahrenheit) - 32) * 5) / 9, 1);
const milesToKilometers = (Miles) => Math.round(Miles * 1.60934);
const kilometersToMiles = (Kilometers) => Math.round(Kilometers / 1.60934);
const feetToMeters = (Feet) => Math.round(Feet * 0.3048);
const metersToFeet = (Meters) => Math.round(Meters / 0.3048);
const inchesToCentimeters = (Inches) => Math.round2(Inches * 2.54, 2);
const pascalToInHg = (Pascal) => Math.round2(Pascal*0.0002953,2);
const pascalToInHg = (Pascal) => Math.round2(Pascal * 0.0002953, 2);
// ***************************** calculations **********************************
@ -125,7 +120,7 @@ const utils = (() => {
const T = Temperature;
const V = WindSpeed;
return Math.round(35.74 + (0.6215 * T) - (35.75 * Math.pow(V, 0.16)) + (0.4275 * T * Math.pow(V, 0.16)));
return Math.round(35.74 + (0.6215 * T) - (35.75 * (V ** 0.16)) + (0.4275 * T * (V ** 0.16)));
};
// wind direction
@ -135,20 +130,22 @@ const utils = (() => {
return arr[(val % 16)];
};
const distance = (x1 ,y1, x2, y2) => Math.sqrt((x2-=x1)*x2 + (y2-=y1)*y2);
const distance = (x1, y1, x2, y2) => Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
// wrap a number to 0-m
const wrap = (x,m) => (x%m + m)%m;
const wrap = (x, m) => ((x % m) + m) % m;
// ********************************* strings *********************************************
const wordWrap = (str, ...rest) => {
let m = ((rest.length >= 1) ? rest[0] : 75);
let b = ((rest.length >= 2) ? rest[1] : '\n');
let c = ((rest.length >= 3) ? rest[2] : false);
const wordWrap = (str = '', ...rest) => {
const m = ((rest.length >= 1) ? rest[0] : 75);
const b = ((rest.length >= 2) ? rest[1] : '\n');
const c = ((rest.length >= 3) ? rest[2] : false);
let i, j, l, s, r;
str += '';
let i;
let j;
let l;
let s;
let r;
if (m < 1) {
return str;
@ -160,8 +157,8 @@ const utils = (() => {
r[i] += s.slice(0, j) + ((s = s.slice(j)).length ? b : '')) {
j = c === 2 || (j = s.slice(0, m + 1).match(/\S*(\s)?$/))[1]
? m
: j.input.length - j[0].length || c === true && m ||
j.input.length + (j = s.slice(m).match(/^\S*/))[0].length;
: ((j.input.length - j[0].length || c === true) && m)
|| j.input.length + (j = s.slice(m).match(/^\S*/))[0].length;
}
}
@ -169,7 +166,8 @@ const utils = (() => {
};
// ********************************* cors ********************************************
// rewrite some urls for local server
const rewriteUrl = (url) => {
const rewriteUrl = (_url) => {
let url = _url;
url = url.replace('https://api.weather.gov/', window.location.href);
url = url.replace('https://radar.weather.gov/', window.location.href);
url = url.replace('https://www.cpc.ncep.noaa.gov/', window.location.href);
@ -182,14 +180,14 @@ const utils = (() => {
const raw = (url, params) => fetchAsync(url, '', params);
const blob = (url, params) => fetchAsync(url, 'blob', params);
const fetchAsync = async (_url, responseType, _params={}) => {
// combine default and provided parametersutils
const params = Object.assign({}, {
const fetchAsync = async (_url, responseType, _params = {}) => {
// combine default and provided parameters
const params = {
method: 'GET',
mode: 'cors',
type: 'GET',
},
_params);
..._params,
};
// build a url, including the rewrite for cors if necessary
let corsUrl = _url;
if (params.cors === true) corsUrl = rewriteUrl(_url);
@ -212,11 +210,11 @@ const utils = (() => {
// return the requested response
switch (responseType) {
case 'json':
return await response.json();
return response.json();
case 'text':
return await response.text();
return response.text();
case 'blob':
return await response.blob();
return response.blob();
default:
return response;
}

View file

@ -19,7 +19,7 @@ class WeatherDisplay {
this.gifs = [];
this.data = undefined;
this.loadingStatus = STATUS.loading;
this.name = name?name:elemId;
this.name = name ?? elemId;
this.getDataCallbacks = [];
// default navigation timing
@ -59,7 +59,7 @@ class WeatherDisplay {
// create a checkbox in the selected displays area
const checkbox = document.createElement('template');
checkbox.innerHTML = `<label for="${this.elemId}Enabled">
<input type="checkbox" value="true" id="${this.elemId}Enabled" name="${this.elemId}Enabled"${this.enabled?' checked':''}/>
<input type="checkbox" value="true" id="${this.elemId}Enabled" name="${this.elemId}Enabled"${this.enabled ? ' checked' : ''}/>
${this.name}</label>`;
checkbox.content.firstChild.addEventListener('change', (e) => this.checkboxChange(e));
const availableDisplays = document.getElementById('enabledDisplays');
@ -99,7 +99,7 @@ class WeatherDisplay {
// create a canvas
const canvas = document.createElement('template');
canvas.innerHTML = `<canvas id='${elemId+'Canvas'}' width='${width}' height='${height}' style='display: none;' />`;
canvas.innerHTML = `<canvas id='${`${elemId}Canvas`}' width='${width}' height='${height}' style='display: none;' />`;
// add to the page
const container = document.getElementById('container');
@ -130,23 +130,22 @@ class WeatherDisplay {
// return any data requested before it was available
getDataCallback() {
// call each callback
this.getDataCallbacks.forEach(fxn => fxn(this.data));
this.getDataCallbacks.forEach((fxn) => fxn(this.data));
// clear the callbacks
this.getDataCallbacks = [];
}
drawCanvas() {
// stop all gifs
this.gifs.forEach(gif => gif.pause());
this.gifs.forEach((gif) => gif.pause());
// delete the gifs
this.gifs.length = 0;
// refresh the canvas
this.canvas = document.getElementById(this.elemId+'Canvas');
this.canvas = document.getElementById(`${this.elemId}Canvas`);
this.context = this.canvas.getContext('2d');
// clean up the first-run flag in screen index
if (this.screenIndex < 0)
this.screenIndex = 0;
if (this.screenIndex < 0) this.screenIndex = 0;
}
finishDraw() {
@ -155,7 +154,7 @@ class WeatherDisplay {
let OkToDrawCurrentDateTime = true;
let OkToDrawLogoImage = true;
// let OkToDrawCustomScrollText = false;
let bottom = undefined;
let bottom;
// visibility tests
// if (_ScrollText !== '') OkToDrawCustomScrollText = true;
@ -201,7 +200,7 @@ class WeatherDisplay {
drawCurrentDateTime(bottom) {
// only draw if canvas is active to conserve battery
if (!this.isActive()) return;
const {DateTime} = luxon;
const { DateTime } = luxon;
const font = 'Star4000 Small';
const size = '24pt';
const color = '#ffffff';
@ -226,10 +225,10 @@ class WeatherDisplay {
// Get the current date and time.
const now = DateTime.local();
//time = "11:35:08 PM";
const time = now.toLocaleString(DateTime.TIME_WITH_SECONDS).padStart(11,' ');
// time = "11:35:08 PM";
const time = now.toLocaleString(DateTime.TIME_WITH_SECONDS).padStart(11, ' ');
let x,y;
let x; let y;
if (bottom) {
x = 400;
y = 402;
@ -241,9 +240,9 @@ class WeatherDisplay {
x += 45;
}
draw.text(this.context, font, size, color, x, y, time.toUpperCase(), shadow); //y += 20;
draw.text(this.context, font, size, color, x, y, time.toUpperCase(), shadow); // y += 20;
const date = now.toFormat(' ccc LLL ') + now.day.toString().padStart(2,' ');
const date = now.toFormat(' ccc LLL ') + now.day.toString().padStart(2, ' ');
if (bottom) {
x = 55;
@ -255,7 +254,7 @@ class WeatherDisplay {
draw.text(this.context, font, size, color, x, y, date.toUpperCase(), shadow);
}
async drawNoaaImage () {
async drawNoaaImage() {
// load the image and store locally
if (!this.drawNoaaImage.image) {
this.drawNoaaImage.image = utils.image.load('images/noaa5.gif');
@ -265,7 +264,7 @@ class WeatherDisplay {
this.context.drawImage(img, 356, 39);
}
async drawLogoImage () {
async drawLogoImage() {
// load the image and store locally
if (!this.drawLogoImage.image) {
this.drawLogoImage.image = utils.image.load('images/Logo3.png');
@ -287,10 +286,9 @@ class WeatherDisplay {
// show the canvas
this.canvas.style.display = 'block';
// stop if timing has been disabled
if (!this.timing) return;
return false;
}
hideCanvas() {
this.resetNavBaseCount();
@ -299,7 +297,7 @@ class WeatherDisplay {
}
isActive() {
return document.getElementById(this.elemId+'Canvas').offsetParent !== null;
return document.getElementById(`${this.elemId}Canvas`).offsetParent !== null;
}
isEnabled() {
@ -318,7 +316,7 @@ class WeatherDisplay {
// see if play is active and screen is active
if (!navigation.isPlaying() || !this.isActive()) return;
// increment the base count
this.navBaseCount++;
this.navBaseCount += 1;
// call base count change if available for this function
if (this.baseCountChange) this.baseCountChange(this.navBaseCount);
@ -329,7 +327,7 @@ class WeatherDisplay {
async updateScreenFromBaseCount() {
// get the next screen index
let nextScreenIndex = this.screenIndexFromBaseCount();
const nextScreenIndex = this.screenIndexFromBaseCount();
// special cases for first and last frame
// must compare with false as nextScreenIndex could be 0 which is valid
@ -368,10 +366,10 @@ class WeatherDisplay {
// if the delay is provided as a single value, expand it to a series of the same value
let intermediateDelay = [];
if (typeof this.timing.delay === 'number') {
for (let i = 0; i < this.timing.totalScreens; i++) intermediateDelay.push(this.timing.delay);
for (let i = 0; i < this.timing.totalScreens; i += 1) intermediateDelay.push(this.timing.delay);
} else {
// map just the delays to the intermediate block
intermediateDelay = this.timing.delay.map(delay => {
intermediateDelay = this.timing.delay.map((delay) => {
if (typeof delay === 'object') return delay.time;
return delay;
});
@ -379,7 +377,7 @@ class WeatherDisplay {
// calculate the cumulative end point of each delay
let sum = 0;
this.timing.fullDelay = intermediateDelay.map(val => {
this.timing.fullDelay = intermediateDelay.map((val) => {
const calc = sum + val;
sum += val;
return calc;
@ -388,11 +386,11 @@ class WeatherDisplay {
// generate a list of screen either sequentially if not provided in an object or from the object
if (Array.isArray(this.timing.delay) && typeof this.timing.delay[0] === 'object') {
// extract screen indexes from objects
this.timing.screenIndexes = this.timing.delay.map(delay => delay.si);
this.timing.screenIndexes = this.timing.delay.map((delay) => delay.si);
} else {
// generate sequential screen indexes
this.timing.screenIndexes = [];
for (let i = 0; i < this.timing.totalScreens; i++) this.timing.screenIndexes.push(i);
for (let i = 0; i < this.timing.totalScreens; i += 1) this.timing.screenIndexes.push(i);
}
}
@ -403,7 +401,7 @@ class WeatherDisplay {
this.resetNavBaseCount();
} else {
// set the base count to the next available frame
const newBaseCount = this.timing.fullDelay.find(delay => delay > this.navBaseCount);
const newBaseCount = this.timing.fullDelay.find((delay) => delay > this.navBaseCount);
this.navBaseCount = newBaseCount;
}
this.updateScreenFromBaseCount();
@ -413,13 +411,13 @@ class WeatherDisplay {
navPrev(command) {
// check for special 'last frame' command
if (command === navigation.msg.command.lastFrame) {
this.navBaseCount = this.timing.fullDelay[this.timing.totalScreens-1]-1;
this.navBaseCount = this.timing.fullDelay[this.timing.totalScreens - 1] - 1;
} else {
// find the highest fullDelay that is less than the current base count
const newBaseCount = this.timing.fullDelay.reduce((acc, delay) => {
if (delay < this.navBaseCount) return delay;
return acc;
},0);
}, 0);
// if the new base count is zero then we're already at the first screen
if (newBaseCount === 0 && this.navBaseCount === 0) {
this.sendNavDisplayMessage(navigation.msg.response.previous);
@ -436,14 +434,14 @@ class WeatherDisplay {
if (!this.timing) return 0;
// find the first timing in the timing array that is greater than the base count
if (this.timing && !this.timing.fullDelay) this.calcNavTiming();
const timingIndex = this.timing.fullDelay.findIndex(delay => delay > this.navBaseCount);
const timingIndex = this.timing.fullDelay.findIndex((delay) => delay > this.navBaseCount);
if (timingIndex === -1) return false;
return this.timing.screenIndexes[timingIndex];
}
// start and stop base counter
startNavCount() {
if (!this.navInterval) this.navInterval = setInterval(()=>this.navBaseTime(), this.timing.baseDelay);
if (!this.navInterval) this.navInterval = setInterval(() => this.navBaseTime(), this.timing.baseDelay);
}
resetNavBaseCount() {

View file

@ -1 +1 @@
module.exports = '3.7.1';
module.exports = '3.8.0';

View file

@ -35,5 +35,6 @@
"**/vendor/auto/**",
"**/twc3.js",
],
"editor.tabSize": 2,
},
}