UI: SCADA symbol - implement create widget from SCADA symbol, add widget size parameters, make target device optional for SCADA symbol widgets.

This commit is contained in:
Igor Kulikov 2024-06-10 17:49:15 +03:00
parent 4fd84315a5
commit cfc4d3d65f
21 changed files with 255 additions and 37 deletions

View File

@ -6,6 +6,8 @@
"level",
"fan"
],
"widgetSizeX": 3,
"widgetSizeY": 3,
"stateRenderFunction": "var showMinMaxLevel = ctx.properties.showMinMaxLevel;\nvar minLevelElement = ctx.tags.minLevel[0];\nvar maxLevelElement = ctx.tags.maxLevel[0];\nvar minLevel = ctx.properties.minLevel; \nvar maxLevel = ctx.properties.maxLevel;\n\nif (showMinMaxLevel) {\n \n var minMaxLevelFont = ctx.properties.minMaxLevelFont;\n var minMaxLevelColor = ctx.properties.minMaxLevelColor;\n \n ctx.api.text(minLevelElement, minLevel);\n ctx.api.text(maxLevelElement, maxLevel);\n \n ctx.api.font(minLevelElement, minMaxLevelFont, minMaxLevelColor);\n ctx.api.font(maxLevelElement, minMaxLevelFont, minMaxLevelColor);\n \n} else {\n minLevelElement.hide();\n maxLevelElement.hide();\n}\n\nvar disabled = ctx.values.disabled;\nvar on = ctx.values.on;\nvar level = ctx.values.level;\n\nvar onButton = ctx.tags.onButton;\nvar offButton = ctx.tags.offButton;\nvar levelUpButton = ctx.tags.levelUpButton;\nvar levelDownButton = ctx.tags.levelDownButton;\n\nvar onButtonEnabled = !disabled && !on;\nvar offButtonEnabled = !disabled && on;\nvar levelUpEnabled = !disabled && level < maxLevel;\nvar levelDownEnabled = !disabled && level > minLevel;\n\nif (onButtonEnabled) {\n ctx.api.enable(onButton);\n onButton[0].findOne('rect').attr({fill: '#12ed19'});\n} else {\n ctx.api.disable(onButton);\n onButton[0].findOne('rect').attr({fill: '#777'});\n}\n \nif (offButtonEnabled) {\n ctx.api.enable(offButton);\n offButton[0].findOne('rect').attr({fill: '#ed121f'});\n} else {\n ctx.api.disable(offButton);\n offButton[0].findOne('rect').attr({fill: '#777'});\n} \n\n\nif (levelUpEnabled) {\n ctx.api.enable(levelUpButton);\n levelUpButton[0].findOne('rect').attr({fill: '#fff'});\n} else {\n ctx.api.disable(levelUpButton);\n levelUpButton[0].findOne('rect').attr({fill: '#777'});\n}\n \nif (levelDownEnabled) {\n ctx.api.enable(levelDownButton);\n levelDownButton[0].findOne('rect').attr({fill: '#fff'});\n} else {\n ctx.api.disable(levelDownButton);\n levelDownButton[0].findOne('rect').attr({fill: '#777'});\n}",
"tags": [
{

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -6,12 +6,12 @@
"description": "",
"descriptor": {
"type": "rpc",
"sizeX": 3.5,
"sizeY": 3.5,
"sizeX": 3,
"sizeY": 3,
"resources": [],
"templateHtml": "<tb-scada-symbol-widget\n [ctx]='ctx'\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-scada-symbol-widget>",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.actionWidget.onInit();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '280px',\n previewHeight: '280px',\n embedTitlePanel: true,\n displayRpcMessageToast: false\n };\n};\n\nself.onDestroy = function() {\n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.actionWidget.onInit();\n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '300px',\n previewHeight: '320px',\n embedTitlePanel: true,\n targetDeviceOptional: true,\n displayRpcMessageToast: false\n };\n};\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "",
"dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-scada-symbol-widget-settings",

View File

@ -382,6 +382,12 @@ public class InstallScripts {
}
settings.put("scadaSymbolUrl", symbolUrl);
((ObjectNode)descriptor).put("defaultConfig", JacksonUtil.toString(defaultConfig));
((ObjectNode)descriptor).put("sizeX", metadata.getWidgetSizeX());
((ObjectNode)descriptor).put("sizeY", metadata.getWidgetSizeY());
String controllerScript = descriptor.get("controllerScript").asText();
controllerScript = controllerScript.replaceAll("previewWidth: '\\d*px'", "previewWidth: '" + (metadata.getWidgetSizeX() * 100) + "px'");
controllerScript = controllerScript.replaceAll("previewHeight: '\\d*px'", "previewHeight: '" + (metadata.getWidgetSizeY() * 100 + 20) + "px'");
((ObjectNode)descriptor).put("controllerScript", controllerScript);
var savedWidget = widgetTypeService.saveWidgetType(scadaSymbolWidget);
return savedWidget.getFqn();
}

View File

@ -283,6 +283,8 @@ public class ImageUtils {
private String title;
private String description;
private String[] searchTags;
private int widgetSizeX;
private int widgetSizeY;
public ScadaSymbolMetadataInfo(String fileName, JsonNode metaData) {
if (metaData != null && metaData.has("title")) {
@ -304,6 +306,16 @@ public class ImageUtils {
} else {
searchTags = new String[0];
}
if (metaData != null && metaData.has("widgetSizeX")) {
widgetSizeX = metaData.get("widgetSizeX").asInt();
} else {
widgetSizeX = 3;
}
if (metaData != null && metaData.has("widgetSizeY")) {
widgetSizeY = metaData.get("widgetSizeY").asInt();
} else {
widgetSizeY = 3;
}
}
}

View File

@ -23,7 +23,9 @@
</mat-spinner>
</div>
<div id="gridster-parent"
fxFlex class="tb-dashboard-content layout-wrap" [class]="{'autofill-height': isAutofillHeight()}"
fxFlex class="tb-dashboard-content" [class.autofill-height]="isAutofillHeight()"
[class.center-vertical]="centerVertical"
[class.center-horizontal]="centerHorizontal"
(contextmenu)="openDashboardContextMenu($event)">
<div #dashboardMenuTrigger="matMenuTrigger" style="visibility: hidden; position: fixed"
[style.left]="dashboardMenuPosition.x"
@ -61,7 +63,7 @@
</div>
</ng-template>
</mat-menu>
<div [class]="dashboardClass" id="gridster-background" style="height: 100%;">
<div [class]="dashboardClass" id="gridster-background">
<gridster #gridster id="gridster-child" [options]="gridsterOpts">
<gridster-item #gridsterItem [item]="widget" [class]="{'tb-noselect': isEdit}" *ngFor="let widget of dashboardWidgets">
<tb-widget-container

View File

@ -37,6 +37,10 @@
outline: none;
overflow-y: auto;
#gridster-background {
height: 100%;
}
gridster-item {
transition: none;
overflow: visible;
@ -49,6 +53,30 @@
overflow-y: hidden;
}
}
&.center-vertical, &.center-horizontal {
display: flex;
align-items: center;
justify-content: center;
#gridster-child {
width: 100%;
height: 100%;
}
}
&.center-vertical {
#gridster-background {
height: auto;
max-height: 100%;
width: 100%;
}
}
&.center-horizontal {
#gridster-background {
width: auto;
max-width: 100%;
height: 100%;
}
}
}
#gridster-child {

View File

@ -59,6 +59,8 @@ import { ResizeObserver } from '@juggle/resize-observer';
import { UtilsService } from '@core/services/utils.service';
import { WidgetComponentAction, WidgetComponentActionType } from '@home/components/widget/widget-container.component';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { displayGrids } from 'angular-gridster2/lib/gridsterConfig.interface';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({
selector: 'tb-dashboard',
@ -88,12 +90,30 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
@Input()
columns: number;
@Input()
@coerceBoolean()
setGridSize = false;
@Input()
margin: number;
@Input()
outerMargin: boolean;
@Input()
displayGrid: displayGrids = 'onDrag&Resize';
@Input()
gridType: GridType;
@Input()
@coerceBoolean()
centerVertical = false;
@Input()
@coerceBoolean()
centerHorizontal = false;
@Input()
isEdit: boolean;
@ -208,7 +228,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
this.dashboardTimewindow = this.timeService.defaultTimewindow();
}
this.gridsterOpts = {
gridType: GridType.ScrollVertical,
gridType: this.gridType || GridType.ScrollVertical,
keepFixedHeightInMobile: true,
disableWarnings: false,
disableAutoPositionOnConflict: false,
@ -216,6 +236,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
swap: false,
maxRows: 3000,
minCols: this.columns ? this.columns : 24,
setGridSize: this.setGridSize,
maxCols: 3000,
maxItemCols: 1000,
maxItemRows: 1000,
@ -226,6 +247,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
minItemRows: 1,
defaultItemCols: 8,
defaultItemRows: 6,
displayGrid: this.displayGrid,
resizable: {enabled: this.isEdit},
draggable: {enabled: this.isEdit},
itemChangeCallback: item => this.dashboardWidgets.sortWidgets(),
@ -530,7 +552,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
if (autofillHeight) {
this.gridsterOpts.gridType = this.isMobileSize ? GridType.Fixed : GridType.Fit;
} else {
this.gridsterOpts.gridType = this.isMobileSize ? GridType.Fixed : GridType.ScrollVertical;
this.gridsterOpts.gridType = this.isMobileSize ? GridType.Fixed : this.gridType || GridType.ScrollVertical;
}
const mobileBreakPoint = this.isMobileSize ? 20000 : 0;
this.gridsterOpts.mobileBreakpoint = mobileBreakPoint;

View File

@ -24,13 +24,13 @@
</tb-toggle-select>
</div>
<tb-entity-autocomplete *ngIf="targetDeviceFormGroup.get('type').value === targetDeviceType.device"
[required]="!widgetEditMode"
[required]="(!widgetEditMode && !targetDeviceOptional)"
[entityType]="entityType.DEVICE"
formControlName="deviceId">
</tb-entity-autocomplete>
<tb-entity-alias-select
*ngIf="targetDeviceFormGroup.get('type').value === targetDeviceType.entity"
[tbRequired]="!widgetEditMode"
[tbRequired]="(!widgetEditMode && !targetDeviceOptional)"
[aliasController]="aliasController"
[callbacks]="entityAliasSelectCallbacks"
formControlName="entityAliasId">

View File

@ -60,6 +60,10 @@ export class TargetDeviceComponent implements ControlValueAccessor, OnInit, Vali
return this.widgetConfigComponent.widgetConfigCallbacks;
}
public get targetDeviceOptional(): boolean {
return this.widgetConfigComponent.modelValue?.typeParameters?.targetDeviceOptional;
}
targetDeviceType = TargetDeviceType;
entityType = EntityType;
@ -103,9 +107,9 @@ export class TargetDeviceComponent implements ControlValueAccessor, OnInit, Vali
ngOnInit() {
this.targetDeviceFormGroup = this.fb.group({
type: [null, !this.widgetEditMode ? [Validators.required] : []],
deviceId: [null, !this.widgetEditMode ? [Validators.required] : []],
entityAliasId: [null, !this.widgetEditMode ? [Validators.required] : []]
type: [null, (!this.widgetEditMode && !this.targetDeviceOptional) ? [Validators.required] : []],
deviceId: [null, (!this.widgetEditMode && !this.targetDeviceOptional) ? [Validators.required] : []],
entityAliasId: [null, (!this.widgetEditMode && !this.targetDeviceOptional) ? [Validators.required] : []]
});
this.targetDeviceFormGroup.get('type').valueChanges.subscribe(() => {
this.updateValidators();

View File

@ -177,6 +177,8 @@ export interface ScadaSymbolMetadata {
title: string;
description?: string;
searchTags?: string[];
widgetSizeX: number;
widgetSizeY: number;
stateRenderFunction?: string;
stateRender?: ScadaSymbolStateRenderFunction;
tags: ScadaSymbolTag[];
@ -186,6 +188,8 @@ export interface ScadaSymbolMetadata {
export const emptyMetadata = (): ScadaSymbolMetadata => ({
title: '',
widgetSizeX: 3,
widgetSizeY: 3,
tags: [],
behavior: [],
properties: []
@ -443,12 +447,14 @@ export class ScadaSymbolObject {
this.stateValueSubjects[stateValueId].unsubscribe();
}
this.valueActions.forEach(v => v.destroy());
for (const tag of this.metadata.tags) {
const elements = this.context.tags[tag.tag];
elements.forEach(element => {
element.timeline().finish();
element.timeline(null);
});
if (this.context) {
for (const tag of this.metadata.tags) {
const elements = this.context.tags[tag.tag];
elements.forEach(element => {
element.timeline().finish();
element.timeline(null);
});
}
}
if (this.svgShape) {
this.svgShape.remove();

View File

@ -589,6 +589,9 @@ export class WidgetComponentService {
if (isUndefined(result.typeParameters.displayRpcMessageToast)) {
result.typeParameters.displayRpcMessageToast = true;
}
if (isUndefined(result.typeParameters.targetDeviceOptional)) {
result.typeParameters.targetDeviceOptional = false;
}
if (isFunction(widgetTypeInstance.actionSources)) {
result.actionSources = widgetTypeInstance.actionSources();
} else {

View File

@ -962,7 +962,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
if (this.modelValue) {
const config = this.modelValue.config;
if (this.widgetType === widgetType.rpc && this.modelValue.isDataEnabled) {
if (!this.widgetEditMode && !targetDeviceValid(config.targetDevice)) {
if ((!this.widgetEditMode && !this.modelValue?.typeParameters.targetDeviceOptional) && !targetDeviceValid(config.targetDevice)) {
return {
targetDevice: {
valid: false

View File

@ -52,6 +52,23 @@
</tb-string-items-list>
</div>
</div>
<div class="tb-form-row">
<div class="fixed-title-width" translate>scada.widget-size</div>
<div fxLayout="row" fxFlex fxLayoutAlign="end center" fxLayoutGap="8px">
<div class="tb-small-label">{{ 'scada.cols' | translate }}</div>
<mat-form-field appearance="outline" class="number flex" subscriptSizing="dynamic">
<input matInput formControlName="widgetSizeX" required
[min]="1" [max]="24" [step]="1"
type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<div class="tb-small-label">{{ 'scada.rows' | translate }}</div>
<mat-form-field appearance="outline" class="number flex" subscriptSizing="dynamic">
<input matInput formControlName="widgetSizeY" required
[min]="1" [max]="24" [step]="1"
type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</div>
<div class="tb-form-panel stroked">
<mat-expansion-panel class="tb-settings" expanded>
<mat-expansion-panel-header fxLayout="row wrap">

View File

@ -130,6 +130,8 @@ export class ScadaSymbolMetadataComponent extends PageComponent implements OnIni
title: [null, [Validators.required]],
description: [null],
searchTags: [null],
widgetSizeX: [null, [Validators.min(1), Validators.max(24), Validators.required]],
widgetSizeY: [null, [Validators.min(1), Validators.max(24), Validators.required]],
stateRenderFunction: [null],
tags: [null],
behavior: [null],
@ -176,6 +178,8 @@ export class ScadaSymbolMetadataComponent extends PageComponent implements OnIni
title: value?.title,
description: value?.description,
searchTags: value?.searchTags,
widgetSizeX: value?.widgetSizeX || 3,
widgetSizeY: value?.widgetSizeY || 3,
stateRenderFunction: value?.stateRenderFunction,
tags: value?.tags,
behavior: value?.behavior,

View File

@ -29,6 +29,13 @@
[showCloseDetails]="false"
headerHeightPx="64"
headerTitle="{{symbolData?.imageResource?.title}}">
<div class="details-buttons" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<button mat-stroked-button
[disabled]="scadaSymbolFormGroup.invalid"
(click)="createWidget()">
{{ 'scada.create-widget' | translate }}
</button>
</div>
<div *ngIf="previewMode" class="tb-scada-symbol-editor-preview-content tb-absolute-fill">
<div class="tb-scada-symbol-editor-preview-header">
<button mat-button
@ -110,7 +117,7 @@
</div>
</tb-details-panel>
</mat-drawer>
<mat-drawer-content class="tb-scada-symbol-editor-content">
<mat-drawer-content class="tb-scada-symbol-editor-content" [class.preview]="previewMode">
<tb-scada-symbol-editor *ngIf="!previewMode" #symbolEditor
[readonly]="readonly"
[data]="symbolEditorData"
@ -121,8 +128,13 @@
class="tb-absolute-fill"
[aliasController]="aliasController"
[widgets]="previewWidgets"
[autofillHeight]="true"
[columns]="24"
[autofillHeight]="false"
displayGrid="always"
[gridType]="previewWidget.sizeX >= previewWidget.sizeY ? GridType.ScrollVertical : GridType.ScrollHorizontal"
[columns]="previewWidget.sizeX"
setGridSize
[centerVertical]="previewWidget.sizeX >= previewWidget.sizeY"
[centerHorizontal]="previewWidget.sizeX < previewWidget.sizeY"
[margin]="0"
[isEdit]="false"
[isMobileDisabled]="true"

View File

@ -63,5 +63,12 @@
min-height: 0;
max-width: 50%;
background: #fff;
&.preview {
#gridster-parent {
#gridster-background {
background-color: #eee;
}
}
}
}
}

View File

@ -45,7 +45,7 @@ import {
ScadaSymbolEditorData
} from '@home/pages/scada-symbol/scada-symbol-editor.component';
import { ImageService } from '@core/http/image.service';
import { imageResourceType, IMAGES_URL_PREFIX } from '@shared/models/resource.models';
import { imageResourceType, IMAGES_URL_PREFIX, TB_IMAGE_PREFIX } from '@shared/models/resource.models';
import { HasDirtyFlag } from '@core/guards/confirm-on-exit.guard';
import { IAliasController, IStateController, StateParams } from '@core/api/widget-api.models';
import { EntityAliases } from '@shared/models/alias.models';
@ -54,7 +54,7 @@ import { AliasController } from '@core/api/alias-controller';
import { EntityService } from '@core/http/entity.service';
import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core';
import { Widget, widgetType } from '@shared/models/widget.models';
import { Widget, WidgetConfig, widgetType, WidgetTypeDetails } from '@shared/models/widget.models';
import {
scadaSymbolWidgetDefaultSettings,
ScadaSymbolWidgetSettings
@ -71,6 +71,14 @@ import {
UploadImageDialogData, UploadImageDialogResult
} from '@shared/components/image/upload-image-dialog.component';
import { MatDialog } from '@angular/material/dialog';
import { BackgroundType } from '@shared/models/widget-settings.models';
import { GridType } from 'angular-gridster2';
import {
SaveWidgetTypeAsDialogComponent, SaveWidgetTypeAsDialogData,
SaveWidgetTypeAsDialogResult
} from '@home/pages/widget/save-widget-type-as-dialog.component';
import { WidgetService } from '@core/http/widget.service';
import { de } from 'date-fns/locale';
@Component({
selector: 'tb-scada-symbol',
@ -83,6 +91,8 @@ export class ScadaSymbolComponent extends PageComponent
widgetType = widgetType;
GridType = GridType;
@HostBinding('style.width') width = '100%';
@HostBinding('style.height') height = '100%';
@ -115,6 +125,8 @@ export class ScadaSymbolComponent extends PageComponent
fetchCellClickColumns: () => []
};
previewWidget: Widget;
previewWidgets: Array<Widget> = [];
tags: string[];
@ -125,8 +137,6 @@ export class ScadaSymbolComponent extends PageComponent
private previewScadaSymbolObjectSettings: ScadaSymbolObjectSettings;
private previewWidget: Widget;
private forcePristine = false;
private authUser = getCurrentAuthUser(this.store);
@ -149,6 +159,7 @@ export class ScadaSymbolComponent extends PageComponent
private utils: UtilsService,
private translate: TranslateService,
private imageService: ImageService,
private widgetService: WidgetService,
private dialog: MatDialog) {
super(store);
}
@ -247,14 +258,23 @@ export class ScadaSymbolComponent extends PageComponent
scadaSymbolUrl: null,
scadaSymbolContent: this.symbolData.scadaSymbolContent,
scadaSymbolObjectSettings: this.previewScadaSymbolObjectSettings,
padding: '0'
padding: '0',
background: {
type: BackgroundType.color,
color: 'rgba(0,0,0,0)',
overlay: {
enabled: false,
color: 'rgba(255,255,255,0.72)',
blur: 3
}
}
}
};
this.previewWidget = {
typeFullFqn: 'system.scada_symbol',
type: widgetType.rpc,
sizeX: 24,
sizeY: 24,
sizeX: this.previewMetadata.widgetSizeX || 3,
sizeY: this.previewMetadata.widgetSizeY || 3,
row: 0,
col: 0,
config: {
@ -262,7 +282,8 @@ export class ScadaSymbolComponent extends PageComponent
showTitle: false,
dropShadow: false,
padding: '0',
margin: '0'
margin: '0',
backgroundColor: 'rgba(0,0,0,0)'
}
};
this.previewWidgets = [this.previewWidget];
@ -366,6 +387,56 @@ export class ScadaSymbolComponent extends PageComponent
linkElement.dispatchEvent(clickEvent);
}
createWidget() {
const metadata: ScadaSymbolMetadata = this.scadaSymbolFormGroup.get('metadata').value;
this.dialog.open<SaveWidgetTypeAsDialogComponent, SaveWidgetTypeAsDialogData,
SaveWidgetTypeAsDialogResult>(SaveWidgetTypeAsDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
title: metadata.title,
dialogTitle: 'scada.create-widget-from-symbol',
saveAsActionTitle: 'action.create'
}
}).afterClosed().subscribe(
(saveWidgetAsData) => {
if (saveWidgetAsData) {
this.widgetService.getWidgetType('system.scada_symbol').subscribe(
(widgetTemplate) => {
const symbolUrl = TB_IMAGE_PREFIX + this.symbolData.imageResource.link;
const widget: WidgetTypeDetails = {
image: symbolUrl,
description: metadata.description,
tags: metadata.searchTags,
...widgetTemplate
};
widget.fqn = undefined;
widget.id = undefined;
widget.name = saveWidgetAsData.widgetName;
const descriptor = widget.descriptor;
descriptor.sizeX = metadata.widgetSizeX;
descriptor.sizeY = metadata.widgetSizeY;
descriptor.controllerScript = descriptor.controllerScript
.replace(/previewWidth: '\d*px'/gm, `previewWidth: '${metadata.widgetSizeX * 100}px'`);
descriptor.controllerScript = descriptor.controllerScript
.replace(/previewHeight: '\d*px'/gm, `previewHeight: '${metadata.widgetSizeY * 100 + 20}px'`);
const config: WidgetConfig = JSON.parse(descriptor.defaultConfig);
config.title = saveWidgetAsData.widgetName;
config.settings = config.settings || {};
config.settings.scadaSymbolUrl = symbolUrl;
descriptor.defaultConfig = JSON.stringify(config);
this.widgetService.saveWidgetType(widget).subscribe((saved) => {
if (saveWidgetAsData.widgetBundleId) {
this.widgetService.addWidgetFqnToWidgetBundle(saveWidgetAsData.widgetBundleId, saved.fqn).subscribe();
}
});
}
);
}
}
);
}
private updatePreviewWidgetSettings() {
this.previewWidget = deepClone(this.previewWidget);
this.previewWidget.config.settings.scadaSymbolObjectSettings = this.previewScadaSymbolObjectSettings;

View File

@ -15,9 +15,9 @@
limitations under the License.
-->
<form [formGroup]="saveWidgetTypeAsFormGroup" (ngSubmit)="saveAs()" style="width: 360px">
<form [formGroup]="saveWidgetTypeAsFormGroup" (ngSubmit)="saveAs()" style="min-width: 360px">
<mat-toolbar color="primary">
<h2 translate>widget.save-widget-as</h2>
<h2 translate>{{ dialogTitle }}</h2>
<span fxFlex></span>
<button mat-icon-button
(click)="cancel()"
@ -29,7 +29,7 @@
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content>
<span translate>widget.save-widget-as-text</span>
<span translate>{{ dialogTitle }}</span>
<mat-form-field class="mat-block">
<mat-label translate>widget.title</mat-label>
<input matInput formControlName="title" required>
@ -53,7 +53,7 @@
type="submit"
[disabled]="(isLoading$ | async) || saveWidgetTypeAsFormGroup.invalid
|| !saveWidgetTypeAsFormGroup.dirty">
{{ 'action.saveAs' | translate }}
{{ saveAsActionTitle | translate }}
</button>
</div>
</form>

View File

@ -14,8 +14,8 @@
/// limitations under the License.
///
import { Component, OnInit } from '@angular/core';
import { MatDialogRef } from '@angular/material/dialog';
import { Component, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@ -29,6 +29,12 @@ export interface SaveWidgetTypeAsDialogResult {
widgetBundleId?: string;
}
export interface SaveWidgetTypeAsDialogData {
dialogTitle?: string;
title?: string;
saveAsActionTitle?: string;
}
@Component({
selector: 'tb-save-widget-type-as-dialog',
templateUrl: './save-widget-type-as-dialog.component.html',
@ -39,9 +45,12 @@ export class SaveWidgetTypeAsDialogComponent extends
saveWidgetTypeAsFormGroup: FormGroup;
bundlesScope: string;
dialogTitle = 'widget.save-widget-as';
saveAsActionTitle = 'action.saveAs';
constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) private data: SaveWidgetTypeAsDialogData,
public dialogRef: MatDialogRef<SaveWidgetTypeAsDialogComponent, SaveWidgetTypeAsDialogResult>,
public fb: FormBuilder) {
super(store, router, dialogRef);
@ -52,11 +61,18 @@ export class SaveWidgetTypeAsDialogComponent extends
} else {
this.bundlesScope = 'system';
}
if (this.data?.dialogTitle) {
this.dialogTitle = this.data.dialogTitle;
}
if (this.data?.saveAsActionTitle) {
this.saveAsActionTitle = this.data.saveAsActionTitle;
}
}
ngOnInit(): void {
this.saveWidgetTypeAsFormGroup = this.fb.group({
title: [null, [Validators.required]],
title: [this.data?.title, [Validators.required]],
widgetsBundle: [null]
});
}

View File

@ -187,6 +187,7 @@ export interface WidgetTypeParameters {
defaultLatestDataKeysFunction?: (configComponent: any, configData: any) => DataKey[];
dataKeySettingsFunction?: DataKeySettingsFunction;
displayRpcMessageToast?: boolean;
targetDeviceOptional?: boolean;
}
export interface WidgetControllerDescriptor {

View File

@ -3455,6 +3455,9 @@
"title": "Title",
"description": "Description",
"search-tags": "Search tags",
"widget-size": "Widget size",
"cols": "cols",
"rows": "rows",
"state-render-function": "State render function",
"preview": "Preview",
"preview-widget-action-text": "Widget action '{{type}}' successfully invoked!",
@ -3464,6 +3467,8 @@
"browse-symbol-from-gallery": "Browse SCADA symbol from gallery",
"zoom-in": "Zoom In",
"zoom-out": "Zoom Out",
"create-widget": "Create widget",
"create-widget-from-symbol": "Create widget from SCADA symbol",
"tag": {
"tag": "Tag",
"on-click-action": "On click action",