UI: Improve dashboard layout settings.

This commit is contained in:
Igor Kulikov 2024-06-14 17:59:28 +03:00
parent 9bd923a878
commit 8fad8245df
14 changed files with 354 additions and 65 deletions

View File

@ -0,0 +1,44 @@
diff --git a/node_modules/angular-gridster2/fesm2020/angular-gridster2.mjs b/node_modules/angular-gridster2/fesm2020/angular-gridster2.mjs
index cf4e220..4275d11 100644
--- a/node_modules/angular-gridster2/fesm2020/angular-gridster2.mjs
+++ b/node_modules/angular-gridster2/fesm2020/angular-gridster2.mjs
@@ -208,6 +208,7 @@ const GridsterConfigService = {
useTransformPositioning: true,
scrollSensitivity: 10,
scrollSpeed: 20,
+ colWidthUpdateCallback: undefined,
initCallback: undefined,
destroyCallback: undefined,
gridSizeChangedCallback: undefined,
@@ -1243,6 +1244,9 @@ class GridsterComponent {
this.renderer.setStyle(this.el, 'padding-right', this.$options.margin + 'px');
}
this.curColWidth = (this.curWidth - marginWidth) / this.columns;
+ if (this.options.colWidthUpdateCallback) {
+ this.curColWidth = this.options.colWidthUpdateCallback(this.curColWidth);
+ }
let marginHeight = -this.$options.margin;
if (this.$options.outerMarginTop !== null) {
marginHeight += this.$options.outerMarginTop;
@@ -1266,6 +1270,9 @@ class GridsterComponent {
}
else {
this.curColWidth = (this.curWidth + this.$options.margin) / this.columns;
+ if (this.options.colWidthUpdateCallback) {
+ this.curColWidth = this.options.colWidthUpdateCallback(this.curColWidth);
+ }
this.curRowHeight =
((this.curHeight + this.$options.margin) / this.rows) *
this.$options.rowHeightRatio;
diff --git a/node_modules/angular-gridster2/lib/gridsterConfig.interface.d.ts b/node_modules/angular-gridster2/lib/gridsterConfig.interface.d.ts
index 1d7cdf0..a712b35 100644
--- a/node_modules/angular-gridster2/lib/gridsterConfig.interface.d.ts
+++ b/node_modules/angular-gridster2/lib/gridsterConfig.interface.d.ts
@@ -73,6 +73,7 @@ export interface GridsterConfig {
useTransformPositioning?: boolean;
scrollSensitivity?: number | null;
scrollSpeed?: number;
+ colWidthUpdateCallback?: (colWidth: number) => number;
initCallback?: (gridster: GridsterComponentInterface) => void;
destroyCallback?: (gridster: GridsterComponentInterface) => void;
gridSizeChangedCallback?: (gridster: GridsterComponentInterface) => void;

View File

@ -610,17 +610,15 @@ export class DashboardUtilsService {
originalColumns = 24;
}
const gridSettings = layout.gridSettings;
if (!gridSettings.isScada) {
let columns = 24;
if (gridSettings && gridSettings.columns) {
columns = gridSettings.columns;
}
columns = columns * layoutCount;
if (columns !== originalColumns) {
const ratio = columns / originalColumns;
widgetLayout.sizeX *= ratio;
widgetLayout.sizeY *= ratio;
}
let columns = 24;
if (gridSettings && gridSettings.columns) {
columns = gridSettings.columns;
}
columns = columns * layoutCount;
if (columns !== originalColumns) {
const ratio = columns / originalColumns;
widgetLayout.sizeX *= ratio;
widgetLayout.sizeY *= ratio;
}
if (row > -1 && column > - 1) {
@ -686,35 +684,32 @@ export class DashboardUtilsService {
const columns = gridSettings.columns || 24;
const ratio = columns / prevColumns;
layout.gridSettings = gridSettings;
if (!gridSettings.isScada) {
let maxRow = 0;
for (const w of Object.keys(layout.widgets)) {
const widget = layout.widgets[w];
if (!widget.sizeX) {
widget.sizeX = 1;
}
if (!widget.sizeY) {
widget.sizeY = 1;
}
maxRow = Math.max(maxRow, widget.row + widget.sizeY);
for (const w of Object.keys(layout.widgets)) {
const widget = layout.widgets[w];
if (!widget.sizeX) {
widget.sizeX = 1;
}
const newMaxRow = Math.round(maxRow * ratio);
for (const w of Object.keys(layout.widgets)) {
const widget = layout.widgets[w];
if (widget.row + widget.sizeY === maxRow) {
widget.row = Math.round(widget.row * ratio);
widget.sizeY = newMaxRow - widget.row;
} else {
widget.row = Math.round(widget.row * ratio);
widget.sizeY = Math.round(widget.sizeY * ratio);
}
widget.sizeX = Math.round(widget.sizeX * ratio);
widget.col = Math.round(widget.col * ratio);
if (widget.col + widget.sizeX > columns) {
widget.sizeX = columns - widget.col;
}
if (!widget.sizeY) {
widget.sizeY = 1;
}
}
for (const w of Object.keys(layout.widgets)) {
const widget = layout.widgets[w];
widget.row = Math.round(widget.row * ratio);
widget.col = Math.round(widget.col * ratio);
widget.sizeX = Math.round(widget.sizeX * ratio);
widget.sizeY = Math.round(widget.sizeY * ratio);
}
}
public moveWidgets(layout: DashboardLayout, cols: number, rows: number) {
cols = isDefinedAndNotNull(cols) ? Math.round(cols) : 0;
rows = isDefinedAndNotNull(rows) ? Math.round(rows) : 0;
for (const w of Object.keys(layout.widgets)) {
const widget = layout.widgets[w];
widget.col = Math.max(0, widget.col + cols);
widget.row = Math.max(0, widget.row + rows);
}
}
public removeUnusedWidgets(dashboard: Dashboard) {

View File

@ -150,6 +150,10 @@ import { LayoutFixedSize, LayoutWidthType } from '@home/components/dashboard-pag
import { TbPopoverComponent } from '@shared/components/popover.component';
import { ResizeObserver } from '@juggle/resize-observer';
import { HasDirtyFlag } from '@core/guards/confirm-on-exit.guard';
import {
MoveWidgetsDialogComponent,
MoveWidgetsDialogResult
} from '@home/components/dashboard-page/layout/move-widgets-dialog.component';
// @dynamic
@Component({
@ -285,7 +289,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
gridSettings: {},
ignoreLoading: true,
ctrl: null,
dashboardCtrl: this
dashboardCtrl: this,
displayGrid: 'onDrag&Resize'
}
},
right: {
@ -297,7 +302,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
gridSettings: {},
ignoreLoading: true,
ctrl: null,
dashboardCtrl: this
dashboardCtrl: this,
displayGrid: 'onDrag&Resize'
}
}
};
@ -913,6 +919,28 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
});
}
private moveWidgets($event: Event, layoutId: DashboardLayoutId) {
if ($event) {
$event.stopPropagation();
}
this.layouts[layoutId].layoutCtx.displayGrid = 'always';
this.cd.markForCheck();
this.dialog.open<MoveWidgetsDialogComponent, any,
MoveWidgetsDialogResult>(MoveWidgetsDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog']
}).afterClosed().subscribe((result) => {
this.layouts[layoutId].layoutCtx.displayGrid = 'onDrag&Resize';
if (result) {
const targetLayout = this.dashboardConfiguration.states[this.dashboardCtx.state].layouts[layoutId];
this.dashboardUtils.moveWidgets(targetLayout, result.cols, result.rows);
this.updateLayouts();
} else {
this.cd.markForCheck();
}
});
}
private updateDashboardLayouts(newLayouts: DashboardStateLayouts) {
this.dashboardUtils.setLayouts(this.dashboard, this.dashboardCtx.state, newLayouts);
this.updateLayouts();
@ -1422,6 +1450,16 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
shortcut: 'M-I'
}
);
dashboardContextActions.push(
{
action: ($event) => {
this.moveWidgets($event, layoutCtx.id);
},
enabled: true,
value: 'dashboard.move-all-widgets',
icon: 'open_with'
}
);
}
return dashboardContextActions;
}

View File

@ -21,6 +21,7 @@ import { IAliasController, IStateController } from '@core/api/widget-api.models'
import { ILayoutController } from './layout/layout.models';
import { DashboardContextMenuItem, WidgetContextMenuItem } from '@home/models/dashboard-component.models';
import { Observable } from 'rxjs';
import { displayGrids } from 'angular-gridster2/lib/gridsterConfig.interface';
export declare type DashboardPageScope = 'tenant' | 'customer';
@ -69,6 +70,7 @@ export interface DashboardPageLayoutContext {
ctrl: ILayoutController;
dashboardCtrl: IDashboardController;
ignoreLoading: boolean;
displayGrid: displayGrids;
}
export interface DashboardPageLayout {

View File

@ -114,8 +114,9 @@
<div class="tb-form-panel-title" translate>dashboard.layout-settings</div>
<div class="tb-form-row space-between">
<div translate>dashboard.columns-count</div>
<mat-form-field appearance="outline" class="number medium-width tb-suffix-absolute" subscriptSizing="dynamic">
<input matInput formControlName="columns" type="number" min="10" max="1000" step="1"
<mat-form-field *ngIf="!gridSettingsFormGroup.get('isScada').value"
appearance="outline" class="number medium-width tb-suffix-absolute" subscriptSizing="dynamic">
<input matInput formControlName="columns" type="number" min="10" max="1008" step="1"
required
placeholder="{{ 'widget-config.set' | translate }}">
<mat-icon matSuffix
@ -128,6 +129,32 @@
warning
</mat-icon>
</mat-form-field>
<mat-form-field *ngIf="gridSettingsFormGroup.get('isScada').value"
class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select required formControlName="columns">
<mat-option *ngFor="let columns of scadaColumns" [value]="columns">
{{ columns }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div translate>dashboard.min-layout-width</div>
<mat-form-field appearance="outline" class="number medium-width tb-suffix-absolute" subscriptSizing="dynamic">
<input matInput formControlName="minColumns" type="number" min="10" max="1008" step="1"
required
placeholder="{{ 'widget-config.set' | translate }}">
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="(gridSettingsFormGroup.get('columns').hasError('required') ? 'dashboard.columns-count-required' :
(gridSettingsFormGroup.get('columns').hasError('min') ? 'dashboard.min-columns-count-message' : 'dashboard.max-columns-count-message')) | translate"
*ngIf="gridSettingsFormGroup.get('minColumns').invalid && gridSettingsFormGroup.get('minColumns').touched"
class="tb-error">
warning
</mat-icon>
<span translate matSuffix>dashboard.columns-suffix</span>
</mat-form-field>
</div>
<div *ngIf="!gridSettingsFormGroup.get('isScada').value" class="tb-form-row space-between">
<div translate>dashboard.widgets-margins</div>

View File

@ -54,6 +54,8 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
submitted = false;
scadaColumns: number[] = [];
private stateControllerTranslationMap = new Map<string, string>([
['default', 'dashboard.state-controller-default'],
]);
@ -155,10 +157,17 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
}
if (this.gridSettings) {
let columns = 24;
while (columns <= 1008) {
this.scadaColumns.push(columns);
columns += 24;
}
const mobileAutoFillHeight = isUndefined(this.gridSettings.mobileAutoFillHeight) ? false : this.gridSettings.mobileAutoFillHeight;
this.gridSettingsFormGroup = this.fb.group({
isScada: [this.gridSettings.isScada, []],
columns: [this.gridSettings.columns || 24, [Validators.required, Validators.min(10), Validators.max(1000)]],
columns: [this.gridSettings.columns || 24, [Validators.required, Validators.min(10), Validators.max(1008)]],
minColumns: [this.gridSettings.minColumns || this.gridSettings.columns || 24,
[Validators.required, Validators.min(10), Validators.max(1008)]],
margin: [isDefined(this.gridSettings.margin) ? this.gridSettings.margin : 10,
[Validators.required, Validators.min(0), Validators.max(50)]],
outerMargin: [isUndefined(this.gridSettings.outerMargin) ? true : this.gridSettings.outerMargin, []],
@ -228,6 +237,11 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
this.gridSettingsFormGroup.get('autoFillHeight').disable();
this.gridSettingsFormGroup.get('mobileAutoFillHeight').disable({emitEvent: false});
this.gridSettingsFormGroup.get('mobileRowHeight').disable();
const columns: number = this.gridSettingsFormGroup.get('columns').value;
if (columns % 24 !== 0) {
const newColumns = Math.min(1008, 24 * Math.ceil(columns / 24));
this.gridSettingsFormGroup.get('columns').patchValue(newColumns);
}
} else {
this.gridSettingsFormGroup.get('margin').enable();
this.gridSettingsFormGroup.get('outerMargin').enable();

View File

@ -41,7 +41,9 @@
[backgroundImage]="backgroundImage$ | async"
[widgets]="layoutCtx.widgets"
[widgetLayouts]="layoutCtx.widgetLayouts"
[columns]="layoutCtx.gridSettings.columns"
[columns]="columns"
[displayGrid]="displayGrid"
[colWidthInteger]="colWidthInteger"
[outerMargin]="outerMargin"
[margin]="margin"
[aliasController]="dashboardCtx.aliasController"

View File

@ -36,6 +36,7 @@ import { TbCheatSheetComponent } from '@shared/components/cheatsheet.component';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { ImagePipe } from '@shared/pipe/image.pipe';
import { map } from 'rxjs/operators';
import { displayGrids } from 'angular-gridster2/lib/gridsterConfig.interface';
@Component({
selector: 'tb-dashboard-layout',
@ -86,6 +87,18 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo
return this.widgetEditMode || this.layoutCtx.gridSettings.isScada;
}
get colWidthInteger(): boolean {
return this.layoutCtx.gridSettings.isScada;
}
get columns(): number {
return this.layoutCtx.gridSettings.minColumns || this.layoutCtx.gridSettings.columns || 24;
}
get displayGrid(): displayGrids {
return this.layoutCtx.displayGrid || 'onDrag&Resize';
}
@Input()
dashboardCtx: DashboardContext;

View File

@ -0,0 +1,60 @@
<!--
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.
-->
<form (ngSubmit)="move()">
<mat-toolbar color="primary">
<h2 translate>dashboard.move-all-widgets</h2>
<span fxFlex></span>
<button mat-icon-button
(click)="cancel()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div mat-dialog-content class="tb-form-panel no-border no-padding" [formGroup]="moveWidgetsFormGroup">
<div class="tb-form-row space-between column-xs">
<div translate>dashboard.move-by</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input required matInput formControlName="cols"
type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<span translate matSuffix>dashboard.cols</span>
</mat-form-field>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input required matInput formControlName="rows"
type="number" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<span translate matSuffix>dashboard.rows</span>
</mat-form-field>
</div>
</div>
</div>
<div mat-dialog-actions fxLayoutAlign="end center">
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
{{ 'action.cancel' | translate }}
</button>
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || moveWidgetsFormGroup.invalid || !moveWidgetsFormGroup.dirty">
{{ 'action.move' | translate }}
</button>
</div>
</form>

View File

@ -0,0 +1,62 @@
///
/// 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 } from '@angular/core';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { DialogComponent } from '@app/shared/components/dialog.component';
export interface MoveWidgetsDialogResult {
cols: number;
rows: number;
}
@Component({
selector: 'tb-move-widgets-dialog',
templateUrl: './move-widgets-dialog.component.html',
providers: [],
styleUrls: []
})
export class MoveWidgetsDialogComponent extends DialogComponent<MoveWidgetsDialogComponent, MoveWidgetsDialogResult> {
moveWidgetsFormGroup: UntypedFormGroup;
constructor(protected store: Store<AppState>,
protected router: Router,
protected dialogRef: MatDialogRef<MoveWidgetsDialogComponent, MoveWidgetsDialogResult>,
private fb: UntypedFormBuilder,
private dialog: MatDialog) {
super(store, router, dialogRef);
this.moveWidgetsFormGroup = this.fb.group({
cols: [0, [Validators.required]],
rows: [0, [Validators.required]]
}
);
}
cancel(): void {
this.dialogRef.close(null);
}
move(): void {
const result: MoveWidgetsDialogResult = this.moveWidgetsFormGroup.value;
this.dialogRef.close(result);
}
}

View File

@ -90,6 +90,10 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
@Input()
columns: number;
@Input()
@coerceBoolean()
colWidthInteger = false;
@Input()
@coerceBoolean()
setGridSize = false;
@ -256,15 +260,22 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
itemChangeCallback: item => this.dashboardWidgets.sortWidgets(),
itemInitCallback: (item, itemComponent) => {
(itemComponent.item as DashboardWidget).gridsterItemComponent = itemComponent;
},
colWidthUpdateCallback: (colWidth) => {
if (this.colWidthInteger) {
return Math.floor(colWidth);
} else {
return colWidth;
}
}
};
this.updateMobileOpts();
this.updateGridOpts();
this.breakpointObserverSubscription = this.breakpointObserver
.observe(MediaBreakpoints['gt-sm']).subscribe(
() => {
this.updateMobileOpts();
this.updateGridOpts();
this.notifyGridsterOptionsChanged();
}
);
@ -291,20 +302,24 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
}
ngOnChanges(changes: SimpleChanges): void {
let updateMobileOpts = false;
let updateLayoutOpts = false;
let updateGridOpts = false;
let updateColumnOpts = false;
let updateEditingOpts = false;
let updateDisplayGridOpts = false;
let updateWidgets = false;
let updateDashboardTimewindow = false;
for (const propName of Object.keys(changes)) {
const change = changes[propName];
if (!change.firstChange && change.currentValue !== change.previousValue) {
if (['isMobile', 'isMobileDisabled', 'autofillHeight', 'mobileAutofillHeight', 'mobileRowHeight'].includes(propName)) {
updateMobileOpts = true;
if (['isMobile', 'isMobileDisabled', 'autofillHeight', 'mobileAutofillHeight',
'mobileRowHeight', 'colWidthInteger'].includes(propName)) {
updateGridOpts = true;
} else if (['outerMargin', 'margin', 'columns'].includes(propName)) {
updateLayoutOpts = true;
updateColumnOpts = true;
} else if (['isEdit', 'isEditingWidget'].includes(propName)) {
updateEditingOpts = true;
} else if (propName === 'displayGrid') {
updateDisplayGridOpts = true;
} else if (['widgets', 'widgetLayouts'].includes(propName)) {
updateWidgets = true;
} else if (propName === 'dashboardTimewindow') {
@ -318,16 +333,19 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
this.dashboardTimewindowChangedSubject.next(this.dashboardTimewindow);
}
if (updateLayoutOpts) {
this.updateLayoutOpts();
if (updateColumnOpts) {
this.updateColumnOpts();
}
if (updateMobileOpts) {
this.updateMobileOpts();
if (updateGridOpts) {
this.updateGridOpts();
}
if (updateEditingOpts) {
this.updateEditingOpts();
}
if (updateMobileOpts || updateLayoutOpts || updateEditingOpts) {
if (updateDisplayGridOpts) {
this.updateDisplayGridOpts();
}
if (updateGridOpts || updateColumnOpts || updateEditingOpts || updateDisplayGridOpts) {
this.notifyGridsterOptionsChanged();
}
}
@ -544,7 +562,15 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
});
}
private updateMobileOpts(parentHeight?: number) {
private onGridsterParentResize() {
const parentHeight = this.gridster.el.offsetHeight;
if (this.isMobileSize && this.mobileAutofillHeight && parentHeight) {
this.updateGridOpts(parentHeight);
}
this.notifyGridsterOptionsChanged();
}
private updateGridOpts(parentHeight?: number) {
let updateWidgetRowsAndSort = false;
const isMobileSize = this.checkIsMobileSize();
if (this.isMobileSize !== isMobileSize) {
@ -568,15 +594,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
}
}
private onGridsterParentResize() {
const parentHeight = this.gridster.el.offsetHeight;
if (this.isMobileSize && this.mobileAutofillHeight && parentHeight) {
this.updateMobileOpts(parentHeight);
}
this.notifyGridsterOptionsChanged();
}
private updateLayoutOpts() {
private updateColumnOpts() {
this.gridsterOpts.minCols = this.columns ? this.columns : 24;
this.gridsterOpts.outerMargin = isDefined(this.outerMargin) ? this.outerMargin : true;
this.gridsterOpts.margin = isDefined(this.margin) ? this.margin : 10;
@ -587,6 +605,10 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
this.gridsterOpts.draggable.enabled = this.isEdit && !this.isEditingWidget;
}
private updateDisplayGridOpts() {
this.gridsterOpts.displayGrid = this.displayGrid;
}
public notifyGridsterOptionsChanged() {
if (!this.optionsChangeNotificationsPaused) {
if (this.gridster && this.gridster.options) {

View File

@ -174,6 +174,7 @@ import {
import { WidgetConfigComponentsModule } from '@home/components/widget/config/widget-config-components.module';
import { BasicWidgetConfigModule } from '@home/components/widget/config/basic/basic-widget-config.module';
import { DeleteTimeseriesPanelComponent } from '@home/components/attribute/delete-timeseries-panel.component';
import { MoveWidgetsDialogComponent } from '@home/components/dashboard-page/layout/move-widgets-dialog.component';
@NgModule({
declarations:
@ -287,6 +288,7 @@ import { DeleteTimeseriesPanelComponent } from '@home/components/attribute/delet
EditWidgetComponent,
DashboardWidgetSelectComponent,
AddWidgetDialogComponent,
MoveWidgetsDialogComponent,
ManageDashboardLayoutsDialogComponent,
DashboardSettingsDialogComponent,
ManageDashboardStatesDialogComponent,
@ -419,6 +421,7 @@ import { DeleteTimeseriesPanelComponent } from '@home/components/attribute/delet
EditWidgetComponent,
DashboardWidgetSelectComponent,
AddWidgetDialogComponent,
MoveWidgetsDialogComponent,
ManageDashboardLayoutsDialogComponent,
DashboardSettingsDialogComponent,
ManageDashboardStatesDialogComponent,

View File

@ -52,6 +52,7 @@ export interface WidgetLayouts {
export interface GridSettings {
backgroundColor?: string;
columns?: number;
minColumns?: number;
margin?: number;
outerMargin?: boolean;
backgroundSizeMode?: string;

View File

@ -1163,12 +1163,18 @@
"maximum-upload-file-size": "Maximum upload file size: {{ size }}",
"cannot-upload-file": "Cannot upload file",
"settings": "Settings",
"move-all-widgets": "Move all widgets",
"move-by": "Move by",
"cols": "cols",
"rows": "rows",
"scada-layout": "SCADA layout",
"layout-settings": "Layout settings",
"columns-count": "Columns count",
"columns-count-required": "Columns count is required.",
"min-columns-count-message": "Only 10 minimum column count is allowed.",
"max-columns-count-message": "Only 1000 maximum column count is allowed.",
"min-layout-width": "Minimum layout width",
"columns-suffix": "columns",
"widgets-margins": "Margin between widgets",
"margin-required": "Margin value is required.",
"min-margin-message": "Only 0 is allowed as minimum margin value.",