mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-10 19:32:41 +02:00
613 lines
24 KiB
JavaScript
613 lines
24 KiB
JavaScript
// app.js -- support functions
|
|
|
|
/* eslint-env console, jquery, browser, shared-node-browser */
|
|
/* eslint-disable comma-dangle */
|
|
/* global Mousetrap, TooltipManager, SettingsJSON, PARAMS, signal, assert, log, debugPrint */
|
|
// ----------------------------------------------------------------------------
|
|
|
|
var viewportUpdated, bridgedSettings, jquerySettings, tooltipManager, lessManager;
|
|
|
|
function setupUI() {
|
|
$('#debug-menu button, footer button')
|
|
.hifiButton({
|
|
create: function() {
|
|
$(this).addClass('tooltip-target')
|
|
.data('tooltipster', { side: ['top','bottom'] });
|
|
}
|
|
});
|
|
|
|
var $json = SettingsJSON;
|
|
|
|
window.buttonHandlers = {
|
|
'test-event-bridge': function() {
|
|
log('bridgedSettings.eventBridge === Window.EventBridge', bridgedSettings.eventBridge === window.EventBridge);
|
|
bridgedSettings.sendEvent({ method: 'test-event-bridge' });
|
|
EventBridge.emitWebEvent('EventBridge.emitWebEvent: testing 1..2..3..');
|
|
},
|
|
'page-reload': function() {
|
|
log('triggering location.reload');
|
|
location.reload();
|
|
},
|
|
'script-reload': function() {
|
|
log('triggering script.reload');
|
|
bridgedSettings.sendEvent({ method: 'reloadClientScript' });
|
|
},
|
|
'reset-sensors': function() {
|
|
log('resetting avatar orientation');
|
|
bridgedSettings.sendEvent({ method: 'resetSensors' });
|
|
},
|
|
'reset-to-defaults': function() {
|
|
tooltipManager && tooltipManager.closeAll();
|
|
document.activeElement && document.activeElement.blur();
|
|
document.body.focus();
|
|
setTimeout(function() {
|
|
bridgedSettings.sendEvent({ method: 'reset' });
|
|
},1);
|
|
},
|
|
'copy-json': $json.showSettings.bind($json, jquerySettings, null),
|
|
'paste-json': function() {
|
|
$json.applyJSON(
|
|
jquerySettings,
|
|
'pasted',
|
|
$json.promptJSON()
|
|
);
|
|
},
|
|
'toggle-advanced-options': function(evt) {
|
|
var checkbox = $(this).hifiButton('instance').checkbox;
|
|
var on = checkbox.value(!checkbox.value());
|
|
$('body').toggleClass('ui-show-advanced-options', on);
|
|
evt.stopPropagation();
|
|
evt.preventDefault();
|
|
if ($(this).is('.tooltipstered')) {
|
|
$(this).tooltipster('instance').content((on ? 'hide' : 'show') + ' advanced options');
|
|
}
|
|
if (checkbox.value()) {
|
|
$('.scrollable').delay(100).animate({
|
|
scrollTop: innerHeight - $('header').innerHeight() - 24
|
|
}, 1500);
|
|
}
|
|
},
|
|
'appVersion': function(evt) {
|
|
evt.shiftKey && $json.showSettings(jquerySettings);
|
|
},
|
|
'errors': function(evt) {
|
|
$(evt.target).is('button') && $(this).find('.output').text('').end().hide();
|
|
},
|
|
};
|
|
buttonHandlers['button-toggle-advanced-options'] =
|
|
buttonHandlers['toggle-advanced-options'];
|
|
Object.keys(window.buttonHandlers).forEach(function(p) {
|
|
$('#' + p).on('click', window.buttonHandlers[p]);
|
|
});
|
|
|
|
// ----------------------------------------------------------------
|
|
// trim whitespace in labels
|
|
$('label').contents().filter(function() {
|
|
if (this.nodeType !== window.Node.TEXT_NODE) {
|
|
return false;
|
|
}
|
|
this.textContent = this.textContent.trim();
|
|
return !this.textContent.length;
|
|
}).remove();
|
|
|
|
var settingsNodes = $('fieldset,button[data-for],input:not([type=radio])');
|
|
settingsNodes.each(function() {
|
|
// set up the bidirectional mapping between DOM and Settings
|
|
jquerySettings.registerNode(this);
|
|
});
|
|
|
|
var spinnerOptions = {
|
|
disabled: true,
|
|
create: function() {
|
|
var input = $(this),
|
|
key = assert(jquerySettings.getKey(input.data('for')));
|
|
|
|
var options = input.hifiSpinner('instance').options;
|
|
options.min = options.min || 0.0;
|
|
|
|
bridgedSettings.getValueAsync(key, function(err, result) {
|
|
input.filter(':not([data-autoenable=false])').hifiSpinner('enable');
|
|
jquerySettings.setValue(key, result);
|
|
});
|
|
},
|
|
};
|
|
|
|
$( ".rows > label" ).each(function() {
|
|
var label = $(this),
|
|
input = label.find('input'),
|
|
type = input.data('type') || input.attr('type');
|
|
label.wrap('<div>').parent().addClass(['hifi-'+type, type, 'row'].join(' '))
|
|
.on('focus.row, click.row, hover.row', function() {
|
|
$(this).find('.tooltipstered').tooltipster('open');
|
|
});
|
|
});
|
|
|
|
debugPrint('initializing hifiSpinners');
|
|
// numeric settings
|
|
$( ".number.row" )
|
|
.find( "input[data-type=number]" )
|
|
.addClass('setting')
|
|
.hifiSpinner(spinnerOptions);
|
|
|
|
// radio groups settings
|
|
$( ".radio.rows" )
|
|
.find('label').addClass('tooltip-target').end()
|
|
.addClass('setting')
|
|
.hifiRadioGroup({
|
|
direction: 'vertical',
|
|
disabled: true,
|
|
create: function() {
|
|
assert(this !== window);
|
|
var group = $(this), id = this.id;
|
|
var key = assert(jquerySettings.getKey(group.data('for')));
|
|
|
|
bridgedSettings.getValueAsync(key, function(err, result) {
|
|
debugPrint('> GOT RADIO', key, id, result);
|
|
group.filter(':not([data-autoenable=false])').hifiRadioGroup('enable');
|
|
jquerySettings.setValue(key, result);
|
|
group.change();
|
|
});
|
|
},
|
|
})
|
|
|
|
// checkbox settings
|
|
$( "input[type=checkbox]" )
|
|
.addClass('setting')
|
|
.hifiCheckbox({
|
|
disabled: true,
|
|
create: function() {
|
|
var key = assert(jquerySettings.getKey(this.id)),
|
|
input = $(this);
|
|
input.closest('label').addClass('tooltip-target');
|
|
bridgedSettings.getValueAsync(key, function(err, result) {
|
|
input.filter(':not([data-autoenable=false])').hifiCheckbox('enable');
|
|
jquerySettings.setValue(key, result);
|
|
});
|
|
},
|
|
});
|
|
|
|
// slider + numeric settings
|
|
// use the whole row as a tooltip target
|
|
$( ".slider.row" ).addClass('tooltip-target').data('tooltipster', {
|
|
distance: -20,
|
|
side: ['top', 'bottom'],
|
|
}).each(function(ent) {
|
|
var element = $(this).find( ".control" ),
|
|
input = $(this).find('input'),
|
|
id = input.prop('id'),
|
|
key = assert(jquerySettings.getKey(id));
|
|
|
|
var commonOptions = {
|
|
disabled: true,
|
|
min: parseFloat(input.prop('min') || 0),
|
|
max: parseFloat(input.prop('max') || 10),
|
|
step: parseFloat(input.prop('step') || 0.01),
|
|
autoenable: input.data('autoenable') !== 'false',
|
|
};
|
|
debugPrint('commonOptions', commonOptions);
|
|
|
|
// see: https://api.jqueryui.com/slider/ for more options
|
|
var slider = element.hifiSlider(Object.assign({
|
|
orientation: "horizontal",
|
|
range: "min",
|
|
animate: 'fast',
|
|
value: 0.0
|
|
}, commonOptions)).hifiSlider('instance');
|
|
|
|
debugPrint('initializing hifiSlider->hifiSpinner');
|
|
// setup chrome up/down arrow steps and propagate input field -> slider
|
|
var spinner = input.on('change', function() {
|
|
var value = spinner.value();
|
|
if (isFinite(value) && slider.value() !== value) {
|
|
slider.value(value);
|
|
}
|
|
}).addClass('setting')
|
|
.hifiSpinner(
|
|
Object.assign({}, commonOptions, { max: 1e4 })
|
|
).hifiSpinner('instance');
|
|
|
|
bridgedSettings.getValueAsync(key, function(err, result) {
|
|
slider.options.autoenable !== false && slider.enable();
|
|
spinner.options.autoenable !== false && spinner.enable();
|
|
spinner.value(result);
|
|
});
|
|
});
|
|
|
|
$('#fps').hifiSpinner(spinnerOptions).closest('.row').css('pointer-events', 'all').on('click.subinput', function(evt) {
|
|
jquerySettings.setValue('thread-update-mode', 'requestAnimationFrame');
|
|
evt.target.focus();
|
|
});
|
|
// detect invalid numbers entered into spinner fields
|
|
$(':ui-hifiSpinner').on('change.validation', function(evt) {
|
|
var spinner = $(this).hifiSpinner('instance');
|
|
$(this).closest('.row').toggleClass('invalid', !spinner.isValid());
|
|
});
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// allow tabbing between checkboxes using the container row
|
|
$(':ui-hifiCheckbox,:ui-hifiRadioButton').prop('tabindex', -1).closest('.row').prop('tabindex', 0);
|
|
|
|
|
|
// select the input field text when first focused
|
|
$('input').not('input[type=radio],input[type=checkbox]').on('focus', function () {
|
|
var dt = (new Date - this.blurredAt);
|
|
if (!(dt < 5)) { // debounce
|
|
this.blurredAt = +new Date;
|
|
$(this).one('mouseup.selectit', function() {
|
|
$(this).select();
|
|
return false;
|
|
}).select();
|
|
}
|
|
}).on('blur', function(e) {
|
|
this.blurredAt = new Date;
|
|
});
|
|
|
|
// monitor changes to specific settings that affect the UI
|
|
var monitors = {
|
|
// advanced options toggle
|
|
'ui-show-advanced-options': function onChange(value) {
|
|
function handle(err, result) {
|
|
log('** ui-show-advanced-options updated', result+'');
|
|
$('body').toggleClass('ui-show-advanced-options', !!result);
|
|
jquerySettings.setValue('ui-show-advanced-options', result)
|
|
}
|
|
if (!onChange.fetched) {
|
|
bridgedSettings.getValueAsync('ui-show-advanced-options', handle);
|
|
return onChange.fetched = true;
|
|
}
|
|
handle(null, value);
|
|
},
|
|
|
|
// UI tooltips toggle
|
|
'ui-enable-tooltips': function(value) {
|
|
if (!tooltipManager) return;
|
|
if (value) {
|
|
tooltipManager.enable();
|
|
tooltipManager.openFocusedTooltip();
|
|
} else {
|
|
tooltipManager.disable();
|
|
}
|
|
},
|
|
|
|
// enable/disable fps field (based on whether thread mode is requestAnimationFrame)
|
|
'thread-update-mode': function(value) {
|
|
var enabled = (value === 'requestAnimationFrame'), fps = $('#fps');
|
|
fps.hifiSpinner(enabled ? 'enable' : 'disable');
|
|
fps.closest('.row').toggleClass('disabled', !enabled);
|
|
},
|
|
|
|
// flag BODY with CSS class to indicate active camera move mode
|
|
'camera-move-enabled': function(value) {
|
|
$('body').toggleClass('camera-move-enabled', value);
|
|
},
|
|
|
|
// update the "keybinding" and #appVersion extraParams displays
|
|
'.extraParams': function extraParams(value, other) {
|
|
value = bridgedSettings.extraParams;
|
|
if (value.mode) {
|
|
for (var p in value.mode) {
|
|
// tablet-mode, hmd-mode, etc.
|
|
$('body').toggleClass(p + '-mode', value.mode[p]);
|
|
}
|
|
document.oncontextmenu = value.mode.tablet ? function(evt) { return evt.preventDefault(),false; } : null;
|
|
$('[data-type=number]').prop('type', value.mode.tablet ? 'number' : 'text');
|
|
}
|
|
var versionDisplay = [
|
|
value.appVersion || '(unknown appVersion)',
|
|
PARAMS.debug && '(debug)',
|
|
value.mode && value.mode.tablet ? '(tablet)' : '',
|
|
].filter(Boolean).join(' | ');
|
|
$('#appVersion').find('.output').text(versionDisplay).end().show();
|
|
|
|
if (value.toggleKey) {
|
|
$('#toggleKey').find('.binding').empty()
|
|
.append(getKeysHTML(value.toggleKey)).end().show();
|
|
}
|
|
|
|
var activateLookAtOption = value.MyAvatar && value.MyAvatar.supportsLookAtSnappingEnabled;
|
|
$(jquerySettings.findNodeByKey('Avatar/lookAtSnappingEnabled'))
|
|
.hifiCheckbox(activateLookAtOption ? 'enable' : 'disable')
|
|
.closest('.row').toggleClass('disabled', !activateLookAtOption)
|
|
.css('pointer-events', 'all') // so tooltips display regardless
|
|
|
|
var activateCursorOption = value.Reticle && value.Reticle.supportsScale;
|
|
$('#minimal-cursor:ui-hifiCheckbox')
|
|
.hifiCheckbox(activateCursorOption ? 'enable' : 'disable')
|
|
.closest('.row').toggleClass('disabled', !activateCursorOption)
|
|
.css('pointer-events', 'all') // so tooltips display regardless
|
|
},
|
|
|
|
// gray out / ungray out page content if user is mouse looking around in Interface
|
|
// (otherwise the cursor still interacts with web content...)
|
|
'Keyboard.RightMouseButton': function(localValue, key, value) {
|
|
debugPrint(localValue, '... Keyboard.RightMouseButton:' + value);
|
|
window.active = !value;
|
|
},
|
|
};
|
|
monitors['toggle-advanced-options'] = monitors['ui-toggle-advanced-options'];
|
|
monitorSettings(monitors);
|
|
// disable selection
|
|
// $('input').css('-webkit-user-select', 'none');
|
|
|
|
viewportUpdated.connect(lessManager, 'onViewportUpdated');
|
|
|
|
setupTooltips();
|
|
|
|
$(window).trigger('resize'); // populate viewport
|
|
|
|
// set up DOM MutationObservers
|
|
settingsNodes.each(function tcobo() {
|
|
if (this.dataset.hifiType === 'hifiButton') {
|
|
return;
|
|
}
|
|
var id = assert(this.dataset['for'] || this.id, 'could not id for node: ' + this.outerHTML);
|
|
assert(!tcobo[id]); // detect dupes
|
|
tcobo[id] = true;
|
|
debugPrint('OBSERVING NODE', id, this.id || this.getAttribute('for'));
|
|
jquerySettings.observeNode(this);
|
|
});
|
|
|
|
// set up key bindings
|
|
setupMousetrapKeys();
|
|
|
|
function getKeysHTML(binding) {
|
|
var text = binding.text || ('(#' + binding.key + ')');
|
|
// translate hifi's proprietary key scheme into human-friendly KBDs
|
|
return [ 'Control', 'Meta', 'Alt', 'Super', 'Menu', 'Shifted' ]
|
|
.map(function(flag) {
|
|
return binding['is' + flag] && flag;
|
|
})
|
|
.concat(text)
|
|
.filter(Boolean)
|
|
.map(function(key) {
|
|
return '<kbd>' + key.replace('Shifted','Shift') + '</kbd>';
|
|
})
|
|
.join('-');
|
|
}
|
|
} // setupUI
|
|
|
|
function setupTooltips() {
|
|
// extract the tooltip captions
|
|
var tooltips = window.tooltips = {};
|
|
var target = '[id], [data-for], [for]';
|
|
$('.tooltip')
|
|
.removeClass('tooltip').addClass('x-tooltip')
|
|
.each(function() {
|
|
var element = $(this),
|
|
input = $(element.parent().find('input').get(0) ||
|
|
element.closest('button').get(0));
|
|
id = element.prop('id') || element.data('for') ||
|
|
input.prop('id') || input.data('for');
|
|
assert(id);
|
|
tooltips[id] = this.outerHTML;
|
|
}).hide();
|
|
tooltipManager = new TooltipManager({
|
|
enabled: false,
|
|
testmode: PARAMS.tooltiptest,
|
|
viewport: PARAMS.viewport,
|
|
tooltips: tooltips,
|
|
elements: '#reset-to-defaults, button, input',
|
|
});
|
|
viewportUpdated.connect(tooltipManager, 'updateViewport');
|
|
// tooltips aren't needed right away, so defer initializing for better page load times
|
|
window.setTimeout(tooltipManager.initialize.bind(tooltipManager), 1000);
|
|
}
|
|
|
|
// helper for instrumenting local jquery onchange handlers
|
|
function monitorSettings(options) {
|
|
return Object.keys(options).reduce(function(out, id) {
|
|
var key = bridgedSettings.resolve(id),
|
|
domId = jquerySettings.getId(key, true);
|
|
|
|
if (!domId) {
|
|
var placeholder = {
|
|
id: id,
|
|
type: 'placeholder',
|
|
toString: function() {
|
|
return this.id;
|
|
},
|
|
value: undefined,
|
|
};
|
|
jquerySettings.registerSetting(placeholder, key);
|
|
debugPrint('registered placeholder value for setting', id, key);
|
|
assert(jquerySettings.findNodeByKey(key) === placeholder);
|
|
}
|
|
|
|
// if (domId === 'toggle-advanced-options') alert([key,id,domId, jquerySettings.findNodeByKey(key)])
|
|
assert(function assertion(){
|
|
return typeof key === 'string';
|
|
}, 'monitorSettings -- received invalid key type');
|
|
|
|
var context = {
|
|
id: id,
|
|
key: key,
|
|
domId: domId,
|
|
options: options,
|
|
lastValue: undefined,
|
|
initializer: function(hint) {
|
|
var key = this.key,
|
|
lastValue = this.lastValue;
|
|
if (lastValue !== undefined) {
|
|
return log('skipping repeat initializer', key, hint);
|
|
}
|
|
this.lastValue = lastValue = jquerySettings.getValue(key);
|
|
this._onChange.call(jquerySettings, key, lastValue, undefined, hint);
|
|
},
|
|
_onChange: function _onChange(key, value) {
|
|
var currentValue = this.getValue(context.id),
|
|
jsonCurrentValue = JSON.stringify(currentValue);
|
|
|
|
if (jsonCurrentValue === context.jsonLastValue) {
|
|
if (jsonCurrentValue !== undefined) {
|
|
debugPrint([context.key, '_onChange', this, 'not triggering _onChange for duplicated value']);
|
|
}
|
|
return;
|
|
}
|
|
context.jsonLastValue = jsonCurrentValue;
|
|
var args = [].slice.call(arguments, 0);
|
|
debugPrint('monitorSetting._onChange', context.key, value, [].concat(args).pop());
|
|
context.options[context.id].apply(this, [ currentValue ].concat(args));
|
|
},
|
|
|
|
onValueReceived: function(key) {
|
|
if (key === this.key) {
|
|
this._onChange.apply(bridgedSettings, arguments);
|
|
}
|
|
},
|
|
onMutationEvent: function(event) {
|
|
if (event.key === this.key) {
|
|
context._onChange.call(jquerySettings, event.key, event.value, event.oldValue, event.hifiType+':mutation');
|
|
}
|
|
},
|
|
onPendingRequestsFinished: function onPendingRequestsFinished() {
|
|
bridgedSettings.pendingRequestsFinished.disconnect(this, 'onPendingRequestsFinished');
|
|
this.initializer('pendingRequestsFinished');
|
|
},
|
|
};
|
|
|
|
bridgedSettings.valueReceived.connect(context, 'onValueReceived');
|
|
jquerySettings.mutationEvent.connect(context, 'onMutationEvent');
|
|
|
|
if (bridgedSettings.pendingRequestCount()) {
|
|
bridgedSettings.pendingRequestsFinished.connect(context, 'onPendingRequestsFinished');
|
|
} else {
|
|
window.setTimeout(context.initializer.bind(context, 'monitorSettings init'), 1);
|
|
}
|
|
return context;
|
|
}, {});
|
|
}
|
|
|
|
function initializeDOM() {
|
|
|
|
Object.defineProperty(window, 'active', {
|
|
get: function() {
|
|
return window._active;
|
|
},
|
|
set: function(nv) {
|
|
nv = !!nv;
|
|
window._active = nv;
|
|
debugPrint('window.active == ' + nv);
|
|
if (!nv) {
|
|
document.activeElement && document.activeElement.blur();
|
|
document.body.focus();
|
|
tooltipManager && tooltipManager.disable();
|
|
debugPrint('TOOLTIPS DISABLED');
|
|
} else if (tooltipManager && bridgedSettings) {
|
|
if (bridgedSettings.getValue('ui-enable-tooltips')){
|
|
tooltipManager.enable();
|
|
debugPrint('TOOLTIPS RE-ENABLED');
|
|
}
|
|
}
|
|
$('body').toggleClass('active', window._active);
|
|
},
|
|
});
|
|
$('body').toggleClass('active', window._active = true);
|
|
|
|
function checkAnim(evt) {
|
|
if (!checkAnim.disabled) {
|
|
if ($('.scrollable').is(':animated')) {
|
|
$('.scrollable').stop();
|
|
log(evt.type, 'stop animation');
|
|
}
|
|
}
|
|
}
|
|
viewportUpdated = signal(function viewportUpdated(viewport) {});
|
|
function triggerViewportUpdate() {
|
|
var viewport = {
|
|
inner: { width: innerWidth, height: innerHeight },
|
|
client: {
|
|
width: document.body.clientWidth || window.innerWidth,
|
|
height: document.body.clientHeight || window.innerHeight,
|
|
},
|
|
min: { width: window.innerWidth / 3, height: 32 },
|
|
max: { width: window.innerWidth * 7/8, height: window.innerHeight * 7/8 },
|
|
};
|
|
debugPrint('viewportUpdated', viewport);
|
|
PARAMS.viewport = Object.assign(PARAMS.viewport||{}, viewport);
|
|
viewportUpdated(viewport, triggerViewportUpdate.lastViewport);
|
|
triggerViewportUpdate.lastViewport = viewport;
|
|
}
|
|
$(window).on({
|
|
resize: function resize() {
|
|
window.clearTimeout(resize.to);
|
|
resize.to = window.setTimeout(triggerViewportUpdate, 100);
|
|
},
|
|
mousedown: checkAnim, mouseup: checkAnim, scroll: checkAnim, wheel: checkAnim,
|
|
blur: function() {
|
|
log('** BLUR ** ');
|
|
$('body').addClass('window-blurred');
|
|
document.body.focus();
|
|
document.activeElement && document.activeElement.blur();
|
|
// tooltipManager.closeAll();
|
|
},
|
|
focus: function() {
|
|
log('** FOCUS **');
|
|
$('body').removeClass('window-blurred');
|
|
},
|
|
});
|
|
}
|
|
|
|
function setupMousetrapKeys() {
|
|
if (!window.Mousetrap) {
|
|
return log('WARNING: window.Mousetrap not found; not configurating keybindings');
|
|
}
|
|
mousetrapMultiBind({
|
|
'ctrl+a, option+a': function global(evt, combo) {
|
|
$(document.activeElement).filter('input').select();
|
|
},
|
|
'enter': function global(evt, combo) {
|
|
var node = document.activeElement;
|
|
if ($(node).is('input')) {
|
|
log('enter on input element');
|
|
tooltipManager.closeAll();
|
|
node.blur();
|
|
var nexts = $('[tabindex],input,:focusable').not('[tabindex=-1],.ui-slider-handle');
|
|
nexts.add(nexts.find('input'));
|
|
nexts = nexts.toArray();
|
|
if (~nexts.indexOf(node)) {
|
|
var nextActive = nexts[nexts.indexOf(node)+1];
|
|
log('setting focus to', nextActive);
|
|
$(nextActive).focus();
|
|
} else {
|
|
log('could not deduce next tabbable element', nexts.length, this);
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
'ctrl+w': bridgedSettings.sendEvent.bind(bridgedSettings, { method: 'window.close' }),
|
|
'r': location.reload.bind(location),
|
|
'space': function global(evt, combo) {
|
|
log('SPACE', evt.target, document.activeElement);
|
|
$(document.activeElement).filter('.row').find(':ui-hifiCheckbox,:ui-hifiRadioButton').click();
|
|
if (!$(document.activeElement).is('input,.ui-widget')) {
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
});
|
|
// $('input').addClass('mousetrap');
|
|
function mousetrapMultiBind(a, b) {
|
|
var obj = typeof a === 'object' ? a :
|
|
Object.defineProperty({}, a, {enumerable: true, value: b });
|
|
Object.keys(obj).forEach(function(key) {
|
|
var method = obj[key].name === 'global' ? 'bindGlobal' : 'bind';
|
|
key.split(/\s*,\s*/).forEach(function(combo) {
|
|
debugPrint('Mousetrap', method, combo, typeof obj[key]);
|
|
Mousetrap[method](combo, function(evt, combo) {
|
|
debugPrint('Mousetrap', method, combo);
|
|
return obj[key].apply(this, arguments);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
// support the URL having a #node-id (or #debug=1&node-id) hash fragment jumping to that element
|
|
function jumpToAnchor(id) {
|
|
id = JSON.stringify(id);
|
|
$('[id='+id+'],[name='+id+']').first().each(function() {
|
|
log('jumpToAnchor', id);
|
|
$(this).show();
|
|
this.scrollIntoView({ behavior: 'smooth' });
|
|
});
|
|
};
|