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...