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, IotSvgPropertyRow,
toPropertyRows toPropertyRows
} from '@home/components/widget/lib/settings/common/svg/iot-svg-object-settings.models'; } 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 { WidgetActionCallbacks } from '@home/components/widget/action/manage-widget-actions.component.models';
import { ImageService } from '@core/http/image.service';
@Component({ @Component({
selector: 'tb-iot-svg-object-settings', selector: 'tb-iot-svg-object-settings',
@ -74,6 +75,12 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
@Input() @Input()
svgPath = 'drawing.svg'; svgPath = 'drawing.svg';
@Input()
svgUrl: string;
@Input()
svgContent: string;
@Input() @Input()
aliasController: IAliasController; aliasController: IAliasController;
@ -101,6 +108,7 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
private fb: UntypedFormBuilder, private fb: UntypedFormBuilder,
private http: HttpClient, private http: HttpClient,
private imageService: ImageService,
private cd: ChangeDetectorRef) { private cd: ChangeDetectorRef) {
} }
@ -116,7 +124,7 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
for (const propName of Object.keys(changes)) { for (const propName of Object.keys(changes)) {
const change = changes[propName]; const change = changes[propName];
if (!change.firstChange && change.currentValue !== change.previousValue) { if (!change.firstChange && change.currentValue !== change.previousValue) {
if (['svgPath'].includes(propName)) { if (['svgPath', 'svgUrl', 'svgContent'].includes(propName)) {
this.loadMetadata(); this.loadMetadata();
} }
} }
@ -160,7 +168,16 @@ export class IotSvgObjectSettingsComponent implements OnInit, OnChanges, Control
this.validatorSubscription = null; this.validatorSubscription = null;
} }
this.validatorTriggers = []; 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) => { (svgContent) => {
this.metadata = parseIotSvgMetadataFromContent(svgContent); this.metadata = parseIotSvgMetadataFromContent(svgContent);
this.propertyRows = toPropertyRows(this.metadata.properties); 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-panel" [style]="backgroundStyle$ | async">
<div class="tb-iot-svg-overlay" [style]="overlayStyle"></div> <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 class="tb-iot-svg-content">
<div #iotSvgShape class="tb-iot-svg-shape"> <div #iotSvgShape class="tb-iot-svg-shape">
</div> </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 { Observable, of } from 'rxjs';
import { backgroundStyle, ComponentStyle, overlayStyle } from '@shared/models/widget-settings.models'; import { backgroundStyle, ComponentStyle, overlayStyle } from '@shared/models/widget-settings.models';
import { ImageService } from '@core/http/image.service'; import { ImageService } from '@core/http/image.service';
import { WidgetComponent } from '@home/components/widget/widget.component';
@Component({ @Component({
selector: 'tb-iot-svg-widget', selector: 'tb-iot-svg-widget',
@ -61,7 +62,8 @@ export class IotSvgWidgetComponent extends
private shapeResize$: ResizeObserver; private shapeResize$: ResizeObserver;
constructor(protected imagePipe: ImagePipe, constructor(public widgetComponent: WidgetComponent,
protected imagePipe: ImagePipe,
protected sanitizer: DomSanitizer, protected sanitizer: DomSanitizer,
private renderer: Renderer2, private renderer: Renderer2,
private imageService: ImageService, 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>
</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>
</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 * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
@import '../../../../../scss/constants'; @import '../../../../../../scss/constants';
.tb-scada-symbol-metadata { .tb-scada-symbol-metadata {
display: flex; display: flex;
@ -61,5 +61,12 @@
gap: 8px; 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. /// limitations under the License.
/// ///
import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { Component, forwardRef, Input, OnInit, ViewEncapsulation } from '@angular/core';
import { import {
ControlValueAccessor, ControlValueAccessor,
NG_VALIDATORS,
NG_VALUE_ACCESSOR, NG_VALUE_ACCESSOR,
UntypedFormBuilder, UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup, UntypedFormGroup,
Validator,
Validators Validators
} from '@angular/forms'; } from '@angular/forms';
import { PageComponent } from '@shared/components/page.component'; import { PageComponent } from '@shared/components/page.component';
@ -39,14 +42,23 @@ import { TranslateService } from '@ngx-translate/core';
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ScadaSymbolMetadataComponent), useExisting: forwardRef(() => ScadaSymbolMetadataComponent),
multi: true 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() @Input()
disabled: boolean; disabled: boolean;
@Input()
tags: string[];
private modelValue: IotSvgMetadata; private modelValue: IotSvgMetadata;
private propagateChange = null; 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() { private updateModel() {
const metadata: IotSvgMetadata = this.metadataFormGroup.getRawValue(); const metadata: IotSvgMetadata = this.metadataFormGroup.getRawValue();
this.modelValue = metadata; this.modelValue = metadata;

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

@ -17,11 +17,11 @@
import { import {
AfterViewInit, AfterViewInit,
Component, Component,
ElementRef, ElementRef, EventEmitter,
Input, Input,
OnChanges, OnChanges,
OnDestroy, OnDestroy,
OnInit, OnInit, Output,
SimpleChanges, SimpleChanges,
ViewChild, ViewChild,
ViewContainerRef, ViewContainerRef,
@ -47,6 +47,9 @@ export class ScadaSymbolEditorComponent implements OnInit, OnDestroy, AfterViewI
@Input() @Input()
data: ScadaSymbolEditorData; data: ScadaSymbolEditorData;
@Output()
tags = new EventEmitter<string[]>();
scadaSymbolEditObject: ScadaSymbolEditObject; scadaSymbolEditObject: ScadaSymbolEditObject;
constructor(private viewContainerRef: ViewContainerRef) { constructor(private viewContainerRef: ViewContainerRef) {
@ -58,6 +61,7 @@ export class ScadaSymbolEditorComponent implements OnInit, OnDestroy, AfterViewI
ngAfterViewInit() { ngAfterViewInit() {
this.scadaSymbolEditObject = new ScadaSymbolEditObject(this.iotSvgShape.nativeElement, this.scadaSymbolEditObject = new ScadaSymbolEditObject(this.iotSvgShape.nativeElement,
this.viewContainerRef); this.viewContainerRef);
this.scadaSymbolEditObject.tagsUpdated.subscribe(tags => this.tags.emit(tags));
if (this.data) { if (this.data) {
this.scadaSymbolEditObject.setContent(this.data.svgContent); 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" [showCloseDetails]="false"
headerHeightPx="64" headerHeightPx="64"
headerTitle="{{symbolData?.imageResource?.title}}"> headerTitle="{{symbolData?.imageResource?.title}}">
<button mat-button <div *ngIf="previewMode" class="tb-scada-symbol-preview-content tb-absolute-fill">
*ngIf="previewMode" <div class="tb-scada-symbol-preview-header">
(click)="exitPreviewMode()"> <button mat-button
<mat-icon>chevron_left</mat-icon> (click)="exitPreviewMode()">
{{ 'action.back' | translate }} <mat-icon>chevron_left</mat-icon>
</button> {{ '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"> <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"> <div class="tb-scada-symbol-metadata-header-prefix">
<button fxHide.gt-sm <button fxHide.gt-sm
[disabled]="!scadaSymbolFormGroup.valid" [disabled]="!scadaSymbolFormGroup.valid"
@ -74,7 +103,9 @@
</tb-details-panel> </tb-details-panel>
</mat-drawer> </mat-drawer>
<mat-drawer-content class="tb-scada-symbol-content"> <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" <tb-dashboard *ngIf="previewMode"
class="tb-absolute-fill" class="tb-absolute-fill"
[aliasController]="aliasController" [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 * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
@import '../../../../../scss/constants';
.tb-scada-symbol { .tb-scada-symbol {
width: 100%; width: 100%;
height: 100%; height: 100%;
.tb-scada-symbol-details-drawer { .tb-scada-symbol-details-drawer {
width: 50%; 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 { .tb-scada-symbol-content {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
min-height: 0; min-height: 0;
background: #fff; 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, iotSvgWidgetDefaultSettings,
IotSvgWidgetSettings IotSvgWidgetSettings
} from '@home/components/widget/lib/svg/iot-svg-widget.models'; } from '@home/components/widget/lib/svg/iot-svg-widget.models';
import { WidgetActionCallbacks } from '@home/components/widget/action/manage-widget-actions.component.models';
@Component({ @Component({
selector: 'tb-scada-symbol', selector: 'tb-scada-symbol',
@ -67,6 +68,8 @@ import {
}) })
export class ScadaSymbolComponent extends PageComponent implements OnInit, OnDestroy, AfterViewInit, HasDirtyFlag { export class ScadaSymbolComponent extends PageComponent implements OnInit, OnDestroy, AfterViewInit, HasDirtyFlag {
widgetType = widgetType;
@HostBinding('style.width') width = '100%'; @HostBinding('style.width') width = '100%';
@HostBinding('style.height') height = '100%'; @HostBinding('style.height') height = '100%';
@ -80,6 +83,8 @@ export class ScadaSymbolComponent extends PageComponent implements OnInit, OnDes
scadaSymbolFormGroup: UntypedFormGroup; scadaSymbolFormGroup: UntypedFormGroup;
scadaPreviewFormGroup: UntypedFormGroup;
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
private origSymbolData: ScadaSymbolData; private origSymbolData: ScadaSymbolData;
@ -88,8 +93,17 @@ export class ScadaSymbolComponent extends PageComponent implements OnInit, OnDes
aliasController: IAliasController; aliasController: IAliasController;
widgetActionCallbacks: WidgetActionCallbacks = {
fetchDashboardStates: () => [],
fetchCellClickColumns: () => []
};
previewWidgets: Array<Widget> = []; previewWidgets: Array<Widget> = [];
tags: string[];
private previewIotSvgObjectSettings: IotSvgObjectSettings;
private previewWidget: Widget; private previewWidget: Widget;
private forcePristine = false; private forcePristine = false;
@ -118,6 +132,9 @@ export class ScadaSymbolComponent extends PageComponent implements OnInit, OnDes
this.scadaSymbolFormGroup = this.fb.group({ this.scadaSymbolFormGroup = this.fb.group({
metadata: [null] metadata: [null]
}); });
this.scadaPreviewFormGroup = this.fb.group({
iotSvgObject: [null]
});
const entitiAliases: EntityAliases = {}; const entitiAliases: EntityAliases = {};
@ -190,9 +207,13 @@ export class ScadaSymbolComponent extends PageComponent implements OnInit, OnDes
enterPreviewMode() { enterPreviewMode() {
this.symbolData.svgContent = this.prepareSvgContent(); 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, const settings: IotSvgWidgetSettings = {...iotSvgWidgetDefaultSettings,
...{iotSvg: null, iotSvgContent: this.symbolData.svgContent, iotSvgObject}}; ...{iotSvg: null, iotSvgContent: this.symbolData.svgContent, iotSvgObject: this.previewIotSvgObjectSettings}};
this.previewWidget = { this.previewWidget = {
typeFullFqn: 'system.iot_svg', typeFullFqn: 'system.iot_svg',
type: widgetType.rpc, type: widgetType.rpc,
@ -202,6 +223,7 @@ export class ScadaSymbolComponent extends PageComponent implements OnInit, OnDes
col: 0, col: 0,
config: { config: {
settings, settings,
showTitle: false,
dropShadow: false, dropShadow: false,
padding: '0', padding: '0',
margin: '0' margin: '0'
@ -221,11 +243,22 @@ export class ScadaSymbolComponent extends PageComponent implements OnInit, OnDes
this.previewMode = false; 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() { private updatePreviewWidgetSettings() {
this.previewWidget = deepClone(this.previewWidget); this.previewWidget = deepClone(this.previewWidget);
this.previewWidget.config.title = this.symbolData.imageResource.title; this.previewWidget.config.settings.iotSvgObject = this.previewIotSvgObjectSettings;
this.previewWidget.config.showTitle = true;
this.previewWidget.config.padding = '48px';
this.previewWidgets = [this.previewWidget]; 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 * as svgjs from '@svgdotjs/svg.js';
import { Box, Element, Rect, Style, SVG, Svg, Timeline } from '@svgdotjs/svg.js'; import { Box, Element, Rect, Style, SVG, Svg, Timeline } from '@svgdotjs/svg.js';
import { ResizeObserver } from '@juggle/resize-observer'; import { ResizeObserver } from '@juggle/resize-observer';
import { ViewContainerRef } from '@angular/core'; import { EventEmitter, ViewContainerRef } from '@angular/core';
import { forkJoin, from } from 'rxjs'; import { forkJoin, from } from 'rxjs';
import { import {
setupAddTagPanelTooltip, setupAddTagPanelTooltip,
@ -47,6 +47,11 @@ export class ScadaSymbolEditObject {
public scale = 1; public scale = 1;
public dirty = false; public dirty = false;
public tags: string[] = [];
public tagsUpdated = new EventEmitter<string[]>();
constructor(private rootElement: HTMLElement, constructor(private rootElement: HTMLElement,
public viewContainerRef: ViewContainerRef) { public viewContainerRef: ViewContainerRef) {
this.shapeResize$ = new ResizeObserver(() => { this.shapeResize$ = new ResizeObserver(() => {
@ -202,6 +207,7 @@ export class ScadaSymbolEditObject {
for (const el of this.elements) { for (const el of this.elements) {
el.init(); el.init();
} }
this.updateTags();
} }
private addElement(e: Element) { private addElement(e: Element) {
@ -221,6 +227,7 @@ export class ScadaSymbolEditObject {
this.shapeResize$.disconnect(); this.shapeResize$.disconnect();
} }
this.destroyElements(); this.destroyElements();
this.tagsUpdated.complete();
} }
private destroyElements() { 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 => { const hasBBox = (e: Element): boolean => {
@ -429,6 +445,7 @@ export class ScadaSymbolElement {
this.unhighlight(); this.unhighlight();
this.createAddTagTooltip(); this.createAddTagTooltip();
this.editObject.dirty = true; this.editObject.dirty = true;
this.editObject.updateTags();
} }
public setTag(tag: string) { public setTag(tag: string) {
@ -437,6 +454,7 @@ export class ScadaSymbolElement {
this.element.attr('tb:tag', tag); this.element.attr('tb:tag', tag);
this.createTagTooltip(); this.createTagTooltip();
this.editObject.dirty = true; this.editObject.dirty = true;
this.editObject.updateTags();
} }
public startEdit(onCancelEdit: () => void) { public startEdit(onCancelEdit: () => void) {
@ -661,7 +679,7 @@ export class ScadaSymbolElement {
return side; return side;
} }
private hasTag() { public hasTag() {
return !!this.tag; 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 { ScadaSymbolComponent } from '@home/pages/scada-symbol/scada-symbol.component';
import { ScadaSymbolEditorComponent } from '@home/pages/scada-symbol/scada-symbol-editor.component'; import { ScadaSymbolEditorComponent } from '@home/pages/scada-symbol/scada-symbol-editor.component';
import { ScadaSymbolTooltipComponentsModule } from '@home/pages/scada-symbol/scada-symbol-tooltip.components'; 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({ @NgModule({
declarations: declarations:
[ [
ScadaSymbolEditorComponent, ScadaSymbolEditorComponent,
ScadaSymbolComponent, ScadaSymbolComponent
ScadaSymbolMetadataComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
SharedModule, SharedModule,
HomeComponentsModule, HomeComponentsModule,
ScadaSymbolTooltipComponentsModule ScadaSymbolMetadataComponentsModule,
ScadaSymbolTooltipComponentsModule,
WidgetSettingsCommonModule
] ]
}) })
export class ScadaSymbolModule { } export class ScadaSymbolModule { }

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

@ -3417,7 +3417,13 @@
"properties": "Properties", "properties": "Properties",
"title": "Title", "title": "Title",
"state-render-function": "State render function", "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": { "item": {
"selected": "Selected" "selected": "Selected"

Loading…
Cancel
Save