mirror of
https://github.com/netbymatt/ws4kp.git
synced 2026-04-15 08:09:31 -07:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
913dc383f6 | ||
|
|
94249560f2 | ||
|
|
75314d92c9 | ||
|
|
168c0c5caf | ||
|
|
b6cd75ab42 | ||
|
|
934a489340 | ||
|
|
c5f5c101f9 | ||
|
|
933367974f | ||
|
|
0e67eb22dc | ||
|
|
9df6f6888f | ||
|
|
04cc5d4252 | ||
|
|
543d3f5196 | ||
|
|
78ceba9c19 | ||
|
|
763d42061e | ||
|
|
318c55b92d | ||
|
|
84d39101e5 | ||
|
|
fd0e42aa67 | ||
|
|
7be20490e8 | ||
|
|
20b4d22115 | ||
|
|
af4ba1b881 | ||
|
|
9f9dafa30c | ||
|
|
227a959e6a | ||
|
|
63703d1fff |
@@ -22,7 +22,7 @@ module.exports = {
|
||||
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 2021,
|
||||
ecmaVersion: 2023,
|
||||
},
|
||||
plugins: [
|
||||
'unicorn',
|
||||
|
||||
11
README.md
11
README.md
@@ -78,7 +78,7 @@ I've made several changes to this Weather Star 4000 simulation compared to the o
|
||||
* "Flavors" are not present in this simulation. Flavors refer to the order of the weather information that was shown on the original units. Instead, the order of the displays has been fixed and a checkboxes can be used to turn on and off individual displays. The travel forecast has been defaulted to off so only local information shows for new users.
|
||||
|
||||
## Sharing a permalink (bookmarking)
|
||||
Selected displays, the forecast city and widescreen setting are sticky from one session to the next. However if you would like to share your exact configuration or bookmark it click the "Copy Permalink" near the bottom of the page. A URL will be copied to your clipboard with all of you selected displays and location. You can then share this link or add it to your bookmarks.
|
||||
Selected displays, the forecast city and widescreen setting are sticky from one session to the next. However if you would like to share your exact configuration or bookmark it click the "Copy Permalink" (or get "Get Parmalink") near the bottom of the page. A URL will be copied to your clipboard with all of you selected displays and location (or copy it from the page if your browser doesn't support clipboard transfers directly). You can then share this link or add it to your bookmarks.
|
||||
|
||||
## Kiosk mode
|
||||
Kiosk mode can be activated by a checkbox on the page. Note that there is no way out of kiosk mode (except refresh or closing the browser), and the play/pause and other controls will not be available. This is deliberate as a browser's kiosk mode it intended not to be exited or significantly modified.
|
||||
@@ -91,9 +91,14 @@ As time allows I will be working on the following enhancements.
|
||||
|
||||
* Better error reporting when api.weather.gov is down (happens more often than you would think)
|
||||
|
||||
And the following technical fixes.
|
||||
## Community Notes
|
||||
|
||||
* Caching of the animated gifs, specifically after they are decompressed
|
||||
Thanks to the WeatherStar community for providing these discussions to further extend your retro forecasts!
|
||||
|
||||
* [Stream as FFMPEG](https://github.com/netbymatt/ws4kp/issues/37#issuecomment-2008491948)
|
||||
|
||||
## Customization
|
||||
A hook is provided as `/server/scripts/custom.js` to allow customizations to your own fork of this project, without accidentally pushing your customizations back upstream to the git repository. An sample file is provided at `/server/scripts/custom.sample.js` and should be renamed to `custom.js` activate it.
|
||||
|
||||
## Issue reporting and feature requests
|
||||
|
||||
|
||||
2
dist/index.html
vendored
2
dist/index.html
vendored
File diff suppressed because one or more lines are too long
2
dist/resources/vendor.min.js
vendored
2
dist/resources/vendor.min.js
vendored
@@ -19,4 +19,4 @@ function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t
|
||||
* @author John Doherty <www.johndoherty.info>
|
||||
* @license MIT
|
||||
*/
|
||||
function(e,t){"use strict";"function"!=typeof e.CustomEvent&&(e.CustomEvent=function(e,n){n=n||{bubbles:!1,cancelable:!1,detail:void 0};var A=t.createEvent("CustomEvent");return A.initCustomEvent(e,n.bubbles,n.cancelable,n.detail),A},e.CustomEvent.prototype=e.Event.prototype),t.addEventListener("touchstart",(function(e){if("true"===e.target.getAttribute("data-swipe-ignore"))return;s=e.target,i=Date.now(),n=e.touches[0].clientX,A=e.touches[0].clientY,r=0,o=0}),!1),t.addEventListener("touchmove",(function(e){if(!n||!A)return;var t=e.touches[0].clientX,i=e.touches[0].clientY;r=n-t,o=A-i}),!1),t.addEventListener("touchend",(function(e){if(s!==e.target)return;var u=parseInt(a(s,"data-swipe-threshold","20"),10),l=a(s,"data-swipe-unit","px"),c=parseInt(a(s,"data-swipe-timeout","500"),10),d=Date.now()-i,f="",p=e.changedTouches||e.touches||[];"vh"===l&&(u=Math.round(u/100*t.documentElement.clientHeight));"vw"===l&&(u=Math.round(u/100*t.documentElement.clientWidth));Math.abs(r)>Math.abs(o)?Math.abs(r)>u&&d<c&&(f=r>0?"swiped-left":"swiped-right"):Math.abs(o)>u&&d<c&&(f=o>0?"swiped-up":"swiped-down");if(""!==f){var h={dir:f.replace(/swiped-/,""),touchType:(p[0]||{}).touchType||"direct",xStart:parseInt(n,10),xEnd:parseInt((p[0]||{}).clientX||-1,10),yStart:parseInt(A,10),yEnd:parseInt((p[0]||{}).clientY||-1,10)};s.dispatchEvent(new CustomEvent("swiped",{bubbles:!0,cancelable:!0,detail:h})),s.dispatchEvent(new CustomEvent(f,{bubbles:!0,cancelable:!0,detail:h}))}n=null,A=null,i=null}),!1);var n=null,A=null,r=null,o=null,i=null,s=null;function a(e,n,A){for(;e&&e!==t.documentElement;){var r=e.getAttribute(n);if(r)return r;e=e.parentNode}return A}}(window,document),function(){"use strict";var e=Math.PI,t=Math.sin,n=Math.cos,A=Math.tan,r=Math.asin,o=Math.atan2,i=Math.acos,s=e/180,a=864e5,u=2440588,l=2451545;function c(e){return new Date((e+.5-u)*a)}function d(e){return function(e){return e.valueOf()/a-.5+u}(e)-l}var f=23.4397*s;function p(e,r){return o(t(e)*n(f)-A(r)*t(f),n(e))}function h(e,A){return r(t(A)*n(f)+n(A)*t(f)*t(e))}function g(e,r,i){return o(t(e),n(e)*t(r)-A(i)*n(r))}function m(e,A,o){return r(t(A)*t(o)+n(A)*n(o)*n(e))}function v(e,t){return s*(280.16+360.9856235*e)-t}function y(e){return s*(357.5291+.98560028*e)}function b(n){return n+s*(1.9148*t(n)+.02*t(2*n)+3e-4*t(3*n))+102.9372*s+e}function x(e){var t=b(y(e));return{dec:h(t,0),ra:p(t,0)}}var C={getPosition:function(e,t,n){var A=s*-n,r=s*t,o=d(e),i=x(o),a=v(o,A)-i.ra;return{azimuth:g(a,r,i.dec),altitude:m(a,r,i.dec)}}},E=C.times=[[-.833,"sunrise","sunset"],[-.3,"sunriseEnd","sunsetStart"],[-6,"dawn","dusk"],[-12,"nauticalDawn","nauticalDusk"],[-18,"nightEnd","night"],[6,"goldenHourEnd","goldenHour"]];C.addTime=function(e,t,n){E.push([e,t,n])};var w=9e-4;function B(t,n,A){return w+(t+n)/(2*e)+A}function S(e,n,A){return l+e+.0053*t(n)-.0069*t(2*A)}function I(e,A,r,o,s,a,u){var l=function(e,A,r){return i((t(e)-t(A)*t(r))/(n(A)*n(r)))}(e,r,o);return S(B(l,A,s),a,u)}function k(e){var A=s*(134.963+13.064993*e),r=s*(93.272+13.22935*e),o=s*(218.316+13.176396*e)+6.289*s*t(A),i=5.128*s*t(r),a=385001-20905*n(A);return{ra:p(o,i),dec:h(o,i),dist:a}}function T(e,t){return new Date(e.valueOf()+t*a/24)}C.getTimes=function(t,n,A,r){var o,i,a,u,l,f=s*-A,p=s*n,g=function(e){return-2.076*Math.sqrt(e)/60}(r=r||0),m=function(t,n){return Math.round(t-w-n/(2*e))}(d(t),f),v=B(0,f,m),x=y(v),C=b(x),k=h(C,0),T=S(v,x,C),D={solarNoon:c(T),nadir:c(T-.5)};for(o=0,i=E.length;o<i;o+=1)l=T-((u=I(((a=E[o])[0]+g)*s,f,p,k,m,x,C))-T),D[a[1]]=c(l),D[a[2]]=c(u);return D},C.getMoonPosition=function(e,r,i){var a=s*-i,u=s*r,l=d(e),c=k(l),f=v(l,a)-c.ra,p=m(f,u,c.dec),h=o(t(f),A(u)*n(c.dec)-t(c.dec)*n(f));return p+=function(e){return e<0&&(e=0),2967e-7/Math.tan(e+.00312536/(e+.08901179))}(p),{azimuth:g(f,u,c.dec),altitude:p,distance:c.dist,parallacticAngle:h}},C.getMoonIllumination=function(e){var A=d(e||new Date),r=x(A),s=k(A),a=149598e3,u=i(t(r.dec)*t(s.dec)+n(r.dec)*n(s.dec)*n(r.ra-s.ra)),l=o(a*t(u),s.dist-a*n(u)),c=o(n(r.dec)*t(r.ra-s.ra),t(r.dec)*n(s.dec)-n(r.dec)*t(s.dec)*n(r.ra-s.ra));return{fraction:(1+n(l))/2,phase:.5+.5*l*(c<0?-1:1)/Math.PI,angle:c}},C.getMoonTimes=function(e,t,n,A){var r=new Date(e);A?r.setUTCHours(0,0,0,0):r.setHours(0,0,0,0);for(var o,i,a,u,l,c,d,f,p,h,g,m,v,y=.133*s,b=C.getMoonPosition(r,t,n).altitude-y,x=1;x<=24&&(o=C.getMoonPosition(T(r,x),t,n).altitude-y,f=((l=(b+(i=C.getMoonPosition(T(r,x+1),t,n).altitude-y))/2-o)*(d=-(c=(i-b)/2)/(2*l))+c)*d+o,h=0,(p=c*c-4*l*o)>=0&&(g=d-(v=Math.sqrt(p)/(2*Math.abs(l))),m=d+v,Math.abs(g)<=1&&h++,Math.abs(m)<=1&&h++,g<-1&&(g=m)),1===h?b<0?a=x+g:u=x+g:2===h&&(a=x+(f<0?m:g),u=x+(f<0?g:m)),!a||!u);x+=2)b=i;var E={};return a&&(E.rise=T(r,a)),u&&(E.set=T(r,u)),a||u||(E[f>0?"alwaysUp":"alwaysDown"]=!0),E},"object"==typeof exports&&"undefined"!=typeof module?module.exports=C:"function"==typeof define&&define.amd?define(C):window.SunCalc=C}();
|
||||
function(e,t){"use strict";"function"!=typeof e.CustomEvent&&(e.CustomEvent=function(e,n){n=n||{bubbles:!1,cancelable:!1,detail:void 0};var A=t.createEvent("CustomEvent");return A.initCustomEvent(e,n.bubbles,n.cancelable,n.detail),A},e.CustomEvent.prototype=e.Event.prototype),t.addEventListener("touchstart",(function(e){if("true"===e.target.getAttribute("data-swipe-ignore"))return;s=e.target,i=Date.now(),n=e.touches[0].clientX,A=e.touches[0].clientY,r=0,o=0,a=e.touches.length}),!1),t.addEventListener("touchmove",(function(e){if(!n||!A)return;var t=e.touches[0].clientX,i=e.touches[0].clientY;r=n-t,o=A-i}),!1),t.addEventListener("touchend",(function(e){if(s!==e.target)return;var l=parseInt(u(s,"data-swipe-threshold","20"),10),c=u(s,"data-swipe-unit","px"),d=parseInt(u(s,"data-swipe-timeout","500"),10),f=Date.now()-i,p="",h=e.changedTouches||e.touches||[];"vh"===c&&(l=Math.round(l/100*t.documentElement.clientHeight));"vw"===c&&(l=Math.round(l/100*t.documentElement.clientWidth));Math.abs(r)>Math.abs(o)?Math.abs(r)>l&&f<d&&(p=r>0?"swiped-left":"swiped-right"):Math.abs(o)>l&&f<d&&(p=o>0?"swiped-up":"swiped-down");if(""!==p){var g={dir:p.replace(/swiped-/,""),touchType:(h[0]||{}).touchType||"direct",fingers:a,xStart:parseInt(n,10),xEnd:parseInt((h[0]||{}).clientX||-1,10),yStart:parseInt(A,10),yEnd:parseInt((h[0]||{}).clientY||-1,10)};s.dispatchEvent(new CustomEvent("swiped",{bubbles:!0,cancelable:!0,detail:g})),s.dispatchEvent(new CustomEvent(p,{bubbles:!0,cancelable:!0,detail:g}))}n=null,A=null,i=null}),!1);var n=null,A=null,r=null,o=null,i=null,s=null,a=0;function u(e,n,A){for(;e&&e!==t.documentElement;){var r=e.getAttribute(n);if(r)return r;e=e.parentNode}return A}}(window,document),function(){"use strict";var e=Math.PI,t=Math.sin,n=Math.cos,A=Math.tan,r=Math.asin,o=Math.atan2,i=Math.acos,s=e/180,a=864e5,u=2440588,l=2451545;function c(e){return new Date((e+.5-u)*a)}function d(e){return function(e){return e.valueOf()/a-.5+u}(e)-l}var f=23.4397*s;function p(e,r){return o(t(e)*n(f)-A(r)*t(f),n(e))}function h(e,A){return r(t(A)*n(f)+n(A)*t(f)*t(e))}function g(e,r,i){return o(t(e),n(e)*t(r)-A(i)*n(r))}function m(e,A,o){return r(t(A)*t(o)+n(A)*n(o)*n(e))}function v(e,t){return s*(280.16+360.9856235*e)-t}function y(e){return s*(357.5291+.98560028*e)}function b(n){return n+s*(1.9148*t(n)+.02*t(2*n)+3e-4*t(3*n))+102.9372*s+e}function x(e){var t=b(y(e));return{dec:h(t,0),ra:p(t,0)}}var C={getPosition:function(e,t,n){var A=s*-n,r=s*t,o=d(e),i=x(o),a=v(o,A)-i.ra;return{azimuth:g(a,r,i.dec),altitude:m(a,r,i.dec)}}},E=C.times=[[-.833,"sunrise","sunset"],[-.3,"sunriseEnd","sunsetStart"],[-6,"dawn","dusk"],[-12,"nauticalDawn","nauticalDusk"],[-18,"nightEnd","night"],[6,"goldenHourEnd","goldenHour"]];C.addTime=function(e,t,n){E.push([e,t,n])};var w=9e-4;function B(t,n,A){return w+(t+n)/(2*e)+A}function S(e,n,A){return l+e+.0053*t(n)-.0069*t(2*A)}function I(e,A,r,o,s,a,u){var l=function(e,A,r){return i((t(e)-t(A)*t(r))/(n(A)*n(r)))}(e,r,o);return S(B(l,A,s),a,u)}function k(e){var A=s*(134.963+13.064993*e),r=s*(93.272+13.22935*e),o=s*(218.316+13.176396*e)+6.289*s*t(A),i=5.128*s*t(r),a=385001-20905*n(A);return{ra:p(o,i),dec:h(o,i),dist:a}}function T(e,t){return new Date(e.valueOf()+t*a/24)}C.getTimes=function(t,n,A,r){var o,i,a,u,l,f=s*-A,p=s*n,g=function(e){return-2.076*Math.sqrt(e)/60}(r=r||0),m=function(t,n){return Math.round(t-w-n/(2*e))}(d(t),f),v=B(0,f,m),x=y(v),C=b(x),k=h(C,0),T=S(v,x,C),D={solarNoon:c(T),nadir:c(T-.5)};for(o=0,i=E.length;o<i;o+=1)l=T-((u=I(((a=E[o])[0]+g)*s,f,p,k,m,x,C))-T),D[a[1]]=c(l),D[a[2]]=c(u);return D},C.getMoonPosition=function(e,r,i){var a=s*-i,u=s*r,l=d(e),c=k(l),f=v(l,a)-c.ra,p=m(f,u,c.dec),h=o(t(f),A(u)*n(c.dec)-t(c.dec)*n(f));return p+=function(e){return e<0&&(e=0),2967e-7/Math.tan(e+.00312536/(e+.08901179))}(p),{azimuth:g(f,u,c.dec),altitude:p,distance:c.dist,parallacticAngle:h}},C.getMoonIllumination=function(e){var A=d(e||new Date),r=x(A),s=k(A),a=149598e3,u=i(t(r.dec)*t(s.dec)+n(r.dec)*n(s.dec)*n(r.ra-s.ra)),l=o(a*t(u),s.dist-a*n(u)),c=o(n(r.dec)*t(r.ra-s.ra),t(r.dec)*n(s.dec)-n(r.dec)*t(s.dec)*n(r.ra-s.ra));return{fraction:(1+n(l))/2,phase:.5+.5*l*(c<0?-1:1)/Math.PI,angle:c}},C.getMoonTimes=function(e,t,n,A){var r=new Date(e);A?r.setUTCHours(0,0,0,0):r.setHours(0,0,0,0);for(var o,i,a,u,l,c,d,f,p,h,g,m,v,y=.133*s,b=C.getMoonPosition(r,t,n).altitude-y,x=1;x<=24&&(o=C.getMoonPosition(T(r,x),t,n).altitude-y,f=((l=(b+(i=C.getMoonPosition(T(r,x+1),t,n).altitude-y))/2-o)*(d=-(c=(i-b)/2)/(2*l))+c)*d+o,h=0,(p=c*c-4*l*o)>=0&&(g=d-(v=Math.sqrt(p)/(2*Math.abs(l))),m=d+v,Math.abs(g)<=1&&h++,Math.abs(m)<=1&&h++,g<-1&&(g=m)),1===h?b<0?a=x+g:u=x+g:2===h&&(a=x+(f<0?m:g),u=x+(f<0?g:m)),!a||!u);x+=2)b=i;var E={};return a&&(E.rise=T(r,a)),u&&(E.set=T(r,u)),a||u||(E[f>0?"alwaysUp":"alwaysDown"]=!0),E},"object"==typeof exports&&"undefined"!=typeof module?module.exports=C:"function"==typeof define&&define.amd?define(C):window.SunCalc=C}();
|
||||
2
dist/resources/ws.min.css
vendored
2
dist/resources/ws.min.css
vendored
File diff suppressed because one or more lines are too long
2
dist/resources/ws.min.js
vendored
2
dist/resources/ws.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,23 +1,26 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
const gulp = require('gulp');
|
||||
const concat = require('gulp-concat');
|
||||
const terser = require('gulp-terser');
|
||||
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');
|
||||
const webpack = require('webpack-stream');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const path = require('path');
|
||||
|
||||
const clean = () => del(['./dist**']);
|
||||
import {
|
||||
src, dest, series, parallel,
|
||||
} from 'gulp';
|
||||
import concat from 'gulp-concat';
|
||||
import terser from 'gulp-terser';
|
||||
import ejs from 'gulp-ejs';
|
||||
import rename from 'gulp-rename';
|
||||
import htmlmin from 'gulp-htmlmin';
|
||||
import { deleteAsync } from 'del';
|
||||
import s3Upload from 'gulp-s3-upload';
|
||||
import webpack from 'webpack-stream';
|
||||
import TerserPlugin from 'terser-webpack-plugin';
|
||||
import { readFile } from 'fs/promises';
|
||||
|
||||
// get cloudfront
|
||||
const AWS = require('aws-sdk');
|
||||
import { CloudFrontClient, CreateInvalidationCommand } from '@aws-sdk/client-cloudfront';
|
||||
|
||||
AWS.config.update({ region: 'us-east-1' });
|
||||
const cloudfront = new AWS.CloudFront({ apiVersion: '2020-01-01' });
|
||||
const clean = () => deleteAsync(['./dist**']);
|
||||
|
||||
const cloudfront = new CloudFrontClient({ region: 'us-east-1' });
|
||||
|
||||
const RESOURCES_PATH = './dist/resources';
|
||||
|
||||
const jsSourcesData = [
|
||||
'server/scripts/data/travelcities.js',
|
||||
@@ -33,7 +36,7 @@ const webpackOptions = {
|
||||
filename: 'ws.min.js',
|
||||
},
|
||||
resolve: {
|
||||
roots: [path.resolve(__dirname, './')],
|
||||
roots: ['./'],
|
||||
},
|
||||
optimization: {
|
||||
minimize: true,
|
||||
@@ -51,10 +54,10 @@ const webpackOptions = {
|
||||
},
|
||||
};
|
||||
|
||||
gulp.task('compress_js_data', () => gulp.src(jsSourcesData)
|
||||
const compressJsData = () => src(jsSourcesData)
|
||||
.pipe(concat('data.min.js'))
|
||||
.pipe(terser())
|
||||
.pipe(gulp.dest('./dist/resources')));
|
||||
.pipe(dest(RESOURCES_PATH));
|
||||
|
||||
const jsVendorSources = [
|
||||
'server/scripts/vendor/auto/jquery.js',
|
||||
@@ -64,10 +67,10 @@ const jsVendorSources = [
|
||||
'server/scripts/vendor/auto/suncalc.js',
|
||||
];
|
||||
|
||||
gulp.task('compress_js_vendor', () => gulp.src(jsVendorSources)
|
||||
const compressJsVendor = () => src(jsVendorSources)
|
||||
.pipe(concat('vendor.min.js'))
|
||||
.pipe(terser())
|
||||
.pipe(gulp.dest('./dist/resources')));
|
||||
.pipe(dest(RESOURCES_PATH));
|
||||
|
||||
const mjsSources = [
|
||||
'server/scripts/modules/currentweatherscroll.mjs',
|
||||
@@ -87,39 +90,40 @@ const mjsSources = [
|
||||
'server/scripts/index.mjs',
|
||||
];
|
||||
|
||||
gulp.task('build_js', () => gulp.src(mjsSources)
|
||||
const buildJs = () => src(mjsSources)
|
||||
.pipe(webpack(webpackOptions))
|
||||
.pipe(gulp.dest('dist/resources')));
|
||||
.pipe(dest(RESOURCES_PATH));
|
||||
|
||||
const cssSources = [
|
||||
'server/styles/main.css',
|
||||
];
|
||||
gulp.task('copy_css', () => gulp.src(cssSources)
|
||||
const copyCss = () => src(cssSources)
|
||||
.pipe(concat('ws.min.css'))
|
||||
.pipe(gulp.dest('./dist/resources')));
|
||||
.pipe(dest(RESOURCES_PATH));
|
||||
|
||||
const htmlSources = [
|
||||
'views/*.ejs',
|
||||
];
|
||||
gulp.task('compress_html', () => {
|
||||
// eslint-disable-next-line global-require
|
||||
const { version } = require('../package.json');
|
||||
return gulp.src(htmlSources)
|
||||
const compressHtml = async () => {
|
||||
const packageJson = await readFile('package.json');
|
||||
const { version } = JSON.parse(packageJson);
|
||||
|
||||
return src(htmlSources)
|
||||
.pipe(ejs({
|
||||
production: version,
|
||||
version,
|
||||
}))
|
||||
.pipe(rename({ extname: '.html' }))
|
||||
.pipe(htmlmin({ collapseWhitespace: true }))
|
||||
.pipe(gulp.dest('./dist'));
|
||||
});
|
||||
.pipe(dest('./dist'));
|
||||
};
|
||||
|
||||
const otherFiles = [
|
||||
'server/robots.txt',
|
||||
'server/manifest.json',
|
||||
];
|
||||
gulp.task('copy_other_files', () => gulp.src(otherFiles, { base: 'server/' })
|
||||
.pipe(gulp.dest('./dist')));
|
||||
const copyOtherFiles = () => src(otherFiles, { base: 'server/' })
|
||||
.pipe(dest('./dist'));
|
||||
|
||||
const s3 = s3Upload({
|
||||
useIAM: true,
|
||||
@@ -130,7 +134,7 @@ const uploadSources = [
|
||||
'dist/**',
|
||||
'!dist/**/*.map',
|
||||
];
|
||||
gulp.task('upload', () => gulp.src(uploadSources, { base: './dist' })
|
||||
const upload = () => src(uploadSources, { base: './dist' })
|
||||
.pipe(s3({
|
||||
Bucket: 'weatherstar',
|
||||
StorageClass: 'STANDARD',
|
||||
@@ -140,21 +144,21 @@ gulp.task('upload', () => gulp.src(uploadSources, { base: './dist' })
|
||||
return 'max-age=2592000'; // 1 month
|
||||
},
|
||||
},
|
||||
})));
|
||||
}));
|
||||
|
||||
const imageSources = [
|
||||
'server/fonts/**',
|
||||
'server/images/**',
|
||||
];
|
||||
gulp.task('upload_images', () => gulp.src(imageSources, { base: './server' })
|
||||
const uploadImages = () => src(imageSources, { base: './server', encoding: false })
|
||||
.pipe(
|
||||
s3({
|
||||
Bucket: 'weatherstar',
|
||||
StorageClass: 'STANDARD',
|
||||
}),
|
||||
));
|
||||
);
|
||||
|
||||
gulp.task('invalidate', async () => cloudfront.createInvalidation({
|
||||
const invalidate = () => cloudfront.send(new CreateInvalidationCommand({
|
||||
DistributionId: 'E9171A4KV8KCW',
|
||||
InvalidationBatch: {
|
||||
CallerReference: (new Date()).toLocaleString(),
|
||||
@@ -163,10 +167,12 @@ gulp.task('invalidate', async () => cloudfront.createInvalidation({
|
||||
Items: ['/*'],
|
||||
},
|
||||
},
|
||||
}).promise());
|
||||
}));
|
||||
|
||||
gulp.task('build-dist', gulp.series(clean, gulp.parallel('build_js', 'compress_js_data', 'compress_js_vendor', 'copy_css', 'compress_html', 'copy_other_files')));
|
||||
const buildDist = series(clean, parallel(buildJs, compressJsData, compressJsVendor, copyCss, compressHtml, copyOtherFiles));
|
||||
|
||||
// upload_images could be in parallel with upload, but _images logs a lot and has little changes
|
||||
// by running upload last the majority of the changes will be at the bottom of the log for easy viewing
|
||||
module.exports = gulp.series('build-dist', 'upload_images', 'upload', 'invalidate');
|
||||
const publishFrontend = series(buildDist, uploadImages, upload, invalidate);
|
||||
|
||||
export default publishFrontend;
|
||||
@@ -1,12 +1,9 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
const gulp = require('gulp');
|
||||
const del = require('del');
|
||||
const rename = require('gulp-rename');
|
||||
import { src, series, dest } from 'gulp';
|
||||
import { deleteAsync } from 'del';
|
||||
import rename from 'gulp-rename';
|
||||
|
||||
const clean = (cb) => {
|
||||
del(['./server/scripts/vendor/auto/**']);
|
||||
cb();
|
||||
};
|
||||
const clean = () => deleteAsync(['./server/scripts/vendor/auto/**']);
|
||||
|
||||
const vendorFiles = [
|
||||
'./node_modules/luxon/build/es6/luxon.js',
|
||||
@@ -17,13 +14,15 @@ const vendorFiles = [
|
||||
'./node_modules/swiped-events/src/swiped-events.js',
|
||||
];
|
||||
|
||||
const copy = () => gulp.src(vendorFiles)
|
||||
const copy = () => src(vendorFiles)
|
||||
.pipe(rename((path) => {
|
||||
path.dirname = path.dirname.toLowerCase();
|
||||
path.basename = path.basename.toLowerCase();
|
||||
path.extname = path.extname.toLowerCase();
|
||||
if (path.basename === 'luxon') path.extname = '.mjs';
|
||||
}))
|
||||
.pipe(gulp.dest('./server/scripts/vendor/auto'));
|
||||
.pipe(dest('./server/scripts/vendor/auto'));
|
||||
|
||||
module.exports = gulp.series(clean, copy);
|
||||
const updateVendor = series(clean, copy);
|
||||
|
||||
export default updateVendor;
|
||||
@@ -1,4 +0,0 @@
|
||||
const gulp = require('gulp');
|
||||
|
||||
gulp.task('update-vendor', require('./gulp/update-vendor'));
|
||||
gulp.task('publish-frontend', require('./gulp/publish-frontend'));
|
||||
7
gulpfile.mjs
Normal file
7
gulpfile.mjs
Normal file
@@ -0,0 +1,7 @@
|
||||
import updateVendor from './gulp/update-vendor.mjs';
|
||||
import publishFrontend from './gulp/publish-frontend.mjs';
|
||||
|
||||
export {
|
||||
updateVendor,
|
||||
publishFrontend,
|
||||
};
|
||||
8372
package-lock.json
generated
8372
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
20
package.json
20
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ws4kp",
|
||||
"version": "5.11.2",
|
||||
"version": "5.11.7",
|
||||
"description": "Welcome to the WeatherStar 4000+ project page!",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@@ -20,20 +20,19 @@
|
||||
},
|
||||
"homepage": "https://github.com/netbymatt/ws4kp#readme",
|
||||
"devDependencies": {
|
||||
"del": "^6.0.0",
|
||||
"del": "^7.1.0",
|
||||
"ejs": "^3.1.5",
|
||||
"eslint": "^8.21.0",
|
||||
"eslint": "^8.2.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-sonarjs": "^0.21.0",
|
||||
"eslint-plugin-unicorn": "^46.0.0",
|
||||
"eslint-plugin-import": "^2.10.0",
|
||||
"eslint-plugin-sonarjs": "^0.25.1",
|
||||
"eslint-plugin-unicorn": "^52.0.0",
|
||||
"express": "^4.17.1",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp": "^5.0.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-sass": "^5.1.0",
|
||||
"gulp-terser": "^2.0.0",
|
||||
"jquery": "^3.6.0",
|
||||
@@ -45,5 +44,10 @@
|
||||
"swiped-events": "^1.1.4",
|
||||
"terser-webpack-plugin": "^5.3.6",
|
||||
"webpack-stream": "^7.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-cloudfront": "^3.609.0",
|
||||
"gulp-awspublish": "^8.0.0",
|
||||
"gulp-s3-upload": "^1.7.3"
|
||||
}
|
||||
}
|
||||
|
||||
14
server/scripts/custom.sample.js
Normal file
14
server/scripts/custom.sample.js
Normal file
@@ -0,0 +1,14 @@
|
||||
// this file is loaded by the main html page (when renamed to custom.js)
|
||||
// it is intended to allow for customizations that do not get published back to the git repo
|
||||
// for example, changing the logo
|
||||
|
||||
// start running after all content is loaded
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// get all of the logo images
|
||||
const logos = document.querySelectorAll('.logo img');
|
||||
// loop through each logo
|
||||
logos.forEach((elem) => {
|
||||
// change the source
|
||||
elem.src = 'my-custom-logo.gif';
|
||||
});
|
||||
});
|
||||
@@ -43,9 +43,12 @@ const init = () => {
|
||||
btnGetGps.addEventListener('click', btnGetGpsClick);
|
||||
if (!navigator.geolocation) btnGetGps.style.display = 'none';
|
||||
|
||||
document.querySelector('#divTwc').addEventListener('click', () => {
|
||||
document.querySelector('#divTwc').addEventListener('mousemove', () => {
|
||||
if (document.fullscreenElement) updateFullScreenNavigate();
|
||||
});
|
||||
// local change detection when exiting full screen via ESC key (or other non button click methods)
|
||||
window.addEventListener('resize', fullScreenResizeCheck);
|
||||
fullScreenResizeCheck.wasFull = false;
|
||||
|
||||
document.querySelector(TXT_ADDRESS_SELECTOR).addEventListener('keydown', (key) => { if (key.code === 'Enter') formSubmit(); });
|
||||
document.querySelector('#btnGetLatLng').addEventListener('click', () => formSubmit());
|
||||
@@ -189,7 +192,7 @@ const enterFullScreen = () => {
|
||||
|
||||
if (requestMethod) {
|
||||
// Native full screen.
|
||||
requestMethod.call(element, { navigationUI: 'hide' }); // https://bugs.chromium.org/p/chromium/issues/detail?id=933436#c7
|
||||
requestMethod.call(element, { navigationUI: 'hide' });
|
||||
} else {
|
||||
// iOS doesn't support FullScreen API.
|
||||
window.scrollTo(0, 0);
|
||||
@@ -217,10 +220,15 @@ const exitFullscreen = () => {
|
||||
document.msExitFullscreen();
|
||||
}
|
||||
resize();
|
||||
exitFullScreenVisibilityChanges();
|
||||
};
|
||||
|
||||
const exitFullScreenVisibilityChanges = () => {
|
||||
// change hover text and image
|
||||
const img = document.querySelector(TOGGLE_FULL_SCREEN_SELECTOR);
|
||||
img.src = 'images/nav/ic_fullscreen_white_24dp_2x.png';
|
||||
img.title = 'Enter fullscreen';
|
||||
document.querySelector('#divTwc').classList.remove('no-cursor');
|
||||
const divTwcBottom = document.querySelector('#divTwcBottom');
|
||||
divTwcBottom.classList.remove('hidden');
|
||||
divTwcBottom.classList.add('visible');
|
||||
@@ -228,7 +236,6 @@ const exitFullscreen = () => {
|
||||
|
||||
const btnNavigateMenuClick = () => {
|
||||
postMessage('navButton', 'menu');
|
||||
updateFullScreenNavigate();
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -261,21 +268,18 @@ const swipeCallBack = (direction) => {
|
||||
const btnNavigateRefreshClick = () => {
|
||||
resetStatuses();
|
||||
loadData();
|
||||
updateFullScreenNavigate();
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const btnNavigateNextClick = () => {
|
||||
postMessage('navButton', 'next');
|
||||
updateFullScreenNavigate();
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const btnNavigatePreviousClick = () => {
|
||||
postMessage('navButton', 'previous');
|
||||
updateFullScreenNavigate();
|
||||
|
||||
return false;
|
||||
};
|
||||
@@ -287,6 +291,7 @@ const updateFullScreenNavigate = () => {
|
||||
const divTwcBottom = document.querySelector('#divTwcBottom');
|
||||
divTwcBottom.classList.remove('hidden');
|
||||
divTwcBottom.classList.add('visible');
|
||||
document.querySelector('#divTwc').classList.remove('no-cursor');
|
||||
|
||||
if (navigateFadeIntervalId) {
|
||||
clearTimeout(navigateFadeIntervalId);
|
||||
@@ -297,6 +302,7 @@ const updateFullScreenNavigate = () => {
|
||||
if (document.fullscreenElement) {
|
||||
divTwcBottom.classList.remove('visible');
|
||||
divTwcBottom.classList.add('hidden');
|
||||
document.querySelector('#divTwc').classList.add('no-cursor');
|
||||
}
|
||||
}, 2000);
|
||||
};
|
||||
@@ -348,7 +354,6 @@ const documentKeydown = (e) => {
|
||||
|
||||
const btnNavigatePlayClick = () => {
|
||||
postMessage('navButton', 'playToggle');
|
||||
updateFullScreenNavigate();
|
||||
|
||||
return false;
|
||||
};
|
||||
@@ -393,3 +398,18 @@ const btnGetGpsClick = async () => {
|
||||
txtAddress.value = `${location.city}, ${location.state}`;
|
||||
});
|
||||
};
|
||||
|
||||
// check for change in full screen triggered by browser and run local functions
|
||||
const fullScreenResizeCheck = () => {
|
||||
if (fullScreenResizeCheck.wasFull && !document.fullscreenElement) {
|
||||
// leaving full screen
|
||||
exitFullScreenVisibilityChanges();
|
||||
}
|
||||
if (!fullScreenResizeCheck.wasFull && document.fullscreenElement) {
|
||||
// entering full screen
|
||||
// can't do much here because a UI interaction is required to change the full screen div element
|
||||
}
|
||||
|
||||
// store state of fullscreen element for next change detection
|
||||
fullScreenResizeCheck.wasFull = !!document.fullscreenElement;
|
||||
};
|
||||
|
||||
@@ -7,7 +7,13 @@ const specialMappings = {
|
||||
|
||||
const init = () => {
|
||||
// add action to existing link
|
||||
document.querySelector('#share-link').addEventListener('click', createLink);
|
||||
const shareLink = document.querySelector('#share-link');
|
||||
shareLink.addEventListener('click', createLink);
|
||||
|
||||
// if navigator.clipboard does not exist, change text
|
||||
if (!navigator?.clipboard) {
|
||||
shareLink.textContent = 'Get Permalink';
|
||||
}
|
||||
};
|
||||
|
||||
const createLink = async (e) => {
|
||||
@@ -33,10 +39,23 @@ const createLink = async (e) => {
|
||||
|
||||
const url = new URL(`?${queryString}`, document.location.href);
|
||||
|
||||
// send to proper function based on availability of clipboard
|
||||
if (navigator?.clipboard) {
|
||||
copyToClipboard(url);
|
||||
} else {
|
||||
writeLinkToPage(url);
|
||||
}
|
||||
};
|
||||
|
||||
const copyToClipboard = async (url) => {
|
||||
try {
|
||||
// write to clipboard
|
||||
await navigator.clipboard.writeText(url.toString());
|
||||
// alert user
|
||||
const confirmSpan = document.querySelector('#share-link-copied');
|
||||
confirmSpan.style.display = 'inline';
|
||||
|
||||
// hide confirm text after 5 seconds
|
||||
setTimeout(() => {
|
||||
confirmSpan.style.display = 'none';
|
||||
}, 5000);
|
||||
@@ -45,6 +64,18 @@ const createLink = async (e) => {
|
||||
}
|
||||
};
|
||||
|
||||
const writeLinkToPage = (url) => {
|
||||
// get elements
|
||||
const shareLinkInstructions = document.querySelector('#share-link-instructions');
|
||||
const shareLinkUrl = shareLinkInstructions.querySelector('#share-link-url');
|
||||
// populate url and display
|
||||
shareLinkUrl.value = url;
|
||||
shareLinkInstructions.style.display = 'inline';
|
||||
// highlight for convenience
|
||||
shareLinkUrl.focus();
|
||||
shareLinkUrl.select();
|
||||
};
|
||||
|
||||
const parseQueryString = () => {
|
||||
// return memoized result
|
||||
if (parseQueryString.params) return parseQueryString.params;
|
||||
|
||||
@@ -21,7 +21,7 @@ const fetchAsync = async (_url, responseType, _params = {}) => {
|
||||
if (params.cors === true) corsUrl = rewriteUrl(_url);
|
||||
const url = new URL(corsUrl, `${window.location.origin}/`);
|
||||
// match the security protocol when not on localhost
|
||||
url.protocol = window.location.hostname === 'localhost' ? url.protocol : window.location.protocol;
|
||||
// url.protocol = window.location.hostname === 'localhost' ? url.protocol : window.location.protocol;
|
||||
// add parameters if necessary
|
||||
if (params.data) {
|
||||
Object.keys(params.data).forEach((key) => {
|
||||
|
||||
@@ -28,7 +28,7 @@ class Setting {
|
||||
}
|
||||
|
||||
// call the change function on startup
|
||||
this.changeAction(this.myValue);
|
||||
this.checkboxChange({ target: { checked: this.myValue } });
|
||||
}
|
||||
|
||||
generateCheckbox() {
|
||||
|
||||
@@ -169,7 +169,7 @@ class WeatherDisplay {
|
||||
// auto clock refresh
|
||||
if (!this.dateTimeInterval) {
|
||||
// only draw if canvas is active to conserve battery
|
||||
setInterval(() => this.active && this.drawCurrentDateTime(), 100);
|
||||
this.dateTimeInterval = setInterval(() => this.active && this.drawCurrentDateTime(), 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
server/scripts/vendor/auto/luxon.js.map
vendored
2
server/scripts/vendor/auto/luxon.js.map
vendored
File diff suppressed because one or more lines are too long
784
server/scripts/vendor/auto/luxon.mjs
vendored
784
server/scripts/vendor/auto/luxon.mjs
vendored
File diff suppressed because it is too large
Load Diff
4
server/scripts/vendor/auto/swiped-events.js
vendored
4
server/scripts/vendor/auto/swiped-events.js
vendored
@@ -35,6 +35,7 @@
|
||||
var yDiff = null;
|
||||
var timeDown = null;
|
||||
var startEl = null;
|
||||
var touchCount = 0;
|
||||
|
||||
/**
|
||||
* Fires swiped event if swipe detected on touchend
|
||||
@@ -84,6 +85,7 @@
|
||||
var eventData = {
|
||||
dir: eventType.replace(/swiped-/, ''),
|
||||
touchType: (changedTouches[0] || {}).touchType || 'direct',
|
||||
fingers: touchCount, // Number of fingers used
|
||||
xStart: parseInt(xDown, 10),
|
||||
xEnd: parseInt((changedTouches[0] || {}).clientX || -1, 10),
|
||||
yStart: parseInt(yDown, 10),
|
||||
@@ -102,7 +104,6 @@
|
||||
yDown = null;
|
||||
timeDown = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records current location on touchstart event
|
||||
* @param {object} e - browser event object
|
||||
@@ -120,6 +121,7 @@
|
||||
yDown = e.touches[0].clientY;
|
||||
xDiff = 0;
|
||||
yDiff = 0;
|
||||
touchCount = e.touches.length;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -353,7 +353,8 @@ body {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
#enabledDisplays, #settings {
|
||||
#enabledDisplays,
|
||||
#settings {
|
||||
margin-bottom: 15px;
|
||||
@include u.status-colors();
|
||||
|
||||
@@ -411,7 +412,12 @@ body {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
|
||||
&.no-cursor {
|
||||
cursor: none;
|
||||
}
|
||||
}
|
||||
|
||||
.kiosk #divTwc {
|
||||
justify-content: unset;
|
||||
}
|
||||
@@ -447,10 +453,10 @@ body {
|
||||
.visible {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition: opacity 1s linear;
|
||||
transition: opacity 0.1s linear;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
#divTwc:fullscreen .hidden {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: visibility 0s 1s, opacity 1s linear
|
||||
@@ -728,7 +734,12 @@ body {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#share-link-instructions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.kiosk {
|
||||
|
||||
#divQuery,
|
||||
>.info,
|
||||
>.heading,
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<script type="module" src="scripts/modules/radar.mjs"></script>
|
||||
<script type="module" src="scripts/modules/settings.mjs"></script>
|
||||
<script type="module" src="scripts/index.mjs"></script>
|
||||
|
||||
<script type="text/javascript" src="scripts/custom.js"></script>
|
||||
<!-- data -->
|
||||
<script type="text/javascript" src="scripts/data/travelcities.js"></script>
|
||||
<script type="text/javascript" src="scripts/data/regionalcities.js"></script>
|
||||
@@ -151,10 +151,16 @@
|
||||
<div id='settings'>
|
||||
</div>
|
||||
|
||||
<div class='heading'>Sharing</div>
|
||||
<div class='info'>
|
||||
<a href='' id='share-link'>Copy Permalink</a> <span id="share-link-copied">Link copied to clipboard!</span>
|
||||
<div id="share-link-instructions">
|
||||
Copy this long URL:
|
||||
<input type='text' id="share-link-url"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='heading'>Forecast Information</div>
|
||||
<div id="divInfo">
|
||||
Location: <span id="spanCity"></span> <span id="spanState"></span><br />
|
||||
Station Id: <span id="spanStationId"></span><br />
|
||||
|
||||
Reference in New Issue
Block a user