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 { TimeService } from '@core/services/time.service';
import {
BreakpointId, breakpointIdIconMap,
breakpointIdTranslationMap,
BreakpointInfo,
BreakpointLayoutInfo,
BreakpointSystemId,
Dashboard,
DashboardConfiguration,
DashboardLayout,
@ -41,7 +44,8 @@ import {
TargetDeviceType,
Widget,
WidgetConfig,
WidgetConfigMode, WidgetSize,
WidgetConfigMode,
WidgetSize,
widgetType,
WidgetTypeDescriptor
} 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 { BackgroundType, colorBackground, isBackgroundSettings } from '@shared/models/widget-settings.models';
import { MediaBreakpoints } from '@shared/models/constants';
import { TranslateService } from '@ngx-translate/core';
@Injectable({
providedIn: 'root'
})
export class DashboardUtilsService {
private systemBreakpoints: BreakpointInfo[];
private systemBreakpoints: {[key in BreakpointSystemId]?: BreakpointInfo};
constructor(private utils: UtilsService,
private timeService: TimeService) {
private timeService: TimeService,
private translate: TranslateService) {
}
public validateAndUpdateDashboard(dashboard: Dashboard): Dashboard {
@ -1001,13 +1007,18 @@ export class DashboardUtilsService {
}
private loadSystemBreakpoints() {
this.systemBreakpoints=[{id: 'default'}];
const dashboardMediaBreakpointIds = ['xs', 'sm', 'md', 'lg', 'xl'];
this.systemBreakpoints = {};
const dashboardMediaBreakpointIds: BreakpointSystemId[] = ['xs', 'sm', 'md', 'lg', 'xl'];
dashboardMediaBreakpointIds.forEach(breakpoint => {
const value = MediaBreakpoints[breakpoint];
const minWidth = value.match(/min-width:\s*(\d+)px/);
const maxWidth = value.match(/max-width:\s*(\d+)px/);
this.systemBreakpoints.push({id: breakpoint, minWidth: minWidth?.[1], maxWidth: maxWidth?.[1]});
const minWidth = value.match(/min-width:\s*(\d+)px/)?.[1];
const maxWidth = value.match(/max-width:\s*(\d+)px/)?.[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) {
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"
[widget]="widget"
[hideHeader]="hideHeader"
[scada]="data.scada"
[showLayoutConfig]="showLayoutConfig"
[isDefaultBreakpoint]="isDefaultBreakpoint"
isAdd
formControlName="widgetConfig">
</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;
widget: Widget;
widgetInfo: WidgetInfo;
scada: boolean;
showLayoutConfig: boolean;
isDefaultBreakpoint: boolean;
}
@Component({
@ -60,6 +61,9 @@ export class AddWidgetDialogComponent extends DialogComponent<AddWidgetDialogCom
stateController: IStateController;
widget: Widget;
showLayoutConfig = true;
isDefaultBreakpoint = true;
widgetConfig: WidgetConfigComponentData;
previewMode = false;
@ -92,6 +96,8 @@ export class AddWidgetDialogComponent extends DialogComponent<AddWidgetDialogCom
this.aliasController = this.data.aliasController;
this.stateController = this.data.stateController;
this.widget = this.data.widget;
this.showLayoutConfig = this.data.showLayoutConfig;
this.isDefaultBreakpoint = this.data.isDefaultBreakpoint;
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>
<ng-container *ngIf="isEdit">
<div style="flex: 1 1 100%" fxHide.gt-sm></div>
<div fxFlex.lt-md fxLayout="row"
fxLayoutAlign.gt-sm="start center"
fxLayoutAlign="end center" fxLayoutGap.lt-lg="6px" fxLayoutGap.gt-md="12px">
<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>
fxLayoutAlign="end center" fxLayoutGap.lt-lg="3px" fxLayoutGap.lg="6px" fxLayoutGap.gt-lg="12px">
<mat-divider class="tb-toolbar-divider" vertical></mat-divider>
<button fxHide.gt-md mat-icon-button
matTooltip="{{'layout.manage' | translate}}"
matTooltipPosition="below"
@ -73,6 +62,21 @@
[syncStateWithQueryParam]="syncStateWithQueryParam"
[states]="dashboardConfiguration.states">
</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"
aria-label="dashboard_logo" class="dashboard_logo"/>
</div>
@ -91,7 +95,7 @@
(click)="toggleLayouts()">
<mat-icon>{{isRightLayoutOpened ? 'arrow_back' : 'menu'}}</mat-icon>
</button>
<button [fxShow]="!hideFullscreenButton()" mat-icon-button
<button [fxShow]="!hideFullscreenButton()" mat-icon-button fxHide.xs
matTooltip="{{(isFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}"
matTooltipPosition="below"
(click)="isFullscreen = !isFullscreen">
@ -233,13 +237,13 @@
</ng-container>
</ng-template>
<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 }}"
matTooltipPosition="below"
(click)="addWidget($event)">
<mat-icon>add</mat-icon>
</button>
<button fxHide.lt-sm mat-stroked-button
<button fxHide.lt-lg mat-stroked-button
matTooltip="{{ 'dashboard.add-widget-button-text' | translate }}"
matTooltipPosition="below"
(click)="addWidget($event)">
@ -285,8 +289,7 @@
</mat-form-field>
</section>
<mat-drawer-container class="tb-dashboard-layouts" fxFlex
[class]="{'tb-shrinked' : isEditingWidget,
'diagonal-stripes': !layouts.right.show && layouts.main.layoutCtx.breakpoint !== 'default'}">
[class]="{'tb-shrinked' : isEditingWidget}">
<mat-drawer *ngIf="layouts.right.show"
id="tb-right-layout"
[ngStyle]="{minWidth: rightLayoutSize.width,
@ -313,9 +316,10 @@
<mat-drawer-content [fxShow]="layouts.main.show"
id="tb-main-layout"
[style]="{width: mainLayoutSize.width,
height: mainLayoutSize.height,
maxWidth: mainLayoutSize.maxWidth}">
height: mainLayoutSize.height}">
<tb-dashboard-layout
[style.max-width]="mainLayoutSize.maxWidth"
[style.min-width]="mainLayoutSize.minWidth"
[dashboardCheatSheet]="cheatSheetComponent"
[layoutCtx]="mobileDisplayRightLayoutFirst ? layouts.right.layoutCtx : layouts.main.layoutCtx"
[dashboardCtx]="dashboardCtx"
@ -326,6 +330,11 @@
[parentDashboard]="parentDashboard"
[popoverComponent]="popoverComponent">
</tb-dashboard-layout>
<div class="layout-filler"
*ngIf="isEdit"
[fxShow]="showMainLayoutFiller">
{{ 'dashboard.breakpoint-value' | translate : {value: currentBreakpointValue } }}
</div>
</mat-drawer-content>
</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)">
@ -385,7 +394,8 @@
[widgetEditMode]="widgetEditMode"
[widget]="editingWidget"
[widgetLayout]="editingWidgetLayout"
[scada]="editingLayoutCtx?.gridSettings?.layoutType === LayoutType.scada"
[showLayoutConfig]="showLayoutConfigInEdit(editingLayoutCtx)"
[isDefaultBreakpoint]="editingLayoutCtx?.breakpoint === 'default'"
(revertWidgetConfig)="onRevertWidgetEdit()"
(applyWidgetConfig)="saveWidget()">
</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));
}
}
&.diagonal-stripes {
background: repeating-linear-gradient(45deg, #ccc, #ccc 10px, #fff0 10px, #fff0 20px);
#tb-main-layout {
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 { UtilsService } from '@core/services/utils.service';
import {
BreakpointId,
BreakpointInfo,
Dashboard,
DashboardConfiguration,
DashboardLayoutId,
@ -51,6 +53,7 @@ import {
GridSettings,
LayoutDimension,
LayoutType,
ViewFormatType,
WidgetLayout
} from '@app/shared/models/dashboard.models';
import { WINDOW } from '@core/services/window.service';
@ -92,7 +95,6 @@ import {
WidgetContextMenuItem
} from '../../models/dashboard-component.models';
import { WidgetComponentService } from '../../components/widget/widget-component.service';
import { UntypedFormBuilder } from '@angular/forms';
import { ItemBufferService } from '@core/services/item-buffer.service';
import { MatDialog } from '@angular/material/dialog';
import {
@ -261,7 +263,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
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%'};
private dashboardLogoCache: SafeUrl;
@ -363,7 +366,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
private itembuffer: ItemBufferService,
private importExport: ImportExportService,
private mobileService: MobileService,
private fb: UntypedFormBuilder,
private dialog: MatDialog,
public translate: TranslateService,
private popoverService: TbPopoverService,
@ -376,8 +378,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
public elRef: ElementRef,
private injector: Injector) {
super(store);
if (isDefinedAndNotNull(embeddedValue)) {
this.embedded = embeddedValue;
if (isDefinedAndNotNull(this.embeddedValue)) {
this.embedded = this.embeddedValue;
}
}
@ -427,40 +429,26 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
);
this.rxSubscriptions.push(
this.breakpointObserver.observe([
MediaBreakpoints.xs,
MediaBreakpoints.sm,
MediaBreakpoints.md,
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';
}
}),
this.breakpointObserver.observe(
this.dashboardUtils.getBreakpoints()
).pipe(
map(value => this.parseBreakpointsResponse(value.breakpoints)),
tap((value) => {
this.dashboardCtx.breakpoint = value;
this.changeMobileSize.next(value === 'xs' || value === 'sm');
this.dashboardCtx.breakpoint = value.id;
this.changeMobileSize.next(this.isMobileSize(value));
}),
distinctUntilChanged((prev, next) => {
distinctUntilChanged((_, next) => {
if (this.layouts.right.show || this.isEdit) {
return true;
}
const allowAdditionalPrevLayout = !!this.layouts.main.layoutCtx.layoutData?.[prev];
const allowAdditionalNextLayout = !!this.layouts.main.layoutCtx?.layoutData?.[next];
return !(allowAdditionalNextLayout || allowAdditionalPrevLayout !== allowAdditionalNextLayout);
let nextBreakpointConfiguration: BreakpointId = 'default';
if (!!this.layouts.main.layoutCtx.layoutData?.[next.id]) {
nextBreakpointConfiguration = next.id;
}
return this.layouts.main.layoutCtx.breakpoint === nextBreakpointConfiguration;
}),
skip(1)
).subscribe((value) => {
).subscribe(() => {
this.layouts.main.layoutCtx.ctrl.updatedCurrentBreakpoint();
this.updateLayoutSizes();
}
@ -639,7 +627,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
}
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 {
@ -780,33 +768,28 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
} else {
this.mainLayoutSize.height = '0px';
}
if (this.isEdit) {
if (this.isEdit && !this.isEditingWidget) {
const xOffset = this.dashboardContainer.nativeElement.getBoundingClientRect().x;
let width;
switch (this.layouts.main.layoutCtx.breakpoint) {
case 'xl':
width = `${5000 - xOffset}px`;
break;
case 'lg':
width = `${1919 - xOffset}px`;
break;
case 'md':
width = `${1279 - xOffset}px`;
break;
case 'sm':
width = '959px';
break;
case 'xs':
width = '599px';
break;
default:
width = '100%';
const breakpoint = this.dashboardUtils.getBreakpointInfoById(this.layouts.main.layoutCtx.breakpoint);
let maxWidth: string;
if (breakpoint?.maxWidth) {
if (this.isMobileSize(breakpoint)) {
maxWidth = `${breakpoint.maxWidth}px`;
} else {
maxWidth = `${breakpoint.maxWidth - xOffset}px`;
}
} else {
maxWidth = '100%';
}
this.mainLayoutSize.maxWidth = width;
const minWidth = breakpoint?.minWidth ? `${breakpoint.minWidth}px` : undefined;
this.mainLayoutSize.maxWidth = maxWidth;
this.mainLayoutSize.minWidth = minWidth;
} else {
this.mainLayoutSize.maxWidth = '100%';
this.mainLayoutSize.minWidth = undefined;
}
return prevMainLayoutWidth !== this.mainLayoutSize.width || prevMainLayoutHeight !== this.mainLayoutSize.height ||
prevMainLayoutMaxWidth !== this.mainLayoutSize.maxWidth;
@ -945,6 +928,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
data: {
settings: deepClone(this.dashboard.configuration.settings),
gridSettings,
breakpointId: this.layouts.main.layoutCtx.breakpoint
}
}).afterClosed().subscribe((data) => {
if (data) {
@ -1327,10 +1311,20 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
col: 0
};
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();
if (scada) {
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) {
this.addWidgetToDashboard(newWidget);
} else {
@ -1346,7 +1340,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
stateController: this.dashboardCtx.stateController,
widget: newWidget,
widgetInfo: widgetTypeInfo,
scada
showLayoutConfig,
isDefaultBreakpoint
}
}).afterClosed().subscribe((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) {
$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 {
BreakpointId,
Dashboard,
DashboardLayoutId,
DashboardLayoutInfo,
@ -41,7 +42,7 @@ export interface DashboardPageInitData {
export interface DashboardContext {
instanceId: string;
state: string;
breakpoint: string;
breakpoint: BreakpointId;
getDashboard: () => Dashboard;
dashboardTimewindow: Timewindow;
aliasController: IAliasController;
@ -75,7 +76,7 @@ export interface DashboardPageLayoutContext {
id: DashboardLayoutId;
layoutData: DashboardLayoutInfo;
layoutDataChanged: BehaviorSubject<void>;
breakpoint: string;
breakpoint: BreakpointId;
widgets: LayoutWidgetsArray;
widgetLayouts: WidgetLayouts;
gridSettings: GridSettings;

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

@ -105,18 +105,18 @@
</mat-expansion-panel>
</div>
<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-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 translate>dashboard.columns-count</div>
<mat-form-field *ngIf="!isScada()"
@ -188,6 +188,25 @@
{{ 'dashboard.autofill-height' | translate }}
</mat-slide-toggle>
</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 translate>dashboard.background-color</div>
<tb-color-input asBoxInput
@ -212,7 +231,7 @@
</mat-form-field>
</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 *ngIf="isRightLayout" class="tb-form-row">
<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.
///
import { Component, Inject, OnInit, SkipSelf } from '@angular/core';
import { Component, Inject, OnDestroy, OnInit, SkipSelf } from '@angular/core';
import { ErrorStateMatcher } from '@angular/material/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
@ -24,20 +24,26 @@ import { Router } from '@angular/router';
import { DialogComponent } from '@app/shared/components/dialog.component';
import { TranslateService } from '@ngx-translate/core';
import {
BreakpointId,
DashboardSettings,
GridSettings,
LayoutType,
layoutTypes, layoutTypeTranslationMap,
StateControllerId
StateControllerId,
ViewFormatType,
viewFormatTypes,
viewFormatTypeTranslationMap
} from '@app/shared/models/dashboard.models';
import { isDefined, isUndefined } from '@core/utils';
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 {
settings?: DashboardSettings;
gridSettings?: GridSettings;
isRightLayout?: boolean;
breakpointId?: BreakpointId;
}
@Component({
@ -47,11 +53,10 @@ export interface DashboardSettingsDialogData {
styleUrls: ['./dashboard-settings-dialog.component.scss']
})
export class DashboardSettingsDialogComponent extends DialogComponent<DashboardSettingsDialogComponent, DashboardSettingsDialogData>
implements OnInit, ErrorStateMatcher {
implements OnDestroy, ErrorStateMatcher {
layoutTypes = layoutTypes;
layoutTypeTranslations = layoutTypeTranslationMap;
viewFormatTypes = viewFormatTypes;
viewFormatTypeTranslationMap = viewFormatTypeTranslationMap;
settings: DashboardSettings;
gridSettings: GridSettings;
@ -62,10 +67,17 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
stateControllerIds: string[];
layoutSettingsType: string;
submitted = false;
scadaColumns: number[] = [];
private layoutType = LayoutType.default;
private breakpointId: BreakpointId = 'default';
private destroy$ = new Subject<void>();
private stateControllerTranslationMap = new Map<string, string>([
['default', 'dashboard.state-controller-default'],
]);
@ -77,7 +89,8 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
public dialogRef: MatDialogRef<DashboardSettingsDialogComponent, DashboardSettingsDialogData>,
private fb: FormBuilder,
private translate: TranslateService,
private statesControllerService: StatesControllerService) {
private statesControllerService: StatesControllerService,
private dashboardUtils: DashboardUtilsService) {
super(store, router, dialogRef);
this.stateControllerIds = Object.keys(this.statesControllerService.getStateControllers());
@ -85,6 +98,7 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
this.settings = this.data.settings;
this.gridSettings = this.data.gridSettings;
this.isRightLayout = this.data.isRightLayout;
this.breakpointId = this.data.breakpointId;
if (this.settings) {
const showTitle = isUndefined(this.settings.showTitle) ? true : this.settings.showTitle;
@ -116,14 +130,18 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
disabled: hideToolbar}, []],
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) => {
if (stateControllerId !== 'default') {
this.settingsFormGroup.get('toolbarAlwaysOpen').setValue(true);
}
}
);
this.settingsFormGroup.get('showTitle').valueChanges.subscribe(
this.settingsFormGroup.get('showTitle').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(
(showTitleValue: boolean) => {
if (showTitleValue) {
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) => {
if (showDashboardLogoValue) {
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) => {
if (hideToolbarValue) {
this.settingsFormGroup.get('toolbarAlwaysOpen').disable();
@ -167,6 +189,16 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
}
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;
while (columns <= 1008) {
this.scadaColumns.push(columns);
@ -174,14 +206,16 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
}
const mobileAutoFillHeight = isUndefined(this.gridSettings.mobileAutoFillHeight) ? false : this.gridSettings.mobileAutoFillHeight;
this.gridSettingsFormGroup = this.fb.group({
layoutType: [this.gridSettings.layoutType || LayoutType.default, []],
columns: [this.gridSettings.columns || 24, [Validators.required, Validators.min(10), Validators.max(1008)]],
minColumns: [this.gridSettings.minColumns || this.gridSettings.columns || 24,
[Validators.required, Validators.min(10), Validators.max(1008)]],
margin: [isDefined(this.gridSettings.margin) ? this.gridSettings.margin : 10,
[Validators.required, Validators.min(0), Validators.max(50)]],
outerMargin: [isUndefined(this.gridSettings.outerMargin) ? true : this.gridSettings.outerMargin, []],
viewFormat: [isUndefined(this.gridSettings.viewFormat) ? ViewFormatType.grid : this.gridSettings.viewFormat, []],
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)', []],
backgroundImageUrl: [this.gridSettings.backgroundImageUrl, []],
backgroundSizeMode: [this.gridSettings.backgroundSizeMode || '100%', []],
@ -194,27 +228,56 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
isUndefined(this.gridSettings.mobileDisplayLayoutFirst) ? false : this.gridSettings.mobileDisplayLayoutFirst;
this.gridSettingsFormGroup.addControl('mobileDisplayLayoutFirst', this.fb.control(mobileDisplayLayoutFirst, []));
}
merge(this.gridSettingsFormGroup.get('layoutType').valueChanges,
this.gridSettingsFormGroup.get('mobileAutoFillHeight').valueChanges).subscribe(
() => {
this.updateGridSettingsFormState();
if (this.isScada()) {
this.gridSettingsFormGroup.get('margin').disable();
this.gridSettingsFormGroup.get('outerMargin').disable();
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);
}
);
this.updateGridSettingsFormState();
} else {
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 {
this.gridSettingsFormGroup = this.fb.group({});
}
}
ngOnInit(): void {
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
isScada() {
if (this.gridSettingsFormGroup) {
return this.gridSettingsFormGroup.get('layoutType').value === LayoutType.scada;
} else {
return false;
}
return this.layoutType === LayoutType.scada;
}
get isDefaultLayout() {
return this.layoutType === LayoutType.default;
}
get isDefaultBreakpoint(): boolean {
return this.breakpointId === 'default';
}
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
@ -248,29 +311,28 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
}
private updateGridSettingsFormState() {
const isScada: boolean = this.gridSettingsFormGroup.get('layoutType').value === LayoutType.scada;
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});
if (this.breakpointId === 'default') {
const mobileAutoFillHeight: boolean = this.gridSettingsFormGroup.get('mobileAutoFillHeight').value;
if (mobileAutoFillHeight) {
this.gridSettingsFormGroup.get('mobileRowHeight').disable();
} else {
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} {
gap: 6px;
&.tb-left-panel {
gap: 3px;
}
}
@media #{$mat-md} {
gap: 6px;
gap: 3px;
&.tb-left-panel {
gap: 1px;
}
}
@media #{$mat-gt-md} {
gap: 12px;
gap: 6px;
&.tb-left-panel {
gap: 3px;
}
}
@media #{$mat-gt-sm} {

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

@ -24,7 +24,8 @@
[dashboard]="dashboard"
[widget]="widget"
[hideToggleHeader]="previewMode"
[scada]="scada"
[showLayoutConfig]="showLayoutConfig"
[isDefaultBreakpoint]="isDefaultBreakpoint"
formControlName="widgetConfig">
<div class="tb-widget-config-header-prefix">
<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()
@coerceBoolean()
scada = false;
showLayoutConfig = true;
@Input()
@coerceBoolean()
isDefaultBreakpoint= true;
@Output()
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">
<mat-form-field appearance="outline">
<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">
{{ 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-select>
</mat-form-field>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<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">
{{ 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-select>
</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 { AppState } from '@core/core.state';
import { Router } from '@angular/router';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
import { BreakpointId } from '@shared/models/dashboard.models';
export interface AddNewBreakpointDialogData {
allowBreakpointIds: string[];
@ -28,8 +30,8 @@ export interface AddNewBreakpointDialogData {
}
export interface AddNewBreakpointDialogResult {
newBreakpointId: string;
copyFrom: string;
newBreakpointId: BreakpointId;
copyFrom: BreakpointId;
}
@Component({
@ -47,12 +49,13 @@ export class AddNewBreakpointDialogComponent extends DialogComponent<AddNewBreak
protected router: Router,
private fb: FormBuilder,
@Inject(MAT_DIALOG_DATA) private data: AddNewBreakpointDialogData,
protected dialogRef: MatDialogRef<AddNewBreakpointDialogComponent, AddNewBreakpointDialogResult>,) {
protected dialogRef: MatDialogRef<AddNewBreakpointDialogComponent, AddNewBreakpointDialogResult>,
private dashboardUtils: DashboardUtilsService,) {
super(store, router, dialogRef);
this.allowBreakpointIds = data.allowBreakpointIds;
this.selectedBreakpointIds = data.selectedBreakpointIds;
this.allowBreakpointIds = this.data.allowBreakpointIds;
this.selectedBreakpointIds = this.data.selectedBreakpointIds;
this.addBreakpointFormGroup = this.fb.group({
newBreakpointId: [{value: this.allowBreakpointIds[0], disabled: this.allowBreakpointIds.length === 1}],
@ -67,4 +70,16 @@ export class AddNewBreakpointDialogComponent extends DialogComponent<AddNewBreak
save(): void {
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"
[autofillHeight]="autoFillHeight"
[mobileAutofillHeight]="mobileAutoFillHeight"
[mobileRowHeight]="layoutCtx.gridSettings.mobileRowHeight"
[isMobile]="isMobile"
[mobileRowHeight]="mobielRowHeigth"
[isMobile]="isMobileValue"
[isMobileDisabled]="isMobileDisabled"
[disableWidgetInteraction]="isEdit"
[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 { map } from 'rxjs/operators';
import { displayGrids } from 'angular-gridster2/lib/gridsterConfig.interface';
import { LayoutType } from '@shared/models/dashboard.models';
import { deepClone, isNotEmptyStr } from '@core/utils';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
import { BreakpointId, LayoutType, ViewFormatType } from '@shared/models/dashboard.models';
import { isNotEmptyStr } from '@core/utils';
@Component({
selector: 'tb-dashboard-layout',
@ -87,11 +86,27 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo
}
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 {
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 {
@ -137,7 +152,6 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo
private itembuffer: ItemBufferService,
private imagePipe: ImagePipe,
private sanitizer: DomSanitizer,
private dashboardUtils: DashboardUtilsService,
) {
super(store);
this.initHotKeys();
@ -293,11 +307,11 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo
this.layoutCtx.dashboardCtrl.widgetClicked($event, this.layoutCtx, widget);
}
prepareDashboardContextMenu($event: Event): Array<DashboardContextMenuItem> {
prepareDashboardContextMenu(_: Event): Array<DashboardContextMenuItem> {
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);
}
@ -319,12 +333,16 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo
this.layoutCtx.dashboardCtrl.pasteWidgetReference($event, this.layoutCtx, pos);
}
updatedCurrentBreakpoint(breakpoint?: string, showLayout = true) {
if (!isNotEmptyStr(breakpoint)) {
breakpoint = this.dashboardCtx.breakpoint;
updatedCurrentBreakpoint(breakpointId?: BreakpointId, showLayout = true) {
if (!isNotEmptyStr(breakpointId)) {
breakpointId = this.dashboardCtx.breakpoint;
}
this.layoutCtx.breakpoint = breakpoint;
const layoutInfo = this.getLayoutDataForBreakpoint(breakpoint);
if (this.layoutCtx.layoutData[breakpointId]) {
this.layoutCtx.breakpoint = breakpointId;
} else {
this.layoutCtx.breakpoint = 'default';
}
const layoutInfo = this.layoutCtx.layoutData[this.layoutCtx.breakpoint];
if (layoutInfo.gridSettings) {
this.layoutCtx.gridSettings = layoutInfo.gridSettings;
}
@ -335,12 +353,4 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo
}
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>
</button>
</mat-toolbar>
<div mat-dialog-content fxLayout="column" fxLayoutGap="20px" style="min-width: 300px; overflow: hidden;">
<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 *ngIf="!isDividerLayout; else dividerLayout" class="tb-form-panel stroked" style="width: 500px">
<div class="tb-form-table tb-layout-breakpoints">
<div class="tb-form-table-header no-padding-right">
<div class="tb-form-table-header-cell tb-icon"></div>
<div class="tb-form-table-header-cell tb-breakpoint" translate>layout.breakpoints</div>
<div class="tb-form-table-header-cell tb-size" translate>layout.size</div>
<div class="tb-form-table-header-cell tb-actions"></div>
</div>
<div class="tb-form-table-body no-gap">
<ng-container *ngFor="let breakpoint of layoutBreakpoints; let last = last">
<div class="tb-form-table-row no-padding-right">
<div class="tb-form-table-row-cell tb-icon">
<tb-icon>{{ breakpoint.icon }}</tb-icon>
</div>
<div class="tb-form-table-row-cell tb-breakpoint">
{{ breakpoint.name }}
</div>
<div class="tb-form-table-row-cell tb-size">
{{ breakpoint.descriptionSize }}
</div>
<div class="tb-form-table-row-cell-buttons tb-actions">
<button mat-icon-button
matTooltip="{{ 'dashboard.layout-settings' | translate }}"
matTooltipPosition="above"
(click)="openLayoutSettings('main', breakpoint.breakpoint)"
type="button" >
<mat-icon>settings</mat-icon>
</button>
<button mat-icon-button
matTooltip="{{ 'action.delete' | translate }}"
matTooltipPosition="above"
[disabled]="breakpoint.breakpoint === 'default'"
[class.tb-hidden]="breakpoint.breakpoint === 'default'"
(click)="deleteBreakpoint($event, breakpoint.breakpoint)"
type="button" >
<tb-icon>delete</tb-icon>
</button>
<div mat-dialog-content style="min-width: 300px; overflow: hidden;">
<section class="tb-form-panel no-padding no-border">
<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 *ngIf="!isDividerLayout; else dividerLayout" class="tb-form-panel stroked">
<div class="tb-form-table tb-layout-breakpoints">
<div class="tb-form-table-header no-padding-right">
<div class="tb-form-table-header-cell tb-icon"></div>
<div class="tb-form-table-header-cell tb-breakpoint" translate>layout.breakpoints</div>
<div class="tb-form-table-header-cell tb-size" translate>layout.size</div>
<div class="tb-form-table-header-cell tb-actions"></div>
</div>
<div class="tb-form-table-body no-gap">
<ng-container *ngFor="let breakpoint of layoutBreakpoints; let last = last">
<div class="tb-form-table-row no-padding-right">
<div class="tb-form-table-row-cell tb-icon">
<tb-icon>{{ breakpoint.icon }}</tb-icon>
</div>
<div class="tb-form-table-row-cell tb-breakpoint">
{{ breakpoint.name }}
</div>
<div class="tb-form-table-row-cell tb-size" tbTruncateWithTooltip>
{{ breakpoint.descriptionSize }}
</div>
<div class="tb-form-table-row-cell-buttons tb-actions">
<button mat-icon-button
matTooltip="{{ 'layout.settings' | translate }}"
matTooltipPosition="above"
(click)="openLayoutSettings('main', breakpoint.breakpoint)"
type="button" >
<mat-icon>settings</mat-icon>
</button>
<button mat-icon-button
matTooltip="{{ 'action.delete' | translate }}"
matTooltipPosition="above"
[disabled]="breakpoint.breakpoint === 'default'"
[class.tb-hidden]="breakpoint.breakpoint === 'default'"
(click)="deleteBreakpoint($event, breakpoint.breakpoint)"
type="button" >
<tb-icon>delete</tb-icon>
</button>
</div>
</div>
</div>
<mat-divider *ngIf="!last"></mat-divider>
</ng-container>
<mat-divider *ngIf="!last"></mat-divider>
</ng-container>
</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>
<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>
</section>
<ng-template #dividerLayout>
<div fxLayout="column" fxLayoutAlign="start center" fxLayoutGap="20px">
<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">
<button mat-icon-button
type="button"
matTooltip="{{ 'dashboard.layout-settings' | translate }}"
matTooltip="{{ 'layout.settings' | translate }}"
matTooltipPosition="above"
(click)="openLayoutSettings('main')"
class="tb-layout-preview-element"
@ -155,7 +157,7 @@
<div fxLayout="column" fxLayoutAlign="start center" fxFlex *ngIf="isDividerLayout" matTooltip="">
<button mat-icon-button
type="button"
matTooltip="{{ 'dashboard.layout-settings' | translate }}"
matTooltip="{{ 'layout.settings' | translate }}"
matTooltipPosition="above"
(click)="openLayoutSettings('right')"
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 {
flex: 1 1 85%;
width: 85%;
min-width: 150px;
width: 200px;
}
&.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 {
AbstractControl,
FormGroupDirective,
NgForm,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
FormGroupDirective,
NgForm,
Validators
} from '@angular/forms';
import { Router } from '@angular/router';
import { DialogComponent } from '@app/shared/components/dialog.component';
import { TranslateService } from '@ngx-translate/core';
import {
BreakpointInfo,
BreakpointId,
DashboardLayout,
DashboardLayoutId,
DashboardStateLayouts,
LayoutDimension, LayoutType, layoutTypes,
layoutTypeTranslationMap
LayoutDimension,
LayoutType,
layoutTypes,
layoutTypeTranslationMap,
ViewFormatType
} from '@app/shared/models/dashboard.models';
import { deepClone, isDefined, isEqual } from '@core/utils';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
@ -53,7 +56,9 @@ import {
import { Subscription } from 'rxjs';
import { MatTooltip } from '@angular/material/tooltip';
import {
AddNewBreakpointDialogComponent, AddNewBreakpointDialogData, AddNewBreakpointDialogResult
AddNewBreakpointDialogComponent,
AddNewBreakpointDialogData,
AddNewBreakpointDialogResult
} from '@home/components/dashboard-page/layout/add-new-breakpoint-dialog.component';
import { DialogService } from '@core/services/dialog.service';
@ -98,9 +103,6 @@ export class ManageDashboardLayoutsDialogComponent extends DialogComponent<Manag
private submitted = false;
private breakpoints: BreakpointInfo[];
private breakpointsData: {[breakpointId in string]: BreakpointInfo} = {};
allowBreakpointIds = [];
selectedBreakpointIds = ['default'];
@ -118,11 +120,6 @@ export class ManageDashboardLayoutsDialogComponent extends DialogComponent<Manag
this.layouts = this.data.layouts;
this.breakpoints = this.dashboardUtils.getListBreakpoint();
this.breakpoints.forEach((breakpoint) => {
this.breakpointsData[breakpoint.id] = breakpoint;
});
let layoutType = LayoutType.default;
if (isDefined(this.layouts.right)) {
layoutType = LayoutType.divider;
@ -201,14 +198,15 @@ export class ManageDashboardLayoutsDialogComponent extends DialogComponent<Manag
this.addLayoutConfiguration('default');
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.selectedBreakpointIds.push(breakpoint);
}
}
this.allowBreakpointIds = Object.keys(this.breakpointsData)
.filter((item) => !this.selectedBreakpointIds.includes(item));
this.allowBreakpointIds = Object.values(this.dashboardUtils.getListBreakpoint())
.filter((item) => !this.selectedBreakpointIds.includes(item.id))
.map(item => item.id);
this.subscriptions.push(
this.layoutsFormGroup.get('sliderPercentage').valueChanges
@ -261,9 +259,10 @@ export class ManageDashboardLayoutsDialogComponent extends DialogComponent<Manag
return originalErrorState || customErrorState;
}
openLayoutSettings(layoutId: DashboardLayoutId, breakpointId = 'default') {
openLayoutSettings(layoutId: DashboardLayoutId, breakpointId: BreakpointId = 'default') {
const layout = this.dashboardUtils.getDashboardLayoutConfig(this.layouts[layoutId], breakpointId);
const gridSettings = layout.gridSettings;
gridSettings.layoutType = this.layoutsFormGroup.get('layoutType').value;
this.dialog.open<DashboardSettingsDialogComponent, DashboardSettingsDialogData,
DashboardSettingsDialogData>(DashboardSettingsDialogComponent, {
disableClose: true,
@ -271,7 +270,8 @@ export class ManageDashboardLayoutsDialogComponent extends DialogComponent<Manag
data: {
settings: null,
gridSettings,
isRightLayout: this.isDividerLayout && layoutId === 'right'
isRightLayout: this.isDividerLayout && layoutId === 'right',
breakpointId
}
}).afterClosed().subscribe((data) => {
if (data && data.gridSettings) {
@ -302,7 +302,7 @@ export class ManageDashboardLayoutsDialogComponent extends DialogComponent<Manag
if (this.layouts.right?.gridSettings) {
delete this.layouts.right.gridSettings.layoutDimension;
}
if (this.layoutsFormGroup.value.right) {
if (this.isDividerLayout) {
const formValues = this.layoutsFormGroup.value;
const widthType = formValues.type;
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}`;
}
private layoutControlChange(key: string, value) {
private layoutControlChange(key: string, value: number) {
const valueToSet = 100 - Number(value);
this.layoutsFormGroup.get(key).setValue(valueToSet, {emitEvent: false});
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 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) {
layoutConfig.breakpoints = {};
}
layoutConfig.breakpoints[newBreakpointId] = {
gridSettings: deepClone(sourceLayout.gridSettings),
widgets: deepClone(sourceLayout.widgets),
gridSettings,
widgets,
};
this.selectedBreakpointIds.push(newBreakpointId);
this.allowBreakpointIds = this.allowBreakpointIds.filter((item) => item !== 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 size = breakpointId === 'default' ? '' : this.createDescriptionSize(breakpointId);
const size = breakpointId === 'default' ? '' : this.dashboardUtils.getBreakpointSizeDescription(breakpointId);
this.layoutBreakpoints.push({
icon: 'mdi:monitor',
name: breakpointId,
icon: this.dashboardUtils.getBreakpointIcon(breakpointId),
name: this.dashboardUtils.getBreakpointName(breakpointId),
layout,
descriptionSize: size,
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"
[(ngModel)]="selectedBreakpoint"
(ngModelChange)="selectLayoutChanged()">
<mat-select-trigger>{{ getName(selectedBreakpoint) }}</mat-select-trigger>
<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-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 { DashboardPageComponent } from '@home/components/dashboard-page/dashboard-page.component';
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';
@Component({
@ -30,18 +30,13 @@ export class SelectDashboardBreakpointComponent implements OnInit, OnDestroy {
@Input()
dashboardCtrl: DashboardPageComponent;
selectedBreakpoint = 'default';
breakpointsData: {[breakpointId in string]: BreakpointInfo} = {};
selectedBreakpoint: BreakpointId = 'default';
breakpointIds: Array<string> = ['default'];
private layoutDataChanged$: Subscription;
constructor(private dashboardUtils: DashboardUtilsService) {
this.dashboardUtils.getListBreakpoint().forEach((breakpoint) => {
this.breakpointsData[breakpoint.id] = breakpoint;
});
}
ngOnInit() {
@ -66,4 +61,16 @@ export class SelectDashboardBreakpointComponent implements OnInit, OnDestroy {
this.dashboardCtrl.layouts.main.layoutCtx.ctrl.updatedCurrentBreakpoint(this.selectedBreakpoint);
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 {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DoCheck,
Input,
@ -46,7 +45,6 @@ import {
} from '../../models/dashboard-component.models';
import { ReplaySubject, Subject, Subscription } from 'rxjs';
import { WidgetLayout, WidgetLayouts } from '@shared/models/dashboard.models';
import { DialogService } from '@core/services/dialog.service';
import { animatedScroll, deepClone, isDefined } from '@app/core/utils';
import { BreakpointObserver } from '@angular/cdk/layout';
import { MediaBreakpoints } from '@shared/models/constants';
@ -203,9 +201,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
widgetContextMenuEvent: MouseEvent;
dashboardWidgets = new DashboardWidgets(this,
this.differs.find([]).create<Widget>((index, item) => {
return item;
}),
this.differs.find([]).create<Widget>((_, item) => item),
this.kvDiffers.find([]).create<string, WidgetLayout>()
);
@ -218,11 +214,9 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
constructor(protected store: Store<AppState>,
public utils: UtilsService,
private timeService: TimeService,
private dialogService: DialogService,
private breakpointObserver: BreakpointObserver,
private differs: IterableDiffers,
private kvDiffers: KeyValueDiffers,
private cd: ChangeDetectorRef,
private ngZone: NgZone) {
super(store);
this.authUser = getCurrentAuthUser(store);
@ -257,8 +251,8 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
displayGrid: this.displayGrid,
resizable: {enabled: this.isEdit && !this.isEditingWidget, delayStart: 50},
draggable: {enabled: this.isEdit && !this.isEditingWidget},
itemChangeCallback: item => this.dashboardWidgets.sortWidgets(),
itemInitCallback: (item, itemComponent) => {
itemChangeCallback: () => this.dashboardWidgets.sortWidgets(),
itemInitCallback: (_, itemComponent) => {
(itemComponent.item as DashboardWidget).gridsterItemComponent = itemComponent;
},
colWidthUpdateCallback: (colWidth) => {
@ -570,7 +564,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
widget.gridsterItemComponent$().subscribe((gridsterItem) => {
const gridsterItemElement = gridsterItem.el as HTMLElement;
const offset = (parentElement.clientHeight - gridsterItemElement.clientHeight) / 2;
let scrollTop;
let scrollTop: number;
if (this.isMobileSize) {
scrollTop = gridsterItemElement.offsetTop;
} else {
@ -604,8 +598,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
} else {
this.gridsterOpts.gridType = this.isMobileSize ? GridType.Fixed : this.gridType || GridType.ScrollVertical;
}
const mobileBreakPoint = this.isMobileSize ? 20000 : 0;
this.gridsterOpts.mobileBreakpoint = mobileBreakPoint;
this.gridsterOpts.mobileBreakpoint = this.isMobileSize ? 20000 : 0;
const rowSize = this.detectRowSize(this.isMobileSize, autofillHeight, parentHeight);
if (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 { SharedModule } from '@shared/shared.module';
import { CommonModule } from '@angular/common';
import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-tooltip.directive';
import { TbTableDatasource } from '@shared/components/table/table-datasource.abstract';
@Component({
@ -78,7 +77,7 @@ import { TbTableDatasource } from '@shared/components/table/table-datasource.abs
}
],
standalone: true,
imports: [CommonModule, SharedModule, TruncateWithTooltipDirective]
imports: [CommonModule, SharedModule]
})
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 { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-tooltip.directive';
@Component({
selector: 'tb-modbus-data-keys-panel',
@ -52,7 +51,6 @@ import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-t
CommonModule,
SharedModule,
GatewayHelpLinkPipe,
TruncateWithTooltipDirective,
]
})
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 { takeUntil } from 'rxjs/operators';
import { coerceBoolean } from '@shared/decorators/coercion';
import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-tooltip.directive';
@Component({
selector: 'tb-modbus-security-config',
@ -64,7 +63,6 @@ import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-t
imports: [
CommonModule,
SharedModule,
TruncateWithTooltipDirective,
]
})
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 { ModbusValuesComponent, } from '../modbus-values/modbus-values.component';
import { isEqual } from '@core/utils';
import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-tooltip.directive';
@Component({
selector: 'tb-modbus-slave-config',
@ -73,7 +72,6 @@ import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-t
ModbusValuesComponent,
ModbusSecurityConfigComponent,
GatewayPortTooltipPipe,
TruncateWithTooltipDirective,
],
})
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 { takeUntil } from 'rxjs/operators';
import { isEqual } from '@core/utils';
import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-tooltip.directive';
import { helpBaseUrl } from '@shared/models/constants';
@Component({
@ -78,7 +77,6 @@ import { helpBaseUrl } from '@shared/models/constants';
ModbusValuesComponent,
ModbusSecurityConfigComponent,
GatewayPortTooltipPipe,
TruncateWithTooltipDirective,
],
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 { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-tooltip.directive';
import {
SecurityConfigComponent
} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component';
@ -62,7 +61,6 @@ import {
CommonModule,
SharedModule,
SecurityConfigComponent,
TruncateWithTooltipDirective,
]
})
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 { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-tooltip.directive';
@Component({
selector: 'tb-workers-config-control',
@ -45,7 +44,6 @@ import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-t
imports: [
CommonModule,
SharedModule,
TruncateWithTooltipDirective,
],
providers: [
{

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

@ -141,9 +141,8 @@ import {
import {
TypeValuePanelComponent
} 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 {
ModbusRpcParametersComponent
ModbusRpcParametersComponent
} 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';
@ -233,7 +232,6 @@ import { ScadaSymbolWidgetComponent } from '@home/components/widget/lib/scada/sc
KeyValueIsNotEmptyPipe,
ModbusBasicConfigComponent,
EllipsisChipListDirective,
TruncateWithTooltipDirective,
ModbusRpcParametersComponent,
],
exports: [

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

@ -169,12 +169,12 @@
</div>
</div>
<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">
{{ 'widget-config.mobile-hide' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel" *ngIf="isDefaultBreakpoint">
<mat-slide-toggle class="mat-slide" formControlName="desktopHide">
{{ 'widget-config.desktop-hide' | translate }}
</mat-slide-toggle>

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

@ -38,7 +38,7 @@ import {
JsonSchema,
JsonSettingsSchema,
TargetDevice,
TargetDeviceType, targetDeviceValid,
targetDeviceValid,
Widget,
WidgetConfigMode,
widgetType
@ -84,8 +84,8 @@ import { ToggleHeaderOption } from '@shared/components/toggle-header.component';
import { coerceBoolean } from '@shared/decorators/coercion';
import { basicWidgetConfigComponentsMap } from '@home/components/widget/config/basic/basic-widget-config.module';
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 Timeout = NodeJS.Timeout;
const emptySettingsSchema: JsonSchema = {
type: 'object',
@ -152,7 +152,11 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
@Input()
@coerceBoolean()
scada = false;
showLayoutConfig = true;
@Input()
@coerceBoolean()
isDefaultBreakpoint = true;
@Input() disabled: boolean;
@ -345,10 +349,12 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
value: 'actions'
}
);
if (!this.scada) {
if (this.showLayoutConfig) {
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'
}
);

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';
@Directive({
standalone: true,
selector: '[tbTruncateWithTooltip]',
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 { TenantId } from '@shared/models/id/tenant-id';
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 { EntityAliases } from './alias.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 {
layoutType?: LayoutType;
backgroundColor?: string;
@ -72,9 +86,11 @@ export interface GridSettings {
minColumns?: number;
margin?: number;
outerMargin?: boolean;
viewFormat?: ViewFormatType;
backgroundSizeMode?: string;
backgroundImageUrl?: string;
autoFillHeight?: boolean;
rowHeight?: number;
mobileAutoFillHeight?: boolean;
mobileRowHeight?: number;
mobileDisplayLayoutFirst?: boolean;
@ -85,7 +101,7 @@ export interface GridSettings {
export interface DashboardLayout {
widgets: WidgetLayouts;
gridSettings: GridSettings;
breakpoints?: {[breakpointId: string]: Omit<DashboardLayout, 'breakpoints'>};
breakpoints?: {[breakpointId in BreakpointId]?: Omit<DashboardLayout, 'breakpoints'>};
}
export declare type DashboardLayoutInfo = {[breakpointId: string]: BreakpointLayoutInfo};
@ -96,11 +112,33 @@ export interface BreakpointLayoutInfo {
gridSettings?: GridSettings;
}
export declare type BreakpointSystemId = 'default' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
export declare type BreakpointId = BreakpointSystemId | string;
export interface BreakpointInfo {
id: string;
id: BreakpointId;
maxWidth?: 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 {
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 { EditorModule, TINYMCE_SCRIPT_SRC } from '@tinymce/tinymce-angular';
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 { TranslateModule } from '@ngx-translate/core';
import { TbCheckboxComponent } from '@shared/components/tb-checkbox.component';
@ -356,6 +357,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
NavTreeComponent,
LedLightComponent,
MarkdownEditorComponent,
TruncateWithTooltipDirective,
NospacePipe,
MillisecondsToTimeStringPipe,
EnumToArrayPipe,
@ -611,6 +613,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
NavTreeComponent,
LedLightComponent,
MarkdownEditorComponent,
TruncateWithTooltipDirective,
NospacePipe,
MillisecondsToTimeStringPipe,
EnumToArrayPipe,

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

@ -1283,7 +1283,6 @@
"maximum-upload-file-size": "الحد الأقصى لحجم الملف المرفوع: {{ size }}",
"cannot-upload-file": "لا يمكن تحميل الملف",
"settings": "الإعدادات",
"layout-settings": "إعدادات التخطيط",
"columns-count": "عدد الأعمدة",
"columns-count-required": "عدد الأعمدة مطلوب.",
"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 }}",
"cannot-upload-file": "No s'ha pogut pujar el fitxer",
"settings": "Configuració",
"layout-settings": "Paràmetres de la disposició",
"columns-count": "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.",

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

@ -745,7 +745,6 @@
"maximum-upload-file-size": "Maximální velikost souboru: {{ size }}",
"cannot-upload-file": "Soubor nelze nahrát",
"settings": "Nastavení",
"layout-settings": "Nastavení rozložení",
"columns-count": "Počet sloupců",
"columns-count-required": "Počet sloupců je povinný.",
"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-scada": "SCADA",
"layout-type-divider": "Divider",
"layout-settings": "Layout settings",
"layout-settings-type": "Layout settings: {{ type }}",
"columns-count": "Columns count",
"columns-count-required": "Columns count is required.",
"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.",
"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.",
"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",
"title-settings": "Title settings",
"display-title": "Display dashboard title",
@ -1276,7 +1280,19 @@
"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",
"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": {
"settings": "Settings",
@ -5951,7 +5967,8 @@
"card-appearance": "Card appearance",
"color": "Color",
"tooltip": "Tooltip",
"units-required": "Unit is required."
"units-required": "Unit is required.",
"list-option": "List option"
},
"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 }}",
"cannot-upload-file": "No es posible subir el fichero",
"settings": "Ajustes",
"layout-settings": "Ajustes de diseño",
"columns-count": "Número de columnas",
"columns-count-required": "Número de columnas requerido.",
"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",
"maximum-upload-file-size": "Taille de fichier maximum: {{ size }}",
"cannot-upload-file": "Le téléchargement a échoué",
"layout-settings": "Paramètres de mise en page",
"margin-required": "Valeur de marge requise.",
"min-margin-message": "0 est la valeur minimum 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 }}",
"cannot-upload-file": "Failo įkelti nepavyko",
"settings": "Nustatymai",
"layout-settings": "Išdėstymo nustatymai",
"columns-count": "Stulpelių skaičius",
"columns-count-required": "Stulpelių skaičius būtinas.",
"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 }}",
"cannot-upload-file": "Kan bestand niet uploaden",
"settings": "Instellingen",
"layout-settings": "Lay-out instellingen",
"columns-count": "Aantal kolommen",
"columns-count-required": "Het aantal kolommen is vereist.",
"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 }}",
"cannot-upload-file": "Nie można przesłać pliku",
"settings": "Ustawienia",
"layout-settings": "Ustawienia układu",
"columns-count": "Liczba kolumn",
"columns-count-required": "Liczba kolumn jest wymagana.",
"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 }}",
"cannot-upload-file": "Dosya yüklenemiyor",
"settings": "Ayarlar",
"layout-settings": "Görünüm ayarları",
"columns-count": "Sütun sayısı",
"columns-count-required": "Sütun sayısı gerekli.",
"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 }}",
"cannot-upload-file": "无法上传文件",
"settings": "设置",
"layout-settings": "布局设置",
"columns-count": "列数",
"columns-count-required": "需要列数。",
"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 }}",
"cannot-upload-file": "無法上傳文件",
"settings": "設定",
"layout-settings": "佈局設定",
"columns-count": "列數",
"columns-count-required": "需要列數。",
"min-columns-count-message": "只允許最少10列",

Loading…
Cancel
Save