Commit 193ef7ddbd3b506d6492c00b5d6cfcf137d30eb7
1 parent
087cd95b
UI: Implement timewindow control on dashboard level
Showing
24 changed files
with
631 additions
and
335 deletions
... | ... | @@ -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 | }; | ... | ... |
... | ... | @@ -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); | ... | ... |
... | ... | @@ -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; | ... | ... |