Browse Source

Add widgets copy/past ability

pull/24/head
Igor Kulikov 10 years ago
parent
commit
29c80b3b85
  1. 2
      ui/src/app/app.js
  2. 108
      ui/src/app/components/dashboard.directive.js
  3. 144
      ui/src/app/components/dashboard.tpl.html
  4. 40
      ui/src/app/components/keyboard-shortcut.filter.js
  5. 51
      ui/src/app/components/mousepoint-menu.directive.js
  6. 2
      ui/src/app/components/tb-event-directives.js
  7. 23
      ui/src/app/components/widgets-bundle-select.directive.js
  8. 4
      ui/src/app/dashboard/dashboard-settings.controller.js
  9. 5
      ui/src/app/dashboard/dashboard-settings.tpl.html
  10. 211
      ui/src/app/dashboard/dashboard.controller.js
  11. 8
      ui/src/app/dashboard/dashboard.tpl.html
  12. 2
      ui/src/app/dashboard/index.js
  13. 81
      ui/src/app/device/attribute/add-widget-to-dashboard-dialog.controller.js
  14. 10
      ui/src/app/device/attribute/attribute-table.directive.js
  15. 3
      ui/src/app/device/attribute/attribute-table.tpl.html
  16. 191
      ui/src/app/services/item-buffer.service.js
  17. 7
      ui/src/locale/en_US.json
  18. 3
      ui/src/scss/main.scss

2
ui/src/app/app.js

@ -49,6 +49,7 @@ import thingsboardDialogs from './components/datakey-config-dialog.controller';
import thingsboardMenu from './services/menu.service';
import thingsboardUtils from './common/utils.service';
import thingsboardTypes from './common/types.constant';
import thingsboardKeyboardShortcut from './components/keyboard-shortcut.filter';
import thingsboardHelp from './help/help.directive';
import thingsboardToast from './services/toast';
import thingsboardHome from './layout';
@ -95,6 +96,7 @@ angular.module('thingsboard', [
thingsboardMenu,
thingsboardUtils,
thingsboardTypes,
thingsboardKeyboardShortcut,
thingsboardHelp,
thingsboardToast,
thingsboardHome,

108
ui/src/app/components/dashboard.directive.js

@ -23,6 +23,7 @@ import thingsboardWidget from './widget.directive';
import thingsboardToast from '../services/toast';
import thingsboardTimewindow from './timewindow.directive';
import thingsboardEvents from './tb-event-directives';
import thingsboardMousepointMenu from './mousepoint-menu.directive';
/* eslint-disable import/no-unresolved, import/default */
@ -38,6 +39,7 @@ export default angular.module('thingsboard.directives.dashboard', [thingsboardTy
thingsboardWidget,
thingsboardTimewindow,
thingsboardEvents,
thingsboardMousepointMenu,
gridster.name])
.directive('tbDashboard', Dashboard)
.name;
@ -59,7 +61,10 @@ function Dashboard() {
isRemoveActionEnabled: '=',
onEditWidget: '&?',
onRemoveWidget: '&?',
onWidgetMouseDown: '&?',
onWidgetClicked: '&?',
prepareDashboardContextMenu: '&?',
prepareWidgetContextMenu: '&?',
loadWidgets: '&?',
onInit: '&?',
onInitFailed: '&?',
@ -75,8 +80,9 @@ function Dashboard() {
function DashboardController($scope, $rootScope, $element, $timeout, $log, toast, types) {
var highlightedMode = false;
var highlightedIndex = -1;
var mouseDownIndex = -1;
var highlightedWidget = null;
var selectedWidget = null;
var mouseDownWidget = -1;
var widgetMouseMoved = false;
var gridsterParent = null;
@ -117,6 +123,8 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
vm.isWidgetExpanded = false;
vm.isHighlighted = isHighlighted;
vm.isNotHighlighted = isNotHighlighted;
vm.selectWidget = selectWidget;
vm.getSelectedWidget = getSelectedWidget;
vm.highlightWidget = highlightWidget;
vm.resetHighlight = resetHighlight;
@ -134,6 +142,17 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
vm.removeWidget = removeWidget;
vm.loading = loading;
vm.openDashboardContextMenu = openDashboardContextMenu;
vm.openWidgetContextMenu = openWidgetContextMenu;
vm.getEventGridPosition = getEventGridPosition;
vm.contextMenuItems = [];
vm.contextMenuEvent = null;
vm.widgetContextMenuItems = [];
vm.widgetContextMenuEvent = null;
//$element[0].onmousemove=function(){
// widgetMouseMove();
// }
@ -305,7 +324,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
}
function resetWidgetClick () {
mouseDownIndex = -1;
mouseDownWidget = -1;
widgetMouseMoved = false;
}
@ -315,25 +334,27 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
}
function widgetMouseDown ($event, widget) {
mouseDownIndex = vm.widgets.indexOf(widget);
mouseDownWidget = widget;
widgetMouseMoved = false;
if (vm.onWidgetMouseDown) {
vm.onWidgetMouseDown({event: $event, widget: widget});
}
}
function widgetMouseMove () {
if (mouseDownIndex > -1) {
if (mouseDownWidget) {
widgetMouseMoved = true;
}
}
function widgetMouseUp ($event, widget) {
$timeout(function () {
if (!widgetMouseMoved && mouseDownIndex > -1) {
var index = vm.widgets.indexOf(widget);
if (index === mouseDownIndex) {
if (!widgetMouseMoved && mouseDownWidget) {
if (widget === mouseDownWidget) {
widgetClicked($event, widget);
}
}
mouseDownIndex = -1;
mouseDownWidget = null;
widgetMouseMoved = false;
}, 0);
}
@ -347,6 +368,41 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
}
}
function openDashboardContextMenu($event, $mdOpenMousepointMenu) {
if (vm.prepareDashboardContextMenu) {
vm.contextMenuItems = vm.prepareDashboardContextMenu();
if (vm.contextMenuItems && vm.contextMenuItems.length > 0) {
vm.contextMenuEvent = $event;
$mdOpenMousepointMenu($event);
}
}
}
function openWidgetContextMenu($event, widget, $mdOpenMousepointMenu) {
if (vm.prepareWidgetContextMenu) {
vm.widgetContextMenuItems = vm.prepareWidgetContextMenu({widget: widget});
if (vm.widgetContextMenuItems && vm.widgetContextMenuItems.length > 0) {
vm.widgetContextMenuEvent = $event;
$mdOpenMousepointMenu($event);
}
}
}
function getEventGridPosition(event) {
var pos = {
row: 0,
column: 0
}
var offset = gridsterParent.offset();
var x = event.pageX - offset.left + gridsterParent.scrollLeft();
var y = event.pageY - offset.top + gridsterParent.scrollTop();
if (gridster) {
pos.row = gridster.pixelsToRows(y);
pos.column = gridster.pixelsToColumns(x);
}
return pos;
}
function editWidget ($event, widget) {
resetWidgetClick();
if ($event) {
@ -367,10 +423,10 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
}
}
function highlightWidget(widgetIndex, delay) {
function highlightWidget(widget, delay) {
highlightedMode = true;
highlightedIndex = widgetIndex;
var item = $('.gridster-item', gridster.$element)[widgetIndex];
highlightedWidget = widget;
var item = $('.gridster-item', gridster.$element)[vm.widgets.indexOf(widget)];
if (item) {
var height = $(item).outerHeight(true);
var rectHeight = gridsterParent.height();
@ -385,17 +441,39 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
}
}
function selectWidget(widget, delay) {
selectedWidget = widget;
var item = $('.gridster-item', gridster.$element)[vm.widgets.indexOf(widget)];
if (item) {
var height = $(item).outerHeight(true);
var rectHeight = gridsterParent.height();
var offset = (rectHeight - height) / 2;
var scrollTop = item.offsetTop;
if (offset > 0) {
scrollTop -= offset;
}
gridsterParent.animate({
scrollTop: scrollTop
}, delay);
}
}
function getSelectedWidget() {
return selectedWidget;
}
function resetHighlight() {
highlightedMode = false;
highlightedIndex = -1;
highlightedWidget = null;
selectedWidget = null;
}
function isHighlighted(widget) {
return highlightedMode && vm.widgets.indexOf(widget) === highlightedIndex;
return (highlightedMode && highlightedWidget === widget) || (selectedWidget === widget);
}
function isNotHighlighted(widget) {
return highlightedMode && vm.widgets.indexOf(widget) != highlightedIndex;
return highlightedMode && highlightedWidget != widget;
}
function widgetColor(widget) {

144
ui/src/app/components/dashboard.tpl.html

@ -19,64 +19,90 @@
ng-show="(vm.loading() || vm.dashboardLoading) && !vm.isEdit">
<md-progress-circular md-mode="indeterminate" class="md-warn" md-diameter="100"></md-progress-circular>
</md-content>
<md-content id="gridster-parent" class="tb-dashboard-content" flex layout-wrap>
<div ng-style="vm.dashboardStyle" id="gridster-background" style="height: auto; min-height: 100%;">
<div id="gridster-child" gridster="vm.gridsterOpts">
<ul>
<!-- ng-click="widgetClicked($event, widget)" -->
<li gridster-item="widget" ng-repeat="widget in vm.widgets">
<div tb-expand-fullscreen expand-button-id="expand-button" on-fullscreen-changed="vm.onWidgetFullscreenChanged(expanded, widget)" layout="column" class="tb-widget md-whiteframe-4dp"
ng-class="{'tb-highlighted': vm.isHighlighted(widget), 'tb-not-highlighted': vm.isNotHighlighted(widget)}"
tb-mousedown="vm.widgetMouseDown($event, widget)"
tb-mousemove="vm.widgetMouseMove($event, widget)"
tb-mouseup="vm.widgetMouseUp($event, widget)"
style="
cursor: pointer;
color: {{vm.widgetColor(widget)}};
background-color: {{vm.widgetBackgroundColor(widget)}};
padding: {{vm.widgetPadding(widget)}}
">
<div class="tb-widget-title" layout="column" ng-show="vm.showWidgetTitle(widget) || vm.hasTimewindow(widget)">
<span ng-show="vm.showWidgetTitle(widget)" class="md-subhead">{{widget.config.title}}</span>
<tb-timewindow ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow>
</div>
<div class="tb-widget-actions" layout="row" layout-align="start center">
<md-button id="expand-button"
aria-label="{{ 'fullscreen.fullscreen' | translate }}"
class="md-icon-button md-primary"></md-button>
<md-button ng-show="vm.isEditActionEnabled && !vm.isWidgetExpanded"
ng-disabled="vm.loading()"
class="md-icon-button md-primary"
ng-click="vm.editWidget($event, widget)"
aria-label="{{ 'widget.edit' | translate }}">
<md-tooltip md-direction="top">
{{ 'widget.edit' | translate }}
</md-tooltip>
<md-icon class="material-icons">
edit
</md-icon>
</md-button>
<md-button ng-show="vm.isRemoveActionEnabled && !vm.isWidgetExpanded"
ng-disabled="vm.loading()"
class="md-icon-button md-primary"
ng-click="vm.removeWidget($event, widget)"
aria-label="{{ 'widget.remove' | translate }}">
<md-tooltip md-direction="top">
{{ 'widget.remove' | translate }}
</md-tooltip>
<md-icon class="material-icons">
close
</md-icon>
</md-button>
</div>
<div flex layout="column" class="tb-widget-content">
<div flex tb-widget
locals="{ visibleRect: vm.visibleRect, widget: widget, deviceAliasList: vm.deviceAliasList, isPreview: vm.isEdit }">
<md-menu md-position-mode="target target" tb-mousepoint-menu>
<md-content id="gridster-parent" class="tb-dashboard-content" flex layout-wrap ng-click="" tb-contextmenu="vm.openDashboardContextMenu($event, $mdOpenMousepointMenu)">
<div ng-style="vm.dashboardStyle" id="gridster-background" style="height: auto; min-height: 100%;">
<div id="gridster-child" gridster="vm.gridsterOpts">
<ul>
<!-- ng-click="widgetClicked($event, widget)" -->
<li gridster-item="widget" ng-repeat="widget in vm.widgets">
<md-menu md-position-mode="target target" tb-mousepoint-menu>
<div tb-expand-fullscreen
expand-button-id="expand-button" on-fullscreen-changed="vm.onWidgetFullscreenChanged(expanded, widget)" layout="column" class="tb-widget md-whiteframe-4dp"
ng-class="{'tb-highlighted': vm.isHighlighted(widget), 'tb-not-highlighted': vm.isNotHighlighted(widget)}"
tb-mousedown="vm.widgetMouseDown($event, widget)"
tb-mousemove="vm.widgetMouseMove($event, widget)"
tb-mouseup="vm.widgetMouseUp($event, widget)"
ng-click=""
tb-contextmenu="vm.openWidgetContextMenu($event, widget, $mdOpenMousepointMenu)"
style="
cursor: pointer;
color: {{vm.widgetColor(widget)}};
background-color: {{vm.widgetBackgroundColor(widget)}};
padding: {{vm.widgetPadding(widget)}}
">
<div class="tb-widget-title" layout="column" ng-show="vm.showWidgetTitle(widget) || vm.hasTimewindow(widget)">
<span ng-show="vm.showWidgetTitle(widget)" class="md-subhead">{{widget.config.title}}</span>
<tb-timewindow ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow>
</div>
<div class="tb-widget-actions" layout="row" layout-align="start center">
<md-button id="expand-button"
ng-show="!vm.isEdit"
aria-label="{{ 'fullscreen.fullscreen' | translate }}"
class="md-icon-button md-primary"></md-button>
<md-button ng-show="vm.isEditActionEnabled && !vm.isWidgetExpanded"
ng-disabled="vm.loading()"
class="md-icon-button md-primary"
ng-click="vm.editWidget($event, widget)"
aria-label="{{ 'widget.edit' | translate }}">
<md-tooltip md-direction="top">
{{ 'widget.edit' | translate }}
</md-tooltip>
<md-icon class="material-icons">
edit
</md-icon>
</md-button>
<md-button ng-show="vm.isRemoveActionEnabled && !vm.isWidgetExpanded"
ng-disabled="vm.loading()"
class="md-icon-button md-primary"
ng-click="vm.removeWidget($event, widget)"
aria-label="{{ 'widget.remove' | translate }}">
<md-tooltip md-direction="top">
{{ 'widget.remove' | translate }}
</md-tooltip>
<md-icon class="material-icons">
close
</md-icon>
</md-button>
</div>
<div flex layout="column" class="tb-widget-content">
<div flex tb-widget
locals="{ visibleRect: vm.visibleRect, widget: widget, deviceAliasList: vm.deviceAliasList, isPreview: vm.isEdit }">
</div>
</div>
</div>
</div>
</div>
</li>
</ul>
<md-menu-content id="menu" width="4" ng-mouseleave="$mdCloseMousepointMenu()">
<md-menu-item ng-repeat ="item in vm.widgetContextMenuItems">
<md-button ng-disabled="!item.enabled" ng-click="item.action(vm.widgetContextMenuEvent, widget)">
<md-icon ng-if="item.icon" md-menu-align-target aria-label="{{ item.value | translate }}" class="material-icons">{{item.icon}}</md-icon>
<span translate>{{item.value}}</span>
<span ng-if="item.shortcut" class="tb-alt-text"> {{ item.shortcut | keyboardShortcut }}</span>
</md-button>
</md-menu-item>
</md-menu-content>
</md-menu>
</li>
</ul>
</div>
</div>
</div>
</md-content>
</md-content>
<md-menu-content id="menu" width="4" ng-mouseleave="$mdCloseMousepointMenu()">
<md-menu-item ng-repeat ="item in vm.contextMenuItems">
<md-button ng-disabled="!item.enabled" ng-click="item.action(vm.contextMenuEvent)">
<md-icon ng-if="item.icon" md-menu-align-target aria-label="{{ item.value | translate }}" class="material-icons">{{item.icon}}</md-icon>
<span translate>{{item.value}}</span>
<span ng-if="item.shortcut" class="tb-alt-text"> {{ item.shortcut | keyboardShortcut }}</span>
</md-button>
</md-menu-item>
</md-menu-content>
</md-menu>

40
ui/src/app/components/keyboard-shortcut.filter.js

@ -0,0 +1,40 @@
/*
* Copyright © 2016 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export default angular.module('thingsboard.filters.keyboardShortcut', [])
.filter('keyboardShortcut', KeyboardShortcut)
.name;
/*@ngInject*/
function KeyboardShortcut($window) {
return function(str) {
if (!str) return;
var keys = str.split('-');
var isOSX = /Mac OS X/.test($window.navigator.userAgent);
var seperator = (!isOSX || keys.length > 2) ? '+' : '';
var abbreviations = {
M: isOSX ? '⌘' : 'Ctrl',
A: isOSX ? 'Option' : 'Alt',
S: 'Shift'
};
return keys.map(function(key, index) {
var last = index == keys.length - 1;
return last ? key : abbreviations[key];
}).join(seperator);
};
}

51
ui/src/app/components/mousepoint-menu.directive.js

@ -0,0 +1,51 @@
/*
* Copyright © 2016 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export default angular.module('thingsboard.directives.mousepointMenu', [])
.directive('tbMousepointMenu', MousepointMenu)
.name;
/*@ngInject*/
function MousepointMenu() {
var linker = function ($scope, $element, $attrs, RightClickContextMenu) {
$scope.$mdOpenMousepointMenu = function($event){
RightClickContextMenu.offsets = function(){
var offset = $element.offset();
var x = $event.pageX - offset.left;
var y = $event.pageY - offset.top;
var offsets = {
left: x,
top: y
}
return offsets;
}
RightClickContextMenu.open($event);
};
$scope.$mdCloseMousepointMenu = function() {
RightClickContextMenu.close();
}
}
return {
restrict: "A",
link: linker,
require: 'mdMenu'
};
}

2
ui/src/app/components/tb-event-directives.js

@ -20,7 +20,7 @@ const PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
var tbEventDirectives = {};
angular.forEach(
'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave contextmenu keydown keyup keypress submit focus blur copy cut paste'.split(' '),
function(eventName) {
var directiveName = directiveNormalize('tb-' + eventName);
tbEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse) {

23
ui/src/app/components/widgets-bundle-select.directive.js

@ -55,12 +55,26 @@ function WidgetsBundleSelect($compile, $templateCache, widgetService, types) {
if (widgetsBundles.length > 0) {
scope.widgetsBundle = widgetsBundles[0];
}
} else if (angular.isDefined(scope.selectBundleAlias)) {
selectWidgetsBundleByAlias(scope.selectBundleAlias);
}
},
function fail() {
}
);
function selectWidgetsBundleByAlias(alias) {
if (scope.widgetsBundles && alias) {
for (var w in scope.widgetsBundles) {
var widgetsBundle = scope.widgetsBundles[w];
if (widgetsBundle.alias === alias) {
scope.widgetsBundle = widgetsBundle;
break;
}
}
}
}
scope.isSystem = function(item) {
return item && item.tenantId.id === types.id.nullUid;
}
@ -79,6 +93,12 @@ function WidgetsBundleSelect($compile, $templateCache, widgetService, types) {
scope.updateView();
});
scope.$watch('selectBundleAlias', function (newVal, prevVal) {
if (newVal !== prevVal) {
selectWidgetsBundleByAlias(scope.selectBundleAlias);
}
});
$compile(element.contents())(scope);
}
@ -90,7 +110,8 @@ function WidgetsBundleSelect($compile, $templateCache, widgetService, types) {
bundlesScope: '@',
theForm: '=?',
tbRequired: '=?',
selectFirstBundle: '='
selectFirstBundle: '=',
selectBundleAlias: '=?'
}
};
}

4
ui/src/app/dashboard/dashboard-settings.controller.js

@ -28,6 +28,10 @@ export default function DashboardSettingsController($scope, $mdDialog, gridSetti
vm.gridSettings = gridSettings || {};
if (angular.isUndefined(vm.gridSettings.showTitle)) {
vm.gridSettings.showTitle = true;
}
vm.gridSettings.backgroundColor = vm.gridSettings.backgroundColor || 'rgba(0,0,0,0)';
vm.gridSettings.columns = vm.gridSettings.columns || 24;
vm.gridSettings.margins = vm.gridSettings.margins || [10, 10];

5
ui/src/app/dashboard/dashboard-settings.tpl.html

@ -31,6 +31,11 @@
<md-dialog-content>
<div class="md-dialog-content">
<fieldset ng-disabled="loading">
<div layout="row" layout-padding>
<md-checkbox flex aria-label="{{ 'dashboard.display-title' | translate }}"
ng-model="vm.gridSettings.showTitle">{{ 'dashboard.display-title' | translate }}
</md-checkbox>
</div>
<md-input-container class="md-block">
<label translate>dashboard.columns-count</label>
<input required type="number" step="any" name="columns" ng-model="vm.gridSettings.columns" min="10"

211
ui/src/app/dashboard/dashboard.controller.js

@ -23,7 +23,7 @@ import addWidgetTemplate from './add-widget.tpl.html';
/*@ngInject*/
export default function DashboardController(types, widgetService, userService,
dashboardService, $window, $rootScope,
dashboardService, itembuffer, hotkeys, $window, $rootScope,
$scope, $state, $stateParams, $mdDialog, $timeout, $document, $q, $translate, $filter) {
var user = userService.getCurrentUser();
@ -48,7 +48,10 @@ export default function DashboardController(types, widgetService, userService,
vm.addWidgetFromType = addWidgetFromType;
vm.dashboardInited = dashboardInited;
vm.dashboardInitFailed = dashboardInitFailed;
vm.widgetMouseDown = widgetMouseDown;
vm.widgetClicked = widgetClicked;
vm.prepareDashboardContextMenu = prepareDashboardContextMenu;
vm.prepareWidgetContextMenu = prepareWidgetContextMenu;
vm.editWidget = editWidget;
vm.isTenantAdmin = isTenantAdmin;
vm.loadDashboard = loadDashboard;
@ -63,6 +66,7 @@ export default function DashboardController(types, widgetService, userService,
vm.toggleDashboardEditMode = toggleDashboardEditMode;
vm.onRevertWidgetEdit = onRevertWidgetEdit;
vm.helpLinkIdForWidgetType = helpLinkIdForWidgetType;
vm.displayTitle = displayTitle;
vm.widgetsBundle;
@ -194,6 +198,7 @@ export default function DashboardController(types, widgetService, userService,
function dashboardInited(dashboard) {
vm.dashboardContainer = dashboard;
initHotKeys();
}
function isTenantAdmin() {
@ -289,18 +294,188 @@ export default function DashboardController(types, widgetService, userService,
var delayOffset = transition ? 350 : 0;
var delay = transition ? 400 : 300;
$timeout(function () {
vm.dashboardContainer.highlightWidget(vm.editingWidgetIndex, delay);
vm.dashboardContainer.highlightWidget(widget, delay);
}, delayOffset, false);
}
}
}
function widgetMouseDown($event, widget) {
if (vm.isEdit && !vm.isEditingWidget) {
vm.dashboardContainer.selectWidget(widget, 0);
}
}
function widgetClicked($event, widget) {
if (vm.isEditingWidget) {
editWidget($event, widget);
}
}
function initHotKeys() {
$translate(['action.copy', 'action.paste', 'action.delete']).then(function (translations) {
hotkeys.bindTo($scope)
.add({
combo: 'ctrl+c',
description: translations['action.copy'],
allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
callback: function (event) {
if (vm.isEdit && !vm.isEditingWidget && !vm.widgetEditMode) {
var widget = vm.dashboardContainer.getSelectedWidget();
if (widget) {
event.preventDefault();
copyWidget(event, widget);
}
}
}
})
.add({
combo: 'ctrl+v',
description: translations['action.paste'],
allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
callback: function (event) {
if (vm.isEdit && !vm.isEditingWidget && !vm.widgetEditMode) {
if (itembuffer.hasWidget()) {
event.preventDefault();
pasteWidget(event);
}
}
}
})
.add({
combo: 'ctrl+x',
description: translations['action.delete'],
allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
callback: function (event) {
if (vm.isEdit && !vm.isEditingWidget && !vm.widgetEditMode) {
var widget = vm.dashboardContainer.getSelectedWidget();
if (widget) {
event.preventDefault();
removeWidget(event, widget);
}
}
}
});
});
}
function prepareDashboardContextMenu() {
var dashboardContextActions = [];
if (vm.isEdit && !vm.isEditingWidget && !vm.widgetEditMode) {
dashboardContextActions.push(
{
action: openDashboardSettings,
enabled: true,
value: "dashboard.settings",
icon: "settings"
}
);
dashboardContextActions.push(
{
action: openDeviceAliases,
enabled: true,
value: "device.aliases",
icon: "devices_other"
}
);
dashboardContextActions.push(
{
action: pasteWidget,
enabled: itembuffer.hasWidget(),
value: "action.paste",
icon: "content_paste",
shortcut: "M-V"
}
);
}
return dashboardContextActions;
}
function pasteWidget($event) {
var pos = vm.dashboardContainer.getEventGridPosition($event);
itembuffer.pasteWidget(vm.dashboard, pos);
}
function prepareWidgetContextMenu() {
var widgetContextActions = [];
if (vm.isEdit && !vm.isEditingWidget) {
widgetContextActions.push(
{
action: editWidget,
enabled: true,
value: "action.edit",
icon: "edit"
}
);
if (!vm.widgetEditMode) {
widgetContextActions.push(
{
action: copyWidget,
enabled: true,
value: "action.copy",
icon: "content_copy",
shortcut: "M-C"
}
);
widgetContextActions.push(
{
action: removeWidget,
enabled: true,
value: "action.delete",
icon: "clear",
shortcut: "M-X"
}
);
}
}
return widgetContextActions;
}
function copyWidget($event, widget) {
var aliasesInfo = {
datasourceAliases: {},
targetDeviceAliases: {}
};
var originalColumns = 24;
if (vm.dashboard.configuration.gridSettings &&
vm.dashboard.configuration.gridSettings.columns) {
originalColumns = vm.dashboard.configuration.gridSettings.columns;
}
if (widget.config && vm.dashboard.configuration
&& vm.dashboard.configuration.deviceAliases) {
var deviceAlias;
if (widget.config.datasources) {
for (var i=0;i<widget.config.datasources.length;i++) {
var datasource = widget.config.datasources[i];
if (datasource.type === types.datasourceType.device && datasource.deviceAliasId) {
deviceAlias = vm.dashboard.configuration.deviceAliases[datasource.deviceAliasId];
if (deviceAlias) {
aliasesInfo.datasourceAliases[i] = {
aliasName: deviceAlias.alias,
deviceId: deviceAlias.deviceId
}
}
}
}
}
if (widget.config.targetDeviceAliasIds) {
for (i=0;i<widget.config.targetDeviceAliasIds.length;i++) {
var targetDeviceAliasId = widget.config.targetDeviceAliasIds[i];
if (targetDeviceAliasId) {
deviceAlias = vm.dashboard.configuration.deviceAliases[targetDeviceAliasId];
if (deviceAlias) {
aliasesInfo.targetDeviceAliases[i] = {
aliasName: deviceAlias.alias,
deviceId: deviceAlias.deviceId
}
}
}
}
}
}
itembuffer.copyWidget(widget, aliasesInfo, originalColumns);
}
function helpLinkIdForWidgetType() {
var link = 'widgetsConfig';
if (vm.editingWidget && vm.editingWidget.type) {
@ -322,6 +497,15 @@ export default function DashboardController(types, widgetService, userService,
return link;
}
function displayTitle() {
if (vm.dashboard && vm.dashboard.configuration.gridSettings &&
angular.isDefined(vm.dashboard.configuration.gridSettings.showTitle)) {
return vm.dashboard.configuration.gridSettings.showTitle;
} else {
return true;
}
}
function onRevertWidgetEdit(widgetForm) {
if (widgetForm.$dirty) {
widgetForm.$setPristine();
@ -331,7 +515,9 @@ export default function DashboardController(types, widgetService, userService,
function saveWidget(widgetForm) {
widgetForm.$setPristine();
vm.widgets[vm.editingWidgetIndex] = angular.copy(vm.editingWidget);
var widget = angular.copy(vm.editingWidget);
vm.widgets[vm.editingWidgetIndex] = widget;
vm.dashboardContainer.highlightWidget(widget, 0);
}
function onEditWidgetClosed() {
@ -421,8 +607,8 @@ export default function DashboardController(types, widgetService, userService,
});
}
function toggleDashboardEditMode() {
vm.isEdit = !vm.isEdit;
function setEditMode(isEdit, revert) {
vm.isEdit = isEdit;
if (vm.isEdit) {
if (vm.widgetEditMode) {
vm.prevWidgets = angular.copy(vm.widgets);
@ -433,14 +619,23 @@ export default function DashboardController(types, widgetService, userService,
if (vm.widgetEditMode) {
vm.widgets = vm.prevWidgets;
} else {
vm.dashboard = vm.prevDashboard;
vm.widgets = vm.dashboard.configuration.widgets;
if (vm.dashboardContainer) {
vm.dashboardContainer.resetHighlight();
}
if (revert) {
vm.dashboard = vm.prevDashboard;
vm.widgets = vm.dashboard.configuration.widgets;
}
}
}
}
function toggleDashboardEditMode() {
setEditMode(!vm.isEdit, true);
}
function saveDashboard() {
vm.isEdit = false;
setEditMode(false, false);
notifyDashboardUpdated();
}

8
ui/src/app/dashboard/dashboard.tpl.html

@ -51,7 +51,7 @@
</md-button>
</section>
<section ng-if="!vm.widgetEditMode" class="tb-dashboard-title" layout="row" layout-align="center center">
<h3 ng-show="!vm.isEdit">{{ vm.dashboard.title }}</h3>
<h3 ng-show="!vm.isEdit && vm.displayTitle()">{{ vm.dashboard.title }}</h3>
<md-input-container ng-show="vm.isEdit" class="md-block" style="height: 30px;">
<label translate>dashboard.title</label>
<input class="tb-dashboard-title" required name="title" ng-model="vm.dashboard.title">
@ -64,7 +64,7 @@
</md-button>
</section>
<div class="tb-absolute-fill"
ng-class="{ 'tb-padded' : !vm.widgetEditMode, 'tb-shrinked' : vm.isEditingWidget }">
ng-class="{ 'tb-padded' : !vm.widgetEditMode && (vm.isEdit || vm.displayTitle()), 'tb-shrinked' : vm.isEditingWidget }">
<tb-dashboard
dashboard-style="{'background-color': vm.dashboard.configuration.gridSettings.backgroundColor,
'background-image': 'url('+vm.dashboard.configuration.gridSettings.backgroundImageUrl+')',
@ -82,7 +82,11 @@
is-edit-action-enabled="vm.isEdit || vm.widgetEditMode"
is-remove-action-enabled="vm.isEdit && !vm.widgetEditMode"
on-edit-widget="vm.editWidget(event, widget)"
on-widget-mouse-down="vm.widgetMouseDown(event, widget)"
on-widget-clicked="vm.widgetClicked(event, widget)"
on-widget-context-menu="vm.widgetContextMenu(event, widget)"
prepare-dashboard-context-menu="vm.prepareDashboardContextMenu()"
prepare-widget-context-menu="vm.prepareWidgetContextMenu(widget)"
on-remove-widget="vm.removeWidget(event, widget)"
load-widgets="vm.loadDashboard()"
on-init="vm.dashboardInited(dashboard)"

2
ui/src/app/dashboard/index.js

@ -29,6 +29,7 @@ import thingsboardDashboard from '../components/dashboard.directive';
import thingsboardExpandFullscreen from '../components/expand-fullscreen.directive';
import thingsboardWidgetsBundleSelect from '../components/widgets-bundle-select.directive';
import thingsboardTypes from '../common/types.constant';
import thingsboardItemBuffer from '../services/item-buffer.service';
import DashboardRoutes from './dashboard.routes';
import DashboardsController from './dashboards.controller';
@ -45,6 +46,7 @@ export default angular.module('thingsboard.dashboard', [
uiRouter,
gridster.name,
thingsboardTypes,
thingsboardItemBuffer,
thingsboardGrid,
thingsboardApiWidget,
thingsboardApiUser,

81
ui/src/app/device/attribute/add-widget-to-dashboard-dialog.controller.js

@ -14,7 +14,7 @@
* limitations under the License.
*/
/*@ngInject*/
export default function AddWidgetToDashboardDialogController($scope, $mdDialog, $state, dashboardService, deviceId, deviceName, widget) {
export default function AddWidgetToDashboardDialogController($scope, $mdDialog, $state, itembuffer, dashboardService, deviceId, deviceName, widget) {
var vm = this;
@ -34,62 +34,20 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog,
function add() {
$scope.theForm.$setPristine();
var theDashboard;
var deviceAliases;
widget.col = 0;
widget.sizeX /= 2;
widget.sizeY /= 2;
if (vm.addToDashboardType === 0) {
theDashboard = vm.dashboard;
if (!theDashboard.configuration) {
theDashboard.configuration = {};
}
deviceAliases = theDashboard.configuration.deviceAliases;
if (!deviceAliases) {
deviceAliases = {};
theDashboard.configuration.deviceAliases = deviceAliases;
}
var newAliasId;
for (var aliasId in deviceAliases) {
if (deviceAliases[aliasId].deviceId === deviceId) {
newAliasId = aliasId;
break;
}
}
if (!newAliasId) {
var newAliasName = createDeviceAliasName(deviceAliases, deviceName);
newAliasId = 0;
for (aliasId in deviceAliases) {
newAliasId = Math.max(newAliasId, aliasId);
}
newAliasId++;
deviceAliases[newAliasId] = {alias: newAliasName, deviceId: deviceId};
}
widget.config.datasources[0].deviceAliasId = newAliasId;
if (!theDashboard.configuration.widgets) {
theDashboard.configuration.widgets = [];
}
var row = 0;
for (var w in theDashboard.configuration.widgets) {
var existingWidget = theDashboard.configuration.widgets[w];
var wRow = existingWidget.row ? existingWidget.row : 0;
var wSizeY = existingWidget.sizeY ? existingWidget.sizeY : 1;
var bottom = wRow + wSizeY;
row = Math.max(row, bottom);
}
widget.row = row;
theDashboard.configuration.widgets.push(widget);
} else {
theDashboard = vm.newDashboard;
deviceAliases = {};
deviceAliases['1'] = {alias: deviceName, deviceId: deviceId};
theDashboard.configuration = {};
theDashboard.configuration.widgets = [];
widget.row = 0;
theDashboard.configuration.widgets.push(widget);
theDashboard.configuration.deviceAliases = deviceAliases;
}
var aliasesInfo = {
datasourceAliases: {},
targetDeviceAliases: {}
};
aliasesInfo.datasourceAliases[0] = {
aliasName: deviceName,
deviceId: deviceId
};
theDashboard = itembuffer.addWidgetToDashboard(theDashboard, widget, aliasesInfo, 48, -1, -1);
dashboardService.saveDashboard(theDashboard).then(
function success(dashboard) {
$mdDialog.hide();
@ -98,25 +56,6 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog,
}
}
);
}
function createDeviceAliasName(deviceAliases, alias) {
var c = 0;
var newAlias = angular.copy(alias);
var unique = false;
while (!unique) {
unique = true;
for (var devAliasId in deviceAliases) {
var devAlias = deviceAliases[devAliasId];
if (newAlias === devAlias.alias) {
c++;
newAlias = alias + c;
unique = false;
}
}
}
return newAlias;
}
}

10
ui/src/app/device/attribute/attribute-table.directive.js

@ -239,6 +239,8 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
index: 0
}
scope.widgetsBundle = null;
scope.firstBundle = true;
scope.selectedWidgetsBundleAlias = types.systemBundleAlias.cards;
scope.deviceAliases = {};
scope.deviceAliases['1'] = {alias: scope.deviceName, deviceId: scope.deviceId};
@ -326,13 +328,6 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
}
}
});
widgetService.getWidgetsBundleByAlias(types.systemBundleAlias.cards).then(
function success(widgetsBundle) {
scope.firstBundle = true;
scope.widgetsBundle = widgetsBundle;
}
);
}
scope.exitWidgetMode = function() {
@ -344,6 +339,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
scope.widgetsIndexWatch();
scope.widgetsIndexWatch = null;
}
scope.selectedWidgetsBundleAlias = null;
scope.mode = 'default';
scope.getDeviceAttributes(true);
}

3
ui/src/app/device/attribute/attribute-table.tpl.html

@ -105,7 +105,8 @@
<tb-widgets-bundle-select flex-offset="5"
flex
ng-model="widgetsBundle"
select-first-bundle="false">
select-first-bundle="false"
select-bundle-alias="selectedWidgetsBundleAlias">
</tb-widgets-bundle-select>
</div>
<md-button ng-show="widgetsList.length > 0" class="md-accent md-hue-2 md-raised" ng-click="addWidgetToDashboard($event)">

191
ui/src/app/services/item-buffer.service.js

@ -0,0 +1,191 @@
/*
* Copyright © 2016 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import angularStorage from 'angular-storage';
export default angular.module('thingsboard.itembuffer', [angularStorage])
.factory('itembuffer', ItemBuffer)
.factory('bufferStore', function(store) {
var newStore = store.getNamespacedStore('tbBufferStore', null, null, false);
return newStore;
})
.name;
/*@ngInject*/
function ItemBuffer(bufferStore) {
const WIDGET_ITEM = "widget_item";
var service = {
copyWidget: copyWidget,
hasWidget: hasWidget,
pasteWidget: pasteWidget,
addWidgetToDashboard: addWidgetToDashboard
}
return service;
/**
aliasesInfo {
datasourceAliases: {
datasourceIndex: {
aliasName: "...",
deviceId: "..."
}
}
targetDeviceAliases: {
targetDeviceAliasIndex: {
aliasName: "...",
deviceId: "..."
}
}
....
}
**/
function copyWidget(widget, aliasesInfo, originalColumns) {
var widgetItem = {
widget: widget,
aliasesInfo: aliasesInfo,
originalColumns: originalColumns
}
bufferStore.set(WIDGET_ITEM, angular.toJson(widgetItem));
}
function hasWidget() {
return bufferStore.get(WIDGET_ITEM);
}
function pasteWidget(targetDasgboard, position) {
var widgetItemJson = bufferStore.get(WIDGET_ITEM);
if (widgetItemJson) {
var widgetItem = angular.fromJson(widgetItemJson);
var widget = widgetItem.widget;
var aliasesInfo = widgetItem.aliasesInfo;
var originalColumns = widgetItem.originalColumns;
var targetRow = -1;
var targetColumn = -1;
if (position) {
targetRow = position.row;
targetColumn = position.column;
}
addWidgetToDashboard(targetDasgboard, widget, aliasesInfo, originalColumns, targetRow, targetColumn);
}
}
function addWidgetToDashboard(dashboard, widget, aliasesInfo, originalColumns, row, column) {
var theDashboard;
if (dashboard) {
theDashboard = dashboard;
} else {
theDashboard = {};
}
if (!theDashboard.configuration) {
theDashboard.configuration = {};
}
if (!theDashboard.configuration.deviceAliases) {
theDashboard.configuration.deviceAliases = {};
}
updateAliases(theDashboard, widget, aliasesInfo);
if (!theDashboard.configuration.widgets) {
theDashboard.configuration.widgets = [];
}
var targetColumns = 24;
if (theDashboard.configuration.gridSettings &&
theDashboard.configuration.gridSettings.columns) {
targetColumns = theDashboard.configuration.gridSettings.columns;
}
if (targetColumns != originalColumns) {
var ratio = targetColumns / originalColumns;
widget.sizeX *= ratio;
widget.sizeY *= ratio;
}
if (row > -1 && column > - 1) {
widget.row = row;
widget.col = column;
} else {
row = 0;
for (var w in theDashboard.configuration.widgets) {
var existingWidget = theDashboard.configuration.widgets[w];
var wRow = existingWidget.row ? existingWidget.row : 0;
var wSizeY = existingWidget.sizeY ? existingWidget.sizeY : 1;
var bottom = wRow + wSizeY;
row = Math.max(row, bottom);
}
widget.row = row;
widget.col = 0;
}
theDashboard.configuration.widgets.push(widget);
return theDashboard;
}
function updateAliases(dashboard, widget, aliasesInfo) {
var deviceAliases = dashboard.configuration.deviceAliases;
var aliasInfo;
var newAliasId;
for (var datasourceIndex in aliasesInfo.datasourceAliases) {
aliasInfo = aliasesInfo.datasourceAliases[datasourceIndex];
newAliasId = getDeviceAliasId(deviceAliases, aliasInfo);
widget.config.datasources[datasourceIndex].deviceAliasId = newAliasId;
}
for (var targetDeviceAliasIndex in aliasesInfo.targetDeviceAliases) {
aliasInfo = aliasesInfo.targetDeviceAliases[targetDeviceAliasIndex];
newAliasId = getDeviceAliasId(deviceAliases, aliasInfo);
widget.config.targetDeviceAliasIds[targetDeviceAliasIndex] = newAliasId;
}
}
function getDeviceAliasId(deviceAliases, aliasInfo) {
var newAliasId;
for (var aliasId in deviceAliases) {
if (deviceAliases[aliasId].deviceId === aliasInfo.deviceId) {
newAliasId = aliasId;
break;
}
}
if (!newAliasId) {
var newAliasName = createDeviceAliasName(deviceAliases, aliasInfo.aliasName);
newAliasId = 0;
for (aliasId in deviceAliases) {
newAliasId = Math.max(newAliasId, aliasId);
}
newAliasId++;
deviceAliases[newAliasId] = {alias: newAliasName, deviceId: aliasInfo.deviceId};
}
return newAliasId;
}
function createDeviceAliasName(deviceAliases, alias) {
var c = 0;
var newAlias = angular.copy(alias);
var unique = false;
while (!unique) {
unique = true;
for (var devAliasId in deviceAliases) {
var devAlias = deviceAliases[devAliasId];
if (newAlias === devAlias.alias) {
c++;
newAlias = alias + c;
unique = false;
}
}
}
return newAlias;
}
}

7
ui/src/locale/en_US.json

@ -38,7 +38,9 @@
"create": "Create",
"drag": "Drag",
"refresh": "Refresh",
"undo": "Undo"
"undo": "Undo",
"copy": "Copy",
"paste": "Paste"
},
"admin": {
"general": "General",
@ -211,7 +213,8 @@
"vertical-margin": "Vertical margin",
"vertical-margin-required": "Vertical margin value is required.",
"min-vertical-margin-message": "Only 0 is allowed as minimum vertical margin value.",
"max-vertical-margin-message": "Only 50 is allowed as maximum vertical margin value."
"max-vertical-margin-message": "Only 50 is allowed as maximum vertical margin value.",
"display-title": "Display dashboard title"
},
"datakey": {
"settings": "Settings",

3
ui/src/scss/main.scss

@ -169,6 +169,9 @@ md-menu-item {
md-menu-item {
.md-button {
display: block;
.tb-alt-text {
float: right;
}
}
}

Loading…
Cancel
Save