mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-04-07 10:02:24 +02:00
cleanup pass
This commit is contained in:
parent
c8d503f717
commit
2a8b91e45e
17 changed files with 1222 additions and 1207 deletions
|
@ -2,6 +2,8 @@
|
|||
/* eslint-disable comma-dangle, no-empty */
|
||||
/* global EventBridge: true, PARAMS, signal, assert, log, debugPrint,
|
||||
bridgedSettings, _utils, jquerySettings, */
|
||||
|
||||
// helper functions for debugging and testing the UI in an external web brower
|
||||
var _debug = {
|
||||
handleUncaughtException: function onerror(message, fileName, lineNumber, colNumber, err) {
|
||||
if (message === onerror.lastMessage) {
|
||||
|
@ -25,7 +27,7 @@ var _debug = {
|
|||
}
|
||||
},
|
||||
loadScriptNodes: function loadScriptNodes(selector) {
|
||||
// scripts are loaded this way to ensure refreshing the client script refreshes dependencies too
|
||||
// scripts are loaded this way to ensure that when the client script refreshes, so are the app's dependencies
|
||||
[].forEach.call(document.querySelectorAll(selector), function(script) {
|
||||
script.parentNode.removeChild(script);
|
||||
if (script.src) {
|
||||
|
@ -35,7 +37,8 @@ var _debug = {
|
|||
document.write(script.outerHTML);
|
||||
});
|
||||
},
|
||||
// TESTING MOCK (allows the UI to be tested using a normal web browser, outside of Interface
|
||||
|
||||
// TESTING MOCKs
|
||||
openEventBridgeMock: function openEventBridgeMock(onEventBridgeOpened) {
|
||||
var updatedValues = openEventBridgeMock.updatedValues = {};
|
||||
// emulate EventBridge's API
|
||||
|
@ -47,12 +50,10 @@ var _debug = {
|
|||
onEventBridgeOpened(EventBridge);
|
||||
assert(!bridgedSettings.onUnhandledMessage);
|
||||
bridgedSettings.onUnhandledMessage = function(msg) {
|
||||
if (1||!msg || msg.method !== 'valueUpdated') {
|
||||
log('bridgedSettings.onUnhandledMessage', msg);
|
||||
}
|
||||
log('bridgedSettings.onUnhandledMessage', msg);
|
||||
return true;
|
||||
};
|
||||
// manually trigger bootstrapping responses
|
||||
// manually trigger initial bootstrapping responses (that the client script would normally send)
|
||||
bridgedSettings.handleExtraParams({uuid: PARAMS.uuid, ns: PARAMS.ns, extraParams: {
|
||||
mock: true,
|
||||
appVersion: 'browsermock',
|
||||
|
@ -61,8 +62,8 @@ var _debug = {
|
|||
toolbar: true,
|
||||
browser: true,
|
||||
desktop: true,
|
||||
tablet: false,
|
||||
hmd: false,
|
||||
tablet: /tablet/.test(location) || /android|ipad|iphone/i.test(navigator.userAgent),
|
||||
hmd: /hmd/.test(location),
|
||||
},
|
||||
} });
|
||||
bridgedSettings.setValue('ui-show-advanced-options', true);
|
||||
|
@ -72,13 +73,13 @@ var _debug = {
|
|||
console.log.apply(console, ['[mock] ' + msg].concat([].slice.call(arguments,1)));
|
||||
}
|
||||
|
||||
// generate mock data in response to outgoing web events
|
||||
// generate mock data in response to outgoing web page events
|
||||
function onEmitWebEvent(message) {
|
||||
try {
|
||||
var obj = JSON.parse(message);
|
||||
var obj = JSON.parse(message);
|
||||
} catch (e) {}
|
||||
if (!obj) {
|
||||
// message isn't JSON or doesn't expect a reply so just log it and bail early
|
||||
// message isn't JSON so just log it and bail early
|
||||
log('consuming non-callback web event', message);
|
||||
return;
|
||||
}
|
||||
|
@ -92,20 +93,21 @@ var _debug = {
|
|||
var key = obj.params[0];
|
||||
var node = jquerySettings.findNodeByKey(key, true);
|
||||
// log('Settings.getValue.findNodeByKey', key, node);
|
||||
var type = node && (node.dataset.type || node.type || node.attributes['type']);
|
||||
var type = node && (node.dataset.hifiType || node.dataset.type || node.type);
|
||||
switch (type) {
|
||||
case 'checkbox': {
|
||||
obj.result = /tooltip/i.test(key) || PARAMS.tooltiptest ? true : Math.random() > 0.5;
|
||||
case 'hifiButton':
|
||||
case 'hifiCheckbox': {
|
||||
obj.result = /tooltip|advanced-options/i.test(key) || PARAMS.tooltiptest ? true : Math.random() > 0.5;
|
||||
} break;
|
||||
case 'radio-group': {
|
||||
case 'hifiRadioGroup': {
|
||||
var radios = $(node).find('input[type=radio]').toArray();
|
||||
while (Math.random() < 0.9) {
|
||||
radios.push(radios.shift());
|
||||
radios.push(radios.shift());
|
||||
}
|
||||
obj.result = radios[0].value;
|
||||
} break;
|
||||
case 'spinner':
|
||||
case 'number': {
|
||||
case 'hifiSpinner':
|
||||
case 'hifiSlider': {
|
||||
var step = node.step || 1, precision = (1/step).toString().length - 1;
|
||||
var magnitude = node.max || (precision >=1 ? Math.pow(10, precision-1) : 10);
|
||||
obj.result = parseFloat((Math.random() * magnitude).toFixed(precision||1));
|
||||
|
|
|
@ -21,10 +21,10 @@ var SettingsJSON = (function() {
|
|||
|
||||
function encodeNodes(resolver) {
|
||||
return resolver.getAllNodes().reduce((function(out, input, i) {
|
||||
debugPrint('input['+i+']', input.id);
|
||||
//debugPrint('input['+i+']', input.id);
|
||||
var id = input.id,
|
||||
key = resolver.getKey(id);
|
||||
debugPrint('toJSON', id, key, input.id);
|
||||
//debugPrint('toJSON', id, key, input.id);
|
||||
setPath(out, key.split('/'), resolver.getValue(key));
|
||||
return out;
|
||||
}).bind(this), {});
|
||||
|
@ -35,7 +35,7 @@ var SettingsJSON = (function() {
|
|||
obj = path.reduce(function(obj, subkey) {
|
||||
return obj[subkey] = obj[subkey] || {};
|
||||
}, obj);
|
||||
debugPrint('setPath', key, Object.keys(obj));
|
||||
//debugPrint('setPath', key, Object.keys(obj));
|
||||
obj[key] = value;
|
||||
}
|
||||
|
||||
|
@ -81,23 +81,24 @@ var SettingsJSON = (function() {
|
|||
var HTML = document.getElementById('POPUP').innerHTML
|
||||
.replace(/\bxx-script\b/g, 'script')
|
||||
.replace('JSON', JSON.stringify(tmp, 0, 2).replace(/\n/g, '<br />'));
|
||||
if (arguments[3] === '_overlayWebWindow') {
|
||||
if (/WebWindowEx/.test(navigator.userAgent) ) {
|
||||
bridgedSettings.sendEvent({
|
||||
method: 'overlayWebWindow',
|
||||
userAgent: navigator.userAgent,
|
||||
options: {
|
||||
title: 'app-camera-move-export' + (title ? '::'+title : ''),
|
||||
content: HTML,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// make the browser address bar less ugly by putting spaces and friedly name as a "URL footer"
|
||||
// append a footer to the data URI so it displays cleaner in the built-in browser window that opens
|
||||
var footer = '<\!-- #' + HTML.substr(0,256).replace(/./g,' ') + (title || 'Camera Move Settings');
|
||||
window.open("data:text/html;escape," + encodeURIComponent(HTML) + footer,"app-camera-move-export");
|
||||
}
|
||||
}
|
||||
|
||||
function applyJSON(resolver, name, tmp) {
|
||||
assert('version' in tmp && 'settings' in tmp, 'invalid settings record: ' + JSON.stringify(tmp));
|
||||
assert(tmp && 'version' in tmp && 'settings' in tmp, 'invalid settings record: ' + JSON.stringify(tmp));
|
||||
var settings = rollupPaths(tmp.settings);
|
||||
for (var p in settings) {
|
||||
if (/^[.]/.test(p)) {
|
||||
|
@ -118,6 +119,7 @@ var SettingsJSON = (function() {
|
|||
return;
|
||||
}
|
||||
try {
|
||||
log('parsing json', json);
|
||||
json = JSON.parse(json);
|
||||
} catch (e) {
|
||||
throw new Error('Could not parse pasted JSON: ' + e + '\n\n' + (json||'').replace(/</g,'<'));
|
||||
|
|
|
@ -9,13 +9,53 @@ function preconfigureLESS(options) {
|
|||
|
||||
options.globalVars = Object.assign(options.globalVars||{}, { hash: '' });
|
||||
Object.assign(options, {
|
||||
errorReporting: Object.assign(function errorReporting(op, e, rootHref) {
|
||||
var type = 'Less-'+e.type+'-Error';
|
||||
var lastResult = errorReporting.lastResult;
|
||||
delete errorReporting.lastResult;
|
||||
if (lastResult && lastResult['#line']) {
|
||||
e.line += lastResult['#line'] - 2;
|
||||
}
|
||||
e.stack = [type+':'+e.message+' at '+e.filename+':'+(e.line+1)]
|
||||
.concat((e.extract||[]).map(function(output, index) {
|
||||
return (index + e.line)+': '+output;
|
||||
})).join('\n\t');
|
||||
_debug.handleUncaughtException(type+': ' + e.message, e.filename, e.line, e.column, {
|
||||
type: type,
|
||||
message: e.message,
|
||||
fileName: e.filename,
|
||||
lineNumber: e.line,
|
||||
stack: e.stack,
|
||||
});
|
||||
}, {
|
||||
lastResult: null,
|
||||
lastSource: null,
|
||||
getLocation: null,
|
||||
$patch: function() {
|
||||
// when errors occur, stash the source and #line {offset} for better error dispay above
|
||||
assert(!this.getLocation);
|
||||
var originalGetLocation = less.utils.getLocation,
|
||||
self = this;
|
||||
less.utils.getLocation = customGetLocation;
|
||||
delete self.$patch; // only need to apply once
|
||||
function customGetLocation(index, stream) {
|
||||
self.lastSource = stream;
|
||||
var result = originalGetLocation.apply(this, arguments);
|
||||
result.source = stream;
|
||||
stream.replace(/#line (\d+)/, function(_, offset) {
|
||||
result['#line'] = parseInt(offset);
|
||||
});
|
||||
return self.lastResult = result;
|
||||
}
|
||||
},
|
||||
}),
|
||||
// poll: 1000,
|
||||
// watch: true,
|
||||
});
|
||||
var lessManager = {
|
||||
options: options,
|
||||
onViewportUpdated: onViewportUpdated,
|
||||
elements: $(options.selector).remove(),
|
||||
elements: $(options.selector).remove()
|
||||
};
|
||||
|
||||
return lessManager;
|
||||
|
@ -32,7 +72,6 @@ function preconfigureLESS(options) {
|
|||
}
|
||||
delete globalVars.hash;
|
||||
Object.assign(globalVars, {
|
||||
'interface-mode': /highfidelity/i.test(navigator.userAgent),
|
||||
'inner-width': viewport.inner.width,
|
||||
'inner-height': viewport.inner.height,
|
||||
'client-width': viewport.client.width,
|
||||
|
@ -41,16 +80,21 @@ function preconfigureLESS(options) {
|
|||
});
|
||||
globalVars.hash = JSON.stringify(JSON.stringify(globalVars,0,2)).replace(/\\n/g , '\\000a');
|
||||
var hash = JSON.stringify(globalVars, 0, 2);
|
||||
log('onViewportUpdated', JSON.parse(onViewportUpdated.lastHash||'{}')['inner-width'], JSON.parse(hash)['inner-width']);
|
||||
debugPrint('onViewportUpdated', JSON.parse(onViewportUpdated.lastHash||'{}')['inner-width'], JSON.parse(hash)['inner-width']);
|
||||
if (onViewportUpdated.lastHash !== hash) {
|
||||
debugPrint('updating lessVars', 'less.modifyVars:' + typeof less.modifyVars, JSON.stringify(globalVars, 0, 2));
|
||||
// dump less variables if lessDebug=true was in the url
|
||||
PARAMS.lessDebug && $('#errors').show().html("<pre>").children(0).text(hash);
|
||||
// LESS needs some help to recompile inline styles, so a fresh copy of the source nodes is swapped-in
|
||||
|
||||
// patch less with absolute line number reporting
|
||||
options.errorReporting && options.errorReporting.$patch && options.errorReporting.$patch();
|
||||
|
||||
// to recompile inline styles (in response to onresize or when developing),
|
||||
// a fresh copy of the source nodes gets swapped-in
|
||||
var newNodes = lessManager.elements.clone().appendTo(document.body);
|
||||
// less.modifyVars && less.modifyVars(true, globalVars);
|
||||
// note: refresh(reload, modifyVars, clearFileCache)
|
||||
less.refresh(false, globalVars).then(function(result) {
|
||||
log('less.refresh completed OK', result);
|
||||
debugPrint('less.refresh completed OK', result);
|
||||
})['catch'](function(err) {
|
||||
log('less ERROR:', err);
|
||||
});
|
||||
|
|
|
@ -5,143 +5,165 @@
|
|||
// ----------------------------------------------------------------------------
|
||||
// manage jquery-tooltipster hover tooltips
|
||||
var TooltipManager = (function(global) {
|
||||
Object.assign(TooltipManager, {
|
||||
BASECONFIG: {
|
||||
theme: ['tooltipster-noir'],
|
||||
side: ['right','top','bottom', 'left'],
|
||||
updateAnimation: 'scale',
|
||||
delay: [750, 1000],
|
||||
distance: { right: 24, left: 8, top: 8, bottom: 8 },
|
||||
contentAsHTML: true,
|
||||
},
|
||||
});
|
||||
|
||||
function TooltipManager(options) {
|
||||
this.options = options;
|
||||
assert(options.elements, 'TooltipManager constructor expects options.elements');
|
||||
assert(options.tooltips, 'TooltipManager constructor expects options.tooltips');
|
||||
var TOOLTIPS = this.TOOLTIPS = {};
|
||||
$(options.tooltips).find('[for]').each(function() {
|
||||
var id = $(this).attr('for');
|
||||
TOOLTIPS[id] = $(this).html();
|
||||
});
|
||||
|
||||
var _self = this;
|
||||
options.elements.each(function() {
|
||||
var element = $($(this).closest('.tooltip-target').get(0) || this),
|
||||
input = element.is('input, button') ? element : element.find('input, button'),
|
||||
parent = element.is('.row, button') ? element : element.parent(),
|
||||
id = element.attr('for') || input.prop('id'),
|
||||
tip = TOOLTIPS[id] || element.prop('title');
|
||||
|
||||
var tooltipSide = element.data('tooltipSide');
|
||||
|
||||
debugPrint('binding tooltip', tooltipSide, element[0].nodeName, id || element[0], tip);
|
||||
if (!tip) {
|
||||
return log('missing tooltip:', this.nodeName, id || this.id || this.name || $(this).text());
|
||||
}
|
||||
if (element.is('.tooltipstered')) {
|
||||
log('already tooltipstered!?', this.id, this.name, id);
|
||||
return;
|
||||
}
|
||||
var instance = element.tooltipster({
|
||||
theme: ['tooltipster-noir'],
|
||||
side: tooltipSide || (
|
||||
input.is('button') ? 'top' :
|
||||
input.closest('.slider').get(0) || input.closest('.column').get(0) ? ['top','bottom'] :
|
||||
['right','top','bottom', 'left']
|
||||
),
|
||||
content: tip,
|
||||
updateAnimation: 'scale',
|
||||
assert(options.elements && options.tooltips, 'TooltipManager constructor expects .elements and .tooltips');
|
||||
Object.assign(this, {
|
||||
instances: [],
|
||||
options: options,
|
||||
config: Object.assign({}, TooltipManager.BASECONFIG, {
|
||||
trigger: !options.testmode ? 'hover' : 'click',
|
||||
distance: element.is('.slider.row') ? -20 : undefined,// element
|
||||
delay: [500, 1000],
|
||||
contentAsHTML: true,
|
||||
interactive: options.testmode,
|
||||
minWidth: options.viewport && options.viewport.min.width,
|
||||
maxWidth: options.viewport && options.viewport.max.width,
|
||||
}).tooltipster('instance');
|
||||
|
||||
instance.on('close', function(event) {
|
||||
if (options.keepopen === element) {
|
||||
debugPrint(event.type, 'canceling close keepopen === element', id);
|
||||
event.stop();
|
||||
options.keepopen = null;
|
||||
}
|
||||
});
|
||||
instance.on('before', function(event) {
|
||||
debugPrint(event.type, 'before', event);
|
||||
!options.testmode && _self.closeAll();
|
||||
!options.enabled && event.stop();
|
||||
return;
|
||||
});
|
||||
parent.find(':focusable, input, [tabindex], button, .control')
|
||||
.add(parent)
|
||||
.add(input.closest(':focusable, input, [tabindex]'))
|
||||
.on({
|
||||
click: function(evt) {
|
||||
if (input.is('button')) {
|
||||
return setTimeout(instance.close.bind(instance,null),50);
|
||||
}
|
||||
options.keepopen = element; 0&&instance.open();
|
||||
},
|
||||
focus: instance.open.bind(instance, null),
|
||||
blur: function(evt) {
|
||||
instance.close(); _self.openFocusedTooltip();
|
||||
},
|
||||
});
|
||||
});
|
||||
Object.assign(this, {
|
||||
openFocusedTooltip: function() {
|
||||
if (!this.options.enabled) {
|
||||
return;
|
||||
}
|
||||
setTimeout(function() {
|
||||
if (!document.activeElement || document.activeElement === document.body ||
|
||||
!$(document.activeElement).closest('section')) {
|
||||
return;
|
||||
}
|
||||
var tip = $([])
|
||||
.add($(document.activeElement))
|
||||
.add($(document.activeElement).find('.tooltipstered'))
|
||||
.add($(document.activeElement).closest('.tooltipstered'))
|
||||
.filter('.tooltipstered');
|
||||
if (tip.is('.tooltipstered')) {
|
||||
// log('opening focused tooltip', tip.length, tip[0].id);
|
||||
tip.tooltipster('open');
|
||||
}
|
||||
},1);
|
||||
},
|
||||
rapidClose: function(instance, reopen) {
|
||||
if (!instance.status().open) {
|
||||
return;
|
||||
}
|
||||
instance.elementTooltip() && $(instance.elementTooltip()).hide();
|
||||
instance.close(function() {
|
||||
reopen && instance.open();
|
||||
});
|
||||
},
|
||||
openAll: function() {
|
||||
$('.tooltipstered').tooltipster('open');
|
||||
},
|
||||
closeAll: function() {
|
||||
$.tooltipster.instances().forEach(function(instance) {
|
||||
this.rapidClose(instance);
|
||||
}.bind(this));
|
||||
},
|
||||
updateViewport: function(viewport) {
|
||||
var options = {
|
||||
minWidth: viewport.min.width,
|
||||
maxWidth: viewport.max.width,
|
||||
};
|
||||
$.tooltipster.setDefaults(options);
|
||||
log('updating tooltipster options', JSON.stringify(options));
|
||||
$.tooltipster.instances().forEach(function(instance) {
|
||||
instance.option('minWidth', options.minWidth);
|
||||
instance.option('maxWidth', options.maxWidth);
|
||||
this.rapidClose(instance, instance.status().open);
|
||||
}.bind(this));
|
||||
},
|
||||
enable: function() {
|
||||
this.options.enabled = true;
|
||||
if (this.options.testmode) {
|
||||
this.openAll();
|
||||
}
|
||||
},
|
||||
disable: function() {
|
||||
this.options.enabled = false;
|
||||
this.closeAll();
|
||||
},
|
||||
}),
|
||||
});
|
||||
options.enabled && this.initialize();
|
||||
}
|
||||
|
||||
TooltipManager.prototype = {
|
||||
constructor: TooltipManager,
|
||||
initialize: function() {
|
||||
var options = this.options,
|
||||
_config = this.config,
|
||||
_self = this,
|
||||
candidates = $(options.elements);
|
||||
|
||||
candidates.add($('button')).each(function() {
|
||||
var id = this.id,
|
||||
input = $(this),
|
||||
tip = options.tooltips[id] || options.tooltips[input.data('for')];
|
||||
|
||||
var alreadyTipped = input.is('.tooltipstered') || input.closest('.tooltipstered').get(0);
|
||||
if (alreadyTipped || !tip) {
|
||||
return !tip && _debugPrint('!tooltippable -- missing tooltip for ' + (id || input.data('for') || input.text()));
|
||||
}
|
||||
var config = Object.assign({ content: tip }, _config);
|
||||
|
||||
function mergeConfig() {
|
||||
var attr = $(this).attr('data-tooltipster'),
|
||||
object = $(this).data('tooltipster');
|
||||
typeof object === 'object' && Object.assign(config, object);
|
||||
attr && Object.assign(config, JSON.parse(attr));
|
||||
}
|
||||
try {
|
||||
input.parents(':data(tooltipster),[data-tooltipster]').each(mergeConfig);
|
||||
input.each(mergeConfig); // prioritize own settings
|
||||
} catch(e) {
|
||||
console.error('error extracting tooltipster data:' + [e, id]);
|
||||
}
|
||||
|
||||
var target = $(input.closest('.tooltip-target').get(0) ||
|
||||
(input.is('input') && input) || null);
|
||||
|
||||
assert(target && target[0] && tip);
|
||||
debugPrint('binding tooltip', config, target[0].nodeName, id || target[0]);
|
||||
var instance = target.tooltipster(config)
|
||||
.tooltipster('instance');
|
||||
|
||||
instance.on('close', function(event) {
|
||||
if (options.keepopen === target) {
|
||||
debugPrint(event.type, 'canceling close keepopen === target', id);
|
||||
event.stop();
|
||||
options.keepopen = null;
|
||||
}
|
||||
});
|
||||
instance.on('before', function(event) {
|
||||
debugPrint(event.type, 'before', event);
|
||||
!options.testmode && _self.closeAll();
|
||||
!options.enabled && event.stop();
|
||||
});
|
||||
target.find(':focusable, input, [tabindex], button, .control')
|
||||
.add(target).add(input)
|
||||
.add(input.closest(':focusable, input, [tabindex]'))
|
||||
.on({
|
||||
click: function(evt) {
|
||||
if (input.is('button')) {
|
||||
return setTimeout(instance.close.bind(instance,null),50);
|
||||
}
|
||||
options.keepopen = target;
|
||||
},
|
||||
focus: instance.open.bind(instance, null),
|
||||
blur: function(evt) {
|
||||
instance.close(); _self.openFocusedTooltip();
|
||||
},
|
||||
});
|
||||
_self.instances.push(instance);
|
||||
});
|
||||
return this.instances;
|
||||
},
|
||||
openFocusedTooltip: function() {
|
||||
if (!this.options.enabled) {
|
||||
return;
|
||||
}
|
||||
setTimeout(function() {
|
||||
if (!document.activeElement || document.activeElement === document.body ||
|
||||
!$(document.activeElement).closest('section')) {
|
||||
return;
|
||||
}
|
||||
var tip = $([])
|
||||
.add($(document.activeElement))
|
||||
.add($(document.activeElement).find('.tooltipstered'))
|
||||
.add($(document.activeElement).closest('.tooltipstered'))
|
||||
.filter('.tooltipstered');
|
||||
if (tip.is('.tooltipstered')) {
|
||||
// log('opening focused tooltip', tip.length, tip[0].id);
|
||||
tip.tooltipster('open');
|
||||
}
|
||||
}, 1);
|
||||
},
|
||||
rapidClose: function(instance, reopen) {
|
||||
if (!instance.status().open) {
|
||||
return;
|
||||
}
|
||||
instance.elementTooltip() && $(instance.elementTooltip()).hide();
|
||||
instance.close(function() {
|
||||
reopen && instance.open();
|
||||
});
|
||||
},
|
||||
openAll: function() {
|
||||
$('.tooltipstered').tooltipster('open');
|
||||
},
|
||||
closeAll: function() {
|
||||
$.tooltipster.instances().forEach(function(instance) {
|
||||
this.rapidClose(instance);
|
||||
}.bind(this));
|
||||
},
|
||||
updateViewport: function(viewport) {
|
||||
var options = {
|
||||
minWidth: viewport.min.width,
|
||||
maxWidth: viewport.max.width,
|
||||
};
|
||||
Object.assign(this.config, options);
|
||||
$.tooltipster.setDefaults(options);
|
||||
debugPrint('updating tooltipster options', JSON.stringify(options));
|
||||
$.tooltipster.instances().forEach(function(instance) {
|
||||
instance.option('minWidth', options.minWidth);
|
||||
instance.option('maxWidth', options.maxWidth);
|
||||
this.rapidClose(instance, instance.status().open);
|
||||
}.bind(this));
|
||||
},
|
||||
enable: function() {
|
||||
this.options.enabled = true;
|
||||
if (this.options.testmode) {
|
||||
this.openAll();
|
||||
}
|
||||
},
|
||||
disable: function() {
|
||||
this.options.enabled = false;
|
||||
this.closeAll();
|
||||
},
|
||||
};// prototype
|
||||
|
||||
return TooltipManager;
|
||||
})(this);
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
/* eslint-disable comma-dangle, no-empty */
|
||||
"use strict";
|
||||
|
||||
var VERSION = '0.0.0d',
|
||||
var VERSION = '0.0.1',
|
||||
NAMESPACE = 'app-camera-move',
|
||||
APP_HTML_URL = Script.resolvePath('app.html'),
|
||||
BUTTON_CONFIG = {
|
||||
|
@ -23,10 +23,7 @@ var VERSION = '0.0.0d',
|
|||
|
||||
var MINIMAL_CURSOR_SCALE = 0.5,
|
||||
FILENAME = Script.resolvePath(''),
|
||||
WANT_DEBUG = (
|
||||
false || (FILENAME.match(/[&#?]debug[=](\w+)/)||[])[1] ||
|
||||
Settings.getValue(NAMESPACE + '/debug')
|
||||
),
|
||||
WANT_DEBUG = Settings.getValue(NAMESPACE + '/debug', false)
|
||||
EPSILON = 1e-6;
|
||||
|
||||
function log() {
|
||||
|
@ -39,8 +36,8 @@ var require = Script.require,
|
|||
overlayDebugOutput = function(){};
|
||||
|
||||
if (WANT_DEBUG) {
|
||||
log('WANT_DEBUG is true; instrumenting debug rigging', WANT_DEBUG);
|
||||
_instrumentDebugValues();
|
||||
log('WANT_DEBUG is true; instrumenting debug support', WANT_DEBUG);
|
||||
_instrumentDebug();
|
||||
}
|
||||
|
||||
var _utils = require('./modules/_utils.js'),
|
||||
|
@ -58,6 +55,7 @@ var cameraControls, eventMapper, cameraConfig, applicationConfig;
|
|||
var DEFAULTS = {
|
||||
'namespace': NAMESPACE,
|
||||
'debug': WANT_DEBUG,
|
||||
'jitter-test': false,
|
||||
'camera-move-enabled': false,
|
||||
'thread-update-mode': movementUtils.CameraControls.SCRIPT_UPDATE,
|
||||
'fps': 90,
|
||||
|
@ -83,30 +81,21 @@ var DEFAULTS = {
|
|||
|
||||
'ui-enable-tooltips': true,
|
||||
'ui-show-advanced-options': false,
|
||||
|
||||
// 'toggle-key': DEFAULT_TOGGLE_KEY,
|
||||
};
|
||||
|
||||
// map setting names to/from corresponding Menu and API properties
|
||||
var APPLICATION_SETTINGS = {
|
||||
'Avatar/pitchSpeed': 'pitchSpeed' in MyAvatar && {
|
||||
object: [ MyAvatar, 'pitchSpeed' ]
|
||||
},
|
||||
'Avatar/yawSpeed': 'yawSpeed' in MyAvatar && {
|
||||
object: [ MyAvatar, 'yawSpeed' ]
|
||||
},
|
||||
'Avatar/Enable Avatar Collisions': {
|
||||
menu: 'Avatar > Enable Avatar Collisions',
|
||||
object: [ MyAvatar, 'collisionsEnabled' ],
|
||||
},
|
||||
'Avatar/Draw Mesh': {
|
||||
menu: 'Developer > Draw Mesh',
|
||||
// object: [ MyAvatar, 'shouldRenderLocally' ], // shouldRenderLocally seems to be broken...
|
||||
object: [ MyAvatar, 'getEnableMeshVisible', 'setEnableMeshVisible' ],
|
||||
},
|
||||
'Avatar/useSnapTurn': {
|
||||
object: [ MyAvatar, 'getSnapTurn', 'setSnapTurn' ],
|
||||
},
|
||||
'Avatar/Show My Eye Vectors': { menu: 'Developer > Show My Eye Vectors' },
|
||||
'Avatar/Show Other Eye Vectors': { menu: 'Developer > Show Other Eye Vectors' },
|
||||
'Avatar/useSnapTurn': { object: [ MyAvatar, 'getSnapTurn', 'setSnapTurn' ] },
|
||||
'Avatar/lookAtSnappingEnabled': 'lookAtSnappingEnabled' in MyAvatar && {
|
||||
menu: 'Developer > Enable LookAt Snapping',
|
||||
object: [ MyAvatar, 'lookAtSnappingEnabled' ]
|
||||
|
@ -119,17 +108,6 @@ var APPLICATION_SETTINGS = {
|
|||
cameraControls.setEnabled(!!nv);
|
||||
},
|
||||
},
|
||||
// 'toggle-key': {
|
||||
// get: function() { try {
|
||||
// return JSON.parse(cameraConfig.getValue('toggle-key'));
|
||||
// } catch (e) {
|
||||
// return DEFAULT_TOGGLE_KEY;
|
||||
// } },
|
||||
// set: function(nv) {
|
||||
// assert(typeof nv === 'object', 'new toggle-key is not an object: ' + nv);
|
||||
// cameraConfig.setValue('toggle-key', JSON.parse(JSON.stringify(nv)));
|
||||
// },
|
||||
// },
|
||||
};
|
||||
|
||||
var DEBUG_INFO = {
|
||||
|
@ -143,7 +121,6 @@ var DEBUG_INFO = {
|
|||
supportsPitchSpeed: 'pitchSpeed' in MyAvatar,
|
||||
supportsYawSpeed: 'yawSpeed' in MyAvatar,
|
||||
supportsLookAtSnappingEnabled: 'lookAtSnappingEnabled' in MyAvatar,
|
||||
supportsDensity: 'density' in MyAvatar,
|
||||
},
|
||||
Reticle: {
|
||||
supportsScale: 'scale' in Reticle,
|
||||
|
@ -160,7 +137,7 @@ var globalState = {
|
|||
},
|
||||
},
|
||||
|
||||
// batch updates to MyAvatar/Camera properties (submitting together seems to help reduce timeslice jitter)
|
||||
// batch updates to MyAvatar/Camera properties (submitting together seems to help reduce jitter)
|
||||
pendingChanges: _utils.DeferredUpdater.createGroup({
|
||||
Camera: Camera,
|
||||
MyAvatar: MyAvatar,
|
||||
|
@ -169,9 +146,7 @@ var globalState = {
|
|||
// current input controls' effective velocities
|
||||
currentVelocities: new movementUtils.VelocityTracker({
|
||||
translation: Vec3.ZERO,
|
||||
step_translation: Vec3.ZERO, // eslint-disable-line camelcase
|
||||
rotation: Vec3.ZERO,
|
||||
step_rotation: Vec3.ZERO, // eslint-disable-line camelcase
|
||||
zoom: Vec3.ZERO,
|
||||
}),
|
||||
};
|
||||
|
@ -187,8 +162,7 @@ function main() {
|
|||
button = null;
|
||||
});
|
||||
|
||||
// track both runtime state (applicationConfig) and settings state (cameraConfig)
|
||||
// (this is necessary because Interface does not yet consistently keep config Menus, APIs and Settings in sync)
|
||||
// track runtime state (applicationConfig) and Settings state (cameraConfig)
|
||||
applicationConfig = new configUtils.ApplicationConfig({
|
||||
namespace: DEFAULTS.namespace,
|
||||
config: APPLICATION_SETTINGS,
|
||||
|
@ -204,12 +178,12 @@ function main() {
|
|||
toggleKey = JSON.parse(cameraConfig.getValue('toggle-key'));
|
||||
} catch (e) {}
|
||||
}
|
||||
// set up a monitor to observe configuration changes between the two sources
|
||||
// monitor configuration changes / keep tablet app up-to-date
|
||||
var MONITOR_INTERVAL_MS = 1000;
|
||||
_startConfigationMonitor(applicationConfig, cameraConfig, MONITOR_INTERVAL_MS);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// set up the tablet webview app
|
||||
// set up the tablet app
|
||||
log('APP_HTML_URL', APP_HTML_URL);
|
||||
var settingsApp = new CustomSettingsApp({
|
||||
namespace: cameraConfig.namespace,
|
||||
|
@ -219,19 +193,20 @@ function main() {
|
|||
tablet: tablet,
|
||||
extraParams: Object.assign({
|
||||
toggleKey: toggleKey,
|
||||
}, _utils.getSystemMetadata(), DEBUG_INFO),
|
||||
}, getSystemMetadata(), DEBUG_INFO),
|
||||
debug: WANT_DEBUG > 1,
|
||||
});
|
||||
Script.scriptEnding.connect(settingsApp, 'cleanup');
|
||||
settingsApp.valueUpdated.connect(function(key, value, oldValue, origin) {
|
||||
log('[settingsApp.valueUpdated @ ' + origin + ']', key + ' = ' + JSON.stringify(value) + ' (was: ' + JSON.stringify(oldValue) + ')');
|
||||
log('settingsApp.valueUpdated: '+ key + ' = ' + JSON.stringify(value) + ' (was: ' + JSON.stringify(oldValue) + ')');
|
||||
if (/tablet/i.test(origin)) {
|
||||
log('cameraConfig applying immediate setting', key, value);
|
||||
// apply relevant settings immediately when changed from the app UI
|
||||
// apply relevant settings immediately if changed from the tablet UI
|
||||
log('applying immediate setting', key, value);
|
||||
applicationConfig.applyValue(key, value, origin);
|
||||
}
|
||||
});
|
||||
|
||||
// process custom eventbridge messages
|
||||
settingsApp.onUnhandledMessage = function(msg) {
|
||||
switch (msg.method) {
|
||||
case 'window.close': {
|
||||
|
@ -250,9 +225,6 @@ function main() {
|
|||
}, 500);
|
||||
} break;
|
||||
case 'reset': {
|
||||
// if (!Window.confirm('Reset all camera move settings to system defaults?')) {
|
||||
// return;
|
||||
// }
|
||||
var resetValues = {};
|
||||
Object.keys(DEFAULTS).reduce(function(out, key) {
|
||||
var resolved = cameraConfig.resolve(key);
|
||||
|
@ -264,16 +236,13 @@ function main() {
|
|||
out[resolved] = resolved in out ? out[resolved] : applicationConfig.getValue(key);
|
||||
return out;
|
||||
}, resetValues);
|
||||
log('resetValues', JSON.stringify(resetValues, 0, 2));
|
||||
log('restting to system defaults:', JSON.stringify(resetValues, 0, 2));
|
||||
for (var p in resetValues) {
|
||||
var value = resetValues[p];
|
||||
applicationConfig.applyValue(p, value, 'reset');
|
||||
cameraConfig.setValue(p, value);
|
||||
}
|
||||
} break;
|
||||
case 'overlayWebWindow': {
|
||||
_utils._overlayWebWindow(msg.options);
|
||||
} break;
|
||||
default: {
|
||||
log('onUnhandledMessage', JSON.stringify(msg,0,2));
|
||||
} break;
|
||||
|
@ -281,11 +250,10 @@ function main() {
|
|||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// set up the keyboard/mouse/controller/meta input state manager
|
||||
// set up the keyboard/mouse/controller input state manager
|
||||
eventMapper = new movementUtils.MovementEventMapper({
|
||||
namespace: DEFAULTS.namespace,
|
||||
mouseSmooth: cameraConfig.getValue('enable-mouse-smooth'),
|
||||
xexcludeNames: [ 'Keyboard.C', 'Keyboard.E', 'Actions.TranslateY' ],
|
||||
mouseMultiplier: cameraConfig.getValue('mouse-multiplier'),
|
||||
keyboardMultiplier: cameraConfig.getValue('keyboard-multiplier'),
|
||||
eventFilter: function eventFilter(from, event, defaultFilter) {
|
||||
|
@ -293,6 +261,7 @@ function main() {
|
|||
driveKeyName = event.driveKeyName;
|
||||
if (!result || !driveKeyName) {
|
||||
if (from === 'Keyboard.RightMouseButton') {
|
||||
// let the app know when the user is mouse looking
|
||||
settingsApp.syncValue('Keyboard.RightMouseButton', event.actionValue, 'eventFilter');
|
||||
}
|
||||
return 0;
|
||||
|
@ -302,14 +271,14 @@ function main() {
|
|||
}
|
||||
if (from === 'Actions.Pitch') {
|
||||
result *= cameraConfig.getFloat('rotation-x-speed');
|
||||
}
|
||||
if (from === 'Actions.Yaw') {
|
||||
} else if (from === 'Actions.Yaw') {
|
||||
result *= cameraConfig.getFloat('rotation-y-speed');
|
||||
}
|
||||
return result;
|
||||
},
|
||||
});
|
||||
Script.scriptEnding.connect(eventMapper, 'disable');
|
||||
// keep track of these changes live so the controller mapping can be kept in sync
|
||||
applicationConfig.register({
|
||||
'enable-mouse-smooth': { object: [ eventMapper.options, 'mouseSmooth' ] },
|
||||
'keyboard-multiplier': { object: [ eventMapper.options, 'keyboardMultiplier' ] },
|
||||
|
@ -347,33 +316,41 @@ function main() {
|
|||
Script.scriptEnding.connect(spacebar, 'disconnect');
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// set up ESC for reset drive key states
|
||||
// set up ESC for resetting all drive key states
|
||||
Script.scriptEnding.connect(new _utils.KeyListener({
|
||||
text: 'ESC',
|
||||
onKeyPressEvent: function(event) {
|
||||
if (cameraControls.enabled) {
|
||||
log('ESC pressed -- resetting drive keys values:', JSON.stringify({
|
||||
log('ESC pressed -- resetting drive keys:', JSON.stringify({
|
||||
virtualDriveKeys: eventMapper.states,
|
||||
movementState: eventMapper.getState(),
|
||||
}, 0, 2));
|
||||
eventMapper.states.reset();
|
||||
MyAvatar.velocity = Vec3.ZERO;
|
||||
MyAvatar.angularVelocity = Vec3.ZERO;
|
||||
}
|
||||
},
|
||||
}), 'disconnect');
|
||||
|
||||
// set up the tablet button to toggle the app UI display
|
||||
// set up the tablet button to toggle the UI display
|
||||
button.clicked.connect(settingsApp, function(enable) {
|
||||
Object.assign(this.extraParams, _utils.getSystemMetadata());
|
||||
Object.assign(this.extraParams, getSystemMetadata());
|
||||
button.editProperties({ text: '(opening)' + BUTTON_CONFIG.text, isActive: true });
|
||||
this.toggle(enable);
|
||||
});
|
||||
|
||||
settingsApp.isActiveChanged.connect(function(isActive) {
|
||||
updateButtonText();
|
||||
if (Overlays.getOverlayType(HMD.tabletScreenID)) {
|
||||
var fromMode = Overlays.getProperty(HMD.tabletScreenID, 'inputMode'),
|
||||
inputMode = isActive ? "Mouse" : "Touch";
|
||||
log('switching HMD.tabletScreenID from inputMode', fromMode, 'to', inputMode);
|
||||
Overlays.editOverlay(HMD.tabletScreenID, { inputMode: inputMode });
|
||||
}
|
||||
});
|
||||
|
||||
cameraControls.modeChanged.connect(onCameraModeChanged);
|
||||
|
||||
var fpsTimeout = 0;
|
||||
function updateButtonText() {
|
||||
var lines = [
|
||||
settingsApp.isActive ? '(app open)' : '',
|
||||
|
@ -382,6 +359,7 @@ function main() {
|
|||
button && button.editProperties({ text: lines.join('\n') });
|
||||
}
|
||||
|
||||
var fpsTimeout = 0;
|
||||
cameraControls.enabledChanged.connect(function(enabled) {
|
||||
log('enabledChanged', enabled);
|
||||
button && button.editProperties({ isActive: enabled });
|
||||
|
@ -396,10 +374,14 @@ function main() {
|
|||
eventMapper.disable();
|
||||
avatarUpdater._resetMyAvatarMotor({ MyAvatar: MyAvatar });
|
||||
updateButtonText();
|
||||
if (settingsApp.isActive) {
|
||||
settingsApp.syncValue('Keyboard.RightMouseButton', false, 'cameraControls.disabled');
|
||||
}
|
||||
}
|
||||
overlayDebugOutput.overlayID && Overlays.editOverlay(overlayDebugOutput.overlayID, { visible: enabled });
|
||||
});
|
||||
|
||||
// when certain settings change we need to reset the drive systems
|
||||
var resetIfChanged = [
|
||||
'minimal-cursor', 'drive-mode', 'fps', 'thread-update-mode',
|
||||
'mouse-multiplier', 'keyboard-multiplier',
|
||||
|
@ -408,28 +390,14 @@ function main() {
|
|||
|
||||
cameraConfig.valueUpdated.connect(function(key, value, oldValue, origin) {
|
||||
var triggerReset = !!~resetIfChanged.indexOf(key);
|
||||
log('[cameraConfig.valueUpdated @ ' + origin + ']',
|
||||
key + ' = ' + JSON.stringify(value), '(was:' + JSON.stringify(oldValue) + ')',
|
||||
log('cameraConfig.valueUpdated: ' + key + ' = ' + JSON.stringify(value), '(was:' + JSON.stringify(oldValue) + ')',
|
||||
'triggerReset: ' + triggerReset);
|
||||
|
||||
if (/tablet/i.test(origin)) {
|
||||
log('cameraConfig applying immediate setting', key, value);
|
||||
// apply relevant settings immediately when changed from the app UI
|
||||
log('applying immediate setting', key, value);
|
||||
applicationConfig.applyValue(key, value, origin);
|
||||
log(JSON.stringify(cameraConfig.getValue(key)));
|
||||
}
|
||||
0&&debugPrint('//cameraConfig.valueUpdated', JSON.stringify({
|
||||
key: key,
|
||||
cameraConfig: cameraConfig.getValue(key),
|
||||
applicationConfig: applicationConfig.getValue(key),
|
||||
value: value,
|
||||
triggerReset: triggerReset,
|
||||
},0,2));
|
||||
|
||||
if (triggerReset) {
|
||||
log('KEYBOARD multiplier', eventMapper.options.keyboardMultiplier);
|
||||
cameraControls.reset();
|
||||
}
|
||||
triggerReset && cameraControls.reset();
|
||||
});
|
||||
|
||||
if (cameraConfig.getValue('camera-move-enabled')) {
|
||||
|
@ -440,7 +408,7 @@ function main() {
|
|||
} // main()
|
||||
|
||||
function onCameraControlsEnabled() {
|
||||
log('onCameraControlsEnabled!!!! ----------------------------------------------');
|
||||
log('onCameraControlsEnabled');
|
||||
globalState.previousValues.reset();
|
||||
globalState.currentVelocities.reset();
|
||||
globalState.pendingChanges.reset();
|
||||
|
@ -450,11 +418,10 @@ function onCameraControlsEnabled() {
|
|||
}
|
||||
log('cameraConfig', JSON.stringify({
|
||||
cameraConfig: getCameraMovementSettings(),
|
||||
//DEFAULTS: DEFAULTS
|
||||
}));
|
||||
}
|
||||
|
||||
// reset values based on the selected Camera.mode (to help keep the visual display/orientation more reasonable)
|
||||
// reset orientation-related values when the Camera.mode changes
|
||||
function onCameraModeChanged(mode, oldMode) {
|
||||
globalState.pendingChanges.reset();
|
||||
globalState.previousValues.reset();
|
||||
|
@ -486,6 +453,7 @@ function getCameraMovementSettings() {
|
|||
return {
|
||||
epsilon: EPSILON,
|
||||
debug: cameraConfig.getValue('debug'),
|
||||
jitterTest: cameraConfig.getValue('jitter-test'),
|
||||
driveMode: cameraConfig.getValue('drive-mode'),
|
||||
threadMode: cameraConfig.getValue('thread-update-mode'),
|
||||
fps: cameraConfig.getValue('fps'),
|
||||
|
@ -504,7 +472,7 @@ function getCameraMovementSettings() {
|
|||
zoom: _getEasingGroup(cameraConfig, 'zoom'),
|
||||
};
|
||||
|
||||
// extract an easing group (translation, rotation, or zoom) from cameraConfig
|
||||
// extract a single easing group (translation, rotation, or zoom) from cameraConfig
|
||||
function _getEasingGroup(cameraConfig, group) {
|
||||
var multiplier = 1.0;
|
||||
if (group === 'zoom') {
|
||||
|
@ -525,11 +493,8 @@ function getCameraMovementSettings() {
|
|||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// monitor and sync Application state -> Settings values
|
||||
function _startConfigationMonitor(applicationConfig, cameraConfig, interval) {
|
||||
// monitor and sync Application state -> Settings values
|
||||
return Script.setInterval(function monitor() {
|
||||
var settingNames = Object.keys(applicationConfig.config);
|
||||
settingNames.forEach(function(key) {
|
||||
|
@ -546,6 +511,23 @@ function _startConfigationMonitor(applicationConfig, cameraConfig, interval) {
|
|||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// DEBUG overlay support (enable by setting app-camera-move/debug = true in settings
|
||||
// ----------------------------------------------------------------------------
|
||||
function _instrumentDebug() {
|
||||
debugPrint = log;
|
||||
var cacheBuster = '?' + new Date().getTime().toString(36);
|
||||
require = Script.require(Script.resolvePath('./modules/_utils.js') + cacheBuster).makeDebugRequire(Script.resolvePath('.'));
|
||||
APP_HTML_URL += cacheBuster;
|
||||
overlayDebugOutput = _createOverlayDebugOutput({
|
||||
lineHeight: 12,
|
||||
font: { size: 12 },
|
||||
width: 250, height: 800 });
|
||||
// auto-disable camera move mode when debugging
|
||||
Script.scriptEnding.connect(function() {
|
||||
cameraConfig && cameraConfig.setValue('camera-move-enabled', false);
|
||||
});
|
||||
}
|
||||
|
||||
function _fixedPrecisionStringifiyFilter(key, value, object) {
|
||||
if (typeof value === 'object' && value && 'w' in value) {
|
||||
return Quat.safeEulerAngles(value);
|
||||
|
@ -554,6 +536,7 @@ function _fixedPrecisionStringifiyFilter(key, value, object) {
|
|||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function _createOverlayDebugOutput(options) {
|
||||
options = require('./modules/_utils.js').assign({
|
||||
x: 0, y: 0, width: 500, height: 800, visible: false
|
||||
|
@ -578,21 +561,18 @@ function _createOverlayDebugOutput(options) {
|
|||
}
|
||||
}
|
||||
function onMessageReceived(channel, message, ssend, local) {
|
||||
if (!local || channel !== _debugChannel) {
|
||||
return;
|
||||
if (local && channel === _debugChannel) {
|
||||
overlayDebugOutput(JSON.parse(message));
|
||||
}
|
||||
overlayDebugOutput(JSON.parse(message));
|
||||
}
|
||||
|
||||
return overlayDebugOutput;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
_patchCameraModeSetting();
|
||||
function _patchCameraModeSetting() {
|
||||
// FIXME: looks like the Camera API suffered a regression where setting Camera.mode = 'first person' or 'third person'
|
||||
// no longer works; the only reliable way to set it now seems to be jury-rigging the Menu items...
|
||||
// FIXME: looks like the Camera API suffered a regression where Camera.mode = 'first person' or 'third person'
|
||||
// no longer works from the API; setting via Menu items still seems to work though.
|
||||
Camera.$setModeString = Camera.$setModeString || function(mode) {
|
||||
// 'independent' => "Independent Mode", 'first person' => 'First Person', etc.
|
||||
var cameraMenuItem = (mode+'')
|
||||
|
@ -605,50 +585,46 @@ function _patchCameraModeSetting() {
|
|||
Menu.setIsOptionChecked(cameraMenuItem, true);
|
||||
};
|
||||
}
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
function _instrumentDebugValues() {
|
||||
debugPrint = log;
|
||||
var cacheBuster = '?' + new Date().getTime().toString(36);
|
||||
require = Script.require(Script.resolvePath('./modules/_utils.js') + cacheBuster).makeDebugRequire(Script.resolvePath('.'));
|
||||
APP_HTML_URL += cacheBuster;
|
||||
overlayDebugOutput = _createOverlayDebugOutput({
|
||||
lineHeight: 12,
|
||||
font: { size: 12 },
|
||||
width: 250, height: 800 });
|
||||
// auto-disable camera move mode when debugging
|
||||
Script.scriptEnding.connect(function() {
|
||||
cameraConfig && cameraConfig.setValue('camera-move-enabled', false);
|
||||
});
|
||||
function getSystemMetadata() {
|
||||
var tablet = Tablet.getTablet('com.highfidelity.interface.tablet.system');
|
||||
return {
|
||||
mode: {
|
||||
hmd: HMD.active,
|
||||
desktop: !HMD.active,
|
||||
toolbar: Uuid.isNull(HMD.tabletID),
|
||||
tablet: !Uuid.isNull(HMD.tabletID),
|
||||
},
|
||||
tablet: {
|
||||
toolbarMode: tablet.toolbarMode,
|
||||
desktopScale: Settings.getValue('desktopTabletScale'),
|
||||
hmdScale: Settings.getValue('hmdTabletScale'),
|
||||
},
|
||||
window: {
|
||||
width: Window.innerWidth,
|
||||
height: Window.innerHeight,
|
||||
},
|
||||
desktop: {
|
||||
width: Desktop.width,
|
||||
height: Desktop.height,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Show fatal (unhandled) exceptions in a BSOD popup
|
||||
Script.unhandledException.connect(function onUnhandledException(error) {
|
||||
log('UNHANDLED EXCEPTION!!', error, error && error.stack);
|
||||
try {
|
||||
cameraControls.disable();
|
||||
} catch (e) {}
|
||||
Script.unhandledException.disconnect(onUnhandledException);
|
||||
if (WANT_DEBUG) {
|
||||
// show blue screen of death with the error details
|
||||
new _utils.BSOD({
|
||||
error: error,
|
||||
buttons: [ 'Abort', 'Retry', 'Fail' ],
|
||||
debugInfo: DEBUG_INFO,
|
||||
}, function(error, button) {
|
||||
log('BSOD.result', error, button);
|
||||
if (button === 'Abort') {
|
||||
Script.stop();
|
||||
} else if (button === 'Retry') {
|
||||
_utils.reloadClientScript(FILENAME);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// use a simple alert to display just the error message
|
||||
Window.alert('app-camera-move error: ' + error.message);
|
||||
Script.stop();
|
||||
}
|
||||
});
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
main();
|
||||
|
||||
if (typeof module !== 'object') {
|
||||
// if uncaught exceptions occur, show the first in an alert with option to stop the script
|
||||
Script.unhandledException.connect(function onUnhandledException(error) {
|
||||
Script.unhandledException.disconnect(onUnhandledException);
|
||||
log('UNHANDLED EXCEPTION', error, error && error.stack);
|
||||
try {
|
||||
cameraControls.disable();
|
||||
} catch (e) {}
|
||||
// show the error message and first two stack entries
|
||||
var trace = _utils.normalizeStackTrace(error);
|
||||
var message = [ error ].concat(trace.split('\n').slice(0,2)).concat('stop script?').join('\n');
|
||||
Window.confirm('app-camera-move error: ' + message.substr(0,256)) && Script.stop();
|
||||
});
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -8,27 +8,13 @@
|
|||
var viewportUpdated, bridgedSettings, jquerySettings, tooltipManager, lessManager;
|
||||
|
||||
function setupUI() {
|
||||
viewportUpdated.connect(lessManager, 'onViewportUpdated');
|
||||
|
||||
tooltipManager = new TooltipManager({
|
||||
enabled: false,
|
||||
testmode: PARAMS.tooltiptest,
|
||||
viewport: PARAMS.viewport,
|
||||
tooltips: $('#tooltips'),
|
||||
elements: $($.unique($('input.setting, button').map(function() {
|
||||
var input = $(this),
|
||||
button = input.is('button') && input,
|
||||
row = input.closest('.row').is('.slider') && input.closest('.row'),
|
||||
label = input.is('[type=checkbox],[type=radio]') && input.parent().find('label');
|
||||
|
||||
return (button || label || row || input).get(0);
|
||||
}))),
|
||||
});
|
||||
viewportUpdated.connect(tooltipManager, 'updateViewport');
|
||||
|
||||
$(window).trigger('resize'); // populate viewport
|
||||
|
||||
$('#debug-menu button, footer button').hifiButton();
|
||||
$('#debug-menu button, footer button')
|
||||
.hifiButton({
|
||||
create: function() {
|
||||
$(this).addClass('tooltip-target')
|
||||
.data('tooltipster', { side: ['top','bottom'] });
|
||||
}
|
||||
});
|
||||
|
||||
var $json = SettingsJSON;
|
||||
|
||||
|
@ -46,7 +32,7 @@ function setupUI() {
|
|||
bridgedSettings.sendEvent({ method: 'resetSensors' });
|
||||
},
|
||||
'reset-to-defaults': function() {
|
||||
tooltipManager.closeAll();
|
||||
tooltipManager && tooltipManager.closeAll();
|
||||
document.activeElement && document.activeElement.blur();
|
||||
document.body.focus();
|
||||
setTimeout(function() {
|
||||
|
@ -79,25 +65,28 @@ function setupUI() {
|
|||
'appVersion': function(evt) {
|
||||
evt.shiftKey && $json.showSettings(jquerySettings);
|
||||
},
|
||||
'errors': function() {
|
||||
$(this).find('.output').text('').end().hide();
|
||||
'errors': function(evt) {
|
||||
$(evt.target).is('button') && $(this).find('.output').text('').end().hide();
|
||||
},
|
||||
};
|
||||
buttonHandlers['button-toggle-advanced-options'] =
|
||||
buttonHandlers['toggle-advanced-options'];
|
||||
Object.keys(window.buttonHandlers).forEach(function(p) {
|
||||
$('#' + p).on('click', window.buttonHandlers[p]);
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// trim whitespace in labels
|
||||
$('label').contents().filter(function() {
|
||||
if (this.nodeType !== window.Node.TEXT_NODE) {
|
||||
return false;
|
||||
}
|
||||
this.textContent = this.textContent.trim();
|
||||
return !this.textContent.length;
|
||||
}).remove();
|
||||
|
||||
$( "input[type=number],input[type=spinner],input[step]" ).each(function() {
|
||||
// set up default numeric precisions
|
||||
var step = $(this).prop('step') || 0.01;
|
||||
var precision = ((1 / step).toString().length - 1);
|
||||
$(this).prop('step', step || Math.pow(10, -precision));
|
||||
});
|
||||
|
||||
var settingsNodes = $('[type=radio-group],input:not([type=radio]),button').filter('[id],[data-for],[for]');
|
||||
settingsNodes.filter(':not(button)').each(function() {
|
||||
var settingsNodes = $('fieldset,button[data-for],input:not([type=radio])');
|
||||
settingsNodes.each(function() {
|
||||
// set up the bidirectional mapping between DOM and Settings
|
||||
jquerySettings.registerNode(this);
|
||||
});
|
||||
|
@ -106,58 +95,81 @@ function setupUI() {
|
|||
disabled: true,
|
||||
create: function() {
|
||||
var input = $(this),
|
||||
key = assert(jquerySettings.getKey(this.id));
|
||||
key = assert(jquerySettings.getKey(input.data('for')));
|
||||
|
||||
var options = input.hifiSpinner('instance').options;
|
||||
options.min = options.min || 0.0;
|
||||
|
||||
bridgedSettings.getValueAsync(key, function(err, result) {
|
||||
options.autoenable !== false && input.hifiSpinner('enable');
|
||||
input.filter(':not([data-autoenable=false])').hifiSpinner('enable');
|
||||
jquerySettings.setValue(key, result);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
$( ".rows > label" ).each(function() {
|
||||
var label = $(this),
|
||||
input = label.find('input'),
|
||||
type = input.data('type') || input.attr('type');
|
||||
label.wrap('<div>').parent().addClass(['hifi-'+type, type, 'row'].join(' '))
|
||||
.on('focus.row, click.row, hover.row', function() {
|
||||
$(this).find('.tooltipstered').tooltipster('open');
|
||||
});
|
||||
});
|
||||
|
||||
debugPrint('initializing hifiSpinners');
|
||||
// numeric settings
|
||||
$( ".number.row input.setting" )
|
||||
$( ".number.row" )
|
||||
.find( "input[data-type=number]" )
|
||||
.addClass('setting')
|
||||
.hifiSpinner(spinnerOptions);
|
||||
|
||||
// radio groups settings
|
||||
$( ".radio.row" )
|
||||
$( ".radio.rows" )
|
||||
.find('label').addClass('tooltip-target').end()
|
||||
.addClass('setting')
|
||||
.hifiRadioGroup({
|
||||
direction: 'horizontal',
|
||||
direction: 'vertical',
|
||||
disabled: true,
|
||||
create: function() {
|
||||
assert(this !== window);
|
||||
var group = $(this), id = this.id;
|
||||
var key = assert(jquerySettings.getKey(id));
|
||||
var key = assert(jquerySettings.getKey(group.data('for')));
|
||||
|
||||
bridgedSettings.getValueAsync(key, function(err, result) {
|
||||
debugPrint('> GOT RADIO', key, id, result);
|
||||
group.hifiRadioGroup('enable');
|
||||
group.filter(':not([data-autoenable=false])').hifiRadioGroup('enable');
|
||||
jquerySettings.setValue(key, result);
|
||||
group.change();
|
||||
});
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
// checkbox settings
|
||||
$( ".bool.row input.setting" )
|
||||
.hifiCheckbox({ disabled: true })
|
||||
.each(function() {
|
||||
var key = assert(jquerySettings.getKey(this.id)),
|
||||
input = $(this);
|
||||
|
||||
bridgedSettings.getValueAsync(key, function(err, result) {
|
||||
input.hifiCheckbox('enable');
|
||||
jquerySettings.setValue(key, result);
|
||||
});
|
||||
$( "input[type=checkbox]" )
|
||||
.addClass('setting')
|
||||
.hifiCheckbox({
|
||||
disabled: true,
|
||||
create: function() {
|
||||
var key = assert(jquerySettings.getKey(this.id)),
|
||||
input = $(this);
|
||||
input.closest('label').addClass('tooltip-target');
|
||||
bridgedSettings.getValueAsync(key, function(err, result) {
|
||||
input.filter(':not([data-autoenable=false])').hifiCheckbox('enable');
|
||||
jquerySettings.setValue(key, result);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// slider + numeric settings
|
||||
$( ".slider.row .control" ).each(function(ent) {
|
||||
var element = $(this),
|
||||
phor = element.attr('data-for'),
|
||||
input = $('input#' + phor),
|
||||
id = input .prop('id'),
|
||||
// use the whole row as a tooltip target
|
||||
$( ".slider.row" ).addClass('tooltip-target').data('tooltipster', {
|
||||
distance: -20,
|
||||
side: ['top', 'bottom'],
|
||||
}).each(function(ent) {
|
||||
var element = $(this).find( ".control" ),
|
||||
input = $(this).find('input'),
|
||||
id = input.prop('id'),
|
||||
key = assert(jquerySettings.getKey(id));
|
||||
|
||||
var commonOptions = {
|
||||
|
@ -165,6 +177,7 @@ function setupUI() {
|
|||
min: parseFloat(input.prop('min') || 0),
|
||||
max: parseFloat(input.prop('max') || 10),
|
||||
step: parseFloat(input.prop('step') || 0.01),
|
||||
autoenable: input.data('autoenable') !== 'false',
|
||||
};
|
||||
debugPrint('commonOptions', commonOptions);
|
||||
|
||||
|
@ -176,41 +189,29 @@ function setupUI() {
|
|||
value: 0.0
|
||||
}, commonOptions)).hifiSlider('instance');
|
||||
|
||||
debugPrint('initializing hifiSlider->hifiSpinner');
|
||||
// setup chrome up/down arrow steps and propagate input field -> slider
|
||||
var spinner = input.on('change', function() {
|
||||
var value = spinner.value();
|
||||
if (isFinite(value) && slider.value() !== value) {
|
||||
slider.value(value);
|
||||
}
|
||||
}).hifiSpinner(
|
||||
Object.assign({}, commonOptions, { max: Infinity })
|
||||
).hifiSpinner('instance');
|
||||
}).addClass('setting')
|
||||
.hifiSpinner(
|
||||
Object.assign({}, commonOptions, { max: 1e4 })
|
||||
).hifiSpinner('instance');
|
||||
|
||||
bridgedSettings.getValueAsync(key, function(err, result) {
|
||||
slider.enable();
|
||||
spinner.enable();
|
||||
slider.options.autoenable !== false && slider.enable();
|
||||
spinner.options.autoenable !== false && spinner.enable();
|
||||
spinner.value(result);
|
||||
});
|
||||
});
|
||||
|
||||
// other numeric settings
|
||||
$('input[data-type=number]:not(:ui-hifiSpinner)')
|
||||
.hifiSpinner(Object.assign({
|
||||
autoenable: false,
|
||||
}, spinnerOptions));
|
||||
|
||||
// set up DOM MutationObservers
|
||||
settingsNodes.each(function tcobo() {
|
||||
if (this.dataset.hifiType === 'hifiButton') {
|
||||
return;
|
||||
}
|
||||
var id = assert(this.dataset['for'] || this.id, 'could not id for node: ' + this.outerHTML);
|
||||
assert(!tcobo[id]); // detect dupes
|
||||
tcobo[id] = true;
|
||||
debugPrint('OBSERVING NODE', id, this.id || this.getAttribute('for'));
|
||||
jquerySettings.observeNode(this);
|
||||
$('#fps').hifiSpinner(spinnerOptions).closest('.row').css('pointer-events', 'all').on('click.subinput', function(evt) {
|
||||
jquerySettings.setValue('thread-update-mode', 'requestAnimationFrame');
|
||||
evt.target.focus();
|
||||
});
|
||||
|
||||
// detect invalid numbers entered into spinner fields
|
||||
$(':ui-hifiSpinner').on('change.validation', function(evt) {
|
||||
var spinner = $(this).hifiSpinner('instance');
|
||||
|
@ -218,8 +219,9 @@ function setupUI() {
|
|||
});
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// allow tabbing between checkboxes
|
||||
$('[type=checkbox]:ui-hifiCheckbox').parent().prop('tabindex', 0);
|
||||
// allow tabbing between checkboxes using the container row
|
||||
$(':ui-hifiCheckbox,:ui-hifiRadioButton').prop('tabindex', -1).closest('.row').prop('tabindex', 0);
|
||||
|
||||
|
||||
// select the input field text when first focused
|
||||
$('input').not('input[type=radio],input[type=checkbox]').on('focus', function () {
|
||||
|
@ -236,12 +238,13 @@ function setupUI() {
|
|||
});
|
||||
|
||||
// monitor changes to specific settings that affect the UI
|
||||
monitorSettings({
|
||||
var monitors = {
|
||||
// advanced options toggle
|
||||
'ui-show-advanced-options': function onChange(value) {
|
||||
function handle(err, result) {
|
||||
log('** ui-show-advanced-options updated', result+'');
|
||||
$('body').toggleClass('ui-show-advanced-options', !!result);
|
||||
jquerySettings.setValue('ui-show-advanced-options', result)
|
||||
}
|
||||
if (!onChange.fetched) {
|
||||
bridgedSettings.getValueAsync('ui-show-advanced-options', handle);
|
||||
|
@ -252,6 +255,7 @@ function setupUI() {
|
|||
|
||||
// UI tooltips toggle
|
||||
'ui-enable-tooltips': function(value) {
|
||||
if (!tooltipManager) return;
|
||||
if (value) {
|
||||
tooltipManager.enable();
|
||||
tooltipManager.openFocusedTooltip();
|
||||
|
@ -264,7 +268,7 @@ function setupUI() {
|
|||
'thread-update-mode': function(value) {
|
||||
var enabled = (value === 'requestAnimationFrame'), fps = $('#fps');
|
||||
fps.hifiSpinner(enabled ? 'enable' : 'disable');
|
||||
fps.closest('.tooltip-target').toggleClass('disabled', !enabled);
|
||||
fps.closest('.row').toggleClass('disabled', !enabled);
|
||||
},
|
||||
|
||||
// flag BODY with CSS class to indicate active camera move mode
|
||||
|
@ -277,8 +281,11 @@ function setupUI() {
|
|||
value = bridgedSettings.extraParams;
|
||||
if (value.mode) {
|
||||
for (var p in value.mode) {
|
||||
// tablet-mode, hmd-mode, etc.
|
||||
$('body').toggleClass(p + '-mode', value.mode[p]);
|
||||
}
|
||||
document.oncontextmenu = value.mode.tablet ? function(evt) { return evt.preventDefault(),false; } : null;
|
||||
$('[data-type=number]').prop('type', value.mode.tablet ? 'number' : 'text');
|
||||
}
|
||||
var versionDisplay = [
|
||||
value.appVersion || '(unknown appVersion)',
|
||||
|
@ -291,18 +298,50 @@ function setupUI() {
|
|||
$('#toggleKey').find('.binding').empty()
|
||||
.append(getKeysHTML(value.toggleKey)).end().show();
|
||||
}
|
||||
|
||||
var activateLookAtOption = value.MyAvatar && value.MyAvatar.supportsLookAtSnappingEnabled;
|
||||
$(jquerySettings.findNodeByKey('Avatar/lookAtSnappingEnabled'))
|
||||
.hifiCheckbox(activateLookAtOption ? 'enable' : 'disable')
|
||||
.closest('.row').toggleClass('disabled', !activateLookAtOption)
|
||||
.css('pointer-events', 'all') // so tooltips display regardless
|
||||
|
||||
var activateCursorOption = value.Reticle && value.Reticle.supportsScale;
|
||||
$('#minimal-cursor:ui-hifiCheckbox')
|
||||
.hifiCheckbox(activateCursorOption ? 'enable' : 'disable')
|
||||
.closest('.row').toggleClass('disabled', !activateCursorOption)
|
||||
.css('pointer-events', 'all') // so tooltips display regardless
|
||||
},
|
||||
|
||||
// gray out / ungray out page content if user is mouse looking around in Interface
|
||||
// (otherwise the cursor still interacts with web content...)
|
||||
'Keyboard.RightMouseButton': function(localValue, key, value) {
|
||||
log(localValue, '... Keyboard.RightMouseButton:' + value);
|
||||
debugPrint(localValue, '... Keyboard.RightMouseButton:' + value);
|
||||
window.active = !value;
|
||||
},
|
||||
});
|
||||
};
|
||||
monitors['toggle-advanced-options'] = monitors['ui-toggle-advanced-options'];
|
||||
monitorSettings(monitors);
|
||||
// disable selection
|
||||
// $('input').css('-webkit-user-select', 'none');
|
||||
|
||||
viewportUpdated.connect(lessManager, 'onViewportUpdated');
|
||||
|
||||
setupTooltips();
|
||||
|
||||
$(window).trigger('resize'); // populate viewport
|
||||
|
||||
// set up DOM MutationObservers
|
||||
settingsNodes.each(function tcobo() {
|
||||
if (this.dataset.hifiType === 'hifiButton') {
|
||||
return;
|
||||
}
|
||||
var id = assert(this.dataset['for'] || this.id, 'could not id for node: ' + this.outerHTML);
|
||||
assert(!tcobo[id]); // detect dupes
|
||||
tcobo[id] = true;
|
||||
debugPrint('OBSERVING NODE', id, this.id || this.getAttribute('for'));
|
||||
jquerySettings.observeNode(this);
|
||||
});
|
||||
|
||||
// set up key bindings
|
||||
setupMousetrapKeys();
|
||||
|
||||
|
@ -311,7 +350,7 @@ function setupUI() {
|
|||
// translate hifi's proprietary key scheme into human-friendly KBDs
|
||||
return [ 'Control', 'Meta', 'Alt', 'Super', 'Menu', 'Shifted' ]
|
||||
.map(function(flag) {
|
||||
return binding['is' + flag] && flag;
|
||||
return binding['is' + flag] && flag;
|
||||
})
|
||||
.concat(text)
|
||||
.filter(Boolean)
|
||||
|
@ -322,6 +361,33 @@ function setupUI() {
|
|||
}
|
||||
} // setupUI
|
||||
|
||||
function setupTooltips() {
|
||||
// extract the tooltip captions
|
||||
var tooltips = window.tooltips = {};
|
||||
var target = '[id], [data-for], [for]';
|
||||
$('.tooltip')
|
||||
.removeClass('tooltip').addClass('x-tooltip')
|
||||
.each(function() {
|
||||
var element = $(this),
|
||||
input = $(element.parent().find('input').get(0) ||
|
||||
element.closest('button').get(0));
|
||||
id = element.prop('id') || element.data('for') ||
|
||||
input.prop('id') || input.data('for');
|
||||
assert(id);
|
||||
tooltips[id] = this.outerHTML;
|
||||
}).hide();
|
||||
tooltipManager = new TooltipManager({
|
||||
enabled: false,
|
||||
testmode: PARAMS.tooltiptest,
|
||||
viewport: PARAMS.viewport,
|
||||
tooltips: tooltips,
|
||||
elements: '#reset-to-defaults, button, input',
|
||||
});
|
||||
viewportUpdated.connect(tooltipManager, 'updateViewport');
|
||||
// tooltips aren't needed right away, so defer initializing for better page load times
|
||||
window.setTimeout(tooltipManager.initialize.bind(tooltipManager), 1000);
|
||||
}
|
||||
|
||||
// helper for instrumenting local jquery onchange handlers
|
||||
function monitorSettings(options) {
|
||||
return Object.keys(options).reduce(function(out, id) {
|
||||
|
@ -333,18 +399,18 @@ function monitorSettings(options) {
|
|||
id: id,
|
||||
type: 'placeholder',
|
||||
toString: function() {
|
||||
return this.id;
|
||||
return this.id;
|
||||
},
|
||||
value: undefined,
|
||||
};
|
||||
jquerySettings.registerSetting(placeholder, key);
|
||||
log('registered placeholder value for setting', id, key);
|
||||
debugPrint('registered placeholder value for setting', id, key);
|
||||
assert(jquerySettings.findNodeByKey(key) === placeholder);
|
||||
}
|
||||
|
||||
// if (domId === 'toggle-advanced-options') alert([key,id,domId, jquerySettings.findNodeByKey(key)])
|
||||
assert(function assertion(){
|
||||
return typeof key === 'string';
|
||||
return typeof key === 'string';
|
||||
}, 'monitorSettings -- received invalid key type');
|
||||
|
||||
var context = {
|
||||
|
@ -368,13 +434,13 @@ function monitorSettings(options) {
|
|||
|
||||
if (jsonCurrentValue === context.jsonLastValue) {
|
||||
if (jsonCurrentValue !== undefined) {
|
||||
log([context.key, '_onChange', this, 'not triggering _onChange for duplicated value']);
|
||||
debugPrint([context.key, '_onChange', this, 'not triggering _onChange for duplicated value']);
|
||||
}
|
||||
return;
|
||||
}
|
||||
context.jsonLastValue = jsonCurrentValue;
|
||||
var args = [].slice.call(arguments, 0);
|
||||
log('monitorSetting._onChange', context.key, value, this+'', id, args);
|
||||
debugPrint('monitorSetting._onChange', context.key, value, [].concat(args).pop());
|
||||
context.options[context.id].apply(this, [ currentValue ].concat(args));
|
||||
},
|
||||
|
||||
|
@ -408,31 +474,29 @@ function monitorSettings(options) {
|
|||
|
||||
function initializeDOM() {
|
||||
|
||||
// document.onselectstart = document.ondragstart =
|
||||
// document.body.ondragstart = document.body.onselectstart = function(){ return false; };
|
||||
|
||||
document.body.oncontextmenu = document.oncontextmenu = document.body.ontouchstart = function(evt) {
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
return false;
|
||||
};
|
||||
|
||||
Object.defineProperty(window, 'active', {
|
||||
get: function() {
|
||||
return window._active;
|
||||
return window._active;
|
||||
},
|
||||
set: function(nv) {
|
||||
nv = !!nv;
|
||||
window._active = nv;
|
||||
log('window.active == ' + nv);
|
||||
debugPrint('window.active == ' + nv);
|
||||
if (!nv) {
|
||||
document.activeElement && document.activeElement.blur();
|
||||
document.body.focus();
|
||||
tooltipManager && tooltipManager.disable();
|
||||
log('TOOLTIPS DISABLED');
|
||||
} else if (tooltipManager && bridgedSettings) {
|
||||
if (bridgedSettings.getValue('ui-enable-tooltips')){
|
||||
tooltipManager.enable();
|
||||
log('TOOLTIPS RE-ENABLED');
|
||||
}
|
||||
}
|
||||
$('body').toggleClass('active', nv);
|
||||
$('body').toggleClass('active', window._active);
|
||||
},
|
||||
});
|
||||
window.active = true;
|
||||
$('body').toggleClass('active', window._active = true);
|
||||
|
||||
function checkAnim(evt) {
|
||||
if (!checkAnim.disabled) {
|
||||
|
@ -453,7 +517,7 @@ function initializeDOM() {
|
|||
min: { width: window.innerWidth / 3, height: 32 },
|
||||
max: { width: window.innerWidth * 7/8, height: window.innerHeight * 7/8 },
|
||||
};
|
||||
log('viewportUpdated', viewport);
|
||||
debugPrint('viewportUpdated', viewport);
|
||||
PARAMS.viewport = Object.assign(PARAMS.viewport||{}, viewport);
|
||||
viewportUpdated(viewport, triggerViewportUpdate.lastViewport);
|
||||
triggerViewportUpdate.lastViewport = viewport;
|
||||
|
@ -469,13 +533,11 @@ function initializeDOM() {
|
|||
$('body').addClass('window-blurred');
|
||||
document.body.focus();
|
||||
document.activeElement && document.activeElement.blur();
|
||||
tooltipManager.disable();
|
||||
// tooltipManager.closeAll();
|
||||
},
|
||||
focus: function() {
|
||||
log('** FOCUS **');
|
||||
$('body').removeClass('window-blurred');
|
||||
bridgedSettings.getValue('ui-enable-tooltips') && tooltipManager.enable();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -511,7 +573,7 @@ function setupMousetrapKeys() {
|
|||
'r': location.reload.bind(location),
|
||||
'space': function global(evt, combo) {
|
||||
log('SPACE', evt.target, document.activeElement);
|
||||
$(document.activeElement).filter('.bool.row').find(':ui-hifiCheckbox').click();
|
||||
$(document.activeElement).filter('.row').find(':ui-hifiCheckbox,:ui-hifiRadioButton').click();
|
||||
if (!$(document.activeElement).is('input,.ui-widget')) {
|
||||
return false;
|
||||
}
|
||||
|
@ -525,12 +587,22 @@ function setupMousetrapKeys() {
|
|||
Object.keys(obj).forEach(function(key) {
|
||||
var method = obj[key].name === 'global' ? 'bindGlobal' : 'bind';
|
||||
key.split(/\s*,\s*/).forEach(function(combo) {
|
||||
log('Mousetrap', method, combo, typeof obj[key]);
|
||||
debugPrint('Mousetrap', method, combo, typeof obj[key]);
|
||||
Mousetrap[method](combo, function(evt, combo) {
|
||||
log('Mousetrap', method, combo);
|
||||
debugPrint('Mousetrap', method, combo);
|
||||
return obj[key].apply(this, arguments);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// support the URL having a #node-id (or #debug=1&node-id) hash fragment jumping to that element
|
||||
function jumpToAnchor(id) {
|
||||
id = JSON.stringify(id);
|
||||
$('[id='+id+'],[name='+id+']').first().each(function() {
|
||||
log('jumpToAnchor', id);
|
||||
$(this).show();
|
||||
this.scrollIntoView({ behavior: 'smooth' });
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
/* eslint-env commonjs */
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// helper module that performs the avatar movement update calculations
|
||||
|
||||
module.exports = AvatarUpdater;
|
||||
|
||||
var _utils = require('./modules/_utils.js'),
|
||||
|
@ -11,7 +14,7 @@ function AvatarUpdater(options) {
|
|||
options = options || {};
|
||||
assert(function assertion() {
|
||||
return typeof options.getCameraMovementSettings === 'function' &&
|
||||
typeof options.getMovementState === 'function' &&
|
||||
typeof options.getMovementState === 'function' &&
|
||||
options.globalState;
|
||||
});
|
||||
|
||||
|
@ -23,7 +26,8 @@ function AvatarUpdater(options) {
|
|||
var MIN_DELTA_TIME = 0.0001; // to avoid math overflow, never consider dt less than this value
|
||||
var DEG_TO_RAD = Math.PI / 180.0;
|
||||
update.frameCount = 0;
|
||||
update.endTime = _utils.getRuntimeSeconds();
|
||||
update.endTime = update.windowStartTime = _utils.getRuntimeSeconds();
|
||||
update.windowFrame = update.windowStartFrame= 0;
|
||||
|
||||
this.update = update;
|
||||
this.options = options;
|
||||
|
@ -43,7 +47,8 @@ function AvatarUpdater(options) {
|
|||
var independentCamera = Camera.mode === 'independent',
|
||||
headPitch = MyAvatar.headPitch;
|
||||
|
||||
var actualDeltaTime = Math.max(MIN_DELTA_TIME, (startTime - update.endTime)),
|
||||
var actualDeltaTime = startTime - update.endTime,
|
||||
practicalDeltaTime = Math.max(MIN_DELTA_TIME, actualDeltaTime),
|
||||
deltaTime;
|
||||
|
||||
if (settings.useConstantDeltaTime) {
|
||||
|
@ -52,7 +57,7 @@ function AvatarUpdater(options) {
|
|||
} else if (settings.threadMode === movementUtils.CameraControls.SCRIPT_UPDATE) {
|
||||
deltaTime = dt;
|
||||
} else {
|
||||
deltaTime = actualDeltaTime;
|
||||
deltaTime = practicalDeltaTime;
|
||||
}
|
||||
|
||||
var orientationProperty = settings.useHead ? 'headOrientation' : 'orientation',
|
||||
|
@ -124,7 +129,6 @@ function AvatarUpdater(options) {
|
|||
previousValues.thrust = pendingChanges.MyAvatar.setThrust = thrust;
|
||||
break;
|
||||
}
|
||||
case DriveModes.JITTER_TEST:
|
||||
case DriveModes.POSITION: {
|
||||
pendingChanges.MyAvatar.position = Vec3.sum(currentPosition, deltaPosition);
|
||||
break;
|
||||
|
@ -155,7 +159,7 @@ function AvatarUpdater(options) {
|
|||
break;
|
||||
}
|
||||
|
||||
if (settings.driveMode === movementUtils.DriveModes.JITTER_TEST) {
|
||||
if (settings.jitterTest) {
|
||||
finalOrientation = Quat.multiply(MyAvatar[orientationProperty], Quat.fromPitchYawRollDegrees(0, 60 * deltaTime, 0));
|
||||
// Quat.fromPitchYawRollDegrees(0, _utils.getRuntimeSeconds() * 60, 0)
|
||||
}
|
||||
|
@ -178,10 +182,17 @@ function AvatarUpdater(options) {
|
|||
|
||||
pendingChanges.submit();
|
||||
|
||||
update.momentaryFPS = 1 / actualDeltaTime;
|
||||
if ((endTime - update.windowStartTime) > 3) {
|
||||
update.momentaryFPS = (update.frameCount - update.windowStartFrame) /
|
||||
(endTime - update.windowStartTime);
|
||||
update.windowStartFrame = update.frameCount;
|
||||
update.windowStartTime = endTime;
|
||||
}
|
||||
|
||||
if (_debugChannel && update.frameCount % 120 === 0) {
|
||||
if (_debugChannel && update.windowStartFrame === update.frameCount) {
|
||||
Messages.sendLocalMessage(_debugChannel, JSON.stringify({
|
||||
threadFrames: update.threadFrames,
|
||||
frame: update.frameCount,
|
||||
threadMode: settings.threadMode,
|
||||
driveMode: settings.driveMode,
|
||||
orientationProperty: orientationProperty,
|
||||
|
|
|
@ -5,28 +5,46 @@
|
|||
/* global assert, log, debugPrint */
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
// WIDGET BASE
|
||||
Object.assign($.Widget.prototype, {
|
||||
initHifiControl: function(hifiType) {
|
||||
// common bootstrapping across widget types
|
||||
initHifiControl: function initHifiControl(hifiType) {
|
||||
initHifiControl.widgetCount = (initHifiControl.widgetCount || 0) + 1;
|
||||
hifiType = hifiType || this.widgetName;
|
||||
var type = this.element.attr('data-type') || this.element.attr('type') || type,
|
||||
id = this.element.prop('id') || hifiType + '-' + ~~(Math.random()*1e20).toString(36),
|
||||
fer = this.element.attr('for'),
|
||||
value = this.element.attr('data-value') || this.element.attr('value');
|
||||
|
||||
this.element.prop('id', id);
|
||||
this.element.attr('data-type', type);
|
||||
this.element.attr('data-hifi-type', hifiType || 'wtf');
|
||||
value && this.element.attr('value', value);
|
||||
fer && this.element.attr('data-for', fer);
|
||||
return id;
|
||||
var element = this.element, options = this.options, node = element.get(0), dataset = node.dataset;
|
||||
assert(!this.element.is('.initialized'));
|
||||
this.element.addClass('initialized');
|
||||
var attributes = [].reduce.call(node.attributes, function(out, attribute) {
|
||||
out[attribute.name] = attribute.value;
|
||||
return out;
|
||||
}, {});
|
||||
|
||||
var searchOrder = [ options, dataset, attributes, node ];
|
||||
function setData(key, fallback) {
|
||||
var value = searchOrder.map(function(obj) {
|
||||
return obj[key];
|
||||
}).concat(fallback).filter(function(value) {
|
||||
return value !== undefined;
|
||||
})[0];
|
||||
return value === undefined ? null : (dataset[key] = value);
|
||||
}
|
||||
options.hifiWidgetId = hifiType + '-' + initHifiControl.widgetCount;
|
||||
node.id = node.id || options.hifiWidgetId;
|
||||
dataset.hifiType = hifiType;
|
||||
setData('type');
|
||||
setData('for', node.id);
|
||||
setData('checked');
|
||||
if (setData('value', null) !== null) {
|
||||
element.attr('value', dataset.value);
|
||||
}
|
||||
|
||||
return node.id;
|
||||
},
|
||||
hifiFindWidget: function(hifiType, quiet) {
|
||||
var selector = ':ui-'+hifiType;
|
||||
// first try based on for property (eg: "[id=translation-ease-in]:ui-hifiSpinner")
|
||||
var fer = this.element.attr('data-for'),
|
||||
element = fer && $('[id='+fer+']').filter(selector);
|
||||
var _for = JSON.stringify(this.element.data('for')||undefined),
|
||||
element = _for && $('[id='+_for+']').filter(selector);
|
||||
if (!element.is(selector)) {
|
||||
element = this.element.closest(selector);
|
||||
}
|
||||
|
@ -36,8 +54,8 @@ Object.assign($.Widget.prototype, {
|
|||
// eslint-disable-next-line no-console
|
||||
console.error([
|
||||
instance, 'could not find target instance ' + selector +
|
||||
' for ' + this.element.attr('data-hifi-type') +
|
||||
' #' + this.element.prop('id') + ' for=' + this.element.attr('data-for')
|
||||
' for ' + this.element.data('hifi-type') +
|
||||
' #' + this.element.prop('id') + ' for=' + this.element.data('for')
|
||||
]);
|
||||
}
|
||||
return instance;
|
||||
|
@ -52,27 +70,23 @@ $.widget('ui.hifiCheckbox', $.ui.checkboxradio, {
|
|||
if (nv !== currentValue){
|
||||
this.element.prop('checked', nv);
|
||||
this.element.change();
|
||||
// this.refresh();
|
||||
}
|
||||
}
|
||||
return this.element.prop('checked');
|
||||
},
|
||||
_create: function() {
|
||||
var id = this.initHifiControl();
|
||||
|
||||
this.element.attr('value', id);
|
||||
// add an implicit label if missing
|
||||
var forId = 'for=' + id;
|
||||
var label = $('label[' + forId + ']');
|
||||
var forId = 'for=' + JSON.stringify(id);
|
||||
var label = $(this.element.get(0)).closest('label').add($('label[' + forId + ']'));
|
||||
if (!label.get(0)) {
|
||||
$('<label ' + forId + '>' + forId + '</label>').appendTo(this.element);
|
||||
}
|
||||
this._super();
|
||||
|
||||
this.element.on('change._hifiCheckbox, click._hifiCheckbox', function() {
|
||||
var checked = this.value(),
|
||||
attr = this.element.attr('checked');
|
||||
|
||||
if (checked && !attr) {
|
||||
this.element.attr('checked', 'checked');
|
||||
} else if (!checked && attr) {
|
||||
|
@ -86,7 +100,6 @@ $.widget('ui.hifiCheckbox', $.ui.checkboxradio, {
|
|||
// BUTTON
|
||||
$.widget('ui.hifiButton', $.ui.button, {
|
||||
value: function(nv) {
|
||||
// log('ui.hifiButton.value', this.element[0].id, nv);
|
||||
var dataset = this.element[0].dataset;
|
||||
if (arguments.length) {
|
||||
var checked = (dataset.checked === 'true');
|
||||
|
@ -102,21 +115,23 @@ $.widget('ui.hifiButton', $.ui.button, {
|
|||
return dataset.checked === 'true';
|
||||
},
|
||||
_create: function() {
|
||||
this.element[0].dataset.type = 'checkbox';
|
||||
this.element.data('type', 'checkbox');
|
||||
this.initHifiControl();
|
||||
this._super();
|
||||
|
||||
this.element[0].dataset.checked = !!this.element.attr('checked');
|
||||
var fer = this.element.attr('data-for');
|
||||
if (fer) {
|
||||
var _for = this.element.data('for') || undefined;
|
||||
if (_for && _for !== this.element[0].id) {
|
||||
_for = JSON.stringify(_for);
|
||||
var checkbox = this.hifiFindWidget('hifiCheckbox', true);
|
||||
if (!checkbox) {
|
||||
var input = $('<label><input type=checkbox id=' + fer + ' value=' + fer +' /></label>').hide();
|
||||
input.appendTo(this.element.parent());
|
||||
var input = $('<label><input type=checkbox id=' + _for + ' value=' + _for +' /></label>').hide();
|
||||
input.appendTo(this.element);
|
||||
checkbox = input.find('input')
|
||||
.hifiCheckbox()
|
||||
.hifiCheckbox('instance');
|
||||
}
|
||||
this.element.find('.tooltip-target').removeClass('tooltip-target');
|
||||
this.element.prop('id', 'button-'+this.element.prop('id'));
|
||||
checkbox.element.on('change._hifiButton', function() {
|
||||
log('checkbox -> button');
|
||||
this.value(checkbox.value());
|
||||
|
@ -130,21 +145,20 @@ $.widget('ui.hifiButton', $.ui.button, {
|
|||
},
|
||||
});
|
||||
|
||||
// RADIO BUTTON
|
||||
$.widget('ui.hifiRadioButton', $.ui.checkboxradio, {
|
||||
value: function value(nv) {
|
||||
if (arguments.length) {
|
||||
log('RADIOBUTTON VALUE', nv);
|
||||
this.element.prop('checked', !!nv);
|
||||
this.element.change();
|
||||
// this.refresh();
|
||||
}
|
||||
return this.element.prop('checked');
|
||||
},
|
||||
_create: function() {
|
||||
var id = this.initHifiControl();
|
||||
this.element.attr('value', id);
|
||||
this.element.attr('value', this.element.data('value') || id);
|
||||
// console.log(this.element[0]);
|
||||
assert(this.element.attr('data-for'));
|
||||
assert(this.element.data('for'));
|
||||
this._super();
|
||||
|
||||
this.element.on('change._hifiRadioButton, click._hifiRadioButton', function() {
|
||||
|
@ -163,7 +177,7 @@ $.widget('ui.hifiRadioButton', $.ui.checkboxradio, {
|
|||
},
|
||||
});
|
||||
|
||||
// RADIO-GROUP
|
||||
// RADIO GROUP
|
||||
$.widget('ui.hifiRadioGroup', $.ui.controlgroup, {
|
||||
radio: function(selector) {
|
||||
return this.element.find(':ui-hifiRadioButton' + selector).hifiRadioButton('instance');
|
||||
|
@ -179,7 +193,7 @@ $.widget('ui.hifiRadioGroup', $.ui.controlgroup, {
|
|||
if (arguments.length) {
|
||||
var id = this.element[0].id,
|
||||
previous = this.value();
|
||||
log('RADIOBUTTON GROUP value', id + ' = ' + nv + '(was: ' + previous + ')');
|
||||
debugPrint('RADIOBUTTON GROUP value', id + ' = ' + nv + '(was: ' + previous + ')');
|
||||
this.element.attr('value', nv);
|
||||
this.refresh();
|
||||
}
|
||||
|
@ -188,12 +202,11 @@ $.widget('ui.hifiRadioGroup', $.ui.controlgroup, {
|
|||
_create: function(x) {
|
||||
debugPrint('ui.hifiRadioGroup._create', this.element[0]);
|
||||
this.initHifiControl();
|
||||
|
||||
var tmp = this.options.items.checkboxradio;
|
||||
delete this.options.items.checkboxradio;
|
||||
this.options.items.hifiRadioButton = tmp;
|
||||
|
||||
this.options.items = {
|
||||
hifiRadioButton: 'input[type=radio]',
|
||||
};
|
||||
this._super();
|
||||
// allow setting correct radio button by assign to .value property (or $.fn.val() etc.)
|
||||
Object.defineProperty(this.element[0], 'value', {
|
||||
set: function(nv) {
|
||||
try {
|
||||
|
@ -208,12 +221,12 @@ $.widget('ui.hifiRadioGroup', $.ui.controlgroup, {
|
|||
},
|
||||
});
|
||||
|
||||
// SPINNER (numeric input w/ up/down arrows)
|
||||
// SPINNER (numeric input + up/down buttons)
|
||||
$.widget('ui.hifiSpinner', $.ui.spinner, {
|
||||
value: function value(nv) {
|
||||
if (arguments.length) {
|
||||
var num = parseFloat(nv);
|
||||
debugPrint('ui.hifiSpinner.value set', num, '(was: ' + this.value() + ')', 'raw:'+nv);
|
||||
debugPrint('ui.hifiSpinner.value set', this.element[0].id, num, '(was: ' + this.value() + ')', 'raw:'+nv);
|
||||
this._value(num);
|
||||
this.element.change();
|
||||
}
|
||||
|
@ -223,15 +236,11 @@ $.widget('ui.hifiSpinner', $.ui.spinner, {
|
|||
debugPrint('ui.hifiSpinner._value', value, allowAny);
|
||||
return this._super(value, allowAny);
|
||||
},
|
||||
|
||||
_create: function() {
|
||||
this.initHifiControl();
|
||||
if (this.options.min === '-Infinity') {
|
||||
this.options.min = -1e10;
|
||||
}
|
||||
if (this.options.max === '-Infinity') {
|
||||
this.options.max = 1e10;
|
||||
}
|
||||
var step = this.options.step = this.options.step || 1.0;
|
||||
// allow step=".01" for precision and data-step=".1" for default increment amount
|
||||
this.options.prescale = parseFloat(this.element.data('step') || step) / (step);
|
||||
this._super();
|
||||
this.previous = null;
|
||||
this.element.on('change._hifiSpinner', function() {
|
||||
|
@ -242,14 +251,9 @@ $.widget('ui.hifiSpinner', $.ui.spinner, {
|
|||
}.bind(this));
|
||||
},
|
||||
_spin: function( step, event ) {
|
||||
if (event.type === 'mousewheel') {
|
||||
if (!event.shiftKey) {
|
||||
step *= ('1e'+Math.max(1,this._precision()))/10;
|
||||
}
|
||||
if (event.ctrlKey) {
|
||||
step *= 10;
|
||||
}
|
||||
}
|
||||
step = step * this.options.prescale * (
|
||||
event.shiftKey ? 0.1 : event.ctrlKey ? 10 : 1
|
||||
);
|
||||
return this._super( step, event );
|
||||
},
|
||||
_stop: function( event, ui ) {
|
||||
|
@ -259,10 +263,6 @@ $.widget('ui.hifiSpinner', $.ui.spinner, {
|
|||
if (/mouse/.test(event && event.type)) {
|
||||
var value = this.value();
|
||||
if ((value || value === 0) && !isNaN(value) && this.previous !== null && this.previous !== value) {
|
||||
debugPrint(this.element[0].id, 'spinner.changed', event.type, JSON.stringify({
|
||||
previous: isNaN(this.previous) ? this.previous+'' : this.previous,
|
||||
val: isNaN(value) ? value+'' : value,
|
||||
}));
|
||||
this.value(this.value());
|
||||
}
|
||||
this.previous = value;
|
||||
|
@ -275,12 +275,11 @@ $.widget('ui.hifiSpinner', $.ui.spinner, {
|
|||
},
|
||||
_events: {
|
||||
mousewheel: function(event, delta) {
|
||||
if (document.activeElement !== this.element[0]) {
|
||||
return;
|
||||
if (document.activeElement === this.element[0]) {
|
||||
// fix broken mousewheel on Chrome / embedded webkit
|
||||
delta = delta === undefined ? -(event.originalEvent.deltaY+event.originalEvent.deltaX) : delta;
|
||||
$.ui.spinner.prototype._events.mousewheel.call(this, event, delta);
|
||||
}
|
||||
// fix broken mousewheel on Chrome / webkit
|
||||
delta = delta === undefined ? event.originalEvent.deltaY : delta;
|
||||
$.ui.spinner.prototype._events.mousewheel.call(this, event, delta);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -300,7 +299,6 @@ $.widget('ui.hifiSlider', $.ui.slider, {
|
|||
},
|
||||
_create: function() {
|
||||
this.initHifiControl();
|
||||
assert(this.element.attr('data-for'));
|
||||
this._super();
|
||||
this.element
|
||||
.attr('type', this.element.attr('type') || 'slider')
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// // EnumMeta.js -- helper module that maps related enum values to names and ids
|
||||
// //
|
||||
// EnumMeta.js -- helper module that maps related enum values to printable names and ids
|
||||
|
||||
/* eslint-env commonjs */
|
||||
/* global DriveKeys, console */
|
||||
|
||||
|
|
|
@ -3,19 +3,18 @@
|
|||
"use strict";
|
||||
/* eslint-env commonjs, hifi */
|
||||
/* eslint-disable comma-dangle, no-empty */
|
||||
/* global HIRES_CLOCK, Desktop */
|
||||
/* global HIRES_CLOCK, Desktop, OverlayWebWindow */
|
||||
// var HIRES_CLOCK = (typeof Window === 'object' && Window && Window.performance) && Window.performance.now;
|
||||
var USE_HIRES_CLOCK = typeof HIRES_CLOCK === 'function';
|
||||
|
||||
var exports = {
|
||||
version: '0.0.1b' + (USE_HIRES_CLOCK ? '-hires' : ''),
|
||||
version: '0.0.1c' + (USE_HIRES_CLOCK ? '-hires' : ''),
|
||||
bind: bind,
|
||||
signal: signal,
|
||||
assign: assign,
|
||||
sortedAssign: sortedAssign,
|
||||
sign: sign,
|
||||
assert: assert,
|
||||
getSystemMetadata: getSystemMetadata,
|
||||
makeDebugRequire: makeDebugRequire,
|
||||
DeferredUpdater: DeferredUpdater,
|
||||
KeyListener: KeyListener,
|
||||
|
@ -25,8 +24,6 @@ var exports = {
|
|||
|
||||
normalizeStackTrace: normalizeStackTrace,
|
||||
BrowserUtils: BrowserUtils,
|
||||
BSOD: BSOD, // exception reporter
|
||||
_overlayWebWindow: _overlayWebWindow,
|
||||
};
|
||||
try {
|
||||
module.exports = exports; // Interface / Node.js
|
||||
|
@ -42,6 +39,7 @@ function makeDebugRequire(relativeTo) {
|
|||
}
|
||||
function debugRequire(id, relativeTo) {
|
||||
if (typeof Script === 'object') {
|
||||
relativeTo = (relativeTo||Script.resolvePath('.')).replace(/\/+$/, '');
|
||||
// hack-around for use during local development / testing that forces every require to re-fetch the script from the server
|
||||
var modulePath = Script._requireResolve(id, relativeTo+'/') + '?' + new Date().getTime().toString(36);
|
||||
print('========== DEBUGREQUIRE:' + modulePath);
|
||||
|
@ -203,9 +201,7 @@ function BrowserUtils(global) {
|
|||
return {
|
||||
global: global,
|
||||
console: global.console,
|
||||
log: function(msg) {
|
||||
this.console.log.apply(this.console, ['browserUtils | ' + msg].concat([].slice.call(arguments, 1)));
|
||||
},
|
||||
log: function(msg) { return this.console.log('browserUtils | ' + [].slice.call(arguments).join(' ')); },
|
||||
makeConsoleWorkRight: function(console, forcePatching) {
|
||||
if (console.$patched || !(forcePatching || global.qt)) {
|
||||
return console;
|
||||
|
@ -258,13 +254,11 @@ function BrowserUtils(global) {
|
|||
};
|
||||
}
|
||||
// ----------------------------------------------------------------------------
|
||||
// queue pending updates so the exact order of application can be varied
|
||||
// (currently Interface exists sporadic jitter that seems to depend on whether
|
||||
// Camera or MyAvatar gets updated first)
|
||||
// queue property/method updates to target so that they can be applied all-at-once
|
||||
function DeferredUpdater(target, options) {
|
||||
options = options || {};
|
||||
// define _meta as a non-enumerable instance property (so it doesn't show up in for(var p in ...) loops)
|
||||
Object.defineProperty(this, '_meta', { value: {
|
||||
// define _meta as a non-enumerable (so it doesn't show up in for (var p in ...) loops)
|
||||
Object.defineProperty(this, '_meta', { enumerable: false, value: {
|
||||
target: target,
|
||||
lastValue: {},
|
||||
dedupe: options.dedupe,
|
||||
|
@ -281,35 +275,11 @@ DeferredUpdater.prototype = {
|
|||
submit: function() {
|
||||
var meta = this._meta,
|
||||
target = meta.target,
|
||||
// lastValue = meta.lastValue,
|
||||
// dedupe = meta.dedupe,
|
||||
self = this,
|
||||
submitted = {};
|
||||
|
||||
self.submit = getRuntimeSeconds();
|
||||
Object.keys(self).forEach(function(property) {
|
||||
var newValue = self[property];
|
||||
// if (dedupe) {
|
||||
// var stringified = JSON.stringify(newValue);
|
||||
// var last = lastValue[property];
|
||||
// if (stringified === last) {
|
||||
// return;
|
||||
// }
|
||||
// lastValue[property] = stringified;
|
||||
// }
|
||||
// if (0) {
|
||||
// var tmp = lastValue['_'+property];
|
||||
// if (typeof tmp === 'object') {
|
||||
// if ('w' in tmp) {
|
||||
// newValue = Quat.normalize(Quat.slerp(tmp, newValue, 0.95));
|
||||
// } else if ('z' in tmp) {
|
||||
// newValue = Vec3.mix(tmp, newValue, 0.95);
|
||||
// }
|
||||
// } else if (typeof tmp === 'number') {
|
||||
// newValue = (newValue + tmp)/2.0;
|
||||
// }
|
||||
// lastValue['_'+property] = newValue;
|
||||
// }
|
||||
submitted[property] = newValue;
|
||||
if (typeof target[property] === 'function') {
|
||||
target[property](newValue);
|
||||
|
@ -347,7 +317,7 @@ DeferredUpdater.createGroup = function(items, options) {
|
|||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// monotonic session runtime (in seconds)
|
||||
// session runtime in seconds
|
||||
getRuntimeSeconds.EPOCH = getRuntimeSeconds(0);
|
||||
function getRuntimeSeconds(since) {
|
||||
since = since === undefined ? getRuntimeSeconds.EPOCH : since;
|
||||
|
@ -355,7 +325,7 @@ function getRuntimeSeconds(since) {
|
|||
return ((now / 1000.0) - since);
|
||||
}
|
||||
|
||||
|
||||
// requestAnimationFrame emulation
|
||||
function createAnimationStepper(options) {
|
||||
options = options || {};
|
||||
var fps = options.fps || 30,
|
||||
|
@ -385,10 +355,10 @@ function createAnimationStepper(options) {
|
|||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// KeyListener provides a scoped wrapper where options.onKeyPressEvent only gets
|
||||
// called when the specified event.text matches the input options
|
||||
// KeyListener provides a scoped wrapper where options.onKeyPressEvent gets
|
||||
// called when a key event matches the specified event.text / key spec
|
||||
// example: var listener = new KeyListener({ text: 'SPACE', isShifted: false, onKeyPressEvent: function(event) { ... } });
|
||||
// Script.scriptEnding.connect(listener, 'disconnect');
|
||||
// Script.scriptEnding.connect(listener, 'disconnect');
|
||||
function KeyListener(options) {
|
||||
assert(typeof options === 'object' && 'text' in options && 'onKeyPressEvent' in options);
|
||||
|
||||
|
@ -415,10 +385,7 @@ KeyListener.prototype = {
|
|||
if (event.text === this.text) {
|
||||
var modifiers = this._getEventModifiers(event, true);
|
||||
if (modifiers !== this.modifiers) {
|
||||
return this.log('KeyListener -- different modifiers, disregarding keystroke', JSON.stringify({
|
||||
expected: this.modifiers,
|
||||
received: modifiers,
|
||||
},0,2));
|
||||
return;
|
||||
}
|
||||
return this[target](event);
|
||||
}
|
||||
|
@ -446,117 +413,26 @@ KeyListener.prototype = {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
// helper to show a verbose exception report in a BSOD-like poup window, in some cases enabling users to
|
||||
// report specfic feedback without having to manually scan through their local debug logs
|
||||
|
||||
// creates an OverlayWebWindow using inline HTML content
|
||||
function _overlayWebWindow(options) {
|
||||
options = Object.assign({
|
||||
title: '_overlayWebWindow',
|
||||
width: Overlays.width() * 2 / 3,
|
||||
height: Overlays.height() * 2 / 3,
|
||||
content: '(empty)',
|
||||
}, options||{}, {
|
||||
source: 'about:blank',
|
||||
// helper to reload a client script
|
||||
reloadClientScript._findRunning = function(filename) {
|
||||
return ScriptDiscoveryService.getRunning().filter(function(script) {
|
||||
return 0 === script.path.indexOf(filename);
|
||||
});
|
||||
var window = new OverlayWebWindow(options);
|
||||
window.options = options;
|
||||
options.content && window.setURL('data:text/html;text,' + encodeURIComponent(options.content));
|
||||
return window;
|
||||
}
|
||||
|
||||
function BSOD(options, callback) {
|
||||
var buttonHTML = Array.isArray(options.buttons) && [
|
||||
'<div onclick="EventBridge.emitWebEvent(arguments[0].target.innerText)">',
|
||||
options.buttons.map(function(innerText) {
|
||||
return '<button>' + innerText + '</button>';
|
||||
}).join(', '),
|
||||
'</div>'].join('\n');
|
||||
|
||||
var HTML = [
|
||||
'<style>body { background:#0000aa; color:#ffffff; font-family:courier; font-size:8pt; margin:10px; }</style>',
|
||||
buttonHTML,
|
||||
'<pre style="white-space:pre-wrap;">',
|
||||
'<strong>' + options.error + '</strong>',
|
||||
normalizeStackTrace(options.error, {
|
||||
wrapLineNumbersWith: ['<b style=color:lime>','</b>'],
|
||||
wrapFilesWith: ['<b style=color:#f99>','</b>'],
|
||||
}),
|
||||
'</pre>',
|
||||
'<hr />DEBUG INFO:<br />',
|
||||
'<pre>' + JSON.stringify(Object.assign({ date: new Date }, options.debugInfo), 0, 2) + '</pre>',
|
||||
].filter(Boolean).join('\n');
|
||||
|
||||
var popup = _overlayWebWindow({
|
||||
title: options.title || 'PC LOAD LETTER',
|
||||
content: HTML,
|
||||
});
|
||||
popup.webEventReceived.connect(function(message) {
|
||||
print('popup.webEventReceived', message);
|
||||
try {
|
||||
callback(null, message);
|
||||
} finally {
|
||||
popup.close();
|
||||
}
|
||||
});
|
||||
return popup;
|
||||
}
|
||||
|
||||
function getSystemMetadata() {
|
||||
var tablet = Tablet.getTablet('com.highfidelity.interface.tablet.system');
|
||||
return {
|
||||
mode: {
|
||||
hmd: HMD.active,
|
||||
desktop: !HMD.active,
|
||||
toolbar: Uuid.isNull(HMD.tabletID),
|
||||
tablet: !Uuid.isNull(HMD.tabletID),
|
||||
},
|
||||
tablet: {
|
||||
toolbarMode: tablet.toolbarMode,
|
||||
desktopScale: Settings.getValue('desktopTabletScale'),
|
||||
hmdScale: Settings.getValue('hmdTabletScale'),
|
||||
},
|
||||
avatar: {
|
||||
pitchSpeed: MyAvatar.pitchSpeed,
|
||||
yawSpeed: MyAvatar.yawSpeed,
|
||||
density: MyAvatar.density,
|
||||
scale: MyAvatar.scale,
|
||||
},
|
||||
overlays: {
|
||||
width: Overlays.width(),
|
||||
height: Overlays.height(),
|
||||
},
|
||||
window: {
|
||||
width: Window.innerWidth,
|
||||
height: Window.innerHeight,
|
||||
},
|
||||
desktop: {
|
||||
width: Desktop.width,
|
||||
height: Desktop.height,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
function reloadClientScript(filename) {
|
||||
function log() {
|
||||
print('reloadClientScript | ', [].slice.call(arguments).join(' '));
|
||||
}
|
||||
log('reloading', filename);
|
||||
log('attempting to reload using stopScript(..., true):', filename);
|
||||
var result = ScriptDiscoveryService.stopScript(filename, true);
|
||||
log('...stopScript', filename, result);
|
||||
if (!result) {
|
||||
var matches = ScriptDiscoveryService.getRunning().filter(function(script) {
|
||||
// log(script.path, script.url);
|
||||
return 0 === script.path.indexOf(filename);
|
||||
});
|
||||
log('...matches', JSON.stringify(matches,0,2));
|
||||
var path = matches[0] && matches[0].path;
|
||||
var matches = reloadClientScript._findRunning(filename),
|
||||
path = matches[0] && matches[0].path;
|
||||
if (path) {
|
||||
log('...stopScript', path);
|
||||
log('attempting to reload using matched getRunning path: ' + path);
|
||||
result = ScriptDiscoveryService.stopScript(path, true);
|
||||
log('///stopScript', result);
|
||||
}
|
||||
}
|
||||
log('///result:' + result);
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
// config-utils.js -- helpers for coordinating Application runtime vs. Settings configuration
|
||||
//
|
||||
// * ApplicationConfig -- provides a way to configure and monitor Menu items and API values.
|
||||
// * SettingsConfig -- provides a similar way to configure and monitor scoped Settings values.
|
||||
// * ... together they provide a way to manage overlapping values and keep them in sync.
|
||||
// * ApplicationConfig -- Menu items and API values.
|
||||
// * SettingsConfig -- scoped Settings values.
|
||||
|
||||
"use strict";
|
||||
/* eslint-env commonjs */
|
||||
|
@ -24,13 +23,14 @@ function _debugPrint() {
|
|||
var debugPrint = function() {};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// grouped Application-specific configuration values using runtime state / API props
|
||||
// Application-specific configuration values using runtime state / API props
|
||||
//
|
||||
// options.config[] supports the following item formats:
|
||||
// 'settingsName': { menu: 'Menu > MenuItem'}, // assumes MenuItem is a checkbox / checkable value
|
||||
// 'settingsName': { object: [ MyAvatar, 'property' ] },
|
||||
// 'settingsName': { object: [ MyAvatar, 'getterMethod', 'setterMethod' ] },
|
||||
// 'settingsName': { menu: 'Menu > MenuItem', object: [ MyAvatar, 'property' ] },
|
||||
// 'settingsName': { get: function getter() { ...}, set: function(nv) { ... } },
|
||||
|
||||
function ApplicationConfig(options) {
|
||||
options = options || {};
|
||||
|
@ -42,7 +42,6 @@ function ApplicationConfig(options) {
|
|||
this.namespace = options.namespace;
|
||||
this.valueUpdated = _utils.signal(function valueUpdated(key, newValue, oldValue, origin){});
|
||||
|
||||
// process shorthand notations into fully-qualfied ApplicationConfigItem instances
|
||||
this.config = {};
|
||||
this.register(options.config);
|
||||
}
|
||||
|
@ -59,6 +58,7 @@ ApplicationConfig.prototype = {
|
|||
item.settingName = ~settingName.indexOf('/') ? settingName : [ this.namespace, settingName ].join('/');
|
||||
return this.config[item.settingName] = this.config[settingName] = new ApplicationConfigItem(item);
|
||||
},
|
||||
// process items into fully-qualfied ApplicationConfigItem instances
|
||||
register: function(items) {
|
||||
for (var p in items) {
|
||||
var item = items[p];
|
||||
|
@ -105,17 +105,20 @@ ApplicationConfig.prototype = {
|
|||
|
||||
// ApplicationConfigItem represents a single API/Menu item accessor
|
||||
function ApplicationConfigItem(item) {
|
||||
Object.assign(this, item, { _item: item });
|
||||
Object.assign(this, item);
|
||||
Object.assign(this, {
|
||||
_menu: this._parseMenuConfig(this.menu),
|
||||
_object: this._parseObjectConfig(this.object)
|
||||
_item: item.get && item,
|
||||
_object: this._parseObjectConfig(this.object),
|
||||
_menu: this._parseMenuConfig(this.menu)
|
||||
});
|
||||
this.authority = this._item ? 'item' : this._object ? 'object' : this._menu ? 'menu' : null;
|
||||
this._authority = this['_'+this.authority];
|
||||
debugPrint('_authority', this.authority, this._authority, Object.keys(this._authority));
|
||||
assert(this._authority, 'expected item.get, .object or .menu definition; ' + this.settingName);
|
||||
}
|
||||
ApplicationConfigItem.prototype = {
|
||||
authority: 'object', // when values conflict, this determines which source is considered the truth
|
||||
resync: function resync() {
|
||||
var authoritativeValue = this.get();
|
||||
|
||||
var authoritativeValue = this._authority.get();
|
||||
if (this._menu && this._menu.get() !== authoritativeValue) {
|
||||
_debugPrint(this.settingName, this._menu.menuItem,
|
||||
'... menu value ('+this._menu.get()+') out of sync;',
|
||||
|
@ -132,14 +135,13 @@ ApplicationConfigItem.prototype = {
|
|||
toString: function() {
|
||||
return '[ApplicationConfigItem ' + [
|
||||
'setting:' + JSON.stringify(this.settingName),
|
||||
this.authority !== ApplicationConfigItem.prototype.authority && 'authority:' + JSON.stringify(this.authority),
|
||||
'authority:' + JSON.stringify(this.authority),
|
||||
this._object && 'object:' + JSON.stringify(this._object.property || this._object.getter),
|
||||
this._menu && 'menu:' + JSON.stringify(this._menu.menu)
|
||||
// 'value:' + this.get(),
|
||||
].filter(Boolean).join(' ') + ']';
|
||||
},
|
||||
get: function get() {
|
||||
return this.authority === 'menu' ? this._menu.get() : this._object.get();
|
||||
return this._authority.get();
|
||||
},
|
||||
set: function set(nv) {
|
||||
this._object && this._object.set(nv);
|
||||
|
@ -174,16 +176,13 @@ ApplicationConfigItem.prototype = {
|
|||
return {
|
||||
object: object, property: getter,
|
||||
get: function() {
|
||||
// log('======> get API, property', object, getter, this.object[this.property]);
|
||||
return this.object[this.property];
|
||||
},
|
||||
set: function(nv) {
|
||||
// log('======> set API, property', object, getter, this.object[this.property], nv);
|
||||
return this.object[this.property] = nv;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this._raiseError('{ object: [ Object, getterOrPropertyName, setterName ] } -- invalid params or does not exist: ' +
|
||||
[ this.settingName, this.object, getter, setter ].join(' | '));
|
||||
},
|
||||
|
@ -209,7 +208,7 @@ ApplicationConfigItem.prototype = {
|
|||
}; // ApplicationConfigItem.prototype
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// grouped configuration using the Settings.* API abstraction
|
||||
// grouped configuration using the Settings.* API
|
||||
function SettingsConfig(options) {
|
||||
options = options || {};
|
||||
assert('namespace' in options);
|
||||
|
@ -251,5 +250,3 @@ SettingsConfig.prototype = {
|
|||
return isFinite(value) ? value : isFinite(defaultValue) ? defaultValue : 0.0;
|
||||
}
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
|
@ -73,19 +73,8 @@ function CustomSettingsApp(options) {
|
|||
|
||||
this.settingsAPI = options.settingsAPI || Settings;
|
||||
|
||||
// keep track of accessed settings so they can be kept in sync when changed on this side
|
||||
this._activeSettings = {
|
||||
sent: {},
|
||||
received: {},
|
||||
remote: {},
|
||||
get: function(key) {
|
||||
return {
|
||||
sent: this.sent[key],
|
||||
received: this.received[key],
|
||||
remote: this.remote[key]
|
||||
};
|
||||
}
|
||||
};
|
||||
// keep track of accessed settings so they can be kept in sync if changed externally
|
||||
this._activeSettings = { sent: {}, received: {}, remote: {} };
|
||||
|
||||
if (options.tablet) {
|
||||
this._initialize(options.tablet);
|
||||
|
@ -137,8 +126,17 @@ CustomSettingsApp.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
_apiGetValue: function(key, defaultValue) {
|
||||
// trim rooted keys like "/desktopTabletBecomesToolbar" => "desktopTabletBecomesToolbar"
|
||||
key = key.replace(/^\//,'');
|
||||
return this.settingsAPI.getValue(key, defaultValue);
|
||||
},
|
||||
_apiSetValue: function(key, value) {
|
||||
key = key.replace(/^\//,'');
|
||||
return this.settingsAPI.setValue(key, value);
|
||||
},
|
||||
_setValue: function(key, value, oldValue, origin) {
|
||||
var current = this.settingsAPI.getValue(key),
|
||||
var current = this._apiGetValue(key),
|
||||
lastRemoteValue = this._activeSettings.remote[key];
|
||||
debugPrint('.setValue(' + JSON.stringify({key: key, value: value, current: current, lastRemoteValue: lastRemoteValue })+')');
|
||||
this._activeSettings.received[key] = value;
|
||||
|
@ -148,7 +146,7 @@ CustomSettingsApp.prototype = {
|
|||
this.valueUpdated(key, value, lastRemoteValue, 'CustomSettingsApp.tablet');
|
||||
}
|
||||
if (current !== value) {
|
||||
result = this.settingsAPI.setValue(key, value);
|
||||
result = this._apiSetValue(key, value);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
@ -160,29 +158,28 @@ CustomSettingsApp.prototype = {
|
|||
}
|
||||
var params = Array.isArray(obj.params) ? obj.params : [obj.params];
|
||||
var parts = (obj.method||'').split('.'), api = parts[0], method = parts[1];
|
||||
if (api === 'valueUpdated') {
|
||||
obj.result = this._setValue.apply(this, params);
|
||||
} else if (api === 'Settings' && method && params[0]) {
|
||||
var key = this.resolve(params[0]), value = params[1];
|
||||
debugPrint('>>>>', method, key, value);
|
||||
switch (method) {
|
||||
case 'getValue': {
|
||||
obj.result = this.settingsAPI.getValue(key, value);
|
||||
this._activeSettings.sent[key] = obj.result;
|
||||
this._activeSettings.remote[key] = obj.result;
|
||||
switch(api) {
|
||||
case 'valueUpdated': obj.result = this._setValue.apply(this, params); break;
|
||||
case 'Settings':
|
||||
if (method && params[0]) {
|
||||
var key = this.resolve(params[0]), value = params[1];
|
||||
debugPrint('>>>>', method, key, value);
|
||||
switch (method) {
|
||||
case 'getValue':
|
||||
obj.result = this._apiGetValue(key, value);
|
||||
this._activeSettings.sent[key] = obj.result;
|
||||
this._activeSettings.remote[key] = obj.result;
|
||||
break;
|
||||
case 'setValue':
|
||||
obj.result = this._setValue(key, value, params[2], params[3]);
|
||||
break;
|
||||
default:
|
||||
obj.error = 'unmapped Settings method: ' + method;
|
||||
throw new Error(obj.error);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'setValue': {
|
||||
obj.result = this._setValue(key, value, params[2], params[3]);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
obj.error = 'unmapped Settings method: ' + method;
|
||||
throw new Error(obj.error);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this.onUnhandledMessage) {
|
||||
default: if (this.onUnhandledMessage) {
|
||||
this.onUnhandledMessage(obj, msg);
|
||||
} else {
|
||||
obj.error = 'unmapped method call: ' + msg;
|
||||
|
@ -192,7 +189,6 @@ CustomSettingsApp.prototype = {
|
|||
// if message has an id, reply with the same message obj which now has a .result or .error field
|
||||
// note: a small delay is needed because of an apparent race condition between ScriptEngine and Tablet WebViews
|
||||
Script.setTimeout(_utils.bind(this, function() {
|
||||
debugPrint(obj.id, '########', JSON.stringify(obj));
|
||||
this.sendEvent(obj);
|
||||
}), 100);
|
||||
} else if (obj.error) {
|
||||
|
@ -200,28 +196,29 @@ CustomSettingsApp.prototype = {
|
|||
}
|
||||
},
|
||||
onWebEventReceived: function onWebEventReceived(msg) {
|
||||
// !/xSettings[.]getValue/.test(msg) &&
|
||||
_debugPrint('onWebEventReceived', msg);
|
||||
debugPrint('onWebEventReceived', msg);
|
||||
var tablet = this.tablet;
|
||||
if (!tablet) {
|
||||
throw new Error('onWebEventReceived called when not connected to tablet...');
|
||||
}
|
||||
if (msg === this.url) {
|
||||
if (this.isActive) {
|
||||
// user (or page) refreshed the web view; trigger !isActive so client script can perform cleanup
|
||||
this.isActiveChanged(this.isActive = false);
|
||||
}
|
||||
this.isActiveChanged(this.isActive = true);
|
||||
// reply to initial HTML page ACK with any extraParams that were specified
|
||||
this.sendEvent({ id: 'extraParams', extraParams: this.extraParams });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
var obj = JSON.parse(msg);
|
||||
var validSender = obj.ns === this.namespace && obj.uuid === this.uuid;
|
||||
if (validSender) {
|
||||
this._handleValidatedMessage(obj, msg);
|
||||
} else {
|
||||
debugPrint('xskipping', JSON.stringify([obj.ns, obj.uuid]), JSON.stringify(this), msg);
|
||||
}
|
||||
var obj = assert(JSON.parse(msg));
|
||||
} catch (e) {
|
||||
_debugPrint('rpc error:', e, msg);
|
||||
return;
|
||||
}
|
||||
if (obj.ns === this.namespace && obj.uuid === this.uuid) {
|
||||
debugPrint('valid onWebEventReceived', msg);
|
||||
this._handleValidatedMessage(obj, msg);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -235,45 +232,31 @@ CustomSettingsApp.prototype = {
|
|||
|
||||
this.onAPIValueUpdated = function(key, newValue, oldValue, origin) {
|
||||
if (this._activeSettings.remote[key] !== newValue) {
|
||||
_debugPrint(
|
||||
'[onAPIValueUpdated @ ' + origin + ']',
|
||||
key + ' = ' + JSON.stringify(newValue), '(was: ' + JSON.stringify(oldValue) +')',
|
||||
JSON.stringify(this._activeSettings.get(key)));
|
||||
_debugPrint('onAPIValueUpdated: ' + key + ' = ' + JSON.stringify(newValue),
|
||||
'(was: ' + JSON.stringify(oldValue) +')');
|
||||
this.syncValue(key, newValue, (origin ? origin+':' : '') + 'CustomSettingsApp.onAPIValueUpdated');
|
||||
}
|
||||
};
|
||||
this.isActiveChanged.connect(this, function(isActive) {
|
||||
debugPrint('============= CustomSettingsApp... isActiveChanged', JSON.stringify({
|
||||
isActive: isActive,
|
||||
interval: this.interval || 0,
|
||||
recheckInterval: this.recheckInterval
|
||||
},0,2));
|
||||
this._activeSettings.remote = {}; // reset assumptions about remote values
|
||||
isActive ? this.$startMonitor() : this.$stopMonitor();
|
||||
});
|
||||
|
||||
debugPrint('CustomSettingsApp...initialized', this.namespace);
|
||||
},
|
||||
|
||||
$syncSettings: function() {
|
||||
for (var p in this._activeSettings.sent) {
|
||||
var value = this.settingsAPI.getValue(p),
|
||||
var value = this._apiGetValue(p),
|
||||
lastValue = this._activeSettings.remote[p];
|
||||
if (value !== undefined && value !== null && value !== '' && value !== lastValue) {
|
||||
_debugPrint('CustomSettingsApp... detected external settings change', JSON.stringify({
|
||||
key: p,
|
||||
lastValueSent: (this._activeSettings.sent[p]+''),
|
||||
lastValueAssumed: (this._activeSettings.remote[p]+''),
|
||||
lastValueReceived: (this._activeSettings.received[p]+''),
|
||||
newValue: (value+'')
|
||||
}));
|
||||
_debugPrint('CustomSettingsApp... detected external settings change', p, value);
|
||||
this.syncValue(p, value, 'Settings');
|
||||
this.valueUpdated(p, value, lastValue, 'CustomSettingsApp.$syncSettings');
|
||||
}
|
||||
}
|
||||
},
|
||||
$startMonitor: function() {
|
||||
if (!(this.recheckInterval > 0)) { // expressed this way handles -1, NaN, null, undefined etc.
|
||||
if (!(this.recheckInterval > 0)) {
|
||||
_debugPrint('$startMonitor -- recheckInterval <= 0; not starting settings monitor thread');
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
}
|
||||
|
||||
var _utils = global._utils || (typeof require === 'function' && require('../../_utils.js'));
|
||||
|
||||
if (!_utils || !_utils.signal) {
|
||||
throw new Error('html.BridgedSettings.js -- expected _utils to be available on the global object (ie: window._utils)');
|
||||
}
|
||||
|
@ -44,7 +43,7 @@
|
|||
debug: options.debug,
|
||||
log: log.bind({}, options.namespace + ' |'),
|
||||
debugPrint: function() {
|
||||
return this.debug && this.log.apply(this, arguments);
|
||||
return this.debug && this.log.apply(this, arguments);
|
||||
},
|
||||
_boundScriptEventReceived: this.onScriptEventReceived.bind(this),
|
||||
callbacks: Object.defineProperties(options.callbacks || {}, {
|
||||
|
@ -59,7 +58,7 @@
|
|||
BridgedSettings.prototype = {
|
||||
_callbackId: 1,
|
||||
toString: function() {
|
||||
return '[BridgedSettings namespace='+this.namespace+']';
|
||||
return '[BridgedSettings namespace='+this.namespace+']';
|
||||
},
|
||||
resolve: function(key) {
|
||||
if (0 !== key.indexOf('.') && !~key.indexOf('/')) {
|
||||
|
@ -144,7 +143,7 @@
|
|||
key = this.resolve(key);
|
||||
var current = this.getValue(key);
|
||||
if (current !== value) {
|
||||
log('SET VALUE : ' + JSON.stringify({ key: key, current: current, value: value }));
|
||||
debugPrint('SET VALUE : ' + JSON.stringify({ key: key, current: current, value: value }));
|
||||
return this.syncValue(key, value, 'setValue');
|
||||
}
|
||||
this._hifiValues[key] = value;
|
||||
|
@ -184,5 +183,4 @@
|
|||
this.sendEvent(event);
|
||||
},
|
||||
};
|
||||
|
||||
})(this);
|
||||
|
|
|
@ -41,16 +41,15 @@
|
|||
boundOnDOMMutation: this._onDOMMutation.bind(this),
|
||||
}, options);
|
||||
}
|
||||
JQuerySettings.idCounter = 0;
|
||||
JQuerySettings.prototype = {
|
||||
toString: function() {
|
||||
return '[JQuerySettings namespace='+this.namespace+']';
|
||||
return '[JQuerySettings namespace='+this.namespace+']';
|
||||
},
|
||||
mutationConfig: {
|
||||
attributes: true,
|
||||
attributeOldValue: true,
|
||||
attributeFilter: [ 'value', 'checked', 'data-checked', 'data-value' ]
|
||||
// 'id', 'checked', 'min', 'max', 'step', 'focused', 'focus', 'active', 'data', 'for' ].reduce(function(out, key, index, arr) {
|
||||
// arr.push('data-'+key); return arr; }),
|
||||
},
|
||||
_onDOMMutation: function(mutations, observer) {
|
||||
mutations.forEach(function(mutation, index) {
|
||||
|
@ -61,7 +60,6 @@
|
|||
hifiType = target.dataset.hifiType,
|
||||
value = hifiType ? $(target)[hifiType]('instance').value() : attrValue,
|
||||
oldValue = mutation.oldValue;
|
||||
|
||||
var event = {
|
||||
key: this.getKey(targetId, true) || this.getKey(domId),
|
||||
value: value,
|
||||
|
@ -110,6 +108,7 @@
|
|||
}
|
||||
},
|
||||
registerSetting: function(id, key) {
|
||||
assert(id, 'registerSetting -- invalid id: ' + id + ' for key:' + key);
|
||||
this.id2Setting[id] = key;
|
||||
if (!(key in this.Setting2id)) {
|
||||
this.Setting2id[key] = id;
|
||||
|
@ -119,19 +118,23 @@
|
|||
debugPrint('JQuerySettings.registerSetting -- registered: ' + JSON.stringify({ id: id, key: key }));
|
||||
},
|
||||
registerNode: function(node) {
|
||||
// node.id = node.id || 'node-' + Math.round(Math.random()*1e20).toString(36);
|
||||
var id = node.id,
|
||||
key = this.resolve(node.dataset['for'] || node.id);
|
||||
this.registerSetting(id, key);
|
||||
var element = $(node),
|
||||
target = element.data('for') || element.attr('for') || element.prop('id');
|
||||
assert(target, 'registerNode could determine settings target: ' + node.outerHTML);
|
||||
if (!node.id) {
|
||||
node.id = ['id', target.replace(/[^-\w]/g,'-'), JQuerySettings.idCounter++ ].join('-');
|
||||
}
|
||||
var key = node.dataset['key'] = this.resolve(target);
|
||||
this.registerSetting(node.id, key);
|
||||
|
||||
debugPrint('registerNode', id, key);
|
||||
debugPrint('registerNode', node.id, target, key);
|
||||
// return this.observeNode(node);
|
||||
},
|
||||
// lookup the DOM id for a given Settings key
|
||||
getId: function(key, missingOk) {
|
||||
key = this.resolve(key);
|
||||
assert(missingOk || function assertion(){
|
||||
return typeof key === 'string';
|
||||
return typeof key === 'string';
|
||||
});
|
||||
if (key in this.Setting2id || missingOk) {
|
||||
return this.Setting2id[key];
|
||||
|
@ -141,7 +144,7 @@
|
|||
getAllNodes: function() {
|
||||
return Object.keys(this.Setting2id)
|
||||
.map(function(key) {
|
||||
return this.findNodeByKey(key);
|
||||
return this.findNodeByKey(key);
|
||||
}.bind(this))
|
||||
.filter(function(node) {
|
||||
return node.type !== 'placeholder';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// movement-utils.js -- helper classes that help manage related Controller.*Event and input API bindings for movement controls
|
||||
// movement-utils.js -- helper classes for managing related Controller.*Event and input API bindings
|
||||
|
||||
/* eslint-disable comma-dangle, no-empty */
|
||||
/* global require: true, DriveKeys, console, __filename, __dirname */
|
||||
|
@ -6,7 +6,7 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
version: '0.0.2c-0104',
|
||||
version: '0.0.2c',
|
||||
|
||||
CameraControls: CameraControls,
|
||||
MovementEventMapper: MovementEventMapper,
|
||||
|
@ -20,15 +20,13 @@ module.exports = {
|
|||
vec3eclamp: vec3eclamp,
|
||||
|
||||
DriveModes: {
|
||||
JITTER_TEST: 'jitter-test',
|
||||
POSITION: 'position', // ~ MyAvatar.position
|
||||
MOTOR: 'motor', // ~ MyAvatar.motorVelocity
|
||||
THRUST: 'thrust', // ~ MyAvatar.setThrust
|
||||
},
|
||||
};
|
||||
|
||||
var MAPPING_TEMPLATE = require('./movement-utils.mapping.json' + (__filename.match(/[?#].*$/)||[''])[0]);
|
||||
|
||||
var MAPPING_TEMPLATE = require('./movement-utils.mapping.json');
|
||||
var WANT_DEBUG = false;
|
||||
|
||||
function log() {
|
||||
|
@ -43,12 +41,10 @@ log(module.exports.version);
|
|||
var _utils = require('./_utils.js'),
|
||||
assert = _utils.assert;
|
||||
|
||||
if (1||WANT_DEBUG) {
|
||||
if (WANT_DEBUG) {
|
||||
require = _utils.makeDebugRequire(__dirname);
|
||||
_utils = require('./_utils.js'); // re-require in debug mode
|
||||
if (WANT_DEBUG) {
|
||||
debugPrint = log;
|
||||
}
|
||||
debugPrint = log;
|
||||
}
|
||||
|
||||
Object.assign = Object.assign || _utils.assign;
|
||||
|
@ -56,9 +52,10 @@ Object.assign = Object.assign || _utils.assign;
|
|||
var enumMeta = require('./EnumMeta.js');
|
||||
assert(enumMeta.version >= '0.0.1', 'enumMeta >= 0.0.1 expected but got: ' + enumMeta.version);
|
||||
|
||||
MovementEventMapper.CAPTURE_DRIVE_KEYS = 'drive-keys';
|
||||
MovementEventMapper.CAPTURE_ACTION_EVENTS = 'action-events';
|
||||
// MovementEventMapper.CAPTURE_KEYBOARD_EVENTS = 'key-events';
|
||||
Object.assign(MovementEventMapper, {
|
||||
CAPTURE_DRIVE_KEYS: 'drive-keys',
|
||||
CAPTURE_ACTION_EVENTS: 'action-events',
|
||||
});
|
||||
|
||||
function MovementEventMapper(options) {
|
||||
assert('namespace' in options, '.namespace expected ' + Object.keys(options) );
|
||||
|
@ -90,7 +87,7 @@ MovementEventMapper.prototype = {
|
|||
return event.actionValue;
|
||||
},
|
||||
getState: function(options) {
|
||||
var state = this.states ? this.states.getDriveKeys(options) : { };
|
||||
var state = this.states ? this.states.getDriveKeys(options) : {};
|
||||
|
||||
state.enabled = this.enabled;
|
||||
|
||||
|
@ -115,10 +112,10 @@ MovementEventMapper.prototype = {
|
|||
}
|
||||
},
|
||||
reset: function() {
|
||||
log(this.constructor.name, 'reset', JSON.stringify(Object.assign({}, this.options, { controllerMapping: undefined }),0,2));
|
||||
var enabled = this.enabled;
|
||||
enabled && this.disable();
|
||||
enabled && this.enable();
|
||||
if (this.enabled) {
|
||||
this.disable();
|
||||
this.enable();
|
||||
}
|
||||
},
|
||||
disable: function() {
|
||||
this.inputMapping.disable();
|
||||
|
@ -229,7 +226,7 @@ VirtualDriveKeys.prototype = {
|
|||
return driveKey in this ? this[driveKey] : defaultValue;
|
||||
},
|
||||
_defaultFilter: function(from, event) {
|
||||
return event.actionValue;
|
||||
return event.actionValue;
|
||||
},
|
||||
handleActionEvent: function(from, event) {
|
||||
var value = this.$eventFilter ? this.$eventFilter(from, event, this._defaultFilter) : event.actionValue;
|
||||
|
@ -279,21 +276,11 @@ VirtualDriveKeys.prototype = {
|
|||
y: this.getValue(DriveKeys.TRANSLATE_Y) || 0,
|
||||
z: this.getValue(DriveKeys.TRANSLATE_Z) || 0
|
||||
},
|
||||
step_translation: { // eslint-disable-line camelcase
|
||||
x: 'STEP_TRANSLATE_X' in DriveKeys && this.getValue(DriveKeys.STEP_TRANSLATE_X) || 0,
|
||||
y: 'STEP_TRANSLATE_Y' in DriveKeys && this.getValue(DriveKeys.STEP_TRANSLATE_Y) || 0,
|
||||
z: 'STEP_TRANSLATE_Z' in DriveKeys && this.getValue(DriveKeys.STEP_TRANSLATE_Z) || 0
|
||||
},
|
||||
rotation: {
|
||||
x: this.getValue(DriveKeys.PITCH) || 0,
|
||||
y: this.getValue(DriveKeys.YAW) || 0,
|
||||
z: 'ROLL' in DriveKeys && this.getValue(DriveKeys.ROLL) || 0
|
||||
},
|
||||
step_rotation: { // eslint-disable-line camelcase
|
||||
x: 'STEP_PITCH' in DriveKeys && this.getValue(DriveKeys.STEP_PITCH) || 0,
|
||||
y: 'STEP_YAW' in DriveKeys && this.getValue(DriveKeys.STEP_YAW) || 0,
|
||||
z: 'STEP_ROLL' in DriveKeys && this.getValue(DriveKeys.STEP_ROLL) || 0
|
||||
},
|
||||
zoom: Vec3.multiply(this.getValue(DriveKeys.ZOOM) || 0, Vec3.ONE)
|
||||
};
|
||||
} finally {
|
||||
|
@ -539,17 +526,16 @@ function vec3eclamp(velocity, epsilon, maxVelocity) {
|
|||
return velocity;
|
||||
}
|
||||
|
||||
function vec3damp(targetVelocity, velocity, drag) {
|
||||
// If force isn't being applied in a direction, incorporate drag;
|
||||
var dragEffect = {
|
||||
x: targetVelocity.x ? 0 : drag.x,
|
||||
y: targetVelocity.y ? 0 : drag.y,
|
||||
z: targetVelocity.z ? 0 : drag.z,
|
||||
function vec3damp(active, positiveEffect, negativeEffect) {
|
||||
// If force isn't being applied in a direction, incorporate negative effect (drag);
|
||||
negativeEffect = {
|
||||
x: active.x ? 0 : negativeEffect.x,
|
||||
y: active.y ? 0 : negativeEffect.y,
|
||||
z: active.z ? 0 : negativeEffect.z,
|
||||
};
|
||||
return Vec3.subtract(Vec3.sum(velocity, targetVelocity), dragEffect);
|
||||
return Vec3.subtract(Vec3.sum(active, positiveEffect), negativeEffect);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
function VelocityTracker(defaultValues) {
|
||||
Object.defineProperty(this, 'defaultValues', { configurable: true, value: defaultValues });
|
||||
|
@ -569,14 +555,14 @@ VelocityTracker.prototype = {
|
|||
return this._integrate.apply(this, [component].concat(args));
|
||||
},
|
||||
_integrate: function(component, targetState, currentVelocities, drag, settings) {
|
||||
assert(targetState[component], component + ' not found in targetState (which has: ' + Object.keys(targetState) + ')');
|
||||
var result = vec3damp(
|
||||
targetState[component],
|
||||
currentVelocities[component],
|
||||
drag[component]
|
||||
);
|
||||
var maxVelocity = settings[component].maxVelocity,
|
||||
epsilon = settings[component].epsilon;
|
||||
return this[component] = vec3eclamp(result, epsilon, maxVelocity);
|
||||
var maxVelocity = settings[component].maxVelocity;
|
||||
return this[component] = vec3eclamp(result, settings.epsilon, maxVelocity);
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -587,6 +573,7 @@ Object.assign(CameraControls, {
|
|||
ANIMATION_FRAME: 'requestAnimationFrame', // emulated
|
||||
NEXT_TICK: 'nextTick', // emulated
|
||||
SET_IMMEDIATE: 'setImmediate', // emulated
|
||||
//WORKER_THREAD: 'workerThread',
|
||||
});
|
||||
|
||||
function CameraControls(options) {
|
||||
|
@ -599,7 +586,7 @@ function CameraControls(options) {
|
|||
this.threadMode = options.threadMode;
|
||||
this.fps = options.fps || 60;
|
||||
this.getRuntimeSeconds = options.getRuntimeSeconds || function() {
|
||||
return +new Date / 1000.0;
|
||||
return +new Date / 1000.0;
|
||||
};
|
||||
this.backupOptions = _utils.DeferredUpdater.createGroup({
|
||||
MyAvatar: MyAvatar,
|
||||
|
@ -625,7 +612,7 @@ CameraControls.prototype = {
|
|||
this.$animate = this.update;
|
||||
Script.update.connect(this, '$animate');
|
||||
this.$animate.disconnect = _utils.bind(this, function() {
|
||||
Script.update.disconnect(this, '$animate');
|
||||
Script.update.disconnect(this, '$animate');
|
||||
});
|
||||
} break;
|
||||
|
||||
|
@ -664,7 +651,7 @@ CameraControls.prototype = {
|
|||
}
|
||||
this.update(this.getRuntimeSeconds(lastTime));
|
||||
lastTime = this.getRuntimeSeconds();
|
||||
this.$animate.timeout = Script.setTimeout(this.$animate, 1);
|
||||
this.$animate.timeout = Script.setTimeout(this.$animate, 0);
|
||||
});
|
||||
this.$animate.quit = false;
|
||||
this.$animate.disconnect = function() {
|
||||
|
@ -674,6 +661,7 @@ CameraControls.prototype = {
|
|||
};
|
||||
this.$animate();
|
||||
} break;
|
||||
|
||||
default: throw new Error('unknown threadMode: ' + this.threadMode);
|
||||
}
|
||||
log(
|
||||
|
@ -731,10 +719,7 @@ CameraControls.prototype = {
|
|||
|
||||
this.$start();
|
||||
|
||||
if (this.enabled !== true) {
|
||||
this.enabled = true;
|
||||
this.enabledChanged(this.enabled);
|
||||
}
|
||||
this.enabledChanged(this.enabled = true);
|
||||
},
|
||||
disable: function disable() {
|
||||
log("DISABLE CameraControls");
|
||||
|
@ -748,8 +733,7 @@ CameraControls.prototype = {
|
|||
this._restore();
|
||||
|
||||
if (this.enabled !== false) {
|
||||
this.enabled = false;
|
||||
this.enabledChanged(this.enabled);
|
||||
this.enabledChanged(this.enabled = false);
|
||||
}
|
||||
},
|
||||
_restore: function() {
|
||||
|
@ -769,6 +753,8 @@ CameraControls.prototype = {
|
|||
motorTimescale: MyAvatar.motorTimescale,
|
||||
motorReferenceFrame: MyAvatar.motorReferenceFrame,
|
||||
motorVelocity: Vec3.ZERO,
|
||||
velocity: Vec3.ZERO,
|
||||
angularVelocity: Vec3.ZERO,
|
||||
});
|
||||
},
|
||||
}; // CameraControls
|
||||
|
@ -777,61 +763,11 @@ CameraControls.prototype = {
|
|||
function applyEasing(deltaTime, direction, settings, state, scaling) {
|
||||
var obj = {};
|
||||
for (var p in scaling) {
|
||||
var group = settings[p],
|
||||
easeConst = group[direction],
|
||||
// multiplier = group.speed,
|
||||
var group = settings[p], // translation | rotation | zoom
|
||||
easeConst = group[direction], // easeIn | easeOut
|
||||
scale = scaling[p],
|
||||
stateVector = state[p];
|
||||
obj[p] = Vec3.multiply(easeConst * scale * deltaTime, stateVector);
|
||||
// var vec = obj[p]
|
||||
// vec.x *= multiplier.x;
|
||||
// vec.y *= multiplier.y;
|
||||
// vec.z *= multiplier.z;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// currently unused
|
||||
/*
|
||||
function normalizeDegrees(x) {
|
||||
while (x > 360) {
|
||||
x -= 360;
|
||||
}
|
||||
while (x < 0) {
|
||||
x += 360;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
function getEffectiveVelocities(deltaTime, collisionsEnabled, previousValues) {
|
||||
// use native values if available, otherwise compute a derivative over deltaTime
|
||||
var effectiveVelocity = collisionsEnabled ? MyAvatar.velocity : Vec3.multiply(Vec3.subtract(
|
||||
MyAvatar.position, previousValues.position
|
||||
), 1/deltaTime);
|
||||
var effectiveAngularVelocity = // collisionsEnabled ? MyAvatar.angularVelocity :
|
||||
Vec3.multiply(
|
||||
Quat.safeEulerAngles(Quat.multiply(
|
||||
MyAvatar.orientation,
|
||||
Quat.inverse(previousValues.orientation)
|
||||
)), 1/deltaTime);
|
||||
|
||||
effectiveVelocity = Vec3.multiplyQbyV(Quat.inverse(previousValues.orientation), effectiveVelocity);
|
||||
|
||||
effectiveAngularVelocity = {
|
||||
x: normalizeDegrees(effectiveAngularVelocity.x),
|
||||
y: normalizeDegrees(effectiveAngularVelocity.y),
|
||||
z: normalizeDegrees(effectiveAngularVelocity.z),
|
||||
};
|
||||
|
||||
return {
|
||||
velocity: effectiveVelocity,
|
||||
angularVelocity: effectiveAngularVelocity,
|
||||
};
|
||||
}
|
||||
function _isAvatarNearlyUpright(maxDegrees) {
|
||||
maxDegrees = maxDegrees || 5;
|
||||
return Math.abs(MyAvatar.headRoll) < maxDegrees && Math.abs(MyAvatar.bodyPitch) < maxDegrees;
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
"to": "Actions.StepYaw",
|
||||
"filters":
|
||||
[
|
||||
{ "type": "invert" },
|
||||
"invert",
|
||||
{ "type": "pulse", "interval": 0.5, "resetOnZero": true },
|
||||
{ "type": "scale", "scale": 22.5 }
|
||||
]
|
||||
|
|
Loading…
Reference in a new issue