From b79ef79242ef97bbbed34bfed19991e2633a9af9 Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Fri, 27 Jan 2017 17:33:21 +0300 Subject: [PATCH] added support for item shortcuts in browse... Signed-off-by: Alex A. Naanou --- ui (gen4)/css/experimenting.css | 49 +++++++++------- ui (gen4)/css/widget/browse.css | 75 ++++++++++++------------ ui (gen4)/features/ui-widgets.js | 21 ++++--- ui (gen4)/lib/keyboard.js | 3 +- ui (gen4)/lib/widget/browse.js | 98 ++++++++++++++++++++++++++++---- ui (gen4)/lib/widget/widget.js | 5 ++ 6 files changed, 172 insertions(+), 79 deletions(-) diff --git a/ui (gen4)/css/experimenting.css b/ui (gen4)/css/experimenting.css index 21523acd..1eada8c6 100755 --- a/ui (gen4)/css/experimenting.css +++ b/ui (gen4)/css/experimenting.css @@ -164,20 +164,25 @@ body { } -.browse-widget.cloud-view .list>div { +.browse-widget .list .keyboard-shortcut { + text-decoration: underline; +} + + +.browse-widget.cloud-view .list .item { font-size: small; } /* browse pinned items... */ -.browse-widget .list>div:not(.pinned) .pin-set { +.browse-widget .list .item:not(.pinned) .pin-set { display: none; } -.browse-widget .list>div.pinned .pin-unset { +.browse-widget .list .item.pinned .pin-unset { display: none; } /* -.browse-widget .list>div.pinned + :not(.pinned) { +.browse-widget .list .item.pinned + :not(.pinned) { border-top: solid 1px rgba(255, 255, 255, 0.3); } */ @@ -189,12 +194,12 @@ body { /* Metadata viewer */ .item-value-view .text:first-child, -.browse-widget.metadata-view .list>div .text:first-child { +.browse-widget.metadata-view .list .item .text:first-child { width: 50%; font-weight: bold; } .item-value-view .text:nth-child(2), -.browse-widget.metadata-view .list>div .text:nth-child(2) { +.browse-widget.metadata-view .list .item .text:nth-child(2) { font-style: italic; -moz-user-select: auto; @@ -208,36 +213,36 @@ body { /* External Editor List */ -.browse-widget.editor-list .list>div:first-child .text:after { +.browse-widget.editor-list .list .item:first-child .text:after { content: "(primary)"; margin-left: 5px; opacity: 0.5; font-style: italic; } /* XXX this is ugly -- use a class... */ -.browse-widget.editor-list .list>div:first-child .button:nth-child(4) { +.browse-widget.editor-list .list .item:first-child .button:nth-child(4) { opacity: 0.1; } -.browse-widget.editor-list .list>div:nth-child(2):not(:last-child) .text:after { +.browse-widget.editor-list .list .item:nth-child(2):not(:last-child) .text:after { content: "(secondary)"; margin-left: 5px; opacity: 0.5; font-style: italic; } /* XXX this is ugly -- use a class... */ -.browse-widget.editor-list .list>div:nth-child(2) .button:nth-child(3) { +.browse-widget.editor-list .list .item:nth-child(2) .button:nth-child(3) { opacity: 0.1; } /* slideshow interval list... */ -.browse-widget.tail-action .list>div:last-child { +.browse-widget.tail-action .list .item:last-child { margin-top: 0.2em; border-top: solid 1px rgba(255,255,255, 0.2); } -.browse-widget.tail-action .list>div:last-child .text { +.browse-widget.tail-action .list .item:last-child .text { font-style: italic; } -.browse-widget.tail-action .list>div:last-child .button { +.browse-widget.tail-action .list .item:last-child .button { display: none; } @@ -274,7 +279,7 @@ body { */ /* XXX experimental key mappings... */ -.browse-widget.browse-actions.show-keys .list>div:after { +.browse-widget.browse-actions.show-keys .list .item:after { display: inline; position: relative; content: attr(keys); @@ -286,14 +291,14 @@ body { opacity: 0.3; font-style: italic; } -.browse-widget.browse-actions.show-keys .list>div.disabled:after { +.browse-widget.browse-actions.show-keys .list .item.disabled:after { opacity: 0.5; } -.browse-widget.browse-actions.show-keys .list>div:hover:after { +.browse-widget.browse-actions.show-keys .list .item:hover:after { opacity: 0.5; } -.browse-widget.browse-actions.show-keys .list>div.disabled:hover:after { +.browse-widget.browse-actions.show-keys .list .item.disabled:hover:after { opacity: 1; } @@ -305,10 +310,10 @@ body { /* key binding editor... */ -.browse-widget.key-bindings .list>div:not(.selected):not(.mode):nth-child(even) { +.browse-widget.key-bindings .list .item:not(.selected):not(.mode):nth-child(even) { background: rgba(0, 0, 0, 0.03); } -.browse-widget.key-bindings .list>div .button { +.browse-widget.key-bindings .list .item .button { background-color: rgba(0, 0, 0, 0.1); } @@ -324,7 +329,7 @@ body { font-style: italic; } -.browse-widget.key-bindings .list>div .text:not(:first-child) { +.browse-widget.key-bindings .list .item .text:not(:first-child) { display: inline; position: relative; @@ -336,7 +341,7 @@ body { font-style: italic; } /* NOTE: the last element is a space... */ -.browse-widget.key-bindings.browse .list>div .text:last-child { +.browse-widget.key-bindings.browse .list .item .text:last-child { margin-right: 0em; } @@ -367,7 +372,7 @@ body { /* dark theme... */ -.dark .browse-widget.key-bindings .list>div:not(.selected):not(.mode):nth-child(even) { +.dark .browse-widget.key-bindings .list .item:not(.selected):not(.mode):nth-child(even) { background: rgba(255, 255, 255, 0.03); } diff --git a/ui (gen4)/css/widget/browse.css b/ui (gen4)/css/widget/browse.css index ec460af1..15392f53 100755 --- a/ui (gen4)/css/widget/browse.css +++ b/ui (gen4)/css/widget/browse.css @@ -215,7 +215,7 @@ opacity: 0.9; background-color: rgba(0, 0, 0, 0.2); } -.browse-widget .list>div { +.browse-widget .list .item { padding: 5px; padding-left: 15px; padding-right: 15px; @@ -233,7 +233,7 @@ /*white-space: nowrap;*/ overflow: hidden; } -.browse-widget .list>div[count]:after { +.browse-widget .list .item[count]:after { display: inline-block; content: "(" attr(count) ")"; @@ -245,39 +245,39 @@ opacity: 0.4; } /* highlight seach... */ -.browse-widget .list>div .text b { +.browse-widget .list .item .text b { background-color: rgba(0, 0, 255, 0.5); } -.browse-widget .list>div.strike-out .text { +.browse-widget .list .item.strike-out .text { text-decoration: line-through; opacity: 0.3; } -.browse-widget .list>div.highlighted { +.browse-widget .list .item.highlighted { font-style: italic; font-weight: bold; } -.browse-widget .list>div.highlighted .text:last-child:after { +.browse-widget .list .item.highlighted .text:last-child:after { content: ' *'; } .browse-widget:not(.flat) .list div:not(.not-traversable) .text:after { content: "/"; } -.browse-widget .list>div.selected:focus, -.browse-widget .list>div.selected :focus, -.browse-widget:focus .list>div.selected, +.browse-widget .list .item.selected:focus, +.browse-widget .list .item.selected :focus, +.browse-widget:focus .list .item.selected, .browse-widget .path>.dir:hover, -.browse-widget .list>div:hover { +.browse-widget .list .item:hover { background: rgba(0, 0, 0, 0.05); opacity: 0.9; } -.browse-widget .list>div.selected { +.browse-widget .list .item.selected { background: rgba(0, 0, 0, 0.08); } -.browse-widget .list>div.selected:focus, -.browse-widget .list>div.selected :focus, -.browse-widget:focus .list>div.selected { +.browse-widget .list .item.selected:focus, +.browse-widget .list .item.selected :focus, +.browse-widget:focus .list .item.selected { background: rgba(0, 0, 0, 0.08); box-shadow: rgba(0, 0, 0, 0.2) 0.1em 0.1em 0.2em; @@ -285,16 +285,16 @@ } /* XXX need to make the next two different... */ -.browse-widget .list>div.filtered-out { +.browse-widget .list .item.filtered-out { opacity: 0.5; } -.browse-widget:not(.show-filtered-out) .list>div.filtered-out { +.browse-widget:not(.show-filtered-out) .list .item.filtered-out { display: none; } -.browse-widget .list>div.disabled { +.browse-widget .list .item.disabled { opacity: 0.3; } -.browse-widget .list>div.hidden { +.browse-widget .list .item.hidden { font-style: italic; } @@ -306,42 +306,45 @@ float: left; font-size: small; } -.browse-widget.filtering .list>div .text:first-child:before { +.browse-widget.filtering .list .item .text:first-child:before { display: none; } -.browse-widget .list>div .text:first-child:before { +.browse-widget .list .item .text:first-child:before { width: 12px; margin-left: -15px; } -.browse-widget:not(.no-item-numbers) .list>div:nth-of-type(1) .text:first-child:before { +.browse-widget:not(.no-item-numbers) .list .item:nth-of-type(1) .text:first-child:before { content: "1"; } -.browse-widget:not(.no-item-numbers) .list>div:nth-of-type(2) .text:first-child:before { +.browse-widget:not(.no-item-numbers) .list .item:nth-of-type(2) .text:first-child:before { content: "2"; } -.browse-widget:not(.no-item-numbers) .list>div:nth-of-type(3) .text:first-child:before { +.browse-widget:not(.no-item-numbers) .list .item:nth-of-type(3) .text:first-child:before { content: "3"; } -.browse-widget:not(.no-item-numbers) .list>div:nth-of-type(4) .text:first-child:before { +.browse-widget:not(.no-item-numbers) .list .item:nth-of-type(4) .text:first-child:before { content: "4"; } -.browse-widget:not(.no-item-numbers) .list>div:nth-of-type(5) .text:first-child:before { +.browse-widget:not(.no-item-numbers) .list .item:nth-of-type(5) .text:first-child:before { content: "5"; } -.browse-widget:not(.no-item-numbers) .list>div:nth-of-type(6) .text:first-child:before { +.browse-widget:not(.no-item-numbers) .list .item:nth-of-type(6) .text:first-child:before { content: "6"; } -.browse-widget:not(.no-item-numbers) .list>div:nth-of-type(7) .text:first-child:before { +.browse-widget:not(.no-item-numbers) .list .item:nth-of-type(7) .text:first-child:before { content: "7"; } -.browse-widget:not(.no-item-numbers) .list>div:nth-of-type(8) .text:first-child:before { +.browse-widget:not(.no-item-numbers) .list .item:nth-of-type(8) .text:first-child:before { content: "8"; } -.browse-widget:not(.no-item-numbers) .list>div:nth-of-type(9) .text:first-child:before { +.browse-widget:not(.no-item-numbers) .list .item:nth-of-type(9) .text:first-child:before { content: "9"; } +.browse-widget:not(.no-item-numbers) .list .item:nth-of-type(10) .text:first-child:before { + content: "0"; +} .browse-widget .list>hr.separator { opacity: 0.3; @@ -396,10 +399,10 @@ opacity: 0.5; } -.browse-widget .list>div.heading:hover { +.browse-widget .list .item.heading:hover { background: rgba(0, 0, 0, 0.6); } -.browse-widget .list>div.heading.selected { +.browse-widget .list .item.heading.selected { background: rgba(0, 0, 0, 0.7); } @@ -410,15 +413,15 @@ /* Show item part on hover... */ -.browse-widget .list>div .show-on-hover { +.browse-widget .list .item .show-on-hover { opacity: 0; } -.browse-widget .list>div:hover .show-on-hover { +.browse-widget .list .item:hover .show-on-hover { opacity: inherit; } /* Show item part on select... */ -.browse-widget .list>div .show-on-select { +.browse-widget .list .item .show-on-select { opacity: 0; } .browse-widget .list>.selected .show-on-select { @@ -430,7 +433,7 @@ /****************************************************** Cloud List ***/ -.browse-widget.cloud-view .list>div { +.browse-widget.cloud-view .list .item { display: inline-block; border-radius: 10px; } @@ -439,7 +442,7 @@ display: block; } -.browse-widget.cloud-view .list>div .text:first-child:before { +.browse-widget.cloud-view .list .item .text:first-child:before { content: ""; } diff --git a/ui (gen4)/features/ui-widgets.js b/ui (gen4)/features/ui-widgets.js index 96972d74..b871a14d 100755 --- a/ui (gen4)/features/ui-widgets.js +++ b/ui (gen4)/features/ui-widgets.js @@ -1386,7 +1386,7 @@ module.Buttons = core.ImageGridFeatures.Feature({ var WidgetTestActions = actions.Actions({ - testAction: ['- Test/', + testAction: ['Test/Test action', function(){ console.log('>>>', [].slice.call(arguments)) return function(){ @@ -1399,7 +1399,7 @@ var WidgetTestActions = actions.Actions({ // .testDrawer('Overlay', 'Header', 'paragraph') // - show html in overlay with // custom text... - testDrawer: ['Test/99: Drawer widget test...', + testDrawer: ['Test/99: D$rawer widget test...', makeUIDialog('Drawer', function(h, txt){ return $('
') @@ -1420,7 +1420,7 @@ var WidgetTestActions = actions.Actions({ focusable: true, })], // XXX show new features... - testBrowse: ['Test/-99: Demo new style dialog...', + testBrowse: ['Test/-99: Demo $new style dialog...', makeUIDialog(function(){ var actions = this @@ -1429,7 +1429,7 @@ var WidgetTestActions = actions.Actions({ return browse.makeLister(null, function(path, make){ var that = this - make('select last') + make('select last') .on('open', function(){ that.select(-1) }) @@ -1437,7 +1437,10 @@ var WidgetTestActions = actions.Actions({ make('do nothing') .addClass('selected') - make('nested dialog...') + make('nested dialog...', + { + shortcut_key: 'n', + }) .on('open', function(){ actions.testBrowse() }) @@ -1445,7 +1448,7 @@ var WidgetTestActions = actions.Actions({ make('---') - make('close parent') + make('$close parent') .on('open', function(){ that.parent.close() }) @@ -1467,7 +1470,7 @@ var WidgetTestActions = actions.Actions({ console.log('Dialog closing...') }) })], - testBrowseCloud: ['Test/Demo cloud dialog...', + testBrowseCloud: ['Test/Demo $cloud dialog...', makeUIDialog(function(){ var actions = this @@ -1510,7 +1513,7 @@ var WidgetTestActions = actions.Actions({ console.log('Dialog closing...') }) })], - testList: ['Test/-99: Demo new style dialog...', + testList: ['Test/-99: Demo new style $lists in dialog...', makeUIDialog(function(){ var actions = this @@ -1553,7 +1556,7 @@ var WidgetTestActions = actions.Actions({ }) })], - testProgress: ['Test/Demo progress bar...', + testProgress: ['Test/Demo $progress bar...', function(text){ var done = 0 var max = 10 diff --git a/ui (gen4)/lib/keyboard.js b/ui (gen4)/lib/keyboard.js index 6850f57c..26f7fffa 100755 --- a/ui (gen4)/lib/keyboard.js +++ b/ui (gen4)/lib/keyboard.js @@ -851,7 +851,8 @@ var KeyboardPrototype = { if(drop // explicit go to next section... && (!handler - || handler.slice(0, 4) != 'NEXT') + || (typeof(handler) == typeof('str') + && handler.slice(0, 4) != 'NEXT')) && (bindings.drop == '*' // XXX should this be more flexible by adding a // specific key combo? diff --git a/ui (gen4)/lib/widget/browse.js b/ui (gen4)/lib/widget/browse.js index 031ff5d7..7bb766fc 100755 --- a/ui (gen4)/lib/widget/browse.js +++ b/ui (gen4)/lib/widget/browse.js @@ -1049,6 +1049,22 @@ var BrowserPrototype = { // 'last' - group traversable items at bottom sortTraversable: null, + // Create item shortcuts... + // + // If false, no shortcuts will be created. + setItemShortcuts: true, + + // Item shortcut text marker... + // + // This can be a regexp string pattern or a RegExp object. This + // should contain one group containing the key. + // Everything outside the last group will be cleaned out of the + // text... + // + // NOTE: it is best to keep this HTML compatible, this makes the + // use of chars like '&' not to be recommended... + itemShortcutMarker: '\\$(\\w)', + // Controls the display of the action button on each list item... // // Possible values: @@ -1204,7 +1220,7 @@ var BrowserPrototype = { ctrl_Left: 'update!: "/"', Backspace: 'Left', Right: 'right', - P: 'push', + //P: 'push', // XXX should these also select buttons??? Tab: 'down!', @@ -1221,16 +1237,16 @@ var BrowserPrototype = { End: 'navigate!: "last"', Enter: 'action', - O: 'action', + //O: 'action', Esc: 'close: "reject"', '/': 'startFilter!', ctrl_A: 'startFullPathEdit!', - D: 'toggleDisabledDrawing', - H: 'toggleHiddenDrawing', - T: 'toggleNonTraversableDrawing', + ctrl_D: 'toggleDisabledDrawing', + ctrl_H: 'toggleHiddenDrawing', + ctrl_T: 'toggleNonTraversableDrawing', // XXX should these use .select(..)??? // XXX should these be relative to visible area or absolute @@ -1245,6 +1261,14 @@ var BrowserPrototype = { '#7': 'push!: "6!"', '#8': 'push!: "7!"', '#9': 'push!: "8!"', + '#0': 'push!: "9!"', + }, + + ItemShortcuts: { + doc: 'Item shortcuts', + pattern: '*', + + }, }, @@ -1267,7 +1291,6 @@ var BrowserPrototype = { // .type - event type/name // .args - arguments passed to trigger // - // // NOTE: event propagation for some events is disabled by binding // to them handlers that stop propagation in .__init__(..). // The list of non-propagated events in defined in @@ -1599,6 +1622,9 @@ var BrowserPrototype = { // // // element button spec... // buttons: , + // + // // shortcut key to open the item... + // shortcut_key: , // } // // format (optional): @@ -1784,6 +1810,21 @@ var BrowserPrototype = { var interactive = false var size_freed = false + // clear previous shortcuts... + var item_shortcuts = this.options.setItemShortcuts ? + (this.keybindings.ItemShortcuts = this.keybindings.ItemShortcuts || {}) + : null + // clear the shortcuts... + Object.keys(item_shortcuts).forEach(function(k){ + if(k != 'doc' && k != 'pattern'){ + delete item_shortcuts[k] + } + }) + var item_shortcut_marker = this.options.itemShortcutMarker || /&(\w)/ + item_shortcut_marker = item_shortcut_marker ? + RegExp(item_shortcut_marker) + : null + // XXX revise signature... var make = function(p, traversable, disabled, buttons){ var opts = {} @@ -1825,7 +1866,7 @@ var BrowserPrototype = { return res } - // array of str/func... + // array of str/func/dom... if(p.constructor === Array){ // resolve handlers... p = p.map(function(e){ @@ -1869,6 +1910,41 @@ var BrowserPrototype = { .text(p.replace(dir, '')) } + // keyboard shortcuts... + if(item_shortcuts){ + // key set in options... + opts.shortcut_key && !item_shortcuts[opts.shortcut_key] + && that.keyboard.handler( + 'ItemShortcuts', + opts.shortcut_key, + function(){ that.push(res) }) + + // text marker... + if(item_shortcut_marker){ + var _replace = function(){ + // get the last group... + var key = [].slice.call(arguments).slice(-3)[0] + !item_shortcuts[key] + && that.keyboard.handler( + 'ItemShortcuts', + key, + function(){ that.push(res) }) + return key + } + + txt = txt.replace(item_shortcut_marker, _replace) + + p.filter('.text') + .each(function(_, e){ + e = $(e) + e.html(e.html().replace(item_shortcut_marker, + function(){ + return '' + +_replace.apply(this, arguments) + +'' })) }) + } + } + interactive = true // skip drawing of non-traversable or disabled elements if @@ -1889,6 +1965,7 @@ var BrowserPrototype = { .append(p) res.addClass([ + 'item', // XXX use the same algorithm as .select(..) selection && res.text() == selection ? 'selected' : '', @@ -1902,7 +1979,6 @@ var BrowserPrototype = { opts.push_on_open && res.attr('push-on-open', 'on') - // buttons... // button container... var btn = res.find('.button-container') @@ -2140,7 +2216,7 @@ var BrowserPrototype = { var that = this var browser = this.dom - var elems = browser.find('.list>div:not(.not-searchable)' + var elems = browser.find('.list .item:not(.not-searchable)' + (this.options.elementSeparatorClass ? ':not('+ this.options.elementSeparatorClass +')' : '') @@ -2498,7 +2574,7 @@ var BrowserPrototype = { // NOTE: this uses .filter(..) for string and regexp matching... select: function(elem, filtering){ var browser = this.dom - var pattern = '.list>div' + var pattern = '.list .item' + (this.options.elementSeparatorClass ? ':not('+ this.options.elementSeparatorClass +')' : '') @@ -2619,7 +2695,7 @@ var BrowserPrototype = { // .navigate('next') will simply navigate to the next element while // .select('next') / .filter('next') will yield that element by name. navigate: function(action, filtering){ - var pattern = '.list>div' + var pattern = '.list .item' + (this.options.elementSeparatorClass ? ':not('+ this.options.elementSeparatorClass +')' : '') diff --git a/ui (gen4)/lib/widget/widget.js b/ui (gen4)/lib/widget/widget.js index 621d69ce..e807edaf 100755 --- a/ui (gen4)/lib/widget/widget.js +++ b/ui (gen4)/lib/widget/widget.js @@ -123,6 +123,8 @@ var WidgetPrototype = { parent = this.parent = $(parent || 'body') options = options || {} + this.keybindings = JSON.parse(JSON.stringify(this.keybindings)) + // merge options... var opts = Object.create(this.options) Object.keys(options).forEach(function(n){ opts[n] = options[n] }) @@ -201,9 +203,12 @@ var ContainerPrototype = { // the client... __init__: function(parent, client, options){ var that = this + parent = this.parent = $(parent || 'body') options = options || {} + this.keybindings = JSON.parse(JSON.stringify(this.keybindings)) + this.client = client client.parent = this