diff --git a/src/app/control-schemes/control-scheme-page/control-scheme-view-io-list/control-scheme-view-io-list.component.ts b/src/app/control-schemes/control-scheme-page/control-scheme-view-io-list/control-scheme-view-io-list.component.ts index e18075b9..77fc0d23 100644 --- a/src/app/control-schemes/control-scheme-page/control-scheme-view-io-list/control-scheme-view-io-list.component.ts +++ b/src/app/control-schemes/control-scheme-page/control-scheme-view-io-list/control-scheme-view-io-list.component.ts @@ -55,7 +55,11 @@ export class ControlSchemeViewIoListComponent { this.treeControl.dataNodes = this.dataSource.data; if (!this.initialExpansionDone) { this.initialExpansionDone = true; - this.treeControl.expandAll(); + this.treeControl.dataNodes + .filter((node) => node.initiallyExpanded) + .forEach((node) => { + this.treeControl.expand(node); + }); } } diff --git a/src/app/control-schemes/control-scheme-page/control-scheme-view-io-list/io-tree-node/io-tree-node.component.html b/src/app/control-schemes/control-scheme-page/control-scheme-view-io-list/io-tree-node/io-tree-node.component.html index c8040f50..74b08ca8 100644 --- a/src/app/control-schemes/control-scheme-page/control-scheme-view-io-list/io-tree-node/io-tree-node.component.html +++ b/src/app/control-schemes/control-scheme-page/control-scheme-view-io-list/io-tree-node/io-tree-node.component.html @@ -3,6 +3,17 @@ [ioType]="node.ioType" [isConnected]="node.isConnected" > + + + + {{ node.runningTask | portCommandTaskSummary | ngrxPush }} + + + + + {{ node.lastExecutedTask | portCommandTaskSummary | ngrxPush }} + + ): ControlSchemeViewIoTreeNode { const ioId = attachedIosIdFn({ hubId: hubConfig.hubId, portId }); const io = iosEntities[ioId]; @@ -66,6 +70,9 @@ function createIoTreeNode( const useDecelerationProfile = bindings.filter((b) => b.useDecelerationProfile && b.portId === portId && b.hubId === hubConfig.hubId) .length > 0; + const runningTask = portTasksModelDictionary[hubPortTasksIdFn({ hubId: hubConfig.hubId, portId })]?.runningTask ?? undefined; + const lastExecutedTask = portTasksModelDictionary[hubPortTasksIdFn({ hubId: hubConfig.hubId, portId })]?.lastExecutedTask ?? undefined; + return { path: `${parentPath}.${portId}`, nodeType: ControlSchemeNodeTypes.Io, @@ -78,7 +85,10 @@ function createIoTreeNode( accelerationTimeMs: portConfig?.accelerationTimeMs ?? 0, useDecelerationProfile, decelerationTimeMs: portConfig?.decelerationTimeMs ?? 0, - children: [] + runningTask, + lastExecutedTask, + children: [], + initiallyExpanded: false }; } @@ -100,7 +110,8 @@ function createBindingTreeNode( binding, controlSchemeId, ioHasNoRequiredCapabilities, - children: [] + children: [], + initiallyExpanded: false }; } @@ -113,6 +124,7 @@ export const CONTROL_SCHEME_VIEW_SELECTORS = { ATTACHED_IO_MODES_SELECTORS.selectEntities, ATTACHED_IO_PORT_MODE_INFO_SELECTORS.selectEntities, PORT_TASKS_SELECTORS.selectLastExecutedBindingIds, + PORT_TASKS_SELECTORS.selectEntities, ( scheme: ControlSchemeModel | undefined, hubEntities: Dictionary, @@ -120,7 +132,8 @@ export const CONTROL_SCHEME_VIEW_SELECTORS = { iosEntities: Dictionary, ioSupportedModesEntities: Dictionary, portModeInfoEntities: Dictionary, - lastExecutedTasksBindingIds: ReadonlySet + lastExecutedTasksBindingIds: ReadonlySet, + portCommandTasksEntities: Dictionary, ): ControlSchemeViewHubTreeNode[] => { if (!scheme) { return []; @@ -178,7 +191,8 @@ export const CONTROL_SCHEME_VIEW_SELECTORS = { binding.portId, scheme.bindings, scheme.portConfigs, - schemeId + schemeId, + portCommandTasksEntities ); hubIosViewMap.set(ioId, ioViewModel); diff --git a/src/app/control-schemes/control-scheme-page/types.ts b/src/app/control-schemes/control-scheme-page/types.ts index fc3fcbc3..a308d7a0 100644 --- a/src/app/control-schemes/control-scheme-page/types.ts +++ b/src/app/control-schemes/control-scheme-page/types.ts @@ -1,5 +1,5 @@ import { HubType, IOType } from '@nvsukhanov/rxpoweredup'; -import { ControlSchemeBinding } from '@app/store'; +import { ControlSchemeBinding, PortCommandTask } from '@app/store'; export enum ControlSchemeNodeTypes { Hub = 'Hub', @@ -15,6 +15,7 @@ export type ControlSchemeViewBindingTreeNodeData = { readonly binding: ControlSchemeBinding; readonly ioHasNoRequiredCapabilities: boolean; readonly children: []; + readonly initiallyExpanded: boolean; }; export type ControlSchemeViewIoTreeNode = { @@ -29,7 +30,10 @@ export type ControlSchemeViewIoTreeNode = { readonly accelerationTimeMs: number; readonly useDecelerationProfile: boolean; readonly decelerationTimeMs: number; + readonly runningTask?: PortCommandTask; + readonly lastExecutedTask?: PortCommandTask; readonly children: ControlSchemeViewBindingTreeNodeData[]; + readonly initiallyExpanded: boolean; }; export type ControlSchemeViewHubTreeNode = { @@ -44,6 +48,7 @@ export type ControlSchemeViewHubTreeNode = { readonly hasCommunication: boolean; // TODO: remove, may impact performance, Use ad-hoc selector instead readonly isConnected: boolean; readonly children: ControlSchemeViewIoTreeNode[]; + readonly initiallyExpanded: boolean; }; export type ControlSchemeViewTreeNode = ControlSchemeViewHubTreeNode diff --git a/src/app/control-schemes/port-command-task-summary/index.ts b/src/app/control-schemes/port-command-task-summary/index.ts new file mode 100644 index 00000000..0e8978aa --- /dev/null +++ b/src/app/control-schemes/port-command-task-summary/index.ts @@ -0,0 +1 @@ +export * from './port-command-task-summary.pipe'; diff --git a/src/app/control-schemes/port-command-task-summary/linear-port-command-task-summary-builder.service.ts b/src/app/control-schemes/port-command-task-summary/linear-port-command-task-summary-builder.service.ts new file mode 100644 index 00000000..7b871e18 --- /dev/null +++ b/src/app/control-schemes/port-command-task-summary/linear-port-command-task-summary-builder.service.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { TranslocoService } from '@ngneat/transloco'; +import { SetLinearSpeedTaskPayload } from '@app/store'; + +@Injectable({ providedIn: 'root' }) +export class LinearPortCommandTaskSummaryBuilderService { + constructor( + private readonly translocoService: TranslocoService + ) { + } + + public build( + payload: SetLinearSpeedTaskPayload + ): Observable { + if (payload.power !== 0 && payload.speed === 0) { + return this.translocoService.selectTranslate('controlScheme.linearBrakeTaskSummary'); + } + return this.translocoService.selectTranslate('controlScheme.linearTaskSummary', payload); + } +} diff --git a/src/app/control-schemes/port-command-task-summary/port-command-task-summary.pipe.ts b/src/app/control-schemes/port-command-task-summary/port-command-task-summary.pipe.ts new file mode 100644 index 00000000..cac262d4 --- /dev/null +++ b/src/app/control-schemes/port-command-task-summary/port-command-task-summary.pipe.ts @@ -0,0 +1,53 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { Observable, filter, switchMap } from 'rxjs'; +import { Store } from '@ngrx/store'; +import { ATTACHED_IO_PROPS_SELECTORS, AttachedIoPropsModel, PortCommandTask, PortCommandTaskType } from '@app/store'; + +import { LinearPortCommandTaskSummaryBuilderService } from './linear-port-command-task-summary-builder.service'; +import { ServoPortCommandTaskSummaryBuilderService } from './servo-port-command-task-summary-builder.service'; +import { SetAnglePortCommandTaskSummaryBuilderService } from './set-angle-port-command-task-summary-builder.service'; +import { StepperPortCommandTaskSummaryBuilderService } from './stepper-port-command-task-summary-builder.service'; + +@Pipe({ + standalone: true, + name: 'portCommandTaskSummary', + pure: true +}) +export class PortCommandTaskSummaryPipe implements PipeTransform { + constructor( + private readonly linearPortCommandTaskSummaryBuilder: LinearPortCommandTaskSummaryBuilderService, + private readonly setAnglePortCommandTaskSummaryBuilder: SetAnglePortCommandTaskSummaryBuilderService, + private readonly servoPortCommandTaskSummaryBuilder: ServoPortCommandTaskSummaryBuilderService, + private readonly stepperPortCommandTaskSummaryBuilder: StepperPortCommandTaskSummaryBuilderService, + private readonly store: Store + ) { + } + + public transform( + portCommandTask: PortCommandTask + ): Observable { + const payload = portCommandTask.payload; + switch (payload.taskType) { + case PortCommandTaskType.SetSpeed: + return this.linearPortCommandTaskSummaryBuilder.build(payload); + case PortCommandTaskType.SetAngle: + return this.store.select(ATTACHED_IO_PROPS_SELECTORS.selectById(portCommandTask)).pipe( + filter((ioProps): ioProps is AttachedIoPropsModel => !!ioProps), + switchMap((ioProps) => this.setAnglePortCommandTaskSummaryBuilder.build( + ioProps, + payload + )) + ); + case PortCommandTaskType.Servo: + return this.store.select(ATTACHED_IO_PROPS_SELECTORS.selectById(portCommandTask)).pipe( + filter((ioProps): ioProps is AttachedIoPropsModel => !!ioProps), + switchMap((ioProps) => this.servoPortCommandTaskSummaryBuilder.build( + ioProps, + payload + )) + ); + case PortCommandTaskType.Stepper: + return this.stepperPortCommandTaskSummaryBuilder.build(payload); + } + } +} diff --git a/src/app/control-schemes/port-command-task-summary/servo-port-command-task-summary-builder.service.ts b/src/app/control-schemes/port-command-task-summary/servo-port-command-task-summary-builder.service.ts new file mode 100644 index 00000000..2aeccc91 --- /dev/null +++ b/src/app/control-schemes/port-command-task-summary/servo-port-command-task-summary-builder.service.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { TranslocoService } from '@ngneat/transloco'; +import { AttachedIoPropsModel, ServoTaskPayload } from '@app/store'; + +@Injectable({ providedIn: 'root' }) +export class ServoPortCommandTaskSummaryBuilderService { + constructor( + private readonly translocoService: TranslocoService + ) { + } + + public build( + attachedIoProps: AttachedIoPropsModel, + payload: ServoTaskPayload + ): Observable { + const angle = (attachedIoProps.motorEncoderOffset ?? 0) + payload.angle; + return this.translocoService.selectTranslate('controlScheme.servoTaskSummary', { angle }); + } +} diff --git a/src/app/control-schemes/port-command-task-summary/set-angle-port-command-task-summary-builder.service.ts b/src/app/control-schemes/port-command-task-summary/set-angle-port-command-task-summary-builder.service.ts new file mode 100644 index 00000000..b5cee322 --- /dev/null +++ b/src/app/control-schemes/port-command-task-summary/set-angle-port-command-task-summary-builder.service.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { TranslocoService } from '@ngneat/transloco'; +import { AttachedIoPropsModel, SetAngleTaskPayload } from '@app/store'; + +@Injectable({ providedIn: 'root' }) +export class SetAnglePortCommandTaskSummaryBuilderService { + constructor( + private readonly translocoService: TranslocoService + ) { + } + + public build( + attachedIoProps: AttachedIoPropsModel, + payload: SetAngleTaskPayload + ): Observable { + const angle = (attachedIoProps.motorEncoderOffset ?? 0) + payload.angle; + return this.translocoService.selectTranslate('controlScheme.setAngleTaskSummary', { angle }); + } +} diff --git a/src/app/control-schemes/port-command-task-summary/stepper-port-command-task-summary-builder.service.ts b/src/app/control-schemes/port-command-task-summary/stepper-port-command-task-summary-builder.service.ts new file mode 100644 index 00000000..12a6cf80 --- /dev/null +++ b/src/app/control-schemes/port-command-task-summary/stepper-port-command-task-summary-builder.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { TranslocoService } from '@ngneat/transloco'; +import { StepperTaskPayload } from '@app/store'; + +@Injectable({ providedIn: 'root' }) +export class StepperPortCommandTaskSummaryBuilderService { + constructor( + private readonly translocoService: TranslocoService + ) { + } + + public build( + payload: StepperTaskPayload + ): Observable { + return this.translocoService.selectTranslate('controlScheme.stepperTaskSummary', payload); + } +} diff --git a/src/app/store/selectors/attached-io-props.selectors.ts b/src/app/store/selectors/attached-io-props.selectors.ts index d2a0f428..5339558d 100644 --- a/src/app/store/selectors/attached-io-props.selectors.ts +++ b/src/app/store/selectors/attached-io-props.selectors.ts @@ -7,6 +7,12 @@ export const ATTACHED_IO_PROPS_SELECTORS = { ATTACHED_IO_PROPS_FEATURE.selectAttachedIoPropsState, ATTACHED_IO_PROPS_ENTITY_ADAPTER.getSelectors().selectEntities, ), + selectById: ( + q: { hubId: string; portId: number } + ) => createSelector( + ATTACHED_IO_PROPS_SELECTORS.selectEntities, + (entities) => entities[hubAttachedIoPropsIdFn(q)] + ), selectMotorEncoderOffset: ( q: { hubId: string; portId: number } ) => createSelector( diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 7ddbf6f1..6b58163a 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -111,19 +111,24 @@ "runningDetailsTotalTasksExecuted": "Total tasks executed:", "runningDetailsLastTenTasksAverageExecutionTime": "Last 10 tasks average execution time (ms):", "setAngleInput": "Set angle", - "setAngleOutputConfigurationAngle": "Target angle", + "setAngleOutputConfigurationAngle": "Target angle (°)", + "setAngleTaskSummary": "Set angle: { angle }°", "linearInputSpeed": "Speed", "linearInputBrake": "Brake", "linearOutputConfigurationTreatAsSwitch": "Treat button as a switch", + "linearTaskSummary": "Speed: { speed }", + "linearBrakeTaskSummary": "Brake", "servoInput": "Servo control", "servoOutputConfigurationCalibrate": "Calibrate", - "servoOutputConfigurationCenterOffset": "Center offset (deg)", - "servoOutputConfigurationAngleRange": "Servo range (deg)", + "servoOutputConfigurationCenterOffset": "Center offset (°)", + "servoOutputConfigurationAngleRange": "Servo range (°)", "servoCalibrationIsInProgress": "Servo calibration is in progress", "servoCalibrationError": "An error occurred during servo calibration", "servoCalibrationCancel": "Cancel", + "servoTaskSummary": "Servo: { angle }°", "stepperInput": "Make step", - "stepperOutputConfigurationDegree": "Angle (deg)", + "stepperOutputConfigurationDegree": "Angle (°)", + "stepperTaskSummary": "Step: { degree }°", "outputSpeedControlTitle": "Speed", "outputPowerControlTitle": "Power", "outputInvertControlTitle": "Invert",