Showing
18 changed files
with
728 additions
and
167 deletions
@@ -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)"> |
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 | +} |
@@ -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", |