53 changed files with 532 additions and 2773 deletions
@ -1,248 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2017 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 './datasource-device.scss'; |
|||
|
|||
import 'md-color-picker'; |
|||
import tinycolor from 'tinycolor2'; |
|||
import $ from 'jquery'; |
|||
import thingsboardTypes from '../common/types.constant'; |
|||
import thingsboardDatakeyConfigDialog from './datakey-config-dialog.controller'; |
|||
import thingsboardTruncate from './truncate.filter'; |
|||
|
|||
/* eslint-disable import/no-unresolved, import/default */ |
|||
|
|||
import datasourceDeviceTemplate from './datasource-device.tpl.html'; |
|||
import datakeyConfigDialogTemplate from './datakey-config-dialog.tpl.html'; |
|||
|
|||
/* eslint-enable import/no-unresolved, import/default */ |
|||
|
|||
/* eslint-disable angular/angularelement */ |
|||
|
|||
export default angular.module('thingsboard.directives.datasourceDevice', [thingsboardTruncate, thingsboardTypes, thingsboardDatakeyConfigDialog]) |
|||
.directive('tbDatasourceDevice', DatasourceDevice) |
|||
.name; |
|||
|
|||
/*@ngInject*/ |
|||
function DatasourceDevice($compile, $templateCache, $q, $mdDialog, $window, $document, $mdColorPicker, $mdConstant, types) { |
|||
|
|||
var linker = function (scope, element, attrs, ngModelCtrl) { |
|||
var template = $templateCache.get(datasourceDeviceTemplate); |
|||
element.html(template); |
|||
|
|||
scope.ngModelCtrl = ngModelCtrl; |
|||
scope.types = types; |
|||
|
|||
scope.selectedTimeseriesDataKey = null; |
|||
scope.timeseriesDataKeySearchText = null; |
|||
|
|||
scope.selectedAttributeDataKey = null; |
|||
scope.attributeDataKeySearchText = null; |
|||
|
|||
scope.updateValidity = function () { |
|||
if (ngModelCtrl.$viewValue) { |
|||
var value = ngModelCtrl.$viewValue; |
|||
var dataValid = angular.isDefined(value) && value != null; |
|||
ngModelCtrl.$setValidity('deviceData', dataValid); |
|||
if (dataValid) { |
|||
ngModelCtrl.$setValidity('deviceAlias', |
|||
angular.isDefined(value.deviceAliasId) && |
|||
value.deviceAliasId != null); |
|||
ngModelCtrl.$setValidity('deviceKeys', |
|||
angular.isDefined(value.dataKeys) && |
|||
value.dataKeys != null && |
|||
value.dataKeys.length > 0); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
scope.$watch('deviceAlias', function () { |
|||
if (ngModelCtrl.$viewValue) { |
|||
if (scope.deviceAlias) { |
|||
ngModelCtrl.$viewValue.deviceAliasId = scope.deviceAlias.id; |
|||
} else { |
|||
ngModelCtrl.$viewValue.deviceAliasId = null; |
|||
} |
|||
scope.updateValidity(); |
|||
scope.selectedDeviceAliasChange(); |
|||
} |
|||
}); |
|||
|
|||
scope.$watch('timeseriesDataKeys', function () { |
|||
if (ngModelCtrl.$viewValue) { |
|||
var dataKeys = []; |
|||
dataKeys = dataKeys.concat(scope.timeseriesDataKeys); |
|||
dataKeys = dataKeys.concat(scope.attributeDataKeys); |
|||
ngModelCtrl.$viewValue.dataKeys = dataKeys; |
|||
scope.updateValidity(); |
|||
} |
|||
}, true); |
|||
|
|||
scope.$watch('attributeDataKeys', function () { |
|||
if (ngModelCtrl.$viewValue) { |
|||
var dataKeys = []; |
|||
dataKeys = dataKeys.concat(scope.timeseriesDataKeys); |
|||
dataKeys = dataKeys.concat(scope.attributeDataKeys); |
|||
ngModelCtrl.$viewValue.dataKeys = dataKeys; |
|||
scope.updateValidity(); |
|||
} |
|||
}, true); |
|||
|
|||
ngModelCtrl.$render = function () { |
|||
if (ngModelCtrl.$viewValue) { |
|||
var deviceAliasId = ngModelCtrl.$viewValue.deviceAliasId; |
|||
if (scope.deviceAliases[deviceAliasId]) { |
|||
scope.deviceAlias = {id: deviceAliasId, alias: scope.deviceAliases[deviceAliasId].alias, |
|||
deviceId: scope.deviceAliases[deviceAliasId].deviceId}; |
|||
} else { |
|||
scope.deviceAlias = null; |
|||
} |
|||
var timeseriesDataKeys = []; |
|||
var attributeDataKeys = []; |
|||
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); |
|||
} |
|||
} |
|||
scope.timeseriesDataKeys = timeseriesDataKeys; |
|||
scope.attributeDataKeys = attributeDataKeys; |
|||
} |
|||
}; |
|||
|
|||
scope.textIsNotEmpty = function(text) { |
|||
return (text && text != null && text.length > 0) ? true : false; |
|||
} |
|||
|
|||
scope.selectedDeviceAliasChange = function () { |
|||
if (!scope.timeseriesDataKeySearchText || scope.timeseriesDataKeySearchText === '') { |
|||
scope.timeseriesDataKeySearchText = scope.timeseriesDataKeySearchText === '' ? null : ''; |
|||
} |
|||
if (!scope.attributeDataKeySearchText || scope.attributeDataKeySearchText === '') { |
|||
scope.attributeDataKeySearchText = scope.attributeDataKeySearchText === '' ? null : ''; |
|||
} |
|||
}; |
|||
|
|||
scope.transformTimeseriesDataKeyChip = function (chip) { |
|||
return scope.generateDataKey({chip: chip, type: types.dataKeyType.timeseries}); |
|||
}; |
|||
|
|||
scope.transformAttributeDataKeyChip = function (chip) { |
|||
return scope.generateDataKey({chip: chip, type: types.dataKeyType.attribute}); |
|||
}; |
|||
|
|||
scope.showColorPicker = function (event, dataKey) { |
|||
$mdColorPicker.show({ |
|||
value: dataKey.color, |
|||
defaultValue: '#fff', |
|||
random: tinycolor.random(), |
|||
clickOutsideToClose: false, |
|||
hasBackdrop: false, |
|||
skipHide: true, |
|||
preserveScope: false, |
|||
|
|||
mdColorAlphaChannel: true, |
|||
mdColorSpectrum: true, |
|||
mdColorSliders: true, |
|||
mdColorGenericPalette: false, |
|||
mdColorMaterialPalette: true, |
|||
mdColorHistory: false, |
|||
mdColorDefaultTab: 2, |
|||
|
|||
$event: event |
|||
|
|||
}).then(function (color) { |
|||
dataKey.color = color; |
|||
ngModelCtrl.$setDirty(); |
|||
}); |
|||
} |
|||
|
|||
scope.editDataKey = function (event, dataKey, index) { |
|||
|
|||
$mdDialog.show({ |
|||
controller: 'DatakeyConfigDialogController', |
|||
controllerAs: 'vm', |
|||
templateUrl: datakeyConfigDialogTemplate, |
|||
locals: { |
|||
dataKey: angular.copy(dataKey), |
|||
dataKeySettingsSchema: scope.datakeySettingsSchema, |
|||
deviceAlias: scope.deviceAlias, |
|||
deviceAliases: scope.deviceAliases |
|||
}, |
|||
parent: angular.element($document[0].body), |
|||
fullscreen: true, |
|||
targetEvent: event, |
|||
skipHide: true, |
|||
onComplete: function () { |
|||
var w = angular.element($window); |
|||
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; |
|||
} |
|||
ngModelCtrl.$setDirty(); |
|||
}, function () { |
|||
}); |
|||
}; |
|||
|
|||
scope.dataKeysSearch = function (searchText, type) { |
|||
if (scope.deviceAlias) { |
|||
var deferred = $q.defer(); |
|||
scope.fetchDeviceKeys({deviceAliasId: scope.deviceAlias.id, query: searchText, type: type}) |
|||
.then(function (dataKeys) { |
|||
deferred.resolve(dataKeys); |
|||
}, function (e) { |
|||
deferred.reject(e); |
|||
}); |
|||
return deferred.promise; |
|||
} else { |
|||
return $q.when([]); |
|||
} |
|||
}; |
|||
|
|||
scope.createKey = function (event, 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.resetChipBuffer(); |
|||
} |
|||
|
|||
$compile(element.contents())(scope); |
|||
} |
|||
|
|||
return { |
|||
restrict: "E", |
|||
require: "^ngModel", |
|||
scope: { |
|||
widgetType: '=', |
|||
deviceAliases: '=', |
|||
datakeySettingsSchema: '=', |
|||
generateDataKey: '&', |
|||
fetchDeviceKeys: '&', |
|||
onCreateDeviceAlias: '&' |
|||
}, |
|||
link: linker |
|||
}; |
|||
} |
|||
|
|||
/* eslint-enable angular/angularelement */ |
|||
@ -1,44 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2017 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 '../../scss/constants'; |
|||
|
|||
.tb-device-alias-autocomplete, .tb-timeseries-datakey-autocomplete, .tb-attribute-datakey-autocomplete { |
|||
.tb-not-found { |
|||
display: block; |
|||
line-height: 1.5; |
|||
height: 48px; |
|||
.tb-no-entries { |
|||
line-height: 48px; |
|||
} |
|||
} |
|||
li { |
|||
height: auto !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; |
|||
} |
|||
} |
|||
} |
|||
@ -1,137 +0,0 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2017 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. |
|||
|
|||
--> |
|||
<section flex layout='column' layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center"> |
|||
<tb-device-alias-select |
|||
tb-required="true" |
|||
device-aliases="deviceAliases" |
|||
ng-model="deviceAlias" |
|||
on-create-device-alias="onCreateDeviceAlias({event: event, alias: alias})"> |
|||
</tb-device-alias-select> |
|||
<section flex layout='column'> |
|||
<section flex layout='column' layout-align="center" style="padding-left: 4px;"> |
|||
<md-chips flex |
|||
id="timeseries_datakey_chips" |
|||
ng-required="true" |
|||
ng-model="timeseriesDataKeys" md-autocomplete-snap |
|||
md-transform-chip="transformTimeseriesDataKeyChip($chip)" |
|||
md-require-match="false"> |
|||
<md-autocomplete |
|||
md-no-cache="true" |
|||
id="timeseries_datakey" |
|||
md-selected-item="selectedTimeseriesDataKey" |
|||
md-search-text="timeseriesDataKeySearchText" |
|||
md-items="item in dataKeysSearch(timeseriesDataKeySearchText, types.dataKeyType.timeseries)" |
|||
md-item-text="item.name" |
|||
md-min-length="0" |
|||
placeholder="{{'datakey.timeseries' | translate }}" |
|||
md-menu-class="tb-timeseries-datakey-autocomplete"> |
|||
<span md-highlight-text="timeseriesDataKeySearchText" md-highlight-flags="^i">{{item}}</span> |
|||
<md-not-found> |
|||
<div class="tb-not-found"> |
|||
<div class="tb-no-entries" ng-if="!textIsNotEmpty(timeseriesDataKeySearchText)"> |
|||
<span translate>device.no-keys-found</span> |
|||
</div> |
|||
<div ng-if="textIsNotEmpty(timeseriesDataKeySearchText)"> |
|||
<span translate translate-values='{ key: "{{timeseriesDataKeySearchText | truncate:true:6:'...'}}" }'>device.no-key-matching</span> |
|||
<span> |
|||
<a translate ng-click="createKey($event, '#timeseries_datakey_chips')">device.create-new-key</a> |
|||
</span> |
|||
</div> |
|||
</div> |
|||
</md-not-found> |
|||
</md-autocomplete> |
|||
<md-chip-template> |
|||
<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-result" ng-style="{background: $chip.color}"></div> |
|||
</div> |
|||
<div layout="row" flex> |
|||
<div class="tb-chip-label"> |
|||
{{$chip.label}} |
|||
</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> |
|||
<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-chips> |
|||
<md-chips flex ng-if="widgetType === types.widgetType.latest.value" |
|||
id="attribute_datakey_chips" |
|||
ng-required="true" |
|||
ng-model="attributeDataKeys" md-autocomplete-snap |
|||
md-transform-chip="transformAttributeDataKeyChip($chip)" |
|||
md-require-match="false"> |
|||
<md-autocomplete |
|||
md-no-cache="true" |
|||
id="attribute_datakey" |
|||
md-selected-item="selectedAttributeDataKey" |
|||
md-search-text="attributeDataKeySearchText" |
|||
md-items="item in dataKeysSearch(attributeDataKeySearchText, types.dataKeyType.attribute)" |
|||
md-item-text="item.name" |
|||
md-min-length="0" |
|||
placeholder="{{'datakey.attributes' | translate }}" |
|||
md-menu-class="tb-attribute-datakey-autocomplete"> |
|||
<span md-highlight-text="attributeDataKeySearchText" md-highlight-flags="^i">{{item}}</span> |
|||
<md-not-found> |
|||
<div class="tb-not-found"> |
|||
<div class="tb-no-entries" ng-if="!textIsNotEmpty(attributeDataKeySearchText)"> |
|||
<span translate>device.no-keys-found</span> |
|||
</div> |
|||
<div ng-if="textIsNotEmpty(attributeDataKeySearchText)"> |
|||
<span translate translate-values='{ key: "{{attributeDataKeySearchText | truncate:true:6:'...'}}" }'>device.no-key-matching</span> |
|||
<span> |
|||
<a translate ng-click="createKey($event, '#attribute_datakey_chips')">device.create-new-key</a> |
|||
</span> |
|||
</div> |
|||
</div> |
|||
</md-not-found> |
|||
</md-autocomplete> |
|||
<md-chip-template> |
|||
<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-result" ng-style="{background: $chip.color}"></div> |
|||
</div> |
|||
<div layout="row" flex> |
|||
<div class="tb-chip-label"> |
|||
{{$chip.label}} |
|||
</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> |
|||
<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-chips> |
|||
</section> |
|||
<div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert"> |
|||
<div translate ng-message="deviceKeys" ng-if="widgetType === types.widgetType.timeseries.value" class="tb-error-message">datakey.timeseries-required</div> |
|||
<div translate ng-message="deviceKeys" ng-if="widgetType === types.widgetType.latest.value" class="tb-error-message">datakey.timeseries-or-attributes-required</div> |
|||
</div> |
|||
</section> |
|||
</section> |
|||
@ -1,144 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2017 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 $ from 'jquery'; |
|||
|
|||
import './device-alias-select.scss'; |
|||
|
|||
/* eslint-disable import/no-unresolved, import/default */ |
|||
|
|||
import deviceAliasSelectTemplate from './device-alias-select.tpl.html'; |
|||
|
|||
/* eslint-enable import/no-unresolved, import/default */ |
|||
|
|||
|
|||
/* eslint-disable angular/angularelement */ |
|||
|
|||
export default angular.module('thingsboard.directives.deviceAliasSelect', []) |
|||
.directive('tbDeviceAliasSelect', DeviceAliasSelect) |
|||
.name; |
|||
|
|||
/*@ngInject*/ |
|||
function DeviceAliasSelect($compile, $templateCache, $mdConstant) { |
|||
|
|||
var linker = function (scope, element, attrs, ngModelCtrl) { |
|||
var template = $templateCache.get(deviceAliasSelectTemplate); |
|||
element.html(template); |
|||
|
|||
scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false; |
|||
|
|||
scope.ngModelCtrl = ngModelCtrl; |
|||
scope.deviceAliasList = []; |
|||
scope.deviceAlias = null; |
|||
|
|||
scope.updateValidity = function () { |
|||
var value = ngModelCtrl.$viewValue; |
|||
var valid = angular.isDefined(value) && value != null || !scope.tbRequired; |
|||
ngModelCtrl.$setValidity('deviceAlias', valid); |
|||
}; |
|||
|
|||
scope.$watch('deviceAliases', function () { |
|||
scope.deviceAliasList = []; |
|||
for (var aliasId in scope.deviceAliases) { |
|||
var deviceAlias = {id: aliasId, alias: scope.deviceAliases[aliasId].alias, deviceId: scope.deviceAliases[aliasId].deviceId}; |
|||
scope.deviceAliasList.push(deviceAlias); |
|||
} |
|||
}, true); |
|||
|
|||
scope.$watch('deviceAlias', function () { |
|||
scope.updateView(); |
|||
}); |
|||
|
|||
scope.deviceAliasSearch = function (deviceAliasSearchText) { |
|||
return deviceAliasSearchText ? scope.deviceAliasList.filter( |
|||
scope.createFilterForDeviceAlias(deviceAliasSearchText)) : scope.deviceAliasList; |
|||
}; |
|||
|
|||
scope.createFilterForDeviceAlias = function (query) { |
|||
var lowercaseQuery = angular.lowercase(query); |
|||
return function filterFn(deviceAlias) { |
|||
return (angular.lowercase(deviceAlias.alias).indexOf(lowercaseQuery) === 0); |
|||
}; |
|||
}; |
|||
|
|||
scope.updateView = function () { |
|||
ngModelCtrl.$setViewValue(scope.deviceAlias); |
|||
scope.updateValidity(); |
|||
} |
|||
|
|||
ngModelCtrl.$render = function () { |
|||
if (ngModelCtrl.$viewValue) { |
|||
scope.deviceAlias = ngModelCtrl.$viewValue; |
|||
} |
|||
} |
|||
|
|||
scope.textIsNotEmpty = function(text) { |
|||
return (text && text != null && text.length > 0) ? true : false; |
|||
} |
|||
|
|||
scope.deviceAliasEnter = function($event) { |
|||
if ($event.keyCode === $mdConstant.KEY_CODE.ENTER) { |
|||
$event.preventDefault(); |
|||
if (!scope.deviceAlias) { |
|||
var found = scope.deviceAliasSearch(scope.deviceAliasSearchText); |
|||
found = found.length > 0; |
|||
if (!found) { |
|||
scope.createDeviceAlias($event, scope.deviceAliasSearchText); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
scope.createDeviceAlias = function (event, alias) { |
|||
var autoChild = $('#device-autocomplete', element)[0].firstElementChild; |
|||
var el = angular.element(autoChild); |
|||
el.scope().$mdAutocompleteCtrl.hidden = true; |
|||
el.scope().$mdAutocompleteCtrl.hasNotFound = false; |
|||
event.preventDefault(); |
|||
var promise = scope.onCreateDeviceAlias({event: event, alias: alias}); |
|||
if (promise) { |
|||
promise.then( |
|||
function success(newAlias) { |
|||
el.scope().$mdAutocompleteCtrl.hasNotFound = true; |
|||
if (newAlias) { |
|||
scope.deviceAliasList.push(newAlias); |
|||
scope.deviceAlias = newAlias; |
|||
} |
|||
}, |
|||
function fail() { |
|||
el.scope().$mdAutocompleteCtrl.hasNotFound = true; |
|||
} |
|||
); |
|||
} else { |
|||
el.scope().$mdAutocompleteCtrl.hasNotFound = true; |
|||
} |
|||
}; |
|||
|
|||
$compile(element.contents())(scope); |
|||
} |
|||
|
|||
return { |
|||
restrict: "E", |
|||
require: "^ngModel", |
|||
link: linker, |
|||
scope: { |
|||
tbRequired: '=?', |
|||
deviceAliases: '=', |
|||
onCreateDeviceAlias: '&' |
|||
} |
|||
}; |
|||
} |
|||
|
|||
/* eslint-enable angular/angularelement */ |
|||
@ -1,52 +0,0 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2017 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. |
|||
|
|||
--> |
|||
<section layout='column'> |
|||
<md-autocomplete id="device-autocomplete" |
|||
md-input-name="device_alias" |
|||
ng-required="tbRequired" |
|||
ng-model="deviceAlias" |
|||
md-selected-item="deviceAlias" |
|||
md-search-text="deviceAliasSearchText" |
|||
md-items="item in deviceAliasSearch(deviceAliasSearchText)" |
|||
md-item-text="item.alias" |
|||
tb-keydown="deviceAliasEnter($event)" |
|||
tb-keypress="deviceAliasEnter($event)" |
|||
md-min-length="0" |
|||
placeholder="{{ 'device.device-alias' | translate }}" |
|||
md-menu-class="tb-device-alias-autocomplete"> |
|||
<md-item-template> |
|||
<span md-highlight-text="deviceAliasSearchText" md-highlight-flags="^i">{{item.alias}}</span> |
|||
</md-item-template> |
|||
<md-not-found> |
|||
<div class="tb-not-found"> |
|||
<div class="tb-no-entries" ng-if="!textIsNotEmpty(deviceAliasSearchText)"> |
|||
<span translate>device.no-aliases-found</span> |
|||
</div> |
|||
<div ng-if="textIsNotEmpty(deviceAliasSearchText)"> |
|||
<span translate translate-values='{ alias: "{{deviceAliasSearchText | truncate:true:6:'...'}}" }'>device.no-alias-matching</span> |
|||
<span> |
|||
<a translate ng-click="createDeviceAlias($event, deviceAliasSearchText)">device.create-new-alias</a> |
|||
</span> |
|||
</div> |
|||
</div> |
|||
</md-not-found> |
|||
</md-autocomplete> |
|||
<div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert"> |
|||
<div translate ng-message="deviceAlias" class="tb-error-message">device.alias-required</div> |
|||
</div> |
|||
</section> |
|||
@ -1,217 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2017 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. |
|||
*/ |
|||
|
|||
/* eslint-disable import/no-unresolved, import/default */ |
|||
|
|||
import deviceFilterTemplate from './device-filter.tpl.html'; |
|||
|
|||
/* eslint-enable import/no-unresolved, import/default */ |
|||
|
|||
import './device-filter.scss'; |
|||
|
|||
export default angular.module('thingsboard.directives.deviceFilter', []) |
|||
.directive('tbDeviceFilter', DeviceFilter) |
|||
.name; |
|||
|
|||
/*@ngInject*/ |
|||
function DeviceFilter($compile, $templateCache, $q, deviceService) { |
|||
|
|||
var linker = function (scope, element, attrs, ngModelCtrl) { |
|||
|
|||
var template = $templateCache.get(deviceFilterTemplate); |
|||
element.html(template); |
|||
|
|||
scope.ngModelCtrl = ngModelCtrl; |
|||
|
|||
scope.fetchDevices = function(searchText, limit) { |
|||
var pageLink = {limit: limit, textSearch: searchText}; |
|||
|
|||
var deferred = $q.defer(); |
|||
|
|||
deviceService.getTenantDevices(pageLink, false).then(function success(result) { |
|||
deferred.resolve(result.data); |
|||
}, function fail() { |
|||
deferred.reject(); |
|||
}); |
|||
|
|||
return deferred.promise; |
|||
} |
|||
|
|||
scope.updateValidity = function() { |
|||
if (ngModelCtrl.$viewValue) { |
|||
var value = ngModelCtrl.$viewValue; |
|||
var valid; |
|||
if (value.useFilter) { |
|||
ngModelCtrl.$setValidity('deviceList', true); |
|||
if (angular.isDefined(value.deviceNameFilter) && value.deviceNameFilter.length > 0) { |
|||
ngModelCtrl.$setValidity('deviceNameFilter', true); |
|||
valid = angular.isDefined(scope.model.matchingFilterDevice) && scope.model.matchingFilterDevice != null; |
|||
ngModelCtrl.$setValidity('deviceNameFilterDeviceMatch', valid); |
|||
} else { |
|||
ngModelCtrl.$setValidity('deviceNameFilter', false); |
|||
} |
|||
} else { |
|||
ngModelCtrl.$setValidity('deviceNameFilter', true); |
|||
ngModelCtrl.$setValidity('deviceNameFilterDeviceMatch', true); |
|||
valid = angular.isDefined(value.deviceList) && value.deviceList.length > 0; |
|||
ngModelCtrl.$setValidity('deviceList', valid); |
|||
} |
|||
} |
|||
} |
|||
|
|||
ngModelCtrl.$render = function () { |
|||
destroyWatchers(); |
|||
scope.model = { |
|||
useFilter: false, |
|||
deviceList: [], |
|||
deviceNameFilter: '' |
|||
} |
|||
if (ngModelCtrl.$viewValue) { |
|||
var value = ngModelCtrl.$viewValue; |
|||
var model = scope.model; |
|||
model.useFilter = value.useFilter === true ? true: false; |
|||
model.deviceList = []; |
|||
model.deviceNameFilter = value.deviceNameFilter || ''; |
|||
processDeviceNameFilter(model.deviceNameFilter).then( |
|||
function(device) { |
|||
scope.model.matchingFilterDevice = device; |
|||
if (value.deviceList && value.deviceList.length > 0) { |
|||
deviceService.getDevices(value.deviceList).then(function (devices) { |
|||
model.deviceList = devices; |
|||
updateMatchingDevice(); |
|||
initWatchers(); |
|||
}); |
|||
} else { |
|||
updateMatchingDevice(); |
|||
initWatchers(); |
|||
} |
|||
} |
|||
) |
|||
} |
|||
} |
|||
|
|||
function updateMatchingDevice() { |
|||
if (scope.model.useFilter) { |
|||
scope.model.matchingDevice = scope.model.matchingFilterDevice; |
|||
} else { |
|||
if (scope.model.deviceList && scope.model.deviceList.length > 0) { |
|||
scope.model.matchingDevice = scope.model.deviceList[0]; |
|||
} else { |
|||
scope.model.matchingDevice = null; |
|||
} |
|||
} |
|||
} |
|||
|
|||
function processDeviceNameFilter(deviceNameFilter) { |
|||
var deferred = $q.defer(); |
|||
if (angular.isDefined(deviceNameFilter) && deviceNameFilter.length > 0) { |
|||
scope.fetchDevices(deviceNameFilter, 1).then(function (devices) { |
|||
if (devices && devices.length > 0) { |
|||
deferred.resolve(devices[0]); |
|||
} else { |
|||
deferred.resolve(null); |
|||
} |
|||
}); |
|||
} else { |
|||
deferred.resolve(null); |
|||
} |
|||
return deferred.promise; |
|||
} |
|||
|
|||
function destroyWatchers() { |
|||
if (scope.deviceListDeregistration) { |
|||
scope.deviceListDeregistration(); |
|||
scope.deviceListDeregistration = null; |
|||
} |
|||
if (scope.useFilterDeregistration) { |
|||
scope.useFilterDeregistration(); |
|||
scope.useFilterDeregistration = null; |
|||
} |
|||
if (scope.deviceNameFilterDeregistration) { |
|||
scope.deviceNameFilterDeregistration(); |
|||
scope.deviceNameFilterDeregistration = null; |
|||
} |
|||
if (scope.matchingDeviceDeregistration) { |
|||
scope.matchingDeviceDeregistration(); |
|||
scope.matchingDeviceDeregistration = null; |
|||
} |
|||
} |
|||
|
|||
function initWatchers() { |
|||
scope.deviceListDeregistration = scope.$watch('model.deviceList', function () { |
|||
if (ngModelCtrl.$viewValue) { |
|||
var value = ngModelCtrl.$viewValue; |
|||
value.deviceList = []; |
|||
if (scope.model.deviceList && scope.model.deviceList.length > 0) { |
|||
for (var i in scope.model.deviceList) { |
|||
value.deviceList.push(scope.model.deviceList[i].id.id); |
|||
} |
|||
} |
|||
updateMatchingDevice(); |
|||
ngModelCtrl.$setViewValue(value); |
|||
scope.updateValidity(); |
|||
} |
|||
}, true); |
|||
scope.useFilterDeregistration = scope.$watch('model.useFilter', function () { |
|||
if (ngModelCtrl.$viewValue) { |
|||
var value = ngModelCtrl.$viewValue; |
|||
value.useFilter = scope.model.useFilter; |
|||
updateMatchingDevice(); |
|||
ngModelCtrl.$setViewValue(value); |
|||
scope.updateValidity(); |
|||
} |
|||
}); |
|||
scope.deviceNameFilterDeregistration = scope.$watch('model.deviceNameFilter', function (newNameFilter, prevNameFilter) { |
|||
if (ngModelCtrl.$viewValue) { |
|||
if (!angular.equals(newNameFilter, prevNameFilter)) { |
|||
var value = ngModelCtrl.$viewValue; |
|||
value.deviceNameFilter = scope.model.deviceNameFilter; |
|||
processDeviceNameFilter(value.deviceNameFilter).then( |
|||
function(device) { |
|||
scope.model.matchingFilterDevice = device; |
|||
updateMatchingDevice(); |
|||
ngModelCtrl.$setViewValue(value); |
|||
scope.updateValidity(); |
|||
} |
|||
); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
scope.matchingDeviceDeregistration = scope.$watch('model.matchingDevice', function (newMatchingDevice, prevMatchingDevice) { |
|||
if (!angular.equals(newMatchingDevice, prevMatchingDevice)) { |
|||
if (scope.onMatchingDeviceChange) { |
|||
scope.onMatchingDeviceChange({device: newMatchingDevice}); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
$compile(element.contents())(scope); |
|||
|
|||
} |
|||
|
|||
return { |
|||
restrict: "E", |
|||
require: "^ngModel", |
|||
link: linker, |
|||
scope: { |
|||
isEdit: '=', |
|||
onMatchingDeviceChange: '&' |
|||
} |
|||
}; |
|||
|
|||
} |
|||
@ -1,45 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2017 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. |
|||
*/ |
|||
.tb-device-filter { |
|||
#device_list_chips { |
|||
.md-chips { |
|||
padding-bottom: 1px; |
|||
} |
|||
} |
|||
.device-name-filter-input { |
|||
margin-top: 10px; |
|||
margin-bottom: 0px; |
|||
.md-errors-spacer { |
|||
min-height: 0px; |
|||
} |
|||
} |
|||
.tb-filter-switch { |
|||
padding-left: 10px; |
|||
.filter-switch { |
|||
margin: 0; |
|||
} |
|||
.filter-label { |
|||
margin: 5px 0; |
|||
} |
|||
} |
|||
.tb-error-messages { |
|||
margin-top: -11px; |
|||
height: 35px; |
|||
.tb-error-message { |
|||
padding-left: 1px; |
|||
} |
|||
} |
|||
} |
|||
@ -1,67 +0,0 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2017 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. |
|||
|
|||
--> |
|||
<section layout='column' class="tb-device-filter"> |
|||
<section layout='row'> |
|||
<section layout="column" flex ng-show="!model.useFilter"> |
|||
<md-chips flex |
|||
id="device_list_chips" |
|||
ng-required="!useFilter" |
|||
ng-model="model.deviceList" md-autocomplete-snap |
|||
md-require-match="true"> |
|||
<md-autocomplete |
|||
md-no-cache="true" |
|||
id="device" |
|||
md-selected-item="selectedDevice" |
|||
md-search-text="deviceSearchText" |
|||
md-items="item in fetchDevices(deviceSearchText, 10)" |
|||
md-item-text="item.name" |
|||
md-min-length="0" |
|||
placeholder="{{ 'device.device-list' | translate }}"> |
|||
<md-item-template> |
|||
<span md-highlight-text="deviceSearchText" md-highlight-flags="^i">{{item.name}}</span> |
|||
</md-item-template> |
|||
<md-not-found> |
|||
<span translate translate-values='{ device: deviceSearchText }'>device.no-devices-matching</span> |
|||
</md-not-found> |
|||
</md-autocomplete> |
|||
<md-chip-template> |
|||
<span> |
|||
<strong>{{$chip.name}}</strong> |
|||
</span> |
|||
</md-chip-template> |
|||
</md-chips> |
|||
</section> |
|||
<section layout="row" flex ng-show="model.useFilter"> |
|||
<md-input-container flex class="device-name-filter-input"> |
|||
<label translate>device.name-starts-with</label> |
|||
<input ng-model="model.deviceNameFilter" aria-label="{{ 'device.name-starts-with' | translate }}"> |
|||
</md-input-container> |
|||
</section> |
|||
<section class="tb-filter-switch" layout="column" layout-align="center center"> |
|||
<label class="tb-small filter-label" translate>device.use-device-name-filter</label> |
|||
<md-switch class="filter-switch" ng-model="model.useFilter" aria-label="use-filter-switcher"> |
|||
</md-switch> |
|||
</section> |
|||
</section> |
|||
<div class="tb-error-messages" ng-messages="ngModelCtrl.$error" role="alert"> |
|||
<div translate ng-message="deviceList" class="tb-error-message">device.device-list-empty</div> |
|||
<div translate ng-message="deviceNameFilter" class="tb-error-message">device.device-name-filter-required</div> |
|||
<div translate translate-values='{ device: model.deviceNameFilter }' ng-message="deviceNameFilterDeviceMatch" |
|||
class="tb-error-message">device.device-name-filter-no-device-matched</div> |
|||
</div> |
|||
</section> |
|||
@ -1,32 +0,0 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2017 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. |
|||
|
|||
--> |
|||
|
|||
<section class="tb-aliases-device-select" layout='row' layout-align="start center" ng-style="{minHeight: '32px', padding: '0 6px'}"> |
|||
<md-button class="md-icon-button" aria-label="{{ 'dashboard.select-devices' | translate }}" ng-click="openEditMode($event)"> |
|||
<md-tooltip md-direction="{{tooltipDirection}}"> |
|||
{{ 'dashboard.select-devices' | translate }} |
|||
</md-tooltip> |
|||
<md-icon aria-label="{{ 'dashboard.select-devices' | translate }}" class="material-icons">devices_other</md-icon> |
|||
</md-button> |
|||
<span hide-xs hide-sm ng-click="openEditMode($event)"> |
|||
<md-tooltip md-direction="{{tooltipDirection}}"> |
|||
{{ 'dashboard.select-devices' | translate }} |
|||
</md-tooltip> |
|||
{{displayValue}} |
|||
</span> |
|||
</section> |
|||
@ -1,31 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2017 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. |
|||
*/ |
|||
|
|||
/*@ngInject*/ |
|||
export default function AliasesDeviceSelectPanelController(mdPanelRef, $scope, types, deviceAliases, deviceAliasesInfo, onDeviceAliasesUpdate) { |
|||
|
|||
var vm = this; |
|||
vm._mdPanelRef = mdPanelRef; |
|||
vm.deviceAliases = deviceAliases; |
|||
vm.deviceAliasesInfo = deviceAliasesInfo; |
|||
vm.onDeviceAliasesUpdate = onDeviceAliasesUpdate; |
|||
|
|||
$scope.$watch('vm.deviceAliases', function () { |
|||
if (onDeviceAliasesUpdate) { |
|||
onDeviceAliasesUpdate(vm.deviceAliases); |
|||
} |
|||
}, true); |
|||
} |
|||
@ -1,33 +0,0 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2017 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. |
|||
|
|||
--> |
|||
<md-content flex layout="column"> |
|||
<section flex layout="column"> |
|||
<md-content flex class="md-padding" layout="column"> |
|||
<div flex layout="row" ng-repeat="(aliasId, deviceAlias) in vm.deviceAliases"> |
|||
<md-input-container flex> |
|||
<label>{{deviceAlias.alias}}</label> |
|||
<md-select ng-model="vm.deviceAliases[aliasId].deviceId"> |
|||
<md-option ng-repeat="deviceInfo in vm.deviceAliasesInfo[aliasId]" ng-value="deviceInfo.id"> |
|||
{{deviceInfo.name}} |
|||
</md-option> |
|||
</md-select> |
|||
</md-input-container> |
|||
</div> |
|||
</md-content> |
|||
</section> |
|||
</md-content> |
|||
@ -1,153 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2017 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 './aliases-device-select.scss'; |
|||
|
|||
import $ from 'jquery'; |
|||
|
|||
/* eslint-disable import/no-unresolved, import/default */ |
|||
|
|||
import aliasesDeviceSelectButtonTemplate from './aliases-device-select-button.tpl.html'; |
|||
import aliasesDeviceSelectPanelTemplate from './aliases-device-select-panel.tpl.html'; |
|||
|
|||
/* eslint-enable import/no-unresolved, import/default */ |
|||
|
|||
/* eslint-disable angular/angularelement */ |
|||
/*@ngInject*/ |
|||
export default function AliasesDeviceSelectDirective($compile, $templateCache, $mdMedia, types, $mdPanel, $document, $translate) { |
|||
|
|||
var linker = function (scope, element, attrs, ngModelCtrl) { |
|||
|
|||
/* tbAliasesDeviceSelect (ng-model) |
|||
* { |
|||
* "aliasId": { |
|||
* alias: alias, |
|||
* deviceId: deviceId |
|||
* } |
|||
* } |
|||
*/ |
|||
|
|||
var template = $templateCache.get(aliasesDeviceSelectButtonTemplate); |
|||
|
|||
scope.tooltipDirection = angular.isDefined(attrs.tooltipDirection) ? attrs.tooltipDirection : 'top'; |
|||
|
|||
element.html(template); |
|||
|
|||
scope.openEditMode = function (event) { |
|||
if (scope.disabled) { |
|||
return; |
|||
} |
|||
var position; |
|||
var panelHeight = $mdMedia('min-height: 350px') ? 250 : 150; |
|||
var panelWidth = 300; |
|||
var offset = element[0].getBoundingClientRect(); |
|||
var bottomY = offset.bottom - $(window).scrollTop(); //eslint-disable-line
|
|||
var leftX = offset.left - $(window).scrollLeft(); //eslint-disable-line
|
|||
var yPosition; |
|||
var xPosition; |
|||
if (bottomY + panelHeight > $( window ).height()) { //eslint-disable-line
|
|||
yPosition = $mdPanel.yPosition.ABOVE; |
|||
} else { |
|||
yPosition = $mdPanel.yPosition.BELOW; |
|||
} |
|||
if (leftX + panelWidth > $( window ).width()) { //eslint-disable-line
|
|||
xPosition = $mdPanel.xPosition.CENTER; |
|||
} else { |
|||
xPosition = $mdPanel.xPosition.ALIGN_START; |
|||
} |
|||
position = $mdPanel.newPanelPosition() |
|||
.relativeTo(element) |
|||
.addPanelPosition(xPosition, yPosition); |
|||
var config = { |
|||
attachTo: angular.element($document[0].body), |
|||
controller: 'AliasesDeviceSelectPanelController', |
|||
controllerAs: 'vm', |
|||
templateUrl: aliasesDeviceSelectPanelTemplate, |
|||
panelClass: 'tb-aliases-device-select-panel', |
|||
position: position, |
|||
fullscreen: false, |
|||
locals: { |
|||
'deviceAliases': angular.copy(scope.model), |
|||
'deviceAliasesInfo': scope.deviceAliasesInfo, |
|||
'onDeviceAliasesUpdate': function (deviceAliases) { |
|||
scope.model = deviceAliases; |
|||
scope.updateView(); |
|||
} |
|||
}, |
|||
openFrom: event, |
|||
clickOutsideToClose: true, |
|||
escapeToClose: true, |
|||
focusOnOpen: false |
|||
}; |
|||
$mdPanel.open(config); |
|||
} |
|||
|
|||
scope.updateView = function () { |
|||
var value = angular.copy(scope.model); |
|||
ngModelCtrl.$setViewValue(value); |
|||
updateDisplayValue(); |
|||
} |
|||
|
|||
ngModelCtrl.$render = function () { |
|||
if (ngModelCtrl.$viewValue) { |
|||
var value = ngModelCtrl.$viewValue; |
|||
scope.model = angular.copy(value); |
|||
updateDisplayValue(); |
|||
} |
|||
} |
|||
|
|||
function updateDisplayValue() { |
|||
var displayValue; |
|||
var singleValue = true; |
|||
var currentAliasId; |
|||
for (var aliasId in scope.model) { |
|||
if (!currentAliasId) { |
|||
currentAliasId = aliasId; |
|||
} else { |
|||
singleValue = false; |
|||
break; |
|||
} |
|||
} |
|||
if (singleValue && currentAliasId) { |
|||
var deviceId = scope.model[currentAliasId].deviceId; |
|||
var devicesInfo = scope.deviceAliasesInfo[currentAliasId]; |
|||
for (var i=0;i<devicesInfo.length;i++) { |
|||
if (devicesInfo[i].id === deviceId) { |
|||
displayValue = devicesInfo[i].name; |
|||
break; |
|||
} |
|||
} |
|||
} else { |
|||
displayValue = $translate.instant('device.devices'); |
|||
} |
|||
scope.displayValue = displayValue; |
|||
} |
|||
|
|||
$compile(element.contents())(scope); |
|||
} |
|||
|
|||
return { |
|||
restrict: "E", |
|||
require: "^ngModel", |
|||
scope: { |
|||
deviceAliasesInfo:'=' |
|||
}, |
|||
link: linker |
|||
}; |
|||
|
|||
} |
|||
|
|||
/* eslint-enable angular/angularelement */ |
|||
@ -1,46 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2017 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. |
|||
*/ |
|||
|
|||
.md-panel { |
|||
&.tb-aliases-device-select-panel { |
|||
position: absolute; |
|||
} |
|||
} |
|||
|
|||
.tb-aliases-device-select-panel { |
|||
max-height: 150px; |
|||
@media (min-height: 350px) { |
|||
max-height: 250px; |
|||
} |
|||
min-width: 300px; |
|||
background: white; |
|||
border-radius: 4px; |
|||
box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2), |
|||
0 13px 19px 2px rgba(0, 0, 0, 0.14), |
|||
0 5px 24px 4px rgba(0, 0, 0, 0.12); |
|||
overflow-x: hidden; |
|||
overflow-y: auto; |
|||
md-content { |
|||
background-color: #fff; |
|||
} |
|||
} |
|||
|
|||
.tb-aliases-device-select { |
|||
span { |
|||
pointer-events: all; |
|||
cursor: pointer; |
|||
} |
|||
} |
|||
@ -1,227 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2017 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 './device-aliases.scss'; |
|||
|
|||
/*@ngInject*/ |
|||
export default function DeviceAliasesController(deviceService, toast, $scope, $mdDialog, $document, $q, $translate, |
|||
types, config) { |
|||
|
|||
var vm = this; |
|||
|
|||
vm.isSingleDeviceAlias = config.isSingleDeviceAlias; |
|||
vm.singleDeviceAlias = config.singleDeviceAlias; |
|||
vm.deviceAliases = []; |
|||
vm.title = config.customTitle ? config.customTitle : 'device.aliases'; |
|||
vm.disableAdd = config.disableAdd; |
|||
vm.aliasToWidgetsMap = {}; |
|||
|
|||
|
|||
vm.onFilterDeviceChanged = onFilterDeviceChanged; |
|||
vm.addAlias = addAlias; |
|||
vm.removeAlias = removeAlias; |
|||
|
|||
vm.cancel = cancel; |
|||
vm.save = save; |
|||
|
|||
initController(); |
|||
|
|||
function initController() { |
|||
var aliasId; |
|||
if (config.widgets) { |
|||
var widgetsTitleList, widget; |
|||
if (config.isSingleWidget && config.widgets.length == 1) { |
|||
widget = config.widgets[0]; |
|||
widgetsTitleList = [widget.config.title]; |
|||
for (aliasId in config.deviceAliases) { |
|||
vm.aliasToWidgetsMap[aliasId] = widgetsTitleList; |
|||
} |
|||
} else { |
|||
for (var w in config.widgets) { |
|||
widget = config.widgets[w]; |
|||
if (widget.type === types.widgetType.rpc.value) { |
|||
if (widget.config.targetDeviceAliasIds && widget.config.targetDeviceAliasIds.length > 0) { |
|||
var targetDeviceAliasId = widget.config.targetDeviceAliasIds[0]; |
|||
widgetsTitleList = vm.aliasToWidgetsMap[targetDeviceAliasId]; |
|||
if (!widgetsTitleList) { |
|||
widgetsTitleList = []; |
|||
vm.aliasToWidgetsMap[targetDeviceAliasId] = widgetsTitleList; |
|||
} |
|||
widgetsTitleList.push(widget.config.title); |
|||
} |
|||
} else { |
|||
for (var i in widget.config.datasources) { |
|||
var datasource = widget.config.datasources[i]; |
|||
if (datasource.type === types.datasourceType.device && datasource.deviceAliasId) { |
|||
widgetsTitleList = vm.aliasToWidgetsMap[datasource.deviceAliasId]; |
|||
if (!widgetsTitleList) { |
|||
widgetsTitleList = []; |
|||
vm.aliasToWidgetsMap[datasource.deviceAliasId] = widgetsTitleList; |
|||
} |
|||
widgetsTitleList.push(widget.config.title); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (vm.isSingleDeviceAlias) { |
|||
if (!vm.singleDeviceAlias.deviceFilter || vm.singleDeviceAlias.deviceFilter == null) { |
|||
vm.singleDeviceAlias.deviceFilter = { |
|||
useFilter: false, |
|||
deviceNameFilter: '', |
|||
deviceList: [], |
|||
}; |
|||
} |
|||
} |
|||
|
|||
for (aliasId in config.deviceAliases) { |
|||
var deviceAlias = config.deviceAliases[aliasId]; |
|||
var alias = deviceAlias.alias; |
|||
var deviceFilter; |
|||
if (!deviceAlias.deviceFilter) { |
|||
deviceFilter = { |
|||
useFilter: false, |
|||
deviceNameFilter: '', |
|||
deviceList: [], |
|||
}; |
|||
if (deviceAlias.deviceId) { |
|||
deviceFilter.deviceList = [deviceAlias.deviceId]; |
|||
} else { |
|||
deviceFilter.deviceList = []; |
|||
} |
|||
} else { |
|||
deviceFilter = deviceAlias.deviceFilter; |
|||
} |
|||
var result = {id: aliasId, alias: alias, deviceFilter: deviceFilter, changed: true}; |
|||
vm.deviceAliases.push(result); |
|||
} |
|||
} |
|||
|
|||
function onFilterDeviceChanged(device, deviceAlias) { |
|||
if (deviceAlias) { |
|||
if (!deviceAlias.alias || deviceAlias.alias.length == 0) { |
|||
deviceAlias.changed = false; |
|||
} |
|||
if (!deviceAlias.changed && device) { |
|||
deviceAlias.alias = device.name; |
|||
} |
|||
} |
|||
} |
|||
|
|||
function addAlias() { |
|||
var aliasId = 0; |
|||
for (var a in vm.deviceAliases) { |
|||
aliasId = Math.max(vm.deviceAliases[a].id, aliasId); |
|||
} |
|||
aliasId++; |
|||
var deviceAlias = {id: aliasId, alias: '', deviceFilter: {useFilter: false, deviceNameFilter: '', deviceList: []}, changed: false}; |
|||
vm.deviceAliases.push(deviceAlias); |
|||
} |
|||
|
|||
function removeAlias($event, deviceAlias) { |
|||
var index = vm.deviceAliases.indexOf(deviceAlias); |
|||
if (index > -1) { |
|||
var widgetsTitleList = vm.aliasToWidgetsMap[deviceAlias.id]; |
|||
if (widgetsTitleList) { |
|||
var widgetsListHtml = ''; |
|||
for (var t in widgetsTitleList) { |
|||
widgetsListHtml += '<br/>\'' + widgetsTitleList[t] + '\''; |
|||
} |
|||
var alert = $mdDialog.alert() |
|||
.parent(angular.element($document[0].body)) |
|||
.clickOutsideToClose(true) |
|||
.title($translate.instant('device.unable-delete-device-alias-title')) |
|||
.htmlContent($translate.instant('device.unable-delete-device-alias-text', {deviceAlias: deviceAlias.alias, widgetsList: widgetsListHtml})) |
|||
.ariaLabel($translate.instant('device.unable-delete-device-alias-title')) |
|||
.ok($translate.instant('action.close')) |
|||
.targetEvent($event); |
|||
alert._options.skipHide = true; |
|||
alert._options.fullscreen = true; |
|||
|
|||
$mdDialog.show(alert); |
|||
} else { |
|||
vm.deviceAliases.splice(index, 1); |
|||
if ($scope.theForm) { |
|||
$scope.theForm.$setDirty(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
function cancel() { |
|||
$mdDialog.cancel(); |
|||
} |
|||
|
|||
function cleanupDeviceFilter(deviceFilter) { |
|||
if (deviceFilter.useFilter) { |
|||
deviceFilter.deviceList = []; |
|||
} else { |
|||
deviceFilter.deviceNameFilter = ''; |
|||
} |
|||
return deviceFilter; |
|||
} |
|||
|
|||
function save() { |
|||
|
|||
var deviceAliases = {}; |
|||
var uniqueAliasList = {}; |
|||
|
|||
var valid = true; |
|||
var aliasId, maxAliasId; |
|||
var alias; |
|||
var i; |
|||
|
|||
if (vm.isSingleDeviceAlias) { |
|||
maxAliasId = 0; |
|||
vm.singleDeviceAlias.deviceFilter = cleanupDeviceFilter(vm.singleDeviceAlias.deviceFilter); |
|||
for (i in vm.deviceAliases) { |
|||
aliasId = vm.deviceAliases[i].id; |
|||
alias = vm.deviceAliases[i].alias; |
|||
if (alias === vm.singleDeviceAlias.alias) { |
|||
valid = false; |
|||
break; |
|||
} |
|||
maxAliasId = Math.max(aliasId, maxAliasId); |
|||
} |
|||
maxAliasId++; |
|||
vm.singleDeviceAlias.id = maxAliasId; |
|||
} else { |
|||
for (i in vm.deviceAliases) { |
|||
aliasId = vm.deviceAliases[i].id; |
|||
alias = vm.deviceAliases[i].alias; |
|||
if (!uniqueAliasList[alias]) { |
|||
uniqueAliasList[alias] = alias; |
|||
deviceAliases[aliasId] = {alias: alias, deviceFilter: cleanupDeviceFilter(vm.deviceAliases[i].deviceFilter)}; |
|||
} else { |
|||
valid = false; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
if (valid) { |
|||
$scope.theForm.$setPristine(); |
|||
if (vm.isSingleDeviceAlias) { |
|||
$mdDialog.hide(vm.singleDeviceAlias); |
|||
} else { |
|||
$mdDialog.hide(deviceAliases); |
|||
} |
|||
} else { |
|||
toast.showError($translate.instant('device.duplicate-alias-error', {alias: alias})); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -1,94 +0,0 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2017 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. |
|||
|
|||
--> |
|||
<md-dialog class="tb-aliases-dialog" style="width: 700px;" aria-label="{{ vm.title | translate }}"> |
|||
<form name="theForm" ng-submit="vm.save()"> |
|||
<md-toolbar> |
|||
<div class="md-toolbar-tools"> |
|||
<h2>{{ vm.isSingleDeviceAlias ? ('device.configure-alias' | translate:vm.singleDeviceAlias ) : (vm.title | translate) }}</h2> |
|||
<span flex></span> |
|||
<md-button class="md-icon-button" ng-click="vm.cancel()"> |
|||
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> |
|||
</md-button> |
|||
</div> |
|||
</md-toolbar> |
|||
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear> |
|||
<span style="min-height: 5px;" flex="" ng-show="!loading"></span> |
|||
<md-dialog-content> |
|||
<div class="md-dialog-content"> |
|||
<fieldset ng-disabled="loading"> |
|||
<div ng-show="vm.isSingleDeviceAlias"> |
|||
<tb-device-filter ng-model="vm.singleDeviceAlias.deviceFilter"> |
|||
</tb-device-filter> |
|||
</div> |
|||
<div ng-show="!vm.isSingleDeviceAlias" flex layout="row" layout-align="start center"> |
|||
<span flex="5"></span> |
|||
<div flex layout="row" layout-align="start center" |
|||
style="padding: 0 0 0 10px; margin: 5px;"> |
|||
<span translate flex="40" style="min-width: 100px;">device.alias</span> |
|||
<span translate flex="60" style="min-width: 190px; padding-left: 10px;">device.devices</span> |
|||
<span style="min-width: 40px;"></span> |
|||
</div> |
|||
</div> |
|||
<div ng-show="!vm.isSingleDeviceAlias" style="max-height: 500px; overflow: auto; padding-bottom: 20px;"> |
|||
<div ng-form name="aliasForm" flex layout="row" layout-align="start center" ng-repeat="deviceAlias in vm.deviceAliases track by $index"> |
|||
<span flex="5">{{$index + 1}}.</span> |
|||
<div class="md-whiteframe-4dp tb-alias" flex layout="row" layout-align="start center"> |
|||
<md-input-container flex="40" style="min-width: 100px;" md-no-float class="md-block"> |
|||
<input required ng-change="deviceAlias.changed=true" name="alias" placeholder="{{ 'device.alias' | translate }}" ng-model="deviceAlias.alias"> |
|||
<div ng-messages="aliasForm.alias.$error"> |
|||
<div translate ng-message="required">device.alias-required</div> |
|||
</div> |
|||
</md-input-container> |
|||
<section flex="60" layout="column"> |
|||
<tb-device-filter style="padding-left: 10px;" |
|||
ng-model="deviceAlias.deviceFilter" |
|||
on-matching-device-change="vm.onFilterDeviceChanged(device, deviceAlias)"> |
|||
</tb-device-filter> |
|||
</section> |
|||
<md-button ng-disabled="loading" class="md-icon-button md-primary" style="min-width: 40px;" |
|||
ng-click="vm.removeAlias($event, deviceAlias)" aria-label="{{ 'action.remove' | translate }}"> |
|||
<md-tooltip md-direction="top"> |
|||
{{ 'device.remove-alias' | translate }} |
|||
</md-tooltip> |
|||
<md-icon aria-label="{{ 'action.delete' | translate }}" class="material-icons"> |
|||
close |
|||
</md-icon> |
|||
</md-button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div ng-show="!vm.isSingleDeviceAlias && !vm.disableAdd" style="padding-bottom: 10px;"> |
|||
<md-button ng-disabled="loading" class="md-primary md-raised" ng-click="vm.addAlias($event)" aria-label="{{ 'action.add' | translate }}"> |
|||
<md-tooltip md-direction="top"> |
|||
{{ 'device.add-alias' | translate }} |
|||
</md-tooltip> |
|||
<span translate>action.add</span> |
|||
</md-button> |
|||
</div> |
|||
</fieldset> |
|||
</div> |
|||
</md-dialog-content> |
|||
<md-dialog-actions layout="row"> |
|||
<span flex></span> |
|||
<md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary"> |
|||
{{ 'action.save' | translate }} |
|||
</md-button> |
|||
<md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button> |
|||
</md-dialog-actions> |
|||
</form> |
|||
</md-dialog> |
|||
@ -1,50 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2017 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. |
|||
*/ |
|||
/*@ngInject*/ |
|||
export default function AddAttributeDialogController($scope, $mdDialog, types, deviceService, deviceId, attributeScope) { |
|||
|
|||
var vm = this; |
|||
|
|||
vm.attribute = {}; |
|||
|
|||
vm.valueTypes = types.valueType; |
|||
|
|||
vm.valueType = types.valueType.string; |
|||
|
|||
vm.add = add; |
|||
vm.cancel = cancel; |
|||
|
|||
function cancel() { |
|||
$mdDialog.cancel(); |
|||
} |
|||
|
|||
function add() { |
|||
$scope.theForm.$setPristine(); |
|||
deviceService.saveDeviceAttributes(deviceId, attributeScope, [vm.attribute]).then( |
|||
function success() { |
|||
$mdDialog.hide(); |
|||
} |
|||
); |
|||
} |
|||
|
|||
$scope.$watch('vm.valueType', function() { |
|||
if (vm.valueType === types.valueType.boolean) { |
|||
vm.attribute.value = false; |
|||
} else { |
|||
vm.attribute.value = null; |
|||
} |
|||
}); |
|||
} |
|||
@ -1,95 +0,0 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2017 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. |
|||
|
|||
--> |
|||
<md-dialog aria-label="{{ 'attribute.add' | translate }}" style="min-width: 400px;"> |
|||
<form name="theForm" ng-submit="vm.add()"> |
|||
<md-toolbar> |
|||
<div class="md-toolbar-tools"> |
|||
<h2 translate>attribute.add</h2> |
|||
<span flex></span> |
|||
<md-button class="md-icon-button" ng-click="vm.cancel()"> |
|||
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> |
|||
</md-button> |
|||
</div> |
|||
</md-toolbar> |
|||
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear> |
|||
<span style="min-height: 5px;" flex="" ng-show="!loading"></span> |
|||
<md-dialog-content> |
|||
<div class="md-dialog-content"> |
|||
<md-content class="md-padding" layout="column"> |
|||
<fieldset ng-disabled="loading"> |
|||
<md-input-container class="md-block"> |
|||
<label translate>attribute.key</label> |
|||
<input required name="key" ng-model="vm.attribute.key"> |
|||
<div ng-messages="theForm.key.$error"> |
|||
<div translate ng-message="required">attribute.key-required</div> |
|||
</div> |
|||
</md-input-container> |
|||
<section layout="row"> |
|||
<md-input-container flex="40" class="md-block" style="width: 200px;"> |
|||
<label translate>value.type</label> |
|||
<md-select ng-model="vm.valueType" ng-disabled="loading()"> |
|||
<md-option ng-repeat="type in vm.valueTypes" ng-value="type"> |
|||
<md-icon md-svg-icon="{{ type.icon }}"></md-icon> |
|||
<span>{{type.name | translate}}</span> |
|||
</md-option> |
|||
</md-select> |
|||
</md-input-container> |
|||
<md-input-container ng-if="vm.valueType===vm.valueTypes.string" flex="60" class="md-block"> |
|||
<label translate>value.string-value</label> |
|||
<input required name="value" ng-model="vm.attribute.value"> |
|||
<div ng-messages="theForm.value.$error"> |
|||
<div translate ng-message="required">attribute.value-required</div> |
|||
</div> |
|||
</md-input-container> |
|||
<md-input-container ng-if="vm.valueType===vm.valueTypes.integer" flex="60" class="md-block"> |
|||
<label translate>value.integer-value</label> |
|||
<input required name="value" type="number" step="1" ng-pattern="/^-?[0-9]+$/" ng-model="vm.attribute.value"> |
|||
<div ng-messages="theForm.value.$error"> |
|||
<div translate ng-message="required">attribute.value-required</div> |
|||
<div translate ng-message="pattern">value.invalid-integer-value</div> |
|||
</div> |
|||
</md-input-container> |
|||
<md-input-container ng-if="vm.valueType===vm.valueTypes.double" flex="60" class="md-block"> |
|||
<label translate>value.double-value</label> |
|||
<input required name="value" type="number" step="any" ng-model="vm.attribute.value"> |
|||
<div ng-messages="theForm.value.$error"> |
|||
<div translate ng-message="required">attribute.value-required</div> |
|||
</div> |
|||
</md-input-container> |
|||
<div layout="column" layout-align="center" flex="60" ng-if="vm.valueType===vm.valueTypes.boolean"> |
|||
<md-checkbox ng-model="vm.attribute.value" style="margin-bottom: 0px;"> |
|||
{{ (vm.attribute.value ? 'value.true' : 'value.false') | translate }} |
|||
</md-checkbox> |
|||
</div> |
|||
</section> |
|||
</fieldset> |
|||
</md-content> |
|||
</div> |
|||
</md-dialog-content> |
|||
<md-dialog-actions layout="row"> |
|||
<span flex></span> |
|||
<md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" |
|||
class="md-raised md-primary"> |
|||
{{ 'action.add' | translate }} |
|||
</md-button> |
|||
<md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | |
|||
translate }} |
|||
</md-button> |
|||
</md-dialog-actions> |
|||
</form> |
|||
</md-dialog> |
|||
@ -1,73 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2017 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. |
|||
*/ |
|||
/*@ngInject*/ |
|||
export default function AddWidgetToDashboardDialogController($scope, $mdDialog, $state, itembuffer, dashboardService, deviceId, deviceName, widget) { |
|||
|
|||
var vm = this; |
|||
|
|||
vm.widget = widget; |
|||
vm.dashboardId = null; |
|||
vm.addToDashboardType = 0; |
|||
vm.newDashboard = {}; |
|||
vm.openDashboard = false; |
|||
|
|||
vm.add = add; |
|||
vm.cancel = cancel; |
|||
|
|||
function cancel() { |
|||
$mdDialog.cancel(); |
|||
} |
|||
|
|||
function add() { |
|||
$scope.theForm.$setPristine(); |
|||
if (vm.addToDashboardType === 0) { |
|||
dashboardService.getDashboard(vm.dashboardId).then( |
|||
function success(dashboard) { |
|||
addWidgetToDashboard(dashboard); |
|||
}, |
|||
function fail() {} |
|||
); |
|||
} else { |
|||
addWidgetToDashboard(vm.newDashboard); |
|||
} |
|||
|
|||
} |
|||
|
|||
function addWidgetToDashboard(theDashboard) { |
|||
var aliasesInfo = { |
|||
datasourceAliases: {}, |
|||
targetDeviceAliases: {} |
|||
}; |
|||
aliasesInfo.datasourceAliases[0] = { |
|||
aliasName: deviceName, |
|||
deviceFilter: { |
|||
useFilter: false, |
|||
deviceNameFilter: '', |
|||
deviceList: [deviceId] |
|||
} |
|||
}; |
|||
theDashboard = itembuffer.addWidgetToDashboard(theDashboard, vm.widget, aliasesInfo, null, 48, -1, -1); |
|||
dashboardService.saveDashboard(theDashboard).then( |
|||
function success(dashboard) { |
|||
$mdDialog.hide(); |
|||
if (vm.openDashboard) { |
|||
$state.go('home.dashboards.dashboard', {dashboardId: dashboard.id.id}); |
|||
} |
|||
} |
|||
); |
|||
} |
|||
|
|||
} |
|||
@ -1,81 +0,0 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2017 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. |
|||
|
|||
--> |
|||
<md-dialog aria-label="{{ 'attribute.add-widget-to-dashboard' | translate }}" style="min-width: 400px;"> |
|||
<form name="theForm" ng-submit="vm.add()"> |
|||
<md-toolbar> |
|||
<div class="md-toolbar-tools"> |
|||
<h2 translate>attribute.add-widget-to-dashboard</h2> |
|||
<span flex></span> |
|||
<md-button class="md-icon-button" ng-click="vm.cancel()"> |
|||
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> |
|||
</md-button> |
|||
</div> |
|||
</md-toolbar> |
|||
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear> |
|||
<span style="min-height: 5px;" flex="" ng-show="!loading"></span> |
|||
<md-dialog-content> |
|||
<div class="md-dialog-content"> |
|||
<md-content class="md-padding" layout="column"> |
|||
<fieldset ng-disabled="loading"> |
|||
<md-radio-group ng-model="vm.addToDashboardType" class="md-primary"> |
|||
<md-radio-button flex ng-value=0 class="md-primary md-align-top-left md-radio-interactive"> |
|||
<section flex layout="column" style="width: 300px;"> |
|||
<span translate style="padding-bottom: 10px;">dashboard.select-existing</span> |
|||
<tb-dashboard-autocomplete the-form="theForm" |
|||
ng-disabled="loading || vm.addToDashboardType != 0" |
|||
tb-required="vm.addToDashboardType === 0" |
|||
ng-model="vm.dashboardId" |
|||
select-first-dashboard="false"> |
|||
</tb-dashboard-autocomplete> |
|||
</section> |
|||
</md-radio-button> |
|||
<md-radio-button flex ng-value=1 class="md-primary md-align-top-left md-radio-interactive"> |
|||
<section flex layout="column" style="width: 300px;"> |
|||
<span translate>dashboard.create-new</span> |
|||
<md-input-container class="md-block"> |
|||
<label translate>dashboard.new-dashboard-title</label> |
|||
<input ng-required="vm.addToDashboardType === 1" name="title" ng-model="vm.newDashboard.title"> |
|||
<div ng-messages="theForm.title.$error"> |
|||
<div translate ng-message="required">dashboard.title-required</div> |
|||
</div> |
|||
</md-input-container> |
|||
</section> |
|||
</md-radio-button> |
|||
</md-radio-group> |
|||
</fieldset> |
|||
</md-content> |
|||
</div> |
|||
</md-dialog-content> |
|||
<md-dialog-actions layout="row"> |
|||
<span flex></span> |
|||
<md-checkbox |
|||
ng-model="vm.openDashboard" |
|||
aria-label="{{ 'dashboard.open-dashboard' | translate }}" |
|||
style="margin-bottom: 0px; padding-right: 20px;"> |
|||
{{ 'dashboard.open-dashboard' | translate }} |
|||
</md-checkbox> |
|||
<md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" |
|||
class="md-raised md-primary"> |
|||
{{ 'action.add' | translate }} |
|||
</md-button> |
|||
<md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | |
|||
translate }} |
|||
</md-button> |
|||
</md-dialog-actions> |
|||
</form> |
|||
</md-dialog> |
|||
@ -1,421 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2017 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 'angular-material-data-table/dist/md-data-table.min.css'; |
|||
import './attribute-table.scss'; |
|||
|
|||
/* eslint-disable import/no-unresolved, import/default */ |
|||
|
|||
import attributeTableTemplate from './attribute-table.tpl.html'; |
|||
import addAttributeDialogTemplate from './add-attribute-dialog.tpl.html'; |
|||
import addWidgetToDashboardDialogTemplate from './add-widget-to-dashboard-dialog.tpl.html'; |
|||
import editAttributeValueTemplate from './edit-attribute-value.tpl.html'; |
|||
|
|||
/* eslint-enable import/no-unresolved, import/default */ |
|||
|
|||
import EditAttributeValueController from './edit-attribute-value.controller'; |
|||
|
|||
/*@ngInject*/ |
|||
export default function AttributeTableDirective($compile, $templateCache, $rootScope, $q, $mdEditDialog, $mdDialog, |
|||
$document, $translate, $filter, utils, types, dashboardService, deviceService, widgetService) { |
|||
|
|||
var linker = function (scope, element, attrs) { |
|||
|
|||
var template = $templateCache.get(attributeTableTemplate); |
|||
|
|||
element.html(template); |
|||
|
|||
scope.types = types; |
|||
scope.attributeScopes = types.deviceAttributesScope; |
|||
|
|||
var getAttributeScopeByValue = function(attributeScopeValue) { |
|||
if (scope.types.latestTelemetry.value === attributeScopeValue) { |
|||
return scope.types.latestTelemetry; |
|||
} |
|||
for (var attrScope in scope.attributeScopes) { |
|||
if (scope.attributeScopes[attrScope].value === attributeScopeValue) { |
|||
return scope.attributeScopes[attrScope]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
scope.attributeScope = getAttributeScopeByValue(attrs.defaultAttributeScope); |
|||
|
|||
scope.attributes = { |
|||
count: 0, |
|||
data: [] |
|||
}; |
|||
|
|||
scope.selectedAttributes = []; |
|||
scope.mode = 'default'; // 'widget'
|
|||
scope.subscriptionId = null; |
|||
|
|||
scope.query = { |
|||
order: 'key', |
|||
limit: 5, |
|||
page: 1, |
|||
search: null |
|||
}; |
|||
|
|||
scope.$watch("deviceId", function(newVal, prevVal) { |
|||
if (newVal && !angular.equals(newVal, prevVal)) { |
|||
scope.resetFilter(); |
|||
scope.getDeviceAttributes(false, true); |
|||
} |
|||
}); |
|||
|
|||
scope.$watch("attributeScope", function(newVal, prevVal) { |
|||
if (newVal && !angular.equals(newVal, prevVal)) { |
|||
scope.mode = 'default'; |
|||
scope.query.search = null; |
|||
scope.selectedAttributes = []; |
|||
scope.getDeviceAttributes(false, true); |
|||
} |
|||
}); |
|||
|
|||
scope.resetFilter = function() { |
|||
scope.mode = 'default'; |
|||
scope.query.search = null; |
|||
scope.selectedAttributes = []; |
|||
scope.attributeScope = getAttributeScopeByValue(attrs.defaultAttributeScope); |
|||
} |
|||
|
|||
scope.enterFilterMode = function() { |
|||
scope.query.search = ''; |
|||
} |
|||
|
|||
scope.exitFilterMode = function() { |
|||
scope.query.search = null; |
|||
scope.getDeviceAttributes(); |
|||
} |
|||
|
|||
scope.$watch("query.search", function(newVal, prevVal) { |
|||
if (!angular.equals(newVal, prevVal) && scope.query.search != null) { |
|||
scope.getDeviceAttributes(); |
|||
} |
|||
}); |
|||
|
|||
function success(attributes, update, apply) { |
|||
scope.attributes = attributes; |
|||
if (!update) { |
|||
scope.selectedAttributes = []; |
|||
} |
|||
if (apply) { |
|||
scope.$digest(); |
|||
} |
|||
} |
|||
|
|||
scope.onReorder = function() { |
|||
scope.getDeviceAttributes(false, false); |
|||
} |
|||
|
|||
scope.onPaginate = function() { |
|||
scope.getDeviceAttributes(false, false); |
|||
} |
|||
|
|||
scope.getDeviceAttributes = function(forceUpdate, reset) { |
|||
if (scope.attributesDeferred) { |
|||
scope.attributesDeferred.resolve(); |
|||
} |
|||
if (scope.deviceId && scope.attributeScope) { |
|||
if (reset) { |
|||
scope.attributes = { |
|||
count: 0, |
|||
data: [] |
|||
}; |
|||
} |
|||
scope.checkSubscription(); |
|||
scope.attributesDeferred = deviceService.getDeviceAttributes(scope.deviceId, scope.attributeScope.value, |
|||
scope.query, function(attributes, update, apply) { |
|||
success(attributes, update || forceUpdate, apply); |
|||
} |
|||
); |
|||
} else { |
|||
var deferred = $q.defer(); |
|||
scope.attributesDeferred = deferred; |
|||
success({ |
|||
count: 0, |
|||
data: [] |
|||
}); |
|||
deferred.resolve(); |
|||
} |
|||
} |
|||
|
|||
scope.checkSubscription = function() { |
|||
var newSubscriptionId = null; |
|||
if (scope.deviceId && scope.attributeScope.clientSide && scope.mode != 'widget') { |
|||
newSubscriptionId = deviceService.subscribeForDeviceAttributes(scope.deviceId, scope.attributeScope.value); |
|||
} |
|||
if (scope.subscriptionId && scope.subscriptionId != newSubscriptionId) { |
|||
deviceService.unsubscribeForDeviceAttributes(scope.subscriptionId); |
|||
} |
|||
scope.subscriptionId = newSubscriptionId; |
|||
} |
|||
|
|||
scope.$on('$destroy', function() { |
|||
if (scope.subscriptionId) { |
|||
deviceService.unsubscribeForDeviceAttributes(scope.subscriptionId); |
|||
} |
|||
}); |
|||
|
|||
scope.editAttribute = function($event, attribute) { |
|||
if (!scope.attributeScope.clientSide) { |
|||
$event.stopPropagation(); |
|||
$mdEditDialog.show({ |
|||
controller: EditAttributeValueController, |
|||
templateUrl: editAttributeValueTemplate, |
|||
locals: {attributeValue: attribute.value, |
|||
save: function (model) { |
|||
var updatedAttribute = angular.copy(attribute); |
|||
updatedAttribute.value = model.value; |
|||
deviceService.saveDeviceAttributes(scope.deviceId, scope.attributeScope.value, [updatedAttribute]).then( |
|||
function success() { |
|||
scope.getDeviceAttributes(); |
|||
} |
|||
); |
|||
}}, |
|||
targetEvent: $event |
|||
}); |
|||
} |
|||
} |
|||
|
|||
scope.addAttribute = function($event) { |
|||
if (!scope.attributeScope.clientSide) { |
|||
$event.stopPropagation(); |
|||
$mdDialog.show({ |
|||
controller: 'AddAttributeDialogController', |
|||
controllerAs: 'vm', |
|||
templateUrl: addAttributeDialogTemplate, |
|||
parent: angular.element($document[0].body), |
|||
locals: {deviceId: scope.deviceId, attributeScope: scope.attributeScope.value}, |
|||
fullscreen: true, |
|||
targetEvent: $event |
|||
}).then(function () { |
|||
scope.getDeviceAttributes(); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
scope.deleteAttributes = function($event) { |
|||
if (!scope.attributeScope.clientSide) { |
|||
$event.stopPropagation(); |
|||
var confirm = $mdDialog.confirm() |
|||
.targetEvent($event) |
|||
.title($translate.instant('attribute.delete-attributes-title', {count: scope.selectedAttributes.length}, 'messageformat')) |
|||
.htmlContent($translate.instant('attribute.delete-attributes-text')) |
|||
.ariaLabel($translate.instant('attribute.delete-attributes')) |
|||
.cancel($translate.instant('action.no')) |
|||
.ok($translate.instant('action.yes')); |
|||
$mdDialog.show(confirm).then(function () { |
|||
deviceService.deleteDeviceAttributes(scope.deviceId, scope.attributeScope.value, scope.selectedAttributes).then( |
|||
function success() { |
|||
scope.selectedAttributes = []; |
|||
scope.getDeviceAttributes(); |
|||
} |
|||
) |
|||
}); |
|||
} |
|||
} |
|||
|
|||
scope.nextWidget = function() { |
|||
if (scope.widgetsCarousel.index < scope.widgetsList.length-1) { |
|||
scope.widgetsCarousel.index++; |
|||
} |
|||
} |
|||
|
|||
scope.prevWidget = function() { |
|||
if (scope.widgetsCarousel.index > 0) { |
|||
scope.widgetsCarousel.index--; |
|||
} |
|||
} |
|||
|
|||
scope.enterWidgetMode = function() { |
|||
|
|||
if (scope.widgetsIndexWatch) { |
|||
scope.widgetsIndexWatch(); |
|||
scope.widgetsIndexWatch = null; |
|||
} |
|||
|
|||
if (scope.widgetsBundleWatch) { |
|||
scope.widgetsBundleWatch(); |
|||
scope.widgetsBundleWatch = null; |
|||
} |
|||
|
|||
scope.mode = 'widget'; |
|||
scope.checkSubscription(); |
|||
scope.widgetsList = []; |
|||
scope.widgetsListCache = []; |
|||
scope.widgetsLoaded = false; |
|||
scope.widgetsCarousel = { |
|||
index: 0 |
|||
} |
|||
scope.widgetsBundle = null; |
|||
scope.firstBundle = true; |
|||
scope.selectedWidgetsBundleAlias = types.systemBundleAlias.cards; |
|||
|
|||
scope.aliasesInfo = { |
|||
deviceAliases: { |
|||
'1': {alias: scope.deviceName, deviceId: scope.deviceId} |
|||
}, |
|||
deviceAliasesInfo: { |
|||
'1': [ |
|||
{name: scope.deviceName, id: scope.deviceId} |
|||
] |
|||
} |
|||
}; |
|||
|
|||
var dataKeyType = scope.attributeScope === types.latestTelemetry ? |
|||
types.dataKeyType.timeseries : types.dataKeyType.attribute; |
|||
|
|||
var datasource = { |
|||
type: types.datasourceType.device, |
|||
deviceAliasId: '1', |
|||
dataKeys: [] |
|||
} |
|||
var i = 0; |
|||
for (var attr =0; attr < scope.selectedAttributes.length;attr++) { |
|||
var attribute = scope.selectedAttributes[attr]; |
|||
var dataKey = { |
|||
name: attribute.key, |
|||
label: attribute.key, |
|||
type: dataKeyType, |
|||
color: utils.getMaterialColor(i), |
|||
settings: {}, |
|||
_hash: Math.random() |
|||
} |
|||
datasource.dataKeys.push(dataKey); |
|||
i++; |
|||
} |
|||
|
|||
scope.widgetsIndexWatch = scope.$watch('widgetsCarousel.index', function(newVal, prevVal) { |
|||
if (scope.mode === 'widget' && (newVal != prevVal)) { |
|||
var index = scope.widgetsCarousel.index; |
|||
for (var i = 0; i < scope.widgetsList.length; i++) { |
|||
scope.widgetsList[i].splice(0, scope.widgetsList[i].length); |
|||
if (i === index) { |
|||
scope.widgetsList[i].push(scope.widgetsListCache[i][0]); |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
|
|||
scope.widgetsBundleWatch = scope.$watch('widgetsBundle', function(newVal, prevVal) { |
|||
if (scope.mode === 'widget' && (scope.firstBundle === true || newVal != prevVal)) { |
|||
scope.widgetsList = []; |
|||
scope.widgetsListCache = []; |
|||
scope.widgetsCarousel.index = 0; |
|||
scope.firstBundle = false; |
|||
if (scope.widgetsBundle) { |
|||
scope.widgetsLoaded = false; |
|||
var bundleAlias = scope.widgetsBundle.alias; |
|||
var isSystem = scope.widgetsBundle.tenantId.id === types.id.nullUid; |
|||
widgetService.getBundleWidgetTypes(scope.widgetsBundle.alias, isSystem).then( |
|||
function success(widgetTypes) { |
|||
|
|||
widgetTypes = $filter('orderBy')(widgetTypes, ['-descriptor.type','-createdTime']); |
|||
|
|||
for (var i = 0; i < widgetTypes.length; i++) { |
|||
var widgetType = widgetTypes[i]; |
|||
var widgetInfo = widgetService.toWidgetInfo(widgetType); |
|||
if (widgetInfo.type !== types.widgetType.static.value) { |
|||
var sizeX = widgetInfo.sizeX * 2; |
|||
var sizeY = widgetInfo.sizeY * 2; |
|||
var col = Math.floor(Math.max(0, (20 - sizeX) / 2)); |
|||
var widget = { |
|||
isSystemType: isSystem, |
|||
bundleAlias: bundleAlias, |
|||
typeAlias: widgetInfo.alias, |
|||
type: widgetInfo.type, |
|||
title: widgetInfo.widgetName, |
|||
sizeX: sizeX, |
|||
sizeY: sizeY, |
|||
row: 0, |
|||
col: col, |
|||
config: angular.fromJson(widgetInfo.defaultConfig) |
|||
}; |
|||
|
|||
widget.config.title = widgetInfo.widgetName; |
|||
widget.config.datasources = [datasource]; |
|||
var length; |
|||
if (scope.attributeScope === types.latestTelemetry && widgetInfo.type !== types.widgetType.rpc.value) { |
|||
length = scope.widgetsListCache.push([widget]); |
|||
scope.widgetsList.push(length === 1 ? [widget] : []); |
|||
} else if (widgetInfo.type === types.widgetType.latest.value) { |
|||
length = scope.widgetsListCache.push([widget]); |
|||
scope.widgetsList.push(length === 1 ? [widget] : []); |
|||
} |
|||
} |
|||
} |
|||
scope.widgetsLoaded = true; |
|||
} |
|||
); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
scope.exitWidgetMode = function() { |
|||
if (scope.widgetsBundleWatch) { |
|||
scope.widgetsBundleWatch(); |
|||
scope.widgetsBundleWatch = null; |
|||
} |
|||
if (scope.widgetsIndexWatch) { |
|||
scope.widgetsIndexWatch(); |
|||
scope.widgetsIndexWatch = null; |
|||
} |
|||
scope.selectedWidgetsBundleAlias = null; |
|||
scope.mode = 'default'; |
|||
scope.getDeviceAttributes(true); |
|||
} |
|||
|
|||
scope.getServerTimeDiff = function() { |
|||
return dashboardService.getServerTimeDiff(); |
|||
} |
|||
|
|||
scope.addWidgetToDashboard = function($event) { |
|||
if (scope.mode === 'widget' && scope.widgetsListCache.length > 0) { |
|||
var widget = scope.widgetsListCache[scope.widgetsCarousel.index][0]; |
|||
$event.stopPropagation(); |
|||
$mdDialog.show({ |
|||
controller: 'AddWidgetToDashboardDialogController', |
|||
controllerAs: 'vm', |
|||
templateUrl: addWidgetToDashboardDialogTemplate, |
|||
parent: angular.element($document[0].body), |
|||
locals: {deviceId: scope.deviceId, deviceName: scope.deviceName, widget: angular.copy(widget)}, |
|||
fullscreen: true, |
|||
targetEvent: $event |
|||
}).then(function () { |
|||
|
|||
}); |
|||
} |
|||
} |
|||
|
|||
scope.loading = function() { |
|||
return $rootScope.loading; |
|||
} |
|||
|
|||
$compile(element.contents())(scope); |
|||
} |
|||
|
|||
return { |
|||
restrict: "E", |
|||
link: linker, |
|||
scope: { |
|||
deviceId: '=', |
|||
deviceName: '=', |
|||
disableAttributeScopeSelection: '@?' |
|||
} |
|||
}; |
|||
} |
|||
@ -1,50 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2017 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 '../../../scss/constants'; |
|||
|
|||
$md-light: rgba(255, 255, 255, 100%); |
|||
$md-edit-icon-fill: #757575; |
|||
|
|||
md-toolbar.md-table-toolbar.alternate { |
|||
.md-toolbar-tools { |
|||
md-icon { |
|||
color: $md-light; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.md-table { |
|||
.md-cell { |
|||
ng-md-icon { |
|||
fill: $md-edit-icon-fill; |
|||
float: right; |
|||
height: 16px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.widgets-carousel { |
|||
position: relative; |
|||
margin: 0px; |
|||
|
|||
min-height: 150px !important; |
|||
|
|||
tb-dashboard { |
|||
#gridster-parent { |
|||
padding: 0 7px; |
|||
} |
|||
} |
|||
} |
|||
@ -1,210 +0,0 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2017 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. |
|||
|
|||
--> |
|||
<md-content flex class="md-padding tb-absolute-fill" layout="column"> |
|||
<section layout="row" ng-show="!disableAttributeScopeSelection"> |
|||
<md-input-container class="md-block" style="width: 200px;"> |
|||
<label translate>attribute.attributes-scope</label> |
|||
<md-select ng-model="attributeScope" ng-disabled="loading()"> |
|||
<md-option ng-repeat="scope in attributeScopes" ng-value="scope"> |
|||
{{scope.name | translate}} |
|||
</md-option> |
|||
</md-select> |
|||
</md-input-container> |
|||
</section> |
|||
<div layout="column" class="md-whiteframe-z1" ng-class="{flex: mode==='widget'}"> |
|||
<md-toolbar class="md-table-toolbar md-default" ng-show="mode==='default' |
|||
&& !selectedAttributes.length |
|||
&& query.search === null"> |
|||
<div class="md-toolbar-tools"> |
|||
<span translate>{{ attributeScope.name }}</span> |
|||
<span flex></span> |
|||
<md-button ng-show="!attributeScope.clientSide" class="md-icon-button" ng-click="addAttribute($event)"> |
|||
<md-icon>add</md-icon> |
|||
<md-tooltip md-direction="top"> |
|||
{{ 'action.add' | translate }} |
|||
</md-tooltip> |
|||
</md-button> |
|||
<md-button class="md-icon-button" ng-click="enterFilterMode()"> |
|||
<md-icon>search</md-icon> |
|||
<md-tooltip md-direction="top"> |
|||
{{ 'action.search' | translate }} |
|||
</md-tooltip> |
|||
</md-button> |
|||
<md-button ng-show="!attributeScope.clientSide" class="md-icon-button" ng-click="getDeviceAttributes()"> |
|||
<md-icon>refresh</md-icon> |
|||
<md-tooltip md-direction="top"> |
|||
{{ 'action.refresh' | translate }} |
|||
</md-tooltip> |
|||
</md-button> |
|||
</div> |
|||
</md-toolbar> |
|||
<md-toolbar class="md-table-toolbar md-default" ng-show="mode==='default' |
|||
&& !selectedAttributes.length |
|||
&& query.search != null"> |
|||
<div class="md-toolbar-tools"> |
|||
<md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}"> |
|||
<md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon> |
|||
<md-tooltip md-direction="top"> |
|||
{{ 'action.search' | translate }} |
|||
</md-tooltip> |
|||
</md-button> |
|||
<md-input-container md-theme="tb-search-input" flex> |
|||
<label> </label> |
|||
<input ng-model="query.search" placeholder="{{ 'common.enter-search' | translate }}"/> |
|||
</md-input-container> |
|||
<md-button class="md-icon-button" aria-label="{{ 'action.back' | translate }}" ng-click="exitFilterMode()"> |
|||
<md-icon aria-label="{{ 'action.close' | translate }}" class="material-icons">close</md-icon> |
|||
<md-tooltip md-direction="top"> |
|||
{{ 'action.close' | translate }} |
|||
</md-tooltip> |
|||
</md-button> |
|||
</div> |
|||
</md-toolbar> |
|||
<md-toolbar class="md-table-toolbar alternate" ng-show="mode==='default' && selectedAttributes.length"> |
|||
<div class="md-toolbar-tools"> |
|||
<span translate="{{attributeScope === types.latestTelemetry |
|||
? 'attribute.selected-telemetry' |
|||
: 'attribute.selected-attributes'}}" |
|||
translate-values="{count: selectedAttributes.length}" |
|||
translate-interpolation="messageformat"></span> |
|||
<span flex></span> |
|||
<md-button ng-show="!attributeScope.clientSide" class="md-icon-button" ng-click="deleteAttributes($event)"> |
|||
<md-icon>delete</md-icon> |
|||
<md-tooltip md-direction="top"> |
|||
{{ 'action.delete' | translate }} |
|||
</md-tooltip> |
|||
</md-button> |
|||
<md-button ng-show="attributeScope.clientSide" class="md-accent md-hue-2 md-raised" ng-click="enterWidgetMode()"> |
|||
<md-tooltip md-direction="top"> |
|||
{{ 'attribute.show-on-widget' | translate }} |
|||
</md-tooltip> |
|||
<md-icon>now_widgets</md-icon> |
|||
<span translate>attribute.show-on-widget</span> |
|||
</md-button> |
|||
</div> |
|||
</md-toolbar> |
|||
<md-toolbar class="md-table-toolbar alternate" ng-show="mode==='widget'"> |
|||
<div class="md-toolbar-tools"> |
|||
<div flex layout="row" layout-align="start"> |
|||
<span class="tb-details-subtitle">{{ 'widgets-bundle.current' | translate }}</span> |
|||
<tb-widgets-bundle-select flex-offset="5" |
|||
flex |
|||
ng-model="widgetsBundle" |
|||
select-first-bundle="false" |
|||
select-bundle-alias="selectedWidgetsBundleAlias"> |
|||
</tb-widgets-bundle-select> |
|||
</div> |
|||
<md-button ng-show="widgetsList.length > 0" class="md-accent md-hue-2 md-raised" ng-click="addWidgetToDashboard($event)"> |
|||
<md-tooltip md-direction="top"> |
|||
{{ 'attribute.add-to-dashboard' | translate }} |
|||
</md-tooltip> |
|||
<md-icon>dashboard</md-icon> |
|||
<span translate>attribute.add-to-dashboard</span> |
|||
</md-button> |
|||
<md-button class="md-icon-button" aria-label="{{ 'action.back' | translate }}" ng-click="exitWidgetMode()"> |
|||
<md-icon aria-label="{{ 'action.close' | translate }}" class="material-icons">close</md-icon> |
|||
<md-tooltip md-direction="top"> |
|||
{{ 'action.close' | translate }} |
|||
</md-tooltip> |
|||
</md-button> |
|||
</div> |
|||
</md-toolbar> |
|||
<md-table-container ng-show="mode!='widget'"> |
|||
<table md-table md-row-select multiple="" ng-model="selectedAttributes" md-progress="attributesDeferred.promise"> |
|||
<thead md-head md-order="query.order" md-on-reorder="onReorder"> |
|||
<tr md-row> |
|||
<th md-column md-order-by="lastUpdateTs"><span>Last update time</span></th> |
|||
<th md-column md-order-by="key"><span>Key</span></th> |
|||
<th md-column>Value</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody md-body> |
|||
<tr md-row md-select="attribute" md-select-id="key" md-auto-select ng-repeat="attribute in attributes.data"> |
|||
<td md-cell>{{attribute.lastUpdateTs | date : 'yyyy-MM-dd HH:mm:ss'}}</td> |
|||
<td md-cell>{{attribute.key}}</td> |
|||
<td md-cell ng-click="editAttribute($event, attribute)"> |
|||
<span>{{attribute.value}}</span> |
|||
<span ng-show="!attributeScope.clientSide"><ng-md-icon size="16" icon="edit"></ng-md-icon></span> |
|||
</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
</md-table-container> |
|||
<md-table-pagination ng-show="mode!='widget'" md-limit="query.limit" md-limit-options="[5, 10, 15]" |
|||
md-page="query.page" md-total="{{attributes.count}}" |
|||
md-on-paginate="onPaginate" md-page-select> |
|||
</md-table-pagination> |
|||
<ul flex rn-carousel ng-if="mode==='widget'" class="widgets-carousel" |
|||
rn-carousel-index="widgetsCarousel.index" |
|||
rn-carousel-buffered |
|||
rn-carousel-transition="fadeAndSlide" |
|||
rn-swipe-disabled="true"> |
|||
<li ng-repeat="widgets in widgetsList"> |
|||
<tb-dashboard |
|||
aliases-info="aliasesInfo" |
|||
widgets="widgets" |
|||
get-st-diff="getServerTimeDiff()" |
|||
columns="20" |
|||
is-edit="false" |
|||
is-mobile-disabled="true" |
|||
is-edit-action-enabled="false" |
|||
is-remove-action-enabled="false"> |
|||
</tb-dashboard> |
|||
</li> |
|||
<span translate ng-if="widgetsLoaded && |
|||
widgetsList.length === 0 && |
|||
widgetsBundle" |
|||
layout-align="center center" |
|||
style="text-transform: uppercase; display: flex;" |
|||
class="md-headline tb-absolute-fill">widgets-bundle.empty</span> |
|||
<span translate ng-if="!widgetsBundle" |
|||
layout-align="center center" |
|||
style="text-transform: uppercase; display: flex;" |
|||
class="md-headline tb-absolute-fill">widget.select-widgets-bundle</span> |
|||
<div ng-show="widgetsList.length > 1" |
|||
style="position: absolute; left: 0; height: 100%;" layout="column" layout-align="center"> |
|||
<md-button ng-show="widgetsCarousel.index > 0" |
|||
class="md-icon-button" |
|||
ng-click="prevWidget()"> |
|||
<md-icon>navigate_before</md-icon> |
|||
<md-tooltip md-direction="top"> |
|||
{{ 'attribute.prev-widget' | translate }} |
|||
</md-tooltip> |
|||
</md-button> |
|||
</div> |
|||
<div ng-show="widgetsList.length > 1" |
|||
style="position: absolute; right: 0; height: 100%;" layout="column" layout-align="center"> |
|||
<md-button ng-show="widgetsCarousel.index < widgetsList.length-1" |
|||
class="md-icon-button" |
|||
ng-click="nextWidget()"> |
|||
<md-icon>navigate_next</md-icon> |
|||
<md-tooltip md-direction="top"> |
|||
{{ 'attribute.next-widget' | translate }} |
|||
</md-tooltip> |
|||
</md-button> |
|||
</div> |
|||
<div style="position: absolute; bottom: 0; width: 100%; font-size: 24px;" layout="row" layout-align="center"> |
|||
<div rn-carousel-indicators |
|||
ng-if="widgetsList.length > 1" |
|||
slides="widgetsList" |
|||
rn-carousel-index="widgetsCarousel.index"> |
|||
</div> |
|||
</div> |
|||
</ul> |
|||
</div> |
|||
</md-content> |
|||
@ -1,71 +0,0 @@ |
|||
/* |
|||
* Copyright © 2016-2017 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. |
|||
*/ |
|||
/*@ngInject*/ |
|||
export default function EditAttributeValueController($scope, $q, $element, types, attributeValue, save) { |
|||
|
|||
$scope.valueTypes = types.valueType; |
|||
|
|||
$scope.model = {}; |
|||
|
|||
$scope.model.value = attributeValue; |
|||
|
|||
if ($scope.model.value === true || $scope.model.value === false) { |
|||
$scope.valueType = types.valueType.boolean; |
|||
} else if (angular.isNumber($scope.model.value)) { |
|||
if ($scope.model.value.toString().indexOf('.') == -1) { |
|||
$scope.valueType = types.valueType.integer; |
|||
} else { |
|||
$scope.valueType = types.valueType.double; |
|||
} |
|||
} else { |
|||
$scope.valueType = types.valueType.string; |
|||
} |
|||
|
|||
$scope.submit = submit; |
|||
$scope.dismiss = dismiss; |
|||
|
|||
function dismiss() { |
|||
$element.remove(); |
|||
} |
|||
|
|||
function update() { |
|||
if($scope.editDialog.$invalid) { |
|||
return $q.reject(); |
|||
} |
|||
|
|||
if(angular.isFunction(save)) { |
|||
return $q.when(save($scope.model)); |
|||
} |
|||
|
|||
return $q.resolve(); |
|||
} |
|||
|
|||
function submit() { |
|||
update().then(function () { |
|||
$scope.dismiss(); |
|||
}); |
|||
} |
|||
|
|||
$scope.$watch('valueType', function(newVal, prevVal) { |
|||
if (newVal != prevVal) { |
|||
if ($scope.valueType === types.valueType.boolean) { |
|||
$scope.model.value = false; |
|||
} else { |
|||
$scope.model.value = null; |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
@ -1,72 +0,0 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2017 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. |
|||
|
|||
--> |
|||
<md-edit-dialog> |
|||
<form name="editDialog" ng-submit="submit()"> |
|||
<div layout="column" class="md-content" style="width: 400px;"> |
|||
<fieldset> |
|||
<section layout="row"> |
|||
<md-input-container flex="40" class="md-block"> |
|||
<label translate>value.type</label> |
|||
<md-select ng-model="valueType"> |
|||
<md-option ng-repeat="type in valueTypes" ng-value="type"> |
|||
<md-icon md-svg-icon="{{ type.icon }}"></md-icon> |
|||
<span>{{type.name | translate}}</span> |
|||
</md-option> |
|||
</md-select> |
|||
</md-input-container> |
|||
<md-input-container ng-if="valueType===valueTypes.string" flex="60" class="md-block"> |
|||
<label translate>value.string-value</label> |
|||
<input required name="value" ng-model="model.value"> |
|||
<div ng-messages="editDialog.value.$error"> |
|||
<div translate ng-message="required">attribute.value-required</div> |
|||
</div> |
|||
</md-input-container> |
|||
<md-input-container ng-if="valueType===valueTypes.integer" flex="60" class="md-block"> |
|||
<label translate>value.integer-value</label> |
|||
<input required name="value" type="number" step="1" ng-pattern="/^-?[0-9]+$/" ng-model="model.value"> |
|||
<div ng-messages="editDialog.value.$error"> |
|||
<div translate ng-message="required">attribute.value-required</div> |
|||
<div translate ng-message="pattern">value.invalid-integer-value</div> |
|||
</div> |
|||
</md-input-container> |
|||
<md-input-container ng-if="valueType===valueTypes.double" flex="60" class="md-block"> |
|||
<label translate>value.double-value</label> |
|||
<input required name="value" type="number" step="any" ng-model="model.value"> |
|||
<div ng-messages="editDialog.value.$error"> |
|||
<div translate ng-message="required">attribute.value-required</div> |
|||
</div> |
|||
</md-input-container> |
|||
<div layout="column" layout-align="center" flex="60" ng-if="valueType===valueTypes.boolean"> |
|||
<md-checkbox ng-model="model.value" style="margin-bottom: 0px;"> |
|||
{{ (model.value ? 'value.true' : 'value.false') | translate }} |
|||
</md-checkbox> |
|||
</div> |
|||
</section> |
|||
</fieldset> |
|||
</div> |
|||
<div layout="row" layout-align="end" class="md-actions"> |
|||
<md-button ng-click="dismiss()">{{ 'action.cancel' | |
|||
translate }} |
|||
</md-button> |
|||
<md-button ng-disabled="editDialog.$invalid || !editDialog.$dirty" type="submit" |
|||
class="md-raised md-primary"> |
|||
{{ 'action.update' | translate }} |
|||
</md-button> |
|||
</div> |
|||
</form> |
|||
</md-edit-dialog> |
|||
@ -0,0 +1,141 @@ |
|||
/* |
|||
* Copyright © 2016-2017 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 './entity-subtype-autocomplete.scss'; |
|||
|
|||
/* eslint-disable import/no-unresolved, import/default */ |
|||
|
|||
import entitySubtypeAutocompleteTemplate from './entity-subtype-autocomplete.tpl.html'; |
|||
|
|||
/* eslint-enable import/no-unresolved, import/default */ |
|||
|
|||
/*@ngInject*/ |
|||
export default function EntitySubtypeAutocomplete($compile, $templateCache, $q, $filter, assetService, deviceService, types) { |
|||
|
|||
var linker = function (scope, element, attrs, ngModelCtrl) { |
|||
var template = $templateCache.get(entitySubtypeAutocompleteTemplate); |
|||
element.html(template); |
|||
|
|||
scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false; |
|||
scope.subType = null; |
|||
scope.subTypeSearchText = ''; |
|||
scope.entitySubtypes = null; |
|||
|
|||
scope.fetchSubTypes = function(searchText) { |
|||
var deferred = $q.defer(); |
|||
loadSubTypes().then( |
|||
function success(subTypes) { |
|||
var result = $filter('filter')(subTypes, {'$': searchText}); |
|||
if (result && result.length) { |
|||
deferred.resolve(result); |
|||
} else { |
|||
deferred.resolve([searchText]); |
|||
} |
|||
}, |
|||
function fail() { |
|||
deferred.reject(); |
|||
} |
|||
); |
|||
return deferred.promise; |
|||
} |
|||
|
|||
scope.subTypeSearchTextChanged = function() { |
|||
} |
|||
|
|||
scope.updateView = function () { |
|||
if (!scope.disabled) { |
|||
ngModelCtrl.$setViewValue(scope.subType); |
|||
} |
|||
} |
|||
|
|||
ngModelCtrl.$render = function () { |
|||
scope.subType = ngModelCtrl.$viewValue; |
|||
} |
|||
|
|||
scope.$watch('entityType', function () { |
|||
load(); |
|||
}); |
|||
|
|||
scope.$watch('subType', function (newValue, prevValue) { |
|||
if (!angular.equals(newValue, prevValue)) { |
|||
scope.updateView(); |
|||
} |
|||
}); |
|||
|
|||
scope.$watch('disabled', function () { |
|||
scope.updateView(); |
|||
}); |
|||
|
|||
function loadSubTypes() { |
|||
var deferred = $q.defer(); |
|||
if (!scope.entitySubtypes) { |
|||
var entitySubtypesPromise; |
|||
if (scope.entityType == types.entityType.asset) { |
|||
entitySubtypesPromise = assetService.getAssetTypes(); |
|||
} else if (scope.entityType == types.entityType.device) { |
|||
entitySubtypesPromise = deviceService.getDeviceTypes(); |
|||
} |
|||
if (entitySubtypesPromise) { |
|||
entitySubtypesPromise.then( |
|||
function success(types) { |
|||
scope.entitySubtypes = []; |
|||
types.forEach(function (type) { |
|||
scope.entitySubtypes.push(type.type); |
|||
}); |
|||
deferred.resolve(scope.entitySubtypes); |
|||
}, |
|||
function fail() { |
|||
deferred.reject(); |
|||
} |
|||
); |
|||
} else { |
|||
deferred.reject(); |
|||
} |
|||
} else { |
|||
deferred.resolve(scope.entitySubtypes); |
|||
} |
|||
return deferred.promise; |
|||
} |
|||
|
|||
function load() { |
|||
if (scope.entityType == types.entityType.asset) { |
|||
scope.selectEntitySubtypeText = 'asset.select-asset-type'; |
|||
scope.entitySubtypeText = 'asset.asset-type'; |
|||
scope.entitySubtypeRequiredText = 'asset.asset-type-required'; |
|||
} else if (scope.entityType == types.entityType.device) { |
|||
scope.selectEntitySubtypeText = 'device.select-device-type'; |
|||
scope.entitySubtypeText = 'device.device-type'; |
|||
scope.entitySubtypeRequiredText = 'device.device-type-required'; |
|||
scope.$on('deviceSaved', function() { |
|||
scope.entitySubtypes = null; |
|||
}); |
|||
} |
|||
} |
|||
|
|||
$compile(element.contents())(scope); |
|||
} |
|||
|
|||
return { |
|||
restrict: "E", |
|||
require: "^ngModel", |
|||
link: linker, |
|||
scope: { |
|||
theForm: '=?', |
|||
tbRequired: '=?', |
|||
disabled:'=ngDisabled', |
|||
entityType: "=" |
|||
} |
|||
}; |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2017 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. |
|||
|
|||
--> |
|||
<md-autocomplete ng-required="tbRequired" |
|||
ng-disabled="disabled" |
|||
md-no-cache="true" |
|||
md-input-name="subType" |
|||
ng-model="subType" |
|||
md-selected-item="subType" |
|||
md-search-text="subTypeSearchText" |
|||
md-search-text-change="subTypeSearchTextChanged()" |
|||
md-items="item in fetchSubTypes(subTypeSearchText)" |
|||
md-item-text="item" |
|||
md-min-length="0" |
|||
placeholder="{{ selectEntitySubtypeText | translate }}" |
|||
md-floating-label="{{ entitySubtypeText | translate }}" |
|||
md-select-on-match="true" |
|||
md-menu-class="tb-entity-subtype-autocomplete"> |
|||
<md-item-template> |
|||
<div class="tb-entity-subtype-item"> |
|||
<span md-highlight-text="subTypeSearchText" md-highlight-flags="^i">{{item}}</span> |
|||
</div> |
|||
</md-item-template> |
|||
<div ng-messages="theForm.subType.$error"> |
|||
<div translate ng-message="required">{{ entitySubtypeRequiredText }}</div> |
|||
</div> |
|||
</md-autocomplete> |
|||
@ -0,0 +1,138 @@ |
|||
/* |
|||
* Copyright © 2016-2017 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 './entity-subtype-select.scss'; |
|||
|
|||
/* eslint-disable import/no-unresolved, import/default */ |
|||
|
|||
import entitySubtypeSelectTemplate from './entity-subtype-select.tpl.html'; |
|||
|
|||
/* eslint-enable import/no-unresolved, import/default */ |
|||
|
|||
/*@ngInject*/ |
|||
export default function EntitySubtypeSelect($compile, $templateCache, $translate, assetService, deviceService, types) { |
|||
|
|||
var linker = function (scope, element, attrs, ngModelCtrl) { |
|||
var template = $templateCache.get(entitySubtypeSelectTemplate); |
|||
element.html(template); |
|||
|
|||
if (angular.isDefined(attrs.hideLabel)) { |
|||
scope.showLabel = false; |
|||
} else { |
|||
scope.showLabel = true; |
|||
} |
|||
|
|||
scope.ngModelCtrl = ngModelCtrl; |
|||
|
|||
scope.entitySubtypes = []; |
|||
|
|||
scope.subTypeName = function(subType) { |
|||
if (subType && subType.length) { |
|||
if (scope.typeTranslatePrefix) { |
|||
return $translate.instant(scope.typeTranslatePrefix + '.' + subType); |
|||
} else { |
|||
return subType; |
|||
} |
|||
} else { |
|||
return $translate.instant('entity.all-subtypes'); |
|||
} |
|||
} |
|||
|
|||
scope.$watch('entityType', function () { |
|||
load(); |
|||
}); |
|||
|
|||
scope.$watch('entitySubtype', function (newValue, prevValue) { |
|||
if (!angular.equals(newValue, prevValue)) { |
|||
scope.updateView(); |
|||
} |
|||
}); |
|||
|
|||
scope.updateView = function () { |
|||
ngModelCtrl.$setViewValue(scope.entitySubtype); |
|||
}; |
|||
|
|||
ngModelCtrl.$render = function () { |
|||
scope.entitySubtype = ngModelCtrl.$viewValue; |
|||
}; |
|||
|
|||
function loadSubTypes() { |
|||
scope.entitySubtypes.length = 0; |
|||
var entitySubtypesPromise; |
|||
if (scope.entityType == types.entityType.asset) { |
|||
entitySubtypesPromise = assetService.getAssetTypes(); |
|||
} else if (scope.entityType == types.entityType.device) { |
|||
entitySubtypesPromise = deviceService.getDeviceTypes(); |
|||
} |
|||
if (entitySubtypesPromise) { |
|||
entitySubtypesPromise.then( |
|||
function success(types) { |
|||
scope.entitySubtypes.push(''); |
|||
types.forEach(function(type) { |
|||
scope.entitySubtypes.push(type.type); |
|||
}); |
|||
if (scope.entitySubtypes.indexOf(scope.entitySubtype) == -1) { |
|||
scope.entitySubtype = ''; |
|||
} |
|||
}, |
|||
function fail() {} |
|||
); |
|||
} |
|||
|
|||
} |
|||
|
|||
function load() { |
|||
if (scope.entityType == types.entityType.asset) { |
|||
scope.entitySubtypeTitle = 'asset.asset-type'; |
|||
scope.entitySubtypeRequiredText = 'asset.asset-type-required'; |
|||
} else if (scope.entityType == types.entityType.device) { |
|||
scope.entitySubtypeTitle = 'device.device-type'; |
|||
scope.entitySubtypeRequiredText = 'device.device-type-required'; |
|||
} |
|||
scope.entitySubtypes.length = 0; |
|||
if (scope.entitySubtypesList && scope.entitySubtypesList.length) { |
|||
scope.entitySubtypesList.forEach(function(subType) { |
|||
scope.entitySubtypes.push(subType); |
|||
}); |
|||
} else { |
|||
loadSubTypes(); |
|||
if (scope.entityType == types.entityType.asset) { |
|||
scope.$on('assetSaved', function() { |
|||
loadSubTypes(); |
|||
}); |
|||
} else if (scope.entityType == types.entityType.device) { |
|||
scope.$on('deviceSaved', function() { |
|||
loadSubTypes(); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
$compile(element.contents())(scope); |
|||
} |
|||
|
|||
return { |
|||
restrict: "E", |
|||
require: "^ngModel", |
|||
link: linker, |
|||
scope: { |
|||
theForm: '=?', |
|||
entityType: "=", |
|||
entitySubtypesList: "=?", |
|||
typeTranslatePrefix: "@?" |
|||
} |
|||
}; |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2017 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. |
|||
|
|||
--> |
|||
<md-input-container> |
|||
<label ng-if="showLabel">{{ entitySubtypeTitle | translate }}</label> |
|||
<md-select name="subType" ng-model="entitySubtype" class="tb-entity-subtype-select" aria-label="{{ entitySubtypeTitle | translate }}"> |
|||
<md-option ng-repeat="subType in entitySubtypes" ng-value="subType"> |
|||
{{ subTypeName(subType) }} |
|||
</md-option> |
|||
</md-select> |
|||
<div ng-messages="theForm.subType.$error"> |
|||
<div ng-message="required" translate>{{ entitySubtypeRequiredText }}</div> |
|||
</div> |
|||
</md-input-container> |
|||
Loading…
Reference in new issue