Browse Source

UI: Add manage dashboard layout and improved selector breakpoint

pull/11430/head
Vladyslav_Prykhodko 2 years ago
parent
commit
7cb15a0e00
  1. 73
      ui-ngx/src/app/core/services/dashboard-utils.service.ts
  2. 3
      ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html
  3. 8
      ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts
  4. 54
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html
  5. 21
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.scss
  6. 141
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts
  7. 5
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.models.ts
  8. 43
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.html
  9. 148
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.ts
  10. 13
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.scss
  11. 3
      ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.html
  12. 6
      ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.ts
  13. 14
      ui-ngx/src/app/modules/home/components/dashboard-page/layout/add-new-breakpoint-dialog.component.html
  14. 25
      ui-ngx/src/app/modules/home/components/dashboard-page/layout/add-new-breakpoint-dialog.component.ts
  15. 4
      ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html
  16. 52
      ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.ts
  17. 128
      ui-ngx/src/app/modules/home/components/dashboard-page/layout/manage-dashboard-layouts-dialog.component.html
  18. 3
      ui-ngx/src/app/modules/home/components/dashboard-page/layout/manage-dashboard-layouts-dialog.component.scss
  19. 81
      ui-ngx/src/app/modules/home/components/dashboard-page/layout/manage-dashboard-layouts-dialog.component.ts
  20. 5
      ui-ngx/src/app/modules/home/components/dashboard-page/layout/select-dashboard-breakpoint.component.html
  21. 21
      ui-ngx/src/app/modules/home/components/dashboard-page/layout/select-dashboard-breakpoint.component.ts
  22. 17
      ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts
  23. 3
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts
  24. 2
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts
  25. 2
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.ts
  26. 2
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts
  27. 2
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts
  28. 2
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.ts
  29. 2
      ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.ts
  30. 4
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
  31. 4
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.html
  32. 16
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts
  33. 1
      ui-ngx/src/app/shared/directives/truncate-with-tooltip.directive.ts
  34. 46
      ui-ngx/src/app/shared/models/dashboard.models.ts
  35. 3
      ui-ngx/src/app/shared/shared.module.ts
  36. 1
      ui-ngx/src/assets/locale/locale.constant-ar_AE.json
  37. 1
      ui-ngx/src/assets/locale/locale.constant-ca_ES.json
  38. 1
      ui-ngx/src/assets/locale/locale.constant-cs_CZ.json
  39. 23
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  40. 1
      ui-ngx/src/assets/locale/locale.constant-es_ES.json
  41. 1
      ui-ngx/src/assets/locale/locale.constant-fr_FR.json
  42. 1
      ui-ngx/src/assets/locale/locale.constant-lt_LT.json
  43. 1
      ui-ngx/src/assets/locale/locale.constant-nl_BE.json
  44. 1
      ui-ngx/src/assets/locale/locale.constant-pl_PL.json
  45. 1
      ui-ngx/src/assets/locale/locale.constant-tr_TR.json
  46. 1
      ui-ngx/src/assets/locale/locale.constant-zh_CN.json
  47. 1
      ui-ngx/src/assets/locale/locale.constant-zh_TW.json

73
ui-ngx/src/app/core/services/dashboard-utils.service.ts

@ -18,8 +18,11 @@ import { Injectable } from '@angular/core';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { TimeService } from '@core/services/time.service'; import { TimeService } from '@core/services/time.service';
import { import {
BreakpointId, breakpointIdIconMap,
breakpointIdTranslationMap,
BreakpointInfo, BreakpointInfo,
BreakpointLayoutInfo, BreakpointLayoutInfo,
BreakpointSystemId,
Dashboard, Dashboard,
DashboardConfiguration, DashboardConfiguration,
DashboardLayout, DashboardLayout,
@ -41,7 +44,8 @@ import {
TargetDeviceType, TargetDeviceType,
Widget, Widget,
WidgetConfig, WidgetConfig,
WidgetConfigMode, WidgetSize, WidgetConfigMode,
WidgetSize,
widgetType, widgetType,
WidgetTypeDescriptor WidgetTypeDescriptor
} from '@app/shared/models/widget.models'; } from '@app/shared/models/widget.models';
@ -53,16 +57,18 @@ import { AlarmSearchStatus } from '@shared/models/alarm.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { BackgroundType, colorBackground, isBackgroundSettings } from '@shared/models/widget-settings.models'; import { BackgroundType, colorBackground, isBackgroundSettings } from '@shared/models/widget-settings.models';
import { MediaBreakpoints } from '@shared/models/constants'; import { MediaBreakpoints } from '@shared/models/constants';
import { TranslateService } from '@ngx-translate/core';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class DashboardUtilsService { export class DashboardUtilsService {
private systemBreakpoints: BreakpointInfo[]; private systemBreakpoints: {[key in BreakpointSystemId]?: BreakpointInfo};
constructor(private utils: UtilsService, constructor(private utils: UtilsService,
private timeService: TimeService) { private timeService: TimeService,
private translate: TranslateService) {
} }
public validateAndUpdateDashboard(dashboard: Dashboard): Dashboard { public validateAndUpdateDashboard(dashboard: Dashboard): Dashboard {
@ -1001,13 +1007,18 @@ export class DashboardUtilsService {
} }
private loadSystemBreakpoints() { private loadSystemBreakpoints() {
this.systemBreakpoints=[{id: 'default'}]; this.systemBreakpoints = {};
const dashboardMediaBreakpointIds = ['xs', 'sm', 'md', 'lg', 'xl']; const dashboardMediaBreakpointIds: BreakpointSystemId[] = ['xs', 'sm', 'md', 'lg', 'xl'];
dashboardMediaBreakpointIds.forEach(breakpoint => { dashboardMediaBreakpointIds.forEach(breakpoint => {
const value = MediaBreakpoints[breakpoint]; const value = MediaBreakpoints[breakpoint];
const minWidth = value.match(/min-width:\s*(\d+)px/); const minWidth = value.match(/min-width:\s*(\d+)px/)?.[1];
const maxWidth = value.match(/max-width:\s*(\d+)px/); const maxWidth = value.match(/max-width:\s*(\d+)px/)?.[1];
this.systemBreakpoints.push({id: breakpoint, minWidth: minWidth?.[1], maxWidth: maxWidth?.[1]}); this.systemBreakpoints[breakpoint] = ({
id: breakpoint,
minWidth: minWidth ? Number(minWidth) : undefined,
maxWidth: maxWidth ? Number(maxWidth) : undefined,
value
});
}); });
} }
@ -1015,6 +1026,50 @@ export class DashboardUtilsService {
if(!this.systemBreakpoints) { if(!this.systemBreakpoints) {
this.loadSystemBreakpoints(); this.loadSystemBreakpoints();
} }
return this.systemBreakpoints; const breakpointsList = Object.values(this.systemBreakpoints);
breakpointsList.unshift({id: 'default'});
return breakpointsList;
}
getBreakpoints(): string[] {
if(!this.systemBreakpoints) {
this.loadSystemBreakpoints();
}
return Object.values(this.systemBreakpoints).map(item => item.value);
}
getBreakpointInfoByValue(breakpointValue: string): BreakpointInfo {
if(!this.systemBreakpoints) {
this.loadSystemBreakpoints();
}
return Object.values(this.systemBreakpoints).find(item => item.value === breakpointValue);
}
getBreakpointInfoById(breakpointId: BreakpointId): BreakpointInfo {
if(!this.systemBreakpoints) {
this.loadSystemBreakpoints();
}
return this.systemBreakpoints[breakpointId];
}
getBreakpointName(breakpointId: BreakpointId): string {
if (breakpointIdTranslationMap.has(breakpointId)) {
return this.translate.instant(breakpointIdTranslationMap.get(breakpointId));
}
return breakpointId;
}
getBreakpointIcon(breakpointId: BreakpointId): string {
if (breakpointIdIconMap.has(breakpointId)) {
return breakpointIdIconMap.get(breakpointId);
}
return 'desktop_windows';
}
getBreakpointSizeDescription(breakpointId: BreakpointId): string {
const currentData = this.getBreakpointInfoById(breakpointId);
const minStr = isDefined(currentData?.minWidth) ? `min ${currentData.minWidth}px` : '';
const maxStr = isDefined(currentData?.maxWidth) ? `max ${currentData.maxWidth}px` : '';
return minStr && maxStr ? `${minStr} - ${maxStr}` : `${minStr}${maxStr}`;
} }
} }

3
ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html

@ -44,7 +44,8 @@
[dashboard]="dashboard" [dashboard]="dashboard"
[widget]="widget" [widget]="widget"
[hideHeader]="hideHeader" [hideHeader]="hideHeader"
[scada]="data.scada" [showLayoutConfig]="showLayoutConfig"
[isDefaultBreakpoint]="isDefaultBreakpoint"
isAdd isAdd
formControlName="widgetConfig"> formControlName="widgetConfig">
</tb-widget-config> </tb-widget-config>

8
ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts

@ -37,7 +37,8 @@ export interface AddWidgetDialogData {
stateController: IStateController; stateController: IStateController;
widget: Widget; widget: Widget;
widgetInfo: WidgetInfo; widgetInfo: WidgetInfo;
scada: boolean; showLayoutConfig: boolean;
isDefaultBreakpoint: boolean;
} }
@Component({ @Component({
@ -60,6 +61,9 @@ export class AddWidgetDialogComponent extends DialogComponent<AddWidgetDialogCom
stateController: IStateController; stateController: IStateController;
widget: Widget; widget: Widget;
showLayoutConfig = true;
isDefaultBreakpoint = true;
widgetConfig: WidgetConfigComponentData; widgetConfig: WidgetConfigComponentData;
previewMode = false; previewMode = false;
@ -92,6 +96,8 @@ export class AddWidgetDialogComponent extends DialogComponent<AddWidgetDialogCom
this.aliasController = this.data.aliasController; this.aliasController = this.data.aliasController;
this.stateController = this.data.stateController; this.stateController = this.data.stateController;
this.widget = this.data.widget; this.widget = this.data.widget;
this.showLayoutConfig = this.data.showLayoutConfig;
this.isDefaultBreakpoint = this.data.isDefaultBreakpoint;
const widgetInfo = this.data.widgetInfo; const widgetInfo = this.data.widgetInfo;

54
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html

@ -29,22 +29,11 @@
<tb-user-menu *ngIf="!isPublicUser() && forceFullscreen" fxHide.gt-sm displayUserInfo="true"> <tb-user-menu *ngIf="!isPublicUser() && forceFullscreen" fxHide.gt-sm displayUserInfo="true">
</tb-user-menu> </tb-user-menu>
<ng-container *ngIf="isEdit"> <ng-container *ngIf="isEdit">
<div style="flex: 1 1 100%" fxHide.gt-sm></div>
<div fxFlex.lt-md fxLayout="row" <div fxFlex.lt-md fxLayout="row"
fxLayoutAlign.gt-sm="start center" fxLayoutAlign.gt-sm="start center"
fxLayoutAlign="end center" fxLayoutGap.lt-lg="6px" fxLayoutGap.gt-md="12px"> fxLayoutAlign="end center" fxLayoutGap.lt-lg="3px" fxLayoutGap.lg="6px" fxLayoutGap.gt-lg="12px">
<button fxHide.gt-md mat-icon-button <mat-divider class="tb-toolbar-divider" vertical></mat-divider>
matTooltip="{{'dashboard.manage-states' | translate}}"
matTooltipPosition="below"
(click)="manageDashboardStates($event)">
<mat-icon>layers</mat-icon>
</button>
<button fxHide.lt-lg mat-button
matTooltip="{{'dashboard.manage-states' | translate}}"
matTooltipPosition="below"
(click)="manageDashboardStates($event)">
<mat-icon>layers</mat-icon>
{{'dashboard.states-short' | translate}}
</button>
<button fxHide.gt-md mat-icon-button <button fxHide.gt-md mat-icon-button
matTooltip="{{'layout.manage' | translate}}" matTooltip="{{'layout.manage' | translate}}"
matTooltipPosition="below" matTooltipPosition="below"
@ -73,6 +62,21 @@
[syncStateWithQueryParam]="syncStateWithQueryParam" [syncStateWithQueryParam]="syncStateWithQueryParam"
[states]="dashboardConfiguration.states"> [states]="dashboardConfiguration.states">
</tb-states-component> </tb-states-component>
<ng-container *ngIf="isEdit">
<button fxHide.gt-md mat-icon-button
matTooltip="{{'dashboard.manage-states' | translate}}"
matTooltipPosition="below"
(click)="manageDashboardStates($event)">
<mat-icon>layers</mat-icon>
</button>
<button fxHide.lt-lg mat-button
matTooltip="{{'dashboard.manage-states' | translate}}"
matTooltipPosition="below"
(click)="manageDashboardStates($event)">
<mat-icon>layers</mat-icon>
{{'dashboard.states-short' | translate}}
</button>
</ng-container>
<img *ngIf="showDashboardLogo()" [src]="dashboardLogo | image | async" <img *ngIf="showDashboardLogo()" [src]="dashboardLogo | image | async"
aria-label="dashboard_logo" class="dashboard_logo"/> aria-label="dashboard_logo" class="dashboard_logo"/>
</div> </div>
@ -91,7 +95,7 @@
(click)="toggleLayouts()"> (click)="toggleLayouts()">
<mat-icon>{{isRightLayoutOpened ? 'arrow_back' : 'menu'}}</mat-icon> <mat-icon>{{isRightLayoutOpened ? 'arrow_back' : 'menu'}}</mat-icon>
</button> </button>
<button [fxShow]="!hideFullscreenButton()" mat-icon-button <button [fxShow]="!hideFullscreenButton()" mat-icon-button fxHide.xs
matTooltip="{{(isFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}" matTooltip="{{(isFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}"
matTooltipPosition="below" matTooltipPosition="below"
(click)="isFullscreen = !isFullscreen"> (click)="isFullscreen = !isFullscreen">
@ -233,13 +237,13 @@
</ng-container> </ng-container>
</ng-template> </ng-template>
<ng-container *ngIf="isEdit"> <ng-container *ngIf="isEdit">
<button fxHide.gt-xs mat-icon-button <button fxHide.gt-md mat-icon-button
matTooltip="{{ 'dashboard.add-widget-button-text' | translate }}" matTooltip="{{ 'dashboard.add-widget-button-text' | translate }}"
matTooltipPosition="below" matTooltipPosition="below"
(click)="addWidget($event)"> (click)="addWidget($event)">
<mat-icon>add</mat-icon> <mat-icon>add</mat-icon>
</button> </button>
<button fxHide.lt-sm mat-stroked-button <button fxHide.lt-lg mat-stroked-button
matTooltip="{{ 'dashboard.add-widget-button-text' | translate }}" matTooltip="{{ 'dashboard.add-widget-button-text' | translate }}"
matTooltipPosition="below" matTooltipPosition="below"
(click)="addWidget($event)"> (click)="addWidget($event)">
@ -285,8 +289,7 @@
</mat-form-field> </mat-form-field>
</section> </section>
<mat-drawer-container class="tb-dashboard-layouts" fxFlex <mat-drawer-container class="tb-dashboard-layouts" fxFlex
[class]="{'tb-shrinked' : isEditingWidget, [class]="{'tb-shrinked' : isEditingWidget}">
'diagonal-stripes': !layouts.right.show && layouts.main.layoutCtx.breakpoint !== 'default'}">
<mat-drawer *ngIf="layouts.right.show" <mat-drawer *ngIf="layouts.right.show"
id="tb-right-layout" id="tb-right-layout"
[ngStyle]="{minWidth: rightLayoutSize.width, [ngStyle]="{minWidth: rightLayoutSize.width,
@ -313,9 +316,10 @@
<mat-drawer-content [fxShow]="layouts.main.show" <mat-drawer-content [fxShow]="layouts.main.show"
id="tb-main-layout" id="tb-main-layout"
[style]="{width: mainLayoutSize.width, [style]="{width: mainLayoutSize.width,
height: mainLayoutSize.height, height: mainLayoutSize.height}">
maxWidth: mainLayoutSize.maxWidth}">
<tb-dashboard-layout <tb-dashboard-layout
[style.max-width]="mainLayoutSize.maxWidth"
[style.min-width]="mainLayoutSize.minWidth"
[dashboardCheatSheet]="cheatSheetComponent" [dashboardCheatSheet]="cheatSheetComponent"
[layoutCtx]="mobileDisplayRightLayoutFirst ? layouts.right.layoutCtx : layouts.main.layoutCtx" [layoutCtx]="mobileDisplayRightLayoutFirst ? layouts.right.layoutCtx : layouts.main.layoutCtx"
[dashboardCtx]="dashboardCtx" [dashboardCtx]="dashboardCtx"
@ -326,6 +330,11 @@
[parentDashboard]="parentDashboard" [parentDashboard]="parentDashboard"
[popoverComponent]="popoverComponent"> [popoverComponent]="popoverComponent">
</tb-dashboard-layout> </tb-dashboard-layout>
<div class="layout-filler"
*ngIf="isEdit"
[fxShow]="showMainLayoutFiller">
{{ 'dashboard.breakpoint-value' | translate : {value: currentBreakpointValue } }}
</div>
</mat-drawer-content> </mat-drawer-content>
</mat-drawer-container> </mat-drawer-container>
<section data-html2canvas-ignore fxLayout="row" class="layout-wrap tb-header-buttons tb-enter-edit-mode" fxLayoutAlign="start end" *ngIf="!readonly && (hideToolbar || widgetEditMode)"> <section data-html2canvas-ignore fxLayout="row" class="layout-wrap tb-header-buttons tb-enter-edit-mode" fxLayoutAlign="start end" *ngIf="!readonly && (hideToolbar || widgetEditMode)">
@ -385,7 +394,8 @@
[widgetEditMode]="widgetEditMode" [widgetEditMode]="widgetEditMode"
[widget]="editingWidget" [widget]="editingWidget"
[widgetLayout]="editingWidgetLayout" [widgetLayout]="editingWidgetLayout"
[scada]="editingLayoutCtx?.gridSettings?.layoutType === LayoutType.scada" [showLayoutConfig]="showLayoutConfigInEdit(editingLayoutCtx)"
[isDefaultBreakpoint]="editingLayoutCtx?.breakpoint === 'default'"
(revertWidgetConfig)="onRevertWidgetEdit()" (revertWidgetConfig)="onRevertWidgetEdit()"
(applyWidgetConfig)="saveWidget()"> (applyWidgetConfig)="saveWidget()">
</tb-edit-widget> </tb-edit-widget>

21
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.scss

@ -131,8 +131,25 @@ div.tb-dashboard-page {
width: max(40%, calc(100% - 1192px)); width: max(40%, calc(100% - 1192px));
} }
} }
&.diagonal-stripes { #tb-main-layout {
background: repeating-linear-gradient(45deg, #ccc, #ccc 10px, #fff0 10px, #fff0 20px); display: flex;
tb-dashboard-layout {
width: 100%;
}
.layout-filler {
flex: 1;
background: repeating-linear-gradient(-16deg, #d3d3d3, #d3d3d3 3px, #e0e0e0 3px, #e0e0e0 11px);
border-bottom: 2px dashed #bdbdbd;
border-right: 2px dashed #bdbdbd;
writing-mode: vertical-rl;
text-align: center;
transform: rotate(180deg);
color: #616161;
letter-spacing: .25px;
}
} }
} }

141
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts

@ -41,6 +41,8 @@ import { AppState } from '@core/core.state';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { import {
BreakpointId,
BreakpointInfo,
Dashboard, Dashboard,
DashboardConfiguration, DashboardConfiguration,
DashboardLayoutId, DashboardLayoutId,
@ -51,6 +53,7 @@ import {
GridSettings, GridSettings,
LayoutDimension, LayoutDimension,
LayoutType, LayoutType,
ViewFormatType,
WidgetLayout WidgetLayout
} from '@app/shared/models/dashboard.models'; } from '@app/shared/models/dashboard.models';
import { WINDOW } from '@core/services/window.service'; import { WINDOW } from '@core/services/window.service';
@ -92,7 +95,6 @@ import {
WidgetContextMenuItem WidgetContextMenuItem
} from '../../models/dashboard-component.models'; } from '../../models/dashboard-component.models';
import { WidgetComponentService } from '../../components/widget/widget-component.service'; import { WidgetComponentService } from '../../components/widget/widget-component.service';
import { UntypedFormBuilder } from '@angular/forms';
import { ItemBufferService } from '@core/services/item-buffer.service'; import { ItemBufferService } from '@core/services/item-buffer.service';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { import {
@ -261,7 +263,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
addingLayoutCtx: DashboardPageLayoutContext; addingLayoutCtx: DashboardPageLayoutContext;
mainLayoutSize: {width: string; height: string; maxWidth: string} = {width: '100%', height: '100%', maxWidth: '100%'}; mainLayoutSize: {width: string; height: string; maxWidth: string; minWidth: string} =
{width: '100%', height: '100%', maxWidth: '100%', minWidth: '100%'};
rightLayoutSize: {width: string; height: string} = {width: '100%', height: '100%'}; rightLayoutSize: {width: string; height: string} = {width: '100%', height: '100%'};
private dashboardLogoCache: SafeUrl; private dashboardLogoCache: SafeUrl;
@ -363,7 +366,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
private itembuffer: ItemBufferService, private itembuffer: ItemBufferService,
private importExport: ImportExportService, private importExport: ImportExportService,
private mobileService: MobileService, private mobileService: MobileService,
private fb: UntypedFormBuilder,
private dialog: MatDialog, private dialog: MatDialog,
public translate: TranslateService, public translate: TranslateService,
private popoverService: TbPopoverService, private popoverService: TbPopoverService,
@ -376,8 +378,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
public elRef: ElementRef, public elRef: ElementRef,
private injector: Injector) { private injector: Injector) {
super(store); super(store);
if (isDefinedAndNotNull(embeddedValue)) { if (isDefinedAndNotNull(this.embeddedValue)) {
this.embedded = embeddedValue; this.embedded = this.embeddedValue;
} }
} }
@ -427,40 +429,26 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
); );
this.rxSubscriptions.push( this.rxSubscriptions.push(
this.breakpointObserver.observe([ this.breakpointObserver.observe(
MediaBreakpoints.xs, this.dashboardUtils.getBreakpoints()
MediaBreakpoints.sm, ).pipe(
MediaBreakpoints.md, map(value => this.parseBreakpointsResponse(value.breakpoints)),
MediaBreakpoints.lg,
MediaBreakpoints.xl
]).pipe(
map(value => {
if (value.breakpoints[MediaBreakpoints.xs]) {
return 'xs';
} else if (value.breakpoints[MediaBreakpoints.sm]) {
return 'sm';
} else if (value.breakpoints[MediaBreakpoints.md]) {
return 'md';
} else if (value.breakpoints[MediaBreakpoints.lg]) {
return 'lg';
} else {
return 'xl';
}
}),
tap((value) => { tap((value) => {
this.dashboardCtx.breakpoint = value; this.dashboardCtx.breakpoint = value.id;
this.changeMobileSize.next(value === 'xs' || value === 'sm'); this.changeMobileSize.next(this.isMobileSize(value));
}), }),
distinctUntilChanged((prev, next) => { distinctUntilChanged((_, next) => {
if (this.layouts.right.show || this.isEdit) { if (this.layouts.right.show || this.isEdit) {
return true; return true;
} }
const allowAdditionalPrevLayout = !!this.layouts.main.layoutCtx.layoutData?.[prev]; let nextBreakpointConfiguration: BreakpointId = 'default';
const allowAdditionalNextLayout = !!this.layouts.main.layoutCtx?.layoutData?.[next]; if (!!this.layouts.main.layoutCtx.layoutData?.[next.id]) {
return !(allowAdditionalNextLayout || allowAdditionalPrevLayout !== allowAdditionalNextLayout); nextBreakpointConfiguration = next.id;
}
return this.layouts.main.layoutCtx.breakpoint === nextBreakpointConfiguration;
}), }),
skip(1) skip(1)
).subscribe((value) => { ).subscribe(() => {
this.layouts.main.layoutCtx.ctrl.updatedCurrentBreakpoint(); this.layouts.main.layoutCtx.ctrl.updatedCurrentBreakpoint();
this.updateLayoutSizes(); this.updateLayoutSizes();
} }
@ -639,7 +627,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
} }
public hideFullscreenButton(): boolean { public hideFullscreenButton(): boolean {
return (this.widgetEditMode || this.iframeMode || this.forceFullscreen || this.singlePageMode || this.isEdit); return (this.widgetEditMode || this.iframeMode || this.forceFullscreen || this.singlePageMode);
} }
public toolbarAlwaysOpen(): boolean { public toolbarAlwaysOpen(): boolean {
@ -780,33 +768,28 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
} else { } else {
this.mainLayoutSize.height = '0px'; this.mainLayoutSize.height = '0px';
} }
if (this.isEdit) { if (this.isEdit && !this.isEditingWidget) {
const xOffset = this.dashboardContainer.nativeElement.getBoundingClientRect().x; const xOffset = this.dashboardContainer.nativeElement.getBoundingClientRect().x;
let width; const breakpoint = this.dashboardUtils.getBreakpointInfoById(this.layouts.main.layoutCtx.breakpoint);
switch (this.layouts.main.layoutCtx.breakpoint) { let maxWidth: string;
case 'xl': if (breakpoint?.maxWidth) {
width = `${5000 - xOffset}px`; if (this.isMobileSize(breakpoint)) {
break; maxWidth = `${breakpoint.maxWidth}px`;
case 'lg': } else {
width = `${1919 - xOffset}px`; maxWidth = `${breakpoint.maxWidth - xOffset}px`;
break; }
case 'md': } else {
width = `${1279 - xOffset}px`; maxWidth = '100%';
break;
case 'sm':
width = '959px';
break;
case 'xs':
width = '599px';
break;
default:
width = '100%';
} }
this.mainLayoutSize.maxWidth = width; const minWidth = breakpoint?.minWidth ? `${breakpoint.minWidth}px` : undefined;
this.mainLayoutSize.maxWidth = maxWidth;
this.mainLayoutSize.minWidth = minWidth;
} else { } else {
this.mainLayoutSize.maxWidth = '100%'; this.mainLayoutSize.maxWidth = '100%';
this.mainLayoutSize.minWidth = undefined;
} }
return prevMainLayoutWidth !== this.mainLayoutSize.width || prevMainLayoutHeight !== this.mainLayoutSize.height || return prevMainLayoutWidth !== this.mainLayoutSize.width || prevMainLayoutHeight !== this.mainLayoutSize.height ||
prevMainLayoutMaxWidth !== this.mainLayoutSize.maxWidth; prevMainLayoutMaxWidth !== this.mainLayoutSize.maxWidth;
@ -945,6 +928,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
data: { data: {
settings: deepClone(this.dashboard.configuration.settings), settings: deepClone(this.dashboard.configuration.settings),
gridSettings, gridSettings,
breakpointId: this.layouts.main.layoutCtx.breakpoint
} }
}).afterClosed().subscribe((data) => { }).afterClosed().subscribe((data) => {
if (data) { if (data) {
@ -1327,10 +1311,20 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
col: 0 col: 0
}; };
newWidget = this.dashboardUtils.validateAndUpdateWidget(newWidget); newWidget = this.dashboardUtils.validateAndUpdateWidget(newWidget);
let isDefaultBreakpoint = true;
if (this.addingLayoutCtx?.breakpoint) {
isDefaultBreakpoint = this.addingLayoutCtx.breakpoint === 'default';
} else if (!this.layouts.right.show) {
isDefaultBreakpoint = this.layouts.main.layoutCtx.breakpoint === 'default';
}
const scada = this.isAddingToScadaLayout(); const scada = this.isAddingToScadaLayout();
if (scada) { if (scada) {
newWidget = this.dashboardUtils.prepareWidgetForScadaLayout(newWidget); newWidget = this.dashboardUtils.prepareWidgetForScadaLayout(newWidget);
} }
let showLayoutConfig = true;
if (scada || this.layouts.right.show || !this.showLayoutConfigInEdit(this.layouts.main.layoutCtx)) {
showLayoutConfig = false;
}
if (widgetTypeInfo.typeParameters.useCustomDatasources) { if (widgetTypeInfo.typeParameters.useCustomDatasources) {
this.addWidgetToDashboard(newWidget); this.addWidgetToDashboard(newWidget);
} else { } else {
@ -1346,7 +1340,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
stateController: this.dashboardCtx.stateController, stateController: this.dashboardCtx.stateController,
widget: newWidget, widget: newWidget,
widgetInfo: widgetTypeInfo, widgetInfo: widgetTypeInfo,
scada showLayoutConfig,
isDefaultBreakpoint
} }
}).afterClosed().subscribe((addedWidget) => { }).afterClosed().subscribe((addedWidget) => {
if (addedWidget) { if (addedWidget) {
@ -1425,6 +1420,13 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
} }
} }
showLayoutConfigInEdit(layoutCtx: DashboardPageLayoutContext): boolean {
return layoutCtx?.gridSettings?.layoutType === LayoutType.divider ||
layoutCtx?.gridSettings?.layoutType === LayoutType.default &&
(layoutCtx?.breakpoint === 'default' ||
layoutCtx?.breakpoint !== 'default' && layoutCtx?.gridSettings?.viewFormat === ViewFormatType.list);
}
replaceReferenceWithWidgetCopy($event: Event, layoutCtx: DashboardPageLayoutContext, widget: Widget) { replaceReferenceWithWidgetCopy($event: Event, layoutCtx: DashboardPageLayoutContext, widget: Widget) {
$event.stopPropagation(); $event.stopPropagation();
@ -1749,4 +1751,31 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
}); });
} }
} }
get showMainLayoutFiller(): boolean {
const layoutMaxWidth = this.dashboardUtils.getBreakpointInfoById(this.layouts.main.layoutCtx.breakpoint)?.maxWidth || Infinity;
const dashboardMaxWidth = this.dashboardUtils.getBreakpointInfoById(this.dashboardCtx.breakpoint)?.maxWidth || Infinity;
return !this.layouts.right.show && layoutMaxWidth < dashboardMaxWidth && !this.isEditingWidget;
}
get currentBreakpointValue(): string {
return this.dashboardUtils.getBreakpointSizeDescription(this.layouts.main.layoutCtx.breakpoint);
}
private parseBreakpointsResponse(breakpoints: {[key: string]: boolean}): BreakpointInfo {
const activeBreakpoints: BreakpointInfo[] = [];
Object.keys(breakpoints).map((key) => {
if (breakpoints[key]) {
activeBreakpoints.push(this.dashboardUtils.getBreakpointInfoByValue(key));
}
});
return activeBreakpoints.pop();
}
private isMobileSize(breakpoint: BreakpointInfo): boolean {
if (breakpoint?.maxWidth) {
return breakpoint.maxWidth < 960;
}
return false;
}
} }

5
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.models.ts

@ -15,6 +15,7 @@
/// ///
import { import {
BreakpointId,
Dashboard, Dashboard,
DashboardLayoutId, DashboardLayoutId,
DashboardLayoutInfo, DashboardLayoutInfo,
@ -41,7 +42,7 @@ export interface DashboardPageInitData {
export interface DashboardContext { export interface DashboardContext {
instanceId: string; instanceId: string;
state: string; state: string;
breakpoint: string; breakpoint: BreakpointId;
getDashboard: () => Dashboard; getDashboard: () => Dashboard;
dashboardTimewindow: Timewindow; dashboardTimewindow: Timewindow;
aliasController: IAliasController; aliasController: IAliasController;
@ -75,7 +76,7 @@ export interface DashboardPageLayoutContext {
id: DashboardLayoutId; id: DashboardLayoutId;
layoutData: DashboardLayoutInfo; layoutData: DashboardLayoutInfo;
layoutDataChanged: BehaviorSubject<void>; layoutDataChanged: BehaviorSubject<void>;
breakpoint: string; breakpoint: BreakpointId;
widgets: LayoutWidgetsArray; widgets: LayoutWidgetsArray;
widgetLayouts: WidgetLayouts; widgetLayouts: WidgetLayouts;
gridSettings: GridSettings; gridSettings: GridSettings;

43
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.html

@ -105,18 +105,18 @@
</mat-expansion-panel> </mat-expansion-panel>
</div> </div>
<div *ngIf="gridSettings" class="tb-form-panel no-border no-padding" [formGroup]="gridSettingsFormGroup"> <div *ngIf="gridSettings" class="tb-form-panel no-border no-padding" [formGroup]="gridSettingsFormGroup">
<div class="tb-form-row space-between">
<div class="fixed-title-width" translate>dashboard.layout</div>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="layoutType">
<mat-option *ngFor="let type of layoutTypes" [value]="type">
{{ layoutTypeTranslations.get(type) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="tb-form-panel stroked"> <div class="tb-form-panel stroked">
<div class="tb-form-panel-title" translate>dashboard.layout-settings</div> <div class="tb-form-panel-title" translate [translateParams]="{type: layoutSettingsType}">dashboard.layout-settings-type</div>
<div *ngIf="isDefaultLayout && !isDefaultBreakpoint" class="tb-form-row space-between">
<div class="fixed-title-width" translate>dashboard.view-format</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="viewFormat">
<mat-option *ngFor="let type of viewFormatTypes" [value]="type">
{{ viewFormatTypeTranslationMap.get(type) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</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 *ngIf="!isScada()" <mat-form-field *ngIf="!isScada()"
@ -188,6 +188,25 @@
{{ 'dashboard.autofill-height' | translate }} {{ 'dashboard.autofill-height' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<div *ngIf="isDefaultLayout && !isDefaultBreakpoint" class="tb-form-row space-between">
<div translate>dashboard.row-height</div>
<mat-form-field appearance="outline" class="number medium-width tb-suffix-absolute" subscriptSizing="dynamic">
<input matInput formControlName="rowHeight" type="number" min="5" max="200" step="any"
required
placeholder="{{ 'widget-config.set' | translate }}">
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="(gridSettingsFormGroup.get('rowHeight').hasError('required') ? 'dashboard.row-height-required' :
(gridSettingsFormGroup.get('rowHeight').hasError('min') ?
'dashboard.min-row-height-message' : 'dashboard.max-row-height-message')) | translate"
*ngIf="gridSettingsFormGroup.get('rowHeight').invalid && gridSettingsFormGroup.get('rowHeight').touched"
class="tb-error">
warning
</mat-icon>
<span matSuffix>px</span>
</mat-form-field>
</div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div translate>dashboard.background-color</div> <div translate>dashboard.background-color</div>
<tb-color-input asBoxInput <tb-color-input asBoxInput
@ -212,7 +231,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
</div> </div>
<div *ngIf="!isScada() || isRightLayout" class="tb-form-panel stroked"> <div *ngIf="!isScada() && isDefaultBreakpoint" class="tb-form-panel stroked">
<div class="tb-form-panel-title" translate>dashboard.mobile-layout</div> <div class="tb-form-panel-title" translate>dashboard.mobile-layout</div>
<div *ngIf="isRightLayout" class="tb-form-row"> <div *ngIf="isRightLayout" class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="mobileDisplayLayoutFirst"> <mat-slide-toggle class="mat-slide" formControlName="mobileDisplayLayoutFirst">

148
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.ts

@ -14,7 +14,7 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; import { Component, Inject, OnDestroy, OnInit, SkipSelf } from '@angular/core';
import { ErrorStateMatcher } from '@angular/material/core'; import { ErrorStateMatcher } from '@angular/material/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
@ -24,20 +24,26 @@ import { Router } from '@angular/router';
import { DialogComponent } from '@app/shared/components/dialog.component'; import { DialogComponent } from '@app/shared/components/dialog.component';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { import {
BreakpointId,
DashboardSettings, DashboardSettings,
GridSettings, GridSettings,
LayoutType, LayoutType,
layoutTypes, layoutTypeTranslationMap, StateControllerId,
StateControllerId ViewFormatType,
viewFormatTypes,
viewFormatTypeTranslationMap
} from '@app/shared/models/dashboard.models'; } from '@app/shared/models/dashboard.models';
import { isDefined, isUndefined } from '@core/utils'; import { isDefined, isUndefined } from '@core/utils';
import { StatesControllerService } from './states/states-controller.service'; import { StatesControllerService } from './states/states-controller.service';
import { merge } from 'rxjs'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
import { merge, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
export interface DashboardSettingsDialogData { export interface DashboardSettingsDialogData {
settings?: DashboardSettings; settings?: DashboardSettings;
gridSettings?: GridSettings; gridSettings?: GridSettings;
isRightLayout?: boolean; isRightLayout?: boolean;
breakpointId?: BreakpointId;
} }
@Component({ @Component({
@ -47,11 +53,10 @@ export interface DashboardSettingsDialogData {
styleUrls: ['./dashboard-settings-dialog.component.scss'] styleUrls: ['./dashboard-settings-dialog.component.scss']
}) })
export class DashboardSettingsDialogComponent extends DialogComponent<DashboardSettingsDialogComponent, DashboardSettingsDialogData> export class DashboardSettingsDialogComponent extends DialogComponent<DashboardSettingsDialogComponent, DashboardSettingsDialogData>
implements OnInit, ErrorStateMatcher { implements OnDestroy, ErrorStateMatcher {
layoutTypes = layoutTypes; viewFormatTypes = viewFormatTypes;
viewFormatTypeTranslationMap = viewFormatTypeTranslationMap;
layoutTypeTranslations = layoutTypeTranslationMap;
settings: DashboardSettings; settings: DashboardSettings;
gridSettings: GridSettings; gridSettings: GridSettings;
@ -62,10 +67,17 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
stateControllerIds: string[]; stateControllerIds: string[];
layoutSettingsType: string;
submitted = false; submitted = false;
scadaColumns: number[] = []; scadaColumns: number[] = [];
private layoutType = LayoutType.default;
private breakpointId: BreakpointId = 'default';
private destroy$ = new Subject<void>();
private stateControllerTranslationMap = new Map<string, string>([ private stateControllerTranslationMap = new Map<string, string>([
['default', 'dashboard.state-controller-default'], ['default', 'dashboard.state-controller-default'],
]); ]);
@ -77,7 +89,8 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
public dialogRef: MatDialogRef<DashboardSettingsDialogComponent, DashboardSettingsDialogData>, public dialogRef: MatDialogRef<DashboardSettingsDialogComponent, DashboardSettingsDialogData>,
private fb: FormBuilder, private fb: FormBuilder,
private translate: TranslateService, private translate: TranslateService,
private statesControllerService: StatesControllerService) { private statesControllerService: StatesControllerService,
private dashboardUtils: DashboardUtilsService) {
super(store, router, dialogRef); super(store, router, dialogRef);
this.stateControllerIds = Object.keys(this.statesControllerService.getStateControllers()); this.stateControllerIds = Object.keys(this.statesControllerService.getStateControllers());
@ -85,6 +98,7 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
this.settings = this.data.settings; this.settings = this.data.settings;
this.gridSettings = this.data.gridSettings; this.gridSettings = this.data.gridSettings;
this.isRightLayout = this.data.isRightLayout; this.isRightLayout = this.data.isRightLayout;
this.breakpointId = this.data.breakpointId;
if (this.settings) { if (this.settings) {
const showTitle = isUndefined(this.settings.showTitle) ? true : this.settings.showTitle; const showTitle = isUndefined(this.settings.showTitle) ? true : this.settings.showTitle;
@ -116,14 +130,18 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
disabled: hideToolbar}, []], disabled: hideToolbar}, []],
dashboardCss: [isUndefined(this.settings.dashboardCss) ? '' : this.settings.dashboardCss, []], dashboardCss: [isUndefined(this.settings.dashboardCss) ? '' : this.settings.dashboardCss, []],
}); });
this.settingsFormGroup.get('stateControllerId').valueChanges.subscribe( this.settingsFormGroup.get('stateControllerId').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(
(stateControllerId: StateControllerId) => { (stateControllerId: StateControllerId) => {
if (stateControllerId !== 'default') { if (stateControllerId !== 'default') {
this.settingsFormGroup.get('toolbarAlwaysOpen').setValue(true); this.settingsFormGroup.get('toolbarAlwaysOpen').setValue(true);
} }
} }
); );
this.settingsFormGroup.get('showTitle').valueChanges.subscribe( this.settingsFormGroup.get('showTitle').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(
(showTitleValue: boolean) => { (showTitleValue: boolean) => {
if (showTitleValue) { if (showTitleValue) {
this.settingsFormGroup.get('titleColor').enable(); this.settingsFormGroup.get('titleColor').enable();
@ -132,7 +150,9 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
} }
} }
); );
this.settingsFormGroup.get('showDashboardLogo').valueChanges.subscribe( this.settingsFormGroup.get('showDashboardLogo').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(
(showDashboardLogoValue: boolean) => { (showDashboardLogoValue: boolean) => {
if (showDashboardLogoValue) { if (showDashboardLogoValue) {
this.settingsFormGroup.get('dashboardLogoUrl').enable(); this.settingsFormGroup.get('dashboardLogoUrl').enable();
@ -141,7 +161,9 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
} }
} }
); );
this.settingsFormGroup.get('hideToolbar').valueChanges.subscribe( this.settingsFormGroup.get('hideToolbar').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(
(hideToolbarValue: boolean) => { (hideToolbarValue: boolean) => {
if (hideToolbarValue) { if (hideToolbarValue) {
this.settingsFormGroup.get('toolbarAlwaysOpen').disable(); this.settingsFormGroup.get('toolbarAlwaysOpen').disable();
@ -167,6 +189,16 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
} }
if (this.gridSettings) { if (this.gridSettings) {
if (isDefined(this.gridSettings.layoutType)) {
this.layoutType = this.gridSettings.layoutType;
}
if (this.layoutType !== LayoutType.divider) {
this.layoutSettingsType = this.dashboardUtils.getBreakpointName(this.breakpointId);
} else if (this.isRightLayout) {
this.layoutSettingsType = this.translate.instant('layout.right');
} else {
this.layoutSettingsType = this.translate.instant('layout.left');
}
let columns = 24; let columns = 24;
while (columns <= 1008) { while (columns <= 1008) {
this.scadaColumns.push(columns); this.scadaColumns.push(columns);
@ -174,14 +206,16 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
} }
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({
layoutType: [this.gridSettings.layoutType || LayoutType.default, []],
columns: [this.gridSettings.columns || 24, [Validators.required, Validators.min(10), Validators.max(1008)]], columns: [this.gridSettings.columns || 24, [Validators.required, Validators.min(10), Validators.max(1008)]],
minColumns: [this.gridSettings.minColumns || this.gridSettings.columns || 24, minColumns: [this.gridSettings.minColumns || this.gridSettings.columns || 24,
[Validators.required, Validators.min(10), Validators.max(1008)]], [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, []],
viewFormat: [isUndefined(this.gridSettings.viewFormat) ? ViewFormatType.grid : this.gridSettings.viewFormat, []],
autoFillHeight: [isUndefined(this.gridSettings.autoFillHeight) ? false : this.gridSettings.autoFillHeight, []], autoFillHeight: [isUndefined(this.gridSettings.autoFillHeight) ? false : this.gridSettings.autoFillHeight, []],
rowHeight: [isUndefined(this.gridSettings.rowHeight) ? 70 : this.gridSettings.rowHeight,
[Validators.required, Validators.min(5), Validators.max(200)]],
backgroundColor: [this.gridSettings.backgroundColor || 'rgba(0,0,0,0)', []], backgroundColor: [this.gridSettings.backgroundColor || 'rgba(0,0,0,0)', []],
backgroundImageUrl: [this.gridSettings.backgroundImageUrl, []], backgroundImageUrl: [this.gridSettings.backgroundImageUrl, []],
backgroundSizeMode: [this.gridSettings.backgroundSizeMode || '100%', []], backgroundSizeMode: [this.gridSettings.backgroundSizeMode || '100%', []],
@ -194,27 +228,56 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
isUndefined(this.gridSettings.mobileDisplayLayoutFirst) ? false : this.gridSettings.mobileDisplayLayoutFirst; isUndefined(this.gridSettings.mobileDisplayLayoutFirst) ? false : this.gridSettings.mobileDisplayLayoutFirst;
this.gridSettingsFormGroup.addControl('mobileDisplayLayoutFirst', this.fb.control(mobileDisplayLayoutFirst, [])); this.gridSettingsFormGroup.addControl('mobileDisplayLayoutFirst', this.fb.control(mobileDisplayLayoutFirst, []));
} }
merge(this.gridSettingsFormGroup.get('layoutType').valueChanges, if (this.isScada()) {
this.gridSettingsFormGroup.get('mobileAutoFillHeight').valueChanges).subscribe( this.gridSettingsFormGroup.get('margin').disable();
() => { this.gridSettingsFormGroup.get('outerMargin').disable();
this.updateGridSettingsFormState(); this.gridSettingsFormGroup.get('viewFormat').disable({emitEvent: false});
this.gridSettingsFormGroup.get('rowHeight').disable();
this.gridSettingsFormGroup.get('autoFillHeight').disable({emitEvent: false});
this.gridSettingsFormGroup.get('mobileAutoFillHeight').disable({emitEvent: false});
this.gridSettingsFormGroup.get('mobileRowHeight').disable();
const columnsFields: number = this.gridSettingsFormGroup.get('columns').value;
if (columnsFields % 24 !== 0) {
const newColumns = Math.min(1008, 24 * Math.ceil(columnsFields / 24));
this.gridSettingsFormGroup.get('columns').patchValue(newColumns);
} }
); } else {
this.updateGridSettingsFormState(); merge(
this.gridSettingsFormGroup.get('viewFormat').valueChanges,
this.breakpointId !== 'default'
? this.gridSettingsFormGroup.get('autoFillHeight').valueChanges
: this.gridSettingsFormGroup.get('mobileAutoFillHeight').valueChanges
).pipe(
takeUntil(this.destroy$)
).subscribe(() => {
this.updateGridSettingsFormState();
});
this.updateGridSettingsFormState();
}
if (this.layoutType === LayoutType.default && this.breakpointId !== 'default' || this.isScada()) {
this.gridSettingsFormGroup.get('mobileAutoFillHeight').disable({emitEvent: false});
this.gridSettingsFormGroup.get('mobileRowHeight').disable();
}
} else { } else {
this.gridSettingsFormGroup = this.fb.group({}); this.gridSettingsFormGroup = this.fb.group({});
} }
} }
ngOnInit(): void { ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
} }
isScada() { isScada() {
if (this.gridSettingsFormGroup) { return this.layoutType === LayoutType.scada;
return this.gridSettingsFormGroup.get('layoutType').value === LayoutType.scada; }
} else {
return false; get isDefaultLayout() {
} return this.layoutType === LayoutType.default;
}
get isDefaultBreakpoint(): boolean {
return this.breakpointId === 'default';
} }
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
@ -248,29 +311,28 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
} }
private updateGridSettingsFormState() { private updateGridSettingsFormState() {
const isScada: boolean = this.gridSettingsFormGroup.get('layoutType').value === LayoutType.scada; if (this.breakpointId === 'default') {
if (isScada) {
this.gridSettingsFormGroup.get('margin').disable();
this.gridSettingsFormGroup.get('outerMargin').disable();
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();
this.gridSettingsFormGroup.get('autoFillHeight').enable();
this.gridSettingsFormGroup.get('mobileAutoFillHeight').enable({emitEvent: false});
const mobileAutoFillHeight: boolean = this.gridSettingsFormGroup.get('mobileAutoFillHeight').value; const mobileAutoFillHeight: boolean = this.gridSettingsFormGroup.get('mobileAutoFillHeight').value;
if (mobileAutoFillHeight) { if (mobileAutoFillHeight) {
this.gridSettingsFormGroup.get('mobileRowHeight').disable(); this.gridSettingsFormGroup.get('mobileRowHeight').disable();
} else { } else {
this.gridSettingsFormGroup.get('mobileRowHeight').enable(); this.gridSettingsFormGroup.get('mobileRowHeight').enable();
} }
} else {
const autoFillHeight: boolean = this.gridSettingsFormGroup.get('autoFillHeight').value;
const viewFormat: ViewFormatType = this.gridSettingsFormGroup.get('viewFormat').value;
if (viewFormat !== ViewFormatType.list || autoFillHeight) {
this.gridSettingsFormGroup.get('rowHeight').disable();
} else {
this.gridSettingsFormGroup.get('rowHeight').enable();
}
if (viewFormat === ViewFormatType.list) {
this.gridSettingsFormGroup.get('columns').disable();
this.gridSettingsFormGroup.get('minColumns').disable();
} else {
this.gridSettingsFormGroup.get('columns').enable();
this.gridSettingsFormGroup.get('minColumns').enable();
}
} }
} }
} }

13
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.scss

@ -174,14 +174,23 @@ tb-dashboard-toolbar {
@media #{$mat-sm} { @media #{$mat-sm} {
gap: 6px; gap: 6px;
&.tb-left-panel {
gap: 3px;
}
} }
@media #{$mat-md} { @media #{$mat-md} {
gap: 6px; gap: 3px;
&.tb-left-panel {
gap: 1px;
}
} }
@media #{$mat-gt-md} { @media #{$mat-gt-md} {
gap: 12px; gap: 6px;
&.tb-left-panel {
gap: 3px;
}
} }
@media #{$mat-gt-sm} { @media #{$mat-gt-sm} {

3
ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.html

@ -24,7 +24,8 @@
[dashboard]="dashboard" [dashboard]="dashboard"
[widget]="widget" [widget]="widget"
[hideToggleHeader]="previewMode" [hideToggleHeader]="previewMode"
[scada]="scada" [showLayoutConfig]="showLayoutConfig"
[isDefaultBreakpoint]="isDefaultBreakpoint"
formControlName="widgetConfig"> formControlName="widgetConfig">
<div class="tb-widget-config-header-prefix"> <div class="tb-widget-config-header-prefix">
<button mat-button <button mat-button

6
ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.ts

@ -60,7 +60,11 @@ export class EditWidgetComponent extends PageComponent implements OnInit, OnChan
@Input() @Input()
@coerceBoolean() @coerceBoolean()
scada = false; showLayoutConfig = true;
@Input()
@coerceBoolean()
isDefaultBreakpoint= true;
@Output() @Output()
applyWidgetConfig = new EventEmitter<void>(); applyWidgetConfig = new EventEmitter<void>();

14
ui-ngx/src/app/modules/home/components/dashboard-page/layout/add-new-breakpoint-dialog.component.html

@ -28,17 +28,23 @@
<form [formGroup]="addBreakpointFormGroup"> <form [formGroup]="addBreakpointFormGroup">
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label translate>layout.breakpoint</mat-label> <mat-label translate>layout.breakpoint</mat-label>
<mat-select formControlName="newBreakpointId"> <mat-select formControlName="newBreakpointId" [hideSingleSelectionIndicator]="false">
<mat-select-trigger>{{ getName(this.addBreakpointFormGroup.get('newBreakpointId').value) }}</mat-select-trigger>
<mat-option *ngFor="let breakpoint of allowBreakpointIds" [value]="breakpoint"> <mat-option *ngFor="let breakpoint of allowBreakpointIds" [value]="breakpoint">
{{ breakpoint }} <mat-icon><tb-icon>{{ getIcon(breakpoint) }}</tb-icon></mat-icon>
<div>{{ getName(breakpoint) }}</div>
<div style="font-size: 12px;letter-spacing: 0.4px;color: #9e9e9e;">{{ getSizeDescription(breakpoint) }}</div>
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field appearance="outline" subscriptSizing="dynamic"> <mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-label translate>layout.copy-from</mat-label> <mat-label translate>layout.copy-from</mat-label>
<mat-select formControlName="copyFrom"> <mat-select formControlName="copyFrom" [hideSingleSelectionIndicator]="false">
<mat-select-trigger>{{ getName(this.addBreakpointFormGroup.get('copyFrom').value) }}</mat-select-trigger>
<mat-option *ngFor="let breakpoint of selectedBreakpointIds" [value]="breakpoint"> <mat-option *ngFor="let breakpoint of selectedBreakpointIds" [value]="breakpoint">
{{ breakpoint }} <mat-icon><tb-icon>{{ getIcon(breakpoint) }}</tb-icon></mat-icon>
<div>{{ getName(breakpoint) }}</div>
<div style="font-size: 12px;letter-spacing: 0.4px;color: #9e9e9e;">{{ getSizeDescription(breakpoint) }}</div>
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>

25
ui-ngx/src/app/modules/home/components/dashboard-page/layout/add-new-breakpoint-dialog.component.ts

@ -21,6 +21,8 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
import { BreakpointId } from '@shared/models/dashboard.models';
export interface AddNewBreakpointDialogData { export interface AddNewBreakpointDialogData {
allowBreakpointIds: string[]; allowBreakpointIds: string[];
@ -28,8 +30,8 @@ export interface AddNewBreakpointDialogData {
} }
export interface AddNewBreakpointDialogResult { export interface AddNewBreakpointDialogResult {
newBreakpointId: string; newBreakpointId: BreakpointId;
copyFrom: string; copyFrom: BreakpointId;
} }
@Component({ @Component({
@ -47,12 +49,13 @@ export class AddNewBreakpointDialogComponent extends DialogComponent<AddNewBreak
protected router: Router, protected router: Router,
private fb: FormBuilder, private fb: FormBuilder,
@Inject(MAT_DIALOG_DATA) private data: AddNewBreakpointDialogData, @Inject(MAT_DIALOG_DATA) private data: AddNewBreakpointDialogData,
protected dialogRef: MatDialogRef<AddNewBreakpointDialogComponent, AddNewBreakpointDialogResult>,) { protected dialogRef: MatDialogRef<AddNewBreakpointDialogComponent, AddNewBreakpointDialogResult>,
private dashboardUtils: DashboardUtilsService,) {
super(store, router, dialogRef); super(store, router, dialogRef);
this.allowBreakpointIds = data.allowBreakpointIds; this.allowBreakpointIds = this.data.allowBreakpointIds;
this.selectedBreakpointIds = data.selectedBreakpointIds; this.selectedBreakpointIds = this.data.selectedBreakpointIds;
this.addBreakpointFormGroup = this.fb.group({ this.addBreakpointFormGroup = this.fb.group({
newBreakpointId: [{value: this.allowBreakpointIds[0], disabled: this.allowBreakpointIds.length === 1}], newBreakpointId: [{value: this.allowBreakpointIds[0], disabled: this.allowBreakpointIds.length === 1}],
@ -67,4 +70,16 @@ export class AddNewBreakpointDialogComponent extends DialogComponent<AddNewBreak
save(): void { save(): void {
this.dialogRef.close(this.addBreakpointFormGroup.getRawValue()); this.dialogRef.close(this.addBreakpointFormGroup.getRawValue());
} }
getName(breakpointId: BreakpointId): string {
return this.dashboardUtils.getBreakpointName(breakpointId);
}
getIcon(breakpointId: BreakpointId): string {
return this.dashboardUtils.getBreakpointIcon(breakpointId);
}
getSizeDescription(breakpointId: BreakpointId): string {
return this.dashboardUtils.getBreakpointSizeDescription(breakpointId);
}
} }

4
ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html

@ -53,8 +53,8 @@
[isEditingWidget]="isEditingWidget" [isEditingWidget]="isEditingWidget"
[autofillHeight]="autoFillHeight" [autofillHeight]="autoFillHeight"
[mobileAutofillHeight]="mobileAutoFillHeight" [mobileAutofillHeight]="mobileAutoFillHeight"
[mobileRowHeight]="layoutCtx.gridSettings.mobileRowHeight" [mobileRowHeight]="mobielRowHeigth"
[isMobile]="isMobile" [isMobile]="isMobileValue"
[isMobileDisabled]="isMobileDisabled" [isMobileDisabled]="isMobileDisabled"
[disableWidgetInteraction]="isEdit" [disableWidgetInteraction]="isEdit"
[isEditActionEnabled]="isEdit && !isEditingWidget" [isEditActionEnabled]="isEdit && !isEditingWidget"

52
ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.ts

@ -37,9 +37,8 @@ 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'; import { displayGrids } from 'angular-gridster2/lib/gridsterConfig.interface';
import { LayoutType } from '@shared/models/dashboard.models'; import { BreakpointId, LayoutType, ViewFormatType } from '@shared/models/dashboard.models';
import { deepClone, isNotEmptyStr } from '@core/utils'; import { isNotEmptyStr } from '@core/utils';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
@Component({ @Component({
selector: 'tb-dashboard-layout', selector: 'tb-dashboard-layout',
@ -87,11 +86,27 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo
} }
get mobileAutoFillHeight(): boolean { get mobileAutoFillHeight(): boolean {
return (this.isEdit || this.isScada) ? false : this.layoutCtx.gridSettings.mobileAutoFillHeight; if (this.isEdit || this.isScada) {
return false;
} else if (this.layoutCtx.breakpoint !== 'default' && this.layoutCtx.gridSettings.viewFormat === ViewFormatType.list) {
return this.layoutCtx.gridSettings.autoFillHeight;
}
return this.layoutCtx.gridSettings.mobileAutoFillHeight;
}
get isMobileValue(): boolean {
return this.isMobile || (this.layoutCtx.breakpoint !== 'default' && this.layoutCtx.gridSettings.viewFormat === ViewFormatType.list);
} }
get isMobileDisabled(): boolean { get isMobileDisabled(): boolean {
return this.widgetEditMode || this.isScada; return this.widgetEditMode || this.isScada || (this.layoutCtx.breakpoint !== 'default' && !this.isMobileValue);
}
get mobielRowHeigth(): number {
if (this.layoutCtx.breakpoint !== 'default' && this.layoutCtx.gridSettings.viewFormat === ViewFormatType.list) {
return this.layoutCtx.gridSettings.rowHeight;
}
return this.layoutCtx.gridSettings.mobileRowHeight;
} }
get colWidthInteger(): boolean { get colWidthInteger(): boolean {
@ -137,7 +152,6 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo
private itembuffer: ItemBufferService, private itembuffer: ItemBufferService,
private imagePipe: ImagePipe, private imagePipe: ImagePipe,
private sanitizer: DomSanitizer, private sanitizer: DomSanitizer,
private dashboardUtils: DashboardUtilsService,
) { ) {
super(store); super(store);
this.initHotKeys(); this.initHotKeys();
@ -293,11 +307,11 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo
this.layoutCtx.dashboardCtrl.widgetClicked($event, this.layoutCtx, widget); this.layoutCtx.dashboardCtrl.widgetClicked($event, this.layoutCtx, widget);
} }
prepareDashboardContextMenu($event: Event): Array<DashboardContextMenuItem> { prepareDashboardContextMenu(_: Event): Array<DashboardContextMenuItem> {
return this.layoutCtx.dashboardCtrl.prepareDashboardContextMenu(this.layoutCtx); return this.layoutCtx.dashboardCtrl.prepareDashboardContextMenu(this.layoutCtx);
} }
prepareWidgetContextMenu($event: Event, widget: Widget, isReference: boolean): Array<WidgetContextMenuItem> { prepareWidgetContextMenu(_: Event, widget: Widget, isReference: boolean): Array<WidgetContextMenuItem> {
return this.layoutCtx.dashboardCtrl.prepareWidgetContextMenu(this.layoutCtx, widget, isReference); return this.layoutCtx.dashboardCtrl.prepareWidgetContextMenu(this.layoutCtx, widget, isReference);
} }
@ -319,12 +333,16 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo
this.layoutCtx.dashboardCtrl.pasteWidgetReference($event, this.layoutCtx, pos); this.layoutCtx.dashboardCtrl.pasteWidgetReference($event, this.layoutCtx, pos);
} }
updatedCurrentBreakpoint(breakpoint?: string, showLayout = true) { updatedCurrentBreakpoint(breakpointId?: BreakpointId, showLayout = true) {
if (!isNotEmptyStr(breakpoint)) { if (!isNotEmptyStr(breakpointId)) {
breakpoint = this.dashboardCtx.breakpoint; breakpointId = this.dashboardCtx.breakpoint;
} }
this.layoutCtx.breakpoint = breakpoint; if (this.layoutCtx.layoutData[breakpointId]) {
const layoutInfo = this.getLayoutDataForBreakpoint(breakpoint); this.layoutCtx.breakpoint = breakpointId;
} else {
this.layoutCtx.breakpoint = 'default';
}
const layoutInfo = this.layoutCtx.layoutData[this.layoutCtx.breakpoint];
if (layoutInfo.gridSettings) { if (layoutInfo.gridSettings) {
this.layoutCtx.gridSettings = layoutInfo.gridSettings; this.layoutCtx.gridSettings = layoutInfo.gridSettings;
} }
@ -335,12 +353,4 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo
} }
this.layoutCtx.ignoreLoading = true; this.layoutCtx.ignoreLoading = true;
} }
private getLayoutDataForBreakpoint(breakpoint: string) {
if (this.layoutCtx.layoutData[breakpoint]) {
return this.layoutCtx.layoutData[breakpoint];
}
return this.layoutCtx.layoutData.default;
}
} }

128
ui-ngx/src/app/modules/home/components/dashboard-page/layout/manage-dashboard-layouts-dialog.component.html

@ -25,70 +25,72 @@
<mat-icon class="material-icons">close</mat-icon> <mat-icon class="material-icons">close</mat-icon>
</button> </button>
</mat-toolbar> </mat-toolbar>
<div mat-dialog-content fxLayout="column" fxLayoutGap="20px" style="min-width: 300px; overflow: hidden;"> <div mat-dialog-content style="min-width: 300px; overflow: hidden;">
<div class="tb-form-row space-between"> <section class="tb-form-panel no-padding no-border">
<div class="fixed-title-width" translate>dashboard.layout</div> <div class="tb-form-row space-between">
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic"> <div class="fixed-title-width" translate>dashboard.layout</div>
<mat-select formControlName="layoutType"> <mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-option *ngFor="let type of layoutTypes" [value]="type"> <mat-select formControlName="layoutType">
{{ layoutTypeTranslations.get(type) | translate }} <mat-option *ngFor="let type of layoutTypes" [value]="type">
</mat-option> {{ layoutTypeTranslations.get(type) | translate }}
</mat-select> </mat-option>
</mat-form-field> </mat-select>
</div> </mat-form-field>
<div *ngIf="!isDividerLayout; else dividerLayout" class="tb-form-panel stroked" style="width: 500px"> </div>
<div class="tb-form-table tb-layout-breakpoints"> <div *ngIf="!isDividerLayout; else dividerLayout" class="tb-form-panel stroked">
<div class="tb-form-table-header no-padding-right"> <div class="tb-form-table tb-layout-breakpoints">
<div class="tb-form-table-header-cell tb-icon"></div> <div class="tb-form-table-header no-padding-right">
<div class="tb-form-table-header-cell tb-breakpoint" translate>layout.breakpoints</div> <div class="tb-form-table-header-cell tb-icon"></div>
<div class="tb-form-table-header-cell tb-size" translate>layout.size</div> <div class="tb-form-table-header-cell tb-breakpoint" translate>layout.breakpoints</div>
<div class="tb-form-table-header-cell tb-actions"></div> <div class="tb-form-table-header-cell tb-size" translate>layout.size</div>
</div> <div class="tb-form-table-header-cell tb-actions"></div>
<div class="tb-form-table-body no-gap"> </div>
<ng-container *ngFor="let breakpoint of layoutBreakpoints; let last = last"> <div class="tb-form-table-body no-gap">
<div class="tb-form-table-row no-padding-right"> <ng-container *ngFor="let breakpoint of layoutBreakpoints; let last = last">
<div class="tb-form-table-row-cell tb-icon"> <div class="tb-form-table-row no-padding-right">
<tb-icon>{{ breakpoint.icon }}</tb-icon> <div class="tb-form-table-row-cell tb-icon">
</div> <tb-icon>{{ breakpoint.icon }}</tb-icon>
<div class="tb-form-table-row-cell tb-breakpoint"> </div>
{{ breakpoint.name }} <div class="tb-form-table-row-cell tb-breakpoint">
</div> {{ breakpoint.name }}
<div class="tb-form-table-row-cell tb-size"> </div>
{{ breakpoint.descriptionSize }} <div class="tb-form-table-row-cell tb-size" tbTruncateWithTooltip>
</div> {{ breakpoint.descriptionSize }}
<div class="tb-form-table-row-cell-buttons tb-actions"> </div>
<button mat-icon-button <div class="tb-form-table-row-cell-buttons tb-actions">
matTooltip="{{ 'dashboard.layout-settings' | translate }}" <button mat-icon-button
matTooltipPosition="above" matTooltip="{{ 'layout.settings' | translate }}"
(click)="openLayoutSettings('main', breakpoint.breakpoint)" matTooltipPosition="above"
type="button" > (click)="openLayoutSettings('main', breakpoint.breakpoint)"
<mat-icon>settings</mat-icon> type="button" >
</button> <mat-icon>settings</mat-icon>
<button mat-icon-button </button>
matTooltip="{{ 'action.delete' | translate }}" <button mat-icon-button
matTooltipPosition="above" matTooltip="{{ 'action.delete' | translate }}"
[disabled]="breakpoint.breakpoint === 'default'" matTooltipPosition="above"
[class.tb-hidden]="breakpoint.breakpoint === 'default'" [disabled]="breakpoint.breakpoint === 'default'"
(click)="deleteBreakpoint($event, breakpoint.breakpoint)" [class.tb-hidden]="breakpoint.breakpoint === 'default'"
type="button" > (click)="deleteBreakpoint($event, breakpoint.breakpoint)"
<tb-icon>delete</tb-icon> type="button" >
</button> <tb-icon>delete</tb-icon>
</button>
</div>
</div> </div>
</div> <mat-divider *ngIf="!last"></mat-divider>
<mat-divider *ngIf="!last"></mat-divider> </ng-container>
</ng-container> </div>
</div> </div>
<button mat-button fxFlex class="tb-add-breakpoint-button"
matTooltip="{{ 'layout.add-new-breakpoint' | translate }}"
matTooltipPosition="above"
color="primary"
type="button"
[disabled]="!allowBreakpointIds.length"
(click)="addBreakpoint()">
<mat-icon>add</mat-icon>
</button>
</div> </div>
<button mat-button fxFlex class="tb-add-breakpoint-button" </section>
matTooltip="{{ 'layout.add-new-breakpoint' | translate }}"
matTooltipPosition="above"
color="primary"
type="button"
[disabled]="!allowBreakpointIds.length"
(click)="addBreakpoint()">
<mat-icon>add</mat-icon>
</button>
</div>
<ng-template #dividerLayout> <ng-template #dividerLayout>
<div fxLayout="column" fxLayoutAlign="start center" fxLayoutGap="20px"> <div fxLayout="column" fxLayoutAlign="start center" fxLayoutGap="20px">
<mat-button-toggle-group aria-label="Select width value type" <mat-button-toggle-group aria-label="Select width value type"
@ -111,7 +113,7 @@
<div fxLayout="column" fxLayoutAlign="start center" fxFlex="{{ buttonFlexValue() }}" class="tb-layout-preview-container-main"> <div fxLayout="column" fxLayoutAlign="start center" fxFlex="{{ buttonFlexValue() }}" class="tb-layout-preview-container-main">
<button mat-icon-button <button mat-icon-button
type="button" type="button"
matTooltip="{{ 'dashboard.layout-settings' | translate }}" matTooltip="{{ 'layout.settings' | translate }}"
matTooltipPosition="above" matTooltipPosition="above"
(click)="openLayoutSettings('main')" (click)="openLayoutSettings('main')"
class="tb-layout-preview-element" class="tb-layout-preview-element"
@ -155,7 +157,7 @@
<div fxLayout="column" fxLayoutAlign="start center" fxFlex *ngIf="isDividerLayout" matTooltip=""> <div fxLayout="column" fxLayoutAlign="start center" fxFlex *ngIf="isDividerLayout" matTooltip="">
<button mat-icon-button <button mat-icon-button
type="button" type="button"
matTooltip="{{ 'dashboard.layout-settings' | translate }}" matTooltip="{{ 'layout.settings' | translate }}"
matTooltipPosition="above" matTooltipPosition="above"
(click)="openLayoutSettings('right')" (click)="openLayoutSettings('right')"
class="tb-layout-preview-element" class="tb-layout-preview-element"

3
ui-ngx/src/app/modules/home/components/dashboard-page/layout/manage-dashboard-layouts-dialog.component.scss

@ -141,8 +141,7 @@ $tb-warn: mat.get-color-from-palette(map-get($tb-theme, warn), text);
&.tb-size { &.tb-size {
flex: 1 1 85%; flex: 1 1 85%;
width: 85%; width: 200px;
min-width: 150px;
} }
&.tb-actions { &.tb-actions {

81
ui-ngx/src/app/modules/home/components/dashboard-page/layout/manage-dashboard-layouts-dialog.component.ts

@ -21,23 +21,26 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { import {
AbstractControl, AbstractControl,
FormGroupDirective,
NgForm,
UntypedFormBuilder, UntypedFormBuilder,
UntypedFormControl, UntypedFormControl,
UntypedFormGroup, UntypedFormGroup,
FormGroupDirective,
NgForm,
Validators Validators
} from '@angular/forms'; } from '@angular/forms';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { DialogComponent } from '@app/shared/components/dialog.component'; import { DialogComponent } from '@app/shared/components/dialog.component';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { import {
BreakpointInfo, BreakpointId,
DashboardLayout, DashboardLayout,
DashboardLayoutId, DashboardLayoutId,
DashboardStateLayouts, DashboardStateLayouts,
LayoutDimension, LayoutType, layoutTypes, LayoutDimension,
layoutTypeTranslationMap LayoutType,
layoutTypes,
layoutTypeTranslationMap,
ViewFormatType
} from '@app/shared/models/dashboard.models'; } from '@app/shared/models/dashboard.models';
import { deepClone, isDefined, isEqual } from '@core/utils'; import { deepClone, isDefined, isEqual } from '@core/utils';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
@ -53,7 +56,9 @@ import {
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { MatTooltip } from '@angular/material/tooltip'; import { MatTooltip } from '@angular/material/tooltip';
import { import {
AddNewBreakpointDialogComponent, AddNewBreakpointDialogData, AddNewBreakpointDialogResult AddNewBreakpointDialogComponent,
AddNewBreakpointDialogData,
AddNewBreakpointDialogResult
} from '@home/components/dashboard-page/layout/add-new-breakpoint-dialog.component'; } from '@home/components/dashboard-page/layout/add-new-breakpoint-dialog.component';
import { DialogService } from '@core/services/dialog.service'; import { DialogService } from '@core/services/dialog.service';
@ -98,9 +103,6 @@ export class ManageDashboardLayoutsDialogComponent extends DialogComponent<Manag
private submitted = false; private submitted = false;
private breakpoints: BreakpointInfo[];
private breakpointsData: {[breakpointId in string]: BreakpointInfo} = {};
allowBreakpointIds = []; allowBreakpointIds = [];
selectedBreakpointIds = ['default']; selectedBreakpointIds = ['default'];
@ -118,11 +120,6 @@ export class ManageDashboardLayoutsDialogComponent extends DialogComponent<Manag
this.layouts = this.data.layouts; this.layouts = this.data.layouts;
this.breakpoints = this.dashboardUtils.getListBreakpoint();
this.breakpoints.forEach((breakpoint) => {
this.breakpointsData[breakpoint.id] = breakpoint;
});
let layoutType = LayoutType.default; let layoutType = LayoutType.default;
if (isDefined(this.layouts.right)) { if (isDefined(this.layouts.right)) {
layoutType = LayoutType.divider; layoutType = LayoutType.divider;
@ -201,14 +198,15 @@ export class ManageDashboardLayoutsDialogComponent extends DialogComponent<Manag
this.addLayoutConfiguration('default'); this.addLayoutConfiguration('default');
if (!this.isDividerLayout && this.layouts.main.breakpoints) { if (!this.isDividerLayout && this.layouts.main.breakpoints) {
for (const breakpoint of Object.keys(this.layouts.main.breakpoints)) { for (const breakpoint of (Object.keys(this.layouts.main.breakpoints) as BreakpointId[])) {
this.addLayoutConfiguration(breakpoint); this.addLayoutConfiguration(breakpoint);
this.selectedBreakpointIds.push(breakpoint); this.selectedBreakpointIds.push(breakpoint);
} }
} }
this.allowBreakpointIds = Object.keys(this.breakpointsData) this.allowBreakpointIds = Object.values(this.dashboardUtils.getListBreakpoint())
.filter((item) => !this.selectedBreakpointIds.includes(item)); .filter((item) => !this.selectedBreakpointIds.includes(item.id))
.map(item => item.id);
this.subscriptions.push( this.subscriptions.push(
this.layoutsFormGroup.get('sliderPercentage').valueChanges this.layoutsFormGroup.get('sliderPercentage').valueChanges
@ -261,9 +259,10 @@ export class ManageDashboardLayoutsDialogComponent extends DialogComponent<Manag
return originalErrorState || customErrorState; return originalErrorState || customErrorState;
} }
openLayoutSettings(layoutId: DashboardLayoutId, breakpointId = 'default') { openLayoutSettings(layoutId: DashboardLayoutId, breakpointId: BreakpointId = 'default') {
const layout = this.dashboardUtils.getDashboardLayoutConfig(this.layouts[layoutId], breakpointId); const layout = this.dashboardUtils.getDashboardLayoutConfig(this.layouts[layoutId], breakpointId);
const gridSettings = layout.gridSettings; const gridSettings = layout.gridSettings;
gridSettings.layoutType = this.layoutsFormGroup.get('layoutType').value;
this.dialog.open<DashboardSettingsDialogComponent, DashboardSettingsDialogData, this.dialog.open<DashboardSettingsDialogComponent, DashboardSettingsDialogData,
DashboardSettingsDialogData>(DashboardSettingsDialogComponent, { DashboardSettingsDialogData>(DashboardSettingsDialogComponent, {
disableClose: true, disableClose: true,
@ -271,7 +270,8 @@ export class ManageDashboardLayoutsDialogComponent extends DialogComponent<Manag
data: { data: {
settings: null, settings: null,
gridSettings, gridSettings,
isRightLayout: this.isDividerLayout && layoutId === 'right' isRightLayout: this.isDividerLayout && layoutId === 'right',
breakpointId
} }
}).afterClosed().subscribe((data) => { }).afterClosed().subscribe((data) => {
if (data && data.gridSettings) { if (data && data.gridSettings) {
@ -302,7 +302,7 @@ export class ManageDashboardLayoutsDialogComponent extends DialogComponent<Manag
if (this.layouts.right?.gridSettings) { if (this.layouts.right?.gridSettings) {
delete this.layouts.right.gridSettings.layoutDimension; delete this.layouts.right.gridSettings.layoutDimension;
} }
if (this.layoutsFormGroup.value.right) { if (this.isDividerLayout) {
const formValues = this.layoutsFormGroup.value; const formValues = this.layoutsFormGroup.value;
const widthType = formValues.type; const widthType = formValues.type;
const layoutDimension: LayoutDimension = { const layoutDimension: LayoutDimension = {
@ -343,7 +343,7 @@ export class ManageDashboardLayoutsDialogComponent extends DialogComponent<Manag
return this.layoutsFormGroup.get('type').value === LayoutWidthType.FIXED ? value : `${value}|${100 - value}`; return this.layoutsFormGroup.get('type').value === LayoutWidthType.FIXED ? value : `${value}|${100 - value}`;
} }
private layoutControlChange(key: string, value) { private layoutControlChange(key: string, value: number) {
const valueToSet = 100 - Number(value); const valueToSet = 100 - Number(value);
this.layoutsFormGroup.get(key).setValue(valueToSet, {emitEvent: false}); this.layoutsFormGroup.get(key).setValue(valueToSet, {emitEvent: false});
this.layoutsFormGroup.get('sliderPercentage') this.layoutsFormGroup.get('sliderPercentage')
@ -462,39 +462,50 @@ export class ManageDashboardLayoutsDialogComponent extends DialogComponent<Manag
); );
} }
private createdNewBreakpoint(newBreakpointId: string, copyFromBreakpointId: string): void { private createdNewBreakpoint(newBreakpointId: BreakpointId, copyFromBreakpointId: BreakpointId): void {
const layoutConfig = this.layouts.main; const layoutConfig = this.layouts.main;
const sourceLayout = copyFromBreakpointId === 'default' ? layoutConfig : layoutConfig.breakpoints[copyFromBreakpointId]; const sourceLayout = copyFromBreakpointId === 'default' ? layoutConfig : layoutConfig.breakpoints[copyFromBreakpointId];
const gridSettings = deepClone(sourceLayout.gridSettings);
const widgets = deepClone(sourceLayout.widgets);
if (copyFromBreakpointId === 'default') {
const breakpointInfo = this.dashboardUtils.getBreakpointInfoById(newBreakpointId);
if (breakpointInfo?.maxWidth < 960) {
gridSettings.viewFormat = ViewFormatType.list;
gridSettings.rowHeight = gridSettings.mobileRowHeight;
gridSettings.autoFillHeight = gridSettings.mobileAutoFillHeight;
}
for (const widgetId in widgets) {
if (widgets[widgetId]) {
delete widgets[widgetId].desktopHide;
delete widgets[widgetId].mobileHide;
}
}
}
if (!layoutConfig.breakpoints) { if (!layoutConfig.breakpoints) {
layoutConfig.breakpoints = {}; layoutConfig.breakpoints = {};
} }
layoutConfig.breakpoints[newBreakpointId] = { layoutConfig.breakpoints[newBreakpointId] = {
gridSettings: deepClone(sourceLayout.gridSettings), gridSettings,
widgets: deepClone(sourceLayout.widgets), widgets,
}; };
this.selectedBreakpointIds.push(newBreakpointId); this.selectedBreakpointIds.push(newBreakpointId);
this.allowBreakpointIds = this.allowBreakpointIds.filter((item) => item !== newBreakpointId); this.allowBreakpointIds = this.allowBreakpointIds.filter((item) => item !== newBreakpointId);
this.addLayoutConfiguration(newBreakpointId); this.addLayoutConfiguration(newBreakpointId);
} }
private addLayoutConfiguration(breakpointId: string) { private addLayoutConfiguration(breakpointId: BreakpointId) {
const layout = breakpointId === 'default' ? this.layouts.main : this.layouts.main.breakpoints[breakpointId]; const layout = breakpointId === 'default' ? this.layouts.main : this.layouts.main.breakpoints[breakpointId];
const size = breakpointId === 'default' ? '' : this.createDescriptionSize(breakpointId); const size = breakpointId === 'default' ? '' : this.dashboardUtils.getBreakpointSizeDescription(breakpointId);
this.layoutBreakpoints.push({ this.layoutBreakpoints.push({
icon: 'mdi:monitor', icon: this.dashboardUtils.getBreakpointIcon(breakpointId),
name: breakpointId, name: this.dashboardUtils.getBreakpointName(breakpointId),
layout, layout,
descriptionSize: size, descriptionSize: size,
breakpoint: breakpointId breakpoint: breakpointId
}); });
} }
private createDescriptionSize(breakpointId: string): string {
const currentData = this.breakpointsData[breakpointId];
const minStr = isDefined(currentData.minWidth) ? `min ${currentData.minWidth}px` : '';
const maxStr = isDefined(currentData.maxWidth) ? `max ${currentData.maxWidth}px` : '';
return minStr && maxStr ? `${minStr} - ${maxStr}` : `${minStr}${maxStr}`;
}
} }

5
ui-ngx/src/app/modules/home/components/dashboard-page/layout/select-dashboard-breakpoint.component.html

@ -20,7 +20,10 @@
class="select-dashboard-breakpoint" class="select-dashboard-breakpoint"
[(ngModel)]="selectedBreakpoint" [(ngModel)]="selectedBreakpoint"
(ngModelChange)="selectLayoutChanged()"> (ngModelChange)="selectLayoutChanged()">
<mat-select-trigger>{{ getName(selectedBreakpoint) }}</mat-select-trigger>
<mat-option *ngFor="let breakpointId of breakpointIds" [value]="breakpointId"> <mat-option *ngFor="let breakpointId of breakpointIds" [value]="breakpointId">
{{ breakpointId }} <mat-icon><tb-icon>{{ getIcon(breakpointId) }}</tb-icon></mat-icon>
<div>{{ getName(breakpointId) }}</div>
<div style="font-size: 12px;letter-spacing: 0.4px;color: #9e9e9e;">{{ getSizeDescription(breakpointId) }}</div>
</mat-option> </mat-option>
</mat-select> </mat-select>

21
ui-ngx/src/app/modules/home/components/dashboard-page/layout/select-dashboard-breakpoint.component.ts

@ -17,7 +17,7 @@
import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { DashboardPageComponent } from '@home/components/dashboard-page/dashboard-page.component'; import { DashboardPageComponent } from '@home/components/dashboard-page/dashboard-page.component';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { BreakpointInfo } from '@shared/models/dashboard.models'; import { BreakpointId } from '@shared/models/dashboard.models';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
@Component({ @Component({
@ -30,18 +30,13 @@ export class SelectDashboardBreakpointComponent implements OnInit, OnDestroy {
@Input() @Input()
dashboardCtrl: DashboardPageComponent; dashboardCtrl: DashboardPageComponent;
selectedBreakpoint = 'default'; selectedBreakpoint: BreakpointId = 'default';
breakpointsData: {[breakpointId in string]: BreakpointInfo} = {};
breakpointIds: Array<string> = ['default']; breakpointIds: Array<string> = ['default'];
private layoutDataChanged$: Subscription; private layoutDataChanged$: Subscription;
constructor(private dashboardUtils: DashboardUtilsService) { constructor(private dashboardUtils: DashboardUtilsService) {
this.dashboardUtils.getListBreakpoint().forEach((breakpoint) => {
this.breakpointsData[breakpoint.id] = breakpoint;
});
} }
ngOnInit() { ngOnInit() {
@ -66,4 +61,16 @@ export class SelectDashboardBreakpointComponent implements OnInit, OnDestroy {
this.dashboardCtrl.layouts.main.layoutCtx.ctrl.updatedCurrentBreakpoint(this.selectedBreakpoint); this.dashboardCtrl.layouts.main.layoutCtx.ctrl.updatedCurrentBreakpoint(this.selectedBreakpoint);
this.dashboardCtrl.updateLayoutSizes(); this.dashboardCtrl.updateLayoutSizes();
} }
getName(breakpointId: BreakpointId): string {
return this.dashboardUtils.getBreakpointName(breakpointId);
}
getIcon(breakpointId: BreakpointId): string {
return this.dashboardUtils.getBreakpointIcon(breakpointId);
}
getSizeDescription(breakpointId: BreakpointId): string {
return this.dashboardUtils.getBreakpointSizeDescription(breakpointId);
}
} }

17
ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts

@ -17,7 +17,6 @@
import { import {
AfterViewInit, AfterViewInit,
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef,
Component, Component,
DoCheck, DoCheck,
Input, Input,
@ -46,7 +45,6 @@ import {
} from '../../models/dashboard-component.models'; } from '../../models/dashboard-component.models';
import { ReplaySubject, Subject, Subscription } from 'rxjs'; import { ReplaySubject, Subject, Subscription } from 'rxjs';
import { WidgetLayout, WidgetLayouts } from '@shared/models/dashboard.models'; import { WidgetLayout, WidgetLayouts } from '@shared/models/dashboard.models';
import { DialogService } from '@core/services/dialog.service';
import { animatedScroll, deepClone, isDefined } from '@app/core/utils'; import { animatedScroll, deepClone, isDefined } from '@app/core/utils';
import { BreakpointObserver } from '@angular/cdk/layout'; import { BreakpointObserver } from '@angular/cdk/layout';
import { MediaBreakpoints } from '@shared/models/constants'; import { MediaBreakpoints } from '@shared/models/constants';
@ -203,9 +201,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
widgetContextMenuEvent: MouseEvent; widgetContextMenuEvent: MouseEvent;
dashboardWidgets = new DashboardWidgets(this, dashboardWidgets = new DashboardWidgets(this,
this.differs.find([]).create<Widget>((index, item) => { this.differs.find([]).create<Widget>((_, item) => item),
return item;
}),
this.kvDiffers.find([]).create<string, WidgetLayout>() this.kvDiffers.find([]).create<string, WidgetLayout>()
); );
@ -218,11 +214,9 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
public utils: UtilsService, public utils: UtilsService,
private timeService: TimeService, private timeService: TimeService,
private dialogService: DialogService,
private breakpointObserver: BreakpointObserver, private breakpointObserver: BreakpointObserver,
private differs: IterableDiffers, private differs: IterableDiffers,
private kvDiffers: KeyValueDiffers, private kvDiffers: KeyValueDiffers,
private cd: ChangeDetectorRef,
private ngZone: NgZone) { private ngZone: NgZone) {
super(store); super(store);
this.authUser = getCurrentAuthUser(store); this.authUser = getCurrentAuthUser(store);
@ -257,8 +251,8 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
displayGrid: this.displayGrid, displayGrid: this.displayGrid,
resizable: {enabled: this.isEdit && !this.isEditingWidget, delayStart: 50}, resizable: {enabled: this.isEdit && !this.isEditingWidget, delayStart: 50},
draggable: {enabled: this.isEdit && !this.isEditingWidget}, draggable: {enabled: this.isEdit && !this.isEditingWidget},
itemChangeCallback: item => this.dashboardWidgets.sortWidgets(), itemChangeCallback: () => this.dashboardWidgets.sortWidgets(),
itemInitCallback: (item, itemComponent) => { itemInitCallback: (_, itemComponent) => {
(itemComponent.item as DashboardWidget).gridsterItemComponent = itemComponent; (itemComponent.item as DashboardWidget).gridsterItemComponent = itemComponent;
}, },
colWidthUpdateCallback: (colWidth) => { colWidthUpdateCallback: (colWidth) => {
@ -570,7 +564,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
widget.gridsterItemComponent$().subscribe((gridsterItem) => { widget.gridsterItemComponent$().subscribe((gridsterItem) => {
const gridsterItemElement = gridsterItem.el as HTMLElement; const gridsterItemElement = gridsterItem.el as HTMLElement;
const offset = (parentElement.clientHeight - gridsterItemElement.clientHeight) / 2; const offset = (parentElement.clientHeight - gridsterItemElement.clientHeight) / 2;
let scrollTop; let scrollTop: number;
if (this.isMobileSize) { if (this.isMobileSize) {
scrollTop = gridsterItemElement.offsetTop; scrollTop = gridsterItemElement.offsetTop;
} else { } else {
@ -604,8 +598,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
} else { } else {
this.gridsterOpts.gridType = this.isMobileSize ? GridType.Fixed : this.gridType || GridType.ScrollVertical; this.gridsterOpts.gridType = this.isMobileSize ? GridType.Fixed : this.gridType || GridType.ScrollVertical;
} }
const mobileBreakPoint = this.isMobileSize ? 20000 : 0; this.gridsterOpts.mobileBreakpoint = this.isMobileSize ? 20000 : 0;
this.gridsterOpts.mobileBreakpoint = mobileBreakPoint;
const rowSize = this.detectRowSize(this.isMobileSize, autofillHeight, parentHeight); const rowSize = this.detectRowSize(this.isMobileSize, autofillHeight, parentHeight);
if (this.gridsterOpts.fixedRowHeight !== rowSize) { if (this.gridsterOpts.fixedRowHeight !== rowSize) {
this.gridsterOpts.fixedRowHeight = rowSize; this.gridsterOpts.fixedRowHeight = rowSize;

3
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component.ts

@ -57,7 +57,6 @@ import { isDefinedAndNotNull, isUndefinedOrNull } from '@core/utils';
import { coerceBoolean } from '@shared/decorators/coercion'; import { coerceBoolean } from '@shared/decorators/coercion';
import { SharedModule } from '@shared/shared.module'; import { SharedModule } from '@shared/shared.module';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-tooltip.directive';
import { TbTableDatasource } from '@shared/components/table/table-datasource.abstract'; import { TbTableDatasource } from '@shared/components/table/table-datasource.abstract';
@Component({ @Component({
@ -78,7 +77,7 @@ import { TbTableDatasource } from '@shared/components/table/table-datasource.abs
} }
], ],
standalone: true, standalone: true,
imports: [CommonModule, SharedModule, TruncateWithTooltipDirective] imports: [CommonModule, SharedModule]
}) })
export class MappingTableComponent implements ControlValueAccessor, Validator, AfterViewInit, OnInit, OnDestroy { export class MappingTableComponent implements ControlValueAccessor, Validator, AfterViewInit, OnInit, OnDestroy {

2
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-data-keys-panel/modbus-data-keys-panel.component.ts

@ -41,7 +41,6 @@ import { generateSecret } from '@core/utils';
import { coerceBoolean } from '@shared/decorators/coercion'; import { coerceBoolean } from '@shared/decorators/coercion';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-tooltip.directive';
@Component({ @Component({
selector: 'tb-modbus-data-keys-panel', selector: 'tb-modbus-data-keys-panel',
@ -52,7 +51,6 @@ import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-t
CommonModule, CommonModule,
SharedModule, SharedModule,
GatewayHelpLinkPipe, GatewayHelpLinkPipe,
TruncateWithTooltipDirective,
] ]
}) })
export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy { export class ModbusDataKeysPanelComponent implements OnInit, OnDestroy {

2
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-security-config/modbus-security-config.component.ts

@ -42,7 +42,6 @@ import { CommonModule } from '@angular/common';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { coerceBoolean } from '@shared/decorators/coercion'; import { coerceBoolean } from '@shared/decorators/coercion';
import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-tooltip.directive';
@Component({ @Component({
selector: 'tb-modbus-security-config', selector: 'tb-modbus-security-config',
@ -64,7 +63,6 @@ import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-t
imports: [ imports: [
CommonModule, CommonModule,
SharedModule, SharedModule,
TruncateWithTooltipDirective,
] ]
}) })
export class ModbusSecurityConfigComponent implements ControlValueAccessor, Validator, OnChanges, OnDestroy { export class ModbusSecurityConfigComponent implements ControlValueAccessor, Validator, OnChanges, OnDestroy {

2
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-config/modbus-slave-config.component.ts

@ -48,7 +48,6 @@ import { GatewayPortTooltipPipe } from '@home/components/widget/lib/gateway/pipe
import { ModbusSecurityConfigComponent } from '../modbus-security-config/modbus-security-config.component'; import { ModbusSecurityConfigComponent } from '../modbus-security-config/modbus-security-config.component';
import { ModbusValuesComponent, } from '../modbus-values/modbus-values.component'; import { ModbusValuesComponent, } from '../modbus-values/modbus-values.component';
import { isEqual } from '@core/utils'; import { isEqual } from '@core/utils';
import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-tooltip.directive';
@Component({ @Component({
selector: 'tb-modbus-slave-config', selector: 'tb-modbus-slave-config',
@ -73,7 +72,6 @@ import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-t
ModbusValuesComponent, ModbusValuesComponent,
ModbusSecurityConfigComponent, ModbusSecurityConfigComponent,
GatewayPortTooltipPipe, GatewayPortTooltipPipe,
TruncateWithTooltipDirective,
], ],
}) })
export class ModbusSlaveConfigComponent implements ControlValueAccessor, Validator, OnDestroy { export class ModbusSlaveConfigComponent implements ControlValueAccessor, Validator, OnDestroy {

2
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-slave-dialog/modbus-slave-dialog.component.ts

@ -52,7 +52,6 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { GatewayPortTooltipPipe } from '@home/components/widget/lib/gateway/pipes/gateway-port-tooltip.pipe'; import { GatewayPortTooltipPipe } from '@home/components/widget/lib/gateway/pipes/gateway-port-tooltip.pipe';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { isEqual } from '@core/utils'; import { isEqual } from '@core/utils';
import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-tooltip.directive';
import { helpBaseUrl } from '@shared/models/constants'; import { helpBaseUrl } from '@shared/models/constants';
@Component({ @Component({
@ -78,7 +77,6 @@ import { helpBaseUrl } from '@shared/models/constants';
ModbusValuesComponent, ModbusValuesComponent,
ModbusSecurityConfigComponent, ModbusSecurityConfigComponent,
GatewayPortTooltipPipe, GatewayPortTooltipPipe,
TruncateWithTooltipDirective,
], ],
styleUrls: ['./modbus-slave-dialog.component.scss'], styleUrls: ['./modbus-slave-dialog.component.scss'],
}) })

2
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/opc-server-config/opc-server-config.component.ts

@ -35,7 +35,6 @@ import { SharedModule } from '@shared/shared.module';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-tooltip.directive';
import { import {
SecurityConfigComponent SecurityConfigComponent
} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; } from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component';
@ -62,7 +61,6 @@ import {
CommonModule, CommonModule,
SharedModule, SharedModule,
SecurityConfigComponent, SecurityConfigComponent,
TruncateWithTooltipDirective,
] ]
}) })
export class OpcServerConfigComponent implements ControlValueAccessor, Validator, OnDestroy { export class OpcServerConfigComponent implements ControlValueAccessor, Validator, OnDestroy {

2
ui-ngx/src/app/modules/home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component.ts

@ -35,7 +35,6 @@ import { CommonModule } from '@angular/common';
import { WorkersConfig } from '@home/components/widget/lib/gateway/gateway-widget.models'; import { WorkersConfig } from '@home/components/widget/lib/gateway/gateway-widget.models';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-tooltip.directive';
@Component({ @Component({
selector: 'tb-workers-config-control', selector: 'tb-workers-config-control',
@ -45,7 +44,6 @@ import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-t
imports: [ imports: [
CommonModule, CommonModule,
SharedModule, SharedModule,
TruncateWithTooltipDirective,
], ],
providers: [ providers: [
{ {

4
ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts

@ -141,9 +141,8 @@ import {
import { import {
TypeValuePanelComponent TypeValuePanelComponent
} from '@home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component'; } from '@home/components/widget/lib/gateway/connectors-configuration/type-value-panel/type-value-panel.component';
import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-tooltip.directive';
import { import {
ModbusRpcParametersComponent ModbusRpcParametersComponent
} from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component'; } from '@home/components/widget/lib/gateway/connectors-configuration/modbus/modbus-rpc-parameters/modbus-rpc-parameters.component';
import { ScadaSymbolWidgetComponent } from '@home/components/widget/lib/scada/scada-symbol-widget.component'; import { ScadaSymbolWidgetComponent } from '@home/components/widget/lib/scada/scada-symbol-widget.component';
@ -233,7 +232,6 @@ import { ScadaSymbolWidgetComponent } from '@home/components/widget/lib/scada/sc
KeyValueIsNotEmptyPipe, KeyValueIsNotEmptyPipe,
ModbusBasicConfigComponent, ModbusBasicConfigComponent,
EllipsisChipListDirective, EllipsisChipListDirective,
TruncateWithTooltipDirective,
ModbusRpcParametersComponent, ModbusRpcParametersComponent,
], ],
exports: [ exports: [

4
ui-ngx/src/app/modules/home/components/widget/widget-config.component.html

@ -169,12 +169,12 @@
</div> </div>
</div> </div>
<div [fxShow]="selectedOption === 'mobile'" [formGroup]="layoutSettings" class="mat-content"> <div [fxShow]="selectedOption === 'mobile'" [formGroup]="layoutSettings" class="mat-content">
<div class="tb-form-panel"> <div class="tb-form-panel" *ngIf="isDefaultBreakpoint">
<mat-slide-toggle class="mat-slide" formControlName="mobileHide"> <mat-slide-toggle class="mat-slide" formControlName="mobileHide">
{{ 'widget-config.mobile-hide' | translate }} {{ 'widget-config.mobile-hide' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<div class="tb-form-panel"> <div class="tb-form-panel" *ngIf="isDefaultBreakpoint">
<mat-slide-toggle class="mat-slide" formControlName="desktopHide"> <mat-slide-toggle class="mat-slide" formControlName="desktopHide">
{{ 'widget-config.desktop-hide' | translate }} {{ 'widget-config.desktop-hide' | translate }}
</mat-slide-toggle> </mat-slide-toggle>

16
ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts

@ -38,7 +38,7 @@ import {
JsonSchema, JsonSchema,
JsonSettingsSchema, JsonSettingsSchema,
TargetDevice, TargetDevice,
TargetDeviceType, targetDeviceValid, targetDeviceValid,
Widget, Widget,
WidgetConfigMode, WidgetConfigMode,
widgetType widgetType
@ -84,8 +84,8 @@ import { ToggleHeaderOption } from '@shared/components/toggle-header.component';
import { coerceBoolean } from '@shared/decorators/coercion'; import { coerceBoolean } from '@shared/decorators/coercion';
import { basicWidgetConfigComponentsMap } from '@home/components/widget/config/basic/basic-widget-config.module'; import { basicWidgetConfigComponentsMap } from '@home/components/widget/config/basic/basic-widget-config.module';
import { TimewindowConfigData } from '@home/components/widget/config/timewindow-config-panel.component'; import { TimewindowConfigData } from '@home/components/widget/config/timewindow-config-panel.component';
import Timeout = NodeJS.Timeout;
import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
import Timeout = NodeJS.Timeout;
const emptySettingsSchema: JsonSchema = { const emptySettingsSchema: JsonSchema = {
type: 'object', type: 'object',
@ -152,7 +152,11 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
@Input() @Input()
@coerceBoolean() @coerceBoolean()
scada = false; showLayoutConfig = true;
@Input()
@coerceBoolean()
isDefaultBreakpoint = true;
@Input() disabled: boolean; @Input() disabled: boolean;
@ -345,10 +349,12 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
value: 'actions' value: 'actions'
} }
); );
if (!this.scada) { if (this.showLayoutConfig) {
this.headerOptions.push( this.headerOptions.push(
{ {
name: this.translate.instant('widget-config.mobile'), name: this.isDefaultBreakpoint
? this.translate.instant('widget-config.mobile')
: this.translate.instant('widget-config.list-option'),
value: 'mobile' value: 'mobile'
} }
); );

1
ui-ngx/src/app/shared/directives/truncate-with-tooltip.directive.ts

@ -29,7 +29,6 @@ import { MatTooltip, TooltipPosition } from '@angular/material/tooltip';
import { coerceBoolean } from '@shared/decorators/coercion'; import { coerceBoolean } from '@shared/decorators/coercion';
@Directive({ @Directive({
standalone: true,
selector: '[tbTruncateWithTooltip]', selector: '[tbTruncateWithTooltip]',
providers: [MatTooltip], providers: [MatTooltip],
}) })

46
ui-ngx/src/app/shared/models/dashboard.models.ts

@ -18,7 +18,7 @@ import { BaseData, ExportableEntity } from '@shared/models/base-data';
import { DashboardId } from '@shared/models/id/dashboard-id'; import { DashboardId } from '@shared/models/id/dashboard-id';
import { TenantId } from '@shared/models/id/tenant-id'; import { TenantId } from '@shared/models/id/tenant-id';
import { ShortCustomerInfo } from '@shared/models/customer.model'; import { ShortCustomerInfo } from '@shared/models/customer.model';
import { LegendPosition, Widget } from './widget.models'; import { Widget } from './widget.models';
import { Timewindow } from '@shared/models/time/time.models'; import { Timewindow } from '@shared/models/time/time.models';
import { EntityAliases } from './alias.models'; import { EntityAliases } from './alias.models';
import { Filters } from '@shared/models/query/query.models'; import { Filters } from '@shared/models/query/query.models';
@ -65,6 +65,20 @@ export const layoutTypeTranslationMap = new Map<LayoutType, string>(
] ]
); );
export enum ViewFormatType {
grid = 'grid',
list = 'list',
}
export const viewFormatTypes = Object.keys(ViewFormatType) as ViewFormatType[];
export const viewFormatTypeTranslationMap = new Map<ViewFormatType, string>(
[
[ ViewFormatType.grid, 'dashboard.view-format-type-grid' ],
[ ViewFormatType.list, 'dashboard.view-format-type-list' ],
]
);
export interface GridSettings { export interface GridSettings {
layoutType?: LayoutType; layoutType?: LayoutType;
backgroundColor?: string; backgroundColor?: string;
@ -72,9 +86,11 @@ export interface GridSettings {
minColumns?: number; minColumns?: number;
margin?: number; margin?: number;
outerMargin?: boolean; outerMargin?: boolean;
viewFormat?: ViewFormatType;
backgroundSizeMode?: string; backgroundSizeMode?: string;
backgroundImageUrl?: string; backgroundImageUrl?: string;
autoFillHeight?: boolean; autoFillHeight?: boolean;
rowHeight?: number;
mobileAutoFillHeight?: boolean; mobileAutoFillHeight?: boolean;
mobileRowHeight?: number; mobileRowHeight?: number;
mobileDisplayLayoutFirst?: boolean; mobileDisplayLayoutFirst?: boolean;
@ -85,7 +101,7 @@ export interface GridSettings {
export interface DashboardLayout { export interface DashboardLayout {
widgets: WidgetLayouts; widgets: WidgetLayouts;
gridSettings: GridSettings; gridSettings: GridSettings;
breakpoints?: {[breakpointId: string]: Omit<DashboardLayout, 'breakpoints'>}; breakpoints?: {[breakpointId in BreakpointId]?: Omit<DashboardLayout, 'breakpoints'>};
} }
export declare type DashboardLayoutInfo = {[breakpointId: string]: BreakpointLayoutInfo}; export declare type DashboardLayoutInfo = {[breakpointId: string]: BreakpointLayoutInfo};
@ -96,11 +112,33 @@ export interface BreakpointLayoutInfo {
gridSettings?: GridSettings; gridSettings?: GridSettings;
} }
export declare type BreakpointSystemId = 'default' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
export declare type BreakpointId = BreakpointSystemId | string;
export interface BreakpointInfo { export interface BreakpointInfo {
id: string; id: BreakpointId;
maxWidth?: number; maxWidth?: number;
minWidth?: number; minWidth?: number;
} value?: string;
}
export const breakpointIdTranslationMap = new Map<BreakpointId, string>([
['default', 'dashboard.breakpoints-id.default'],
['xs', 'dashboard.breakpoints-id.xs'],
['sm', 'dashboard.breakpoints-id.sm'],
['md', 'dashboard.breakpoints-id.md'],
['lg', 'dashboard.breakpoints-id.lg'],
['xl', 'dashboard.breakpoints-id.xl'],
]);
export const breakpointIdIconMap = new Map<BreakpointId, string>([
['default', 'desktop_windows'],
['xs', 'phone_iphone'],
['sm', 'tablet_mac'],
['md', 'computer'],
['lg', 'monitor'],
['xl', 'desktop_windows'],
]);
export interface LayoutDimension { export interface LayoutDimension {
type?: LayoutDimensionType; type?: LayoutDimensionType;

3
ui-ngx/src/app/shared/shared.module.ts

@ -67,6 +67,7 @@ import { ColorPickerModule } from '@iplab/ngx-color-picker';
import { NgxHmCarouselModule } from 'ngx-hm-carousel'; import { NgxHmCarouselModule } from 'ngx-hm-carousel';
import { EditorModule, TINYMCE_SCRIPT_SRC } from '@tinymce/tinymce-angular'; import { EditorModule, TINYMCE_SCRIPT_SRC } from '@tinymce/tinymce-angular';
import { UserMenuComponent } from '@shared/components/user-menu.component'; import { UserMenuComponent } from '@shared/components/user-menu.component';
import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-tooltip.directive';
import { NospacePipe } from '@shared/pipe/nospace.pipe'; import { NospacePipe } from '@shared/pipe/nospace.pipe';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { TbCheckboxComponent } from '@shared/components/tb-checkbox.component'; import { TbCheckboxComponent } from '@shared/components/tb-checkbox.component';
@ -356,6 +357,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
NavTreeComponent, NavTreeComponent,
LedLightComponent, LedLightComponent,
MarkdownEditorComponent, MarkdownEditorComponent,
TruncateWithTooltipDirective,
NospacePipe, NospacePipe,
MillisecondsToTimeStringPipe, MillisecondsToTimeStringPipe,
EnumToArrayPipe, EnumToArrayPipe,
@ -611,6 +613,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
NavTreeComponent, NavTreeComponent,
LedLightComponent, LedLightComponent,
MarkdownEditorComponent, MarkdownEditorComponent,
TruncateWithTooltipDirective,
NospacePipe, NospacePipe,
MillisecondsToTimeStringPipe, MillisecondsToTimeStringPipe,
EnumToArrayPipe, EnumToArrayPipe,

1
ui-ngx/src/assets/locale/locale.constant-ar_AE.json

@ -1283,7 +1283,6 @@
"maximum-upload-file-size": "الحد الأقصى لحجم الملف المرفوع: {{ size }}", "maximum-upload-file-size": "الحد الأقصى لحجم الملف المرفوع: {{ size }}",
"cannot-upload-file": "لا يمكن تحميل الملف", "cannot-upload-file": "لا يمكن تحميل الملف",
"settings": "الإعدادات", "settings": "الإعدادات",
"layout-settings": "إعدادات التخطيط",
"columns-count": "عدد الأعمدة", "columns-count": "عدد الأعمدة",
"columns-count-required": "عدد الأعمدة مطلوب.", "columns-count-required": "عدد الأعمدة مطلوب.",
"min-columns-count-message": "الحد الأدنى المسموح به هو 10 أعمدة.", "min-columns-count-message": "الحد الأدنى المسموح به هو 10 أعمدة.",

1
ui-ngx/src/assets/locale/locale.constant-ca_ES.json

@ -1056,7 +1056,6 @@
"maximum-upload-file-size": "Mida màxima del fitxer de pujada: {{ size }}", "maximum-upload-file-size": "Mida màxima del fitxer de pujada: {{ size }}",
"cannot-upload-file": "No s'ha pogut pujar el fitxer", "cannot-upload-file": "No s'ha pogut pujar el fitxer",
"settings": "Configuració", "settings": "Configuració",
"layout-settings": "Paràmetres de la disposició",
"columns-count": "Número de columnes", "columns-count": "Número de columnes",
"columns-count-required": "Cal número de columnes.", "columns-count-required": "Cal número de columnes.",
"min-columns-count-message": "Sol es permet un número mínim de 10 columnes.", "min-columns-count-message": "Sol es permet un número mínim de 10 columnes.",

1
ui-ngx/src/assets/locale/locale.constant-cs_CZ.json

@ -745,7 +745,6 @@
"maximum-upload-file-size": "Maximální velikost souboru: {{ size }}", "maximum-upload-file-size": "Maximální velikost souboru: {{ size }}",
"cannot-upload-file": "Soubor nelze nahrát", "cannot-upload-file": "Soubor nelze nahrát",
"settings": "Nastavení", "settings": "Nastavení",
"layout-settings": "Nastavení rozložení",
"columns-count": "Počet sloupců", "columns-count": "Počet sloupců",
"columns-count-required": "Počet sloupců je povinný.", "columns-count-required": "Počet sloupců je povinný.",
"min-columns-count-message": "Minimální povolený počet sloupců je 10.", "min-columns-count-message": "Minimální povolený počet sloupců je 10.",

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

@ -1172,7 +1172,7 @@
"layout-type-default": "Default", "layout-type-default": "Default",
"layout-type-scada": "SCADA", "layout-type-scada": "SCADA",
"layout-type-divider": "Divider", "layout-type-divider": "Divider",
"layout-settings": "Layout settings", "layout-settings-type": "Layout settings: {{ type }}",
"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.",
@ -1198,6 +1198,10 @@
"mobile-row-height-required": "Mobile row height value is required.", "mobile-row-height-required": "Mobile row height value is required.",
"min-mobile-row-height-message": "Only 5 pixels is allowed as minimum mobile row height value.", "min-mobile-row-height-message": "Only 5 pixels is allowed as minimum mobile row height value.",
"max-mobile-row-height-message": "Only 200 pixels is allowed as maximum mobile row height value.", "max-mobile-row-height-message": "Only 200 pixels is allowed as maximum mobile row height value.",
"row-height": "Row height",
"row-height-required": "Row height value is required.",
"min-row-height-message": "Only 5 pixels is allowed as minimum row height value.",
"max-row-height-message": "Only 200 pixels is allowed as maximum row height value.",
"display-first-in-mobile-view": "Display first in mobile view", "display-first-in-mobile-view": "Display first in mobile view",
"title-settings": "Title settings", "title-settings": "Title settings",
"display-title": "Display dashboard title", "display-title": "Display dashboard title",
@ -1276,7 +1280,19 @@
"assign-dashboard-to-edge-text": "Please select the dashboards to assign to the edge", "assign-dashboard-to-edge-text": "Please select the dashboards to assign to the edge",
"non-existent-dashboard-state-error": "Dashboard state with id \"{{ stateId }}\" is not found", "non-existent-dashboard-state-error": "Dashboard state with id \"{{ stateId }}\" is not found",
"edit-mode": "Edit mode", "edit-mode": "Edit mode",
"duplicate-state-action": "Duplicate state" "duplicate-state-action": "Duplicate state",
"breakpoint-value": "Breakpoint ({{ value }})",
"breakpoints-id": {
"default": "Default",
"xs": "Mobile (xs)",
"sm": "Tablet (sm)",
"md": "Laptop (md)",
"lg": "Desktop (lg)",
"xl": "Desktop (xl)"
},
"view-format-type-grid": "Grid",
"view-format-type-list": "List",
"view-format": "View format"
}, },
"datakey": { "datakey": {
"settings": "Settings", "settings": "Settings",
@ -5951,7 +5967,8 @@
"card-appearance": "Card appearance", "card-appearance": "Card appearance",
"color": "Color", "color": "Color",
"tooltip": "Tooltip", "tooltip": "Tooltip",
"units-required": "Unit is required." "units-required": "Unit is required.",
"list-option": "List option"
}, },
"widget-type": { "widget-type": {
"import": "Import widget type", "import": "Import widget type",

1
ui-ngx/src/assets/locale/locale.constant-es_ES.json

@ -1066,7 +1066,6 @@
"maximum-upload-file-size": "Tamaño máximo de fichero: {{ size }}", "maximum-upload-file-size": "Tamaño máximo de fichero: {{ size }}",
"cannot-upload-file": "No es posible subir el fichero", "cannot-upload-file": "No es posible subir el fichero",
"settings": "Ajustes", "settings": "Ajustes",
"layout-settings": "Ajustes de diseño",
"columns-count": "Número de columnas", "columns-count": "Número de columnas",
"columns-count-required": "Número de columnas requerido.", "columns-count-required": "Número de columnas requerido.",
"min-columns-count-message": "Solo se permite un número mínimo de 10 columnas.", "min-columns-count-message": "Solo se permite un número mínimo de 10 columnas.",

1
ui-ngx/src/assets/locale/locale.constant-fr_FR.json

@ -830,7 +830,6 @@
"empty-image": "Aucune image", "empty-image": "Aucune image",
"maximum-upload-file-size": "Taille de fichier maximum: {{ size }}", "maximum-upload-file-size": "Taille de fichier maximum: {{ size }}",
"cannot-upload-file": "Le téléchargement a échoué", "cannot-upload-file": "Le téléchargement a échoué",
"layout-settings": "Paramètres de mise en page",
"margin-required": "Valeur de marge requise.", "margin-required": "Valeur de marge requise.",
"min-margin-message": "0 est la valeur minimum permise.", "min-margin-message": "0 est la valeur minimum permise.",
"max-margin-message": "50 est la valeur maximum permise.", "max-margin-message": "50 est la valeur maximum permise.",

1
ui-ngx/src/assets/locale/locale.constant-lt_LT.json

@ -1256,7 +1256,6 @@
"maximum-upload-file-size": "Maksimalus įkeliamo failo dydis: {{ size }}", "maximum-upload-file-size": "Maksimalus įkeliamo failo dydis: {{ size }}",
"cannot-upload-file": "Failo įkelti nepavyko", "cannot-upload-file": "Failo įkelti nepavyko",
"settings": "Nustatymai", "settings": "Nustatymai",
"layout-settings": "Išdėstymo nustatymai",
"columns-count": "Stulpelių skaičius", "columns-count": "Stulpelių skaičius",
"columns-count-required": "Stulpelių skaičius būtinas.", "columns-count-required": "Stulpelių skaičius būtinas.",
"min-columns-count-message": "Minimalus stulpelių skaičius - 10.", "min-columns-count-message": "Minimalus stulpelių skaičius - 10.",

1
ui-ngx/src/assets/locale/locale.constant-nl_BE.json

@ -1168,7 +1168,6 @@
"maximum-upload-file-size": "Maximale bestandsgrootte uploaden: {{ size }}", "maximum-upload-file-size": "Maximale bestandsgrootte uploaden: {{ size }}",
"cannot-upload-file": "Kan bestand niet uploaden", "cannot-upload-file": "Kan bestand niet uploaden",
"settings": "Instellingen", "settings": "Instellingen",
"layout-settings": "Lay-out instellingen",
"columns-count": "Aantal kolommen", "columns-count": "Aantal kolommen",
"columns-count-required": "Het aantal kolommen is vereist.", "columns-count-required": "Het aantal kolommen is vereist.",
"min-columns-count-message": "Er is slechts een minimum van 10 kolommen toegestaan.", "min-columns-count-message": "Er is slechts een minimum van 10 kolommen toegestaan.",

1
ui-ngx/src/assets/locale/locale.constant-pl_PL.json

@ -1256,7 +1256,6 @@
"maximum-upload-file-size": "Maksymalny rozmiar przesyłanego pliku: {{ size }}", "maximum-upload-file-size": "Maksymalny rozmiar przesyłanego pliku: {{ size }}",
"cannot-upload-file": "Nie można przesłać pliku", "cannot-upload-file": "Nie można przesłać pliku",
"settings": "Ustawienia", "settings": "Ustawienia",
"layout-settings": "Ustawienia układu",
"columns-count": "Liczba kolumn", "columns-count": "Liczba kolumn",
"columns-count-required": "Liczba kolumn jest wymagana.", "columns-count-required": "Liczba kolumn jest wymagana.",
"min-columns-count-message": "Dozwolona jest tylko minimalna liczba kolumn wynosząca 10.", "min-columns-count-message": "Dozwolona jest tylko minimalna liczba kolumn wynosząca 10.",

1
ui-ngx/src/assets/locale/locale.constant-tr_TR.json

@ -748,7 +748,6 @@
"maximum-upload-file-size": "Maksimum yükleme dosyası boyutu: {{ size }}", "maximum-upload-file-size": "Maksimum yükleme dosyası boyutu: {{ size }}",
"cannot-upload-file": "Dosya yüklenemiyor", "cannot-upload-file": "Dosya yüklenemiyor",
"settings": "Ayarlar", "settings": "Ayarlar",
"layout-settings": "Görünüm ayarları",
"columns-count": "Sütun sayısı", "columns-count": "Sütun sayısı",
"columns-count-required": "Sütun sayısı gerekli.", "columns-count-required": "Sütun sayısı gerekli.",
"min-columns-count-message": "Yalnızca 10 minimum sütun sayısına izin verilir.", "min-columns-count-message": "Yalnızca 10 minimum sütun sayısına izin verilir.",

1
ui-ngx/src/assets/locale/locale.constant-zh_CN.json

@ -1097,7 +1097,6 @@
"maximum-upload-file-size": "最大上传文件大小: {{ size }}", "maximum-upload-file-size": "最大上传文件大小: {{ size }}",
"cannot-upload-file": "无法上传文件", "cannot-upload-file": "无法上传文件",
"settings": "设置", "settings": "设置",
"layout-settings": "布局设置",
"columns-count": "列数", "columns-count": "列数",
"columns-count-required": "需要列数。", "columns-count-required": "需要列数。",
"min-columns-count-message": "只允许最少10列", "min-columns-count-message": "只允许最少10列",

1
ui-ngx/src/assets/locale/locale.constant-zh_TW.json

@ -828,7 +828,6 @@
"maximum-upload-file-size": "最大上傳文件大小: {{ size }}", "maximum-upload-file-size": "最大上傳文件大小: {{ size }}",
"cannot-upload-file": "無法上傳文件", "cannot-upload-file": "無法上傳文件",
"settings": "設定", "settings": "設定",
"layout-settings": "佈局設定",
"columns-count": "列數", "columns-count": "列數",
"columns-count-required": "需要列數。", "columns-count-required": "需要列數。",
"min-columns-count-message": "只允許最少10列", "min-columns-count-message": "只允許最少10列",

Loading…
Cancel
Save