diff --git a/CHANGELOG.md b/CHANGELOG.md index b57d7732..bb2793d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # CHANGELOG: Freeboard +### v2.8.0 + +- **Added**: Option to display laylines when destination is active. (#149) +- **Added**: Support for Signal K server "Features" API. +- **Updated**: Measure function to display both segment and total distance. (#153) +- **Updated**: Clearing an alarm no longer sets the value to `null`, sets alarm state to normal instead. + + ### v2.7.1 - **Added**: Set a default waypoint name when adding a waypoint at vessel position. (#146) diff --git a/helper/alarms/alarms.ts b/helper/alarms/alarms.ts index 464ac47b..cd7a09f0 100644 --- a/helper/alarms/alarms.ts +++ b/helper/alarms/alarms.ts @@ -5,7 +5,8 @@ import { ALARM_STATE, Path, PathValue, - Position + Position, + SKVersion } from '@signalk/server-api'; import { FreeboardHelperApp } from '../index'; @@ -24,6 +25,7 @@ const STANDARD_ALARMS = [ let server: FreeboardHelperApp; let pluginId: string; +const ALARM_API_PATH = '/signalk/v1/api/alarms'; export const initAlarms = (app: FreeboardHelperApp, id: string) => { server = app; @@ -48,12 +50,11 @@ export const initAlarms = (app: FreeboardHelperApp, id: string) => { const initAlarmEndpoints = () => { server.debug(`** Registering Alarm Action API endpoint(s) **`); - server.put( - '/signalk/v2/api/notifications/:alarmType', + + server.post( + `${ALARM_API_PATH}/:alarmType`, (req: Request, res: Response, next: NextFunction) => { - server.debug( - `** PUT /signalk/v2/api/notifications/${req.params.alarmType}` - ); + server.debug(`** POST ${ALARM_API_PATH}/${req.params.alarmType}`); if (!STANDARD_ALARMS.includes(req.params.alarmType)) { next(); return; @@ -82,12 +83,57 @@ const initAlarmEndpoints = () => { } } ); + server.post( + `${ALARM_API_PATH}/:alarmType/silence`, + (req: Request, res: Response) => { + server.debug(`** POST ${req.path}`); + if (!STANDARD_ALARMS.includes(req.params.alarmType)) { + res.status(200).json({ + state: 'COMPLETED', + statusCode: 200, + message: `Unsupported Alarm (${req.params.alarmType}).` + }); + return; + } + try { + const al = server.getSelfPath(`notifications.${req.params.alarmType}`); + if (al && al.value) { + server.debug('Alarm value....'); + if (al.value.method && al.value.method.includes('sound')) { + server.debug('Alarm has sound... silence!!!'); + al.value.method = al.value.method.filter((i) => i !== 'sound'); + const r = handlePutAlarmState( + 'vessels.self', + `notifications.${req.params.alarmType}` as Path, + al.value + ); + res.status(200).json(r); + } else { + server.debug('Alarm has no sound... no action required.'); + res.status(200).json({ + state: 'COMPLETED', + statusCode: 200, + message: `Alarm (${req.params.alarmType}) is already silent.` + }); + } + } else { + throw new Error( + `Alarm (${req.params.alarmType}) has no value or was not found!` + ); + } + } catch (e) { + res.status(400).json({ + state: 'FAILED', + statusCode: 400, + message: (e as Error).message + }); + } + } + ); server.delete( - '/signalk/v2/api/notifications/:alarmType', + `${ALARM_API_PATH}/:alarmType`, (req: Request, res: Response, next: NextFunction) => { - server.debug( - `** DELETE /signalk/v2/api/notifications/${req.params.alarmType}` - ); + server.debug(`** DELETE ${ALARM_API_PATH}/${req.params.alarmType}`); if (!STANDARD_ALARMS.includes(req.params.alarmType)) { next(); return; @@ -96,7 +142,11 @@ const initAlarmEndpoints = () => { const r = handlePutAlarmState( 'vessels.self', `notifications.${req.params.alarmType}` as Path, - null + { + message: '', + method: [], + state: ALARM_STATE.normal + } ); res.status(200).json(r); } catch (e) { @@ -137,16 +187,16 @@ const handlePutAlarmState = ( server.debug(JSON.stringify(alarmType)); let noti: PathValue; if (value) { - const alm = buildAlarmMessage(value.message); + const alm = value.state === ALARM_STATE.normal ? null : buildAlarmData(); noti = { path: `notifications.${alarmType}` as Path, value: { state: value.state ?? null, method: value.method ?? null, - message: alm.message + message: value.message } }; - if (alm.data) { + if (alm && alm.data) { // eslint-disable-next-line @typescript-eslint/no-explicit-any (noti.value as any).data = alm.data; } @@ -171,10 +221,9 @@ const handlePutAlarmState = ( } }; -const buildAlarmMessage = (message: string) => { +const buildAlarmData = () => { const pos: { value: Position } = server.getSelfPath('navigation.position'); return { - message: message, data: { position: pos ? pos.value : null } @@ -186,5 +235,5 @@ const emitNotification = (msg: PathValue) => { const delta = { updates: [{ values: [msg] }] }; - server.handleMessage(pluginId, delta); + server.handleMessage(pluginId, delta, SKVersion.v2); }; diff --git a/package.json b/package.json index 645befc5..ddbe98b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@signalk/freeboard-sk", - "version": "2.7.1", + "version": "2.8.0", "description": "Openlayers chart plotter implementation for Signal K", "keywords": [ "signalk-webapp", @@ -87,6 +87,7 @@ "proj4": "2.6.2", "protractor": "~7.0.0", "rxjs": "~6.6.3", + "semver": "^7.6.0", "signalk-client-angular": "^2.0.3", "signalk-worker-angular": "^1.1.4", "simplify-ts": "^1.0.2", @@ -97,4 +98,4 @@ "xml2js": "^0.6.2", "zone.js": "~0.14.4" } -} \ No newline at end of file +} diff --git a/src/app/lib/components/build-route.component.ts b/src/app/lib/components/build-route.component.ts index 23b974d9..8af13195 100644 --- a/src/app/lib/components/build-route.component.ts +++ b/src/app/lib/components/build-route.component.ts @@ -108,23 +108,19 @@ import { (cdkDropListDropped)="dropEventHandler($event)" > @for(item of rtepts; track item; let idx = $index) { -
-
- room -
-
- {{ item.name }} -
-
- -
+
+
+ room +
+
+ {{ item.name }}
+
+ +
+
}
@@ -149,6 +145,7 @@ export class BuildRouteComponent { wpts = []; rtepts = []; + // eslint-disable-next-line @typescript-eslint/no-explicit-any @Output() save: EventEmitter = new EventEmitter(); constructor( diff --git a/src/app/lib/components/dial-text.ts b/src/app/lib/components/dial-text.ts index b80c9d79..ee8f40e3 100644 --- a/src/app/lib/components/dial-text.ts +++ b/src/app/lib/components/dial-text.ts @@ -1,7 +1,7 @@ /** Text Dial Component ** ************************/ -import { Component, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; /*********** Text Dial *************** title: "" title text, @@ -10,6 +10,7 @@ units: "" dsisplay units, ***********************************/ @Component({ standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ap-dial-text', imports: [], template: ` @@ -25,6 +26,4 @@ export class TextDialComponent { @Input() title: string; @Input() value: string; @Input() units: string; - - //constructor() {} } diff --git a/src/app/lib/components/dialogs/common/dialogs.component.ts b/src/app/lib/components/dialogs/common/dialogs.component.ts index 4b63f2cc..48fedd40 100644 --- a/src/app/lib/components/dialogs/common/dialogs.component.ts +++ b/src/app/lib/components/dialogs/common/dialogs.component.ts @@ -477,48 +477,48 @@ export class MessageBarComponent {
@for(c of data.content; track c; let i = $index) { - -
-

{{ c.title }}

+ +
+

{{ c.title }}

+
+
+
+ @if(i !== 0 && data.content.length > 1) { + + }
-
-
- @if(i !== 0 && data.content.length > 1) { - - } -
-
-
- @if(i !== data.content.length - 1) { - - } -
+
+
+ @if(i !== data.content.length - 1) { + + }
- +
+ }
@for(c of data.content; track c; let i = $index) { - - fiber_manual_record - + + fiber_manual_record + }
diff --git a/src/app/lib/components/dialogs/gpx/gpxload/gpxload-dialog.html b/src/app/lib/components/dialogs/gpx/gpxload/gpxload-dialog.html index 9b713fb9..e10917f5 100644 --- a/src/app/lib/components/dialogs/gpx/gpxload/gpxload-dialog.html +++ b/src/app/lib/components/dialogs/gpx/gpxload/gpxload-dialog.html @@ -86,30 +86,30 @@
@for(w of gpxData.waypoints; track w; let i=$index) { - -
-
-
- place - {{w.name}} -
-
- {{w.description}} -
-
- - -
+ +
+
+
+ place + {{w.name}} +
+
+ {{w.description}} +
+
+ +
- +
+
}
@@ -153,31 +153,31 @@
@for(r of gpxData.routes; track r; let i=$index) { - -
-
-
- directions - {{r.name}} -
-
- {{r.description}}
- {{r.length}} -
-
- - -
+ +
+
+
+ directions + {{r.name}} +
+
+ {{r.description}}
+ {{r.length}} +
+
+ +
- +
+
}
@@ -222,30 +222,30 @@
@for(t of gpxData.tracks; track t; let i=$index) { - -
-
-
- show_chart - {{t.name}} -
-
- {{t.description}} -
-
- - -
+ +
+
+
+ show_chart + {{t.name}} +
+
+ {{t.description}} +
+
+ +
- +
+
}
diff --git a/src/app/lib/components/dialogs/gpx/gpxsave/gpxsave-dialog.html b/src/app/lib/components/dialogs/gpx/gpxsave/gpxsave-dialog.html index 06cbc1c2..7effdc87 100644 --- a/src/app/lib/components/dialogs/gpx/gpxsave/gpxsave-dialog.html +++ b/src/app/lib/components/dialogs/gpx/gpxsave/gpxsave-dialog.html @@ -74,30 +74,30 @@
@for(w of resData.waypoints;track w;let i=$index) { - -
-
-
- place - {{w.name}} -
-
- -
-
- - -
+ +
+
+
+ place + {{w.name}} +
+
+ +
+
+ +
- +
+
}
@@ -141,31 +141,31 @@
@for(r of resData.routes; track r; let i=$index) { - -
-
-
- directions - {{r.name}} -
-
- {{r.description}}
- {{r.length}} -
-
- - -
+ +
+
+
+ directions + {{r.name}} +
+
+ {{r.description}}
+ {{r.length}} +
+
+ +
- +
+
}
@@ -205,28 +205,28 @@
@for(r of resData.tracks; track r; let i=$index) { - -
-
-
- directions_boat - Vessel Trail -
-
-
- - -
+ +
+
+
+ directions_boat + Vessel Trail +
+
+
+ +
- +
+
}
diff --git a/src/app/lib/components/index.ts b/src/app/lib/components/index.ts index f90a31dc..87943a79 100644 --- a/src/app/lib/components/index.ts +++ b/src/app/lib/components/index.ts @@ -6,3 +6,4 @@ export * from './pip.component'; export * from './signalk-details.component'; export * from './wakelock.component'; export * from './build-route.component'; +export * from './measurements.component'; diff --git a/src/app/lib/geoutils.ts b/src/app/lib/geoutils.ts index 0725a80b..f84e1070 100644 --- a/src/app/lib/geoutils.ts +++ b/src/app/lib/geoutils.ts @@ -102,3 +102,34 @@ export class GeoUtils { } } } + +export class Angle { + /** difference between two angles (in degrees) + * @param h: angle 1 (in degrees) + * @param b: angle 2 (in degrees) + * @returns angle (-ive = port) + */ + static difference(h: number, b: number): number { + const d = 360 - b; + const hd = h + d; + const a = Angle.normalise(hd); + return a < 180 ? 0 - a : 360 - a; + } + + /** Add two angles (in degrees) + * @param h: angle 1 (in degrees) + * @param b: angle 2 (in degrees) + * @returns sum angle + */ + static add(h: number, b: number): number { + return Angle.normalise(h + b); + } + + /** Normalises angle to a value between 0 & 360 degrees + * @param a: angle (in degrees) + * @returns value between 0-360 + */ + static normalise(a: number): number { + return a < 0 ? a + 360 : a >= 360 ? a - 360 : a; + } +} diff --git a/src/app/modules/map/components/popover/compass.component.ts b/src/app/modules/map/components/popover/compass.component.ts index de4c0990..c2dc1673 100644 --- a/src/app/modules/map/components/popover/compass.component.ts +++ b/src/app/modules/map/components/popover/compass.component.ts @@ -33,7 +33,7 @@ class SvgDialBase { this._init(); } - //** initaise: calculate scaling ** + //** initialise: calculate scaling ** private _init() { const a: number = this.minAngle > this.maxAngle diff --git a/src/app/modules/map/mapconfig.ts b/src/app/modules/map/mapconfig.ts index 38096dba..41163668 100644 --- a/src/app/modules/map/mapconfig.ts +++ b/src/app/modules/map/mapconfig.ts @@ -412,3 +412,22 @@ export const meteoStyles = { }) }) }; + +export const laylineStyles = { + port: new Style({ + fill: new Fill({ color: 'green' }), + stroke: new Stroke({ + color: 'green', + width: 1, + lineDash: [5, 5] + }) + }), + starboard: new Style({ + fill: new Fill({ color: 'red' }), + stroke: new Stroke({ + color: 'red', + width: 1, + lineDash: [5, 5] + }) + }) +}; diff --git a/src/app/modules/map/ol/index.ts b/src/app/modules/map/ol/index.ts index 5d2bd05c..76df0a59 100644 --- a/src/app/modules/map/ol/index.ts +++ b/src/app/modules/map/ol/index.ts @@ -45,6 +45,7 @@ import { CPAAlarmComponent } from './lib/alarms/layer-cpa-alarm.component'; import { ArrivalCircleComponent } from './lib/navigation/layer-arrival-circle.component'; import { XTEPathComponent } from './lib/navigation/layer-xte-path.component'; import { BearingLineComponent } from './lib/navigation/layer-bearing-line.component'; +import { LaylineComponent } from './lib/navigation/layer-layline.component'; import { DirectionOfTravelComponent } from './lib/navigation/layer-dot.component'; import { VesselComponent } from './lib/vessel/layer-vessel.component'; import { VesselTrailComponent } from './lib/vessel/layer-vessel-trail.component'; @@ -95,6 +96,7 @@ export { CPAAlarmComponent } from './lib/alarms/layer-cpa-alarm.component'; export { ArrivalCircleComponent } from './lib/navigation/layer-arrival-circle.component'; export { XTEPathComponent } from './lib/navigation/layer-xte-path.component'; export { BearingLineComponent } from './lib/navigation/layer-bearing-line.component'; +export { LaylineComponent } from './lib/navigation/layer-layline.component'; export { DirectionOfTravelComponent } from './lib/navigation/layer-dot.component'; export { VesselComponent } from './lib/vessel/layer-vessel.component'; export { VesselTrailComponent } from './lib/vessel/layer-vessel-trail.component'; @@ -130,6 +132,7 @@ const declarations = [ ArrivalCircleComponent, XTEPathComponent, BearingLineComponent, + LaylineComponent, VesselComponent, VesselTrailComponent, SKVesselsLayerComponent, diff --git a/src/app/modules/skresources/lists/chartlist.ts b/src/app/modules/skresources/lists/chartlist.ts index 056a50ff..9421e1fa 100644 --- a/src/app/modules/skresources/lists/chartlist.ts +++ b/src/app/modules/skresources/lists/chartlist.ts @@ -166,29 +166,29 @@ export class ChartListComponent { (cdkDropListDropped)="drop($event)" > @for(ch of chartList; track ch; let i = $index) { - - -
+ + +
+
+
+ {{ isLocal(ch[1].url) }} +
-
- {{ isLocal(ch[1].url) }} -
-
- {{ ch[1].name }} -
-
- drag_indicator -
+ > + {{ ch[1].name }} +
+
+ drag_indicator
- - +
+
+
}
diff --git a/src/app/modules/skresources/resource-dialogs.ts b/src/app/modules/skresources/resource-dialogs.ts index 5bcbc0b8..d00ef812 100644 --- a/src/app/modules/skresources/resource-dialogs.ts +++ b/src/app/modules/skresources/resource-dialogs.ts @@ -82,11 +82,9 @@ import { SKResourceSet } from './sets/resource-set'; Signal K Type @for(i of resourceTypeList; track i) { - - {{ i.name }} - + + {{ i.name }} + } @@ -777,60 +775,60 @@ export class AircraftPropertiesModal { (cdkDropListDropped)="drop($event)" > @for(pt of points; track pt; let i = $index) { - - -
- -
-
- @if(selIndex === i) { - flag - } + + +
+ +
+
+ @if(selIndex === i) { + flag + } +
+
+
+
Lat:
+
-
-
-
Lat:
-
-
-
-
Lon:
-
-
- @if(i < pointNames.length) { -
-
Name:
-
-
- } +
+
Lon:
+
-
- @if(data.type === 'route') { - drag_indicator - } + @if(i < pointNames.length) { +
+
Name:
+
+ }
- - +
+ @if(data.type === 'route') { + drag_indicator + } +
+
+ + }
@@ -1198,38 +1196,38 @@ export class ChartInfoDialog { @for(trk of trackList; track trk; let idx = $index) { - - -
-
- -
-
-
- {{ trk[1].feature?.properties?.name }} -
-
- {{ trk[1].feature?.properties?.description }} -
+ + +
+
+ +
+
+
+ {{ trk[1].feature?.properties?.name }}
-
- +
+ {{ trk[1].feature?.properties?.description }}
- - +
+ +
+
+ + }
`, @@ -1414,29 +1412,29 @@ export class TracksModal implements OnInit { @for(res of resList; track res; let idx = $index) { - - -
-
- + + +
+
+ +
+
+
+ {{ res.name }}
-
-
- {{ res.name }} -
-
- {{ res.description }} -
+
+ {{ res.description }}
-
- - +
+
+ + }
`, diff --git a/src/assets/help/img/settings_vessels_1.png b/src/assets/help/img/settings_vessels_1.png index 5158a0f7..a1b11e17 100644 Binary files a/src/assets/help/img/settings_vessels_1.png and b/src/assets/help/img/settings_vessels_1.png differ diff --git a/src/assets/help/index.html b/src/assets/help/index.html index 56a77e28..9beac7a2 100644 --- a/src/assets/help/index.html +++ b/src/assets/help/index.html @@ -1892,6 +1892,10 @@

settings Settings

Display Wind Vectors: Select to display wind vectors for the vessel on the map. +
  • + Display Laylines: Select to display laylines when + navigating to a destination. +
  • Heading Line Length: Select length of heading line displayed on the map.