From baa1b06dcee300b70737315614200924f56b4edd Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Thu, 5 Nov 2020 22:46:27 +0300 Subject: [PATCH] reworked long action abort... the results still need some tweaking... Signed-off-by: Alex A. Naanou --- Viewer/features/base.js | 2 +- Viewer/features/core.js | 201 +++++++++++++++++++++++++++++++-- Viewer/features/metadata.js | 25 ++-- Viewer/features/sharp.js | 140 +++++++++++++++++------ Viewer/features/ui-progress.js | 87 ++++++++------ Viewer/features/ui-widgets.js | 26 ++++- Viewer/lib/tasks.js | 83 +++++--------- Viewer/package-lock.json | 6 +- Viewer/package.json | 2 +- Viewer/ui.js | 6 +- 10 files changed, 425 insertions(+), 153 deletions(-) diff --git a/Viewer/features/base.js b/Viewer/features/base.js index 2ac5100e..aee8572c 100755 --- a/Viewer/features/base.js +++ b/Viewer/features/base.js @@ -1575,7 +1575,7 @@ module.CropActions = actions.Actions({ return this.crop(list.slice(list.indexOf(image)), flatten) }], // XXX not sure if we actually need this... - cropFlatten: ['Crop/$Flatten', + cropFlatten: ['Crop|Ribbon/Crop $flatten', {mode: function(){ return this.data.ribbon_order.length <= 1 && 'disabled' }}, function(list){ this.data.length > 0 && this.crop(list, true) }], diff --git a/Viewer/features/core.js b/Viewer/features/core.js index 019f4422..036bf353 100755 --- a/Viewer/features/core.js +++ b/Viewer/features/core.js @@ -18,6 +18,7 @@ * - introspection * - lifecycle * base life-cycle events (start/stop/..) +* base abort api * - serialization * base methods to handle loading, serialization and cloning... * - cache @@ -226,6 +227,8 @@ var LoggerActions = actions.Actions({ Logger: object.Constructor('BaseLogger', { doc: `Logger object constructor...`, + quiet: false, + __context: null, get context(){ return this.__context || this.root.__context }, @@ -324,11 +327,20 @@ var LoggerActions = actions.Actions({ // main API... + // + // .push(str, ...) + // + // .push(str, ..., attrs) + // push: function(...msg){ + attrs = typeof(msg.last()) != typeof('str') ? + msg.pop() + : {} return msg.length == 0 ? this : Object.assign( this.constructor(), + attrs, { root: this.root, path: this.path.concat(msg), @@ -359,7 +371,7 @@ var LoggerActions = actions.Actions({ // call context log handler... this.context && this.context.handleLogItem - && this.context.handleLogItem(this.path, status, ...rest) + && this.context.handleLogItem(this, this.path, status, ...rest) return this }, @@ -379,15 +391,16 @@ var LoggerActions = actions.Actions({ // XXX move this to console-logger??? handleLogItem: ['- System/', - function(path, status, ...rest){ - console.log( - path.join(': ') + (path.length > 0 ? ': ' : '') - + status - + (rest.length > 1 ? - ':\n\t' - : rest.length == 1 ? - ': ' - : ''), ...rest) }], + function(logger, path, status, ...rest){ + logger.quiet + || console.log( + path.join(': ') + (path.length > 0 ? ': ' : '') + + status + + (rest.length > 1 ? + ':\n\t' + : rest.length == 1 ? + ': ' + : ''), ...rest) }], }) var Logger = @@ -583,6 +596,82 @@ module.Introspection = ImageGridFeatures.Feature({ //--------------------------------------------------------------------- // System life-cycle... +// XXX docs... +// +// action: ['Path/To/Action', +// abortablePromise('abort-id', function(abort, ...args){ +// +// abort.cleanup(function(reason, res){ +// if(reason == 'done'){ +// // ... +// } +// if(reason == 'aborted'){ +// // ... +// } +// }) +// +// return new Promise(function(resolve, reject){ +// // ... +// +// if(abort.isAborted){ +// // handle abort... +// } +// +// // ... +// }) })], +// +// +// NOTE: if the returned promise is not resolved .cleanup(..) will not +// be called even if the appropriate .abort(..) as called... +var abortablePromise = +module.abortablePromise = +function(title, func){ + return Object.assign( + function(...args){ + var that = this + + var abort = object.mixinFlat( + this.abortable(title, function(){ + that.clearAbortable(title, abort) + return abort }), + { + get isAborted(){ + return !((that.__abortable || new Map()) + .get(title) || new Set()) + .has(this) }, + + __cleanup: null, + cleanup: function(func){ + var args = [...arguments] + var reason = this.isAborted ? + 'aborted' + : 'done' + typeof(func) == 'function' ? + // register handler... + (this.__cleanup = this.__cleanup + || new Set()).add(func) + // call cleanup handlers... + : [...(this.__cleanup || [])] + .forEach(function(f){ + f.call(that, reason, ...args) }) + return this }, + }) + + return func.call(this, abort, ...args) + .then(function(res){ + abort.cleanup(res)() + return res }) + .catch(function(res){ + abort.cleanup(res)() }) }, + { + toString: function(){ + return `core.abortablePromise('${ title }', \n${ func.toString() })` }, + }) } + + + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // XXX should his have state??? // ...if so, should this be a toggler??? var LifeCycleActions = actions.Actions({ @@ -966,6 +1055,98 @@ var LifeCycleActions = actions.Actions({ .stop() .clear() .start() }], + + + // Abortable... + // + // Format: + // Map({ + // title: Set([ func, ... ]), + // ... + // }) + // + __abortable: null, + + abortable: ['- System/Register abort handler', + doc`Register abortable action + + .abortable(title, func) + -> func + + `, + function(title, callback){ + // reserved titles... + if(title == 'all' || title == '*'){ + throw new Error('.abortable(..): can not set reserved title: "'+ title +'".') } + + var abortable = this.__abortable = this.__abortable || new Map() + var set = abortable.get(title) || new Set() + abortable.set(title, set) + set.add(callback) + + return actions.ASIS(callback) }], + clearAbortable: ['- System/Clear abort handler(s)', + doc`Clear abort handler(s) + + Clear abort handler... + .clearAbortable(title, callback) + + Clear all abort handlers for title... + .clearAbortable(title) + .clearAbortable(title, 'all') + + Clear all abort handlers... + .clearAbortable('all') + + `, + function(title, callback){ + callback = callback || '*' + + // clear all... + if(title == '*' || title == 'all'){ + delete this.__abortable } + + var set = ((this.__abortable || new Map()).get(title) || new Set()) + // clear specific handler... + callback != '*' + && callback != 'all' + && set.delete(callback) + // cleanup / clear title... + ;(set.size == 0 + || callback == '*' + || callback == 'all') + && (this.__abortable || new Set()).delete(title) + // cleanup... + this.__abortable + && this.__abortable.size == 0 + && (delete this.__abortable) }], + abort: ['- System/Run abort handler(s)', + doc` + + .abort(title) + .abort([title, .. ]) + + .abort('all') + + `, + function(title){ + title = title == '*' || title == 'all' ? + [...(this.__abortable || new Map()).keys()] + : title instanceof Array ? + title + : [title] + + this.__abortable + && title + .forEach(function(title){ + [...(this.__abortable || new Map()).get(title) || []] + .forEach(function(f){ f() }) + this.__abortable + && this.__abortable.delete(title) }.bind(this)) + // cleanup... + this.__abortable + && this.__abortable.size == 0 + && (delete this.__abortable) }], }) var LifeCycle = diff --git a/Viewer/features/metadata.js b/Viewer/features/metadata.js index 65e46e07..85f0e199 100755 --- a/Viewer/features/metadata.js +++ b/Viewer/features/metadata.js @@ -106,7 +106,18 @@ var MetadataReaderActions = actions.Actions({ // XXX this uses .markChanged(..) form filesystem.FileSystemWriter // feature, but technically does not depend on it... // XXX should we store metadata in an image (current) or in fs??? + // XXX should this set .orientation / .flipped if they are not set??? readMetadata: ['- Image/Get metadata data', + core.doc` + + This will overwrite/update if: + - image .metadata is not set + - image .metadata.ImageGridMetadata is not 'full' + - force is true + + + NOTE: also see: .cacheMetadata(..) + `, function(image, force){ var that = this @@ -169,6 +180,7 @@ var MetadataReaderActions = actions.Actions({ resolve(data) }) }) }) }], + // XXX make this abortable... // XXX STUB: add support for this to .readMetadata(..) readAllMetadata: ['File/Read all metadata', function(){ @@ -254,6 +266,12 @@ var MetadataReaderActions = actions.Actions({ && this.markChanged('data') mode == 'crop' && this.crop(data) }], + + // shorthands... + cropRatingsAsRibbons: ['Ribbon|Crop/Crop ratings to ribbons', + 'ratingToRibbons: "crop"'], + splitRatingsAsRibbons: ['Ribbon/Split ratings to ribbons (in-place)', + 'ratingToRibbons: "in-place"'], }) var MetadataReader = @@ -705,13 +723,6 @@ var MetadataUIActions = actions.Actions({ && make.Separator() }) .forEach(function(e){ make(...e) }) })], - - - // shorthands... - cropRatingsAsRibbons: ['Ribbon|Crop/Split ratings to ribbons (crop)', - 'ratingToRibbons: "crop"'], - splitRatingsAsRibbons: ['Ribbon/Split ratings to ribbons (in-place)', - 'ratingToRibbons: "in-place"'], }) var MetadataUI = diff --git a/Viewer/features/sharp.js b/Viewer/features/sharp.js index 35027058..9d66ae89 100755 --- a/Viewer/features/sharp.js +++ b/Viewer/features/sharp.js @@ -7,6 +7,8 @@ (function(require){ var module={} // make module AMD/node compatible... /*********************************************************************/ +var array = require('lib/types/Array') + var actions = require('lib/actions') var features = require('lib/features') @@ -245,13 +247,22 @@ var SharpActions = actions.Actions({ NOTE: all options are optional. NOTE: this will not overwrite existing images. `, - function(images, size, path, options={}){ + core.abortablePromise('makeResizedImage', function(abort, images, size, path, options={}){ var that = this // sanity check... if(arguments.length < 3){ throw new Error('.makeResizedImage(..): ' +'need at least images, size and path.') } + + var CHUNK_SIZE = 4 + + abort.cleanup(function(reason, res){ + logger + && logger.emit('close') + && reason == 'aborted' + && logger.emit(res) }) + // get/normalize images... //images = images || this.current images = images @@ -304,7 +315,7 @@ var SharpActions = actions.Actions({ logger = logger !== false ? (logger || this.logger) : false - logger = logger && logger.push('Resize') + logger = logger && logger.push('Resize', {onclose: abort}) // backup... // XXX make backup name pattern configurable... @@ -314,8 +325,11 @@ var SharpActions = actions.Actions({ i++ } return `${to}.${timestamp}.bak`+ (i || '') } - return Promise.all(images - .map(function(gid){ + return images + .mapChunks(CHUNK_SIZE, function(gid){ + if(abort.isAborted){ + throw array.StopIteration('aborted') } + // skip non-images... if(!['image', null, undefined] .includes(that.images[gid].type)){ @@ -342,7 +356,7 @@ var SharpActions = actions.Actions({ && Math.max(m.width, m.height) < size) || (fit == 'outside' && Math.min(m.width, m.height) < size)){ - skipping(gid) + logger && logger.emit('skipping', gid) return } // continue... return img }) @@ -362,7 +376,7 @@ var SharpActions = actions.Actions({ fse.removeSync(to) // skip... } else { - skipping(gid) + logger && logger.emit('skipping', gid) return } } // write... @@ -396,11 +410,11 @@ var SharpActions = actions.Actions({ .then(function(){ logger && logger.emit('done', to) - return img }) }) }) })) }], + return img }) }) }) }) })], - // XXX test against .makePreviews(..) for speed... // XXX this does not update image.base_path -- is this correct??? - // XXX do we need to be able to run this in a worker??? + // XXX add support for offloading the processing to a thread/worker... + // XXX should we use task.Queue()??? makePreviews: ['Sharp|File/Make image $previews', core.doc`Make image previews @@ -428,16 +442,23 @@ var SharpActions = actions.Actions({ NOTE: if base_path is given .images will not be updated with new preview paths... `, - function(images, sizes, base_path, logger){ + core.abortablePromise('makePreviews', function(abort, images, sizes, base_path, logger){ var that = this + var CHUNK_SIZE = 4 + + abort.cleanup(function(reason, res){ + logger + && logger.emit('close') + && reason == 'aborted' + && logger.emit(res) }) + var logger_mode = this.config['preview-progress-mode'] || 'gids' logger = logger !== false ? (logger || this.logger) : false - var gid_logger = logger && logger.push('Images') - logger = logger && logger.push('Previews') - + var gid_logger = logger && logger.push('Images', {onclose: abort}) + logger = logger && logger.push('Previews', {onclose: abort}) // get/normalize images... //images = images || this.current @@ -473,8 +494,11 @@ var SharpActions = actions.Actions({ var path_tpl = that.config['preview-path-template'] .replace(/\$INDEX|\$\{INDEX\}/g, that.config['index-dir'] || '.ImageGrid') - return Promise.all(images - .map(function(gid){ + return images + .mapChunks(CHUNK_SIZE, function(gid){ + if(abort.isAborted){ + throw array.StopIteration('aborted') } + var img = that.images[gid] var base = base_path || img.base_path @@ -484,6 +508,9 @@ var SharpActions = actions.Actions({ return sizes .map(function(size, i){ + if(abort.isAborted){ + throw array.StopIteration('aborted') } + var name = path = path_tpl .replace(/\$RESOLUTION|\$\{RESOLUTION\}/g, parseInt(size)) .replace(/\$GID|\$\{GID\}/g, gid) @@ -512,14 +539,12 @@ var SharpActions = actions.Actions({ && that.markChanged('images', [gid]) } return [gid, size, name] }) }) }) - .flat()) }], - + .then(function(res){ + return res.flat() }) })], // XXX add support for offloading the processing to a thread/worker... - // XXX would be nice to be able to abort this... - // ...and/or have a generic abort protocol triggered when loading... - // ...use task queue??? - // XXX make each section optional... + // XXX should we use task.Queue()??? + __cache_metadata_reading: null, cacheMetadata: ['- Sharp|Image/', core.doc`Cache metadata @@ -550,21 +575,41 @@ var SharpActions = actions.Actions({ -> promise([ gid | null, .. ]) + This quickly reads/caches essential (.orientation and .flipped) + metadata and some non-essential but already there values. + + + This will overwrite/update if: + - .orientation and .flipped iff image .orientation AND .flipped + are unset or force is true + - metadata if image .metadata is not set or + .metadata.ImageGridMetadata is not set + - all metadata if force is set to true + + NOTE: this will effectively update metadata format to the new spec... + NOTE: for info on full metadata format see: .readMetadata(..) `, - function(images, logger){ + core.abortablePromise('cacheMetadata', function(abort, images, logger){ var that = this + var CHUNK_SIZE = 4 + + abort.cleanup(function(reason, res){ + logger + && logger.emit('close') + && reason == 'aborted' + && logger.emit(res) + delete that.__cache_metadata_reading }) + // handle logging and processing list... - // NOTE: these will maintain .__metadata_reading helping + // NOTE: these will maintain .__cache_metadata_reading helping // avoid processing an image more than once at the same // time... var done = function(gid, msg){ logger && logger.emit(msg || 'done', gid) - if(that.__metadata_reading){ - that.__metadata_reading.delete(gid) - if(that.__metadata_reading.size == 0){ - delete that.__metadata_reading } } + if(that.__cache_metadata_reading){ + that.__cache_metadata_reading.delete(gid) } return gid } var skipping = function(gid){ return done(gid, 'skipping') } @@ -601,13 +646,14 @@ var SharpActions = actions.Actions({ images : [images]) .filter(function(gid){ - return !that.__metadata_reading - || !that.__metadata_reading.has(gid) }) + return !that.__cache_metadata_reading + || !that.__cache_metadata_reading.has(gid) }) logger = logger !== false ? (logger || this.logger) : false - logger = logger && logger.push('Caching image metadata') + logger = logger + && logger.push('Caching image metadata', {onclose: abort}) logger && logger.emit('queued', images) /*/ XXX set this to tmp for .location.load =='loadImages' @@ -624,11 +670,15 @@ var SharpActions = actions.Actions({ //*/ return images - .mapChunks(function(gid){ + .mapChunks(CHUNK_SIZE, function(gid){ + // abort... + if(abort.isAborted){ + throw array.StopIteration('aborted') } + var img = cached_images[gid] var path = img && that.getImagePath(gid) - ;(that.__metadata_reading = - that.__metadata_reading || new Set()) + ;(that.__cache_metadata_reading = + that.__cache_metadata_reading || new Set()) .add(gid) // skip... @@ -702,11 +752,19 @@ var SharpActions = actions.Actions({ that.ribbons && that.ribbons.updateImage(gid) - return done(gid) }) }) }], + return done(gid) }) }) })], cacheAllMetadata: ['- Sharp|Image/', core.doc`Cache all metadata NOTE: this is a shorthand to .cacheMetadata('all', ..)`, 'cacheMetadata: "all" ...'], + + + // shorthands... + // XXX do we need these??? + abortMakePreviews: ['- Sharp/', + 'abort: "makePreviews"'], + abortCacheMetadata: ['- Sharp/', + 'abort: "cacheMetadata"'], }) @@ -727,9 +785,19 @@ module.Sharp = core.ImageGridFeatures.Feature({ isApplicable: function(){ return !!sharp }, handlers: [ - /* XXX this needs to be run in the background... + // XXX + ['load.pre', + function(){ + this.abort([ + 'makeResizedImage', + 'makePreviews', + 'cacheMetadata', + ]) }], + + //* XXX this needs to be run in the background... // XXX this is best done in a thread + needs to be abortable (on .load(..))... - ['loadImages', + [['loadImages', + 'loadNewImages'], function(){ this.cacheMetadata('all') }], //*/ diff --git a/Viewer/features/ui-progress.js b/Viewer/features/ui-progress.js index 6e7e829b..b8a7bfd9 100755 --- a/Viewer/features/ui-progress.js +++ b/Viewer/features/ui-progress.js @@ -24,28 +24,6 @@ var ProgressActions = actions.Actions({ 'progress-update-min': 200, }, - // Progress bar widget... - // - // Create progress bar... - // .showProgress('text') - // - // Update progress bar (value, max, msg)... - // .showProgress('text', 0, 10) - // .showProgress('text', 10, 50, 'message') - // - // Update progress bar value (has no effect if max is not set)... - // .showProgress('text', 10) - // - // Close progress bar... - // .showProgress('text', 'close') - // - // Relative progress modification... - // .showProgress('text', '+1') - // .showProgress('text', '+0', '+1') - // - // .showProgress(logger) - // - // // XXX add message to be shown... // XXX should we report errors and stoppages??? (error state??) // XXX multiple containers... @@ -53,17 +31,50 @@ var ProgressActions = actions.Actions({ // XXX revise styles... __progress_cache: null, showProgress: ['- Interface/Show progress bar...', - function(text, value, max){ + core.doc`Progress bar widget... + + Create progress bar... + .showProgress('text') + + Update progress bar (value, max, msg)... + .showProgress('text', 0, 10) + .showProgress('text', 10, 50, 'message') + + Update progress bar value (has no effect if max is not set)... + .showProgress('text', 10) + + Close progress bar... + .showProgress('text', 'close') + + Relative progress modification... + .showProgress('text', '+1') + .showProgress('text', '+0', '+1') + + .showProgress(logger) + + + `, + function(text, value, max, attrs){ var that = this var viewer = this.dom + // get attrs... + var args = [...arguments] + attrs = args.slice(1).last() instanceof Object ? + args.pop() + : null + ;[text, value, max] = args + var msg = text instanceof Array ? text.slice(1).join(': ') : null text = text instanceof Array ? text[0] : text // make sure we do not update too often... if(value != 'close'){ var cache = (this.__progress_cache = this.__progress_cache || {}) - cache = cache[text] = cache[text] || {} + cache = cache[text] = + Object.assign( + cache[text] || {}, + attrs || {}) var updateValue = function(name, value){ var v = cache[name] || 0 @@ -115,7 +126,11 @@ var ProgressActions = actions.Actions({ .text(text) // close button... .append($('×') - .on('click', function(){ widget.trigger('progressClose') })) + .on('click', function(){ + var cache = (that.__progress_cache || {})[text] + cache.onclose + && cache.onclose() + widget.trigger('progressClose') })) // state... .append($('') .addClass('progress-details')) @@ -125,10 +140,12 @@ var ProgressActions = actions.Actions({ .on('progressClose', function(){ widget .fadeOut(that.config['progress-fade-duration'] || 200, function(){ - var cache = (that.__progress_cache || {}) - cache[text].timeout - && clearTimeout(cache[text].timeout) - delete cache[text] + var cache = (that.__progress_cache || {})[text] + cache.timeout + && clearTimeout(cache.timeout) + cache.ondone + && cache.ondone() + delete (that.__progress_cache || {})[text] $(this).remove() }) }) .appendTo(container) : widget @@ -169,7 +186,7 @@ var ProgressActions = actions.Actions({ // handle logger progress... // XXX revise... handleLogItem: ['- System/', - function(path, status, ...rest){ + function(logger, path, status, ...rest){ var msg = path.join(': ') var l = (rest.length == 1 && rest[0] instanceof Array) ? rest[0].length @@ -196,24 +213,24 @@ var ProgressActions = actions.Actions({ // close... // XXX is the right keyword... if(status == 'done' && rest.length == 0){ - this.showProgress(path, 'close') + this.showProgress(path, 'close', logger) // report progress... // XXX HACK -- need meaningful status... } else if(add.includes(status)){ - this.showProgress(path, '+0', '+'+l) + this.showProgress(path, '+0', '+'+l, logger) } else if(done.includes(status)){ - this.showProgress(path, '+'+l) + this.showProgress(path, '+'+l, logger) } else if(skipped.includes(status)){ // XXX if everything is skipped the indicator does not // get hidden... - this.showProgress(path, '+'+l) + this.showProgress(path, '+'+l, logger) // XXX STUB... } else if(status == 'error' ){ - this.showProgress(['Error'].concat(msg), '+0', '+'+l) + this.showProgress(['Error'].concat(msg), '+0', '+'+l, logger) } }], }) diff --git a/Viewer/features/ui-widgets.js b/Viewer/features/ui-widgets.js index 80bfb084..b284e12c 100755 --- a/Viewer/features/ui-widgets.js +++ b/Viewer/features/ui-widgets.js @@ -2189,13 +2189,13 @@ var BrowseActionsActions = actions.Actions({ // it to sort an prioritize stuff... 'action-category-order': [ '99:$File', + // ... // We can order any sub-tree we want in the same manner // as the root... 'File/-80:Clear viewer', 'File/-90:Close viewer', // Non existing elements will not get drawn... //'File/-99:moo', - // XXX this seems over-crowded -- revise!!! '99:$Edit', 'Edit/90:Undo', 'Edit/90:Redo', @@ -2210,10 +2210,12 @@ var BrowseActionsActions = actions.Actions({ 'Edit/70:.*shift.*', 'Edit/60:.*rotate.*', 'Edit/50:.*flip.*', + // ... '$Navigate', 'Navigate/90:.*image.*', 'Navigate/80:.*screen.*', 'Navigate/70:.*ribbon.*', + // ... '$Image', 'Image/99:$Copy image', 'Image/99:.*copy.*', @@ -2229,6 +2231,7 @@ var BrowseActionsActions = actions.Actions({ 'Image/65:.*shift.*', 'Image/60:.*rotate.*', 'Image/55:.*flip.*', + // ... 'Image/-70:---', 'Image/-70:.*remove.*', '$Virtual block', @@ -2238,14 +2241,25 @@ var BrowseActionsActions = actions.Actions({ 'Virtual block/60:.*mark.*', 'Virtual block/50:---', 'Virtual block/40:.*crop.*', + // ... '$Ribbon', + 'Ribbon/80:set.*', + 'Ribbon/70:.*shift.*', + 'Ribbon/60:.*merge.*', + 'Ribbon/50:Flatten', + 'Ribbon/40:.*split.*', + 'Ribbon/30:.*crop.*', + 'Ribbon/20:.*order.*', + 'Ribbon/20:.*align.*', + // ... + 'Ribbon/-60:Add.*collection.*', 'Ribbon/-70:---', 'Ribbon/-70:.*remove.*', '$Crop', 'Crop/80:Crop $marked images', 'Crop/80:Crop $bookmarked images', 'Crop/70:$Crop', - 'Crop/70:$Flatten', + 'Crop/70:Crop $flatten', // Path patterns... // @@ -2279,32 +2293,35 @@ var BrowseActionsActions = actions.Actions({ 'Crop/-70:---', //*/ + // ... 'Crop/-50:---', 'Crop/-60:Remove from crop', 'Crop/-70:Remove ribbon.*', 'Crop/-71:Remove marked.*', 'Crop/-72:.*remove.*', - 'Crop/-75:---', - 'Crop/-80:Uncrop keeping image order', 'Crop/-81:Uncrop all', 'Crop/-82:$Uncrop', 'Co$llections', + // ... 'Collections/-50:.*exit.*', 'Collections/-60:.*edit.*', 'Collections/-70:---', 'Collections/-70:.*remove.*', '$Tag', + // ... // XXX revise... 'Tag/-80:---', 'Tag/-90:.*remove.*', '$Mark', + // ... 'Mark/-75:.*collection...', 'Mark/-80:---', 'Mark/-80:.*remove.*', 'Mark/-90:.*unmark.*', '$Bookmark', + // ... 'Bookmark/-80:---', 'Bookmark/-80:.*remove.*', @@ -2312,6 +2329,7 @@ var BrowseActionsActions = actions.Actions({ '-40:Interface', 'Interface/90:Theme', + // ... '-50:$Workspace', '-60:System', '-70:$Help', diff --git a/Viewer/lib/tasks.js b/Viewer/lib/tasks.js index 2682beb0..5fc95a84 100755 --- a/Viewer/lib/tasks.js +++ b/Viewer/lib/tasks.js @@ -17,6 +17,13 @@ var object = require('lib/object') var QueuePrototype = Object.create(actions.MetaActions) +// XXX might be good to add a Promise-like api: +// .then(..) +// .catch(..) +// .finally(..) +// ...but since the queue may be fed and running without stopping +// not sure what purpose these can serve if they are global... +// ...these could have the semantics of run after last task added... // XXX need a mechanism to either queue chains of tasks that depend on // on the previous results or a way to delay a task until what it // needs is finished... @@ -49,9 +56,7 @@ module.QueueActions = actions.Actions(QueuePrototype, { : 0) + (typeof(b) == typeof(1) ? b : that[b] ? that[b].len - : 0) - }, 0) - }, + : 0) }, 0) }, set length(val){}, // can be: @@ -71,8 +76,7 @@ module.QueueActions = actions.Actions(QueuePrototype, { // // XXX should be more informative -- now supports only 'running' and 'stopped' get state(){ - return this._state || 'stopped' - }, + return this._state || 'stopped' }, // General task life cycle events... @@ -111,8 +115,8 @@ module.QueueActions = actions.Actions(QueuePrototype, { } else { var tag = null var task = a - var mode = b - } + var mode = b } + mode = mode || this.config['default-queue-mode'] var ready = this.__ready = this.__ready || [] @@ -120,22 +124,19 @@ module.QueueActions = actions.Actions(QueuePrototype, { this.taskQueued(tag, task, mode) // restart in case the queue was depleted... - this._run() - }], + this._run() }], unqueue: ['', function(a, b){ var that = this var ready = this.__ready // empty queue... if(ready == null || ready.len == 0){ - return - } + return } // special case -- drop all... if(a == '*'){ ready.splice(0, ready.length) - return - } + return } // XXX prep args... var tag = typeof(a) == typeof('str') ? a : b @@ -143,8 +144,7 @@ module.QueueActions = actions.Actions(QueuePrototype, { // no args... if(tag == null && task == null){ - return - } + return } // remove matching tasks from the queue... ready.forEach(function(e, i){ @@ -155,17 +155,13 @@ module.QueueActions = actions.Actions(QueuePrototype, { // both task and tag given... : e[0] == tag && e[1] === task){ delete ready[i] - that.taskDropped(e[0], e[1], e[2]) - } - }) - }], + that.taskDropped(e[0], e[1], e[2]) } }) }], delay: ['', function(a, b){ var ready = this.__ready // empty queue... if(ready == null || ready.len == 0){ - return - } + return } // XXX prep args... var tag = typeof(a) == typeof('str') ? a : b @@ -173,8 +169,7 @@ module.QueueActions = actions.Actions(QueuePrototype, { // no args... if(tag == null && task == null){ - return - } + return } var delayed = [] // remove the matching tasks... @@ -188,19 +183,14 @@ module.QueueActions = actions.Actions(QueuePrototype, { if(res){ delete ready[i] - - delayed.push(e) - } - }) + delayed.push(e) } }) // push delayed list to the end of the queue... delayed.forEach(function(e){ - ready.push(e) - }) + ready.push(e) }) // restart in case the queue was depleted... - this._run() - }], + this._run() }], // Run the queue... // @@ -230,8 +220,7 @@ module.QueueActions = actions.Actions(QueuePrototype, { _run: ['', function(){ if(this.__is_running){ - return - } + return } var that = this var size = this.config['running-pool-size'] @@ -249,8 +238,7 @@ module.QueueActions = actions.Actions(QueuePrototype, { // XXX this might race... var elem = that.__ready.shift() if(elem == null){ - return - } + return } var task = elem[1] that.__is_running = true @@ -292,9 +280,7 @@ module.QueueActions = actions.Actions(QueuePrototype, { that.done() that.config['clear-on-done'] - && that.clear() - } - }) + && that.clear() } }) // push to done and ._run some more... .then(function(){ // pop self of .__running @@ -316,9 +302,7 @@ module.QueueActions = actions.Actions(QueuePrototype, { that.done() that.config['clear-on-done'] - && that.clear() - } - }) + && that.clear() } }) // other... } else { @@ -338,13 +322,10 @@ module.QueueActions = actions.Actions(QueuePrototype, { that.done() that.config['clear-on-done'] - && that.clear() - } - } + && that.clear() } } })() } - delete that.__is_running - }], + delete that.__is_running }], // State manipulation actions... // @@ -352,12 +333,10 @@ module.QueueActions = actions.Actions(QueuePrototype, { start: ['', function(){ this._state = 'ready' - this._run() - }], + this._run() }], stop: ['', function(){ - delete this._state - }], + delete this._state }], clear: ['', function(){ // XXX should this stop??? @@ -365,8 +344,7 @@ module.QueueActions = actions.Actions(QueuePrototype, { delete this.__ready delete this.__running delete this.__failed - delete this.__done - }], + delete this.__done }], }) @@ -376,5 +354,6 @@ object.Constructor('Queue', QueueActions) + /********************************************************************** * vim:set ts=4 sw=4 : */ return module }) diff --git a/Viewer/package-lock.json b/Viewer/package-lock.json index c7a047eb..47814fed 100755 --- a/Viewer/package-lock.json +++ b/Viewer/package-lock.json @@ -1117,9 +1117,9 @@ "integrity": "sha512-EzT4CP6d6lI8bnknNgT3W8mUQhSVXflO0yPbKD4dKsFcINiC6npjoEBz+8m3VQmWJhc+36pXD4JLwNxUEgzi+Q==" }, "ig-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/ig-types/-/ig-types-3.0.2.tgz", - "integrity": "sha512-djnS6UnS+a0y37DNFUk8PZf0lt7TQO5u/RQPYpFVW4sqwA2HpSdAmmrWyu6UCVdhw+b9Q6E+RjAXrWVEkn1w3A==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ig-types/-/ig-types-3.1.0.tgz", + "integrity": "sha512-k/QbS9D30Fun3Xrh+6LHpCYsbQOwYlj1PX0uaNOnpmxg2tlWSdLOdykH8IMUbGIm/tI8MsJeKnJc4vu51s89Tg==", "requires": { "ig-object": "^5.2.8", "object-run": "^1.0.1" diff --git a/Viewer/package.json b/Viewer/package.json index 6ab5cacb..0cbeea93 100755 --- a/Viewer/package.json +++ b/Viewer/package.json @@ -32,7 +32,7 @@ "ig-argv": "^2.15.0", "ig-features": "^3.4.2", "ig-object": "^5.2.8", - "ig-types": "^3.0.2", + "ig-types": "^3.1.0", "moment": "^2.29.1", "object-run": "^1.0.1", "requirejs": "^2.3.6", diff --git a/Viewer/ui.js b/Viewer/ui.js index 9c33c293..6ec70b22 100755 --- a/Viewer/ui.js +++ b/Viewer/ui.js @@ -77,8 +77,7 @@ $(function(){ } catch(err){ console.error(err) //throw err - return - } + return } // used to switch experimental actions on (set to true) or off (unset or false)... @@ -105,8 +104,7 @@ $(function(){ err.missing_suggested) err.missing.length > 0 && console.warn('Missing dependencies:', - err.missing) - } + err.missing) } // setup the viewer...