@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/public/dock.js +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signalk/app-dock",
3
- "version": "1.7.0",
3
+ "version": "1.7.1",
4
4
  "description": "macOS-style configurable app dock for switching between SignalK webapps on touch screens",
5
5
  "repository": {
6
6
  "url": "https://github.com/SignalK/app-dock"
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));