widget-config.directive.js 12.7 KB
/*
 * Copyright © 2016 The Thingsboard Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import jsonSchemaDefaults from 'json-schema-defaults';
import thingsboardTypes from '../common/types.constant';
import thingsboardUtils from '../common/utils.service';
import thingsboardDeviceAliasSelect from './device-alias-select.directive';
import thingsboardDatasource from './datasource.directive';
import thingsboardTimewindow from './timewindow.directive';
import thingsboardJsonForm from "./json-form.directive";

/* eslint-disable import/no-unresolved, import/default */

import widgetConfigTemplate from './widget-config.tpl.html';

/* eslint-enable import/no-unresolved, import/default */

/* eslint-disable angular/angularelement */

export default angular.module('thingsboard.directives.widgetConfig', [thingsboardTypes,
    thingsboardUtils,
    thingsboardJsonForm,
    thingsboardDeviceAliasSelect,
    thingsboardDatasource,
    thingsboardTimewindow])
    .directive('tbWidgetConfig', WidgetConfig)
    .name;

/*@ngInject*/
function WidgetConfig($compile, $templateCache, $rootScope, types, utils) {

    var linker = function (scope, element, attrs, ngModelCtrl) {

        var template = $templateCache.get(widgetConfigTemplate);

        element.html(template);

        scope.types = types;
        scope.widgetEditMode = $rootScope.widgetEditMode;

        scope.emptySettingsSchema = {
            type: "object",
            properties: {}
        };
        scope.defaultSettingsForm = [
            '*'
        ];

        if (angular.isUndefined(scope.forceExpandDatasources)) {
            scope.forceExpandDatasources = false;
        }

        scope.currentSettingsSchema = {};
        scope.currentSettings = angular.copy(scope.emptySettingsSchema);

        scope.targetDeviceAlias = {
            value: null
        }

        ngModelCtrl.$render = function () {
            if (ngModelCtrl.$viewValue) {
                scope.selectedTab = 0;
                scope.title = ngModelCtrl.$viewValue.title;
                scope.showTitle = ngModelCtrl.$viewValue.showTitle;
                scope.backgroundColor = ngModelCtrl.$viewValue.backgroundColor;
                scope.color = ngModelCtrl.$viewValue.color;
                scope.padding = ngModelCtrl.$viewValue.padding;
                scope.timewindow = ngModelCtrl.$viewValue.timewindow;
                if (scope.widgetType !== types.widgetType.rpc.value) {
                    if (scope.datasources) {
                        scope.datasources.splice(0, scope.datasources.length);
                    } else {
                        scope.datasources = [];
                    }
                    if (ngModelCtrl.$viewValue.datasources) {
                        for (var i in ngModelCtrl.$viewValue.datasources) {
                            scope.datasources.push({value: ngModelCtrl.$viewValue.datasources[i]});
                        }
                    }
                } else {
                    if (ngModelCtrl.$viewValue.targetDeviceAliasIds && ngModelCtrl.$viewValue.targetDeviceAliasIds.length > 0) {
                        var aliasId = ngModelCtrl.$viewValue.targetDeviceAliasIds[0];
                        if (scope.deviceAliases[aliasId]) {
                            scope.targetDeviceAlias.value = {id: aliasId, alias: scope.deviceAliases[aliasId].alias,
                                deviceId: scope.deviceAliases[aliasId].deviceId};
                        } else {
                            scope.targetDeviceAlias.value = null;
                        }
                    } else {
                        scope.targetDeviceAlias.value = null;
                    }
                }

                scope.settings = ngModelCtrl.$viewValue.settings;

                scope.updateSchemaForm();

                scope.updateDatasourcesAccordionState();
            }
        };

        scope.displayAdvanced = function() {
            return scope.widgetSettingsSchema && scope.widgetSettingsSchema.schema;
        }

        scope.updateSchemaForm = function() {
            if (scope.widgetSettingsSchema && scope.widgetSettingsSchema.schema) {
                scope.currentSettingsSchema = scope.widgetSettingsSchema.schema;
                scope.currentSettingsForm = scope.widgetSettingsSchema.form || angular.copy(scope.defaultSettingsForm);
                scope.currentSettings = scope.settings;
            } else {
                scope.currentSettingsForm = angular.copy(scope.defaultSettingsForm);
                scope.currentSettingsSchema = angular.copy(scope.emptySettingsSchema);
                scope.currentSettings = {};
            }
        }

        scope.$on('datasources-accordion:onReady', function () {
            if (scope.updateDatasourcesAccordionStatePending) {
                scope.updateDatasourcesAccordionState();
            }
        });

        scope.updateValidity = function () {
            if (ngModelCtrl.$viewValue) {
                var value = ngModelCtrl.$viewValue;
                var valid;
                if (scope.widgetType === types.widgetType.rpc.value) {
                    valid = value && value.targetDeviceAliasIds && value.targetDeviceAliasIds.length > 0;
                    ngModelCtrl.$setValidity('targetDeviceAliasIds', valid);
                } else {
                    valid = value && value.datasources && value.datasources.length > 0;
                    ngModelCtrl.$setValidity('datasources', valid);
                }
            }
        };

        scope.$watch('title + showTitle + backgroundColor + color + padding + intervalSec', function () {
            if (ngModelCtrl.$viewValue) {
                var value = ngModelCtrl.$viewValue;
                value.title = scope.title;
                value.showTitle = scope.showTitle;
                value.backgroundColor = scope.backgroundColor;
                value.color = scope.color;
                value.padding = scope.padding;
                value.intervalSec = scope.intervalSec;
                ngModelCtrl.$setViewValue(value);
            }
        });

        scope.$watch('currentSettings', function () {
            if (ngModelCtrl.$viewValue) {
                var value = ngModelCtrl.$viewValue;
                value.settings = scope.currentSettings;
                ngModelCtrl.$setViewValue(value);
            }
        }, true);

        scope.$watch('timewindow', function () {
            if (ngModelCtrl.$viewValue) {
                var value = ngModelCtrl.$viewValue;
                value.timewindow = scope.timewindow;
                ngModelCtrl.$setViewValue(value);
            }
        }, true);

        scope.$watch('datasources', function () {
            if (ngModelCtrl.$viewValue && scope.widgetType !== types.widgetType.rpc.value) {
                var value = ngModelCtrl.$viewValue;
                if (value.datasources) {
                    value.datasources.splice(0, value.datasources.length);
                } else {
                    value.datasources = [];
                }
                if (scope.datasources) {
                    for (var i in scope.datasources) {
                        value.datasources.push(scope.datasources[i].value);
                    }
                }
                ngModelCtrl.$setViewValue(value);
                scope.updateValidity();
            }
        }, true);

        scope.$watch('targetDeviceAlias.value', function () {
            if (ngModelCtrl.$viewValue && scope.widgetType === types.widgetType.rpc.value) {
                var value = ngModelCtrl.$viewValue;
                if (scope.targetDeviceAlias.value) {
                    value.targetDeviceAliasIds = [scope.targetDeviceAlias.value.id];
                } else {
                    value.targetDeviceAliasIds = [];
                }
                ngModelCtrl.$setViewValue(value);
                scope.updateValidity();
            }
        });

        scope.addDatasource = function () {
            var newDatasource;
            if (scope.functionsOnly) {
                newDatasource = angular.copy(utils.getDefaultDatasource(scope.datakeySettingsSchema.schema));
                newDatasource.dataKeys = [scope.generateDataKey('Sin', types.dataKeyType.function)];
            } else {
                newDatasource = { type: types.datasourceType.device,
                    dataKeys: []
                };
            }
            var datasource = {value: newDatasource};
            scope.datasources.push(datasource);
            if (scope.theForm) {
                scope.theForm.$setDirty();
            }
        }

        scope.removeDatasource = function ($event, datasource) {
            var index = scope.datasources.indexOf(datasource);
            if (index > -1) {
                scope.datasources.splice(index, 1);
                if (scope.theForm) {
                    scope.theForm.$setDirty();
                }
            }
        };

        scope.updateDatasourcesAccordionState = function () {
            if (scope.widgetType !== types.widgetType.rpc.value) {
                if (scope.datasourcesAccordion) {
                    scope.updateDatasourcesAccordionStatePending = false;
                    var expand = scope.datasources && scope.datasources.length < 4;
                    if (expand) {
                        scope.datasourcesAccordion.expand('datasources-pane');
                    } else {
                        scope.datasourcesAccordion.collapse('datasources-pane');
                    }
                } else {
                    scope.updateDatasourcesAccordionStatePending = true;
                }
            }
        }

        scope.generateDataKey = function (chip, type) {

            if (angular.isObject(chip)) {
                chip._hash = Math.random();
                return chip;
            }

            var result = {
                name: chip,
                type: type,
                label: scope.genNextLabel(chip),
                color: scope.genNextColor(),
                settings: {},
                _hash: Math.random()
            };

            if (type === types.dataKeyType.function) {
                result.name = 'f(x)';
                result.funcBody = utils.getPredefinedFunctionBody(chip);
                if (!result.funcBody) {
                    result.funcBody = "return prevValue + 1;";
                }
            }

            if (angular.isDefined(scope.datakeySettingsSchema.schema)) {
                result.settings = jsonSchemaDefaults(scope.datakeySettingsSchema.schema);
            }

            return result;
        };

        scope.genNextLabel = function (name) {
            var label = name;
            var value = ngModelCtrl.$viewValue;
            var i = 1;
            var matches = false;
            do {
                matches = false;
                if (value.datasources) {
                    for (var d in value.datasources) {
                        var datasource = value.datasources[d];
                        for (var k in datasource.dataKeys) {
                            var dataKey = datasource.dataKeys[k];
                            if (dataKey.label === label) {
                                i++;
                                label = name + ' ' + i;
                                matches = true;
                            }
                        }
                    }
                }
            } while (matches);
            return label;
        }

        scope.genNextColor = function () {
            var i = 0;
            var value = ngModelCtrl.$viewValue;
            if (value.datasources) {
                for (var d in value.datasources) {
                    var datasource = value.datasources[d];
                    i += datasource.dataKeys.length;
                }
            }
            return utils.getMaterialColor(i);
        }

        $compile(element.contents())(scope);
    }
    return {
        restrict: "E",
        require: "^ngModel",
        scope: {
            forceExpandDatasources: '=?',
            widgetType: '=',
            widgetSettingsSchema: '=',
            datakeySettingsSchema: '=',
            deviceAliases: '=',
            functionsOnly: '=',
            fetchDeviceKeys: '&',
            onCreateDeviceAlias: '&',
            theForm: '='
        },
        link: linker
    };
}

/* eslint-enable angular/angularelement */