reworked long action abort... the results still need some tweaking...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2020-11-05 22:46:27 +03:00
parent da63da2f2c
commit baa1b06dce
10 changed files with 425 additions and 153 deletions

View File

@ -1575,7 +1575,7 @@ module.CropActions = actions.Actions({
return this.crop(list.slice(list.indexOf(image)), flatten) }], return this.crop(list.slice(list.indexOf(image)), flatten) }],
// XXX not sure if we actually need this... // XXX not sure if we actually need this...
cropFlatten: ['Crop/$Flatten', cropFlatten: ['Crop|Ribbon/Crop $flatten',
{mode: function(){ {mode: function(){
return this.data.ribbon_order.length <= 1 && 'disabled' }}, return this.data.ribbon_order.length <= 1 && 'disabled' }},
function(list){ this.data.length > 0 && this.crop(list, true) }], function(list){ this.data.length > 0 && this.crop(list, true) }],

View File

@ -18,6 +18,7 @@
* - introspection * - introspection
* - lifecycle * - lifecycle
* base life-cycle events (start/stop/..) * base life-cycle events (start/stop/..)
* base abort api
* - serialization * - serialization
* base methods to handle loading, serialization and cloning... * base methods to handle loading, serialization and cloning...
* - cache * - cache
@ -226,6 +227,8 @@ var LoggerActions = actions.Actions({
Logger: object.Constructor('BaseLogger', { Logger: object.Constructor('BaseLogger', {
doc: `Logger object constructor...`, doc: `Logger object constructor...`,
quiet: false,
__context: null, __context: null,
get context(){ get context(){
return this.__context || this.root.__context }, return this.__context || this.root.__context },
@ -324,11 +327,20 @@ var LoggerActions = actions.Actions({
// main API... // main API...
//
// .push(str, ...)
//
// .push(str, ..., attrs)
//
push: function(...msg){ push: function(...msg){
attrs = typeof(msg.last()) != typeof('str') ?
msg.pop()
: {}
return msg.length == 0 ? return msg.length == 0 ?
this this
: Object.assign( : Object.assign(
this.constructor(), this.constructor(),
attrs,
{ {
root: this.root, root: this.root,
path: this.path.concat(msg), path: this.path.concat(msg),
@ -359,7 +371,7 @@ var LoggerActions = actions.Actions({
// call context log handler... // call context log handler...
this.context this.context
&& this.context.handleLogItem && this.context.handleLogItem
&& this.context.handleLogItem(this.path, status, ...rest) && this.context.handleLogItem(this, this.path, status, ...rest)
return this }, return this },
@ -379,15 +391,16 @@ var LoggerActions = actions.Actions({
// XXX move this to console-logger??? // XXX move this to console-logger???
handleLogItem: ['- System/', handleLogItem: ['- System/',
function(path, status, ...rest){ function(logger, path, status, ...rest){
console.log( logger.quiet
path.join(': ') + (path.length > 0 ? ': ' : '') || console.log(
+ status path.join(': ') + (path.length > 0 ? ': ' : '')
+ (rest.length > 1 ? + status
':\n\t' + (rest.length > 1 ?
: rest.length == 1 ? ':\n\t'
': ' : rest.length == 1 ?
: ''), ...rest) }], ': '
: ''), ...rest) }],
}) })
var Logger = var Logger =
@ -583,6 +596,82 @@ module.Introspection = ImageGridFeatures.Feature({
//--------------------------------------------------------------------- //---------------------------------------------------------------------
// System life-cycle... // 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??? // XXX should his have state???
// ...if so, should this be a toggler??? // ...if so, should this be a toggler???
var LifeCycleActions = actions.Actions({ var LifeCycleActions = actions.Actions({
@ -966,6 +1055,98 @@ var LifeCycleActions = actions.Actions({
.stop() .stop()
.clear() .clear()
.start() }], .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 = var LifeCycle =

View File

@ -106,7 +106,18 @@ var MetadataReaderActions = actions.Actions({
// XXX this uses .markChanged(..) form filesystem.FileSystemWriter // XXX this uses .markChanged(..) form filesystem.FileSystemWriter
// feature, but technically does not depend on it... // feature, but technically does not depend on it...
// XXX should we store metadata in an image (current) or in fs??? // 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', 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){ function(image, force){
var that = this var that = this
@ -169,6 +180,7 @@ var MetadataReaderActions = actions.Actions({
resolve(data) }) }) }) }], resolve(data) }) }) }) }],
// XXX make this abortable...
// XXX STUB: add support for this to .readMetadata(..) // XXX STUB: add support for this to .readMetadata(..)
readAllMetadata: ['File/Read all metadata', readAllMetadata: ['File/Read all metadata',
function(){ function(){
@ -254,6 +266,12 @@ var MetadataReaderActions = actions.Actions({
&& this.markChanged('data') && this.markChanged('data')
mode == 'crop' mode == 'crop'
&& this.crop(data) }], && 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 = var MetadataReader =
@ -705,13 +723,6 @@ var MetadataUIActions = actions.Actions({
&& make.Separator() }) && make.Separator() })
.forEach(function(e){ .forEach(function(e){
make(...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 = var MetadataUI =

View File

@ -7,6 +7,8 @@
(function(require){ var module={} // make module AMD/node compatible... (function(require){ var module={} // make module AMD/node compatible...
/*********************************************************************/ /*********************************************************************/
var array = require('lib/types/Array')
var actions = require('lib/actions') var actions = require('lib/actions')
var features = require('lib/features') var features = require('lib/features')
@ -245,13 +247,22 @@ var SharpActions = actions.Actions({
NOTE: all options are optional. NOTE: all options are optional.
NOTE: this will not overwrite existing images. NOTE: this will not overwrite existing images.
`, `,
function(images, size, path, options={}){ core.abortablePromise('makeResizedImage', function(abort, images, size, path, options={}){
var that = this var that = this
// sanity check... // sanity check...
if(arguments.length < 3){ if(arguments.length < 3){
throw new Error('.makeResizedImage(..): ' throw new Error('.makeResizedImage(..): '
+'need at least images, size and path.') } +'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... // get/normalize images...
//images = images || this.current //images = images || this.current
images = images images = images
@ -304,7 +315,7 @@ var SharpActions = actions.Actions({
logger = logger !== false ? logger = logger !== false ?
(logger || this.logger) (logger || this.logger)
: false : false
logger = logger && logger.push('Resize') logger = logger && logger.push('Resize', {onclose: abort})
// backup... // backup...
// XXX make backup name pattern configurable... // XXX make backup name pattern configurable...
@ -314,8 +325,11 @@ var SharpActions = actions.Actions({
i++ } i++ }
return `${to}.${timestamp}.bak`+ (i || '') } return `${to}.${timestamp}.bak`+ (i || '') }
return Promise.all(images return images
.map(function(gid){ .mapChunks(CHUNK_SIZE, function(gid){
if(abort.isAborted){
throw array.StopIteration('aborted') }
// skip non-images... // skip non-images...
if(!['image', null, undefined] if(!['image', null, undefined]
.includes(that.images[gid].type)){ .includes(that.images[gid].type)){
@ -342,7 +356,7 @@ var SharpActions = actions.Actions({
&& Math.max(m.width, m.height) < size) && Math.max(m.width, m.height) < size)
|| (fit == 'outside' || (fit == 'outside'
&& Math.min(m.width, m.height) < size)){ && Math.min(m.width, m.height) < size)){
skipping(gid) logger && logger.emit('skipping', gid)
return } return }
// continue... // continue...
return img }) return img })
@ -362,7 +376,7 @@ var SharpActions = actions.Actions({
fse.removeSync(to) fse.removeSync(to)
// skip... // skip...
} else { } else {
skipping(gid) logger && logger.emit('skipping', gid)
return } } return } }
// write... // write...
@ -396,11 +410,11 @@ var SharpActions = actions.Actions({
.then(function(){ .then(function(){
logger logger
&& logger.emit('done', to) && 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 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', makePreviews: ['Sharp|File/Make image $previews',
core.doc`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 NOTE: if base_path is given .images will not be updated with new
preview paths... preview paths...
`, `,
function(images, sizes, base_path, logger){ core.abortablePromise('makePreviews', function(abort, images, sizes, base_path, logger){
var that = this 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' var logger_mode = this.config['preview-progress-mode'] || 'gids'
logger = logger !== false ? logger = logger !== false ?
(logger || this.logger) (logger || this.logger)
: false : false
var gid_logger = logger && logger.push('Images') var gid_logger = logger && logger.push('Images', {onclose: abort})
logger = logger && logger.push('Previews') logger = logger && logger.push('Previews', {onclose: abort})
// get/normalize images... // get/normalize images...
//images = images || this.current //images = images || this.current
@ -473,8 +494,11 @@ var SharpActions = actions.Actions({
var path_tpl = that.config['preview-path-template'] var path_tpl = that.config['preview-path-template']
.replace(/\$INDEX|\$\{INDEX\}/g, that.config['index-dir'] || '.ImageGrid') .replace(/\$INDEX|\$\{INDEX\}/g, that.config['index-dir'] || '.ImageGrid')
return Promise.all(images return images
.map(function(gid){ .mapChunks(CHUNK_SIZE, function(gid){
if(abort.isAborted){
throw array.StopIteration('aborted') }
var img = that.images[gid] var img = that.images[gid]
var base = base_path var base = base_path
|| img.base_path || img.base_path
@ -484,6 +508,9 @@ var SharpActions = actions.Actions({
return sizes return sizes
.map(function(size, i){ .map(function(size, i){
if(abort.isAborted){
throw array.StopIteration('aborted') }
var name = path = path_tpl var name = path = path_tpl
.replace(/\$RESOLUTION|\$\{RESOLUTION\}/g, parseInt(size)) .replace(/\$RESOLUTION|\$\{RESOLUTION\}/g, parseInt(size))
.replace(/\$GID|\$\{GID\}/g, gid) .replace(/\$GID|\$\{GID\}/g, gid)
@ -512,14 +539,12 @@ var SharpActions = actions.Actions({
&& that.markChanged('images', [gid]) } && that.markChanged('images', [gid]) }
return [gid, size, name] }) }) }) return [gid, size, name] }) }) })
.flat()) }], .then(function(res){
return res.flat() }) })],
// XXX add support for offloading the processing to a thread/worker... // XXX add support for offloading the processing to a thread/worker...
// XXX would be nice to be able to abort this... // XXX should we use task.Queue()???
// ...and/or have a generic abort protocol triggered when loading... __cache_metadata_reading: null,
// ...use task queue???
// XXX make each section optional...
cacheMetadata: ['- Sharp|Image/', cacheMetadata: ['- Sharp|Image/',
core.doc`Cache metadata core.doc`Cache metadata
@ -550,21 +575,41 @@ var SharpActions = actions.Actions({
-> promise([ gid | null, .. ]) -> 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: 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 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... // 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 // avoid processing an image more than once at the same
// time... // time...
var done = function(gid, msg){ var done = function(gid, msg){
logger && logger.emit(msg || 'done', gid) logger && logger.emit(msg || 'done', gid)
if(that.__metadata_reading){ if(that.__cache_metadata_reading){
that.__metadata_reading.delete(gid) that.__cache_metadata_reading.delete(gid) }
if(that.__metadata_reading.size == 0){
delete that.__metadata_reading } }
return gid } return gid }
var skipping = function(gid){ var skipping = function(gid){
return done(gid, 'skipping') } return done(gid, 'skipping') }
@ -601,13 +646,14 @@ var SharpActions = actions.Actions({
images images
: [images]) : [images])
.filter(function(gid){ .filter(function(gid){
return !that.__metadata_reading return !that.__cache_metadata_reading
|| !that.__metadata_reading.has(gid) }) || !that.__cache_metadata_reading.has(gid) })
logger = logger !== false ? logger = logger !== false ?
(logger || this.logger) (logger || this.logger)
: false : false
logger = logger && logger.push('Caching image metadata') logger = logger
&& logger.push('Caching image metadata', {onclose: abort})
logger && logger.emit('queued', images) logger && logger.emit('queued', images)
/*/ XXX set this to tmp for .location.load =='loadImages' /*/ XXX set this to tmp for .location.load =='loadImages'
@ -624,11 +670,15 @@ var SharpActions = actions.Actions({
//*/ //*/
return images return images
.mapChunks(function(gid){ .mapChunks(CHUNK_SIZE, function(gid){
// abort...
if(abort.isAborted){
throw array.StopIteration('aborted') }
var img = cached_images[gid] var img = cached_images[gid]
var path = img && that.getImagePath(gid) var path = img && that.getImagePath(gid)
;(that.__metadata_reading = ;(that.__cache_metadata_reading =
that.__metadata_reading || new Set()) that.__cache_metadata_reading || new Set())
.add(gid) .add(gid)
// skip... // skip...
@ -702,11 +752,19 @@ var SharpActions = actions.Actions({
that.ribbons that.ribbons
&& that.ribbons.updateImage(gid) && that.ribbons.updateImage(gid)
return done(gid) }) }) }], return done(gid) }) }) })],
cacheAllMetadata: ['- Sharp|Image/', cacheAllMetadata: ['- Sharp|Image/',
core.doc`Cache all metadata core.doc`Cache all metadata
NOTE: this is a shorthand to .cacheMetadata('all', ..)`, NOTE: this is a shorthand to .cacheMetadata('all', ..)`,
'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 }, isApplicable: function(){ return !!sharp },
handlers: [ 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(..))... // XXX this is best done in a thread + needs to be abortable (on .load(..))...
['loadImages', [['loadImages',
'loadNewImages'],
function(){ function(){
this.cacheMetadata('all') }], this.cacheMetadata('all') }],
//*/ //*/

View File

@ -24,28 +24,6 @@ var ProgressActions = actions.Actions({
'progress-update-min': 200, '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 add message to be shown...
// XXX should we report errors and stoppages??? (error state??) // XXX should we report errors and stoppages??? (error state??)
// XXX multiple containers... // XXX multiple containers...
@ -53,17 +31,50 @@ var ProgressActions = actions.Actions({
// XXX revise styles... // XXX revise styles...
__progress_cache: null, __progress_cache: null,
showProgress: ['- Interface/Show progress bar...', 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 that = this
var viewer = this.dom 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 var msg = text instanceof Array ? text.slice(1).join(': ') : null
text = text instanceof Array ? text[0] : text text = text instanceof Array ? text[0] : text
// make sure we do not update too often... // make sure we do not update too often...
if(value != 'close'){ if(value != 'close'){
var cache = (this.__progress_cache = this.__progress_cache || {}) 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 updateValue = function(name, value){
var v = cache[name] || 0 var v = cache[name] || 0
@ -115,7 +126,11 @@ var ProgressActions = actions.Actions({
.text(text) .text(text)
// close button... // close button...
.append($('<span class="close">&times;</span>') .append($('<span class="close">&times;</span>')
.on('click', function(){ widget.trigger('progressClose') })) .on('click', function(){
var cache = (that.__progress_cache || {})[text]
cache.onclose
&& cache.onclose()
widget.trigger('progressClose') }))
// state... // state...
.append($('<span/>') .append($('<span/>')
.addClass('progress-details')) .addClass('progress-details'))
@ -125,10 +140,12 @@ var ProgressActions = actions.Actions({
.on('progressClose', function(){ .on('progressClose', function(){
widget widget
.fadeOut(that.config['progress-fade-duration'] || 200, function(){ .fadeOut(that.config['progress-fade-duration'] || 200, function(){
var cache = (that.__progress_cache || {}) var cache = (that.__progress_cache || {})[text]
cache[text].timeout cache.timeout
&& clearTimeout(cache[text].timeout) && clearTimeout(cache.timeout)
delete cache[text] cache.ondone
&& cache.ondone()
delete (that.__progress_cache || {})[text]
$(this).remove() }) }) $(this).remove() }) })
.appendTo(container) .appendTo(container)
: widget : widget
@ -169,7 +186,7 @@ var ProgressActions = actions.Actions({
// handle logger progress... // handle logger progress...
// XXX revise... // XXX revise...
handleLogItem: ['- System/', handleLogItem: ['- System/',
function(path, status, ...rest){ function(logger, path, status, ...rest){
var msg = path.join(': ') var msg = path.join(': ')
var l = (rest.length == 1 && rest[0] instanceof Array) ? var l = (rest.length == 1 && rest[0] instanceof Array) ?
rest[0].length rest[0].length
@ -196,24 +213,24 @@ var ProgressActions = actions.Actions({
// close... // close...
// XXX is the right keyword... // XXX is the right keyword...
if(status == 'done' && rest.length == 0){ if(status == 'done' && rest.length == 0){
this.showProgress(path, 'close') this.showProgress(path, 'close', logger)
// report progress... // report progress...
// XXX HACK -- need meaningful status... // XXX HACK -- need meaningful status...
} else if(add.includes(status)){ } else if(add.includes(status)){
this.showProgress(path, '+0', '+'+l) this.showProgress(path, '+0', '+'+l, logger)
} else if(done.includes(status)){ } else if(done.includes(status)){
this.showProgress(path, '+'+l) this.showProgress(path, '+'+l, logger)
} else if(skipped.includes(status)){ } else if(skipped.includes(status)){
// XXX if everything is skipped the indicator does not // XXX if everything is skipped the indicator does not
// get hidden... // get hidden...
this.showProgress(path, '+'+l) this.showProgress(path, '+'+l, logger)
// XXX STUB... // XXX STUB...
} else if(status == 'error' ){ } else if(status == 'error' ){
this.showProgress(['Error'].concat(msg), '+0', '+'+l) this.showProgress(['Error'].concat(msg), '+0', '+'+l, logger)
} }
}], }],
}) })

View File

@ -2189,13 +2189,13 @@ var BrowseActionsActions = actions.Actions({
// it to sort an prioritize stuff... // it to sort an prioritize stuff...
'action-category-order': [ 'action-category-order': [
'99:$File', '99:$File',
// ...
// We can order any sub-tree we want in the same manner // We can order any sub-tree we want in the same manner
// as the root... // as the root...
'File/-80:Clear viewer', 'File/-80:Clear viewer',
'File/-90:Close viewer', 'File/-90:Close viewer',
// Non existing elements will not get drawn... // Non existing elements will not get drawn...
//'File/-99:moo', //'File/-99:moo',
// XXX this seems over-crowded -- revise!!!
'99:$Edit', '99:$Edit',
'Edit/90:Undo', 'Edit/90:Undo',
'Edit/90:Redo', 'Edit/90:Redo',
@ -2210,10 +2210,12 @@ var BrowseActionsActions = actions.Actions({
'Edit/70:.*shift.*', 'Edit/70:.*shift.*',
'Edit/60:.*rotate.*', 'Edit/60:.*rotate.*',
'Edit/50:.*flip.*', 'Edit/50:.*flip.*',
// ...
'$Navigate', '$Navigate',
'Navigate/90:.*image.*', 'Navigate/90:.*image.*',
'Navigate/80:.*screen.*', 'Navigate/80:.*screen.*',
'Navigate/70:.*ribbon.*', 'Navigate/70:.*ribbon.*',
// ...
'$Image', '$Image',
'Image/99:$Copy image', 'Image/99:$Copy image',
'Image/99:.*copy.*', 'Image/99:.*copy.*',
@ -2229,6 +2231,7 @@ var BrowseActionsActions = actions.Actions({
'Image/65:.*shift.*', 'Image/65:.*shift.*',
'Image/60:.*rotate.*', 'Image/60:.*rotate.*',
'Image/55:.*flip.*', 'Image/55:.*flip.*',
// ...
'Image/-70:---', 'Image/-70:---',
'Image/-70:.*remove.*', 'Image/-70:.*remove.*',
'$Virtual block', '$Virtual block',
@ -2238,14 +2241,25 @@ var BrowseActionsActions = actions.Actions({
'Virtual block/60:.*mark.*', 'Virtual block/60:.*mark.*',
'Virtual block/50:---', 'Virtual block/50:---',
'Virtual block/40:.*crop.*', 'Virtual block/40:.*crop.*',
// ...
'$Ribbon', '$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:---',
'Ribbon/-70:.*remove.*', 'Ribbon/-70:.*remove.*',
'$Crop', '$Crop',
'Crop/80:Crop $marked images', 'Crop/80:Crop $marked images',
'Crop/80:Crop $bookmarked images', 'Crop/80:Crop $bookmarked images',
'Crop/70:$Crop', 'Crop/70:$Crop',
'Crop/70:$Flatten', 'Crop/70:Crop $flatten',
// Path patterns... // Path patterns...
// //
@ -2279,32 +2293,35 @@ var BrowseActionsActions = actions.Actions({
'Crop/-70:---', 'Crop/-70:---',
//*/ //*/
// ...
'Crop/-50:---', 'Crop/-50:---',
'Crop/-60:Remove from crop', 'Crop/-60:Remove from crop',
'Crop/-70:Remove ribbon.*', 'Crop/-70:Remove ribbon.*',
'Crop/-71:Remove marked.*', 'Crop/-71:Remove marked.*',
'Crop/-72:.*remove.*', 'Crop/-72:.*remove.*',
'Crop/-75:---', 'Crop/-75:---',
'Crop/-80:Uncrop keeping image order', 'Crop/-80:Uncrop keeping image order',
'Crop/-81:Uncrop all', 'Crop/-81:Uncrop all',
'Crop/-82:$Uncrop', 'Crop/-82:$Uncrop',
'Co$llections', 'Co$llections',
// ...
'Collections/-50:.*exit.*', 'Collections/-50:.*exit.*',
'Collections/-60:.*edit.*', 'Collections/-60:.*edit.*',
'Collections/-70:---', 'Collections/-70:---',
'Collections/-70:.*remove.*', 'Collections/-70:.*remove.*',
'$Tag', '$Tag',
// ...
// XXX revise... // XXX revise...
'Tag/-80:---', 'Tag/-80:---',
'Tag/-90:.*remove.*', 'Tag/-90:.*remove.*',
'$Mark', '$Mark',
// ...
'Mark/-75:.*collection...', 'Mark/-75:.*collection...',
'Mark/-80:---', 'Mark/-80:---',
'Mark/-80:.*remove.*', 'Mark/-80:.*remove.*',
'Mark/-90:.*unmark.*', 'Mark/-90:.*unmark.*',
'$Bookmark', '$Bookmark',
// ...
'Bookmark/-80:---', 'Bookmark/-80:---',
'Bookmark/-80:.*remove.*', 'Bookmark/-80:.*remove.*',
@ -2312,6 +2329,7 @@ var BrowseActionsActions = actions.Actions({
'-40:Interface', '-40:Interface',
'Interface/90:Theme', 'Interface/90:Theme',
// ...
'-50:$Workspace', '-50:$Workspace',
'-60:System', '-60:System',
'-70:$Help', '-70:$Help',

View File

@ -17,6 +17,13 @@ var object = require('lib/object')
var QueuePrototype = Object.create(actions.MetaActions) 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 // 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 // on the previous results or a way to delay a task until what it
// needs is finished... // needs is finished...
@ -49,9 +56,7 @@ module.QueueActions = actions.Actions(QueuePrototype, {
: 0) : 0)
+ (typeof(b) == typeof(1) ? b + (typeof(b) == typeof(1) ? b
: that[b] ? that[b].len : that[b] ? that[b].len
: 0) : 0) }, 0) },
}, 0)
},
set length(val){}, set length(val){},
// can be: // can be:
@ -71,8 +76,7 @@ module.QueueActions = actions.Actions(QueuePrototype, {
// //
// XXX should be more informative -- now supports only 'running' and 'stopped' // XXX should be more informative -- now supports only 'running' and 'stopped'
get state(){ get state(){
return this._state || 'stopped' return this._state || 'stopped' },
},
// General task life cycle events... // General task life cycle events...
@ -111,8 +115,8 @@ module.QueueActions = actions.Actions(QueuePrototype, {
} else { } else {
var tag = null var tag = null
var task = a var task = a
var mode = b var mode = b }
}
mode = mode || this.config['default-queue-mode'] mode = mode || this.config['default-queue-mode']
var ready = this.__ready = this.__ready || [] var ready = this.__ready = this.__ready || []
@ -120,22 +124,19 @@ module.QueueActions = actions.Actions(QueuePrototype, {
this.taskQueued(tag, task, mode) this.taskQueued(tag, task, mode)
// restart in case the queue was depleted... // restart in case the queue was depleted...
this._run() this._run() }],
}],
unqueue: ['', unqueue: ['',
function(a, b){ function(a, b){
var that = this var that = this
var ready = this.__ready var ready = this.__ready
// empty queue... // empty queue...
if(ready == null || ready.len == 0){ if(ready == null || ready.len == 0){
return return }
}
// special case -- drop all... // special case -- drop all...
if(a == '*'){ if(a == '*'){
ready.splice(0, ready.length) ready.splice(0, ready.length)
return return }
}
// XXX prep args... // XXX prep args...
var tag = typeof(a) == typeof('str') ? a : b var tag = typeof(a) == typeof('str') ? a : b
@ -143,8 +144,7 @@ module.QueueActions = actions.Actions(QueuePrototype, {
// no args... // no args...
if(tag == null && task == null){ if(tag == null && task == null){
return return }
}
// remove matching tasks from the queue... // remove matching tasks from the queue...
ready.forEach(function(e, i){ ready.forEach(function(e, i){
@ -155,17 +155,13 @@ module.QueueActions = actions.Actions(QueuePrototype, {
// both task and tag given... // both task and tag given...
: e[0] == tag && e[1] === task){ : e[0] == tag && e[1] === task){
delete ready[i] delete ready[i]
that.taskDropped(e[0], e[1], e[2]) that.taskDropped(e[0], e[1], e[2]) } }) }],
}
})
}],
delay: ['', delay: ['',
function(a, b){ function(a, b){
var ready = this.__ready var ready = this.__ready
// empty queue... // empty queue...
if(ready == null || ready.len == 0){ if(ready == null || ready.len == 0){
return return }
}
// XXX prep args... // XXX prep args...
var tag = typeof(a) == typeof('str') ? a : b var tag = typeof(a) == typeof('str') ? a : b
@ -173,8 +169,7 @@ module.QueueActions = actions.Actions(QueuePrototype, {
// no args... // no args...
if(tag == null && task == null){ if(tag == null && task == null){
return return }
}
var delayed = [] var delayed = []
// remove the matching tasks... // remove the matching tasks...
@ -188,19 +183,14 @@ module.QueueActions = actions.Actions(QueuePrototype, {
if(res){ if(res){
delete ready[i] delete ready[i]
delayed.push(e) } })
delayed.push(e)
}
})
// push delayed list to the end of the queue... // push delayed list to the end of the queue...
delayed.forEach(function(e){ delayed.forEach(function(e){
ready.push(e) ready.push(e) })
})
// restart in case the queue was depleted... // restart in case the queue was depleted...
this._run() this._run() }],
}],
// Run the queue... // Run the queue...
// //
@ -230,8 +220,7 @@ module.QueueActions = actions.Actions(QueuePrototype, {
_run: ['', _run: ['',
function(){ function(){
if(this.__is_running){ if(this.__is_running){
return return }
}
var that = this var that = this
var size = this.config['running-pool-size'] var size = this.config['running-pool-size']
@ -249,8 +238,7 @@ module.QueueActions = actions.Actions(QueuePrototype, {
// XXX this might race... // XXX this might race...
var elem = that.__ready.shift() var elem = that.__ready.shift()
if(elem == null){ if(elem == null){
return return }
}
var task = elem[1] var task = elem[1]
that.__is_running = true that.__is_running = true
@ -292,9 +280,7 @@ module.QueueActions = actions.Actions(QueuePrototype, {
that.done() that.done()
that.config['clear-on-done'] that.config['clear-on-done']
&& that.clear() && that.clear() } })
}
})
// push to done and ._run some more... // push to done and ._run some more...
.then(function(){ .then(function(){
// pop self of .__running // pop self of .__running
@ -316,9 +302,7 @@ module.QueueActions = actions.Actions(QueuePrototype, {
that.done() that.done()
that.config['clear-on-done'] that.config['clear-on-done']
&& that.clear() && that.clear() } })
}
})
// other... // other...
} else { } else {
@ -338,13 +322,10 @@ module.QueueActions = actions.Actions(QueuePrototype, {
that.done() that.done()
that.config['clear-on-done'] that.config['clear-on-done']
&& that.clear() && that.clear() } }
}
}
})() } })() }
delete that.__is_running delete that.__is_running }],
}],
// State manipulation actions... // State manipulation actions...
// //
@ -352,12 +333,10 @@ module.QueueActions = actions.Actions(QueuePrototype, {
start: ['', start: ['',
function(){ function(){
this._state = 'ready' this._state = 'ready'
this._run() this._run() }],
}],
stop: ['', stop: ['',
function(){ function(){
delete this._state delete this._state }],
}],
clear: ['', clear: ['',
function(){ function(){
// XXX should this stop??? // XXX should this stop???
@ -365,8 +344,7 @@ module.QueueActions = actions.Actions(QueuePrototype, {
delete this.__ready delete this.__ready
delete this.__running delete this.__running
delete this.__failed 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 }) * vim:set ts=4 sw=4 : */ return module })

View File

@ -1117,9 +1117,9 @@
"integrity": "sha512-EzT4CP6d6lI8bnknNgT3W8mUQhSVXflO0yPbKD4dKsFcINiC6npjoEBz+8m3VQmWJhc+36pXD4JLwNxUEgzi+Q==" "integrity": "sha512-EzT4CP6d6lI8bnknNgT3W8mUQhSVXflO0yPbKD4dKsFcINiC6npjoEBz+8m3VQmWJhc+36pXD4JLwNxUEgzi+Q=="
}, },
"ig-types": { "ig-types": {
"version": "3.0.2", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/ig-types/-/ig-types-3.0.2.tgz", "resolved": "https://registry.npmjs.org/ig-types/-/ig-types-3.1.0.tgz",
"integrity": "sha512-djnS6UnS+a0y37DNFUk8PZf0lt7TQO5u/RQPYpFVW4sqwA2HpSdAmmrWyu6UCVdhw+b9Q6E+RjAXrWVEkn1w3A==", "integrity": "sha512-k/QbS9D30Fun3Xrh+6LHpCYsbQOwYlj1PX0uaNOnpmxg2tlWSdLOdykH8IMUbGIm/tI8MsJeKnJc4vu51s89Tg==",
"requires": { "requires": {
"ig-object": "^5.2.8", "ig-object": "^5.2.8",
"object-run": "^1.0.1" "object-run": "^1.0.1"

View File

@ -32,7 +32,7 @@
"ig-argv": "^2.15.0", "ig-argv": "^2.15.0",
"ig-features": "^3.4.2", "ig-features": "^3.4.2",
"ig-object": "^5.2.8", "ig-object": "^5.2.8",
"ig-types": "^3.0.2", "ig-types": "^3.1.0",
"moment": "^2.29.1", "moment": "^2.29.1",
"object-run": "^1.0.1", "object-run": "^1.0.1",
"requirejs": "^2.3.6", "requirejs": "^2.3.6",

View File

@ -77,8 +77,7 @@ $(function(){
} catch(err){ } catch(err){
console.error(err) console.error(err)
//throw err //throw err
return return }
}
// used to switch experimental actions on (set to true) or off (unset or false)... // used to switch experimental actions on (set to true) or off (unset or false)...
@ -105,8 +104,7 @@ $(function(){
err.missing_suggested) err.missing_suggested)
err.missing.length > 0 err.missing.length > 0
&& console.warn('Missing dependencies:', && console.warn('Missing dependencies:',
err.missing) err.missing) }
}
// setup the viewer... // setup the viewer...