@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.
Files changed (102) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +11 -7
  3. package/package.json +21 -8
  4. package/plugin/duckdb-parquet-storage.service.js +1182 -0
  5. package/plugin/history-series.service.js +439 -0
  6. package/plugin/index.js +705 -30
  7. package/plugin/openApi.json +253 -3
  8. package/plugin/plugin-auth.service.js +75 -0
  9. package/public/assets/help-docs/chartplotter.md +5 -18
  10. package/public/assets/help-docs/community.md +0 -3
  11. package/public/assets/help-docs/configuration.md +1 -1
  12. package/public/assets/help-docs/contact-us.md +0 -4
  13. package/public/assets/help-docs/dashboards.md +20 -18
  14. package/public/assets/help-docs/datainspector.md +7 -5
  15. package/public/assets/help-docs/history-api.md +116 -0
  16. package/public/assets/help-docs/menu.json +18 -6
  17. package/public/assets/help-docs/nodered-control-flows.md +125 -0
  18. package/public/assets/help-docs/putcontrols.md +101 -60
  19. package/public/assets/help-docs/welcome.md +6 -7
  20. package/public/assets/help-docs/widget-historical-series.md +66 -0
  21. package/public/assets/help-docs/zones.md +5 -10
  22. package/public/chunk-A6DQJFP4.js +16 -0
  23. package/public/chunk-B75MT7ND.js +1 -0
  24. package/public/{chunk-T6TFVZVM.js → chunk-CEB42O2C.js} +1 -1
  25. package/public/chunk-CHGXAEKT.js +2 -0
  26. package/public/chunk-D7VDX7ZF.js +5 -0
  27. package/public/{chunk-ZQER6AIQ.js → chunk-DEGYRCMI.js} +1 -1
  28. package/public/{chunk-M2B5OYGO.js → chunk-DEM56G4S.js} +1 -1
  29. package/public/chunk-DYTBBUMI.js +4 -0
  30. package/public/chunk-EQ2N7KDA.js +3 -0
  31. package/public/chunk-FNF7M3AE.js +1 -0
  32. package/public/chunk-IHURI4IH.js +5 -0
  33. package/public/{chunk-YIYYVDFO.js → chunk-IYRLINL7.js} +2 -2
  34. package/public/{chunk-5FEX27I4.js → chunk-JB4YVVNW.js} +1 -1
  35. package/public/chunk-JGGMFMY5.js +1 -0
  36. package/public/chunk-KPHICV76.js +5 -0
  37. package/public/{chunk-QZKCRH3H.js → chunk-KZ5DUKAX.js} +1 -1
  38. package/public/{chunk-HMOOTAEA.js → chunk-LQDSU4WS.js} +3 -3
  39. package/public/{chunk-IXQ7KIFY.js → chunk-MGPPVLZ7.js} +1 -1
  40. package/public/{chunk-QVCLOCEC.js → chunk-R7RQHWKJ.js} +1 -1
  41. package/public/chunk-RONXIZ2U.js +9 -0
  42. package/public/chunk-S72JTJPN.js +6 -0
  43. package/public/{chunk-KFFAA7DL.js → chunk-VCY32MWT.js} +8 -8
  44. package/public/chunk-YCEXTKGG.js +1 -0
  45. package/public/chunk-YKJKIWXO.js +6 -0
  46. package/public/chunk-ZV7IYYEQ.js +50 -0
  47. package/public/index.html +1 -1
  48. package/public/main-FQESQQV6.js +1 -0
  49. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -84
  50. package/.github/ISSUE_TEMPLATE/config.yml +0 -5
  51. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -35
  52. package/.github/copilot-instructions.md +0 -205
  53. package/.github/instructions/angular.instructions.md +0 -123
  54. package/.github/instructions/best-practices.instructions.md +0 -59
  55. package/.github/instructions/project.instructions.md +0 -432
  56. package/.github/workflows/ci.yml +0 -37
  57. package/docs/widget-schematic.md +0 -102
  58. package/images/ActionSidenav.png +0 -0
  59. package/images/ChartplotterMode.png +0 -0
  60. package/images/KIPDemo.png +0 -0
  61. package/images/KipBrightness-1024.png +0 -0
  62. package/images/KipConfig-Units-1024.png +0 -0
  63. package/images/KipConfig-display-1024x488.png +0 -0
  64. package/images/KipFreeboard-SK-1024.png +0 -0
  65. package/images/KipGaugeSample1-1024x545.png +0 -0
  66. package/images/KipGaugeSample2-1024x488.png +0 -0
  67. package/images/KipGaugeSample3-1024x508.png +0 -0
  68. package/images/KipNightMode-1024.png +0 -0
  69. package/images/KipWidgetConfig-layout-1024.png +0 -0
  70. package/images/KipWidgetConfig-paths-1024x488.png +0 -0
  71. package/images/Options.png +0 -0
  72. package/images/exterior_user_installs.png +0 -0
  73. package/images/formfactor.png +0 -0
  74. package/public/assets/help-docs/datasets.md +0 -95
  75. package/public/chunk-2OB7ZJBR.js +0 -3
  76. package/public/chunk-6GGJZDRE.js +0 -1
  77. package/public/chunk-6V4GGGXE.js +0 -2
  78. package/public/chunk-A5BW6BUM.js +0 -1
  79. package/public/chunk-DGE5YFPU.js +0 -5
  80. package/public/chunk-G6M3Z3BY.js +0 -53
  81. package/public/chunk-GMGZLXY7.js +0 -4
  82. package/public/chunk-GUZ3BDVZ.js +0 -2
  83. package/public/chunk-ICDGHQFP.js +0 -6
  84. package/public/chunk-JCNE4QHQ.js +0 -15
  85. package/public/chunk-K6XYUNG4.js +0 -8
  86. package/public/chunk-LGCQEN7V.js +0 -4
  87. package/public/chunk-O3JH7UTR.js +0 -1
  88. package/public/chunk-Q3USFT4F.js +0 -2
  89. package/public/chunk-VIKU7BH7.js +0 -1
  90. package/public/chunk-XMQPXXLW.js +0 -8
  91. package/public/main-4URMGBQS.js +0 -1
  92. package/rm-npmjs-beta.sh +0 -50
  93. package/tools/schematics/collection.json +0 -9
  94. package/tools/schematics/create-host2-widget/files/readme/README.md.template +0 -109
  95. package/tools/schematics/create-host2-widget/files/spec/widget-__name@dasherize__.component.spec.ts +0 -38
  96. package/tools/schematics/create-host2-widget/files/widget/widget-__name@dasherize__.component.html +0 -6
  97. package/tools/schematics/create-host2-widget/files/widget/widget-__name@dasherize__.component.scss +0 -5
  98. package/tools/schematics/create-host2-widget/files/widget/widget-__name@dasherize__.component.ts.template +0 -94
  99. package/tools/schematics/create-host2-widget/index.js +0 -138
  100. package/tools/schematics/create-host2-widget/schema.json +0 -89
  101. package/tools/schematics/create-host2-widget/test/create-host2-widget.spec.ts +0 -70
  102. 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-2OB7ZJBR.js"><link rel="modulepreload" href="chunk-G6M3Z3BY.js"><link rel="modulepreload" href="chunk-QZKCRH3H.js"><link rel="modulepreload" href="chunk-XMQPXXLW.js"><link rel="modulepreload" href="chunk-QVCLOCEC.js"><link rel="modulepreload" href="chunk-IXQ7KIFY.js"><link rel="modulepreload" href="chunk-GMGZLXY7.js"><link rel="modulepreload" href="chunk-HMOOTAEA.js"><link rel="modulepreload" href="chunk-Q3USFT4F.js"><link rel="modulepreload" href="chunk-LGCQEN7V.js"><script src="polyfills-L4FJGPOC.js" type="module"></script><script src="scripts-M6PHZBML.js" defer></script><script src="main-4URMGBQS.js" type="module"></script></body>
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,5 +0,0 @@
1
- blank_issues_enabled: false
2
- contact_links:
3
- - name: Community Support (Discord)
4
- url: https://discord.gg/AMDYT2DQga
5
- about: Please ask questions and discuss ideas in near real time here
@@ -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.