diff --git a/ui (gen4)/css/widget/browse.css b/ui (gen4)/css/widget/browse.css index 719a2eb9..fa52d5cd 100755 --- a/ui (gen4)/css/widget/browse.css +++ b/ui (gen4)/css/widget/browse.css @@ -187,10 +187,10 @@ overflow: hidden; } -.browse-widget .list>div:after { +.browse-widget .list>div[count]:after { display: inline-block; - content: attr(count); + content: "(" attr(count) ")"; float: right; margin-right: 10px; diff --git a/ui (gen4)/lib/widget/browse-walk.js b/ui (gen4)/lib/widget/browse-walk.js index 90c79cba..5ad5b9b8 100755 --- a/ui (gen4)/lib/widget/browse-walk.js +++ b/ui (gen4)/lib/widget/browse-walk.js @@ -126,7 +126,7 @@ function(path, make){ // count the number of files... // NOTE: we do not care how long it will take // so we'll not wait... - if(res && dir){ + if(res && dir && that.options.fileCountPattern){ var i = 0 glob(path +'/'+ file +'/'+ that.options.fileCountPattern) /* @@ -137,7 +137,7 @@ function(path, make){ */ .on('end', function(lst){ i += 1 - elem.attr('count', '('+ lst.length +')') + elem.attr('count', lst.length) }) } }) @@ -233,7 +233,8 @@ WalkPrototype.options = { list: listDir, - fileCountPattern: '*+(jpg|jpeg|png|JPG|JPEG|PNG)', + //disableItemPattern: false, + fileCountPattern: '*', } WalkPrototype.options.__proto__ = browse.Browser.prototype.options diff --git a/ui (gen4)/lib/widget/browse.html b/ui (gen4)/lib/widget/browse.html index c8b814a6..3bb2324c 100755 --- a/ui (gen4)/lib/widget/browse.html +++ b/ui (gen4)/lib/widget/browse.html @@ -272,7 +272,7 @@ requirejs(['../keyboard', '../object', './browse'], function(k, o, br){ f2 = browser.makeList($('.container.flat2'), { 'option 1': function(_, p){ console.log('option:', p) }, 'option 2': function(_, p){ console.log('option:', p) }, - 'option 3': function(_, p){ console.log('option:', p) }, + '- option 3': function(_, p){ console.log('option:', p) }, 'option 4': function(_, p){ console.log('option:', p) }, }) // another way to handle the opening of items... @@ -284,11 +284,12 @@ requirejs(['../keyboard', '../object', './browse'], function(k, o, br){ f3 = browser.makePathList($('.container.pathlist'), { // build a basic tree... // XXX need a way to trigger open events with touch/mouse... + '- /dir 3': function(_, p){ console.log('dir:', p) }, '/dir 1': function(_, p){ console.log('dir:', p) }, 'dir 1/option 1': function(_, p){ console.log('option:', p) }, // add an element to two paths... 'dir 1|dir 2|dir 3/option 2/': function(_, p){ console.log('option:', p) }, - 'dir 2/option 3': function(_, p){ console.log('option:', p) }, + '- dir 2/option 3': function(_, p){ console.log('option:', p) }, 'option 4': function(_, p){ console.log('option:', p) }, // XXX this is the wrong way to do this, but it shows a bug... diff --git a/ui (gen4)/lib/widget/browse.js b/ui (gen4)/lib/widget/browse.js index 01b17fe3..f3b56286 100755 --- a/ui (gen4)/lib/widget/browse.js +++ b/ui (gen4)/lib/widget/browse.js @@ -35,6 +35,15 @@ var quoteWS = function(str){ } +// Quote a string and convert to RegExp to match self literally. +function toRegExp(str){ + return RegExp('^' + // quote regular expression chars... + +str.replace(/([\.\\\/\(\)\[\]\$\*\+\-\{\}\@\^\&\?\<\>])/g, '\\$1') + +'$') +} + + function makeBrowserMaker(constructor){ return function(elem, list, rest){ if(typeof(rest) == typeof('str')){ @@ -527,11 +536,13 @@ var BrowserPrototype = { // ...will need to account for 1-9 shortcut keys and hints to // still work... toggleNonTraversableDrawing: function(){ + var cur = this.selected if(this.options.traversable == false){ return this } this.options.showNonTraversable = !this.options.showNonTraversable this.update() + cur && this.select(cur) return this }, // XXX this will not affect elements that were disabled via setting @@ -542,11 +553,13 @@ var BrowserPrototype = { // ...will need to account for 1-9 shortcut keys and hints to // still work... toggleDisabledDrawing: function(){ + var cur = this.selected if(this.options.toggleDisabledDrawing == false){ return this } this.options.showDisabled = !this.options.showDisabled this.update() + cur && this.select(cur) return this }, @@ -882,6 +895,15 @@ var BrowserPrototype = { // 'a b c' - three sub patterns: 'a', 'b' and 'c' // 'a\ b\ c' - single pattern // + // Get element exactly matching a string... + // .filter() + // -> elements + // NOTE: this supports bot single and double quotes, e.g. + // '"abc"' and "'abc'" are equivalent... + // NOTE: only outer quotes are considered, so if there is a + // need to exactly match '"X"', just add a set of quotes + // around it, e.g. '""X""' or '\'"X"\''... + // // Get all elements matching a regexp... // .filter() // -> elements @@ -965,7 +987,12 @@ var BrowserPrototype = { } // regexp... - } else if(pattern.constructor == RegExp){ + } else if(pattern.constructor == RegExp + || (typeof(pattern) == typeof('str') + && /^'.*'$|^".*"$/.test(pattern.trim()))){ + if(typeof(pattern) == typeof('str')){ + pattern = toRegExp(pattern.trim().slice(1, -1)) + } var filter = function(i, e){ if(!pattern.test($(e).find('.text').text())){ if(rejected){ @@ -1270,6 +1297,9 @@ var BrowserPrototype = { // NOTE: if text matches one of the reserved commands above use // quotes to escape it... // .select('') + // -> elem + // + // Select element by its literal full test... // .select("''") // .select('""') // -> elem @@ -1369,11 +1399,6 @@ var BrowserPrototype = { // string... } else if(typeof(elem) == typeof('str')){ - // clear quotes... - // XXX can an item contain '"' or "'"??? - if(/^'.*'$|^".*"$/.test(elem.trim())){ - elem = elem.trim().slice(1, -1) - } return this.select(this.filter(elem).first(), filtering) // regexp... @@ -1991,6 +2016,10 @@ Browser.prototype.__proto__ = widget.Widget.prototype // , // ... // ] +// +// If starts with a '- ' then it will be added disabled, +// to control the pattern use the .disableItemPattern option, and to +// disable this feature set it to false|null. // // NOTE: this essentially a different default configuration of Browser... var ListPrototype = Object.create(BrowserPrototype) @@ -2001,13 +2030,34 @@ ListPrototype.options = { traversable: false, flat: true, + // XXX not sure if we need these... + skipDisabledItems: false, + // NOTE: to disable this set it to false or null + disableItemPattern: '^- ', + list: function(path, make){ var that = this var data = this.options.data var keys = data.constructor == Array ? data : Object.keys(data) + var pattern = this.options.disableItemPattern + && RegExp(this.options.disableItemPattern) + return keys .map(function(k){ - var e = make(k) + var disable = null + + if(pattern){ + var n = k.replace(pattern, '') + if(n != k){ + disable = true + + if(that.options.skipDisabledItems){ + return + } + } + } + + var e = make(n, null, disable) if(data !== keys){ e.on('open', function(){ @@ -2105,6 +2155,9 @@ module.makeList = makeBrowserMaker(List) // NOTE: in the A|B|C pattern, ALL of the alternatives will be created. // NOTE: there may be multiple matching patterns/listers or a given path // the one used is the longest match. +// NOTE: if path is receded with '- ' ('- a|b/c') then the basename of +// that path will be disabled, to control the pattern use +// .disableItemPattern and to disable this feature set it to false. // // // Handler format: @@ -2136,10 +2189,21 @@ PathListPrototype.options = { traversable: true, flat: false, + // XXX not sure if we need these... + skipDisabledItems: false, + // NOTE: to disable this set it to false or null + disableItemPattern: '^- ', + list: function(path, make){ var that = this var data = this.options.data var keys = data.constructor == Array ? data : Object.keys(data) + var pattern = this.options.disableItemPattern + && RegExp(this.options.disableItemPattern) + + if(pattern && this.options.skipDisabledItems){ + keys = keys.filter(function(k){ return !pattern.test(k) }) + } var visited = [] @@ -2182,6 +2246,13 @@ PathListPrototype.options = { } else { return keys .map(function(k){ + var disable = null + if(pattern){ + var n = k.replace(pattern, '') + disable = n != k + k = n + } + var kp = k.split(/[\\\/]+/g) kp[0] == '' && kp.shift() @@ -2203,26 +2274,36 @@ PathListPrototype.options = { return false } - cur.split('|').forEach(function(cur){ - if(visited.indexOf(cur) >= 0){ - // set element to traversable... - if(kp.length > 0){ - that.filter(cur).removeClass('not-traversable') + cur.split('|') + // skip empty path items... + // NOTE: this avoids creating empty items in cases + // of paths ending with '/' or containing '//' + .filter(function(e){ return e.trim() != '' }) + .forEach(function(cur){ + if(visited.indexOf(cur) >= 0){ + // set element to traversable if we visit it again... + if(kp.length > 0){ + that.filter(cur, false) + .removeClass('not-traversable') + //.removeClass('disabled') + } + return false } - return false - } - visited.push(cur) + visited.push(cur) - // build the element.... - var e = make(cur, star || kp.length > 0) + // build the element.... + var e = make(cur, + star || kp.length > 0, + // XXX this might still disable a dir... + !star && kp.length == 0 && disable) - // setup handlers... - if(!star && data !== keys && kp.length == 0 && data[k] != null){ - e.on('open', function(){ - return that.options.data[k].apply(this, arguments) - }) - } - }) + // setup handlers... + if(!star && data !== keys && kp.length == 0 && data[k] != null){ + e.on('open', function(){ + return that.options.data[k].apply(this, arguments) + }) + } + }) return cur }) diff --git a/ui (gen4)/viewer.js b/ui (gen4)/viewer.js index 42a0629f..e9dca98e 100755 --- a/ui (gen4)/viewer.js +++ b/ui (gen4)/viewer.js @@ -35,7 +35,7 @@ var ribbons = require('ribbons') /*********************************************************************/ -// helpers... +// Helpers... function reloadAfter(force){ return function(){ @@ -250,12 +250,12 @@ actions.Actions({ // basic life-cycle actions... // // XXX do we need to call .syncTags(..) here??? - load: ['File|Interface/', + load: ['- File|Interface/', function(d){ this.images = images.Images(d.images) this.data = data.Data(d.data) }], - clear: ['File|Interface/', + clear: ['File|Interface/Clear viewer', function(){ delete this.data delete this.images @@ -263,7 +263,7 @@ actions.Actions({ // NOTE: for complete isolation it is best to completely copy the // .config... - clone: ['File/', + clone: ['- File/', function(full){ var res = actions.MetaActions.clone.call(this, full) @@ -293,7 +293,7 @@ actions.Actions({ // adding new items to the resulting structure... // XXX is this the correct way to go??? // ...can we save simple attribute values??? - json: ['File/Dump state as JSON object', + json: ['- File/Dump state as JSON object', 'This will collect JSON data from every available attribute ' +'supporting the .dumpJSON() method.', function(mode){ @@ -314,7 +314,7 @@ actions.Actions({ // basic navigation... // - focusImage: ['Navigate/Focus image', + focusImage: ['- Navigate/Focus image', function(img, list){ this.data.focusImage(img, list) }], @@ -326,7 +326,7 @@ actions.Actions({ // 'visual' - focus visually closest to current image // // NOTE: default mode is set in .config.ribbon-focus-mode - focusRibbon: ['Navigate/Focus Ribbon', + focusRibbon: ['- Navigate/Focus Ribbon', function(target, mode){ var data = this.data var r = data.getRibbon(target) @@ -464,9 +464,9 @@ actions.Actions({ }], // XXX should these be here??? - prevTagged: ['Navigate/Previous image tagged with tag', + prevTagged: ['- Navigate/Previous image tagged with tag', makeTagWalker('prev')], - nextTagged: ['Navigate/Next image tagged with tag', + nextTagged: ['- Navigate/Next image tagged with tag', makeTagWalker('next')], firstRibbon: ['Navigate/First ribbon', @@ -484,7 +484,7 @@ actions.Actions({ // NOTE: for all of these, current/ribbon image is a default... // XXX to be used for things like mark/place and dragging... - shiftImageTo: ['Edit|Sort/', + shiftImageTo: ['- Edit|Sort/', function(target, to){ // XXX }], @@ -634,7 +634,7 @@ actions.Actions({ // tags... // // XXX mark updated... - tag: ['Tag/Tag image(s)', + tag: ['- Tag/Tag image(s)', function(tags, gids){ gids = gids || this.current gids = gids.constructor !== Array ? [gids] : gids @@ -660,7 +660,7 @@ actions.Actions({ }) }], // XXX mark updated... - untag: ['Tag/Untag image(s)', + untag: ['- Tag/Untag image(s)', function(tags, gids){ gids = gids || this.current gids = gids.constructor !== Array ? [gids] : gids @@ -733,7 +733,7 @@ actions.Actions({ // crop... // - crop: ['Crop/Crop image list', + crop: ['- Crop/Crop image list', function(list, flatten){ list = list || this.data.order if(this.crop_stack == null){ @@ -844,7 +844,7 @@ actions.Actions({ // grouping... // XXX need to tell .images about this... - group: ['Group|Edit/Group images', + group: ['- Group|Edit/Group images', function(gids, group){ this.data.group(gids, group) }], ungroup: ['Group|Edit/Ungroup images', function(gids, group){ this.data.ungroup(gids, group) }], @@ -852,7 +852,7 @@ actions.Actions({ // direction can be: // 'next' // 'prev' - groupTo: ['Group|Edit/Group to', + groupTo: ['- Group|Edit/Group to', function(target, direction){ target = this.data.getImage(target) var other = this.data.getImage(target, direction == 'next' ? 1 : -1) @@ -1065,7 +1065,7 @@ actions.Actions({ // XXX problem: need to either redesign this or distinguish from // other actions as I keep calling it expecting results... // XXX hide from user action list... - updateImage: ['Interface/Update image (This will do nothing)', + updateImage: ['- Interface/Update image (This will do nothing)', 'This will be called by .refresh(..) and intended for use as an ' +'trigger for handlers, and not as a callable acation.', function(gid, image){ }], @@ -1081,7 +1081,7 @@ actions.Actions({ 'dark', 'light' ]) ], - setEmptyMsg: ['Interface/Set message to be displayed when nothing is loaded.', + setEmptyMsg: ['- Interface/Set message to be displayed when nothing is loaded.', function(msg, help){ this.ribbons.setEmptyMsg(msg, help) }], @@ -1184,7 +1184,7 @@ actions.Actions({ // NOTE: this will align only a single image... // XXX do we need these low level primitives here??? - centerImage: ['Interface/Center an image in ribbon horizontally', + centerImage: ['- Interface/Center an image in ribbon horizontally', function(target, align){ target = target instanceof jQuery ? this.ribbons.getElemGID(target) @@ -1193,7 +1193,7 @@ actions.Actions({ // align current ribbon... this.ribbons.centerImage(target, align) }], - centerRibbon: ['Interface/Center a ribbon vertically', + centerRibbon: ['- Interface/Center a ribbon vertically', function(target){ target = target instanceof jQuery ? this.ribbons.getElemGID(target) @@ -1493,7 +1493,7 @@ module.Journal = ImageGridFeatures.Feature({ }], // XXX might be good to add some kind of metadata to journal... - journalPush: ['Journal/Add an item to journal', + journalPush: ['- Journal/Add an item to journal', function(){ this.journal = (this.hasOwnProperty('journal') || this.journal) ? @@ -1515,7 +1515,7 @@ module.Journal = ImageGridFeatures.Feature({ this.journal = null } }], - runJournal: ['Journal/Run journal', + runJournal: ['- Journal/Run journal', function(journal){ var that = this journal.forEach(function(e){ @@ -1694,7 +1694,7 @@ var PartialRibbonsActions = actions.Actions({ // XXX coordinate this with .resizeRibbon(..) // XXX make this support an explicit list of gids.... // XXX should this be here??? - preCacheJumpTargets: ['Interface/Pre-cache potential jump target images', + preCacheJumpTargets: ['- Interface/Pre-cache potential jump target images', function(target, sources, radius, size){ target = target instanceof jQuery ? this.ribbons.getElemGID(target) @@ -1806,7 +1806,7 @@ var PartialRibbonsActions = actions.Actions({ // - we are less than screen width from the edge // - threshold is set to 0 // XXX this is not signature compatible with data.updateRibbon(..) - updateRibbon: ['Interface/Update partial ribbon size', + updateRibbon: ['- Interface/Update partial ribbon size', function(target, w, size, threshold){ target = target instanceof jQuery ? this.ribbons.getElemGID(target) @@ -1884,7 +1884,7 @@ var PartialRibbonsActions = actions.Actions({ } }], // XXX do we handle off-screen ribbons here??? - resizeRibbon: ['Interface/Resize ribbon to n images', + resizeRibbon: ['- Interface/Resize ribbon to n images', function(target, size){ size = size || (this.config['ribbon-size-screens'] * this.screenwidth) @@ -2291,7 +2291,7 @@ module.ShiftAnimation = ImageGridFeatures.Feature({ //--------------------------------------------------------------------- var BoundsIndicatorsActions = actions.Actions({ - flashIndicator: ['Interface/Flash an indicator', + flashIndicator: ['- Interface/Flash an indicator', function(direction){ if(this.ribbons.getRibbonSet().length == 0){ return @@ -2433,7 +2433,7 @@ var CurrentImageIndicatorActions = actions.Actions({ 'current-image-indicator-screen-nav-mode': 'hide', }, - updateCurrentImageIndicator: ['Interface/Update current image indicator', + updateCurrentImageIndicator: ['- Interface/Update current image indicator', function(target, update_border){ var ribbon_set = this.ribbons.getRibbonSet() @@ -2874,15 +2874,19 @@ var makeActionLister = function(list, filter, pre_order){ var ActionTreeActions = actions.Actions({ config: { + // NOTE: the slashes at the end are significant, of they are not + // present the .toggleNonTraversableDrawing(..) will hide + // these paths before they can get any content... + // XXX not sure if this is a bug or not... 'action-category-order': [ - 'File', - 'Edit', - 'Navigate', + 'File/', + 'Edit/', + 'Navigate/', ], }, // XXX move this to a generic modal overlay feature... - getOverlay: ['Interface/Get overlay object', + getOverlay: ['- Interface/Get overlay object', function(o){ return overlay.getOverlay(o || this.viewer) }], @@ -3254,7 +3258,7 @@ var ImageMarkActions = actions.Actions({ // XXX }], - markTagged: ['Mark/Mark images by tags', + markTagged: ['- Mark/Mark images by tags', function(tags, mode){ var selector = mode == 'any' ? 'getTaggedByAny' : 'getTaggedByAll' @@ -3495,7 +3499,7 @@ var FileSystemLoaderActions = actions.Actions({ // XXX is this a hack??? // XXX need a more generic form... - checkPath: ['File/', + checkPath: ['- File/', function(path){ return fse.existsSync(path) }], // NOTE: when passed no path this will not do anything... @@ -3910,7 +3914,7 @@ var URLHistoryActions = actions.Actions({ } }], - setTopURLHistory: ['History/', + setTopURLHistory: ['- History/', function(url){ var data = this.url_history[url] @@ -3921,7 +3925,7 @@ var URLHistoryActions = actions.Actions({ delete this.url_history[url] this.url_history[url] = data }], - pushURLToHistory: ['History/', + pushURLToHistory: ['- History/', function(url, open, check){ var l = this.config['url-history-length'] || -1 @@ -3954,7 +3958,7 @@ var URLHistoryActions = actions.Actions({ }], // NOTE: url can be an index, 0 being the last url added to history; // negative values are also supported. - dropURLFromHistory: ['History/', + dropURLFromHistory: ['- History/', function(url){ this.url_history = this.url_history || {} @@ -3966,7 +3970,7 @@ var URLHistoryActions = actions.Actions({ delete this.url_history[url] } }], - checkURLFromHistory: ['History/', + checkURLFromHistory: ['- History/', function(url){ this.url_history = this.url_history || {} @@ -3990,7 +3994,7 @@ var URLHistoryActions = actions.Actions({ return true } }], - openURLFromHistory: ['History/', + openURLFromHistory: ['- History/', function(url){ this.url_history = this.url_history || {} @@ -4096,7 +4100,7 @@ var URLHistoryLocalStorageActions = actions.Actions({ localStorage[loaded] = this.base_path } }], - loadLastSavedBasePath: ['History/', + loadLastSavedBasePath: ['- History/', function(){ var loaded = this.config['url-history-loaded-local-storage-key'] @@ -4187,12 +4191,6 @@ var URLHistoryUIActions = actions.Actions({ }) to_remove = [] } - var makeRE = function(path){ - return RegExp('^' - // quote regular expression chars... - +path.replace(/([\.\\\/\(\)\[\]\$\*\+\-\{\}\@\^\&\?\<\>])/g, '\\$1') - +'$') - } var o = overlay.Overlay(this.ribbons.viewer, browse.makeList( @@ -4205,7 +4203,7 @@ var URLHistoryUIActions = actions.Actions({ ['♦', function(p){ var top = this.filter().first() - var cur = this.filter(makeRE(p)) + var cur = this.filter('"'+p+'"') if(!top.is(cur)){ top.before(cur) @@ -4215,7 +4213,7 @@ var URLHistoryUIActions = actions.Actions({ // mark for removal... ['×', function(p){ - var e = this.filter(makeRE(p)) + var e = this.filter('"'+p+'"') .toggleClass('strike-out') if(e.hasClass('strike-out')){ @@ -4435,7 +4433,7 @@ var FileSystemWriterActions = actions.Actions({ // // For more info see file.writeIndex(..) and file.loadIndex(..). // - prepareIndexForWrite: ['File/Prepare index for writing', + prepareIndexForWrite: ['- File/Prepare index for writing', function(json, full){ json = json || this.json('base') var changes = full ? null