Browse Source

Merge pull request #24 from thingsboard/feature/ui-improvements

Feature/ui improvements
pull/25/head
Andrew Shvayka 10 years ago
committed by GitHub
parent
commit
f78b8e55f1
  1. 7
      dao/src/main/resources/system-data.cql
  2. 2
      ui/src/app/app.js
  3. 108
      ui/src/app/components/dashboard.directive.js
  4. 2
      ui/src/app/components/dashboard.scss
  5. 144
      ui/src/app/components/dashboard.tpl.html
  6. 16
      ui/src/app/components/datasource-device.scss
  7. 58
      ui/src/app/components/datasource-device.tpl.html
  8. 9
      ui/src/app/components/datasource-func.scss
  9. 33
      ui/src/app/components/datasource-func.tpl.html
  10. 12
      ui/src/app/components/datasource.scss
  11. 8
      ui/src/app/components/datasource.tpl.html
  12. 13
      ui/src/app/components/details-sidenav.scss
  13. 5
      ui/src/app/components/details-sidenav.tpl.html
  14. 40
      ui/src/app/components/keyboard-shortcut.filter.js
  15. 51
      ui/src/app/components/mousepoint-menu.directive.js
  16. 2
      ui/src/app/components/tb-event-directives.js
  17. 4
      ui/src/app/components/widget-config.tpl.html
  18. 23
      ui/src/app/components/widgets-bundle-select.directive.js
  19. 4
      ui/src/app/components/widgets-bundle-select.scss
  20. 4
      ui/src/app/dashboard/dashboard-settings.controller.js
  21. 5
      ui/src/app/dashboard/dashboard-settings.tpl.html
  22. 217
      ui/src/app/dashboard/dashboard.controller.js
  23. 36
      ui/src/app/dashboard/dashboard.tpl.html
  24. 2
      ui/src/app/dashboard/index.js
  25. 81
      ui/src/app/device/attribute/add-widget-to-dashboard-dialog.controller.js
  26. 10
      ui/src/app/device/attribute/attribute-table.directive.js
  27. 3
      ui/src/app/device/attribute/attribute-table.tpl.html
  28. 2
      ui/src/app/layout/breadcrumb.tpl.html
  29. 5
      ui/src/app/layout/home.scss
  30. 4
      ui/src/app/layout/home.tpl.html
  31. 191
      ui/src/app/services/item-buffer.service.js
  32. 7
      ui/src/locale/en_US.json
  33. 3
      ui/src/scss/main.scss

7
dao/src/main/resources/system-data.cql

File diff suppressed because one or more lines are too long

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 thingsboardMenu from './services/menu.service';
import thingsboardUtils from './common/utils.service'; import thingsboardUtils from './common/utils.service';
import thingsboardTypes from './common/types.constant'; import thingsboardTypes from './common/types.constant';
import thingsboardKeyboardShortcut from './components/keyboard-shortcut.filter';
import thingsboardHelp from './help/help.directive'; import thingsboardHelp from './help/help.directive';
import thingsboardToast from './services/toast'; import thingsboardToast from './services/toast';
import thingsboardHome from './layout'; import thingsboardHome from './layout';
@ -95,6 +96,7 @@ angular.module('thingsboard', [
thingsboardMenu, thingsboardMenu,
thingsboardUtils, thingsboardUtils,
thingsboardTypes, thingsboardTypes,
thingsboardKeyboardShortcut,
thingsboardHelp, thingsboardHelp,
thingsboardToast, thingsboardToast,
thingsboardHome, thingsboardHome,

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

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

2
ui/src/app/components/dashboard.scss

@ -20,6 +20,7 @@ div.tb-widget {
height: 100%; height: 100%;
margin: 0; margin: 0;
overflow: hidden; overflow: hidden;
outline: none;
@include transition(all .2s ease-in-out); @include transition(all .2s ease-in-out);
.tb-widget-title { .tb-widget-title {
@ -91,6 +92,7 @@ md-content.tb-dashboard-content {
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
outline: none;
} }
.tb-widget-error-container { .tb-widget-error-container {

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

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

16
ui/src/app/components/datasource-device.scss

@ -13,6 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
@import '../../scss/constants';
.tb-device-alias-autocomplete, .tb-timeseries-datakey-autocomplete, .tb-attribute-datakey-autocomplete { .tb-device-alias-autocomplete, .tb-timeseries-datakey-autocomplete, .tb-attribute-datakey-autocomplete {
.tb-not-found { .tb-not-found {
display: block; display: block;
@ -27,3 +30,16 @@
white-space: normal !important; white-space: normal !important;
} }
} }
tb-datasource-device {
@media (min-width: $layout-breakpoint-gt-sm) {
padding-left: 4px;
padding-right: 4px;
}
tb-device-alias-select {
@media (min-width: $layout-breakpoint-gt-sm) {
width: 200px;
max-width: 200px;
}
}
}

58
ui/src/app/components/datasource-device.tpl.html

@ -15,16 +15,16 @@
limitations under the License. limitations under the License.
--> -->
<section flex layout='row' layout-align="start center"> <section flex layout='column' layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
<tb-device-alias-select flex="40" <tb-device-alias-select
tb-required="true" tb-required="true"
device-aliases="deviceAliases" device-aliases="deviceAliases"
ng-model="deviceAlias" ng-model="deviceAlias"
on-create-device-alias="onCreateDeviceAlias({event: event, alias: alias})"> on-create-device-alias="onCreateDeviceAlias({event: event, alias: alias})">
</tb-device-alias-select> </tb-device-alias-select>
<section flex="120" layout='column'> <section flex layout='column'>
<section flex layout='row' layout-align="start center"> <section flex layout='column' layout-align="center" style="padding-left: 4px;">
<md-chips flex style="padding-left: 4px;" <md-chips flex
id="timeseries_datakey_chips" id="timeseries_datakey_chips"
ng-required="true" ng-required="true"
ng-model="timeseriesDataKeys" md-autocomplete-snap ng-model="timeseriesDataKeys" md-autocomplete-snap
@ -56,14 +56,19 @@
</md-not-found> </md-not-found>
</md-autocomplete> </md-autocomplete>
<md-chip-template> <md-chip-template>
<div layout="row" layout-align="start center"> <div layout="row" layout-align="start center" class="tb-attribute-chip">
<div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;"> <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
<div class="tb-color-result" ng-style="{background: $chip.color}"></div> <div class="tb-color-result" ng-style="{background: $chip.color}"></div>
</div> </div>
<div> <div layout="row" flex>
{{$chip.label}}: <div class="tb-chip-label">
<strong ng-if="!$chip.postFuncBody">{{$chip.name}}</strong> {{$chip.label}}
<strong ng-if="$chip.postFuncBody">f({{$chip.name}})</strong> </div>
<div class="tb-chip-separator">: </div>
<div class="tb-chip-label">
<strong ng-if="!$chip.postFuncBody">{{$chip.name}}</strong>
<strong ng-if="$chip.postFuncBody">f({{$chip.name}})</strong>
</div>
</div> </div>
<md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32"> <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">
<md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon> <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>
@ -71,7 +76,7 @@
</div> </div>
</md-chip-template> </md-chip-template>
</md-chips> </md-chips>
<md-chips flex ng-if="widgetType === types.widgetType.latest.value" style="padding-left: 4px;" <md-chips flex ng-if="widgetType === types.widgetType.latest.value"
id="attribute_datakey_chips" id="attribute_datakey_chips"
ng-required="true" ng-required="true"
ng-model="attributeDataKeys" md-autocomplete-snap ng-model="attributeDataKeys" md-autocomplete-snap
@ -103,19 +108,24 @@
</md-not-found> </md-not-found>
</md-autocomplete> </md-autocomplete>
<md-chip-template> <md-chip-template>
<div layout="row" layout-align="start center"> <div layout="row" layout-align="start center" class="tb-attribute-chip">
<div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;"> <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
<div class="tb-color-result" ng-style="{background: $chip.color}"></div> <div class="tb-color-result" ng-style="{background: $chip.color}"></div>
</div> </div>
<div> <div layout="row" flex>
{{$chip.label}}: <div class="tb-chip-label">
<strong ng-if="!$chip.postFuncBody">{{$chip.name}}</strong> {{$chip.label}}
<strong ng-if="$chip.postFuncBody">f({{$chip.name}})</strong> </div>
</div> <div class="tb-chip-separator">: </div>
<md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32"> <div class="tb-chip-label">
<md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon> <strong ng-if="!$chip.postFuncBody">{{$chip.name}}</strong>
</md-button> <strong ng-if="$chip.postFuncBody">f({{$chip.name}})</strong>
</div> </div>
</div>
<md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">
<md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>
</md-button>
</div>
</md-chip-template> </md-chip-template>
</md-chips> </md-chips>
</section> </section>

9
ui/src/app/components/datasource-func.scss

@ -13,6 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
@import '../../scss/constants';
.tb-func-datakey-autocomplete { .tb-func-datakey-autocomplete {
.tb-not-found { .tb-not-found {
display: block; display: block;
@ -27,3 +30,9 @@
white-space: normal !important; white-space: normal !important;
} }
} }
tb-datasource-func {
@media (min-width: $layout-breakpoint-gt-sm) {
padding-left: 8px;
}
}

33
ui/src/app/components/datasource-func.tpl.html

@ -15,8 +15,8 @@
limitations under the License. limitations under the License.
--> -->
<section flex layout='column'> <section flex layout='column' style="padding-left: 4px;">
<md-chips flex style="padding-left: 4px;" <md-chips flex
id="function_datakey_chips" id="function_datakey_chips"
ng-required="true" ng-required="true"
ng-model="funcDataKeys" md-autocomplete-snap ng-model="funcDataKeys" md-autocomplete-snap
@ -48,18 +48,23 @@
</md-not-found> </md-not-found>
</md-autocomplete> </md-autocomplete>
<md-chip-template> <md-chip-template>
<div layout="row" layout-align="start center"> <div layout="row" layout-align="start center" class="tb-attribute-chip">
<div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;"> <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
<div class="tb-color-result" ng-style="{background: $chip.color}"></div> <div class="tb-color-result" ng-style="{background: $chip.color}"></div>
</div> </div>
<div> <div layout="row" flex>
{{$chip.label}}: <div class="tb-chip-label">
<strong>{{$chip.name}}</strong> {{$chip.label}}
</div> </div>
<md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32"> <div class="tb-chip-separator">: </div>
<md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon> <div class="tb-chip-label">
</md-button> <strong>{{$chip.name}}</strong>
</div> </div>
</div>
<md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">
<md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>
</md-button>
</div>
</md-chip-template> </md-chip-template>
</md-chips> </md-chips>
<div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert"> <div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert">

12
ui/src/app/components/datasource.scss

@ -38,6 +38,7 @@
.tb-color-preview { .tb-color-preview {
content: ''; content: '';
min-width: 24px;
width: 24px; width: 24px;
height: 24px; height: 24px;
border: 2px solid #fff; border: 2px solid #fff;
@ -52,3 +53,14 @@
height: 100%; height: 100%;
} }
} }
.tb-attribute-chip {
.tb-chip-label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tb-chip-separator {
white-space: pre;
}
}

8
ui/src/app/components/datasource.tpl.html

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
--> -->
<section flex layout='row' layout-align="start center" class="tb-datasource"> <section flex layout='column' layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center" class="tb-datasource">
<md-input-container style="min-width: 110px;"> <md-input-container style="min-width: 110px;">
<md-select placeholder="{{ 'datasource.type' | translate }}" required id="datasourceType" ng-model="model.type"> <md-select placeholder="{{ 'datasource.type' | translate }}" required id="datasourceType" ng-model="model.type">
<md-option ng-repeat="datasourceType in datasourceTypes" value="{{datasourceType}}"> <md-option ng-repeat="datasourceType in datasourceTypes" value="{{datasourceType}}">
@ -23,15 +23,15 @@
</md-option> </md-option>
</md-select> </md-select>
</md-input-container> </md-input-container>
<section flex layout='row' layout-align="start center" class="datasource" ng-switch on="model.type"> <section flex class="datasource" ng-switch on="model.type">
<tb-datasource-func flex style="padding-left: 8px;" <tb-datasource-func flex
ng-switch-default ng-switch-default
ng-model="model" ng-model="model"
datakey-settings-schema="datakeySettingsSchema" datakey-settings-schema="datakeySettingsSchema"
ng-required="model.type === types.datasourceType.function" ng-required="model.type === types.datasourceType.function"
generate-data-key="generateDataKey({chip: chip, type: type})"> generate-data-key="generateDataKey({chip: chip, type: type})">
</tb-datasource-func> </tb-datasource-func>
<tb-datasource-device flex style="padding-left: 4px; padding-right: 4px;" <tb-datasource-device flex
ng-model="model" ng-model="model"
datakey-settings-schema="datakeySettingsSchema" datakey-settings-schema="datakeySettingsSchema"
ng-switch-when="device" ng-switch-when="device"

13
ui/src/app/components/details-sidenav.scss

@ -20,15 +20,28 @@
font-weight: 400; font-weight: 400;
text-transform: uppercase; text-transform: uppercase;
margin: 20px 8px 0 0; margin: 20px 8px 0 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: inherit;
} }
.tb-details-subtitle { .tb-details-subtitle {
font-size: 1.000rem; font-size: 1.000rem;
margin: 10px 0; margin: 10px 0;
opacity: 0.8; opacity: 0.8;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: inherit;
} }
md-sidenav.tb-sidenav-details { md-sidenav.tb-sidenav-details {
.md-toolbar-tools {
min-height: 100px;
max-height: 120px;
height: 100%;
}
width: 100% !important; width: 100% !important;
max-width: 100% !important; max-width: 100% !important;
z-index: 59 !important; z-index: 59 !important;

5
ui/src/app/components/details-sidenav.tpl.html

@ -22,13 +22,12 @@
layout="column"> layout="column">
<header> <header>
<md-toolbar class="md-theme-light" ng-style="{'height':headerHeightPx+'px'}"> <md-toolbar class="md-theme-light" ng-style="{'height':headerHeightPx+'px'}">
<div class="md-toolbar-tools"> <div class="md-toolbar-tools" layout="row">
<div class="md-toolbar-tools" layout="column" layout-align="start start"> <div flex class="md-toolbar-tools" layout="column" layout-align="start start">
<span class="tb-details-title">{{headerTitle}}</span> <span class="tb-details-title">{{headerTitle}}</span>
<span class="tb-details-subtitle">{{headerSubtitle}}</span> <span class="tb-details-subtitle">{{headerSubtitle}}</span>
<span style="width: 100%;" ng-transclude="headerPane"></span> <span style="width: 100%;" ng-transclude="headerPane"></span>
</div> </div>
<span flex></span>
<div ng-transclude="detailsButtons"></div> <div ng-transclude="detailsButtons"></div>
<md-button class="md-icon-button" ng-click="closeDetails()"> <md-button class="md-icon-button" ng-click="closeDetails()">
<md-icon aria-label="close" class="material-icons">close</md-icon> <md-icon aria-label="close" class="material-icons">close</md-icon>

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 = {}; var tbEventDirectives = {};
angular.forEach( 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) { function(eventName) {
var directiveName = directiveNormalize('tb-' + eventName); var directiveName = directiveNormalize('tb-' + eventName);
tbEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse) { tbEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse) {

4
ui/src/app/components/widget-config.tpl.html

@ -25,7 +25,7 @@
<input name="title" ng-model="title"> <input name="title" ng-model="title">
</md-input-container> </md-input-container>
<span translate>widget-config.general-settings</span> <span translate>widget-config.general-settings</span>
<div layout="row" layout-align="start center"> <div layout='column' layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
<div layout="row" layout-padding> <div layout="row" layout-padding>
<md-checkbox flex aria-label="{{ 'widget-config.display-title' | translate }}" <md-checkbox flex aria-label="{{ 'widget-config.display-title' | translate }}"
ng-model="showTitle">{{ 'widget-config.display-title' | translate }} ng-model="showTitle">{{ 'widget-config.display-title' | translate }}
@ -80,7 +80,7 @@
<div flex layout="row" layout-align="start center" <div flex layout="row" layout-align="start center"
style="padding: 0 0 0 10px; margin: 5px;"> style="padding: 0 0 0 10px; margin: 5px;">
<span translate style="min-width: 110px;">widget-config.datasource-type</span> <span translate style="min-width: 110px;">widget-config.datasource-type</span>
<span translate flex <span hide show-gt-sm translate flex
style="padding-left: 10px;">widget-config.datasource-parameters</span> style="padding-left: 10px;">widget-config.datasource-parameters</span>
<span style="min-width: 40px;"></span> <span style="min-width: 40px;"></span>
</div> </div>

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

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

4
ui/src/app/components/widgets-bundle-select.scss

@ -35,10 +35,12 @@ tb-widgets-bundle-select {
tb-widgets-bundle-select, .tb-widgets-bundle-select { tb-widgets-bundle-select, .tb-widgets-bundle-select {
.md-text { .md-text {
display: block;
width: 100%; width: 100%;
} }
.tb-bundle-item { .tb-bundle-item {
display: block; display: inline-block;
width: 100%;
span { span {
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;

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

@ -28,6 +28,10 @@ export default function DashboardSettingsController($scope, $mdDialog, gridSetti
vm.gridSettings = gridSettings || {}; 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.backgroundColor = vm.gridSettings.backgroundColor || 'rgba(0,0,0,0)';
vm.gridSettings.columns = vm.gridSettings.columns || 24; vm.gridSettings.columns = vm.gridSettings.columns || 24;
vm.gridSettings.margins = vm.gridSettings.margins || [10, 10]; vm.gridSettings.margins = vm.gridSettings.margins || [10, 10];

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

@ -31,6 +31,11 @@
<md-dialog-content> <md-dialog-content>
<div class="md-dialog-content"> <div class="md-dialog-content">
<fieldset ng-disabled="loading"> <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"> <md-input-container class="md-block">
<label translate>dashboard.columns-count</label> <label translate>dashboard.columns-count</label>
<input required type="number" step="any" name="columns" ng-model="vm.gridSettings.columns" min="10" <input required type="number" step="any" name="columns" ng-model="vm.gridSettings.columns" min="10"

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

@ -23,7 +23,7 @@ import addWidgetTemplate from './add-widget.tpl.html';
/*@ngInject*/ /*@ngInject*/
export default function DashboardController(types, widgetService, userService, export default function DashboardController(types, widgetService, userService,
dashboardService, $window, $rootScope, dashboardService, itembuffer, hotkeys, $window, $rootScope,
$scope, $state, $stateParams, $mdDialog, $timeout, $document, $q, $translate, $filter) { $scope, $state, $stateParams, $mdDialog, $timeout, $document, $q, $translate, $filter) {
var user = userService.getCurrentUser(); var user = userService.getCurrentUser();
@ -48,7 +48,10 @@ export default function DashboardController(types, widgetService, userService,
vm.addWidgetFromType = addWidgetFromType; vm.addWidgetFromType = addWidgetFromType;
vm.dashboardInited = dashboardInited; vm.dashboardInited = dashboardInited;
vm.dashboardInitFailed = dashboardInitFailed; vm.dashboardInitFailed = dashboardInitFailed;
vm.widgetMouseDown = widgetMouseDown;
vm.widgetClicked = widgetClicked; vm.widgetClicked = widgetClicked;
vm.prepareDashboardContextMenu = prepareDashboardContextMenu;
vm.prepareWidgetContextMenu = prepareWidgetContextMenu;
vm.editWidget = editWidget; vm.editWidget = editWidget;
vm.isTenantAdmin = isTenantAdmin; vm.isTenantAdmin = isTenantAdmin;
vm.loadDashboard = loadDashboard; vm.loadDashboard = loadDashboard;
@ -63,6 +66,7 @@ export default function DashboardController(types, widgetService, userService,
vm.toggleDashboardEditMode = toggleDashboardEditMode; vm.toggleDashboardEditMode = toggleDashboardEditMode;
vm.onRevertWidgetEdit = onRevertWidgetEdit; vm.onRevertWidgetEdit = onRevertWidgetEdit;
vm.helpLinkIdForWidgetType = helpLinkIdForWidgetType; vm.helpLinkIdForWidgetType = helpLinkIdForWidgetType;
vm.displayTitle = displayTitle;
vm.widgetsBundle; vm.widgetsBundle;
@ -194,6 +198,7 @@ export default function DashboardController(types, widgetService, userService,
function dashboardInited(dashboard) { function dashboardInited(dashboard) {
vm.dashboardContainer = dashboard; vm.dashboardContainer = dashboard;
initHotKeys();
} }
function isTenantAdmin() { function isTenantAdmin() {
@ -289,18 +294,194 @@ export default function DashboardController(types, widgetService, userService,
var delayOffset = transition ? 350 : 0; var delayOffset = transition ? 350 : 0;
var delay = transition ? 400 : 300; var delay = transition ? 400 : 300;
$timeout(function () { $timeout(function () {
vm.dashboardContainer.highlightWidget(vm.editingWidgetIndex, delay); vm.dashboardContainer.highlightWidget(widget, delay);
}, delayOffset, false); }, delayOffset, false);
} }
} }
} }
function widgetMouseDown($event, widget) {
if (vm.isEdit && !vm.isEditingWidget) {
vm.dashboardContainer.selectWidget(widget, 0);
}
}
function widgetClicked($event, widget) { function widgetClicked($event, widget) {
if (vm.isEditingWidget) { if (vm.isEditingWidget) {
editWidget($event, widget); editWidget($event, widget);
} }
} }
function isHotKeyAllowed(event) {
var target = event.target || event.srcElement;
var scope = angular.element(target).scope();
return scope && scope.$parent !== $rootScope;
}
function initHotKeys() {
$translate(['action.copy', 'action.paste', 'action.delete']).then(function (translations) {
hotkeys.bindTo($scope)
.add({
combo: 'ctrl+c',
description: translations['action.copy'],
callback: function (event) {
if (isHotKeyAllowed(event) &&
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'],
callback: function (event) {
if (isHotKeyAllowed(event) &&
vm.isEdit && !vm.isEditingWidget && !vm.widgetEditMode) {
if (itembuffer.hasWidget()) {
event.preventDefault();
pasteWidget(event);
}
}
}
})
.add({
combo: 'ctrl+x',
description: translations['action.delete'],
callback: function (event) {
if (isHotKeyAllowed(event) &&
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() { function helpLinkIdForWidgetType() {
var link = 'widgetsConfig'; var link = 'widgetsConfig';
if (vm.editingWidget && vm.editingWidget.type) { if (vm.editingWidget && vm.editingWidget.type) {
@ -322,6 +503,15 @@ export default function DashboardController(types, widgetService, userService,
return link; 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) { function onRevertWidgetEdit(widgetForm) {
if (widgetForm.$dirty) { if (widgetForm.$dirty) {
widgetForm.$setPristine(); widgetForm.$setPristine();
@ -331,7 +521,9 @@ export default function DashboardController(types, widgetService, userService,
function saveWidget(widgetForm) { function saveWidget(widgetForm) {
widgetForm.$setPristine(); 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() { function onEditWidgetClosed() {
@ -421,8 +613,8 @@ export default function DashboardController(types, widgetService, userService,
}); });
} }
function toggleDashboardEditMode() { function setEditMode(isEdit, revert) {
vm.isEdit = !vm.isEdit; vm.isEdit = isEdit;
if (vm.isEdit) { if (vm.isEdit) {
if (vm.widgetEditMode) { if (vm.widgetEditMode) {
vm.prevWidgets = angular.copy(vm.widgets); vm.prevWidgets = angular.copy(vm.widgets);
@ -433,14 +625,23 @@ export default function DashboardController(types, widgetService, userService,
if (vm.widgetEditMode) { if (vm.widgetEditMode) {
vm.widgets = vm.prevWidgets; vm.widgets = vm.prevWidgets;
} else { } else {
vm.dashboard = vm.prevDashboard; if (vm.dashboardContainer) {
vm.widgets = vm.dashboard.configuration.widgets; vm.dashboardContainer.resetHighlight();
}
if (revert) {
vm.dashboard = vm.prevDashboard;
vm.widgets = vm.dashboard.configuration.widgets;
}
} }
} }
} }
function toggleDashboardEditMode() {
setEditMode(!vm.isEdit, true);
}
function saveDashboard() { function saveDashboard() {
vm.isEdit = false; setEditMode(false, false);
notifyDashboardUpdated(); notifyDashboardUpdated();
} }

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

@ -16,7 +16,7 @@
--> -->
<md-content flex tb-expand-fullscreen="vm.widgetEditMode" hide-expand-button="vm.widgetEditMode"> <md-content flex tb-expand-fullscreen="vm.widgetEditMode" hide-expand-button="vm.widgetEditMode">
<section ng-show="!vm.isAddingWidget && !loading && !vm.widgetEditMode" layout="row" layout-wrap <!--section ng-show="!vm.isAddingWidget && !loading && !vm.widgetEditMode" layout="row" layout-wrap
class="tb-header-buttons tb-top-header-buttons md-fab" ng-style="{'right': '50px'}"> class="tb-header-buttons tb-top-header-buttons md-fab" ng-style="{'right': '50px'}">
<md-button ng-if="vm.isTenantAdmin()" ng-show="vm.isEdit" ng-disabled="loading" <md-button ng-if="vm.isTenantAdmin()" ng-show="vm.isEdit" ng-disabled="loading"
class="tb-btn-header md-accent md-hue-2 md-fab md-fab-bottom-right" class="tb-btn-header md-accent md-hue-2 md-fab md-fab-bottom-right"
@ -37,7 +37,7 @@
<ng-md-icon icon="{{vm.isEdit ? 'close' : 'edit'}}" <ng-md-icon icon="{{vm.isEdit ? 'close' : 'edit'}}"
options='{"easing": "circ-in-out", "duration": 375, "rotation": "none"}'></ng-md-icon> options='{"easing": "circ-in-out", "duration": 375, "rotation": "none"}'></ng-md-icon>
</md-button> </md-button>
</section> </section-->
<section ng-show="!loading && vm.noData()" layout-align="center center" <section ng-show="!loading && vm.noData()" layout-align="center center"
ng-class="{'tb-padded' : !vm.widgetEditMode}" ng-class="{'tb-padded' : !vm.widgetEditMode}"
style="text-transform: uppercase; display: flex; z-index: 1;" style="text-transform: uppercase; display: flex; z-index: 1;"
@ -51,7 +51,7 @@
</md-button> </md-button>
</section> </section>
<section ng-if="!vm.widgetEditMode" class="tb-dashboard-title" layout="row" layout-align="center center"> <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;"> <md-input-container ng-show="vm.isEdit" class="md-block" style="height: 30px;">
<label translate>dashboard.title</label> <label translate>dashboard.title</label>
<input class="tb-dashboard-title" required name="title" ng-model="vm.dashboard.title"> <input class="tb-dashboard-title" required name="title" ng-model="vm.dashboard.title">
@ -64,7 +64,7 @@
</md-button> </md-button>
</section> </section>
<div class="tb-absolute-fill" <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 <tb-dashboard
dashboard-style="{'background-color': vm.dashboard.configuration.gridSettings.backgroundColor, dashboard-style="{'background-color': vm.dashboard.configuration.gridSettings.backgroundColor,
'background-image': 'url('+vm.dashboard.configuration.gridSettings.backgroundImageUrl+')', 'background-image': 'url('+vm.dashboard.configuration.gridSettings.backgroundImageUrl+')',
@ -82,7 +82,11 @@
is-edit-action-enabled="vm.isEdit || vm.widgetEditMode" is-edit-action-enabled="vm.isEdit || vm.widgetEditMode"
is-remove-action-enabled="vm.isEdit && !vm.widgetEditMode" is-remove-action-enabled="vm.isEdit && !vm.widgetEditMode"
on-edit-widget="vm.editWidget(event, widget)" on-edit-widget="vm.editWidget(event, widget)"
on-widget-mouse-down="vm.widgetMouseDown(event, widget)"
on-widget-clicked="vm.widgetClicked(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)" on-remove-widget="vm.removeWidget(event, widget)"
load-widgets="vm.loadDashboard()" load-widgets="vm.loadDashboard()"
on-init="vm.dashboardInited(dashboard)" on-init="vm.dashboardInited(dashboard)"
@ -176,8 +180,8 @@
</div> </div>
</tb-details-sidenav> </tb-details-sidenav>
<!-- </section> --> <!-- </section> -->
<section layout="row" layout-wrap class="tb-footer-buttons md-fab "> <section layout="row" layout-wrap class="tb-footer-buttons md-fab">
<md-button ng-disabled="loading" ng-if="!vm.isAddingWidget && vm.isEdit && !vm.widgetEditMode" <md-button ng-disabled="loading" ng-show="!vm.isAddingWidget && vm.isEdit && !vm.widgetEditMode"
class="tb-btn-footer md-accent md-hue-2 md-fab" ng-click="vm.addWidget($event)" class="tb-btn-footer md-accent md-hue-2 md-fab" ng-click="vm.addWidget($event)"
aria-label="{{ 'dashboard.add-widget' | translate }}"> aria-label="{{ 'dashboard.add-widget' | translate }}">
<md-tooltip md-direction="top"> <md-tooltip md-direction="top">
@ -185,5 +189,25 @@
</md-tooltip> </md-tooltip>
<ng-md-icon icon="add"></ng-md-icon> <ng-md-icon icon="add"></ng-md-icon>
</md-button> </md-button>
<md-button ng-if="vm.isTenantAdmin()" ng-show="vm.isEdit && !vm.isAddingWidget && !loading && !vm.widgetEditMode" ng-disabled="loading"
class="tb-btn-footer md-accent md-hue-2 md-fab"
aria-label="{{ 'action.apply' | translate }}"
ng-click="vm.saveDashboard()">
<md-tooltip md-direction="top">
{{ 'action.apply-changes' | translate }}
</md-tooltip>
<ng-md-icon icon="done"></ng-md-icon>
</md-button>
<md-button ng-show="!vm.isAddingWidget && !loading && !vm.widgetEditMode"
ng-if="vm.isTenantAdmin()" ng-disabled="loading"
class="tb-btn-footer md-accent md-hue-2 md-fab"
aria-label="{{ 'action.edit-mode' | translate }}"
ng-click="vm.toggleDashboardEditMode()">
<md-tooltip md-direction="top">
{{ (vm.isEdit ? 'action.decline-changes' : 'action.enter-edit-mode') | translate }}
</md-tooltip>
<ng-md-icon icon="{{vm.isEdit ? 'close' : 'edit'}}"
options='{"easing": "circ-in-out", "duration": 375, "rotation": "none"}'></ng-md-icon>
</md-button>
</section> </section>
</md-content> </md-content>

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

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

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
/*@ngInject*/ /*@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; var vm = this;
@ -34,62 +34,20 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog,
function add() { function add() {
$scope.theForm.$setPristine(); $scope.theForm.$setPristine();
var theDashboard; var theDashboard;
var deviceAliases;
widget.col = 0;
widget.sizeX /= 2;
widget.sizeY /= 2;
if (vm.addToDashboardType === 0) { if (vm.addToDashboardType === 0) {
theDashboard = vm.dashboard; 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 { } else {
theDashboard = vm.newDashboard; 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( dashboardService.saveDashboard(theDashboard).then(
function success(dashboard) { function success(dashboard) {
$mdDialog.hide(); $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 index: 0
} }
scope.widgetsBundle = null; scope.widgetsBundle = null;
scope.firstBundle = true;
scope.selectedWidgetsBundleAlias = types.systemBundleAlias.cards;
scope.deviceAliases = {}; scope.deviceAliases = {};
scope.deviceAliases['1'] = {alias: scope.deviceName, deviceId: scope.deviceId}; 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() { scope.exitWidgetMode = function() {
@ -344,6 +339,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
scope.widgetsIndexWatch(); scope.widgetsIndexWatch();
scope.widgetsIndexWatch = null; scope.widgetsIndexWatch = null;
} }
scope.selectedWidgetsBundleAlias = null;
scope.mode = 'default'; scope.mode = 'default';
scope.getDeviceAttributes(true); scope.getDeviceAttributes(true);
} }

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

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

2
ui/src/app/layout/breadcrumb.tpl.html

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
--> -->
<div class="tb-breadcrumb"> <div flex class="tb-breadcrumb" layout="row">
<h1 flex hide-gt-sm>{{ steps[steps.length-1].ncyBreadcrumbLabel | breadcrumbLabel }}</h1> <h1 flex hide-gt-sm>{{ steps[steps.length-1].ncyBreadcrumbLabel | breadcrumbLabel }}</h1>
<span hide-xs hide-sm ng-repeat="step in steps" ng-switch="$last || !!step.abstract"> <span hide-xs hide-sm ng-repeat="step in steps" ng-switch="$last || !!step.abstract">
<a ng-switch-when="false" href="{{step.ncyBreadcrumbLink}}"> <a ng-switch-when="false" href="{{step.ncyBreadcrumbLink}}">

5
ui/src/app/layout/home.scss

@ -29,6 +29,11 @@
.tb-breadcrumb { .tb-breadcrumb {
font-size: 18px !important; font-size: 18px !important;
font-weight: 400 !important; font-weight: 400 !important;
h1, a, span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
a { a {
border: none; border: none;
opacity: 0.75; opacity: 0.75;

4
ui/src/app/layout/home.tpl.html

@ -39,7 +39,7 @@
<div flex layout="column" tabIndex="-1" role="main"> <div flex layout="column" tabIndex="-1" role="main">
<md-toolbar class="md-whiteframe-z1 tb-primary-toolbar" ng-class="{'md-hue-1': vm.displaySearchMode()}"> <md-toolbar class="md-whiteframe-z1 tb-primary-toolbar" ng-class="{'md-hue-1': vm.displaySearchMode()}">
<div flex class="md-toolbar-tools"> <div layout="row" flex class="md-toolbar-tools">
<md-button id="main" hide-gt-sm <md-button id="main" hide-gt-sm
class="md-icon-button" ng-click="vm.openSidenav()" aria-label="{{ 'home.menu' | translate }}" ng-class="{'tb-invisible': vm.displaySearchMode()}"> class="md-icon-button" ng-click="vm.openSidenav()" aria-label="{{ 'home.menu' | translate }}" ng-class="{'tb-invisible': vm.displaySearchMode()}">
<md-icon aria-label="{{ 'home.menu' | translate }}" class="material-icons">menu</md-icon> <md-icon aria-label="{{ 'home.menu' | translate }}" class="material-icons">menu</md-icon>
@ -47,7 +47,7 @@
<md-button class="md-icon-button" aria-label="{{ 'action.back' | translate }}" ng-click="searchConfig.showSearch = !searchConfig.showSearch" ng-class="{'tb-invisible': !vm.displaySearchMode()}" > <md-button class="md-icon-button" aria-label="{{ 'action.back' | translate }}" ng-click="searchConfig.showSearch = !searchConfig.showSearch" ng-class="{'tb-invisible': !vm.displaySearchMode()}" >
<md-icon aria-label="{{ 'action.back' | translate }}" class="material-icons">arrow_back</md-icon> <md-icon aria-label="{{ 'action.back' | translate }}" class="material-icons">arrow_back</md-icon>
</md-button> </md-button>
<div flex ng-show="!vm.displaySearchMode()" tb-no-animate flex class="md-toolbar-tools"> <div flex layout="row" ng-show="!vm.displaySearchMode()" tb-no-animate class="md-toolbar-tools">
<span ng-cloak ncy-breadcrumb></span> <span ng-cloak ncy-breadcrumb></span>
</div> </div>
<md-input-container ng-show="vm.displaySearchMode()" md-theme="tb-search-input" flex> <md-input-container ng-show="vm.displaySearchMode()" md-theme="tb-search-input" flex>

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", "create": "Create",
"drag": "Drag", "drag": "Drag",
"refresh": "Refresh", "refresh": "Refresh",
"undo": "Undo" "undo": "Undo",
"copy": "Copy",
"paste": "Paste"
}, },
"admin": { "admin": {
"general": "General", "general": "General",
@ -211,7 +213,8 @@
"vertical-margin": "Vertical margin", "vertical-margin": "Vertical margin",
"vertical-margin-required": "Vertical margin value is required.", "vertical-margin-required": "Vertical margin value is required.",
"min-vertical-margin-message": "Only 0 is allowed as minimum vertical margin value.", "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": { "datakey": {
"settings": "Settings", "settings": "Settings",

3
ui/src/scss/main.scss

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

Loading…
Cancel
Save