+
{{$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'
])
From 76e5aa24fa80b7bf6c8dbf8508353f0002aabfd0 Mon Sep 17 00:00:00 2001
From: pgrisu <42511852+pgrisu@users.noreply.github.com>
Date: Fri, 10 May 2019 12:08:02 +0200
Subject: [PATCH 2/3] add support for flexible entity dataKeys order
(timeseries mixed with attributes in arbitrary way) in the widget config
---
.../components/datasource-entity.directive.js | 81 +++++++--------
ui/src/app/components/datasource-entity.scss | 1 +
.../app/components/datasource-entity.tpl.html | 99 ++++++-------------
3 files changed, 69 insertions(+), 112 deletions(-)
diff --git a/ui/src/app/components/datasource-entity.directive.js b/ui/src/app/components/datasource-entity.directive.js
index 2b8f2ec0c5..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,9 @@ 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);
- if (ngModelCtrl.$viewValue.dataKeys != dataKeys)
+ if (!angular.equals(ngModelCtrl.$viewValue.dataKeys, dataKeys))
{
ngModelCtrl.$setDirty();
ngModelCtrl.$viewValue.dataKeys = dataKeys;
@@ -128,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;
}
};
@@ -152,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});
}
};
@@ -230,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;
}
@@ -242,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;
@@ -250,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);
});
@@ -270,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 128bf8305b..b9925f9b96 100644
--- a/ui/src/app/components/datasource-entity.tpl.html
+++ b/ui/src/app/components/datasource-entity.tpl.html
@@ -25,82 +25,39 @@
-
- {{item}}
-
-
-
- entity.no-keys-found
-
-
-
-
-
-
-
-
-
-
- {{$chip.label}}
-
-
:
-
- {{$chip.name}}
- f({{$chip.name}})
-
-
-
- edit
-
-
-
-
-
- {{item}}
+ placeholder=""
+ md-menu-class="tb-datakey-autocomplete">
+
+
+ {{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 }}
+
+
@@ -112,6 +69,14 @@
+
+ {{'datakey.attributes' | translate }}
+
+
+
+ {{'datakey.timeseries' | translate }}
+
+
{{$chip.label}}
:
@@ -137,7 +102,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 }}"
From 47d56f43681bf08b95305a9a448fa0ad82a066fe Mon Sep 17 00:00:00 2001
From: pgrisu <42511852+pgrisu@users.noreply.github.com>
Date: Fri, 10 May 2019 17:53:18 +0200
Subject: [PATCH 3/3] add tooltip on dataKeys list items in widget datasource
settings
---
ui/src/app/components/datasource-entity.tpl.html | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/ui/src/app/components/datasource-entity.tpl.html b/ui/src/app/components/datasource-entity.tpl.html
index b9925f9b96..50858caef1 100644
--- a/ui/src/app/components/datasource-entity.tpl.html
+++ b/ui/src/app/components/datasource-entity.tpl.html
@@ -39,8 +39,14 @@
md-min-length="0"
placeholder=""
md-menu-class="tb-datakey-autocomplete">
-
-
+
+ {{'datakey.attributes' | translate }}
+
+
+
+ {{'datakey.timeseries' | translate }}
+
+
{{item.name}}