UI: Added support difference dashboard layout for one state

This commit is contained in:
Vladyslav Prykhodko 2024-08-05 00:49:43 +03:00
parent ea662bc26a
commit 963160efa6
13 changed files with 353 additions and 70 deletions

View File

@ -18,11 +18,11 @@ import { Injectable } from '@angular/core';
import { UtilsService } from '@core/services/utils.service';
import { TimeService } from '@core/services/time.service';
import {
BreakpointLayoutInfo,
Dashboard,
DashboardConfiguration,
DashboardLayout,
DashboardLayoutId,
DashboardLayoutInfo,
DashboardLayoutsInfo,
DashboardState,
DashboardStateLayouts,
@ -520,16 +520,14 @@ export class DashboardUtilsService {
for (const l of Object.keys(state.layouts)) {
const layout: DashboardLayout = state.layouts[l];
if (layout) {
result[l] = {
widgetIds: [],
widgetLayouts: {},
gridSettings: {}
} as DashboardLayoutInfo;
for (const id of Object.keys(layout.widgets)) {
result[l].widgetIds.push(id);
result[l]= {
default: this.getBreakpointLayoutData(layout)
};
if (layout.breakpoints) {
for (const breakpoint of Object.keys(layout.breakpoints)) {
result[l][breakpoint] = this.getBreakpointLayoutData(layout.breakpoints[breakpoint]);
}
}
result[l].widgetLayouts = layout.widgets;
result[l].gridSettings = layout.gridSettings;
}
}
return result;
@ -538,6 +536,20 @@ export class DashboardUtilsService {
}
}
private getBreakpointLayoutData(layout: DashboardLayout): BreakpointLayoutInfo {
const result: BreakpointLayoutInfo = {
widgetIds: [],
widgetLayouts: {},
gridSettings: {}
};
for (const id of Object.keys(layout.widgets)) {
result.widgetIds.push(id);
}
result.widgetLayouts = layout.widgets;
result.gridSettings = layout.gridSettings;
return result;
}
public getWidgetsArray(dashboard: Dashboard): Array<Widget> {
const widgetsArray: Array<Widget> = [];
const dashboardConfiguration = dashboard.configuration;
@ -564,11 +576,15 @@ export class DashboardUtilsService {
originalColumns?: number,
originalSize?: {sizeX: number; sizeY: number},
row?: number,
column?: number): void {
column?: number,
breakpoint = 'default'): void {
const dashboardConfiguration = dashboard.configuration;
const states = dashboardConfiguration.states;
const state = states[targetState];
const layout = state.layouts[targetLayout];
let layout = state.layouts[targetLayout];
if (breakpoint !== 'default' && layout.breakpoints?.[breakpoint]) {
layout = layout.breakpoints[breakpoint];
}
const layoutCount = Object.keys(state.layouts).length;
if (!widget.id) {
widget.id = this.utils.guid();
@ -626,12 +642,17 @@ export class DashboardUtilsService {
public removeWidgetFromLayout(dashboard: Dashboard,
targetState: string,
targetLayout: DashboardLayoutId,
widgetId: string) {
widgetId: string,
breakpoint: string) {
const dashboardConfiguration = dashboard.configuration;
const states = dashboardConfiguration.states;
const state = states[targetState];
const layout = state.layouts[targetLayout];
if (layout.breakpoints[breakpoint]) {
delete layout.breakpoints[breakpoint].widgets[widgetId];
} else {
delete layout.widgets[widgetId];
}
this.removeUnusedWidgets(dashboard);
}
@ -700,11 +721,19 @@ export class DashboardUtilsService {
for (const s of Object.keys(states)) {
const state = states[s];
for (const l of Object.keys(state.layouts)) {
const layout = state.layouts[l];
const layout: DashboardLayout = state.layouts[l];
if (layout.widgets[widgetId]) {
found = true;
break;
}
if (layout.breakpoints) {
for (const breakpoint of Object.keys(layout.breakpoints)) {
if (layout.breakpoints[breakpoint].widgets[widgetId]) {
found = true;
break;
}
}
}
}
}
if (!found) {

View File

@ -56,6 +56,7 @@ export interface WidgetReference {
widgetId: string;
originalSize: WidgetSize;
originalColumns: number;
breakpoint: string;
}
export interface RuleNodeConnection {
@ -85,7 +86,8 @@ export class ItemBufferService {
private ruleChainService: RuleChainService,
private utils: UtilsService) {}
public prepareWidgetItem(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId, widget: Widget): WidgetItem {
public prepareWidgetItem(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId,
widget: Widget, breakpoint: string): WidgetItem {
const aliasesInfo: AliasesInfo = {
datasourceAliases: {},
targetDeviceAlias: null
@ -93,8 +95,8 @@ export class ItemBufferService {
const filtersInfo: FiltersInfo = {
datasourceFilters: {}
};
const originalColumns = this.getOriginalColumns(dashboard, sourceState, sourceLayout);
const originalSize = this.getOriginalSize(dashboard, sourceState, sourceLayout, widget);
const originalColumns = this.getOriginalColumns(dashboard, sourceState, sourceLayout, breakpoint);
const originalSize = this.getOriginalSize(dashboard, sourceState, sourceLayout, widget, breakpoint);
const datasources: Datasource[] = widget.type === widgetType.alarm ? [widget.config.alarmSource] : widget.config.datasources;
if (widget.config && dashboard.configuration
&& dashboard.configuration.entityAliases) {
@ -146,13 +148,14 @@ export class ItemBufferService {
};
}
public copyWidget(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId, widget: Widget): void {
const widgetItem = this.prepareWidgetItem(dashboard, sourceState, sourceLayout, widget);
public copyWidget(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId, widget: Widget, breakpoint: string): void {
const widgetItem = this.prepareWidgetItem(dashboard, sourceState, sourceLayout, widget, breakpoint);
this.storeSet(WIDGET_ITEM, widgetItem);
}
public copyWidgetReference(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId, widget: Widget): void {
const widgetReference = this.prepareWidgetReference(dashboard, sourceState, sourceLayout, widget);
public copyWidgetReference(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId,
widget: Widget, breakpoint: string): void {
const widgetReference = this.prepareWidgetReference(dashboard, sourceState, sourceLayout, widget, breakpoint);
this.storeSet(WIDGET_REFERENCE, widgetReference);
}
@ -160,11 +163,11 @@ export class ItemBufferService {
return this.storeHas(WIDGET_ITEM);
}
public canPasteWidgetReference(dashboard: Dashboard, state: string, layout: DashboardLayoutId): boolean {
public canPasteWidgetReference(dashboard: Dashboard, state: string, layout: DashboardLayoutId, breakpoint: string): boolean {
const widgetReference: WidgetReference = this.storeGet(WIDGET_REFERENCE);
if (widgetReference) {
if (widgetReference.dashboardId === dashboard.id.id) {
if ((widgetReference.sourceState !== state || widgetReference.sourceLayout !== layout)
if ((widgetReference.sourceState !== state || widgetReference.sourceLayout !== layout || widgetReference.breakpoint !== breakpoint)
&& dashboard.configuration.widgets[widgetReference.widgetId]) {
return true;
}
@ -387,13 +390,17 @@ export class ItemBufferService {
return ruleChainImport;
}
private getOriginalColumns(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId): number {
private getOriginalColumns(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId,
breakpoint: string): number {
let originalColumns = 24;
let gridSettings = null;
const state = dashboard.configuration.states[sourceState];
const layoutCount = Object.keys(state.layouts).length;
if (state) {
const layout = state.layouts[sourceLayout];
let layout = state.layouts[sourceLayout];
if (breakpoint !== 'default') {
layout = layout.breakpoints[breakpoint];
}
if (layout) {
gridSettings = layout.gridSettings;
@ -407,8 +414,12 @@ export class ItemBufferService {
return originalColumns;
}
private getOriginalSize(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId, widget: Widget): WidgetSize {
const layout = dashboard.configuration.states[sourceState].layouts[sourceLayout];
private getOriginalSize(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId, widget: Widget,
breakpoint: string): WidgetSize {
let layout = dashboard.configuration.states[sourceState].layouts[sourceLayout];
if (breakpoint !== 'default') {
layout = layout.breakpoints[breakpoint];
}
const widgetLayout = layout.widgets[widget.id];
return {
sizeX: widgetLayout.sizeX,
@ -432,16 +443,17 @@ export class ItemBufferService {
}
private prepareWidgetReference(dashboard: Dashboard, sourceState: string,
sourceLayout: DashboardLayoutId, widget: Widget): WidgetReference {
const originalColumns = this.getOriginalColumns(dashboard, sourceState, sourceLayout);
const originalSize = this.getOriginalSize(dashboard, sourceState, sourceLayout, widget);
sourceLayout: DashboardLayoutId, widget: Widget, breakpoint: string): WidgetReference {
const originalColumns = this.getOriginalColumns(dashboard, sourceState, sourceLayout, breakpoint);
const originalSize = this.getOriginalSize(dashboard, sourceState, sourceLayout, widget, breakpoint);
return {
dashboardId: dashboard.id.id,
sourceState,
sourceLayout,
widgetId: widget.id,
originalSize,
originalColumns
originalColumns,
breakpoint
};
}

View File

@ -58,6 +58,9 @@
<mat-icon>view_compact</mat-icon>
{{'layout.layouts' | translate}}
</button>
<tb-select-dashboard-layout
[dashboardCtrl]="this">
</tb-select-dashboard-layout>
</div>
</ng-container>
<tb-states-component fxFlex.lt-md

View File

@ -40,7 +40,6 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { ActivatedRoute, Router } from '@angular/router';
import { UtilsService } from '@core/services/utils.service';
import { AuthService } from '@core/auth/auth.service';
import {
Dashboard,
DashboardConfiguration,
@ -66,7 +65,7 @@ import {
IDashboardController,
LayoutWidgetsArray
} from './dashboard-page.models';
import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
import { BreakpointObserver } from '@angular/cdk/layout';
import { MediaBreakpoints } from '@shared/models/constants';
import { AuthUser } from '@shared/models/user.model';
import { getCurrentAuthState } from '@core/auth/auth.selectors';
@ -83,7 +82,7 @@ import { Authority } from '@shared/models/authority.enum';
import { DialogService } from '@core/services/dialog.service';
import { EntityService } from '@core/http/entity.service';
import { AliasController } from '@core/api/alias-controller';
import { Observable, of, Subscription } from 'rxjs';
import { Observable, of, Subject, Subscription } from 'rxjs';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
import { DashboardService } from '@core/http/dashboard.service';
import {
@ -138,14 +137,14 @@ import {
DashboardImageDialogData,
DashboardImageDialogResult
} from '@home/components/dashboard-page/dashboard-image-dialog.component';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { SafeUrl } from '@angular/platform-browser';
import cssjs from '@core/css/css';
import { DOCUMENT } from '@angular/common';
import { IAliasController } from '@core/api/widget-api.models';
import { MatButton } from '@angular/material/button';
import { VersionControlComponent } from '@home/components/vc/version-control.component';
import { TbPopoverService } from '@shared/components/popover.service';
import { map, tap } from 'rxjs/operators';
import { distinctUntilChanged, map, skip, tap } from 'rxjs/operators';
import { LayoutFixedSize, LayoutWidthType } from '@home/components/dashboard-page/layout/layout.models';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { ResizeObserver } from '@juggle/resize-observer';
@ -268,6 +267,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
getDashboard: () => this.dashboard,
dashboardTimewindow: null,
state: null,
breakpoint: null,
stateController: null,
stateChanged: null,
stateId: null,
@ -280,24 +280,28 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
show: false,
layoutCtx: {
id: 'main',
breakpoint: 'default',
widgets: null,
widgetLayouts: {},
gridSettings: {},
ignoreLoading: true,
ctrl: null,
dashboardCtrl: this
dashboardCtrl: this,
layoutData: null
}
},
right: {
show: false,
layoutCtx: {
id: 'right',
breakpoint: 'default',
widgets: null,
widgetLayouts: {},
gridSettings: {},
ignoreLoading: true,
ctrl: null,
dashboardCtrl: this
dashboardCtrl: this,
layoutData: null
}
}
};
@ -331,6 +335,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
@ViewChild('dashboardWidgetSelect') dashboardWidgetSelectComponent: DashboardWidgetSelectComponent;
private changeMobileSize = new Subject<boolean>();
constructor(protected store: Store<AppState>,
@Inject(WINDOW) private window: Window,
@Inject(DOCUMENT) private document: Document,
@ -339,7 +345,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
private router: Router,
private utils: UtilsService,
private dashboardUtils: DashboardUtilsService,
private authService: AuthService,
private entityService: EntityService,
private dialogService: DialogService,
private widgetComponentService: WidgetComponentService,
@ -357,7 +362,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
private overlay: Overlay,
private viewContainerRef: ViewContainerRef,
private cd: ChangeDetectorRef,
private sanitizer: DomSanitizer,
public elRef: ElementRef,
private injector: Injector) {
super(store);
@ -402,13 +406,56 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
}
));
}
this.rxSubscriptions.push(this.breakpointObserver
.observe(MediaBreakpoints['gt-sm'])
.subscribe((state: BreakpointState) => {
this.isMobile = !state.matches;
this.rxSubscriptions.push(
this.changeMobileSize.pipe(
distinctUntilChanged(),
).subscribe((state) => {
this.isMobile = state;
this.updateLayoutSizes();
})
);
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';
}
}),
tap((value) => {
this.dashboardCtx.breakpoint = value;
this.changeMobileSize.next(value === 'xs' || value === 'sm');
}),
distinctUntilChanged((prev, 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);
}),
skip(1)
).subscribe((value) => {
this.layouts.main.layoutCtx.ctrl.updatedCurrentBreakpoint();
this.updateLayoutSizes();
}
));
)
);
if (this.isMobileApp && this.syncStateWithQueryParam) {
this.mobileService.registerToggleLayoutFunction(() => {
setTimeout(() => {
@ -694,7 +741,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
this.mobileService.onDashboardRightLayoutChanged(this.isRightLayoutOpened);
}
private updateLayoutSizes() {
public updateLayoutSizes() {
let changeMainLayoutSize = false;
let changeRightLayoutSize = false;
if (this.dashboardCtx.state) {
@ -1025,21 +1072,14 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
this.updateLayout(layout, layoutInfo);
} else {
layout.show = false;
this.updateLayout(layout, {widgetIds: [], widgetLayouts: {}, gridSettings: null});
this.updateLayout(layout, {default: {widgetIds: [], widgetLayouts: {}, gridSettings: null}});
}
}
}
private updateLayout(layout: DashboardPageLayout, layoutInfo: DashboardLayoutInfo) {
if (layoutInfo.gridSettings) {
layout.layoutCtx.gridSettings = layoutInfo.gridSettings;
}
layout.layoutCtx.widgets.setWidgetIds(layoutInfo.widgetIds);
layout.layoutCtx.widgetLayouts = layoutInfo.widgetLayouts;
if (layout.show && layout.layoutCtx.ctrl) {
layout.layoutCtx.ctrl.reload();
}
layout.layoutCtx.ignoreLoading = true;
layout.layoutCtx.layoutData = layoutInfo;
layout.layoutCtx.ctrl?.updatedCurrentBreakpoint(null, layout.show);
this.updateLayoutSizes();
}
@ -1158,8 +1198,10 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
}
private addWidgetToLayout(widget: Widget, layoutId: DashboardLayoutId) {
this.dashboardUtils.addWidgetToLayout(this.dashboard, this.dashboardCtx.state, layoutId, widget);
this.layouts[layoutId].layoutCtx.widgets.addWidgetId(widget.id);
const layoutCtx = this.layouts[layoutId].layoutCtx;
this.dashboardUtils.addWidgetToLayout(this.dashboard, this.dashboardCtx.state, layoutId, widget, undefined,
undefined, -1, -1, layoutCtx.breakpoint);
layoutCtx.widgets.addWidgetId(widget.id);
this.runChangeDetection();
}
@ -1303,12 +1345,12 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
copyWidget($event: Event, layoutCtx: DashboardPageLayoutContext, widget: Widget) {
this.itembuffer.copyWidget(this.dashboard,
this.dashboardCtx.state, layoutCtx.id, widget);
this.dashboardCtx.state, layoutCtx.id, widget, layoutCtx.breakpoint);
}
copyWidgetReference($event: Event, layoutCtx: DashboardPageLayoutContext, widget: Widget) {
this.itembuffer.copyWidgetReference(this.dashboard,
this.dashboardCtx.state, layoutCtx.id, widget);
this.dashboardCtx.state, layoutCtx.id, widget, layoutCtx.breakpoint);
}
pasteWidget($event: Event, layoutCtx: DashboardPageLayoutContext, pos: WidgetPosition) {
@ -1343,7 +1385,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
).subscribe((res) => {
if (res) {
if (layoutCtx.widgets.removeWidgetId(widget.id)) {
this.dashboardUtils.removeWidgetFromLayout(this.dashboard, this.dashboardCtx.state, layoutCtx.id, widget.id);
this.dashboardUtils.removeWidgetFromLayout(this.dashboard, this.dashboardCtx.state, layoutCtx.id,
widget.id, layoutCtx.breakpoint);
this.runChangeDetection();
}
}
@ -1352,7 +1395,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
exportWidget($event: Event, layoutCtx: DashboardPageLayoutContext, widget: Widget, widgetTitle: string) {
$event.stopPropagation();
this.importExport.exportWidget(this.dashboard, this.dashboardCtx.state, layoutCtx.id, widget, widgetTitle);
this.importExport.exportWidget(this.dashboard, this.dashboardCtx.state, layoutCtx.id, widget, widgetTitle, layoutCtx.breakpoint);
}
widgetClicked($event: Event, layoutCtx: DashboardPageLayoutContext, widget: Widget) {
@ -1402,7 +1445,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
action: ($event) => {
layoutCtx.ctrl.pasteWidgetReference($event);
},
enabled: this.itembuffer.canPasteWidgetReference(this.dashboard, this.dashboardCtx.state, layoutCtx.id),
enabled: this.itembuffer.canPasteWidgetReference(this.dashboard, this.dashboardCtx.state, layoutCtx.id, layoutCtx.breakpoint),
value: 'action.paste-reference',
icon: 'content_paste',
shortcut: 'M-I'

View File

@ -14,7 +14,13 @@
/// limitations under the License.
///
import { Dashboard, DashboardLayoutId, GridSettings, WidgetLayouts } from '@app/shared/models/dashboard.models';
import {
Dashboard,
DashboardLayoutId,
DashboardLayoutInfo,
GridSettings,
WidgetLayouts
} from '@app/shared/models/dashboard.models';
import { Widget, WidgetPosition } from '@app/shared/models/widget.models';
import { Timewindow } from '@shared/models/time/time.models';
import { IAliasController, IStateController } from '@core/api/widget-api.models';
@ -34,6 +40,7 @@ export interface DashboardPageInitData {
export interface DashboardContext {
instanceId: string;
state: string;
breakpoint: string;
getDashboard: () => Dashboard;
dashboardTimewindow: Timewindow;
aliasController: IAliasController;
@ -63,6 +70,8 @@ export interface IDashboardController {
export interface DashboardPageLayoutContext {
id: DashboardLayoutId;
layoutData: DashboardLayoutInfo;
breakpoint: string;
widgets: LayoutWidgetsArray;
widgetLayouts: WidgetLayouts;
gridSettings: GridSettings;

View File

@ -36,6 +36,8 @@ import { TbCheatSheetComponent } from '@shared/components/cheatsheet.component';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { ImagePipe } from '@shared/pipe/image.pipe';
import { map } from 'rxjs/operators';
import { deepClone, isNotEmptyStr } from '@core/utils';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
@Component({
selector: 'tb-dashboard-layout',
@ -95,7 +97,8 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo
private translate: TranslateService,
private itembuffer: ItemBufferService,
private imagePipe: ImagePipe,
private sanitizer: DomSanitizer) {
private sanitizer: DomSanitizer,
private dashboardUtils: DashboardUtilsService,) {
super(store);
this.initHotKeys();
}
@ -161,7 +164,7 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo
new Hotkey('ctrl+i', (event: KeyboardEvent) => {
if (this.isEdit && !this.isEditingWidget && !this.widgetEditMode) {
if (this.itembuffer.canPasteWidgetReference(this.dashboardCtx.getDashboard(),
this.dashboardCtx.state, this.layoutCtx.id)) {
this.dashboardCtx.state, this.layoutCtx.id, this.layoutCtx.breakpoint)) {
event.preventDefault();
this.pasteWidgetReference(event);
}
@ -268,4 +271,45 @@ 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;
}
this.layoutCtx.breakpoint = breakpoint;
const layoutInfo = this.getLayoutDataForBreakpoint(breakpoint);
if (layoutInfo.gridSettings) {
this.layoutCtx.gridSettings = layoutInfo.gridSettings;
}
this.layoutCtx.widgets.setWidgetIds(layoutInfo.widgetIds);
this.layoutCtx.widgetLayouts = layoutInfo.widgetLayouts;
if (showLayout && this.layoutCtx.ctrl) {
this.layoutCtx.ctrl.reload();
}
this.layoutCtx.ignoreLoading = true;
}
private getLayoutDataForBreakpoint(breakpoint: string) {
if (this.layoutCtx.layoutData[breakpoint]) {
return this.layoutCtx.layoutData[breakpoint];
}
return this.layoutCtx.layoutData.default;
}
createBreakpointConfig(breakpoint: string) {
const currentDashboard = this.dashboardCtx.getDashboard();
const dashboardConfiguration = currentDashboard.configuration;
const states = dashboardConfiguration.states;
const state = states[this.dashboardCtx.state];
const layout = state.layouts[this.layoutCtx.id];
if (!layout.breakpoints) {
layout.breakpoints = {};
}
layout.breakpoints[breakpoint] = {
gridSettings: deepClone(layout.gridSettings),
widgets: deepClone(layout.widgets),
};
this.layoutCtx.layoutData =
this.dashboardUtils.getStateLayoutsData(currentDashboard, this.dashboardCtx.state)[this.layoutCtx.id];
}
}

View File

@ -21,6 +21,8 @@ export interface ILayoutController {
selectWidget(widgetId: string, delay?: number);
pasteWidget($event: MouseEvent);
pasteWidgetReference($event: MouseEvent);
updatedCurrentBreakpoint(breakpoint?: string, showLayout?: boolean);
createBreakpointConfig(breakpoint?: string);
}
export enum LayoutWidthType {

View File

@ -0,0 +1,25 @@
<!--
Copyright © 2016-2024 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<mat-select
class="select-dashboard-layout"
[(ngModel)]="selectLayout"
(ngModelChange)="selectLayoutChanged()">
<mat-option *ngFor="let layout of layouts" [value]="layout">
{{ layout }}
</mat-option>
</mat-select>

View File

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host {
pointer-events: all;
width: min-content; //for Safari
}
:host ::ng-deep {
.mat-mdc-select.select-dashboard-layout {
.mat-mdc-select-value {
max-width: 200px;
}
.mat-mdc-select-arrow {
width: 24px;
}
}
}

View File

@ -0,0 +1,77 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { DashboardPageComponent } from '@home/components/dashboard-page/dashboard-page.component';
import { Subscription } from 'rxjs';
@Component({
selector: 'tb-select-dashboard-layout',
templateUrl: './select-dashboard-layout.component.html',
styleUrls: ['./select-dashboard-layout.component.scss']
})
export class SelectDashboardLayoutComponent implements OnInit, OnDestroy {
@Input()
dashboardCtrl: DashboardPageComponent;
layout = {
default: 'Default',
xs: 'xs',
sm: 'sm',
md: 'md',
lg: 'lg',
xl: 'xl'
};
layouts = Object.keys(this.layout);
selectLayout = 'default';
private allowBreakpointsSize = new Set<string>();
private stateChanged$: Subscription;
constructor() { }
ngOnInit() {
this.stateChanged$ = this.dashboardCtrl.dashboardCtx.stateChanged.subscribe(() => {
if (this.dashboardCtrl.layouts.main.layoutCtx.layoutData) {
this.allowBreakpointsSize = new Set(Object.keys(this.dashboardCtrl.layouts.main.layoutCtx?.layoutData));
} else {
this.allowBreakpointsSize.add('default');
}
if (this.allowBreakpointsSize.has(this.dashboardCtrl.layouts.main.layoutCtx.breakpoint)) {
this.selectLayout = this.dashboardCtrl.layouts.main.layoutCtx.breakpoint;
} else {
this.selectLayout = 'default';
this.dashboardCtrl.layouts.main.layoutCtx.breakpoint = this.selectLayout;
}
});
}
ngOnDestroy() {
this.stateChanged$.unsubscribe();
}
selectLayoutChanged() {
if (!this.dashboardCtrl.layouts.main.layoutCtx.layoutData[this.selectLayout]) {
this.dashboardCtrl.layouts.main.layoutCtx.ctrl.createBreakpointConfig(this.selectLayout);
}
this.dashboardCtrl.layouts.main.layoutCtx.ctrl.updatedCurrentBreakpoint(this.selectLayout);
this.dashboardCtrl.updateLayoutSizes();
}
}

View File

@ -171,6 +171,9 @@ import {
import { WidgetConfigComponentsModule } from '@home/components/widget/config/widget-config-components.module';
import { BasicWidgetConfigModule } from '@home/components/widget/config/basic/basic-widget-config.module';
import { DeleteTimeseriesPanelComponent } from '@home/components/attribute/delete-timeseries-panel.component';
import {
SelectDashboardLayoutComponent
} from '@home/components/dashboard-page/layout/select-dashboard-layout.component';
@NgModule({
declarations:
@ -280,6 +283,7 @@ import { DeleteTimeseriesPanelComponent } from '@home/components/attribute/delet
DashboardPageComponent,
DashboardStateComponent,
DashboardLayoutComponent,
SelectDashboardLayoutComponent,
EditWidgetComponent,
DashboardWidgetSelectComponent,
AddWidgetDialogComponent,
@ -411,6 +415,7 @@ import { DeleteTimeseriesPanelComponent } from '@home/components/attribute/delet
DashboardPageComponent,
DashboardStateComponent,
DashboardLayoutComponent,
SelectDashboardLayoutComponent,
EditWidgetComponent,
DashboardWidgetSelectComponent,
AddWidgetDialogComponent,

View File

@ -198,8 +198,9 @@ export class ImportExportService {
);
}
public exportWidget(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId, widget: Widget, widgetTitle: string) {
const widgetItem = this.itembuffer.prepareWidgetItem(dashboard, sourceState, sourceLayout, widget);
public exportWidget(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId, widget: Widget,
widgetTitle: string, breakpoint: string) {
const widgetItem = this.itembuffer.prepareWidgetItem(dashboard, sourceState, sourceLayout, widget, breakpoint);
const widgetDefaultName = this.widgetService.getWidgetInfoFromCache(widget.typeFullFqn).widgetName;
let fileName = widgetDefaultName + (isNotEmptyStr(widgetTitle) ? `_${widgetTitle}` : '');
fileName = fileName.toLowerCase().replace(/\W/g, '_');

View File

@ -67,9 +67,12 @@ export interface GridSettings {
export interface DashboardLayout {
widgets: WidgetLayouts;
gridSettings: GridSettings;
breakpoints?: {[breakpoint: string]: Omit<DashboardLayout, 'breakpoints'>};
}
export interface DashboardLayoutInfo {
export declare type DashboardLayoutInfo = {[breakpoint: string]: BreakpointLayoutInfo};
export interface BreakpointLayoutInfo {
widgetIds?: string[];
widgetLayouts?: WidgetLayouts;
gridSettings?: GridSettings;