added key binding tips to action menu...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2017-01-03 02:55:25 +03:00
parent 2a04daa811
commit ef441ff473
11 changed files with 203 additions and 340 deletions

View File

@ -261,6 +261,30 @@ body {
}
*/
/* XXX experimental key mappings... */
.browse-widget.show-keys .list>div:after {
display: inline;
position: relative;
content: attr(keys);
float: right;
margin-left: 0.5em;
margin-right: 0.5em;
opacity: 0.3;
font-style: italic;
}
.browse-widget.show-keys .list>div.disabled:after {
opacity: 0.5;
}
.browse-widget.show-keys .list>div:hover:after {
opacity: 0.5;
}
.browse-widget.show-keys .list>div.disabled:hover:after {
opacity: 1;
}
/* DEBUG stuff... */
.container-center {

View File

@ -210,6 +210,8 @@
padding-left: 10px;
padding-right: 10px;
box-sizing: border-box;
/* XXX not sure about this yet... */
background-color: rgba(0, 0, 0, 0.1);
}
.browse-widget .list .button:hover {
opacity: 0.9;

View File

@ -1,5 +1,30 @@
<!DOCTYPE html>
<html>
<!--
//---------------------------------------------------------------------
// Experiment: use native scroll for ribbons and view...
// Factors:
// + the browser will do all the heavy lifting and do it faster
// than we can ever hope to do it in JS (assumption)
// - will require us to add an extra container per ribbon
//
// Experiment result:
// - more uniform and fast across browsers
// (except FF - can't disable scrollbars, need to cheat)
// - less controllable (inertia, gestures, ...)
// - is affected by scaling in a bad way - paralax...
//
// Conclusion:
// - this again brings us to using code to control the scroll
// which in turn defeats the original purpose of avoiding
// extra complexity...
//
// See:
// experiments/native-scroll-ribbon.html
//
-->
<style>
.mark-center:after {
position: absolute;

View File

@ -468,8 +468,8 @@ var JournalActions = actions.Actions({
this.journalable = this.actions
.filter(function(action){
return !!that.getAttr(action, 'undo')
|| !!that.getAttr(action, 'journal')
return !!that.getActionAttr(action, 'undo')
|| !!that.getActionAttr(action, 'journal')
})
// reset the handler
.map(function(action){
@ -538,7 +538,7 @@ var JournalActions = actions.Actions({
var a = journal[i]
// see if the action has an explicit undo attr...
var undo = this.getAttr(a.action, 'undo')
var undo = this.getActionAttr(a.action, 'undo')
// general undo...
if(undo){

View File

@ -868,13 +868,20 @@ var FileSystemLoaderUIActions = actions.Actions({
browsePath: ['File/Browse file system...',
widgets.makeUIDialog(function(base, callback){
var that = this
var cfg = this.config['file-browser-settings']
base = base || this.location.path || '/'
base = util.normalizePath(base)
var o = browseWalk.makeWalk(
null, base, this.config['image-file-pattern'],
this.config['file-browser-settings'])
{
cls: 'file-browser',
disableFiles: cfg.disableFiles,
showNonTraversable: cfg.showNonTraversable,
showDisabled: cfg.showDisabled,
})
// path selected...
.open(function(evt, path){
var item = o.selected
@ -932,8 +939,6 @@ var FileSystemLoaderUIActions = actions.Actions({
config.showNonTraversable = o.options.showNonTraversable
})
o.dom.addClass('file-browser')
return o
})],

View File

@ -429,8 +429,7 @@ var KeyboardActions = actions.Actions({
},
get keyboard(){
return this.__keyboard_config
},
return this.__keyboard_config },
pauseKeyboardRepeat: ['- Interface/',
function(){
@ -523,6 +522,47 @@ var KeyboardActions = actions.Actions({
},
['on', 'off'])],
// Format:
// {
// <action>: {
// <mode>: [
// <key>,
// ...
// ],
// ...
// },
// ...
// }
//
getKeysForAction: ['- Interface/',
function(actions){
actions = arguments.length == null || actions == '*' ? this.actions
: arguments.length > 1 ? [].slice.call(arguments)
: actions
actions = actions instanceof Array ? actions : [actions]
var paths = this.getPath(actions)
var help = keyboard.buildKeybindingsHelp(this.keyboard, null, this)
var res = {}
Object.keys(paths).map(function(k){
var action = paths[k][0]
var keys = keyboard.getKeysByDoc(k, help)
if(Object.keys(keys).length > 0){
res[action] = keys
}
})
return res
}],
// XXX argument #3 is not yet used (see: lib/keyboard.js)...
getKeyboardModes: ['- Interface/',
function(){
return keyboard.getApplicableModes(this.keyboard, null, this.ribbons.viewer) }],
// XXX need to pre-process the docs...
// - remove the path component...
// - insert the action name where not doc present...

View File

@ -788,6 +788,8 @@ var BrowseActionsActions = actions.Actions({
showHidden: false,
showEmpty: false,
},
'browse-actions-keys': true,
},
// Browse actions dialog...
@ -862,6 +864,21 @@ var BrowseActionsActions = actions.Actions({
var priority = /^(-?[0-9]+)\s*:\s*/
var dialog
// get keys for each action...
var keys = this.getKeysForAction ? this.getKeysForAction('*') : {}
var modes = this.getKeyboardModes ? this.getKeyboardModes() : []
// Get keys for action...
var getKeys = function(action){
var k = keys[action] || {}
return Object.keys(k)
// keep only the applicable modes...
.filter(function(m){ return modes.indexOf(m) >= 0 })
.map(function(m){ return k[m] })
.reduce(function(a, b){ return a.concat(b) }, [])
.join(' / ')
}
// Get item from tree level taking into account additional
// syntax like prioority...
// returns:
@ -882,13 +899,14 @@ var BrowseActionsActions = actions.Actions({
return []
}
// Get action browse mode (disabled or hidden)...
var getMode = function(action){
var m = action
var visited = [m]
// handle aliases...
do {
m = actions.getAttr(m, 'browseMode')
m = actions.getActionAttr(m, 'browseMode')
// check for loops...
if(m && visited[m] != null){
m = null
@ -1040,12 +1058,15 @@ var BrowseActionsActions = actions.Actions({
disabled: mode == 'hidden' || mode == 'disabled',
hidden: mode == 'hidden',
})
.addClass(state == cur_state ? 'selected highlighted' : '')
.attr('keys', getKeys(action))
.addClass([
state == cur_state ? 'selected highlighted' : '',
mode == 'hidden' ? mode : ''
].join(' '))
.on('open', function(){
actions[action](state)
that.pop()
})
.addClass(mode == 'hidden' ? mode : '')
})
// Level: lister -- hand control to lister...
@ -1107,8 +1128,10 @@ var BrowseActionsActions = actions.Actions({
actions[action]()
that.update()
that.select('"'+ text +'"')
}]
}],
//[getKeys(action)],
]})
.attr('keys', getKeys(action))
.addClass(mode == 'hidden' ? mode : '')
.on('open', function(){
// XXX can this open a dialog???
@ -1127,6 +1150,7 @@ var BrowseActionsActions = actions.Actions({
disabled: mode == 'hidden' || mode == 'disabled',
hidden: mode == 'hidden',
})
.attr('keys', getKeys(action))
.on('open', function(){
waitFor(actions[action]())
})
@ -1145,6 +1169,8 @@ var BrowseActionsActions = actions.Actions({
})
}
}, {
cls: 'browse-actions',
path: path,
flat: false,
@ -1163,8 +1189,20 @@ var BrowseActionsActions = actions.Actions({
config.showHidden = dialog.options.showHidden
})
this.config['browse-actions-keys']
&& dialog.dom.addClass('show-keys')
return dialog
})],
toggleBrowseActionKeys: ['Interface/Show keys in menu',
core.makeConfigToggler(
'browse-actions-keys',
[true, false],
function(state){
this.modal.client.dom.hasClass('browse-actions')
&& this.modal.client.dom[state ? 'addClass' : 'removeClass']('show-keys')
})],
})
var BrowseActions =
@ -1177,6 +1215,9 @@ module.BrowseActions = core.ImageGridFeatures.Feature({
'ui',
'ui-dialogs',
],
suggested: [
'keyboard',
],
actions: BrowseActionsActions,
})

View File

@ -4,13 +4,13 @@
*
* Features:
* - ui
* maps ribbons to base (data + images)
* maps ribbons to base feature (data + images)
* - config-local-storage
* maintain configuration state in local storage
* - ui-url-hash
* handle .location.hash
* - ui-ribbon-auto-align
* handle ribbon alignment...
* unify and handle ribbon alignment...
* - ui-ribbon-align-to-order
* - ui-ribbon-align-to-first
* - ui-ribbon-manual-align
@ -18,7 +18,7 @@
* manage UI non-css animations...
* - ui-autohide-cursor
* - ui-control
* control mechanics (touch/mouse)
* touch/mouse control mechanics
*
* Dev Features:
* - fail-safe-devtools
@ -31,12 +31,6 @@
* - auto-single-image
* - auto-ribbon
*
* Legacy:
* - ui-clickable
* - ui-direct-control-hammer
* - ui-indirect-control
*
*
*
**********************************************************************/
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
@ -1589,74 +1583,8 @@ module.ShiftAnimation = core.ImageGridFeatures.Feature({
/*********************************************************************/
// Mouse...
/*
// XXX add setup/taredown...
var Clickable =
module.Clickable = core.ImageGridFeatures.Feature({
title: '',
doc: '',
tag: 'ui-clickable',
depends: ['ui'],
config: {
'click-threshold': {
t: 100,
d: 5,
},
},
handlers: [
// setup click targets...
// XXX click only if we did not drag...
['updateImage',
function(res, gid){
var that = this
var img = this.ribbons.getImage(gid)
// set the clicker only once...
if(!img.prop('clickable')){
var x, y, t, last, threshold
img
.prop('clickable', true)
.on('mousedown touchstart', function(evt){
threshold = that.config['click-threshold']
x = evt.clientX
y = evt.clientY
t = Date.now()
})
.on('mouseup touchend', function(evt){
if(that.__control_in_progress){
return
}
// prevent another handler within a timeout...
// XXX not too sure about this...
if(t - last < threshold.t){
return
}
// constrain distance between down and up events...
if(x != null
&& Math.max(
Math.abs(x - evt.clientX),
Math.abs(y - evt.clientY)) < threshold.d){
// this will prevent double clicks...
x = null
y = null
that.focusImage(that.ribbons.getElemGID($(this)))
last = Date.now()
}
})
}
}],
],
})
*/
//---------------------------------------------------------------------
// Auto-hide cursor...
//
// NOTE: for legacy stuff see: features/ui-legacy.js
// NOTE: removing the prop 'cursor-autohide' will stop hiding the cursor
// and show it on next timeout/mousemove.
@ -1758,7 +1686,6 @@ module.AutoHideCursor = core.ImageGridFeatures.Feature({
/*********************************************************************/
// Touch/Control...
//
@ -1768,243 +1695,13 @@ module.AutoHideCursor = core.ImageGridFeatures.Feature({
// Each new interaction should increment this when starting and
// decrement when done.
// This should be removed when 0
//
// This is to enable other events to handle the situation gracefully
//
// XXX how should multiple long interactions be handled??
/*
// XXX add zoom...
// XXX add vertical pan to ribbon-set...
var DirectControlHammer =
module.DirectControlHammer = core.ImageGridFeatures.Feature({
title: '',
doc: '',
tag: 'ui-direct-control-hammer',
exclusive: ['ui-control'],
depends: [
'ui',
// this is only used to trigger reoad...
//'ui-partial-ribbons',
],
config: {
// This can be:
// 'silent' - silently focus central image after pan
// true - focus central image after pan
// null - do nothing.
'focus-central-image': 'silent',
},
// XXX add setup/taredown...
// XXX add inertia...
// XXX hide current image indicator on drag...
// XXX add swipe up/down control...
// XXX add mode switching....
// XXX BUG: after panning and silent focus, marking works correctly
// but image is not updated -- mark not drawn...
handlers: [
// setup ribbon dragging...
// XXX it is possible to drag over the loaded ribbon section with
// two fingers, need to force update somehow...
// ...and need to try and make the update in a single frame...
// Ways to go:
// - update on touchdown
// - update on liftoff
// XXX drag in single image mode ONLY if image is larger than screen...
['updateRibbon',
function(_, target){
var that = this
var r = this.ribbons.getRibbon(target)
// setup dragging...
if(r.length > 0 && !r.hasClass('draggable')){
r
.addClass('draggable')
.hammer()
.on('pan', function(evt){
//evt.stopPropagation()
// XXX stop all previous animations...
//r.velocity("stop")
var d = that.ribbons.dom
var s = that.scale
var g = evt.gesture
var data = r.data('drag-data')
// we just started...
if(!data){
that.__control_in_progress = (that.__control_in_progress || 0) + 1
// hide and remove current image indicator...
// NOTE: it will be reconstructed on
// next .focusImage(..)
var m = that.ribbons.viewer
.find('.current-marker')
.velocity({opacity: 0}, {
duration: 100,
complete: function(){
m.remove()
},
})
// store initial position...
var data = {
left: d.getOffset(this).left
}
r.data('drag-data', data)
}
// do the actual move...
d.setOffset(this, data.left + (g.deltaX / s))
// when done...
if(g.isFinal){
r.removeData('drag-data')
// XXX this seems to have trouble with off-screen images...
var central = that.ribbons.getImageByPosition('center', r)
// load stuff if needed...
that.updateRibbon(central)
// XXX add inertia....
//console.log('!!!!', g.velocityX)
//r.velocity({
// translateX: (data.left + g.deltaX + (g.velocityX * 10)) +'px'
//}, 'easeInSine')
// silently focus central image...
if(that.config['focus-central-image'] == 'silent'){
that.data.current = that.ribbons.getElemGID(central)
// focus central image in a normal manner...
} else if(that.config['focus-central-image']){
that.focusImage(that.ribbons.getElemGID(central))
}
setTimeout(function(){
that.__control_in_progress -= 1
if(that.__control_in_progress <= 0){
delete that.__control_in_progress
}
}, 50)
}
})
}
}],
],
})
// XXX try direct control with hammer.js
// XXX load state from config...
// XXX sometimes this makes the indicator hang for longer than needed...
// XXX BUG: this conflicts a bit whith ui-clickable...
// ...use this with hammer.js taps instead...
// XXX might be a good idea to make a universal and extensible control
// mode toggler...
// ...obvious chice would seem to be a meta toggler:
// config['control-mode'] = {
// <mode-name>: <mode-toggler>
// }
// and the action will toggle the given mode on and the previous
// off...
// XXX this seems a bit too complicated...
var IndirectControl =
module.IndirectControl = core.ImageGridFeatures.Feature({
title: '',
doc: '',
tag: 'ui-indirect-control',
// XXX is this correct???
exclusive: ['ui-control'],
depends: ['ui'],
config: {
},
actions: actions.Actions({
toggleSwipeHandling:['Interface/Toggle indirect control swipe handling',
toggler.Toggler(null,
function(_, state){
if(state == null){
return (this.ribbons
&& this.ribbons.viewer
&& this.ribbons.viewer.data('hammer'))
|| 'none'
// on...
} else if(state == 'handling-swipes'){
var that = this
var viewer = this.ribbons.viewer
// prevent multiple handlers...
if(viewer.data('hammer') != null){
return
}
viewer.hammer()
var h = viewer.data('hammer')
h.get('swipe').set({direction: Hammer.DIRECTION_ALL})
viewer
.on('swipeleft', function(){ that.nextImage() })
.on('swiperight', function(){ that.prevImage() })
.on('swipeup', function(){ that.shiftImageUp() })
.on('swipedown', function(){ that.shiftImageDown() })
// off...
} else {
this.ribbons.viewer
.off('swipeleft')
.off('swiperight')
.off('swipeup')
.off('swipedown')
.removeData('hammer')
}
},
'handling-swipes')],
}),
handlers: [
['load',
function(){ a.toggleSwipeHandling('on') }],
['stop',
function(){ a.toggleSwipeHandling('off') }],
],
})
*/
//---------------------------------------------------------------------
// Experiment: use native scroll for ribbons and view...
// Factors:
// + the browser will do all the heavy lifting and do it faster
// than we can ever hope to do it in JS (assumption)
// - will require us to add an extra container per ribbon
//
// Experiment result:
// - more uniform and fast across browsers
// (except FF -- can't disable scrollbars, need to cheat)
// - less controllable (inertia, gestures, ...)
// - is affected by scaling in a bad way -- paralax...
//
// Conclusion:
// - this again brings us to using code to control the scroll
// which in turn defeats the original purpose of avoiding
// extra complexity...
//
// See:
// experiments/native-scroll-ribbon.html
// XXX revise...
//
// NOTE: for legacy stuff see: features/ui-legacy.js
// XXX STUB: needs more thought....
var ControlActions = actions.Actions({
@ -2448,7 +2145,7 @@ var ControlActions = actions.Actions({
})],
// XXX we are not using this....
/*// XXX we are not using this....
__control_mode_handlers__: {
indirect:
function(state){
@ -2484,6 +2181,7 @@ var ControlActions = actions.Actions({
this.config['control-mode'] = state
})],
//*/
})

View File

@ -137,6 +137,36 @@ function doc(text, func){
}
// Get list of applicable modes...
//
// XXX elem is not used...
var getApplicableModes =
module.getApplicableModes =
function getApplicableModes(keybindings, modes, elem){
return Object.keys(keybindings)
.filter(function(title){
if(keybindings[title].pattern != null){
var mode = keybindings[title].pattern
} else {
var mode = title
}
// check if we need to skip this mode...
return modes == 'all'
// explicit mode match...
|| modes == mode
// 'any' means we need to check the mode...
|| (modes == 'any'
// '*' always matches...
&& mode == '*'
// match the mode...
// XXX is this too global???
|| $(mode).length != 0)
})
}
// Build or normalize a modifier string.
//
// Acceptable argument sets:
@ -369,6 +399,9 @@ function getKeyHandlers(key, modifiers, keybindings, modes, shifted_keys, action
shifted_keys = shifted_keys || _SHIFT_KEYS
actions = actions || {}
// XXX too global -- need to pass some context...
var applicable_modes = getApplicableModes(keybindings, modes)
if(typeof(key) == typeof(123)){
key = key
chr = toKeyName(key)
@ -403,22 +436,10 @@ function getKeyHandlers(key, modifiers, keybindings, modes, shifted_keys, action
}
// older version compatibility...
if(keybindings[title].pattern != null){
var mode = keybindings[title].pattern
} else {
var mode = title
}
var mode = keybindings[title].pattern || title
// check if we need to skip this mode...
if( !(modes == 'all'
// explicit mode match...
|| modes == mode
// 'any' means we need to check the mode...
|| (modes == 'any'
// '*' always matches...
&& mode == '*'
// match the mode...
|| $(mode).length != 0))){
if(applicable_modes.indexOf(title) < 0){
continue
}

View File

@ -140,6 +140,10 @@ var BrowserClassPrototype = {
browser.addClass('flat')
}
if(options.cls){
browser.addClass(options.cls)
}
// path...
var path = $('<div>')
.addClass('v-block path')
@ -189,6 +193,9 @@ var BrowserPrototype = {
// option defaults and doc...
options: {
// CSS classes to add to widget...
cls: null,
// Initial path...
//path: null,

View File

@ -23,11 +23,11 @@
"glob": "^4.0.6",
"guarantee-events": "^1.0.0",
"ig-features": "^2.0.0",
"ig-actions": "^1.9.0",
"ig-actions": "^2.0.0",
"ig-object": "^1.0.1",
"openseadragon": "^2.1.0",
"requirejs": "^2.1.23",
"sharp": "^0.15.0"
"sharp": "^0.17.0"
},
"optionalDependencies": {
"flickrapi": "^0.3.28"