From 9f684869ecc920b5a4caeb40919a969ed8320dec Mon Sep 17 00:00:00 2001 From: panaaj <38519157+panaaj@users.noreply.github.com> Date: Mon, 23 Oct 2023 18:28:55 +1030 Subject: [PATCH] autopilot console --- CHANGELOG.md | 9 + helper/alarms/alarms.ts | 54 ++- helper/index.ts | 30 +- helper/lib/fetch.ts | 8 +- helper/lib/types.ts | 75 --- helper/pypilot.ts | 24 +- helper/weather.ts | 10 +- package.json | 6 +- src/app/app.component.html | 45 +- src/app/app.component.ts | 52 +- src/app/app.info.ts | 9 +- src/app/app.module.ts | 6 +- .../lib/components/autopilot.component.css | 59 +++ src/app/lib/components/autopilot.component.ts | 251 ++++++++++ src/app/lib/components/course-settings.ts | 133 ----- .../dialogs/common/dialogs.component.ts | 2 +- .../dialogs/geojson-dialog.facade.ts | 10 +- .../lib/components/dialogs/geojson-dialog.ts | 3 + src/app/lib/components/dialogs/gpx/gpxlib.ts | 24 +- .../gpx/gpxload/gpxload-dialog.facade.ts | 21 +- .../dialogs/gpx/gpxload/gpxload-dialog.ts | 27 +- .../gpx/gpxsave/gpxsave-dialog.facade.ts | 11 + .../dialogs/gpx/gpxsave/gpxsave-dialog.ts | 27 +- .../components/dialogs/gpx/gpxsave/sk2gpx.ts | 6 + src/app/lib/components/dialogs/gpx/xml2js.ts | 112 +++-- .../lib/components/dialogs/playback-dialog.ts | 1 + .../components/dialogs/trail2route-dialog.ts | 8 +- .../lib/components/file-input.component.ts | 6 +- src/app/lib/components/index.ts | 1 + .../components/signalk-details.component.ts | 16 +- src/app/lib/geoutils.ts | 2 +- src/app/lib/pipes/reverse.pipe.ts | 1 + src/app/lib/services/indexeddb.ts | 207 ++++---- src/app/lib/services/info.service.ts | 4 +- src/app/lib/services/state.service.ts | 2 +- src/app/modules/alarms/alarms.facade.ts | 65 +-- src/app/modules/alarms/alarms.module.ts | 7 +- src/app/modules/alarms/alarms.ts | 458 ------------------ .../alarms/components/alarm.component.ts | 219 +++++++++ .../components/alarms-dialog.component.ts | 237 +++++++++ .../components/anchor-watch.component.html | 92 ++-- .../components/anchor-watch.component.ts | 41 +- .../components/timer-button.component.ts | 4 +- .../experiments/experiments.component.ts | 2 + .../experiments/weather/weather.module.ts | 2 +- .../components/popover/compass.component.ts | 10 +- .../components/popover/popover.component.ts | 96 ++++ .../popover/resource-popover.component.ts | 19 +- .../map/components/profiles/default/index.ts | 1 - src/app/modules/map/fb-map.component.html | 43 +- src/app/modules/map/fb-map.component.ts | 22 +- src/app/modules/map/map.module.ts | 3 + src/app/modules/map/mapconfig.ts | 13 + src/app/modules/map/ol/index.ts | 7 +- .../ol/lib/alarms/layer-alarm.component.ts | 143 ++++++ .../alarms/layer-anchor-alarm.component.ts | 8 +- .../lib/alarms/layer-cpa-alarm.component.ts | 4 +- .../interaction-modify.component.ts | 2 +- .../navigation/layer-xte-path.component.ts | 4 +- .../layer-sktarget-tracks.component.ts | 18 +- .../resources/layer-skvessels.component.ts | 4 +- src/app/modules/map/ol/lib/util.ts | 99 +--- .../signalk-preferredpaths.component.ts | 4 +- src/app/modules/settings/settings-dialog.ts | 2 +- src/app/modules/skresources/lists/aislist.ts | 6 +- .../modules/skresources/lists/chartlist.ts | 9 +- src/app/modules/skresources/lists/notelist.ts | 2 +- .../modules/skresources/lists/routelist.ts | 4 +- .../modules/skresources/lists/waypointlist.ts | 12 +- .../modules/skresources/notes/note-dialog.ts | 1 + .../skresources/notes/relatednotes-dialog.ts | 1 + .../modules/skresources/notes/safe.pipe.ts | 3 +- .../modules/skresources/resource-dialogs.ts | 18 +- .../modules/skresources/resources.service.ts | 23 +- .../modules/skresources/sets/resource-set.ts | 2 +- .../skresources/sets/resource-sets.service.ts | 4 +- .../sets/resource-upload-dialog.ts | 1 + src/app/modules/skstream/skstream.facade.ts | 7 +- src/app/modules/skstream/skstream.service.ts | 4 + src/app/modules/skstream/skstream.worker.ts | 55 ++- src/app/types/index.d.ts | 9 + src/assets/help/img/screen.png | Bin 186429 -> 194553 bytes src/assets/help/index.html | 8 + src/assets/img/alarms/mob_map.png | Bin 0 -> 770 bytes 84 files changed, 1826 insertions(+), 1234 deletions(-) delete mode 100644 helper/lib/types.ts create mode 100644 src/app/lib/components/autopilot.component.css create mode 100644 src/app/lib/components/autopilot.component.ts delete mode 100644 src/app/modules/alarms/alarms.ts create mode 100644 src/app/modules/alarms/components/alarm.component.ts create mode 100644 src/app/modules/alarms/components/alarms-dialog.component.ts create mode 100644 src/app/modules/map/ol/lib/alarms/layer-alarm.component.ts create mode 100644 src/assets/img/alarms/mob_map.png diff --git a/CHANGELOG.md b/CHANGELOG.md index f069a20d..da352fb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # CHANGELOG: Freeboard +### v2.3.0 + +- **Added**: Display a badge on menu icon when server has security enabled and client is not authenticated. +- **Added**: Autopilot console for use with built-in PyPyilot integration. +- **Added**: Display location of Man Overboard alarm on map. +- **Updated**: Align anchor watch UI to anchor-alarm API operation. +- **Updated**: Display the waypoint name when a route point is a reference to a waypoint resource. +- **Updated**: Use updated typings in `@signalk/server-api`. + ### v2.2.6 - **Fixed**: Issue where anchor watch could not be set when Signal K server had security enabled. _(Requires signalk-anchoralarm-plugin v1.13.0 or later)_ diff --git a/helper/alarms/alarms.ts b/helper/alarms/alarms.ts index cbb8c244..7a4fa895 100644 --- a/helper/alarms/alarms.ts +++ b/helper/alarms/alarms.ts @@ -1,12 +1,11 @@ // **** Signal K Standard ALARMS notifier **** import { NextFunction, Request, Response } from 'express'; import { - Notification, - DeltaMessage, - ActionResult, ALARM_METHOD, - ALARM_STATE -} from '../lib/types'; + ALARM_STATE, + Path, + PathValue +} from '@signalk/server-api'; import { FreeboardHelperApp } from '../index'; const STANDARD_ALARMS = [ @@ -65,7 +64,7 @@ const initAlarmEndpoints = () => { const r = handlePutAlarmState( 'vessels.self', - `notifications.${req.params.alarmType}`, + `notifications.${req.params.alarmType}` as Path, { message: msg, method: [ALARM_METHOD.sound, ALARM_METHOD.visual], @@ -95,7 +94,7 @@ const initAlarmEndpoints = () => { try { const r = handlePutAlarmState( 'vessels.self', - `notifications.${req.params.alarmType}`, + `notifications.${req.params.alarmType}` as Path, null ); res.status(200).json(r); @@ -112,13 +111,13 @@ const initAlarmEndpoints = () => { const handlePutAlarmState = ( context: string, - path: string, + path: Path, value: { message: string; state: ALARM_STATE; method: ALARM_METHOD[]; } -): ActionResult => { +) => { server.debug(context); server.debug(path); server.debug(JSON.stringify(value)); @@ -135,14 +134,21 @@ const handlePutAlarmState = ( const pa = path.split('.'); const alarmType = pa[pa.length - 1]; server.debug(JSON.stringify(alarmType)); - let noti: Notification | DeltaMessage; + let noti: PathValue; if (value) { - noti = new Notification( - alarmType, - buildAlarmMessage(value.message, alarmType), - value.state ?? null, - value.method ?? null - ); + const alm = buildAlarmMessage(value.message, alarmType); + noti = { + path: `notifications.${alarmType}` as Path, + value: { + state: value.state ?? null, + method: value.method ?? null, + message: alm.message + } + }; + if (alm.data) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (noti.value as any).data = alm.data; + } } else { noti = { path, @@ -164,19 +170,23 @@ const handlePutAlarmState = ( } }; -const buildAlarmMessage = (message: string, alarmType?: string): string => { - let msgAttrib = ''; +const buildAlarmMessage = (message: string, alarmType?: string) => { + let data = null; if (['mob', 'sinking'].includes(alarmType)) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const pos: any = server.getSelfPath('navigation.position'); - msgAttrib = pos ? JSON.stringify(pos?.value) : ''; + data = { + position: pos ? pos.value : null + }; } - return `${message}\n\r${msgAttrib}`; + return { + message: message, + data: data + }; }; // ** send notification delta message ** -const emitNotification = (n: Notification | DeltaMessage) => { - const msg = n instanceof Notification ? n.message : n; +const emitNotification = (msg: PathValue) => { const delta = { updates: [{ values: [msg] }] }; diff --git a/helper/index.ts b/helper/index.ts index 7c415e49..af3a8a49 100644 --- a/helper/index.ts +++ b/helper/index.ts @@ -1,11 +1,6 @@ -import { - Plugin, - PluginServerApp, - ResourceProviderRegistry -} from '@signalk/server-api'; +import { Plugin, ServerAPI } from '@signalk/server-api'; import { IRouter, Application, Request, Response } from 'express'; import { initAlarms } from './alarms/alarms'; -import { ActionResult } from './lib/types'; import { WEATHER_SERVICES, @@ -118,18 +113,7 @@ interface OpenApiPlugin extends Plugin { export interface FreeboardHelperApp extends Application, - PluginServerApp, - ResourceProviderRegistry { - statusMessage?: () => string; - error: (...msg: any) => void; - debug: (...msg: any) => void; - setPluginStatus: (pluginId: string, status?: string) => void; - setPluginError: (pluginId: string, status?: string) => void; - setProviderStatus: (providerId: string, status?: string) => void; - setProviderError: (providerId: string, status?: string) => void; - getSelfPath: (path: string) => void; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - savePluginOptions: (options: any, callback: () => void) => void; + Omit { config: { ssl: boolean; configPath: string; @@ -138,7 +122,7 @@ export interface FreeboardHelperApp }; // eslint-disable-next-line @typescript-eslint/no-explicit-any - handleMessage: (id: string | null, msg: any, version?: string) => void; + //handleMessage: (id: string | null, msg: any, version?: string) => void; streambundle: { // eslint-disable-next-line @typescript-eslint/no-explicit-any getSelfBus: (path: string | void) => any; @@ -151,8 +135,10 @@ export interface FreeboardHelperApp path: string, // eslint-disable-next-line @typescript-eslint/no-explicit-any value: any, - actionResultCallback: (actionResult: ActionResult) => void - ) => ActionResult + // eslint-disable-next-line @typescript-eslint/no-explicit-any + actionResultCallback: (actionResult: any) => void + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ) => any ) => void; } @@ -280,9 +266,11 @@ module.exports = (server: FreeboardHelperApp): OpenApiPlugin => { }, // eslint-disable-next-line @typescript-eslint/no-explicit-any setResource: (id: string, value: any) => { + server.debug(`${id}, ${value}`); throw 'Not implemented!'; }, deleteResource: (id: string) => { + server.debug(`${id}`); throw 'Not implemented!'; } } diff --git a/helper/lib/fetch.ts b/helper/lib/fetch.ts index 2f260fda..ddf83af2 100644 --- a/helper/lib/fetch.ts +++ b/helper/lib/fetch.ts @@ -1,10 +1,11 @@ import * as https from 'https'; import * as url from 'url'; import * as http from 'http'; -import { ActionResult } from './types'; // HTTP GET +// eslint-disable-next-line @typescript-eslint/no-explicit-any export const fetch = (href: string): Promise => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const opt: any = url.parse(href); opt.headers = { 'User-Agent': 'Mozilla/5.0' }; @@ -12,6 +13,7 @@ export const fetch = (href: string): Promise => { return new Promise((resolve, reject) => { req + // eslint-disable-next-line @typescript-eslint/no-explicit-any .get(opt, (res: any) => { let data = ''; res.on('data', (chunk: string) => { @@ -33,7 +35,8 @@ export const fetch = (href: string): Promise => { }; // HTTP POST -export const post = (href: string, data: string): Promise => { +export const post = (href: string, data: string) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const opt: any = url.parse(href); (opt.method = 'POST'), (opt.headers = { @@ -44,6 +47,7 @@ export const post = (href: string, data: string): Promise => { return new Promise((resolve, reject) => { const postReq = req + // eslint-disable-next-line @typescript-eslint/no-explicit-any .request(opt, (res: any) => { let resText = ''; res.on('data', (chunk: string) => { diff --git a/helper/lib/types.ts b/helper/lib/types.ts deleted file mode 100644 index 5f3dd419..00000000 --- a/helper/lib/types.ts +++ /dev/null @@ -1,75 +0,0 @@ -// ** Notifications ** -export enum ALARM_STATE { - nominal = 'nominal', - normal = 'normal', - alert = 'alert', - warn = 'warn', - alarm = 'alarm', - emergency = 'emergency' -} - -export enum ALARM_METHOD { - visual = 'visual', - sound = 'sound' -} - -// ** Server Messages ** -export interface DeltaMessage { - path: string; - value: object | string | number | null; -} - -export interface DeltaUpdate { - updates: [ - { - values: Array; - } - ]; -} - -export interface DeltaNotification extends DeltaMessage { - value: { - state: ALARM_STATE; - method: Array; - message: string; - }; -} - -export interface ActionResult { - state: string; - statusCode: number; - message?: string; - resultStatus?: number; -} - -// Class encapsulating Signal K Notification -export class Notification { - private _message: DeltaNotification = { - path: `notifications.`, - value: { - state: ALARM_STATE.alarm, - method: [ALARM_METHOD.sound, ALARM_METHOD.visual], - message: 'Alarm!' - } - }; - - constructor( - path: string, - msg: string, - state?: ALARM_STATE, - method?: ALARM_METHOD[] - ) { - this._message.path += path; - this._message.value.message = msg; - if (state) { - this._message.value.state = state; - } - if (method) { - this._message.value.method = method; - } - } - - get message(): DeltaNotification { - return this._message; - } -} diff --git a/helper/pypilot.ts b/helper/pypilot.ts index cd4cb187..3bddea7e 100644 --- a/helper/pypilot.ts +++ b/helper/pypilot.ts @@ -2,6 +2,7 @@ import { Request, Response } from 'express'; import { FreeboardHelperApp } from '.'; +import { Path, SKVersion } from '@signalk/server-api'; import { io, Socket } from 'socket.io-client'; @@ -11,6 +12,7 @@ export interface PYPILOT_CONFIG { port: number; } +// eslint-disable-next-line @typescript-eslint/no-explicit-any const apData: any = { options: { state: ['enabled', 'disabled'], @@ -322,6 +324,7 @@ const sendToPyPilot = (command: string, value: string | number | boolean) => { }; // process received pypilot update messages and send SK delta +// eslint-disable-next-line @typescript-eslint/no-explicit-any const handlePyPilotUpdateMsg = (data: any) => { // compare and send delta @@ -329,7 +332,7 @@ const handlePyPilotUpdateMsg = (data: any) => { let heading = data['ap.heading'] === false ? null : data['ap.heading'] if (heading !== apData.heading) { apData.heading = heading - emitAPDelta('target', (Math.PI /180) * apData.heading) + emitAPDelta('target' as Path, (Math.PI /180) * apData.heading) } }*/ @@ -338,7 +341,7 @@ const handlePyPilotUpdateMsg = (data: any) => { data['ap.heading_command'] === false ? null : data['ap.heading_command']; if (heading !== apData.heading_command) { apData.target = heading; - emitAPDelta('target', (Math.PI / 180) * apData.target); + emitAPDelta('target' as Path, (Math.PI / 180) * apData.target); } } @@ -346,7 +349,7 @@ const handlePyPilotUpdateMsg = (data: any) => { server.debug(`ap.mode -> data = ${JSON.stringify(data)}`); if (data['ap.mode'] !== apData.mode) { apData.mode = data['ap.mode']; - emitAPDelta('mode', apData.mode); + emitAPDelta('mode' as Path, apData.mode); } } @@ -354,27 +357,30 @@ const handlePyPilotUpdateMsg = (data: any) => { if (data['ap.enabled'] !== apData.state) { apData.state = data['ap.enabled'] ? 'enabled' : 'disabled'; apData.active = apData.state === 'enabled' ? true : false; - emitAPDelta('state', apData.state); + emitAPDelta('state' as Path, apData.state); + emitAPDelta('active' as Path, apData.active); } } }; // process received pypilot_values message and send SK delta +// eslint-disable-next-line @typescript-eslint/no-explicit-any const handlePyPilotValuesMsg = (data: any) => { // available modes if (typeof data['ap.mode'] !== undefined && data['ap.mode'].choices) { apData.options.mode = Array.isArray(data['ap.mode'].choices) ? data['ap.mode'].choices : []; - //emitAPDelta('availableModes', apData.availableModes) + //emitAPDelta('availableModes' as Path, apData.availableModes) } // available states - //emitAPDelta('availableStates', ['enabled', 'disabled']) + //emitAPDelta('availableStates' as Path, ['enabled', 'disabled']) }; // emit SK delta steering.autopilot.xxx -const emitAPDelta = (path: string, value: any) => { - const pathRoot = 'steering.autopilot'; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const emitAPDelta = (path: Path, value: any) => { + const pathRoot: Path = 'steering.autopilot' as Path; const msg = { path: `${pathRoot}.${path}`, value: value @@ -389,6 +395,6 @@ const emitAPDelta = (path: string, value: any) => { } ] }, - 'v2' + SKVersion.v2 ); }; diff --git a/helper/weather.ts b/helper/weather.ts index 8d13c503..7c4bd340 100644 --- a/helper/weather.ts +++ b/helper/weather.ts @@ -1,7 +1,11 @@ // **** Experiment: OpenWeather integration **** -import { Position } from '@signalk/server-api'; +import { + Position, + SKVersion, + ALARM_METHOD, + ALARM_STATE +} from '@signalk/server-api'; import { FreeboardHelperApp } from '.'; -import { ALARM_METHOD, ALARM_STATE } from './lib/types'; import { OpenWeather } from './lib/openweather'; import { NOAA } from './lib/noaa'; @@ -266,6 +270,6 @@ const emitWarningNotification = (warning?: SKWeatherWarning) => { { updates: [{ values: [delta] }] }, - 'v2' + SKVersion.v2 ); }; diff --git a/package.json b/package.json index 22acb2b0..2ca72578 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@signalk/freeboard-sk", - "version": "2.2.6", + "version": "2.3.0", "description": "Openlayers chart plotter implementation for Signal K", "keywords": [ "signalk-webapp", @@ -56,7 +56,7 @@ "@angular/platform-browser": "^16.2.0", "@angular/platform-browser-dynamic": "^16.2.0", "@kolkov/angular-editor": "^2.1.0", - "@signalk/server-api": "^2.0.0", + "@signalk/server-api": "^2.3.0", "@types/arcgis-rest-api": "^10.4.5", "@types/express": "^4.17.17", "@types/geojson": "^7946.0.10", @@ -92,4 +92,4 @@ "typescript": "~4.9.5", "zone.js": "~0.13.1" } -} \ No newline at end of file +} diff --git a/src/app/app.component.html b/src/app/app.component.html index 0dd91972..3fb1d82c 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -361,8 +361,21 @@  Settings - - account_circle + + + account_circle +  {{ app.data.loggedIn ? 'Change User' : 'Login' }} @@ -447,6 +460,10 @@ + +
 
+ +
+ +
 
+
+

+ +
+ +
+
Target
+
+ {{ formatTargetValue(app.data.vessels.self.autopilot.target) }} +
+
degrees
+
+ +
+
+ +
+ + + + + ` +}) +export class AutopilotComponent { + protected autoPilotModes = []; + private context = 'self'; + private deltaSub: Subscription; + + constructor( + protected app: AppInfo, + private signalk: SignalKClient, + private stream: SKStreamFacade, + private cdr: ChangeDetectorRef + ) {} + + ngOnInit() { + this.deltaSub = this.stream.delta$().subscribe((e: UpdateMessage) => { + if (e.action === 'update') { + this.cdr.detectChanges(); + } + }); + + this.signalk.api + .get(this.app.skApiVersion, 'vessels/self/steering/autopilot') + .subscribe( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (val: any) => { + if ( + val.options && + val.options.mode && + Array.isArray(val.options.mode) + ) { + this.autoPilotModes = val.options.mode; + } + }, + () => { + this.autoPilotModes = []; + this.app.data.autopilot.hasApi = false; + } + ); + } + + ngOnDestroy() { + this.deltaSub.unsubscribe(); + } + + targetAdjust(value: number) { + const rad = value ? Convert.degreesToRadians(value) : 0; + if (!rad) { + return; + } + this.signalk.api + .put( + this.app.skApiVersion, + 'vessels/self/steering/autopilot/target/adjust', + { value: rad } + ) + .subscribe( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + () => { + this.app.debug(`Target adjusted: ${value} deg.`); + }, + (err: HttpErrorResponse) => { + this.app.debug(err); + } + ); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + dragEventHandler(e: any, type: string) { + this.app.debug( + 'e:', + e, + 'type:', + type, + e.event.srcElement.clientLeft, + e.event.srcElement.clientTop + ); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onFormChange(e: any) { + if (e.source) { + if (e.source.id === 'autopilotenable') { + // toggle autopilot enable + this.signalk.api + .post( + this.app.skApiVersion, + `vessels/self/${ + e.checked + ? 'steering/autopilot/engage' + : 'steering/autopilot/disengage' + }`, + null + ) + .subscribe( + () => undefined, + (error: HttpErrorResponse) => { + let msg = `Error setting Autopilot state!\n`; + if (error.status === 403) { + msg += 'Unauthorised: Please login.'; + } + this.app.showAlert(`Error (${error.status}):`, msg); + } + ); + } + if (e.source.id === 'autopilotmode') { + // autopilot mode + this.signalk.api + .putWithContext( + this.app.skApiVersion, + this.context, + 'steering/autopilot/mode', + { + value: e.value + } + ) + .subscribe( + () => undefined, + (error: HttpErrorResponse) => { + let msg = `Error setting Autopilot mode!\n`; + if (error.status === 403) { + msg += 'Unauthorised: Please login.'; + } + this.app.showAlert(`Error (${error.status}):`, msg); + } + ); + } + } + } + + formatTargetValue(value: number) { + if (value) { + return Convert.radiansToDegrees(value)?.toFixed(1); + } else return '--'; + } +} diff --git a/src/app/lib/components/course-settings.ts b/src/app/lib/components/course-settings.ts index 877df8fa..44597016 100644 --- a/src/app/lib/components/course-settings.ts +++ b/src/app/lib/components/course-settings.ts @@ -24,11 +24,6 @@ import { Convert } from 'src/app/lib/convert'; import { SKStreamFacade } from 'src/app/modules'; import { UpdateMessage } from 'src/app/types'; import { Subscription } from 'rxjs'; -import { - DateAdapter, - MAT_DATE_FORMATS, - MAT_DATE_LOCALE -} from '@angular/material/core'; /********* Course Settings Modal ******** data: { @@ -180,59 +175,6 @@ import { - -
- Auto-Pilot -
-
- - Enable - -
- - - Mode - - - {{ i }} - - - - - - Target (degrees) - - -
-
`, styles: [ @@ -262,8 +204,6 @@ export class CourseSettingsModal implements OnInit { seconds: '00', datetime: null }; - protected autoPilotModes = []; - protected hasAutoPilot = false; private context = 'self'; /*this.app.data.vessels.activeId ? this.app.data.vessels.activeId @@ -317,26 +257,6 @@ export class CourseSettingsModal implements OnInit { ).slice(-2); } }); - - this.signalk.api - .get(this.app.skApiVersion, 'vessels/self/steering/autopilot') - .subscribe( - (val) => { - console.log(val); - if ( - val.options && - val.options.mode && - Array.isArray(val.options.mode) - ) { - this.autoPilotModes = val.options.mode; - this.hasAutoPilot = true; - } - }, - () => { - this.autoPilotModes = []; - this.hasAutoPilot = false; - } - ); } ngOnDestroy() { @@ -392,53 +312,6 @@ export class CourseSettingsModal implements OnInit { ); } } - if (e.source) { - if (e.source.id === 'autopilotenable') { - // toggle autopilot enable - this.signalk.api - .post( - this.app.skApiVersion, - `vessels/self/${ - e.checked - ? 'steering/autopilot/engage' - : 'steering/autopilot/disengage' - }`, - null - ) - .subscribe( - () => undefined, - (error: HttpErrorResponse) => { - let msg = `Error setting Autopilot state!\n`; - if (error.status === 403) { - msg += 'Unauthorised: Please login.'; - } - this.app.showAlert(`Error (${error.status}):`, msg); - } - ); - } - if (e.source.id === 'autopilotmode') { - // autopilot mode - this.signalk.api - .putWithContext( - this.app.skApiVersion, - this.context, - 'steering/autopilot/mode', - { - value: e.value - } - ) - .subscribe( - () => undefined, - (error: HttpErrorResponse) => { - let msg = `Error setting Autopilot mode!\n`; - if (error.status === 403) { - msg += 'Unauthorised: Please login.'; - } - this.app.showAlert(`Error (${error.status}):`, msg); - } - ); - } - } } toggleTargetArrival(e) { @@ -490,12 +363,6 @@ export class CourseSettingsModal implements OnInit { } } - formatTargetValue(value: number) { - if (value) { - return Convert.radiansToDegrees(value)?.toFixed(1); - } else return '--'; - } - formatArrivalTime(): string { let ts = ''; this.arrivalData.datetime.setHours(parseInt(this.arrivalData.hour)); diff --git a/src/app/lib/components/dialogs/common/dialogs.component.ts b/src/app/lib/components/dialogs/common/dialogs.component.ts index 7a85b745..378dfd65 100644 --- a/src/app/lib/components/dialogs/common/dialogs.component.ts +++ b/src/app/lib/components/dialogs/common/dialogs.component.ts @@ -398,7 +398,7 @@ export class LoginDialog implements OnInit { } keyUp(e, u, p) { - if (e.key == 'Enter') { + if (e.key === 'Enter') { this.login(u.value, p.value); } } diff --git a/src/app/lib/components/dialogs/geojson-dialog.facade.ts b/src/app/lib/components/dialogs/geojson-dialog.facade.ts index 7510b6ec..1e6845a4 100644 --- a/src/app/lib/components/dialogs/geojson-dialog.facade.ts +++ b/src/app/lib/components/dialogs/geojson-dialog.facade.ts @@ -100,7 +100,7 @@ export class GeoJSONLoadFacade { // ** check all submissions are resolved and emit upload$ checkComplete() { - if (this.subCount == 0) { + if (this.subCount === 0) { this.uploadSource.next(this.errorCount); this.app.debug(`GeoJSONLoad: complete: ${this.errorCount}`); } @@ -136,7 +136,7 @@ export class GeoJSONLoadFacade { .subscribe( (r) => { this.subCount--; - if (r['state'] == 'COMPLETED') { + if (r['state'] === 'COMPLETED') { this.app.debug('SUCCESS: GeoJSON Route added.'); } else { this.errorCount++; @@ -181,7 +181,7 @@ export class GeoJSONLoadFacade { .subscribe( (r) => { this.subCount--; - if (r['state'] == 'COMPLETED') { + if (r['state'] === 'COMPLETED') { this.app.debug('SUCCESS: GeoJSON Waypoint added.'); } else { this.errorCount++; @@ -217,7 +217,7 @@ export class GeoJSONLoadFacade { .subscribe( (r) => { this.subCount--; - if (r['state'] == 'COMPLETED') { + if (r['state'] === 'COMPLETED') { this.app.debug('SUCCESS: GeoJSON Track added.'); } else { this.errorCount++; @@ -260,7 +260,7 @@ export class GeoJSONLoadFacade { .subscribe( (r) => { this.subCount--; - if (r['state'] == 'COMPLETED') { + if (r['state'] === 'COMPLETED') { this.app.debug('SUCCESS: GeoJSON Region added.'); } else { this.errorCount++; diff --git a/src/app/lib/components/dialogs/geojson-dialog.ts b/src/app/lib/components/dialogs/geojson-dialog.ts index a6da21bc..e11ca27e 100644 --- a/src/app/lib/components/dialogs/geojson-dialog.ts +++ b/src/app/lib/components/dialogs/geojson-dialog.ts @@ -60,6 +60,7 @@ export class GeoJSONImportDialog implements OnInit { public app: AppInfo, public facade: GeoJSONLoadFacade, public dialogRef: MatDialogRef, + // eslint-disable-next-line @typescript-eslint/no-explicit-any @Inject(MAT_DIALOG_DATA) public data: any ) {} @@ -84,6 +85,7 @@ export class GeoJSONImportDialog implements OnInit { this.facade.uploadToServer(this.geoData.value); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any parseFileData(fileData: any) { this.geoData.value = this.facade.validate(fileData); if (!this.geoData.value) { @@ -91,6 +93,7 @@ export class GeoJSONImportDialog implements OnInit { this.display.notValid = true; return; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any this.geoData.value.features.forEach((f: any) => { if (f.type && f.type === 'Feature' && f.geometry && f.geometry.type) { switch (f.geometry.type) { diff --git a/src/app/lib/components/dialogs/gpx/gpxlib.ts b/src/app/lib/components/dialogs/gpx/gpxlib.ts index 25c27811..9682b62a 100644 --- a/src/app/lib/components/dialogs/gpx/gpxlib.ts +++ b/src/app/lib/components/dialogs/gpx/gpxlib.ts @@ -13,6 +13,7 @@ export class GPX { public wpt: Array; // ** array of waypoints public rte: Array; // ** array of routes public trk: Array; // ** array of tracks + // eslint-disable-next-line @typescript-eslint/no-explicit-any public extensions: any; public metadata: GPXMetadataType; @@ -181,6 +182,7 @@ export class GPX { } // ** return formatted ptType XML for supplied GPXWaypoint object + // eslint-disable-next-line @typescript-eslint/no-explicit-any ptToXML(pt: any, tag: any) { let pad = ''; let padLevel = 2; @@ -238,9 +240,10 @@ export class GPX { } // ** return formatted XML for supplied .extension object + // eslint-disable-next-line @typescript-eslint/no-explicit-any extensionsToXML(ext: any, padLevel = 1) { let xml = ''; - if (ext && Object.keys(ext).length != 0) { + if (ext && Object.keys(ext).length !== 0) { const pad = '\t\t\t\t\t\t\t\t\t'.slice(0 - padLevel); xml += `${pad}\r\n` + @@ -253,7 +256,7 @@ export class GPX { /** parse GPX string contents and fill this.data **/ parse(gpxstr: string) { // ** check for valid file contents ** - if (!gpxstr || gpxstr.indexOf(' { @@ -363,6 +367,7 @@ export class GPX { } //** parse rte xml element object array into this.rte array + // eslint-disable-next-line @typescript-eslint/no-explicit-any parseRoutes(routes: any[]) { if (Array.isArray(routes)) { routes.forEach((r) => { @@ -378,6 +383,7 @@ export class GPX { } //** parse trk xml element object array into this.trk array + // eslint-disable-next-line @typescript-eslint/no-explicit-any parseTracks(tracks: any[]) { if (Array.isArray(tracks)) { tracks.forEach((t) => { @@ -393,6 +399,7 @@ export class GPX { } //** updateBounds ** + // eslint-disable-next-line @typescript-eslint/no-explicit-any updateBounds(pt: any) { this.metadata.bounds.minLat = pt.lat < this.metadata.bounds.minLat @@ -413,6 +420,7 @@ export class GPX { } // ** return extension data + // eslint-disable-next-line @typescript-eslint/no-explicit-any parseExtensions(xext: any) { return xext; /* @@ -423,6 +431,7 @@ export class GPX { } // ** return array of GPXLinkType objects from x2js + // eslint-disable-next-line @typescript-eslint/no-explicit-any parseLinks(xlink: any): Array { const links = []; if (!xlink) { @@ -448,6 +457,7 @@ export class GPX { } // ** return GPXRoute object for supplied rte xml object + // eslint-disable-next-line @typescript-eslint/no-explicit-any toRte(xmlrte: any): GPXRoute { const rte = new GPXRoute(); @@ -470,6 +480,7 @@ export class GPX { } // ** return GPXWaypoint object for supplied wpt xml object + // eslint-disable-next-line @typescript-eslint/no-explicit-any toWpt(xmlwpt: any): GPXWaypoint { const pt = new GPXWaypoint(); pt.lat = parseFloat(xmlwpt._lat) || 0; @@ -507,6 +518,7 @@ export class GPX { } // ** return GPXTrack object for supplied trk xml object + // eslint-disable-next-line @typescript-eslint/no-explicit-any toTrk(xmltrk: any): GPXTrack { const tk = new GPXTrack(); @@ -548,6 +560,7 @@ export class GPX { } // ** return GPXWaypoint array for supplied pt xml object(s) + // eslint-disable-next-line @typescript-eslint/no-explicit-any toPtArray(xpts: any[]): Array { const pts = []; if (Array.isArray(xpts)) { @@ -569,6 +582,7 @@ export class GPX { * name: extension path/name in dotted notation e.g. signalk.uuid * value: extension value * ************************/ + // eslint-disable-next-line @typescript-eslint/no-explicit-any setExtension(ele: any, name: string, value: any) { if (!ele.extensions) { ele.extensions = {}; @@ -600,6 +614,7 @@ export class GPXWaypoint { public link: Array = []; public sym: string; public type: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any public fix: any; public sat: number; public hdop: number; @@ -607,6 +622,7 @@ export class GPXWaypoint { public pdop: number; public ageOfGpsData: number; public dgpsid: number; + // eslint-disable-next-line @typescript-eslint/no-explicit-any public extensions: { [key: string]: any } = {}; constructor(lat?: number, lon?: number) { @@ -624,6 +640,7 @@ export class GPXRoute { public link: Array = []; public number: number; public type: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any public extensions: { [key: string]: any } = {}; public rtept: Array = []; @@ -641,6 +658,7 @@ export class GPXTrack { public link: Array = []; public number: number; public type: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any public extensions: { [key: string]: any } = {}; public trkseg: Array = []; @@ -651,6 +669,7 @@ export class GPXTrack { export class GPXTrackSegment { public trkpt: Array = []; + // eslint-disable-next-line @typescript-eslint/no-explicit-any public extensions: { [key: string]: any } = {}; //constructor() { } } @@ -675,6 +694,7 @@ export class GPXMetadataType { public time: string = new Date().toISOString(); public keywords = ''; public bounds: GPXBoundsType = new GPXBoundsType(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any public extensions: { [key: string]: any } = {}; //constructor() { } } diff --git a/src/app/lib/components/dialogs/gpx/gpxload/gpxload-dialog.facade.ts b/src/app/lib/components/dialogs/gpx/gpxload/gpxload-dialog.facade.ts index 4953cf5c..4bb4d13b 100644 --- a/src/app/lib/components/dialogs/gpx/gpxload/gpxload-dialog.facade.ts +++ b/src/app/lib/components/dialogs/gpx/gpxload/gpxload-dialog.facade.ts @@ -13,7 +13,6 @@ import { GPXTrack, GPXTrackSegment } from '../gpxlib'; -import { HttpErrorResponse } from '@angular/common/http'; @Injectable({ providedIn: 'root' }) export class GPXLoadFacade { @@ -58,7 +57,7 @@ export class GPXLoadFacade { let idx = 1; this.gpx.rte.forEach((r) => { gpxData.routes.push({ - name: r['name'] != '' ? r['name'] : `Rte: ${idx}`, + name: r['name'] !== '' ? r['name'] : `Rte: ${idx}`, description: r['desc'] ? r['desc'] : r['cmt'] ? r['cmt'] : '', wptcount: r['rtept'].length }); @@ -109,7 +108,7 @@ export class GPXLoadFacade { // ** check all submissions are resolved and emit result$ checkComplete() { - if (this.subCount == 0) { + if (this.subCount === 0) { this.app.saveConfig(); this.resultSource.next(this.errorCount); this.app.debug(`GPXLoad: complete: ${this.errorCount}`); @@ -155,7 +154,7 @@ export class GPXLoadFacade { .subscribe( (r) => { this.subCount--; - if (Math.floor(r['statusCode'] / 100) == 2) { + if (Math.floor(r['statusCode'] / 100) === 2) { this.app.debug('SUCCESS: Route added.'); this.app.config.selections.routes.push(skObj[0]); } else { @@ -163,7 +162,7 @@ export class GPXLoadFacade { } this.checkComplete(); }, - (err: HttpErrorResponse) => { + () => { this.errorCount++; this.subCount--; this.checkComplete(); @@ -178,10 +177,10 @@ export class GPXLoadFacade { if (pt.ele) { wpt.feature.geometry.coordinates.push(pt.ele); } - if (pt.name && pt.name.length != 0) { + if (pt.name && pt.name.length !== 0) { wpt.name = pt.name; } - if (pt.desc && pt.desc.length != 0) { + if (pt.desc && pt.desc.length !== 0) { wpt.description = pt.desc; } if (pt.cmt) { @@ -231,7 +230,7 @@ export class GPXLoadFacade { .subscribe( (r) => { this.subCount--; - if (Math.floor(r['statusCode'] / 100) == 2) { + if (Math.floor(r['statusCode'] / 100) === 2) { this.app.debug('SUCCESS: Waypoint added.'); this.app.config.selections.waypoints.push(wptObj[0]); } else { @@ -266,14 +265,14 @@ export class GPXLoadFacade { trk.feature.geometry.coordinates.push(line); }); } - if (gpxtrk.name && gpxtrk.name.length != 0) { + if (gpxtrk.name && gpxtrk.name.length !== 0) { trk.feature.properties['name'] = gpxtrk.name; } else { trk.feature.properties['name'] = `gpxtrk #${Math.random() .toString() .slice(-5)}`; } - if (gpxtrk.desc && gpxtrk.desc.length != 0) { + if (gpxtrk.desc && gpxtrk.desc.length !== 0) { trk.feature.properties['description'] = gpxtrk.desc; } if (gpxtrk.cmt) { @@ -292,7 +291,7 @@ export class GPXLoadFacade { .subscribe( (r) => { this.subCount--; - if (Math.floor(r['statusCode'] / 100) == 2) { + if (Math.floor(r['statusCode'] / 100) === 2) { this.app.debug('SUCCESS: Track added.'); } else { this.errorCount++; diff --git a/src/app/lib/components/dialogs/gpx/gpxload/gpxload-dialog.ts b/src/app/lib/components/dialogs/gpx/gpxload/gpxload-dialog.ts index 24ba7445..4aeb2da6 100644 --- a/src/app/lib/components/dialogs/gpx/gpxload/gpxload-dialog.ts +++ b/src/app/lib/components/dialogs/gpx/gpxload/gpxload-dialog.ts @@ -45,6 +45,7 @@ export class GPXImportDialog implements OnInit { public app: AppInfo, public facade: GPXLoadFacade, public dialogRef: MatDialogRef, + // eslint-disable-next-line @typescript-eslint/no-explicit-any @Inject(MAT_DIALOG_DATA) public data: any ) {} @@ -101,7 +102,7 @@ export class GPXImportDialog implements OnInit { this.gpxData.routes.forEach(() => { this.selRoutes.push(false); }); - if (this.selRoutes.length == 1) { + if (this.selRoutes.length === 1) { this.selRoutes[0] = true; this.display.allRoutesChecked = true; this.display.expand.routes = true; @@ -119,7 +120,7 @@ export class GPXImportDialog implements OnInit { // ** select Route idx=-1 -> check all checkRte(checked: boolean, idx = -1) { let selcount = 0; - if (idx != -1) { + if (idx !== -1) { this.selRoutes[idx] = checked; this.display.loadRoutesOK = checked; for (const c of this.selRoutes) { @@ -127,9 +128,9 @@ export class GPXImportDialog implements OnInit { selcount++; } } - this.display.loadRoutesOK = selcount != 0 ? true : false; + this.display.loadRoutesOK = selcount !== 0 ? true : false; this.display.selCount.routes = selcount; - this.display.allRoutesChecked = selcount == this.selRoutes.length; + this.display.allRoutesChecked = selcount === this.selRoutes.length; } else { for (let i = 0; i < this.selRoutes.length; i++) { this.selRoutes[i] = checked; @@ -139,13 +140,13 @@ export class GPXImportDialog implements OnInit { this.display.selCount.routes = checked ? this.selRoutes.length : 0; } this.display.someRteChecked = - this.display.allRoutesChecked || selcount == 0 ? false : true; + this.display.allRoutesChecked || selcount === 0 ? false : true; } // ** select Waypoint idx=-1 -> check all checkWpt(checked: boolean, idx = -1) { let selcount = 0; - if (idx != -1) { + if (idx !== -1) { this.selWaypoints[idx] = checked; this.display.loadWaypointsOK = checked; for (const c of this.selWaypoints) { @@ -153,9 +154,9 @@ export class GPXImportDialog implements OnInit { selcount++; } } - this.display.loadWaypointsOK = selcount != 0 ? true : false; + this.display.loadWaypointsOK = selcount !== 0 ? true : false; this.display.selCount.waypoints = selcount; - this.display.allWaypointsChecked = selcount == this.selWaypoints.length; + this.display.allWaypointsChecked = selcount === this.selWaypoints.length; } else { for (let i = 0; i < this.selWaypoints.length; i++) { this.selWaypoints[i] = checked; @@ -165,13 +166,13 @@ export class GPXImportDialog implements OnInit { this.display.selCount.waypoints = checked ? this.selWaypoints.length : 0; } this.display.someWptChecked = - this.display.allWaypointsChecked || selcount == 0 ? false : true; + this.display.allWaypointsChecked || selcount === 0 ? false : true; } // ** select Track idx=-1 -> check all checkTrk(checked: boolean, idx = -1) { let selcount = 0; - if (idx != -1) { + if (idx !== -1) { this.selTracks[idx] = checked; this.display.loadTracksOK = checked; for (const c of this.selTracks) { @@ -179,9 +180,9 @@ export class GPXImportDialog implements OnInit { selcount++; } } - this.display.loadTracksOK = selcount != 0 ? true : false; + this.display.loadTracksOK = selcount !== 0 ? true : false; this.display.selCount.tracks = selcount; - this.display.allTracksChecked = selcount == this.selTracks.length; + this.display.allTracksChecked = selcount === this.selTracks.length; } else { for (let i = 0; i < this.selTracks.length; i++) { this.selTracks[i] = checked; @@ -191,6 +192,6 @@ export class GPXImportDialog implements OnInit { this.display.selCount.tracks = checked ? this.selTracks.length : 0; } this.display.someTrkChecked = - this.display.allTracksChecked || selcount == 0 ? false : true; + this.display.allTracksChecked || selcount === 0 ? false : true; } } diff --git a/src/app/lib/components/dialogs/gpx/gpxsave/gpxsave-dialog.facade.ts b/src/app/lib/components/dialogs/gpx/gpxsave/gpxsave-dialog.facade.ts index b477d502..ddff8e64 100644 --- a/src/app/lib/components/dialogs/gpx/gpxsave/gpxsave-dialog.facade.ts +++ b/src/app/lib/components/dialogs/gpx/gpxsave/gpxsave-dialog.facade.ts @@ -10,6 +10,7 @@ import { SignalKClient } from 'signalk-client-angular'; export class GPXSaveFacade { // **************** ATTRIBUTES *************************** private resultSource: Subject; + // eslint-disable-next-line @typescript-eslint/no-explicit-any public result$: Observable; private sk2gpx: SK2GPX; public hasFSA: boolean; @@ -28,6 +29,7 @@ export class GPXSaveFacade { } // ** prepare resource data + // eslint-disable-next-line @typescript-eslint/no-explicit-any prepData(data: { [key: string]: any }) { const resData = { routes: [], @@ -36,12 +38,14 @@ export class GPXSaveFacade { }; let idx = 1; + // eslint-disable-next-line @typescript-eslint/no-explicit-any resData.routes = data.routes.map((r: any[]) => { const rte = r[1]; rte.feature.id = r[0]; return rte; }); idx = 1; + // eslint-disable-next-line @typescript-eslint/no-explicit-any resData.waypoints = data.waypoints.map((w: any[]) => { const wpt = w[1]; wpt.feature.id = w[0]; @@ -54,11 +58,15 @@ export class GPXSaveFacade { } // ** save selected resources to GPX file ** + // eslint-disable-next-line @typescript-eslint/no-explicit-any saveToFile(res: any, selections: any) { this.sk2gpx = new SK2GPX(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const skroutes: { [key: string]: any } = {}; + // eslint-disable-next-line @typescript-eslint/no-explicit-any const skwaypoints: { [key: string]: any } = {}; + // eslint-disable-next-line @typescript-eslint/no-explicit-any const sktracks: { [key: string]: any } = {}; for (let i = 0; i < selections.rte.selected.length; i++) { @@ -110,6 +118,7 @@ export class GPXSaveFacade { // Using fileSystem Access API (https) fsaSaveFile() { + // eslint-disable-next-line @typescript-eslint/no-explicit-any (window as any) .showSaveFilePicker({ types: [ @@ -119,8 +128,10 @@ export class GPXSaveFacade { } ] }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any .then((h: any) => { h.createWritable() + // eslint-disable-next-line @typescript-eslint/no-explicit-any .then((writable: any) => { const blob = new Blob([this.sk2gpx.toXML()]); writable.write(blob).then(() => { diff --git a/src/app/lib/components/dialogs/gpx/gpxsave/gpxsave-dialog.ts b/src/app/lib/components/dialogs/gpx/gpxsave/gpxsave-dialog.ts index b82530e6..b43fc761 100644 --- a/src/app/lib/components/dialogs/gpx/gpxsave/gpxsave-dialog.ts +++ b/src/app/lib/components/dialogs/gpx/gpxsave/gpxsave-dialog.ts @@ -44,6 +44,7 @@ export class GPXExportDialog implements OnInit { public app: AppInfo, public facade: GPXSaveFacade, public dialogRef: MatDialogRef, + // eslint-disable-next-line @typescript-eslint/no-explicit-any @Inject(MAT_DIALOG_DATA) public data: any ) {} @@ -91,7 +92,7 @@ export class GPXExportDialog implements OnInit { this.resData.routes.forEach(() => { this.selRoutes.push(false); }); - if (this.selRoutes.length == 1) { + if (this.selRoutes.length === 1) { this.selRoutes[0] = true; this.display.allRoutesChecked = true; this.display.expand.routes = true; @@ -108,7 +109,7 @@ export class GPXExportDialog implements OnInit { // ** select Route idx=-1 -> check all checkRte(checked: boolean, idx = -1) { let selcount = 0; - if (idx != -1) { + if (idx !== -1) { this.selRoutes[idx] = checked; this.display.saveRoutesOK = checked; for (const c of this.selRoutes) { @@ -116,9 +117,9 @@ export class GPXExportDialog implements OnInit { selcount++; } } - this.display.saveRoutesOK = selcount != 0 ? true : false; + this.display.saveRoutesOK = selcount !== 0 ? true : false; this.display.selCount.routes = selcount; - this.display.allRoutesChecked = selcount == this.selRoutes.length; + this.display.allRoutesChecked = selcount === this.selRoutes.length; } else { for (let i = 0; i < this.selRoutes.length; i++) { this.selRoutes[i] = checked; @@ -128,13 +129,13 @@ export class GPXExportDialog implements OnInit { this.display.selCount.routes = checked ? this.selRoutes.length : 0; } this.display.someRteChecked = - this.display.allRoutesChecked || selcount == 0 ? false : true; + this.display.allRoutesChecked || selcount === 0 ? false : true; } // ** select Waypoint idx=-1 -> check all checkWpt(checked: boolean, idx = -1) { let selcount = 0; - if (idx != -1) { + if (idx !== -1) { this.selWaypoints[idx] = checked; this.display.saveWaypointsOK = checked; for (const c of this.selWaypoints) { @@ -142,9 +143,9 @@ export class GPXExportDialog implements OnInit { selcount++; } } - this.display.saveWaypointsOK = selcount != 0 ? true : false; + this.display.saveWaypointsOK = selcount !== 0 ? true : false; this.display.selCount.waypoints = selcount; - this.display.allWaypointsChecked = selcount == this.selWaypoints.length; + this.display.allWaypointsChecked = selcount === this.selWaypoints.length; } else { for (let i = 0; i < this.selWaypoints.length; i++) { this.selWaypoints[i] = checked; @@ -154,13 +155,13 @@ export class GPXExportDialog implements OnInit { this.display.selCount.waypoints = checked ? this.selWaypoints.length : 0; } this.display.someWptChecked = - this.display.allWaypointsChecked || selcount == 0 ? false : true; + this.display.allWaypointsChecked || selcount === 0 ? false : true; } // ** select Track idx=-1 -> check all checkTrk(checked: boolean, idx = -1) { let selcount = 0; - if (idx != -1) { + if (idx !== -1) { this.selTracks[idx] = checked; this.display.saveTracksOK = checked; for (const c of this.selTracks) { @@ -168,9 +169,9 @@ export class GPXExportDialog implements OnInit { selcount++; } } - this.display.saveTracksOK = selcount != 0 ? true : false; + this.display.saveTracksOK = selcount !== 0 ? true : false; this.display.selCount.tracks = selcount; - this.display.allTracksChecked = selcount == this.selTracks.length; + this.display.allTracksChecked = selcount === this.selTracks.length; } else { for (let i = 0; i < this.selTracks.length; i++) { this.selTracks[i] = checked; @@ -180,6 +181,6 @@ export class GPXExportDialog implements OnInit { this.display.selCount.tracks = checked ? this.selTracks.length : 0; } this.display.someTrkChecked = - this.display.allTracksChecked || selcount == 0 ? false : true; + this.display.allTracksChecked || selcount === 0 ? false : true; } } diff --git a/src/app/lib/components/dialogs/gpx/gpxsave/sk2gpx.ts b/src/app/lib/components/dialogs/gpx/gpxsave/sk2gpx.ts index 8d4898c7..a9f92654 100644 --- a/src/app/lib/components/dialogs/gpx/gpxsave/sk2gpx.ts +++ b/src/app/lib/components/dialogs/gpx/gpxsave/sk2gpx.ts @@ -29,6 +29,7 @@ export class SK2GPX { /** add GPXRoutes to GPX object from supplied SK resources * add only routes with supplied ids (add all if ids=undefined) */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any setRoutes(routes: { [key: string]: any }, ids?: string[]) { if (!ids) { // ** process all @@ -46,6 +47,7 @@ export class SK2GPX { } // ** return GPXRoute from SK Route wpt: waypoints to get start, end details + // eslint-disable-next-line @typescript-eslint/no-explicit-any packageRoute(uuid: string, r: any) { const rte = new GPXRoute(); rte.extensions['signalk'] = {}; @@ -83,6 +85,7 @@ export class SK2GPX { /** add GPXWaypoints to GPX object from supplied SK waypoints * add only waypoints with supplied ids (add all if ids=null) */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any setWaypoints(waypoints: { [key: string]: any }, ids?: string[]) { if (!ids) { // ** process all @@ -100,6 +103,7 @@ export class SK2GPX { } // ** return GPXWaypoint from SK Waypoint + // eslint-disable-next-line @typescript-eslint/no-explicit-any packageWaypoint(uuid: string, w: any) { const wpt = new GPXWaypoint(); wpt.extensions = wpt.extensions || {}; @@ -130,6 +134,7 @@ export class SK2GPX { /** add GPXTracks to GPX object from supplied SK tracks * add only tracks with supplied ids (add all if ids=null) */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any setTracks(tracks: { [key: string]: any }, ids?: string[]) { if (!ids) { // ** process all @@ -147,6 +152,7 @@ export class SK2GPX { } // ** return GPXTrack from SK Track + // eslint-disable-next-line @typescript-eslint/no-explicit-any packageTrack(uuid: string, t: any) { const trk = new GPXTrack(); trk.extensions = trk.extensions || {}; diff --git a/src/app/lib/components/dialogs/gpx/xml2js.ts b/src/app/lib/components/dialogs/gpx/xml2js.ts index 9523018a..dcf414a8 100644 --- a/src/app/lib/components/dialogs/gpx/xml2js.ts +++ b/src/app/lib/components/dialogs/gpx/xml2js.ts @@ -10,8 +10,10 @@ enum DOMNodeTypes { } export class XML2JS { + // eslint-disable-next-line @typescript-eslint/no-explicit-any private config: any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any constructor(config?: any) { this.config = config || {}; this.initConfigDefaults(); @@ -51,23 +53,25 @@ export class XML2JS { } } + // eslint-disable-next-line @typescript-eslint/no-explicit-any private getNodeLocalName(node: any) { let nodeLocalName = node.localName; - if (nodeLocalName == null) + if (nodeLocalName === null) // Yeah, this is IE!! nodeLocalName = node.baseName; - if (nodeLocalName == null || nodeLocalName == '') + if (nodeLocalName === null || nodeLocalName === '') // =="" is IE too nodeLocalName = node.nodeName; return nodeLocalName; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any private getNodePrefix(node: any) { return node.prefix; } private escapeXmlChars(str: string) { - if (typeof str == 'string') + if (typeof str === 'string') return str .replace(/&/g, '&') .replace(/ 0 ) { return this.checkInStdFiltersArrayForm( @@ -192,19 +196,19 @@ export class XML2JS { } private parseDOMChildren(node, path?) { - if (node.nodeType == DOMNodeTypes.DOCUMENT_NODE) { + if (node.nodeType === DOMNodeTypes.DOCUMENT_NODE) { const result = new Object(); const nodeChildren = node.childNodes; // Alternative for firstElementChild which is not supported in some environments for (let cidx = 0; cidx < nodeChildren.length; cidx++) { const child = nodeChildren.item(cidx); - if (child.nodeType == DOMNodeTypes.ELEMENT_NODE) { + if (child.nodeType === DOMNodeTypes.ELEMENT_NODE) { const childName = this.getNodeLocalName(child); result[childName] = this.parseDOMChildren(child, childName); } } return result; - } else if (node.nodeType == DOMNodeTypes.ELEMENT_NODE) { + } else if (node.nodeType === DOMNodeTypes.ELEMENT_NODE) { let result = new Object(); result['__cnt'] = 0; const nodeChildren = node.childNodes; @@ -215,7 +219,7 @@ export class XML2JS { const child = nodeChildren.item(cidx); // nodeChildren[cidx]; childName = this.getNodeLocalName(child); - if (child.nodeType != DOMNodeTypes.COMMENT_NODE) { + if (child.nodeType !== DOMNodeTypes.COMMENT_NODE) { const childPath = path + '.' + childName; if ( this.checkXmlElementsFilter( @@ -226,11 +230,11 @@ export class XML2JS { ) ) { result['__cnt']++; - if (result[childName] == null) { + if (result[childName] === null) { result[childName] = this.parseDOMChildren(child, childPath); this.toArrayAccessForm(result, childName, childPath); } else { - if (result[childName] != null) { + if (result[childName] !== null) { if (!(result[childName] instanceof Array)) { result[childName] = [result[childName]]; this.toArrayAccessForm(result, childName, childPath); @@ -252,12 +256,12 @@ export class XML2JS { // Node namespace prefix const nodePrefix = this.getNodePrefix(node); - if (nodePrefix != null && nodePrefix != '') { + if (nodePrefix !== null && nodePrefix !== '') { result['__cnt']++; result['__prefix'] = nodePrefix; } - if (result['#text'] != null) { + if (result['#text'] !== null) { result['__text'] = result['#text']; if (result['__text'] instanceof Array) { result['__text'] = result['__text'].join('\n'); @@ -267,7 +271,7 @@ export class XML2JS { if (this.config.stripWhitespaces) result['__text'] = result['__text'].trim(); delete result['#text']; - if (this.config.arrayAccessForm == 'property') + if (this.config.arrayAccessForm === 'property') delete result['#text_asArray']; result['__text'] = this.checkFromXmlDateTimePaths( result['__text'], @@ -275,31 +279,31 @@ export class XML2JS { path + '.' + childName ); } - if (result['#cdata-section'] != null) { + if (result['#cdata-section'] !== null) { result['__cdata'] = result['#cdata-section']; delete result['#cdata-section']; - if (this.config.arrayAccessForm == 'property') + if (this.config.arrayAccessForm === 'property') delete result['#cdata-section_asArray']; } - if (result['__cnt'] == 0 && this.config.emptyNodeForm == 'text') { + if (result['__cnt'] === 0 && this.config.emptyNodeForm === 'text') { result = ''; - } else if (result['__cnt'] == 1 && result['__text'] != null) { + } else if (result['__cnt'] === 1 && result['__text'] !== null) { result = result['__text']; } else if ( - result['__cnt'] == 1 && - result['__cdata'] != null && + result['__cnt'] === 1 && + result['__cdata'] !== null && !this.config.keepCData ) { result = result['__cdata']; } else if ( result['__cnt'] > 1 && - result['__text'] != null && + result['__text'] !== null && this.config.skipEmptyTextNodesForObj ) { if ( - (this.config.stripWhitespaces && result['__text'] == '') || - result['__text'].trim() == '' + (this.config.stripWhitespaces && result['__text'] === '') || + result['__text'].trim() === '' ) { delete result['__text']; } @@ -308,20 +312,20 @@ export class XML2JS { if ( this.config.enableToStringFunc && - (result['__text'] != null || result['__cdata'] != null) + (result['__text'] !== null || result['__cdata'] !== null) ) { result.toString = function () { return ( - (this.__text != null ? this.__text : '') + - (this.__cdata != null ? this.__cdata : '') + (this.__text !== null ? this.__text : '') + + (this.__cdata !== null ? this.__cdata : '') ); }; } return result; } else if ( - node.nodeType == DOMNodeTypes.TEXT_NODE || - node.nodeType == DOMNodeTypes.CDATA_SECTION_NODE + node.nodeType === DOMNodeTypes.TEXT_NODE || + node.nodeType === DOMNodeTypes.CDATA_SECTION_NODE ) { return node.nodeValue; } @@ -330,11 +334,11 @@ export class XML2JS { private startTag(jsonObj, element, attrList, closed) { let resultStr = '<' + - (jsonObj != null && jsonObj.__prefix != null + (jsonObj !== null && jsonObj.__prefix !== null ? jsonObj.__prefix + ':' : '') + element; - if (attrList != null) { + if (attrList !== null) { for (let aidx = 0; aidx < attrList.length; aidx++) { const attrName = attrList[aidx]; let attrVal = jsonObj[attrName]; @@ -353,7 +357,7 @@ export class XML2JS { private endTag(jsonObj, elementName) { return ( '' ); @@ -365,10 +369,10 @@ export class XML2JS { private jsonXmlSpecialElem(jsonObj, jsonObjField) { if ( - (this.config.arrayAccessForm == 'property' && + (this.config.arrayAccessForm === 'property' && this.endsWith(jsonObjField.toString(), '_asArray')) || - jsonObjField.toString().indexOf(this.config.attributePrefix) == 0 || - jsonObjField.toString().indexOf('__') == 0 || + jsonObjField.toString().indexOf(this.config.attributePrefix) === 0 || + jsonObjField.toString().indexOf('__') === 0 || jsonObj[jsonObjField] instanceof Function ) return true; @@ -388,8 +392,8 @@ export class XML2JS { private checkJsonObjPropertiesFilter(jsonObj, propertyName, jsonObjPath) { return ( - this.config.jsonPropertiesFilter.length == 0 || - jsonObjPath == '' || + this.config.jsonPropertiesFilter.length === 0 || + jsonObjPath === '' || this.checkInStdFiltersArrayForm( this.config.jsonPropertiesFilter, jsonObj, @@ -404,8 +408,8 @@ export class XML2JS { if (jsonObj instanceof Object) { for (const ait in jsonObj) { if ( - ait.toString().indexOf('__') == -1 && - ait.toString().indexOf(this.config.attributePrefix) == 0 + ait.toString().indexOf('__') === -1 && + ait.toString().indexOf(this.config.attributePrefix) === 0 ) { attrList.push(ait); } @@ -417,11 +421,11 @@ export class XML2JS { private parseJSONTextAttrs(jsonTxtObj) { let result = ''; - if (jsonTxtObj.__cdata != null) { + if (jsonTxtObj.__cdata !== null) { result += ''; } - if (jsonTxtObj.__text != null) { + if (jsonTxtObj.__text !== null) { if (this.config.escapeMode) result += this.escapeXmlChars(jsonTxtObj.__text); else result += jsonTxtObj.__text; @@ -434,7 +438,7 @@ export class XML2JS { if (jsonTxtObj instanceof Object) { result += this.parseJSONTextAttrs(jsonTxtObj); - } else if (jsonTxtObj != null) { + } else if (jsonTxtObj !== null) { if (this.config.escapeMode) result += this.escapeXmlChars(jsonTxtObj); else result += jsonTxtObj; } @@ -450,7 +454,7 @@ export class XML2JS { private parseJSONArray(jsonArrRoot, jsonArrObj, attrList, jsonObjPath) { let result = ''; - if (jsonArrRoot.length == 0) { + if (jsonArrRoot.length === 0) { result += this.startTag(jsonArrRoot, jsonArrObj, attrList, true); } else { for (let arIdx = 0; arIdx < jsonArrRoot.length; arIdx++) { @@ -479,7 +483,7 @@ export class XML2JS { for (const it in jsonObj) { if ( this.jsonXmlSpecialElem(jsonObj, it) || - (jsonObjPath != '' && + (jsonObjPath !== '' && !this.checkJsonObjPropertiesFilter( jsonObj, it, @@ -492,7 +496,7 @@ export class XML2JS { const attrList = this.parseJSONAttributes(subObj); - if (subObj == null || subObj == undefined) { + if (subObj === null || subObj === undefined) { result += this.startTag(subObj, it, attrList, true); } else if (subObj instanceof Object) { if (subObj instanceof Array) { @@ -505,8 +509,8 @@ export class XML2JS { const subObjElementsCnt = this.jsonXmlElemCount(subObj); if ( subObjElementsCnt > 0 || - subObj.__text != null || - subObj.__cdata != null + subObj.__text !== null || + subObj.__cdata !== null ) { result += this.startTag(subObj, it, attrList, false); result += this.parseJSONObject( @@ -537,7 +541,7 @@ export class XML2JS { try { xmlDoc = parser.parseFromString(xmlDocStr, 'text/xml'); if ( - parsererrorNS != null && + parsererrorNS !== null && xmlDoc.getElementsByTagNameNS(parsererrorNS, 'parsererror').length > 0 ) { xmlDoc = null; @@ -549,8 +553,9 @@ export class XML2JS { return xmlDoc; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any public asArray(prop: any = null) { - if (prop === undefined || prop == null) { + if (prop === undefined || prop === null) { return []; } else if (prop instanceof Array) { return prop; @@ -559,6 +564,7 @@ export class XML2JS { } } + // eslint-disable-next-line @typescript-eslint/no-explicit-any public toXmlDateTime(dt: any) { if (dt instanceof Date) { return dt.toISOString(); @@ -570,7 +576,7 @@ export class XML2JS { } public asDateTime(prop: string) { - if (typeof prop == 'string') { + if (typeof prop === 'string') { return this.fromXmlDateTime(prop); } else { return prop; @@ -585,7 +591,7 @@ export class XML2JS { // ** XML string to JSON ** public toJson(xmlDocStr: string) { const xmlDoc = this.parseXmlString(xmlDocStr); - if (xmlDoc != null) { + if (xmlDoc !== null) { return this.domToJson(xmlDoc); } else { return null; @@ -593,11 +599,13 @@ export class XML2JS { } // ** JSON DOM to XML string ** + // eslint-disable-next-line @typescript-eslint/no-explicit-any public toString(jsonObj: any) { return this.parseJSONObject(jsonObj, ''); } // ** JSON DOM to XML DOC ** + // eslint-disable-next-line @typescript-eslint/no-explicit-any public toDom(jsonObj: any) { return this.parseXmlString(this.toString(jsonObj)); } diff --git a/src/app/lib/components/dialogs/playback-dialog.ts b/src/app/lib/components/dialogs/playback-dialog.ts index 751d45a4..1daad400 100644 --- a/src/app/lib/components/dialogs/playback-dialog.ts +++ b/src/app/lib/components/dialogs/playback-dialog.ts @@ -145,6 +145,7 @@ export class PlaybackDialog { constructor( public dialogRef: MatDialogRef, + // eslint-disable-next-line @typescript-eslint/no-explicit-any @Inject(MAT_DIALOG_DATA) public data: any ) {} diff --git a/src/app/lib/components/dialogs/trail2route-dialog.ts b/src/app/lib/components/dialogs/trail2route-dialog.ts index d7e03ad5..7e31d134 100644 --- a/src/app/lib/components/dialogs/trail2route-dialog.ts +++ b/src/app/lib/components/dialogs/trail2route-dialog.ts @@ -51,6 +51,7 @@ import { AppInfo } from 'src/app/app.info'; ] }) export class Trail2RouteDialog implements OnInit { + // eslint-disable-next-line @typescript-eslint/no-explicit-any rteFromTrail: any[]; mapCenter = [0, 0]; pointCount = 0; @@ -61,8 +62,10 @@ export class Trail2RouteDialog implements OnInit { mapControls = [{ name: 'zoom' }]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any private obsList: Array = []; private fetching = false; + // eslint-disable-next-line @typescript-eslint/no-explicit-any private serverCoords: Array = []; constructor( @@ -70,6 +73,7 @@ export class Trail2RouteDialog implements OnInit { protected app: AppInfo, private stream: SKStreamFacade, public dialogRef: MatDialogRef, + // eslint-disable-next-line @typescript-eslint/no-explicit-any @Inject(MAT_DIALOG_DATA) public data: any ) {} @@ -132,9 +136,11 @@ export class Trail2RouteDialog implements OnInit { } // get server trail event handler + // eslint-disable-next-line @typescript-eslint/no-explicit-any onServerResource(value: any) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any let serverTrail: Array = []; - if (this.fetching && value.action == 'get' && value.mode == 'trail') { + if (this.fetching && value.action === 'get' && value.mode === 'trail') { this.fetching = false; serverTrail = value.data; this.serverCoords = []; diff --git a/src/app/lib/components/file-input.component.ts b/src/app/lib/components/file-input.component.ts index 9f37b635..913c6ba3 100644 --- a/src/app/lib/components/file-input.component.ts +++ b/src/app/lib/components/file-input.component.ts @@ -82,7 +82,7 @@ export class FileInputComponent { return; } // ** Check file type ** - if (this.accept.indexOf('/*') != -1) { + if (this.accept.indexOf('/*') !== -1) { // ** if /* used in accept attribute const ftype = files[index].type.substring( 0, @@ -100,7 +100,7 @@ export class FileInputComponent { // Start reading this file this.readFile(files[index], reader, (result) => { // ** callback function ** - if (index == 0 && this.preview) { + if (index === 0 && this.preview) { this.avatar = result; } this.chosen.emit({ name: files[index].name, data: result }); //** fire chosen event @@ -138,7 +138,7 @@ export class FileInputComponent { callback(reader.result); }; - if (file.type.indexOf('text') != -1 || this.astext) { + if (file.type.indexOf('text') !== -1 || this.astext) { reader.readAsText(file); } else { // read as base64 diff --git a/src/app/lib/components/index.ts b/src/app/lib/components/index.ts index 8b707a0d..50be827d 100644 --- a/src/app/lib/components/index.ts +++ b/src/app/lib/components/index.ts @@ -1,3 +1,4 @@ +export * from './autopilot.component'; export * from './course-settings'; export * from './dial-text'; export * from './file-input.component'; diff --git a/src/app/lib/components/signalk-details.component.ts b/src/app/lib/components/signalk-details.component.ts index 5baba8e5..747c6dfc 100644 --- a/src/app/lib/components/signalk-details.component.ts +++ b/src/app/lib/components/signalk-details.component.ts @@ -33,8 +33,9 @@ import { MatTooltipModule } from '@angular/material/tooltip'; }) export class SignalKDetailsComponent { @Input() title = ''; + // eslint-disable-next-line @typescript-eslint/no-explicit-any @Input() details: any; - + // eslint-disable-next-line @typescript-eslint/no-explicit-any public items: any; //constructor() { } @@ -56,11 +57,12 @@ export class SignalKDetailsComponent { this.items = this.section(this.flatten(u)); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any private section(u: Array): Array { const result = []; u.forEach((i) => { const p = i[0].split('.'); - if (p.length == 1) { + if (p.length === 1) { result.push([0, '', p[0], typeof i[1] !== 'object' ? i[1] : null]); } else { const pp = p.slice(0, p.length - 1).join('.'); @@ -75,9 +77,9 @@ export class SignalKDetailsComponent { const processedParents = []; u = []; result.forEach((i) => { - if (i[1] != prevParent) { + if (i[1] !== prevParent) { prevParent = i[1]; - if (processedParents.indexOf(i[1]) == -1) { + if (processedParents.indexOf(i[1]) === -1) { // processedParents.push(i[1]); u.push([0, i[1], null]); @@ -89,10 +91,11 @@ export class SignalKDetailsComponent { } // ** flatten object values to .paths ** + // eslint-disable-next-line @typescript-eslint/no-explicit-any private flatten(u: Array): Array { let paths = []; u.forEach((i) => { - if (typeof i[1] === 'object' && i[1] != null) { + if (typeof i[1] === 'object' && i[1] !== null) { paths = paths.concat(this.processObject(i[0], i[1])); } else { paths.push([i[0], i[1]]); @@ -102,6 +105,7 @@ export class SignalKDetailsComponent { } // ** process object values ** + // eslint-disable-next-line @typescript-eslint/no-explicit-any private processObject(parent: string, obj: any) { let paths = []; const u = Object.entries(obj); @@ -109,7 +113,7 @@ export class SignalKDetailsComponent { return a[0] < b[0] ? -1 : 1; }); u.forEach((i) => { - if (typeof i[1] === 'object' && i[1] != null) { + if (typeof i[1] === 'object' && i[1] !== null) { paths = paths.concat(this.processObject(parent + '.' + i[0], i[1])); } else { paths.push([parent + '.' + i[0], i[1]]); diff --git a/src/app/lib/geoutils.ts b/src/app/lib/geoutils.ts index 2be5ae66..129b9610 100644 --- a/src/app/lib/geoutils.ts +++ b/src/app/lib/geoutils.ts @@ -87,7 +87,7 @@ export class GeoUtils { if (!Array.isArray(coords)) { return [0, 0]; } - if (typeof coords[0] == 'number') { + if (typeof coords[0] === 'number') { if (coords[0] > 180) { while (coords[0] > 180) { coords[0] = coords[0] - 360; diff --git a/src/app/lib/pipes/reverse.pipe.ts b/src/app/lib/pipes/reverse.pipe.ts index 49c6b045..d100975b 100644 --- a/src/app/lib/pipes/reverse.pipe.ts +++ b/src/app/lib/pipes/reverse.pipe.ts @@ -4,6 +4,7 @@ import { Pipe, PipeTransform } from '@angular/core'; export class ReversePipe implements PipeTransform { //constructor() {} + // eslint-disable-next-line @typescript-eslint/no-explicit-any public transform(value: Array): Array { return value.slice().reverse(); } diff --git a/src/app/lib/services/indexeddb.ts b/src/app/lib/services/indexeddb.ts index ede05a05..95cfca04 100644 --- a/src/app/lib/services/indexeddb.ts +++ b/src/app/lib/services/indexeddb.ts @@ -9,7 +9,12 @@ export class IndexedDB { this.dbWrapper = new DbWrapper(dbName, version); } - openDatabase(version: number, upgradeCallback?: Function) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + openDatabase( + version: number, + upgradeCallback?: (evt: any, db: IDBDatabase) => void + ) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any return new Promise((resolve, reject) => { this.dbWrapper.dbVersion = version; const request = this.utils.indexedDB.open(this.dbWrapper.dbName, version); @@ -19,6 +24,7 @@ export class IndexedDB { }; request.onerror = function (e) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any reject('IndexedDB error: ' + (e.target).errorCode); }; @@ -30,22 +36,27 @@ export class IndexedDB { }); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any getByKey(storeName: string, key: any) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any return new Promise((resolve, reject) => { this.dbWrapper.validateBeforeTransaction(storeName, reject); - let transaction = this.dbWrapper.createTransaction({ + const transaction = this.dbWrapper.createTransaction({ storeName: storeName, dbMode: 'readonly', error: (e: Event) => { reject(e); }, - complete: (e: Event) => {} + complete: () => { + /* */ + } }); - let objectStore = transaction.objectStore(storeName); + const objectStore = transaction.objectStore(storeName); const request: IDBRequest = objectStore.get(key); request.onsuccess = (event: Event) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any resolve((event.target).result); }; }); @@ -56,22 +67,27 @@ export class IndexedDB { keyRange?: IDBKeyRange, indexDetails?: IndexDetails ) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any return new Promise((resolve, reject) => { this.dbWrapper.validateBeforeTransaction(storeName, reject); - let transaction = this.dbWrapper.createTransaction({ - storeName: storeName, - dbMode: 'readonly', - error: (e: Event) => { - reject(e); - }, - complete: (e: Event) => {} - }), - objectStore = transaction.objectStore(storeName), - result: Array = [], - request: IDBRequest; + const transaction = this.dbWrapper.createTransaction({ + storeName: storeName, + dbMode: 'readonly', + error: (e: Event) => { + reject(e); + }, + complete: () => { + /* */ + } + }); + const objectStore = transaction.objectStore(storeName); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const result: Array = []; + let request: IDBRequest; + if (indexDetails) { - let index = objectStore.index(indexDetails.indexName), + const index = objectStore.index(indexDetails.indexName), order = indexDetails.order === 'desc' ? 'prev' : 'next'; request = index.openCursor(keyRange, order); } else { @@ -94,30 +110,35 @@ export class IndexedDB { }); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any add(storeName: string, value: any, key?: any) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any return new Promise((resolve, reject) => { this.dbWrapper.validateBeforeTransaction(storeName, reject); - let transaction = this.dbWrapper.createTransaction({ - storeName: storeName, - dbMode: 'readwrite', - error: (e: Event) => { - reject(e); - }, - complete: (e: Event) => { - resolve({ key: key, value: value }); - } - }), - objectStore = transaction.objectStore(storeName); + const transaction = this.dbWrapper.createTransaction({ + storeName: storeName, + dbMode: 'readwrite', + error: (e: Event) => { + reject(e); + }, + complete: () => { + resolve({ key: key, value: value }); + } + }); + const objectStore = transaction.objectStore(storeName); const request = objectStore.add(value, key); + // eslint-disable-next-line @typescript-eslint/no-explicit-any request.onsuccess = (evt: any) => { key = evt.target.result; }; }); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any update(storeName: string, value: any, key?: any) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any return new Promise((resolve, reject) => { this.dbWrapper.validateBeforeTransaction(storeName, reject); @@ -127,7 +148,7 @@ export class IndexedDB { error: (e: Event) => { reject(e); }, - complete: (e: Event) => { + complete: () => { resolve(value); }, abort: (e: Event) => { @@ -140,24 +161,26 @@ export class IndexedDB { }); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any delete(storeName: string, key: any) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any return new Promise((resolve, reject) => { this.dbWrapper.validateBeforeTransaction(storeName, reject); - let transaction = this.dbWrapper.createTransaction({ - storeName: storeName, - dbMode: 'readwrite', - error: (e: Event) => { - reject(e); - }, - complete: (e: Event) => { - resolve(e); - }, - abort: (e: Event) => { - reject(e); - } - }), - objectStore = transaction.objectStore(storeName); + const transaction = this.dbWrapper.createTransaction({ + storeName: storeName, + dbMode: 'readwrite', + error: (e: Event) => { + reject(e); + }, + complete: (e: Event) => { + resolve(e); + }, + abort: (e: Event) => { + reject(e); + } + }); + const objectStore = transaction.objectStore(storeName); objectStore['delete'](key); }); @@ -168,24 +191,25 @@ export class IndexedDB { cursorCallback: (evt: Event) => void, keyRange?: IDBKeyRange ) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any return new Promise((resolve, reject) => { this.dbWrapper.validateBeforeTransaction(storeName, reject); - let transaction = this.dbWrapper.createTransaction({ - storeName: storeName, - dbMode: 'readonly', - error: (e: Event) => { - reject(e); - }, - complete: (e: Event) => { - resolve(e); - }, - abort: (e: Event) => { - reject(e); - } - }), - objectStore = transaction.objectStore(storeName), - request = objectStore.openCursor(keyRange); + const transaction = this.dbWrapper.createTransaction({ + storeName: storeName, + dbMode: 'readonly', + error: (e: Event) => { + reject(e); + }, + complete: (e: Event) => { + resolve(e); + }, + abort: (e: Event) => { + reject(e); + } + }); + const objectStore = transaction.objectStore(storeName); + const request = objectStore.openCursor(keyRange); request.onsuccess = (evt: Event) => { cursorCallback(evt); @@ -195,46 +219,51 @@ export class IndexedDB { } clear(storeName: string) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any return new Promise((resolve, reject) => { this.dbWrapper.validateBeforeTransaction(storeName, reject); - let transaction = this.dbWrapper.createTransaction({ - storeName: storeName, - dbMode: 'readwrite', - error: (e: Event) => { - reject(e); - }, - complete: (e: Event) => { - resolve(e); - }, - abort: (e: Event) => { - reject(e); - } - }), - objectStore = transaction.objectStore(storeName); + const transaction = this.dbWrapper.createTransaction({ + storeName: storeName, + dbMode: 'readwrite', + error: (e: Event) => { + reject(e); + }, + complete: (e: Event) => { + resolve(e); + }, + abort: (e: Event) => { + reject(e); + } + }); + const objectStore = transaction.objectStore(storeName); objectStore.clear(); resolve(null); }); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any getByIndex(storeName: string, indexName: string, key: any) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any return new Promise((resolve, reject) => { this.dbWrapper.validateBeforeTransaction(storeName, reject); - let transaction = this.dbWrapper.createTransaction({ - storeName: storeName, - dbMode: 'readonly', - error: (e: Event) => { - reject(e); - }, - abort: (e: Event) => { - reject(e); - }, - complete: (e: Event) => {} - }), - objectStore = transaction.objectStore(storeName), - index = objectStore.index(indexName), - request = index.get(key); + const transaction = this.dbWrapper.createTransaction({ + storeName: storeName, + dbMode: 'readonly', + error: (e: Event) => { + reject(e); + }, + abort: (e: Event) => { + reject(e); + }, + complete: () => { + /* */ + } + }); + const objectStore = transaction.objectStore(storeName); + const index = objectStore.index(indexName); + const request = index.get(key); request.onsuccess = (event) => { resolve((event.target).result); @@ -249,8 +278,11 @@ export class Utils { constructor() { this.indexedDB = window.indexedDB || + // eslint-disable-next-line @typescript-eslint/no-explicit-any (window).mozIndexedDB || + // eslint-disable-next-line @typescript-eslint/no-explicit-any (window).webkitIndexedDB || + // eslint-disable-next-line @typescript-eslint/no-explicit-any (window).msIndexedDB; } } @@ -275,7 +307,7 @@ export class DbWrapper { return this.db.objectStoreNames.contains(storeName); } - validateBeforeTransaction(storeName: string, reject: Function) { + validateBeforeTransaction(storeName: string, reject: (e: string) => void) { if (!this.db) { reject( 'You need to use the createStore function to create a database before you query it!' @@ -289,8 +321,11 @@ export class DbWrapper { createTransaction(options: { storeName: string; dbMode: IDBTransactionMode; + // eslint-disable-next-line @typescript-eslint/no-explicit-any error: (e: Event) => any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any complete: (e: Event) => any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any abort?: (e: Event) => any; }): IDBTransaction { const trans: IDBTransaction = this.db.transaction( diff --git a/src/app/lib/services/info.service.ts b/src/app/lib/services/info.service.ts index d6c8d6cd..4e781215 100644 --- a/src/app/lib/services/info.service.ts +++ b/src/app/lib/services/info.service.ts @@ -31,7 +31,9 @@ export class Info { protected devMode: boolean; + // eslint-disable-next-line @typescript-eslint/no-explicit-any public config: any; //** holds app configuration settings ** + // eslint-disable-next-line @typescript-eslint/no-explicit-any public data: any; //** holds app data ** public state: State; @@ -80,7 +82,7 @@ export class Info { }; if (cv.version) { // ** check for newer version ** - if (this.version.indexOf(cv.version) == -1) { + if (this.version.indexOf(cv.version) === -1) { value.result = 'update'; this.debug( `AppInfo: Version update detected.. (${cv.version} -> ${this.version})`, diff --git a/src/app/lib/services/state.service.ts b/src/app/lib/services/state.service.ts index 4fbacfb1..55d84a9f 100644 --- a/src/app/lib/services/state.service.ts +++ b/src/app/lib/services/state.service.ts @@ -21,7 +21,7 @@ export class State { } set appId(value: string) { - if (value && value.length != 0) { + if (value && value.length !== 0) { this._appid = value; this.ls.namespace = this._appid; } diff --git a/src/app/modules/alarms/alarms.facade.ts b/src/app/modules/alarms/alarms.facade.ts index fb7fcf8e..3eb903d2 100644 --- a/src/app/modules/alarms/alarms.facade.ts +++ b/src/app/modules/alarms/alarms.facade.ts @@ -8,22 +8,16 @@ import { AppInfo } from 'src/app/app.info'; import { SKResources } from 'src/app/modules'; import { SignalKClient } from 'signalk-client-angular'; import { SKStreamProvider } from '../skstream/skstream.service'; -import { NotificationMessage } from 'src/app/types'; +import { NotificationMessage, SKNotification } from 'src/app/types'; import { Position } from '../../lib/geoutils'; interface IStatus { action: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any error: any; result: unknown; } -interface SKNotification { - method: Array; - visual: unknown; - state: string; - message: string; -} - @Injectable({ providedIn: 'root' }) export class AlarmsFacade { // **************** ATTRIBUTES *************************** @@ -31,7 +25,7 @@ export class AlarmsFacade { private anchorSource = new Subject(); private alarmSource = new Subject(); private closestApproachSource = new Subject(); - + // eslint-disable-next-line @typescript-eslint/no-explicit-any public alarms!: any; // ******************************************************* @@ -72,43 +66,25 @@ export class AlarmsFacade { context = context ? context : 'self'; if (e.action === 'setRadius') { //send radius value only + const val = typeof e.radius === 'number' ? { radius: e.radius } : {}; this.app.config.anchorRadius = e.radius; - this.signalk - .post('/plugins/anchoralarm/setRadius', { - radius: e.radius - }) - .subscribe( - () => { - this.app.saveConfig(); - resolve(true); - }, - (err: HttpErrorResponse) => { - this.parseAnchorError(err, 'radius'); - this.queryAnchorStatus(context, position); - reject(); - } - ); + this.signalk.post('/plugins/anchoralarm/setRadius', val).subscribe( + () => { + this.app.saveConfig(); + resolve(true); + }, + (err: HttpErrorResponse) => { + this.parseAnchorError(err, 'radius'); + this.queryAnchorStatus(context, position); + reject(); + } + ); } else if (e.action === 'drop') { // ** drop anchor this.app.config.anchorRadius = e.radius; this.signalk.post('/plugins/anchoralarm/dropAnchor', {}).subscribe( () => { this.app.saveConfig(); - this.signalk - .post('/plugins/anchoralarm/setRadius', { - radius: e.radius - }) - .subscribe( - () => { - console.log('Radius Set: ', e.radius); - resolve(true); - }, - (err: HttpErrorResponse) => { - this.parseAnchorError(err, 'radius'); - this.queryAnchorStatus(context, position); - reject(); - } - ); }, (err: HttpErrorResponse) => { this.parseAnchorError(err, 'drop'); @@ -139,7 +115,7 @@ export class AlarmsFacade { // ** query anchor status queryAnchorStatus(context: string, position?: Position) { this.app.debug('Retrieving anchor status...'); - context = !context || context == 'self' ? 'vessels/self' : context; + context = !context || context === 'self' ? 'vessels/self' : context; this.signalk.api.get(`/${context}/navigation/anchor`).subscribe( (r) => { const aData = { position: null, maxRadius: null }; @@ -151,7 +127,7 @@ export class AlarmsFacade { } if (r['maxRadius']) { aData.maxRadius = - typeof r['maxRadius']['value'] == 'number' + typeof r['maxRadius']['value'] === 'number' ? r['maxRadius']['value'] : null; } @@ -208,9 +184,7 @@ export class AlarmsFacade { this.app.data.anchor.raised = true; } - if (typeof r.maxRadius === 'number') { - this.app.data.anchor.radius = r.maxRadius; - } + this.app.data.anchor.radius = r.maxRadius ?? -1; // ** emit anchorStatus$ ** this.anchorSource.next({ @@ -280,7 +254,8 @@ export class AlarmsFacade { state: notification.state, message: notification.message, isSmoothing: false, - acknowledged: initAcknowledged + acknowledged: initAcknowledged, + data: notification.data }); } else { // update alarm entry diff --git a/src/app/modules/alarms/alarms.module.ts b/src/app/modules/alarms/alarms.module.ts index 3ee36310..4b25a1c4 100644 --- a/src/app/modules/alarms/alarms.module.ts +++ b/src/app/modules/alarms/alarms.module.ts @@ -12,9 +12,11 @@ import { MatToolbarModule } from '@angular/material/toolbar'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatSliderModule } from '@angular/material/slider'; import { MatDialogModule } from '@angular/material/dialog'; +import { MatStepperModule } from '@angular/material/stepper'; import { CommonDialogs } from 'src/app/lib/components/dialogs'; // ** components ** -import { AlarmsDialog, AlarmComponent } from './alarms'; +import { AlarmComponent } from './components/alarm.component'; +import { AlarmsDialog } from './components/alarms-dialog.component'; import { AnchorWatchComponent } from './components/anchor-watch.component'; import { TimerButtonComponent } from './components/timer-button.component'; @@ -29,6 +31,7 @@ import { TimerButtonComponent } from './components/timer-button.component'; MatSliderModule, MatTooltipModule, MatToolbarModule, + MatStepperModule, CommonDialogs, TimerButtonComponent ], @@ -39,5 +42,5 @@ import { TimerButtonComponent } from './components/timer-button.component'; export class AlarmsModule {} export * from './alarms.facade'; -export * from './alarms'; +export * from './components/alarms-dialog.component'; export * from './components/anchor-watch.component'; diff --git a/src/app/modules/alarms/alarms.ts b/src/app/modules/alarms/alarms.ts deleted file mode 100644 index edbf7474..00000000 --- a/src/app/modules/alarms/alarms.ts +++ /dev/null @@ -1,458 +0,0 @@ -/** Alarms Dialog Components ** - ********************************/ - -import { HttpErrorResponse } from '@angular/common/http'; -import { - Component, - OnInit, - Inject, - Input, - Output, - EventEmitter -} from '@angular/core'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { Subscription } from 'rxjs'; -import { Alarm, AlarmState, SignalKClient } from 'signalk-client-angular'; -import { AlarmsFacade } from './alarms.facade'; -import { AppInfo } from 'src/app/app.info'; - -/********* AlarmsDialog ********* - data: { - alarms: current alarms and their state - } -***********************************/ -@Component({ - selector: 'ap-alarmsdialog', - template: ` -
- - alarm_on - Alarms - - - - - -
-
- - - - {{ i.title }} - - - - - - - -
-
-
-
- `, - styles: [ - ` - ._ap-alarms { - font-family: arial; - max-width: 500px; - } - .alarm-item { - width: 100%; - } - - @media only screen and (min-width: 500px) { - .alarm-item { - width: 50%; - } - } - ` - ] -}) -export class AlarmsDialog implements OnInit { - public stdAlarms: Array<{ - key: string; - title: string; - subtitle: string; - cancel: boolean; - displayAlways: boolean; - }>; - private obs: Subscription; - - constructor( - private signalk: SignalKClient, - private facade: AlarmsFacade, - public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: unknown - ) {} - - ngOnInit() { - this.obs = this.facade.update$().subscribe(() => { - this.parseAlarms(); - }); - this.stdAlarms = [ - { - key: 'mob', - title: 'M.O.B.', - subtitle: 'Man Overboard!', - cancel: false, - displayAlways: true - }, - { - key: 'sinking', - title: 'Sinking', - subtitle: 'Vessel sinking!', - cancel: false, - displayAlways: true - }, - { - key: 'fire', - title: 'Fire', - subtitle: 'Fire onboard!', - cancel: false, - displayAlways: true - }, - { - key: 'piracy', - title: 'Piracy', - subtitle: 'Pirates encountered!', - cancel: false, - displayAlways: true - }, - { - key: 'abandon', - title: 'Abandon', - subtitle: 'Abandoned ship!', - cancel: false, - displayAlways: true - }, - { - key: 'adrift', - title: 'Adrift', - subtitle: 'Vessel adrift!', - cancel: false, - displayAlways: true - }, - { - key: 'flooding', - title: 'Flooding', - subtitle: 'Taking on Water!', - cancel: false, - displayAlways: false - }, - { - key: 'listing', - title: 'Listing', - subtitle: 'Vessel listing!', - cancel: false, - displayAlways: false - }, - { - key: 'collision', - title: 'Collision', - subtitle: 'Vessel collision!', - cancel: false, - displayAlways: false - }, - { - key: 'grounding', - title: 'Grounding', - subtitle: 'Vessel run aground!', - cancel: false, - displayAlways: false - } - ]; - - this.parseAlarms(); - } - - ngOnDestroy() { - this.obs.unsubscribe(); - } - - parseAlarms() { - this.stdAlarms.forEach((i) => { - if (this.facade.alarms.has(i.key)) { - i.cancel = - this.facade.alarms.get(i.key).state != 'normal' ? true : false; - } else { - i.cancel = false; - } - }); - this.stdAlarms.sort((a, b) => { - if (!a.cancel && b.cancel) { - return 1; - } - if (a.cancel && !b.cancel) { - return -1; - } else { - return 0; - } - }); - } - - raise(alarmType: string, msg: string) { - this.signalk.api - .raiseAlarm( - 'self', - alarmType, - new Alarm(msg, AlarmState.emergency, true, true) - ) - .subscribe( - () => { - console.log(`Raise Alarm: ${alarmType}`); - }, - (err: HttpErrorResponse) => { - console.warn(`Error raising alarm: ${alarmType}`, err); - } - ); - this.dialogRef.close(); - } - - clear(alarmType: string) { - this.signalk.api.clearAlarm('self', alarmType).subscribe( - () => undefined, - (err: HttpErrorResponse) => { - console.warn(`Error clearing alarm: ${alarmType}`, err); - } - ); - } -} - -interface AlarmData { - key: string; - title: string; - value: { - isSmoothing: boolean; - message: string; - sound: boolean; - state: string; - visual: boolean; - acknowledged: boolean; - muted: boolean; - }; -} - -/********* AlarmComponent ********/ -@Component({ - selector: 'ap-alarm', - template: ` - - - - - - - {{ alarm.value.message }} - - -
-
- -
-
- -
- -
- - -
-
-
-
-
-
- `, - styles: [ - ` - .alarmAck { - text-align: center; - flex-wrap: wrap; - } - .alarmAck img { - height: 35px; - } - ` - ] -}) -export class AlarmComponent implements OnInit { - @Input() alarm: AlarmData; // alarm data - @Input() audioContext: AudioContext; // Web Audio API - @Input() audioStatus: string; // changed audio context state - @Input() soundFile = './assets/sound/woop.mp3'; - @Input() loop = true; // true: loop audio file playback - @Input() mute = false; // true: mute audio playback - - @Output() acknowledge: EventEmitter = new EventEmitter(); - @Output() unacknowledge: EventEmitter = new EventEmitter(); - @Output() muted: EventEmitter = new EventEmitter(); - @Output() clear: EventEmitter = new EventEmitter(); - @Output() open: EventEmitter = new EventEmitter(); - @Output() nextPoint: EventEmitter = new EventEmitter(); - - private stdAlarms = [ - 'mob', - 'sinking', - 'fire', - 'piracy', - 'flooding', - 'collision', - 'grounding', - 'listing', - 'adrift', - 'abandon' - ]; - - canUnAck = false; - isStdAlarm = false; - iconUrl: string; - private imgPath = './assets/img/alarms/'; - - private audio: HTMLAudioElement; - private source: MediaElementAudioSourceNode; - nextPointClicked = false; - - constructor(public app: AppInfo) {} - - ngOnInit() { - this.canUnAck = this.stdAlarms.indexOf(this.alarm.key) == -1 ? true : false; - this.isStdAlarm = - this.stdAlarms.indexOf(this.alarm.key) != -1 ? true : false; - this.iconUrl = `${this.imgPath}${this.alarm.key}.png`; - } - - ngOnDestroy() { - this.audio.pause(); - this.audio = null; - this.source = null; - } - - ngOnChanges() { - this.processAudio(); - } - - ackAlarm(key: string) { - this.acknowledge.emit(key); - } - - muteAlarm(key: string) { - this.muted.emit(key); - } - - clearAlarm(key: string) { - this.clear.emit(key); - } - - minClick(key: string) { - if (this.canUnAck) { - this.unacknowledge.emit(key); - } else { - this.open.emit(key); - } - } - - processAudio() { - // play audio when this.alarm.value.sound = true - if (this.audioContext) { - if (!this.audio) { - this.audio = new Audio(); - } - if (!this.source) { - this.source = this.audioContext.createMediaElementSource(this.audio); - this.source.connect(this.audioContext.destination); - } - this.audio.loop = this.loop; - this.audio.src = this.soundFile; - if (this.audioStatus == 'running') { - if (this.alarm.value.sound) { - if (this.mute) { - this.audio.pause(); - } else { - this.audio.play(); - } - } else { - this.audio.pause(); - } - } - } - } - - // arrival alarm - goto next point handler - gotoNextPoint() { - this.nextPointClicked = true; - this.nextPoint.emit(); - console.log('gotoNextPoint()'); - } -} diff --git a/src/app/modules/alarms/components/alarm.component.ts b/src/app/modules/alarms/components/alarm.component.ts new file mode 100644 index 00000000..9d0f493e --- /dev/null +++ b/src/app/modules/alarms/components/alarm.component.ts @@ -0,0 +1,219 @@ +/** Alarms Dialog Components ** + ********************************/ + +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { AppInfo } from 'src/app/app.info'; + +interface AlarmData { + key: string; + title: string; + value: { + isSmoothing: boolean; + message: string; + sound: boolean; + state: string; + visual: boolean; + acknowledged: boolean; + muted: boolean; + }; +} + +/********* AlarmComponent ********/ +@Component({ + selector: 'ap-alarm', + template: ` + + + + + + + {{ alarm.value.message }} + + +
+
+ +
+
+ +
+ +
+ + +
+
+
+
+
+
+ `, + styles: [ + ` + .alarmAck { + text-align: center; + flex-wrap: wrap; + } + .alarmAck img { + height: 35px; + } + ` + ] +}) +export class AlarmComponent implements OnInit { + @Input() alarm: AlarmData; // alarm data + @Input() audioContext: AudioContext; // Web Audio API + @Input() audioStatus: string; // changed audio context state + @Input() soundFile = './assets/sound/woop.mp3'; + @Input() loop = true; // true: loop audio file playback + @Input() mute = false; // true: mute audio playback + + @Output() acknowledge: EventEmitter = new EventEmitter(); + @Output() unacknowledge: EventEmitter = new EventEmitter(); + @Output() muted: EventEmitter = new EventEmitter(); + @Output() clear: EventEmitter = new EventEmitter(); + @Output() open: EventEmitter = new EventEmitter(); + @Output() nextPoint: EventEmitter = new EventEmitter(); + + private stdAlarms = [ + 'mob', + 'sinking', + 'fire', + 'piracy', + 'flooding', + 'collision', + 'grounding', + 'listing', + 'adrift', + 'abandon' + ]; + + canUnAck = false; + isStdAlarm = false; + iconUrl: string; + private imgPath = './assets/img/alarms/'; + + private audio: HTMLAudioElement; + private source: MediaElementAudioSourceNode; + nextPointClicked = false; + + constructor(public app: AppInfo) {} + + ngOnInit() { + this.canUnAck = + this.stdAlarms.indexOf(this.alarm.key) === -1 ? true : false; + this.isStdAlarm = + this.stdAlarms.indexOf(this.alarm.key) !== -1 ? true : false; + this.iconUrl = `${this.imgPath}${this.alarm.key}.png`; + } + + ngOnDestroy() { + this.audio.pause(); + this.audio = null; + this.source = null; + } + + ngOnChanges() { + this.processAudio(); + } + + ackAlarm(key: string) { + this.acknowledge.emit(key); + } + + muteAlarm(key: string) { + this.muted.emit(key); + } + + clearAlarm(key: string) { + this.clear.emit(key); + } + + minClick(key: string) { + if (this.canUnAck) { + this.unacknowledge.emit(key); + } else { + this.open.emit(key); + } + } + + processAudio() { + // play audio when this.alarm.value.sound = true + if (this.audioContext) { + if (!this.audio) { + this.audio = new Audio(); + } + if (!this.source) { + this.source = this.audioContext.createMediaElementSource(this.audio); + this.source.connect(this.audioContext.destination); + } + this.audio.loop = this.loop; + this.audio.src = this.soundFile; + if (this.audioStatus === 'running') { + if (this.alarm.value.sound) { + if (this.mute) { + this.audio.pause(); + } else { + this.audio.play(); + } + } else { + this.audio.pause(); + } + } + } + } + + // arrival alarm - goto next point handler + gotoNextPoint() { + this.nextPointClicked = true; + this.nextPoint.emit(); + console.log('gotoNextPoint()'); + } +} diff --git a/src/app/modules/alarms/components/alarms-dialog.component.ts b/src/app/modules/alarms/components/alarms-dialog.component.ts new file mode 100644 index 00000000..d36568b3 --- /dev/null +++ b/src/app/modules/alarms/components/alarms-dialog.component.ts @@ -0,0 +1,237 @@ +/** Alarms Dialog Components ** + ********************************/ + +import { HttpErrorResponse } from '@angular/common/http'; +import { Component, OnInit, Inject } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { Subscription } from 'rxjs'; +import { Alarm, AlarmState, SignalKClient } from 'signalk-client-angular'; +import { AlarmsFacade } from '../alarms.facade'; + +/********* AlarmsDialog ********* + data: { + alarms: current alarms and their state + } +***********************************/ +@Component({ + selector: 'ap-alarmsdialog', + template: ` +
+ + alarm_on + Alarms + + + + + +
+
+ + + + {{ i.title }} + + + + + + + +
+
+
+
+ `, + styles: [ + ` + ._ap-alarms { + font-family: arial; + max-width: 500px; + } + .alarm-item { + width: 100%; + } + + @media only screen and (min-width: 500px) { + .alarm-item { + width: 50%; + } + } + ` + ] +}) +export class AlarmsDialog implements OnInit { + public stdAlarms: Array<{ + key: string; + title: string; + subtitle: string; + cancel: boolean; + displayAlways: boolean; + }>; + private obs: Subscription; + + constructor( + private signalk: SignalKClient, + private facade: AlarmsFacade, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: unknown + ) {} + + ngOnInit() { + this.obs = this.facade.update$().subscribe(() => { + this.parseAlarms(); + }); + this.stdAlarms = [ + { + key: 'mob', + title: 'M.O.B.', + subtitle: 'Man Overboard!', + cancel: false, + displayAlways: true + }, + { + key: 'sinking', + title: 'Sinking', + subtitle: 'Vessel sinking!', + cancel: false, + displayAlways: true + }, + { + key: 'fire', + title: 'Fire', + subtitle: 'Fire onboard!', + cancel: false, + displayAlways: true + }, + { + key: 'piracy', + title: 'Piracy', + subtitle: 'Pirates encountered!', + cancel: false, + displayAlways: true + }, + { + key: 'abandon', + title: 'Abandon', + subtitle: 'Abandoned ship!', + cancel: false, + displayAlways: true + }, + { + key: 'adrift', + title: 'Adrift', + subtitle: 'Vessel adrift!', + cancel: false, + displayAlways: true + }, + { + key: 'flooding', + title: 'Flooding', + subtitle: 'Taking on Water!', + cancel: false, + displayAlways: false + }, + { + key: 'listing', + title: 'Listing', + subtitle: 'Vessel listing!', + cancel: false, + displayAlways: false + }, + { + key: 'collision', + title: 'Collision', + subtitle: 'Vessel collision!', + cancel: false, + displayAlways: false + }, + { + key: 'grounding', + title: 'Grounding', + subtitle: 'Vessel run aground!', + cancel: false, + displayAlways: false + } + ]; + + this.parseAlarms(); + } + + ngOnDestroy() { + this.obs.unsubscribe(); + } + + parseAlarms() { + this.stdAlarms.forEach((i) => { + if (this.facade.alarms.has(i.key)) { + i.cancel = + this.facade.alarms.get(i.key).state !== 'normal' ? true : false; + } else { + i.cancel = false; + } + }); + this.stdAlarms.sort((a, b) => { + if (!a.cancel && b.cancel) { + return 1; + } + if (a.cancel && !b.cancel) { + return -1; + } else { + return 0; + } + }); + } + + raise(alarmType: string, msg: string) { + this.signalk.api + .raiseAlarm( + 'self', + alarmType, + new Alarm(msg, AlarmState.emergency, true, true) + ) + .subscribe( + () => { + console.log(`Raise Alarm: ${alarmType}`); + }, + (err: HttpErrorResponse) => { + console.warn(`Error raising alarm: ${alarmType}`, err); + } + ); + this.dialogRef.close(); + } + + clear(alarmType: string) { + this.signalk.api.clearAlarm('self', alarmType).subscribe( + () => undefined, + (err: HttpErrorResponse) => { + console.warn(`Error clearing alarm: ${alarmType}`, err); + } + ); + } +} diff --git a/src/app/modules/alarms/components/anchor-watch.component.html b/src/app/modules/alarms/components/anchor-watch.component.html index f1e63889..d6b2d153 100644 --- a/src/app/modules/alarms/components/anchor-watch.component.html +++ b/src/app/modules/alarms/components/anchor-watch.component.html @@ -41,41 +41,12 @@ text-align: right; " > - {{ feet ? mToFt(radius) : radius }} + {{ radius === -1 ? '--' : feet ? mToFt(radius) : radius.toFixed(0) }} {{ feet ? 'ft' : 'm' }} - -
-
- Set Radius: -
-
-
- - - -
-
- {{ feet ? mToFt(msg.radius) : msg.radius }} {{ feet ? 'ft' : 'm' }} -
-
@@ -97,4 +68,65 @@ + +
+ + + When rode is out click Set. +
+ +
+
+ +
+
+
+ Set Radius: +
+
+
+ + + +
+
+ {{ feet ? mToFt(msg.radius) : msg.radius }} + {{ feet ? 'ft' : 'm' }} +
+
+
+
+
+
diff --git a/src/app/modules/alarms/components/anchor-watch.component.ts b/src/app/modules/alarms/components/anchor-watch.component.ts index 0d2ac08d..2286e877 100644 --- a/src/app/modules/alarms/components/anchor-watch.component.ts +++ b/src/app/modules/alarms/components/anchor-watch.component.ts @@ -23,7 +23,6 @@ interface OutputMessage { styleUrls: ['./anchor-watch.component.css'] }) export class AnchorWatchComponent { - @Input() sliderValue: number; @Input() radius: number; @Input() min = 5; @Input() max = 100; @@ -39,6 +38,9 @@ export class AnchorWatchComponent { display = { sliderColor: 'primary' }; msg: OutputMessage = { radius: null, action: undefined }; + sliderValue: number; + rodeOut = false; + constructor(private facade: AlarmsFacade) {} ngOnInit() { @@ -48,11 +50,18 @@ export class AnchorWatchComponent { ngOnChanges(changes: SimpleChanges) { this.display.sliderColor = !this.raised ? 'warn' : 'primary'; - if (changes.sliderValue) { - this.sliderValue = - changes.sliderValue.currentValue ?? - changes.sliderValue.previousValue ?? - 0; + if (changes.radius) { + if (changes.radius.previousValue === -1) { + this.sliderValue = Math.round(changes.radius.currentValue); + this.msg.radius = this.sliderValue; + this.max = this.sliderValue + 100; + } else if ( + changes.radius.firstChange && + changes.radius.currentValue !== -1 + ) { + this.sliderValue = Math.round(changes.radius.currentValue); + this.max = this.sliderValue + 100; + } } this.bgImage = `url('${ this.raised @@ -60,15 +69,19 @@ export class AnchorWatchComponent { : './assets/img/anchor-radius.png' }')`; this.display.sliderColor = !this.raised ? 'warn' : 'primary'; + this.rodeOut = !this.raised && this.radius !== -1; } - setRadius(value: number) { - console.log('raw:', value); - console.log('converted:', this.feet ? this.ftToM(value) : value); - this.msg.radius = this.feet ? this.ftToM(value) : value; + setRadius(value?: number) { + this.rodeOut = true; + this.msg.radius = + typeof value === 'number' + ? this.feet + ? this.ftToM(value) + : value + : value; if (!this.raised) { - this.msg.action = 'setRadius'; // set radius change only - //this.change.emit(this.msg); + this.msg.action = 'setRadius'; this.facade.anchorEvent({ radius: this.msg.radius, action: this.msg.action @@ -78,10 +91,10 @@ export class AnchorWatchComponent { setAnchor(e: { checked: boolean }) { this.msg.action = e.checked ? 'drop' : 'raise'; - //this.change.emit(this.msg); this.facade .anchorEvent({ radius: this.msg.radius, action: this.msg.action }) - .catch((err: Error) => { + .catch(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any (this.slideCtl as any).checked = !(this.slideCtl as any).checked; }); } diff --git a/src/app/modules/alarms/components/timer-button.component.ts b/src/app/modules/alarms/components/timer-button.component.ts index 26f24aab..c8564515 100644 --- a/src/app/modules/alarms/components/timer-button.component.ts +++ b/src/app/modules/alarms/components/timer-button.component.ts @@ -27,7 +27,7 @@ import { MatIconModule } from '@angular/material/icon'; mat-button *ngSwitchCase="false" [disabled]="disabled" - (click)="cancel($event)" + (click)="cancel()" > {{ label }} {{ timeLeft }} secs @@ -80,7 +80,7 @@ export class TimerButtonComponent { } } - cancel(e) { + cancel() { if (this.timer) { clearInterval(this.timer); this.timer = null; diff --git a/src/app/modules/experiments/experiments.component.ts b/src/app/modules/experiments/experiments.component.ts index f956cefc..c9f699eb 100644 --- a/src/app/modules/experiments/experiments.component.ts +++ b/src/app/modules/experiments/experiments.component.ts @@ -38,10 +38,12 @@ import { Component, Output, EventEmitter } from '@angular/core'; styles: [``] }) export class ExperimentsComponent { + // eslint-disable-next-line @typescript-eslint/no-explicit-any @Output() selected: EventEmitter = new EventEmitter(); //constructor() {} + // eslint-disable-next-line @typescript-eslint/no-explicit-any handleSelect(choice: string, value?: any) { this.selected.emit({ choice: choice, value: value }); } diff --git a/src/app/modules/experiments/weather/weather.module.ts b/src/app/modules/experiments/weather/weather.module.ts index e18677b0..b80907c2 100644 --- a/src/app/modules/experiments/weather/weather.module.ts +++ b/src/app/modules/experiments/weather/weather.module.ts @@ -1,4 +1,4 @@ -import { NgModule, Injectable } from '@angular/core'; +import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; diff --git a/src/app/modules/map/components/popover/compass.component.ts b/src/app/modules/map/components/popover/compass.component.ts index 73cf04d5..de4c0990 100644 --- a/src/app/modules/map/components/popover/compass.component.ts +++ b/src/app/modules/map/components/popover/compass.component.ts @@ -150,8 +150,8 @@ export class CompassComponent extends SvgDialBase { if (isNaN(d.heading)) { d.heading = null; } - this.ptrOffset = d.heading != null ? d.heading : 0; - if (d.heading != null) { + this.ptrOffset = d.heading !== null ? d.heading : 0; + if (d.heading !== null) { d.heading = d.heading > 360 ? d.heading - 360 : d.heading; this.valueStr = d.heading.toFixed(0) + String.fromCharCode(186); const val = 0 - this.getAngle(d.heading); @@ -183,7 +183,7 @@ export class CompassComponent extends SvgDialBase { if (isNaN(d.needle)) { d.needle = null; } - if (d.needle != null) { + if (d.needle !== null) { const val = this.getAngle(d.needle) - this.ptrOffset; this.renderer.setAttribute( this.pointer.nativeElement, @@ -197,7 +197,7 @@ export class CompassComponent extends SvgDialBase { if (isNaN(d.windtrue)) { d.windtrue = null; } - if (d.windtrue != null) { + if (d.windtrue !== null) { const val = d.windtrue < 0 ? d.windtrue @@ -214,7 +214,7 @@ export class CompassComponent extends SvgDialBase { if (isNaN(d.windapparent)) { d.windapparent = null; } - if (d.windapparent != null) { + if (d.windapparent !== null) { const val = d.windapparent; this.renderer.setAttribute( this.needlelo.nativeElement, diff --git a/src/app/modules/map/components/popover/popover.component.ts b/src/app/modules/map/components/popover/popover.component.ts index bd70fcb4..0df9264b 100644 --- a/src/app/modules/map/components/popover/popover.component.ts +++ b/src/app/modules/map/components/popover/popover.component.ts @@ -10,6 +10,7 @@ import { } from '@angular/core'; import { AppInfo } from 'src/app/app.info'; import { SKAtoN, SKAircraft } from 'src/app/modules'; +import { SKNotification } from 'src/app/types'; /*********** Popover *************** title: string - title text, @@ -82,10 +83,12 @@ export class FeatureListPopoverComponent { @Input() features: Array<{ text: string; icon: string }> = []; @Input() canClose: boolean; @Output() closed: EventEmitter = new EventEmitter(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any @Output() selected: EventEmitter = new EventEmitter(); //constructor() {} + // eslint-disable-next-line @typescript-eslint/no-explicit-any handleSelect(item: any) { this.selected.emit(item); } @@ -95,6 +98,99 @@ export class FeatureListPopoverComponent { } } +/*********** Alarm Popover *************** +title: string - title text, +aton: SKNotification - alarm data +*************************************************/ +@Component({ + selector: 'alarm-popover', + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` + +
+
Message:
+
{{ alarm.message }}
+
+ +
+
Type:
+
{{ id }}
+
+ +
+
Latitude:
+
+
+
+
Longitude:
+
+
+ +
+
 
+
+ +
+
+
+ `, + styleUrls: [] +}) +export class AlarmPopoverComponent { + @Input() title: string; + @Input() id: string; + @Input() alarm: SKNotification & { id: string }; + @Input() canClose: boolean; + @Output() info: EventEmitter = new EventEmitter(); + @Output() closed: EventEmitter = new EventEmitter(); + + _title: string; + + constructor(public app: AppInfo) {} + + ngOnInit() { + if (!this.alarm) { + this.handleClose(); + } else { + this._title = this.title || 'Alarm:'; + } + } + + ngOnChanges() { + if (!this.alarm) { + this.handleClose(); + return; + } + } + + handleInfo() { + this.info.emit(this.alarm.id); + } + + handleClose() { + this.closed.emit(); + } +} + /*********** AtoN Popover *************** title: string - title text, aton: SKAtoN - aton data diff --git a/src/app/modules/map/components/popover/resource-popover.component.ts b/src/app/modules/map/components/popover/resource-popover.component.ts index a625ad29..9b712c4e 100644 --- a/src/app/modules/map/components/popover/resource-popover.component.ts +++ b/src/app/modules/map/components/popover/resource-popover.component.ts @@ -185,6 +185,7 @@ export class ResourcePopoverComponent { @Output() notes: EventEmitter = new EventEmitter(); properties: Array; // ** resource properties + // eslint-disable-next-line @typescript-eslint/no-explicit-any ctrl: any = { showInfoButton: false, showModifyButton: false, @@ -211,19 +212,19 @@ export class ResourcePopoverComponent { } parse() { - if (this.type == 'destination') { + if (this.type === 'destination') { this.parseDestination(); } - if (this.type == 'waypoint') { + if (this.type === 'waypoint') { this.parseWaypoint(); } - if (this.type == 'route') { + if (this.type === 'route') { this.parseRoute(); } - if (this.type == 'note') { + if (this.type === 'note') { this.parseNote(); } - if (this.type == 'region') { + if (this.type === 'region') { this.parseRegion(); } } @@ -258,7 +259,7 @@ export class ResourcePopoverComponent { parseWaypoint() { this.ctrl.isActive = - this.active && this.active == this.resource[0] ? true : false; + this.active && this.active === this.resource[0] ? true : false; this.ctrl.activeText = 'GO TO'; this.ctrl.canActivate = true; this.ctrl.showInfoButton = true; @@ -299,7 +300,7 @@ export class ResourcePopoverComponent { this.ctrl.showDeleteButton = false; } else { this.ctrl.isActive = - this.active && this.active == this.resource[0] ? true : false; + this.active && this.active === this.resource[0] ? true : false; this.ctrl.activeText = 'ACTIVATE'; this.ctrl.canActivate = true; this.ctrl.showInfoButton = true; @@ -313,7 +314,7 @@ export class ResourcePopoverComponent { this.properties = []; this.properties.push(['Name', this.resource[1].name]); const d = - this.units == 'm' + this.units === 'm' ? [(this.resource[1].distance / 1000).toFixed(1), 'km'] : [ Convert.kmToNauticalMiles(this.resource[1].distance / 1000).toFixed( @@ -349,7 +350,7 @@ export class ResourcePopoverComponent { this.ctrl.canActivate = false; this.ctrl.showInfoButton = true; this.ctrl.showModifyButton = - this.resource[1].feature.geometry.type == 'MultiPolygon' ? false : true; + this.resource[1].feature.geometry.type === 'MultiPolygon' ? false : true; this.ctrl.showDeleteButton = true; this.ctrl.showAddNoteButton = false; this.ctrl.showNotesButton = true; diff --git a/src/app/modules/map/components/profiles/default/index.ts b/src/app/modules/map/components/profiles/default/index.ts index a7a1130d..6c1285e1 100644 --- a/src/app/modules/map/components/profiles/default/index.ts +++ b/src/app/modules/map/components/profiles/default/index.ts @@ -1,2 +1 @@ -//export * from './feature-aisvessels.component'; export * from './vessel-popover.component'; diff --git a/src/app/modules/map/fb-map.component.html b/src/app/modules/map/fb-map.component.html index 28e11fc4..086a532b 100644 --- a/src/app/modules/map/fb-map.component.html +++ b/src/app/modules/map/fb-map.component.html @@ -299,7 +299,7 @@
+ + + @@ -403,6 +415,23 @@ > + + + + + alt_route + Display Autopilot console: + Available when the Autopilot API is detected on the server. + Displays the autopilot console enabling operations permitted by + the Autopilot API. + +
  • add_location Drop Waypoint: diff --git a/src/assets/img/alarms/mob_map.png b/src/assets/img/alarms/mob_map.png new file mode 100644 index 0000000000000000000000000000000000000000..0209ced2cc62d03193eeeb2829bf01539c7eaa3f GIT binary patch literal 770 zcmV+d1O5DoP)>!SSr{UO59*5& zgox2g5QIoQg)bt8DC$E=guNt)3PzeEg)eGKXhTX#j9-M>IFotxVeOlH&OP^zjP!@a zJ?E~q*MIG`)?WJzHFz{*8*bqe_F^s$mKhIu45A5GgXtKKE7i*(JxAGnzay74{!Dfnx`{oKK1{EE})s6+BLdeg%DxQ zxPVEy;+@RF70fRzY{tdRrYeU@NEYI1(pRuNSGJ)ub8rVMGyQ1XDP$9EurFKx2KFmlBXjX>_WlH} zWf6AZ8{AGitcaU+AD&M(Pl;)sXX!i4%Zb)uOJRq{ut)z{)sCr2+Oz-L@Rc}cId+Hx z{vVd%l$dE%yeP&@7d{jdW2C4@i^%^>d?Y4&H)dq(Tkwm*^d2rQvQG;UO~x1K6Y07m zQhLq9$zVD8M6wWX;fNB< z5w~JbitM_L>36DV;nt+@6E!%8lQ@i@#2~K01IiZQw-5MR_5c6?07*qoM6N<$f+3M^ AL;wH) literal 0 HcmV?d00001