Browse Source

UI: Improve scada symbol tags panel.

pull/11391/head
Igor Kulikov 2 years ago
parent
commit
d58600ffd4
  1. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.html
  2. 23
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.scss
  3. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/svg/iot-svg-object-settings.component.ts
  4. 4
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-components.module.ts
  5. 55
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag-function-panel.component.html
  6. 55
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag-function-panel.component.scss
  7. 121
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag-function-panel.component.ts
  8. 33
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.html
  9. 25
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.scss
  10. 134
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component.ts
  11. 32
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.html
  12. 32
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.scss
  13. 116
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component.ts
  14. 24
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.scss
  15. 36
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts
  16. 17
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  17. 1
      ui-ngx/src/styles.scss

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

@ -16,7 +16,7 @@
-->
<ng-container [formGroup]="iotSvgObjectSettingsFormGroup">
<div>{{ metadata?.title | customTranslate }}</div>
<div class="iot-svg-object-title">{{ metadata?.title | customTranslate }}</div>
<div *ngIf="metadata?.behavior?.length" class="tb-form-panel" formGroupName="behavior">
<div class="tb-form-panel-title" translate>scada.behavior.behavior</div>
<div *ngFor="let behaviour of metadata.behavior" class="tb-form-row">
@ -52,6 +52,7 @@
</div>
</div>
<div *ngIf="propertyRows?.length" class="tb-form-panel" formGroupName="properties">
<div class="tb-form-panel-title" translate>widget-config.appearance</div>
<div *ngFor="let propertyRow of propertyRows" class="tb-form-row space-between" [class]="propertyRow.rowClass">
<mat-slide-toggle *ngIf="propertyRow.switch" class="mat-slide fixed-title-width" formControlName="{{ propertyRow.switch.id }}">
{{ propertyRow.label | customTranslate }}

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

@ -0,0 +1,23 @@
/**
* 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.
*/
:host {
.iot-svg-object-title {
font-size: 18px;
font-style: normal;
font-weight: 500;
line-height: 24px;
}
}

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

@ -51,7 +51,7 @@ import { ImageService } from '@core/http/image.service';
@Component({
selector: 'tb-iot-svg-object-settings',
templateUrl: './iot-svg-object-settings.component.html',
styleUrls: ['./../../widget-settings.scss'],
styleUrls: ['./iot-svg-object-settings.component.scss', './../../widget-settings.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,

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

@ -45,6 +45,9 @@ import {
ScadaSymbolPropertyPanelComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-property-panel.component';
import { WidgetSettingsCommonModule } from '@home/components/widget/lib/settings/common/widget-settings-common.module';
import {
ScadaSymbolMetadataTagFunctionPanelComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag-function-panel.component';
@NgModule({
declarations:
@ -52,6 +55,7 @@ import { WidgetSettingsCommonModule } from '@home/components/widget/lib/settings
ScadaSymbolMetadataComponent,
ScadaSymbolMetadataTagComponent,
ScadaSymbolMetadataTagsComponent,
ScadaSymbolMetadataTagFunctionPanelComponent,
ScadaSymbolBehaviorsComponent,
ScadaSymbolBehaviorRowComponent,
ScadaSymbolBehaviorPanelComponent,

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

@ -0,0 +1,55 @@
<!--
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-scada-symbol-metadata-tag-function-panel" [formGroup]="tagFunctionFormGroup">
<div class="tb-scada-symbol-metadata-tag-function-title-container">
<mat-chip-set>
<mat-chip>
<span>
{{ tag }}
</span>
</mat-chip>
</mat-chip-set>
<div class="tb-scada-symbol-metadata-tag-function-title">{{ panelTitle | translate }}</div>
</div>
<div class="tb-scada-symbol-metadata-tag-function-panel-content">
<tb-js-func #tagFunctionComponent
minHeight="300px"
formControlName="tagFunction"
[objectHighlightRules]="objectHighlightRules"
[propertyHighlightRules]="propertyHighlightRules"
[editorCompleter]="completer"
[functionArgs]="tagFunctionArgs"
[helpId]="tagFunctionHelpId">
</tb-js-func>
</div>
<div class="tb-scada-symbol-metadata-tag-function-panel-buttons">
<button mat-button
color="primary"
type="button"
(click)="cancel()">
{{ 'action.cancel' | translate }}
</button>
<button mat-raised-button
color="primary"
type="button"
(click)="applyTagFunction()"
[disabled]="tagFunctionFormGroup.invalid || !tagFunctionFormGroup.dirty">
{{ 'action.apply' | translate }}
</button>
</div>
</div>

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

@ -0,0 +1,55 @@
/**
* 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 '../../../../../../scss/constants';
.tb-scada-symbol-metadata-tag-function-panel {
width: 800px;
display: flex;
flex-direction: column;
gap: 16px;
@media #{$mat-lt-md} {
width: 90vw;
}
.tb-scada-symbol-metadata-tag-function-panel-content {
display: flex;
flex-direction: column;
gap: 16px;
overflow: auto;
margin: -10px;
padding: 10px;
}
.tb-scada-symbol-metadata-tag-function-title-container {
display: flex;
flex-direction: row;
gap: 16px;
align-items: center;
}
.tb-scada-symbol-metadata-tag-function-title {
font-size: 16px;
font-weight: 500;
line-height: 24px;
letter-spacing: 0.25px;
color: rgba(0, 0, 0, 0.87);
}
.tb-scada-symbol-metadata-tag-function-panel-buttons {
height: 40px;
display: flex;
flex-direction: row;
gap: 16px;
justify-content: flex-end;
align-items: flex-end;
}
}

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

@ -0,0 +1,121 @@
///
/// 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 {
AfterViewInit,
Component,
EventEmitter,
Input,
OnInit,
Output,
ViewChild,
ViewEncapsulation
} from '@angular/core';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { WidgetService } from '@core/http/widget.service';
import { TranslateService } from '@ngx-translate/core';
import { TbEditorCompleter } from '@shared/models/ace/completion.models';
import { TbHighlightRule } from '@shared/models/ace/ace.models';
import {
scadaSymbolClickActionHighlightRules,
scadaSymbolClickActionPropertiesHighlightRules,
scadaSymbolElementStateRenderHighlightRules,
scadaSymbolElementStateRenderPropertiesHighlightRules
} from '@home/pages/scada-symbol/scada-symbol.models';
import { JsFuncComponent } from '@shared/components/js-func.component';
@Component({
selector: 'tb-scada-symbol-metadata-tag-function-panel',
templateUrl: './scada-symbol-metadata-tag-function-panel.component.html',
styleUrls: ['./scada-symbol-metadata-tag-function-panel.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class ScadaSymbolMetadataTagFunctionPanelComponent implements OnInit, AfterViewInit {
@ViewChild('tagFunctionComponent')
tagFunctionComponent: JsFuncComponent;
@Input()
tagFunction: string;
@Input()
tagFunctionType: 'renderFunction' | 'clickAction';
@Input()
tag: string;
@Input()
completer: TbEditorCompleter;
@Input()
popover: TbPopoverComponent<ScadaSymbolMetadataTagFunctionPanelComponent>;
@Output()
tagFunctionApplied = new EventEmitter<string>();
functionScopeVariables = this.widgetService.getWidgetScopeVariables();
tagFunctionFormGroup: UntypedFormGroup;
panelTitle: string;
tagFunctionArgs: string[];
tagFunctionHelpId: string;
objectHighlightRules: TbHighlightRule[];
propertyHighlightRules: TbHighlightRule[];
constructor(private fb: UntypedFormBuilder,
private widgetService: WidgetService) {
}
ngOnInit(): void {
this.tagFunctionFormGroup = this.fb.group(
{
tagFunction: [this.tagFunction, []]
}
);
if (this.tagFunctionType === 'renderFunction') {
this.panelTitle = 'scada.state-render-function';
this.tagFunctionArgs = ['ctx', 'element'];
this.objectHighlightRules = scadaSymbolElementStateRenderHighlightRules;
this.propertyHighlightRules = scadaSymbolElementStateRenderPropertiesHighlightRules;
this.tagFunctionHelpId = 'widget/lib/timeseries/cell_style_fn'; // TODO:
} else if (this.tagFunctionType === 'clickAction') {
this.panelTitle = 'scada.tag.on-click-action';
this.tagFunctionArgs = ['ctx', 'element', 'event'];
this.objectHighlightRules = scadaSymbolClickActionHighlightRules;
this.propertyHighlightRules = scadaSymbolClickActionPropertiesHighlightRules;
this.tagFunctionHelpId = 'widget/lib/timeseries/cell_style_fn'; // TODO:
}
}
ngAfterViewInit() {
this.tagFunctionComponent.focus();
}
cancel() {
this.popover?.hide();
}
applyTagFunction() {
const tagFunction: string = this.tagFunctionFormGroup.get('tagFunction').value;
this.tagFunctionApplied.emit(tagFunction);
}
}

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

@ -15,8 +15,35 @@
limitations under the License.
-->
<div [formGroup]="tagFormGroup" class="tb-form-panel stroked">
<mat-expansion-panel #expansionPanel class="tb-settings">
<div [formGroup]="tagFormGroup" class="tb-form-table-row tb-scada-symbol-metadata-tag-row">
<div class="tb-tag-field">
<mat-chip-set>
<mat-chip>
<span class="tb-scada-symbol-metadata-tag-label">
{{ tagFormGroup.get('tag').value }}
</span>
</mat-chip>
</mat-chip-set>
</div>
<div class="tb-state-render-function-field">
<button #editStateRenderFunctionButton
mat-button
color="primary"
(click)="editTagStateRenderFunction()">
<mat-icon>{{ tagFormGroup.get('stateRenderFunction').value ? 'edit' : 'add' }}</mat-icon>
<span>{{ (tagFormGroup.get('stateRenderFunction').value ? 'action.edit' : 'action.add') | translate }}</span>
</button>
</div>
<div class="tb-on-click-action-field">
<button #editClickActionButton
mat-button
color="primary"
(click)="editClickAction()">
<mat-icon>{{ tagFormGroup.get('clickAction').value ? 'edit' : 'add' }}</mat-icon>
<span>{{ (tagFormGroup.get('clickAction').value ? 'action.edit' : 'action.add') | translate }}</span>
</button>
</div>
<!--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">
@ -78,5 +105,5 @@
</mat-expansion-panel>
</div>
</ng-template>
</mat-expansion-panel>
</mat-expansion-panel-->
</div>

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

@ -13,10 +13,23 @@
* 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;
.tb-scada-symbol-metadata-tag-row {
.tb-tag-field {
flex: 1 1 70%;
}
.tb-state-render-function-field {
flex: 1 1 15%;
}
.tb-on-click-action-field {
flex: 1 1 15%;
}
.tb-state-render-function-field, .tb-on-click-action-field {
display: flex;
justify-content: flex-end;
.mdc-button {
.mat-mdc-button-touch-target {
height: 36px;
}
}
}
}

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

@ -21,12 +21,13 @@ import {
Input,
OnChanges,
OnInit,
Output,
Output, Renderer2,
SimpleChanges,
ViewChild,
ViewChild, ViewContainerRef,
ViewEncapsulation
} from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
@ -47,6 +48,15 @@ import {
scadaSymbolElementStateRenderHighlightRules,
scadaSymbolElementStateRenderPropertiesHighlightRules
} from '@home/pages/scada-symbol/scada-symbol.models';
import { MatButton } from '@angular/material/button';
import { TbPopoverService } from '@shared/components/popover.service';
import { deepClone } from '@core/utils';
import {
ScadaSymbolBehaviorPanelComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-behavior-panel.component';
import {
ScadaSymbolMetadataTagFunctionPanelComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag-function-panel.component';
@Component({
selector: 'tb-scada-symbol-metadata-tag',
@ -66,83 +76,41 @@ import {
],
encapsulation: ViewEncapsulation.None
})
export class ScadaSymbolMetadataTagComponent implements ControlValueAccessor, OnInit, OnChanges, Validator {
@ViewChild('tagSelect')
tagSelect: MatSelect;
@ViewChild('expansionPanel')
expansionPanel: MatExpansionPanel;
export class ScadaSymbolMetadataTagComponent implements ControlValueAccessor, OnInit, Validator {
@ViewChild('renderFunctionExpansionPanel')
renderFunctionExpansionPanel: MatExpansionPanel;
@ViewChild('editStateRenderFunctionButton')
editStateRenderFunctionButton: MatButton;
@ViewChild('clickActionExpansionPanel')
clickActionExpansionPanel: MatExpansionPanel;
@ViewChild('stateRenderFunction')
stateRenderFunction: JsFuncComponent;
@ViewChild('clickAction')
clickAction: JsFuncComponent;
@ViewChild('editClickActionButton')
editClickActionButton: MatButton;
@Input()
disabled: boolean;
@Input()
tags: string[];
@Input()
elementStateRenderFunctionCompleter: TbEditorCompleter;
@Input()
clickActionFunctionCompleter: TbEditorCompleter;
@Output()
tagRemoved = new EventEmitter();
availableTags: string[];
tagFormGroup: UntypedFormGroup;
modelValue: IotSvgTag;
scadaSymbolElementStateRenderHighlightRules = scadaSymbolElementStateRenderHighlightRules;
scadaSymbolElementStateRenderPropertiesHighlightRules = scadaSymbolElementStateRenderPropertiesHighlightRules;
scadaSymbolClickActionHighlightRules = scadaSymbolClickActionHighlightRules;
scadaSymbolClickActionPropertiesHighlightRules = scadaSymbolClickActionPropertiesHighlightRules;
private propagateChange = (_val: any) => {};
constructor(private fb: UntypedFormBuilder) {
constructor(private fb: UntypedFormBuilder,
private popoverService: TbPopoverService,
private renderer: Renderer2,
private viewContainerRef: ViewContainerRef) {
}
ngOnInit() {
this.availableTags = (this.tags || []).concat();
this.tagFormGroup = this.fb.group({
tag: [null, [Validators.required]],
tag: [null, []],
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 {
@ -163,9 +131,6 @@ export class ScadaSymbolMetadataTagComponent implements ControlValueAccessor, On
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(
{
@ -186,27 +151,61 @@ export class ScadaSymbolMetadataTagComponent implements ControlValueAccessor, On
}
editTagStateRenderFunction(): void {
this.openPanelWithCallback(this.expansionPanel, () => {
this.openTagFunction('renderFunction', this.editStateRenderFunctionButton);
/*this.openPanelWithCallback(this.expansionPanel, () => {
this.openPanelWithCallback(this.renderFunctionExpansionPanel, () => {
this.stateRenderFunction.focus();
});
});
});*/
}
editClickAction(): void {
this.openPanelWithCallback(this.expansionPanel, () => {
this.openTagFunction('clickAction', this.editClickActionButton);
/*this.openPanelWithCallback(this.expansionPanel, () => {
this.openPanelWithCallback(this.clickActionExpansionPanel, () => {
this.clickAction.focus();
});
});
});*/
}
focus() {
this.tagSelect._elementRef.nativeElement.scrollIntoView();
this.tagSelect.focus();
private openTagFunction(tagFunctionType: 'renderFunction' | 'clickAction',
button: MatButton) {
const trigger = button._elementRef.nativeElement;
trigger.scrollIntoView();
if (this.popoverService.hasPopover(trigger)) {
this.popoverService.hidePopover(trigger);
} else {
let tagFunctionControl: AbstractControl;
let completer: TbEditorCompleter;
if (tagFunctionType === 'renderFunction') {
tagFunctionControl = this.tagFormGroup.get('stateRenderFunction');
completer = this.elementStateRenderFunctionCompleter;
} else if (tagFunctionType === 'clickAction') {
tagFunctionControl = this.tagFormGroup.get('clickAction');
completer = this.clickActionFunctionCompleter;
}
const ctx: any = {
tagFunction: tagFunctionControl.value,
tagFunctionType,
tag: this.tagFormGroup.get('tag').value,
completer
};
const scadaSymbolTagFunctionPanelPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, ScadaSymbolMetadataTagFunctionPanelComponent,
['leftOnly', 'leftTopOnly', 'leftBottomOnly'], true, null,
ctx,
{},
{}, {}, true);
scadaSymbolTagFunctionPanelPopover.tbComponentRef.instance.popover = scadaSymbolTagFunctionPanelPopover;
scadaSymbolTagFunctionPanelPopover.tbComponentRef.instance.tagFunctionApplied.subscribe((tagFunction) => {
scadaSymbolTagFunctionPanelPopover.hide();
tagFunctionControl.patchValue(tagFunction, {emitEvent: false});
this.updateModel();
});
}
}
private openPanelWithCallback(panel: MatExpansionPanel, callback: () => void) {
/* private openPanelWithCallback(panel: MatExpansionPanel, callback: () => void) {
if (!panel.expanded) {
const s = panel.afterExpand.subscribe(() => {
s.unsubscribe();
@ -218,13 +217,10 @@ export class ScadaSymbolMetadataTagComponent implements ControlValueAccessor, On
} else {
callback();
}
}
}*/
private updateModel() {
const value = this.tagFormGroup.value;
if (value.tag && !this.modelValue?.tag) {
this.expansionPanel.open();
}
this.modelValue = {
tag: value.tag,
stateRenderFunction: value.stateRenderFunction

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

@ -16,23 +16,25 @@
-->
<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
[elementStateRenderFunctionCompleter]="elementStateRenderFunctionCompleter"
[clickActionFunctionCompleter]="clickActionFunctionCompleter"
[formControl]="tagControl"
[tags]="availableTags"
(tagRemoved)="removeTag($index)">
</tb-scada-symbol-metadata-tag>
<div class="tb-form-table">
<div class="tb-form-table-header">
<div class="tb-form-table-header-cell tb-tag-header" translate>scada.tag.tag</div>
<div class="tb-form-table-header-cell tb-state-render-function-header" translate>scada.state-render-function</div>
<div class="tb-form-table-header-cell tb-on-click-action-header" translate>scada.tag.on-click-action</div>
</div>
<div *ngIf="tagsFormArray().controls.length; else noTags" class="tb-form-table-body">
<div *ngFor="let tagControl of tagsFormArray().controls; trackBy: trackByTag; let $index = index; let $last = last">
<tb-scada-symbol-metadata-tag fxFlex
[elementStateRenderFunctionCompleter]="elementStateRenderFunctionCompleter"
[clickActionFunctionCompleter]="clickActionFunctionCompleter"
[formControl]="tagControl">
</tb-scada-symbol-metadata-tag>
<mat-divider *ngIf="!$last"></mat-divider>
</div>
</div>
</div>
<div>
<button *ngIf="availableTags?.length" 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>
<span fxLayoutAlign="start center"
class="tb-prompt">{{ 'scada.tag.no-tags' | translate }}</span>
</ng-template>

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

@ -16,13 +16,31 @@
.tb-scada-symbol-metadata-tags {
flex: 1;
margin: 12px;
.tb-scada-symbol-metadata-tags-list {
overflow: auto;
display: flex;
flex-direction: column;
gap: 8px;
.tb-form-table-header-cell {
&.tb-tag-header {
flex: 1 1 70%;
}
&.tb-state-render-function-header {
flex: 1 1 15%;
}
&.tb-on-click-action-header {
flex: 1 1 15%;
}
&.tb-state-render-function-header, &.tb-on-click-action-header {
display: flex;
justify-content: flex-end;
}
}
.tb-form-table {
overflow: hidden;
}
.tb-no-tags {
height: 60px;
.tb-form-table-body {
overflow: auto;
tb-scada-symbol-metadata-tag {
overflow: hidden;
}
.mat-divider {
margin-top: 8px;
}
}
}

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

@ -16,10 +16,12 @@
import {
Component,
forwardRef, HostBinding,
forwardRef,
HostBinding,
Input,
OnChanges,
OnInit, QueryList,
OnInit,
QueryList,
SimpleChanges,
ViewChildren,
ViewEncapsulation
@ -33,7 +35,6 @@ import {
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
ValidationErrors,
Validator
} from '@angular/forms';
import { IotSvgTag } from '@home/components/widget/lib/svg/iot-svg.models';
@ -42,15 +43,8 @@ import {
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component';
import { TbEditorCompleter } from '@shared/models/ace/completion.models';
const tagValidator = (control: AbstractControl): ValidationErrors | null => {
const tag: IotSvgTag = control.value;
if (!tag.tag) {
return {
tag: true
};
}
return null;
};
const tagIsEmpty = (tag: IotSvgTag): boolean =>
!tag.stateRenderFunction && !tag.actions?.click?.actionFunction;
@Component({
selector: 'tb-scada-symbol-metadata-tags',
@ -90,10 +84,10 @@ export class ScadaSymbolMetadataTagsComponent implements ControlValueAccessor, O
@Input()
clickActionFunctionCompleter: TbEditorCompleter;
availableTags: string[];
tagsFormGroup: UntypedFormGroup;
private modelValue: IotSvgTag[];
private propagateChange = (_val: any) => {};
constructor(private fb: UntypedFormBuilder) {
@ -103,15 +97,17 @@ export class ScadaSymbolMetadataTagsComponent implements ControlValueAccessor, O
this.tagsFormGroup = this.fb.group({
tags: this.fb.array([])
});
this.updateAvailableTags();
const tagsResult = this.setupTags();
this.tagsFormGroup.setControl('tags', this.prepareTagsFormArray(tagsResult.tags), {emitEvent: false});
this.tagsFormGroup.valueChanges.subscribe(
() => {
let tags: IotSvgTag[] = this.tagsFormGroup.get('tags').value;
if (tags) {
tags = tags.filter(t => !!t.tag);
let value: IotSvgTag[] = this.tagsFormGroup.get('tags').value;
if (value) {
value = value.filter(t => !tagIsEmpty(t));
}
this.updateAvailableTags();
this.propagateChange(tags);
this.modelValue = value;
this.propagateChange(this.modelValue);
}
);
}
@ -121,7 +117,15 @@ export class ScadaSymbolMetadataTagsComponent implements ControlValueAccessor, O
const change = changes[propName];
if (!change.firstChange && change.currentValue !== change.previousValue) {
if (['tags'].includes(propName)) {
this.updateAvailableTags();
const tagsResult = this.setupTags(this.modelValue);
const tagsControls = this.prepareTagsFormArray(tagsResult.tags);
if (tagsResult.emitEvent) {
setTimeout(() => {
this.tagsFormGroup.setControl('tags', tagsControls, {emitEvent: true});
});
} else {
this.tagsFormGroup.setControl('tags', tagsControls, {emitEvent: false});
}
}
}
}
@ -144,8 +148,9 @@ export class ScadaSymbolMetadataTagsComponent implements ControlValueAccessor, O
}
writeValue(value: IotSvgTag[] | undefined): void {
const tags= value || [];
this.tagsFormGroup.setControl('tags', this.prepareTagsFormArray(tags), {emitEvent: false});
this.modelValue = value || [];
const tagsResult= this.setupTags(this.modelValue);
this.tagsFormGroup.setControl('tags', this.prepareTagsFormArray(tagsResult.tags), {emitEvent: false});
}
public validate(_c: UntypedFormControl) {
@ -165,20 +170,7 @@ export class ScadaSymbolMetadataTagsComponent implements ControlValueAccessor, O
return tagControl;
}
removeTag(index: number, emitEvent = true) {
(this.tagsFormGroup.get('tags') as UntypedFormArray).removeAt(index, {emitEvent});
}
addTag() {
this.addNewTag(null);
setTimeout(() => {
const tagComponent = this.metadataTags.get(this.metadataTags.length-1);
tagComponent.focus();
});
}
editTagStateRenderFunction(tag: string): void {
this.addTagIfNotPresent(tag);
setTimeout(() => {
const tags: IotSvgTag[] = this.tagsFormGroup.get('tags').value;
const index = tags.findIndex(t => t.tag === tag);
@ -188,7 +180,6 @@ export class ScadaSymbolMetadataTagsComponent implements ControlValueAccessor, O
}
editTagClickAction(tag: string): void {
this.addTagIfNotPresent(tag);
setTimeout(() => {
const tags: IotSvgTag[] = this.tagsFormGroup.get('tags').value;
const index = tags.findIndex(t => t.tag === tag);
@ -197,44 +188,39 @@ export class ScadaSymbolMetadataTagsComponent implements ControlValueAccessor, O
});
}
private addTagIfNotPresent(tag: string) {
const tags: IotSvgTag[] = this.tagsFormGroup.get('tags').value;
if (!tags || !tags.find(t => t.tag === tag)) {
this.addNewTag(tag);
private setupTags(existing?: IotSvgTag[]): {tags: IotSvgTag[]; emitEvent: boolean} {
existing = (existing || []).filter(t => !tagIsEmpty(t));
const result = (this.tags || []).sort().map(tag => ({
tag,
stateRenderFunction: null,
actions: {
click: {
actionFunction: null
}
}
}));
for (const tag of existing) {
const found = result.find(t => t.tag === tag.tag);
if (found) {
found.stateRenderFunction = tag.stateRenderFunction;
found.actions.click.actionFunction = tag.actions?.click?.actionFunction;
}
}
}
private addNewTag(tagLabel: string) {
const tag: IotSvgTag = {
tag: tagLabel
const tagRemoved = !!existing.find(existingTag =>
!result.find(t => t.tag === existingTag.tag));
return {
tags: result,
emitEvent: tagRemoved
};
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]));
tagsControls.push(this.fb.control(tag, []));
});
}
return this.fb.array(tagsControls);
}
private updateAvailableTags() {
if (this.tags) {
for (const c of this.tagsFormArray().controls) {
const svgTag: IotSvgTag = c.value;
if (svgTag.tag && !this.tags.includes(svgTag.tag)) {
const index = this.tagsFormArray().controls.indexOf(c);
this.removeTag(index, false);
}
}
}
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));
}
}

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

@ -79,35 +79,33 @@
.ace_tb {
&.ace_scada-symbol {
&-ctx {
color: orangered;
font-weight: bold;
color: #C52F00;
&-properties, &-property {
color: #0c959c;
color: #185F2A;
}
&-values, &-value {
color: #669900;
color: #2CAA00;
}
&-tags, &-tag {
color: #cc8500;
color: #DF8600;
}
&-api, &-api-method {
color: #ff00fe;
color: #7214D0;
}
&-properties, &-values, &-tags, &-api {
font-style: italic;
// font-style: italic;
// font-weight: bold;
}
&-property, &-value, &-tag, &-api-method {
font-weight: bold;
// font-weight: bold;
font-weight: 700;
}
}
&-svg, &-element, &-event {
color: orangered;
font-weight: bold;
color: #C52F00;
// font-weight: bold;
&-properties {
color: #0c959c;
font-weight: bold;
color: #7214D0;
font-weight: 700;
}
}
}

36
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol-tooltip.components.ts

@ -37,6 +37,7 @@ import { ENTER } from '@angular/cdk/keycodes';
import Timeout = NodeJS.Timeout;
import { MatButton } from '@angular/material/button';
import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance;
import { TranslateService } from '@ngx-translate/core';
@Directive()
abstract class ScadaSymbolPanelComponent implements AfterViewInit {
@ -60,7 +61,7 @@ abstract class ScadaSymbolPanelComponent implements AfterViewInit {
<span>{{ symbolElement?.element?.type }}:</span>
<button mat-stroked-button color="primary" (click)="onAddTag()">
<mat-icon>add</mat-icon>
<span>Add tag</span>
<span translate>scada.tag.add-tag</span>
</button>
</div>`,
styleUrls: ['./scada-symbol-tooltip.component.scss'],
@ -83,20 +84,20 @@ class ScadaSymbolAddTagPanelComponent extends ScadaSymbolPanelComponent {
@Component({
template: `<div class="tb-scada-symbol-tooltip-panel">
<span>{{ isAdd ? 'Enter tag:' : 'Update tag:' }}</span>
<span>{{ (isAdd ? 'scada.tag.enter-tag' : 'scada.tag.update-tag' ) | translate }}:</span>
<mat-form-field class="tb-inline-field" appearance="outline" subscriptSizing="dynamic">
<input #tagField matInput [(ngModel)]="tag" (keydown)="tagEnter($event)" (blur)="onBlur()"
placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<button type="button" mat-icon-button class="tb-mat-20"
matTooltip="Apply"
matTooltip="{{ 'action.apply' | translate }}"
matTooltipPosition="above"
[disabled]="!tag"
(click)="onApply()">
<mat-icon>done</mat-icon>
</button>
<button type="button" mat-icon-button class="tb-mat-20"
matTooltip="Cancel"
matTooltip="{{ 'action.cancel' | translate }}"
matTooltipPosition="above"
(click)="onCancel()">
<mat-icon>close</mat-icon>
@ -184,20 +185,20 @@ class ScadaSymbolTagInputPanelComponent extends ScadaSymbolPanelComponent implem
<span>{{ symbolElement?.element?.type }}:</span>
<span><b>{{ symbolElement?.tag }}</b></span>
<button type="button" mat-icon-button class="tb-mat-20"
matTooltip="Update tag"
matTooltip="{{ 'scada.tag.update-tag' | translate }}"
matTooltipPosition="above"
(click)="onUpdateTag()">
<mat-icon>edit</mat-icon>
</button>
<button #tagSettingsButton type="button"
mat-icon-button class="tb-mat-20"
matTooltip="Settings"
matTooltip="{{ 'scada.tag.tag-settings' | translate }}"
matTooltipPosition="above">
<mat-icon>settings</mat-icon>
</button>
<button #removeTagButton type="button"
mat-icon-button class="tb-mat-20"
matTooltip="Remove tag"
matTooltip="{{ 'scada.tag.remove-tag' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
@ -288,12 +289,12 @@ class ScadaSymbolTagPanelComponent extends ScadaSymbolPanelComponent implements
<div class="tb-scada-symbol-tooltip-panel">
<button mat-stroked-button color="primary" (click)="onCancel()">
<mat-icon>close</mat-icon>
<span>No</span>
<span translate>action.no</span>
</button>
<button #yesButton
mat-stroked-button color="primary" (click)="onRemoveTag()">
<mat-icon>done</mat-icon>
<span>Yes</span>
<span translate>action.yes</span>
</button>
</div>
</div>`,
@ -313,13 +314,14 @@ class ScadaSymbolRemoveTagConfirmComponent extends ScadaSymbolPanelComponent imp
@Output()
removeTag = new EventEmitter();
constructor(public element: ElementRef<HTMLElement>) {
constructor(public element: ElementRef<HTMLElement>,
private translate: TranslateService) {
super(element);
}
ngOnInit() {
this.deleteText = `Are you sure you want to delete tag<br/><b>${this.symbolElement?.tag}</b>
from <b>${this.symbolElement?.element?.type}</b> element?`;
this.deleteText = this.translate.instant('scada.tag.delete-tag-text',
{tag: this.symbolElement?.tag, elementType: this.symbolElement?.element?.type});
}
public onCancel() {
@ -339,29 +341,29 @@ class ScadaSymbolRemoveTagConfirmComponent extends ScadaSymbolPanelComponent imp
color="primary"
(click)="editStateRenderFunction()">
<mat-icon>edit</mat-icon>
<span>Edit</span>
<span translate>action.edit</span>
</button>
<button *ngIf="!hasStateRenderFunction"
mat-stroked-button
color="primary"
(click)="editStateRenderFunction()">
<mat-icon>add</mat-icon>
<span>Add</span>
<span translate>action.add</span>
</button>
<div translate>scada.on-click-action</div>
<div translate>scada.tag.on-click-action</div>
<button *ngIf="hasClickAction"
mat-stroked-button
color="primary"
(click)="editClickAction()">
<mat-icon>edit</mat-icon>
<span>Edit</span>
<span translate>action.edit</span>
</button>
<button *ngIf="!hasClickAction"
mat-stroked-button
color="primary"
(click)="editClickAction()">
<mat-icon>add</mat-icon>
<span>Add</span>
<span translate>action.add</span>
</button>
</div>`,
styleUrls: ['./scada-symbol-tooltip.component.scss'],

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

@ -3414,13 +3414,18 @@
"title": "Title",
"state-render-function": "State render function",
"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",
"preview-widget-action-text": "Widget action '{{type}}' successfully invoked!",
"tag": {
"tag": "Tag",
"on-click-action": "On click action",
"no-tags": "No tags configured",
"delete-tag-text": "Are you sure you want to delete tag<br/><b>{{tag}}</b> from <b>{{elementType}}</b> element?",
"update-tag": "Update tag",
"enter-tag": "Enter tag",
"tag-settings": "Tag settings",
"remove-tag": "Remove tag",
"add-tag": "Add tag"
},
"behavior": {
"behavior": "Behavior",
"id": "Id",

1
ui-ngx/src/styles.scss

@ -392,6 +392,7 @@ mat-icon {
}
.tb-ace-doc-tooltip {
white-space: pre-wrap;
code {
color: #444;
&.title {

Loading…
Cancel
Save