Merge branch 'master' into feature/time-series-chart

This commit is contained in:
Igor Kulikov 2024-02-29 18:00:35 +02:00
commit ddb0c0fd2f
18 changed files with 156 additions and 33 deletions

View File

@ -159,6 +159,7 @@ export interface IStateController {
dashboardCtrl: IDashboardController;
getStateParams(): StateParams;
stateChanged(): Observable<string>;
stateId(): Observable<string>;
getStateParamsByStateId(stateId: string): StateParams;
openState(id: string, params?: StateParams, openRightLayout?: boolean): void;
updateState(id?: string, params?: StateParams, openRightLayout?: boolean): void;

View File

@ -270,6 +270,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
state: null,
stateController: null,
stateChanged: null,
stateId: null,
aliasController: null,
runChangeDetection: this.runChangeDetection.bind(this)
};

View File

@ -39,6 +39,7 @@ export interface DashboardContext {
aliasController: IAliasController;
stateController: IStateController;
stateChanged: Observable<string>;
stateId: Observable<string>;
runChangeDetection: () => void;
}

View File

@ -240,6 +240,7 @@ export class DefaultStateControllerComponent extends StateControllerComponent im
private gotoState(stateId: string, update: boolean, openRightLayout?: boolean) {
if (this.dashboardCtrl.dashboardCtx.state !== stateId) {
this.dashboardCtrl.openDashboardState(stateId, openRightLayout);
this.stateIdSubject.next(stateId);
if (this.syncStateWithQueryParam && stateId && this.statesValue[stateId]) {
this.mobileService.handleDashboardStateName(this.getStateName(stateId, this.statesValue[stateId]));
}

View File

@ -280,6 +280,9 @@ export class EntityStateControllerComponent extends StateControllerComponent imp
private gotoState(stateId: string, update: boolean, openRightLayout?: boolean) {
const isStateIdChanged = this.dashboardCtrl.dashboardCtx.state !== stateId;
this.dashboardCtrl.openDashboardState(stateId, openRightLayout);
if (isStateIdChanged) {
this.stateIdSubject.next(stateId);
}
if (this.syncStateWithQueryParam) {
this.mobileService.handleDashboardStateName(this.getStateName(this.stateObject.length - 1));
}

View File

@ -17,7 +17,7 @@
import { IStateControllerComponent, StateControllerState } from '@home/components/dashboard-page/states/state-controller.models';
import { IDashboardController } from '../dashboard-page.models';
import { DashboardState } from '@app/shared/models/dashboard.models';
import { Observable, Subject, Subscription } from 'rxjs';
import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs';
import { NgZone, OnDestroy, OnInit, Directive } from '@angular/core';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { StatesControllerService } from '@home/components/dashboard-page/states/states-controller.service';
@ -28,6 +28,7 @@ import { StateObject, StateParams } from '@app/core/api/widget-api.models';
export abstract class StateControllerComponent implements IStateControllerComponent, OnInit, OnDestroy {
private stateChangedSubject = new Subject<string>();
protected stateIdSubject = new Subject<string>();
stateObject: StateControllerState = [];
dashboardCtrl: IDashboardController;
preservedState: any;
@ -126,6 +127,7 @@ export abstract class StateControllerComponent implements IStateControllerCompon
subscription.unsubscribe();
});
this.rxSubscriptions.length = 0;
this.stateIdSubject.complete();
this.stateChangedSubject.complete();
}
@ -152,6 +154,10 @@ export abstract class StateControllerComponent implements IStateControllerCompon
return this.stateChangedSubject.asObservable();
}
public stateId(): Observable<string> {
return this.stateIdSubject.asObservable();
}
public openRightLayout(): void {
this.dashboardCtrl.openRightLayout();
}

View File

@ -28,7 +28,7 @@ import { DashboardState } from '@shared/models/dashboard.models';
import { IDashboardController } from '@home/components/dashboard-page/dashboard-page.models';
import { StatesControllerService } from '@home/components/dashboard-page/states/states-controller.service';
import { IStateControllerComponent } from '@home/components/dashboard-page/states/state-controller.models';
import { Subject } from 'rxjs';
import { BehaviorSubject, Subject } from 'rxjs';
@Directive({
// eslint-disable-next-line @angular-eslint/directive-selector
@ -64,18 +64,21 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges {
stateControllerComponent: IStateControllerComponent;
private stateChangedSubject = new Subject<string>();
private stateIdSubject: BehaviorSubject<string>;
constructor(private viewContainerRef: ViewContainerRef,
private statesControllerService: StatesControllerService) {
}
ngOnInit(): void {
this.stateIdSubject = new BehaviorSubject<string>(this.dashboardCtrl.dashboardCtx.state);
this.init();
}
ngOnDestroy(): void {
this.destroy();
this.stateChangedSubject.complete();
this.stateIdSubject.complete();
}
ngOnChanges(changes: SimpleChanges): void {
@ -128,9 +131,13 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges {
this.stateControllerComponent = this.stateControllerComponentRef.instance;
this.dashboardCtrl.dashboardCtx.stateController = this.stateControllerComponent;
this.dashboardCtrl.dashboardCtx.stateChanged = this.stateChangedSubject.asObservable();
this.dashboardCtrl.dashboardCtx.stateId = this.stateIdSubject.asObservable();
this.stateControllerComponent.stateChanged().subscribe((state) => {
this.stateChangedSubject.next(state);
});
this.stateControllerComponent.stateId().subscribe((stateId) => {
this.stateIdSubject.next(stateId);
});
this.stateControllerComponent.preservedState = preservedState;
this.stateControllerComponent.dashboardCtrl = this.dashboardCtrl;
this.stateControllerComponent.stateControllerInstanceId = stateControllerInstanceId;

View File

@ -23,8 +23,8 @@ import {
telemetryTypeTranslationsShort
} from '@shared/models/telemetry/telemetry.models';
import { WidgetContext } from '@home/models/widget-component.models';
import { BehaviorSubject, forkJoin, Observable, Observer, of, throwError } from 'rxjs';
import { catchError, delay, map, share, take } from 'rxjs/operators';
import { BehaviorSubject, forkJoin, Observable, Observer, of, Subscription, throwError } from 'rxjs';
import { catchError, delay, map, share, take, tap } from 'rxjs/operators';
import { UtilsService } from '@core/services/utils.service';
import { AfterViewInit, ChangeDetectorRef, Directive, Input, OnDestroy, OnInit, TemplateRef } from '@angular/core';
import {
@ -186,8 +186,14 @@ export class DataToValueConverter<V> {
case DataToValueType.FUNCTION:
result = data;
try {
result = this.dataToValueFunction(!!data ? JSON.parse(data) : data);
} catch (e) {}
let input = data;
if (!!data) {
try {
input = JSON.parse(data);
} catch (_e) {}
}
result = this.dataToValueFunction(input);
} catch (_e) {}
break;
case DataToValueType.NONE:
result = data;
@ -233,12 +239,16 @@ export abstract class ValueGetter<V> extends ValueAction {
return new AttributeValueGetter<V>(ctx, settings, valueType, valueObserver);
case GetValueAction.GET_TIME_SERIES:
return new TimeSeriesValueGetter<V>(ctx, settings, valueType, valueObserver);
case GetValueAction.GET_DASHBOARD_STATE:
return new DashboardStateGetter<V>(ctx, settings, valueType, valueObserver);
}
}
private readonly isSimulated: boolean;
private readonly dataConverter: DataToValueConverter<V>;
private getValueSubscription: Subscription;
protected constructor(protected ctx: WidgetContext,
protected settings: GetValueSettings<V>,
protected valueType: ValueType,
@ -263,7 +273,10 @@ export abstract class ValueGetter<V> extends ValueAction {
throw this.handleError(err);
})
);
valueObservable.subscribe({
if (this.getValueSubscription) {
this.getValueSubscription.unsubscribe();
}
this.getValueSubscription = valueObservable.subscribe({
next: (value) => {
this.valueObserver.next(value);
},
@ -277,6 +290,9 @@ export abstract class ValueGetter<V> extends ValueAction {
}
destroy() {
if (this.getValueSubscription) {
this.getValueSubscription.unsubscribe();
}
super.destroy();
}
@ -504,6 +520,19 @@ export class TimeSeriesValueGetter<V> extends TelemetryValueGetter<V, TelemetryV
}
}
export class DashboardStateGetter<V> extends ValueGetter<V> {
constructor(protected ctx: WidgetContext,
protected settings: GetValueSettings<V>,
protected valueType: ValueType,
protected valueObserver: Partial<Observer<V>>) {
super(ctx, settings, valueType, valueObserver);
}
protected doGetValue(): Observable<string> {
return this.ctx.stateController.dashboardCtrl.dashboardCtx.stateId;
}
}
export class ExecuteRpcValueSetter<V> extends ValueSetter<V> {
private readonly executeRpcSettings: RpcSettings;

View File

@ -41,7 +41,7 @@ export const commandButtonDefaultSettings: CommandButtonWidgetSettings = {
},
setAttribute: {
key: 'state',
scope: AttributeScope.SHARED_SCOPE
scope: AttributeScope.SERVER_SCOPE
},
putTimeSeries: {
key: 'state'

View File

@ -92,7 +92,7 @@ export const toggleButtonDefaultSettings: ToggleButtonWidgetSettings = {
},
setAttribute: {
key: 'state',
scope: AttributeScope.SHARED_SCOPE
scope: AttributeScope.SERVER_SCOPE
},
putTimeSeries: {
key: 'state'
@ -113,7 +113,7 @@ export const toggleButtonDefaultSettings: ToggleButtonWidgetSettings = {
},
setAttribute: {
key: 'state',
scope: AttributeScope.SHARED_SCOPE
scope: AttributeScope.SERVER_SCOPE
},
putTimeSeries: {
key: 'state'

View File

@ -126,7 +126,7 @@ export const powerButtonDefaultSettings: PowerButtonWidgetSettings = {
},
setAttribute: {
key: 'state',
scope: AttributeScope.SHARED_SCOPE
scope: AttributeScope.SERVER_SCOPE
},
putTimeSeries: {
key: 'state'
@ -147,7 +147,7 @@ export const powerButtonDefaultSettings: PowerButtonWidgetSettings = {
},
setAttribute: {
key: 'state',
scope: AttributeScope.SHARED_SCOPE
scope: AttributeScope.SERVER_SCOPE
},
putTimeSeries: {
key: 'state'

View File

@ -131,7 +131,7 @@ export const singleSwitchDefaultSettings: SingleSwitchWidgetSettings = {
},
setAttribute: {
key: 'state',
scope: AttributeScope.SHARED_SCOPE
scope: AttributeScope.SERVER_SCOPE
},
putTimeSeries: {
key: 'state'
@ -152,7 +152,7 @@ export const singleSwitchDefaultSettings: SingleSwitchWidgetSettings = {
},
setAttribute: {
key: 'state',
scope: AttributeScope.SHARED_SCOPE
scope: AttributeScope.SERVER_SCOPE
},
putTimeSeries: {
key: 'state'

View File

@ -132,7 +132,7 @@ export const sliderWidgetDefaultSettings: SliderWidgetSettings = {
},
setAttribute: {
key: 'state',
scope: AttributeScope.SHARED_SCOPE
scope: AttributeScope.SERVER_SCOPE
},
putTimeSeries: {
key: 'state'

View File

@ -125,6 +125,8 @@
<div class="fixed-title-width fixed-title-height">{{ 'widgets.value-action.state-when-result-is' | translate:{state: (stateLabel | translate)} }}</div>
<tb-value-input
fxFlex
[valueType]="(getValueSettingsFormGroup.get('action').value === getValueAction.GET_DASHBOARD_STATE &&
getValueSettingsFormGroup.get('dataToValue').get('type').value === dataToValueType.NONE) ? ValueType.STRING : null"
layout="column"
formControlName="compareToValue">
</tb-value-input>

View File

@ -28,7 +28,7 @@ import {
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { MatButton } from '@angular/material/button';
import { TbPopoverService } from '@shared/components/popover.service';
import { GetValueAction, GetValueSettings } from '@shared/models/action-widget-settings.models';
import { DataToValueType, GetValueAction, GetValueSettings } from '@shared/models/action-widget-settings.models';
import { TranslateService } from '@ngx-translate/core';
import { ValueType } from '@shared/models/constants';
import {
@ -170,6 +170,18 @@ export class GetValueActionSettingsComponent implements OnInit, ControlValueAcce
case GetValueAction.GET_TIME_SERIES:
this.displayValue = this.translate.instant('widgets.value-action.get-time-series-text', {key: this.modelValue.getTimeSeries.key});
break;
case GetValueAction.GET_DASHBOARD_STATE:
if (this.valueType === ValueType.BOOLEAN) {
const state = this.modelValue.dataToValue?.compareToValue;
if (this.modelValue.dataToValue?.type === DataToValueType.FUNCTION) {
this.displayValue = this.translate.instant('widgets.value-action.when-dashboard-state-function-is-text', {state});
} else {
this.displayValue = this.translate.instant('widgets.value-action.when-dashboard-state-is-text', {state});
}
} else {
this.displayValue = this.translate.instant('widgets.value-action.get-dashboard-state-text');
}
break;
}
this.cd.markForCheck();
}

View File

@ -14,7 +14,17 @@
/// limitations under the License.
///
import { Component, forwardRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import {
ChangeDetectorRef,
Component,
forwardRef,
Input,
OnChanges,
OnDestroy,
OnInit,
SimpleChanges,
ViewChild
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgForm } from '@angular/forms';
import { resolveBreakpoint, ValueType, valueTypesMap } from '@shared/models/constants';
import { isObject } from '@core/utils';
@ -45,7 +55,7 @@ export interface ValueInputLayout {
}
]
})
export class ValueInputComponent implements OnInit, OnDestroy, ControlValueAccessor {
export class ValueInputComponent implements OnInit, OnDestroy, OnChanges, ControlValueAccessor {
@Input()
disabled: boolean;
@ -85,6 +95,7 @@ export class ValueInputComponent implements OnInit, OnDestroy, ControlValueAcces
constructor(
private breakpointObserver: BreakpointObserver,
private cd: ChangeDetectorRef,
public dialog: MatDialog,
) {
@ -104,6 +115,23 @@ export class ValueInputComponent implements OnInit, OnDestroy, ControlValueAcces
}
}
ngOnChanges(changes: SimpleChanges): void {
for (const propName of Object.keys(changes)) {
const change = changes[propName];
if (!change.firstChange) {
if (propName === 'valueType') {
this.showValueType = !this.valueType;
if (this.valueType) {
this.updateModelToValueType();
} else {
this.detectValueType();
}
this.cd.markForCheck();
}
}
}
}
ngOnDestroy() {
this._subscription.unsubscribe();
}
@ -144,19 +172,12 @@ export class ValueInputComponent implements OnInit, OnDestroy, ControlValueAcces
writeValue(value: any): void {
this.modelValue = value;
if (this.showValueType) {
if (this.modelValue === true || this.modelValue === false) {
this.valueType = ValueType.BOOLEAN;
} else if (typeof this.modelValue === 'number') {
if (this.modelValue.toString().indexOf('.') === -1) {
this.valueType = ValueType.INTEGER;
this.detectValueType();
} else {
this.valueType = ValueType.DOUBLE;
}
} else if (isObject(this.modelValue)) {
this.valueType = ValueType.JSON;
} else {
this.valueType = ValueType.STRING;
}
setTimeout(() => {
this.updateModelToValueType();
this.cd.markForCheck();
}, 0);
}
}
@ -185,6 +206,39 @@ export class ValueInputComponent implements OnInit, OnDestroy, ControlValueAcces
this.updateView();
}
private detectValueType() {
if (this.modelValue === true || this.modelValue === false) {
this.valueType = ValueType.BOOLEAN;
} else if (typeof this.modelValue === 'number') {
if (this.modelValue.toString().indexOf('.') === -1) {
this.valueType = ValueType.INTEGER;
} else {
this.valueType = ValueType.DOUBLE;
}
} else if (isObject(this.modelValue)) {
this.valueType = ValueType.JSON;
} else {
this.valueType = ValueType.STRING;
}
}
private updateModelToValueType() {
if (this.valueType === ValueType.BOOLEAN && typeof this.modelValue !== 'boolean') {
this.modelValue = !!this.modelValue;
this.updateView();
} else if (this.valueType === ValueType.STRING && typeof this.modelValue !== 'string') {
this.modelValue = null;
this.updateView();
} else if ([ValueType.DOUBLE, ValueType.INTEGER].includes(this.valueType) && typeof this.modelValue !== 'number') {
this.modelValue = null;
this.updateView();
} else if (this.valueType === ValueType.JSON && typeof this.modelValue !== 'object') {
this.modelValue = {};
this.inputForm.form.get('value').patchValue({});
this.updateView();
}
}
private _computeLayout(): Layout {
if (typeof this.layout !== 'object') {
return this.layout;

View File

@ -21,7 +21,8 @@ export enum GetValueAction {
DO_NOTHING = 'DO_NOTHING',
EXECUTE_RPC = 'EXECUTE_RPC',
GET_ATTRIBUTE = 'GET_ATTRIBUTE',
GET_TIME_SERIES = 'GET_TIME_SERIES'
GET_TIME_SERIES = 'GET_TIME_SERIES',
GET_DASHBOARD_STATE = 'GET_DASHBOARD_STATE'
}
export const getValueActions = Object.keys(GetValueAction) as GetValueAction[];
@ -39,7 +40,8 @@ export const getValueActionTranslations = new Map<GetValueAction, string>(
[GetValueAction.DO_NOTHING, 'widgets.value-action.do-nothing'],
[GetValueAction.EXECUTE_RPC, 'widgets.value-action.execute-rpc'],
[GetValueAction.GET_ATTRIBUTE, 'widgets.value-action.get-attribute'],
[GetValueAction.GET_TIME_SERIES, 'widgets.value-action.get-time-series']
[GetValueAction.GET_TIME_SERIES, 'widgets.value-action.get-time-series'],
[GetValueAction.GET_DASHBOARD_STATE, 'widgets.value-action.get-dashboard-state']
]
);

View File

@ -6638,10 +6638,14 @@
"get-attribute": "Get attribute",
"set-attribute": "Set attribute",
"get-time-series": "Get time-series",
"get-dashboard-state": "Get dashboard state",
"add-time-series": "Add time-series",
"execute-rpc-text": "Execute RPC method '{{methodName}}'",
"get-attribute-text": "Use attribute '{{key}}'",
"get-time-series-text": "Use time-series '{{key}}'",
"get-dashboard-state-text": "Use dashboard state",
"when-dashboard-state-is-text": "When dashboard state is '{{state}}'",
"when-dashboard-state-function-is-text": "When f(dashboard state) is '{{state}}'",
"set-attribute-to-value-text": "Set '{{key}}' attribute to: {{value}}",
"add-time-series-value-text": "Add '{{key}}' time-series value: {{value}}",
"set-attribute-text": "Set '{{key}}' attribute",