UI: Improve dashboard layout settings.
This commit is contained in:
		
							parent
							
								
									9bd923a878
								
							
						
					
					
						commit
						8fad8245df
					
				
							
								
								
									
										44
									
								
								ui-ngx/patches/angular-gridster2+15.0.4.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								ui-ngx/patches/angular-gridster2+15.0.4.patch
									
									
									
									
									
										Normal 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;
 | 
			
		||||
@ -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) {
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -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 {
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
 | 
			
		||||
@ -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"
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
@ -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);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -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) {
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -52,6 +52,7 @@ export interface WidgetLayouts {
 | 
			
		||||
export interface GridSettings {
 | 
			
		||||
  backgroundColor?: string;
 | 
			
		||||
  columns?: number;
 | 
			
		||||
  minColumns?: number;
 | 
			
		||||
  margin?: number;
 | 
			
		||||
  outerMargin?: boolean;
 | 
			
		||||
  backgroundSizeMode?: string;
 | 
			
		||||
 | 
			
		||||
@ -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.",
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user