From 122ca092ad31ad0e7dd1312555875b9dc233081d Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Tue, 17 Sep 2024 17:33:43 +0300 Subject: [PATCH 01/21] UI: Fixed next btn for notify again dialog --- .../src/app/shared/components/entity/entity-list.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts index 2108551031..c06569e0b9 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts @@ -196,8 +196,8 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV this.entityListFormGroup.get('entities').setValue(this.entities); if (this.syncIdsWithDB && this.modelValue.length !== entities.length) { this.modelValue = entities.map(entity => entity.id.id); - this.propagateChange(this.modelValue); } + this.propagateChange(this.modelValue); } ); } else { From fd8107aa90415aca881d29b40d2fbf081475b3ca Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Wed, 18 Sep 2024 12:19:12 +0300 Subject: [PATCH 02/21] UI: Refactoring --- .../app/shared/components/entity/entity-list.component.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts index c06569e0b9..a9aae0c9ce 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts @@ -46,6 +46,7 @@ import { MatChipGrid } from '@angular/material/chips'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { SubscriptSizing } from '@angular/material/form-field'; import { coerceBoolean } from '@shared/decorators/coercion'; +import { isArray } from 'lodash'; @Component({ selector: 'tb-entity-list', @@ -196,8 +197,8 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV this.entityListFormGroup.get('entities').setValue(this.entities); if (this.syncIdsWithDB && this.modelValue.length !== entities.length) { this.modelValue = entities.map(entity => entity.id.id); + this.propagateChange(this.modelValue); } - this.propagateChange(this.modelValue); } ); } else { @@ -209,7 +210,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV } validate(): ValidationErrors | null { - return this.entityListFormGroup.valid ? null : { + return isArray(this.modelValue) && this.modelValue.length ? null : { entities: {valid: false} }; } From 1b36e5770ab361f1cc197783eccfd630e82a80c8 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Wed, 18 Sep 2024 15:33:43 +0300 Subject: [PATCH 03/21] Added Modbus Report Strategy --- .../modbus-version-processor.abstract.ts | 8 +- .../modbus-basic-config.abstract.ts | 6 +- .../modbus-basic-config.component.html | 2 +- .../modbus-basic-config.component.ts | 4 +- .../modbus-legacy-basic-config.component.ts | 26 ++- .../modbus-data-keys-panel.component.html | 5 + .../modbus-data-keys-panel.component.ts | 29 ++- .../modbus-master-table.component.ts | 49 ++++- .../modbus-legacy-slave-dialog.component.ts | 84 +++++++ .../modbus-slave-dialog.abstract.ts | 207 ++++++++++++++++++ .../modbus-slave-dialog.component.html | 7 +- .../modbus-slave-dialog.component.ts | 191 ++-------------- .../modbus-values/modbus-values.component.ts | 6 +- .../report-strategy.component.html | 57 +++++ .../report-strategy.component.ts | 168 ++++++++++++++ .../gateway/gateway-connectors.component.html | 1 + .../gateway/gateway-connectors.component.ts | 17 +- .../lib/gateway/gateway-widget.models.ts | 38 +++- .../utils/modbus-version-mapping.util.ts | 30 ++- .../widget/widget-components.module.ts | 4 + .../assets/locale/locale.constant-en_US.json | 7 + .../connector-default-configs/modbus.json | 1 - 22 files changed, 725 insertions(+), 222 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-legacy-slave-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts index b38a07cd0f..8b05572659 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract.ts @@ -16,10 +16,12 @@ import { GatewayConnector, + LegacySlaveConfig, ModbusBasicConfig, ModbusBasicConfig_v3_5_2, ModbusLegacyBasicConfig, ModbusLegacySlave, + ModbusMasterConfig, ModbusSlave, } from '../gateway-widget.models'; import { GatewayConnectorVersionProcessor } from './gateway-connector-version-processor.abstract'; @@ -40,7 +42,7 @@ export class ModbusVersionProcessor extends GatewayConnectorVersionProcessor) : { slaves: [] }, slave: configurationJson.slave ? ModbusVersionMappingUtil.mapSlaveToUpgradedVersion(configurationJson.slave as ModbusLegacySlave) @@ -59,7 +61,9 @@ export class ModbusVersionProcessor extends GatewayConnectorVersionProcessor; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract.ts index f3daa214dd..66024c6a8c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.abstract.ts @@ -25,8 +25,8 @@ import { } from '@home/components/widget/lib/gateway/gateway-widget.models'; @Directive() -export abstract class ModbusBasicConfigDirective - extends GatewayConnectorBasicConfigDirective { +export abstract class ModbusBasicConfigDirective + extends GatewayConnectorBasicConfigDirective { enableSlaveControl: FormControl = new FormControl(false); @@ -41,7 +41,7 @@ export abstract class ModbusBasicConfigDirective }); } - override writeValue(basicConfig: BasicConfig & ModbusBasicConfig): void { + override writeValue(basicConfig: OutputBasicConfig & ModbusBasicConfig): void { super.writeValue(basicConfig); this.onEnableSlaveControl(basicConfig); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.html index 30233cddcf..76098288b8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.html @@ -20,7 +20,7 @@ - +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts index b5fab7c92d..dceffd3f7d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-basic-config.component.ts @@ -56,7 +56,9 @@ import { ], styleUrls: ['./modbus-basic-config.component.scss'], }) -export class ModbusBasicConfigComponent extends ModbusBasicConfigDirective { +export class ModbusBasicConfigComponent extends ModbusBasicConfigDirective { + + isLegacy = false; protected override mapConfigToFormValue({ master, slave }: ModbusBasicConfig_v3_5_2): ModbusBasicConfig_v3_5_2 { return { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts index 5bca9b0292..c10214d57b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component.ts @@ -17,8 +17,10 @@ import { ChangeDetectionStrategy, Component, forwardRef } from '@angular/core'; import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; import { - ModbusBasicConfig_v3_5_2, - ModbusLegacyBasicConfig, ModbusLegacySlave, + LegacySlaveConfig, + ModbusBasicConfig, + ModbusLegacyBasicConfig, + ModbusLegacySlave, ModbusMasterConfig, ModbusSlave } from '@home/components/widget/lib/gateway/gateway-widget.models'; @@ -58,21 +60,21 @@ import { ], styleUrls: ['./modbus-basic-config.component.scss'], }) -export class ModbusLegacyBasicConfigComponent extends ModbusBasicConfigDirective { +export class ModbusLegacyBasicConfigComponent extends ModbusBasicConfigDirective { - protected override mapConfigToFormValue(config: ModbusLegacyBasicConfig): ModbusBasicConfig_v3_5_2 { + isLegacy = true; + + protected override mapConfigToFormValue(config: ModbusLegacyBasicConfig): ModbusBasicConfig { return { - master: config.master?.slaves - ? ModbusVersionMappingUtil.mapMasterToUpgradedVersion(config.master) - : { slaves: [] } as ModbusMasterConfig, - slave: config.slave ? ModbusVersionMappingUtil.mapSlaveToUpgradedVersion(config.slave) : {} as ModbusSlave, - }; + master: config.master?.slaves ? config.master : { slaves: [] } as ModbusMasterConfig, + slave: config.slave ? ModbusVersionMappingUtil.mapSlaveToUpgradedVersion(config.slave as ModbusLegacySlave) : {}, + } as ModbusBasicConfig; } - protected override getMappedValue(value: ModbusBasicConfig_v3_5_2): ModbusLegacyBasicConfig { + protected override getMappedValue(value: ModbusBasicConfig): ModbusLegacyBasicConfig { return { - master: value.master, - slave: value.slave ? ModbusVersionMappingUtil.mapSlaveToDowngradedVersion(value.slave) : {} as ModbusLegacySlave, + master: value.master as ModbusMasterConfig, + slave: value.slave ? ModbusVersionMappingUtil.mapSlaveToDowngradedVersion(value.slave as ModbusSlave) : {} as ModbusLegacySlave, }; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html index fc94fe49ea..092621b178 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.html @@ -165,6 +165,11 @@
+ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts index 3c61d351b7..f158efbad4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts @@ -41,6 +41,9 @@ import { generateSecret } from '@core/utils'; import { coerceBoolean } from '@shared/decorators/coercion'; import { takeUntil } from 'rxjs/operators'; import { Subject } from 'rxjs'; +import { + ReportStrategyComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component'; @Component({ selector: 'tb-modbus-data-keys-panel', @@ -51,12 +54,15 @@ import { Subject } from 'rxjs'; CommonModule, SharedModule, GatewayHelpLinkPipe, + ReportStrategyComponent, ] }) export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy { @coerceBoolean() @Input() isMaster = false; + @coerceBoolean() + @Input() hideNewFields = false; @Input() panelTitle: string; @Input() addKeyTitle: string; @Input() deleteKeyTitle: string; @@ -70,6 +76,7 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy { keysListFormArray: FormArray; modbusDataTypes = Object.values(ModbusDataType); withFunctionCode = true; + withReportStrategy = true; functionCodesMap = new Map(); defaultFunctionCodes = []; @@ -87,6 +94,9 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy { ngOnInit(): void { this.withFunctionCode = !this.isMaster || (this.keysType !== ModbusValueKey.ATTRIBUTES && this.keysType !== ModbusValueKey.TIMESERIES); + this.withReportStrategy = !this.isMaster + && (this.keysType === ModbusValueKey.ATTRIBUTES || this.keysType === ModbusValueKey.TIMESERIES) + && !this.hideNewFields; this.keysListFormArray = this.prepareKeysFormArray(this.values); this.defaultFunctionCodes = this.getDefaultFunctionCodes(); } @@ -108,6 +118,7 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy { address: [null, [Validators.required]], objectsCount: [1, [Validators.required]], functionCode: [{ value: this.getDefaultFunctionCodes()[0], disabled: !this.withFunctionCode }, [Validators.required]], + reportStrategy: [{ value: null, disabled: !this.withReportStrategy }], id: [{value: generateSecret(5), disabled: true}], }); this.observeKeyDataType(dataKeyFormGroup); @@ -128,7 +139,20 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy { } applyKeysData(): void { - this.keysDataApplied.emit(this.keysListFormArray.value); + this.keysDataApplied.emit(this.getFormValue()); + } + + private getFormValue(): ModbusValue[] { + return this.withReportStrategy + ? this.cleanUpEmptyStrategies(this.keysListFormArray.value) + : this.keysListFormArray.value; + } + + private cleanUpEmptyStrategies(values: ModbusValue[]): ModbusValue[] { + return values.map((key) => { + const { reportStrategy, ...updatedKey } = key; + return !reportStrategy ? updatedKey : key; + }); } private prepareKeysFormArray(values: ModbusValue[]): UntypedFormArray { @@ -148,7 +172,7 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy { } private createDataKeyFormGroup(modbusValue: ModbusValue): FormGroup { - const { tag, value, type, address, objectsCount, functionCode } = modbusValue; + const { tag, value, type, address, objectsCount, functionCode, reportStrategy } = modbusValue; return this.fb.group({ tag: [tag, [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], @@ -158,6 +182,7 @@ export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy { objectsCount: [objectsCount, [Validators.required]], functionCode: [{ value: functionCode, disabled: !this.withFunctionCode }, [Validators.required]], id: [{ value: generateSecret(5), disabled: true }], + reportStrategy: [{ value: reportStrategy, disabled: !this.withReportStrategy }], }); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts index 4787523b40..8f1761dd87 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts @@ -21,12 +21,13 @@ import { Component, ElementRef, forwardRef, + Input, OnDestroy, OnInit, ViewChild, } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; -import { MatDialog } from '@angular/material/dialog'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { DialogService } from '@core/services/dialog.service'; import { Subject } from 'rxjs'; import { debounceTime, distinctUntilChanged, take, takeUntil } from 'rxjs/operators'; @@ -38,8 +39,9 @@ import { UntypedFormGroup, } from '@angular/forms'; import { + LegacySlaveConfig, ModbusMasterConfig, - ModbusProtocolLabelsMap, + ModbusProtocolLabelsMap, ModbusSlave, ModbusSlaveInfo, ModbusValues, SlaveConfig @@ -49,6 +51,10 @@ import { SharedModule } from '@shared/shared.module'; import { CommonModule } from '@angular/common'; import { ModbusSlaveDialogComponent } from '../modbus-slave-dialog/modbus-slave-dialog.component'; import { TbTableDatasource } from '@shared/components/table/table-datasource.abstract'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { + ModbusLegacySlaveDialogComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-legacy-slave-dialog.component'; @Component({ selector: 'tb-modbus-master-table', @@ -69,6 +75,9 @@ export class ModbusMasterTableComponent implements ControlValueAccessor, AfterVi @ViewChild('searchInput') searchInputField: ElementRef; + @coerceBoolean() + @Input() isLegacy = false; + textSearchMode = false; dataSource: SlavesDatasource; masterFormGroup: UntypedFormGroup; @@ -152,14 +161,7 @@ export class ModbusMasterTableComponent implements ControlValueAccessor, AfterVi } const withIndex = isDefinedAndNotNull(index); const value = withIndex ? this.slaves.at(index).value : {}; - this.dialog.open(ModbusSlaveDialogComponent, { - disableClose: true, - panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], - data: { - value, - buttonTitle: withIndex ? 'action.apply' : 'action.add' - } - }).afterClosed() + this.getSlaveDialog(value, withIndex ? 'action.apply' : 'action.add').afterClosed() .pipe(take(1), takeUntil(this.destroy$)) .subscribe(res => { if (res) { @@ -173,6 +175,33 @@ export class ModbusMasterTableComponent implements ControlValueAccessor, AfterVi }); } + private getSlaveDialog( + value: LegacySlaveConfig | SlaveConfig, + buttonTitle: string + ): MatDialogRef { + if (this.isLegacy) { + return this.dialog.open, ModbusValues> + (ModbusLegacySlaveDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + value: value as LegacySlaveConfig, + hideNewFields: true, + buttonTitle + } + }); + } + return this.dialog.open(ModbusSlaveDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + value: value as SlaveConfig, + buttonTitle, + hideNewFields: false, + } + }); + } + deleteSlave($event: Event, index: number): void { if ($event) { $event.stopPropagation(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-legacy-slave-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-legacy-slave-dialog.component.ts new file mode 100644 index 0000000000..137ef385ab --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-legacy-slave-dialog.component.ts @@ -0,0 +1,84 @@ +/// +/// 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 { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; +import { + FormBuilder, +} from '@angular/forms'; +import { + LegacySlaveConfig, + ModbusProtocolType, + ModbusSlaveInfo, +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { SharedModule } from '@shared/shared.module'; +import { CommonModule } from '@angular/common'; +import { ModbusValuesComponent } from '../modbus-values/modbus-values.component'; +import { ModbusSecurityConfigComponent } from '../modbus-security-config/modbus-security-config.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { GatewayPortTooltipPipe } from '@home/components/widget/lib/gateway/pipes/gateway-port-tooltip.pipe'; +import { + ReportStrategyComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component'; +import { + ModbusSlaveDialogAbstract +} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract'; + +@Component({ + selector: 'tb-modbus-legacy-slave-dialog', + templateUrl: './modbus-slave-dialog.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + CommonModule, + SharedModule, + ModbusValuesComponent, + ModbusSecurityConfigComponent, + GatewayPortTooltipPipe, + ReportStrategyComponent, + ], + styleUrls: ['./modbus-slave-dialog.component.scss'], +}) +export class ModbusLegacySlaveDialogComponent extends ModbusSlaveDialogAbstract { + + constructor( + protected fb: FormBuilder, + protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: ModbusSlaveInfo, + public dialogRef: MatDialogRef, + ) { + super(fb, store, router, data, dialogRef); + } + + protected override getSlaveResultData(): LegacySlaveConfig { + const { values, type, serialPort, ...rest } = this.slaveConfigFormGroup.value; + const slaveResult = { ...rest, type, ...values }; + + if (type === ModbusProtocolType.Serial) { + slaveResult.port = serialPort; + } + + return slaveResult; + } + + + protected override addFieldsToFormGroup(): void { + this.slaveConfigFormGroup.addControl('sendDataOnlyOnChange', this.fb.control(false)); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract.ts new file mode 100644 index 0000000000..6d434591d0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract.ts @@ -0,0 +1,207 @@ +/// +/// 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 { Directive, Inject, OnDestroy } from '@angular/core'; +import { + FormBuilder, + FormControl, + UntypedFormGroup, + Validators, +} from '@angular/forms'; +import { + ModbusBaudrates, + ModbusByteSizes, + ModbusMethodLabelsMap, + ModbusMethodType, + ModbusOrderType, + ModbusParity, + ModbusParityLabelsMap, + ModbusProtocolLabelsMap, + ModbusProtocolType, + ModbusSerialMethodType, + ModbusSlaveInfo, + noLeadTrailSpacesRegex, + PortLimits, +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { Subject } from 'rxjs'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { takeUntil } from 'rxjs/operators'; +import { isEqual } from '@core/utils'; +import { helpBaseUrl } from '@shared/models/constants'; + +@Directive() +export abstract class ModbusSlaveDialogAbstract extends DialogComponent implements OnDestroy { + + slaveConfigFormGroup: UntypedFormGroup; + showSecurityControl: FormControl; + portLimits = PortLimits; + + readonly modbusProtocolTypes = Object.values(ModbusProtocolType); + readonly modbusMethodTypes = Object.values(ModbusMethodType); + readonly modbusSerialMethodTypes = Object.values(ModbusSerialMethodType); + readonly modbusParities = Object.values(ModbusParity); + readonly modbusByteSizes = ModbusByteSizes; + readonly modbusBaudrates = ModbusBaudrates; + readonly modbusOrderType = Object.values(ModbusOrderType); + readonly ModbusProtocolType = ModbusProtocolType; + readonly ModbusParityLabelsMap = ModbusParityLabelsMap; + readonly ModbusProtocolLabelsMap = ModbusProtocolLabelsMap; + readonly ModbusMethodLabelsMap = ModbusMethodLabelsMap; + readonly modbusHelpLink = + helpBaseUrl + '/docs/iot-gateway/config/modbus/#section-master-description-and-configuration-parameters'; + + private readonly serialSpecificControlKeys = ['serialPort', 'baudrate', 'stopbits', 'bytesize', 'parity', 'strict']; + private readonly tcpUdpSpecificControlKeys = ['port', 'security', 'host']; + + private destroy$ = new Subject(); + + constructor( + protected fb: FormBuilder, + protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: ModbusSlaveInfo, + public dialogRef: MatDialogRef, + ) { + super(store, router, dialogRef); + + this.showSecurityControl = this.fb.control(false); + this.initializeSlaveFormGroup(); + this.updateSlaveFormGroup(); + this.updateControlsEnabling(this.data.value.type); + this.observeTypeChange(); + this.observeShowSecurity(); + this.showSecurityControl.patchValue(!!this.data.value.security && !isEqual(this.data.value.security, {})); + } + + get protocolType(): ModbusProtocolType { + return this.slaveConfigFormGroup.get('type').value; + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + cancel(): void { + this.dialogRef.close(null); + } + + add(): void { + if (!this.slaveConfigFormGroup.valid) { + return; + } + + this.dialogRef.close(this.getSlaveResultData()); + } + + private initializeSlaveFormGroup(): void { + this.slaveConfigFormGroup = this.fb.group({ + name: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + type: [ModbusProtocolType.TCP], + host: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + port: [null, [Validators.required, Validators.min(PortLimits.MIN), Validators.max(PortLimits.MAX)]], + serialPort: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + method: [ModbusMethodType.SOCKET, [Validators.required]], + baudrate: [this.modbusBaudrates[0]], + stopbits: [1], + bytesize: [ModbusByteSizes[0]], + parity: [ModbusParity.None], + strict: [true], + unitId: [null, [Validators.required]], + deviceName: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + deviceType: ['', [Validators.required, Validators.pattern(noLeadTrailSpacesRegex)]], + timeout: [35], + byteOrder: [ModbusOrderType.BIG], + wordOrder: [ModbusOrderType.BIG], + retries: [true], + retryOnEmpty: [true], + retryOnInvalid: [true], + pollPeriod: [5000, [Validators.required]], + connectAttemptTimeMs: [5000, [Validators.required]], + connectAttemptCount: [5, [Validators.required]], + waitAfterFailedAttemptsMs: [300000, [Validators.required]], + values: [{}], + security: [{}], + }); + this.addFieldsToFormGroup(); + } + + private updateSlaveFormGroup(): void { + this.slaveConfigFormGroup.patchValue({ + ...this.data.value, + port: this.data.value.type === ModbusProtocolType.Serial ? null : this.data.value.port, + serialPort: this.data.value.type === ModbusProtocolType.Serial ? this.data.value.port : '', + values: { + attributes: this.data.value.attributes ?? [], + timeseries: this.data.value.timeseries ?? [], + attributeUpdates: this.data.value.attributeUpdates ?? [], + rpc: this.data.value.rpc ?? [], + } + }); + } + + private observeTypeChange(): void { + this.slaveConfigFormGroup.get('type').valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(type => { + this.updateControlsEnabling(type); + this.updateMethodType(type); + }); + } + + private updateMethodType(type: ModbusProtocolType): void { + if (this.slaveConfigFormGroup.get('method').value !== ModbusMethodType.RTU) { + this.slaveConfigFormGroup.get('method').patchValue( + type === ModbusProtocolType.Serial + ? ModbusSerialMethodType.ASCII + : ModbusMethodType.SOCKET, + {emitEvent: false} + ); + } + } + + private updateControlsEnabling(type: ModbusProtocolType): void { + const [enableKeys, disableKeys] = type === ModbusProtocolType.Serial + ? [this.serialSpecificControlKeys, this.tcpUdpSpecificControlKeys] + : [this.tcpUdpSpecificControlKeys, this.serialSpecificControlKeys]; + + enableKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.enable({ emitEvent: false })); + disableKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.disable({ emitEvent: false })); + + this.updateSecurityEnabling(this.showSecurityControl.value); + } + + private observeShowSecurity(): void { + this.showSecurityControl.valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(value => this.updateSecurityEnabling(value)); + } + + private updateSecurityEnabling(isEnabled: boolean): void { + if (isEnabled && this.protocolType !== ModbusProtocolType.Serial) { + this.slaveConfigFormGroup.get('security').enable({emitEvent: false}); + } else { + this.slaveConfigFormGroup.get('security').disable({emitEvent: false}); + } + } + + protected abstract addFieldsToFormGroup(): void; + protected abstract getSlaveResultData(): Config; +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html index 6304cdc0cb..d869785589 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.html @@ -227,13 +227,16 @@ -
+
{{ 'gateway.send-data-on-change' | translate }}
+ + +
@@ -345,7 +348,7 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts index 0167eaafb7..d9a9c426c2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts @@ -14,62 +14,35 @@ /// limitations under the License. /// -import { ChangeDetectionStrategy, Component, forwardRef, Inject, OnDestroy } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; import { FormBuilder, - FormControl, - NG_VALIDATORS, - NG_VALUE_ACCESSOR, - UntypedFormGroup, - Validators, } from '@angular/forms'; import { - ModbusBaudrates, - ModbusByteSizes, - ModbusMethodLabelsMap, - ModbusMethodType, - ModbusOrderType, - ModbusParity, - ModbusParityLabelsMap, - ModbusProtocolLabelsMap, ModbusProtocolType, - ModbusSerialMethodType, ModbusSlaveInfo, - noLeadTrailSpacesRegex, - PortLimits, SlaveConfig, } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { SharedModule } from '@shared/shared.module'; import { CommonModule } from '@angular/common'; -import { Subject } from 'rxjs'; import { ModbusValuesComponent } from '../modbus-values/modbus-values.component'; import { ModbusSecurityConfigComponent } from '../modbus-security-config/modbus-security-config.component'; -import { DialogComponent } from '@shared/components/dialog.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { Router } from '@angular/router'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { GatewayPortTooltipPipe } from '@home/components/widget/lib/gateway/pipes/gateway-port-tooltip.pipe'; -import { takeUntil } from 'rxjs/operators'; -import { isEqual } from '@core/utils'; -import { helpBaseUrl } from '@shared/models/constants'; +import { + ReportStrategyComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component'; +import { + ModbusSlaveDialogAbstract +} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.abstract'; @Component({ selector: 'tb-modbus-slave-dialog', templateUrl: './modbus-slave-dialog.component.html', changeDetection: ChangeDetectionStrategy.OnPush, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => ModbusSlaveDialogComponent), - multi: true - }, - { - provide: NG_VALIDATORS, - useExisting: forwardRef(() => ModbusSlaveDialogComponent), - multi: true - } - ], standalone: true, imports: [ CommonModule, @@ -77,70 +50,23 @@ import { helpBaseUrl } from '@shared/models/constants'; ModbusValuesComponent, ModbusSecurityConfigComponent, GatewayPortTooltipPipe, + ReportStrategyComponent, ], styleUrls: ['./modbus-slave-dialog.component.scss'], }) -export class ModbusSlaveDialogComponent extends DialogComponent implements OnDestroy { - - slaveConfigFormGroup: UntypedFormGroup; - showSecurityControl: FormControl; - portLimits = PortLimits; - - readonly modbusProtocolTypes = Object.values(ModbusProtocolType); - readonly modbusMethodTypes = Object.values(ModbusMethodType); - readonly modbusSerialMethodTypes = Object.values(ModbusSerialMethodType); - readonly modbusParities = Object.values(ModbusParity); - readonly modbusByteSizes = ModbusByteSizes; - readonly modbusBaudrates = ModbusBaudrates; - readonly modbusOrderType = Object.values(ModbusOrderType); - readonly ModbusProtocolType = ModbusProtocolType; - readonly ModbusParityLabelsMap = ModbusParityLabelsMap; - readonly ModbusProtocolLabelsMap = ModbusProtocolLabelsMap; - readonly ModbusMethodLabelsMap = ModbusMethodLabelsMap; - readonly modbusHelpLink = - helpBaseUrl + '/docs/iot-gateway/config/modbus/#section-master-description-and-configuration-parameters'; - - private readonly serialSpecificControlKeys = ['serialPort', 'baudrate', 'stopbits', 'bytesize', 'parity', 'strict']; - private readonly tcpUdpSpecificControlKeys = ['port', 'security', 'host']; - - private destroy$ = new Subject(); +export class ModbusSlaveDialogComponent extends ModbusSlaveDialogAbstract { constructor( - private fb: FormBuilder, + protected fb: FormBuilder, protected store: Store, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: ModbusSlaveInfo, public dialogRef: MatDialogRef, ) { - super(store, router, dialogRef); - - this.showSecurityControl = this.fb.control(false); - this.initializeSlaveFormGroup(); - this.updateSlaveFormGroup(); - this.updateControlsEnabling(this.data.value.type); - this.observeTypeChange(); - this.observeShowSecurity(); - this.showSecurityControl.patchValue(!!this.data.value.security && !isEqual(this.data.value.security, {})); - } - - get protocolType(): ModbusProtocolType { - return this.slaveConfigFormGroup.get('type').value; - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - cancel(): void { - this.dialogRef.close(null); + super(fb, store, router, data, dialogRef); } - add(): void { - if (!this.slaveConfigFormGroup.valid) { - return; - } - + protected override getSlaveResultData(): SlaveConfig { const { values, type, serialPort, ...rest } = this.slaveConfigFormGroup.value; const slaveResult = { ...rest, type, ...values }; @@ -148,97 +74,14 @@ export class ModbusSlaveDialogComponent extends DialogComponent { - this.updateControlsEnabling(type); - this.updateMethodType(type); - }); - } - - private updateMethodType(type: ModbusProtocolType): void { - if (this.slaveConfigFormGroup.get('method').value !== ModbusMethodType.RTU) { - this.slaveConfigFormGroup.get('method').patchValue( - type === ModbusProtocolType.Serial - ? ModbusSerialMethodType.ASCII - : ModbusMethodType.SOCKET, - {emitEvent: false} - ); + if (!slaveResult.reportStrategy) { + delete slaveResult.reportStrategy; } - } - - private updateControlsEnabling(type: ModbusProtocolType): void { - const [enableKeys, disableKeys] = type === ModbusProtocolType.Serial - ? [this.serialSpecificControlKeys, this.tcpUdpSpecificControlKeys] - : [this.tcpUdpSpecificControlKeys, this.serialSpecificControlKeys]; - - enableKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.enable({ emitEvent: false })); - disableKeys.forEach(key => this.slaveConfigFormGroup.get(key)?.disable({ emitEvent: false })); - this.updateSecurityEnabling(this.showSecurityControl.value); + return slaveResult; } - private observeShowSecurity(): void { - this.showSecurityControl.valueChanges - .pipe(takeUntil(this.destroy$)) - .subscribe(value => this.updateSecurityEnabling(value)); - } - - private updateSecurityEnabling(isEnabled: boolean): void { - if (isEnabled && this.protocolType !== ModbusProtocolType.Serial) { - this.slaveConfigFormGroup.get('security').enable({emitEvent: false}); - } else { - this.slaveConfigFormGroup.get('security').disable({emitEvent: false}); - } + protected override addFieldsToFormGroup(): void { + this.slaveConfigFormGroup.addControl('reportStrategy', this.fb.control(null)); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.ts index 6bfc07c130..c0824869e3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-values/modbus-values.component.ts @@ -87,6 +87,9 @@ export class ModbusValuesComponent implements ControlValueAccessor, Validator, O @coerceBoolean() @Input() singleMode = false; + @coerceBoolean() + @Input() hideNewFields = false; + disabled = false; modbusRegisterTypes: ModbusRegisterType[] = Object.values(ModbusRegisterType); modbusValueKeys = Object.values(ModbusValueKey); @@ -172,7 +175,8 @@ export class ModbusValuesComponent implements ControlValueAccessor, Validator, O panelTitle: ModbusKeysPanelTitleTranslationsMap.get(keysType), addKeyTitle: ModbusKeysAddKeyTranslationsMap.get(keysType), deleteKeyTitle: ModbusKeysDeleteKeyTranslationsMap.get(keysType), - noKeysText: ModbusKeysNoKeysTextTranslationsMap.get(keysType) + noKeysText: ModbusKeysNoKeysTextTranslationsMap.get(keysType), + hideNewFields: this.hideNewFields, }; const dataKeysPanelPopover = this.popoverService.displayPopover( trigger, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.html new file mode 100644 index 0000000000..dec8292720 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.html @@ -0,0 +1,57 @@ + +
+ + + + + + {{ 'gateway.report-strategy.label' | translate }} + + + + + + + +
gateway.report-strategy.label
+ +
+ +
+
{{ 'gateway.type' | translate }}
+ + + {{ ReportTypeTranslateMap.get(type) | translate }} + + +
+
+
+ + gateway.report-strategy.report-period + +
+
+ + + +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.ts new file mode 100644 index 0000000000..a4320f4a3d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.ts @@ -0,0 +1,168 @@ +/// +/// 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 { + ChangeDetectionStrategy, + Component, + forwardRef, + Input, + OnDestroy, +} from '@angular/core'; +import { Subject } from 'rxjs'; +import { + ControlValueAccessor, + FormBuilder, + FormControl, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + UntypedFormGroup, + ValidationErrors, + Validators +} from '@angular/forms'; +import { + ReportStrategyConfig, + ReportStrategyType, + ReportStrategyTypeTranslationsMap +} from '@home/components/widget/lib/gateway/gateway-widget.models'; +import { filter, takeUntil } from 'rxjs/operators'; +import { SharedModule } from '@shared/shared.module'; +import { CommonModule } from '@angular/common'; +import { + ModbusSecurityConfigComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component'; +import { coerceBoolean } from '@shared/decorators/coercion'; + +@Component({ + selector: 'tb-report-strategy', + templateUrl: './report-strategy.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ReportStrategyComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => ReportStrategyComponent), + multi: true + } + ], + standalone: true, + imports: [ + CommonModule, + SharedModule, + ModbusSecurityConfigComponent, + ] +}) +export class ReportStrategyComponent implements ControlValueAccessor, OnDestroy { + + @coerceBoolean() + @Input() isExpansionMode = false; + + reportStrategyFormGroup: UntypedFormGroup; + showStrategyControl: FormControl; + + readonly reportStrategyTypes = Object.values(ReportStrategyType); + readonly ReportTypeTranslateMap = ReportStrategyTypeTranslationsMap; + readonly ReportStrategyType = ReportStrategyType; + + private onChange: (value: ReportStrategyConfig) => void; + private onTouched: () => void; + + private destroy$ = new Subject(); + + constructor(private fb: FormBuilder) { + this.showStrategyControl = this.fb.control(false); + + this.reportStrategyFormGroup = this.fb.group({ + type: [ReportStrategyType.OnReportPeriod, []], + reportPeriod: [5000, [Validators.required]], + }); + + this.observeStrategyFormChange(); + this.observeStrategyToggle(); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + writeValue(reportStrategyConfig: ReportStrategyConfig): void { + if (this.isExpansionMode) { + this.showStrategyControl.setValue(!!reportStrategyConfig, {emitEvent: false}); + } + const { type = ReportStrategyType.OnReportPeriod, reportPeriod = 5000 } = reportStrategyConfig ?? {}; + this.reportStrategyFormGroup.setValue({ type, reportPeriod }, {emitEvent: false}); + this.onTypeChange(type); + } + + validate(): ValidationErrors | null { + return this.reportStrategyFormGroup.valid || this.reportStrategyFormGroup.disabled ? null : { + reportStrategyForm: { valid: false } + }; + } + + registerOnChange(fn: (value: ReportStrategyConfig) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + private observeStrategyFormChange(): void { + this.reportStrategyFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value) => { + this.onChange(value); + this.onTouched(); + }); + + this.reportStrategyFormGroup.get('type').valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(type => this.onTypeChange(type)); + } + + private observeStrategyToggle(): void { + this.showStrategyControl.valueChanges + .pipe(takeUntil(this.destroy$), filter(() => this.isExpansionMode)) + .subscribe(enable => { + if (enable) { + this.reportStrategyFormGroup.enable({emitEvent: false}); + this.reportStrategyFormGroup.get('reportPeriod').addValidators(Validators.required); + this.onChange(this.reportStrategyFormGroup.value); + } else { + this.reportStrategyFormGroup.disable({emitEvent: false}); + this.reportStrategyFormGroup.get('reportPeriod').removeValidators(Validators.required); + this.onChange(null); + } + this.reportStrategyFormGroup.updateValueAndValidity({emitEvent: false}); + }); + } + + private onTypeChange(type: ReportStrategyType): void { + const reportPeriodControl = this.reportStrategyFormGroup.get('reportPeriod'); + const isDisabled = reportPeriodControl.disabled; + + if (type === ReportStrategyType.OnChange && !isDisabled) { + reportPeriodControl.disable({emitEvent: false}); + } else if (isDisabled) { + reportPeriodControl.enable({emitEvent: false}); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html index 7c495d34f1..6a054a4000 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html @@ -299,5 +299,6 @@ + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts index 04dd274f81..dcc473ce71 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.ts @@ -297,12 +297,13 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie } private hasSameConfig(sharedDataConfigJson: ConnectorBaseInfo, connectorDataConfigJson: ConnectorBaseInfo): boolean { - const { name, id, enableRemoteLogging, logLevel, ...sharedDataConfig } = sharedDataConfigJson; + const { name, id, enableRemoteLogging, logLevel, reportStrategy, ...sharedDataConfig } = sharedDataConfigJson; const { name: connectorName, id: connectorId, enableRemoteLogging: connectorEnableRemoteLogging, logLevel: connectorLogLevel, + reportStrategy: connectorReportStrategy, ...connectorConfig } = connectorDataConfigJson; @@ -351,7 +352,8 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie configuration: '', configurationJson: {}, basicConfig: {}, - configVersion: '' + configVersion: '', + reportStrategy: [{ value: {}, disabled: true }], }, {emitEvent: false}); this.connectorForm.markAsPristine(); } @@ -542,6 +544,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie configurationJson: [{}, [Validators.required]], basicConfig: [{}], configVersion: [''], + reportStrategy: [{ value: {}, disabled: true }], }); this.connectorForm.disable(); } @@ -751,6 +754,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie } private updateConnector(connector: GatewayConnector): void { + this.toggleReportStrategy(connector.type); switch (connector.type) { case ConnectorType.MQTT: case ConnectorType.OPCUA: @@ -770,6 +774,15 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie this.createJsonConfigWatcher(); } + private toggleReportStrategy(type: ConnectorType): void { + const reportStrategyControl = this.connectorForm.get('reportStrategy'); + if (type === ConnectorType.MODBUS) { + reportStrategyControl.enable({emitEvent: false}); + } else { + reportStrategyControl.disable({emitEvent: false}); + } + } + private setClientData(data: PageData): void { if (this.initialConnector) { const clientConnectorData = data.data.find(attr => attr.key === this.initialConnector.name); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts index a2184089b1..45f8d3cc54 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-widget.models.ts @@ -208,6 +208,7 @@ export interface ConnectorBaseInfo { id: string; enableRemoteLogging: boolean; logLevel: GatewayLogLevel; + reportStrategy?: ReportStrategyConfig; } export type MQTTBasicConfig = MQTTBasicConfig_v3_5_2 | MQTTLegacyBasicConfig; @@ -254,7 +255,7 @@ export interface ModbusBasicConfig_v3_5_2 { } export interface ModbusLegacyBasicConfig { - master: ModbusMasterConfig; + master: ModbusMasterConfig; slave: ModbusLegacySlave; } @@ -588,9 +589,10 @@ export interface MappingInfo { buttonTitle: string; } -export interface ModbusSlaveInfo { - value: SlaveConfig; +export interface ModbusSlaveInfo { + value: Slave; buttonTitle: string; + hideNewFields: boolean; } export enum ConfigurationModes { @@ -604,6 +606,20 @@ export enum SecurityType { CERTIFICATES = 'certificates' } +export enum ReportStrategyType { + OnChange = 'ON_CHANGE', + OnReportPeriod = 'ON_REPORT_PERIOD', + OnChangeOrReportPeriod = 'ON_CHANGE_OR_REPORT_PERIOD' +} + +export const ReportStrategyTypeTranslationsMap = new Map( + [ + [ReportStrategyType.OnChange, 'gateway.report-strategy.on-change'], + [ReportStrategyType.OnReportPeriod, 'gateway.report-strategy.on-report-period'], + [ReportStrategyType.OnChangeOrReportPeriod, 'gateway.report-strategy.on-change-or-report-period'] + ] +); + export enum ModeType { NONE = 'None', SIGN = 'Sign', @@ -1097,8 +1113,12 @@ export const ModbusKeysNoKeysTextTranslationsMap = new Map { + slaves: Slave[]; +} + +export interface LegacySlaveConfig extends Omit { + sendDataOnlyOnChange: boolean; } export interface SlaveConfig { @@ -1118,7 +1138,7 @@ export interface SlaveConfig { unitId: number; deviceName: string; deviceType?: string; - sendDataOnlyOnChange: boolean; + reportStrategy: ReportStrategyConfig; connectAttemptTimeMs: number; connectAttemptCount: number; waitAfterFailedAttemptsMs: number; @@ -1141,6 +1161,7 @@ export interface ModbusValue { objectsCount: number; address: number; value?: string; + reportStrategy?: ReportStrategyConfig; } export interface ModbusSecurity { @@ -1205,4 +1226,9 @@ export interface ModbusIdentity { modelName?: string; } +export interface ReportStrategyConfig { + type: ReportStrategyType; + reportPeriod?: number; +} + export const ModbusBaudrates = [4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600]; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts index 3f34d49ab2..eb1ba9033f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/utils/modbus-version-mapping.util.ts @@ -15,6 +15,7 @@ /// import { + LegacySlaveConfig, ModbusDataType, ModbusLegacyRegisterValues, ModbusLegacySlave, @@ -23,17 +24,36 @@ import { ModbusSlave, ModbusValue, ModbusValues, + ReportStrategyType, SlaveConfig } from '@home/components/widget/lib/gateway/gateway-widget.models'; export class ModbusVersionMappingUtil { - static mapMasterToUpgradedVersion(master: ModbusMasterConfig): ModbusMasterConfig { + static mapMasterToUpgradedVersion(master: ModbusMasterConfig): ModbusMasterConfig { return { - slaves: master.slaves.map((slave: SlaveConfig) => ({ - ...slave, - deviceType: slave.deviceType ?? 'default', - })) + slaves: master.slaves.map((slave: LegacySlaveConfig) => { + const { sendDataOnlyOnChange, ...restSlave } = slave; + return { + ...restSlave, + deviceType: slave.deviceType ?? 'default', + reportStrategy: sendDataOnlyOnChange + ? { type: ReportStrategyType.OnChange } + : { type: ReportStrategyType.OnReportPeriod, reportPeriod: slave.pollPeriod } + }; + }) + }; + } + + static mapMasterToDowngradedVersion(master: ModbusMasterConfig): ModbusMasterConfig { + return { + slaves: master.slaves.map((slave: SlaveConfig) => { + const { reportStrategy, ...restSlave } = slave; + return { + ...restSlave, + sendDataOnlyOnChange: reportStrategy?.type !== ReportStrategyType.OnReportPeriod + }; + }) }; } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index f241bc91d0..e809c22b25 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts @@ -160,6 +160,9 @@ import { import { ModbusLegacyBasicConfigComponent } from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-basic-config/modbus-legacy-basic-config.component'; +import { + ReportStrategyComponent +} from '@home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component'; @NgModule({ declarations: [ @@ -253,6 +256,7 @@ import { GatewayAdvancedConfigurationComponent, OpcUaLegacyBasicConfigComponent, ModbusLegacyBasicConfigComponent, + ReportStrategyComponent, ], exports: [ EntitiesTableWidgetComponent, 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 f5f26beeeb..a06896e9e9 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -3348,6 +3348,13 @@ "memory-storage": "Memory storage", "sqlite": "SQLITE" }, + "report-strategy": { + "label": "Report strategy", + "on-change": "On value change", + "on-report-period": "On report period", + "on-change-or-report-period": "On value change or report period", + "report-period": "Report period" + }, "source-type": { "msg": "Extract from message", "topic": "Extract from topic", diff --git a/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json b/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json index 38bb44e036..fbace65d78 100644 --- a/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json +++ b/ui-ngx/src/assets/metadata/connector-default-configs/modbus.json @@ -18,7 +18,6 @@ "unitId": 1, "deviceName": "Temp Sensor", "deviceType": "default", - "sendDataOnlyOnChange": true, "connectAttemptTimeMs": 5000, "connectAttemptCount": 5, "waitAfterFailedAttemptsMs": 300000, From 041ba57a08ced5fa1656b7aa396e7435b8678430 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Wed, 18 Sep 2024 15:47:44 +0300 Subject: [PATCH 04/21] [4421] fixed toggle bug --- .../report-strategy/report-strategy.component.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.ts index a4320f4a3d..fb0819d119 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component.ts @@ -89,8 +89,8 @@ export class ReportStrategyComponent implements ControlValueAccessor, OnDestroy this.showStrategyControl = this.fb.control(false); this.reportStrategyFormGroup = this.fb.group({ - type: [ReportStrategyType.OnReportPeriod, []], - reportPeriod: [5000, [Validators.required]], + type: [{ value: ReportStrategyType.OnReportPeriod, disabled: true }, []], + reportPeriod: [{ value: 5000, disabled: true }, [Validators.required]], }); this.observeStrategyFormChange(); @@ -106,6 +106,9 @@ export class ReportStrategyComponent implements ControlValueAccessor, OnDestroy if (this.isExpansionMode) { this.showStrategyControl.setValue(!!reportStrategyConfig, {emitEvent: false}); } + if (reportStrategyConfig) { + this.reportStrategyFormGroup.enable({emitEvent: false}); + } const { type = ReportStrategyType.OnReportPeriod, reportPeriod = 5000 } = reportStrategyConfig ?? {}; this.reportStrategyFormGroup.setValue({ type, reportPeriod }, {emitEvent: false}); this.onTypeChange(type); @@ -157,11 +160,10 @@ export class ReportStrategyComponent implements ControlValueAccessor, OnDestroy private onTypeChange(type: ReportStrategyType): void { const reportPeriodControl = this.reportStrategyFormGroup.get('reportPeriod'); - const isDisabled = reportPeriodControl.disabled; - if (type === ReportStrategyType.OnChange && !isDisabled) { + if (type === ReportStrategyType.OnChange) { reportPeriodControl.disable({emitEvent: false}); - } else if (isDisabled) { + } else if (!this.isExpansionMode || this.showStrategyControl.value) { reportPeriodControl.enable({emitEvent: false}); } } From 7711fe811f66aceac177f2bad01d056eac722140 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Wed, 18 Sep 2024 16:17:04 +0300 Subject: [PATCH 05/21] [4421] minor refactoring --- .../modbus-master-table/modbus-master-table.component.ts | 2 +- .../widget/lib/gateway/gateway-connectors.component.html | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts index 8f1761dd87..71bbc76c75 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-master-table/modbus-master-table.component.ts @@ -41,7 +41,7 @@ import { import { LegacySlaveConfig, ModbusMasterConfig, - ModbusProtocolLabelsMap, ModbusSlave, + ModbusProtocolLabelsMap, ModbusSlaveInfo, ModbusValues, SlaveConfig diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html index 6a054a4000..124b698612 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html @@ -299,6 +299,9 @@ - + From 8d13785051dbe644df8c9c17ce963742f8974d81 Mon Sep 17 00:00:00 2001 From: mpetrov Date: Mon, 23 Sep 2024 12:48:56 +0300 Subject: [PATCH 06/21] Refactored logic: Customer - Version Conflict - Export --- .../src/app/shared/import-export/import-export.service.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/shared/import-export/import-export.service.ts b/ui-ngx/src/app/shared/import-export/import-export.service.ts index 819a47778a..3963a81c1e 100644 --- a/ui-ngx/src/app/shared/import-export/import-export.service.ts +++ b/ui-ngx/src/app/shared/import-export/import-export.service.ts @@ -363,6 +363,7 @@ export class ImportExportService { public exportEntity(entityData: EntityInfoData | RuleChainMetaData): void { const id = (entityData as EntityInfoData).id ?? (entityData as RuleChainMetaData).ruleChainId; + let fileName = (entityData as EntityInfoData).name; let preparedData; switch (id.entityType) { case EntityType.DEVICE_PROFILE: @@ -389,13 +390,13 @@ export class ImportExportService { preparedData = this.prepareDashboardExport(entityData as Dashboard); break; case EntityType.CUSTOMER: - preparedData = this.prepareExport({...entityData, name: (entityData as Customer).title}); - (entityData as EntityInfoData).name = (entityData as Customer).title; + fileName = (entityData as Customer).title; + preparedData = this.prepareExport(entityData); break; default: preparedData = this.prepareExport(entityData); } - this.exportToPc(preparedData, (entityData as EntityInfoData).name); + this.exportToPc(preparedData, fileName); } private exportSelectedWidgetsBundle(widgetsBundle: WidgetsBundle): void { From 221e17789a64e7be9814b70389480141e2ec0c43 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 23 Sep 2024 12:59:15 +0300 Subject: [PATCH 07/21] UI: SCADA symbol editor: fix tag settings tooltip to correctly update add/edit function buttons. --- .../scada-symbol-tooltip.components.ts | 49 ++++++++++++------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts index ef811e9746..7114712848 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts @@ -225,6 +225,8 @@ class ScadaSymbolTagPanelComponent extends ScadaSymbolPanelComponent implements displayTagSettings = true; + private scadaSymbolTagSettingsTooltip: ITooltipsterInstance; + constructor(public element: ElementRef, private container: ViewContainerRef) { super(element); @@ -242,23 +244,9 @@ class ScadaSymbolTagPanelComponent extends ScadaSymbolPanelComponent implements if (this.displayTagSettings) { const tagSettingsButton = $(this.tagSettingsButton.nativeElement); - tagSettingsButton.tooltipster( - { - parent: this.symbolElement.tooltipContainer, - zIndex: 200, - arrow: true, - theme: ['scada-symbol', 'tb-active'], - interactive: true, - trigger: 'click', - trackOrigin: true, - trackerInterval: 100, - side: 'top', - content: '' - } - ); - - const scadaSymbolTagSettingsTooltip = tagSettingsButton.tooltipster('instance'); - setTooltipComponent(this.symbolElement, this.container, ScadaSymbolTagSettingsComponent, scadaSymbolTagSettingsTooltip); + tagSettingsButton.on('click', () => { + this.createTagSettingsTooltip(); + }); } if (!this.symbolElement.readonly) { @@ -295,6 +283,33 @@ class ScadaSymbolTagPanelComponent extends ScadaSymbolPanelComponent implements }); } + private createTagSettingsTooltip() { + if (!this.scadaSymbolTagSettingsTooltip) { + const tagSettingsButton = $(this.tagSettingsButton.nativeElement); + tagSettingsButton.tooltipster( + { + parent: this.symbolElement.tooltipContainer, + zIndex: 200, + arrow: true, + theme: ['scada-symbol', 'tb-active'], + interactive: true, + trigger: 'click', + trackOrigin: true, + trackerInterval: 100, + side: 'top', + content: '', + functionAfter: () => { + this.scadaSymbolTagSettingsTooltip.destroy(); + this.scadaSymbolTagSettingsTooltip = null; + } + } + ); + this.scadaSymbolTagSettingsTooltip = tagSettingsButton.tooltipster('instance'); + setTooltipComponent(this.symbolElement, this.container, ScadaSymbolTagSettingsComponent, this.scadaSymbolTagSettingsTooltip); + this.scadaSymbolTagSettingsTooltip.open(); + } + } + public onUpdateTag() { this.updateTag.emit(); } From 8d59917ba53e1c2707ed286642618216c6876268 Mon Sep 17 00:00:00 2001 From: rusikv Date: Mon, 23 Sep 2024 13:32:41 +0300 Subject: [PATCH 08/21] UI: fixed coping of instances when deleting lwm2m objects --- ...wm2m-device-profile-transport-configuration.component.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts index 0bda4c08c2..08e1de73f2 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts @@ -530,8 +530,10 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro this.removeObserveAttrTelemetryFromJson(ATTRIBUTE, value.keyId); this.removeKeyNameFromJson(value.keyId); this.removeAttributesFromJson(value.keyId); - this.updateObserveAttrTelemetryObjectFormGroup(objectsOld); - } + this.lwm2mDeviceProfileFormGroup.patchValue({ + observeAttrTelemetry: deepClone(objectsOld) + }, {emitEvent: false}); + }; private removeObserveAttrTelemetryFromJson = (observeAttrTel: string, keyId: string): void => { const isIdIndex = (element) => element.startsWith(`/${keyId}`); From ea180219f6cf485eae69978d0f50453772e7c5bf Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 23 Sep 2024 13:36:24 +0300 Subject: [PATCH 09/21] UI: Fix widget edit tooltip positioning. --- .../components/widget/widget-container.component.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts index 5c32a3ec30..7c8e3c12ea 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts @@ -283,17 +283,9 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O functionPosition: (instance, helper, position) => { const clientRect = helper.origin.getBoundingClientRect(); const container = parent.getBoundingClientRect(); - position.coord.left = clientRect.right - position.size.width - container.left; + position.coord.left = Math.max(0,clientRect.right - position.size.width - container.left); position.coord.top = position.coord.top - container.top; position.target = clientRect.right; - const rightOverflow = container.right - (position.coord.left + position.size.width); - if (rightOverflow < 0) { - position.coord.left += rightOverflow; - } - const leftOverflow = container.left - position.coord.left; - if (leftOverflow > 0) { - position.coord.left += leftOverflow; - } return position; }, functionReady: (_instance, helper) => { From 5fd9b5fcc49eb44ef5ebdb4f51b2241c229ee57a Mon Sep 17 00:00:00 2001 From: rusikv Date: Mon, 23 Sep 2024 13:41:12 +0300 Subject: [PATCH 10/21] UI: removed unused style class --- ui-ngx/src/styles.scss | 3 --- 1 file changed, 3 deletions(-) diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index 7a49333984..969cb431e6 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -912,9 +912,6 @@ mat-icon { &.tb-mat-28 { @include tb-mat-icon-size(28); } - &.tb-mat-30 { - @include tb-mat-icon-size(30); - } &.tb-mat-32 { @include tb-mat-icon-size(32); } From 027d4eeeab00c18b1e5e736535447323883ef89c Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Mon, 23 Sep 2024 14:43:22 +0300 Subject: [PATCH 11/21] UI: Refactoring for required --- .../src/app/shared/components/entity/entity-list.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts index a9aae0c9ce..cfb0f19b1b 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts @@ -210,7 +210,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV } validate(): ValidationErrors | null { - return isArray(this.modelValue) && this.modelValue.length ? null : { + return (isArray(this.modelValue) && this.modelValue.length) || !this.required ? null : { entities: {valid: false} }; } From 185526be85b2e0d65518e9284ae6b2a7bafa18d3 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Mon, 23 Sep 2024 15:08:46 +0200 Subject: [PATCH 12/21] 2.17.2 2.17.2 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index aa2a50bc8b..6d0fa6f090 100755 --- a/pom.xml +++ b/pom.xml @@ -69,8 +69,8 @@ 4.5.14 4.4.16 2.12.7 - 2.17.0 - 2.17.0 + 2.17.2 + 2.17.2 1.7.0 4.4.0 2.2.14 From c8a39f979d051921d28a939c978aba3ff4248b1d Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 23 Sep 2024 17:48:07 +0300 Subject: [PATCH 13/21] UI: Fixed select widget in touch screen --- .../widget/widget-container.component.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts index 5c32a3ec30..231dedceee 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts @@ -200,6 +200,9 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O } onMouseDown(event: MouseEvent) { + if (event) { + event.stopPropagation(); + } this.widgetComponentAction.emit({ event, actionType: WidgetComponentActionType.MOUSE_DOWN @@ -207,6 +210,9 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O } onClicked(event: MouseEvent) { + if (event) { + event.stopPropagation(); + } this.widgetComponentAction.emit({ event, actionType: WidgetComponentActionType.CLICKED @@ -214,6 +220,9 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O } onContextMenu(event: MouseEvent) { + if (event) { + event.stopPropagation(); + } this.widgetComponentAction.emit({ event, actionType: WidgetComponentActionType.CONTEXT_MENU @@ -221,6 +230,9 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O } onEdit(event: MouseEvent) { + if (event) { + event.stopPropagation(); + } this.widgetComponentAction.emit({ event, actionType: WidgetComponentActionType.EDIT @@ -228,6 +240,9 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O } onReplaceReferenceWithWidgetCopy(event: MouseEvent) { + if (event) { + event.stopPropagation(); + } this.widgetComponentAction.emit({ event, actionType: WidgetComponentActionType.REPLACE_REFERENCE_WITH_WIDGET_COPY @@ -235,6 +250,9 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O } onExport(event: MouseEvent) { + if (event) { + event.stopPropagation(); + } this.widgetComponentAction.emit({ event, actionType: WidgetComponentActionType.EXPORT @@ -242,6 +260,9 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O } onRemove(event: MouseEvent) { + if (event) { + event.stopPropagation(); + } this.widgetComponentAction.emit({ event, actionType: WidgetComponentActionType.REMOVE From c9bd23b9e7f7c66a5de9f63cc14b6ba7de1cfa38 Mon Sep 17 00:00:00 2001 From: rusikv Date: Mon, 23 Sep 2024 18:59:54 +0300 Subject: [PATCH 14/21] UI: fixed translation with pluralization for Dutch Belgium language --- .../assets/locale/locale.constant-lt_LT.json | 8 +- .../assets/locale/locale.constant-nl_BE.json | 386 +++++++++--------- .../assets/locale/locale.constant-pl_PL.json | 8 +- 3 files changed, 201 insertions(+), 201 deletions(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-lt_LT.json b/ui-ngx/src/assets/locale/locale.constant-lt_LT.json index 23e0682d6e..df1659e112 100644 --- a/ui-ngx/src/assets/locale/locale.constant-lt_LT.json +++ b/ui-ngx/src/assets/locale/locale.constant-lt_LT.json @@ -1826,9 +1826,10 @@ "condition-duration-value-required": "Duration value is required.", "condition-duration-time-unit-required": "Time unit is required.", "advanced-settings": "Advanced settings", - "alarm-rule-details": "Details", - "alarm-rule-details-hint": "Hint: use ${keyName} to substitute values of the attribute or telemetry keys that are used in alarm rule condition.", - "add-alarm-rule-details": "Add details", + "alarm-rule-additional-info": "Papildoma informacija", + "edit-alarm-rule-additional-info": "Redaguoti papildomą informaciją", + "alarm-rule-additional-info-placeholder": "Pateikite savo komentarus ir patikslinimus čia, kad jie būtų rodomi pavojaus signalo detalių skiltyje „Papildoma informacija“.", + "alarm-rule-additional-info-hint": "Hint: use ${keyName} to substitute values of the attribute or telemetry keys that are used in alarm rule condition.", "alarm-rule-mobile-dashboard": "Mobile dashboard", "alarm-rule-mobile-dashboard-hint": "Used by mobile application as an alarm details dashboard", "alarm-rule-no-mobile-dashboard": "No dashboard selected", @@ -1838,7 +1839,6 @@ "propagate-alarm-to-owner": "Propagate alarm to entity owner (Customer or Tenant)", "propagate-alarm-to-owner-hierarchy": "Propagate alarm to entity owners hierarchy", "propagate-alarm-to-tenant": "Propagate alarm to Tenant", - "alarm-details": "Alarm details", "alarm-rule-condition": "Alarm rule condition", "enter-alarm-rule-condition-prompt": "Please add alarm rule condition", "edit-alarm-rule-condition": "Edit alarm rule condition", diff --git a/ui-ngx/src/assets/locale/locale.constant-nl_BE.json b/ui-ngx/src/assets/locale/locale.constant-nl_BE.json index 2188379a4b..afe23e039a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-nl_BE.json +++ b/ui-ngx/src/assets/locale/locale.constant-nl_BE.json @@ -509,17 +509,17 @@ "acknowledge": "Erkennen", "clear": "Duidelijk", "search": "Alarmen zoeken", - "selected-alarms": "{ count, plural, =1 {1 alarm} andere {# alarms} } geselecteerd", + "selected-alarms": "{ count, plural, =1 {1 alarm} other {# alarms} } geselecteerd", "no-data": "Geen gegevens om weer te geven", "polling-interval": "Polling-interval alarmen (sec)", "polling-interval-required": "Alarmen polling-interval is vereist.", "min-polling-interval-message": "Minimaal 1 sec polling-interval is toegestaan.", - "aknowledge-alarms-title": "Erken { count, plural, =1 {1 alarm} andere {# alarms} }", - "aknowledge-alarms-text": "Weet je zeker dat je { count, plural, =1 {1 alarm} andere {# alarms} } wilt erkennen?", + "aknowledge-alarms-title": "Erken { count, plural, =1 {1 alarm} other {# alarms} }", + "aknowledge-alarms-text": "Weet je zeker dat je { count, plural, =1 {1 alarm} other {# alarms} } wilt erkennen?", "aknowledge-alarm-title": "Alarm bevestigen", "aknowledge-alarm-text": "Weet u zeker dat u Alarm wilt erkennen?", - "clear-alarms-title": "Wis { count, plural, =1 {1 alarm} andere {# alarms} }", - "clear-alarms-text": "Weet u zeker dat u { count, plural, =1 {1 alarm} andere {# alarms} } wilt wissen?", + "clear-alarms-title": "Wis { count, plural, =1 {1 alarm} other {# alarms} }", + "clear-alarms-text": "Weet u zeker dat u { count, plural, =1 {1 alarm} other {# alarms} } wilt wissen?", "clear-alarm-title": "Alarm wissen", "clear-alarm-text": "Weet u zeker dat u Alarm wilt wissen?", "alarm-status-filter": "Alarm Status Filter", @@ -670,17 +670,17 @@ "add-asset-text": "Nieuw asset toevoegen", "asset-details": "Details van het bedrijfsmiddel", "assign-assets": "Middelen toewijzen", - "assign-assets-text": "Wijs { count, plural, =1 {1 asset} andere {# assets} } toe aan de klant", + "assign-assets-text": "Wijs { count, plural, =1 {1 asset} other {# assets} } toe aan de klant", "assign-asset-to-edge-title": "Asset(s) toewijzen aan Edge", "assign-asset-to-edge-text": "Selecteer de assets die u aan de edge wilt toewijzen", "delete-assets": "Assets verwijderen", "unassign-assets": "Toewijzing van assets ongedaan maken", - "unassign-assets-action-title": "Toewijzing { count, plural, =1 {1 asset} andere {# assets} } van klant ongedaan maken", + "unassign-assets-action-title": "Toewijzing { count, plural, =1 {1 asset} other {# assets} } van klant ongedaan maken", "assign-new-asset": "Nieuw asset toewijzen", "delete-asset-title": "Weet je zeker dat je de asset '{{assetName}}' wilt verwijderen?", "delete-asset-text": "Opgelet, na de bevestiging worden de asset en alle gerelateerde gegevens onherstelbaar.", - "delete-assets-title": "Weet u zeker dat u { count, plural, =1 {1 asset} andere {# assets} } wilt verwijderen?", - "delete-assets-action-title": "Verwijder { count, plural, =1 {1 asset} andere {# assets} }", + "delete-assets-title": "Weet u zeker dat u { count, plural, =1 {1 asset} other {# assets} } wilt verwijderen?", + "delete-assets-action-title": "Verwijder { count, plural, =1 {1 asset} other {# assets} }", "delete-assets-text": "Opgelet, na de bevestiging worden alle geselecteerde assets verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld.", "make-public-asset-title": "Weet je zeker dat je de asset '{{assetName}}' openbaar wilt maken?", "make-public-asset-text": "Na de bevestiging worden de asset en alle bijbehorende gegevens openbaar en toegankelijk gemaakt voor anderen.", @@ -689,7 +689,7 @@ "unassign-asset-title": "Weet je zeker dat je de toewijzing van de asset '{{assetName}}' ongedaan wilt maken?", "unassign-asset-text": "Na de bevestiging wordt de toewijzing van de asset ongedaan gemaakt en is het niet toegankelijk voor de klant.", "unassign-asset": "Toewijzing van asset ongedaan maken", - "unassign-assets-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 asset} andere {# assets} } wilt opheffen?", + "unassign-assets-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 asset} other {# assets} } wilt opheffen?", "unassign-assets-text": "Na de bevestiging worden alle geselecteerde assets ongedaan gemaakt en zijn ze niet toegankelijk voor de klant.", "copyId": "Item-ID kopiëren", "idCopiedMessage": "Item-ID is gekopieerd naar het klembord", @@ -701,9 +701,9 @@ "search": "Assets zoeken", "select-group-to-add": "Selecteer doelgroep om geselecteerde assets toe te voegen", "select-group-to-move": "Selecteer de doelgroep om geselecteerde assets te verplaatsen", - "remove-assets-from-group": "Weet je zeker dat je { count, plural, =1 {1 asset} andere {# assets} } uit groep '{{entityGroup}}' wilt verwijderen?", + "remove-assets-from-group": "Weet je zeker dat je { count, plural, =1 {1 asset} other {# assets} } uit groep '{{entityGroup}}' wilt verwijderen?", "group": "Groep van assets", - "list-of-groups": "{ count, plural, =1 {One asset group} andere {List of # asset groups} }", + "list-of-groups": "{ count, plural, =1 {One asset group} other {List of # asset groups} }", "group-name-starts-with": "Asset groepen waarvan de naam begint met '{{prefix}}'", "import": "Asset importeren", "asset-file": "Asset bestand", @@ -712,9 +712,9 @@ "unassign-asset-from-edge": "Toewijzing van asset ongedaan maken", "unassign-asset-from-edge-title": "Weet je zeker dat je de toewijzing van de asset '{{assetName}}' ongedaan wilt maken?", "unassign-asset-from-edge-text": "Na de bevestiging wordt de toewijzing van de asset ongedaan gemaakt en is het niet toegankelijk via de edge.", - "unassign-assets-from-edge-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 asset} andere {# assets} } wilt opheffen?", + "unassign-assets-from-edge-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 asset} other {# assets} } wilt opheffen?", "unassign-assets-from-edge-text": "Na de bevestiging worden alle geselecteerde assets ongedaan gemaakt en zijn ze niet toegankelijk via de edge.", - "selected-assets": "{ count, plural, =1 {1 asset} andere {# assets} } geselecteerd" + "selected-assets": "{ count, plural, =1 {1 asset} other {# assets} } geselecteerd" }, "attribute": { "attributes": "Attributen", @@ -732,7 +732,7 @@ "key-required": "Attribuutsleutel is vereist.", "value": "Waarde", "value-required": "Kenmerkwaarde is vereist.", - "delete-attributes-title": "Weet u zeker dat u { count, plural, =1 {1 attribute} andere {# attributes} } wilt verwijderen?", + "delete-attributes-title": "Weet u zeker dat u { count, plural, =1 {1 attribute} other {# attributes} } wilt verwijderen?", "delete-attributes-text": "Opgelet, na de bevestiging worden alle geselecteerde attributen verwijderd.", "delete-attributes": "Attributen verwijderen", "enter-attribute-value": "Kenmerkwaarde invoeren", @@ -742,8 +742,8 @@ "prev-widget": "Vorige widget", "add-to-dashboard": "Toevoegen aan dashboard", "add-widget-to-dashboard": "Widget toevoegen aan dashboard", - "selected-attributes": "{ count, plural, =1 {1 attribute} andere {# attributes} } geselecteerd", - "selected-telemetry": "{ count, plural, =1 {1 telemetry unit} andere {# telemetry units} } geselecteerd", + "selected-attributes": "{ count, plural, =1 {1 attribute} other {# attributes} } geselecteerd", + "selected-telemetry": "{ count, plural, =1 {1 telemetry unit} other {# telemetry units} } geselecteerd", "no-attributes-text": "Geen attributen gevonden", "no-telemetry-text": "Geen telemetrie gevonden", "copy-key": "Kopieer sleutel", @@ -911,11 +911,11 @@ "management": "Beheer van dataconverters", "add-converter-text": "Nieuwe gegevensconverter toevoegen", "no-converters-text": "Geen dataconverters gevonden", - "selected-converters": "{ count, plural, =1 {1 data converter} andere {# data converters} } geselecteerd", + "selected-converters": "{ count, plural, =1 {1 data converter} other {# data converters} } geselecteerd", "delete-converter-title": "Weet u zeker dat u de dataconverter '{{converterName}}' wilt verwijderen?", "delete-converter-text": "Opgelet, na de bevestiging worden de gegevensomzetter en alle gerelateerde gegevens onherstelbaar.", - "delete-converters-title": "Weet u zeker dat u { count, plural, =1 {1 data converter} andere {# data converters} } wilt verwijderen?", - "delete-converters-action-title": "Verwijder { count, plural, =1 {1 data converter} andere {# data converters} }", + "delete-converters-title": "Weet u zeker dat u { count, plural, =1 {1 data converter} other {# data converters} } wilt verwijderen?", + "delete-converters-action-title": "Verwijder { count, plural, =1 {1 data converter} other {# data converters} }", "delete-converters-text": "Opgelet, na de bevestiging worden alle geselecteerde gegevensconverters verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld.", "events": "Events", "add": "Gegevensconverter toevoegen", @@ -1012,8 +1012,8 @@ "customer-details": "Klantgegevens", "delete-customer-title": "Weet je zeker dat je de '{{customerTitle}}' van de klant wilt verwijderen?", "delete-customer-text": "Let op, na de bevestiging worden de klant en alle gerelateerde gegevens onherstelbaar.", - "delete-customers-title": "Weet u zeker dat u { count, plural, =1 {1 customer} andere {# customers} } wilt verwijderen?", - "delete-customers-action-title": "Verwijder { count, plural, =1 {1 customer} andere {# customers} }", + "delete-customers-title": "Weet u zeker dat u { count, plural, =1 {1 customer} other {# customers} } wilt verwijderen?", + "delete-customers-action-title": "Verwijder { count, plural, =1 {1 customer} other {# customers} }", "delete-customers-text": "Opgelet, na de bevestiging worden alle geselecteerde klanten verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld.", "manage-user-groups": "Gebruikersgroepen beheren", "manage-asset-groups": "Asset groepen beheren", @@ -1039,16 +1039,16 @@ "customer-required": "Klant is verplicht", "select-group-to-add": "Selecteer doelgroep om geselecteerde klanten toe te voegen", "select-group-to-move": "Selecteer doelgroep om geselecteerde klanten te verhuizen", - "remove-customers-from-group": "Weet je zeker dat je { count, plural, =1 {1 customer} andere {# customers} } uit groep '{{entityGroup}}' wilt verwijderen?", + "remove-customers-from-group": "Weet je zeker dat je { count, plural, =1 {1 customer} other {# customers} } uit groep '{{entityGroup}}' wilt verwijderen?", "group": "Groep klanten", - "list-of-groups": "{ count, plural, =1 {One customer group} andere {List of # customer groups} }", + "list-of-groups": "{ count, plural, =1 {One customer group} other {List of # customer groups} }", "group-name-starts-with": "Klantgroepen waarvan de naam begint met '{{prefix}}'", "select-default-customer": "Selecteer standaardklant", "default-customer": "Standaard klant", "default-customer-required": "Standaardklant is vereist om fouten op te sporen in het dashboard op tenantniveau", "allow-white-labeling": "White Labeling toestaan", "search": "Klanten zoeken", - "selected-customers": "{ count, plural, =1 {1 customer} andere {# customers} } geselecteerd", + "selected-customers": "{ count, plural, =1 {1 customer} other {# customers} } geselecteerd", "edges": "Edge-instanties van de klant", "manage-edges": "Edge beheren" }, @@ -1125,20 +1125,20 @@ "add-dashboard-text": "Nieuw dashboard toevoegen", "assign-dashboards": "Dashboards toewijzen", "assign-new-dashboard": "Nieuw dashboard toewijzen", - "assign-dashboards-text": "Wijs { count, plural, =1 {1 dashboard} andere {# dashboards} } toe aan klanten", - "unassign-dashboards-action-text": "Toewijzing { count, plural, =1 {1 dashboard} andere {# dashboards} } van klanten ongedaan maken", + "assign-dashboards-text": "Wijs { count, plural, =1 {1 dashboard} other {# dashboards} } toe aan klanten", + "unassign-dashboards-action-text": "Toewijzing { count, plural, =1 {1 dashboard} other {# dashboards} } van klanten ongedaan maken", "delete-dashboards": "Dashboards verwijderen", "unassign-dashboards": "Toewijzing van dashboards ongedaan maken", - "unassign-dashboards-action-title": "Toewijzing { count, plural, =1 {1 dashboard} andere {# dashboards} } van klant ongedaan maken", + "unassign-dashboards-action-title": "Toewijzing { count, plural, =1 {1 dashboard} other {# dashboards} } van klant ongedaan maken", "delete-dashboard-title": "Weet je zeker dat je de '{{dashboardTitle}}' van het dashboard wilt verwijderen?", "delete-dashboard-text": "Opgelet, na de bevestiging worden het dashboard en alle gerelateerde gegevens onherstelbaar.", - "delete-dashboards-title": "Weet u zeker dat u { count, plural, =1 {1 dashboard} andere {# dashboards} } wilt verwijderen?", - "delete-dashboards-action-title": "Verwijder { count, plural, =1 {1 dashboard} andere {# dashboards} }", + "delete-dashboards-title": "Weet u zeker dat u { count, plural, =1 {1 dashboard} other {# dashboards} } wilt verwijderen?", + "delete-dashboards-action-title": "Verwijder { count, plural, =1 {1 dashboard} other {# dashboards} }", "delete-dashboards-text": "Opgelet, na de bevestiging worden alle geselecteerde dashboards verwijderd en worden alle gerelateerde gegevens onherstelbaar.", "unassign-dashboard-title": "Weet u zeker dat u de toewijzing van het dashboard '{{dashboardTitle}}' wilt opheffen?", "unassign-dashboard-text": "Na de bevestiging wordt de toewijzing van het dashboard ongedaan gemaakt en is het niet toegankelijk voor de klant.", "unassign-dashboard": "Toewijzing dashboard ongedaan maken", - "unassign-dashboards-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 dashboard} andere {# dashboards} } wilt opheffen?", + "unassign-dashboards-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 dashboard} other {# dashboards} } wilt opheffen?", "unassign-dashboards-text": "Na de bevestiging worden alle geselecteerde dashboards ongedaan gemaakt en zijn ze niet toegankelijk voor de klant.", "public-dashboard-title": "Dashboard is nu openbaar", "public-dashboard-text": "De {{dashboardTitle}} van uw dashboard is nu openbaar en toegankelijk via de volgende openbare link:", @@ -1242,7 +1242,7 @@ "manage-states": "Dashboardstatussen beheren", "states": "Statussen van dashboards", "search-states": "Statussen van zoekdashboard", - "selected-states": "{ count, plural, =1 {1 dashboard state} andere {# dashboard states} } geselecteerd", + "selected-states": "{ count, plural, =1 {1 dashboard state} other {# dashboard states} } geselecteerd", "edit-state": "Dashboardstatus bewerken", "delete-state": "Dashboardstatus verwijderen", "add-state": "Dashboardstatus toevoegen", @@ -1261,17 +1261,17 @@ "select-state": "Selecteer de doelstatus", "state-controller": "Controle van de staat", "search": "Dashboards doorzoeken", - "selected-dashboards": "{ count, plural, =1 {1 dashboard} andere {# dashboards} } geselecteerd", + "selected-dashboards": "{ count, plural, =1 {1 dashboard} other {# dashboards} } geselecteerd", "home-dashboard": "Startpagina dashboard", "home-dashboard-hide-toolbar": "Verberg de werkbalk van het startdashboard", "select-group-to-add": "Selecteer doelgroep om geselecteerde dashboards toe te voegen", "select-group-to-move": "Selecteer de doelgroep om geselecteerde dashboards te verplaatsen", - "remove-dashboards-from-group": "Weet je zeker dat je { count, plural, =1 {1 dashboard} andere {# dashboards} } uit groep '{{entityGroup}}' wilt verwijderen?", + "remove-dashboards-from-group": "Weet je zeker dat je { count, plural, =1 {1 dashboard} other {# dashboards} } uit groep '{{entityGroup}}' wilt verwijderen?", "group": "Groep dashboards", - "list-of-groups": "{ count, plural, =1 {One dashboard group} andere {List of # dashboard groups} }", + "list-of-groups": "{ count, plural, =1 {One dashboard group} other {List of # dashboard groups} }", "group-name-starts-with": "Dashboardgroepen waarvan de naam begint met '{{prefix}}'", "unassign-dashboard-from-edge-text": "Na de bevestiging wordt de toewijzing van het dashboard ongedaan gemaakt en is het niet toegankelijk voor de edge.", - "unassign-dashboards-from-edge-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 dashboard} andere {# dashboards} } wilt opheffen?", + "unassign-dashboards-from-edge-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 dashboard} other {# dashboards} } wilt opheffen?", "unassign-dashboards-from-edge-text": "Na de bevestiging worden alle geselecteerde dashboards ongedaan gemaakt en zijn ze niet toegankelijk voor de edge.", "assign-dashboard-to-edge": "Dashboard(s) toewijzen aan Edge", "assign-dashboard-to-edge-text": "Selecteer de dashboards die u aan de edge wilt toewijzen", @@ -1294,7 +1294,7 @@ "timeseries-required": "Timeseries van entiteiten zijn vereist.", "timeseries-or-attributes-required": "Timeseries/attributen van entiteiten zijn vereist.", "alarm-fields-timeseries-or-attributes-required": "Alarmvelden of tijdreeksen/attributen van entiteiten zijn vereist.", - "maximum-timeseries-or-attributes": "Maximaal { count, plural, =1 {1 timeseries/attribute is allowed.} andere {# timeseries/attributes are allowed} }", + "maximum-timeseries-or-attributes": "Maximaal { count, plural, =1 {1 timeseries/attribute is allowed.} other {# timeseries/attributes are allowed} }", "alarm-fields-required": "Alarmvelden zijn verplicht.", "function-types": "Soorten functies", "function-type": "Soort functie", @@ -1311,7 +1311,7 @@ "timeseries-key": "Gegevenssleutel tijdreeksen", "timeseries-key-functions": "Timeseries belangrijkste functies", "timeseries-key-function": "Tijdreeks-sleutelfunctie", - "maximum-function-types": "Maximaal { count, plural, =1 {1 function type is allowed.} andere {# function types are allowed} }", + "maximum-function-types": "Maximaal { count, plural, =1 {1 function type is allowed.} other {# function types are allowed} }", "time-description": "tijdstempel van de huidige waarde;", "value-description": "de huidige waarde;", "prev-value-description": "resultaat van de vorige functieaanroep;", @@ -1392,11 +1392,11 @@ "manage-credentials": "Inloggegevens beheren", "delete": "Device verwijderen", "assign-devices": "Devices toewijzen", - "assign-devices-text": "Wijs { count, plural, =1 {1 device} andere {# devices} } toe aan de klant", + "assign-devices-text": "Wijs { count, plural, =1 {1 device} other {# devices} } toe aan de klant", "delete-devices": "Devices verwijderen", "unassign-from-customer": "Toewijzing van klant ongedaan maken", "unassign-devices": "Toewijzing van devices ongedaan maken", - "unassign-devices-action-title": "Toewijzing { count, plural, =1 {1 device} andere {# devices} } van klant ongedaan maken", + "unassign-devices-action-title": "Toewijzing { count, plural, =1 {1 device} other {# devices} } van klant ongedaan maken", "unassign-device-from-edge-title": "Weet u zeker dat u de toewijzing van het device '{{deviceName}}' wilt opheffen?", "unassign-device-from-edge-text": "Na de bevestiging wordt de toewijzing van het device ongedaan gemaakt en is het niet toegankelijk via de edge.", "unassign-devices-from-edge": "Toewijzing van devices aan de edge ongedaan maken", @@ -1408,13 +1408,13 @@ "view-credentials": "Inloggegevens bekijken", "delete-device-title": "Weet u zeker dat u de '{{deviceName}}' van het device wilt verwijderen?", "delete-device-text": "Opgelet, na de bevestiging worden het device en alle gerelateerde gegevens onherstelbaar.", - "delete-devices-title": "Weet u zeker dat u { count, plural, =1 {1 device} andere {# devices} } wilt verwijderen?", - "delete-devices-action-title": "Verwijder { count, plural, =1 {1 device} andere {# devices} }", + "delete-devices-title": "Weet u zeker dat u { count, plural, =1 {1 device} other {# devices} } wilt verwijderen?", + "delete-devices-action-title": "Verwijder { count, plural, =1 {1 device} other {# devices} }", "delete-devices-text": "Opgelet, na de bevestiging worden alle geselecteerde devices verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld.", "unassign-device-title": "Weet u zeker dat u de toewijzing van het device '{{deviceName}}' wilt opheffen?", "unassign-device-text": "Na de bevestiging wordt de toewijzing van het device ongedaan gemaakt en is het niet toegankelijk voor de klant.", "unassign-device": "Toewijzing van device ongedaan maken", - "unassign-devices-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 device} andere {# devices} } wilt opheffen?", + "unassign-devices-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 device} other {# devices} } wilt opheffen?", "unassign-devices-text": "Na de bevestiging worden alle geselecteerde devices ongedaan gemaakt en zijn ze niet toegankelijk voor de klant.", "device-credentials": "Inloggegevens van het device", "loading-device-credentials": "Devicereferenties worden geladen...", @@ -1511,14 +1511,14 @@ "select-device": "Selecteer device", "select-group-to-add": "Selecteer de doelgroep om geselecteerde devices toe te voegen", "select-group-to-move": "Selecteer de doelgroep om geselecteerde devices te verplaatsen", - "remove-devices-from-group": "Weet je zeker dat je { count, plural, =1 {1 device} andere {# devices} } uit groep '{{entityGroup}}' wilt verwijderen?", + "remove-devices-from-group": "Weet je zeker dat je { count, plural, =1 {1 device} other {# devices} } uit groep '{{entityGroup}}' wilt verwijderen?", "group": "Device groep", - "list-of-groups": "{ count, plural, =1 {One device group} andere {List of # device groups} }", + "list-of-groups": "{ count, plural, =1 {One device group} other {List of # device groups} }", "group-name-starts-with": "Asset groepen waarvan de naam begint met '{{prefix}}'", "import": "Device importeren", "device-file": "Device bestand", "search": "Devices zoeken", - "selected-devices": "{ count, plural, =1 {1 device} andere {# devices} } geselecteerd", + "selected-devices": "{ count, plural, =1 {1 device} other {# devices} } geselecteerd", "device-configuration": "Configuratie van het device", "transport-configuration": "Configuratie van het transport", "wizard": { @@ -1530,7 +1530,7 @@ "customer-to-assign-device": "Klant om het device toe te wijzen", "add-credentials": "Inloggegevens toevoegen" }, - "unassign-devices-from-edge-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 device} andere {# devices} } wilt opheffen?", + "unassign-devices-from-edge-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 device} other {# devices} } wilt opheffen?", "unassign-devices-from-edge-text": "Na de bevestiging worden alle geselecteerde devices ongedaan gemaakt en zijn ze niet toegankelijk via de edge." }, "asset-profile": { @@ -1542,7 +1542,7 @@ "asset-profile-details": "Details van de asset profiel", "no-asset-profiles-text": "Geen assetprofielen gevonden", "search": "Assetprofielen zoeken", - "selected-asset-profiles": "{ count, plural, =1 {1 asset profile} andere {# asset profiles} } geselecteerd", + "selected-asset-profiles": "{ count, plural, =1 {1 asset profile} other {# asset profiles} } geselecteerd", "no-asset-profiles-matching": "Er zijn geen asset profielen gevonden die overeenkomen met '{{entity}}'.", "asset-profile-required": "Asset profiel is vereist", "idCopiedMessage": "De ID van het itemprofiel is gekopieerd naar het klembord", @@ -1565,7 +1565,7 @@ "select-queue-hint": "Maak een keuze uit een vervolgkeuzelijst.", "delete-asset-profile-title": "Weet je zeker dat je de asset profiel '{{assetProfileName}}' wilt verwijderen?", "delete-asset-profile-text": "Let op, na de bevestiging worden de asset profiel en alle gerelateerde gegevens onherstelbaar.", - "delete-asset-profiles-title": "Weet u zeker dat u { count, plural, =1 {1 asset profile} andere {# asset profiles} } wilt verwijderen?", + "delete-asset-profiles-title": "Weet u zeker dat u { count, plural, =1 {1 asset profile} other {# asset profiles} } wilt verwijderen?", "delete-asset-profiles-text": "Opgelet, na de bevestiging worden alle geselecteerde asset profielen verwijderd en worden alle gerelateerde gegevens onherstelbaar.", "set-default-asset-profile-title": "Weet u zeker dat u de asset profiel standaard op '{{assetProfileName}}' wilt zetten?", "set-default-asset-profile-text": "Na de bevestiging wordt de asset profiel gemarkeerd als standaard en wordt het gebruikt voor nieuwe asset waarvoor geen profiel is opgegeven.", @@ -1587,7 +1587,7 @@ "device-profile-details": "Details van device profiel", "no-device-profiles-text": "Geen device profielen gevonden", "search": "Device profielen zoeken", - "selected-device-profiles": "{ count, plural, =1 {1 device profile} andere {# device profiles} } geselecteerd", + "selected-device-profiles": "{ count, plural, =1 {1 device profile} other {# device profiles} } geselecteerd", "no-device-profiles-matching": "Er zijn geen device profielen gevonden die overeenkomen met '{{entity}}'.", "device-profile-required": "Device profiel is vereist", "idCopiedMessage": "Device profiel-ID is gekopieerd naar klembord", @@ -1627,7 +1627,7 @@ "select-queue-hint": "Maak een keuze uit een vervolgkeuzelijst.", "delete-device-profile-title": "Weet u zeker dat u het device profiel '{{deviceProfileName}}' wilt verwijderen?", "delete-device-profile-text": "Opgelet, na de bevestiging worden het device profiel en alle gerelateerde gegevens, inclusief bijbehorende OTA-updates, onherstelbaar.", - "delete-device-profiles-title": "Weet u zeker dat u { count, plural, =1 {1 device profile} andere {# device profiles} } wilt verwijderen?", + "delete-device-profiles-title": "Weet u zeker dat u { count, plural, =1 {1 device profile} other {# device profiles} } wilt verwijderen?", "delete-device-profiles-text": "Opgelet, na de bevestiging worden alle geselecteerde device profielen verwijderd en kunnen alle gerelateerde gegevens, inclusief bijbehorende OTA-updates, niet meer worden hersteld.", "set-default-device-profile-title": "Weet u zeker dat u het device profiel standaard '{{deviceProfileName}}' wilt maken?", "set-default-device-profile-text": "Na de bevestiging wordt het device profiel als standaard gemarkeerd en wordt het gebruikt voor nieuwe devices waarvoor geen profiel is opgegeven.", @@ -1713,9 +1713,10 @@ "condition-duration-value-required": "Duurwaarde is vereist.", "condition-duration-time-unit-required": "Tijdseenheid is vereist.", "advanced-settings": "Geavanceerde instellingen", - "alarm-rule-details": "Details", - "alarm-rule-details-hint": "Tip: gebruik ${keyName} om waarden van de attribuut- of telemetriesleutels te vervangen die worden gebruikt in de alarmregelvoorwaarde.", - "add-alarm-rule-details": "Voeg details toe", + "alarm-rule-additional-info": "Extra info", + "edit-alarm-rule-additional-info": "Extra informatie bewerken", + "alarm-rule-additional-info-placeholder": "Geef hier uw opmerkingen en aanpassingen op om ze weer te geven in Alarmdetails onder Extra info", + "alarm-rule-additional-info-hint": "Tip: gebruik ${keyName} om waarden van de attribuut- of telemetriesleutels te vervangen die worden gebruikt in de alarmregelvoorwaarde.", "alarm-rule-mobile-dashboard": "Mobiel dashboard", "alarm-rule-mobile-dashboard-hint": "Gebruikt door mobiele applicatie als dashboard met alarmdetails", "alarm-rule-no-mobile-dashboard": "Geen dashboard geselecteerd", @@ -1725,7 +1726,6 @@ "propagate-alarm-to-owner": "Alarm doorgeven aan entiteitseigenaar (klant of tenant)", "propagate-alarm-to-owner-hierarchy": "Alarm doorgeven aan de hiërarchie van entiteitseigenaren", "propagate-alarm-to-tenant": "Alarm doorgeven aan tenant", - "alarm-details": "Details van het alarm", "alarm-rule-condition": "Voorwaarde van de alarmregel", "enter-alarm-rule-condition-prompt": "Voeg de voorwaarde van de alarmregel toe", "edit-alarm-rule-condition": "Voorwaarde van de alarmregel bewerken", @@ -1766,8 +1766,8 @@ "condition-repeating-value-range": "Het aantal gebeurtenissen moet tussen 1 en 2147483647 liggen.", "condition-repeating-value-pattern": "Het aantal gebeurtenissen moet gehele getallen zijn.", "condition-repeating-value-required": "Het aantal gebeurtenissen is vereist.", - "condition-repeat-times": "Herhalingen { count, plural, =1 {1 time} andere {# times} }", - "condition-repeat-times-dynamic": "Herhaalt \"{ attribute }\" ({ count, plural, =1 {1 time} andere {# times} })", + "condition-repeat-times": "Herhalingen { count, plural, =1 {1 time} other {# times} }", + "condition-repeat-times-dynamic": "Herhaalt \"{ attribute }\" ({ count, plural, =1 {1 time} other {# times} })", "schedule-type": "Type planner", "schedule-type-required": "Het type planner is vereist.", "schedule": "Rooster", @@ -2015,7 +2015,7 @@ "delete": "Edge verwijderen", "delete-edge-title": "Weet je zeker dat je de edge '{{edgeName}}' wilt verwijderen?", "delete-edge-text": "Opgelet, na de bevestiging worden de edge en alle gerelateerde gegevens onherstelbaar.", - "delete-edges-title": "Weet je zeker dat je { count, plural, =1 {1 edge} andere {# edges} } wilt bevoordelen?", + "delete-edges-title": "Weet je zeker dat je { count, plural, =1 {1 edge} other {# edges} } wilt bevoordelen?", "delete-edges-text": "Opgelet, na de bevestiging worden alle geselecteerde edges verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld.", "name": "Naam", "name-starts-with": "Edge naam begint met", @@ -2050,7 +2050,7 @@ "unassign-from-customer": "Toewijzing van klant ongedaan maken", "unassign-edge-title": "Weet u zeker dat u de toewijzing van de edge '{{edgeName}}' wilt opheffen?", "unassign-edge-text": "Na de bevestiging wordt de toewijzing van de edge ongedaan gemaakt en is deze niet toegankelijk voor de klant.", - "unassign-edges-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 edge} andere {# edges} } wilt opheffen?", + "unassign-edges-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 edge} other {# edges} } wilt opheffen?", "unassign-edges-text": "Na de bevestiging worden alle geselecteerde edges ongedaan gemaakt en zijn ze niet toegankelijk voor de klant.", "make-public": "Edge openbaar maken", "make-public-edge-title": "Weet je zeker dat je de edge '{{edgeName}}' openbaar wilt maken?", @@ -2090,7 +2090,7 @@ "rulechains": "Edge rule chains", "integrations": "Integraties", "search": "Edge zoeken", - "selected-edges": "{ count, plural, =1 {1 edge} andere {# edges} } geselecteerd", + "selected-edges": "{ count, plural, =1 {1 edge} other {# edges} } geselecteerd", "any-edge": "Elke edge", "no-edge-types-matching": "Er zijn geen edge typen gevonden die overeenkomen met '{{entitySubtype}}'.", "edge-type-list-empty": "Geen edge typen geselecteerd.", @@ -2116,9 +2116,9 @@ "manage-edge-scheduler-events": "Edge-scheduler events beheren", "select-group-to-add": "Selecteer de doelgroep om geselecteerde edges toe te voegen", "select-group-to-move": "Selecteer de doelgroep om geselecteerde edges te verplaatsen", - "remove-edges-from-group": "Weet je zeker dat je { count, plural, =1 {1 edge} andere {# edges} } uit groep '{entityGroup}' wilt verwijderen?", + "remove-edges-from-group": "Weet je zeker dat je { count, plural, =1 {1 edge} other {# edges} } uit groep '{entityGroup}' wilt verwijderen?", "group": "Groep edges", - "list-of-groups": "{ count, plural, =1 {One edge group} andere {List of # edge groups} }", + "list-of-groups": "{ count, plural, =1 {One edge group} other {List of # edge groups} }", "group-name-starts-with": "Edge-groepen waarvan de naam begint met '{{prefix}}'", "unassign-entity-group-from-edge-title": "Weet u zeker dat u de toewijzing van de entiteitsgroep '{{ entityGroupName }}' wilt opheffen?", "unassign-entity-group-from-edge-text": "Na de bevestiging wordt de toewijzing van de entiteitsgroep ongedaan gemaakt en is deze niet toegankelijk via de edge.", @@ -2129,7 +2129,7 @@ "unassign-scheduler-events-from-edge": "Toewijzing van scheduler events van edge ongedaan maken", "unassign-scheduler-event-from-edge-title": "Weet u zeker dat u de toewijzing van de '{{schedulerEventName}}' van de planningsgebeurtenis wilt intrekken?", "unassign-scheduler-event-from-edge-text": "Na de bevestiging wordt de toewijzing van de planningsgebeurtenis ongedaan gemaakt en is deze niet toegankelijk via de edge.", - "unassign-scheduler-events-from-edge-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 scheduler event} andere {# scheduler events} } wilt opheffen?", + "unassign-scheduler-events-from-edge-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 scheduler event} other {# scheduler events} } wilt opheffen?", "unassign-scheduler-events-from-edge-text": "Na de bevestiging worden alle geselecteerde scheduler events ongedaan gemaakt en zijn ze niet toegankelijk voor de edge.", "manage-user-groups": "Gebruikersgroepen beheren", "manage-asset-groups": "Asset groepen beheren", @@ -2249,71 +2249,71 @@ "type-required": "Entiteitstype is vereist.", "type-device": "Device", "type-devices": "Devices", - "list-of-devices": "{ count, plural, =1 {One device} andere {List of # devices} }", + "list-of-devices": "{ count, plural, =1 {One device} other {List of # devices} }", "device-name-starts-with": "Devices waarvan de naam begint met '{{prefix}}'", "type-device-profile": "Device profiel", "type-device-profiles": "Device profielen", - "list-of-device-profiles": "{ count, plural, =1 {One device profile} andere {List of # device profiles} }", + "list-of-device-profiles": "{ count, plural, =1 {One device profile} other {List of # device profiles} }", "device-profile-name-starts-with": "Device profielen waarvan de naam begint met '{{prefix}}'", "type-asset-profile": "Asset profiel", "type-asset-profiles": "Asset profielen", - "list-of-asset-profiles": "{ count, plural, =1 {One asset profile} andere {List of # asset profiles} }", + "list-of-asset-profiles": "{ count, plural, =1 {One asset profile} other {List of # asset profiles} }", "asset-profile-name-starts-with": "Assetprofielen waarvan de naam begint met '{{prefix}}'", "type-asset": "Asset", "type-assets": "Assets", - "list-of-assets": "{ count, plural, =1 {One asset} andere {List of # assets} }", + "list-of-assets": "{ count, plural, =1 {One asset} other {List of # assets} }", "asset-name-starts-with": "Assets waarvan de naam begint met '{{prefix}}'", "type-entity-view": "Entiteit Weergave", "type-entity-views": "Entiteitsweergaven", - "list-of-entity-views": "{ count, plural, =1 {One entity view} andere {List of # entity views} }", + "list-of-entity-views": "{ count, plural, =1 {One entity view} other {List of # entity views} }", "entity-view-name-starts-with": "Entiteitsweergaven waarvan de naam begint met '{{prefix}}'", "type-rule": "Regel", "type-rules": "Reglement", - "list-of-rules": "{ count, plural, =1 {One rule} andere {List of # rules} }", + "list-of-rules": "{ count, plural, =1 {One rule} other {List of # rules} }", "rule-name-starts-with": "Regels waarvan de naam begint met '{{prefix}}'", "type-plugin": "Plug-in", "type-plugins": "Insteekplaatsen", - "list-of-plugins": "{ count, plural, =1 {One plugin} andere {List of # plugins} }", + "list-of-plugins": "{ count, plural, =1 {One plugin} other {List of # plugins} }", "plugin-name-starts-with": "Plug-ins waarvan de naam begint met '{{prefix}}'", "type-tenant": "Tenant", "type-tenants": "Tenants", - "list-of-tenants": "{ count, plural, =1 {One tenant} andere {List of # tenants} }", + "list-of-tenants": "{ count, plural, =1 {One tenant} other {List of # tenants} }", "tenant-name-starts-with": "Tenants van wie de naam begint met '{{prefix}}'", "type-tenant-profile": "Profiel van de tenant", "type-tenant-profiles": "Profielen van tenants", - "list-of-tenant-profiles": "{ count, plural, =1 {One tenant profile} andere {List of # tenant profiles} }", + "list-of-tenant-profiles": "{ count, plural, =1 {One tenant profile} other {List of # tenant profiles} }", "tenant-profile-name-starts-with": "Tentant profielen waarvan de naam begint met '{{prefix}}'", "type-customer": "Klant", "type-customers": "Klanten", - "list-of-customers": "{ count, plural, =1 {One customer} andere {List of # customers} }", + "list-of-customers": "{ count, plural, =1 {One customer} other {List of # customers} }", "customer-name-starts-with": "Klanten van wie de naam begint met '{{prefix}}'", "type-user": "Gebruiker", "type-users": "Gebruikers", - "list-of-users": "{ count, plural, =1 {One user} andere {List of # users} }", + "list-of-users": "{ count, plural, =1 {One user} other {List of # users} }", "user-name-starts-with": "Gebruikers van wie de naam begint met '{{prefix}}'", "type-dashboard": "Dashboard", "type-dashboards": "Dashboards", - "list-of-dashboards": "{ count, plural, =1 {One dashboard} andere {List of # dashboards} }", + "list-of-dashboards": "{ count, plural, =1 {One dashboard} other {List of # dashboards} }", "dashboard-name-starts-with": "Dashboards waarvan de naam begint met '{{prefix}}'", "type-alarm": "Alarm", "type-alarms": "Alarmen", - "list-of-alarms": "{ count, plural, =1 {One alarm} andere {List of # alarms} }", + "list-of-alarms": "{ count, plural, =1 {One alarm} other {List of # alarms} }", "alarm-name-starts-with": "Alarmen waarvan de naam begint met '{{prefix}}'", "type-rulechain": "Rule chains", "type-rulechains": "Rule chains", - "list-of-rulechains": "{ count, plural, =1 {One rule chain} andere {List of # rule chains} }", + "list-of-rulechains": "{ count, plural, =1 {One rule chain} other {List of # rule chains} }", "rulechain-name-starts-with": "Rule chains waarvan de naam begint met '{{prefix}}'", "type-scheduler-event": "Scheduler-event", "type-scheduler-events": "Scheduler-events", - "list-of-scheduler-events": "{ count, plural, =1 {One scheduler event} andere {List of # scheduler events} }", + "list-of-scheduler-events": "{ count, plural, =1 {One scheduler event} other {List of # scheduler events} }", "scheduler-event-name-starts-with": "Scheduler-events waarvan de naam begint met '{{prefix}}'", "type-blob-entity": "Blob-entiteit", "type-blob-entities": "Blob-entiteiten", - "list-of-blob-entities": "{ count, plural, =1 {One blob entity} andere {List of # blob entities} }", + "list-of-blob-entities": "{ count, plural, =1 {One blob entity} other {List of # blob entities} }", "blob-entity-name-starts-with": "Blob-entiteiten waarvan de naam begint met '{{prefix}}'", "type-rulenode": "Rule node", "type-rulenodes": "Rule nodes", - "list-of-rulenodes": "{ count, plural, =1 {One rule node} andere {List of # rule nodes} }", + "list-of-rulenodes": "{ count, plural, =1 {One rule node} other {List of # rule nodes} }", "rulenode-name-starts-with": "Rule node waarvan de naam begint met '{{prefix}}'", "type-current-customer": "Huidige klant", "type-current-tenant": "Huidige tenant", @@ -2321,9 +2321,9 @@ "type-current-user-owner": "Huidige gebruikerseigenaar", "type-widgets-bundle": "Widgets bundel", "type-widgets-bundles": "Widgets bundels", - "list-of-widgets-bundles": "{ count, plural, =1 {One widgets bundle} andere {List of # widget bundles} }", + "list-of-widgets-bundles": "{ count, plural, =1 {One widgets bundle} other {List of # widget bundles} }", "search": "Entiteiten zoeken", - "selected-entities": "{ count, plural, =1 {1 entity} andere {# entities} } geselecteerd", + "selected-entities": "{ count, plural, =1 {1 entity} other {# entities} } geselecteerd", "entity-name": "Naam van de entiteit", "entity-label": "Entiteitslabel", "details": "Gegevens van de entiteit", @@ -2334,20 +2334,20 @@ "type-entity-group": "Entiteitsgroep", "type-converter": "Gegevens converteren", "type-converters": "Data-converters", - "list-of-converters": "{ count, plural, =1 {One data converter} andere {List of # data converters} }", + "list-of-converters": "{ count, plural, =1 {One data converter} other {List of # data converters} }", "converter-name-starts-with": "Dataconverters waarvan de naam begint met \"{{prefix}}\"", "type-integration": "Integratie", "type-integrations": "Integraties", - "list-of-integrations": "{ count, plural, =1 {One integration} andere {List of # integrations} }", + "list-of-integrations": "{ count, plural, =1 {One integration} other {List of # integrations} }", "integration-name-starts-with": "Integraties waarvan de naam begint met '{{prefix}}'", "type-role": "Rol", "type-roles": "Rollen", - "list-of-roles": "{ count, plural, =1 {One role} andere {List of # roles} }", + "list-of-roles": "{ count, plural, =1 {One role} other {List of # roles} }", "role-name-starts-with": "Rollen waarvan de naam begint met '{{prefix}}'", "type-group-permission": "Toestemming voor groepen", "type-edge": "Edge", "type-edges": "Edge", - "list-of-edges": "{ count, plural, =1 {One edge} andere {List of # edges} }", + "list-of-edges": "{ count, plural, =1 {One edge} other {List of # edges} }", "edge-name-starts-with": "Edge waarvan de naam begint met '{{prefix}}'", "type-tb-resource": "Hulpbron", "type-ota-package": "OTA-pakket", @@ -2394,12 +2394,12 @@ "add-entity-group-text": "Nieuwe entiteitsgroep toevoegen", "no-entity-groups-text": "Geen entiteitsgroepen gevonden", "entity-group-details": "Details van entiteitsgroep", - "selected-entity-groups": "{ count, plural, =1 {1 entity group} andere {# entity groups} } geselecteerd", + "selected-entity-groups": "{ count, plural, =1 {1 entity group} other {# entity groups} } geselecteerd", "delete-entity-groups": "Entiteitsgroepen verwijderen", "delete-entity-group-title": "Weet u zeker dat u de entiteitsgroep '{{entityGroupName}}' wilt verwijderen?", "delete-entity-group-text": "Let op, na de bevestiging worden de entiteitsgroep en alle gerelateerde gegevens onherstelbaar.", - "delete-entity-groups-title": "Weet u zeker dat u { count, plural, =1 {1 entity group} andere {# entity groups} } wilt verwijderen?", - "delete-entity-groups-action-title": "Verwijder { count, plural, =1 {1 entity group} andere {# entity groups} }", + "delete-entity-groups-title": "Weet u zeker dat u { count, plural, =1 {1 entity group} other {# entity groups} } wilt verwijderen?", + "delete-entity-groups-action-title": "Verwijder { count, plural, =1 {1 entity group} other {# entity groups} }", "delete-entity-groups-text": "Opgelet, na de bevestiging worden alle geselecteerde entiteitsgroepen verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld.", "device-groups": "Asset groepen", "shared-device-groups": "Groepen met gedeelde devices", @@ -2466,7 +2466,7 @@ "select-target-owner": "Selecteer doeleigenaar", "no-owners-matching": "Er zijn geen eigenaren gevonden die overeenkomen met '{{owner}}'.", "target-owner-required": "Doeleigenaar is vereist.", - "confirm-change-owner-title": "Weet u zeker dat u van eigenaar wilt veranderen voor { count, plural, =1 {1 selected entity} andere {# selected entities} }?", + "confirm-change-owner-title": "Weet u zeker dat u van eigenaar wilt veranderen voor { count, plural, =1 {1 selected entity} other {# selected entities} }?", "confirm-change-owner-text": "Opgelet, na de bevestiging worden alle geselecteerde entiteiten verwijderd van de huidige eigenaar en worden ze geplaatst in de groep 'Alle' van de doeleigenaar.", "add-to-group": "Toevoegen aan groep", "move-to-group": "Verplaatsen naar groep", @@ -2590,18 +2590,18 @@ "add-entity-view-text": "Nieuwe entiteitsweergave toevoegen", "delete": "Entiteitsweergave verwijderen", "assign-entity-views": "Entiteitsweergaven toewijzen", - "assign-entity-views-text": "Wijs { count, plural, =1 {1 entity view} andere {# entity views} } toe aan de klant", + "assign-entity-views-text": "Wijs { count, plural, =1 {1 entity view} other {# entity views} } toe aan de klant", "delete-entity-views": "Entiteitsweergaven verwijderen", "make-public": "Entiteitsweergave openbaar maken", "make-private": "Entiteitsweergave privé maken", "unassign-from-customer": "Toewijzing van klant ongedaan maken", "unassign-entity-views": "Toewijzing van entiteitsweergaven intrekken", - "unassign-entity-views-action-title": "Toewijzing { count, plural, =1 {1 entity view} andere {# entity views} } van klant ongedaan maken", + "unassign-entity-views-action-title": "Toewijzing { count, plural, =1 {1 entity view} other {# entity views} } van klant ongedaan maken", "assign-new-entity-view": "Nieuwe entiteitsweergave toewijzen", "delete-entity-view-title": "Weet u zeker dat u de entiteitsweergave '{{entityViewName}}' wilt verwijderen?", "delete-entity-view-text": "Opgelet, na de bevestiging worden de entiteitsweergave en alle gerelateerde gegevens onherstelbaar.", - "delete-entity-views-title": "Weet u zeker dat u { count, plural, =1 {1 entity view} andere {# entity views} } wilt verwijderen?", - "delete-entity-views-action-title": "Verwijder { count, plural, =1 {1 entity view} andere {# entity views} }", + "delete-entity-views-title": "Weet u zeker dat u { count, plural, =1 {1 entity view} other {# entity views} } wilt verwijderen?", + "delete-entity-views-action-title": "Verwijder { count, plural, =1 {1 entity view} other {# entity views} }", "delete-entity-views-text": "Opgelet, na de bevestiging worden alle geselecteerde entiteitsweergaven verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld.", "make-public-entity-view-title": "Weet u zeker dat u de entiteitsweergave '{{entityViewName}}' openbaar wilt maken?", "make-public-entity-view-text": "Na de bevestiging worden de entiteitsweergave en al haar gegevens openbaar en toegankelijk gemaakt voor anderen.", @@ -2610,7 +2610,7 @@ "unassign-entity-view-title": "Weet u zeker dat u de toewijzing van de entiteitsweergave '{{entityViewName}}' wilt opheffen?", "unassign-entity-view-text": "Na de bevestiging wordt de toewijzing van de entiteitsweergave ongedaan gemaakt en is deze niet toegankelijk voor de klant.", "unassign-entity-view": "Toewijzing van entiteitsweergave ongedaan maken", - "unassign-entity-views-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 entity view} andere {# entity views} } wilt opheffen?", + "unassign-entity-views-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 entity view} other {# entity views} } wilt opheffen?", "unassign-entity-views-text": "Na de bevestiging worden alle geselecteerde entiteitsweergaven ongedaan gemaakt en zijn ze niet toegankelijk voor de klant.", "entity-view-type": "Type entiteitsweergave", "entity-view-type-required": "Het type Entiteitsweergave is vereist.", @@ -2654,19 +2654,19 @@ "timeseries-data-hint": "Configureer timeseries sleutels van de doelentiteit die toegankelijk zijn voor de entiteitsweergave. Deze timeseries gegevens zijn niet editeerbaar.", "select-group-to-add": "Selecteer de doelgroep om geselecteerde entiteitsweergaven toe te voegen", "select-group-to-move": "Selecteer de doelgroep om geselecteerde entiteitsweergaven te verplaatsen", - "remove-entity-views-from-group": "Weet je zeker dat je { count, plural, =1 {1 entity view} andere {# entity views} } uit groep '{{entityGroup}}' wilt verwijderen?", + "remove-entity-views-from-group": "Weet je zeker dat je { count, plural, =1 {1 entity view} other {# entity views} } uit groep '{{entityGroup}}' wilt verwijderen?", "group": "Groep entiteitsweergaven", - "list-of-groups": "{ count, plural, =1 {One entity view group} andere {List of # entity view groups} }", + "list-of-groups": "{ count, plural, =1 {One entity view group} other {List of # entity view groups} }", "group-name-starts-with": "Entiteitsweergavegroepen waarvan de naam begint met '{{prefix}}'", "search": "Entiteitsweergaven zoeken", - "selected-entity-views": "{ count, plural, =1 {1 entity view} andere {# entity views} } geselecteerd", + "selected-entity-views": "{ count, plural, =1 {1 entity view} other {# entity views} } geselecteerd", "assign-entity-view-to-edge": "Entiteitsweergave(n) toewijzen aan edge", "assign-entity-view-to-edge-text": "Selecteer de entiteitsweergaven die u aan de edge wilt toewijzen", "unassign-entity-view-from-edge-title": "Weet u zeker dat u de toewijzing van de entiteitsweergave '{{entityViewName}}' wilt opheffen?", "unassign-entity-view-from-edge-text": "Na de bevestiging wordt de toewijzing van de entiteitsweergave ongedaan gemaakt en is deze niet toegankelijk via de edge.", - "unassign-entity-views-from-edge-action-title": "Toewijzing { count, plural, =1 {1 entity view} andere {# entity views} } van edge ongedaan maken", + "unassign-entity-views-from-edge-action-title": "Toewijzing { count, plural, =1 {1 entity view} other {# entity views} } van edge ongedaan maken", "unassign-entity-view-from-edge": "Toewijzing van entiteitsweergave ongedaan maken", - "unassign-entity-views-from-edge-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 entity view} andere {# entity views} } wilt opheffen?", + "unassign-entity-views-from-edge-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 entity view} other {# entity views} } wilt opheffen?", "unassign-entity-views-from-edge-text": "Na de bevestiging worden alle geselecteerde entiteitsweergaven ongedaan gemaakt en zijn ze niet toegankelijk via de edge." }, "event": { @@ -2725,7 +2725,7 @@ }, "extension": { "extensions": "Extensies", - "selected-extensions": "{ count, plural, =1 {1 extension} andere {# extensions} } geselecteerd", + "selected-extensions": "{ count, plural, =1 {1 extension} other {# extensions} } geselecteerd", "type": "Type", "key": "Sleutel", "value": "Waarde", @@ -2740,7 +2740,7 @@ "view": "Bekijk extensie", "delete-extension-title": "Weet je zeker dat je de extensie '{{extensionId}}' wilt verwijderen?", "delete-extension-text": "Opgelet, na de bevestiging worden de extensie en alle gerelateerde gegevens onherstelbaar.", - "delete-extensions-title": "Weet u zeker dat u { count, plural, =1 {1 extension} andere {# extensions} } wilt verwijderen?", + "delete-extensions-title": "Weet u zeker dat u { count, plural, =1 {1 extension} other {# extensions} } wilt verwijderen?", "delete-extensions-text": "Opgelet, na de bevestiging worden alle geselecteerde extensies verwijderd.", "converters": "Converters", "converter-id": "Converter-id", @@ -3069,8 +3069,8 @@ "grid": { "delete-item-title": "Weet u zeker dat u dit item wilt verwijderen?", "delete-item-text": "Opgelet, na de bevestiging zullen dit item en alle gerelateerde gegevens onherstelbaar worden.", - "delete-items-title": "Weet u zeker dat u { count, plural, =1 {1 item} andere {# items} } wilt verwijderen?", - "delete-items-action-title": "Verwijder { count, plural, =1 {1 item} andere {# items} }", + "delete-items-title": "Weet u zeker dat u { count, plural, =1 {1 item} other {# items} } wilt verwijderen?", + "delete-items-action-title": "Verwijder { count, plural, =1 {1 item} other {# items} }", "delete-items-text": "Opgelet, na de bevestiging worden alle geselecteerde items verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld.", "add-item-text": "Nieuw item toevoegen", "no-items-text": "Geen items gevonden", @@ -3180,11 +3180,11 @@ "management": "Beheer van integraties", "add-integration-text": "Nieuwe integratie toevoegen", "no-integrations-text": "Geen integraties gevonden", - "selected-integrations": "{ count, plural, =1 {1 integration} andere {# integrations} } geselecteerd", + "selected-integrations": "{ count, plural, =1 {1 integration} other {# integrations} } geselecteerd", "delete-integration-title": "Weet je zeker dat je de integratie '{{integrationName}}' wilt verwijderen?", "delete-integration-text": "Opgelet, na de bevestiging worden de integratie en alle gerelateerde gegevens onherstelbaar.", - "delete-integrations-title": "Weet u zeker dat u { count, plural, =1 {1 integration} andere {# integrations} } wilt verwijderen?", - "delete-integrations-action-title": "Verwijder { count, plural, =1 {1 integration} andere {# integrations} }", + "delete-integrations-title": "Weet u zeker dat u { count, plural, =1 {1 integration} other {# integrations} } wilt verwijderen?", + "delete-integrations-action-title": "Verwijder { count, plural, =1 {1 integration} other {# integrations} }", "delete-integrations-text": "Opgelet, na de bevestiging worden alle geselecteerde integraties verwijderd en worden alle gerelateerde gegevens onherstelbaar.", "events": "Events", "enabled": "Integratie inschakelen", @@ -3588,7 +3588,7 @@ "coap-dtls-endpoint-url-copied-message": "De URL van het CoAP DTLS-eindpunt is gekopieerd naar het klembord", "unassign-integration-title": "Weet je zeker dat je de toewijzing van de integratie '{{integrationName}}' ongedaan wilt maken?", "unassign-integration-from-edge-text": "Na de bevestiging wordt de toewijzing van de integratie ongedaan gemaakt en is deze niet toegankelijk voor de edge.", - "unassign-integrations-from-edge-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 integration} andere {# integrations} } wilt opheffen?", + "unassign-integrations-from-edge-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 integration} other {# integrations} } wilt opheffen?", "unassign-integrations-from-edge-text": "Na de bevestiging worden alle geselecteerde integraties ongedaan gemaakt en zijn ze niet toegankelijk voor de edge.", "unassign-integrations": "Toewijzing van integraties ongedaan maken", "edge-placeholder-hint": "U kunt tijdelijke aanduiding ${{ATTRIBUTE_KEY}} gebruiken om het integratieveld te vervangen door de kenmerkwaarde van een specifieke Edge-entiteit. 'Edge A' heeft bijvoorbeeld het kenmerk 'baseUrl' dat gelijk is aan 'http://localhost:9999'. U kunt $ {{baseUrl}} instellen als een van de integratievelden, en het wordt vervangen door 'http://localhost:9999' tijdens de toewijzing van deze integratie aan 'Edge A'. Bovendien, als 'Edge A' attribuut 'baseUrl' wordt bijgewerkt, wordt de integratie met de nieuwe bijgewerkte waarde automatisch ingericht voor 'Edge A'.", @@ -3716,7 +3716,7 @@ "verify-your-identity": "Verifieer uw identiteit", "select-way-to-verify": "Selecteer een manier om te verifiëren", "resend-code": "Code opnieuw verzenden", - "resend-code-wait": "Code opnieuw verzenden in { time, plural, =1 {1 second} andere {# seconds} }", + "resend-code-wait": "Code opnieuw verzenden in { time, plural, =1 {1 second} other {# seconds} }", "try-another-way": "Probeer een andere manier", "totp-auth-description": "Voer de beveiligingscode van je authenticator-app in.", "totp-auth-placeholder": "Code", @@ -3791,23 +3791,23 @@ "delete-notification-text": "Opgelet, na de bevestiging wordt de melding onherstelbaar.", "delete-notification-title": "Weet je zeker dat je de melding wilt verwijderen?", "delete-notifications-text": "Opgelet, na de bevestiging worden meldingen onherstelbaar.", - "delete-notifications-title": "Weet u zeker dat u { count, plural, =1 {1 notification} andere {# notifications} } wilt verwijderen?", + "delete-notifications-title": "Weet u zeker dat u { count, plural, =1 {1 notification} other {# notifications} } wilt verwijderen?", "delete-recipient-text": "Let op, na de bevestiging wordt de ontvanger onherstelbaar.", "delete-recipient-title": "Weet u zeker dat u de '{{recipientName}}' van de ontvanger wilt verwijderen?", "delete-recipients-text": "Opgelet, na de bevestiging worden ontvangers onherstelbaar.", - "delete-recipients-title": "Weet u zeker dat u { count, plural, =1 {1 recipient} andere {# recipients} } wilt verwijderen?", + "delete-recipients-title": "Weet u zeker dat u { count, plural, =1 {1 recipient} other {# recipients} } wilt verwijderen?", "delete-request-text": "Opgelet, na het bevestigingsverzoek wordt het onherstelbaar.", "delete-request-title": "Weet je zeker dat je een verzoek wilt verwijderen?", "delete-requests-text": "Opgelet, na de bevestiging worden verzoeken onherstelbaar.", - "delete-requests-title": "Weet u zeker dat u { count, plural, =1 {1 request} andere {# requests} } wilt verwijderen?", + "delete-requests-title": "Weet u zeker dat u { count, plural, =1 {1 request} other {# requests} } wilt verwijderen?", "delete-rule-text": "Opgelet, nadat de bevestigingsregel onherstelbaar wordt.", "delete-rule-title": "Weet u zeker dat u rule '{{ruleName}}' wilt verwijderen?", "delete-rules-text": "Opgelet, na de bevestiging worden de regels onherstelbaar.", - "delete-rules-title": "Weet u zeker dat u { count, plural, =1 {1 rule} andere {# rules} } wilt verwijderen?", + "delete-rules-title": "Weet u zeker dat u { count, plural, =1 {1 rule} other {# rules} } wilt verwijderen?", "delete-template-text": "Opgelet, nadat de bevestigingssjabloon onherstelbaar wordt.", "delete-template-title": "Weet u zeker dat u sjabloon '{{templateName}}' wilt verwijderen?", "delete-templates-text": "Opgelet, nadat de bevestigingssjablonen onherstelbaar zijn geworden.", - "delete-templates-title": "Weet u zeker dat u { count, plural, =1 {1 template} andere {# templates} } wilt verwijderen?", + "delete-templates-title": "Weet u zeker dat u { count, plural, =1 {1 template} other {# templates} } wilt verwijderen?", "deleted": "Verwijderd", "delivery-method": { "delivery-method": "Wijze van levering", @@ -3838,7 +3838,7 @@ "entity-type": "Type entiteit", "escalation-chain": "Escalatie keten", "failed-send": "Fouten verzenden", - "fails": "{ count, plural, =1 {1 fail} andere {# fails} }", + "fails": "{ count, plural, =1 {1 fail} other {# fails} }", "filter": "Filter", "first-recipient": "Eerste ontvanger", "inactive": "Inactief", @@ -3912,7 +3912,7 @@ }, "recipients": "Ontvangers", "notification-recipients": "Meldingen / Ontvangers", - "recipients-count": "{ count, plural, =1 {1 recipient} andere {# recipients} }", + "recipients-count": "{ count, plural, =1 {1 recipient} other {# recipients} }", "recipients-required": "Ontvangers zijn verplicht", "refresh-allow-delivery-method": "Vernieuwen Bezorgmethode toestaan", "request-search": "Zoekopdracht aanvragen", @@ -3937,11 +3937,11 @@ "search-rules": "Zoekregels", "search-templates": "Sjablonen zoeken", "see-documentation": "Zie documentatie", - "selected-notifications": "{ count, plural, =1 {1 notification} andere {# notifications} } geselecteerd", - "selected-recipients": "{ count, plural, =1 {1 recipient} andere {# recipients} } geselecteerd", - "selected-requests": "{ count, plural, =1 {1 request} andere {# requests} } geselecteerd", - "selected-rules": "{ count, plural, =1 {1 rule} andere {# rules} } geselecteerd", - "selected-template": "{ count, plural, =1 {1 template} andere {# templates} } geselecteerd", + "selected-notifications": "{ count, plural, =1 {1 notification} other {# notifications} } geselecteerd", + "selected-recipients": "{ count, plural, =1 {1 recipient} other {# recipients} } geselecteerd", + "selected-requests": "{ count, plural, =1 {1 request} other {# requests} } geselecteerd", + "selected-rules": "{ count, plural, =1 {1 rule} other {# rules} } geselecteerd", + "selected-template": "{ count, plural, =1 {1 template} other {# templates} } geselecteerd", "send-notification": "Notificatie versturen", "sent": "Verzonden", "notification-sent": "Notificaties / Verzonden", @@ -4017,8 +4017,8 @@ "checksum-hint": "Als de checksum leeg is, wordt deze automatisch gegenereerd", "checksum-algorithm": "Checksum-algoritme", "checksum-copied-message": "De controlesom van het pakket is gekopieerd naar het klembord", - "change-firmware": "Wijziging van de firmware kan leiden tot een update van { count, plural, =1 {1 device} andere {# devices} }.", - "change-software": "Wijziging van de software kan leiden tot een update van { count, plural, =1 {1 device} andere {# devices} }.", + "change-firmware": "Wijziging van de firmware kan leiden tot een update van { count, plural, =1 {1 device} other {# devices} }.", + "change-software": "Wijziging van de software kan leiden tot een update van { count, plural, =1 {1 device} other {# devices} }.", "chose-compatible-device-profile": "Het geüploade pakket is alleen beschikbaar voor devices met het gekozen profiel.", "chose-firmware-distributed-device": "Kies firmware die naar de devices wordt gedistribueerd", "chose-software-distributed-device": "Kies software die naar de devices wordt gedistribueerd", @@ -4031,7 +4031,7 @@ "delete-ota-update-text": "Opgelet, na de bevestiging wordt de OTA-update onherstelbaar.", "delete-ota-update-title": "Weet je zeker dat je de OTA-update '{{title}}' wilt verwijderen?", "delete-ota-updates-text": "Opgelet, na de bevestiging worden alle geselecteerde OTA-updates verwijderd.", - "delete-ota-updates-title": "Weet u zeker dat u { count, plural, =1 {1 OTA update} andere {# OTA updates} } wilt verwijderen?", + "delete-ota-updates-title": "Weet u zeker dat u { count, plural, =1 {1 OTA update} other {# OTA updates} } wilt verwijderen?", "description": "Omschrijving: __________", "direct-url": "Directe URL", "direct-url-copied-message": "De directe URL van het pakket is gekopieerd naar het klembord", @@ -4054,7 +4054,7 @@ "package-type": "Soort verpakking", "packages-repository": "Pakketten repository", "search": "Pakketten zoeken", - "selected-package": "{ count, plural, =1 {1 package} andere {# packages} } geselecteerd", + "selected-package": "{ count, plural, =1 {1 package} other {# packages} } geselecteerd", "title": "Titel", "title-required": "Titel is vereist.", "title-max-length": "Titel moet kleiner zijn dan 256 tekens", @@ -4150,17 +4150,17 @@ }, "password-requirement": { "at-least": "Minstens:", - "character": "{ count, plural, =1 {1 character} andere {# characters} }", - "digit": "{ count, plural, =1 {1 digit} andere {# digits} }", + "character": "{ count, plural, =1 {1 character} other {# characters} }", + "digit": "{ count, plural, =1 {1 digit} other {# digits} }", "incorrect-password-try-again": "Onjuist wachtwoord. Probeer het opnieuw", - "lowercase-letter": "{ count, plural, =1 {1 lowercase letter} andere {# lowercase letters} }", + "lowercase-letter": "{ count, plural, =1 {1 lowercase letter} other {# lowercase letters} }", "new-passwords-not-match": "Nieuw wachtwoord komt niet overeen", "password-should-not-contain-spaces": "Uw wachtwoord mag geen spaties bevatten", "password-not-meet-requirements": "Wachtwoord voldeed niet aan de vereisten", "password-requirements": "Vereisten voor wachtwoorden", "password-should-difference": "Het nieuwe wachtwoord moet anders zijn dan het huidige", - "special-character": "{ count, plural, =1 {1 special character} andere {# special characters} }", - "uppercase-letter": "{ count, plural, =1 {1 uppercase letter} andere {# uppercase letters} }" + "special-character": "{ count, plural, =1 {1 special character} other {# special characters} }", + "uppercase-letter": "{ count, plural, =1 {1 uppercase letter} other {# uppercase letters} }" } }, "relation": { @@ -4176,7 +4176,7 @@ }, "from-relations": "Uitgaande relaties", "to-relations": "Inkomende relaties", - "selected-relations": "{ count, plural, =1 {1 relation} andere {# relations} } geselecteerd", + "selected-relations": "{ count, plural, =1 {1 relation} other {# relations} } geselecteerd", "type": "Type", "to-entity-type": "Naar entiteitstype", "to-entity-name": "Naar entiteitsnaam", @@ -4194,11 +4194,11 @@ "view": "Bekijk relatie", "delete-to-relation-title": "Weet u zeker dat u de relatie met de entiteit '{{entityName}}' wilt verwijderen?", "delete-to-relation-text": "Let op, na de bevestiging is de entiteit '{{entityName}}' niet meer gerelateerd aan de huidige entiteit.", - "delete-to-relations-title": "Weet u zeker dat u { count, plural, =1 {1 relation} andere {# relations} } wilt verwijderen?", + "delete-to-relations-title": "Weet u zeker dat u { count, plural, =1 {1 relation} other {# relations} } wilt verwijderen?", "delete-to-relations-text": "Opgelet, na de bevestiging worden alle geselecteerde relaties verwijderd en worden de bijbehorende entiteiten losgekoppeld van de huidige entiteit.", "delete-from-relation-title": "Weet u zeker dat u de relatie uit de entiteit '{{entityName}}' wilt verwijderen?", "delete-from-relation-text": "Opgelet, na de bevestiging zal de huidige entiteit niet meer gerelateerd zijn aan de entiteit '{{entityName}}'.", - "delete-from-relations-title": "Weet u zeker dat u { count, plural, =1 {1 relation} andere {# relations} } wilt verwijderen?", + "delete-from-relations-title": "Weet u zeker dat u { count, plural, =1 {1 relation} other {# relations} } wilt verwijderen?", "delete-from-relations-text": "Opgelet, na de bevestiging worden alle geselecteerde relaties verwijderd en wordt de huidige entiteit losgekoppeld van de corresponderende entiteiten.", "remove-relation-filter": "Relatiefilter verwijderen", "add-relation-filter": "Relatiefilter toevoegen", @@ -4214,9 +4214,9 @@ "delete": "Bron verwijderen", "delete-resource-text": "Opgelet, na de bevestiging wordt de bron onherstelbaar.", "delete-resource-title": "Weet u zeker dat u de bron '{{resourceTitle}}' wilt verwijderen?", - "delete-resources-action-title": "Verwijder { count, plural, =1 {1 resource} andere {# resources} }", + "delete-resources-action-title": "Verwijder { count, plural, =1 {1 resource} other {# resources} }", "delete-resources-text": "Houd er rekening mee dat de geselecteerde bronnen, zelfs als ze in device profielen worden gebruikt, worden verwijderd.", - "delete-resources-title": "Weet u zeker dat u { count, plural, =1 {1 resource} andere {# resources} } wilt verwijderen?", + "delete-resources-title": "Weet u zeker dat u { count, plural, =1 {1 resource} other {# resources} } wilt verwijderen?", "download": "Bron downloaden", "drop-file": "Zet een bronbestand neer of klik om een bestand te selecteren om te uploaden.", "drop-resource-file-or": "Sleep een bronbestand of", @@ -4231,7 +4231,7 @@ "resource-type": "Type bron", "resources-library": "Bibliotheek met bronnen", "search": "Bronnen zoeken", - "selected-resources": "{ count, plural, =1 {1 resource} andere {# resources} } geselecteerd", + "selected-resources": "{ count, plural, =1 {1 resource} other {# resources} } geselecteerd", "system": "Systeem", "title": "Titel", "title-required": "Titel is vereist.", @@ -4253,8 +4253,8 @@ "set-root-rulechain-text": "Na de bevestiging wordt de rule chain de bron en worden alle inkomende transportberichten afgehandeld.", "delete-rulechain-title": "Weet u zeker dat u de rule chain '{{ruleChainName}}' wilt verwijderen?", "delete-rulechain-text": "Opgelet, na de bevestiging worden de rule chain en alle gerelateerde gegevens onherstelbaar.", - "delete-rulechains-title": "Weet u zeker dat u { count, plural, =1 {1 rule chain} andere {# rule chains} } wilt verwijderen?", - "delete-rulechains-action-title": "Verwijder { count, plural, =1 {1 rule chain} andere {# rule chains} }", + "delete-rulechains-title": "Weet u zeker dat u { count, plural, =1 {1 rule chain} other {# rule chains} } wilt verwijderen?", + "delete-rulechains-action-title": "Verwijder { count, plural, =1 {1 rule chain} other {# rule chains} }", "delete-rulechains-text": "Opgelet, na de bevestiging worden alle geselecteerde rule chains verwijderd en worden alle gerelateerde gegevens onherstelbaar.", "add-rulechain-text": "Nieuwe rule chain toevoegen", "no-rulechains-text": "Geen rule chains gevonden", @@ -4276,13 +4276,13 @@ "management": "Beheer van rule chains", "debug-mode": "Foutopsporingsmodus", "search": "Rule chains zoeken", - "selected-rulechains": "{ count, plural, =1 {1 rule chain} andere {# rule chains} } geselecteerd", + "selected-rulechains": "{ count, plural, =1 {1 rule chain} other {# rule chains} } geselecteerd", "open-rulechain": "Rule chain openen", "edge-template-root": "Sjabloon Wortel", "assign-to-edge": "Toewijzen aan Edge", "edge-rulechain": "Edge rule chain", "unassign-rulechain-from-edge-text": "Na de bevestiging wordt de toewijzing van de rule chain ongedaan gemaakt en is deze niet toegankelijk voor de edge.", - "unassign-rulechains-from-edge-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 rulechain} andere {# rulechains} } wilt opheffen?", + "unassign-rulechains-from-edge-title": "Weet u zeker dat u de toewijzing van { count, plural, =1 {1 rulechain} other {# rulechains} } wilt opheffen?", "unassign-rulechains-from-edge-text": "Na de bevestiging worden alle geselecteerde rule chains ongedaan gemaakt en zijn ze niet toegankelijk via de edge.", "assign-rulechain-to-edge-title": "Regel Chain(s) toewijzen aan Edge", "assign-rulechain-to-edge-text": "Selecteer de rule chains die u aan de edge wilt toewijzen", @@ -4385,7 +4385,7 @@ "add": "Rol toevoegen", "view": "Bekijk rol", "search": "Rollen zoeken", - "selected-roles": "{ count, plural, =1 {1 role} andere {# roles} } geselecteerd", + "selected-roles": "{ count, plural, =1 {1 role} other {# roles} } geselecteerd", "no-roles-text": "Geen rollen gevonden", "role-details": "Details van de rol", "add-role-text": "Nieuwe rol toevoegen", @@ -4393,8 +4393,8 @@ "delete-roles": "Rollen verwijderen", "delete-role-title": "Weet je zeker dat je de rol '{{roleName}}' wilt verwijderen?", "delete-role-text": "Opgelet, na de bevestiging worden de rol en alle gerelateerde gegevens onherstelbaar.", - "delete-roles-title": "Weet u zeker dat u { count, plural, =1 {1 role} andere {# roles} } wilt verwijderen?", - "delete-roles-action-title": "Verwijder { count, plural, =1 {1 role} andere {# roles} }", + "delete-roles-title": "Weet u zeker dat u { count, plural, =1 {1 role} other {# roles} } wilt verwijderen?", + "delete-roles-action-title": "Verwijder { count, plural, =1 {1 role} other {# roles} }", "delete-roles-text": "Opgelet, na de bevestiging worden alle geselecteerde rollen verwijderd en worden alle gerelateerde gegevens onherstelbaar.", "role-type": "Soort rol", "role-type-required": "Roltype is vereist.", @@ -4433,11 +4433,11 @@ "user-group-owner": "Eigenaar van gebruikersgroep", "edit": "Machtigingen bewerken", "delete": "Machtigingen verwijderen", - "selected-group-permissions": "{ count, plural, =1 {1 group permission} andere {# group permissions} } geselecteerd", + "selected-group-permissions": "{ count, plural, =1 {1 group permission} other {# group permissions} } geselecteerd", "delete-group-permission-title": "Weet je zeker dat je de groepsrechten '{{roleName}}' wilt verwijderen?", "delete-group-permission-text": "Opgelet, na de bevestiging worden de groepstoestemming en alle gerelateerde gegevens onherstelbaar.", "delete-group-permission": "Groepsmachtiging verwijderen", - "delete-group-permissions-title": "Weet u zeker dat u { count, plural, =1 {1 group permission} andere {# group permission} } wilt verwijderen?", + "delete-group-permissions-title": "Weet u zeker dat u { count, plural, =1 {1 group permission} other {# group permission} } wilt verwijderen?", "delete-group-permissions-text": "Opgelet, na de bevestiging worden alle geselecteerde groepsmachtigingen verwijderd en verliezen de bijbehorende gebruikers de toegang tot gespecificeerde bronnen.", "delete-group-permissions": "Groepsmachtigingen verwijderen", "add-group-permission": "Groepsmachtiging toevoegen", @@ -4552,10 +4552,10 @@ "view-scheduler-event": "Scheduler event weergeven", "delete-scheduler-event": "Scheduler-event verwijderen", "no-scheduler-events": "Geen planner-gebeurtenissen gevonden", - "selected-scheduler-events": "{ count, plural, =1 {1 scheduler event} andere {# scheduler events} } geselecteerd", + "selected-scheduler-events": "{ count, plural, =1 {1 scheduler event} other {# scheduler events} } geselecteerd", "delete-scheduler-event-title": "Weet u zeker dat u de '{{schedulerEventName}}' van de planningsgebeurtenis wilt verwijderen?", "delete-scheduler-event-text": "Opgelet, na de bevestiging worden de event van de planner en alle gerelateerde gegevens onherstelbaar.", - "delete-scheduler-events-title": "Weet u zeker dat u { count, plural, =1 {1 scheduler event} andere {# scheduler events} } wilt verwijderen?", + "delete-scheduler-events-title": "Weet u zeker dat u { count, plural, =1 {1 scheduler event} other {# scheduler events} } wilt verwijderen?", "delete-scheduler-events-text": "Opgelet, na de bevestiging worden alle geselecteerde scheduler events verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld.", "create": "Scheduler-event maken", "edit": "Scheduler-event bewerken", @@ -4567,10 +4567,10 @@ "repeats": "Herhaalt", "daily": "Dagelijks", "every-n-days": "Elke N dagen", - "every-n-days-text": "Elke { days, plural, =1 {day} andere {# days} }", + "every-n-days-text": "Elke { days, plural, =1 {day} other {# days} }", "weekly": "Wekelijks", "every-n-weeks": "Elke N weken", - "every-n-weeks-text": "Elke { weeks, plural, =1 {week} andere {# weeks} }", + "every-n-weeks-text": "Elke { weeks, plural, =1 {week} other {# weeks} }", "monthly": "Maandelijks", "yearly": "Jaarlijks", "timer": "Op basis van een timer", @@ -4646,9 +4646,9 @@ "seconds": "Seconden", "time-interval-required": "Tijdsinterval is vereist", "time-unit-required": "Tijdseenheid is vereist", - "every-hour": "elke { count, plural, =1 {hour} andere {# hours} }", - "every-minute": "elke { count, plural, =1 {minute} andere {# minutes} }", - "every-second": "elke { count, plural, =1 {second} andere {# seconds} }", + "every-hour": "elke { count, plural, =1 {hour} other {# hours} }", + "every-minute": "elke { count, plural, =1 {minute} other {# minutes} }", + "every-second": "elke { count, plural, =1 {second} other {# seconds} }", "invalid-time": "Ongeldige tijd" }, "report": { @@ -4692,12 +4692,12 @@ "name": "Naam", "type": "Type", "created_customer": "Gemaakt door de klant", - "selected-blob-entities": "{ count, plural, =1 {1 file} andere {# files} } geselecteerd", + "selected-blob-entities": "{ count, plural, =1 {1 file} other {# files} } geselecteerd", "download-blob-entity": "Bestand downloaden", "delete-blob-entity": "Bestand verwijderen", "delete-blob-entity-title": "Weet u zeker dat u bestand '{{blobEntityName}}' wilt verwijderen?", "delete-blob-entity-text": "Opgelet, na de bevestiging worden de gegevens van het bestand onherstelbaar.", - "delete-blob-entities-title": "Weet u zeker dat u { count, plural, =1 {1 file} andere {# files} } wilt verwijderen?", + "delete-blob-entities-title": "Weet u zeker dat u { count, plural, =1 {1 file} other {# files} } wilt verwijderen?", "delete-blob-entities-text": "Opgelet, na de bevestiging worden alle geselecteerde bestanden verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld." }, "timezone": { @@ -4738,9 +4738,9 @@ "submit-strategy-type-required": "Strategietype indienen is vereist!", "processing-strategy-type-required": "Het type verwerkingsstrategie is vereist!", "queues": "Queues", - "selected-queues": "{ count, plural, =1 {1 queue} andere {# queues} } geselecteerd", + "selected-queues": "{ count, plural, =1 {1 queue} other {# queues} } geselecteerd", "delete-queue-title": "Weet je zeker dat je de queue '{{queueName}}' wilt verwijderen?", - "delete-queues-title": "Weet u zeker dat u { count, plural, =1 {1 queue} andere {# queues} } wilt verwijderen?", + "delete-queues-title": "Weet u zeker dat u { count, plural, =1 {1 queue} other {# queues} } wilt verwijderen?", "delete-queue-text": "Opgelet, na de bevestiging worden de queue en alle gerelateerde gegevens onherstelbaar.", "delete-queues-text": "Na de bevestiging worden alle geselecteerde queues verwijderd en zijn ze niet meer toegankelijk.", "search": "Queue zoeken", @@ -4824,8 +4824,8 @@ "title-max-length": "Titel moet kleiner zijn dan 256 tekens", "delete-tenant-title": "Weet u zeker dat u de tenant '{{tenantTitle}}' wilt verwijderen?", "delete-tenant-text": "Let op, na de bevestiging worden de tenant en alle gerelateerde gegevens onherstelbaar.", - "delete-tenants-title": "Weet u zeker dat u { count, plural, =1 {1 tenant} andere {# tenants} } wilt verwijderen?", - "delete-tenants-action-title": "Verwijder { count, plural, =1 {1 tenant} andere {# tenants} }", + "delete-tenants-title": "Weet u zeker dat u { count, plural, =1 {1 tenant} other {# tenants} } wilt verwijderen?", + "delete-tenants-action-title": "Verwijder { count, plural, =1 {1 tenant} other {# tenants} }", "delete-tenants-text": "Let op, na de bevestiging worden alle geselecteerde tenants verwijderd en worden alle gerelateerde gegevens onherstelbaar.", "title": "Titel", "title-required": "Titel is vereist.", @@ -4840,7 +4840,7 @@ "allow-white-labeling": "White Labeling toestaan", "allow-customer-white-labeling": "White labeling van klanten toestaan", "search": "Tenants zoeken", - "selected-tenants": "{ count, plural, =1 {1 tenant} andere {# tenants} } geselecteerd", + "selected-tenants": "{ count, plural, =1 {1 tenant} other {# tenants} } geselecteerd", "isolated-tb-rule-engine": "Verwerking in geïsoleerde ThingsBoard Rule Engine-container", "isolated-tb-rule-engine-details": "Vereist afzonderlijke microservice(s) per geïsoleerde tenant" }, @@ -4854,7 +4854,7 @@ "no-tenant-profiles-text": "Geen tenantsprofielen gevonden", "name-max-length": "Naam moet kleiner zijn dan 256 tekens", "search": "Tentant profielen zoeken", - "selected-tenant-profiles": "{ count, plural, =1 {1 tenant profile} andere {# tenant profiles} } geselecteerd", + "selected-tenant-profiles": "{ count, plural, =1 {1 tenant profile} other {# tenant profiles} } geselecteerd", "no-tenant-profiles-matching": "Er zijn geen tenantprofielen gevonden die overeenkomen met '{{entity}}'.", "tenant-profile-required": "Huurdersprofiel is vereist", "idCopiedMessage": "De profiel-id van de tenant is gekopieerd naar het klembord", @@ -4869,7 +4869,7 @@ "default": "Verstek", "delete-tenant-profile-title": "Weet u zeker dat u het tenantsprofiel '{{tenantProfileName}}' wilt verwijderen?", "delete-tenant-profile-text": "Let op, na de bevestiging wordt het tenantsprofiel en alle gerelateerde gegevens onherstelbaar.", - "delete-tenant-profiles-title": "Weet u zeker dat u { count, plural, =1 {1 tenant profile} andere {# tenant profiles} } wilt verwijderen?", + "delete-tenant-profiles-title": "Weet u zeker dat u { count, plural, =1 {1 tenant profile} other {# tenant profiles} } wilt verwijderen?", "delete-tenant-profiles-text": "Opgelet, na de bevestiging worden alle geselecteerde tenantsprofielen verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld.", "set-default-tenant-profile-title": "Weet u zeker dat u het tenantsprofiel standaard '{{tenantProfileName}}' wilt maken?", "set-default-tenant-profile-text": "Na de bevestiging wordt het tenantsprofiel als standaard gemarkeerd en wordt het gebruikt voor nieuwe tenants waarvoor geen profiel is opgegeven.", @@ -5028,10 +5028,10 @@ } }, "timeinterval": { - "seconds-interval": "{ seconds, plural, =1 {1 second} andere {# seconds} }", - "minutes-interval": "{ minutes, plural, =1 {1 minute} andere {# minutes} }", - "hours-interval": "{ hours, plural, =1 {1 hour} andere {# hours} }", - "days-interval": "{ days, plural, =1 {1 day} andere {# days} }", + "seconds-interval": "{ seconds, plural, =1 {1 second} other {# seconds} }", + "minutes-interval": "{ minutes, plural, =1 {1 minute} other {# minutes} }", + "hours-interval": "{ hours, plural, =1 {1 hour} other {# hours} }", + "days-interval": "{ days, plural, =1 {1 day} other {# days} }", "days": "Dagen", "hours": "Uren", "minutes": "Notulen", @@ -5072,19 +5072,19 @@ "days": "Dagen" }, "timewindow": { - "years": "{ years, plural, =1 { year } andere {# years } }", - "months": "{ months, plural, =1 { month } andere {# months } }", - "weeks": "{ weeks, plural, =1 { week } andere {# weeks } }", - "days": "{ days, plural, =1 { day } andere {# days } }", - "hours": "{ hours, plural, =0 { hour } =1 {1 hour } andere {# hours } }", + "years": "{ years, plural, =1 { year } other {# years } }", + "months": "{ months, plural, =1 { month } other {# months } }", + "weeks": "{ weeks, plural, =1 { week } other {# weeks } }", + "days": "{ days, plural, =1 { day } other {# days } }", + "hours": "{ hours, plural, =0 { hour } =1 {1 hour } other {# hours } }", "hr": "{{ hr }} uur", - "minutes": "{ minutes, plural, =0 { minute } =1 {1 minute } andere {# minutes } }", + "minutes": "{ minutes, plural, =0 { minute } =1 {1 minute } other {# minutes } }", "min": "{{ min }} min", - "seconds": "{ seconds, plural, =0 { second } =1 {1 second } andere {# seconds } }", + "seconds": "{ seconds, plural, =0 { second } =1 {1 second } other {# seconds } }", "sec": "{{ sec }} sec", "short": { - "days": "{ days, plural, =1 {1 day } andere {# days } }", - "hours": "{ hours, plural, =1 {1 hour } andere {# hours } }", + "days": "{ days, plural, =1 {1 day } other {# days } }", + "hours": "{ hours, plural, =1 {1 hour } other {# hours } }", "minutes": "{{minutes}} min", "seconds": "{{seconds}} sec" }, @@ -5123,8 +5123,8 @@ "delete-users": "Gebruikers verwijderen", "delete-user-title": "Weet u zeker dat u de '{{userEmail}}' van de gebruiker wilt verwijderen?", "delete-user-text": "Opgelet, na de bevestiging worden de gebruiker en alle gerelateerde gegevens onherstelbaar.", - "delete-users-title": "Weet u zeker dat u { count, plural, =1 {1 user} andere {# users} } wilt verwijderen?", - "delete-users-action-title": "Verwijder { count, plural, =1 {1 user} andere {# users} }", + "delete-users-title": "Weet u zeker dat u { count, plural, =1 {1 user} other {# users} } wilt verwijderen?", + "delete-users-action-title": "Verwijder { count, plural, =1 {1 user} other {# users} }", "delete-users-text": "Opgelet, na de bevestiging worden alle geselecteerde gebruikers verwijderd en kunnen alle gerelateerde gegevens niet meer worden hersteld.", "activation-email-sent-message": "Activeringsmail is succesvol verzonden!", "resend-activation": "Activering opnieuw verzenden", @@ -5151,12 +5151,12 @@ "login-as-customer-user": "Inloggen als klantgebruiker", "select-group-to-add": "Selecteer doelgroep om geselecteerde gebruikers toe te voegen", "select-group-to-move": "Selecteer de doelgroep om geselecteerde gebruikers te verplaatsen", - "remove-users-from-group": "Weet je zeker dat je { count, plural, =1 {1 user} andere {# users} } uit groep '{{entityGroup}}' wilt verwijderen?", + "remove-users-from-group": "Weet je zeker dat je { count, plural, =1 {1 user} other {# users} } uit groep '{{entityGroup}}' wilt verwijderen?", "group": "Groep gebruikers", - "list-of-groups": "{ count, plural, =1 {One user group} andere {List of # user groups} }", + "list-of-groups": "{ count, plural, =1 {One user group} other {List of # user groups} }", "group-name-starts-with": "Gebruikersgroepen waarvan de naam begint met '{{prefix}}'", "search": "Gebruikers zoeken", - "selected-users": "{ count, plural, =1 {1 user} andere {# users} } geselecteerd", + "selected-users": "{ count, plural, =1 {1 user} other {# users} } geselecteerd", "disable-account": "Gebruikersaccount uitschakelen", "enable-account": "Gebruikersaccount inschakelen", "enable-account-message": "Gebruikersaccount is succesvol ingeschakeld!", @@ -5228,7 +5228,7 @@ "previous-difference": "Vorig verschil", "next-difference": "Volgende Verschil", "current": "Actueel", - "differences": "{ count, plural, =1 {1 difference} andere {# differences} }", + "differences": "{ count, plural, =1 {1 difference} other {# differences} }", "create-entities-version": "Versie van entiteiten maken", "default-sync-strategy": "Standaard synchronisatiestrategie", "sync-strategy-merge": "Fuseren", @@ -5241,7 +5241,7 @@ "no-entities-to-restore-prompt": "Geef entiteiten op die u wilt herstellen", "add-entity-type": "Entiteitstype toevoegen", "remove-all": "Alles verwijderen", - "version-create-result": "{ added, plural, =0 {No entities} =1 {1 entity} andere {# entities} } toegevoegd.
{ modified, plural, =0 {No entities} =1 {1 entity} andere {# entities} } gewijzigd.
{ removed, plural, =0 {No entities} =1 {1 entity} andere {# entities} } verwijderd.", + "version-create-result": "{ added, plural, =0 {No entities} =1 {1 entity} other {# entities} } toegevoegd.
{ modified, plural, =0 {No entities} =1 {1 entity} other {# entities} } gewijzigd.
{ removed, plural, =0 {No entities} =1 {1 entity} other {# entities} } verwijderd.", "remove-other-entities": "Andere entiteiten verwijderen", "find-existing-entity-by-name": "Bestaande entiteit zoeken op naam", "restore-entities-from-version": "Entiteiten herstellen vanaf versie '{{versionName}}'", @@ -5250,9 +5250,9 @@ "created": "{{created}} gemaakt", "updated": "{{updated}} geüpdatet", "deleted": "{{deleted}} geschrapt", - "groups-created": "{ created, plural, =1 {1 group} andere {# groups} } gemaakt", - "groups-updated": "{ updated, plural, =1 {1 group} andere {# groups} } bijgewerkt", - "groups-deleted": "{ deleted, plural, =1 {1 group} andere {# groups} } verwijderd", + "groups-created": "{ created, plural, =1 {1 group} other {# groups} } gemaakt", + "groups-updated": "{ updated, plural, =1 {1 group} other {# groups} } bijgewerkt", + "groups-deleted": "{ deleted, plural, =1 {1 group} other {# groups} } verwijderd", "remove-other-entities-confirm-text": "Opgelet! Hiermee worden alle huidige entiteiten
die niet aanwezig zijn in de versie die u wilt herstellen, permanent verwijderd.

Typ andere entiteiten verwijderen om te bevestigen.", "auto-commit-to-branch": "Automatisch vastleggen op {{ branch }} filiaal", "default-create-entity-version-name": "{{entityName}} update", @@ -5414,8 +5414,8 @@ "widgets-bundle-details": "Details van de widgetsbundel", "delete-widgets-bundle-title": "Weet je zeker dat je de widgets bundel '{{widgetsBundleTitle}}' wilt verwijderen?", "delete-widgets-bundle-text": "Opgelet, na de bevestiging worden de widgetbundel en alle gerelateerde gegevens onherstelbaar.", - "delete-widgets-bundles-title": "Weet u zeker dat u { count, plural, =1 {1 widgets bundle} andere {# widgets bundles} } wilt verwijderen?", - "delete-widgets-bundles-action-title": "Verwijder { count, plural, =1 {1 widgets bundle} andere {# widgets bundles} }", + "delete-widgets-bundles-title": "Weet u zeker dat u { count, plural, =1 {1 widgets bundle} other {# widgets bundles} } wilt verwijderen?", + "delete-widgets-bundles-action-title": "Verwijder { count, plural, =1 {1 widgets bundle} other {# widgets bundles} }", "delete-widgets-bundles-text": "Opgelet, na de bevestiging worden alle geselecteerde widgetbundels verwijderd en worden alle gerelateerde gegevens onherstelbaar.", "no-widgets-bundles-matching": "Er zijn geen widgetbundels gevonden die overeenkomen met '{{widgetsBundle}}'.", "widgets-bundle-required": "Widgets-bundel is vereist.", @@ -5427,7 +5427,7 @@ "widgets-bundle-file": "Widgets bundel bestand", "invalid-widgets-bundle-file-error": "Kan widgetbundel niet importeren: Ongeldige widgetbundel gegevensstructuur.", "search": "Widgetbundels zoeken", - "selected-widgets-bundles": "{ count, plural, =1 {1 widgets bundle} andere {# widgets bundles} } geselecteerd", + "selected-widgets-bundles": "{ count, plural, =1 {1 widgets bundle} other {# widgets bundles} } geselecteerd", "open-widgets-bundle": "Widgets-bundel openen", "loading-widgets-bundles": "Widgets bundels worden geladen..." }, @@ -5462,7 +5462,7 @@ "legend": "Legende", "display-legend": "Legende weergeven", "datasources": "Gegevensbronnen", - "maximum-datasources": "Maximaal { count, plural, =1 {1 datasource is allowed.} andere {# datasources are allowed} }", + "maximum-datasources": "Maximaal { count, plural, =1 {1 datasource is allowed.} other {# datasources are allowed} }", "timeseries-key-error": "Er moet ten minste één tijdreeksgegevenssleutel worden opgegeven", "datasource-type": "Type", "datasource-parameters": "Parameters", @@ -6172,7 +6172,7 @@ }, "label-widget": { "label-pattern": "Patroon", - "label-pattern-hint": "Tip: bijv. 'Text <span style=\"color: #000;\">${keyName<span style=\"color: #000;\">} eenheden.' of <span style=\"color: #000;\">${#<key index><span style=\"color: #000;\">} eenheden'", + "label-pattern-hint": "Tip: bijv. 'Text ${keyName} eenheden.' of ${#<key index>}", "label-pattern-required": "Patroon is vereist", "label-position": "Positie (percentage ten opzichte van achtergrond)", "x-pos": "X", @@ -6722,10 +6722,10 @@ "title": "Licentie info", "view-all": "Alles weergeven", "license-portal": "Licentie portaal", - "devices-info": "{count} / { max, plural, =0 {∞} andere {#} } Devices", - "assets-info": "{count} / { max, plural, =0 {∞} andere {#} } Assets", - "dashboards-info": "{count} / { max, plural, =0 {∞} andere {#} } Dashboards", - "integrations-info": "{count} / { max, plural, =0 {∞} andere {#} } Integraties", + "devices-info": "{count} / { max, plural, =0 {∞} other {#} } Devices", + "assets-info": "{count} / { max, plural, =0 {∞} other {#} } Assets", + "dashboards-info": "{count} / { max, plural, =0 {∞} other {#} } Dashboards", + "integrations-info": "{count} / { max, plural, =0 {∞} other {#} } Integraties", "community-support": "Ondersteuning van de gemeenschap", "white-labeling": "White labelling", "unlimited-datapoints-and-messages": "Onbeperkt aantal datapunten en berichten", diff --git a/ui-ngx/src/assets/locale/locale.constant-pl_PL.json b/ui-ngx/src/assets/locale/locale.constant-pl_PL.json index bec945acf9..8f35a7daa2 100644 --- a/ui-ngx/src/assets/locale/locale.constant-pl_PL.json +++ b/ui-ngx/src/assets/locale/locale.constant-pl_PL.json @@ -1832,9 +1832,10 @@ "condition-duration-value-required": "Wartość czasu trwania jest wymagana.", "condition-duration-time-unit-required": "Jednostka czasu jest wymagana.", "advanced-settings": "Zaawansowane ustawienia", - "alarm-rule-details": "Szczegóły", - "alarm-rule-details-hint": "Wskazówka: użyj ${NazwaKlucza} w celu zastąpienia wartości atrybutu lub kluczy telemetrycznych używanych w warunku reguły alarmowej.", - "add-alarm-rule-details": "Dodaj szczegóły", + "alarm-rule-additional-info": "Szczegóły", + "edit-alarm-rule-additional-info": "Extra informatie bewerken", + "alarm-rule-additional-info-placeholder": "Komentarze i poprawki należy wprowadzić tutaj, aby wyświetlić je w szczegółach alarmu w sekcji Dodatkowe informacje.", + "alarm-rule-additional-info-hint": "Wskazówka: użyj ${NazwaKlucza} w celu zastąpienia wartości atrybutu lub kluczy telemetrycznych używanych w warunku reguły alarmowej.", "alarm-rule-mobile-dashboard": "Mobilny panel", "alarm-rule-mobile-dashboard-hint": "Używany przez aplikację mobilną jako panel ze szczegółami alarmów", "alarm-rule-no-mobile-dashboard": "Nie wybrano żadnego panelu", @@ -1844,7 +1845,6 @@ "propagate-alarm-to-owner": "Prześlij alarm do właściciela obiektu (Klienta lub Najemcy)", "propagate-alarm-to-owner-hierarchy": "Propaguj alarm w hierarchii właścicieli jednostek", "propagate-alarm-to-tenant": "Przekaż alarm do Najemcy", - "alarm-details": "Szczegóły alarmu", "alarm-rule-condition": "Warunek reguły alarmowej", "enter-alarm-rule-condition-prompt": "Dodaj warunek reguły alarmowej", "edit-alarm-rule-condition": "Edytuj warunek reguły alarmowej", From 6a36874e37f0146e7b320798bebf85cc0b6c919c Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Tue, 24 Sep 2024 09:08:57 +0300 Subject: [PATCH 15/21] UI: Refactoring SCADA symbols for css animation --- .../scada_symbols/bottom-flow-meter.svg | 13 +++-- .../scada_symbols/bottom-right-elbow-pipe.svg | 48 ++++----------- .../system/scada_symbols/bottom-tee-pipe.svg | 17 +++--- .../system/scada_symbols/centrifugal-pump.svg | 10 ++-- .../json/system/scada_symbols/cross-pipe.svg | 22 ++++--- .../system/scada_symbols/cylindrical-tank.svg | 15 ++--- .../system/scada_symbols/elevated-tank.svg | 9 +-- .../scada_symbols/horizontal-ball-valve.svg | 10 ++-- .../horizontal-inline-flow-meter.svg | 13 +++-- .../system/scada_symbols/horizontal-pipe.svg | 11 ++-- .../system/scada_symbols/horizontal-tank.svg | 15 ++--- .../scada_symbols/horizontal-wheel-valve.svg | 10 ++-- .../scada_symbols/large-cylindrical-tank.svg | 15 ++--- .../large-stand-cylindrical-tank.svg | 15 ++--- .../large-stand-vertical-tank.svg | 15 ++--- .../scada_symbols/large-vertical-tank.svg | 15 ++--- .../json/system/scada_symbols/leak-sensor.svg | 8 +-- .../left-analog-water-level-meter.svg | 4 +- .../scada_symbols/left-bottom-elbow-pipe.svg | 58 +++++-------------- .../system/scada_symbols/left-flow-meter.svg | 13 +++-- .../system/scada_symbols/left-heat-pump.svg | 10 ++-- .../system/scada_symbols/left-motor-pump.svg | 2 +- .../system/scada_symbols/left-tee-pipe.svg | 17 +++--- .../scada_symbols/left-top-elbow-pipe.svg | 48 ++++----------- .../scada_symbols/long-horizontal-pipe.svg | 11 ++-- .../scada_symbols/long-vertical-pipe.svg | 11 ++-- .../data/json/system/scada_symbols/pool.svg | 13 +++-- .../right-analog-water-level-meter.svg | 4 +- .../system/scada_symbols/right-flow-meter.svg | 13 +++-- .../system/scada_symbols/right-heat-pump.svg | 10 ++-- .../system/scada_symbols/right-motor-pump.svg | 2 +- .../system/scada_symbols/right-tee-pipe.svg | 17 +++--- .../scada_symbols/small-left-motor-pump.svg | 2 +- .../scada_symbols/small-right-motor-pump.svg | 8 +-- .../scada_symbols/small-spherical-tank.svg | 15 ++--- .../system/scada_symbols/spherical-tank.svg | 15 ++--- .../scada_symbols/stand-cylindrical-tank.svg | 15 ++--- .../scada_symbols/stand-horizontal-tank.svg | 15 ++--- .../stand-vertical-short-tank.svg | 15 ++--- .../scada_symbols/stand-vertical-tank.svg | 15 ++--- .../system/scada_symbols/top-flow-meter.svg | 13 +++-- .../scada_symbols/top-right-elbow-pipe.svg | 50 ++++------------ .../system/scada_symbols/top-tee-pipe.svg | 17 +++--- .../scada_symbols/vertical-ball-valve.svg | 10 ++-- .../vertical-inline-flow-meter.svg | 13 +++-- .../system/scada_symbols/vertical-pipe.svg | 11 ++-- .../scada_symbols/vertical-short-tank.svg | 15 ++--- .../system/scada_symbols/vertical-tank.svg | 15 ++--- .../scada_symbols/vertical-wheel-valve.svg | 4 +- .../json/system/scada_symbols/waterstop.svg | 12 ++-- 50 files changed, 346 insertions(+), 408 deletions(-) diff --git a/application/src/main/data/json/system/scada_symbols/bottom-flow-meter.svg b/application/src/main/data/json/system/scada_symbols/bottom-flow-meter.svg index 24927d595e..e59a2c8375 100644 --- a/application/src/main/data/json/system/scada_symbols/bottom-flow-meter.svg +++ b/application/src/main/data/json/system/scada_symbols/bottom-flow-meter.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="400" fill="none" version="1.1" viewBox="0 0 400 400"><tb:metadata xmlns=""><![CDATA[{ "title": "Bottom flow meter", "description": "Bottom flow meter component used to display flow related value and render various states. Includes pipe fluid and leak visualizations.", "searchTags": [ @@ -20,7 +19,7 @@ }, { "tag": "border", - "stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", + "stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}", "actions": { "click": { "actionFunction": "ctx.api.callAction(event, 'displayClick');" @@ -38,7 +37,7 @@ }, { "tag": "fluid", - "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}", + "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}", "actions": null }, { @@ -733,7 +732,8 @@ "step": null } ] -} +}]]> + @@ -750,7 +750,7 @@ - + @@ -781,6 +781,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/bottom-right-elbow-pipe.svg b/application/src/main/data/json/system/scada_symbols/bottom-right-elbow-pipe.svg index 4dc6074864..b13b9e5553 100644 --- a/application/src/main/data/json/system/scada_symbols/bottom-right-elbow-pipe.svg +++ b/application/src/main/data/json/system/scada_symbols/bottom-right-elbow-pipe.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{ "title": "Bottom right elbow pipe", "description": "Bottom right elbow pipe with fluid and leak visualizations.", "searchTags": [ @@ -8,7 +7,7 @@ ], "widgetSizeX": 1, "widgetSizeY": 1, - "stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var delta = deltaX * 1.17 * Math.cos(45*Math.PI/180);\n liquidPattern.animate(1000).ease('-').relative(-delta, delta).loop();\n } else {\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}\n\n", + "stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n \n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var duration = 1000 * 1.17;\n return ctx.api.cssAnimate(liquidPattern, duration).relative(deltaX, 0).loop();\n } else {\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n }\n}", "tags": [ { "tag": "center-fluid", @@ -251,7 +250,8 @@ "step": null } ] -} +}]]> + @@ -264,7 +264,7 @@ - + @@ -415,37 +415,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -530,7 +499,12 @@ - + + + + + + diff --git a/application/src/main/data/json/system/scada_symbols/bottom-tee-pipe.svg b/application/src/main/data/json/system/scada_symbols/bottom-tee-pipe.svg index 67eb7d3472..307d12a6d0 100644 --- a/application/src/main/data/json/system/scada_symbols/bottom-tee-pipe.svg +++ b/application/src/main/data/json/system/scada_symbols/bottom-tee-pipe.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{ "title": "Bottom tee pipe", "description": "Bottom tee pipe with configurable left/right/bottom fluid and leak visualizations.", "searchTags": [ @@ -8,7 +7,7 @@ ], "widgetSizeX": 1, "widgetSizeY": 1, - "stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n}\n\n", + "stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}\n", "tags": [ { "tag": "bottom-fluid", @@ -576,7 +575,8 @@ "step": null } ] -} +}]]> + @@ -738,9 +738,12 @@ - - - + + + + + + diff --git a/application/src/main/data/json/system/scada_symbols/centrifugal-pump.svg b/application/src/main/data/json/system/scada_symbols/centrifugal-pump.svg index c1bef78aa5..3e2524bd2f 100644 --- a/application/src/main/data/json/system/scada_symbols/centrifugal-pump.svg +++ b/application/src/main/data/json/system/scada_symbols/centrifugal-pump.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="400" fill="none" version="1.1" viewBox="0 0 400 400"><tb:metadata xmlns=""><![CDATA[{ "title": "Centrifugal pump", "description": "Centrifugal pump with configurable connectors, running animation and various states.", "searchTags": [ @@ -11,12 +10,12 @@ "tags": [ { "tag": "background", - "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}", "actions": null }, { "tag": "center", - "stateRenderFunction": "var running = ctx.values.running;\nvar t = element.timeline();\nif (running) {\n if (!t.active()) {\n if (t.time()) {\n t.play();\n } else {\n ctx.api.animate(element, 2000).ease('-').rotate(360).loop();\n t = element.timeline();\n }\n }\n var speed = ctx.values.rotationAnimationSpeed;\n t.speed(speed);\n} else {\n t.pause();\n}\n", + "stateRenderFunction": "var running = ctx.values.running;\nvar speed = ctx.values.rotationAnimationSpeed;\nvar centerRotate = ctx.api.cssAnimation(element);\nif (running) {\n if (!centerRotate) {\n centerRotate = ctx.api.cssAnimate(element, 2000)\n .rotate(360).loop().speed(speed);\n } else {\n centerRotate.speed(speed).play();\n }\n} else {\n if (centerRotate) {\n centerRotate.pause();\n }\n}\n", "actions": null }, { @@ -415,7 +414,8 @@ "step": null } ] -} +}]]> + diff --git a/application/src/main/data/json/system/scada_symbols/cross-pipe.svg b/application/src/main/data/json/system/scada_symbols/cross-pipe.svg index 9edf91c686..cda15d1023 100644 --- a/application/src/main/data/json/system/scada_symbols/cross-pipe.svg +++ b/application/src/main/data/json/system/scada_symbols/cross-pipe.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{ "title": "Cross pipe", "description": "Cross pipe with configurable left/right/top/bottom fluid and leak visualizations.", "searchTags": [ @@ -8,7 +7,7 @@ ], "widgetSizeX": 1, "widgetSizeY": 1, - "stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n}\n\n", + "stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}\n", "tags": [ { "tag": "bottom-fluid", @@ -741,7 +740,8 @@ "step": null } ] -} +}]]> + @@ -793,7 +793,7 @@ - + @@ -1013,10 +1013,14 @@ - - - - + + + + + + + + diff --git a/application/src/main/data/json/system/scada_symbols/cylindrical-tank.svg b/application/src/main/data/json/system/scada_symbols/cylindrical-tank.svg index 104b7bc864..3b07c31186 100644 --- a/application/src/main/data/json/system/scada_symbols/cylindrical-tank.svg +++ b/application/src/main/data/json/system/scada_symbols/cylindrical-tank.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="1e3" fill="none" version="1.1" viewBox="0 0 600 1e3"><tb:metadata xmlns=""><![CDATA[{ "title": "Cylindrical tank", "description": "Cylindrical tank with current volume value and level visualizations.", "searchTags": [ @@ -24,17 +23,17 @@ }, { "tag": "fluid", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n", "actions": null }, { "tag": "fluid-background", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n", "actions": null }, { "tag": "scale", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "actions": null }, { @@ -560,7 +559,8 @@ "step": null } ] -} +}]]> + @@ -653,7 +653,7 @@ - + @@ -801,6 +801,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/elevated-tank.svg b/application/src/main/data/json/system/scada_symbols/elevated-tank.svg index 7874e627df..9502136bf1 100644 --- a/application/src/main/data/json/system/scada_symbols/elevated-tank.svg +++ b/application/src/main/data/json/system/scada_symbols/elevated-tank.svg @@ -24,17 +24,17 @@ }, { "tag": "fluid", - "stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*895; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*895; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n", "actions": null }, { "tag": "fluid-background", - "stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*895; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*895; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n", "actions": null }, { "tag": "scale", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 265;\n var majorIntervalLength = 895 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(825, y, 857, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 815, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(837, minorY, 857, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 265;\n var majorIntervalLength = 895 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(825, y, 857, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 815, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(837, minorY, 857, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "actions": null }, { @@ -900,7 +900,7 @@ - + @@ -1048,6 +1048,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/horizontal-ball-valve.svg b/application/src/main/data/json/system/scada_symbols/horizontal-ball-valve.svg index da140b4383..eb1ee66b24 100644 --- a/application/src/main/data/json/system/scada_symbols/horizontal-ball-valve.svg +++ b/application/src/main/data/json/system/scada_symbols/horizontal-ball-valve.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{ "title": "Horizontal ball valve", "description": "Horizontal ball valve with open/close animation and state colors.", "searchTags": [ @@ -11,7 +10,7 @@ "tags": [ { "tag": "background", - "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.animate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n", + "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.cssAnimate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n", "actions": null }, { @@ -25,7 +24,7 @@ }, { "tag": "wheel", - "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle, originX: 100});\n} else {\n ctx.api.animate(element, 500).transform({rotate: angle, originX: 100});\n element.remember('openAnimate', false);\n}\n", + "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle, originX: 100});\n} else {\n ctx.api.cssAnimate(element, 500).transform({rotate: angle, originX: 100});\n element.remember('openAnimate', false);\n}\n", "actions": null } ], @@ -200,7 +199,8 @@ "step": null } ] -} +}]]> + diff --git a/application/src/main/data/json/system/scada_symbols/horizontal-inline-flow-meter.svg b/application/src/main/data/json/system/scada_symbols/horizontal-inline-flow-meter.svg index 7028a3f854..c20584288a 100644 --- a/application/src/main/data/json/system/scada_symbols/horizontal-inline-flow-meter.svg +++ b/application/src/main/data/json/system/scada_symbols/horizontal-inline-flow-meter.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="200" fill="none" version="1.1" viewBox="0 0 400 200"><tb:metadata xmlns=""><![CDATA[{ "title": "Horizontal inline flow meter", "description": "Horizontal inline flow meter component used to display flow related value and render various states. Includes pipe fluid and leak visualizations.", "searchTags": [ @@ -20,7 +19,7 @@ }, { "tag": "border", - "stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", + "stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}", "actions": { "click": { "actionFunction": "ctx.api.callAction(event, 'displayClick');" @@ -38,7 +37,7 @@ }, { "tag": "fluid", - "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}", + "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}", "actions": null }, { @@ -733,8 +732,9 @@ "step": null } ] -} - +}]]> + + @@ -765,6 +765,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/horizontal-pipe.svg b/application/src/main/data/json/system/scada_symbols/horizontal-pipe.svg index f166c403b2..a352789371 100644 --- a/application/src/main/data/json/system/scada_symbols/horizontal-pipe.svg +++ b/application/src/main/data/json/system/scada_symbols/horizontal-pipe.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{ "title": "Horizontal pipe", "description": "Horizontal pipe with fluid and leak visualizations.", "searchTags": [ @@ -11,7 +10,7 @@ "tags": [ { "tag": "fluid", - "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}", + "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}", "actions": null }, { @@ -240,7 +239,8 @@ "step": null } ] -} +}]]> + @@ -254,7 +254,7 @@ - + @@ -285,6 +285,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/horizontal-tank.svg b/application/src/main/data/json/system/scada_symbols/horizontal-tank.svg index 02832fdce1..db0906677e 100644 --- a/application/src/main/data/json/system/scada_symbols/horizontal-tank.svg +++ b/application/src/main/data/json/system/scada_symbols/horizontal-tank.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="600" fill="none" version="1.1" viewBox="0 0 1e3 600"><tb:metadata xmlns=""><![CDATA[{ "title": "Horizontal tank", "description": "Horizontal tank with current volume value and level visualizations.", "searchTags": [ @@ -24,17 +23,17 @@ }, { "tag": "fluid", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n", "actions": null }, { "tag": "fluid-background", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n", "actions": null }, { "tag": "scale", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 17;\n var majorIntervalLength = 568 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(715, y, 747, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 705, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(727, minorY, 747, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 17;\n var majorIntervalLength = 568 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(715, y, 747, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 705, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(727, minorY, 747, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "actions": null }, { @@ -555,7 +554,8 @@ "step": null } ] -} +}]]> + @@ -675,7 +675,7 @@ - + @@ -823,6 +823,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/horizontal-wheel-valve.svg b/application/src/main/data/json/system/scada_symbols/horizontal-wheel-valve.svg index a96f61781d..44980119c1 100644 --- a/application/src/main/data/json/system/scada_symbols/horizontal-wheel-valve.svg +++ b/application/src/main/data/json/system/scada_symbols/horizontal-wheel-valve.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="200" fill="none" version="1.1" viewBox="0 0 400 200"><tb:metadata xmlns=""><![CDATA[{ "title": "Horizontal wheel valve", "description": "Horizontal wheel valve with open/close animation and state colors.", "searchTags": [ @@ -11,7 +10,7 @@ "tags": [ { "tag": "background", - "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.animate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n", + "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.cssAnimate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n", "actions": null }, { @@ -25,7 +24,7 @@ }, { "tag": "wheel", - "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle});\n} else {\n ctx.api.animate(element, 500).transform({rotate: angle});\n element.remember('openAnimate', false);\n}\n", + "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle});\n} else {\n ctx.api.cssAnimate(element, 500).transform({rotate: angle});\n element.remember('openAnimate', false);\n}\n", "actions": null } ], @@ -200,7 +199,8 @@ "step": null } ] -} +}]]> + diff --git a/application/src/main/data/json/system/scada_symbols/large-cylindrical-tank.svg b/application/src/main/data/json/system/scada_symbols/large-cylindrical-tank.svg index 33dcc947ba..08ea8a4533 100644 --- a/application/src/main/data/json/system/scada_symbols/large-cylindrical-tank.svg +++ b/application/src/main/data/json/system/scada_symbols/large-cylindrical-tank.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="1e3" fill="none" version="1.1" viewBox="0 0 1e3 1e3"><tb:metadata xmlns=""><![CDATA[{ "title": "Large cylindrical tank", "description": " Large cylindrical tank with current volume value and level visualizations.", "searchTags": [ @@ -24,17 +23,17 @@ }, { "tag": "fluid", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n", "actions": null }, { "tag": "fluid-background", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n", "actions": null }, { "tag": "scale", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 60;\n var majorIntervalLength = 910 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(656, y, 688, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 646, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(668, minorY, 688, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 60;\n var majorIntervalLength = 910 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(656, y, 688, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 646, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(668, minorY, 688, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "actions": null }, { @@ -560,7 +559,8 @@ "step": null } ] -} +}]]> + @@ -660,7 +660,7 @@ - + @@ -808,6 +808,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/large-stand-cylindrical-tank.svg b/application/src/main/data/json/system/scada_symbols/large-stand-cylindrical-tank.svg index 89b72339c2..c805ec3de3 100644 --- a/application/src/main/data/json/system/scada_symbols/large-stand-cylindrical-tank.svg +++ b/application/src/main/data/json/system/scada_symbols/large-stand-cylindrical-tank.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="1200" fill="none" version="1.1" viewBox="0 0 1e3 1200"><tb:metadata xmlns=""><![CDATA[{ "title": "Large stand cylindrical tank", "description": "Large stand cylindrical tank with current volume value and level visualizations.", "searchTags": [ @@ -25,17 +24,17 @@ }, { "tag": "fluid", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n", "actions": null }, { "tag": "fluid-background", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*910; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n", "actions": null }, { "tag": "scale", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 60;\n var majorIntervalLength = 910 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(656, y, 688, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 646, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(668, minorY, 688, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 60;\n var majorIntervalLength = 910 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(656, y, 688, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 646, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(668, minorY, 688, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "actions": null }, { @@ -561,7 +560,8 @@ "step": null } ] -} +}]]> + @@ -661,7 +661,7 @@ - + @@ -809,6 +809,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/large-stand-vertical-tank.svg b/application/src/main/data/json/system/scada_symbols/large-stand-vertical-tank.svg index 7e5170b582..46fb273d2c 100644 --- a/application/src/main/data/json/system/scada_symbols/large-stand-vertical-tank.svg +++ b/application/src/main/data/json/system/scada_symbols/large-stand-vertical-tank.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="1200" fill="none" version="1.1" viewBox="0 0 1e3 1200"><tb:metadata xmlns=""><![CDATA[{ "title": "Large stand vertical tank", "description": "Large stand tank with current volume value and level visualizations.", "searchTags": [ @@ -25,17 +24,17 @@ }, { "tag": "fluid", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n", "actions": null }, { "tag": "fluid-background", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n", "actions": null }, { "tag": "scale", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 203;\n var majorIntervalLength = 763 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(676, y, 708, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 666, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(688, minorY, 708, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 203;\n var majorIntervalLength = 763 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(676, y, 708, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 666, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(688, minorY, 708, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "actions": null }, { @@ -561,7 +560,8 @@ "step": null } ] -} +}]]> + @@ -663,7 +663,7 @@ - + @@ -811,6 +811,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/large-vertical-tank.svg b/application/src/main/data/json/system/scada_symbols/large-vertical-tank.svg index 5958836557..935e43f7a5 100644 --- a/application/src/main/data/json/system/scada_symbols/large-vertical-tank.svg +++ b/application/src/main/data/json/system/scada_symbols/large-vertical-tank.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1e3" height="1e3" fill="none" version="1.1" viewBox="0 0 1e3 1e3"><tb:metadata xmlns=""><![CDATA[{ "title": "Large vertical tank", "description": "Large vertical tank with current volume value and level visualizations.", "searchTags": [ @@ -24,17 +23,17 @@ }, { "tag": "fluid", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n", "actions": null }, { "tag": "fluid-background", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*763; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n", "actions": null }, { "tag": "scale", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 203;\n var majorIntervalLength = 763 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(676, y, 708, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 666, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(688, minorY, 708, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 203;\n var majorIntervalLength = 763 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(676, y, 708, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 666, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(688, minorY, 708, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "actions": null }, { @@ -560,7 +559,8 @@ "step": null } ] -} +}]]> + @@ -662,7 +662,7 @@ - + @@ -810,6 +810,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/leak-sensor.svg b/application/src/main/data/json/system/scada_symbols/leak-sensor.svg index 506385d7fb..e2859ea90c 100644 --- a/application/src/main/data/json/system/scada_symbols/leak-sensor.svg +++ b/application/src/main/data/json/system/scada_symbols/leak-sensor.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="100" height="100" fill="none" version="1.1" viewBox="0 0 100 100"><tb:metadata xmlns=""><![CDATA[{ "title": "Leak sensor", "description": "Leak sensor", "searchTags": [ @@ -24,7 +23,7 @@ }, { "tag": "icon", - "stateRenderFunction": "var leak = ctx.values.leak;\nif (leak) {\n element.attr({stroke: ctx.properties.leakColor});\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n} else {\n element.attr({stroke: ctx.properties.defaultColor});\n ctx.api.resetAnimation(element);\n}\n", + "stateRenderFunction": "var leak = ctx.values.leak;\nif (leak) {\n element.attr({stroke: ctx.properties.leakColor});\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n} else {\n element.attr({stroke: ctx.properties.defaultColor});\n ctx.api.finishCssAnimation(element);\n}\n", "actions": null } ], @@ -135,7 +134,8 @@ "step": null } ] -} +}]]> + diff --git a/application/src/main/data/json/system/scada_symbols/left-analog-water-level-meter.svg b/application/src/main/data/json/system/scada_symbols/left-analog-water-level-meter.svg index c2b2a32a2d..b3defcd1bf 100644 --- a/application/src/main/data/json/system/scada_symbols/left-analog-water-level-meter.svg +++ b/application/src/main/data/json/system/scada_symbols/left-analog-water-level-meter.svg @@ -19,7 +19,7 @@ }, { "tag": "border", - "stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", + "stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}", "actions": { "click": { "actionFunction": "ctx.api.callAction(event, 'displayClick');" @@ -47,7 +47,7 @@ }, { "tag": "pointer", - "stateRenderFunction": "var valueSet = element.remember('valueSet');\nif (!valueSet) {\n element.remember('valueSet', true);\n element.transform({\n rotate: 0\n });\n}\n\nvar value = ctx.values.value;\nvar fullValue = ctx.values.fullValue;\n\nvar degrees = Math.max(0, Math.min(1, value/fullValue))*179.99;\nvar rotate = element.remember('rotate');\nif (degrees !== rotate) {\n element.remember('rotate', degrees);\n ctx.api.animate(element, 1000).ease('-').transform({rotate: degrees});\n}", + "stateRenderFunction": "var valueSet = element.remember('valueSet');\nif (!valueSet) {\n element.remember('valueSet', true);\n element.transform({\n rotate: 0\n });\n}\n\nvar value = ctx.values.value;\nvar fullValue = ctx.values.fullValue;\n\nvar degrees = Math.max(0, Math.min(1, value/fullValue))*179.99;\nvar rotate = element.remember('rotate');\nif (degrees !== rotate) {\n element.remember('rotate', degrees);\n ctx.api.cssAnimate(element, 500).ease('ease-in').transform({rotate: degrees});\n}", "actions": { "click": { "actionFunction": "ctx.api.callAction(event, 'displayClick');" diff --git a/application/src/main/data/json/system/scada_symbols/left-bottom-elbow-pipe.svg b/application/src/main/data/json/system/scada_symbols/left-bottom-elbow-pipe.svg index 6179153171..8bbe6cf5de 100644 --- a/application/src/main/data/json/system/scada_symbols/left-bottom-elbow-pipe.svg +++ b/application/src/main/data/json/system/scada_symbols/left-bottom-elbow-pipe.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{ "title": "Left bottom elbow pipe", "description": "Left bottom elbow pipe with fluid and leak visualizations.", "searchTags": [ @@ -8,7 +7,7 @@ ], "widgetSizeX": 1, "widgetSizeY": 1, - "stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var delta = deltaX * 1.17 * Math.cos(45*Math.PI/180);\n liquidPattern.animate(1000).ease('-').relative(-delta, delta).loop();\n } else {\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}\n\n", + "stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n \n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var duration = 1000 * 1.17;\n return ctx.api.cssAnimate(liquidPattern, duration).relative(deltaX, 0).loop();\n } else {\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n }\n}", "tags": [ { "tag": "center-fluid", @@ -251,7 +250,8 @@ "step": null } ] -} +}]]> + @@ -264,7 +264,7 @@ - + @@ -415,37 +415,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -530,12 +499,17 @@ - - - - - - + + + + + + + + + + + diff --git a/application/src/main/data/json/system/scada_symbols/left-flow-meter.svg b/application/src/main/data/json/system/scada_symbols/left-flow-meter.svg index 9ea330b68d..d558e81df1 100644 --- a/application/src/main/data/json/system/scada_symbols/left-flow-meter.svg +++ b/application/src/main/data/json/system/scada_symbols/left-flow-meter.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="400" fill="none" version="1.1" viewBox="0 0 400 400"><tb:metadata xmlns=""><![CDATA[{ "title": "Left flow meter", "description": "Left flow meter component used to display flow related value and render various states. Includes pipe fluid and leak visualizations.", "searchTags": [ @@ -20,7 +19,7 @@ }, { "tag": "border", - "stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", + "stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}", "actions": { "click": { "actionFunction": "ctx.api.callAction(event, 'displayClick');" @@ -38,7 +37,7 @@ }, { "tag": "fluid", - "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}", + "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}", "actions": null }, { @@ -733,7 +732,8 @@ "step": null } ] -} +}]]> + @@ -888,7 +888,7 @@ - + @@ -919,6 +919,7 @@ + 0m³/hr diff --git a/application/src/main/data/json/system/scada_symbols/left-heat-pump.svg b/application/src/main/data/json/system/scada_symbols/left-heat-pump.svg index 1fa5013fcc..b682fe2063 100644 --- a/application/src/main/data/json/system/scada_symbols/left-heat-pump.svg +++ b/application/src/main/data/json/system/scada_symbols/left-heat-pump.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="800" height="600" fill="none" version="1.1" viewBox="0 0 800 600"><tb:metadata xmlns=""><![CDATA[{ "title": "Left heat pump", "description": "Left heat pump with configurable connectors, running animation and various states.", "searchTags": [ @@ -26,12 +25,12 @@ }, { "tag": "fan", - "stateRenderFunction": "var running = ctx.values.running;\nvar t = element.timeline();\nif (running) {\n if (!t.active()) {\n if (t.time()) {\n t.play();\n } else {\n ctx.api.animate(element, 2000).ease('-').rotate(360).loop();\n t = element.timeline();\n }\n }\n var speed = ctx.values.rotationAnimationSpeed;\n t.speed(speed);\n} else {\n t.pause();\n}\n", + "stateRenderFunction": "var running = ctx.values.running;\nvar speed = ctx.values.rotationAnimationSpeed;\nvar fanRotate = ctx.api.cssAnimation(element);\nif (running) {\n if (!fanRotate) {\n fanRotate = ctx.api.cssAnimate(element, 2000)\n .rotate(360).loop().speed(speed);\n } else {\n fanRotate.speed(speed).play();\n }\n} else {\n if (fanRotate) {\n fanRotate.pause();\n }\n}\n", "actions": null }, { "tag": "fan-blade", - "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}", "actions": null }, { @@ -673,7 +672,8 @@ "step": null } ] -} +}]]> + diff --git a/application/src/main/data/json/system/scada_symbols/left-motor-pump.svg b/application/src/main/data/json/system/scada_symbols/left-motor-pump.svg index 5be0a454e5..998501a59f 100644 --- a/application/src/main/data/json/system/scada_symbols/left-motor-pump.svg +++ b/application/src/main/data/json/system/scada_symbols/left-motor-pump.svg @@ -10,7 +10,7 @@ "tags": [ { "tag": "background", - "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}", "actions": null }, { diff --git a/application/src/main/data/json/system/scada_symbols/left-tee-pipe.svg b/application/src/main/data/json/system/scada_symbols/left-tee-pipe.svg index d124709a68..20e9beeb71 100644 --- a/application/src/main/data/json/system/scada_symbols/left-tee-pipe.svg +++ b/application/src/main/data/json/system/scada_symbols/left-tee-pipe.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{ "title": "Left tee pipe", "description": "Left tee pipe with configurable left/top/bottom fluid and leak visualizations.", "searchTags": [ @@ -8,7 +7,7 @@ ], "widgetSizeX": 1, "widgetSizeY": 1, - "stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n}\n\n", + "stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}\n", "tags": [ { "tag": "bottom-fluid", @@ -576,7 +575,8 @@ "step": null } ] -} +}]]> + @@ -756,9 +756,12 @@ - - - + + + + + + diff --git a/application/src/main/data/json/system/scada_symbols/left-top-elbow-pipe.svg b/application/src/main/data/json/system/scada_symbols/left-top-elbow-pipe.svg index aa3fe45323..1562f87762 100644 --- a/application/src/main/data/json/system/scada_symbols/left-top-elbow-pipe.svg +++ b/application/src/main/data/json/system/scada_symbols/left-top-elbow-pipe.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{ "title": "Left top elbow pipe", "description": "Left top elbow pipe with fluid and leak visualizations.", "searchTags": [ @@ -8,7 +7,7 @@ ], "widgetSizeX": 1, "widgetSizeY": 1, - "stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var delta = deltaX * 1.17 * Math.cos(45*Math.PI/180);\n liquidPattern.animate(1000).ease('-').relative(delta, -delta).loop();\n } else {\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}\n\n", + "stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n \n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var duration = 1000 * 1.17;\n return ctx.api.cssAnimate(liquidPattern, duration).relative(deltaX, 0).loop();\n } else {\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n }\n}", "tags": [ { "tag": "center-fluid", @@ -251,7 +250,8 @@ "step": null } ] -} +}]]> + @@ -264,7 +264,7 @@ - + @@ -415,37 +415,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -530,7 +499,12 @@ - + + + + + + diff --git a/application/src/main/data/json/system/scada_symbols/long-horizontal-pipe.svg b/application/src/main/data/json/system/scada_symbols/long-horizontal-pipe.svg index bd3b223251..57aa88e415 100644 --- a/application/src/main/data/json/system/scada_symbols/long-horizontal-pipe.svg +++ b/application/src/main/data/json/system/scada_symbols/long-horizontal-pipe.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="200" fill="none" version="1.1" viewBox="0 0 400 200"><tb:metadata xmlns=""><![CDATA[{ "title": "Long horizontal pipe", "description": "Long horizontal pipe with fluid and leak visualizations.", "searchTags": [ @@ -11,7 +10,7 @@ "tags": [ { "tag": "fluid", - "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}", + "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}", "actions": null }, { @@ -240,7 +239,8 @@ "step": null } ] -} +}]]> + @@ -254,7 +254,7 @@ - + @@ -285,6 +285,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/long-vertical-pipe.svg b/application/src/main/data/json/system/scada_symbols/long-vertical-pipe.svg index 769ae94270..f926c532b5 100644 --- a/application/src/main/data/json/system/scada_symbols/long-vertical-pipe.svg +++ b/application/src/main/data/json/system/scada_symbols/long-vertical-pipe.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="400" fill="none" version="1.1" viewBox="0 0 200 400"><tb:metadata xmlns=""><![CDATA[{ "title": "Long vertical pipe", "description": "Long vertical pipe with fluid and leak visualizations.", "searchTags": [ @@ -11,7 +10,7 @@ "tags": [ { "tag": "fluid", - "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}", + "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}", "actions": null }, { @@ -240,7 +239,8 @@ "step": null } ] -} +}]]> + @@ -254,7 +254,7 @@ - + @@ -285,6 +285,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/pool.svg b/application/src/main/data/json/system/scada_symbols/pool.svg index 86bf0d5907..57086a7a71 100644 --- a/application/src/main/data/json/system/scada_symbols/pool.svg +++ b/application/src/main/data/json/system/scada_symbols/pool.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2400" height="800" fill="none" version="1.1" viewBox="0 0 2400 800"><tb:metadata xmlns=""><![CDATA[{ "title": "Pool", "description": "Pool with current volume value and level visualizations.", "searchTags": [ @@ -24,12 +23,12 @@ }, { "tag": "fluid", - "stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*740; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*740; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n", "actions": null }, { "tag": "fluid-background", - "stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*740; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.transparent) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*740; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n", "actions": null }, { @@ -279,7 +278,8 @@ "step": null } ] -} +}]]> + @@ -324,7 +324,7 @@ - + @@ -472,6 +472,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/right-analog-water-level-meter.svg b/application/src/main/data/json/system/scada_symbols/right-analog-water-level-meter.svg index 739505d421..7c942b6fe5 100644 --- a/application/src/main/data/json/system/scada_symbols/right-analog-water-level-meter.svg +++ b/application/src/main/data/json/system/scada_symbols/right-analog-water-level-meter.svg @@ -19,7 +19,7 @@ }, { "tag": "border", - "stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", + "stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}", "actions": { "click": { "actionFunction": "ctx.api.callAction(event, 'displayClick');" @@ -47,7 +47,7 @@ }, { "tag": "pointer", - "stateRenderFunction": "var valueSet = element.remember('valueSet');\nif (!valueSet) {\n element.remember('valueSet', true);\n element.transform({\n rotate: 0\n });\n}\n\nvar value = ctx.values.value;\nvar fullValue = ctx.values.fullValue;\n\nvar degrees = Math.max(0, Math.min(1, value/fullValue))*179.99;\nvar rotate = element.remember('rotate');\nif (degrees !== rotate) {\n element.remember('rotate', degrees);\n ctx.api.animate(element, 1000).ease('-').transform({rotate: degrees});\n}", + "stateRenderFunction": "var valueSet = element.remember('valueSet');\nif (!valueSet) {\n element.remember('valueSet', true);\n element.transform({\n rotate: 0\n });\n}\n\nvar value = ctx.values.value;\nvar fullValue = ctx.values.fullValue;\n\nvar degrees = Math.max(0, Math.min(1, value/fullValue))*179.99;\nvar rotate = element.remember('rotate');\nif (degrees !== rotate) {\n element.remember('rotate', degrees);\n ctx.api.cssAnimate(element, 500).ease('ease-in').transform({rotate: degrees});\n}", "actions": { "click": { "actionFunction": "ctx.api.callAction(event, 'displayClick');" diff --git a/application/src/main/data/json/system/scada_symbols/right-flow-meter.svg b/application/src/main/data/json/system/scada_symbols/right-flow-meter.svg index 11684d2cb8..9fc751851a 100644 --- a/application/src/main/data/json/system/scada_symbols/right-flow-meter.svg +++ b/application/src/main/data/json/system/scada_symbols/right-flow-meter.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="400" fill="none" version="1.1" viewBox="0 0 400 400"><tb:metadata xmlns=""><![CDATA[{ "title": "Right flow meter", "description": "Right flow meter component used to display flow related value and render various states. Includes pipe fluid and leak visualizations.", "searchTags": [ @@ -20,7 +19,7 @@ }, { "tag": "border", - "stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", + "stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}", "actions": { "click": { "actionFunction": "ctx.api.callAction(event, 'displayClick');" @@ -38,7 +37,7 @@ }, { "tag": "fluid", - "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}", + "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}", "actions": null }, { @@ -733,7 +732,8 @@ "step": null } ] -} +}]]> + @@ -888,7 +888,7 @@ - + @@ -919,6 +919,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/right-heat-pump.svg b/application/src/main/data/json/system/scada_symbols/right-heat-pump.svg index f4b718eeb4..32007b18f7 100644 --- a/application/src/main/data/json/system/scada_symbols/right-heat-pump.svg +++ b/application/src/main/data/json/system/scada_symbols/right-heat-pump.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="800" height="600" fill="none" version="1.1" viewBox="0 0 800 600"><tb:metadata xmlns=""><![CDATA[{ "title": "Right heat pump", "description": "Right heat pump with configurable connectors, running animation and various states.", "searchTags": [ @@ -26,12 +25,12 @@ }, { "tag": "fan", - "stateRenderFunction": "var running = ctx.values.running;\nvar t = element.timeline();\nif (running) {\n if (!t.active()) {\n if (t.time()) {\n t.play();\n } else {\n ctx.api.animate(element, 2000).ease('-').rotate(360).loop();\n t = element.timeline();\n }\n }\n var speed = ctx.values.rotationAnimationSpeed;\n t.speed(speed);\n} else {\n t.pause();\n}\n", + "stateRenderFunction": "var running = ctx.values.running;\nvar speed = ctx.values.rotationAnimationSpeed;\nvar fanRotate = ctx.api.cssAnimation(element);\nif (running) {\n if (!fanRotate) {\n fanRotate = ctx.api.cssAnimate(element, 2000)\n .rotate(360).loop().speed(speed);\n } else {\n fanRotate.speed(speed).play();\n }\n} else {\n if (fanRotate) {\n fanRotate.pause();\n }\n}", "actions": null }, { "tag": "fan-blade", - "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}", "actions": null }, { @@ -673,7 +672,8 @@ "step": null } ] -} +}]]> + diff --git a/application/src/main/data/json/system/scada_symbols/right-motor-pump.svg b/application/src/main/data/json/system/scada_symbols/right-motor-pump.svg index 7ada9a91e3..bb52315f1b 100644 --- a/application/src/main/data/json/system/scada_symbols/right-motor-pump.svg +++ b/application/src/main/data/json/system/scada_symbols/right-motor-pump.svg @@ -10,7 +10,7 @@ "tags": [ { "tag": "background", - "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}", "actions": null }, { diff --git a/application/src/main/data/json/system/scada_symbols/right-tee-pipe.svg b/application/src/main/data/json/system/scada_symbols/right-tee-pipe.svg index fce69fa7a3..88aebed74d 100644 --- a/application/src/main/data/json/system/scada_symbols/right-tee-pipe.svg +++ b/application/src/main/data/json/system/scada_symbols/right-tee-pipe.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{ "title": "Right tee pipe", "description": "Right tee pipe with configurable right/top/bottom fluid and leak visualizations.", "searchTags": [ @@ -8,7 +7,7 @@ ], "widgetSizeX": 1, "widgetSizeY": 1, - "stateRenderFunction": "var rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n}\n\n", + "stateRenderFunction": "var rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\nvar bottomLiquidPattern = prepareLiquidPattern('bottom-fluid');\n\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\nupdateLiquidPatternAnimation(bottomLiquidPattern, 'bottom');\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}\n", "tags": [ { "tag": "bottom-fluid", @@ -576,7 +575,8 @@ "step": null } ] -} +}]]> + @@ -756,9 +756,12 @@ - - - + + + + + + diff --git a/application/src/main/data/json/system/scada_symbols/small-left-motor-pump.svg b/application/src/main/data/json/system/scada_symbols/small-left-motor-pump.svg index 85e617ff22..196f032511 100644 --- a/application/src/main/data/json/system/scada_symbols/small-left-motor-pump.svg +++ b/application/src/main/data/json/system/scada_symbols/small-left-motor-pump.svg @@ -10,7 +10,7 @@ "tags": [ { "tag": "background", - "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}", "actions": null }, { diff --git a/application/src/main/data/json/system/scada_symbols/small-right-motor-pump.svg b/application/src/main/data/json/system/scada_symbols/small-right-motor-pump.svg index 7b9b1c260d..171fbdfca1 100644 --- a/application/src/main/data/json/system/scada_symbols/small-right-motor-pump.svg +++ b/application/src/main/data/json/system/scada_symbols/small-right-motor-pump.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="400" fill="none" version="1.1" viewBox="0 0 600 400"><tb:metadata xmlns=""><![CDATA[{ "title": "Small right motor pump", "description": "Small right motor pump with configurable states.", "searchTags": [ @@ -11,7 +10,7 @@ "tags": [ { "tag": "background", - "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", + "stateRenderFunction": "var color = ctx.properties.stoppedColor;\nif (ctx.values.critical) {\n color = ctx.properties.criticalColor;\n} else if (ctx.values.warning) {\n color = ctx.properties.warningColor;\n} else if (ctx.values.running) {\n color = ctx.properties.runningColor;\n}\nelement.attr({fill: color});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}", "actions": null }, { @@ -250,7 +249,8 @@ "step": null } ] -} +}]]> + diff --git a/application/src/main/data/json/system/scada_symbols/small-spherical-tank.svg b/application/src/main/data/json/system/scada_symbols/small-spherical-tank.svg index 0acbbc9f38..fd0370db2f 100644 --- a/application/src/main/data/json/system/scada_symbols/small-spherical-tank.svg +++ b/application/src/main/data/json/system/scada_symbols/small-spherical-tank.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="600" fill="none" version="1.1" viewBox="0 0 600 600"><tb:metadata xmlns=""><![CDATA[{ "title": "Small spherical tank", "description": "Small spherical tank with current volume value and level visualizations.", "searchTags": [ @@ -25,17 +24,17 @@ }, { "tag": "fluid", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*560; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*560; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n", "actions": null }, { "tag": "fluid-background", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*560; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*560; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n", "actions": null }, { "tag": "scale", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 23;\n var majorIntervalLength = 560 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(268, y, 300, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 258, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(280, minorY, 300, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 23;\n var majorIntervalLength = 560 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(268, y, 300, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 258, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(280, minorY, 300, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "actions": null }, { @@ -556,7 +555,8 @@ "step": null } ] -} +}]]> + @@ -694,7 +694,7 @@ - + @@ -842,6 +842,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/spherical-tank.svg b/application/src/main/data/json/system/scada_symbols/spherical-tank.svg index 069c54f444..191be8dcec 100644 --- a/application/src/main/data/json/system/scada_symbols/spherical-tank.svg +++ b/application/src/main/data/json/system/scada_symbols/spherical-tank.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="1e3" fill="none" version="1.1" viewBox="0 0 1e3 1e3"><tb:metadata xmlns=""><![CDATA[{ "title": "Spherical tank", "description": "Spherical tank with current volume value and level visualizations.", "searchTags": [ @@ -25,17 +24,17 @@ }, { "tag": "fluid", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*960; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*960; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n", "actions": null }, { "tag": "fluid-background", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*960; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*960; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n", "actions": null }, { "tag": "scale", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 23;\n var majorIntervalLength = 960 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(458, y, 490, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 448, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(470, minorY, 490, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 23;\n var majorIntervalLength = 960 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(458, y, 490, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 448, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(470, minorY, 490, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "actions": null }, { @@ -556,7 +555,8 @@ "step": null } ] -} +}]]> + @@ -724,7 +724,7 @@ - + @@ -872,6 +872,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/stand-cylindrical-tank.svg b/application/src/main/data/json/system/scada_symbols/stand-cylindrical-tank.svg index 2b3b3f010f..127f6bbd84 100644 --- a/application/src/main/data/json/system/scada_symbols/stand-cylindrical-tank.svg +++ b/application/src/main/data/json/system/scada_symbols/stand-cylindrical-tank.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="1200" fill="none" version="1.1" viewBox="0 0 600 1200"><tb:metadata xmlns=""><![CDATA[{ "title": "Stand cylindrical tank", "description": "Stand cylindrical tank with current volume value and level visualizations.", "searchTags": [ @@ -25,17 +24,17 @@ }, { "tag": "fluid", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n", "actions": null }, { "tag": "fluid-background", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*900; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n", "actions": null }, { "tag": "scale", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "actions": null }, { @@ -561,7 +560,8 @@ "step": null } ] -} +}]]> + @@ -654,7 +654,7 @@ - + @@ -802,6 +802,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/stand-horizontal-tank.svg b/application/src/main/data/json/system/scada_symbols/stand-horizontal-tank.svg index bea10fd317..df7ac82ffc 100644 --- a/application/src/main/data/json/system/scada_symbols/stand-horizontal-tank.svg +++ b/application/src/main/data/json/system/scada_symbols/stand-horizontal-tank.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="1e3" height="800" fill="none" version="1.1" viewBox="0 0 1e3 800"><tb:metadata xmlns=""><![CDATA[{ "title": "Stand horizontal tank", "description": "Stand horizontal tank with current volume value and level visualizations.", "searchTags": [ @@ -25,17 +24,17 @@ }, { "tag": "fluid", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n", "actions": null }, { "tag": "fluid-background", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*568; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n", "actions": null }, { "tag": "scale", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 17;\n var majorIntervalLength = 568 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(715, y, 747, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 705, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(727, minorY, 747, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 17;\n var majorIntervalLength = 568 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(715, y, 747, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 705, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(727, minorY, 747, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "actions": null }, { @@ -556,7 +555,8 @@ "step": null } ] -} +}]]> + @@ -676,7 +676,7 @@ - + @@ -824,6 +824,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/stand-vertical-short-tank.svg b/application/src/main/data/json/system/scada_symbols/stand-vertical-short-tank.svg index f9dcb3b141..e14833c18b 100644 --- a/application/src/main/data/json/system/scada_symbols/stand-vertical-short-tank.svg +++ b/application/src/main/data/json/system/scada_symbols/stand-vertical-short-tank.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="800" height="800" fill="none" version="1.1" viewBox="0 0 800 800"><tb:metadata xmlns=""><![CDATA[{ "title": "Stand vertical short tank", "description": "Stand vertical short tank with current volume value and level visualizations.", "searchTags": [ @@ -26,17 +25,17 @@ }, { "tag": "fluid", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 245});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 245 + height });\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 245});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 245 + height });\n }\n}\n", "actions": null }, { "tag": "fluid-background", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n", "actions": null }, { "tag": "scale", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 137;\n var majorIntervalLength = 442 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(523, y, 555, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 513, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(535, minorY, 555, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 137;\n var majorIntervalLength = 442 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(523, y, 555, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 513, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(535, minorY, 555, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "actions": null }, { @@ -562,7 +561,8 @@ "step": null } ] -} +}]]> + @@ -643,7 +643,7 @@ - + @@ -791,6 +791,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/stand-vertical-tank.svg b/application/src/main/data/json/system/scada_symbols/stand-vertical-tank.svg index 078f58d810..2e6831b8ca 100644 --- a/application/src/main/data/json/system/scada_symbols/stand-vertical-tank.svg +++ b/application/src/main/data/json/system/scada_symbols/stand-vertical-tank.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="1200" fill="none" version="1.1" viewBox="0 0 600 1200"><tb:metadata xmlns=""><![CDATA[{ "title": "Stand vertical tank", "description": "Stand vertical tank with current volume value and level visualizations.", "searchTags": [ @@ -25,17 +24,17 @@ }, { "tag": "fluid", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n", "actions": null }, { "tag": "fluid-background", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n", "actions": null }, { "tag": "scale", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "actions": null }, { @@ -561,7 +560,8 @@ "step": null } ] -} +}]]> + @@ -1243,7 +1243,7 @@ - + @@ -1391,6 +1391,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/top-flow-meter.svg b/application/src/main/data/json/system/scada_symbols/top-flow-meter.svg index 8b785bcf46..907b0c5211 100644 --- a/application/src/main/data/json/system/scada_symbols/top-flow-meter.svg +++ b/application/src/main/data/json/system/scada_symbols/top-flow-meter.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="400" height="400" fill="none" version="1.1" viewBox="0 0 400 400"><tb:metadata xmlns=""><![CDATA[{ "title": "Top flow meter", "description": "Top flow meter component used to display flow related value and render various states. Includes pipe fluid and leak visualizations.", "searchTags": [ @@ -20,7 +19,7 @@ }, { "tag": "border", - "stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", + "stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}", "actions": { "click": { "actionFunction": "ctx.api.callAction(event, 'displayClick');" @@ -38,7 +37,7 @@ }, { "tag": "fluid", - "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}", + "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}", "actions": null }, { @@ -733,7 +732,8 @@ "step": null } ] -} +}]]> + @@ -768,7 +768,7 @@ - + @@ -799,6 +799,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/top-right-elbow-pipe.svg b/application/src/main/data/json/system/scada_symbols/top-right-elbow-pipe.svg index 89beca4c85..34eee7f9b2 100644 --- a/application/src/main/data/json/system/scada_symbols/top-right-elbow-pipe.svg +++ b/application/src/main/data/json/system/scada_symbols/top-right-elbow-pipe.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{ "title": "Top right elbow pipe", "description": "Top right elbow pipe with fluid and leak visualizations.", "searchTags": [ @@ -8,7 +7,7 @@ ], "widgetSizeX": 1, "widgetSizeY": 1, - "stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var delta = deltaX * 1.17 * Math.cos(45*Math.PI/180);\n liquidPattern.animate(1000).ease('-').relative(delta, -delta).loop();\n } else {\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}\n\n", + "stateRenderFunction": "var centerLiquidPattern = prepareLiquidPattern('center-fluid-background');\nvar horizontalLiquidPattern = prepareLiquidPattern('horizontal-fluid');\nvar verticalLiquidPattern = prepareLiquidPattern('vertical-fluid');\n\nvar fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\nvar flowAnimationSpeed = ctx.values.flowAnimationSpeed;\n \nif (horizontalLiquidPattern) {\n updateLiquidPatternAnimation(horizontalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (verticalLiquidPattern) {\n updateLiquidPatternAnimation(verticalLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, false);\n}\n\nif (centerLiquidPattern) {\n updateLiquidPatternAnimation(centerLiquidPattern, fluid, \n flow, flowDirection, flowAnimationSpeed, true);\n}\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, fluid, flow, flowDirection, flowAnimationSpeed, center) {\n \n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection, center);\n }\n if (flow && fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse, center) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n if (center) {\n var duration = 1000 * 1.17;\n return ctx.api.cssAnimate(liquidPattern, duration).relative(deltaX, 0).loop();\n } else {\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n }\n}", "tags": [ { "tag": "center-fluid", @@ -251,7 +250,8 @@ "step": null } ] -} +}]]> + @@ -264,7 +264,7 @@ - + @@ -415,37 +415,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -530,9 +499,14 @@ - + + + + + + - + diff --git a/application/src/main/data/json/system/scada_symbols/top-tee-pipe.svg b/application/src/main/data/json/system/scada_symbols/top-tee-pipe.svg index 96199571ef..6edc1b7c99 100644 --- a/application/src/main/data/json/system/scada_symbols/top-tee-pipe.svg +++ b/application/src/main/data/json/system/scada_symbols/top-tee-pipe.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{ "title": "Top tee pipe", "description": "Top tee pipe with configurable left/right/top fluid and leak visualizations.", "searchTags": [ @@ -8,7 +7,7 @@ ], "widgetSizeX": 1, "widgetSizeY": 1, - "stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\n\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill');\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(flowAnimationSpeed);\n }\n } else {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n}\n\n", + "stateRenderFunction": "var leftLiquidPattern = prepareLiquidPattern('left-fluid');\nvar rightLiquidPattern = prepareLiquidPattern('right-fluid');\nvar topLiquidPattern = prepareLiquidPattern('top-fluid');\n\nupdateLiquidPatternAnimation(leftLiquidPattern, 'left');\nupdateLiquidPatternAnimation(rightLiquidPattern, 'right');\nupdateLiquidPatternAnimation(topLiquidPattern, 'top');\n\n\nfunction prepareLiquidPattern(fluidElementTag) {\n return ctx.tags[fluidElementTag][0].reference('fill').first();\n}\n\nfunction updateLiquidPatternAnimation(liquidPattern, prefix) {\n if (liquidPattern) {\n var fluid = ctx.values[prefix + 'Fluid'] && !ctx.values.leak;\n var flow = ctx.values[prefix + 'Flow'];\n var flowDirection = ctx.values[prefix + 'FlowDirection'];\n var flowAnimationSpeed = ctx.values[prefix + 'FlowAnimationSpeed'];\n\n var elementFluid = liquidPattern.remember('fluid');\n var elementFlow = null;\n var elementFlowDirection = null;\n \n if (fluid !== elementFluid) {\n liquidPattern.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n } else {\n elementFlow = liquidPattern.remember('flow');\n elementFlowDirection = liquidPattern.remember('flowDirection');\n }\n var fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n \n if (fluid) {\n if (flow !== elementFlow) {\n liquidPattern.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n liquidPattern.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(flowAnimationSpeed);\n }\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n }\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}\n", "tags": [ { "tag": "leak", @@ -576,7 +575,8 @@ "step": null } ] -} +}]]> + @@ -738,9 +738,12 @@ - - - + + + + + + diff --git a/application/src/main/data/json/system/scada_symbols/vertical-ball-valve.svg b/application/src/main/data/json/system/scada_symbols/vertical-ball-valve.svg index f284c7f86d..a3d6d92086 100644 --- a/application/src/main/data/json/system/scada_symbols/vertical-ball-valve.svg +++ b/application/src/main/data/json/system/scada_symbols/vertical-ball-valve.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{ "title": "Vertical ball valve", "description": "Vertical ball valve with open/close animation and state colors.", "searchTags": [ @@ -11,7 +10,7 @@ "tags": [ { "tag": "background", - "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.animate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n", + "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.cssAnimate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n", "actions": null }, { @@ -25,7 +24,7 @@ }, { "tag": "wheel", - "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle, originY: 100});\n} else {\n ctx.api.animate(element, 500).transform({rotate: angle, originY: 100});\n element.remember('openAnimate', false);\n}\n", + "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle, originY: 100});\n} else {\n ctx.api.cssAnimate(element, 500).transform({rotate: angle, originY: 100});\n element.remember('openAnimate', false);\n}\n", "actions": null } ], @@ -200,7 +199,8 @@ "step": null } ] -} +}]]> + diff --git a/application/src/main/data/json/system/scada_symbols/vertical-inline-flow-meter.svg b/application/src/main/data/json/system/scada_symbols/vertical-inline-flow-meter.svg index c4485449ea..a002ca57a4 100644 --- a/application/src/main/data/json/system/scada_symbols/vertical-inline-flow-meter.svg +++ b/application/src/main/data/json/system/scada_symbols/vertical-inline-flow-meter.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="400" fill="none" version="1.1" viewBox="0 0 200 400"><tb:metadata xmlns=""><![CDATA[{ "title": "Vertical inline flow meter", "description": "Vertical inline flow meter component used to display flow related value and render various states. Includes pipe fluid and leak visualizations.", "searchTags": [ @@ -20,7 +19,7 @@ }, { "tag": "border", - "stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}", + "stateRenderFunction": "var value = ctx.values.value;\nvar colorProcessor = ctx.properties.defaultBorderColor;\nif (ctx.values.critical) {\n colorProcessor = ctx.properties.criticalBorderColor;\n} else if (ctx.values.warning) {\n colorProcessor = ctx.properties.warningBorderColor;\n} else if (value) {\n colorProcessor = ctx.properties.activeBorderColor;\n}\ncolorProcessor.update(value);\nvar fill = colorProcessor.color;\nelement.attr({fill: fill});\n\nvar elementCriticalAnimation = element.remember('criticalAnimation');\nvar criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\nif (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}", "actions": { "click": { "actionFunction": "ctx.api.callAction(event, 'displayClick');" @@ -38,7 +37,7 @@ }, { "tag": "fluid", - "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}", + "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}", "actions": null }, { @@ -733,7 +732,8 @@ "step": null } ] -} +}]]> + @@ -754,7 +754,7 @@ - + @@ -785,6 +785,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/vertical-pipe.svg b/application/src/main/data/json/system/scada_symbols/vertical-pipe.svg index 3f6d912459..b4db216936 100644 --- a/application/src/main/data/json/system/scada_symbols/vertical-pipe.svg +++ b/application/src/main/data/json/system/scada_symbols/vertical-pipe.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{ "title": "Vertical pipe", "description": "Vertical pipe with fluid and leak visualizations.", "searchTags": [ @@ -11,7 +10,7 @@ "tags": [ { "tag": "fluid", - "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill');\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n } else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n animateFlow(liquidPattern, flowDirection);\n }\n if (flow && liquidPattern) {\n liquidPattern.timeline().speed(ctx.values.flowAnimationSpeed);\n }\n} else {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n if (liquidPattern) {\n ctx.api.resetAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n liquidPattern.animate(1000).ease('-').relative(deltaX, 0).loop();\n }\n}", + "stateRenderFunction": "var fluid = ctx.values.fluid && !ctx.values.leak;\nvar flow = ctx.values.flow;\nvar flowDirection = ctx.values.flowDirection;\n\nvar elementFluid = element.remember('fluid');\nvar elementFlow = null;\nvar elementFlowDirection = null;\n\nif (fluid !== elementFluid) {\n element.remember('fluid', fluid);\n elementFlow = null;\n elementFlowDirection = null;\n} else {\n elementFlow = element.remember('flow');\n elementFlowDirection = element.remember('flowDirection');\n}\n\nvar liquidPattern = element.reference('fill').first();\n\nvar fluidAnimation = ctx.api.cssAnimation(liquidPattern);\n\n\nif (fluid) {\n element.show();\n if (flow !== elementFlow) {\n element.remember('flow', flow);\n if (flow) {\n if (elementFlowDirection !== flowDirection || !fluidAnimation) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n } else {\n fluidAnimation.play();\n }\n } else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n }\n } else if (flow && elementFlowDirection !== flowDirection) {\n element.remember('flowDirection', flowDirection);\n fluidAnimation = animateFlow(liquidPattern, flowDirection);\n }\n if (flow) {\n if (fluidAnimation) {\n fluidAnimation.speed(ctx.values.flowAnimationSpeed);\n }\n }\n} else {\n if (fluidAnimation) {\n fluidAnimation.pause();\n }\n element.hide();\n}\n\nfunction animateFlow(liquidPattern, forwardElseReverse) {\n ctx.api.resetCssAnimation(liquidPattern);\n var deltaX = forwardElseReverse ? 172 : -172;\n return ctx.api.cssAnimate(liquidPattern, 1000).relative(deltaX, 0).loop();\n}", "actions": null }, { @@ -240,7 +239,8 @@ "step": null } ] -} +}]]> + @@ -254,7 +254,7 @@ - + @@ -285,6 +285,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/vertical-short-tank.svg b/application/src/main/data/json/system/scada_symbols/vertical-short-tank.svg index 1b1e825d48..332a138e2c 100644 --- a/application/src/main/data/json/system/scada_symbols/vertical-short-tank.svg +++ b/application/src/main/data/json/system/scada_symbols/vertical-short-tank.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="800" height="600" fill="none" version="1.1" viewBox="0 0 800 600"><tb:metadata xmlns=""><![CDATA[{ "title": "Vertical short tank", "description": "Vertical short tank with current volume value and level visualizations.", "searchTags": [ @@ -25,17 +24,17 @@ }, { "tag": "fluid", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 245});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 245 + height });\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 245});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 245 + height });\n }\n}\n", "actions": null }, { "tag": "fluid-background", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*442; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n", "actions": null }, { "tag": "scale", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 137;\n var majorIntervalLength = 442 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(523, y, 555, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 513, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(535, minorY, 555, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 137;\n var majorIntervalLength = 442 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(523, y, 555, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 513, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(535, minorY, 555, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "actions": null }, { @@ -561,7 +560,8 @@ "step": null } ] -} +}]]> + @@ -642,7 +642,7 @@ - + @@ -790,6 +790,7 @@ + diff --git a/application/src/main/data/json/system/scada_symbols/vertical-tank.svg b/application/src/main/data/json/system/scada_symbols/vertical-tank.svg index 5c3a4561db..bb0cc8f2b6 100644 --- a/application/src/main/data/json/system/scada_symbols/vertical-tank.svg +++ b/application/src/main/data/json/system/scada_symbols/vertical-tank.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="600" height="1e3" fill="none" version="1.1" viewBox="0 0 600 1e3"><tb:metadata xmlns=""><![CDATA[{ "title": "Vertical tank", "description": "Vertical tank with current volume value and level visualizations.", "searchTags": [ @@ -24,17 +23,17 @@ }, { "tag": "fluid", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n liquidPattern.animate(500)\n .transform({ translateY: 590 + height });\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var liquidPattern = element.reference('fill');\n\n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n liquidPattern.transform({translateY: 590});\n }\n\n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n ctx.api.cssAnimate(liquidPattern, 500).transform({ translateY: 590 + height });\n }\n}\n", "actions": null }, { "tag": "fluid-background", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.animate(element, 500).attr({height: height});\n }\n}\n", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var color = ctx.properties.fluidColor;\n element.attr({fill: color, 'fill-opacity': 1});\n \n var valueSet = element.remember('valueSet');\n if (!valueSet) {\n element.remember('valueSet', true);\n element.attr({height: 0});\n }\n \n var currentVolume = ctx.values.currentVolume; \n var tankCapacity = ctx.values.tankCapacity; \n\n var height = currentVolume / tankCapacity;\n height = Math.max(0, Math.min(1, height))*765; \n \n var elementHeight = element.remember('height');\n if (height !== elementHeight) {\n element.remember('height', height);\n ctx.api.cssAnimate(element, 500).attr({height: height});\n }\n}\n", "actions": null }, { "tag": "scale", - "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", + "stateRenderFunction": "if (!ctx.properties.scale) {\n element.hide();\n} else {\n var scaleSet = element.remember('scaleSet');\n if (!scaleSet) {\n element.remember('scaleSet', true);\n element.clear();\n \n var majorIntervals = ctx.properties.majorIntervals;\n var minorIntervals = ctx.properties.minorIntervals;\n \n var start = 205;\n var majorIntervalLength = 760 / majorIntervals;\n var minorIntervalLength = majorIntervalLength / minorIntervals;\n for (var i = 0; i < majorIntervals + 1; i++) {\n var y = start + i * majorIntervalLength;\n var line = ctx.svg.line(340, y, 372, y).stroke({ width: 3 }).attr({class: 'majorTick'});\n element.add(line);\n var majorText = (100 - i * (100/majorIntervals)).toFixed(0);\n var majorTickText = ctx.svg.text(majorText);\n majorTickText.attr({x: 330, y: y + 2, 'text-anchor': 'end', 'dominant-baseline': 'middle', class: 'majorTickText'});\n element.add(majorTickText);\n if (i < majorIntervals) {\n drawMinorTicks(y, minorIntervals, minorIntervalLength);\n }\n }\n }\n \n var majorFont = ctx.properties.majorFont;\n var majorColor = ctx.properties.majorColor;\n var minorColor = ctx.properties.minorColor;\n if (ctx.values.critical) {\n majorColor = ctx.properties.majorCriticalColor;\n minorColor = ctx.properties.minorCriticalColor;\n } else if (ctx.values.warning) {\n majorColor = ctx.properties.minorWarningColor;\n minorColor = ctx.properties.minorWarningColor;\n }\n \n var majorTicks = element.find('line.majorTick');\n majorTicks.forEach(t => t.attr({stroke: majorColor}));\n \n var majorTicksText = element.find('text.majorTickText');\n ctx.api.font(majorTicksText, majorFont, majorColor);\n \n var minorTicks = element.find('line.minorTick');\n minorTicks.forEach(t => t.attr({stroke: minorColor}));\n \n var elementCriticalAnimation = element.remember('criticalAnimation');\n var criticalAnimation = ctx.values.critical && ctx.values.criticalAnimation;\n\n if (elementCriticalAnimation !== criticalAnimation) {\n element.remember('criticalAnimation', criticalAnimation);\n if (criticalAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n }\n}\n\nfunction drawMinorTicks(start, minorIntervals, minorIntervalLength) {\n for (var i = 1; i < minorIntervals; i++) {\n var minorY = start + i * minorIntervalLength;\n var minorLine = ctx.svg.line(352, minorY, 372, minorY).stroke({ width: 3 }).attr({class: 'minorTick'});\n element.add(minorLine);\n }\n}", "actions": null }, { @@ -560,7 +559,8 @@ "step": null } ] -} +}]]> + @@ -1242,7 +1242,7 @@ - + @@ -1390,5 +1390,6 @@ + \ No newline at end of file diff --git a/application/src/main/data/json/system/scada_symbols/vertical-wheel-valve.svg b/application/src/main/data/json/system/scada_symbols/vertical-wheel-valve.svg index d43083040f..3e2e9d845d 100644 --- a/application/src/main/data/json/system/scada_symbols/vertical-wheel-valve.svg +++ b/application/src/main/data/json/system/scada_symbols/vertical-wheel-valve.svg @@ -10,7 +10,7 @@ "tags": [ { "tag": "background", - "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.animate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n", + "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar color = opened ? ctx.properties.openedColor : ctx.properties.closedColor;\nif (!openAnimate) {\n element.attr({fill: color});\n} else {\n ctx.api.cssAnimate(element, 500).attr({fill: color});\n element.remember('openAnimate', false);\n}\n", "actions": null }, { @@ -24,7 +24,7 @@ }, { "tag": "wheel", - "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle});\n} else {\n ctx.api.animate(element, 500).transform({rotate: angle});\n element.remember('openAnimate', false);\n}\n", + "stateRenderFunction": "var opened = ctx.values.opened;\nvar openAnimate = element.remember('openAnimate');\nvar angle = opened ? ctx.properties.openedRotationAngle : ctx.properties.closedRotationAngle;\nif (!openAnimate) {\n element.transform({rotate: angle});\n} else {\n ctx.api.cssAnimate(element, 500).transform({rotate: angle});\n element.remember('openAnimate', false);\n}\n", "actions": null } ], diff --git a/application/src/main/data/json/system/scada_symbols/waterstop.svg b/application/src/main/data/json/system/scada_symbols/waterstop.svg index e3fb963d71..2e734a135f 100644 --- a/application/src/main/data/json/system/scada_symbols/waterstop.svg +++ b/application/src/main/data/json/system/scada_symbols/waterstop.svg @@ -1,5 +1,4 @@ - -{ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200"><tb:metadata xmlns=""><![CDATA[{ "title": "Water stop", "description": "Remotely controlled water shutoff valve with configurable connectors and various states.", "searchTags": [ @@ -25,7 +24,7 @@ }, { "tag": "walve", - "stateRenderFunction": "var opened = ctx.values.opened;\nif (opened) {\n element.attr({fill: ctx.properties.openedColor});\n ctx.api.resetAnimation(element);\n} else {\n element.attr({fill: ctx.properties.closedColor});\n if (ctx.values.closeAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}\n", + "stateRenderFunction": "var opened = ctx.values.opened;\nif (opened) {\n element.attr({fill: ctx.properties.openedColor});\n ctx.api.resetCssAnimation(element);\n} else {\n element.attr({fill: ctx.properties.closedColor});\n if (ctx.values.closeAnimation) {\n ctx.api.cssAnimate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetCssAnimation(element);\n }\n}\n", "actions": null } ], @@ -93,7 +92,7 @@ }, "valueToData": { "type": "CONSTANT", - "constantValue": false, + "constantValue": true, "valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;" } }, @@ -127,7 +126,7 @@ }, "valueToData": { "type": "CONSTANT", - "constantValue": true, + "constantValue": false, "valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;" } }, @@ -219,7 +218,8 @@ "step": null } ] -} +}]]> + From d3f074be538243c15adee391ca5b53bd72bc0681 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 24 Sep 2024 11:30:54 +0300 Subject: [PATCH 16/21] UI: Fix dashboard widgets positioning -> allow decimal values for gridster item x/y position. Other minor fixes. --- ui-ngx/patches/angular-gridster2+15.0.4.patch | 49 ++++--------------- .../layout/dashboard-layout.component.html | 1 - .../layout/dashboard-layout.component.ts | 4 -- .../dashboard/dashboard.component.ts | 11 ----- .../lib/rpc/power-button-widget.component.ts | 11 +++-- .../widget/lib/scada/scada-symbol.models.ts | 6 ++- 6 files changed, 22 insertions(+), 60 deletions(-) diff --git a/ui-ngx/patches/angular-gridster2+15.0.4.patch b/ui-ngx/patches/angular-gridster2+15.0.4.patch index a6642928c3..df511350c5 100644 --- a/ui-ngx/patches/angular-gridster2+15.0.4.patch +++ b/ui-ngx/patches/angular-gridster2+15.0.4.patch @@ -1,44 +1,15 @@ diff --git a/node_modules/angular-gridster2/fesm2020/angular-gridster2.mjs b/node_modules/angular-gridster2/fesm2020/angular-gridster2.mjs -index cf4e220..4275d11 100644 +index cf4e220..df51c91 100644 --- a/node_modules/angular-gridster2/fesm2020/angular-gridster2.mjs +++ b/node_modules/angular-gridster2/fesm2020/angular-gridster2.mjs -@@ -208,6 +208,7 @@ const GridsterConfigService = { - useTransformPositioning: true, - scrollSensitivity: 10, - scrollSpeed: 20, -+ colWidthUpdateCallback: undefined, - initCallback: undefined, - destroyCallback: undefined, - gridSizeChangedCallback: undefined, -@@ -1243,6 +1244,9 @@ class GridsterComponent { - this.renderer.setStyle(this.el, 'padding-right', this.$options.margin + 'px'); - } - this.curColWidth = (this.curWidth - marginWidth) / this.columns; -+ if (this.options.colWidthUpdateCallback) { -+ this.curColWidth = this.options.colWidthUpdateCallback(this.curColWidth); -+ } - let marginHeight = -this.$options.margin; - if (this.$options.outerMarginTop !== null) { - marginHeight += this.$options.outerMarginTop; -@@ -1266,6 +1270,9 @@ class GridsterComponent { +@@ -666,8 +666,8 @@ class GridsterRenderer { + renderer.setStyle(el, DirTypes.LTR ? 'margin-right' : 'margin-left', ''); } else { - this.curColWidth = (this.curWidth + this.$options.margin) / this.columns; -+ if (this.options.colWidthUpdateCallback) { -+ this.curColWidth = this.options.colWidthUpdateCallback(this.curColWidth); -+ } - this.curRowHeight = - ((this.curHeight + this.$options.margin) / this.rows) * - this.$options.rowHeightRatio; -diff --git a/node_modules/angular-gridster2/lib/gridsterConfig.interface.d.ts b/node_modules/angular-gridster2/lib/gridsterConfig.interface.d.ts -index 1d7cdf0..a712b35 100644 ---- a/node_modules/angular-gridster2/lib/gridsterConfig.interface.d.ts -+++ b/node_modules/angular-gridster2/lib/gridsterConfig.interface.d.ts -@@ -73,6 +73,7 @@ export interface GridsterConfig { - useTransformPositioning?: boolean; - scrollSensitivity?: number | null; - scrollSpeed?: number; -+ colWidthUpdateCallback?: (colWidth: number) => number; - initCallback?: (gridster: GridsterComponentInterface) => void; - destroyCallback?: (gridster: GridsterComponentInterface) => void; - gridSizeChangedCallback?: (gridster: GridsterComponentInterface) => void; +- const x = Math.round(this.gridster.curColWidth * item.x); +- const y = Math.round(this.gridster.curRowHeight * item.y); ++ const x = this.gridster.curColWidth * item.x; ++ const y = this.gridster.curRowHeight * item.y; + const width = this.gridster.curColWidth * item.cols - this.gridster.$options.margin; + const height = this.gridster.curRowHeight * item.rows - this.gridster.$options.margin; + // set the cell style diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html index 0b35de5d9a..0442061e99 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html @@ -43,7 +43,6 @@ [widgetLayouts]="layoutCtx.widgetLayouts" [columns]="columns" [displayGrid]="displayGrid" - [colWidthInteger]="colWidthInteger" [outerMargin]="outerMargin" [margin]="margin" [aliasController]="dashboardCtx.aliasController" diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.ts index 9646152eba..c04a2af3df 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.ts @@ -109,10 +109,6 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo return this.layoutCtx.gridSettings.mobileRowHeight; } - get colWidthInteger(): boolean { - return this.isScada; - } - get columns(): number { return this.layoutCtx.gridSettings.minColumns || this.layoutCtx.gridSettings.columns || 24; } diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts index 180b4da3e3..b99c89377a 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts @@ -87,10 +87,6 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo @Input() columns: number; - @Input() - @coerceBoolean() - colWidthInteger = false; - @Input() @coerceBoolean() setGridSize = false; @@ -260,13 +256,6 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo itemChangeCallback: () => this.dashboardWidgets.sortWidgets(), itemInitCallback: (_, itemComponent) => { (itemComponent.item as DashboardWidget).gridsterItemComponent = itemComponent; - }, - colWidthUpdateCallback: (colWidth) => { - if (this.colWidthInteger) { - return Math.floor(colWidth); - } else { - return colWidth; - } } }; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.ts index abf97356dd..6c9072e915 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/power-button-widget.component.ts @@ -18,7 +18,7 @@ import { AfterViewInit, ChangeDetectorRef, Component, - ElementRef, + ElementRef, NgZone, OnDestroy, OnInit, Renderer2, @@ -72,7 +72,8 @@ export class PowerButtonWidgetComponent extends constructor(protected imagePipe: ImagePipe, protected sanitizer: DomSanitizer, private renderer: Renderer2, - protected cd: ChangeDetectorRef) { + protected cd: ChangeDetectorRef, + protected zone: NgZone) { super(cd); } @@ -178,8 +179,10 @@ export class PowerButtonWidgetComponent extends this.renderer.setStyle(this.svgShape.node, 'overflow', 'visible'); this.renderer.setStyle(this.svgShape.node, 'user-select', 'none'); - this.powerButtonSvgShape = PowerButtonShape.fromSettings(this.ctx, this.svgShape, - this.settings, this.value, this.disabledState, () => this.onClick()); + this.zone.run(() => { + this.powerButtonSvgShape = PowerButtonShape.fromSettings(this.ctx, this.svgShape, + this.settings, this.value, this.disabledState, () => this.onClick()); + }); this.shapeResize$ = new ResizeObserver(() => { this.onResize(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts index e9f55843fd..a7f96262c7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts @@ -1515,7 +1515,11 @@ class CssScadaSymbolAnimation implements ScadaSymbolAnimation { const transform = this._initialTransform; for (const key of Object.keys(this._transform)) { if (this._relative) { - transformed[key] = this.normFloat(transform[key] + this._transform[key]); + if (['scaleX', 'scaleY'].includes(key)) { + transformed[key] = this.normFloat(transform[key] * this._transform[key]); + } else { + transformed[key] = this.normFloat(transform[key] + this._transform[key]); + } } else { transformed[key] = this.normFloat(this._transform[key]); } From 9adcded5e9f220eab058c0e2cb070702a730ab78 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Tue, 24 Sep 2024 11:33:40 +0300 Subject: [PATCH 17/21] UI: Add hint for scada symbol widget --- .../src/main/data/json/system/widget_types/scada_symbol.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/data/json/system/widget_types/scada_symbol.json b/application/src/main/data/json/system/widget_types/scada_symbol.json index bdcdd1674d..43dc839025 100644 --- a/application/src/main/data/json/system/widget_types/scada_symbol.json +++ b/application/src/main/data/json/system/widget_types/scada_symbol.json @@ -4,7 +4,7 @@ "deprecated": false, "scada": true, "image": "tb-image:bmV3X3NjYWRhX3N5bWJvbHNfc3lzdGVtX3dpZGdldF9pbWFnZV8oMSkuc3Zn:Ik5ldyBTQ0FEQSBzeW1ib2wiIHN5c3RlbSB3aWRnZXQgaW1hZ2U=:SU1BR0U=;data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE2MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBjbGlwLXBhdGg9InVybCgjYSkiIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBmaWxsPSIjMzA1NjgwIiBmaWxsLW9wYWNpdHk9Ii42Ij48cGF0aCBkPSJtMTQ3LjE2MyA0OC43NTM3LTE0LjcxOC0xNS4zODU1Yy0xLjIxMy0xLjI2MTQtMi42OTMtMi4xOTYtNC4zNC0yLjczOTYtNS4wNzUtNi41NDc1LTExLjE5OS0xMC42Nzc3LTIwLjEzNC0xMC42Nzc3LTguNjkwNiAwLTE2LjQzMDMgMy42MjQxLTIxLjUzMDMgMTAuMzI4SDcyLjQ0NzRjLTUuNTYzMS4wMDA1LTEwLjE1NyA0LjM2Ny0xMC4xNTcgOS45MzA3djM5Ljc5MDVoNS4wMjhWNDAuMjA5NmMwLTIuNzk1NSAyLjMzMzUtNC45MDIyIDUuMTI5LTQuOTAyMmgxMC43MjU3Yy0xLjkxMTIgMy4zNTE5LTMuMDA1NiA3Ljg0MTktMy4wMDU2IDEyLjM1ODZ2My44NDI1aC04LjM2MmMtLjY1MDggMC0xLjIzOC4yNjI1LTEuNDg4My44NjMxLS4yNTAyLjYwMDYtLjExNTYgMS4yMjg1LjM0MiAxLjY5MTFsMjIuNDE3MyAyMi42MjQ1Yy4zMjM0LjMyNjkuNzY0OC40OTUgMS4yMjQ2LjQ5NS40NTk3IDAgLjkwMTEtLjE5MjIgMS4yMjQ1LS41MTlsMjIuMjg4NC0yMi41MzA3Yy40NzktLjQ4NDQuNjIxLTEuMDgyNy4zNTgtMS43MTIzLS4yNjMtLjYyODUtLjg3Ny0uOTExNy0xLjU1OS0uOTExN0gxMDguMVY0Ny42NjZjMC00Ljk3MzIgMi40ODYtOS41NjUzIDUuODk1LTEyLjM1ODZoOS4xMjNjMS45ODEuNTU4NiAzLjQxOCAyLjA3MzcgMy40MTggNC4yMDIydjEwLjgxMTdjMCAyLjkwMTcgMi4zODYgNS42NTY0IDUuMjg3IDUuNjU2NGg4Ljg5OWMyLjUwMiAwIDQuMzg5IDEuODI2MyA0LjM4OSA0LjMyNjlsLS4xMzktLjIwOVYxMzAuMDVjMCAyLjc5NS0yLjAzMiA1LjI1Ny00LjgyNyA1LjI1N0g3Mi40NDc0Yy0yLjc5NTUgMC01LjEyOS0yLjQ2MS01LjEyOS01LjI1N3YtMi41NjRoLTUuMDI4djIuNTY0YzAgNS41NjQgNC41OTM5IDEwLjI4NSAxMC4xNTcgMTAuMjg1aDY3LjY5NzZjNS41NjMgMCA5Ljg1NS00LjcyMSA5Ljg1NS0xMC4yODVWNTYuMDg3OGMwLTIuNzQ4Ni0uOTMyLTUuMzUzMS0yLjgzNy03LjMzNDFaIi8+PHBhdGggZD0iTTEyNy42NTQgODguMTQ5NmMwLTEuNzI0LTEuMzk4LTMuMTIxOC0zLjEyMi0zLjEyMThINTMuMTIxN2MtMS43MjQgMC0zLjEyMTggMS4zOTc4LTMuMTIxOCAzLjEyMTh2MzEuNzQ0NGMwIDEuNzI0IDEuMzk3OCAzLjEyMiAzLjEyMTggMy4xMjJoNzEuNDEwM2MxLjcyNCAwIDMuMTIyLTEuMzk4IDMuMTIyLTMuMTIyVjg4LjE0OTZaTTc2LjEzNjMgMTExLjc1NWMtLjQxMjkuOTE0LTEuMDI3NCAxLjcwMi0xLjg0MzYgMi4zNjYtLjgxNTcuNjYzLTEuODI3NCAxLjE3OS0zLjAzNDcgMS41NDktMS4yMDY3LjM3LTIuNjA1LjU1NS00LjE5MjcuNTU1LTEuMjgzOCAwLTIuNTI4NS0uMTU4LTMuNzM1OC0uNDczLTEuMjA3Mi0uMzE1LTIuMjczMS0uODEtMy4xOTc3LTEuNDg1LS45MjUyLS42NzQtMS42NTkyLTEuNTUyLTIuMjAyMy0yLjU5Ni0uNTQ0MS0xLjA0NC0uODA1LTIuMDYyLS43ODMyLTMuNzM4aDQuOTU5OGMwIDEuMTE3LjE0MTMgMS40NjUuNDI0IDIuMDA5LjI4MjcuNTQ0LjY1ODEuOTk0IDEuMTI2MyAxLjMzMS40Njc2LjMzOCAxLjAxNjIuNTkyIDEuNjQ3NC43NTUuNjMwOC4xNjQgMS4yODMzLjI0NyAxLjk1NzYuMjQ3LjQ1NjQgMCAuOTQ2NC0uMDM3IDEuNDY4MS0uMTEzLjUyMjQtLjA3NiAxLjAxMTgtLjIyMiAxLjQ2ODgtLjQ0LjQ1NjQtLjIxOC44MzY4LS41MTcgMS4xNDE5LS44OTcuMzA0NC0uMzguNDU2NC0uODY1LjQ1NjQtMS40NTIgMC0uNjMxLS4yMDExLTEuMTQyLS42MDM0LTEuNTMzLS40MDI4LS4zOTItLjkzMDEtLjcxOC0xLjU4MjYtLjk3OS0uNjUyNi0uMjYxLTEuMzkyMi0uNDktMi4yMTktLjY4Ni0uODI2My0uMTk1LTEuNjY0My0uNDEzLTIuNTEyMy0uNjUyLS44NzA0LS4yMTgtMS43MTg1LS40ODQtMi41NDQ3LS44LS44MjY4LS4zMTUtMS41NjY1LS43MjMtMi4yMTktMS4yMjMtLjY1MjUtLjUtMS4xODA1LTEuMTI2LTEuNTgyNy0xLjg3Ni0uNDAyOC0uNzUtLjYwMzQtMS42NTgyLS42MDM0LTIuNzI0MSAwLTEuMTk2MS4yNTU0LTIuMjM1Mi43NjY1LTMuMTE2Mi41MTA2LS44ODEgMS4xNzk0LTEuNjE1MSAyLjAwNjctMi4yMDIyLjgyNjMtLjU4NzIgMS43NjItMS4wMjI0IDIuODA2Mi0xLjMwNTEgMS4wNDM2LS4yODI2IDIuMDg3Ny0uNDI0IDMuMTMxOC0uNDI0IDEuMjE3OSAwIDIuMzg2Ni4xMzYzIDMuNTA3My40MDc4IDEuMTIwMS4yNzIxIDIuMTE1MS43MTI5IDIuOTg1NSAxLjMyMTMuODcwMy42MDg5IDEuNTYwOSAxLjQ4NDkgMi4wNzIgMi40MzEyLjUxMDYuOTQ2NC43NjY1IDIuNDIzNS43NjY1IDMuNTQwOGgtNC45NTkyYy0uMDQzNi0uNTU4Ni0uMTkxMS0xLjM3MDktLjQ0MDItMS44MjczLS4yNTAzLS40NTctLjU4MjctLjg2NDktLjk5NTYtMS4xMjU3LS40MTI4LS4yNjA5LS44ODY2LS40NzA0LTEuNDE5LS41Nzk0LS41MzM1LS4xMDg0LTEuMTE1LS4xNzU0LTEuNzQ1OC0uMTc1NC0uNDEzNCAwLS44MjYyLjAzOC0xLjIzOTYuMTI0Ni0uNDEzNC4wODcxLS43ODg5LjIzNjMtMS4xMjYzLjQ1MzYtLjMzNzQuMjE3OS0uNjE1MS40ODc3LS44MzE4LjgxNDUtLjIxNzkuMzI2My0uMzI2My43Mzg2LS4zMjYzIDEuMjM5MiAwIC40NTY0LjA4NjYuODI2Mi4yNjE1IDEuMTA4OS4xNzM3LjI4MjcuNTE2Mi41NDM1IDEuMDI3OS43ODI1LjUxMDYuMjQgMS4yMTc5LjQ3OSAyLjEyMDcuNzE4LjkwMjIuMjQgMi4wODI2LjU0NCAzLjU0MDIuOTEzLjQzNDYuMDg4IDEuMDM4LjI0NSAxLjgxMDYuNDc0Ljc3MjEuMjI4IDEuNTM4Ni41OTIgMi4zIDEuMDkzLjc2MTUuNSAxLjQxOTYgMS4xNyAxLjk3NDMgMi4wMDcuNTU0Mi44MzguODMxOCAxLjkwOS44MzE4IDMuMjE0LjAwMTIgMS4wNjQtLjIwNjEgMi4wNTQtLjYxODkgMi45NjdabTE0LjQ4NiAzLjk5OWgtNS43NzQ5TDc3LjMxIDkyLjg0OTFoNS4yNTMxbDUuMTg3NyAxNi4yMDA5aC4wNjUzbDUuMjUzMS0xNi4yMDA5aDUuMjg1NWwtNy43MzI0IDIyLjkwNDlabTMwLjMyNzcgMGgtMy4yNDdsLS41MjEtMi42NjhjLS45MTQgMS4xNzUtMS45MjYgMS45ODMtMy4wMzUgMi40NTEtMS4xMS40NjctMi4yMy42OTUtMy4zNjEuNjk1LTEuNzg0IDAtMy4zODgtLjMxNC00LjgxMy0uOTMzLTEuNDI1LS42Mi0yLjYyNi0xLjQ3NS0zLjYwNS0yLjU2My0uOTc5LTEuMDg3LTEuNzMtMi4zNjctMi4yNTEtMy44MzUtLjUyMjYtMS40NjgtLjc4My0zLjA1MS0uNzgzLTQuNzQ4IDAtMS43NC4yNjA5LTMuMzU1Ljc4My00Ljg0NTMuNTIyLTEuNDg5OSAxLjI3Mi0yLjc4OTkgMi4yNTEtMy44OTk0Ljk3OS0xLjEwOTUgMi4xOC0xLjk3OTQgMy42MDUtMi42MTAxIDEuNDI1LS42MzA3IDMuMDI5LS45NDU4IDQuODEzLS45NDU4IDEuMTk2IDAgMi4zNTQuMTc5MyAzLjQ3NS41MzggMS4xMi4zNTkyIDIuMTMxLjg4NjYgMy4wMzQgMS41ODIxLjkwMy42OTY3IDEuNjQ4IDEuNTczOCAyLjIzNiAyLjU5NjcuNTg3IDEuMDIyOS45NDUgMi40MjUxIDEuMDc2IDMuNTQyOGgtNC44OTRjLS4zMDUtMS4xMTc3LS44OTItMi4zMDMyLTEuNzYyLTIuOTU1Ny0uODctLjY1MjUtMS45MjUtLjk4ODItMy4xNjQtLjk4ODItMS4xNTQgMC0yLjEzMi4yMTktMi45MzcuNjY0Mi0uODA1LjQ0NjQtMS40NTggMS4wNDE5LTEuOTU4IDEuNzkyMnMtLjg2NSAxLjYwMzUtMS4wOTMgMi41NjA1Yy0uMjI5Ljk1Ny0uMzQzIDEuOTQ2LS4zNDMgMi45NjggMCAuOTc5LjExNCAxLjkzMS4zNDMgMi44NTUuMjI4LjkyNS41OTMgMS43NTcgMS4wOTMgMi40OTYuNS43MzkgMS4xNTIgMS4zMzIgMS45NTggMS43NzguODA0LjQ0NiAxLjc4My42NjkgMi45MzcuNjY5IDEuNjk2IDAgMy4wMDYtLjIwNiAzLjkzMS0xLjA2NS45MjQtLjg1OSAxLjQ2Mi0yLjM5NSAxLjYxNS0zLjUxMmgtNS40Mzl2LTMuOTExaDEwLjA1NnYxMi4yOTFaIi8+PC9nPjxkZWZzPjxjbGlwUGF0aCBpZD0iYSI+PHBhdGggZmlsbD0iI2ZmZiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNTAgMTkuNjY0OCkiIGQ9Ik0wIDBoMTAwdjEyMC42N0gweiIvPjwvY2xpcFBhdGg+PC9kZWZzPjwvc3ZnPg==", - "description": "", + "description": "Use for uploading SVG symbols to your SCADA dashboard", "descriptor": { "type": "rpc", "sizeX": 3, From 47ec9496b509a532940a7c2349a107cb25e32a4a Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 24 Sep 2024 11:34:59 +0300 Subject: [PATCH 18/21] Revert "UI: SCADA symbol editor: fix tag settings tooltip to correctly update add/edit function buttons." This reverts commit 221e17789a64e7be9814b70389480141e2ec0c43. --- .../scada-symbol-tooltip.components.ts | 49 +++++++------------ 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts index 7114712848..ef811e9746 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts @@ -225,8 +225,6 @@ class ScadaSymbolTagPanelComponent extends ScadaSymbolPanelComponent implements displayTagSettings = true; - private scadaSymbolTagSettingsTooltip: ITooltipsterInstance; - constructor(public element: ElementRef, private container: ViewContainerRef) { super(element); @@ -244,9 +242,23 @@ class ScadaSymbolTagPanelComponent extends ScadaSymbolPanelComponent implements if (this.displayTagSettings) { const tagSettingsButton = $(this.tagSettingsButton.nativeElement); - tagSettingsButton.on('click', () => { - this.createTagSettingsTooltip(); - }); + tagSettingsButton.tooltipster( + { + parent: this.symbolElement.tooltipContainer, + zIndex: 200, + arrow: true, + theme: ['scada-symbol', 'tb-active'], + interactive: true, + trigger: 'click', + trackOrigin: true, + trackerInterval: 100, + side: 'top', + content: '' + } + ); + + const scadaSymbolTagSettingsTooltip = tagSettingsButton.tooltipster('instance'); + setTooltipComponent(this.symbolElement, this.container, ScadaSymbolTagSettingsComponent, scadaSymbolTagSettingsTooltip); } if (!this.symbolElement.readonly) { @@ -283,33 +295,6 @@ class ScadaSymbolTagPanelComponent extends ScadaSymbolPanelComponent implements }); } - private createTagSettingsTooltip() { - if (!this.scadaSymbolTagSettingsTooltip) { - const tagSettingsButton = $(this.tagSettingsButton.nativeElement); - tagSettingsButton.tooltipster( - { - parent: this.symbolElement.tooltipContainer, - zIndex: 200, - arrow: true, - theme: ['scada-symbol', 'tb-active'], - interactive: true, - trigger: 'click', - trackOrigin: true, - trackerInterval: 100, - side: 'top', - content: '', - functionAfter: () => { - this.scadaSymbolTagSettingsTooltip.destroy(); - this.scadaSymbolTagSettingsTooltip = null; - } - } - ); - this.scadaSymbolTagSettingsTooltip = tagSettingsButton.tooltipster('instance'); - setTooltipComponent(this.symbolElement, this.container, ScadaSymbolTagSettingsComponent, this.scadaSymbolTagSettingsTooltip); - this.scadaSymbolTagSettingsTooltip.open(); - } - } - public onUpdateTag() { this.updateTag.emit(); } From 1c6e245d17325af78001ad4fb9859385271a9428 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 24 Sep 2024 11:40:41 +0300 Subject: [PATCH 19/21] UI: SCADA symbol editor -> fix updating state of tag settings tooltip. --- .../pages/scada-symbol/scada-symbol-tooltip.components.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts index ef811e9746..4195c8587c 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts @@ -258,7 +258,11 @@ class ScadaSymbolTagPanelComponent extends ScadaSymbolPanelComponent implements ); const scadaSymbolTagSettingsTooltip = tagSettingsButton.tooltipster('instance'); - setTooltipComponent(this.symbolElement, this.container, ScadaSymbolTagSettingsComponent, scadaSymbolTagSettingsTooltip); + const scadaSymbolTagSettingsComponentRef = + setTooltipComponent(this.symbolElement, this.container, ScadaSymbolTagSettingsComponent, scadaSymbolTagSettingsTooltip); + scadaSymbolTagSettingsTooltip.on('ready', () => { + scadaSymbolTagSettingsComponentRef.instance.updateFunctionsState(); + }); } if (!this.symbolElement.readonly) { @@ -400,7 +404,7 @@ class ScadaSymbolTagSettingsComponent extends ScadaSymbolPanelComponent implemen this.updateFunctionsState(); } - private updateFunctionsState() { + updateFunctionsState() { this.hasStateRenderFunction = this.symbolElement.hasStateRenderFunction(); this.hasClickAction = this.symbolElement.hasClickAction(); } From a2e3e8df7af380443a1205f5d9bbfdd32f748441 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 24 Sep 2024 12:05:22 +0300 Subject: [PATCH 20/21] UI: SCADA symbol -> improve element text icon positioning. --- .../components/widget/lib/scada/scada-symbol.models.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts index a7f96262c7..c8a3990cef 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts @@ -980,11 +980,14 @@ export class ScadaSymbolObject { fontSetClasses.forEach(className => textElement.addClass(className)); textElement.font({size: `${size}px`}); textElement.attr({ - 'text-anchor': 'start', - 'dominant-baseline': 'hanging', style: `font-size: ${size}px` }); textElement.fill(color); + const tspan = textElement.first(); + tspan.attr({ + 'text-anchor': 'start', + 'dominant-baseline': 'hanging' + }); return of(textElement); } } From aa3f8b757f9a7ab4039ff494296b3263d13330a5 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 24 Sep 2024 13:20:56 +0300 Subject: [PATCH 21/21] UI: SCADA - Remove old animation methods. --- .../widget/lib/scada/scada-symbol.models.ts | 23 ---------- .../scada-symbol-editor.models.ts | 43 ------------------- 2 files changed, 66 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts index c8a3990cef..91297bb689 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts @@ -72,9 +72,6 @@ export interface ScadaSymbolApi { text: (element: Element | Element[], text: string) => void; font: (element: Element | Element[], font: Font, color: string) => void; icon: (element: Element | Element[], icon: string, size?: number, color?: string, center?: boolean) => void; - animate: (element: Element, duration: number) => Runner; - resetAnimation: (element: Element) => void; - finishAnimation: (element: Element) => void; cssAnimate: (element: Element, duration: number) => ScadaSymbolAnimation; cssAnimation: (element: Element) => ScadaSymbolAnimation | undefined; resetCssAnimation: (element: Element) => void; @@ -657,9 +654,6 @@ export class ScadaSymbolObject { text: this.setElementText.bind(this), font: this.setElementFont.bind(this), icon: this.setElementIcon.bind(this), - animate: this.animate.bind(this), - resetAnimation: this.resetAnimation.bind(this), - finishAnimation: this.finishAnimation.bind(this), cssAnimate: this.cssAnimate.bind(this), cssAnimation: this.cssAnimation.bind(this), resetCssAnimation: this.resetCssAnimation.bind(this), @@ -731,8 +725,6 @@ export class ScadaSymbolObject { const valueSetter = ValueSetter.fromSettings(this.ctx, setValueSettings, this.simulated); this.valueSetters[setBehavior.id] = valueSetter; this.valueActions.push(valueSetter); - } else if (behavior.type === ScadaSymbolBehaviorType.widgetAction) { - // TODO: } } this.renderState(); @@ -992,21 +984,6 @@ export class ScadaSymbolObject { } } - private animate(element: Element, duration: number): Runner { - this.finishAnimation(element); - return element.animate(duration, 0, 'now'); - } - - private resetAnimation(element: Element) { - element.timeline().stop(); - element.timeline(new Timeline()); - } - - private finishAnimation(element: Element) { - element.timeline().finish(); - element.timeline(new Timeline()); - } - private cssAnimate(element: Element, duration: number): ScadaSymbolAnimation { return this.cssAnimations.animate(element, duration); } diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.models.ts b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.models.ts index f6be48a08c..005f6abd8e 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.models.ts +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.models.ts @@ -1235,49 +1235,6 @@ export const scadaSymbolContextCompletion = (metadata: ScadaSymbolMetadata, tags }, ] }, - animate: { - meta: 'function', - description: 'Finishes any previous animation and starts new animation for SVG element.', - args: [ - { - name: 'element', - description: 'SVG element', - type: 'Element' - }, - { - name: 'duration', - description: 'Animation duration in milliseconds', - type: 'number' - } - ], - return: { - description: 'Instance of SVG.Runner which has the same methods as any element and additional methods to control the runner.', - type: 'SVG.Runner' - } - }, - resetAnimation: { - meta: 'function', - description: 'Stops animation if any and restore SVG element initial state, resets animation timeline.', - args: [ - { - name: 'element', - description: 'SVG element', - type: 'Element' - }, - ] - }, - finishAnimation: { - meta: 'function', - description: 'Finishes animation if any, SVG element state updated according to the end animation values, ' + - 'resets animation timeline.', - args: [ - { - name: 'element', - description: 'SVG element', - type: 'Element' - }, - ] - }, generateElementId: { meta: 'function', description: 'Generates new unique element id.',