From 7823cee633fca2901aed4ae056bbf4cd22f67e55 Mon Sep 17 00:00:00 2001 From: William Brawner Date: Thu, 18 Aug 2022 01:32:42 +0000 Subject: [PATCH] Add shortcuts --- client/index.html | 2 +- .../{manifest.webmanifest => manifest.json} | 54 ++++++++++++- client/package.json | 2 +- client/src/index.ts | 67 ++++++++-------- client/static/css/style.css | 72 ++++++++++++++++-- client/static/icons/shortcut-pause.png | Bin 0 -> 1819 bytes client/static/icons/shortcut-play.png | Bin 0 -> 1941 bytes 7 files changed, 151 insertions(+), 46 deletions(-) rename client/{manifest.webmanifest => manifest.json} (65%) create mode 100644 client/static/icons/shortcut-pause.png create mode 100644 client/static/icons/shortcut-play.png diff --git a/client/index.html b/client/index.html index 32e3c09..a4ac1e2 100644 --- a/client/index.html +++ b/client/index.html @@ -7,7 +7,7 @@ - + diff --git a/client/manifest.webmanifest b/client/manifest.json similarity index 65% rename from client/manifest.webmanifest rename to client/manifest.json index 1bdec9e..c38b9ee 100644 --- a/client/manifest.webmanifest +++ b/client/manifest.json @@ -1,5 +1,7 @@ { "name": "Pi-helper", + "description": "A PWA to facillitate enabling/disabling a Pi-hole", + "orientation": "any", "short_name": "Pi-helper", "theme_color": "#000000", "background_color": "#000000", @@ -95,5 +97,55 @@ "type": "image/png", "purpose": "maskable" } + ], + "shortcuts": [ + { + "name": "Enable", + "description": "Enable the Pi-hole", + "url": "/?enable", + "icons": [ + { + "src": "static/icons/shortcut-play.png", + "type": "image/png", + "sizes": "96x96" + } + ] + }, + { + "name": "Disable for 30 seconds", + "description": "Enable the Pi-hole", + "url": "/?disable=30", + "icons": [ + { + "src": "static/icons/shortcut-pause.png", + "type": "image/png", + "sizes": "96x96" + } + ] + }, + { + "name": "Disable for 1 minute", + "description": "Enable the Pi-hole", + "url": "/?disable=60", + "icons": [ + { + "src": "static/icons/shortcut-pause.png", + "type": "image/png", + "sizes": "96x96" + } + ] + }, + { + "name": "Disable for 5 minutes", + "description": "Enable the Pi-hole", + "url": "/?disable=300", + "icons": [ + { + "src": "static/icons/shortcut-pause.png", + "type": "image/png", + "sizes": "96x96" + } + ] + } ] -} +} \ No newline at end of file diff --git a/client/package.json b/client/package.json index bb615a3..e42078d 100644 --- a/client/package.json +++ b/client/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.js", "scripts": { - "prebuild": "copyfiles -Ve src/** index.html manifest.webmanifest sw.js **/*.css **/*.png ../dist/public", + "prebuild": "copyfiles -Ve src/** index.html manifest.json sw.js **/*.css **/*.png ../dist/public", "build": "tsc --project ./", "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/client/src/index.ts b/client/src/index.ts index 46d7aed..836bede 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -10,17 +10,26 @@ if ('serviceWorker' in navigator) { }); } - function monitorChanges() { const protocol = location.protocol === 'https:' ? 'wss' : 'ws'; socket = new WebSocket(`${protocol}://${location.host}/`) socket.onopen = (e) => { console.log('socket opened', e) + if (window.location.search === '?enable') { + enable(); + } else if (window.location.search.startsWith('?disable')) { + const duration = window.location.search.split('=')[1]; + disable(Number.parseInt(duration)); + } + window.history.replaceState(null, '', window.location.pathname); } + let timeout: NodeJS.Timeout | null = null; socket.onmessage = (e) => { + if (timeout) { + clearTimeout(timeout!); + } const data = JSON.parse(e.data) - console.log('message received', data) switch (data.status) { case 'enabled': if (durationInterval) { @@ -28,12 +37,18 @@ function monitorChanges() { } animateLogo(false) showDisable(false) - showEnable(true) + timeout = setTimeout(() => { + showEnable(true); + timeout = null; + }, 500) break case 'disabled': animateLogo(false) showEnable(false, false) - showDisable(true, data.until) + timeout = setTimeout(() => { + showDisable(true, data.until) + timeout = null; + }, 500) break default: console.error('Unhandled status', data) @@ -80,7 +95,6 @@ function setUnit(unit: string) { } function disableCustom() { - showEnable(false, false) } @@ -97,33 +111,23 @@ function showEnable(show: boolean, showCustom?: boolean) { const enableDiv = document.getElementById('enabled') as HTMLElement const disableCustom = document.getElementById('disable-custom') as HTMLElement if (show) { - if (disableCustom.style.opacity === '1') { - disableCustom.style.opacity = '0' + if (disableCustom.classList.contains('visible')) { + disableCustom.classList.replace('visible', 'hidden'); setTimeout(() => { - disableCustom.style.maxHeight = '0' - enableDiv.style.maxHeight = '100vh' - }, 250) - setTimeout(() => { - enableDiv.style.opacity = '1' + enableDiv.classList.add('visible') + enableDiv.classList.remove('hidden') }, 500) } else { - enableDiv.style.maxHeight = '100vh' - setTimeout(() => { - enableDiv.style.opacity = '1' - }, 250) + enableDiv.classList.add('visible') + enableDiv.classList.remove('hidden') } } else { - enableDiv.style.opacity = '0' - setTimeout(() => { - enableDiv.style.maxHeight = '0' - }, 250) + enableDiv.classList.replace('visible', 'hidden') if (showCustom) { setTimeout(() => { - disableCustom.style.maxHeight = '100vh' + disableCustom.classList.add('visible') + disableCustom.classList.remove('hidden') }, 250) - setTimeout(() => { - disableCustom.style.opacity = '1' - }, 500) } } } @@ -133,17 +137,11 @@ let durationInterval: any; function showDisable(show: boolean, timestamp?: number) { const disableDiv = document.getElementById('disabled') as HTMLElement if (show) { - disableDiv.style.maxHeight = '100vh' + disableDiv.classList.add('visible'); + disableDiv.classList.remove('hidden'); } else { - disableDiv.style.opacity = '0' + disableDiv.classList.replace('visible', 'hidden'); } - setTimeout(() => { - if (show) { - disableDiv.style.opacity = '1' - } else { - disableDiv.style.maxHeight = '0' - } - }, 250) const duration = document.getElementById('duration') as HTMLElement if (!timestamp) { duration.innerText = '' @@ -163,7 +161,6 @@ function showDisable(show: boolean, timestamp?: number) { return } let seconds = Math.ceil(difference / 1000) - console.log(`${until.getTime()} - ${now.getTime()} = ${seconds} seconds`) let hours = 0 let minutes = 0 let durationText: string = ''; @@ -171,12 +168,10 @@ function showDisable(show: boolean, timestamp?: number) { hours = Math.floor(seconds / 3600) seconds -= hours * 3600 } - console.log(`hours: ${hours} seconds: ${seconds}`) if (seconds >= 60) { minutes = Math.floor(seconds / 60) seconds -= minutes * 60 } - console.log(`minutes: ${minutes} seconds: ${seconds}`) if (hours > 0) { durationText += `${hours.toString().padStart(2, '0')}:` } diff --git a/client/static/css/style.css b/client/static/css/style.css index aa3f993..a920cbc 100644 --- a/client/static/css/style.css +++ b/client/static/css/style.css @@ -38,7 +38,8 @@ transition: all 0.25s ease; } -html, body { +html, +body { box-sizing: border-box; margin: auto; padding: 0; @@ -49,7 +50,8 @@ html, body { color: var(--color-foreground); } -body, div { +body, +div { display: flex; flex-direction: column; max-width: 400px; @@ -62,7 +64,8 @@ body { padding: 10px; } -div, div > * { +div, +div>* { width: 100%; } @@ -82,12 +85,14 @@ button { cursor: pointer; } -button, p { +button, +p { margin: 5px 0; font-size: 1em; } -#enabled, #disabled, #disable-custom { +.enabled, +.disabled { max-height: 0; opacity: 0; overflow: hidden; @@ -131,8 +136,13 @@ button, p { } @keyframes spin { - from { transform: rotate(0deg) } - to { transform: rotate(360deg) } + from { + transform: rotate(0deg) + } + + to { + transform: rotate(360deg) + } } .custom * { @@ -162,3 +172,51 @@ input { margin: 5px 0; padding: 10px; } + +.visible { + animation-name: show; + animation-fill-mode: both; + animation-duration: 500ms; + max-height: 100%; + opacity: 1; +} + +.hidden { + animation-name: hide; + animation-fill-mode: forwards; + animation-duration: 500ms; + max-height: 0; + opacity: 0; +} + +@keyframes show { + 0% { + max-height: 0; + opacity: 0; + } + + 50% { + max-height: 100vh; + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@keyframes hide { + 0% { + max-height: 100vh; + opacity: 1; + } + + 50% { + max-height: 100vh; + opacity: 0; + } + + 100% { + max-height: 0; + } +} \ No newline at end of file diff --git a/client/static/icons/shortcut-pause.png b/client/static/icons/shortcut-pause.png new file mode 100644 index 0000000000000000000000000000000000000000..a4521c6a0688aabaddb5a8ceb19ea7abd3822fa6 GIT binary patch literal 1819 zcmV+$2juvPP)Px*)k#D_RCr$Pojt5nRS7^23X2nlFHp@raIq4-0k@(OCf0qmYPNkoaiS1dKq8CSc+*vz*1&x8o z?KyMLo$t;0oiig$=o3P?5LgLZ2`mSe0aL(YU=gqoSP+|k2KWm&2^=BV)>|*NG91DOD!tKB&;EsCDZ>;tVuuDWfY)oRJppgOyA#4VwTj*XAKew{j z_rS{{@=9gNh6;=XKvet|;L?WL-L{xnV4H~SX!}L20*GDBR^aNkKcpr32Z5(WWxXmQk2KY;MgW8mE(cx%ZfL4u6E5d~PNPNS$GYvT4IqSYEAZAl>~gAft6kDY z5&5{P;IQ9o0f_zm>vbA159&TtXIwA?h;#ex^FV=3e8RMK&0qrH$}ePQf{+csyPcs1 zqXTnRmz)J44!9pRVYBmC#%(zRb25N9)%>DpwzK2>3mVnulIyPJu6?>~-|X}5>a`sE z`kO|DZa)3Tpgf%&txBDu<^b3aTwm0B2QWt~`$go&6w*lms#drTpitFGE9Oc7DTA&9 zDE(irTq6tx5RVM?EOqF{ngb{WpyR=cVY{eR0Nza5Qg#4Cd;8rY@<3UMQULKVCFvl- z0aOmR>&woYhXMFD^(4yyR023CBG(j^Dg=tJAfnrvm$cY zV97ZEa{v2PpaVz(P-q230Cd`^{+P*+14vfYVG+5iw{#zX5W@QCu9qy)Env=GP?hfb zW<-Mk-m|z1x&>6}yzloJ5xKMbmvaE(s#o2*vmXbLt+J&e@~hVB1@IW~VzwwBge@w2 zTtr?P1n`;FMcgeQo55ZYx#b*ycsW9EmY5y~U{c!>5jo!Jf#(JxisMbOaMBIGKsel^>(58aR6r3Em8n$;>|&`6bE2d z-5Ld;_qJ-e7&-tmfN2Hr9Pp%BgwKNNsP1_MVA}ubeXCo5nZb+#D0R3>73%;}H8rQ) z0R@nC`c?EQ2Ve#;s{nql^82Y0?*OVaRQmf#1#lWT&n&_R;X0~2qX0r330l`Jz^uWb z0RHJc7qEzO02Zxv>U02j7i88S6xm@M)-7cV{csw1Cbu z^sBlz+}#4q6gvws--(*P_WCTyMDOgOd}lpeGt!jmBXRxuXNn@0=UQ0e4Ry9e_gjbPcv@iX!fVa5lNAZukPfqLi(>{tAXM zp!kYX?1nU~d{TGcBRDs0`+yas)rU&bY5IkH>f?g9XATL}^?u^JSppsO6z@`X6Ge>N!67K+pYRDF{ zECSFxUz%BHsNawSC4W%u17grWC{-2w_Jb?yOI z@vXFg9tfolTn=E4nh%IbQaYC$0GcPpgLe<;x+Hk_Y7x;au9Z(RfH){?zL+`T>)itS z^xRJP`kt;x^Mqw3DX9E6m_G$72i?I>&9ZkGV&9|0i zzpyI3(OFPRBjPovEjc*}QslN+W)|3HP0*Rsu2wBzuptq|be#x|IUi>zs~1(ak3#X? zwv7}(&jL=nrcIVe?FOZ$RGHEJEfLA~3QQ3`tjt&d^ng%@;Cp}#*0|L*6tFkuO4u_j z&sPokNma)RU|_)3OCsw4y|uZjg>FjEw)e#lou6vl<|GZZTxend6pR;odvj$>r?MPa z2225qqm#fwU_q?+H}Ge??RhM^*&hXt0D3TTNJLJxtV@&le*uJ}K3%v8sO10v002ov JPDHLkV1hUMM1ueT literal 0 HcmV?d00001 diff --git a/client/static/icons/shortcut-play.png b/client/static/icons/shortcut-play.png new file mode 100644 index 0000000000000000000000000000000000000000..be9ae2d33a0ac33e9e5674f6469a5f031c83b85f GIT binary patch literal 1941 zcmV;G2Wt3Px+Pf0{URCr$PolA@sMG(jTb-)V;*Tf|eE{MTI#SlG+5fC-upa(&t2gL_y;$sg! z4jZp{R1#k?C?*C})C+NaBziEq9@Myd5E4-jBrF#-5hHNW1WasGL-#u0?9B9Iy65ZZ zneVte(^Fl)uIg8HH6hZU5aL1r>j7K^U@d^v09FB*1TX<$Ie=wS{Br>Q25=g{NdUh9 zI04`ofS&*yA)+%eR+VBAN(gZ=fUN*-0&oL>b)IEA3gBA+Ujg`>i2m?gzEg2y1t5f2 z3E(aO+X39{R0H>yW&wN%;1Ci0>RwEn{XMo*>udt0OU~fo3hc)uDOTt(3)M? zJh>@hy&6}IM@Jh?hp09H<^Wt*CdOr-T%P^;#ub|f5a$&%M?}|~ATt7>o8c+|>VpG_ zOcO>kW-S1dPPYhP0I}HiwR4180CLNa_fl&Y)~WyuAi({>y1G?V9e{UCmXvh>dJf_s z5#3+aA5;R6n<+*c5vBlo3}Rc=esdjw?@f2IYytET#9<=3w#*7j0oX1zxB}=Ih|;Bs zDgeiAHqe^_=n04gB3e_lf+YZKTR#`*3qTJ*EU|(z06gsEcueD;ZvY8{I7&oU=M_5w zKnSr-hU*zCKZFGoKc3jkdSpaY1n_}NxL_y?NEE~j5#6ckwg^DZdO57y`qvhKgj~}~ zBKnQ%lLznwfS0YcAD|O9m7&9~=}97br3m0l*AVfxEg%8cG)qLcECP^+Blu*A({J1W zI_jEM5YfrZ1qmVU0q~xa_Q&Xgs^sm6Yr2<+-d6xP9^N_JE*1;um}}ZcM2{%|KQuWs z5i5YmT~jtGBY7H;PZv7>Vh0d0hzSO;MV=f?9zcX3wlDxbx78@buqzf2VIMKY0A2v_ zv~%;1a1>-)mobi-USt4H>p%HQcGUtRu!tE3P#JJ#+LBTMFor&2o&i|xew9H<4M2+^ z78t;vy0{;mC8P?V1rVnhz*ztn=#*Ud|I`5l1aXc5h$hPCcl!b$00_YV{>|?TxWMQe zfINu(8{TwLnR+J!Ft{K~6?&XEoMiwUK*|}YoPT|>0FR1Sx;`}wLk*f;0L81XX2a0$ znx1e^)dB)4UIEA^V#88I!3tVXy!v9c6w&*|xB$aT1ILyCidUb`mZN*O zArpb(l?d5}3@1qK#g=45idUjcs7-n6i0HhN>&hso#m?2F4>-Rm&rj;k0qaVO9YAD? zSL)2p5hod#6``vsYr`XfS)8}I^P!0yQM?j!lJi&RqoX?;oZB(Qv$&(dxw0SVTzGm1 z6|Y1t4NvD}t4>+O$JGf(+PUh+Ed2jfDt$qxjwp4w#pj`vA5oHSh||g&r6(B2JfOoZ zKCiCoLwXW|b)ovCi*JRL_)(=QNW{lib;4$;3S6wf zmjxu);`1=flXXZ4!PC_w-s1Dhw@X)JJ0fzmfW%vT<;W-h4_(SKzByutZf0Q%pht>l z)w!iClUBg@eDTdfwQ)nH0D7i)Rv~Y6U8&L%lr`j_X)Al=)f~20&);41GtI+daig@**p;$UFTv0fbWSZP2Q~l42sX1E63VyA|k%U zRr@mrAUkEgFJ?*l`XPJzh@Qiwug|$6z9-D;k^*frNaq1r%Hv*9Bn1_qp9DQtK01^TZQ4dsIapEXj%&*0B zm0?~>x*2oK8nuwkXohQAo)-%!Kp3vGHPwF8PPpnjG70Q+Cbw@`$U4qPxsRK!Th(+G z&btK^tbxN^x_+hk?yB1_T$S#16l5ZVJO(vwb0>jGxdES90Iz=`Z;7=6ZRZ%31)cIjkGNqzn>R4q%yl?;ikv$xZOzWtja50LK9E#>f#OIulrz bPR0KR&tb$r3!v4G00000NkvXXu0mjfMOj+A literal 0 HcmV?d00001