2015-06-07 10:30:27 +02:00
|
|
|
// Pressing `Escape` should close the last opened “element” and only the last
|
|
|
|
|
// one. Components can register themselves using a label a condition, and an
|
|
|
|
|
// action. This is used by Popup or inlinedForm for instance. When we press
|
|
|
|
|
// escape we execute the action which have a valid condition and his the highest
|
|
|
|
|
// in the label hierarchy.
|
|
|
|
|
EscapeActions = {
|
2015-08-28 03:35:18 +02:00
|
|
|
_nextclickPrevented: false,
|
|
|
|
|
|
2015-06-07 10:30:27 +02:00
|
|
|
_actions: [],
|
|
|
|
|
|
|
|
|
|
// Executed in order
|
|
|
|
|
hierarchy: [
|
|
|
|
|
'textcomplete',
|
2015-08-23 11:04:35 +02:00
|
|
|
'popup-back',
|
|
|
|
|
'popup-close',
|
2015-08-27 00:27:23 +02:00
|
|
|
'modalWindow',
|
2015-06-07 10:30:27 +02:00
|
|
|
'inlinedForm',
|
|
|
|
|
'detailsPane',
|
|
|
|
|
'multiselection',
|
2015-08-31 15:09:53 +02:00
|
|
|
'sidebarView',
|
2015-06-07 10:30:27 +02:00
|
|
|
],
|
|
|
|
|
|
2015-08-31 15:09:53 +02:00
|
|
|
register(label, action, condition = () => true, options = {}) {
|
2015-08-25 22:18:43 +02:00
|
|
|
const priority = this.hierarchy.indexOf(label);
|
2015-06-07 10:30:27 +02:00
|
|
|
if (priority === -1) {
|
|
|
|
|
throw Error('You must define the label in the EscapeActions hierarchy');
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-25 22:18:43 +02:00
|
|
|
let enabledOnClick = options.enabledOnClick;
|
|
|
|
|
if (_.isUndefined(enabledOnClick)) {
|
|
|
|
|
enabledOnClick = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let noClickEscapeOn = options.noClickEscapeOn;
|
|
|
|
|
|
2015-08-31 15:09:53 +02:00
|
|
|
this._actions = _.sortBy([...this._actions, {
|
2015-08-24 23:30:56 +02:00
|
|
|
priority,
|
|
|
|
|
condition,
|
|
|
|
|
action,
|
2015-08-25 22:18:43 +02:00
|
|
|
noClickEscapeOn,
|
2015-08-31 15:09:53 +02:00
|
|
|
enabledOnClick,
|
|
|
|
|
}], (action) => action.priority);
|
2015-06-07 10:30:27 +02:00
|
|
|
},
|
|
|
|
|
|
2015-08-31 15:09:53 +02:00
|
|
|
executeLowest() {
|
2015-06-07 10:30:27 +02:00
|
|
|
return this._execute({
|
|
|
|
|
multipleAction: false
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
2015-08-31 15:09:53 +02:00
|
|
|
executeAll() {
|
2015-06-07 10:30:27 +02:00
|
|
|
return this._execute({
|
|
|
|
|
multipleActions: true
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
2015-08-31 15:09:53 +02:00
|
|
|
executeUpTo(maxLabel) {
|
2015-06-07 10:30:27 +02:00
|
|
|
return this._execute({
|
|
|
|
|
maxLabel: maxLabel,
|
|
|
|
|
multipleActions: true
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
2015-08-31 15:09:53 +02:00
|
|
|
clickExecute(target, maxLabel) {
|
2015-08-28 03:35:18 +02:00
|
|
|
if (this._nextclickPrevented) {
|
|
|
|
|
this._nextclickPrevented = false;
|
|
|
|
|
} else {
|
|
|
|
|
return this._execute({
|
|
|
|
|
maxLabel: maxLabel,
|
|
|
|
|
multipleActions: false,
|
|
|
|
|
isClick: true,
|
|
|
|
|
clickTarget: target
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2015-08-31 15:09:53 +02:00
|
|
|
preventNextClick() {
|
2015-08-28 03:35:18 +02:00
|
|
|
this._nextclickPrevented = true;
|
2015-06-07 10:30:27 +02:00
|
|
|
},
|
|
|
|
|
|
2015-08-31 15:09:53 +02:00
|
|
|
_stopClick(action, clickTarget) {
|
2015-06-07 10:30:27 +02:00
|
|
|
if (! _.isString(action.noClickEscapeOn))
|
|
|
|
|
return false;
|
|
|
|
|
else
|
|
|
|
|
return $(clickTarget).closest(action.noClickEscapeOn).length > 0;
|
|
|
|
|
},
|
|
|
|
|
|
2015-08-31 15:09:53 +02:00
|
|
|
_execute(options) {
|
2015-08-25 22:18:43 +02:00
|
|
|
const maxLabel = options.maxLabel;
|
|
|
|
|
const multipleActions = options.multipleActions;
|
|
|
|
|
const isClick = !! options.isClick;
|
|
|
|
|
const clickTarget = options.clickTarget;
|
|
|
|
|
|
|
|
|
|
let executedAtLeastOne = false;
|
|
|
|
|
let maxPriority;
|
2015-06-07 10:30:27 +02:00
|
|
|
|
|
|
|
|
if (! maxLabel)
|
|
|
|
|
maxPriority = Infinity;
|
|
|
|
|
else
|
|
|
|
|
maxPriority = this.hierarchy.indexOf(maxLabel);
|
|
|
|
|
|
2015-08-31 15:09:53 +02:00
|
|
|
for (let currentAction of this._actions) {
|
2015-06-07 10:30:27 +02:00
|
|
|
if (currentAction.priority > maxPriority)
|
|
|
|
|
return executedAtLeastOne;
|
|
|
|
|
|
2015-08-23 11:04:35 +02:00
|
|
|
if (isClick && this._stopClick(currentAction, clickTarget))
|
2015-06-07 10:30:27 +02:00
|
|
|
return executedAtLeastOne;
|
|
|
|
|
|
2015-08-25 22:18:43 +02:00
|
|
|
let isEnabled = currentAction.enabledOnClick || ! isClick;
|
2015-08-23 11:04:35 +02:00
|
|
|
if (isEnabled && currentAction.condition()) {
|
|
|
|
|
currentAction.action();
|
2015-06-07 10:30:27 +02:00
|
|
|
executedAtLeastOne = true;
|
|
|
|
|
if (! multipleActions)
|
|
|
|
|
return executedAtLeastOne;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return executedAtLeastOne;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// MouseTrap plugin bindGlobal plugin. Adds a bindGlobal method to Mousetrap
|
|
|
|
|
// that allows you to bind specific keyboard shortcuts that will still work
|
|
|
|
|
// inside a text input field.
|
|
|
|
|
//
|
|
|
|
|
// usage:
|
|
|
|
|
// Mousetrap.bindGlobal('ctrl+s', _saveChanges);
|
|
|
|
|
//
|
|
|
|
|
// source:
|
|
|
|
|
// https://github.com/ccampbell/mousetrap/tree/master/plugins/global-bind
|
|
|
|
|
var _globalCallbacks = {};
|
|
|
|
|
var _originalStopCallback = Mousetrap.stopCallback;
|
|
|
|
|
|
|
|
|
|
Mousetrap.stopCallback = function(e, element, combo, sequence) {
|
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
|
|
if (self.paused) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_globalCallbacks[combo] || _globalCallbacks[sequence]) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return _originalStopCallback.call(self, e, element, combo);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Mousetrap.bindGlobal = function(keys, callback, action) {
|
|
|
|
|
var self = this;
|
|
|
|
|
self.bind(keys, callback, action);
|
|
|
|
|
|
|
|
|
|
if (keys instanceof Array) {
|
|
|
|
|
for (var i = 0; i < keys.length; i++) {
|
|
|
|
|
_globalCallbacks[keys[i]] = true;
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_globalCallbacks[keys] = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Pressing escape to execute one escape action. We use `bindGloabal` vecause
|
|
|
|
|
// the shortcut sould work on textarea and inputs as well.
|
|
|
|
|
Mousetrap.bindGlobal('esc', function() {
|
|
|
|
|
EscapeActions.executeLowest();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// On a left click on the document, we try to exectute one escape action (eg,
|
|
|
|
|
// close the popup). We don't execute any action if the user has clicked on a
|
|
|
|
|
// link or a button.
|
2015-08-25 22:18:43 +02:00
|
|
|
$(document).on('click', function(evt) {
|
|
|
|
|
if (evt.button === 0 &&
|
2015-06-11 12:35:08 +02:00
|
|
|
$(evt.target).closest('a,button,.is-editable').length === 0) {
|
2015-08-23 11:04:35 +02:00
|
|
|
EscapeActions.clickExecute(evt.target, 'multiselection');
|
2015-06-07 10:30:27 +02:00
|
|
|
}
|
|
|
|
|
});
|