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
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"name": "Frontend",
|
||||
"request": "launch",
|
||||
"type": "pwa-chrome",
|
||||
"type": "chrome",
|
||||
"url": "http://localhost:8080",
|
||||
"webRoot": "${workspaceFolder}/server",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**",
|
||||
"**/*.min.js",
|
||||
"**/vendor/**"
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "Data:stations",
|
||||
|
@ -40,7 +39,10 @@
|
|||
"compounds": [
|
||||
{
|
||||
"name": "Compound",
|
||||
"configurations": ["Frontend", "Server"]
|
||||
"configurations": [
|
||||
"Frontend",
|
||||
"Server"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
18
.vscode/settings.json
vendored
18
.vscode/settings.json
vendored
|
@ -1,5 +1,21 @@
|
|||
{
|
||||
"cSpell.enableFiletypes": [
|
||||
"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
|
||||
cd ws4kp
|
||||
npm i
|
||||
node index.js
|
||||
```
|
||||
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?
|
||||
|
||||
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 concat = require('gulp-concat');
|
||||
const terser = require('gulp-terser');
|
||||
const cleanCSS = require('gulp-clean-css');
|
||||
const ejs = require('gulp-ejs');
|
||||
const rename = require('gulp-rename');
|
||||
const htmlmin = require('gulp-htmlmin');
|
||||
|
@ -34,7 +33,6 @@ const jsSources = [
|
|||
'server/scripts/vendor/auto/nosleep.js',
|
||||
'server/scripts/vendor/auto/swiped-events.js',
|
||||
'server/scripts/index.js',
|
||||
'server/scripts/libgif.js',
|
||||
'server/scripts/vendor/auto/luxon.js',
|
||||
'server/scripts/vendor/auto/suncalc.js',
|
||||
'server/scripts/modules/draw.js',
|
||||
|
@ -60,11 +58,10 @@ gulp.task('compress_js', () => gulp.src(jsSources)
|
|||
.pipe(gulp.dest('./dist/resources')));
|
||||
|
||||
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(cleanCSS())
|
||||
.pipe(gulp.dest('./dist/resources')));
|
||||
|
||||
const htmlSources = [
|
||||
|
@ -121,4 +118,4 @@ gulp.task('invalidate', async () => cloudfront.createInvalidation({
|
|||
},
|
||||
}).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');
|
||||
|
|
28242
package-lock.json
generated
28242
package-lock.json
generated
File diff suppressed because it is too large
Load diff
86
package.json
86
package.json
|
@ -1,41 +1,47 @@
|
|||
{
|
||||
"name": "ws4kp",
|
||||
"version": "4.1.5",
|
||||
"description": "Welcome to the WeatherStar 4000+ project page!",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/netbymatt/ws4kp.git"
|
||||
},
|
||||
"author": "Matt Walsh",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/netbymatt/ws4kp/issues"
|
||||
},
|
||||
"homepage": "https://github.com/netbymatt/ws4kp#readme",
|
||||
"devDependencies": {
|
||||
"del": "^6.0.0",
|
||||
"ejs": "^3.1.5",
|
||||
"eslint": "^8.10.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"express": "^4.17.1",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-clean-css": "^4.3.0",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-ejs": "^5.1.0",
|
||||
"gulp-htmlmin": "^5.0.1",
|
||||
"gulp-rename": "^2.0.0",
|
||||
"gulp-s3-upload": "^1.7.3",
|
||||
"gulp-terser": "^2.0.0",
|
||||
"jquery": "^3.6.0",
|
||||
"jquery-touchswipe": "^1.6.19",
|
||||
"luxon": "^3.0.0",
|
||||
"nosleep.js": "^0.12.0",
|
||||
"suncalc": "^1.8.0",
|
||||
"swiped-events": "^1.1.4"
|
||||
}
|
||||
}
|
||||
"name": "ws4kp",
|
||||
"version": "5.0.0",
|
||||
"description": "Welcome to the WeatherStar 4000+ project page!",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build:css": "sass ./server/styles/scss/style.scss ./server/styles/compiled.css",
|
||||
"lint": "eslint ./server/scripts/**",
|
||||
"lint:fix": "eslint --fix ./server/scripts/**"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/netbymatt/ws4kp.git"
|
||||
},
|
||||
"author": "Matt Walsh",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/netbymatt/ws4kp/issues"
|
||||
},
|
||||
"homepage": "https://github.com/netbymatt/ws4kp#readme",
|
||||
"devDependencies": {
|
||||
"del": "^6.0.0",
|
||||
"ejs": "^3.1.5",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"express": "^4.17.1",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-ejs": "^5.1.0",
|
||||
"gulp-htmlmin": "^5.0.1",
|
||||
"gulp-rename": "^2.0.0",
|
||||
"gulp-s3-upload": "^1.7.3",
|
||||
"gulp-sass": "^5.1.0",
|
||||
"gulp-terser": "^2.0.0",
|
||||
"jquery": "^3.6.0",
|
||||
"jquery-touchswipe": "^1.6.19",
|
||||
"luxon": "^3.0.0",
|
||||
"nosleep.js": "^0.12.0",
|
||||
"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,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -4,9 +4,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
});
|
||||
|
||||
const index = (() => {
|
||||
const overrides = {
|
||||
// '32899, Orlando, Florida, USA': { x: -80.6774, y: 28.6143 },
|
||||
};
|
||||
const overrides = {};
|
||||
const AutoRefreshIntervalMs = 500;
|
||||
const AutoRefreshTotalIntervalMs = 600000; // 10 min.
|
||||
|
||||
|
@ -127,8 +125,10 @@ const index = (() => {
|
|||
const TwcUnits = localStorage.getItem('TwcUnits');
|
||||
if (!TwcUnits || TwcUnits === 'ENGLISH') {
|
||||
document.getElementById('radEnglish').checked = true;
|
||||
navigation.message({ type: 'units', message: 'english' });
|
||||
} else if (TwcUnits === 'METRIC') {
|
||||
document.getElementById('radMetric').checked = true;
|
||||
navigation.message({ type: 'units', message: 'metric' });
|
||||
}
|
||||
|
||||
document.getElementById('radEnglish').addEventListener('change', changeUnits);
|
||||
|
@ -231,8 +231,11 @@ const index = (() => {
|
|||
window.scrollTo(0, 0);
|
||||
FullScreenOverride = true;
|
||||
}
|
||||
|
||||
navigation.resize();
|
||||
UpdateFullScreenNavigate();
|
||||
|
||||
// change hover text
|
||||
document.getElementById('ToggleFullScreen').title = 'Exit fullscreen';
|
||||
};
|
||||
|
||||
const ExitFullscreen = () => {
|
||||
|
@ -252,6 +255,9 @@ const index = (() => {
|
|||
} else if (document.msExitFullscreen) {
|
||||
document.msExitFullscreen();
|
||||
}
|
||||
navigation.resize();
|
||||
// change hover text
|
||||
document.getElementById('ToggleFullScreen').title = 'Enter fullscreen';
|
||||
};
|
||||
|
||||
const btnNavigateMenuClick = () => {
|
||||
|
@ -311,6 +317,7 @@ const index = (() => {
|
|||
};
|
||||
|
||||
const btnNavigateRefreshClick = () => {
|
||||
navigation.resetStatuses();
|
||||
LoadTwcData();
|
||||
UpdateFullScreenNavigate();
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,50 +1,35 @@
|
|||
// 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
|
||||
class Almanac extends WeatherDisplay {
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId, 'Almanac');
|
||||
super(navId, elemId, 'Almanac', true);
|
||||
|
||||
// pre-load background images (returns promises)
|
||||
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)
|
||||
this.moonImages = [
|
||||
utils.image.load('images/2/Full-Moon.gif'),
|
||||
utils.image.load('images/2/Last-Quarter.gif'),
|
||||
utils.image.load('images/2/New-Moon.gif'),
|
||||
utils.image.load('images/2/First-Quarter.gif'),
|
||||
];
|
||||
// preload the moon images
|
||||
utils.image.preload('images/2/Full-Moon.gif');
|
||||
utils.image.preload('images/2/Last-Quarter.gif');
|
||||
utils.image.preload('images/2/New-Moon.gif');
|
||||
utils.image.preload('images/2/First-Quarter.gif');
|
||||
|
||||
this.timing.totalScreens = 2;
|
||||
this.timing.totalScreens = 1;
|
||||
}
|
||||
|
||||
async getData(_weatherParameters) {
|
||||
super.getData(_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
|
||||
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
|
||||
this.data = {
|
||||
sun,
|
||||
moon,
|
||||
outlook,
|
||||
};
|
||||
// update status
|
||||
this.setStatus(STATUS.loaded);
|
||||
|
@ -127,115 +112,6 @@ class Almanac extends WeatherDisplay {
|
|||
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() {
|
||||
super.drawCanvas();
|
||||
const info = this.data;
|
||||
|
@ -243,81 +119,47 @@ class Almanac extends WeatherDisplay {
|
|||
const Today = DateTime.local();
|
||||
const Tomorrow = Today.plus({ days: 1 });
|
||||
|
||||
// extract moon images
|
||||
const [FullMoonImage, LastMoonImage, NewMoonImage, FirstMoonImage] = await Promise.all(this.moonImages);
|
||||
// sun and moon data
|
||||
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) {
|
||||
case 1: {
|
||||
this.context.drawImage(await this.backgroundImage1, 0, 0);
|
||||
draw.horizontalGradientSingle(this.context, 0, 30, 500, 90, draw.topColor1, draw.topColor2);
|
||||
draw.triangle(this.context, 'rgb(28, 10, 87)', 500, 30, 450, 90, 500, 90);
|
||||
draw.horizontalGradientSingle(this.context, 0, 90, 52, 399, draw.sideColor1, draw.sideColor2);
|
||||
draw.horizontalGradientSingle(this.context, 584, 90, 640, 399, draw.sideColor1, draw.sideColor2);
|
||||
const days = info.moon.map((MoonPhase) => {
|
||||
const fill = {};
|
||||
|
||||
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()}`;
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 320, 220, DateRange, 2, 'center');
|
||||
return this.fillTemplate('day', fill);
|
||||
});
|
||||
|
||||
const Temperature = info.outlook.temperature;
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 70, 300, `Temperatures: ${Temperature}`, 2);
|
||||
|
||||
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;
|
||||
}
|
||||
const daysContainer = this.elem.querySelector('.moon .days');
|
||||
daysContainer.innerHTML = '';
|
||||
daysContainer.append(...days);
|
||||
|
||||
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
|
||||
// promise allows for data to be requested before it is available
|
||||
async getSun() {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// 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
|
||||
class CurrentWeather extends WeatherDisplay {
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId, 'Current Conditions');
|
||||
super(navId, elemId, 'Current Conditions', true);
|
||||
// pre-load background image (returns promise)
|
||||
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.TemperatureUnit = 'C';
|
||||
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.Visibility = Math.round(observations.visibility.value / 1000);
|
||||
data.VisibilityUnit = ' km.';
|
||||
|
@ -111,94 +111,43 @@ class CurrentWeather extends WeatherDisplay {
|
|||
|
||||
async drawCanvas() {
|
||||
super.drawCanvas();
|
||||
const fill = {};
|
||||
// parse each time to deal with a change in units if necessary
|
||||
const data = this.parseData();
|
||||
|
||||
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, 'Current', 'Conditions');
|
||||
|
||||
draw.text(this.context, 'Star4000 Large', '24pt', '#FFFFFF', 170, 135, data.Temperature + String.fromCharCode(176), 2);
|
||||
fill.temp = data.Temperature + String.fromCharCode(176);
|
||||
|
||||
let Conditions = data.observations.textDescription;
|
||||
if (Conditions.length > 15) {
|
||||
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);
|
||||
draw.text(this.context, 'Star4000 Extended', '24pt', '#FFFFFF', 300, 330, `${data.WindDirection} ${data.WindSpeed}`, 2, 'right');
|
||||
fill.wind = data.WindDirection.padEnd(3, '') + data.WindSpeed.toString().padStart(3, ' ');
|
||||
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);
|
||||
|
||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 340, 165, 'Humidity:', 2);
|
||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 560, 165, `${data.Humidity}%`, 2, 'right');
|
||||
|
||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 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:
|
||||
}
|
||||
fill.humidity = `${data.Humidity}%`;
|
||||
fill.dewpoint = data.DewPoint + String.fromCharCode(176);
|
||||
fill.ceiling = (data.Ceiling === 0 ? 'Unlimited' : data.Ceiling + data.CeilingUnit);
|
||||
fill.visibility = data.Visibility + data.VisibilityUnit;
|
||||
fill.pressure = `${data.Pressure} ${data.PressureDirection}`;
|
||||
|
||||
if (data.observations.heatIndex.value && data.HeatIndex !== data.Temperature) {
|
||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 340, 365, 'Heat Index:', 2);
|
||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 560, 365, data.HeatIndex + String.fromCharCode(176), 2, 'right');
|
||||
fill['heat-index-label'] = 'Heat Index:';
|
||||
fill['heat-index'] = data.HeatIndex + String.fromCharCode(176);
|
||||
} 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);
|
||||
draw.text(this.context, 'Star4000 Large', 'bold 16pt', '#FFFFFF', 560, 365, data.WindChill + String.fromCharCode(176), 2, 'right');
|
||||
fill['heat-index-label'] = 'Wind Chill:';
|
||||
fill['heat-index'] = data.WindChill + String.fromCharCode(176);
|
||||
}
|
||||
|
||||
// get main icon
|
||||
this.gifs.push(await utils.image.superGifAsync({
|
||||
src: data.Icon,
|
||||
auto_play: true,
|
||||
canvas: this.canvas,
|
||||
x: 140,
|
||||
y: 175,
|
||||
max_width: 126,
|
||||
}));
|
||||
fill.icon = { type: 'img', src: data.Icon };
|
||||
|
||||
const area = this.elem.querySelector('.main');
|
||||
|
||||
area.innerHTML = '';
|
||||
area.append(this.fillTemplate('weather', fill));
|
||||
|
||||
this.finishDraw();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* globals draw, navigation */
|
||||
/* globals navigation, utils */
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const currentWeatherScroll = (() => {
|
||||
|
@ -6,25 +6,13 @@ const currentWeatherScroll = (() => {
|
|||
const degree = String.fromCharCode(176);
|
||||
|
||||
// local variables
|
||||
let context; // currently active context
|
||||
let blankDrawArea; // original state of context
|
||||
let interval;
|
||||
let screenIndex = 0;
|
||||
|
||||
// start drawing conditions
|
||||
// reset starts from the first item in the text scroll list
|
||||
const start = (_context) => {
|
||||
// see if there is a context available
|
||||
if (!_context) return;
|
||||
const start = () => {
|
||||
// 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
|
||||
if (!interval) {
|
||||
|
@ -36,17 +24,10 @@ const currentWeatherScroll = (() => {
|
|||
};
|
||||
|
||||
const stop = (reset) => {
|
||||
cleanLastContext();
|
||||
if (interval) interval = clearInterval(interval);
|
||||
if (reset) screenIndex = 0;
|
||||
};
|
||||
|
||||
const cleanLastContext = () => {
|
||||
if (blankDrawArea) context.putImageData(blankDrawArea, 0, 405);
|
||||
blankDrawArea = undefined;
|
||||
context = undefined;
|
||||
};
|
||||
|
||||
// increment interval, roll over
|
||||
const incrementInterval = () => {
|
||||
screenIndex = (screenIndex + 1) % (screens.length);
|
||||
|
@ -61,16 +42,13 @@ const currentWeatherScroll = (() => {
|
|||
// nothing to do if there's no data yet
|
||||
if (!data) return;
|
||||
|
||||
// clean up any old text
|
||||
context.putImageData(blankDrawArea, 0, 405);
|
||||
|
||||
drawCondition(screens[screenIndex](data));
|
||||
};
|
||||
|
||||
// the "screens" are stored in an array for easy addition and removal
|
||||
const screens = [
|
||||
// station name
|
||||
(data) => `Conditions at ${data.station.properties.name.substr(0, 20)}`,
|
||||
(data) => `Conditions at ${utils.string.locationCleanup(data.station.properties.name).substr(0, 20)}`,
|
||||
|
||||
// temperature
|
||||
(data) => {
|
||||
|
@ -109,7 +87,10 @@ const currentWeatherScroll = (() => {
|
|||
|
||||
// internal draw function with preset parameters
|
||||
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
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
// display extended forecast graphically
|
||||
// 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
|
||||
class ExtendedForecast extends WeatherDisplay {
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId, 'Extended Forecast');
|
||||
super(navId, elemId, 'Extended Forecast', true);
|
||||
|
||||
// set timings
|
||||
this.timing.totalScreens = 2;
|
||||
|
||||
// pre-load background image (returns promise)
|
||||
this.backgroundImage = utils.image.load('images/BackGround2_1.png');
|
||||
}
|
||||
|
||||
async getData(_weatherParameters) {
|
||||
|
@ -85,14 +82,18 @@ class ExtendedForecast extends WeatherDisplay {
|
|||
}
|
||||
|
||||
static shortenExtendedForecastText(long) {
|
||||
let short = long;
|
||||
short = short.replace(/ and /g, ' ');
|
||||
short = short.replace(/Slight /g, '');
|
||||
short = short.replace(/Chance /g, '');
|
||||
short = short.replace(/Very /g, '');
|
||||
short = short.replace(/Patchy /g, '');
|
||||
short = short.replace(/Areas /g, '');
|
||||
short = short.replace(/Dense /g, '');
|
||||
const regexList = [
|
||||
[/ and /ig, ' '],
|
||||
[/Slight /ig, ''],
|
||||
[/Chance /ig, ''],
|
||||
[/Very /ig, ''],
|
||||
[/Patchy /ig, ''],
|
||||
[/Areas /ig, ''],
|
||||
[/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(' ');
|
||||
if (short.indexOf('then') !== -1) {
|
||||
|
@ -113,12 +114,12 @@ class ExtendedForecast extends WeatherDisplay {
|
|||
short2 = '';
|
||||
}
|
||||
}
|
||||
short = short1;
|
||||
let result = short1;
|
||||
if (short2 !== '') {
|
||||
short += ` ${short2}`;
|
||||
result += ` ${short2}`;
|
||||
}
|
||||
|
||||
return [short, short1, short2];
|
||||
return result;
|
||||
}
|
||||
|
||||
async drawCanvas() {
|
||||
|
@ -128,45 +129,32 @@ class ExtendedForecast extends WeatherDisplay {
|
|||
// 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 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;
|
||||
if (low !== undefined) {
|
||||
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;
|
||||
if (navigation.units() === UNITS.metric) high = utils.units.fahrenheitToCelsius(high);
|
||||
draw.text(this.context, 'Star4000 Large', '24pt', '#FFFFFF', 165 + offset, 385, high, 2, 'center');
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 120 + offset, 270, Day.text[1], 2, 'center');
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 120 + offset, 310, Day.text[2], 2, 'center');
|
||||
fill['value-hi'] = Math.round(high);
|
||||
fill.condition = Day.text;
|
||||
|
||||
// draw the icon
|
||||
this.gifs.push(await utils.image.superGifAsync({
|
||||
src: Day.icon,
|
||||
auto_play: true,
|
||||
canvas: this.canvas,
|
||||
x: 70 + Index * 195,
|
||||
y: 150,
|
||||
max_height: 75,
|
||||
}));
|
||||
}));
|
||||
fill.icon = { type: 'img', src: Day.icon };
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,17 @@
|
|||
// 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
|
||||
class Hourly extends WeatherDisplay {
|
||||
constructor(navId, elemId, defaultActive) {
|
||||
// special height and width for scrolling
|
||||
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
|
||||
this.timing.baseDelay = 20;
|
||||
// 24 hours = 6 pages
|
||||
const pages = 4; // first page is already displayed, last page doesn't happen
|
||||
const timingStep = this.hourHeight * 4;
|
||||
const timingStep = 75 * 4;
|
||||
this.timing.delay = [150 + timingStep];
|
||||
// add additional pages
|
||||
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(e.status, e.responseJSON);
|
||||
this.setStatus(STATUS.failed);
|
||||
return;
|
||||
}
|
||||
|
||||
this.data = await Hourly.parseForecast(forecast.properties);
|
||||
|
@ -114,52 +110,26 @@ class Hourly extends WeatherDisplay {
|
|||
}
|
||||
|
||||
async drawLongCanvas() {
|
||||
// create the "long" canvas if necessary
|
||||
if (!this.longCanvas) {
|
||||
this.longCanvas = document.createElement('canvas');
|
||||
this.longCanvas.width = 640;
|
||||
this.longCanvas.height = 24 * this.hourHeight;
|
||||
this.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');
|
||||
}
|
||||
// get the list element and populate
|
||||
const list = this.elem.querySelector('.hourly-lines');
|
||||
list.innerHTML = '';
|
||||
|
||||
const startingHour = luxon.DateTime.local();
|
||||
|
||||
await Promise.all(this.data.map(async (data, index) => {
|
||||
// calculate base y value
|
||||
const y = 50 + this.hourHeight * index;
|
||||
|
||||
const lines = this.data.map((data, index) => {
|
||||
const fillValues = {};
|
||||
// hour
|
||||
const hour = startingHour.plus({ hours: index });
|
||||
const formattedHour = hour.toLocaleString({ weekday: 'short', hour: 'numeric' });
|
||||
draw.text(this.longContext, 'Star4000 Large Compressed', '24pt', '#FFFF00', 80, y, formattedHour, 2);
|
||||
fillValues.hour = formattedHour;
|
||||
|
||||
// temperatures, convert to strings with no decimal
|
||||
const temperature = Math.round(data.temperature).toString().padStart(3);
|
||||
const feelsLike = Math.round(data.apparentTemperature).toString().padStart(3);
|
||||
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
|
||||
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
|
||||
let wind = 'Calm';
|
||||
|
@ -167,44 +137,25 @@ class Hourly extends WeatherDisplay {
|
|||
const windSpeed = Math.round(data.windSpeed).toString();
|
||||
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({
|
||||
src: data.icon,
|
||||
auto_play: true,
|
||||
canvas: this.longCanvas,
|
||||
x: 290,
|
||||
y: y - 35,
|
||||
max_width: 47,
|
||||
}));
|
||||
}));
|
||||
// image
|
||||
fillValues.icon = { type: 'img', src: data.icon };
|
||||
|
||||
return this.fillTemplate('hourly-row', fillValues);
|
||||
});
|
||||
|
||||
list.append(...lines);
|
||||
}
|
||||
|
||||
async 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
|
||||
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();
|
||||
}
|
||||
|
||||
async showCanvas() {
|
||||
// special to travel forecast to draw the remainder of the canvas
|
||||
await this.drawCanvas();
|
||||
showCanvas() {
|
||||
// special to hourly to draw the remainder of the canvas
|
||||
this.drawCanvas();
|
||||
super.showCanvas();
|
||||
}
|
||||
|
||||
|
@ -215,17 +166,14 @@ class Hourly extends WeatherDisplay {
|
|||
|
||||
// base count change callback
|
||||
baseCountChange(count) {
|
||||
// get a fresh canvas
|
||||
const longCanvas = this.getLongCanvas();
|
||||
|
||||
// calculate scroll offset and don't go past end
|
||||
let offsetY = Math.min(longCanvas.height - 289, (count - 150));
|
||||
let offsetY = Math.min(this.elem.querySelector('.hourly-lines').getBoundingClientRect().height - 289, (count - 150));
|
||||
|
||||
// don't let offset go negative
|
||||
if (offsetY < 0) offsetY = 0;
|
||||
|
||||
// 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) {
|
||||
|
@ -241,9 +189,4 @@ class Hourly extends WeatherDisplay {
|
|||
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 'nskc':
|
||||
case 'nskc-n':
|
||||
case 'cold-n':
|
||||
return addPath('Clear-1992.gif');
|
||||
|
||||
case 'bkn':
|
||||
|
@ -135,6 +136,9 @@ const icons = (() => {
|
|||
case 'blizzard':
|
||||
return addPath('Blowing Snow.gif');
|
||||
|
||||
case 'cold':
|
||||
return addPath('cold.gif');
|
||||
|
||||
default:
|
||||
console.log(`Unable to locate regional icon for ${conditionName} ${link} ${isNightTime}`);
|
||||
return false;
|
||||
|
@ -142,6 +146,8 @@ const icons = (() => {
|
|||
};
|
||||
|
||||
const getWeatherIconFromIconLink = (link, _isNightTime) => {
|
||||
if (!link) return false;
|
||||
|
||||
// internal function to add path to returned icon
|
||||
const addPath = (icon) => `images/${icon}`;
|
||||
// extract day or night if not provided
|
||||
|
@ -164,11 +170,13 @@ const icons = (() => {
|
|||
case 'skc':
|
||||
case 'hot':
|
||||
case 'haze':
|
||||
case 'cold':
|
||||
return addPath('CC_Clear1.gif');
|
||||
|
||||
case 'skc-n':
|
||||
case 'nskc':
|
||||
case 'nskc-n':
|
||||
case 'cold-n':
|
||||
return addPath('CC_Clear0.gif');
|
||||
|
||||
case 'sct':
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
// 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
|
||||
class LatestObservations extends WeatherDisplay {
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId, 'Latest Observations');
|
||||
// pre-load background image (returns promise)
|
||||
this.backgroundImage = utils.image.load('images/BackGround1_1.png');
|
||||
super(navId, elemId, 'Latest Observations', true);
|
||||
|
||||
// constants
|
||||
this.MaximumRegionalStations = 7;
|
||||
|
@ -67,25 +65,15 @@ class LatestObservations extends WeatherDisplay {
|
|||
// sort array by station name
|
||||
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) {
|
||||
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 {
|
||||
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;
|
||||
|
||||
sortedConditions.forEach((condition) => {
|
||||
const lines = sortedConditions.map((condition) => {
|
||||
let Temperature = condition.temperature.value;
|
||||
let WindSpeed = condition.windSpeed.value;
|
||||
const windDirection = utils.calc.directionToNSEW(condition.windDirection.value);
|
||||
|
@ -94,23 +82,28 @@ class LatestObservations extends WeatherDisplay {
|
|||
Temperature = utils.units.celsiusToFahrenheit(Temperature);
|
||||
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);
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 345, y, LatestObservations.shortenCurrentConditions(condition.textDescription).substr(0, 9), 2);
|
||||
|
||||
const fill = {};
|
||||
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) {
|
||||
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') {
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 495, y, 'NA', 2);
|
||||
fill.wind = 'NA';
|
||||
} else {
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', 495, y, 'Calm', 2);
|
||||
fill.wind = 'Calm';
|
||||
}
|
||||
|
||||
const x = (325 - (Temperature.toString().length * 15));
|
||||
draw.text(this.context, 'Star4000', '24pt', '#FFFFFF', x, y, Temperature, 2);
|
||||
|
||||
y += 40;
|
||||
return this.fillTemplate('observation-row', fill);
|
||||
});
|
||||
|
||||
const linesContainer = this.elem.querySelector('.observation-lines');
|
||||
linesContainer.innerHTML = '';
|
||||
linesContainer.append(...lines);
|
||||
|
||||
this.finishDraw();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
// 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
|
||||
class LocalForecast extends WeatherDisplay {
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId, 'Local Forecast');
|
||||
super(navId, elemId, 'Local Forecast', true);
|
||||
|
||||
// set timings
|
||||
this.timing.baseDelay = 5000;
|
||||
|
||||
// pre-load background image (returns promise)
|
||||
this.backgroundImage = utils.image.load('images/BackGround1_1.png');
|
||||
}
|
||||
|
||||
async getData(_weatherParameters) {
|
||||
|
@ -28,14 +25,8 @@ class LocalForecast extends WeatherDisplay {
|
|||
// parse raw data
|
||||
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
|
||||
conditions.forEach((condition) => {
|
||||
this.screenTexts = conditions.map((condition) => {
|
||||
// process the text
|
||||
let text = `${condition.DayName.toUpperCase()}...`;
|
||||
let conditionText = condition.Text;
|
||||
|
@ -44,44 +35,23 @@ class LocalForecast extends WeatherDisplay {
|
|||
}
|
||||
text += conditionText.toUpperCase().replace('...', ' ');
|
||||
|
||||
text = utils.string.wordWrap(text, maxCols, '\n');
|
||||
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);
|
||||
// }
|
||||
return text;
|
||||
});
|
||||
|
||||
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.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() {
|
||||
super.drawCanvas();
|
||||
|
||||
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);
|
||||
const top = -this.screenIndex * this.pageHeight;
|
||||
this.elem.querySelector('.forecasts').style.top = `${top}px`;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,8 @@ const navigation = (() => {
|
|||
let almanac;
|
||||
|
||||
const init = async () => {
|
||||
// nothing to do
|
||||
// set up resize handler
|
||||
window.addEventListener('resize', resize);
|
||||
};
|
||||
|
||||
const message = (data) => {
|
||||
|
@ -87,22 +88,22 @@ const navigation = (() => {
|
|||
// draw the progress canvas and hide others
|
||||
hideAllCanvases();
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
progress = new Progress(-1, 'progress');
|
||||
if (!progress) progress = new Progress(-1, 'progress');
|
||||
await progress.drawCanvas();
|
||||
progress.showCanvas();
|
||||
|
||||
// start loading canvases if necessary
|
||||
if (displays.length === 0) {
|
||||
currentWeather = new CurrentWeather(0, 'currentWeather');
|
||||
currentWeather = new CurrentWeather(0, 'current-weather');
|
||||
almanac = new Almanac(7, 'almanac');
|
||||
displays = [
|
||||
currentWeather,
|
||||
new LatestObservations(1, 'latestObservations'),
|
||||
new LatestObservations(1, 'latest-observations'),
|
||||
new Hourly(2, 'hourly'),
|
||||
new TravelForecast(3, 'travelForecast', false), // not active by default
|
||||
new RegionalForecast(4, 'regionalForecast'),
|
||||
new LocalForecast(5, 'localForecast'),
|
||||
new ExtendedForecast(6, 'extendedForecast'),
|
||||
new TravelForecast(3, 'travel', false), // not active by default
|
||||
new RegionalForecast(4, 'regional-forecast'),
|
||||
new LocalForecast(5, 'local-forecast'),
|
||||
new ExtendedForecast(6, 'extended-forecast'),
|
||||
almanac,
|
||||
new Radar(8, 'radar'),
|
||||
];
|
||||
|
@ -177,7 +178,15 @@ const navigation = (() => {
|
|||
progress.hideCanvas();
|
||||
if (!current) {
|
||||
// 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;
|
||||
}
|
||||
if (direction === msg.command.nextFrame) currentDisplay().navNext();
|
||||
|
@ -266,6 +275,25 @@ const navigation = (() => {
|
|||
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 {
|
||||
init,
|
||||
message,
|
||||
|
@ -277,5 +305,7 @@ const navigation = (() => {
|
|||
getDisplay,
|
||||
getCurrentWeather,
|
||||
getSun,
|
||||
resize,
|
||||
resetStatuses,
|
||||
};
|
||||
})();
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
// regional forecast and observations
|
||||
|
||||
/* globals WeatherDisplay, utils, STATUS, draw, navigation */
|
||||
/* globals WeatherDisplay, utils, STATUS, navigation */
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
class Progress extends WeatherDisplay {
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId);
|
||||
super(navId, elemId, '', false);
|
||||
|
||||
// pre-load background image (returns promise)
|
||||
this.backgroundImage = utils.image.load('images/BackGround1_1.png');
|
||||
|
@ -14,101 +14,90 @@ class Progress extends WeatherDisplay {
|
|||
this.timing = false;
|
||||
|
||||
this.version = document.getElementById('version').innerHTML;
|
||||
|
||||
// setup event listener
|
||||
this.elem.querySelector('.container').addEventListener('click', this.lineClick.bind(this));
|
||||
}
|
||||
|
||||
async drawCanvas(displays, loadedCount) {
|
||||
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
|
||||
const backgroundImage = await this.backgroundImage;
|
||||
// get the progress bar cover (makes percentage)
|
||||
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 (!displays) return;
|
||||
displays.forEach((display, idx) => {
|
||||
const y = 120 + idx * 29;
|
||||
const dots = Array(120 - Math.floor(display.name.length * 2.5)).join('.');
|
||||
draw.text(this.context, 'Star4000 Extended', '19pt', '#ffffff', 70, y, display.name + dots, 2);
|
||||
const lines = displays.map((display, index) => {
|
||||
const fill = {};
|
||||
|
||||
let statusText;
|
||||
let statusColor;
|
||||
fill.name = display.name;
|
||||
|
||||
let statusClass;
|
||||
switch (display.status) {
|
||||
case STATUS.loading:
|
||||
statusText = 'Loading';
|
||||
statusColor = '#ffff00';
|
||||
statusClass = 'loading';
|
||||
break;
|
||||
case STATUS.loaded:
|
||||
statusText = 'Press Here';
|
||||
statusColor = '#00ff00';
|
||||
this.context.drawImage(backgroundImage, 440, y - 20, 75, 25, 440, y - 20, 75, 25);
|
||||
statusClass = 'press-here';
|
||||
break;
|
||||
case STATUS.failed:
|
||||
statusText = 'Failed';
|
||||
statusColor = '#ff0000';
|
||||
statusClass = 'failed';
|
||||
break;
|
||||
case STATUS.noData:
|
||||
statusText = 'No Data';
|
||||
statusColor = '#C0C0C0';
|
||||
draw.box(this.context, 'rgb(33, 40, 90)', 475, y - 15, 75, 15);
|
||||
statusClass = 'no-data';
|
||||
break;
|
||||
case STATUS.disabled:
|
||||
statusText = 'Disabled';
|
||||
statusColor = '#C0C0C0';
|
||||
this.context.drawImage(backgroundImage, 470, y - 20, 45, 25, 470, y - 20, 45, 25);
|
||||
statusClass = 'disabled';
|
||||
break;
|
||||
default:
|
||||
}
|
||||
// Erase any dots that spill into the status text.
|
||||
this.context.drawImage(backgroundImage, 475, y - 20, 165, 30, 475, y - 20, 165, 30);
|
||||
draw.text(this.context, 'Star4000 Extended', '19pt', statusColor, 565, y, statusText, 2, 'end');
|
||||
});
|
||||
|
||||
// make the line
|
||||
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
|
||||
const loadedPercent = (loadedCount / displays.length);
|
||||
|
||||
this.progressCover.style.width = `${(1.0 - loadedPercent) * 100}%`;
|
||||
if (loadedPercent < 1.0) {
|
||||
// Draw a box for the progress.
|
||||
draw.box(this.context, '#000000', 51, 428, 534, 22);
|
||||
draw.box(this.context, '#ffffff', 53, 430, 530, 18);
|
||||
// update the progress gif
|
||||
draw.box(this.context, '#1d7fff', 55, 432, 526 * loadedPercent, 14);
|
||||
// show the progress bar and set width
|
||||
this.progressCover.parentNode.classList.add('show');
|
||||
} else {
|
||||
// restore the background
|
||||
this.context.drawImage(backgroundImage, 51, 428, 534, 22, 51, 428, 534, 22);
|
||||
// hide the progressbar after 1 second (lines up with with width transition animation)
|
||||
setTimeout(() => this.progressCover.parentNode.classList.remove('show'), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
canvasClick(e) {
|
||||
const x = e.offsetX;
|
||||
const y = e.offsetY;
|
||||
// eliminate off canvas and outside area clicks
|
||||
if (!this.isActive()) return;
|
||||
if (y < 100 || y > 410) return;
|
||||
if (x < 440 || x > 570) return;
|
||||
lineClick(e) {
|
||||
// get index
|
||||
const indexRaw = e.target?.parentNode?.dataset?.index;
|
||||
if (indexRaw === undefined) return;
|
||||
const index = +indexRaw;
|
||||
|
||||
// stop playing
|
||||
navigation.message('navButton');
|
||||
// use the y value to determine an index
|
||||
const index = Math.floor((y - 100) / 29);
|
||||
const display = navigation.getDisplay(index);
|
||||
if (display && display.status === STATUS.loaded) {
|
||||
display.showCanvas(navigation.msg.command.firstFrame);
|
||||
this.hideCanvas();
|
||||
this.elem.classList.remove('show');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// current weather conditions display
|
||||
/* globals WeatherDisplay, utils, STATUS, draw, luxon */
|
||||
/* globals WeatherDisplay, utils, STATUS, luxon */
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
class Radar extends WeatherDisplay {
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId, 'Local Radar');
|
||||
super(navId, elemId, 'Local Radar', true);
|
||||
|
||||
// set max images
|
||||
this.dopplerRadarImageMax = 6;
|
||||
|
@ -31,9 +31,6 @@ class Radar extends WeatherDisplay {
|
|||
{ time: 1, si: 4 },
|
||||
{ time: 12, si: 5 },
|
||||
];
|
||||
|
||||
// pre-load background image (returns promise)
|
||||
this.backgroundImage = utils.image.load('images/BackGround4_1.png');
|
||||
}
|
||||
|
||||
async getData(_weatherParameters) {
|
||||
|
@ -163,7 +160,6 @@ class Radar extends WeatherDisplay {
|
|||
const imgBlob = await utils.image.load(blob);
|
||||
|
||||
// draw the entire image
|
||||
|
||||
workingContext.clearRect(0, 0, width, 1600);
|
||||
workingContext.drawImage(imgBlob, 0, 0, width, 1600);
|
||||
|
||||
|
@ -174,7 +170,7 @@ class Radar extends WeatherDisplay {
|
|||
const cropCanvas = document.createElement('canvas');
|
||||
cropCanvas.width = 640;
|
||||
cropCanvas.height = 367;
|
||||
const cropContext = cropCanvas.getContext('2d');
|
||||
const cropContext = cropCanvas.getContext('2d', { willReadFrequently: true });
|
||||
cropContext.imageSmoothingEnabled = false;
|
||||
cropContext.drawImage(workingCanvas, radarSourceX, radarSourceY, (radarOffsetX * 2), (radarOffsetY * 2.33), 0, 0, 640, 367);
|
||||
// clean the image
|
||||
|
@ -183,11 +179,20 @@ class Radar extends WeatherDisplay {
|
|||
// merge the radar and map
|
||||
Radar.mergeDopplerRadarImage(context, cropContext);
|
||||
|
||||
const elem = this.fillTemplate('frame', { map: { type: 'img', src: canvas.toDataURL() } });
|
||||
|
||||
return {
|
||||
canvas,
|
||||
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
|
||||
this.timing.totalScreens = radarInfo.length;
|
||||
// store the images
|
||||
|
@ -199,31 +204,13 @@ class Radar extends WeatherDisplay {
|
|||
|
||||
async drawCanvas() {
|
||||
super.drawCanvas();
|
||||
if (this.screenIndex === -1) return;
|
||||
this.context.drawImage(await this.backgroundImage, 0, 0);
|
||||
const { DateTime } = luxon;
|
||||
// Title
|
||||
draw.text(this.context, 'Arial', 'bold 28pt', '#ffffff', 155, 60, 'Local', 2);
|
||||
draw.text(this.context, 'Arial', 'bold 28pt', '#ffffff', 155, 95, 'Radar', 2);
|
||||
const time = this.times[this.screenIndex].toLocaleString(DateTime.TIME_SIMPLE);
|
||||
const timePadded = time.length >= 8 ? time : ` ${time}`;
|
||||
this.elem.querySelector('.header .right .time').innerHTML = timePadded;
|
||||
|
||||
draw.text(this.context, 'Star4000', 'bold 18pt', '#ffffff', 438, 49, 'PRECIP', 2, 'center');
|
||||
draw.text(this.context, 'Star4000', 'bold 18pt', '#ffffff', 298, 73, 'Light', 2);
|
||||
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');
|
||||
// scroll to image
|
||||
this.elem.querySelector('.scroll-area').style.top = `${-this.screenIndex * 371}px`;
|
||||
|
||||
this.finishDraw();
|
||||
}
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
// regional forecast and observations
|
||||
// type 0 = observations, 1 = first forecast, 2 = second forecast
|
||||
|
||||
/* globals WeatherDisplay, utils, STATUS, icons, UNITS, draw, navigation, luxon, StationInfo, RegionalCities */
|
||||
/* globals WeatherDisplay, utils, STATUS, icons, UNITS, navigation, luxon, StationInfo, RegionalCities */
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
class RegionalForecast extends WeatherDisplay {
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId, 'Regional Forecast');
|
||||
|
||||
// pre-load background image (returns promise)
|
||||
this.backgroundImage = utils.image.load('images/BackGround5_1.png');
|
||||
super(navId, elemId, 'Regional Forecast', true);
|
||||
|
||||
// timings
|
||||
this.timing.totalScreens = 3;
|
||||
|
@ -19,14 +16,14 @@ class RegionalForecast extends WeatherDisplay {
|
|||
super.getData(_weatherParameters);
|
||||
const weatherParameters = _weatherParameters ?? this.weatherParameters;
|
||||
|
||||
// pre-load the base map (returns promise)
|
||||
let src = 'images/Basemap2.png';
|
||||
// pre-load the base map
|
||||
let baseMap = 'images/Basemap2.png';
|
||||
if (weatherParameters.state === 'HI') {
|
||||
src = 'images/HawaiiRadarMap4.png';
|
||||
baseMap = 'images/HawaiiRadarMap4.png';
|
||||
} 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
|
||||
const offsetXY = {
|
||||
|
@ -34,10 +31,10 @@ class RegionalForecast extends WeatherDisplay {
|
|||
y: 117,
|
||||
};
|
||||
// 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
|
||||
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
|
||||
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)
|
||||
const regionalForecastPromises = regionalCities.map(async (city) => {
|
||||
const regionalDataAll = await Promise.all(regionalCities.map(async (city) => {
|
||||
try {
|
||||
// get the point first, then break down into forecast and observations
|
||||
const point = await utils.weather.getPoint(city.lat, city.lon);
|
||||
|
@ -77,7 +74,7 @@ class RegionalForecast extends WeatherDisplay {
|
|||
const forecast = await utils.fetch.json(point.properties.forecast);
|
||||
|
||||
// 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
|
||||
const observation = await observationPromise;
|
||||
|
@ -105,14 +102,12 @@ class RegionalForecast extends WeatherDisplay {
|
|||
RegionalForecast.buildForecast(forecast.properties.periods[2], city, cityXY),
|
||||
];
|
||||
} 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);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
// wait for the forecasts
|
||||
const regionalDataAll = await Promise.all(regionalForecastPromises);
|
||||
// filter out any false (unavailable data)
|
||||
const regionalData = regionalDataAll.filter((data) => data);
|
||||
|
||||
|
@ -154,20 +149,21 @@ class RegionalForecast extends WeatherDisplay {
|
|||
// get the observation data
|
||||
const observation = await utils.fetch.json(`${station}/observations/latest`);
|
||||
// preload the image
|
||||
if (!observation.properties.icon) return false;
|
||||
utils.image.preload(icons.getWeatherRegionalIconFromIconLink(observation.properties.icon, !observation.properties.daytime));
|
||||
// return the observation
|
||||
return observation.properties;
|
||||
} 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);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// utility latitude/pixel conversions
|
||||
getXYFromLatitudeLongitude(Latitude, Longitude, OffsetX, OffsetY, state) {
|
||||
if (state === 'AK') return this.getXYFromLatitudeLongitudeAK(Latitude, Longitude, OffsetX, OffsetY);
|
||||
if (state === 'HI') return this.getXYFromLatitudeLongitudeHI(Latitude, Longitude, OffsetX, OffsetY);
|
||||
static getXYFromLatitudeLongitude(Latitude, Longitude, OffsetX, OffsetY, state) {
|
||||
if (state === 'AK') return RegionalForecast.getXYFromLatitudeLongitudeAK(Latitude, Longitude, OffsetX, OffsetY);
|
||||
if (state === 'HI') return RegionalForecast.getXYFromLatitudeLongitudeHI(Latitude, Longitude, OffsetX, OffsetY);
|
||||
let y = 0;
|
||||
let x = 0;
|
||||
const ImgHeight = 1600;
|
||||
|
@ -248,9 +244,9 @@ class RegionalForecast extends WeatherDisplay {
|
|||
return { x, y };
|
||||
}
|
||||
|
||||
getMinMaxLatitudeLongitude(X, Y, OffsetX, OffsetY, state) {
|
||||
if (state === 'AK') return this.getMinMaxLatitudeLongitudeAK(X, Y, OffsetX, OffsetY);
|
||||
if (state === 'HI') return this.getMinMaxLatitudeLongitudeHI(X, Y, OffsetX, OffsetY);
|
||||
static getMinMaxLatitudeLongitude(X, Y, OffsetX, OffsetY, state) {
|
||||
if (state === 'AK') return RegionalForecast.getMinMaxLatitudeLongitudeAK(X, Y, OffsetX, OffsetY);
|
||||
if (state === 'HI') return RegionalForecast.getMinMaxLatitudeLongitudeHI(X, Y, OffsetX, OffsetY);
|
||||
const maxLat = ((Y / 55.2) - 50.5) * -1;
|
||||
const minLat = (((Y + (OffsetY * 2)) / 55.2) - 50.5) * -1;
|
||||
const minLon = (((X * -1) / 41.775) + 127.5) * -1;
|
||||
|
@ -283,9 +279,9 @@ class RegionalForecast extends WeatherDisplay {
|
|||
};
|
||||
}
|
||||
|
||||
getXYForCity(City, MaxLatitude, MinLongitude, state) {
|
||||
if (state === 'AK') this.getXYForCityAK(City, MaxLatitude, MinLongitude);
|
||||
if (state === 'HI') this.getXYForCityHI(City, MaxLatitude, MinLongitude);
|
||||
static getXYForCity(City, MaxLatitude, MinLongitude, state) {
|
||||
if (state === 'AK') RegionalForecast.getXYForCityAK(City, MaxLatitude, MinLongitude);
|
||||
if (state === 'HI') RegionalForecast.getXYForCityHI(City, MaxLatitude, MinLongitude);
|
||||
let x = (City.lon - MinLongitude) * 57;
|
||||
let y = (MaxLatitude - City.lat) * 70;
|
||||
|
||||
|
@ -328,61 +324,61 @@ class RegionalForecast extends WeatherDisplay {
|
|||
return city.match(/[^-;/\\,]*/)[0].substr(0, 12);
|
||||
}
|
||||
|
||||
async drawCanvas() {
|
||||
drawCanvas() {
|
||||
super.drawCanvas();
|
||||
// break up data into useful values
|
||||
const { regionalData: data, sourceXY, offsetXY } = this.data;
|
||||
|
||||
// fixed offset for all y values when drawing to the map
|
||||
const mapYOff = 90;
|
||||
|
||||
const { DateTime } = luxon;
|
||||
// draw the header graphics
|
||||
this.context.drawImage(await this.backgroundImage, 0, 0);
|
||||
draw.horizontalGradientSingle(this.context, 0, 30, 500, 90, draw.topColor1, draw.topColor2);
|
||||
draw.triangle(this.context, 'rgb(28, 10, 87)', 500, 30, 450, 90, 500, 90);
|
||||
|
||||
// draw the appropriate title
|
||||
const titleTop = this.elem.querySelector('.title.dual .top');
|
||||
const titleBottom = this.elem.querySelector('.title.dual .bottom');
|
||||
if (this.screenIndex === 0) {
|
||||
draw.titleText(this.context, 'Regional', 'Observations');
|
||||
titleTop.innerHTML = 'Regional';
|
||||
titleBottom.innerHTML = 'Observations';
|
||||
} else {
|
||||
const forecastDate = DateTime.fromISO(data[0][this.screenIndex].time);
|
||||
|
||||
// get the name of the day
|
||||
const dayName = forecastDate.toLocaleString({ weekday: 'long' });
|
||||
titleTop.innerHTML = 'Forecast for';
|
||||
// draw the title
|
||||
if (data[0][this.screenIndex].daytime) {
|
||||
draw.titleText(this.context, 'Forecast for', dayName);
|
||||
titleBottom.innerHTML = dayName;
|
||||
} else {
|
||||
draw.titleText(this.context, 'Forecast for', `${dayName} Night`);
|
||||
titleBottom.innerHTML = `${dayName} Night`;
|
||||
}
|
||||
}
|
||||
|
||||
// draw the map
|
||||
this.context.drawImage(await this.baseMap, sourceXY.x, sourceXY.y, (offsetXY.x * 2), (offsetXY.y * 2), 0, mapYOff, 640, 312);
|
||||
await Promise.all(data.map(async (city) => {
|
||||
const scale = 640 / (offsetXY.x * 2);
|
||||
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];
|
||||
// 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
|
||||
draw.text(this.context, 'Star4000', '20px', '#ffffff', period.x - 40, period.y - 15 + mapYOff, period.name, 2);
|
||||
|
||||
// Temperature
|
||||
fill.icon = { type: 'img', src: icons.getWeatherRegionalIconFromIconLink(period.icon, !period.daytime) };
|
||||
fill.city = period.name;
|
||||
let { temperature } = period;
|
||||
if (navigation.units() === UNITS.metric) temperature = Math.round(utils.units.fahrenheitToCelsius(temperature));
|
||||
draw.text(this.context, 'Star4000 Large Compressed', '28px', '#ffff00', period.x - (temperature.toString().length * 15), period.y + 20 + mapYOff, temperature, 2);
|
||||
}));
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
// 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
|
||||
class TravelForecast extends WeatherDisplay {
|
||||
constructor(navId, elemId, defaultActive) {
|
||||
// special height and width for scrolling
|
||||
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
|
||||
this.timing.baseDelay = 20;
|
||||
|
@ -18,7 +13,7 @@ class TravelForecast extends WeatherDisplay {
|
|||
const pagesFloat = TravelCities.length / 4;
|
||||
const pages = Math.floor(pagesFloat) - 2; // first page is already displayed, last page doesn't happen
|
||||
const extra = pages % 1;
|
||||
const timingStep = this.cityHeight * 4;
|
||||
const timingStep = 75 * 4;
|
||||
this.timing.delay = [150 + timingStep];
|
||||
// add additional pages
|
||||
for (let i = 0; i < pages; i += 1) this.timing.delay.push(timingStep);
|
||||
|
@ -49,7 +44,7 @@ class TravelForecast extends WeatherDisplay {
|
|||
} catch (e) {
|
||||
console.error(`GetTravelWeather for ${city.Name} failed`);
|
||||
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() {
|
||||
// create the "long" canvas if necessary
|
||||
if (!this.longCanvas) {
|
||||
this.longCanvas = document.createElement('canvas');
|
||||
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;
|
||||
// get the element and populate
|
||||
const list = this.elem.querySelector('.travel-lines');
|
||||
list.innerHTML = '';
|
||||
|
||||
// set up variables
|
||||
const cities = this.data;
|
||||
|
||||
// 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, 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;
|
||||
const lines = cities.map((city) => {
|
||||
if (city.error) return false;
|
||||
const fillValues = {};
|
||||
|
||||
// city name
|
||||
draw.text(this.longContext, 'Star4000 Large Compressed', '24pt', '#FFFF00', 80, y, city.name, 2);
|
||||
fillValues.city = city;
|
||||
|
||||
// check for forecast data
|
||||
if (city.icon) {
|
||||
fillValues.city = city.name;
|
||||
// get temperatures and convert if necessary
|
||||
let { low, high } = city;
|
||||
|
||||
|
@ -122,25 +93,16 @@ class TravelForecast extends WeatherDisplay {
|
|||
const lowString = Math.round(low).toString();
|
||||
const highString = Math.round(high).toString();
|
||||
|
||||
const xLow = (500 - (lowString.length * 20));
|
||||
draw.text(this.longContext, 'Star4000 Large', '24pt', '#FFFF00', xLow, y, lowString, 2);
|
||||
fillValues.low = lowString;
|
||||
fillValues.high = highString;
|
||||
|
||||
const xHigh = (560 - (highString.length * 20));
|
||||
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,
|
||||
}));
|
||||
fillValues.icon = { type: 'img', src: city.icon };
|
||||
} else {
|
||||
draw.text(this.longContext, 'Star4000 Small', '24pt', '#FFFFFF', 400, y - 18, 'NO TRAVEL', 2);
|
||||
draw.text(this.longContext, 'Star4000 Small', '24pt', '#FFFFFF', 400, y, 'DATA AVAILABLE', 2);
|
||||
fillValues.error = 'NO TRAVEL DATA AVAILABLE';
|
||||
}
|
||||
}));
|
||||
return this.fillTemplate('travel-row', fillValues);
|
||||
}).filter((d) => d);
|
||||
list.append(...lines);
|
||||
}
|
||||
|
||||
async drawCanvas() {
|
||||
|
@ -151,18 +113,7 @@ class TravelForecast extends WeatherDisplay {
|
|||
// set up variables
|
||||
const cities = this.data;
|
||||
|
||||
// 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, '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.elem.querySelector('.header .title.dual .bottom').innerHTML = `For ${TravelForecast.getTravelCitiesDayName(cities)}`;
|
||||
|
||||
this.finishDraw();
|
||||
}
|
||||
|
@ -180,17 +131,14 @@ class TravelForecast extends WeatherDisplay {
|
|||
|
||||
// base count change callback
|
||||
baseCountChange(count) {
|
||||
// get a fresh canvas
|
||||
const longCanvas = this.getLongCanvas();
|
||||
|
||||
// calculate scroll offset and don't go past end
|
||||
let offsetY = Math.min(longCanvas.height - 289, (count - 150));
|
||||
let offsetY = Math.min(this.elem.querySelector('.travel-lines').getBoundingClientRect().height - 289, (count - 150));
|
||||
|
||||
// don't let offset go negative
|
||||
if (offsetY < 0) offsetY = 0;
|
||||
|
||||
// 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) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
// radar utilities
|
||||
|
||||
/* globals SuperGif */
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const utils = (() => {
|
||||
// ****************************** 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
|
||||
// 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
|
||||
const cachedImages = [];
|
||||
const preload = (src) => {
|
||||
if (cachedImages.includes(src)) return false;
|
||||
const img = new Image();
|
||||
img.scr = src;
|
||||
cachedImages.push(src);
|
||||
blob(src);
|
||||
// cachedImages.push(src);
|
||||
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 ***********************
|
||||
|
||||
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;
|
||||
|
||||
// ********************************* strings *********************************************
|
||||
const wordWrap = (_str, ...rest) => {
|
||||
// discuss at: https://locutus.io/php/wordwrap/
|
||||
// original by: Jonas Raoni Soares Silva (https://www.jsfromhell.com)
|
||||
// improved by: Nick Callen
|
||||
// improved by: Kevin van Zonneveld (https://kvz.io)
|
||||
// improved by: Sakimori
|
||||
// revised by: Jonas Raoni Soares Silva (https://www.jsfromhell.com)
|
||||
// bugfixed by: Michael Grier
|
||||
// bugfixed by: Feras ALHAEK
|
||||
// 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;
|
||||
const locationCleanup = (input) => {
|
||||
// regexes to run
|
||||
const regexes = [
|
||||
// "Chicago / West Chicago", removes before slash
|
||||
/^[A-Za-z ]+ \/ /,
|
||||
// "Chicago/Waukegan" removes before slash
|
||||
/^[A-Za-z ]+\//,
|
||||
// "Chicago, Chicago O'hare" removes before comma
|
||||
/^[A-Za-z ]+, /,
|
||||
];
|
||||
|
||||
let i;
|
||||
let j;
|
||||
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');
|
||||
// run all regexes
|
||||
return regexes.reduce((value, regex) => value.replace(regex, ''), input);
|
||||
};
|
||||
|
||||
// ********************************* cors ********************************************
|
||||
// rewrite some urls for local server
|
||||
const rewriteUrl = (_url) => {
|
||||
|
@ -255,9 +153,9 @@ const utils = (() => {
|
|||
// build a url, including the rewrite for cors if necessary
|
||||
let corsUrl = _url;
|
||||
if (params.cors === true) corsUrl = rewriteUrl(_url);
|
||||
const url = new URL(corsUrl);
|
||||
// match the security protocol
|
||||
url.protocol = window.location.protocol;
|
||||
const url = new URL(corsUrl, `${window.location.origin}/`);
|
||||
// match the security protocol when not on localhost
|
||||
url.protocol = window.location.hostname !== 'localhost' ? window.location.protocol : url.protocol;
|
||||
// add parameters if necessary
|
||||
if (params.data) {
|
||||
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 {
|
||||
elem: {
|
||||
forEach: elemForEach,
|
||||
},
|
||||
image: {
|
||||
load: loadImg,
|
||||
superGifAsync,
|
||||
preload,
|
||||
drawLocalCanvas,
|
||||
},
|
||||
weather: {
|
||||
getPoint,
|
||||
|
@ -318,7 +221,7 @@ const utils = (() => {
|
|||
wrap,
|
||||
},
|
||||
string: {
|
||||
wordWrap,
|
||||
locationCleanup,
|
||||
},
|
||||
cors: {
|
||||
rewriteUrl,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// base weather display class
|
||||
|
||||
/* globals navigation, utils, draw, UNITS, luxon, currentWeatherScroll */
|
||||
/* globals navigation, utils, luxon, currentWeatherScroll */
|
||||
|
||||
const STATUS = {
|
||||
loading: Symbol('loading'),
|
||||
|
@ -31,8 +31,8 @@ class WeatherDisplay {
|
|||
this.navBaseCount = 0;
|
||||
this.screenIndex = -1; // special starting condition
|
||||
|
||||
// create the canvas, also stores this.elemId
|
||||
this.createCanvas(elemId);
|
||||
// store elemId once
|
||||
this.storeElemId(elemId);
|
||||
|
||||
if (elemId !== 'progress') this.addCheckbox(defaultEnabled);
|
||||
if (this.enabled) {
|
||||
|
@ -41,6 +41,9 @@ class WeatherDisplay {
|
|||
this.setStatus(STATUS.disabled);
|
||||
}
|
||||
this.startNavCount();
|
||||
|
||||
// get any templates
|
||||
this.loadTemplates();
|
||||
}
|
||||
|
||||
addCheckbox(defaultEnabled = true) {
|
||||
|
@ -92,18 +95,10 @@ class WeatherDisplay {
|
|||
this.loadingStatus = state;
|
||||
}
|
||||
|
||||
createCanvas(elemId, width = 640, height = 480) {
|
||||
storeElemId(elemId) {
|
||||
// only create it once
|
||||
if (this.elemId) return;
|
||||
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
|
||||
|
@ -136,46 +131,27 @@ class WeatherDisplay {
|
|||
}
|
||||
|
||||
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
|
||||
if (this.screenIndex < 0) this.screenIndex = 0;
|
||||
}
|
||||
|
||||
finishDraw() {
|
||||
let OkToDrawCurrentConditions = true;
|
||||
let OkToDrawNoaaImage = true;
|
||||
let OkToDrawCurrentDateTime = true;
|
||||
let OkToDrawLogoImage = true;
|
||||
// let OkToDrawCustomScrollText = false;
|
||||
let bottom;
|
||||
|
||||
// visibility tests
|
||||
// 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') {
|
||||
OkToDrawCurrentConditions = false;
|
||||
OkToDrawNoaaImage = false;
|
||||
}
|
||||
if (this.elemId === 'radar') {
|
||||
OkToDrawCurrentConditions = false;
|
||||
OkToDrawCurrentDateTime = false;
|
||||
OkToDrawNoaaImage = false;
|
||||
// OkToDrawCustomScrollText = false;
|
||||
}
|
||||
if (this.elemId === 'hazards') {
|
||||
OkToDrawNoaaImage = false;
|
||||
bottom = true;
|
||||
OkToDrawLogoImage = false;
|
||||
}
|
||||
// draw functions
|
||||
if (OkToDrawCurrentDateTime) {
|
||||
|
@ -185,10 +161,8 @@ class WeatherDisplay {
|
|||
setInterval(() => this.drawCurrentDateTime(bottom), 100);
|
||||
}
|
||||
}
|
||||
if (OkToDrawLogoImage) this.drawLogoImage();
|
||||
if (OkToDrawNoaaImage) this.drawNoaaImage();
|
||||
if (OkToDrawCurrentConditions) {
|
||||
currentWeatherScroll.start(this.context);
|
||||
currentWeatherScroll.start();
|
||||
} else {
|
||||
// cause a reset if the progress screen is displayed
|
||||
currentWeatherScroll.stop(this.elemId === 'progress');
|
||||
|
@ -197,81 +171,27 @@ class WeatherDisplay {
|
|||
// if (OkToDrawCustomScrollText) DrawCustomScrollText(WeatherParameters, context);
|
||||
}
|
||||
|
||||
drawCurrentDateTime(bottom) {
|
||||
drawCurrentDateTime() {
|
||||
// only draw if canvas is active to conserve battery
|
||||
if (!this.isActive()) return;
|
||||
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.
|
||||
const now = DateTime.local();
|
||||
|
||||
// time = "11:35:08 PM";
|
||||
const time = now.toLocaleString(DateTime.TIME_WITH_SECONDS).padStart(11, ' ');
|
||||
|
||||
let x; let y;
|
||||
if (bottom) {
|
||||
x = 400;
|
||||
y = 402;
|
||||
} else {
|
||||
x = 410;
|
||||
y = 65;
|
||||
if (this.lastTime !== time) {
|
||||
utils.elem.forEach('.date-time.time', (elem) => { elem.innerHTML = time.toUpperCase(); });
|
||||
}
|
||||
if (navigation.units() === UNITS.metric) {
|
||||
x += 45;
|
||||
}
|
||||
|
||||
draw.text(this.context, font, size, color, x, y, time.toUpperCase(), shadow); // y += 20;
|
||||
this.lastTime = time;
|
||||
|
||||
const date = now.toFormat(' ccc LLL ') + now.day.toString().padStart(2, ' ');
|
||||
|
||||
if (bottom) {
|
||||
x = 55;
|
||||
y = 402;
|
||||
} else {
|
||||
x = 410;
|
||||
y = 85;
|
||||
if (this.lastDate !== date) {
|
||||
utils.elem.forEach('.date-time.date', (elem) => { elem.innerHTML = date.toUpperCase(); });
|
||||
}
|
||||
draw.text(this.context, font, size, color, x, y, date.toUpperCase(), shadow);
|
||||
}
|
||||
|
||||
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);
|
||||
this.lastDate = date;
|
||||
}
|
||||
|
||||
// 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.lastFrame) this.navPrev(navCmd);
|
||||
|
||||
// see if the canvas is already showing
|
||||
if (this.canvas.style.display === 'block') return false;
|
||||
this.startNavCount();
|
||||
|
||||
// show the canvas
|
||||
this.canvas.style.display = 'block';
|
||||
return false;
|
||||
this.elem.classList.add('show');
|
||||
}
|
||||
|
||||
hideCanvas() {
|
||||
this.resetNavBaseCount();
|
||||
|
||||
if (!this.canvas) return;
|
||||
this.canvas.style.display = 'none';
|
||||
this.elem.classList.remove('show');
|
||||
}
|
||||
|
||||
isActive() {
|
||||
return document.getElementById(`${this.elemId}Canvas`).offsetParent !== null;
|
||||
return this.elem.offsetHeight !== 0;
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
|
@ -452,7 +367,6 @@ class WeatherDisplay {
|
|||
clearInterval(this.navInterval);
|
||||
this.navInterval = undefined;
|
||||
}
|
||||
this.startNavCount();
|
||||
}
|
||||
|
||||
sendNavDisplayMessage(message) {
|
||||
|
@ -461,4 +375,44 @@ class WeatherDisplay {
|
|||
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>
|
||||
|
||||
<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" />
|
||||
<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+" />
|
||||
|
||||
<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" />
|
||||
|
||||
<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" />
|
||||
<% if (production) { %>
|
||||
<link rel="stylesheet" type="text/css" href="resources/ws.min.css?_=<%=production%>" />
|
||||
<script type="text/javascript" src="resources/data.min.js?_=<%=production%>"></script>
|
||||
<script type="text/javascript" src="resources/ws.min.js?_=<%=production%>"></script>
|
||||
<% } 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) { %>
|
||||
<link rel="stylesheet" type="text/css" href="resources/ws.min.css?_=<%=production%>" />
|
||||
<script type="text/javascript" src="resources/data.min.js?_=<%=production%>"></script>
|
||||
<script type="text/javascript" src="resources/ws.min.js?_=<%=production%>"></script>
|
||||
<% } else { %>
|
||||
<link rel="stylesheet" type="text/css" href="styles/index.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/vendor/jquery.touchswipe.min.js"></script>
|
||||
<script type="text/javascript" src="scripts/index.js"></script>
|
||||
<script type="text/javascript" src="scripts/data/states.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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<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 />
|
||||
<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"><%- version %> </div>
|
||||
<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">
|
||||
<%- version %>
|
||||
</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 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">
|
||||
<%- 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>
|
||||
|
||||
<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 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>
|
||||
|
||||
<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>
|
||||
<br />
|
||||
|
||||
<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>
|
||||
<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>
|
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": {
|
||||
"search.exclude": {
|
||||
"**/node_modules": true,
|
||||
"**/bower_components": true,
|
||||
"**/*.code-search": true,
|
||||
"dist/**": true,
|
||||
"**/*.css": true,
|
||||
"**/*.min.js": true,
|
||||
"**/bower_components": true,
|
||||
"**/node_modules": true,
|
||||
"**/vendor": true,
|
||||
"dist/**": true
|
||||
},
|
||||
"cSpell.enabled": true,
|
||||
"cSpell.words": [
|
||||
"'storm",
|
||||
"arcgis",
|
||||
"Battaglia",
|
||||
"devbridge",
|
||||
"gifs",
|
||||
"ltrim",
|
||||
"Noaa",
|
||||
"nosleep",
|
||||
"Pngs",
|
||||
"PRECIP",
|
||||
"rtrim",
|
||||
"T",
|
||||
"T'storm",
|
||||
"uscomp",
|
||||
"Visib",
|
||||
"arcgis",
|
||||
"devbridge",
|
||||
"ltrim",
|
||||
"nosleep",
|
||||
"rtrim",
|
||||
"uscomp"
|
||||
"Waukegan"
|
||||
],
|
||||
"cSpell.ignorePaths": [
|
||||
"**/package-lock.json",
|
||||
|
@ -40,5 +44,11 @@
|
|||
"**/twc3.js",
|
||||
],
|
||||
"editor.tabSize": 2,
|
||||
"emmet.includeLanguages": {
|
||||
"ejs": "html",
|
||||
},
|
||||
"[html]": {
|
||||
"editor.defaultFormatter": "j69.ejs-beautify"
|
||||
},
|
||||
},
|
||||
}
|
Loading…
Reference in a new issue