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; originalColumns = 24;
} }
const gridSettings = layout.gridSettings; const gridSettings = layout.gridSettings;
if (!gridSettings.isScada) { let columns = 24;
let columns = 24; if (gridSettings && gridSettings.columns) {
if (gridSettings && gridSettings.columns) { columns = gridSettings.columns;
columns = gridSettings.columns; }
} columns = columns * layoutCount;
columns = columns * layoutCount; if (columns !== originalColumns) {
if (columns !== originalColumns) { const ratio = columns / originalColumns;
const ratio = columns / originalColumns; widgetLayout.sizeX *= ratio;
widgetLayout.sizeX *= ratio; widgetLayout.sizeY *= ratio;
widgetLayout.sizeY *= ratio;
}
} }
if (row > -1 && column > - 1) { if (row > -1 && column > - 1) {
@ -686,35 +684,32 @@ export class DashboardUtilsService {
const columns = gridSettings.columns || 24; const columns = gridSettings.columns || 24;
const ratio = columns / prevColumns; const ratio = columns / prevColumns;
layout.gridSettings = gridSettings; layout.gridSettings = gridSettings;
if (!gridSettings.isScada) { for (const w of Object.keys(layout.widgets)) {
let maxRow = 0; const widget = layout.widgets[w];
for (const w of Object.keys(layout.widgets)) { if (!widget.sizeX) {
const widget = layout.widgets[w]; widget.sizeX = 1;
if (!widget.sizeX) {
widget.sizeX = 1;
}
if (!widget.sizeY) {
widget.sizeY = 1;
}
maxRow = Math.max(maxRow, widget.row + widget.sizeY);
} }
const newMaxRow = Math.round(maxRow * ratio); if (!widget.sizeY) {
for (const w of Object.keys(layout.widgets)) { widget.sizeY = 1;
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;
}
} }
} }
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) { 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 { TbPopoverComponent } from '@shared/components/popover.component';
import { ResizeObserver } from '@juggle/resize-observer'; import { ResizeObserver } from '@juggle/resize-observer';
import { HasDirtyFlag } from '@core/guards/confirm-on-exit.guard'; import { HasDirtyFlag } from '@core/guards/confirm-on-exit.guard';
import {
MoveWidgetsDialogComponent,
MoveWidgetsDialogResult
} from '@home/components/dashboard-page/layout/move-widgets-dialog.component';
// @dynamic // @dynamic
@Component({ @Component({
@ -285,7 +289,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
gridSettings: {}, gridSettings: {},
ignoreLoading: true, ignoreLoading: true,
ctrl: null, ctrl: null,
dashboardCtrl: this dashboardCtrl: this,
displayGrid: 'onDrag&Resize'
} }
}, },
right: { right: {
@ -297,7 +302,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
gridSettings: {}, gridSettings: {},
ignoreLoading: true, ignoreLoading: true,
ctrl: null, 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) { private updateDashboardLayouts(newLayouts: DashboardStateLayouts) {
this.dashboardUtils.setLayouts(this.dashboard, this.dashboardCtx.state, newLayouts); this.dashboardUtils.setLayouts(this.dashboard, this.dashboardCtx.state, newLayouts);
this.updateLayouts(); this.updateLayouts();
@ -1422,6 +1450,16 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
shortcut: 'M-I' shortcut: 'M-I'
} }
); );
dashboardContextActions.push(
{
action: ($event) => {
this.moveWidgets($event, layoutCtx.id);
},
enabled: true,
value: 'dashboard.move-all-widgets',
icon: 'open_with'
}
);
} }
return dashboardContextActions; return dashboardContextActions;
} }

View File

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

View File

@ -114,8 +114,9 @@
<div class="tb-form-panel-title" translate>dashboard.layout-settings</div> <div class="tb-form-panel-title" translate>dashboard.layout-settings</div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div translate>dashboard.columns-count</div> <div translate>dashboard.columns-count</div>
<mat-form-field appearance="outline" class="number medium-width tb-suffix-absolute" subscriptSizing="dynamic"> <mat-form-field *ngIf="!gridSettingsFormGroup.get('isScada').value"
<input matInput formControlName="columns" type="number" min="10" max="1000" step="1" appearance="outline" class="number medium-width tb-suffix-absolute" subscriptSizing="dynamic">
<input matInput formControlName="columns" type="number" min="10" max="1008" step="1"
required required
placeholder="{{ 'widget-config.set' | translate }}"> placeholder="{{ 'widget-config.set' | translate }}">
<mat-icon matSuffix <mat-icon matSuffix
@ -128,6 +129,32 @@
warning warning
</mat-icon> </mat-icon>
</mat-form-field> </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>
<div *ngIf="!gridSettingsFormGroup.get('isScada').value" class="tb-form-row space-between"> <div *ngIf="!gridSettingsFormGroup.get('isScada').value" class="tb-form-row space-between">
<div translate>dashboard.widgets-margins</div> <div translate>dashboard.widgets-margins</div>

View File

@ -54,6 +54,8 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
submitted = false; submitted = false;
scadaColumns: number[] = [];
private stateControllerTranslationMap = new Map<string, string>([ private stateControllerTranslationMap = new Map<string, string>([
['default', 'dashboard.state-controller-default'], ['default', 'dashboard.state-controller-default'],
]); ]);
@ -155,10 +157,17 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
} }
if (this.gridSettings) { 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; const mobileAutoFillHeight = isUndefined(this.gridSettings.mobileAutoFillHeight) ? false : this.gridSettings.mobileAutoFillHeight;
this.gridSettingsFormGroup = this.fb.group({ this.gridSettingsFormGroup = this.fb.group({
isScada: [this.gridSettings.isScada, []], 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, margin: [isDefined(this.gridSettings.margin) ? this.gridSettings.margin : 10,
[Validators.required, Validators.min(0), Validators.max(50)]], [Validators.required, Validators.min(0), Validators.max(50)]],
outerMargin: [isUndefined(this.gridSettings.outerMargin) ? true : this.gridSettings.outerMargin, []], 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('autoFillHeight').disable();
this.gridSettingsFormGroup.get('mobileAutoFillHeight').disable({emitEvent: false}); this.gridSettingsFormGroup.get('mobileAutoFillHeight').disable({emitEvent: false});
this.gridSettingsFormGroup.get('mobileRowHeight').disable(); 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 { } else {
this.gridSettingsFormGroup.get('margin').enable(); this.gridSettingsFormGroup.get('margin').enable();
this.gridSettingsFormGroup.get('outerMargin').enable(); this.gridSettingsFormGroup.get('outerMargin').enable();

View File

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

View File

@ -36,6 +36,7 @@ import { TbCheatSheetComponent } from '@shared/components/cheatsheet.component';
import { TbPopoverComponent } from '@shared/components/popover.component'; import { TbPopoverComponent } from '@shared/components/popover.component';
import { ImagePipe } from '@shared/pipe/image.pipe'; import { ImagePipe } from '@shared/pipe/image.pipe';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { displayGrids } from 'angular-gridster2/lib/gridsterConfig.interface';
@Component({ @Component({
selector: 'tb-dashboard-layout', selector: 'tb-dashboard-layout',
@ -86,6 +87,18 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo
return this.widgetEditMode || this.layoutCtx.gridSettings.isScada; 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() @Input()
dashboardCtx: DashboardContext; 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() @Input()
columns: number; columns: number;
@Input()
@coerceBoolean()
colWidthInteger = false;
@Input() @Input()
@coerceBoolean() @coerceBoolean()
setGridSize = false; setGridSize = false;
@ -256,15 +260,22 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
itemChangeCallback: item => this.dashboardWidgets.sortWidgets(), itemChangeCallback: item => this.dashboardWidgets.sortWidgets(),
itemInitCallback: (item, itemComponent) => { itemInitCallback: (item, itemComponent) => {
(itemComponent.item as DashboardWidget).gridsterItemComponent = 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 this.breakpointObserverSubscription = this.breakpointObserver
.observe(MediaBreakpoints['gt-sm']).subscribe( .observe(MediaBreakpoints['gt-sm']).subscribe(
() => { () => {
this.updateMobileOpts(); this.updateGridOpts();
this.notifyGridsterOptionsChanged(); this.notifyGridsterOptionsChanged();
} }
); );
@ -291,20 +302,24 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
} }
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
let updateMobileOpts = false; let updateGridOpts = false;
let updateLayoutOpts = false; let updateColumnOpts = false;
let updateEditingOpts = false; let updateEditingOpts = false;
let updateDisplayGridOpts = false;
let updateWidgets = false; let updateWidgets = false;
let updateDashboardTimewindow = false; let updateDashboardTimewindow = false;
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 (['isMobile', 'isMobileDisabled', 'autofillHeight', 'mobileAutofillHeight', 'mobileRowHeight'].includes(propName)) { if (['isMobile', 'isMobileDisabled', 'autofillHeight', 'mobileAutofillHeight',
updateMobileOpts = true; 'mobileRowHeight', 'colWidthInteger'].includes(propName)) {
updateGridOpts = true;
} else if (['outerMargin', 'margin', 'columns'].includes(propName)) { } else if (['outerMargin', 'margin', 'columns'].includes(propName)) {
updateLayoutOpts = true; updateColumnOpts = true;
} else if (['isEdit', 'isEditingWidget'].includes(propName)) { } else if (['isEdit', 'isEditingWidget'].includes(propName)) {
updateEditingOpts = true; updateEditingOpts = true;
} else if (propName === 'displayGrid') {
updateDisplayGridOpts = true;
} else if (['widgets', 'widgetLayouts'].includes(propName)) { } else if (['widgets', 'widgetLayouts'].includes(propName)) {
updateWidgets = true; updateWidgets = true;
} else if (propName === 'dashboardTimewindow') { } else if (propName === 'dashboardTimewindow') {
@ -318,16 +333,19 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
this.dashboardTimewindowChangedSubject.next(this.dashboardTimewindow); this.dashboardTimewindowChangedSubject.next(this.dashboardTimewindow);
} }
if (updateLayoutOpts) { if (updateColumnOpts) {
this.updateLayoutOpts(); this.updateColumnOpts();
} }
if (updateMobileOpts) { if (updateGridOpts) {
this.updateMobileOpts(); this.updateGridOpts();
} }
if (updateEditingOpts) { if (updateEditingOpts) {
this.updateEditingOpts(); this.updateEditingOpts();
} }
if (updateMobileOpts || updateLayoutOpts || updateEditingOpts) { if (updateDisplayGridOpts) {
this.updateDisplayGridOpts();
}
if (updateGridOpts || updateColumnOpts || updateEditingOpts || updateDisplayGridOpts) {
this.notifyGridsterOptionsChanged(); 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; let updateWidgetRowsAndSort = false;
const isMobileSize = this.checkIsMobileSize(); const isMobileSize = this.checkIsMobileSize();
if (this.isMobileSize !== isMobileSize) { if (this.isMobileSize !== isMobileSize) {
@ -568,15 +594,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
} }
} }
private onGridsterParentResize() { private updateColumnOpts() {
const parentHeight = this.gridster.el.offsetHeight;
if (this.isMobileSize && this.mobileAutofillHeight && parentHeight) {
this.updateMobileOpts(parentHeight);
}
this.notifyGridsterOptionsChanged();
}
private updateLayoutOpts() {
this.gridsterOpts.minCols = this.columns ? this.columns : 24; this.gridsterOpts.minCols = this.columns ? this.columns : 24;
this.gridsterOpts.outerMargin = isDefined(this.outerMargin) ? this.outerMargin : true; this.gridsterOpts.outerMargin = isDefined(this.outerMargin) ? this.outerMargin : true;
this.gridsterOpts.margin = isDefined(this.margin) ? this.margin : 10; 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; this.gridsterOpts.draggable.enabled = this.isEdit && !this.isEditingWidget;
} }
private updateDisplayGridOpts() {
this.gridsterOpts.displayGrid = this.displayGrid;
}
public notifyGridsterOptionsChanged() { public notifyGridsterOptionsChanged() {
if (!this.optionsChangeNotificationsPaused) { if (!this.optionsChangeNotificationsPaused) {
if (this.gridster && this.gridster.options) { 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 { WidgetConfigComponentsModule } from '@home/components/widget/config/widget-config-components.module';
import { BasicWidgetConfigModule } from '@home/components/widget/config/basic/basic-widget-config.module'; import { BasicWidgetConfigModule } from '@home/components/widget/config/basic/basic-widget-config.module';
import { DeleteTimeseriesPanelComponent } from '@home/components/attribute/delete-timeseries-panel.component'; import { DeleteTimeseriesPanelComponent } from '@home/components/attribute/delete-timeseries-panel.component';
import { MoveWidgetsDialogComponent } from '@home/components/dashboard-page/layout/move-widgets-dialog.component';
@NgModule({ @NgModule({
declarations: declarations:
@ -287,6 +288,7 @@ import { DeleteTimeseriesPanelComponent } from '@home/components/attribute/delet
EditWidgetComponent, EditWidgetComponent,
DashboardWidgetSelectComponent, DashboardWidgetSelectComponent,
AddWidgetDialogComponent, AddWidgetDialogComponent,
MoveWidgetsDialogComponent,
ManageDashboardLayoutsDialogComponent, ManageDashboardLayoutsDialogComponent,
DashboardSettingsDialogComponent, DashboardSettingsDialogComponent,
ManageDashboardStatesDialogComponent, ManageDashboardStatesDialogComponent,
@ -419,6 +421,7 @@ import { DeleteTimeseriesPanelComponent } from '@home/components/attribute/delet
EditWidgetComponent, EditWidgetComponent,
DashboardWidgetSelectComponent, DashboardWidgetSelectComponent,
AddWidgetDialogComponent, AddWidgetDialogComponent,
MoveWidgetsDialogComponent,
ManageDashboardLayoutsDialogComponent, ManageDashboardLayoutsDialogComponent,
DashboardSettingsDialogComponent, DashboardSettingsDialogComponent,
ManageDashboardStatesDialogComponent, ManageDashboardStatesDialogComponent,

View File

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

View File

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