Showing
18 changed files
with
728 additions
and
167 deletions
... | ... | @@ -49,6 +49,7 @@ import thingsboardDialogs from './components/datakey-config-dialog.controller'; |
49 | 49 | import thingsboardMenu from './services/menu.service'; |
50 | 50 | import thingsboardUtils from './common/utils.service'; |
51 | 51 | import thingsboardTypes from './common/types.constant'; |
52 | +import thingsboardKeyboardShortcut from './components/keyboard-shortcut.filter'; | |
52 | 53 | import thingsboardHelp from './help/help.directive'; |
53 | 54 | import thingsboardToast from './services/toast'; |
54 | 55 | import thingsboardHome from './layout'; |
... | ... | @@ -95,6 +96,7 @@ angular.module('thingsboard', [ |
95 | 96 | thingsboardMenu, |
96 | 97 | thingsboardUtils, |
97 | 98 | thingsboardTypes, |
99 | + thingsboardKeyboardShortcut, | |
98 | 100 | thingsboardHelp, |
99 | 101 | thingsboardToast, |
100 | 102 | thingsboardHome, | ... | ... |
... | ... | @@ -23,6 +23,7 @@ import thingsboardWidget from './widget.directive'; |
23 | 23 | import thingsboardToast from '../services/toast'; |
24 | 24 | import thingsboardTimewindow from './timewindow.directive'; |
25 | 25 | import thingsboardEvents from './tb-event-directives'; |
26 | +import thingsboardMousepointMenu from './mousepoint-menu.directive'; | |
26 | 27 | |
27 | 28 | /* eslint-disable import/no-unresolved, import/default */ |
28 | 29 | |
... | ... | @@ -38,6 +39,7 @@ export default angular.module('thingsboard.directives.dashboard', [thingsboardTy |
38 | 39 | thingsboardWidget, |
39 | 40 | thingsboardTimewindow, |
40 | 41 | thingsboardEvents, |
42 | + thingsboardMousepointMenu, | |
41 | 43 | gridster.name]) |
42 | 44 | .directive('tbDashboard', Dashboard) |
43 | 45 | .name; |
... | ... | @@ -59,7 +61,10 @@ function Dashboard() { |
59 | 61 | isRemoveActionEnabled: '=', |
60 | 62 | onEditWidget: '&?', |
61 | 63 | onRemoveWidget: '&?', |
64 | + onWidgetMouseDown: '&?', | |
62 | 65 | onWidgetClicked: '&?', |
66 | + prepareDashboardContextMenu: '&?', | |
67 | + prepareWidgetContextMenu: '&?', | |
63 | 68 | loadWidgets: '&?', |
64 | 69 | onInit: '&?', |
65 | 70 | onInitFailed: '&?', |
... | ... | @@ -75,8 +80,9 @@ function Dashboard() { |
75 | 80 | function DashboardController($scope, $rootScope, $element, $timeout, $log, toast, types) { |
76 | 81 | |
77 | 82 | var highlightedMode = false; |
78 | - var highlightedIndex = -1; | |
79 | - var mouseDownIndex = -1; | |
83 | + var highlightedWidget = null; | |
84 | + var selectedWidget = null; | |
85 | + var mouseDownWidget = -1; | |
80 | 86 | var widgetMouseMoved = false; |
81 | 87 | |
82 | 88 | var gridsterParent = null; |
... | ... | @@ -117,6 +123,8 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast |
117 | 123 | vm.isWidgetExpanded = false; |
118 | 124 | vm.isHighlighted = isHighlighted; |
119 | 125 | vm.isNotHighlighted = isNotHighlighted; |
126 | + vm.selectWidget = selectWidget; | |
127 | + vm.getSelectedWidget = getSelectedWidget; | |
120 | 128 | vm.highlightWidget = highlightWidget; |
121 | 129 | vm.resetHighlight = resetHighlight; |
122 | 130 | |
... | ... | @@ -134,6 +142,17 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast |
134 | 142 | vm.removeWidget = removeWidget; |
135 | 143 | vm.loading = loading; |
136 | 144 | |
145 | + vm.openDashboardContextMenu = openDashboardContextMenu; | |
146 | + vm.openWidgetContextMenu = openWidgetContextMenu; | |
147 | + | |
148 | + vm.getEventGridPosition = getEventGridPosition; | |
149 | + | |
150 | + vm.contextMenuItems = []; | |
151 | + vm.contextMenuEvent = null; | |
152 | + | |
153 | + vm.widgetContextMenuItems = []; | |
154 | + vm.widgetContextMenuEvent = null; | |
155 | + | |
137 | 156 | //$element[0].onmousemove=function(){ |
138 | 157 | // widgetMouseMove(); |
139 | 158 | // } |
... | ... | @@ -305,7 +324,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast |
305 | 324 | } |
306 | 325 | |
307 | 326 | function resetWidgetClick () { |
308 | - mouseDownIndex = -1; | |
327 | + mouseDownWidget = -1; | |
309 | 328 | widgetMouseMoved = false; |
310 | 329 | } |
311 | 330 | |
... | ... | @@ -315,25 +334,27 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast |
315 | 334 | } |
316 | 335 | |
317 | 336 | function widgetMouseDown ($event, widget) { |
318 | - mouseDownIndex = vm.widgets.indexOf(widget); | |
337 | + mouseDownWidget = widget; | |
319 | 338 | widgetMouseMoved = false; |
339 | + if (vm.onWidgetMouseDown) { | |
340 | + vm.onWidgetMouseDown({event: $event, widget: widget}); | |
341 | + } | |
320 | 342 | } |
321 | 343 | |
322 | 344 | function widgetMouseMove () { |
323 | - if (mouseDownIndex > -1) { | |
345 | + if (mouseDownWidget) { | |
324 | 346 | widgetMouseMoved = true; |
325 | 347 | } |
326 | 348 | } |
327 | 349 | |
328 | 350 | function widgetMouseUp ($event, widget) { |
329 | 351 | $timeout(function () { |
330 | - if (!widgetMouseMoved && mouseDownIndex > -1) { | |
331 | - var index = vm.widgets.indexOf(widget); | |
332 | - if (index === mouseDownIndex) { | |
352 | + if (!widgetMouseMoved && mouseDownWidget) { | |
353 | + if (widget === mouseDownWidget) { | |
333 | 354 | widgetClicked($event, widget); |
334 | 355 | } |
335 | 356 | } |
336 | - mouseDownIndex = -1; | |
357 | + mouseDownWidget = null; | |
337 | 358 | widgetMouseMoved = false; |
338 | 359 | }, 0); |
339 | 360 | } |
... | ... | @@ -347,6 +368,41 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast |
347 | 368 | } |
348 | 369 | } |
349 | 370 | |
371 | + function openDashboardContextMenu($event, $mdOpenMousepointMenu) { | |
372 | + if (vm.prepareDashboardContextMenu) { | |
373 | + vm.contextMenuItems = vm.prepareDashboardContextMenu(); | |
374 | + if (vm.contextMenuItems && vm.contextMenuItems.length > 0) { | |
375 | + vm.contextMenuEvent = $event; | |
376 | + $mdOpenMousepointMenu($event); | |
377 | + } | |
378 | + } | |
379 | + } | |
380 | + | |
381 | + function openWidgetContextMenu($event, widget, $mdOpenMousepointMenu) { | |
382 | + if (vm.prepareWidgetContextMenu) { | |
383 | + vm.widgetContextMenuItems = vm.prepareWidgetContextMenu({widget: widget}); | |
384 | + if (vm.widgetContextMenuItems && vm.widgetContextMenuItems.length > 0) { | |
385 | + vm.widgetContextMenuEvent = $event; | |
386 | + $mdOpenMousepointMenu($event); | |
387 | + } | |
388 | + } | |
389 | + } | |
390 | + | |
391 | + function getEventGridPosition(event) { | |
392 | + var pos = { | |
393 | + row: 0, | |
394 | + column: 0 | |
395 | + } | |
396 | + var offset = gridsterParent.offset(); | |
397 | + var x = event.pageX - offset.left + gridsterParent.scrollLeft(); | |
398 | + var y = event.pageY - offset.top + gridsterParent.scrollTop(); | |
399 | + if (gridster) { | |
400 | + pos.row = gridster.pixelsToRows(y); | |
401 | + pos.column = gridster.pixelsToColumns(x); | |
402 | + } | |
403 | + return pos; | |
404 | + } | |
405 | + | |
350 | 406 | function editWidget ($event, widget) { |
351 | 407 | resetWidgetClick(); |
352 | 408 | if ($event) { |
... | ... | @@ -367,10 +423,10 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast |
367 | 423 | } |
368 | 424 | } |
369 | 425 | |
370 | - function highlightWidget(widgetIndex, delay) { | |
426 | + function highlightWidget(widget, delay) { | |
371 | 427 | highlightedMode = true; |
372 | - highlightedIndex = widgetIndex; | |
373 | - var item = $('.gridster-item', gridster.$element)[widgetIndex]; | |
428 | + highlightedWidget = widget; | |
429 | + var item = $('.gridster-item', gridster.$element)[vm.widgets.indexOf(widget)]; | |
374 | 430 | if (item) { |
375 | 431 | var height = $(item).outerHeight(true); |
376 | 432 | var rectHeight = gridsterParent.height(); |
... | ... | @@ -385,17 +441,39 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast |
385 | 441 | } |
386 | 442 | } |
387 | 443 | |
444 | + function selectWidget(widget, delay) { | |
445 | + selectedWidget = widget; | |
446 | + var item = $('.gridster-item', gridster.$element)[vm.widgets.indexOf(widget)]; | |
447 | + if (item) { | |
448 | + var height = $(item).outerHeight(true); | |
449 | + var rectHeight = gridsterParent.height(); | |
450 | + var offset = (rectHeight - height) / 2; | |
451 | + var scrollTop = item.offsetTop; | |
452 | + if (offset > 0) { | |
453 | + scrollTop -= offset; | |
454 | + } | |
455 | + gridsterParent.animate({ | |
456 | + scrollTop: scrollTop | |
457 | + }, delay); | |
458 | + } | |
459 | + } | |
460 | + | |
461 | + function getSelectedWidget() { | |
462 | + return selectedWidget; | |
463 | + } | |
464 | + | |
388 | 465 | function resetHighlight() { |
389 | 466 | highlightedMode = false; |
390 | - highlightedIndex = -1; | |
467 | + highlightedWidget = null; | |
468 | + selectedWidget = null; | |
391 | 469 | } |
392 | 470 | |
393 | 471 | function isHighlighted(widget) { |
394 | - return highlightedMode && vm.widgets.indexOf(widget) === highlightedIndex; | |
472 | + return (highlightedMode && highlightedWidget === widget) || (selectedWidget === widget); | |
395 | 473 | } |
396 | 474 | |
397 | 475 | function isNotHighlighted(widget) { |
398 | - return highlightedMode && vm.widgets.indexOf(widget) != highlightedIndex; | |
476 | + return highlightedMode && highlightedWidget != widget; | |
399 | 477 | } |
400 | 478 | |
401 | 479 | function widgetColor(widget) { | ... | ... |
... | ... | @@ -19,64 +19,90 @@ |
19 | 19 | ng-show="(vm.loading() || vm.dashboardLoading) && !vm.isEdit"> |
20 | 20 | <md-progress-circular md-mode="indeterminate" class="md-warn" md-diameter="100"></md-progress-circular> |
21 | 21 | </md-content> |
22 | -<md-content id="gridster-parent" class="tb-dashboard-content" flex layout-wrap> | |
23 | - <div ng-style="vm.dashboardStyle" id="gridster-background" style="height: auto; min-height: 100%;"> | |
24 | - <div id="gridster-child" gridster="vm.gridsterOpts"> | |
25 | - <ul> | |
26 | - <!-- ng-click="widgetClicked($event, widget)" --> | |
27 | - <li gridster-item="widget" ng-repeat="widget in vm.widgets"> | |
28 | - <div tb-expand-fullscreen expand-button-id="expand-button" on-fullscreen-changed="vm.onWidgetFullscreenChanged(expanded, widget)" layout="column" class="tb-widget md-whiteframe-4dp" | |
29 | - ng-class="{'tb-highlighted': vm.isHighlighted(widget), 'tb-not-highlighted': vm.isNotHighlighted(widget)}" | |
30 | - tb-mousedown="vm.widgetMouseDown($event, widget)" | |
31 | - tb-mousemove="vm.widgetMouseMove($event, widget)" | |
32 | - tb-mouseup="vm.widgetMouseUp($event, widget)" | |
33 | - style=" | |
34 | - cursor: pointer; | |
35 | - color: {{vm.widgetColor(widget)}}; | |
36 | - background-color: {{vm.widgetBackgroundColor(widget)}}; | |
37 | - padding: {{vm.widgetPadding(widget)}} | |
38 | - "> | |
39 | - <div class="tb-widget-title" layout="column" ng-show="vm.showWidgetTitle(widget) || vm.hasTimewindow(widget)"> | |
40 | - <span ng-show="vm.showWidgetTitle(widget)" class="md-subhead">{{widget.config.title}}</span> | |
41 | - <tb-timewindow ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow> | |
42 | - </div> | |
43 | - <div class="tb-widget-actions" layout="row" layout-align="start center"> | |
44 | - <md-button id="expand-button" | |
45 | - aria-label="{{ 'fullscreen.fullscreen' | translate }}" | |
46 | - class="md-icon-button md-primary"></md-button> | |
47 | - <md-button ng-show="vm.isEditActionEnabled && !vm.isWidgetExpanded" | |
48 | - ng-disabled="vm.loading()" | |
49 | - class="md-icon-button md-primary" | |
50 | - ng-click="vm.editWidget($event, widget)" | |
51 | - aria-label="{{ 'widget.edit' | translate }}"> | |
52 | - <md-tooltip md-direction="top"> | |
53 | - {{ 'widget.edit' | translate }} | |
54 | - </md-tooltip> | |
55 | - <md-icon class="material-icons"> | |
56 | - edit | |
57 | - </md-icon> | |
58 | - </md-button> | |
59 | - <md-button ng-show="vm.isRemoveActionEnabled && !vm.isWidgetExpanded" | |
60 | - ng-disabled="vm.loading()" | |
61 | - class="md-icon-button md-primary" | |
62 | - ng-click="vm.removeWidget($event, widget)" | |
63 | - aria-label="{{ 'widget.remove' | translate }}"> | |
64 | - <md-tooltip md-direction="top"> | |
65 | - {{ 'widget.remove' | translate }} | |
66 | - </md-tooltip> | |
67 | - <md-icon class="material-icons"> | |
68 | - close | |
69 | - </md-icon> | |
70 | - </md-button> | |
71 | - </div> | |
72 | - <div flex layout="column" class="tb-widget-content"> | |
73 | - <div flex tb-widget | |
74 | - locals="{ visibleRect: vm.visibleRect, widget: widget, deviceAliasList: vm.deviceAliasList, isPreview: vm.isEdit }"> | |
22 | +<md-menu md-position-mode="target target" tb-mousepoint-menu> | |
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%;"> | |
25 | + <div id="gridster-child" gridster="vm.gridsterOpts"> | |
26 | + <ul> | |
27 | + <!-- ng-click="widgetClicked($event, widget)" --> | |
28 | + <li gridster-item="widget" ng-repeat="widget in vm.widgets"> | |
29 | + <md-menu md-position-mode="target target" tb-mousepoint-menu> | |
30 | + <div tb-expand-fullscreen | |
31 | + expand-button-id="expand-button" on-fullscreen-changed="vm.onWidgetFullscreenChanged(expanded, widget)" layout="column" class="tb-widget md-whiteframe-4dp" | |
32 | + ng-class="{'tb-highlighted': vm.isHighlighted(widget), 'tb-not-highlighted': vm.isNotHighlighted(widget)}" | |
33 | + tb-mousedown="vm.widgetMouseDown($event, widget)" | |
34 | + tb-mousemove="vm.widgetMouseMove($event, widget)" | |
35 | + tb-mouseup="vm.widgetMouseUp($event, widget)" | |
36 | + ng-click="" | |
37 | + tb-contextmenu="vm.openWidgetContextMenu($event, widget, $mdOpenMousepointMenu)" | |
38 | + style=" | |
39 | + cursor: pointer; | |
40 | + color: {{vm.widgetColor(widget)}}; | |
41 | + background-color: {{vm.widgetBackgroundColor(widget)}}; | |
42 | + padding: {{vm.widgetPadding(widget)}} | |
43 | + "> | |
44 | + <div class="tb-widget-title" layout="column" ng-show="vm.showWidgetTitle(widget) || vm.hasTimewindow(widget)"> | |
45 | + <span ng-show="vm.showWidgetTitle(widget)" class="md-subhead">{{widget.config.title}}</span> | |
46 | + <tb-timewindow ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow> | |
47 | + </div> | |
48 | + <div class="tb-widget-actions" layout="row" layout-align="start center"> | |
49 | + <md-button id="expand-button" | |
50 | + ng-show="!vm.isEdit" | |
51 | + aria-label="{{ 'fullscreen.fullscreen' | translate }}" | |
52 | + class="md-icon-button md-primary"></md-button> | |
53 | + <md-button ng-show="vm.isEditActionEnabled && !vm.isWidgetExpanded" | |
54 | + ng-disabled="vm.loading()" | |
55 | + class="md-icon-button md-primary" | |
56 | + ng-click="vm.editWidget($event, widget)" | |
57 | + aria-label="{{ 'widget.edit' | translate }}"> | |
58 | + <md-tooltip md-direction="top"> | |
59 | + {{ 'widget.edit' | translate }} | |
60 | + </md-tooltip> | |
61 | + <md-icon class="material-icons"> | |
62 | + edit | |
63 | + </md-icon> | |
64 | + </md-button> | |
65 | + <md-button ng-show="vm.isRemoveActionEnabled && !vm.isWidgetExpanded" | |
66 | + ng-disabled="vm.loading()" | |
67 | + class="md-icon-button md-primary" | |
68 | + ng-click="vm.removeWidget($event, widget)" | |
69 | + aria-label="{{ 'widget.remove' | translate }}"> | |
70 | + <md-tooltip md-direction="top"> | |
71 | + {{ 'widget.remove' | translate }} | |
72 | + </md-tooltip> | |
73 | + <md-icon class="material-icons"> | |
74 | + close | |
75 | + </md-icon> | |
76 | + </md-button> | |
77 | + </div> | |
78 | + <div flex layout="column" class="tb-widget-content"> | |
79 | + <div flex tb-widget | |
80 | + locals="{ visibleRect: vm.visibleRect, widget: widget, deviceAliasList: vm.deviceAliasList, isPreview: vm.isEdit }"> | |
81 | + </div> | |
82 | + </div> | |
75 | 83 | </div> |
76 | - </div> | |
77 | - </div> | |
78 | - </li> | |
79 | - </ul> | |
84 | + <md-menu-content id="menu" width="4" ng-mouseleave="$mdCloseMousepointMenu()"> | |
85 | + <md-menu-item ng-repeat ="item in vm.widgetContextMenuItems"> | |
86 | + <md-button ng-disabled="!item.enabled" ng-click="item.action(vm.widgetContextMenuEvent, widget)"> | |
87 | + <md-icon ng-if="item.icon" md-menu-align-target aria-label="{{ item.value | translate }}" class="material-icons">{{item.icon}}</md-icon> | |
88 | + <span translate>{{item.value}}</span> | |
89 | + <span ng-if="item.shortcut" class="tb-alt-text"> {{ item.shortcut | keyboardShortcut }}</span> | |
90 | + </md-button> | |
91 | + </md-menu-item> | |
92 | + </md-menu-content> | |
93 | + </md-menu> | |
94 | + </li> | |
95 | + </ul> | |
96 | + </div> | |
80 | 97 | </div> |
81 | - </div> | |
82 | -</md-content> | |
\ No newline at end of file | ||
98 | + </md-content> | |
99 | + <md-menu-content id="menu" width="4" ng-mouseleave="$mdCloseMousepointMenu()"> | |
100 | + <md-menu-item ng-repeat ="item in vm.contextMenuItems"> | |
101 | + <md-button ng-disabled="!item.enabled" ng-click="item.action(vm.contextMenuEvent)"> | |
102 | + <md-icon ng-if="item.icon" md-menu-align-target aria-label="{{ item.value | translate }}" class="material-icons">{{item.icon}}</md-icon> | |
103 | + <span translate>{{item.value}}</span> | |
104 | + <span ng-if="item.shortcut" class="tb-alt-text"> {{ item.shortcut | keyboardShortcut }}</span> | |
105 | + </md-button> | |
106 | + </md-menu-item> | |
107 | + </md-menu-content> | |
108 | +</md-menu> | |
\ No newline at end of file | ... | ... |
1 | +/* | |
2 | + * Copyright © 2016 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +export default angular.module('thingsboard.filters.keyboardShortcut', []) | |
17 | + .filter('keyboardShortcut', KeyboardShortcut) | |
18 | + .name; | |
19 | + | |
20 | +/*@ngInject*/ | |
21 | +function KeyboardShortcut($window) { | |
22 | + return function(str) { | |
23 | + if (!str) return; | |
24 | + var keys = str.split('-'); | |
25 | + var isOSX = /Mac OS X/.test($window.navigator.userAgent); | |
26 | + | |
27 | + var seperator = (!isOSX || keys.length > 2) ? '+' : ''; | |
28 | + | |
29 | + var abbreviations = { | |
30 | + M: isOSX ? '⌘' : 'Ctrl', | |
31 | + A: isOSX ? 'Option' : 'Alt', | |
32 | + S: 'Shift' | |
33 | + }; | |
34 | + | |
35 | + return keys.map(function(key, index) { | |
36 | + var last = index == keys.length - 1; | |
37 | + return last ? key : abbreviations[key]; | |
38 | + }).join(seperator); | |
39 | + }; | |
40 | +} | ... | ... |
1 | +/* | |
2 | + * Copyright © 2016 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +export default angular.module('thingsboard.directives.mousepointMenu', []) | |
18 | + .directive('tbMousepointMenu', MousepointMenu) | |
19 | + .name; | |
20 | + | |
21 | +/*@ngInject*/ | |
22 | +function MousepointMenu() { | |
23 | + | |
24 | + var linker = function ($scope, $element, $attrs, RightClickContextMenu) { | |
25 | + | |
26 | + $scope.$mdOpenMousepointMenu = function($event){ | |
27 | + RightClickContextMenu.offsets = function(){ | |
28 | + var offset = $element.offset(); | |
29 | + var x = $event.pageX - offset.left; | |
30 | + var y = $event.pageY - offset.top; | |
31 | + | |
32 | + var offsets = { | |
33 | + left: x, | |
34 | + top: y | |
35 | + } | |
36 | + return offsets; | |
37 | + } | |
38 | + RightClickContextMenu.open($event); | |
39 | + }; | |
40 | + | |
41 | + $scope.$mdCloseMousepointMenu = function() { | |
42 | + RightClickContextMenu.close(); | |
43 | + } | |
44 | + } | |
45 | + | |
46 | + return { | |
47 | + restrict: "A", | |
48 | + link: linker, | |
49 | + require: 'mdMenu' | |
50 | + }; | |
51 | +} | ... | ... |
... | ... | @@ -20,7 +20,7 @@ const PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i; |
20 | 20 | var tbEventDirectives = {}; |
21 | 21 | |
22 | 22 | angular.forEach( |
23 | - 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), | |
23 | + 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave contextmenu keydown keyup keypress submit focus blur copy cut paste'.split(' '), | |
24 | 24 | function(eventName) { |
25 | 25 | var directiveName = directiveNormalize('tb-' + eventName); |
26 | 26 | tbEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse) { | ... | ... |
... | ... | @@ -55,12 +55,26 @@ function WidgetsBundleSelect($compile, $templateCache, widgetService, types) { |
55 | 55 | if (widgetsBundles.length > 0) { |
56 | 56 | scope.widgetsBundle = widgetsBundles[0]; |
57 | 57 | } |
58 | + } else if (angular.isDefined(scope.selectBundleAlias)) { | |
59 | + selectWidgetsBundleByAlias(scope.selectBundleAlias); | |
58 | 60 | } |
59 | 61 | }, |
60 | 62 | function fail() { |
61 | 63 | } |
62 | 64 | ); |
63 | 65 | |
66 | + function selectWidgetsBundleByAlias(alias) { | |
67 | + if (scope.widgetsBundles && alias) { | |
68 | + for (var w in scope.widgetsBundles) { | |
69 | + var widgetsBundle = scope.widgetsBundles[w]; | |
70 | + if (widgetsBundle.alias === alias) { | |
71 | + scope.widgetsBundle = widgetsBundle; | |
72 | + break; | |
73 | + } | |
74 | + } | |
75 | + } | |
76 | + } | |
77 | + | |
64 | 78 | scope.isSystem = function(item) { |
65 | 79 | return item && item.tenantId.id === types.id.nullUid; |
66 | 80 | } |
... | ... | @@ -79,6 +93,12 @@ function WidgetsBundleSelect($compile, $templateCache, widgetService, types) { |
79 | 93 | scope.updateView(); |
80 | 94 | }); |
81 | 95 | |
96 | + scope.$watch('selectBundleAlias', function (newVal, prevVal) { | |
97 | + if (newVal !== prevVal) { | |
98 | + selectWidgetsBundleByAlias(scope.selectBundleAlias); | |
99 | + } | |
100 | + }); | |
101 | + | |
82 | 102 | $compile(element.contents())(scope); |
83 | 103 | } |
84 | 104 | |
... | ... | @@ -90,7 +110,8 @@ function WidgetsBundleSelect($compile, $templateCache, widgetService, types) { |
90 | 110 | bundlesScope: '@', |
91 | 111 | theForm: '=?', |
92 | 112 | tbRequired: '=?', |
93 | - selectFirstBundle: '=' | |
113 | + selectFirstBundle: '=', | |
114 | + selectBundleAlias: '=?' | |
94 | 115 | } |
95 | 116 | }; |
96 | 117 | } |
\ No newline at end of file | ... | ... |
... | ... | @@ -28,6 +28,10 @@ export default function DashboardSettingsController($scope, $mdDialog, gridSetti |
28 | 28 | |
29 | 29 | vm.gridSettings = gridSettings || {}; |
30 | 30 | |
31 | + if (angular.isUndefined(vm.gridSettings.showTitle)) { | |
32 | + vm.gridSettings.showTitle = true; | |
33 | + } | |
34 | + | |
31 | 35 | vm.gridSettings.backgroundColor = vm.gridSettings.backgroundColor || 'rgba(0,0,0,0)'; |
32 | 36 | vm.gridSettings.columns = vm.gridSettings.columns || 24; |
33 | 37 | vm.gridSettings.margins = vm.gridSettings.margins || [10, 10]; | ... | ... |
... | ... | @@ -31,6 +31,11 @@ |
31 | 31 | <md-dialog-content> |
32 | 32 | <div class="md-dialog-content"> |
33 | 33 | <fieldset ng-disabled="loading"> |
34 | + <div layout="row" layout-padding> | |
35 | + <md-checkbox flex aria-label="{{ 'dashboard.display-title' | translate }}" | |
36 | + ng-model="vm.gridSettings.showTitle">{{ 'dashboard.display-title' | translate }} | |
37 | + </md-checkbox> | |
38 | + </div> | |
34 | 39 | <md-input-container class="md-block"> |
35 | 40 | <label translate>dashboard.columns-count</label> |
36 | 41 | <input required type="number" step="any" name="columns" ng-model="vm.gridSettings.columns" min="10" | ... | ... |
... | ... | @@ -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, $window, $rootScope, | |
26 | + dashboardService, itembuffer, hotkeys, $window, $rootScope, | |
27 | 27 | $scope, $state, $stateParams, $mdDialog, $timeout, $document, $q, $translate, $filter) { |
28 | 28 | |
29 | 29 | var user = userService.getCurrentUser(); |
... | ... | @@ -48,7 +48,10 @@ export default function DashboardController(types, widgetService, userService, |
48 | 48 | vm.addWidgetFromType = addWidgetFromType; |
49 | 49 | vm.dashboardInited = dashboardInited; |
50 | 50 | vm.dashboardInitFailed = dashboardInitFailed; |
51 | + vm.widgetMouseDown = widgetMouseDown; | |
51 | 52 | vm.widgetClicked = widgetClicked; |
53 | + vm.prepareDashboardContextMenu = prepareDashboardContextMenu; | |
54 | + vm.prepareWidgetContextMenu = prepareWidgetContextMenu; | |
52 | 55 | vm.editWidget = editWidget; |
53 | 56 | vm.isTenantAdmin = isTenantAdmin; |
54 | 57 | vm.loadDashboard = loadDashboard; |
... | ... | @@ -63,6 +66,7 @@ export default function DashboardController(types, widgetService, userService, |
63 | 66 | vm.toggleDashboardEditMode = toggleDashboardEditMode; |
64 | 67 | vm.onRevertWidgetEdit = onRevertWidgetEdit; |
65 | 68 | vm.helpLinkIdForWidgetType = helpLinkIdForWidgetType; |
69 | + vm.displayTitle = displayTitle; | |
66 | 70 | |
67 | 71 | vm.widgetsBundle; |
68 | 72 | |
... | ... | @@ -194,6 +198,7 @@ export default function DashboardController(types, widgetService, userService, |
194 | 198 | |
195 | 199 | function dashboardInited(dashboard) { |
196 | 200 | vm.dashboardContainer = dashboard; |
201 | + initHotKeys(); | |
197 | 202 | } |
198 | 203 | |
199 | 204 | function isTenantAdmin() { |
... | ... | @@ -289,18 +294,188 @@ export default function DashboardController(types, widgetService, userService, |
289 | 294 | var delayOffset = transition ? 350 : 0; |
290 | 295 | var delay = transition ? 400 : 300; |
291 | 296 | $timeout(function () { |
292 | - vm.dashboardContainer.highlightWidget(vm.editingWidgetIndex, delay); | |
297 | + vm.dashboardContainer.highlightWidget(widget, delay); | |
293 | 298 | }, delayOffset, false); |
294 | 299 | } |
295 | 300 | } |
296 | 301 | } |
297 | 302 | |
303 | + function widgetMouseDown($event, widget) { | |
304 | + if (vm.isEdit && !vm.isEditingWidget) { | |
305 | + vm.dashboardContainer.selectWidget(widget, 0); | |
306 | + } | |
307 | + } | |
308 | + | |
298 | 309 | function widgetClicked($event, widget) { |
299 | 310 | if (vm.isEditingWidget) { |
300 | 311 | editWidget($event, widget); |
301 | 312 | } |
302 | 313 | } |
303 | 314 | |
315 | + function initHotKeys() { | |
316 | + $translate(['action.copy', 'action.paste', 'action.delete']).then(function (translations) { | |
317 | + hotkeys.bindTo($scope) | |
318 | + .add({ | |
319 | + combo: 'ctrl+c', | |
320 | + description: translations['action.copy'], | |
321 | + allowIn: ['INPUT', 'SELECT', 'TEXTAREA'], | |
322 | + callback: function (event) { | |
323 | + if (vm.isEdit && !vm.isEditingWidget && !vm.widgetEditMode) { | |
324 | + var widget = vm.dashboardContainer.getSelectedWidget(); | |
325 | + if (widget) { | |
326 | + event.preventDefault(); | |
327 | + copyWidget(event, widget); | |
328 | + } | |
329 | + } | |
330 | + } | |
331 | + }) | |
332 | + .add({ | |
333 | + combo: 'ctrl+v', | |
334 | + description: translations['action.paste'], | |
335 | + allowIn: ['INPUT', 'SELECT', 'TEXTAREA'], | |
336 | + callback: function (event) { | |
337 | + if (vm.isEdit && !vm.isEditingWidget && !vm.widgetEditMode) { | |
338 | + if (itembuffer.hasWidget()) { | |
339 | + event.preventDefault(); | |
340 | + pasteWidget(event); | |
341 | + } | |
342 | + } | |
343 | + } | |
344 | + }) | |
345 | + .add({ | |
346 | + combo: 'ctrl+x', | |
347 | + description: translations['action.delete'], | |
348 | + allowIn: ['INPUT', 'SELECT', 'TEXTAREA'], | |
349 | + callback: function (event) { | |
350 | + if (vm.isEdit && !vm.isEditingWidget && !vm.widgetEditMode) { | |
351 | + var widget = vm.dashboardContainer.getSelectedWidget(); | |
352 | + if (widget) { | |
353 | + event.preventDefault(); | |
354 | + removeWidget(event, widget); | |
355 | + } | |
356 | + } | |
357 | + } | |
358 | + }); | |
359 | + }); | |
360 | + } | |
361 | + | |
362 | + function prepareDashboardContextMenu() { | |
363 | + var dashboardContextActions = []; | |
364 | + if (vm.isEdit && !vm.isEditingWidget && !vm.widgetEditMode) { | |
365 | + dashboardContextActions.push( | |
366 | + { | |
367 | + action: openDashboardSettings, | |
368 | + enabled: true, | |
369 | + value: "dashboard.settings", | |
370 | + icon: "settings" | |
371 | + } | |
372 | + ); | |
373 | + dashboardContextActions.push( | |
374 | + { | |
375 | + action: openDeviceAliases, | |
376 | + enabled: true, | |
377 | + value: "device.aliases", | |
378 | + icon: "devices_other" | |
379 | + } | |
380 | + ); | |
381 | + dashboardContextActions.push( | |
382 | + { | |
383 | + action: pasteWidget, | |
384 | + enabled: itembuffer.hasWidget(), | |
385 | + value: "action.paste", | |
386 | + icon: "content_paste", | |
387 | + shortcut: "M-V" | |
388 | + } | |
389 | + ); | |
390 | + } | |
391 | + return dashboardContextActions; | |
392 | + } | |
393 | + | |
394 | + function pasteWidget($event) { | |
395 | + var pos = vm.dashboardContainer.getEventGridPosition($event); | |
396 | + itembuffer.pasteWidget(vm.dashboard, pos); | |
397 | + } | |
398 | + | |
399 | + function prepareWidgetContextMenu() { | |
400 | + var widgetContextActions = []; | |
401 | + if (vm.isEdit && !vm.isEditingWidget) { | |
402 | + widgetContextActions.push( | |
403 | + { | |
404 | + action: editWidget, | |
405 | + enabled: true, | |
406 | + value: "action.edit", | |
407 | + icon: "edit" | |
408 | + } | |
409 | + ); | |
410 | + if (!vm.widgetEditMode) { | |
411 | + widgetContextActions.push( | |
412 | + { | |
413 | + action: copyWidget, | |
414 | + enabled: true, | |
415 | + value: "action.copy", | |
416 | + icon: "content_copy", | |
417 | + shortcut: "M-C" | |
418 | + } | |
419 | + ); | |
420 | + widgetContextActions.push( | |
421 | + { | |
422 | + action: removeWidget, | |
423 | + enabled: true, | |
424 | + value: "action.delete", | |
425 | + icon: "clear", | |
426 | + shortcut: "M-X" | |
427 | + } | |
428 | + ); | |
429 | + } | |
430 | + } | |
431 | + return widgetContextActions; | |
432 | + } | |
433 | + | |
434 | + function copyWidget($event, widget) { | |
435 | + var aliasesInfo = { | |
436 | + datasourceAliases: {}, | |
437 | + targetDeviceAliases: {} | |
438 | + }; | |
439 | + var originalColumns = 24; | |
440 | + if (vm.dashboard.configuration.gridSettings && | |
441 | + vm.dashboard.configuration.gridSettings.columns) { | |
442 | + originalColumns = vm.dashboard.configuration.gridSettings.columns; | |
443 | + } | |
444 | + if (widget.config && vm.dashboard.configuration | |
445 | + && vm.dashboard.configuration.deviceAliases) { | |
446 | + var deviceAlias; | |
447 | + if (widget.config.datasources) { | |
448 | + for (var i=0;i<widget.config.datasources.length;i++) { | |
449 | + var datasource = widget.config.datasources[i]; | |
450 | + if (datasource.type === types.datasourceType.device && datasource.deviceAliasId) { | |
451 | + deviceAlias = vm.dashboard.configuration.deviceAliases[datasource.deviceAliasId]; | |
452 | + if (deviceAlias) { | |
453 | + aliasesInfo.datasourceAliases[i] = { | |
454 | + aliasName: deviceAlias.alias, | |
455 | + deviceId: deviceAlias.deviceId | |
456 | + } | |
457 | + } | |
458 | + } | |
459 | + } | |
460 | + } | |
461 | + if (widget.config.targetDeviceAliasIds) { | |
462 | + for (i=0;i<widget.config.targetDeviceAliasIds.length;i++) { | |
463 | + var targetDeviceAliasId = widget.config.targetDeviceAliasIds[i]; | |
464 | + if (targetDeviceAliasId) { | |
465 | + deviceAlias = vm.dashboard.configuration.deviceAliases[targetDeviceAliasId]; | |
466 | + if (deviceAlias) { | |
467 | + aliasesInfo.targetDeviceAliases[i] = { | |
468 | + aliasName: deviceAlias.alias, | |
469 | + deviceId: deviceAlias.deviceId | |
470 | + } | |
471 | + } | |
472 | + } | |
473 | + } | |
474 | + } | |
475 | + } | |
476 | + itembuffer.copyWidget(widget, aliasesInfo, originalColumns); | |
477 | + } | |
478 | + | |
304 | 479 | function helpLinkIdForWidgetType() { |
305 | 480 | var link = 'widgetsConfig'; |
306 | 481 | if (vm.editingWidget && vm.editingWidget.type) { |
... | ... | @@ -322,6 +497,15 @@ export default function DashboardController(types, widgetService, userService, |
322 | 497 | return link; |
323 | 498 | } |
324 | 499 | |
500 | + function displayTitle() { | |
501 | + if (vm.dashboard && vm.dashboard.configuration.gridSettings && | |
502 | + angular.isDefined(vm.dashboard.configuration.gridSettings.showTitle)) { | |
503 | + return vm.dashboard.configuration.gridSettings.showTitle; | |
504 | + } else { | |
505 | + return true; | |
506 | + } | |
507 | + } | |
508 | + | |
325 | 509 | function onRevertWidgetEdit(widgetForm) { |
326 | 510 | if (widgetForm.$dirty) { |
327 | 511 | widgetForm.$setPristine(); |
... | ... | @@ -331,7 +515,9 @@ export default function DashboardController(types, widgetService, userService, |
331 | 515 | |
332 | 516 | function saveWidget(widgetForm) { |
333 | 517 | widgetForm.$setPristine(); |
334 | - vm.widgets[vm.editingWidgetIndex] = angular.copy(vm.editingWidget); | |
518 | + var widget = angular.copy(vm.editingWidget); | |
519 | + vm.widgets[vm.editingWidgetIndex] = widget; | |
520 | + vm.dashboardContainer.highlightWidget(widget, 0); | |
335 | 521 | } |
336 | 522 | |
337 | 523 | function onEditWidgetClosed() { |
... | ... | @@ -421,8 +607,8 @@ export default function DashboardController(types, widgetService, userService, |
421 | 607 | }); |
422 | 608 | } |
423 | 609 | |
424 | - function toggleDashboardEditMode() { | |
425 | - vm.isEdit = !vm.isEdit; | |
610 | + function setEditMode(isEdit, revert) { | |
611 | + vm.isEdit = isEdit; | |
426 | 612 | if (vm.isEdit) { |
427 | 613 | if (vm.widgetEditMode) { |
428 | 614 | vm.prevWidgets = angular.copy(vm.widgets); |
... | ... | @@ -433,14 +619,23 @@ export default function DashboardController(types, widgetService, userService, |
433 | 619 | if (vm.widgetEditMode) { |
434 | 620 | vm.widgets = vm.prevWidgets; |
435 | 621 | } else { |
436 | - vm.dashboard = vm.prevDashboard; | |
437 | - vm.widgets = vm.dashboard.configuration.widgets; | |
622 | + if (vm.dashboardContainer) { | |
623 | + vm.dashboardContainer.resetHighlight(); | |
624 | + } | |
625 | + if (revert) { | |
626 | + vm.dashboard = vm.prevDashboard; | |
627 | + vm.widgets = vm.dashboard.configuration.widgets; | |
628 | + } | |
438 | 629 | } |
439 | 630 | } |
440 | 631 | } |
441 | 632 | |
633 | + function toggleDashboardEditMode() { | |
634 | + setEditMode(!vm.isEdit, true); | |
635 | + } | |
636 | + | |
442 | 637 | function saveDashboard() { |
443 | - vm.isEdit = false; | |
638 | + setEditMode(false, false); | |
444 | 639 | notifyDashboardUpdated(); |
445 | 640 | } |
446 | 641 | ... | ... |
... | ... | @@ -51,7 +51,7 @@ |
51 | 51 | </md-button> |
52 | 52 | </section> |
53 | 53 | <section ng-if="!vm.widgetEditMode" class="tb-dashboard-title" layout="row" layout-align="center center"> |
54 | - <h3 ng-show="!vm.isEdit">{{ vm.dashboard.title }}</h3> | |
54 | + <h3 ng-show="!vm.isEdit && vm.displayTitle()">{{ vm.dashboard.title }}</h3> | |
55 | 55 | <md-input-container ng-show="vm.isEdit" class="md-block" style="height: 30px;"> |
56 | 56 | <label translate>dashboard.title</label> |
57 | 57 | <input class="tb-dashboard-title" required name="title" ng-model="vm.dashboard.title"> |
... | ... | @@ -64,7 +64,7 @@ |
64 | 64 | </md-button> |
65 | 65 | </section> |
66 | 66 | <div class="tb-absolute-fill" |
67 | - ng-class="{ 'tb-padded' : !vm.widgetEditMode, 'tb-shrinked' : vm.isEditingWidget }"> | |
67 | + ng-class="{ 'tb-padded' : !vm.widgetEditMode && (vm.isEdit || vm.displayTitle()), 'tb-shrinked' : vm.isEditingWidget }"> | |
68 | 68 | <tb-dashboard |
69 | 69 | dashboard-style="{'background-color': vm.dashboard.configuration.gridSettings.backgroundColor, |
70 | 70 | 'background-image': 'url('+vm.dashboard.configuration.gridSettings.backgroundImageUrl+')', |
... | ... | @@ -82,7 +82,11 @@ |
82 | 82 | is-edit-action-enabled="vm.isEdit || vm.widgetEditMode" |
83 | 83 | is-remove-action-enabled="vm.isEdit && !vm.widgetEditMode" |
84 | 84 | on-edit-widget="vm.editWidget(event, widget)" |
85 | + on-widget-mouse-down="vm.widgetMouseDown(event, widget)" | |
85 | 86 | on-widget-clicked="vm.widgetClicked(event, widget)" |
87 | + on-widget-context-menu="vm.widgetContextMenu(event, widget)" | |
88 | + prepare-dashboard-context-menu="vm.prepareDashboardContextMenu()" | |
89 | + prepare-widget-context-menu="vm.prepareWidgetContextMenu(widget)" | |
86 | 90 | on-remove-widget="vm.removeWidget(event, widget)" |
87 | 91 | load-widgets="vm.loadDashboard()" |
88 | 92 | on-init="vm.dashboardInited(dashboard)" | ... | ... |
... | ... | @@ -29,6 +29,7 @@ import thingsboardDashboard from '../components/dashboard.directive'; |
29 | 29 | import thingsboardExpandFullscreen from '../components/expand-fullscreen.directive'; |
30 | 30 | import thingsboardWidgetsBundleSelect from '../components/widgets-bundle-select.directive'; |
31 | 31 | import thingsboardTypes from '../common/types.constant'; |
32 | +import thingsboardItemBuffer from '../services/item-buffer.service'; | |
32 | 33 | |
33 | 34 | import DashboardRoutes from './dashboard.routes'; |
34 | 35 | import DashboardsController from './dashboards.controller'; |
... | ... | @@ -45,6 +46,7 @@ export default angular.module('thingsboard.dashboard', [ |
45 | 46 | uiRouter, |
46 | 47 | gridster.name, |
47 | 48 | thingsboardTypes, |
49 | + thingsboardItemBuffer, | |
48 | 50 | thingsboardGrid, |
49 | 51 | thingsboardApiWidget, |
50 | 52 | thingsboardApiUser, | ... | ... |
... | ... | @@ -14,7 +14,7 @@ |
14 | 14 | * limitations under the License. |
15 | 15 | */ |
16 | 16 | /*@ngInject*/ |
17 | -export default function AddWidgetToDashboardDialogController($scope, $mdDialog, $state, dashboardService, deviceId, deviceName, widget) { | |
17 | +export default function AddWidgetToDashboardDialogController($scope, $mdDialog, $state, itembuffer, dashboardService, deviceId, deviceName, widget) { | |
18 | 18 | |
19 | 19 | var vm = this; |
20 | 20 | |
... | ... | @@ -34,62 +34,20 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog, |
34 | 34 | function add() { |
35 | 35 | $scope.theForm.$setPristine(); |
36 | 36 | var theDashboard; |
37 | - var deviceAliases; | |
38 | - widget.col = 0; | |
39 | - widget.sizeX /= 2; | |
40 | - widget.sizeY /= 2; | |
41 | 37 | if (vm.addToDashboardType === 0) { |
42 | 38 | theDashboard = vm.dashboard; |
43 | - if (!theDashboard.configuration) { | |
44 | - theDashboard.configuration = {}; | |
45 | - } | |
46 | - deviceAliases = theDashboard.configuration.deviceAliases; | |
47 | - if (!deviceAliases) { | |
48 | - deviceAliases = {}; | |
49 | - theDashboard.configuration.deviceAliases = deviceAliases; | |
50 | - } | |
51 | - var newAliasId; | |
52 | - for (var aliasId in deviceAliases) { | |
53 | - if (deviceAliases[aliasId].deviceId === deviceId) { | |
54 | - newAliasId = aliasId; | |
55 | - break; | |
56 | - } | |
57 | - } | |
58 | - if (!newAliasId) { | |
59 | - var newAliasName = createDeviceAliasName(deviceAliases, deviceName); | |
60 | - newAliasId = 0; | |
61 | - for (aliasId in deviceAliases) { | |
62 | - newAliasId = Math.max(newAliasId, aliasId); | |
63 | - } | |
64 | - newAliasId++; | |
65 | - deviceAliases[newAliasId] = {alias: newAliasName, deviceId: deviceId}; | |
66 | - } | |
67 | - widget.config.datasources[0].deviceAliasId = newAliasId; | |
68 | - | |
69 | - if (!theDashboard.configuration.widgets) { | |
70 | - theDashboard.configuration.widgets = []; | |
71 | - } | |
72 | - | |
73 | - var row = 0; | |
74 | - for (var w in theDashboard.configuration.widgets) { | |
75 | - var existingWidget = theDashboard.configuration.widgets[w]; | |
76 | - var wRow = existingWidget.row ? existingWidget.row : 0; | |
77 | - var wSizeY = existingWidget.sizeY ? existingWidget.sizeY : 1; | |
78 | - var bottom = wRow + wSizeY; | |
79 | - row = Math.max(row, bottom); | |
80 | - } | |
81 | - widget.row = row; | |
82 | - theDashboard.configuration.widgets.push(widget); | |
83 | 39 | } else { |
84 | 40 | theDashboard = vm.newDashboard; |
85 | - deviceAliases = {}; | |
86 | - deviceAliases['1'] = {alias: deviceName, deviceId: deviceId}; | |
87 | - theDashboard.configuration = {}; | |
88 | - theDashboard.configuration.widgets = []; | |
89 | - widget.row = 0; | |
90 | - theDashboard.configuration.widgets.push(widget); | |
91 | - theDashboard.configuration.deviceAliases = deviceAliases; | |
92 | 41 | } |
42 | + var aliasesInfo = { | |
43 | + datasourceAliases: {}, | |
44 | + targetDeviceAliases: {} | |
45 | + }; | |
46 | + aliasesInfo.datasourceAliases[0] = { | |
47 | + aliasName: deviceName, | |
48 | + deviceId: deviceId | |
49 | + }; | |
50 | + theDashboard = itembuffer.addWidgetToDashboard(theDashboard, widget, aliasesInfo, 48, -1, -1); | |
93 | 51 | dashboardService.saveDashboard(theDashboard).then( |
94 | 52 | function success(dashboard) { |
95 | 53 | $mdDialog.hide(); |
... | ... | @@ -98,25 +56,6 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog, |
98 | 56 | } |
99 | 57 | } |
100 | 58 | ); |
101 | - | |
102 | - } | |
103 | - | |
104 | - function createDeviceAliasName(deviceAliases, alias) { | |
105 | - var c = 0; | |
106 | - var newAlias = angular.copy(alias); | |
107 | - var unique = false; | |
108 | - while (!unique) { | |
109 | - unique = true; | |
110 | - for (var devAliasId in deviceAliases) { | |
111 | - var devAlias = deviceAliases[devAliasId]; | |
112 | - if (newAlias === devAlias.alias) { | |
113 | - c++; | |
114 | - newAlias = alias + c; | |
115 | - unique = false; | |
116 | - } | |
117 | - } | |
118 | - } | |
119 | - return newAlias; | |
120 | 59 | } |
121 | 60 | |
122 | 61 | } | ... | ... |
... | ... | @@ -239,6 +239,8 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS |
239 | 239 | index: 0 |
240 | 240 | } |
241 | 241 | scope.widgetsBundle = null; |
242 | + scope.firstBundle = true; | |
243 | + scope.selectedWidgetsBundleAlias = types.systemBundleAlias.cards; | |
242 | 244 | |
243 | 245 | scope.deviceAliases = {}; |
244 | 246 | scope.deviceAliases['1'] = {alias: scope.deviceName, deviceId: scope.deviceId}; |
... | ... | @@ -326,13 +328,6 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS |
326 | 328 | } |
327 | 329 | } |
328 | 330 | }); |
329 | - | |
330 | - widgetService.getWidgetsBundleByAlias(types.systemBundleAlias.cards).then( | |
331 | - function success(widgetsBundle) { | |
332 | - scope.firstBundle = true; | |
333 | - scope.widgetsBundle = widgetsBundle; | |
334 | - } | |
335 | - ); | |
336 | 331 | } |
337 | 332 | |
338 | 333 | scope.exitWidgetMode = function() { |
... | ... | @@ -344,6 +339,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS |
344 | 339 | scope.widgetsIndexWatch(); |
345 | 340 | scope.widgetsIndexWatch = null; |
346 | 341 | } |
342 | + scope.selectedWidgetsBundleAlias = null; | |
347 | 343 | scope.mode = 'default'; |
348 | 344 | scope.getDeviceAttributes(true); |
349 | 345 | } | ... | ... |
... | ... | @@ -105,7 +105,8 @@ |
105 | 105 | <tb-widgets-bundle-select flex-offset="5" |
106 | 106 | flex |
107 | 107 | ng-model="widgetsBundle" |
108 | - select-first-bundle="false"> | |
108 | + select-first-bundle="false" | |
109 | + select-bundle-alias="selectedWidgetsBundleAlias"> | |
109 | 110 | </tb-widgets-bundle-select> |
110 | 111 | </div> |
111 | 112 | <md-button ng-show="widgetsList.length > 0" class="md-accent md-hue-2 md-raised" ng-click="addWidgetToDashboard($event)"> | ... | ... |
ui/src/app/services/item-buffer.service.js
0 → 100644
1 | +/* | |
2 | + * Copyright © 2016 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +import angularStorage from 'angular-storage'; | |
18 | + | |
19 | +export default angular.module('thingsboard.itembuffer', [angularStorage]) | |
20 | + .factory('itembuffer', ItemBuffer) | |
21 | + .factory('bufferStore', function(store) { | |
22 | + var newStore = store.getNamespacedStore('tbBufferStore', null, null, false); | |
23 | + return newStore; | |
24 | + }) | |
25 | + .name; | |
26 | + | |
27 | +/*@ngInject*/ | |
28 | +function ItemBuffer(bufferStore) { | |
29 | + | |
30 | + const WIDGET_ITEM = "widget_item"; | |
31 | + | |
32 | + var service = { | |
33 | + copyWidget: copyWidget, | |
34 | + hasWidget: hasWidget, | |
35 | + pasteWidget: pasteWidget, | |
36 | + addWidgetToDashboard: addWidgetToDashboard | |
37 | + } | |
38 | + | |
39 | + return service; | |
40 | + | |
41 | + /** | |
42 | + aliasesInfo { | |
43 | + datasourceAliases: { | |
44 | + datasourceIndex: { | |
45 | + aliasName: "...", | |
46 | + deviceId: "..." | |
47 | + } | |
48 | + } | |
49 | + targetDeviceAliases: { | |
50 | + targetDeviceAliasIndex: { | |
51 | + aliasName: "...", | |
52 | + deviceId: "..." | |
53 | + } | |
54 | + } | |
55 | + .... | |
56 | + } | |
57 | + **/ | |
58 | + | |
59 | + function copyWidget(widget, aliasesInfo, originalColumns) { | |
60 | + var widgetItem = { | |
61 | + widget: widget, | |
62 | + aliasesInfo: aliasesInfo, | |
63 | + originalColumns: originalColumns | |
64 | + } | |
65 | + bufferStore.set(WIDGET_ITEM, angular.toJson(widgetItem)); | |
66 | + } | |
67 | + | |
68 | + function hasWidget() { | |
69 | + return bufferStore.get(WIDGET_ITEM); | |
70 | + } | |
71 | + | |
72 | + function pasteWidget(targetDasgboard, position) { | |
73 | + var widgetItemJson = bufferStore.get(WIDGET_ITEM); | |
74 | + if (widgetItemJson) { | |
75 | + var widgetItem = angular.fromJson(widgetItemJson); | |
76 | + var widget = widgetItem.widget; | |
77 | + var aliasesInfo = widgetItem.aliasesInfo; | |
78 | + var originalColumns = widgetItem.originalColumns; | |
79 | + var targetRow = -1; | |
80 | + var targetColumn = -1; | |
81 | + if (position) { | |
82 | + targetRow = position.row; | |
83 | + targetColumn = position.column; | |
84 | + } | |
85 | + addWidgetToDashboard(targetDasgboard, widget, aliasesInfo, originalColumns, targetRow, targetColumn); | |
86 | + } | |
87 | + } | |
88 | + | |
89 | + function addWidgetToDashboard(dashboard, widget, aliasesInfo, originalColumns, row, column) { | |
90 | + var theDashboard; | |
91 | + if (dashboard) { | |
92 | + theDashboard = dashboard; | |
93 | + } else { | |
94 | + theDashboard = {}; | |
95 | + } | |
96 | + if (!theDashboard.configuration) { | |
97 | + theDashboard.configuration = {}; | |
98 | + } | |
99 | + if (!theDashboard.configuration.deviceAliases) { | |
100 | + theDashboard.configuration.deviceAliases = {}; | |
101 | + } | |
102 | + updateAliases(theDashboard, widget, aliasesInfo); | |
103 | + | |
104 | + if (!theDashboard.configuration.widgets) { | |
105 | + theDashboard.configuration.widgets = []; | |
106 | + } | |
107 | + var targetColumns = 24; | |
108 | + if (theDashboard.configuration.gridSettings && | |
109 | + theDashboard.configuration.gridSettings.columns) { | |
110 | + targetColumns = theDashboard.configuration.gridSettings.columns; | |
111 | + } | |
112 | + if (targetColumns != originalColumns) { | |
113 | + var ratio = targetColumns / originalColumns; | |
114 | + widget.sizeX *= ratio; | |
115 | + widget.sizeY *= ratio; | |
116 | + } | |
117 | + if (row > -1 && column > - 1) { | |
118 | + widget.row = row; | |
119 | + widget.col = column; | |
120 | + } else { | |
121 | + row = 0; | |
122 | + for (var w in theDashboard.configuration.widgets) { | |
123 | + var existingWidget = theDashboard.configuration.widgets[w]; | |
124 | + var wRow = existingWidget.row ? existingWidget.row : 0; | |
125 | + var wSizeY = existingWidget.sizeY ? existingWidget.sizeY : 1; | |
126 | + var bottom = wRow + wSizeY; | |
127 | + row = Math.max(row, bottom); | |
128 | + } | |
129 | + widget.row = row; | |
130 | + widget.col = 0; | |
131 | + } | |
132 | + theDashboard.configuration.widgets.push(widget); | |
133 | + return theDashboard; | |
134 | + } | |
135 | + | |
136 | + function updateAliases(dashboard, widget, aliasesInfo) { | |
137 | + var deviceAliases = dashboard.configuration.deviceAliases; | |
138 | + var aliasInfo; | |
139 | + var newAliasId; | |
140 | + for (var datasourceIndex in aliasesInfo.datasourceAliases) { | |
141 | + aliasInfo = aliasesInfo.datasourceAliases[datasourceIndex]; | |
142 | + newAliasId = getDeviceAliasId(deviceAliases, aliasInfo); | |
143 | + widget.config.datasources[datasourceIndex].deviceAliasId = newAliasId; | |
144 | + } | |
145 | + for (var targetDeviceAliasIndex in aliasesInfo.targetDeviceAliases) { | |
146 | + aliasInfo = aliasesInfo.targetDeviceAliases[targetDeviceAliasIndex]; | |
147 | + newAliasId = getDeviceAliasId(deviceAliases, aliasInfo); | |
148 | + widget.config.targetDeviceAliasIds[targetDeviceAliasIndex] = newAliasId; | |
149 | + } | |
150 | + } | |
151 | + | |
152 | + function getDeviceAliasId(deviceAliases, aliasInfo) { | |
153 | + var newAliasId; | |
154 | + for (var aliasId in deviceAliases) { | |
155 | + if (deviceAliases[aliasId].deviceId === aliasInfo.deviceId) { | |
156 | + newAliasId = aliasId; | |
157 | + break; | |
158 | + } | |
159 | + } | |
160 | + if (!newAliasId) { | |
161 | + var newAliasName = createDeviceAliasName(deviceAliases, aliasInfo.aliasName); | |
162 | + newAliasId = 0; | |
163 | + for (aliasId in deviceAliases) { | |
164 | + newAliasId = Math.max(newAliasId, aliasId); | |
165 | + } | |
166 | + newAliasId++; | |
167 | + deviceAliases[newAliasId] = {alias: newAliasName, deviceId: aliasInfo.deviceId}; | |
168 | + } | |
169 | + return newAliasId; | |
170 | + } | |
171 | + | |
172 | + function createDeviceAliasName(deviceAliases, alias) { | |
173 | + var c = 0; | |
174 | + var newAlias = angular.copy(alias); | |
175 | + var unique = false; | |
176 | + while (!unique) { | |
177 | + unique = true; | |
178 | + for (var devAliasId in deviceAliases) { | |
179 | + var devAlias = deviceAliases[devAliasId]; | |
180 | + if (newAlias === devAlias.alias) { | |
181 | + c++; | |
182 | + newAlias = alias + c; | |
183 | + unique = false; | |
184 | + } | |
185 | + } | |
186 | + } | |
187 | + return newAlias; | |
188 | + } | |
189 | + | |
190 | + | |
191 | +} | |
\ No newline at end of file | ... | ... |
... | ... | @@ -38,7 +38,9 @@ |
38 | 38 | "create": "Create", |
39 | 39 | "drag": "Drag", |
40 | 40 | "refresh": "Refresh", |
41 | - "undo": "Undo" | |
41 | + "undo": "Undo", | |
42 | + "copy": "Copy", | |
43 | + "paste": "Paste" | |
42 | 44 | }, |
43 | 45 | "admin": { |
44 | 46 | "general": "General", |
... | ... | @@ -211,7 +213,8 @@ |
211 | 213 | "vertical-margin": "Vertical margin", |
212 | 214 | "vertical-margin-required": "Vertical margin value is required.", |
213 | 215 | "min-vertical-margin-message": "Only 0 is allowed as minimum vertical margin value.", |
214 | - "max-vertical-margin-message": "Only 50 is allowed as maximum vertical margin value." | |
216 | + "max-vertical-margin-message": "Only 50 is allowed as maximum vertical margin value.", | |
217 | + "display-title": "Display dashboard title" | |
215 | 218 | }, |
216 | 219 | "datakey": { |
217 | 220 | "settings": "Settings", | ... | ... |