mirror of
https://github.com/netbymatt/ws4kp.git
synced 2026-04-14 23:59:30 -07:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6cd75ab42 | ||
|
|
934a489340 | ||
|
|
c5f5c101f9 | ||
|
|
933367974f | ||
|
|
0e67eb22dc | ||
|
|
9df6f6888f | ||
|
|
04cc5d4252 | ||
|
|
543d3f5196 | ||
|
|
78ceba9c19 | ||
|
|
763d42061e | ||
|
|
318c55b92d | ||
|
|
84d39101e5 | ||
|
|
fd0e42aa67 | ||
|
|
7be20490e8 | ||
|
|
20b4d22115 | ||
|
|
af4ba1b881 | ||
|
|
9f9dafa30c | ||
|
|
227a959e6a | ||
|
|
63703d1fff | ||
|
|
e87290ed5e | ||
|
|
f4f3720793 | ||
|
|
535b1072d8 | ||
|
|
6d4ec8b958 | ||
|
|
5d0f41f207 | ||
|
|
14367cf71a | ||
|
|
f158b8a138 | ||
|
|
240cc416b2 | ||
|
|
eb69df8b80 | ||
|
|
941bcacfad | ||
|
|
9aa877d6fb | ||
|
|
5d00cf4608 | ||
|
|
53ad8eb317 | ||
|
|
81561f75a1 | ||
|
|
c414b88067 | ||
|
|
0808f1ac0e | ||
|
|
022a10be08 |
13
.github/workflows/build-docker.yaml
vendored
13
.github/workflows/build-docker.yaml
vendored
@@ -10,19 +10,22 @@ jobs:
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- id: short-sha
|
||||
uses: benjlevesque/short-sha@v1.2
|
||||
uses: actions/checkout@v4
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/netbymatt/ws4kp
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=raw,priority=1000,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
|
||||
type=ref,event=branch
|
||||
${{ steps.short-sha.outputs.sha }}
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=sha
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Buildx
|
||||
@@ -44,4 +47,4 @@ jobs:
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -18,4 +18,8 @@
|
||||
"**/compiled.css": true,
|
||||
"**/*.min.js": true,
|
||||
},
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
}
|
||||
}
|
||||
17
README.md
17
README.md
@@ -77,15 +77,28 @@ I've made several changes to this Weather Star 4000 simulation compared to the o
|
||||
* 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.
|
||||
* "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" (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.
|
||||
|
||||
It's also possible to enter kiosk mode using a permalink. First generate a [Permalink](#sharing-a-permalink-bookmarking), then to the end of it add `&kiosk=true`. Opening this link will load all of the selected displays included in the Permalink, enter kiosk mode immediately upon loading and start playing the forecast.
|
||||
|
||||
## Wish list
|
||||
|
||||
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
@@ -14,10 +14,11 @@ const path = require('path');
|
||||
const clean = () => del(['./dist**']);
|
||||
|
||||
// get cloudfront
|
||||
const AWS = require('aws-sdk');
|
||||
const { CloudFrontClient, CreateInvalidationCommand } = require('@aws-sdk/client-cloudfront');
|
||||
|
||||
AWS.config.update({ region: 'us-east-1' });
|
||||
const cloudfront = new AWS.CloudFront({ apiVersion: '2020-01-01' });
|
||||
const cloudfront = new CloudFrontClient({ region: 'us-east-1' });
|
||||
|
||||
const RESOURCES_PATH = './dist/resources';
|
||||
|
||||
const jsSourcesData = [
|
||||
'server/scripts/data/travelcities.js',
|
||||
@@ -54,7 +55,7 @@ const webpackOptions = {
|
||||
gulp.task('compress_js_data', () => gulp.src(jsSourcesData)
|
||||
.pipe(concat('data.min.js'))
|
||||
.pipe(terser())
|
||||
.pipe(gulp.dest('./dist/resources')));
|
||||
.pipe(gulp.dest(RESOURCES_PATH)));
|
||||
|
||||
const jsVendorSources = [
|
||||
'server/scripts/vendor/auto/jquery.js',
|
||||
@@ -67,7 +68,7 @@ const jsVendorSources = [
|
||||
gulp.task('compress_js_vendor', () => gulp.src(jsVendorSources)
|
||||
.pipe(concat('vendor.min.js'))
|
||||
.pipe(terser())
|
||||
.pipe(gulp.dest('./dist/resources')));
|
||||
.pipe(gulp.dest(RESOURCES_PATH)));
|
||||
|
||||
const mjsSources = [
|
||||
'server/scripts/modules/currentweatherscroll.mjs',
|
||||
@@ -89,14 +90,14 @@ const mjsSources = [
|
||||
|
||||
gulp.task('build_js', () => gulp.src(mjsSources)
|
||||
.pipe(webpack(webpackOptions))
|
||||
.pipe(gulp.dest('dist/resources')));
|
||||
.pipe(gulp.dest(RESOURCES_PATH)));
|
||||
|
||||
const cssSources = [
|
||||
'server/styles/main.css',
|
||||
];
|
||||
gulp.task('copy_css', () => gulp.src(cssSources)
|
||||
.pipe(concat('ws.min.css'))
|
||||
.pipe(gulp.dest('./dist/resources')));
|
||||
.pipe(gulp.dest(RESOURCES_PATH)));
|
||||
|
||||
const htmlSources = [
|
||||
'views/*.ejs',
|
||||
@@ -146,7 +147,7 @@ const imageSources = [
|
||||
'server/fonts/**',
|
||||
'server/images/**',
|
||||
];
|
||||
gulp.task('upload_images', () => gulp.src(imageSources, { base: './server' })
|
||||
gulp.task('upload_images', () => gulp.src(imageSources, { base: './server', encoding: false })
|
||||
.pipe(
|
||||
s3({
|
||||
Bucket: 'weatherstar',
|
||||
@@ -154,7 +155,7 @@ gulp.task('upload_images', () => gulp.src(imageSources, { base: './server' })
|
||||
}),
|
||||
));
|
||||
|
||||
gulp.task('invalidate', async () => cloudfront.createInvalidation({
|
||||
gulp.task('invalidate', async () => cloudfront.send(new CreateInvalidationCommand({
|
||||
DistributionId: 'E9171A4KV8KCW',
|
||||
InvalidationBatch: {
|
||||
CallerReference: (new Date()).toLocaleString(),
|
||||
@@ -163,8 +164,10 @@ gulp.task('invalidate', async () => cloudfront.createInvalidation({
|
||||
Items: ['/*'],
|
||||
},
|
||||
},
|
||||
}).promise());
|
||||
}).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')));
|
||||
|
||||
// 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(clean, gulp.parallel('build_js', 'compress_js_data', 'compress_js_vendor', 'copy_css', 'compress_html', 'copy_other_files'), 'upload_images', 'upload', 'invalidate');
|
||||
module.exports = gulp.series('build-dist', 'upload_images', 'upload', 'invalidate');
|
||||
|
||||
8005
package-lock.json
generated
8005
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
18
package.json
18
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ws4kp",
|
||||
"version": "5.10.0",
|
||||
"version": "5.11.6",
|
||||
"description": "Welcome to the WeatherStar 4000+ project page!",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@@ -22,18 +22,17 @@
|
||||
"devDependencies": {
|
||||
"del": "^6.0.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';
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,8 @@ import {
|
||||
message as navMessage, isPlaying, resize, resetStatuses, latLonReceived, stopAutoRefreshTimer, registerRefreshData,
|
||||
} from './modules/navigation.mjs';
|
||||
import { round2 } from './modules/utils/units.mjs';
|
||||
import { parseQueryString } from './modules/share.mjs';
|
||||
import settings from './modules/settings.mjs';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
init();
|
||||
@@ -41,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());
|
||||
@@ -81,10 +86,15 @@ const init = () => {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Auto load the previous query
|
||||
const query = localStorage.getItem('latLonQuery');
|
||||
const latLon = localStorage.getItem('latLon');
|
||||
const fromGPS = localStorage.getItem('latLonFromGPS');
|
||||
// attempt to parse the url parameters
|
||||
const parsedParameters = parseQueryString();
|
||||
|
||||
const loadFromParsed = parsedParameters.latLonQuery && parsedParameters.latLon;
|
||||
|
||||
// Auto load the parsed parameters and fall back to the previous query
|
||||
const query = parsedParameters.latLonQuery ?? localStorage.getItem('latLonQuery');
|
||||
const latLon = parsedParameters.latLon ?? localStorage.getItem('latLon');
|
||||
const fromGPS = localStorage.getItem('latLonFromGPS') && !loadFromParsed;
|
||||
if (query && latLon && !fromGPS) {
|
||||
const txtAddress = document.querySelector(TXT_ADDRESS_SELECTOR);
|
||||
txtAddress.value = query;
|
||||
@@ -94,7 +104,9 @@ const init = () => {
|
||||
btnGetGpsClick();
|
||||
}
|
||||
|
||||
const play = localStorage.getItem('play');
|
||||
// if kiosk mode was set via the query string, also play immediately
|
||||
settings.kiosk.value = parsedParameters['settings-kiosk-checkbox'] === 'true';
|
||||
const play = parsedParameters['settings-kiosk-checkbox'] ?? localStorage.getItem('play');
|
||||
if (play === null || play === 'true') postMessage('navButton', 'play');
|
||||
|
||||
document.querySelector('#btnClearQuery').addEventListener('click', () => {
|
||||
@@ -176,11 +188,11 @@ const enterFullScreen = () => {
|
||||
|
||||
// Supports most browsers and their versions.
|
||||
const requestMethod = element.requestFullScreen || element.webkitRequestFullScreen
|
||||
|| element.mozRequestFullScreen || element.msRequestFullscreen;
|
||||
|| 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
|
||||
requestMethod.call(element, { navigationUI: 'hide' });
|
||||
} else {
|
||||
// iOS doesn't support FullScreen API.
|
||||
window.scrollTo(0, 0);
|
||||
@@ -208,6 +220,10 @@ 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';
|
||||
@@ -219,7 +235,6 @@ const exitFullscreen = () => {
|
||||
|
||||
const btnNavigateMenuClick = () => {
|
||||
postMessage('navButton', 'menu');
|
||||
updateFullScreenNavigate();
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -252,21 +267,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;
|
||||
};
|
||||
@@ -339,7 +351,6 @@ const documentKeydown = (e) => {
|
||||
|
||||
const btnNavigatePlayClick = () => {
|
||||
postMessage('navButton', 'playToggle');
|
||||
updateFullScreenNavigate();
|
||||
|
||||
return false;
|
||||
};
|
||||
@@ -384,3 +395,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;
|
||||
};
|
||||
|
||||
@@ -66,7 +66,7 @@ class Hazards extends WeatherDisplay {
|
||||
const lines = this.data.map((data) => {
|
||||
const fillValues = {};
|
||||
// text
|
||||
fillValues['hazard-text'] = `${data.properties.event}<br/><br/>${data.properties.description.replace('\n', '<br/><br/>')}`;
|
||||
fillValues['hazard-text'] = `${data.properties.event}<br/><br/>${data.properties.description.replaceAll('\n\n', '<br/><br/>').replaceAll('\n', ' ')}`;
|
||||
|
||||
return this.fillTemplate('hazard', fillValues);
|
||||
});
|
||||
|
||||
@@ -275,7 +275,7 @@ const resize = () => {
|
||||
const heightZoomPercent = (window.innerHeight) / 480;
|
||||
|
||||
const scale = Math.min(widthZoomPercent, heightZoomPercent);
|
||||
if (scale < 1.0 || document.fullscreenElement) {
|
||||
if (scale < 1.0 || document.fullscreenElement || settings.kiosk) {
|
||||
document.querySelector('#container').style.transform = `scale(${scale})`;
|
||||
} else {
|
||||
document.querySelector('#container').style.transform = 'unset';
|
||||
|
||||
@@ -8,7 +8,8 @@ const settings = {};
|
||||
|
||||
const init = () => {
|
||||
// create settings
|
||||
settings.wide = new Setting('wide', 'Widescreen', 'boolean', false, wideScreenChange);
|
||||
settings.wide = new Setting('wide', 'Widescreen', 'boolean', false, wideScreenChange, true);
|
||||
settings.kiosk = new Setting('kiosk', 'Kiosk', 'boolean', false, kioskChange, false);
|
||||
|
||||
// generate checkboxes
|
||||
const checkboxes = Object.values(settings).map((d) => d.generateCheckbox());
|
||||
@@ -28,4 +29,14 @@ const wideScreenChange = (value) => {
|
||||
}
|
||||
};
|
||||
|
||||
const kioskChange = (value) => {
|
||||
const body = document.querySelector('body');
|
||||
if (value) {
|
||||
body.classList.add('kiosk');
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
} else {
|
||||
body.classList.remove('kiosk');
|
||||
}
|
||||
};
|
||||
|
||||
export default settings;
|
||||
|
||||
104
server/scripts/modules/share.mjs
Normal file
104
server/scripts/modules/share.mjs
Normal file
@@ -0,0 +1,104 @@
|
||||
document.addEventListener('DOMContentLoaded', () => init());
|
||||
|
||||
// shorthand mappings for frequently used values
|
||||
const specialMappings = {
|
||||
kiosk: 'settings-kiosk-checkbox',
|
||||
};
|
||||
|
||||
const init = () => {
|
||||
// add action to existing link
|
||||
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) => {
|
||||
// cancel default event (click on hyperlink)
|
||||
e.preventDefault();
|
||||
// get all checkboxes on page
|
||||
const checkboxes = document.querySelectorAll('input[type=checkbox]');
|
||||
|
||||
// list to receive checkbox statuses
|
||||
const queryStringElements = {};
|
||||
|
||||
[...checkboxes].forEach((elem) => {
|
||||
if (elem?.id) {
|
||||
queryStringElements[elem.id] = elem?.checked ?? false;
|
||||
}
|
||||
});
|
||||
|
||||
// add the location string
|
||||
queryStringElements.latLonQuery = localStorage.getItem('latLonQuery');
|
||||
queryStringElements.latLon = localStorage.getItem('latLon');
|
||||
|
||||
const queryString = (new URLSearchParams(queryStringElements)).toString();
|
||||
|
||||
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);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
|
||||
// turn into an array of key-value pairs
|
||||
const paramsArray = [...urlSearchParams];
|
||||
|
||||
// add additional expanded keys
|
||||
paramsArray.forEach((paramPair) => {
|
||||
const expandedKey = specialMappings[paramPair[0]];
|
||||
if (expandedKey) {
|
||||
paramsArray.push([expandedKey, paramPair[1]]);
|
||||
}
|
||||
});
|
||||
|
||||
// memoize result
|
||||
parseQueryString.params = Object.fromEntries(paramsArray);
|
||||
|
||||
return parseQueryString.params;
|
||||
};
|
||||
|
||||
export {
|
||||
createLink,
|
||||
parseQueryString,
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
// rewrite some urls for local server
|
||||
const rewriteUrl = (_url) => {
|
||||
let url = _url;
|
||||
url = url.replace('https://api.weather.gov/', window.location.href);
|
||||
url = url.replace('https://www.cpc.ncep.noaa.gov/', window.location.href);
|
||||
url = url.replace('https://api.weather.gov/', `${window.location.protocol}//${window.location.host}/`);
|
||||
url = url.replace('https://www.cpc.ncep.noaa.gov/', `${window.location.protocol}//${window.location.host}/`);
|
||||
return url;
|
||||
};
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -1,24 +1,34 @@
|
||||
import { parseQueryString } from '../share.mjs';
|
||||
|
||||
const SETTINGS_KEY = 'Settings';
|
||||
|
||||
class Setting {
|
||||
constructor(shortName, name, type, defaultValue, changeAction) {
|
||||
constructor(shortName, name, type, defaultValue, changeAction, sticky) {
|
||||
// store values
|
||||
this.shortName = shortName;
|
||||
this.name = name;
|
||||
this.defaultValue = defaultValue;
|
||||
this.myValue = defaultValue;
|
||||
this.type = type;
|
||||
// a defualt blank change function is provded
|
||||
this.changeAction = changeAction ?? (() => {});
|
||||
this.sticky = sticky;
|
||||
// a default blank change function is provided
|
||||
this.changeAction = changeAction ?? (() => { });
|
||||
|
||||
// get value from url
|
||||
const urlValue = parseQueryString()?.[`settings-${shortName}-checkbox`];
|
||||
let urlState;
|
||||
if (urlValue !== undefined) {
|
||||
urlState = urlValue === 'true';
|
||||
}
|
||||
|
||||
// get existing value if present
|
||||
const storedValue = this.getFromLocalStorage();
|
||||
if (storedValue !== null) {
|
||||
const storedValue = urlState ?? this.getFromLocalStorage();
|
||||
if (sticky && storedValue !== null) {
|
||||
this.myValue = storedValue;
|
||||
}
|
||||
|
||||
// call the change function on startup
|
||||
this.changeAction(this.myValue);
|
||||
this.checkboxChange({ target: { checked: this.myValue } });
|
||||
}
|
||||
|
||||
generateCheckbox() {
|
||||
@@ -53,6 +63,7 @@ class Setting {
|
||||
}
|
||||
|
||||
storeToLocalStorage(value) {
|
||||
if (!this.sticky) return;
|
||||
const allSettingsString = localStorage?.getItem(SETTINGS_KEY) ?? '{}';
|
||||
const allSettings = JSON.parse(allSettingsString);
|
||||
allSettings[this.shortName] = value;
|
||||
@@ -86,7 +97,13 @@ class Setting {
|
||||
}
|
||||
|
||||
set value(newValue) {
|
||||
// update the state
|
||||
this.myValue = newValue;
|
||||
this.checkbox.checked = newValue;
|
||||
this.storeToLocalStorage(this.myValue);
|
||||
|
||||
// call change action
|
||||
this.changeAction(this.myValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { DateTime } from '../vendor/auto/luxon.mjs';
|
||||
import {
|
||||
msg, displayNavMessage, isPlaying, updateStatus, timeZone,
|
||||
} from './navigation.mjs';
|
||||
import { parseQueryString } from './share.mjs';
|
||||
|
||||
class WeatherDisplay {
|
||||
constructor(navId, elemId, name, defaultEnabled) {
|
||||
@@ -50,8 +51,15 @@ class WeatherDisplay {
|
||||
// no checkbox if progress
|
||||
if (this.elemId === 'progress') return false;
|
||||
|
||||
// get the saved status of the checkbox
|
||||
let savedStatus = window.localStorage.getItem(`display-enabled: ${this.elemId}`);
|
||||
// get url provided state
|
||||
const urlValue = parseQueryString()?.[`${this.elemId}-checkbox`];
|
||||
let urlState;
|
||||
if (urlValue !== undefined) {
|
||||
urlState = urlValue === 'true';
|
||||
}
|
||||
|
||||
// get the saved status of the checkbox, but defer to a value set in the url
|
||||
let savedStatus = urlState ?? window.localStorage.getItem(`display-enabled: ${this.elemId}`);
|
||||
if (savedStatus === null) savedStatus = defaultEnabled;
|
||||
this.isEnabled = !!((savedStatus === 'true' || savedStatus === true));
|
||||
|
||||
@@ -161,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
@@ -1,4 +1,5 @@
|
||||
@use 'shared/_utils'as u;
|
||||
@use 'shared/_colors'as c;
|
||||
|
||||
@font-face {
|
||||
font-family: "Star4000";
|
||||
@@ -18,6 +19,10 @@ body {
|
||||
color: lightblue;
|
||||
}
|
||||
}
|
||||
|
||||
&.kiosk {
|
||||
margin: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
#divQuery {
|
||||
@@ -301,13 +306,18 @@ body {
|
||||
background: url(../images/BackGround1_1_wide.png)
|
||||
}
|
||||
|
||||
#divTwc:fullscreen #container {
|
||||
#divTwc:fullscreen #container,
|
||||
.kiosk #divTwc #container {
|
||||
// background-image: none;
|
||||
width: unset;
|
||||
height: unset;
|
||||
transform-origin: unset;
|
||||
}
|
||||
|
||||
.kiosk #divTwc #container {
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
|
||||
#loading {
|
||||
width: 640px;
|
||||
height: 480px;
|
||||
@@ -343,7 +353,8 @@ body {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
#enabledDisplays {
|
||||
#enabledDisplays,
|
||||
#settings {
|
||||
margin-bottom: 15px;
|
||||
@include u.status-colors();
|
||||
|
||||
@@ -395,18 +406,25 @@ body {
|
||||
transform: scale(0.75);
|
||||
}
|
||||
|
||||
#divTwc:fullscreen {
|
||||
#divTwc:fullscreen,
|
||||
.kiosk #divTwc {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
#divTwc:fullscreen #display {
|
||||
.kiosk #divTwc {
|
||||
justify-content: unset;
|
||||
}
|
||||
|
||||
#divTwc:fullscreen #display,
|
||||
.kiosk #divTwc #display {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#divTwc:fullscreen #divTwcBottom {
|
||||
#divTwc:fullscreen #divTwcBottom,
|
||||
.kiosk #divTwc #divTwcBottom {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: rgb(0 0 0 / 0.5);
|
||||
@@ -416,6 +434,14 @@ body {
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
.kiosk {
|
||||
#divTwc #divTwcBottom {
|
||||
>div {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navButton {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -423,10 +449,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
|
||||
@@ -697,4 +723,26 @@ body {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#share-link-copied {
|
||||
color: c.$title-color;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#share-link-instructions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.kiosk {
|
||||
|
||||
#divQuery,
|
||||
>.info,
|
||||
>.heading,
|
||||
#enabledDisplays,
|
||||
#settings,
|
||||
#divInfo,
|
||||
#divRefresh {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -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,6 +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 />
|
||||
|
||||
@@ -53,5 +53,10 @@
|
||||
},
|
||||
"files.exclude": {},
|
||||
"files.eol": "\n",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user