Commit 3467cb4659c2a9e508f5f3b3e162c056ee2ae693

Authored by Igor Kulikov
2 parents ea0a2544 39da7835

Merge branch 'pgrisu-features/widget_entity_datakeys_mixed_creation_order'

... ... @@ -120,18 +120,6 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t
120 120 ];
121 121
122 122 var defaultAlarmDataKeys = [];
123   - for (var i=0;i<defaultAlarmFields.length;i++) {
124   - var name = defaultAlarmFields[i];
125   - var dataKey = {
126   - name: name,
127   - type: types.dataKeyType.alarm,
128   - label: $translate.instant(types.alarmFields[name].name)+'',
129   - color: getMaterialColor(i),
130   - settings: {},
131   - _hash: Math.random()
132   - };
133   - defaultAlarmDataKeys.push(dataKey);
134   - }
135 123
136 124 var imageAspectMap = {};
137 125
... ... @@ -312,7 +300,25 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t
312 300 return angular.toJson(getDefaultDatasource(dataKeySchema));
313 301 }
314 302
  303 + function initDefaultAlarmDataKeys() {
  304 + for (var i=0;i<defaultAlarmFields.length;i++) {
  305 + var name = defaultAlarmFields[i];
  306 + var dataKey = {
  307 + name: name,
  308 + type: types.dataKeyType.alarm,
  309 + label: $translate.instant(types.alarmFields[name].name)+'',
  310 + color: getMaterialColor(i),
  311 + settings: {},
  312 + _hash: Math.random()
  313 + };
  314 + defaultAlarmDataKeys.push(dataKey);
  315 + }
  316 + }
  317 +
315 318 function getDefaultAlarmDataKeys() {
  319 + if (!defaultAlarmDataKeys.length) {
  320 + initDefaultAlarmDataKeys();
  321 + }
316 322 return angular.copy(defaultAlarmDataKeys);
317 323 }
318 324
... ...
... ... @@ -50,11 +50,8 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
50 50 scope.alarmFields.push(alarmField);
51 51 }
52 52
53   - scope.selectedTimeseriesDataKey = null;
54   - scope.timeseriesDataKeySearchText = null;
55   -
56   - scope.selectedAttributeDataKey = null;
57   - scope.attributeDataKeySearchText = null;
  53 + scope.selectedDataKey = null;
  54 + scope.dataKeySearchText = null;
58 55
59 56 scope.selectedAlarmDataKey = null;
60 57 scope.alarmDataKeySearchText = null;
... ... @@ -92,11 +89,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
92 89 }
93 90 });
94 91
95   - scope.$watch('timeseriesDataKeys', function () {
96   - updateDataKeys();
97   - }, true);
98   -
99   - scope.$watch('attributeDataKeys', function () {
  92 + scope.$watch('dataKeys', function () {
100 93 updateDataKeys();
101 94 }, true);
102 95
... ... @@ -107,10 +100,13 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
107 100 function updateDataKeys() {
108 101 if (ngModelCtrl.$viewValue) {
109 102 var dataKeys = [];
110   - dataKeys = dataKeys.concat(scope.timeseriesDataKeys);
111   - dataKeys = dataKeys.concat(scope.attributeDataKeys);
  103 + dataKeys = dataKeys.concat(scope.dataKeys);
112 104 dataKeys = dataKeys.concat(scope.alarmDataKeys);
113   - ngModelCtrl.$viewValue.dataKeys = dataKeys;
  105 + if (!angular.equals(ngModelCtrl.$viewValue.dataKeys, dataKeys))
  106 + {
  107 + ngModelCtrl.$setDirty();
  108 + ngModelCtrl.$viewValue.dataKeys = dataKeys;
  109 + }
114 110 scope.updateValidity();
115 111 }
116 112 }
... ... @@ -124,21 +120,17 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
124 120 } else {
125 121 scope.entityAlias = null;
126 122 }
127   - var timeseriesDataKeys = [];
128   - var attributeDataKeys = [];
  123 + var dataKeys = [];
129 124 var alarmDataKeys = [];
130 125 for (var d in ngModelCtrl.$viewValue.dataKeys) {
131 126 var dataKey = ngModelCtrl.$viewValue.dataKeys[d];
132   - if (dataKey.type === types.dataKeyType.timeseries) {
133   - timeseriesDataKeys.push(dataKey);
134   - } else if (dataKey.type === types.dataKeyType.attribute) {
135   - attributeDataKeys.push(dataKey);
  127 + if ((dataKey.type === types.dataKeyType.timeseries) || (dataKey.type === types.dataKeyType.attribute)) {
  128 + dataKeys.push(dataKey);
136 129 } else if (dataKey.type === types.dataKeyType.alarm) {
137 130 alarmDataKeys.push(dataKey);
138 131 }
139 132 }
140   - scope.timeseriesDataKeys = timeseriesDataKeys;
141   - scope.attributeDataKeys = attributeDataKeys;
  133 + scope.dataKeys = dataKeys;
142 134 scope.alarmDataKeys = alarmDataKeys;
143 135 }
144 136 };
... ... @@ -148,30 +140,27 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
148 140 }
149 141
150 142 scope.selectedEntityAliasChange = function () {
151   - if (!scope.timeseriesDataKeySearchText || scope.timeseriesDataKeySearchText === '') {
152   - scope.timeseriesDataKeySearchText = scope.timeseriesDataKeySearchText === '' ? null : '';
153   - }
154   - if (!scope.attributeDataKeySearchText || scope.attributeDataKeySearchText === '') {
155   - scope.attributeDataKeySearchText = scope.attributeDataKeySearchText === '' ? null : '';
  143 + if (!scope.dataKeySearchText || scope.dataKeySearchText === '') {
  144 + scope.dataKeySearchText = scope.dataKeySearchText === '' ? null : '';
156 145 }
157 146 if (!scope.alarmDataKeySearchText || scope.alarmDataKeySearchText === '') {
158 147 scope.alarmDataKeySearchText = scope.alarmDataKeySearchText === '' ? null : '';
159 148 }
160 149 };
161 150
162   - scope.transformTimeseriesDataKeyChip = function (chip) {
  151 + scope.transformDataKeyChip = function (chip) {
163 152 if (scope.maxDataKeys > 0 && ngModelCtrl.$viewValue.dataKeys.length >= scope.maxDataKeys ) {
164 153 return null;
165 154 } else {
166   - return scope.generateDataKey({chip: chip, type: types.dataKeyType.timeseries});
167   - }
168   - };
169   -
170   - scope.transformAttributeDataKeyChip = function (chip) {
171   - if (scope.maxDataKeys > 0 && ngModelCtrl.$viewValue.dataKeys.length >= scope.maxDataKeys ) {
172   - return null;
173   - } else {
174   - return scope.generateDataKey({chip: chip, type: types.dataKeyType.attribute});
  155 + if (chip.type) {
  156 + return scope.generateDataKey({chip: chip.name, type: chip.type});
  157 + } else {
  158 + if (scope.widgetType != types.widgetType.latest.value) {
  159 + return scope.generateDataKey({chip: chip, type: types.dataKeyType.timeseries});
  160 + } else {
  161 + return null;
  162 + }
  163 + }
175 164 }
176 165 };
177 166
... ... @@ -226,10 +215,8 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
226 215 w.triggerHandler('resize');
227 216 }
228 217 }).then(function (dataKey) {
229   - if (dataKey.type === types.dataKeyType.timeseries) {
230   - scope.timeseriesDataKeys[index] = dataKey;
231   - } else if (dataKey.type === types.dataKeyType.attribute) {
232   - scope.attributeDataKeys[index] = dataKey;
  218 + if ((dataKey.type === types.dataKeyType.timeseries) || (dataKey.type === types.dataKeyType.attribute)) {
  219 + scope.dataKeys[index] = dataKey;
233 220 } else if (dataKey.type === types.dataKeyType.alarm) {
234 221 scope.alarmDataKeys[index] = dataKey;
235 222 }
... ... @@ -238,7 +225,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
238 225 });
239 226 };
240 227
241   - scope.dataKeysSearch = function (searchText, type) {
  228 + scope.dataKeysSearch = function (searchText) {
242 229 if (scope.widgetType == types.widgetType.alarm.value) {
243 230 var dataKeys = searchText ? scope.alarmFields.filter(
244 231 scope.createFilterForDataKey(searchText)) : scope.alarmFields;
... ... @@ -246,9 +233,25 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
246 233 } else {
247 234 if (scope.entityAlias) {
248 235 var deferred = $q.defer();
249   - scope.fetchEntityKeys({entityAliasId: scope.entityAlias.id, query: searchText, type: type})
  236 + scope.fetchEntityKeys({entityAliasId: scope.entityAlias.id, query: searchText, type: types.dataKeyType.timeseries})
250 237 .then(function (dataKeys) {
251   - deferred.resolve(dataKeys);
  238 + var items = [];
  239 + for (var i = 0; i < dataKeys.length; i++) {
  240 + items.push({ name: dataKeys[i], type: types.dataKeyType.timeseries });
  241 + }
  242 + if (scope.widgetType == types.widgetType.latest.value) {
  243 + scope.fetchEntityKeys({entityAliasId: scope.entityAlias.id, query: searchText, type: types.dataKeyType.attribute})
  244 + .then(function (dataKeys) {
  245 + for (var i = 0; i < dataKeys.length; i++) {
  246 + items.push({ name: dataKeys[i], type: types.dataKeyType.attribute });
  247 + }
  248 + deferred.resolve(items);
  249 + }, function (e) {
  250 + deferred.reject(e);
  251 + });
  252 + }
  253 + else
  254 + deferred.resolve(items);
252 255 }, function (e) {
253 256 deferred.reject(e);
254 257 });
... ... @@ -266,13 +269,13 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
266 269 };
267 270 };
268 271
269   - scope.createKey = function (event, chipsId) {
  272 + scope.createKey = function (event, type, chipsId) {
270 273 var chipsChild = $(chipsId, element)[0].firstElementChild;
271 274 var el = angular.element(chipsChild);
272 275 var chipBuffer = el.scope().$mdChipsCtrl.getChipBuffer();
273 276 event.preventDefault();
274 277 event.stopPropagation();
275   - el.scope().$mdChipsCtrl.appendChip(chipBuffer.trim());
  278 + el.scope().$mdChipsCtrl.appendChip({ name: chipBuffer.trim(), type: type});
276 279 el.scope().$mdChipsCtrl.resetChipBuffer();
277 280 }
278 281
... ...
... ... @@ -16,6 +16,7 @@
16 16 @import "../../scss/constants";
17 17
18 18 .tb-entity-alias-autocomplete,
  19 +.tb-datakey-autocomplete,
19 20 .tb-timeseries-datakey-autocomplete,
20 21 .tb-attribute-datakey-autocomplete,
21 22 .tb-alarm-datakey-autocomplete {
... ...
... ... @@ -25,94 +25,68 @@
25 25 <section flex layout='column'>
26 26 <section flex layout='column' layout-align="center" style="padding-left: 4px;">
27 27 <md-chips flex ng-if="widgetType != types.widgetType.alarm.value"
28   - id="timeseries_datakey_chips"
29   - ng-model="timeseriesDataKeys" md-autocomplete-snap
30   - md-transform-chip="transformTimeseriesDataKeyChip($chip)"
31   - md-require-match="false">
32   - <md-autocomplete
33   - md-no-cache="true"
34   - id="timeseries_datakey"
35   - md-selected-item="selectedTimeseriesDataKey"
36   - md-search-text="timeseriesDataKeySearchText"
37   - md-items="item in dataKeysSearch(timeseriesDataKeySearchText, types.dataKeyType.timeseries)"
38   - md-item-text="item.name"
39   - md-min-length="0"
40   - placeholder="{{'datakey.timeseries' | translate }}"
41   - md-menu-class="tb-timeseries-datakey-autocomplete">
42   - <span md-highlight-text="timeseriesDataKeySearchText" md-highlight-flags="^i">{{item}}</span>
43   - <md-not-found>
44   - <div class="tb-not-found">
45   - <div class="tb-no-entries" ng-if="!textIsNotEmpty(timeseriesDataKeySearchText)">
46   - <span translate>entity.no-keys-found</span>
47   - </div>
48   - <div ng-if="textIsNotEmpty(timeseriesDataKeySearchText)">
49   - <span translate translate-values='{ key: "{{timeseriesDataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>entity.no-key-matching</span>
50   - <span>
51   - <a translate ng-click="createKey($event, '#timeseries_datakey_chips')">entity.create-new-key</a>
52   - </span>
53   - </div>
54   - </div>
55   - </md-not-found>
56   - </md-autocomplete>
57   - <md-chip-template>
58   - <div layout="row" layout-align="start center" class="tb-attribute-chip">
59   - <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
60   - <div class="tb-color-result" ng-style="{background: $chip.color}"></div>
61   - </div>
62   - <div layout="row">
63   - <div class="tb-chip-label">
64   - {{$chip.label}}
65   - </div>
66   - <div class="tb-chip-separator">: </div>
67   - <div class="tb-chip-label">
68   - <strong ng-if="!$chip.postFuncBody">{{$chip.name}}</strong>
69   - <strong ng-if="$chip.postFuncBody">f({{$chip.name}})</strong>
70   - </div>
71   - </div>
72   - <md-button ng-click="editDataKey($event, $chip, $index)" class="md-icon-button tb-md-32">
73   - <md-icon aria-label="edit" class="material-icons tb-md-20">edit</md-icon>
74   - </md-button>
75   - </div>
76   - </md-chip-template>
77   - </md-chips>
78   - <md-chips flex ng-if="widgetType === types.widgetType.latest.value"
79   - id="attribute_datakey_chips"
80   - ng-model="attributeDataKeys" md-autocomplete-snap
81   - md-transform-chip="transformAttributeDataKeyChip($chip)"
  28 + id="datakey_chips"
  29 + ng-model="dataKeys" md-autocomplete-snap
  30 + md-transform-chip="transformDataKeyChip($chip)"
82 31 md-require-match="false">
83 32 <md-autocomplete
84 33 md-no-cache="true"
85   - id="attribute_datakey"
86   - md-selected-item="selectedAttributeDataKey"
87   - md-search-text="attributeDataKeySearchText"
88   - md-items="item in dataKeysSearch(attributeDataKeySearchText, types.dataKeyType.attribute)"
  34 + id="datakey"
  35 + md-selected-item="selectedDataKey"
  36 + md-search-text="dataKeySearchText"
  37 + md-items="item in dataKeysSearch(dataKeySearchText)"
89 38 md-item-text="item.name"
90 39 md-min-length="0"
91   - placeholder="{{'datakey.attributes' | translate }}"
92   - md-menu-class="tb-attribute-datakey-autocomplete">
93   - <span md-highlight-text="attributeDataKeySearchText" md-highlight-flags="^i">{{item}}</span>
  40 + placeholder=""
  41 + md-menu-class="tb-datakey-autocomplete">
  42 + <span ng-show="item.type==types.dataKeyType.attribute">
  43 + <md-tooltip>{{'datakey.attributes' | translate }}</md-tooltip>
  44 + <ng-md-icon size="16" icon="perm_device_information"></ng-md-icon>
  45 + </span>
  46 + <span ng-show="item.type==types.dataKeyType.timeseries">
  47 + <md-tooltip>{{'datakey.timeseries' | translate }}</md-tooltip>
  48 + <ng-md-icon size="16" icon="timeline"></ng-md-icon>
  49 + </span>
  50 + <span md-highlight-text="dataKeySearchText" md-highlight-flags="^i">{{item.name}}</span>
94 51 <md-not-found>
95 52 <div class="tb-not-found">
96   - <div class="tb-no-entries" ng-if="!textIsNotEmpty(attributeDataKeySearchText)">
  53 + <div class="tb-no-entries" ng-if="!textIsNotEmpty(dataKeySearchText)">
97 54 <span translate>entity.no-keys-found</span>
98 55 </div>
99   - <div ng-if="textIsNotEmpty(attributeDataKeySearchText)">
100   - <span translate translate-values='{ key: "{{attributeDataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>entity.no-key-matching</span>
101   - <span>
102   - <a translate ng-click="createKey($event, '#attribute_datakey_chips')">entity.create-new-key</a>
  56 + <div ng-if="textIsNotEmpty(dataKeySearchText)">
  57 + <span translate translate-values='{ key: "{{dataKeySearchText | truncate:true:6:&apos;...&apos;}}" }'>entity.no-key-matching</span>
  58 + <span>{{'entity.create-new-key' | translate }} </span>
  59 + <span ng-show="widgetType == types.widgetType.latest.value">
  60 + <md-tooltip>{{'datakey.attributes' | translate }}</md-tooltip>
  61 + <ng-md-icon size="16" icon="perm_device_information" ng-click="createKey($event, types.dataKeyType.attribute, '#datakey_chips')"></ng-md-icon>
103 62 </span>
  63 + <span>
  64 + <md-tooltip>{{'datakey.timeseries' | translate }}</md-tooltip>
  65 + <ng-md-icon size="16" icon="timeline" ng-click="createKey($event, types.dataKeyType.timeseries, '#datakey_chips')"></ng-md-icon>
  66 + </span>
104 67 </div>
105 68 </div>
106 69 </md-not-found>
107 70 </md-autocomplete>
108 71 <md-chip-template>
109 72 <div layout="row" layout-align="start center" class="tb-attribute-chip">
  73 + <div class="tb-chip-drag-handle" style="margin-right: 5px;" tb-chip-draggable>
  74 + <ng-md-icon size="20" icon="drag_handle"></ng-md-icon>
  75 + </div>
110 76 <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
111 77 <div class="tb-color-result" ng-style="{background: $chip.color}"></div>
112 78 </div>
113 79 <div layout="row">
114 80 <div class="tb-chip-label">
115   - {{$chip.label}}
  81 + <span ng-show="$chip.type==types.dataKeyType.attribute">
  82 + <md-tooltip>{{'datakey.attributes' | translate }}</md-tooltip>
  83 + <ng-md-icon size="20" icon="perm_device_information"></ng-md-icon>
  84 + </span>
  85 + <span ng-show="$chip.type==types.dataKeyType.timeseries">
  86 + <md-tooltip>{{'datakey.timeseries' | translate }}</md-tooltip>
  87 + <ng-md-icon size="20" icon="timeline"></ng-md-icon>
  88 + </span>
  89 + {{$chip.label}}
116 90 </div>
117 91 <div class="tb-chip-separator">: </div>
118 92 <div class="tb-chip-label">
... ... @@ -137,7 +111,7 @@
137 111 id="alarm_datakey"
138 112 md-selected-item="selectedAlarmDataKey"
139 113 md-search-text="alarmDataKeySearchText"
140   - md-items="item in dataKeysSearch(alarmDataKeySearchText, types.dataKeyType.alarm)"
  114 + md-items="item in dataKeysSearch(alarmDataKeySearchText)"
141 115 md-item-text="item.name"
142 116 md-min-length="0"
143 117 placeholder="{{'datakey.alarm' | translate }}"
... ... @@ -159,6 +133,9 @@
159 133 </md-autocomplete>
160 134 <md-chip-template>
161 135 <div layout="row" layout-align="start center" class="tb-attribute-chip">
  136 + <div class="tb-chip-drag-handle" style="margin-right: 5px;" tb-chip-draggable>
  137 + <ng-md-icon size="20" icon="drag_handle"></ng-md-icon>
  138 + </div>
162 139 <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
163 140 <div class="tb-color-result" ng-style="{background: $chip.color}"></div>
164 141 </div>
... ...
... ... @@ -88,7 +88,11 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
88 88 var dataKeys = [];
89 89 dataKeys = dataKeys.concat(scope.funcDataKeys);
90 90 dataKeys = dataKeys.concat(scope.alarmDataKeys);
91   - ngModelCtrl.$viewValue.dataKeys = dataKeys;
  91 + if (ngModelCtrl.$viewValue.dataKeys != dataKeys)
  92 + {
  93 + ngModelCtrl.$setDirty();
  94 + ngModelCtrl.$viewValue.dataKeys = dataKeys;
  95 + }
92 96 scope.updateValidity();
93 97 }
94 98 }
... ...
... ... @@ -58,7 +58,10 @@
58 58 </md-autocomplete>
59 59 <md-chip-template>
60 60 <div layout="row" layout-align="start center" class="tb-attribute-chip">
61   - <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
  61 + <div class="tb-chip-drag-handle" style="margin-right: 5px;" tb-chip-draggable>
  62 + <ng-md-icon size="20" icon="drag_handle"></ng-md-icon>
  63 + </div>
  64 + <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
62 65 <div class="tb-color-result" ng-style="{background: $chip.color}"></div>
63 66 </div>
64 67 <div layout="row">
... ... @@ -109,7 +112,10 @@
109 112 </md-autocomplete>
110 113 <md-chip-template>
111 114 <div layout="row" layout-align="start center" class="tb-attribute-chip">
112   - <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
  115 + <div class="tb-chip-drag-handle" style="margin-right: 5px;" tb-chip-draggable>
  116 + <ng-md-icon size="20" icon="drag_handle"></ng-md-icon>
  117 + </div>
  118 + <div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
113 119 <div class="tb-color-result" ng-style="{background: $chip.color}"></div>
114 120 </div>
115 121 <div layout="row">
... ...
... ... @@ -58,6 +58,14 @@
58 58 }
59 59
60 60 .tb-attribute-chip {
  61 + .tb-chip-drag-handle {
  62 + cursor: move;
  63 +
  64 + ng-md-icon {
  65 + pointer-events: none;
  66 + }
  67 + }
  68 +
61 69 .tb-chip-label {
62 70 overflow: hidden;
63 71 text-overflow: ellipsis;
... ...
  1 +/*
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +export default angular.module('thingsboard.directives.mdChipDraggable', [])
  17 + .directive('tbChipDraggable', function () {
  18 + return {
  19 + restrict: 'A',
  20 + scope: {},
  21 + bindToController: true,
  22 + controllerAs: 'vm',
  23 + controller: ['$document', '$scope', '$element', '$timeout',
  24 + function ($document, $scope, $element, $timeout) {
  25 + var handle = $element[0];
  26 + var draggingClassName = 'dragging';
  27 + var droppingClassName = 'dropping';
  28 + var droppingBeforeClassName = 'dropping--before';
  29 + var droppingAfterClassName = 'dropping--after';
  30 + var dragging = false;
  31 + var preventDrag = false;
  32 + var dropPosition;
  33 + var dropTimeout;
  34 +
  35 + var move = function (from, to) {
  36 + this.splice(to, 0, this.splice(from, 1)[0]);
  37 + };
  38 +
  39 + $element = angular.element($element[0].closest('md-chip'));
  40 +
  41 + $element.attr('draggable', true);
  42 +
  43 + $element.on('mousedown', function (event) {
  44 + if (event.target !== handle) {
  45 + preventDrag = true;
  46 + }
  47 + });
  48 +
  49 + $document.on('mouseup', function () {
  50 + preventDrag = false;
  51 + });
  52 +
  53 + $element.on('dragstart', function (event) {
  54 + if (preventDrag) {
  55 + event.preventDefault();
  56 +
  57 + } else {
  58 + dragging = true;
  59 +
  60 + $element.addClass(draggingClassName);
  61 +
  62 + var dataTransfer = event.dataTransfer || event.originalEvent.dataTransfer;
  63 +
  64 + dataTransfer.effectAllowed = 'copyMove';
  65 + dataTransfer.dropEffect = 'move';
  66 + dataTransfer.setData('text/plain', $scope.$parent.$mdChipsCtrl.items.indexOf($scope.$parent.$chip));
  67 + }
  68 + });
  69 +
  70 + $element.on('dragend', function () {
  71 + dragging = false;
  72 +
  73 + $element.removeClass(draggingClassName);
  74 + });
  75 +
  76 + var dragOverHandler = function (event) {
  77 + if (dragging) {
  78 + return;
  79 + }
  80 +
  81 + event.preventDefault();
  82 +
  83 + var bounds = $element[0].getBoundingClientRect();
  84 +
  85 + var props = {
  86 + width: bounds.right - bounds.left,
  87 + height: bounds.bottom - bounds.top,
  88 + x: (event.originalEvent || event).clientX - bounds.left,
  89 + y: (event.originalEvent || event).clientY - bounds.top,
  90 + };
  91 +
  92 + var horizontalOffset = props.x;
  93 + var horizontalMidPoint = props.width / 2;
  94 +
  95 + var verticalOffset = props.y;
  96 + var verticalMidPoint = props.height / 2;
  97 +
  98 + $element.addClass(droppingClassName);
  99 +
  100 + if (horizontalOffset >= horizontalMidPoint || verticalOffset >= verticalMidPoint) {
  101 + dropPosition = 'after';
  102 + $element.removeClass(droppingBeforeClassName);
  103 + $element.addClass(droppingAfterClassName);
  104 + } else {
  105 + dropPosition = 'before';
  106 + $element.removeClass(droppingAfterClassName);
  107 + $element.addClass(droppingBeforeClassName);
  108 + }
  109 +
  110 + };
  111 +
  112 + var dropHandler = function (event) {
  113 + event.preventDefault();
  114 + var droppedItemIndex = parseInt((event.dataTransfer || event.originalEvent.dataTransfer).getData('text/plain'), 10);
  115 + var currentIndex = $scope.$parent.$mdChipsCtrl.items.indexOf($scope.$parent.$chip);
  116 + var newIndex = null;
  117 +
  118 + if (dropPosition === 'before') {
  119 + if (droppedItemIndex < currentIndex) {
  120 + newIndex = currentIndex - 1;
  121 + } else {
  122 + newIndex = currentIndex;
  123 + }
  124 + } else {
  125 + if (droppedItemIndex < currentIndex) {
  126 + newIndex = currentIndex;
  127 + } else {
  128 + newIndex = currentIndex + 1;
  129 + }
  130 + }
  131 +
  132 + // prevent event firing multiple times in firefox
  133 + $timeout.cancel(dropTimeout);
  134 + dropTimeout = $timeout(function () {
  135 + dropPosition = null;
  136 +
  137 + move.apply($scope.$parent.$mdChipsCtrl.items, [droppedItemIndex, newIndex]);
  138 +
  139 + $scope.$apply(function () {
  140 + $scope.$emit('mdChipDraggable:change', {
  141 + collection: $scope.$parent.$mdChipsCtrl.items,
  142 + item: $scope.$parent.$mdChipsCtrl.items[droppedItemIndex],
  143 + from: droppedItemIndex,
  144 + to: newIndex,
  145 + });
  146 + });
  147 +
  148 + $element.removeClass(droppingClassName);
  149 + $element.removeClass(droppingBeforeClassName);
  150 + $element.removeClass(droppingAfterClassName);
  151 +
  152 + $element.off('drop', dropHandler);
  153 + }, 1000 / 16);
  154 + };
  155 +
  156 + $element.on('dragenter', function () {
  157 + if (dragging) {
  158 + return;
  159 + }
  160 +
  161 + $element.off('dragover', dragOverHandler);
  162 + $element.off('drop', dropHandler);
  163 +
  164 + $element.on('dragover', dragOverHandler);
  165 + $element.on('drop', dropHandler);
  166 + });
  167 +
  168 + $element.on('dragleave', function () {
  169 + $element.removeClass(droppingClassName);
  170 + $element.removeClass(droppingBeforeClassName);
  171 + $element.removeClass(droppingAfterClassName);
  172 + });
  173 +
  174 + }],
  175 + };
  176 + })
  177 + .name;
  178 +
  179 +/* eslint-enable angular/angularelement */
... ...
... ... @@ -23,12 +23,14 @@
23 23 <md-content class="md-padding" layout="column">
24 24 <div ng-show="widgetType === types.widgetType.timeseries.value || widgetType === types.widgetType.alarm.value" layout='column' layout-align="center"
25 25 layout-gt-sm='row' layout-align-gt-sm="start center">
26   - <md-checkbox flex aria-label="{{ 'widget-config.use-dashboard-timewindow' | translate }}"
27   - ng-model="useDashboardTimewindow">{{ 'widget-config.use-dashboard-timewindow' | translate }}
28   - </md-checkbox>
29   - <md-checkbox ng-disabled="useDashboardTimewindow" flex aria-label="{{ 'widget-config.display-timewindow' | translate }}"
30   - ng-model="displayTimewindow">{{ 'widget-config.display-timewindow' | translate }}
31   - </md-checkbox>
  26 + <div layout="column" flex>
  27 + <md-checkbox flex aria-label="{{ 'widget-config.use-dashboard-timewindow' | translate }}"
  28 + ng-model="useDashboardTimewindow">{{ 'widget-config.use-dashboard-timewindow' | translate }}
  29 + </md-checkbox>
  30 + <md-checkbox ng-disabled="useDashboardTimewindow" flex aria-label="{{ 'widget-config.display-timewindow' | translate }}"
  31 + ng-model="displayTimewindow">{{ 'widget-config.display-timewindow' | translate }}
  32 + </md-checkbox>
  33 + </div>
32 34 <section flex layout="row" layout-align="start center" style="margin-bottom: 16px;">
33 35 <span ng-class="{'tb-disabled-label': useDashboardTimewindow}" translate style="padding-right: 8px;">widget-config.timewindow</span>
34 36 <tb-timewindow ng-disabled="useDashboardTimewindow" as-button="true" aggregation="{{ widgetType === types.widgetType.timeseries.value }}"
... ...
... ... @@ -27,6 +27,7 @@ import thingsboardConfirmOnExit from '../components/confirm-on-exit.directive';
27 27 import thingsboardDashboard from '../components/dashboard.directive';
28 28 import thingsboardExpandFullscreen from '../components/expand-fullscreen.directive';
29 29 import thingsboardCircularProgress from '../components/circular-progress.directive';
  30 +import thingsboardMdChipDraggable from '../components/md-chip-draggable.directive';
30 31
31 32 import WidgetLibraryRoutes from './widget-library.routes';
32 33 import WidgetLibraryController from './widget-library.controller';
... ... @@ -46,6 +47,7 @@ export default angular.module('thingsboard.widget-library', [
46 47 thingsboardDashboard,
47 48 thingsboardExpandFullscreen,
48 49 thingsboardCircularProgress,
  50 + thingsboardMdChipDraggable,
49 51 'cfp.hotkeys',
50 52 'ui.ace'
51 53 ])
... ...