Commit 193ef7ddbd3b506d6492c00b5d6cfcf137d30eb7

Authored by Igor Kulikov
1 parent 087cd95b

UI: Implement timewindow control on dashboard level

... ... @@ -74,7 +74,7 @@ export default class DataAggregator {
74 74 }, this.aggregationTimeout, false);
75 75 }
76 76
77   - onData(data, update, history) {
  77 + onData(data, update, history, apply) {
78 78 if (!this.dataReceived || this.resetPending) {
79 79 var updateIntervalScheduledTime = true;
80 80 if (!this.dataReceived) {
... ... @@ -96,18 +96,18 @@ export default class DataAggregator {
96 96 if (updateIntervalScheduledTime) {
97 97 this.intervalScheduledTime = currentTime();
98 98 }
99   - this.onInterval(history);
  99 + this.onInterval(history, apply);
100 100 } else {
101 101 updateAggregatedData(this.aggregationMap, this.aggregationType === this.types.aggregation.count.value,
102 102 this.noAggregation, this.aggFunction, data.data, this.interval, this.startTs);
103 103 if (history) {
104 104 this.intervalScheduledTime = currentTime();
105   - this.onInterval(history);
  105 + this.onInterval(history, apply);
106 106 }
107 107 }
108 108 }
109 109
110   - onInterval(history) {
  110 + onInterval(history, apply) {
111 111 var now = currentTime();
112 112 this.elapsed += now - this.intervalScheduledTime;
113 113 this.intervalScheduledTime = now;
... ... @@ -127,7 +127,7 @@ export default class DataAggregator {
127 127 this.data = toData(this.tsKeyNames, this.aggregationMap, this.startTs, this.endTs, this.$filter, this.limit);
128 128 }
129 129 if (this.onDataCb) {
130   - this.onDataCb(this.data, this.startTs, this.endTs);
  130 + this.onDataCb(this.data, this.startTs, this.endTs, apply);
131 131 }
132 132
133 133 var self = this;
... ...
... ... @@ -197,7 +197,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
197 197 var datasourceKey = key + '_' + i;
198 198 listener.dataUpdated(datasourceData[datasourceKey],
199 199 listener.datasourceIndex,
200   - dataKey.index);
  200 + dataKey.index, false);
201 201 }
202 202 }
203 203 } else {
... ... @@ -205,7 +205,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
205 205 dataKey = dataKeys[key];
206 206 listener.dataUpdated(datasourceData[key],
207 207 listener.datasourceIndex,
208   - dataKey.index);
  208 + dataKey.index, false);
209 209 }
210 210 }
211 211 }
... ... @@ -264,7 +264,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
264 264 type: types.dataKeyType.timeseries,
265 265 onData: function (data) {
266 266 if (data.data) {
267   - onData(data.data, types.dataKeyType.timeseries);
  267 + onData(data.data, types.dataKeyType.timeseries, null, null, true);
268 268 }
269 269 },
270 270 onReconnected: function() {}
... ... @@ -287,9 +287,9 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
287 287
288 288 if (datasourceSubscription.type === types.widgetType.timeseries.value) {
289 289 updateRealtimeSubscriptionCommand(subscriptionCommand, subsTw);
290   - dataAggregator = createRealtimeDataAggregator(subsTw, tsKeyNames);
  290 + dataAggregator = createRealtimeDataAggregator(subsTw, tsKeyNames, types.dataKeyType.timeseries);
291 291 subscriber.onData = function(data) {
292   - dataAggregator.onData(data);
  292 + dataAggregator.onData(data, false, false, true);
293 293 }
294 294 subscriber.onReconnected = function() {
295 295 var newSubsTw = null;
... ... @@ -308,7 +308,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
308 308 subscriber.onReconnected = function() {}
309 309 subscriber.onData = function(data) {
310 310 if (data.data) {
311   - onData(data.data, types.dataKeyType.timeseries);
  311 + onData(data.data, types.dataKeyType.timeseries, null, null, true);
312 312 }
313 313 }
314 314 }
... ... @@ -331,7 +331,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
331 331 type: types.dataKeyType.attribute,
332 332 onData: function (data) {
333 333 if (data.data) {
334   - onData(data.data, types.dataKeyType.attribute);
  334 + onData(data.data, types.dataKeyType.attribute, null, null, true);
335 335 }
336 336 },
337 337 onReconnected: function() {}
... ... @@ -351,33 +351,24 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
351 351 tsKeyNames.push(dataKey.name+'_'+dataKey.index);
352 352 }
353 353 }
354   - dataAggregator = new DataAggregator(
355   - function (data, startTs, endTs) {
356   - onData(data, types.dataKeyType.function, startTs, endTs);
357   - },
358   - tsKeyNames,
359   - subsTw.startTs,
360   - subsTw.aggregation.limit,
361   - subsTw.aggregation.type,
362   - subsTw.aggregation.timeWindow,
363   - subsTw.aggregation.interval,
364   - types,
365   - $timeout,
366   - $filter
367   - );
  354 + dataAggregator = createRealtimeDataAggregator(subsTw, tsKeyNames, types.dataKeyType.function);
368 355 }
369 356 if (history) {
370   - onTick();
  357 + onTick(false);
371 358 } else {
372   - timer = $timeout(onTick, 0, false);
  359 + timer = $timeout(
  360 + function() {onTick(true)},
  361 + 0,
  362 + false
  363 + );
373 364 }
374 365 }
375 366 }
376 367
377   - function createRealtimeDataAggregator(subsTw, tsKeyNames) {
  368 + function createRealtimeDataAggregator(subsTw, tsKeyNames, dataKeyType) {
378 369 return new DataAggregator(
379   - function(data, startTs, endTs) {
380   - onData(data, types.dataKeyType.timeseries, startTs, endTs);
  370 + function(data, startTs, endTs, apply) {
  371 + onData(data, dataKeyType, startTs, endTs, apply);
381 372 },
382 373 tsKeyNames,
383 374 subsTw.startTs,
... ... @@ -443,7 +434,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
443 434 return data;
444 435 }
445 436
446   - function generateLatest(dataKey) {
  437 + function generateLatest(dataKey, apply) {
447 438 var prevSeries;
448 439 var datasourceKeyData = datasourceData[dataKey.key].data;
449 440 if (datasourceKeyData.length > 0) {
... ... @@ -461,11 +452,11 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
461 452 var listener = listeners[i];
462 453 listener.dataUpdated(datasourceData[dataKey.key],
463 454 listener.datasourceIndex,
464   - dataKey.index);
  455 + dataKey.index, apply);
465 456 }
466 457 }
467 458
468   - function onTick() {
  459 + function onTick(apply) {
469 460 var key;
470 461 if (datasourceSubscription.type === types.widgetType.timeseries.value) {
471 462 var startTime;
... ... @@ -495,15 +486,15 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
495 486 generatedData.data[dataKey.name+'_'+dataKey.index] = data;
496 487 }
497 488 }
498   - dataAggregator.onData(generatedData, true, history);
  489 + dataAggregator.onData(generatedData, true, history, apply);
499 490 } else if (datasourceSubscription.type === types.widgetType.latest.value) {
500 491 for (key in dataKeys) {
501   - generateLatest(dataKeys[key]);
  492 + generateLatest(dataKeys[key], apply);
502 493 }
503 494 }
504 495
505 496 if (!history) {
506   - timer = $timeout(onTick, frequency / 2, false);
  497 + timer = $timeout(function() {onTick(true)}, frequency / 2, false);
507 498 }
508 499 }
509 500
... ... @@ -519,7 +510,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
519 510 }
520 511 }
521 512
522   - function onData(sourceData, type, startTs, endTs) {
  513 + function onData(sourceData, type, startTs, endTs, apply) {
523 514 for (var keyName in sourceData) {
524 515 var keyData = sourceData[keyName];
525 516 var key = keyName + '_' + type;
... ... @@ -572,7 +563,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
572 563 var listener = listeners[i2];
573 564 listener.dataUpdated(datasourceData[datasourceKey],
574 565 listener.datasourceIndex,
575   - dataKey.index);
  566 + dataKey.index, apply);
576 567 }
577 568 }
578 569 }
... ...
... ... @@ -206,9 +206,9 @@ function TimeService($translate, types) {
206 206 function defaultTimewindow() {
207 207 var currentTime = (new Date).getTime();
208 208 var timewindow = {
209   - displayValue: "",
210   - selectedTab: 0,
211   - realtime: {
  209 + displayValue: "",
  210 + selectedTab: 0,
  211 + realtime: {
212 212 interval: SECOND,
213 213 timewindowMs: MINUTE // 1 min by default
214 214 },
... ...
... ... @@ -52,6 +52,7 @@ function Dashboard() {
52 52 bindToController: {
53 53 widgets: '=',
54 54 deviceAliasList: '=',
  55 + dashboardTimewindow: '=?',
55 56 columns: '=',
56 57 margins: '=',
57 58 isEdit: '=',
... ... @@ -71,7 +72,8 @@ function Dashboard() {
71 72 getStDiff: '&?',
72 73 onInit: '&?',
73 74 onInitFailed: '&?',
74   - dashboardStyle: '=?'
  75 + dashboardStyle: '=?',
  76 + dashboardClass: '=?'
75 77 },
76 78 controller: DashboardController,
77 79 controllerAs: 'vm',
... ... @@ -80,7 +82,7 @@ function Dashboard() {
80 82 }
81 83
82 84 /*@ngInject*/
83   -function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $log, toast, types) {
  85 +function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, timeService, types) {
84 86
85 87 var highlightedMode = false;
86 88 var highlightedWidget = null;
... ... @@ -99,6 +101,10 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
99 101
100 102 vm.isMobileDisabled = angular.isDefined(vm.isMobileDisabled) ? vm.isMobileDisabled : false;
101 103
  104 + if (!('dashboardTimewindow' in vm)) {
  105 + vm.dashboardTimewindow = timeService.defaultTimewindow();
  106 + }
  107 +
102 108 vm.dashboardLoading = true;
103 109 vm.visibleRect = {
104 110 top: 0,
... ... @@ -176,6 +182,21 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
176 182 vm.widgetContextMenuItems = [];
177 183 vm.widgetContextMenuEvent = null;
178 184
  185 + vm.dashboardTimewindowApi = {
  186 + onResetTimewindow: function() {
  187 + if (vm.originalDashboardTimewindow) {
  188 + vm.dashboardTimewindow = angular.copy(vm.originalDashboardTimewindow);
  189 + vm.originalDashboardTimewindow = null;
  190 + }
  191 + },
  192 + onUpdateTimewindow: function(startTimeMs, endTimeMs) {
  193 + if (!vm.originalDashboardTimewindow) {
  194 + vm.originalDashboardTimewindow = angular.copy(vm.dashboardTimewindow);
  195 + }
  196 + vm.dashboardTimewindow = timeService.toHistoryTimewindow(vm.dashboardTimewindow, startTimeMs, endTimeMs);
  197 + }
  198 + };
  199 +
179 200 //$element[0].onmousemove=function(){
180 201 // widgetMouseMove();
181 202 // }
... ... @@ -656,7 +677,12 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
656 677 }
657 678
658 679 function hasTimewindow(widget) {
659   - return widget.type === types.widgetType.timeseries.value;
  680 + if (widget.type === types.widgetType.timeseries.value) {
  681 + return angular.isDefined(widget.config.useDashboardTimewindow) ?
  682 + !widget.config.useDashboardTimewindow : false;
  683 + } else {
  684 + return false;
  685 + }
660 686 }
661 687
662 688 function adoptMaxRows() {
... ... @@ -673,6 +699,9 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
673 699
674 700 function dashboardLoaded() {
675 701 $timeout(function () {
  702 + $scope.$watch('vm.dashboardTimewindow', function () {
  703 + $scope.$broadcast('dashboardTimewindowChanged', vm.dashboardTimewindow);
  704 + }, true);
676 705 adoptMaxRows();
677 706 vm.dashboardLoading = false;
678 707 $timeout(function () {
... ...
... ... @@ -51,7 +51,7 @@ div.tb-widget {
51 51 height: 32px;
52 52 min-width: 32px;
53 53 min-height: 32px;
54   - md-icon {
  54 + md-icon, ng-md-icon {
55 55 width: 20px;
56 56 height: 20px;
57 57 min-width: 20px;
... ... @@ -93,6 +93,7 @@ md-content.tb-dashboard-content {
93 93 right: 0;
94 94 bottom: 0;
95 95 outline: none;
  96 + background: none;
96 97 .gridster-item {
97 98 @include transition(none);
98 99 }
... ...
... ... @@ -21,7 +21,7 @@
21 21 </md-content>
22 22 <md-menu md-position-mode="target target" tb-mousepoint-menu>
23 23 <md-content id="gridster-parent" class="tb-dashboard-content" flex layout-wrap ng-click="" tb-contextmenu="vm.openDashboardContextMenu($event, $mdOpenMousepointMenu)">
24   - <div ng-style="vm.dashboardStyle" id="gridster-background" style="height: auto; min-height: 100%;">
  24 + <div ng-class="vm.dashboardClass" id="gridster-background" style="height: auto; min-height: 100%;">
25 25 <div id="gridster-child" gridster="vm.gridsterOpts">
26 26 <ul>
27 27 <!-- ng-click="widgetClicked($event, widget)" -->
... ... @@ -30,6 +30,7 @@
30 30 <div tb-expand-fullscreen
31 31 fullscreen-background-style="vm.dashboardStyle"
32 32 expand-button-id="expand-button"
  33 + expand-button-size="20"
33 34 on-fullscreen-changed="vm.onWidgetFullscreenChanged(expanded, widget)"
34 35 layout="column"
35 36 class="tb-widget"
... ... @@ -45,55 +46,55 @@
45 46 color: vm.widgetColor(widget),
46 47 backgroundColor: vm.widgetBackgroundColor(widget),
47 48 padding: vm.widgetPadding(widget)}">
48   - <div class="tb-widget-title" layout="column" ng-show="vm.showWidgetTitle(widget) || vm.hasTimewindow(widget)">
  49 + <div class="tb-widget-title" layout="column" layout-align="center start" ng-show="vm.showWidgetTitle(widget) || vm.hasTimewindow(widget)">
49 50 <span ng-show="vm.showWidgetTitle(widget)" ng-style="vm.widgetTitleStyle(widget)" class="md-subhead">{{widget.config.title}}</span>
50   - <tb-timewindow button-color="vm.widgetColor(widget)" aggregation ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow>
  51 + <tb-timewindow aggregation ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow>
51 52 </div>
52 53 <div class="tb-widget-actions" layout="row" layout-align="start center">
53 54 <md-button id="expand-button"
54 55 ng-show="!vm.isEdit && vm.enableWidgetFullscreen(widget)"
55 56 aria-label="{{ 'fullscreen.fullscreen' | translate }}"
56   - class="md-icon-button md-primary"></md-button>
  57 + class="md-icon-button"></md-button>
57 58 <md-button ng-show="vm.isEditActionEnabled && !vm.isWidgetExpanded"
58 59 ng-disabled="vm.loading()"
59   - class="md-icon-button md-primary"
  60 + class="md-icon-button"
60 61 ng-click="vm.editWidget($event, widget)"
61 62 aria-label="{{ 'widget.edit' | translate }}">
62 63 <md-tooltip md-direction="top">
63 64 {{ 'widget.edit' | translate }}
64 65 </md-tooltip>
65   - <md-icon class="material-icons">
66   - edit
67   - </md-icon>
  66 + <ng-md-icon size="20" icon="edit"></ng-md-icon>
68 67 </md-button>
69 68 <md-button ng-show="vm.isExportActionEnabled && !vm.isWidgetExpanded"
70 69 ng-disabled="vm.loading()"
71   - class="md-icon-button md-primary"
  70 + class="md-icon-button"
72 71 ng-click="vm.exportWidget($event, widget)"
73 72 aria-label="{{ 'widget.export' | translate }}">
74 73 <md-tooltip md-direction="top">
75 74 {{ 'widget.export' | translate }}
76 75 </md-tooltip>
77   - <md-icon class="material-icons">
78   - file_download
79   - </md-icon>
  76 + <ng-md-icon size="20" icon="file_download"></ng-md-icon>
80 77 </md-button>
81 78 <md-button ng-show="vm.isRemoveActionEnabled && !vm.isWidgetExpanded"
82 79 ng-disabled="vm.loading()"
83   - class="md-icon-button md-primary"
  80 + class="md-icon-button"
84 81 ng-click="vm.removeWidget($event, widget)"
85 82 aria-label="{{ 'widget.remove' | translate }}">
86 83 <md-tooltip md-direction="top">
87 84 {{ 'widget.remove' | translate }}
88 85 </md-tooltip>
89   - <md-icon class="material-icons">
90   - close
91   - </md-icon>
  86 + <ng-md-icon size="20" icon="close"></ng-md-icon>
92 87 </md-button>
93 88 </div>
94 89 <div flex layout="column" class="tb-widget-content">
95 90 <div flex tb-widget
96   - locals="{ visibleRect: vm.visibleRect, widget: widget, deviceAliasList: vm.deviceAliasList, isEdit: vm.isEdit, stDiff: vm.stDiff }">
  91 + locals="{ visibleRect: vm.visibleRect,
  92 + widget: widget,
  93 + deviceAliasList: vm.deviceAliasList,
  94 + isEdit: vm.isEdit,
  95 + stDiff: vm.stDiff,
  96 + dashboardTimewindow: vm.dashboardTimewindow,
  97 + dashboardTimewindowApi: vm.dashboardTimewindowApi }">
97 98 </div>
98 99 </div>
99 100 </div>
... ...
... ... @@ -101,11 +101,15 @@ function ExpandFullscreen($compile, $document) {
101 101 if (attrs.expandButtonId) {
102 102 expandButton = $('#' + attrs.expandButtonId, element)[0];
103 103 }
  104 + var buttonSize;
  105 + if (attrs.expandButtonSize) {
  106 + buttonSize = attrs.expandButtonSize;
  107 + }
104 108
105 109 var html = '<md-tooltip md-direction="{{expanded ? \'bottom\' : \'top\'}}">' +
106 110 '{{(expanded ? \'fullscreen.exit\' : \'fullscreen.expand\') | translate}}' +
107 111 '</md-tooltip>' +
108   - '<ng-md-icon icon="{{expanded ? \'fullscreen_exit\' : \'fullscreen\'}}" ' +
  112 + '<ng-md-icon ' + (buttonSize ? 'size="'+ buttonSize +'" ' : '') + 'icon="{{expanded ? \'fullscreen_exit\' : \'fullscreen\'}}" ' +
109 113 'options=\'{"easing": "circ-in-out", "duration": 375, "rotation": "none"}\'>' +
110 114 '</ng-md-icon>';
111 115
... ...
... ... @@ -15,7 +15,7 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<md-button class="md-raised md-primary" ng-click="openEditMode($event)">
19   - <md-icon class="material-icons">date_range</md-icon>
  18 +<md-button ng-disabled="disabled" class="md-raised md-primary" ng-click="openEditMode($event)">
  19 + <ng-md-icon icon="query_builder"></ng-md-icon>
20 20 <span>{{model.displayValue}}</span>
21 21 </md-button>
\ No newline at end of file
... ...
... ... @@ -79,26 +79,38 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM
79 79 if (scope.asButton) {
80 80 template = $templateCache.get(timewindowButtonTemplate);
81 81 } else {
  82 + scope.direction = scope.direction || 'left';
82 83 template = $templateCache.get(timewindowTemplate);
83 84 }
84 85 element.html(template);
85 86
86 87 scope.openEditMode = function (event) {
  88 + if (scope.disabled) {
  89 + return;
  90 + }
87 91 var position;
88 92 var isGtSm = $mdMedia('gt-sm');
89 93 if (isGtSm) {
90 94 var panelHeight = 375;
  95 + var panelWidth = 417;
91 96 var offset = element[0].getBoundingClientRect();
92 97 var bottomY = offset.bottom - $(window).scrollTop(); //eslint-disable-line
  98 + var leftX = offset.left - $(window).scrollLeft(); //eslint-disable-line
93 99 var yPosition;
  100 + var xPosition;
94 101 if (bottomY + panelHeight > $( window ).height()) { //eslint-disable-line
95 102 yPosition = $mdPanel.yPosition.ABOVE;
96 103 } else {
97 104 yPosition = $mdPanel.yPosition.BELOW;
98 105 }
  106 + if (leftX + panelWidth > $( window ).width()) { //eslint-disable-line
  107 + xPosition = $mdPanel.xPosition.ALIGN_END;
  108 + } else {
  109 + xPosition = $mdPanel.xPosition.ALIGN_START;
  110 + }
99 111 position = $mdPanel.newPanelPosition()
100 112 .relativeTo(element)
101   - .addPanelPosition($mdPanel.xPosition.ALIGN_START, yPosition);
  113 + .addPanelPosition(xPosition, yPosition);
102 114 } else {
103 115 position = $mdPanel.newPanelPosition()
104 116 .absolute()
... ... @@ -223,7 +235,8 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM
223 235 require: "^ngModel",
224 236 scope: {
225 237 asButton: '=asButton',
226   - buttonColor: '=?'
  238 + direction: '=?',
  239 + disabled:'=ngDisabled'
227 240 },
228 241 link: linker
229 242 };
... ...
... ... @@ -57,3 +57,10 @@
57 57 }
58 58 }
59 59 }
  60 +
  61 +tb-timewindow {
  62 + span {
  63 + pointer-events: all;
  64 + cursor: pointer;
  65 + }
  66 +}
... ...
... ... @@ -15,9 +15,23 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<section layout='row' layout-align="start center" style="min-height: 32px;">
19   - <span ng-click="openEditMode($event)">{{model.displayValue}}</span>
20   - <md-button class="md-icon-button tb-md-32" aria-label="{{ 'timewindow.edit' | translate }}" ng-click="openEditMode($event)">
21   - <md-icon ng-style="{ color: buttonColor }" aria-label="{{ 'timewindow.date-range' | translate }}" class="material-icons">date_range</md-icon>
  18 +<section layout='row' layout-align="start center" ng-style="{minHeight: '32px', padding: '0 6px'}">
  19 + <md-button ng-if="direction === 'left'" ng-disabled="disabled" class="md-icon-button tb-md-32" aria-label="{{ 'timewindow.edit' | translate }}" ng-click="openEditMode($event)">
  20 + <md-tooltip md-direction="top">
  21 + {{ 'timewindow.edit' | translate }}
  22 + </md-tooltip>
  23 + <ng-md-icon aria-label="{{ 'timewindow.date-range' | translate }}" icon="query_builder"></ng-md-icon>
  24 + </md-button>
  25 + <span ng-click="openEditMode($event)">
  26 + <md-tooltip md-direction="top">
  27 + {{ 'timewindow.edit' | translate }}
  28 + </md-tooltip>
  29 + {{model.displayValue}}
  30 + </span>
  31 + <md-button ng-if="direction === 'right'" ng-disabled="disabled" class="md-icon-button tb-md-32" aria-label="{{ 'timewindow.edit' | translate }}" ng-click="openEditMode($event)">
  32 + <md-tooltip md-direction="top">
  33 + {{ 'timewindow.edit' | translate }}
  34 + </md-tooltip>
  35 + <ng-md-icon aria-label="{{ 'timewindow.date-range' | translate }}" icon="query_builder"></ng-md-icon>
22 36 </md-button>
23 37 </section>
\ No newline at end of file
... ...
... ... @@ -98,6 +98,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
98 98 }, true);
99 99 scope.mobileOrder = ngModelCtrl.$viewValue.mobileOrder;
100 100 scope.mobileHeight = ngModelCtrl.$viewValue.mobileHeight;
  101 + scope.useDashboardTimewindow = angular.isDefined(ngModelCtrl.$viewValue.useDashboardTimewindow) ?
  102 + ngModelCtrl.$viewValue.useDashboardTimewindow : true;
101 103 scope.timewindow = ngModelCtrl.$viewValue.timewindow;
102 104 if (scope.widgetType !== types.widgetType.rpc.value && scope.widgetType !== types.widgetType.static.value) {
103 105 if (scope.datasources) {
... ... @@ -174,7 +176,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
174 176 }
175 177 };
176 178
177   - scope.$watch('title + showTitle + dropShadow + enableFullscreen + backgroundColor + color + padding + titleStyle + mobileOrder + mobileHeight + intervalSec', function () {
  179 + scope.$watch('title + showTitle + dropShadow + enableFullscreen + backgroundColor + color + ' +
  180 + 'padding + titleStyle + mobileOrder + mobileHeight + useDashboardTimewindow', function () {
178 181 if (ngModelCtrl.$viewValue) {
179 182 var value = ngModelCtrl.$viewValue;
180 183 value.title = scope.title;
... ... @@ -191,7 +194,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
191 194 }
192 195 value.mobileOrder = angular.isNumber(scope.mobileOrder) ? scope.mobileOrder : undefined;
193 196 value.mobileHeight = scope.mobileHeight;
194   - value.intervalSec = scope.intervalSec;
  197 + value.useDashboardTimewindow = scope.useDashboardTimewindow;
195 198 ngModelCtrl.$setViewValue(value);
196 199 scope.updateValidity();
197 200 }
... ...
... ... @@ -88,10 +88,15 @@
88 88 <input ng-model="mobileHeight" type="number">
89 89 </md-input-container>
90 90 </div>
91   - <div ng-show="widgetType === types.widgetType.timeseries.value" layout="row"
92   - layout-align="center center">
93   - <span translate style="padding-right: 8px;">widget-config.timewindow</span>
94   - <tb-timewindow as-button="true" aggregation flex ng-model="timewindow"></tb-timewindow>
  91 + <div ng-show="widgetType === types.widgetType.timeseries.value" layout='column' layout-align="center"
  92 + layout-gt-sm='row' layout-align-gt-sm="start center">
  93 + <md-checkbox flex aria-label="{{ 'widget-config.use-dashboard-timewindow' | translate }}"
  94 + ng-model="useDashboardTimewindow">{{ 'widget-config.use-dashboard-timewindow' | translate }}
  95 + </md-checkbox>
  96 + <section flex layout="row" layout-align="start center" style="margin-bottom: 16px;">
  97 + <span ng-class="{'tb-disabled-label': useDashboardTimewindow}" translate style="padding-right: 8px;">widget-config.timewindow</span>
  98 + <tb-timewindow ng-disabled="useDashboardTimewindow" as-button="true" aggregation flex ng-model="timewindow"></tb-timewindow>
  99 + </section>
95 100 </div>
96 101 <v-accordion id="datasources-accordion" control="datasourcesAccordion" class="vAccordion--default"
97 102 ng-show="widgetType !== types.widgetType.rpc.value && widgetType !== types.widgetType.static.value">
... ...
... ... @@ -20,7 +20,8 @@ import 'javascript-detect-element-resize/detect-element-resize';
20 20
21 21 /*@ngInject*/
22 22 export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, tbRaf, types, utils, timeService,
23   - datasourceService, deviceService, visibleRect, isEdit, stDiff, widget, deviceAliasList, widgetType) {
  23 + datasourceService, deviceService, visibleRect, isEdit, stDiff, dashboardTimewindow,
  24 + dashboardTimewindowApi, widget, deviceAliasList, widgetType) {
24 25
25 26 var vm = this;
26 27
... ... @@ -136,6 +137,24 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
136 137 $scope.widgetErrorData = utils.processWidgetException(e);
137 138 }
138 139
  140 + function notifyDataLoaded(apply) {
  141 + if ($scope.loadingData === true) {
  142 + $scope.loadingData = false;
  143 + if (apply) {
  144 + $scope.$digest();
  145 + }
  146 + }
  147 + }
  148 +
  149 + function notifyDataLoading(apply) {
  150 + if ($scope.loadingData === false) {
  151 + $scope.loadingData = true;
  152 + if (apply) {
  153 + $scope.$digest();
  154 + }
  155 + }
  156 + }
  157 +
139 158 function onInit() {
140 159 if (!widgetContext.inited) {
141 160 widgetContext.inited = true;
... ... @@ -274,7 +293,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
274 293 }
275 294
276 295 function initialize() {
277   - if (widget.type !== types.widgetType.rpc.value) {
  296 + if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) {
278 297 for (var i in widget.config.datasources) {
279 298 var datasource = angular.copy(widget.config.datasources[i]);
280 299 for (var a in datasource.dataKeys) {
... ... @@ -287,7 +306,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
287 306 widgetContext.data.push(datasourceData);
288 307 }
289 308 }
290   - } else {
  309 + } else if (widget.type === types.widgetType.rpc.value) {
291 310 if (widget.config.targetDeviceAliasIds && widget.config.targetDeviceAliasIds.length > 0) {
292 311 targetDeviceAliasId = widget.config.targetDeviceAliasIds[0];
293 312 if (deviceAliasList[targetDeviceAliasId]) {
... ... @@ -354,14 +373,26 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
354 373 });
355 374
356 375 if (widget.type === types.widgetType.timeseries.value) {
357   - $scope.$watch(function () {
358   - return widget.config.timewindow;
359   - }, function (newTimewindow, prevTimewindow) {
360   - if (!angular.equals(newTimewindow, prevTimewindow)) {
361   - unsubscribe();
362   - subscribe();
363   - }
364   - });
  376 + widgetContext.useDashboardTimewindow = angular.isDefined(widget.config.useDashboardTimewindow)
  377 + ? widget.config.useDashboardTimewindow : true;
  378 + if (widgetContext.useDashboardTimewindow) {
  379 + $scope.$on('dashboardTimewindowChanged', function (event, newDashboardTimewindow) {
  380 + if (!angular.equals(dashboardTimewindow, newDashboardTimewindow)) {
  381 + dashboardTimewindow = newDashboardTimewindow;
  382 + unsubscribe();
  383 + subscribe();
  384 + }
  385 + });
  386 + } else {
  387 + $scope.$watch(function () {
  388 + return widgetContext.useDashboardTimewindow ? dashboardTimewindow : widget.config.timewindow;
  389 + }, function (newTimewindow, prevTimewindow) {
  390 + if (!angular.equals(newTimewindow, prevTimewindow)) {
  391 + unsubscribe();
  392 + subscribe();
  393 + }
  394 + });
  395 + }
365 396 }
366 397 subscribe();
367 398 }
... ... @@ -474,20 +505,29 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
474 505 }*/
475 506
476 507 function onResetTimewindow() {
477   - if (originalTimewindow) {
478   - widget.config.timewindow = angular.copy(originalTimewindow);
479   - originalTimewindow = null;
  508 + if (widgetContext.useDashboardTimewindow) {
  509 + dashboardTimewindowApi.onResetTimewindow();
  510 + } else {
  511 + if (originalTimewindow) {
  512 + widget.config.timewindow = angular.copy(originalTimewindow);
  513 + originalTimewindow = null;
  514 + }
480 515 }
481 516 }
482 517
483 518 function onUpdateTimewindow(startTimeMs, endTimeMs) {
484   - if (!originalTimewindow) {
485   - originalTimewindow = angular.copy(widget.config.timewindow);
  519 + if (widgetContext.useDashboardTimewindow) {
  520 + dashboardTimewindowApi.onUpdateTimewindow(startTimeMs, endTimeMs);
  521 + } else {
  522 + if (!originalTimewindow) {
  523 + originalTimewindow = angular.copy(widget.config.timewindow);
  524 + }
  525 + widget.config.timewindow = timeService.toHistoryTimewindow(widget.config.timewindow, startTimeMs, endTimeMs);
486 526 }
487   - widget.config.timewindow = timeService.toHistoryTimewindow(widget.config.timewindow, startTimeMs, endTimeMs);
488 527 }
489 528
490   - function dataUpdated(sourceData, datasourceIndex, dataKeyIndex) {
  529 + function dataUpdated(sourceData, datasourceIndex, dataKeyIndex, apply) {
  530 + notifyDataLoaded(apply);
491 531 var update = true;
492 532 if (widget.type === types.widgetType.latest.value) {
493 533 var prevData = widgetContext.data[datasourceIndex + dataKeyIndex].data;
... ... @@ -547,16 +587,28 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
547 587 if (_subscriptionTimewindow) {
548 588 subscriptionTimewindow = _subscriptionTimewindow;
549 589 } else {
550   - subscriptionTimewindow = timeService.createSubscriptionTimewindow(widget.config.timewindow, widgetContext.timeWindow.stDiff);
  590 + subscriptionTimewindow =
  591 + timeService.createSubscriptionTimewindow(
  592 + widgetContext.useDashboardTimewindow ? dashboardTimewindow : widget.config.timewindow,
  593 + widgetContext.timeWindow.stDiff);
551 594 }
552 595 updateTimewindow();
553 596 return subscriptionTimewindow;
554 597 }
555 598
  599 + function hasTimewindow() {
  600 + if (widgetContext.useDashboardTimewindow) {
  601 + return angular.isDefined(dashboardTimewindow);
  602 + } else {
  603 + return angular.isDefined(widget.config.timewindow);
  604 + }
  605 + }
  606 +
556 607 function subscribe() {
557   - if (widget.type !== types.widgetType.rpc.value) {
  608 + if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) {
  609 + notifyDataLoading();
558 610 if (widget.type === types.widgetType.timeseries.value &&
559   - angular.isDefined(widget.config.timewindow)) {
  611 + hasTimewindow()) {
560 612 updateRealtimeSubscription();
561 613 if (subscriptionTimewindow.fixedWindow) {
562 614 onDataUpdated();
... ... @@ -579,8 +631,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
579 631 subscriptionTimewindow: subscriptionTimewindow,
580 632 datasource: datasource,
581 633 deviceId: deviceId,
582   - dataUpdated: function (data, datasourceIndex, dataKeyIndex) {
583   - dataUpdated(data, datasourceIndex, dataKeyIndex);
  634 + dataUpdated: function (data, datasourceIndex, dataKeyIndex, apply) {
  635 + dataUpdated(data, datasourceIndex, dataKeyIndex, apply);
584 636 },
585 637 updateRealtimeSubscription: function() {
586 638 this.subscriptionTimewindow = updateRealtimeSubscription();
... ... @@ -601,6 +653,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
601 653 datasourceListeners.push(listener);
602 654 datasourceService.subscribeToDatasource(listener);
603 655 }
  656 + } else {
  657 + notifyDataLoaded();
604 658 }
605 659 }
606 660
... ...
... ... @@ -66,6 +66,8 @@ function Widget($controller, $compile, widgetService) {
66 66
67 67 function loadFromWidgetInfo(widgetInfo) {
68 68
  69 + scope.loadingData = true;
  70 +
69 71 elem.addClass("tb-widget");
70 72
71 73 var widgetNamespace = "widget-type-" + (widget.isSystemType ? 'sys-' : '')
... ... @@ -73,9 +75,12 @@ function Widget($controller, $compile, widgetService) {
73 75 + widget.typeAlias;
74 76
75 77 elem.addClass(widgetNamespace);
76   - elem.html('<div class="tb-absolute-fill tb-widget-error"" ng-if="widgetErrorData">' +
  78 + elem.html('<div class="tb-absolute-fill tb-widget-error" ng-if="widgetErrorData">' +
77 79 '<span>Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}</span>' +
78 80 '</div>' +
  81 + '<div class="tb-absolute-fill tb-widget-loading" ng-show="loadingData" layout="column" layout-align="center center">' +
  82 + '<md-progress-circular md-mode="indeterminate" class="md-accent" md-diameter="40"></md-progress-circular>' +
  83 + '</div>' +
79 84 '<div id="container">' + widgetInfo.templateHtml + '</div>');
80 85
81 86 $compile(elem.contents())(scope);
... ...
... ... @@ -23,4 +23,8 @@
23 23 color: red;
24 24 }
25 25 }
  26 + .tb-widget-loading {
  27 + background: rgba(255,255,255,0.15);
  28 + z-index: 3;
  29 + }
26 30 }
... ...
... ... @@ -32,6 +32,7 @@ export default function DashboardSettingsController($scope, $mdDialog, gridSetti
32 32 }
33 33
34 34 vm.gridSettings.backgroundColor = vm.gridSettings.backgroundColor || 'rgba(0,0,0,0)';
  35 + vm.gridSettings.titleColor = vm.gridSettings.titleColor || 'rgba(0,0,0,0.870588)';
35 36 vm.gridSettings.columns = vm.gridSettings.columns || 24;
36 37 vm.gridSettings.margins = vm.gridSettings.margins || [10, 10];
37 38 vm.hMargin = vm.gridSettings.margins[0];
... ...
... ... @@ -31,10 +31,22 @@
31 31 <md-dialog-content>
32 32 <div class="md-dialog-content">
33 33 <fieldset ng-disabled="loading">
34   - <div layout="row" layout-padding>
  34 + <div layout="row" layout-align="start center">
35 35 <md-checkbox flex aria-label="{{ 'dashboard.display-title' | translate }}"
36 36 ng-model="vm.gridSettings.showTitle">{{ 'dashboard.display-title' | translate }}
37 37 </md-checkbox>
  38 + <div flex
  39 + ng-required="false"
  40 + md-color-picker
  41 + ng-model="vm.gridSettings.titleColor"
  42 + label="{{ 'dashboard.title-color' | translate }}"
  43 + icon="format_color_fill"
  44 + default="rgba(0, 0, 0, 0.870588)"
  45 + md-color-clear-button="false"
  46 + open-on-input="true"
  47 + md-color-generic-palette="false"
  48 + md-color-history="false"
  49 + ></div>
38 50 </div>
39 51 <md-input-container class="md-block">
40 52 <label translate>dashboard.columns-count</label>
... ...
... ... @@ -23,7 +23,7 @@ import addWidgetTemplate from './add-widget.tpl.html';
23 23
24 24 /*@ngInject*/
25 25 export default function DashboardController(types, widgetService, userService,
26   - dashboardService, itembuffer, importExport, hotkeys, $window, $rootScope,
  26 + dashboardService, timeService, itembuffer, importExport, hotkeys, $window, $rootScope,
27 27 $scope, $state, $stateParams, $mdDialog, $timeout, $document, $q, $translate, $filter) {
28 28
29 29 var user = userService.getCurrentUser();
... ... @@ -47,6 +47,25 @@ export default function DashboardController(types, widgetService, userService,
47 47 vm.widgets = [];
48 48 vm.dashboardInitComplete = false;
49 49
  50 + vm.isToolbarOpened = false;
  51 +
  52 + Object.defineProperty(vm, 'toolbarOpened', {
  53 + get: function() { return vm.isToolbarOpened || vm.isEdit; },
  54 + set: function() { }
  55 + });
  56 +
  57 + vm.openToolbar = function() {
  58 + $timeout(function() {
  59 + vm.isToolbarOpened = true;
  60 + });
  61 + }
  62 +
  63 + vm.closeToolbar = function() {
  64 + $timeout(function() {
  65 + vm.isToolbarOpened = false;
  66 + });
  67 + }
  68 +
50 69 vm.addWidget = addWidget;
51 70 vm.addWidgetFromType = addWidgetFromType;
52 71 vm.dashboardInited = dashboardInited;
... ... @@ -154,6 +173,9 @@ export default function DashboardController(types, widgetService, userService,
154 173
155 174 if (vm.widgetEditMode) {
156 175 $timeout(function () {
  176 + vm.dashboardConfiguration = {
  177 + timewindow: timeService.defaultTimewindow()
  178 + };
157 179 vm.widgets = [{
158 180 isSystemType: true,
159 181 bundleAlias: 'customWidgetBundle',
... ... @@ -186,9 +208,12 @@ export default function DashboardController(types, widgetService, userService,
186 208 if (angular.isUndefined(vm.dashboard.configuration.deviceAliases)) {
187 209 vm.dashboard.configuration.deviceAliases = {};
188 210 }
189   - //$timeout(function () {
190   - vm.widgets = vm.dashboard.configuration.widgets;
191   - //});
  211 +
  212 + if (angular.isUndefined(vm.dashboard.configuration.timewindow)) {
  213 + vm.dashboard.configuration.timewindow = timeService.defaultTimewindow();
  214 + }
  215 + vm.dashboardConfiguration = vm.dashboard.configuration;
  216 + vm.widgets = vm.dashboard.configuration.widgets;
192 217 deferred.resolve();
193 218 }, function fail(e) {
194 219 deferred.reject(e);
... ... @@ -607,6 +632,7 @@ export default function DashboardController(types, widgetService, userService,
607 632 if (revert) {
608 633 vm.dashboard = vm.prevDashboard;
609 634 vm.widgets = vm.dashboard.configuration.widgets;
  635 + vm.dashboardConfiguration = vm.dashboard.configuration;
610 636 }
611 637 }
612 638 }
... ...
... ... @@ -13,6 +13,8 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
  17 +@import "~compass-sass-mixins/lib/compass";
16 18 @import '../../scss/constants';
17 19
18 20 section.tb-dashboard-title {
... ... @@ -53,3 +55,81 @@ tb-details-sidenav.tb-widget-details-sidenav {
53 55 }
54 56 }
55 57 }
  58 +
  59 +/***********************
  60 + * dashboard toolbar
  61 + ***********************/
  62 +
  63 +section.tb-dashboard-toolbar {
  64 + position: absolute;
  65 + top: 0px;
  66 + left: -100%;
  67 + z-index: 3;
  68 + pointer-events: none;
  69 + &.tb-dashboard-toolbar-opened {
  70 + right: 0px;
  71 + @include transition(right .3s cubic-bezier(.55,0,.55,.2));
  72 + }
  73 + &.tb-dashboard-toolbar-closed {
  74 + right: 18px;
  75 + @include transition(right .3s cubic-bezier(.55,0,.55,.2) .2s);
  76 + }
  77 + md-fab-toolbar {
  78 + &.md-is-open {
  79 + md-fab-trigger {
  80 + .md-button {
  81 + &.md-fab {
  82 + opacity: 1;
  83 + @include transition(opacity .3s cubic-bezier(.55,0,.55,.2));
  84 + }
  85 + }
  86 + }
  87 + }
  88 + md-fab-trigger {
  89 + .md-button {
  90 + &.md-fab {
  91 + line-height: 36px;
  92 + width: 36px;
  93 + height: 36px;
  94 + margin: 4px 0 0 4px;
  95 + opacity: 0.5;
  96 + @include transition(opacity .3s cubic-bezier(.55,0,.55,.2) .2s);
  97 + md-icon {
  98 + margin: 0;
  99 + line-height: 18px;
  100 + height: 18px;
  101 + width: 18px;
  102 + min-height: 18px;
  103 + min-width: 18px;
  104 + }
  105 + }
  106 + }
  107 + }
  108 + .md-fab-toolbar-wrapper {
  109 + height: 40px;
  110 + md-toolbar {
  111 + min-height: 36px;
  112 + height: 36px;
  113 + md-fab-actions {
  114 + .close-action {
  115 + margin-right: -18px;
  116 + }
  117 + tb-timewindow {
  118 + font-size: 16px;
  119 + }
  120 + }
  121 + }
  122 + }
  123 + }
  124 +}
  125 +
  126 +.tb-dashboard-container {
  127 + &.tb-dashboard-toolbar-opened {
  128 + margin-top: 40px;
  129 + @include transition(margin-top .3s cubic-bezier(.55,0,.55,.2));
  130 + }
  131 + &.tb-dashboard-toolbar-closed {
  132 + margin-top: 0px;
  133 + @include transition(margin-top .3s cubic-bezier(.55,0,.55,.2) .2s);
  134 + }
  135 +}
... ...
... ... @@ -15,235 +15,263 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<md-content flex tb-expand-fullscreen="vm.widgetEditMode || vm.iframeMode" hide-expand-button="vm.widgetEditMode || vm.iframeMode">
19   - <!--section ng-show="!vm.isAddingWidget && !loading && !vm.widgetEditMode" layout="row" layout-wrap
20   - class="tb-header-buttons tb-top-header-buttons md-fab" ng-style="{'right': '50px'}">
21   - <md-button ng-if="vm.isTenantAdmin()" ng-show="vm.isEdit" ng-disabled="loading"
22   - class="tb-btn-header md-accent md-hue-2 md-fab md-fab-bottom-right"
23   - aria-label="{{ 'action.apply' | translate }}"
24   - ng-click="vm.saveDashboard()">
25   - <md-tooltip md-direction="top">
26   - {{ 'action.apply-changes' | translate }}
27   - </md-tooltip>
28   - <ng-md-icon icon="done"></ng-md-icon>
29   - </md-button>
30   - <md-button ng-if="vm.isTenantAdmin()" ng-disabled="loading"
31   - class="tb-btn-header md-accent md-hue-2 md-fab md-fab-bottom-right"
32   - aria-label="{{ 'action.edit-mode' | translate }}"
33   - ng-click="vm.toggleDashboardEditMode()">
34   - <md-tooltip md-direction="top">
35   - {{ (vm.isEdit ? 'action.decline-changes' : 'action.enter-edit-mode') | translate }}
36   - </md-tooltip>
37   - <ng-md-icon icon="{{vm.isEdit ? 'close' : 'edit'}}"
38   - options='{"easing": "circ-in-out", "duration": 375, "rotation": "none"}'></ng-md-icon>
39   - </md-button>
40   - </section-->
41   - <section ng-show="!loading && vm.noData()" layout-align="center center"
42   - ng-class="{'tb-padded' : !vm.widgetEditMode}"
43   - style="text-transform: uppercase; display: flex; z-index: 1;"
44   - class="md-headline tb-absolute-fill">
45   - <span translate ng-if="!vm.isEdit">
46   - dashboard.no-widgets
47   - </span>
48   - <md-button ng-if="vm.isEdit && !vm.widgetEditMode" class="tb-add-new-widget" ng-click="vm.addWidget($event)">
49   - <md-icon aria-label="{{ 'action.add' | translate }}" class="material-icons tb-md-96">add</md-icon>
50   - {{ 'dashboard.add-widget' | translate }}
51   - </md-button>
52   - </section>
53   - <section ng-if="!vm.widgetEditMode" class="tb-dashboard-title" layout="row" layout-align="center center">
54   - <h3 ng-show="!vm.isEdit && vm.displayTitle()">{{ vm.dashboard.title }}</h3>
55   - <md-input-container ng-show="vm.isEdit" class="md-block" style="height: 30px;">
56   - <label translate>dashboard.title</label>
57   - <input class="tb-dashboard-title" required name="title" ng-model="vm.dashboard.title">
58   - </md-input-container>
59   - <md-button class="md-raised" flex="none" ng-show="vm.isEdit" ng-click="vm.openDeviceAliases($event)">
60   - {{ 'device.aliases' | translate }}
61   - </md-button>
62   - <md-button class="md-raised" flex="none" ng-show="vm.isEdit" ng-click="vm.openDashboardSettings($event)">
63   - {{ 'dashboard.settings' | translate }}
64   - </md-button>
65   - </section>
66   - <div class="tb-absolute-fill"
67   - ng-class="{ 'tb-padded' : !vm.widgetEditMode && (vm.isEdit || vm.displayTitle()), 'tb-shrinked' : vm.isEditingWidget }">
68   - <tb-dashboard
69   - dashboard-style="{'background-color': vm.dashboard.configuration.gridSettings.backgroundColor,
70   - 'background-image': 'url('+vm.dashboard.configuration.gridSettings.backgroundImageUrl+')',
71   - 'background-repeat': 'no-repeat',
72   - 'background-attachment': 'scroll',
73   - 'background-size': vm.dashboard.configuration.gridSettings.backgroundSizeMode || '100%',
74   - 'background-position': '0% 0%'}"
75   - widgets="vm.widgets"
76   - columns="vm.dashboard.configuration.gridSettings.columns"
77   - margins="vm.dashboard.configuration.gridSettings.margins"
78   - device-alias-list="vm.dashboard.configuration.deviceAliases"
79   - is-edit="vm.isEdit"
80   - is-mobile="vm.forceDashboardMobileMode"
81   - is-mobile-disabled="vm.widgetEditMode"
82   - is-edit-action-enabled="vm.isEdit"
83   - is-export-action-enabled="vm.isEdit && !vm.widgetEditMode"
84   - is-remove-action-enabled="vm.isEdit && !vm.widgetEditMode"
85   - on-edit-widget="vm.editWidget(event, widget)"
86   - on-export-widget="vm.exportWidget(event, widget)"
87   - on-widget-mouse-down="vm.widgetMouseDown(event, widget)"
88   - on-widget-clicked="vm.widgetClicked(event, widget)"
89   - on-widget-context-menu="vm.widgetContextMenu(event, widget)"
90   - prepare-dashboard-context-menu="vm.prepareDashboardContextMenu()"
91   - prepare-widget-context-menu="vm.prepareWidgetContextMenu(widget)"
92   - on-remove-widget="vm.removeWidget(event, widget)"
93   - load-widgets="vm.loadDashboard()"
94   - get-st-diff="vm.getServerTimeDiff()"
95   - on-init="vm.dashboardInited(dashboard)"
96   - on-init-failed="vm.dashboardInitFailed(e)">
97   - </tb-dashboard>
98   - </div>
99   - <tb-details-sidenav class="tb-widget-details-sidenav"
100   - header-title="vm.editingWidget.config.title"
101   - header-subtitle="{{vm.editingWidgetSubtitle}}"
102   - is-read-only="false"
103   - is-open="vm.isEditingWidget"
104   - is-always-edit="true"
105   - on-close-details="vm.onEditWidgetClosed()"
106   - on-toggle-details-edit-mode="vm.onRevertWidgetEdit(vm.widgetForm)"
107   - on-apply-details="vm.saveWidget(vm.widgetForm)"
108   - the-form="vm.widgetForm">
109   - <details-buttons tb-help="vm.helpLinkIdForWidgetType()" help-container-id="help-container">
110   - <div id="help-container"></div>
111   - </details-buttons>
112   - <form name="vm.widgetForm" ng-if="vm.isEditingWidget">
113   - <tb-edit-widget
114   - dashboard="vm.dashboard"
115   - widget="vm.editingWidget"
116   - the-form="vm.widgetForm">
117   - </tb-edit-widget>
118   - </form>
119   - </tb-details-sidenav>
120   - <tb-details-sidenav ng-if="!vm.widgetEditMode" class="tb-select-widget-sidenav"
121   - header-title="'dashboard.select-widget-title' | translate"
122   - header-height-px="120"
123   - is-read-only="true"
124   - is-open="vm.isAddingWidget"
125   - is-edit="false"
126   - on-close-details="vm.onAddWidgetClosed()">
127   - <header-pane ng-if="vm.isAddingWidget">
128   - <div layout="row">
129   - <span class="tb-details-subtitle">{{ 'widgets-bundle.current' | translate }}</span>
130   - <tb-widgets-bundle-select flex-offset="5"
131   - flex
132   - ng-model="vm.widgetsBundle"
133   - tb-required="true"
134   - select-first-bundle="false">
135   - </tb-widgets-bundle-select>
136   - </div>
137   - </header-pane>
138   - <div ng-if="vm.isAddingWidget">
139   - <md-tabs ng-if="vm.timeseriesWidgetTypes.length > 0 || vm.latestWidgetTypes.length > 0 ||
140   - vm.rpcWidgetTypes.length > 0 || vm.staticWidgetTypes.length > 0"
141   - flex
142   - class="tb-absolute-fill" md-border-bottom>
143   - <md-tab ng-if="vm.timeseriesWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.timeseries' | translate }}">
144   - <tb-dashboard
145   - widgets="vm.timeseriesWidgetTypes"
146   - is-edit="false"
147   - is-mobile="true"
148   - is-edit-action-enabled="false"
149   - is-remove-action-enabled="false"
150   - on-widget-clicked="vm.addWidgetFromType(event, widget)">
151   - </tb-dashboard>
152   - </md-tab>
153   - <md-tab ng-if="vm.latestWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.latest-values' | translate }}">
154   - <tb-dashboard
155   - widgets="vm.latestWidgetTypes"
156   - is-edit="false"
157   - is-mobile="true"
158   - is-edit-action-enabled="false"
159   - is-remove-action-enabled="false"
160   - on-widget-clicked="vm.addWidgetFromType(event, widget)">
161   - </tb-dashboard>
162   - </md-tab>
163   - <md-tab ng-if="vm.rpcWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.rpc' | translate }}">
164   - <tb-dashboard
165   - widgets="vm.rpcWidgetTypes"
166   - is-edit="false"
167   - is-mobile="true"
168   - is-edit-action-enabled="false"
169   - is-remove-action-enabled="false"
170   - on-widget-clicked="vm.addWidgetFromType(event, widget)">
171   - </tb-dashboard>
172   - </md-tab>
173   - <md-tab ng-if="vm.staticWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.static' | translate }}">
174   - <tb-dashboard
175   - widgets="vm.staticWidgetTypes"
176   - is-edit="false"
177   - is-mobile="true"
178   - is-edit-action-enabled="false"
179   - is-remove-action-enabled="false"
180   - on-widget-clicked="vm.addWidgetFromType(event, widget)">
181   - </tb-dashboard>
182   - </md-tab>
183   - </md-tabs>
184   - <span translate ng-if="vm.timeseriesWidgetTypes.length === 0 && vm.latestWidgetTypes.length === 0 &&
185   - vm.rpcWidgetTypes.length === 0 && vm.staticWidgetTypes.length === 0 && vm.widgetsBundle"
186   - layout-align="center center"
187   - style="text-transform: uppercase; display: flex;"
188   - class="md-headline tb-absolute-fill">widgets-bundle.empty</span>
189   - <span translate ng-if="!vm.widgetsBundle"
190   - layout-align="center center"
191   - style="text-transform: uppercase; display: flex;"
192   - class="md-headline tb-absolute-fill">widget.select-widgets-bundle</span>
193   - </div>
194   - </tb-details-sidenav>
195   - <!-- </section> -->
196   - <section layout="row" layout-wrap class="tb-footer-buttons md-fab" layout-align="start end">
197   - <md-fab-speed-dial ng-disabled="loading" ng-show="!vm.isAddingWidget && vm.isEdit && !vm.widgetEditMode"
198   - md-open="vm.addItemActionsOpen" class="md-scale" md-direction="up">
199   - <md-fab-trigger>
200   - <md-button ng-disabled="loading"
201   - class="tb-btn-footer md-accent md-hue-2 md-fab"
202   - aria-label="{{ 'dashboard.add-widget' | translate }}">
203   - <md-tooltip md-direction="top">
204   - {{ 'dashboard.add-widget' | translate }}
  18 +<md-content flex tb-expand-fullscreen="vm.widgetEditMode || vm.iframeMode" expand-button-id="dashboard-expand-button"
  19 + hide-expand-button="vm.widgetEditMode || vm.iframeMode"
  20 + ng-style="{'background-color': vm.dashboard.configuration.gridSettings.backgroundColor,
  21 + 'background-image': 'url('+vm.dashboard.configuration.gridSettings.backgroundImageUrl+')',
  22 + 'background-repeat': 'no-repeat',
  23 + 'background-attachment': 'scroll',
  24 + 'background-size': vm.dashboard.configuration.gridSettings.backgroundSizeMode || '100%',
  25 + 'background-position': '0% 0%'}">
  26 + <section class="tb-dashboard-toolbar"
  27 + ng-class="{ 'tb-dashboard-toolbar-opened': vm.toolbarOpened, 'tb-dashboard-toolbar-closed': !vm.toolbarOpened }">
  28 + <md-fab-toolbar md-open="vm.toolbarOpened"
  29 + md-direction="left">
  30 + <md-fab-trigger class="align-with-text">
  31 + <md-button aria-label="menu" class="md-fab md-primary" ng-click="vm.openToolbar()">
  32 + <md-tooltip ng-show="!vm.toolbarOpened" md-direction="top">
  33 + {{ 'dashboard.open-toolbar' | translate }}
205 34 </md-tooltip>
206   - <ng-md-icon icon="add"></ng-md-icon>
  35 + <md-icon aria-label="dashboard-toolbar" class="material-icons">more_horiz</md-icon>
207 36 </md-button>
208 37 </md-fab-trigger>
209   - <md-fab-actions>
210   - <md-button ng-disabled="loading"
211   - class="tmd-accent md-hue-2 md-fab" ng-click="vm.addWidget($event)"
212   - aria-label="{{ 'action.create' | translate }}">
213   - <md-tooltip md-direction="top">
214   - {{ 'dashboard.create-new-widget' | translate }}
215   - </md-tooltip>
216   - <ng-md-icon icon="insert_drive_file"></ng-md-icon>
217   - </md-button>
218   - <md-button ng-disabled="loading"
219   - class="tmd-accent md-hue-2 md-fab" ng-click="vm.importWidget($event)"
220   - aria-label="{{ 'action.import' | translate }}">
221   - <md-tooltip md-direction="top">
222   - {{ 'dashboard.import-widget' | translate }}
223   - </md-tooltip>
224   - <ng-md-icon icon="file_upload"></ng-md-icon>
225   - </md-button>
226   - </md-fab-actions>
227   - </md-fab-speed-dial>
228   - <md-button ng-if="vm.isTenantAdmin() || vm.isSystemAdmin()" ng-show="vm.isEdit && !vm.isAddingWidget && !loading" ng-disabled="loading"
229   - class="tb-btn-footer md-accent md-hue-2 md-fab"
230   - aria-label="{{ 'action.apply' | translate }}"
231   - ng-click="vm.saveDashboard()">
232   - <md-tooltip md-direction="top">
233   - {{ 'action.apply-changes' | translate }}
234   - </md-tooltip>
235   - <ng-md-icon icon="done"></ng-md-icon>
236   - </md-button>
237   - <md-button ng-show="!vm.isAddingWidget && !loading"
238   - ng-if="vm.isTenantAdmin() || vm.isSystemAdmin()" ng-disabled="loading"
239   - class="tb-btn-footer md-accent md-hue-2 md-fab"
240   - aria-label="{{ 'action.edit-mode' | translate }}"
241   - ng-click="vm.toggleDashboardEditMode()">
242   - <md-tooltip md-direction="top">
243   - {{ (vm.isEdit ? 'action.decline-changes' : 'action.enter-edit-mode') | translate }}
244   - </md-tooltip>
245   - <ng-md-icon icon="{{vm.isEdit ? 'close' : 'edit'}}"
246   - options='{"easing": "circ-in-out", "duration": 375, "rotation": "none"}'></ng-md-icon>
247   - </md-button>
  38 + <md-toolbar>
  39 + <md-fab-actions class="md-toolbar-tools">
  40 + <md-button ng-show="!vm.isEdit" aria-label="close-toolbar" class="md-icon-button close-action" ng-click="vm.closeToolbar()">
  41 + <md-tooltip md-direction="top">
  42 + {{ 'dashboard.close-toolbar' | translate }}
  43 + </md-tooltip>
  44 + <md-icon aria-label="close-toolbar" class="material-icons">arrow_forward</md-icon>
  45 + </md-button>
  46 + <md-button id="dashboard-expand-button"
  47 + aria-label="{{ 'fullscreen.fullscreen' | translate }}"
  48 + class="md-icon-button">
  49 + </md-button>
  50 + <tb-timewindow direction="left" aggregation ng-model="vm.dashboardConfiguration.timewindow">
  51 + </tb-timewindow>
  52 + <md-button ng-show="vm.isEdit" aria-label="{{ 'device.aliases' | translate }}" class="md-icon-button"
  53 + ng-click="vm.openDeviceAliases($event)">
  54 + <md-tooltip md-direction="top">
  55 + {{ 'device.aliases' | translate }}
  56 + </md-tooltip>
  57 + <md-icon aria-label="{{ 'device.aliases' | translate }}" class="material-icons">devices_other</md-icon>
  58 + </md-button>
  59 + <md-button ng-show="vm.isEdit" aria-label="{{ 'dashboard.settings' | translate }}" class="md-icon-button"
  60 + ng-click="vm.openDashboardSettings($event)">
  61 + <md-tooltip md-direction="top">
  62 + {{ 'dashboard.settings' | translate }}
  63 + </md-tooltip>
  64 + <md-icon aria-label="{{ 'dashboard.settings' | translate }}" class="material-icons">settings</md-icon>
  65 + </md-button>
  66 + </md-fab-actions>
  67 + </md-toolbar>
  68 + </md-fab-toolbar>
  69 + </section>
  70 + <section class="tb-dashboard-container tb-absolute-fill"
  71 + ng-class="{ 'tb-dashboard-toolbar-opened': vm.toolbarOpened, 'tb-dashboard-toolbar-closed': !vm.toolbarOpened }">
  72 + <section ng-show="!loading && vm.noData()" layout-align="center center"
  73 + ng-class="{'tb-padded' : !vm.widgetEditMode}"
  74 + style="text-transform: uppercase; display: flex; z-index: 1;"
  75 + class="md-headline tb-absolute-fill">
  76 + <span translate ng-if="!vm.isEdit">
  77 + dashboard.no-widgets
  78 + </span>
  79 + <md-button ng-if="vm.isEdit && !vm.widgetEditMode" class="tb-add-new-widget" ng-click="vm.addWidget($event)">
  80 + <md-icon aria-label="{{ 'action.add' | translate }}" class="material-icons tb-md-96">add</md-icon>
  81 + {{ 'dashboard.add-widget' | translate }}
  82 + </md-button>
  83 + </section>
  84 + <section ng-if="!vm.widgetEditMode" class="tb-dashboard-title" layout="row" layout-align="center center"
  85 + ng-style="{'color': vm.dashboard.configuration.gridSettings.titleColor}">
  86 + <h3 ng-show="!vm.isEdit && vm.displayTitle()">{{ vm.dashboard.title }}</h3>
  87 + <md-input-container ng-show="vm.isEdit" class="md-block" style="height: 30px;">
  88 + <label translate ng-style="{'color': vm.dashboard.configuration.gridSettings.titleColor}">dashboard.title</label>
  89 + <input class="tb-dashboard-title" ng-style="{'color': vm.dashboard.configuration.gridSettings.titleColor}" required name="title" ng-model="vm.dashboard.title">
  90 + </md-input-container>
  91 + </section>
  92 + <div class="tb-absolute-fill"
  93 + ng-class="{ 'tb-padded' : !vm.widgetEditMode && (vm.isEdit || vm.displayTitle()), 'tb-shrinked' : vm.isEditingWidget }">
  94 + <tb-dashboard
  95 + dashboard-style="{'background-color': vm.dashboard.configuration.gridSettings.backgroundColor,
  96 + 'background-image': 'url('+vm.dashboard.configuration.gridSettings.backgroundImageUrl+')',
  97 + 'background-repeat': 'no-repeat',
  98 + 'background-attachment': 'scroll',
  99 + 'background-size': vm.dashboard.configuration.gridSettings.backgroundSizeMode || '100%',
  100 + 'background-position': '0% 0%'}"
  101 + widgets="vm.widgets"
  102 + columns="vm.dashboard.configuration.gridSettings.columns"
  103 + margins="vm.dashboard.configuration.gridSettings.margins"
  104 + device-alias-list="vm.dashboard.configuration.deviceAliases"
  105 + dashboard-timewindow="vm.dashboardConfiguration.timewindow"
  106 + is-edit="vm.isEdit"
  107 + is-mobile="vm.forceDashboardMobileMode"
  108 + is-mobile-disabled="vm.widgetEditMode"
  109 + is-edit-action-enabled="vm.isEdit"
  110 + is-export-action-enabled="vm.isEdit && !vm.widgetEditMode"
  111 + is-remove-action-enabled="vm.isEdit && !vm.widgetEditMode"
  112 + on-edit-widget="vm.editWidget(event, widget)"
  113 + on-export-widget="vm.exportWidget(event, widget)"
  114 + on-widget-mouse-down="vm.widgetMouseDown(event, widget)"
  115 + on-widget-clicked="vm.widgetClicked(event, widget)"
  116 + on-widget-context-menu="vm.widgetContextMenu(event, widget)"
  117 + prepare-dashboard-context-menu="vm.prepareDashboardContextMenu()"
  118 + prepare-widget-context-menu="vm.prepareWidgetContextMenu(widget)"
  119 + on-remove-widget="vm.removeWidget(event, widget)"
  120 + load-widgets="vm.loadDashboard()"
  121 + get-st-diff="vm.getServerTimeDiff()"
  122 + on-init="vm.dashboardInited(dashboard)"
  123 + on-init-failed="vm.dashboardInitFailed(e)">
  124 + </tb-dashboard>
  125 + </div>
  126 + <tb-details-sidenav class="tb-widget-details-sidenav"
  127 + header-title="vm.editingWidget.config.title"
  128 + header-subtitle="{{vm.editingWidgetSubtitle}}"
  129 + is-read-only="false"
  130 + is-open="vm.isEditingWidget"
  131 + is-always-edit="true"
  132 + on-close-details="vm.onEditWidgetClosed()"
  133 + on-toggle-details-edit-mode="vm.onRevertWidgetEdit(vm.widgetForm)"
  134 + on-apply-details="vm.saveWidget(vm.widgetForm)"
  135 + the-form="vm.widgetForm">
  136 + <details-buttons tb-help="vm.helpLinkIdForWidgetType()" help-container-id="help-container">
  137 + <div id="help-container"></div>
  138 + </details-buttons>
  139 + <form name="vm.widgetForm" ng-if="vm.isEditingWidget">
  140 + <tb-edit-widget
  141 + dashboard="vm.dashboard"
  142 + widget="vm.editingWidget"
  143 + the-form="vm.widgetForm">
  144 + </tb-edit-widget>
  145 + </form>
  146 + </tb-details-sidenav>
  147 + <tb-details-sidenav ng-if="!vm.widgetEditMode" class="tb-select-widget-sidenav"
  148 + header-title="'dashboard.select-widget-title' | translate"
  149 + header-height-px="120"
  150 + is-read-only="true"
  151 + is-open="vm.isAddingWidget"
  152 + is-edit="false"
  153 + on-close-details="vm.onAddWidgetClosed()">
  154 + <header-pane ng-if="vm.isAddingWidget">
  155 + <div layout="row">
  156 + <span class="tb-details-subtitle">{{ 'widgets-bundle.current' | translate }}</span>
  157 + <tb-widgets-bundle-select flex-offset="5"
  158 + flex
  159 + ng-model="vm.widgetsBundle"
  160 + tb-required="true"
  161 + select-first-bundle="false">
  162 + </tb-widgets-bundle-select>
  163 + </div>
  164 + </header-pane>
  165 + <div ng-if="vm.isAddingWidget">
  166 + <md-tabs ng-if="vm.timeseriesWidgetTypes.length > 0 || vm.latestWidgetTypes.length > 0 ||
  167 + vm.rpcWidgetTypes.length > 0 || vm.staticWidgetTypes.length > 0"
  168 + flex
  169 + class="tb-absolute-fill" md-border-bottom>
  170 + <md-tab ng-if="vm.timeseriesWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.timeseries' | translate }}">
  171 + <tb-dashboard
  172 + widgets="vm.timeseriesWidgetTypes"
  173 + is-edit="false"
  174 + is-mobile="true"
  175 + is-edit-action-enabled="false"
  176 + is-remove-action-enabled="false"
  177 + on-widget-clicked="vm.addWidgetFromType(event, widget)">
  178 + </tb-dashboard>
  179 + </md-tab>
  180 + <md-tab ng-if="vm.latestWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.latest-values' | translate }}">
  181 + <tb-dashboard
  182 + widgets="vm.latestWidgetTypes"
  183 + is-edit="false"
  184 + is-mobile="true"
  185 + is-edit-action-enabled="false"
  186 + is-remove-action-enabled="false"
  187 + on-widget-clicked="vm.addWidgetFromType(event, widget)">
  188 + </tb-dashboard>
  189 + </md-tab>
  190 + <md-tab ng-if="vm.rpcWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.rpc' | translate }}">
  191 + <tb-dashboard
  192 + widgets="vm.rpcWidgetTypes"
  193 + is-edit="false"
  194 + is-mobile="true"
  195 + is-edit-action-enabled="false"
  196 + is-remove-action-enabled="false"
  197 + on-widget-clicked="vm.addWidgetFromType(event, widget)">
  198 + </tb-dashboard>
  199 + </md-tab>
  200 + <md-tab ng-if="vm.staticWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.static' | translate }}">
  201 + <tb-dashboard
  202 + widgets="vm.staticWidgetTypes"
  203 + is-edit="false"
  204 + is-mobile="true"
  205 + is-edit-action-enabled="false"
  206 + is-remove-action-enabled="false"
  207 + on-widget-clicked="vm.addWidgetFromType(event, widget)">
  208 + </tb-dashboard>
  209 + </md-tab>
  210 + </md-tabs>
  211 + <span translate ng-if="vm.timeseriesWidgetTypes.length === 0 && vm.latestWidgetTypes.length === 0 &&
  212 + vm.rpcWidgetTypes.length === 0 && vm.staticWidgetTypes.length === 0 && vm.widgetsBundle"
  213 + layout-align="center center"
  214 + style="text-transform: uppercase; display: flex;"
  215 + class="md-headline tb-absolute-fill">widgets-bundle.empty</span>
  216 + <span translate ng-if="!vm.widgetsBundle"
  217 + layout-align="center center"
  218 + style="text-transform: uppercase; display: flex;"
  219 + class="md-headline tb-absolute-fill">widget.select-widgets-bundle</span>
  220 + </div>
  221 + </tb-details-sidenav>
  222 + <!-- </section> -->
  223 + <section layout="row" layout-wrap class="tb-footer-buttons md-fab" layout-align="start end">
  224 + <md-fab-speed-dial ng-disabled="loading" ng-show="!vm.isAddingWidget && vm.isEdit && !vm.widgetEditMode"
  225 + md-open="vm.addItemActionsOpen" class="md-scale" md-direction="up">
  226 + <md-fab-trigger>
  227 + <md-button ng-disabled="loading"
  228 + class="tb-btn-footer md-accent md-hue-2 md-fab"
  229 + aria-label="{{ 'dashboard.add-widget' | translate }}">
  230 + <md-tooltip md-direction="top">
  231 + {{ 'dashboard.add-widget' | translate }}
  232 + </md-tooltip>
  233 + <ng-md-icon icon="add"></ng-md-icon>
  234 + </md-button>
  235 + </md-fab-trigger>
  236 + <md-fab-actions>
  237 + <md-button ng-disabled="loading"
  238 + class="tmd-accent md-hue-2 md-fab" ng-click="vm.addWidget($event)"
  239 + aria-label="{{ 'action.create' | translate }}">
  240 + <md-tooltip md-direction="top">
  241 + {{ 'dashboard.create-new-widget' | translate }}
  242 + </md-tooltip>
  243 + <ng-md-icon icon="insert_drive_file"></ng-md-icon>
  244 + </md-button>
  245 + <md-button ng-disabled="loading"
  246 + class="tmd-accent md-hue-2 md-fab" ng-click="vm.importWidget($event)"
  247 + aria-label="{{ 'action.import' | translate }}">
  248 + <md-tooltip md-direction="top">
  249 + {{ 'dashboard.import-widget' | translate }}
  250 + </md-tooltip>
  251 + <ng-md-icon icon="file_upload"></ng-md-icon>
  252 + </md-button>
  253 + </md-fab-actions>
  254 + </md-fab-speed-dial>
  255 + <md-button ng-if="vm.isTenantAdmin() || vm.isSystemAdmin()" ng-show="vm.isEdit && !vm.isAddingWidget && !loading" ng-disabled="loading"
  256 + class="tb-btn-footer md-accent md-hue-2 md-fab"
  257 + aria-label="{{ 'action.apply' | translate }}"
  258 + ng-click="vm.saveDashboard()">
  259 + <md-tooltip md-direction="top">
  260 + {{ 'action.apply-changes' | translate }}
  261 + </md-tooltip>
  262 + <ng-md-icon icon="done"></ng-md-icon>
  263 + </md-button>
  264 + <md-button ng-show="!vm.isAddingWidget && !loading"
  265 + ng-if="vm.isTenantAdmin() || vm.isSystemAdmin()" ng-disabled="loading"
  266 + class="tb-btn-footer md-accent md-hue-2 md-fab"
  267 + aria-label="{{ 'action.edit-mode' | translate }}"
  268 + ng-click="vm.toggleDashboardEditMode()">
  269 + <md-tooltip md-direction="top">
  270 + {{ (vm.isEdit ? 'action.decline-changes' : 'action.enter-edit-mode') | translate }}
  271 + </md-tooltip>
  272 + <ng-md-icon icon="{{vm.isEdit ? 'close' : 'edit'}}"
  273 + options='{"easing": "circ-in-out", "duration": 375, "rotation": "none"}'></ng-md-icon>
  274 + </md-button>
  275 + </section>
248 276 </section>
249 277 </md-content>
... ...
... ... @@ -247,6 +247,7 @@ export default angular.module('thingsboard.locale', [])
247 247 "min-vertical-margin-message": "Only 0 is allowed as minimum vertical margin value.",
248 248 "max-vertical-margin-message": "Only 50 is allowed as maximum vertical margin value.",
249 249 "display-title": "Display dashboard title",
  250 + "title-color": "Title color",
250 251 "import": "Import dashboard",
251 252 "export": "Export dashboard",
252 253 "export-failed-error": "Unable to export dashboard: {error}",
... ... @@ -258,7 +259,9 @@ export default angular.module('thingsboard.locale', [])
258 259 "import-widget": "Import widget",
259 260 "widget-file": "Widget file",
260 261 "invalid-widget-file-error": "Unable to import widget: Invalid widget data structure.",
261   - "widget-import-missing-aliases-title": "Select missing devices used by widget"
  262 + "widget-import-missing-aliases-title": "Select missing devices used by widget",
  263 + "open-toolbar": "Open dashboard toolbar",
  264 + "close-toolbar": "Close toolbar"
262 265 },
263 266 "datakey": {
264 267 "settings": "Settings",
... ... @@ -702,6 +705,7 @@ export default angular.module('thingsboard.locale', [])
702 705 "order": "Order",
703 706 "height": "Height",
704 707 "timewindow": "Timewindow",
  708 + "use-dashboard-timewindow": "Use dashboard timewindow",
705 709 "datasources": "Datasources",
706 710 "datasource-type": "Type",
707 711 "datasource-parameters": "Parameters",
... ...
... ... @@ -360,9 +360,12 @@ export default class TbFlot {
360 360 update() {
361 361 if (!this.isMouseInteraction) {
362 362 if (this.chartType === 'line' || this.chartType === 'bar') {
  363 + this.options.xaxis.min = this.ctx.timeWindow.minTime;
  364 + this.options.xaxis.max = this.ctx.timeWindow.maxTime;
363 365 this.ctx.plot.getOptions().xaxes[0].min = this.ctx.timeWindow.minTime;
364 366 this.ctx.plot.getOptions().xaxes[0].max = this.ctx.timeWindow.maxTime;
365 367 if (this.chartType === 'bar') {
  368 + this.options.series.bars.barWidth = this.ctx.timeWindow.interval * 0.6;
366 369 this.ctx.plot.getOptions().series.bars.barWidth = this.ctx.timeWindow.interval * 0.6;
367 370 }
368 371 this.ctx.plot.setData(this.ctx.data);
... ... @@ -879,6 +882,7 @@ export default class TbFlot {
879 882 destroy() {
880 883 if (this.ctx.plot) {
881 884 this.ctx.plot.destroy();
  885 + this.ctx.plot = null;
882 886 }
883 887 }
884 888
... ...
... ... @@ -193,6 +193,12 @@ md-sidenav {
193 193 pointer-events: all;
194 194 }
195 195
  196 +.md-color-picker-input-container {
  197 + md-input-container {
  198 + margin-bottom: 0px;
  199 + }
  200 +}
  201 +
196 202 /***********************
197 203 * THINGSBOARD SPECIFIC
198 204 ***********************/
... ... @@ -201,6 +207,10 @@ md-sidenav {
201 207 color: rgba(0,0,0,0.54);
202 208 }
203 209
  210 +.tb-disabled-label {
  211 + color: rgba(0,0,0,0.44);
  212 +}
  213 +
204 214 label {
205 215 &.tb-small {
206 216 pointer-events: none;
... ...