Compare commits

...

27 Commits

Author SHA1 Message Date
Matt Walsh
763d42061e 5.11.4 2024-05-19 22:39:27 -05:00
Matt Walsh
318c55b92d Fix date/time update rate #45 2024-05-19 22:39:15 -05:00
Matt Walsh
84d39101e5 capture dist 2024-05-08 16:42:00 -05:00
Matt Walsh
fd0e42aa67 5.11.3 2024-05-08 16:30:21 -05:00
Matt Walsh
7be20490e8 remove localhost-specific protocol matching 2024-05-08 16:30:11 -05:00
Matt Walsh
20b4d22115 update build dependencies 2024-04-19 21:50:57 -05:00
Matt Walsh
af4ba1b881 update vendor scripts 2024-04-19 21:37:58 -05:00
Matt Walsh
9f9dafa30c add community notes close #37 2024-04-19 21:23:54 -05:00
Matt Walsh
227a959e6a capture dist 2024-04-19 21:15:07 -05:00
Matt Walsh
63703d1fff settings persist when set by querystring 2024-04-19 21:14:28 -05:00
Matt Walsh
e87290ed5e 5.11.2 2024-04-19 21:10:52 -05:00
Matt Walsh
f4f3720793 fix stickiness for settings checkboxes close #39 2024-04-19 21:10:43 -05:00
Matt Walsh
535b1072d8 5.11.1 2024-04-19 21:06:01 -05:00
Matt Walsh
6d4ec8b958 parse settings from querystring 2024-04-19 21:05:52 -05:00
Matt Walsh
5d0f41f207 capture dist 2024-04-12 16:20:38 -05:00
Matt Walsh
14367cf71a 5.11.0 2024-04-12 16:17:27 -05:00
Matt Walsh
f158b8a138 Merge branch 'share-kiosk' 2024-04-12 16:17:14 -05:00
Matt Walsh
240cc416b2 complete kiosk mode and permalink close #33 2024-04-12 16:16:01 -05:00
Matt Walsh
eb69df8b80 set display checkboxes (todo widescreen, refresh) 2024-04-12 00:03:21 -05:00
Matt Walsh
941bcacfad generate and parse querystring 2024-04-11 23:42:51 -05:00
Matt Walsh
9aa877d6fb better hazard formatting 2024-04-11 22:46:50 -05:00
Matt Walsh
5d00cf4608 better hazard formatting 2024-04-11 22:44:12 -05:00
Matt Walsh
53ad8eb317 add "share" link 2024-01-08 10:12:52 -06:00
Matt Walsh
81561f75a1 add gulp task build-dist 2024-01-08 08:49:51 -06:00
Matt Walsh
c414b88067 Merge pull request #35 from rmitchellscott/docker-version-tags
Add version tags to Docker build
2024-01-07 17:17:54 -06:00
Mitchell Scott
0808f1ac0e Update checkout action to v4, add semver git tags to Docker image tags 2024-01-04 13:17:51 -07:00
Matt Walsh
022a10be08 capture dist 2023-12-19 23:46:00 -06:00
27 changed files with 2172 additions and 4179 deletions

View File

@@ -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

View File

@@ -18,4 +18,8 @@
"**/compiled.css": true,
"**/*.min.js": true,
},
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}

View File

@@ -77,6 +77,14 @@ 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" 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.
## 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.
@@ -87,6 +95,12 @@ And the following technical fixes.
* Caching of the animated gifs, specifically after they are decompressed
## Community Notes
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)
## 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.

2
dist/index.html vendored

File diff suppressed because one or more lines are too long

View File

@@ -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}();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -165,6 +165,8 @@ gulp.task('invalidate', async () => cloudfront.createInvalidation({
},
}).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');

5282
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "ws4kp",
"version": "5.10.0",
"version": "5.11.4",
"description": "Welcome to the WeatherStar 4000+ project page!",
"main": "index.js",
"scripts": {
@@ -22,13 +22,13 @@
"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",

View File

@@ -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();
@@ -81,10 +83,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 +101,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,7 +185,7 @@ 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.

View File

@@ -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);
});

View File

@@ -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';

View File

@@ -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;

View File

@@ -0,0 +1,73 @@
document.addEventListener('DOMContentLoaded', () => init());
// shorthand mappings for frequently used values
const specialMappings = {
kiosk: 'settings-kiosk-checkbox',
};
const init = () => {
// add action to existing link
document.querySelector('#share-link').addEventListener('click', createLink);
};
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);
try {
await navigator.clipboard.writeText(url.toString());
const confirmSpan = document.querySelector('#share-link-copied');
confirmSpan.style.display = 'inline';
setTimeout(() => {
confirmSpan.style.display = 'none';
}, 5000);
} catch (error) {
console.error(error);
}
};
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,
};

View File

@@ -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;
};

View File

@@ -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) => {

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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,7 @@ body {
margin-bottom: 15px;
}
#enabledDisplays {
#enabledDisplays, #settings {
margin-bottom: 15px;
@include u.status-colors();
@@ -395,18 +405,24 @@ body {
transform: scale(0.75);
}
#divTwc:fullscreen {
#divTwc:fullscreen,
.kiosk #divTwc {
display: flex;
align-items: center;
justify-content: center;
align-content: center;
}
.kiosk #divTwc {
justify-content: unset;
}
#divTwc:fullscreen #display {
#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 +432,14 @@ body {
bottom: 0px;
}
.kiosk {
#divTwc #divTwcBottom {
>div {
display: none;
}
}
}
.navButton {
cursor: pointer;
}
@@ -697,4 +721,21 @@ body {
}
}
}
}
#share-link-copied {
color: c.$title-color;
display: none;
}
.kiosk {
#divQuery,
>.info,
>.heading,
#enabledDisplays,
#settings,
#divInfo,
#divRefresh {
display: none;
}
}

View File

@@ -151,6 +151,10 @@
<div id='settings'>
</div>
<div class='info'>
<a href='' id='share-link'>Copy Permalink</a> <span id="share-link-copied">Link copied to clipboard!</span>
</div>
<div id="divInfo">
Location: <span id="spanCity"></span> <span id="spanState"></span><br />
Station Id: <span id="spanStationId"></span><br />

View File

@@ -53,5 +53,10 @@
},
"files.exclude": {},
"files.eol": "\n",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
},
}