Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@mxtommy/kip

Package Overview
Dependencies
Maintainers
1
Versions
90
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@mxtommy/kip - npm Package Compare versions

Comparing version 0.1.6 to 0.1.7

public/inline.318b50c57b4eba3d437b.bundle.js

0

.angular-cli.json

@@ -0,0 +0,0 @@ {

@@ -16,2 +16,22 @@ ### V0.1

### V0.1.5
* fix select dialogs (missing mat-form-field)
* fix select dialogs (missing mat-form-field)
### V0.1.6
* Hash based routing
* Start of Boolean state widget
* default to /signalk in url
*
### V0.1.7
Note, Any configs stored in browser will be lost as config format has changed.
* Performance gain in Numeric widget in large fonts
* Sailgauge updates (laylines, windsectors, general refactor)
* Simplified source selection in the code
* Removed Derived Data
* Added Percentage unit, and stopped showing "no unit" on numeric
* Updated Angular from v4 to v5 and also all dependencies to latest version
* Complete re-write of widget settings modal for future ease of coding
* Complete re-write of unit conversion service. Now if metadata specifies unit, it only offers you compatible units
* Gauge Background and frame color options!
* new Signal K Theme

63

package.json
{
"name": "@mxtommy/kip",
"version": "0.1.6",
"version": "0.1.7",
"license": "MIT",

@@ -26,34 +26,39 @@ "keywords": [

},
"repository": {
"type": "git",
"url": "https://github.com/npm/npm.git"
},
"dependencies": {
"@angular/animations": "^4.4.3",
"@angular/cdk": "^2.0.0-beta.11",
"@angular/common": "^4.0.0",
"@angular/compiler": "^4.0.0",
"@angular/core": "^4.0.0",
"@angular/forms": "^4.0.0",
"@angular/http": "^4.0.0",
"@angular/material": "^2.0.0-beta.11",
"@angular/platform-browser": "^4.0.0",
"@angular/platform-browser-dynamic": "^4.0.0",
"@angular/router": "^4.0.0",
"angular-split": "^0.2.2",
"chart.js": "^2.7.0",
"core-js": "^2.4.1",
"@angular/animations": "^5.2.9",
"@angular/cdk": "^5.2.4",
"@angular/common": "^5.2.9",
"@angular/compiler": "^5.2.9",
"@angular/core": "^5.2.9",
"@angular/forms": "^5.2.9",
"@angular/http": "^5.2.9",
"@angular/material": "^5.2.4",
"@angular/platform-browser": "^5.2.9",
"@angular/platform-browser-dynamic": "^5.2.9",
"@angular/platform-server": "^5.2.9",
"@angular/router": "^5.2.9",
"angular-split": "^1.0.0-rc.3",
"chart.js": "^2.7.2",
"core-js": "^2.5.3",
"js-quantities": "^1.7.0",
"rxjs": "^5.4.1",
"rxjs": "^5.5.7",
"screenfull": "^3.3.2",
"zone.js": "^0.8.14"
"zone.js": "^0.8.19"
},
"devDependencies": {
"@angular/cli": "1.2.7",
"@angular/compiler-cli": "^4.0.0",
"@angular/language-service": "^4.0.0",
"@types/jasmine": "~2.5.53",
"@angular/cli": "^1.7.3",
"@angular/compiler-cli": "^5.2.9",
"@angular/language-service": "^5.2.9",
"@types/jasmine": "~2.8.3",
"@types/jasminewd2": "~2.0.2",
"@types/node": "~6.0.60",
"codelyzer": "~3.0.1",
"jasmine-core": "~2.6.2",
"jasmine-spec-reporter": "~4.1.0",
"karma": "~1.7.0",
"karma-chrome-launcher": "~2.1.1",
"codelyzer": "^4.0.1",
"jasmine-core": "~2.8.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~2.0.0",
"karma-chrome-launcher": "~2.2.0",
"karma-cli": "~1.0.1",

@@ -64,6 +69,6 @@ "karma-coverage-istanbul-reporter": "^1.2.1",

"protractor": "~5.1.2",
"ts-node": "~3.0.4",
"tslint": "~5.3.2",
"typescript": "~2.3.3"
"ts-node": "~4.1.0",
"tslint": "~5.9.1",
"typescript": "~2.5.3"
}
}
/*! NoSleep.min.js v0.7.0 - git.io/vfn01 - Rich Tibbett - MIT license */
!function(A,B){"object"==typeof exports&&"object"==typeof module?module.exports=B():"function"==typeof define&&define.amd?define([],B):"object"==typeof exports?exports.NoSleep=B():A.NoSleep=B()}(this,function(){return function(A){function B(e){if(Q[e])return Q[e].exports;var o=Q[e]={i:e,l:!1,exports:{}};return A[e].call(o.exports,o,o.exports,B),o.l=!0,o.exports}var Q={};return B.m=A,B.c=Q,B.d=function(A,Q,e){B.o(A,Q)||Object.defineProperty(A,Q,{configurable:!1,enumerable:!0,get:e})},B.n=function(A){var Q=A&&A.__esModule?function(){return A.default}:function(){return A};return B.d(Q,"a",Q),Q},B.o=function(A,B){return Object.prototype.hasOwnProperty.call(A,B)},B.p="",B(B.s=0)}([function(A,B,Q){"use strict";function e(A,B){if(!(A instanceof B))throw new TypeError("Cannot call a class as a function")}var o=function(){function A(A,B){for(var Q=0;Q<B.length;Q++){var e=B[Q];e.enumerable=e.enumerable||!1,e.configurable=!0,"value"in e&&(e.writable=!0),Object.defineProperty(A,e.key,e)}}return function(B,Q,e){return Q&&A(B.prototype,Q),e&&A(B,e),B}}(),t=Q(1),n="undefined"!=typeof navigator&&parseFloat((""+(/CPU.*OS ([0-9_]{3,4})[0-9_]{0,1}|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent)||[0,""])[1]).replace("undefined","3_2").replace("_",".").replace("_",""))<10&&!window.MSStream,E=function(){function A(){e(this,A),n?this.noSleepTimer=null:(this.noSleepVideo=document.createElement("video"),this.noSleepVideo.setAttribute("playsinline",""),this.noSleepVideo.setAttribute("src",t),this.noSleepVideo.addEventListener("timeupdate",function(A){this.noSleepVideo.currentTime>.5&&(this.noSleepVideo.currentTime=Math.random())}.bind(this)))}return o(A,[{key:"enable",value:function(){n?(this.disable(),this.noSleepTimer=window.setInterval(function(){window.location.href="/",window.setTimeout(window.stop,0)},15e3)):this.noSleepVideo.play()}},{key:"disable",value:function(){n?this.noSleepTimer&&(window.clearInterval(this.noSleepTimer),this.noSleepTimer=null):this.noSleepVideo.pause()}}]),A}();A.exports=E},function(A,B,Q){"use strict";A.exports="data:video/mp4;base64,AAAAIGZ0eXBtcDQyAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAACKBtZGF0AAAC8wYF///v3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE0MiByMjQ3OSBkZDc5YTYxIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxNCAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTEgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MToweDExMSBtZT1oZXggc3VibWU9MiBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0wIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MCA4eDhkY3Q9MCBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0wIHRocmVhZHM9NiBsb29rYWhlYWRfdGhyZWFkcz0xIHNsaWNlZF90aHJlYWRzPTAgbnI9MCBkZWNpbWF0ZT0xIGludGVybGFjZWQ9MCBibHVyYXlfY29tcGF0PTAgY29uc3RyYWluZWRfaW50cmE9MCBiZnJhbWVzPTMgYl9weXJhbWlkPTIgYl9hZGFwdD0xIGJfYmlhcz0wIGRpcmVjdD0xIHdlaWdodGI9MSBvcGVuX2dvcD0wIHdlaWdodHA9MSBrZXlpbnQ9MzAwIGtleWludF9taW49MzAgc2NlbmVjdXQ9NDAgaW50cmFfcmVmcmVzaD0wIHJjX2xvb2thaGVhZD0xMCByYz1jcmYgbWJ0cmVlPTEgY3JmPTIwLjAgcWNvbXA9MC42MCBxcG1pbj0wIHFwbWF4PTY5IHFwc3RlcD00IHZidl9tYXhyYXRlPTIwMDAwIHZidl9idWZzaXplPTI1MDAwIGNyZl9tYXg9MC4wIG5hbF9ocmQ9bm9uZSBmaWxsZXI9MCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAAOWWIhAA3//p+C7v8tDDSTjf97w55i3SbRPO4ZY+hkjD5hbkAkL3zpJ6h/LR1CAABzgB1kqqzUorlhQAAAAxBmiQYhn/+qZYADLgAAAAJQZ5CQhX/AAj5IQAD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"}])});

@@ -0,0 +0,0 @@ /**********************************************************************

@@ -20,14 +20,14 @@ # Kip

### V0.2
* Sailgauge Laylines and sectors
* Customization options for radial/linear Gauges
* Sailgauge Laylines and sectors - Done!
* Customization options for radial/linear Gauges - In Progress
* Customization options for Historical Charts
* Pre-created widgets for commonly used displays
* Redo conversion service
* Know at least which type of unit it should be. (either with meta or hard coded/derived from schema)
* Redo conversion service
* Know at least which type of unit it should be. Done!
* Default unit for each unit type in config.
* Show SignalK status/alert if disconnected
* Radial/Linear Gauge Zones
* Hash based routing to be friendly to node server *Done!
* Boolean state widget
* Attitude Indicator
* Hash based routing to be friendly to node server * Done!
* Boolean state widget * In progress
* Attitude Indicator * In progress

@@ -40,3 +40,2 @@ ### V0.3

* Delete Page.
* Support boolean values.
* Numeric widget max/avg values.

@@ -43,0 +42,0 @@

@@ -11,6 +11,6 @@ import { Injectable } from '@angular/core';

import { IWidget } from './widget-manager.service';
import { IDerivation } from './derived.service';
import { BlankConfig } from './blank-config.const';
import { DemoConfig } from './demo-config.const';
import { isNumber } from 'util';

@@ -20,4 +20,7 @@ const defaultSignalKUrl = 'http://demo.signalk.org/signalk';

const defaultTheme = 'default-light';
const configVersion = 1; // used to invalidate old configs to avoir errors loading it.
interface appSettings {
configVersion: number;
signalKUrl: string;

@@ -30,3 +33,2 @@ themeName: string;

rootSplits: string[];
derivations: IDerivation[];
}

@@ -47,3 +49,2 @@

rootSplits: string[] = [];
derivations: IDerivation[] = [];

@@ -56,7 +57,14 @@ themeName: BehaviorSubject<string> = new BehaviorSubject<string>(defaultTheme);

private router: Router) {
let storageObject: appSettings
if (localStorage.getItem('signalKData') == null) {
this.setDefaultConfig();
storageObject = this.getDefaultConfig();
}
storageObject = JSON.parse(localStorage.getItem('signalKData'));
if (!isNumber(storageObject.configVersion) || (storageObject.configVersion != configVersion)) {
console.error("Invalid config version, loading default");
storageObject = this.getDefaultConfig();
}
this.loadSettings();
this.loadSettings(storageObject);

@@ -66,4 +74,3 @@ }

loadSettings() {
let storageObject: appSettings = JSON.parse(localStorage.getItem('signalKData'));
loadSettings(storageObject: appSettings) {
this.signalKUrl.next(storageObject['signalKUrl']);

@@ -76,3 +83,2 @@ this.themeName.next(storageObject['themeName']);

this.rootSplits = storageObject.rootSplits;
this.derivations = storageObject.derivations;
}

@@ -147,10 +153,2 @@

// derivations
getDerivations() {
return this.derivations;
}
saveDerivations(derivations: IDerivation[]) {
this.derivations = derivations;
this.saveToLocalStorage();
}

@@ -162,2 +160,3 @@ // saving.

let storageObject: appSettings = {
configVersion: configVersion,
signalKUrl: this.signalKUrl.getValue(),

@@ -170,3 +169,2 @@ themeName: this.themeName.getValue(),

rootSplits: this.rootSplits,
derivations: this.derivations
}

@@ -205,7 +203,9 @@ return storageObject;

setDefaultConfig() {
getDefaultConfig(): appSettings {
let config = BlankConfig;
config.signalKUrl = window.location.origin;
config['configVersion'] = configVersion;
localStorage.setItem('signalKData', JSON.stringify(config));
return config;
}
}

@@ -11,3 +11,3 @@ import { Component, OnInit, OnDestroy } from '@angular/core';

import { DataSetService } from './data-set.service';
import { DerivedService } from './derived.service';
//import { DerivedService } from './derived.service';

@@ -43,3 +43,3 @@

private DataSetService: DataSetService,
private DerivedService: DerivedService,
//private DerivedService: DerivedService,
private overlayContainer: OverlayContainer,

@@ -65,3 +65,3 @@ private LayoutSplitsService: LayoutSplitsService) { }

this.DataSetService.startAllDataSets();
this.DerivedService.startAllDerivations();
//this.DerivedService.startAllDerivations();
}

@@ -68,0 +68,0 @@

@@ -5,2 +5,3 @@ import { BrowserModule } from '@angular/platform-browser';

import { FormsModule } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

@@ -39,7 +40,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { UnitConvertService } from './unit-convert.service';
import { DerivedService } from './derived.service';
import { UnitsService } from './units.service';
import { WidgetBlankComponent } from './widget-blank/widget-blank.component';
import { WidgetUnknownComponent } from './widget-unknown/widget-unknown.component';
import { WidgetTextGenericComponent, WidgetTextGenericModalComponent } from './widget-text-generic/widget-text-generic.component';
import { WidgetTextGenericComponent } from './widget-text-generic/widget-text-generic.component';
import { UnitWindowComponent, UnitWindowModalComponent } from './unit-window/unit-window.component';

@@ -49,16 +50,19 @@ import { SettingsComponent } from './settings/settings.component';

import { FilterSelfPipe } from './filter-self.pipe';
import { WidgetNumericComponent, WidgetNumericModalComponent } from './widget-numeric/widget-numeric.component';
import { WidgetNumericComponent } from './widget-numeric/widget-numeric.component';
import { SettingsDatasetsComponent, SettingsDatasetsModalComponent } from './settings-datasets/settings-datasets.component';
import { SettingsSignalkComponent } from './settings-signalk/settings-signalk.component';
import { WidgetHistoricalComponent, WidgetHistoricalModalComponent } from './widget-historical/widget-historical.component';
import { WidgetHistoricalComponent } from './widget-historical/widget-historical.component';
import { LayoutSplitComponent } from './layout-split/layout-split.component';
import { WidgetWindComponent, WidgetWindModalComponent } from './widget-wind/widget-wind.component';
import { WidgetWindComponent, } from './widget-wind/widget-wind.component';
import { SvgWindComponent } from './svg-wind/svg-wind.component';
import { WidgetGaugeComponent, WidgetGaugeModalComponent } from './widget-gauge/widget-gauge.component';
import { WidgetGaugeComponent } from './widget-gauge/widget-gauge.component';
import { GaugeSteelComponent } from './gauge-steel/gauge-steel.component';
import { SettingsConfigComponent } from './settings-config/settings-config.component';
import { SettingsDerivedComponent, SettingsDerivedModalComponent } from './settings-derived/settings-derived.component';
import { WidgetTutorialComponent } from './widget-tutorial/widget-tutorial.component';
import { ResetConfigComponent } from './reset-config/reset-config.component';
import { WidgetStateComponent, WidgetStateModalComponent } from './widget-state/widget-state.component'
import { WidgetStateComponent } from './widget-state/widget-state.component';
import { ModalWidgetComponent } from './modal-widget/modal-widget.component';
import { ModalPathSelectorComponent } from './modal-path-selector/modal-path-selector.component';
import { ModalUnitSelectorComponent } from './modal-unit-selector/modal-unit-selector.component';
import { ObjectKeysPipe } from './object-keys.pipe'

@@ -84,3 +88,2 @@

WidgetTextGenericComponent,
WidgetTextGenericModalComponent,
FitTextDirective,

@@ -90,3 +93,2 @@ RootDisplayComponent,

WidgetNumericComponent,
WidgetNumericModalComponent,
SettingsDatasetsComponent,

@@ -96,17 +98,15 @@ SettingsDatasetsModalComponent,

WidgetHistoricalComponent,
WidgetHistoricalModalComponent,
LayoutSplitComponent,
WidgetWindComponent,
WidgetWindModalComponent,
SvgWindComponent,
WidgetGaugeComponent,
WidgetGaugeModalComponent,
GaugeSteelComponent,
SettingsConfigComponent,
SettingsDerivedComponent,
SettingsDerivedModalComponent,
WidgetTutorialComponent,
ResetConfigComponent,
WidgetStateComponent,
WidgetStateModalComponent
ModalWidgetComponent,
ModalPathSelectorComponent,
ModalUnitSelectorComponent,
ObjectKeysPipe
],

@@ -116,2 +116,3 @@ imports: [

FormsModule,
ReactiveFormsModule,
HttpClientModule,

@@ -138,3 +139,2 @@ RouterModule.forRoot(appRoutes, { useHash: true /* , enableTracing: true */ } ),

WidgetTextGenericComponent,
WidgetTextGenericModalComponent,
WidgetHistoricalComponent,

@@ -147,10 +147,5 @@ WidgetWindComponent,

//dialogs
ModalWidgetComponent,
UnitWindowModalComponent,
WidgetNumericModalComponent,
WidgetHistoricalModalComponent,
WidgetWindModalComponent,
WidgetGaugeModalComponent,
SettingsDerivedModalComponent,
SettingsDatasetsModalComponent,
WidgetStateModalComponent
],

@@ -167,4 +162,4 @@ providers: [

UnitConvertService,
DerivedService,
AppSettingsService
UnitsService,
AppSettingsService
],

@@ -171,0 +166,0 @@ bootstrap: [AppComponent]

export const BlankConfig = {
"configVersion": 0,
"signalKUrl": "", // get's overwritten with host

@@ -3,0 +4,0 @@ "themeName": "default-light",

@@ -36,2 +36,3 @@ import { Injectable } from '@angular/core';

interface DataSetSub {

@@ -164,14 +165,5 @@ uuid: string;

//Subscribe to path data
this.dataSetSub[dataSubIndex].pathSub = this.SignalKService.subscribePath(this.dataSets[dataIndex].uuid, this.dataSets[dataIndex].path).subscribe(
pathObject => {
if (pathObject === null) {
return; // we will get null back if we subscribe to a path before the app knows about it. when it learns about it we will get first value
}
let source: string;
if (this.dataSets[dataIndex].signalKSource == 'default') {
source = pathObject.defaultSource;
} else {
source = this.dataSets[dataIndex].signalKSource;
}
this.updateDataCache(uuid, pathObject.sources[source].value);
this.dataSetSub[dataSubIndex].pathSub = this.SignalKService.subscribePath(this.dataSets[dataIndex].uuid, this.dataSets[dataIndex].path, this.dataSets[dataIndex].signalKSource).subscribe(
newValue => {
this.updateDataCache(uuid, newValue);
});

@@ -215,3 +207,3 @@

getDataSets() {
getDataSets(): IDataSet[] {
let result = [];

@@ -218,0 +210,0 @@ for (let i=0;i<this.dataSets.length; i++) {

export const DemoConfig = {
"configVersion": 1,
"signalKUrl": "http://demo.signalk.org",

@@ -11,212 +12,176 @@ "themeName": "default-light",

"config": {
"signalKPath": "vessels.self.environment.wind.speedTrue",
"signalKSource": "default",
"label": "True Wind",
"unitGroup": "speed",
"unitName": "knots",
"numDecimal": 2,
"numInt": 2
"widgetLabel": "Depth Feet",
"paths": {
"numericPath": {
"description": "Numeric Data",
"path": "self.environment.depth.belowTransducer",
"source": "default",
"pathType": "number"
}
},
"units": {
"numericPath": "feet"
},
"selfPaths": true,
"numDecimal": 1,
"numInt": 1
}
},
{
"uuid": "a3d595eb-f1a0-45a2-9faa-7af31757f129",
"uuid": "7298b3be-232f-48bf-9b3d-3b433131a908",
"type": "WidgetWindComponent",
"config": {
"headingPath": "vessels.self.navigation.headingTrue",
"headingSource": "default",
"trueWindAnglePath": "vessels.self.environment.wind.angleTrueWater",
"trueWindAngleSource": "default",
"trueWindSpeedPath": "vessels.self.environment.wind.speedTrue",
"trueWindSpeedSource": "default",
"appWindAnglePath": "vessels.self.environment.wind.angleApparent",
"appWindAngleSource": "default",
"appWindSpeedPath": "vessels.self.environment.wind.speedApparent",
"appWindSpeedSource": "default",
"unitName": "knots"
"paths": {
"headingPath": {
"description": "Heading",
"path": "self.navigation.courseOverGroundTrue",
"source": "default",
"pathType": "number"
},
"trueWindAngle": {
"description": "True Wind Angle",
"path": "self.environment.wind.angleTrueWater",
"source": "default",
"pathType": "number"
},
"trueWindSpeed": {
"description": "True Wind Speed",
"path": "self.environment.wind.speedTrue",
"source": "default",
"pathType": "number"
},
"appWindAngle": {
"description": "Apparent Wind Angle",
"path": "self.environment.wind.angleApparent",
"source": "default",
"pathType": "number"
},
"appWindSpeed": {
"description": "Apparent Wind Speed",
"path": "self.environment.wind.speedApparent",
"source": "default",
"pathType": "number"
}
},
"units": {
"trueWindSpeed": "knots",
"appWindSpeed": "knots"
},
"selfPaths": true,
"windSectorEnable": true,
"windSectorWindowSeconds": 10,
"laylineEnable": true,
"laylineAngle": 35
}
},
{
"uuid": "e595897d-a78a-46c1-ba93-d4c6741983a4",
"type": "WidgetNumeric",
"config": {
"signalKPath": "vessels.self.environment.depth.belowKeel",
"signalKSource": "default",
"label": "Depth below keel",
"unitGroup": "distance",
"unitName": "feet",
"numDecimal": 2,
"numInt": 2
}
},
{
"uuid": "4d953a0b-a7be-4bdf-84fd-d6d244056378",
"type": "WidgetNumeric",
"config": {
"signalKPath": "vessels.self.environment.wind.speedApparent",
"signalKSource": "default",
"label": "App Wind",
"unitGroup": "speed",
"unitName": "knots",
"numDecimal": 2,
"numInt": 2
}
},
{
"uuid": "7955f86d-1ba6-4a2f-8c3e-a1f5da895768",
"type": "WidgetNumeric",
"config": {
"signalKPath": "vessels.self.navigation.speedThroughWater",
"signalKSource": "default",
"label": "Speed (Water)",
"unitGroup": "speed",
"unitName": "knots",
"numDecimal": 2,
"numInt": 2
}
},
{
"uuid": "d1068028-ea14-4db3-8a8d-8bbfea0fbfd3",
"type": "WidgetHistorical",
"config": {
"dataSetUUID": "a5e77585-7e7b-4dbc-860e-0a53da687794",
"label": "Depth",
"unitGroup": "distance",
"unitName": "m",
"numDecimal": 2,
"invertData": true,
"displayMinMax": false,
"animateGraph": false,
"suggestedMin": -50,
"suggestedMax": null,
"includeZero": true
}
},
{
"uuid": "1bdb821f-4b0d-4232-bccb-6ea8eebf04cf",
"type": "WidgetHistorical",
"config": {
"dataSetUUID": "63f7f37a-099c-48db-92aa-01e7ba2dc65d",
"label": "Wind-True",
"unitGroup": "speed",
"unitName": "knots",
"numDecimal": 2,
"invertData": false,
"displayMinMax": true,
"animateGraph": false,
"suggestedMin": null,
"suggestedMax": 20,
"includeZero": true
}
},
{
"uuid": "bb0da568-cb87-49f6-8359-48ff7b7cfa18",
"uuid": "912b86e4-e068-49e9-9f75-a2292d772578",
"type": "WidgetGaugeComponent",
"config": {
"widgetLabel": "Speed over ground",
"paths": {
"gaugePath": {
"description": "Numeric Data",
"path": "self.navigation.speedOverGround",
"source": "default",
"pathType": "number"
}
},
"units": {
"gaugePath": "knots"
},
"selfPaths": true,
"gaugeType": "radial",
"signalKPath": "vessels.self.electrical.batteries.1.voltage",
"signalKSource": "default",
"label": "Battery Voltage",
"unitGroup": "electrity",
"unitName": "volts",
"barGraph": false,
"radialSize": "three-quarter",
"minValue": 8,
"maxValue": 18
}
},
{
"uuid": "a626a0ac-dcef-4e80-a3db-b121fe144854",
"type": "WidgetGaugeComponent",
"config": {
"gaugeType": "linear",
"signalKPath": "vessels.self.electrical.batteries.1.temperature",
"signalKSource": "default",
"label": "Battery Temp",
"unitGroup": "temp",
"unitName": "C",
"barGraph": true,
"radialSize": "full",
"minValue": 0,
"maxValue": 100
"maxValue": 10,
"rotateFace": false,
"backgroundColor": "turned",
"frameColor": "tiltedBlack"
}
},
{
"uuid": "9b4420a5-5527-4be3-85fe-b8a6bff563f6",
"uuid": "85525ebc-c40c-41e6-8379-05d573a331e1",
"type": "WidgetGaugeComponent",
"config": {
"gaugeType": "radial",
"signalKPath": "vessels.self.environment.wind.speedApparent",
"signalKSource": "default",
"label": "App Wind",
"unitGroup": "speed",
"unitName": "knots",
"widgetLabel": "Apparent Wind Speed",
"paths": {
"gaugePath": {
"description": "Numeric Data",
"path": "self.environment.wind.speedApparent",
"source": "default",
"pathType": "number"
}
},
"units": {
"gaugePath": "knots"
},
"selfPaths": true,
"gaugeType": "linear",
"barGraph": true,
"radialSize": "full",
"minValue": 0,
"maxValue": 30
"maxValue": 30,
"rotateFace": false,
"backgroundColor": "stainless",
"frameColor": "chrome"
}
},
{
"uuid": "03e53522-77e0-4b09-8e1d-19959053a8fb",
"type": "WidgetGaugeComponent",
"uuid": "a49a59c6-b83d-40e0-b759-9d153da69105",
"type": "WidgetNumeric",
"config": {
"gaugeType": "linear",
"signalKPath": "vessels.self.environment.water.temperature",
"signalKSource": "default",
"label": "Water temperature",
"unitGroup": "temp",
"unitName": "C",
"barGraph": false,
"radialSize": "full",
"minValue": 0,
"maxValue": 30
"widgetLabel": "Speed (water)",
"paths": {
"numericPath": {
"description": "Numeric Data",
"path": "self.navigation.speedThroughWater",
"source": "default",
"pathType": "number"
}
},
"units": {
"numericPath": "knots"
},
"selfPaths": true,
"numDecimal": 1,
"numInt": 1
}
},
{
"uuid": "3c84892b-60bb-47c1-8b42-8beef6260972",
"type": "WidgetGaugeComponent",
"uuid": "62fa8155-10fd-49cb-a495-cee6e9491b8a",
"type": "WidgetNumeric",
"config": {
"gaugeType": "linear",
"signalKPath": "vessels.self.navigation.gnss.satellites",
"signalKSource": "default",
"label": "GPS Satellites",
"unitGroup": "discreet",
"unitName": "no unit",
"barGraph": true,
"radialSize": "full",
"minValue": 0,
"maxValue": 20
"widgetLabel": "VMG",
"paths": {
"numericPath": {
"description": "Numeric Data",
"path": "self.performance.velocityMadeGood",
"source": "default",
"pathType": "number"
}
},
"units": {
"numericPath": "knots"
},
"selfPaths": true,
"numDecimal": 1,
"numInt": 1
}
},
{
"uuid": "0e6067e7-e819-4a75-853c-d16dbfec5e05",
"type": "WidgetGaugeComponent",
"config": {
"gaugeType": "radial",
"signalKPath": "vessels.self.environment.wind.speedTrue",
"signalKSource": "default",
"label": "True Wind",
"unitGroup": "speed",
"unitName": "knots",
"barGraph": true,
"radialSize": "full",
"minValue": 0,
"maxValue": 30
}
},
{
"uuid": "072502e7-5512-4c69-8b5e-118a1290bc27",
"uuid": "42de0119-481c-4466-8b50-1407533ac2aa",
"type": "WidgetHistorical",
"config": {
"dataSetUUID": "dd9f511f-d94f-4f40-a20e-c08d2cd4690e",
"label": "True Wind",
"unitGroup": "speed",
"unitName": "knots",
"numDecimal": 2,
"widgetLabel": "WindSpeed True",
"units": {
"dataset": "knots"
},
"dataSetUUID": "afbe4e41-26f5-404f-a55d-9f7b9b76fbd1",
"invertData": false,
"displayMinMax": true,
"animateGraph": false,
"suggestedMin": null,
"suggestedMax": 30,
"includeZero": true
"displayMinMax": false,
"includeZero": true,
"minValue": null,
"maxValue": null
}

@@ -228,21 +193,7 @@ }

{
"uuid": "a5e77585-7e7b-4dbc-860e-0a53da687794",
"path": "vessels.self.environment.depth.belowKeel",
"uuid": "afbe4e41-26f5-404f-a55d-9f7b9b76fbd1",
"path": "self.environment.wind.speedTrue",
"signalKSource": "default",
"updateTimer": 1,
"dataPoints": 20
},
{
"uuid": "63f7f37a-099c-48db-92aa-01e7ba2dc65d",
"path": "vessels.self.environment.wind.speedTrue",
"signalKSource": "default",
"updateTimer": 5,
"dataPoints": 30
},
{
"uuid": "dd9f511f-d94f-4f40-a20e-c08d2cd4690e",
"path": "vessels.self.environment.wind.speedApparent",
"signalKSource": "default",
"updateTimer": 10,
"dataPoints": 60
"dataPoints": 15
}

@@ -256,15 +207,15 @@ ],

{
"uuid": "a68fff4a-15f2-4d62-8083-835e7ac74cf0",
"uuid": "d107e54d-2db5-4abf-aba7-b96ce19f5abd",
"type": "splitSet",
"size": 24.166
"size": 27.406021225904265
},
{
"uuid": "a3d595eb-f1a0-45a2-9faa-7af31757f129",
"type": "widget",
"size": 48.177
"uuid": "9249373f-7aa4-4673-8004-3e4e900e0b3d",
"type": "splitSet",
"size": 45.296959997548555
},
{
"uuid": "be832489-4796-4f69-9025-11937c1e2bc4",
"uuid": "d5be7f74-28c0-484c-a0cd-e623eb5db837",
"type": "splitSet",
"size": 27.656
"size": 27.297018776547173
}

@@ -274,3 +225,3 @@ ]

{
"uuid": "a68fff4a-15f2-4d62-8083-835e7ac74cf0",
"uuid": "9249373f-7aa4-4673-8004-3e4e900e0b3d",
"parentUUID": "isplitsx-xxxx-4xxx-yxxx-xxxxxxxxxxxx",

@@ -280,20 +231,10 @@ "direction": "vertical",

{
"uuid": "widgetno-1xxx-4xxx-yxxx-xxxxxxxxxxxx",
"uuid": "7298b3be-232f-48bf-9b3d-3b433131a908",
"type": "widget",
"size": 25
"size": 77.30434782608697
},
{
"uuid": "4d953a0b-a7be-4bdf-84fd-d6d244056378",
"uuid": "85525ebc-c40c-41e6-8379-05d573a331e1",
"type": "widget",
"size": 25
},
{
"uuid": "e595897d-a78a-46c1-ba93-d4c6741983a4",
"type": "widget",
"size": 25
},
{
"uuid": "7955f86d-1ba6-4a2f-8c3e-a1f5da895768",
"type": "widget",
"size": 25
"size": 22.69565217391304
}

@@ -303,3 +244,3 @@ ]

{
"uuid": "be832489-4796-4f69-9025-11937c1e2bc4",
"uuid": "d107e54d-2db5-4abf-aba7-b96ce19f5abd",
"parentUUID": "isplitsx-xxxx-4xxx-yxxx-xxxxxxxxxxxx",

@@ -309,60 +250,15 @@ "direction": "vertical",

{
"uuid": "d1068028-ea14-4db3-8a8d-8bbfea0fbfd3",
"uuid": "widgetno-1xxx-4xxx-yxxx-xxxxxxxxxxxx",
"type": "widget",
"size": 50
"size": 33.13526570048309
},
{
"uuid": "1bdb821f-4b0d-4232-bccb-6ea8eebf04cf",
"uuid": "a49a59c6-b83d-40e0-b759-9d153da69105",
"type": "widget",
"size": 50
}
]
},
{
"uuid": "ebe306cc-9da9-4531-89ed-2252a0251dbe",
"direction": "horizontal",
"splitAreas": [
{
"uuid": "bb0da568-cb87-49f6-8359-48ff7b7cfa18",
"type": "widget",
"size": 50
"size": 33.432367149758456
},
{
"uuid": "0663e4c3-51b3-4a92-beb2-009d9e5ee047",
"type": "splitSet",
"size": 50
}
]
},
{
"uuid": "0663e4c3-51b3-4a92-beb2-009d9e5ee047",
"parentUUID": "ebe306cc-9da9-4531-89ed-2252a0251dbe",
"direction": "vertical",
"splitAreas": [
{
"uuid": "57311752-f510-497f-af83-476020d9116f",
"type": "splitSet",
"size": 50
},
{
"uuid": "a12ab428-9d8f-4550-8b33-33b921570dcf",
"type": "splitSet",
"size": 50
}
]
},
{
"uuid": "57311752-f510-497f-af83-476020d9116f",
"parentUUID": "0663e4c3-51b3-4a92-beb2-009d9e5ee047",
"direction": "horizontal",
"splitAreas": [
{
"uuid": "a626a0ac-dcef-4e80-a3db-b121fe144854",
"uuid": "62fa8155-10fd-49cb-a495-cee6e9491b8a",
"type": "widget",
"size": 21.055
},
{
"uuid": "a11f9ba9-9cda-40d4-a85e-949f3cb0794f",
"type": "splitSet",
"size": 78.945
"size": 33.432367149758456
}

@@ -372,90 +268,22 @@ ]

{
"uuid": "a11f9ba9-9cda-40d4-a85e-949f3cb0794f",
"parentUUID": "57311752-f510-497f-af83-476020d9116f",
"uuid": "d5be7f74-28c0-484c-a0cd-e623eb5db837",
"parentUUID": "isplitsx-xxxx-4xxx-yxxx-xxxxxxxxxxxx",
"direction": "vertical",
"splitAreas": [
{
"uuid": "03e53522-77e0-4b09-8e1d-19959053a8fb",
"uuid": "912b86e4-e068-49e9-9f75-a2292d772578",
"type": "widget",
"size": 50.441
"size": 25
},
{
"uuid": "3c84892b-60bb-47c1-8b42-8beef6260972",
"uuid": "42de0119-481c-4466-8b50-1407533ac2aa",
"type": "widget",
"size": 49.559
"size": 25
}
]
},
{
"uuid": "a12ab428-9d8f-4550-8b33-33b921570dcf",
"parentUUID": "0663e4c3-51b3-4a92-beb2-009d9e5ee047",
"direction": "horizontal",
"splitAreas": [
{
"uuid": "9b4420a5-5527-4be3-85fe-b8a6bff563f6",
"type": "widget",
"size": 50
},
{
"uuid": "0e6067e7-e819-4a75-853c-d16dbfec5e05",
"type": "widget",
"size": 50
}
]
},
{
"uuid": "d538dd07-679b-413e-9910-8b6508942059",
"direction": "horizontal",
"splitAreas": [
{
"uuid": "072502e7-5512-4c69-8b5e-118a1290bc27",
"type": "widget",
"size": 100
}
]
}
],
"rootSplits": [
"isplitsx-xxxx-4xxx-yxxx-xxxxxxxxxxxx",
"ebe306cc-9da9-4531-89ed-2252a0251dbe",
"d538dd07-679b-413e-9910-8b6508942059"
],
"derivations": [
{
"name": "True Wind",
"updateAny": false,
"paths": [
{
"path": "vessels.self.navigation.headingTrue",
"source": "default"
},
{
"path": "vessels.self.navigation.speedThroughWater",
"source": "default"
},
{
"path": "vessels.self.environment.wind.speedApparent",
"source": "default"
},
{
"path": "vessels.self.environment.wind.angleApparent",
"source": "default"
}
]
},
{
"name": "Dew Point",
"updateAny": false,
"paths": [
{
"path": "vessels.self.environment.outside.temperature",
"source": "default"
},
{
"path": "vessels.self.environment.outside.humidity",
"source": "default"
}
]
}
"isplitsx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
]
}

@@ -0,0 +0,0 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';

@@ -285,5 +285,18 @@ import { Component, Input, AfterViewInit, OnInit, OnChanges, AfterViewChecked, SimpleChanges, ViewChild, ElementRef } from '@angular/core';

if (changes.backgroundColor) {
if ( !changes.backgroundColor.firstChange) {
this.buildOptions();
this.startGauge();//reset
}
}
if (changes.frameColor) {
if ( !changes.frameColor.firstChange) {
this.buildOptions();
this.startGauge();//reset
}
}
}

@@ -290,0 +303,0 @@

@@ -45,4 +45,4 @@ import { Component, OnInit, OnDestroy, OnChanges, SimpleChanges, Input } from '@angular/core';

onDragEnd(sizesArray: Array<number>) {
this.LayoutSplitsService.updateSplitSizes(this.splitSet.uuid, sizesArray);
onDragEnd(sizesArray: {gutterNum: number, sizes: Array<number>}) {
this.LayoutSplitsService.updateSplitSizes(this.splitSet.uuid, sizesArray.sizes);
}

@@ -49,0 +49,0 @@

@@ -181,3 +181,3 @@ import { Injectable } from '@angular/core';

updateSplitSizes(splitSetUUID: string, sizesArray) {
updateSplitSizes(splitSetUUID: string, sizesArray: Array<number>) {
let splitIndex = this.splitSets.findIndex(sSet => sSet.uuid == splitSetUUID);

@@ -184,0 +184,0 @@ if (splitIndex < 0) { return null; }

@@ -0,0 +0,0 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';

@@ -0,0 +0,0 @@ import { Component, OnInit } from '@angular/core';

@@ -0,0 +0,0 @@ import { Component, OnInit, OnDestroy } from '@angular/core';

@@ -0,0 +0,0 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';

@@ -0,0 +0,0 @@ import { Component, OnInit } from '@angular/core';

@@ -0,0 +0,0 @@ import { Component, OnInit, Inject } from '@angular/core';

@@ -0,0 +0,0 @@ import { Component, OnInit } from '@angular/core';

@@ -0,0 +0,0 @@ import { Injectable } from '@angular/core';

@@ -0,0 +0,0 @@ import { Injectable } from '@angular/core';

@@ -0,0 +0,0 @@ import { Injectable } from '@angular/core';

@@ -42,3 +42,4 @@ import { Injectable } from '@angular/core';

path: string;
observable: BehaviorSubject<pathObject>;
source?: string; // if this is set, updates to observable are the direct value of this source...
observable: BehaviorSubject<any>;
}

@@ -73,3 +74,3 @@

subscribePath(uuid, path) {
subscribePath(uuid: string, path: string, source: string = null) {
//see if already subscribed, if yes return that...

@@ -82,16 +83,25 @@ let registerIndex = this.pathRegister.findIndex(registration => (registration.path == path) && (registration.uuid == uuid));

//find if we already have a value for this path to return.
let currentValue: pathObject;
let currentValue = null;
let pathIndex = this.paths.findIndex(pathObject => pathObject.path == path);
if (pathIndex >= 0) { // exists
currentValue = this.paths[pathIndex];
} else {
currentValue = null;
}
if (source === null) {
currentValue = this.paths[pathIndex]; // return the entire pathObject
} else if (source == 'default') {
currentValue = this.paths[pathIndex].sources[this.paths[pathIndex].defaultSource].value;
} else if (source in this.paths[pathIndex].sources) {
currentValue = this.paths[pathIndex].sources[source].value;
}
}
//register
this.pathRegister.push({
let newRegister = {
uuid: uuid,
path: path,
observable: new BehaviorSubject<pathObject>(currentValue)
});
observable: new BehaviorSubject<any>(currentValue)
};
if (source !== null) {
newRegister['source'] = source;
}
//register
this.pathRegister.push(newRegister);
// should be subscribed now, use search now as maybe someone else adds something and it's no longer last in array :P

@@ -140,7 +150,25 @@ pathIndex = this.pathRegister.findIndex(registration => (registration.path == path) && (registration.uuid == uuid));

// push it to any subscriptions of that data
for (let i = 0; i < this.pathRegister.length; i++) {
if (this.pathRegister[i].path == pathSelf) {
this.pathRegister[i].observable.next(this.paths[pathIndex]);
this.pathRegister.filter(pathRegister => pathRegister.path == pathSelf).forEach(
pathRegister => {
// new type sub that just wants the value
if ("source" in pathRegister) {
let source: string = null;
if (pathRegister.source == 'default') {
source = this.paths[pathIndex].defaultSource;
} else if (pathRegister.source in this.paths[pathIndex].sources) {
source = pathRegister.source;
} else {
//we're looking for a source we don't know of... do nothing I guess?
}
if (source !== null) {
pathRegister.observable.next(this.paths[pathIndex].sources[source].value);
}
} else {
//old type sub that wants whole pathObject...
pathRegister.observable.next(this.paths[pathIndex]);
}
}
}
);
}

@@ -182,2 +210,8 @@

getPathUnitType(path: string): string {
let pathIndex = this.paths.findIndex(pathObject => pathObject.path == path);
if (pathIndex < 0) { return null; }
if (('meta' in this.paths[pathIndex]) && ('units' in this.paths[pathIndex].meta)) { return this.paths[pathIndex].meta.units; } else { return null; }
}
}

@@ -0,0 +0,0 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { Component, OnInit, Input, ViewChild, ElementRef, OnChanges, SimpleChanges } from '@angular/core';
import { isNumeric } from 'rxjs/util/isNumeric';
import { isNumber } from 'util';
const angle = ([a,b],[c,d],[e,f]) => (Math.atan2(f-d,e-c)-Math.atan2(b-d,a-c)+3*Math.PI)%(2*Math.PI)-Math.PI;
@Component({

@@ -13,2 +17,4 @@ selector: 'app-svg-wind',

@ViewChild('trueWindAnimate') trueWindAnimate: ElementRef;
//@ViewChild('laylinePortAnimate') laylinePortAnimate: ElementRef;
//@ViewChild('laylineStbAnimate') laylineStbAnimate: ElementRef;

@@ -21,9 +27,10 @@

@Input('appWindSpeed') appWindSpeed: number;
@Input('laylinePortAngle') laylinePortAngle : number;
@Input('laylineStbAngle') laylineStbAngle: number;
@Input('windSectorPortStart') windSectorPortStart: number;
@Input('windSectorPortEnd') windSectorPortEnd: number;
@Input('windSectorStbStart') windSectorStbStart: number;
@Input('windSectorStbEnd') windSectorStbEnd: number;
@Input('laylineAngle') laylineAngle : number;
@Input('laylineEnable') laylineEnable: boolean;
@Input('windSectorEnable') windSectorEnable: boolean;
@Input('trueWindMinHistoric') trueWindMinHistoric: number;
@Input('trueWindMidHistoric') trueWindMidHistoric: number;
@Input('trueWindMaxHistoric') trueWindMaxHistoric: number;

@@ -42,6 +49,14 @@ constructor() { }

//truewind
oldTrueWindAngle: string = "0";
newTrueWindAngle: string = "0";
oldTrueWindRotateAngle: string = "0";
newTrueWindRotateAngle: string = "0";
trueWindHeading: number = 0;
trueWindSpeedDisplay: string = "";
//laylines
laylinePortPath: string = "M 250,250 250,90";
laylineStbdPath: string = "M 250,250 250,90";
//WindSectors
portWindSectorPath: string = "none";
stbdWindSectorPath: string = "none";
ngOnInit() {

@@ -62,2 +77,4 @@

this.compassAnimate.nativeElement.beginElement();
this.updateTrueWind();// rotates with heading change
this.updateWindSectors();// they need to update to new heading too
}

@@ -87,8 +104,4 @@ }

if (! changes.trueWindAngle.firstChange) {
this.oldTrueWindAngle = this.newTrueWindAngle;
this.newTrueWindAngle = changes.trueWindAngle.currentValue; //.toString();
if (this.trueWindAnimate) { // only update if on dom...
this.trueWindAnimate.nativeElement.beginElement();
}
this.trueWindHeading = changes.trueWindAngle.currentValue;
this.updateTrueWind();
}

@@ -103,6 +116,85 @@ }

//Min/Max
if (changes.trueWindMinHistoric || changes.trueWindMaxHistoric) {
if (isNumber(this.trueWindMinHistoric) && isNumber(this.trueWindMaxHistoric)) {
this.updateWindSectors();
}
}
}
updateTrueWind(){
this.oldTrueWindRotateAngle = this.newTrueWindRotateAngle;
this.newTrueWindRotateAngle = this.addHeading(this.trueWindHeading, (this.newCompassRotate*-1)).toFixed(0); //compass rotate is negative as we actually have to rotate counter clockwise
if (this.trueWindAnimate) { // only update if on dom...
this.trueWindAnimate.nativeElement.beginElement();
}
//calculate laylines
let portLaylineRotate = this.addHeading(Number(this.newTrueWindRotateAngle), (this.laylineAngle*-1));
//find xy of that roation (160 = radius of inner circle)
let portX = 160 * Math.sin((portLaylineRotate*Math.PI)/180) + 250; //250 is middle
let portY = (160 * Math.cos((portLaylineRotate*Math.PI)/180)*-1) + 250; //-1 since SVG 0 is at top
this.laylinePortPath = 'M 250,250 ' + portX +',' + portY;
let stbdLaylineRotate = this.addHeading(Number(this.newTrueWindRotateAngle), (this.laylineAngle));
//find xy of that roation (160 = radius of inner circle)
let stbdX = 160 * Math.sin((stbdLaylineRotate*Math.PI)/180) + 250; //250 is middle
let stbdY = (160 * Math.cos((stbdLaylineRotate*Math.PI)/180)*-1) + 250; //-1 since SVG 0 is at top
this.laylineStbdPath = 'M 250,250 ' + stbdX +',' + stbdY;
}
updateWindSectors() {
let portMin = this.addHeading(this.addHeading(this.trueWindMinHistoric, (this.newCompassRotate*-1)), (this.laylineAngle*-1));
let portMid = this.addHeading(this.addHeading(this.trueWindMidHistoric, (this.newCompassRotate*-1)), (this.laylineAngle*-1));
let portMax = this.addHeading(this.addHeading(this.trueWindMaxHistoric, (this.newCompassRotate*-1)), (this.laylineAngle*-1));
//console.log(this.trueWindMinHistoric.toFixed(0) + ' ' + this.trueWindMaxHistoric.toFixed(0) + ' ' + portMin.toFixed(0) + ' ' + portMax.toFixed(0));
let portMinX = 160 * Math.sin((portMin*Math.PI)/180) + 250; //250 is middle
let portMinY = (160 * Math.cos((portMin*Math.PI)/180)*-1) + 250; //-1 since SVG 0 is at top
let portMidX = 160 * Math.sin((portMid*Math.PI)/180) + 250; //250 is middle
let portMidY = (160 * Math.cos((portMid*Math.PI)/180)*-1) + 250; //-1 since SVG 0 is at top
let portMaxX = 160 * Math.sin((portMax*Math.PI)/180) + 250; //250 is middle
let portMaxY = (160 * Math.cos((portMax*Math.PI)/180)*-1) + 250; //-1 since SVG 0 is at top
//calculate angles for arc options https://stackoverflow.com/questions/21816286/svg-arc-how-to-determine-sweep-and-larg-arc-flags-given-start-end-via-point
let portLgArcFl = Math.abs(angle([portMinX,portMinY],[portMidX,portMidY],[portMaxX,portMaxY])) > Math.PI/2 ? 0 : 1;
let portSweepFl = angle([portMaxX,portMaxY],[portMinX,portMinY],[portMidX,portMidY]) > 0 ? 0 : 1;
this.portWindSectorPath = 'M 250,250 L ' + portMinX + ',' + portMinY + ' A 160,160 0 ' + portLgArcFl + ' ' + portSweepFl + ' ' + portMaxX + ',' + portMaxY +' z';
//////////
let stbdMin = this.addHeading(this.addHeading(this.trueWindMinHistoric, (this.newCompassRotate*-1)), (this.laylineAngle));
let stbdMid = this.addHeading(this.addHeading(this.trueWindMidHistoric, (this.newCompassRotate*-1)), (this.laylineAngle));
let stbdMax = this.addHeading(this.addHeading(this.trueWindMaxHistoric, (this.newCompassRotate*-1)), (this.laylineAngle));
let stbdMinX = 160 * Math.sin((stbdMin*Math.PI)/180) + 250; //250 is middle
let stbdMinY = (160 * Math.cos((stbdMin*Math.PI)/180)*-1) + 250; //-1 since SVG 0 is at top
let stbdMidX = 160 * Math.sin((stbdMid*Math.PI)/180) + 250; //250 is middle
let stbdMidY = (160 * Math.cos((stbdMid*Math.PI)/180)*-1) + 250; //-1 since SVG 0 is at top
let stbdMaxX = 160 * Math.sin((stbdMax*Math.PI)/180) + 250; //250 is middle
let stbdMaxY = (160 * Math.cos((stbdMax*Math.PI)/180)*-1) + 250; //-1 since SVG 0 is at top
let stbdLgArcFl = Math.abs(angle([stbdMinX,stbdMinY],[stbdMidX,stbdMidY],[stbdMaxX,stbdMaxY])) > Math.PI/2 ? 0 : 1;
let stbdSweepFl = angle([stbdMaxX,stbdMaxY],[stbdMinX,stbdMinY],[stbdMidX,stbdMidY]) > 0 ? 0 : 1;
this.stbdWindSectorPath = 'M 250,250 L ' + stbdMinX + ',' + stbdMinY + ' A 160,160 0 ' + stbdLgArcFl + ' ' + stbdSweepFl + ' ' + stbdMaxX + ',' + stbdMaxY +' z';
}
addHeading(h1: number, h2: number) {
let h3 = h1 + h2;
while (h3 > 359) { h3 = h3 - 359; }
while (h3 < 0) { h3 = h3 + 359; }
return h3;
}
}

@@ -109,0 +201,0 @@

@@ -68,2 +68,5 @@ import { Injectable } from '@angular/core';

"deg": Qty.swiftConverter('rad', 'deg')
},
'ratio': {
'%': function(v) { return v * 100 },
}

@@ -70,0 +73,0 @@

@@ -0,0 +0,0 @@ import { Component, OnInit, Input, Inject, ComponentFactoryResolver, ComponentRef, ViewChild } from '@angular/core';

@@ -0,0 +0,0 @@ import { Component, OnInit, Input } from '@angular/core';

@@ -0,0 +0,0 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { Component, Input, OnInit, OnDestroy, Inject } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import {MatDialog,MatDialogRef,MAT_DIALOG_DATA } from '@angular/material';
import { MatDialog } from '@angular/material';
import { SignalKService, pathObject } from '../signalk.service';
import { WidgetManagerService, IWidget } from '../widget-manager.service';
import { UnitConvertService } from '../unit-convert.service';
import { ModalWidgetComponent } from '../modal-widget/modal-widget.component';
import { WidgetManagerService, IWidget, IWidgetConfig } from '../widget-manager.service';
import { UnitsService } from '../units.service';
interface IWidgetConfig {
gaugeType: string;
signalKPath: string;
signalKSource: string;
label: string;
unitGroup: string;
unitName: string;
barGraph: boolean;
radialSize: string;
minValue: number;
maxValue: number;
}
const defaultConfig: IWidgetConfig = {
widgetLabel: null,
paths: {
"gaugePath": {
description: "Numeric Data",
path: null,
source: null,
pathType: "number",
}
},
units: {
"gaugePath": "unitless"
},
selfPaths: true,
gaugeType: 'linear',
barGraph: false, // if linear/radial, is it digital?
radialSize: 'full',
minValue: 0,
maxValue: 100,
rotateFace: false,
backgroundColor: 'carbon',
frameColor: 'anthracite'
};

@@ -36,6 +49,5 @@

converter = this.UnitConvertService.getConverter();
activeWidget: IWidget;
config: IWidgetConfig;
activeWidget: IWidget;
dataValue: any = null;

@@ -45,15 +57,2 @@

widgetConfig: IWidgetConfig = {
gaugeType: 'linear',
signalKPath: null,
signalKSource: 'default',
label: null,
unitGroup: 'discreet',
unitName: 'no unit',
barGraph: false,
radialSize: 'full',
minValue: 0,
maxValue: 100
}
constructor(

@@ -63,3 +62,3 @@ public dialog:MatDialog,

private WidgetManagerService: WidgetManagerService,
private UnitConvertService: UnitConvertService) {
private UnitsService: UnitsService) {
}

@@ -71,7 +70,7 @@

// no data, let's set some!
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, this.widgetConfig);
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, defaultConfig);
this.config = defaultConfig; // load default config.
} else {
this.widgetConfig = this.activeWidget.config; // load existing config.
this.config = this.activeWidget.config;
}
this.subscribePath();

@@ -88,24 +87,7 @@

this.unsubscribePath();
if (this.widgetConfig.signalKPath === null) { return } // nothing to sub to...
if (typeof(this.config.paths['gaugePath'].path) != 'string') { return } // nothing to sub to...
this.valueSub = this.SignalKService.subscribePath(this.widgetUUID, this.widgetConfig.signalKPath).subscribe(
pathObject => {
if (pathObject === null) {
return; // we will get null back if we subscribe to a path before the app knows about it. when it learns about it we will get first value
}
let source: string;
if (this.widgetConfig.signalKSource == 'default') {
source = pathObject.defaultSource;
} else {
source = this.widgetConfig.signalKSource;
}
if (pathObject.sources[source].value === null) {
this.dataValue = null;
}
let value:number = pathObject.sources[source].value;
let converted = this.converter[this.widgetConfig.unitGroup][this.widgetConfig.unitName](value);
this.dataValue = converted;
this.valueSub = this.SignalKService.subscribePath(this.widgetUUID, this.config.paths['gaugePath'].path, this.config.paths['gaugePath'].source).subscribe(
newValue => {
this.dataValue = this.UnitsService.convertUnit(this.config.units['gaugePath'], newValue);
}

@@ -119,3 +101,3 @@ );

this.valueSub = null;
this.SignalKService.unsubscribePath(this.widgetUUID, this.widgetConfig.signalKPath)
this.SignalKService.unsubscribePath(this.widgetUUID, this.config.paths['gaugePath'].path)
}

@@ -125,117 +107,23 @@ }

openWidgetSettings() {
//prepare current data
let settingsData: IWidgetConfig = {
gaugeType: this.widgetConfig.gaugeType,
signalKPath: this.widgetConfig.signalKPath,
signalKSource: this.widgetConfig.signalKSource,
label: this.widgetConfig.label,
unitGroup: this.widgetConfig.unitGroup,
unitName: this.widgetConfig.unitName,
barGraph: this.widgetConfig.barGraph,
radialSize: this.widgetConfig.radialSize,
minValue: this.widgetConfig.minValue,
maxValue: this.widgetConfig.maxValue
}
let dialogRef = this.dialog.open(WidgetGaugeModalComponent, {
width: '650px',
data: settingsData
});
dialogRef.afterClosed().subscribe(result => {
// save new settings
if (result) {
console.debug("Updating widget config");
this.unsubscribePath();//unsub now as we will change variables so wont know what was subbed before...
this.widgetConfig.gaugeType = result.gaugeType;
this.widgetConfig.signalKPath = result.signalKPath;
this.widgetConfig.signalKSource = result.signalKSource;
this.widgetConfig.label = result.label;
this.widgetConfig.unitGroup = result.unitGroup;
this.widgetConfig.unitName = result.unitName;
this.widgetConfig.barGraph = result.barGraph;
this.widgetConfig.radialSize = result.radialSize;
this.widgetConfig.minValue = result.minValue;
this.widgetConfig.maxValue = result.maxValue;
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, this.widgetConfig);
this.subscribePath();
}
});
}
}
let dialogRef = this.dialog.open(ModalWidgetComponent, {
width: '80%',
data: this.config
});
dialogRef.afterClosed().subscribe(result => {
// save new settings
if (result) {
console.log(result);
this.unsubscribePath();//unsub now as we will change variables so wont know what was subbed before...
this.config = result;
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, this.config);
this.subscribePath();
}
@Component({
selector: 'gauge-widget-modal',
templateUrl: './widget-gauge.modal.html',
styleUrls: ['./widget-gauge.component.css']
})
export class WidgetGaugeModalComponent implements OnInit {
});
settingsData: IWidgetConfig;
selfPaths: boolean = true;
availablePaths: Array<string> = [];
availableSources: Array<string>;
availableUnitGroups: string[];
availableUnitNames: string[];
}
converter: Object = this.UnitConvertService.getConverter();
constructor(
private SignalKService: SignalKService,
private UnitConvertService: UnitConvertService,
public dialogRef:MatDialogRef<WidgetGaugeModalComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) { }
ngOnInit() {
this.settingsData = this.data;
//populate available choices
this.availablePaths = this.SignalKService.getPathsByType('number').sort();
if (this.availablePaths.includes(this.settingsData.signalKPath)) {
this.settingsDataUpdatePath(); //TODO: this wipes out existing config, not good when editing existing config...
}
this.availableUnitGroups = Object.keys(this.converter);
if (this.converter.hasOwnProperty(this.settingsData.unitGroup)) {
this.availableUnitNames = Object.keys(this.converter[this.settingsData.unitGroup]);
}
}
settingsDataUpdatePath() { // called when we choose a new path. resets the rest with default info of this path
let pathObject = this.SignalKService.getPathObject(this.settingsData.signalKPath);
if (pathObject === null) { return; }
this.availableSources = ['default'].concat(Object.keys(pathObject.sources));
this.settingsData.signalKSource = 'default';
if (pathObject.meta) {
if (typeof(pathObject.meta.abbreviation) == 'string') {
this.settingsData.label = pathObject.meta.abbreviation;
} else if (typeof(pathObject.meta.label) == 'string') {
this.settingsData.label = pathObject.meta.label;
} else {
this.settingsData.label = this.settingsData.signalKPath; // who knows?
}
} else {
this.settingsData.label = this.settingsData.signalKPath;// who knows?
}
}
updateUnitType() {
if (this.converter.hasOwnProperty(this.settingsData.unitGroup)) {
this.availableUnitNames = Object.keys(this.converter[this.settingsData.unitGroup]);
// select first name
this.settingsData.unitName = this.availableUnitNames[0];
}
}
submitConfig() {
this.dialogRef.close(this.settingsData);
}
}
import { Component, OnInit, ViewChild, ElementRef, OnDestroy, Input, Inject } from '@angular/core';
import Chart from 'chart.js';
import {MatDialog,MatDialogRef,MAT_DIALOG_DATA } from '@angular/material';
import * as Chart from 'chart.js';
import { MatDialog } from '@angular/material';
import { Subscription } from 'rxjs/Subscription';
import { ModalWidgetComponent } from '../modal-widget/modal-widget.component';
import { dataPoint, DataSetService } from '../data-set.service';
import { WidgetManagerService, IWidget } from '../widget-manager.service';
import { UnitConvertService } from '../unit-convert.service';
import { WidgetManagerService, IWidget, IWidgetConfig } from '../widget-manager.service';
import { UnitsService } from '../units.service';
const defaultConfig: IWidgetConfig = {
widgetLabel: null,
units: {
"dataset": "unitless"
},
dataSetUUID: null,
invertData: false,
displayMinMax: false,
includeZero: true,
minValue: null,
maxValue: null
};
interface IHistoricalWidgetSettings {
selectedDataSet: string;
label: string;
selectedUnitGroup: string;
selectedUnitName: string;
numDecimal: number;
invertData: boolean;
displayMinMax: boolean;
animateGraph: boolean;
suggestedMin: number;
suggestedMax: number;
includeZero: boolean;
}
interface widgetConfig {
dataSetUUID: string;
label: string;
unitGroup: string;
unitName: string;
numDecimal: number; // number of decimal places if a number
invertData: boolean;
displayMinMax: boolean;
animateGraph: boolean;
suggestedMin: number;
suggestedMax: number;
includeZero: boolean;
}
interface IDataSetOptions {

@@ -61,3 +47,4 @@ label: string;

activeWidget: IWidget;
config: IWidgetConfig;
chartCtx;

@@ -72,22 +59,5 @@ chart = null;

converter = this.UnitConvertService.getConverter();
dataSetSub: Subscription = null;
widgetConfig: widgetConfig = {
dataSetUUID: null,
label: '',
unitGroup: 'discreet',
unitName: 'no unit',
numDecimal: 2,
invertData: false,
displayMinMax: false,
animateGraph: false,
suggestedMin: null,
suggestedMax: null,
includeZero: true
}
constructor(

@@ -97,307 +67,220 @@ public dialog:MatDialog,

private WidgetManagerService: WidgetManagerService,
private UnitConvertService: UnitConvertService
private UnitsService: UnitsService
) { }
ngOnInit() {
this.activeWidget = this.WidgetManagerService.getWidget(this.widgetUUID);
if (this.activeWidget.config === null) {
// no data, let's set some!
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, this.widgetConfig);
} else {
this.widgetConfig = this.activeWidget.config; // load existing config.
}
ngOnInit() {
this.activeWidget = this.WidgetManagerService.getWidget(this.widgetUUID);
if (this.activeWidget.config === null) {
// no data, let's set some!
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, defaultConfig);
this.config = defaultConfig; // load default config.
} else {
this.config = this.activeWidget.config;
}
//TODO, this only works on chart init... need to find when theme changes...
this.textColor = window.getComputedStyle(this.lineGraph.nativeElement).color;
this.chartCtx = this.lineGraph.nativeElement.getContext('2d');
this.startChart();
setTimeout(this.subscribeDataSet(),1000);//TODO, see why destroy called before we even get subbed (or just after...)
//TODO, this only works on chart init... need to find when theme changes...
this.textColor = window.getComputedStyle(this.lineGraph.nativeElement).color;
this.chartCtx = this.lineGraph.nativeElement.getContext('2d');
this.startChart();
this.subscribeDataSet();
//setTimeout(this.subscribeDataSet(),1000);//TODO, see why destroy called before we even get subbed (or just after...)
}
}
ngOnDestroy() {
if (this.chart !== null) {
//this.chart.destroy(); // doesn't seem to be needed since chart is destoryed when destroying component. was giving errors. (maybe html was destroyed before this is called?)
}
this.unsubscribeDataSet();
console.log("stopped Sub");
}
ngOnDestroy() {
if (this.chart !== null) {
//this.chart.destroy(); // doesn't seem to be needed since chart is destoryed when destroying component. was giving errors. (maybe html was destroyed before this is called?)
}
this.unsubscribeDataSet();
console.log("stopped Sub");
}
startChart() {
if (this.chart !== null) {
this.chart.destroy();
startChart() {
if (this.chart !== null) {
this.chart.destroy();
}
// Setup DataSets
let ds: IDataSetOptions[] = [
{
label: this.config.widgetLabel + '-Avg.',
data: this.chartDataAvg,
fill: 'false',
//borderWidth: 1
borderColor: this.textColor
}
// Setup DataSets
let ds: IDataSetOptions[] = [
];
if (this.config.displayMinMax) {
ds.push(
{
label: this.widgetConfig.label + '-Avg.',
data: this.chartDataAvg,
fill: 'false',
label: this.config.widgetLabel + '-Min',
data: this.chartDataMin,
fill: '+1',
//borderWidth: 1
borderColor: this.textColor
borderColor: this.textColor,
borderDash: [ 5, 5 ]
},
{
label: this.config.widgetLabel + '-Max',
data: this.chartDataMax,
fill: '-1',
//borderWidth: 1
borderColor: this.textColor,
borderDash: [ 5, 5 ]
}
);
}
//setup Options
let yAxisTickOptions = {};
if (this.config.includeZero) {
yAxisTickOptions['beginAtZero'] = true;
}
if (this.config.minValue !== null) {
yAxisTickOptions['suggestedMin'] = this.config.minValue;
}
if (this.config.maxValue !== null) {
yAxisTickOptions['suggestedMax'] = this.config.maxValue;
}
this.chart = new Chart(this.chartCtx,{
type: 'line',
data: {
datasets: ds
},
options: {
maintainAspectRatio: false,
scales: {
yAxes: [{
scaleLabel: {
labelString: 'feet',
},
position: 'right',
ticks: yAxisTickOptions
}],
xAxes: [{
type: 'time',
time: {
minUnit: 'second',
round: 'second',
displayFormats: 'YY', //no mater what it seems to default to full time...
},
ticks: {
// minRotation: 15,
callback: function(value) { //TODO, left pad 0 for min/sec
let tickTime = Date.parse(value);
let nowTime = Date.now();
let timeDiff = Math.floor((nowTime - tickTime)/1000);
if (timeDiff < 60) {
return timeDiff.toString() + " sec ago";
} else if (timeDiff < 3600) {
let minDiff = Math.floor(timeDiff / 60);
let secDiff = timeDiff % 60;
return (minDiff.toString() + ":" +secDiff.toString() + " mins ago");
} else if (timeDiff < 86400) {
let hourDiff = Math.floor(timeDiff / 3600);
return (hourDiff.toString() + " hours ago");
} else {
let dayDiff = Math.floor(timeDiff / 86400);
return (dayDiff.toString() + " days ago");
}
}
}
}]
}
];
if (this.widgetConfig.displayMinMax) {
ds.push(
{
label: this.widgetConfig.label + '-Min',
data: this.chartDataMin,
fill: '+1',
//borderWidth: 1
borderColor: this.textColor,
borderDash: [ 5, 5 ]
},
{
label: this.widgetConfig.label + '-Max',
data: this.chartDataMax,
fill: '-1',
//borderWidth: 1
borderColor: this.textColor,
borderDash: [ 5, 5 ]
}
);
}
//setup Options
let yAxisTickOptions = {};
if (this.widgetConfig.includeZero) {
yAxisTickOptions['beginAtZero'] = true;
}
if (this.widgetConfig.suggestedMin !== null) {
yAxisTickOptions['suggestedMin'] = this.widgetConfig.suggestedMin;
}
if (this.widgetConfig.suggestedMax !== null) {
yAxisTickOptions['suggestedMax'] = this.widgetConfig.suggestedMax;
}
this.chart = new Chart(this.chartCtx,{
type: 'line',
data: {
datasets: ds
},
options: {
maintainAspectRatio: false,
scales: {
yAxes: [{
scaleLabel: {
labelString: 'feet',
},
position: 'right',
ticks: yAxisTickOptions
}],
xAxes: [{
type: 'time',
time: {
minUnit: 'second',
round: 'second',
displayFormats: 'YY', //no mater what it seems to default to full time...
},
ticks: {
// minRotation: 15,
callback: function(value) { //TODO, left pad 0 for min/sec
let tickTime = Date.parse(value);
let nowTime = Date.now();
let timeDiff = Math.floor((nowTime - tickTime)/1000);
if (timeDiff < 60) {
return timeDiff.toString() + " sec ago";
} else if (timeDiff < 3600) {
let minDiff = Math.floor(timeDiff / 60);
let secDiff = timeDiff % 60;
return (minDiff.toString() + ":" +secDiff.toString() + " mins ago");
} else if (timeDiff < 86400) {
let hourDiff = Math.floor(timeDiff / 3600);
return (hourDiff.toString() + " hours ago");
} else {
let dayDiff = Math.floor(timeDiff / 86400);
return (dayDiff.toString() + " days ago");
}
}
}
}]
}
}
}
});
}
}
subscribeDataSet() {
//this.unsubscribeDataSet();
if (this.widgetConfig.dataSetUUID === null) { return } // nothing to sub to...
this.dataSetSub = this.DataSetService.subscribeDataSet(this.widgetUUID, this.widgetConfig.dataSetUUID).subscribe(
dataSet => {
if (dataSet === null) {
subscribeDataSet() {
//this.unsubscribeDataSet();
if (this.config.dataSetUUID === null) { return } // nothing to sub to...
this.dataSetSub = this.DataSetService.subscribeDataSet(this.widgetUUID, this.config.dataSetUUID).subscribe(
dataSet => {
if (dataSet === null) {
return; // we will get null back if we subscribe to a dataSet before the app has started it.when it learns about it we will get first value
}
let invert = 1;
if (this.config.invertData) { invert = -1; }
//Avg
this.chartDataAvg = [];
for (let i=0;i<dataSet.length;i++){
if (dataSet[i].average === null) {
this.chartDataAvg.push({t: dataSet[i].timestamp, y: null });
continue;
}
let invert = 1;
if (this.widgetConfig.invertData) { invert = -1; }
//Avg
this.chartDataAvg = [];
this.chartDataAvg.push({
t: dataSet[i].timestamp,
y: (this.UnitsService.convertUnit(this.config.units['dataset'], dataSet[i].average) * invert).toFixed(2)
});
}
this.chart.config.data.datasets[0].data = this.chartDataAvg;
//min/max
if (this.config.displayMinMax) {
this.chartDataMin = [];
this.chartDataMax = [];
for (let i=0;i<dataSet.length;i++){
//process datapoint and add it to our chart.
if (dataSet[i].average === null) {
this.chartDataAvg.push({t: dataSet[i].timestamp, y: null });
continue;
this.chartDataMin.push({t: dataSet[i].timestamp, y: null });
this.chartDataMax.push({t: dataSet[i].timestamp, y: null });
} else {
this.chartDataMin.push({
t: dataSet[i].timestamp,
y: (this.UnitsService.convertUnit(this.config.units['dataset'], dataSet[i].minValue) * invert).toFixed(2)
});
this.chartDataMax.push({
t: dataSet[i].timestamp,
y: (this.UnitsService.convertUnit(this.config.units['dataset'], dataSet[i].maxValue) * invert).toFixed(2)
});
}
this.chartDataAvg.push({
t: dataSet[i].timestamp,
y: this.converter[this.widgetConfig.unitGroup][this.widgetConfig.unitName](dataSet[i].average).toFixed(this.widgetConfig.numDecimal)*invert
});
}
this.chart.config.data.datasets[0].data = this.chartDataAvg;
//min/max
if (this.widgetConfig.displayMinMax) {
this.chartDataMin = [];
this.chartDataMax = [];
for (let i=0;i<dataSet.length;i++){
//process datapoint and add it to our chart.
if (dataSet[i].average === null) {
this.chartDataMin.push({t: dataSet[i].timestamp, y: null });
this.chartDataMax.push({t: dataSet[i].timestamp, y: null });
} else {
this.chartDataMin.push({
t: dataSet[i].timestamp,
y: this.converter[this.widgetConfig.unitGroup][this.widgetConfig.unitName](dataSet[i].minValue).toFixed(this.widgetConfig.numDecimal)*invert
});
this.chartDataMax.push({
t: dataSet[i].timestamp,
y: this.converter[this.widgetConfig.unitGroup][this.widgetConfig.unitName](dataSet[i].maxValue).toFixed(this.widgetConfig.numDecimal)*invert
});
}
}
this.chart.config.data.datasets[1].data = this.chartDataMin;
this.chart.config.data.datasets[2].data = this.chartDataMax;
}
this.chart.config.data.datasets[1].data = this.chartDataMin;
this.chart.config.data.datasets[2].data = this.chartDataMax;
}
if (this.widgetConfig.animateGraph) {
this.chart.update();
} else {
this.chart.update(0);
}
}
);
}
//if (this.widgetConfig.animateGraph) {
// this.chart.update();
//} else {
this.chart.update(0);
//}
}
);
}
unsubscribeDataSet() {
if (this.dataSetSub !== null) {
this.dataSetSub.unsubscribe();
this.dataSetSub = null;
}
}
unsubscribeDataSet() {
if (this.dataSetSub !== null) {
this.dataSetSub.unsubscribe();
this.dataSetSub = null;
}
}
openWidgetSettings() {
openWidgetSettings(content) {
let dialogRef = this.dialog.open(ModalWidgetComponent, {
width: '80%',
data: this.config
});
//prepare current data
let settingsData: IHistoricalWidgetSettings = {
selectedDataSet: this.widgetConfig.dataSetUUID,
label: this.widgetConfig.label,
numDecimal: this.widgetConfig.numDecimal,
selectedUnitGroup: this.widgetConfig.unitGroup,
selectedUnitName: this.widgetConfig.unitName,
invertData: this.widgetConfig.invertData,
displayMinMax: this.widgetConfig.displayMinMax,
animateGraph: this.widgetConfig.animateGraph,
suggestedMin: this.widgetConfig.suggestedMin,
suggestedMax: this.widgetConfig.suggestedMax,
includeZero: this.widgetConfig.includeZero
};
let dialogRef = this.dialog.open(WidgetHistoricalModalComponent, {
width: '650px',
data: settingsData
});
dialogRef.afterClosed().subscribe(result => {
// save new settings
if (result) {
console.log(result);
this.config = result;
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, this.config);
this.startChart(); //need to recreate chart to update options :P
this.subscribeDataSet();
}
});
dialogRef.afterClosed().subscribe(result => {
// save new settings
if (result) {
this.widgetConfig.dataSetUUID = result.selectedDataSet;
this.widgetConfig.label = result.label;
this.widgetConfig.unitGroup = result.selectedUnitGroup;
this.widgetConfig.unitName = result.selectedUnitName;
this.widgetConfig.numDecimal = result.numDecimal;
this.widgetConfig.invertData = result.invertData;
this.widgetConfig.displayMinMax = result.displayMinMax;
this.widgetConfig.animateGraph = result.animateGraph;
this.widgetConfig.includeZero = result.includeZero;
if (typeof(result.suggestedMin) == 'number') {
this.widgetConfig.suggestedMin = result.suggestedMin;
} else {
this.widgetConfig.suggestedMin = null;
}
if (typeof(result.suggestedMax) == 'number') {
this.widgetConfig.suggestedMax = result.suggestedMax;
} else {
this.widgetConfig.suggestedMax = null;
}
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, this.widgetConfig);
this.startChart(); //need to recreate chart to update options :P
this.subscribeDataSet();
}
});
}
}
@Component({
selector: 'historical-widget-modal',
templateUrl: './widget-historical.modal.html',
styleUrls: ['./widget-historical.component.css']
})
export class WidgetHistoricalModalComponent implements OnInit {
settingsData: IHistoricalWidgetSettings;
availableDataSets: string[];
availableUnitGroups: string[];
availableUnitNames: string[];
converter: Object = this.UnitConvertService.getConverter();
constructor(
private UnitConvertService: UnitConvertService,
private DataSetService: DataSetService,
public dialogRef:MatDialogRef<WidgetHistoricalModalComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) { }
ngOnInit() {
this.settingsData = this.data;
this.availableUnitGroups = Object.keys(this.converter);
if (this.converter.hasOwnProperty(this.settingsData.selectedUnitGroup)) {
this.availableUnitNames = Object.keys(this.converter[this.settingsData.selectedUnitGroup]);
}
this.availableDataSets = this.DataSetService.getDataSets().sort();
}
updateUnitType() {
this.availableUnitNames = Object.keys(this.converter[this.settingsData.selectedUnitGroup]);
this.settingsData.selectedUnitName = this.availableUnitNames[0];
}
submitConfig() {
this.dialogRef.close(this.settingsData);
}
}

@@ -45,8 +45,8 @@ import { Injectable } from '@angular/core';

},
// {
// name: 'WidgetStateComponent',
// componentName: WidgetStateComponent,
// description: 'State (boolean) Value',
// },
{
name: 'WidgetStateComponent',
componentName: WidgetStateComponent,
description: 'State (boolean) Value',
},
{
name: 'WidgetGaugeComponent',

@@ -53,0 +53,0 @@ componentName: WidgetGaugeComponent,

@@ -10,6 +10,53 @@ import { Injectable } from '@angular/core';

type: string;
config: any;
config: IWidgetConfig;
}
export interface IWidgetConfig {
paths?: {
[key: string]: ISignalKPathInfo;
}
units?: {
[key: string]: string; // key should match key in paths, specifies unit for that path
}
widgetLabel?: string;
selfPaths?: boolean;
//numeric data
numDecimal?: number; // number of decimal places if a number
numInt?: number;
//Wind Gague data
windSectorEnable?: boolean;
windSectorWindowSeconds?: number;
laylineEnable?: boolean;
laylineAngle?: number;
//gauge Data
gaugeType?: string;
barGraph?: boolean;
radialSize?: string;
minValue?: number;
maxValue?: number;
rotateFace?: boolean;
backgroundColor?: string;
frameColor?: string;
//Historical
dataSetUUID?: string;
invertData?: boolean;
displayMinMax?: boolean;
animateGraph?: boolean;
includeZero?: boolean;
}
interface ISignalKPathInfo {
description: string;
path: string; //can be null or set
source: string; //can be null or set
pathType: string;
}
@Injectable()

@@ -16,0 +63,0 @@ export class WidgetManagerService {

import { Component, Input, OnInit, OnDestroy, Inject, ViewChild, ElementRef, AfterViewChecked } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import {MatDialog,MatDialogRef,MAT_DIALOG_DATA } from '@angular/material';
import { MatDialog,MatDialogRef,MAT_DIALOG_DATA } from '@angular/material';
import { ModalWidgetComponent } from '../modal-widget/modal-widget.component';
import { SignalKService, pathObject } from '../signalk.service';
import { WidgetManagerService, IWidget } from '../widget-manager.service';
import { UnitConvertService } from '../unit-convert.service';
import { WidgetManagerService, IWidget, IWidgetConfig } from '../widget-manager.service';
import { UnitsService } from '../units.service';
import { isNumeric } from 'rxjs/util/isNumeric';
interface IWidgetConfig {
signalKPath: string;
signalKSource: string;
label: string;
unitGroup: string;
unitName: string;
numDecimal: number; // number of decimal places if a number
numInt: number;
}
const defaultConfig: IWidgetConfig = {
widgetLabel: null,
paths: {
"numericPath": {
description: "Numeric Data",
path: null,
source: null,
pathType: "number",
}
},
units: {
"numericPath": "unitless"
},
selfPaths: true,
numDecimal: 1,
numInt: 1
};
@Component({

@@ -31,21 +43,11 @@ selector: 'app-widget-numeric',

@ViewChild('wrapperDiv') wrapperDiv: ElementRef;
activeWidget: IWidget;
config: IWidgetConfig;
converter = this.UnitConvertService.getConverter();
activeWidget: IWidget;
dataValue: any = null;
dataTimestamp: number = Date.now();
widgetConfig: IWidgetConfig = {
signalKPath: null,
signalKSource: 'default',
label: null,
unitGroup: 'discreet',
unitName: 'no unit',
numDecimal: 2,
numInt: 2
}
//subs

@@ -61,3 +63,3 @@ valueSub: Subscription = null;

private WidgetManagerService: WidgetManagerService,
private UnitConvertService: UnitConvertService) {
private UnitsService: UnitsService) {
}

@@ -69,5 +71,6 @@

// no data, let's set some!
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, this.widgetConfig);
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, defaultConfig);
this.config = defaultConfig; // load default config.
} else {
this.widgetConfig = this.activeWidget.config; // load existing config.
this.config = this.activeWidget.config;
}

@@ -77,2 +80,3 @@ this.subscribePath();

this.canvasCtx = this.canvasEl.nativeElement.getContext('2d');
this.updateCanvas();
}

@@ -97,2 +101,3 @@

}
this.updateCanvas();
}

@@ -102,26 +107,7 @@

this.unsubscribePath();
if (this.widgetConfig.signalKPath === null) { return } // nothing to sub to...
if (typeof(this.config.paths['numericPath'].path) != 'string') { return } // nothing to sub to...
this.valueSub = this.SignalKService.subscribePath(this.widgetUUID, this.widgetConfig.signalKPath).subscribe(
pathObject => {
if (pathObject === null) {
return; // we will get null back if we subscribe to a path before the app knows about it. when it learns about it we will get first value
}
let source: string;
if (this.widgetConfig.signalKSource == 'default') {
source = pathObject.defaultSource;
} else {
source = this.widgetConfig.signalKSource;
}
this.dataTimestamp = pathObject.sources[source].timestamp;
if (pathObject.sources[source].value === null) {
this.dataValue = null;
return;
}
let value:number = pathObject.sources[source].value;
let converted = this.converter[this.widgetConfig.unitGroup][this.widgetConfig.unitName](value);
this.dataValue = converted.toFixed(this.widgetConfig.numDecimal);
this.valueSub = this.SignalKService.subscribePath(this.widgetUUID, this.config.paths['numericPath'].path, this.config.paths['numericPath'].source).subscribe(
newValue => {
this.dataValue = this.UnitsService.convertUnit(this.config.units['numericPath'], newValue);
this.updateCanvas();

@@ -136,3 +122,4 @@ }

this.valueSub = null;
this.SignalKService.unsubscribePath(this.widgetUUID, this.widgetConfig.signalKPath)
this.SignalKService.unsubscribePath(this.widgetUUID, this.config.paths['numericPath'].path);
}

@@ -143,17 +130,5 @@ }

//prepare current data
let settingsData: IWidgetConfig = {
signalKPath: this.widgetConfig.signalKPath,
signalKSource: this.widgetConfig.signalKSource,
label: this.widgetConfig.label,
numDecimal: this.widgetConfig.numDecimal,
numInt: this.widgetConfig.numInt,
unitGroup: this.widgetConfig.unitGroup,
unitName: this.widgetConfig.unitName
}
let dialogRef = this.dialog.open(WidgetNumericModalComponent, {
width: '650px',
data: settingsData
let dialogRef = this.dialog.open(ModalWidgetComponent, {
width: '80%',
data: this.config
});

@@ -164,13 +139,8 @@

if (result) {
console.debug("Updating widget config");
console.log(result);
this.unsubscribePath();//unsub now as we will change variables so wont know what was subbed before...
this.widgetConfig.signalKPath = result.signalKPath;
this.widgetConfig.signalKSource = result.signalKSource;
this.widgetConfig.label = result.label;
this.widgetConfig.unitGroup = result.unitGroup;
this.widgetConfig.unitName = result.unitName;
this.widgetConfig.numDecimal = result.numDecimal;
this.widgetConfig.numInt = result.numInt;
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, this.widgetConfig);
this.config = result;
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, this.config);
this.subscribePath();
this.updateCanvas();
}

@@ -208,4 +178,11 @@

let maxTextHeight = Math.floor(this.canvasEl.nativeElement.height - (this.canvasEl.nativeElement.height * 0.2));
let valueText = this.padValue(this.dataValue, this.widgetConfig.numInt, this.widgetConfig.numDecimal);
//TODO: at high res.large area, this can take way too long :( (500ms+)
let valueText;
if (isNumeric(this.dataValue)) {
valueText = this.padValue(this.dataValue.toFixed(this.config.numDecimal), this.config.numInt, this.config.numDecimal);
} else {
valueText = "--";
}
//TODO: at high res.large area, this can take way too long :( (500ms+) (added skip by 10 which helps, still feel it could be better...)
// set font small and make bigger until we hit a max.

@@ -215,4 +192,10 @@ let fontSize = 1;

this.canvasCtx.font = "bold " + fontSize.toString() + "px Arial"; // need to init it so we do loop at least once :)
//first increase fontsize by 10, skips lots of loops.
while ( (this.canvasCtx.measureText(valueText).width < maxTextWidth) && (fontSize < maxTextHeight)) {
fontSize++;
fontSize = fontSize + 10;
this.canvasCtx.font = "bold " + fontSize.toString() + "px Arial";
}
// now decrease by 1 to find the right size
while ( (this.canvasCtx.measureText(valueText).width < maxTextWidth) && (fontSize < maxTextHeight)) {
fontSize--;
this.canvasCtx.font = "bold " + fontSize.toString() + "px Arial";

@@ -227,6 +210,6 @@ }

drawTitle() {
var maxTextWidth = Math.floor(this.canvasEl.nativeElement.width - (this.canvasEl.nativeElement.width * 0.8));
var maxTextWidth = Math.floor(this.canvasEl.nativeElement.width - (this.canvasEl.nativeElement.width * 0.2));
var maxTextHeight = Math.floor(this.canvasEl.nativeElement.height - (this.canvasEl.nativeElement.height * 0.8));
// set font small and make bigger until we hit a max.
if (this.config.widgetLabel === null) { return; }
var fontSize = 1;

@@ -237,3 +220,3 @@ // get color

this.canvasCtx.font = "bold " + fontSize.toString() + "px Arial"; // need to init it so we do loop at least once :)
while ( (this.canvasCtx.measureText(this.widgetConfig.label).width < maxTextWidth) && (fontSize < maxTextHeight)) {
while ( (this.canvasCtx.measureText(this.config.widgetLabel).width < maxTextWidth) && (fontSize < maxTextHeight)) {
fontSize++;

@@ -244,6 +227,7 @@ this.canvasCtx.font = "bold " + fontSize.toString() + "px Arial";

this.canvasCtx.textBaseline="top";
this.canvasCtx.fillText(this.widgetConfig.label,this.canvasEl.nativeElement.width*0.03,this.canvasEl.nativeElement.height*0.03, maxTextWidth);
this.canvasCtx.fillText(this.config.widgetLabel,this.canvasEl.nativeElement.width*0.03,this.canvasEl.nativeElement.height*0.03, maxTextWidth);
}
drawUnit() {
if (this.config.units['numericPath'] == 'unitless') { return; }
var maxTextWidth = Math.floor(this.canvasEl.nativeElement.width - (this.canvasEl.nativeElement.width * 0.8));

@@ -256,3 +240,3 @@ var maxTextHeight = Math.floor(this.canvasEl.nativeElement.height - (this.canvasEl.nativeElement.height * 0.8));

this.canvasCtx.font = "bold " + fontSize.toString() + "px Arial"; // need to init it so we do loop at least once :)
while ( (this.canvasCtx.measureText(this.widgetConfig.unitName).width < maxTextWidth) && (fontSize < maxTextHeight)) {
while ( (this.canvasCtx.measureText(this.config.units['numericPath']).width < maxTextWidth) && (fontSize < maxTextHeight)) {
fontSize++;

@@ -263,3 +247,3 @@ this.canvasCtx.font = "bold " + fontSize.toString() + "px Arial";

this.canvasCtx.textBaseline="bottom";
this.canvasCtx.fillText(this.widgetConfig.unitName,this.canvasEl.nativeElement.width*0.97,this.canvasEl.nativeElement.height*0.97, maxTextWidth);
this.canvasCtx.fillText(this.config.units['numericPath'],this.canvasEl.nativeElement.width*0.97,this.canvasEl.nativeElement.height*0.97, maxTextWidth);
}

@@ -300,74 +284,1 @@

}
@Component({
selector: 'numeric-widget-modal',
templateUrl: './widget-numeric.modal.html',
styleUrls: ['./widget-numeric.component.css']
})
export class WidgetNumericModalComponent implements OnInit {
settingsData: IWidgetConfig;
selfPaths: boolean = true;
availablePaths: Array<string> = [];
availableSources: Array<string>;
availableUnitGroups: string[];
availableUnitNames: string[];
converter: Object = this.UnitConvertService.getConverter();
constructor(
private SignalKService: SignalKService,
private UnitConvertService: UnitConvertService,
public dialogRef:MatDialogRef<WidgetNumericModalComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) { }
ngOnInit() {
this.settingsData = this.data;
//populate available choices
this.availablePaths = this.SignalKService.getPathsByType('number').sort();
if (this.availablePaths.includes(this.settingsData.signalKPath)) {
this.settingsDataUpdatePath(); //TODO: this wipes out existing config, not good when editing existing config...
}
this.availableUnitGroups = Object.keys(this.converter);
if (this.converter.hasOwnProperty(this.settingsData.unitGroup)) {
this.availableUnitNames = Object.keys(this.converter[this.settingsData.unitGroup]);
}
}
settingsDataUpdatePath() { // called when we choose a new path. resets the rest with default info of this path
let pathObject = this.SignalKService.getPathObject(this.settingsData.signalKPath);
if (pathObject === null) { return; }
this.availableSources = ['default'].concat(Object.keys(pathObject.sources));
this.settingsData.signalKSource = 'default';
this.settingsData.numDecimal = this.data.numDecimal;
if (pathObject.meta) {
if (typeof(pathObject.meta.abbreviation) == 'string') {
this.settingsData.label = pathObject.meta.abbreviation;
} else if (typeof(pathObject.meta.label) == 'string') {
this.settingsData.label = pathObject.meta.label;
} else {
this.settingsData.label = this.settingsData.signalKPath; // who knows?
}
} else {
this.settingsData.label = this.settingsData.signalKPath;// who knows?
}
}
updateUnitType() {
if (this.converter.hasOwnProperty(this.settingsData.unitGroup)) {
this.availableUnitNames = Object.keys(this.converter[this.settingsData.unitGroup]);
// select first name
this.settingsData.unitName = this.availableUnitNames[0];
}
}
submitConfig() {
this.dialogRef.close(this.settingsData);
}
}

@@ -0,0 +0,0 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';

@@ -5,11 +5,19 @@ import { Component, Input, OnInit, OnDestroy, Inject } from '@angular/core';

import { SignalKService, pathObject } from '../signalk.service';
import { WidgetManagerService, IWidget } from '../widget-manager.service';
import { ModalWidgetComponent } from '../modal-widget/modal-widget.component';
import { SignalKService } from '../signalk.service';
import { WidgetManagerService, IWidget, IWidgetConfig } from '../widget-manager.service';
interface widgetConfig {
signalKPath: string;
signalKSource: string;
label: string;
}
const defaultConfig: IWidgetConfig = {
widgetLabel: null,
paths: {
"boolPath": {
description: "Boolean Data",
path: null,
source: null,
pathType: "boolean",
}
},
selfPaths: true
};

@@ -29,11 +37,7 @@

activeWidget: IWidget;
config: IWidgetConfig;
state: boolean = null;
widgetConfig: widgetConfig = {
signalKPath: null,
signalKSource: 'default',
label: null
}
constructor(

@@ -49,6 +53,8 @@ public dialog:MatDialog,

// no data, let's set some!
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, this.widgetConfig);
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, defaultConfig);
this.config = defaultConfig; // load default config.
} else {
this.widgetConfig = this.activeWidget.config; // load existing config.
this.config = this.activeWidget.config;
}
this.subscribePath();

@@ -63,23 +69,7 @@ }

this.unsubscribePath();
if (this.widgetConfig.signalKPath === null) { return } // nothing to sub to...
if (typeof(this.config.paths['boolPath'].path) != 'string') { return } // nothing to sub to...
this.valueSub = this.SignalKService.subscribePath(this.widgetUUID, this.widgetConfig.signalKPath).subscribe(
pathObject => {
if (pathObject === null) {
return; // we will get null back if we subscribe to a path before the app knows about it. when it learns about it we will get first value
}
let source: string;
if (this.widgetConfig.signalKSource == 'default') {
source = pathObject.defaultSource;
} else {
source = this.widgetConfig.signalKSource;
}
if (pathObject.sources[source].value === null) {
this.state = null;
}
this.state = pathObject.sources[source].value;
this.valueSub = this.SignalKService.subscribePath(this.widgetUUID, this.config.paths['boolPath'].path, this.config.paths['boolPath'].source).subscribe(
newValue => {
this.state = newValue;
}

@@ -95,3 +85,3 @@ );

this.valueSub = null;
this.SignalKService.unsubscribePath(this.widgetUUID, this.widgetConfig.signalKPath)
this.SignalKService.unsubscribePath(this.widgetUUID, this.config.paths['boolPath'].path)
}

@@ -102,23 +92,17 @@ }

//prepare current data
let settingsData: widgetConfig = {
signalKPath: this.widgetConfig.signalKPath,
signalKSource: this.widgetConfig.signalKSource,
label: this.widgetConfig.label
}
let dialogRef = this.dialog.open(WidgetStateModalComponent, {
width: '500px',
data: settingsData
let dialogRef = this.dialog.open(ModalWidgetComponent, {
width: '80%',
data: this.config
});
dialogRef.afterClosed().subscribe(result => {
// save new settings
if (result) {
console.debug("Updating widget config");
console.log(result);
this.unsubscribePath();//unsub now as we will change variables so wont know what was subbed before...
this.widgetConfig.signalKPath = result.signalKPath;
this.widgetConfig.signalKSource = result.signalKSource;
this.widgetConfig.label = result.label;
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, this.widgetConfig);
this.config = result;
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, this.config);
this.subscribePath();
}
});

@@ -129,63 +113,1 @@ }

}
@Component({
selector: 'state-widget-modal',
templateUrl: './widget-state.modal.html',
styleUrls: ['./widget-state.component.css']
})
export class WidgetStateModalComponent implements OnInit {
settingsData: widgetConfig;
selfPaths: boolean = true;
availablePaths: Array<string> = [];
availableSources: Array<string>;
constructor(
private SignalKService: SignalKService,
public dialogRef:MatDialogRef<WidgetStateModalComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) { }
ngOnInit() {
this.settingsData = this.data;
//populate available choices
this.availablePaths = this.SignalKService.getPathsByType('boolean').sort();
if (this.availablePaths.includes(this.settingsData.signalKPath)) {
this.settingsDataUpdatePath(); //TODO: this wipes out existing config, not good when editing existing config...
}
}
settingsDataUpdatePath() { // called when we choose a new path. resets the rest with default info of this path
let pathObject = this.SignalKService.getPathObject(this.settingsData.signalKPath);
if (pathObject === null) { return; }
this.availableSources = ['default'].concat(Object.keys(pathObject.sources));
this.settingsData.signalKSource = 'default';
if (pathObject.meta) {
if (typeof(pathObject.meta.abbreviation) == 'string') {
this.settingsData.label = pathObject.meta.abbreviation;
} else if (typeof(pathObject.meta.label) == 'string') {
this.settingsData.label = pathObject.meta.label;
} else {
this.settingsData.label = this.settingsData.signalKPath; // who knows?
}
} else {
this.settingsData.label = this.settingsData.signalKPath;// who knows?
}
}
submitConfig() {
this.dialogRef.close(this.settingsData);
}
}
import { Component, Input, OnInit, OnDestroy, Inject } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import {MatDialog,MatDialogRef,MAT_DIALOG_DATA } from '@angular/material';
import { MatDialog,MatDialogRef,MAT_DIALOG_DATA } from '@angular/material';
import { SignalKService, pathObject } from '../signalk.service';
import { WidgetManagerService, IWidget } from '../widget-manager.service';
import { UnitConvertService } from '../unit-convert.service';
import { ModalWidgetComponent } from '../modal-widget/modal-widget.component';
import { SignalKService } from '../signalk.service';
import { WidgetManagerService, IWidget, IWidgetConfig } from '../widget-manager.service';
interface widgetConfig {
signalKPath: string;
signalKSource: string;
label: string;
}
const defaultConfig: IWidgetConfig = {
widgetLabel: null,
paths: {
"stringPath": {
description: "String Data",
path: null,
source: null,
pathType: "string",
}
},
selfPaths: true
};
interface IWidgetsettingsData {
selectedPath: string;
selectedSource: string;
label: string;
}
@Component({

@@ -33,13 +33,10 @@ selector: 'app-widget-text-generic',

activeWidget: IWidget;
activeWidget: IWidget;
config: IWidgetConfig;
dataValue: any = null;
dataTimestamp: number = Date.now();
widgetConfig: widgetConfig = {
signalKPath: null,
signalKSource: 'default',
label: null
}
//subs

@@ -59,6 +56,9 @@ valueSub: Subscription = null;

// no data, let's set some!
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, this.widgetConfig);
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, defaultConfig);
this.config = defaultConfig; // load default config.
} else {
this.widgetConfig = this.activeWidget.config; // load existing config.
this.config = this.activeWidget.config;
}
this.subscribePath();

@@ -74,24 +74,6 @@ }

this.unsubscribePath();
if (this.widgetConfig.signalKPath === null) { return } // nothing to sub to...
this.valueSub = this.SignalKService.subscribePath(this.widgetUUID, this.widgetConfig.signalKPath).subscribe(
pathObject => {
if (pathObject === null) {
return; // we will get null back if we subscribe to a path before the app knows about it. when it learns about it we will get first value
}
let source: string;
if (this.widgetConfig.signalKSource == 'default') {
source = pathObject.defaultSource;
} else {
source = this.widgetConfig.signalKSource;
}
this.dataTimestamp = pathObject.sources[source].timestamp;
if (pathObject.sources[source].value === null) {
this.dataValue = null;
}
this.dataValue = pathObject.sources[source].value;
if (typeof(this.config.paths['stringPath'].path) != 'string') { return } // nothing to sub to...
this.valueSub = this.SignalKService.subscribePath(this.widgetUUID, this.config.paths['stringPath'].path, this.config.paths['stringPath'].source).subscribe(
newValue => {
this.dataValue = newValue;
}

@@ -105,3 +87,3 @@ );

this.valueSub = null;
this.SignalKService.unsubscribePath(this.widgetUUID, this.widgetConfig.signalKPath)
this.SignalKService.unsubscribePath(this.widgetUUID, this.config.paths['stringPath'].path)
}

@@ -112,86 +94,20 @@ }

//prepare current data
let settingsData: IWidgetsettingsData = {
selectedPath: this.widgetConfig.signalKPath,
selectedSource: this.widgetConfig.signalKSource,
label: this.widgetConfig.label
}
let dialogRef = this.dialog.open(WidgetTextGenericModalComponent, {
width: '500px',
data: settingsData
let dialogRef = this.dialog.open(ModalWidgetComponent, {
width: '80%',
data: this.config
});
dialogRef.afterClosed().subscribe(result => {
// save new settings
if (result) {
console.debug("Updating widget config");
console.log(result);
this.unsubscribePath();//unsub now as we will change variables so wont know what was subbed before...
this.widgetConfig.signalKPath = result.selectedPath;
this.widgetConfig.signalKSource = result.selectedSource;
this.widgetConfig.label = result.label;
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, this.widgetConfig);
this.config = result;
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, this.config);
this.subscribePath();
}
});
}
}
@Component({
selector: 'text-widget-modal',
templateUrl: './widget-text-generic.modal.html',
styleUrls: ['./widget-text-generic.component.css']
})
export class WidgetTextGenericModalComponent implements OnInit {
settingsData: IWidgetsettingsData;
selfPaths: boolean = true;
availablePaths: Array<string> = [];
availableSources: Array<string>;
constructor(
private SignalKService: SignalKService,
private UnitConvertService: UnitConvertService,
public dialogRef:MatDialogRef<WidgetTextGenericModalComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) { }
ngOnInit() {
this.settingsData = this.data;
//populate available choices
this.availablePaths = this.SignalKService.getPathsByType('string').sort();
if (this.availablePaths.includes(this.settingsData.selectedPath)) {
this.settingsDataUpdatePath(); //TODO: this wipes out existing config, not good when editing existing config...
}
}
settingsDataUpdatePath() { // called when we choose a new path. resets the rest with default info of this path
let pathObject = this.SignalKService.getPathObject(this.settingsData.selectedPath);
if (pathObject === null) { return; }
this.availableSources = ['default'].concat(Object.keys(pathObject.sources));
this.settingsData.selectedSource = 'default';
if (pathObject.meta) {
if (typeof(pathObject.meta.abbreviation) == 'string') {
this.settingsData.label = pathObject.meta.abbreviation;
} else if (typeof(pathObject.meta.label) == 'string') {
this.settingsData.label = pathObject.meta.label;
} else {
this.settingsData.label = this.settingsData.selectedPath; // who knows?
}
} else {
this.settingsData.label = this.settingsData.selectedPath;// who knows?
}
}
submitConfig() {
this.dialogRef.close(this.settingsData);
}
}

@@ -0,0 +0,0 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';

@@ -0,0 +0,0 @@ import { Component, Input, OnInit } from '@angular/core';

@@ -0,0 +0,0 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { Component, Input, OnInit, OnDestroy, Inject } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import {MatDialog,MatDialogRef,MAT_DIALOG_DATA } from '@angular/material';
import { Observable } from 'rxjs/Observable';
import { SignalKService, pathObject } from '../signalk.service';
import { WidgetManagerService, IWidget } from '../widget-manager.service';
import { UnitConvertService } from '../unit-convert.service';
import { MatDialog } from '@angular/material';
import { ModalWidgetComponent } from '../modal-widget/modal-widget.component';
import { SignalKService } from '../signalk.service';
import { WidgetManagerService, IWidget, IWidgetConfig } from '../widget-manager.service';
import { UnitsService } from '../units.service';
interface IWidgetConfig {
headingPath: string;
headingSource: string;
trueWindAnglePath: string;
trueWindAngleSource: string;
trueWindSpeedPath: string;
trueWindSpeedSource: string;
appWindAnglePath: string;
appWindAngleSource: string;
appWindSpeedPath: string;
appWindSpeedSource: string;
unitName: string;
}
const defaultConfig: IWidgetConfig = {
paths: {
"headingPath": {
description: "Heading",
path: 'self.navigation.courseOverGroundTrue',
source: 'default',
pathType: "number",
},
"trueWindAngle": {
description: "True Wind Angle",
path: 'self.environment.wind.angleTrueWater',
source: 'default',
pathType: "number",
},
"trueWindSpeed": {
description: "True Wind Speed",
path: 'self.environment.wind.speedTrue',
source: 'default',
pathType: "number",
},
"appWindAngle": {
description: "Apparent Wind Angle",
path: 'self.environment.wind.angleApparent',
source: 'default',
pathType: "number",
},
"appWindSpeed": {
description: "Apparent Wind Speed",
path: 'self.environment.wind.speedApparent',
source: 'default',
pathType: "number",
},
},
units: {
"trueWindSpeed": "knots",
"appWindSpeed": "knots"
},
selfPaths: true,
windSectorEnable: true,
windSectorWindowSeconds: 10,
laylineEnable: true,
laylineAngle: 35,
};
@Component({

@@ -33,19 +68,5 @@ selector: 'app-widget-wind',

@Input('unlockStatus') unlockStatus: boolean;
converter: Object = this.UnitConvertService.getConverter();
activeWidget: IWidget;
widgetConfig: IWidgetConfig = {
headingPath: 'vessels.self.navigation.headingTrue',
headingSource: 'default',
trueWindAnglePath: 'vessels.self.environment.wind.angleTrueWater',
trueWindAngleSource: 'default',
trueWindSpeedPath: 'vessels.self.environment.wind.speedTrue',
trueWindSpeedSource: 'default',
appWindAnglePath: 'vessels.self.environment.wind.angleApparent',
appWindAngleSource: 'default',
appWindSpeedPath: 'vessels.self.environment.wind.speedApparent',
appWindSpeedSource: 'default',
unitName: 'knots'
}
config: IWidgetConfig;

@@ -67,2 +88,13 @@ currentHeading: number = 0;

trueWindHistoric: {
timestamp: number;
heading: number;
}[] = [];
trueWindMinHistoric: number;
trueWindMidHistoric: number;
trueWindMaxHistoric: number;
windSectorObservableSub: Subscription;
constructor(

@@ -72,3 +104,3 @@ public dialog:MatDialog,

private WidgetManagerService: WidgetManagerService,
private UnitConvertService: UnitConvertService) {
private UnitsService: UnitsService) {
}

@@ -81,5 +113,6 @@

// no data, let's set some!
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, this.widgetConfig);
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, defaultConfig);
this.config = defaultConfig; // load default config.
} else {
this.widgetConfig = this.activeWidget.config; // load existing config.
this.config = this.activeWidget.config;
}

@@ -99,2 +132,3 @@ this.startAll();

this.subscribeTrueWindSpeed();
this.startWindSectors();
}

@@ -107,3 +141,4 @@

this.unsubscribeTrueWindAngle();
this.unsubscribeTrueWindSpeed();
this.unsubscribeTrueWindSpeed();
this.stopWindSectors();
}

@@ -113,23 +148,11 @@

this.unsubscribeHeading();
if (this.widgetConfig.headingPath === null) { return } // nothing to sub to...
this.headingSub = this.SignalKService.subscribePath(this.widgetUUID, this.widgetConfig.headingPath).subscribe(
pathObject => {
if (pathObject === null) {
return; // we will get null back if we subscribe to a path before the app knows about it. when it learns about it we will get first value
}
let source: string;
if (this.widgetConfig.headingSource == 'default') {
source = pathObject.defaultSource;
if (typeof(this.config.paths['headingPath'].path) != 'string') { return } // nothing to sub to...
this.headingSub = this.SignalKService.subscribePath(this.widgetUUID, this.config.paths['headingPath'].path, this.config.paths['headingPath'].source).subscribe(
newValue => {
if (newValue === null) {
this.currentHeading = 0;
} else {
source = this.widgetConfig.headingSource;
this.currentHeading = this.UnitsService.convertUnit('deg', newValue);
}
if (pathObject.sources[source].value === null) {
this.currentHeading = 0;
}
let value:number = pathObject.sources[source].value;
let converted = this.converter['angle']['deg'](value);
this.currentHeading = converted;
}

@@ -141,17 +164,7 @@ );

this.unsubscribeAppWindAngle();
if (this.widgetConfig.appWindAnglePath === null) { return } // nothing to sub to...
if (typeof(this.config.paths['appWindAngle'].path) != 'string') { return } // nothing to sub to...
this.appWindAngleSub = this.SignalKService.subscribePath(this.widgetUUID, this.widgetConfig.appWindAnglePath).subscribe(
pathObject => {
if (pathObject === null) {
return; // we will get null back if we subscribe to a path before the app knows about it. when it learns about it we will get first value
}
let source: string;
if (this.widgetConfig.appWindAngleSource == 'default') {
source = pathObject.defaultSource;
} else {
source = this.widgetConfig.appWindAngleSource;
}
if (pathObject.sources[source].value === null) {
this.appWindAngleSub = this.SignalKService.subscribePath(this.widgetUUID, this.config.paths['appWindAngle'].path, this.config.paths['appWindAngle'].source).subscribe(
newValue => {
if (newValue === null) {
this.appWindAngle = null;

@@ -161,11 +174,10 @@ return;

let value:number = pathObject.sources[source].value;
let converted = this.converter['angle']['deg'](value);
let converted = this.UnitsService.convertUnit('deg', newValue);
// 0-180+ for stb
// -0 to -180 for port
// need in 0-360
if (converted > 0) {// stb
this.appWindAngle= 360 - converted;
} else if (converted < 0) {
this.appWindAngle = (converted * -1);
if (converted < 0) {// stb
this.appWindAngle= 360 + converted; // adding a negative number subtracts it...
} else {
this.appWindAngle = converted;
}

@@ -179,23 +191,7 @@

this.unsubscribeAppWindSpeed();
if (this.widgetConfig.appWindSpeedPath === null) { return } // nothing to sub to...
if (typeof(this.config.paths['appWindSpeed'].path) != 'string') { return } // nothing to sub to...
this.appWindSpeedSub = this.SignalKService.subscribePath(this.widgetUUID, this.widgetConfig.appWindSpeedPath).subscribe(
pathObject => {
if (pathObject === null) {
return; // we will get null back if we subscribe to a path before the app knows about it. when it learns about it we will get first value
}
let source: string;
if (this.widgetConfig.appWindSpeedSource == 'default') {
source = pathObject.defaultSource;
} else {
source = this.widgetConfig.appWindSpeedSource;
}
if (pathObject.sources[source].value === null) {
this.appWindSpeed = null;
return;
}
let value:number = pathObject.sources[source].value;
this.appWindSpeed = this.converter['speed'][this.widgetConfig.unitName](value);
this.appWindSpeedSub = this.SignalKService.subscribePath(this.widgetUUID, this.config.paths['appWindSpeed'].path, this.config.paths['appWindSpeed'].source).subscribe(
newValue => {
this.appWindSpeed = this.UnitsService.convertUnit(this.config.units['appWindSpeed'], newValue);
}

@@ -207,32 +203,31 @@ );

this.unsubscribeTrueWindAngle();
if (this.widgetConfig.trueWindAnglePath === null) { return } // nothing to sub to...
if (typeof(this.config.paths['trueWindAngle'].path) != 'string') { return } // nothing to sub to...
this.trueWindAngleSub = this.SignalKService.subscribePath(this.widgetUUID, this.widgetConfig.trueWindAnglePath).subscribe(
pathObject => {
if (pathObject === null) {
return; // we will get null back if we subscribe to a path before the app knows about it. when it learns about it we will get first value
}
let source: string;
if (this.widgetConfig.trueWindAngleSource == 'default') {
source = pathObject.defaultSource;
} else {
source = this.widgetConfig.trueWindAngleSource;
}
if (pathObject.sources[source].value === null) {
this.trueWindAngleSub = this.SignalKService.subscribePath(this.widgetUUID, this.config.paths['trueWindAngle'].path, this.config.paths['trueWindAngle'].source).subscribe(
newValue => {
if (newValue === null) {
this.trueWindAngle = null;
return;
}
let converted = this.UnitsService.convertUnit('deg', newValue);
let value:number = pathObject.sources[source].value;
let converted = this.converter['angle']['deg'](value);
// 0-180+ for stb
// -0 to -180 for port
// need in 0-360
if (converted > 0) {// stb
this.trueWindAngle= 360 - converted;
} else if (converted < 0) {
this.trueWindAngle = (converted * -1);
// Depending on path, this number can either be the magnetic compass heading, true compass heading, or heading relative to boat heading (-180 to 180deg)... Ugh...
// 0-180+ for stb
// -0 to -180 for port
// need in 0-360
if (this.config.paths['trueWindAngle'].path.match('angleTrueWater')||
this.config.paths['trueWindAngle'].path.match('angleTrueGround')) {
//-180 to 180
this.trueWindAngle = this.addHeading(this.currentHeading, converted);
} else if (this.config.paths['trueWindAngle'].path.match('direction')) {
//0-360
this.trueWindAngle = converted;
}
//add to historical for wind sectors
if (this.config.windSectorEnable) {
this.addHistoricalTrue(this.trueWindAngle);
}
}

@@ -244,28 +239,57 @@ );

this.unsubscribeTrueWindSpeed();
if (this.widgetConfig.trueWindSpeedPath === null) { return } // nothing to sub to...
if (typeof(this.config.paths['trueWindSpeed'].path) != 'string') { return } // nothing to sub to...
this.trueWindSpeedSub = this.SignalKService.subscribePath(this.widgetUUID, this.widgetConfig.trueWindSpeedPath).subscribe(
pathObject => {
if (pathObject === null) {
return; // we will get null back if we subscribe to a path before the app knows about it. when it learns about it we will get first value
}
let source: string;
if (this.widgetConfig.trueWindSpeedSource == 'default') {
source = pathObject.defaultSource;
} else {
source = this.widgetConfig.trueWindSpeedSource;
}
this.trueWindSpeedSub = this.SignalKService.subscribePath(this.widgetUUID, this.config.paths['trueWindSpeed'].path, this.config.paths['trueWindSpeed'].source).subscribe(
newValue => {
this.trueWindSpeed = this.UnitsService.convertUnit(this.config.units['trueWindSpeed'], newValue);
}
);
}
if (pathObject.sources[source].value === null) {
this.trueWindSpeed = null;
return;
}
startWindSectors() {
this.windSectorObservableSub = Observable.interval (500).subscribe(x => {
this.historicalCleanup();
});
}
let value:number = pathObject.sources[source].value;
this.trueWindSpeed = this.converter['speed'][this.widgetConfig.unitName](value);
addHistoricalTrue (windHeading) {
this.trueWindHistoric.push({
timestamp: Date.now(),
heading: windHeading
});
let arr = this.arcForAngles(this.trueWindHistoric.map(d => d.heading));
this.trueWindMinHistoric = arr[0];
this.trueWindMaxHistoric = arr[1];
this.trueWindMidHistoric = arr[2];
}
arcForAngles (data) {
return data.slice(1).reduce((acc, theValue) => {
let value = theValue
while (value < acc[0] - 180) {
value += 360
}
);
while (value > acc[1] + 180) {
value -= 360
}
acc[0] = Math.min(acc[0], value)
acc[1] = Math.max(acc[1], value)
acc[2] = ((acc[1]-acc[0])/2)+acc[0];
return acc
}, [data[0], data[0]])
}
historicalCleanup() {
let n = Date.now()-(this.config.windSectorWindowSeconds*1000);
for (var i = this.trueWindHistoric.length - 1; i >= 0; --i) {
if (this.trueWindHistoric[i].timestamp < n) {
this.trueWindHistoric.splice(i,1);
}
}
}
stopWindSectors() {
this.windSectorObservableSub.unsubscribe();
}
unsubscribeHeading() {

@@ -275,3 +299,3 @@ if (this.headingSub !== null) {

this.headingSub = null;
this.SignalKService.unsubscribePath(this.widgetUUID, this.widgetConfig.headingPath);
this.SignalKService.unsubscribePath(this.widgetUUID, this.config.paths['headingPath'].path);
}

@@ -284,3 +308,3 @@ }

this.appWindAngleSub = null;
this.SignalKService.unsubscribePath(this.widgetUUID, this.widgetConfig.appWindAnglePath);
this.SignalKService.unsubscribePath(this.widgetUUID, this.config.paths['appWindAngle'].path);
}

@@ -293,3 +317,3 @@ }

this.appWindSpeedSub = null;
this.SignalKService.unsubscribePath(this.widgetUUID, this.widgetConfig.appWindSpeedPath);
this.SignalKService.unsubscribePath(this.widgetUUID, this.config.paths['appWindSpeed'].path);
}

@@ -302,3 +326,3 @@ }

this.trueWindAngleSub = null;
this.SignalKService.unsubscribePath(this.widgetUUID, this.widgetConfig.trueWindAnglePath);
this.SignalKService.unsubscribePath(this.widgetUUID, this.config.paths['trueWindAngle'].path);
}

@@ -311,158 +335,45 @@ }

this.trueWindSpeedSub = null;
this.SignalKService.unsubscribePath(this.widgetUUID, this.widgetConfig.trueWindSpeedPath);
this.SignalKService.unsubscribePath(this.widgetUUID, this.config.paths['trueWindSpeed'].path);
}
}
openWidgetSettings() {
//prepare current data
let settingsData: IWidgetConfig = {
headingPath: this.widgetConfig.headingPath,
headingSource: this.widgetConfig.headingSource,
trueWindAnglePath: this.widgetConfig.trueWindAnglePath,
trueWindAngleSource: this.widgetConfig.trueWindAngleSource,
trueWindSpeedPath: this.widgetConfig.trueWindSpeedPath,
trueWindSpeedSource: this.widgetConfig.trueWindSpeedSource,
appWindAnglePath: this.widgetConfig.appWindAnglePath,
appWindAngleSource: this.widgetConfig.appWindAngleSource,
appWindSpeedPath: this.widgetConfig.appWindSpeedPath,
appWindSpeedSource: this.widgetConfig.appWindSpeedSource,
unitName: this.widgetConfig.unitName
}
let dialogRef = this.dialog.open(WidgetWindModalComponent, {
width: '650px',
data: settingsData
});
dialogRef.afterClosed().subscribe(result => {
// save new settings
if (result) {
console.debug("Updating widget config");
this.stopAll();//unsub now as we will change variables so wont know what was subbed before...
this.widgetConfig.headingPath = result.headingPath;
this.widgetConfig.headingSource = result.headingSource;
this.widgetConfig.trueWindAnglePath = result.trueWindAnglePath;
this.widgetConfig.trueWindAngleSource = result.trueWindAngleSource;
this.widgetConfig.trueWindSpeedPath = result.trueWindSpeedPath;
this.widgetConfig.trueWindSpeedSource = result.trueWindSpeedSource;
this.widgetConfig.appWindAnglePath = result.appWindAnglePath;
this.widgetConfig.appWindAngleSource = result.appWindAngleSource;
this.widgetConfig.appWindSpeedPath = result.appWindSpeedPath;
this.widgetConfig.appWindSpeedSource = result.appWindSpeedSource;
this.widgetConfig.unitName = result.unitName;
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, this.widgetConfig);
this.startAll();
}
});
addHeading(h1: number, h2: number) {
let h3 = h1 + h2;
while (h3 > 359) { h3 = h3 - 359; }
while (h3 < 0) { h3 = h3 + 359; }
return h3;
}
}
/*************************************************************
* ***********************************************************
* ***********************************************************
* Modal
* ***********************************************************
* ***********************************************************
*/
@Component({
selector: 'wind-widget-modal',
templateUrl: './widget-wind.modal.html',
styleUrls: ['./widget-wind.component.css']
})
export class WidgetWindModalComponent implements OnInit {
settingsData: IWidgetConfig;
availableUnitNames: string[];
selfPaths: boolean = true;
availablePaths: Array<string> = [];
headingSources: Array<string>;
trueWindAngleSources: Array<string>;
trueWindSpeedSources: Array<string>;
appWindAngleSources: Array<string>;
appWindSpeedSources: Array<string>;
converter: Object = this.UnitConvertService.getConverter();
openWidgetSettings() {
constructor(
private SignalKService: SignalKService,
private UnitConvertService: UnitConvertService,
public dialogRef:MatDialogRef<WidgetWindModalComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) { }
let dialogRef = this.dialog.open(ModalWidgetComponent, {
width: '80%',
data: this.config
});
ngOnInit() {
this.settingsData = this.data;
//populate available choices
this.availablePaths = this.SignalKService.getPathsByType('number').sort();
dialogRef.afterClosed().subscribe(result => {
// save new settings
if (result) {
console.log(result);
this.stopAll();//unsub now as we will change variables so wont know what was subbed before...
this.config = result;
this.WidgetManagerService.updateWidgetConfig(this.widgetUUID, this.config);
this.startAll();
}
this.updateHeadingSources();
this.updateTrueWindAngleSources();
this.updateTrueWindSpeedSources();
this.updateAppWindAngleSources();
this.updateAppWindSpeedSources();
this.availableUnitNames = Object.keys(this.converter['speed']);
});
}
updateHeadingSources() {
let pathObject = this.SignalKService.getPathObject(this.settingsData.headingPath);
if (pathObject === null) { return; }
this.headingSources = ['default'].concat(Object.keys(pathObject.sources));
if (!this.headingSources.includes(this.settingsData.headingSource))
{ this.settingsData.headingSource = 'default'; }
}
updateTrueWindAngleSources() {
let pathObject = this.SignalKService.getPathObject(this.settingsData.trueWindAnglePath);
if (pathObject === null) { return; }
this.trueWindAngleSources = ['default'].concat(Object.keys(pathObject.sources));
if (!this.trueWindAngleSources.includes(this.settingsData.trueWindAngleSource))
{ this.settingsData.trueWindAngleSource = 'default'; }
}
updateTrueWindSpeedSources() {
let pathObject = this.SignalKService.getPathObject(this.settingsData.trueWindSpeedPath);
if (pathObject === null) { return; }
this.trueWindSpeedSources = ['default'].concat(Object.keys(pathObject.sources));
if (!this.trueWindSpeedSources.includes(this.settingsData.trueWindSpeedSource))
{ this.settingsData.trueWindSpeedSource = 'default'; }
}
updateAppWindAngleSources() {
let pathObject = this.SignalKService.getPathObject(this.settingsData.appWindAnglePath);
if (pathObject === null) { return; }
this.appWindAngleSources = ['default'].concat(Object.keys(pathObject.sources));
if (!this.appWindAngleSources.includes(this.settingsData.appWindAngleSource))
{ this.settingsData.appWindAngleSource = 'default'; }
}
updateAppWindSpeedSources() {
let pathObject = this.SignalKService.getPathObject(this.settingsData.appWindSpeedPath);
if (pathObject === null) { return; }
this.appWindSpeedSources = ['default'].concat(Object.keys(pathObject.sources));
if (!this.appWindSpeedSources.includes(this.settingsData.appWindSpeedSource))
{ this.settingsData.appWindSpeedSource = 'default'; }
}
submitConfig() {
this.dialogRef.close(this.settingsData);
}
}
/*! NoSleep.min.js v0.7.0 - git.io/vfn01 - Rich Tibbett - MIT license */
!function(A,B){"object"==typeof exports&&"object"==typeof module?module.exports=B():"function"==typeof define&&define.amd?define([],B):"object"==typeof exports?exports.NoSleep=B():A.NoSleep=B()}(this,function(){return function(A){function B(e){if(Q[e])return Q[e].exports;var o=Q[e]={i:e,l:!1,exports:{}};return A[e].call(o.exports,o,o.exports,B),o.l=!0,o.exports}var Q={};return B.m=A,B.c=Q,B.d=function(A,Q,e){B.o(A,Q)||Object.defineProperty(A,Q,{configurable:!1,enumerable:!0,get:e})},B.n=function(A){var Q=A&&A.__esModule?function(){return A.default}:function(){return A};return B.d(Q,"a",Q),Q},B.o=function(A,B){return Object.prototype.hasOwnProperty.call(A,B)},B.p="",B(B.s=0)}([function(A,B,Q){"use strict";function e(A,B){if(!(A instanceof B))throw new TypeError("Cannot call a class as a function")}var o=function(){function A(A,B){for(var Q=0;Q<B.length;Q++){var e=B[Q];e.enumerable=e.enumerable||!1,e.configurable=!0,"value"in e&&(e.writable=!0),Object.defineProperty(A,e.key,e)}}return function(B,Q,e){return Q&&A(B.prototype,Q),e&&A(B,e),B}}(),t=Q(1),n="undefined"!=typeof navigator&&parseFloat((""+(/CPU.*OS ([0-9_]{3,4})[0-9_]{0,1}|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent)||[0,""])[1]).replace("undefined","3_2").replace("_",".").replace("_",""))<10&&!window.MSStream,E=function(){function A(){e(this,A),n?this.noSleepTimer=null:(this.noSleepVideo=document.createElement("video"),this.noSleepVideo.setAttribute("playsinline",""),this.noSleepVideo.setAttribute("src",t),this.noSleepVideo.addEventListener("timeupdate",function(A){this.noSleepVideo.currentTime>.5&&(this.noSleepVideo.currentTime=Math.random())}.bind(this)))}return o(A,[{key:"enable",value:function(){n?(this.disable(),this.noSleepTimer=window.setInterval(function(){window.location.href="/",window.setTimeout(window.stop,0)},15e3)):this.noSleepVideo.play()}},{key:"disable",value:function(){n?this.noSleepTimer&&(window.clearInterval(this.noSleepTimer),this.noSleepTimer=null):this.noSleepVideo.pause()}}]),A}();A.exports=E},function(A,B,Q){"use strict";A.exports="data:video/mp4;base64,AAAAIGZ0eXBtcDQyAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAACKBtZGF0AAAC8wYF///v3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE0MiByMjQ3OSBkZDc5YTYxIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxNCAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTEgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MToweDExMSBtZT1oZXggc3VibWU9MiBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0wIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MCA4eDhkY3Q9MCBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0wIHRocmVhZHM9NiBsb29rYWhlYWRfdGhyZWFkcz0xIHNsaWNlZF90aHJlYWRzPTAgbnI9MCBkZWNpbWF0ZT0xIGludGVybGFjZWQ9MCBibHVyYXlfY29tcGF0PTAgY29uc3RyYWluZWRfaW50cmE9MCBiZnJhbWVzPTMgYl9weXJhbWlkPTIgYl9hZGFwdD0xIGJfYmlhcz0wIGRpcmVjdD0xIHdlaWdodGI9MSBvcGVuX2dvcD0wIHdlaWdodHA9MSBrZXlpbnQ9MzAwIGtleWludF9taW49MzAgc2NlbmVjdXQ9NDAgaW50cmFfcmVmcmVzaD0wIHJjX2xvb2thaGVhZD0xMCByYz1jcmYgbWJ0cmVlPTEgY3JmPTIwLjAgcWNvbXA9MC42MCBxcG1pbj0wIHFwbWF4PTY5IHFwc3RlcD00IHZidl9tYXhyYXRlPTIwMDAwIHZidl9idWZzaXplPTI1MDAwIGNyZl9tYXg9MC4wIG5hbF9ocmQ9bm9uZSBmaWxsZXI9MCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAAOWWIhAA3//p+C7v8tDDSTjf97w55i3SbRPO4ZY+hkjD5hbkAkL3zpJ6h/LR1CAABzgB1kqqzUorlhQAAAAxBmiQYhn/+qZYADLgAAAAJQZ5CQhX/AAj5IQAD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"}])});

@@ -0,0 +0,0 @@ /**********************************************************************

@@ -15,6 +15,9 @@ {

"lib": [
"es2016",
"es2017",
"dom"
]
},
"angularCompilerOptions": {
"preserveWhitespaces": false
}
}

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc