mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
split into smaller modules for review
This commit is contained in:
parent
16170cd754
commit
c8d503f717
16 changed files with 1970 additions and 1484 deletions
|
@ -1,11 +1,28 @@
|
|||
_debug = {
|
||||
/* eslint-env jquery, browser */
|
||||
/* eslint-disable comma-dangle, no-empty */
|
||||
/* global EventBridge: true, PARAMS, signal, assert, log, debugPrint,
|
||||
bridgedSettings, _utils, jquerySettings, */
|
||||
var _debug = {
|
||||
handleUncaughtException: function onerror(message, fileName, lineNumber, colNumber, err) {
|
||||
var output = _utils.normalizeStackTrace(err || { message: message });
|
||||
console.error('window.onerror: ' + output, err);
|
||||
if (message === onerror.lastMessage) {
|
||||
return;
|
||||
}
|
||||
onerror.lastMessage = message;
|
||||
var error = (err || Error.lastError);
|
||||
// var stack = error && error.stack;
|
||||
var output = _utils.normalizeStackTrace(error || { message: message });
|
||||
window.console.error(['window.onerror: ', output, message]); // eslint-disable-line no-console
|
||||
var errorNode = document.querySelector('#errors'),
|
||||
textNode = errorNode && errorNode.querySelector('.output');
|
||||
if (textNode) textNode.innerText = output;
|
||||
if (errorNode) errorNode.style.display = 'block';
|
||||
if (textNode) {
|
||||
textNode.innerText = output;
|
||||
}
|
||||
if (errorNode) {
|
||||
errorNode.style.display = 'block';
|
||||
}
|
||||
if (error){
|
||||
error.onerrored = true;
|
||||
}
|
||||
},
|
||||
loadScriptNodes: function loadScriptNodes(selector) {
|
||||
// scripts are loaded this way to ensure refreshing the client script refreshes dependencies too
|
||||
|
@ -20,6 +37,7 @@ _debug = {
|
|||
},
|
||||
// TESTING MOCK (allows the UI to be tested using a normal web browser, outside of Interface
|
||||
openEventBridgeMock: function openEventBridgeMock(onEventBridgeOpened) {
|
||||
var updatedValues = openEventBridgeMock.updatedValues = {};
|
||||
// emulate EventBridge's API
|
||||
EventBridge = {
|
||||
emitWebEvent: signal(function emitWebEvent(message){}),
|
||||
|
@ -27,63 +45,77 @@ _debug = {
|
|||
};
|
||||
EventBridge.emitWebEvent.connect(onEmitWebEvent);
|
||||
onEventBridgeOpened(EventBridge);
|
||||
setTimeout(function() {
|
||||
assert(!bridgedSettings.onUnhandledMessage);
|
||||
bridgedSettings.onUnhandledMessage = function(msg) {
|
||||
return true;
|
||||
};
|
||||
// manually trigger bootstrapping responses
|
||||
$('.slider .control').parent().css('visibility','visible');
|
||||
bridgedSettings.handleExtraParams({uuid: PARAMS.uuid, ns: PARAMS.ns, extraParams: {
|
||||
mock: true,
|
||||
appVersion: 'browsermock',
|
||||
toggleKey: { text: 'SPACE', isShifted: true },
|
||||
} });
|
||||
bridgedSettings.setValue('ui-show-advanced-options', true);
|
||||
if (/fps/.test(location.hash)) setTimeout(function() { $('#fps').each(function(){ this.scrollIntoView(); }); }, 100);
|
||||
},1);
|
||||
assert(!bridgedSettings.onUnhandledMessage);
|
||||
bridgedSettings.onUnhandledMessage = function(msg) {
|
||||
if (1||!msg || msg.method !== 'valueUpdated') {
|
||||
log('bridgedSettings.onUnhandledMessage', msg);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
// manually trigger bootstrapping responses
|
||||
bridgedSettings.handleExtraParams({uuid: PARAMS.uuid, ns: PARAMS.ns, extraParams: {
|
||||
mock: true,
|
||||
appVersion: 'browsermock',
|
||||
toggleKey: { text: 'SPACE', isShifted: true },
|
||||
mode: {
|
||||
toolbar: true,
|
||||
browser: true,
|
||||
desktop: true,
|
||||
tablet: false,
|
||||
hmd: false,
|
||||
},
|
||||
} });
|
||||
bridgedSettings.setValue('ui-show-advanced-options', true);
|
||||
|
||||
function log(msg) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log.apply(console, ['[mock] ' + msg].concat([].slice.call(arguments,1)));
|
||||
}
|
||||
|
||||
var updatedValues = {};
|
||||
// generate mock data in response to outgoing web events
|
||||
function onEmitWebEvent(message) {
|
||||
try { var obj = JSON.parse(message); } catch(e) {}
|
||||
try {
|
||||
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
|
||||
log('consuming non-callback web event', message);
|
||||
return;
|
||||
}
|
||||
switch(obj.method) {
|
||||
switch (obj.method) {
|
||||
case 'valueUpdated': {
|
||||
log('valueUpdated',obj.params);
|
||||
updatedValues[obj.params[0]] = obj.params[1];
|
||||
} break;
|
||||
return;
|
||||
}
|
||||
case 'Settings.getValue': {
|
||||
var key = obj.params[0];
|
||||
var node = jquerySettings.findNodeByKey(key, true);
|
||||
var type = node && (node.dataset.type || node.getAttribute('type'));
|
||||
switch(type) {
|
||||
// log('Settings.getValue.findNodeByKey', key, node);
|
||||
var type = node && (node.dataset.type || node.type || node.attributes['type']);
|
||||
switch (type) {
|
||||
case 'checkbox': {
|
||||
obj.result = /tooltip/i.test(key) || PARAMS.tooltiptest ? true : Math.random() > .5;
|
||||
obj.result = /tooltip/i.test(key) || PARAMS.tooltiptest ? true : Math.random() > 0.5;
|
||||
} break;
|
||||
case 'radio-group': {
|
||||
var radios = $(node).find('input[type=radio]').toArray();
|
||||
while(Math.random() < .9) { radios.push(radios.shift()); }
|
||||
while (Math.random() < 0.9) {
|
||||
radios.push(radios.shift());
|
||||
}
|
||||
obj.result = radios[0].value;
|
||||
} break;
|
||||
case 'spinner':
|
||||
case 'number': {
|
||||
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));
|
||||
} break;
|
||||
default: {
|
||||
log('unhandled node type for making dummy data: ' + [key, node && node.type, type, node && node.getAttribute('type')] + ' @ ' + (node && node.id));
|
||||
log('unhandled node type for making dummy data: ' + [key, node && node.type, type, node && node.type] + ' @ ' + (node && node.id));
|
||||
obj.result = updatedValues[key] || false;
|
||||
} break;
|
||||
}
|
||||
log('mock getValue data %c%s = %c%s', 'color:blue',
|
||||
debugPrint('mock getValue data %c%s = %c%s', 'color:blue',
|
||||
JSON.stringify(key), 'color:green', JSON.stringify(obj.result));
|
||||
} break;
|
||||
default: {
|
||||
|
|
128
unpublishedScripts/marketplace/camera-move/_json-persist.js
Normal file
128
unpublishedScripts/marketplace/camera-move/_json-persist.js
Normal file
|
@ -0,0 +1,128 @@
|
|||
/* eslint-env jquery, browser */
|
||||
/* eslint-disable comma-dangle, no-empty */
|
||||
/* global _utils, PARAMS, VERSION, signal, assert, log, debugPrint,
|
||||
bridgedSettings, POPUP */
|
||||
|
||||
// JSON export / import helpers proto module
|
||||
var SettingsJSON = (function() {
|
||||
_utils.exists;
|
||||
assert.exists;
|
||||
|
||||
return {
|
||||
setPath: setPath,
|
||||
rollupPaths: rollupPaths,
|
||||
encodeNodes: encodeNodes,
|
||||
exportAll: exportAll,
|
||||
showSettings: showSettings,
|
||||
applyJSON: applyJSON,
|
||||
promptJSON: promptJSON,
|
||||
popupJSON: popupJSON,
|
||||
};
|
||||
|
||||
function encodeNodes(resolver) {
|
||||
return resolver.getAllNodes().reduce((function(out, input, i) {
|
||||
debugPrint('input['+i+']', input.id);
|
||||
var id = input.id,
|
||||
key = resolver.getKey(id);
|
||||
debugPrint('toJSON', id, key, input.id);
|
||||
setPath(out, key.split('/'), resolver.getValue(key));
|
||||
return out;
|
||||
}).bind(this), {});
|
||||
}
|
||||
|
||||
function setPath(obj, path, value) {
|
||||
var key = path.pop();
|
||||
obj = path.reduce(function(obj, subkey) {
|
||||
return obj[subkey] = obj[subkey] || {};
|
||||
}, obj);
|
||||
debugPrint('setPath', key, Object.keys(obj));
|
||||
obj[key] = value;
|
||||
}
|
||||
|
||||
function rollupPaths(obj, output, path) {
|
||||
path = path || [];
|
||||
output = output || {};
|
||||
// log('rollupPaths', Object.keys(obj||{}), Object.keys(output), path);
|
||||
for (var p in obj) {
|
||||
path.push(p);
|
||||
var value = obj[p];
|
||||
if (value && typeof value === 'object') {
|
||||
rollupPaths(obj[p], output, path);
|
||||
} else {
|
||||
output[path.join('/')] = value;
|
||||
}
|
||||
path.pop();
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
function exportAll(resolver, name) {
|
||||
var settings = encodeNodes(resolver);
|
||||
Object.keys(settings).forEach(function(prop) {
|
||||
if (typeof settings[prop] === 'object') {
|
||||
_utils.sortedAssign(settings[prop]);
|
||||
}
|
||||
});
|
||||
return {
|
||||
version: VERSION,
|
||||
name: name || undefined,
|
||||
settings: settings,
|
||||
_metadata: { timestamp: new Date(), PARAMS: PARAMS, url: location.href, }
|
||||
};
|
||||
}
|
||||
|
||||
function showSettings(resolver, saveName) {
|
||||
popupJSON(saveName || '(current settings)', Object.assign(exportAll(resolver, saveName), {
|
||||
extraParams: bridgedSettings.extraParams,
|
||||
}));
|
||||
}
|
||||
|
||||
function popupJSON(title, tmp) {
|
||||
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') {
|
||||
bridgedSettings.sendEvent({
|
||||
method: 'overlayWebWindow',
|
||||
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"
|
||||
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));
|
||||
var settings = rollupPaths(tmp.settings);
|
||||
for (var p in settings) {
|
||||
if (/^[.]/.test(p)) {
|
||||
continue;
|
||||
}
|
||||
var key = resolver.getId(p, true);
|
||||
if (!key) {
|
||||
log('$applySettings -- skipping unregistered Settings key: ', p);
|
||||
} else {
|
||||
resolver.setValue(p, settings[p], name+'.settings.'+p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function promptJSON() {
|
||||
var json = window.prompt('(paste JSON here)', '');
|
||||
if (!json) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
json = JSON.parse(json);
|
||||
} catch (e) {
|
||||
throw new Error('Could not parse pasted JSON: ' + e + '\n\n' + (json||'').replace(/</g,'<'));
|
||||
}
|
||||
return json;
|
||||
}
|
||||
})(this);
|
||||
|
63
unpublishedScripts/marketplace/camera-move/_less-utils.js
Normal file
63
unpublishedScripts/marketplace/camera-move/_less-utils.js
Normal file
|
@ -0,0 +1,63 @@
|
|||
/* eslint-env jquery, browser */
|
||||
/* eslint-disable comma-dangle */
|
||||
/* global PARAMS, signal, assert, log, debugPrint */
|
||||
|
||||
function preconfigureLESS(options) {
|
||||
assert(function assertion() {
|
||||
return options.selector && options.globalVars;
|
||||
});
|
||||
|
||||
options.globalVars = Object.assign(options.globalVars||{}, { hash: '' });
|
||||
Object.assign(options, {
|
||||
// poll: 1000,
|
||||
// watch: true,
|
||||
});
|
||||
var lessManager = {
|
||||
options: options,
|
||||
onViewportUpdated: onViewportUpdated,
|
||||
elements: $(options.selector).remove(),
|
||||
};
|
||||
|
||||
return lessManager;
|
||||
|
||||
function onViewportUpdated(viewport) {
|
||||
var globalVars = options.globalVars,
|
||||
less = window.less;
|
||||
if (onViewportUpdated.to) {
|
||||
clearTimeout(onViewportUpdated.to);
|
||||
onViewportUpdated.to = 0;
|
||||
} else if (globalVars.hash) {
|
||||
onViewportUpdated.to = setTimeout(onViewportUpdated.bind(this, viewport), 500); // debounce
|
||||
return;
|
||||
}
|
||||
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,
|
||||
'client-height': viewport.client.height,
|
||||
'hash': '',
|
||||
});
|
||||
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']);
|
||||
if (onViewportUpdated.lastHash !== hash) {
|
||||
debugPrint('updating lessVars', 'less.modifyVars:' + typeof less.modifyVars, JSON.stringify(globalVars, 0, 2));
|
||||
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
|
||||
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);
|
||||
})['catch'](function(err) {
|
||||
log('less ERROR:', err);
|
||||
});
|
||||
var oldNodes = onViewportUpdated.lastNodes;
|
||||
oldNodes && oldNodes.remove();
|
||||
onViewportUpdated.lastNodes = newNodes;
|
||||
}
|
||||
onViewportUpdated.lastHash = hash;
|
||||
}
|
||||
}
|
147
unpublishedScripts/marketplace/camera-move/_tooltips.js
Normal file
147
unpublishedScripts/marketplace/camera-move/_tooltips.js
Normal file
|
@ -0,0 +1,147 @@
|
|||
/* eslint-env jquery, browser */
|
||||
/* eslint-disable comma-dangle, no-empty */
|
||||
/* global PARAMS, signal, assert, log, debugPrint */
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// manage jquery-tooltipster hover tooltips
|
||||
var TooltipManager = (function(global) {
|
||||
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',
|
||||
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();
|
||||
},
|
||||
});
|
||||
}
|
||||
return TooltipManager;
|
||||
})(this);
|
|
@ -9,7 +9,7 @@
|
|||
// This Client script sets up the Camera Control Tablet App, which can be used to configure and
|
||||
// drive your avatar with easing/smoothing movement constraints for a less jittery filming experience.
|
||||
|
||||
/* eslint-disable comma-dangle */
|
||||
/* eslint-disable comma-dangle, no-empty */
|
||||
"use strict";
|
||||
|
||||
var VERSION = '0.0.0d',
|
||||
|
@ -19,7 +19,7 @@ var VERSION = '0.0.0d',
|
|||
text: '\nCam Drive',
|
||||
icon: Script.resolvePath('Eye-Camera.svg'),
|
||||
},
|
||||
DEFAULT_TOGGLE_KEY = '{ "text": "SPACE" }';
|
||||
DEFAULT_TOGGLE_KEY = { text: 'SPACE' };
|
||||
|
||||
var MINIMAL_CURSOR_SCALE = 0.5,
|
||||
FILENAME = Script.resolvePath(''),
|
||||
|
@ -27,10 +27,7 @@ var MINIMAL_CURSOR_SCALE = 0.5,
|
|||
false || (FILENAME.match(/[&#?]debug[=](\w+)/)||[])[1] ||
|
||||
Settings.getValue(NAMESPACE + '/debug')
|
||||
),
|
||||
EPSILON = 1e-6,
|
||||
DEG_TO_RAD = Math.PI / 180.0;
|
||||
|
||||
WANT_DEBUG = 1;
|
||||
EPSILON = 1e-6;
|
||||
|
||||
function log() {
|
||||
print( NAMESPACE + ' | ' + [].slice.call(arguments).join(' ') );
|
||||
|
@ -42,6 +39,7 @@ var require = Script.require,
|
|||
overlayDebugOutput = function(){};
|
||||
|
||||
if (WANT_DEBUG) {
|
||||
log('WANT_DEBUG is true; instrumenting debug rigging', WANT_DEBUG);
|
||||
_instrumentDebugValues();
|
||||
}
|
||||
|
||||
|
@ -49,7 +47,9 @@ var _utils = require('./modules/_utils.js'),
|
|||
assert = _utils.assert,
|
||||
CustomSettingsApp = require('./modules/custom-settings-app/CustomSettingsApp.js'),
|
||||
movementUtils = require('./modules/movement-utils.js?'+ +new Date),
|
||||
configUtils = require('./modules/config-utils.js');
|
||||
configUtils = require('./modules/config-utils.js'),
|
||||
AvatarUpdater = require('./avatar-updater.js');
|
||||
|
||||
|
||||
Object.assign = Object.assign || _utils.assign;
|
||||
|
||||
|
@ -78,11 +78,13 @@ var DEFAULTS = {
|
|||
'rotation-x-speed': 45,
|
||||
'rotation-y-speed': 60,
|
||||
'rotation-z-speed': 1,
|
||||
'rotation-mouse-multiplier': 1.0,
|
||||
'rotation-keyboard-multiplier': 1.0,
|
||||
'mouse-multiplier': 1.0,
|
||||
'keyboard-multiplier': 1.0,
|
||||
|
||||
'ui-enable-tooltips': true,
|
||||
'ui-show-advanced-options': false,
|
||||
|
||||
// 'toggle-key': DEFAULT_TOGGLE_KEY,
|
||||
};
|
||||
|
||||
// map setting names to/from corresponding Menu and API properties
|
||||
|
@ -117,9 +119,19 @@ 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 = {
|
||||
// these values are also sent to the tablet app after EventBridge initialization
|
||||
appVersion: VERSION,
|
||||
|
@ -157,9 +169,9 @@ var globalState = {
|
|||
// current input controls' effective velocities
|
||||
currentVelocities: new movementUtils.VelocityTracker({
|
||||
translation: Vec3.ZERO,
|
||||
step_translation: Vec3.ZERO,
|
||||
step_translation: Vec3.ZERO, // eslint-disable-line camelcase
|
||||
rotation: Vec3.ZERO,
|
||||
step_rotation: Vec3.ZERO,
|
||||
step_rotation: Vec3.ZERO, // eslint-disable-line camelcase
|
||||
zoom: Vec3.ZERO,
|
||||
}),
|
||||
};
|
||||
|
@ -186,9 +198,11 @@ function main() {
|
|||
defaultValues: DEFAULTS,
|
||||
});
|
||||
|
||||
var toggleKey = JSON.parse(DEFAULT_TOGGLE_KEY);
|
||||
var toggleKey = DEFAULT_TOGGLE_KEY;
|
||||
if (cameraConfig.getValue('toggle-key')) {
|
||||
try { toggleKey = JSON.parse(cameraConfig.getValue('toggle-key')); } catch(e) {}
|
||||
try {
|
||||
toggleKey = JSON.parse(cameraConfig.getValue('toggle-key'));
|
||||
} catch (e) {}
|
||||
}
|
||||
// set up a monitor to observe configuration changes between the two sources
|
||||
var MONITOR_INTERVAL_MS = 1000;
|
||||
|
@ -219,11 +233,11 @@ function main() {
|
|||
});
|
||||
|
||||
settingsApp.onUnhandledMessage = function(msg) {
|
||||
switch(msg.method) {
|
||||
switch (msg.method) {
|
||||
case 'window.close': {
|
||||
this.toggle(false);
|
||||
} break;
|
||||
case 'reloadClientScript': {
|
||||
case 'reloadClientScript': {
|
||||
log('reloadClientScript...');
|
||||
_utils.reloadClientScript(FILENAME);
|
||||
} break;
|
||||
|
@ -236,10 +250,9 @@ function main() {
|
|||
}, 500);
|
||||
} break;
|
||||
case 'reset': {
|
||||
//if (!Window.confirm('Reset all camera move settings to system defaults?')) {
|
||||
// if (!Window.confirm('Reset all camera move settings to system defaults?')) {
|
||||
// return;
|
||||
//}
|
||||
var novalue = Uuid.generate();
|
||||
// }
|
||||
var resetValues = {};
|
||||
Object.keys(DEFAULTS).reduce(function(out, key) {
|
||||
var resolved = cameraConfig.resolve(key);
|
||||
|
@ -259,7 +272,7 @@ function main() {
|
|||
}
|
||||
} break;
|
||||
case 'overlayWebWindow': {
|
||||
_overlayWebWindow(msg.options);
|
||||
_utils._overlayWebWindow(msg.options);
|
||||
} break;
|
||||
default: {
|
||||
log('onUnhandledMessage', JSON.stringify(msg,0,2));
|
||||
|
@ -273,8 +286,8 @@ function main() {
|
|||
namespace: DEFAULTS.namespace,
|
||||
mouseSmooth: cameraConfig.getValue('enable-mouse-smooth'),
|
||||
xexcludeNames: [ 'Keyboard.C', 'Keyboard.E', 'Actions.TranslateY' ],
|
||||
mouseMultiplier: cameraConfig.getValue('rotation-mouse-multiplier'),
|
||||
keyboardMultiplier: cameraConfig.getValue('rotation-keyboard-multiplier'),
|
||||
mouseMultiplier: cameraConfig.getValue('mouse-multiplier'),
|
||||
keyboardMultiplier: cameraConfig.getValue('keyboard-multiplier'),
|
||||
eventFilter: function eventFilter(from, event, defaultFilter) {
|
||||
var result = defaultFilter(from, event),
|
||||
driveKeyName = event.driveKeyName;
|
||||
|
@ -299,15 +312,21 @@ function main() {
|
|||
Script.scriptEnding.connect(eventMapper, 'disable');
|
||||
applicationConfig.register({
|
||||
'enable-mouse-smooth': { object: [ eventMapper.options, 'mouseSmooth' ] },
|
||||
'rotation-keyboard-multiplier': { object: [ eventMapper.options, 'keyboardMultiplier' ] },
|
||||
'rotation-mouse-multiplier': { object: [ eventMapper.options, 'mouseMultiplier' ] },
|
||||
'keyboard-multiplier': { object: [ eventMapper.options, 'keyboardMultiplier' ] },
|
||||
'mouse-multiplier': { object: [ eventMapper.options, 'mouseMultiplier' ] },
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// set up the top-level camera controls manager / animator
|
||||
var avatarUpdater = new AvatarUpdater({
|
||||
debugChannel: _debugChannel,
|
||||
globalState: globalState,
|
||||
getCameraMovementSettings: getCameraMovementSettings,
|
||||
getMovementState: _utils.bind(eventMapper, 'getState'),
|
||||
});
|
||||
cameraControls = new movementUtils.CameraControls({
|
||||
namespace: DEFAULTS.namespace,
|
||||
update: update,
|
||||
update: avatarUpdater,
|
||||
threadMode: cameraConfig.getValue('thread-update-mode'),
|
||||
fps: cameraConfig.getValue('fps'),
|
||||
getRuntimeSeconds: _utils.getRuntimeSeconds,
|
||||
|
@ -358,7 +377,7 @@ function main() {
|
|||
function updateButtonText() {
|
||||
var lines = [
|
||||
settingsApp.isActive ? '(app open)' : '',
|
||||
cameraControls.enabled ? (update.momentaryFPS||0).toFixed(2) + 'fps' : BUTTON_CONFIG.text.trim()
|
||||
cameraControls.enabled ? (avatarUpdater.update.momentaryFPS||0).toFixed(2) + 'fps' : BUTTON_CONFIG.text.trim()
|
||||
];
|
||||
button && button.editProperties({ text: lines.join('\n') });
|
||||
}
|
||||
|
@ -375,15 +394,15 @@ function main() {
|
|||
fpsTimeout = 0;
|
||||
}
|
||||
eventMapper.disable();
|
||||
_resetMyAvatarMotor({ MyAvatar: MyAvatar });
|
||||
avatarUpdater._resetMyAvatarMotor({ MyAvatar: MyAvatar });
|
||||
updateButtonText();
|
||||
}
|
||||
cameraConfig.getValue('debug') && Overlays.editOverlay(overlayDebugOutput.overlayID, { visible: enabled });
|
||||
overlayDebugOutput.overlayID && Overlays.editOverlay(overlayDebugOutput.overlayID, { visible: enabled });
|
||||
});
|
||||
|
||||
var resetIfChanged = [
|
||||
'minimal-cursor', 'drive-mode', 'fps', 'thread-update-mode',
|
||||
'rotation-mouse-multiplier', 'rotation-keyboard-multiplier',
|
||||
'mouse-multiplier', 'keyboard-multiplier',
|
||||
'enable-mouse-smooth', 'constant-delta-time',
|
||||
].filter(Boolean).map(_utils.bind(cameraConfig, 'resolve'));
|
||||
|
||||
|
@ -430,7 +449,7 @@ function onCameraControlsEnabled() {
|
|||
Reticle.scale = MINIMAL_CURSOR_SCALE;
|
||||
}
|
||||
log('cameraConfig', JSON.stringify({
|
||||
cameraConfig: getCameraMovementSettings(cameraConfig),
|
||||
cameraConfig: getCameraMovementSettings(),
|
||||
//DEFAULTS: DEFAULTS
|
||||
}));
|
||||
}
|
||||
|
@ -463,20 +482,22 @@ function onCameraModeChanged(mode, oldMode) {
|
|||
}
|
||||
|
||||
// consolidate and normalize cameraConfig settings
|
||||
function getCameraMovementSettings(cameraConfig) {
|
||||
function getCameraMovementSettings() {
|
||||
return {
|
||||
epsilon: EPSILON,
|
||||
debug: cameraConfig.getValue('debug'),
|
||||
driveMode: cameraConfig.getValue('drive-mode'),
|
||||
threadMode: cameraConfig.getValue('thread-update-mode'),
|
||||
fps: cameraConfig.getValue('fps'),
|
||||
useHead: cameraConfig.getValue('use-head'),
|
||||
stayGrounded: cameraConfig.getValue('stay-grounded'),
|
||||
preventRoll: cameraConfig.getValue('prevent-roll'),
|
||||
useConstantDeltaTime: cameraConfig.getValue('constant-delta-time'),
|
||||
|
||||
collisionsEnabled: applicationConfig.getValue('Avatar/Enable Avatar Collisions'),
|
||||
mouseSmooth: cameraConfig.getValue('enable-mouse-smooth'),
|
||||
mouseMultiplier: cameraConfig.getValue('rotation-mouse-multiplier'),
|
||||
keyboardMultiplier: cameraConfig.getValue('rotation-keyboard-multiplier'),
|
||||
mouseMultiplier: cameraConfig.getValue('mouse-multiplier'),
|
||||
keyboardMultiplier: cameraConfig.getValue('keyboard-multiplier'),
|
||||
|
||||
rotation: _getEasingGroup(cameraConfig, 'rotation'),
|
||||
translation: _getEasingGroup(cameraConfig, 'translation'),
|
||||
|
@ -489,12 +510,8 @@ function getCameraMovementSettings(cameraConfig) {
|
|||
if (group === 'zoom') {
|
||||
// BoomIn / TranslateCameraZ support is only partially plumbed -- for now use scaled translation easings
|
||||
group = 'translation';
|
||||
multiplier = 0.01;
|
||||
} else if (group === 'rotation') {
|
||||
// degrees -> radians
|
||||
//multiplier = DEG_TO_RAD;
|
||||
multiplier = 0.001;
|
||||
}
|
||||
|
||||
return {
|
||||
easeIn: cameraConfig.getFloat(group + '-ease-in'),
|
||||
easeOut: cameraConfig.getFloat(group + '-ease-out'),
|
||||
|
@ -508,232 +525,7 @@ function getCameraMovementSettings(cameraConfig) {
|
|||
}
|
||||
}
|
||||
|
||||
var DEFAULT_MOTOR_TIMESCALE = 1e6; // a large value that matches Interface's default
|
||||
var EASED_MOTOR_TIMESCALE = 0.01; // a small value to make Interface quickly apply MyAvatar.motorVelocity
|
||||
var EASED_MOTOR_THRESHOLD = 0.1; // above this speed (m/s) EASED_MOTOR_TIMESCALE is used
|
||||
var ACCELERATION_MULTIPLIERS = { translation: 1, rotation: 1, zoom: 1 };
|
||||
var STAYGROUNDED_PITCH_THRESHOLD = 45.0; // degrees; ground level is maintained when pitch is within this threshold
|
||||
var MIN_DELTA_TIME = 0.0001; // to avoid math overflow, never consider dt less than this value
|
||||
|
||||
update.frameCount = 0;
|
||||
update.endTime = _utils.getRuntimeSeconds();
|
||||
|
||||
function update(dt) {
|
||||
update.frameCount++;
|
||||
var startTime = _utils.getRuntimeSeconds();
|
||||
var settings = getCameraMovementSettings(cameraConfig);
|
||||
|
||||
var collisions = applicationConfig.getValue('Avatar/Enable Avatar Collisions'),
|
||||
independentCamera = Camera.mode === 'independent',
|
||||
headPitch = MyAvatar.headPitch;
|
||||
|
||||
var actualDeltaTime = Math.max(MIN_DELTA_TIME, (startTime - update.endTime)),
|
||||
deltaTime;
|
||||
|
||||
if (settings.useConstantDeltaTime) {
|
||||
deltaTime = settings.threadMode === movementUtils.CameraControls.ANIMATION_FRAME ?
|
||||
(1 / cameraControls.fps) : (1 / 90);
|
||||
} else if (settings.threadMode === movementUtils.CameraControls.SCRIPT_UPDATE) {
|
||||
deltaTime = dt;
|
||||
} else {
|
||||
deltaTime = actualDeltaTime;
|
||||
}
|
||||
|
||||
var orientationProperty = settings.useHead ? 'headOrientation' : 'orientation',
|
||||
currentOrientation = independentCamera ? Camera.orientation : MyAvatar[orientationProperty],
|
||||
currentPosition = MyAvatar.position;
|
||||
|
||||
var previousValues = globalState.previousValues,
|
||||
pendingChanges = globalState.pendingChanges,
|
||||
currentVelocities = globalState.currentVelocities;
|
||||
|
||||
var movementState = eventMapper.getState({ update: deltaTime }),
|
||||
targetState = movementUtils.applyEasing(deltaTime, 'easeIn', settings, movementState, ACCELERATION_MULTIPLIERS),
|
||||
dragState = movementUtils.applyEasing(deltaTime, 'easeOut', settings, currentVelocities, ACCELERATION_MULTIPLIERS);
|
||||
|
||||
currentVelocities.integrate(targetState, currentVelocities, dragState, settings);
|
||||
|
||||
var currentSpeed = Vec3.length(currentVelocities.translation),
|
||||
targetSpeed = Vec3.length(movementState.translation),
|
||||
verticalHold = movementState.isGrounded && settings.stayGrounded && Math.abs(headPitch) < STAYGROUNDED_PITCH_THRESHOLD;
|
||||
|
||||
var deltaOrientation = Quat.fromVec3Degrees(Vec3.multiply(deltaTime, currentVelocities.rotation)),
|
||||
targetOrientation = Quat.normalize(Quat.multiply(currentOrientation, deltaOrientation));
|
||||
|
||||
var targetVelocity = Vec3.multiplyQbyV(targetOrientation, currentVelocities.translation);
|
||||
|
||||
if (verticalHold) {
|
||||
targetVelocity.y = 0;
|
||||
}
|
||||
|
||||
var deltaPosition = Vec3.multiply(deltaTime, targetVelocity);
|
||||
|
||||
_resetMyAvatarMotor(pendingChanges);
|
||||
|
||||
if (!independentCamera) {
|
||||
var DriveModes = movementUtils.DriveModes;
|
||||
switch(settings.driveMode) {
|
||||
case DriveModes.MOTOR: {
|
||||
if (currentSpeed > EPSILON || targetSpeed > EPSILON) {
|
||||
var motorTimescale = (currentSpeed > EASED_MOTOR_THRESHOLD ? EASED_MOTOR_TIMESCALE : DEFAULT_MOTOR_TIMESCALE);
|
||||
var motorPitch = Quat.fromPitchYawRollDegrees(headPitch, 180, 0),
|
||||
motorVelocity = Vec3.multiplyQbyV(motorPitch, currentVelocities.translation);
|
||||
if (verticalHold) {
|
||||
motorVelocity.y = 0;
|
||||
}
|
||||
Object.assign(pendingChanges.MyAvatar, {
|
||||
motorVelocity: motorVelocity,
|
||||
motorTimescale: motorTimescale,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DriveModes.THRUST: {
|
||||
var thrustVector = currentVelocities.translation,
|
||||
maxThrust = settings.translation.maxVelocity,
|
||||
thrust;
|
||||
if (targetSpeed > EPSILON) {
|
||||
thrust = movementUtils.calculateThrust(maxThrust * 5, thrustVector, previousValues.thrust);
|
||||
} else if (currentSpeed > 1 && Vec3.length(previousValues.thrust) > 1) {
|
||||
thrust = Vec3.multiply(-currentSpeed / 10.0, thrustVector);
|
||||
} else {
|
||||
thrust = Vec3.ZERO;
|
||||
}
|
||||
if (thrust) {
|
||||
thrust = Vec3.multiplyQbyV(MyAvatar[orientationProperty], thrust);
|
||||
if (verticalHold) {
|
||||
thrust.y = 0;
|
||||
}
|
||||
}
|
||||
previousValues.thrust = pendingChanges.MyAvatar.setThrust = thrust;
|
||||
break;
|
||||
}
|
||||
case DriveModes.JITTER_TEST:
|
||||
case DriveModes.POSITION: {
|
||||
pendingChanges.MyAvatar.position = Vec3.sum(currentPosition, deltaPosition);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new Error('unknown driveMode: ' + settings.driveMode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var finalOrientation;
|
||||
switch (Camera.mode) {
|
||||
case 'mirror': // fall through
|
||||
case 'independent':
|
||||
targetOrientation = settings.preventRoll ? Quat.cancelOutRoll(targetOrientation) : targetOrientation;
|
||||
var boomVector = Vec3.multiply(-currentVelocities.zoom.z, Quat.getFront(targetOrientation)),
|
||||
deltaCameraPosition = Vec3.sum(boomVector, deltaPosition);
|
||||
Object.assign(pendingChanges.Camera, {
|
||||
position: Vec3.sum(Camera.position, deltaCameraPosition),
|
||||
orientation: targetOrientation,
|
||||
});
|
||||
break;
|
||||
case 'entity':
|
||||
finalOrientation = targetOrientation;
|
||||
break;
|
||||
default: // 'first person', 'third person'
|
||||
finalOrientation = targetOrientation;
|
||||
break;
|
||||
}
|
||||
|
||||
if (settings.driveMode === movementUtils.DriveModes.JITTER_TEST) {
|
||||
finalOrientation = Quat.multiply(MyAvatar[orientationProperty], Quat.fromPitchYawRollDegrees(0, 60 * deltaTime, 0));
|
||||
// Quat.fromPitchYawRollDegrees(0, _utils.getRuntimeSeconds() * 60, 0)
|
||||
}
|
||||
|
||||
if (finalOrientation) {
|
||||
if (settings.preventRoll) {
|
||||
finalOrientation = Quat.cancelOutRoll(finalOrientation);
|
||||
}
|
||||
previousValues.finalOrientation = pendingChanges.MyAvatar[orientationProperty] = Quat.normalize(finalOrientation);
|
||||
}
|
||||
|
||||
if (!movementState.mouseSmooth && movementState.isRightMouseButton) {
|
||||
// directly apply mouse pitch and yaw when mouse smoothing is disabled
|
||||
_applyDirectPitchYaw(deltaTime, movementState, settings);
|
||||
}
|
||||
|
||||
var endTime = _utils.getRuntimeSeconds();
|
||||
var cycleTime = endTime - update.endTime;
|
||||
update.endTime = endTime;
|
||||
|
||||
var submitted = pendingChanges.submit();
|
||||
|
||||
update.momentaryFPS = 1 / actualDeltaTime;
|
||||
|
||||
if (settings.debug && update.frameCount % 120 === 0) {
|
||||
Messages.sendLocalMessage(_debugChannel, JSON.stringify({
|
||||
threadMode: cameraControls.threadMode,
|
||||
driveMode: settings.driveMode,
|
||||
orientationProperty: orientationProperty,
|
||||
isGrounded: movementState.isGrounded,
|
||||
targetAnimationFPS: cameraControls.threadMode === movementUtils.CameraControls.ANIMATION_FRAME ? cameraControls.fps : undefined,
|
||||
actualFPS: 1 / actualDeltaTime,
|
||||
effectiveAnimationFPS: 1 / deltaTime,
|
||||
seconds: {
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
},
|
||||
milliseconds: {
|
||||
actualDeltaTime: actualDeltaTime * 1000,
|
||||
deltaTime: deltaTime * 1000,
|
||||
cycleTime: cycleTime * 1000,
|
||||
calculationTime: (endTime - startTime) * 1000,
|
||||
},
|
||||
finalOrientation: finalOrientation,
|
||||
thrust: thrust,
|
||||
maxVelocity: settings.translation,
|
||||
targetVelocity: targetVelocity,
|
||||
currentSpeed: currentSpeed,
|
||||
targetSpeed: targetSpeed,
|
||||
}, 0, 2));
|
||||
}
|
||||
}
|
||||
|
||||
if (0) {
|
||||
Script.update.connect(gc);
|
||||
Script.scriptEnding.connect(function() {
|
||||
Script.update.disconnect(gc);
|
||||
});
|
||||
}
|
||||
|
||||
function _applyDirectPitchYaw(deltaTime, movementState, settings) {
|
||||
var orientationProperty = settings.useHead ? 'headOrientation' : 'orientation',
|
||||
rotation = movementState.rotation,
|
||||
speed = Vec3.multiply(-DEG_TO_RAD / 2.0, settings.rotation.speed);
|
||||
|
||||
var previousValues = globalState.previousValues,
|
||||
pendingChanges = globalState.pendingChanges,
|
||||
currentVelocities = globalState.currentVelocities;
|
||||
|
||||
var previous = previousValues.pitchYawRoll,
|
||||
target = Vec3.multiply(deltaTime, Vec3.multiplyVbyV(rotation, speed)),
|
||||
pitchYawRoll = Vec3.mix(previous, target, 0.5),
|
||||
orientation = Quat.fromVec3Degrees(pitchYawRoll);
|
||||
|
||||
previousValues.pitchYawRoll = pitchYawRoll;
|
||||
|
||||
if (pendingChanges.MyAvatar.headOrientation || pendingChanges.MyAvatar.orientation) {
|
||||
var newOrientation = Quat.multiply(MyAvatar[orientationProperty], orientation);
|
||||
delete pendingChanges.MyAvatar.headOrientation;
|
||||
delete pendingChanges.MyAvatar.orientation;
|
||||
if (settings.preventRoll) {
|
||||
newOrientation = Quat.cancelOutRoll(newOrientation);
|
||||
}
|
||||
MyAvatar[orientationProperty] = newOrientation;
|
||||
} else if (pendingChanges.Camera.orientation) {
|
||||
var cameraOrientation = Quat.multiply(Camera.orientation, orientation);
|
||||
if (settings.preventRoll) {
|
||||
cameraOrientation = Quat.cancelOutRoll(cameraOrientation);
|
||||
}
|
||||
Camera.orientation = cameraOrientation;
|
||||
}
|
||||
currentVelocities.rotation = Vec3.ZERO;
|
||||
}
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
function _startConfigationMonitor(applicationConfig, cameraConfig, interval) {
|
||||
|
@ -753,20 +545,6 @@ function _startConfigationMonitor(applicationConfig, cameraConfig, interval) {
|
|||
}, interval);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
function _resetMyAvatarMotor(targetObject) {
|
||||
if (MyAvatar.motorTimescale !== DEFAULT_MOTOR_TIMESCALE) {
|
||||
targetObject.MyAvatar.motorTimescale = DEFAULT_MOTOR_TIMESCALE;
|
||||
}
|
||||
if (MyAvatar.motorReferenceFrame !== 'avatar') {
|
||||
targetObject.MyAvatar.motorReferenceFrame = 'avatar';
|
||||
}
|
||||
if (Vec3.length(MyAvatar.motorVelocity)) {
|
||||
targetObject.MyAvatar.motorVelocity = Vec3.ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
function _fixedPrecisionStringifiyFilter(key, value, object) {
|
||||
if (typeof value === 'object' && value && 'w' in value) {
|
||||
|
@ -789,7 +567,7 @@ function _createOverlayDebugOutput(options) {
|
|||
|
||||
Script.scriptEnding.connect(function() {
|
||||
Overlays.deleteOverlay(overlayDebugOutput.overlayID);
|
||||
Messages.unsubscribe(debugChannel);
|
||||
Messages.unsubscribe(_debugChannel);
|
||||
Messages.messageReceived.disconnect(onMessageReceived);
|
||||
});
|
||||
function overlayDebugOutput(output) {
|
||||
|
@ -835,8 +613,8 @@ function _instrumentDebugValues() {
|
|||
require = Script.require(Script.resolvePath('./modules/_utils.js') + cacheBuster).makeDebugRequire(Script.resolvePath('.'));
|
||||
APP_HTML_URL += cacheBuster;
|
||||
overlayDebugOutput = _createOverlayDebugOutput({
|
||||
lineHeight: 10,
|
||||
font: { size: 10 },
|
||||
lineHeight: 12,
|
||||
font: { size: 12 },
|
||||
width: 250, height: 800 });
|
||||
// auto-disable camera move mode when debugging
|
||||
Script.scriptEnding.connect(function() {
|
||||
|
@ -847,11 +625,13 @@ function _instrumentDebugValues() {
|
|||
// 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) {}
|
||||
try {
|
||||
cameraControls.disable();
|
||||
} catch (e) {}
|
||||
Script.unhandledException.disconnect(onUnhandledException);
|
||||
if (WANT_DEBUG) {
|
||||
// show blue screen of death with the error details
|
||||
_utils.BSOD({
|
||||
new _utils.BSOD({
|
||||
error: error,
|
||||
buttons: [ 'Abort', 'Retry', 'Fail' ],
|
||||
debugInfo: DEBUG_INFO,
|
||||
|
|
|
@ -10,13 +10,14 @@
|
|||
.content { display: none }
|
||||
</style>
|
||||
|
||||
<!-- bring in jQuery and jQuery UI -->
|
||||
<!-- bring in jQuery (MIT) and jQuery UI (MIT) -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/themes/base/jquery-ui.css" />
|
||||
|
||||
<!--script src="https://d3js.org/d3-ease.v1.min.js"></script-->
|
||||
|
||||
<!-- bring in Tooltipster (MIT) for tooltip support -->
|
||||
<script src="https://cdn.jsdelivr.net/jquery.tooltipster/4.2.5/js/tooltipster.bundle.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/g/jquery.tooltipster@4.2.5(css/tooltipster.bundle.min.css+css/plugins/tooltipster/sideTip/themes/tooltipster-sideTip-noir.min.css)">
|
||||
|
||||
|
@ -33,19 +34,24 @@
|
|||
browserUtils = new _utils.BrowserUtils(window);
|
||||
}</script>
|
||||
<script type='require' src='./app.js'></script>
|
||||
<script type='require' src='./hifi-jquery-ui.js'></script>
|
||||
<script type='require' src='./_json-persist.js'></script>
|
||||
<script type='require' src='./_tooltips.js'></script>
|
||||
<script type='require' src='./_less-utils.js'></script>
|
||||
<script type='require' src='../app.fileOnErrors.js'></script>
|
||||
|
||||
<script>
|
||||
_debug.loadScriptNodes('script[type=require]');
|
||||
</script>
|
||||
|
||||
<script>;
|
||||
{
|
||||
try {
|
||||
// Qt web views only show the first parameter passed to console; patch if needed so all parameters show up
|
||||
console = browserUtils.makeConsoleWorkRight(console);
|
||||
|
||||
// display unhandled exceptions in an error div
|
||||
if (/debug/.test(location) || window.qt) {
|
||||
window.onerror = _debug.handleUncaughtException;
|
||||
if (/debug|localhost|file:/.test(location) || window.qt) {
|
||||
window.onerror = window._debug ? _debug.handleUncaughtException : null;
|
||||
}
|
||||
|
||||
// process querystring parameters from main client script
|
||||
|
@ -66,9 +72,7 @@
|
|||
var debugPrint = PARAMS.debug ? _debugPrint : function() {};
|
||||
|
||||
$(document).ready(function() {
|
||||
defineCustomWidgets();
|
||||
initializeDOM();
|
||||
viewportUpdated.connect(preconfigureLESS.onViewportUpdated);
|
||||
|
||||
// event bridge intialization
|
||||
log('document.ready...');
|
||||
|
@ -90,8 +94,10 @@
|
|||
namespace: PARAMS.namespace,
|
||||
uuid: PARAMS.uuid,
|
||||
debug: PARAMS.debug,
|
||||
extraParams: { contentVersion: VERSION },
|
||||
});
|
||||
window.addEventListener('unload', bridgedSettings.cleanup.bind(bridgedSettings));
|
||||
|
||||
bridgedSettings.callbackError.connect(function onCallbackError(err, msg) {
|
||||
console.error(err.stack);
|
||||
throw err;
|
||||
|
@ -107,17 +113,41 @@
|
|||
// let Client script know we are ready
|
||||
EventBridge.emitWebEvent(location.href);
|
||||
|
||||
// keep Interface in sync when DOM changes
|
||||
jquerySettings.valueUpdated.connect(function(key, value, oldValue, origin) {
|
||||
logValueUpdate('jquerySettings.valueUpdated', key, value, oldValue, origin);
|
||||
bridgedSettings.syncValue(key, value, origin);
|
||||
});
|
||||
|
||||
function onValueReceived(key, value, oldValue, origin) {
|
||||
onMutationEvent.paused++;
|
||||
try {
|
||||
jquerySettings.setValue(key, value, origin);
|
||||
} finally {
|
||||
setTimeout(function() { onMutationEvent.paused--; }, 1);
|
||||
}
|
||||
}
|
||||
// keep DOM in sync when Interface changes
|
||||
bridgedSettings.valueUpdated.connect(function(key, value, oldValue, origin) {
|
||||
logValueUpdate('bridgedSettings.valueUpdated', key, value, oldValue, origin);
|
||||
jquerySettings.setValue(key, value);
|
||||
bridgedSettings.valueReceived.connect(onValueReceived);
|
||||
|
||||
// Object.defineProperty(onMutationEvent, 'paused', {
|
||||
// enumerable: true,
|
||||
// get: function() { return this._paused || 0; },
|
||||
// set: function(nv) { this._paused = nv; log('paused = ' + nv); return nv; },
|
||||
// });
|
||||
|
||||
onMutationEvent.paused = 1;
|
||||
function onMutationEvent(event) {
|
||||
assert(onMutationEvent.paused >= 0);
|
||||
if (!onMutationEvent.paused) {
|
||||
bridgedSettings.sendEvent({
|
||||
method: 'valueUpdated', params: [
|
||||
event.key, event.value, event.oldValue, event.hifiType
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// unpause the mutation handler after the initial requests complete
|
||||
bridgedSettings.pendingRequestsFinished.connect(function once() {
|
||||
bridgedSettings.pendingRequestsFinished.disconnect(once);
|
||||
onMutationEvent.paused--;
|
||||
});
|
||||
jquerySettings.mutationEvent.connect(onMutationEvent);
|
||||
|
||||
setupUI();
|
||||
|
||||
|
@ -126,11 +156,10 @@
|
|||
$('section').show();
|
||||
}, 150);
|
||||
}
|
||||
}</script>
|
||||
} catch(e) { alert(e) }</script>
|
||||
</head>
|
||||
|
||||
<body class="settings-app">
|
||||
<div id="errors" style="display:none"><pre class='output'></pre><button onclick=location.reload()>adsfsadf</button></div>
|
||||
<div class="content">
|
||||
<header class="title">
|
||||
<div class="inner-title">
|
||||
|
@ -144,12 +173,6 @@
|
|||
</header>
|
||||
|
||||
<div class='scrollable'>
|
||||
<!-- generate debug line rows -->
|
||||
<pre style='display:none;font-size:8px'>
|
||||
<script id='delme'>for (var i=0; !(i >= 100); i++) document.write('<span class=content></span>'); </script>
|
||||
</pre>
|
||||
<script>with({ parent: '', node: document.getElementById('delme') }) node.parentNode.removeChild(node);</script>
|
||||
|
||||
<section>
|
||||
<h2>Translation</h2>
|
||||
<div class="number row">
|
||||
|
@ -158,12 +181,12 @@
|
|||
</div>
|
||||
<div class="slider row">
|
||||
<label>Ease In Coefficient</label>
|
||||
<div class="control"></div>
|
||||
<div type="slider" class="control" data-for="translation-ease-in"></div>
|
||||
<input class="setting" data-type="number" id="translation-ease-in" />
|
||||
</div>
|
||||
<div class="slider row">
|
||||
<label>Ease Out Coefficient</label>
|
||||
<div class="control"></div>
|
||||
<div type="slider" class="control" data-for="translation-ease-out"></div>
|
||||
<input class="setting" data-type="number" id="translation-ease-out" />
|
||||
</div>
|
||||
</section>
|
||||
|
@ -178,12 +201,12 @@
|
|||
</div>
|
||||
<div class="slider row">
|
||||
<label>Ease In Coefficient</label>
|
||||
<div class="control"></div>
|
||||
<div type="slider" class="control" data-for="rotation-ease-in"></div>
|
||||
<input class="setting" data-type="number" id="rotation-ease-in" />
|
||||
</div>
|
||||
<div class="slider row">
|
||||
<label>Ease Out Coefficient</label>
|
||||
<div class="control"></div>
|
||||
<div type="slider" class="control" data-for="rotation-ease-out"></div>
|
||||
<input class="setting" data-type="number" id="rotation-ease-out" />
|
||||
</div>
|
||||
</section>
|
||||
|
@ -193,11 +216,11 @@
|
|||
<section>
|
||||
<h2>Options</h2>
|
||||
<div class="bool row">
|
||||
<input class="setting" id="enable-lookat-snapping" name="Avatar/lookAtSnappingEnabled" type="checkbox" />
|
||||
<input class="setting" id="enable-lookat-snapping" data-for="Avatar/lookAtSnappingEnabled" type="checkbox" />
|
||||
<label for="enable-lookat-snapping">Avatars snap look at camera</label>
|
||||
</div>
|
||||
<div class="bool row">
|
||||
<input class="setting" id="use-snap-turn" name="Avatar/useSnapTurn" type="checkbox" />
|
||||
<input class="setting" id="use-snap-turn" data-for="Avatar/useSnapTurn" type="checkbox" />
|
||||
<label for="use-snap-turn">Enable snap turn in HMD</label>
|
||||
</div>
|
||||
<div class="bool row">
|
||||
|
@ -220,11 +243,11 @@
|
|||
<div class="column">
|
||||
<div class="number row">
|
||||
<label for='rotation-x-speed'>Pitch speed<span class="unit">deg/s</span></label>
|
||||
<input class="setting" data-type="number" step="1" id="rotation-x-speed" />
|
||||
<input class="setting" data-type="number" min="-Infinity" step="1" id="rotation-x-speed" />
|
||||
</div>
|
||||
<div class="number row">
|
||||
<label for='rotation-y-speed'>Yaw speed<span class="unit">deg/s</span></label>
|
||||
<input class="setting" data-type="number" step="1" id="rotation-y-speed" />
|
||||
<input class="setting" data-type="number" min="-Infinity" step="1" id="rotation-y-speed" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -235,12 +258,12 @@
|
|||
<h2>input scaling</h2>
|
||||
<div class="column">
|
||||
<div class="number row">
|
||||
<label for='rotation-keyboard-multiplier'>Keyboard multiplier</label>
|
||||
<input class="setting" data-type="number" step=".1" id="rotation-keyboard-multiplier" />
|
||||
<label for='keyboard-multiplier'>Keyboard multiplier</label>
|
||||
<input class="setting" data-type="number" min="-Infinity" step=".1" id="keyboard-multiplier" />
|
||||
</div>
|
||||
<div class="number row">
|
||||
<label for='rotation-mouse-multiplier'>Mouse multiplier</label>
|
||||
<input class="setting" data-type="number" step=".1" id="rotation-mouse-multiplier" />
|
||||
<label for='mouse-multiplier'>Mouse multiplier</label>
|
||||
<input class="setting" data-type="number" min="-Infinity" step=".1" id="mouse-multiplier" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -270,11 +293,11 @@
|
|||
<label for="normalize-inputs">Normalize mouse movement</label>
|
||||
</div>
|
||||
<div class="bool row">
|
||||
<input class="setting" id="collisions-enabled" name="Avatar/Enable Avatar Collisions" type="checkbox" />
|
||||
<input class="setting" id="collisions-enabled" data-for="Avatar/Enable Avatar Collisions" type="checkbox" />
|
||||
<label for="collisions-enabled">Enable Avatar Collisions</label>
|
||||
</div>
|
||||
<div class="bool row">
|
||||
<input class="setting" id="draw-mesh" name="Avatar/Draw Mesh" type="checkbox" />
|
||||
<input class="setting" id="draw-mesh" data-for="Avatar/Draw Mesh" type="checkbox" />
|
||||
<label for="draw-mesh">Draw My Avatar</label>
|
||||
</div>
|
||||
<div class="bool row">
|
||||
|
@ -289,21 +312,21 @@
|
|||
<section>
|
||||
<h2>Update Mode</h2>
|
||||
<div class='column'>
|
||||
<div class="radio row" id="drive-mode" >
|
||||
<div class="radio row" id="drive-mode" type='radio-group'>
|
||||
<div>
|
||||
<input class="setting" name="drive-mode" id="motor" type="radio" />
|
||||
<input class="setting" data-for="drive-mode" id="motor" type="radio" />
|
||||
<label for="motor">Scripted Motor Control</label>
|
||||
</div>
|
||||
<div>
|
||||
<input class="setting" name="drive-mode" id="position" type="radio" />
|
||||
<input class="setting" data-for="drive-mode" id="position" type="radio" />
|
||||
<label for="position">Absolute Avatar position</label>
|
||||
</div>
|
||||
<div>
|
||||
<input class="setting" name="drive-mode" id="thrust" type="radio" />
|
||||
<input class="setting" data-for="drive-mode" id="thrust" type="radio" />
|
||||
<label for="thrust">Thrust/force vectors</label>
|
||||
</div>
|
||||
<div>
|
||||
<input class="setting" name="drive-mode" id="jitter-test" type="radio" />
|
||||
<input class="setting" data-for="drive-mode" id="jitter-test" type="radio" />
|
||||
<label for="jitter-test">Debug Jitter Testing</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -311,22 +334,22 @@
|
|||
<hr />
|
||||
<section>
|
||||
<h2>Script update mode:</h2-->
|
||||
<div class="radio row" id="thread-update-mode" >
|
||||
<div class="radio row" id="thread-update-mode" type='radio-group'>
|
||||
<div>
|
||||
<input class="setting" name="thread-update-mode" id="update" type="radio" />
|
||||
<input class="setting" data-for="thread-update-mode" id="update" type="radio" />
|
||||
<label for="update"><code>Script.update</code></label>
|
||||
</div>
|
||||
<div class='row tooltip-target' for="requestAnimationFrame" data-tooltip-side='left'>
|
||||
<label for="requestAnimationFrame"><code>requestAnimationFrame</code></label>
|
||||
<input class="setting" name="thread-update-mode" id="requestAnimationFrame" type="radio" />
|
||||
<br /> <small>fps:</small> <input class="setting" data-type="number" step="1" id="fps" />
|
||||
<input class="setting" data-for="thread-update-mode" id="requestAnimationFrame" type="radio" />
|
||||
<br /> <small>fps:</small> <input class="setting" data-type="number" step="1" min="0" id="fps" />
|
||||
</div>
|
||||
<div>
|
||||
<input class="setting" name="thread-update-mode" id="setImmediate" type="radio" />
|
||||
<input class="setting" data-for="thread-update-mode" id="setImmediate" type="radio" />
|
||||
<label for="setImmediate"><code>setImmediate</code></label>
|
||||
</div>
|
||||
<div>
|
||||
<input class="setting" name="thread-update-mode" id="nextTick" type="radio" />
|
||||
<input class="setting" data-for="thread-update-mode" id="nextTick" type="radio" />
|
||||
<label for="nextTick"><code>nextTick</code></label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -337,11 +360,11 @@
|
|||
|
||||
<section id='debug-menu'>
|
||||
<h2>debug menu</h2>
|
||||
<div>
|
||||
<div class='buttons'>
|
||||
<div style='float:left'>
|
||||
<button id='reset-sensors'>Reset Avatar</button>
|
||||
<button class='localhost-only' id='page-reload'>window.reload</button>
|
||||
<button class='localhost-only' id='script-reload'>script.reload</button>
|
||||
<button title='reload the tablet app web view' class='localhost-only' id='page-reload'>refresh page</button>
|
||||
<button title='reload the Client script' class='localhost-only' id='script-reload'>reload script</button>
|
||||
</div>
|
||||
<div style='float:right'>
|
||||
<button id='copy-json'>Export JSON</button>
|
||||
|
@ -362,9 +385,37 @@
|
|||
</div>
|
||||
<center>
|
||||
<div id='toggleKey'>keybinding: <span class='binding'>…</span></div>
|
||||
|
||||
<code>
|
||||
<!-- ** TEMPORARY EXPERIMENT -- checking how well capturing arbitrary keybindings from a tablet app could work -->
|
||||
|
||||
<!-- bring in Mousetrap (Apache 2.0) for easy keybinding support -->
|
||||
<script src="https://cdn.jsdelivr.net/g/mousetrap@1.6.1(mousetrap.js+plugins/bind-dictionary/mousetrap-bind-dictionary.min.js+plugins/pause/mousetrap-pause.min.js+plugins/record/mousetrap-record.min.js+plugins/global-bind/mousetrap-global-bind.min.js)"></script>
|
||||
|
||||
<style type='text/less'>
|
||||
#record {
|
||||
button { font-size: 8px; margin: 0px; padding: 2px; margin-right: 4px; line-height: 1em; }
|
||||
pre { position: absolute; left: 70%; top: 0; font-size: 10px; margin: 0; padding: 0; color: white; font-weight: normal; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<!--"-->
|
||||
<div id='record' onclick="{
|
||||
$(this).find('button').css('background-color','red');
|
||||
Mousetrap.record(function(sequence) {
|
||||
$(this).find('button').css('background-color','');
|
||||
log(sequence);
|
||||
// sequence is an array like ['ctrl+k', 'c']
|
||||
$(this).find('pre').text(sequence.shift());
|
||||
}.bind(this));
|
||||
}"><!--"-->
|
||||
<button title='capture new keybinding'>✇</button>
|
||||
<pre></pre>
|
||||
</div>
|
||||
</code>
|
||||
</center>
|
||||
<div style='float:right'>
|
||||
<button id='toggle-advanced-options'>Advanced<span class='chevron'></span></button>
|
||||
<button checked=false for='ui-show-advanced-options' id='toggle-advanced-options'>Advanced<span class='chevron'></span></button>
|
||||
</div>
|
||||
<br />
|
||||
</footer>
|
||||
|
@ -453,8 +504,8 @@
|
|||
<section>
|
||||
<div for="rotation-x-speed">degrees per up/down controller inputs</div>
|
||||
<div for="rotation-y-speed">degrees per left/right controller inputs</div>
|
||||
<div for="rotation-keyboard-multiplier">prescale raw controller inputs by this amount</div>
|
||||
<div for="rotation-mouse-multiplier">prescale raw controller inputs by this amount</div>
|
||||
<div for="keyboard-multiplier">prescale raw controller inputs by this amount</div>
|
||||
<div for="mouse-multiplier">prescale raw controller inputs by this amount</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
|
@ -495,14 +546,44 @@
|
|||
replace current settings with previously-exported JSON
|
||||
</div>
|
||||
<div for="reset-sensors">
|
||||
trigger <b>MyAvatar > Reset Sensors</b> and then reset <b>bodyPitch</b> and <b>bodyYaw</b>
|
||||
trigger <b>MyAvatar > Reset Sensors</b>;reset <b>bodyPitch</b> and <b>bodyYaw</b>
|
||||
</div>
|
||||
<div for="toggle-advanced-options">
|
||||
<div for="ui-show-advanced-options">
|
||||
show / hide advanced settings
|
||||
</div>
|
||||
</section>
|
||||
</div><!-- /tooltips -->
|
||||
|
||||
<div id="errors" style="display:none">
|
||||
<div class='title'>PAGE ERROR:</div>
|
||||
<pre class='output'></pre>
|
||||
<pre class='debug-lines' style='display:none;'><span class='content'></span></pre>
|
||||
<hr />
|
||||
<div class='buttons'><button onclick='location.reload()'>reload page</button> | <button>dismiss</button></div>
|
||||
</div>
|
||||
<!-- generate debug line outputs -->
|
||||
<script>(function(template) {
|
||||
for (var i=0; i !== 25; i++) {
|
||||
template.parentNode.appendChild(template.cloneNode());
|
||||
}
|
||||
})(document.querySelector('#errors .debug-lines .content'));
|
||||
</script>
|
||||
<script>
|
||||
var LESS_GLOBALS = {
|
||||
'header-height': 48,
|
||||
'footer-height': 32,
|
||||
'custom-font-family': 'Raleway-Regular',
|
||||
'input-font-family': 'FiraSans-Regular',
|
||||
'color-highlight': '#009bd5',
|
||||
'color-text': '#afafaf',
|
||||
'color-bg': '#393939',
|
||||
'color-bg-darker': '#252525',
|
||||
'color-bg-icon': '#545454',
|
||||
'color-primary-button': 'darken(darkblue,10%)',
|
||||
'color-alt-button': 'green',
|
||||
'color-caution-button': 'darkred',
|
||||
};
|
||||
</script>
|
||||
<style id='tablet-ui-less' type="text/less">
|
||||
// Embedded LESS stylesheets are used as a structured way to manage page styling.
|
||||
// The template rules below get compiled at page load time by less.js.
|
||||
|
@ -513,15 +594,18 @@
|
|||
.mixins > .load-hifi-font(@custom-font-family);
|
||||
.mixins > .load-hifi-font(@input-font-family);
|
||||
|
||||
input, textarea {
|
||||
font-family: '@{input-font-family}', sans-serif;
|
||||
}
|
||||
|
||||
.mixin-tablet-mode() {
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
margin-left: -3px;
|
||||
margin-right: 0;
|
||||
overflow-x: hidden;
|
||||
.content {
|
||||
.slider {
|
||||
.ui-spinner {
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.tablet-mode { .mixin-tablet-mode() !important; }
|
||||
|
||||
|
@ -529,6 +613,10 @@
|
|||
color: @color-text;
|
||||
background-color: @color-bg;
|
||||
|
||||
&.window-blurred header {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
font-family: '@{custom-font-family}';
|
||||
font-size: 15px;
|
||||
text-rendering: optimizeLegibility;
|
||||
|
@ -544,6 +632,11 @@
|
|||
//margin-top: 6px;
|
||||
margin-left: -5px;
|
||||
overflow: hidden;
|
||||
input, textarea {
|
||||
font-family: '@{input-font-family}', sans-serif;
|
||||
user-select: auto;
|
||||
-webkit-user-select: auto;
|
||||
}
|
||||
}
|
||||
|
||||
p { margin: 2px 0; }
|
||||
|
@ -561,12 +654,13 @@
|
|||
//position: absolute;
|
||||
}
|
||||
|
||||
.title {
|
||||
header.title {
|
||||
position: relative;
|
||||
padding: 6px 0 6px 10px;
|
||||
text-align: left;
|
||||
clear: both;
|
||||
|
||||
h1 { font-size: 18px; }
|
||||
h1, label {
|
||||
font-weight: normal;
|
||||
//margin: 16px 0;
|
||||
|
@ -574,18 +668,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
#errors .content { padding: 1px 3px; background-color: black; color:blue; display:block; }
|
||||
#errors span:nth-child(1).content:before { content: 'hash:@{hash}\000a'; }
|
||||
section h1 { font-size: 18px; }
|
||||
|
||||
h1 { font-size: 18px; }
|
||||
|
||||
h2 {
|
||||
section h2 {
|
||||
margin-left: -12px;
|
||||
font-size: 14px;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
input[type=text], input[data-type=number], textarea {
|
||||
input[type=text], input[type=number], input[data-type=number], textarea {
|
||||
margin: 0;
|
||||
padding: 0 0 0 12px;
|
||||
color: @color-text;
|
||||
|
@ -761,12 +852,16 @@
|
|||
@subtle-border: solid 1px rgba(158, 158, 158, 0.151);
|
||||
|
||||
br { clear: both; }
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #99f;
|
||||
&:hover { color: darken(#99f, 10%); }
|
||||
}
|
||||
.content { display: block; }
|
||||
|
||||
#overlay {
|
||||
opacity: .15;
|
||||
background-color: white;
|
||||
opacity: .35;
|
||||
background-color: black;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -774,7 +869,10 @@
|
|||
width: 100%;
|
||||
z-index: 9999999;
|
||||
}
|
||||
&:not(.active) #overlay { display: block; }
|
||||
&:not(.active) {
|
||||
#overlay { display: block; }
|
||||
.tooltipster-base { display: none; }
|
||||
}
|
||||
&.active #overlay { display: none; }
|
||||
|
||||
section {
|
||||
|
@ -790,8 +888,10 @@
|
|||
top: auto;
|
||||
text-align: center;
|
||||
height: @footer-height * 1px;
|
||||
border-bottom: none;
|
||||
box-shadow: none;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-bottom: none !important;//solid -1px blue;
|
||||
box-shadow: none !important;
|
||||
center {
|
||||
//line-height: @footer-height * 1px;
|
||||
vertical-align: middle;
|
||||
|
@ -981,8 +1081,8 @@
|
|||
border-color: transparent;
|
||||
margin: 2px;
|
||||
text-transform: uppercase;
|
||||
&:hover { background-color: lighten(@color-primary-button,10%); }
|
||||
border: solid 1px transparent;
|
||||
&:hover { background-color: lighten(@color-primary-button,10%); }
|
||||
&:focus { border: dotted 1px tint(@color-highlight) !important; }
|
||||
}
|
||||
.localhost-only {
|
||||
|
@ -991,6 +1091,20 @@
|
|||
display: inline-block;
|
||||
}
|
||||
}
|
||||
#debug-menu {
|
||||
.buttons {
|
||||
margin: 0px -8px;
|
||||
button {
|
||||
padding: 4px;
|
||||
margin: 1px;
|
||||
font-size: 12px;
|
||||
text-transform: none;
|
||||
font-weight: normal;
|
||||
border-radius: 6px;
|
||||
&.localhost-only { background-color: #303; &:hover { background-color: lighten(#303,10%);} }
|
||||
}
|
||||
}
|
||||
}
|
||||
#advanced-options {
|
||||
.content { background-color: #333; }
|
||||
display: none;
|
||||
|
@ -1011,7 +1125,10 @@
|
|||
}
|
||||
.ui-show-advanced-options {
|
||||
#advanced-options { display: block !important; }
|
||||
#toggle-advanced-options .chevron:after { content: ' \25BC' !important; }
|
||||
#toggle-advanced-options {
|
||||
background-color: mix(blue,@color-alt-button,20%) !important;
|
||||
.chevron:after { content: ' \25B2' !important; }
|
||||
}
|
||||
}
|
||||
|
||||
#appVersion {
|
||||
|
@ -1029,14 +1146,43 @@
|
|||
left: 0;
|
||||
top: 0;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
min-height: @header-height * 1px;
|
||||
border-bottom: solid 1px gray;
|
||||
box-shadow: 2px 2px 8px black;
|
||||
right: 0;
|
||||
font-size: .8em;
|
||||
background-color: rgb(95,0,0);
|
||||
color: white;
|
||||
overflow: auto;
|
||||
margin: 0;
|
||||
padding: 8px;
|
||||
padding: 4px;
|
||||
z-index: 9999999;
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
margin: 0 6px;
|
||||
}
|
||||
.title {
|
||||
background-color: red;
|
||||
padding: 2px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.buttons {
|
||||
margin-top: 4px;
|
||||
float: right;
|
||||
button {
|
||||
font-weight: normal;
|
||||
margin: 4px 2px;
|
||||
}
|
||||
}
|
||||
.debug-lines {
|
||||
font-size: 8px;
|
||||
background-color: black;
|
||||
color: lightblue;
|
||||
.content {
|
||||
padding: 1px 3px;
|
||||
&:nth-child(1):before { content: 'hash:@{hash}\000a'; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer center > div { display: none; }
|
||||
|
@ -1056,9 +1202,17 @@ footer center > div { display: none; }
|
|||
}
|
||||
}
|
||||
</style>
|
||||
<script> preconfigureLESS(); </script>
|
||||
<script>
|
||||
lessManager = preconfigureLESS({
|
||||
selector: 'style[type="text/less"]',
|
||||
globalVars: Object.assign({
|
||||
debug: !!PARAMS.debug,
|
||||
localhost: /\b(?:file:\/\/|localhost|127[.])/.test(location),
|
||||
}, LESS_GLOBALS)
|
||||
});
|
||||
less = lessManager.options;
|
||||
</script>
|
||||
<script data-env-'development' xx-data-env='production' src="https://cdnjs.cloudflare.com/ajax/libs/less.js/2.7.2/less.js"></script>
|
||||
|
||||
<!-- JSON DUMP TEMPLATE -->
|
||||
<script type='text/html' id='POPUP'>
|
||||
<html>
|
||||
|
|
File diff suppressed because it is too large
Load diff
257
unpublishedScripts/marketplace/camera-move/avatar-updater.js
Normal file
257
unpublishedScripts/marketplace/camera-move/avatar-updater.js
Normal file
|
@ -0,0 +1,257 @@
|
|||
/* eslint-env commonjs */
|
||||
// ----------------------------------------------------------------------------
|
||||
module.exports = AvatarUpdater;
|
||||
|
||||
var _utils = require('./modules/_utils.js'),
|
||||
assert = _utils.assert;
|
||||
|
||||
var movementUtils = require('./modules/movement-utils.js');
|
||||
|
||||
function AvatarUpdater(options) {
|
||||
options = options || {};
|
||||
assert(function assertion() {
|
||||
return typeof options.getCameraMovementSettings === 'function' &&
|
||||
typeof options.getMovementState === 'function' &&
|
||||
options.globalState;
|
||||
});
|
||||
|
||||
var DEFAULT_MOTOR_TIMESCALE = 1e6; // a large value that matches Interface's default
|
||||
var EASED_MOTOR_TIMESCALE = 0.01; // a small value to make Interface quickly apply MyAvatar.motorVelocity
|
||||
var EASED_MOTOR_THRESHOLD = 0.1; // above this speed (m/s) EASED_MOTOR_TIMESCALE is used
|
||||
var ACCELERATION_MULTIPLIERS = { translation: 1, rotation: 1, zoom: 1 };
|
||||
var STAYGROUNDED_PITCH_THRESHOLD = 45.0; // degrees; ground level is maintained when pitch is within this threshold
|
||||
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();
|
||||
|
||||
this.update = update;
|
||||
this.options = options;
|
||||
this._resetMyAvatarMotor = _resetMyAvatarMotor;
|
||||
this._applyDirectPitchYaw = _applyDirectPitchYaw;
|
||||
|
||||
var globalState = options.globalState;
|
||||
var getCameraMovementSettings = options.getCameraMovementSettings;
|
||||
var getMovementState = options.getMovementState;
|
||||
var _debugChannel = options.debugChannel;
|
||||
function update(dt) {
|
||||
update.frameCount++;
|
||||
var startTime = _utils.getRuntimeSeconds();
|
||||
var settings = getCameraMovementSettings(),
|
||||
EPSILON = settings.epsilon;
|
||||
|
||||
var independentCamera = Camera.mode === 'independent',
|
||||
headPitch = MyAvatar.headPitch;
|
||||
|
||||
var actualDeltaTime = Math.max(MIN_DELTA_TIME, (startTime - update.endTime)),
|
||||
deltaTime;
|
||||
|
||||
if (settings.useConstantDeltaTime) {
|
||||
deltaTime = settings.threadMode === movementUtils.CameraControls.ANIMATION_FRAME ?
|
||||
(1 / settings.fps) : (1 / 90);
|
||||
} else if (settings.threadMode === movementUtils.CameraControls.SCRIPT_UPDATE) {
|
||||
deltaTime = dt;
|
||||
} else {
|
||||
deltaTime = actualDeltaTime;
|
||||
}
|
||||
|
||||
var orientationProperty = settings.useHead ? 'headOrientation' : 'orientation',
|
||||
currentOrientation = independentCamera ? Camera.orientation : MyAvatar[orientationProperty],
|
||||
currentPosition = MyAvatar.position;
|
||||
|
||||
var previousValues = globalState.previousValues,
|
||||
pendingChanges = globalState.pendingChanges,
|
||||
currentVelocities = globalState.currentVelocities;
|
||||
|
||||
var movementState = getMovementState({ update: deltaTime }),
|
||||
targetState = movementUtils.applyEasing(deltaTime, 'easeIn', settings, movementState, ACCELERATION_MULTIPLIERS),
|
||||
dragState = movementUtils.applyEasing(deltaTime, 'easeOut', settings, currentVelocities, ACCELERATION_MULTIPLIERS);
|
||||
|
||||
currentVelocities.integrate(targetState, currentVelocities, dragState, settings);
|
||||
|
||||
var currentSpeed = Vec3.length(currentVelocities.translation),
|
||||
targetSpeed = Vec3.length(movementState.translation),
|
||||
verticalHold = movementState.isGrounded && settings.stayGrounded && Math.abs(headPitch) < STAYGROUNDED_PITCH_THRESHOLD;
|
||||
|
||||
var deltaOrientation = Quat.fromVec3Degrees(Vec3.multiply(deltaTime, currentVelocities.rotation)),
|
||||
targetOrientation = Quat.normalize(Quat.multiply(currentOrientation, deltaOrientation));
|
||||
|
||||
var targetVelocity = Vec3.multiplyQbyV(targetOrientation, currentVelocities.translation);
|
||||
|
||||
if (verticalHold) {
|
||||
targetVelocity.y = 0;
|
||||
}
|
||||
|
||||
var deltaPosition = Vec3.multiply(deltaTime, targetVelocity);
|
||||
|
||||
_resetMyAvatarMotor(pendingChanges);
|
||||
|
||||
if (!independentCamera) {
|
||||
var DriveModes = movementUtils.DriveModes;
|
||||
switch (settings.driveMode) {
|
||||
case DriveModes.MOTOR: {
|
||||
if (currentSpeed > EPSILON || targetSpeed > EPSILON) {
|
||||
var motorTimescale = (currentSpeed > EASED_MOTOR_THRESHOLD ? EASED_MOTOR_TIMESCALE : DEFAULT_MOTOR_TIMESCALE);
|
||||
var motorPitch = Quat.fromPitchYawRollDegrees(headPitch, 180, 0),
|
||||
motorVelocity = Vec3.multiplyQbyV(motorPitch, currentVelocities.translation);
|
||||
if (verticalHold) {
|
||||
motorVelocity.y = 0;
|
||||
}
|
||||
Object.assign(pendingChanges.MyAvatar, {
|
||||
motorVelocity: motorVelocity,
|
||||
motorTimescale: motorTimescale
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DriveModes.THRUST: {
|
||||
var thrustVector = currentVelocities.translation,
|
||||
maxThrust = settings.translation.maxVelocity,
|
||||
thrust;
|
||||
if (targetSpeed > EPSILON) {
|
||||
thrust = movementUtils.calculateThrust(maxThrust * 5, thrustVector, previousValues.thrust);
|
||||
} else if (currentSpeed > 1 && Vec3.length(previousValues.thrust) > 1) {
|
||||
thrust = Vec3.multiply(-currentSpeed / 10.0, thrustVector);
|
||||
} else {
|
||||
thrust = Vec3.ZERO;
|
||||
}
|
||||
if (thrust) {
|
||||
thrust = Vec3.multiplyQbyV(MyAvatar[orientationProperty], thrust);
|
||||
if (verticalHold) {
|
||||
thrust.y = 0;
|
||||
}
|
||||
}
|
||||
previousValues.thrust = pendingChanges.MyAvatar.setThrust = thrust;
|
||||
break;
|
||||
}
|
||||
case DriveModes.JITTER_TEST:
|
||||
case DriveModes.POSITION: {
|
||||
pendingChanges.MyAvatar.position = Vec3.sum(currentPosition, deltaPosition);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new Error('unknown driveMode: ' + settings.driveMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var finalOrientation;
|
||||
switch (Camera.mode) {
|
||||
case 'mirror': // fall through
|
||||
case 'independent':
|
||||
targetOrientation = settings.preventRoll ? Quat.cancelOutRoll(targetOrientation) : targetOrientation;
|
||||
var boomVector = Vec3.multiply(-currentVelocities.zoom.z, Quat.getFront(targetOrientation)),
|
||||
deltaCameraPosition = Vec3.sum(boomVector, deltaPosition);
|
||||
Object.assign(pendingChanges.Camera, {
|
||||
position: Vec3.sum(Camera.position, deltaCameraPosition),
|
||||
orientation: targetOrientation
|
||||
});
|
||||
break;
|
||||
case 'entity':
|
||||
finalOrientation = targetOrientation;
|
||||
break;
|
||||
default: // 'first person', 'third person'
|
||||
finalOrientation = targetOrientation;
|
||||
break;
|
||||
}
|
||||
|
||||
if (settings.driveMode === movementUtils.DriveModes.JITTER_TEST) {
|
||||
finalOrientation = Quat.multiply(MyAvatar[orientationProperty], Quat.fromPitchYawRollDegrees(0, 60 * deltaTime, 0));
|
||||
// Quat.fromPitchYawRollDegrees(0, _utils.getRuntimeSeconds() * 60, 0)
|
||||
}
|
||||
|
||||
if (finalOrientation) {
|
||||
if (settings.preventRoll) {
|
||||
finalOrientation = Quat.cancelOutRoll(finalOrientation);
|
||||
}
|
||||
previousValues.finalOrientation = pendingChanges.MyAvatar[orientationProperty] = Quat.normalize(finalOrientation);
|
||||
}
|
||||
|
||||
if (!movementState.mouseSmooth && movementState.isRightMouseButton) {
|
||||
// directly apply mouse pitch and yaw when mouse smoothing is disabled
|
||||
_applyDirectPitchYaw(deltaTime, movementState, settings);
|
||||
}
|
||||
|
||||
var endTime = _utils.getRuntimeSeconds();
|
||||
var cycleTime = endTime - update.endTime;
|
||||
update.endTime = endTime;
|
||||
|
||||
pendingChanges.submit();
|
||||
|
||||
update.momentaryFPS = 1 / actualDeltaTime;
|
||||
|
||||
if (_debugChannel && update.frameCount % 120 === 0) {
|
||||
Messages.sendLocalMessage(_debugChannel, JSON.stringify({
|
||||
threadMode: settings.threadMode,
|
||||
driveMode: settings.driveMode,
|
||||
orientationProperty: orientationProperty,
|
||||
isGrounded: movementState.isGrounded,
|
||||
targetAnimationFPS: settings.threadMode === movementUtils.CameraControls.ANIMATION_FRAME ? settings.fps : undefined,
|
||||
actualFPS: 1 / actualDeltaTime,
|
||||
effectiveAnimationFPS: 1 / deltaTime,
|
||||
seconds: {
|
||||
startTime: startTime,
|
||||
endTime: endTime
|
||||
},
|
||||
milliseconds: {
|
||||
actualDeltaTime: actualDeltaTime * 1000,
|
||||
deltaTime: deltaTime * 1000,
|
||||
cycleTime: cycleTime * 1000,
|
||||
calculationTime: (endTime - startTime) * 1000
|
||||
},
|
||||
finalOrientation: finalOrientation,
|
||||
thrust: thrust,
|
||||
maxVelocity: settings.translation,
|
||||
targetVelocity: targetVelocity,
|
||||
currentSpeed: currentSpeed,
|
||||
targetSpeed: targetSpeed
|
||||
}, 0, 2));
|
||||
}
|
||||
}
|
||||
|
||||
function _resetMyAvatarMotor(targetObject) {
|
||||
if (MyAvatar.motorTimescale !== DEFAULT_MOTOR_TIMESCALE) {
|
||||
targetObject.MyAvatar.motorTimescale = DEFAULT_MOTOR_TIMESCALE;
|
||||
}
|
||||
if (MyAvatar.motorReferenceFrame !== 'avatar') {
|
||||
targetObject.MyAvatar.motorReferenceFrame = 'avatar';
|
||||
}
|
||||
if (Vec3.length(MyAvatar.motorVelocity)) {
|
||||
targetObject.MyAvatar.motorVelocity = Vec3.ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
function _applyDirectPitchYaw(deltaTime, movementState, settings) {
|
||||
var orientationProperty = settings.useHead ? 'headOrientation' : 'orientation',
|
||||
rotation = movementState.rotation,
|
||||
speed = Vec3.multiply(-DEG_TO_RAD / 2.0, settings.rotation.speed);
|
||||
|
||||
var previousValues = globalState.previousValues,
|
||||
pendingChanges = globalState.pendingChanges,
|
||||
currentVelocities = globalState.currentVelocities;
|
||||
|
||||
var previous = previousValues.pitchYawRoll,
|
||||
target = Vec3.multiply(deltaTime, Vec3.multiplyVbyV(rotation, speed)),
|
||||
pitchYawRoll = Vec3.mix(previous, target, 0.5),
|
||||
orientation = Quat.fromVec3Degrees(pitchYawRoll);
|
||||
|
||||
previousValues.pitchYawRoll = pitchYawRoll;
|
||||
|
||||
if (pendingChanges.MyAvatar.headOrientation || pendingChanges.MyAvatar.orientation) {
|
||||
var newOrientation = Quat.multiply(MyAvatar[orientationProperty], orientation);
|
||||
delete pendingChanges.MyAvatar.headOrientation;
|
||||
delete pendingChanges.MyAvatar.orientation;
|
||||
if (settings.preventRoll) {
|
||||
newOrientation = Quat.cancelOutRoll(newOrientation);
|
||||
}
|
||||
MyAvatar[orientationProperty] = newOrientation;
|
||||
} else if (pendingChanges.Camera.orientation) {
|
||||
var cameraOrientation = Quat.multiply(Camera.orientation, orientation);
|
||||
if (settings.preventRoll) {
|
||||
cameraOrientation = Quat.cancelOutRoll(cameraOrientation);
|
||||
}
|
||||
Camera.orientation = cameraOrientation;
|
||||
}
|
||||
currentVelocities.rotation = Vec3.ZERO;
|
||||
}
|
||||
}
|
313
unpublishedScripts/marketplace/camera-move/hifi-jquery-ui.js
Normal file
313
unpublishedScripts/marketplace/camera-move/hifi-jquery-ui.js
Normal file
|
@ -0,0 +1,313 @@
|
|||
// extended jQuery UI controls
|
||||
|
||||
/* eslint-env console, jquery, browser */
|
||||
/* eslint-disable comma-dangle, no-empty */
|
||||
/* global assert, log, debugPrint */
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
// WIDGET BASE
|
||||
Object.assign($.Widget.prototype, {
|
||||
initHifiControl: function(hifiType) {
|
||||
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;
|
||||
},
|
||||
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);
|
||||
if (!element.is(selector)) {
|
||||
element = this.element.closest(selector);
|
||||
}
|
||||
var instance = element.filter(selector)[hifiType]('instance');
|
||||
|
||||
if (!instance && !quiet) {
|
||||
// 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')
|
||||
]);
|
||||
}
|
||||
return instance;
|
||||
},
|
||||
});
|
||||
|
||||
// CHECKBOX
|
||||
$.widget('ui.hifiCheckbox', $.ui.checkboxradio, {
|
||||
value: function value(nv) {
|
||||
if (arguments.length) {
|
||||
var currentValue = this.element.prop('checked');
|
||||
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 + ']');
|
||||
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) {
|
||||
this.element.removeAttr('checked');
|
||||
}
|
||||
this.refresh();
|
||||
}.bind(this));
|
||||
},
|
||||
});
|
||||
|
||||
// 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');
|
||||
nv = (nv === 'true' || !!nv);
|
||||
if (nv !== checked) {
|
||||
log('hifibutton checked changed', nv, checked);
|
||||
dataset.checked = nv;
|
||||
this.element.change();
|
||||
} else {
|
||||
log('hifibutton value same', nv, checked);
|
||||
}
|
||||
}
|
||||
return dataset.checked === 'true';
|
||||
},
|
||||
_create: function() {
|
||||
this.element[0].dataset.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 checkbox = this.hifiFindWidget('hifiCheckbox', true);
|
||||
if (!checkbox) {
|
||||
var input = $('<label><input type=checkbox id=' + fer + ' value=' + fer +' /></label>').hide();
|
||||
input.appendTo(this.element.parent());
|
||||
checkbox = input.find('input')
|
||||
.hifiCheckbox()
|
||||
.hifiCheckbox('instance');
|
||||
}
|
||||
checkbox.element.on('change._hifiButton', function() {
|
||||
log('checkbox -> button');
|
||||
this.value(checkbox.value());
|
||||
}.bind(this));
|
||||
this.element.on('change', function() {
|
||||
log('button -> checkbox');
|
||||
checkbox.value(this.value());
|
||||
}.bind(this));
|
||||
this.checkbox = checkbox;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
$.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);
|
||||
// console.log(this.element[0]);
|
||||
assert(this.element.attr('data-for'));
|
||||
this._super();
|
||||
|
||||
this.element.on('change._hifiRadioButton, click._hifiRadioButton', function() {
|
||||
var group = this.hifiFindWidget('hifiRadioGroup'),
|
||||
checked = !!this.element.attr('checked'),
|
||||
dotchecked = this.element.prop('checked'),
|
||||
value = this.element.attr('value');
|
||||
|
||||
if (dotchecked !== checked || group.value() !== value) {
|
||||
if (dotchecked && group.value() !== value) {
|
||||
log(value, 'UPDATING GRUOP', group.element[0].id);
|
||||
group.value(value);
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
});
|
||||
|
||||
// RADIO-GROUP
|
||||
$.widget('ui.hifiRadioGroup', $.ui.controlgroup, {
|
||||
radio: function(selector) {
|
||||
return this.element.find(':ui-hifiRadioButton' + selector).hifiRadioButton('instance');
|
||||
},
|
||||
refresh: function() {
|
||||
var value = this.value();
|
||||
this.element.find(':ui-hifiRadioButton').each(function() {
|
||||
$(this).prop('checked', $(this).attr('value') === value).hifiRadioButton('refresh');
|
||||
});
|
||||
this._super();
|
||||
},
|
||||
value: function value(nv) {
|
||||
if (arguments.length) {
|
||||
var id = this.element[0].id,
|
||||
previous = this.value();
|
||||
log('RADIOBUTTON GROUP value', id + ' = ' + nv + '(was: ' + previous + ')');
|
||||
this.element.attr('value', nv);
|
||||
this.refresh();
|
||||
}
|
||||
return this.element.attr('value');
|
||||
},
|
||||
_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._super();
|
||||
Object.defineProperty(this.element[0], 'value', {
|
||||
set: function(nv) {
|
||||
try {
|
||||
this.radio('#' + nv).value(true);
|
||||
} catch (e) {}
|
||||
return this.value();
|
||||
}.bind(this),
|
||||
get: function() {
|
||||
return this.element.attr('value');
|
||||
}.bind(this),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// SPINNER (numeric input w/ up/down arrows)
|
||||
$.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);
|
||||
this._value(num);
|
||||
this.element.change();
|
||||
}
|
||||
return parseFloat(this.element.val());
|
||||
},
|
||||
_value: function(value, allowAny) {
|
||||
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;
|
||||
}
|
||||
this._super();
|
||||
this.previous = null;
|
||||
this.element.on('change._hifiSpinner', function() {
|
||||
var value = this.value(),
|
||||
invalid = !this.isValid();
|
||||
debugPrint('hifiSpinner.changed', value, invalid ? '!!!invalid' : 'valid');
|
||||
!invalid && this.element.attr('value', value);
|
||||
}.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;
|
||||
}
|
||||
}
|
||||
return this._super( step, event );
|
||||
},
|
||||
_stop: function( event, ui ) {
|
||||
try {
|
||||
return this._super(event, ui);
|
||||
} finally {
|
||||
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;
|
||||
}
|
||||
}
|
||||
},
|
||||
_format: function(n) {
|
||||
var precision = this._precision();
|
||||
return parseFloat(n).toFixed(precision);
|
||||
},
|
||||
_events: {
|
||||
mousewheel: function(event, delta) {
|
||||
if (document.activeElement !== this.element[0]) {
|
||||
return;
|
||||
}
|
||||
// fix broken mousewheel on Chrome / webkit
|
||||
delta = delta === undefined ? event.originalEvent.deltaY : delta;
|
||||
$.ui.spinner.prototype._events.mousewheel.call(this, event, delta);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// SLIDER
|
||||
$.widget('ui.hifiSlider', $.ui.slider, {
|
||||
value: function value(nv) {
|
||||
if (arguments.length) {
|
||||
var num = this._trimAlignValue(nv);
|
||||
debugPrint('hifiSlider.value', nv, num);
|
||||
if (this.options.value !== num) {
|
||||
this.options.value = num;
|
||||
this.element.change();
|
||||
}
|
||||
}
|
||||
return this.options.value;
|
||||
},
|
||||
_create: function() {
|
||||
this.initHifiControl();
|
||||
assert(this.element.attr('data-for'));
|
||||
this._super();
|
||||
this.element
|
||||
.attr('type', this.element.attr('type') || 'slider')
|
||||
.find('.ui-slider-handle').html('<div class="inner-ui-slider-handle"></div>').end()
|
||||
.on('change', function() {
|
||||
this.hifiFindWidget('hifiSpinner').value(this.value());
|
||||
this._refresh();
|
||||
}.bind(this));
|
||||
},
|
||||
});
|
|
@ -1,14 +1,14 @@
|
|||
// EnumMeta.js -- helper module that maps related enum values to names and ids
|
||||
//
|
||||
|
||||
// // EnumMeta.js -- helper module that maps related enum values to names and ids
|
||||
// //
|
||||
/* eslint-env commonjs */
|
||||
/* global DriveKeys */
|
||||
/* global DriveKeys, console */
|
||||
|
||||
var VERSION = '0.0.1';
|
||||
var WANT_DEBUG = false;
|
||||
|
||||
function _debugPrint() {
|
||||
print('EnumMeta | ' + [].slice.call(arguments).join(' '));
|
||||
// eslint-disable-next-line no-console
|
||||
(typeof Script === 'object' ? print : console.log)('EnumMeta | ' + [].slice.call(arguments).join(' '));
|
||||
}
|
||||
|
||||
var debugPrint = WANT_DEBUG ? _debugPrint : function(){};
|
||||
|
@ -45,9 +45,8 @@ module.exports = {
|
|||
getActionNameFromDriveKeyName: getActionNameFromDriveKeyName,
|
||||
eventKeyText2KeyboardName: eventKeyText2KeyboardName,
|
||||
keyboardName2eventKeyText: keyboardName2eventKeyText,
|
||||
|
||||
ACTION_TRANSLATE_CAMERA_Z: ACTION_TRANSLATE_CAMERA_Z,
|
||||
INVALID_ACTION_ID: Controller.findAction('INVALID_ACTION_ID_FOO'),
|
||||
INVALID_ACTION_ID: Controller.findAction('INVALID_ACTION_ID_FOO')
|
||||
};
|
||||
|
||||
_debugPrint('///'+VERSION, Object.keys(module.exports));
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
"use strict";
|
||||
/* eslint-env commonjs, hifi */
|
||||
|
||||
//var HIRES_CLOCK = (typeof Window === 'object' && Window && Window.performance) && Window.performance.now;
|
||||
/* eslint-disable comma-dangle, no-empty */
|
||||
/* global HIRES_CLOCK, Desktop */
|
||||
// var HIRES_CLOCK = (typeof Window === 'object' && Window && Window.performance) && Window.performance.now;
|
||||
var USE_HIRES_CLOCK = typeof HIRES_CLOCK === 'function';
|
||||
|
||||
var exports = {
|
||||
|
@ -11,6 +12,7 @@ var exports = {
|
|||
bind: bind,
|
||||
signal: signal,
|
||||
assign: assign,
|
||||
sortedAssign: sortedAssign,
|
||||
sign: sign,
|
||||
assert: assert,
|
||||
getSystemMetadata: getSystemMetadata,
|
||||
|
@ -24,10 +26,11 @@ var exports = {
|
|||
normalizeStackTrace: normalizeStackTrace,
|
||||
BrowserUtils: BrowserUtils,
|
||||
BSOD: BSOD, // exception reporter
|
||||
_overlayWebWindow: _overlayWebWindow,
|
||||
};
|
||||
try {
|
||||
module.exports = exports; // Interface / Node.js
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
this._utils = assign(this._utils || {}, exports); // browser
|
||||
}
|
||||
|
||||
|
@ -38,19 +41,31 @@ function makeDebugRequire(relativeTo) {
|
|||
};
|
||||
}
|
||||
function debugRequire(id, relativeTo) {
|
||||
// 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);
|
||||
Script.require.cache[modulePath] = Script.require.cache[id] = undefined;
|
||||
Script.require.__qt_data__[modulePath] = Script.require.__qt_data__[id] = true;
|
||||
return Script.require(modulePath);
|
||||
if (typeof Script === 'object') {
|
||||
// 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);
|
||||
Script.require.cache[modulePath] = Script.require.cache[id] = undefined;
|
||||
Script.require.__qt_data__[modulePath] = Script.require.__qt_data__[id] = true;
|
||||
return Script.require(modulePath);
|
||||
} else {
|
||||
return require(id);
|
||||
}
|
||||
}
|
||||
|
||||
// examples:
|
||||
// assert(function assertion() { return (conditions === true) }, 'assertion failed!')
|
||||
// var neededValue = assert(idString, 'idString not specified!');
|
||||
// assert(false, 'unexpected state');
|
||||
function assert(truthy, message) {
|
||||
message = message || 'Assertion Failed:';
|
||||
|
||||
if (typeof truthy === 'function' && truthy.name === 'assertion') {
|
||||
message += ' ' + JSON.stringify((truthy+'').replace(/^[^{]+\{|\}$|^\s*|\s*$/g, ''));
|
||||
// extract function body to display with the assertion message
|
||||
var assertion = (truthy+'').replace(/[\r\n]/g, ' ')
|
||||
.replace(/^[^{]+\{|\}$|^\s*|\s*$/g, '').trim()
|
||||
.replace(/^return /,'').replace(/\s[\r\n\t\s]+/g, ' ');
|
||||
message += ' ' + JSON.stringify(assertion);
|
||||
try {
|
||||
truthy = truthy();
|
||||
} catch (e) {
|
||||
|
@ -58,7 +73,7 @@ function assert(truthy, message) {
|
|||
}
|
||||
}
|
||||
if (!truthy) {
|
||||
message += ' (! '+truthy+')';
|
||||
message += ' ('+truthy+')';
|
||||
throw new Error(message);
|
||||
}
|
||||
return truthy;
|
||||
|
@ -100,6 +115,27 @@ function assign(target, varArgs) { // .length of function is 2
|
|||
/* eslint-enable */
|
||||
// //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill
|
||||
|
||||
// hack to sort keys in v8 for prettier JSON exports
|
||||
function sortedAssign(target, sources) {
|
||||
var allParams = assign.apply(this, [{}].concat([].slice.call(arguments)));
|
||||
for (var p in target) {
|
||||
delete target[p];
|
||||
}
|
||||
Object.keys(allParams).sort(function(a,b) {
|
||||
function swapCase(ch) {
|
||||
return /[A-Z]/.test(ch) ? ch.toLowerCase() : ch.toUpperCase();
|
||||
}
|
||||
a = a.replace(/^./, swapCase);
|
||||
b = b.replace(/^./, swapCase);
|
||||
a = /Version/.test(a) ? 'AAAA'+a : a;
|
||||
b = /Version/.test(b) ? 'AAAA'+b : b;
|
||||
return a < b ? -1 : a > b ? 1 : 0;
|
||||
}).forEach(function(key) {
|
||||
target[key] = allParams[key];
|
||||
});
|
||||
return target;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// @function - bind a function to a `this` context
|
||||
// @param {Object} - the `this` context
|
||||
|
@ -204,13 +240,13 @@ function BrowserUtils(global) {
|
|||
this.log('openEventBridge |', 'typeof global.EventBridge == ' + typeof global.EventBridge);
|
||||
try {
|
||||
global.EventBridge.scriptEventReceived.connect.exists;
|
||||
//this.log('openEventBridge| EventBridge already exists... -- invoking callback', 'typeof EventBridge == ' + typeof global.EventBridge);
|
||||
// this.log('openEventBridge| EventBridge already exists... -- invoking callback', 'typeof EventBridge == ' + typeof global.EventBridge);
|
||||
return callback(global.EventBridge);
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
this.log('EventBridge does not yet exist -- attempting to instrument via qt.webChannelTransport');
|
||||
var QWebChannel = assert(global.QWebChannel, 'expected global.QWebChannel to exist'),
|
||||
qt = assert(global.qt, 'expected global.qt to exist'),
|
||||
webChannelTransport = assert(qt.webChannelTransport, 'expected global.qt.webChannelTransport to exist');
|
||||
qt = assert(global.qt, 'expected global.qt to exist');
|
||||
assert(qt.webChannelTransport, 'expected global.qt.webChannelTransport to exist');
|
||||
new QWebChannel(qt.webChannelTransport, bind(this, function (channel) {
|
||||
global.EventBridge = channel.objects.eventBridgeWrapper.eventBridge;
|
||||
global.EventBridge.$WebChannel = channel;
|
||||
|
@ -245,35 +281,35 @@ DeferredUpdater.prototype = {
|
|||
submit: function() {
|
||||
var meta = this._meta,
|
||||
target = meta.target,
|
||||
lastValue = meta.lastValue,
|
||||
dedupe = meta.dedupe,
|
||||
// lastValue = meta.lastValue,
|
||||
// dedupe = meta.dedupe,
|
||||
self = this,
|
||||
submitted = {};
|
||||
|
||||
self.submit = getRuntimeSeconds();
|
||||
Object.keys(self).forEach(function(property) {
|
||||
var newValue = self[property];
|
||||
if (0 && 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;
|
||||
}
|
||||
// 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);
|
||||
|
@ -315,7 +351,7 @@ DeferredUpdater.createGroup = function(items, options) {
|
|||
getRuntimeSeconds.EPOCH = getRuntimeSeconds(0);
|
||||
function getRuntimeSeconds(since) {
|
||||
since = since === undefined ? getRuntimeSeconds.EPOCH : since;
|
||||
var now = USE_HIRES_CLOCK ? HIRES_CLOCK() : +new Date;
|
||||
var now = USE_HIRES_CLOCK ? new HIRES_CLOCK() : +new Date;
|
||||
return ((now / 1000.0) - since);
|
||||
}
|
||||
|
||||
|
@ -360,23 +396,26 @@ function KeyListener(options) {
|
|||
assign(this, {
|
||||
modifiers: this._getEventModifiers(options, true)
|
||||
}, options);
|
||||
log('created KeyListener', JSON.stringify(this, 0, 2));
|
||||
this.log = options.log || function log() {
|
||||
print('KeyListener | ', [].slice.call(arguments).join(' '));
|
||||
};
|
||||
this.log('created KeyListener', JSON.stringify(this.text), this.modifiers);
|
||||
this.connect();
|
||||
}
|
||||
KeyListener.prototype = {
|
||||
_getEventModifiers: function(event, trueOnly) {
|
||||
return [ 'Control', 'Meta', 'Alt', 'Super', 'Menu', 'Shifted' ].map(function(mod) {
|
||||
return '(' + [ 'Control', 'Meta', 'Alt', 'Super', 'Menu', 'Shifted' ].map(function(mod) {
|
||||
var isMod = 'is' + mod,
|
||||
value = event[isMod],
|
||||
found = (trueOnly ? value : typeof value === 'boolean');
|
||||
return found && isMod + ' = ' + value;
|
||||
}).filter(Boolean).sort().join('|');
|
||||
return found && isMod + ' == ' + value;
|
||||
}).filter(Boolean).sort().join(' | ') + ')';
|
||||
},
|
||||
handleEvent: function(event, target) {
|
||||
if (event.text === this.text) {
|
||||
var modifiers = this._getEventModifiers(event, true);
|
||||
if (modifiers !== this.modifiers) {
|
||||
return log('KeyListener -- different modifiers, disregarding keystroke', JSON.stringify({
|
||||
return this.log('KeyListener -- different modifiers, disregarding keystroke', JSON.stringify({
|
||||
expected: this.modifiers,
|
||||
received: modifiers,
|
||||
},0,2));
|
||||
|
@ -438,9 +477,9 @@ function BSOD(options, callback) {
|
|||
var HTML = [
|
||||
'<style>body { background:#0000aa; color:#ffffff; font-family:courier; font-size:8pt; margin:10px; }</style>',
|
||||
buttonHTML,
|
||||
'<pre style=whitespace:pre-wrap>',
|
||||
'<pre style="white-space:pre-wrap;">',
|
||||
'<strong>' + options.error + '</strong>',
|
||||
_utils.normalizeStackTrace(options.error, {
|
||||
normalizeStackTrace(options.error, {
|
||||
wrapLineNumbersWith: ['<b style=color:lime>','</b>'],
|
||||
wrapFilesWith: ['<b style=color:#f99>','</b>'],
|
||||
}),
|
||||
|
@ -454,7 +493,7 @@ function BSOD(options, callback) {
|
|||
content: HTML,
|
||||
});
|
||||
popup.webEventReceived.connect(function(message) {
|
||||
log('popup.webEventReceived', message);
|
||||
print('popup.webEventReceived', message);
|
||||
try {
|
||||
callback(null, message);
|
||||
} finally {
|
||||
|
@ -500,12 +539,15 @@ function getSystemMetadata() {
|
|||
}
|
||||
|
||||
function reloadClientScript(filename) {
|
||||
function log() {
|
||||
print('reloadClientScript | ', [].slice.call(arguments).join(' '));
|
||||
}
|
||||
log('reloading', 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);
|
||||
// log(script.path, script.url);
|
||||
return 0 === script.path.indexOf(filename);
|
||||
});
|
||||
log('...matches', JSON.stringify(matches,0,2));
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
"use strict";
|
||||
/* eslint-env commonjs */
|
||||
|
||||
/* global log */
|
||||
module.exports = {
|
||||
version: '0.0.1a',
|
||||
ApplicationConfig: ApplicationConfig,
|
||||
|
@ -47,16 +47,12 @@ function ApplicationConfig(options) {
|
|||
this.register(options.config);
|
||||
}
|
||||
ApplicationConfig.prototype = {
|
||||
resolve: function(key) {
|
||||
resolve: function resolve(key) {
|
||||
assert(typeof key === 'string', 'ApplicationConfig.resolve error: key is not a string: ' + key);
|
||||
if (0 !== key.indexOf('.') && !~key.indexOf('/')) {
|
||||
key = [ this.namespace, key ].join('/');
|
||||
}
|
||||
if (key in this.config) {
|
||||
return key;
|
||||
}
|
||||
log('ApplicationConfig -- could not resolve key: ' + key);
|
||||
return undefined;
|
||||
return (key in this.config) ? key : (debugPrint('ApplicationConfig -- could not resolve key: ' + key),undefined);
|
||||
},
|
||||
registerItem: function(settingName, item) {
|
||||
item._settingName = settingName;
|
||||
|
@ -66,9 +62,7 @@ ApplicationConfig.prototype = {
|
|||
register: function(items) {
|
||||
for (var p in items) {
|
||||
var item = items[p];
|
||||
if (item) {
|
||||
this.registerItem(p, item)
|
||||
}
|
||||
item && this.registerItem(p, item);
|
||||
}
|
||||
},
|
||||
_getItem: function(key) {
|
||||
|
@ -116,7 +110,6 @@ function ApplicationConfigItem(item) {
|
|||
_menu: this._parseMenuConfig(this.menu),
|
||||
_object: this._parseObjectConfig(this.object)
|
||||
});
|
||||
if(0)debugPrint('>>>>>' + this);
|
||||
}
|
||||
ApplicationConfigItem.prototype = {
|
||||
authority: 'object', // when values conflict, this determines which source is considered the truth
|
||||
|
@ -181,11 +174,11 @@ ApplicationConfigItem.prototype = {
|
|||
return {
|
||||
object: object, property: getter,
|
||||
get: function() {
|
||||
//log('======> get API, property', object, getter, this.object[this.property]);
|
||||
// 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);
|
||||
// log('======> set API, property', object, getter, this.object[this.property], nv);
|
||||
return this.object[this.property] = nv;
|
||||
}
|
||||
};
|
||||
|
@ -234,11 +227,8 @@ function SettingsConfig(options) {
|
|||
SettingsConfig.prototype = {
|
||||
resolve: function(key) {
|
||||
assert(typeof key === 'string', 'SettingsConfig.resolve error: key is not a string: ' + key);
|
||||
if (0 !== key.indexOf('.') && !~key.indexOf('/')) {
|
||||
return [ this.namespace, key ].join('/');
|
||||
} else {
|
||||
return key;
|
||||
}
|
||||
return (0 !== key.indexOf('.') && !~key.indexOf('/')) ?
|
||||
[ this.namespace, key ].join('/') : key;
|
||||
},
|
||||
getValue: function(key, defaultValue) {
|
||||
key = this.resolve(key);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// CustomSettingsApp.js -- manages Settings between a Client script and connected "settings" tablet WebView page
|
||||
// see html.bridgedSettings.js for the webView side
|
||||
// see browser/BridgedSettings.js for the webView side
|
||||
|
||||
// example:
|
||||
// var button = tablet.addButton({ text: 'My Settings' });
|
||||
|
@ -11,8 +11,8 @@
|
|||
// });
|
||||
//
|
||||
// // settings are automatically sync'd from the web page back to Interface; to be notified when that happens, use:
|
||||
// myAppSettings.settingUpdated.connect(function(name, value, origin) {
|
||||
// print('setting updated from web page', name, value, origin);
|
||||
// myAppSettings.valueUpdated.connect(function(name, value, oldValue, origin) {
|
||||
// print('setting updated from web page', name, value, oldValue, origin);
|
||||
// });
|
||||
//
|
||||
// // settings are also automatically sync'd from Interface back to the web page; to manually sync a value, use:
|
||||
|
@ -127,7 +127,7 @@ CustomSettingsApp.prototype = {
|
|||
this.sendEvent({ id: 'valueUpdated', params: [key, value, oldValue, origin] });
|
||||
this._activeSettings.sent[key] = value;
|
||||
this._activeSettings.remote[key] = value;
|
||||
this.valueUpdated(key, value, oldValue, (origin ? origin+':' : '') + 'CustomSettingsApp.syncValue');
|
||||
this.valueUpdated(key, value, oldValue, (origin ? origin+':' : '') + 'CustomSettingsApp.syncValue');
|
||||
},
|
||||
onScreenChanged: function onScreenChanged(type, url) {
|
||||
this.settingsScreenVisible = (url === this.url);
|
||||
|
@ -172,8 +172,8 @@ CustomSettingsApp.prototype = {
|
|||
this._activeSettings.remote[key] = obj.result;
|
||||
break;
|
||||
}
|
||||
case 'setValue': {
|
||||
obj.result = this._setValue(key, value, params[2], params[3]);
|
||||
case 'setValue': {
|
||||
obj.result = this._setValue(key, value, params[2], params[3]);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
|
@ -200,6 +200,8 @@ CustomSettingsApp.prototype = {
|
|||
}
|
||||
},
|
||||
onWebEventReceived: function onWebEventReceived(msg) {
|
||||
// !/xSettings[.]getValue/.test(msg) &&
|
||||
_debugPrint('onWebEventReceived', msg);
|
||||
var tablet = this.tablet;
|
||||
if (!tablet) {
|
||||
throw new Error('onWebEventReceived called when not connected to tablet...');
|
||||
|
@ -216,7 +218,7 @@ CustomSettingsApp.prototype = {
|
|||
if (validSender) {
|
||||
this._handleValidatedMessage(obj, msg);
|
||||
} else {
|
||||
debugPrint('skipping', JSON.stringify([obj.ns, obj.uuid]), JSON.stringify(this), msg);
|
||||
debugPrint('xskipping', JSON.stringify([obj.ns, obj.uuid]), JSON.stringify(this), msg);
|
||||
}
|
||||
} catch (e) {
|
||||
_debugPrint('rpc error:', e, msg);
|
||||
|
@ -236,7 +238,7 @@ CustomSettingsApp.prototype = {
|
|||
_debugPrint(
|
||||
'[onAPIValueUpdated @ ' + origin + ']',
|
||||
key + ' = ' + JSON.stringify(newValue), '(was: ' + JSON.stringify(oldValue) +')',
|
||||
JSON.stringify(this._activeSettings.get(key),0,2));
|
||||
JSON.stringify(this._activeSettings.get(key)));
|
||||
this.syncValue(key, newValue, (origin ? origin+':' : '') + 'CustomSettingsApp.onAPIValueUpdated');
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// see ../CustomSettingsApp.js for the corresponding Interface script
|
||||
|
||||
/* eslint-env commonjs, browser */
|
||||
/* eslint-disable comma-dangle, no-empty */
|
||||
|
||||
(function(global) {
|
||||
"use strict";
|
||||
|
||||
|
@ -13,13 +15,12 @@
|
|||
global.BridgedSettings = BridgedSettings;
|
||||
}
|
||||
|
||||
var _utils = global._utils || (global.require && global.require('../_utils.js'));
|
||||
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)');
|
||||
}
|
||||
var signal = _utils.signal,
|
||||
assert = _utils.assert;
|
||||
var signal = _utils.signal;
|
||||
|
||||
function log() {
|
||||
console.info('bridgedSettings | ' + [].slice.call(arguments).join(' ')); // eslint-disable-line no-console
|
||||
|
@ -30,37 +31,36 @@
|
|||
|
||||
function BridgedSettings(options) {
|
||||
options = options || {};
|
||||
this.eventBridge = options.eventBridge || global.EventBridge;
|
||||
this.namespace = options.namespace || 'BridgedSettings';
|
||||
this.uuid = options.uuid || undefined;
|
||||
this.valueUpdated = signal(function valueUpdated(key, newValue, oldValue, origin){});
|
||||
this.callbackError = signal(function callbackError(error, message){});
|
||||
this.pendingRequestsFinished = signal(function pendingRequestsFinished(){});
|
||||
this.extraParams = {};
|
||||
Object.assign(this, {
|
||||
eventBridge: options.eventBridge || global.EventBridge,
|
||||
namespace: options.namespace || 'BridgedSettings',
|
||||
uuid: options.uuid || undefined,
|
||||
valueReceived: signal(function valueReceived(key, newValue, oldValue, origin){}),
|
||||
callbackError: signal(function callbackError(error, message){}),
|
||||
pendingRequestsFinished: signal(function pendingRequestsFinished(){}),
|
||||
extraParams: options.extraParams || {},
|
||||
_hifiValues: {},
|
||||
|
||||
// keep track of accessed settings so they can be kept in sync when changed on this side
|
||||
this._activeSettings = {
|
||||
sent: {},
|
||||
received: {},
|
||||
remote: {},
|
||||
};
|
||||
|
||||
this.debug = options.debug;
|
||||
this.log = log.bind({}, this.namespace + ' |');
|
||||
this.debugPrint = function() { return this.debug && this.log.apply(this, arguments); };
|
||||
|
||||
this.log('connecting to EventBridge.scriptEventReceived');
|
||||
this._boundScriptEventReceived = this.onScriptEventReceived.bind(this);
|
||||
this.eventBridge.scriptEventReceived.connect(this._boundScriptEventReceived);
|
||||
|
||||
this.callbacks = Object.defineProperties(options.callbacks || {}, {
|
||||
extraParams: { value: this.handleExtraParams },
|
||||
valueUpdated: { value: this.handleValueUpdated },
|
||||
debug: options.debug,
|
||||
log: log.bind({}, options.namespace + ' |'),
|
||||
debugPrint: function() {
|
||||
return this.debug && this.log.apply(this, arguments);
|
||||
},
|
||||
_boundScriptEventReceived: this.onScriptEventReceived.bind(this),
|
||||
callbacks: Object.defineProperties(options.callbacks || {}, {
|
||||
extraParams: { value: this.handleExtraParams },
|
||||
valueUpdated: { value: this.handleValueUpdated },
|
||||
})
|
||||
});
|
||||
this.log('connecting to EventBridge.scriptEventReceived');
|
||||
this.eventBridge.scriptEventReceived.connect(this._boundScriptEventReceived);
|
||||
}
|
||||
|
||||
BridgedSettings.prototype = {
|
||||
_callbackId: 1,
|
||||
toString: function() {
|
||||
return '[BridgedSettings namespace='+this.namespace+']';
|
||||
},
|
||||
resolve: function(key) {
|
||||
if (0 !== key.indexOf('.') && !~key.indexOf('/')) {
|
||||
return [ this.namespace, key ].join('/');
|
||||
|
@ -74,89 +74,84 @@
|
|||
value = msg.params[1],
|
||||
oldValue = msg.params[2],
|
||||
origin = msg.params[3];
|
||||
log('callbacks.valueUpdated', key, value, oldValue, origin);
|
||||
this._activeSettings.received[key] = this._activeSettings.remote[key] = value;
|
||||
this.valueUpdated(key, value, oldValue, (origin?origin+':':'') + 'callbacks.valueUpdated');
|
||||
log('callbacks.valueUpdated', key, value, oldValue, origin);
|
||||
this._hifiValues[key] = value;
|
||||
this.valueReceived(key, value, oldValue, (origin?origin+':':'') + 'callbacks.valueUpdated');
|
||||
},
|
||||
handleExtraParams: function(msg) {
|
||||
// client script sent us extraParams
|
||||
var extraParams = msg.extraParams;
|
||||
Object.assign(this.extraParams, extraParams);
|
||||
var previousParams = JSON.parse(JSON.stringify(this.extraParams));
|
||||
|
||||
_utils.sortedAssign(this.extraParams, extraParams);
|
||||
|
||||
this._hifiValues['.extraParams'] = this.extraParams;
|
||||
this.debugPrint('received .extraParams', JSON.stringify(extraParams,0,2));
|
||||
var key = '.extraParams';
|
||||
this._activeSettings.received[key] = this._activeSettings.remote[key] = extraParams;
|
||||
this.valueUpdated(key, this.extraParams, this.extraParams, 'html.bridgedSettings.handleExtraParams');
|
||||
this.valueReceived('.extraParams', this.extraParams, previousParams, 'html.bridgedSettings.handleExtraParams');
|
||||
},
|
||||
cleanup: function() {
|
||||
try {
|
||||
this.eventBridge.scriptEventReceived.disconnect(this._boundScriptEventReceived);
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
this.log('error disconnecting from scriptEventReceived:', e);
|
||||
}
|
||||
},
|
||||
pendingRequestCount: function() {
|
||||
return Object.keys(this.callbacks).length;
|
||||
},
|
||||
onScriptEventReceived: function(_msg) {
|
||||
var error;
|
||||
this.debugPrint(this.namespace, '_onScriptEventReceived......' + _msg);
|
||||
try {
|
||||
var msg = JSON.parse(_msg);
|
||||
if (msg.ns === this.namespace && msg.uuid === this.uuid) {
|
||||
this.debugPrint('_onScriptEventReceived', msg);
|
||||
var callback = this.callbacks[msg.id],
|
||||
handled = false,
|
||||
debug = this.debug;
|
||||
if (callback) {
|
||||
try {
|
||||
callback.call(this, msg);
|
||||
handled = true;
|
||||
} catch (e) {
|
||||
error = e;
|
||||
this.log('CALLBACK ERROR', this.namespace, msg.id, '_onScriptEventReceived', e);
|
||||
this.callbackError(error, msg);
|
||||
if (debug) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!handled) {
|
||||
if (this.onUnhandledMessage) {
|
||||
return this.onUnhandledMessage(msg, _msg);
|
||||
} else {
|
||||
error = new Error('unhandled message: ' + _msg);
|
||||
}
|
||||
_handleValidatedMessage: function(obj, msg) {
|
||||
var callback = this.callbacks[obj.id];
|
||||
if (callback) {
|
||||
try {
|
||||
return callback.call(this, obj) || true;
|
||||
} catch (e) {
|
||||
this.log('CALLBACK ERROR', this.namespace, obj.id, '_onScriptEventReceived', e);
|
||||
this.callbackError(e, obj);
|
||||
if (this.debug) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
} catch (e) { error = e; }
|
||||
if (this.debug && error) {
|
||||
throw error;
|
||||
} else if (this.onUnhandledMessage) {
|
||||
return this.onUnhandledMessage(obj, msg);
|
||||
}
|
||||
},
|
||||
onScriptEventReceived: function(msg) {
|
||||
this.debugPrint(this.namespace, '_onScriptEventReceived......' + msg);
|
||||
try {
|
||||
var obj = JSON.parse(msg);
|
||||
var validSender = obj.ns === this.namespace && obj.uuid === this.uuid;
|
||||
if (validSender) {
|
||||
return this._handleValidatedMessage(obj, msg);
|
||||
} else {
|
||||
debugPrint('xskipping', JSON.stringify([obj.ns, obj.uuid]), JSON.stringify(this), msg);
|
||||
}
|
||||
} catch (e) {
|
||||
log('rpc error:', e, msg);
|
||||
return e;
|
||||
}
|
||||
return error;
|
||||
},
|
||||
sendEvent: function(msg) {
|
||||
msg.ns = msg.ns || this.namespace;
|
||||
msg.uuid = msg.uuid || this.uuid;
|
||||
debugPrint('sendEvent', JSON.stringify(msg));
|
||||
this.eventBridge.emitWebEvent(JSON.stringify(msg));
|
||||
},
|
||||
getValue: function(key, defaultValue) {
|
||||
key = this.resolve(key);
|
||||
return key in this._activeSettings.remote ? this._activeSettings.remote[key] : defaultValue;
|
||||
return key in this._hifiValues ? this._hifiValues[key] : defaultValue;
|
||||
},
|
||||
setValue: function(key, value) {
|
||||
key = this.resolve(key);
|
||||
var current = this.getValue(key);
|
||||
if (current !== value) {
|
||||
log('SET VALUE : ' + JSON.stringify({ key: key, current: current, value: value }));
|
||||
return this.syncValue(key, value, 'setValue');
|
||||
}
|
||||
this._hifiValues[key] = value;
|
||||
return false;
|
||||
},
|
||||
syncValue: function(key, value, origin) {
|
||||
key = this.resolve(key);
|
||||
var oldValue = this._activeSettings.remote[key];
|
||||
this.sendEvent({ method: 'valueUpdated', params: [key, value, oldValue, origin] });
|
||||
this._activeSettings.sent[key] = this._activeSettings.remote[key] = value;
|
||||
this.valueUpdated(key, value, oldValue, (origin ? origin+':' : '') + 'html.bridgedSettings.syncValue');
|
||||
return this.sendEvent({ method: 'valueUpdated', params: [key, value, this.getValue(key), origin] });
|
||||
},
|
||||
getValueAsync: function(key, defaultValue, callback) {
|
||||
key = this.resolve(key);
|
||||
|
@ -174,34 +169,20 @@
|
|||
try {
|
||||
callback(obj.error, obj.result);
|
||||
if (!obj.error) {
|
||||
this._activeSettings.received[key] = this._activeSettings.remote[key] = obj.result;
|
||||
this._hifiValues[key] = obj.result;
|
||||
}
|
||||
} finally {
|
||||
delete this.callbacks[event.id];
|
||||
}
|
||||
this.pendingRequestCount() === 0 && this.pendingRequestsFinished();
|
||||
if (this.pendingRequestCount() === 0) {
|
||||
setTimeout(function() {
|
||||
this.pendingRequestsFinished();
|
||||
}.bind(this), 1);
|
||||
}
|
||||
};
|
||||
}
|
||||
this.sendEvent(event);
|
||||
},
|
||||
setValueAsync: function(key, value, callback) {
|
||||
key = this.resolve(key);
|
||||
this.log('< setValueAsync', key, value);
|
||||
var params = [ key, value ],
|
||||
event = { method: 'Settings.setValue', params: params };
|
||||
if (callback) {
|
||||
event.id = this._callbackId++;
|
||||
this.callbacks[event.id] = function(obj) {
|
||||
try {
|
||||
callback(obj.error, obj.result);
|
||||
} finally {
|
||||
delete this.callbacks[event.id];
|
||||
}
|
||||
};
|
||||
}
|
||||
this._activeSettings.sent[key] = this._activeSettings.remote[key] = value;
|
||||
this.sendEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
})(this);
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
// JQuerySettings.js -- HTML-side helper class for managing settings-linked jQuery UI elements
|
||||
|
||||
/* eslint-env commonjs, browser */
|
||||
/* eslint-env jquery, commonjs, browser */
|
||||
/* eslint-disable comma-dangle, no-empty */
|
||||
/* global assert, log */
|
||||
// ----------------------------------------------------------------------------
|
||||
(function(global) {
|
||||
"use strict";
|
||||
|
||||
|
@ -9,13 +12,13 @@
|
|||
try {
|
||||
module.exports = JQuerySettings;
|
||||
} catch (e) {
|
||||
global.JQuerySettings= JQuerySettings
|
||||
global.JQuerySettings= JQuerySettings;
|
||||
}
|
||||
|
||||
var _utils = global._utils || (global.require && global.require('../_utils.js'));
|
||||
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)');
|
||||
throw new Error('html.JQuerySettings.js -- expected _utils to be available on the global object (ie: window._utils)'+module);
|
||||
}
|
||||
var signal = _utils.signal,
|
||||
assert = _utils.assert;
|
||||
|
@ -29,20 +32,77 @@
|
|||
|
||||
function JQuerySettings(options) {
|
||||
assert('namespace' in options);
|
||||
Object.assign(this, {
|
||||
id2Setting: {}, // DOM id -> qualified Settings key
|
||||
Setting2id: {}, // qualified Settings key -> DOM id
|
||||
_activeSettings: {
|
||||
received: {}, // from DOM elements
|
||||
sent: {}, // to DOM elements
|
||||
remote: {}, // MRU values
|
||||
},
|
||||
|
||||
Object.assign(this, {
|
||||
id2Setting: {}, // DOM id -> qualified Settings key
|
||||
Setting2id: {}, // qualified Settings key -> DOM id
|
||||
observers: {}, // DOM MutationObservers
|
||||
mutationEvent: signal(function mutationEvent(event) {}),
|
||||
boundOnDOMMutation: this._onDOMMutation.bind(this),
|
||||
}, options);
|
||||
this.valueUpdated = signal(function valueUpdated(key, value, oldValue, origin){});
|
||||
}
|
||||
JQuerySettings.prototype = {
|
||||
toString: function() {
|
||||
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) {
|
||||
var target = mutation.target,
|
||||
targetId = target.dataset['for'] || target.id,
|
||||
domId = target.id,
|
||||
attrValue = target.getAttribute(mutation.attributeName),
|
||||
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,
|
||||
oldValue: oldValue,
|
||||
hifiType: hifiType,
|
||||
domId: domId,
|
||||
domType: target.getAttribute('type') || target.type,
|
||||
targetId: targetId,
|
||||
attrValue: attrValue,
|
||||
domName: target.name,
|
||||
type: mutation.type,
|
||||
};
|
||||
|
||||
switch (typeof value) {
|
||||
case 'boolean': event.oldValue = !!event.oldValue; break;
|
||||
case 'number':
|
||||
var tmp = parseFloat(oldValue);
|
||||
if (isFinite(tmp)) {
|
||||
event.oldValue = tmp;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return (event.oldValue === event.value) ?
|
||||
debugPrint('SKIP NON-MUTATION', event.key, event.hifiType) :
|
||||
this.mutationEvent(event);
|
||||
}.bind(this));
|
||||
},
|
||||
observeNode: function(node) {
|
||||
assert(node.id);
|
||||
var observer = this.observers[node.id];
|
||||
if (!observer) {
|
||||
observer = new MutationObserver(this.boundOnDOMMutation);
|
||||
observer.observe(node, this.mutationConfig);
|
||||
this.observers[node.id] = observer;
|
||||
}
|
||||
debugPrint('observeNode', node.id, node.dataset.hifiType, node.name);
|
||||
return observer;
|
||||
},
|
||||
resolve: function(key) {
|
||||
assert(typeof key === 'string');
|
||||
if (0 !== key.indexOf('.') && !~key.indexOf('/')) {
|
||||
return [ this.namespace, key ].join('/');
|
||||
} else {
|
||||
|
@ -51,181 +111,82 @@
|
|||
},
|
||||
registerSetting: function(id, key) {
|
||||
this.id2Setting[id] = key;
|
||||
this.Setting2id[key] = id;
|
||||
if (!(key in this.Setting2id)) {
|
||||
this.Setting2id[key] = id;
|
||||
} else {
|
||||
key = null;
|
||||
}
|
||||
debugPrint('JQuerySettings.registerSetting -- registered: ' + JSON.stringify({ id: id, key: key }));
|
||||
},
|
||||
registerNode: function(node) {
|
||||
var name = node.name || node.id,
|
||||
key = this.resolve(name);
|
||||
this.registerSetting(node.id, key);
|
||||
if (node.type === 'radio') {
|
||||
// for radio buttons also map the overall radio-group to the key
|
||||
this.registerSetting(name, key);
|
||||
}
|
||||
// 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);
|
||||
|
||||
debugPrint('registerNode', id, key);
|
||||
// return this.observeNode(node);
|
||||
},
|
||||
// lookup the DOM id for a given Settings key
|
||||
getId: function(key, missingOk) {
|
||||
key = this.resolve(key);
|
||||
return assert(this.Setting2id[key] || missingOk || true, 'jquerySettings.getId: !Setting2id['+key+']');
|
||||
assert(missingOk || function assertion(){
|
||||
return typeof key === 'string';
|
||||
});
|
||||
if (key in this.Setting2id || missingOk) {
|
||||
return this.Setting2id[key];
|
||||
}
|
||||
log('WARNING: jquerySettings.getId: !Setting2id['+key+'] ' + this.Setting2id[key], key in this.Setting2id);
|
||||
},
|
||||
getAllNodes: function() {
|
||||
return Object.keys(this.Setting2id)
|
||||
.map(function(key) {
|
||||
return this.findNodeByKey(key);
|
||||
}.bind(this))
|
||||
.filter(function(node) {
|
||||
return node.type !== 'placeholder';
|
||||
}).filter(Boolean);
|
||||
},
|
||||
// lookup the Settings key for a given DOM id
|
||||
getKey: function(id, missingOk) {
|
||||
return assert(this.id2Setting[id] || missingOk || true, 'jquerySettings.getKey: !id2Setting['+id+']');
|
||||
if ((id in this.id2Setting) || missingOk) {
|
||||
return this.id2Setting[id];
|
||||
}
|
||||
log('WARNING: jquerySettings.getKey: !id2Setting['+id+']');
|
||||
},
|
||||
// lookup the DOM node for a given Settings key
|
||||
findNodeByKey: function(key, missingOk) {
|
||||
key = this.resolve(key);
|
||||
var id = this.getId(key, missingOk);
|
||||
var node = document.getElementById(id);
|
||||
if (typeof node !== 'object') {
|
||||
log('jquerySettings.getNodeByKey -- node not found:', 'key=='+key, 'id=='+id);
|
||||
var node = typeof id === 'object' ? id : document.getElementById(id);
|
||||
if (node || missingOk) {
|
||||
return node;
|
||||
}
|
||||
return node;
|
||||
},
|
||||
_notifyValueUpdated: function(key, value, oldValue, origin) {
|
||||
this._activeSettings.sent[key] = value;
|
||||
this._activeSettings.remote[key] = value;
|
||||
this.valueUpdated(this.resolve(key), value, oldValue, origin);
|
||||
log('WARNING: jquerySettings.findNodeByKey -- node not found:', 'key=='+key, 'id=='+id);
|
||||
},
|
||||
getValue: function(key, defaultValue) {
|
||||
key = this.resolve(key);
|
||||
var node = this.findNodeByKey(key);
|
||||
if (node) {
|
||||
var value = this.__getNodeValue(node);
|
||||
this._activeSettings.remote[key] = value;
|
||||
this._activeSettings.received[key] = value;
|
||||
return value;
|
||||
}
|
||||
return defaultValue;
|
||||
return this.getNodeValue(this.findNodeByKey(key));
|
||||
},
|
||||
setValue: function(key, value, origin) {
|
||||
key = this.resolve(key);
|
||||
var lastValue = this.getValue(key, value);
|
||||
if (lastValue !== value || origin) {
|
||||
var node = assert(this.findNodeByKey(key), 'jquerySettings.setValue -- node not found: ' + key);
|
||||
var ret = this.__setNodeValue(node, value);
|
||||
if (origin) {
|
||||
this._notifyValueUpdated(key, value, lastValue, origin || 'jquerySettings.setValue');
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
return this.setNodeValue(this.findNodeByKey(key), value, origin || 'setValue');
|
||||
},
|
||||
resyncValue: function(key, origin) {
|
||||
var sentValue = key in this._activeSettings.sent ? this._activeSettings.sent[key] : null,
|
||||
receivedValue = key in this._activeSettings.received ? this._activeSettings.received[key] : null,
|
||||
currentValue = this.getValue(key, null);
|
||||
|
||||
log('resyncValue', JSON.stringify({
|
||||
key: key, current: currentValue, sent: sentValue, received: receivedValue,
|
||||
'current !== received': [typeof currentValue, currentValue+'', typeof receivedValue, receivedValue+'', currentValue !== receivedValue],
|
||||
'current !== sent': [currentValue+'', sentValue+'', currentValue !== sentValue],
|
||||
}));
|
||||
|
||||
if (currentValue !== receivedValue || currentValue !== sentValue) {
|
||||
this._notifyValueUpdated(key, currentValue, sentValue, (origin?origin+':':'')+'jquerySettings.resyncValue');
|
||||
}
|
||||
},
|
||||
|
||||
domAccessors: {
|
||||
'default': {
|
||||
get: function() { return this.value; },
|
||||
set: function(nv) { $(this).val(nv); },
|
||||
},
|
||||
'checkbox': {
|
||||
get: function() { return this.checked; },
|
||||
set: function(nv) { $(this).prop('checked', nv); },
|
||||
},
|
||||
'number': {
|
||||
get: function() { return parseFloat(this.value); },
|
||||
set: function(nv) {
|
||||
var step = this.step || 1, precision = (1/step).toString(this).length - 1;
|
||||
var value = parseFloat(newValue).toFixed(precision);
|
||||
if (isNaN(value)) {
|
||||
log('domAccessors.number.set', id, 'ignoring NaN value:', value+'');
|
||||
return;
|
||||
}
|
||||
$(this).val(value);
|
||||
},
|
||||
},
|
||||
'radio': {
|
||||
get: function() { assert(false, 'use radio-group to get current selected radio value for ' + this.id); },
|
||||
set: function(nv) {},
|
||||
},
|
||||
'radio-group': {
|
||||
get: function() {
|
||||
var checked = $(this).closest(':ui-hifiControlGroup').find('input:checked');
|
||||
debugPrint('_getthisValue.radio-group checked item: ' + checked[0], checked.val());
|
||||
return checked.val();
|
||||
},
|
||||
set: function(nv) {
|
||||
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
__getNodeValue: function(node) {
|
||||
assert(node && typeof node === 'object', '__getNodeValue expects a DOM node');
|
||||
getNodeValue: function(node) {
|
||||
assert(node && typeof node === 'object', 'getNodeValue expects a DOM node');
|
||||
node = node.jquery ? node.get(0) : node;
|
||||
var type = node ? (node.dataset.type || node.type) : undefined;
|
||||
var value;
|
||||
assert(type in this.domAccessors, 'DOM value accessor not defined for node type: ' + type);
|
||||
debugPrint('__getNodeValue', type);
|
||||
return this.domAccessors[type].get.call(node);
|
||||
|
||||
// switch(node.type) {
|
||||
// case 'checkbox': value = node.checked; break;
|
||||
// case 'number': value = parseFloat(node.value); break;
|
||||
// case 'radio':
|
||||
// case 'radio-group':
|
||||
// var checked = $(node).closest(':ui-hifiControlGroup').find('input:checked');
|
||||
// debugPrint('_getNodeValue.radio-group checked item: ' + checked[0], checked.val());
|
||||
// value = checked.val();
|
||||
// break;
|
||||
// default: value = node.value;
|
||||
// }
|
||||
// debugPrint('_getNodeValue', node, node.id, node.type, node.type !== 'radio-group' && node.value, value);
|
||||
// return value;
|
||||
if (node.type === 'placeholder') {
|
||||
return node.value;
|
||||
}
|
||||
assert(node.dataset.hifiType);
|
||||
return $(node)[node.dataset.hifiType]('instance').value();
|
||||
},
|
||||
|
||||
__setNodeValue: function(node, newValue) {
|
||||
if (node && node.jquery) {
|
||||
node = node[0];
|
||||
setNodeValue: function(node, newValue) {
|
||||
assert(node, 'JQuerySettings::setNodeValue -- invalid node:' + node);
|
||||
node = node.jquery ? node[0] : node;
|
||||
if (node.type === 'placeholder') {
|
||||
return node.value = newValue;
|
||||
}
|
||||
var id = node.id,
|
||||
key = this.getKey(node.id),
|
||||
type = node.dataset.type || node.type,
|
||||
value = newValue,
|
||||
element = $(node);
|
||||
|
||||
debugPrint('__setNodeValue', '('+type+') #' + key + '=' + newValue);
|
||||
|
||||
switch(type) {
|
||||
case 'radio':
|
||||
assert(false, 'radio buttons should be set through their radio-group parent');
|
||||
break;
|
||||
case 'checkbox':
|
||||
element.prop('checked', newValue);
|
||||
element.is(':ui-hifiCheckboxRadio') && element.hifiCheckboxRadio('refresh');
|
||||
break;
|
||||
case 'radio-group':
|
||||
var input = element.find('input[type=radio]#' + newValue);
|
||||
assert(input[0], 'ERROR: ' + key + ': could not find "input[type=radio]#' + newValue + '" to set new radio-group value of: ' + newValue);
|
||||
input.prop('checked', true);
|
||||
input.closest(':ui-hifiControlGroup').find(':ui-hifiCheckboxRadio').hifiCheckboxRadio('refresh');
|
||||
break;
|
||||
case 'number':
|
||||
var step = node.step || 1, precision = (1/step).toString().length - 1;
|
||||
value = parseFloat(newValue).toFixed(precision);
|
||||
if (isNaN(value)) {
|
||||
log(id, 'ignoring NaN value');
|
||||
break;
|
||||
}
|
||||
element.val(value);
|
||||
element.closest('.row').find(':ui-hifiSlider').hifiSlider('value', parseFloat(value));
|
||||
break;
|
||||
default:
|
||||
element.val(newValue);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
var hifiType = assert(node.dataset.hifiType);
|
||||
return $(node)[hifiType]('instance').value(newValue);
|
||||
},
|
||||
};
|
||||
})(this);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// movement-utils.js -- helper classes that help manage related Controller.*Event and input API bindings for movement controls
|
||||
|
||||
/* eslint-disable comma-dangle */
|
||||
/* global require: true */
|
||||
/* eslint-disable comma-dangle, no-empty */
|
||||
/* global require: true, DriveKeys, console, __filename, __dirname */
|
||||
/* eslint-env commonjs */
|
||||
"use strict";
|
||||
|
||||
|
@ -27,12 +27,13 @@ module.exports = {
|
|||
},
|
||||
};
|
||||
|
||||
var MAPPING_TEMPLATE = require('./movement-utils.mapping.json' + (Script.resolvePath('').match(/[?#].*$/)||[''])[0]);
|
||||
var MAPPING_TEMPLATE = require('./movement-utils.mapping.json' + (__filename.match(/[?#].*$/)||[''])[0]);
|
||||
|
||||
var WANT_DEBUG = false;
|
||||
|
||||
function log() {
|
||||
print('movement-utils | ' + [].slice.call(arguments).join(' '));
|
||||
// eslint-disable-next-line no-console
|
||||
(typeof Script === 'object' ? print : console.log)('movement-utils | ' + [].slice.call(arguments).join(' '));
|
||||
}
|
||||
|
||||
var debugPrint = function() {};
|
||||
|
@ -43,9 +44,11 @@ var _utils = require('./_utils.js'),
|
|||
assert = _utils.assert;
|
||||
|
||||
if (1||WANT_DEBUG) {
|
||||
require = _utils.makeDebugRequire(Script.resolvePath('.'));
|
||||
require = _utils.makeDebugRequire(__dirname);
|
||||
_utils = require('./_utils.js'); // re-require in debug mode
|
||||
if (WANT_DEBUG) debugPrint = log;
|
||||
if (WANT_DEBUG) {
|
||||
debugPrint = log;
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign = Object.assign || _utils.assign;
|
||||
|
@ -55,7 +58,7 @@ assert(enumMeta.version >= '0.0.1', 'enumMeta >= 0.0.1 expected but got: ' + enu
|
|||
|
||||
MovementEventMapper.CAPTURE_DRIVE_KEYS = 'drive-keys';
|
||||
MovementEventMapper.CAPTURE_ACTION_EVENTS = 'action-events';
|
||||
//MovementEventMapper.CAPTURE_KEYBOARD_EVENTS = 'key-events';
|
||||
// MovementEventMapper.CAPTURE_KEYBOARD_EVENTS = 'key-events';
|
||||
|
||||
function MovementEventMapper(options) {
|
||||
assert('namespace' in options, '.namespace expected ' + Object.keys(options) );
|
||||
|
@ -82,6 +85,7 @@ function MovementEventMapper(options) {
|
|||
this.inputMapping.virtualActionEvent.connect(this, 'onVirtualActionEvent');
|
||||
}
|
||||
MovementEventMapper.prototype = {
|
||||
constructor: MovementEventMapper,
|
||||
defaultEventFilter: function(from, event) {
|
||||
return event.actionValue;
|
||||
},
|
||||
|
@ -103,25 +107,10 @@ MovementEventMapper.prototype = {
|
|||
return state;
|
||||
},
|
||||
updateOptions: function(options) {
|
||||
var changed = 0;
|
||||
for (var p in this.options) {
|
||||
if (p in options) {
|
||||
log('MovementEventMapper updating options.'+p, this.options[p] + ' -> ' + options[p]);
|
||||
this.options[p] = options[p];
|
||||
changed++;
|
||||
}
|
||||
}
|
||||
for (var p in options) {
|
||||
if (!(p in this.options)) {
|
||||
var value = options[p];
|
||||
log('MovementEventMapper warning: ignoring option:', p, (value +'').substr(0, 40)+'...');
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
return _updateOptions(this.options, options || {}, this.constructor.name);
|
||||
},
|
||||
applyOptions: function(options, applyNow) {
|
||||
var changed = this.updateOptions(options || {});
|
||||
if (changed && applyNow) {
|
||||
if (this.updateOptions(options) && applyNow) {
|
||||
this.reset();
|
||||
}
|
||||
},
|
||||
|
@ -148,6 +137,7 @@ MovementEventMapper.prototype = {
|
|||
}
|
||||
},
|
||||
bindEvents: function bindEvents(capture) {
|
||||
var captureMode = this.options.captureMode;
|
||||
assert(function assertion() {
|
||||
return captureMode === MovementEventMapper.CAPTURE_ACTION_EVENTS ||
|
||||
captureMode === MovementEventMapper.CAPTURE_DRIVE_KEYS;
|
||||
|
@ -155,13 +145,14 @@ MovementEventMapper.prototype = {
|
|||
log('bindEvents....', capture, this.options.captureMode);
|
||||
var exclude = Array.isArray(this.options.excludeNames) && this.options.excludeNames;
|
||||
|
||||
var tmp;
|
||||
if (!capture || this.options.captureMode === MovementEventMapper.CAPTURE_ACTION_EVENTS) {
|
||||
var tmp = capture ? 'captureActionEvents' : 'releaseActionEvents';
|
||||
tmp = capture ? 'captureActionEvents' : 'releaseActionEvents';
|
||||
log('bindEvents -- ', tmp.toUpperCase());
|
||||
Controller[tmp]();
|
||||
}
|
||||
if (!capture || this.options.captureMode === MovementEventMapper.CAPTURE_DRIVE_KEYS) {
|
||||
var tmp = capture ? 'disableDriveKey' : 'enableDriveKey';
|
||||
tmp = capture ? 'disableDriveKey' : 'enableDriveKey';
|
||||
log('bindEvents -- ', tmp.toUpperCase());
|
||||
for (var p in DriveKeys) {
|
||||
if (capture && (exclude && ~exclude.indexOf(p))) {
|
||||
|
@ -173,7 +164,7 @@ MovementEventMapper.prototype = {
|
|||
}
|
||||
try {
|
||||
Controller.actionEvent[capture ? 'connect' : 'disconnect'](this, 'onActionEvent');
|
||||
} catch (e) { /* eslint-disable-line empty-block */ }
|
||||
} catch (e) { }
|
||||
|
||||
if (!capture || !/person/i.test(Camera.mode)) {
|
||||
Controller[capture ? 'captureWheelEvents' : 'releaseWheelEvents']();
|
||||
|
@ -200,7 +191,7 @@ MovementEventMapper.prototype = {
|
|||
actionValue: actionValue,
|
||||
extra: extra
|
||||
};
|
||||
if(0)debugPrint('onActionEvent', actionID, actionName, driveKeyName);
|
||||
// debugPrint('onActionEvent', actionID, actionName, driveKeyName);
|
||||
this.states.handleActionEvent('Actions.' + actionName, event);
|
||||
},
|
||||
onVirtualActionEvent: function(from, event) {
|
||||
|
@ -227,28 +218,22 @@ function VirtualDriveKeys(options) {
|
|||
});
|
||||
}
|
||||
VirtualDriveKeys.prototype = {
|
||||
constructor: VirtualDriveKeys,
|
||||
update: function update(dt) {
|
||||
Object.keys(this.$pendingReset).forEach(_utils.bind(this, function(i) {
|
||||
var reset = this.$pendingReset[i];
|
||||
if (reset.event.driveKey in this) {
|
||||
this.setValue(reset.event, 0);
|
||||
}
|
||||
var event = this.$pendingReset[i].event;
|
||||
(event.driveKey in this) && this.setValue(event, 0);
|
||||
}));
|
||||
},
|
||||
getValue: function(driveKey, defaultValue) {
|
||||
return driveKey in this ? this[driveKey] : defaultValue;
|
||||
},
|
||||
_defaultFilter: function(from, event) {
|
||||
return event.actionValue;
|
||||
},
|
||||
handleActionEvent: function(from, event) {
|
||||
var value = event.actionValue;
|
||||
if (this.$eventFilter) {
|
||||
value = this.$eventFilter(from, event, function(from, event) {
|
||||
return event.actionValue;
|
||||
});
|
||||
}
|
||||
if (event.driveKeyName) {
|
||||
return this.setValue(event, value);
|
||||
}
|
||||
return false;
|
||||
var value = this.$eventFilter ? this.$eventFilter(from, event, this._defaultFilter) : event.actionValue;
|
||||
return event.driveKeyName && this.setValue(event, value);
|
||||
},
|
||||
setValue: function(event, value) {
|
||||
var driveKeyName = event.driveKeyName,
|
||||
|
@ -257,8 +242,6 @@ VirtualDriveKeys.prototype = {
|
|||
previous = this[driveKey],
|
||||
autoReset = (driveKeyName === 'ZOOM');
|
||||
|
||||
if(0)debugPrint('setValue', 'id:'+id, 'driveKey:' + driveKey, 'driveKeyName:'+driveKeyName, 'actionName:'+event.actionName, 'previous:'+previous, 'value:'+value);
|
||||
|
||||
this[driveKey] = value;
|
||||
|
||||
if (previous !== value) {
|
||||
|
@ -273,7 +256,6 @@ VirtualDriveKeys.prototype = {
|
|||
reset: function() {
|
||||
Object.keys(this).forEach(_utils.bind(this, function(p) {
|
||||
this[p] = 0.0;
|
||||
if(0)debugPrint('actionStates.$pendingReset reset', enumMeta.DriveKeyNames[p]);
|
||||
}));
|
||||
Object.keys(this.$pendingReset).forEach(_utils.bind(this, function(p) {
|
||||
delete this.$pendingReset[p];
|
||||
|
@ -297,7 +279,7 @@ VirtualDriveKeys.prototype = {
|
|||
y: this.getValue(DriveKeys.TRANSLATE_Y) || 0,
|
||||
z: this.getValue(DriveKeys.TRANSLATE_Z) || 0
|
||||
},
|
||||
step_translation: {
|
||||
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
|
||||
|
@ -307,7 +289,7 @@ VirtualDriveKeys.prototype = {
|
|||
y: this.getValue(DriveKeys.YAW) || 0,
|
||||
z: 'ROLL' in DriveKeys && this.getValue(DriveKeys.ROLL) || 0
|
||||
},
|
||||
step_rotation: {
|
||||
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
|
||||
|
@ -340,6 +322,7 @@ function MovementMapping(options) {
|
|||
this.virtualActionEvent = _utils.signal(function virtualActionEvent(from, event) {});
|
||||
}
|
||||
MovementMapping.prototype = {
|
||||
constructor: MovementMapping,
|
||||
enable: function() {
|
||||
this.enabled = true;
|
||||
if (this.mapping) {
|
||||
|
@ -363,25 +346,10 @@ MovementMapping.prototype = {
|
|||
enabled && this.enable();
|
||||
},
|
||||
updateOptions: function(options) {
|
||||
var changed = 0;
|
||||
for (var p in this.options) {
|
||||
if (p in options) {
|
||||
log('MovementMapping updating options.'+p, this.options[p] + ' -> ' + options[p]);
|
||||
this.options[p] = options[p];
|
||||
changed++;
|
||||
}
|
||||
}
|
||||
for (var p in options) {
|
||||
if (!(p in this.options)) {
|
||||
var value = options[p];
|
||||
log('MovementMapping warning: ignoring option:', p, (value +'').substr(0, 40)+'...');
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
return _updateOptions(this.options, options || {}, this.constructor.name);
|
||||
},
|
||||
applyOptions: function(options) {
|
||||
var changed = this.updateOptions(options || {});
|
||||
if (changed && applyNow) {
|
||||
applyOptions: function(options, applyNow) {
|
||||
if (this.updateOptions(options) && applyNow) {
|
||||
this.reset();
|
||||
}
|
||||
},
|
||||
|
@ -426,7 +394,7 @@ MovementMapping.prototype = {
|
|||
this._mapping = this._getTemplate();
|
||||
var mappingJSON = JSON.stringify(this._mapping, 0, 2);
|
||||
var mapping = Controller.parseMapping(mappingJSON);
|
||||
log(mappingJSON);
|
||||
debugPrint(mappingJSON);
|
||||
mapping.name = mapping.name || this._mapping.name;
|
||||
|
||||
mapping.from(Controller.Hardware.Keyboard.Shift).peek().to(_utils.bind(this, 'onShiftKey'));
|
||||
|
@ -460,7 +428,7 @@ MovementMapping.prototype = {
|
|||
|
||||
template.channels = template.channels.filter(_utils.bind(this, function(item, i) {
|
||||
debugPrint('channel['+i+']', item.from && item.from.makeAxis, item.to, JSON.stringify(item.filters) || '');
|
||||
//var hasFilters = Array.isArray(item.filters) && !item.filters[1];
|
||||
// var hasFilters = Array.isArray(item.filters) && !item.filters[1];
|
||||
item.filters = Array.isArray(item.filters) ? item.filters :
|
||||
typeof item.filters === 'string' ? [ { type: item.filters }] : [ item.filters ];
|
||||
|
||||
|
@ -477,30 +445,28 @@ MovementMapping.prototype = {
|
|||
return false;
|
||||
}
|
||||
var when = Array.isArray(item.when) ? item.when : [item.when];
|
||||
when = when.filter(shouldInclude);
|
||||
function shouldIncludeWhen(p, i) {
|
||||
if (~exclude.indexOf(when)) {
|
||||
log('EXCLUDING item.when === ' + when);
|
||||
for (var j=0; j < when.length; j++) {
|
||||
if (~exclude.indexOf(when[j])) {
|
||||
log('EXCLUDING item.when contains ' + when[j]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function shouldInclude(p, i) {
|
||||
if (~exclude.indexOf(p)) {
|
||||
log('EXCLUDING from.makeAxis[][' + i + '] === ' + p);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
item.when = when.length > 1 ? when : when[0];
|
||||
|
||||
if (item.from && Array.isArray(item.from.makeAxis)) {
|
||||
var makeAxis = item.from.makeAxis;
|
||||
function shouldInclude(p, i) {
|
||||
if (~exclude.indexOf(p)) {
|
||||
log('EXCLUDING from.makeAxis[][' + i + '] === ' + p);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
item.from.makeAxis = makeAxis.map(function(axis) {
|
||||
if (Array.isArray(axis))
|
||||
if (Array.isArray(axis)) {
|
||||
return axis.filter(shouldInclude);
|
||||
else
|
||||
} else {
|
||||
return shouldInclude(axis, -1) && axis;
|
||||
}
|
||||
}).filter(Boolean);
|
||||
}
|
||||
return true;
|
||||
|
@ -510,6 +476,26 @@ MovementMapping.prototype = {
|
|||
}
|
||||
}; // MovementMapping.prototype
|
||||
|
||||
// update target properties from source, but iff the property already exists in target
|
||||
function _updateOptions(target, source, debugName) {
|
||||
debugName = debugName || '_updateOptions';
|
||||
var changed = 0;
|
||||
if (!source || typeof source !== 'object') {
|
||||
return changed;
|
||||
}
|
||||
for (var p in target) {
|
||||
if (p in source && target[p] !== source[p]) {
|
||||
log(debugName, 'updating source.'+p, target[p] + ' -> ' + source[p]);
|
||||
target[p] = source[p];
|
||||
changed++;
|
||||
}
|
||||
}
|
||||
for (p in source) {
|
||||
(!(p in target)) && log(debugName, 'warning: ignoring unknown option:', p, (source[p] +'').substr(0, 40)+'...');
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
function calculateThrust(maxVelocity, targetVelocity, previousThrust) {
|
||||
var THRUST_FALLOFF = 0.1; // round to ZERO if component is below this threshold
|
||||
|
@ -569,6 +555,7 @@ function VelocityTracker(defaultValues) {
|
|||
Object.defineProperty(this, 'defaultValues', { configurable: true, value: defaultValues });
|
||||
}
|
||||
VelocityTracker.prototype = {
|
||||
constructor: VelocityTracker,
|
||||
reset: function() {
|
||||
Object.assign(this, this.defaultValues);
|
||||
},
|
||||
|
@ -605,10 +592,15 @@ Object.assign(CameraControls, {
|
|||
function CameraControls(options) {
|
||||
options = options || {};
|
||||
assert('update' in options && 'threadMode' in options);
|
||||
this.update = options.update;
|
||||
this.updateObject = typeof options.update === 'function' ? options : options.update;
|
||||
assert(typeof this.updateObject.update === 'function',
|
||||
'construction options expected either { update: function(){}... } object or a function(){}');
|
||||
this.update = _utils.bind(this.updateObject, 'update');
|
||||
this.threadMode = options.threadMode;
|
||||
this.fps = options.fps || 60;
|
||||
this.getRuntimeSeconds = options.getRuntimeSeconds || function() { return +new Date / 1000.0; };
|
||||
this.getRuntimeSeconds = options.getRuntimeSeconds || function() {
|
||||
return +new Date / 1000.0;
|
||||
};
|
||||
this.backupOptions = _utils.DeferredUpdater.createGroup({
|
||||
MyAvatar: MyAvatar,
|
||||
Camera: Camera,
|
||||
|
@ -620,18 +612,21 @@ function CameraControls(options) {
|
|||
this.modeChanged = _utils.signal(function modeChanged(mode, oldMode){});
|
||||
}
|
||||
CameraControls.prototype = {
|
||||
constructor: CameraControls,
|
||||
$animate: null,
|
||||
$start: function() {
|
||||
if (this.$animate) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch(this.threadMode) {
|
||||
|
||||
var lastTime;
|
||||
switch (this.threadMode) {
|
||||
case CameraControls.SCRIPT_UPDATE: {
|
||||
this.$animate = this.update;
|
||||
Script.update.connect(this, '$animate');
|
||||
this.$animate.disconnect = _utils.bind(this, function() { Script.update.disconnect(this, '$animate'); });
|
||||
this.$animate.disconnect = _utils.bind(this, function() {
|
||||
Script.update.disconnect(this, '$animate');
|
||||
});
|
||||
} break;
|
||||
|
||||
case CameraControls.ANIMATION_FRAME: {
|
||||
|
@ -649,7 +644,7 @@ CameraControls.prototype = {
|
|||
|
||||
case CameraControls.SET_IMMEDIATE: {
|
||||
// emulate process.setImmediate (attempt to execute at start of next update frame, sans Script.update throttling)
|
||||
var lastTime = this.getRuntimeSeconds();
|
||||
lastTime = this.getRuntimeSeconds();
|
||||
this.$animate = Script.setInterval(_utils.bind(this, function() {
|
||||
this.update(this.getRuntimeSeconds(lastTime));
|
||||
lastTime = this.getRuntimeSeconds();
|
||||
|
@ -661,7 +656,7 @@ CameraControls.prototype = {
|
|||
|
||||
case CameraControls.NEXT_TICK: {
|
||||
// emulate process.nextTick (attempt to queue at the very next opportunity beyond current scope)
|
||||
var lastTime = this.getRuntimeSeconds();
|
||||
lastTime = this.getRuntimeSeconds();
|
||||
this.$animate = _utils.bind(this, function() {
|
||||
this.$animate.timeout = 0;
|
||||
if (this.$animate.quit) {
|
||||
|
@ -692,7 +687,7 @@ CameraControls.prototype = {
|
|||
}
|
||||
try {
|
||||
this.$animate.disconnect();
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
log('$animate.disconnect error: ' + e, '(threadMode: ' + this.threadMode +')');
|
||||
}
|
||||
this.$animate = null;
|
||||
|
@ -784,10 +779,11 @@ function applyEasing(deltaTime, direction, settings, state, scaling) {
|
|||
for (var p in scaling) {
|
||||
var group = settings[p],
|
||||
easeConst = group[direction],
|
||||
multiplier = group.speed,
|
||||
// multiplier = group.speed,
|
||||
scale = scaling[p],
|
||||
stateVector = state[p];
|
||||
var vec = obj[p] = Vec3.multiply(easeConst * scale * deltaTime, stateVector);
|
||||
obj[p] = Vec3.multiply(easeConst * scale * deltaTime, stateVector);
|
||||
// var vec = obj[p]
|
||||
// vec.x *= multiplier.x;
|
||||
// vec.y *= multiplier.y;
|
||||
// vec.z *= multiplier.z;
|
||||
|
|
Loading…
Reference in a new issue