diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 568931e074..8b33fede2e 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -67,10 +67,44 @@ "label": "Audio", "assignment-types": [0], "settings": [ + { + "name": "zones", + "type": "table", + "label": "Zones", + "help": "In this table you can define a set of zones in which you can specify various audio properties.", + "number": false, + "can_add": true, + "can_delete": true, + "key": { + "name": "name", + "label": "Name", + "placeholder": "Zone name" + }, + "columns": [ + { + "name": "x_range", + "label": "X range", + "can_set": true, + "placeholder": "0-16384" + }, + { + "name": "y_range", + "label": "Y range", + "can_set": true, + "placeholder": "0-16384" + }, + { + "name": "z_range", + "label": "Z range", + "can_set": true, + "placeholder": "0-16384" + } + ] + }, { "name": "enable_filter", "type": "checkbox", - "label": "Enable Positional Filter", + "label": "Positional filter", "help": "positional audio stream uses lowpass filter", "default": true }, diff --git a/domain-server/resources/web/js/settings.js b/domain-server/resources/web/js/settings.js index fb6901b34d..d6646a87da 100644 --- a/domain-server/resources/web/js/settings.js +++ b/domain-server/resources/web/js/settings.js @@ -25,10 +25,12 @@ var viewHelpers = { form_group += "" form_group += "
" form_group += ""; form_group += "
" + } else if (setting.type === 'table') { + form_group += makeTable(setting, setting_name, setting_value); } else { input_type = _.has(setting, 'type') ? setting.type : "text" @@ -39,7 +41,7 @@ var viewHelpers = { _.each(setting.options, function(option) { form_group += "" + (option.value == setting_value ? 'selected' : '') + ">" + option.label + "" }) form_group += "" @@ -74,17 +76,101 @@ $(document).ready(function(){ */ $('[data-clampedwidth]').each(function () { - var elem = $(this); - var parentPanel = elem.data('clampedwidth'); - var resizeFn = function () { - var sideBarNavWidth = $(parentPanel).width() - parseInt(elem.css('paddingLeft')) - parseInt(elem.css('paddingRight')) - parseInt(elem.css('marginLeft')) - parseInt(elem.css('marginRight')) - parseInt(elem.css('borderLeftWidth')) - parseInt(elem.css('borderRightWidth')); - elem.css('width', sideBarNavWidth); - }; + var elem = $(this); + var parentPanel = elem.data('clampedwidth'); + var resizeFn = function () { + var sideBarNavWidth = $(parentPanel).width() - parseInt(elem.css('paddingLeft')) - parseInt(elem.css('paddingRight')) - parseInt(elem.css('marginLeft')) - parseInt(elem.css('marginRight')) - parseInt(elem.css('borderLeftWidth')) - parseInt(elem.css('borderRightWidth')); + elem.css('width', sideBarNavWidth); + }; - resizeFn(); - $(window).resize(resizeFn); + resizeFn(); + $(window).resize(resizeFn); }) - + + $('#settings-form').on('click', '.add-row', function(){ + var row = $(this).parents("tr") + var data = row.parent().children(".row-data") + + // Check key spaces + var name = row.children(".key").children("input").val() + if (name.indexOf(' ') !== -1) { + showAlertMessage("Key contains spaces", false) + return + } + // Check keys with the same name + var equals = false; + _.each(data.children(".key"), function(element) { + if ($(element).text() === name) { + equals = true + return + } + }) + if (equals) { + showAlertMessage("Two keys cannot be identical.", false) + return + } + + // Check empty fields + var empty = false; + _.each(row.children(".row-data").children("input"), function(element) { + if ($(element).val().length === 0) { + empty = true + return + } + }) + if (empty) { + showAlertMessage("Empty field(s)") + return + } + + var input_clone = row.clone() + // Change input row to data row + var full_name = row.parents("table").attr("name") + "." + name + row.attr("class", "row-data") + + _.each(row.children(), function(element) { + if ($(element).hasClass("number")) { // Index row + var numbers = data.children(".number") + if (numbers.length > 0) { + $(element).html(parseInt(numbers.last().text()) + 1) + } else { + $(element).html(1) + } + } else if ($(element).hasClass("buttons")) { // Change buttons + var prevSpan = $(element).parent().prev().children(".buttons").children("span") + var span = $(element).children("span") + if (prevSpan.hasClass("del-row")) { + span.removeClass("glyphicon-ok add-row") + span.addClass("glyphicon-remove del-row") + } else { + span.remove() + } + } else if ($(element).hasClass("key")) { + var input = $(element).children("input") + $(element).html(input.val()) + input.remove() + } else if($(element).hasClass("row-data")) { // Hide inputs + var input = $(element).children("input") + input.attr("type", "hidden") + input.attr("name", full_name + "." + $(element).attr("name")) + input.attr("value", input.val()) + input.attr("data-changed", "true") + + $(element).html($(element).html() + input.val()) + } else { + console.log("Unknown table element") + } + }) + row.parent().append(input_clone) + showAlertMessage("Row added", true) + }) + + $('#settings-form').on('click', '.del-row', function(){ + var row = $(this).parents("tr") + row.empty() + row.html(""); + }) + $('#settings-form').on('change', 'input', function(){ // this input was changed, add the changed data attribute to it $(this).attr('data-changed', true) @@ -94,7 +180,7 @@ $(document).ready(function(){ $('#advanced-toggle-button').click(function(){ Settings.showAdvanced = !Settings.showAdvanced - var advancedSelector = $('.advanced-setting') + var advancedSelector = $('.advanced-setting') if (Settings.showAdvanced) { advancedSelector.show() @@ -193,6 +279,82 @@ $('body').on('click', '.save-button', function(e){ return false; }); +function makeTable(setting, setting_name, setting_value) { + var html = "
" + html += "
" + setting.label + "
" + html += "
" + html += "

" + setting.help + "

" + html += "
" + html += "" + + // Column names + html += "" + if (setting.number === true) { + html += "" // Row number + } + html += "" // Key + _.each(setting.columns, function(col) { + html += "" // Data + }) + if (setting.can_delete === true || setting.can_add === true) { + html += "" // Buttons + } + html += "" + + // Rows + var row_num = 1 + _.each(setting_value, function(row, name) { + html += "" + if (setting.number === true) { + html += "" + } + html += "" + _.each(setting.columns, function(col) { + html += "" + }) + if (setting.can_delete === true) { + html += "" + } else if (setting.can_add === true) { + html += "" + } + html += "" + row_num++ + }) + + // Inputs + if (setting.can_add === true) { + html += makeTableInputs(setting) + } + + html += "
#" + setting.key.label + "" + col.label + "
" + row_num + "" + name + "" + if (row.hasOwnProperty(col.name)) { + html += row[col.name] + } + html += "
" + html += "
" + + return html; +} + +function makeTableInputs(setting) { + var html = "" + if (setting.number === true) { + html += "" + } + html += "\ + \ + " + _.each(setting.columns, function(col) { + html += "\ + \ + " + }) + html += "" + html += "" + + return html +} + function badgeSidebarForDifferences(changedInput) { // figure out which group this input is in var panelParentID = changedInput.closest('.panel').attr('id') @@ -242,7 +404,7 @@ function showRestartModal() { }, 1000); } -function cleanupFormValues(node) { +function cleanupFormValues(node) { if (node.type && node.type === 'checkbox') { return { name: node.name, value: node.checked ? true : false }; } else { diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 7c92f1cc51..6a63168579 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -29,6 +29,7 @@ const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings const QString DESCRIPTION_SETTINGS_KEY = "settings"; const QString SETTING_DEFAULT_KEY = "default"; const QString DESCRIPTION_NAME_KEY = "name"; +const QString SETTING_DESCRIPTION_TYPE_KEY = "type"; DomainServerSettingsManager::DomainServerSettingsManager() : _descriptionArray(), @@ -232,66 +233,92 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty return responseObject; } +bool DomainServerSettingsManager::settingExists(const QString& groupName, const QString& settingName, + const QJsonArray& descriptionArray, QJsonValue& settingDescription) { + foreach(const QJsonValue& groupValue, descriptionArray) { + QJsonObject groupObject = groupValue.toObject(); + if (groupObject[DESCRIPTION_NAME_KEY].toString() == groupName) { + + foreach(const QJsonValue& settingValue, groupObject[DESCRIPTION_SETTINGS_KEY].toArray()) { + QJsonObject settingObject = settingValue.toObject(); + if (settingObject[DESCRIPTION_NAME_KEY].toString() == settingName) { + settingDescription = settingObject[SETTING_DEFAULT_KEY]; + return true; + } + } + } + } + settingDescription = QJsonValue::Undefined; + return false; +} -const QString SETTING_DESCRIPTION_TYPE_KEY = "type"; +void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap, + const QJsonValue& settingDescription) { + if (newValue.isString()) { + if (newValue.toString().isEmpty()) { + // this is an empty value, clear it in settings variant so the default is sent + settingMap.remove(key); + } else { + // make sure the resulting json value has the right type + const QString settingType = settingDescription.toObject()[SETTING_DESCRIPTION_TYPE_KEY].toString(); + const QString INPUT_DOUBLE_TYPE = "double"; + const QString INPUT_INTEGER_TYPE = "int"; + + if (settingType == INPUT_DOUBLE_TYPE) { + settingMap[key] = newValue.toString().toDouble(); + } else if (settingType == INPUT_INTEGER_TYPE) { + settingMap[key] = newValue.toString().toInt(); + } else { + settingMap[key] = newValue.toString(); + } + } + } else if (newValue.isBool()) { + settingMap[key] = newValue.toBool(); + } else if (newValue.isObject()) { + if (!settingMap.contains(key)) { + // we don't have a map below this key yet, so set it up now + settingMap[key] = QVariantMap(); + } + + QVariantMap& thisMap = *reinterpret_cast(settingMap[key].data()); + foreach(const QString childKey, newValue.toObject().keys()) { + updateSetting(childKey, newValue.toObject()[childKey], thisMap, settingDescription.toObject()[key]); + } + + if (settingMap[key].toMap().isEmpty()) { + // we've cleared all of the settings below this value, so remove this one too + settingMap.remove(key); + } + } +} void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, QVariantMap& settingsVariant, - QJsonArray descriptionArray) { - foreach(const QString& key, postedObject.keys()) { + const QJsonArray& descriptionArray) { + // Iterate on the setting groups + foreach(const QString& groupKey, postedObject.keys()) { + QJsonValue groupValue = postedObject[groupKey]; - QJsonValue rootValue = postedObject[key]; + if (!settingsVariant.contains(groupKey)) { + // we don't have a map below this key yet, so set it up now + settingsVariant[groupKey] = QVariantMap(); + } - // we don't continue if this key is not present in our descriptionObject - foreach(const QJsonValue& groupValue, descriptionArray) { - if (groupValue.toObject()[DESCRIPTION_NAME_KEY].toString() == key) { - QJsonObject groupObject = groupValue.toObject(); - if (rootValue.isString()) { - if (rootValue.toString().isEmpty()) { - // this is an empty value, clear it in settings variant so the default is sent - settingsVariant.remove(key); - } else { - QString settingType = groupObject[SETTING_DESCRIPTION_TYPE_KEY].toString(); - - const QString INPUT_DOUBLE_TYPE = "double"; - const QString INPUT_INTEGER_TYPE = "int"; - - // make sure the resulting json value has the right type - - if (settingType == INPUT_DOUBLE_TYPE) { - settingsVariant[key] = rootValue.toString().toDouble(); - } else if (settingType == INPUT_INTEGER_TYPE) { - settingsVariant[key] = rootValue.toString().toInt(); - } else { - settingsVariant[key] = rootValue.toString(); - } - } - } else if (rootValue.isBool()) { - settingsVariant[key] = rootValue.toBool(); - } else if (rootValue.isObject()) { - // there's a JSON Object to explore, so attempt to recurse into it - QJsonObject nextDescriptionObject = groupObject; - - if (nextDescriptionObject.contains(DESCRIPTION_SETTINGS_KEY)) { - if (!settingsVariant.contains(key)) { - // we don't have a map below this key yet, so set it up now - settingsVariant[key] = QVariantMap(); - } - - QVariantMap& thisMap = *reinterpret_cast(settingsVariant[key].data()); - - recurseJSONObjectAndOverwriteSettings(rootValue.toObject(), - thisMap, - nextDescriptionObject[DESCRIPTION_SETTINGS_KEY].toArray()); - - if (thisMap.isEmpty()) { - // we've cleared all of the settings below this value, so remove this one too - settingsVariant.remove(key); - } - } - } + // Iterate on the settings + foreach(const QString& settingKey, groupValue.toObject().keys()) { + QJsonValue settingValue = groupValue.toObject()[settingKey]; + + QJsonValue thisDescription; + if (settingExists(groupKey, settingKey, descriptionArray, thisDescription)) { + QVariantMap& thisMap = *reinterpret_cast(settingsVariant[groupKey].data()); + updateSetting(settingKey, settingValue, thisMap, thisDescription); } } + + if (settingsVariant[groupKey].toMap().empty()) { + // we've cleared all of the settings below this value, so remove this one too + settingsVariant.remove(groupKey); + } } } diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index c2e8a7d90d..a21e0dc93a 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -32,7 +32,11 @@ public: private: QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false); void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, QVariantMap& settingsVariant, - QJsonArray descriptionArray); + const QJsonArray& descriptionArray); + bool settingExists(const QString& groupName, const QString& settingName, + const QJsonArray& descriptionArray, QJsonValue& settingDescription); + void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap, + const QJsonValue& settingDescription); void persistToFile(); QJsonArray _descriptionArray;