*
* Author: LV
*/
$('[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);
};
resizeFn();
$(window).resize(resizeFn);
});
$('#settings-form').on('click', '.' + Settings.ADD_ROW_BUTTON_CLASS, function(){
addTableRow(this);
});
$('#settings-form').on('click', '.' + Settings.DEL_ROW_BUTTON_CLASS, function(){
deleteTableRow(this);
});
$('#settings-form').on('click', '.' + Settings.MOVE_UP_BUTTON_CLASS, function(){
moveTableRow(this, true);
});
$('#settings-form').on('click', '.' + Settings.MOVE_DOWN_BUTTON_CLASS, function(){
moveTableRow(this, false);
});
$('#settings-form').on('keypress', 'table input', function(e){
if (e.keyCode == 13) {
// capture enter in table input
// if we have a sibling next to us that has an input, jump to it, otherwise check if we have a glyphicon for add to click
sibling = $(this).parent('td').next();
if (sibling.hasClass(Settings.DATA_COL_CLASS)) {
// set focus to next input
sibling.find('input').focus()
} else if (sibling.hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) {
sibling.find('.' + Settings.ADD_ROW_BUTTON_CLASS).click()
// set focus to the first input in the new row
$(this).closest('table').find('tr.inputs input:first').focus()
}
}
});
$('#settings-form').on('change', '.' + Settings.TRIGGER_CHANGE_CLASS , function(){
// this input was changed, add the changed data attribute to it
$(this).attr('data-changed', true)
badgeSidebarForDifferences($(this))
})
$('.advanced-toggle').click(function(){
Settings.showAdvanced = !Settings.showAdvanced
var advancedSelector = $('.' + Settings.ADVANCED_CLASS)
if (Settings.showAdvanced) {
advancedSelector.show();
$(this).html("Hide advanced")
} else {
advancedSelector.hide();
$(this).html("Show advanced")
}
$(this).blur();
})
$('#settings-form').on('click', '#' + Settings.CREATE_DOMAIN_ID_BTN_ID, function(){
showDomainCreationAlert(false);
})
$('#settings-form').on('click', '#' + Settings.CHOOSE_DOMAIN_ID_BTN_ID, function(){
chooseFromHighFidelityDomains($(this))
});
$('#settings-form').on('change', 'select', function(){
$("input[name='" + $(this).attr('data-hidden-input') + "']").val($(this).val()).change()
});
$('#settings-form').on('click', '#' + Settings.DISCONNECT_ACCOUNT_BTN_ID, function(e){
disonnectHighFidelityAccount();
e.preventDefault();
});
$('#settings-form').on('click', '#' + Settings.CONNECT_ACCOUNT_BTN_ID, function(e){
$(this).blur();
prepareAccessTokenPrompt();
});
var panelsSource = $('#panels-template').html()
Settings.panelsTemplate = _.template(panelsSource)
var sidebarTemplate = $('#list-group-template').html()
Settings.sidebarTemplate = _.template(sidebarTemplate)
// $('body').scrollspy({ target: '#setup-sidebar'})
reloadSettings();
});
function postSettings(jsonSettings) {
// POST the form JSON to the domain-server settings.json endpoint so the settings are saved
$.ajax('/settings.json', {
data: JSON.stringify(jsonSettings),
contentType: 'application/json',
type: 'POST'
}).done(function(data){
if (data.status == "success") {
showRestartModal();
} else {
showErrorMessage("Error", SETTINGS_ERROR_MESSAGE)
reloadSettings();
}
}).fail(function(){
showErrorMessage("Error", SETTINGS_ERROR_MESSAGE)
reloadSettings();
});
}
function setupHFAccountButton() {
// figure out how we should handle the HF connect button
var accessToken = Settings.data.values.metaverse.access_token;
// setup an object for the settings we want our button to have
var buttonSetting = {
type: 'button',
name: 'connected_account',
label: 'Connected Account',
}
var hasAccessToken = accessToken.length > 0;
if (hasAccessToken) {
buttonSetting.help = "Click the button above to clear your OAuth token and disconnect your High Fidelity account.";
buttonSetting.classes = "btn-danger";
buttonSetting.button_label = "Disconnect High Fidelity Account";
buttonSetting.id = Settings.DISCONNECT_ACCOUNT_BTN_ID;
} else {
buttonSetting.help = "Click the button above to connect your High Fidelity account.";
buttonSetting.classes = "btn-primary";
buttonSetting.button_label = "Connect High Fidelity Account";
buttonSetting.id = Settings.CONNECT_ACCOUNT_BTN_ID;
buttonSetting.href = Settings.METAVERSE_URL + "/user/tokens/new?for_domain_server=true";
// since we do not have an access token we change hide domain ID and auto networking settings
// without an access token niether of them can do anything
$("[data-keypath='metaverse.id']").hide();
$("[data-keypath='metaverse.automatic_networking']").hide();
}
var tokenLocked = _(Settings.data).valueForKeyPath("locked.metaverse.access_token");
// use the existing getFormGroup helper to ask for a button
var buttonGroup = viewHelpers.getFormGroup('', buttonSetting, Settings.data.values, false, tokenLocked);
// add the button group to the top of the metaverse panel
$('#metaverse .panel-body').prepend(buttonGroup);
}
function postNewAccessToken(access_token) {
var newAccessToken = {
"metaverse": {
"access_token": access_token
}
};
postSettings(newAccessToken);
}
function disonnectHighFidelityAccount() {
// the user clicked on the disconnect account btn - give them a sweet alert to make sure this is what they want to do
swal({
title: "Are you sure?",
text: "This will remove your domain-server OAuth access token."
+ "This could cause your domain to appear offline and no longer be reachable via any place names.",
type: "warning",
html: true,
showCancelButton: true,
}, function(){
// we need to post to settings to clear the access-token
postNewAccessToken("");
});
}
function prepareAccessTokenPrompt() {
swal({
title: "Connect Account",
type: "input",
text: "Paste your created access token here." +
"If you did not successfully create an access token click cancel below and attempt to connect your account again.",
showCancelButton: true,
closeOnConfirm: false,
html: true
}, function(inputValue){
if (inputValue === false) return false;
if (inputValue === "") {
swal.showInputError("Please paste your access token in the input field.")
return false
}
// we have an input value - set the access token input with this and save settings
$("[name='metaverse.access_token']").val(inputValue).change();
// if the user doesn't have a domain ID set, give them the option to create one now
if (!Settings.data.values.metaverse.id) {
// show domain ID selection alert
showDomainIDChoiceAlert();
} else {
swal.close();
saveSettings();
}
});
}
function showDomainIDChoiceAlert() {
swal({
title: 'Domain ID',
type: 'info',
text: "You do not currently have a domain ID." +
"This is required to point place names at your domain and to use automatic networking" +
"Would you like to create a domain ID via the Metaverse API?",
showCancelButton: true,
confirmButtonText: "Create new domain ID",
cancelButtonText: "Skip",
closeOnConfirm: false,
html: true
}, function(isConfirm){
if (isConfirm) {
// show the swal to create a new domain via API
showDomainCreationAlert(true);
} else {
// user cancelled, close this swal and save the access token we got
swal.close();
saveSettings();
}
});
}
function showDomainCreationAlert(justConnected) {
swal({
title: 'Create new domain ID',
type: 'input',
text: 'Enter a short description for this machine.This will help you identify which domain ID belongs to which machine.',
showCancelButton: true,
confirmButtonText: "Create",
closeOnConfirm: false,
html: true
}, function(inputValue){
if (inputValue === false) {
swal.close();
// user cancelled domain ID creation - if we're supposed to save after cancel then save here
if (justConnected) {
saveSettings();
}
} else {
// we're going to change the alert to a new one with a spinner while we create this domain
swal({
title: 'Creating domain ID',
text: '
',
html: true,
showConfirmButton: false,
allowEscapeKey: false
});
createNewDomainID(inputValue, justConnected);
}
});
}
function createNewDomainID(description, justConnected) {
// get the JSON object ready that we'll use to create a new domain
var domainJSON = {
"domain": {
"description": description
},
"access_token": $("[name='metaverse.access_token']").val()
}
$.post(Settings.METAVERSE_URL + "/api/v1/domains", domainJSON, function(data){
if (data.status == "success") {
// we successfully created a domain ID, set it on that field
var domainID = data.domain.id;
$("[name='metaverse.id']").val(domainID).change();
if (justConnected) {
var successText = "We connnected your High Fidelity account and created a new domain ID for this machine."
} else {
var successText = "We created a new domain ID for this machine."
}
successText += "Click the button below to save your new settings and restart your domain-server.";
// show a sweet alert to say we are all finished up and that we need to save
swal({
title: 'Success!',
type: 'success',
text: successText,
html: true,
confirmButtonText: 'Save'
}, function(){
saveSettings();
});
}
}).fail(function(){
var errorText = "There was a problem creating your new domain ID. Do you want to try again or";
if (justConnected) {
errorText += " just save your new access token?You can always create a new domain ID later.";
} else {
errorText += " cancel?"
}
// we failed to create the new domain ID, show a sweet-alert that lets them try again or cancel
swal({
title: '',
type: 'error',
text: errorText,
html: true,
confirmButtonText: 'Try again',
showCancelButton: true,
closeOnConfirm: false
}, function(isConfirm){
if (isConfirm) {
// they want to try creating a domain ID again
showDomainCreationAlert(justConnected);
} else {
// they want to cancel
if (justConnected) {
// since they just connected we need to save the access token here
saveSettings();
}
}
});
});
}
function reloadSettings() {
$.getJSON('/settings.json', function(data){
_.extend(data, viewHelpers)
$('.nav-stacked').html(Settings.sidebarTemplate(data))
$('#panels').html(Settings.panelsTemplate(data))
Settings.data = data;
Settings.initialValues = form2js('settings-form', ".", false, cleanupFormValues, true);
if (!_.has(data["locked"], "metaverse") && !_.has(data["locked"]["metaverse"], "id")) {
// append the domain selection modal, as long as it's not locked
appendDomainIDButtons();
}
// call our method to setup the HF account button
setupHFAccountButton();
// add tooltip to locked settings
$('label.locked').tooltip({
placement: 'right',
title: 'This setting is in the master config file and cannot be changed'
});
});
}
function appendDomainIDButtons() {
var metaverseInput = $("[name='metaverse.id']");
var createButton = $("");
var chooseButton = $("");
metaverseInput.after(chooseButton);
metaverseInput.after(createButton);
}
var SETTINGS_ERROR_MESSAGE = "There was a problem saving domain settings. Please try again!";
function saveSettings() {
// disable any inputs not changed
$("input:not([data-changed])").each(function(){
$(this).prop('disabled', true);
});
// grab a JSON representation of the form via form2js
var formJSON = form2js('settings-form', ".", false, cleanupFormValues, true);
console.log(formJSON);
// re-enable all inputs
$("input").each(function(){
$(this).prop('disabled', false);
});
// remove focus from the button
$(this).blur();
// POST the form JSON to the domain-server settings.json endpoint so the settings are saved
postSettings(formJSON);
}
$('body').on('click', '.save-button', function(e){
saveSettings();
return false;
});
function makeTable(setting, keypath, setting_value, isLocked) {
var isArray = !_.has(setting, 'key')
if (!isArray && setting.can_order) {
setting.can_order = false;
}
var html = "";
if (setting.help) {
html += "" + setting.help + ""
}
html += "
"
// Column names
html += "
"
if (setting.numbered === true) {
html += "
#
" // Row number
}
if (setting.key) {
html += "
" + setting.key.label + "
" // Key
}
_.each(setting.columns, function(col) {
html += "
" + col.label + "
" // Data
})
if (!isLocked) {
if (setting.can_order) {
html += "
";
}
html += "
"
}
// populate rows in the table from existing values
var row_num = 1
_.each(setting_value, function(row, indexOrName) {
html += "
"
if (setting.numbered === true) {
html += "
" + row_num + "
"
}
if (setting.key) {
html += "
" + indexOrName + "
"
}
_.each(setting.columns, function(col) {
html += "
"
if (isArray) {
rowIsObject = setting.columns.length > 1
colValue = rowIsObject ? row[col.name] : row
html += colValue
// for arrays we add a hidden input to this td so that values can be posted appropriately
html += ""
} else if (row.hasOwnProperty(col.name)) {
html += row[col.name]
}
html += "
"
})
if (!isLocked) {
if (setting.can_order) {
html += "
"
}
html += "
"
}
html += "
"
row_num++
});
// populate inputs in the table for new values
if (!isLocked) {
html += makeTableInputs(setting)
}
html += "
"
return html;
}
function makeTableInputs(setting) {
var html = "
"
if (setting.numbered === true) {
html += "
"
}
if (setting.key) {
html += "
\
\
"
}
_.each(setting.columns, function(col) {
html += "
\
\
"
})
if (setting.can_order) {
html += "
"
}
html += "
"
html += "
"
return html
}
function badgeSidebarForDifferences(changedElement) {
// figure out which group this input is in
var panelParentID = changedElement.closest('.panel').attr('id')
// if the panel contains non-grouped settings, the initial value is Settings.initialValues
var isGrouped = $(panelParentID).hasClass('grouped');
if (isGrouped) {
var initialPanelJSON = Settings.initialValues[panelParentID];
// get a JSON representation of that section
var panelJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID];
} else {
var initialPanelJSON = Settings.initialValues;
// get a JSON representation of that section
var panelJSON = form2js(panelParentID, ".", false, cleanupFormValues, true);
}
var badgeValue = 0
// badge for any settings we have that are not the same or are not present in initialValues
for (var setting in panelJSON) {
if ((!_.has(initialPanelJSON, setting) && panelJSON[setting] !== "") ||
(!_.isEqual(panelJSON[setting], initialPanelJSON[setting])
&& (panelJSON[setting] !== "" || _.has(initialPanelJSON, setting)))) {
badgeValue += 1
}
}
// update the list-group-item badge to have the new value
if (badgeValue == 0) {
badgeValue = ""
}
$("a[href='#" + panelParentID + "'] .badge").html(badgeValue);
}
function addTableRow(add_glyphicon) {
var row = $(add_glyphicon).closest('tr')
var table = row.parents('table')
var isArray = table.data('setting-type') === 'array'
var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS)
if (!isArray) {
// Check key spaces
var key = row.children(".key").children("input").val()
if (key.indexOf(' ') !== -1) {
showErrorMessage("Error", "Key contains spaces")
return
}
// Check keys with the same name
var equals = false;
_.each(columns.children(".key"), function(element) {
if ($(element).text() === key) {
equals = true
return
}
})
if (equals) {
showErrorMessage("Error", "Two keys cannot be identical")
return
}
}
// Check empty fields
var empty = false;
_.each(row.children('.' + Settings.DATA_COL_CLASS + ' input'), function(element) {
if ($(element).val().length === 0) {
empty = true
return
}
})
if (empty) {
showErrorMessage("Error", "Empty field(s)")
return
}
var input_clone = row.clone()
// Change input row to data row
var table = row.parents("table")
var setting_name = table.attr("name")
var full_name = setting_name + "." + key
row.addClass(Settings.DATA_ROW_CLASS + " " + Settings.NEW_ROW_CLASS)
row.removeClass("inputs")
_.each(row.children(), function(element) {
if ($(element).hasClass("numbered")) {
// Index row
var numbers = columns.children(".numbered")
if (numbers.length > 0) {
$(element).html(parseInt(numbers.last().text()) + 1)
} else {
$(element).html(1)
}
} else if ($(element).hasClass(Settings.REORDER_BUTTONS_CLASS)) {
$(element).html("
")
} else if ($(element).hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) {
// Change buttons
var span = $(element).children("span")
span.removeClass(Settings.ADD_ROW_SPAN_CLASSES)
span.addClass(Settings.DEL_ROW_SPAN_CLASSES)
} else if ($(element).hasClass("key")) {
var input = $(element).children("input")
$(element).html(input.val())
input.remove()
} else if ($(element).hasClass(Settings.DATA_COL_CLASS)) {
// Hide inputs
var input = $(element).children("input")
input.attr("type", "hidden")
if (isArray) {
var row_index = row.siblings('.' + Settings.DATA_ROW_CLASS).length
var key = $(element).attr('name')
// are there multiple columns or just one?
// with multiple we have an array of Objects, with one we have an array of whatever the value type is
var num_columns = row.children('.' + Settings.DATA_COL_CLASS).length
input.attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : ""))
} else {
input.attr("name", full_name + "." + $(element).attr("name"))
}
input.attr("data-changed", "true")
$(element).append(input.val())
} else {
console.log("Unknown table element")
}
})
input_clone.find('input').each(function(){
$(this).val($(this).attr('data-default'));
});
if (isArray) {
updateDataChangedForSiblingRows(row, true)
// the addition of any table row should remove the empty-array-row
row.siblings('.empty-array-row').remove()
}
badgeSidebarForDifferences($(table))
row.parent().append(input_clone)
}
function deleteTableRow(delete_glyphicon) {
var row = $(delete_glyphicon).closest('tr')
var table = $(row).closest('table')
var isArray = table.data('setting-type') === 'array'
row.empty();
if (!isArray) {
row.html("");
} else {
if (table.find('.' + Settings.DATA_ROW_CLASS).length > 1) {
updateDataChangedForSiblingRows(row)
// this isn't the last row - we can just remove it
row.remove()
} else {
// this is the last row, we can't remove it completely since we need to post an empty array
row.removeClass(Settings.DATA_ROW_CLASS).removeClass(Settings.NEW_ROW_CLASS)
row.addClass('empty-array-row')
row.html("");
}
}
// we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated
badgeSidebarForDifferences($(table))
}
function moveTableRow(move_glyphicon, move_up) {
var row = $(move_glyphicon).closest('tr')
var table = $(row).closest('table')
var isArray = table.data('setting-type') === 'array'
if (!isArray) {
return;
}
if (move_up) {
var prev_row = row.prev()
if (prev_row.hasClass(Settings.DATA_ROW_CLASS)) {
prev_row.before(row)
}
} else {
var next_row = row.next()
if (next_row.hasClass(Settings.DATA_ROW_CLASS)) {
next_row.after(row)
}
}
// we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated
badgeSidebarForDifferences($(table))
}
function updateDataChangedForSiblingRows(row, forceTrue) {
// anytime a new row is added to an array we need to set data-changed for all sibling row inputs to true
// unless it matches the inital set of values
if (!forceTrue) {
// figure out which group this row is in
var panelParentID = row.closest('.panel').attr('id')
// get the short name for the setting from the table
var tableShortName = row.closest('table').data('short-name')
// get a JSON representation of that section
var panelSettingJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID][tableShortName]
var initialPanelSettingJSON = Settings.initialValues[panelParentID][tableShortName]
// if they are equal, we don't need data-changed
isTrue = !_.isEqual(panelSettingJSON, initialPanelSettingJSON)
} else {
isTrue = true
}
row.siblings('.' + Settings.DATA_ROW_CLASS).each(function(){
var hiddenInput = $(this).find('td.' + Settings.DATA_COL_CLASS + ' input')
if (isTrue) {
hiddenInput.attr('data-changed', isTrue)
} else {
hiddenInput.removeAttr('data-changed')
}
})
}
function showRestartModal() {
$('#restart-modal').modal({
backdrop: 'static',
keyboard: false
});
var secondsElapsed = 0;
var numberOfSecondsToWait = 3;
var refreshSpan = $('span#refresh-time')
refreshSpan.html(numberOfSecondsToWait + " seconds");
// call ourselves every 1 second to countdown
var refreshCountdown = setInterval(function(){
secondsElapsed++;
secondsLeft = numberOfSecondsToWait - secondsElapsed
refreshSpan.html(secondsLeft + (secondsLeft == 1 ? " second" : " seconds"))
if (secondsElapsed == numberOfSecondsToWait) {
location.reload(true);
clearInterval(refreshCountdown);
}
}, 1000);
}
function cleanupFormValues(node) {
if (node.type && node.type === 'checkbox') {
return { name: node.name, value: node.checked ? true : false };
} else {
return false;
}
}
function showErrorMessage(title, message) {
swal(title, message)
}
function chooseFromHighFidelityDomains(clickedButton) {
// setup the modal to help user pick their domain
if (Settings.initialValues.metaverse.access_token) {
// add a spinner to the choose button
clickedButton.html("Loading domains...")
clickedButton.attr('disabled', 'disabled')
// get a list of user domains from data-web
data_web_domains_url = Settings.METAVERSE_URL + "/api/v1/domains?access_token="
$.getJSON(data_web_domains_url + Settings.initialValues.metaverse.access_token, function(data){
modal_buttons = {
cancel: {
label: 'Cancel',
className: 'btn-default'
}
}
if (data.data.domains.length) {
// setup a select box for the returned domains
modal_body = "
Choose the High Fidelity domain you want this domain-server to represent. This will set your domain ID on the settings page.
"
domain_select = $("")
_.each(data.data.domains, function(domain){
domain_select.append("");
})
modal_body += "" + domain_select[0].outerHTML
modal_buttons["success"] = {
label: 'Choose domain',
callback: function() {
domainID = $('#domain-name-select').val()
// set the domain ID on the form
$("[name='metaverse.id']").val(domainID).change();
}
}
} else {
modal_buttons["success"] = {
label: 'Create new domain',
callback: function() {
window.open("https://metaverse.highfidelity.com/user/domains", '_blank');
}
}
modal_body = "
You do not have any domains in your High Fidelity account." +
"
Go to your domains page to create a new one. Once your domain is created re-open this dialog to select it.
"
}
bootbox.dialog({
title: "Choose matching domain",
message: modal_body,
buttons: modal_buttons
})
// remove the spinner from the choose button
clickedButton.html("Choose from my domains")
clickedButton.removeAttr('disabled')
})
} else {
bootbox.alert({
message: "You must have an access token to query your High Fidelity domains.
" +
"Please follow the instructions on the settings page to add an access token.",
title: "Access token required"
})
}
}