diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html
index c050b524cf..e5341f241b 100644
--- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html
+++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html
@@ -64,101 +64,20 @@
diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.scss b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.scss
index 5414291787..9d9057b77d 100644
--- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.scss
+++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.scss
@@ -48,105 +48,6 @@
}
}
-tb-widget.tb-widget {
- position: relative;
- height: 100%;
- margin: 0;
- overflow: hidden;
- outline: none;
-
- transition: all .2s ease-in-out;
-}
-
-div.tb-widget {
- position: relative;
- height: 100%;
- margin: 0;
- overflow: hidden;
- outline: none;
-
- transition: all .2s ease-in-out;
-
- .tb-widget-title {
- max-height: 65px;
- padding-top: 5px;
- padding-left: 5px;
- overflow: hidden;
-
- tb-timewindow {
- font-size: 14px;
- opacity: .85;
- margin: 0;
- }
-
- .title {
- width: 100%;
- overflow: hidden;
- text-overflow: ellipsis;
- line-height: 24px;
- letter-spacing: .01em;
- margin: 0;
- display: -webkit-box;
- -webkit-box-orient: vertical;
- -webkit-line-clamp: 2;
-
- &.single-row{
- -webkit-line-clamp: 1;
- }
- }
- }
-
- .tb-widget-actions {
- z-index: 19;
- margin: 5px 0 0;
-
- &-absolute {
- position: absolute;
- top: 3px;
- right: 8px;
- z-index: 150;
- }
-
- button.mat-icon-button {
- width: 32px;
- min-width: 32px;
- height: 32px;
- min-height: 32px;
- padding: 0 !important;
- margin: 0 !important;
- line-height: 20px;
-
- mat-icon {
- width: 20px;
- min-width: 20px;
- height: 20px;
- min-height: 20px;
- font-size: 20px;
- line-height: 20px;
- }
- }
- }
-
- .tb-widget-content {
- &.tb-no-interaction {
- pointer-events: none;
- }
- tb-widget {
- position: relative;
- width: 100%;
- }
- }
-
- &.tb-highlighted {
- border: 1px solid #039be5;
- box-shadow: 0 0 20px #039be5;
- }
-
- &.tb-not-highlighted {
- opacity: .5;
- }
-}
-
.tb-dashboard-context-menu-items {
min-width: 256px;
}
diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts
index c871587ba2..72c242c308 100644
--- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts
+++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts
@@ -55,6 +55,7 @@ import { SafeStyle } from '@angular/platform-browser';
import { distinct } from 'rxjs/operators';
import { ResizeObserver } from '@juggle/resize-observer';
import { UtilsService } from '@core/services/utils.service';
+import { WidgetComponentAction, WidgetComponentActionType } from '@home/components/widget/widget-container.component';
@Component({
selector: 'tb-dashboard',
@@ -348,7 +349,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
}
}
- openWidgetContextMenu($event: MouseEvent, widget: DashboardWidget) {
+ private openWidgetContextMenu($event: MouseEvent, widget: DashboardWidget) {
if (this.callbacks && this.callbacks.prepareWidgetContextMenu) {
const items = this.callbacks.prepareWidgetContextMenu($event, widget.widget);
if (items && items.length) {
@@ -363,23 +364,47 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
}
}
- onWidgetFullscreenChanged(expanded: boolean, widget: DashboardWidget) {
+ onWidgetFullscreenChanged(expanded: boolean) {
this.isWidgetExpanded = expanded;
}
- widgetMouseDown($event: Event, widget: DashboardWidget) {
+ onWidgetComponentAction(action: WidgetComponentAction, widget: DashboardWidget) {
+ const $event = action.event;
+ switch (action.actionType) {
+ case WidgetComponentActionType.MOUSE_DOWN:
+ this.widgetMouseDown($event, widget);
+ break;
+ case WidgetComponentActionType.CLICKED:
+ this.widgetClicked($event, widget);
+ break;
+ case WidgetComponentActionType.CONTEXT_MENU:
+ this.openWidgetContextMenu($event, widget);
+ break;
+ case WidgetComponentActionType.EDIT:
+ this.editWidget($event, widget);
+ break;
+ case WidgetComponentActionType.EXPORT:
+ this.exportWidget($event, widget);
+ break;
+ case WidgetComponentActionType.REMOVE:
+ this.removeWidget($event, widget);
+ break;
+ }
+ }
+
+ private widgetMouseDown($event: Event, widget: DashboardWidget) {
if (this.callbacks && this.callbacks.onWidgetMouseDown) {
this.callbacks.onWidgetMouseDown($event, widget.widget);
}
}
- widgetClicked($event: Event, widget: DashboardWidget) {
+ private widgetClicked($event: Event, widget: DashboardWidget) {
if (this.callbacks && this.callbacks.onWidgetClicked) {
this.callbacks.onWidgetClicked($event, widget.widget);
}
}
- editWidget($event: Event, widget: DashboardWidget) {
+ private editWidget($event: Event, widget: DashboardWidget) {
if ($event) {
$event.stopPropagation();
}
@@ -388,7 +413,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
}
}
- exportWidget($event: Event, widget: DashboardWidget) {
+ private exportWidget($event: Event, widget: DashboardWidget) {
if ($event) {
$event.stopPropagation();
}
@@ -397,7 +422,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
}
}
- removeWidget($event: Event, widget: DashboardWidget) {
+ private removeWidget($event: Event, widget: DashboardWidget) {
if ($event) {
$event.stopPropagation();
}
@@ -454,14 +479,6 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
}
}
- isHighlighted(widget: DashboardWidget) {
- return this.dashboardWidgets.isHighlighted(widget);
- }
-
- isNotHighlighted(widget: DashboardWidget) {
- return this.dashboardWidgets.isNotHighlighted(widget);
- }
-
private scrollToWidget(widget: DashboardWidget, delay?: number) {
const parentElement = this.gridster.el as HTMLElement;
widget.gridsterItemComponent$().subscribe((gridsterItem) => {
@@ -534,10 +551,6 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
this.updateWidgetLayouts();
}
- public detectChanges() {
- this.cd.detectChanges();
- }
-
private detectRowSize(isMobile: boolean, autofillHeight: boolean, parentHeight?: number): number | null {
let rowHeight = null;
if (!autofillHeight) {
diff --git a/ui-ngx/src/app/modules/home/components/details-panel.component.ts b/ui-ngx/src/app/modules/home/components/details-panel.component.ts
index 26391db994..edb1d3c405 100644
--- a/ui-ngx/src/app/modules/home/components/details-panel.component.ts
+++ b/ui-ngx/src/app/modules/home/components/details-panel.component.ts
@@ -14,18 +14,19 @@
/// limitations under the License.
///
-import { Component, EventEmitter, Input, Output } from '@angular/core';
+import { ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { FormGroup } from '@angular/forms';
+import { Subscription } from 'rxjs';
@Component({
selector: 'tb-details-panel',
templateUrl: './details-panel.component.html',
styleUrls: ['./details-panel.component.scss']
})
-export class DetailsPanelComponent extends PageComponent {
+export class DetailsPanelComponent extends PageComponent implements OnDestroy {
@Input() headerHeightPx = 100;
@Input() headerTitle = '';
@@ -35,11 +36,21 @@ export class DetailsPanelComponent extends PageComponent {
@Input() isShowSearch = false;
@Input() backgroundColor = '#FFF';
- theFormValue: FormGroup;
+ private theFormValue: FormGroup;
+ private formSubscription: Subscription = null;
@Input()
set theForm(value: FormGroup) {
- this.theFormValue = value;
+ if (this.theFormValue !== value) {
+ if (this.formSubscription !== null) {
+ this.formSubscription.unsubscribe();
+ this.formSubscription = null;
+ }
+ this.theFormValue = value;
+ if (this.theFormValue !== null) {
+ this.formSubscription = this.theFormValue.valueChanges.subscribe(() => this.cd.detectChanges());
+ }
+ }
}
get theForm(): FormGroup {
@@ -72,10 +83,18 @@ export class DetailsPanelComponent extends PageComponent {
}
- constructor(protected store: Store) {
+ constructor(protected store: Store,
+ private cd: ChangeDetectorRef) {
super(store);
}
+ ngOnDestroy() {
+ if (this.formSubscription !== null) {
+ this.formSubscription.unsubscribe();
+ }
+ super.ngOnDestroy();
+ }
+
onCloseDetails() {
this.closeDetails.emit();
}
diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts
index 98525ffa90..3958830088 100644
--- a/ui-ngx/src/app/modules/home/components/home-components.module.ts
+++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts
@@ -142,6 +142,7 @@ import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-pag
import { SecurityConfigLwm2mComponent } from '@home/components/device/security-config-lwm2m.component';
import { SecurityConfigLwm2mServerComponent } from '@home/components/device/security-config-lwm2m-server.component';
import { DashboardImageDialogComponent } from '@home/components/dashboard-page/dashboard-image-dialog.component';
+import { WidgetContainerComponent } from '@home/components/widget/widget-container.component';
@NgModule({
declarations:
@@ -172,6 +173,7 @@ import { DashboardImageDialogComponent } from '@home/components/dashboard-page/d
EntityAliasesDialogComponent,
EntityAliasDialogComponent,
DashboardComponent,
+ WidgetContainerComponent,
WidgetComponent,
LegendComponent,
WidgetConfigComponent,
@@ -290,6 +292,7 @@ import { DashboardImageDialogComponent } from '@home/components/dashboard-page/d
EntityAliasesDialogComponent,
EntityAliasDialogComponent,
DashboardComponent,
+ WidgetContainerComponent,
WidgetComponent,
LegendComponent,
WidgetConfigComponent,
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html
new file mode 100644
index 0000000000..09f3199b06
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html
@@ -0,0 +1,112 @@
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss
new file mode 100644
index 0000000000..ed66a2b235
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss
@@ -0,0 +1,117 @@
+/**
+ * Copyright © 2016-2021 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 {
+
+}
+
+tb-widget.tb-widget {
+ position: relative;
+ height: 100%;
+ margin: 0;
+ overflow: hidden;
+ outline: none;
+
+ transition: all .2s ease-in-out;
+}
+
+div.tb-widget {
+ position: relative;
+ height: 100%;
+ margin: 0;
+ overflow: hidden;
+ outline: none;
+
+ transition: all .2s ease-in-out;
+
+ .tb-widget-title {
+ max-height: 65px;
+ padding-top: 5px;
+ padding-left: 5px;
+ overflow: hidden;
+
+ tb-timewindow {
+ font-size: 14px;
+ opacity: .85;
+ margin: 0;
+ }
+
+ .title {
+ width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ line-height: 24px;
+ letter-spacing: .01em;
+ margin: 0;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+
+ &.single-row{
+ -webkit-line-clamp: 1;
+ }
+ }
+ }
+
+ .tb-widget-actions {
+ z-index: 19;
+ margin: 5px 0 0;
+
+ &-absolute {
+ position: absolute;
+ top: 3px;
+ right: 8px;
+ z-index: 150;
+ }
+
+ button.mat-icon-button {
+ width: 32px;
+ min-width: 32px;
+ height: 32px;
+ min-height: 32px;
+ padding: 0 !important;
+ margin: 0 !important;
+ line-height: 20px;
+
+ mat-icon {
+ width: 20px;
+ min-width: 20px;
+ height: 20px;
+ min-height: 20px;
+ font-size: 20px;
+ line-height: 20px;
+ }
+ }
+ }
+
+ .tb-widget-content {
+ &.tb-no-interaction {
+ pointer-events: none;
+ }
+ tb-widget {
+ position: relative;
+ width: 100%;
+ }
+ }
+
+ &.tb-highlighted {
+ border: 1px solid #039be5;
+ box-shadow: 0 0 20px #039be5;
+ }
+
+ &.tb-not-highlighted {
+ opacity: .5;
+ }
+}
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts
new file mode 100644
index 0000000000..cd2b322420
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts
@@ -0,0 +1,153 @@
+///
+/// Copyright © 2016-2021 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 {
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ EventEmitter,
+ Input,
+ OnInit,
+ Output
+} from '@angular/core';
+import { PageComponent } from '@shared/components/page.component';
+import { DashboardWidget, DashboardWidgets } from '@home/models/dashboard-component.models';
+import { Store } from '@ngrx/store';
+import { AppState } from '@core/core.state';
+import { SafeStyle } from '@angular/platform-browser';
+
+export enum WidgetComponentActionType {
+ MOUSE_DOWN,
+ CLICKED,
+ CONTEXT_MENU,
+ EDIT,
+ EXPORT,
+ REMOVE
+}
+
+export class WidgetComponentAction {
+ event: MouseEvent;
+ actionType: WidgetComponentActionType;
+}
+
+@Component({
+ selector: 'tb-widget-container',
+ templateUrl: './widget-container.component.html',
+ styleUrls: ['./widget-container.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class WidgetContainerComponent extends PageComponent implements OnInit {
+
+ @Input()
+ widget: DashboardWidget;
+
+ @Input()
+ dashboardStyle: {[klass: string]: any};
+
+ @Input()
+ backgroundImage: SafeStyle | string;
+
+ @Input()
+ isEdit: boolean;
+
+ @Input()
+ isMobile: boolean;
+
+ @Input()
+ dashboardWidgets: DashboardWidgets;
+
+ @Input()
+ isEditActionEnabled: boolean;
+
+ @Input()
+ isExportActionEnabled: boolean;
+
+ @Input()
+ isRemoveActionEnabled: boolean;
+
+ @Input()
+ disableWidgetInteraction = false;
+
+ @Output()
+ widgetFullscreenChanged: EventEmitter = new EventEmitter();
+
+ @Output()
+ widgetComponentAction: EventEmitter = new EventEmitter();
+
+ constructor(protected store: Store,
+ private cd: ChangeDetectorRef) {
+ super(store);
+ }
+
+ ngOnInit(): void {
+ this.widget.widgetContext.containerChangeDetector = this.cd;
+ }
+
+ isHighlighted(widget: DashboardWidget) {
+ return this.dashboardWidgets.isHighlighted(widget);
+ }
+
+ isNotHighlighted(widget: DashboardWidget) {
+ return this.dashboardWidgets.isNotHighlighted(widget);
+ }
+
+ onFullscreenChanged(expanded: boolean) {
+ this.widgetFullscreenChanged.emit(expanded);
+ }
+
+ onMouseDown(event: MouseEvent) {
+ this.widgetComponentAction.emit({
+ event,
+ actionType: WidgetComponentActionType.MOUSE_DOWN
+ });
+ }
+
+ onClicked(event: MouseEvent) {
+ this.widgetComponentAction.emit({
+ event,
+ actionType: WidgetComponentActionType.CLICKED
+ });
+ }
+
+ onContextMenu(event: MouseEvent) {
+ this.widgetComponentAction.emit({
+ event,
+ actionType: WidgetComponentActionType.CONTEXT_MENU
+ });
+ }
+
+ onEdit(event: MouseEvent) {
+ this.widgetComponentAction.emit({
+ event,
+ actionType: WidgetComponentActionType.EDIT
+ });
+ }
+
+ onExport(event: MouseEvent) {
+ this.widgetComponentAction.emit({
+ event,
+ actionType: WidgetComponentActionType.EXPORT
+ });
+ }
+
+ onRemove(event: MouseEvent) {
+ this.widgetComponentAction.emit({
+ event,
+ actionType: WidgetComponentActionType.REMOVE
+ });
+ }
+
+}
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts
index 4caf5f3edd..fea6e14291 100644
--- a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts
@@ -468,12 +468,12 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
);
}
- private detectChanges(detectDashboardChanges = false) {
+ private detectChanges(detectContainerChanges = false) {
if (!this.destroyed) {
try {
this.cd.detectChanges();
- if (detectDashboardChanges) {
- this.widgetContext.dashboard.detectChanges();
+ if (detectContainerChanges) {
+ this.widgetContext.detectContainerChanges();
}
} catch (e) {
// console.log(e);
@@ -494,7 +494,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
}
if (!this.widgetContext.inited && this.isReady()) {
this.widgetContext.inited = true;
- this.widgetContext.dashboard.detectChanges();
+ this.widgetContext.detectContainerChanges();
if (this.cafs.init) {
this.cafs.init();
this.cafs.init = null;
diff --git a/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts b/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts
index 88d09ac855..2edd7f9afa 100644
--- a/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts
+++ b/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts
@@ -79,7 +79,6 @@ export interface IDashboardComponent {
pauseChangeNotifications();
resumeChangeNotifications();
notifyLayoutUpdated();
- detectChanges();
}
declare type DashboardWidgetUpdateOperation = 'add' | 'remove' | 'update';
@@ -287,8 +286,8 @@ export class DashboardWidgets implements Iterable {
export class DashboardWidget implements GridsterItem, IDashboardWidget {
- highlighted = false;
- selected = false;
+ private highlightedValue = false;
+ private selectedValue = false;
isFullscreen = false;
@@ -335,6 +334,28 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
this.gridsterItemComponentSubject.complete();
}
+ get highlighted() {
+ return this.highlightedValue;
+ }
+
+ set highlighted(highlighted: boolean) {
+ if (this.highlightedValue !== highlighted) {
+ this.highlightedValue = highlighted;
+ this.widgetContext.detectContainerChanges();
+ }
+ }
+
+ get selected() {
+ return this.selectedValue;
+ }
+
+ set selected(selected: boolean) {
+ if (this.selectedValue !== selected) {
+ this.selectedValue = selected;
+ this.widgetContext.detectContainerChanges();
+ }
+ }
+
constructor(
private dashboard: IDashboardComponent,
public widget: Widget,
@@ -407,7 +428,7 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
this.customHeaderActions = this.widgetContext.customHeaderActions ? this.widgetContext.customHeaderActions : [];
this.widgetActions = this.widgetContext.widgetActions ? this.widgetContext.widgetActions : [];
if (detectChanges) {
- this.dashboard.detectChanges();
+ this.widgetContext.detectContainerChanges();
}
}
diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts
index 64fbe59efa..50466d7600 100644
--- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts
+++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts
@@ -136,6 +136,10 @@ export class WidgetContext {
this.changeDetectorValue = cd;
}
+ set containerChangeDetector(cd: ChangeDetectorRef) {
+ this.containerChangeDetectorValue = cd;
+ }
+
get currentUser(): AuthUser {
if (this.store) {
return getCurrentAuthUser(this.store);
@@ -162,6 +166,7 @@ export class WidgetContext {
router: Router;
private changeDetectorValue: ChangeDetectorRef;
+ private containerChangeDetectorValue: ChangeDetectorRef;
inited = false;
destroyed = false;
@@ -309,6 +314,16 @@ export class WidgetContext {
}
}
+ detectContainerChanges() {
+ if (!this.destroyed) {
+ try {
+ this.containerChangeDetectorValue.detectChanges();
+ } catch (e) {
+ // console.log(e);
+ }
+ }
+ }
+
updateWidgetParams() {
if (!this.destroyed) {
setTimeout(() => {