reorganize for build system

This commit is contained in:
Matt Walsh 2020-09-04 13:02:20 -05:00
commit 8bc7a7dd95
477 changed files with 49411 additions and 0 deletions

2
.eslintignore Normal file
View file

@ -0,0 +1,2 @@
*.min.*
server/scripts/vendor/*

50
.eslintrc.js Normal file
View 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
View 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
View 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

View 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
View file

@ -0,0 +1 @@
node_modules

29
.vscode/launch.json vendored Normal file
View 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
View 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
View 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
View 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

File diff suppressed because one or more lines are too long

12
dist/manifest.json vendored Normal file
View 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
View 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

File diff suppressed because one or more lines are too long

0
dist/robots.txt vendored Normal file
View file

66
dist/scripts/TimerWorker.js vendored Normal file
View 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
View 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
View 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
View 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];
};
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dist/scripts/vendor/nosleep.min.js vendored Normal file
View 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
View 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
View 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
View file

@ -0,0 +1,3 @@
const gulp = require('gulp');
gulp.task('publish-frontend', require('./gulp/publish-frontend'));

45
index.js Normal file
View 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

File diff suppressed because it is too large Load diff

35
package.json Normal file
View 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
View 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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
server/fonts/Star4000.ttf Normal file

Binary file not shown.

BIN
server/fonts/Star4000.woff Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
server/images/2/Clear.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
server/images/2/Cloudy.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
server/images/2/Fog.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

BIN
server/images/2/Rain.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
server/images/2/Shower.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
server/images/2/Showers.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
server/images/2/Sleet.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
server/images/2/Sunny.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
server/images/2/Thunder.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
server/images/2/Windy.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
server/images/2/r/Clear.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 B

BIN
server/images/2/r/Fog.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 925 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Some files were not shown because too many files have changed in this diff Show more