added key-speciffic debounce support (see: 'Enter' kandling)...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2018-08-31 01:59:01 +03:00
parent aa0760fcb9
commit 8ff734bf65
3 changed files with 151 additions and 70 deletions

View File

@ -256,8 +256,7 @@ module.GLOBAL_KEYBOARD = {
// modes... // modes...
//Enter: 'toggleSingleImage', Enter: '@500 toggleSingleImage',
Enter: 'debounce: 500 "toggleSingleImage" -- Toggle single image mode (debounced)',
S: 'slideshowDialog', S: 'slideshowDialog',
@ -513,6 +512,120 @@ var KeyboardActions = actions.Actions({
function(){ return that.dom }) function(){ return that.dom })
return kb }, return kb },
debounce: ['- Interface/',
core.doc`Debounce action call...
Debounce call an action...
.debounce(action, ...)
.debounce(timeout, action, ...)
.debounce(tag, action, ...)
.debounce(timeout, tag, action, ...)
Debounce call a function...
.debounce(tag, func, ...)
.debounce(timeout, tag, func, ...)
NOTE: when using a tag, it must not resolve to and action, i.e.
this[tag] must not be callable...
NOTE: this ignores action return value and returns this...
`,
function(...args){
// parse the args...
var timeout = typeof(args[0]) == typeof(123) ?
args.shift()
: (this.config['debounce-action-timeout'] || 200)
// NOTE: this[tag] must not be callable, otherwise we treat it
// as an action...
var tag = (args[0] instanceof Function
|| this[args[0]] instanceof Function) ?
args[0]
: args.shift()
var action = args.shift()
// when debouncing a function a tag is required...
if(tag instanceof Function){
throw new TypeError('debounce: when passing a function a tag is required.')
}
var attr = '__debounce_'+ tag
// repeated call...
if(this[attr]){
this[attr +'_retriggered'] = true
// setup and first call...
} else {
// NOTE: we are ignoring the return value here so as to
// make the first and repeated call uniform...
var context = this
;(action instanceof Function ?
action
: action.split('.')
.reduce(function(res, e){
context = res
return res[e]
}, this))
.call(context, ...args)
this[attr] = setTimeout(function(){
delete this[attr]
// retrigger...
if(this[attr +'_retriggered']){
delete this[attr +'_retriggered']
tag == action ?
this.debounce(timeout, action, ...args)
: this.debounce(timeout, tag, action, ...args)
}
}.bind(this), timeout)
}
}],
// Add debounce support to keyboard handling...
//
// NOTE: these are not actions to make the call as light as possible...
parseStringHandler: function(txt, ...rest){
var debounce = 0
var scale = {
ms: 1,
s: 1000,
m: 1000 * 60,
h: 1000 * 60 * 60,
}
txt = txt.replace(/^\s*@([^\s]*)\s*/,
function(_, time){
var unit = time
.replace(/(\d+|\d*\.\d+)(h|m|s|ms)/, '$2')
unit = unit == time ? 'ms' : unit
debounce = parseFloat(time) * scale[unit]
return ''
})
return debounce > 0 ?
Object.assign(
this.parseStringAction(txt, ...rest),
{debounce: debounce})
: this.parseStringAction(txt, ...rest)
},
callKeyboardHandler: function(data, context){
context = context || this
var meth = data.action
.split('.')
.reduce(function(res, e){
context = res
return res[e]
}, this)
return data.debounce ?
// debounce...
this.debounce(
data.debounce,
'tag:'+data.action,
meth.bind(context), ...data.arguments)
// direct call...
: meth.call(context, ...data.arguments) },
testKeyboardDoc: ['- Interface/', testKeyboardDoc: ['- Interface/',
core.doc`Self-test action keyboard configuration.`, core.doc`Self-test action keyboard configuration.`,
{self_test: true}, {self_test: true},
@ -529,7 +642,8 @@ var KeyboardActions = actions.Actions({
// XXX we should also check if code is a key (i.e. alias)... // XXX we should also check if code is a key (i.e. alias)...
var a = keyboard.parseActionCall(code, that) //var a = keyboard.parseActionCall(code, that)
var a = that.parseStringHandler(code, that)
// skip aliases that look like actions (namely ':') and bad actions... // skip aliases that look like actions (namely ':') and bad actions...
if(a.action == ''){ if(a.action == ''){
return return
@ -691,7 +805,8 @@ var KeyboardActions = actions.Actions({
// - .no_default // - .no_default
// - .stop_propagation // - .stop_propagation
var normalizeHandler = function(action){ var normalizeHandler = function(action){
var a = keyboard.parseActionCall(action.doc || action, that) //var a = keyboard.parseActionCall(action.doc || action, that)
var a = that.parseStringHandler(action.doc || action, that)
return a.action in that ? return a.action in that ?
a.action a.action
+(a.arguments.length > 0 ? +(a.arguments.length > 0 ?
@ -838,56 +953,6 @@ var KeyboardActions = actions.Actions({
this.config['keyboard-repeat-pause-check'] > 0 this.config['keyboard-repeat-pause-check'] > 0
&& this.keyboard.pauseRepeat && this.keyboard.pauseRepeat
&& this.keyboard.pauseRepeat() }], && this.keyboard.pauseRepeat() }],
debounce: ['- Interface/',
core.doc`Debounce action call...
.debounce(action, ...)
.debounce(timeout, action, ...)
.debounce(timeout, tag, action, ...)
NOTE: when using a tag, it must not resolve to and action, i.e.
this[tag] must not be callable...
NOTE: this ignores action return value and returns this...
`,
function(...args){
// parse the args...
var timeout = typeof(args[0]) == typeof(123) ?
args.shift()
: (this.config['debounce-action-timeout'] || 200)
// NOTE: this[tag] must not be callable, otherwise we treat it
// as an action...
var tag = this[args[0]] instanceof Function ?
args[0]
: args.shift()
var action = args.shift()
var attr = '__debounce_'+ tag
// repeated call...
if(this[attr]){
this[attr +'_retriggered'] = true
// setup and first call...
} else {
// NOTE: we are ignoring the return value here so as to
// make the first and repeated call uniform...
this[action](...args)
this[attr] = setTimeout(function(){
delete this[attr]
// retrigger...
if(this[attr +'_retriggered']){
delete this[attr +'_retriggered']
tag == action ?
this.debounce(timeout, action, ...args)
: this.debounce(timeout, tag, action, ...args)
}
}.bind(this), timeout)
}
}],
}) })
var Keyboard = var Keyboard =
@ -913,7 +978,8 @@ module.Keyboard = core.ImageGridFeatures.Feature({
this.__keyboard_config = this.keybindings || GLOBAL_KEYBOARD this.__keyboard_config = this.keybindings || GLOBAL_KEYBOARD
// string action call parser... // string action call parser...
this.parseStringHandler = this.parseStringAction //this.parseStringHandler = this.parseStringAction
//this.parseStringHandler = this.parseStringActionWithDebounce
this.toggleKeyboardHandling('on') this.toggleKeyboardHandling('on')
}], }],
@ -1069,7 +1135,8 @@ var KeyboardUIActions = actions.Actions({
var c = 0 var c = 0
Object.keys(keys[mode] || {}).forEach(function(action){ Object.keys(keys[mode] || {}).forEach(function(action){
var o = keyboard.parseActionCall(action, that) //var o = keyboard.parseActionCall(action, that)
var o = that.parseStringHandler(action, that)
if(getKeyText){ if(getKeyText){
var doc = '' var doc = ''
@ -1419,7 +1486,8 @@ var KeyboardUIActions = actions.Actions({
['&ctdot;', function(evt, elem){ ['&ctdot;', function(evt, elem){
code = code || '' code = code || ''
// highlight the current action... // highlight the current action...
var a = keyboard.parseActionCall(code, that) //var a = keyboard.parseActionCall(code, that)
var a = that.parseStringHandler(code, that)
var p = a.action in that ? var p = a.action in that ?
that.getDocPath(a.action) that.getDocPath(a.action)
: '' : ''
@ -1444,6 +1512,9 @@ var KeyboardUIActions = actions.Actions({
.on('edit-commit', .on('edit-commit',
function(evt, text){ code = text }) function(evt, text){ code = text })
// XXX should we edit/view this separately???
//make(['Debounce:', that.parseStringHandler(code).debounce || ''])
make('---') make('---')
make.EditableList(keys, { make.EditableList(keys, {

View File

@ -536,6 +536,18 @@ var KeyboardPrototype = {
// XXX revise name... // XXX revise name...
parseStringHandler: parseActionCall, parseStringHandler: parseActionCall,
// call keyboard handler...
//
callKeyboardHandler: function(data, context){
// call the handler...
return data.action
.split('.')
.reduce(function(res, k){
context = res
return res[k]
}, context)
.apply(context, h.arguments) },
// utils... // utils...
event2key: KeyboardClassPrototype.event2key, event2key: KeyboardClassPrototype.event2key,
@ -1193,7 +1205,9 @@ function makeKeyboardHandler(keyboard, unhandled, actions){
// action call syntax... // action call syntax...
// XXX should this be a Keyboard thing or a context thing??? // XXX should this be a Keyboard thing or a context thing???
} else if(actions.parseStringHandler || kb.parseStringHandler){ } else if(actions.parseStringHandler || kb.parseStringHandler){
var h = (actions.parseStringHandler || kb.parseStringHandler)(handler, actions) var h = actions.parseStringHandler ?
actions.parseStringHandler(handler, actions)
: kb.parseStringHandler(handler, actions)
var path = h ? h.action.split('.') : [] var path = h ? h.action.split('.') : []
if(path.length > 0 && path[0] in actions){ if(path.length > 0 && path[0] in actions){
@ -1204,13 +1218,9 @@ function makeKeyboardHandler(keyboard, unhandled, actions){
&& evt.preventDefault() && evt.preventDefault()
// call the handler... // call the handler...
var context = actions res = actions.callKeyboardHandler ?
res = path actions.callKeyboardHandler(h, actions)
.reduce(function(res, k){ : kb.callKeyboardHandler(h, actions)
context = res
return res[k]
}, actions)
.apply(context, h.arguments)
evt evt
&& h.stop_propagation && h.stop_propagation

View File

@ -1,6 +1,6 @@
{ {
"name": "ImageGrid.Viewer.g4", "name": "ImageGrid.Viewer.g4",
"version": "4.0.0a", "version": "4.0.0-a",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -1141,11 +1141,11 @@
"integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ=="
}, },
"ig-actions": { "ig-actions": {
"version": "3.19.3", "version": "3.20.0",
"resolved": "https://registry.npmjs.org/ig-actions/-/ig-actions-3.19.3.tgz", "resolved": "https://registry.npmjs.org/ig-actions/-/ig-actions-3.20.0.tgz",
"integrity": "sha512-UPhN/RVUGNJ9PSV/zrgZNDDm5ySPYvcrRTheHZ5QW/UtdKtEI0CGTbmTVGuEAXH/Ag23KexJnUUlnSrtU5Jsjw==", "integrity": "sha512-TcKAJg3ZtbZC7IiaNW/cMgwDkEt5xrr8hsTsoH0UTA9gQSIlnp6TACxcTW/gOS9lQiK82yDYHMrmH12Yn6+SFg==",
"requires": { "requires": {
"ig-object": "^1.0.0" "ig-object": "^1.0.7"
} }
}, },
"ig-features": { "ig-features": {