@mxtommy/kip 4.5.0-beta.1 → 4.5.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/CHANGELOG.md +18 -0
- package/README.md +11 -7
- package/package.json +21 -8
- package/plugin/duckdb-parquet-storage.service.js +1182 -0
- package/plugin/history-series.service.js +439 -0
- package/plugin/index.js +705 -30
- package/plugin/openApi.json +253 -3
- package/plugin/plugin-auth.service.js +75 -0
- package/public/assets/help-docs/chartplotter.md +5 -18
- package/public/assets/help-docs/community.md +0 -3
- package/public/assets/help-docs/configuration.md +1 -1
- package/public/assets/help-docs/contact-us.md +0 -4
- package/public/assets/help-docs/dashboards.md +20 -18
- package/public/assets/help-docs/datainspector.md +7 -5
- package/public/assets/help-docs/history-api.md +116 -0
- package/public/assets/help-docs/menu.json +18 -6
- package/public/assets/help-docs/nodered-control-flows.md +125 -0
- package/public/assets/help-docs/putcontrols.md +101 -60
- package/public/assets/help-docs/welcome.md +6 -7
- package/public/assets/help-docs/widget-historical-series.md +66 -0
- package/public/assets/help-docs/zones.md +5 -10
- package/public/chunk-A6DQJFP4.js +16 -0
- package/public/chunk-B75MT7ND.js +1 -0
- package/public/{chunk-T6TFVZVM.js → chunk-CEB42O2C.js} +1 -1
- package/public/chunk-CHGXAEKT.js +2 -0
- package/public/chunk-D7VDX7ZF.js +5 -0
- package/public/{chunk-ZQER6AIQ.js → chunk-DEGYRCMI.js} +1 -1
- package/public/{chunk-M2B5OYGO.js → chunk-DEM56G4S.js} +1 -1
- package/public/chunk-DYTBBUMI.js +4 -0
- package/public/chunk-EQ2N7KDA.js +3 -0
- package/public/chunk-FNF7M3AE.js +1 -0
- package/public/chunk-IHURI4IH.js +5 -0
- package/public/{chunk-YIYYVDFO.js → chunk-IYRLINL7.js} +2 -2
- package/public/{chunk-5FEX27I4.js → chunk-JB4YVVNW.js} +1 -1
- package/public/chunk-JGGMFMY5.js +1 -0
- package/public/chunk-KPHICV76.js +5 -0
- package/public/{chunk-QZKCRH3H.js → chunk-KZ5DUKAX.js} +1 -1
- package/public/{chunk-HMOOTAEA.js → chunk-LQDSU4WS.js} +3 -3
- package/public/{chunk-IXQ7KIFY.js → chunk-MGPPVLZ7.js} +1 -1
- package/public/{chunk-QVCLOCEC.js → chunk-R7RQHWKJ.js} +1 -1
- package/public/chunk-RONXIZ2U.js +9 -0
- package/public/chunk-S72JTJPN.js +6 -0
- package/public/{chunk-KFFAA7DL.js → chunk-VCY32MWT.js} +8 -8
- package/public/chunk-YCEXTKGG.js +1 -0
- package/public/chunk-YKJKIWXO.js +6 -0
- package/public/chunk-ZV7IYYEQ.js +50 -0
- package/public/index.html +1 -1
- package/public/main-FQESQQV6.js +1 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -84
- package/.github/ISSUE_TEMPLATE/config.yml +0 -5
- package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -35
- package/.github/copilot-instructions.md +0 -205
- package/.github/instructions/angular.instructions.md +0 -123
- package/.github/instructions/best-practices.instructions.md +0 -59
- package/.github/instructions/project.instructions.md +0 -432
- package/.github/workflows/ci.yml +0 -37
- package/docs/widget-schematic.md +0 -102
- package/images/ActionSidenav.png +0 -0
- package/images/ChartplotterMode.png +0 -0
- package/images/KIPDemo.png +0 -0
- package/images/KipBrightness-1024.png +0 -0
- package/images/KipConfig-Units-1024.png +0 -0
- package/images/KipConfig-display-1024x488.png +0 -0
- package/images/KipFreeboard-SK-1024.png +0 -0
- package/images/KipGaugeSample1-1024x545.png +0 -0
- package/images/KipGaugeSample2-1024x488.png +0 -0
- package/images/KipGaugeSample3-1024x508.png +0 -0
- package/images/KipNightMode-1024.png +0 -0
- package/images/KipWidgetConfig-layout-1024.png +0 -0
- package/images/KipWidgetConfig-paths-1024x488.png +0 -0
- package/images/Options.png +0 -0
- package/images/exterior_user_installs.png +0 -0
- package/images/formfactor.png +0 -0
- package/public/assets/help-docs/datasets.md +0 -95
- package/public/chunk-2OB7ZJBR.js +0 -3
- package/public/chunk-6GGJZDRE.js +0 -1
- package/public/chunk-6V4GGGXE.js +0 -2
- package/public/chunk-A5BW6BUM.js +0 -1
- package/public/chunk-DGE5YFPU.js +0 -5
- package/public/chunk-G6M3Z3BY.js +0 -53
- package/public/chunk-GMGZLXY7.js +0 -4
- package/public/chunk-GUZ3BDVZ.js +0 -2
- package/public/chunk-ICDGHQFP.js +0 -6
- package/public/chunk-JCNE4QHQ.js +0 -15
- package/public/chunk-K6XYUNG4.js +0 -8
- package/public/chunk-LGCQEN7V.js +0 -4
- package/public/chunk-O3JH7UTR.js +0 -1
- package/public/chunk-Q3USFT4F.js +0 -2
- package/public/chunk-VIKU7BH7.js +0 -1
- package/public/chunk-XMQPXXLW.js +0 -8
- package/public/main-4URMGBQS.js +0 -1
- package/rm-npmjs-beta.sh +0 -50
- package/tools/schematics/collection.json +0 -9
- package/tools/schematics/create-host2-widget/files/readme/README.md.template +0 -109
- package/tools/schematics/create-host2-widget/files/spec/widget-__name@dasherize__.component.spec.ts +0 -38
- package/tools/schematics/create-host2-widget/files/widget/widget-__name@dasherize__.component.html +0 -6
- package/tools/schematics/create-host2-widget/files/widget/widget-__name@dasherize__.component.scss +0 -5
- package/tools/schematics/create-host2-widget/files/widget/widget-__name@dasherize__.component.ts.template +0 -94
- package/tools/schematics/create-host2-widget/index.js +0 -138
- package/tools/schematics/create-host2-widget/schema.json +0 -89
- package/tools/schematics/create-host2-widget/test/create-host2-widget.spec.ts +0 -70
- package/tools/schematics/create-host2-widget/utils/formatting.js +0 -119
package/public/index.html
CHANGED
|
@@ -62,5 +62,5 @@
|
|
|
62
62
|
</div>
|
|
63
63
|
</app-root>
|
|
64
64
|
</div>
|
|
65
|
-
<link rel="modulepreload" href="chunk-
|
|
65
|
+
<link rel="modulepreload" href="chunk-EQ2N7KDA.js"><link rel="modulepreload" href="chunk-ZV7IYYEQ.js"><link rel="modulepreload" href="chunk-KZ5DUKAX.js"><link rel="modulepreload" href="chunk-RONXIZ2U.js"><link rel="modulepreload" href="chunk-R7RQHWKJ.js"><link rel="modulepreload" href="chunk-MGPPVLZ7.js"><link rel="modulepreload" href="chunk-S72JTJPN.js"><link rel="modulepreload" href="chunk-LQDSU4WS.js"><link rel="modulepreload" href="chunk-CHGXAEKT.js"><link rel="modulepreload" href="chunk-DYTBBUMI.js"><script src="polyfills-L4FJGPOC.js" type="module"></script><script src="scripts-M6PHZBML.js" defer></script><script src="main-FQESQQV6.js" type="module"></script></body>
|
|
66
66
|
</html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as xe,b as vt}from"./chunk-EQ2N7KDA.js";import{a as Vt,b as A,c as H,i as he,k as E,l as _e,m as lt,o as ve,p as be,s as Ce,u as dt}from"./chunk-ZV7IYYEQ.js";import{a as Ie}from"./chunk-KZ5DUKAX.js";import{c as le}from"./chunk-RONXIZ2U.js";import{a as Oe,b as Pe,c as Ne,d as De,e as Ae,f as Te}from"./chunk-R7RQHWKJ.js";import{a as ke}from"./chunk-MGPPVLZ7.js";import{c as pe,d as Se,g as ye,h as we,j as Me}from"./chunk-S72JTJPN.js";import{b as Ee}from"./chunk-LQDSU4WS.js";import{e as ge}from"./chunk-CHGXAEKT.js";import{c as de,d as me,e as fe,f as ue}from"./chunk-DYTBBUMI.js";import{o as st,p as ct,q as B,r as z}from"./chunk-KPHICV76.js";import{a as Zt,b as rt,d as te,e as k,g as ee,h as ie,i as oe}from"./chunk-JB4YVVNW.js";import{a as ce}from"./chunk-CEB42O2C.js";import{f as ae,g as re,t as se}from"./chunk-D7VDX7ZF.js";import{c as Kt,e as Xt,g as ot,h as K,i as nt,k as D,l as at,m as x,n as ne}from"./chunk-IHURI4IH.js";import{$b as Pt,A as yt,Aa as Y,Ac as Et,Ad as Ht,Bb as V,Cb as F,Cd as $t,Db as M,Dd as qt,Eb as r,Ec as Rt,Ee as it,Fa as kt,Fb as s,Fc as tt,Ga as w,Gb as I,Ha as q,Hc as _t,Ic as Ut,Ja as It,K as U,Ke as Yt,Nb as v,Ne as Jt,Oe as R,Pb as h,Pe as _,Rb as g,Re as L,Se as T,U as wt,Vd as jt,Wa as f,Zb as X,_b as Z,a as Ct,ba as Mt,bc as Nt,c as St,cd as Ft,ce as Wt,ec as d,f as $,fc as j,gb as P,gc as Dt,ge as et,he as Gt,je as N,ka as S,kc as ut,ke as Qt,lc as ht,m as mt,mc as gt,pa as n,ra as xt,rb as ft,ua as p,va as m,vb as Ot,vc as At,vd as Lt,wd as Bt,xb as b,xc as Tt,xd as zt,ya as Q,yb as C,z as G,zb as J}from"./chunk-A6DQJFP4.js";var pt=(i,c)=>{let t=n(x),e=n(k),o=t.getSplitShellEnabled(),a="/"+c.map(y=>y.path).join("/"),l=a.startsWith("/chartplotter"),u=a.startsWith("/dashboard");if(o&&u&&!l){let y=c.length>1?c[1].path:void 0;return e.createUrlTree(["chartplotter",y??""])}if(!o&&l){let y=c.length>1?c[1].path:void 0;return e.createUrlTree(["dashboard",y??""])}return!0};var Re=[{path:"dashboard/:id",component:vt,canMatch:[pt]},{path:"chartplotter/:id",loadComponent:()=>import("./chunk-JGGMFMY5.js").then(i=>i.SplitShellComponent),canMatch:[pt]},{path:"settings",loadComponent:()=>import("./chunk-IYRLINL7.js").then(i=>i.SettingsComponent),title:"KIP - Settings"},{path:"options",loadComponent:()=>import("./chunk-VCY32MWT.js").then(i=>i.TabsComponent),title:"KIP - Options"},{path:"remote",loadComponent:()=>import("./chunk-B75MT7ND.js").then(i=>i.RemoteControlComponent),title:"KIP - Remote Control"},{path:"help/:page",loadComponent:()=>import("./chunk-FNF7M3AE.js").then(i=>i.AppHelpComponent),title:"KIP - Help"},{path:"help",loadComponent:()=>import("./chunk-FNF7M3AE.js").then(i=>i.AppHelpComponent),title:"KIP - Help"},{path:"data",loadComponent:()=>import("./chunk-YKJKIWXO.js").then(i=>i.DataInspectorComponent),title:"KIP - Data Inspector"},{path:"login",loadComponent:()=>import("./chunk-YCEXTKGG.js").then(i=>i.WidgetLoginComponent),title:"Login"},{path:"**",component:vt,canMatch:[pt]}];var Ue={production:!0};var Ye=(i,c)=>c.path;function Je(i,c){i&1&&(r(0,"mat-icon"),d(1,"info"),s())}function Xe(i,c){i&1&&(r(0,"mat-icon"),d(1,"info"),s())}function Ze(i,c){i&1&&(r(0,"mat-icon",3),d(1,"report"),s())}function ti(i,c){i&1&&(r(0,"mat-icon",4),d(1,"warning"),s())}function ei(i,c){i&1&&(r(0,"mat-icon",5),d(1,"error"),s())}function ii(i,c){i&1&&(r(0,"mat-icon",6),d(1,"emergency_home"),s())}function oi(i,c){if(i&1){let t=v();r(0,"div",2),b(1,Je,2,0,"mat-icon")(2,Xe,2,0,"mat-icon")(3,Ze,2,0,"mat-icon",3)(4,ti,2,0,"mat-icon",4)(5,ei,2,0,"mat-icon",5)(6,ii,2,0,"mat-icon",6),r(7,"div",7),d(8),s(),r(9,"div",8),d(10),At(11,"slice"),s(),r(12,"div",9),d(13),s(),r(14,"button",10),h("click",function(){let o=p(t).$implicit,a=g();return m(a.silence(o.path))}),r(15,"mat-icon"),d(16,"music_off"),s()(),r(17,"button",11),h("click",function(){let o=p(t).$implicit,a=g();return m(a.clear(o.path))}),r(18,"mat-icon"),d(19,"published_with_changes"),s()()(),I(20,"mat-divider",12)}if(i&2){let t,e=c.$implicit;f(),C((t=e.value.state)==="nominal"?1:t==="normal"?2:t==="alert"?3:t==="warn"?4:t==="alarm"?5:t==="emergency"?6:-1),f(7),j(e.value.state),f(2),Dt(" ",Tt(11,5,e.path,14)," "),f(3),j(e.value.message),f(),M("disabled",!(e.value.method!=null&&e.value.method.includes("sound")))}}function ni(i,c){i&1&&(r(0,"mat-list-item",13)(1,"span",15),d(2,"Notifications Disabled"),s(),r(3,"span")(4,"i"),d(5,"*Enable notifications in Settings."),s()()())}function ai(i,c){i&1&&(r(0,"mat-list-item",14)(1,"i"),d(2,'"No Notification"'),s()())}function ri(i,c){if(i&1&&b(0,ni,6,0,"mat-list-item",13)(1,ai,3,0,"mat-list-item",14),i&2){let t=g();C(t.notificationConfig().disableNotifications?0:1)}}function si(i,c){if(i&1){let t=v();r(0,"button",17),h("click",function(){p(t);let o=g(2);return m(o.mutePlayer(!o.isMuted))}),r(1,"span",18),d(2,"volume_up"),s(),d(3," Unmute Audio "),s()}}function ci(i,c){if(i&1){let t=v();r(0,"button",17),h("click",function(){p(t);let o=g(2);return m(o.mutePlayer(!o.isMuted))}),r(1,"span",18),d(2,"volume_off"),s(),d(3," Mute Audio "),s()}}function li(i,c){if(i&1&&b(0,si,4,0,"button",16)(1,ci,4,0,"button",16),i&2){let t=g();C(t.isMuted?0:1)}}var Ke=(()=>{class i{_notificationsService=n(E);_notifications$=this._notificationsService.observeNotifications();notificationConfig=_(this._notificationsService.observeNotificationConfiguration(),{requireSync:!0});menuNotifications=_(yt([this._notifications$,this._notificationsService.observeNotificationConfiguration()]).pipe(G(([t,e])=>{let o=[];return e.devices.showNormalState||o.push(st.Normal),e.devices.showNominalState||o.push(st.Nominal),t.filter(a=>a.value&&a.value.state&&!o.includes(a.value.state)).filter(a=>{let l=a.value?.method;return l?.length?l.includes(ct.Visual)||l.includes(ct.Sound):!1})})),{requireSync:!0,equal:Xt});isMuted=!1;mutePlayer(t){this.isMuted=t,this._notificationsService.mutePlayer(t)}silence(t){this._notificationsService.setSkMethod(t,[ct.Visual])}clear(t){this._notificationsService.setSkState(t,st.Normal)}static \u0275fac=function(e){return new(e||i)};static \u0275cmp=P({type:i,selectors:[["menu-notifications"]],decls:6,vars:2,consts:[[1,"menu-item-container"],[1,"actions-container","actions-bottom-container"],[1,"notification-container"],[1,"icon-alert-color"],[1,"icon-warn-color"],[1,"icon-alarm-color"],[1,"icon-emergency-color"],[1,"notification-title"],[1,"notification-path","scrollable-text"],[1,"notification-text"],["mat-icon-button","",1,"notification-action-btn",3,"click","disabled"],["mat-icon-button","",1,"notification-action-btn",3,"click"],[1,"notification-divider"],["lines","3","disabled","true",2,"text-align","center"],["disabled","true",2,"text-align","center"],["matListItemTitle",""],["mat-flat-button","","matTooltip","Temporally toggle all notification audio. To permanently disable/enable notification audio, use the configuration settings option",1,"action-button"],["mat-flat-button","","matTooltip","Temporally toggle all notification audio. To permanently disable/enable notification audio, use the configuration settings option",1,"action-button",3,"click"],[1,"material-icons"]],template:function(e,o){e&1&&(r(0,"mat-list",0),V(1,oi,21,8,null,null,Ye,!1,ri,2,1),s(),r(4,"div",1),b(5,li,2,1),s()),e&2&&(f(),F(o.menuNotifications()),f(4),C(o.notificationConfig().disableNotifications?-1:5))},dependencies:[Me,ye,we,ce,Se,N,et,Wt,lt,ue,fe,T,L,Ft],styles:[".notification-container[_ngcontent-%COMP%]{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-evenly;padding:2px 10px 7px;scroll-behavior:smooth}mat-icon[_ngcontent-%COMP%]{margin-right:10px}.scrollable-text[_ngcontent-%COMP%]{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.scrollable-text[_ngcontent-%COMP%]:hover{overflow:auto;white-space:normal;word-wrap:break-word}.notification-title[_ngcontent-%COMP%]{text-transform:capitalize;font-size:var(--mat-sys-headline-small-size);width:calc(100% - 34px)}.notification-path[_ngcontent-%COMP%]{font-size:var(--mat-sys-body-small-size);word-break:break-all;width:100%}.notification-text[_ngcontent-%COMP%]{font-size:var(--mat-sys-body-small-line-height);font-style:italic;width:100%;overflow:auto;white-space:normal;word-wrap:break-word}.notification-action-btn[_ngcontent-%COMP%]{background-color:var(--mat-sys-surface-container)}.notification-btn-container[_ngcontent-%COMP%]{display:inline-block;width:50%;height:48px;text-align:center}.menu-item-container[_ngcontent-%COMP%]{width:230px;overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;height:calc(100% - 48px)}.notification-bottom-container[_ngcontent-%COMP%]{position:absolute;bottom:0;width:100%;background-color:var(--mat-sys-surface-container)}.actions-container[_ngcontent-%COMP%]{display:grid;grid-auto-flow:column}.actions-bottom-container[_ngcontent-%COMP%]{position:absolute;bottom:0;width:100%;height:48px}.action-button[_ngcontent-%COMP%]{border-radius:0;height:48px;border-bottom-right-radius:var(--mat-sidenav-container-shape, var(--mat-sys-corner-large))}.icon-alert-color[_ngcontent-%COMP%]{color:var(--kip-zone-alert-color)}.icon-warn-color[_ngcontent-%COMP%]{color:var(--kip-zone-warn-color)}.icon-alarm-color[_ngcontent-%COMP%]{color:var(--kip-zone-alarm-color)}.icon-emergency-color[_ngcontent-%COMP%]{color:var(--kip-zone-emergency-color)}.alarm-emergency-blink[_ngcontent-%COMP%]{animation:_ngcontent-%COMP%_blinking-emergency 1.5s infinite}@keyframes _ngcontent-%COMP%_blinking-emergency{0%{background-color:var(--kip-zone-alarm-color)}50%{background-color:transparent}to{background-color:var(--kip-zone-alarm-color)}}.warn[_ngcontent-%COMP%]{color:var(--kip-zone-warn-color)}.notification-divider[_ngcontent-%COMP%]{padding-top:10px}"],changeDetection:0})}return i})();var di=["badgeButton"],Le=(()=>{class i{badgeButton=tt.required("badgeButton");_notifications=n(E);notificationsInfo=_(this._notifications.observerNotificationsInfo());openNotificationMenu(){let t=new Event("openRightSidenav",{bubbles:!0,cancelable:!0});window.document.dispatchEvent(t)}onKeyDown(t){let e=t.key?.toLowerCase();(e==="enter"||e===" "||e==="spacebar")&&(t.preventDefault(),this.openNotificationMenu())}focus(){try{let t=this.badgeButton&&this.badgeButton(),e=t&&t.nativeElement;e&&typeof e.focus=="function"&&e.focus()}catch(t){}}static \u0275fac=function(e){return new(e||i)};static \u0275cmp=P({type:i,selectors:[["notification-badge"]],viewQuery:function(e,o){e&1&&X(o.badgeButton,di,5),e&2&&Z()},decls:5,vars:7,consts:[["badgeButton",""],["mat-fab","","role","button","aria-label","Open notifications",1,"layout-action-btn",3,"click","keydown"],["aria-hidden","false","matBadgeSize","large","matBadgePosition","after","matBadgeOverlap","true",1,"icon-alert-color",3,"matBadgeHidden","matBadge"]],template:function(e,o){if(e&1){let a=v();r(0,"div")(1,"button",1,0),h("click",function(){return p(a),m(o.openNotificationMenu())})("keydown",function(u){return p(a),m(o.onKeyDown(u))}),r(3,"mat-icon",2),d(4,"notifications "),s()()()}e&2&&(f(),Nt("warn",o.notificationsInfo().isWarn)("alarm-emergency-blink",o.notificationsInfo().isAlarmEmergency),Ot("aria-haspopup",!0),f(2),M("matBadgeHidden",!o.notificationsInfo().alarmCount)("matBadge",o.notificationsInfo().alarmCount))},dependencies:[N,Gt,T,L,lt,_e],styles:["[_nghost-%COMP%]{display:block}.layout-action-btn[_ngcontent-%COMP%]{color:var(--mat-sys-on-primary);background-color:var(--mat-sys-primary)}.icon-alert-color[_ngcontent-%COMP%]{--mat-list-list-item-leading-icon-color: var(--kip-zone-alert-color)}.icon-warn-color[_ngcontent-%COMP%]{--mat-list-list-item-leading-icon-color: var(--kip-zone-warn-color)}.icon-alarm-color[_ngcontent-%COMP%]{--mat-list-list-item-leading-icon-color: var(--kip-zone-alarm-color)}.icon-emergency-color[_ngcontent-%COMP%]{--mat-list-list-item-leading-icon-color: var(--kip-zone-emergency-color)}.alarm-emergency-blink[_ngcontent-%COMP%]{animation:_ngcontent-%COMP%_blinking-emergency 1.5s infinite}@keyframes _ngcontent-%COMP%_blinking-emergency{0%{background-color:var(--kip-zone-alarm-color)}50%{background-color:var(--mat-sys-primary)}to{background-color:var(--kip-zone-alarm-color)}}.warn[_ngcontent-%COMP%]{color:var(--kip-zone-warn-color)}"]})}return i})();var Be=(()=>{class i{overlayRef=null;overlay=n(Yt);injector=n(Q);open(){if(this.overlayRef)return;this.overlayRef=this.overlay.create({hasBackdrop:!1,panelClass:"notification-overlay-panel",positionStrategy:this.overlay.position().global().bottom("20px").left("20px"),scrollStrategy:this.overlay.scrollStrategies.reposition()});let t=new Qt(Le,null,this.injector),e=this.overlayRef.attach(t);try{let o=e&&e.instance;if(o&&typeof o.focus=="function")o.focus();else{let a=this.overlayRef.overlayElement,l=a&&a.querySelector("button[mat-fab], .layout-action-btn");l&&typeof l.focus=="function"&&l.focus()}}catch(o){}}close(){try{this.overlayRef&&this.overlayRef.dispose()}catch(t){console.warn("[NotificationOverlayService] dispose failed",t)}finally{this.overlayRef=null}}toggle(t){t?this.open():this.close()}isOpen(){return!!this.overlayRef}ngOnDestroy(){try{this.overlayRef&&this.overlayRef.dispose()}catch(t){}finally{this.overlayRef=null}}static \u0275fac=function(e){return new(e||i)};static \u0275prov=S({token:i,factory:i.\u0275fac,providedIn:"root"})}return i})();function pi(i,c){if(i&1){let t=v();r(0,"button",12),h("click",function(){p(t);let o=g(2);return m(o.onActionItem("toggleFullScreen"))}),r(1,"span",10),d(2,"fullscreen"),s()()}}function mi(i,c){if(i&1){let t=v();r(0,"button",12),h("click",function(){p(t);let o=g(2);return m(o.onActionItem("toggleFullScreen"))}),r(1,"span",10),d(2,"close_fullscreen"),s()()}}function fi(i,c){if(i&1&&b(0,pi,3,0,"button",11)(1,mi,3,0,"button",11),i&2){let t=g();C(t.uiEvent.fullscreenStatus()?1:0)}}function ui(i,c){if(i&1){let t=v();r(0,"button",14),h("click",function(){p(t);let o=g(2);return m(o.onActionItem("nightMode"))}),r(1,"span",10),d(2,"mode_night"),s()()}}function hi(i,c){if(i&1){let t=v();r(0,"button",14),h("click",function(){p(t);let o=g(2);return m(o.onActionItem("nightMode"))}),r(1,"span",10),d(2,"light_mode"),s()()}}function gi(i,c){if(i&1&&b(0,ui,3,0,"button",13)(1,hi,3,0,"button",13),i&2){let t=g();C(t.app.isNightMode()?1:0)}}function _i(i,c){if(i&1){let t=v();r(0,"tile-large-icon",15,1),h("click",function(){let o=p(t).$index,a=g();return m(a.navigateToDashboard(o))}),s()}if(i&2){let t=c.$implicit;M("active",t.active)("svgIcon",t.svgIcon)("iconSize",t.iconSize)("label",t.label)}}var ze=(()=>{class i{actionsSidenav=Rt.required();_router=n(k);uiEvent=n(dt);dashboard=n(A);app=n(le);_settings=n(x);isAutoNightMode=_(this._settings.getAutoNightModeAsO(),{requireSync:!0});_initialDashboardMatch=(()=>{let e=this._router.parseUrl(this._router.url).root.children.primary,o=e?e.segments.map(a=>a.path):[];return o.length===0||o[0]==="dashboard"||o[0]==="chartplotter"})();isDashboardContext=_(this._router.events.pipe(U(t=>t instanceof rt),Mt(null),G(()=>{let e=this._router.parseUrl(this._router.url).root.children.primary,o=e?e.segments.map(a=>a.path):[];return o.length===0||o[0]==="dashboard"||o[0]==="chartplotter"}),wt()),{initialValue:this._initialDashboardMatch});menuItems=Et(()=>{let t=this.dashboard.dashboards(),e=this.dashboard.activeDashboard();return t.map((o,a)=>({id:Number(o.id),active:a===e,pinned:!1,svgIcon:o.icon||"dashboard-dashboard",iconSize:60,label:o.name||`Dashboard ${a+1}`}))});ngAfterViewInit(){this.uiEvent.addHotkeyListener((t,e)=>this.handleKeyDown(t,e),{ctrlKey:!0,shiftKey:!0,keys:["e","f","n"]})}ngOnDestroy(){this.uiEvent.removeHotkeyListener(this.handleKeyDown.bind(this))}handleKeyDown(t,e){switch(t){case"e":this.onActionItem("layout");break;case"f":this.onActionItem("toggleFullScreen");break;case"n":this.onActionItem("nightMode");break;default:break}}onActionItem(t){switch(this.actionsSidenav().close(),t){case"toggleFullScreen":this.uiEvent.toggleFullScreen();break;case"settings":this._router.navigate(["/settings"]);break;case"layout":this.dashboard.setStaticDashboard(!1);break;case"nightMode":this.app.isNightMode.set(!this.app.isNightMode()),this.app.toggleDayNightMode();break;default:break}}navigateToDashboard(t){this.actionsSidenav().close(),this._router.navigate([`dashboard/${t}`])}static \u0275fac=function(e){return new(e||i)};static \u0275cmp=P({type:i,selectors:[["menu-actions"]],inputs:{actionsSidenav:[1,"actionsSidenav"]},decls:14,vars:3,consts:[["tileContainer",""],["tile",""],[1,"actions-container"],[1,"actions-top-container"],["mat-flat-button","","tabindex","0",1,"action-button","home-btn",3,"click"],["height","24px","width","24px","svgIcon","kip","aria-hidden","false",1,"home-icon",2,"height","24px","width","24px"],[1,"menu-item-container"],[3,"active","svgIcon","iconSize","label"],[1,"actions-bottom-container"],["mat-flat-button","",1,"action-button",2,"width","135px",3,"click","disabled"],[1,"material-icons"],["mat-flat-button","","tabindex","0",1,"action-button"],["mat-flat-button","","tabindex","0",1,"action-button",3,"click"],["mat-flat-button","",1,"action-button"],["mat-flat-button","",1,"action-button",3,"click"],[3,"click","active","svgIcon","iconSize","label"]],template:function(e,o){if(e&1){let a=v();r(0,"div",2)(1,"div",3),b(2,fi,2,1),b(3,gi,2,1),r(4,"button",4),h("click",function(){return p(a),m(o.onActionItem("settings"))}),I(5,"mat-icon",5),s()(),r(6,"div",6,0),V(8,_i,2,4,"tile-large-icon",7,J),s(),r(10,"div",8)(11,"button",9),h("click",function(){return p(a),m(o.onActionItem("layout"))}),r(12,"span",10),d(13,"lock_open"),s()()()()}e&2&&(f(2),C(o.uiEvent.fullscreenSupported()?2:-1),f(),C(o.isAutoNightMode()?-1:3),f(5),F(o.menuItems()),f(3),M("disabled",!o.isDashboardContext()))},dependencies:[T,L,N,et,ke],styles:[".actions-container[_ngcontent-%COMP%]{display:flex;flex-direction:column;height:100%;width:135px}.actions-top-container[_ngcontent-%COMP%]{display:flex;flex-direction:row;flex-wrap:wrap;width:100%}.actions-bottom-container[_ngcontent-%COMP%]{height:48px;width:100%}.action-button[_ngcontent-%COMP%]{border-radius:0;height:48px;flex:1 1 48px;min-height:0;overflow:hidden;border:1px solid var(--mat-sidenav-container-background-color, var(--mat-sys-surface))}.home-btn[_ngcontent-%COMP%]{--mat-icon-button-icon-size: 24px;--mat-button-filled-icon-offset: 0px;--mat-button-filled-icon-spacing: 0px;--mat-button-filled-label-text-size: 0px}.home-icon[_ngcontent-%COMP%] .mat-mdc-unelevated-button[_ngcontent-%COMP%] > .mat-icon[_ngcontent-%COMP%]{font-size:0px}.full-size[_ngcontent-%COMP%]{width:135px}.menu-item-container[_ngcontent-%COMP%]{display:flex;flex-direction:column;flex-wrap:nowrap;padding:3px 1px;gap:5px;width:135px;overflow-y:auto;scrollbar-width:none;overflow-x:hidden;scroll-behavior:smooth;flex:1 1 auto;min-height:0}"]})}return i})();var He=(()=>{class i{COMMAND_SET_DISPLAY_PATH="self.kip.remote.setDisplay";COMMAND_SET_SCREEN_INDEX_PATH="self.kip.remote.setScreenIndex";COMMAND_REQUEST_ACTIVE_SCREEN_PATH="self.kip.remote.requestActiveScreen";settings=n(x);dashboard=n(A);data=n(z);requests=n(pe);KIP_UUID=this.settings.KipUUID;ACTIVE_SCREEN_PATH=`self.displays.${this.KIP_UUID}.screenIndex`;CHANGE_SCREEN_PATH=`self.displays.${this.KIP_UUID}.activeScreen`;displayName=_(this.settings.getInstanceNameAsO());isRemoteControl=_(this.settings.getIsRemoteControlAsO());remoteScreenPosition=_(this.data.subscribePath(this.ACTIVE_SCREEN_PATH,"default"));changeDashboardTo=_(this.data.subscribePath(this.CHANGE_SCREEN_PATH,"default"));previousIsRemoteControl=!1;constructor(){this.setActiveDashboardOnRemote(this.KIP_UUID,null),this.setScreensOnRemote(this.KIP_UUID,null),this.clearActiveScreenOnRemote(this.KIP_UUID,null),console.log("[Remote Dashboards] Cleaning paths on server"),w(()=>{let t=this.isRemoteControl(),e=this.displayName();q(()=>{if(!t&&!this.previousIsRemoteControl)return;this.previousIsRemoteControl=t;let o;t?(o=this.getScreensPayload(this.dashboard.dashboards()),this.dashboard.activeDashboard()!==null&&this.setActiveDashboardOnRemote(this.KIP_UUID,this.dashboard.activeDashboard())):(o=null,this.clearActiveDashboardOnServer()),this.shareScreens(o)})}),w(()=>{let t=this.dashboard.dashboards();q(()=>{if(!this.isRemoteControl())return;let e=this.getScreensPayload(t);this.shareScreens(e)})}),w(()=>{let t=this.dashboard.activeDashboard();q(()=>{this.isRemoteControl()&&t!==null&&(this.setActiveDashboardOnRemote(this.KIP_UUID,t),console.log(`[Remote Dashboards] Sent new dashboard highlight index ${t} to server.`))})}),w(()=>{let t=this.changeDashboardTo();q(()=>{if(!this.isRemoteControl()||t.data.value==null)return;let e=Number(t.data.value);!isNaN(e)&&e>=0&&e<this.dashboard.dashboards().length&&this.dashboard.activeDashboard()!==e&&(this.dashboard.navigateTo(e),console.log(`[Remote Dashboards] Executed remote request to change active dashboard to: ${e}`))})})}clearActiveDashboardOnServer(){this.setActiveDashboardOnRemote(this.KIP_UUID,null),console.log("[Remote Dashboards] Disabled: Cleared active dashboard on server")}getScreensPayload(t){let e=this.displayName(),o=t.map(u=>{var y=u,{configuration:a}=y,l=St(y,["configuration"]);return l});return{displayName:e,screens:o}}shareScreens(t){this.setScreensOnRemote(this.KIP_UUID,t),console.log("[Remote Dashboards] Sending dashboard configurations to server.")}setActiveDashboardOnRemote(t,e){let o={displayId:t,screenIdx:e};this.requests.putRequest(this.COMMAND_SET_SCREEN_INDEX_PATH,o,this.KIP_UUID)||console.error("[Remote Dashboards] Error sharing active dashboard: request was not accepted")}setScreensOnRemote(t,e){let o={displayId:t,display:e===null?null:Ct({},e)};this.requests.putRequest(this.COMMAND_SET_DISPLAY_PATH,o,this.KIP_UUID)||console.error("[Remote Dashboards] Error sharing screen configuration: request was not accepted")}clearActiveScreenOnRemote(t,e){let o={displayId:t,screenIdx:e};this.requests.putRequest(this.COMMAND_REQUEST_ACTIVE_SCREEN_PATH,o,this.KIP_UUID)||console.error("[Remote Dashboards] Error clearing active screen request path: request was not accepted")}static \u0275fac=function(e){return new(e||i)};static \u0275prov=S({token:i,factory:i.\u0275fac,providedIn:"root"})}return i})();var $e=11,qe="connectionConfig",W=(()=>{class i{config;isLoggedIn=null;loggedInSubscription=null;connection=n(nt);auth=n(D);connectionStateMachine=n(K);router=n(k);delta=n(B);data=n(z);storage=n(at);internetReachability=n(Ie);injector=n(Q);datasetService=null;_bootstrapStatus$=new mt("starting");_bootstrapIssue$=new mt({reason:"none"});constructor(){this.loggedInSubscription=this.auth.isLoggedIn$.subscribe(t=>{this.isLoggedIn=t})}initNetworkServices(){return $(this,null,function*(){let t=!1;this.loadLocalStorageConfig(),this.preloadFonts(),this.internetReachability.start();try{if(this.config?.signalKUrl!==void 0&&this.config.signalKUrl!==null&&(yield this.connection.initializeConnection({url:this.config.signalKUrl,new:!1},this.config.proxyEnabled,this.config.signalKSubscribeAll)),!this.isLoggedIn&&this.config?.signalKUrl&&this.config?.useSharedConfig&&this.config?.loginName&&this.config?.loginPassword&&(yield this.login()),this.isLoggedIn&&this.config?.useSharedConfig)if(yield this.storage.waitUntilReady()){let o=yield this.storage.getConfig("user",this.config.sharedConfigName,$e),a={sharedConfigName:this.config.sharedConfigName,configFileVersion:$e,initConfig:o};this.storage.bootstrapRemoteContext(a)}else throw new Error("[AppInit Network Service] StorageService did not become ready in time. Cannot bootstrap remote configuration.");this._bootstrapIssue$.next({reason:"none"}),yield this.getDatasetService().waitUntilReady(),!this.isLoggedIn&&this.config?.signalKUrl&&this.config?.useSharedConfig&&this.router.navigate(["/login"])}catch(e){if(t=!0,e?.status===404&&this.config?.useSharedConfig){let o=yield this.probeLegacyUpgradeAvailability(this.config.sharedConfigName);this._bootstrapIssue$.next({reason:"missing-shared-config",statusCode:404,sharedConfigName:this.config.sharedConfigName,legacyUpgradeAvailable:o})}else e?.status===0?this._bootstrapIssue$.next({reason:"network-unreachable",statusCode:0}):e?.status===401?this._bootstrapIssue$.next({reason:"unauthorized",statusCode:401}):this._bootstrapIssue$.next({reason:"unknown",statusCode:e?.status});e.status===0?(yield this.waitForHttpRetryCompletion())===ot.HTTPConnected||this.connectionStateMachine.isHTTPConnected()?console.warn("[AppInit Network Service] Initial connection recovered during retry cycle. Skipping fallback route."):(console.warn("[AppInit Network Service] Initialization failed after HTTP retries. Redirecting to settings page."),yield this.router.navigate(["/options"])):e.status===401?(console.warn("[AppInit Network Service] Initialization failed. Unauthorized access. Redirecting to login page."),yield this.router.navigate(["/login"])):(console.warn("[AppInit Network Service] Initialization failed. Error: ",JSON.stringify(e)),yield this.router.navigate(["/options"])),console.warn("[AppInit Network Service] Startup continuing in degraded mode to allow UI feedback."),this._bootstrapStatus$.next("degraded");return}finally{t||this._bootstrapStatus$.next("ready"),console.log("[AppInit Network Service] Initialization completed"),this.connectionStateMachine.enableWebSocketMode(),this.connectionStateMachine.isHTTPConnected()&&(console.log("[AppInit Network Service] Starting WebSocket connection after initialization"),this.connectionStateMachine.startWebSocketConnection())}})}get bootstrapStatus$(){return this._bootstrapStatus$.asObservable()}get bootstrapIssue$(){return this._bootstrapIssue$.asObservable()}getDatasetService(){return this.datasetService||(this.datasetService=this.injector.get(H)),this.datasetService}waitForHttpRetryCompletion(t){return $(this,null,function*(){let e=t??this.connectionStateMachine.getHttpRetryWindowMs(2e3),o=new Set([ot.HTTPConnected,ot.PermanentFailure]),a=this.connectionStateMachine.currentState;return o.has(a)?a:new Promise(l=>{let u=window.setTimeout(()=>{y.unsubscribe(),l(null)},e),y=this.connectionStateMachine.state$.subscribe(bt=>{o.has(bt)&&(clearTimeout(u),y.unsubscribe(),l(bt))})})})}probeLegacyUpgradeAvailability(t){return $(this,null,function*(){if(!t)return!1;try{return(yield this.storage.getConfig("user",t,9))?.app?.configVersion===10}catch(e){return!1}})}login(){return $(this,null,function*(){if(!this.isLoggedIn&&this.config.useSharedConfig&&this.config.loginName&&this.config.loginPassword)try{yield this.auth.login({usr:this.config.loginName,pwd:this.config.loginPassword})}catch(t){throw t.status===0?this.router.navigate(["/settings"]):t.status===401&&this.router.navigate(["/login"]),t}})}setLocalStorageConfig(){localStorage.setItem(qe,JSON.stringify(this.config))}loadLocalStorageConfig(){this.config=JSON.parse(localStorage.getItem(qe)),this.config?this.config.signalKUrl||(this.config.signalKUrl=window.location.origin,this.setLocalStorageConfig(),console.log(`[AppInit Network Service] Config found with no server URL. Setting Auto-Discovery URL: ${this.config.signalKUrl}`)):(this.config=Kt,this.config.signalKUrl=window.location.origin,console.log(`[AppInit Network Service] Connection Configuration not found. Creating configuration using Auto-Discovery URL: ${this.config.signalKUrl}`),this.setLocalStorageConfig()),this.config.configVersion==9&&(this.config.configVersion=10,this.setLocalStorageConfig(),console.log("[AppInit Network Service] Upgrading Connection version from 9 to 10")),this.config.configVersion==10&&(this.config.configVersion=11,this.setLocalStorageConfig(),console.log("[AppInit Network Service] Upgrading Connection version from 10 to 11")),this.config.configVersion==11&&(this.config.configVersion=12,this.setLocalStorageConfig(),console.log("[AppInit Network Service] Upgrading Connection version from 11 to 12"))}preloadFonts(){let t=[{family:"Roboto",src:"url(./assets/google-fonts/KFOlCnqEu92Fr1MmSU5fChc4AMP6lbBP.woff2)",options:{weight:"300",style:"normal"}},{family:"Roboto",src:"url(./assets/google-fonts/KFOlCnqEu92Fr1MmSU5fBBc4AMP6lQ.woff2)",options:{weight:"300",style:"normal"}},{family:"Roboto",src:"url(./assets/google-fonts/KFOmCnqEu92Fr1Mu7GxKKTU1Kvnz.woff2)",options:{weight:"400",style:"normal"}},{family:"Roboto",src:"url(./assets/google-fonts/KFOmCnqEu92Fr1Mu4mxKKTU1Kg.woff2)",options:{weight:"400",style:"normal"}},{family:"Roboto",src:"url(./assets/google-fonts/KFOlCnqEu92Fr1MmEU9fChc4AMP6lbBP.woff2)",options:{weight:"500",style:"normal"}},{family:"Roboto",src:"url(./assets/google-fonts/KFOlCnqEu92Fr1MmEU9fBBc4AMP6lQ.woff2)",options:{weight:"500",style:"normal"}}];for(let{family:e,src:o,options:a}of t){let l=new FontFace(e,o,a);l.load().then(()=>document.fonts.add(l)).catch(u=>console.log(`[AppInit Network Service] Error loading fonts: ${u}`))}}ngOnDestroy(){this.loggedInSubscription?.unsubscribe()}static \u0275fac=function(e){return new(e||i)};static \u0275prov=S({token:i,factory:i.\u0275fac})}return i})();var vi=["upgradeMessages"];function bi(i,c){if(i&1&&(r(0,"li"),d(1),s()),i&2){let t=c.$implicit;f(),j(t)}}function Ci(i,c){if(i&1&&(r(0,"ul",11,1),V(2,bi,2,1,"li",null,J),s()),i&2){let t=g(2);f(2),F(t.upgrade.messages())}}function Si(i,c){if(i&1&&(r(0,"div",7)(1,"div",8),I(2,"mat-progress-spinner",9),r(3,"div",10)(4,"h3"),d(5,"Upgrading Configuration\u2026"),s(),r(6,"p"),d(7,"Please wait a moment while we migrate your dashboards and widgets."),s(),b(8,Ci,4,0,"ul",11),s()()()),i&2){let t=g();f(8),C(t.upgrade.messages().length?8:-1)}}var je=(()=>{class i{_deltaService=n(B);_connectionStateMachine=n(K);_appNetworkInit=n(W);_historySeriesReconcile=n(xe);authenticationService=n(D);_dataSet=n(H);_dashboard=n(A);_remoteControl=n(He);toast=n(ne);_notifications=n(E);_uiEvent=n(dt);_dialog=n(be);settings=n(x);_responsive=n(Lt);_destroyRef=n(Y);_notificationOverlay=n(Be);_router=n(k);upgrade=n(ve);upgradeMessagesRef=tt("upgradeMessages");_upgradeShown=!1;actionsSidenavOpened=_t(!1);notificationsSidenavOpened=_t(!1);notificationsInfo=_(this._notifications.observerNotificationsInfo());bootstrapStatus=_(this._appNetworkInit.bootstrapStatus$,{initialValue:"starting"});bootstrapIssue=_(this._appNetworkInit.bootstrapIssue$,{initialValue:{reason:"none"}});dashboardVisible=kt(!1);isPhonePortrait;scheduledOpen=null;OPEN_DELAY_MS=300;missingConfigPromptShown=!1;_swipeLeftHandler=()=>this.onSwipeLeft();_swipeRightHandler=()=>this.onSwipeRight();_hotkeyHandler=t=>this.handleKeyDown(t);constructor(){w(()=>{if(this.settings.configUpgrade()){let t=this.settings.getConfigVersion();t===11&&this.upgrade.runUpgrade(t),t||this._upgradeShown||(this._upgradeShown=!0,this._dialog.openFrameDialog({title:"Upgrade Instructions",component:"upgrade-config"},!0).pipe(R(this._destroyRef)).subscribe())}}),w(()=>{let t=this.upgrade.messages();if(this.upgrade.upgrading()&&t.length&&this.upgradeMessagesRef()){let e=this.upgradeMessagesRef().nativeElement;e.scrollTop=e.scrollHeight}});try{this.dashboardVisible.set(this.isUrlDashboard(this._router.url))}catch(t){}this._router.events.pipe(U(t=>t instanceof rt),R(this._destroyRef)).subscribe(t=>{try{this.dashboardVisible.set(this.isUrlDashboard(t.urlAfterRedirects||t.url))}catch(e){}}),w(()=>{let t=this.dashboardVisible()&&this._dashboard.isDashboardStatic()&&this.notificationsInfo().alarmCount>0;if(this.notificationsSidenavOpened()){this.scheduledOpen&&(clearTimeout(this.scheduledOpen),this.scheduledOpen=null);try{this._notificationOverlay.close()}catch(o){}return}if(t){if(this.scheduledOpen)return;this.scheduledOpen=window.setTimeout(()=>{this.scheduledOpen=null;try{this._notificationOverlay.open()}catch(o){}},this.OPEN_DELAY_MS)}else{this.scheduledOpen&&(clearTimeout(this.scheduledOpen),this.scheduledOpen=null);try{this._notificationOverlay.close()}catch(o){}}}),w(()=>{if(this.notificationsSidenavOpened())try{this._notificationOverlay.close()}catch(e){}}),this.isPhonePortrait=_(this._responsive.observe(jt.HandsetPortrait)),this._connectionStateMachine.status$.pipe(R(this._destroyRef)).subscribe(t=>this.displayConnectionsStatusNotification(t)),w(()=>{let t=this.bootstrapIssue();if(t.reason!=="missing-shared-config"||this.missingConfigPromptShown)return;this.missingConfigPromptShown=!0;let e=t.sharedConfigName||"default",o=t.legacyUpgradeAvailable===!0,a=o?"Upgrade":"Create",l=o?`Server is reachable, but no current version shared configuration '${e}' exists for this user. A legacy configuration was found and can be upgraded. Upgrade now?`:`Server is reachable, but no shared configuration '${e}' exists for this user. Create a default configuration now?`;this.toast.show(l,0,!0,"warn",a).onAction().pipe(R(this._destroyRef)).subscribe(()=>{o?this.upgrade.runUpgrade():this.settings.resetSettings()})})}isUrlDashboard(t){if(!t)return!1;let e=t.split("?")[0].replace(/\/+$/,"");return e==="/"||e==="/dashboard"||/^\/dashboard(\/\d+)?$/.test(e)||e==="/chartplotter"||/^\/chartplotter(\/\d+)?$/.test(e)}ngOnInit(){this._uiEvent.addGestureListeners(this._swipeLeftHandler,this._swipeRightHandler)}ngAfterViewInit(){this._uiEvent.addHotkeyListener(this._hotkeyHandler,{ctrlKey:!0,keys:["arrowright","arrowleft"]})}handleKeyDown(t){switch(t){case"arrowright":this.onSwipeRight();break;case"arrowleft":this.onSwipeLeft();break;case"escape":this.backdropClicked();break}}escapeKeyPressed(t){t=t.toLowerCase(),t==="escape"&&this.backdropClicked()}displayConnectionsStatusNotification(t){let e=t.message,o=this.bootstrapStatus()!=="ready";switch(t.operation){case 0:this.toast.show(e,5e3,!0);break;case 1:case 2:break;case 3:this.toast.show(e,3e3,o,"warn");break;case 4:this.toast.show(e,3e3,!0,"info");break;case 5:this.toast.show(e,0,o);break;default:console.error("[AppComponent] Unknown operation code:",t.operation),this.toast.show(`Unknown connection status: ${t.state}`,0,o,"error")}}onSwipeRight(){this._dashboard.isDashboardStatic()&&!this._uiEvent.isDragging()&&(this.isPhonePortrait().matches?(this.actionsSidenavOpened.set(!1),this.notificationsSidenavOpened.set(!0)):(this.actionsSidenavOpened.set(!1),this.notificationsSidenavOpened.update(t=>!t)))}backdropClicked(){this.notificationsSidenavOpened.update(t=>t?!t:!1),this.actionsSidenavOpened.update(t=>t?!t:!1)}onSwipeLeft(){this._dashboard.isDashboardStatic()&&!this._uiEvent.isDragging()&&(this.isPhonePortrait().matches?(this.notificationsSidenavOpened.set(!1),this.actionsSidenavOpened.set(!0)):(this.notificationsSidenavOpened.set(!1),this.actionsSidenavOpened.update(t=>!t)))}ngOnDestroy(){this._uiEvent.removeGestureListeners(this._swipeLeftHandler,this._swipeRightHandler),this._uiEvent.removeHotkeyListener(this._hotkeyHandler),this.scheduledOpen&&(clearTimeout(this.scheduledOpen),this.scheduledOpen=null)}static \u0275fac=function(e){return new(e||i)};static \u0275cmp=P({type:i,selectors:[["app-root"]],viewQuery:function(e,o){e&1&&X(o.upgradeMessagesRef,vi,5),e&2&&Z()},inputs:{actionsSidenavOpened:[1,"actionsSidenavOpened"],notificationsSidenavOpened:[1,"notificationsSidenavOpened"]},outputs:{actionsSidenavOpened:"actionsSidenavOpenedChange",notificationsSidenavOpened:"notificationsSidenavOpenedChange"},decls:9,vars:6,consts:[["actionsSidenav",""],["upgradeMessages",""],["kipGestures","",1,"sidenav-container",3,"swipeleft","swiperight","backdropClick","mode","bridgeUiEvents"],["mode","over","position","start","autoFocus","true","disableClose","",1,"sidenav-notifications",3,"openedChange","keydown","opened"],["mode","over","position","end","autoFocus","true","disableClose","",1,"sidenav-actions",3,"openedChange","keydown","opened"],[3,"actionsSidenav"],[1,"router-outlet-container"],["role","alert","aria-live","polite",1,"config-upgrade-overlay"],[1,"config-upgrade-card"],["diameter","46","mode","indeterminate"],[1,"config-upgrade-text"],[1,"config-upgrade-messages"]],template:function(e,o){if(e&1){let a=v();r(0,"mat-sidenav-container",2),h("swipeleft",function(){return p(a),m(o.onSwipeLeft())})("swiperight",function(){return p(a),m(o.onSwipeRight())})("backdropClick",function(){return p(a),m(o.backdropClicked())}),r(1,"mat-sidenav",3),gt("openedChange",function(u){return p(a),ht(o.notificationsSidenavOpened,u)||(o.notificationsSidenavOpened=u),m(u)}),h("keydown",function(u){return p(a),m(o.escapeKeyPressed(u.key))}),I(2,"menu-notifications"),s(),r(3,"mat-sidenav",4,0),gt("openedChange",function(u){return p(a),ht(o.actionsSidenavOpened,u)||(o.actionsSidenavOpened=u),m(u)}),h("keydown",function(u){return p(a),m(o.escapeKeyPressed(u.key))}),I(5,"menu-actions",5),s(),r(6,"mat-sidenav-content"),I(7,"router-outlet",6),s()(),b(8,Si,9,1,"div",7)}if(e&2){let a=Pt(4);M("mode","horizontal")("bridgeUiEvents",!0),f(),ut("opened",o.notificationsSidenavOpened),f(2),ut("opened",o.actionsSidenavOpened),f(2),M("actionsSidenav",a),f(3),C(o.upgrade.upgrading()?8:-1)}},dependencies:[Ke,ze,N,ge,T,oe,te,De,Pe,Ne,Oe,Vt,Jt,Te,Ae],styles:[".sidenav-container[_ngcontent-%COMP%]{height:100%;width:100%;--mat-sidenav-scrim-color: color-mix(in srgb, var(--mat-sys-neutral-variant20) 70%, transparent)}mat-sidenav-content[_ngcontent-%COMP%]{overflow-y:hidden;overflow-x:hidden}mat-sidenav[_ngcontent-%COMP%]{box-shadow:0 0 1px 0 var(--kip-contrast-color)}mat-sidenav.sidenav-notifications[_ngcontent-%COMP%]{width:230px;z-index:1200}mat-sidenav.sidenav-actions[_ngcontent-%COMP%]{width:135px;z-index:1200;overflow-x:hidden}.config-upgrade-overlay[_ngcontent-%COMP%]{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:#00000073;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);z-index:2000;pointer-events:all}.config-upgrade-card[_ngcontent-%COMP%]{display:flex;flex-direction:row;align-items:center;gap:18px;padding:20px 24px;width:clamp(300px,60vw,640px);background:var(--kip-background, #1f1f1f);border:1px solid rgba(255,255,255,.18);border-radius:14px;color:var(--kip-contrast-color, #fff);box-shadow:0 6px 22px #0000008c}.config-upgrade-text[_ngcontent-%COMP%] h3[_ngcontent-%COMP%]{margin:0 0 4px;font-size:1.05rem;font-weight:600;letter-spacing:.5px}.config-upgrade-text[_ngcontent-%COMP%] p[_ngcontent-%COMP%]{margin:0 0 10px;font-size:.8rem;opacity:.85}.config-upgrade-messages[_ngcontent-%COMP%]{list-style:disc;margin:0;padding-left:18px;max-height:180px;font-size:.68rem;line-height:1.25;scrollbar-width:thin;overflow-y:auto}.config-upgrade-messages[_ngcontent-%COMP%] li[_ngcontent-%COMP%]{margin-bottom:2px}"]})}return i})();var We=(()=>{class i{auth=n(D);authToken=null;authTokenSubscription=null;constructor(){this.authTokenSubscription=this.auth.authToken$.subscribe(t=>{this.authToken=t})}intercept(t,e){let o=t.clone();return this.authToken&&(o=t.clone({headers:t.headers.set("authorization","JWT "+this.authToken.token)})),e.handle(o)}ngOnDestroy(){this.authTokenSubscription?.unsubscribe()}static \u0275fac=function(e){return new(e||i)};static \u0275prov=S({token:i,factory:i.\u0275fac})}return i})();var Ge=(()=>{class i extends it{_createContainer(){let t=document.createElement("div");t.classList.add("cdk-overlay-container"),(document.getElementById("app-filter-wrapper")||document.querySelector("app-root")||document.body).appendChild(t),this._containerElement=t}static \u0275fac=(()=>{let t;return function(o){return(t||(t=It(i)))(o||i)}})();static \u0275prov=S({token:i,factory:i.\u0275fac})}return i})();var Qe=(()=>{class i{_router=n(k);_dialog=n(re);_bottomSheet=n(Ce);_destroyRef=n(Y);constructor(){this._router.events.pipe(U(t=>t instanceof Zt),R(this._destroyRef)).subscribe(()=>{this._dialog.openDialogs.length>0&&this._dialog.closeAll(),this._bottomSheet.dismiss()})}static \u0275fac=function(e){return new(e||i)};static \u0275prov=S({token:i,factory:i.\u0275fac,providedIn:"root"})}return i})();Ue.production&&void 0;Bt(je,{providers:[Ut(),xt(zt),{provide:Ht,useClass:We,multi:!0},{provide:ae,useValue:{hasBackdrop:!0,disableClose:!1,autoFocus:"first-tabbable",delayFocusTrap:!0,backdropClass:"dialogBackdrop"}},{provide:se,useValue:{appearance:"outline",floatLabel:"always",subscriptSizing:"dynamic"}},{provide:me,useValue:{showDelay:1500,hideDelay:0}},D,z,nt,B,K,H,A,de,W,x,E,he,at,Ee(),$t(qt()),ee(Re,ie()),{provide:it,useClass:Ge},ft(()=>n(W).initNetworkServices()),ft(()=>{n(Qe)})]});
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
name: Bug Report
|
|
2
|
-
description: Report a bug or issue
|
|
3
|
-
title: "[Bug]: "
|
|
4
|
-
labels: ["bug"]
|
|
5
|
-
body:
|
|
6
|
-
- type: markdown
|
|
7
|
-
attributes:
|
|
8
|
-
value: |
|
|
9
|
-
Thanks for reporting a bug! Please provide the following information to help us investigate.
|
|
10
|
-
|
|
11
|
-
- type: dropdown
|
|
12
|
-
id: operating-system
|
|
13
|
-
attributes:
|
|
14
|
-
label: Operating System
|
|
15
|
-
description: Which operating system(s) are you using?
|
|
16
|
-
multiple: true
|
|
17
|
-
options:
|
|
18
|
-
- Raspberry Pi OS
|
|
19
|
-
- macOS
|
|
20
|
-
- iOS
|
|
21
|
-
- Windows
|
|
22
|
-
- Linux
|
|
23
|
-
- Android
|
|
24
|
-
- Other
|
|
25
|
-
validations:
|
|
26
|
-
required: true
|
|
27
|
-
|
|
28
|
-
- type: dropdown
|
|
29
|
-
id: browser
|
|
30
|
-
attributes:
|
|
31
|
-
label: Browser
|
|
32
|
-
description: Which browser(s) are you using?
|
|
33
|
-
multiple: true
|
|
34
|
-
options:
|
|
35
|
-
- Chromium
|
|
36
|
-
- Chrome
|
|
37
|
-
- Safari
|
|
38
|
-
- Edge
|
|
39
|
-
- Firefox
|
|
40
|
-
- Brave
|
|
41
|
-
- Opera
|
|
42
|
-
- Other
|
|
43
|
-
validations:
|
|
44
|
-
required: true
|
|
45
|
-
|
|
46
|
-
- type: input
|
|
47
|
-
id: kip-version
|
|
48
|
-
attributes:
|
|
49
|
-
label: KIP Version
|
|
50
|
-
description: What version of Kip are you using (Found in Settings -> Options -> Connectivity)?
|
|
51
|
-
placeholder: e.g., v2.5.1
|
|
52
|
-
validations:
|
|
53
|
-
required: true
|
|
54
|
-
|
|
55
|
-
- type: input
|
|
56
|
-
id: signalk-version
|
|
57
|
-
attributes:
|
|
58
|
-
label: Signal K Server Version
|
|
59
|
-
description: What version of Signal K server are you using?
|
|
60
|
-
placeholder: e.g., v2.5.1
|
|
61
|
-
validations:
|
|
62
|
-
required: false
|
|
63
|
-
|
|
64
|
-
- type: textarea
|
|
65
|
-
id: bug-description
|
|
66
|
-
attributes:
|
|
67
|
-
label: Bug Description
|
|
68
|
-
description: A clear and concise description of what the bug is - What did you expect, what actually happened and any other context about the problem here, including screenshots, logs, or error messages.
|
|
69
|
-
placeholder: What happened?
|
|
70
|
-
validations:
|
|
71
|
-
required: true
|
|
72
|
-
|
|
73
|
-
- type: textarea
|
|
74
|
-
id: steps-to-reproduce
|
|
75
|
-
attributes:
|
|
76
|
-
label: Steps to Reproduce
|
|
77
|
-
description: Steps to reproduce the behavior
|
|
78
|
-
placeholder: |
|
|
79
|
-
1. Go to '...'
|
|
80
|
-
2. Click on '....'
|
|
81
|
-
3. Scroll down to '....'
|
|
82
|
-
4. See error
|
|
83
|
-
validations:
|
|
84
|
-
required: true
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
name: Feature Request
|
|
2
|
-
description: Suggest a new feature or enhancement
|
|
3
|
-
title: "[Feature]: "
|
|
4
|
-
labels: ["enhancement"]
|
|
5
|
-
body:
|
|
6
|
-
- type: markdown
|
|
7
|
-
attributes:
|
|
8
|
-
value: |
|
|
9
|
-
Thanks for suggesting a feature! Please provide details below.
|
|
10
|
-
|
|
11
|
-
- type: textarea
|
|
12
|
-
id: feature-request
|
|
13
|
-
attributes:
|
|
14
|
-
label: Feature Request
|
|
15
|
-
description: |
|
|
16
|
-
Please describe the feature you'd like to see:
|
|
17
|
-
- What problem does this solve? What are you trying to accomplish?
|
|
18
|
-
- Provide a clear description of what you'd like to see added or changed.
|
|
19
|
-
placeholder: |
|
|
20
|
-
**Problem/Use Case:**
|
|
21
|
-
Describe what you're trying to accomplish...
|
|
22
|
-
|
|
23
|
-
**Proposed Solution:**
|
|
24
|
-
Describe how you envision this feature working...
|
|
25
|
-
validations:
|
|
26
|
-
required: true
|
|
27
|
-
|
|
28
|
-
- type: textarea
|
|
29
|
-
id: concepts-ideas-research
|
|
30
|
-
attributes:
|
|
31
|
-
label: Concepts, Ideas & Research
|
|
32
|
-
description: Add any other information, screenshots, mockups, links similar apps or existing documentation about the feature request here.
|
|
33
|
-
placeholder: Paste links, mockups, screenshots or related documentation to help us come up with a good and informed design...
|
|
34
|
-
validations:
|
|
35
|
-
required: false
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
# KIP – Copilot Instructions (for AI coding agents)
|
|
2
|
-
|
|
3
|
-
Use this quick-start map to be productive in this repo. Prefer these concrete patterns over generic Angular tips. For depth, see COPILOT.md (root) and .github/instructions/angular.instructions.md.
|
|
4
|
-
|
|
5
|
-
## Big picture
|
|
6
|
-
- Angular v20+ PWA served under base path /@mxtommy/kip/ (angular.json baseHref, package.json scripts).
|
|
7
|
-
- Data flow: SignalKConnectionService → SignalKDeltaService → DataService → Widgets.
|
|
8
|
-
- UI: Dashboard(s) with draggable/resizable widgets (gridstack). Themes: light/dark/night via SCSS roles + CSS variables.
|
|
9
|
-
- Storage: Config lives in Signal K when logged in, else local (StorageService). App init via APP_INITIALIZER (AppNetworkInitService).
|
|
10
|
-
|
|
11
|
-
## Daily workflows
|
|
12
|
-
- Dev: npm run dev, then open http://localhost:4200/@mxtommy/kip/ (needs a running Signal K server).
|
|
13
|
-
- Build KIP app: npm run build:dev | npm run build:prod (outputs KIP to public/ and respects baseHref).
|
|
14
|
-
- Build KIP app and KIP plugin: npm run build:all (outputs KIP to public/ and respects baseHref. Outputs plugin to kip-plugin).
|
|
15
|
-
- Quality: npm run lint, npm test (Karma). E2E (Protractor) is legacy/optional.
|
|
16
|
-
|
|
17
|
-
## Host2 widget contract (critical)
|
|
18
|
-
All widgets now follow the Host2 architecture (legacy BaseWidgetComponent removed).
|
|
19
|
-
|
|
20
|
-
1. Standalone component with required functional inputs:
|
|
21
|
-
```ts
|
|
22
|
-
id = input.required<string>();
|
|
23
|
-
type = input.required<string>();
|
|
24
|
-
theme = input.required<ITheme | null>();
|
|
25
|
-
```
|
|
26
|
-
2. Provide a static `DEFAULT_CONFIG: IWidgetSvcConfig` fully describing initial config (paths + options).
|
|
27
|
-
3. Inject directives for runtime + streams (and optionally metadata):
|
|
28
|
-
```ts
|
|
29
|
-
private runtime = inject(WidgetRuntimeDirective);
|
|
30
|
-
private streams = inject(WidgetStreamsDirective);
|
|
31
|
-
// optional
|
|
32
|
-
// private meta = inject(WidgetMetadataDirective);
|
|
33
|
-
```
|
|
34
|
-
4. Register data observers in a single effect:
|
|
35
|
-
```ts
|
|
36
|
-
effect(() => {
|
|
37
|
-
const cfg = this.runtime.options();
|
|
38
|
-
if (!cfg) return;
|
|
39
|
-
untracked(() => {
|
|
40
|
-
if (cfg.paths?.numericPath?.path) {
|
|
41
|
-
this.streams.observe('numericPath', pkt => this.value.set(pkt?.data?.value ?? null));
|
|
42
|
-
}
|
|
43
|
-
// repeat for other path keys
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
```
|
|
47
|
-
5. Always guard for missing config or optional paths (check `cfg?.paths?.key?.path`).
|
|
48
|
-
6. Avoid mutating the merged config object; store transient UI state in signals.
|
|
49
|
-
7. Use UnitsService and existing formatting helpers; do not hardcode conversions. Using streams directive handles path unit conversion settings automatically for number types.
|
|
50
|
-
8. Timeout settings honored automatically (`enableTimeout`, `dataTimeout`)—`WidgetStreamsDirective`.
|
|
51
|
-
9. Provide meaningful path keys (e.g. `numericPath`, `headingTrue`, `windSpeed`) and keep them stable.
|
|
52
|
-
10. Destroy logic is usually implicit (streams directive centralizes subscriptions); only tear down custom resources manually if you allocate them (e.g., canvases, animation frames).
|
|
53
|
-
|
|
54
|
-
### Path definition recap
|
|
55
|
-
Each entry in `DEFAULT_CONFIG.paths`:
|
|
56
|
-
```ts
|
|
57
|
-
someKey: {
|
|
58
|
-
description: 'User label',
|
|
59
|
-
path: 'navigation.speedThroughWater',
|
|
60
|
-
pathType: 'number' | 'string' | 'Date' | 'boolean',
|
|
61
|
-
convertUnitTo: 'knots', // For numeric path only. Sets automatic conversion to this unit
|
|
62
|
-
sampleTime: 1000, // ms, typical 500+
|
|
63
|
-
source: null, // optional source selection. null = default source
|
|
64
|
-
isPathConfigurable: true, // false to hide path in path options UI
|
|
65
|
-
pathRequired: true, // set false for optional
|
|
66
|
-
showPathSkUnitsFilter: false, // Show numeric UI filter support
|
|
67
|
-
pathSkUnitsFilter: null // Set and apply a path unit filter (e.g. 'knots' for speed)
|
|
68
|
-
}
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Data stream behavior (via WidgetStreamsDirective)
|
|
72
|
-
- Respects per-path `sampleTime`.
|
|
73
|
-
- Converts units for number paths using UnitsService.
|
|
74
|
-
- Optional timeout logic based on widget config flags.
|
|
75
|
-
- Centralized unsubscribe when host destroys the widget.
|
|
76
|
-
|
|
77
|
-
### Embedded widgets (composite pattern)
|
|
78
|
-
Use this patterns when a parent widget (e.g. Autopilot) displays other widgets:
|
|
79
|
-
|
|
80
|
-
#### `<widget-embedded>`
|
|
81
|
-
Supply a complete `widgetProperties` object (no persistence writes):
|
|
82
|
-
```ts
|
|
83
|
-
xteWidgetProps = {
|
|
84
|
-
uuid: this.id() + '-xte',
|
|
85
|
-
type: 'widget-numeric',
|
|
86
|
-
config: {
|
|
87
|
-
type: 'widget-numeric',
|
|
88
|
-
title: 'XTE',
|
|
89
|
-
paths: { numericPath: { description: 'Cross Track Error', path: 'navigation.course.crossTrackError', pathType: 'number', convertUnitTo: 'nm', sampleTime: 1000, isPathConfigurable: false } },
|
|
90
|
-
numDecimal: 2
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
```
|
|
94
|
-
Template:
|
|
95
|
-
```html
|
|
96
|
-
<widget-embedded [widgetProperties]="xteWidgetProps"></widget-embedded>
|
|
97
|
-
```
|
|
98
|
-
`widget-embedded` internally wires runtime + streams + metadata and instantiates the child.
|
|
99
|
-
|
|
100
|
-
### Safety patterns
|
|
101
|
-
- Null guard every `runtime.options()` access in effects & template (`runtime.options()?.paths?.key`).
|
|
102
|
-
- Avoid repeated `runtime.options()` chains in template: expose a computed `cfg = computed(() => this.runtime.options())`.
|
|
103
|
-
- For performance, do all `streams.observe` calls in one untracked block.
|
|
104
|
-
|
|
105
|
-
## Widget path options (important)
|
|
106
|
-
- pathType: Controls pipeline behavior (see features above). Must be accurate: 'number' | 'string' | 'Date' | 'boolean'.
|
|
107
|
-
- path: Signal K path string (e.g., navigation.speedThroughWater). Empty allowed only when pathRequired=false.
|
|
108
|
-
- sampleTime: Sampling period for the observer (ms). Keep modest (e.g., 250–1000) to reduce churn.
|
|
109
|
-
- convertUnitTo: Target display unit understood by UnitsService (e.g., 'knots', 'celsius', 'deg'). If omitted, treat value as base/metadata unit.
|
|
110
|
-
- source: Optional Signal K source filter; omit to accept any uniquely available source.
|
|
111
|
-
- isPathConfigurable: When false, hides the path from the widget-config UI (for fixed/internal paths). Validation is skipped for this key.
|
|
112
|
-
- pathRequired: Defaults to true. When false, empty path is valid; your widget must handle “no path” gracefully (don’t subscribe; show placeholder).
|
|
113
|
-
- Timeouts: At widgetProperties.config level, enableTimeout + dataTimeout are respected by observeDataStream—don’t add custom timeouts downstream.
|
|
114
|
-
|
|
115
|
-
## Data, metadata, zones
|
|
116
|
-
- Use DataService for values and metadata. observeDataStream wraps DataService.subscribePath.
|
|
117
|
-
- zones$ emits Signal K zones metadata when observeMetaStream is used; map states to theme roles.
|
|
118
|
-
|
|
119
|
-
## Theming
|
|
120
|
-
- TS: live theme via this.theme().<role> (from AppService.cssThemeColorRoles$).
|
|
121
|
-
- SCSS: use variables from src/themes/_m3*.scss; avoid hardcoded hex.
|
|
122
|
-
|
|
123
|
-
## Datasets & charts
|
|
124
|
-
- Historical/trend data: DataSetService (src/app/core/services/data-set.service.ts). Create/update/remove in widget lifecycle.
|
|
125
|
-
- Example: src/app/widgets/widget-windtrends-chart uses Chart.js + date-fns and DataSetService for batch-then-live streams.
|
|
126
|
-
|
|
127
|
-
## Signal K PUT/requests
|
|
128
|
-
- Read via DataService; write via SignalKRequestsService. UI filters PUT-enabled paths (see src/assets/help-docs/putcontrols.md).
|
|
129
|
-
|
|
130
|
-
## Project specifics & gotchas
|
|
131
|
-
- Always respect serve path /@mxtommy/kip/ (dev/prod). Assets and routing assume this base.
|
|
132
|
-
- CommonJS deps are explicitly allowed (js-quantities). Avoid introducing new CJS without adding to allowedCommonJsDependencies.
|
|
133
|
-
- Use standalone components, signals, @if/@for; follow .github/instructions/angular.instructions.md for style.
|
|
134
|
-
- Widget config UIs live under src/app/widget-config; path controls use custom validators (no Validators.required). Respect isPathConfigurable and pathRequired.
|
|
135
|
-
- Documentation standard: Every public property and public method in TypeScript code MUST include full JSDoc with: purpose, parameters, return value, and at least one usage example. Apply this by default for all generated/edited code unless a file explicitly cannot use comments.
|
|
136
|
-
|
|
137
|
-
## Widgets: do this, not that (Host2)
|
|
138
|
-
- Do: Provide a complete `DEFAULT_CONFIG` with all paths & options. Don’t: Scatter defaults across lifecycle hooks.
|
|
139
|
-
- Do: Centralize `streams.observe` calls in a single effect. Don’t: Register observers in multiple hooks.
|
|
140
|
-
- Do: Keep transient state in signals. Don’t: Mutate merged config objects.
|
|
141
|
-
- Do: Use UnitsService / formatting helpers. Don’t: Hardcode conversion factors.
|
|
142
|
-
- Do: Guard `options()` & path existence. Don’t: Assume presence.
|
|
143
|
-
- Do: Use widget-embedded or inline directives for composites. Don’t: Reintroduce legacy host wrappers.
|
|
144
|
-
|
|
145
|
-
## Key files/dirs
|
|
146
|
-
- Core services: `src/app/core/services/` (DataService, SignalKConnectionService, SignalKDeltaService, AppNetworkInitService, UnitsService, DataSetService, NotificationsService)
|
|
147
|
-
- Plugin config foundation: `src/app/core/services/signalk-plugin-config.service.ts` (plugin-only detection, dependency validation, schema normalization metadata, and config persistence via `/plugins` endpoints)
|
|
148
|
-
- Directives: `src/app/core/directives/` (widget-runtime, widget-streams, widget-metadata)
|
|
149
|
-
- Widgets: `src/app/widgets/` (e.g., widget-numeric, widget-gauge-ng-*, widget-data-chart, widget-windtrends-chart, widget-autopilot)
|
|
150
|
-
- Embedded host: `src/app/core/components/widget-embedded/`
|
|
151
|
-
- Config UI: `src/app/widget-config/`
|
|
152
|
-
- Plugin management (server plugins) is handled separately through `SignalkPluginConfigService` and `/plugins` REST endpoints. Keep install/uninstall out of scope unless explicitly added.
|
|
153
|
-
- Build: `angular.json`, `package.json` scripts
|
|
154
|
-
|
|
155
|
-
## Debugging
|
|
156
|
-
- Use Data Inspector (src/app/core/components/data-inspector) to verify live paths/metadata.
|
|
157
|
-
- Dev with source maps: npm run dev. Watch console from DataService/DataSetService for timeouts/lifecycle logs.
|
|
158
|
-
- Embeds (widget-iframe): prefer same-origin or relative URLs to avoid CORS and input-injection limits (see embedwidget.md).
|
|
159
|
-
|
|
160
|
-
## SVG Animation Helpers (rAF)
|
|
161
|
-
High-frequency SVG updates (rotations, path morphs) should NOT trigger Angular change detection every frame.
|
|
162
|
-
|
|
163
|
-
Core utilities (src/app/widgets/utils/svg-animate.util.ts):
|
|
164
|
-
- animateRotation(el, fromDeg, toDeg, durationMs, onDone?, ngZone?)
|
|
165
|
-
- animateRudderWidth(rectEl, from, to, durationMs, onDone?, ngZone?)
|
|
166
|
-
- animateAngleTransition(fromDeg, toDeg, durationMs, applyFn(angle), onDone?, ngZone?)
|
|
167
|
-
- animateSectorTransition(fromAngles, toAngles, durationMs, applyFn(sector), onDone?, ngZone?)
|
|
168
|
-
|
|
169
|
-
Pattern:
|
|
170
|
-
1. Inject NgZone; pass it so frames run outside Angular.
|
|
171
|
-
2. Cancel prior frame id before starting a new conceptual animation (store returned id from the generic helpers).
|
|
172
|
-
3. Skip tiny angle deltas (< ~0.25°) to prevent jitter.
|
|
173
|
-
4. On destroy: cancel outstanding ids (including those tracked internally for animateRotation/animateRudderWidth via element refs).
|
|
174
|
-
|
|
175
|
-
Example (angle interpolation):
|
|
176
|
-
```
|
|
177
|
-
if (this.portLaylineAnimId) cancelAnimationFrame(this.portLaylineAnimId);
|
|
178
|
-
this.portLaylineAnimId = animateAngleTransition(
|
|
179
|
-
prev,
|
|
180
|
-
next,
|
|
181
|
-
300,
|
|
182
|
-
angle => this.drawLayline(angle, true),
|
|
183
|
-
() => { this.portLaylineAnimId = null; },
|
|
184
|
-
this.ngZone
|
|
185
|
-
);
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
See COPILOT.md Section 12 for full rationale, cancellation rules, and future extension ideas.
|
|
189
|
-
|
|
190
|
-
## Pointer Swipe Guard (tap vs swipe)
|
|
191
|
-
Use the shared swipe guard utility to prevent swipe gestures from triggering click/tap actions on SVG controls.
|
|
192
|
-
|
|
193
|
-
Utility (core): `src/app/core/utils/pointer-swipe-guard.util.ts`
|
|
194
|
-
|
|
195
|
-
When to use:
|
|
196
|
-
- Any widget that accepts pointer input and should ignore swipe gestures (e.g., multi-state switch, boolean switch).
|
|
197
|
-
|
|
198
|
-
Pattern:
|
|
199
|
-
1. Create a guard in the component: `private readonly swipeGuard = createSwipeGuard();`
|
|
200
|
-
2. Wire `pointerdown`, `pointermove`, `pointerup`, and `pointercancel` events.
|
|
201
|
-
3. Trigger action only when `swipeGuard.onPointerUp(...)` returns true.
|
|
202
|
-
|
|
203
|
-
Notes:
|
|
204
|
-
- The guard uses pointer capture for robust fast drags and releases capture on `pointerup`/`pointercancel`.
|
|
205
|
-
- Default swipe threshold is 30px (override via `createSwipeGuard({ threshold })` if needed).
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
# Persona
|
|
2
|
-
|
|
3
|
-
You are a dedicated Angular developer who thrives on leveraging the absolute latest features of the framework to build cutting-edge applications. You are currently immersed in Angular v20+, passionately adopting signals for reactive state management, embracing standalone components for streamlined architecture, and utilizing the new control flow for more intuitive template logic. Performance is paramount to you, who constantly seeks to optimize change detection and improve user experience through these modern Angular paradigms. When prompted, assume You are familiar with all the newest APIs and best practices, valuing clean, efficient, and maintainable code.
|
|
4
|
-
|
|
5
|
-
## Examples
|
|
6
|
-
|
|
7
|
-
These are modern examples of how to write an Angular 20 component with signals
|
|
8
|
-
|
|
9
|
-
```ts
|
|
10
|
-
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@Component({
|
|
14
|
-
selector: '{{tag-name}}-root',
|
|
15
|
-
templateUrl: '{{tag-name}}.html',
|
|
16
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
17
|
-
})
|
|
18
|
-
export class {{ClassName}} {
|
|
19
|
-
protected readonly isServerRunning = signal(true);
|
|
20
|
-
toggleServerStatus() {
|
|
21
|
-
this.isServerRunning.update(isServerRunning => !isServerRunning);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
```scss
|
|
27
|
-
.container {
|
|
28
|
-
display: flex;
|
|
29
|
-
flex-direction: column;
|
|
30
|
-
align-items: center;
|
|
31
|
-
justify-content: center;
|
|
32
|
-
height: 100vh;
|
|
33
|
-
|
|
34
|
-
button {
|
|
35
|
-
margin-top: 10px;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
```html
|
|
41
|
-
<section class="container">
|
|
42
|
-
@if (isServerRunning()) {
|
|
43
|
-
<span>Yes, the server is running</span>
|
|
44
|
-
} @else {
|
|
45
|
-
<span>No, the server is not running</span>
|
|
46
|
-
}
|
|
47
|
-
<button (click)="toggleServerStatus()">Toggle Server Status</button>
|
|
48
|
-
</section>
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
When you update a component, be sure to put the logic in the ts file, the styles in the scss file, and the html template in the html file.
|
|
52
|
-
|
|
53
|
-
## Resources
|
|
54
|
-
|
|
55
|
-
Here are some links to the essentials for building Angular applications. Use these to get an understanding of how some of the core functionality works
|
|
56
|
-
https://angular.dev/essentials/components
|
|
57
|
-
https://angular.dev/essentials/signals
|
|
58
|
-
https://angular.dev/essentials/templates
|
|
59
|
-
https://angular.dev/essentials/dependency-injection
|
|
60
|
-
|
|
61
|
-
## Best practices & Style guide
|
|
62
|
-
|
|
63
|
-
Here are the best practices and the style guide information.
|
|
64
|
-
|
|
65
|
-
### Coding Style guide
|
|
66
|
-
|
|
67
|
-
Here is a link to the most recent Angular style guide https://angular.dev/style-guide
|
|
68
|
-
|
|
69
|
-
### TypeScript Best Practices
|
|
70
|
-
|
|
71
|
-
- Use strict type checking
|
|
72
|
-
- Prefer type inference when the type is obvious
|
|
73
|
-
- Avoid the `any` type; use `unknown` when type is uncertain
|
|
74
|
-
|
|
75
|
-
### Angular Best Practices
|
|
76
|
-
|
|
77
|
-
- Always use standalone components over `NgModules`
|
|
78
|
-
- Do NOT set `standalone: true` inside the `@Component`, `@Directive` and `@Pipe` decorators
|
|
79
|
-
- Use signals for state management
|
|
80
|
-
- Implement lazy loading for feature routes
|
|
81
|
-
- Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead
|
|
82
|
-
- Use `NgOptimizedImage` for all static images.
|
|
83
|
-
- `NgOptimizedImage` does not work for inline base64 images.
|
|
84
|
-
|
|
85
|
-
### Accessibility Requirements
|
|
86
|
-
|
|
87
|
-
- It MUST pass all AXE checks.
|
|
88
|
-
- It MUST follow all WCAG AA minimums, including focus management, color contrast, and ARIA attributes.
|
|
89
|
-
|
|
90
|
-
### Components
|
|
91
|
-
|
|
92
|
-
- Keep components small and focused on a single responsibility
|
|
93
|
-
- Use `input()` signal instead of decorators, learn more here https://angular.dev/guide/components/inputs
|
|
94
|
-
- Use `output()` function instead of decorators, learn more here https://angular.dev/guide/components/outputs
|
|
95
|
-
- Use `computed()` for derived state learn more about signals here https://angular.dev/guide/signals.
|
|
96
|
-
- Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator
|
|
97
|
-
- Prefer inline templates for small components
|
|
98
|
-
- Prefer Reactive forms instead of Template-driven ones
|
|
99
|
-
- Do NOT use `ngClass`, use `class` bindings instead, for context: https://angular.dev/guide/templates/binding#css-class-and-style-property-bindings
|
|
100
|
-
- Do NOT use `ngStyle`, use `style` bindings instead, for context: https://angular.dev/guide/templates/binding#css-class-and-style-property-bindings
|
|
101
|
-
|
|
102
|
-
### State Management
|
|
103
|
-
|
|
104
|
-
- Use signals for local component state
|
|
105
|
-
- Use `computed()` for derived state
|
|
106
|
-
- Keep state transformations pure and predictable
|
|
107
|
-
- Do NOT use `mutate` on signals, use `update` or `set` instead
|
|
108
|
-
|
|
109
|
-
### Templates
|
|
110
|
-
|
|
111
|
-
- Keep templates simple and avoid complex logic
|
|
112
|
-
- Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch`
|
|
113
|
-
- Avoid non-deterministic work in templates (e.g., calling `new Date()` or `Math.random()` in bindings); compute values in TypeScript and bind to signals/`computed()`.
|
|
114
|
-
- Do not write arrow functions in templates (they are not supported).
|
|
115
|
-
- Use the async pipe to handle observables
|
|
116
|
-
- Use built in pipes and import pipes when being used in a template, learn more https://angular.dev/guide/templates/pipes#
|
|
117
|
-
- When using external templates/styles, use paths relative to the component TS file.
|
|
118
|
-
|
|
119
|
-
### Services
|
|
120
|
-
|
|
121
|
-
- Design services around a single responsibility
|
|
122
|
-
- Use the `providedIn: 'root'` option for singleton services
|
|
123
|
-
- Use the `inject()` function instead of constructor injection
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
You are an expert in TypeScript, Angular, and scalable web application development. You write functional, maintainable, performant, and accessible code following Angular and TypeScript best practices.
|
|
2
|
-
|
|
3
|
-
## TypeScript Best Practices
|
|
4
|
-
|
|
5
|
-
- Use strict type checking
|
|
6
|
-
- Prefer type inference when the type is obvious
|
|
7
|
-
- Avoid the `any` type; use `unknown` when type is uncertain
|
|
8
|
-
|
|
9
|
-
## Angular Best Practices
|
|
10
|
-
|
|
11
|
-
- Always use standalone components over NgModules
|
|
12
|
-
- Must NOT set `standalone: true` inside Angular decorators. It's the default in Angular v20+.
|
|
13
|
-
- Use signals for state management
|
|
14
|
-
- Implement lazy loading for feature routes
|
|
15
|
-
- Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead
|
|
16
|
-
- Use `NgOptimizedImage` for all static images.
|
|
17
|
-
- `NgOptimizedImage` does not work for inline base64 images.
|
|
18
|
-
|
|
19
|
-
## Accessibility Requirements
|
|
20
|
-
|
|
21
|
-
- It MUST pass all AXE checks.
|
|
22
|
-
- It MUST follow all WCAG AA minimums, including focus management, color contrast, and ARIA attributes.
|
|
23
|
-
|
|
24
|
-
### Components
|
|
25
|
-
|
|
26
|
-
- Keep components small and focused on a single responsibility
|
|
27
|
-
- Use `input()` and `output()` functions instead of decorators
|
|
28
|
-
- Use `computed()` for derived state
|
|
29
|
-
- Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator
|
|
30
|
-
- Prefer inline templates for small components
|
|
31
|
-
- Prefer Reactive forms instead of Template-driven ones
|
|
32
|
-
- Do NOT use `ngClass`, use `class` bindings instead
|
|
33
|
-
- Do NOT use `ngStyle`, use `style` bindings instead
|
|
34
|
-
- When using external templates/styles, use paths relative to the component TS file.
|
|
35
|
-
|
|
36
|
-
## State Management
|
|
37
|
-
|
|
38
|
-
- Use signals for local component state
|
|
39
|
-
- Use `computed()` for derived state
|
|
40
|
-
- Keep state transformations pure and predictable
|
|
41
|
-
- Do NOT use `mutate` on signals, use `update` or `set` instead
|
|
42
|
-
|
|
43
|
-
## Templates
|
|
44
|
-
|
|
45
|
-
- Keep templates simple and avoid complex logic
|
|
46
|
-
- Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch`
|
|
47
|
-
- Use the async pipe to handle observables
|
|
48
|
-
- Avoid non-deterministic work in templates (e.g., calling `new Date()` or `Math.random()` in bindings); compute values in TypeScript and bind to signals/`computed()`.
|
|
49
|
-
- Do not write arrow functions in templates (they are not supported).
|
|
50
|
-
|
|
51
|
-
## Services
|
|
52
|
-
|
|
53
|
-
- Design services around a single responsibility
|
|
54
|
-
- Use the `providedIn: 'root'` option for singleton services
|
|
55
|
-
- Use the `inject()` function instead of constructor injection
|
|
56
|
-
|
|
57
|
-
## Documentation
|
|
58
|
-
|
|
59
|
-
- Every public TypeScript property and public method MUST include full JSDoc with: purpose, parameters, return value, and at least one usage example.
|