@signalk/app-dock 1.7.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/public/dock.js +1 -1
package/package.json
CHANGED
package/public/dock.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e=(e,t)=>`<svg class="dock-svg-icon dock-svg-${e}" width="100%" height="100%" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${t}</svg>`,t={sun:e(`sun`,`<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/>`),moon:e(`moon`,`<path d="M20.5 14A9 9 0 1 1 10 3.5a7.5 7.5 0 0 0 10.5 10.5z"/>`),fullscreen:e(`fullscreen`,`<path d="M4 9V4h5M15 4h5v5M20 15v5h-5M9 20H4v-5"/>`),exitFullscreen:e(`exit-fullscreen`,`<path d="M9 4v5H4M20 9h-5V4M15 20v-5h5M4 15h5v5"/>`),close:e(`close`,`<path d="M6 6l12 12M18 6L6 18"/>`)};function n(e){let t=document.getElementById(e);if(!t)throw Error(`[Dock] #${e} is missing from index.html`);return t}function r(e){return typeof e==`number`&&Number.isFinite(e)}(async function(){if(window.self!==window.top){let e=window.location.href;try{window.top?.location.replace(e)}catch{}document.body.innerHTML=`<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;height:100vh;color:#fff;background:#000;font-family:-apple-system,BlinkMacSystemFont,sans-serif;text-align:center;padding:16px"><img src="app-icon.svg" alt="App Dock" style="width:72px;height:72px;border-radius:16px;opacity:0.85" /><a href="`+e+`" target="_top" style="color:#fff;text-decoration:underline;font-size:16px">Open App Dock</a></div>`;return}let e={position:`bottom`,iframeMode:`destroy`,iconSize:56,magnification:!0,magnificationScale:1.7,showNightModeButton:!0,showFullscreenButton:!1,showExitButton:!1,apps:[]},i={showNightModeButton:!0,apps:[{enabled:!0,autostart:!0,url:`/@signalk/freeboard-sk/`,label:`Freeboard-SK`,icon:`/@signalk/freeboard-sk/assets/icons/icon-72x72.png`,color:``},{enabled:!0,autostart:!1,url:`/@halos-org/skip/`,label:`Skip`,icon:`/@halos-org/skip/assets/icon-72x72.png`,color:``},{enabled:!0,autostart:!1,url:`/admin/#/webapps`,label:`More`,icon:`/@signalk/app-dock/icon-more.svg`,color:`#78788c`}]},a=!1,o=!0;for(let e of[`/skServer/loginStatus`,`/loginStatus`])try{let t=await fetch(e);if(t.ok){let e=await t.json(),n=e.authenticationRequired===!1,r=e.status===`loggedIn`?e.userLevel:void 0;a=!n&&r!==`admin`,o=n||r===`admin`||r===`readwrite`;break}}catch{}async function s(){try{let t=await fetch(`/plugins/signalk-app-dock/settings`);if(!t.ok)return null;let n=await t.json();return Array.isArray(n.apps)?{...e,...n}:null}catch{return null}}let c={...e},l=!a;if(a){let t=await s();l=t!==null,c=t||{...e,...i}}else try{let[t,n]=await Promise.all([fetch(`/plugins/signalk-app-dock/config`),fetch(`/plugins/signalk-app-dock/settings`)]);if(t.ok){let n=await t.json(),r=n.configuration||n;c={...e,...r}}if(n.ok){let e=await n.json();Array.isArray(e.apps)&&e.apps.length>0&&(c.apps=e.apps)}}catch(e){console.warn(`[Dock] Could not load config, using defaults.`,e)}Array.isArray(c.apps)&&c.apps.length!==0||console.warn(`[Dock] No apps configured — open Plugin Config to add webapps.`);let u=n(`iframe-container`),d=n(`dock-backdrop`),f=n(`dock`),p=n(`dock-inner`),ee=n(`loading-overlay`),te=n(`loading-label`),ne=document.getElementById(`idle-hint`),m=n(`idle-hint-text`),h=n(`dock-handle`),g=!1,_={},v=`day`,y=l&&o;async function b(){if(l)try{let e=await fetch(`/plugins/signalk-app-dock/mode`);e.ok&&(v=(await e.json()).value||`day`,x())}catch(e){console.warn(`[Dock] Could not fetch environment.mode`,e)}}function x(){let e=document.querySelector(`.dock-item-nightmode .dock-icon`);if(!e)return;e.innerHTML=t[v===`night`?`moon`:`sun`];let n=document.querySelector(`.dock-item-nightmode .dock-label`);n&&(n.textContent=v===`night`?`Day mode`:`Night mode`)}async function re(){if(!y)return;await b();let e=v===`night`?`day`:`night`;try{(await fetch(`/signalk/v1/api/vessels/self/environment/mode`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({value:e})})).ok&&(v=e,x())}catch(e){console.warn(`[Dock] Failed to toggle mode`,e)}}function S(){return!!(document.fullscreenEnabled&&document.documentElement.requestFullscreen)}function C(){return!!document.fullscreenElement}function ie(){S()&&(C()?document.exitFullscreen().catch(e=>console.warn(`[Dock] exitFullscreen`,e)):document.documentElement.requestFullscreen().catch(e=>console.warn(`[Dock] requestFullscreen`,e)))}function ae(){let e=document.querySelector(`.dock-item-fullscreen .dock-icon`);if(!e)return;e.innerHTML=t[C()?`exitFullscreen`:`fullscreen`];let n=document.querySelector(`.dock-item-fullscreen .dock-label`);n&&(n.textContent=C()?`Exit fullscreen`:`Fullscreen`)}let w=c.position;f.classList.add(`pos-${w}`),p.classList.add(`align-${w}`),h.classList.add(`pos-${w}`);let T=w===`left`||w===`right`;T&&p.classList.add(`layout-vertical`);let oe=c.magnification!==!1,se=Math.max(1,Math.min(c.magnificationScale||1.7,2.5)),ce=c.iconSize*3.5;function le(){let e=document.getElementById(`idle-version`);e&&fetch(`/skServer/webapps`).then(e=>e.json()).then(t=>{let n=t.find(e=>e.name===`@signalk/app-dock`);n&&(e.textContent=`v`+n.version)}).catch(()=>{}),!c.apps||c.apps.length===0?a?m.innerHTML=`App Dock can only be customised by an administrator.<br>Ask an admin to set up apps in <strong>Plugin Config</strong>.`:m.innerHTML=`No apps configured yet.<br>Open <strong>Admin UI → Plugin Config → App Dock</strong><br>and click <strong>Discover Installed Webapps</strong> to get started.`:m.innerHTML=`Double-tap <strong>anywhere</strong> to open the dock`+(a?`<br><br><span style="font-size:12px;opacity:0.6">Customisation is admin-only</span>`:`<br><br><span style="font-size:12px;opacity:0.6">Configure in Admin UI → Plugin Config → App Dock</span>`)}function ue(){ne&&ne.classList.add(`hidden`)}function de(){switch(w){case`top`:return`dock-label-below`;case`left`:return`dock-label-right`;case`right`:return`dock-label-left`;default:return``}}function E(e,n,r,i){let a=c.iconSize,o=Math.round(a*.25)+`px`,s=de(),l=document.createElement(`div`);l.className=`dock-item dock-item-utility `+e;let u=document.createElement(`div`);u.className=`dock-icon dock-icon-utility`,u.style.width=a+`px`,u.style.height=a+`px`,u.style.borderRadius=o,u.innerHTML=t[n];let d=document.createElement(`div`);d.className=`dock-dot`;let f=document.createElement(`div`);return f.className=`dock-label`+(s?` `+s:``),f.textContent=r,l.appendChild(u),l.appendChild(d),l.appendChild(f),l.addEventListener(`touchstart`,()=>l.classList.add(`pressing`),{passive:!0}),l.addEventListener(`touchend`,()=>l.classList.remove(`pressing`),{passive:!0}),l.addEventListener(`touchcancel`,()=>l.classList.remove(`pressing`),{passive:!0}),l.addEventListener(`click`,e=>{O?e.preventDefault():i()}),l}function fe(){let e=document.createElement(`div`);return e.className=`dock-separator`,e}function pe(){p.innerHTML=``;let e=c.iconSize,t=Math.round(e*.25)+`px`,n=de(),r=c.showNightModeButton&&y;r&&p.appendChild(E(`dock-item-nightmode`,`sun`,`Night mode`,()=>void re())),c.showFullscreenButton&&S()&&p.appendChild(E(`dock-item-fullscreen`,`fullscreen`,`Fullscreen`,ie)),(r||c.showFullscreenButton&&S())&&p.appendChild(fe()),c.apps.forEach((r,i)=>{let a=document.createElement(`div`);a.className=`dock-item`,a.dataset.index=String(i);let o=document.createElement(`div`);o.className=`dock-icon`,o.style.width=e+`px`,o.style.height=e+`px`,o.style.borderRadius=t,r.color&&(o.style.background=r.color);let s=r.icon||``;if(s.startsWith(`/`)||s.startsWith(`http`)){let t=document.createElement(`img`);t.src=s,t.alt=r.label,t.onerror=()=>{o.removeChild(t),o.style.fontSize=Math.round(e*.42)+`px`,o.textContent=r.label.slice(0,2).toUpperCase()},o.appendChild(t)}else o.style.fontSize=Math.round(e*.48)+`px`,o.textContent=s||r.label.slice(0,2).toUpperCase();let c=document.createElement(`div`);c.className=`dock-dot`;let l=document.createElement(`div`);l.className=`dock-label`+(n?` `+n:``),l.textContent=r.label,a.appendChild(o),a.appendChild(c),a.appendChild(l),a.addEventListener(`touchstart`,()=>a.classList.add(`pressing`),{passive:!0}),a.addEventListener(`touchend`,()=>a.classList.remove(`pressing`),{passive:!0}),a.addEventListener(`touchcancel`,()=>a.classList.remove(`pressing`),{passive:!0}),a.addEventListener(`click`,e=>{O?e.preventDefault():I(i)}),p.appendChild(a)}),c.showExitButton&&(p.appendChild(fe()),p.appendChild(E(`dock-item-exit`,`close`,`Exit to Admin`,()=>{window.location.href=`/admin/`}))),c.showNightModeButton&&x(),c.showFullscreenButton&&S()&&ae()}let D=!1,O=!1,me=0;function he(e){let t=p.querySelectorAll(`.dock-item`),n=-1,r=1/0;t.forEach((t,i)=>{let a=t.querySelector(`.dock-icon`);if(!a)return;let o=t.getBoundingClientRect(),s=T?o.top+o.height/2:o.left+o.width/2,l=T?e.y:e.x,u=Math.abs(l-s);u<r&&(r=u,n=i);let d=Math.min(u/ce,1),f=1+(se-1)*(1-d*d),p=Math.round(c.iconSize*f);a.classList.remove(`settling`),a.style.width=p+`px`,a.style.height=p+`px`,a.style.borderRadius=Math.round(p*.25)+`px`,a.querySelector(`img`)||(a.style.fontSize=Math.round(p*.48)+`px`)}),t.forEach((e,t)=>{e.classList.toggle(`mag-closest`,t===n)})}function k(){let e=p.querySelectorAll(`.dock-item`),t=c.iconSize;e.forEach(e=>{let n=e.querySelector(`.dock-icon`);n&&(n.classList.add(`settling`),n.style.width=t+`px`,n.style.height=t+`px`,n.style.borderRadius=Math.round(t*.25)+`px`,n.querySelector(`img`)||(n.style.fontSize=Math.round(t*.48)+`px`),e.classList.remove(`mag-closest`))})}oe&&(p.addEventListener(`mousemove`,e=>{if(D)return;D=!0;let t={x:e.clientX,y:e.clientY};requestAnimationFrame(()=>{he(t),D=!1})}),p.addEventListener(`mouseleave`,()=>{D=!1,k()}),p.addEventListener(`touchstart`,e=>{let t=e.touches[0];O=!1,me=T?t.clientY:t.clientX},{passive:!0}),p.addEventListener(`touchmove`,e=>{let t=e.touches[0],n=T?t.clientY:t.clientX;if(Math.abs(n-me)>10&&(O=!0),D)return;D=!0;let r={x:t.clientX,y:t.clientY};requestAnimationFrame(()=>{he(r),D=!1})},{passive:!0}),p.addEventListener(`touchend`,()=>{D=!1,k(),setTimeout(()=>{O=!1},50)},{passive:!0}));let A=0;function j(){g||(g=!0,A=Date.now(),f.classList.add(`visible`),d.classList.add(`visible`),Y())}function M(){g&&(X&&X.classList.contains(`visible`)||(g=!1,f.classList.remove(`visible`),d.classList.remove(`visible`),k(),Y()))}d.addEventListener(`click`,e=>{Date.now()-A<500?e.stopPropagation():M()});function N(e){te.textContent=`Loading ${e}\u2026`,ee.classList.add(`visible`)}function P(){ee.classList.remove(`visible`)}let F=`allow-same-origin allow-scripts allow-forms allow-popups allow-pointer-lock allow-top-navigation-by-user-activation`;function ge(e){try{let t=new URL(e.url,window.location.href);return t.origin===window.location.origin&&/^\/admin(\/|$)/.test(t.pathname)}catch{return!1}}function _e(e,t){N(t.label),e.addEventListener(`load`,P,{once:!0});try{e.contentWindow?.location.reload()}catch{e.src=t.url}}function I(e){let t=c.apps[e];if(!t)return;ue(),document.querySelectorAll(`.dock-item`).forEach(t=>{t.classList.toggle(`active`,t.dataset.index===String(e))});let n=ge(t),r=!n&&!!_[t.url];if(r&&c.iframeMode!==`destroy`&&_[t.url].classList.contains(`active`))_e(_[t.url],t),Y(),M();else{if(c.iframeMode===`destroy`?(u.innerHTML=``,Object.keys(_).forEach(e=>delete _[e])):Object.values(_).forEach(e=>e.classList.remove(`active`)),n&&_[t.url]&&(_[t.url].remove(),delete _[t.url]),!_[t.url]){r||N(t.label);let e=document.createElement(`iframe`);e.allow=`fullscreen; geolocation`,e.setAttribute(`sandbox`,F),e.style.touchAction=`auto`,e.addEventListener(`load`,P,{once:!0}),u.appendChild(e),e.src=t.url,_[t.url]=e}_[t.url].classList.add(`active`),Y(),M()}}let L=0,R=0,z=0,B=null;function V(){return performance.timeOrigin+performance.now()}function ve(e,t){let n=e.defaultView&&e.defaultView.performance;return n?n.timeOrigin+t.timeStamp:V()}function ye(){if(H&&console.log(`[Dock] triggerDock called, dockVisible=`,g),!g){if(j(),H){let e=f.getBoundingClientRect();console.log(`[Dock] after showDock: dock rect`,e,`classes`,f.className)}navigator.vibrate&&navigator.vibrate(8)}}let H=new URLSearchParams(location.search).has(`debugDock`);function U(e,t,n=null,r=V()){if(g)return;let i=e-R,a=t-z,o=Math.round(Math.sqrt(i*i+a*a)),s=o<=40&&n===B,c=r-L,l=c>=0&&c<450&&s;H&&console.log(`[Dock] tap`,{x:Math.round(e),y:Math.round(t),gap:c,dist:o,close:s,match:l}),l?(L=0,ye()):(L=r,R=e,z=t,B=n)}let be=new WeakSet;function xe(e,t){if(be.has(e))return;be.add(e);let n=()=>{if(!t)return{x:0,y:0};let e=t.getBoundingClientRect();return{x:e.left,y:e.top}},r=-1/0,i=(e,t,n)=>{Math.abs(n-r)<50||(r=n,U(e,t,null,n))};e.addEventListener(`pointerdown`,t=>{let r=n();i(t.clientX+r.x,t.clientY+r.y,ve(e,t))},{passive:!0,capture:!0}),e.addEventListener(`touchstart`,t=>{if(!t.touches||t.touches.length===0)return;let r=t.touches[0],a=n();i(r.clientX+a.x,r.clientY+a.y,ve(e,t))},{passive:!0,capture:!0})}let W=new WeakSet;function G(e){let t=()=>{let t=null;try{t=e.contentDocument}catch{}let n=we(e);n&&Te(n,e,t!==null),t&&!W.has(t)&&(W.add(t),xe(t,e),Se(t))};e.addEventListener(`load`,t),t()}function Se(e){e.querySelectorAll(`iframe`).forEach(e=>G(e));let t=e.documentElement||e.body||e;new MutationObserver(e=>{for(let t of e)t.addedNodes.forEach(e=>{if(!e||e.nodeType!==Node.ELEMENT_NODE)return;let t=e;t.tagName===`IFRAME`&&G(t),t.querySelectorAll(`iframe`).forEach(e=>G(e))}),t.removedNodes.length>0&&Y()}).observe(t,{childList:!0,subtree:!0})}let K=new WeakMap,q=new WeakSet,Ce=-1/0;function we(e){let t=e;for(;t&&t.parentElement!==u;){let e=t.ownerDocument&&t.ownerDocument.defaultView;t=e?e.frameElement:null}return t}function Te(e,t,n){q.delete(t);let r=K.get(e);t!==e&&r||(r=new Set,K.set(e,r)),n?r.delete(t):r.add(t),Y()}function J(e){let t=e&&K.get(e)||new Set;for(let e of t)e.isConnected&&e.ownerDocument.defaultView||t.delete(e);return t}function Y(){let e=u.querySelector(`iframe.active`),t=!1;if(!g)for(let n of J(e))q.has(n)||(t=!0);h.classList.toggle(`visible`,t)}function Ee(e){let t=0,n=0;for(let r=e;r;){let e=r.getBoundingClientRect();if(t+=e.left,n+=e.top,r.parentElement===u)break;let i=r.ownerDocument.defaultView;r=i?i.frameElement:null}return{x:t,y:n}}function De(e,t){for(let n=e;n;n=n.parent){if(n===t)return!0;if(n===n.parent)return!1}return!1}window.addEventListener(`message`,e=>{let t=e.data;if(!t||t.type!==`signalk-app-dock:pointerdown`||!r(t.x)||!r(t.y))return;let n=u.querySelector(`iframe.active`);if(!n||!e.source)return;let i=null;for(let t of J(n))De(e.source,t.contentWindow)&&(i=t);if(!i)return;e.source===i.contentWindow&&(q.add(i),Y());let a=V(),o=r(t.t)&&t.t<=a?t.t:a;if(!(Math.abs(o-Ce)<50)){if(Ce=o,e.source===i.contentWindow){let e=Ee(i);U(t.x+e.x,t.y+e.y,null,o)}else U(t.x,t.y,e.source,o)}}),h.addEventListener(`click`,ye),xe(document,null),W.add(document),Se(document),pe(),c.showNightModeButton&&y&&(b(),setInterval(b,5e3)),c.showFullscreenButton&&S()&&document.addEventListener(`fullscreenchange`,ae);let Oe=[`position`,`iframeMode`,`iconSize`,`magnification`,`magnificationScale`,`showNightModeButton`,`showFullscreenButton`,`showExitButton`];async function ke(){try{let t=c,n=c;if(a)n=await s()||t;else{let[r,i]=await Promise.all([fetch(`/plugins/signalk-app-dock/config`),fetch(`/plugins/signalk-app-dock/settings`)]);if(r.ok){let i=await r.json(),a=i.configuration||i;n={...e,...a,apps:t.apps}}if(i.ok){let e=await i.json();Array.isArray(e.apps)&&e.apps.length>0&&(n={...n,apps:e.apps})}}if(Oe.some(e=>t[e]!==n[e])){window.location.reload();return}JSON.stringify(n.apps)===JSON.stringify(t.apps)?c=n:(c=n,pe())}catch{}}l&&setInterval(ke,5e3);let X=document.getElementById(`tour-overlay`),Ae=document.getElementById(`tour-btn-got-it`),Z=document.getElementById(`tour-btn-dismiss`);function Q(){X?.classList.remove(`visible`),M()}function je(){X?.classList.add(`visible`),j()}Ae&&Ae.addEventListener(`click`,Q),!l||!o?(Z&&Z.remove(),document.getElementById(`tour-buttons`)?.style.setProperty(`justify-content`,`center`)):Z&&Z.addEventListener(`click`,()=>{Q(),fetch(`/plugins/signalk-app-dock/dismiss-tour`,{method:`POST`}).catch(()=>{})});let $=document.getElementById(`tour-link`);$&&(a?$.replaceWith(document.createTextNode(`Plugin Config (admin only)`)):$.addEventListener(`click`,e=>{e.preventDefault();let t=`/admin/#/serverConfiguration/plugins/signalk-app-dock`;ue();let n=document.createElement(`iframe`);n.allow=`fullscreen; geolocation`,n.setAttribute(`sandbox`,F),n.style.touchAction=`auto`,n.classList.add(`active`),u.innerHTML=``,Object.keys(_).forEach(e=>delete _[e]),u.appendChild(n),n.src=t,_[t]=n,Q()})),c.tourDismissed||je();let Me=c.apps.findIndex(e=>e.autostart);Me>=0?I(Me):le()})().catch(e=>console.error(`[Dock] failed to start`,e));
|
|
1
|
+
var e=(e,t)=>`<svg class="dock-svg-icon dock-svg-${e}" width="100%" height="100%" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${t}</svg>`,t={sun:e(`sun`,`<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/>`),moon:e(`moon`,`<path d="M20.5 14A9 9 0 1 1 10 3.5a7.5 7.5 0 0 0 10.5 10.5z"/>`),fullscreen:e(`fullscreen`,`<path d="M4 9V4h5M15 4h5v5M20 15v5h-5M9 20H4v-5"/>`),exitFullscreen:e(`exit-fullscreen`,`<path d="M9 4v5H4M20 9h-5V4M15 20v-5h5M4 15h5v5"/>`),close:e(`close`,`<path d="M6 6l12 12M18 6L6 18"/>`)};function n(e){let t=document.getElementById(e);if(!t)throw Error(`[Dock] #${e} is missing from index.html`);return t}function r(e){return typeof e==`number`&&Number.isFinite(e)}(async function(){if(window.self!==window.top){let e=window.location.href;try{window.top?.location.replace(e)}catch{}document.body.innerHTML=`<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;height:100vh;color:#fff;background:#000;font-family:-apple-system,BlinkMacSystemFont,sans-serif;text-align:center;padding:16px"><img src="app-icon.svg" alt="App Dock" style="width:72px;height:72px;border-radius:16px;opacity:0.85" /><a href="`+e+`" target="_top" style="color:#fff;text-decoration:underline;font-size:16px">Open App Dock</a></div>`;return}let e={position:`bottom`,iframeMode:`destroy`,iconSize:56,magnification:!0,magnificationScale:1.7,showNightModeButton:!0,showFullscreenButton:!1,showExitButton:!1,apps:[]},i={showNightModeButton:!0,apps:[{enabled:!0,autostart:!0,url:`/@signalk/freeboard-sk/`,label:`Freeboard-SK`,icon:`/@signalk/freeboard-sk/assets/icons/icon-72x72.png`,color:``},{enabled:!0,autostart:!1,url:`/@halos-org/skip/`,label:`Skip`,icon:`/@halos-org/skip/assets/icon-72x72.png`,color:``},{enabled:!0,autostart:!1,url:`/admin/#/webapps`,label:`More`,icon:`/@signalk/app-dock/icon-more.svg`,color:`#78788c`}]},a=!1,o=!0;for(let e of[`/skServer/loginStatus`,`/loginStatus`])try{let t=await fetch(e);if(t.ok){let e=await t.json(),n=e.authenticationRequired===!1,r=e.status===`loggedIn`?e.userLevel:void 0;a=!n&&r!==`admin`,o=n||r===`admin`||r===`readwrite`;break}}catch{}async function s(){try{let t=await fetch(`/plugins/signalk-app-dock/settings`);if(!t.ok)return null;let n=await t.json();return Array.isArray(n.apps)?{...e,...n}:null}catch{return null}}let c={...e},l=!a;if(a){let t=await s();l=t!==null,c=t||{...e,...i}}else try{let[t,n]=await Promise.all([fetch(`/plugins/signalk-app-dock/config`),fetch(`/plugins/signalk-app-dock/settings`)]);if(t.ok){let n=await t.json(),r=n.configuration||n;c={...e,...r}}if(n.ok){let e=await n.json();Array.isArray(e.apps)&&e.apps.length>0&&(c.apps=e.apps)}}catch(e){console.warn(`[Dock] Could not load config, using defaults.`,e)}Array.isArray(c.apps)&&c.apps.length!==0||console.warn(`[Dock] No apps configured — open Plugin Config to add webapps.`);let u=n(`iframe-container`),d=n(`dock-backdrop`),f=n(`dock`),p=n(`dock-inner`),m=n(`loading-overlay`),ee=n(`loading-label`),te=document.getElementById(`idle-hint`),h=n(`idle-hint-text`),g=n(`dock-handle`),_=!1,v={},y=`day`,b=l&&o;async function x(){if(l)try{let e=await fetch(`/plugins/signalk-app-dock/mode`);e.ok&&(y=(await e.json()).value||`day`,S())}catch(e){console.warn(`[Dock] Could not fetch environment.mode`,e)}}function S(){let e=document.querySelector(`.dock-item-nightmode .dock-icon`);if(!e)return;e.innerHTML=t[y===`night`?`moon`:`sun`];let n=document.querySelector(`.dock-item-nightmode .dock-label`);n&&(n.textContent=y===`night`?`Day mode`:`Night mode`)}async function ne(){if(!b)return;await x();let e=y===`night`?`day`:`night`;try{(await fetch(`/signalk/v1/api/vessels/self/environment/mode`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({value:e})})).ok&&(y=e,S())}catch(e){console.warn(`[Dock] Failed to toggle mode`,e)}}function C(){return!!(document.fullscreenEnabled&&document.documentElement.requestFullscreen)}function w(){return!!document.fullscreenElement}function re(){C()&&(w()?document.exitFullscreen().catch(e=>console.warn(`[Dock] exitFullscreen`,e)):document.documentElement.requestFullscreen().catch(e=>console.warn(`[Dock] requestFullscreen`,e)))}function ie(){let e=document.querySelector(`.dock-item-fullscreen .dock-icon`);if(!e)return;e.innerHTML=t[w()?`exitFullscreen`:`fullscreen`];let n=document.querySelector(`.dock-item-fullscreen .dock-label`);n&&(n.textContent=w()?`Exit fullscreen`:`Fullscreen`)}let T=c.position;f.classList.add(`pos-${T}`),p.classList.add(`align-${T}`),g.classList.add(`pos-${T}`);let E=T===`left`||T===`right`;E&&p.classList.add(`layout-vertical`);let ae=c.magnification!==!1,oe=Math.max(1,Math.min(c.magnificationScale||1.7,2.5)),se=c.iconSize*3.5;function ce(){let e=document.getElementById(`idle-version`);e&&fetch(`/skServer/webapps`).then(e=>e.json()).then(t=>{let n=t.find(e=>e.name===`@signalk/app-dock`);n&&(e.textContent=`v`+n.version)}).catch(()=>{}),!c.apps||c.apps.length===0?a?h.innerHTML=`App Dock can only be customised by an administrator.<br>Ask an admin to set up apps in <strong>Plugin Config</strong>.`:h.innerHTML=`No apps configured yet.<br>Open <strong>Admin UI → Plugin Config → App Dock</strong><br>and click <strong>Discover Installed Webapps</strong> to get started.`:h.innerHTML=`Double-tap <strong>anywhere</strong> to open the dock`+(a?`<br><br><span style="font-size:12px;opacity:0.6">Customisation is admin-only</span>`:`<br><br><span style="font-size:12px;opacity:0.6">Configure in Admin UI → Plugin Config → App Dock</span>`)}function le(){te&&te.classList.add(`hidden`)}function ue(){switch(T){case`top`:return`dock-label-below`;case`left`:return`dock-label-right`;case`right`:return`dock-label-left`;default:return``}}function D(e,n,r,i){let a=c.iconSize,o=Math.round(a*.25)+`px`,s=ue(),l=document.createElement(`div`);l.className=`dock-item dock-item-utility `+e;let u=document.createElement(`div`);u.className=`dock-icon dock-icon-utility`,u.style.width=a+`px`,u.style.height=a+`px`,u.style.borderRadius=o,u.innerHTML=t[n];let d=document.createElement(`div`);d.className=`dock-dot`;let f=document.createElement(`div`);return f.className=`dock-label`+(s?` `+s:``),f.textContent=r,l.appendChild(u),l.appendChild(d),l.appendChild(f),l.addEventListener(`touchstart`,()=>l.classList.add(`pressing`),{passive:!0}),l.addEventListener(`touchend`,()=>l.classList.remove(`pressing`),{passive:!0}),l.addEventListener(`touchcancel`,()=>l.classList.remove(`pressing`),{passive:!0}),l.addEventListener(`click`,e=>{k?e.preventDefault():i()}),l}function de(){let e=document.createElement(`div`);return e.className=`dock-separator`,e}function fe(){p.innerHTML=``;let e=c.iconSize,t=Math.round(e*.25)+`px`,n=ue(),r=c.showNightModeButton&&b;r&&p.appendChild(D(`dock-item-nightmode`,`sun`,`Night mode`,()=>void ne())),c.showFullscreenButton&&C()&&p.appendChild(D(`dock-item-fullscreen`,`fullscreen`,`Fullscreen`,re)),(r||c.showFullscreenButton&&C())&&p.appendChild(de()),c.apps.forEach((r,i)=>{let a=document.createElement(`div`);a.className=`dock-item`,a.dataset.index=String(i);let o=document.createElement(`div`);o.className=`dock-icon`,o.style.width=e+`px`,o.style.height=e+`px`,o.style.borderRadius=t,r.color&&(o.style.background=r.color);let s=r.icon||``;if(s.startsWith(`/`)||s.startsWith(`http`)){let t=document.createElement(`img`);t.src=s,t.alt=r.label,t.onerror=()=>{o.removeChild(t),o.style.fontSize=Math.round(e*.42)+`px`,o.textContent=r.label.slice(0,2).toUpperCase()},o.appendChild(t)}else o.style.fontSize=Math.round(e*.48)+`px`,o.textContent=s||r.label.slice(0,2).toUpperCase();let c=document.createElement(`div`);c.className=`dock-dot`;let l=document.createElement(`div`);l.className=`dock-label`+(n?` `+n:``),l.textContent=r.label,a.appendChild(o),a.appendChild(c),a.appendChild(l),a.addEventListener(`touchstart`,()=>a.classList.add(`pressing`),{passive:!0}),a.addEventListener(`touchend`,()=>a.classList.remove(`pressing`),{passive:!0}),a.addEventListener(`touchcancel`,()=>a.classList.remove(`pressing`),{passive:!0}),a.addEventListener(`click`,e=>{k?e.preventDefault():R(i)}),p.appendChild(a)}),c.showExitButton&&(p.appendChild(de()),p.appendChild(D(`dock-item-exit`,`close`,`Exit to Admin`,()=>{window.location.href=`/admin/`}))),c.showNightModeButton&&S(),c.showFullscreenButton&&C()&&ie()}let O=!1,k=!1,pe=0;function me(e){let t=p.querySelectorAll(`.dock-item`),n=-1,r=1/0;t.forEach((t,i)=>{let a=t.querySelector(`.dock-icon`);if(!a)return;let o=t.getBoundingClientRect(),s=E?o.top+o.height/2:o.left+o.width/2,l=E?e.y:e.x,u=Math.abs(l-s);u<r&&(r=u,n=i);let d=Math.min(u/se,1),f=1+(oe-1)*(1-d*d),p=Math.round(c.iconSize*f);a.classList.remove(`settling`),a.style.width=p+`px`,a.style.height=p+`px`,a.style.borderRadius=Math.round(p*.25)+`px`,a.querySelector(`img`)||(a.style.fontSize=Math.round(p*.48)+`px`)}),t.forEach((e,t)=>{e.classList.toggle(`mag-closest`,t===n)})}function A(){let e=p.querySelectorAll(`.dock-item`),t=c.iconSize;e.forEach(e=>{let n=e.querySelector(`.dock-icon`);n&&(n.classList.add(`settling`),n.style.width=t+`px`,n.style.height=t+`px`,n.style.borderRadius=Math.round(t*.25)+`px`,n.querySelector(`img`)||(n.style.fontSize=Math.round(t*.48)+`px`),e.classList.remove(`mag-closest`))})}ae&&(p.addEventListener(`mousemove`,e=>{if(O)return;O=!0;let t={x:e.clientX,y:e.clientY};requestAnimationFrame(()=>{me(t),O=!1})}),p.addEventListener(`mouseleave`,()=>{O=!1,A()}),p.addEventListener(`touchstart`,e=>{let t=e.touches[0];k=!1,pe=E?t.clientY:t.clientX},{passive:!0}),p.addEventListener(`touchmove`,e=>{let t=e.touches[0],n=E?t.clientY:t.clientX;if(Math.abs(n-pe)>10&&(k=!0),O)return;O=!0;let r={x:t.clientX,y:t.clientY};requestAnimationFrame(()=>{me(r),O=!1})},{passive:!0}),p.addEventListener(`touchend`,()=>{O=!1,A(),setTimeout(()=>{k=!1},50)},{passive:!0}));let j=0;function M(){_||(_=!0,j=Date.now(),f.classList.add(`visible`),d.classList.add(`visible`),Y())}function N(){_&&(X&&X.classList.contains(`visible`)||(_=!1,f.classList.remove(`visible`),d.classList.remove(`visible`),A(),Y()))}d.addEventListener(`click`,e=>{Date.now()-j<500?e.stopPropagation():N()});function P(e){ee.textContent=`Loading ${e}\u2026`,m.classList.add(`visible`)}function F(){m.classList.remove(`visible`)}let I=`allow-same-origin allow-scripts allow-forms allow-popups allow-pointer-lock allow-top-navigation-by-user-activation`;function he(e){try{let t=new URL(e.url,window.location.href);return t.origin===window.location.origin&&/^\/admin(\/|$)/.test(t.pathname)}catch{return!1}}function ge(e,t){P(t.label),e.addEventListener(`load`,F,{once:!0});try{e.contentWindow?.location.reload()}catch{e.src=t.url}}let L=document.title;function R(e){let t=c.apps[e];if(!t)return;le(),document.title=t.label?`${t.label} – ${L}`:L,document.querySelectorAll(`.dock-item`).forEach(t=>{t.classList.toggle(`active`,t.dataset.index===String(e))});let n=he(t),r=!n&&!!v[t.url];if(r&&c.iframeMode!==`destroy`&&v[t.url].classList.contains(`active`))ge(v[t.url],t),Y(),N();else{if(c.iframeMode===`destroy`?(u.innerHTML=``,Object.keys(v).forEach(e=>delete v[e])):Object.values(v).forEach(e=>e.classList.remove(`active`)),n&&v[t.url]&&(v[t.url].remove(),delete v[t.url]),!v[t.url]){r||P(t.label);let e=document.createElement(`iframe`);e.allow=`fullscreen; geolocation`,e.setAttribute(`sandbox`,I),e.style.touchAction=`auto`,e.addEventListener(`load`,F,{once:!0}),u.appendChild(e),e.src=t.url,v[t.url]=e}v[t.url].classList.add(`active`),Y(),N()}}let z=0,B=0,V=0,_e=null;function H(){return performance.timeOrigin+performance.now()}function ve(e,t){let n=e.defaultView&&e.defaultView.performance;return n?n.timeOrigin+t.timeStamp:H()}function ye(){if(U&&console.log(`[Dock] triggerDock called, dockVisible=`,_),!_){if(M(),U){let e=f.getBoundingClientRect();console.log(`[Dock] after showDock: dock rect`,e,`classes`,f.className)}navigator.vibrate&&navigator.vibrate(8)}}let U=new URLSearchParams(location.search).has(`debugDock`);function W(e,t,n=null,r=H()){if(_)return;let i=e-B,a=t-V,o=Math.round(Math.sqrt(i*i+a*a)),s=o<=40&&n===_e,c=r-z,l=c>=0&&c<450&&s;U&&console.log(`[Dock] tap`,{x:Math.round(e),y:Math.round(t),gap:c,dist:o,close:s,match:l}),l?(z=0,ye()):(z=r,B=e,V=t,_e=n)}let be=new WeakSet;function xe(e,t){if(be.has(e))return;be.add(e);let n=()=>{if(!t)return{x:0,y:0};let e=t.getBoundingClientRect();return{x:e.left,y:e.top}},r=-1/0,i=(e,t,n)=>{Math.abs(n-r)<50||(r=n,W(e,t,null,n))};e.addEventListener(`pointerdown`,t=>{let r=n();i(t.clientX+r.x,t.clientY+r.y,ve(e,t))},{passive:!0,capture:!0}),e.addEventListener(`touchstart`,t=>{if(!t.touches||t.touches.length===0)return;let r=t.touches[0],a=n();i(r.clientX+a.x,r.clientY+a.y,ve(e,t))},{passive:!0,capture:!0})}let G=new WeakSet;function K(e){let t=()=>{let t=null;try{t=e.contentDocument}catch{}let n=we(e);n&&Te(n,e,t!==null),t&&!G.has(t)&&(G.add(t),xe(t,e),Se(t))};e.addEventListener(`load`,t),t()}function Se(e){e.querySelectorAll(`iframe`).forEach(e=>K(e));let t=e.documentElement||e.body||e;new MutationObserver(e=>{for(let t of e)t.addedNodes.forEach(e=>{if(!e||e.nodeType!==Node.ELEMENT_NODE)return;let t=e;t.tagName===`IFRAME`&&K(t),t.querySelectorAll(`iframe`).forEach(e=>K(e))}),t.removedNodes.length>0&&Y()}).observe(t,{childList:!0,subtree:!0})}let q=new WeakMap,J=new WeakSet,Ce=-1/0;function we(e){let t=e;for(;t&&t.parentElement!==u;){let e=t.ownerDocument&&t.ownerDocument.defaultView;t=e?e.frameElement:null}return t}function Te(e,t,n){J.delete(t);let r=q.get(e);t!==e&&r||(r=new Set,q.set(e,r)),n?r.delete(t):r.add(t),Y()}function Ee(e){let t=e&&q.get(e)||new Set;for(let e of t)e.isConnected&&e.ownerDocument.defaultView||t.delete(e);return t}function Y(){let e=u.querySelector(`iframe.active`),t=!1;if(!_)for(let n of Ee(e))J.has(n)||(t=!0);g.classList.toggle(`visible`,t)}function De(e){let t=0,n=0;for(let r=e;r;){let e=r.getBoundingClientRect();if(t+=e.left,n+=e.top,r.parentElement===u)break;let i=r.ownerDocument.defaultView;r=i?i.frameElement:null}return{x:t,y:n}}function Oe(e,t){for(let n=e;n;n=n.parent){if(n===t)return!0;if(n===n.parent)return!1}return!1}window.addEventListener(`message`,e=>{let t=e.data;if(!t||t.type!==`signalk-app-dock:pointerdown`||!r(t.x)||!r(t.y))return;let n=u.querySelector(`iframe.active`);if(!n||!e.source)return;let i=null;for(let t of Ee(n))Oe(e.source,t.contentWindow)&&(i=t);if(!i)return;e.source===i.contentWindow&&(J.add(i),Y());let a=H(),o=r(t.t)&&t.t<=a?t.t:a;if(!(Math.abs(o-Ce)<50)){if(Ce=o,e.source===i.contentWindow){let e=De(i);W(t.x+e.x,t.y+e.y,null,o)}else W(t.x,t.y,e.source,o)}}),g.addEventListener(`click`,ye),xe(document,null),G.add(document),Se(document),fe(),c.showNightModeButton&&b&&(x(),setInterval(x,5e3)),c.showFullscreenButton&&C()&&document.addEventListener(`fullscreenchange`,ie);let ke=[`position`,`iframeMode`,`iconSize`,`magnification`,`magnificationScale`,`showNightModeButton`,`showFullscreenButton`,`showExitButton`];async function Ae(){try{let t=c,n=c;if(a)n=await s()||t;else{let[r,i]=await Promise.all([fetch(`/plugins/signalk-app-dock/config`),fetch(`/plugins/signalk-app-dock/settings`)]);if(r.ok){let i=await r.json(),a=i.configuration||i;n={...e,...a,apps:t.apps}}if(i.ok){let e=await i.json();Array.isArray(e.apps)&&e.apps.length>0&&(n={...n,apps:e.apps})}}if(ke.some(e=>t[e]!==n[e])){window.location.reload();return}JSON.stringify(n.apps)===JSON.stringify(t.apps)?c=n:(c=n,fe())}catch{}}l&&setInterval(Ae,5e3);let X=document.getElementById(`tour-overlay`),je=document.getElementById(`tour-btn-got-it`),Z=document.getElementById(`tour-btn-dismiss`);function Q(){X?.classList.remove(`visible`),N()}function Me(){X?.classList.add(`visible`),M()}je&&je.addEventListener(`click`,Q),!l||!o?(Z&&Z.remove(),document.getElementById(`tour-buttons`)?.style.setProperty(`justify-content`,`center`)):Z&&Z.addEventListener(`click`,()=>{Q(),fetch(`/plugins/signalk-app-dock/dismiss-tour`,{method:`POST`}).catch(()=>{})});let $=document.getElementById(`tour-link`);$&&(a?$.replaceWith(document.createTextNode(`Plugin Config (admin only)`)):$.addEventListener(`click`,e=>{e.preventDefault();let t=`/admin/#/serverConfiguration/plugins/signalk-app-dock`;le();let n=document.createElement(`iframe`);n.allow=`fullscreen; geolocation`,n.setAttribute(`sandbox`,I),n.style.touchAction=`auto`,n.classList.add(`active`),u.innerHTML=``,Object.keys(v).forEach(e=>delete v[e]),u.appendChild(n),n.src=t,v[t]=n,Q()})),c.tourDismissed||Me();let Ne=c.apps.findIndex(e=>e.autostart);Ne>=0?R(Ne):ce()})().catch(e=>console.error(`[Dock] failed to start`,e));
|