diff --git a/ui-ngx/src/app/app.component.ts b/ui-ngx/src/app/app.component.ts
index 19a06c3d0b..309e9a2150 100644
--- a/ui-ngx/src/app/app.component.ts
+++ b/ui-ngx/src/app/app.component.ts
@@ -49,9 +49,13 @@ export class AppComponent implements OnInit {
}
setupTranslate() {
- console.log(`Supported Langs: ${env.supportedLangs}`);
+ if (!env.production) {
+ console.log(`Supported Langs: ${env.supportedLangs}`);
+ }
this.translate.addLangs(env.supportedLangs);
- console.log(`Default Lang: ${env.defaultLang}`);
+ if (!env.production) {
+ console.log(`Default Lang: ${env.defaultLang}`);
+ }
this.translate.setDefaultLang(env.defaultLang);
}
diff --git a/ui-ngx/src/app/core/services/item-buffer.service.ts b/ui-ngx/src/app/core/services/item-buffer.service.ts
new file mode 100644
index 0000000000..edcca2701b
--- /dev/null
+++ b/ui-ngx/src/app/core/services/item-buffer.service.ts
@@ -0,0 +1,35 @@
+///
+/// Copyright © 2016-2019 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 { Injectable } from '@angular/core';
+import { Dashboard, DashboardLayoutId } from '@app/shared/models/dashboard.models';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class ItemBufferService {
+ constructor() {}
+
+ public hasWidget(): boolean {
+ // TODO:
+ return false;
+ }
+
+ public canPasteWidgetReference(dashboard: Dashboard, state: string, layout: DashboardLayoutId): boolean {
+ // TODO:
+ return false;
+ }
+}
diff --git a/ui-ngx/src/app/core/settings/settings.utils.ts b/ui-ngx/src/app/core/settings/settings.utils.ts
index 1f7a24d7d5..c5798ac793 100644
--- a/ui-ngx/src/app/core/settings/settings.utils.ts
+++ b/ui-ngx/src/app/core/settings/settings.utils.ts
@@ -14,25 +14,31 @@
/// limitations under the License.
///
-import { environment } from '@env/environment';
+import { environment as env } from '@env/environment';
import { TranslateService } from '@ngx-translate/core';
export function updateUserLang(translate: TranslateService, userLang: string) {
let targetLang = userLang;
- console.log(`User lang: ${targetLang}`);
+ if (!env.production) {
+ console.log(`User lang: ${targetLang}`);
+ }
if (!targetLang) {
targetLang = translate.getBrowserCultureLang();
- console.log(`Fallback to browser lang: ${targetLang}`);
+ if (!env.production) {
+ console.log(`Fallback to browser lang: ${targetLang}`);
+ }
}
const detectedSupportedLang = detectSupportedLang(targetLang);
- console.log(`Detected supported lang: ${detectedSupportedLang}`);
+ if (!env.production) {
+ console.log(`Detected supported lang: ${detectedSupportedLang}`);
+ }
translate.use(detectedSupportedLang);
}
function detectSupportedLang(targetLang: string): string {
const langTag = (targetLang || '').split('-').join('_');
if (langTag.length) {
- if (environment.supportedLangs.indexOf(langTag) > -1) {
+ if (env.supportedLangs.indexOf(langTag) > -1) {
return langTag;
} else {
const parts = langTag.split('_');
@@ -42,7 +48,7 @@ function detectSupportedLang(targetLang: string): string {
} else {
lang = langTag;
}
- const foundLangs = environment.supportedLangs.filter(
+ const foundLangs = env.supportedLangs.filter(
(supportedLang: string) => {
const supportedLangParts = supportedLang.split('_');
return supportedLangParts[0] === lang;
@@ -53,5 +59,5 @@ function detectSupportedLang(targetLang: string): string {
}
}
}
- return environment.defaultLang;
+ return env.defaultLang;
}
diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts
index 6b8846f8d3..192387f853 100644
--- a/ui-ngx/src/app/core/utils.ts
+++ b/ui-ngx/src/app/core/utils.ts
@@ -61,11 +61,15 @@ export function animatedScroll(element: HTMLElement, scrollTop: number, delay?:
const duration = delay ? delay : 0;
const remaining = to - start;
const animateScroll = () => {
- currentTime += increment;
- const val = easeInOut(currentTime, start, remaining, duration);
- element.scrollTop = val;
- if (currentTime < duration) {
- setTimeout(animateScroll, increment);
+ if (duration === 0) {
+ element.scrollTop = to;
+ } else {
+ currentTime += increment;
+ const val = easeInOut(currentTime, start, remaining, duration);
+ element.scrollTop = val;
+ if (currentTime < duration) {
+ setTimeout(animateScroll, increment);
+ }
}
};
animateScroll();
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 467ce77fb5..135f397b20 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
@@ -24,6 +24,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 642cec4192..b903b6fb2b 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
@@ -123,3 +123,7 @@ div.tb-widget {
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 d1c5fe976e..605b2d43eb 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
@@ -17,13 +17,14 @@
import {
AfterViewInit,
Component,
+ DoCheck,
Input,
+ IterableDiffers,
+ KeyValueDiffers,
OnChanges,
OnInit,
- QueryList,
SimpleChanges,
- ViewChild,
- ViewChildren
+ ViewChild
} from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
@@ -32,16 +33,15 @@ import { AuthUser } from '@shared/models/user.model';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { Timewindow, toHistoryTimewindow } from '@shared/models/time/time.models';
import { TimeService } from '@core/services/time.service';
-import { GridsterComponent, GridsterConfig, GridsterItemComponent } from 'angular-gridster2';
+import { GridsterComponent, GridsterConfig } from 'angular-gridster2';
import {
DashboardCallbacks,
DashboardWidget,
+ DashboardWidgets,
IDashboardComponent,
- WidgetsData,
- DashboardWidgets
+ WidgetPosition
} from '../../models/dashboard-component.models';
-import { merge, Observable, ReplaySubject, Subject } from 'rxjs';
-import { map, share, tap } from 'rxjs/operators';
+import { ReplaySubject, Subject } 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';
@@ -49,13 +49,14 @@ import { BreakpointObserver } from '@angular/cdk/layout';
import { MediaBreakpoints } from '@shared/models/constants';
import { IAliasController, IStateController } from '@app/core/api/widget-api.models';
import { Widget } from '@app/shared/models/widget.models';
+import { MatMenuTrigger } from '@angular/material';
@Component({
selector: 'tb-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss']
})
-export class DashboardComponent extends PageComponent implements IDashboardComponent, OnInit, AfterViewInit, OnChanges {
+export class DashboardComponent extends PageComponent implements IDashboardComponent, DoCheck, OnInit, AfterViewInit, OnChanges {
authUser: AuthUser;
@@ -130,25 +131,38 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
gridsterOpts: GridsterConfig;
- highlightedMode = false;
- highlightedWidget: DashboardWidget = null;
- selectedWidget: DashboardWidget = null;
-
isWidgetExpanded = false;
isMobileSize = false;
@ViewChild('gridster', {static: true}) gridster: GridsterComponent;
- @ViewChildren(GridsterItemComponent) gridsterItems: QueryList;
+ @ViewChild('dashboardMenuTrigger', {static: true}) dashboardMenuTrigger: MatMenuTrigger;
+
+ dashboardMenuPosition = { x: '0px', y: '0px' };
+
+ dashboardContextMenuEvent: MouseEvent;
+
+ @ViewChild('widgetMenuTrigger', {static: true}) widgetMenuTrigger: MatMenuTrigger;
+
+ widgetMenuPosition = { x: '0px', y: '0px' };
+
+ widgetContextMenuEvent: MouseEvent;
dashboardLoading = true;
- dashboardWidgets = new DashboardWidgets(this);
+ dashboardWidgets = new DashboardWidgets(this,
+ this.differs.find([]).create((index, item) => {
+ return item;
+ }),
+ this.kvDiffers.find([]).create()
+ );
constructor(protected store: Store,
private timeService: TimeService,
private dialogService: DialogService,
- private breakpointObserver: BreakpointObserver) {
+ private breakpointObserver: BreakpointObserver,
+ private differs: IterableDiffers,
+ private kvDiffers: KeyValueDiffers) {
super(store);
this.authUser = getCurrentAuthUser(store);
}
@@ -175,7 +189,10 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
defaultItemRows: 6,
resizable: {enabled: this.isEdit},
draggable: {enabled: this.isEdit},
- itemChangeCallback: item => this.dashboardWidgets.sortWidgets()
+ itemChangeCallback: item => this.dashboardWidgets.sortWidgets(),
+ itemInitCallback: (item, itemComponent) => {
+ (itemComponent.item as DashboardWidget).gridsterItemComponent = itemComponent;
+ }
};
this.updateMobileOpts();
@@ -184,12 +201,17 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
.observe(MediaBreakpoints['gt-sm']).subscribe(
() => {
this.updateMobileOpts();
+ this.notifyGridsterOptionsChanged();
}
);
this.updateWidgets();
}
+ ngDoCheck() {
+ this.dashboardWidgets.doCheck();
+ }
+
ngOnChanges(changes: SimpleChanges): void {
let updateMobileOpts = false;
let updateLayoutOpts = false;
@@ -206,6 +228,8 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
updateEditingOpts = true;
} else if (['widgets', 'widgetLayouts'].includes(propName)) {
updateWidgets = true;
+ } else if (propName === 'dashboardTimewindow') {
+ this.dashboardTimewindowChangedSubject.next(this.dashboardTimewindow);
}
}
}
@@ -259,14 +283,34 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
}
}
- openDashboardContextMenu($event: Event) {
- // TODO:
- // this.dialogService.todo();
+ openDashboardContextMenu($event: MouseEvent) {
+ if (this.callbacks && this.callbacks.prepareDashboardContextMenu) {
+ const items = this.callbacks.prepareDashboardContextMenu($event);
+ if (items && items.length) {
+ $event.preventDefault();
+ $event.stopPropagation();
+ this.dashboardContextMenuEvent = $event;
+ this.dashboardMenuPosition.x = $event.clientX + 'px';
+ this.dashboardMenuPosition.y = $event.clientY + 'px';
+ this.dashboardMenuTrigger.menuData = { items };
+ this.dashboardMenuTrigger.openMenu();
+ }
+ }
}
- openWidgetContextMenu($event: Event, widget: DashboardWidget) {
- // TODO:
- // this.dialogService.todo();
+ openWidgetContextMenu($event: MouseEvent, widget: DashboardWidget) {
+ if (this.callbacks && this.callbacks.prepareWidgetContextMenu) {
+ const items = this.callbacks.prepareWidgetContextMenu($event, widget.widget, widget.widgetIndex);
+ if (items && items.length) {
+ $event.preventDefault();
+ $event.stopPropagation();
+ this.widgetContextMenuEvent = $event;
+ this.widgetMenuPosition.x = $event.clientX + 'px';
+ this.widgetMenuPosition.y = $event.clientY + 'px';
+ this.widgetMenuTrigger.menuData = { items, widget: widget.widget };
+ this.widgetMenuTrigger.openMenu();
+ }
+ }
}
onWidgetFullscreenChanged(expanded: boolean, widget: DashboardWidget) {
@@ -275,13 +319,13 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
widgetMouseDown($event: Event, widget: DashboardWidget) {
if (this.callbacks && this.callbacks.onWidgetMouseDown) {
- this.callbacks.onWidgetMouseDown($event, widget.widget);
+ this.callbacks.onWidgetMouseDown($event, widget.widget, widget.widgetIndex);
}
}
widgetClicked($event: Event, widget: DashboardWidget) {
if (this.callbacks && this.callbacks.onWidgetClicked) {
- this.callbacks.onWidgetClicked($event, widget.widget);
+ this.callbacks.onWidgetClicked($event, widget.widget, widget.widgetIndex);
}
}
@@ -290,7 +334,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
$event.stopPropagation();
}
if (this.isEditActionEnabled && this.callbacks && this.callbacks.onEditWidget) {
- this.callbacks.onEditWidget($event, widget.widget);
+ this.callbacks.onEditWidget($event, widget.widget, widget.widgetIndex);
}
}
@@ -299,7 +343,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
$event.stopPropagation();
}
if (this.isExportActionEnabled && this.callbacks && this.callbacks.onExportWidget) {
- this.callbacks.onExportWidget($event, widget.widget);
+ this.callbacks.onExportWidget($event, widget.widget, widget.widgetIndex);
}
}
@@ -308,56 +352,81 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
$event.stopPropagation();
}
if (this.isRemoveActionEnabled && this.callbacks && this.callbacks.onRemoveWidget) {
- this.callbacks.onRemoveWidget($event, widget.widget).subscribe(
- (result) => {
- if (result) {
- this.dashboardWidgets.removeWidget(widget.widget);
- }
- }
- );
+ this.callbacks.onRemoveWidget($event, widget.widget, widget.widgetIndex);
}
}
- highlightWidget(widget: DashboardWidget, delay?: number) {
- if (!this.highlightedMode || this.highlightedWidget !== widget) {
- this.highlightedMode = true;
- this.highlightedWidget = widget;
- this.scrollToWidget(widget, delay);
+ highlightWidget(index: number, delay?: number) {
+ const highlighted = this.dashboardWidgets.highlightWidget(index);
+ if (highlighted) {
+ this.scrollToWidget(highlighted, delay);
}
}
- selectWidget(widget: DashboardWidget, delay?: number) {
- if (this.selectedWidget !== widget) {
- this.selectedWidget = widget;
- this.scrollToWidget(widget, delay);
+ selectWidget(index: number, delay?: number) {
+ const selected = this.dashboardWidgets.selectWidget(index);
+ if (selected) {
+ this.scrollToWidget(selected, delay);
}
}
+ getSelectedWidget(): Widget {
+ const dashboardWidget = this.dashboardWidgets.getSelectedWidget();
+ return dashboardWidget ? dashboardWidget.widget : null;
+ }
+
+ getEventGridPosition(event: Event): WidgetPosition {
+ const pos: WidgetPosition = {
+ row: 0,
+ column: 0
+ };
+ const parentElement = this.gridster.el as HTMLElement;
+ let pageX = 0;
+ let pageY = 0;
+ if (event instanceof MouseEvent) {
+ pageX = event.pageX;
+ pageY = event.pageY;
+ }
+ const x = pageX - parentElement.offsetLeft + parentElement.scrollLeft;
+ const y = pageY - parentElement.offsetTop + parentElement.scrollTop;
+ pos.row = this.gridster.pixelsToPositionY(y, Math.floor);
+ pos.column = this.gridster.pixelsToPositionX(x, Math.floor);
+ return pos;
+ }
+
resetHighlight() {
- this.highlightedMode = false;
- this.highlightedWidget = null;
- this.selectedWidget = null;
+ const highlighted = this.dashboardWidgets.resetHighlight();
+ if (highlighted) {
+ setTimeout(() => {
+ this.scrollToWidget(highlighted, 0);
+ }, 0);
+ }
}
isHighlighted(widget: DashboardWidget) {
- return (this.highlightedMode && this.highlightedWidget === widget) || (this.selectedWidget === widget);
+ return this.dashboardWidgets.isHighlighted(widget);
}
isNotHighlighted(widget: DashboardWidget) {
- return this.highlightedMode && this.highlightedWidget !== widget;
+ return this.dashboardWidgets.isNotHighlighted(widget);
}
- scrollToWidget(widget: DashboardWidget, delay?: number) {
- if (this.gridsterItems) {
- const gridsterItem = this.gridsterItems.find((item => item.item === widget));
- const offset = (this.gridster.curHeight - gridsterItem.height) / 2;
- let scrollTop = gridsterItem.top;
+ private scrollToWidget(widget: DashboardWidget, delay?: number) {
+ const parentElement = this.gridster.el as HTMLElement;
+ widget.gridsterItemComponent$().subscribe((gridsterItem) => {
+ const gridsterItemElement = gridsterItem.el as HTMLElement;
+ const offset = (parentElement.clientHeight - gridsterItemElement.clientHeight) / 2;
+ let scrollTop;
+ if (this.isMobileSize) {
+ scrollTop = gridsterItemElement.offsetTop;
+ } else {
+ scrollTop = scrollTop = gridsterItem.top;
+ }
if (offset > 0) {
scrollTop -= offset;
}
- const parentElement = this.gridster.el as HTMLElement;
animatedScroll(parentElement, scrollTop, delay);
- }
+ });
}
private updateMobileOpts() {
diff --git a/ui-ngx/src/app/modules/home/components/details-panel.component.html b/ui-ngx/src/app/modules/home/components/details-panel.component.html
index 05f855fc43..4268147c72 100644
--- a/ui-ngx/src/app/modules/home/components/details-panel.component.html
+++ b/ui-ngx/src/app/modules/home/components/details-panel.component.html
@@ -31,7 +31,7 @@
-
-
-
-
+
+
-
+
+
+
+
+
+
+
+