diff --git a/ui/src/app/components/datasource-entity.directive.js b/ui/src/app/components/datasource-entity.directive.js index b65ffd32ea..f9dbc45ada 100644 --- a/ui/src/app/components/datasource-entity.directive.js +++ b/ui/src/app/components/datasource-entity.directive.js @@ -50,11 +50,8 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc scope.alarmFields.push(alarmField); } - scope.selectedTimeseriesDataKey = null; - scope.timeseriesDataKeySearchText = null; - - scope.selectedAttributeDataKey = null; - scope.attributeDataKeySearchText = null; + scope.selectedDataKey = null; + scope.dataKeySearchText = null; scope.selectedAlarmDataKey = null; scope.alarmDataKeySearchText = null; @@ -92,11 +89,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc } }); - scope.$watch('timeseriesDataKeys', function () { - updateDataKeys(); - }, true); - - scope.$watch('attributeDataKeys', function () { + scope.$watch('dataKeys', function () { updateDataKeys(); }, true); @@ -107,10 +100,13 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc function updateDataKeys() { if (ngModelCtrl.$viewValue) { var dataKeys = []; - dataKeys = dataKeys.concat(scope.timeseriesDataKeys); - dataKeys = dataKeys.concat(scope.attributeDataKeys); + dataKeys = dataKeys.concat(scope.dataKeys); dataKeys = dataKeys.concat(scope.alarmDataKeys); - ngModelCtrl.$viewValue.dataKeys = dataKeys; + if (!angular.equals(ngModelCtrl.$viewValue.dataKeys, dataKeys)) + { + ngModelCtrl.$setDirty(); + ngModelCtrl.$viewValue.dataKeys = dataKeys; + } scope.updateValidity(); } } @@ -124,21 +120,17 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc } else { scope.entityAlias = null; } - var timeseriesDataKeys = []; - var attributeDataKeys = []; + var dataKeys = []; var alarmDataKeys = []; for (var d in ngModelCtrl.$viewValue.dataKeys) { var dataKey = ngModelCtrl.$viewValue.dataKeys[d]; - if (dataKey.type === types.dataKeyType.timeseries) { - timeseriesDataKeys.push(dataKey); - } else if (dataKey.type === types.dataKeyType.attribute) { - attributeDataKeys.push(dataKey); + if ((dataKey.type === types.dataKeyType.timeseries) || (dataKey.type === types.dataKeyType.attribute)) { + dataKeys.push(dataKey); } else if (dataKey.type === types.dataKeyType.alarm) { alarmDataKeys.push(dataKey); } } - scope.timeseriesDataKeys = timeseriesDataKeys; - scope.attributeDataKeys = attributeDataKeys; + scope.dataKeys = dataKeys; scope.alarmDataKeys = alarmDataKeys; } }; @@ -148,30 +140,19 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc } scope.selectedEntityAliasChange = function () { - if (!scope.timeseriesDataKeySearchText || scope.timeseriesDataKeySearchText === '') { - scope.timeseriesDataKeySearchText = scope.timeseriesDataKeySearchText === '' ? null : ''; - } - if (!scope.attributeDataKeySearchText || scope.attributeDataKeySearchText === '') { - scope.attributeDataKeySearchText = scope.attributeDataKeySearchText === '' ? null : ''; + if (!scope.dataKeySearchText || scope.dataKeySearchText === '') { + scope.dataKeySearchText = scope.dataKeySearchText === '' ? null : ''; } if (!scope.alarmDataKeySearchText || scope.alarmDataKeySearchText === '') { scope.alarmDataKeySearchText = scope.alarmDataKeySearchText === '' ? null : ''; } }; - scope.transformTimeseriesDataKeyChip = function (chip) { - if (scope.maxDataKeys > 0 && ngModelCtrl.$viewValue.dataKeys.length >= scope.maxDataKeys ) { - return null; - } else { - return scope.generateDataKey({chip: chip, type: types.dataKeyType.timeseries}); - } - }; - - scope.transformAttributeDataKeyChip = function (chip) { + scope.transformDataKeyChip = function (chip) { if (scope.maxDataKeys > 0 && ngModelCtrl.$viewValue.dataKeys.length >= scope.maxDataKeys ) { return null; } else { - return scope.generateDataKey({chip: chip, type: types.dataKeyType.attribute}); + return scope.generateDataKey({chip: chip.name, type: chip.type}); } }; @@ -226,10 +207,8 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc w.triggerHandler('resize'); } }).then(function (dataKey) { - if (dataKey.type === types.dataKeyType.timeseries) { - scope.timeseriesDataKeys[index] = dataKey; - } else if (dataKey.type === types.dataKeyType.attribute) { - scope.attributeDataKeys[index] = dataKey; + if ((dataKey.type === types.dataKeyType.timeseries) || (dataKey.type === types.dataKeyType.attribute)) { + scope.dataKeys[index] = dataKey; } else if (dataKey.type === types.dataKeyType.alarm) { scope.alarmDataKeys[index] = dataKey; } @@ -238,7 +217,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc }); }; - scope.dataKeysSearch = function (searchText, type) { + scope.dataKeysSearch = function (searchText) { if (scope.widgetType == types.widgetType.alarm.value) { var dataKeys = searchText ? scope.alarmFields.filter( scope.createFilterForDataKey(searchText)) : scope.alarmFields; @@ -246,9 +225,25 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc } else { if (scope.entityAlias) { var deferred = $q.defer(); - scope.fetchEntityKeys({entityAliasId: scope.entityAlias.id, query: searchText, type: type}) + scope.fetchEntityKeys({entityAliasId: scope.entityAlias.id, query: searchText, type: types.dataKeyType.timeseries}) .then(function (dataKeys) { - deferred.resolve(dataKeys); + var items = []; + for (var i = 0; i < dataKeys.length; i++) { + items.push({ name: dataKeys[i], type: types.dataKeyType.timeseries }); + } + if (scope.widgetType == types.widgetType.latest.value) { + scope.fetchEntityKeys({entityAliasId: scope.entityAlias.id, query: searchText, type: types.dataKeyType.attribute}) + .then(function (dataKeys) { + for (var i = 0; i < dataKeys.length; i++) { + items.push({ name: dataKeys[i], type: types.dataKeyType.attribute }); + } + deferred.resolve(items); + }, function (e) { + deferred.reject(e); + }); + } + else + deferred.resolve(items); }, function (e) { deferred.reject(e); }); @@ -266,13 +261,13 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc }; }; - scope.createKey = function (event, chipsId) { + scope.createKey = function (event, type, chipsId) { var chipsChild = $(chipsId, element)[0].firstElementChild; var el = angular.element(chipsChild); var chipBuffer = el.scope().$mdChipsCtrl.getChipBuffer(); event.preventDefault(); event.stopPropagation(); - el.scope().$mdChipsCtrl.appendChip(chipBuffer.trim()); + el.scope().$mdChipsCtrl.appendChip({ name: chipBuffer.trim(), type: type}); el.scope().$mdChipsCtrl.resetChipBuffer(); } diff --git a/ui/src/app/components/datasource-entity.scss b/ui/src/app/components/datasource-entity.scss index ef4fcd0545..75a2eed632 100644 --- a/ui/src/app/components/datasource-entity.scss +++ b/ui/src/app/components/datasource-entity.scss @@ -16,6 +16,7 @@ @import "../../scss/constants"; .tb-entity-alias-autocomplete, +.tb-datakey-autocomplete, .tb-timeseries-datakey-autocomplete, .tb-attribute-datakey-autocomplete, .tb-alarm-datakey-autocomplete { diff --git a/ui/src/app/components/datasource-entity.tpl.html b/ui/src/app/components/datasource-entity.tpl.html index c954e83608..50858caef1 100644 --- a/ui/src/app/components/datasource-entity.tpl.html +++ b/ui/src/app/components/datasource-entity.tpl.html @@ -25,82 +25,45 @@
- - {{item}} - -
-
- entity.no-keys-found -
-
- entity.no-key-matching - - entity.create-new-key - -
-
-
-
- -
-
-
-
-
-
- {{$chip.label}} -
-
:
-
- {{$chip.name}} - f({{$chip.name}}) -
-
- - edit - -
-
-
- - {{item}} + placeholder="" + md-menu-class="tb-datakey-autocomplete"> + + {{'datakey.attributes' | translate }} + + + + {{'datakey.timeseries' | translate }} + + + {{item.name}}
-
+
entity.no-keys-found
-
- entity.no-key-matching - - entity.create-new-key +
+ entity.no-key-matching + {{'entity.create-new-key' | translate }} + + {{'datakey.attributes' | translate }} + + + {{'datakey.timeseries' | translate }} + +
@@ -111,7 +74,15 @@
-
+
+ + {{'datakey.attributes' | translate }} + + + + {{'datakey.timeseries' | translate }} + + {{$chip.label}}
:
@@ -137,7 +108,7 @@ id="alarm_datakey" md-selected-item="selectedAlarmDataKey" md-search-text="alarmDataKeySearchText" - md-items="item in dataKeysSearch(alarmDataKeySearchText, types.dataKeyType.alarm)" + md-items="item in dataKeysSearch(alarmDataKeySearchText)" md-item-text="item.name" md-min-length="0" placeholder="{{'datakey.alarm' | translate }}" @@ -163,7 +134,7 @@
-
+
{{$chip.label}}
:
diff --git a/ui/src/app/components/datasource-func.directive.js b/ui/src/app/components/datasource-func.directive.js index 357dc2140a..c42cb9344e 100644 --- a/ui/src/app/components/datasource-func.directive.js +++ b/ui/src/app/components/datasource-func.directive.js @@ -88,7 +88,11 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document, var dataKeys = []; dataKeys = dataKeys.concat(scope.funcDataKeys); dataKeys = dataKeys.concat(scope.alarmDataKeys); - ngModelCtrl.$viewValue.dataKeys = dataKeys; + if (ngModelCtrl.$viewValue.dataKeys != dataKeys) + { + ngModelCtrl.$setDirty(); + ngModelCtrl.$viewValue.dataKeys = dataKeys; + } scope.updateValidity(); } } diff --git a/ui/src/app/components/datasource-func.tpl.html b/ui/src/app/components/datasource-func.tpl.html index c0e681b662..289570fd77 100644 --- a/ui/src/app/components/datasource-func.tpl.html +++ b/ui/src/app/components/datasource-func.tpl.html @@ -62,7 +62,7 @@
-
+
{{$chip.label}}
:
@@ -113,7 +113,7 @@
-
+
{{$chip.label}}
:
diff --git a/ui/src/app/components/md-chip-draggable.directive.js b/ui/src/app/components/md-chip-draggable.directive.js new file mode 100644 index 0000000000..8fcdc0ad73 --- /dev/null +++ b/ui/src/app/components/md-chip-draggable.directive.js @@ -0,0 +1,180 @@ +/* + * Copyright © 2016-2019 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.mdChipDraggable', []) + .directive('tbChipDraggable', function () { + return { + restrict: 'A', + scope: {}, + bindToController: true, + controllerAs: 'vm', + controller: ['$document', '$scope', '$element', '$timeout', + function ($document, $scope, $element, $timeout) { + var options = { + axis: 'horizontal', + }; + var handle = $element[0]; + var draggingClassName = 'dragging'; + var droppingClassName = 'dropping'; + var droppingBeforeClassName = 'dropping--before'; + var droppingAfterClassName = 'dropping--after'; + var dragging = false; + var preventDrag = false; + var dropPosition; + var dropTimeout; + + var move = function (from, to) { + this.splice(to, 0, this.splice(from, 1)[0]); + }; + + $element = angular.element($element[0].closest('md-chip')); + + $element.attr('draggable', true); + + $element.on('mousedown', function (event) { + if (event.target !== handle) { + preventDrag = true; + } + }); + + $document.on('mouseup', function () { + preventDrag = false; + }); + + $element.on('dragstart', function (event) { + if (preventDrag) { + event.preventDefault(); + + } else { + dragging = true; + + $element.addClass(draggingClassName); + + var dataTransfer = event.dataTransfer || event.originalEvent.dataTransfer; + + dataTransfer.effectAllowed = 'copyMove'; + dataTransfer.dropEffect = 'move'; + dataTransfer.setData('text/plain', $scope.$parent.$mdChipsCtrl.items.indexOf($scope.$parent.$chip)); + } + }); + + $element.on('dragend', function () { + dragging = false; + + $element.removeClass(draggingClassName); + }); + + var dragOverHandler = function (event) { + if (dragging) { + return; + } + + event.preventDefault(); + + var bounds = $element[0].getBoundingClientRect(); + + var props = { + width: bounds.right - bounds.left, + height: bounds.bottom - bounds.top, + x: (event.originalEvent || event).clientX - bounds.left, + y: (event.originalEvent || event).clientY - bounds.top, + }; + + var offset = options.axis === 'vertical' ? props.y : props.x; + var midPoint = (options.axis === 'vertical' ? props.height : props.width) / 2; + + $element.addClass(droppingClassName); + + if (offset < midPoint) { + dropPosition = 'before'; + $element.removeClass(droppingAfterClassName); + $element.addClass(droppingBeforeClassName); + + } else { + dropPosition = 'after'; + $element.removeClass(droppingBeforeClassName); + $element.addClass(droppingAfterClassName); + } + }; + + var dropHandler = function (event) { + event.preventDefault(); + + var droppedItemIndex = parseInt((event.dataTransfer || event.originalEvent.dataTransfer).getData('text/plain'), 10); + var currentIndex = $scope.$parent.$mdChipsCtrl.items.indexOf($scope.$parent.$chip); + var newIndex = null; + + if (dropPosition === 'before') { + if (droppedItemIndex < currentIndex) { + newIndex = currentIndex - 1; + } else { + newIndex = currentIndex; + } + } else { + if (droppedItemIndex < currentIndex) { + newIndex = currentIndex; + } else { + newIndex = currentIndex + 1; + } + } + + // prevent event firing multiple times in firefox + $timeout.cancel(dropTimeout); + dropTimeout = $timeout(function () { + dropPosition = null; + + move.apply($scope.$parent.$mdChipsCtrl.items, [droppedItemIndex, newIndex]); + + $scope.$apply(function () { + $scope.$emit('mdChipDraggable:change', { + collection: $scope.$parent.$mdChipsCtrl.items, + item: $scope.$parent.$mdChipsCtrl.items[droppedItemIndex], + from: droppedItemIndex, + to: newIndex, + }); + }); + + $element.removeClass(droppingClassName); + $element.removeClass(droppingBeforeClassName); + $element.removeClass(droppingAfterClassName); + + $element.off('drop', dropHandler); + }, 1000 / 16); + }; + + $element.on('dragenter', function () { + if (dragging) { + return; + } + + $element.off('dragover', dragOverHandler); + $element.off('drop', dropHandler); + + $element.on('dragover', dragOverHandler); + $element.on('drop', dropHandler); + }); + + $element.on('dragleave', function () { + $element.removeClass(droppingClassName); + $element.removeClass(droppingBeforeClassName); + $element.removeClass(droppingAfterClassName); + }); + + }], + }; + }) + .name; + +/* eslint-enable angular/angularelement */ diff --git a/ui/src/app/widget/index.js b/ui/src/app/widget/index.js index 41d85a8032..71c1edc0ab 100644 --- a/ui/src/app/widget/index.js +++ b/ui/src/app/widget/index.js @@ -27,6 +27,7 @@ import thingsboardConfirmOnExit from '../components/confirm-on-exit.directive'; import thingsboardDashboard from '../components/dashboard.directive'; import thingsboardExpandFullscreen from '../components/expand-fullscreen.directive'; import thingsboardCircularProgress from '../components/circular-progress.directive'; +import thingsboardMdChipDraggable from '../components/md-chip-draggable.directive'; import WidgetLibraryRoutes from './widget-library.routes'; import WidgetLibraryController from './widget-library.controller'; @@ -46,6 +47,7 @@ export default angular.module('thingsboard.widget-library', [ thingsboardDashboard, thingsboardExpandFullscreen, thingsboardCircularProgress, + thingsboardMdChipDraggable, 'cfp.hotkeys', 'ui.ace' ])