@mxtommy/kip 4.7.0 → 4.8.0
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 +10 -0
- package/LICENSE +3 -0
- package/README.md +37 -29
- package/package.json +23 -34
- package/plugin/history-series.service.js +108 -11
- package/plugin/index.js +170 -44
- package/plugin/kip-series-contract.js +18 -1
- package/plugin/openApi.json +75 -7
- package/public/3rdpartylicenses.txt +0 -26
- package/public/assets/help-docs/contributing-widgets.md +40 -0
- package/public/assets/help-docs/dashboards.md +8 -0
- package/public/assets/help-docs/menu.json +4 -2
- package/public/assets/svg/icons.svg +48 -1
- package/public/assets/svg/symbols.svg +68 -0
- package/public/{chunk-PKB75FW2.js → chunk-2TP7C66X.js} +1 -1
- package/public/chunk-3SJSLBCV.js +6 -0
- package/public/chunk-5SMHTIVS.js +50 -0
- package/public/{chunk-E3ROX3JL.js → chunk-6ISGIPNP.js} +1 -1
- package/public/chunk-6ZR3M3IQ.js +3 -0
- package/public/{chunk-6LPU67GV.js → chunk-ACY7HYMZ.js} +1 -1
- package/public/{chunk-E3VC75HO.js → chunk-C23GLDFH.js} +1 -1
- package/public/{chunk-W5RYIISE.js → chunk-CB4E7PPA.js} +1 -1
- package/public/{chunk-JL7DODKZ.js → chunk-DNM5XUIF.js} +1 -1
- package/public/{chunk-2BKBAKG4.js → chunk-GSM7FZE2.js} +2 -2
- package/public/{chunk-OBMTAHXF.js → chunk-HF7V6XFA.js} +1 -1
- package/public/{chunk-2BY2XRFX.js → chunk-HUCDJ6CS.js} +7 -7
- package/public/{chunk-CSJ2TU72.js → chunk-ILQQCGMJ.js} +1 -1
- package/public/chunk-IXUKD73N.js +5 -0
- package/public/{chunk-BMEO4VRV.js → chunk-JMZYPL54.js} +1 -1
- package/public/chunk-KG4C6HZC.js +1 -0
- package/public/{chunk-UBBLNNPA.js → chunk-LEY6MANN.js} +13 -13
- package/public/chunk-NS2OV2OW.js +9 -0
- package/public/{chunk-3JK476CX.js → chunk-O5BTKN5D.js} +1 -1
- package/public/{chunk-PDJFHMII.js → chunk-Q32FSCUX.js} +1 -1
- package/public/{chunk-3B5RVPSS.js → chunk-SHJMXSDM.js} +1 -1
- package/public/{chunk-GWAFO3MC.js → chunk-T4VTC6GW.js} +1 -1
- package/public/{chunk-GHPW4BPE.js → chunk-VFJD3XKT.js} +1 -1
- package/public/{chunk-KHZPPOSI.js → chunk-VFZSH4TC.js} +1 -1
- package/public/{chunk-S4UHQERA.js → chunk-W6MCE3GH.js} +1 -1
- package/public/index.html +1 -1
- package/public/main-UBENKC2D.js +1 -0
- package/public/polyfills-BWA36QKG.js +1 -0
- package/public/chunk-B2CYEVGO.js +0 -9
- package/public/chunk-HIXACNNV.js +0 -6
- package/public/chunk-QN4XMV3X.js +0 -50
- package/public/chunk-U5QHLGHD.js +0 -1
- package/public/chunk-WYC65OAX.js +0 -5
- package/public/chunk-XFHXLFTX.js +0 -3
- package/public/main-WGYSOKJC.js +0 -1
- package/public/polyfills-L4FJGPOC.js +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
# v4.8.0
|
|
2
|
+
## New Features
|
|
3
|
+
* Solar Charger Widget: Get instant clarity on your solar system with a compact, purpose-built Solar Charger Widget. Track individual panels or full arrays in real time, including State of Charge, remaining capacity, remaining time, voltage, current, power flow, and temperature. Device discovery is automatic, and Zones support keeps warnings and alarms state highly visible.
|
|
4
|
+
* AC/DC Charger Widget: Monitor charging performance at a glance with a compact AC/DC Charger Widget. View single or multiple chargers with charge mode, voltage, current, power and temperature. Chargers are discovered automatically.
|
|
5
|
+
## Improvements
|
|
6
|
+
* Battery Monitor Widget visual cleanup for better readability and tighter consistency with the electrical widget family.
|
|
7
|
+
* Framework upgrades and core refactoring to improve long-term maintainability and runtime performance.
|
|
8
|
+
## Fixes
|
|
9
|
+
* Improved Battery Monitor text contrast when color state is Alert (yellow), making critical values easier to read. Fixes #1027
|
|
10
|
+
* Restored Countdown Timer visibility in Add Widgets.
|
|
1
11
|
# v4.7.0
|
|
2
12
|
## New Features
|
|
3
13
|
* Battery Monitor Widget: Stay on top of your vessel’s power system with a dedicated compact Battery Monitor Widget. Instantly view individual batteries or whole banks, including State of Charge, remaining capacity, remaining time, voltage, current, power flow, and temperature. Batteries are detected automatically, with Signal K Zones support for clear warning and alarm visibility at a glance.
|
package/LICENSE
CHANGED
|
@@ -19,3 +19,6 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
This repository may not be used to train machine learning or AI models
|
|
24
|
+
without explicit permission from the author.
|
package/README.md
CHANGED
|
@@ -96,31 +96,27 @@ Organize your dashboards and access tools.
|
|
|
96
96
|
|
|
97
97
|
## Widget Library
|
|
98
98
|
All KIP widgets are visual presentation controls that are very versatile, with multiple advanced configuration options available to suit your needs:
|
|
99
|
-
- **
|
|
100
|
-
- **
|
|
101
|
-
- **
|
|
102
|
-
- **
|
|
103
|
-
- **
|
|
104
|
-
- **
|
|
105
|
-
- **
|
|
106
|
-
- **
|
|
107
|
-
- **
|
|
108
|
-
- **
|
|
109
|
-
- **
|
|
110
|
-
- **
|
|
111
|
-
- **
|
|
112
|
-
- **
|
|
113
|
-
- **
|
|
114
|
-
- **Pitch & Roll**: Horizon-style attitude indicator showing live pitch and roll for monitoring trim, heel, and sea-state response.
|
|
115
|
-
- **Wind Steering Display**: Your typical sailboat wind gauge.
|
|
116
|
-
- **Freeboard-SK Chart Plotter**: A high-quality Signal K implementation of the Freeboard integration widget.
|
|
117
|
-
- **Autopilot Head**: Operate your autopilot from any device remotely.
|
|
118
|
-
- **Data Chart**: Visualize data trends over time.
|
|
99
|
+
- **Compact Linear** – Simple horizontal linear gauge with a large value label and modern look.
|
|
100
|
+
- **Linear** – Horizontal or vertical linear gauge with zone highlighting.
|
|
101
|
+
- **Radial** – Radial gauge with configurable dials and zone highlighting.
|
|
102
|
+
- **Compass** – Rotating compass gauge with multiple cardinal indicator options.
|
|
103
|
+
- **Level Gauge** – Dual-scale heel angle indicator for trim tuning and sea-state monitoring.
|
|
104
|
+
- **Pitch & Roll** – Horizon-style attitude indicator showing live pitch and roll degrees.
|
|
105
|
+
- **Classic Steel** – Traditional steel-look linear & radial gauges with range sizes and zone highlights.
|
|
106
|
+
- **Windsteer** – Combines wind, wind sectors, heading, COG, and waypoint info for wind steering.
|
|
107
|
+
- **Wind Trends** – Real-time True Wind trends with dual axes for direction and speed, live values, and averages.
|
|
108
|
+
- **Battery Monitor** - Display batteries or whole banks state State of Charge, remaining capacity, remaining time, voltage, current, power flow, and temperature.
|
|
109
|
+
- **Solar Charger**- Track solar generation and charging performance at a glance with live panel output, battery-side metrics, and clear charger and relay status indicators.
|
|
110
|
+
- **AC/DC Charger**- Monitor charging performance at a glance with a compact AC/DC Charger Widget. View single or multiple chargers with charge mode, voltage, current, power and temperature. Chargers are discovered automatically.
|
|
111
|
+
- **Freeboard-SK** – Adds the Freeboard-SK chart plotter as a widget with automatic sign-in.
|
|
112
|
+
- **Autopilot Head** – Typical autopilot controls for compatible Signal K Autopilot devices.
|
|
113
|
+
- **Realtime Data Chart** – Visualizes data on a real-time chart with actuals, averages, and min/max.
|
|
119
114
|
- **AIS Radar**: Display AIS targets with range rings, interactive target details, and quick zoom and filtering controls.
|
|
120
|
-
- **
|
|
121
|
-
- **
|
|
122
|
-
- **Racer Start
|
|
123
|
-
- **
|
|
115
|
+
- **Embed Webpage Viewer** – Embeds external web apps (Grafana, Node-RED, etc.) into your dashboard.
|
|
116
|
+
- **Racesteer** – Race steering display fusing polar performance data with live conditions for optimal tactics.
|
|
117
|
+
- **Racer - Start Line Insight** – Set and adjust start line ends, see distance, favored end, and line bias; integrates with Freeboard SK.
|
|
118
|
+
- **Racer - Start Timer** – Advanced racing countdown timer with OCS status and auto dashboard switching.
|
|
119
|
+
- **Countdown Timer** – Simple race start countdown timer with start, pause, sync, and reset options.
|
|
124
120
|
|
|
125
121
|
Get the latest version of KIP to see what's new!
|
|
126
122
|
|
|
@@ -138,7 +134,7 @@ Grafana integration with other widgets
|
|
|
138
134
|

|
|
139
135
|
|
|
140
136
|
## Historical Data
|
|
141
|
-
Experience effortless insight into your vessel’s past with KIP’s Widget Historical Charts—automatically track, store, and visualize key data, unlocking instant access charts showing up to the last full day of performance. Whether you’re sailing or docked, simply tap or right-click widgets to reveal a seamless history dialog—no setup, no clutter, just the trends you need.
|
|
137
|
+
Experience effortless insight into your vessel’s past with KIP’s Widget Historical Charts—automatically track, store, and visualize key data, unlocking instant access charts showing up to the last full day of performance. Whether you’re sailing or docked, simply two-finger tap or right-click widgets to reveal a seamless history dialog—no setup, no clutter, just the trends you need. When combining data visualisation using Data Driven widgets, live-to-history transitions, KIP puts your boat’s story at your fingertips—so you can make smarter decisions, spot patterns, and sail with confidence.
|
|
142
138
|
|
|
143
139
|
## Night Modes
|
|
144
140
|
Keep your night vision with automatic or manual day and night switching to a color preserving dim mode or an all Red theme. The images below look very dark, but at night... they are perfect!
|
|
@@ -262,17 +258,26 @@ Once done with your work, from your fork's working branch, make a GitHub pull re
|
|
|
262
258
|
For comprehensive development guidance, please refer to these instruction files:
|
|
263
259
|
|
|
264
260
|
### Primary Instructions
|
|
265
|
-
- **[
|
|
261
|
+
- **[Project Instructions](./.github/instructions/project.instructions.md)**: KIP policy owner for architecture/domain rules, including widget creation and Host2 contracts.
|
|
262
|
+
- **[COPILOT.md](./COPILOT.md)**: Architecture context, rationale, and evolution notes (non-policy).
|
|
266
263
|
- **[Angular Instructions](./.github/instructions/angular.instructions.md)**: Modern Angular v21+ coding standards, component patterns, and framework best practices.
|
|
267
264
|
- **[Copilot Agent Instructions](./.github/copilot-instructions.md)**: Architecture details and coding-agent guardrails for this repository.
|
|
268
265
|
|
|
269
266
|
### Development Workflow
|
|
270
|
-
1. **Start Here**: Read
|
|
267
|
+
1. **Start Here**: Read `.github/instructions/project.instructions.md` for KIP policy contracts.
|
|
271
268
|
2. **Angular Standards**: Follow `.github/instructions/angular.instructions.md` for modern Angular development.
|
|
272
|
-
3. **
|
|
269
|
+
3. **Architecture Context**: Use `COPILOT.md` for rationale and dated architecture notes.
|
|
270
|
+
4. **Setup & Build**: Use this README for project setup and build commands.
|
|
271
|
+
|
|
272
|
+
### Widget Creation Workflow
|
|
273
|
+
1. Scaffold with `npm run generate:widget` (Host2 schematic-first path).
|
|
274
|
+
2. Use `docs/widget-schematic.md` for CLI flags, prompting behavior, and troubleshooting.
|
|
275
|
+
3. Follow Host2 runtime/stream patterns in `.agents/skills/kip-host2-widget/SKILL.md`.
|
|
276
|
+
4. Apply widget creation implementation checklist from `.agents/skills/kip-widget-creation/SKILL.md`.
|
|
277
|
+
5. Keep enforceable behavior aligned with `.github/instructions/project.instructions.md` (`Widget Creation Domain Rules`).
|
|
273
278
|
|
|
274
279
|
### Key Priorities
|
|
275
|
-
- **Widget Development**: Use
|
|
280
|
+
- **Widget Development**: Use Host2 patterns and scaffold with the `create-host2-widget` schematic (see `docs/widget-schematic.md`).
|
|
276
281
|
- **Angular Patterns**: Use signals, standalone components, and modern control flow.
|
|
277
282
|
- **Theming**: Follow KIP's theme system for consistent UI.
|
|
278
283
|
- **Code Quality**: Run `npm run lint` before commits.
|
|
@@ -285,3 +290,6 @@ KIP has its own Discord Signal K channel for getting in touch. Join us at https:
|
|
|
285
290
|
# Features, Ideas, Bugs
|
|
286
291
|
See KIP's GitHub project for the latest feature requests:
|
|
287
292
|
https://github.com/mxtommy/Kip/issues
|
|
293
|
+
|
|
294
|
+
This repository may not be used to train machine learning or AI models
|
|
295
|
+
without explicit permission from the author.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mxtommy/kip",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.8.0",
|
|
4
4
|
"description": "An advanced and versatile marine instrumentation package to display Signal K data.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -51,84 +51,73 @@
|
|
|
51
51
|
],
|
|
52
52
|
"signalk-plugin-enabled-by-default": true,
|
|
53
53
|
"scripts": {
|
|
54
|
-
"test": "
|
|
54
|
+
"test": "ng test --watch=false",
|
|
55
55
|
"test:plugin": "npm run build:plugin && node --test --test-concurrency=1 kip-plugin/tests/index.test.cjs",
|
|
56
56
|
"test:interactive": "ng test",
|
|
57
|
-
"test:headless": "CI=1 ng test --
|
|
57
|
+
"test:headless": "CI=1 ng test --watch=false",
|
|
58
58
|
"lint": "ng lint",
|
|
59
59
|
"dev": "ng serve --configuration=dev --serve-path=/@mxtommy/kip/",
|
|
60
60
|
"build:plugin": "tsc -p ./kip-plugin/tsconfig.plugin.json",
|
|
61
61
|
"build:dev": "ng build --configuration=dev",
|
|
62
62
|
"build:prod": "ng build --configuration=production",
|
|
63
63
|
"build:all": "npm run build:plugin && npm run build:prod",
|
|
64
|
-
"e2e": "ng e2e",
|
|
65
64
|
"generate:widget": "npx schematics ./tools/schematics/collection.json:create-host2-widget --dry-run=false"
|
|
66
65
|
},
|
|
67
66
|
"schematics": "tools/schematics/collection.json",
|
|
68
67
|
"devDependencies": {
|
|
68
|
+
"@angular-devkit/build-angular": "^21.2.3",
|
|
69
|
+
"@angular-devkit/schematics-cli": "^20.1.6",
|
|
70
|
+
"@angular/animations": "21.2.5",
|
|
71
|
+
"@angular/build": "^21.2.3",
|
|
69
72
|
"@angular/cdk": "21.2.3",
|
|
73
|
+
"@angular/cli": "^21.2.3",
|
|
70
74
|
"@angular/common": "21.2.5",
|
|
71
75
|
"@angular/compiler": "21.2.5",
|
|
76
|
+
"@angular/compiler-cli": "21.2.5",
|
|
72
77
|
"@angular/core": "21.2.5",
|
|
73
78
|
"@angular/forms": "21.2.5",
|
|
79
|
+
"@angular/language-service": "21.2.5",
|
|
74
80
|
"@angular/material": "21.2.3",
|
|
75
|
-
"@angular/animations": "21.2.5",
|
|
76
81
|
"@angular/platform-browser": "21.2.5",
|
|
77
82
|
"@angular/platform-browser-dynamic": "21.2.5",
|
|
78
83
|
"@angular/router": "21.2.5",
|
|
79
|
-
"@
|
|
80
|
-
"@
|
|
81
|
-
"@angular/build": "^21.2.3",
|
|
82
|
-
"@angular/cli": "^21.2.3",
|
|
83
|
-
"@angular/compiler-cli": "21.2.5",
|
|
84
|
-
"@angular/language-service": "21.2.5",
|
|
84
|
+
"@aziham/chartjs-plugin-streaming": "^3.5.1",
|
|
85
|
+
"@godind/ng-canvas-gauges": "^6.2.1",
|
|
85
86
|
"@types/canvas-gauges": "^2.1.8",
|
|
86
87
|
"@types/d3": "^7.4.3",
|
|
87
|
-
"@types/jasmine": "~3.6.0",
|
|
88
|
-
"@types/jasminewd2": "^2.0.9",
|
|
89
88
|
"@types/js-quantities": "^1.6.6",
|
|
90
89
|
"@types/lodash-es": "^4.17.9",
|
|
91
90
|
"@types/node": "^24.1.0",
|
|
92
|
-
"
|
|
93
|
-
"codelyzer": "^6.0.0",
|
|
94
|
-
"eslint": "^9.29.0",
|
|
95
|
-
"jasmine-core": "~4.0.1",
|
|
96
|
-
"jasmine-spec-reporter": "~5.0.0",
|
|
97
|
-
"karma": "^6.4.4",
|
|
98
|
-
"karma-chrome-launcher": "~3.1.0",
|
|
99
|
-
"karma-cli": "~2.0.0",
|
|
100
|
-
"karma-coverage": "^2.2.0",
|
|
101
|
-
"karma-jasmine": "~4.0.0",
|
|
102
|
-
"karma-jasmine-html-reporter": "^1.6.0",
|
|
103
|
-
"karma-spec-reporter": "^0.0.36",
|
|
104
|
-
"ng-packagr": "^21.0.1",
|
|
105
|
-
"protractor": "~7.0.0",
|
|
106
|
-
"pwa-asset-generator": "^8.1.1",
|
|
107
|
-
"sass": "^1.49.9",
|
|
108
|
-
"ts-node": "^10.9.2",
|
|
109
|
-
"typescript": "^5.9.3",
|
|
110
|
-
"@aziham/chartjs-plugin-streaming": "^3.5.1",
|
|
111
|
-
"@godind/ng-canvas-gauges": "^6.2.1",
|
|
91
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
112
92
|
"@zakj/no-sleep": "^0.13.5",
|
|
93
|
+
"angular-eslint": "21.3.1",
|
|
113
94
|
"chart.js": "^4.5.1",
|
|
114
95
|
"chartjs-adapter-date-fns": "^3.0.0",
|
|
115
96
|
"chartjs-plugin-annotation": "^3.0.1",
|
|
116
97
|
"clipboard": "^2.0.11",
|
|
98
|
+
"codelyzer": "^6.0.0",
|
|
117
99
|
"compare-versions": "^6.1.1",
|
|
118
100
|
"core-js": "^3.13.1",
|
|
119
101
|
"d3": "^7.9.0",
|
|
120
102
|
"date-fns": "^2.30.0",
|
|
103
|
+
"eslint": "^9.29.0",
|
|
121
104
|
"gridstack": "^12.3.3",
|
|
122
105
|
"js-quantities": "^1.8.0",
|
|
106
|
+
"jsdom": "^26.1.0",
|
|
123
107
|
"lodash-es": "^4.17.23",
|
|
108
|
+
"ng-packagr": "^21.0.1",
|
|
124
109
|
"ngx-markdown": "^21.0.1",
|
|
125
110
|
"prismjs": "^1.30.0",
|
|
111
|
+
"pwa-asset-generator": "^8.1.1",
|
|
126
112
|
"rxjs": "^7.8.2",
|
|
113
|
+
"sass": "^1.49.9",
|
|
127
114
|
"screenfull": "^6.0.2",
|
|
128
115
|
"sk-ais-status-plugin": "^1.0.0",
|
|
129
116
|
"steelseries": "^2.0.9",
|
|
117
|
+
"ts-node": "^10.9.2",
|
|
130
118
|
"tslib": "^2.6.2",
|
|
131
|
-
"
|
|
119
|
+
"typescript": "^5.9.3",
|
|
120
|
+
"vitest": "^4.1.0"
|
|
132
121
|
},
|
|
133
122
|
"dependencies": {
|
|
134
123
|
"@signalk/server-api": "^2.22.0"
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.HistorySeriesService = exports.isKipTemplateSeriesDefinition = exports.isKipSeriesEnabled = exports.isKipConcreteSeriesDefinition = void 0;
|
|
3
|
+
exports.HistorySeriesService = exports.isKipTemplateSeriesDefinition = exports.isKipSolarTemplateSeriesDefinition = exports.isKipSeriesEnabled = exports.isKipElectricalTemplateSeriesDefinition = exports.isKipConcreteSeriesDefinition = exports.isKipBmsTemplateSeriesDefinition = void 0;
|
|
4
4
|
const kip_series_contract_1 = require("./kip-series-contract");
|
|
5
|
+
Object.defineProperty(exports, "isKipElectricalTemplateSeriesDefinition", { enumerable: true, get: function () { return kip_series_contract_1.isKipElectricalTemplateSeriesDefinition; } });
|
|
6
|
+
Object.defineProperty(exports, "isKipBmsTemplateSeriesDefinition", { enumerable: true, get: function () { return kip_series_contract_1.isKipBmsTemplateSeriesDefinition; } });
|
|
5
7
|
Object.defineProperty(exports, "isKipConcreteSeriesDefinition", { enumerable: true, get: function () { return kip_series_contract_1.isKipConcreteSeriesDefinition; } });
|
|
6
8
|
Object.defineProperty(exports, "isKipSeriesEnabled", { enumerable: true, get: function () { return kip_series_contract_1.isKipSeriesEnabled; } });
|
|
9
|
+
Object.defineProperty(exports, "isKipSolarTemplateSeriesDefinition", { enumerable: true, get: function () { return kip_series_contract_1.isKipSolarTemplateSeriesDefinition; } });
|
|
7
10
|
Object.defineProperty(exports, "isKipTemplateSeriesDefinition", { enumerable: true, get: function () { return kip_series_contract_1.isKipTemplateSeriesDefinition; } });
|
|
8
11
|
/**
|
|
9
12
|
* Manages history capture series definitions and serves History API-compatible query results.
|
|
@@ -225,6 +228,18 @@ class HistorySeriesService {
|
|
|
225
228
|
if (!seriesKeys || seriesKeys.length === 0) {
|
|
226
229
|
return;
|
|
227
230
|
}
|
|
231
|
+
const hasSpecificSourceMatch = seriesKeys.some(seriesKey => {
|
|
232
|
+
const series = this.seriesById.get(seriesKey);
|
|
233
|
+
if (!series) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
const seriesContext = series.context ?? 'vessels.self';
|
|
237
|
+
if (!this.isContextMatch(seriesContext, context)) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
const seriesSource = series.source ?? 'default';
|
|
241
|
+
return seriesSource !== 'default' && seriesSource === source;
|
|
242
|
+
});
|
|
228
243
|
seriesKeys.forEach(seriesKey => {
|
|
229
244
|
const series = this.seriesById.get(seriesKey);
|
|
230
245
|
if (!series) {
|
|
@@ -238,6 +253,9 @@ class HistorySeriesService {
|
|
|
238
253
|
if (!this.isSourceMatch(seriesSource, source)) {
|
|
239
254
|
return;
|
|
240
255
|
}
|
|
256
|
+
if (seriesSource === 'default' && source !== 'default' && hasSpecificSourceMatch) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
241
259
|
if (this.recordSampleByKey(seriesKey, leaf.value, ts)) {
|
|
242
260
|
recorded += 1;
|
|
243
261
|
}
|
|
@@ -326,7 +344,9 @@ class HistorySeriesService {
|
|
|
326
344
|
&& leftComparable.ownerWidgetSelector === rightComparable.ownerWidgetSelector
|
|
327
345
|
&& leftComparable.path === rightComparable.path
|
|
328
346
|
&& leftComparable.expansionMode === rightComparable.expansionMode
|
|
329
|
-
&&
|
|
347
|
+
&& leftComparable.familyKey === rightComparable.familyKey
|
|
348
|
+
&& this.areStringArraysEquivalent(leftComparable.allowedIds, rightComparable.allowedIds)
|
|
349
|
+
&& this.areTrackedDevicesEquivalent(leftComparable.trackedDevices, rightComparable.trackedDevices)
|
|
330
350
|
&& leftComparable.source === rightComparable.source
|
|
331
351
|
&& leftComparable.context === rightComparable.context
|
|
332
352
|
&& leftComparable.timeScale === rightComparable.timeScale
|
|
@@ -341,10 +361,22 @@ class HistorySeriesService {
|
|
|
341
361
|
void reconcileTs;
|
|
342
362
|
return {
|
|
343
363
|
...comparable,
|
|
344
|
-
|
|
364
|
+
allowedIds: this.normalizeComparableStringArray(comparable.allowedIds),
|
|
365
|
+
trackedDevices: this.normalizeComparableTrackedDevices(comparable.trackedDevices),
|
|
345
366
|
methods: this.normalizeComparableStringArray(comparable.methods)
|
|
346
367
|
};
|
|
347
368
|
}
|
|
369
|
+
areTrackedDevicesEquivalent(left, right) {
|
|
370
|
+
const normalizedLeft = this.normalizeComparableTrackedDevices(left) ?? [];
|
|
371
|
+
const normalizedRight = this.normalizeComparableTrackedDevices(right) ?? [];
|
|
372
|
+
if (normalizedLeft.length !== normalizedRight.length) {
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
return normalizedLeft.every((value, index) => {
|
|
376
|
+
const candidate = normalizedRight[index];
|
|
377
|
+
return value.id === candidate?.id && value.source === candidate?.source;
|
|
378
|
+
});
|
|
379
|
+
}
|
|
348
380
|
areStringArraysEquivalent(left, right) {
|
|
349
381
|
const normalizedLeft = this.normalizeComparableStringArray(left) ?? [];
|
|
350
382
|
const normalizedRight = this.normalizeComparableStringArray(right) ?? [];
|
|
@@ -361,6 +393,31 @@ class HistorySeriesService {
|
|
|
361
393
|
.filter((value) => typeof value === 'string')
|
|
362
394
|
.sort((left, right) => left.localeCompare(right));
|
|
363
395
|
}
|
|
396
|
+
normalizeComparableTrackedDevices(values) {
|
|
397
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
398
|
+
return undefined;
|
|
399
|
+
}
|
|
400
|
+
const normalizedByKey = new Map();
|
|
401
|
+
values.forEach(value => {
|
|
402
|
+
if (!value || typeof value !== 'object') {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const id = typeof value.id === 'string' ? value.id.trim() : '';
|
|
406
|
+
const sourceText = typeof value.source === 'string' ? value.source.trim() : '';
|
|
407
|
+
const source = sourceText.length > 0 ? sourceText : 'default';
|
|
408
|
+
if (!id) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
normalizedByKey.set(`${id}||${source}`, { id, source });
|
|
412
|
+
});
|
|
413
|
+
if (normalizedByKey.size === 0) {
|
|
414
|
+
return undefined;
|
|
415
|
+
}
|
|
416
|
+
return Array.from(normalizedByKey.values()).sort((left, right) => {
|
|
417
|
+
const idCompare = left.id.localeCompare(right.id);
|
|
418
|
+
return idCompare !== 0 ? idCompare : left.source.localeCompare(right.source);
|
|
419
|
+
});
|
|
420
|
+
}
|
|
364
421
|
isChartWidget(ownerWidgetSelector, ownerWidgetUuid) {
|
|
365
422
|
if (ownerWidgetSelector === 'widget-data-chart' || ownerWidgetSelector === 'widget-windtrends-chart') {
|
|
366
423
|
return true;
|
|
@@ -387,12 +444,29 @@ class HistorySeriesService {
|
|
|
387
444
|
}
|
|
388
445
|
const ownerWidgetSelector = typeof input.ownerWidgetSelector === 'string' ? input.ownerWidgetSelector.trim() : null;
|
|
389
446
|
const expansionMode = input.expansionMode ?? null;
|
|
390
|
-
|
|
391
|
-
|
|
447
|
+
const familyKey = expansionMode ? this.expansionModeToFamilyKey(expansionMode) : null;
|
|
448
|
+
let normalizedTemplateSelector = null;
|
|
449
|
+
if (expansionMode) {
|
|
450
|
+
const selectorByMode = {
|
|
451
|
+
'bms-battery-tree': 'widget-bms',
|
|
452
|
+
'solar-tree': 'widget-solar-charger',
|
|
453
|
+
'charger-tree': 'widget-charger',
|
|
454
|
+
'inverter-tree': 'widget-inverter',
|
|
455
|
+
'alternator-tree': 'widget-alternator',
|
|
456
|
+
'ac-tree': 'widget-ac'
|
|
457
|
+
};
|
|
458
|
+
const requiredSelector = selectorByMode[expansionMode];
|
|
459
|
+
if (ownerWidgetSelector !== requiredSelector) {
|
|
460
|
+
throw new Error(`Template series mode "${expansionMode}" must use ownerWidgetSelector "${requiredSelector}"`);
|
|
461
|
+
}
|
|
462
|
+
normalizedTemplateSelector = requiredSelector;
|
|
392
463
|
}
|
|
393
464
|
const normalizedMethods = this.normalizeComparableStringArray(input.methods);
|
|
394
|
-
const
|
|
395
|
-
? this.normalizeComparableStringArray(input.
|
|
465
|
+
const normalizedAllowedIds = expansionMode
|
|
466
|
+
? this.normalizeComparableStringArray(input.allowedIds)
|
|
467
|
+
: undefined;
|
|
468
|
+
const normalizedTrackedDevices = expansionMode
|
|
469
|
+
? this.normalizeComparableTrackedDevices(input.trackedDevices)
|
|
396
470
|
: undefined;
|
|
397
471
|
const isDataWidget = this.isChartWidget(ownerWidgetSelector, ownerWidgetUuid);
|
|
398
472
|
const retentionMs = this.resolveRetentionMs(input);
|
|
@@ -413,6 +487,7 @@ class HistorySeriesService {
|
|
|
413
487
|
ownerWidgetUuid,
|
|
414
488
|
ownerWidgetSelector,
|
|
415
489
|
path,
|
|
490
|
+
familyKey,
|
|
416
491
|
source: input.source ?? 'default',
|
|
417
492
|
context: input.context ?? 'vessels.self',
|
|
418
493
|
timeScale: input.timeScale ?? null,
|
|
@@ -423,22 +498,44 @@ class HistorySeriesService {
|
|
|
423
498
|
methods: normalizedMethods,
|
|
424
499
|
reconcileTs: input.reconcileTs
|
|
425
500
|
};
|
|
426
|
-
if (expansionMode
|
|
501
|
+
if (expansionMode) {
|
|
427
502
|
const templateSeries = {
|
|
428
503
|
...normalizedBase,
|
|
429
|
-
ownerWidgetSelector:
|
|
504
|
+
ownerWidgetSelector: normalizedTemplateSelector,
|
|
430
505
|
expansionMode,
|
|
431
|
-
|
|
506
|
+
familyKey,
|
|
507
|
+
allowedIds: normalizedAllowedIds ?? null,
|
|
508
|
+
trackedDevices: normalizedTrackedDevices ?? null
|
|
432
509
|
};
|
|
433
510
|
return templateSeries;
|
|
434
511
|
}
|
|
435
512
|
const concreteSeries = {
|
|
436
513
|
...normalizedBase,
|
|
437
514
|
expansionMode: null,
|
|
438
|
-
|
|
515
|
+
familyKey: null,
|
|
516
|
+
allowedIds: null,
|
|
517
|
+
trackedDevices: null
|
|
439
518
|
};
|
|
440
519
|
return concreteSeries;
|
|
441
520
|
}
|
|
521
|
+
expansionModeToFamilyKey(mode) {
|
|
522
|
+
switch (mode) {
|
|
523
|
+
case 'bms-battery-tree':
|
|
524
|
+
return 'batteries';
|
|
525
|
+
case 'solar-tree':
|
|
526
|
+
return 'solar';
|
|
527
|
+
case 'charger-tree':
|
|
528
|
+
return 'chargers';
|
|
529
|
+
case 'inverter-tree':
|
|
530
|
+
return 'inverters';
|
|
531
|
+
case 'alternator-tree':
|
|
532
|
+
return 'alternators';
|
|
533
|
+
case 'ac-tree':
|
|
534
|
+
return 'ac';
|
|
535
|
+
default:
|
|
536
|
+
throw new Error(`Unsupported expansion mode: ${String(mode)}`);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
442
539
|
resolveRetentionMs(series) {
|
|
443
540
|
if (Number.isFinite(series.retentionDurationMs) && series.retentionDurationMs > 0) {
|
|
444
541
|
return series.retentionDurationMs;
|