diff --git a/ui (gen4)/features/filesystem.js b/ui (gen4)/features/filesystem.js index 192fdfe7..966b4842 100755 --- a/ui (gen4)/features/filesystem.js +++ b/ui (gen4)/features/filesystem.js @@ -33,6 +33,15 @@ var browseWalk = require('lib/widget/browse-walk') +/*********************************************************************/ + +if(typeof(process) != 'undefined'){ + var copy = file.denodeify(fse.copy) + var ensureDir = file.denodeify(fse.ensureDir) +} + + + /*********************************************************************/ // fs reader/loader... @@ -758,17 +767,21 @@ var FileSystemWriterActions = actions.Actions({ exportView: ['File/Export current view', function(){ }], - // XXX not done yet... - // needs: - // ensureDir(..) - // copy(..) - // ...both denodeify(..)'ed - // XXX export current state as a full loadable index + + // Export current state as a full loadable index + // // XXX might be interesting to unify this and .exportView(..) // XXX should this return a promise??? ...a clean promise??? + // XXX add preview selection... + // XXX handle .image.path and other stack files... // XXX local collections??? + // XXX add a ui... + // - select path + // - select preview size exportCollection: ['File/Export as collection', function(path, logger){ + logger = logger || this.logger + var json = this.json() // get all loaded gids... @@ -785,6 +798,7 @@ var FileSystemWriterActions = actions.Actions({ var img = json.images[gid] if(img){ images[gid] = json.images[gid] + // remove un-needed previews... // XXX } @@ -794,27 +808,28 @@ var FileSystemWriterActions = actions.Actions({ json.data.order = gids json.images = images // XXX should we check if index dir is present in path??? - path = path +'/'+ this.config['index-dir'] + var index_path = path +'/'+ this.config['index-dir'] - // NOTE: if we are to use .saveIndex(..) here, do not forget - // to reset .changes - file.writeIndex( - this.prepareIndexForWrite(json).prepared, - path, - this.config['index-filename-template'], - logger || this.logger) - // copy previews for the loaded images... // XXX should also optionally populate the base dir and nested favs... - var base_dir = this.base_dir + var base_dir = this.location.path + gids.forEach(function(gid){ var img = json.images[gid] var img_base = img.base_path - img.base_path = path var previews = img.preview - for(var res in previews){ - var from = (img_base || base_dir) +'/'+ preview_path + // NOTE: we are copying everything to one place so no + // need for a base path... + delete img.base_path + + // XXX copy img.path -- the main image, especially when no previews present.... + // XXX + + Object.keys(previews).forEach(function(res){ + var preview_path = decodeURI(previews[res]) + + var from = (img_base || base_dir) +'/'+ preview_path var to = path +'/'+ preview_path // XXX do we queue these or let the OS handle it??? @@ -823,7 +838,7 @@ var FileSystemWriterActions = actions.Actions({ // XXX ensureDir(pathlib.dirname(to)) .catch(function(err){ - // XXX + logger && logger.emit('error', err) }) .then(function(){ return copy(from, to) @@ -832,12 +847,99 @@ var FileSystemWriterActions = actions.Actions({ // we just use the one above (after // .then(..)) .catch(function(err){ - // XXX + logger && logger.emit('error', err) + }) + .then(function(){ + logger && logger.emit('done', to) }) }) - } + }) }) + + // NOTE: if we are to use .saveIndex(..) here, do not forget + // to reset .changes + file.writeIndex( + this.prepareIndexForWrite(json, true).prepared, + index_path, + this.config['index-filename-template'], + logger || this.logger) + }], + + // XXX use options: + // - level dir name + // - size + // - filename pattern + // XXX might also be good to save/load the export state to .ImageGrid-export.json + // XXX make custom previews... + // ...should this be a function of .images.getBestPreview(..)??? + exportDirs: ['File/Export as nested directories', + function(path, pattern, level_dir, size, logger){ + logger = logger || this.logger + var that = this + var base_dir = this.location.path + var to_dir = path + + // get/set the config data... + // XXX should this store the last set??? + level_dir = level_dir || this.config['export-level-directory-name'] || 'fav' + size = size || this.config['export-preview-size'] || 1000 + pattern = pattern || this.config['export-preview-name-pattern'] || '%f' + + this.data.ribbon_order + .slice() + .reverse() + .forEach(function(ribbon){ + // NOTE: this is here to keep the specific path local to + // this scope... + var img_dir = to_dir + + ensureDir(pathlib.dirname(img_dir)) + .catch(function(err){ + logger && logger.emit('error', err) + }) + .then(function(){ + that.data.ribbons[ribbon].forEach(function(gid){ + var img = that.images[gid] + + + // get best preview... + var from = (img.base_path || base_dir) +'/'+ that.images.getBestPreview(gid, size).url + + // XXX see if we need to make a preview (sharp) + // XXX + + // XXX get/form image name... + // XXX might be a good idea to connect this to the info framework... + var ext = pathlib.extname(img.name) + var name = pattern + .replace(/%f/, img.name) + .replace(/%n/, img.name.replace(ext, '')) + .replace(/%e/, ext) + .replace(/%gid/, gid) + // XXX get the correct length... + .replace(/%g/, gid.slice(-7, -1)) + // XXX %()m marked... + // XXX + // XXX %()b bookmarked... + // XXX + // XXX EXIF... + + var to = img_dir +'/'+ name + + copy(from, to) + .catch(function(err){ + logger && logger.emit('error', err) + }) + .then(function(){ + logger && logger.emit('done', to) + }) + }) + }) + + to_dir += '/'+level_dir + }) + }] }) diff --git a/ui (gen4)/features/ui-slideshow.js b/ui (gen4)/features/ui-slideshow.js index f3c41a7d..abacc775 100755 --- a/ui (gen4)/features/ui-slideshow.js +++ b/ui (gen4)/features/ui-slideshow.js @@ -57,33 +57,25 @@ var SlideshowActions = actions.Actions({ this.suspendSlideshowTimer() + // XXX might be a good idea to make this generic... + var _makeTogglHandler = function(toggler){ + return function(){ + var txt = $(this).find('.text').first().text() + that[toggler]() + o.client.update() + .then(function(){ o.client.select(txt) }) + that.toggleSlideshow('?') == 'on' + && o.close() + } + } + var o = overlay.Overlay(this.ribbons.viewer, - browse.makeList( - null, - [ - // XXX make this editable... - ['Interval: ', - function(){ return that.config['slideshow-interval'] }], - ['Direction: ', - function(){ return that.config['slideshow-direction'] }], - ['Looping: ', - function(){ return that.config['slideshow-looping'] }], + browse.makeLister(null, function(path, make){ + make(['Interval: ', + function(){ return that.config['slideshow-interval'] }]) + .on('open', function(){ + var txt = $(this).find('.text').first().text() - //'---', - [function(){ - return that.toggleSlideshow('?') == 'on' ? 'Stop' : 'Start' }], - ]) - .open(function(evt, path){ - // start/stop... - if(path == 'Start' || path == 'Stop'){ - that.toggleSlideshow() - o.close() - return - } - - // interval... - if(/interval/i.test(path)){ - var to_remove = [] var oo = widgets.makeConfigListEditor(that, 'slideshow-intervals', { new_button: 'New...', length_limit: that.config['slideshow-interval-max-count'], @@ -107,9 +99,8 @@ var SlideshowActions = actions.Actions({ // XXX this is ugly... o.focus() - if(that.toggleSlideshow('?') == 'on'){ - o.close() - } + that.toggleSlideshow('?') == 'on' + && o.close() }) oo.client @@ -122,42 +113,36 @@ var SlideshowActions = actions.Actions({ // XXX this is ugly... oo.close() + o.client.update() - o.client.select(path.split(':')[0]) + o.client.select(txt) } }) oo.client.dom.addClass('tail-action') oo.client.select(that.config['slideshow-interval']) + }) - return - } + make(['Direction: ', + function(){ return that.config['slideshow-direction'] }]) + .on('open', _makeTogglHandler('toggleSlideshowDirection')) + make(['Looping: ', + function(){ return that.config['slideshow-looping'] }]) + .on('open', _makeTogglHandler('toggleSlideshowLooping')) - // direction... - if(/direction/i.test(path)){ - that.toggleSlideshowDirection() - o.client.update() - - // Looping... - } else if(/looping/i.test(path)){ - that.toggleSlideshowLooping() - o.client.update() - } - - // XXX this is ugly... - o.client.select(path.split(':')[0]) - - // do not keep the dialog open during the slideshow... - if(that.toggleSlideshow('?') == 'on'){ + // Start/stop... + make([function(){ + return that.toggleSlideshow('?') == 'on' ? 'Stop' : 'Start' }]) + .on('open', function(){ + that.toggleSlideshow() o.close() - } - })) + }) + })) .close(function(){ that.resetSlideshowTimer() }) o.client.dom.addClass('metadata-view tail-action') - o.client.select(-1) return o diff --git a/ui (gen4)/file.js b/ui (gen4)/file.js index 9fbc6c00..b70c424f 100755 --- a/ui (gen4)/file.js +++ b/ui (gen4)/file.js @@ -94,8 +94,15 @@ function listJSON(path, pattern){ return gGlob(path +'/'+ pattern +'.json') } +// wrap a node style callback function into a Promise... +// +// NOTE: this is inspired by the promise module for node this stopped +// working, and porting one method was simpler that trying to get +// to the bottom of the issue, especially with native promises... // XXX move to someplace generic... -var denodeify = function(func){ +var denodeify = +module.denodeify = +function(func){ return function(){ // XXX for some reason this does not see args2array... // XXX and for some reason the error is not reported... diff --git a/ui (gen4)/lib/widget/browse.js b/ui (gen4)/lib/widget/browse.js index a3d6df9b..fb49ff5f 100755 --- a/ui (gen4)/lib/widget/browse.js +++ b/ui (gen4)/lib/widget/browse.js @@ -2189,6 +2189,48 @@ Browser.prototype.__proto__ = widget.Widget.prototype +/*********************************************************************/ + +var ListerPrototype = Object.create(BrowserPrototype) +ListerPrototype.options = { + pathPrefix: '', + fullPathEdit: false, + traversable: false, + flat: true, + + // XXX not sure if we need these... + skipDisabledItems: false, + // NOTE: to disable this set it to false or null + disableItemPattern: '^- ', + + elementSeparatorText: '---', +} +// XXX should we inherit or copy options??? +// ...inheriting might pose problems with deleting values reverting +// them to default instead of nulling them and mutable options might +// get overwritten... +ListerPrototype.options.__proto__ = BrowserPrototype.options + +var Lister = +module.Lister = +object.makeConstructor('Lister', + BrowserClassPrototype, + ListerPrototype) + + +// This is a shorthand for: new List(, { data: }) +var makeLister = +module.makeLister = function(elem, lister, options){ + var opts = {} + for(var k in options){ + opts[k] = rest[k] + } + opts.list = lister + return Lister(elem, opts) +} + + + /*********************************************************************/ // Flat list...