Browse Source

UI: Implement metadata tags section.

pull/11391/head
Igor Kulikov 2 years ago
parent
commit
d8d62979fc
  1. 23
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.ts
  2. 4
      ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.html
  3. 4
      ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.ts
  4. 43
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-components.module.ts
  5. 74
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.html
  6. 22
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.scss
  7. 154
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.ts
  8. 36
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.html
  9. 25
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.scss
  10. 171
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.ts
  11. 6
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.html
  12. 9
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.scss
  13. 27
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts
  14. 8
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.ts
  15. 47
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.html
  16. 41
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.scss
  17. 43
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.ts
  18. 22
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.models.ts
  19. 12
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.module.ts
  20. 8
      ui-ngx/src/assets/locale/locale.constant-en_US.json

23
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.ts

@ -44,8 +44,9 @@ import {
IotSvgPropertyRow,
toPropertyRows
} from '@home/components/widget/lib/settings/common/svg/iot-svg-object-settings.models';
import { merge, Observable, Subscription } from 'rxjs';
import { merge, Observable, of, Subscription } from 'rxjs';
import { WidgetActionCallbacks } from '@home/components/widget/action/manage-widget-actions.component.models';
import { ImageService } from '@core/http/image.service';
@Component({
selector: 'tb-iot-svg-object-settings',
@ -74,6 +75,12 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
@Input()
svgPath = 'drawing.svg';
@Input()
svgUrl: string;
@Input()
svgContent: string;
@Input()
aliasController: IAliasController;
@ -101,6 +108,7 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
constructor(protected store: Store<AppState>,
private fb: UntypedFormBuilder,
private http: HttpClient,
private imageService: ImageService,
private cd: ChangeDetectorRef) {
}
@ -116,7 +124,7 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
for (const propName of Object.keys(changes)) {
const change = changes[propName];
if (!change.firstChange && change.currentValue !== change.previousValue) {
if (['svgPath'].includes(propName)) {
if (['svgPath', 'svgUrl', 'svgContent'].includes(propName)) {
this.loadMetadata();
}
}
@ -160,7 +168,16 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
this.validatorSubscription = null;
}
this.validatorTriggers = [];
this.http.get(this.svgPath, {responseType: 'text'}).subscribe(
let svgContent$: Observable<string>;
if (this.svgContent) {
svgContent$ = of(this.svgContent);
} else if (this.svgUrl) {
svgContent$ = this.imageService.getImageString(this.svgUrl);
} else {
svgContent$ = this.http.get(this.svgPath, {responseType: 'text'});
}
svgContent$.subscribe(
(svgContent) => {
this.metadata = parseIotSvgMetadataFromContent(svgContent);
this.propertyRows = toPropertyRows(this.metadata.properties);

4
ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.html

@ -17,7 +17,9 @@
-->
<div class="tb-iot-svg-panel" [style]="backgroundStyle$ | async">
<div class="tb-iot-svg-overlay" [style]="overlayStyle"></div>
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
<ng-container *ngIf="widgetComponent.dashboardWidget.showWidgetTitlePanel">
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
</ng-container>
<div class="tb-iot-svg-content">
<div #iotSvgShape class="tb-iot-svg-shape">
</div>

4
ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.ts

@ -38,6 +38,7 @@ import {
import { Observable, of } from 'rxjs';
import { backgroundStyle, ComponentStyle, overlayStyle } from '@shared/models/widget-settings.models';
import { ImageService } from '@core/http/image.service';
import { WidgetComponent } from '@home/components/widget/widget.component';
@Component({
selector: 'tb-iot-svg-widget',
@ -61,7 +62,8 @@ export class IotSvgWidgetComponent extends
private shapeResize$: ResizeObserver;
constructor(protected imagePipe: ImagePipe,
constructor(public widgetComponent: WidgetComponent,
protected imagePipe: ImagePipe,
protected sanitizer: DomSanitizer,
private renderer: Renderer2,
private imageService: ImageService,

43
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-components.module.ts

@ -0,0 +1,43 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SharedModule } from '@shared/shared.module';
import {
ScadaSymbolMetadataTagComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component';
import { ScadaSymbolMetadataComponent } from '@home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component';
import {
ScadaSymbolMetadataTagsComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component';
@NgModule({
declarations:
[
ScadaSymbolMetadataComponent,
ScadaSymbolMetadataTagComponent,
ScadaSymbolMetadataTagsComponent
],
imports: [
CommonModule,
SharedModule
],
exports: [
ScadaSymbolMetadataComponent
]
})
export class ScadaSymbolMetadataComponentsModule { }

74
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.html

@ -0,0 +1,74 @@
<!--
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.
-->
<div [formGroup]="tagFormGroup" class="tb-form-panel stroked">
<mat-expansion-panel #expansionPanel class="tb-settings">
<mat-expansion-panel-header fxLayout="row wrap">
<div class="tb-scada-symbol-metadata-tag-header">
<div class="tb-form-row tb-flex">
<div class="fixed-title-width" translate>scada.tag</div>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic" (click)="$event.stopPropagation()">
<mat-select required formControlName="tag" placeholder="{{ 'scada.select-tag' | translate }}">
<mat-option *ngFor="let tag of availableTags" [value]="tag">
{{ tag }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<button type="button"
mat-icon-button
(click)="tagRemoved.emit()"
matTooltip="{{ 'scada.remove-tag' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</div>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-panel stroked">
<mat-expansion-panel class="tb-settings">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
{{ 'scada.state-render-function' | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-js-func formControlName="stateRenderFunction"
[functionArgs]="['element', 'ctx']"
helpId="widget/lib/timeseries/cell_style_fn">
</tb-js-func>
</ng-template>
</mat-expansion-panel>
</div>
<div class="tb-form-panel stroked">
<mat-expansion-panel class="tb-settings">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
{{ 'scada.on-click-action' | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-js-func formControlName="clickAction"
[functionArgs]="['event', 'ctx']"
helpId="widget/lib/timeseries/cell_style_fn">
</tb-js-func>
</ng-template>
</mat-expansion-panel>
</div>
</ng-template>
</mat-expansion-panel>
</div>

22
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.scss

@ -0,0 +1,22 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-scada-symbol-metadata-tag-header {
flex: 1;
display: flex;
flex-direction: row;
gap: 8px;
padding-right: 8px;
}

154
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.ts

@ -0,0 +1,154 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {
ChangeDetectorRef,
Component, EventEmitter,
forwardRef,
Input,
OnChanges,
OnInit, Output,
SimpleChanges, ViewChild,
ViewEncapsulation
} from '@angular/core';
import {
ControlValueAccessor,
NG_VALUE_ACCESSOR,
UntypedFormBuilder,
UntypedFormGroup,
Validators
} from '@angular/forms';
import { IotSvgTag } from '@home/components/widget/lib/svg/iot-svg.models';
import { MatExpansionPanel } from '@angular/material/expansion';
@Component({
selector: 'tb-scada-symbol-metadata-tag',
templateUrl: './scada-symbol-metadata-tag.component.html',
styleUrls: ['./scada-symbol-metadata-tag.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ScadaSymbolMetadataTagComponent),
multi: true
}
],
encapsulation: ViewEncapsulation.None
})
export class ScadaSymbolMetadataTagComponent implements ControlValueAccessor, OnInit, OnChanges {
@ViewChild('expansionPanel')
expansionPanel: MatExpansionPanel;
@Input()
disabled: boolean;
@Input()
tags: string[];
@Output()
tagRemoved = new EventEmitter();
availableTags: string[];
tagFormGroup: UntypedFormGroup;
modelValue: IotSvgTag;
private propagateChange = (_val: any) => {};
constructor(private fb: UntypedFormBuilder,
private cd: ChangeDetectorRef) {
}
ngOnInit() {
this.availableTags = (this.tags || []).concat();
this.tagFormGroup = this.fb.group({
tag: [null, [Validators.required]],
stateRenderFunction: [null, []],
clickAction: [null, []]
});
this.tagFormGroup.valueChanges.subscribe(
() => this.updateModel()
);
}
ngOnChanges(changes: SimpleChanges): void {
for (const propName of Object.keys(changes)) {
const change = changes[propName];
if (!change.firstChange && change.currentValue !== change.previousValue) {
if (['tags'].includes(propName)) {
this.availableTags = (this.tags || []).concat();
if (this.modelValue?.tag && !this.availableTags.includes(this.modelValue?.tag)) {
this.availableTags.push(this.modelValue?.tag);
this.availableTags.sort();
}
}
}
}
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.tagFormGroup.disable({emitEvent: false});
} else {
this.tagFormGroup.enable({emitEvent: false});
}
}
writeValue(value: IotSvgTag): void {
this.modelValue = value;
if (value?.tag && !(this.tags || []).includes(value?.tag)) {
this.availableTags = (this.tags || []).concat(value.tag).sort();
}
const clickAction = value?.actions && value?.actions.click ? value.actions.click.actionFunction : null;
this.tagFormGroup.patchValue(
{
tag: value?.tag,
stateRenderFunction: value?.stateRenderFunction,
clickAction
}, {emitEvent: false}
);
}
private updateModel() {
const value = this.tagFormGroup.value;
if (value.tag && !this.modelValue?.tag) {
this.expansionPanel.open();
}
this.modelValue = {
tag: value.tag,
stateRenderFunction: value.stateRenderFunction
};
if (value.clickAction) {
this.modelValue.actions = {
click: {
actionFunction: value.clickAction
}
};
} else {
this.modelValue.actions = null;
}
this.propagateChange(this.modelValue);
}
}

36
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.html

@ -0,0 +1,36 @@
<!--
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.
-->
<div class="tb-form-panel tb-scada-symbol-metadata-tags">
<div *ngIf="tagsFormArray().controls.length; else noTags" class="tb-scada-symbol-metadata-tags-list">
<div *ngFor="let tagControl of tagsFormArray().controls; trackBy: trackByTag; let $index = index; let $last = last">
<tb-scada-symbol-metadata-tag fxFlex
[formControl]="tagControl"
[tags]="availableTags"
(tagRemoved)="removeTag($index)">
</tb-scada-symbol-metadata-tag>
</div>
</div>
<div>
<button type="button" mat-stroked-button color="primary" (click)="addTag()">
{{ 'scada.add-tag' | translate }}
</button>
</div>
</div>
<ng-template #noTags>
<span fxLayoutAlign="start center" class="tb-prompt tb-no-tags">{{ 'scada.no-tags' | translate }}</span>
</ng-template>

25
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.scss

@ -0,0 +1,25 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-scada-symbol-metadata-tags {
.tb-scada-symbol-metadata-tags-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.tb-no-tags {
height: 60px;
}
}

171
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.ts

@ -0,0 +1,171 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, forwardRef, Input, OnChanges, OnInit, SimpleChanges, ViewEncapsulation } from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
UntypedFormArray,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
ValidationErrors,
Validator
} from '@angular/forms';
import { IotSvgTag } from '@home/components/widget/lib/svg/iot-svg.models';
const tagValidator = (control: AbstractControl): ValidationErrors | null => {
const tag: IotSvgTag = control.value;
if (!tag.tag) {
return {
tag: true
};
}
return null;
};
@Component({
selector: 'tb-scada-symbol-metadata-tags',
templateUrl: './scada-symbol-metadata-tags.component.html',
styleUrls: ['./scada-symbol-metadata-tags.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ScadaSymbolMetadataTagsComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => ScadaSymbolMetadataTagsComponent),
multi: true
}
],
encapsulation: ViewEncapsulation.None
})
export class ScadaSymbolMetadataTagsComponent implements ControlValueAccessor, OnInit, Validator, OnChanges {
@Input()
disabled: boolean;
@Input()
tags: string[];
availableTags: string[];
tagsFormGroup: UntypedFormGroup;
private propagateChange = (_val: any) => {};
constructor(private fb: UntypedFormBuilder) {
}
ngOnInit() {
this.tagsFormGroup = this.fb.group({
tags: this.fb.array([])
});
this.updateAvailableTags();
this.tagsFormGroup.valueChanges.subscribe(
() => {
let tags: IotSvgTag[] = this.tagsFormGroup.get('tags').value;
if (tags) {
tags = tags.filter(t => !!t.tag);
}
this.updateAvailableTags();
this.propagateChange(tags);
}
);
}
ngOnChanges(changes: SimpleChanges): void {
for (const propName of Object.keys(changes)) {
const change = changes[propName];
if (!change.firstChange && change.currentValue !== change.previousValue) {
if (['tags'].includes(propName)) {
this.updateAvailableTags();
}
}
}
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.tagsFormGroup.disable({emitEvent: false});
} else {
this.tagsFormGroup.enable({emitEvent: false});
}
}
writeValue(value: IotSvgTag[] | undefined): void {
const tags= value || [];
this.tagsFormGroup.setControl('tags', this.prepareTagsFormArray(tags), {emitEvent: false});
}
public validate(c: UntypedFormControl) {
const valid = this.tagsFormGroup.valid;
return valid ? null : {
tags: {
valid: false,
},
};
}
tagsFormArray(): UntypedFormArray {
return this.tagsFormGroup.get('tags') as UntypedFormArray;
}
trackByTag(index: number, tagControl: AbstractControl): any {
return tagControl;
}
removeTag(index: number) {
(this.tagsFormGroup.get('tags') as UntypedFormArray).removeAt(index);
}
addTag() {
const tag: IotSvgTag = {
tag: null
};
const tagsArray = this.tagsFormGroup.get('tags') as UntypedFormArray;
const tagControl = this.fb.control(tag, [tagValidator]);
tagsArray.push(tagControl);
}
private prepareTagsFormArray(tags: IotSvgTag[] | undefined): UntypedFormArray {
const tagsControls: Array<AbstractControl> = [];
if (tags) {
tags.forEach((tag) => {
tagsControls.push(this.fb.control(tag, [tagValidator]));
});
}
return this.fb.array(tagsControls);
}
private updateAvailableTags() {
const svgTags: IotSvgTag[] = this.tagsFormGroup.get('tags').value;
const usedTags = (svgTags || []).filter(t => !!t.tag).map(t => t.tag);
this.availableTags = (this.tags || []).filter(t => !usedTags.includes(t));
}
}

6
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-metadata.component.html → ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.html

@ -51,5 +51,11 @@
</div>
</div>
</div>
<div [fxShow]="selectedOption === 'tags'" class="mat-content">
<tb-scada-symbol-metadata-tags
[tags]="tags"
formControlName="tags">
</tb-scada-symbol-metadata-tags>
</div>
</div>
</div>

9
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-metadata.component.scss → ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.scss

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../../../../scss/constants';
@import '../../../../../../scss/constants';
.tb-scada-symbol-metadata {
display: flex;
@ -61,5 +61,12 @@
gap: 8px;
}
}
.tb-form-panel {
.mat-expansion-panel.tb-settings {
& > .mat-expansion-panel-header {
font-weight: 400;
}
}
}
}
}

27
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-metadata.component.ts → ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts

@ -14,12 +14,15 @@
/// limitations under the License.
///
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { Component, forwardRef, Input, OnInit, ViewEncapsulation } from '@angular/core';
import {
ControlValueAccessor,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
Validator,
Validators
} from '@angular/forms';
import { PageComponent } from '@shared/components/page.component';
@ -39,14 +42,23 @@ import { TranslateService } from '@ngx-translate/core';
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ScadaSymbolMetadataComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => ScadaSymbolMetadataComponent),
multi: true
}
]
],
encapsulation: ViewEncapsulation.None
})
export class ScadaSymbolMetadataComponent extends PageComponent implements OnInit, ControlValueAccessor {
export class ScadaSymbolMetadataComponent extends PageComponent implements OnInit, ControlValueAccessor, Validator {
@Input()
disabled: boolean;
@Input()
tags: string[];
private modelValue: IotSvgMetadata;
private propagateChange = null;
@ -118,6 +130,15 @@ export class ScadaSymbolMetadataComponent extends PageComponent implements OnIni
);
}
public validate(c: UntypedFormControl) {
const valid = this.metadataFormGroup.valid;
return valid ? null : {
metadata: {
valid: false,
},
};
}
private updateModel() {
const metadata: IotSvgMetadata = this.metadataFormGroup.getRawValue();
this.modelValue = metadata;

8
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-editor.component.ts

@ -17,11 +17,11 @@
import {
AfterViewInit,
Component,
ElementRef,
ElementRef, EventEmitter,
Input,
OnChanges,
OnDestroy,
OnInit,
OnInit, Output,
SimpleChanges,
ViewChild,
ViewContainerRef,
@ -47,6 +47,9 @@ export class ScadaSymbolEditorComponent implements OnInit, OnDestroy, AfterViewI
@Input()
data: ScadaSymbolEditorData;
@Output()
tags = new EventEmitter<string[]>();
scadaSymbolEditObject: ScadaSymbolEditObject;
constructor(private viewContainerRef: ViewContainerRef) {
@ -58,6 +61,7 @@ export class ScadaSymbolEditorComponent implements OnInit, OnDestroy, AfterViewI
ngAfterViewInit() {
this.scadaSymbolEditObject = new ScadaSymbolEditObject(this.iotSvgShape.nativeElement,
this.viewContainerRef);
this.scadaSymbolEditObject.tagsUpdated.subscribe(tags => this.tags.emit(tags));
if (this.data) {
this.scadaSymbolEditObject.setContent(this.data.svgContent);
}

47
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.html

@ -29,14 +29,43 @@
[showCloseDetails]="false"
headerHeightPx="64"
headerTitle="{{symbolData?.imageResource?.title}}">
<button mat-button
*ngIf="previewMode"
(click)="exitPreviewMode()">
<mat-icon>chevron_left</mat-icon>
{{ 'action.back' | translate }}
</button>
<div *ngIf="previewMode" class="tb-scada-symbol-preview-content tb-absolute-fill">
<div class="tb-scada-symbol-preview-header">
<button mat-button
(click)="exitPreviewMode()">
<mat-icon>chevron_left</mat-icon>
{{ 'action.back' | translate }}
</button>
<span style="flex: 1;"></span>
<button [disabled]="!scadaPreviewFormGroup.dirty"
mat-stroked-button color="primary"
(click)="onRevertPreviewSettings()">
<mat-icon>close</mat-icon>
{{ 'action.decline' | translate }}
</button>
<button [disabled]="!scadaPreviewFormGroup.valid || !scadaPreviewFormGroup.dirty"
mat-flat-button color="primary"
(click)="onApplyPreviewSettings()">
<mat-icon>done</mat-icon>
{{ 'action.apply' | translate }}
</button>
</div>
<div [formGroup]="scadaPreviewFormGroup" class="tb-scada-symbol-preview-settings">
<div class="mat-content">
<tb-iot-svg-object-settings
formControlName="iotSvgObject"
[svgContent]="symbolData.svgContent"
[aliasController]="aliasController"
[widgetType]="widgetType.rpc"
[callbacks]="widgetActionCallbacks">
</tb-iot-svg-object-settings>
</div>
</div>
</div>
<div [fxShow]="!previewMode" [formGroup]="scadaSymbolFormGroup">
<tb-scada-symbol-metadata formControlName="metadata">
<tb-scada-symbol-metadata
[tags]="tags"
formControlName="metadata">
<div class="tb-scada-symbol-metadata-header-prefix">
<button fxHide.gt-sm
[disabled]="!scadaSymbolFormGroup.valid"
@ -74,7 +103,9 @@
</tb-details-panel>
</mat-drawer>
<mat-drawer-content class="tb-scada-symbol-content">
<tb-scada-symbol-editor *ngIf="!previewMode" #symbolEditor [data]="symbolEditorData"></tb-scada-symbol-editor>
<tb-scada-symbol-editor *ngIf="!previewMode" #symbolEditor
[data]="symbolEditorData"
(tags)="tags = $event"></tb-scada-symbol-editor>
<tb-dashboard *ngIf="previewMode"
class="tb-absolute-fill"
[aliasController]="aliasController"

41
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.scss

@ -13,16 +13,57 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../../../../scss/constants';
.tb-scada-symbol {
width: 100%;
height: 100%;
.tb-scada-symbol-details-drawer {
width: 50%;
.tb-scada-symbol-preview-content {
display: flex;
flex-direction: column;
gap: 8px;
.tb-scada-symbol-preview-header {
padding: 24px 24px 0;
display: flex;
gap: 12px;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.tb-scada-symbol-preview-settings {
& > .mat-content {
padding-top: 8px;
@media #{$mat-xs} {
padding-left: 8px;
padding-right: 8px;
}
}
flex: 1;
overflow: auto;
& > div {
padding: 16px;
}
.mat-content {
display: flex;
flex-direction: column;
gap: 16px;
@media #{$mat-xs} {
gap: 8px;
}
}
}
}
}
.tb-scada-symbol-content {
flex: 1;
min-width: 0;
min-height: 0;
background: #fff;
.tb-iot-svg-panel {
padding: 0;
}
}
}

43
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.ts

@ -58,6 +58,7 @@ import {
iotSvgWidgetDefaultSettings,
IotSvgWidgetSettings
} from '@home/components/widget/lib/svg/iot-svg-widget.models';
import { WidgetActionCallbacks } from '@home/components/widget/action/manage-widget-actions.component.models';
@Component({
selector: 'tb-scada-symbol',
@ -67,6 +68,8 @@ import {
})
export class ScadaSymbolComponent extends PageComponent implements OnInit, OnDestroy, AfterViewInit, HasDirtyFlag {
widgetType = widgetType;
@HostBinding('style.width') width = '100%';
@HostBinding('style.height') height = '100%';
@ -80,6 +83,8 @@ export class ScadaSymbolComponent extends PageComponent implements OnInit, OnDes
scadaSymbolFormGroup: UntypedFormGroup;
scadaPreviewFormGroup: UntypedFormGroup;
private destroy$ = new Subject<void>();
private origSymbolData: ScadaSymbolData;
@ -88,8 +93,17 @@ export class ScadaSymbolComponent extends PageComponent implements OnInit, OnDes
aliasController: IAliasController;
widgetActionCallbacks: WidgetActionCallbacks = {
fetchDashboardStates: () => [],
fetchCellClickColumns: () => []
};
previewWidgets: Array<Widget> = [];
tags: string[];
private previewIotSvgObjectSettings: IotSvgObjectSettings;
private previewWidget: Widget;
private forcePristine = false;
@ -118,6 +132,9 @@ export class ScadaSymbolComponent extends PageComponent implements OnInit, OnDes
this.scadaSymbolFormGroup = this.fb.group({
metadata: [null]
});
this.scadaPreviewFormGroup = this.fb.group({
iotSvgObject: [null]
});
const entitiAliases: EntityAliases = {};
@ -190,9 +207,13 @@ export class ScadaSymbolComponent extends PageComponent implements OnInit, OnDes
enterPreviewMode() {
this.symbolData.svgContent = this.prepareSvgContent();
const iotSvgObject: IotSvgObjectSettings = {};
this.previewIotSvgObjectSettings = {};
this.scadaPreviewFormGroup.patchValue({
iotSvgObject: this.previewIotSvgObjectSettings
}, {emitEvent: false});
this.scadaPreviewFormGroup.markAsPristine();
const settings: IotSvgWidgetSettings = {...iotSvgWidgetDefaultSettings,
...{iotSvg: null, iotSvgContent: this.symbolData.svgContent, iotSvgObject}};
...{iotSvg: null, iotSvgContent: this.symbolData.svgContent, iotSvgObject: this.previewIotSvgObjectSettings}};
this.previewWidget = {
typeFullFqn: 'system.iot_svg',
type: widgetType.rpc,
@ -202,6 +223,7 @@ export class ScadaSymbolComponent extends PageComponent implements OnInit, OnDes
col: 0,
config: {
settings,
showTitle: false,
dropShadow: false,
padding: '0',
margin: '0'
@ -221,11 +243,22 @@ export class ScadaSymbolComponent extends PageComponent implements OnInit, OnDes
this.previewMode = false;
}
onRevertPreviewSettings() {
this.scadaPreviewFormGroup.patchValue({
iotSvgObject: this.previewIotSvgObjectSettings
}, {emitEvent: false});
this.scadaPreviewFormGroup.markAsPristine();
}
onApplyPreviewSettings() {
this.scadaPreviewFormGroup.markAsPristine();
this.previewIotSvgObjectSettings = this.scadaPreviewFormGroup.get('iotSvgObject').value;
this.updatePreviewWidgetSettings();
}
private updatePreviewWidgetSettings() {
this.previewWidget = deepClone(this.previewWidget);
this.previewWidget.config.title = this.symbolData.imageResource.title;
this.previewWidget.config.showTitle = true;
this.previewWidget.config.padding = '48px';
this.previewWidget.config.settings.iotSvgObject = this.previewIotSvgObjectSettings;
this.previewWidgets = [this.previewWidget];
}

22
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.models.ts

@ -18,7 +18,7 @@ import { ImageResourceInfo } from '@shared/models/resource.models';
import * as svgjs from '@svgdotjs/svg.js';
import { Box, Element, Rect, Style, SVG, Svg, Timeline } from '@svgdotjs/svg.js';
import { ResizeObserver } from '@juggle/resize-observer';
import { ViewContainerRef } from '@angular/core';
import { EventEmitter, ViewContainerRef } from '@angular/core';
import { forkJoin, from } from 'rxjs';
import {
setupAddTagPanelTooltip,
@ -47,6 +47,11 @@ export class ScadaSymbolEditObject {
public scale = 1;
public dirty = false;
public tags: string[] = [];
public tagsUpdated = new EventEmitter<string[]>();
constructor(private rootElement: HTMLElement,
public viewContainerRef: ViewContainerRef) {
this.shapeResize$ = new ResizeObserver(() => {
@ -202,6 +207,7 @@ export class ScadaSymbolEditObject {
for (const el of this.elements) {
el.init();
}
this.updateTags();
}
private addElement(e: Element) {
@ -221,6 +227,7 @@ export class ScadaSymbolEditObject {
this.shapeResize$.disconnect();
}
this.destroyElements();
this.tagsUpdated.complete();
}
private destroyElements() {
@ -286,6 +293,15 @@ export class ScadaSymbolEditObject {
}
}
public updateTags() {
this.tags = this.elements
.filter(e => e.hasTag())
.map(e => e.tag)
.filter((v, i, a) => a.indexOf(v) === i)
.sort();
this.tagsUpdated.emit(this.tags);
}
}
const hasBBox = (e: Element): boolean => {
@ -429,6 +445,7 @@ export class ScadaSymbolElement {
this.unhighlight();
this.createAddTagTooltip();
this.editObject.dirty = true;
this.editObject.updateTags();
}
public setTag(tag: string) {
@ -437,6 +454,7 @@ export class ScadaSymbolElement {
this.element.attr('tb:tag', tag);
this.createTagTooltip();
this.editObject.dirty = true;
this.editObject.updateTags();
}
public startEdit(onCancelEdit: () => void) {
@ -661,7 +679,7 @@ export class ScadaSymbolElement {
return side;
}
private hasTag() {
public hasTag() {
return !!this.tag;
}

12
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.module.ts

@ -21,20 +21,24 @@ import { HomeComponentsModule } from '@modules/home/components/home-components.m
import { ScadaSymbolComponent } from '@home/pages/scada-symbol/scada-symbol.component';
import { ScadaSymbolEditorComponent } from '@home/pages/scada-symbol/scada-symbol-editor.component';
import { ScadaSymbolTooltipComponentsModule } from '@home/pages/scada-symbol/scada-symbol-tooltip.components';
import { ScadaSymbolMetadataComponent } from '@home/pages/scada-symbol/scada-symbol-metadata.component';
import { WidgetSettingsCommonModule } from '@home/components/widget/lib/settings/common/widget-settings-common.module';
import {
ScadaSymbolMetadataComponentsModule
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-metadata-components.module';
@NgModule({
declarations:
[
ScadaSymbolEditorComponent,
ScadaSymbolComponent,
ScadaSymbolMetadataComponent
ScadaSymbolComponent
],
imports: [
CommonModule,
SharedModule,
HomeComponentsModule,
ScadaSymbolTooltipComponentsModule
ScadaSymbolMetadataComponentsModule,
ScadaSymbolTooltipComponentsModule,
WidgetSettingsCommonModule
]
})
export class ScadaSymbolModule { }

8
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -3417,7 +3417,13 @@
"properties": "Properties",
"title": "Title",
"state-render-function": "State render function",
"preview": "Preview"
"preview": "Preview",
"tag": "Tag",
"on-click-action": "On click action",
"remove-tag": "Remove tag",
"add-tag": "Add tag",
"no-tags": "No tags configured",
"select-tag": "Select tag"
},
"item": {
"selected": "Selected"

Loading…
Cancel
Save