Browse Source

Date range navigator widget. Minor improvements.

pull/2490/head
Igor Kulikov 7 years ago
parent
commit
dfb26cc033
  1. 4
      application/src/main/data/json/system/widget_bundles/date.json
  2. 4
      application/src/main/data/json/system/widget_bundles/gpio_widgets.json
  3. 5772
      ui-ngx/package-lock.json
  4. 1
      ui-ngx/package.json
  5. 9
      ui-ngx/src/app/core/services/time.service.ts
  6. 2
      ui-ngx/src/app/core/settings/settings.utils.ts
  7. 14
      ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts
  8. 36
      ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.html
  9. 33
      ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.scss
  10. 56
      ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.html
  11. 109
      ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.scss
  12. 307
      ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.ts
  13. 109
      ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.models.ts
  14. 1
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.ts
  15. 8
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.ts
  16. 8
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.ts
  17. 8
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.ts
  18. 1
      ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.scss
  19. 11
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
  20. 2
      ui-ngx/src/app/modules/home/models/dashboard-component.models.ts
  21. 44
      ui-ngx/src/app/shared/models/time/time.models.ts
  22. 3
      ui-ngx/src/app/shared/shared.module.ts

4
application/src/main/data/json/system/widget_bundles/date.json

@ -13,9 +13,9 @@
"sizeX": 5,
"sizeY": 5.5,
"resources": [],
"templateHtml": "<date-range-navigator-widget class=\"date-range-navigator-widget\" ctx=\"ctx\"></date-range-navigator-widget>",
"templateHtml": "<tb-date-range-navigator-widget [ctx]=\"ctx\"></tb-date-range-navigator-widget>",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}",
"controllerScript": "self.onInit = function() {\n}\n",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"hidePicker\": {\n \"title\": \"Hide date range picker\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"onePanel\": {\n \"title\": \"Date range picker one panel\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"autoConfirm\": {\n \"title\": \"Date range picker auto confirm\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"showTemplate\": {\n \"title\": \"Date range picker show template\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"firstDayOfWeek\": {\n \"title\": \"First day of the week\",\n \"type\": \"number\",\n \"default\": 1\n },\n \"hideInterval\": {\n \"title\": \"Hide interval\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"initialInterval\": {\n\t\t\t\t\"title\": \"Initial interval\",\n\t\t\t\t\"type\": \"string\",\n\t\t\t\t\"default\": \"week\"\n\t\t\t},\n \"hideStepSize\": {\n \"title\": \"Hide step size\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"stepSize\": {\n\t\t\t\t\"title\": \"Initial step size\",\n\t\t\t\t\"type\": \"string\",\n\t\t\t\t\"default\": \"day\"\n\t\t\t},\n \"hideLabels\": {\n \"title\": \"Hide labels\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useSessionStorage\": {\n \"title\": \"Use session storage\",\n \"type\": \"boolean\",\n \"default\": true\n }\n }\n },\n \"form\": [\n \"hidePicker\",\n\t\t\"onePanel\",\n\t\t\"autoConfirm\",\n\t\t\"showTemplate\",\n\t\t\"firstDayOfWeek\",\n \"hideInterval\",\n {\n\t\t\t\"key\": \"initialInterval\",\n\t\t\t\"type\": \"rc-select\",\n\t\t\t\"multiple\": false,\n\t\t\t\"items\": [\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"hour\",\n\t\t\t\t\t\"label\": \"Hour\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"day\",\n\t\t\t\t\t\"label\": \"Day\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"week\",\n\t\t\t\t\t\"label\": \"Week\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"twoWeeks\",\n\t\t\t\t\t\"label\": \"2 weeks\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"month\",\n\t\t\t\t\t\"label\": \"Month\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"threeMonths\",\n\t\t\t\t\t\"label\": \"3 months\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"sixMonths\",\n\t\t\t\t\t\"label\": \"6 months\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n \"hideStepSize\",\n {\n\t\t\t\"key\": \"stepSize\",\n\t\t\t\"type\": \"rc-select\",\n\t\t\t\"multiple\": false,\n\t\t\t\"items\": [\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"hour\",\n\t\t\t\t\t\"label\": \"Hour\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"day\",\n\t\t\t\t\t\"label\": \"Day\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"week\",\n\t\t\t\t\t\"label\": \"Week\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"twoWeeks\",\n\t\t\t\t\t\"label\": \"2 weeks\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"month\",\n\t\t\t\t\t\"label\": \"Month\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"threeMonths\",\n\t\t\t\t\t\"label\": \"3 months\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"sixMonths\",\n\t\t\t\t\t\"label\": \"6 months\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t\"hideLabels\",\n\t\t\"useSessionStorage\"\n ]\n}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"defaultInterval\":\"week\",\"stepSize\":\"day\"},\"title\":\"Date-range-navigator\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"

4
application/src/main/data/json/system/widget_bundles/gpio_widgets.json

File diff suppressed because one or more lines are too long

5772
ui-ngx/package-lock.json

File diff suppressed because it is too large

1
ui-ngx/package.json

@ -62,6 +62,7 @@
"moment": "^2.24.0",
"ngx-clipboard": "^12.3.0",
"ngx-color-picker": "^9.0.0",
"ngx-daterangepicker-material": "^2.2.0",
"ngx-flowchart": "git://github.com/thingsboard/ngx-flowchart.git#master",
"ngx-hm-carousel": "^1.7.2",
"ngx-translate-messageformat-compiler": "^4.5.0",

9
ui-ngx/src/app/core/services/time.service.ts

@ -20,6 +20,7 @@ import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { defaultHttpOptions } from '@core/http/http-utils';
import { map } from 'rxjs/operators';
import { isDefined } from '@core/utils';
export interface TimeInterval {
name: string;
@ -70,10 +71,16 @@ export class TimeService {
}
public boundMinInterval(min: number): number {
if (isDefined(min)) {
min = Math.floor(min / 1000) * 1000;
}
return this.toBound(min, MIN_INTERVAL, MAX_INTERVAL, MIN_INTERVAL);
}
public boundMaxInterval(max: number): number {
if (isDefined(max)) {
max = Math.floor(max / 1000) * 1000;
}
return this.toBound(max, MIN_INTERVAL, MAX_INTERVAL, MAX_INTERVAL);
}
@ -137,7 +144,7 @@ export class TimeService {
}
private toBound(value: number, min: number, max: number, defValue: number): number {
if (typeof value !== 'undefined') {
if (isDefined(value)) {
value = Math.max(value, min);
value = Math.min(value, max);
return value;

2
ui-ngx/src/app/core/settings/settings.utils.ts

@ -16,6 +16,7 @@
import { environment as env } from '@env/environment';
import { TranslateService } from '@ngx-translate/core';
import * as _moment from 'moment';
export function updateUserLang(translate: TranslateService, userLang: string) {
let targetLang = userLang;
@ -33,6 +34,7 @@ export function updateUserLang(translate: TranslateService, userLang: string) {
console.log(`Detected supported lang: ${detectedSupportedLang}`);
}
translate.use(detectedSupportedLang);
_moment.locale([detectedSupportedLang]);
}
function detectSupportedLang(targetLang: string): string {

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

@ -19,7 +19,8 @@ import {
Component,
DoCheck,
Input,
IterableDiffers, KeyValueDiffers,
IterableDiffers,
KeyValueDiffers,
NgZone,
OnChanges,
OnDestroy,
@ -34,7 +35,7 @@ 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, GridsterComponentInterface, GridsterConfig } from 'angular-gridster2';
import { GridsterComponent, GridsterConfig } from 'angular-gridster2';
import {
DashboardCallbacks,
DashboardWidget,
@ -51,6 +52,7 @@ import { IAliasController, IStateController } from '@app/core/api/widget-api.mod
import { Widget, WidgetPosition } from '@app/shared/models/widget.models';
import { MatMenuTrigger } from '@angular/material/menu';
import { SafeStyle } from '@angular/platform-browser';
import { distinct } from 'rxjs/operators';
@Component({
selector: 'tb-dashboard',
@ -126,7 +128,9 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
dashboardTimewindowChangedSubject: Subject<Timewindow> = new ReplaySubject<Timewindow>();
dashboardTimewindowChanged = this.dashboardTimewindowChangedSubject.asObservable();
dashboardTimewindowChanged = this.dashboardTimewindowChangedSubject.asObservable().pipe(
distinct()
);
originalDashboardTimewindow: Timewindow;
@ -291,9 +295,9 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
addResizeListener(this.gridster.el, this.gridsterResizeListener);
}
onUpdateTimewindow(startTimeMs: number, endTimeMs: number, interval?: number): void {
onUpdateTimewindow(startTimeMs: number, endTimeMs: number, interval?: number, persist?: boolean): void {
this.ngZone.run(() => {
if (!this.originalDashboardTimewindow) {
if (!this.originalDashboardTimewindow && !persist) {
this.originalDashboardTimewindow = deepClone(this.dashboardTimewindow);
}
this.dashboardTimewindow = toHistoryTimewindow(this.dashboardTimewindow,

36
ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.html

@ -0,0 +1,36 @@
<!--
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.
-->
<div fxLayout="column" class="mat-content mat-padding">
<mat-form-field *ngIf="settings.showTemplate" class="mat-block">
<input readonly [ngModel]="model.chosenLabel" matInput type="text">
</mat-form-field>
<ngx-daterangepicker-material [startDate]="model.startDate"
[endDate]="model.endDate"
[locale]="locale"
[singleDatePicker]="settings.onePanel"
[autoApply]="true" (choosedDate)="choosedDate($event)">
</ngx-daterangepicker-material>
<div class="tb-panel-actions" fxLayout="row" *ngIf="!settings.autoConfirm">
<span fxFlex></span>
<button mat-button mat-raised-button color="primary"
style="margin-right: 20px;"
type="button" (click)="apply()">
{{ 'action.ok' | translate }}
</button>
</div>
</div>

33
ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.scss

@ -0,0 +1,33 @@
/**
* 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.
*/
.tb-date-range-navigator-panel {
overflow: auto;
background: #fff;
border-radius: 4px;
box-shadow:
0 7px 8px -4px rgba(0, 0, 0, .2),
0 13px 19px 2px rgba(0, 0, 0, .14),
0 5px 24px 4px rgba(0, 0, 0, .12);
.mat-content {
overflow: hidden;
background-color: #fff;
}
.mat-padding {
padding: 16px;
}
}

56
ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.html

@ -0,0 +1,56 @@
<!--
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.
-->
<div class="date-range-navigator"
[ngClass]="{'short-mode':ctx.width < 400, 'labels-hidden': settings.hideLabels, 'long-mode': ctx.width >= 400}">
<mat-form-field class="mat-block" [fxHide]="settings.hidePicker" (click)="openNavigatorPanel($event)">
<mat-label *ngIf="!settings.hideLabels" translate>widgets.date-range-navigator.localizationMap.Date picker</mat-label>
<mat-select #datePicker [ngModel]="advancedModel.chosenLabel" matInput>
<mat-option [value]="advancedModel.chosenLabel">
{{advancedModel.chosenLabel}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="mat-block" [fxHide]="settings.hideInterval">
<mat-label translate *ngIf="!settings.hideLabels">widgets.date-range-navigator.localizationMap.Interval</mat-label>
<mat-select matInput [(ngModel)]="selectedDateInterval" (ngModelChange)="changeInterval()">
<mat-option *ngIf="customInterval" [value]="customInterval.ts">
{{'widgets.date-range-navigator.localizationMap.' + customInterval.label | translate}}
</mat-option>
<mat-option *ngFor="let date of datesMap | keyvalue: originalOrder" [value]="date.value.ts">
{{'widgets.date-range-navigator.localizationMap.' + date.value.label | translate}}
</mat-option>
</mat-select>
</mat-form-field>
<div class="drn__element step" [fxHide]="settings.hideStepSize">
<button mat-button mat-icon-button (click)="goBack()">
<mat-icon>keyboard_arrow_left</mat-icon>
</button>
<mat-form-field class="mat-block" fxFlex.gt-sm>
<mat-label translate *ngIf="!settings.hideLabels">widgets.date-range-navigator.localizationMap.Step size</mat-label>
<mat-select matInput [(ngModel)]="selectedStepSize">
<mat-option *ngFor="let date of datesMap | keyvalue: originalOrder" [value]="date.value.ts">
{{'widgets.date-range-navigator.localizationMap.' + date.value.label | translate}}
</mat-option>
</mat-select>
</mat-form-field>
<button mat-button mat-icon-button (click)="goForth()">
<mat-icon>keyboard_arrow_right</mat-icon>
</button>
</div>
</div>

109
ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.scss

@ -0,0 +1,109 @@
/**
* 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.
*/
:host {
display: flex;
height: 100%;
.date-range-navigator {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-evenly;
width: 100%;
margin: auto;
.drn__element {
display: flex;
flex-direction: row;
align-items: center;
max-width: 100%;
height: 60px;
margin: 4px 0;
}
.navigation {
md-input-container {
margin: 0;
}
}
.picker {
.picker__wrapper {
position: relative;
max-width: 100%;
padding: 2px;
> label {
position: absolute;
right: -3px;
bottom: 100%;
left: 0;
padding-left: 3px;
color: #787878;
transform: scale(.75);
transform-origin: left bottom;
}
}
.md-select-value {
min-width: 225px;
border-color: #e1e1e1;
.md-select-icon {
color: #757575;
}
}
}
&.short-mode {
display: block;
width: 90%;
.drn__element {
width: 100%;
md-input-container {
flex: 1;
}
}
.picker {
.picker__wrapper {
width: 100%;
}
.md-select-value {
min-width: initial;
}
}
&.labels-hidden {
.drn__element {
margin: 0;
}
}
}
&.long-mode {
&.labels-hidden {
.drn__element {
height: 36px;
}
}
}
}
}

307
ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.ts

@ -0,0 +1,307 @@
///
/// 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 {
Component,
Inject,
InjectionToken,
Input,
OnDestroy,
OnInit,
ViewChild,
ViewContainerRef,
ViewEncapsulation
} from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { WidgetContext } from '@home/models/widget-component.models';
import { UtilsService } from '@core/services/utils.service';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import {
cloneDateRangeNavigatorModel,
DateIntervalEntry,
dateIntervalsMap,
DateRangeNavigatorModel,
DateRangeNavigatorSettings,
getFormattedDate
} from '@home/components/widget/lib/date-range-navigator/date-range-navigator.models';
import { DatePipe, KeyValue } from '@angular/common';
import * as _moment from 'moment';
import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
import { ComponentPortal, PortalInjector } from '@angular/cdk/portal';
import { MatSelect } from '@angular/material/select';
import { Subscription } from 'rxjs';
import { HistoryWindowType, TimewindowType } from '@shared/models/time/time.models';
import { isDefined } from '@core/utils';
@Component({
selector: 'tb-date-range-navigator-widget',
templateUrl: './date-range-navigator.component.html',
styleUrls: ['./date-range-navigator.component.scss']
})
export class DateRangeNavigatorWidgetComponent extends PageComponent implements OnInit, OnDestroy {
@Input()
ctx: WidgetContext;
@ViewChild('datePicker', {static: true}) datePickerSelect: MatSelect;
settings: DateRangeNavigatorSettings;
datesMap = dateIntervalsMap;
advancedModel: DateRangeNavigatorModel = {};
selectedDateInterval: number;
customInterval: DateIntervalEntry;
selectedStepSize: number;
private firstUpdate = true;
private dashboardTimewindowChangedSubscription: Subscription;
originalOrder = (a: KeyValue<number,DateIntervalEntry>, b: KeyValue<number,DateIntervalEntry>): number => {
return 0;
};
constructor(private utils: UtilsService,
private overlay: Overlay,
private viewContainerRef: ViewContainerRef,
protected store: Store<AppState>) {
super(store);
}
ngOnInit(): void {
this.dashboardTimewindowChangedSubscription = this.ctx.dashboard.dashboardTimewindowChanged.subscribe(() => {
this.widgetContextTimewindowSync();
});
this.settings = this.ctx.settings;
this.settings.useSessionStorage = isDefined(this.settings.useSessionStorage) ? this.settings.useSessionStorage : true;
let selection;
if (this.settings.useSessionStorage) {
selection = this.readFromStorage('date-range');
}
if (selection) {
this.advancedModel = {
chosenLabel: selection.name,
startDate: _moment(selection.start),
endDate: _moment(selection.end)
};
} else {
const end = new Date();
end.setHours(23, 59, 59, 999);
this.advancedModel = {
startDate: _moment((end.getTime() + 1) - this.datesMap[this.settings.initialInterval || 'week'].ts),
endDate: _moment(end.getTime())
};
this.advancedModel.chosenLabel = getFormattedDate(this.advancedModel);
}
this.selectedStepSize = this.datesMap[this.settings.stepSize || 'day'].ts;
this.widgetContextTimewindowSync();
}
ngOnDestroy(): void {
if (this.dashboardTimewindowChangedSubscription) {
this.dashboardTimewindowChangedSubscription.unsubscribe();
this.dashboardTimewindowChangedSubscription = null;
}
}
openNavigatorPanel($event: Event) {
if ($event) {
$event.stopPropagation();
}
this.datePickerSelect.close();
const target = $event.target || $event.srcElement || $event.currentTarget;
const config = new OverlayConfig();
config.backdropClass = 'cdk-overlay-transparent-backdrop';
config.hasBackdrop = true;
config.panelClass = 'tb-date-range-navigator-panel';
const connectedPosition: ConnectedPosition = {
originX: 'end',
originY: 'bottom',
overlayX: 'end',
overlayY: 'top'
};
config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement)
.withPositions([connectedPosition]);
const overlayRef = this.overlay.create(config);
overlayRef.backdropClick().subscribe(() => {
overlayRef.dispose();
});
const injectionTokens = new WeakMap<any, any>([
[DATE_RANGE_NAVIGATOR_PANEL_DATA, {
model: cloneDateRangeNavigatorModel(this.advancedModel),
settings: this.settings,
onChange: model => {
this.advancedModel = model;
this.triggerChange();
}
} as DateRangeNavigatorPanelData],
[OverlayRef, overlayRef]
]);
const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens);
overlayRef.attach(new ComponentPortal(DateRangeNavigatorPanelComponent,
this.viewContainerRef, injector));
this.ctx.detectChanges();
}
private widgetContextTimewindowSync() {
if (!this.firstUpdate) {
this.updateAdvancedModel();
}
this.updateDateInterval();
if (this.settings.useSessionStorage) {
this.updateStorageDate();
}
if (this.firstUpdate) {
this.firstUpdate = false;
this.updateTimewindow(this.advancedModel.startDate.valueOf(), this.advancedModel.endDate.valueOf());
}
this.ctx.detectChanges();
}
private updateAdvancedModel() {
const timewindow = this.ctx.dashboardTimewindow;
if (timewindow.selectedTab === TimewindowType.HISTORY && timewindow.history.historyType === HistoryWindowType.FIXED) {
const fixedTimewindow = timewindow.history.fixedTimewindow;
this.advancedModel.startDate = _moment(fixedTimewindow.startTimeMs);
this.advancedModel.endDate = _moment(fixedTimewindow.endTimeMs);
this.advancedModel.chosenLabel = getFormattedDate(this.advancedModel);
}
}
private updateTimewindow(startTime: number, endTime: number) {
this.ctx.dashboard.onUpdateTimewindow(startTime, endTime, 10, true);
}
private updateDateInterval() {
const interval = this.advancedModel.endDate.valueOf() - this.advancedModel.startDate.valueOf();
for (const key of Object.keys(this.datesMap)) {
if (Object.prototype.hasOwnProperty.call(this.datesMap, key)) {
if (this.datesMap[key].ts === interval || this.datesMap[key].ts === interval + 1 || this.datesMap[key].ts === interval - 1) {
this.selectedDateInterval = this.datesMap[key].ts;
this.customInterval = undefined;
return;
}
}
}
this.selectedDateInterval = interval;
this.customInterval = {ts: interval, label: 'Custom interval'};
}
triggerChange() {
this.updateTimewindow(this.advancedModel.startDate.valueOf(), this.advancedModel.endDate.valueOf());
}
changeInterval() {
const endTime = this.ctx.dashboard.dashboardTimewindow.history ?
this.ctx.dashboard.dashboardTimewindow.history.fixedTimewindow.endTimeMs :
this.advancedModel.endDate.valueOf();
this.updateTimewindow(endTime - this.selectedDateInterval / 2, endTime + this.selectedDateInterval / 2);
}
goBack() {
this.step(-1);
}
goForth() {
this.step(1);
}
private step(direction: number) {
const startTime = this.ctx.dashboard.dashboardTimewindow.history ?
this.ctx.dashboard.dashboardTimewindow.history.fixedTimewindow.startTimeMs :
this.advancedModel.startDate.valueOf();
const endTime = this.ctx.dashboard.dashboardTimewindow.history ?
this.ctx.dashboard.dashboardTimewindow.history.fixedTimewindow.endTimeMs :
this.advancedModel.endDate.valueOf();
this.updateTimewindow(startTime + this.selectedStepSize * direction, endTime + this.selectedStepSize * direction);
}
private readFromStorage(itemKey: string): any {
if (window.sessionStorage.getItem(itemKey)) {
const selection = JSON.parse(window.sessionStorage.getItem(itemKey));
selection.start = new Date(parseInt(selection.start, 10));
selection.end = new Date(parseInt(selection.end, 10));
return selection;
}
return undefined;
}
private updateStorageDate() {
this.saveIntoStorage('date-range', {
start: this.advancedModel.startDate.valueOf(),
end: this.advancedModel.endDate.valueOf(),
name: this.advancedModel.chosenLabel
});
}
private saveIntoStorage(keyName: string, selection: any) {
if (selection) {
window.sessionStorage.setItem(keyName, JSON.stringify(selection));
}
}
}
const DATE_RANGE_NAVIGATOR_PANEL_DATA = new InjectionToken<any>('DateRangeNavigatorPanelData');
export interface DateRangeNavigatorPanelData {
model: DateRangeNavigatorModel;
settings: DateRangeNavigatorSettings;
onChange: (model: DateRangeNavigatorModel) => void;
}
@Component({
selector: 'tb-date-range-navigator-panel',
templateUrl: './date-range-navigator-panel.component.html',
styleUrls: ['./date-range-navigator-panel.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class DateRangeNavigatorPanelComponent {
settings: DateRangeNavigatorSettings;
model: DateRangeNavigatorModel;
locale: any = {};
constructor(@Inject(DATE_RANGE_NAVIGATOR_PANEL_DATA) public data: DateRangeNavigatorPanelData,
private overlayRef: OverlayRef) {
this.model = data.model;
this.settings = data.settings;
this.locale.firstDay = this.settings.firstDayOfWeek || 1;
this.locale.daysOfWeek = _moment.weekdaysMin();
this.locale.monthNames = _moment.monthsShort();
}
choosedDate($event) {
this.model = $event;
this.model.chosenLabel = getFormattedDate(this.model);
if (this.settings.autoConfirm) {
this.data.onChange(this.model);
this.overlayRef.dispose();
}
}
apply() {
this.data.onChange(this.model);
this.overlayRef.dispose();
}
}

109
ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.models.ts

@ -0,0 +1,109 @@
///
/// 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 * as _moment from 'moment';
export type DateRangeInterval = 'hour' | 'day' | 'week' | 'twoWeeks' | 'month' | 'threeMonths' | 'sixMonths';
export interface DateRangeNavigatorSettings {
hidePicker: boolean;
onePanel: boolean;
autoConfirm: boolean;
showTemplate: boolean;
firstDayOfWeek: number;
hideInterval: boolean;
initialInterval: DateRangeInterval;
hideStepSize: boolean;
stepSize: DateRangeInterval;
hideLabels: boolean;
useSessionStorage: boolean;
}
export interface DateIntervalEntry {
ts: number;
label: string;
}
export interface DateRangeNavigatorModel {
chosenLabel?: string;
startDate?: _moment.Moment;
endDate?: _moment.Moment;
}
export function cloneDateRangeNavigatorModel(model: DateRangeNavigatorModel): DateRangeNavigatorModel {
const cloned: DateRangeNavigatorModel = {};
cloned.chosenLabel = model.chosenLabel;
cloned.startDate = model.startDate ? model.startDate.clone() : undefined;
cloned.endDate = model.endDate ? model.endDate.clone() : undefined;
return cloned;
}
export function getFormattedDate(model: DateRangeNavigatorModel): string {
let template: string;
const startDate = model.startDate;
const endDate = model.endDate;
if (startDate.diff(endDate, 'days') === 0) {
template = startDate.format('DD MMM YYYY'); // datePipe.transform(startDate, 'dd MMM yyyy');
} else {
let startDateFormat = 'DD';
if (startDate.month() !== endDate.month() || startDate.year() !== endDate.year()) {
startDateFormat += ' MMM';
}
if (startDate.year() !== endDate.year()) {
startDateFormat += ' YYYY';
}
template = startDate.format(startDateFormat) + ' - ' + endDate.format('DD MMM YYYY');
}
return template;
}
const hour = 3600000;
const day = 86400000;
const week = 604800000;
const month = 2629743000;
export const dateIntervalsMap: {[key: string]: DateIntervalEntry} = {
hour: {
ts: hour,
label: 'Hour'
},
day: {
ts: day,
label: 'Day'
},
week: {
ts: week,
label: 'Week'
},
twoWeeks: {
ts: week * 2,
label: '2 weeks'
},
month: {
ts: month,
label: 'Month'
},
threeMonths: {
ts: month * 3,
label: '3 months'
},
sixMonths: {
ts: month * 6,
label: '6 months'
}
};

1
ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.ts

@ -359,6 +359,7 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
this.updateColor(this.canvasBar.getValueColor());
this.value = this.formatValue(value);
this.checkValueSize();
this.ctx.detectChanges();
}
private updateColor(color: string) {

8
ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.ts

@ -97,8 +97,12 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe
private subscriptionOptions: WidgetSubscriptionOptions = {
callbacks: {
onDataUpdated: this.onDataUpdated.bind(this),
onDataUpdateError: this.onDataUpdateError.bind(this),
onDataUpdated: (subscription, detectChanges) => this.ctx.ngZone.run(() => {
this.onDataUpdated(subscription, detectChanges);
}),
onDataUpdateError: (subscription, e) => this.ctx.ngZone.run(() => {
this.onDataUpdateError(subscription, e);
}),
dataLoading: () => {}
}
};

8
ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.ts

@ -292,8 +292,12 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes
}
const subscriptionOptions: WidgetSubscriptionOptions = {
callbacks: {
onDataUpdated: this.onDataUpdated.bind(this),
onDataUpdateError: this.onDataUpdateError.bind(this)
onDataUpdated: (subscription, detectChanges) => this.ctx.ngZone.run(() => {
this.onDataUpdated(subscription, detectChanges);
}),
onDataUpdateError: (subscription, e) => this.ctx.ngZone.run(() => {
this.onDataUpdateError(subscription, e);
})
}
};
this.ctx.subscriptionApi.createSubscriptionFromInfo (

8
ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.ts

@ -309,8 +309,12 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy
}
const subscriptionOptions: WidgetSubscriptionOptions = {
callbacks: {
onDataUpdated: this.onDataUpdated.bind(this),
onDataUpdateError: this.onDataUpdateError.bind(this)
onDataUpdated: (subscription, detectChanges) => this.ctx.ngZone.run(() => {
this.onDataUpdated(subscription, detectChanges);
}),
onDataUpdateError: (subscription, e) => this.ctx.ngZone.run(() => {
this.onDataUpdateError(subscription, e);
})
}
};
this.ctx.subscriptionApi.createSubscriptionFromInfo (

1
ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.scss

@ -36,6 +36,7 @@
.tb-table-widget {
.mat-tab-group {
height: 100%;
overflow: hidden;
}
.mat-tab-body-wrapper {
height: 100%;

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

@ -26,6 +26,10 @@ import { TimeseriesTableWidgetComponent } from '@home/components/widget/lib/time
import { EntitiesHierarchyWidgetComponent } from '@home/components/widget/lib/entities-hierarchy-widget.component';
import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service';
import { RpcWidgetsModule } from '@home/components/widget/lib/rpc/rpc-widgets.module';
import {
DateRangeNavigatorPanelComponent,
DateRangeNavigatorWidgetComponent
} from '@home/components/widget/lib/date-range-navigator/date-range-navigator.component';
@NgModule({
declarations:
@ -35,7 +39,9 @@ import { RpcWidgetsModule } from '@home/components/widget/lib/rpc/rpc-widgets.mo
EntitiesTableWidgetComponent,
AlarmsTableWidgetComponent,
TimeseriesTableWidgetComponent,
EntitiesHierarchyWidgetComponent
EntitiesHierarchyWidgetComponent,
DateRangeNavigatorWidgetComponent,
DateRangeNavigatorPanelComponent
],
imports: [
CommonModule,
@ -48,7 +54,8 @@ import { RpcWidgetsModule } from '@home/components/widget/lib/rpc/rpc-widgets.mo
AlarmsTableWidgetComponent,
TimeseriesTableWidgetComponent,
EntitiesHierarchyWidgetComponent,
RpcWidgetsModule
RpcWidgetsModule,
DateRangeNavigatorWidgetComponent
],
providers: [
CustomDialogService

2
ui-ngx/src/app/modules/home/models/dashboard-component.models.ts

@ -66,7 +66,7 @@ export interface IDashboardComponent {
dashboardTimewindowChanged: Observable<Timewindow>;
aliasController: IAliasController;
stateController: IStateController;
onUpdateTimewindow(startTimeMs: number, endTimeMs: number, interval?: number): void;
onUpdateTimewindow(startTimeMs: number, endTimeMs: number, interval?: number, persist?: boolean): void;
onResetTimewindow(): void;
resetHighlight(): void;
highlightWidget(widgetId: string, delay?: number);

44
ui-ngx/src/app/shared/models/time/time.models.ts

@ -15,7 +15,7 @@
///
import { TimeService } from '@core/services/time.service';
import { deepClone, isDefined } from '@app/core/utils';
import { deepClone, isDefined, isUndefined } from '@app/core/utils';
export const SECOND = 1000;
export const MINUTE = 60 * SECOND;
@ -140,22 +140,36 @@ export function defaultTimewindow(timeService: TimeService): Timewindow {
export function initModelFromDefaultTimewindow(value: Timewindow, timeService: TimeService): Timewindow {
const model = defaultTimewindow(timeService);
if (value) {
if (value.realtime) {
model.selectedTab = TimewindowType.REALTIME;
if (isUndefined(value.selectedTab)) {
if (value.realtime) {
model.selectedTab = TimewindowType.REALTIME;
} else {
model.selectedTab = TimewindowType.HISTORY;
}
} else {
model.selectedTab = value.selectedTab;
}
if (model.selectedTab === TimewindowType.REALTIME) {
if (isDefined(value.realtime.interval)) {
model.realtime.interval = value.realtime.interval;
}
model.realtime.timewindowMs = value.realtime.timewindowMs;
} else {
model.selectedTab = TimewindowType.HISTORY;
if (isDefined(value.history.interval)) {
model.history.interval = value.history.interval;
}
if (isDefined(value.history.timewindowMs)) {
model.history.historyType = HistoryWindowType.LAST_INTERVAL;
if (isUndefined(value.history.historyType)) {
if (isDefined(value.history.timewindowMs)) {
model.history.historyType = HistoryWindowType.LAST_INTERVAL;
} else {
model.history.historyType = HistoryWindowType.FIXED;
}
} else {
model.history.historyType = value.history.historyType;
}
if (model.history.historyType === HistoryWindowType.LAST_INTERVAL) {
model.history.timewindowMs = value.history.timewindowMs;
} else {
model.history.historyType = HistoryWindowType.FIXED;
model.history.fixedTimewindow.startTimeMs = value.history.fixedTimewindow.startTimeMs;
model.history.fixedTimewindow.endTimeMs = value.history.fixedTimewindow.endTimeMs;
}
@ -189,7 +203,9 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number,
limit = timeService.getMaxDatapointsLimit();
}
const historyTimewindow: Timewindow = {
selectedTab: TimewindowType.HISTORY,
history: {
historyType: HistoryWindowType.FIXED,
fixedTimewindow: {
startTimeMs,
endTimeMs
@ -226,7 +242,11 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
limit: timewindow.aggregation.limit || timeService.getMaxDatapointsLimit()
};
}
if (isDefined(timewindow.realtime)) {
let selectedTab = timewindow.selectedTab;
if (isUndefined(selectedTab)) {
selectedTab = isDefined(timewindow.realtime) ? TimewindowType.REALTIME : TimewindowType.HISTORY;
}
if (selectedTab === TimewindowType.REALTIME) {
subscriptionTimewindow.realtimeWindowMs = timewindow.realtime.timewindowMs;
subscriptionTimewindow.aggregation.interval =
timeService.boundIntervalToTimewindow(subscriptionTimewindow.realtimeWindowMs, timewindow.realtime.interval,
@ -238,8 +258,12 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
subscriptionTimewindow.startTs -= startDiff;
aggTimewindow += subscriptionTimewindow.aggregation.interval;
}
} else if (isDefined(timewindow.history)) {
if (isDefined(timewindow.history.timewindowMs)) {
} else {
let historyType = timewindow.history.historyType;
if (isUndefined(historyType)) {
historyType = isDefined(timewindow.history.timewindowMs) ? HistoryWindowType.LAST_INTERVAL : HistoryWindowType.FIXED;
}
if (historyType === HistoryWindowType.LAST_INTERVAL) {
const currentTime = Date.now();
subscriptionTimewindow.fixedWindow = {
startTimeMs: currentTime - timewindow.history.timewindowMs,

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

@ -53,6 +53,7 @@ import { MatTabsModule } from '@angular/material/tabs';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core';
import { NgxDaterangepickerMd } from 'ngx-daterangepicker-material';
import { GridsterModule } from 'angular-gridster2';
import { FlexLayoutModule } from '@angular/flex-layout';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@ -232,6 +233,7 @@ import { LedLightComponent } from '@shared/components/led-light.component';
MatDatepickerModule,
MatNativeDatetimeModule,
MatDatetimepickerModule,
NgxDaterangepickerMd.forRoot(),
MatSliderModule,
MatExpansionModule,
MatStepperModule,
@ -318,6 +320,7 @@ import { LedLightComponent } from '@shared/components/led-light.component';
MatDatepickerModule,
MatNativeDatetimeModule,
MatDatetimepickerModule,
NgxDaterangepickerMd,
MatSliderModule,
MatExpansionModule,
MatStepperModule,

Loading…
Cancel
Save