reorganize for build system
2
.eslintignore
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
*.min.*
|
||||||
|
server/scripts/vendor/*
|
50
.eslintrc.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
module.exports = {
|
||||||
|
'env': {
|
||||||
|
'browser': true,
|
||||||
|
'commonjs': true,
|
||||||
|
'es6': true,
|
||||||
|
'node': true,
|
||||||
|
'jquery': true,
|
||||||
|
},
|
||||||
|
'extends': 'eslint:recommended',
|
||||||
|
'globals': {
|
||||||
|
'Atomics': 'readonly',
|
||||||
|
'SharedArrayBuffer': 'readonly'
|
||||||
|
},
|
||||||
|
'parserOptions': {
|
||||||
|
'ecmaVersion': 2018
|
||||||
|
},
|
||||||
|
'rules': {
|
||||||
|
'indent': [
|
||||||
|
'error',
|
||||||
|
'tab'
|
||||||
|
],
|
||||||
|
'linebreak-style': [
|
||||||
|
'error',
|
||||||
|
'unix'
|
||||||
|
],
|
||||||
|
'quotes': [
|
||||||
|
'error',
|
||||||
|
'single'
|
||||||
|
],
|
||||||
|
'semi': [
|
||||||
|
'error',
|
||||||
|
'always'
|
||||||
|
],
|
||||||
|
'no-prototype-builtins': 0,
|
||||||
|
'comma-dangle': ['error', 'always-multiline'],
|
||||||
|
'block-scoped-var': ['error'],
|
||||||
|
'default-case': ['error'],
|
||||||
|
'default-param-last': ['error'],
|
||||||
|
'dot-location': ['error', 'property'],
|
||||||
|
'eqeqeq': ['error'],
|
||||||
|
'no-eval': ['error'],
|
||||||
|
'no-eq-null': ['error'],
|
||||||
|
'no-floating-decimal': ['error'],
|
||||||
|
'no-trailing-spaces': ['error'],
|
||||||
|
'brace-style': [2, '1tbs', { 'allowSingleLine': true }],
|
||||||
|
},
|
||||||
|
'ignorePatterns': [
|
||||||
|
'*.min.js'
|
||||||
|
],
|
||||||
|
};
|
5
.gitattributes
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
*.js text eol=lf
|
||||||
|
*.ejs text eol=lf
|
||||||
|
*.html text eol=lf
|
||||||
|
*.json text eol=lf
|
||||||
|
*.php text eol=lf
|
14
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Please do not report issues with api.weather.gov being down. It's a new service and not considered fully operational yet. Before reporting an issue or requesting a feature please consider that this is not intended to be a perfect recreation of the WeatherStar 4000, it's a best effort that fits within what's available from the API and within a web browser.
|
||||||
|
|
||||||
|
Please include:
|
||||||
|
* Web browser and OS
|
||||||
|
* Location for which you are viewing a forecast
|
10
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Before requesting a feature please consider that this is not intended to be a perfect recreation of the WeatherStar 4000, it's a best effort that fits within what's available from the API and within a web browser.
|
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
node_modules
|
29
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Frontend",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "pwa-chrome",
|
||||||
|
"url": "http://localhost:8080",
|
||||||
|
"webRoot": "${workspaceFolder}/server",
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**",
|
||||||
|
"**/*.min.js",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Server",
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**",
|
||||||
|
],
|
||||||
|
"program": "${workspaceFolder}/index.js",
|
||||||
|
"outputCapture": "std",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016-2017 Michael Battaglia
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
67
README.md
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
# WeatherStar 4000+
|
||||||
|
|
||||||
|
A live version of this project is available at https://weatherstar.netbymatt.com
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
This project aims to bring back the feel of the 90's with a weather forecast that has the look and feel of The Weather Channel at that time but available in a modern way. This is by no means intended to be a perfect emulation of the WeatherStar 4000, the hardware that produced those wonderful blue and orange graphics you saw during the local forecast on The Weather Channel. If you would like a much more accurate project please see the [WS4000 Simulator](http://www.taiganet.com/). Instead, this project intends to create a simple to use interface with minimal configuration fuss. Some changes have been made to the screens available because either more or less forecast information is available today than was in the 90's. Most of these changes are captured in sections below.
|
||||||
|
|
||||||
|
## Acknowledgements
|
||||||
|
|
||||||
|
This project is based on the work of [Mike Battaglia](https://github.com/vbguyny/ws4kp). It was forked from his work in August 2020.
|
||||||
|
|
||||||
|
* Mike Battaglia For the original project and all of the code which draws the weather displays. This code remains largely intact and was a huge amount of work to get exactly right.
|
||||||
|
* The team at [TWCClassics](https://twcclassics.com/) for several resources.
|
||||||
|
* A [font](https://twcclassics.com/downloads.html) set used on the original WeatherStar 4000
|
||||||
|
* [Icon](https://twcclassics.com/downloads.html) sets
|
||||||
|
* Countless photos and videos of WeatherStar 4000 forecasts used as references.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
* Make use of the new API available at https://api.weather.gov ([documentation](https://www.weather.gov/documentation/services-web-api)). This caused the removal of some of the original WeatherStar 4000 displays.
|
||||||
|
* Changed code to make extensive use of ES6 functionality including:
|
||||||
|
* Arrow functions
|
||||||
|
* Promises
|
||||||
|
* Async/await and parallel loading of all forecast resources
|
||||||
|
* Classes
|
||||||
|
* Common code base for each display through use of classes
|
||||||
|
* Separation between weather display code and user interface
|
||||||
|
* Use of a modern date parsing library [luxon](https://moment.github.io/luxon/)
|
||||||
|
* Attempt to remove the need for a local server to bypass [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) issues with the various APIs used. This is almost workable but there are still some minor CORS issues with https://api.weather.gov.
|
||||||
|
|
||||||
|
## What's different
|
||||||
|
|
||||||
|
I've made several changes to this Weather Star 4000 simulation compared to the original hardware unit and the code that this was forked from.
|
||||||
|
|
||||||
|
* Narration was removed. In the original code narration made use of the computer's local text-to-speech engine which didn't sound great.
|
||||||
|
* Music was removed. I don't want to deal with copyright issues and hosting MP3s. If you're looking for the music that played during forecasts please visit [TWCClassics](https://twcclassics.com/audio/).
|
||||||
|
* Marine forecast (tides) is not available as it is not part of the new API.
|
||||||
|
* The nearby cities displayed on screens such as "Latest Observations" and "Regional Forecast" are likely not the same as they were in the 90's. The weather monitoring equipment at these stations move over time for one reason or another, and coming up with a simple formulaic way of finding nearby stations is sufficient to give the same look-and-feel as the original.
|
||||||
|
* The "Local Forecast" and "Extended Forecast" provide several additional days of information compared to the original format in the 90's.
|
||||||
|
* "Flavors" are not present in this simulation. Flavors refer to the order of the weather information that was shown on the original units.
|
||||||
|
|
||||||
|
## Wish list
|
||||||
|
|
||||||
|
As time allows I will be working on the following enhancements.
|
||||||
|
|
||||||
|
* On/off selection of the displays that are used when in "play" mode.
|
||||||
|
* Better error reporting when api.weather.gov is down (happens more often than you would think)
|
||||||
|
|
||||||
|
And the following technical fixes.
|
||||||
|
|
||||||
|
* Caching of the animated gifs, specifically after they are decompressed
|
||||||
|
* Proper settings for static resource caching
|
||||||
|
* Build system integration to reduce the number of scripts that need to be loaded
|
||||||
|
|
||||||
|
## Issue reporting and feature requests
|
||||||
|
|
||||||
|
Please do not report issues with api.weather.gov being down. It's a new service and not considered fully operational yet. Before reporting an issue or requesting a feature please consider that this is not intended to be a perfect recreation of the WeatherStar 4000, it's a best effort that fits within what's available from the API and within a web browser.
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
This web site should NOT be used in life threatening weather situations, or be relied on to inform the public of such situations. The Internet is an unreliable network subject to server and network outages and by nature is not suitable for such mission critical use. If you require such access to NWS data, please consider one of their subscription services. The authors of this web site shall not be held liable in the event of injury, death or property damage that occur as a result of disregarding this warning.
|
||||||
|
|
||||||
|
The WeatherSTAR 4000 unit and technology is owned by The Weather Channel. This web site is a free, non-profit work by fans. All of the back ground graphics of this web site were created from scratch. The icons were created by Charles Abel and Nick Smith (http://twcclassics.com/downloads/icons.html) as well as by Malek Masoud. The fonts were originally created by Nick Smith (http://twcclassics.com/downloads/fonts.html).
|
||||||
|
|
48
cors/index.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
// pass through api requests
|
||||||
|
|
||||||
|
// http(s) modules
|
||||||
|
const https = require('https');
|
||||||
|
|
||||||
|
// url parsing
|
||||||
|
const queryString = require('querystring');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// return an express router
|
||||||
|
module.exports = (req, res) => {
|
||||||
|
if (!req.query.u) res.status(404);
|
||||||
|
|
||||||
|
// add out-going headers
|
||||||
|
const headers = {};
|
||||||
|
headers['user-agent'] = '(WeatherStar 4000+, ws4000@netbymatt.com)';
|
||||||
|
headers['accept'] = req.headers.accept;
|
||||||
|
|
||||||
|
// get query paramaters if the exist
|
||||||
|
const queryParams = Object.keys(req.query).reduce((acc, key) => {
|
||||||
|
// skip the paramater 'u'
|
||||||
|
if (key === 'u') return acc;
|
||||||
|
// add the paramter to the resulting object
|
||||||
|
acc[key] = req.query[key];
|
||||||
|
return acc;
|
||||||
|
},{});
|
||||||
|
let query = queryString.encode(queryParams);
|
||||||
|
if (query.length > 0) query = '?' + query;
|
||||||
|
|
||||||
|
// get the page
|
||||||
|
https.get('https://api.weather.gov' + req.path + query, {
|
||||||
|
headers,
|
||||||
|
}, getRes => {
|
||||||
|
// pull some info
|
||||||
|
const {statusCode} = getRes;
|
||||||
|
// pass the status code through
|
||||||
|
res.status(statusCode);
|
||||||
|
|
||||||
|
// set headers
|
||||||
|
res.header('content-type', getRes.headers['content-type']);
|
||||||
|
// pipe to response
|
||||||
|
getRes.pipe(res);
|
||||||
|
|
||||||
|
}).on('error', e=>{
|
||||||
|
console.error(e);
|
||||||
|
});
|
||||||
|
};
|
1
dist/index.html
vendored
Normal file
12
dist/manifest.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"name": "WeatherStar 4000+",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/images/Logo192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "images/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
1
dist/resources/ws.min.css
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
@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:'Star4000 Large Compressed Numbers';src:url('../fonts/Star4000 Large Compressed Numbers.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')}body{font-family:Star4000}button,input{font-family:Star4000}#imgGetGps{height:13px;vertical-align:middle}#txtAddress{width:490px;font-size:16pt}#btnClearQuery,#btnGetGps,#btnGetLatLng{font-size:16pt}.autocomplete-suggestions{background-color:#fff;border:1px solid #000}.autocomplete-suggestion{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:16pt}.autocomplete-selected{background-color:#00f;color:#fff}#divTwc{display:block;background-color:#000;color:#fff;width:640px}#divTwcMiddle{display:flex}#divTwcLeft{visibility:hidden;text-align:right;display:flex;flex-direction:column;vertical-align:middle}#divTwcLeft>div{flex:1;padding-right:12px;display:flex;flex-direction:column;justify-content:center}#divTwcRight{visibility:hidden;text-align:left;display:flex;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:#000;color:#fff;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{display:flex;flex-direction:row;background-color:#000;color:#fff;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}#iframeTwc{width:640px;height:480px;overflow:hidden;border:0}.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')}body{font-family:Star4000;margin:0;overflow:hidden}input{font-family:Star4000}jsgif{display:none}.fontPreload{visibility:hidden;position:absolute}#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'}.HideCursor{cursor:none!important}#container{position:absolute}#container canvas{position:absolute}
|
2
dist/resources/ws.min.js
vendored
Normal file
0
dist/robots.txt
vendored
Normal file
66
dist/scripts/TimerWorker.js
vendored
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
var _TimerInfos = [];
|
||||||
|
|
||||||
|
var TimerElasped = function (Id)
|
||||||
|
{
|
||||||
|
var TimerInfo = _TimerInfos[Id];
|
||||||
|
|
||||||
|
if (!TimerInfo)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.postMessage({
|
||||||
|
Action: "ELASPED",
|
||||||
|
Id: TimerInfo.Id,
|
||||||
|
RunOnce: TimerInfo.RunOnce,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.onmessage = function (e)
|
||||||
|
{
|
||||||
|
var Message = e.data;
|
||||||
|
|
||||||
|
switch (Message.Action)
|
||||||
|
{
|
||||||
|
case "START":
|
||||||
|
var TimerInfo = {
|
||||||
|
Id: Message.Id,
|
||||||
|
RunOnce: Message.RunOnce,
|
||||||
|
TimeOut: Message.TimeOut,
|
||||||
|
};
|
||||||
|
|
||||||
|
_TimerInfos[Message.Id] = TimerInfo;
|
||||||
|
|
||||||
|
if (Message.RunOnce == true)
|
||||||
|
{
|
||||||
|
TimerInfo.TimerId = setTimeout(TimerElasped, Message.TimeOut, Message.Id);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TimerInfo.TimerId = setInterval(TimerElasped, Message.TimeOut, Message.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "STOP":
|
||||||
|
var TimerInfo = _TimerInfos[Message.Id];
|
||||||
|
|
||||||
|
if (!TimerInfo)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TimerInfo.RunOnce == true)
|
||||||
|
{
|
||||||
|
clearTimeout(TimerInfo.TimerId);
|
||||||
|
delete _TimerInfos[Message.Id];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
clearInterval(TimerInfo.TimerId);
|
||||||
|
delete _TimerInfos[Message.Id];
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
60
dist/scripts/data/states.js
vendored
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const states = (() => {
|
||||||
|
const stateList = {
|
||||||
|
'Arizona': 'AZ',
|
||||||
|
'Alabama': 'AL',
|
||||||
|
'Alaska': 'AK',
|
||||||
|
'Arkansas': 'AR',
|
||||||
|
'California': 'CA',
|
||||||
|
'Colorado': 'CO',
|
||||||
|
'Connecticut': 'CT',
|
||||||
|
'Delaware': 'DE',
|
||||||
|
'Florida': 'FL',
|
||||||
|
'Georgia': 'GA',
|
||||||
|
'Hawaii': 'HI',
|
||||||
|
'Idaho': 'ID',
|
||||||
|
'Illinois': 'IL',
|
||||||
|
'Indiana': 'IN',
|
||||||
|
'Iowa': 'IA',
|
||||||
|
'Kansas': 'KS',
|
||||||
|
'Kentucky': 'KY',
|
||||||
|
'Louisiana': 'LA',
|
||||||
|
'Maine': 'ME',
|
||||||
|
'Maryland': 'MD',
|
||||||
|
'Massachusetts': 'MA',
|
||||||
|
'Michigan': 'MI',
|
||||||
|
'Minnesota': 'MN',
|
||||||
|
'Mississippi': 'MS',
|
||||||
|
'Missouri': 'MO',
|
||||||
|
'Montana': 'MT',
|
||||||
|
'Nebraska': 'NE',
|
||||||
|
'Nevada': 'NV',
|
||||||
|
'New Hampshire': 'NH',
|
||||||
|
'New Jersey': 'NJ',
|
||||||
|
'New Mexico': 'NM',
|
||||||
|
'New York': 'NY',
|
||||||
|
'North Carolina': 'NC',
|
||||||
|
'North Dakota': 'ND',
|
||||||
|
'Ohio': 'OH',
|
||||||
|
'Oklahoma': 'OK',
|
||||||
|
'Oregon': 'OR',
|
||||||
|
'Pennsylvania': 'PA',
|
||||||
|
'Rhode Island': 'RI',
|
||||||
|
'South Carolina': 'SC',
|
||||||
|
'South Dakota': 'SD',
|
||||||
|
'Tennessee': 'TN',
|
||||||
|
'Texas': 'TX',
|
||||||
|
'Utah': 'UT',
|
||||||
|
'Vermont': 'VT',
|
||||||
|
'Virginia': 'VA',
|
||||||
|
'Washington': 'WA',
|
||||||
|
'West Virginia': 'WV',
|
||||||
|
'Wisconsin': 'WI',
|
||||||
|
'Wyoming': 'WY',
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
getTwoDigitCode: (stateFullName) => stateList[stateFullName],
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
956
dist/scripts/index.js
vendored
Normal file
|
@ -0,0 +1,956 @@
|
||||||
|
/* globals NoSleep, states */
|
||||||
|
let frmGetLatLng;
|
||||||
|
let txtAddress;
|
||||||
|
let btnClearQuery;
|
||||||
|
let btnGetGps;
|
||||||
|
|
||||||
|
let divTwc;
|
||||||
|
let divTwcTop;
|
||||||
|
let divTwcMiddle;
|
||||||
|
let divTwcBottom;
|
||||||
|
let divTwcLeft;
|
||||||
|
let divTwcRight;
|
||||||
|
let divTwcNavContainer;
|
||||||
|
let iframeTwc;
|
||||||
|
|
||||||
|
let spanLastRefresh;
|
||||||
|
let chkAutoRefresh;
|
||||||
|
let spanRefreshCountDown;
|
||||||
|
|
||||||
|
let spanCity;
|
||||||
|
let spanState;
|
||||||
|
let spanStationId;
|
||||||
|
let spanRadarId;
|
||||||
|
let spanZoneId;
|
||||||
|
|
||||||
|
let frmScrollText;
|
||||||
|
let chkScrollText;
|
||||||
|
let txtScrollText;
|
||||||
|
|
||||||
|
let _AutoSelectQuery = false;
|
||||||
|
let _TwcDataUrl = '';
|
||||||
|
let _IsPlaying = false;
|
||||||
|
|
||||||
|
let _NoSleep = new NoSleep();
|
||||||
|
|
||||||
|
let _LastUpdate = null;
|
||||||
|
let _AutoRefreshIntervalId = null;
|
||||||
|
let _AutoRefreshIntervalMs = 500;
|
||||||
|
let _AutoRefreshTotalIntervalMs = 600000; // 10 min.
|
||||||
|
let _AutoRefreshCountMs = 0;
|
||||||
|
|
||||||
|
let _FullScreenOverride = false;
|
||||||
|
|
||||||
|
let _WindowHeight = 0;
|
||||||
|
let _WindowWidth = 0;
|
||||||
|
|
||||||
|
let latLon;
|
||||||
|
|
||||||
|
let _canvasIds = [
|
||||||
|
'canvasProgress',
|
||||||
|
'canvasCurrentWeather',
|
||||||
|
'canvasLatestObservations',
|
||||||
|
'canvasTravelForecast',
|
||||||
|
'canvasRegionalForecast1',
|
||||||
|
'canvasRegionalForecast2',
|
||||||
|
'canvasRegionalObservations',
|
||||||
|
'canvasLocalForecast',
|
||||||
|
'canvasExtendedForecast1',
|
||||||
|
'canvasExtendedForecast2',
|
||||||
|
'canvasAlmanac',
|
||||||
|
'canvasAlmanacTides',
|
||||||
|
'canvasOutlook',
|
||||||
|
'canvasMarineForecast',
|
||||||
|
'canvasAirQuality',
|
||||||
|
'canvasLocalRadar',
|
||||||
|
'canvasHazards',
|
||||||
|
];
|
||||||
|
|
||||||
|
const FullScreenResize = () => {
|
||||||
|
const iframeDoc = $(iframeTwc[0].contentWindow.document);
|
||||||
|
const WindowWidth = $(window).width();
|
||||||
|
const WindowHeight = $(window).height();
|
||||||
|
const inFullScreen = InFullScreen();
|
||||||
|
let NewWidth;
|
||||||
|
let NewHeight;
|
||||||
|
let IFrameWidth;
|
||||||
|
let IFrameHeight;
|
||||||
|
let LeftWidth;
|
||||||
|
let RightWidth;
|
||||||
|
let TopHeight;
|
||||||
|
let BottomHeight;
|
||||||
|
let Offset;
|
||||||
|
|
||||||
|
if (inFullScreen) {
|
||||||
|
if ((WindowWidth / WindowHeight) >= 1.583333333333333) {
|
||||||
|
NewHeight = WindowHeight + 'px';
|
||||||
|
NewWidth = '';
|
||||||
|
divTwcTop.hide();
|
||||||
|
divTwcBottom.hide();
|
||||||
|
divTwcLeft.show();
|
||||||
|
divTwcRight.show();
|
||||||
|
|
||||||
|
divTwcMiddle.attr('style', 'width:100%; height:100%; border:none; margin:0; padding:0; overflow:hidden; z-index:999999;');
|
||||||
|
|
||||||
|
LeftWidth = ((WindowWidth - (WindowHeight * 1.33333333333333333333)) / 2);
|
||||||
|
if (LeftWidth < 60) {
|
||||||
|
LeftWidth = 60;
|
||||||
|
}
|
||||||
|
divTwcLeft.attr('style', 'width:' + LeftWidth + 'px; height:100%; border:none; margin:0; padding:0; overflow:hidden; z-index:999999;');
|
||||||
|
divTwcLeft.css('visibility', 'visible');
|
||||||
|
|
||||||
|
RightWidth = ((WindowWidth - (WindowHeight * 1.33333333333333333333)) / 2);
|
||||||
|
if (RightWidth < 60) {
|
||||||
|
RightWidth = 60;
|
||||||
|
}
|
||||||
|
divTwcRight.attr('style', 'width:' + RightWidth + 'px; height:100%; border:none; margin:0; padding:0; overflow:hidden; z-index:999999;');
|
||||||
|
divTwcRight.css('visibility', 'visible');
|
||||||
|
|
||||||
|
IFrameWidth = WindowWidth - LeftWidth - RightWidth;
|
||||||
|
NewWidth = IFrameWidth + 'px';
|
||||||
|
iframeTwc.attr('style', 'width:' + IFrameWidth + 'px; height:100%; border:none; margin:0; padding:0; overflow:hidden; z-index:999999;');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
NewHeight = '';
|
||||||
|
NewWidth = WindowWidth + 'px';
|
||||||
|
divTwcTop.show();
|
||||||
|
divTwcBottom.show();
|
||||||
|
divTwcLeft.hide();
|
||||||
|
divTwcRight.hide();
|
||||||
|
Offset = 0;
|
||||||
|
|
||||||
|
TopHeight = ((WindowHeight - ((WindowWidth - Offset) * 0.75)) / 2);
|
||||||
|
if (TopHeight < 0) {
|
||||||
|
TopHeight = 0;
|
||||||
|
}
|
||||||
|
divTwcTop.attr('style', 'width:100%; height:' + TopHeight + 'px; border:none; margin:0; padding:0; overflow:hidden; z-index:999999;');
|
||||||
|
|
||||||
|
BottomHeight = ((WindowHeight - ((WindowWidth - Offset) * 0.75)) / 2);
|
||||||
|
if (BottomHeight < 30) {
|
||||||
|
BottomHeight = 30;
|
||||||
|
}
|
||||||
|
divTwcBottom.attr('style', 'width:100%; height:' + BottomHeight + 'px; border:none; margin:0; padding:0; overflow:hidden; z-index:999999;');
|
||||||
|
divTwcBottom.css('visibility', 'visible');
|
||||||
|
|
||||||
|
IFrameHeight = WindowHeight - TopHeight - BottomHeight;
|
||||||
|
NewHeight = IFrameHeight + 'px';
|
||||||
|
iframeTwc.attr('style', 'width:100%; height:' + IFrameHeight + 'px; border:none; margin:0; padding:0; overflow:hidden; z-index:999999;');
|
||||||
|
divTwcMiddle.attr('style', 'width:100%; height:' + IFrameHeight + 'px; border:none; margin:0; padding:0; overflow:hidden; z-index:999999;');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inFullScreen) {
|
||||||
|
NewHeight = '';
|
||||||
|
NewWidth = '';
|
||||||
|
divTwcTop.hide();
|
||||||
|
divTwcBottom.hide();
|
||||||
|
divTwcLeft.hide();
|
||||||
|
divTwcRight.hide();
|
||||||
|
|
||||||
|
divTwc.attr('style', '');
|
||||||
|
divTwcMiddle.attr('style', '');
|
||||||
|
iframeTwc.attr('style', '');
|
||||||
|
|
||||||
|
$(window).off('resize', FullScreenResize);
|
||||||
|
}
|
||||||
|
|
||||||
|
$(_canvasIds).each(function () {
|
||||||
|
const canvas = iframeDoc.find('#' + this.toString());
|
||||||
|
canvas.css('width', NewWidth);
|
||||||
|
canvas.css('height', NewHeight);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (inFullScreen) {
|
||||||
|
$('body').css('overflow', 'hidden');
|
||||||
|
$('.ToggleFullScreen').val('Exit Full Screen');
|
||||||
|
|
||||||
|
if (!GetFullScreenElement()) {
|
||||||
|
EnterFullScreen();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$('body').css('overflow', '');
|
||||||
|
$('.ToggleFullScreen').val('Full Screen');
|
||||||
|
}
|
||||||
|
|
||||||
|
divTwcNavContainer.show();
|
||||||
|
};
|
||||||
|
|
||||||
|
const _lockOrientation = screen.lockOrientation || screen.mozLockOrientation || screen.msLockOrientation;
|
||||||
|
const _unlockOrientation = screen.unlockOrientation || screen.mozUnlockOrientation || screen.msUnlockOrientation || (screen.orientation && screen.orientation.unlock);
|
||||||
|
|
||||||
|
const OnFullScreen = () => {
|
||||||
|
if (InFullScreen()) {
|
||||||
|
divTwc.attr('style', 'position:fixed; top:0px; left:0px; bottom:0px; right:0px; width:100%; height:100%; border:none; margin:0; padding:0; overflow:hidden; z-index:999999;');
|
||||||
|
FullScreenResize();
|
||||||
|
|
||||||
|
$(window).on('resize', FullScreenResize);
|
||||||
|
//FullScreenResize();
|
||||||
|
|
||||||
|
if (_lockOrientation) try { _lockOrientation('landscape-primary'); } catch (ex) { console.log('Unable to lock screen orientation.'); }
|
||||||
|
} else {
|
||||||
|
divTwc.attr('style', '');
|
||||||
|
divTwcMiddle.attr('style', '');
|
||||||
|
iframeTwc.attr('style', '');
|
||||||
|
|
||||||
|
$(window).off('resize', FullScreenResize);
|
||||||
|
FullScreenResize();
|
||||||
|
|
||||||
|
if (_unlockOrientation) try { _unlockOrientation(); } catch (ex) { console.log('Unable to unlock screen orientation.'); }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const InFullScreen = () => ((_FullScreenOverride) || (GetFullScreenElement()) || (window.innerHeight === screen.height) || (window.innerHeight === (screen.height - 1)));
|
||||||
|
|
||||||
|
const GetFullScreenElement = () => {
|
||||||
|
if (_FullScreenOverride) return document.body;
|
||||||
|
return (document.fullScreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement);
|
||||||
|
};
|
||||||
|
|
||||||
|
const btnFullScreen_click = () => {
|
||||||
|
|
||||||
|
if (!InFullScreen()) {
|
||||||
|
EnterFullScreen();
|
||||||
|
} else {
|
||||||
|
ExitFullscreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_IsPlaying) {
|
||||||
|
_NoSleep.enable();
|
||||||
|
} else {
|
||||||
|
_NoSleep.disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateFullScreenNavigate();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const EnterFullScreen = () => {
|
||||||
|
const element = document.body;
|
||||||
|
|
||||||
|
// Supports most browsers and their versions.
|
||||||
|
const requestMethod = element.requestFullScreen || element.webkitRequestFullScreen || element.mozRequestFullScreen || element.msRequestFullscreen;
|
||||||
|
|
||||||
|
if (requestMethod) {
|
||||||
|
// Native full screen.
|
||||||
|
requestMethod.call(element, { navigationUI: 'hide' }); // https://bugs.chromium.org/p/chromium/issues/detail?id=933436#c7
|
||||||
|
} else {
|
||||||
|
// iOS doesn't support FullScreen API.
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
_FullScreenOverride = true;
|
||||||
|
$(window).resize();
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateFullScreenNavigate();
|
||||||
|
};
|
||||||
|
|
||||||
|
const ExitFullscreen = () => {
|
||||||
|
// exit full-screen
|
||||||
|
|
||||||
|
if (_FullScreenOverride) {
|
||||||
|
_FullScreenOverride = false;
|
||||||
|
$(window).resize();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.exitFullscreen) {
|
||||||
|
// Chrome 71 broke this if the user pressed F11 to enter full screen mode.
|
||||||
|
document.exitFullscreen();
|
||||||
|
} else if (document.webkitExitFullscreen) {
|
||||||
|
document.webkitExitFullscreen();
|
||||||
|
} else if (document.mozCancelFullScreen) {
|
||||||
|
document.mozCancelFullScreen();
|
||||||
|
} else if (document.msExitFullscreen) {
|
||||||
|
document.msExitFullscreen();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const btnNavigateMenu_click = () => {
|
||||||
|
postMessage('navButton', 'menu');
|
||||||
|
UpdateFullScreenNavigate();
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const LoadTwcData = () => {
|
||||||
|
txtAddress.blur();
|
||||||
|
StopAutoRefreshTimer();
|
||||||
|
_LastUpdate = null;
|
||||||
|
AssignLastUpdate();
|
||||||
|
|
||||||
|
postMessage('latLon', latLon);
|
||||||
|
|
||||||
|
iframeTwc.off('load');
|
||||||
|
FullScreenResize();
|
||||||
|
|
||||||
|
if (chkScrollText.is(':checked')) {
|
||||||
|
postMessage('assignScrollText', txtScrollText.val());
|
||||||
|
}
|
||||||
|
|
||||||
|
postMessage('units', $('input[type=\'radio\'][name=\'radUnits\']:checked').val());
|
||||||
|
|
||||||
|
|
||||||
|
if (_IsPlaying) postMessage('navButton', 'playToggle');
|
||||||
|
|
||||||
|
$(iframeTwc[0].contentWindow.document).on('mousemove', document_mousemove);
|
||||||
|
$(iframeTwc[0].contentWindow.document).on('mousedown', document_mousemove);
|
||||||
|
$(iframeTwc[0].contentWindow.document).on('keydown', document_keydown);
|
||||||
|
|
||||||
|
const SwipeCallBack = (event, direction) => {
|
||||||
|
switch (direction) {
|
||||||
|
case 'left':
|
||||||
|
btnNavigateNext_click();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'right':
|
||||||
|
default:
|
||||||
|
btnNavigatePrevious_click();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$(iframeTwc[0].contentWindow.document).swipe({
|
||||||
|
//Generic swipe handler for all directions
|
||||||
|
swipeRight: SwipeCallBack,
|
||||||
|
swipeLeft: SwipeCallBack,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const AssignLastUpdate = () => {
|
||||||
|
let LastUpdate = '(None)';
|
||||||
|
|
||||||
|
if (_LastUpdate) {
|
||||||
|
switch ($('input[type=\'radio\'][name=\'radUnits\']:checked').val()) {
|
||||||
|
case 'ENGLISH':
|
||||||
|
LastUpdate = _LastUpdate.toLocaleString('en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', timeZoneName: 'short' });
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LastUpdate = _LastUpdate.toLocaleString('en-GB', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', timeZoneName: 'short' });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spanLastRefresh.html(LastUpdate);
|
||||||
|
|
||||||
|
if (_LastUpdate && chkAutoRefresh.is(':checked')) StartAutoRefreshTimer();
|
||||||
|
};
|
||||||
|
|
||||||
|
const btnNavigateRefresh_click = () => {
|
||||||
|
LoadTwcData(_TwcDataUrl);
|
||||||
|
UpdateFullScreenNavigate();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const btnNavigateNext_click = () => {
|
||||||
|
postMessage('navButton', 'next');
|
||||||
|
UpdateFullScreenNavigate();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const btnNavigatePrevious_click = () => {
|
||||||
|
postMessage('navButton', 'previous');
|
||||||
|
UpdateFullScreenNavigate();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const window_resize = () => {
|
||||||
|
const $window = $(window);
|
||||||
|
|
||||||
|
if ($window.height() === _WindowHeight || $window.width() === _WindowWidth) return;
|
||||||
|
|
||||||
|
_WindowHeight = $window.height();
|
||||||
|
_WindowWidth = $window.width();
|
||||||
|
|
||||||
|
try {
|
||||||
|
postMessage('navButton', 'reset');
|
||||||
|
} catch (ex) {
|
||||||
|
console.log(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateFullScreenNavigate();
|
||||||
|
};
|
||||||
|
|
||||||
|
let _NavigateFadeIntervalId = null;
|
||||||
|
|
||||||
|
const UpdateFullScreenNavigate = () => {
|
||||||
|
$(document.activeElement).blur();
|
||||||
|
|
||||||
|
$('body').removeClass('HideCursor');
|
||||||
|
$(iframeTwc[0].contentWindow.document).find('body').removeClass('HideCursor');
|
||||||
|
divTwcLeft.fadeIn2();
|
||||||
|
divTwcRight.fadeIn2();
|
||||||
|
divTwcBottom.fadeIn2();
|
||||||
|
|
||||||
|
if (_NavigateFadeIntervalId) {
|
||||||
|
window.clearTimeout(_NavigateFadeIntervalId);
|
||||||
|
_NavigateFadeIntervalId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_NavigateFadeIntervalId = window.setTimeout(() => {
|
||||||
|
//console.log("window_mousemove: TimeOut");
|
||||||
|
if (InFullScreen()) {
|
||||||
|
$('body').addClass('HideCursor');
|
||||||
|
$(iframeTwc[0].contentWindow.document).find('body').addClass('HideCursor');
|
||||||
|
|
||||||
|
divTwcLeft.fadeOut2();
|
||||||
|
divTwcRight.fadeOut2();
|
||||||
|
divTwcBottom.fadeOut2();
|
||||||
|
}
|
||||||
|
|
||||||
|
}, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const document_mousemove = (e) => {
|
||||||
|
if (InFullScreen() && (e.originalEvent.movementX === 0 && e.originalEvent.movementY === 0 && e.originalEvent.buttons === 0)) return;
|
||||||
|
UpdateFullScreenNavigate();
|
||||||
|
};
|
||||||
|
|
||||||
|
const document_keydown = (e) => {
|
||||||
|
|
||||||
|
const code = (e.keyCode || e.which);
|
||||||
|
|
||||||
|
if (InFullScreen() || document.activeElement === document.body) {
|
||||||
|
switch (code) {
|
||||||
|
case 32: // Space
|
||||||
|
btnNavigatePlay_click();
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case 39: // Right Arrow
|
||||||
|
case 34: // Page Down
|
||||||
|
btnNavigateNext_click();
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case 37: // Left Arrow
|
||||||
|
case 33: // Page Up
|
||||||
|
btnNavigatePrevious_click();
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case 36: // Home
|
||||||
|
btnNavigateMenu_click();
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case 48: // Restart
|
||||||
|
btnNavigateRefresh_click();
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case 70: // F
|
||||||
|
btnFullScreen_click();
|
||||||
|
return false;
|
||||||
|
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.fadeIn2 = function () {
|
||||||
|
const _self = this;
|
||||||
|
let opacity = 0.0;
|
||||||
|
let IntervalId = null;
|
||||||
|
|
||||||
|
if (_self.css('opacity') !== '0') return;
|
||||||
|
|
||||||
|
_self.css('visibility', 'visible');
|
||||||
|
_self.css('opacity', '0.0');
|
||||||
|
|
||||||
|
IntervalId = window.setInterval(() => {
|
||||||
|
opacity += 0.1;
|
||||||
|
opacity = Math.round2(opacity, 1);
|
||||||
|
_self.css('opacity', opacity.toString());
|
||||||
|
|
||||||
|
if (opacity === 1.0) {
|
||||||
|
//_self.css("visibility", "");
|
||||||
|
_self.css('visibility', 'visible');
|
||||||
|
window.clearInterval(IntervalId);
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
return _self;
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.fadeOut2 = function () {
|
||||||
|
const _self = this;
|
||||||
|
let opacity = 1.0;
|
||||||
|
let IntervalId = null;
|
||||||
|
|
||||||
|
if (_self.css('opacity') !== '1') return;
|
||||||
|
|
||||||
|
_self.css('visibility', 'visible');
|
||||||
|
_self.css('opacity', '1.0');
|
||||||
|
|
||||||
|
IntervalId = window.setInterval(() => {
|
||||||
|
opacity -= 0.2;
|
||||||
|
opacity = Math.round2(opacity, 1);
|
||||||
|
_self.css('opacity', opacity.toString());
|
||||||
|
|
||||||
|
if (opacity === 0) {
|
||||||
|
_self.css('visibility', 'hidden');
|
||||||
|
window.clearInterval(IntervalId);
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
return _self;
|
||||||
|
};
|
||||||
|
|
||||||
|
Math.round2 = (value, decimals) => Number(Math.round(value + 'e' + decimals) + 'e-' + decimals);
|
||||||
|
|
||||||
|
const btnNavigatePlay_click = () => {
|
||||||
|
postMessage('navButton', 'playToggle');
|
||||||
|
UpdateFullScreenNavigate();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
$(() => {
|
||||||
|
_WindowHeight = $(window).height();
|
||||||
|
_WindowWidth = $(window).width();
|
||||||
|
|
||||||
|
frmGetLatLng = $('#frmGetLatLng');
|
||||||
|
txtAddress = $('#txtAddress');
|
||||||
|
btnGetLatLng = $('#btnGetLatLng');
|
||||||
|
btnClearQuery = $('#btnClearQuery');
|
||||||
|
btnGetGps = $('#btnGetGps');
|
||||||
|
|
||||||
|
divLat = $('#divLat');
|
||||||
|
spanLat = $('#spanLat');
|
||||||
|
divLng = $('#divLng');
|
||||||
|
spanLng = $('#spanLng');
|
||||||
|
|
||||||
|
iframeTwc = $('#iframeTwc');
|
||||||
|
btnFullScreen = $('#btnFullScreen');
|
||||||
|
divTwc = $('#divTwc');
|
||||||
|
divTwcTop = $('#divTwcTop');
|
||||||
|
divTwcMiddle = $('#divTwcMiddle');
|
||||||
|
divTwcBottom = $('#divTwcBottom');
|
||||||
|
divTwcLeft = $('#divTwcLeft');
|
||||||
|
divTwcRight = $('#divTwcRight');
|
||||||
|
divTwcNav = $('#divTwcNav');
|
||||||
|
divTwcNavContainer = $('#divTwcNavContainer');
|
||||||
|
|
||||||
|
frmScrollText = $('#frmScrollText');
|
||||||
|
chkScrollText = $('#chkScrollText');
|
||||||
|
txtScrollText = $('#txtScrollText');
|
||||||
|
btnScrollText = $('#btnScrollText');
|
||||||
|
|
||||||
|
frmScrollText.on('submit', frmScrollText_submit);
|
||||||
|
txtScrollText.on('focus', function () {
|
||||||
|
txtScrollText.select();
|
||||||
|
});
|
||||||
|
chkScrollText.on('change', chkScrollText_change);
|
||||||
|
|
||||||
|
txtAddress.on('focus', function () {
|
||||||
|
txtAddress.select();
|
||||||
|
});
|
||||||
|
|
||||||
|
txtAddress.focus();
|
||||||
|
|
||||||
|
$('.NavigateMenu').on('click', btnNavigateMenu_click);
|
||||||
|
$('.NavigateRefresh').on('click', btnNavigateRefresh_click);
|
||||||
|
$('.NavigateNext').on('click', btnNavigateNext_click);
|
||||||
|
$('.NavigatePrevious').on('click', btnNavigatePrevious_click);
|
||||||
|
$('.NavigatePlay').on('click', btnNavigatePlay_click);
|
||||||
|
|
||||||
|
$(btnGetGps).on('click', btnGetGps_click);
|
||||||
|
|
||||||
|
$(window).on('resize', OnFullScreen);
|
||||||
|
$(window).on('resize', window_resize);
|
||||||
|
$(document).on('mousemove', document_mousemove);
|
||||||
|
$(document).on('mousedown', document_mousemove);
|
||||||
|
divTwc.on('mousedown', document_mousemove);
|
||||||
|
$(document).on('keydown', document_keydown);
|
||||||
|
document.addEventListener('touchmove', e => { if (_FullScreenOverride) e.preventDefault(); });
|
||||||
|
$('.ToggleFullScreen').on('click', btnFullScreen_click);
|
||||||
|
FullScreenResize();
|
||||||
|
|
||||||
|
// listen for messages (from iframe) and handle accordingly
|
||||||
|
window.addEventListener('message', messageHandler, false);
|
||||||
|
|
||||||
|
const categories = [
|
||||||
|
'Land Features',
|
||||||
|
'Bay', 'Channel', 'Cove', 'Dam', 'Delta', 'Gulf', 'Lagoon', 'Lake', 'Ocean', 'Reef', 'Reservoir', 'Sea', 'Sound', 'Strait', 'Waterfall', 'Wharf', // Water Features
|
||||||
|
'Amusement Park', 'Historical Monument', 'Landmark', 'Tourist Attraction', 'Zoo', // POI/Arts and Entertainment
|
||||||
|
'College', // POI/Education
|
||||||
|
'Beach', 'Campground', 'Golf Course', 'Harbor', 'Nature Reserve', 'Other Parks and Outdoors', 'Park', 'Racetrack',
|
||||||
|
'Scenic Overlook', 'Ski Resort', 'Sports Center', 'Sports Field', 'Wildlife Reserve', // POI/Parks and Outdoors
|
||||||
|
'Airport', 'Ferry', 'Marina', 'Pier', 'Port', 'Resort', // POI/Travel
|
||||||
|
'Postal', 'Populated Place',
|
||||||
|
];
|
||||||
|
const cats = categories.join(',');
|
||||||
|
|
||||||
|
const overrides = {
|
||||||
|
'08736, Manasquan, New Jersey, USA': { x: -74.037, y: 40.1128 },
|
||||||
|
'32899, Orlando, Florida, USA': { x: -80.6774, y: 28.6143 },
|
||||||
|
'97003, Beaverton, Oregon, USA': { x: -122.8752489, y: 45.5050916 },
|
||||||
|
'99734, Prudhoe Bay, Alaska, USA': { x: -148.3372, y: 70.2552 },
|
||||||
|
'Guam, Oceania': { x: 144.74, y: 13.46 },
|
||||||
|
'Andover, Maine, United States': { x: -70.7525, y: 44.634167 },
|
||||||
|
'Bear Creek, Pennsylvania, United States': { x: -75.772809, y: 41.204074 },
|
||||||
|
'Bear Creek Village, Pennsylvania, United States': { x: -75.772809, y: 41.204074 },
|
||||||
|
'New York City, New York, United States': { x: -74.0059, y: 40.7142 },
|
||||||
|
'Pinnacles National Monument, San Benito County,California, United States': { x: -121.147278, y: 36.47075 },
|
||||||
|
'Pinnacles National Park, CA-146, Paicines, California': { x: -121.147278, y: 36.47075 },
|
||||||
|
'Welcome, Maryland, United States': { x: -77.081212, y: 38.4692469 },
|
||||||
|
'Tampa, Florida, United States (City)': { x: -82.5329, y: 27.9756 },
|
||||||
|
'San Francisco, California, United States': { x: -122.3758, y: 37.6188 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const roundToPlaces = function (num, decimals) {
|
||||||
|
var n = Math.pow(10, decimals);
|
||||||
|
return Math.round((n * num).toFixed(decimals)) / n;
|
||||||
|
};
|
||||||
|
|
||||||
|
const doRedirectToGeometry = (geom) => {
|
||||||
|
latLon = {lat:roundToPlaces(geom.y, 4), lon:roundToPlaces(geom.x, 4)};
|
||||||
|
LoadTwcData();
|
||||||
|
// Save the query
|
||||||
|
localStorage.setItem('TwcQuery', txtAddress.val());
|
||||||
|
};
|
||||||
|
|
||||||
|
let PreviousSeggestionValue = null;
|
||||||
|
let PreviousSeggestion = null;
|
||||||
|
const OnSelect = (suggestion) => {
|
||||||
|
let request;
|
||||||
|
|
||||||
|
// Do not auto get the same city twice.
|
||||||
|
if (PreviousSeggestionValue === suggestion.value) return;
|
||||||
|
PreviousSeggestionValue = suggestion.value;
|
||||||
|
PreviousSeggestion = suggestion;
|
||||||
|
|
||||||
|
if (overrides[suggestion.value]) {
|
||||||
|
doRedirectToGeometry(overrides[suggestion.value]);
|
||||||
|
} else {
|
||||||
|
request = $.ajax({
|
||||||
|
url: location.protocol + '//geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/find',
|
||||||
|
data: {
|
||||||
|
text: suggestion.value,
|
||||||
|
magicKey: suggestion.data,
|
||||||
|
f: 'json',
|
||||||
|
},
|
||||||
|
jsonp: 'callback',
|
||||||
|
dataType: 'jsonp',
|
||||||
|
});
|
||||||
|
request.done((data) => {
|
||||||
|
const loc = data.locations[0];
|
||||||
|
if (loc) {
|
||||||
|
doRedirectToGeometry(loc.feature.geometry);
|
||||||
|
} else {
|
||||||
|
alert('An unexpected error occurred. Please try a different search string.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$('#frmGetLatLng #txtAddress').devbridgeAutocomplete({
|
||||||
|
serviceUrl: location.protocol + '//geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/suggest',
|
||||||
|
deferRequestBy: 300,
|
||||||
|
paramName: 'text',
|
||||||
|
params: {
|
||||||
|
f: 'json',
|
||||||
|
countryCode: 'USA', //'USA,PRI,VIR,GUM,ASM',
|
||||||
|
category: cats,
|
||||||
|
maxSuggestions: 10,
|
||||||
|
},
|
||||||
|
dataType: 'jsonp',
|
||||||
|
transformResult: (response) => {
|
||||||
|
if (_AutoSelectQuery) {
|
||||||
|
_AutoSelectQuery = false;
|
||||||
|
window.setTimeout(() => {
|
||||||
|
$(ac.suggestionsContainer.children[0]).click();
|
||||||
|
}, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
suggestions: $.map(response.suggestions, function (i) {
|
||||||
|
return {
|
||||||
|
value: i.text,
|
||||||
|
data: i.magicKey,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
minChars: 3,
|
||||||
|
showNoSuggestionNotice: true,
|
||||||
|
noSuggestionNotice: 'No results found. Please try a different search string.',
|
||||||
|
onSelect: OnSelect,
|
||||||
|
width: 490,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ac = $('#frmGetLatLng #txtAddress').devbridgeAutocomplete();
|
||||||
|
frmGetLatLng.submit(function () {
|
||||||
|
if (ac.suggestions[0]) {
|
||||||
|
$(ac.suggestionsContainer.children[0]).click();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (PreviousSeggestion) {
|
||||||
|
PreviousSeggestionValue = null;
|
||||||
|
OnSelect(PreviousSeggestion);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto load the previous query
|
||||||
|
const TwcQuery = localStorage.getItem('TwcQuery');
|
||||||
|
if (TwcQuery) {
|
||||||
|
_AutoSelectQuery = true;
|
||||||
|
txtAddress.val(TwcQuery);
|
||||||
|
txtAddress.blur();
|
||||||
|
txtAddress.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
const TwcPlay = localStorage.getItem('TwcPlay');
|
||||||
|
if (!TwcPlay || TwcPlay === 'true') {
|
||||||
|
_IsPlaying = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TwcScrollText = localStorage.getItem('TwcScrollText');
|
||||||
|
if (TwcScrollText) {
|
||||||
|
txtScrollText.val(TwcScrollText);
|
||||||
|
}
|
||||||
|
const TwcScrollTextChecked = localStorage.getItem('TwcScrollTextChecked');
|
||||||
|
if (TwcScrollTextChecked && TwcScrollTextChecked === 'true') {
|
||||||
|
chkScrollText.prop('checked', 'checked');
|
||||||
|
} else {
|
||||||
|
chkScrollText.prop('checked', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
btnClearQuery.on('click', () => {
|
||||||
|
spanCity.text('');
|
||||||
|
spanState.text('');
|
||||||
|
spanStationId.text('');
|
||||||
|
spanRadarId.text('');
|
||||||
|
spanZoneId.text('');
|
||||||
|
|
||||||
|
chkScrollText.prop('checked', '');
|
||||||
|
txtScrollText.val('');
|
||||||
|
localStorage.removeItem('TwcScrollText');
|
||||||
|
localStorage.removeItem('TwcScrollTextChecked');
|
||||||
|
|
||||||
|
chkAutoRefresh.prop('checked', 'checked');
|
||||||
|
localStorage.removeItem('TwcAutoRefresh');
|
||||||
|
|
||||||
|
$('#radEnglish').prop('checked', 'checked');
|
||||||
|
localStorage.removeItem('TwcUnits');
|
||||||
|
|
||||||
|
TwcCallBack({ Status: 'ISPLAYING', Value: false });
|
||||||
|
localStorage.removeItem('TwcPlay');
|
||||||
|
_IsPlaying = true;
|
||||||
|
|
||||||
|
localStorage.removeItem('TwcQuery');
|
||||||
|
PreviousSeggestionValue = null;
|
||||||
|
PreviousSeggestion = null;
|
||||||
|
|
||||||
|
LoadTwcData('');
|
||||||
|
});
|
||||||
|
|
||||||
|
const TwcUnits = localStorage.getItem('TwcUnits');
|
||||||
|
if (!TwcUnits || TwcUnits === 'ENGLISH') {
|
||||||
|
$('#radEnglish').prop('checked', 'checked');
|
||||||
|
} else if (TwcUnits === 'METRIC') {
|
||||||
|
$('#radMetric').prop('checked', 'checked');
|
||||||
|
}
|
||||||
|
|
||||||
|
$('input[type=\'radio\'][name=\'radUnits\']').on('change', (e) => {
|
||||||
|
const Units = $(e.target).val();
|
||||||
|
e;
|
||||||
|
localStorage.setItem('TwcUnits', Units);
|
||||||
|
AssignLastUpdate();
|
||||||
|
postMessage('units', Units);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
divRefresh = $('#divRefresh');
|
||||||
|
spanLastRefresh = $('#spanLastRefresh');
|
||||||
|
chkAutoRefresh = $('#chkAutoRefresh');
|
||||||
|
lblRefreshCountDown = $('#lblRefreshCountDown');
|
||||||
|
spanRefreshCountDown = $('#spanRefreshCountDown');
|
||||||
|
|
||||||
|
chkAutoRefresh.on('change', (e) => {
|
||||||
|
const Checked = $(e.target).is(':checked');
|
||||||
|
|
||||||
|
if (_LastUpdate) {
|
||||||
|
if (Checked) {
|
||||||
|
StartAutoRefreshTimer();
|
||||||
|
} else {
|
||||||
|
StopAutoRefreshTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem('TwcAutoRefresh', Checked);
|
||||||
|
});
|
||||||
|
|
||||||
|
const TwcAutoRefresh = localStorage.getItem('TwcAutoRefresh');
|
||||||
|
if (!TwcAutoRefresh || TwcAutoRefresh === 'true') {
|
||||||
|
chkAutoRefresh.prop('checked', 'checked');
|
||||||
|
} else {
|
||||||
|
chkAutoRefresh.prop('checked', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
spanCity = $('#spanCity');
|
||||||
|
spanState = $('#spanState');
|
||||||
|
spanStationId = $('#spanStationId');
|
||||||
|
spanRadarId = $('#spanRadarId');
|
||||||
|
spanZoneId = $('#spanZoneId');
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
// read and dispatch an event from the iframe
|
||||||
|
const messageHandler = (event) => {
|
||||||
|
// test for trust
|
||||||
|
if (!event.isTrusted) return;
|
||||||
|
// get the data
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
|
||||||
|
// dispatch event
|
||||||
|
if (!data.type) return;
|
||||||
|
switch (data.type) {
|
||||||
|
case 'loaded':
|
||||||
|
_LastUpdate = new Date();
|
||||||
|
AssignLastUpdate();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'weatherParameters':
|
||||||
|
populateWeatherParameters(data.message);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'isPlaying':
|
||||||
|
_IsPlaying = data.message;
|
||||||
|
localStorage.setItem('TwcPlay', _IsPlaying);
|
||||||
|
|
||||||
|
if (_IsPlaying) {
|
||||||
|
_NoSleep.enable();
|
||||||
|
|
||||||
|
$('img[src=\'images/nav/ic_play_arrow_white_24dp_1x.png\']').attr('title', 'Pause');
|
||||||
|
$('img[src=\'images/nav/ic_play_arrow_white_24dp_1x.png\']').attr('src', 'images/nav/ic_pause_white_24dp_1x.png');
|
||||||
|
$('img[src=\'images/nav/ic_play_arrow_white_24dp_2x.png\']').attr('title', 'Pause');
|
||||||
|
$('img[src=\'images/nav/ic_play_arrow_white_24dp_2x.png\']').attr('src', 'images/nav/ic_pause_white_24dp_2x.png');
|
||||||
|
} else {
|
||||||
|
_NoSleep.disable();
|
||||||
|
|
||||||
|
$('img[src=\'images/nav/ic_pause_white_24dp_1x.png\']').attr('title', 'Play');
|
||||||
|
$('img[src=\'images/nav/ic_pause_white_24dp_1x.png\']').attr('src', 'images/nav/ic_play_arrow_white_24dp_1x.png');
|
||||||
|
$('img[src=\'images/nav/ic_pause_white_24dp_2x.png\']').attr('title', 'Play');
|
||||||
|
$('img[src=\'images/nav/ic_pause_white_24dp_2x.png\']').attr('src', 'images/nav/ic_play_arrow_white_24dp_2x.png');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.error(`Unknown event '${data.eventType}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// post a message to the iframe
|
||||||
|
const postMessage = (type, message = {}) => {
|
||||||
|
const iframeWindow = document.getElementById('iframeTwc').contentWindow;
|
||||||
|
iframeWindow.postMessage(JSON.stringify({type, message}, window.location.origin));
|
||||||
|
};
|
||||||
|
|
||||||
|
const StartAutoRefreshTimer = () => {
|
||||||
|
// Esnure that any previous timer has already stopped.
|
||||||
|
//StopAutoRefreshTimer();
|
||||||
|
if (_AutoRefreshIntervalId) {
|
||||||
|
// Timer is already running.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the time elapsed.
|
||||||
|
_AutoRefreshCountMs = 0;
|
||||||
|
|
||||||
|
const AutoRefreshTimer = () => {
|
||||||
|
// Increment the total time elapsed.
|
||||||
|
_AutoRefreshCountMs += _AutoRefreshIntervalMs;
|
||||||
|
|
||||||
|
// Display the count down.
|
||||||
|
let RemainingMs = (_AutoRefreshTotalIntervalMs - _AutoRefreshCountMs);
|
||||||
|
if (RemainingMs < 0) {
|
||||||
|
RemainingMs = 0;
|
||||||
|
}
|
||||||
|
const dt = new Date(RemainingMs);
|
||||||
|
spanRefreshCountDown.html((dt.getMinutes() < 10 ? '0' + dt.getMinutes() : dt.getMinutes()) + ':' + (dt.getSeconds() < 10 ? '0' + dt.getSeconds() : dt.getSeconds()));
|
||||||
|
|
||||||
|
// Time has elapsed.
|
||||||
|
if (_AutoRefreshCountMs >= _AutoRefreshTotalIntervalMs) LoadTwcData(_TwcDataUrl);
|
||||||
|
};
|
||||||
|
_AutoRefreshIntervalId = window.setInterval(AutoRefreshTimer, _AutoRefreshIntervalMs);
|
||||||
|
AutoRefreshTimer();
|
||||||
|
};
|
||||||
|
const StopAutoRefreshTimer = () => {
|
||||||
|
if (_AutoRefreshIntervalId) {
|
||||||
|
window.clearInterval(_AutoRefreshIntervalId);
|
||||||
|
spanRefreshCountDown.html('--:--');
|
||||||
|
_AutoRefreshIntervalId = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const btnGetGps_click = () => {
|
||||||
|
if (!navigator.geolocation) return;
|
||||||
|
|
||||||
|
const CurrentPosition = (position) => {
|
||||||
|
const latitude = position.coords.latitude;
|
||||||
|
const longitude = position.coords.longitude;
|
||||||
|
|
||||||
|
console.log('Latitude: ' + latitude + '; Longitude: ' + longitude);
|
||||||
|
|
||||||
|
//http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/reverseGeocode?location=-72.971293%2C+40.850043&f=pjson
|
||||||
|
const request = $.ajax({
|
||||||
|
url: location.protocol + '//geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/reverseGeocode',
|
||||||
|
data: {
|
||||||
|
location: longitude + ',' + latitude,
|
||||||
|
distance: 1000, // Find location upto 1 KM.
|
||||||
|
f: 'json',
|
||||||
|
},
|
||||||
|
jsonp: 'callback',
|
||||||
|
dataType: 'jsonp',
|
||||||
|
});
|
||||||
|
request.done((data) => {
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
const ZipCode = data.address.Postal;
|
||||||
|
const City = data.address.City;
|
||||||
|
const State = states.getStateTwoDigitCode(data.address.Region);
|
||||||
|
const Country = data.address.CountryCode;
|
||||||
|
const TwcQuery = `${ZipCode}, ${City}, ${State}, ${Country}`;
|
||||||
|
|
||||||
|
txtAddress.val(TwcQuery);
|
||||||
|
txtAddress.blur();
|
||||||
|
txtAddress.focus();
|
||||||
|
|
||||||
|
// Save the query
|
||||||
|
localStorage.setItem('TwcQuery', TwcQuery);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
navigator.geolocation.getCurrentPosition(CurrentPosition);
|
||||||
|
};
|
||||||
|
|
||||||
|
const populateWeatherParameters = (weatherParameters) => {
|
||||||
|
spanCity.text(weatherParameters.city + ', ');
|
||||||
|
spanState.text(weatherParameters.state);
|
||||||
|
spanStationId.text(weatherParameters.stationId);
|
||||||
|
spanRadarId.text(weatherParameters.radarId);
|
||||||
|
spanZoneId.text(weatherParameters.zoneId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const frmScrollText_submit = () => {
|
||||||
|
chkScrollText_change();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const chkScrollText_change = () => {
|
||||||
|
txtScrollText.blur();
|
||||||
|
|
||||||
|
let ScrollText = txtScrollText.val();
|
||||||
|
localStorage.setItem('TwcScrollText', ScrollText);
|
||||||
|
|
||||||
|
const ScrollTextChecked = chkScrollText.is(':checked');
|
||||||
|
localStorage.setItem('TwcScrollTextChecked', ScrollTextChecked);
|
||||||
|
|
||||||
|
if (chkScrollText.is(':checked') === false) {
|
||||||
|
ScrollText = '';
|
||||||
|
}
|
||||||
|
postMessage('assignScrollText', ScrollText);
|
||||||
|
};
|
94
dist/scripts/timer.js
vendored
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
if (window.Worker)
|
||||||
|
{
|
||||||
|
var _TimerWorkCallBacks = [];
|
||||||
|
var _TimerIds = 0;
|
||||||
|
|
||||||
|
var _TimerWorker = new window.Worker("scripts/TimerWorker.js");
|
||||||
|
_TimerWorker.onmessage = function (e)
|
||||||
|
{
|
||||||
|
var Message = e.data;
|
||||||
|
|
||||||
|
switch (Message.Action)
|
||||||
|
{
|
||||||
|
case "ELASPED":
|
||||||
|
var TimerWorkCallBack = _TimerWorkCallBacks[Message.Id];
|
||||||
|
|
||||||
|
if (!TimerWorkCallBack)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var Window = TimerWorkCallBack.Window;
|
||||||
|
var CallBack = TimerWorkCallBack.CallBack;
|
||||||
|
var Arguments = TimerWorkCallBack.Arguments;
|
||||||
|
|
||||||
|
if (typeof (CallBack) === 'string')
|
||||||
|
{
|
||||||
|
CallBack = new Function(CallBack);
|
||||||
|
}
|
||||||
|
if (typeof (CallBack) === 'function')
|
||||||
|
{
|
||||||
|
CallBack.apply(Window, Arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Message.RunOnce == true)
|
||||||
|
{
|
||||||
|
_clearIntervalWorker(Message.Id);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.setIntervalWorker = function (CallBack, TimeOut, Arguments)
|
||||||
|
{
|
||||||
|
Arguments = Array.prototype.slice.call(arguments).slice(2);
|
||||||
|
return _setIntervalWorker(false, CallBack, TimeOut, Arguments, window);
|
||||||
|
};
|
||||||
|
window.setTimeoutWorker = function (CallBack, TimeOut, Arguments)
|
||||||
|
{
|
||||||
|
Arguments = Array.prototype.slice.call(arguments).slice(2);
|
||||||
|
return _setIntervalWorker(true, CallBack, TimeOut, Arguments, window);
|
||||||
|
};
|
||||||
|
var _setIntervalWorker = function (RunOnce, CallBack, TimeOut, Arguments, Window)
|
||||||
|
{
|
||||||
|
var Id = ++_TimerIds;
|
||||||
|
|
||||||
|
_TimerWorkCallBacks[Id] = {
|
||||||
|
CallBack: CallBack,
|
||||||
|
Arguments: Arguments,
|
||||||
|
Window: Window,
|
||||||
|
};
|
||||||
|
|
||||||
|
_TimerWorker.postMessage({
|
||||||
|
Action: "START",
|
||||||
|
RunOnce: RunOnce,
|
||||||
|
Id: Id,
|
||||||
|
TimeOut: TimeOut,
|
||||||
|
});
|
||||||
|
|
||||||
|
return Id;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.clearIntervalWorker = function (Id)
|
||||||
|
{
|
||||||
|
_clearIntervalWorker(Id);
|
||||||
|
};
|
||||||
|
window.clearTimeoutWorker = function (Id)
|
||||||
|
{
|
||||||
|
_clearIntervalWorker(Id);
|
||||||
|
};
|
||||||
|
var _clearIntervalWorker = function (Id)
|
||||||
|
{
|
||||||
|
if (!Id)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_TimerWorker.postMessage({
|
||||||
|
Action: "STOP",
|
||||||
|
Id: Id,
|
||||||
|
});
|
||||||
|
|
||||||
|
delete _TimerWorkCallBacks[Id];
|
||||||
|
};
|
||||||
|
}
|
2
dist/scripts/vendor/jquery-3.5.1.min.js
vendored
Normal file
8
dist/scripts/vendor/jquery.autocomplete.min.js
vendored
Normal file
14
dist/scripts/vendor/jquery.touchSwipe.min.js
vendored
Normal file
2
dist/scripts/vendor/nosleep.min.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// NoSleep.min.js v0.5.0 - git.io/vfn01 - Rich Tibbett - MIT license
|
||||||
|
!function(A){function e(A,e,o){var t=document.createElement("source");t.src=o,t.type="video/"+e,A.appendChild(t)}var o={Android:/Android/gi.test(navigator.userAgent),iOS:/AppleWebKit/.test(navigator.userAgent)&&/Mobile\/\w+/.test(navigator.userAgent)},t={WebM:"data:video/webm;base64,GkXfo0AgQoaBAUL3gQFC8oEEQvOBCEKCQAR3ZWJtQoeBAkKFgQIYU4BnQI0VSalmQCgq17FAAw9CQE2AQAZ3aGFtbXlXQUAGd2hhbW15RIlACECPQAAAAAAAFlSua0AxrkAu14EBY8WBAZyBACK1nEADdW5khkAFVl9WUDglhohAA1ZQOIOBAeBABrCBCLqBCB9DtnVAIueBAKNAHIEAAIAwAQCdASoIAAgAAUAmJaQAA3AA/vz0AAA=",MP4:"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAAG21kYXQAAAGzABAHAAABthADAowdbb9/AAAC6W1vb3YAAABsbXZoZAAAAAB8JbCAfCWwgAAAA+gAAAAAAAEAAAEAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAIVdHJhawAAAFx0a2hkAAAAD3wlsIB8JbCAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAIAAAACAAAAAABsW1kaWEAAAAgbWRoZAAAAAB8JbCAfCWwgAAAA+gAAAAAVcQAAAAAAC1oZGxyAAAAAAAAAAB2aWRlAAAAAAAAAAAAAAAAVmlkZW9IYW5kbGVyAAAAAVxtaW5mAAAAFHZtaGQAAAABAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAAEcc3RibAAAALhzdHNkAAAAAAAAAAEAAACobXA0dgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAIAAgASAAAAEgAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABj//wAAAFJlc2RzAAAAAANEAAEABDwgEQAAAAADDUAAAAAABS0AAAGwAQAAAbWJEwAAAQAAAAEgAMSNiB9FAEQBFGMAAAGyTGF2YzUyLjg3LjQGAQIAAAAYc3R0cwAAAAAAAAABAAAAAQAAAAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAEAAAABAAAAFHN0c3oAAAAAAAAAEwAAAAEAAAAUc3RjbwAAAAAAAAABAAAALAAAAGB1ZHRhAAAAWG1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAAK2lsc3QAAAAjqXRvbwAAABtkYXRhAAAAAQAAAABMYXZmNTIuNzguMw=="},i=function(){return o.iOS?this.noSleepTimer=null:o.Android&&(this.noSleepVideo=document.createElement("video"),this.noSleepVideo.setAttribute("loop",""),e(this.noSleepVideo,"webm",t.WebM),e(this.noSleepVideo,"mp4",t.MP4)),this};i.prototype.enable=function(A){o.iOS?(this.disable(),this.noSleepTimer=window.setInterval(function(){window.location.href='/',window.setTimeout(window.stop,0)},A||15e3)):o.Android&&this.noSleepVideo.play()},i.prototype.disable=function(){o.iOS?this.noSleepTimer&&(window.clearInterval(this.noSleepTimer),this.noSleepTimer=null):o.Android&&this.noSleepVideo.pause()},A.NoSleep=i}(this);
|
1
dist/twc3.html
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<!DOCTYPE html><html lang="en" xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="utf-8"><link rel="stylesheet" type="text/css" href="resources/ws.min.css?_=2.0.0"><script type="text/javascript" src="resources/ws.min.js?_=2.0.0"></script></head><body><!-- This will force the browser to download the font before the canvas is rendered. --><div class="fontPreload" id="Star4000">123 This is a test</div><div class="fontPreload" id="Star4000Extended">123 This is a test</div><div class="fontPreload" id="Star4000LargeCompressedNumbers">123 This is a test</div><div class="fontPreload" id="Star4000LargeCompressed">123 This is a test</div><div class="fontPreload" id="Star4000Large">123 This is a test</div><div class="fontPreload" id="Star4000Small">123 This is a test</div><div class="fontPreload" id="Star4Radar">123 This is a test</div><div id="container"><canvas id="progressCanvas" width="640" height="480"></canvas></div></body></html>
|
127
gulp/publish-frontend.js
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
const version = '2.0.0';
|
||||||
|
|
||||||
|
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');
|
||||||
|
const del = require('del');
|
||||||
|
const s3Upload = require('gulp-s3-upload');
|
||||||
|
|
||||||
|
gulp.task('clean', () =>
|
||||||
|
del(['./dist**']),
|
||||||
|
);
|
||||||
|
|
||||||
|
const js_sources = [
|
||||||
|
'server/scripts/vendor/jquery-3.5.1.min.js',
|
||||||
|
'server/scripts/vendor/libgif.js',
|
||||||
|
'server/scripts/vendor/luxon.js',
|
||||||
|
'server/scripts/vendor/suncalc.js',
|
||||||
|
'server/scripts/data/travelcities.js',
|
||||||
|
'server/scripts/data/regionalcities.js',
|
||||||
|
'server/scripts/data/stations.js',
|
||||||
|
'server/scripts/modules/draw.js',
|
||||||
|
'server/scripts/modules/weatherdisplay.js',
|
||||||
|
'server/scripts/modules/icons.js',
|
||||||
|
'server/scripts/modules/utilities.js',
|
||||||
|
'server/scripts/modules/currentweather.js',
|
||||||
|
'server/scripts/modules/latestobservations.js',
|
||||||
|
'server/scripts/modules/travelforecast.js',
|
||||||
|
'server/scripts/modules/regionalforecastdata.js',
|
||||||
|
'server/scripts/modules/regionalforecast.js',
|
||||||
|
'server/scripts/modules/localforecast.js',
|
||||||
|
'server/scripts/modules/extendedforecast.js',
|
||||||
|
'server/scripts/modules/almanac.js',
|
||||||
|
'server/scripts/modules/navigation.js',
|
||||||
|
];
|
||||||
|
gulp.task('compress_js', () =>
|
||||||
|
gulp.src(js_sources)
|
||||||
|
.pipe(concat('ws.min.js'))
|
||||||
|
.pipe(terser())
|
||||||
|
.pipe(gulp.dest('./dist/resources')),
|
||||||
|
);
|
||||||
|
|
||||||
|
const css_sources = [
|
||||||
|
'server/styles/*.css',
|
||||||
|
];
|
||||||
|
gulp.task('compress_css', () =>
|
||||||
|
gulp.src(css_sources)
|
||||||
|
.pipe(concat('ws.min.css'))
|
||||||
|
.pipe(cleanCSS())
|
||||||
|
.pipe(gulp.dest('./dist/resources')),
|
||||||
|
);
|
||||||
|
|
||||||
|
const html_sources = [
|
||||||
|
'views/*.ejs',
|
||||||
|
];
|
||||||
|
gulp.task('compress_html', () =>
|
||||||
|
gulp.src(html_sources)
|
||||||
|
.pipe(ejs({
|
||||||
|
production: version,
|
||||||
|
}))
|
||||||
|
.pipe(rename({extname: '.html'}))
|
||||||
|
.pipe(htmlmin({collapseWhitespace: true}))
|
||||||
|
.pipe(gulp.dest('./dist')),
|
||||||
|
);
|
||||||
|
|
||||||
|
const other_files = [
|
||||||
|
'server/robots.txt',
|
||||||
|
'server/manifest.json',
|
||||||
|
'server/scripts/index.js',
|
||||||
|
'server/scripts/timer.js',
|
||||||
|
'server/scripts/TimerWorker.js',
|
||||||
|
'server/scripts/data/states.js',
|
||||||
|
'server/scripts/vendor/jquery-3.5.1.min.js',
|
||||||
|
'server/scripts/vendor/jquery.autocomplete.min.js',
|
||||||
|
'server/scripts/vendor/nosleep.min.js',
|
||||||
|
'server/scripts/vendor/jquery.touchSwipe.min.js',
|
||||||
|
];
|
||||||
|
gulp.task('copy_other_files', () =>
|
||||||
|
gulp.src(other_files, {base: 'server/'})
|
||||||
|
.pipe(gulp.dest('./dist')),
|
||||||
|
);
|
||||||
|
|
||||||
|
const s3 = s3Upload({
|
||||||
|
useIAM: true,
|
||||||
|
},{
|
||||||
|
region: 'us-east-1',
|
||||||
|
});
|
||||||
|
const upload_sources = [
|
||||||
|
'dist/**',
|
||||||
|
];
|
||||||
|
gulp.task('upload', () =>
|
||||||
|
gulp.src(upload_sources, {base: './dist'})
|
||||||
|
.pipe(s3({
|
||||||
|
Bucket: 'weatherstar',
|
||||||
|
StorageClass: 'STANDARD',
|
||||||
|
maps: {
|
||||||
|
CacheControl: (keyname) => {
|
||||||
|
if (keyname.indexOf('index.html') > -1) return 'max-age=300'; // 10 minutes
|
||||||
|
if (keyname.indexOf('twc3.html') > -1) return 'max-age=300'; // 10 minutes
|
||||||
|
return 'max-age=2592000'; // 1 month
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
gulp.task('invalidate', async () => {
|
||||||
|
// get cloudfront
|
||||||
|
const AWS = require('aws-sdk');
|
||||||
|
AWS.config.update({region: 'us-east-1'});
|
||||||
|
const cloudfront = new AWS.CloudFront({apiVersion: '2020-01-01'});
|
||||||
|
|
||||||
|
return cloudfront.createInvalidation({
|
||||||
|
DistributionId: 'E9171A4KV8KCW',
|
||||||
|
InvalidationBatch: {
|
||||||
|
CallerReference: (new Date()).toLocaleString(),
|
||||||
|
Paths: {
|
||||||
|
Quantity: 1,
|
||||||
|
Items: ['/*'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).promise();
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = gulp.series('clean', gulp.parallel('compress_js','compress_css', 'compress_html', 'copy_other_files'), 'upload', 'invalidate');
|
3
gulpfile.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
const gulp = require('gulp');
|
||||||
|
|
||||||
|
gulp.task('publish-frontend', require('./gulp/publish-frontend'));
|
45
index.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
// express
|
||||||
|
const express = require('express');
|
||||||
|
const app = express();
|
||||||
|
const port = 8080;
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// template engine
|
||||||
|
app.set('view engine', 'ejs');
|
||||||
|
|
||||||
|
// cors pass through
|
||||||
|
const corsPassThru = require('./cors');
|
||||||
|
|
||||||
|
// cors pass-thru to api.weather.gov
|
||||||
|
app.get('/stations/*', corsPassThru);
|
||||||
|
|
||||||
|
|
||||||
|
const index = (req, res) => {
|
||||||
|
res.render(path.join(__dirname, 'views/index'), {
|
||||||
|
production: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const twc3 = (req, res) => {
|
||||||
|
res.render(path.join(__dirname, 'views/twc3'), {
|
||||||
|
production: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// two html pages
|
||||||
|
app.get('/index.html', index);
|
||||||
|
app.get('/', index);
|
||||||
|
app.get('/twc3.html', twc3);
|
||||||
|
|
||||||
|
// fallback
|
||||||
|
app.get('*', express.static('./server'));
|
||||||
|
|
||||||
|
const server = app.listen(port, () => {
|
||||||
|
console.log(`Server listening on port ${port}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// graceful shutdown
|
||||||
|
process.on('SIGINT', () => {
|
||||||
|
server.close(()=> {
|
||||||
|
console.log('Server closed');
|
||||||
|
});
|
||||||
|
});
|
5003
package-lock.json
generated
Normal file
35
package.json
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"name": "ws4kp",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"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": "^5.1.0",
|
||||||
|
"ejs": "^3.1.5",
|
||||||
|
"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": "^1.4.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"eslint": "^7.7.0"
|
||||||
|
}
|
||||||
|
}
|
30
server/fonts/ReadMe.txt
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
--Star 3000--
|
||||||
|
|
||||||
|
Star3000.ttf - Standard text style for most screens (and Travel Cities title header)
|
||||||
|
Star3000 Small.ttf - Time/Date and some page headers
|
||||||
|
Star3000 Large.ttf - Travel Cities Forecast (Forecast portion only)
|
||||||
|
Star3000 Extra Large.ttf - Only used on some advertiser text
|
||||||
|
Star3000 Extended.ttf - Only used on some advertiser text
|
||||||
|
"Heavy" style is an emboldened version of the standard font (used on some STARs)
|
||||||
|
|
||||||
|
Star3000 Outline.ttf - A contrast border (stroke) that surrounds the Star3000.ttf base font. When used, must be as a text layer undeneath the base font (and is usually black in color).
|
||||||
|
Star3000 Small Outline.ttf - A contrast border (stroke) that surrounds the Star3000 Small.ttf base font. When used, must be as a text layer undeneath the base font (and is usually black in color).
|
||||||
|
Star3000 Large Outline.ttf - A contrast border (stroke) that surrounds the Star3000 Large.ttf base font. When used, must be as a text layer undeneath the base font (and is usually black in color).
|
||||||
|
|
||||||
|
***Outlines for other font styles are not currently available.
|
||||||
|
|
||||||
|
--Star 4000--
|
||||||
|
|
||||||
|
Star4000.ttf - Standard text style for zone forecast, observation tables, regional map cities, almanac, extended forecast day/weather/temperature headers, Current Conditions right half data and most page header titles (also Travel Cities title header before Nov. 1992)
|
||||||
|
Star4000 Small.ttf - Time/Date, NWS Local Update page header, temperature header for Travel Cities Forecast (after Nov. 1992)
|
||||||
|
Star4000 Large.ttf - City names and temperature data on Travel Cities Forecast (after Nov. 1992), Extended forecast temperature values (after Feb. 1991), Current Conditions temperature value (after Mar. 1991)
|
||||||
|
Star4000 Large Compressed - Travel Cities Forecast (before Nov. 1992), regional map temperatures
|
||||||
|
Star4000 Large Compressed Numbers - Temperature values on regional forecast/observation maps
|
||||||
|
Star4000 Extended - A proportional width font used for the Current Conditions present weather description and wind data
|
||||||
|
Star 4 Radar.ttf - Radar airport I.D.
|
||||||
|
|
||||||
|
--Star Jr.--
|
||||||
|
|
||||||
|
StarJr.ttf - Standard text style for most screens (and Travel Cities title header)
|
||||||
|
StarJr Small.ttf - Time/Date and some page headers
|
||||||
|
StarJr Compressed.ttf - Travel Cities Forecast
|
BIN
server/fonts/Star 4 Radar.ttf
Normal file
BIN
server/fonts/Star 4 Radar.woff
Normal file
BIN
server/fonts/Star4000 Extended.ttf
Normal file
BIN
server/fonts/Star4000 Extended.woff
Normal file
BIN
server/fonts/Star4000 Large Compressed Numbers.ttf
Normal file
BIN
server/fonts/Star4000 Large Compressed Numbers.woff
Normal file
BIN
server/fonts/Star4000 Large Compressed.ttf
Normal file
BIN
server/fonts/Star4000 Large Compressed.woff
Normal file
BIN
server/fonts/Star4000 Large.ttf
Normal file
BIN
server/fonts/Star4000 Large.woff
Normal file
BIN
server/fonts/Star4000 Small.ttf
Normal file
BIN
server/fonts/Star4000 Small.woff
Normal file
BIN
server/fonts/Star4000.ttf
Normal file
BIN
server/fonts/Star4000.woff
Normal file
BIN
server/fonts/Star4000LCN.woff
Normal file
BIN
server/images/2/Blowing-Snow.gif
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
server/images/2/Clear.gif
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
server/images/2/Cloudy.gif
Normal file
After Width: | Height: | Size: 745 B |
BIN
server/images/2/First-Quarter.gif
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
server/images/2/Fog.gif
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
server/images/2/Freezing-Rain-Sleet.gif
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
server/images/2/Freezing-Rain.gif
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
server/images/2/Full-Moon.gif
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
server/images/2/Heavy-Snow.gif
Normal file
After Width: | Height: | Size: 4 KiB |
BIN
server/images/2/Ice-Snow.gif
Normal file
After Width: | Height: | Size: 6 KiB |
BIN
server/images/2/Isolated-Tstorms.gif
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
server/images/2/Last-Quarter.gif
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
server/images/2/Light-Snow.gif
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
server/images/2/Mostly-Clear.gif
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
server/images/2/Mostly-Cloudy.gif
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
server/images/2/New-Moon.gif
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
server/images/2/Partly-Clear.gif
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
server/images/2/Partly-Cloudy.gif
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
server/images/2/Rain-Snow.gif
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
server/images/2/Rain.gif
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
server/images/2/Scattered-Showers.gif
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
server/images/2/Scattered-Snow-Showers.gif
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
server/images/2/Scattered-Tstorms.gif
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
server/images/2/Shower.gif
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
server/images/2/Showers.gif
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
server/images/2/Sleet.gif
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
server/images/2/Snow-Sleet.gif
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
server/images/2/Snow-to-Rain.gif
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
server/images/2/Sunny.gif
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
server/images/2/Thunder.gif
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
server/images/2/ThunderSnow.gif
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
server/images/2/Thunderstorm.gif
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
server/images/2/Thunderstorms.gif
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
server/images/2/Windy.gif
Normal file
After Width: | Height: | Size: 582 B |
BIN
server/images/2/Wintry-Mix.gif
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
server/images/2/r/AM-Snow-1994.gif
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
server/images/2/r/Blowing Snow.gif
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
server/images/2/r/Clear-1992.gif
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
server/images/2/r/Clear.gif
Normal file
After Width: | Height: | Size: 890 B |
BIN
server/images/2/r/Cloudy 1990.gif
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
server/images/2/r/Cloudy-Wind.gif
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
server/images/2/r/Cloudy.gif
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
server/images/2/r/Flurries.gif
Normal file
After Width: | Height: | Size: 110 B |
BIN
server/images/2/r/Fog.gif
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
server/images/2/r/Freezing-Rain-1991.gif
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
server/images/2/r/Freezing-Rain-1992.gif
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
server/images/2/r/Freezing-Rain-Sleet-1992.gif
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
server/images/2/r/Freezing-Rain-Sleet.gif
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
server/images/2/r/Freezing-Rain-Snow-1992.gif
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
server/images/2/r/Freezing-Rain-Snow.gif
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
server/images/2/r/Freezing-Rain.gif
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
server/images/2/r/Frigid.gif
Normal file
After Width: | Height: | Size: 925 B |
BIN
server/images/2/r/Frz-Rain-Snow-1991.gif
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
server/images/2/r/Heavy Snow.gif
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
server/images/2/r/Heavy-Snow-1994.gif
Normal file
After Width: | Height: | Size: 2.3 KiB |