Merge branch 'html'
This commit is contained in:
commit
33570a8030
10
.vscode/launch.json
vendored
10
.vscode/launch.json
vendored
|
@ -4,18 +4,17 @@
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "Frontend",
|
"name": "Frontend",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"type": "pwa-chrome",
|
"type": "chrome",
|
||||||
"url": "http://localhost:8080",
|
"url": "http://localhost:8080",
|
||||||
"webRoot": "${workspaceFolder}/server",
|
"webRoot": "${workspaceFolder}/server",
|
||||||
"skipFiles": [
|
"skipFiles": [
|
||||||
"<node_internals>/**",
|
"<node_internals>/**",
|
||||||
"**/*.min.js",
|
"**/*.min.js",
|
||||||
"**/vendor/**"
|
"**/vendor/**"
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Data:stations",
|
"name": "Data:stations",
|
||||||
|
@ -40,7 +39,10 @@
|
||||||
"compounds": [
|
"compounds": [
|
||||||
{
|
{
|
||||||
"name": "Compound",
|
"name": "Compound",
|
||||||
"configurations": ["Frontend", "Server"]
|
"configurations": [
|
||||||
|
"Frontend",
|
||||||
|
"Server"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
18
.vscode/settings.json
vendored
18
.vscode/settings.json
vendored
|
@ -1,5 +1,21 @@
|
||||||
{
|
{
|
||||||
"cSpell.enableFiletypes": [
|
"cSpell.enableFiletypes": [
|
||||||
"javascript"
|
"javascript"
|
||||||
]
|
],
|
||||||
|
"liveSassCompile.settings.formats": [
|
||||||
|
{
|
||||||
|
"format": "compressed",
|
||||||
|
"extensionName": ".css",
|
||||||
|
"savePath": "/server/styles",
|
||||||
|
"savePathSegmentKeys": null,
|
||||||
|
"savePathReplaceSegmentsWith": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"search.exclude": {
|
||||||
|
"**/node_modules": true,
|
||||||
|
"**/bower_components": true,
|
||||||
|
"**/*.code-search": true,
|
||||||
|
"**/compiled.css": true,
|
||||||
|
"**/*.min.js": true,
|
||||||
|
},
|
||||||
}
|
}
|
14
README.md
14
README.md
|
@ -22,10 +22,24 @@ There are a lot of CORS considerations and issues with api.weather.gov that are
|
||||||
```
|
```
|
||||||
git clone https://github.com/netbymatt/ws4kp.git
|
git clone https://github.com/netbymatt/ws4kp.git
|
||||||
cd ws4kp
|
cd ws4kp
|
||||||
|
npm i
|
||||||
node index.js
|
node index.js
|
||||||
```
|
```
|
||||||
Open your web browser: http://localhost:8080/
|
Open your web browser: http://localhost:8080/
|
||||||
|
|
||||||
|
## Updates in 5.0
|
||||||
|
The change to 5.0 changes from drawing the weather graphics on canvas elements and instead uses HTML and CSS to style all of the weather graphics. A lot of other changes and fixes were implemented at the same time.
|
||||||
|
|
||||||
|
* Replace all canvas elements with HTML and CSS
|
||||||
|
* City and airport names are better parsed to only show the city name.
|
||||||
|
* Remove the dependency on libgif-js
|
||||||
|
* Use browser for text wrapping where necessary
|
||||||
|
* Some new weather icons
|
||||||
|
* Refresh only on slideshow repeat
|
||||||
|
* Removed Almanac 30-day outlook
|
||||||
|
* Fixed startup issue when current conditions are unavailable
|
||||||
|
*
|
||||||
|
|
||||||
## Why the fork?
|
## Why the fork?
|
||||||
|
|
||||||
The fork is a result of wanting a more manageable, modern code base to work with. Part of it is an exercise in my education in JavaScript. There are several technical changes that were made behind the scenes.
|
The fork is a result of wanting a more manageable, modern code base to work with. Part of it is an exercise in my education in JavaScript. There are several technical changes that were made behind the scenes.
|
||||||
|
|
390
dist/index.html
vendored
390
dist/index.html
vendored
|
@ -1 +1,389 @@
|
||||||
<!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?_=4.1.5"><script type="text/javascript" src="resources/data.min.js?_=4.1.5"></script><script type="text/javascript" src="resources/ws.min.js?_=4.1.5"></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">4.1.5</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">
|
||||||
|
<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?_=5.0.0">
|
||||||
|
<script type="text/javascript" src="resources/data.min.js?_=5.0.0"></script>
|
||||||
|
<script type="text/javascript" src="resources/ws.min.js?_=5.0.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">5.0.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 id="progress-html" class="weather-display">
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo"><img src="images/Logo3.png"></div>
|
||||||
|
<div class="title dual">
|
||||||
|
<div class="top">WeatherStar</div>
|
||||||
|
<div class="bottom">4000+ v5.0.0</div>
|
||||||
|
</div>
|
||||||
|
<div class="date-time date"></div>
|
||||||
|
<div class="date-time time"></div>
|
||||||
|
</div>
|
||||||
|
<div class="main has-box progress">
|
||||||
|
<div class="container">
|
||||||
|
<div class="item template">
|
||||||
|
<div class="name">Current Conditions</div>
|
||||||
|
<div class="links loading">
|
||||||
|
<div class="loading">Loading</div>
|
||||||
|
<div class="press-here">Press Here</div>
|
||||||
|
<div class="failed">Failed</div>
|
||||||
|
<div class="no-data">No Data</div>
|
||||||
|
<div class="disabled">Disabled</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="scroll">
|
||||||
|
<div class="progress-bar-container show">
|
||||||
|
<div class="progress-bar"></div>
|
||||||
|
<div class="cover"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="hourly-html" class="weather-display">
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo"><img src="images/Logo3.png"></div>
|
||||||
|
<div class="title single">Hourly Forecast</div>
|
||||||
|
<div class="date-time date"></div>
|
||||||
|
<div class="date-time time"></div>
|
||||||
|
</div>
|
||||||
|
<div class="main has-scroll hourly">
|
||||||
|
<div class="column-headers">
|
||||||
|
<div class="temp">TEMP</div>
|
||||||
|
<div class="like">LIKE</div>
|
||||||
|
<div class="wind">WIND</div>
|
||||||
|
</div>
|
||||||
|
<div class="hourly-lines">
|
||||||
|
<div class="hourly-row template">
|
||||||
|
<div class="hour"></div>
|
||||||
|
<div class="icon"><img></div>
|
||||||
|
<div class="temp"></div>
|
||||||
|
<div class="like"></div>
|
||||||
|
<div class="wind"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="scroll">
|
||||||
|
<div class="scrolling template"></div>
|
||||||
|
<div class="fixed"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="travel-html" class="weather-display">
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo"><img src="images/Logo3.png"></div>
|
||||||
|
<div class="title dual">
|
||||||
|
<div class="top">Travel Forecast</div>
|
||||||
|
<div class="bottom">For</div>
|
||||||
|
</div>
|
||||||
|
<div class="date-time date"></div>
|
||||||
|
<div class="date-time time"></div>
|
||||||
|
</div>
|
||||||
|
<div class="main has-scroll travel">
|
||||||
|
<div class="column-headers">
|
||||||
|
<div class="temp low">LOW</div>
|
||||||
|
<div class="temp high">HIGH</div>
|
||||||
|
</div>
|
||||||
|
<div class="travel-lines">
|
||||||
|
<div class="travel-row template">
|
||||||
|
<div class="city"></div>
|
||||||
|
<div class="icon"><img></div>
|
||||||
|
<div class="temp low"></div>
|
||||||
|
<div class="temp high"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="scroll">
|
||||||
|
<div class="scrolling template"></div>
|
||||||
|
<div class="fixed"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="current-weather-html" class="weather-display">
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo"><img src="images/Logo3.png"></div>
|
||||||
|
<div class="title dual">
|
||||||
|
<div class="top">Current</div>
|
||||||
|
<div class="bottom">Conditions</div>
|
||||||
|
</div>
|
||||||
|
<div class="noaa-logo"><img src="images/noaa5.gif"></div>
|
||||||
|
</div>
|
||||||
|
<div class="main has-scroll has-box current-weather">
|
||||||
|
<div class="weather template">
|
||||||
|
<div class="left col">
|
||||||
|
<div class="temp center"></div>
|
||||||
|
<div class="condition center"></div>
|
||||||
|
<div class="icon center"><img src=""></div>
|
||||||
|
<div class="wind-container">
|
||||||
|
<div class="wind-label">Wind:</div>
|
||||||
|
<div class="wind"></div>
|
||||||
|
</div>
|
||||||
|
<div class="wind-gusts"></div>
|
||||||
|
</div>
|
||||||
|
<div class="right col">
|
||||||
|
<div class="location"></div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="label">Humidity:</div>
|
||||||
|
<div class="humidity value"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="label">Dewpoint:</div>
|
||||||
|
<div class="dewpoint value"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="label">Ceiling:</div>
|
||||||
|
<div class="ceiling value"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="label">Visibility:</div>
|
||||||
|
<div class="visibility value"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="label">Pressure:</div>
|
||||||
|
<div class="pressure value"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="heat-index-label label"></div>
|
||||||
|
<div class="heat-index value"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="scroll">
|
||||||
|
<div class="scrolling template"></div>
|
||||||
|
<div class="fixed"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="local-forecast-html" class="weather-display">
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo"><img src="images/Logo3.png"></div>
|
||||||
|
<div class="title dual">
|
||||||
|
<div class="top">Local</div>
|
||||||
|
<div class="bottom">Forecast</div>
|
||||||
|
</div>
|
||||||
|
<div class="date-time date"></div>
|
||||||
|
<div class="date-time time"></div>
|
||||||
|
<div class="noaa-logo"><img src="images/noaa5.gif"></div>
|
||||||
|
</div>
|
||||||
|
<div class="main has-scroll has-box local-forecast">
|
||||||
|
<div class="container">
|
||||||
|
<div class="forecasts">
|
||||||
|
<div class="forecast template">
|
||||||
|
<div class="text"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="scroll">
|
||||||
|
<div class="scrolling template"></div>
|
||||||
|
<div class="fixed"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="latest-observations-html" class="weather-display">
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo"><img src="images/Logo3.png"></div>
|
||||||
|
<div class="title dual">
|
||||||
|
<div class="top">Latest</div>
|
||||||
|
<div class="bottom">Observations</div>
|
||||||
|
</div>
|
||||||
|
<div class="noaa-logo"><img src="images/noaa5.gif"></div>
|
||||||
|
</div>
|
||||||
|
<div class="main has-scroll latest-observations has-box">
|
||||||
|
<div class="container">
|
||||||
|
<div class="column-headers">
|
||||||
|
<div class="temp english">°F</div>
|
||||||
|
<div class="temp metric">°C</div>
|
||||||
|
<div class="weather">Weather</div>
|
||||||
|
<div class="wind">Wind</div>
|
||||||
|
</div>
|
||||||
|
<div class="observation-lines">
|
||||||
|
<div class="observation-row template">
|
||||||
|
<div class="location"></div>
|
||||||
|
<div class="temp"></div>
|
||||||
|
<div class="weather"></div>
|
||||||
|
<div class="wind"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="scroll">
|
||||||
|
<div class="scrolling template"></div>
|
||||||
|
<div class="fixed"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="regional-forecast-html" class="weather-display">
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo"><img src="images/Logo3.png"></div>
|
||||||
|
<div class="title dual">
|
||||||
|
<div class="top">Regional</div>
|
||||||
|
<div class="bottom">Observations</div>
|
||||||
|
</div>
|
||||||
|
<div class="date-time date"></div>
|
||||||
|
<div class="date-time time"></div>
|
||||||
|
</div>
|
||||||
|
<div class="main has-scroll regional-forecast">
|
||||||
|
<div class="map"><img src="images/Basemap2.png"></div>
|
||||||
|
<div class="location-container">
|
||||||
|
<div class="location template">
|
||||||
|
<div class="icon"><img src=""></div>
|
||||||
|
<div class="city"></div>
|
||||||
|
<div class="temp"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="scroll">
|
||||||
|
<div class="scrolling template"></div>
|
||||||
|
<div class="fixed"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="almanac-html" class="weather-display">
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo"><img src="images/Logo3.png"></div>
|
||||||
|
<div class="title single">Almanac</div>
|
||||||
|
<div class="date-time date"></div>
|
||||||
|
<div class="date-time time"></div>
|
||||||
|
</div>
|
||||||
|
<div class="main has-scroll almanac">
|
||||||
|
<div class="sun">
|
||||||
|
<div class="days">
|
||||||
|
<div class="day"></div>
|
||||||
|
<div class="day day-1">Monday</div>
|
||||||
|
<div class="day day-2">Tuesday</div>
|
||||||
|
</div>
|
||||||
|
<div class="times times-1">
|
||||||
|
<div class="name">Sunrise:</div>
|
||||||
|
<div class="sun-time rise-1">6:24 am</div>
|
||||||
|
<div class="sun-time rise-2">6:25 am</div>
|
||||||
|
</div>
|
||||||
|
<div class="times times-2">
|
||||||
|
<div class="name">Sunset:</div>
|
||||||
|
<div class="sun-time set-1">6:24 am</div>
|
||||||
|
<div class="sun-time set-2">6:25 am</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="moon">
|
||||||
|
<div class="title">Moon Data:</div>
|
||||||
|
<div class="days">
|
||||||
|
<div class="day template">
|
||||||
|
<div class="type"></div>
|
||||||
|
<div class="icon"><img></div>
|
||||||
|
<div class="date"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="scroll">
|
||||||
|
<div class="scrolling template"></div>
|
||||||
|
<div class="fixed"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="extended-forecast-html" class="weather-display">
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo"><img src="images/Logo3.png"></div>
|
||||||
|
<div class="title dual">
|
||||||
|
<div class="top">Extended</div>
|
||||||
|
<div class="bottom">Forecast</div>
|
||||||
|
</div>
|
||||||
|
<div class="date-time date"></div>
|
||||||
|
<div class="date-time time"></div>
|
||||||
|
</div>
|
||||||
|
<div class="main has-scroll extended-forecast">
|
||||||
|
<div class="day-container">
|
||||||
|
<div class="day template">
|
||||||
|
<div class="date"></div>
|
||||||
|
<div class="icon"><img src=""></div>
|
||||||
|
<div class="condition"></div>
|
||||||
|
<div class="temperatures">
|
||||||
|
<div class="temperature-block lo">
|
||||||
|
<div class="label">Lo</div>
|
||||||
|
<div class="value value-lo"></div>
|
||||||
|
</div>
|
||||||
|
<div class="temperature-block hi">
|
||||||
|
<div class="label">Hi</div>
|
||||||
|
<div class="value value-hi"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="scroll">
|
||||||
|
<div class="scrolling template"></div>
|
||||||
|
<div class="fixed"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="radar-html" class="weather-display">
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo"><img src="images/Logo3.png"></div>
|
||||||
|
<div class="title dual">
|
||||||
|
<div class="top">Local</div>
|
||||||
|
<div class="bottom">Radar</div>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<div class="precip">
|
||||||
|
<div class="precip-header">PRECIP</div>
|
||||||
|
<div class="scale">
|
||||||
|
<div class="text">Light</div>
|
||||||
|
<div class="scale-table">
|
||||||
|
<div class="box box-1"></div>
|
||||||
|
<div class="box box-2"></div>
|
||||||
|
<div class="box box-3"></div>
|
||||||
|
<div class="box box-4"></div>
|
||||||
|
<div class="box box-5"></div>
|
||||||
|
<div class="box box-6"></div>
|
||||||
|
<div class="box box-7"></div>
|
||||||
|
<div class="box box-7"></div>
|
||||||
|
</div>
|
||||||
|
<div class="text">Heavy</div>
|
||||||
|
</div>
|
||||||
|
<div class="time"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="main radar">
|
||||||
|
<div class="container">
|
||||||
|
<div class="scroll-area">
|
||||||
|
<div class="frame template">
|
||||||
|
<div class="map"><img src="images/4000RadarMap2.jpg"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</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="Enter 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/ws.min.css
vendored
2
dist/resources/ws.min.css
vendored
File diff suppressed because one or more lines are too long
8
dist/resources/ws.min.js
vendored
8
dist/resources/ws.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -2,7 +2,6 @@
|
||||||
const gulp = require('gulp');
|
const gulp = require('gulp');
|
||||||
const concat = require('gulp-concat');
|
const concat = require('gulp-concat');
|
||||||
const terser = require('gulp-terser');
|
const terser = require('gulp-terser');
|
||||||
const cleanCSS = require('gulp-clean-css');
|
|
||||||
const ejs = require('gulp-ejs');
|
const ejs = require('gulp-ejs');
|
||||||
const rename = require('gulp-rename');
|
const rename = require('gulp-rename');
|
||||||
const htmlmin = require('gulp-htmlmin');
|
const htmlmin = require('gulp-htmlmin');
|
||||||
|
@ -34,7 +33,6 @@ const jsSources = [
|
||||||
'server/scripts/vendor/auto/nosleep.js',
|
'server/scripts/vendor/auto/nosleep.js',
|
||||||
'server/scripts/vendor/auto/swiped-events.js',
|
'server/scripts/vendor/auto/swiped-events.js',
|
||||||
'server/scripts/index.js',
|
'server/scripts/index.js',
|
||||||
'server/scripts/libgif.js',
|
|
||||||
'server/scripts/vendor/auto/luxon.js',
|
'server/scripts/vendor/auto/luxon.js',
|
||||||
'server/scripts/vendor/auto/suncalc.js',
|
'server/scripts/vendor/auto/suncalc.js',
|
||||||
'server/scripts/modules/draw.js',
|
'server/scripts/modules/draw.js',
|
||||||
|
@ -60,11 +58,10 @@ gulp.task('compress_js', () => gulp.src(jsSources)
|
||||||
.pipe(gulp.dest('./dist/resources')));
|
.pipe(gulp.dest('./dist/resources')));
|
||||||
|
|
||||||
const cssSources = [
|
const cssSources = [
|
||||||
'server/styles/index.css',
|
'server/styles/main.css',
|
||||||
];
|
];
|
||||||
gulp.task('compress_css', () => gulp.src(cssSources)
|
gulp.task('copy_css', () => gulp.src(cssSources)
|
||||||
.pipe(concat('ws.min.css'))
|
.pipe(concat('ws.min.css'))
|
||||||
.pipe(cleanCSS())
|
|
||||||
.pipe(gulp.dest('./dist/resources')));
|
.pipe(gulp.dest('./dist/resources')));
|
||||||
|
|
||||||
const htmlSources = [
|
const htmlSources = [
|
||||||
|
@ -121,4 +118,4 @@ gulp.task('invalidate', async () => cloudfront.createInvalidation({
|
||||||
},
|
},
|
||||||
}).promise());
|
}).promise());
|
||||||
|
|
||||||
module.exports = gulp.series(clean, gulp.parallel('compress_js', 'compress_js_data', 'compress_css', 'compress_html', 'copy_other_files'), 'upload', 'invalidate');
|
module.exports = gulp.series(clean, gulp.parallel('compress_js', 'compress_js_data', 'copy_css', 'compress_html', 'copy_other_files'), 'upload', 'invalidate');
|
||||||
|
|
28240
package-lock.json
generated
28240
package-lock.json
generated
File diff suppressed because it is too large
Load diff
84
package.json
84
package.json
|
@ -1,41 +1,47 @@
|
||||||
{
|
{
|
||||||
"name": "ws4kp",
|
"name": "ws4kp",
|
||||||
"version": "4.1.5",
|
"version": "5.0.0",
|
||||||
"description": "Welcome to the WeatherStar 4000+ project page!",
|
"description": "Welcome to the WeatherStar 4000+ project page!",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
},
|
"build:css": "sass ./server/styles/scss/style.scss ./server/styles/compiled.css",
|
||||||
"repository": {
|
"lint": "eslint ./server/scripts/**",
|
||||||
"type": "git",
|
"lint:fix": "eslint --fix ./server/scripts/**"
|
||||||
"url": "git+https://github.com/netbymatt/ws4kp.git"
|
},
|
||||||
},
|
"repository": {
|
||||||
"author": "Matt Walsh",
|
"type": "git",
|
||||||
"license": "MIT",
|
"url": "git+https://github.com/netbymatt/ws4kp.git"
|
||||||
"bugs": {
|
},
|
||||||
"url": "https://github.com/netbymatt/ws4kp/issues"
|
"author": "Matt Walsh",
|
||||||
},
|
"license": "MIT",
|
||||||
"homepage": "https://github.com/netbymatt/ws4kp#readme",
|
"bugs": {
|
||||||
"devDependencies": {
|
"url": "https://github.com/netbymatt/ws4kp/issues"
|
||||||
"del": "^6.0.0",
|
},
|
||||||
"ejs": "^3.1.5",
|
"homepage": "https://github.com/netbymatt/ws4kp#readme",
|
||||||
"eslint": "^8.10.0",
|
"devDependencies": {
|
||||||
"eslint-config-airbnb-base": "^15.0.0",
|
"del": "^6.0.0",
|
||||||
"eslint-plugin-import": "^2.22.1",
|
"ejs": "^3.1.5",
|
||||||
"express": "^4.17.1",
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
"gulp": "^4.0.2",
|
"express": "^4.17.1",
|
||||||
"gulp-clean-css": "^4.3.0",
|
"gulp": "^4.0.2",
|
||||||
"gulp-concat": "^2.6.1",
|
"gulp-concat": "^2.6.1",
|
||||||
"gulp-ejs": "^5.1.0",
|
"gulp-ejs": "^5.1.0",
|
||||||
"gulp-htmlmin": "^5.0.1",
|
"gulp-htmlmin": "^5.0.1",
|
||||||
"gulp-rename": "^2.0.0",
|
"gulp-rename": "^2.0.0",
|
||||||
"gulp-s3-upload": "^1.7.3",
|
"gulp-s3-upload": "^1.7.3",
|
||||||
"gulp-terser": "^2.0.0",
|
"gulp-sass": "^5.1.0",
|
||||||
"jquery": "^3.6.0",
|
"gulp-terser": "^2.0.0",
|
||||||
"jquery-touchswipe": "^1.6.19",
|
"jquery": "^3.6.0",
|
||||||
"luxon": "^3.0.0",
|
"jquery-touchswipe": "^1.6.19",
|
||||||
"nosleep.js": "^0.12.0",
|
"luxon": "^3.0.0",
|
||||||
"suncalc": "^1.8.0",
|
"nosleep.js": "^0.12.0",
|
||||||
"swiped-events": "^1.1.4"
|
"sass": "^1.54.0",
|
||||||
}
|
"suncalc": "^1.8.0",
|
||||||
|
"swiped-events": "^1.1.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"eslint": "^8.21.0",
|
||||||
|
"eslint-plugin-import": "^2.26.0"
|
||||||
|
}
|
||||||
}
|
}
|
BIN
server/fonts/Star4000 Large-old.ttf
Normal file
BIN
server/fonts/Star4000 Large-old.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
server/images/r/cold.gif
Normal file
BIN
server/images/r/cold.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
|
@ -581,4 +581,3 @@ const RegionalCities = [
|
||||||
lon: -110.9698,
|
lon: -110.9698,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const index = (() => {
|
const index = (() => {
|
||||||
const overrides = {
|
const overrides = {};
|
||||||
// '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.
|
||||||
|
|
||||||
|
@ -127,8 +125,10 @@ const index = (() => {
|
||||||
const TwcUnits = localStorage.getItem('TwcUnits');
|
const TwcUnits = localStorage.getItem('TwcUnits');
|
||||||
if (!TwcUnits || TwcUnits === 'ENGLISH') {
|
if (!TwcUnits || TwcUnits === 'ENGLISH') {
|
||||||
document.getElementById('radEnglish').checked = true;
|
document.getElementById('radEnglish').checked = true;
|
||||||
|
navigation.message({ type: 'units', message: 'english' });
|
||||||
} else if (TwcUnits === 'METRIC') {
|
} else if (TwcUnits === 'METRIC') {
|
||||||
document.getElementById('radMetric').checked = true;
|
document.getElementById('radMetric').checked = true;
|
||||||
|
navigation.message({ type: 'units', message: 'metric' });
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('radEnglish').addEventListener('change', changeUnits);
|
document.getElementById('radEnglish').addEventListener('change', changeUnits);
|
||||||
|
@ -231,8 +231,11 @@ const index = (() => {
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
FullScreenOverride = true;
|
FullScreenOverride = true;
|
||||||
}
|
}
|
||||||
|
navigation.resize();
|
||||||
UpdateFullScreenNavigate();
|
UpdateFullScreenNavigate();
|
||||||
|
|
||||||
|
// change hover text
|
||||||
|
document.getElementById('ToggleFullScreen').title = 'Exit fullscreen';
|
||||||
};
|
};
|
||||||
|
|
||||||
const ExitFullscreen = () => {
|
const ExitFullscreen = () => {
|
||||||
|
@ -252,6 +255,9 @@ const index = (() => {
|
||||||
} else if (document.msExitFullscreen) {
|
} else if (document.msExitFullscreen) {
|
||||||
document.msExitFullscreen();
|
document.msExitFullscreen();
|
||||||
}
|
}
|
||||||
|
navigation.resize();
|
||||||
|
// change hover text
|
||||||
|
document.getElementById('ToggleFullScreen').title = 'Enter fullscreen';
|
||||||
};
|
};
|
||||||
|
|
||||||
const btnNavigateMenuClick = () => {
|
const btnNavigateMenuClick = () => {
|
||||||
|
@ -311,6 +317,7 @@ const index = (() => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const btnNavigateRefreshClick = () => {
|
const btnNavigateRefreshClick = () => {
|
||||||
|
navigation.resetStatuses();
|
||||||
LoadTwcData();
|
LoadTwcData();
|
||||||
UpdateFullScreenNavigate();
|
UpdateFullScreenNavigate();
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,50 +1,35 @@
|
||||||
// display sun and moon data
|
// display sun and moon data
|
||||||
|
|
||||||
/* globals WeatherDisplay, utils, STATUS, draw, SunCalc, luxon */
|
/* globals WeatherDisplay, utils, STATUS, SunCalc, luxon */
|
||||||
|
|
||||||
// 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', true);
|
||||||
|
|
||||||
// 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');
|
||||||
this.backgroundImage1 = utils.image.load('images/BackGround1_1.png');
|
|
||||||
|
|
||||||
// load all images in parallel (returns promises)
|
// preload the moon images
|
||||||
this.moonImages = [
|
utils.image.preload('images/2/Full-Moon.gif');
|
||||||
utils.image.load('images/2/Full-Moon.gif'),
|
utils.image.preload('images/2/Last-Quarter.gif');
|
||||||
utils.image.load('images/2/Last-Quarter.gif'),
|
utils.image.preload('images/2/New-Moon.gif');
|
||||||
utils.image.load('images/2/New-Moon.gif'),
|
utils.image.preload('images/2/First-Quarter.gif');
|
||||||
utils.image.load('images/2/First-Quarter.gif'),
|
|
||||||
];
|
|
||||||
|
|
||||||
this.timing.totalScreens = 2;
|
this.timing.totalScreens = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getData(_weatherParameters) {
|
async getData(_weatherParameters) {
|
||||||
super.getData(_weatherParameters);
|
super.getData(_weatherParameters);
|
||||||
const weatherParameters = _weatherParameters ?? this.weatherParameters;
|
const weatherParameters = _weatherParameters ?? this.weatherParameters;
|
||||||
|
|
||||||
// get images for outlook
|
|
||||||
const imagePromises = [
|
|
||||||
utils.image.load('https://www.cpc.ncep.noaa.gov/products/predictions/30day/off14_temp.gif', true),
|
|
||||||
utils.image.load('https://www.cpc.ncep.noaa.gov/products/predictions/30day/off14_prcp.gif', true),
|
|
||||||
];
|
|
||||||
|
|
||||||
// get sun/moon data
|
// get sun/moon data
|
||||||
const { sun, moon } = this.calcSunMoonData(weatherParameters);
|
const { sun, moon } = this.calcSunMoonData(weatherParameters);
|
||||||
|
|
||||||
// process images for outlook
|
|
||||||
const [outlookTemp, outlookPrecip] = await Promise.all(imagePromises);
|
|
||||||
|
|
||||||
const outlook = Almanac.parseOutlooks(weatherParameters.latitude, weatherParameters.longitude, outlookTemp, outlookPrecip);
|
|
||||||
|
|
||||||
// store the data
|
// store the data
|
||||||
this.data = {
|
this.data = {
|
||||||
sun,
|
sun,
|
||||||
moon,
|
moon,
|
||||||
outlook,
|
|
||||||
};
|
};
|
||||||
// update status
|
// update status
|
||||||
this.setStatus(STATUS.loaded);
|
this.setStatus(STATUS.loaded);
|
||||||
|
@ -127,115 +112,6 @@ class Almanac extends WeatherDisplay {
|
||||||
return { phase: phaseName, date: moonDate };
|
return { phase: phaseName, date: moonDate };
|
||||||
}
|
}
|
||||||
|
|
||||||
// use the color of the pixel to determine the outlook
|
|
||||||
static parseOutlooks(lat, lon, temp, precip) {
|
|
||||||
const { DateTime } = luxon;
|
|
||||||
const month = DateTime.local();
|
|
||||||
const thisMonth = month.toLocaleString({ month: 'short' });
|
|
||||||
const nextMonth = month.plus({ months: 1 }).toLocaleString({ month: 'short' });
|
|
||||||
|
|
||||||
// draw the images on the canvases
|
|
||||||
const tempContext = utils.image.drawLocalCanvas(temp);
|
|
||||||
const precipContext = utils.image.drawLocalCanvas(precip);
|
|
||||||
|
|
||||||
// get the color from each canvas
|
|
||||||
const tempColor = Almanac.getOutlookColor(lat, lon, tempContext);
|
|
||||||
const precipColor = Almanac.getOutlookColor(lat, lon, precipContext);
|
|
||||||
|
|
||||||
return {
|
|
||||||
thisMonth,
|
|
||||||
nextMonth,
|
|
||||||
temperature: Almanac.getOutlookTemperatureIndicator(tempColor),
|
|
||||||
precipitation: Almanac.getOutlookPrecipitationIndicator(precipColor),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static getOutlookColor(lat, lon, context) {
|
|
||||||
let x = 0;
|
|
||||||
let y = 0;
|
|
||||||
|
|
||||||
// The height is in the range of latitude 75'N (top) - 15'N (bottom)
|
|
||||||
y = ((75 - lat) / 53) * 707;
|
|
||||||
|
|
||||||
if (lat < 48.83) {
|
|
||||||
y -= Math.abs(48.83 - lat) * 2.9;
|
|
||||||
}
|
|
||||||
if (lon < -100.46) {
|
|
||||||
y -= Math.abs(-100.46 - lon) * 1.7;
|
|
||||||
} else {
|
|
||||||
y -= Math.abs(-100.46 - lon) * 1.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The width is in the range of the longitude ???
|
|
||||||
x = ((-155 - lon) / -110) * 719; // -155 - -40
|
|
||||||
|
|
||||||
if (lon < -100.46) {
|
|
||||||
x -= Math.abs(-100.46 - lon) * 1;
|
|
||||||
|
|
||||||
if (lat > 40) {
|
|
||||||
x += Math.abs(40 - lat) * 4;
|
|
||||||
} else {
|
|
||||||
x -= Math.abs(40 - lat) * 4;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
x += Math.abs(-100.46 - lon) * 2;
|
|
||||||
|
|
||||||
if (lat < 36 && lon > -90) {
|
|
||||||
x += Math.abs(36 - lat) * 8;
|
|
||||||
} else {
|
|
||||||
x -= Math.abs(36 - lat) * 6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The further left and right from lat 45 and lon -97 the y increases
|
|
||||||
x = Math.round(x);
|
|
||||||
y = Math.round(y);
|
|
||||||
|
|
||||||
// Determine if there is any "non-white" colors around the area.
|
|
||||||
// Search a 16x16 region.
|
|
||||||
for (let colorX = x - 8; colorX <= x + 8; colorX += 1) {
|
|
||||||
for (let colorY = y - 8; colorY <= y + 8; colorY += 1) {
|
|
||||||
const pixelColor = Almanac.getPixelColor(context, colorX, colorY);
|
|
||||||
if ((pixelColor.r !== 0 && pixelColor.g !== 0 && pixelColor.b !== 0)
|
|
||||||
|| (pixelColor.r !== 255 && pixelColor.g !== 255 && pixelColor.b !== 255)) {
|
|
||||||
return pixelColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get rgb values of a pixel
|
|
||||||
static getPixelColor(context, x, y) {
|
|
||||||
const pixelData = context.getImageData(x, y, 1, 1).data;
|
|
||||||
return {
|
|
||||||
r: pixelData[0],
|
|
||||||
g: pixelData[1],
|
|
||||||
b: pixelData[2],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// get temperature outlook from color
|
|
||||||
static getOutlookTemperatureIndicator(pixelColor) {
|
|
||||||
if (pixelColor.b > pixelColor.r) {
|
|
||||||
return 'Below Normal';
|
|
||||||
} if (pixelColor.r > pixelColor.b) {
|
|
||||||
return 'Above Normal';
|
|
||||||
}
|
|
||||||
return 'Normal';
|
|
||||||
}
|
|
||||||
|
|
||||||
// get precipitation outlook from color
|
|
||||||
static getOutlookPrecipitationIndicator(pixelColor) {
|
|
||||||
if (pixelColor.g > pixelColor.r) {
|
|
||||||
return 'Above Normal';
|
|
||||||
} if (pixelColor.r > pixelColor.g) {
|
|
||||||
return 'Below Normal';
|
|
||||||
}
|
|
||||||
return 'Normal';
|
|
||||||
}
|
|
||||||
|
|
||||||
async drawCanvas() {
|
async drawCanvas() {
|
||||||
super.drawCanvas();
|
super.drawCanvas();
|
||||||
const info = this.data;
|
const info = this.data;
|
||||||
|
@ -243,81 +119,47 @@ class Almanac extends WeatherDisplay {
|
||||||
const Today = DateTime.local();
|
const Today = DateTime.local();
|
||||||
const Tomorrow = Today.plus({ days: 1 });
|
const Tomorrow = Today.plus({ days: 1 });
|
||||||
|
|
||||||
// extract moon images
|
// sun and moon data
|
||||||
const [FullMoonImage, LastMoonImage, NewMoonImage, FirstMoonImage] = await Promise.all(this.moonImages);
|
this.elem.querySelector('.day-1').innerHTML = Today.toLocaleString({ weekday: 'long' });
|
||||||
|
this.elem.querySelector('.day-2').innerHTML = Tomorrow.toLocaleString({ weekday: 'long' });
|
||||||
|
this.elem.querySelector('.rise-1').innerHTML = DateTime.fromJSDate(info.sun[0].sunrise).toLocaleString(DateTime.TIME_SIMPLE).toLowerCase();
|
||||||
|
this.elem.querySelector('.rise-2').innerHTML = DateTime.fromJSDate(info.sun[1].sunrise).toLocaleString(DateTime.TIME_SIMPLE).toLowerCase();
|
||||||
|
this.elem.querySelector('.set-1').innerHTML = DateTime.fromJSDate(info.sun[0].sunset).toLocaleString(DateTime.TIME_SIMPLE).toLowerCase();
|
||||||
|
this.elem.querySelector('.set-2').innerHTML = DateTime.fromJSDate(info.sun[1].sunset).toLocaleString(DateTime.TIME_SIMPLE).toLowerCase();
|
||||||
|
|
||||||
switch (this.screenIndex) {
|
const days = info.moon.map((MoonPhase) => {
|
||||||
case 1: {
|
const fill = {};
|
||||||
this.context.drawImage(await this.backgroundImage1, 0, 0);
|
|
||||||
draw.horizontalGradientSingle(this.context, 0, 30, 500, 90, draw.topColor1, draw.topColor2);
|
|
||||||
draw.triangle(this.context, 'rgb(28, 10, 87)', 500, 30, 450, 90, 500, 90);
|
|
||||||
draw.horizontalGradientSingle(this.context, 0, 90, 52, 399, draw.sideColor1, draw.sideColor2);
|
|
||||||
draw.horizontalGradientSingle(this.context, 584, 90, 640, 399, draw.sideColor1, draw.sideColor2);
|
|
||||||
|
|
||||||
draw.titleText(this.context, 'Almanac', 'Outlook');
|
const date = MoonPhase.date.toLocaleString({ month: 'short', day: 'numeric' });
|
||||||
|
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 320, 180, '30 Day Outlook', 2, 'center');
|
fill.date = date;
|
||||||
|
fill.type = MoonPhase.phase;
|
||||||
|
fill.icon = { type: 'img', src: Almanac.imageName(MoonPhase.Phase) };
|
||||||
|
|
||||||
const DateRange = `MID-${info.outlook.thisMonth.toUpperCase()} TO MID-${info.outlook.nextMonth.toUpperCase()}`;
|
return this.fillTemplate('day', fill);
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 320, 220, DateRange, 2, 'center');
|
});
|
||||||
|
|
||||||
const Temperature = info.outlook.temperature;
|
const daysContainer = this.elem.querySelector('.moon .days');
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 70, 300, `Temperatures: ${Temperature}`, 2);
|
daysContainer.innerHTML = '';
|
||||||
|
daysContainer.append(...days);
|
||||||
const Precipitation = info.outlook.precipitation;
|
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 70, 380, `Precipitation: ${Precipitation}`, 2);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 0:
|
|
||||||
default:
|
|
||||||
// sun and moon data
|
|
||||||
this.context.drawImage(await this.backgroundImage0, 0, 0);
|
|
||||||
draw.horizontalGradientSingle(this.context, 0, 30, 500, 90, draw.topColor1, draw.topColor2);
|
|
||||||
draw.triangle(this.context, 'rgb(28, 10, 87)', 500, 30, 450, 90, 500, 90);
|
|
||||||
draw.horizontalGradientSingle(this.context, 0, 90, 640, 190, draw.sideColor1, draw.sideColor2);
|
|
||||||
|
|
||||||
draw.titleText(this.context, 'Almanac', 'Astronomical');
|
|
||||||
|
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 320, 120, Today.toLocaleString({ weekday: 'long' }), 2, 'center');
|
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 500, 120, Tomorrow.toLocaleString({ weekday: 'long' }), 2, 'center');
|
|
||||||
|
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#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', 450, 150, DateTime.fromJSDate(info.sun[1].sunrise).toLocaleString(DateTime.TIME_SIMPLE).toLowerCase(), 2);
|
|
||||||
|
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 70, 180, ' Sunset:', 2);
|
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 270, 180, DateTime.fromJSDate(info.sun[0].sunset).toLocaleString(DateTime.TIME_SIMPLE).toLowerCase(), 2);
|
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 450, 180, DateTime.fromJSDate(info.sun[1].sunset).toLocaleString(DateTime.TIME_SIMPLE).toLowerCase(), 2);
|
|
||||||
|
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 70, 220, 'Moon Data:', 2);
|
|
||||||
|
|
||||||
info.moon.forEach((MoonPhase, Index) => {
|
|
||||||
const date = MoonPhase.date.toLocaleString({ month: 'short', day: 'numeric' });
|
|
||||||
|
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 120 + Index * 130, 260, MoonPhase.phase, 2, 'center');
|
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 120 + Index * 130, 390, date, 2, 'center');
|
|
||||||
|
|
||||||
const image = (() => {
|
|
||||||
switch (MoonPhase.phase) {
|
|
||||||
case 'Full':
|
|
||||||
return FullMoonImage;
|
|
||||||
case 'Last':
|
|
||||||
return LastMoonImage;
|
|
||||||
case 'New':
|
|
||||||
return NewMoonImage;
|
|
||||||
case 'First':
|
|
||||||
default:
|
|
||||||
return FirstMoonImage;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
this.context.drawImage(image, 75 + Index * 130, 270);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.finishDraw();
|
this.finishDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static imageName(type) {
|
||||||
|
switch (type) {
|
||||||
|
case 'Full':
|
||||||
|
return 'images/2/Full-Moon.gif';
|
||||||
|
case 'Last':
|
||||||
|
return 'images/2/Last-Quarter.gif';
|
||||||
|
case 'New':
|
||||||
|
return 'images/2/New-Moon.gif';
|
||||||
|
case 'First':
|
||||||
|
default:
|
||||||
|
return 'images/2/First-Quarter.gif';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// make sun and moon data available outside this class
|
// make sun and moon data available outside this class
|
||||||
// promise allows for data to be requested before it is available
|
// promise allows for data to be requested before it is available
|
||||||
async getSun() {
|
async getSun() {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// current weather conditions display
|
// current weather conditions display
|
||||||
/* globals WeatherDisplay, utils, STATUS, icons, UNITS, draw, navigation */
|
/* globals WeatherDisplay, utils, STATUS, icons, UNITS, navigation */
|
||||||
|
|
||||||
// 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', true);
|
||||||
// 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');
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ class CurrentWeather extends WeatherDisplay {
|
||||||
data.Temperature = Math.round(observations.temperature.value);
|
data.Temperature = Math.round(observations.temperature.value);
|
||||||
data.TemperatureUnit = 'C';
|
data.TemperatureUnit = 'C';
|
||||||
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 ?? 0);
|
||||||
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.';
|
||||||
|
@ -111,94 +111,43 @@ class CurrentWeather extends WeatherDisplay {
|
||||||
|
|
||||||
async drawCanvas() {
|
async drawCanvas() {
|
||||||
super.drawCanvas();
|
super.drawCanvas();
|
||||||
|
const fill = {};
|
||||||
// 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();
|
||||||
|
|
||||||
this.context.drawImage(await this.backgroundImage, 0, 0);
|
fill.temp = data.Temperature + String.fromCharCode(176);
|
||||||
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.horizontalGradientSingle(this.context, 0, 90, 52, 399, draw.sideColor1, draw.sideColor2);
|
|
||||||
draw.horizontalGradientSingle(this.context, 584, 90, 640, 399, draw.sideColor1, draw.sideColor2);
|
|
||||||
|
|
||||||
draw.titleText(this.context, 'Current', 'Conditions');
|
|
||||||
|
|
||||||
draw.text(this.context, 'Star4000 Large', '24pt', '#FFFFFF', 170, 135, data.Temperature + String.fromCharCode(176), 2);
|
|
||||||
|
|
||||||
let Conditions = data.observations.textDescription;
|
let Conditions = data.observations.textDescription;
|
||||||
if (Conditions.length > 15) {
|
if (Conditions.length > 15) {
|
||||||
Conditions = this.shortConditions(Conditions);
|
Conditions = this.shortConditions(Conditions);
|
||||||
}
|
}
|
||||||
draw.text(this.context, 'Star4000 Extended', '24pt', '#FFFFFF', 195, 170, Conditions, 2, 'center');
|
fill.condition = Conditions;
|
||||||
|
|
||||||
draw.text(this.context, 'Star4000 Extended', '24pt', '#FFFFFF', 80, 330, 'Wind:', 2);
|
fill.wind = data.WindDirection.padEnd(3, '') + data.WindSpeed.toString().padStart(3, ' ');
|
||||||
draw.text(this.context, 'Star4000 Extended', '24pt', '#FFFFFF', 300, 330, `${data.WindDirection} ${data.WindSpeed}`, 2, 'right');
|
if (data.WindGust) fill['wind-gusts'] = `Gusts to ${data.WindGust}`;
|
||||||
|
|
||||||
if (data.WindGust) draw.text(this.context, 'Star4000 Extended', '24pt', '#FFFFFF', 80, 375, `Gusts to ${data.WindGust}`, 2);
|
fill.location = utils.string.locationCleanup(this.data.station.properties.name).substr(0, 20);
|
||||||
|
|
||||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFF00', 315, 120, this.data.station.properties.name.substr(0, 20), 2);
|
fill.humidity = `${data.Humidity}%`;
|
||||||
|
fill.dewpoint = data.DewPoint + String.fromCharCode(176);
|
||||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 340, 165, 'Humidity:', 2);
|
fill.ceiling = (data.Ceiling === 0 ? 'Unlimited' : data.Ceiling + data.CeilingUnit);
|
||||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 560, 165, `${data.Humidity}%`, 2, 'right');
|
fill.visibility = data.Visibility + data.VisibilityUnit;
|
||||||
|
fill.pressure = `${data.Pressure} ${data.PressureDirection}`;
|
||||||
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', 340, 245, 'Ceiling:', 2);
|
|
||||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 560, 245, (data.Ceiling === '' ? 'Unlimited' : data.Ceiling + data.CeilingUnit), 2, 'right');
|
|
||||||
|
|
||||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 340, 285, 'Visibility:', 2);
|
|
||||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 560, 285, data.Visibility + data.VisibilityUnit, 2, 'right');
|
|
||||||
|
|
||||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 340, 325, 'Pressure:', 2);
|
|
||||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 535, 325, data.Pressure, 2, 'right');
|
|
||||||
|
|
||||||
switch (data.PressureDirection) {
|
|
||||||
case 'R':
|
|
||||||
// Shadow
|
|
||||||
draw.triangle(this.context, '#000000', 552, 302, 542, 312, 562, 312);
|
|
||||||
draw.box(this.context, '#000000', 549, 312, 6, 15);
|
|
||||||
|
|
||||||
// Border
|
|
||||||
draw.triangle(this.context, '#000000', 550, 300, 540, 310, 560, 310);
|
|
||||||
draw.box(this.context, '#000000', 547, 310, 6, 15);
|
|
||||||
|
|
||||||
// Fill
|
|
||||||
draw.triangle(this.context, '#FFFF00', 550, 301, 541, 309, 559, 309);
|
|
||||||
draw.box(this.context, '#FFFF00', 548, 309, 4, 15);
|
|
||||||
break;
|
|
||||||
case 'F':
|
|
||||||
// Shadow
|
|
||||||
draw.triangle(this.context, '#000000', 552, 327, 542, 317, 562, 317);
|
|
||||||
draw.box(this.context, '#000000', 549, 302, 6, 15);
|
|
||||||
|
|
||||||
// Border
|
|
||||||
draw.triangle(this.context, '#000000', 550, 325, 540, 315, 560, 315);
|
|
||||||
draw.box(this.context, '#000000', 547, 300, 6, 15);
|
|
||||||
|
|
||||||
// Fill
|
|
||||||
draw.triangle(this.context, '#FFFF00', 550, 324, 541, 314, 559, 314);
|
|
||||||
draw.box(this.context, '#FFFF00', 548, 301, 4, 15);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.observations.heatIndex.value && data.HeatIndex !== data.Temperature) {
|
if (data.observations.heatIndex.value && data.HeatIndex !== data.Temperature) {
|
||||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 340, 365, 'Heat Index:', 2);
|
fill['heat-index-label'] = 'Heat Index:';
|
||||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 560, 365, data.HeatIndex + String.fromCharCode(176), 2, 'right');
|
fill['heat-index'] = data.HeatIndex + String.fromCharCode(176);
|
||||||
} else if (data.observations.windChill.value && data.WindChill !== '' && data.WindChill < data.Temperature) {
|
} else if (data.observations.windChill.value && data.WindChill !== '' && data.WindChill < data.Temperature) {
|
||||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 340, 365, 'Wind Chill:', 2);
|
fill['heat-index-label'] = 'Wind Chill:';
|
||||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 560, 365, data.WindChill + String.fromCharCode(176), 2, 'right');
|
fill['heat-index'] = data.WindChill + String.fromCharCode(176);
|
||||||
}
|
}
|
||||||
|
|
||||||
// get main icon
|
fill.icon = { type: 'img', src: data.Icon };
|
||||||
this.gifs.push(await utils.image.superGifAsync({
|
|
||||||
src: data.Icon,
|
const area = this.elem.querySelector('.main');
|
||||||
auto_play: true,
|
|
||||||
canvas: this.canvas,
|
area.innerHTML = '';
|
||||||
x: 140,
|
area.append(this.fillTemplate('weather', fill));
|
||||||
y: 175,
|
|
||||||
max_width: 126,
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.finishDraw();
|
this.finishDraw();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* globals draw, navigation */
|
/* globals navigation, utils */
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
const currentWeatherScroll = (() => {
|
const currentWeatherScroll = (() => {
|
||||||
|
@ -6,25 +6,13 @@ const currentWeatherScroll = (() => {
|
||||||
const degree = String.fromCharCode(176);
|
const degree = String.fromCharCode(176);
|
||||||
|
|
||||||
// local variables
|
// local variables
|
||||||
let context; // currently active context
|
|
||||||
let blankDrawArea; // original state of context
|
|
||||||
let interval;
|
let interval;
|
||||||
let screenIndex = 0;
|
let screenIndex = 0;
|
||||||
|
|
||||||
// start drawing conditions
|
// start drawing conditions
|
||||||
// reset starts from the first item in the text scroll list
|
// reset starts from the first item in the text scroll list
|
||||||
const start = (_context) => {
|
const start = () => {
|
||||||
// see if there is a context available
|
|
||||||
if (!_context) return;
|
|
||||||
// store see if the context is new
|
// store see if the context is new
|
||||||
if (_context !== context) {
|
|
||||||
// clean the outgoing context
|
|
||||||
cleanLastContext();
|
|
||||||
// store the new blank context
|
|
||||||
blankDrawArea = _context.getImageData(0, 405, 640, 75);
|
|
||||||
}
|
|
||||||
// store the context locally
|
|
||||||
context = _context;
|
|
||||||
|
|
||||||
// set up the interval if needed
|
// set up the interval if needed
|
||||||
if (!interval) {
|
if (!interval) {
|
||||||
|
@ -36,17 +24,10 @@ const currentWeatherScroll = (() => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const stop = (reset) => {
|
const stop = (reset) => {
|
||||||
cleanLastContext();
|
|
||||||
if (interval) interval = clearInterval(interval);
|
if (interval) interval = clearInterval(interval);
|
||||||
if (reset) screenIndex = 0;
|
if (reset) screenIndex = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const cleanLastContext = () => {
|
|
||||||
if (blankDrawArea) context.putImageData(blankDrawArea, 0, 405);
|
|
||||||
blankDrawArea = undefined;
|
|
||||||
context = undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
// increment interval, roll over
|
// increment interval, roll over
|
||||||
const incrementInterval = () => {
|
const incrementInterval = () => {
|
||||||
screenIndex = (screenIndex + 1) % (screens.length);
|
screenIndex = (screenIndex + 1) % (screens.length);
|
||||||
|
@ -61,16 +42,13 @@ const currentWeatherScroll = (() => {
|
||||||
// nothing to do if there's no data yet
|
// nothing to do if there's no data yet
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
|
|
||||||
// clean up any old text
|
|
||||||
context.putImageData(blankDrawArea, 0, 405);
|
|
||||||
|
|
||||||
drawCondition(screens[screenIndex](data));
|
drawCondition(screens[screenIndex](data));
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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 ${utils.string.locationCleanup(data.station.properties.name).substr(0, 20)}`,
|
||||||
|
|
||||||
// temperature
|
// temperature
|
||||||
(data) => {
|
(data) => {
|
||||||
|
@ -109,7 +87,10 @@ const currentWeatherScroll = (() => {
|
||||||
|
|
||||||
// internal draw function with preset parameters
|
// internal draw function with preset parameters
|
||||||
const drawCondition = (text) => {
|
const drawCondition = (text) => {
|
||||||
draw.text(context, 'Star4000', '24pt', '#ffffff', 70, 430, text, 2);
|
// update all html scroll elements
|
||||||
|
utils.elem.forEach('.weather-display .scroll .fixed', (elem) => {
|
||||||
|
elem.innerHTML = text;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// return the api
|
// return the api
|
||||||
|
|
|
@ -1,18 +1,15 @@
|
||||||
// display extended forecast graphically
|
// display extended forecast graphically
|
||||||
// technically uses the same data as the local forecast, we'll let the browser do the caching of that
|
// technically uses the same data as the local forecast, we'll let the browser do the caching of that
|
||||||
|
|
||||||
/* globals WeatherDisplay, utils, STATUS, UNITS, draw, icons, navigation, luxon */
|
/* globals WeatherDisplay, utils, STATUS, UNITS, icons, navigation, luxon */
|
||||||
|
|
||||||
// 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', true);
|
||||||
|
|
||||||
// set timings
|
// set timings
|
||||||
this.timing.totalScreens = 2;
|
this.timing.totalScreens = 2;
|
||||||
|
|
||||||
// pre-load background image (returns promise)
|
|
||||||
this.backgroundImage = utils.image.load('images/BackGround2_1.png');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getData(_weatherParameters) {
|
async getData(_weatherParameters) {
|
||||||
|
@ -85,14 +82,18 @@ class ExtendedForecast extends WeatherDisplay {
|
||||||
}
|
}
|
||||||
|
|
||||||
static shortenExtendedForecastText(long) {
|
static shortenExtendedForecastText(long) {
|
||||||
let short = long;
|
const regexList = [
|
||||||
short = short.replace(/ and /g, ' ');
|
[/ and /ig, ' '],
|
||||||
short = short.replace(/Slight /g, '');
|
[/Slight /ig, ''],
|
||||||
short = short.replace(/Chance /g, '');
|
[/Chance /ig, ''],
|
||||||
short = short.replace(/Very /g, '');
|
[/Very /ig, ''],
|
||||||
short = short.replace(/Patchy /g, '');
|
[/Patchy /ig, ''],
|
||||||
short = short.replace(/Areas /g, '');
|
[/Areas /ig, ''],
|
||||||
short = short.replace(/Dense /g, '');
|
[/Dense /ig, ''],
|
||||||
|
[/Thunderstorm/g, 'T\'Storm'],
|
||||||
|
];
|
||||||
|
// run all regexes
|
||||||
|
const short = regexList.reduce((working, [regex, replace]) => working.replace(regex, replace), long);
|
||||||
|
|
||||||
let conditions = short.split(' ');
|
let conditions = short.split(' ');
|
||||||
if (short.indexOf('then') !== -1) {
|
if (short.indexOf('then') !== -1) {
|
||||||
|
@ -113,12 +114,12 @@ class ExtendedForecast extends WeatherDisplay {
|
||||||
short2 = '';
|
short2 = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
short = short1;
|
let result = short1;
|
||||||
if (short2 !== '') {
|
if (short2 !== '') {
|
||||||
short += ` ${short2}`;
|
result += ` ${short2}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [short, short1, short2];
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async drawCanvas() {
|
async drawCanvas() {
|
||||||
|
@ -128,45 +129,32 @@ class ExtendedForecast extends WeatherDisplay {
|
||||||
// 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;
|
// create each day template
|
||||||
|
const days = forecast.map((Day) => {
|
||||||
|
const fill = {};
|
||||||
|
fill.date = Day.dayName;
|
||||||
|
|
||||||
this.context.drawImage(backgroundImage, 0, 0);
|
|
||||||
draw.horizontalGradientSingle(this.context, 0, 30, 500, 90, draw.topColor1, draw.topColor2);
|
|
||||||
draw.triangle(this.context, 'rgb(28, 10, 87)', 500, 30, 450, 90, 500, 90);
|
|
||||||
draw.horizontalGradientSingle(this.context, 0, 90, 640, 399, draw.sideColor1, draw.sideColor2);
|
|
||||||
this.context.drawImage(backgroundImage, 38, 100, 174, 297, 38, 100, 174, 297);
|
|
||||||
this.context.drawImage(backgroundImage, 232, 100, 174, 297, 232, 100, 174, 297);
|
|
||||||
this.context.drawImage(backgroundImage, 426, 100, 174, 297, 426, 100, 174, 297);
|
|
||||||
|
|
||||||
draw.titleText(this.context, 'Extended', 'Forecast');
|
|
||||||
|
|
||||||
await Promise.all(forecast.map(async (Day, Index) => {
|
|
||||||
const offset = Index * 195;
|
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 100 + offset, 135, Day.dayName.toUpperCase(), 2);
|
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#8080FF', 85 + offset, 345, 'Lo', 2, 'center');
|
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFF00', 165 + offset, 345, 'Hi', 2, 'center');
|
|
||||||
let { low } = Day;
|
let { low } = Day;
|
||||||
if (low !== undefined) {
|
if (low !== undefined) {
|
||||||
if (navigation.units() === UNITS.metric) low = utils.units.fahrenheitToCelsius(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');
|
fill['value-lo'] = Math.round(low);
|
||||||
}
|
}
|
||||||
let { high } = Day;
|
let { high } = Day;
|
||||||
if (navigation.units() === UNITS.metric) high = utils.units.fahrenheitToCelsius(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');
|
fill['value-hi'] = Math.round(high);
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 120 + offset, 270, Day.text[1], 2, 'center');
|
fill.condition = Day.text;
|
||||||
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({
|
fill.icon = { type: 'img', src: Day.icon };
|
||||||
src: Day.icon,
|
|
||||||
auto_play: true,
|
|
||||||
canvas: this.canvas,
|
|
||||||
x: 70 + Index * 195,
|
|
||||||
y: 150,
|
|
||||||
max_height: 75,
|
|
||||||
}));
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
// return the filled template
|
||||||
|
return this.fillTemplate('day', fill);
|
||||||
|
});
|
||||||
|
|
||||||
|
// empty and update the container
|
||||||
|
const dayContainer = this.elem.querySelector('.day-container');
|
||||||
|
dayContainer.innerHTML = '';
|
||||||
|
dayContainer.append(...days);
|
||||||
this.finishDraw();
|
this.finishDraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,17 @@
|
||||||
// hourly forecast list
|
// hourly forecast list
|
||||||
/* globals WeatherDisplay, utils, STATUS, UNITS, draw, navigation, icons, luxon */
|
/* globals WeatherDisplay, utils, STATUS, UNITS, navigation, icons, luxon */
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
class Hourly extends WeatherDisplay {
|
class Hourly extends WeatherDisplay {
|
||||||
constructor(navId, elemId, defaultActive) {
|
constructor(navId, elemId, defaultActive) {
|
||||||
// special height and width for scrolling
|
// special height and width for scrolling
|
||||||
super(navId, elemId, 'Hourly Forecast', defaultActive);
|
super(navId, elemId, 'Hourly Forecast', defaultActive);
|
||||||
// pre-load background image (returns promise)
|
|
||||||
this.backgroundImage = utils.image.load('images/BackGround6_1.png');
|
|
||||||
|
|
||||||
// height of one hour in the forecast
|
|
||||||
this.hourHeight = 72;
|
|
||||||
|
|
||||||
// set up the timing
|
// set up the timing
|
||||||
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 = 75 * 4;
|
||||||
this.timing.delay = [150 + timingStep];
|
this.timing.delay = [150 + timingStep];
|
||||||
// add additional pages
|
// add additional pages
|
||||||
for (let i = 0; i < pages; i += 1) this.timing.delay.push(timingStep);
|
for (let i = 0; i < pages; i += 1) this.timing.delay.push(timingStep);
|
||||||
|
@ -35,6 +30,7 @@ class Hourly extends WeatherDisplay {
|
||||||
console.error('Get hourly forecast failed');
|
console.error('Get hourly forecast failed');
|
||||||
console.error(e.status, e.responseJSON);
|
console.error(e.status, e.responseJSON);
|
||||||
this.setStatus(STATUS.failed);
|
this.setStatus(STATUS.failed);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.data = await Hourly.parseForecast(forecast.properties);
|
this.data = await Hourly.parseForecast(forecast.properties);
|
||||||
|
@ -114,52 +110,26 @@ class Hourly extends WeatherDisplay {
|
||||||
}
|
}
|
||||||
|
|
||||||
async drawLongCanvas() {
|
async drawLongCanvas() {
|
||||||
// create the "long" canvas if necessary
|
// get the list element and populate
|
||||||
if (!this.longCanvas) {
|
const list = this.elem.querySelector('.hourly-lines');
|
||||||
this.longCanvas = document.createElement('canvas');
|
list.innerHTML = '';
|
||||||
this.longCanvas.width = 640;
|
|
||||||
this.longCanvas.height = 24 * this.hourHeight;
|
|
||||||
this.longContext = this.longCanvas.getContext('2d');
|
|
||||||
this.longCanvasGifs = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// stop all gifs
|
|
||||||
this.longCanvasGifs.forEach((gif) => gif.pause());
|
|
||||||
// delete the gifs
|
|
||||||
this.longCanvasGifs.length = 0;
|
|
||||||
|
|
||||||
// clean up existing gifs
|
|
||||||
this.gifs.forEach((gif) => gif.pause());
|
|
||||||
// delete the gifs
|
|
||||||
this.gifs.length = 0;
|
|
||||||
|
|
||||||
this.longContext.clearRect(0, 0, this.longCanvas.width, this.longCanvas.height);
|
|
||||||
|
|
||||||
// draw the "long" canvas with all cities
|
|
||||||
draw.box(this.longContext, 'rgb(35, 50, 112)', 0, 0, 640, 24 * this.hourHeight);
|
|
||||||
|
|
||||||
for (let i = 0; i <= 4; i += 1) {
|
|
||||||
const y = i * 346;
|
|
||||||
draw.horizontalGradient(this.longContext, 0, y, 640, y + 346, '#102080', '#001040');
|
|
||||||
}
|
|
||||||
|
|
||||||
const startingHour = luxon.DateTime.local();
|
const startingHour = luxon.DateTime.local();
|
||||||
|
|
||||||
await Promise.all(this.data.map(async (data, index) => {
|
const lines = this.data.map((data, index) => {
|
||||||
// calculate base y value
|
const fillValues = {};
|
||||||
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);
|
fillValues.hour = formattedHour;
|
||||||
|
|
||||||
// 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);
|
||||||
draw.text(this.longContext, 'Star4000 Large', '24pt', '#FFFF00', 390, y, temperature, 2, 'center');
|
fillValues.temp = temperature;
|
||||||
// only plot apparent temperature if there is a difference
|
// only plot apparent temperature if there is a difference
|
||||||
if (temperature !== feelsLike) draw.text(this.longContext, 'Star4000 Large', '24pt', '#FFFF00', 470, y, feelsLike, 2, 'center');
|
// if (temperature !== feelsLike) line.querySelector('.like').innerHTML = feelsLike;
|
||||||
|
if (temperature !== feelsLike) fillValues.like = feelsLike;
|
||||||
|
|
||||||
// wind
|
// wind
|
||||||
let wind = 'Calm';
|
let wind = 'Calm';
|
||||||
|
@ -167,44 +137,25 @@ class Hourly extends WeatherDisplay {
|
||||||
const windSpeed = Math.round(data.windSpeed).toString();
|
const windSpeed = Math.round(data.windSpeed).toString();
|
||||||
wind = data.windDirection + (Array(6 - data.windDirection.length - windSpeed.length).join(' ')) + windSpeed;
|
wind = data.windDirection + (Array(6 - data.windDirection.length - windSpeed.length).join(' ')) + windSpeed;
|
||||||
}
|
}
|
||||||
draw.text(this.longContext, 'Star4000 Large', '24pt', '#FFFF00', 580, y, wind, 2, 'center');
|
fillValues.wind = wind;
|
||||||
|
|
||||||
this.longCanvasGifs.push(await utils.image.superGifAsync({
|
// image
|
||||||
src: data.icon,
|
fillValues.icon = { type: 'img', src: data.icon };
|
||||||
auto_play: true,
|
|
||||||
canvas: this.longCanvas,
|
return this.fillTemplate('hourly-row', fillValues);
|
||||||
x: 290,
|
});
|
||||||
y: y - 35,
|
|
||||||
max_width: 47,
|
list.append(...lines);
|
||||||
}));
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async drawCanvas() {
|
drawCanvas() {
|
||||||
// there are technically 2 canvases: the standard canvas and the extra-long canvas that contains the complete
|
|
||||||
// list of cities. The second canvas is copied into the standard canvas to create the scroll
|
|
||||||
super.drawCanvas();
|
super.drawCanvas();
|
||||||
|
|
||||||
// draw the standard context
|
|
||||||
this.context.drawImage(await this.backgroundImage, 0, 0);
|
|
||||||
draw.horizontalGradientSingle(this.context, 0, 30, 500, 90, draw.topColor1, draw.topColor2);
|
|
||||||
draw.triangle(this.context, 'rgb(28, 10, 87)', 500, 30, 450, 90, 500, 90);
|
|
||||||
|
|
||||||
draw.titleText(this.context, 'Hourly Forecast');
|
|
||||||
|
|
||||||
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFF00', 390, 105, 'TEMP', 2, 'center');
|
|
||||||
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFF00', 470, 105, 'LIKE', 2, 'center');
|
|
||||||
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFF00', 580, 105, 'WIND', 2, 'center');
|
|
||||||
|
|
||||||
// copy the scrolled portion of the canvas for the initial run before the scrolling starts
|
|
||||||
this.context.drawImage(this.longCanvas, 0, 0, 640, 289, 0, 110, 640, 289);
|
|
||||||
|
|
||||||
this.finishDraw();
|
this.finishDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
async showCanvas() {
|
showCanvas() {
|
||||||
// special to travel forecast to draw the remainder of the canvas
|
// special to hourly to draw the remainder of the canvas
|
||||||
await this.drawCanvas();
|
this.drawCanvas();
|
||||||
super.showCanvas();
|
super.showCanvas();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,17 +166,14 @@ class Hourly extends WeatherDisplay {
|
||||||
|
|
||||||
// base count change callback
|
// base count change callback
|
||||||
baseCountChange(count) {
|
baseCountChange(count) {
|
||||||
// get a fresh canvas
|
|
||||||
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(this.elem.querySelector('.hourly-lines').getBoundingClientRect().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;
|
||||||
|
|
||||||
// copy the scrolled portion of the canvas
|
// copy the scrolled portion of the canvas
|
||||||
this.context.drawImage(longCanvas, 0, offsetY, 640, 289, 0, 110, 640, 289);
|
this.elem.querySelector('.main').scrollTo(0, offsetY);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getTravelCitiesDayName(cities) {
|
static getTravelCitiesDayName(cities) {
|
||||||
|
@ -241,9 +189,4 @@ class Hourly extends WeatherDisplay {
|
||||||
return dayName;
|
return dayName;
|
||||||
}, '');
|
}, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
// necessary to get the lastest long canvas when scrolling
|
|
||||||
getLongCanvas() {
|
|
||||||
return this.longCanvas;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ const icons = (() => {
|
||||||
case 'skc-n':
|
case 'skc-n':
|
||||||
case 'nskc':
|
case 'nskc':
|
||||||
case 'nskc-n':
|
case 'nskc-n':
|
||||||
|
case 'cold-n':
|
||||||
return addPath('Clear-1992.gif');
|
return addPath('Clear-1992.gif');
|
||||||
|
|
||||||
case 'bkn':
|
case 'bkn':
|
||||||
|
@ -135,6 +136,9 @@ const icons = (() => {
|
||||||
case 'blizzard':
|
case 'blizzard':
|
||||||
return addPath('Blowing Snow.gif');
|
return addPath('Blowing Snow.gif');
|
||||||
|
|
||||||
|
case 'cold':
|
||||||
|
return addPath('cold.gif');
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.log(`Unable to locate regional icon for ${conditionName} ${link} ${isNightTime}`);
|
console.log(`Unable to locate regional icon for ${conditionName} ${link} ${isNightTime}`);
|
||||||
return false;
|
return false;
|
||||||
|
@ -142,6 +146,8 @@ const icons = (() => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const getWeatherIconFromIconLink = (link, _isNightTime) => {
|
const getWeatherIconFromIconLink = (link, _isNightTime) => {
|
||||||
|
if (!link) return false;
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -164,11 +170,13 @@ const icons = (() => {
|
||||||
case 'skc':
|
case 'skc':
|
||||||
case 'hot':
|
case 'hot':
|
||||||
case 'haze':
|
case 'haze':
|
||||||
|
case 'cold':
|
||||||
return addPath('CC_Clear1.gif');
|
return addPath('CC_Clear1.gif');
|
||||||
|
|
||||||
case 'skc-n':
|
case 'skc-n':
|
||||||
case 'nskc':
|
case 'nskc':
|
||||||
case 'nskc-n':
|
case 'nskc-n':
|
||||||
|
case 'cold-n':
|
||||||
return addPath('CC_Clear0.gif');
|
return addPath('CC_Clear0.gif');
|
||||||
|
|
||||||
case 'sct':
|
case 'sct':
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
// current weather conditions display
|
// current weather conditions display
|
||||||
/* globals WeatherDisplay, utils, STATUS, UNITS, draw, navigation, StationInfo */
|
/* globals WeatherDisplay, utils, STATUS, UNITS, 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', true);
|
||||||
// pre-load background image (returns promise)
|
|
||||||
this.backgroundImage = utils.image.load('images/BackGround1_1.png');
|
|
||||||
|
|
||||||
// constants
|
// constants
|
||||||
this.MaximumRegionalStations = 7;
|
this.MaximumRegionalStations = 7;
|
||||||
|
@ -67,25 +65,15 @@ class LatestObservations extends WeatherDisplay {
|
||||||
// sort array by station name
|
// sort array by station name
|
||||||
const sortedConditions = conditions.sort((a, b) => ((a.Name < b.Name) ? -1 : 1));
|
const sortedConditions = conditions.sort((a, b) => ((a.Name < b.Name) ? -1 : 1));
|
||||||
|
|
||||||
this.context.drawImage(await this.backgroundImage, 0, 0);
|
|
||||||
draw.horizontalGradientSingle(this.context, 0, 30, 500, 90, draw.topColor1, draw.topColor2);
|
|
||||||
draw.triangle(this.context, 'rgb(28, 10, 87)', 500, 30, 450, 90, 500, 90);
|
|
||||||
draw.horizontalGradientSingle(this.context, 0, 90, 52, 399, draw.sideColor1, draw.sideColor2);
|
|
||||||
draw.horizontalGradientSingle(this.context, 584, 90, 640, 399, draw.sideColor1, draw.sideColor2);
|
|
||||||
|
|
||||||
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);
|
this.elem.querySelector('.column-headers .temp.english').classList.add('show');
|
||||||
|
this.elem.querySelector('.column-headers .temp.metric').classList.remove('show');
|
||||||
} else {
|
} else {
|
||||||
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFFFF', 295, 105, `${String.fromCharCode(176)}C`, 2);
|
this.elem.querySelector('.column-headers .temp.english').classList.remove('show');
|
||||||
|
this.elem.querySelector('.column-headers .temp.metric').classList.add('show');
|
||||||
}
|
}
|
||||||
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFFFF', 345, 105, 'WEATHER', 2);
|
|
||||||
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFFFF', 495, 105, 'WIND', 2);
|
|
||||||
|
|
||||||
let y = 140;
|
const lines = sortedConditions.map((condition) => {
|
||||||
|
|
||||||
sortedConditions.forEach((condition) => {
|
|
||||||
let Temperature = condition.temperature.value;
|
let Temperature = condition.temperature.value;
|
||||||
let WindSpeed = condition.windSpeed.value;
|
let WindSpeed = condition.windSpeed.value;
|
||||||
const windDirection = utils.calc.directionToNSEW(condition.windDirection.value);
|
const windDirection = utils.calc.directionToNSEW(condition.windDirection.value);
|
||||||
|
@ -94,23 +82,28 @@ class LatestObservations extends WeatherDisplay {
|
||||||
Temperature = utils.units.celsiusToFahrenheit(Temperature);
|
Temperature = utils.units.celsiusToFahrenheit(Temperature);
|
||||||
WindSpeed = utils.units.kphToMph(WindSpeed);
|
WindSpeed = utils.units.kphToMph(WindSpeed);
|
||||||
}
|
}
|
||||||
|
WindSpeed = Math.round(WindSpeed);
|
||||||
|
Temperature = Math.round(Temperature);
|
||||||
|
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 65, y, condition.city.substr(0, 14), 2);
|
const fill = {};
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 345, y, LatestObservations.shortenCurrentConditions(condition.textDescription).substr(0, 9), 2);
|
fill.location = utils.string.locationCleanup(condition.city).substr(0, 14);
|
||||||
|
fill.temp = Temperature;
|
||||||
|
fill.weather = LatestObservations.shortenCurrentConditions(condition.textDescription).substr(0, 9);
|
||||||
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);
|
fill.wind = windDirection + (Array(6 - windDirection.length - WindSpeed.toString().length).join(' ')) + WindSpeed.toString();
|
||||||
} else if (WindSpeed === 'NA') {
|
} else if (WindSpeed === 'NA') {
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 495, y, 'NA', 2);
|
fill.wind = 'NA';
|
||||||
} else {
|
} else {
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 495, y, 'Calm', 2);
|
fill.wind = 'Calm';
|
||||||
}
|
}
|
||||||
|
|
||||||
const x = (325 - (Temperature.toString().length * 15));
|
return this.fillTemplate('observation-row', fill);
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', x, y, Temperature, 2);
|
|
||||||
|
|
||||||
y += 40;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const linesContainer = this.elem.querySelector('.observation-lines');
|
||||||
|
linesContainer.innerHTML = '';
|
||||||
|
linesContainer.append(...lines);
|
||||||
|
|
||||||
this.finishDraw();
|
this.finishDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
// display text based local forecast
|
// display text based local forecast
|
||||||
|
|
||||||
/* globals WeatherDisplay, utils, STATUS, UNITS, draw, navigation */
|
/* globals WeatherDisplay, utils, STATUS, UNITS, 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', true);
|
||||||
|
|
||||||
// set timings
|
// set timings
|
||||||
this.timing.baseDelay = 5000;
|
this.timing.baseDelay = 5000;
|
||||||
|
|
||||||
// pre-load background image (returns promise)
|
|
||||||
this.backgroundImage = utils.image.load('images/BackGround1_1.png');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getData(_weatherParameters) {
|
async getData(_weatherParameters) {
|
||||||
|
@ -28,14 +25,8 @@ class LocalForecast extends WeatherDisplay {
|
||||||
// parse raw data
|
// parse raw data
|
||||||
const conditions = LocalForecast.parse(rawData);
|
const conditions = LocalForecast.parse(rawData);
|
||||||
|
|
||||||
// split this forecast into the correct number of screens
|
|
||||||
const maxRows = 7;
|
|
||||||
const maxCols = 32;
|
|
||||||
|
|
||||||
this.screenTexts = [];
|
|
||||||
|
|
||||||
// read each text
|
// read each text
|
||||||
conditions.forEach((condition) => {
|
this.screenTexts = conditions.map((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;
|
||||||
|
@ -44,44 +35,23 @@ class LocalForecast extends WeatherDisplay {
|
||||||
}
|
}
|
||||||
text += conditionText.toUpperCase().replace('...', ' ');
|
text += conditionText.toUpperCase().replace('...', ' ');
|
||||||
|
|
||||||
text = utils.string.wordWrap(text, maxCols, '\n');
|
return text;
|
||||||
const lines = text.split('\n');
|
|
||||||
const lineCount = lines.length;
|
|
||||||
let ScreenText = '';
|
|
||||||
const maxRowCount = maxRows;
|
|
||||||
let rowCount = 0;
|
|
||||||
|
|
||||||
// if (PrependAlert) {
|
|
||||||
// ScreenText = LocalForecastScreenTexts[LocalForecastScreenTexts.length - 1];
|
|
||||||
// rowCount = ScreenText.split('\n').length - 1;
|
|
||||||
// }
|
|
||||||
|
|
||||||
for (let i = 0; i <= lineCount - 1; i += 1) {
|
|
||||||
if (lines[i] !== '') {
|
|
||||||
if (rowCount > maxRowCount - 1) {
|
|
||||||
// if (PrependAlert) {
|
|
||||||
// LocalForecastScreenTexts[LocalForecastScreenTexts.length - 1] = ScreenText;
|
|
||||||
// PrependAlert = false;
|
|
||||||
// } else {
|
|
||||||
this.screenTexts.push(ScreenText);
|
|
||||||
// }
|
|
||||||
ScreenText = '';
|
|
||||||
rowCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScreenText += `${lines[i]}\n`;
|
|
||||||
rowCount += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if (PrependAlert) {
|
|
||||||
// this.screenTexts[this.screenTexts.length - 1] = ScreenText;
|
|
||||||
// PrependAlert = false;
|
|
||||||
// } else {
|
|
||||||
this.screenTexts.push(ScreenText);
|
|
||||||
// }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.timing.totalScreens = this.screenTexts.length;
|
// fill the forecast texts
|
||||||
|
const templates = this.screenTexts.map((text) => this.fillTemplate('forecast', { text }));
|
||||||
|
const forecastsElem = this.elem.querySelector('.forecasts');
|
||||||
|
forecastsElem.innerHTML = '';
|
||||||
|
forecastsElem.append(...templates);
|
||||||
|
|
||||||
|
// increase each forecast height to a multiple of container height
|
||||||
|
this.pageHeight = forecastsElem.parentNode.getBoundingClientRect().height;
|
||||||
|
templates.forEach((forecast) => {
|
||||||
|
const newHeight = Math.ceil(forecast.scrollHeight / this.pageHeight) * this.pageHeight;
|
||||||
|
forecast.style.height = `${newHeight}px`;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.timing.totalScreens = forecastsElem.scrollHeight / this.pageHeight;
|
||||||
this.calcNavTiming();
|
this.calcNavTiming();
|
||||||
this.setStatus(STATUS.loaded);
|
this.setStatus(STATUS.loaded);
|
||||||
}
|
}
|
||||||
|
@ -105,25 +75,12 @@ class LocalForecast extends WeatherDisplay {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: alerts needs a cleanup
|
|
||||||
// TODO: second page of screenTexts when needed
|
|
||||||
async drawCanvas() {
|
async drawCanvas() {
|
||||||
super.drawCanvas();
|
super.drawCanvas();
|
||||||
|
|
||||||
this.context.drawImage(await this.backgroundImage, 0, 0);
|
const top = -this.screenIndex * this.pageHeight;
|
||||||
draw.horizontalGradientSingle(this.context, 0, 30, 500, 90, draw.topColor1, draw.topColor2);
|
this.elem.querySelector('.forecasts').style.top = `${top}px`;
|
||||||
draw.triangle(this.context, 'rgb(28, 10, 87)', 500, 30, 450, 90, 500, 90);
|
|
||||||
draw.horizontalGradientSingle(this.context, 0, 90, 52, 399, draw.sideColor1, draw.sideColor2);
|
|
||||||
draw.horizontalGradientSingle(this.context, 584, 90, 640, 399, draw.sideColor1, draw.sideColor2);
|
|
||||||
|
|
||||||
draw.titleText(this.context, 'Local ', 'Forecast');
|
|
||||||
|
|
||||||
// clear existing text
|
|
||||||
draw.box(this.context, 'rgb(33, 40, 90)', 65, 105, 505, 280);
|
|
||||||
// Draw the text.
|
|
||||||
this.screenTexts[this.screenIndex].split('\n').forEach((text, index) => {
|
|
||||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 75, 140 + 40 * index, text, 2);
|
|
||||||
});
|
|
||||||
this.finishDraw();
|
this.finishDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,8 @@ const navigation = (() => {
|
||||||
let almanac;
|
let almanac;
|
||||||
|
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
// nothing to do
|
// set up resize handler
|
||||||
|
window.addEventListener('resize', resize);
|
||||||
};
|
};
|
||||||
|
|
||||||
const message = (data) => {
|
const message = (data) => {
|
||||||
|
@ -87,22 +88,22 @@ 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');
|
if (!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, 'current-weather');
|
||||||
almanac = new Almanac(7, 'almanac');
|
almanac = new Almanac(7, 'almanac');
|
||||||
displays = [
|
displays = [
|
||||||
currentWeather,
|
currentWeather,
|
||||||
new LatestObservations(1, 'latestObservations'),
|
new LatestObservations(1, 'latest-observations'),
|
||||||
new Hourly(2, 'hourly'),
|
new Hourly(2, 'hourly'),
|
||||||
new TravelForecast(3, 'travelForecast', false), // not active by default
|
new TravelForecast(3, 'travel', false), // not active by default
|
||||||
new RegionalForecast(4, 'regionalForecast'),
|
new RegionalForecast(4, 'regional-forecast'),
|
||||||
new LocalForecast(5, 'localForecast'),
|
new LocalForecast(5, 'local-forecast'),
|
||||||
new ExtendedForecast(6, 'extendedForecast'),
|
new ExtendedForecast(6, 'extended-forecast'),
|
||||||
almanac,
|
almanac,
|
||||||
new Radar(8, 'radar'),
|
new Radar(8, 'radar'),
|
||||||
];
|
];
|
||||||
|
@ -177,7 +178,15 @@ const navigation = (() => {
|
||||||
progress.hideCanvas();
|
progress.hideCanvas();
|
||||||
if (!current) {
|
if (!current) {
|
||||||
// special case for no active displays (typically on progress screen)
|
// special case for no active displays (typically on progress screen)
|
||||||
displays[0].navNext(msg.command.firstFrame);
|
// find the first ready display
|
||||||
|
let firstDisplay;
|
||||||
|
let displayCount = 0;
|
||||||
|
do {
|
||||||
|
if (displays[displayCount].status === STATUS.loaded) firstDisplay = displays[displayCount];
|
||||||
|
displayCount += 1;
|
||||||
|
} while (!firstDisplay && displayCount < displays.length);
|
||||||
|
|
||||||
|
firstDisplay.navNext(msg.command.firstFrame);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (direction === msg.command.nextFrame) currentDisplay().navNext();
|
if (direction === msg.command.nextFrame) currentDisplay().navNext();
|
||||||
|
@ -266,6 +275,25 @@ const navigation = (() => {
|
||||||
return almanac.getSun();
|
return almanac.getSun();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// resize the container on a page resize
|
||||||
|
const resize = () => {
|
||||||
|
const widthZoomPercent = window.innerWidth / 640;
|
||||||
|
const heightZoomPercent = window.innerHeight / 480;
|
||||||
|
|
||||||
|
const scale = Math.min(widthZoomPercent, heightZoomPercent);
|
||||||
|
|
||||||
|
if (scale < 1.0 || document.fullscreenElement) {
|
||||||
|
document.getElementById('container').style.zoom = scale;
|
||||||
|
} else {
|
||||||
|
document.getElementById('container').style.zoom = 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// reset all statuses to loading on all displays, used to keep the progress bar accurate during refresh
|
||||||
|
const resetStatuses = () => {
|
||||||
|
displays.forEach((display) => { display.status = STATUS.loading; });
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init,
|
init,
|
||||||
message,
|
message,
|
||||||
|
@ -277,5 +305,7 @@ const navigation = (() => {
|
||||||
getDisplay,
|
getDisplay,
|
||||||
getCurrentWeather,
|
getCurrentWeather,
|
||||||
getSun,
|
getSun,
|
||||||
|
resize,
|
||||||
|
resetStatuses,
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
// regional forecast and observations
|
// regional forecast and observations
|
||||||
|
|
||||||
/* globals WeatherDisplay, utils, STATUS, draw, navigation */
|
/* globals WeatherDisplay, utils, STATUS, navigation */
|
||||||
|
|
||||||
// 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, '', false);
|
||||||
|
|
||||||
// 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');
|
||||||
|
@ -14,101 +14,90 @@ class Progress extends WeatherDisplay {
|
||||||
this.timing = false;
|
this.timing = false;
|
||||||
|
|
||||||
this.version = document.getElementById('version').innerHTML;
|
this.version = document.getElementById('version').innerHTML;
|
||||||
|
|
||||||
|
// setup event listener
|
||||||
|
this.elem.querySelector('.container').addEventListener('click', this.lineClick.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
async drawCanvas(displays, loadedCount) {
|
async drawCanvas(displays, loadedCount) {
|
||||||
super.drawCanvas();
|
super.drawCanvas();
|
||||||
// set up an event listener
|
|
||||||
if (!this.eventListener) {
|
|
||||||
this.eventListener = true;
|
|
||||||
this.canvas.addEventListener('click', (e) => this.canvasClick(e), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the background image
|
// get the progress bar cover (makes percentage)
|
||||||
const backgroundImage = await this.backgroundImage;
|
if (!this.progressCover) this.progressCover = this.elem.querySelector('.scroll .cover');
|
||||||
|
|
||||||
// only draw the background once
|
|
||||||
if (!this.backgroundDrawn) {
|
|
||||||
this.context.drawImage(backgroundImage, 0, 0, 640, 480, 0, 0, 640, 480);
|
|
||||||
draw.horizontalGradientSingle(this.context, 0, 90, 52, 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.triangle(this.context, 'rgb(28, 10, 87)', 500, 30, 450, 90, 500, 90);
|
|
||||||
draw.titleText(this.context, 'WeatherStar', `4000+ ${this.version}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
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) => {
|
const lines = displays.map((display, index) => {
|
||||||
const y = 120 + idx * 29;
|
const fill = {};
|
||||||
const dots = Array(120 - Math.floor(display.name.length * 2.5)).join('.');
|
|
||||||
draw.text(this.context, 'Star4000 Extended', '19pt', '#ffffff', 70, y, display.name + dots, 2);
|
|
||||||
|
|
||||||
let statusText;
|
fill.name = display.name;
|
||||||
let statusColor;
|
|
||||||
|
let statusClass;
|
||||||
switch (display.status) {
|
switch (display.status) {
|
||||||
case STATUS.loading:
|
case STATUS.loading:
|
||||||
statusText = 'Loading';
|
statusClass = 'loading';
|
||||||
statusColor = '#ffff00';
|
|
||||||
break;
|
break;
|
||||||
case STATUS.loaded:
|
case STATUS.loaded:
|
||||||
statusText = 'Press Here';
|
statusClass = 'press-here';
|
||||||
statusColor = '#00ff00';
|
|
||||||
this.context.drawImage(backgroundImage, 440, y - 20, 75, 25, 440, y - 20, 75, 25);
|
|
||||||
break;
|
break;
|
||||||
case STATUS.failed:
|
case STATUS.failed:
|
||||||
statusText = 'Failed';
|
statusClass = 'failed';
|
||||||
statusColor = '#ff0000';
|
|
||||||
break;
|
break;
|
||||||
case STATUS.noData:
|
case STATUS.noData:
|
||||||
statusText = 'No Data';
|
statusClass = 'no-data';
|
||||||
statusColor = '#C0C0C0';
|
|
||||||
draw.box(this.context, 'rgb(33, 40, 90)', 475, y - 15, 75, 15);
|
|
||||||
break;
|
break;
|
||||||
case STATUS.disabled:
|
case STATUS.disabled:
|
||||||
statusText = 'Disabled';
|
statusClass = 'disabled';
|
||||||
statusColor = '#C0C0C0';
|
|
||||||
this.context.drawImage(backgroundImage, 470, y - 20, 45, 25, 470, y - 20, 45, 25);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
// Erase any dots that spill into the status text.
|
|
||||||
this.context.drawImage(backgroundImage, 475, y - 20, 165, 30, 475, y - 20, 165, 30);
|
// make the line
|
||||||
draw.text(this.context, 'Star4000 Extended', '19pt', statusColor, 565, y, statusText, 2, 'end');
|
const line = this.fillTemplate('item', fill);
|
||||||
});
|
// because of timing, this might get called before the template is loaded
|
||||||
|
if (!line) return false;
|
||||||
|
|
||||||
|
// update the status
|
||||||
|
const links = line.querySelector('.links');
|
||||||
|
links.classList.remove('loading');
|
||||||
|
links.classList.add(statusClass);
|
||||||
|
links.dataset.index = index;
|
||||||
|
return line;
|
||||||
|
}).filter((d) => d);
|
||||||
|
|
||||||
|
// get the container and update
|
||||||
|
const container = this.elem.querySelector('.container');
|
||||||
|
container.innerHTML = '';
|
||||||
|
container.append(...lines);
|
||||||
|
|
||||||
|
this.finishDraw();
|
||||||
|
|
||||||
// calculate loaded percent
|
// calculate loaded percent
|
||||||
const loadedPercent = (loadedCount / displays.length);
|
const loadedPercent = (loadedCount / displays.length);
|
||||||
|
|
||||||
|
this.progressCover.style.width = `${(1.0 - loadedPercent) * 100}%`;
|
||||||
if (loadedPercent < 1.0) {
|
if (loadedPercent < 1.0) {
|
||||||
// Draw a box for the progress.
|
// show the progress bar and set width
|
||||||
draw.box(this.context, '#000000', 51, 428, 534, 22);
|
this.progressCover.parentNode.classList.add('show');
|
||||||
draw.box(this.context, '#ffffff', 53, 430, 530, 18);
|
|
||||||
// update the progress gif
|
|
||||||
draw.box(this.context, '#1d7fff', 55, 432, 526 * loadedPercent, 14);
|
|
||||||
} else {
|
} else {
|
||||||
// restore the background
|
// hide the progressbar after 1 second (lines up with with width transition animation)
|
||||||
this.context.drawImage(backgroundImage, 51, 428, 534, 22, 51, 428, 534, 22);
|
setTimeout(() => this.progressCover.parentNode.classList.remove('show'), 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
canvasClick(e) {
|
lineClick(e) {
|
||||||
const x = e.offsetX;
|
// get index
|
||||||
const y = e.offsetY;
|
const indexRaw = e.target?.parentNode?.dataset?.index;
|
||||||
// eliminate off canvas and outside area clicks
|
if (indexRaw === undefined) return;
|
||||||
if (!this.isActive()) return;
|
const index = +indexRaw;
|
||||||
if (y < 100 || y > 410) return;
|
|
||||||
if (x < 440 || x > 570) return;
|
|
||||||
|
|
||||||
// stop playing
|
// stop playing
|
||||||
navigation.message('navButton');
|
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 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);
|
||||||
this.hideCanvas();
|
this.elem.classList.remove('show');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// current weather conditions display
|
// current weather conditions display
|
||||||
/* globals WeatherDisplay, utils, STATUS, draw, luxon */
|
/* globals WeatherDisplay, utils, STATUS, luxon */
|
||||||
|
|
||||||
// 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', true);
|
||||||
|
|
||||||
// set max images
|
// set max images
|
||||||
this.dopplerRadarImageMax = 6;
|
this.dopplerRadarImageMax = 6;
|
||||||
|
@ -31,9 +31,6 @@ class Radar extends WeatherDisplay {
|
||||||
{ time: 1, si: 4 },
|
{ time: 1, si: 4 },
|
||||||
{ time: 12, si: 5 },
|
{ time: 12, si: 5 },
|
||||||
];
|
];
|
||||||
|
|
||||||
// pre-load background image (returns promise)
|
|
||||||
this.backgroundImage = utils.image.load('images/BackGround4_1.png');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getData(_weatherParameters) {
|
async getData(_weatherParameters) {
|
||||||
|
@ -163,7 +160,6 @@ class Radar extends WeatherDisplay {
|
||||||
const imgBlob = await utils.image.load(blob);
|
const imgBlob = await utils.image.load(blob);
|
||||||
|
|
||||||
// draw the entire image
|
// draw the entire image
|
||||||
|
|
||||||
workingContext.clearRect(0, 0, width, 1600);
|
workingContext.clearRect(0, 0, width, 1600);
|
||||||
workingContext.drawImage(imgBlob, 0, 0, width, 1600);
|
workingContext.drawImage(imgBlob, 0, 0, width, 1600);
|
||||||
|
|
||||||
|
@ -174,7 +170,7 @@ class Radar extends WeatherDisplay {
|
||||||
const cropCanvas = document.createElement('canvas');
|
const cropCanvas = document.createElement('canvas');
|
||||||
cropCanvas.width = 640;
|
cropCanvas.width = 640;
|
||||||
cropCanvas.height = 367;
|
cropCanvas.height = 367;
|
||||||
const cropContext = cropCanvas.getContext('2d');
|
const cropContext = cropCanvas.getContext('2d', { willReadFrequently: true });
|
||||||
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
|
||||||
|
@ -183,11 +179,20 @@ class Radar extends WeatherDisplay {
|
||||||
// merge the radar and map
|
// merge the radar and map
|
||||||
Radar.mergeDopplerRadarImage(context, cropContext);
|
Radar.mergeDopplerRadarImage(context, cropContext);
|
||||||
|
|
||||||
|
const elem = this.fillTemplate('frame', { map: { type: 'img', src: canvas.toDataURL() } });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
canvas,
|
canvas,
|
||||||
time,
|
time,
|
||||||
|
elem,
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// put the elements in the container
|
||||||
|
const scrollArea = this.elem.querySelector('.scroll-area');
|
||||||
|
scrollArea.innerHTML = '';
|
||||||
|
scrollArea.append(...radarInfo.map((r) => r.elem));
|
||||||
|
|
||||||
// set max length
|
// set max length
|
||||||
this.timing.totalScreens = radarInfo.length;
|
this.timing.totalScreens = radarInfo.length;
|
||||||
// store the images
|
// store the images
|
||||||
|
@ -199,31 +204,13 @@ class Radar extends WeatherDisplay {
|
||||||
|
|
||||||
async drawCanvas() {
|
async drawCanvas() {
|
||||||
super.drawCanvas();
|
super.drawCanvas();
|
||||||
if (this.screenIndex === -1) return;
|
|
||||||
this.context.drawImage(await this.backgroundImage, 0, 0);
|
|
||||||
const { DateTime } = luxon;
|
const { DateTime } = luxon;
|
||||||
// Title
|
const time = this.times[this.screenIndex].toLocaleString(DateTime.TIME_SIMPLE);
|
||||||
draw.text(this.context, 'Arial', 'bold 28pt', '#ffffff', 155, 60, 'Local', 2);
|
const timePadded = time.length >= 8 ? time : ` ${time}`;
|
||||||
draw.text(this.context, 'Arial', 'bold 28pt', '#ffffff', 155, 95, 'Radar', 2);
|
this.elem.querySelector('.header .right .time').innerHTML = timePadded;
|
||||||
|
|
||||||
draw.text(this.context, 'Star4000', 'bold 18pt', '#ffffff', 438, 49, 'PRECIP', 2, 'center');
|
// scroll to image
|
||||||
draw.text(this.context, 'Star4000', 'bold 18pt', '#ffffff', 298, 73, 'Light', 2);
|
this.elem.querySelector('.scroll-area').style.top = `${-this.screenIndex * 371}px`;
|
||||||
draw.text(this.context, 'Star4000', 'bold 18pt', '#ffffff', 517, 73, 'Heavy', 2);
|
|
||||||
|
|
||||||
let x = 362;
|
|
||||||
const y = 52;
|
|
||||||
draw.box(this.context, '#000000', x - 2, y - 2, 154, 28);
|
|
||||||
draw.box(this.context, 'rgb(49, 210, 22)', x, y, 17, 24); x += 19;
|
|
||||||
draw.box(this.context, 'rgb(28, 138, 18)', x, y, 17, 24); x += 19;
|
|
||||||
draw.box(this.context, 'rgb(20, 90, 15)', x, y, 17, 24); x += 19;
|
|
||||||
draw.box(this.context, 'rgb(10, 40, 10)', x, y, 17, 24); x += 19;
|
|
||||||
draw.box(this.context, 'rgb(196, 179, 70)', x, y, 17, 24); x += 19;
|
|
||||||
draw.box(this.context, 'rgb(190, 72, 19)', x, y, 17, 24); x += 19;
|
|
||||||
draw.box(this.context, 'rgb(171, 14, 14)', x, y, 17, 24); x += 19;
|
|
||||||
draw.box(this.context, 'rgb(115, 31, 4)', x, y, 17, 24); x += 19;
|
|
||||||
|
|
||||||
this.context.drawImage(this.data[this.screenIndex], 0, 0, 640, 367, 0, 113, 640, 367);
|
|
||||||
draw.text(this.context, 'Star4000 Small', '24pt', '#ffffff', 438, 105, this.times[this.screenIndex].toLocaleString(DateTime.TIME_SIMPLE), 2, 'center');
|
|
||||||
|
|
||||||
this.finishDraw();
|
this.finishDraw();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +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, 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', true);
|
||||||
|
|
||||||
// pre-load background image (returns promise)
|
|
||||||
this.backgroundImage = utils.image.load('images/BackGround5_1.png');
|
|
||||||
|
|
||||||
// timings
|
// timings
|
||||||
this.timing.totalScreens = 3;
|
this.timing.totalScreens = 3;
|
||||||
|
@ -19,14 +16,14 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
super.getData(_weatherParameters);
|
super.getData(_weatherParameters);
|
||||||
const weatherParameters = _weatherParameters ?? this.weatherParameters;
|
const weatherParameters = _weatherParameters ?? this.weatherParameters;
|
||||||
|
|
||||||
// pre-load the base map (returns promise)
|
// pre-load the base map
|
||||||
let src = 'images/Basemap2.png';
|
let baseMap = 'images/Basemap2.png';
|
||||||
if (weatherParameters.state === 'HI') {
|
if (weatherParameters.state === 'HI') {
|
||||||
src = 'images/HawaiiRadarMap4.png';
|
baseMap = 'images/HawaiiRadarMap4.png';
|
||||||
} else if (weatherParameters.state === 'AK') {
|
} else if (weatherParameters.state === 'AK') {
|
||||||
src = 'images/AlaskaRadarMap6.png';
|
baseMap = 'images/AlaskaRadarMap6.png';
|
||||||
}
|
}
|
||||||
this.baseMap = utils.image.load(src);
|
this.elem.querySelector('.map img').src = baseMap;
|
||||||
|
|
||||||
// map offset
|
// map offset
|
||||||
const offsetXY = {
|
const offsetXY = {
|
||||||
|
@ -34,10 +31,10 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
y: 117,
|
y: 117,
|
||||||
};
|
};
|
||||||
// get user's location in x/y
|
// get user's location in x/y
|
||||||
const sourceXY = this.getXYFromLatitudeLongitude(weatherParameters.latitude, weatherParameters.longitude, offsetXY.x, offsetXY.y, weatherParameters.state);
|
const sourceXY = RegionalForecast.getXYFromLatitudeLongitude(weatherParameters.latitude, weatherParameters.longitude, offsetXY.x, offsetXY.y, weatherParameters.state);
|
||||||
|
|
||||||
// get latitude and longitude limits
|
// get latitude and longitude limits
|
||||||
const minMaxLatLon = this.getMinMaxLatitudeLongitude(sourceXY.x, sourceXY.y, offsetXY.x, offsetXY.y, weatherParameters.state);
|
const minMaxLatLon = RegionalForecast.getMinMaxLatitudeLongitude(sourceXY.x, sourceXY.y, offsetXY.x, offsetXY.y, weatherParameters.state);
|
||||||
|
|
||||||
// get a target distance
|
// get a target distance
|
||||||
let targetDistance = 2.5;
|
let targetDistance = 2.5;
|
||||||
|
@ -66,7 +63,7 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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 regionalDataAll = await Promise.all(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);
|
||||||
|
@ -77,7 +74,7 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
const forecast = await utils.fetch.json(point.properties.forecast);
|
const forecast = await utils.fetch.json(point.properties.forecast);
|
||||||
|
|
||||||
// get XY on map for city
|
// get XY on map for city
|
||||||
const cityXY = this.getXYForCity(city, minMaxLatLon.maxLat, minMaxLatLon.minLon, weatherParameters.state);
|
const cityXY = RegionalForecast.getXYForCity(city, minMaxLatLon.maxLat, minMaxLatLon.minLon, weatherParameters.state);
|
||||||
|
|
||||||
// wait for the regional observation if it's not done yet
|
// wait for the regional observation if it's not done yet
|
||||||
const observation = await observationPromise;
|
const observation = await observationPromise;
|
||||||
|
@ -105,14 +102,12 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
RegionalForecast.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 ?? city.city}'`);
|
||||||
console.log(e);
|
console.log(e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
|
|
||||||
// wait for the forecasts
|
|
||||||
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);
|
||||||
|
|
||||||
|
@ -154,20 +149,21 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
// get the observation data
|
// get the observation data
|
||||||
const observation = await utils.fetch.json(`${station}/observations/latest`);
|
const observation = await utils.fetch.json(`${station}/observations/latest`);
|
||||||
// preload the image
|
// preload the image
|
||||||
|
if (!observation.properties.icon) return false;
|
||||||
utils.image.preload(icons.getWeatherRegionalIconFromIconLink(observation.properties.icon, !observation.properties.daytime));
|
utils.image.preload(icons.getWeatherRegionalIconFromIconLink(observation.properties.icon, !observation.properties.daytime));
|
||||||
// return the observation
|
// return the observation
|
||||||
return observation.properties;
|
return observation.properties;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`Unable to get regional observations for ${city.Name}`);
|
console.log(`Unable to get regional observations for ${city.Name ?? city.city}`);
|
||||||
console.error(e.status, e.responseJSON);
|
console.error(e.status, e.responseJSON);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// utility latitude/pixel conversions
|
// utility latitude/pixel conversions
|
||||||
getXYFromLatitudeLongitude(Latitude, Longitude, OffsetX, OffsetY, state) {
|
static getXYFromLatitudeLongitude(Latitude, Longitude, OffsetX, OffsetY, state) {
|
||||||
if (state === 'AK') return this.getXYFromLatitudeLongitudeAK(Latitude, Longitude, OffsetX, OffsetY);
|
if (state === 'AK') return RegionalForecast.getXYFromLatitudeLongitudeAK(Latitude, Longitude, OffsetX, OffsetY);
|
||||||
if (state === 'HI') return this.getXYFromLatitudeLongitudeHI(Latitude, Longitude, OffsetX, OffsetY);
|
if (state === 'HI') return RegionalForecast.getXYFromLatitudeLongitudeHI(Latitude, Longitude, OffsetX, OffsetY);
|
||||||
let y = 0;
|
let y = 0;
|
||||||
let x = 0;
|
let x = 0;
|
||||||
const ImgHeight = 1600;
|
const ImgHeight = 1600;
|
||||||
|
@ -248,9 +244,9 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
return { x, y };
|
return { x, y };
|
||||||
}
|
}
|
||||||
|
|
||||||
getMinMaxLatitudeLongitude(X, Y, OffsetX, OffsetY, state) {
|
static getMinMaxLatitudeLongitude(X, Y, OffsetX, OffsetY, state) {
|
||||||
if (state === 'AK') return this.getMinMaxLatitudeLongitudeAK(X, Y, OffsetX, OffsetY);
|
if (state === 'AK') return RegionalForecast.getMinMaxLatitudeLongitudeAK(X, Y, OffsetX, OffsetY);
|
||||||
if (state === 'HI') return this.getMinMaxLatitudeLongitudeHI(X, Y, OffsetX, OffsetY);
|
if (state === 'HI') return RegionalForecast.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;
|
||||||
|
@ -283,9 +279,9 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getXYForCity(City, MaxLatitude, MinLongitude, state) {
|
static getXYForCity(City, MaxLatitude, MinLongitude, state) {
|
||||||
if (state === 'AK') this.getXYForCityAK(City, MaxLatitude, MinLongitude);
|
if (state === 'AK') RegionalForecast.getXYForCityAK(City, MaxLatitude, MinLongitude);
|
||||||
if (state === 'HI') this.getXYForCityHI(City, MaxLatitude, MinLongitude);
|
if (state === 'HI') RegionalForecast.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;
|
||||||
|
|
||||||
|
@ -328,61 +324,61 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
return city.match(/[^-;/\\,]*/)[0].substr(0, 12);
|
return city.match(/[^-;/\\,]*/)[0].substr(0, 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
async drawCanvas() {
|
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
|
|
||||||
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);
|
|
||||||
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 the appropriate title
|
// draw the appropriate title
|
||||||
|
const titleTop = this.elem.querySelector('.title.dual .top');
|
||||||
|
const titleBottom = this.elem.querySelector('.title.dual .bottom');
|
||||||
if (this.screenIndex === 0) {
|
if (this.screenIndex === 0) {
|
||||||
draw.titleText(this.context, 'Regional', 'Observations');
|
titleTop.innerHTML = 'Regional';
|
||||||
|
titleBottom.innerHTML = 'Observations';
|
||||||
} else {
|
} else {
|
||||||
const 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' });
|
||||||
|
titleTop.innerHTML = 'Forecast for';
|
||||||
// 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);
|
titleBottom.innerHTML = dayName;
|
||||||
} else {
|
} else {
|
||||||
draw.titleText(this.context, 'Forecast for', `${dayName} Night`);
|
titleBottom.innerHTML = `${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);
|
const scale = 640 / (offsetXY.x * 2);
|
||||||
await Promise.all(data.map(async (city) => {
|
const map = this.elem.querySelector('.map');
|
||||||
|
map.style.zoom = scale;
|
||||||
|
map.style.top = `-${sourceXY.y}px`;
|
||||||
|
map.style.left = `-${sourceXY.x}px`;
|
||||||
|
|
||||||
|
const cities = data.map((city) => {
|
||||||
|
const fill = {};
|
||||||
const period = city[this.screenIndex];
|
const period = city[this.screenIndex];
|
||||||
// draw the icon if possible
|
|
||||||
const icon = icons.getWeatherRegionalIconFromIconLink(period.icon, !period.daytime);
|
|
||||||
if (icon) {
|
|
||||||
this.gifs.push(await utils.image.superGifAsync({
|
|
||||||
src: icon,
|
|
||||||
max_width: 42,
|
|
||||||
auto_play: true,
|
|
||||||
canvas: this.canvas,
|
|
||||||
x: period.x,
|
|
||||||
y: period.y - 15 + mapYOff,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// City Name
|
fill.icon = { type: 'img', src: icons.getWeatherRegionalIconFromIconLink(period.icon, !period.daytime) };
|
||||||
draw.text(this.context, 'Star4000', '20px', '#ffffff', period.x - 40, period.y - 15 + mapYOff, period.name, 2);
|
fill.city = period.name;
|
||||||
|
|
||||||
// Temperature
|
|
||||||
let { temperature } = period;
|
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);
|
fill.temp = temperature;
|
||||||
}));
|
|
||||||
|
const elem = this.fillTemplate('location', fill);
|
||||||
|
elem.style.left = `${period.x}px`;
|
||||||
|
elem.style.top = `${period.y}px`;
|
||||||
|
|
||||||
|
return elem;
|
||||||
|
});
|
||||||
|
|
||||||
|
const locationContainer = this.elem.querySelector('.location-container');
|
||||||
|
locationContainer.innerHTML = '';
|
||||||
|
locationContainer.append(...cities);
|
||||||
|
|
||||||
this.finishDraw();
|
this.finishDraw();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,11 @@
|
||||||
// travel forecast display
|
// travel forecast display
|
||||||
/* globals WeatherDisplay, utils, STATUS, UNITS, draw, navigation, icons, luxon, TravelCities */
|
/* globals WeatherDisplay, utils, STATUS, UNITS, 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 {
|
||||||
constructor(navId, elemId, defaultActive) {
|
constructor(navId, elemId, defaultActive) {
|
||||||
// special height and width for scrolling
|
// special height and width for scrolling
|
||||||
super(navId, elemId, 'Travel Forecast', defaultActive);
|
super(navId, elemId, 'Travel Forecast', defaultActive);
|
||||||
// pre-load background image (returns promise)
|
|
||||||
this.backgroundImage = utils.image.load('images/BackGround6_1.png');
|
|
||||||
|
|
||||||
// height of one city in the travel forecast
|
|
||||||
this.cityHeight = 72;
|
|
||||||
|
|
||||||
// set up the timing
|
// set up the timing
|
||||||
this.timing.baseDelay = 20;
|
this.timing.baseDelay = 20;
|
||||||
|
@ -18,7 +13,7 @@ class TravelForecast extends WeatherDisplay {
|
||||||
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 = 75 * 4;
|
||||||
this.timing.delay = [150 + timingStep];
|
this.timing.delay = [150 + timingStep];
|
||||||
// add additional pages
|
// add additional pages
|
||||||
for (let i = 0; i < pages; i += 1) this.timing.delay.push(timingStep);
|
for (let i = 0; i < pages; i += 1) this.timing.delay.push(timingStep);
|
||||||
|
@ -49,7 +44,7 @@ class TravelForecast extends WeatherDisplay {
|
||||||
} 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, error: true };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -69,47 +64,23 @@ class TravelForecast extends WeatherDisplay {
|
||||||
}
|
}
|
||||||
|
|
||||||
async drawLongCanvas() {
|
async drawLongCanvas() {
|
||||||
// create the "long" canvas if necessary
|
// get the element and populate
|
||||||
if (!this.longCanvas) {
|
const list = this.elem.querySelector('.travel-lines');
|
||||||
this.longCanvas = document.createElement('canvas');
|
list.innerHTML = '';
|
||||||
this.longCanvas.width = 640;
|
|
||||||
this.longCanvas.height = 1728;
|
|
||||||
this.longContext = this.longCanvas.getContext('2d');
|
|
||||||
this.longCanvasGifs = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// stop all gifs
|
|
||||||
this.longCanvasGifs.forEach((gif) => gif.pause());
|
|
||||||
// delete the gifs
|
|
||||||
this.longCanvasGifs.length = 0;
|
|
||||||
|
|
||||||
// set up variables
|
// set up variables
|
||||||
const cities = this.data;
|
const cities = this.data;
|
||||||
|
|
||||||
// clean up existing gifs
|
const lines = cities.map((city) => {
|
||||||
this.gifs.forEach((gif) => gif.pause());
|
if (city.error) return false;
|
||||||
// delete the gifs
|
const fillValues = {};
|
||||||
this.gifs.length = 0;
|
|
||||||
|
|
||||||
this.longContext.clearRect(0, 0, this.longCanvas.width, this.longCanvas.height);
|
|
||||||
|
|
||||||
// draw the "long" canvas with all cities
|
|
||||||
draw.box(this.longContext, 'rgb(35, 50, 112)', 0, 0, 640, TravelCities.length * this.cityHeight);
|
|
||||||
|
|
||||||
for (let i = 0; i <= 4; i += 1) {
|
|
||||||
const y = i * 346;
|
|
||||||
draw.horizontalGradient(this.longContext, 0, y, 640, y + 346, '#102080', '#001040');
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(cities.map(async (city, index) => {
|
|
||||||
// calculate base y value
|
|
||||||
const y = 50 + this.cityHeight * index;
|
|
||||||
|
|
||||||
// city name
|
// city name
|
||||||
draw.text(this.longContext, 'Star4000 Large Compressed', '24pt', '#FFFF00', 80, y, city.name, 2);
|
fillValues.city = city;
|
||||||
|
|
||||||
// check for forecast data
|
// check for forecast data
|
||||||
if (city.icon) {
|
if (city.icon) {
|
||||||
|
fillValues.city = city.name;
|
||||||
// get temperatures and convert if necessary
|
// get temperatures and convert if necessary
|
||||||
let { low, high } = city;
|
let { low, high } = city;
|
||||||
|
|
||||||
|
@ -122,25 +93,16 @@ class TravelForecast extends WeatherDisplay {
|
||||||
const lowString = Math.round(low).toString();
|
const lowString = Math.round(low).toString();
|
||||||
const highString = Math.round(high).toString();
|
const highString = Math.round(high).toString();
|
||||||
|
|
||||||
const xLow = (500 - (lowString.length * 20));
|
fillValues.low = lowString;
|
||||||
draw.text(this.longContext, 'Star4000 Large', '24pt', '#FFFF00', xLow, y, lowString, 2);
|
fillValues.high = highString;
|
||||||
|
|
||||||
const xHigh = (560 - (highString.length * 20));
|
fillValues.icon = { type: 'img', src: city.icon };
|
||||||
draw.text(this.longContext, 'Star4000 Large', '24pt', '#FFFF00', xHigh, y, highString, 2);
|
|
||||||
|
|
||||||
this.longCanvasGifs.push(await utils.image.superGifAsync({
|
|
||||||
src: city.icon,
|
|
||||||
auto_play: true,
|
|
||||||
canvas: this.longCanvas,
|
|
||||||
x: 330,
|
|
||||||
y: y - 35,
|
|
||||||
max_width: 47,
|
|
||||||
}));
|
|
||||||
} else {
|
} else {
|
||||||
draw.text(this.longContext, 'Star4000 Small', '24pt', '#FFFFFF', 400, y - 18, 'NO TRAVEL', 2);
|
fillValues.error = 'NO TRAVEL DATA AVAILABLE';
|
||||||
draw.text(this.longContext, 'Star4000 Small', '24pt', '#FFFFFF', 400, y, 'DATA AVAILABLE', 2);
|
|
||||||
}
|
}
|
||||||
}));
|
return this.fillTemplate('travel-row', fillValues);
|
||||||
|
}).filter((d) => d);
|
||||||
|
list.append(...lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
async drawCanvas() {
|
async drawCanvas() {
|
||||||
|
@ -151,18 +113,7 @@ class TravelForecast extends WeatherDisplay {
|
||||||
// set up variables
|
// set up variables
|
||||||
const cities = this.data;
|
const cities = this.data;
|
||||||
|
|
||||||
// draw the standard context
|
this.elem.querySelector('.header .title.dual .bottom').innerHTML = `For ${TravelForecast.getTravelCitiesDayName(cities)}`;
|
||||||
this.context.drawImage(await this.backgroundImage, 0, 0);
|
|
||||||
draw.horizontalGradientSingle(this.context, 0, 30, 500, 90, draw.topColor1, draw.topColor2);
|
|
||||||
draw.triangle(this.context, 'rgb(28, 10, 87)', 500, 30, 450, 90, 500, 90);
|
|
||||||
|
|
||||||
draw.titleText(this.context, 'Travel Forecast', `For ${TravelForecast.getTravelCitiesDayName(cities)}`);
|
|
||||||
|
|
||||||
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFF00', 455, 105, 'LOW', 2);
|
|
||||||
draw.text(this.context, 'Star4000 Small', '24pt', '#FFFF00', 510, 105, 'HIGH', 2);
|
|
||||||
|
|
||||||
// copy the scrolled portion of the canvas for the initial run before the scrolling starts
|
|
||||||
this.context.drawImage(this.longCanvas, 0, 0, 640, 289, 0, 110, 640, 289);
|
|
||||||
|
|
||||||
this.finishDraw();
|
this.finishDraw();
|
||||||
}
|
}
|
||||||
|
@ -180,17 +131,14 @@ class TravelForecast extends WeatherDisplay {
|
||||||
|
|
||||||
// base count change callback
|
// base count change callback
|
||||||
baseCountChange(count) {
|
baseCountChange(count) {
|
||||||
// get a fresh canvas
|
|
||||||
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(this.elem.querySelector('.travel-lines').getBoundingClientRect().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;
|
||||||
|
|
||||||
// copy the scrolled portion of the canvas
|
// copy the scrolled portion of the canvas
|
||||||
this.context.drawImage(longCanvas, 0, offsetY, 640, 289, 0, 110, 640, 289);
|
this.elem.querySelector('.main').scrollTo(0, offsetY);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getTravelCitiesDayName(cities) {
|
static getTravelCitiesDayName(cities) {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
// radar utilities
|
// radar utilities
|
||||||
|
|
||||||
/* globals SuperGif */
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
const utils = (() => {
|
const utils = (() => {
|
||||||
// ****************************** weather data ********************************
|
// ****************************** weather data ********************************
|
||||||
|
@ -30,40 +29,17 @@ const utils = (() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// async version of SuperGif
|
|
||||||
const superGifAsync = (e) => new Promise((resolve) => {
|
|
||||||
const gif = new SuperGif(e);
|
|
||||||
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
|
||||||
// a list of cached icons is used to avoid hitting the cache multiple times
|
// a list of cached icons is used to avoid hitting the cache multiple times
|
||||||
const cachedImages = [];
|
const cachedImages = [];
|
||||||
const preload = (src) => {
|
const preload = (src) => {
|
||||||
if (cachedImages.includes(src)) return false;
|
if (cachedImages.includes(src)) return false;
|
||||||
const img = new Image();
|
blob(src);
|
||||||
img.scr = src;
|
// cachedImages.push(src);
|
||||||
cachedImages.push(src);
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// draw an image on a local canvas and return the context
|
|
||||||
const drawLocalCanvas = (img) => {
|
|
||||||
// create a canvas
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.width = img.width;
|
|
||||||
canvas.height = img.height;
|
|
||||||
|
|
||||||
// get the context
|
|
||||||
const context = canvas.getContext('2d');
|
|
||||||
context.imageSmoothingEnabled = false;
|
|
||||||
|
|
||||||
// draw the image
|
|
||||||
context.drawImage(img, 0, 0);
|
|
||||||
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}`);
|
||||||
|
@ -136,99 +112,21 @@ const utils = (() => {
|
||||||
const wrap = (x, m) => ((x % m) + m) % m;
|
const wrap = (x, m) => ((x % m) + m) % m;
|
||||||
|
|
||||||
// ********************************* strings *********************************************
|
// ********************************* strings *********************************************
|
||||||
const wordWrap = (_str, ...rest) => {
|
const locationCleanup = (input) => {
|
||||||
// discuss at: https://locutus.io/php/wordwrap/
|
// regexes to run
|
||||||
// original by: Jonas Raoni Soares Silva (https://www.jsfromhell.com)
|
const regexes = [
|
||||||
// improved by: Nick Callen
|
// "Chicago / West Chicago", removes before slash
|
||||||
// improved by: Kevin van Zonneveld (https://kvz.io)
|
/^[A-Za-z ]+ \/ /,
|
||||||
// improved by: Sakimori
|
// "Chicago/Waukegan" removes before slash
|
||||||
// revised by: Jonas Raoni Soares Silva (https://www.jsfromhell.com)
|
/^[A-Za-z ]+\//,
|
||||||
// bugfixed by: Michael Grier
|
// "Chicago, Chicago O'hare" removes before comma
|
||||||
// bugfixed by: Feras ALHAEK
|
/^[A-Za-z ]+, /,
|
||||||
// improved by: Rafał Kukawski (https://kukawski.net)
|
];
|
||||||
// example 1: wordwrap('Kevin van Zonneveld', 6, '|', true)
|
|
||||||
// returns 1: 'Kevin|van|Zonnev|eld'
|
|
||||||
// example 2: wordwrap('The quick brown fox jumped over the lazy dog.', 20, '<br />\n')
|
|
||||||
// returns 2: 'The quick brown fox<br />\njumped over the lazy<br />\ndog.'
|
|
||||||
// example 3: wordwrap('Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.')
|
|
||||||
// returns 3: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod\ntempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim\nveniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea\ncommodo consequat.'
|
|
||||||
const intWidth = rest[0] ?? 75;
|
|
||||||
const strBreak = rest[1] ?? '\n';
|
|
||||||
const cut = rest[2] ?? false;
|
|
||||||
|
|
||||||
let i;
|
// run all regexes
|
||||||
let j;
|
return regexes.reduce((value, regex) => value.replace(regex, ''), input);
|
||||||
let line;
|
|
||||||
|
|
||||||
let str = _str;
|
|
||||||
str += '';
|
|
||||||
|
|
||||||
if (intWidth < 1) {
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reLineBreaks = /\r\n|\n|\r/;
|
|
||||||
const reBeginningUntilFirstWhitespace = /^\S*/;
|
|
||||||
const reLastCharsWithOptionalTrailingWhitespace = /\S*(\s)?$/;
|
|
||||||
|
|
||||||
const lines = str.split(reLineBreaks);
|
|
||||||
const l = lines.length;
|
|
||||||
let match;
|
|
||||||
|
|
||||||
// for each line of text
|
|
||||||
// eslint-disable-next-line no-plusplus
|
|
||||||
for (i = 0; i < l; lines[i++] += line) {
|
|
||||||
line = lines[i];
|
|
||||||
lines[i] = '';
|
|
||||||
|
|
||||||
while (line.length > intWidth) {
|
|
||||||
// get slice of length one char above limit
|
|
||||||
const slice = line.slice(0, intWidth + 1);
|
|
||||||
|
|
||||||
// remove leading whitespace from rest of line to parse
|
|
||||||
let ltrim = 0;
|
|
||||||
// remove trailing whitespace from new line content
|
|
||||||
let rtrim = 0;
|
|
||||||
|
|
||||||
match = slice.match(reLastCharsWithOptionalTrailingWhitespace);
|
|
||||||
|
|
||||||
// if the slice ends with whitespace
|
|
||||||
if (match[1]) {
|
|
||||||
// then perfect moment to cut the line
|
|
||||||
j = intWidth;
|
|
||||||
ltrim = 1;
|
|
||||||
} else {
|
|
||||||
// otherwise cut at previous whitespace
|
|
||||||
j = slice.length - match[0].length;
|
|
||||||
|
|
||||||
if (j) {
|
|
||||||
rtrim = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// but if there is no previous whitespace
|
|
||||||
// and cut is forced
|
|
||||||
// cut just at the defined limit
|
|
||||||
if (!j && cut && intWidth) {
|
|
||||||
j = intWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if cut wasn't forced
|
|
||||||
// cut at next possible whitespace after the limit
|
|
||||||
if (!j) {
|
|
||||||
const charsUntilNextWhitespace = (line.slice(intWidth).match(reBeginningUntilFirstWhitespace) || [''])[0];
|
|
||||||
|
|
||||||
j = slice.length + charsUntilNextWhitespace.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lines[i] += line.slice(0, j - rtrim);
|
|
||||||
line = line.slice(j + ltrim);
|
|
||||||
lines[i] += line.length ? strBreak : '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines.join('\n');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ********************************* cors ********************************************
|
// ********************************* cors ********************************************
|
||||||
// rewrite some urls for local server
|
// rewrite some urls for local server
|
||||||
const rewriteUrl = (_url) => {
|
const rewriteUrl = (_url) => {
|
||||||
|
@ -255,9 +153,9 @@ const utils = (() => {
|
||||||
// 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);
|
||||||
const url = new URL(corsUrl);
|
const url = new URL(corsUrl, `${window.location.origin}/`);
|
||||||
// match the security protocol
|
// match the security protocol when not on localhost
|
||||||
url.protocol = window.location.protocol;
|
url.protocol = window.location.hostname !== 'localhost' ? window.location.protocol : url.protocol;
|
||||||
// add parameters if necessary
|
// add parameters if necessary
|
||||||
if (params.data) {
|
if (params.data) {
|
||||||
Object.keys(params.data).forEach((key) => {
|
Object.keys(params.data).forEach((key) => {
|
||||||
|
@ -286,13 +184,18 @@ const utils = (() => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const elemForEach = (selector, callback) => {
|
||||||
|
[...document.querySelectorAll(selector)].forEach(callback);
|
||||||
|
};
|
||||||
|
|
||||||
// return an orderly object
|
// return an orderly object
|
||||||
return {
|
return {
|
||||||
|
elem: {
|
||||||
|
forEach: elemForEach,
|
||||||
|
},
|
||||||
image: {
|
image: {
|
||||||
load: loadImg,
|
load: loadImg,
|
||||||
superGifAsync,
|
|
||||||
preload,
|
preload,
|
||||||
drawLocalCanvas,
|
|
||||||
},
|
},
|
||||||
weather: {
|
weather: {
|
||||||
getPoint,
|
getPoint,
|
||||||
|
@ -318,7 +221,7 @@ const utils = (() => {
|
||||||
wrap,
|
wrap,
|
||||||
},
|
},
|
||||||
string: {
|
string: {
|
||||||
wordWrap,
|
locationCleanup,
|
||||||
},
|
},
|
||||||
cors: {
|
cors: {
|
||||||
rewriteUrl,
|
rewriteUrl,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// base weather display class
|
// base weather display class
|
||||||
|
|
||||||
/* globals navigation, utils, draw, UNITS, luxon, currentWeatherScroll */
|
/* globals navigation, utils, luxon, currentWeatherScroll */
|
||||||
|
|
||||||
const STATUS = {
|
const STATUS = {
|
||||||
loading: Symbol('loading'),
|
loading: Symbol('loading'),
|
||||||
|
@ -31,8 +31,8 @@ class WeatherDisplay {
|
||||||
this.navBaseCount = 0;
|
this.navBaseCount = 0;
|
||||||
this.screenIndex = -1; // special starting condition
|
this.screenIndex = -1; // special starting condition
|
||||||
|
|
||||||
// create the canvas, also stores this.elemId
|
// store elemId once
|
||||||
this.createCanvas(elemId);
|
this.storeElemId(elemId);
|
||||||
|
|
||||||
if (elemId !== 'progress') this.addCheckbox(defaultEnabled);
|
if (elemId !== 'progress') this.addCheckbox(defaultEnabled);
|
||||||
if (this.enabled) {
|
if (this.enabled) {
|
||||||
|
@ -41,6 +41,9 @@ class WeatherDisplay {
|
||||||
this.setStatus(STATUS.disabled);
|
this.setStatus(STATUS.disabled);
|
||||||
}
|
}
|
||||||
this.startNavCount();
|
this.startNavCount();
|
||||||
|
|
||||||
|
// get any templates
|
||||||
|
this.loadTemplates();
|
||||||
}
|
}
|
||||||
|
|
||||||
addCheckbox(defaultEnabled = true) {
|
addCheckbox(defaultEnabled = true) {
|
||||||
|
@ -92,18 +95,10 @@ class WeatherDisplay {
|
||||||
this.loadingStatus = state;
|
this.loadingStatus = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
createCanvas(elemId, width = 640, height = 480) {
|
storeElemId(elemId) {
|
||||||
// only create it once
|
// only create it once
|
||||||
if (this.elemId) return;
|
if (this.elemId) return;
|
||||||
this.elemId = elemId;
|
this.elemId = elemId;
|
||||||
|
|
||||||
// create a canvas
|
|
||||||
const canvas = document.createElement('template');
|
|
||||||
canvas.innerHTML = `<canvas id='${`${elemId}Canvas`}' width='${width}' height='${height}' style='display: none;' />`;
|
|
||||||
|
|
||||||
// add to the page
|
|
||||||
const container = document.getElementById('container');
|
|
||||||
container.appendChild(canvas.content.firstChild);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// get necessary data for this display
|
// get necessary data for this display
|
||||||
|
@ -136,46 +131,27 @@ class WeatherDisplay {
|
||||||
}
|
}
|
||||||
|
|
||||||
drawCanvas() {
|
drawCanvas() {
|
||||||
// stop all gifs
|
|
||||||
this.gifs.forEach((gif) => gif.pause());
|
|
||||||
// delete the gifs
|
|
||||||
this.gifs.length = 0;
|
|
||||||
// refresh the canvas
|
|
||||||
this.canvas = document.getElementById(`${this.elemId}Canvas`);
|
|
||||||
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) this.screenIndex = 0;
|
if (this.screenIndex < 0) this.screenIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
finishDraw() {
|
finishDraw() {
|
||||||
let OkToDrawCurrentConditions = true;
|
let OkToDrawCurrentConditions = true;
|
||||||
let OkToDrawNoaaImage = true;
|
|
||||||
let OkToDrawCurrentDateTime = true;
|
let OkToDrawCurrentDateTime = true;
|
||||||
let OkToDrawLogoImage = true;
|
|
||||||
// let OkToDrawCustomScrollText = false;
|
// let OkToDrawCustomScrollText = false;
|
||||||
let bottom;
|
let bottom;
|
||||||
|
|
||||||
// visibility tests
|
// visibility tests
|
||||||
// if (_ScrollText !== '') OkToDrawCustomScrollText = true;
|
// if (_ScrollText !== '') OkToDrawCustomScrollText = true;
|
||||||
if (this.elemId === 'almanac') OkToDrawNoaaImage = false;
|
|
||||||
if (this.elemId === 'travelForecast') OkToDrawNoaaImage = false;
|
|
||||||
if (this.elemId === 'hourly') OkToDrawNoaaImage = false;
|
|
||||||
if (this.elemId === 'regionalForecast') OkToDrawNoaaImage = false;
|
|
||||||
if (this.elemId === 'progress') {
|
if (this.elemId === 'progress') {
|
||||||
OkToDrawCurrentConditions = false;
|
OkToDrawCurrentConditions = false;
|
||||||
OkToDrawNoaaImage = false;
|
|
||||||
}
|
}
|
||||||
if (this.elemId === 'radar') {
|
if (this.elemId === 'radar') {
|
||||||
OkToDrawCurrentConditions = false;
|
OkToDrawCurrentConditions = false;
|
||||||
OkToDrawCurrentDateTime = false;
|
OkToDrawCurrentDateTime = false;
|
||||||
OkToDrawNoaaImage = false;
|
|
||||||
// OkToDrawCustomScrollText = false;
|
|
||||||
}
|
}
|
||||||
if (this.elemId === 'hazards') {
|
if (this.elemId === 'hazards') {
|
||||||
OkToDrawNoaaImage = false;
|
|
||||||
bottom = true;
|
bottom = true;
|
||||||
OkToDrawLogoImage = false;
|
|
||||||
}
|
}
|
||||||
// draw functions
|
// draw functions
|
||||||
if (OkToDrawCurrentDateTime) {
|
if (OkToDrawCurrentDateTime) {
|
||||||
|
@ -185,10 +161,8 @@ class WeatherDisplay {
|
||||||
setInterval(() => this.drawCurrentDateTime(bottom), 100);
|
setInterval(() => this.drawCurrentDateTime(bottom), 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (OkToDrawLogoImage) this.drawLogoImage();
|
|
||||||
if (OkToDrawNoaaImage) this.drawNoaaImage();
|
|
||||||
if (OkToDrawCurrentConditions) {
|
if (OkToDrawCurrentConditions) {
|
||||||
currentWeatherScroll.start(this.context);
|
currentWeatherScroll.start();
|
||||||
} else {
|
} else {
|
||||||
// cause a reset if the progress screen is displayed
|
// cause a reset if the progress screen is displayed
|
||||||
currentWeatherScroll.stop(this.elemId === 'progress');
|
currentWeatherScroll.stop(this.elemId === 'progress');
|
||||||
|
@ -197,81 +171,27 @@ class WeatherDisplay {
|
||||||
// if (OkToDrawCustomScrollText) DrawCustomScrollText(WeatherParameters, context);
|
// if (OkToDrawCustomScrollText) DrawCustomScrollText(WeatherParameters, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawCurrentDateTime(bottom) {
|
drawCurrentDateTime() {
|
||||||
// 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 size = '24pt';
|
|
||||||
const color = '#ffffff';
|
|
||||||
const shadow = 2;
|
|
||||||
|
|
||||||
// on the first pass store the background for the date and time
|
|
||||||
if (!this.dateTimeBackground) {
|
|
||||||
const bg = this.context.getImageData(410, 30, 175, 60);
|
|
||||||
// test background draw complete and skip drawing if there is no background yet
|
|
||||||
if (bg.data[0] === 0) return;
|
|
||||||
// store the background
|
|
||||||
this.dateTimeBackground = bg;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear the date and time area.
|
|
||||||
if (bottom) {
|
|
||||||
draw.box(this.context, 'rgb(25, 50, 112)', 0, 389, 640, 16);
|
|
||||||
} else {
|
|
||||||
this.context.putImageData(this.dateTimeBackground, 410, 30);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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; let y;
|
if (this.lastTime !== time) {
|
||||||
if (bottom) {
|
utils.elem.forEach('.date-time.time', (elem) => { elem.innerHTML = time.toUpperCase(); });
|
||||||
x = 400;
|
|
||||||
y = 402;
|
|
||||||
} else {
|
|
||||||
x = 410;
|
|
||||||
y = 65;
|
|
||||||
}
|
}
|
||||||
if (navigation.units() === UNITS.metric) {
|
this.lastTime = time;
|
||||||
x += 45;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (this.lastDate !== date) {
|
||||||
x = 55;
|
utils.elem.forEach('.date-time.date', (elem) => { elem.innerHTML = date.toUpperCase(); });
|
||||||
y = 402;
|
|
||||||
} else {
|
|
||||||
x = 410;
|
|
||||||
y = 85;
|
|
||||||
}
|
}
|
||||||
draw.text(this.context, font, size, color, x, y, date.toUpperCase(), shadow);
|
this.lastDate = date;
|
||||||
}
|
|
||||||
|
|
||||||
async drawNoaaImage() {
|
|
||||||
// load the image and store locally
|
|
||||||
if (!this.drawNoaaImage.image) {
|
|
||||||
this.drawNoaaImage.image = utils.image.load('images/noaa5.gif');
|
|
||||||
}
|
|
||||||
// wait for the image to load completely
|
|
||||||
const img = await this.drawNoaaImage.image;
|
|
||||||
this.context.drawImage(img, 356, 39);
|
|
||||||
}
|
|
||||||
|
|
||||||
async drawLogoImage() {
|
|
||||||
// load the image and store locally
|
|
||||||
if (!this.drawLogoImage.image) {
|
|
||||||
this.drawLogoImage.image = utils.image.load('images/Logo3.png');
|
|
||||||
}
|
|
||||||
// wait for the image load completely
|
|
||||||
const img = await this.drawLogoImage.image;
|
|
||||||
this.context.drawImage(img, 50, 30, 85, 67);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// show/hide the canvas and start/stop the navigation timer
|
// show/hide the canvas and start/stop the navigation timer
|
||||||
|
@ -281,23 +201,18 @@ class WeatherDisplay {
|
||||||
if (navCmd === navigation.msg.command.firstFrame) this.navNext(navCmd);
|
if (navCmd === navigation.msg.command.firstFrame) this.navNext(navCmd);
|
||||||
if (navCmd === navigation.msg.command.lastFrame) this.navPrev(navCmd);
|
if (navCmd === navigation.msg.command.lastFrame) this.navPrev(navCmd);
|
||||||
|
|
||||||
// see if the canvas is already showing
|
this.startNavCount();
|
||||||
if (this.canvas.style.display === 'block') return false;
|
|
||||||
|
|
||||||
// show the canvas
|
this.elem.classList.add('show');
|
||||||
this.canvas.style.display = 'block';
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hideCanvas() {
|
hideCanvas() {
|
||||||
this.resetNavBaseCount();
|
this.resetNavBaseCount();
|
||||||
|
this.elem.classList.remove('show');
|
||||||
if (!this.canvas) return;
|
|
||||||
this.canvas.style.display = 'none';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isActive() {
|
isActive() {
|
||||||
return document.getElementById(`${this.elemId}Canvas`).offsetParent !== null;
|
return this.elem.offsetHeight !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
isEnabled() {
|
isEnabled() {
|
||||||
|
@ -452,7 +367,6 @@ class WeatherDisplay {
|
||||||
clearInterval(this.navInterval);
|
clearInterval(this.navInterval);
|
||||||
this.navInterval = undefined;
|
this.navInterval = undefined;
|
||||||
}
|
}
|
||||||
this.startNavCount();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sendNavDisplayMessage(message) {
|
sendNavDisplayMessage(message) {
|
||||||
|
@ -461,4 +375,44 @@ class WeatherDisplay {
|
||||||
type: message,
|
type: message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadTemplates() {
|
||||||
|
this.templates = {};
|
||||||
|
this.elem = document.getElementById(`${this.elemId}-html`);
|
||||||
|
if (!this.elem) return;
|
||||||
|
const templates = this.elem.querySelectorAll('.template');
|
||||||
|
templates.forEach((template) => {
|
||||||
|
const className = template.classList[0];
|
||||||
|
const node = template.cloneNode(true);
|
||||||
|
node.classList.remove('template');
|
||||||
|
this.templates[className] = node;
|
||||||
|
template.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fillTemplate(name, fillValues) {
|
||||||
|
// get the template
|
||||||
|
const templateNode = this.templates[name];
|
||||||
|
if (!templateNode) return false;
|
||||||
|
|
||||||
|
// clone it
|
||||||
|
const template = templateNode.cloneNode(true);
|
||||||
|
|
||||||
|
Object.entries(fillValues).forEach(([key, value]) => {
|
||||||
|
// get the specified element
|
||||||
|
const elem = template.querySelector(`.${key}`);
|
||||||
|
if (!elem) return;
|
||||||
|
|
||||||
|
// fill based on type provided
|
||||||
|
if (typeof value === 'string' || typeof value === 'number') {
|
||||||
|
// string and number fill the first found selector
|
||||||
|
elem.innerHTML = value;
|
||||||
|
} else if (value?.type === 'img') {
|
||||||
|
// fill the image source
|
||||||
|
elem.querySelector('img').src = value.src;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return template;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,288 +0,0 @@
|
||||||
var GetWeatherHazards3 = function (WeatherParameters) {
|
|
||||||
var ZoneId = WeatherParameters.ZoneId;
|
|
||||||
var HazardUrls = [];
|
|
||||||
var HazardCounter = 0;
|
|
||||||
|
|
||||||
WeatherParameters.WeatherHazardConditions =
|
|
||||||
{
|
|
||||||
ZoneId: WeatherParameters.ZoneId,
|
|
||||||
Hazards: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
var Url = 'https://alerts.weather.gov/cap/wwaatmget.php?x=' + ZoneId + '&y=0';
|
|
||||||
|
|
||||||
// Load the xml file using ajax
|
|
||||||
$.ajaxCORS({
|
|
||||||
type: 'GET',
|
|
||||||
url: Url,
|
|
||||||
dataType: 'text',
|
|
||||||
crossDomain: true,
|
|
||||||
cache: false,
|
|
||||||
success: function (text) {
|
|
||||||
// IE doesn't support XML tags with colons.
|
|
||||||
text = text.replaceAll('<cap:', '<cap_');
|
|
||||||
text = text.replaceAll('</cap:', '</cap_');
|
|
||||||
|
|
||||||
var $xml = $(text);
|
|
||||||
//console.log(xml);
|
|
||||||
|
|
||||||
$xml.find('entry').each(function () {
|
|
||||||
var entry = $(this);
|
|
||||||
|
|
||||||
// Skip non-alerts.
|
|
||||||
var cap_msgType = entry.find('cap_msgType');
|
|
||||||
if (cap_msgType.text() !== 'Alert') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var link = entry.find('link');
|
|
||||||
var Url = link.attr('href');
|
|
||||||
|
|
||||||
HazardUrls.push(Url);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (HazardUrls.length === 0) {
|
|
||||||
PopulateHazardConditions(WeatherParameters);
|
|
||||||
console.log(WeatherParameters.WeatherHazardConditions);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$(HazardUrls).each(function () {
|
|
||||||
var Url = this.toString();
|
|
||||||
|
|
||||||
$.ajaxCORS({
|
|
||||||
type: 'GET',
|
|
||||||
url: Url,
|
|
||||||
dataType: 'xml',
|
|
||||||
crossDomain: true,
|
|
||||||
cache: true,
|
|
||||||
success: function (xml) {
|
|
||||||
var $xml = $(xml);
|
|
||||||
console.log(xml);
|
|
||||||
|
|
||||||
var description = $xml.find('description');
|
|
||||||
WeatherParameters.WeatherHazardConditions.Hazards.push(description.text());
|
|
||||||
|
|
||||||
HazardCounter++;
|
|
||||||
if (HazardCounter === HazardUrls.length) {
|
|
||||||
PopulateHazardConditions(WeatherParameters);
|
|
||||||
console.log(WeatherParameters.WeatherHazardConditions);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function () {
|
|
||||||
console.error('GetWeatherHazards3 failed for Url: ' + Url);
|
|
||||||
WeatherParameters.Progress.Hazards = LoadStatuses.Failed;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
error: function (xhr, error, errorThrown) {
|
|
||||||
console.error('GetWeatherHazards3 failed: ' + errorThrown);
|
|
||||||
WeatherParameters.Progress.Hazards = LoadStatuses.Failed;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var canvasProgress_mousemove = function (e) {
|
|
||||||
canvasProgress.css('cursor', '');
|
|
||||||
|
|
||||||
var RatioX = canvasProgress.width() / 640;
|
|
||||||
var RatioY = canvasProgress.height() / 480;
|
|
||||||
|
|
||||||
if (e.offsetX >= (70 * RatioX) && e.offsetX <= (565 * RatioX)) {
|
|
||||||
//if (e.offsetY >= (105 * RatioY) && e.offsetY <= (350 * RatioY))
|
|
||||||
if (e.offsetY >= (100 * RatioY) && e.offsetY <= (385 * RatioY)) {
|
|
||||||
// Show hand cursor.
|
|
||||||
canvasProgress.css('cursor', 'pointer');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var PopulateHazardConditions = function (WeatherParameters) {
|
|
||||||
if (WeatherParameters === null || (_DontLoadGifs && WeatherParameters.Progress.Hazards !== LoadStatuses.Loaded)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var WeatherHazardConditions = WeatherParameters.WeatherHazardConditions;
|
|
||||||
var ZoneId = WeatherHazardConditions.ZoneId;
|
|
||||||
var Text;
|
|
||||||
var Line;
|
|
||||||
var SkipLine;
|
|
||||||
|
|
||||||
var DontLoadGifs = _DontLoadGifs;
|
|
||||||
|
|
||||||
divHazards.empty();
|
|
||||||
|
|
||||||
$(WeatherHazardConditions.Hazards).each(function () {
|
|
||||||
//Text = this.replaceAll("\n", "<br/>");
|
|
||||||
//divHazards.html(divHazards.html() + "<br/><br/>" + Text);
|
|
||||||
|
|
||||||
Text = this.toString();
|
|
||||||
|
|
||||||
SkipLine = false;
|
|
||||||
|
|
||||||
Text = Text.replaceAll('\n', ' ');
|
|
||||||
//Text = Text.replaceAll("*** ", "");
|
|
||||||
|
|
||||||
//$(Text.split("\n")).each(function ()
|
|
||||||
$(Text.split(' ')).each(function () {
|
|
||||||
Line = this.toString();
|
|
||||||
Line = Line.toUpperCase();
|
|
||||||
|
|
||||||
if (Line.startsWith('&&')) {
|
|
||||||
return false;
|
|
||||||
} else if (Line.startsWith('$$')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (SkipLine) {
|
|
||||||
if (Line === '') {
|
|
||||||
SkipLine = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Line.startsWith(ZoneId)) {
|
|
||||||
SkipLine = true;
|
|
||||||
return true;
|
|
||||||
} else if (Line.indexOf('>') !== -1) {
|
|
||||||
SkipLine = true;
|
|
||||||
return true;
|
|
||||||
} else if (Line.indexOf('LAT...LON ') !== -1) {
|
|
||||||
SkipLine = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//divHazards.html(divHazards.html() + "<br/>" + Line);
|
|
||||||
if (Line.indexOf('.') === 0 || Line.indexOf('*') === 0) {
|
|
||||||
divHazards.html(divHazards.html() + '<br/><br/>');
|
|
||||||
if (Line.indexOf('.') === 0 && Line.indexOf('...') !== 0) {
|
|
||||||
Line = Line.substr(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
divHazards.html(divHazards.html() + Line + ' ');
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
divHazards.html(divHazards.html() + '<br/><br/>');
|
|
||||||
});
|
|
||||||
|
|
||||||
var DrawHazards = function () {
|
|
||||||
// Draw canvas
|
|
||||||
var canvas = canvasHazards[0];
|
|
||||||
var context = canvas.getContext('2d');
|
|
||||||
|
|
||||||
var BackGroundImage = new Image();
|
|
||||||
BackGroundImage.onload = function () {
|
|
||||||
context.drawImage(BackGroundImage, 0, 0);
|
|
||||||
|
|
||||||
if (DontLoadGifs) {
|
|
||||||
UpdateHazards();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (WeatherHazardConditions.Hazards.length > 0) {
|
|
||||||
WeatherParameters.Progress.Hazards = LoadStatuses.Loaded;
|
|
||||||
} else {
|
|
||||||
WeatherParameters.Progress.Hazards = LoadStatuses.NoData;
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateWeatherCanvas(WeatherParameters, canvasHazards);
|
|
||||||
};
|
|
||||||
BackGroundImage.src = 'images/BackGround7.png';
|
|
||||||
};
|
|
||||||
|
|
||||||
var HazardsText = divHazards.html();
|
|
||||||
|
|
||||||
HazardsText = HazardsText.replaceAll('<br>', '\n');
|
|
||||||
HazardsText = HazardsText.replaceAll('<br/>', '\n');
|
|
||||||
HazardsText = HazardsText.replaceAll('<br />', '\n');
|
|
||||||
HazardsText = HazardsText.replaceAll('<br></br>', '\n');
|
|
||||||
|
|
||||||
WeatherHazardConditions.HazardsText = HazardsText;
|
|
||||||
WeatherHazardConditions.HazardsTextC = ConvertConditionsToMetric(HazardsText);
|
|
||||||
if (_Units === Units.Metric) {
|
|
||||||
HazardsText = WeatherHazardConditions.HazardsTextC;
|
|
||||||
}
|
|
||||||
|
|
||||||
var HazardsWrapped = HazardsText.wordWrap(32);
|
|
||||||
|
|
||||||
var cnvHazardsScroll;
|
|
||||||
|
|
||||||
var ShowHazardsScroll = function () {
|
|
||||||
var cnvHazardsScrollId;
|
|
||||||
var context;
|
|
||||||
|
|
||||||
cnvHazardsScrollId = 'cnvHazardsScroll';
|
|
||||||
|
|
||||||
var HazardsWrappedLines = HazardsWrapped.split('\n');
|
|
||||||
var MaxHazardsWrappedLines = 365;
|
|
||||||
if (_OperatingSystem === OperatingSystems.Andriod) {
|
|
||||||
MaxHazardsWrappedLines = 92;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (HazardsWrappedLines.length > MaxHazardsWrappedLines) {
|
|
||||||
HazardsWrappedLines = HazardsWrappedLines.splice(0, MaxHazardsWrappedLines - 1);
|
|
||||||
}
|
|
||||||
var height = 0 + (HazardsWrappedLines.length * 45);
|
|
||||||
|
|
||||||
if (DontLoadGifs === false) {
|
|
||||||
// Clear the current image.
|
|
||||||
divHazardsScroll.empty();
|
|
||||||
divHazardsScroll.html('<canvas id=\'' + cnvHazardsScrollId + '\' />');
|
|
||||||
cnvHazardsScroll = $('#' + cnvHazardsScrollId);
|
|
||||||
cnvHazardsScroll.attr('width', '640'); // For Chrome.
|
|
||||||
cnvHazardsScroll.attr('height', height); // For Chrome.
|
|
||||||
}
|
|
||||||
cnvHazardsScroll = $('#' + cnvHazardsScrollId);
|
|
||||||
context = cnvHazardsScroll[0].getContext('2d');
|
|
||||||
|
|
||||||
DrawBox(context, 'rgb(112, 35, 35)', 0, 0, 640, height);
|
|
||||||
|
|
||||||
//var y = 0;
|
|
||||||
var y = 45;
|
|
||||||
|
|
||||||
$(HazardsWrappedLines).each(function () {
|
|
||||||
var HazardLine = this.toString();
|
|
||||||
|
|
||||||
DrawText(context, 'Star4000', '24pt', '#FFFFFF', 80, y, HazardLine, 1);
|
|
||||||
|
|
||||||
y += 45;
|
|
||||||
});
|
|
||||||
|
|
||||||
DrawHazards();
|
|
||||||
};
|
|
||||||
ShowHazardsScroll();
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
var UpdateHazards = function (Offset) {
|
|
||||||
var canvas = canvasHazards[0];
|
|
||||||
var context = canvas.getContext('2d');
|
|
||||||
var cnvHazardsScroll = $('#cnvHazardsScroll');
|
|
||||||
|
|
||||||
switch (Offset) {
|
|
||||||
case undefined:
|
|
||||||
break;
|
|
||||||
case 0:
|
|
||||||
_UpdateHazardsY = 0;
|
|
||||||
break;
|
|
||||||
case Infinity:
|
|
||||||
_UpdateHazardsY = cnvHazardsScroll.height();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
_UpdateHazardsY += (385 * Offset);
|
|
||||||
if (_UpdateHazardsY > cnvHazardsScroll.height()) {
|
|
||||||
_UpdateHazardsY = cnvHazardsScroll.height();
|
|
||||||
} else if (_UpdateHazardsY < 0) {
|
|
||||||
_UpdateHazardsY = 0;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawBox(context, 'rgb(112, 35,35)', 0, 0, 640, 385);
|
|
||||||
context.drawImage(cnvHazardsScroll[0], 0, _UpdateHazardsY, 640, 385, 0, 0, 640, 385);
|
|
||||||
|
|
||||||
|
|
||||||
};
|
|
14
server/scripts/vendor/jquery.touchswipe.min.js
vendored
14
server/scripts/vendor/jquery.touchswipe.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1,364 +0,0 @@
|
||||||
@font-face
|
|
||||||
{
|
|
||||||
font-family: "Star4000";
|
|
||||||
src: url('../fonts/Star4000.woff') format('woff');
|
|
||||||
}
|
|
||||||
body
|
|
||||||
{
|
|
||||||
font-family: "Star4000";
|
|
||||||
}
|
|
||||||
|
|
||||||
input, button
|
|
||||||
{
|
|
||||||
font-family: "Star4000";
|
|
||||||
}
|
|
||||||
|
|
||||||
#imgGetGps
|
|
||||||
{
|
|
||||||
height: 13px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
#txtAddress
|
|
||||||
{
|
|
||||||
width: 490px;
|
|
||||||
font-size: 16pt;
|
|
||||||
}
|
|
||||||
|
|
||||||
#btnGetGps, #btnGetLatLng, #btnClearQuery
|
|
||||||
{
|
|
||||||
font-size: 16pt;
|
|
||||||
}
|
|
||||||
|
|
||||||
.autocomplete-suggestions
|
|
||||||
{
|
|
||||||
background-color: #ffffff;
|
|
||||||
border: 1px solid #000000;
|
|
||||||
/*overflow: auto;*/
|
|
||||||
}
|
|
||||||
.autocomplete-suggestion
|
|
||||||
{
|
|
||||||
/*padding: 2px 5px;*/
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
font-size: 16pt;
|
|
||||||
}
|
|
||||||
.autocomplete-selected
|
|
||||||
{
|
|
||||||
background-color: #0000ff;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
#divTwc
|
|
||||||
{
|
|
||||||
display: block;
|
|
||||||
background-color: #000000;
|
|
||||||
color: #ffffff;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 640px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#divTwcLeft
|
|
||||||
{
|
|
||||||
display: none;
|
|
||||||
text-align: right;
|
|
||||||
flex-direction: column;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
#divTwcLeft > div
|
|
||||||
{
|
|
||||||
flex: 1;
|
|
||||||
padding-right: 12px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#divTwcRight
|
|
||||||
{
|
|
||||||
text-align: left;
|
|
||||||
display: none;
|
|
||||||
flex-direction: column;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
#divTwcRight > div
|
|
||||||
{
|
|
||||||
flex: 1;
|
|
||||||
padding-left: 12px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#divTwcBottom
|
|
||||||
{
|
|
||||||
/* visibility: hidden; */
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
background-color: #000000;
|
|
||||||
color: #ffffff;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
#divTwcBottom > div
|
|
||||||
{
|
|
||||||
padding-left: 6px;
|
|
||||||
padding-right: 6px;
|
|
||||||
}
|
|
||||||
#divTwcBottomLeft
|
|
||||||
{
|
|
||||||
flex: 1;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
#divTwcBottomMiddle
|
|
||||||
{
|
|
||||||
flex: 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#divTwcBottomRight
|
|
||||||
{
|
|
||||||
flex: 1;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
#divTwcNavContainer
|
|
||||||
{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#divTwcNav
|
|
||||||
{
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
background-color: #000000;
|
|
||||||
color: #ffffff;
|
|
||||||
max-width: 640px;
|
|
||||||
}
|
|
||||||
#divTwcNav > div
|
|
||||||
{
|
|
||||||
padding-left: 6px;
|
|
||||||
padding-right: 6px;
|
|
||||||
}
|
|
||||||
#divTwcNavLeft
|
|
||||||
{
|
|
||||||
flex: 1;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
#divTwcNavMiddle
|
|
||||||
{
|
|
||||||
flex: 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#divTwcNavRight
|
|
||||||
{
|
|
||||||
flex: 1;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
#imgPause1x, #imgPause2x
|
|
||||||
{
|
|
||||||
visibility: hidden;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.HideCursor
|
|
||||||
{
|
|
||||||
cursor: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#txtScrollText
|
|
||||||
{
|
|
||||||
width: 475px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face
|
|
||||||
{
|
|
||||||
font-family: "Star4000";
|
|
||||||
src: url('../fonts/Star4000.woff') format('woff');
|
|
||||||
}
|
|
||||||
@font-face
|
|
||||||
{
|
|
||||||
font-family: "Star 4 Radar";
|
|
||||||
src: url('../fonts/Star 4 Radar.woff') format('woff');
|
|
||||||
}
|
|
||||||
@font-face
|
|
||||||
{
|
|
||||||
font-family: 'Star4000 Extended';
|
|
||||||
src: url('../fonts/Star4000 Extended.woff') format('woff');
|
|
||||||
}
|
|
||||||
@font-face
|
|
||||||
{
|
|
||||||
font-family: 'Star4000LCN';
|
|
||||||
src: url('../fonts/Star4000LCN.woff') format('woff');
|
|
||||||
}
|
|
||||||
@font-face
|
|
||||||
{
|
|
||||||
font-family: 'Star4000 Large Compressed';
|
|
||||||
src: url('../fonts/Star4000 Large Compressed.woff') format('woff');
|
|
||||||
}
|
|
||||||
@font-face
|
|
||||||
{
|
|
||||||
font-family: 'Star4000 Large';
|
|
||||||
src: url('../fonts/Star4000 Large.woff') format('woff');
|
|
||||||
}
|
|
||||||
@font-face
|
|
||||||
{
|
|
||||||
font-family: 'Star4000 Small';
|
|
||||||
src: url('../fonts/Star4000 Small.woff') format('woff');
|
|
||||||
}
|
|
||||||
|
|
||||||
#display
|
|
||||||
{
|
|
||||||
font-family: "Star4000";
|
|
||||||
margin: 0 0 0 0;
|
|
||||||
/* overflow: hidden; */
|
|
||||||
width: 100%;
|
|
||||||
/* height: 480px; */
|
|
||||||
/* max-width: 640px; */
|
|
||||||
}
|
|
||||||
|
|
||||||
jsgif
|
|
||||||
{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#Star4000
|
|
||||||
{
|
|
||||||
font-family: 'Star4000';
|
|
||||||
}
|
|
||||||
#Star4000Extended
|
|
||||||
{
|
|
||||||
font-family: 'Star4000 Extended';
|
|
||||||
}
|
|
||||||
#Star4000LargeCompressed
|
|
||||||
{
|
|
||||||
font-family: 'Star4000 Large Compressed';
|
|
||||||
}
|
|
||||||
#Star4000Large
|
|
||||||
{
|
|
||||||
font-family: 'Star4000 Large';
|
|
||||||
}
|
|
||||||
#Star4000LargeCompressedNumbers
|
|
||||||
{
|
|
||||||
font-family: 'Star4000LCN';
|
|
||||||
}
|
|
||||||
#Star4000Small
|
|
||||||
{
|
|
||||||
font-family: 'Star4000 Small';
|
|
||||||
}
|
|
||||||
#Star4Radar
|
|
||||||
{
|
|
||||||
font-family: 'Star 4 Radar';
|
|
||||||
}
|
|
||||||
|
|
||||||
#container {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
/* max-width: 640px; */
|
|
||||||
height: 100%;
|
|
||||||
max-height: 480;
|
|
||||||
background-image: url(../images/BackGround1_1.png);
|
|
||||||
}
|
|
||||||
#divTwc:fullscreen #container {
|
|
||||||
background-image: none;
|
|
||||||
}
|
|
||||||
#loading {
|
|
||||||
width: 640px;
|
|
||||||
height: 480px;
|
|
||||||
max-width: 100%;
|
|
||||||
text-shadow: 4px 4px black;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
#loading .title {
|
|
||||||
font-family: Star4000 Large;
|
|
||||||
font-size: 36px;
|
|
||||||
color: yellow;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}
|
|
||||||
#loading .instructions {font-size: 18pt;}
|
|
||||||
#container canvas {
|
|
||||||
/* position: absolute; */
|
|
||||||
width: 100%;
|
|
||||||
/* max-width: 640px; */
|
|
||||||
}
|
|
||||||
.heading {
|
|
||||||
font-weight: bold;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
#enabledDisplays {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
#enabledDisplays label {
|
|
||||||
display: block;
|
|
||||||
max-width: 300px;
|
|
||||||
}
|
|
||||||
#divTwcBottom img {
|
|
||||||
zoom: 150%;
|
|
||||||
}
|
|
||||||
#divTwc:fullscreen {
|
|
||||||
display:flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
align-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#divTwc:fullscreen #display {
|
|
||||||
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
#divTwc:fullscreen #divTwcBottom {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
background-color: rgb(0 0 0 / 0.5);
|
|
||||||
color: #ffffff;
|
|
||||||
width: 100%;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (orientation: portrait) {
|
|
||||||
#divTwc:fullscreen canvas {
|
|
||||||
width: 100vw;
|
|
||||||
max-width: 100vw;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
#divTwc:fullscreen #container {
|
|
||||||
width: 100vw;
|
|
||||||
height: auto;
|
|
||||||
max-height: unset;
|
|
||||||
max-width: unset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (orientation: landscape) {
|
|
||||||
#divTwc:fullscreen canvas {
|
|
||||||
height: 100vh;
|
|
||||||
max-height: 100vh;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
#divTwc:fullscreen #container {
|
|
||||||
height: 100vh;
|
|
||||||
width: auto;
|
|
||||||
max-width: 100vw;
|
|
||||||
max-height: unset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.navButton {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.visible {
|
|
||||||
visibility: visible;
|
|
||||||
opacity: 1;
|
|
||||||
transition: opacity 1s linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hidden {
|
|
||||||
visibility: hidden;
|
|
||||||
opacity: 0;
|
|
||||||
transition: visibility 0s 1s, opacity 1s linear
|
|
||||||
}
|
|
1
server/styles/main.css
Normal file
1
server/styles/main.css
Normal file
File diff suppressed because one or more lines are too long
1
server/styles/main.css.map
Normal file
1
server/styles/main.css.map
Normal file
File diff suppressed because one or more lines are too long
85
server/styles/scss/_almanac.scss
Normal file
85
server/styles/scss/_almanac.scss
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
@use 'shared/_colors'as c;
|
||||||
|
@use 'shared/_utils'as u;
|
||||||
|
|
||||||
|
#almanac-html.weather-display {
|
||||||
|
background-image: url('../images/BackGround3_1.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-display .main.almanac {
|
||||||
|
font-family: 'Star4000';
|
||||||
|
font-size: 24pt;
|
||||||
|
@include u.text-shadow();
|
||||||
|
|
||||||
|
.sun {
|
||||||
|
display: table;
|
||||||
|
margin-left: 50px;
|
||||||
|
height: 100px;
|
||||||
|
|
||||||
|
|
||||||
|
&>div {
|
||||||
|
display: table-row;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&>div {
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.days {
|
||||||
|
color: c.$column-header-text;
|
||||||
|
text-align: right;
|
||||||
|
top: -5px;
|
||||||
|
|
||||||
|
.day {
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.times {
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
.sun-time {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.times-1 {
|
||||||
|
top: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.times-2 {
|
||||||
|
top: -15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.moon {
|
||||||
|
position: relative;
|
||||||
|
top: -10px;
|
||||||
|
|
||||||
|
padding: 0px 60px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: c.$column-header-text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day {
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
width: 130px;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
// shadow in image make it look off center
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date {
|
||||||
|
position: relative;
|
||||||
|
top: -10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
96
server/styles/scss/_current-weather.scss
Normal file
96
server/styles/scss/_current-weather.scss
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
@use 'shared/_colors'as c;
|
||||||
|
@use 'shared/_utils'as u;
|
||||||
|
|
||||||
|
.weather-display .main.current-weather {
|
||||||
|
&.main {
|
||||||
|
|
||||||
|
.col {
|
||||||
|
height: 50px;
|
||||||
|
width: 255px;
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 10px;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
@include u.text-shadow();
|
||||||
|
|
||||||
|
&.left {
|
||||||
|
font-family: 'Star4000 Extended';
|
||||||
|
font-size: 24pt;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&.right {
|
||||||
|
right: 0px;
|
||||||
|
font-family: 'Star4000 Large';
|
||||||
|
font-size: 16pt;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
.row {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
.label,
|
||||||
|
.value {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
float: right;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp {
|
||||||
|
font-family: 'Star4000 Large';
|
||||||
|
font-size: 24pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.condition {}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
height: 100px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 126px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wind-container {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
&>div {
|
||||||
|
width: 45%;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wind-label {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wind {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wind-gusts {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location {
|
||||||
|
color: c.$title-color;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
73
server/styles/scss/_extended-forecast.scss
Normal file
73
server/styles/scss/_extended-forecast.scss
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
@use 'shared/_colors'as c;
|
||||||
|
@use 'shared/_utils'as u;
|
||||||
|
|
||||||
|
#extended-forecast-html.weather-display {
|
||||||
|
background-image: url('../images/BackGround2_1.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-display .main.extended-forecast {
|
||||||
|
.day-container {
|
||||||
|
margin-top: 16px;
|
||||||
|
margin-left: 27px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day {
|
||||||
|
@include u.text-shadow();
|
||||||
|
padding: 5px;
|
||||||
|
height: 285px;
|
||||||
|
width: 155px;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0px 15px;
|
||||||
|
font-family: 'Star4000';
|
||||||
|
font-size: 24pt;
|
||||||
|
|
||||||
|
.date {
|
||||||
|
text-transform: uppercase;
|
||||||
|
text-align: center;
|
||||||
|
color: c.$title-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.condition {
|
||||||
|
text-align: center;
|
||||||
|
height: 74px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
text-align: center;
|
||||||
|
height: 75px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-height: 75px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.temperatures {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 5px;
|
||||||
|
|
||||||
|
.temperature-block {
|
||||||
|
display: inline-block;
|
||||||
|
width: 44%;
|
||||||
|
vertical-align: top;
|
||||||
|
|
||||||
|
>div {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
font-family: 'Star4000 Large';
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.lo .label {
|
||||||
|
color: c.$extended-low;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hi .label {
|
||||||
|
color: c.$title-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
95
server/styles/scss/_hourly.scss
Normal file
95
server/styles/scss/_hourly.scss
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
@use 'shared/_colors'as c;
|
||||||
|
@use 'shared/_utils'as u;
|
||||||
|
|
||||||
|
.weather-display .main.hourly {
|
||||||
|
&.main {
|
||||||
|
overflow-y: hidden;
|
||||||
|
|
||||||
|
.column-headers {
|
||||||
|
background-color: c.$column-header;
|
||||||
|
height: 20px;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-headers {
|
||||||
|
position: sticky;
|
||||||
|
top: 0px;
|
||||||
|
z-index: 5;
|
||||||
|
|
||||||
|
div {
|
||||||
|
display: inline-block;
|
||||||
|
font-family: 'Star4000 Small';
|
||||||
|
font-size: 24pt;
|
||||||
|
color: c.$column-header-text;
|
||||||
|
position: absolute;
|
||||||
|
top: -14px;
|
||||||
|
z-index: 5;
|
||||||
|
@include u.text-shadow();
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp {
|
||||||
|
left: 355px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.like {
|
||||||
|
left: 435px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wind {
|
||||||
|
left: 535px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hourly-lines {
|
||||||
|
min-height: 338px;
|
||||||
|
padding-top: 10px;
|
||||||
|
|
||||||
|
background: repeating-linear-gradient(0deg, c.$gradient-main-background-2 0px,
|
||||||
|
c.$gradient-main-background-1 136px,
|
||||||
|
c.$gradient-main-background-1 202px,
|
||||||
|
c.$gradient-main-background-2 338px,
|
||||||
|
);
|
||||||
|
|
||||||
|
.hourly-row {
|
||||||
|
font-family: 'Star4000 Large';
|
||||||
|
font-size: 24pt;
|
||||||
|
height: 72px;
|
||||||
|
color: c.$title-color;
|
||||||
|
@include u.text-shadow();
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
>div {
|
||||||
|
position: absolute;
|
||||||
|
white-space: pre;
|
||||||
|
top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hour {
|
||||||
|
left: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
left: 255px;
|
||||||
|
width: 70px;
|
||||||
|
text-align: center;
|
||||||
|
top: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp {
|
||||||
|
left: 355px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.like {
|
||||||
|
left: 425px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wind {
|
||||||
|
left: 505px;
|
||||||
|
width: 100px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
72
server/styles/scss/_latest-observations.scss
Normal file
72
server/styles/scss/_latest-observations.scss
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
@use 'shared/_colors'as c;
|
||||||
|
@use 'shared/_utils'as u;
|
||||||
|
|
||||||
|
.weather-display .latest-observations {
|
||||||
|
|
||||||
|
&.main {
|
||||||
|
overflow-y: hidden;
|
||||||
|
|
||||||
|
.column-headers {
|
||||||
|
height: 20px;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-headers {
|
||||||
|
top: 0px;
|
||||||
|
|
||||||
|
div {
|
||||||
|
display: inline-block;
|
||||||
|
font-family: 'Star4000 Small';
|
||||||
|
font-size: 24pt;
|
||||||
|
position: absolute;
|
||||||
|
top: -14px;
|
||||||
|
@include u.text-shadow();
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp {
|
||||||
|
// hidden initially for english/metric switching
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
&.show {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp {
|
||||||
|
left: 230px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather {
|
||||||
|
left: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wind {
|
||||||
|
left: 430px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.observation-lines {
|
||||||
|
min-height: 338px;
|
||||||
|
padding-top: 10px;
|
||||||
|
|
||||||
|
.observation-row {
|
||||||
|
font-family: 'Star4000';
|
||||||
|
font-size: 24pt;
|
||||||
|
@include u.text-shadow();
|
||||||
|
position: relative;
|
||||||
|
height: 40px;
|
||||||
|
|
||||||
|
>div {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wind {
|
||||||
|
white-space: pre;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
server/styles/scss/_local-forecast.scss
Normal file
26
server/styles/scss/_local-forecast.scss
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
@use 'shared/_colors'as c;
|
||||||
|
@use 'shared/_utils'as u;
|
||||||
|
|
||||||
|
.weather-display .local-forecast {
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
top: 15px;
|
||||||
|
margin: 0px 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 280px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forecasts {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forecast {
|
||||||
|
font-family: 'Star4000';
|
||||||
|
font-size: 24pt;
|
||||||
|
text-transform: uppercase;
|
||||||
|
@include u.text-shadow();
|
||||||
|
min-height: 280px;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
}
|
331
server/styles/scss/_page.scss
Normal file
331
server/styles/scss/_page.scss
Normal file
|
@ -0,0 +1,331 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: "Star4000";
|
||||||
|
src: url('../fonts/Star4000.woff') format('woff');
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: "Star4000";
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button {
|
||||||
|
font-family: "Star4000";
|
||||||
|
}
|
||||||
|
|
||||||
|
#imgGetGps {
|
||||||
|
height: 13px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
#txtAddress {
|
||||||
|
width: 490px;
|
||||||
|
font-size: 16pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
#btnGetGps,
|
||||||
|
#btnGetLatLng,
|
||||||
|
#btnClearQuery {
|
||||||
|
font-size: 16pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-suggestions {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #000000;
|
||||||
|
/*overflow: auto;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-suggestion {
|
||||||
|
/*padding: 2px 5px;*/
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
font-size: 16pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-selected {
|
||||||
|
background-color: #0000ff;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#divTwc {
|
||||||
|
display: block;
|
||||||
|
background-color: #000000;
|
||||||
|
color: #ffffff;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 640px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#divTwcLeft {
|
||||||
|
display: none;
|
||||||
|
text-align: right;
|
||||||
|
flex-direction: column;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
#divTwcLeft>div {
|
||||||
|
flex: 1;
|
||||||
|
padding-right: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#divTwcRight {
|
||||||
|
text-align: left;
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
#divTwcRight>div {
|
||||||
|
flex: 1;
|
||||||
|
padding-left: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#divTwcBottom {
|
||||||
|
/* visibility: hidden; */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
background-color: #000000;
|
||||||
|
color: #ffffff;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#divTwcBottom>div {
|
||||||
|
padding-left: 6px;
|
||||||
|
padding-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#divTwcBottomLeft {
|
||||||
|
flex: 1;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#divTwcBottomMiddle {
|
||||||
|
flex: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#divTwcBottomRight {
|
||||||
|
flex: 1;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#divTwcNavContainer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#divTwcNav {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
background-color: #000000;
|
||||||
|
color: #ffffff;
|
||||||
|
max-width: 640px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#divTwcNav>div {
|
||||||
|
padding-left: 6px;
|
||||||
|
padding-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#divTwcNavLeft {
|
||||||
|
flex: 1;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#divTwcNavMiddle {
|
||||||
|
flex: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#divTwcNavRight {
|
||||||
|
flex: 1;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#imgPause1x,
|
||||||
|
#imgPause2x {
|
||||||
|
visibility: hidden;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.HideCursor {
|
||||||
|
cursor: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#txtScrollText {
|
||||||
|
width: 475px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Star4000";
|
||||||
|
src: url('../fonts/Star4000.woff') format('woff');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Star 4 Radar";
|
||||||
|
src: url('../fonts/Star 4 Radar.woff') format('woff');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Star4000 Extended';
|
||||||
|
src: url('../fonts/Star4000 Extended.woff') format('woff');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Star4000LCN';
|
||||||
|
src: url('../fonts/Star4000LCN.woff') format('woff');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Star4000 Large Compressed';
|
||||||
|
src: url('../fonts/Star4000 Large Compressed.woff') format('woff');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Star4000 Large';
|
||||||
|
src: url('../fonts/Star4000 Large.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Star4000 Small';
|
||||||
|
src: url('../fonts/Star4000 Small.woff') format('woff');
|
||||||
|
}
|
||||||
|
|
||||||
|
#display {
|
||||||
|
font-family: "Star4000";
|
||||||
|
margin: 0 0 0 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsgif {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#Star4000 {
|
||||||
|
font-family: 'Star4000';
|
||||||
|
}
|
||||||
|
|
||||||
|
#Star4000Extended {
|
||||||
|
font-family: 'Star4000 Extended';
|
||||||
|
}
|
||||||
|
|
||||||
|
#Star4000LargeCompressed {
|
||||||
|
font-family: 'Star4000 Large Compressed';
|
||||||
|
}
|
||||||
|
|
||||||
|
#Star4000Large {
|
||||||
|
font-family: 'Star4000 Large';
|
||||||
|
}
|
||||||
|
|
||||||
|
#Star4000LargeCompressedNumbers {
|
||||||
|
font-family: 'Star4000LCN';
|
||||||
|
}
|
||||||
|
|
||||||
|
#Star4000Small {
|
||||||
|
font-family: 'Star4000 Small';
|
||||||
|
}
|
||||||
|
|
||||||
|
#Star4Radar {
|
||||||
|
font-family: 'Star 4 Radar';
|
||||||
|
}
|
||||||
|
|
||||||
|
#container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-image: url(../images/BackGround1_1.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
#divTwc:fullscreen #container {
|
||||||
|
background-image: none;
|
||||||
|
width: unset;
|
||||||
|
height: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loading {
|
||||||
|
width: 640px;
|
||||||
|
height: 480px;
|
||||||
|
max-width: 100%;
|
||||||
|
text-shadow: 4px 4px black;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loading .title {
|
||||||
|
font-family: Star4000 Large;
|
||||||
|
font-size: 36px;
|
||||||
|
color: yellow;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loading .instructions {
|
||||||
|
font-size: 18pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container canvas {
|
||||||
|
/* position: absolute; */
|
||||||
|
width: 100%;
|
||||||
|
/* max-width: 640px; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#enabledDisplays {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#enabledDisplays label {
|
||||||
|
display: block;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#divTwcBottom img {
|
||||||
|
zoom: 150%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#divTwc:fullscreen {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#divTwc:fullscreen #display {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#divTwc:fullscreen #divTwcBottom {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
background-color: rgb(0 0 0 / 0.5);
|
||||||
|
color: #ffffff;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navButton {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.visible {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 1s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transition: visibility 0s 1s, opacity 1s linear
|
||||||
|
}
|
150
server/styles/scss/_progress.scss
Normal file
150
server/styles/scss/_progress.scss
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
@use 'shared/_colors'as c;
|
||||||
|
@use 'shared/_utils'as u;
|
||||||
|
|
||||||
|
.weather-display .progress {
|
||||||
|
@include u.text-shadow();
|
||||||
|
font-family: 'Star4000 Extended';
|
||||||
|
font-size: 19pt;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
top: 15px;
|
||||||
|
margin: 0px 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 310px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.name {
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '........................................................................';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.links {
|
||||||
|
position: absolute;
|
||||||
|
text-align: right;
|
||||||
|
right: 0px;
|
||||||
|
top: 0px;
|
||||||
|
|
||||||
|
>div {
|
||||||
|
background-color: c.$blue-box;
|
||||||
|
display: none;
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
color: #ffff00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.press-here {
|
||||||
|
color: #00ff00;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.failed {
|
||||||
|
color: #ff0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data {
|
||||||
|
color: #C0C0C0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
color: #C0C0C0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.loading .loading {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.press-here .press-here {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.failed .failed {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.no-data .no-data {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled .disabled {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress-html.weather-display .scroll {
|
||||||
|
|
||||||
|
@keyframes progress-scroll {
|
||||||
|
0% {
|
||||||
|
background-position: -40px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background-position: 40px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar-container {
|
||||||
|
border: 2px solid black;
|
||||||
|
background-color: white;
|
||||||
|
margin: 20px auto;
|
||||||
|
width: 524px;
|
||||||
|
position: relative;
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
&.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
height: 20px;
|
||||||
|
margin: 2px;
|
||||||
|
width: 520px;
|
||||||
|
background: repeating-linear-gradient(90deg,
|
||||||
|
c.$gradient-loading-1 0px,
|
||||||
|
c.$gradient-loading-1 5px,
|
||||||
|
c.$gradient-loading-2 5px,
|
||||||
|
c.$gradient-loading-2 10px,
|
||||||
|
c.$gradient-loading-3 10px,
|
||||||
|
c.$gradient-loading-3 15px,
|
||||||
|
c.$gradient-loading-4 15px,
|
||||||
|
c.$gradient-loading-4 20px,
|
||||||
|
c.$gradient-loading-3 20px,
|
||||||
|
c.$gradient-loading-3 25px,
|
||||||
|
c.$gradient-loading-2 25px,
|
||||||
|
c.$gradient-loading-2 30px,
|
||||||
|
c.$gradient-loading-1 30px,
|
||||||
|
c.$gradient-loading-1 40px,
|
||||||
|
);
|
||||||
|
// animation
|
||||||
|
animation-duration: 2s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-name: progress-scroll;
|
||||||
|
animation-timing-function: steps(8, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
right: 0px;
|
||||||
|
background-color: white;
|
||||||
|
width: 100%;
|
||||||
|
height: 24px;
|
||||||
|
transition: width 1s steps(6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
114
server/styles/scss/_radar.scss
Normal file
114
server/styles/scss/_radar.scss
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
@use 'shared/_colors'as c;
|
||||||
|
@use 'shared/_utils'as u;
|
||||||
|
|
||||||
|
#radar-html.weather-display {
|
||||||
|
background-image: url('../images/BackGround4_1.png');
|
||||||
|
|
||||||
|
.header {
|
||||||
|
height: 83px;
|
||||||
|
|
||||||
|
.title.dual {
|
||||||
|
color: white;
|
||||||
|
font-family: 'Arial', sans-serif;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 28pt;
|
||||||
|
left: 155px;
|
||||||
|
|
||||||
|
.top {
|
||||||
|
top: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
top: 31px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
position: absolute;
|
||||||
|
right: 0px;
|
||||||
|
width: 360px;
|
||||||
|
margin-top: 2px;
|
||||||
|
font-family: 'Star4000';
|
||||||
|
font-size: 18pt;
|
||||||
|
font-weight: bold;
|
||||||
|
@include u.text-shadow();
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.scale>div {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scale-table {
|
||||||
|
display: table-row;
|
||||||
|
border-collapse: collapse;
|
||||||
|
|
||||||
|
.box {
|
||||||
|
display: table-cell;
|
||||||
|
border: 2px solid black;
|
||||||
|
width: 17px;
|
||||||
|
height: 24px;
|
||||||
|
padding: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-1 {
|
||||||
|
background-color: rgb(49, 210, 22);
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-2 {
|
||||||
|
background-color: rgb(28, 138, 18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-3 {
|
||||||
|
background-color: rgb(20, 90, 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-4 {
|
||||||
|
background-color: rgb(10, 40, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-5 {
|
||||||
|
background-color: rgb(196, 179, 70);
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-6 {
|
||||||
|
background-color: rgb(190, 72, 19);
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-7 {
|
||||||
|
background-color: rgb(171, 14, 14);
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-8 {
|
||||||
|
background-color: rgb(115, 31, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scale {
|
||||||
|
.text {
|
||||||
|
position: relative;
|
||||||
|
top: -5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.time {
|
||||||
|
position: relative;
|
||||||
|
font-weight: normal;
|
||||||
|
top: -14px;
|
||||||
|
font-family: 'Star4000 Small';
|
||||||
|
font-size: 24pt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-display .main.radar {
|
||||||
|
overflow: hidden;
|
||||||
|
height: 367px;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
|
||||||
|
.scroll-area {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
server/styles/scss/_regional-forecast.scss
Normal file
51
server/styles/scss/_regional-forecast.scss
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
@use 'shared/_colors'as c;
|
||||||
|
@use 'shared/_utils'as u;
|
||||||
|
|
||||||
|
#regional-forecast-html.weather-display {
|
||||||
|
background-image: url('../images/BackGround5_1.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-display .main.regional-forecast {
|
||||||
|
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.map {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location {
|
||||||
|
position: absolute;
|
||||||
|
width: 140px;
|
||||||
|
margin-left: -40px;
|
||||||
|
margin-top: -35px;
|
||||||
|
|
||||||
|
>div {
|
||||||
|
position: absolute;
|
||||||
|
@include u.text-shadow();
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
top: 26px;
|
||||||
|
left: 44px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-height: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp {
|
||||||
|
font-family: 'Star4000 Large';
|
||||||
|
font-size: 28px;
|
||||||
|
color: c.$title-color;
|
||||||
|
top: 28px;
|
||||||
|
text-align: right;
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city {
|
||||||
|
font-family: Star4000;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
103
server/styles/scss/_travel.scss
Normal file
103
server/styles/scss/_travel.scss
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
@use 'shared/_colors'as c;
|
||||||
|
@use 'shared/_utils'as u;
|
||||||
|
|
||||||
|
.weather-display .main.travel {
|
||||||
|
&.main {
|
||||||
|
overflow-y: hidden;
|
||||||
|
|
||||||
|
.column-headers {
|
||||||
|
background-color: c.$column-header;
|
||||||
|
height: 20px;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-headers {
|
||||||
|
position: sticky;
|
||||||
|
top: 0px;
|
||||||
|
z-index: 5;
|
||||||
|
|
||||||
|
div {
|
||||||
|
display: inline-block;
|
||||||
|
font-family: 'Star4000 Small';
|
||||||
|
font-size: 24pt;
|
||||||
|
color: c.$column-header-text;
|
||||||
|
position: absolute;
|
||||||
|
top: -14px;
|
||||||
|
z-index: 5;
|
||||||
|
@include u.text-shadow();
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp {
|
||||||
|
width: 50px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&.low {
|
||||||
|
left: 455px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&.high {
|
||||||
|
left: 510px;
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.travel-lines {
|
||||||
|
min-height: 338px;
|
||||||
|
padding-top: 10px;
|
||||||
|
|
||||||
|
background: repeating-linear-gradient(0deg, c.$gradient-main-background-2 0px,
|
||||||
|
c.$gradient-main-background-1 136px,
|
||||||
|
c.$gradient-main-background-1 202px,
|
||||||
|
c.$gradient-main-background-2 338px,
|
||||||
|
);
|
||||||
|
|
||||||
|
.travel-row {
|
||||||
|
font-family: 'Star4000 Large';
|
||||||
|
font-size: 24pt;
|
||||||
|
height: 72px;
|
||||||
|
color: c.$title-color;
|
||||||
|
@include u.text-shadow();
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
>div {
|
||||||
|
position: absolute;
|
||||||
|
white-space: pre;
|
||||||
|
top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city {
|
||||||
|
left: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
left: 330px;
|
||||||
|
width: 70px;
|
||||||
|
text-align: center;
|
||||||
|
top: unset;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 47px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp {
|
||||||
|
width: 50px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&.low {
|
||||||
|
left: 455px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.high {
|
||||||
|
left: 510px;
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
123
server/styles/scss/_weather-display.scss
Normal file
123
server/styles/scss/_weather-display.scss
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
@use 'shared/_colors'as c;
|
||||||
|
@use 'shared/_utils'as u;
|
||||||
|
|
||||||
|
.weather-display {
|
||||||
|
width: 640px;
|
||||||
|
height: 480px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
background-image: url(../images/BackGround1_1.png);
|
||||||
|
|
||||||
|
/* this method is required to hide blocks so they can be measured while off screen */
|
||||||
|
height: 0px;
|
||||||
|
|
||||||
|
&.show {
|
||||||
|
height: 480px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
width: 640px;
|
||||||
|
height: 60px;
|
||||||
|
padding-top: 30px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: c.$title-color;
|
||||||
|
@include u.text-shadow(3px, 1.5px);
|
||||||
|
font-family: 'Star4000';
|
||||||
|
font-size: 24pt;
|
||||||
|
position: absolute;
|
||||||
|
width: 250px;
|
||||||
|
|
||||||
|
&.single {
|
||||||
|
left: 170px;
|
||||||
|
top: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dual {
|
||||||
|
left: 170px;
|
||||||
|
|
||||||
|
&>div {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top {
|
||||||
|
top: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
top: 26px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
top: 30px;
|
||||||
|
left: 50px;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noaa-logo {
|
||||||
|
position: absolute;
|
||||||
|
top: 39px;
|
||||||
|
left: 356px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title.single {
|
||||||
|
top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-time {
|
||||||
|
white-space: pre;
|
||||||
|
color: c.$date-time;
|
||||||
|
font-family: 'Star4000 Small';
|
||||||
|
font-size: 24pt;
|
||||||
|
@include u.text-shadow(3px, 1.5px);
|
||||||
|
left: 415px;
|
||||||
|
width: 170px;
|
||||||
|
text-align: right;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
&.date {
|
||||||
|
padding-top: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.has-scroll {
|
||||||
|
width: 640px;
|
||||||
|
height: 310px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-box {
|
||||||
|
margin-left: 64px;
|
||||||
|
margin-right: 64px;
|
||||||
|
width: calc(100% - 128px);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.scroll {
|
||||||
|
@include u.text-shadow(3px, 1.5px);
|
||||||
|
width: 640px;
|
||||||
|
height: 70px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
.fixed {
|
||||||
|
font-family: 'Star4000';
|
||||||
|
font-size: 24pt;
|
||||||
|
margin-left: 55px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
server/styles/scss/main.scss
Normal file
12
server/styles/scss/main.scss
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
@import 'page';
|
||||||
|
@import 'weather-display';
|
||||||
|
@import 'current-weather';
|
||||||
|
@import 'extended-forecast';
|
||||||
|
@import 'hourly';
|
||||||
|
@import 'travel';
|
||||||
|
@import 'latest-observations';
|
||||||
|
@import 'local-forecast';
|
||||||
|
@import 'progress';
|
||||||
|
@import 'radar';
|
||||||
|
@import 'regional-forecast';
|
||||||
|
@import 'almanac';
|
17
server/styles/scss/shared/_colors.scss
Normal file
17
server/styles/scss/shared/_colors.scss
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
$title-color: yellow;
|
||||||
|
$date-time: white;
|
||||||
|
$text-shadow: black;
|
||||||
|
$column-header-text: yellow;
|
||||||
|
$column-header: rgb(32, 0, 87);
|
||||||
|
|
||||||
|
$gradient-main-background-1: #102080;
|
||||||
|
$gradient-main-background-2: #001040;
|
||||||
|
|
||||||
|
$gradient-loading-1: #09246f;
|
||||||
|
$gradient-loading-2: #364ac0;
|
||||||
|
$gradient-loading-3: #4f99f9;
|
||||||
|
$gradient-loading-4: #8ffdfa;
|
||||||
|
|
||||||
|
$extended-low: #8080FF;
|
||||||
|
|
||||||
|
$blue-box: #26235a;
|
17
server/styles/scss/shared/_utils.scss
Normal file
17
server/styles/scss/shared/_utils.scss
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
@use 'colors'as c;
|
||||||
|
|
||||||
|
@mixin text-shadow($offset: 3px, $outline: 1.5px) {
|
||||||
|
/* eventually, when chrome supports paint-order for html elements */
|
||||||
|
/* -webkit-text-stroke: 2px black; */
|
||||||
|
/* paint-order: stroke fill; */
|
||||||
|
text-shadow:
|
||||||
|
$offset $offset 0 c.$text-shadow,
|
||||||
|
(-$outline) (-$outline) 0 c.$text-shadow,
|
||||||
|
0 (-$outline) 0 c.$text-shadow,
|
||||||
|
$outline (-$outline) 0 c.$text-shadow,
|
||||||
|
$outline 0 0 c.$text-shadow,
|
||||||
|
$outline $outline 0 c.$text-shadow,
|
||||||
|
0 $outline 0 c.$text-shadow,
|
||||||
|
(-$outline) $outline 0 c.$text-shadow,
|
||||||
|
(-$outline) 0 0 c.$text-shadow;
|
||||||
|
}
|
256
views/index.ejs
256
views/index.ejs
|
@ -1,139 +1,165 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
|
||||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="preload" href="fonts/Star4000.woff" as="font" crossorigin="anonymous" />
|
<title>WeatherStar 4000+</title>
|
||||||
<link rel="preload" href="fonts/Star 4 Radar.woff" as="font" crossorigin="anonymous" />
|
<meta name="description" content="Web based WeatherStar 4000 simulator that reports current and forecast weather conditions plus a few extras!" />
|
||||||
<link rel="preload" href="fonts/Star4000 Extended.woff" as="font" crossorigin="anonymous" />
|
<meta name="keywords" content="WeatherStar 4000+" />
|
||||||
<link rel="preload" href="fonts/Star4000 Large Compressed.woff" as="font" crossorigin="anonymous" />
|
<meta name="author" content="Matt Walsh" />
|
||||||
<link rel="preload" href="fonts/Star4000 Large.woff" as="font" crossorigin="anonymous" />
|
<meta name="application-name" content="WeatherStar 4000+" />
|
||||||
<link rel="preload" href="fonts/Star4000 Small.woff" as="font" crossorigin="anonymous" />
|
|
||||||
|
|
||||||
<title>WeatherStar 4000+</title>
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<meta name="description" content="Web based WeatherStar 4000 simulator that reports current and forecast weather conditions plus a few extras!" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
<meta name="keywords" content="WeatherStar 4000+" />
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
<meta name="author" content="Matt Walsh" />
|
<link rel="manifest" href="manifest.json" />
|
||||||
<meta name="application-name" content="WeatherStar 4000+" />
|
<link rel="icon" href="images/Logo192.png" />
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<% if (production) { %>
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
<link rel="stylesheet" type="text/css" href="resources/ws.min.css?_=<%=production%>" />
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
<script type="text/javascript" src="resources/data.min.js?_=<%=production%>"></script>
|
||||||
<link rel="manifest" href="manifest.json" />
|
<script type="text/javascript" src="resources/ws.min.js?_=<%=production%>"></script>
|
||||||
<link rel="icon" href="images/Logo192.png" />
|
<% } else { %>
|
||||||
|
<link rel="stylesheet" type="text/css" href="styles/main.css" />
|
||||||
|
<script type="text/javascript" src="scripts/vendor/auto/jquery.js"></script>
|
||||||
|
<script type="text/javascript" src="scripts/vendor/jquery.autocomplete.min.js"></script>
|
||||||
|
<script type="text/javascript" src="scripts/vendor/auto/nosleep.js"></script>
|
||||||
|
<script type="text/javascript" src="scripts/vendor/auto/swiped-events.js"></script>
|
||||||
|
<script type="text/javascript" src="scripts/index.js"></script>
|
||||||
|
<script type="text/javascript" src="scripts/data/states.js"></script>
|
||||||
|
|
||||||
<% if (production) { %>
|
<script type="text/javascript" src="scripts/vendor/auto/luxon.js"></script>
|
||||||
<link rel="stylesheet" type="text/css" href="resources/ws.min.css?_=<%=production%>" />
|
<script type="text/javascript" src="scripts/vendor/auto/suncalc.js"></script>
|
||||||
<script type="text/javascript" src="resources/data.min.js?_=<%=production%>"></script>
|
<script type="text/javascript" src="scripts/data/travelcities.js"></script>
|
||||||
<script type="text/javascript" src="resources/ws.min.js?_=<%=production%>"></script>
|
<script type="text/javascript" src="scripts/data/regionalcities.js"></script>
|
||||||
<% } else { %>
|
<script type="text/javascript" src="scripts/data/stations.js"></script>
|
||||||
<link rel="stylesheet" type="text/css" href="styles/index.css" />
|
<script type="text/javascript" src="scripts/modules/draw.js"></script>
|
||||||
<script type="text/javascript" src="scripts/vendor/auto/jquery.js"></script>
|
<script type="text/javascript" src="scripts/modules/weatherdisplay.js"></script>
|
||||||
<script type="text/javascript" src="scripts/vendor/jquery.autocomplete.min.js"></script>
|
<script type="text/javascript" src="scripts/modules/icons.js"></script>
|
||||||
<script type="text/javascript" src="scripts/vendor/auto/nosleep.js"></script>
|
<script type="text/javascript" src="scripts/modules/utilities.js"></script>
|
||||||
<script type="text/javascript" src="scripts/vendor/auto/swiped-events.js"></script>
|
<script type="text/javascript" src="scripts/modules/currentweather.js"></script>
|
||||||
<script type="text/javascript" src="scripts/vendor/jquery.touchswipe.min.js"></script>
|
<script type="text/javascript" src="scripts/modules/currentweatherscroll.js"></script>
|
||||||
<script type="text/javascript" src="scripts/index.js"></script>
|
<script type="text/javascript" src="scripts/modules/latestobservations.js"></script>
|
||||||
<script type="text/javascript" src="scripts/data/states.js"></script>
|
<script type="text/javascript" src="scripts/modules/travelforecast.js"></script>
|
||||||
|
<script type="text/javascript" src="scripts/modules/regionalforecast.js"></script>
|
||||||
|
<script type="text/javascript" src="scripts/modules/localforecast.js"></script>
|
||||||
|
<script type="text/javascript" src="scripts/modules/extendedforecast.js"></script>
|
||||||
|
<script type="text/javascript" src="scripts/modules/almanac.js"></script>
|
||||||
|
<script type="text/javascript" src="scripts/modules/radar.js"></script>
|
||||||
|
<script type="text/javascript" src="scripts/modules/hourly.js"></script>
|
||||||
|
<script type="text/javascript" src="scripts/modules/progress.js"></script>
|
||||||
|
<script type="text/javascript" src="scripts/modules/navigation.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="scripts/libgif.js"></script>
|
<% } %>
|
||||||
<script type="text/javascript" src="scripts/vendor/auto/luxon.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/vendor/auto/suncalc.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/data/travelcities.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/data/regionalcities.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/data/stations.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/draw.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/weatherdisplay.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/icons.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/utilities.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/currentweather.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/currentweatherscroll.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/latestobservations.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/travelforecast.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/regionalforecast.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/localforecast.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/extendedforecast.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/almanac.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/radar.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/hourly.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/progress.js"></script>
|
|
||||||
<script type="text/javascript" src="scripts/modules/navigation.js"></script>
|
|
||||||
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
||||||
<div id="divQuery">
|
<div id="divQuery">
|
||||||
<form id="frmGetLatLng">
|
<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="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="btnGetLatLng" type="submit" value="GO" />
|
||||||
<input id="btnClearQuery" type="reset" value="Reset" />
|
<input id="btnClearQuery" type="reset" value="Reset" />
|
||||||
</form>
|
</form>
|
||||||
<div id="divLat"></div>
|
<div id="divLat"></div>
|
||||||
<div id="divLng"></div>
|
<div id="divLng"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<img id="imgPause1x" src="images/nav/ic_pause_white_24dp_1x.png" />
|
<img id="imgPause1x" src="images/nav/ic_pause_white_24dp_1x.png" />
|
||||||
<img id="imgPause2x" src="images/nav/ic_pause_white_24dp_2x.png" />
|
<img id="imgPause2x" src="images/nav/ic_pause_white_24dp_2x.png" />
|
||||||
<div id="version" style="display:none"><%- version %> </div>
|
<div id="version" style="display:none">
|
||||||
|
<%- version %>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="divTwc">
|
<div id="divTwc">
|
||||||
<div id="container">
|
<div id="container">
|
||||||
<div id="loading" width="640" height="480">
|
<div id="loading" width="640" height="480">
|
||||||
<div>
|
<div>
|
||||||
<div class="title">WeatherStar 4000+</div>
|
<div class="title">WeatherStar 4000+</div>
|
||||||
<div class="instructions">Enter your location above to continue</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>
|
||||||
|
</div>
|
||||||
|
<div id="progress-html" class="weather-display">
|
||||||
|
<%- include('partials/progress.ejs') %>
|
||||||
|
</div>
|
||||||
|
<div id="hourly-html" class="weather-display">
|
||||||
|
<%- include('partials/hourly.ejs') %>
|
||||||
|
</div>
|
||||||
|
<div id="travel-html" class="weather-display">
|
||||||
|
<%- include('partials/travel.ejs') %>
|
||||||
|
</div>
|
||||||
|
<div id="current-weather-html" class="weather-display">
|
||||||
|
<%- include('partials/current-weather.ejs') %>
|
||||||
|
</div>
|
||||||
|
<div id="local-forecast-html" class="weather-display">
|
||||||
|
<%- include('partials/local-forecast.ejs') %>
|
||||||
|
</div>
|
||||||
|
<div id="latest-observations-html" class="weather-display">
|
||||||
|
<%- include('partials/latest-observations.ejs') %>
|
||||||
|
</div>
|
||||||
|
<div id="regional-forecast-html" class="weather-display">
|
||||||
|
<%- include('partials/regional-forecast.ejs') %>
|
||||||
|
</div>
|
||||||
|
<div id="almanac-html" class="weather-display">
|
||||||
|
<%- include('partials/almanac.ejs') %>
|
||||||
|
</div>
|
||||||
|
<div id="extended-forecast-html" class="weather-display">
|
||||||
|
<%- include('partials/extended-forecast.ejs') %>
|
||||||
|
</div>
|
||||||
|
<div id="radar-html" class="weather-display">
|
||||||
|
<%- include('partials/radar.ejs') %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="divTwcBottom">
|
||||||
<br />
|
<div id="divTwcBottomLeft">
|
||||||
|
<img id="NavigateMenu" class="navButton" src="images/nav/ic_menu_white_24dp_1x.png" title="Menu" />
|
||||||
<div class="info">
|
<img id="NavigatePrevious" class="navButton" src="images/nav/ic_skip_previous_white_24dp_1x.png" title="Previous" />
|
||||||
<a href="https://github.com/netbymatt/ws4kp#weatherstar-4000">More information</a>
|
<img id="NavigateNext" class="navButton" src="images/nav/ic_skip_next_white_24dp_1x.png" title="Next" />
|
||||||
</div>
|
<img id="NavigatePlay" class="navButton" src="images/nav/ic_play_arrow_white_24dp_1x.png" title="Play" />
|
||||||
|
</div>
|
||||||
<div class='heading'>Selected displays</div>
|
<div id="divTwcBottomMiddle">
|
||||||
<div id='enabledDisplays'>
|
<img id="NavigateRefresh" class="navButton" src="images/nav/ic_refresh_white_24dp_1x.png" title="Refresh" />
|
||||||
|
</div>
|
||||||
</div>
|
<div id="divTwcBottomRight">
|
||||||
|
<img id="ToggleFullScreen" class="navButton" src="images/nav/ic_fullscreen_exit_white_24dp_1x.png" title="Enter Fullscreen" />
|
||||||
<div id="divInfo">
|
</div>
|
||||||
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>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="divRefresh">
|
<br />
|
||||||
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">
|
<div class="info">
|
||||||
Units:
|
<a href="https://github.com/netbymatt/ws4kp#weatherstar-4000">More information</a>
|
||||||
<input id="radEnglish" name="radUnits" type="radio" value="ENGLISH" /><label for="radEnglish">English</label>
|
</div>
|
||||||
<input id="radMetric" name="radUnits" type="radio" value="METRIC" /><label for="radMetric">Metric</label>
|
|
||||||
</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>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
31
views/partials/almanac.ejs
Normal file
31
views/partials/almanac.ejs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<%- include('header.ejs', {title:'Almanac', hasTime: true}) %>
|
||||||
|
<div class="main has-scroll almanac">
|
||||||
|
<div class="sun">
|
||||||
|
<div class="days">
|
||||||
|
<div class="day"></div>
|
||||||
|
<div class="day day-1">Monday</div>
|
||||||
|
<div class="day day-2">Tuesday</div>
|
||||||
|
</div>
|
||||||
|
<div class="times times-1">
|
||||||
|
<div class="name">Sunrise:</div>
|
||||||
|
<div class="sun-time rise-1">6:24 am</div>
|
||||||
|
<div class="sun-time rise-2">6:25 am</div>
|
||||||
|
</div>
|
||||||
|
<div class="times times-2">
|
||||||
|
<div class="name">Sunset:</div>
|
||||||
|
<div class="sun-time set-1">6:24 am</div>
|
||||||
|
<div class="sun-time set-2">6:25 am</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="moon">
|
||||||
|
<div class="title">Moon Data:</div>
|
||||||
|
<div class="days">
|
||||||
|
<div class="day template">
|
||||||
|
<div class="type"></div>
|
||||||
|
<div class="icon"><img /></div>
|
||||||
|
<div class="date"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<%- include('scroll.ejs') %>
|
43
views/partials/current-weather.ejs
Normal file
43
views/partials/current-weather.ejs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<%- include('header.ejs', {titleDual:{ top: 'Current' , bottom: 'Conditions' }, noaaLogo: true}) %>
|
||||||
|
<div class="main has-scroll has-box current-weather">
|
||||||
|
<div class="weather template">
|
||||||
|
<div class="left col">
|
||||||
|
<div class="temp center"></div>
|
||||||
|
<div class="condition center"></div>
|
||||||
|
<div class="icon center"><img src="" /></div>
|
||||||
|
<div class="wind-container">
|
||||||
|
<div class="wind-label">Wind:</div>
|
||||||
|
<div class="wind"></div>
|
||||||
|
</div>
|
||||||
|
<div class="wind-gusts"></div>
|
||||||
|
</div>
|
||||||
|
<div class="right col">
|
||||||
|
<div class="location"></div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="label">Humidity:</div>
|
||||||
|
<div class="humidity value"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="label">Dewpoint:</div>
|
||||||
|
<div class="dewpoint value"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="label">Ceiling:</div>
|
||||||
|
<div class="ceiling value"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="label">Visibility:</div>
|
||||||
|
<div class="visibility value"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="label">Pressure:</div>
|
||||||
|
<div class="pressure value"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="heat-index-label label"></div>
|
||||||
|
<div class="heat-index value"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<%- include('scroll.ejs') %>
|
23
views/partials/extended-forecast.ejs
Normal file
23
views/partials/extended-forecast.ejs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<%- include('header.ejs', {titleDual:{ top: 'Extended' , bottom: 'Forecast' }, hasTime: true }) %>
|
||||||
|
<div class="main has-scroll extended-forecast">
|
||||||
|
<div class="day-container">
|
||||||
|
<div class="day template">
|
||||||
|
<div class="date"></div>
|
||||||
|
<div class="icon">
|
||||||
|
<img src="" />
|
||||||
|
</div>
|
||||||
|
<div class="condition"></div>
|
||||||
|
<div class="temperatures">
|
||||||
|
<div class="temperature-block lo">
|
||||||
|
<div class="label">Lo</div>
|
||||||
|
<div class="value value-lo"></div>
|
||||||
|
</div>
|
||||||
|
<div class="temperature-block hi">
|
||||||
|
<div class="label">Hi</div>
|
||||||
|
<div class="value value-hi"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<%- include('scroll.ejs') %>
|
26
views/partials/header.ejs
Normal file
26
views/partials/header.ejs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo"><img src="images/Logo3.png" /></div>
|
||||||
|
<% if (locals?.titleDual) { %>
|
||||||
|
<div class="title dual">
|
||||||
|
<div class="top">
|
||||||
|
<%-titleDual.top %>
|
||||||
|
</div>
|
||||||
|
<div class="bottom">
|
||||||
|
<%-titleDual.bottom %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% } else { %>
|
||||||
|
<div class="title single">
|
||||||
|
<%-title %>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
|
<% if (locals?.hasTime) { %>
|
||||||
|
<div class="date-time date"></div>
|
||||||
|
<div class="date-time time"></div>
|
||||||
|
<% } %>
|
||||||
|
<% if (locals?.noaaLogo) { %>
|
||||||
|
<div class="noaa-logo">
|
||||||
|
<img src="images/noaa5.gif" />
|
||||||
|
</div>
|
||||||
|
<%}%>
|
||||||
|
</div>
|
18
views/partials/hourly.ejs
Normal file
18
views/partials/hourly.ejs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<%- include('header.ejs', {title: 'Hourly Forecast' , hasTime: true }) %>
|
||||||
|
<div class="main has-scroll hourly">
|
||||||
|
<div class="column-headers">
|
||||||
|
<div class="temp">TEMP</div>
|
||||||
|
<div class="like">LIKE</div>
|
||||||
|
<div class="wind">WIND</div>
|
||||||
|
</div>
|
||||||
|
<div class="hourly-lines">
|
||||||
|
<div class="hourly-row template">
|
||||||
|
<div class="hour"></div>
|
||||||
|
<div class="icon"><img /></div>
|
||||||
|
<div class="temp"></div>
|
||||||
|
<div class="like"></div>
|
||||||
|
<div class="wind"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<%- include('scroll.ejs') %>
|
20
views/partials/latest-observations.ejs
Normal file
20
views/partials/latest-observations.ejs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<%- include('header.ejs', {titleDual:{ top: 'Latest' , bottom: 'Observations' }, noaaLogo: true }) %>
|
||||||
|
<div class="main has-scroll latest-observations has-box">
|
||||||
|
<div class="container">
|
||||||
|
<div class="column-headers">
|
||||||
|
<div class="temp english">°F</div>
|
||||||
|
<div class="temp metric">°C</div>
|
||||||
|
<div class="weather">Weather</div>
|
||||||
|
<div class="wind">Wind</div>
|
||||||
|
</div>
|
||||||
|
<div class="observation-lines">
|
||||||
|
<div class="observation-row template">
|
||||||
|
<div class="location"></div>
|
||||||
|
<div class="temp"></div>
|
||||||
|
<div class="weather"></div>
|
||||||
|
<div class="wind"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<%- include('scroll.ejs') %>
|
12
views/partials/local-forecast.ejs
Normal file
12
views/partials/local-forecast.ejs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<%- include('header.ejs', {titleDual:{ top: 'Local' , bottom: 'Forecast' }, hasTime: true, noaaLogo: true}) %>
|
||||||
|
<div class="main has-scroll has-box local-forecast">
|
||||||
|
<div class="container">
|
||||||
|
<div class="forecasts">
|
||||||
|
<div class="forecast template">
|
||||||
|
<div class="text">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<%- include('scroll.ejs') %>
|
21
views/partials/progress.ejs
Normal file
21
views/partials/progress.ejs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<%- include('header.ejs', {titleDual:{ top: 'WeatherStar' , bottom: '4000+ v' + version }, hasTime: true}) %>
|
||||||
|
<div class="main has-box progress">
|
||||||
|
<div class="container">
|
||||||
|
<div class="item template">
|
||||||
|
<div class="name">Current Conditions</div>
|
||||||
|
<div class="links loading">
|
||||||
|
<div class="loading">Loading</div>
|
||||||
|
<div class="press-here">Press Here</div>
|
||||||
|
<div class="failed">Failed</div>
|
||||||
|
<div class="no-data">No Data</div>
|
||||||
|
<div class="disabled">Disabled</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="scroll">
|
||||||
|
<div class="progress-bar-container show">
|
||||||
|
<div class="progress-bar"></div>
|
||||||
|
<div class="cover"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
43
views/partials/radar.ejs
Normal file
43
views/partials/radar.ejs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo"><img src="images/Logo3.png" /></div>
|
||||||
|
<div class="title dual">
|
||||||
|
<div class="top">
|
||||||
|
Local
|
||||||
|
</div>
|
||||||
|
<div class="bottom">
|
||||||
|
Radar
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<div class="precip">
|
||||||
|
<div class="precip-header">PRECIP</div>
|
||||||
|
<div class="scale">
|
||||||
|
<div class="text">Light</div>
|
||||||
|
<div class="scale-table">
|
||||||
|
<div class="box box-1"></div>
|
||||||
|
<div class="box box-2"></div>
|
||||||
|
<div class="box box-3"></div>
|
||||||
|
<div class="box box-4"></div>
|
||||||
|
<div class="box box-5"></div>
|
||||||
|
<div class="box box-6"></div>
|
||||||
|
<div class="box box-7"></div>
|
||||||
|
<div class="box box-7"></div>
|
||||||
|
</div>
|
||||||
|
<div class="text">Heavy</div>
|
||||||
|
</div>
|
||||||
|
<div class="time"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main radar">
|
||||||
|
<div class="container">
|
||||||
|
<div class="scroll-area">
|
||||||
|
<div class="frame template">
|
||||||
|
<div class="map">
|
||||||
|
<img src="images/4000RadarMap2.jpg" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
14
views/partials/regional-forecast.ejs
Normal file
14
views/partials/regional-forecast.ejs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<%- include('header.ejs', {titleDual:{ top: 'Regional' , bottom: 'Observations' }, hasTime: true }) %>
|
||||||
|
<div class="main has-scroll regional-forecast">
|
||||||
|
<div class="map"><img src="images/Basemap2.png" /></div>
|
||||||
|
<div class="location-container">
|
||||||
|
<div class="location template">
|
||||||
|
<div class="icon">
|
||||||
|
<img src="" />
|
||||||
|
</div>
|
||||||
|
<div class="city"></div>
|
||||||
|
<div class="temp"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<%- include('scroll.ejs') %>
|
4
views/partials/scroll.ejs
Normal file
4
views/partials/scroll.ejs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<div class="scroll">
|
||||||
|
<div class="scrolling template"></div>
|
||||||
|
<div class="fixed"></div>
|
||||||
|
</div>
|
16
views/partials/travel.ejs
Normal file
16
views/partials/travel.ejs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<%- include('header.ejs', {titleDual: {top: 'Travel Forecast', bottom: 'For '} , hasTime: true }) %>
|
||||||
|
<div class="main has-scroll travel">
|
||||||
|
<div class="column-headers">
|
||||||
|
<div class="temp low">LOW</div>
|
||||||
|
<div class="temp high">HIGH</div>
|
||||||
|
</div>
|
||||||
|
<div class="travel-lines">
|
||||||
|
<div class="travel-row template">
|
||||||
|
<div class="city"></div>
|
||||||
|
<div class="icon"><img /></div>
|
||||||
|
<div class="temp low"></div>
|
||||||
|
<div class="temp high"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<%- include('scroll.ejs') %>
|
|
@ -6,28 +6,32 @@
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"**/node_modules": true,
|
|
||||||
"**/bower_components": true,
|
|
||||||
"**/*.code-search": true,
|
"**/*.code-search": true,
|
||||||
"dist/**": true,
|
"**/*.css": true,
|
||||||
"**/*.min.js": true,
|
"**/*.min.js": true,
|
||||||
|
"**/bower_components": true,
|
||||||
|
"**/node_modules": true,
|
||||||
"**/vendor": true,
|
"**/vendor": true,
|
||||||
|
"dist/**": true
|
||||||
},
|
},
|
||||||
"cSpell.enabled": true,
|
"cSpell.enabled": true,
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"'storm",
|
"'storm",
|
||||||
|
"arcgis",
|
||||||
"Battaglia",
|
"Battaglia",
|
||||||
|
"devbridge",
|
||||||
|
"gifs",
|
||||||
|
"ltrim",
|
||||||
"Noaa",
|
"Noaa",
|
||||||
|
"nosleep",
|
||||||
"Pngs",
|
"Pngs",
|
||||||
|
"PRECIP",
|
||||||
|
"rtrim",
|
||||||
"T",
|
"T",
|
||||||
"T'storm",
|
"T'storm",
|
||||||
|
"uscomp",
|
||||||
"Visib",
|
"Visib",
|
||||||
"arcgis",
|
"Waukegan"
|
||||||
"devbridge",
|
|
||||||
"ltrim",
|
|
||||||
"nosleep",
|
|
||||||
"rtrim",
|
|
||||||
"uscomp"
|
|
||||||
],
|
],
|
||||||
"cSpell.ignorePaths": [
|
"cSpell.ignorePaths": [
|
||||||
"**/package-lock.json",
|
"**/package-lock.json",
|
||||||
|
@ -40,5 +44,11 @@
|
||||||
"**/twc3.js",
|
"**/twc3.js",
|
||||||
],
|
],
|
||||||
"editor.tabSize": 2,
|
"editor.tabSize": 2,
|
||||||
|
"emmet.includeLanguages": {
|
||||||
|
"ejs": "html",
|
||||||
|
},
|
||||||
|
"[html]": {
|
||||||
|
"editor.defaultFormatter": "j69.ejs-beautify"
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
Loading…
Reference in a new issue