Commit 29c80b3b85e50f63d1bfb0092f71bc5535f1baef

Authored by Igor Kulikov
1 parent 7a8cc68f

Add widgets copy/past ability

@@ -49,6 +49,7 @@ import thingsboardDialogs from './components/datakey-config-dialog.controller'; @@ -49,6 +49,7 @@ import thingsboardDialogs from './components/datakey-config-dialog.controller';
49 import thingsboardMenu from './services/menu.service'; 49 import thingsboardMenu from './services/menu.service';
50 import thingsboardUtils from './common/utils.service'; 50 import thingsboardUtils from './common/utils.service';
51 import thingsboardTypes from './common/types.constant'; 51 import thingsboardTypes from './common/types.constant';
  52 +import thingsboardKeyboardShortcut from './components/keyboard-shortcut.filter';
52 import thingsboardHelp from './help/help.directive'; 53 import thingsboardHelp from './help/help.directive';
53 import thingsboardToast from './services/toast'; 54 import thingsboardToast from './services/toast';
54 import thingsboardHome from './layout'; 55 import thingsboardHome from './layout';
@@ -95,6 +96,7 @@ angular.module('thingsboard', [ @@ -95,6 +96,7 @@ angular.module('thingsboard', [
95 thingsboardMenu, 96 thingsboardMenu,
96 thingsboardUtils, 97 thingsboardUtils,
97 thingsboardTypes, 98 thingsboardTypes,
  99 + thingsboardKeyboardShortcut,
98 thingsboardHelp, 100 thingsboardHelp,
99 thingsboardToast, 101 thingsboardToast,
100 thingsboardHome, 102 thingsboardHome,
@@ -23,6 +23,7 @@ import thingsboardWidget from './widget.directive'; @@ -23,6 +23,7 @@ import thingsboardWidget from './widget.directive';
23 import thingsboardToast from '../services/toast'; 23 import thingsboardToast from '../services/toast';
24 import thingsboardTimewindow from './timewindow.directive'; 24 import thingsboardTimewindow from './timewindow.directive';
25 import thingsboardEvents from './tb-event-directives'; 25 import thingsboardEvents from './tb-event-directives';
  26 +import thingsboardMousepointMenu from './mousepoint-menu.directive';
26 27
27 /* eslint-disable import/no-unresolved, import/default */ 28 /* eslint-disable import/no-unresolved, import/default */
28 29
@@ -38,6 +39,7 @@ export default angular.module('thingsboard.directives.dashboard', [thingsboardTy @@ -38,6 +39,7 @@ export default angular.module('thingsboard.directives.dashboard', [thingsboardTy
38 thingsboardWidget, 39 thingsboardWidget,
39 thingsboardTimewindow, 40 thingsboardTimewindow,
40 thingsboardEvents, 41 thingsboardEvents,
  42 + thingsboardMousepointMenu,
41 gridster.name]) 43 gridster.name])
42 .directive('tbDashboard', Dashboard) 44 .directive('tbDashboard', Dashboard)
43 .name; 45 .name;
@@ -59,7 +61,10 @@ function Dashboard() { @@ -59,7 +61,10 @@ function Dashboard() {
59 isRemoveActionEnabled: '=', 61 isRemoveActionEnabled: '=',
60 onEditWidget: '&?', 62 onEditWidget: '&?',
61 onRemoveWidget: '&?', 63 onRemoveWidget: '&?',
  64 + onWidgetMouseDown: '&?',
62 onWidgetClicked: '&?', 65 onWidgetClicked: '&?',
  66 + prepareDashboardContextMenu: '&?',
  67 + prepareWidgetContextMenu: '&?',
63 loadWidgets: '&?', 68 loadWidgets: '&?',
64 onInit: '&?', 69 onInit: '&?',
65 onInitFailed: '&?', 70 onInitFailed: '&?',
@@ -75,8 +80,9 @@ function Dashboard() { @@ -75,8 +80,9 @@ function Dashboard() {
75 function DashboardController($scope, $rootScope, $element, $timeout, $log, toast, types) { 80 function DashboardController($scope, $rootScope, $element, $timeout, $log, toast, types) {
76 81
77 var highlightedMode = false; 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 var widgetMouseMoved = false; 86 var widgetMouseMoved = false;
81 87
82 var gridsterParent = null; 88 var gridsterParent = null;
@@ -117,6 +123,8 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast @@ -117,6 +123,8 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
117 vm.isWidgetExpanded = false; 123 vm.isWidgetExpanded = false;
118 vm.isHighlighted = isHighlighted; 124 vm.isHighlighted = isHighlighted;
119 vm.isNotHighlighted = isNotHighlighted; 125 vm.isNotHighlighted = isNotHighlighted;
  126 + vm.selectWidget = selectWidget;
  127 + vm.getSelectedWidget = getSelectedWidget;
120 vm.highlightWidget = highlightWidget; 128 vm.highlightWidget = highlightWidget;
121 vm.resetHighlight = resetHighlight; 129 vm.resetHighlight = resetHighlight;
122 130
@@ -134,6 +142,17 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast @@ -134,6 +142,17 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
134 vm.removeWidget = removeWidget; 142 vm.removeWidget = removeWidget;
135 vm.loading = loading; 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 //$element[0].onmousemove=function(){ 156 //$element[0].onmousemove=function(){
138 // widgetMouseMove(); 157 // widgetMouseMove();
139 // } 158 // }
@@ -305,7 +324,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast @@ -305,7 +324,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
305 } 324 }
306 325
307 function resetWidgetClick () { 326 function resetWidgetClick () {
308 - mouseDownIndex = -1; 327 + mouseDownWidget = -1;
309 widgetMouseMoved = false; 328 widgetMouseMoved = false;
310 } 329 }
311 330
@@ -315,25 +334,27 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast @@ -315,25 +334,27 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
315 } 334 }
316 335
317 function widgetMouseDown ($event, widget) { 336 function widgetMouseDown ($event, widget) {
318 - mouseDownIndex = vm.widgets.indexOf(widget); 337 + mouseDownWidget = widget;
319 widgetMouseMoved = false; 338 widgetMouseMoved = false;
  339 + if (vm.onWidgetMouseDown) {
  340 + vm.onWidgetMouseDown({event: $event, widget: widget});
  341 + }
320 } 342 }
321 343
322 function widgetMouseMove () { 344 function widgetMouseMove () {
323 - if (mouseDownIndex > -1) { 345 + if (mouseDownWidget) {
324 widgetMouseMoved = true; 346 widgetMouseMoved = true;
325 } 347 }
326 } 348 }
327 349
328 function widgetMouseUp ($event, widget) { 350 function widgetMouseUp ($event, widget) {
329 $timeout(function () { 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 widgetClicked($event, widget); 354 widgetClicked($event, widget);
334 } 355 }
335 } 356 }
336 - mouseDownIndex = -1; 357 + mouseDownWidget = null;
337 widgetMouseMoved = false; 358 widgetMouseMoved = false;
338 }, 0); 359 }, 0);
339 } 360 }
@@ -347,6 +368,41 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast @@ -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 function editWidget ($event, widget) { 406 function editWidget ($event, widget) {
351 resetWidgetClick(); 407 resetWidgetClick();
352 if ($event) { 408 if ($event) {
@@ -367,10 +423,10 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast @@ -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 highlightedMode = true; 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 if (item) { 430 if (item) {
375 var height = $(item).outerHeight(true); 431 var height = $(item).outerHeight(true);
376 var rectHeight = gridsterParent.height(); 432 var rectHeight = gridsterParent.height();
@@ -385,17 +441,39 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast @@ -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 function resetHighlight() { 465 function resetHighlight() {
389 highlightedMode = false; 466 highlightedMode = false;
390 - highlightedIndex = -1; 467 + highlightedWidget = null;
  468 + selectedWidget = null;
391 } 469 }
392 470
393 function isHighlighted(widget) { 471 function isHighlighted(widget) {
394 - return highlightedMode && vm.widgets.indexOf(widget) === highlightedIndex; 472 + return (highlightedMode && highlightedWidget === widget) || (selectedWidget === widget);
395 } 473 }
396 474
397 function isNotHighlighted(widget) { 475 function isNotHighlighted(widget) {
398 - return highlightedMode && vm.widgets.indexOf(widget) != highlightedIndex; 476 + return highlightedMode && highlightedWidget != widget;
399 } 477 }
400 478
401 function widgetColor(widget) { 479 function widgetColor(widget) {
@@ -19,64 +19,90 @@ @@ -19,64 +19,90 @@
19 ng-show="(vm.loading() || vm.dashboardLoading) && !vm.isEdit"> 19 ng-show="(vm.loading() || vm.dashboardLoading) && !vm.isEdit">
20 <md-progress-circular md-mode="indeterminate" class="md-warn" md-diameter="100"></md-progress-circular> 20 <md-progress-circular md-mode="indeterminate" class="md-warn" md-diameter="100"></md-progress-circular>
21 </md-content> 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 </div> 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 </div> 97 </div>
81 - </div>  
82 -</md-content>  
  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>
  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,7 +20,7 @@ const PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
20 var tbEventDirectives = {}; 20 var tbEventDirectives = {};
21 21
22 angular.forEach( 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 function(eventName) { 24 function(eventName) {
25 var directiveName = directiveNormalize('tb-' + eventName); 25 var directiveName = directiveNormalize('tb-' + eventName);
26 tbEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse) { 26 tbEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse) {
@@ -55,12 +55,26 @@ function WidgetsBundleSelect($compile, $templateCache, widgetService, types) { @@ -55,12 +55,26 @@ function WidgetsBundleSelect($compile, $templateCache, widgetService, types) {
55 if (widgetsBundles.length > 0) { 55 if (widgetsBundles.length > 0) {
56 scope.widgetsBundle = widgetsBundles[0]; 56 scope.widgetsBundle = widgetsBundles[0];
57 } 57 }
  58 + } else if (angular.isDefined(scope.selectBundleAlias)) {
  59 + selectWidgetsBundleByAlias(scope.selectBundleAlias);
58 } 60 }
59 }, 61 },
60 function fail() { 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 scope.isSystem = function(item) { 78 scope.isSystem = function(item) {
65 return item && item.tenantId.id === types.id.nullUid; 79 return item && item.tenantId.id === types.id.nullUid;
66 } 80 }
@@ -79,6 +93,12 @@ function WidgetsBundleSelect($compile, $templateCache, widgetService, types) { @@ -79,6 +93,12 @@ function WidgetsBundleSelect($compile, $templateCache, widgetService, types) {
79 scope.updateView(); 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 $compile(element.contents())(scope); 102 $compile(element.contents())(scope);
83 } 103 }
84 104
@@ -90,7 +110,8 @@ function WidgetsBundleSelect($compile, $templateCache, widgetService, types) { @@ -90,7 +110,8 @@ function WidgetsBundleSelect($compile, $templateCache, widgetService, types) {
90 bundlesScope: '@', 110 bundlesScope: '@',
91 theForm: '=?', 111 theForm: '=?',
92 tbRequired: '=?', 112 tbRequired: '=?',
93 - selectFirstBundle: '=' 113 + selectFirstBundle: '=',
  114 + selectBundleAlias: '=?'
94 } 115 }
95 }; 116 };
96 } 117 }
@@ -28,6 +28,10 @@ export default function DashboardSettingsController($scope, $mdDialog, gridSetti @@ -28,6 +28,10 @@ export default function DashboardSettingsController($scope, $mdDialog, gridSetti
28 28
29 vm.gridSettings = gridSettings || {}; 29 vm.gridSettings = gridSettings || {};
30 30
  31 + if (angular.isUndefined(vm.gridSettings.showTitle)) {
  32 + vm.gridSettings.showTitle = true;
  33 + }
  34 +
31 vm.gridSettings.backgroundColor = vm.gridSettings.backgroundColor || 'rgba(0,0,0,0)'; 35 vm.gridSettings.backgroundColor = vm.gridSettings.backgroundColor || 'rgba(0,0,0,0)';
32 vm.gridSettings.columns = vm.gridSettings.columns || 24; 36 vm.gridSettings.columns = vm.gridSettings.columns || 24;
33 vm.gridSettings.margins = vm.gridSettings.margins || [10, 10]; 37 vm.gridSettings.margins = vm.gridSettings.margins || [10, 10];
@@ -31,6 +31,11 @@ @@ -31,6 +31,11 @@
31 <md-dialog-content> 31 <md-dialog-content>
32 <div class="md-dialog-content"> 32 <div class="md-dialog-content">
33 <fieldset ng-disabled="loading"> 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 <md-input-container class="md-block"> 39 <md-input-container class="md-block">
35 <label translate>dashboard.columns-count</label> 40 <label translate>dashboard.columns-count</label>
36 <input required type="number" step="any" name="columns" ng-model="vm.gridSettings.columns" min="10" 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,7 +23,7 @@ import addWidgetTemplate from './add-widget.tpl.html';
23 23
24 /*@ngInject*/ 24 /*@ngInject*/
25 export default function DashboardController(types, widgetService, userService, 25 export default function DashboardController(types, widgetService, userService,
26 - dashboardService, $window, $rootScope, 26 + dashboardService, itembuffer, hotkeys, $window, $rootScope,
27 $scope, $state, $stateParams, $mdDialog, $timeout, $document, $q, $translate, $filter) { 27 $scope, $state, $stateParams, $mdDialog, $timeout, $document, $q, $translate, $filter) {
28 28
29 var user = userService.getCurrentUser(); 29 var user = userService.getCurrentUser();
@@ -48,7 +48,10 @@ export default function DashboardController(types, widgetService, userService, @@ -48,7 +48,10 @@ export default function DashboardController(types, widgetService, userService,
48 vm.addWidgetFromType = addWidgetFromType; 48 vm.addWidgetFromType = addWidgetFromType;
49 vm.dashboardInited = dashboardInited; 49 vm.dashboardInited = dashboardInited;
50 vm.dashboardInitFailed = dashboardInitFailed; 50 vm.dashboardInitFailed = dashboardInitFailed;
  51 + vm.widgetMouseDown = widgetMouseDown;
51 vm.widgetClicked = widgetClicked; 52 vm.widgetClicked = widgetClicked;
  53 + vm.prepareDashboardContextMenu = prepareDashboardContextMenu;
  54 + vm.prepareWidgetContextMenu = prepareWidgetContextMenu;
52 vm.editWidget = editWidget; 55 vm.editWidget = editWidget;
53 vm.isTenantAdmin = isTenantAdmin; 56 vm.isTenantAdmin = isTenantAdmin;
54 vm.loadDashboard = loadDashboard; 57 vm.loadDashboard = loadDashboard;
@@ -63,6 +66,7 @@ export default function DashboardController(types, widgetService, userService, @@ -63,6 +66,7 @@ export default function DashboardController(types, widgetService, userService,
63 vm.toggleDashboardEditMode = toggleDashboardEditMode; 66 vm.toggleDashboardEditMode = toggleDashboardEditMode;
64 vm.onRevertWidgetEdit = onRevertWidgetEdit; 67 vm.onRevertWidgetEdit = onRevertWidgetEdit;
65 vm.helpLinkIdForWidgetType = helpLinkIdForWidgetType; 68 vm.helpLinkIdForWidgetType = helpLinkIdForWidgetType;
  69 + vm.displayTitle = displayTitle;
66 70
67 vm.widgetsBundle; 71 vm.widgetsBundle;
68 72
@@ -194,6 +198,7 @@ export default function DashboardController(types, widgetService, userService, @@ -194,6 +198,7 @@ export default function DashboardController(types, widgetService, userService,
194 198
195 function dashboardInited(dashboard) { 199 function dashboardInited(dashboard) {
196 vm.dashboardContainer = dashboard; 200 vm.dashboardContainer = dashboard;
  201 + initHotKeys();
197 } 202 }
198 203
199 function isTenantAdmin() { 204 function isTenantAdmin() {
@@ -289,18 +294,188 @@ export default function DashboardController(types, widgetService, userService, @@ -289,18 +294,188 @@ export default function DashboardController(types, widgetService, userService,
289 var delayOffset = transition ? 350 : 0; 294 var delayOffset = transition ? 350 : 0;
290 var delay = transition ? 400 : 300; 295 var delay = transition ? 400 : 300;
291 $timeout(function () { 296 $timeout(function () {
292 - vm.dashboardContainer.highlightWidget(vm.editingWidgetIndex, delay); 297 + vm.dashboardContainer.highlightWidget(widget, delay);
293 }, delayOffset, false); 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 function widgetClicked($event, widget) { 309 function widgetClicked($event, widget) {
299 if (vm.isEditingWidget) { 310 if (vm.isEditingWidget) {
300 editWidget($event, widget); 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 function helpLinkIdForWidgetType() { 479 function helpLinkIdForWidgetType() {
305 var link = 'widgetsConfig'; 480 var link = 'widgetsConfig';
306 if (vm.editingWidget && vm.editingWidget.type) { 481 if (vm.editingWidget && vm.editingWidget.type) {
@@ -322,6 +497,15 @@ export default function DashboardController(types, widgetService, userService, @@ -322,6 +497,15 @@ export default function DashboardController(types, widgetService, userService,
322 return link; 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 function onRevertWidgetEdit(widgetForm) { 509 function onRevertWidgetEdit(widgetForm) {
326 if (widgetForm.$dirty) { 510 if (widgetForm.$dirty) {
327 widgetForm.$setPristine(); 511 widgetForm.$setPristine();
@@ -331,7 +515,9 @@ export default function DashboardController(types, widgetService, userService, @@ -331,7 +515,9 @@ export default function DashboardController(types, widgetService, userService,
331 515
332 function saveWidget(widgetForm) { 516 function saveWidget(widgetForm) {
333 widgetForm.$setPristine(); 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 function onEditWidgetClosed() { 523 function onEditWidgetClosed() {
@@ -421,8 +607,8 @@ export default function DashboardController(types, widgetService, userService, @@ -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 if (vm.isEdit) { 612 if (vm.isEdit) {
427 if (vm.widgetEditMode) { 613 if (vm.widgetEditMode) {
428 vm.prevWidgets = angular.copy(vm.widgets); 614 vm.prevWidgets = angular.copy(vm.widgets);
@@ -433,14 +619,23 @@ export default function DashboardController(types, widgetService, userService, @@ -433,14 +619,23 @@ export default function DashboardController(types, widgetService, userService,
433 if (vm.widgetEditMode) { 619 if (vm.widgetEditMode) {
434 vm.widgets = vm.prevWidgets; 620 vm.widgets = vm.prevWidgets;
435 } else { 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 function saveDashboard() { 637 function saveDashboard() {
443 - vm.isEdit = false; 638 + setEditMode(false, false);
444 notifyDashboardUpdated(); 639 notifyDashboardUpdated();
445 } 640 }
446 641
@@ -51,7 +51,7 @@ @@ -51,7 +51,7 @@
51 </md-button> 51 </md-button>
52 </section> 52 </section>
53 <section ng-if="!vm.widgetEditMode" class="tb-dashboard-title" layout="row" layout-align="center center"> 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 <md-input-container ng-show="vm.isEdit" class="md-block" style="height: 30px;"> 55 <md-input-container ng-show="vm.isEdit" class="md-block" style="height: 30px;">
56 <label translate>dashboard.title</label> 56 <label translate>dashboard.title</label>
57 <input class="tb-dashboard-title" required name="title" ng-model="vm.dashboard.title"> 57 <input class="tb-dashboard-title" required name="title" ng-model="vm.dashboard.title">
@@ -64,7 +64,7 @@ @@ -64,7 +64,7 @@
64 </md-button> 64 </md-button>
65 </section> 65 </section>
66 <div class="tb-absolute-fill" 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 <tb-dashboard 68 <tb-dashboard
69 dashboard-style="{'background-color': vm.dashboard.configuration.gridSettings.backgroundColor, 69 dashboard-style="{'background-color': vm.dashboard.configuration.gridSettings.backgroundColor,
70 'background-image': 'url('+vm.dashboard.configuration.gridSettings.backgroundImageUrl+')', 70 'background-image': 'url('+vm.dashboard.configuration.gridSettings.backgroundImageUrl+')',
@@ -82,7 +82,11 @@ @@ -82,7 +82,11 @@
82 is-edit-action-enabled="vm.isEdit || vm.widgetEditMode" 82 is-edit-action-enabled="vm.isEdit || vm.widgetEditMode"
83 is-remove-action-enabled="vm.isEdit && !vm.widgetEditMode" 83 is-remove-action-enabled="vm.isEdit && !vm.widgetEditMode"
84 on-edit-widget="vm.editWidget(event, widget)" 84 on-edit-widget="vm.editWidget(event, widget)"
  85 + on-widget-mouse-down="vm.widgetMouseDown(event, widget)"
85 on-widget-clicked="vm.widgetClicked(event, widget)" 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 on-remove-widget="vm.removeWidget(event, widget)" 90 on-remove-widget="vm.removeWidget(event, widget)"
87 load-widgets="vm.loadDashboard()" 91 load-widgets="vm.loadDashboard()"
88 on-init="vm.dashboardInited(dashboard)" 92 on-init="vm.dashboardInited(dashboard)"
@@ -29,6 +29,7 @@ import thingsboardDashboard from '../components/dashboard.directive'; @@ -29,6 +29,7 @@ import thingsboardDashboard from '../components/dashboard.directive';
29 import thingsboardExpandFullscreen from '../components/expand-fullscreen.directive'; 29 import thingsboardExpandFullscreen from '../components/expand-fullscreen.directive';
30 import thingsboardWidgetsBundleSelect from '../components/widgets-bundle-select.directive'; 30 import thingsboardWidgetsBundleSelect from '../components/widgets-bundle-select.directive';
31 import thingsboardTypes from '../common/types.constant'; 31 import thingsboardTypes from '../common/types.constant';
  32 +import thingsboardItemBuffer from '../services/item-buffer.service';
32 33
33 import DashboardRoutes from './dashboard.routes'; 34 import DashboardRoutes from './dashboard.routes';
34 import DashboardsController from './dashboards.controller'; 35 import DashboardsController from './dashboards.controller';
@@ -45,6 +46,7 @@ export default angular.module('thingsboard.dashboard', [ @@ -45,6 +46,7 @@ export default angular.module('thingsboard.dashboard', [
45 uiRouter, 46 uiRouter,
46 gridster.name, 47 gridster.name,
47 thingsboardTypes, 48 thingsboardTypes,
  49 + thingsboardItemBuffer,
48 thingsboardGrid, 50 thingsboardGrid,
49 thingsboardApiWidget, 51 thingsboardApiWidget,
50 thingsboardApiUser, 52 thingsboardApiUser,
@@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 /*@ngInject*/ 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 var vm = this; 19 var vm = this;
20 20
@@ -34,62 +34,20 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog, @@ -34,62 +34,20 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog,
34 function add() { 34 function add() {
35 $scope.theForm.$setPristine(); 35 $scope.theForm.$setPristine();
36 var theDashboard; 36 var theDashboard;
37 - var deviceAliases;  
38 - widget.col = 0;  
39 - widget.sizeX /= 2;  
40 - widget.sizeY /= 2;  
41 if (vm.addToDashboardType === 0) { 37 if (vm.addToDashboardType === 0) {
42 theDashboard = vm.dashboard; 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 } else { 39 } else {
84 theDashboard = vm.newDashboard; 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 dashboardService.saveDashboard(theDashboard).then( 51 dashboardService.saveDashboard(theDashboard).then(
94 function success(dashboard) { 52 function success(dashboard) {
95 $mdDialog.hide(); 53 $mdDialog.hide();
@@ -98,25 +56,6 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog, @@ -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,6 +239,8 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
239 index: 0 239 index: 0
240 } 240 }
241 scope.widgetsBundle = null; 241 scope.widgetsBundle = null;
  242 + scope.firstBundle = true;
  243 + scope.selectedWidgetsBundleAlias = types.systemBundleAlias.cards;
242 244
243 scope.deviceAliases = {}; 245 scope.deviceAliases = {};
244 scope.deviceAliases['1'] = {alias: scope.deviceName, deviceId: scope.deviceId}; 246 scope.deviceAliases['1'] = {alias: scope.deviceName, deviceId: scope.deviceId};
@@ -326,13 +328,6 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS @@ -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 scope.exitWidgetMode = function() { 333 scope.exitWidgetMode = function() {
@@ -344,6 +339,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS @@ -344,6 +339,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
344 scope.widgetsIndexWatch(); 339 scope.widgetsIndexWatch();
345 scope.widgetsIndexWatch = null; 340 scope.widgetsIndexWatch = null;
346 } 341 }
  342 + scope.selectedWidgetsBundleAlias = null;
347 scope.mode = 'default'; 343 scope.mode = 'default';
348 scope.getDeviceAttributes(true); 344 scope.getDeviceAttributes(true);
349 } 345 }
@@ -105,7 +105,8 @@ @@ -105,7 +105,8 @@
105 <tb-widgets-bundle-select flex-offset="5" 105 <tb-widgets-bundle-select flex-offset="5"
106 flex 106 flex
107 ng-model="widgetsBundle" 107 ng-model="widgetsBundle"
108 - select-first-bundle="false"> 108 + select-first-bundle="false"
  109 + select-bundle-alias="selectedWidgetsBundleAlias">
109 </tb-widgets-bundle-select> 110 </tb-widgets-bundle-select>
110 </div> 111 </div>
111 <md-button ng-show="widgetsList.length > 0" class="md-accent md-hue-2 md-raised" ng-click="addWidgetToDashboard($event)"> 112 <md-button ng-show="widgetsList.length > 0" class="md-accent md-hue-2 md-raised" ng-click="addWidgetToDashboard($event)">
  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 +}
@@ -38,7 +38,9 @@ @@ -38,7 +38,9 @@
38 "create": "Create", 38 "create": "Create",
39 "drag": "Drag", 39 "drag": "Drag",
40 "refresh": "Refresh", 40 "refresh": "Refresh",
41 - "undo": "Undo" 41 + "undo": "Undo",
  42 + "copy": "Copy",
  43 + "paste": "Paste"
42 }, 44 },
43 "admin": { 45 "admin": {
44 "general": "General", 46 "general": "General",
@@ -211,7 +213,8 @@ @@ -211,7 +213,8 @@
211 "vertical-margin": "Vertical margin", 213 "vertical-margin": "Vertical margin",
212 "vertical-margin-required": "Vertical margin value is required.", 214 "vertical-margin-required": "Vertical margin value is required.",
213 "min-vertical-margin-message": "Only 0 is allowed as minimum vertical margin value.", 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 "datakey": { 219 "datakey": {
217 "settings": "Settings", 220 "settings": "Settings",
@@ -169,6 +169,9 @@ md-menu-item { @@ -169,6 +169,9 @@ md-menu-item {
169 md-menu-item { 169 md-menu-item {
170 .md-button { 170 .md-button {
171 display: block; 171 display: block;
  172 + .tb-alt-text {
  173 + float: right;
  174 + }
172 } 175 }
173 } 176 }
174 177