change to airbnb eslint plugin
This commit is contained in:
parent
b7967fca05
commit
dd98daf0c2
52
.eslintrc.js
52
.eslintrc.js
|
@ -1,35 +1,39 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
'env': {
|
env: {
|
||||||
'browser': true,
|
browser: true,
|
||||||
'commonjs': true,
|
commonjs: true,
|
||||||
'es6': true,
|
es6: true,
|
||||||
'node': true,
|
node: true,
|
||||||
'jquery': true,
|
jquery: true,
|
||||||
},
|
},
|
||||||
'extends': 'eslint:recommended',
|
extends: 'airbnb-base',
|
||||||
'globals': {
|
globals: {
|
||||||
'Atomics': 'readonly',
|
Atomics: 'readonly',
|
||||||
'SharedArrayBuffer': 'readonly'
|
SharedArrayBuffer: 'readonly',
|
||||||
},
|
},
|
||||||
'parserOptions': {
|
parserOptions: {
|
||||||
'ecmaVersion': 2018
|
ecmaVersion: 2020,
|
||||||
},
|
},
|
||||||
'rules': {
|
rules: {
|
||||||
'indent': [
|
indent: [
|
||||||
'error',
|
'error',
|
||||||
'tab'
|
'tab',
|
||||||
],
|
],
|
||||||
|
'no-tabs': 0,
|
||||||
|
'no-use-before-define': 0,
|
||||||
|
'no-console': 0,
|
||||||
'linebreak-style': [
|
'linebreak-style': [
|
||||||
'error',
|
'error',
|
||||||
'unix'
|
'unix',
|
||||||
],
|
],
|
||||||
'quotes': [
|
'max-len': 0,
|
||||||
|
quotes: [
|
||||||
'error',
|
'error',
|
||||||
'single'
|
'single',
|
||||||
],
|
],
|
||||||
'semi': [
|
semi: [
|
||||||
'error',
|
'error',
|
||||||
'always'
|
'always',
|
||||||
],
|
],
|
||||||
'no-prototype-builtins': 0,
|
'no-prototype-builtins': 0,
|
||||||
'comma-dangle': ['error', 'always-multiline'],
|
'comma-dangle': ['error', 'always-multiline'],
|
||||||
|
@ -37,14 +41,14 @@ module.exports = {
|
||||||
'default-case': ['error'],
|
'default-case': ['error'],
|
||||||
'default-param-last': ['error'],
|
'default-param-last': ['error'],
|
||||||
'dot-location': ['error', 'property'],
|
'dot-location': ['error', 'property'],
|
||||||
'eqeqeq': ['error'],
|
eqeqeq: ['error'],
|
||||||
'no-eval': ['error'],
|
'no-eval': ['error'],
|
||||||
'no-eq-null': ['error'],
|
'no-eq-null': ['error'],
|
||||||
'no-floating-decimal': ['error'],
|
'no-floating-decimal': ['error'],
|
||||||
'no-trailing-spaces': ['error'],
|
'no-trailing-spaces': ['error'],
|
||||||
'brace-style': [2, '1tbs', { 'allowSingleLine': true }],
|
'brace-style': [2, '1tbs', { allowSingleLine: true }],
|
||||||
},
|
},
|
||||||
'ignorePatterns': [
|
ignorePatterns: [
|
||||||
'*.min.js'
|
'*.min.js',
|
||||||
],
|
],
|
||||||
};
|
};
|
|
@ -1,4 +1,4 @@
|
||||||
const _StationInfo = {
|
const StationInfo = {
|
||||||
MADC: {
|
MADC: {
|
||||||
id: 'MADC',
|
id: 'MADC',
|
||||||
city: 'Durango Complex',
|
city: 'Durango Complex',
|
||||||
|
|
2
dist/index.html
vendored
2
dist/index.html
vendored
|
@ -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>
|
2
dist/resources/data.min.js
vendored
2
dist/resources/data.min.js
vendored
File diff suppressed because one or more lines are too long
4
dist/resources/ws.min.js
vendored
4
dist/resources/ws.min.js
vendored
File diff suppressed because one or more lines are too long
562
package-lock.json
generated
562
package-lock.json
generated
|
@ -44,9 +44,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@eslint/eslintrc": {
|
"@eslint/eslintrc": {
|
||||||
"version": "0.1.3",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.1.tgz",
|
||||||
"integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==",
|
"integrity": "sha512-XRUeBZ5zBWLYgSANMpThFddrZZkEbGHgUdt5UJjZfnlN9BGCiUBrf+nvbRupSjMvqzwnQN0qwCmOxITt1cfywA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ajv": "^6.12.4",
|
"ajv": "^6.12.4",
|
||||||
|
@ -104,26 +104,10 @@
|
||||||
"fastq": "^1.6.0"
|
"fastq": "^1.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/glob": {
|
"@types/json5": {
|
||||||
"version": "7.1.3",
|
"version": "0.0.29",
|
||||||
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
||||||
"integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==",
|
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
|
||||||
"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==",
|
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"accepts": {
|
"accepts": {
|
||||||
|
@ -308,6 +292,17 @@
|
||||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
|
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
|
||||||
"dev": true
|
"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": {
|
"array-initial": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz",
|
||||||
|
@ -380,6 +375,16 @@
|
||||||
"integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
|
"integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
|
||||||
"dev": true
|
"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": {
|
"assign-symbols": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
|
"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": {
|
"content-disposition": {
|
||||||
"version": "0.5.3",
|
"version": "0.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
|
||||||
|
@ -1118,7 +1135,6 @@
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
|
||||||
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
|
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"object-keys": "^1.0.12"
|
"object-keys": "^1.0.12"
|
||||||
}
|
}
|
||||||
|
@ -1165,18 +1181,18 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"del": {
|
"del": {
|
||||||
"version": "5.1.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/del/-/del-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz",
|
||||||
"integrity": "sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==",
|
"integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"globby": "^10.0.1",
|
"globby": "^11.0.1",
|
||||||
"graceful-fs": "^4.2.2",
|
"graceful-fs": "^4.2.4",
|
||||||
"is-glob": "^4.0.1",
|
"is-glob": "^4.0.1",
|
||||||
"is-path-cwd": "^2.2.0",
|
"is-path-cwd": "^2.2.0",
|
||||||
"is-path-inside": "^3.0.1",
|
"is-path-inside": "^3.0.2",
|
||||||
"p-map": "^3.0.0",
|
"p-map": "^4.0.0",
|
||||||
"rimraf": "^3.0.0",
|
"rimraf": "^3.0.2",
|
||||||
"slash": "^3.0.0"
|
"slash": "^3.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -1317,6 +1333,66 @@
|
||||||
"is-arrayish": "^0.2.1"
|
"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": {
|
"es5-ext": {
|
||||||
"version": "0.10.53",
|
"version": "0.10.53",
|
||||||
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz",
|
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz",
|
||||||
|
@ -1389,13 +1465,13 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"eslint": {
|
"eslint": {
|
||||||
"version": "7.11.0",
|
"version": "7.12.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.12.1.tgz",
|
||||||
"integrity": "sha512-G9+qtYVCHaDi1ZuWzBsOWo2wSwd70TXnU6UHA3cTYHp7gCTXZcpggWFoUVAMRarg68qtPoNfFbzPh+VdOgmwmw==",
|
"integrity": "sha512-HlMTEdr/LicJfN08LB3nM1rRYliDXOmfoO4vj39xN6BLpFzF00hbwBoqHk8UcJ2M/3nlARZWy/mslvGEuZFvsg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/code-frame": "^7.0.0",
|
"@babel/code-frame": "^7.0.0",
|
||||||
"@eslint/eslintrc": "^0.1.3",
|
"@eslint/eslintrc": "^0.2.1",
|
||||||
"ajv": "^6.10.0",
|
"ajv": "^6.10.0",
|
||||||
"chalk": "^4.0.0",
|
"chalk": "^4.0.0",
|
||||||
"cross-spawn": "^7.0.2",
|
"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": {
|
"eslint-scope": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||||
|
@ -1862,9 +2059,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"fastq": {
|
"fastq": {
|
||||||
"version": "1.8.0",
|
"version": "1.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.9.0.tgz",
|
||||||
"integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==",
|
"integrity": "sha512-i7FVWL8HhVY+CTkwFxkN2mk3h+787ixS5S63eb78diVRc1MCssarHq3W5cj0av7YDSwmaV928RNag+U1etRQ7w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"reusify": "^1.0.4"
|
"reusify": "^1.0.4"
|
||||||
|
@ -2073,8 +2270,7 @@
|
||||||
"function-bind": {
|
"function-bind": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"functional-red-black-tree": {
|
"functional-red-black-tree": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
|
@ -2216,18 +2412,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"globby": {
|
"globby": {
|
||||||
"version": "10.0.2",
|
"version": "11.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz",
|
||||||
"integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==",
|
"integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/glob": "^7.1.1",
|
|
||||||
"array-union": "^2.1.0",
|
"array-union": "^2.1.0",
|
||||||
"dir-glob": "^3.0.1",
|
"dir-glob": "^3.0.1",
|
||||||
"fast-glob": "^3.0.3",
|
"fast-glob": "^3.1.1",
|
||||||
"glob": "^7.1.3",
|
"ignore": "^5.1.4",
|
||||||
"ignore": "^5.1.1",
|
"merge2": "^1.3.0",
|
||||||
"merge2": "^1.2.3",
|
|
||||||
"slash": "^3.0.0"
|
"slash": "^3.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -2452,6 +2646,14 @@
|
||||||
"glogg": "^1.0.0"
|
"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": {
|
"has-flag": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||||
|
@ -2461,8 +2663,7 @@
|
||||||
"has-symbols": {
|
"has-symbols": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
|
||||||
"integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==",
|
"integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"has-value": {
|
"has-value": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
@ -2715,6 +2916,11 @@
|
||||||
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
|
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
|
||||||
"dev": true
|
"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": {
|
"is-data-descriptor": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
|
"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": {
|
"is-descriptor": {
|
||||||
"version": "0.1.6",
|
"version": "0.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
|
||||||
|
@ -2787,6 +2998,11 @@
|
||||||
"integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=",
|
"integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=",
|
||||||
"dev": true
|
"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": {
|
"is-number": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
|
||||||
|
@ -2834,6 +3050,14 @@
|
||||||
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
|
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
|
||||||
"dev": true
|
"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": {
|
"is-relative": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz",
|
||||||
|
@ -2849,6 +3073,20 @@
|
||||||
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
|
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
|
||||||
"dev": true
|
"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": {
|
"is-unc-path": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz",
|
||||||
|
@ -2965,6 +3203,15 @@
|
||||||
"integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
|
"integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
|
||||||
"dev": true
|
"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": {
|
"just-debounce": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz",
|
||||||
|
@ -3053,6 +3300,24 @@
|
||||||
"strip-bom": "^2.0.0"
|
"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": {
|
"lodash": {
|
||||||
"version": "4.17.20",
|
"version": "4.17.20",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
"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": {
|
"object-keys": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
|
||||||
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
|
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"object-visit": {
|
"object-visit": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
|
@ -3435,6 +3704,17 @@
|
||||||
"isobject": "^3.0.0"
|
"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": {
|
"object.map": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz",
|
||||||
|
@ -3464,6 +3744,18 @@
|
||||||
"make-iterator": "^1.0.0"
|
"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": {
|
"on-finished": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||||
|
@ -3514,15 +3806,39 @@
|
||||||
"lcid": "^1.0.0"
|
"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": {
|
"p-map": {
|
||||||
"version": "3.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
|
||||||
"integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==",
|
"integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"aggregate-error": "^3.0.0"
|
"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": {
|
"param-case": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
|
||||||
|
@ -3686,6 +4002,26 @@
|
||||||
"pinkie": "^2.0.0"
|
"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": {
|
"plugin-error": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz",
|
||||||
|
@ -4014,9 +4350,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"run-parallel": {
|
"run-parallel": {
|
||||||
"version": "1.1.9",
|
"version": "1.1.10",
|
||||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz",
|
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz",
|
||||||
"integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==",
|
"integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"safe-buffer": {
|
"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": {
|
"string_decoder": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||||
|
@ -4672,6 +5090,26 @@
|
||||||
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
|
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
|
||||||
"dev": true
|
"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": {
|
"type": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
|
||||||
|
@ -4915,9 +5353,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"v8-compile-cache": {
|
"v8-compile-cache": {
|
||||||
"version": "2.1.1",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz",
|
||||||
"integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==",
|
"integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"v8flags": {
|
"v8flags": {
|
||||||
|
|
|
@ -17,9 +17,11 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/netbymatt/ws4kp#readme",
|
"homepage": "https://github.com/netbymatt/ws4kp#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"del": "^5.1.0",
|
"del": "^6.0.0",
|
||||||
"ejs": "^3.1.5",
|
"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",
|
"express": "^4.17.1",
|
||||||
"gulp": "^4.0.2",
|
"gulp": "^4.0.2",
|
||||||
"gulp-clean-css": "^4.3.0",
|
"gulp-clean-css": "^4.3.0",
|
||||||
|
|
|
@ -2,3 +2,4 @@
|
||||||
[1020/120229.281:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
|
[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)
|
[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)
|
[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)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
const _RegionalCities = [
|
const RegionalCities = [
|
||||||
{
|
{
|
||||||
city: 'Atlanta',
|
city: 'Atlanta',
|
||||||
lat: 33.749,
|
lat: 33.749,
|
||||||
|
|
|
@ -1,60 +1,59 @@
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
const states = (() => {
|
const states = (() => {
|
||||||
const stateList = {
|
const stateList = {
|
||||||
'Arizona': 'AZ',
|
Arizona: 'AZ',
|
||||||
'Alabama': 'AL',
|
Alabama: 'AL',
|
||||||
'Alaska': 'AK',
|
Alaska: 'AK',
|
||||||
'Arkansas': 'AR',
|
Arkansas: 'AR',
|
||||||
'California': 'CA',
|
California: 'CA',
|
||||||
'Colorado': 'CO',
|
Colorado: 'CO',
|
||||||
'Connecticut': 'CT',
|
Connecticut: 'CT',
|
||||||
'Delaware': 'DE',
|
Delaware: 'DE',
|
||||||
'Florida': 'FL',
|
Florida: 'FL',
|
||||||
'Georgia': 'GA',
|
Georgia: 'GA',
|
||||||
'Hawaii': 'HI',
|
Hawaii: 'HI',
|
||||||
'Idaho': 'ID',
|
Idaho: 'ID',
|
||||||
'Illinois': 'IL',
|
Illinois: 'IL',
|
||||||
'Indiana': 'IN',
|
Indiana: 'IN',
|
||||||
'Iowa': 'IA',
|
Iowa: 'IA',
|
||||||
'Kansas': 'KS',
|
Kansas: 'KS',
|
||||||
'Kentucky': 'KY',
|
Kentucky: 'KY',
|
||||||
'Louisiana': 'LA',
|
Louisiana: 'LA',
|
||||||
'Maine': 'ME',
|
Maine: 'ME',
|
||||||
'Maryland': 'MD',
|
Maryland: 'MD',
|
||||||
'Massachusetts': 'MA',
|
Massachusetts: 'MA',
|
||||||
'Michigan': 'MI',
|
Michigan: 'MI',
|
||||||
'Minnesota': 'MN',
|
Minnesota: 'MN',
|
||||||
'Mississippi': 'MS',
|
Mississippi: 'MS',
|
||||||
'Missouri': 'MO',
|
Missouri: 'MO',
|
||||||
'Montana': 'MT',
|
Montana: 'MT',
|
||||||
'Nebraska': 'NE',
|
Nebraska: 'NE',
|
||||||
'Nevada': 'NV',
|
Nevada: 'NV',
|
||||||
'New Hampshire': 'NH',
|
'New Hampshire': 'NH',
|
||||||
'New Jersey': 'NJ',
|
'New Jersey': 'NJ',
|
||||||
'New Mexico': 'NM',
|
'New Mexico': 'NM',
|
||||||
'New York': 'NY',
|
'New York': 'NY',
|
||||||
'North Carolina': 'NC',
|
'North Carolina': 'NC',
|
||||||
'North Dakota': 'ND',
|
'North Dakota': 'ND',
|
||||||
'Ohio': 'OH',
|
Ohio: 'OH',
|
||||||
'Oklahoma': 'OK',
|
Oklahoma: 'OK',
|
||||||
'Oregon': 'OR',
|
Oregon: 'OR',
|
||||||
'Pennsylvania': 'PA',
|
Pennsylvania: 'PA',
|
||||||
'Rhode Island': 'RI',
|
'Rhode Island': 'RI',
|
||||||
'South Carolina': 'SC',
|
'South Carolina': 'SC',
|
||||||
'South Dakota': 'SD',
|
'South Dakota': 'SD',
|
||||||
'Tennessee': 'TN',
|
Tennessee: 'TN',
|
||||||
'Texas': 'TX',
|
Texas: 'TX',
|
||||||
'Utah': 'UT',
|
Utah: 'UT',
|
||||||
'Vermont': 'VT',
|
Vermont: 'VT',
|
||||||
'Virginia': 'VA',
|
Virginia: 'VA',
|
||||||
'Washington': 'WA',
|
Washington: 'WA',
|
||||||
'West Virginia': 'WV',
|
'West Virginia': 'WV',
|
||||||
'Wisconsin': 'WI',
|
Wisconsin: 'WI',
|
||||||
'Wyoming': 'WY',
|
Wyoming: 'WY',
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getTwoDigitCode: (stateFullName) => stateList[stateFullName],
|
getTwoDigitCode: (stateFullName) => stateList[stateFullName],
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
const _StationInfo = {
|
// cspell: disable
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const StationInfo = {
|
||||||
MADC: {
|
MADC: {
|
||||||
id: 'MADC',
|
id: 'MADC',
|
||||||
city: 'Durango Complex',
|
city: 'Durango Complex',
|
||||||
|
|
|
@ -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
|
// eslint-disable-next-line no-unused-vars
|
||||||
const _TravelCities = [
|
const TravelCities = [
|
||||||
{
|
{
|
||||||
Name: 'Atlanta',
|
Name: 'Atlanta',
|
||||||
Latitude: 33.749,
|
Latitude: 33.749,
|
||||||
|
@ -146,4 +121,3 @@ const _TravelCities = [
|
||||||
Longitude: -77.0364,
|
Longitude: -77.0364,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
'use strict';
|
|
||||||
/* globals NoSleep, states, navigation, UNITS, utils */
|
/* globals NoSleep, states, navigation, UNITS, utils */
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
index.init();
|
index.init();
|
||||||
|
@ -8,17 +7,16 @@ const index = (() => {
|
||||||
const overrides = {
|
const overrides = {
|
||||||
// '32899, Orlando, Florida, USA': { x: -80.6774, y: 28.6143 },
|
// '32899, Orlando, Florida, USA': { x: -80.6774, y: 28.6143 },
|
||||||
};
|
};
|
||||||
const _AutoRefreshIntervalMs = 500;
|
const AutoRefreshIntervalMs = 500;
|
||||||
const _AutoRefreshTotalIntervalMs = 600000; // 10 min.
|
const AutoRefreshTotalIntervalMs = 600000; // 10 min.
|
||||||
const _NoSleep = new NoSleep();
|
|
||||||
|
|
||||||
let _AutoSelectQuery = false;
|
let AutoSelectQuery = false;
|
||||||
|
|
||||||
let _LastUpdate = null;
|
let LastUpdate = null;
|
||||||
let _AutoRefreshIntervalId = null;
|
let AutoRefreshIntervalId = null;
|
||||||
let _AutoRefreshCountMs = 0;
|
let AutoRefreshCountMs = 0;
|
||||||
|
|
||||||
let _FullScreenOverride = false;
|
let FullScreenOverride = false;
|
||||||
|
|
||||||
const categories = [
|
const categories = [
|
||||||
'Land Features',
|
'Land Features',
|
||||||
|
@ -37,20 +35,20 @@ const index = (() => {
|
||||||
e.target.select();
|
e.target.select();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('NavigateMenu').addEventListener('click', btnNavigateMenu_click);
|
document.getElementById('NavigateMenu').addEventListener('click', btnNavigateMenuClick);
|
||||||
document.getElementById('NavigateRefresh').addEventListener('click', btnNavigateRefresh_click);
|
document.getElementById('NavigateRefresh').addEventListener('click', btnNavigateRefreshClick);
|
||||||
document.getElementById('NavigateNext').addEventListener('click', btnNavigateNext_click);
|
document.getElementById('NavigateNext').addEventListener('click', btnNavigateNextClick);
|
||||||
document.getElementById('NavigatePrevious').addEventListener('click', btnNavigatePrevious_click);
|
document.getElementById('NavigatePrevious').addEventListener('click', btnNavigatePreviousClick);
|
||||||
document.getElementById('NavigatePlay').addEventListener('click', btnNavigatePlay_click);
|
document.getElementById('NavigatePlay').addEventListener('click', btnNavigatePlayClick);
|
||||||
document.getElementById('ToggleFullScreen').addEventListener('click', btnFullScreen_click);
|
document.getElementById('ToggleFullScreen').addEventListener('click', btnFullScreenClick);
|
||||||
document.getElementById('btnGetGps').addEventListener('click', btnGetGps_click);
|
document.getElementById('btnGetGps').addEventListener('click', btnGetGpsClick);
|
||||||
|
|
||||||
document.getElementById('divTwc').addEventListener('click', () => {
|
document.getElementById('divTwc').addEventListener('click', () => {
|
||||||
if (document.fullscreenElement) UpdateFullScreenNavigate();
|
if (document.fullscreenElement) UpdateFullScreenNavigate();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('keydown', document_keydown);
|
document.addEventListener('keydown', documentKeydown);
|
||||||
document.addEventListener('touchmove', e => { if (_FullScreenOverride) e.preventDefault(); });
|
document.addEventListener('touchmove', (e) => { if (FullScreenOverride) e.preventDefault(); });
|
||||||
|
|
||||||
$('#frmGetLatLng #txtAddress').devbridgeAutocomplete({
|
$('#frmGetLatLng #txtAddress').devbridgeAutocomplete({
|
||||||
serviceUrl: 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/suggest',
|
serviceUrl: 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/suggest',
|
||||||
|
@ -58,26 +56,24 @@ const index = (() => {
|
||||||
paramName: 'text',
|
paramName: 'text',
|
||||||
params: {
|
params: {
|
||||||
f: 'json',
|
f: 'json',
|
||||||
countryCode: 'USA', //'USA,PRI,VIR,GUM,ASM',
|
countryCode: 'USA', // 'USA,PRI,VIR,GUM,ASM',
|
||||||
category: cats,
|
category: cats,
|
||||||
maxSuggestions: 10,
|
maxSuggestions: 10,
|
||||||
},
|
},
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
transformResult: (response) => {
|
transformResult: (response) => {
|
||||||
if (_AutoSelectQuery) {
|
if (AutoSelectQuery) {
|
||||||
_AutoSelectQuery = false;
|
AutoSelectQuery = false;
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
$(ac.suggestionsContainer.children[0]).click();
|
$(ac.suggestionsContainer.children[0]).click();
|
||||||
}, 1);
|
}, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
suggestions: $.map(response.suggestions, function (i) {
|
suggestions: $.map(response.suggestions, (i) => ({
|
||||||
return {
|
value: i.text,
|
||||||
value: i.text,
|
data: i.magicKey,
|
||||||
data: i.magicKey,
|
})),
|
||||||
};
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
minChars: 3,
|
minChars: 3,
|
||||||
|
@ -96,7 +92,7 @@ const index = (() => {
|
||||||
// Auto load the previous query
|
// Auto load the previous query
|
||||||
const TwcQuery = localStorage.getItem('TwcQuery');
|
const TwcQuery = localStorage.getItem('TwcQuery');
|
||||||
if (TwcQuery) {
|
if (TwcQuery) {
|
||||||
_AutoSelectQuery = true;
|
AutoSelectQuery = true;
|
||||||
const txtAddress = document.getElementById('txtAddress');
|
const txtAddress = document.getElementById('txtAddress');
|
||||||
txtAddress.value = TwcQuery;
|
txtAddress.value = TwcQuery;
|
||||||
txtAddress.blur();
|
txtAddress.blur();
|
||||||
|
@ -141,7 +137,7 @@ const index = (() => {
|
||||||
document.getElementById('chkAutoRefresh').addEventListener('change', (e) => {
|
document.getElementById('chkAutoRefresh').addEventListener('change', (e) => {
|
||||||
const Checked = e.target.checked;
|
const Checked = e.target.checked;
|
||||||
|
|
||||||
if (_LastUpdate) {
|
if (LastUpdate) {
|
||||||
if (Checked) {
|
if (Checked) {
|
||||||
StartAutoRefreshTimer();
|
StartAutoRefreshTimer();
|
||||||
} else {
|
} else {
|
||||||
|
@ -162,12 +158,10 @@ const index = (() => {
|
||||||
// swipe functionality
|
// swipe functionality
|
||||||
document.getElementById('container').addEventListener('swiped-left', () => swipeCallBack('left'));
|
document.getElementById('container').addEventListener('swiped-left', () => swipeCallBack('left'));
|
||||||
document.getElementById('container').addEventListener('swiped-right', () => swipeCallBack('right'));
|
document.getElementById('container').addEventListener('swiped-right', () => swipeCallBack('right'));
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeUnits = (e) => {
|
const changeUnits = (e) => {
|
||||||
const Units = e.target.value;
|
const Units = e.target.value;
|
||||||
e;
|
|
||||||
localStorage.setItem('TwcUnits', Units);
|
localStorage.setItem('TwcUnits', Units);
|
||||||
AssignLastUpdate();
|
AssignLastUpdate();
|
||||||
postMessage('units', Units);
|
postMessage('units', Units);
|
||||||
|
@ -175,7 +169,7 @@ const index = (() => {
|
||||||
|
|
||||||
const autocompleteOnSelect = async (suggestion) => {
|
const autocompleteOnSelect = async (suggestion) => {
|
||||||
// Do not auto get the same city twice.
|
// Do not auto get the same city twice.
|
||||||
if (this.previousSuggestionValue === suggestion.value) return;
|
if (this.previousSuggestionValue === suggestion.value) return;
|
||||||
|
|
||||||
if (overrides[suggestion.value]) {
|
if (overrides[suggestion.value]) {
|
||||||
doRedirectToGeometry(overrides[suggestion.value]);
|
doRedirectToGeometry(overrides[suggestion.value]);
|
||||||
|
@ -192,20 +186,19 @@ const index = (() => {
|
||||||
if (loc) {
|
if (loc) {
|
||||||
doRedirectToGeometry(loc.feature.geometry);
|
doRedirectToGeometry(loc.feature.geometry);
|
||||||
} else {
|
} 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 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);
|
LoadTwcData(latLon);
|
||||||
// Save the query
|
// Save the query
|
||||||
localStorage.setItem('TwcQuery', document.getElementById('txtAddress').value);
|
localStorage.setItem('TwcQuery', document.getElementById('txtAddress').value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const btnFullScreen_click = () => {
|
const btnFullScreenClick = () => {
|
||||||
|
|
||||||
if (!document.fullscreenElement) {
|
if (!document.fullscreenElement) {
|
||||||
EnterFullScreen();
|
EnterFullScreen();
|
||||||
} else {
|
} else {
|
||||||
|
@ -213,9 +206,9 @@ const index = (() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (navigation.isPlaying()) {
|
if (navigation.isPlaying()) {
|
||||||
noSleepEnable();
|
noSleep(true);
|
||||||
} else {
|
} else {
|
||||||
noSleepDisable();
|
noSleep(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateFullScreenNavigate();
|
UpdateFullScreenNavigate();
|
||||||
|
@ -227,7 +220,8 @@ const index = (() => {
|
||||||
const element = document.getElementById('divTwc');
|
const element = document.getElementById('divTwc');
|
||||||
|
|
||||||
// Supports most browsers and their versions.
|
// 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) {
|
if (requestMethod) {
|
||||||
// Native full screen.
|
// Native full screen.
|
||||||
|
@ -235,7 +229,7 @@ const index = (() => {
|
||||||
} else {
|
} else {
|
||||||
// iOS doesn't support FullScreen API.
|
// iOS doesn't support FullScreen API.
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
_FullScreenOverride = true;
|
FullScreenOverride = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateFullScreenNavigate();
|
UpdateFullScreenNavigate();
|
||||||
|
@ -244,8 +238,8 @@ const index = (() => {
|
||||||
const ExitFullscreen = () => {
|
const ExitFullscreen = () => {
|
||||||
// exit full-screen
|
// exit full-screen
|
||||||
|
|
||||||
if (_FullScreenOverride) {
|
if (FullScreenOverride) {
|
||||||
_FullScreenOverride = false;
|
FullScreenOverride = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.exitFullscreen) {
|
if (document.exitFullscreen) {
|
||||||
|
@ -260,7 +254,7 @@ const index = (() => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const btnNavigateMenu_click = () => {
|
const btnNavigateMenuClick = () => {
|
||||||
postMessage('navButton', 'menu');
|
postMessage('navButton', 'menu');
|
||||||
UpdateFullScreenNavigate();
|
UpdateFullScreenNavigate();
|
||||||
return false;
|
return false;
|
||||||
|
@ -270,134 +264,133 @@ const index = (() => {
|
||||||
// if latlon is provided store it locally
|
// if latlon is provided store it locally
|
||||||
if (_latLon) LoadTwcData.latLon = _latLon;
|
if (_latLon) LoadTwcData.latLon = _latLon;
|
||||||
// get the data
|
// get the data
|
||||||
const latLon = LoadTwcData.latLon;
|
const { latLon } = LoadTwcData;
|
||||||
// if there's no data stop
|
// if there's no data stop
|
||||||
if (!latLon) return;
|
if (!latLon) return;
|
||||||
|
|
||||||
document.getElementById('txtAddress').blur();
|
document.getElementById('txtAddress').blur();
|
||||||
StopAutoRefreshTimer();
|
StopAutoRefreshTimer();
|
||||||
_LastUpdate = null;
|
LastUpdate = null;
|
||||||
AssignLastUpdate();
|
AssignLastUpdate();
|
||||||
|
|
||||||
postMessage('latLon', latLon);
|
postMessage('latLon', latLon);
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const swipeCallBack = (direction) => {
|
const swipeCallBack = (direction) => {
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case 'left':
|
case 'left':
|
||||||
btnNavigateNext_click();
|
btnNavigateNextClick();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'right':
|
case 'right':
|
||||||
default:
|
default:
|
||||||
btnNavigatePrevious_click();
|
btnNavigatePreviousClick();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const AssignLastUpdate = () => {
|
const AssignLastUpdate = () => {
|
||||||
let LastUpdate = '(None)';
|
if (LastUpdate) {
|
||||||
|
|
||||||
if (_LastUpdate) {
|
|
||||||
switch (navigation.units()) {
|
switch (navigation.units()) {
|
||||||
case UNITS.english:
|
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;
|
break;
|
||||||
default:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('spanLastRefresh').innerHTML = LastUpdate;
|
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();
|
LoadTwcData();
|
||||||
UpdateFullScreenNavigate();
|
UpdateFullScreenNavigate();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const btnNavigateNext_click = () => {
|
const btnNavigateNextClick = () => {
|
||||||
postMessage('navButton', 'next');
|
postMessage('navButton', 'next');
|
||||||
UpdateFullScreenNavigate();
|
UpdateFullScreenNavigate();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const btnNavigatePrevious_click = () => {
|
const btnNavigatePreviousClick = () => {
|
||||||
postMessage('navButton', 'previous');
|
postMessage('navButton', 'previous');
|
||||||
UpdateFullScreenNavigate();
|
UpdateFullScreenNavigate();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
let _NavigateFadeIntervalId = null;
|
let NavigateFadeIntervalId = null;
|
||||||
|
|
||||||
const UpdateFullScreenNavigate = () => {
|
const UpdateFullScreenNavigate = () => {
|
||||||
document.activeElement.blur();
|
document.activeElement.blur();
|
||||||
document.getElementById('divTwcBottom').classList.remove('hidden');
|
document.getElementById('divTwcBottom').classList.remove('hidden');
|
||||||
document.getElementById('divTwcBottom').classList.add('visible');
|
document.getElementById('divTwcBottom').classList.add('visible');
|
||||||
|
|
||||||
if (_NavigateFadeIntervalId) {
|
if (NavigateFadeIntervalId) {
|
||||||
clearTimeout(_NavigateFadeIntervalId);
|
clearTimeout(NavigateFadeIntervalId);
|
||||||
_NavigateFadeIntervalId = null;
|
NavigateFadeIntervalId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_NavigateFadeIntervalId = setTimeout(() => {
|
NavigateFadeIntervalId = setTimeout(() => {
|
||||||
if (document.fullscreenElement) {
|
if (document.fullscreenElement) {
|
||||||
document.getElementById('divTwcBottom').classList.remove('visible');
|
document.getElementById('divTwcBottom').classList.remove('visible');
|
||||||
document.getElementById('divTwcBottom').classList.add('hidden');
|
document.getElementById('divTwcBottom').classList.add('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
}, 2000);
|
}, 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
const document_keydown = (e) => {
|
const documentKeydown = (e) => {
|
||||||
|
|
||||||
const code = (e.keyCode || e.which);
|
const code = (e.keyCode || e.which);
|
||||||
|
|
||||||
if (document.fullscreenElement || document.activeElement === document.body) {
|
if (document.fullscreenElement || document.activeElement === document.body) {
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case 32: // Space
|
case 32: // Space
|
||||||
btnNavigatePlay_click();
|
btnNavigatePlayClick();
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
case 39: // Right Arrow
|
case 39: // Right Arrow
|
||||||
case 34: // Page Down
|
case 34: // Page Down
|
||||||
btnNavigateNext_click();
|
btnNavigateNextClick();
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
case 37: // Left Arrow
|
case 37: // Left Arrow
|
||||||
case 33: // Page Up
|
case 33: // Page Up
|
||||||
btnNavigatePrevious_click();
|
btnNavigatePreviousClick();
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
case 36: // Home
|
case 36: // Home
|
||||||
btnNavigateMenu_click();
|
btnNavigateMenuClick();
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
case 48: // Restart
|
case 48: // Restart
|
||||||
btnNavigateRefresh_click();
|
btnNavigateRefreshClick();
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
case 70: // F
|
case 70: // F
|
||||||
btnFullScreen_click();
|
btnFullScreenClick();
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
default:
|
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');
|
postMessage('navButton', 'playToggle');
|
||||||
UpdateFullScreenNavigate();
|
UpdateFullScreenNavigate();
|
||||||
|
|
||||||
|
@ -411,7 +404,7 @@ const index = (() => {
|
||||||
if (!data.type) return;
|
if (!data.type) return;
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case 'loaded':
|
case 'loaded':
|
||||||
_LastUpdate = new Date();
|
LastUpdate = new Date();
|
||||||
AssignLastUpdate();
|
AssignLastUpdate();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -422,79 +415,74 @@ const index = (() => {
|
||||||
case 'isPlaying':
|
case 'isPlaying':
|
||||||
localStorage.setItem('TwcPlay', navigation.isPlaying());
|
localStorage.setItem('TwcPlay', navigation.isPlaying());
|
||||||
|
|
||||||
|
|
||||||
if (navigation.isPlaying()) {
|
if (navigation.isPlaying()) {
|
||||||
noSleepEnable();
|
noSleep(true);
|
||||||
playButton.title = 'Pause';
|
playButton.title = 'Pause';
|
||||||
playButton.src = 'images/nav/ic_pause_white_24dp_1x.png';
|
playButton.src = 'images/nav/ic_pause_white_24dp_1x.png';
|
||||||
} else {
|
} else {
|
||||||
noSleepDisable();
|
noSleep(false);
|
||||||
playButton.title = 'Play';
|
playButton.title = 'Play';
|
||||||
playButton.src = 'images/nav/ic_play_arrow_white_24dp_1x.png';
|
playButton.src = 'images/nav/ic_play_arrow_white_24dp_1x.png';
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.error(`Unknown event '${data.eventType}`);
|
console.error(`Unknown event '${data.eventType}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// post a message to the iframe
|
// post a message to the iframe
|
||||||
const postMessage = (type, message = {}) => {
|
const postMessage = (type, myMessage = {}) => {
|
||||||
navigation.message({type, message});
|
navigation.message({ type, message: myMessage });
|
||||||
};
|
};
|
||||||
|
|
||||||
const StartAutoRefreshTimer = () => {
|
const StartAutoRefreshTimer = () => {
|
||||||
// Ensure that any previous timer has already stopped.
|
// Ensure that any previous timer has already stopped.
|
||||||
// check if timer is running
|
// check if timer is running
|
||||||
if (_AutoRefreshIntervalId) return;
|
if (AutoRefreshIntervalId) return;
|
||||||
|
|
||||||
// Reset the time elapsed.
|
// Reset the time elapsed.
|
||||||
_AutoRefreshCountMs = 0;
|
AutoRefreshCountMs = 0;
|
||||||
|
|
||||||
const AutoRefreshTimer = () => {
|
const AutoRefreshTimer = () => {
|
||||||
// Increment the total time elapsed.
|
// Increment the total time elapsed.
|
||||||
_AutoRefreshCountMs += _AutoRefreshIntervalMs;
|
AutoRefreshCountMs += AutoRefreshIntervalMs;
|
||||||
|
|
||||||
// Display the count down.
|
// Display the count down.
|
||||||
let RemainingMs = (_AutoRefreshTotalIntervalMs - _AutoRefreshCountMs);
|
let RemainingMs = (AutoRefreshTotalIntervalMs - AutoRefreshCountMs);
|
||||||
if (RemainingMs < 0) {
|
if (RemainingMs < 0) {
|
||||||
RemainingMs = 0;
|
RemainingMs = 0;
|
||||||
}
|
}
|
||||||
const dt = new Date(RemainingMs);
|
const dt = new Date(RemainingMs);
|
||||||
document.getElementById('spanRefreshCountDown').innerHTML = (dt.getMinutes() < 10 ? '0' + dt.getMinutes() : dt.getMinutes()) + ':' + (dt.getSeconds() < 10 ? '0' + dt.getSeconds() : dt.getSeconds());
|
document.getElementById('spanRefreshCountDown').innerHTML = `${dt.getMinutes() < 10 ? `0${dt.getMinutes()}` : dt.getMinutes()}:${dt.getSeconds() < 10 ? `0${dt.getSeconds()}` : dt.getSeconds()}`;
|
||||||
|
|
||||||
// Time has elapsed.
|
// Time has elapsed.
|
||||||
if (_AutoRefreshCountMs >= _AutoRefreshTotalIntervalMs) LoadTwcData();
|
if (AutoRefreshCountMs >= AutoRefreshTotalIntervalMs) LoadTwcData();
|
||||||
};
|
};
|
||||||
_AutoRefreshIntervalId = window.setInterval(AutoRefreshTimer, _AutoRefreshIntervalMs);
|
AutoRefreshIntervalId = window.setInterval(AutoRefreshTimer, AutoRefreshIntervalMs);
|
||||||
AutoRefreshTimer();
|
AutoRefreshTimer();
|
||||||
};
|
};
|
||||||
const StopAutoRefreshTimer = () => {
|
const StopAutoRefreshTimer = () => {
|
||||||
if (_AutoRefreshIntervalId) {
|
if (AutoRefreshIntervalId) {
|
||||||
window.clearInterval(_AutoRefreshIntervalId);
|
window.clearInterval(AutoRefreshIntervalId);
|
||||||
document.getElementById('spanRefreshCountDown').innerHTML = '--:--';
|
document.getElementById('spanRefreshCountDown').innerHTML = '--:--';
|
||||||
_AutoRefreshIntervalId = null;
|
AutoRefreshIntervalId = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const btnGetGps_click = async () => {
|
const btnGetGpsClick = async () => {
|
||||||
if (!navigator.geolocation) return;
|
if (!navigator.geolocation) return;
|
||||||
|
|
||||||
const position = await (() => {
|
const position = await (() => new Promise((resolve) => {
|
||||||
return new Promise(resolve => {
|
navigator.geolocation.getCurrentPosition(resolve);
|
||||||
navigator.geolocation.getCurrentPosition(resolve);
|
}))();
|
||||||
});
|
const { latitude, longitude } = position.coords;
|
||||||
})();
|
|
||||||
const latitude = position.coords.latitude;
|
|
||||||
const longitude = position.coords.longitude;
|
|
||||||
|
|
||||||
let data;
|
let data;
|
||||||
try {
|
try {
|
||||||
data = await utils.fetch.json('https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/reverseGeocode', {
|
data = await utils.fetch.json('https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/reverseGeocode', {
|
||||||
data: {
|
data: {
|
||||||
location: longitude + ',' + latitude,
|
location: `${longitude},${latitude}`,
|
||||||
distance: 1000, // Find location up to 1 KM.
|
distance: 1000, // Find location up to 1 KM.
|
||||||
f: 'json',
|
f: 'json',
|
||||||
},
|
},
|
||||||
|
@ -504,7 +492,7 @@ const index = (() => {
|
||||||
console.error(e.status, e.responseJSONe);
|
console.error(e.status, e.responseJSONe);
|
||||||
}
|
}
|
||||||
const ZipCode = data.address.Postal;
|
const ZipCode = data.address.Postal;
|
||||||
const City = data.address.City;
|
const { City } = data.address;
|
||||||
const State = states.getTwoDigitCode(data.address.Region);
|
const State = states.getTwoDigitCode(data.address.Region);
|
||||||
const Country = data.address.CountryCode;
|
const Country = data.address.CountryCode;
|
||||||
const TwcQuery = `${ZipCode}, ${City}, ${State}, ${Country}`;
|
const TwcQuery = `${ZipCode}, ${City}, ${State}, ${Country}`;
|
||||||
|
@ -519,29 +507,30 @@ const index = (() => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const populateWeatherParameters = (weatherParameters) => {
|
const populateWeatherParameters = (weatherParameters) => {
|
||||||
|
document.getElementById('spanCity').innerHTML = `${weatherParameters.city}, `;
|
||||||
document.getElementById('spanCity').innerHTML = weatherParameters.city + ', ';
|
|
||||||
document.getElementById('spanState').innerHTML = weatherParameters.state;
|
document.getElementById('spanState').innerHTML = weatherParameters.state;
|
||||||
document.getElementById('spanStationId').innerHTML = weatherParameters.stationId;
|
document.getElementById('spanStationId').innerHTML = weatherParameters.stationId;
|
||||||
document.getElementById('spanRadarId').innerHTML = weatherParameters.radarId;
|
document.getElementById('spanRadarId').innerHTML = weatherParameters.radarId;
|
||||||
document.getElementById('spanZoneId').innerHTML = weatherParameters.zoneId;
|
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;
|
let wakeLock = false;
|
||||||
const noSleepEnable = () => {
|
const noSleep = (enable = false) => {
|
||||||
_NoSleep.enable();
|
// get a nosleep controller
|
||||||
wakeLock = true;
|
if (!noSleep.controller) noSleep.controller = new NoSleep();
|
||||||
};
|
// don't call anything if the states match
|
||||||
const noSleepDisable = () => {
|
if (wakeLock === enable) return false;
|
||||||
if (!wakeLock) return;
|
// store the value
|
||||||
_NoSleep.disable();
|
wakeLock = enable;
|
||||||
wakeLock = false;
|
// call the function
|
||||||
|
if (enable) return noSleep.controller.enable();
|
||||||
|
return noSleep.controller.disable();
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init,
|
init,
|
||||||
message,
|
message,
|
||||||
};
|
};
|
||||||
|
|
||||||
})();
|
})();
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
class Almanac extends WeatherDisplay {
|
class Almanac extends WeatherDisplay {
|
||||||
constructor(navId,elemId) {
|
constructor(navId, elemId) {
|
||||||
super(navId,elemId,'Almanac');
|
super(navId, elemId, 'Almanac');
|
||||||
|
|
||||||
// pre-load background images (returns promises)
|
// pre-load background images (returns promises)
|
||||||
this.backgroundImage0 = utils.image.load('images/BackGround3_1.png');
|
this.backgroundImage0 = utils.image.load('images/BackGround3_1.png');
|
||||||
|
@ -20,12 +20,11 @@ class Almanac extends WeatherDisplay {
|
||||||
];
|
];
|
||||||
|
|
||||||
this.timing.totalScreens = 2;
|
this.timing.totalScreens = 2;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getData(weatherParameters) {
|
async getData(_weatherParameters) {
|
||||||
super.getData(weatherParameters);
|
super.getData(_weatherParameters);
|
||||||
if (!weatherParameters) weatherParameters = this.weatherParameters;
|
const weatherParameters = _weatherParameters ?? this.weatherParameters;
|
||||||
|
|
||||||
// get images for outlook
|
// get images for outlook
|
||||||
const imagePromises = [
|
const imagePromises = [
|
||||||
|
@ -34,15 +33,15 @@ class Almanac extends WeatherDisplay {
|
||||||
];
|
];
|
||||||
|
|
||||||
// get sun/moon data
|
// get sun/moon data
|
||||||
const {sun, moon} = this.calcSunMoonData(weatherParameters);
|
const { sun, moon } = this.calcSunMoonData(weatherParameters);
|
||||||
|
|
||||||
// process images for outlook
|
// process images for outlook
|
||||||
const [outlookTemp, outlookPrecip] = await Promise.all(imagePromises);
|
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
|
// store the data
|
||||||
this.data = {
|
this.data = {
|
||||||
sun,
|
sun,
|
||||||
moon,
|
moon,
|
||||||
outlook,
|
outlook,
|
||||||
|
@ -52,28 +51,27 @@ class Almanac extends WeatherDisplay {
|
||||||
|
|
||||||
// share data
|
// share data
|
||||||
this.getDataCallback();
|
this.getDataCallback();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
calcSunMoonData(weatherParameters) {
|
calcSunMoonData(weatherParameters) {
|
||||||
const {DateTime} = luxon;
|
const { DateTime } = luxon;
|
||||||
|
|
||||||
const sun = [
|
const sun = [
|
||||||
SunCalc.getTimes(new Date(), weatherParameters.latitude, weatherParameters.longitude),
|
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
|
// brute force the moon phases by scanning the next 30 days
|
||||||
const moon = [];
|
const moon = [];
|
||||||
// start with yesterday
|
// start with yesterday
|
||||||
let moonDate = DateTime.local().minus({days:1});
|
let moonDate = DateTime.local().minus({ days: 1 });
|
||||||
let phase = SunCalc.getMoonIllumination(moonDate.toJSDate()).phase;
|
let { phase } = SunCalc.getMoonIllumination(moonDate.toJSDate());
|
||||||
let iterations = 0;
|
let iterations = 0;
|
||||||
do {
|
do {
|
||||||
// get yesterday's moon info
|
// get yesterday's moon info
|
||||||
const lastPhase = phase;
|
const lastPhase = phase;
|
||||||
// calculate new values
|
// calculate new values
|
||||||
moonDate = moonDate.plus({days:1});
|
moonDate = moonDate.plus({ days: 1 });
|
||||||
phase = SunCalc.getMoonIllumination(moonDate.toJSDate()).phase;
|
phase = SunCalc.getMoonIllumination(moonDate.toJSDate()).phase;
|
||||||
// check for 4 cases
|
// check for 4 cases
|
||||||
if (lastPhase < 0.25 && phase >= 0.25) moon.push(this.getMoonTransition(0.25, 'First', moonDate));
|
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));
|
if (lastPhase > phase) moon.push(this.getMoonTransition(0.00, 'New', moonDate));
|
||||||
|
|
||||||
// stop after 30 days or 4 moon phases
|
// stop after 30 days or 4 moon phases
|
||||||
iterations++;
|
iterations += 1;
|
||||||
} while (iterations <= 30 && moon.length < 4);
|
} while (iterations <= 30 && moon.length < 4);
|
||||||
|
|
||||||
return {
|
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
|
// get moon transition from one phase to the next by drilling down by hours, minutes and seconds
|
||||||
getMoonTransition(threshold, phaseName, start, iteration = 0) {
|
getMoonTransition(threshold, phaseName, start, iteration = 0) {
|
||||||
let moonDate = start;
|
let moonDate = start;
|
||||||
let phase = SunCalc.getMoonIllumination(moonDate.toJSDate()).phase;
|
let { phase } = SunCalc.getMoonIllumination(moonDate.toJSDate());
|
||||||
let iterations = 0;
|
let iterations = 0;
|
||||||
const step = {
|
const step = {
|
||||||
hours: iteration === 0 ? -1:0,
|
hours: iteration === 0 ? -1 : 0,
|
||||||
minutes: iteration === 1 ? 1:0,
|
minutes: iteration === 1 ? 1 : 0,
|
||||||
seconds: iteration === 2 ? -1:0,
|
seconds: iteration === 2 ? -1 : 0,
|
||||||
milliseconds: iteration === 3 ? 1:0,
|
milliseconds: iteration === 3 ? 1 : 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
// increasing test
|
// increasing test
|
||||||
let test = (lastPhase,phase,threshold) => lastPhase < threshold && phase >= threshold;
|
let test = (lastPhase, testPhase) => lastPhase < threshold && testPhase >= threshold;
|
||||||
// decreasing test
|
// 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 {
|
do {
|
||||||
// store last phase
|
// store last phase
|
||||||
|
@ -117,42 +115,42 @@ class Almanac extends WeatherDisplay {
|
||||||
// wrap phases > 0.9 to -0.1 for ease of detection
|
// wrap phases > 0.9 to -0.1 for ease of detection
|
||||||
if (phase > 0.9) phase -= 1.0;
|
if (phase > 0.9) phase -= 1.0;
|
||||||
// compare
|
// compare
|
||||||
if (test(lastPhase, phase, threshold)) {
|
if (test(lastPhase, phase)) {
|
||||||
// last iteration is three, return value
|
// last iteration is three, return value
|
||||||
if (iteration >= 3) break;
|
if (iteration >= 3) break;
|
||||||
// iterate recursively
|
// iterate recursively
|
||||||
return this.getMoonTransition(threshold, phaseName, moonDate, iteration+1);
|
return this.getMoonTransition(threshold, phaseName, moonDate, iteration + 1);
|
||||||
}
|
}
|
||||||
iterations++;
|
iterations += 1;
|
||||||
} while (iterations < 1000);
|
} while (iterations < 1000);
|
||||||
|
|
||||||
return {phase: phaseName, date: moonDate};
|
return { phase: phaseName, date: moonDate };
|
||||||
}
|
}
|
||||||
|
|
||||||
// use the color of the pixel to determine the outlook
|
// use the color of the pixel to determine the outlook
|
||||||
parseOutlooks(lat, lon, temp, precip) {
|
static parseOutlooks(lat, lon, temp, precip) {
|
||||||
const {DateTime} = luxon;
|
const { DateTime } = luxon;
|
||||||
const month = DateTime.local();
|
const month = DateTime.local();
|
||||||
const thisMonth = month.toLocaleString({month: 'short'});
|
const thisMonth = month.toLocaleString({ month: 'short' });
|
||||||
const nextMonth = month.plus({months: 1}).toLocaleString({month: 'short'});
|
const nextMonth = month.plus({ months: 1 }).toLocaleString({ month: 'short' });
|
||||||
|
|
||||||
// draw the images on the canvases
|
// draw the images on the canvases
|
||||||
const tempContext = utils.image.drawLocalCanvas(temp);
|
const tempContext = utils.image.drawLocalCanvas(temp);
|
||||||
const precipContext = utils.image.drawLocalCanvas(precip);
|
const precipContext = utils.image.drawLocalCanvas(precip);
|
||||||
|
|
||||||
// get the color from each canvas
|
// get the color from each canvas
|
||||||
const tempColor = this.getOutlookColor(lat, lon, tempContext);
|
const tempColor = Almanac.getOutlookColor(lat, lon, tempContext);
|
||||||
const precipColor = this.getOutlookColor(lat, lon, precipContext);
|
const precipColor = Almanac.getOutlookColor(lat, lon, precipContext);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
thisMonth,
|
thisMonth,
|
||||||
nextMonth,
|
nextMonth,
|
||||||
temperature: this.getOutlookTemperatureIndicator(tempColor),
|
temperature: Almanac.getOutlookTemperatureIndicator(tempColor),
|
||||||
precipitation: this.getOutlookPrecipitationIndicator(precipColor),
|
precipitation: Almanac.getOutlookPrecipitationIndicator(precipColor),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getOutlookColor (lat, lon, context) {
|
static getOutlookColor(lat, lon, context) {
|
||||||
let x = 0;
|
let x = 0;
|
||||||
let y = 0;
|
let y = 0;
|
||||||
|
|
||||||
|
@ -195,11 +193,11 @@ class Almanac extends WeatherDisplay {
|
||||||
|
|
||||||
// Determine if there is any "non-white" colors around the area.
|
// Determine if there is any "non-white" colors around the area.
|
||||||
// Search a 16x16 region.
|
// Search a 16x16 region.
|
||||||
for (let colorX = x - 8; colorX <= x + 8; colorX++) {
|
for (let colorX = x - 8; colorX <= x + 8; colorX += 1) {
|
||||||
for (let colorY = y - 8; colorY <= y + 8; colorY++) {
|
for (let colorY = y - 8; colorY <= y + 8; colorY += 1) {
|
||||||
const pixelColor = this.getPixelColor(context, colorX, colorY);
|
const pixelColor = Almanac.getPixelColor(context, colorX, colorY);
|
||||||
if ((pixelColor.r !== 0 && pixelColor.g !== 0 && pixelColor.b !== 0) ||
|
if ((pixelColor.r !== 0 && pixelColor.g !== 0 && pixelColor.b !== 0)
|
||||||
(pixelColor.r !== 255 && pixelColor.g !== 255 && pixelColor.b !== 255)) {
|
|| (pixelColor.r !== 255 && pixelColor.g !== 255 && pixelColor.b !== 255)) {
|
||||||
return pixelColor;
|
return pixelColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -209,7 +207,7 @@ class Almanac extends WeatherDisplay {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get rgb values of a pixel
|
// get rgb values of a pixel
|
||||||
getPixelColor (context, x, y) {
|
static getPixelColor(context, x, y) {
|
||||||
const pixelData = context.getImageData(x, y, 1, 1).data;
|
const pixelData = context.getImageData(x, y, 1, 1).data;
|
||||||
return {
|
return {
|
||||||
r: pixelData[0],
|
r: pixelData[0],
|
||||||
|
@ -219,33 +217,31 @@ class Almanac extends WeatherDisplay {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get temperature outlook from color
|
// get temperature outlook from color
|
||||||
getOutlookTemperatureIndicator(pixelColor) {
|
static getOutlookTemperatureIndicator(pixelColor) {
|
||||||
if (pixelColor.b > pixelColor.r) {
|
if (pixelColor.b > pixelColor.r) {
|
||||||
return 'Below Normal';
|
return 'Below Normal';
|
||||||
} else if (pixelColor.r > pixelColor.b) {
|
} if (pixelColor.r > pixelColor.b) {
|
||||||
return 'Above Normal';
|
return 'Above Normal';
|
||||||
} else {
|
|
||||||
return 'Normal';
|
|
||||||
}
|
}
|
||||||
|
return 'Normal';
|
||||||
}
|
}
|
||||||
|
|
||||||
// get precipitation outlook from color
|
// get precipitation outlook from color
|
||||||
getOutlookPrecipitationIndicator (pixelColor) {
|
static getOutlookPrecipitationIndicator(pixelColor) {
|
||||||
if (pixelColor.g > pixelColor.r) {
|
if (pixelColor.g > pixelColor.r) {
|
||||||
return 'Above Normal';
|
return 'Above Normal';
|
||||||
} else if (pixelColor.r > pixelColor.g) {
|
} if (pixelColor.r > pixelColor.g) {
|
||||||
return 'Below Normal';
|
return 'Below Normal';
|
||||||
} else {
|
|
||||||
return 'Normal';
|
|
||||||
}
|
}
|
||||||
|
return 'Normal';
|
||||||
}
|
}
|
||||||
|
|
||||||
async drawCanvas() {
|
async drawCanvas() {
|
||||||
super.drawCanvas();
|
super.drawCanvas();
|
||||||
const info = this.data;
|
const info = this.data;
|
||||||
const {DateTime} = luxon;
|
const { DateTime } = luxon;
|
||||||
const Today = DateTime.local();
|
const Today = DateTime.local();
|
||||||
const Tomorrow = Today.plus({days: 1});
|
const Tomorrow = Today.plus({ days: 1 });
|
||||||
|
|
||||||
// extract moon images
|
// extract moon images
|
||||||
const [FullMoonImage, LastMoonImage, NewMoonImage, FirstMoonImage] = await Promise.all(this.moonImages);
|
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.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', 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', 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', 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);
|
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);
|
draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 70, 220, 'Moon Data:', 2);
|
||||||
|
|
||||||
|
|
||||||
info.moon.forEach((MoonPhase, Index) => {
|
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, 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, 390, date, 2, 'center');
|
||||||
|
|
||||||
const image = (() => {
|
const image = (() => {
|
||||||
switch (MoonPhase.phase) {
|
switch (MoonPhase.phase) {
|
||||||
|
@ -294,11 +289,11 @@ class Almanac extends WeatherDisplay {
|
||||||
return FirstMoonImage;
|
return FirstMoonImage;
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
this.context.drawImage(image, 75+Index*130, 270);
|
this.context.drawImage(image, 75 + Index * 130, 270);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1:
|
case 1: {
|
||||||
this.context.drawImage(await this.backgroundImage1, 0, 0);
|
this.context.drawImage(await this.backgroundImage1, 0, 0);
|
||||||
draw.horizontalGradientSingle(this.context, 0, 30, 500, 90, draw.topColor1, draw.topColor2);
|
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.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');
|
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');
|
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 320, 220, DateRange, 2, 'center');
|
||||||
|
|
||||||
var Temperature = info.outlook.temperature;
|
const Temperature = info.outlook.temperature;
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 70, 300, 'Temperatures: ' + Temperature, 2);
|
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 70, 300, `Temperatures: ${Temperature}`, 2);
|
||||||
|
|
||||||
var Precipitation = info.outlook.precipitation;
|
const Precipitation = info.outlook.precipitation;
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 70, 380, 'Precipitation: ' + Precipitation, 2);
|
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 70, 380, `Precipitation: ${Precipitation}`, 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.finishDraw();
|
this.finishDraw();
|
||||||
|
|
|
@ -3,27 +3,28 @@
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
class CurrentWeather extends WeatherDisplay {
|
class CurrentWeather extends WeatherDisplay {
|
||||||
constructor(navId,elemId) {
|
constructor(navId, elemId) {
|
||||||
super(navId,elemId,'Current Conditions');
|
super(navId, elemId, 'Current Conditions');
|
||||||
// pre-load background image (returns promise)
|
// pre-load background image (returns promise)
|
||||||
this.backgroundImage = utils.image.load('images/BackGround1_1.png');
|
this.backgroundImage = utils.image.load('images/BackGround1_1.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
async getData(weatherParameters) {
|
async getData(_weatherParameters) {
|
||||||
super.getData(weatherParameters);
|
super.getData(_weatherParameters);
|
||||||
if (!weatherParameters) weatherParameters = this.weatherParameters;
|
const weatherParameters = _weatherParameters ?? this.weatherParameters;
|
||||||
|
|
||||||
// Load the observations
|
// Load the observations
|
||||||
let observations, station;
|
let observations; let
|
||||||
|
station;
|
||||||
// station number counter
|
// station number counter
|
||||||
let stationNum = 0;
|
let stationNum = 0;
|
||||||
while (!observations && stationNum < weatherParameters.stations.length) {
|
while (!observations && stationNum < weatherParameters.stations.length) {
|
||||||
// get the station
|
// get the station
|
||||||
station = weatherParameters.stations[stationNum];
|
station = weatherParameters.stations[stationNum];
|
||||||
stationNum++;
|
stationNum += 1;
|
||||||
try {
|
try {
|
||||||
// station observations
|
// station observations
|
||||||
observations = await utils.fetch.json(`${station.id}/observations`,{
|
observations = await utils.fetch.json(`${station.id}/observations`, {
|
||||||
cors: true,
|
cors: true,
|
||||||
data: {
|
data: {
|
||||||
limit: 2,
|
limit: 2,
|
||||||
|
@ -31,9 +32,9 @@ class CurrentWeather extends WeatherDisplay {
|
||||||
});
|
});
|
||||||
|
|
||||||
// test data quality
|
// test data quality
|
||||||
if (observations.features[0].properties.temperature.value === null ||
|
if (observations.features[0].properties.temperature.value === null
|
||||||
observations.features[0].properties.windSpeed.value === null ||
|
|| observations.features[0].properties.windSpeed.value === null
|
||||||
observations.features[0].properties.textDescription === null) {
|
|| observations.features[0].properties.textDescription === null) {
|
||||||
observations = undefined;
|
observations = undefined;
|
||||||
throw new Error(`Unable to get observations: ${station.properties.stationIdentifier}, trying next station`);
|
throw new Error(`Unable to get observations: ${station.properties.stationIdentifier}, trying next station`);
|
||||||
}
|
}
|
||||||
|
@ -53,7 +54,7 @@ class CurrentWeather extends WeatherDisplay {
|
||||||
utils.image.preload(icons.getWeatherIconFromIconLink(observations.features[0].properties.icon));
|
utils.image.preload(icons.getWeatherIconFromIconLink(observations.features[0].properties.icon));
|
||||||
|
|
||||||
// we only get here if there was no error above
|
// 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.setStatus(STATUS.loaded);
|
||||||
|
|
||||||
this.getDataCallback();
|
this.getDataCallback();
|
||||||
|
@ -71,7 +72,7 @@ class CurrentWeather extends WeatherDisplay {
|
||||||
data.DewPoint = Math.round(observations.dewpoint.value);
|
data.DewPoint = Math.round(observations.dewpoint.value);
|
||||||
data.Ceiling = Math.round(observations.cloudLayers[0].base.value);
|
data.Ceiling = Math.round(observations.cloudLayers[0].base.value);
|
||||||
data.CeilingUnit = 'm.';
|
data.CeilingUnit = 'm.';
|
||||||
data.Visibility = Math.round(observations.visibility.value/1000);
|
data.Visibility = Math.round(observations.visibility.value / 1000);
|
||||||
data.VisibilityUnit = ' km.';
|
data.VisibilityUnit = ' km.';
|
||||||
data.WindSpeed = Math.round(observations.windSpeed.value);
|
data.WindSpeed = Math.round(observations.windSpeed.value);
|
||||||
data.WindDirection = utils.calc.directionToNSEW(observations.windDirection.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.Temperature = utils.units.celsiusToFahrenheit(data.Temperature);
|
||||||
data.TemperatureUnit = 'F';
|
data.TemperatureUnit = 'F';
|
||||||
data.DewPoint = utils.units.celsiusToFahrenheit(data.DewPoint);
|
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.CeilingUnit = 'ft.';
|
||||||
data.Visibility = utils.units.kilometersToMiles(observations.visibility.value/1000);
|
data.Visibility = utils.units.kilometersToMiles(observations.visibility.value / 1000);
|
||||||
data.VisibilityUnit = ' mi.';
|
data.VisibilityUnit = ' mi.';
|
||||||
data.WindSpeed = utils.units.kphToMph(data.WindSpeed);
|
data.WindSpeed = utils.units.kphToMph(data.WindSpeed);
|
||||||
data.WindUnit = 'MPH';
|
data.WindUnit = 'MPH';
|
||||||
|
@ -109,7 +110,7 @@ class CurrentWeather extends WeatherDisplay {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async drawCanvas () {
|
async drawCanvas() {
|
||||||
super.drawCanvas();
|
super.drawCanvas();
|
||||||
// parse each time to deal with a change in units if necessary
|
// parse each time to deal with a change in units if necessary
|
||||||
const data = this.parseData();
|
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', 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', 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', '#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', 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', 340, 205, 'Dewpoint:', 2);
|
||||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 560, 205, data.DewPoint + String.fromCharCode(176), 2, 'right');
|
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(/Light/g, 'L');
|
||||||
condition = condition.replace(/Heavy/g, 'H');
|
condition = condition.replace(/Heavy/g, 'H');
|
||||||
condition = condition.replace(/Partly/g, 'P');
|
condition = condition.replace(/Partly/g, 'P');
|
||||||
|
@ -230,5 +232,4 @@ class CurrentWeather extends WeatherDisplay {
|
||||||
condition = condition.replace(/ with /g, '/');
|
condition = condition.replace(/ with /g, '/');
|
||||||
return condition;
|
return condition;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,5 +1,3 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/* globals draw, navigation */
|
/* globals draw, navigation */
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
@ -10,7 +8,6 @@ const currentWeatherScroll = (() => {
|
||||||
// local variables
|
// local variables
|
||||||
let context; // currently active context
|
let context; // currently active context
|
||||||
let blankDrawArea; // original state of context
|
let blankDrawArea; // original state of context
|
||||||
let station;
|
|
||||||
let interval;
|
let interval;
|
||||||
let screenIndex = 0;
|
let screenIndex = 0;
|
||||||
|
|
||||||
|
@ -36,7 +33,6 @@ const currentWeatherScroll = (() => {
|
||||||
|
|
||||||
// draw the data
|
// draw the data
|
||||||
drawScreen();
|
drawScreen();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const stop = (reset) => {
|
const stop = (reset) => {
|
||||||
|
@ -53,7 +49,7 @@ const currentWeatherScroll = (() => {
|
||||||
|
|
||||||
// increment interval, roll over
|
// increment interval, roll over
|
||||||
const incrementInterval = () => {
|
const incrementInterval = () => {
|
||||||
screenIndex = (screenIndex+1)%(screens.length);
|
screenIndex = (screenIndex + 1) % (screens.length);
|
||||||
// draw new text
|
// draw new text
|
||||||
drawScreen();
|
drawScreen();
|
||||||
};
|
};
|
||||||
|
@ -74,7 +70,7 @@ const currentWeatherScroll = (() => {
|
||||||
// the "screens" are stored in an array for easy addition and removal
|
// the "screens" are stored in an array for easy addition and removal
|
||||||
const screens = [
|
const screens = [
|
||||||
// station name
|
// station name
|
||||||
(data) => `Conditions at ${data.station.properties.name.substr(0,20)}`,
|
(data) => `Conditions at ${data.station.properties.name.substr(0, 20)}`,
|
||||||
|
|
||||||
// temperature
|
// temperature
|
||||||
(data) => {
|
(data) => {
|
||||||
|
@ -108,7 +104,7 @@ const currentWeatherScroll = (() => {
|
||||||
},
|
},
|
||||||
|
|
||||||
// visibility
|
// 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
|
// internal draw function with preset parameters
|
||||||
|
|
|
@ -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.textAlign = align;
|
||||||
context.font = size + ` '${font}'`;
|
context.font = `${size} '${font}'`;
|
||||||
context.shadowColor = '#000000';
|
context.shadowColor = '#000000';
|
||||||
context.shadowOffsetX = shadow;
|
context.shadowOffsetX = shadow;
|
||||||
context.shadowOffsetY = shadow;
|
context.shadowOffsetY = shadow;
|
||||||
context.strokeStyle = '#000000';
|
context.strokeStyle = '#000000';
|
||||||
context.lineWidth = 2;
|
context.lineWidth = 2;
|
||||||
context.strokeText(text, x, y);
|
context.strokeText(myText, x, y);
|
||||||
context.fillStyle = color;
|
context.fillStyle = color;
|
||||||
context.fillText(text, x, y);
|
context.fillText(myText, x, y);
|
||||||
context.fillStyle = '';
|
context.fillStyle = '';
|
||||||
context.strokeStyle = '';
|
context.strokeStyle = '';
|
||||||
context.shadowOffsetX = 0;
|
context.shadowOffsetX = 0;
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
class ExtendedForecast extends WeatherDisplay {
|
class ExtendedForecast extends WeatherDisplay {
|
||||||
constructor(navId,elemId) {
|
constructor(navId, elemId) {
|
||||||
super(navId,elemId,'Extended Forecast');
|
super(navId, elemId, 'Extended Forecast');
|
||||||
|
|
||||||
// set timings
|
// set timings
|
||||||
this.timing.totalScreens = 2;
|
this.timing.totalScreens = 2;
|
||||||
|
@ -15,17 +15,16 @@ class ExtendedForecast extends WeatherDisplay {
|
||||||
this.backgroundImage = utils.image.load('images/BackGround2_1.png');
|
this.backgroundImage = utils.image.load('images/BackGround2_1.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
async getData(weatherParameters) {
|
async getData(_weatherParameters) {
|
||||||
super.getData(weatherParameters);
|
super.getData(_weatherParameters);
|
||||||
if (!weatherParameters) weatherParameters = this.weatherParameters;
|
const weatherParameters = _weatherParameters ?? this.weatherParameters;
|
||||||
|
|
||||||
|
|
||||||
// request us or si units
|
// request us or si units
|
||||||
let units = 'us';
|
let units = 'us';
|
||||||
if (navigation.units() === UNITS.metric) units = 'si';
|
if (navigation.units() === UNITS.metric) units = 'si';
|
||||||
let forecast;
|
let forecast;
|
||||||
try {
|
try {
|
||||||
forecast = await utils.fetch.json(weatherParameters.forecast,{
|
forecast = await utils.fetch.json(weatherParameters.forecast, {
|
||||||
data: {
|
data: {
|
||||||
units,
|
units,
|
||||||
},
|
},
|
||||||
|
@ -37,32 +36,36 @@ class ExtendedForecast extends WeatherDisplay {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// we only get here if there was no error above
|
// 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.screenIndex = 0;
|
||||||
this.setStatus(STATUS.loaded);
|
this.setStatus(STATUS.loaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
// the api provides the forecast in 12 hour increments, flatten to day increments with high and low temperatures
|
// 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
|
// 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 dates = Days.map((shift) => {
|
||||||
const date = luxon.DateTime.local().startOf('day').plus({days:shift});
|
const date = luxon.DateTime.local().startOf('day').plus({ days: shift });
|
||||||
return date.toLocaleString({weekday: 'short'});
|
return date.toLocaleString({ weekday: 'short' });
|
||||||
});
|
});
|
||||||
|
|
||||||
// track the destination forecast index
|
// track the destination forecast index
|
||||||
let destIndex = 0;
|
let destIndex = 0;
|
||||||
const forecast = [];
|
const forecast = [];
|
||||||
fullForecast.forEach(period => {
|
fullForecast.forEach((period) => {
|
||||||
// create the destination object if necessary
|
// 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
|
// get the object to modify/populate
|
||||||
const fDay = forecast[destIndex];
|
const fDay = forecast[destIndex];
|
||||||
// high temperature will always be last in the source array so it will overwrite the low values assigned below
|
// 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.icon = icons.getWeatherIconFromIconLink(period.icon);
|
||||||
fDay.text = this.shortenExtendedForecastText(period.shortForecast);
|
fDay.text = ExtendedForecast.shortenExtendedForecastText(period.shortForecast);
|
||||||
fDay.dayName = dates[destIndex];
|
fDay.dayName = dates[destIndex];
|
||||||
|
|
||||||
// preload the icon
|
// preload the icon
|
||||||
|
@ -71,7 +74,7 @@ class ExtendedForecast extends WeatherDisplay {
|
||||||
if (period.isDaytime) {
|
if (period.isDaytime) {
|
||||||
// day time is the high temperature
|
// day time is the high temperature
|
||||||
fDay.high = period.temperature;
|
fDay.high = period.temperature;
|
||||||
destIndex++;
|
destIndex += 1;
|
||||||
} else {
|
} else {
|
||||||
// low temperature
|
// low temperature
|
||||||
fDay.low = period.temperature;
|
fDay.low = period.temperature;
|
||||||
|
@ -81,7 +84,7 @@ class ExtendedForecast extends WeatherDisplay {
|
||||||
return forecast;
|
return forecast;
|
||||||
}
|
}
|
||||||
|
|
||||||
shortenExtendedForecastText(long) {
|
static shortenExtendedForecastText(long) {
|
||||||
let short = long;
|
let short = long;
|
||||||
short = short.replace(/ and /g, ' ');
|
short = short.replace(/ and /g, ' ');
|
||||||
short = short.replace(/Slight /g, '');
|
short = short.replace(/Slight /g, '');
|
||||||
|
@ -112,7 +115,7 @@ class ExtendedForecast extends WeatherDisplay {
|
||||||
}
|
}
|
||||||
short = short1;
|
short = short1;
|
||||||
if (short2 !== '') {
|
if (short2 !== '') {
|
||||||
short += ' ' + short2;
|
short += ` ${short2}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [short, short1, short2];
|
return [short, short1, short2];
|
||||||
|
@ -123,7 +126,7 @@ class ExtendedForecast extends WeatherDisplay {
|
||||||
|
|
||||||
// determine bounds
|
// determine bounds
|
||||||
// grab the first three or second set of three array elements
|
// 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;
|
const backgroundImage = await this.backgroundImage;
|
||||||
|
|
||||||
|
@ -138,27 +141,27 @@ class ExtendedForecast extends WeatherDisplay {
|
||||||
draw.titleText(this.context, 'Extended', 'Forecast');
|
draw.titleText(this.context, 'Extended', 'Forecast');
|
||||||
|
|
||||||
await Promise.all(forecast.map(async (Day, Index) => {
|
await Promise.all(forecast.map(async (Day, Index) => {
|
||||||
const offset = Index*195;
|
const offset = Index * 195;
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 100+offset, 135, Day.dayName.toUpperCase(), 2);
|
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', '#8080FF', 85 + offset, 345, 'Lo', 2, 'center');
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 165+offset, 345, 'Hi', 2, 'center');
|
draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 165 + offset, 345, 'Hi', 2, 'center');
|
||||||
let low = Day.low;
|
let { low } = Day;
|
||||||
if (low !== undefined) {
|
if (low !== undefined) {
|
||||||
if (navigation.units() === UNITS.metric) low = utils.units.rahrenheitToCelsius(low);
|
if (navigation.units() === UNITS.metric) low = utils.units.fahrenheitToCelsius(low);
|
||||||
draw.text(this.context, 'Star4000 Large', '24pt', '#FFFFFF', 85+offset, 385, low, 2, 'center');
|
draw.text(this.context, 'Star4000 Large', '24pt', '#FFFFFF', 85 + offset, 385, low, 2, 'center');
|
||||||
}
|
}
|
||||||
let high = Day.high;
|
let { high } = Day;
|
||||||
if (navigation.units() === UNITS.metric) high = utils.units.rahrenheitToCelsius(high);
|
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 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, 270, Day.text[1], 2, 'center');
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 120+offset, 310, Day.text[2], 2, 'center');
|
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 120 + offset, 310, Day.text[2], 2, 'center');
|
||||||
|
|
||||||
// draw the icon
|
// draw the icon
|
||||||
this.gifs.push(await utils.image.superGifAsync({
|
this.gifs.push(await utils.image.superGifAsync({
|
||||||
src: Day.icon,
|
src: Day.icon,
|
||||||
auto_play: true,
|
auto_play: true,
|
||||||
canvas: this.canvas,
|
canvas: this.canvas,
|
||||||
x: 70 + Index*195,
|
x: 70 + Index * 195,
|
||||||
y: 150,
|
y: 150,
|
||||||
max_height: 75,
|
max_height: 75,
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -16,10 +16,10 @@ class Hourly extends WeatherDisplay {
|
||||||
this.timing.baseDelay = 20;
|
this.timing.baseDelay = 20;
|
||||||
// 24 hours = 6 pages
|
// 24 hours = 6 pages
|
||||||
const pages = 4; // first page is already displayed, last page doesn't happen
|
const pages = 4; // first page is already displayed, last page doesn't happen
|
||||||
const timingStep = this.hourHeight*4;
|
const timingStep = this.hourHeight * 4;
|
||||||
this.timing.delay = [150+timingStep];
|
this.timing.delay = [150 + timingStep];
|
||||||
// add additional pages
|
// 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
|
// add the final 3 second delay
|
||||||
this.timing.delay.push(150);
|
this.timing.delay.push(150);
|
||||||
}
|
}
|
||||||
|
@ -37,34 +37,36 @@ class Hourly extends WeatherDisplay {
|
||||||
this.setStatus(STATUS.failed);
|
this.setStatus(STATUS.failed);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.data = await this.parseForecast(forecast.properties);
|
this.data = await Hourly.parseForecast(forecast.properties);
|
||||||
|
|
||||||
this.setStatus(STATUS.loaded);
|
this.setStatus(STATUS.loaded);
|
||||||
this.drawLongCanvas();
|
this.drawLongCanvas();
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract specific values from forecast and format as an array
|
// extract specific values from forecast and format as an array
|
||||||
async parseForecast(data) {
|
static async parseForecast(data) {
|
||||||
const temperature = this.expand(data.temperature.values);
|
const temperature = Hourly.expand(data.temperature.values);
|
||||||
const apparentTemperature = this.expand(data.apparentTemperature.values);
|
const apparentTemperature = Hourly.expand(data.apparentTemperature.values);
|
||||||
const windSpeed = this.expand(data.windSpeed.values);
|
const windSpeed = Hourly.expand(data.windSpeed.values);
|
||||||
const windDirection = this.expand(data.windDirection.values);
|
const windDirection = Hourly.expand(data.windDirection.values);
|
||||||
const skyCover = this.expand(data.skyCover.values); // cloud icon
|
const skyCover = Hourly.expand(data.skyCover.values); // cloud icon
|
||||||
const weather = this.expand(data.weather.values); // fog icon
|
const weather = Hourly.expand(data.weather.values); // fog icon
|
||||||
const iceAccumulation = this.expand(data.iceAccumulation.values); // ice icon
|
const iceAccumulation = Hourly.expand(data.iceAccumulation.values); // ice icon
|
||||||
const probabilityOfPrecipitation = this.expand(data.probabilityOfPrecipitation.values); // rain icon
|
const probabilityOfPrecipitation = Hourly.expand(data.probabilityOfPrecipitation.values); // rain icon
|
||||||
const snowfallAmount = this.expand(data.snowfallAmount.values); // snow 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) => {
|
return temperature.map((val, idx) => {
|
||||||
if (navigation.units === UNITS.metric) return {
|
if (navigation.units === UNITS.metric) {
|
||||||
temperature: temperature[idx],
|
return {
|
||||||
apparentTemperature: apparentTemperature[idx],
|
temperature: temperature[idx],
|
||||||
windSpeed: windSpeed[idx],
|
apparentTemperature: apparentTemperature[idx],
|
||||||
windDirection: utils.calc.directionToNSEW(windDirection[idx]),
|
windSpeed: windSpeed[idx],
|
||||||
icon: icons[idx],
|
windDirection: utils.calc.directionToNSEW(windDirection[idx]),
|
||||||
};
|
icon: icons[idx],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
temperature: utils.units.celsiusToFahrenheit(temperature[idx]),
|
temperature: utils.units.celsiusToFahrenheit(temperature[idx]),
|
||||||
|
@ -73,30 +75,29 @@ class Hourly extends WeatherDisplay {
|
||||||
windDirection: utils.calc.directionToNSEW(windDirection[idx]),
|
windDirection: utils.calc.directionToNSEW(windDirection[idx]),
|
||||||
icon: icons[idx],
|
icon: icons[idx],
|
||||||
};
|
};
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// given forecast paramaters determine a suitable icon
|
// 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 startOfHour = luxon.DateTime.local().startOf('hour');
|
||||||
const sunTimes = (await navigation.getSun()).sun;
|
const sunTimes = (await navigation.getSun()).sun;
|
||||||
const overnight = luxon.Interval.fromDateTimes(luxon.DateTime.fromJSDate(sunTimes[0].sunset), luxon.DateTime.fromJSDate(sunTimes[1].sunrise));
|
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);
|
const tomorrowOvernight = luxon.DateTime.fromJSDate(sunTimes[1].sunset);
|
||||||
return skyCover.map((val, idx) => {
|
return skyCover.map((val, idx) => {
|
||||||
const hour = startOfHour.plus({hours: idx});
|
const hour = startOfHour.plus({ hours: idx });
|
||||||
const isNight = overnight.contains(hour) || (hour > tomorrowOvernight);
|
const isNight = overnight.contains(hour) || (hour > tomorrowOvernight);
|
||||||
return icons.getHourlyIcon(skyCover[idx], weather[idx], iceAccumulation[idx], probabilityOfPrecipitation[idx], snowfallAmount[idx], windSpeed[idx], isNight);
|
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 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 startOfHour = luxon.DateTime.utc().startOf('hour').toMillis();
|
||||||
const result = []; // resulting expanded values
|
const result = []; // resulting expanded values
|
||||||
data.forEach(item => {
|
data.forEach((item) => {
|
||||||
let startTime = Date.parse(item.validTime.substr(0, item.validTime.indexOf('/')));
|
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;
|
const endTime = startTime + duration;
|
||||||
// loop through duration at one hour intervals
|
// loop through duration at one hour intervals
|
||||||
do {
|
do {
|
||||||
|
@ -105,39 +106,39 @@ class Hourly extends WeatherDisplay {
|
||||||
result.push(item.value); // push data array
|
result.push(item.value); // push data array
|
||||||
} // timestamp is after now
|
} // timestamp is after now
|
||||||
// increment start time by 1 hour
|
// increment start time by 1 hour
|
||||||
startTime = startTime + 3600000;
|
startTime += 3600000;
|
||||||
} while (startTime < endTime && result.length < 24);
|
} while (startTime < endTime && result.length < 24);
|
||||||
}); // for each value
|
}); // for each value
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async drawLongCanvas () {
|
async drawLongCanvas() {
|
||||||
// create the "long" canvas if necessary
|
// create the "long" canvas if necessary
|
||||||
if (!this.longCanvas) {
|
if (!this.longCanvas) {
|
||||||
this.longCanvas = document.createElement('canvas');
|
this.longCanvas = document.createElement('canvas');
|
||||||
this.longCanvas.width = 640;
|
this.longCanvas.width = 640;
|
||||||
this.longCanvas.height = 24*this.hourHeight;
|
this.longCanvas.height = 24 * this.hourHeight;
|
||||||
this.longContext = this.longCanvas.getContext('2d');
|
this.longContext = this.longCanvas.getContext('2d');
|
||||||
this.longCanvasGifs = [];
|
this.longCanvasGifs = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// stop all gifs
|
// stop all gifs
|
||||||
this.longCanvasGifs.forEach(gif => gif.pause());
|
this.longCanvasGifs.forEach((gif) => gif.pause());
|
||||||
// delete the gifs
|
// delete the gifs
|
||||||
this.longCanvasGifs.length = 0;
|
this.longCanvasGifs.length = 0;
|
||||||
|
|
||||||
// clean up existing gifs
|
// clean up existing gifs
|
||||||
this.gifs.forEach(gif => gif.pause());
|
this.gifs.forEach((gif) => gif.pause());
|
||||||
// delete the gifs
|
// delete the gifs
|
||||||
this.gifs.length = 0;
|
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 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;
|
const y = i * 346;
|
||||||
draw.horizontalGradient(this.longContext, 0, y, 640, y + 346, '#102080', '#001040');
|
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) => {
|
await Promise.all(this.data.map(async (data, index) => {
|
||||||
// calculate base y value
|
// calculate base y value
|
||||||
const y = 50+this.hourHeight*index;
|
const y = 50 + this.hourHeight * index;
|
||||||
|
|
||||||
// hour
|
// hour
|
||||||
const hour = startingHour.plus({hours: index});
|
const hour = startingHour.plus({ hours: index });
|
||||||
const formattedHour = hour.toLocaleString({weekday: 'short', hour: 'numeric'});
|
const formattedHour = hour.toLocaleString({ weekday: 'short', hour: 'numeric' });
|
||||||
draw.text(this.longContext, 'Star4000 Large Compressed', '24pt', '#FFFF00', 80, y, formattedHour, 2);
|
draw.text(this.longContext, 'Star4000 Large Compressed', '24pt', '#FFFF00', 80, y, formattedHour, 2);
|
||||||
|
|
||||||
|
|
||||||
// temperatures, convert to strings with no decimal
|
// temperatures, convert to strings with no decimal
|
||||||
const temperature = Math.round(data.temperature).toString().padStart(3);
|
const temperature = Math.round(data.temperature).toString().padStart(3);
|
||||||
const feelsLike = Math.round(data.apparentTemperature).toString().padStart(3);
|
const feelsLike = Math.round(data.apparentTemperature).toString().padStart(3);
|
||||||
|
@ -177,8 +177,6 @@ class Hourly extends WeatherDisplay {
|
||||||
y: y - 35,
|
y: y - 35,
|
||||||
max_width: 47,
|
max_width: 47,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,7 +219,7 @@ class Hourly extends WeatherDisplay {
|
||||||
const longCanvas = this.getLongCanvas();
|
const longCanvas = this.getLongCanvas();
|
||||||
|
|
||||||
// calculate scroll offset and don't go past end
|
// 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
|
// don't let offset go negative
|
||||||
if (offsetY < 0) offsetY = 0;
|
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);
|
this.context.drawImage(longCanvas, 0, offsetY, 640, 289, 0, 110, 640, 289);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTravelCitiesDayName(cities) {
|
static getTravelCitiesDayName(cities) {
|
||||||
const {DateTime} = luxon;
|
const { DateTime } = luxon;
|
||||||
// effectively returns early on the first found date
|
// effectively returns early on the first found date
|
||||||
return cities.reduce((dayName, city) => {
|
return cities.reduce((dayName, city) => {
|
||||||
if (city && dayName === '') {
|
if (city && dayName === '') {
|
||||||
// today or tomorrow
|
// 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 the day
|
||||||
return day.toLocaleString({weekday: 'long'});
|
return day.toLocaleString({ weekday: 'long' });
|
||||||
}
|
}
|
||||||
return dayName;
|
return dayName;
|
||||||
}, '');
|
}, '');
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
'use strict';
|
|
||||||
/* spell-checker: disable */
|
/* spell-checker: disable */
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
const icons = (() => {
|
const icons = (() => {
|
||||||
|
const getWeatherRegionalIconFromIconLink = (link, _isNightTime) => {
|
||||||
const getWeatherRegionalIconFromIconLink = (link, isNightTime) => {
|
|
||||||
// extract day or night if not provided
|
// 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
|
// internal function to add path to returned icon
|
||||||
const addPath = (icon) => `images/r/${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 a 'DualImage' is captured, adjust to just the j parameter
|
||||||
if (conditionName === 'dualimage') {
|
if (conditionName === 'dualimage') {
|
||||||
const match = link.match(/&j=(.*)&/);
|
const match = link.match(/&j=(.*)&/);
|
||||||
conditionName = match[1];
|
[, conditionName] = match;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// find the icon
|
// find the icon
|
||||||
switch (conditionName + (isNightTime?'-n':'')) {
|
switch (conditionName + (isNightTime ? '-n' : '')) {
|
||||||
case 'skc':
|
case 'skc':
|
||||||
case 'hot':
|
case 'hot':
|
||||||
case 'haze':
|
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
|
// internal function to add path to returned icon
|
||||||
const addPath = (icon) => `images/${icon}`;
|
const addPath = (icon) => `images/${icon}`;
|
||||||
// extract day or night if not provided
|
// 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: ?&,
|
// grab everything after the last slash ending at any of these: ?&,
|
||||||
const afterLastSlash = link.toLowerCase().match(/[^/]+$/)[0];
|
const afterLastSlash = link.toLowerCase().match(/[^/]+$/)[0];
|
||||||
|
@ -145,12 +142,11 @@ const icons = (() => {
|
||||||
// if a 'DualImage' is captured, adjust to just the j parameter
|
// if a 'DualImage' is captured, adjust to just the j parameter
|
||||||
if (conditionName === 'dualimage') {
|
if (conditionName === 'dualimage') {
|
||||||
const match = link.match(/&j=(.*)&/);
|
const match = link.match(/&j=(.*)&/);
|
||||||
conditionName = match[1];
|
[, conditionName] = match;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// find the icon
|
// find the icon
|
||||||
switch (conditionName + (isNightTime?'-n':'')) {
|
switch (conditionName + (isNightTime ? '-n' : '')) {
|
||||||
case 'skc':
|
case 'skc':
|
||||||
case 'hot':
|
case 'hot':
|
||||||
case 'haze':
|
case 'haze':
|
||||||
|
@ -262,12 +258,11 @@ const icons = (() => {
|
||||||
let thunder = false;
|
let thunder = false;
|
||||||
let snow = false;
|
let snow = false;
|
||||||
let ice = false;
|
let ice = false;
|
||||||
let fog = false;
|
let fog = false;
|
||||||
let wind = false;
|
let wind = false;
|
||||||
|
|
||||||
// test the phenomenon for various value if it is provided.
|
// test the phenomenon for various value if it is provided.
|
||||||
weather.forEach(phenomenon => {
|
weather.forEach((phenomenon) => {
|
||||||
console.log(phenomenon.weather);
|
|
||||||
if (!phenomenon.weather) return;
|
if (!phenomenon.weather) return;
|
||||||
if (phenomenon.weather.toLowerCase().includes('thunder')) thunder = true;
|
if (phenomenon.weather.toLowerCase().includes('thunder')) thunder = true;
|
||||||
if (phenomenon.weather.toLowerCase().includes('snow')) snow = 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) && thunder) return addPath('ThunderSnow.gif');
|
||||||
if (snowfallAmount > 0 || snow) return addPath('Light-Snow.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 > 70) return addPath('Rain-1992.gif');
|
||||||
if (probabilityOfPrecipitation > 50) return addPath('Shower.gif');
|
if (probabilityOfPrecipitation > 50) return addPath('Shower.gif');
|
||||||
if (probabilityOfPrecipitation > 30) {
|
if (probabilityOfPrecipitation > 30) {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// current weather conditions display
|
// 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
|
// eslint-disable-next-line no-unused-vars
|
||||||
class LatestObservations extends WeatherDisplay {
|
class LatestObservations extends WeatherDisplay {
|
||||||
constructor(navId,elemId) {
|
constructor(navId, elemId) {
|
||||||
super(navId,elemId,'Latest Observations');
|
super(navId, elemId, 'Latest Observations');
|
||||||
// pre-load background image (returns promise)
|
// pre-load background image (returns promise)
|
||||||
this.backgroundImage = utils.image.load('images/BackGround1_1.png');
|
this.backgroundImage = utils.image.load('images/BackGround1_1.png');
|
||||||
|
|
||||||
|
@ -12,44 +12,45 @@ class LatestObservations extends WeatherDisplay {
|
||||||
this.MaximumRegionalStations = 7;
|
this.MaximumRegionalStations = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getData(weatherParameters) {
|
async getData(_weatherParameters) {
|
||||||
super.getData(weatherParameters);
|
super.getData(_weatherParameters);
|
||||||
if (!weatherParameters) weatherParameters = this.weatherParameters;
|
const weatherParameters = _weatherParameters ?? this.weatherParameters;
|
||||||
|
|
||||||
// calculate distance to each station
|
// calculate distance to each station
|
||||||
const stationsByDistance = Object.keys(_StationInfo).map(key => {
|
const stationsByDistance = Object.keys(StationInfo).map((key) => {
|
||||||
const station = _StationInfo[key];
|
const station = StationInfo[key];
|
||||||
const distance = utils.calc.distance(station.lat, station.lon, weatherParameters.latitude, weatherParameters.longitude);
|
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
|
// 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
|
// try up to 30 regional stations
|
||||||
const regionalStations = sortedStations.slice(0,30);
|
const regionalStations = sortedStations.slice(0, 30);
|
||||||
|
|
||||||
// get data for regional stations
|
// get data for regional stations
|
||||||
const allConditions = await Promise.all(regionalStations.map(async station => {
|
const allConditions = await Promise.all(regionalStations.map(async (station) => {
|
||||||
try {
|
try {
|
||||||
const data = await utils.fetch.json(`https://api.weather.gov/stations/${station.id}/observations/latest`);
|
const data = await utils.fetch.json(`https://api.weather.gov/stations/${station.id}/observations/latest`);
|
||||||
// test for temperature, weather and wind values present
|
// test for temperature, weather and wind values present
|
||||||
if (data.properties.temperature.value === null ||
|
if (data.properties.temperature.value === null
|
||||||
data.properties.textDescription === '' ||
|
|| data.properties.textDescription === ''
|
||||||
data.properties.windSpeed.value === null) return;
|
|| data.properties.windSpeed.value === null) return false;
|
||||||
// format the return values
|
// format the return values
|
||||||
return Object.assign({}, data.properties, {
|
return {
|
||||||
|
...data.properties,
|
||||||
StationId: station.id,
|
StationId: station.id,
|
||||||
city: station.city,
|
city: station.city,
|
||||||
});
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`Unable to get latest observations for ${station.id}`);
|
console.log(`Unable to get latest observations for ${station.id}`);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
// remove and stations that did not return data
|
// 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
|
// cut down to the maximum of 7
|
||||||
this.data = actualConditions.slice(0,this.MaximumRegionalStations);
|
this.data = actualConditions.slice(0, this.MaximumRegionalStations);
|
||||||
|
|
||||||
// test for at least one station
|
// test for at least one station
|
||||||
if (this.data.length < 1) {
|
if (this.data.length < 1) {
|
||||||
|
@ -64,7 +65,7 @@ class LatestObservations extends WeatherDisplay {
|
||||||
const conditions = this.data;
|
const conditions = this.data;
|
||||||
|
|
||||||
// sort array by station name
|
// 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);
|
this.context.drawImage(await this.backgroundImage, 0, 0);
|
||||||
draw.horizontalGradientSingle(this.context, 0, 30, 500, 90, draw.topColor1, draw.topColor2);
|
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');
|
draw.titleText(this.context, 'Latest', 'Observations');
|
||||||
|
|
||||||
if (navigation.units() === UNITS.english) {
|
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 {
|
} 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', 345, 105, 'WEATHER', 2);
|
||||||
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFFFF', 495, 105, 'WIND', 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', 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) {
|
if (WindSpeed > 0) {
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 495, y, windDirection + (Array(6 - windDirection.length - WindSpeed.toString().length).join(' ')) + WindSpeed.toString(), 2);
|
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();
|
this.finishDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
shortenCurrentConditions(condition) {
|
static shortenCurrentConditions(_condition) {
|
||||||
|
let condition = _condition;
|
||||||
condition = condition.replace(/Light/, 'L');
|
condition = condition.replace(/Light/, 'L');
|
||||||
condition = condition.replace(/Heavy/, 'H');
|
condition = condition.replace(/Heavy/, 'H');
|
||||||
condition = condition.replace(/Partly/, 'P');
|
condition = condition.replace(/Partly/, 'P');
|
||||||
|
|
|
@ -1,23 +1,22 @@
|
||||||
// display text based local forecast
|
// 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
|
// eslint-disable-next-line no-unused-vars
|
||||||
class LocalForecast extends WeatherDisplay {
|
class LocalForecast extends WeatherDisplay {
|
||||||
constructor(navId,elemId) {
|
constructor(navId, elemId) {
|
||||||
super(navId,elemId, 'Local Forecast');
|
super(navId, elemId, 'Local Forecast');
|
||||||
|
|
||||||
// set timings
|
// set timings
|
||||||
this.timing.baseDelay= 5000;
|
this.timing.baseDelay = 5000;
|
||||||
|
|
||||||
// pre-load background image (returns promise)
|
// pre-load background image (returns promise)
|
||||||
this.backgroundImage = utils.image.load('images/BackGround1_1.png');
|
this.backgroundImage = utils.image.load('images/BackGround1_1.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
async getData(weatherParameters) {
|
async getData(_weatherParameters) {
|
||||||
super.getData(weatherParameters);
|
super.getData(_weatherParameters);
|
||||||
if (!weatherParameters) weatherParameters = this.weatherParameters;
|
const weatherParameters = _weatherParameters ?? this.weatherParameters;
|
||||||
|
|
||||||
|
|
||||||
// get raw data
|
// get raw data
|
||||||
const rawData = await this.getRawData(weatherParameters);
|
const rawData = await this.getRawData(weatherParameters);
|
||||||
|
@ -27,7 +26,7 @@ class LocalForecast extends WeatherDisplay {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// parse raw data
|
// parse raw data
|
||||||
const conditions = this.parseLocalForecast(rawData);
|
const conditions = LocalForecast.parse(rawData);
|
||||||
|
|
||||||
// split this forecast into the correct number of screens
|
// split this forecast into the correct number of screens
|
||||||
const maxRows = 7;
|
const maxRows = 7;
|
||||||
|
@ -36,9 +35,9 @@ class LocalForecast extends WeatherDisplay {
|
||||||
this.screenTexts = [];
|
this.screenTexts = [];
|
||||||
|
|
||||||
// read each text
|
// read each text
|
||||||
conditions.forEach(condition => {
|
conditions.forEach((condition) => {
|
||||||
// process the text
|
// process the text
|
||||||
let text = condition.DayName.toUpperCase() + '...';
|
let text = `${condition.DayName.toUpperCase()}...`;
|
||||||
let conditionText = condition.Text;
|
let conditionText = condition.Text;
|
||||||
if (navigation.units() === UNITS.metric) {
|
if (navigation.units() === UNITS.metric) {
|
||||||
conditionText = condition.TextC;
|
conditionText = condition.TextC;
|
||||||
|
@ -52,28 +51,27 @@ class LocalForecast extends WeatherDisplay {
|
||||||
const maxRowCount = maxRows;
|
const maxRowCount = maxRows;
|
||||||
let rowCount = 0;
|
let rowCount = 0;
|
||||||
|
|
||||||
|
|
||||||
// if (PrependAlert) {
|
// if (PrependAlert) {
|
||||||
// ScreenText = LocalForecastScreenTexts[LocalForecastScreenTexts.length - 1];
|
// ScreenText = LocalForecastScreenTexts[LocalForecastScreenTexts.length - 1];
|
||||||
// rowCount = ScreenText.split('\n').length - 1;
|
// rowCount = ScreenText.split('\n').length - 1;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
for (let i = 0; i <= lineCount - 1; i++) {
|
for (let i = 0; i <= lineCount - 1; i += 1) {
|
||||||
if (lines[i] === '') continue;
|
if (lines[i] !== '') {
|
||||||
|
if (rowCount > maxRowCount - 1) {
|
||||||
if (rowCount > maxRowCount - 1) {
|
|
||||||
// if (PrependAlert) {
|
// if (PrependAlert) {
|
||||||
// LocalForecastScreenTexts[LocalForecastScreenTexts.length - 1] = ScreenText;
|
// LocalForecastScreenTexts[LocalForecastScreenTexts.length - 1] = ScreenText;
|
||||||
// PrependAlert = false;
|
// PrependAlert = false;
|
||||||
// } else {
|
// } else {
|
||||||
this.screenTexts.push(ScreenText);
|
this.screenTexts.push(ScreenText);
|
||||||
// }
|
// }
|
||||||
ScreenText = '';
|
ScreenText = '';
|
||||||
rowCount = 0;
|
rowCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ScreenText += lines[i] + '\n';
|
ScreenText += `${lines[i]}\n`;
|
||||||
rowCount++;
|
rowCount += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// if (PrependAlert) {
|
// if (PrependAlert) {
|
||||||
// this.screenTexts[this.screenTexts.length - 1] = ScreenText;
|
// this.screenTexts[this.screenTexts.length - 1] = ScreenText;
|
||||||
|
@ -99,7 +97,6 @@ class LocalForecast extends WeatherDisplay {
|
||||||
units,
|
units,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`GetWeatherForecast failed: ${weatherParameters.forecast}`);
|
console.error(`GetWeatherForecast failed: ${weatherParameters.forecast}`);
|
||||||
console.error(e.status, e.responseJSON);
|
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.box(this.context, 'rgb(33, 40, 90)', 65, 105, 505, 280);
|
||||||
// Draw the text.
|
// Draw the text.
|
||||||
this.screenTexts[this.screenIndex].split('\n').forEach((text, index) => {
|
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();
|
this.finishDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
// format the forecast
|
// format the forecast
|
||||||
parseLocalForecast (forecast) {
|
static parse(forecast) {
|
||||||
// only use the first 6 lines
|
// 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
|
// format day and text
|
||||||
DayName: text.name.toUpperCase(),
|
DayName: text.name.toUpperCase(),
|
||||||
Text: text.detailedForecast,
|
Text: text.detailedForecast,
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
'use strict';
|
|
||||||
// navigation handles progress, next/previous and initial load messages from the parent frame
|
// 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 */
|
/* globals CurrentWeather, LatestObservations, TravelForecast, RegionalForecast, LocalForecast, ExtendedForecast, Almanac, Radar, Progress, Hourly */
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
@ -13,11 +12,11 @@ const UNITS = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigation = (() => {
|
const navigation = (() => {
|
||||||
let weatherParameters = {};
|
|
||||||
let displays = [];
|
let displays = [];
|
||||||
let currentUnits = UNITS.english;
|
let currentUnits = UNITS.english;
|
||||||
let playing = false;
|
let playing = false;
|
||||||
let progress;
|
let progress;
|
||||||
|
const weatherParameters = {};
|
||||||
|
|
||||||
// current conditions and sunrise/sunset are made available from the display below
|
// current conditions and sunrise/sunset are made available from the display below
|
||||||
let currentWeather;
|
let currentWeather;
|
||||||
|
@ -46,11 +45,10 @@ const navigation = (() => {
|
||||||
default:
|
default:
|
||||||
console.error(`Unknown event ${data.type}`);
|
console.error(`Unknown event ${data.type}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const postMessage = (type, message = {}) => {
|
const postMessage = (type, myMessage = {}) => {
|
||||||
index.message({type, message});
|
index.message({ type, message: myMessage });
|
||||||
};
|
};
|
||||||
|
|
||||||
const getWeather = async (latLon) => {
|
const getWeather = async (latLon) => {
|
||||||
|
@ -62,11 +60,11 @@ const navigation = (() => {
|
||||||
|
|
||||||
const StationId = stations.features[0].properties.stationIdentifier;
|
const StationId = stations.features[0].properties.stationIdentifier;
|
||||||
|
|
||||||
let city = point.properties.relativeLocation.properties.city;
|
let { city } = point.properties.relativeLocation.properties;
|
||||||
|
|
||||||
if (StationId in _StationInfo) {
|
if (StationId in StationInfo) {
|
||||||
city = _StationInfo[StationId].city;
|
city = StationInfo[StationId].city;
|
||||||
city = city.split('/')[0];
|
[city] = city.split('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
// populate the weather parameters
|
// populate the weather parameters
|
||||||
|
@ -89,13 +87,13 @@ const navigation = (() => {
|
||||||
// draw the progress canvas and hide others
|
// draw the progress canvas and hide others
|
||||||
hideAllCanvases();
|
hideAllCanvases();
|
||||||
document.getElementById('loading').style.display = 'none';
|
document.getElementById('loading').style.display = 'none';
|
||||||
progress = new Progress(-1,'progress');
|
progress = new Progress(-1, 'progress');
|
||||||
await progress.drawCanvas();
|
await progress.drawCanvas();
|
||||||
progress.showCanvas();
|
progress.showCanvas();
|
||||||
|
|
||||||
// start loading canvases if necessary
|
// start loading canvases if necessary
|
||||||
if (displays.length === 0) {
|
if (displays.length === 0) {
|
||||||
currentWeather = new CurrentWeather(0,'currentWeather');
|
currentWeather = new CurrentWeather(0, 'currentWeather');
|
||||||
almanac = new Almanac(7, 'almanac');
|
almanac = new Almanac(7, 'almanac');
|
||||||
displays = [
|
displays = [
|
||||||
currentWeather,
|
currentWeather,
|
||||||
|
@ -110,8 +108,7 @@ const navigation = (() => {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
// call for new data on each display
|
// 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}
|
// receive a status update from a module {id, value}
|
||||||
|
@ -130,12 +127,12 @@ const navigation = (() => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const countLoadedCanvases = () => displays.reduce((acc, display) => {
|
const countLoadedCanvases = () => displays.reduce((acc, display) => {
|
||||||
if (display.status !== STATUS.loading) return acc+1;
|
if (display.status !== STATUS.loading) return acc + 1;
|
||||||
return acc;
|
return acc;
|
||||||
},0);
|
}, 0);
|
||||||
|
|
||||||
const hideAllCanvases = () => {
|
const hideAllCanvases = () => {
|
||||||
displays.forEach(display => display.hideCanvas());
|
displays.forEach((display) => display.hideCanvas());
|
||||||
};
|
};
|
||||||
|
|
||||||
const units = () => currentUnits;
|
const units = () => currentUnits;
|
||||||
|
@ -168,9 +165,9 @@ const navigation = (() => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// receive navigation messages from displays
|
// receive navigation messages from displays
|
||||||
const displayNavMessage = (message) => {
|
const displayNavMessage = (myMessage) => {
|
||||||
if (message.type === msg.response.previous) loadDisplay(-1);
|
if (myMessage.type === msg.response.previous) loadDisplay(-1);
|
||||||
if (message.type === msg.response.next) loadDisplay(1);
|
if (myMessage.type === msg.response.next) loadDisplay(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
// navigate to next or previous
|
// navigate to next or previous
|
||||||
|
@ -192,9 +189,9 @@ const navigation = (() => {
|
||||||
const totalDisplays = displays.length;
|
const totalDisplays = displays.length;
|
||||||
const curIdx = currentDisplayIndex();
|
const curIdx = currentDisplayIndex();
|
||||||
let idx;
|
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
|
// 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;
|
if (displays[idx].status === STATUS.loaded) break;
|
||||||
}
|
}
|
||||||
const newDisplay = displays[idx];
|
const newDisplay = displays[idx];
|
||||||
|
@ -207,12 +204,10 @@ const navigation = (() => {
|
||||||
|
|
||||||
// get the current display index or value
|
// get the current display index or value
|
||||||
const currentDisplayIndex = () => {
|
const currentDisplayIndex = () => {
|
||||||
let index = displays.findIndex(display=>display.isActive());
|
const index = displays.findIndex((display) => display.isActive());
|
||||||
return index;
|
return index;
|
||||||
};
|
};
|
||||||
const currentDisplay = () => {
|
const currentDisplay = () => displays[currentDisplayIndex()];
|
||||||
return displays[currentDisplayIndex()];
|
|
||||||
};
|
|
||||||
|
|
||||||
const setPlaying = (newValue) => {
|
const setPlaying = (newValue) => {
|
||||||
playing = newValue;
|
playing = newValue;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
class Progress extends WeatherDisplay {
|
class Progress extends WeatherDisplay {
|
||||||
constructor(navId,elemId) {
|
constructor(navId, elemId) {
|
||||||
super(navId, elemId);
|
super(navId, elemId);
|
||||||
|
|
||||||
// pre-load background image (returns promise)
|
// 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, 584, 90, 640, 399, draw.sideColor1, draw.sideColor2);
|
||||||
draw.horizontalGradientSingle(this.context, 0, 30, 500, 90, draw.topColor1, draw.topColor2);
|
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.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();
|
this.finishDraw();
|
||||||
// if no displays provided just draw the backgrounds (above)
|
// if no displays provided just draw the backgrounds (above)
|
||||||
if (!displays) return;
|
if (!displays) return;
|
||||||
displays.forEach((display, idx) => {
|
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('.');
|
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);
|
draw.text(this.context, 'Star4000 Extended', '19pt', '#ffffff', 70, y, display.name + dots, 2);
|
||||||
|
|
||||||
|
|
||||||
let statusText;
|
let statusText;
|
||||||
let statusColor;
|
let statusColor;
|
||||||
switch (display.status) {
|
switch (display.status) {
|
||||||
|
@ -77,24 +76,21 @@ class Progress extends WeatherDisplay {
|
||||||
// Erase any dots that spill into the status text.
|
// Erase any dots that spill into the status text.
|
||||||
this.context.drawImage(backgroundImage, 475, y - 20, 165, 30, 475, y - 20, 165, 30);
|
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');
|
draw.text(this.context, 'Star4000 Extended', '19pt', statusColor, 565, y, statusText, 2, 'end');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// calculate loaded percent
|
// calculate loaded percent
|
||||||
const loadedPercent = (loadedCount/displays.length);
|
const loadedPercent = (loadedCount / displays.length);
|
||||||
|
|
||||||
if (loadedPercent < 1.0) {
|
if (loadedPercent < 1.0) {
|
||||||
// Draw a box for the progress.
|
// Draw a box for the progress.
|
||||||
draw.box(this.context, '#000000', 51, 428, 534, 22);
|
draw.box(this.context, '#000000', 51, 428, 534, 22);
|
||||||
draw.box(this.context, '#ffffff', 53, 430, 530, 18);
|
draw.box(this.context, '#ffffff', 53, 430, 530, 18);
|
||||||
// update the progress gif
|
// 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 {
|
} else {
|
||||||
// restore the background
|
// restore the background
|
||||||
this.context.drawImage(backgroundImage, 51, 428, 534, 22, 51, 428, 534, 22);
|
this.context.drawImage(backgroundImage, 51, 428, 534, 22, 51, 428, 534, 22);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
canvasClick(e) {
|
canvasClick(e) {
|
||||||
|
@ -106,9 +102,9 @@ class Progress extends WeatherDisplay {
|
||||||
if (x < 440 || x > 570) return;
|
if (x < 440 || x > 570) return;
|
||||||
|
|
||||||
// stop playing
|
// stop playing
|
||||||
navigation.message('navButton', stop);
|
navigation.message('navButton');
|
||||||
// use the y value to determine an index
|
// 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);
|
const display = navigation.getDisplay(index);
|
||||||
if (display && display.status === STATUS.loaded) {
|
if (display && display.status === STATUS.loaded) {
|
||||||
display.showCanvas(navigation.msg.command.firstFrame);
|
display.showCanvas(navigation.msg.command.firstFrame);
|
||||||
|
|
|
@ -3,42 +3,42 @@
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
class Radar extends WeatherDisplay {
|
class Radar extends WeatherDisplay {
|
||||||
constructor(navId,elemId) {
|
constructor(navId, elemId) {
|
||||||
super(navId,elemId,'Local Radar');
|
super(navId, elemId, 'Local Radar');
|
||||||
|
|
||||||
// set max images
|
// set max images
|
||||||
this.dopplerRadarImageMax = 6;
|
this.dopplerRadarImageMax = 6;
|
||||||
// update timing
|
// update timing
|
||||||
this.timing.baseDelay = 350;
|
this.timing.baseDelay = 350;
|
||||||
this.timing.delay = [
|
this.timing.delay = [
|
||||||
{time: 4, si: 5},
|
{ time: 4, si: 5 },
|
||||||
{time: 1, si: 0},
|
{ time: 1, si: 0 },
|
||||||
{time: 1, si: 1},
|
{ time: 1, si: 1 },
|
||||||
{time: 1, si: 2},
|
{ time: 1, si: 2 },
|
||||||
{time: 1, si: 3},
|
{ time: 1, si: 3 },
|
||||||
{time: 1, si: 4},
|
{ time: 1, si: 4 },
|
||||||
{time: 4, si: 5},
|
{ time: 4, si: 5 },
|
||||||
{time: 1, si: 0},
|
{ time: 1, si: 0 },
|
||||||
{time: 1, si: 1},
|
{ time: 1, si: 1 },
|
||||||
{time: 1, si: 2},
|
{ time: 1, si: 2 },
|
||||||
{time: 1, si: 3},
|
{ time: 1, si: 3 },
|
||||||
{time: 1, si: 4},
|
{ time: 1, si: 4 },
|
||||||
{time: 4, si: 5},
|
{ time: 4, si: 5 },
|
||||||
{time: 1, si: 0},
|
{ time: 1, si: 0 },
|
||||||
{time: 1, si: 1},
|
{ time: 1, si: 1 },
|
||||||
{time: 1, si: 2},
|
{ time: 1, si: 2 },
|
||||||
{time: 1, si: 3},
|
{ time: 1, si: 3 },
|
||||||
{time: 1, si: 4},
|
{ time: 1, si: 4 },
|
||||||
{time: 12, si: 5},
|
{ time: 12, si: 5 },
|
||||||
];
|
];
|
||||||
|
|
||||||
// pre-load background image (returns promise)
|
// pre-load background image (returns promise)
|
||||||
this.backgroundImage = utils.image.load('images/BackGround4_1.png');
|
this.backgroundImage = utils.image.load('images/BackGround4_1.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
async getData(weatherParameters) {
|
async getData(_weatherParameters) {
|
||||||
super.getData(weatherParameters);
|
super.getData(_weatherParameters);
|
||||||
if (!weatherParameters) weatherParameters = this.weatherParameters;
|
const weatherParameters = _weatherParameters ?? this.weatherParameters;
|
||||||
|
|
||||||
// ALASKA ISN'T SUPPORTED!
|
// ALASKA ISN'T SUPPORTED!
|
||||||
if (weatherParameters.state === 'AK') {
|
if (weatherParameters.state === 'AK') {
|
||||||
|
@ -47,7 +47,7 @@ class Radar extends WeatherDisplay {
|
||||||
}
|
}
|
||||||
|
|
||||||
// date and time parsing
|
// date and time parsing
|
||||||
const {DateTime} = luxon;
|
const { DateTime } = luxon;
|
||||||
|
|
||||||
// get the base map
|
// get the base map
|
||||||
let src = 'images/4000RadarMap2.jpg';
|
let src = 'images/4000RadarMap2.jpg';
|
||||||
|
@ -59,9 +59,9 @@ class Radar extends WeatherDisplay {
|
||||||
let radarHtml;
|
let radarHtml;
|
||||||
try {
|
try {
|
||||||
// get a list of available radars
|
// get a list of available radars
|
||||||
radarHtml = await utils.fetch.text(baseUrl, {cors: true});
|
radarHtml = await utils.fetch.text(baseUrl, { cors: true });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('Unable to get list of radars');
|
console.error('Unable to get list of radars');
|
||||||
console.error(e);
|
console.error(e);
|
||||||
this.setStatus(STATUS.failed);
|
this.setStatus(STATUS.failed);
|
||||||
return;
|
return;
|
||||||
|
@ -72,17 +72,17 @@ class Radar extends WeatherDisplay {
|
||||||
const xmlDoc = parser.parseFromString(radarHtml, 'text/html');
|
const xmlDoc = parser.parseFromString(radarHtml, 'text/html');
|
||||||
const anchors = xmlDoc.getElementsByTagName('a');
|
const anchors = xmlDoc.getElementsByTagName('a');
|
||||||
const gifs = [];
|
const gifs = [];
|
||||||
for (let idx in anchors) {
|
Object.values(anchors).forEach((a) => {
|
||||||
gifs.push(anchors[idx].innerHTML);
|
gifs.push(a.innerHTML);
|
||||||
}
|
});
|
||||||
|
|
||||||
// filter for selected urls
|
// filter for selected urls
|
||||||
let filter = /Conus_\d/;
|
let filter = /Conus_\d/;
|
||||||
if (weatherParameters.state === 'HI') filter = /hawaii_\d/;
|
if (weatherParameters.state === 'HI') filter = /hawaii_\d/;
|
||||||
|
|
||||||
// get the last few images
|
// get the last few images
|
||||||
const urlsFull = gifs.filter(gif => gif && gif.match(filter));
|
const urlsFull = gifs.filter((gif) => gif && gif.match(filter));
|
||||||
const urls = urlsFull.slice(-(this.dopplerRadarImageMax-1));
|
const urls = urlsFull.slice(-(this.dopplerRadarImageMax - 1));
|
||||||
|
|
||||||
// add additional 'latest.gif'
|
// add additional 'latest.gif'
|
||||||
if (weatherParameters.state !== 'HI') urls.push('latest_radaronly.gif');
|
if (weatherParameters.state !== 'HI') urls.push('latest_radaronly.gif');
|
||||||
|
@ -97,13 +97,13 @@ class Radar extends WeatherDisplay {
|
||||||
if (weatherParameters.state === 'HI') {
|
if (weatherParameters.state === 'HI') {
|
||||||
width = 600;
|
width = 600;
|
||||||
height = 571;
|
height = 571;
|
||||||
sourceXY = this.getXYFromLatitudeLongitudeHI(weatherParameters.latitude, weatherParameters.longitude, offsetX, offsetY);
|
sourceXY = Radar.getXYFromLatitudeLongitudeHI(weatherParameters.latitude, weatherParameters.longitude, offsetX, offsetY);
|
||||||
} else {
|
} else {
|
||||||
width = 2550;
|
width = 2550;
|
||||||
height = 1600;
|
height = 1600;
|
||||||
offsetX *= 2;
|
offsetX *= 2;
|
||||||
offsetY *= 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
|
// create working context for manipulation
|
||||||
|
@ -116,7 +116,7 @@ class Radar extends WeatherDisplay {
|
||||||
// calculate radar offsets
|
// calculate radar offsets
|
||||||
let radarOffsetX = 117;
|
let radarOffsetX = 117;
|
||||||
let radarOffsetY = 60;
|
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 radarSourceX = radarSourceXY.x / 2;
|
||||||
let radarSourceY = radarSourceXY.y / 2;
|
let radarSourceY = radarSourceXY.y / 2;
|
||||||
|
|
||||||
|
@ -176,7 +176,7 @@ class Radar extends WeatherDisplay {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the base map
|
// 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
|
// crop the radar image
|
||||||
const cropCanvas = document.createElement('canvas');
|
const cropCanvas = document.createElement('canvas');
|
||||||
|
@ -186,10 +186,10 @@ class Radar extends WeatherDisplay {
|
||||||
cropContext.imageSmoothingEnabled = false;
|
cropContext.imageSmoothingEnabled = false;
|
||||||
cropContext.drawImage(workingCanvas, radarSourceX, radarSourceY, (radarOffsetX * 2), (radarOffsetY * 2.33), 0, 0, 640, 367);
|
cropContext.drawImage(workingCanvas, radarSourceX, radarSourceY, (radarOffsetX * 2), (radarOffsetY * 2.33), 0, 0, 640, 367);
|
||||||
// clean the image
|
// clean the image
|
||||||
this.removeDopplerRadarImageNoise(cropContext);
|
Radar.removeDopplerRadarImageNoise(cropContext);
|
||||||
|
|
||||||
// merge the radar and map
|
// merge the radar and map
|
||||||
this.mergeDopplerRadarImage(context, cropContext);
|
Radar.mergeDopplerRadarImage(context, cropContext);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
canvas,
|
canvas,
|
||||||
|
@ -199,9 +199,9 @@ class Radar extends WeatherDisplay {
|
||||||
// set max length
|
// set max length
|
||||||
this.timing.totalScreens = radarInfo.length;
|
this.timing.totalScreens = radarInfo.length;
|
||||||
// store the images
|
// 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);
|
this.setStatus(STATUS.loaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,7 +209,7 @@ class Radar extends WeatherDisplay {
|
||||||
super.drawCanvas();
|
super.drawCanvas();
|
||||||
if (this.screenIndex === -1) return;
|
if (this.screenIndex === -1) return;
|
||||||
this.context.drawImage(await this.backgroundImage, 0, 0);
|
this.context.drawImage(await this.backgroundImage, 0, 0);
|
||||||
const {DateTime} = luxon;
|
const { DateTime } = luxon;
|
||||||
// Title
|
// Title
|
||||||
draw.text(this.context, 'Arial', 'bold 28pt', '#ffffff', 155, 60, 'Local', 2);
|
draw.text(this.context, 'Arial', 'bold 28pt', '#ffffff', 155, 60, 'Local', 2);
|
||||||
draw.text(this.context, 'Arial', 'bold 28pt', '#ffffff', 155, 95, 'Radar', 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
|
// utility latitude/pixel conversions
|
||||||
getXYFromLatitudeLongitude (Latitude, Longitude, OffsetX, OffsetY, state) {
|
getXYFromLatitudeLongitude(Latitude, Longitude, OffsetX, OffsetY, state) {
|
||||||
if (state === 'HI') return this.getXYFromLatitudeLongitudeHI(...arguments);
|
if (state === 'HI') return this.getXYFromLatitudeLongitudeHI(...arguments);
|
||||||
let y = 0;
|
let y = 0;
|
||||||
let x = 0;
|
let x = 0;
|
||||||
|
@ -265,7 +265,7 @@ class Radar extends WeatherDisplay {
|
||||||
return { x, y };
|
return { x, y };
|
||||||
}
|
}
|
||||||
|
|
||||||
getXYFromLatitudeLongitudeHI(Latitude, Longitude, OffsetX, OffsetY) {
|
static getXYFromLatitudeLongitudeHI(Latitude, Longitude, OffsetX, OffsetY) {
|
||||||
let y = 0;
|
let y = 0;
|
||||||
let x = 0;
|
let x = 0;
|
||||||
const ImgHeight = 571;
|
const ImgHeight = 571;
|
||||||
|
@ -292,7 +292,7 @@ class Radar extends WeatherDisplay {
|
||||||
return { x, y };
|
return { x, y };
|
||||||
}
|
}
|
||||||
|
|
||||||
getXYFromLatitudeLongitudeDoppler (Latitude, Longitude, OffsetX, OffsetY) {
|
static getXYFromLatitudeLongitudeDoppler(Latitude, Longitude, OffsetX, OffsetY) {
|
||||||
let y = 0;
|
let y = 0;
|
||||||
let x = 0;
|
let x = 0;
|
||||||
const ImgHeight = 3200;
|
const ImgHeight = 3200;
|
||||||
|
@ -319,7 +319,7 @@ class Radar extends WeatherDisplay {
|
||||||
return { x: x * 2, y: y * 2 };
|
return { x: x * 2, y: y * 2 };
|
||||||
}
|
}
|
||||||
|
|
||||||
removeDopplerRadarImageNoise (RadarContext) {
|
static removeDopplerRadarImageNoise(RadarContext) {
|
||||||
const RadarImageData = RadarContext.getImageData(0, 0, RadarContext.canvas.width, RadarContext.canvas.height);
|
const RadarImageData = RadarContext.getImageData(0, 0, RadarContext.canvas.width, RadarContext.canvas.height);
|
||||||
|
|
||||||
// examine every pixel,
|
// examine every pixel,
|
||||||
|
@ -329,7 +329,7 @@ class Radar extends WeatherDisplay {
|
||||||
// i + 1 = green
|
// i + 1 = green
|
||||||
// i + 2 = blue
|
// i + 2 = blue
|
||||||
// i + 3 = alpha (0 = transparent, 255 = opaque)
|
// 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?
|
// is this pixel the old rgb?
|
||||||
if ((R === 1 && G === 159 && B === 244)
|
if ((R === 1 && G === 159 && B === 244)
|
||||||
|
@ -402,13 +402,13 @@ class Radar extends WeatherDisplay {
|
||||||
RadarContext.putImageData(RadarImageData, 0, 0);
|
RadarContext.putImageData(RadarImageData, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeDopplerRadarImage (mapContext, radarContext) {
|
static mergeDopplerRadarImage(mapContext, radarContext) {
|
||||||
var mapImageData = mapContext.getImageData(0, 0, mapContext.canvas.width, mapContext.canvas.height);
|
const mapImageData = mapContext.getImageData(0, 0, mapContext.canvas.width, mapContext.canvas.height);
|
||||||
var radarImageData = radarContext.getImageData(0, 0, radarContext.canvas.width, radarContext.canvas.height);
|
const radarImageData = radarContext.getImageData(0, 0, radarContext.canvas.width, radarContext.canvas.height);
|
||||||
|
|
||||||
// examine every pixel,
|
// examine every pixel,
|
||||||
// change any old rgb to the new-rgb
|
// 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 + 0 = red
|
||||||
// i + 1 = green
|
// i + 1 = green
|
||||||
// i + 2 = blue
|
// i + 2 = blue
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
// regional forecast and observations
|
// regional forecast and observations
|
||||||
// type 0 = observations, 1 = first forecast, 2 = second forecast
|
// 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
|
// eslint-disable-next-line no-unused-vars
|
||||||
class RegionalForecast extends WeatherDisplay {
|
class RegionalForecast extends WeatherDisplay {
|
||||||
constructor(navId,elemId) {
|
constructor(navId, elemId) {
|
||||||
super(navId,elemId,'Regional Forecast');
|
super(navId, elemId, 'Regional Forecast');
|
||||||
|
|
||||||
// pre-load background image (returns promise)
|
// pre-load background image (returns promise)
|
||||||
this.backgroundImage = utils.image.load('images/BackGround5_1.png');
|
this.backgroundImage = utils.image.load('images/BackGround5_1.png');
|
||||||
|
@ -15,9 +15,9 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
this.timing.totalScreens = 3;
|
this.timing.totalScreens = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getData(weatherParameters) {
|
async getData(_weatherParameters) {
|
||||||
super.getData(weatherParameters);
|
super.getData(_weatherParameters);
|
||||||
if (!weatherParameters) weatherParameters = this.weatherParameters;
|
const weatherParameters = _weatherParameters ?? this.weatherParameters;
|
||||||
|
|
||||||
// pre-load the base map (returns promise)
|
// pre-load the base map (returns promise)
|
||||||
let src = 'images/Basemap2.png';
|
let src = 'images/Basemap2.png';
|
||||||
|
@ -44,35 +44,35 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
if (weatherParameters.state === 'HI') targetDistance = 1;
|
if (weatherParameters.state === 'HI') targetDistance = 1;
|
||||||
|
|
||||||
// make station info into an array
|
// 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
|
// combine regional cities with station info for additional stations
|
||||||
// stations are intentionally after cities to allow cities priority when drawing the map
|
// 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.
|
// Determine which cities are within the max/min latitude/longitude.
|
||||||
const regionalCities = [];
|
const regionalCities = [];
|
||||||
combinedCities.forEach(city => {
|
combinedCities.forEach((city) => {
|
||||||
if (city.lat > minMaxLatLon.minLat && city.lat < minMaxLatLon.maxLat &&
|
if (city.lat > minMaxLatLon.minLat && city.lat < minMaxLatLon.maxLat
|
||||||
city.lon > minMaxLatLon.minLon && city.lon < minMaxLatLon.maxLon - 1) {
|
&& city.lon > minMaxLatLon.minLon && city.lon < minMaxLatLon.maxLon - 1) {
|
||||||
// default to 1 for cities loaded from _RegionalCities, use value calculate above for remaining stations
|
// default to 1 for cities loaded from RegionalCities, use value calculate above for remaining stations
|
||||||
const targetDistance = city.targetDistance || 1;
|
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.
|
// 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 okToAddCity = regionalCities.reduce((acc, testCity) => {
|
||||||
const distance = utils.calc.distance(city.lon, city.lat, testCity.lon, testCity.lat);
|
const distance = utils.calc.distance(city.lon, city.lat, testCity.lon, testCity.lat);
|
||||||
return acc && distance >= targetDistance;
|
return acc && distance >= targetDist;
|
||||||
}, true);
|
}, true);
|
||||||
if (okToAddCity) regionalCities.push(city);
|
if (okToAddCity) regionalCities.push(city);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// get regional forecasts and observations (the two are intertwined due to the design of api.weather.gov)
|
// 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 {
|
try {
|
||||||
// get the point first, then break down into forecast and observations
|
// get the point first, then break down into forecast and observations
|
||||||
const point = await utils.weather.getPoint(city.lat, city.lon);
|
const point = await utils.weather.getPoint(city.lat, city.lon);
|
||||||
|
|
||||||
// start off the observation task
|
// 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);
|
const forecast = await utils.fetch.json(point.properties.forecast);
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
const regionalObservation = {
|
const regionalObservation = {
|
||||||
daytime: !!observation.icon.match(/\/day\//),
|
daytime: !!observation.icon.match(/\/day\//),
|
||||||
temperature: utils.units.celsiusToFahrenheit(observation.temperature.value),
|
temperature: utils.units.celsiusToFahrenheit(observation.temperature.value),
|
||||||
name: this.formatCity(city.city),
|
name: RegionalForecast.formatCity(city.city),
|
||||||
icon: observation.icon,
|
icon: observation.icon,
|
||||||
x: cityXY.x,
|
x: cityXY.x,
|
||||||
y: cityXY.y,
|
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
|
// always skip the first forecast index because it's what's going on right now
|
||||||
return [
|
return [
|
||||||
regionalObservation,
|
regionalObservation,
|
||||||
this.buildForecast(forecast.properties.periods[1], city, cityXY),
|
RegionalForecast.buildForecast(forecast.properties.periods[1], city, cityXY),
|
||||||
this.buildForecast(forecast.properties.periods[2], city, cityXY),
|
RegionalForecast.buildForecast(forecast.properties.periods[2], city, cityXY),
|
||||||
];
|
];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`No regional forecast data for '${city.name}'`);
|
console.log(`No regional forecast data for '${city.name}'`);
|
||||||
|
@ -114,7 +114,7 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
// wait for the forecasts
|
// wait for the forecasts
|
||||||
const regionalDataAll = await Promise.all(regionalForecastPromises);
|
const regionalDataAll = await Promise.all(regionalForecastPromises);
|
||||||
// filter out any false (unavailable data)
|
// filter out any false (unavailable data)
|
||||||
const regionalData = regionalDataAll.filter(data => data);
|
const regionalData = regionalDataAll.filter((data) => data);
|
||||||
|
|
||||||
// test for data present
|
// test for data present
|
||||||
if (regionalData.length === 0) {
|
if (regionalData.length === 0) {
|
||||||
|
@ -132,11 +132,11 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
this.setStatus(STATUS.loaded);
|
this.setStatus(STATUS.loaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
buildForecast (forecast, city, cityXY) {
|
static buildForecast(forecast, city, cityXY) {
|
||||||
return {
|
return {
|
||||||
daytime: forecast.isDaytime,
|
daytime: forecast.isDaytime,
|
||||||
temperature: forecast.temperature||0,
|
temperature: forecast.temperature || 0,
|
||||||
name: this.formatCity(city.city),
|
name: RegionalForecast.formatCity(city.city),
|
||||||
icon: forecast.icon,
|
icon: forecast.icon,
|
||||||
x: cityXY.x,
|
x: cityXY.x,
|
||||||
y: cityXY.y,
|
y: cityXY.y,
|
||||||
|
@ -144,7 +144,7 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRegionalObservation (point, city) {
|
static async getRegionalObservation(point, city) {
|
||||||
try {
|
try {
|
||||||
// get stations
|
// get stations
|
||||||
const stations = await utils.fetch.json(point.properties.observationStations);
|
const stations = await utils.fetch.json(point.properties.observationStations);
|
||||||
|
@ -165,9 +165,9 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
}
|
}
|
||||||
|
|
||||||
// utility latitude/pixel conversions
|
// utility latitude/pixel conversions
|
||||||
getXYFromLatitudeLongitude (Latitude, Longitude, OffsetX, OffsetY, state) {
|
getXYFromLatitudeLongitude(Latitude, Longitude, OffsetX, OffsetY, state) {
|
||||||
if (state === 'AK') return this.getXYFromLatitudeLongitudeAK(...arguments);
|
if (state === 'AK') return this.getXYFromLatitudeLongitudeAK(Latitude, Longitude, OffsetX, OffsetY);
|
||||||
if (state === 'HI') return this.getXYFromLatitudeLongitudeHI(...arguments);
|
if (state === 'HI') return this.getXYFromLatitudeLongitudeHI(Latitude, Longitude, OffsetX, OffsetY);
|
||||||
let y = 0;
|
let y = 0;
|
||||||
let x = 0;
|
let x = 0;
|
||||||
const ImgHeight = 1600;
|
const ImgHeight = 1600;
|
||||||
|
@ -194,7 +194,7 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
return { x, y };
|
return { x, y };
|
||||||
}
|
}
|
||||||
|
|
||||||
getXYFromLatitudeLongitudeAK (Latitude, Longitude, OffsetX, OffsetY) {
|
static getXYFromLatitudeLongitudeAK(Latitude, Longitude, OffsetX, OffsetY) {
|
||||||
let y = 0;
|
let y = 0;
|
||||||
let x = 0;
|
let x = 0;
|
||||||
const ImgHeight = 1142;
|
const ImgHeight = 1142;
|
||||||
|
@ -221,7 +221,7 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
return { x, y };
|
return { x, y };
|
||||||
}
|
}
|
||||||
|
|
||||||
getXYFromLatitudeLongitudeHI (Latitude, Longitude, OffsetX, OffsetY) {
|
static getXYFromLatitudeLongitudeHI(Latitude, Longitude, OffsetX, OffsetY) {
|
||||||
let y = 0;
|
let y = 0;
|
||||||
let x = 0;
|
let x = 0;
|
||||||
const ImgHeight = 571;
|
const ImgHeight = 571;
|
||||||
|
@ -248,38 +248,44 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
return { x, y };
|
return { x, y };
|
||||||
}
|
}
|
||||||
|
|
||||||
getMinMaxLatitudeLongitude (X, Y, OffsetX, OffsetY, state) {
|
getMinMaxLatitudeLongitude(X, Y, OffsetX, OffsetY, state) {
|
||||||
if (state === 'AK') return this.getMinMaxLatitudeLongitudeAK(...arguments);
|
if (state === 'AK') return this.getMinMaxLatitudeLongitudeAK(X, Y, OffsetX, OffsetY);
|
||||||
if (state === 'HI') return this.getMinMaxLatitudeLongitudeHI(...arguments);
|
if (state === 'HI') return this.getMinMaxLatitudeLongitudeHI(X, Y, OffsetX, OffsetY);
|
||||||
const maxLat = ((Y / 55.2) - 50.5) * -1;
|
const maxLat = ((Y / 55.2) - 50.5) * -1;
|
||||||
const minLat = (((Y + (OffsetY * 2)) / 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 minLon = (((X * -1) / 41.775) + 127.5) * -1;
|
||||||
const maxLon = ((((X + (OffsetX * 2)) * -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 maxLat = ((Y / 56) - 73.0) * -1;
|
||||||
const minLat = (((Y + (OffsetY * 2)) / 56) - 73.0) * -1;
|
const minLat = (((Y + (OffsetY * 2)) / 56) - 73.0) * -1;
|
||||||
const minLon = (((X * -1) / 25) + 175.0) * -1;
|
const minLon = (((X * -1) / 25) + 175.0) * -1;
|
||||||
const maxLon = ((((X + (OffsetX * 2)) * -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 maxLat = ((Y / 55.2) - 25) * -1;
|
||||||
const minLat = (((Y + (OffsetY * 2)) / 55.2) - 25) * -1;
|
const minLat = (((Y + (OffsetY * 2)) / 55.2) - 25) * -1;
|
||||||
const minLon = (((X * -1) / 41.775) + 164.5) * -1;
|
const minLon = (((X * -1) / 41.775) + 164.5) * -1;
|
||||||
const maxLon = ((((X + (OffsetX * 2)) * -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) {
|
getXYForCity(City, MaxLatitude, MinLongitude, state) {
|
||||||
if (state === 'AK') this.getXYForCityAK(...arguments);
|
if (state === 'AK') this.getXYForCityAK(City, MaxLatitude, MinLongitude);
|
||||||
if (state === 'HI') this.getXYForCityHI(...arguments);
|
if (state === 'HI') this.getXYForCityHI(City, MaxLatitude, MinLongitude);
|
||||||
let x = (City.lon - MinLongitude) * 57;
|
let x = (City.lon - MinLongitude) * 57;
|
||||||
let y = (MaxLatitude - City.lat) * 70;
|
let y = (MaxLatitude - City.lat) * 70;
|
||||||
|
|
||||||
|
@ -292,7 +298,7 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
return { x, y };
|
return { x, y };
|
||||||
}
|
}
|
||||||
|
|
||||||
getXYForCityAK (City, MaxLatitude, MinLongitude) {
|
static getXYForCityAK(City, MaxLatitude, MinLongitude) {
|
||||||
let x = (City.lon - MinLongitude) * 37;
|
let x = (City.lon - MinLongitude) * 37;
|
||||||
let y = (MaxLatitude - City.lat) * 70;
|
let y = (MaxLatitude - City.lat) * 70;
|
||||||
|
|
||||||
|
@ -304,7 +310,7 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
return { x, y };
|
return { x, y };
|
||||||
}
|
}
|
||||||
|
|
||||||
getXYForCityHI (City, MaxLatitude, MinLongitude) {
|
static getXYForCityHI(City, MaxLatitude, MinLongitude) {
|
||||||
let x = (City.lon - MinLongitude) * 57;
|
let x = (City.lon - MinLongitude) * 57;
|
||||||
let y = (MaxLatitude - City.lat) * 70;
|
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
|
// to fit on the map, remove anything after punctuation and then limit to 15 characters
|
||||||
formatCity(city) {
|
static formatCity(city) {
|
||||||
return city.match(/[^-;/\\,]*/)[0].substr(0,12);
|
return city.match(/[^-;/\\,]*/)[0].substr(0, 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
async drawCanvas() {
|
async drawCanvas() {
|
||||||
super.drawCanvas();
|
super.drawCanvas();
|
||||||
// break up data into useful values
|
// 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
|
// fixed offset for all y values when drawing to the map
|
||||||
const mapYOff = 90;
|
const mapYOff = 90;
|
||||||
|
|
||||||
const {DateTime} = luxon;
|
const { DateTime } = luxon;
|
||||||
// draw the header graphics
|
// draw the header graphics
|
||||||
this.context.drawImage(await this.backgroundImage, 0, 0);
|
this.context.drawImage(await this.backgroundImage, 0, 0);
|
||||||
draw.horizontalGradientSingle(this.context, 0, 30, 500, 90, draw.topColor1, draw.topColor2);
|
draw.horizontalGradientSingle(this.context, 0, 30, 500, 90, draw.topColor1, draw.topColor2);
|
||||||
|
@ -340,21 +346,21 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
if (this.screenIndex === 0) {
|
if (this.screenIndex === 0) {
|
||||||
draw.titleText(this.context, 'Regional', 'Observations');
|
draw.titleText(this.context, 'Regional', 'Observations');
|
||||||
} else {
|
} 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
|
// get the name of the day
|
||||||
const dayName = forecastDate.toLocaleString({weekday: 'long'});
|
const dayName = forecastDate.toLocaleString({ weekday: 'long' });
|
||||||
// draw the title
|
// draw the title
|
||||||
if (data[0][this.screenIndex].daytime) {
|
if (data[0][this.screenIndex].daytime) {
|
||||||
draw.titleText(this.context, 'Forecast for', dayName);
|
draw.titleText(this.context, 'Forecast for', dayName);
|
||||||
} else {
|
} else {
|
||||||
draw.titleText(this.context, 'Forecast for', dayName + ' Night');
|
draw.titleText(this.context, 'Forecast for', `${dayName} Night`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw the map
|
// draw the map
|
||||||
this.context.drawImage(await this.baseMap, sourceXY.x, sourceXY.y, (offsetXY.x * 2), (offsetXY.y * 2), 0, mapYOff, 640, 312);
|
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];
|
const period = city[this.screenIndex];
|
||||||
// draw the icon if possible
|
// draw the icon if possible
|
||||||
const icon = icons.getWeatherRegionalIconFromIconLink(period.icon, !period.daytime);
|
const icon = icons.getWeatherRegionalIconFromIconLink(period.icon, !period.daytime);
|
||||||
|
@ -365,20 +371,19 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
auto_play: true,
|
auto_play: true,
|
||||||
canvas: this.canvas,
|
canvas: this.canvas,
|
||||||
x: period.x,
|
x: period.x,
|
||||||
y: period.y - 15+mapYOff,
|
y: period.y - 15 + mapYOff,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// City Name
|
// 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
|
// Temperature
|
||||||
let temperature = period.temperature;
|
let { temperature } = period;
|
||||||
if (navigation.units() === UNITS.metric) temperature = Math.round(utils.units.fahrenheitToCelsius(temperature));
|
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();
|
this.finishDraw();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
// travel forecast display
|
// 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
|
// eslint-disable-next-line no-unused-vars
|
||||||
class TravelForecast extends WeatherDisplay {
|
class TravelForecast extends WeatherDisplay {
|
||||||
|
@ -15,15 +15,15 @@ class TravelForecast extends WeatherDisplay {
|
||||||
// set up the timing
|
// set up the timing
|
||||||
this.timing.baseDelay = 20;
|
this.timing.baseDelay = 20;
|
||||||
// page sizes are 4 cities, calculate the number of pages necessary plus overflow
|
// 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 pages = Math.floor(pagesFloat) - 2; // first page is already displayed, last page doesn't happen
|
||||||
const extra = pages%1;
|
const extra = pages % 1;
|
||||||
const timingStep = this.cityHeight*4;
|
const timingStep = this.cityHeight * 4;
|
||||||
this.timing.delay = [150+timingStep];
|
this.timing.delay = [150 + timingStep];
|
||||||
// add additional pages
|
// 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)
|
// 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
|
// add the final 3 second delay
|
||||||
this.timing.delay.push(150);
|
this.timing.delay.push(150);
|
||||||
}
|
}
|
||||||
|
@ -31,25 +31,25 @@ class TravelForecast extends WeatherDisplay {
|
||||||
async getData() {
|
async getData() {
|
||||||
// super checks for enabled
|
// super checks for enabled
|
||||||
if (!super.getData()) return;
|
if (!super.getData()) return;
|
||||||
const forecastPromises = _TravelCities.map(async city => {
|
const forecastPromises = TravelCities.map(async (city) => {
|
||||||
try {
|
try {
|
||||||
// get point then forecast
|
// get point then forecast
|
||||||
const point = await utils.weather.getPoint(city.Latitude, city.Longitude);
|
const point = await utils.weather.getPoint(city.Latitude, city.Longitude);
|
||||||
const forecast = await utils.fetch.json(point.properties.forecast);
|
const forecast = await utils.fetch.json(point.properties.forecast);
|
||||||
// determine today or tomorrow (shift periods by 1 if tomorrow)
|
// 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 a pared-down forecast
|
||||||
return {
|
return {
|
||||||
today: todayShift === 0,
|
today: todayShift === 0,
|
||||||
high: forecast.properties.periods[todayShift].temperature,
|
high: forecast.properties.periods[todayShift].temperature,
|
||||||
low: forecast.properties.periods[todayShift+1].temperature,
|
low: forecast.properties.periods[todayShift + 1].temperature,
|
||||||
name: city.Name,
|
name: city.Name,
|
||||||
icon: icons.getWeatherRegionalIconFromIconLink(forecast.properties.periods[todayShift].icon),
|
icon: icons.getWeatherRegionalIconFromIconLink(forecast.properties.periods[todayShift].icon),
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`GetTravelWeather for ${city.Name} failed`);
|
console.error(`GetTravelWeather for ${city.Name} failed`);
|
||||||
console.error(e.status, e.responseJSON);
|
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;
|
this.data = forecasts;
|
||||||
|
|
||||||
// test for some data available in at least one forecast
|
// test for some data available in at least one forecast
|
||||||
const hasData = this.data.reduce((acc,forecast) => acc || forecast.high, false);
|
const hasData = this.data.reduce((acc, forecast) => acc || forecast.high, false);
|
||||||
if (!hasData) {
|
if (!hasData) {
|
||||||
this.setStatus(STATUS.noData);
|
this.setStatus(STATUS.noData);
|
||||||
return;
|
return;
|
||||||
|
@ -68,7 +68,7 @@ class TravelForecast extends WeatherDisplay {
|
||||||
this.drawLongCanvas();
|
this.drawLongCanvas();
|
||||||
}
|
}
|
||||||
|
|
||||||
async drawLongCanvas () {
|
async drawLongCanvas() {
|
||||||
// create the "long" canvas if necessary
|
// create the "long" canvas if necessary
|
||||||
if (!this.longCanvas) {
|
if (!this.longCanvas) {
|
||||||
this.longCanvas = document.createElement('canvas');
|
this.longCanvas = document.createElement('canvas');
|
||||||
|
@ -79,7 +79,7 @@ class TravelForecast extends WeatherDisplay {
|
||||||
}
|
}
|
||||||
|
|
||||||
// stop all gifs
|
// stop all gifs
|
||||||
this.longCanvasGifs.forEach(gif => gif.pause());
|
this.longCanvasGifs.forEach((gif) => gif.pause());
|
||||||
// delete the gifs
|
// delete the gifs
|
||||||
this.longCanvasGifs.length = 0;
|
this.longCanvasGifs.length = 0;
|
||||||
|
|
||||||
|
@ -87,23 +87,23 @@ class TravelForecast extends WeatherDisplay {
|
||||||
const cities = this.data;
|
const cities = this.data;
|
||||||
|
|
||||||
// clean up existing gifs
|
// clean up existing gifs
|
||||||
this.gifs.forEach(gif => gif.pause());
|
this.gifs.forEach((gif) => gif.pause());
|
||||||
// delete the gifs
|
// delete the gifs
|
||||||
this.gifs.length = 0;
|
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 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;
|
const y = i * 346;
|
||||||
draw.horizontalGradient(this.longContext, 0, y, 640, y + 346, '#102080', '#001040');
|
draw.horizontalGradient(this.longContext, 0, y, 640, y + 346, '#102080', '#001040');
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(cities.map(async (city, index) => {
|
await Promise.all(cities.map(async (city, index) => {
|
||||||
// calculate base y value
|
// calculate base y value
|
||||||
const y = 50+this.cityHeight*index;
|
const y = 50 + this.cityHeight * index;
|
||||||
|
|
||||||
// city name
|
// city name
|
||||||
draw.text(this.longContext, 'Star4000 Large Compressed', '24pt', '#FFFF00', 80, y, city.name, 2);
|
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
|
// check for forecast data
|
||||||
if (city.icon) {
|
if (city.icon) {
|
||||||
// get temperatures and convert if necessary
|
// get temperatures and convert if necessary
|
||||||
let {low, high} = city;
|
let { low, high } = city;
|
||||||
|
|
||||||
if (navigation.units() === UNITS.metric) {
|
if (navigation.units() === UNITS.metric) {
|
||||||
low = utils.units.fahrenheitToCelsius(low);
|
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 - 18, 'NO TRAVEL', 2);
|
||||||
draw.text(this.longContext, 'Star4000 Small', '24pt', '#FFFFFF', 400, y, 'DATA AVAILABLE', 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.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.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', 455, 105, 'LOW', 2);
|
||||||
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFF00', 510, 105, 'HIGH', 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();
|
const longCanvas = this.getLongCanvas();
|
||||||
|
|
||||||
// calculate scroll offset and don't go past end
|
// 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
|
// don't let offset go negative
|
||||||
if (offsetY < 0) offsetY = 0;
|
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);
|
this.context.drawImage(longCanvas, 0, offsetY, 640, 289, 0, 110, 640, 289);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTravelCitiesDayName(cities) {
|
static getTravelCitiesDayName(cities) {
|
||||||
const {DateTime} = luxon;
|
const { DateTime } = luxon;
|
||||||
// effectively returns early on the first found date
|
// effectively returns early on the first found date
|
||||||
return cities.reduce((dayName, city) => {
|
return cities.reduce((dayName, city) => {
|
||||||
if (city && dayName === '') {
|
if (city && dayName === '') {
|
||||||
// today or tomorrow
|
// 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 the day
|
||||||
return day.toLocaleString({weekday: 'long'});
|
return day.toLocaleString({ weekday: 'long' });
|
||||||
}
|
}
|
||||||
return dayName;
|
return dayName;
|
||||||
}, '');
|
}, '');
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
'use strict';
|
|
||||||
// radar utilities
|
// radar utilities
|
||||||
|
|
||||||
/* globals SuperGif */
|
/* globals SuperGif */
|
||||||
|
@ -17,29 +16,25 @@ const utils = (() => {
|
||||||
|
|
||||||
// ****************************** load images *********************************
|
// ****************************** load images *********************************
|
||||||
// load an image from a blob or url
|
// load an image from a blob or url
|
||||||
const loadImg = (imgData, cors = false) => {
|
const loadImg = (imgData, cors = false) => new Promise((resolve) => {
|
||||||
return new Promise(resolve => {
|
const img = new Image();
|
||||||
const img = new Image();
|
img.onload = (e) => {
|
||||||
img.onload = (e) => {
|
resolve(e.target);
|
||||||
resolve(e.target);
|
};
|
||||||
};
|
if (imgData instanceof Blob) {
|
||||||
if (imgData instanceof Blob) {
|
img.src = window.URL.createObjectURL(imgData);
|
||||||
img.src = window.URL.createObjectURL(imgData);
|
} else {
|
||||||
} else {
|
let url = imgData;
|
||||||
let url = imgData;
|
if (cors) url = rewriteUrl(imgData);
|
||||||
if (cors) url = rewriteUrl(imgData);
|
img.src = url;
|
||||||
img.src = url;
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// async version of SuperGif
|
// async version of SuperGif
|
||||||
const superGifAsync = (e) => {
|
const superGifAsync = (e) => new Promise((resolve) => {
|
||||||
return new Promise(resolve => {
|
const gif = new SuperGif(e);
|
||||||
const gif = new SuperGif(e);
|
gif.load(() => resolve(gif));
|
||||||
gif.load(() => resolve(gif));
|
});
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// preload an image
|
// 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
|
// 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;
|
context.imageSmoothingEnabled = false;
|
||||||
|
|
||||||
// draw the image
|
// draw the image
|
||||||
context.drawImage(img, 0,0);
|
context.drawImage(img, 0, 0);
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
// *********************************** unit conversions ***********************
|
// *********************************** 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 mphToKph = (Mph) => Math.round(Mph * 1.60934);
|
||||||
const kphToMph = (Kph) => Math.round(Kph / 1.60934);
|
const kphToMph = (Kph) => Math.round(Kph / 1.60934);
|
||||||
const celsiusToFahrenheit = (Celsius) => Math.round(Celsius * 9 / 5 + 32);
|
const celsiusToFahrenheit = (Celsius) => Math.round((Celsius * 9) / 5 + 32);
|
||||||
const fahrenheitToCelsius = (Fahrenheit) => Math.round2(((Fahrenheit) - 32) * 5 / 9, 1);
|
const fahrenheitToCelsius = (Fahrenheit) => Math.round2((((Fahrenheit) - 32) * 5) / 9, 1);
|
||||||
const milesToKilometers = (Miles) => Math.round(Miles * 1.60934);
|
const milesToKilometers = (Miles) => Math.round(Miles * 1.60934);
|
||||||
const kilometersToMiles = (Kilometers) => Math.round(Kilometers / 1.60934);
|
const kilometersToMiles = (Kilometers) => Math.round(Kilometers / 1.60934);
|
||||||
const feetToMeters = (Feet) => Math.round(Feet * 0.3048);
|
const feetToMeters = (Feet) => Math.round(Feet * 0.3048);
|
||||||
const metersToFeet = (Meters) => Math.round(Meters / 0.3048);
|
const metersToFeet = (Meters) => Math.round(Meters / 0.3048);
|
||||||
const inchesToCentimeters = (Inches) => Math.round2(Inches * 2.54, 2);
|
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 **********************************
|
// ***************************** calculations **********************************
|
||||||
|
|
||||||
|
@ -125,7 +120,7 @@ const utils = (() => {
|
||||||
const T = Temperature;
|
const T = Temperature;
|
||||||
const V = WindSpeed;
|
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
|
// wind direction
|
||||||
|
@ -135,20 +130,22 @@ const utils = (() => {
|
||||||
return arr[(val % 16)];
|
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
|
// wrap a number to 0-m
|
||||||
const wrap = (x,m) => (x%m + m)%m;
|
const wrap = (x, m) => ((x % m) + m) % m;
|
||||||
|
|
||||||
// ********************************* strings *********************************************
|
// ********************************* strings *********************************************
|
||||||
const wordWrap = (str, ...rest) => {
|
const wordWrap = (str = '', ...rest) => {
|
||||||
let m = ((rest.length >= 1) ? rest[0] : 75);
|
const m = ((rest.length >= 1) ? rest[0] : 75);
|
||||||
let b = ((rest.length >= 2) ? rest[1] : '\n');
|
const b = ((rest.length >= 2) ? rest[1] : '\n');
|
||||||
let c = ((rest.length >= 3) ? rest[2] : false);
|
const c = ((rest.length >= 3) ? rest[2] : false);
|
||||||
|
|
||||||
let i, j, l, s, r;
|
let i;
|
||||||
|
let j;
|
||||||
str += '';
|
let l;
|
||||||
|
let s;
|
||||||
|
let r;
|
||||||
|
|
||||||
if (m < 1) {
|
if (m < 1) {
|
||||||
return str;
|
return str;
|
||||||
|
@ -160,8 +157,8 @@ const utils = (() => {
|
||||||
r[i] += s.slice(0, j) + ((s = s.slice(j)).length ? b : '')) {
|
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]
|
j = c === 2 || (j = s.slice(0, m + 1).match(/\S*(\s)?$/))[1]
|
||||||
? m
|
? m
|
||||||
: j.input.length - j[0].length || c === true && 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 = s.slice(m).match(/^\S*/))[0].length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +166,8 @@ const utils = (() => {
|
||||||
};
|
};
|
||||||
// ********************************* cors ********************************************
|
// ********************************* cors ********************************************
|
||||||
// rewrite some urls for local server
|
// 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://api.weather.gov/', window.location.href);
|
||||||
url = url.replace('https://radar.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);
|
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 raw = (url, params) => fetchAsync(url, '', params);
|
||||||
const blob = (url, params) => fetchAsync(url, 'blob', params);
|
const blob = (url, params) => fetchAsync(url, 'blob', params);
|
||||||
|
|
||||||
const fetchAsync = async (_url, responseType, _params={}) => {
|
const fetchAsync = async (_url, responseType, _params = {}) => {
|
||||||
// combine default and provided parametersutils
|
// combine default and provided parameters
|
||||||
const params = Object.assign({}, {
|
const params = {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
},
|
..._params,
|
||||||
_params);
|
};
|
||||||
// build a url, including the rewrite for cors if necessary
|
// build a url, including the rewrite for cors if necessary
|
||||||
let corsUrl = _url;
|
let corsUrl = _url;
|
||||||
if (params.cors === true) corsUrl = rewriteUrl(_url);
|
if (params.cors === true) corsUrl = rewriteUrl(_url);
|
||||||
|
@ -212,11 +210,11 @@ const utils = (() => {
|
||||||
// return the requested response
|
// return the requested response
|
||||||
switch (responseType) {
|
switch (responseType) {
|
||||||
case 'json':
|
case 'json':
|
||||||
return await response.json();
|
return response.json();
|
||||||
case 'text':
|
case 'text':
|
||||||
return await response.text();
|
return response.text();
|
||||||
case 'blob':
|
case 'blob':
|
||||||
return await response.blob();
|
return response.blob();
|
||||||
default:
|
default:
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ class WeatherDisplay {
|
||||||
this.gifs = [];
|
this.gifs = [];
|
||||||
this.data = undefined;
|
this.data = undefined;
|
||||||
this.loadingStatus = STATUS.loading;
|
this.loadingStatus = STATUS.loading;
|
||||||
this.name = name?name:elemId;
|
this.name = name ?? elemId;
|
||||||
this.getDataCallbacks = [];
|
this.getDataCallbacks = [];
|
||||||
|
|
||||||
// default navigation timing
|
// default navigation timing
|
||||||
|
@ -59,7 +59,7 @@ class WeatherDisplay {
|
||||||
// create a checkbox in the selected displays area
|
// create a checkbox in the selected displays area
|
||||||
const checkbox = document.createElement('template');
|
const checkbox = document.createElement('template');
|
||||||
checkbox.innerHTML = `<label for="${this.elemId}Enabled">
|
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>`;
|
${this.name}</label>`;
|
||||||
checkbox.content.firstChild.addEventListener('change', (e) => this.checkboxChange(e));
|
checkbox.content.firstChild.addEventListener('change', (e) => this.checkboxChange(e));
|
||||||
const availableDisplays = document.getElementById('enabledDisplays');
|
const availableDisplays = document.getElementById('enabledDisplays');
|
||||||
|
@ -99,7 +99,7 @@ class WeatherDisplay {
|
||||||
|
|
||||||
// create a canvas
|
// create a canvas
|
||||||
const canvas = document.createElement('template');
|
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
|
// add to the page
|
||||||
const container = document.getElementById('container');
|
const container = document.getElementById('container');
|
||||||
|
@ -130,23 +130,22 @@ class WeatherDisplay {
|
||||||
// return any data requested before it was available
|
// return any data requested before it was available
|
||||||
getDataCallback() {
|
getDataCallback() {
|
||||||
// call each callback
|
// call each callback
|
||||||
this.getDataCallbacks.forEach(fxn => fxn(this.data));
|
this.getDataCallbacks.forEach((fxn) => fxn(this.data));
|
||||||
// clear the callbacks
|
// clear the callbacks
|
||||||
this.getDataCallbacks = [];
|
this.getDataCallbacks = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
drawCanvas() {
|
drawCanvas() {
|
||||||
// stop all gifs
|
// stop all gifs
|
||||||
this.gifs.forEach(gif => gif.pause());
|
this.gifs.forEach((gif) => gif.pause());
|
||||||
// delete the gifs
|
// delete the gifs
|
||||||
this.gifs.length = 0;
|
this.gifs.length = 0;
|
||||||
// refresh the canvas
|
// refresh the canvas
|
||||||
this.canvas = document.getElementById(this.elemId+'Canvas');
|
this.canvas = document.getElementById(`${this.elemId}Canvas`);
|
||||||
this.context = this.canvas.getContext('2d');
|
this.context = this.canvas.getContext('2d');
|
||||||
|
|
||||||
// clean up the first-run flag in screen index
|
// clean up the first-run flag in screen index
|
||||||
if (this.screenIndex < 0)
|
if (this.screenIndex < 0) this.screenIndex = 0;
|
||||||
this.screenIndex = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
finishDraw() {
|
finishDraw() {
|
||||||
|
@ -155,7 +154,7 @@ class WeatherDisplay {
|
||||||
let OkToDrawCurrentDateTime = true;
|
let OkToDrawCurrentDateTime = true;
|
||||||
let OkToDrawLogoImage = true;
|
let OkToDrawLogoImage = true;
|
||||||
// let OkToDrawCustomScrollText = false;
|
// let OkToDrawCustomScrollText = false;
|
||||||
let bottom = undefined;
|
let bottom;
|
||||||
|
|
||||||
// visibility tests
|
// visibility tests
|
||||||
// if (_ScrollText !== '') OkToDrawCustomScrollText = true;
|
// if (_ScrollText !== '') OkToDrawCustomScrollText = true;
|
||||||
|
@ -201,7 +200,7 @@ class WeatherDisplay {
|
||||||
drawCurrentDateTime(bottom) {
|
drawCurrentDateTime(bottom) {
|
||||||
// only draw if canvas is active to conserve battery
|
// only draw if canvas is active to conserve battery
|
||||||
if (!this.isActive()) return;
|
if (!this.isActive()) return;
|
||||||
const {DateTime} = luxon;
|
const { DateTime } = luxon;
|
||||||
const font = 'Star4000 Small';
|
const font = 'Star4000 Small';
|
||||||
const size = '24pt';
|
const size = '24pt';
|
||||||
const color = '#ffffff';
|
const color = '#ffffff';
|
||||||
|
@ -226,10 +225,10 @@ class WeatherDisplay {
|
||||||
// Get the current date and time.
|
// Get the current date and time.
|
||||||
const now = DateTime.local();
|
const now = DateTime.local();
|
||||||
|
|
||||||
//time = "11:35:08 PM";
|
// time = "11:35:08 PM";
|
||||||
const time = now.toLocaleString(DateTime.TIME_WITH_SECONDS).padStart(11,' ');
|
const time = now.toLocaleString(DateTime.TIME_WITH_SECONDS).padStart(11, ' ');
|
||||||
|
|
||||||
let x,y;
|
let x; let y;
|
||||||
if (bottom) {
|
if (bottom) {
|
||||||
x = 400;
|
x = 400;
|
||||||
y = 402;
|
y = 402;
|
||||||
|
@ -241,9 +240,9 @@ class WeatherDisplay {
|
||||||
x += 45;
|
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) {
|
if (bottom) {
|
||||||
x = 55;
|
x = 55;
|
||||||
|
@ -255,7 +254,7 @@ class WeatherDisplay {
|
||||||
draw.text(this.context, font, size, color, x, y, date.toUpperCase(), shadow);
|
draw.text(this.context, font, size, color, x, y, date.toUpperCase(), shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
async drawNoaaImage () {
|
async drawNoaaImage() {
|
||||||
// load the image and store locally
|
// load the image and store locally
|
||||||
if (!this.drawNoaaImage.image) {
|
if (!this.drawNoaaImage.image) {
|
||||||
this.drawNoaaImage.image = utils.image.load('images/noaa5.gif');
|
this.drawNoaaImage.image = utils.image.load('images/noaa5.gif');
|
||||||
|
@ -265,7 +264,7 @@ class WeatherDisplay {
|
||||||
this.context.drawImage(img, 356, 39);
|
this.context.drawImage(img, 356, 39);
|
||||||
}
|
}
|
||||||
|
|
||||||
async drawLogoImage () {
|
async drawLogoImage() {
|
||||||
// load the image and store locally
|
// load the image and store locally
|
||||||
if (!this.drawLogoImage.image) {
|
if (!this.drawLogoImage.image) {
|
||||||
this.drawLogoImage.image = utils.image.load('images/Logo3.png');
|
this.drawLogoImage.image = utils.image.load('images/Logo3.png');
|
||||||
|
@ -287,10 +286,9 @@ class WeatherDisplay {
|
||||||
|
|
||||||
// show the canvas
|
// show the canvas
|
||||||
this.canvas.style.display = 'block';
|
this.canvas.style.display = 'block';
|
||||||
|
return false;
|
||||||
// stop if timing has been disabled
|
|
||||||
if (!this.timing) return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hideCanvas() {
|
hideCanvas() {
|
||||||
this.resetNavBaseCount();
|
this.resetNavBaseCount();
|
||||||
|
|
||||||
|
@ -299,7 +297,7 @@ class WeatherDisplay {
|
||||||
}
|
}
|
||||||
|
|
||||||
isActive() {
|
isActive() {
|
||||||
return document.getElementById(this.elemId+'Canvas').offsetParent !== null;
|
return document.getElementById(`${this.elemId}Canvas`).offsetParent !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
isEnabled() {
|
isEnabled() {
|
||||||
|
@ -318,7 +316,7 @@ class WeatherDisplay {
|
||||||
// see if play is active and screen is active
|
// see if play is active and screen is active
|
||||||
if (!navigation.isPlaying() || !this.isActive()) return;
|
if (!navigation.isPlaying() || !this.isActive()) return;
|
||||||
// increment the base count
|
// increment the base count
|
||||||
this.navBaseCount++;
|
this.navBaseCount += 1;
|
||||||
|
|
||||||
// call base count change if available for this function
|
// call base count change if available for this function
|
||||||
if (this.baseCountChange) this.baseCountChange(this.navBaseCount);
|
if (this.baseCountChange) this.baseCountChange(this.navBaseCount);
|
||||||
|
@ -329,7 +327,7 @@ class WeatherDisplay {
|
||||||
|
|
||||||
async updateScreenFromBaseCount() {
|
async updateScreenFromBaseCount() {
|
||||||
// get the next screen index
|
// get the next screen index
|
||||||
let nextScreenIndex = this.screenIndexFromBaseCount();
|
const nextScreenIndex = this.screenIndexFromBaseCount();
|
||||||
|
|
||||||
// special cases for first and last frame
|
// special cases for first and last frame
|
||||||
// must compare with false as nextScreenIndex could be 0 which is valid
|
// 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
|
// if the delay is provided as a single value, expand it to a series of the same value
|
||||||
let intermediateDelay = [];
|
let intermediateDelay = [];
|
||||||
if (typeof this.timing.delay === 'number') {
|
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 {
|
} else {
|
||||||
// map just the delays to the intermediate block
|
// 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;
|
if (typeof delay === 'object') return delay.time;
|
||||||
return delay;
|
return delay;
|
||||||
});
|
});
|
||||||
|
@ -379,7 +377,7 @@ class WeatherDisplay {
|
||||||
|
|
||||||
// calculate the cumulative end point of each delay
|
// calculate the cumulative end point of each delay
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
this.timing.fullDelay = intermediateDelay.map(val => {
|
this.timing.fullDelay = intermediateDelay.map((val) => {
|
||||||
const calc = sum + val;
|
const calc = sum + val;
|
||||||
sum += val;
|
sum += val;
|
||||||
return calc;
|
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
|
// 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') {
|
if (Array.isArray(this.timing.delay) && typeof this.timing.delay[0] === 'object') {
|
||||||
// extract screen indexes from objects
|
// 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 {
|
} else {
|
||||||
// generate sequential screen indexes
|
// generate sequential screen indexes
|
||||||
this.timing.screenIndexes = [];
|
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();
|
this.resetNavBaseCount();
|
||||||
} else {
|
} else {
|
||||||
// set the base count to the next available frame
|
// 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.navBaseCount = newBaseCount;
|
||||||
}
|
}
|
||||||
this.updateScreenFromBaseCount();
|
this.updateScreenFromBaseCount();
|
||||||
|
@ -413,13 +411,13 @@ class WeatherDisplay {
|
||||||
navPrev(command) {
|
navPrev(command) {
|
||||||
// check for special 'last frame' command
|
// check for special 'last frame' command
|
||||||
if (command === navigation.msg.command.lastFrame) {
|
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 {
|
} else {
|
||||||
// find the highest fullDelay that is less than the current base count
|
// find the highest fullDelay that is less than the current base count
|
||||||
const newBaseCount = this.timing.fullDelay.reduce((acc, delay) => {
|
const newBaseCount = this.timing.fullDelay.reduce((acc, delay) => {
|
||||||
if (delay < this.navBaseCount) return delay;
|
if (delay < this.navBaseCount) return delay;
|
||||||
return acc;
|
return acc;
|
||||||
},0);
|
}, 0);
|
||||||
// if the new base count is zero then we're already at the first screen
|
// if the new base count is zero then we're already at the first screen
|
||||||
if (newBaseCount === 0 && this.navBaseCount === 0) {
|
if (newBaseCount === 0 && this.navBaseCount === 0) {
|
||||||
this.sendNavDisplayMessage(navigation.msg.response.previous);
|
this.sendNavDisplayMessage(navigation.msg.response.previous);
|
||||||
|
@ -436,14 +434,14 @@ class WeatherDisplay {
|
||||||
if (!this.timing) return 0;
|
if (!this.timing) return 0;
|
||||||
// find the first timing in the timing array that is greater than the base count
|
// find the first timing in the timing array that is greater than the base count
|
||||||
if (this.timing && !this.timing.fullDelay) this.calcNavTiming();
|
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;
|
if (timingIndex === -1) return false;
|
||||||
return this.timing.screenIndexes[timingIndex];
|
return this.timing.screenIndexes[timingIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
// start and stop base counter
|
// start and stop base counter
|
||||||
startNavCount() {
|
startNavCount() {
|
||||||
if (!this.navInterval) this.navInterval = setInterval(()=>this.navBaseTime(), this.timing.baseDelay);
|
if (!this.navInterval) this.navInterval = setInterval(() => this.navBaseTime(), this.timing.baseDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
resetNavBaseCount() {
|
resetNavBaseCount() {
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
module.exports = '3.7.1';
|
module.exports = '3.8.0';
|
|
@ -35,5 +35,6 @@
|
||||||
"**/vendor/auto/**",
|
"**/vendor/auto/**",
|
||||||
"**/twc3.js",
|
"**/twc3.js",
|
||||||
],
|
],
|
||||||
|
"editor.tabSize": 2,
|
||||||
},
|
},
|
||||||
}
|
}
|
Loading…
Reference in a new issue