diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts index 050637468d..b1eb79f2bb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/action/action-widget.models.ts @@ -24,7 +24,7 @@ import { } from '@shared/models/telemetry/telemetry.models'; import { WidgetContext } from '@home/models/widget-component.models'; import { BehaviorSubject, forkJoin, Observable, Observer, of, Subscription, throwError } from 'rxjs'; -import { catchError, delay, map, share, take, tap } from 'rxjs/operators'; +import { catchError, delay, map, share, take } from 'rxjs/operators'; import { UtilsService } from '@core/services/utils.service'; import { AfterViewInit, ChangeDetectorRef, Directive, Input, OnDestroy, OnInit, TemplateRef } from '@angular/core'; import { @@ -45,6 +45,7 @@ import { import { ValueType } from '@shared/models/constants'; import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models'; import { EntityId } from '@shared/models/id/entity-id'; +import { isDefinedAndNotNull } from '@core/utils'; @Directive() // eslint-disable-next-line @angular-eslint/directive-class-suffix @@ -117,14 +118,16 @@ export abstract class BasicActionWidgetComponent implements OnInit, OnDestroy, A } } }; - const valueGetter = ValueGetter.fromSettings(this.ctx, getValueSettings, valueType, observer); + const simulated = this.ctx.utilsService.widgetEditMode || this.ctx.isPreview; + const valueGetter = ValueGetter.fromSettings(this.ctx, getValueSettings, valueType, observer, simulated); this.valueGetters.push(valueGetter); this.valueActions.push(valueGetter); return valueGetter; } protected createValueSetter(setValueSettings: SetValueSettings): ValueSetter { - const valueSetter = ValueSetter.fromSettings(this.ctx, setValueSettings); + const simulated = this.ctx.utilsService.widgetEditMode || this.ctx.isPreview; + const valueSetter = ValueSetter.fromSettings(this.ctx, setValueSettings, simulated); this.valueActions.push(valueSetter); return valueSetter; } @@ -229,22 +232,22 @@ export abstract class ValueGetter extends ValueAction { static fromSettings(ctx: WidgetContext, settings: GetValueSettings, valueType: ValueType, - valueObserver: Partial>): ValueGetter { + valueObserver: Partial>, + simulated: boolean): ValueGetter { switch (settings.action) { case GetValueAction.DO_NOTHING: - return new DefaultValueGetter(ctx, settings, valueType, valueObserver); + return new DefaultValueGetter(ctx, settings, valueType, valueObserver, simulated); case GetValueAction.EXECUTE_RPC: - return new ExecuteRpcValueGetter(ctx, settings, valueType, valueObserver); + return new ExecuteRpcValueGetter(ctx, settings, valueType, valueObserver, simulated); case GetValueAction.GET_ATTRIBUTE: - return new AttributeValueGetter(ctx, settings, valueType, valueObserver); + return new AttributeValueGetter(ctx, settings, valueType, valueObserver, simulated); case GetValueAction.GET_TIME_SERIES: - return new TimeSeriesValueGetter(ctx, settings, valueType, valueObserver); + return new TimeSeriesValueGetter(ctx, settings, valueType, valueObserver, simulated); case GetValueAction.GET_DASHBOARD_STATE: - return new DashboardStateGetter(ctx, settings, valueType, valueObserver); + return new DashboardStateGetter(ctx, settings, valueType, valueObserver, simulated); } } - private readonly isSimulated: boolean; private readonly dataConverter: DataToValueConverter; private getValueSubscription: Subscription; @@ -252,16 +255,16 @@ export abstract class ValueGetter extends ValueAction { protected constructor(protected ctx: WidgetContext, protected settings: GetValueSettings, protected valueType: ValueType, - protected valueObserver: Partial>) { + protected valueObserver: Partial>, + protected simulated: boolean) { super(ctx, settings); - this.isSimulated = this.ctx.$injector.get(UtilsService).widgetEditMode; if (this.settings.action !== GetValueAction.DO_NOTHING) { this.dataConverter = new DataToValueConverter(settings.dataToValue, valueType); } } getValue(): Observable { - const valueObservable: Observable = (this.isSimulated ? of(null).pipe(delay(500)) : this.doGetValue()).pipe( + const valueObservable: Observable = this.doGetValue().pipe( map((data) => { if (this.dataConverter) { return this.dataConverter.dataToValue(data); @@ -346,29 +349,29 @@ export class ValueToDataConverter { export abstract class ValueSetter extends ValueAction { static fromSettings(ctx: WidgetContext, - settings: SetValueSettings): ValueSetter { + settings: SetValueSettings, + simulated: boolean): ValueSetter { switch (settings.action) { case SetValueAction.EXECUTE_RPC: - return new ExecuteRpcValueSetter(ctx, settings); + return new ExecuteRpcValueSetter(ctx, settings, simulated); case SetValueAction.SET_ATTRIBUTE: - return new AttributeValueSetter(ctx, settings); + return new AttributeValueSetter(ctx, settings, simulated); case SetValueAction.ADD_TIME_SERIES: - return new TimeSeriesValueSetter(ctx, settings); + return new TimeSeriesValueSetter(ctx, settings, simulated); } } - private readonly isSimulated: boolean; private readonly valueToDataConverter: ValueToDataConverter; protected constructor(protected ctx: WidgetContext, - protected settings: SetValueSettings) { + protected settings: SetValueSettings, + protected simulated: boolean) { super(ctx, settings); - this.isSimulated = this.ctx.$injector.get(UtilsService).widgetEditMode; this.valueToDataConverter = new ValueToDataConverter(settings.valueToData); } setValue(value: V): Observable { - if (this.isSimulated) { + if (this.simulated) { return of(null).pipe(delay(500)); } else { return this.doSetValue(this.valueToDataConverter.valueToData(value)).pipe( @@ -389,8 +392,9 @@ export class DefaultValueGetter extends ValueGetter { constructor(protected ctx: WidgetContext, protected settings: GetValueSettings, protected valueType: ValueType, - protected valueObserver: Partial>) { - super(ctx, settings, valueType, valueObserver); + protected valueObserver: Partial>, + protected simulated: boolean) { + super(ctx, settings, valueType, valueObserver, simulated); this.defaultValue = settings.defaultValue; } @@ -406,20 +410,26 @@ export class ExecuteRpcValueGetter extends ValueGetter { constructor(protected ctx: WidgetContext, protected settings: GetValueSettings, protected valueType: ValueType, - protected valueObserver: Partial>) { - super(ctx, settings, valueType, valueObserver); + protected valueObserver: Partial>, + protected simulated: boolean) { + super(ctx, settings, valueType, valueObserver, simulated); this.executeRpcSettings = settings.executeRpc; } protected doGetValue(): Observable { - return this.ctx.controlApi.sendTwoWayCommand(this.executeRpcSettings.method, null, - this.executeRpcSettings.requestTimeout, - this.executeRpcSettings.requestPersistent, - this.executeRpcSettings.persistentPollingInterval).pipe( + if (this.simulated) { + const defaultValue = isDefinedAndNotNull(this.settings.defaultValue) ? this.settings.defaultValue : null; + return of(defaultValue).pipe(delay(500)); + } else { + return this.ctx.controlApi.sendTwoWayCommand(this.executeRpcSettings.method, null, + this.executeRpcSettings.requestTimeout, + this.executeRpcSettings.requestPersistent, + this.executeRpcSettings.persistentPollingInterval).pipe( catchError((err) => { throw handleRpcError(this.ctx, err); }) - ); + ); + } } } @@ -431,24 +441,30 @@ export abstract class TelemetryValueGetter protected constructor(protected ctx: WidgetContext, protected settings: GetValueSettings, protected valueType: ValueType, - protected valueObserver: Partial>) { - super(ctx, settings, valueType, valueObserver); + protected valueObserver: Partial>, + protected simulated: boolean) { + super(ctx, settings, valueType, valueObserver, simulated); const entityInfo = this.ctx.defaultSubscription.getFirstEntityInfo(); this.targetEntityId = entityInfo?.entityId; } protected doGetValue(): Observable { - if (!this.targetEntityId && !this.ctx.defaultSubscription.rpcEnabled) { - return throwError(() => new Error(this.ctx.translate.instant('widgets.value-action.error.target-entity-is-not-set'))); - } - if (this.targetEntityId) { - const err = validateAttributeScope(this.ctx, this.targetEntityId, this.scope()); - if (err) { - return throwError(() => err); - } - return this.subscribeForTelemetryValue(); + if (this.simulated) { + const defaultValue = isDefinedAndNotNull(this.settings.defaultValue) ? this.settings.defaultValue : null; + return of(defaultValue).pipe(delay(100)); } else { - return of(null); + if (!this.targetEntityId && !this.ctx.defaultSubscription.rpcEnabled) { + return throwError(() => new Error(this.ctx.translate.instant('widgets.value-action.error.target-entity-is-not-set'))); + } + if (this.targetEntityId) { + const err = validateAttributeScope(this.ctx, this.targetEntityId, this.scope()); + if (err) { + return throwError(() => err); + } + return this.subscribeForTelemetryValue(); + } else { + return of(null); + } } } @@ -492,8 +508,9 @@ export class AttributeValueGetter extends TelemetryValueGetter, protected valueType: ValueType, - protected valueObserver: Partial>) { - super(ctx, settings, valueType, valueObserver); + protected valueObserver: Partial>, + protected simulated: boolean) { + super(ctx, settings, valueType, valueObserver, simulated); } protected getTelemetryValueSettings(): GetAttributeValueSettings { @@ -511,8 +528,9 @@ export class TimeSeriesValueGetter extends TelemetryValueGetter, protected valueType: ValueType, - protected valueObserver: Partial>) { - super(ctx, settings, valueType, valueObserver); + protected valueObserver: Partial>, + protected simulated: boolean) { + super(ctx, settings, valueType, valueObserver, simulated); } protected getTelemetryValueSettings(): TelemetryValueSettings { @@ -524,12 +542,17 @@ export class DashboardStateGetter extends ValueGetter { constructor(protected ctx: WidgetContext, protected settings: GetValueSettings, protected valueType: ValueType, - protected valueObserver: Partial>) { - super(ctx, settings, valueType, valueObserver); + protected valueObserver: Partial>, + protected simulated: boolean) { + super(ctx, settings, valueType, valueObserver, simulated); } protected doGetValue(): Observable { - return this.ctx.stateController.dashboardCtrl.dashboardCtx.stateId; + if (this.simulated) { + return of('default'); + } else { + return this.ctx.stateController.dashboardCtrl.dashboardCtx.stateId; + } } } @@ -538,8 +561,9 @@ export class ExecuteRpcValueSetter extends ValueSetter { private readonly executeRpcSettings: RpcSettings; constructor(protected ctx: WidgetContext, - protected settings: SetValueSettings) { - super(ctx, settings); + protected settings: SetValueSettings, + protected simulated: boolean) { + super(ctx, settings, simulated); this.executeRpcSettings = settings.executeRpc; } @@ -560,8 +584,9 @@ export abstract class TelemetryValueSetter extends ValueSetter { protected targetEntityId: EntityId; protected constructor(protected ctx: WidgetContext, - protected settings: SetValueSettings) { - super(ctx, settings); + protected settings: SetValueSettings, + protected simulated: boolean) { + super(ctx, settings, simulated); const entityInfo = this.ctx.defaultSubscription.getFirstEntityInfo(); this.targetEntityId = entityInfo?.entityId; } @@ -594,8 +619,9 @@ export class AttributeValueSetter extends TelemetryValueSetter { private readonly setAttributeValueSettings: SetAttributeValueSettings; constructor(protected ctx: WidgetContext, - protected settings: SetValueSettings) { - super(ctx, settings); + protected settings: SetValueSettings, + protected simulated: boolean) { + super(ctx, settings, simulated); this.setAttributeValueSettings = settings.setAttribute; } @@ -616,8 +642,9 @@ export class TimeSeriesValueSetter extends TelemetryValueSetter { private readonly putTimeSeriesValueSettings: TelemetryValueSettings; constructor(protected ctx: WidgetContext, - protected settings: SetValueSettings) { - super(ctx, settings); + protected settings: SetValueSettings, + protected simulated: boolean) { + super(ctx, settings, simulated); this.putTimeSeriesValueSettings = settings.putTimeSeries; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.html index 3d550ae02e..1e9bd9616d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.html @@ -17,8 +17,8 @@ -->
{{ metadata?.title | customTranslate }}
-
-
iot-svg.behavior
+
+
scada.behavior.behavior
{{ behaviour.name | customTranslate }}
-
+
{{ propertyRow.label | customTranslate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.ts index 613e8b26f9..58d4346e53 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.ts @@ -39,7 +39,7 @@ import { HttpClient } from '@angular/common/http'; import { ValueType } from '@shared/models/constants'; import { IAliasController } from '@core/api/widget-api.models'; import { TargetDevice, widgetType } from '@shared/models/widget.models'; -import { isDefinedAndNotNull } from '@core/utils'; +import { isDefinedAndNotNull, mergeDeep } from '@core/utils'; import { IotSvgPropertyRow, toPropertyRows @@ -113,7 +113,10 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control } ngOnInit(): void { - this.iotSvgObjectSettingsFormGroup = this.fb.group({}); + this.iotSvgObjectSettingsFormGroup = this.fb.group({ + behavior: this.fb.group({}), + properties: this.fb.group({}) + }); this.iotSvgObjectSettingsFormGroup.valueChanges.subscribe(() => { this.updateModel(); }); @@ -149,7 +152,7 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control } writeValue(value: IotSvgObjectSettings): void { - this.modelValue = value || {}; + this.modelValue = value || { behavior: {}, properties: {} }; this.setupValue(); } @@ -181,11 +184,16 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control (svgContent) => { this.metadata = parseIotSvgMetadataFromContent(svgContent); this.propertyRows = toPropertyRows(this.metadata.properties); - for (const control of Object.keys(this.iotSvgObjectSettingsFormGroup.controls)) { - this.iotSvgObjectSettingsFormGroup.removeControl(control, {emitEvent: false}); + const behaviorFormGroup = this.iotSvgObjectSettingsFormGroup.get('behavior') as UntypedFormGroup; + for (const control of Object.keys(behaviorFormGroup.controls)) { + behaviorFormGroup.removeControl(control, {emitEvent: false}); + } + const propertiesFormGroup = this.iotSvgObjectSettingsFormGroup.get('properties') as UntypedFormGroup; + for (const control of Object.keys(propertiesFormGroup.controls)) { + propertiesFormGroup.removeControl(control, {emitEvent: false}); } for (const behaviour of this.metadata.behavior) { - this.iotSvgObjectSettingsFormGroup.addControl(behaviour.id, this.fb.control(null, []), {emitEvent: false}); + behaviorFormGroup.addControl(behaviour.id, this.fb.control(null, []), {emitEvent: false}); } for (const property of this.metadata.properties) { if (property.disableOnProperty) { @@ -205,12 +213,12 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control validators.push(Validators.max(property.max)); } } - this.iotSvgObjectSettingsFormGroup.addControl(property.id, this.fb.control(null, validators), {emitEvent: false}); + propertiesFormGroup.addControl(property.id, this.fb.control(null, validators), {emitEvent: false}); } if (this.validatorTriggers.length) { const observables: Observable[] = []; for (const trigger of this.validatorTriggers) { - observables.push(this.iotSvgObjectSettingsFormGroup.get(trigger).valueChanges); + observables.push(propertiesFormGroup.get(trigger).valueChanges); } this.validatorSubscription = merge(...observables).subscribe(() => { this.updateValidators(); @@ -223,11 +231,12 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control } private updateValidators() { + const propertiesFormGroup = this.iotSvgObjectSettingsFormGroup.get('properties') as UntypedFormGroup; for (const trigger of this.validatorTriggers) { - const value: boolean = this.iotSvgObjectSettingsFormGroup.get(trigger).value; + const value: boolean = propertiesFormGroup.get(trigger).value; this.metadata.properties.filter(p => p.disableOnProperty === trigger).forEach( (p) => { - const control = this.iotSvgObjectSettingsFormGroup.get(p.id); + const control = propertiesFormGroup.get(p.id); if (value) { control.enable({emitEvent: false}); } else { @@ -241,7 +250,7 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control private setupValue() { if (this.metadata) { const defaults = defaultIotSvgObjectSettings(this.metadata); - this.modelValue = {...defaults, ...this.modelValue}; + this.modelValue = mergeDeep(defaults, this.modelValue); this.iotSvgObjectSettingsFormGroup.patchValue( this.modelValue, {emitEvent: false} ); @@ -253,6 +262,4 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control this.modelValue = this.iotSvgObjectSettingsFormGroup.getRawValue(); this.propagateChange(this.modelValue); } - - protected readonly ValueType = ValueType; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.ts index 2dadb5b48d..fc443bcd39 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.ts @@ -39,6 +39,7 @@ import { Observable, of } from 'rxjs'; import { backgroundStyle, ComponentStyle, overlayStyle } from '@shared/models/widget-settings.models'; import { ImageService } from '@core/http/image.service'; import { WidgetComponent } from '@home/components/widget/widget.component'; +import { isDefinedAndNotNull, mergeDeep } from '@core/utils'; @Component({ selector: 'tb-iot-svg-widget', @@ -74,7 +75,7 @@ export class IotSvgWidgetComponent extends ngOnInit(): void { super.ngOnInit(); - this.settings = {...iotSvgWidgetDefaultSettings, ...this.ctx.settings}; + this.settings = mergeDeep({} as IotSvgWidgetSettings, iotSvgWidgetDefaultSettings, this.ctx.settings || {}); this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); this.overlayStyle = overlayStyle(this.settings.background.overlay); @@ -96,10 +97,15 @@ export class IotSvgWidgetComponent extends } private initObject(svgContent: string) { - this.iotSvgObject = new IotSvgObject(this.ctx, svgContent, this.settings.iotSvgObject); + const simulated = this.ctx.utilsService.widgetEditMode || + this.ctx.isPreview || (isDefinedAndNotNull(this.settings.simulated) ? this.settings.simulated : false); + this.iotSvgObject = new IotSvgObject(this.ctx, svgContent, this.settings.iotSvgObject, simulated); this.iotSvgObject.onError((error) => { this.ctx.showErrorToast(error, 'bottom', 'center', this.ctx.toastTargetId, true); }); + this.iotSvgObject.onMessage((message) => { + this.ctx.showSuccessToast(message, 3000, 'bottom', 'center', this.ctx.toastTargetId, true); + }); this.iotSvgObject.init(); if (this.iotSvgShape) { this.iotSvgObject.addTo(this.iotSvgShape.nativeElement); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.models.ts index b3223c5b79..6d25e94c71 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.models.ts @@ -21,13 +21,17 @@ export interface IotSvgWidgetSettings { iotSvg?: string; iotSvgUrl?: string; iotSvgContent?: string; + simulated?: boolean; iotSvgObject: IotSvgObjectSettings; background: BackgroundSettings; } export const iotSvgWidgetDefaultSettings: IotSvgWidgetSettings = { iotSvg: '/assets/widget/svg/drawing.svg', - iotSvgObject: {}, + iotSvgObject: { + behavior: {}, + properties: {} + }, background: { type: BackgroundType.color, color: '#fff', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg.models.ts index b037b947ed..1302afefd1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg.models.ts @@ -33,14 +33,14 @@ import { mergeDeep, parseFunction } from '@core/utils'; -import { BehaviorSubject, forkJoin, Observable, Observer } from 'rxjs'; +import { BehaviorSubject, forkJoin, Observable, Observer, of } from 'rxjs'; import { share } from 'rxjs/operators'; import { ValueAction, ValueGetter, ValueSetter } from '@home/components/widget/lib/action/action-widget.models'; import { WidgetContext } from '@home/models/widget-component.models'; import { ColorProcessor, constantColor, Font } from '@shared/models/widget-settings.models'; import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; import { UtilsService } from '@core/services/utils.service'; -import { WidgetAction, WidgetActionType } from '@shared/models/widget.models'; +import { WidgetAction, WidgetActionType, widgetActionTypeTranslationMap } from '@shared/models/widget.models'; export interface IotSvgApi { formatValue: (value: any, dec?: number, units?: string, showZeroDecimals?: boolean) => string | undefined; @@ -85,6 +85,17 @@ export enum IotSvgBehaviorType { widgetAction = 'widgetAction' } +export const iotSvgBehaviorTypes = Object.keys(IotSvgBehaviorType) as IotSvgBehaviorType[]; + +export const iotSvgBehaviorTypeTranslations = new Map( + [ + [IotSvgBehaviorType.value, 'scada.behavior.type-value'], + [IotSvgBehaviorType.action, 'scada.behavior.type-action'], + [IotSvgBehaviorType.widgetAction, 'scada.behavior.type-widget-action'] + ] +); + + export interface IotSvgBehaviorBase { id: string; name: string; @@ -95,7 +106,6 @@ export interface IotSvgBehaviorBase { export interface IotSvgBehaviorValue extends IotSvgBehaviorBase { valueType: ValueType; defaultValue: any; - valueId: string; trueLabel?: string; falseLabel?: string; stateLabel?: string; @@ -283,23 +293,29 @@ const defaultWidgetActionSettings = (widgetAction: IotSvgBehavior): WidgetAction }); export const defaultIotSvgObjectSettings = (metadata: IotSvgMetadata): IotSvgObjectSettings => { - const settings: IotSvgObjectSettings = {}; + const settings: IotSvgObjectSettings = { + behavior: {}, + properties: {} + }; for (const behavior of metadata.behavior) { if (behavior.type === IotSvgBehaviorType.value) { - settings[behavior.id] = defaultGetValueSettings(behavior as IotSvgBehaviorValue); + settings.behavior[behavior.id] = defaultGetValueSettings(behavior as IotSvgBehaviorValue); } else if (behavior.type === IotSvgBehaviorType.action) { - settings[behavior.id] = defaultSetValueSettings(behavior as IotSvgBehaviorAction); + settings.behavior[behavior.id] = defaultSetValueSettings(behavior as IotSvgBehaviorAction); } else if (behavior.type === IotSvgBehaviorType.widgetAction) { - settings[behavior.id] = defaultWidgetActionSettings(behavior); + settings.behavior[behavior.id] = defaultWidgetActionSettings(behavior); } } for (const property of metadata.properties) { - settings[property.id] = property.default; + settings.properties[property.id] = property.default; } return settings; }; -export type IotSvgObjectSettings = {[id: string]: any}; +export type IotSvgObjectSettings = { + behavior: {[id: string]: any}; + properties: {[id: string]: any}; +}; const parseError = (ctx: WidgetContext, err: any): string => ctx.$injector.get(UtilsService).parseException(err).message || 'Unknown Error'; @@ -327,16 +343,20 @@ export class IotSvgObject { private _onError: (error: string) => void = () => {}; + private _onMessage: (message: string) => void = () => {}; + constructor(private ctx: WidgetContext, private svgContent: string, - private inputSettings: IotSvgObjectSettings) { + private inputSettings: IotSvgObjectSettings, + private simulated: boolean) { } public init() { const doc: XMLDocument = new DOMParser().parseFromString(this.svgContent, 'image/svg+xml'); this.metadata = parseIotSvgMetadataFromDom(doc); const defaults = defaultIotSvgObjectSettings(this.metadata); - this.settings = mergeDeep({}, defaults, this.inputSettings || {}); + this.settings = mergeDeep({} as IotSvgObjectSettings, + defaults, this.inputSettings || {} as IotSvgObjectSettings); this.prepareMetadata(); this.prepareSvgShape(doc); this.initialize(); @@ -346,6 +366,10 @@ export class IotSvgObject { this._onError = onError; } + public onMessage(onMessage: (message: string) => void) { + this._onMessage = onMessage; + } + public addTo(element: HTMLElement) { this.rootElement = element; if (this.svgShape) { @@ -451,13 +475,14 @@ export class IotSvgObject { for (const behavior of this.metadata.behavior) { if (behavior.type === IotSvgBehaviorType.value) { const getBehavior = behavior as IotSvgBehaviorValue; - let getValueSettings: GetValueSettings = this.settings[getBehavior.id]; - getValueSettings = {...getValueSettings, actionLabel: getBehavior.name}; + let getValueSettings: GetValueSettings = this.settings.behavior[getBehavior.id]; + getValueSettings = {...getValueSettings, actionLabel: + this.ctx.utilsService.customTranslation(getBehavior.name, getBehavior.name)}; const stateValueSubject = new BehaviorSubject(getValueSettings.defaultValue); - this.stateValueSubjects[getBehavior.valueId] = stateValueSubject; - this.context.values[getBehavior.valueId] = getValueSettings.defaultValue; + this.stateValueSubjects[getBehavior.id] = stateValueSubject; + this.context.values[getBehavior.id] = getValueSettings.defaultValue; stateValueSubject.subscribe((value) => { - this.onStateValueChanged(getBehavior.valueId, value); + this.onStateValueChanged(getBehavior.id, value); }); const valueGetter = ValueGetter.fromSettings(this.ctx, getValueSettings, getBehavior.valueType, { @@ -466,14 +491,15 @@ export class IotSvgObject { const message = parseError(this.ctx, err); this._onError(message); } - }); + }, this.simulated); this.valueGetters.push(valueGetter); this.valueActions.push(valueGetter); } else if (behavior.type === IotSvgBehaviorType.action) { const setBehavior = behavior as IotSvgBehaviorAction; - let setValueSettings: SetValueSettings = this.settings[setBehavior.id]; - setValueSettings = {...setValueSettings, actionLabel: setBehavior.name}; - const valueSetter = ValueSetter.fromSettings(this.ctx, setValueSettings); + let setValueSettings: SetValueSettings = this.settings.behavior[setBehavior.id]; + setValueSettings = {...setValueSettings, actionLabel: + this.ctx.utilsService.customTranslation(setBehavior.name, setBehavior.name)}; + const valueSetter = ValueSetter.fromSettings(this.ctx, setValueSettings, this.simulated); this.valueSetters[setBehavior.id] = valueSetter; this.valueActions.push(valueSetter); } else if (behavior.type === IotSvgBehaviorType.widgetAction) { @@ -527,8 +553,14 @@ export class IotSvgObject { ); } } else if (behavior.type === IotSvgBehaviorType.widgetAction) { - const widgetAction: WidgetAction = this.settings[behavior.id]; - this.ctx.actionsApi.onWidgetAction(event, widgetAction); + const widgetAction: WidgetAction = this.settings.behavior[behavior.id]; + if (this.simulated) { + const translatedType = this.ctx.translate.instant(widgetActionTypeTranslationMap.get(widgetAction.type)); + const message = this.ctx.translate.instant('scada.preview-widget-action-text', {type: translatedType}); + this._onMessage(message); + } else { + this.ctx.actionsApi.onWidgetAction(event, widgetAction); + } } } } @@ -546,8 +578,7 @@ export class IotSvgObject { private onValue(id: string, value: any) { const valueBehavior = this.metadata.behavior.find(b => b.id === id) as IotSvgBehaviorValue; value = this.normalizeValue(value, valueBehavior.valueType); - const valueId = valueBehavior.valueId; - this.setValue(valueId, value); + this.setValue(valueBehavior.id, value); } private setValue(valueId: string, value: any) { @@ -659,7 +690,7 @@ export class IotSvgObject { private getPropertyValue(id: string): any { const property = this.getProperty(id); if (property) { - const value = this.settings[id]; + const value = this.settings.properties[id]; if (isDefinedAndNotNull(value)) { if (property.type === 'color-settings') { return ColorProcessor.fromSettings(value); diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.html b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.html new file mode 100644 index 0000000000..5b9c4d2cf9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.html @@ -0,0 +1,48 @@ + + diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.scss b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.scss new file mode 100644 index 0000000000..d0592c431e --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.scss @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +.tb-scada-symbol-metadata-behavior-row { + .tb-id-field { + flex: 1 1 40%; + } + .tb-name-field { + flex: 1 1 40%; + } + .tb-type-field { + flex: 1 1 20%; + } +} diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.ts new file mode 100644 index 0000000000..277d9fe4ce --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.ts @@ -0,0 +1,172 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + forwardRef, + Input, + OnInit, + Output, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import { + AbstractControl, + ControlValueAccessor, + NG_VALUE_ACCESSOR, + UntypedFormBuilder, + UntypedFormGroup, + ValidationErrors +} from '@angular/forms'; +import { MatDialog } from '@angular/material/dialog'; +import { + IotSvgBehavior, + IotSvgBehaviorAction, + IotSvgBehaviorType, + iotSvgBehaviorTypes, + iotSvgBehaviorTypeTranslations, + IotSvgBehaviorValue +} from '@home/components/widget/lib/svg/iot-svg.models'; + +export const behaviorValid = (behavior: IotSvgBehavior): boolean => { + if (!behavior.id || !behavior.name || !behavior.type) { + return false; + } + switch (behavior.type) { + case IotSvgBehaviorType.value: + const valueBehavior = behavior as IotSvgBehaviorValue; + if (!valueBehavior.valueType) { + return false; + } + break; + case IotSvgBehaviorType.action: + const actionBehavior = behavior as IotSvgBehaviorAction; + if (!actionBehavior.valueToDataType) { + return false; + } + break; + case IotSvgBehaviorType.widgetAction: + break; + } + return true; +}; + +export const behaviorValidator = (control: AbstractControl): ValidationErrors | null => { + const behavior: IotSvgBehavior = control.value; + if (!behaviorValid(behavior)) { + return { + behavior: true + }; + } + return null; +}; + +@Component({ + selector: 'tb-scada-symbol-metadata-behavior-row', + templateUrl: './scada-symbol-behavior-row.component.html', + styleUrls: ['./scada-symbol-behavior-row.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ScadaSymbolBehaviorRowComponent), + multi: true + } + ], + encapsulation: ViewEncapsulation.None +}) +export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, OnInit { + + @ViewChild('idInput') + idInput: ElementRef; + + iotSvgBehaviorTypes = iotSvgBehaviorTypes; + iotSvgBehaviorTypeTranslations = iotSvgBehaviorTypeTranslations; + + @Input() + disabled: boolean; + + @Output() + behaviorRemoved = new EventEmitter(); + + behaviorRowFormGroup: UntypedFormGroup; + + modelValue: IotSvgBehavior; + + private propagateChange = (_val: any) => {}; + + constructor(private fb: UntypedFormBuilder, + private dialog: MatDialog, + private cd: ChangeDetectorRef) { + } + + ngOnInit() { + this.behaviorRowFormGroup = this.fb.group({ + id: [null, []], + name: [null, []], + type: [null, []] + }); + this.behaviorRowFormGroup.valueChanges.subscribe( + () => this.updateModel() + ); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.behaviorRowFormGroup.disable({emitEvent: false}); + } else { + this.behaviorRowFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: IotSvgBehavior): void { + this.modelValue = value; + this.behaviorRowFormGroup.patchValue( + { + id: value?.id, + name: value?.name, + type: value?.type + }, {emitEvent: false} + ); + this.cd.markForCheck(); + } + + editBehavior() { + + } + + focus() { + this.idInput.nativeElement.scrollIntoView(); + this.idInput.nativeElement.focus(); + } + + private updateModel() { + const value: IotSvgBehavior = this.behaviorRowFormGroup.value; + this.modelValue = {...this.modelValue, ...value}; + this.propagateChange(this.modelValue); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behaviors.component.html b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behaviors.component.html new file mode 100644 index 0000000000..28efaca269 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behaviors.component.html @@ -0,0 +1,60 @@ + +
+
+
+
scada.behavior.id
+
scada.behavior.name
+
scada.behavior.type
+
+
+
+
+ + +
+ +
+
+
+
+
+ +
+
+ + + {{ 'scada.behavior.no-behaviors' | translate }} + diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behaviors.component.scss b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behaviors.component.scss new file mode 100644 index 0000000000..a9fd4df110 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behaviors.component.scss @@ -0,0 +1,43 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +.tb-scada-symbol-behaviors { + flex: 1; + margin: 12px; + .tb-form-table-header-cell { + &.tb-id-header { + flex: 1 1 40%; + } + &.tb-name-header { + flex: 1 1 40%; + } + &.tb-type-header { + flex: 1 1 20%; + } + &.tb-actions-header { + width: 120px; + min-width: 120px; + } + } + .tb-form-table { + overflow: hidden; + } + .tb-form-table-body { + overflow: auto; + tb-scada-symbol-metadata-behavior-row { + overflow: hidden; + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behaviors.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behaviors.component.ts new file mode 100644 index 0000000000..d06e790784 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behaviors.component.ts @@ -0,0 +1,177 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + Component, + forwardRef, + HostBinding, + Input, + OnInit, + QueryList, + ViewChildren, + ViewEncapsulation +} from '@angular/core'; +import { + AbstractControl, + ControlValueAccessor, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + UntypedFormArray, + UntypedFormBuilder, + UntypedFormControl, + UntypedFormGroup, + Validator +} from '@angular/forms'; +import { IotSvgBehavior, IotSvgBehaviorType } from '@home/components/widget/lib/svg/iot-svg.models'; +import { ValueType } from '@shared/models/constants'; +import { CdkDragDrop } from '@angular/cdk/drag-drop'; +import { + behaviorValid, + behaviorValidator, + ScadaSymbolBehaviorRowComponent +} from '@home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component'; + +@Component({ + selector: 'tb-scada-symbol-metadata-behaviors', + templateUrl: './scada-symbol-behaviors.component.html', + styleUrls: ['./scada-symbol-behaviors.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ScadaSymbolBehaviorsComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => ScadaSymbolBehaviorsComponent), + multi: true + } + ], + encapsulation: ViewEncapsulation.None +}) +export class ScadaSymbolBehaviorsComponent implements ControlValueAccessor, OnInit, Validator { + + @HostBinding('style.display') styleDisplay = 'flex'; + @HostBinding('style.overflow') styleOverflow = 'hidden'; + + @ViewChildren(ScadaSymbolBehaviorRowComponent) + behaviorRows: QueryList; + + @Input() + disabled: boolean; + + behaviorsFormGroup: UntypedFormGroup; + + get dragEnabled(): boolean { + return this.behaviorsFormArray().controls.length > 1; + } + + private propagateChange = (_val: any) => {}; + + constructor(private fb: UntypedFormBuilder) { + } + + ngOnInit() { + this.behaviorsFormGroup = this.fb.group({ + behaviors: this.fb.array([]) + }); + this.behaviorsFormGroup.valueChanges.subscribe( + () => { + let behaviors: IotSvgBehavior[] = this.behaviorsFormGroup.get('behaviors').value; + if (behaviors) { + behaviors = behaviors.filter(b => behaviorValid(b)); + } + this.propagateChange(behaviors); + } + ); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.behaviorsFormGroup.disable({emitEvent: false}); + } else { + this.behaviorsFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: IotSvgBehavior[] | undefined): void { + const behaviors= value || []; + this.behaviorsFormGroup.setControl('behaviors', this.prepareBehaviorsFormArray(behaviors), {emitEvent: false}); + } + + public validate(c: UntypedFormControl) { + const valid = this.behaviorsFormGroup.valid; + return valid ? null : { + behaviors: { + valid: false, + }, + }; + } + + behaviorDrop(event: CdkDragDrop) { + const behaviorsArray = this.behaviorsFormGroup.get('behaviors') as UntypedFormArray; + const behavior = behaviorsArray.at(event.previousIndex); + behaviorsArray.removeAt(event.previousIndex); + behaviorsArray.insert(event.currentIndex, behavior); + } + + behaviorsFormArray(): UntypedFormArray { + return this.behaviorsFormGroup.get('behaviors') as UntypedFormArray; + } + + trackByBehavior(index: number, behaviorControl: AbstractControl): any { + return behaviorControl; + } + + removeBehavior(index: number, emitEvent = true) { + (this.behaviorsFormGroup.get('behaviors') as UntypedFormArray).removeAt(index, {emitEvent}); + } + + addBehavior() { + const behavior: IotSvgBehavior = { + id: '', + name: '', + type: IotSvgBehaviorType.value, + valueType: ValueType.BOOLEAN, + defaultValue: false + }; + const behaviorsArray = this.behaviorsFormGroup.get('behaviors') as UntypedFormArray; + const behaviorControl = this.fb.control(behavior, [behaviorValidator]); + behaviorsArray.push(behaviorControl); + setTimeout(() => { + const behaviorRow = this.behaviorRows.get(this.behaviorRows.length-1); + behaviorRow.focus(); + }); + } + + private prepareBehaviorsFormArray(behaviors: IotSvgBehavior[] | undefined): UntypedFormArray { + const behaviorsControls: Array = []; + if (behaviors) { + behaviors.forEach((behavior) => { + behaviorsControls.push(this.fb.control(behavior, [behaviorValidator])); + }); + } + return this.fb.array(behaviorsControls); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-components.module.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-components.module.ts index 5b86c23f04..469f7e9782 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-components.module.ts +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-components.module.ts @@ -24,13 +24,21 @@ import { ScadaSymbolMetadataComponent } from '@home/pages/scada-symbol/metadata- import { ScadaSymbolMetadataTagsComponent } from '@home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component'; +import { + ScadaSymbolBehaviorsComponent +} from '@home/pages/scada-symbol/metadata-components/scada-symbol-behaviors.component'; +import { + ScadaSymbolBehaviorRowComponent +} from '@home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component'; @NgModule({ declarations: [ ScadaSymbolMetadataComponent, ScadaSymbolMetadataTagComponent, - ScadaSymbolMetadataTagsComponent + ScadaSymbolMetadataTagsComponent, + ScadaSymbolBehaviorsComponent, + ScadaSymbolBehaviorRowComponent ], imports: [ CommonModule, diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.html b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.html index e3e0b82d82..01914d1e32 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.html +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.html @@ -22,7 +22,7 @@
scada.tag
- + {{ tag }} diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.ts index f074955776..663a87d8fa 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.ts +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.ts @@ -37,6 +37,7 @@ import { import { IotSvgTag } from '@home/components/widget/lib/svg/iot-svg.models'; import { MatExpansionPanel } from '@angular/material/expansion'; import { JsFuncComponent } from '@shared/components/js-func.component'; +import { MatSelect } from '@angular/material/select'; @Component({ selector: 'tb-scada-symbol-metadata-tag', @@ -53,6 +54,9 @@ import { JsFuncComponent } from '@shared/components/js-func.component'; }) export class ScadaSymbolMetadataTagComponent implements ControlValueAccessor, OnInit, OnChanges { + @ViewChild('tagSelect') + tagSelect: MatSelect; + @ViewChild('expansionPanel') expansionPanel: MatExpansionPanel; @@ -163,6 +167,11 @@ export class ScadaSymbolMetadataTagComponent implements ControlValueAccessor, On }); } + focus() { + this.tagSelect._elementRef.nativeElement.scrollIntoView(); + this.tagSelect.focus(); + } + private openPanelWithCallback(panel: MatExpansionPanel, callback: () => void) { if (!panel.expanded) { const s = panel.afterExpand.subscribe(() => { diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.ts index 13bebfe72d..5b6c8210db 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.ts +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.ts @@ -164,6 +164,10 @@ export class ScadaSymbolMetadataTagsComponent implements ControlValueAccessor, O addTag() { this.addNewTag(null); + setTimeout(() => { + const tagComponent = this.metadataTags.get(this.metadataTags.length-1); + tagComponent.focus(); + }); } editTagStateRenderFunction(tag: string): void { diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.html b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.html index 16573951a7..395f6c67f0 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.html +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.html @@ -58,5 +58,10 @@ formControlName="tags">
+
+ + +
diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts index d8b6267a8f..96f4b0b184 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts @@ -80,7 +80,7 @@ export class ScadaSymbolMetadataComponent extends PageComponent implements OnIni value: 'tags' }, { - name: this.translate.instant('scada.behavior'), + name: this.translate.instant('scada.behavior.behavior'), value: 'behavior' }, { diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.ts index d3b08fd795..c595f63071 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.ts +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.ts @@ -218,13 +218,22 @@ export class ScadaSymbolComponent extends PageComponent enterPreviewMode() { this.symbolData.svgContent = this.prepareSvgContent(); - this.previewIotSvgObjectSettings = {}; + this.previewIotSvgObjectSettings = { + behavior: {}, + properties: {} + }; this.scadaPreviewFormGroup.patchValue({ iotSvgObject: this.previewIotSvgObjectSettings }, {emitEvent: false}); this.scadaPreviewFormGroup.markAsPristine(); const settings: IotSvgWidgetSettings = {...iotSvgWidgetDefaultSettings, - ...{iotSvg: null, iotSvgContent: this.symbolData.svgContent, iotSvgObject: this.previewIotSvgObjectSettings}}; + ...{ + simulated: true, + iotSvg: null, + iotSvgContent: this.symbolData.svgContent, + iotSvgObject: this.previewIotSvgObjectSettings + } + }; this.previewWidget = { typeFullFqn: 'system.iot_svg', type: widgetType.rpc, diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index f6a81e8f1d..8ac12e376e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3389,9 +3389,6 @@ "error-entities": "There was an error creating {{count}} entities." } }, - "iot-svg": { - "behavior": "Behavior" - }, "scada": { "symbol": "SCADA symbol", "symbols": "SCADA symbols", @@ -3413,7 +3410,6 @@ "symbol-preview": "Symbol preview", "general": "General", "tags": "Tags", - "behavior": "Behavior", "properties": "Properties", "title": "Title", "state-render-function": "State render function", @@ -3423,7 +3419,21 @@ "remove-tag": "Remove tag", "add-tag": "Add tag", "no-tags": "No tags configured", - "select-tag": "Select tag" + "select-tag": "Select tag", + "preview-widget-action-text": "Widget action '{{type}}' successfully invoked!", + "behavior": { + "behavior": "Behavior", + "id": "Id", + "name": "Name", + "type": "Type", + "no-behaviors": "No behaviors configured", + "add-behavior": "Add behavior", + "type-action": "Action", + "type-value": "Value", + "type-widget-action": "Widget action", + "behavior-settings": "Behavior settings", + "remove-behavior": "Remove behavior" + } }, "item": { "selected": "Selected" diff --git a/ui-ngx/src/assets/widget/svg/drawing.svg b/ui-ngx/src/assets/widget/svg/drawing.svg index 55d60d04c1..5f64fc6643 100644 --- a/ui-ngx/src/assets/widget/svg/drawing.svg +++ b/ui-ngx/src/assets/widget/svg/drawing.svg @@ -28,7 +28,7 @@ "tag": "onButton", "actions": { "click": { - "actionFunction": "ctx.api.callAction(event, 'onUpdateState');" + "actionFunction": "ctx.api.callAction(event, 'onUpdateState', undefined, {\n next: () => {\n ctx.api.setValue('on', true);\n }\n});" } } }, @@ -36,7 +36,7 @@ "tag": "offButton", "actions": { "click": { - "actionFunction": "ctx.api.callAction(event, 'offUpdateState');" + "actionFunction": "ctx.api.callAction(event, 'offUpdateState', undefined, {\n next: () => {\n ctx.api.setValue('on', false);\n }\n});\n" } } }, @@ -59,33 +59,30 @@ ], "behavior": [ { - "id": "levelState", + "id": "level", "name": "Level", "type": "value", "valueType": "DOUBLE", - "defaultValue": 0, - "valueId": "level" + "defaultValue": 0 }, { - "id": "disabledState", + "id": "disabled", "name": "{i18n:widgets.rpc-state.disabled-state}", "hint": "{i18n:widgets.rpc-state.disabled-state-hint}", "stateLabel": "{i18n:widgets.rpc-state.disabled}", "type": "value", "valueType": "BOOLEAN", - "defaultValue": false, - "valueId": "disabled" + "defaultValue": false }, { - "id": "onState", + "id": "on", "name": "On/Off state", "trueLabel": "{i18n:widgets.rpc-state.on}", "falseLabel": "{i18n:widgets.rpc-state.off}", "stateLabel": "{i18n:widgets.rpc-state.on}", "type": "value", "valueType": "BOOLEAN", - "defaultValue": false, - "valueId": "on" + "defaultValue": false }, { "id": "onUpdateState", diff --git a/ui-ngx/src/form.scss b/ui-ngx/src/form.scss index 18f5f6755d..88d0df0e29 100644 --- a/ui-ngx/src/form.scss +++ b/ui-ngx/src/form.scss @@ -564,6 +564,7 @@ .tb-form-table-header { height: 48px; + min-height: 48px; border-bottom: 1px solid rgba(0, 0, 0, 0.12); &-cell { color: rgba(0, 0, 0, 0.54);