mirror of
				https://github.com/flynx/ImageGrid.git
				synced 2025-10-31 11:20:09 +00:00 
			
		
		
		
	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:
		
							parent
							
								
									da63da2f2c
								
							
						
					
					
						commit
						baa1b06dce
					
				| @ -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) }], | ||||||
|  | |||||||
| @ -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,8 +391,9 @@ 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 | ||||||
|  | 				|| console.log( | ||||||
| 					path.join(': ') + (path.length > 0 ? ': ' : '') | 					path.join(': ') + (path.length > 0 ? ': ' : '') | ||||||
| 						+ status  | 						+ status  | ||||||
| 						+ (rest.length > 1 ?  | 						+ (rest.length > 1 ?  | ||||||
| @ -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 =  | ||||||
|  | |||||||
| @ -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 =  | ||||||
|  | |||||||
| @ -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') }], | ||||||
| 		//*/
 | 		//*/
 | ||||||
|  | |||||||
| @ -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">×</span>') | 					.append($('<span class="close">×</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) | ||||||
| 			} | 			} | ||||||
| 		}], | 		}], | ||||||
| }) | }) | ||||||
|  | |||||||
| @ -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', | ||||||
|  | |||||||
| @ -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 }) | ||||||
|  | |||||||
							
								
								
									
										6
									
								
								Viewer/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								Viewer/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -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" | ||||||
|  | |||||||
| @ -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", | ||||||
|  | |||||||
| @ -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...
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user