From 2c82500b5b6c510fddab6605b19a66b76d024b39 Mon Sep 17 00:00:00 2001 From: Nikolai Sukhanov Date: Mon, 11 Mar 2024 21:40:06 +0400 Subject: [PATCH] feat: separate yaw/pitch/roll widgets --- .../src/lib/control-scheme-page.component.ts | 3 +- .../src/lib/control-scheme-page.selectors.ts | 19 ++- .../i-control-scheme-widget-config-factory.ts | 3 +- ...control-scheme-widgets-grid.component.scss | 2 +- .../shared/misc/src/lib/types/widget-type.ts | 6 +- .../shared/ui/src/lib/changelog/changelog.ts | 4 +- .../tilt-gauge-sector-highlight.pipe.ts | 6 +- .../tilt-gauge-sectors.component.ts | 2 +- .../lib/tilt-gauge/tilt-gauge.component.html | 2 +- .../lib/tilt-gauge/tilt-gauge.component.ts | 6 +- .../lib/actions/attached-io-props.actions.ts | 9 +- .../src/lib/effects/tasks-processing/index.ts | 2 +- .../create-widget-read-tasks.ts | 13 +- .../i-widget-read-task-factory.ts | 17 --- .../i-widgets-read-tasks-factory.ts | 12 ++ .../tasks-processing/scheme-pre-run/index.ts | 2 +- .../scheme-pre-run/pre-run-scheme.effect.ts | 9 +- .../src/lib/migrations/v30-v31/v30-store.ts | 26 +++- .../v30-to-v31-migration.service.spec.ts | 83 ++++++++++++- .../v30-v31/v30-to-v31-migration.service.ts | 70 ++++++++++- .../control-scheme-widgets-data.model.ts | 2 +- .../src/lib/models/control-scheme.model.ts | 33 +++++- .../lib/reducers/attached-io-props.reducer.ts | 56 +++++++-- .../control-scheme-widgets-data.selectors.ts | 12 +- ...n-tilt-widgets-blocker-checker.service.ts} | 4 +- ...on-tilt-widgets-config-factory.service.ts} | 46 ++++---- ...ommon-tilt-widgets-form-builder.service.ts | 77 ++++++++++++ ...tilt-widgets-read-task-factory.service.ts} | 37 +++--- ...gets-settings-component-factory.service.ts | 32 +++++ modules/widgets/src/lib/common/index.ts | 6 + .../common/provide-common-widget-services.ts | 17 +++ ...common-tilt-widget-settings.component.html | 22 ++++ ...ommon-tilt-widget-settings.component.scss} | 0 .../common-tilt-widget-settings.component.ts | 82 +++++++++++++ .../lib/common/tilt-widgets-settings/index.ts | 1 + .../lib/common/unified-tilt-widget-config.ts | 3 + ...me-start-widget-blocker-checker.service.ts | 8 +- .../src/lib/i-widget-read-task-factory.ts | 8 ++ modules/widgets/src/lib/pitch/index.ts | 2 + .../pitch/pitch-sensor-widget.component.html | 49 ++++++++ .../pitch/pitch-sensor-widget.component.scss | 47 ++++++++ .../pitch/pitch-sensor-widget.component.ts | 58 +++++++++ ...pitch-widget-component-factory.service.ts} | 25 ++-- .../src/lib/pitch/pitch-widget.selectors.ts | 8 ++ .../src/lib/pitch/provide-pitch-widget.ts | 9 ++ modules/widgets/src/lib/provide-widgets.ts | 16 ++- modules/widgets/src/lib/roll/index.ts | 2 + .../src/lib/roll/provide-roll-widget.ts | 9 ++ .../roll-widget-component-factory.service.ts | 49 ++++++++ .../src/lib/roll/roll-widget.component.html | 49 ++++++++ .../src/lib/roll/roll-widget.component.scss | 40 +++++++ .../src/lib/roll/roll-widget.component.ts | 58 +++++++++ .../src/lib/roll/roll-widget.selectors.ts | 8 ++ ...mperature-widget-config-factory.service.ts | 9 +- ...rature-widget-read-task-factory.service.ts | 21 ++-- modules/widgets/src/lib/tilt/index.ts | 6 - .../src/lib/tilt/provide-tilt-widget.ts | 17 --- .../widgets/src/lib/tilt/settings/index.ts | 1 - ...tilt-sensor-widget-settings.component.html | 29 ----- .../tilt-sensor-widget-settings.component.ts | 111 ------------------ ...dget-settings-component-factory.service.ts | 23 ---- modules/widgets/src/lib/tilt/widget/index.ts | 1 - .../lib/tilt/widget/pitch-indicator/index.ts | 1 - .../pitch-indicator.component.html | 14 --- .../pitch-indicator.component.scss | 20 ---- .../pitch-indicator.component.ts | 16 --- .../lib/tilt/widget/roll-indicator/index.ts | 1 - .../roll-indicator.component.html | 14 --- .../roll-indicator.component.scss | 13 -- .../roll-indicator.component.ts | 16 --- .../widget/tilt-sensor-widget.component.html | 26 ---- .../widget/tilt-sensor-widget.component.scss | 19 --- .../widget/tilt-sensor-widget.component.ts | 61 ---------- .../lib/tilt/widget/yaw-indicator/index.ts | 1 - .../yaw-indicator.component.html | 16 --- .../yaw-indicator.component.scss | 20 ---- .../yaw-indicator/yaw-indicator.component.ts | 22 ---- .../voltage-widget-config-factory.service.ts | 7 +- ...oltage-widget-read-task-factory.service.ts | 23 ++-- .../lib/widget-component-factory.service.ts | 18 ++- .../src/lib/widget-config-factory.service.ts | 16 ++- .../widget-connection-info-l10n.service.ts | 4 +- .../lib/widget-read-task-factory.service.ts | 44 ------- ...dget-settings-component-factory.service.ts | 18 ++- .../widget-type-to-l10n-key-mapper.service.ts | 6 +- .../lib/widgets-read-task-factory.service.ts | 44 +++++++ modules/widgets/src/lib/yaw/index.ts | 2 + .../widgets/src/lib/yaw/provide-yaw-widget.ts | 9 ++ .../yaw-widget-component-factory.service.ts | 49 ++++++++ .../src/lib/yaw/yaw-widget.component.html | 52 ++++++++ .../src/lib/yaw/yaw-widget.component.scss | 47 ++++++++ .../src/lib/yaw/yaw-widget.component.ts | 64 ++++++++++ .../src/lib/yaw/yaw-widget.selectors.ts | 8 ++ src/assets/i18n/en.json | 37 ++++-- src/assets/i18n/ru.json | 37 ++++-- 95 files changed, 1434 insertions(+), 700 deletions(-) delete mode 100644 modules/store/src/lib/effects/tasks-processing/scheme-pre-run/i-widget-read-task-factory.ts create mode 100644 modules/store/src/lib/effects/tasks-processing/scheme-pre-run/i-widgets-read-tasks-factory.ts rename modules/widgets/src/lib/{tilt/tilt-widget-blocker-checker.service.ts => common/common-tilt-widgets-blocker-checker.service.ts} (80%) rename modules/widgets/src/lib/{tilt/tilt-widget-config-factory.service.ts => common/common-tilt-widgets-config-factory.service.ts} (53%) create mode 100644 modules/widgets/src/lib/common/common-tilt-widgets-form-builder.service.ts rename modules/widgets/src/lib/{tilt/tilt-widget-read-task-factory.service.ts => common/common-tilt-widgets-read-task-factory.service.ts} (50%) create mode 100644 modules/widgets/src/lib/common/common-tilt-widgets-settings-component-factory.service.ts create mode 100644 modules/widgets/src/lib/common/index.ts create mode 100644 modules/widgets/src/lib/common/provide-common-widget-services.ts create mode 100644 modules/widgets/src/lib/common/tilt-widgets-settings/common-tilt-widget-settings.component.html rename modules/widgets/src/lib/{tilt/settings/tilt-sensor-widget-settings.component.scss => common/tilt-widgets-settings/common-tilt-widget-settings.component.scss} (100%) create mode 100644 modules/widgets/src/lib/common/tilt-widgets-settings/common-tilt-widget-settings.component.ts create mode 100644 modules/widgets/src/lib/common/tilt-widgets-settings/index.ts create mode 100644 modules/widgets/src/lib/common/unified-tilt-widget-config.ts create mode 100644 modules/widgets/src/lib/i-widget-read-task-factory.ts create mode 100644 modules/widgets/src/lib/pitch/index.ts create mode 100644 modules/widgets/src/lib/pitch/pitch-sensor-widget.component.html create mode 100644 modules/widgets/src/lib/pitch/pitch-sensor-widget.component.scss create mode 100644 modules/widgets/src/lib/pitch/pitch-sensor-widget.component.ts rename modules/widgets/src/lib/{tilt/tilt-widget-component-factory.service.ts => pitch/pitch-widget-component-factory.service.ts} (61%) create mode 100644 modules/widgets/src/lib/pitch/pitch-widget.selectors.ts create mode 100644 modules/widgets/src/lib/pitch/provide-pitch-widget.ts create mode 100644 modules/widgets/src/lib/roll/index.ts create mode 100644 modules/widgets/src/lib/roll/provide-roll-widget.ts create mode 100644 modules/widgets/src/lib/roll/roll-widget-component-factory.service.ts create mode 100644 modules/widgets/src/lib/roll/roll-widget.component.html create mode 100644 modules/widgets/src/lib/roll/roll-widget.component.scss create mode 100644 modules/widgets/src/lib/roll/roll-widget.component.ts create mode 100644 modules/widgets/src/lib/roll/roll-widget.selectors.ts delete mode 100644 modules/widgets/src/lib/tilt/index.ts delete mode 100644 modules/widgets/src/lib/tilt/provide-tilt-widget.ts delete mode 100644 modules/widgets/src/lib/tilt/settings/index.ts delete mode 100644 modules/widgets/src/lib/tilt/settings/tilt-sensor-widget-settings.component.html delete mode 100644 modules/widgets/src/lib/tilt/settings/tilt-sensor-widget-settings.component.ts delete mode 100644 modules/widgets/src/lib/tilt/tilt-widget-settings-component-factory.service.ts delete mode 100644 modules/widgets/src/lib/tilt/widget/index.ts delete mode 100644 modules/widgets/src/lib/tilt/widget/pitch-indicator/index.ts delete mode 100644 modules/widgets/src/lib/tilt/widget/pitch-indicator/pitch-indicator.component.html delete mode 100644 modules/widgets/src/lib/tilt/widget/pitch-indicator/pitch-indicator.component.scss delete mode 100644 modules/widgets/src/lib/tilt/widget/pitch-indicator/pitch-indicator.component.ts delete mode 100644 modules/widgets/src/lib/tilt/widget/roll-indicator/index.ts delete mode 100644 modules/widgets/src/lib/tilt/widget/roll-indicator/roll-indicator.component.html delete mode 100644 modules/widgets/src/lib/tilt/widget/roll-indicator/roll-indicator.component.scss delete mode 100644 modules/widgets/src/lib/tilt/widget/roll-indicator/roll-indicator.component.ts delete mode 100644 modules/widgets/src/lib/tilt/widget/tilt-sensor-widget.component.html delete mode 100644 modules/widgets/src/lib/tilt/widget/tilt-sensor-widget.component.scss delete mode 100644 modules/widgets/src/lib/tilt/widget/tilt-sensor-widget.component.ts delete mode 100644 modules/widgets/src/lib/tilt/widget/yaw-indicator/index.ts delete mode 100644 modules/widgets/src/lib/tilt/widget/yaw-indicator/yaw-indicator.component.html delete mode 100644 modules/widgets/src/lib/tilt/widget/yaw-indicator/yaw-indicator.component.scss delete mode 100644 modules/widgets/src/lib/tilt/widget/yaw-indicator/yaw-indicator.component.ts delete mode 100644 modules/widgets/src/lib/widget-read-task-factory.service.ts create mode 100644 modules/widgets/src/lib/widgets-read-task-factory.service.ts create mode 100644 modules/widgets/src/lib/yaw/index.ts create mode 100644 modules/widgets/src/lib/yaw/provide-yaw-widget.ts create mode 100644 modules/widgets/src/lib/yaw/yaw-widget-component-factory.service.ts create mode 100644 modules/widgets/src/lib/yaw/yaw-widget.component.html create mode 100644 modules/widgets/src/lib/yaw/yaw-widget.component.scss create mode 100644 modules/widgets/src/lib/yaw/yaw-widget.component.ts create mode 100644 modules/widgets/src/lib/yaw/yaw-widget.selectors.ts diff --git a/modules/pages/control-scheme-view/src/lib/control-scheme-page.component.ts b/modules/pages/control-scheme-view/src/lib/control-scheme-page.component.ts index 26b698e2..111a5abc 100644 --- a/modules/pages/control-scheme-view/src/lib/control-scheme-page.component.ts +++ b/modules/pages/control-scheme-view/src/lib/control-scheme-page.component.ts @@ -102,7 +102,8 @@ export class ControlSchemePageComponent implements OnInit, OnDestroy, ISchemeRun return this.widgetDefaultConfigFactory.createConfigs( addableWidgetData.ios, addableWidgetData.portModes, - addableWidgetData.portModesInfo + addableWidgetData.portModesInfo, + addableWidgetData.existingWidgets ); }) ); diff --git a/modules/pages/control-scheme-view/src/lib/control-scheme-page.selectors.ts b/modules/pages/control-scheme-view/src/lib/control-scheme-page.selectors.ts index e996a8b0..494c5c03 100644 --- a/modules/pages/control-scheme-view/src/lib/control-scheme-page.selectors.ts +++ b/modules/pages/control-scheme-view/src/lib/control-scheme-page.selectors.ts @@ -18,6 +18,7 @@ import { HUB_RUNTIME_DATA_SELECTORS, HubModel, ROUTER_SELECTORS, + WidgetConfigModel, attachedIoModesIdFn, attachedIoPortModeInfoIdFn, attachedIosIdFn @@ -288,28 +289,22 @@ export const CONTROL_SCHEME_PAGE_SELECTORS = { ios: AttachedIoModel[]; portModes: Dictionary; portModesInfo: Dictionary; + existingWidgets: WidgetConfigModel[]; } => { if (!controlScheme || schemeRunningState !== ControlSchemeRunState.Idle) { return { ios: [], portModes: {}, - portModesInfo: {} + portModesInfo: {}, + existingWidgets: [] }; } - // There are certain limitations on IO value reading: only one IO mode can be used at a time. - // This means that if an IO is used for a widget, it cannot be re-used for another widget. - // And if an IO is used for a binding, it also cannot be used for a widget due to output mode not strictly matching to input mode. - - const existingIoWidgetIds = new Set(controlScheme.widgets.map((widget) => attachedIosIdFn(widget))); - const iosWithoutWidgets = attachedIos.filter((attachedIo) => !existingIoWidgetIds.has(attachedIosIdFn(attachedIo))); - - const controlledIosIds = new Set(controlScheme.bindings.map((binding) => attachedIosIdFn(binding))); - const remainingIos = iosWithoutWidgets.filter((attachedIo) => !controlledIosIds.has(attachedIosIdFn(attachedIo))); return { - ios: remainingIos, + ios: attachedIos, portModes: ioPortModes, - portModesInfo: portModesInfo + portModesInfo: portModesInfo, + existingWidgets: controlScheme.widgets }; } ), diff --git a/modules/pages/control-scheme-view/src/lib/widgets/i-control-scheme-widget-config-factory.ts b/modules/pages/control-scheme-view/src/lib/widgets/i-control-scheme-widget-config-factory.ts index b79a7ec9..d28ee105 100644 --- a/modules/pages/control-scheme-view/src/lib/widgets/i-control-scheme-widget-config-factory.ts +++ b/modules/pages/control-scheme-view/src/lib/widgets/i-control-scheme-widget-config-factory.ts @@ -6,7 +6,8 @@ export interface IControlSchemeWidgetConfigFactory, - portModesInfo: Dictionary + portModesInfo: Dictionary, + existingWidgets: WidgetConfigModel[] ): T[]; } diff --git a/modules/pages/control-scheme-view/src/lib/widgets/widgets-grid/control-scheme-widgets-grid.component.scss b/modules/pages/control-scheme-view/src/lib/widgets/widgets-grid/control-scheme-widgets-grid.component.scss index b8c45d72..2bc18b0e 100644 --- a/modules/pages/control-scheme-view/src/lib/widgets/widgets-grid/control-scheme-widgets-grid.component.scss +++ b/modules/pages/control-scheme-view/src/lib/widgets/widgets-grid/control-scheme-widgets-grid.component.scss @@ -1,7 +1,7 @@ :host { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - grid-auto-rows: 150px; + grid-auto-rows: 180px; gap: 15px; justify-items: stretch; } diff --git a/modules/shared/misc/src/lib/types/widget-type.ts b/modules/shared/misc/src/lib/types/widget-type.ts index 23830fbf..444d9e45 100644 --- a/modules/shared/misc/src/lib/types/widget-type.ts +++ b/modules/shared/misc/src/lib/types/widget-type.ts @@ -1,5 +1,7 @@ export enum WidgetType { Voltage, - Tilt, - Temperature + Pitch, + Temperature, + Yaw, + Roll } diff --git a/modules/shared/ui/src/lib/changelog/changelog.ts b/modules/shared/ui/src/lib/changelog/changelog.ts index 66f1b2f7..a092f177 100644 --- a/modules/shared/ui/src/lib/changelog/changelog.ts +++ b/modules/shared/ui/src/lib/changelog/changelog.ts @@ -7,7 +7,9 @@ export const CHANGELOG: Changelog = [ { version: '1.3.0', changeL10nKeys: [ - 'changelog.1-3-0.featOnOffToggleInputPreset' + 'changelog.1-3-0.featOnOffToggleInputPreset', + 'changelog.1-3-0.featSeparatePitchYawRollTiltSensorWidgets', + 'changelog.bugfixesAndImprovements' ] }, { diff --git a/modules/shared/ui/src/lib/tilt-gauge/tilt-gauge-sectors/tilt-gauge-sector-highlight.pipe.ts b/modules/shared/ui/src/lib/tilt-gauge/tilt-gauge-sectors/tilt-gauge-sector-highlight.pipe.ts index 9f15fae0..67a4b5dc 100644 --- a/modules/shared/ui/src/lib/tilt-gauge/tilt-gauge-sectors/tilt-gauge-sector-highlight.pipe.ts +++ b/modules/shared/ui/src/lib/tilt-gauge/tilt-gauge-sectors/tilt-gauge-sector-highlight.pipe.ts @@ -10,10 +10,10 @@ import { TiltGaugeSectorDefinition } from './tilt-gauge-sector-definition'; }) export class TiltGaugeSectorHighlightPipe implements PipeTransform { public transform( - tiltDegrees?: number, - gaugeSector?: TiltGaugeSectorDefinition + tiltDegrees: number | null, + gaugeSector: TiltGaugeSectorDefinition ): boolean { - if (gaugeSector === undefined || tiltDegrees === undefined) { + if (tiltDegrees === null) { return false; } diff --git a/modules/shared/ui/src/lib/tilt-gauge/tilt-gauge-sectors/tilt-gauge-sectors.component.ts b/modules/shared/ui/src/lib/tilt-gauge/tilt-gauge-sectors/tilt-gauge-sectors.component.ts index fa35cf9d..7aef3f3c 100644 --- a/modules/shared/ui/src/lib/tilt-gauge/tilt-gauge-sectors/tilt-gauge-sectors.component.ts +++ b/modules/shared/ui/src/lib/tilt-gauge/tilt-gauge-sectors/tilt-gauge-sectors.component.ts @@ -17,7 +17,7 @@ import { TiltGaugeSectorHighlightPipe } from './tilt-gauge-sector-highlight.pipe export class TiltGaugeSectorsComponent { @Input() public viewBox = '0 0 0 0'; - @Input() public tiltDegrees?: number; + @Input() public tiltDegrees: number | null = null; @Input('libTiltGaugeSectors') public sectors: TiltGaugeSectorDefinition[] = []; } diff --git a/modules/shared/ui/src/lib/tilt-gauge/tilt-gauge.component.html b/modules/shared/ui/src/lib/tilt-gauge/tilt-gauge.component.html index 3990f422..1ab8a29c 100644 --- a/modules/shared/ui/src/lib/tilt-gauge/tilt-gauge.component.html +++ b/modules/shared/ui/src/lib/tilt-gauge/tilt-gauge.component.html @@ -13,7 +13,7 @@ class="brackets" > - @if (iconTemplate && tiltDegrees !== undefined) { + @if (iconTemplate && tiltDegrees !== null) { diff --git a/modules/shared/ui/src/lib/tilt-gauge/tilt-gauge.component.ts b/modules/shared/ui/src/lib/tilt-gauge/tilt-gauge.component.ts index df35a112..df1d9eaf 100644 --- a/modules/shared/ui/src/lib/tilt-gauge/tilt-gauge.component.ts +++ b/modules/shared/ui/src/lib/tilt-gauge/tilt-gauge.component.ts @@ -48,7 +48,7 @@ export class TiltGaugeComponent implements OnInit { private _viewBox: string; - private _tiltDegrees?: number; + private _tiltDegrees: number | null = null; private _iconTransform = 'rotate(0)'; @@ -64,13 +64,13 @@ export class TiltGaugeComponent implements OnInit { @Input() public set tiltDegrees( - v: number | undefined + v: number | null ) { this._tiltDegrees = v; this.updateIconTransform(); } - public get tiltDegrees(): number | undefined { + public get tiltDegrees(): number | null { return this._tiltDegrees; } diff --git a/modules/store/src/lib/actions/attached-io-props.actions.ts b/modules/store/src/lib/actions/attached-io-props.actions.ts index c76d6ffd..d4202465 100644 --- a/modules/store/src/lib/actions/attached-io-props.actions.ts +++ b/modules/store/src/lib/actions/attached-io-props.actions.ts @@ -1,12 +1,15 @@ import { createActionGroup, props } from '@ngrx/store'; -import { TiltData } from 'rxpoweredup'; export const ATTACHED_IO_PROPS_ACTIONS = createActionGroup({ source: 'Attached IO Props', events: { 'motor encoder offset received': props<{ hubId: string; portId: number; offset: number }>(), 'startup servo calibration data received': props<{ hubId: string; portId: number; range: number; aposCenter: number }>(), - 'compensate tilt': props<{ hubId: string; portId: number; compensationData: TiltData }>(), - 'reset tilt compensation': props<{ hubId: string; portId: number }>(), + 'compensate pitch': props<{ hubId: string; portId: number; currentPitch: number }>(), + 'compensate yaw': props<{ hubId: string; portId: number; currentYaw: number }>(), + 'compensate roll': props<{ hubId: string; portId: number; currentRoll: number }>(), + 'reset pitch compensation': props<{ hubId: string; portId: number }>(), + 'reset yaw compensation': props<{ hubId: string; portId: number }>(), + 'reset roll compensation': props<{ hubId: string; portId: number }>(), } }); diff --git a/modules/store/src/lib/effects/tasks-processing/index.ts b/modules/store/src/lib/effects/tasks-processing/index.ts index c5ac333b..65a1894f 100644 --- a/modules/store/src/lib/effects/tasks-processing/index.ts +++ b/modules/store/src/lib/effects/tasks-processing/index.ts @@ -11,7 +11,7 @@ export * from './i-task-filter'; export * from './i-task-runner'; export * from './i-task-factory'; export * from './i-task-input-extractor'; -export { IWidgetReadTaskFactory, WIDGET_READ_TASK_FACTORY } from './scheme-pre-run'; +export { IWidgetsReadTasksFactory, WIDGET_READ_TASKS_FACTORY } from './scheme-pre-run'; export const TASK_PROCESSING_EFFECTS: Record = { preRunScheme: PRE_RUN_SCHEME_EFFECT, diff --git a/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/create-widget-read-tasks.ts b/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/create-widget-read-tasks.ts index 2cf19d4e..4f20ddb0 100644 --- a/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/create-widget-read-tasks.ts +++ b/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/create-widget-read-tasks.ts @@ -3,22 +3,19 @@ import { Store } from '@ngrx/store'; import { CONTROL_SCHEME_WIDGETS_DATA_ACTIONS } from '../../../actions'; import { ControlSchemeModel } from '../../../models'; -import { HubStorageService } from '../../../hub-storage.service'; -import { IWidgetReadTaskFactory } from './i-widget-read-task-factory'; +import { IWidgetsReadTasksFactory } from './i-widgets-read-tasks-factory'; export function createWidgetReadTasks( scheme: ControlSchemeModel, - hubStorage: HubStorageService, store: Store, - schemeStop$: Observable, - widgetTaskFactory: IWidgetReadTaskFactory + widgetTaskFactory: IWidgetsReadTasksFactory ): Array> { const result: Array> = []; - for (const widgetConfig of scheme.widgets) { - const readerTask = widgetTaskFactory.createReadTask(widgetConfig, store, hubStorage, schemeStop$); + const readerTasks = widgetTaskFactory.createReadTasks(scheme.widgets); + for (const task of readerTasks) { const obs = new Observable((subscriber) => { let initialValueReceived = false; - readerTask.subscribe((taskData) => { + task.subscribe((taskData) => { if (!initialValueReceived) { initialValueReceived = true; subscriber.next(null); diff --git a/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/i-widget-read-task-factory.ts b/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/i-widget-read-task-factory.ts deleted file mode 100644 index df2c9981..00000000 --- a/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/i-widget-read-task-factory.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Store } from '@ngrx/store'; -import { Observable } from 'rxjs'; -import { InjectionToken } from '@angular/core'; - -import { ControlSchemeWidgetsDataModel, WidgetConfigModel } from '../../../models'; -import { HubStorageService } from '../../../hub-storage.service'; - -export interface IWidgetReadTaskFactory { - createReadTask( - config: TConfig, - store: Store, - hubStorage: HubStorageService, - schemeStop$: Observable - ): Observable<{ widgetId: number; data: ControlSchemeWidgetsDataModel }>; -} - -export const WIDGET_READ_TASK_FACTORY = new InjectionToken('WIDGET_READ_TASK_FACTORY'); diff --git a/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/i-widgets-read-tasks-factory.ts b/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/i-widgets-read-tasks-factory.ts new file mode 100644 index 00000000..ad30fff7 --- /dev/null +++ b/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/i-widgets-read-tasks-factory.ts @@ -0,0 +1,12 @@ +import { Observable } from 'rxjs'; +import { InjectionToken } from '@angular/core'; + +import { ControlSchemeWidgetsDataModel, WidgetConfigModel } from '../../../models'; + +export interface IWidgetsReadTasksFactory { + createReadTasks( + configs: TConfig[] + ): Array>; +} + +export const WIDGET_READ_TASKS_FACTORY = new InjectionToken('WIDGET_READ_TASK_FACTORY'); diff --git a/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/index.ts b/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/index.ts index ed0903b6..9de82fdc 100644 --- a/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/index.ts +++ b/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/index.ts @@ -1,2 +1,2 @@ export * from './pre-run-scheme.effect'; -export * from './i-widget-read-task-factory'; +export * from './i-widgets-read-tasks-factory'; diff --git a/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/pre-run-scheme.effect.ts b/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/pre-run-scheme.effect.ts index 5bc7ffdb..83fb05ca 100644 --- a/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/pre-run-scheme.effect.ts +++ b/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/pre-run-scheme.effect.ts @@ -13,7 +13,7 @@ import { createPreRunSetAccelerationProfileTasks } from './create-pre-run-set-ac import { createPreRunSetDecelerationProfileTasks } from './create-pre-run-set-deceleration-profile-tasks'; import { createWidgetReadTasks } from './create-widget-read-tasks'; import { HubServoCalibrationFacadeService } from '../../../hub-facades'; -import { IWidgetReadTaskFactory, WIDGET_READ_TASK_FACTORY } from './i-widget-read-task-factory'; +import { IWidgetsReadTasksFactory, WIDGET_READ_TASKS_FACTORY } from './i-widgets-read-tasks-factory'; export const PRE_RUN_SCHEME_EFFECT = createEffect(( actions: Actions = inject(Actions), @@ -21,7 +21,7 @@ export const PRE_RUN_SCHEME_EFFECT = createEffect(( store: Store = inject(Store), hubCalibrationFacade: HubServoCalibrationFacadeService = inject(HubServoCalibrationFacadeService), appConfig: IAppConfig = inject(APP_CONFIG), - widgetReadTaskFactory: IWidgetReadTaskFactory = inject(WIDGET_READ_TASK_FACTORY) + widgetReadTaskFactory: IWidgetsReadTasksFactory = inject(WIDGET_READ_TASKS_FACTORY) ) => { return actions.pipe( ofType(CONTROL_SCHEME_ACTIONS.startScheme), @@ -29,13 +29,10 @@ export const PRE_RUN_SCHEME_EFFECT = createEffect(( map(([ , scheme ]) => scheme), filter((scheme): scheme is ControlSchemeModel => !!scheme), switchMap((scheme) => { - const controlSchemeStopEvent$ = actions.pipe( - ofType(CONTROL_SCHEME_ACTIONS.stopScheme), - ); const combinedTasks = [ ...createPreRunSetAccelerationProfileTasks(scheme, hubStorage), ...createPreRunSetDecelerationProfileTasks(scheme, hubStorage), - ...createWidgetReadTasks(scheme, hubStorage, store, controlSchemeStopEvent$, widgetReadTaskFactory) + ...createWidgetReadTasks(scheme, store, widgetReadTaskFactory) ]; // TODO: move to Bindings module const calibrationServoTasks = createPreRunServoCalibrationTasks(scheme, hubCalibrationFacade, store, appConfig); diff --git a/modules/store/src/lib/migrations/v30-v31/v30-store.ts b/modules/store/src/lib/migrations/v30-v31/v30-store.ts index fbe1333a..d43277de 100644 --- a/modules/store/src/lib/migrations/v30-v31/v30-store.ts +++ b/modules/store/src/lib/migrations/v30-v31/v30-store.ts @@ -16,6 +16,26 @@ export type V31SetAngleBinding = V31Binding & { bindingType: ControlSchemeBindin export type V31InputConfig = V31SetAngleBinding['inputs'][SetAngleBindingInputAction.SetAngle]; +export const OLD_TITLE_WIDGET_TYPE = 1; + +export type OldTiltWidgetConfigModel = { + widgetType: number; + hubId: string; + portId: number; + modeId: number; + valueChangeThreshold: number; + invertYaw: boolean; + invertPitch: boolean; + invertRoll: boolean; + id: number; + title: string; + width: number; + height: number; +}; + +export type V31WidgetConfigModel = ExtractArrayType; +export type V30WidgetConfigModel = V31WidgetConfigModel | OldTiltWidgetConfigModel; + export enum OldInputGain { Linear, Exponential, @@ -60,7 +80,11 @@ export type V30Bindings = V30SpeedBinding | V30TrainBinding | V30GearboxBinding | V30SetAngleBinding; -export type V30ControlSchemesEntitiesState = Omit & { bindings: V30Bindings[] }; + +export type V30ControlSchemesEntitiesState = Omit & { + bindings: V30Bindings[]; + widgets: V30WidgetConfigModel[]; +}; export type V30Store = Override & EntityState; diff --git a/modules/store/src/lib/migrations/v30-v31/v30-to-v31-migration.service.spec.ts b/modules/store/src/lib/migrations/v30-v31/v30-to-v31-migration.service.spec.ts index c011d36b..cf5ff16b 100644 --- a/modules/store/src/lib/migrations/v30-v31/v30-to-v31-migration.service.spec.ts +++ b/modules/store/src/lib/migrations/v30-v31/v30-to-v31-migration.service.spec.ts @@ -1,6 +1,6 @@ import { anything, instance, mock, when } from 'ts-mockito'; import { ControllerType, GamepadProfile, GamepadProfileFactoryService, GamepadSettings } from '@app/controller-profiles'; -import { ControlSchemeBindingType, DeepPartial } from '@app/shared-misc'; +import { ControlSchemeBindingType, DeepPartial, WidgetType } from '@app/shared-misc'; import { AppStoreVersion } from '../../app-store-version'; import { InputPipeType } from '../../models'; @@ -17,7 +17,7 @@ import { V22ToV23MigrationService } from '../v22-v23'; import { V26ToV27MigrationService } from '../v26-v27'; import { V29ToV30MigrationService } from '../v29-v30'; import { V31Store } from '../v31'; -import { OldInputGain, V30Store } from './v30-store'; +import { OLD_TITLE_WIDGET_TYPE, OldInputGain, V30Store } from './v30-store'; describe('v30 to v31 migration', () => { let v30Store: DeepPartial; @@ -52,9 +52,88 @@ describe('v30 to v31 migration', () => { const v28Store = v27Tov28Migration.migrate(v27Store); const v29Store = v28Tov29Migration.migrate(v28Store); v30Store = v29Tov30Migration.migrate(v29Store); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + v30Store.controlSchemes!.entities!['Speed control test']!.widgets = [ + { + widgetType: OLD_TITLE_WIDGET_TYPE, + id: 1, + title: 'Tilt', + hubId: '90:84:2b:4f:93:20', + portId: 99, + modeId: 0, + valueChangeThreshold: 5, + width: 2, + height: 2, + invertRoll: false, + invertPitch: true, + invertYaw: false + }, + { + widgetType: 0, + id: 2, + title: 'Voltage', + hubId: '90:84:2b:4f:93:20', + portId: 60, + modeId: 0, + valueChangeThreshold: 0.05, + width: 1, + height: 1 + } + ]; v31Store = v30Tov31Migration.migrate(v30Store); }); + it('should migrate widgets', () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const widgets = v31Store.controlSchemes!.entities!['Speed control test']!.widgets; + expect(widgets).toEqual([ + { + widgetType: WidgetType.Pitch, + id: 1, + title: 'Pitch', + hubId: '90:84:2b:4f:93:20', + portId: 99, + modeId: 0, + valueChangeThreshold: 5, + width: 1, + height: 1, + invert: true + }, { + widgetType: WidgetType.Roll, + id: 2, + title: 'Roll', + hubId: '90:84:2b:4f:93:20', + portId: 99, + modeId: 0, + valueChangeThreshold: 5, + width: 1, + height: 1, + invert: false + }, { + widgetType: WidgetType.Yaw, + id: 3, + title: 'Yaw', + hubId: '90:84:2b:4f:93:20', + portId: 99, + modeId: 0, + valueChangeThreshold: 5, + width: 1, + height: 1, + invert: false + }, { + widgetType: 0, + id: 4, + title: 'Voltage', + hubId: '90:84:2b:4f:93:20', + portId: 60, + modeId: 0, + valueChangeThreshold: 0.05, + width: 1, + height: 1 + } + ]); + }); + it('should migrate input gain to input pipes', () => { const controlSchemes = v31Store.controlSchemes; if (!controlSchemes?.entities) { diff --git a/modules/store/src/lib/migrations/v30-v31/v30-to-v31-migration.service.ts b/modules/store/src/lib/migrations/v30-v31/v30-to-v31-migration.service.ts index 4e266092..69289cf6 100644 --- a/modules/store/src/lib/migrations/v30-v31/v30-to-v31-migration.service.ts +++ b/modules/store/src/lib/migrations/v30-v31/v30-to-v31-migration.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { ControlSchemeBindingType, DeepPartial } from '@app/shared-misc'; +import { ControlSchemeBindingType, DeepPartial, WidgetType } from '@app/shared-misc'; import { AppStoreVersion } from '../../app-store-version'; import { IMigration } from '../i-migration'; @@ -14,7 +14,9 @@ import { } from '../../models'; import { V31Store } from '../v31'; import { + OLD_TITLE_WIDGET_TYPE, OldInputGain, + OldTiltWidgetConfigModel, V30ControlSchemesEntitiesState, V30GearboxBinding, V30InputConfig, @@ -24,6 +26,7 @@ import { V30StepperBinding, V30Store, V30TrainBinding, + V30WidgetConfigModel, V31Binding, V31GearboxBinding, V31InputConfig, @@ -31,7 +34,8 @@ import { V31SetAngleBinding, V31SpeedBinding, V31StepperBinding, - V31TrainBinding + V31TrainBinding, + V31WidgetConfigModel } from './v30-store'; @Injectable() @@ -84,7 +88,8 @@ export class V30ToV31MigrationService implements IMigration // eslint-disable-next-line @typescript-eslint/no-non-null-assertion result.controlSchemes!.entities![k] = { ...controlScheme, - bindings + bindings, + widgets: this.migrateWidgets(controlScheme.widgets) }; }); return result; @@ -222,4 +227,63 @@ export class V30ToV31MigrationService implements IMigration inputPipes: [] }; } + + private migrateWidgets( + widgets: V30WidgetConfigModel[] + ): V31WidgetConfigModel[] { + const result: V31WidgetConfigModel[] = []; + let id = 1; + widgets.forEach((w) => { + if (this.isOldTiltWidget(w)) { + result.push({ + widgetType: WidgetType.Pitch, + hubId: w.hubId, + portId: w.portId, + modeId: w.modeId, + valueChangeThreshold: w.valueChangeThreshold, + title: 'Pitch', + id: id++, + width: 1, + height: 1, + invert: w.invertPitch + }); + result.push({ + widgetType: WidgetType.Roll, + hubId: w.hubId, + portId: w.portId, + modeId: w.modeId, + valueChangeThreshold: w.valueChangeThreshold, + title: 'Roll', + id: id++, + width: 1, + height: 1, + invert: w.invertRoll + }); + result.push({ + widgetType: WidgetType.Yaw, + hubId: w.hubId, + portId: w.portId, + modeId: w.modeId, + valueChangeThreshold: w.valueChangeThreshold, + title: 'Yaw', + id: id++, + width: 1, + height: 1, + invert: w.invertYaw + }); + } else { + result.push({ + ...w, + id: id++ + }); + } + }); + return result; + } + + private isOldTiltWidget( + w: V30WidgetConfigModel + ): w is OldTiltWidgetConfigModel { + return w.widgetType === OLD_TITLE_WIDGET_TYPE; + } } diff --git a/modules/store/src/lib/models/control-scheme-widgets-data.model.ts b/modules/store/src/lib/models/control-scheme-widgets-data.model.ts index a370ff87..ea692b7d 100644 --- a/modules/store/src/lib/models/control-scheme-widgets-data.model.ts +++ b/modules/store/src/lib/models/control-scheme-widgets-data.model.ts @@ -7,7 +7,7 @@ export type ControlSchemeVoltageWidgetDataModel = { }; export type ControlSchemeTiltWidgetDataModel = { - widgetType: WidgetType.Tilt; + widgetType: WidgetType.Yaw | WidgetType.Roll | WidgetType.Pitch; tilt: TiltData; }; diff --git a/modules/store/src/lib/models/control-scheme.model.ts b/modules/store/src/lib/models/control-scheme.model.ts index b447313c..be57d955 100644 --- a/modules/store/src/lib/models/control-scheme.model.ts +++ b/modules/store/src/lib/models/control-scheme.model.ts @@ -209,15 +209,31 @@ export type VoltageWidgetConfigModel = { valueChangeThreshold: number; } & BaseWidgetConfigModel; -export type TiltWidgetConfigModel = { - widgetType: WidgetType.Tilt; +export type YawWidgetConfigModel = { + widgetType: WidgetType.Yaw; hubId: string; portId: number; modeId: number; valueChangeThreshold: number; - invertYaw: boolean; - invertPitch: boolean; - invertRoll: boolean; + invert: boolean; +} & BaseWidgetConfigModel; + +export type PitchWidgetConfigModel = { + widgetType: WidgetType.Pitch; + hubId: string; + portId: number; + modeId: number; + valueChangeThreshold: number; + invert: boolean; +} & BaseWidgetConfigModel; + +export type RollWidgetConfigModel = { + widgetType: WidgetType.Roll; + hubId: string; + portId: number; + modeId: number; + valueChangeThreshold: number; + invert: boolean; } & BaseWidgetConfigModel; export type TemperatureWidgetConfigModel = { @@ -228,7 +244,12 @@ export type TemperatureWidgetConfigModel = { valueChangeThreshold: number; } & BaseWidgetConfigModel; -export type WidgetConfigModel = VoltageWidgetConfigModel | TiltWidgetConfigModel | TemperatureWidgetConfigModel; +export type WidgetConfigModel = + VoltageWidgetConfigModel + | TemperatureWidgetConfigModel + | PitchWidgetConfigModel + | YawWidgetConfigModel + | RollWidgetConfigModel; export type ControlSchemeModel = { name: string; diff --git a/modules/store/src/lib/reducers/attached-io-props.reducer.ts b/modules/store/src/lib/reducers/attached-io-props.reducer.ts index 722285cb..3e14b1e5 100644 --- a/modules/store/src/lib/reducers/attached-io-props.reducer.ts +++ b/modules/store/src/lib/reducers/attached-io-props.reducer.ts @@ -50,32 +50,70 @@ export const ATTACHED_IO_PROPS_FEATURE = createFeature({ state ) ), - on(ATTACHED_IO_PROPS_ACTIONS.compensateTilt, (state, data): AttacheIoPropsState => { - const initialCompensationData = state.entities[hubAttachedIoPropsIdFn(data)]?.runtimeTiltCompensation ?? { yaw: 0, pitch: 0, roll: 0 }; + on(ATTACHED_IO_PROPS_ACTIONS.compensatePitch, (state, data): AttacheIoPropsState => { + const currentCompensation = state.entities[hubAttachedIoPropsIdFn(data)]?.runtimeTiltCompensation ?? { yaw: 0, pitch: 0, roll: 0 }; return ATTACHED_IO_PROPS_ENTITY_ADAPTER.upsertOne({ hubId: data.hubId, portId: data.portId, motorEncoderOffset: state.entities[hubAttachedIoPropsIdFn(data)]?.motorEncoderOffset ?? 0, startupServoCalibrationData: state.entities[hubAttachedIoPropsIdFn(data)]?.startupServoCalibrationData ?? null, runtimeTiltCompensation: { - yaw: initialCompensationData.yaw + data.compensationData.yaw, - pitch: initialCompensationData.pitch + data.compensationData.pitch, - roll: initialCompensationData.roll + data.compensationData.roll, + yaw: currentCompensation.yaw, + pitch: currentCompensation.pitch + data.currentPitch, + roll: currentCompensation.roll, }, }, state ); }), - on(ATTACHED_IO_PROPS_ACTIONS.resetTiltCompensation, (state, data): AttacheIoPropsState => - ATTACHED_IO_PROPS_ENTITY_ADAPTER.upsertOne({ + on(ATTACHED_IO_PROPS_ACTIONS.resetPitchCompensation, (state, data): AttacheIoPropsState => { + const currentCompensation = state.entities[hubAttachedIoPropsIdFn(data)]?.runtimeTiltCompensation ?? { yaw: 0, pitch: 0, roll: 0 }; + return ATTACHED_IO_PROPS_ENTITY_ADAPTER.upsertOne({ + hubId: data.hubId, + portId: data.portId, + motorEncoderOffset: state.entities[hubAttachedIoPropsIdFn(data)]?.motorEncoderOffset ?? 0, + startupServoCalibrationData: state.entities[hubAttachedIoPropsIdFn(data)]?.startupServoCalibrationData ?? null, + runtimeTiltCompensation: { + yaw: currentCompensation.yaw, + pitch: 0, + roll: currentCompensation.roll, + }, + }, + state + ); + }), + on(ATTACHED_IO_PROPS_ACTIONS.compensateYaw, (state, data): AttacheIoPropsState => { + const currentCompensation = state.entities[hubAttachedIoPropsIdFn(data)]?.runtimeTiltCompensation ?? { yaw: 0, pitch: 0, roll: 0 }; + return ATTACHED_IO_PROPS_ENTITY_ADAPTER.upsertOne({ + hubId: data.hubId, + portId: data.portId, + motorEncoderOffset: state.entities[hubAttachedIoPropsIdFn(data)]?.motorEncoderOffset ?? 0, + startupServoCalibrationData: state.entities[hubAttachedIoPropsIdFn(data)]?.startupServoCalibrationData ?? null, + runtimeTiltCompensation: { + yaw: currentCompensation.yaw + data.currentYaw, + pitch: currentCompensation.pitch, + roll: currentCompensation.roll, + }, + }, + state + ); + }), + on(ATTACHED_IO_PROPS_ACTIONS.resetYawCompensation, (state, data): AttacheIoPropsState => { + const currentCompensation = state.entities[hubAttachedIoPropsIdFn(data)]?.runtimeTiltCompensation ?? { yaw: 0, pitch: 0, roll: 0 }; + return ATTACHED_IO_PROPS_ENTITY_ADAPTER.upsertOne({ hubId: data.hubId, portId: data.portId, motorEncoderOffset: state.entities[hubAttachedIoPropsIdFn(data)]?.motorEncoderOffset ?? 0, startupServoCalibrationData: state.entities[hubAttachedIoPropsIdFn(data)]?.startupServoCalibrationData ?? null, - runtimeTiltCompensation: null, + runtimeTiltCompensation: { + yaw: 0, + pitch: currentCompensation.pitch, + roll: currentCompensation.roll, + }, }, state - )), + ); + }), on(HUBS_ACTIONS.forgetHub, HUBS_ACTIONS.disconnected, (state, { hubId }): AttacheIoPropsState => ATTACHED_IO_PROPS_ENTITY_ADAPTER.removeMany((io) => io.hubId === hubId, state) ) diff --git a/modules/store/src/lib/selectors/control-scheme-widgets-data.selectors.ts b/modules/store/src/lib/selectors/control-scheme-widgets-data.selectors.ts index 1a920a12..b2fb9e4d 100644 --- a/modules/store/src/lib/selectors/control-scheme-widgets-data.selectors.ts +++ b/modules/store/src/lib/selectors/control-scheme-widgets-data.selectors.ts @@ -4,18 +4,20 @@ import { WidgetType } from '@app/shared-misc'; import { ATTACHED_IO_PROPS_SELECTORS } from './attached-io-props.selectors'; import { CONTROL_SCHEME_WIDGET_DATA_FEATURE } from '../reducers'; -import { TiltWidgetConfigModel } from '../models'; export const CONTROL_SCHEME_WIDGETS_DATA_SELECTORS = { selectById: (widgetId: number) => createSelector( CONTROL_SCHEME_WIDGET_DATA_FEATURE.selectControlSchemeWidgetsDataState, (state) => state.widgetsData[widgetId] ), - selectWidgetTiltData: (widgetConfig: TiltWidgetConfigModel) => createSelector( - CONTROL_SCHEME_WIDGETS_DATA_SELECTORS.selectById(widgetConfig.id), - ATTACHED_IO_PROPS_SELECTORS.selectById(widgetConfig), + selectWidgetTiltData: ({ id, hubId, portId }: { id: number; hubId: string; portId: number }) => createSelector( + CONTROL_SCHEME_WIDGETS_DATA_SELECTORS.selectById(id), + ATTACHED_IO_PROPS_SELECTORS.selectById({hubId, portId}), (widgetData, attachedIoProps) => { - if (widgetData?.widgetType === WidgetType.Tilt) { + if (widgetData?.widgetType === WidgetType.Yaw + || widgetData?.widgetType === WidgetType.Pitch + || widgetData?.widgetType === WidgetType.Roll + ) { const tiltCompensationData: TiltData = attachedIoProps?.runtimeTiltCompensation ?? { yaw: 0, pitch: 0, roll: 0 }; return { yaw: widgetData.tilt.yaw - tiltCompensationData.yaw, diff --git a/modules/widgets/src/lib/tilt/tilt-widget-blocker-checker.service.ts b/modules/widgets/src/lib/common/common-tilt-widgets-blocker-checker.service.ts similarity index 80% rename from modules/widgets/src/lib/tilt/tilt-widget-blocker-checker.service.ts rename to modules/widgets/src/lib/common/common-tilt-widgets-blocker-checker.service.ts index c79eccd0..0d11548c 100644 --- a/modules/widgets/src/lib/tilt/tilt-widget-blocker-checker.service.ts +++ b/modules/widgets/src/lib/common/common-tilt-widgets-blocker-checker.service.ts @@ -1,10 +1,10 @@ -import { Injectable } from '@angular/core'; import { PortModeName } from 'rxpoweredup'; +import { Injectable } from '@angular/core'; import { IWidgetBlockerChecker } from '../i-widget-blocker-checker'; @Injectable() -export class TiltWidgetBlockerCheckerService implements IWidgetBlockerChecker { +export class CommonTiltWidgetsBlockerCheckerService implements IWidgetBlockerChecker { public canBeUsedWithInputModes( portModes: PortModeName[] ): boolean { diff --git a/modules/widgets/src/lib/tilt/tilt-widget-config-factory.service.ts b/modules/widgets/src/lib/common/common-tilt-widgets-config-factory.service.ts similarity index 53% rename from modules/widgets/src/lib/tilt/tilt-widget-config-factory.service.ts rename to modules/widgets/src/lib/common/common-tilt-widgets-config-factory.service.ts index 0f86445c..2227e240 100644 --- a/modules/widgets/src/lib/tilt/tilt-widget-config-factory.service.ts +++ b/modules/widgets/src/lib/common/common-tilt-widgets-config-factory.service.ts @@ -1,33 +1,38 @@ import { Injectable } from '@angular/core'; import { Dictionary } from '@ngrx/entity'; import { PortModeName } from 'rxpoweredup'; -import { WidgetType } from '@app/shared-misc'; import { AttachedIoModel, AttachedIoModesModel, AttachedIoPortModeInfoModel, - TiltWidgetConfigModel, + WidgetConfigModel, attachedIoModesIdFn, attachedIoPortModeInfoIdFn } from '@app/store'; -import { IControlSchemeWidgetConfigFactory } from '@app/control-scheme-view'; +import { WidgetType } from '@app/shared-misc'; -import { TiltWidgetBlockerCheckerService } from './tilt-widget-blocker-checker.service'; +import { UnifiedTiltWidgetConfig } from './unified-tilt-widget-config'; +import { CommonTiltWidgetsBlockerCheckerService } from './common-tilt-widgets-blocker-checker.service'; @Injectable() -export class TiltWidgetConfigFactoryService implements IControlSchemeWidgetConfigFactory { +export class CommonTiltWidgetsConfigFactoryService { constructor( - private blockerChecker: TiltWidgetBlockerCheckerService + private blockerChecker: CommonTiltWidgetsBlockerCheckerService ) { } - public createConfigs( + public createConfigs( + widgetType: T, attachedIos: AttachedIoModel[], ioPortModes: Dictionary, - portModesInfo: Dictionary - ): TiltWidgetConfigModel[] { - const result: TiltWidgetConfigModel[] = []; + portModesInfo: Dictionary, + existingWidgets: WidgetConfigModel[] + ): Array { + const result: Array = []; for (const io of attachedIos) { + if (existingWidgets.some((widget) => widget.widgetType === widgetType && widget.hubId === io.hubId && widget.portId === io.portId)) { + continue; + } const portInputModeIds = (ioPortModes[attachedIoModesIdFn(io)]?.portInputModes ?? []); const attacheIoPortModes = portInputModeIds.map((modeId) => { const portModeInfo = portModesInfo[attachedIoPortModeInfoIdFn({ ...io, modeId })]; @@ -37,31 +42,30 @@ export class TiltWidgetConfigFactoryService implements IControlSchemeWidgetConfi if (this.blockerChecker.canBeUsedWithInputModes(attacheIoPortModes.map((portMode) => portMode.name))) { const positionPortMode = attacheIoPortModes.find((name) => name.name === PortModeName.position); if (positionPortMode) { - result.push(this.createConfig(io.hubId, io.portId, positionPortMode.modeId)); + result.push(this.createConfig(widgetType, io.hubId, io.portId, positionPortMode.modeId)); } } } return result; } - private createConfig( + private createConfig( + widgetType: T, hubId: string, portId: number, modeId: number, - ): TiltWidgetConfigModel { + ): UnifiedTiltWidgetConfig & { widgetType: T } { return { id: 0, title: '', - widgetType: WidgetType.Tilt, + widgetType, hubId, portId, modeId, - valueChangeThreshold: 5, - width: 2, - height: 2, - invertYaw: false, - invertPitch: false, - invertRoll: false, - }; + valueChangeThreshold: 10, + width: 1, + height: 1, + invert: false + } as UnifiedTiltWidgetConfig & { widgetType: T }; } } diff --git a/modules/widgets/src/lib/common/common-tilt-widgets-form-builder.service.ts b/modules/widgets/src/lib/common/common-tilt-widgets-form-builder.service.ts new file mode 100644 index 00000000..2421f6ff --- /dev/null +++ b/modules/widgets/src/lib/common/common-tilt-widgets-form-builder.service.ts @@ -0,0 +1,77 @@ +import { Injectable } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; +import { ControlSchemeFormBuilderService } from '@app/shared-control-schemes'; +import { WidgetType } from '@app/shared-misc'; + +import { UnifiedTiltWidgetConfig } from './unified-tilt-widget-config'; + +export type CommonTiltWidgetsConfigForm = FormGroup<{ + id: FormControl; + title: FormControl; + hubId: FormControl; + portId: FormControl; + modeId: FormControl; + valueChangeThreshold: FormControl; + width: FormControl; + height: FormControl; + invert: FormControl; +}>; + +@Injectable() +export class CommonTiltWidgetsFormBuilderService { + constructor( + private readonly formBuilder: FormBuilder, + private readonly commonFormBuilder: ControlSchemeFormBuilderService + ) { + } + + public buildPitchWidgetForm(): CommonTiltWidgetsConfigForm { + return this.buildCommonForm(); + } + + public mapFormToTiltWidgetConfig( + widgetType: WidgetType.Pitch | WidgetType.Yaw, + form: CommonTiltWidgetsConfigForm + ): UnifiedTiltWidgetConfig | undefined { + if (form.controls.hubId.value === null + || form.controls.portId.value === null + || form.controls.modeId.value === null + || form.invalid + ) { + return undefined; + } + return { + widgetType: widgetType, + id: form.controls.id.value, + title: form.controls.title.value, + hubId: form.controls.hubId.value, + portId: form.controls.portId.value, + modeId: form.controls.modeId.value, + valueChangeThreshold: form.controls.valueChangeThreshold.value, + width: form.controls.width.value, + height: form.controls.height.value, + invert: form.controls.invert.value, + }; + } + + private buildCommonForm(): CommonTiltWidgetsConfigForm { + return this.formBuilder.group({ + id: this.formBuilder.control(0, { validators: Validators.required, nonNullable: true }), + title: this.formBuilder.control('', { validators: [ Validators.required ], nonNullable: true }), + hubId: this.commonFormBuilder.hubIdControl(), + portId: this.commonFormBuilder.portIdControl(), + modeId: this.formBuilder.control(null, { validators: Validators.required, nonNullable: false }), + valueChangeThreshold: this.formBuilder.control(10, { + validators: [ + Validators.required, + Validators.min(5), + Validators.max(30) + ], + nonNullable: true + }), + width: this.formBuilder.control(1, { validators: Validators.required, nonNullable: true }), + height: this.formBuilder.control(1, { validators: Validators.required, nonNullable: true }), + invert: this.formBuilder.control(false, { nonNullable: true }), + }); + } +} diff --git a/modules/widgets/src/lib/tilt/tilt-widget-read-task-factory.service.ts b/modules/widgets/src/lib/common/common-tilt-widgets-read-task-factory.service.ts similarity index 50% rename from modules/widgets/src/lib/tilt/tilt-widget-read-task-factory.service.ts rename to modules/widgets/src/lib/common/common-tilt-widgets-read-task-factory.service.ts index 782b8e67..8e5cd1e5 100644 --- a/modules/widgets/src/lib/tilt/tilt-widget-read-task-factory.service.ts +++ b/modules/widgets/src/lib/common/common-tilt-widgets-read-task-factory.service.ts @@ -1,22 +1,27 @@ -import { Store } from '@ngrx/store'; import { Observable, concatWith, map, takeUntil } from 'rxjs'; import { TiltData, ValueTransformers } from 'rxpoweredup'; import { Injectable } from '@angular/core'; -import { WidgetType } from '@app/shared-misc'; -import { ControlSchemeWidgetsDataModel, HubStorageService, IWidgetReadTaskFactory, TiltWidgetConfigModel } from '@app/store'; +import { Actions, ofType } from '@ngrx/effects'; +import { CONTROL_SCHEME_ACTIONS, ControlSchemeWidgetsDataModel, HubStorageService } from '@app/store'; + +import { IWidgetReadTaskFactory } from '../i-widget-read-task-factory'; +import { UnifiedTiltWidgetConfig } from './unified-tilt-widget-config'; @Injectable() -export class TiltWidgetReadTaskFactoryService implements IWidgetReadTaskFactory { +export class CommonTiltWidgetsReadTaskFactoryService implements IWidgetReadTaskFactory { + constructor( + private readonly hubStorage: HubStorageService, + private readonly actions: Actions + ) { + } + public createReadTask( - config: TiltWidgetConfigModel, - store: Store, - hubStorage: HubStorageService, - schemeStop$: Observable + config: UnifiedTiltWidgetConfig, ): Observable<{ widgetId: number; data: ControlSchemeWidgetsDataModel; }> { - const hub = hubStorage.get(config.hubId); + const hub = this.hubStorage.get(config.hubId); const tiltThreshold: TiltData = { roll: config.valueChangeThreshold, pitch: config.valueChangeThreshold, @@ -24,19 +29,15 @@ export class TiltWidgetReadTaskFactoryService implements IWidgetReadTaskFactory< }; return hub.ports.getPortValue(config.portId, config.modeId, ValueTransformers.tilt).pipe( concatWith(hub.ports.portValueChanges(config.portId, config.modeId, tiltThreshold, ValueTransformers.tilt)), - takeUntil(schemeStop$), + takeUntil(this.actions.pipe( + ofType(CONTROL_SCHEME_ACTIONS.schemeStopped) + )), map((tilt) => { - const transformedTilt: TiltData = { - roll: config.invertRoll ? -tilt.roll : tilt.roll, - pitch: config.invertPitch ? -tilt.pitch : tilt.pitch, - yaw: config.invertYaw ? -tilt.yaw : tilt.yaw - }; - return { widgetId: config.id, data: { - widgetType: WidgetType.Tilt, - tilt: transformedTilt + widgetType: config.widgetType, + tilt: tilt } }; }) diff --git a/modules/widgets/src/lib/common/common-tilt-widgets-settings-component-factory.service.ts b/modules/widgets/src/lib/common/common-tilt-widgets-settings-component-factory.service.ts new file mode 100644 index 00000000..3298d878 --- /dev/null +++ b/modules/widgets/src/lib/common/common-tilt-widgets-settings-component-factory.service.ts @@ -0,0 +1,32 @@ +import { Injectable, ViewContainerRef } from '@angular/core'; +import { ControlSchemeWidgetSettingsDescriptor } from '@app/control-scheme-view'; +import { WidgetType } from '@app/shared-misc'; + +import { CommonTiltWidgetSettingsComponent } from './tilt-widgets-settings'; +import { UnifiedTiltWidgetConfig } from './unified-tilt-widget-config'; + +@Injectable() +export class CommonTiltWidgetsSettingsComponentFactoryService { + private defaultWidgetNameL10nKeys: {[ k in UnifiedTiltWidgetConfig['widgetType'] ]: string} = { + [WidgetType.Pitch]: 'controlScheme.widgets.pitch.defaultName', + [WidgetType.Yaw]: 'controlScheme.widgets.yaw.defaultName', + [WidgetType.Roll]: 'controlScheme.widgets.roll.defaultName', + }; + + public createWidgetSettings( + container: ViewContainerRef, + config: UnifiedTiltWidgetConfig + ): ControlSchemeWidgetSettingsDescriptor { + const componentRef = container.createComponent(CommonTiltWidgetSettingsComponent); + componentRef.setInput('defaultNameL10nKey', this.defaultWidgetNameL10nKeys[config.widgetType]); + componentRef.setInput('config', config); + componentRef.setInput('widgetType', config.widgetType); + return { + get config(): UnifiedTiltWidgetConfig | undefined { + return componentRef.instance.config; + }, + configChanges$: componentRef.instance.configChanges, + destroy: () => componentRef.destroy(), + }; + } +} diff --git a/modules/widgets/src/lib/common/index.ts b/modules/widgets/src/lib/common/index.ts new file mode 100644 index 00000000..4db4d618 --- /dev/null +++ b/modules/widgets/src/lib/common/index.ts @@ -0,0 +1,6 @@ +export * from './common-tilt-widgets-read-task-factory.service'; +export * from './provide-common-widget-services'; +export * from './common-tilt-widgets-form-builder.service'; +export * from './common-tilt-widgets-blocker-checker.service'; +export * from './unified-tilt-widget-config'; +export * from './common-tilt-widgets-settings-component-factory.service'; diff --git a/modules/widgets/src/lib/common/provide-common-widget-services.ts b/modules/widgets/src/lib/common/provide-common-widget-services.ts new file mode 100644 index 00000000..33d015ec --- /dev/null +++ b/modules/widgets/src/lib/common/provide-common-widget-services.ts @@ -0,0 +1,17 @@ +import { Provider } from '@angular/core'; + +import { CommonTiltWidgetsReadTaskFactoryService } from './common-tilt-widgets-read-task-factory.service'; +import { CommonTiltWidgetsFormBuilderService } from './common-tilt-widgets-form-builder.service'; +import { CommonTiltWidgetsBlockerCheckerService } from './common-tilt-widgets-blocker-checker.service'; +import { CommonTiltWidgetsConfigFactoryService } from './common-tilt-widgets-config-factory.service'; +import { CommonTiltWidgetsSettingsComponentFactoryService } from './common-tilt-widgets-settings-component-factory.service'; + +export function provideCommonWidgetServices(): Provider[] { + return [ + CommonTiltWidgetsReadTaskFactoryService, + CommonTiltWidgetsFormBuilderService, + CommonTiltWidgetsBlockerCheckerService, + CommonTiltWidgetsConfigFactoryService, + CommonTiltWidgetsSettingsComponentFactoryService + ]; +} diff --git a/modules/widgets/src/lib/common/tilt-widgets-settings/common-tilt-widget-settings.component.html b/modules/widgets/src/lib/common/tilt-widgets-settings/common-tilt-widget-settings.component.html new file mode 100644 index 00000000..03b612fb --- /dev/null +++ b/modules/widgets/src/lib/common/tilt-widgets-settings/common-tilt-widget-settings.component.html @@ -0,0 +1,22 @@ + + {{ 'controlScheme.widgets.nameField' | transloco }} + + + + + + {{ 'controlScheme.widgets.pitch.valueChangeThresholdTitle' | transloco }} + + + + + + diff --git a/modules/widgets/src/lib/tilt/settings/tilt-sensor-widget-settings.component.scss b/modules/widgets/src/lib/common/tilt-widgets-settings/common-tilt-widget-settings.component.scss similarity index 100% rename from modules/widgets/src/lib/tilt/settings/tilt-sensor-widget-settings.component.scss rename to modules/widgets/src/lib/common/tilt-widgets-settings/common-tilt-widget-settings.component.scss diff --git a/modules/widgets/src/lib/common/tilt-widgets-settings/common-tilt-widget-settings.component.ts b/modules/widgets/src/lib/common/tilt-widgets-settings/common-tilt-widget-settings.component.ts new file mode 100644 index 00000000..abbe0997 --- /dev/null +++ b/modules/widgets/src/lib/common/tilt-widgets-settings/common-tilt-widget-settings.component.ts @@ -0,0 +1,82 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core'; +import { Subscription, startWith, take } from 'rxjs'; +import { ReactiveFormsModule } from '@angular/forms'; +import { TranslocoPipe, TranslocoService } from '@ngneat/transloco'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { ValidationMessagesDirective, WidgetType } from '@app/shared-misc'; +import { ToggleControlComponent } from '@app/shared-ui'; + +import { UnifiedTiltWidgetConfig } from '../unified-tilt-widget-config'; +import { CommonTiltWidgetsFormBuilderService } from '../common-tilt-widgets-form-builder.service'; + +@Component({ + standalone: true, + selector: 'lib-pitch-sensor-widget-settings', + templateUrl: './common-tilt-widget-settings.component.html', + styleUrls: [ './common-tilt-widget-settings.component.scss' ], + imports: [ + MatFormFieldModule, + MatInputModule, + ReactiveFormsModule, + TranslocoPipe, + ValidationMessagesDirective, + ToggleControlComponent + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class CommonTiltWidgetSettingsComponent implements OnDestroy { + @Output() public readonly configChanges = new EventEmitter(); + + @Input() public defaultNameL10nKey = ''; + + @Input() public widgetType: WidgetType.Yaw | WidgetType.Pitch | null = null; + + public readonly form = this.formBuilder.buildPitchWidgetForm(); + + private configChangesSubscription?: Subscription; + + constructor( + private readonly formBuilder: CommonTiltWidgetsFormBuilderService, + private readonly translocoService: TranslocoService, + ) { + } + + @Input() + public set config( + config: UnifiedTiltWidgetConfig | undefined + ) { + this.configChangesSubscription?.unsubscribe(); + if (config) { + this.form.patchValue(config); + this.form.updateValueAndValidity(); + } else { + this.form.reset(); + } + if (!this.form.controls.title.valid) { + this.translocoService.selectTranslate(this.defaultNameL10nKey).pipe( + take(1) + ).subscribe((name) => { + this.form.controls.title.setValue(name); + this.form.markAsDirty(); + this.form.updateValueAndValidity(); + }); + } + this.configChangesSubscription = this.form.valueChanges.pipe( + startWith(null) + ).subscribe(() => { + this.configChanges.emit(this.config); + }); + } + + public get config(): UnifiedTiltWidgetConfig | undefined { + if (this.widgetType !== null) { + return this.formBuilder.mapFormToTiltWidgetConfig(this.widgetType, this.form); + } + return undefined; + } + + public ngOnDestroy(): void { + this.configChangesSubscription?.unsubscribe(); + } +} diff --git a/modules/widgets/src/lib/common/tilt-widgets-settings/index.ts b/modules/widgets/src/lib/common/tilt-widgets-settings/index.ts new file mode 100644 index 00000000..2ac6c84e --- /dev/null +++ b/modules/widgets/src/lib/common/tilt-widgets-settings/index.ts @@ -0,0 +1 @@ +export * from './common-tilt-widget-settings.component'; diff --git a/modules/widgets/src/lib/common/unified-tilt-widget-config.ts b/modules/widgets/src/lib/common/unified-tilt-widget-config.ts new file mode 100644 index 00000000..748c2239 --- /dev/null +++ b/modules/widgets/src/lib/common/unified-tilt-widget-config.ts @@ -0,0 +1,3 @@ +import { PitchWidgetConfigModel, RollWidgetConfigModel, YawWidgetConfigModel } from '@app/store'; + +export type UnifiedTiltWidgetConfig = PitchWidgetConfigModel | YawWidgetConfigModel | RollWidgetConfigModel; diff --git a/modules/widgets/src/lib/control-scheme-start-widget-blocker-checker.service.ts b/modules/widgets/src/lib/control-scheme-start-widget-blocker-checker.service.ts index a5cd4b2b..31a0c7d8 100644 --- a/modules/widgets/src/lib/control-scheme-start-widget-blocker-checker.service.ts +++ b/modules/widgets/src/lib/control-scheme-start-widget-blocker-checker.service.ts @@ -6,22 +6,24 @@ import { AttachedIoModel, WidgetConfigModel, attachedIosIdFn } from '@app/store' import { IControlSchemeRunWidgetBlockersChecker, SchemeRunBlocker } from '@app/control-scheme-view'; import { VoltageWidgetBlockerCheckerService } from './voltage'; -import { TiltWidgetBlockerCheckerService } from './tilt'; import { TemperatureWidgetBlockerCheckerService } from './temperature'; import { IWidgetBlockerChecker } from './i-widget-blocker-checker'; +import { CommonTiltWidgetsBlockerCheckerService } from './common'; @Injectable() export class ControlSchemeStartWidgetBlockerCheckerService implements IControlSchemeRunWidgetBlockersChecker { private readonly blockerCheckers: { [k in WidgetType]: IWidgetBlockerChecker } = { [WidgetType.Temperature]: this.temperatureBlockerChecker, - [WidgetType.Tilt]: this.tiltBlockerChecker, [WidgetType.Voltage]: this.voltageBlockerChecker, + [WidgetType.Pitch]: this.commonTiltWidgetBlockerCheckerService, + [WidgetType.Yaw]: this.commonTiltWidgetBlockerCheckerService, + [WidgetType.Roll]: this.commonTiltWidgetBlockerCheckerService, }; constructor( private readonly voltageBlockerChecker: VoltageWidgetBlockerCheckerService, - private readonly tiltBlockerChecker: TiltWidgetBlockerCheckerService, private readonly temperatureBlockerChecker: TemperatureWidgetBlockerCheckerService, + private readonly commonTiltWidgetBlockerCheckerService: CommonTiltWidgetsBlockerCheckerService, ) { } diff --git a/modules/widgets/src/lib/i-widget-read-task-factory.ts b/modules/widgets/src/lib/i-widget-read-task-factory.ts new file mode 100644 index 00000000..cca4405e --- /dev/null +++ b/modules/widgets/src/lib/i-widget-read-task-factory.ts @@ -0,0 +1,8 @@ +import { Observable } from 'rxjs'; +import { ControlSchemeWidgetsDataModel, WidgetConfigModel } from '@app/store'; + +export interface IWidgetReadTaskFactory { + createReadTask( + config: TConfig + ): Observable<{ widgetId: number; data: ControlSchemeWidgetsDataModel }>; +} diff --git a/modules/widgets/src/lib/pitch/index.ts b/modules/widgets/src/lib/pitch/index.ts new file mode 100644 index 00000000..30637d84 --- /dev/null +++ b/modules/widgets/src/lib/pitch/index.ts @@ -0,0 +1,2 @@ +export * from './provide-pitch-widget'; +export * from './pitch-widget-component-factory.service'; diff --git a/modules/widgets/src/lib/pitch/pitch-sensor-widget.component.html b/modules/widgets/src/lib/pitch/pitch-sensor-widget.component.html new file mode 100644 index 00000000..84ce59bc --- /dev/null +++ b/modules/widgets/src/lib/pitch/pitch-sensor-widget.component.html @@ -0,0 +1,49 @@ + +
+ + + + @if (pitch !== null) { + {{ pitch }}° + } @else { +   + } + + + + + + + + + + + + + +
+
diff --git a/modules/widgets/src/lib/pitch/pitch-sensor-widget.component.scss b/modules/widgets/src/lib/pitch/pitch-sensor-widget.component.scss new file mode 100644 index 00000000..efc0651a --- /dev/null +++ b/modules/widgets/src/lib/pitch/pitch-sensor-widget.component.scss @@ -0,0 +1,47 @@ +.pitch-content { + display: grid; + grid-template-columns: max-content auto; + gap: 0 20px; + grid-template-rows: auto auto; + width: 100%; + height: 100%; + + --mat-text-button-icon-spacing: 0px; +} + +.pitch-value { + font-weight: bold; + align-self: center; + justify-self: center; +} + +.pitch-control-button { + height: 36px; + width: 36px; + margin: 0; + padding: 0; + min-width: 36px; + justify-self: end; +} + +.pitch-gauge { + display: block; + height: 100%; + position: relative; + overflow: hidden; + grid-row: span 3; +} + +.pitch-icon { + fill: none; + stroke: var(--app-text-color); + stroke-linecap: round; + + &__outline { + stroke-width: 2; + } + + &__inline { + stroke-width: 1; + } +} diff --git a/modules/widgets/src/lib/pitch/pitch-sensor-widget.component.ts b/modules/widgets/src/lib/pitch/pitch-sensor-widget.component.ts new file mode 100644 index 00000000..a2ba4331 --- /dev/null +++ b/modules/widgets/src/lib/pitch/pitch-sensor-widget.component.ts @@ -0,0 +1,58 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { TranslocoPipe } from '@ngneat/transloco'; +import { MatMenuItem } from '@angular/material/menu'; +import { MatIcon } from '@angular/material/icon'; +import { TiltGaugeComponent, TiltGaugeIconDirective, WidgetComponent } from '@app/shared-ui'; + +@Component({ + standalone: true, + selector: 'lib-tilt-sensor-widget', + templateUrl: './pitch-sensor-widget.component.html', + styleUrls: [ './pitch-sensor-widget.component.scss' ], + imports: [ + WidgetComponent, + TiltGaugeIconDirective, + MatButtonModule, + TranslocoPipe, + TiltGaugeComponent, + MatMenuItem, + MatIcon + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class PitchSensorWidgetComponent { + @Input() public pitch: number | null = null; + + @Input() public title = ''; + + @Input() public canBeDeleted = false; + + @Input() public canBeEdited = false; + + @Output() public readonly edit = new EventEmitter(); + + @Output() public readonly delete = new EventEmitter(); + + @Output() public readonly compensate = new EventEmitter(); + + @Output() public readonly resetCompensation = new EventEmitter(); + + public onCompensate(): void { + if (this.pitch !== null) { + this.compensate.emit(this.pitch); + } + } + + public onResetCompensation(): void { + this.resetCompensation.emit(); + } + + public onEdit(): void { + this.edit.emit(); + } + + public onDelete(): void { + this.delete.emit(); + } +} diff --git a/modules/widgets/src/lib/tilt/tilt-widget-component-factory.service.ts b/modules/widgets/src/lib/pitch/pitch-widget-component-factory.service.ts similarity index 61% rename from modules/widgets/src/lib/tilt/tilt-widget-component-factory.service.ts rename to modules/widgets/src/lib/pitch/pitch-widget-component-factory.service.ts index 2ba1490e..e044cc97 100644 --- a/modules/widgets/src/lib/tilt/tilt-widget-component-factory.service.ts +++ b/modules/widgets/src/lib/pitch/pitch-widget-component-factory.service.ts @@ -1,12 +1,13 @@ import { Injectable, ViewContainerRef } from '@angular/core'; import { Store } from '@ngrx/store'; -import { ATTACHED_IO_PROPS_ACTIONS, CONTROL_SCHEME_WIDGETS_DATA_SELECTORS, TiltWidgetConfigModel } from '@app/store'; +import { ATTACHED_IO_PROPS_ACTIONS, PitchWidgetConfigModel } from '@app/store'; import { ControlSchemeWidgetDescriptor } from '@app/control-scheme-view'; -import { TiltSensorWidgetComponent } from './widget'; +import { PitchSensorWidgetComponent } from './pitch-sensor-widget.component'; +import { SELECT_PITCH_WIDGET_DATA } from './pitch-widget.selectors'; -@Injectable({ providedIn: 'root' }) -export class TiltWidgetComponentFactoryService { +@Injectable() +export class PitchWidgetComponentFactoryService { constructor( private readonly store: Store ) { @@ -14,22 +15,22 @@ export class TiltWidgetComponentFactoryService { public createWidget( container: ViewContainerRef, - config: TiltWidgetConfigModel + config: PitchWidgetConfigModel ): ControlSchemeWidgetDescriptor { - const componentRef = container.createComponent(TiltSensorWidgetComponent); + const componentRef = container.createComponent(PitchSensorWidgetComponent); componentRef.setInput('title', config.title); // eslint-disable-next-line @ngrx/no-store-subscription - const dataSub = this.store.select(CONTROL_SCHEME_WIDGETS_DATA_SELECTORS.selectWidgetTiltData(config)).subscribe((widgetData) => { - componentRef.setInput('tilt', widgetData); + const dataSub = this.store.select(SELECT_PITCH_WIDGET_DATA(config)).subscribe((pitch) => { + componentRef.setInput('pitch', pitch); }); - const compensateTiltSub = componentRef.instance.compensateTilt.subscribe((compensationData) => { - this.store.dispatch(ATTACHED_IO_PROPS_ACTIONS.compensateTilt({ ...config, compensationData })); + const compensateTiltSub = componentRef.instance.compensate.subscribe((currentPitch) => { + this.store.dispatch(ATTACHED_IO_PROPS_ACTIONS.compensatePitch({ ...config, currentPitch })); }); - const resetTiltCompensationSub = componentRef.instance.resetTiltCompensation.subscribe(() => { - this.store.dispatch(ATTACHED_IO_PROPS_ACTIONS.resetTiltCompensation(config)); + const resetTiltCompensationSub = componentRef.instance.resetCompensation.subscribe(() => { + this.store.dispatch(ATTACHED_IO_PROPS_ACTIONS.resetPitchCompensation(config)); }); return { diff --git a/modules/widgets/src/lib/pitch/pitch-widget.selectors.ts b/modules/widgets/src/lib/pitch/pitch-widget.selectors.ts new file mode 100644 index 00000000..3bf6e5dd --- /dev/null +++ b/modules/widgets/src/lib/pitch/pitch-widget.selectors.ts @@ -0,0 +1,8 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { createSelector } from '@ngrx/store'; +import { CONTROL_SCHEME_WIDGETS_DATA_SELECTORS, PitchWidgetConfigModel } from '@app/store'; + +export const SELECT_PITCH_WIDGET_DATA = (config: PitchWidgetConfigModel) => createSelector( + CONTROL_SCHEME_WIDGETS_DATA_SELECTORS.selectWidgetTiltData(config), + (widgetData) => widgetData?.pitch ?? null +); diff --git a/modules/widgets/src/lib/pitch/provide-pitch-widget.ts b/modules/widgets/src/lib/pitch/provide-pitch-widget.ts new file mode 100644 index 00000000..b33599ee --- /dev/null +++ b/modules/widgets/src/lib/pitch/provide-pitch-widget.ts @@ -0,0 +1,9 @@ +import { Provider } from '@angular/core'; + +import { PitchWidgetComponentFactoryService } from './pitch-widget-component-factory.service'; + +export function providePitchWidget(): Provider[] { + return [ + PitchWidgetComponentFactoryService + ]; +} diff --git a/modules/widgets/src/lib/provide-widgets.ts b/modules/widgets/src/lib/provide-widgets.ts index 65700b06..0232735c 100644 --- a/modules/widgets/src/lib/provide-widgets.ts +++ b/modules/widgets/src/lib/provide-widgets.ts @@ -7,31 +7,37 @@ import { WIDGET_CONNECTION_INFO_PROVIDER } from '@app/control-scheme-view'; import { WIDGET_TYPE_TO_L10N_KEY_MAPPER } from '@app/shared-control-schemes'; -import { WIDGET_READ_TASK_FACTORY } from '@app/store'; +import { WIDGET_READ_TASKS_FACTORY } from '@app/store'; import { WidgetConnectionInfoL10nService } from './widget-connection-info-l10n.service'; import { provideTemperatureWidget } from './temperature/provide-temperature-widget'; -import { provideTiltWidget } from './tilt'; import { provideVoltageWidget } from './voltage'; import { ControlSchemeStartWidgetBlockerCheckerService } from './control-scheme-start-widget-blocker-checker.service'; import { WidgetComponentFactoryService } from './widget-component-factory.service'; import { WidgetConfigFactoryService } from './widget-config-factory.service'; import { WidgetTypeToL10nKeyMapperService } from './widget-type-to-l10n-key-mapper.service'; import { WidgetSettingsComponentFactoryService } from './widget-settings-component-factory.service'; -import { WidgetReadTaskFactoryService } from './widget-read-task-factory.service'; +import { WidgetsReadTaskFactoryService } from './widgets-read-task-factory.service'; +import { provideCommonWidgetServices } from './common'; +import { providePitchWidget } from './pitch'; +import { provideYawWidget } from './yaw'; +import { provideRollWidget } from './roll'; export function provideWidgets(): Provider[] { return [ WidgetConnectionInfoL10nService, ...provideTemperatureWidget(), - ...provideTiltWidget(), ...provideVoltageWidget(), + ...provideCommonWidgetServices(), + ...providePitchWidget(), + ...provideYawWidget(), + ...provideRollWidget(), { provide: CONTROL_SCHEME_RUN_WIDGET_BLOCKERS_CHECKER, useClass: ControlSchemeStartWidgetBlockerCheckerService }, { provide: CONTROL_SCHEME_WIDGET_COMPONENT_FACTORY, useClass: WidgetComponentFactoryService }, { provide: CONTROL_SCHEME_WIDGET_CONFIG_FACTORY, useClass: WidgetConfigFactoryService }, { provide: WIDGET_TYPE_TO_L10N_KEY_MAPPER, useClass: WidgetTypeToL10nKeyMapperService }, { provide: CONTROL_SCHEME_WIDGET_SETTINGS_COMPONENT_FACTORY, useClass: WidgetSettingsComponentFactoryService }, - { provide: WIDGET_READ_TASK_FACTORY, useClass: WidgetReadTaskFactoryService }, + { provide: WIDGET_READ_TASKS_FACTORY, useClass: WidgetsReadTaskFactoryService }, { provide: WIDGET_CONNECTION_INFO_PROVIDER, useClass: WidgetConnectionInfoL10nService } ]; } diff --git a/modules/widgets/src/lib/roll/index.ts b/modules/widgets/src/lib/roll/index.ts new file mode 100644 index 00000000..952e6f3f --- /dev/null +++ b/modules/widgets/src/lib/roll/index.ts @@ -0,0 +1,2 @@ +export * from './provide-roll-widget'; +export * from './roll-widget-component-factory.service'; diff --git a/modules/widgets/src/lib/roll/provide-roll-widget.ts b/modules/widgets/src/lib/roll/provide-roll-widget.ts new file mode 100644 index 00000000..937858e5 --- /dev/null +++ b/modules/widgets/src/lib/roll/provide-roll-widget.ts @@ -0,0 +1,9 @@ +import { Provider } from '@angular/core'; + +import { RollWidgetComponentFactoryService } from './roll-widget-component-factory.service'; + +export function provideRollWidget(): Provider[] { + return [ + RollWidgetComponentFactoryService + ]; +} diff --git a/modules/widgets/src/lib/roll/roll-widget-component-factory.service.ts b/modules/widgets/src/lib/roll/roll-widget-component-factory.service.ts new file mode 100644 index 00000000..c43649b9 --- /dev/null +++ b/modules/widgets/src/lib/roll/roll-widget-component-factory.service.ts @@ -0,0 +1,49 @@ +import { Injectable, ViewContainerRef } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { ATTACHED_IO_PROPS_ACTIONS, RollWidgetConfigModel } from '@app/store'; +import { ControlSchemeWidgetDescriptor } from '@app/control-scheme-view'; + +import { RollWidgetComponent } from './roll-widget.component'; +import { SELECT_ROLL_WIDGET_DATA } from './roll-widget.selectors'; + +@Injectable() +export class RollWidgetComponentFactoryService { + constructor( + private readonly store: Store + ) { + } + + public createWidget( + container: ViewContainerRef, + config: RollWidgetConfigModel + ): ControlSchemeWidgetDescriptor { + const componentRef = container.createComponent(RollWidgetComponent); + componentRef.setInput('title', config.title); + + // eslint-disable-next-line @ngrx/no-store-subscription + const dataSub = this.store.select(SELECT_ROLL_WIDGET_DATA(config)).subscribe((roll) => { + componentRef.setInput('roll', roll); + }); + + const compensateRollSub = componentRef.instance.compensate.subscribe((currentRoll) => { + this.store.dispatch(ATTACHED_IO_PROPS_ACTIONS.compensateRoll({ ...config, currentRoll })); + }); + + const resetRollCompensationSub = componentRef.instance.resetCompensation.subscribe(() => { + this.store.dispatch(ATTACHED_IO_PROPS_ACTIONS.resetRollCompensation(config)); + }); + + return { + edit$: componentRef.instance.edit, + delete$: componentRef.instance.delete, + setCanBeEdited: (canBeEdited: boolean) => componentRef.setInput('canBeEdited', canBeEdited), + setCanBeDeleted: (canBeDeleted: boolean) => componentRef.setInput('canBeDeleted', canBeDeleted), + destroy: (): void => { + dataSub.unsubscribe(); + compensateRollSub.unsubscribe(); + resetRollCompensationSub.unsubscribe(); + componentRef.destroy(); + } + }; + } +} diff --git a/modules/widgets/src/lib/roll/roll-widget.component.html b/modules/widgets/src/lib/roll/roll-widget.component.html new file mode 100644 index 00000000..8b211dc5 --- /dev/null +++ b/modules/widgets/src/lib/roll/roll-widget.component.html @@ -0,0 +1,49 @@ + +
+ + + + + + + + + + + + @if (roll !== null) { + {{ roll }}° + } @else { +   + } + + + + +
+
diff --git a/modules/widgets/src/lib/roll/roll-widget.component.scss b/modules/widgets/src/lib/roll/roll-widget.component.scss new file mode 100644 index 00000000..55946c37 --- /dev/null +++ b/modules/widgets/src/lib/roll/roll-widget.component.scss @@ -0,0 +1,40 @@ +.roll-content { + display: grid; + grid-template-columns: max-content auto; + gap: 0 20px; + grid-template-rows: auto auto; + width: 100%; + height: 100%; + + --mat-text-button-icon-spacing: 0px; +} + +.pitch-value { + font-weight: bold; + align-self: center; + justify-self: center; +} + +.pitch-control-button { + height: 36px; + width: 36px; + margin: 0; + padding: 0; + min-width: 36px; + justify-self: end; +} + +.roll-gauge { + display: block; + height: 100%; + position: relative; + overflow: hidden; + grid-row: span 3; +} + +.roll-icon { + fill: none; + stroke: var(--app-text-color); + stroke-linecap: round; + stroke-width: 2; +} diff --git a/modules/widgets/src/lib/roll/roll-widget.component.ts b/modules/widgets/src/lib/roll/roll-widget.component.ts new file mode 100644 index 00000000..6f2bdfa2 --- /dev/null +++ b/modules/widgets/src/lib/roll/roll-widget.component.ts @@ -0,0 +1,58 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { TranslocoPipe } from '@ngneat/transloco'; +import { MatMenuItem } from '@angular/material/menu'; +import { MatIcon } from '@angular/material/icon'; +import { TiltGaugeComponent, TiltGaugeIconDirective, WidgetComponent } from '@app/shared-ui'; + +@Component({ + standalone: true, + selector: 'lib-roll-sensor-widget', + templateUrl: './roll-widget.component.html', + styleUrls: [ './roll-widget.component.scss' ], + imports: [ + WidgetComponent, + TiltGaugeIconDirective, + MatButtonModule, + TranslocoPipe, + TiltGaugeComponent, + MatMenuItem, + MatIcon + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class RollWidgetComponent { + @Input() public roll: number | null = null; + + @Input() public title = ''; + + @Input() public canBeDeleted = false; + + @Input() public canBeEdited = false; + + @Output() public readonly edit = new EventEmitter(); + + @Output() public readonly delete = new EventEmitter(); + + @Output() public readonly compensate = new EventEmitter(); + + @Output() public readonly resetCompensation = new EventEmitter(); + + public onCompensate(): void { + if (this.roll !== null) { + this.compensate.emit(this.roll); + } + } + + public onResetCompensation(): void { + this.resetCompensation.emit(); + } + + public onEdit(): void { + this.edit.emit(); + } + + public onDelete(): void { + this.delete.emit(); + } +} diff --git a/modules/widgets/src/lib/roll/roll-widget.selectors.ts b/modules/widgets/src/lib/roll/roll-widget.selectors.ts new file mode 100644 index 00000000..6c9dce83 --- /dev/null +++ b/modules/widgets/src/lib/roll/roll-widget.selectors.ts @@ -0,0 +1,8 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { createSelector } from '@ngrx/store'; +import { CONTROL_SCHEME_WIDGETS_DATA_SELECTORS, RollWidgetConfigModel } from '@app/store'; + +export const SELECT_ROLL_WIDGET_DATA = (config: RollWidgetConfigModel) => createSelector( + CONTROL_SCHEME_WIDGETS_DATA_SELECTORS.selectWidgetTiltData(config), + (widgetData) => widgetData?.roll ?? null +); diff --git a/modules/widgets/src/lib/temperature/temperature-widget-config-factory.service.ts b/modules/widgets/src/lib/temperature/temperature-widget-config-factory.service.ts index ea6ae1b6..f8b2c2c7 100644 --- a/modules/widgets/src/lib/temperature/temperature-widget-config-factory.service.ts +++ b/modules/widgets/src/lib/temperature/temperature-widget-config-factory.service.ts @@ -6,6 +6,7 @@ import { AttachedIoModesModel, AttachedIoPortModeInfoModel, TemperatureWidgetConfigModel, + WidgetConfigModel, attachedIoModesIdFn, attachedIoPortModeInfoIdFn } from '@app/store'; @@ -23,13 +24,13 @@ export class TemperatureWidgetConfigFactoryService implements IControlSchemeWidg public createConfigs( attachedIos: AttachedIoModel[], ioPortModes: Dictionary, - portModesInfo: Dictionary + portModesInfo: Dictionary, + existingWidgets: WidgetConfigModel[] ): TemperatureWidgetConfigModel[] { const result: TemperatureWidgetConfigModel[] = []; - for (const io of attachedIos) { - + const freeIos = attachedIos.filter((io) => !existingWidgets.some((widget) => widget.hubId === io.hubId && widget.portId === io.portId)); + for (const io of freeIos) { const portInputModeIds = (ioPortModes[attachedIoModesIdFn(io)]?.portInputModes ?? []); - for (const modeId of portInputModeIds) { const modeName = portModesInfo[attachedIoPortModeInfoIdFn({ ...io, modeId })]?.name; if (modeName !== undefined && this.blockerChecker.canBeUsedWithInputModes([modeName])) { diff --git a/modules/widgets/src/lib/temperature/temperature-widget-read-task-factory.service.ts b/modules/widgets/src/lib/temperature/temperature-widget-read-task-factory.service.ts index 236a8cc7..7e917e17 100644 --- a/modules/widgets/src/lib/temperature/temperature-widget-read-task-factory.service.ts +++ b/modules/widgets/src/lib/temperature/temperature-widget-read-task-factory.service.ts @@ -1,25 +1,32 @@ -import { Store } from '@ngrx/store'; import { Observable, concatWith, map, takeUntil } from 'rxjs'; import { ValueTransformers } from 'rxpoweredup'; import { Injectable } from '@angular/core'; +import { Actions, ofType } from '@ngrx/effects'; import { WidgetType } from '@app/shared-misc'; -import { ControlSchemeWidgetsDataModel, HubStorageService, IWidgetReadTaskFactory, TemperatureWidgetConfigModel } from '@app/store'; +import { CONTROL_SCHEME_ACTIONS, ControlSchemeWidgetsDataModel, HubStorageService, TemperatureWidgetConfigModel } from '@app/store'; + +import { IWidgetReadTaskFactory } from '../i-widget-read-task-factory'; @Injectable() export class TemperatureWidgetReadTaskFactoryService implements IWidgetReadTaskFactory { + constructor( + private readonly hubStorage: HubStorageService, + private readonly actions: Actions + ) { + } + public createReadTask( config: TemperatureWidgetConfigModel, - store: Store, - hubStorage: HubStorageService, - schemeStop$: Observable ): Observable<{ widgetId: number; data: ControlSchemeWidgetsDataModel; }> { - const hub = hubStorage.get(config.hubId); + const hub = this.hubStorage.get(config.hubId); return hub.ports.getPortValue(config.portId, config.modeId, ValueTransformers.temperature).pipe( concatWith(hub.ports.portValueChanges(config.portId, config.modeId, config.valueChangeThreshold, ValueTransformers.temperature)), - takeUntil(schemeStop$), + takeUntil(this.actions.pipe( + ofType(CONTROL_SCHEME_ACTIONS.schemeStopped) + )), map((temperature) => ({ widgetId: config.id, data: { diff --git a/modules/widgets/src/lib/tilt/index.ts b/modules/widgets/src/lib/tilt/index.ts deleted file mode 100644 index fac2539e..00000000 --- a/modules/widgets/src/lib/tilt/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './provide-tilt-widget'; -export * from './tilt-widget-config-factory.service'; -export * from './tilt-widget-component-factory.service'; -export * from './tilt-widget-settings-component-factory.service'; -export * from './tilt-widget-blocker-checker.service'; -export * from './tilt-widget-read-task-factory.service'; diff --git a/modules/widgets/src/lib/tilt/provide-tilt-widget.ts b/modules/widgets/src/lib/tilt/provide-tilt-widget.ts deleted file mode 100644 index 8733e38f..00000000 --- a/modules/widgets/src/lib/tilt/provide-tilt-widget.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Provider } from '@angular/core'; - -import { TiltWidgetComponentFactoryService } from './tilt-widget-component-factory.service'; -import { TiltWidgetConfigFactoryService } from './tilt-widget-config-factory.service'; -import { TiltWidgetReadTaskFactoryService } from './tilt-widget-read-task-factory.service'; -import { TiltWidgetSettingsComponentFactoryService } from './tilt-widget-settings-component-factory.service'; -import { TiltWidgetBlockerCheckerService } from './tilt-widget-blocker-checker.service'; - -export function provideTiltWidget(): Provider[] { - return [ - TiltWidgetComponentFactoryService, - TiltWidgetConfigFactoryService, - TiltWidgetBlockerCheckerService, - TiltWidgetReadTaskFactoryService, - TiltWidgetSettingsComponentFactoryService - ]; -} diff --git a/modules/widgets/src/lib/tilt/settings/index.ts b/modules/widgets/src/lib/tilt/settings/index.ts deleted file mode 100644 index 2d0fd7b1..00000000 --- a/modules/widgets/src/lib/tilt/settings/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './tilt-sensor-widget-settings.component'; diff --git a/modules/widgets/src/lib/tilt/settings/tilt-sensor-widget-settings.component.html b/modules/widgets/src/lib/tilt/settings/tilt-sensor-widget-settings.component.html deleted file mode 100644 index 9d0c635d..00000000 --- a/modules/widgets/src/lib/tilt/settings/tilt-sensor-widget-settings.component.html +++ /dev/null @@ -1,29 +0,0 @@ - - {{ 'controlScheme.widgets.nameField' | transloco }} - - - - - - {{ 'controlScheme.widgets.tilt.valueChangeThresholdTitle' | transloco }} - - - - - - - - - diff --git a/modules/widgets/src/lib/tilt/settings/tilt-sensor-widget-settings.component.ts b/modules/widgets/src/lib/tilt/settings/tilt-sensor-widget-settings.component.ts deleted file mode 100644 index 5f1cab80..00000000 --- a/modules/widgets/src/lib/tilt/settings/tilt-sensor-widget-settings.component.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core'; -import { Subscription, startWith, take } from 'rxjs'; -import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; -import { TranslocoPipe, TranslocoService } from '@ngneat/transloco'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatInputModule } from '@angular/material/input'; -import { ValidationMessagesDirective, WidgetType } from '@app/shared-misc'; -import { ToggleControlComponent } from '@app/shared-ui'; -import { TiltWidgetConfigModel } from '@app/store'; -import { ControlSchemeFormBuilderService } from '@app/shared-control-schemes'; - -@Component({ - standalone: true, - selector: 'lib-tilt-sensor-widget-settings', - templateUrl: './tilt-sensor-widget-settings.component.html', - styleUrls: [ './tilt-sensor-widget-settings.component.scss' ], - imports: [ - MatFormFieldModule, - MatInputModule, - ReactiveFormsModule, - TranslocoPipe, - ValidationMessagesDirective, - ToggleControlComponent - ], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TiltSensorWidgetSettingsComponent implements OnDestroy { - @Output() public readonly configChanges = new EventEmitter(); - - public readonly form = this.formBuilder.group({ - id: this.formBuilder.control(0, { validators: Validators.required, nonNullable: true }), - title: this.formBuilder.control('', { validators: [ Validators.required ], nonNullable: true }), - hubId: this.commonFormBuilder.hubIdControl(), - portId: this.commonFormBuilder.portIdControl(), - modeId: this.formBuilder.control(null, { validators: Validators.required, nonNullable: false }), - valueChangeThreshold: this.formBuilder.control(5, { - validators: [ - Validators.required, - Validators.min(5), - Validators.max(30) - ], - nonNullable: true - }), - width: this.formBuilder.control(2, { validators: Validators.required, nonNullable: true }), - height: this.formBuilder.control(2, { validators: Validators.required, nonNullable: true }), - invertRoll: this.formBuilder.control(false, { nonNullable: true }), - invertPitch: this.formBuilder.control(false, { nonNullable: true }), - invertYaw: this.formBuilder.control(false, { nonNullable: true }), - }); - - private configChangesSubscription?: Subscription; - - constructor( - private readonly formBuilder: FormBuilder, - private readonly translocoService: TranslocoService, - private readonly commonFormBuilder: ControlSchemeFormBuilderService, - ) { - } - - @Input() - public set config( - config: TiltWidgetConfigModel | undefined - ) { - this.configChangesSubscription?.unsubscribe(); - if (config) { - this.form.patchValue(config); - } else { - this.form.reset(); - } - if (!this.form.controls.title.valid) { - this.translocoService.selectTranslate('controlScheme.widgets.tilt.defaultName').pipe( - take(1) - ).subscribe((name) => { - this.form.controls.title.setValue(name); - }); - } - this.configChangesSubscription = this.form.valueChanges.pipe( - startWith(null) - ).subscribe(() => { - this.configChanges.emit(this.config); - }); - } - - public get config(): TiltWidgetConfigModel | undefined { - if (this.form.controls.hubId.value === null - || this.form.controls.portId.value === null - || this.form.controls.modeId.value === null - || this.form.invalid - ) { - return undefined; - } - return { - widgetType: WidgetType.Tilt, - id: this.form.controls.id.value, - title: this.form.controls.title.value, - hubId: this.form.controls.hubId.value, - portId: this.form.controls.portId.value, - modeId: this.form.controls.modeId.value, - valueChangeThreshold: this.form.controls.valueChangeThreshold.value, - width: this.form.controls.width.value, - height: this.form.controls.height.value, - invertRoll: this.form.controls.invertRoll.value, - invertPitch: this.form.controls.invertPitch.value, - invertYaw: this.form.controls.invertYaw.value, - }; - } - - public ngOnDestroy(): void { - this.configChangesSubscription?.unsubscribe(); - } -} diff --git a/modules/widgets/src/lib/tilt/tilt-widget-settings-component-factory.service.ts b/modules/widgets/src/lib/tilt/tilt-widget-settings-component-factory.service.ts deleted file mode 100644 index 3f749074..00000000 --- a/modules/widgets/src/lib/tilt/tilt-widget-settings-component-factory.service.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Injectable, ViewContainerRef } from '@angular/core'; -import { TiltWidgetConfigModel } from '@app/store'; -import { ControlSchemeWidgetSettingsDescriptor } from '@app/control-scheme-view'; - -import { TiltSensorWidgetSettingsComponent } from './settings/tilt-sensor-widget-settings.component'; - -@Injectable() -export class TiltWidgetSettingsComponentFactoryService { - public createWidgetSettings( - container: ViewContainerRef, - config: TiltWidgetConfigModel - ): ControlSchemeWidgetSettingsDescriptor { - const componentRef = container.createComponent(TiltSensorWidgetSettingsComponent); - componentRef.setInput('config', config); - return { - get config(): TiltWidgetConfigModel | undefined { - return componentRef.instance.config; - }, - configChanges$: componentRef.instance.configChanges, - destroy: () => componentRef.destroy(), - }; - } -} diff --git a/modules/widgets/src/lib/tilt/widget/index.ts b/modules/widgets/src/lib/tilt/widget/index.ts deleted file mode 100644 index 345de567..00000000 --- a/modules/widgets/src/lib/tilt/widget/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './tilt-sensor-widget.component'; diff --git a/modules/widgets/src/lib/tilt/widget/pitch-indicator/index.ts b/modules/widgets/src/lib/tilt/widget/pitch-indicator/index.ts deleted file mode 100644 index 569fe8fb..00000000 --- a/modules/widgets/src/lib/tilt/widget/pitch-indicator/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './pitch-indicator.component'; diff --git a/modules/widgets/src/lib/tilt/widget/pitch-indicator/pitch-indicator.component.html b/modules/widgets/src/lib/tilt/widget/pitch-indicator/pitch-indicator.component.html deleted file mode 100644 index 495c2686..00000000 --- a/modules/widgets/src/lib/tilt/widget/pitch-indicator/pitch-indicator.component.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - diff --git a/modules/widgets/src/lib/tilt/widget/pitch-indicator/pitch-indicator.component.scss b/modules/widgets/src/lib/tilt/widget/pitch-indicator/pitch-indicator.component.scss deleted file mode 100644 index cc75b16a..00000000 --- a/modules/widgets/src/lib/tilt/widget/pitch-indicator/pitch-indicator.component.scss +++ /dev/null @@ -1,20 +0,0 @@ -:host { - display: block; - height: 100%; - position: relative; - overflow: hidden; -} - -.pitch-icon { - fill: none; - stroke: var(--app-text-color); - stroke-linecap: round; - - &__outline { - stroke-width: 2; - } - - &__inline { - stroke-width: 1; - } -} diff --git a/modules/widgets/src/lib/tilt/widget/pitch-indicator/pitch-indicator.component.ts b/modules/widgets/src/lib/tilt/widget/pitch-indicator/pitch-indicator.component.ts deleted file mode 100644 index c0ea56ab..00000000 --- a/modules/widgets/src/lib/tilt/widget/pitch-indicator/pitch-indicator.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; -import { TiltGaugeComponent } from '@app/shared-ui'; - -@Component({ - standalone: true, - selector: 'lib-pitch-indicator', - templateUrl: './pitch-indicator.component.html', - styleUrls: [ './pitch-indicator.component.scss' ], - imports: [ - TiltGaugeComponent - ], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class PitchIndicatorComponent { - @Input() public pitch?: number; -} diff --git a/modules/widgets/src/lib/tilt/widget/roll-indicator/index.ts b/modules/widgets/src/lib/tilt/widget/roll-indicator/index.ts deleted file mode 100644 index cb983b25..00000000 --- a/modules/widgets/src/lib/tilt/widget/roll-indicator/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './roll-indicator.component'; diff --git a/modules/widgets/src/lib/tilt/widget/roll-indicator/roll-indicator.component.html b/modules/widgets/src/lib/tilt/widget/roll-indicator/roll-indicator.component.html deleted file mode 100644 index 20a598b2..00000000 --- a/modules/widgets/src/lib/tilt/widget/roll-indicator/roll-indicator.component.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - diff --git a/modules/widgets/src/lib/tilt/widget/roll-indicator/roll-indicator.component.scss b/modules/widgets/src/lib/tilt/widget/roll-indicator/roll-indicator.component.scss deleted file mode 100644 index 336bbce8..00000000 --- a/modules/widgets/src/lib/tilt/widget/roll-indicator/roll-indicator.component.scss +++ /dev/null @@ -1,13 +0,0 @@ -:host { - display: block; - height: 100%; - position: relative; - overflow: hidden; -} - -.roll-icon { - fill: none; - stroke: var(--app-text-color); - stroke-linecap: round; - stroke-width: 2; -} diff --git a/modules/widgets/src/lib/tilt/widget/roll-indicator/roll-indicator.component.ts b/modules/widgets/src/lib/tilt/widget/roll-indicator/roll-indicator.component.ts deleted file mode 100644 index 5f4be9de..00000000 --- a/modules/widgets/src/lib/tilt/widget/roll-indicator/roll-indicator.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; -import { TiltGaugeComponent } from '@app/shared-ui'; - -@Component({ - standalone: true, - selector: 'lib-roll-indicator', - templateUrl: './roll-indicator.component.html', - styleUrls: [ './roll-indicator.component.scss' ], - imports: [ - TiltGaugeComponent - ], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RollIndicatorComponent { - @Input() public roll?: number; -} diff --git a/modules/widgets/src/lib/tilt/widget/tilt-sensor-widget.component.html b/modules/widgets/src/lib/tilt/widget/tilt-sensor-widget.component.html deleted file mode 100644 index 2e44145e..00000000 --- a/modules/widgets/src/lib/tilt/widget/tilt-sensor-widget.component.html +++ /dev/null @@ -1,26 +0,0 @@ - -
- - - -
- - -
-
-
diff --git a/modules/widgets/src/lib/tilt/widget/tilt-sensor-widget.component.scss b/modules/widgets/src/lib/tilt/widget/tilt-sensor-widget.component.scss deleted file mode 100644 index 12c05c63..00000000 --- a/modules/widgets/src/lib/tilt/widget/tilt-sensor-widget.component.scss +++ /dev/null @@ -1,19 +0,0 @@ -.tilt-content { - width: 100%; - height: 100%; - display: grid; - grid-template-columns: 1fr 1fr; - grid-template-rows: 1fr 1fr; - align-items: center; - justify-items: center; - gap: 5px; -} - -.tilt-reset-buttons { - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - gap: 5px; - justify-content: flex-end; -} diff --git a/modules/widgets/src/lib/tilt/widget/tilt-sensor-widget.component.ts b/modules/widgets/src/lib/tilt/widget/tilt-sensor-widget.component.ts deleted file mode 100644 index cf3d02ac..00000000 --- a/modules/widgets/src/lib/tilt/widget/tilt-sensor-widget.component.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; -import { TiltData } from 'rxpoweredup'; -import { MatButtonModule } from '@angular/material/button'; -import { TranslocoPipe } from '@ngneat/transloco'; -import { TiltGaugeIconDirective, WidgetComponent } from '@app/shared-ui'; - -import { RollIndicatorComponent } from './roll-indicator'; -import { YawIndicatorComponent } from './yaw-indicator'; -import { PitchIndicatorComponent } from './pitch-indicator'; - -@Component({ - standalone: true, - selector: 'lib-tilt-sensor-widget', - templateUrl: './tilt-sensor-widget.component.html', - styleUrls: [ './tilt-sensor-widget.component.scss' ], - imports: [ - WidgetComponent, - RollIndicatorComponent, - YawIndicatorComponent, - PitchIndicatorComponent, - TiltGaugeIconDirective, - MatButtonModule, - TranslocoPipe, - ], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TiltSensorWidgetComponent { - @Input() public tilt: TiltData | undefined; - - @Input() public title = ''; - - @Input() public canBeDeleted = false; - - @Input() public canBeEdited = false; - - @Output() public readonly edit = new EventEmitter(); - - @Output() public readonly delete = new EventEmitter(); - - @Output() public readonly compensateTilt = new EventEmitter(); - - @Output() public readonly resetTiltCompensation = new EventEmitter(); - - public onCompensateTilt( - compensationData?: TiltData - ): void { - this.compensateTilt.emit(compensationData); - } - - public onResetTiltCompensation(): void { - this.resetTiltCompensation.emit(); - } - - public onEdit(): void { - this.edit.emit(); - } - - public onDelete(): void { - this.delete.emit(); - } -} diff --git a/modules/widgets/src/lib/tilt/widget/yaw-indicator/index.ts b/modules/widgets/src/lib/tilt/widget/yaw-indicator/index.ts deleted file mode 100644 index ef639c94..00000000 --- a/modules/widgets/src/lib/tilt/widget/yaw-indicator/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './yaw-indicator.component'; diff --git a/modules/widgets/src/lib/tilt/widget/yaw-indicator/yaw-indicator.component.html b/modules/widgets/src/lib/tilt/widget/yaw-indicator/yaw-indicator.component.html deleted file mode 100644 index f926a7cd..00000000 --- a/modules/widgets/src/lib/tilt/widget/yaw-indicator/yaw-indicator.component.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - diff --git a/modules/widgets/src/lib/tilt/widget/yaw-indicator/yaw-indicator.component.scss b/modules/widgets/src/lib/tilt/widget/yaw-indicator/yaw-indicator.component.scss deleted file mode 100644 index d68c1100..00000000 --- a/modules/widgets/src/lib/tilt/widget/yaw-indicator/yaw-indicator.component.scss +++ /dev/null @@ -1,20 +0,0 @@ -:host { - display: block; - height: 100%; - position: relative; - overflow: hidden; -} - -.yaw-icon { - fill: none; - stroke: var(--app-text-color); - stroke-linecap: round; - - &__outline { - stroke-width: 2; - } - - &__inline { - stroke-width: 1; - } -} diff --git a/modules/widgets/src/lib/tilt/widget/yaw-indicator/yaw-indicator.component.ts b/modules/widgets/src/lib/tilt/widget/yaw-indicator/yaw-indicator.component.ts deleted file mode 100644 index 514a5b15..00000000 --- a/modules/widgets/src/lib/tilt/widget/yaw-indicator/yaw-indicator.component.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; -import { TiltGaugeComponent, TiltGaugeOptions } from '@app/shared-ui'; - -@Component({ - standalone: true, - selector: 'lib-yaw-indicator', - templateUrl: './yaw-indicator.component.html', - styleUrls: [ './yaw-indicator.component.scss' ], - imports: [ - TiltGaugeComponent - ], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class YawIndicatorComponent { - @Input() public yaw?: number; - - public readonly options: Partial = { - chartRotation: 90, - gaugeSteps: 9, - bracketAngleSizeDegrees: 181 - }; -} diff --git a/modules/widgets/src/lib/voltage/voltage-widget-config-factory.service.ts b/modules/widgets/src/lib/voltage/voltage-widget-config-factory.service.ts index 90355319..01d58cd2 100644 --- a/modules/widgets/src/lib/voltage/voltage-widget-config-factory.service.ts +++ b/modules/widgets/src/lib/voltage/voltage-widget-config-factory.service.ts @@ -7,6 +7,7 @@ import { AttachedIoModesModel, AttachedIoPortModeInfoModel, VoltageWidgetConfigModel, + WidgetConfigModel, attachedIoModesIdFn, attachedIoPortModeInfoIdFn } from '@app/store'; @@ -24,10 +25,12 @@ export class VoltageWidgetConfigFactoryService implements IControlSchemeWidgetCo public createConfigs( attachedIos: AttachedIoModel[], ioPortModes: Dictionary, - portModesInfo: Dictionary + portModesInfo: Dictionary, + existingWidgets: WidgetConfigModel[] ): VoltageWidgetConfigModel[] { const result: VoltageWidgetConfigModel[] = []; - for (const io of attachedIos) { + const freeIos = attachedIos.filter((io) => !existingWidgets.some((widget) => widget.hubId === io.hubId && widget.portId === io.portId)); + for (const io of freeIos) { const portInputModeIds = (ioPortModes[attachedIoModesIdFn(io)]?.portInputModes ?? []); const attacheIoPortModes = portInputModeIds.map((modeId) => { const portModeInfo = portModesInfo[attachedIoPortModeInfoIdFn({ ...io, modeId })]; diff --git a/modules/widgets/src/lib/voltage/voltage-widget-read-task-factory.service.ts b/modules/widgets/src/lib/voltage/voltage-widget-read-task-factory.service.ts index cbb4e412..2dbc5d4b 100644 --- a/modules/widgets/src/lib/voltage/voltage-widget-read-task-factory.service.ts +++ b/modules/widgets/src/lib/voltage/voltage-widget-read-task-factory.service.ts @@ -2,30 +2,39 @@ import { Store } from '@ngrx/store'; import { Observable, concatWith, filter, map, switchMap, take, takeUntil } from 'rxjs'; import { ValueTransformers } from 'rxpoweredup'; import { Injectable } from '@angular/core'; +import { Actions, ofType } from '@ngrx/effects'; import { WidgetType } from '@app/shared-misc'; -import { ControlSchemeWidgetsDataModel, HUBS_SELECTORS, HubModel, HubStorageService, IWidgetReadTaskFactory, VoltageWidgetConfigModel } from '@app/store'; +import { CONTROL_SCHEME_ACTIONS, ControlSchemeWidgetsDataModel, HUBS_SELECTORS, HubModel, HubStorageService, VoltageWidgetConfigModel } from '@app/store'; + +import { IWidgetReadTaskFactory } from '../i-widget-read-task-factory'; @Injectable() export class VoltageWidgetReadTaskFactoryService implements IWidgetReadTaskFactory { + constructor( + private readonly hubStorage: HubStorageService, + private readonly actions: Actions, + private readonly store: Store + ) { + } + public createReadTask( config: VoltageWidgetConfigModel, - store: Store, - hubStorage: HubStorageService, - schemeStop$: Observable ): Observable<{ widgetId: number; data: ControlSchemeWidgetsDataModel; }> { - return store.select(HUBS_SELECTORS.selectHub(config.hubId)).pipe( + return this.store.select(HUBS_SELECTORS.selectHub(config.hubId)).pipe( take(1), filter((hubModel): hubModel is HubModel => !!hubModel), switchMap(({ hubType }) => { - const hub = hubStorage.get(config.hubId); + const hub = this.hubStorage.get(config.hubId); return hub.ports.getPortValue(config.portId, config.modeId, ValueTransformers.voltage(hubType)).pipe( concatWith(hub.ports.portValueChanges(config.portId, config.modeId, config.valueChangeThreshold, ValueTransformers.voltage(hubType))) ); }), - takeUntil(schemeStop$), + takeUntil(this.actions.pipe( + ofType(CONTROL_SCHEME_ACTIONS.schemeStopped) + )), map((voltage) => ({ widgetId: config.id, data: { diff --git a/modules/widgets/src/lib/widget-component-factory.service.ts b/modules/widgets/src/lib/widget-component-factory.service.ts index 73a329bf..5ebbe2d3 100644 --- a/modules/widgets/src/lib/widget-component-factory.service.ts +++ b/modules/widgets/src/lib/widget-component-factory.service.ts @@ -4,15 +4,19 @@ import { WidgetConfigModel } from '@app/store'; import { ControlSchemeWidgetDescriptor, IControlSchemeWidgetComponentFactory } from '@app/control-scheme-view'; import { TemperatureWidgetComponentFactoryService } from './temperature'; -import { TiltWidgetComponentFactoryService } from './tilt'; import { VoltageWidgetComponentFactoryService } from './voltage'; +import { PitchWidgetComponentFactoryService } from './pitch'; +import { YawWidgetComponentFactoryService } from './yaw'; +import { RollWidgetComponentFactoryService } from './roll'; @Injectable() export class WidgetComponentFactoryService implements IControlSchemeWidgetComponentFactory { constructor( private readonly temperatureWidgetFactory: TemperatureWidgetComponentFactoryService, - private readonly tiltWidgetFactory: TiltWidgetComponentFactoryService, - private readonly voltageWidgetFactory: VoltageWidgetComponentFactoryService + private readonly voltageWidgetFactory: VoltageWidgetComponentFactoryService, + private readonly pitchWidgetFactory: PitchWidgetComponentFactoryService, + private readonly yawWidgetFactory: YawWidgetComponentFactoryService, + private readonly rollWidgetFactory: RollWidgetComponentFactoryService ) { } @@ -23,10 +27,14 @@ export class WidgetComponentFactoryService implements IControlSchemeWidgetCompon switch (config.widgetType) { case WidgetType.Temperature: return this.temperatureWidgetFactory.createWidget(container, config); - case WidgetType.Tilt: - return this.tiltWidgetFactory.createWidget(container, config); case WidgetType.Voltage: return this.voltageWidgetFactory.createWidget(container, config); + case WidgetType.Pitch: + return this.pitchWidgetFactory.createWidget(container, config); + case WidgetType.Yaw: + return this.yawWidgetFactory.createWidget(container, config); + case WidgetType.Roll: + return this.rollWidgetFactory.createWidget(container, config); } } } diff --git a/modules/widgets/src/lib/widget-config-factory.service.ts b/modules/widgets/src/lib/widget-config-factory.service.ts index 5f55746f..e01f6c23 100644 --- a/modules/widgets/src/lib/widget-config-factory.service.ts +++ b/modules/widgets/src/lib/widget-config-factory.service.ts @@ -2,29 +2,33 @@ import { Injectable } from '@angular/core'; import { Dictionary } from '@ngrx/entity'; import { AttachedIoModel, AttachedIoModesModel, AttachedIoPortModeInfoModel, WidgetConfigModel } from '@app/store'; import { IControlSchemeWidgetConfigFactory } from '@app/control-scheme-view'; +import { WidgetType } from '@app/shared-misc'; import { VoltageWidgetConfigFactoryService } from './voltage'; -import { TiltWidgetConfigFactoryService } from './tilt'; import { TemperatureWidgetConfigFactoryService } from './temperature'; +import { CommonTiltWidgetsConfigFactoryService } from './common/common-tilt-widgets-config-factory.service'; @Injectable() export class WidgetConfigFactoryService implements IControlSchemeWidgetConfigFactory { constructor( private readonly voltageWidgetConfigFactoryService: VoltageWidgetConfigFactoryService, - private readonly tiltWidgetConfigFactoryService: TiltWidgetConfigFactoryService, private readonly temperatureWidgetConfigFactoryService: TemperatureWidgetConfigFactoryService, + private readonly commonTiltWidgetsConfigFactoryService: CommonTiltWidgetsConfigFactoryService ) { } public createConfigs( attachedIos: AttachedIoModel[], ioPortModes: Dictionary, - portModesInfo: Dictionary + portModesInfo: Dictionary, + existingWidgets: WidgetConfigModel[] ): WidgetConfigModel[] { return [ - ...this.voltageWidgetConfigFactoryService.createConfigs(attachedIos, ioPortModes, portModesInfo), - ...this.tiltWidgetConfigFactoryService.createConfigs(attachedIos, ioPortModes, portModesInfo), - ...this.temperatureWidgetConfigFactoryService.createConfigs(attachedIos, ioPortModes, portModesInfo), + ...this.voltageWidgetConfigFactoryService.createConfigs(attachedIos, ioPortModes, portModesInfo, existingWidgets), + ...this.temperatureWidgetConfigFactoryService.createConfigs(attachedIos, ioPortModes, portModesInfo, existingWidgets), + ...this.commonTiltWidgetsConfigFactoryService.createConfigs(WidgetType.Pitch, attachedIos, ioPortModes, portModesInfo, existingWidgets), + ...this.commonTiltWidgetsConfigFactoryService.createConfigs(WidgetType.Yaw, attachedIos, ioPortModes, portModesInfo, existingWidgets), + ...this.commonTiltWidgetsConfigFactoryService.createConfigs(WidgetType.Roll, attachedIos, ioPortModes, portModesInfo, existingWidgets), ]; } } diff --git a/modules/widgets/src/lib/widget-connection-info-l10n.service.ts b/modules/widgets/src/lib/widget-connection-info-l10n.service.ts index dd470224..14bea834 100644 --- a/modules/widgets/src/lib/widget-connection-info-l10n.service.ts +++ b/modules/widgets/src/lib/widget-connection-info-l10n.service.ts @@ -10,8 +10,10 @@ import { ATTACHED_IO_SELECTORS, HUBS_SELECTORS } from '@app/store'; export class WidgetConnectionInfoL10nService { private readonly widgetTypeToWidgetConnectionL10nKeyMap: { [k in WidgetType]?: string } = { [WidgetType.Voltage]: 'controlScheme.widgets.voltage.connectionInfo', - [WidgetType.Tilt]: 'controlScheme.widgets.tilt.connectionInfo', [WidgetType.Temperature]: 'controlScheme.widgets.temperature.connectionInfo', + [WidgetType.Pitch]: 'controlScheme.widgets.pitch.connectionInfo', + [WidgetType.Yaw]: 'controlScheme.widgets.yaw.connectionInfo', + [WidgetType.Roll]: 'controlScheme.widgets.roll.connectionInfo', }; constructor( diff --git a/modules/widgets/src/lib/widget-read-task-factory.service.ts b/modules/widgets/src/lib/widget-read-task-factory.service.ts deleted file mode 100644 index a80287f5..00000000 --- a/modules/widgets/src/lib/widget-read-task-factory.service.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Store } from '@ngrx/store'; -import { Observable } from 'rxjs'; -import { Injectable } from '@angular/core'; -import { ControlSchemeWidgetsDataModel, HubStorageService, IWidgetReadTaskFactory, WidgetConfigModel } from '@app/store'; -import { WidgetType } from '@app/shared-misc'; - -import { TemperatureWidgetReadTaskFactoryService } from './temperature'; -import { TiltWidgetReadTaskFactoryService } from './tilt'; -import { VoltageWidgetReadTaskFactoryService } from './voltage'; - -@Injectable() -export class WidgetReadTaskFactoryService implements IWidgetReadTaskFactory { - private readonly widgetReaderTaskFactories: { [k in WidgetType]: IWidgetReadTaskFactory } = { - [WidgetType.Temperature]: this.temperatureWidgetReadTaskFactoryService, - [WidgetType.Tilt]: this.tiltWidgetReadTaskFactoryService, - [WidgetType.Voltage]: this.voltageWidgetReadTaskFactoryService - }; - - constructor( - private readonly temperatureWidgetReadTaskFactoryService: TemperatureWidgetReadTaskFactoryService, - private readonly tiltWidgetReadTaskFactoryService: TiltWidgetReadTaskFactoryService, - private readonly voltageWidgetReadTaskFactoryService: VoltageWidgetReadTaskFactoryService - ) { - } - - public createReadTask( - config: WidgetConfigModel & { widgetType: T }, - store: Store, - hubStorage: HubStorageService, - schemeStop$: Observable - ): Observable<{ - widgetId: number; - data: ControlSchemeWidgetsDataModel; - }> { - const widgetType: T = config.widgetType; - const readerTaskFactory = this.widgetReaderTaskFactories[widgetType]; - return readerTaskFactory.createReadTask( - config, - store, - hubStorage, - schemeStop$ - ); - } -} diff --git a/modules/widgets/src/lib/widget-settings-component-factory.service.ts b/modules/widgets/src/lib/widget-settings-component-factory.service.ts index f73cb7cb..58dc0a43 100644 --- a/modules/widgets/src/lib/widget-settings-component-factory.service.ts +++ b/modules/widgets/src/lib/widget-settings-component-factory.service.ts @@ -4,23 +4,25 @@ import { ControlSchemeWidgetSettingsDescriptor, IControlSchemeWidgetSettingsComp import { WidgetConfigModel } from '@app/store'; import { TemperatureWidgetSettingsComponentFactoryService } from './temperature'; -import { TiltWidgetSettingsComponentFactoryService } from './tilt'; import { VoltageWidgetSettingsComponentFactoryService } from './voltage'; +import { CommonTiltWidgetsSettingsComponentFactoryService } from './common'; @Injectable() export class WidgetSettingsComponentFactoryService implements IControlSchemeWidgetSettingsComponentFactory { constructor( private readonly temperatureWidgetSettingsComponentFactoryService: TemperatureWidgetSettingsComponentFactoryService, - private readonly tiltWidgetSettingsComponentFactoryService: TiltWidgetSettingsComponentFactoryService, - private readonly voltageWidgetSettingsComponentFactoryService: VoltageWidgetSettingsComponentFactoryService + private readonly voltageWidgetSettingsComponentFactoryService: VoltageWidgetSettingsComponentFactoryService, + private readonly commonTiltWidgetSettingsComponentFactoryService: CommonTiltWidgetsSettingsComponentFactoryService ) { } public hasSettings(widgetType: WidgetType): boolean { switch (widgetType) { case WidgetType.Temperature: - case WidgetType.Tilt: case WidgetType.Voltage: + case WidgetType.Pitch: + case WidgetType.Yaw: + case WidgetType.Roll: return true; } } @@ -32,10 +34,14 @@ export class WidgetSettingsComponentFactoryService implements IControlSchemeWidg switch (config.widgetType) { case WidgetType.Temperature: return this.temperatureWidgetSettingsComponentFactoryService.createWidgetSettings(container, config); - case WidgetType.Tilt: - return this.tiltWidgetSettingsComponentFactoryService.createWidgetSettings(container, config); case WidgetType.Voltage: return this.voltageWidgetSettingsComponentFactoryService.createWidgetSettings(container, config); + case WidgetType.Pitch: + return this.commonTiltWidgetSettingsComponentFactoryService.createWidgetSettings(container, config); + case WidgetType.Yaw: + return this.commonTiltWidgetSettingsComponentFactoryService.createWidgetSettings(container, config); + case WidgetType.Roll: + return this.commonTiltWidgetSettingsComponentFactoryService.createWidgetSettings(container, config); } } } diff --git a/modules/widgets/src/lib/widget-type-to-l10n-key-mapper.service.ts b/modules/widgets/src/lib/widget-type-to-l10n-key-mapper.service.ts index 3f46ddc4..fc922dc5 100644 --- a/modules/widgets/src/lib/widget-type-to-l10n-key-mapper.service.ts +++ b/modules/widgets/src/lib/widget-type-to-l10n-key-mapper.service.ts @@ -6,8 +6,10 @@ import { WidgetType } from '@app/shared-misc'; export class WidgetTypeToL10nKeyMapperService implements IWidgetTypeToL10nKeyMapper { private readonly widgetTypeToL10n: { [k in WidgetType]: string } = { [WidgetType.Voltage]: 'controlScheme.widgets.voltage.name', - [WidgetType.Tilt]: 'controlScheme.widgets.tilt.name', - [WidgetType.Temperature]: 'controlScheme.widgets.temperature.name' + [WidgetType.Temperature]: 'controlScheme.widgets.temperature.name', + [WidgetType.Pitch]: 'controlScheme.widgets.pitch.name', + [WidgetType.Yaw]: 'controlScheme.widgets.yaw.name', + [WidgetType.Roll]: 'controlScheme.widgets.roll.name', }; public map( diff --git a/modules/widgets/src/lib/widgets-read-task-factory.service.ts b/modules/widgets/src/lib/widgets-read-task-factory.service.ts new file mode 100644 index 00000000..4079cb5c --- /dev/null +++ b/modules/widgets/src/lib/widgets-read-task-factory.service.ts @@ -0,0 +1,44 @@ +import { Observable } from 'rxjs'; +import { Injectable } from '@angular/core'; +import { ControlSchemeWidgetsDataModel, IWidgetsReadTasksFactory, WidgetConfigModel } from '@app/store'; +import { WidgetType } from '@app/shared-misc'; + +import { TemperatureWidgetReadTaskFactoryService } from './temperature'; +import { VoltageWidgetReadTaskFactoryService } from './voltage'; +import { IWidgetReadTaskFactory } from './i-widget-read-task-factory'; +import { CommonTiltWidgetsReadTaskFactoryService } from './common'; + +@Injectable() +export class WidgetsReadTaskFactoryService implements IWidgetsReadTasksFactory { + private readonly widgetReaderTaskFactories: { [k in WidgetType]: IWidgetReadTaskFactory } = { + [WidgetType.Temperature]: this.temperatureWidgetReadTaskFactoryService, + [WidgetType.Voltage]: this.voltageWidgetReadTaskFactoryService, + [WidgetType.Pitch]: this.commonTiltWidgetReadTaskFactoryService, + [WidgetType.Yaw]: this.commonTiltWidgetReadTaskFactoryService, + [WidgetType.Roll]: this.commonTiltWidgetReadTaskFactoryService, + }; + + constructor( + private readonly temperatureWidgetReadTaskFactoryService: TemperatureWidgetReadTaskFactoryService, + private readonly voltageWidgetReadTaskFactoryService: VoltageWidgetReadTaskFactoryService, + private readonly commonTiltWidgetReadTaskFactoryService: CommonTiltWidgetsReadTaskFactoryService + ) { + } + + public createReadTasks( + configs: ReadonlyArray, + ): Array> { + // TODO: we can group tilt widget tasks by hubId and portId and generate a single task for each group + // to reduce the number of messages sent to the hub. But it's not a priority now. + return configs.map((config) => this.getWidgetReaderTaskFactory(config.widgetType).createReadTask(config)); + } + + private getWidgetReaderTaskFactory( + widgetType: T + ): IWidgetReadTaskFactory { + return this.widgetReaderTaskFactories[widgetType]; + } +} diff --git a/modules/widgets/src/lib/yaw/index.ts b/modules/widgets/src/lib/yaw/index.ts new file mode 100644 index 00000000..0032190e --- /dev/null +++ b/modules/widgets/src/lib/yaw/index.ts @@ -0,0 +1,2 @@ +export * from './provide-yaw-widget'; +export * from './yaw-widget-component-factory.service'; diff --git a/modules/widgets/src/lib/yaw/provide-yaw-widget.ts b/modules/widgets/src/lib/yaw/provide-yaw-widget.ts new file mode 100644 index 00000000..e5259123 --- /dev/null +++ b/modules/widgets/src/lib/yaw/provide-yaw-widget.ts @@ -0,0 +1,9 @@ +import { Provider } from '@angular/core'; + +import { YawWidgetComponentFactoryService } from './yaw-widget-component-factory.service'; + +export function provideYawWidget(): Provider[] { + return [ + YawWidgetComponentFactoryService + ]; +} diff --git a/modules/widgets/src/lib/yaw/yaw-widget-component-factory.service.ts b/modules/widgets/src/lib/yaw/yaw-widget-component-factory.service.ts new file mode 100644 index 00000000..4fd380ec --- /dev/null +++ b/modules/widgets/src/lib/yaw/yaw-widget-component-factory.service.ts @@ -0,0 +1,49 @@ +import { Injectable, ViewContainerRef } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { ATTACHED_IO_PROPS_ACTIONS, YawWidgetConfigModel } from '@app/store'; +import { ControlSchemeWidgetDescriptor } from '@app/control-scheme-view'; + +import { YawWidgetComponent } from './yaw-widget.component'; +import { SELECT_YAW_WIDGET_DATA } from './yaw-widget.selectors'; + +@Injectable() +export class YawWidgetComponentFactoryService { + constructor( + private readonly store: Store + ) { + } + + public createWidget( + container: ViewContainerRef, + config: YawWidgetConfigModel + ): ControlSchemeWidgetDescriptor { + const componentRef = container.createComponent(YawWidgetComponent); + componentRef.setInput('title', config.title); + + // eslint-disable-next-line @ngrx/no-store-subscription + const dataSub = this.store.select(SELECT_YAW_WIDGET_DATA(config)).subscribe((yaw) => { + componentRef.setInput('yaw', yaw); + }); + + const compensateYawSub = componentRef.instance.compensate.subscribe((currentYaw) => { + this.store.dispatch(ATTACHED_IO_PROPS_ACTIONS.compensateYaw({ ...config, currentYaw })); + }); + + const resetYawCompensationSub = componentRef.instance.resetCompensation.subscribe(() => { + this.store.dispatch(ATTACHED_IO_PROPS_ACTIONS.resetYawCompensation(config)); + }); + + return { + edit$: componentRef.instance.edit, + delete$: componentRef.instance.delete, + setCanBeEdited: (canBeEdited: boolean) => componentRef.setInput('canBeEdited', canBeEdited), + setCanBeDeleted: (canBeDeleted: boolean) => componentRef.setInput('canBeDeleted', canBeDeleted), + destroy: (): void => { + dataSub.unsubscribe(); + compensateYawSub.unsubscribe(); + resetYawCompensationSub.unsubscribe(); + componentRef.destroy(); + } + }; + } +} diff --git a/modules/widgets/src/lib/yaw/yaw-widget.component.html b/modules/widgets/src/lib/yaw/yaw-widget.component.html new file mode 100644 index 00000000..80e2d02d --- /dev/null +++ b/modules/widgets/src/lib/yaw/yaw-widget.component.html @@ -0,0 +1,52 @@ + +
+ + + + + + + + + + + + + + + @if (yaw !== null) { + {{ yaw }}° + } @else { +   + } + + + + +
+
diff --git a/modules/widgets/src/lib/yaw/yaw-widget.component.scss b/modules/widgets/src/lib/yaw/yaw-widget.component.scss new file mode 100644 index 00000000..4e5e4463 --- /dev/null +++ b/modules/widgets/src/lib/yaw/yaw-widget.component.scss @@ -0,0 +1,47 @@ +.roll-content { + display: grid; + grid-template-columns: max-content auto; + gap: 0 20px; + grid-template-rows: auto auto; + width: 100%; + height: 100%; + + --mat-text-button-icon-spacing: 0px; +} + +.pitch-value { + font-weight: bold; + align-self: center; + justify-self: center; +} + +.pitch-control-button { + height: 36px; + width: 36px; + margin: 0; + padding: 0; + min-width: 36px; + justify-self: end; +} + +.roll-gauge { + display: block; + height: 100%; + position: relative; + overflow: hidden; + grid-row: span 3; +} + +.yaw-icon { + fill: none; + stroke: var(--app-text-color); + stroke-linecap: round; + + &__outline { + stroke-width: 2; + } + + &__inline { + stroke-width: 1; + } +} diff --git a/modules/widgets/src/lib/yaw/yaw-widget.component.ts b/modules/widgets/src/lib/yaw/yaw-widget.component.ts new file mode 100644 index 00000000..492bff88 --- /dev/null +++ b/modules/widgets/src/lib/yaw/yaw-widget.component.ts @@ -0,0 +1,64 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { TranslocoPipe } from '@ngneat/transloco'; +import { MatMenuItem } from '@angular/material/menu'; +import { MatIcon } from '@angular/material/icon'; +import { TiltGaugeComponent, TiltGaugeIconDirective, TiltGaugeOptions, WidgetComponent } from '@app/shared-ui'; + +@Component({ + standalone: true, + selector: 'lib-yaw-sensor-widget', + templateUrl: './yaw-widget.component.html', + styleUrls: [ './yaw-widget.component.scss' ], + imports: [ + WidgetComponent, + TiltGaugeIconDirective, + MatButtonModule, + TranslocoPipe, + TiltGaugeComponent, + MatMenuItem, + MatIcon + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class YawWidgetComponent { + @Input() public yaw: number | null = null; + + @Input() public title = ''; + + @Input() public canBeDeleted = false; + + @Input() public canBeEdited = false; + + @Output() public readonly edit = new EventEmitter(); + + @Output() public readonly delete = new EventEmitter(); + + @Output() public readonly compensate = new EventEmitter(); + + @Output() public readonly resetCompensation = new EventEmitter(); + + public readonly options: Partial = { + chartRotation: 90, + gaugeSteps: 9, + bracketAngleSizeDegrees: 181 + }; + + public onCompensate(): void { + if (this.yaw !== null) { + this.compensate.emit(this.yaw); + } + } + + public onResetCompensation(): void { + this.resetCompensation.emit(); + } + + public onEdit(): void { + this.edit.emit(); + } + + public onDelete(): void { + this.delete.emit(); + } +} diff --git a/modules/widgets/src/lib/yaw/yaw-widget.selectors.ts b/modules/widgets/src/lib/yaw/yaw-widget.selectors.ts new file mode 100644 index 00000000..f7a07ff6 --- /dev/null +++ b/modules/widgets/src/lib/yaw/yaw-widget.selectors.ts @@ -0,0 +1,8 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { createSelector } from '@ngrx/store'; +import { CONTROL_SCHEME_WIDGETS_DATA_SELECTORS, YawWidgetConfigModel } from '@app/store'; + +export const SELECT_YAW_WIDGET_DATA = (config: YawWidgetConfigModel) => createSelector( + CONTROL_SCHEME_WIDGETS_DATA_SELECTORS.selectWidgetTiltData(config), + (widgetData) => widgetData?.yaw ?? null +); diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index ba5aabec..941b82ca 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -367,16 +367,34 @@ "valueChangeThresholdTitle": "Value change threshold (volts)", "noDataTitle": "No data" }, - "tilt": { - "name": "Tilt", - "defaultName": "Tilt", + "pitch": { + "name": "Pitch", + "defaultName": "Pitch", "connectionInfo": "{hubName}:{portName} - {ioName}", "valueChangeThresholdTitle": "Value change threshold (°)", - "invertYawOptionTitle": "Invert yaw", - "invertPitchOptionTitle": "Invert pitch", - "invertRollOptionTitle": "Invert roll", - "compensateTiltButtonTitle": "Compensate", - "resetTiltCompensationButtonTitle": "Reset", + "invertOptionTitle": "Invert", + "compensateButtonTitle": "Trim", + "resetCompensationButtonTitle": "Reset trim", + "noDataTitle": "No data" + }, + "yaw": { + "name": "Yaw", + "defaultName": "Yaw", + "connectionInfo": "{hubName}:{portName} - {ioName}", + "valueChangeThresholdTitle": "Value change threshold (°)", + "invertOptionTitle": "Invert", + "compensateButtonTitle": "Trim", + "resetCompensationButtonTitle": "Reset trim", + "noDataTitle": "No data" + }, + "roll": { + "name": "Roll", + "defaultName": "Roll", + "connectionInfo": "{hubName}:{portName} - {ioName}", + "valueChangeThresholdTitle": "Value change threshold (°)", + "invertOptionTitle": "Invert", + "compensateButtonTitle": "Trim", + "resetCompensationButtonTitle": "Reset trim", "noDataTitle": "No data" }, "temperature": { @@ -775,7 +793,8 @@ "featHubHWAndSWVersionInHubInfo": "Display of hub hardware and software versions in the hub info panel" }, "1-3-0": { - "featOnOffToggleInputPreset": "On/off toggle mode for 'Speed' and 'Servo' binding inputs" + "featOnOffToggleInputPreset": "On/off toggle mode for 'Speed' and 'Servo' binding inputs", + "featSeparatePitchYawRollTiltSensorWidgets": "Separate pitch, yaw, and roll tilt sensor widgets" } } } diff --git a/src/assets/i18n/ru.json b/src/assets/i18n/ru.json index 26ad8e76..8a348e30 100644 --- a/src/assets/i18n/ru.json +++ b/src/assets/i18n/ru.json @@ -367,16 +367,34 @@ "valueChangeThresholdTitle": "Порог изменения значения (вольт)", "noDataTitle": "Нет данных" }, - "tilt": { - "name": "Наклон", - "defaultName": "Наклон", + "pitch": { + "name": "Тангаж", + "defaultName": "Тангаж", "connectionInfo": "{hubName}:{portName} - {ioName}", "valueChangeThresholdTitle": "Порог изменения значения (°)", - "invertYawOptionTitle": "Инвертировать рыскание", - "invertPitchOptionTitle": "Инвертировать тангаж", - "invertRollOptionTitle": "Инвертировать крен", - "compensateTiltButtonTitle": "Компенсация", - "resetTiltCompensationButtonTitle": "Сброс", + "invertOptionTitle": "Инвертировать", + "compensateButtonTitle": "Компенсация", + "resetCompensationButtonTitle": "Сброс компенсации", + "noDataTitle": "Нет данных" + }, + "yaw": { + "name": "Рыскание", + "defaultName": "Рыскание", + "connectionInfo": "{hubName}:{portName} - {ioName}", + "valueChangeThresholdTitle": "Порог изменения значения (°)", + "invertOptionTitle": "Инвертировать", + "compensateButtonTitle": "Компенсация", + "resetCompensationButtonTitle": "Сброс компенсации", + "noDataTitle": "Нет данных" + }, + "roll": { + "name": "Крен", + "defaultName": "Крен", + "connectionInfo": "{hubName}:{portName} - {ioName}", + "valueChangeThresholdTitle": "Порог изменения значения (°)", + "invertOptionTitle": "Инвертировать", + "compensateButtonTitle": "Компенсация", + "resetCompensationButtonTitle": "Сброс компенсации", "noDataTitle": "Нет данных" }, "temperature": { @@ -775,7 +793,8 @@ "featHubHWAndSWVersionInHubInfo": "Версия аппаратного и программного обеспечения хаба в информации о хабе" }, "1-3-0": { - "featOnOffToggleInputPreset": "Режим 'Вкл/Выкл' для управления привязками 'Скорость' и 'Серво'" + "featOnOffToggleInputPreset": "Режим 'Вкл/Выкл' для управления привязками 'Скорость' и 'Серво'", + "featSeparatePitchYawRollTiltSensorWidgets": "Отдельные виджеты для отображения тангажа, рыскания и крена" } } }