| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | /********************************************************************** | 
					
						
							|  |  |  | *  | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | * Core features... | 
					
						
							|  |  |  | * 	Setup the life-cycle and the base interfaces for features to use... | 
					
						
							|  |  |  | * | 
					
						
							|  |  |  | * Defined here: | 
					
						
							|  |  |  | * 	- protocol action constructor | 
					
						
							|  |  |  | * 	- config value toggler constructor | 
					
						
							|  |  |  | * 	- meta actions | 
					
						
							|  |  |  | * 		- .isToggler(..) predicate | 
					
						
							|  |  |  | * 		- .preActionHandler(..) used to toggler special args | 
					
						
							|  |  |  | * 	- ImageGrid root object/constructor | 
					
						
							|  |  |  | * 	- ImageGridFeatures object | 
					
						
							|  |  |  | * | 
					
						
							|  |  |  | * | 
					
						
							|  |  |  | * Features: | 
					
						
							| 
									
										
										
										
											2020-12-05 03:40:55 +03:00
										 |  |  | * 	- util | 
					
						
							| 
									
										
										
										
											2017-01-26 21:49:32 +03:00
										 |  |  | * 	- introspection | 
					
						
							| 
									
										
										
										
											2020-12-05 03:40:55 +03:00
										 |  |  | * 	- logger | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | * 	- lifecycle | 
					
						
							|  |  |  | * 		base life-cycle events (start/stop/..) | 
					
						
							| 
									
										
										
										
											2020-11-05 22:46:27 +03:00
										 |  |  | * 		base abort api | 
					
						
							| 
									
										
										
										
											2017-08-27 22:17:47 +03:00
										 |  |  | *	- serialization | 
					
						
							|  |  |  | *		base methods to handle loading, serialization and cloning... | 
					
						
							| 
									
										
										
										
											2018-01-19 01:21:56 +03:00
										 |  |  | *	- cache | 
					
						
							| 
									
										
										
										
											2018-02-12 01:35:35 +03:00
										 |  |  | *		basic action/prop caching api... | 
					
						
							|  |  |  | *	- timers | 
					
						
							|  |  |  | *		wrapper around setInterval(..), setTimeout(..) and friends,  | 
					
						
							|  |  |  | *		provides persistent timer triggers and introspection... | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | * 	- journal | 
					
						
							|  |  |  | * 		action journaling and undo/redo functionality | 
					
						
							|  |  |  | * 		XXX needs revision... | 
					
						
							| 
									
										
										
										
											2017-02-13 05:47:14 +03:00
										 |  |  | * 	- changes | 
					
						
							|  |  |  | * 		change tracking | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | * 	- workspace | 
					
						
							|  |  |  | * 		XXX needs revision... | 
					
						
							|  |  |  | * 	- tasks | 
					
						
							| 
									
										
										
										
											2020-12-02 04:47:53 +03:00
										 |  |  | * 		tasks -- manage long running actions | 
					
						
							|  |  |  | * 		queue -- manage lots of small actions as a single task | 
					
						
							| 
									
										
										
										
											2017-01-12 02:41:03 +03:00
										 |  |  | * 	- self-test | 
					
						
							|  |  |  | * 		basic framework for running test actions at startup... | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | * | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | * | 
					
						
							| 
									
										
										
										
											2017-02-13 05:47:14 +03:00
										 |  |  | * XXX some actions use the .clone(..) action/protocol, should this be  | 
					
						
							|  |  |  | * 	defined here??? | 
					
						
							| 
									
										
										
										
											2020-12-05 03:40:55 +03:00
										 |  |  | * XXX should this be split into a generic app building lib? | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | * | 
					
						
							|  |  |  | **********************************************************************/ | 
					
						
							| 
									
										
										
										
											2016-08-21 02:19:24 +03:00
										 |  |  | ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | 
					
						
							|  |  |  | (function(require){ var module={} // make module AMD/node compatible...
 | 
					
						
							| 
									
										
										
										
											2016-08-20 22:49:36 +03:00
										 |  |  | /*********************************************************************/ | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-10 17:02:38 +03:00
										 |  |  | // XXX
 | 
					
						
							|  |  |  | var DEBUG = typeof(DEBUG) != 'undefined' ? DEBUG : true | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-26 00:44:46 +03:00
										 |  |  | var types = require('lib/types') | 
					
						
							|  |  |  | var runner = require('lib/types/runner') | 
					
						
							| 
									
										
										
										
											2016-05-28 02:47:52 +03:00
										 |  |  | var util = require('lib/util') | 
					
						
							| 
									
										
										
										
											2016-05-27 18:41:28 +03:00
										 |  |  | var object = require('lib/object') | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | var actions = require('lib/actions') | 
					
						
							|  |  |  | var features = require('lib/features') | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | var toggler = require('lib/toggler') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-05 03:42:09 +03:00
										 |  |  | // code/text normalization...
 | 
					
						
							|  |  |  | var doc = module.doc = actions.doc | 
					
						
							|  |  |  | var text = module.text = object.text | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-26 00:44:46 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | /*********************************************************************/ | 
					
						
							| 
									
										
										
										
											2016-05-27 18:41:28 +03:00
										 |  |  | // Root ImageGrid.viewer object constructor...
 | 
					
						
							| 
									
										
										
										
											2016-04-27 08:59:13 +03:00
										 |  |  | //
 | 
					
						
							| 
									
										
										
										
											2016-11-07 18:21:32 +03:00
										 |  |  | // This adds:
 | 
					
						
							|  |  |  | // 	- toggler as action compatibility
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2016-08-21 01:21:03 +03:00
										 |  |  | var ImageGridMetaActions = | 
					
						
							|  |  |  | module.ImageGridMetaActions = { | 
					
						
							| 
									
										
										
										
											2020-12-23 20:02:37 +03:00
										 |  |  | 	__proto__: actions.MetaActions, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-21 01:21:03 +03:00
										 |  |  | 	// Test if the action is a Toggler...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2018-03-05 00:39:13 +03:00
										 |  |  | 	isToggler:  | 
					
						
							|  |  |  | 		actions.doWithRootAction(function(action){ | 
					
						
							|  |  |  | 			return action instanceof toggler.Toggler }), | 
					
						
							| 
									
										
										
										
											2016-08-21 01:21:03 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Handle special cases where we need to get the action result early,
 | 
					
						
							|  |  |  | 	// without calling handlers...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2016-11-07 18:21:32 +03:00
										 |  |  | 	// These include:
 | 
					
						
							|  |  |  | 	// 	- toggler action special command handling (like: '?', '??', ..)
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2016-08-21 01:21:03 +03:00
										 |  |  | 	preActionHandler: actions.doWithRootAction(function(action, name, handlers, args){ | 
					
						
							|  |  |  | 		// Special case: do not call handlers for toggler state queries...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// NOTE: if the root handler is instance of Toggler (jli) and 
 | 
					
						
							|  |  |  | 		// 		the action is called with '?'/'??' as argument, then the
 | 
					
						
							|  |  |  | 		// 		toggler will be called with the argument and return the
 | 
					
						
							|  |  |  | 		// 		result bypassing the handlers.
 | 
					
						
							|  |  |  | 		// NOTE: an action is considered a toggler only if it's base action
 | 
					
						
							|  |  |  | 		// 		is a toggler (instance of Toggler), thus, the same "top"
 | 
					
						
							|  |  |  | 		// 		action can be or not be a toggler in different contexts.
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// For more info on togglers see: lib/toggler.js
 | 
					
						
							|  |  |  | 		if(this.isToggler(name) | 
					
						
							|  |  |  | 				&& args.length == 1  | 
					
						
							|  |  |  | 				&& (args[0] == '?' || args[0] == '??')){ | 
					
						
							|  |  |  | 			return { | 
					
						
							|  |  |  | 				result: handlers.slice(-1)[0].pre.apply(this, args), | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}), | 
					
						
							|  |  |  | }  | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-27 18:56:08 +03:00
										 |  |  | var ImageGrid =  | 
					
						
							| 
									
										
										
										
											2016-05-27 23:04:15 +03:00
										 |  |  | module.ImageGrid =  | 
					
						
							| 
									
										
										
										
											2019-07-17 00:01:09 +03:00
										 |  |  | 	object.Constructor('ImageGrid', ImageGridMetaActions) | 
					
						
							| 
									
										
										
										
											2016-05-27 18:41:28 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-23 20:02:37 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-27 18:41:28 +03:00
										 |  |  | // Root ImageGrid feature set....
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | var ImageGridFeatures = | 
					
						
							| 
									
										
										
										
											2020-12-23 20:02:37 +03:00
										 |  |  | module.ImageGridFeatures =  | 
					
						
							|  |  |  | 	new features.FeatureSet() | 
					
						
							| 
									
										
										
										
											2016-05-27 18:41:28 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | // setup base instance constructor...
 | 
					
						
							| 
									
										
										
										
											2016-05-27 18:56:08 +03:00
										 |  |  | ImageGridFeatures.__actions__ =  | 
					
						
							| 
									
										
										
										
											2020-12-23 20:02:37 +03:00
										 |  |  | 	function(){  | 
					
						
							|  |  |  | 		return actions.Actions(ImageGrid()) } | 
					
						
							| 
									
										
										
										
											2016-05-27 18:41:28 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-27 08:59:13 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | // Setup runtime info...
 | 
					
						
							| 
									
										
										
										
											2020-12-05 03:40:55 +03:00
										 |  |  | //
 | 
					
						
							|  |  |  | // XXX add PWA / chrome-app...
 | 
					
						
							| 
									
										
										
										
											2018-01-03 05:06:00 +03:00
										 |  |  | // XXX add cordova...
 | 
					
						
							|  |  |  | // XXX add mobile...
 | 
					
						
							|  |  |  | // XXX add widget...
 | 
					
						
							| 
									
										
										
										
											2017-10-04 08:08:40 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | // XXX should this contain feature versions???
 | 
					
						
							|  |  |  | var runtime = ImageGridFeatures.runtime = {} | 
					
						
							| 
									
										
										
										
											2016-04-27 08:59:13 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-09 18:39:01 +03:00
										 |  |  | // nw or node...
 | 
					
						
							| 
									
										
										
										
											2015-12-29 07:28:19 +03:00
										 |  |  | if(typeof(process) != 'undefined'){ | 
					
						
							| 
									
										
										
										
											2017-10-04 08:08:40 +03:00
										 |  |  | 	// node...
 | 
					
						
							|  |  |  | 	runtime.node = true | 
					
						
							| 
									
										
										
										
											2015-12-29 07:28:19 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-16 23:18:19 +03:00
										 |  |  | 	// Electron...
 | 
					
						
							| 
									
										
										
										
											2020-12-12 09:29:57 +03:00
										 |  |  | 	if(process.versions['electron'] != null  | 
					
						
							|  |  |  | 			// node mode...
 | 
					
						
							|  |  |  | 			&& typeof(document) != 'undefined'){ | 
					
						
							| 
									
										
										
										
											2017-10-04 08:08:40 +03:00
										 |  |  | 		runtime.electron = true | 
					
						
							|  |  |  | 		runtime.desktop = true | 
					
						
							| 
									
										
										
										
											2017-07-16 23:18:19 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-09 18:39:01 +03:00
										 |  |  | 	// nw.js 0.13+
 | 
					
						
							| 
									
										
										
										
											2017-07-16 23:18:19 +03:00
										 |  |  | 	} else if(typeof(nw) != 'undefined'){ | 
					
						
							| 
									
										
										
										
											2017-10-04 08:08:40 +03:00
										 |  |  | 		runtime.nw = true | 
					
						
							|  |  |  | 		runtime.desktop = true | 
					
						
							| 
									
										
										
										
											2016-04-09 18:39:01 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-10 14:04:44 +03:00
										 |  |  | 		// NOTE: jli is patching the Date object and with two separate 
 | 
					
						
							|  |  |  | 		// 		instances we'll need to sync things up...
 | 
					
						
							| 
									
										
										
										
											2016-04-11 15:58:12 +03:00
										 |  |  | 		// XXX HACK: if node and chrome Date implementations ever 
 | 
					
						
							|  |  |  | 		// 		significantly diverge this will break things + this is 
 | 
					
						
							|  |  |  | 		// 		a potential data leak between contexts...
 | 
					
						
							|  |  |  | 		//global.Date = window.Date
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// XXX this is less of a hack but it is still an implicit
 | 
					
						
							| 
									
										
										
										
											2016-05-28 02:47:52 +03:00
										 |  |  | 		util.patchDate(global.Date) | 
					
						
							|  |  |  | 		util.patchDate(window.Date) | 
					
						
							| 
									
										
										
										
											2016-04-10 14:04:44 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-09 18:39:01 +03:00
										 |  |  | 	// node...
 | 
					
						
							|  |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2016-04-11 15:58:12 +03:00
										 |  |  | 		// XXX patch Date...
 | 
					
						
							|  |  |  | 		// XXX this will not work directly as we will need to explicitly
 | 
					
						
							|  |  |  | 		// 		require jli...
 | 
					
						
							|  |  |  | 		//patchDate(global.Date)
 | 
					
						
							| 
									
										
										
										
											2020-12-19 14:59:34 +03:00
										 |  |  | 	} } | 
					
						
							| 
									
										
										
										
											2015-12-29 07:28:19 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | // browser...
 | 
					
						
							| 
									
										
										
										
											2016-04-09 18:39:01 +03:00
										 |  |  | // NOTE: we're avoiding detecting browser specifics for as long as possible,
 | 
					
						
							|  |  |  | // 		this will minimize the headaches of supporting several non-standard
 | 
					
						
							|  |  |  | // 		versions of code...
 | 
					
						
							| 
									
										
										
										
											2017-10-04 08:08:40 +03:00
										 |  |  | if(typeof(window) != 'undefined'){ | 
					
						
							| 
									
										
										
										
											2020-12-19 14:59:34 +03:00
										 |  |  | 	runtime.browser = true } | 
					
						
							| 
									
										
										
										
											2015-12-29 07:28:19 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | /*********************************************************************/ | 
					
						
							| 
									
										
										
										
											2020-12-05 03:40:55 +03:00
										 |  |  | // Util...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Toggle value in .config...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // NOTE: if no toggler state is set this assumes that the first state 
 | 
					
						
							|  |  |  | // 		is the default...
 | 
					
						
							|  |  |  | // NOTE: default states is [false, true]
 | 
					
						
							|  |  |  | var makeConfigToggler =  | 
					
						
							|  |  |  | module.makeConfigToggler =  | 
					
						
							|  |  |  | function(attr, states, a, b){ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	states = states || [false, true] | 
					
						
							|  |  |  | 	var pre = a | 
					
						
							|  |  |  | 	// XXX is this a good default???
 | 
					
						
							|  |  |  | 	//var post = b || function(action){ action != null && this.focusImage() }
 | 
					
						
							|  |  |  | 	var post = b | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return toggler.Toggler(null, | 
					
						
							|  |  |  | 		function(_, action){ | 
					
						
							|  |  |  | 			var lst = states instanceof Array ? states  | 
					
						
							|  |  |  | 				: states instanceof Function ? states.call(this) | 
					
						
							|  |  |  | 				: states | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// get attr path...
 | 
					
						
							|  |  |  | 			var a = attr.split(/\./g) | 
					
						
							|  |  |  | 			var cfg = a.slice(0, -1)  | 
					
						
							|  |  |  | 				.reduce(function(res, cur){ | 
					
						
							|  |  |  | 					return res[cur] }, this.config) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if(action == null){ | 
					
						
							|  |  |  | 				var val = cfg[a.pop()] | 
					
						
							|  |  |  | 				return val == null ?  | 
					
						
							|  |  |  | 					(lst[lst.indexOf('none')] || lst[0]) | 
					
						
							|  |  |  | 					: val  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				cfg[a[a.length-1]] = action | 
					
						
							|  |  |  | 				this.config[a[0]] = this.config[a[0]] | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		states, pre, post) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | 
					
						
							|  |  |  | var UtilActions = actions.Actions({ | 
					
						
							|  |  |  | 	mergeConfig: ['- System/',  | 
					
						
							|  |  |  | 		doc`Merge a config object into .config
 | 
					
						
							|  |  |  | 		`,
 | 
					
						
							|  |  |  | 		function(config){ | 
					
						
							|  |  |  | 			config = config instanceof Function ? config.call(this) | 
					
						
							|  |  |  | 				: typeof(config) == typeof('str') ? this.config[config] | 
					
						
							|  |  |  | 				: config | 
					
						
							|  |  |  | 			Object.assign(this.config, config) }], | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Util =  | 
					
						
							|  |  |  | module.Util = ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: '', | 
					
						
							|  |  |  | 	doc: '', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'util', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	actions: UtilActions, | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | // Introspection...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Indicate that an action is not intended for direct use...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // NOTE: this will not do anything but mark the action.
 | 
					
						
							|  |  |  | var notUserCallable = | 
					
						
							|  |  |  | module.notUserCallable =  | 
					
						
							|  |  |  | function(func){ | 
					
						
							|  |  |  | 	func.__not_user_callable__ = true | 
					
						
							|  |  |  | 	return func } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NOTE: this is the same as event but user-callable...
 | 
					
						
							|  |  |  | var UserEvent = | 
					
						
							|  |  |  | module.UserEvent =  | 
					
						
							|  |  |  | function(func){ | 
					
						
							|  |  |  | 	func.__event__ = true | 
					
						
							|  |  |  | 	return func } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // XXX rename???
 | 
					
						
							|  |  |  | var Event = | 
					
						
							|  |  |  | module.Event =  | 
					
						
							|  |  |  | function(func){ | 
					
						
							|  |  |  | 	func.__event__ = true | 
					
						
							|  |  |  | 	return notUserCallable(func) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var IntrospectionActions = actions.Actions({ | 
					
						
							|  |  |  | 	get useractions(){ | 
					
						
							|  |  |  | 		return this.cache('useractions', function(d){ | 
					
						
							|  |  |  | 			return d instanceof Array ?  | 
					
						
							|  |  |  | 				d.slice()  | 
					
						
							|  |  |  | 				: this.actions.filter(this.isUserCallable.bind(this)) }) }, | 
					
						
							|  |  |  | 	get events(){ | 
					
						
							|  |  |  | 		return this.cache('events', function(d){ | 
					
						
							|  |  |  | 			return d instanceof Array ?  | 
					
						
							|  |  |  | 				d.slice()  | 
					
						
							|  |  |  | 				: this.actions.filter(this.isEvent.bind(this)) }) }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	isUserCallable: | 
					
						
							|  |  |  | 		// XXX should this check only the root action or the whole set???
 | 
					
						
							|  |  |  | 		// 		...in other words: can we make an action non-user-callable
 | 
					
						
							|  |  |  | 		// 		anywhere other than the root action?
 | 
					
						
							|  |  |  | 		// 		IMO no...
 | 
					
						
							|  |  |  | 		//function(action){ 
 | 
					
						
							|  |  |  | 		//	return this.getActionAttr(action, '__not_user_callable__') != true }],
 | 
					
						
							|  |  |  | 		actions.doWithRootAction(function(action){ | 
					
						
							|  |  |  | 			return action.__not_user_callable__ != true }), | 
					
						
							|  |  |  | 	isEvent:  | 
					
						
							|  |  |  | 		actions.doWithRootAction(function(action){ | 
					
						
							|  |  |  | 			return !!action.__event__ }), | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX revise... 
 | 
					
						
							|  |  |  | 	getActionMode: ['- Interface/', | 
					
						
							|  |  |  | 		doc`Get action browse mode...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		Get and action's .mode(..) method and return its result. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		Action .mode can be: | 
					
						
							|  |  |  | 			<function>			- action method. | 
					
						
							|  |  |  | 			<action-name>		- alias, name of action to get the | 
					
						
							|  |  |  | 									method from. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		The action .mode(..) method is called in the context of actions. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		Basic example: | 
					
						
							|  |  |  | 			someAction: ['Path/To/Some action', | 
					
						
							|  |  |  | 				{mode: function(){ ... }}, | 
					
						
							|  |  |  | 				function(){ | 
					
						
							|  |  |  | 					... | 
					
						
							|  |  |  | 				}], | 
					
						
							|  |  |  | 			someOtherAction: ['Path/To/Some action', | 
					
						
							|  |  |  | 				// alias
 | 
					
						
							|  |  |  | 				{mode: 'someAction'}, | 
					
						
							|  |  |  | 				function(){ | 
					
						
							|  |  |  | 					... | 
					
						
							|  |  |  | 				}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		Usage pattern: | 
					
						
							|  |  |  | 			// for cases where we need to define an explicit mode...
 | 
					
						
							|  |  |  | 			actionModeX: ['- System/', | 
					
						
							|  |  |  | 				{mode: function(){ | 
					
						
							|  |  |  | 					return this.actionModeX() }}, | 
					
						
							|  |  |  | 				core.notUserCallable(function(){ | 
					
						
							|  |  |  | 					return ... | 
					
						
							|  |  |  | 				})], | 
					
						
							|  |  |  | 			someAction: [ | 
					
						
							|  |  |  | 				// use the mode...
 | 
					
						
							|  |  |  | 				{mode: 'actionModeX'}, | 
					
						
							|  |  |  | 				function(){ | 
					
						
							|  |  |  | 					... | 
					
						
							|  |  |  | 				}], | 
					
						
							|  |  |  | 		`,
 | 
					
						
							|  |  |  | 		function(action, mode_cache){ | 
					
						
							|  |  |  | 			var m = action | 
					
						
							|  |  |  | 			var visited = [m] | 
					
						
							|  |  |  | 			var last | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// check cache...
 | 
					
						
							|  |  |  | 			if(m in (mode_cache || {})){ | 
					
						
							|  |  |  | 				return mode_cache[m] } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// handle aliases...
 | 
					
						
							|  |  |  | 			do { | 
					
						
							|  |  |  | 				last = m | 
					
						
							|  |  |  | 				m = this.getActionAttr(m, 'mode') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// check cache...
 | 
					
						
							|  |  |  | 				if(m in (mode_cache || {})){ | 
					
						
							|  |  |  | 					return mode_cache[m] } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// check for loops...
 | 
					
						
							|  |  |  | 				if(m && visited[m] != null){ | 
					
						
							|  |  |  | 					m = null | 
					
						
							|  |  |  | 					break | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				visited.push(m) | 
					
						
							|  |  |  | 			} while(typeof(m) == typeof('str')) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			//return m ? m.call(this) : undefined
 | 
					
						
							|  |  |  | 			return m ?  | 
					
						
							|  |  |  | 				// no cache...
 | 
					
						
							|  |  |  | 				(mode_cache == null ? | 
					
						
							|  |  |  | 						m.call(this) | 
					
						
							|  |  |  | 					// cache hit...
 | 
					
						
							|  |  |  | 					: last in mode_cache ?  | 
					
						
							|  |  |  | 						mode_cache[last]  | 
					
						
							|  |  |  | 					// call check and populate cache...
 | 
					
						
							|  |  |  | 					: (mode_cache[action] =  | 
					
						
							|  |  |  | 						mode_cache[last] =  | 
					
						
							|  |  |  | 							m.call(this))) | 
					
						
							|  |  |  | 				: actions.UNDEFINED }], | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Introspection =  | 
					
						
							|  |  |  | module.Introspection = ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: '', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'introspection', | 
					
						
							|  |  |  | 	depends: [ | 
					
						
							|  |  |  | 		'cache' | 
					
						
							|  |  |  | 	], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	actions: IntrospectionActions, | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //---------------------------------------------------------------------
 | 
					
						
							| 
									
										
										
										
											2020-04-10 19:10:21 +03:00
										 |  |  | // Logger...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var LoggerActions = actions.Actions({ | 
					
						
							| 
									
										
										
										
											2020-04-10 23:05:16 +03:00
										 |  |  | 	config: { | 
					
						
							|  |  |  | 		// NOTE: if set to 0 no log limit is applied...
 | 
					
						
							|  |  |  | 		'log-size': 10000, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-10 19:10:21 +03:00
										 |  |  | 	Logger: object.Constructor('BaseLogger', { | 
					
						
							| 
									
										
										
										
											2020-04-10 23:05:16 +03:00
										 |  |  | 		doc: `Logger object constructor...`, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-09 03:53:57 +03:00
										 |  |  | 		root: null, | 
					
						
							|  |  |  | 		parent: null, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Quiet mode...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// NOTE: if local mode is not defined this will get the mode of 
 | 
					
						
							|  |  |  | 		// 		the nearest parent...
 | 
					
						
							|  |  |  | 		// XXX need these to be persistent...
 | 
					
						
							|  |  |  | 		// XXX add support for log levels...
 | 
					
						
							|  |  |  | 		__quiet: null, | 
					
						
							|  |  |  | 		get quiet(){ | 
					
						
							|  |  |  | 			var cur = this | 
					
						
							|  |  |  | 			while(cur.__quiet == null && cur.parent){ | 
					
						
							|  |  |  | 				cur = cur.parent } | 
					
						
							|  |  |  | 			return !!cur.__quiet }, | 
					
						
							|  |  |  | 		set quiet(value){ | 
					
						
							|  |  |  | 			value == null ? | 
					
						
							|  |  |  | 				(delete this.__quiet) | 
					
						
							|  |  |  | 				: (this.__quiet = !!value) }, | 
					
						
							| 
									
										
										
										
											2020-11-05 22:46:27 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-10 19:10:21 +03:00
										 |  |  | 		__context: null, | 
					
						
							|  |  |  | 		get context(){ | 
					
						
							|  |  |  | 			return this.__context || this.root.__context }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		get isRoot(){ | 
					
						
							|  |  |  | 			return this === this.root }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-10 23:05:16 +03:00
										 |  |  | 		__path: null, | 
					
						
							|  |  |  | 		get path(){ | 
					
						
							|  |  |  | 			return (this.__path =  | 
					
						
							|  |  |  | 				this.__path == null ?  | 
					
						
							|  |  |  | 					[]  | 
					
						
							|  |  |  | 					: this.__path) }, | 
					
						
							|  |  |  | 		set path(value){ | 
					
						
							|  |  |  | 			this.__path = value }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// NOTE: if set to 0 no log limit is applied...
 | 
					
						
							|  |  |  | 		// NOTE: writing to this will modify .context.config['log-size']
 | 
					
						
							|  |  |  | 		// 		if a available and .__max_size otherwise...
 | 
					
						
							|  |  |  | 		__max_size: null, | 
					
						
							|  |  |  | 		get max_size(){ | 
					
						
							|  |  |  | 			return this.__max_size != null ? | 
					
						
							|  |  |  | 				this.__max_size | 
					
						
							|  |  |  | 				// this.context.config['log-size']
 | 
					
						
							|  |  |  | 				: ((this.context || {}).config || {})['log-size'] || 10000 }, | 
					
						
							|  |  |  | 		set max_size(value){ | 
					
						
							|  |  |  | 			return this.context ? | 
					
						
							|  |  |  | 				(this.context.config['log-size'] = value) | 
					
						
							|  |  |  | 				: this.__max_size = value }, | 
					
						
							| 
									
										
										
										
											2020-04-10 19:10:21 +03:00
										 |  |  | 		// NOTE: to disable log retention in .log set this to false...
 | 
					
						
							|  |  |  | 		__log: null, | 
					
						
							|  |  |  | 		get log(){ | 
					
						
							|  |  |  | 			return this.__log === false ? | 
					
						
							|  |  |  | 					false | 
					
						
							|  |  |  | 				: this.__log ? | 
					
						
							|  |  |  | 					this.__log | 
					
						
							|  |  |  | 				: this.isRoot ? | 
					
						
							|  |  |  | 					(this.__log = this.__log || [])  | 
					
						
							|  |  |  | 				: this.root.log }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// log management...
 | 
					
						
							|  |  |  | 		clear: function(){ | 
					
						
							|  |  |  | 			this.log  | 
					
						
							|  |  |  | 				&& this.log.splice(0, this.log.length)  | 
					
						
							|  |  |  | 			return this }, | 
					
						
							| 
									
										
										
										
											2020-04-11 01:43:34 +03:00
										 |  |  | 		// Format log to string...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// 	Full log...
 | 
					
						
							|  |  |  | 		// 	.log2str()
 | 
					
						
							|  |  |  | 		// 		-> str
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// 	Slice log...
 | 
					
						
							|  |  |  | 		// 	.log2str(from)
 | 
					
						
							|  |  |  | 		// 	.log2str(from, to)
 | 
					
						
							|  |  |  | 		// 		-> str
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// 	Specific item...
 | 
					
						
							|  |  |  | 		// 	.log2str(date, path, status, rest)
 | 
					
						
							|  |  |  | 		// 	.log2str([date, path, status, rest])
 | 
					
						
							|  |  |  | 		// 		-> str
 | 
					
						
							|  |  |  | 		// 		NOTE: this form does not depend on context...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// NOTE: the later form is useful for filtering:
 | 
					
						
							|  |  |  | 		// 		logger.log
 | 
					
						
							|  |  |  | 		// 			.filter(..)
 | 
					
						
							|  |  |  | 		// 			.map(logger.log2str)
 | 
					
						
							|  |  |  | 		// 			.join('\n')
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		log2str: function(){ | 
					
						
							|  |  |  | 			return (arguments.length == 0 ? | 
					
						
							|  |  |  | 					   (this.log || []) | 
					
						
							|  |  |  | 					: arguments[0] instanceof Array ? | 
					
						
							|  |  |  | 						[arguments[0]] | 
					
						
							|  |  |  | 					: arguments.length < 2 ? | 
					
						
							|  |  |  | 						(this.log ? | 
					
						
							|  |  |  | 							this.log.slice(arguments[0], arguments[1]) | 
					
						
							|  |  |  | 							: []) | 
					
						
							|  |  |  | 					: [arguments]) | 
					
						
							|  |  |  | 				.map(function([date, path, status, rest]){ | 
					
						
							|  |  |  | 					return `[${ new Date(date).getTimeStamp(true) }] `  | 
					
						
							|  |  |  | 						+ path.join(': ') + (path.length > 0 ? ': ' : '') | 
					
						
							|  |  |  | 						+ status  | 
					
						
							|  |  |  | 						+ (rest.length > 1 ?  | 
					
						
							|  |  |  | 								':\n\t' | 
					
						
							|  |  |  | 							: rest.length == 1 ? | 
					
						
							|  |  |  | 								': ' | 
					
						
							|  |  |  | 							: '') | 
					
						
							|  |  |  | 						+ rest.join(': ') }) | 
					
						
							|  |  |  | 				.join('\n') }, | 
					
						
							|  |  |  | 		print: function(...args){ | 
					
						
							|  |  |  | 			console.log(this.log2str(...args)) | 
					
						
							|  |  |  | 			return this }, | 
					
						
							| 
									
										
										
										
											2020-04-10 19:10:21 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// main API...
 | 
					
						
							| 
									
										
										
										
											2020-11-05 22:46:27 +03:00
										 |  |  | 		//
 | 
					
						
							|  |  |  | 		// 	.push(str, ...)
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// 	.push(str, ..., attrs)
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							| 
									
										
										
										
											2020-04-10 19:10:21 +03:00
										 |  |  | 		push: function(...msg){ | 
					
						
							| 
									
										
										
										
											2020-12-09 03:53:57 +03:00
										 |  |  | 			// settings...
 | 
					
						
							|  |  |  | 			var attrs = typeof(msg.last()) != typeof('str') ? | 
					
						
							| 
									
										
										
										
											2020-11-05 22:46:27 +03:00
										 |  |  | 				msg.pop() | 
					
						
							|  |  |  | 				: {} | 
					
						
							| 
									
										
										
										
											2020-04-10 19:10:21 +03:00
										 |  |  | 			return msg.length == 0 ? | 
					
						
							|  |  |  | 				this | 
					
						
							|  |  |  | 				: Object.assign( | 
					
						
							|  |  |  | 					this.constructor(), | 
					
						
							| 
									
										
										
										
											2020-11-05 22:46:27 +03:00
										 |  |  | 					attrs, | 
					
						
							| 
									
										
										
										
											2020-04-10 19:10:21 +03:00
										 |  |  | 					{ | 
					
						
							|  |  |  | 						root: this.root, | 
					
						
							| 
									
										
										
										
											2020-12-09 03:53:57 +03:00
										 |  |  | 						parent: this, | 
					
						
							| 
									
										
										
										
											2020-04-10 19:10:21 +03:00
										 |  |  | 						path: this.path.concat(msg), | 
					
						
							|  |  |  | 					}) }, | 
					
						
							|  |  |  | 		pop: function(){ | 
					
						
							|  |  |  | 			return (this.root === this || this.path.length == 1) ? | 
					
						
							|  |  |  | 				this | 
					
						
							|  |  |  | 				: Object.assign( | 
					
						
							|  |  |  | 					this.constructor(), | 
					
						
							|  |  |  | 					{ | 
					
						
							|  |  |  | 						root: this.root, | 
					
						
							|  |  |  | 						path: this.path.slice(0, -1), | 
					
						
							|  |  |  | 					}) }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		emit: function(status, ...rest){ | 
					
						
							| 
									
										
										
										
											2020-04-10 23:05:16 +03:00
										 |  |  | 			// write to log...
 | 
					
						
							|  |  |  | 			this.log !== false | 
					
						
							| 
									
										
										
										
											2020-04-11 01:09:49 +03:00
										 |  |  | 				&& this.log.push([ | 
					
						
							|  |  |  | 					Date.now(),  | 
					
						
							|  |  |  | 					this.path,  | 
					
						
							|  |  |  | 					status,  | 
					
						
							|  |  |  | 					rest]) | 
					
						
							| 
									
										
										
										
											2020-04-10 23:05:16 +03:00
										 |  |  | 			// maintain log size...
 | 
					
						
							| 
									
										
										
										
											2020-04-10 19:10:21 +03:00
										 |  |  | 			this.log !== false | 
					
						
							| 
									
										
										
										
											2020-04-10 23:05:16 +03:00
										 |  |  | 				&& (this.max_size > 0  | 
					
						
							|  |  |  | 					&& this.log.length > this.max_size  | 
					
						
							|  |  |  | 					&& this.log.splice(0, this.log.length - this.max_size)) | 
					
						
							|  |  |  | 			// call context log handler...
 | 
					
						
							| 
									
										
										
										
											2020-04-10 19:10:21 +03:00
										 |  |  | 			this.context | 
					
						
							|  |  |  | 				&& this.context.handleLogItem | 
					
						
							| 
									
										
										
										
											2020-11-05 22:46:27 +03:00
										 |  |  | 				&& this.context.handleLogItem(this, this.path, status, ...rest) | 
					
						
							| 
									
										
										
										
											2020-04-10 19:10:21 +03:00
										 |  |  | 			return this }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		__call__: function(_, status, ...rest){ | 
					
						
							|  |  |  | 			return this.emit(status, ...rest) }, | 
					
						
							|  |  |  | 		__init__: function(context){ | 
					
						
							|  |  |  | 			this.__context = context | 
					
						
							|  |  |  | 			this.root = this  | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	}), | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	__logger: null, | 
					
						
							|  |  |  | 	get logger(){ | 
					
						
							|  |  |  | 		return (this.__logger =  | 
					
						
							|  |  |  | 			this.__logger  | 
					
						
							|  |  |  | 				|| this.Logger(this)) }, | 
					
						
							| 
									
										
										
										
											2020-12-19 14:59:34 +03:00
										 |  |  | 	set logger(value){ | 
					
						
							|  |  |  | 		this.__logger = value }, | 
					
						
							| 
									
										
										
										
											2020-04-10 19:10:21 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-10 23:05:16 +03:00
										 |  |  | 	// XXX move this to console-logger???
 | 
					
						
							| 
									
										
										
										
											2020-12-09 19:02:31 +03:00
										 |  |  | 	// XXX should this be an action???
 | 
					
						
							| 
									
										
										
										
											2020-04-10 19:10:21 +03:00
										 |  |  | 	handleLogItem: ['- System/', | 
					
						
							| 
									
										
										
										
											2020-11-05 22:46:27 +03:00
										 |  |  | 		function(logger, path, status, ...rest){ | 
					
						
							| 
									
										
										
										
											2020-12-09 03:53:57 +03:00
										 |  |  | 			logger.quiet  | 
					
						
							|  |  |  | 				|| logger.root.quiet | 
					
						
							| 
									
										
										
										
											2020-11-05 22:46:27 +03:00
										 |  |  | 				|| console.log( | 
					
						
							|  |  |  | 					path.join(': ') + (path.length > 0 ? ': ' : '') | 
					
						
							|  |  |  | 						+ status  | 
					
						
							|  |  |  | 						+ (rest.length > 1 ?  | 
					
						
							|  |  |  | 								':\n\t' | 
					
						
							|  |  |  | 							: rest.length == 1 ? | 
					
						
							|  |  |  | 								': ' | 
					
						
							|  |  |  | 							: ''), ...rest) }], | 
					
						
							| 
									
										
										
										
											2020-04-10 19:10:21 +03:00
										 |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Logger =  | 
					
						
							|  |  |  | module.Logger = ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: '', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'logger', | 
					
						
							|  |  |  | 	depends: [], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	actions: LoggerActions, | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-26 07:01:20 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							| 
									
										
										
										
											2016-04-27 08:59:13 +03:00
										 |  |  | // System life-cycle...
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-24 02:15:53 +03:00
										 |  |  | // XXX potential pitfall: setting up new features without restarting...
 | 
					
						
							|  |  |  | // 		this can happen for instance in ig.js when starting a minimal
 | 
					
						
							|  |  |  | // 		imagegrid instance and then adding new features -- these new 
 | 
					
						
							|  |  |  | // 		features will not get their .start() (and friends) run...
 | 
					
						
							| 
									
										
										
										
											2020-12-25 06:00:51 +03:00
										 |  |  | // 		There are three stages here:
 | 
					
						
							|  |  |  | // 			- feature setup
 | 
					
						
							|  |  |  | // 				things the feature needs to run -- <feature>.setup(..)
 | 
					
						
							|  |  |  | // 			- app start
 | 
					
						
							|  |  |  | // 				things the app wants to do on start
 | 
					
						
							|  |  |  | // 			- ???
 | 
					
						
							|  |  |  | // 				things feature action needs to run in cli should be 
 | 
					
						
							|  |  |  | // 				documented and not depend on .start(..)
 | 
					
						
							|  |  |  | // 				...or there should be a way to "start" the new features...
 | 
					
						
							|  |  |  | // 		XXX put this in the docs...
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | // XXX should his have state???
 | 
					
						
							|  |  |  | // 		...if so, should this be a toggler???
 | 
					
						
							|  |  |  | var LifeCycleActions = actions.Actions({ | 
					
						
							| 
									
										
										
										
											2019-07-25 18:07:38 +03:00
										 |  |  | 	config: { | 
					
						
							|  |  |  | 		// if set indicates the timeput after which the application quits
 | 
					
						
							|  |  |  | 		// wating for .declareReady() and forcefully triggers .ready()
 | 
					
						
							|  |  |  | 		'declare-ready-timeout': 15000, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	 | 
					
						
							| 
									
										
										
										
											2018-03-05 00:39:13 +03:00
										 |  |  | 	__stop_handler: null, | 
					
						
							|  |  |  | 	__ready: null, | 
					
						
							|  |  |  | 	__ready_announce_requested: null, | 
					
						
							| 
									
										
										
										
											2019-07-25 18:07:38 +03:00
										 |  |  | 	__ready_announce_requests: null, | 
					
						
							| 
									
										
										
										
											2018-03-05 00:39:13 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// introspection...
 | 
					
						
							|  |  |  | 	isStarted: function(){  | 
					
						
							|  |  |  | 		return !!this.__stop_handler }, | 
					
						
							|  |  |  | 	isStopped: function(){  | 
					
						
							|  |  |  | 		return !this.__stop_handler }, | 
					
						
							|  |  |  | 	isReady: function(){  | 
					
						
							|  |  |  | 		return !!this.__ready }, | 
					
						
							| 
									
										
										
										
											2018-12-11 17:18:02 +03:00
										 |  |  | 	// XXX is this the right name for this???
 | 
					
						
							|  |  |  | 	get runtimeState(){ | 
					
						
							| 
									
										
										
										
											2018-12-12 00:53:17 +03:00
										 |  |  | 		return this.isStopped() ?  | 
					
						
							| 
									
										
										
										
											2018-12-11 17:18:02 +03:00
										 |  |  | 				'stopped' | 
					
						
							|  |  |  | 			: this.isReady() ?  | 
					
						
							|  |  |  | 				'ready' | 
					
						
							| 
									
										
										
										
											2018-12-12 00:53:17 +03:00
										 |  |  | 			: this.isStarted() ?  | 
					
						
							|  |  |  | 				'started' | 
					
						
							| 
									
										
										
										
											2018-12-11 17:18:02 +03:00
										 |  |  | 			: undefined }, | 
					
						
							| 
									
										
										
										
											2018-03-05 00:39:13 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-01 03:52:27 +03:00
										 |  |  | 	// XXX not implemented...
 | 
					
						
							|  |  |  | 	// 		...this should be triggered on first run and after updates...
 | 
					
						
							|  |  |  | 	setup: ['- System/', | 
					
						
							|  |  |  | 		doc``, | 
					
						
							|  |  |  | 		Event(function(mode){ | 
					
						
							|  |  |  | 			// System started event...
 | 
					
						
							|  |  |  | 			//
 | 
					
						
							|  |  |  | 			// Not intended for direct use.
 | 
					
						
							|  |  |  | 		})], | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 	start: ['- System/',  | 
					
						
							| 
									
										
										
										
											2017-01-29 04:10:10 +03:00
										 |  |  | 		doc`Start core action/event
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			.start() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		This action triggers system start, sets up basic runtime, prepares | 
					
						
							|  |  |  | 		for shutdown (stop) and handles the .ready() event. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		Attributes set here: | 
					
						
							|  |  |  | 			.runtime		- indicates the runtime ImageGrid is running. | 
					
						
							|  |  |  | 								this currently supports: | 
					
						
							|  |  |  | 									node, browser, nw, unknown | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		This will trigger .declareReady() if no action called  | 
					
						
							|  |  |  | 		.requestReadyAnnounce() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-05 00:39:13 +03:00
										 |  |  | 		This will trigger .started() event when done. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-25 06:00:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-29 04:10:10 +03:00
										 |  |  | 		NOTE: .runtime attribute will not be available on the .pre handler | 
					
						
							|  |  |  | 			phase. | 
					
						
							|  |  |  | 		NOTE: .requestReadyAnnounce() should be called exclusively on the | 
					
						
							|  |  |  | 			.pre handler phase as this will check and trigger the .ready() | 
					
						
							|  |  |  | 			event before the .post phase starts. | 
					
						
							| 
									
										
										
										
											2018-03-05 00:39:13 +03:00
										 |  |  | 		NOTE: handlers bound to this action/event will get called on the  | 
					
						
							|  |  |  | 			start *event* thus handlers bound when the system is already  | 
					
						
							|  |  |  | 			started will not get called until next start, to bind a handler  | 
					
						
							|  |  |  | 			to the started *state* bind to 'started' / .started() | 
					
						
							| 
									
										
										
										
											2017-01-29 04:10:10 +03:00
										 |  |  | 		`,
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 		function(){ | 
					
						
							|  |  |  | 			var that = this | 
					
						
							| 
									
										
										
										
											2020-12-08 18:35:50 +03:00
										 |  |  | 			this.logger  | 
					
						
							|  |  |  | 				&& this.logger.push('System').emit('start') | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// NOTE: jQuery currently provides no way to check if an event
 | 
					
						
							|  |  |  | 			// 		is bound so we'll need to keep track manually...
 | 
					
						
							|  |  |  | 			if(this.__stop_handler == null){ | 
					
						
							|  |  |  | 				var stop = this.__stop_handler = function(){ that.stop() } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							| 
									
										
										
										
											2020-12-10 19:13:48 +03:00
										 |  |  | 				return } | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-29 07:28:19 +03:00
										 |  |  | 			// set the runtime...
 | 
					
						
							|  |  |  | 			var runtime = this.runtime = ImageGridFeatures.runtime | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-29 07:28:19 +03:00
										 |  |  | 			// nw.js...
 | 
					
						
							| 
									
										
										
										
											2017-10-04 08:08:40 +03:00
										 |  |  | 			if(runtime.nw){ | 
					
						
							| 
									
										
										
										
											2015-12-29 07:28:19 +03:00
										 |  |  | 				// this handles both reload and close...
 | 
					
						
							|  |  |  | 				$(window).on('beforeunload', stop) | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-29 07:28:19 +03:00
										 |  |  | 				// NOTE: we are using both events as some of them do not
 | 
					
						
							|  |  |  | 				// 		get triggered in specific conditions and some do,
 | 
					
						
							|  |  |  | 				// 		for example, this gets triggered when the window's
 | 
					
						
							|  |  |  | 				// 		'X' is clicked while does not on reload...
 | 
					
						
							|  |  |  | 				this.__nw_stop_handler = function(){ | 
					
						
							|  |  |  | 					var w = this | 
					
						
							|  |  |  | 					try{ | 
					
						
							|  |  |  | 						that | 
					
						
							|  |  |  | 							// wait till ALL the handlers finish before 
 | 
					
						
							|  |  |  | 							// exiting...
 | 
					
						
							|  |  |  | 							.on('stop.post', function(){ | 
					
						
							| 
									
										
										
										
											2016-04-09 18:39:01 +03:00
										 |  |  | 								// XXX might be broken in nw13 -- test!!!
 | 
					
						
							|  |  |  | 								//w.close(true)
 | 
					
						
							|  |  |  | 								nw.App.quit() | 
					
						
							| 
									
										
										
										
											2015-12-29 07:28:19 +03:00
										 |  |  | 							}) | 
					
						
							|  |  |  | 							.stop() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					// in case something breaks exit...
 | 
					
						
							|  |  |  | 					// XXX not sure if this is correct...
 | 
					
						
							|  |  |  | 					} catch(e){ | 
					
						
							| 
									
										
										
										
											2016-05-10 17:02:38 +03:00
										 |  |  | 						console.log('ERROR:', e) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						DEBUG || nw.App.quit() | 
					
						
							|  |  |  | 						//this.close(true)
 | 
					
						
							| 
									
										
										
										
											2015-12-29 07:28:19 +03:00
										 |  |  | 					} | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2016-04-09 18:39:01 +03:00
										 |  |  | 				nw.Window.get().on('close', this.__nw_stop_handler) | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-26 07:01:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-02 21:18:30 +03:00
										 |  |  | 			// electron...
 | 
					
						
							|  |  |  | 			} else if(runtime.electron){ | 
					
						
							|  |  |  | 				$(window).on('beforeunload', stop) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-04 08:08:40 +03:00
										 |  |  | 			// node...
 | 
					
						
							|  |  |  | 			} else if(runtime.node){ | 
					
						
							| 
									
										
										
										
											2015-12-29 07:28:19 +03:00
										 |  |  | 				process.on('exit', stop) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// browser...
 | 
					
						
							| 
									
										
										
										
											2017-10-04 08:08:40 +03:00
										 |  |  | 			} else if(runtime.browser){ | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 				$(window).on('beforeunload', stop) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-29 07:28:19 +03:00
										 |  |  | 			// other...
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 			} else { | 
					
						
							| 
									
										
										
										
											2015-12-29 07:28:19 +03:00
										 |  |  | 				// XXX
 | 
					
						
							| 
									
										
										
										
											2020-12-10 19:13:48 +03:00
										 |  |  | 				console.warn('Unknown runtime:', runtime) } | 
					
						
							| 
									
										
										
										
											2017-01-26 07:01:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-04 08:08:40 +03:00
										 |  |  | 			// handle ready event...
 | 
					
						
							| 
									
										
										
										
											2017-01-26 07:01:20 +03:00
										 |  |  | 			// ...if no one requested to do it.
 | 
					
						
							|  |  |  | 			if(this.__ready_announce_requested == null | 
					
						
							|  |  |  | 					|| this.__ready_announce_requested <= 0){ | 
					
						
							| 
									
										
										
										
											2019-07-25 18:07:38 +03:00
										 |  |  | 				// in the browser world trigger .declareReady(..) on load event...
 | 
					
						
							| 
									
										
										
										
											2017-10-04 08:08:40 +03:00
										 |  |  | 				if(runtime.browser){ | 
					
						
							| 
									
										
										
										
											2019-07-25 18:07:38 +03:00
										 |  |  | 					$(function(){ that.declareReady('start') }) | 
					
						
							| 
									
										
										
										
											2017-01-26 07:01:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				} else { | 
					
						
							| 
									
										
										
										
											2020-12-10 19:13:48 +03:00
										 |  |  | 					this.declareReady('start') } } | 
					
						
							| 
									
										
										
										
											2018-03-05 00:39:13 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-25 18:07:38 +03:00
										 |  |  | 			// ready timeout -> force ready...
 | 
					
						
							|  |  |  | 			this.config['declare-ready-timeout'] > 0 | 
					
						
							| 
									
										
										
										
											2019-07-25 18:22:37 +03:00
										 |  |  | 				&& !this.__ready_announce_timeout | 
					
						
							|  |  |  | 				&& (this.__ready_announce_timeout =  | 
					
						
							|  |  |  | 					setTimeout(function(){ | 
					
						
							|  |  |  | 						// cleanup...
 | 
					
						
							|  |  |  | 						delete this.__ready_announce_timeout | 
					
						
							|  |  |  | 						if((this.__ready_announce_requests || new Set()).size == 0){ | 
					
						
							|  |  |  | 							delete this.__ready_announce_requests | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						// force start...
 | 
					
						
							|  |  |  | 						if(!this.isReady()){ | 
					
						
							|  |  |  | 							// report...
 | 
					
						
							|  |  |  | 							this.logger  | 
					
						
							|  |  |  | 								&& this.logger.push('start') | 
					
						
							|  |  |  | 									.emit('forcing ready.') | 
					
						
							|  |  |  | 									.emit('stalled:',  | 
					
						
							|  |  |  | 										this.__ready_announce_requested,  | 
					
						
							|  |  |  | 										...(this.__ready_announce_requests || [])) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 							// force ready...
 | 
					
						
							|  |  |  | 							this.__ready = !!this.ready()  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 							// cleanup...
 | 
					
						
							|  |  |  | 							delete this.__ready_announce_requested | 
					
						
							|  |  |  | 							delete this.__ready_announce_requests | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					}.bind(this), this.config['declare-ready-timeout'])) | 
					
						
							| 
									
										
										
										
											2019-07-25 18:07:38 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-05 00:39:13 +03:00
										 |  |  | 			// trigger the started event...
 | 
					
						
							| 
									
										
										
										
											2020-12-10 19:13:48 +03:00
										 |  |  | 			this.started() }], | 
					
						
							| 
									
										
										
										
											2018-03-05 00:39:13 +03:00
										 |  |  | 	started: ['- System/System started event', | 
					
						
							|  |  |  | 		doc`
 | 
					
						
							|  |  |  | 		`,
 | 
					
						
							|  |  |  | 	   	Event(function(){ | 
					
						
							|  |  |  | 			// System started event...
 | 
					
						
							|  |  |  | 			//
 | 
					
						
							|  |  |  | 			// Not intended for direct use.
 | 
					
						
							|  |  |  | 		})], | 
					
						
							| 
									
										
										
										
											2017-01-26 07:01:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-25 18:07:38 +03:00
										 |  |  | 	// NOTE: it is recommended to use this protocol in such a way that
 | 
					
						
							|  |  |  | 	// 		the .ready() handler would recover from a stalled 
 | 
					
						
							|  |  |  | 	// 		.requestReadyAnnounce() call...
 | 
					
						
							| 
									
										
										
										
											2017-01-26 07:01:20 +03:00
										 |  |  | 	ready: ['- System/System ready event', | 
					
						
							| 
									
										
										
										
											2017-01-29 04:10:10 +03:00
										 |  |  | 		doc`Ready core event
 | 
					
						
							| 
									
										
										
										
											2017-01-26 07:01:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		The ready event is fired right after start is done. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		Any feature can request to announce 'ready' itself when it is  | 
					
						
							|  |  |  | 		done by calling .requestReadyAnnounce(). | 
					
						
							|  |  |  | 		If .requestReadyAnnounce() is called, then the caller is required | 
					
						
							|  |  |  | 		to also call .declareReady(). | 
					
						
							|  |  |  | 		.ready() will actually be triggered only after when .declareReady() | 
					
						
							|  |  |  | 		is called the same number of times as .requestReadyAnnounce(). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		NOTE: at this point the system does not track the caller  | 
					
						
							|  |  |  | 			"honesty", so it is the caller's responsibility to follow | 
					
						
							|  |  |  | 			the protocol. | 
					
						
							|  |  |  | 		`,
 | 
					
						
							| 
									
										
										
										
											2018-02-13 01:14:35 +03:00
										 |  |  | 	   	Event(function(){ | 
					
						
							| 
									
										
										
										
											2017-01-26 07:01:20 +03:00
										 |  |  | 			// System ready event...
 | 
					
						
							|  |  |  | 			//
 | 
					
						
							|  |  |  | 			// Not intended for direct use, use .declareReady() to initiate.
 | 
					
						
							| 
									
										
										
										
											2020-12-08 18:35:50 +03:00
										 |  |  | 			this.logger  | 
					
						
							|  |  |  | 				&& this.logger.push('System').emit('ready') })], | 
					
						
							| 
									
										
										
										
											2017-01-26 07:01:20 +03:00
										 |  |  | 	// NOTE: this calls .ready() once per session.
 | 
					
						
							|  |  |  | 	declareReady: ['- System/Declare system ready',  | 
					
						
							| 
									
										
										
										
											2017-01-29 04:10:10 +03:00
										 |  |  | 		doc`Declare ready state
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			.declareReady() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		This will call .ready() but only in the following conditions: | 
					
						
							|  |  |  | 			- .requestReadyAnnounce() has not been called. | 
					
						
							|  |  |  | 			- .requestReadyAnnounce() has been called the same number of | 
					
						
							|  |  |  | 				times as .declareReady() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		NOTE: this will call .ready() only once per start/stop cycle. | 
					
						
							|  |  |  | 		`,
 | 
					
						
							| 
									
										
										
										
											2019-07-25 18:07:38 +03:00
										 |  |  | 		function(message){ | 
					
						
							| 
									
										
										
										
											2017-01-26 07:01:20 +03:00
										 |  |  | 			this.__ready_announce_requested | 
					
						
							|  |  |  | 				&& (this.__ready_announce_requested -= 1) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-25 18:07:38 +03:00
										 |  |  | 			message | 
					
						
							|  |  |  | 				&& this.__ready_announce_requests instanceof Set | 
					
						
							|  |  |  | 				&& this.__ready_announce_requests.delete(message) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-26 07:01:20 +03:00
										 |  |  | 			if(!this.__ready_announce_requested  | 
					
						
							| 
									
										
										
										
											2019-07-25 18:07:38 +03:00
										 |  |  | 					|| this.__ready_announce_requested <= 0){ | 
					
						
							|  |  |  | 				this.__ready = this.__ready  | 
					
						
							|  |  |  | 					|| !!this.ready()  | 
					
						
							| 
									
										
										
										
											2020-12-10 19:13:48 +03:00
										 |  |  | 				delete this.__ready_announce_requested } }], | 
					
						
							| 
									
										
										
										
											2017-01-26 07:01:20 +03:00
										 |  |  | 	requestReadyAnnounce: ['- System/', | 
					
						
							| 
									
										
										
										
											2017-01-29 04:10:10 +03:00
										 |  |  | 		doc`Request to announce the .ready() event.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			.requestReadyAnnounce() | 
					
						
							| 
									
										
										
										
											2019-07-25 18:07:38 +03:00
										 |  |  | 			.requestReadyAnnounce(message) | 
					
						
							| 
									
										
										
										
											2017-01-29 04:10:10 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		This enables a feature to delay the .ready() call until it is  | 
					
						
							|  |  |  | 		ready, this is useful for async or long stuff that can block | 
					
						
							|  |  |  | 		or slow down the .ready() phase. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		To indicate readiness, .declareReady() should be used. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		The system will call .ready() automatically when the last  | 
					
						
							|  |  |  | 		subscriber who called .requestReadyAnnounce() calls  | 
					
						
							|  |  |  | 		.declareReady(), i.e. .declareReady() must be called at least | 
					
						
							|  |  |  | 		as many times as .requestReadyAnnounce() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		The actual .ready() should never get called directly. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		NOTE: if this is called, .ready() will not get triggered  | 
					
						
							|  |  |  | 			automatically by the system. | 
					
						
							|  |  |  | 		`,
 | 
					
						
							| 
									
										
										
										
											2019-07-25 18:07:38 +03:00
										 |  |  | 		function(message){ | 
					
						
							|  |  |  | 			message | 
					
						
							|  |  |  | 				&& (this.__ready_announce_requests =  | 
					
						
							|  |  |  | 					this.__ready_announce_requests || new Set()) | 
					
						
							|  |  |  | 				&& this.__ready_announce_requests.add(message) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return (this.__ready_announce_requested = (this.__ready_announce_requested || 0) + 1) | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 		}], | 
					
						
							| 
									
										
										
										
											2017-01-29 04:10:10 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 	stop: ['- System/',  | 
					
						
							| 
									
										
										
										
											2017-01-29 04:10:10 +03:00
										 |  |  | 		doc`Stop core action
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			.stop() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		This will cleanup and unbind stop events. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		The goal of this is to prepare for system shutdown. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		NOTE: it is good practice for the bound handlers to set the  | 
					
						
							|  |  |  | 			system to a state from which their corresponding start/ready | 
					
						
							|  |  |  | 			handlers can run cleanly. | 
					
						
							|  |  |  | 		`,
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 		function(){ | 
					
						
							| 
									
										
										
										
											2017-10-04 08:08:40 +03:00
										 |  |  | 			// browser...
 | 
					
						
							|  |  |  | 			if(this.__stop_handler && this.runtime.browser){ | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 				$(window).off('beforeunload', this.__stop_handler) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// nw...
 | 
					
						
							| 
									
										
										
										
											2017-10-04 08:08:40 +03:00
										 |  |  | 			if(this.__nw_stop_handler && this.runtime.nw){ | 
					
						
							| 
									
										
										
										
											2016-07-03 02:21:33 +03:00
										 |  |  | 				nw.Window.get().removeAllListeners('close') | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 				delete this.__nw_stop_handler | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// node...
 | 
					
						
							| 
									
										
										
										
											2017-10-04 08:08:40 +03:00
										 |  |  | 			if(this.__stop_handler && this.runtime.node){ | 
					
						
							| 
									
										
										
										
											2016-07-03 02:21:33 +03:00
										 |  |  | 				process.removeAllListeners('exit') | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-26 07:01:20 +03:00
										 |  |  | 			delete this.__ready | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 			delete this.__stop_handler | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-08 18:35:50 +03:00
										 |  |  | 			this.logger  | 
					
						
							|  |  |  | 				&& this.logger.push('System').emit('stop') | 
					
						
							| 
									
										
										
										
											2018-03-05 00:39:13 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// trigger the stopped event...
 | 
					
						
							|  |  |  | 			this.stopped() | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	stopped: ['- System/System stopped event', | 
					
						
							|  |  |  | 		doc`
 | 
					
						
							|  |  |  | 		`,
 | 
					
						
							|  |  |  | 	   	Event(function(){ | 
					
						
							|  |  |  | 			// System stopped event...
 | 
					
						
							|  |  |  | 			//
 | 
					
						
							|  |  |  | 			// Not intended for direct use.
 | 
					
						
							|  |  |  | 		})], | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-01 03:52:27 +03:00
										 |  |  | 	// XXX not implemented...
 | 
					
						
							|  |  |  | 	// 		...this should be triggered before uninstall...
 | 
					
						
							|  |  |  | 	cleanup: ['- System/', | 
					
						
							|  |  |  | 		doc``, | 
					
						
							|  |  |  | 		Event(function(){ | 
					
						
							|  |  |  | 			// System started event...
 | 
					
						
							|  |  |  | 			//
 | 
					
						
							|  |  |  | 			// Not intended for direct use.
 | 
					
						
							|  |  |  | 		})], | 
					
						
							| 
									
										
										
										
											2018-03-05 00:39:13 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// trigger core events...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: we do not need to do .one(..) as it is implemented via .on(..)
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX EXPERIMENTAL...
 | 
					
						
							|  |  |  | 	// 		...should this be an action???
 | 
					
						
							|  |  |  | 	on: ['- System/', | 
					
						
							|  |  |  | 		function(evt, ...rest){ | 
					
						
							|  |  |  | 			var func = rest.slice().pop() | 
					
						
							|  |  |  | 			evt = typeof(evt) == typeof('') ? evt.split(/\s/g) : evt | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// we trigger the handler AFTER it is registered...
 | 
					
						
							|  |  |  | 			return function(){ | 
					
						
							|  |  |  | 				// started...
 | 
					
						
							|  |  |  | 				Math.max( | 
					
						
							|  |  |  | 						evt.indexOf('started'),  | 
					
						
							|  |  |  | 						evt.indexOf('started.pre'),  | 
					
						
							|  |  |  | 						evt.indexOf('started.post')) >= 0  | 
					
						
							|  |  |  | 					&& this.isStarted() | 
					
						
							|  |  |  | 					&& func.call(this) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// ready...
 | 
					
						
							|  |  |  | 				// NOTE: we are ignoring the '.pre' events here as we are already
 | 
					
						
							|  |  |  | 				// 		in the specific state... 
 | 
					
						
							|  |  |  | 				Math.max( | 
					
						
							|  |  |  | 						evt.indexOf('ready'),  | 
					
						
							|  |  |  | 						evt.indexOf('ready.post')) >= 0  | 
					
						
							|  |  |  | 					&& this.isReady() | 
					
						
							|  |  |  | 					&& func.call(this) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// started...
 | 
					
						
							|  |  |  | 				Math.max( | 
					
						
							|  |  |  | 						evt.indexOf('stopped'),  | 
					
						
							|  |  |  | 						evt.indexOf('stopped.pre'),  | 
					
						
							|  |  |  | 						evt.indexOf('stopped.post')) >= 0  | 
					
						
							|  |  |  | 					&& this.isStopped() | 
					
						
							| 
									
										
										
										
											2020-11-26 00:44:46 +03:00
										 |  |  | 					&& func.call(this) } }], | 
					
						
							| 
									
										
										
										
											2020-10-10 05:53:26 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// helpers...
 | 
					
						
							| 
									
										
										
										
											2020-12-23 20:02:37 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2020-10-10 05:53:26 +03:00
										 |  |  | 	restart: ['System/Soft restart', | 
					
						
							|  |  |  | 		doc`Soft restart
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		This will stop, clear and then start ImageGrid. | 
					
						
							|  |  |  | 		`,
 | 
					
						
							|  |  |  | 		function(){ | 
					
						
							|  |  |  | 			this | 
					
						
							|  |  |  | 				.stop() | 
					
						
							|  |  |  | 				.clear() | 
					
						
							|  |  |  | 				.start() }], | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var LifeCycle =  | 
					
						
							|  |  |  | module.LifeCycle = ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: '', | 
					
						
							|  |  |  | 	doc: '', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'lifecycle', | 
					
						
							| 
									
										
										
										
											2020-04-11 01:09:49 +03:00
										 |  |  | 	suggested: [ | 
					
						
							|  |  |  | 		'logger', | 
					
						
							|  |  |  | 	], | 
					
						
							| 
									
										
										
										
											2016-01-03 04:49:00 +03:00
										 |  |  | 	priority: 'high', | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	actions: LifeCycleActions, | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-05 03:40:55 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-27 22:17:47 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							| 
									
										
										
										
											2018-02-13 01:14:35 +03:00
										 |  |  | // Serialization...
 | 
					
						
							| 
									
										
										
										
											2017-08-27 22:17:47 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | var SerializationActions = actions.Actions({ | 
					
						
							|  |  |  | 	clone: ['- System/', | 
					
						
							| 
									
										
										
										
											2020-12-19 14:59:34 +03:00
										 |  |  | 		function(full){  | 
					
						
							|  |  |  | 			return actions.MetaActions.clone.call(this, full) }], | 
					
						
							| 
									
										
										
										
											2017-08-27 22:17:47 +03:00
										 |  |  | 	json: ['- System/', | 
					
						
							|  |  |  | 		function(){ return {} }], | 
					
						
							|  |  |  | 	load: ['- System/', | 
					
						
							| 
									
										
										
										
											2017-08-28 00:50:37 +03:00
										 |  |  | 		function(data, merge){ !merge && this.clear() }], | 
					
						
							| 
									
										
										
										
											2017-08-27 22:17:47 +03:00
										 |  |  | 	clear: ['- Sustem/', | 
					
						
							|  |  |  | 		function(){ }], | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Serialization =  | 
					
						
							|  |  |  | module.Serialization = ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: '', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'serialization', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	actions: SerializationActions, | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-05 03:40:55 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-18 09:16:46 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							| 
									
										
										
										
											2018-02-13 01:14:35 +03:00
										 |  |  | // Cache...
 | 
					
						
							| 
									
										
										
										
											2018-01-18 09:16:46 +03:00
										 |  |  | 	 | 
					
						
							| 
									
										
										
										
											2020-11-27 23:11:10 +03:00
										 |  |  | // XXX revise: cache group naming...
 | 
					
						
							|  |  |  | // 		currently the used groups are:
 | 
					
						
							|  |  |  | // 			Session groups -- cleared on .clear() ('cache')
 | 
					
						
							|  |  |  | // 				session-*
 | 
					
						
							|  |  |  | // 				view-*
 | 
					
						
							|  |  |  | // 			View groups -- cleared by crop/collection ('crop', 'collections')
 | 
					
						
							|  |  |  | // 				view-*
 | 
					
						
							|  |  |  | // 			Changes groups -- cleared when specific changes are made ('changes')
 | 
					
						
							|  |  |  | // 				*-data
 | 
					
						
							|  |  |  | // 				*-images
 | 
					
						
							|  |  |  | // 				...
 | 
					
						
							|  |  |  | // 		This approach seems not flexible enough...
 | 
					
						
							|  |  |  | // 		Ideas:
 | 
					
						
							|  |  |  | // 			- use keywords in group names??
 | 
					
						
							|  |  |  | // XXX should we consider persistent caches -- localStorage???
 | 
					
						
							| 
									
										
										
										
											2020-11-29 16:52:22 +03:00
										 |  |  | // XXX would be nice to have a simple cachedAction(name, cache-tag, expire, func) 
 | 
					
						
							|  |  |  | // 		action wrapper that would not require anything from the action and 
 | 
					
						
							|  |  |  | // 		just not call it if already called...
 | 
					
						
							| 
									
										
										
										
											2020-11-30 05:14:37 +03:00
										 |  |  | // 		...to do this we'll need to be able to select a value by args 
 | 
					
						
							|  |  |  | // 		from the cache this will require a diff mattch or something 
 | 
					
						
							|  |  |  | // 		similar...
 | 
					
						
							| 
									
										
										
										
											2018-01-18 09:16:46 +03:00
										 |  |  | var CacheActions = actions.Actions({ | 
					
						
							|  |  |  | 	config: { | 
					
						
							|  |  |  | 		// Enable/disable caching...
 | 
					
						
							|  |  |  | 		'cache': true, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Control pre-caching...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// This can be:
 | 
					
						
							| 
									
										
										
										
											2018-01-20 01:13:41 +03:00
										 |  |  | 		// 		true	- sync pre-cache (recursion)
 | 
					
						
							| 
									
										
										
										
											2018-01-18 09:43:38 +03:00
										 |  |  | 		// 		0		- semi-sync pre-cache
 | 
					
						
							| 
									
										
										
										
											2018-01-20 01:13:41 +03:00
										 |  |  | 		// 		number	- delay in milliseconds between pre-cache chunks
 | 
					
						
							| 
									
										
										
										
											2018-01-18 09:16:46 +03:00
										 |  |  | 		// 		false	- pre-caching disabled
 | 
					
						
							| 
									
										
										
										
											2018-01-18 09:43:38 +03:00
										 |  |  | 		'pre-cache': 0, | 
					
						
							| 
									
										
										
										
											2018-01-19 01:21:56 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Cache chunk length in ms...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// Caching is done in a series of chunks set by this separated by 
 | 
					
						
							|  |  |  | 		// timeouts set by .config['pre-cache'] to let other stuff run...
 | 
					
						
							|  |  |  | 		'pre-cache-chunk': 8, | 
					
						
							| 
									
										
										
										
											2018-01-20 01:13:41 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Control pre-cache progress display...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// This can be:
 | 
					
						
							|  |  |  | 		// 	false		- never show progress
 | 
					
						
							|  |  |  | 		// 	true		- always show progress
 | 
					
						
							|  |  |  | 		// 	number		- show progress if number of milliseconds has 
 | 
					
						
							|  |  |  | 		// 					passed and we are not done yet...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// NOTE: progress will only be displayed if .showProgress(..) 
 | 
					
						
							|  |  |  | 		// 		action is available...
 | 
					
						
							|  |  |  | 		'pre-cache-progress': 3000, | 
					
						
							| 
									
										
										
										
											2018-04-10 17:05:59 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-27 23:11:10 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Groups to be cleared at the longest on session change...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// These include by default:
 | 
					
						
							|  |  |  | 		// 	'session'			- will live through the whole session.
 | 
					
						
							|  |  |  | 		// 	'view'				- cleared when view changes
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		'cache-session-groups': [ | 
					
						
							|  |  |  | 			'session', | 
					
						
							|  |  |  | 			'view', | 
					
						
							|  |  |  | 		], | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-10 17:05:59 +03:00
										 |  |  | 		// XXX handler cache..
 | 
					
						
							| 
									
										
										
										
											2018-01-18 09:16:46 +03:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-27 23:11:10 +03:00
										 |  |  | 	__cache: null, | 
					
						
							| 
									
										
										
										
											2020-11-30 05:14:37 +03:00
										 |  |  | 	cache: doc('Get or set cache value', | 
					
						
							|  |  |  | 		doc`Get or set cache value
 | 
					
						
							|  |  |  | 		 | 
					
						
							|  |  |  | 			.cache(title, handler) | 
					
						
							|  |  |  | 				-> value | 
					
						
							|  |  |  | 		 | 
					
						
							|  |  |  | 			.cache(group, title, handler) | 
					
						
							|  |  |  | 				-> value | 
					
						
							|  |  |  | 		 | 
					
						
							| 
									
										
										
										
											2020-11-30 05:21:23 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		Currently the used groups are: | 
					
						
							|  |  |  | 			Session groups -- cleared on .clear() (feature: 'cache') | 
					
						
							|  |  |  | 				session-* | 
					
						
							|  |  |  | 				view-* | 
					
						
							|  |  |  | 			View groups -- cleared by crop/collection (feature: 'crop', 'collections') | 
					
						
							|  |  |  | 				view-* | 
					
						
							|  |  |  | 			Changes groups -- cleared when specific changes are made (feature: 'changes') | 
					
						
							|  |  |  | 				*-data | 
					
						
							|  |  |  | 				*-images | 
					
						
							|  |  |  | 				... | 
					
						
							| 
									
										
										
										
											2020-11-30 05:14:37 +03:00
										 |  |  | 		 | 
					
						
							| 
									
										
										
										
											2020-11-30 05:21:23 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-30 05:14:37 +03:00
										 |  |  | 		Example use: | 
					
						
							|  |  |  | 			someAction: [ | 
					
						
							|  |  |  | 				function(){ | 
					
						
							|  |  |  | 					return this.cache('someAction',  | 
					
						
							|  |  |  | 						function(data){ | 
					
						
							|  |  |  | 							if(data){ | 
					
						
							|  |  |  | 								// clone/update the data...
 | 
					
						
							|  |  |  | 								// NOTE: this should be faster than the construction
 | 
					
						
							|  |  |  | 								//		branch below or this will defeat the purpose 
 | 
					
						
							|  |  |  | 								//		of caching...
 | 
					
						
							|  |  |  | 								... | 
					
						
							|  |  |  | 		 | 
					
						
							|  |  |  | 							} else { | 
					
						
							|  |  |  | 								// get the data...
 | 
					
						
							|  |  |  | 								... | 
					
						
							|  |  |  | 							} | 
					
						
							|  |  |  | 							return data | 
					
						
							|  |  |  | 						}) }], | 
					
						
							|  |  |  | 		 | 
					
						
							|  |  |  | 		 | 
					
						
							|  |  |  | 		NOTE: since this is here to help speed things up, introducing a  | 
					
						
							|  |  |  | 			small but not necessary overhead by making this an action is | 
					
						
							|  |  |  | 			not logical... | 
					
						
							|  |  |  | 		`,
 | 
					
						
							|  |  |  | 		function(title, handler){ | 
					
						
							|  |  |  | 			var group = 'global' | 
					
						
							|  |  |  | 			// caching disabled...
 | 
					
						
							|  |  |  | 			if(!(this.config || {}).cache){ | 
					
						
							|  |  |  | 				return handler.call(this) } | 
					
						
							|  |  |  | 			arguments.length > 2 | 
					
						
							|  |  |  | 				&& ([group, title, handler] = arguments) | 
					
						
							|  |  |  | 			var cache = this.__cache = this.__cache || {} | 
					
						
							|  |  |  | 			cache = cache[group] = cache[group] || {} | 
					
						
							|  |  |  | 			return (cache[title] =  | 
					
						
							|  |  |  | 				title in cache ?  | 
					
						
							|  |  |  | 					// pass the cached data for cloning/update to the handler...
 | 
					
						
							|  |  |  | 					handler.call(this, cache[title]) | 
					
						
							|  |  |  | 					: handler.call(this)) }), | 
					
						
							| 
									
										
										
										
											2020-11-27 23:11:10 +03:00
										 |  |  | 	clearCache: ['System/Clear cache', | 
					
						
							|  |  |  | 		doc`
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Clear cache fully... | 
					
						
							|  |  |  | 			.clearCache() | 
					
						
							|  |  |  | 			 | 
					
						
							|  |  |  | 			Clear title (global group)... | 
					
						
							|  |  |  | 			.clearCache(title) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Clear title from group... | 
					
						
							|  |  |  | 			.clearCache(group, title) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Clear out the full group... | 
					
						
							|  |  |  | 			.clearCache(group, '*') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		NOTE: a group can be a string, list or a regexp object. | 
					
						
							|  |  |  | 		`,
 | 
					
						
							|  |  |  | 		function(title){ | 
					
						
							|  |  |  | 			var that = this | 
					
						
							|  |  |  | 			// full clear...
 | 
					
						
							|  |  |  | 			if(arguments.length == 0  | 
					
						
							|  |  |  | 					|| (arguments[0] == '*'  | 
					
						
							|  |  |  | 						&& arguments[1] == '*')){ | 
					
						
							|  |  |  | 				delete this.__cache | 
					
						
							|  |  |  | 			// partial clear...
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				var group = 'global' | 
					
						
							|  |  |  | 				// both group and title given...
 | 
					
						
							|  |  |  | 				arguments.length > 1 | 
					
						
							|  |  |  | 					&& ([group, title] = arguments) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// regexp...
 | 
					
						
							|  |  |  | 				// NOTE: these are only supported in groups...
 | 
					
						
							|  |  |  | 				if(group != '*' && group.includes('*')){ | 
					
						
							|  |  |  | 					group = new RegExp('^'+ group +'$', 'i') | 
					
						
							|  |  |  | 					group = Object.keys(this.__cache || {}) | 
					
						
							|  |  |  | 						.filter(function(g){ | 
					
						
							|  |  |  | 							return group.test(g) }) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// clear title from each group...
 | 
					
						
							|  |  |  | 				if(group == '*' || group instanceof Array || group instanceof RegExp){ | 
					
						
							|  |  |  | 					;(group instanceof Array ? | 
					
						
							|  |  |  | 						group | 
					
						
							|  |  |  | 					: group instanceof RegExp ?  | 
					
						
							|  |  |  | 						Object.keys(this.__cache || {}) | 
					
						
							|  |  |  | 							.filter(function(g){ | 
					
						
							|  |  |  | 								return group.test(g) }) | 
					
						
							|  |  |  | 					: Object.keys(this.__cache || {})) | 
					
						
							|  |  |  | 						.forEach(function(group){ | 
					
						
							|  |  |  | 							that.clearCache(group, title) }) | 
					
						
							|  |  |  | 				// clear multiple titles...
 | 
					
						
							|  |  |  | 				} else if(title instanceof Array){ | 
					
						
							|  |  |  | 					title.forEach(function(title){ | 
					
						
							|  |  |  | 						delete ((that.__cache || {})[group] || {})[title] }) | 
					
						
							|  |  |  | 				// clear group...
 | 
					
						
							|  |  |  | 				} else if(title == '*'){ | 
					
						
							|  |  |  | 					delete (this.__cache || {})[group] | 
					
						
							|  |  |  | 				// clear title from group...
 | 
					
						
							|  |  |  | 				} else { | 
					
						
							|  |  |  | 					delete ((this.__cache || {})[group] || {})[title] } } }], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// special caches...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	sessionCache: ['- System/', | 
					
						
							|  |  |  | 		doc`Add to session cache...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			.sessionCache(title, handler) | 
					
						
							|  |  |  | 				-> value | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		This is a shorthand to: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			.cache('session', title, handler) | 
					
						
							|  |  |  | 				-> value | 
					
						
							| 
									
										
										
										
											2018-01-18 09:43:38 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-27 23:11:10 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		NOTE: also see .cache(..) | 
					
						
							|  |  |  | 		`,
 | 
					
						
							|  |  |  | 		'cache: "session" ...'], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX doc: what are we precaching???
 | 
					
						
							| 
									
										
										
										
											2018-01-20 01:13:41 +03:00
										 |  |  | 	preCache: ['System/Run pre-cache', | 
					
						
							|  |  |  | 		doc`Run pre-cache...
 | 
					
						
							| 
									
										
										
										
											2018-01-18 09:43:38 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-20 01:13:41 +03:00
										 |  |  | 			Do an async pre-cache... | 
					
						
							|  |  |  | 			.preCache() | 
					
						
							| 
									
										
										
										
											2018-01-19 01:21:56 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-20 01:13:41 +03:00
										 |  |  | 			Do a sync pre-cache... | 
					
						
							|  |  |  | 			.preCache(true) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		NOTE: both "modes" of doing a pre-cache run in the main thread, | 
					
						
							|  |  |  | 			the difference is that the "async" version lets JS run frames | 
					
						
							|  |  |  | 			between processing sync chunks... | 
					
						
							| 
									
										
										
										
											2018-02-18 15:02:45 +03:00
										 |  |  | 		NOTE: this will not drop the existing cache, to do this run  | 
					
						
							|  |  |  | 			.clearCache() first or run .reCache(..). | 
					
						
							| 
									
										
										
										
											2018-01-20 01:13:41 +03:00
										 |  |  | 		`,
 | 
					
						
							|  |  |  | 		function(t){ | 
					
						
							|  |  |  | 			if(this.config.cache){ | 
					
						
							|  |  |  | 				var t = t || this.config['pre-cache'] || 0 | 
					
						
							|  |  |  | 				var c = this.config['pre-cache-chunk'] || 8 | 
					
						
							|  |  |  | 				var done = 0 | 
					
						
							|  |  |  | 				var attrs = [] | 
					
						
							|  |  |  | 				for(var k in this){ | 
					
						
							| 
									
										
										
										
											2020-11-27 23:11:10 +03:00
										 |  |  | 					attrs.push(k) } | 
					
						
							| 
									
										
										
										
											2018-01-20 01:13:41 +03:00
										 |  |  | 				var l = attrs.length | 
					
						
							| 
									
										
										
										
											2018-01-19 01:21:56 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-20 01:13:41 +03:00
										 |  |  | 				var started = Date.now() | 
					
						
							|  |  |  | 				var show = this.config['pre-cache-progress'] | 
					
						
							| 
									
										
										
										
											2018-01-18 09:43:38 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-20 01:13:41 +03:00
										 |  |  | 				var tick = function(){ | 
					
						
							|  |  |  | 					var a = Date.now() | 
					
						
							|  |  |  | 					var b = a | 
					
						
							|  |  |  | 					if(attrs.length == 0){ | 
					
						
							| 
									
										
										
										
											2020-11-27 23:11:10 +03:00
										 |  |  | 						return } | 
					
						
							| 
									
										
										
										
											2018-01-20 01:13:41 +03:00
										 |  |  | 					while(b - a < c){ | 
					
						
							|  |  |  | 						this[attrs.pop()] | 
					
						
							|  |  |  | 						b = Date.now() | 
					
						
							|  |  |  | 						done += 1 | 
					
						
							|  |  |  | 						this.showProgress | 
					
						
							|  |  |  | 							&& (show === true || (show && b - started > show)) | 
					
						
							| 
									
										
										
										
											2020-11-27 23:11:10 +03:00
										 |  |  | 							&& this.showProgress('Caching', done, l) } | 
					
						
							| 
									
										
										
										
											2018-01-20 01:13:41 +03:00
										 |  |  | 					t === true ? | 
					
						
							|  |  |  | 						tick() | 
					
						
							| 
									
										
										
										
											2020-11-27 23:11:10 +03:00
										 |  |  | 						: setTimeout(tick, t) }.bind(this) | 
					
						
							| 
									
										
										
										
											2018-01-20 01:13:41 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-27 23:11:10 +03:00
										 |  |  | 				tick() } }], | 
					
						
							| 
									
										
										
										
											2018-02-18 15:02:45 +03:00
										 |  |  | 	reCache: ['System/Re-cache', | 
					
						
							|  |  |  | 		function(t){ | 
					
						
							|  |  |  | 			this | 
					
						
							|  |  |  | 				.clearCache() | 
					
						
							|  |  |  | 				.preCache(t) }], | 
					
						
							| 
									
										
										
										
											2018-04-10 17:05:59 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	toggleHandlerCache: ['System/Action handler cache', | 
					
						
							|  |  |  | 		makeConfigToggler('action-handler-cache',  | 
					
						
							|  |  |  | 			['off', 'on']/*, | 
					
						
							|  |  |  | 			function(state){}*/)], | 
					
						
							|  |  |  | 	resetHanlerCache: ['System/Reset action handler cache', | 
					
						
							|  |  |  | 		function(){ | 
					
						
							|  |  |  | 			delete this.__handler_cache }], | 
					
						
							| 
									
										
										
										
											2018-01-18 09:16:46 +03:00
										 |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Cache =  | 
					
						
							|  |  |  | module.Cache = ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: '', | 
					
						
							|  |  |  | 	doc: '', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'cache', | 
					
						
							| 
									
										
										
										
											2018-01-20 01:13:41 +03:00
										 |  |  | 	// NOTE: we use .showProgress(..) of 'ui-progress' but we do not 
 | 
					
						
							|  |  |  | 	// 		need it to work, thus we do not declare it as a dependency...
 | 
					
						
							|  |  |  | 	//depends: [],
 | 
					
						
							| 
									
										
										
										
											2018-01-18 09:16:46 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	actions: CacheActions, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	handlers: [ | 
					
						
							| 
									
										
										
										
											2020-11-27 23:11:10 +03:00
										 |  |  | 		// System...
 | 
					
						
							| 
									
										
										
										
											2018-01-18 09:43:38 +03:00
										 |  |  | 		['start.pre',  | 
					
						
							| 
									
										
										
										
											2018-01-18 09:16:46 +03:00
										 |  |  | 			function(){  | 
					
						
							| 
									
										
										
										
											2020-11-27 23:11:10 +03:00
										 |  |  | 				this.clearCache() | 
					
						
							| 
									
										
										
										
											2018-01-18 09:16:46 +03:00
										 |  |  | 				var t = this.config['pre-cache'] | 
					
						
							| 
									
										
										
										
											2018-01-18 09:43:38 +03:00
										 |  |  | 				t === true ? | 
					
						
							| 
									
										
										
										
											2018-01-20 01:13:41 +03:00
										 |  |  | 					this.preCache('now')  | 
					
						
							| 
									
										
										
										
											2018-01-18 09:43:38 +03:00
										 |  |  | 				: t >= 0 ? | 
					
						
							| 
									
										
										
										
											2018-01-20 01:13:41 +03:00
										 |  |  | 					this.preCache()  | 
					
						
							| 
									
										
										
										
											2020-11-27 23:11:10 +03:00
										 |  |  | 				: false }], | 
					
						
							| 
									
										
										
										
											2018-04-10 17:05:59 +03:00
										 |  |  | 		['start', | 
					
						
							|  |  |  | 			function(){ | 
					
						
							|  |  |  | 				// XXX this breaks loading...
 | 
					
						
							| 
									
										
										
										
											2018-04-11 03:30:09 +03:00
										 |  |  | 				// 		...not sure why, but when switched on manually 
 | 
					
						
							|  |  |  | 				// 		there seems to be no problems...
 | 
					
						
							| 
									
										
										
										
											2018-04-10 17:05:59 +03:00
										 |  |  | 				//this.toggleHandlerCache(this.config['action-handler-cache'] || 'on')
 | 
					
						
							|  |  |  | 			}], | 
					
						
							| 
									
										
										
										
											2018-04-04 00:01:09 +03:00
										 |  |  | 		/*/ XXX clear cache when feature/action topology changes... | 
					
						
							|  |  |  | 		[[ | 
					
						
							|  |  |  | 			'inlineMixin', | 
					
						
							|  |  |  | 			'inlineMixout', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// XXX not sure about this...
 | 
					
						
							|  |  |  | 			'mixout', | 
					
						
							|  |  |  | 		], | 
					
						
							|  |  |  | 			function(){ | 
					
						
							|  |  |  | 				// XXX should this trigger a recache???
 | 
					
						
							|  |  |  | 				this.clearCache() | 
					
						
							|  |  |  | 			}], | 
					
						
							|  |  |  | 		//*/
 | 
					
						
							| 
									
										
										
										
											2020-11-27 23:11:10 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// clear session cache...
 | 
					
						
							|  |  |  | 		['clear', | 
					
						
							|  |  |  | 			//'clearCache: "(session|view)(-.*)?" "*" -- Clear session cache'],
 | 
					
						
							|  |  |  | 			function(){ | 
					
						
							|  |  |  | 				this.clearCache(`(${  | 
					
						
							|  |  |  | 					(this.config['cache-session-groups']  | 
					
						
							|  |  |  | 							|| ['session', 'view']) | 
					
						
							|  |  |  | 						.join('|') })(-.*)?`) }],
 | 
					
						
							| 
									
										
										
										
											2018-01-18 09:16:46 +03:00
										 |  |  | 	], | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-04 00:01:09 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-12 01:35:35 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							| 
									
										
										
										
											2018-02-13 01:14:35 +03:00
										 |  |  | // Timers...
 | 
					
						
							| 
									
										
										
										
											2018-02-12 01:35:35 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-01 23:45:00 +03:00
										 |  |  | // Create a debounced action...
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2018-09-02 00:12:08 +03:00
										 |  |  | // 	debounce(<func>)
 | 
					
						
							|  |  |  | // 	debounce(<timeout>, <func>)
 | 
					
						
							|  |  |  | // 	debounce(<options>, <func>)
 | 
					
						
							|  |  |  | // 		-> function
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2018-09-01 23:45:00 +03:00
										 |  |  | // options format:
 | 
					
						
							|  |  |  | // 	{
 | 
					
						
							|  |  |  | // 		timeout: number,
 | 
					
						
							|  |  |  | // 		returns: 'cached' | 'dropped',
 | 
					
						
							|  |  |  | // 		callback: function(retriggered, args),
 | 
					
						
							| 
									
										
										
										
											2020-12-03 04:03:20 +03:00
										 |  |  | //
 | 
					
						
							|  |  |  | // 		postcall: true,
 | 
					
						
							| 
									
										
										
										
											2018-09-01 23:45:00 +03:00
										 |  |  | // 	}
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2020-12-03 04:03:20 +03:00
										 |  |  | // XXX might be a good ide to move this someplace generic...
 | 
					
						
							|  |  |  | // XXX this is not debouncing pre/post calls, just the base action...
 | 
					
						
							| 
									
										
										
										
											2018-09-01 20:19:12 +03:00
										 |  |  | var debounce = | 
					
						
							|  |  |  | module.debounce = | 
					
						
							| 
									
										
										
										
											2018-09-01 23:45:00 +03:00
										 |  |  | function(options, func){ | 
					
						
							|  |  |  | 	// parse args...
 | 
					
						
							| 
									
										
										
										
											2018-09-02 00:12:08 +03:00
										 |  |  | 	var args = [...arguments] | 
					
						
							|  |  |  | 	func = args.pop() | 
					
						
							|  |  |  | 	options = args.pop() || {}  | 
					
						
							| 
									
										
										
										
											2018-09-01 23:45:00 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-03 04:03:20 +03:00
										 |  |  | 	typeof(options) == typeof(123) | 
					
						
							|  |  |  | 		&& (options.timeout = options) | 
					
						
							| 
									
										
										
										
											2018-09-01 23:45:00 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// closure state...
 | 
					
						
							| 
									
										
										
										
											2020-12-03 04:03:20 +03:00
										 |  |  | 	var res | 
					
						
							|  |  |  | 	var last_args | 
					
						
							| 
									
										
										
										
											2018-09-01 23:45:00 +03:00
										 |  |  | 	var debounced = false | 
					
						
							|  |  |  | 	var retriggered = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-03 04:03:20 +03:00
										 |  |  | 	// call the action...
 | 
					
						
							|  |  |  | 	var call = function(context, ...args){ | 
					
						
							|  |  |  | 		return func instanceof Function ? | 
					
						
							|  |  |  | 			func.call(context, ...args) | 
					
						
							|  |  |  | 			// alias...
 | 
					
						
							|  |  |  | 			: this.parseStringAction.callAction(context, func, ...args) } | 
					
						
							| 
									
										
										
										
											2018-09-01 23:45:00 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-03 04:03:20 +03:00
										 |  |  | 	return object.mixin( | 
					
						
							|  |  |  | 		function(...args){ | 
					
						
							|  |  |  | 			var retrigger | 
					
						
							|  |  |  | 			// call...
 | 
					
						
							|  |  |  | 			if(!debounced){ | 
					
						
							|  |  |  | 				res = call(this, ...args) | 
					
						
							|  |  |  | 				res = options.returns != 'cahced' ?  | 
					
						
							|  |  |  | 					res  | 
					
						
							|  |  |  | 					: undefined | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// start the timer...
 | 
					
						
							|  |  |  | 				debounced = setTimeout( | 
					
						
							|  |  |  | 					function(){ | 
					
						
							|  |  |  | 						var c | 
					
						
							|  |  |  | 						// callback...
 | 
					
						
							|  |  |  | 						options.callback instanceof Function | 
					
						
							|  |  |  | 							&& (c = options.callback.call(this, retriggered, args)) | 
					
						
							|  |  |  | 						// retrigger...
 | 
					
						
							|  |  |  | 						options.postcall | 
					
						
							|  |  |  | 							&& retriggered > 0 | 
					
						
							|  |  |  | 							&& c !== false | 
					
						
							|  |  |  | 							// XXX should this be a debounced call or a normal call...
 | 
					
						
							|  |  |  | 							// XXX this is not the actual action thus no 
 | 
					
						
							|  |  |  | 							// 		handlers will be triggered...
 | 
					
						
							|  |  |  | 							&& call(this, ...last_args) | 
					
						
							|  |  |  | 						// cleanup...
 | 
					
						
							|  |  |  | 						retriggered = 0 | 
					
						
							|  |  |  | 						res = undefined | 
					
						
							|  |  |  | 						debounced = false }.bind(this),  | 
					
						
							|  |  |  | 					options.timeout  | 
					
						
							|  |  |  | 						|| this.config['debounce-action-timeout']  | 
					
						
							|  |  |  | 						|| 200) | 
					
						
							|  |  |  | 			// skip...
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				retriggered++ | 
					
						
							|  |  |  | 				last_args = args | 
					
						
							|  |  |  | 				return res } }, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			toString: function(){ | 
					
						
							|  |  |  | 				return `// debounced...\n${ | 
					
						
							|  |  |  | 					doc([ func instanceof Function ?  | 
					
						
							|  |  |  | 						func.toString()  | 
					
						
							|  |  |  | 						: func ])}` },
 | 
					
						
							|  |  |  | 		}) } | 
					
						
							| 
									
										
										
										
											2018-09-01 23:45:00 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-01 20:19:12 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-01 23:45:00 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-12 01:35:35 +03:00
										 |  |  | var TimersActions = actions.Actions({ | 
					
						
							|  |  |  | 	config: { | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// Format:
 | 
					
						
							|  |  |  | 		// 	{
 | 
					
						
							|  |  |  | 		// 		<id>: {
 | 
					
						
							|  |  |  | 		// 			// action code (string)...
 | 
					
						
							|  |  |  | 		// 			action: <action>,
 | 
					
						
							|  |  |  | 		// 			// interval in milliseconds...
 | 
					
						
							|  |  |  | 		// 			ms: <ms>,
 | 
					
						
							|  |  |  | 		// 		},
 | 
					
						
							|  |  |  | 		// 		...
 | 
					
						
							|  |  |  | 		// 	}
 | 
					
						
							|  |  |  | 		'persistent-intervals': null, | 
					
						
							| 
									
										
										
										
											2018-09-01 20:19:12 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// A timeout to wait between calls to actions triggered via 
 | 
					
						
							|  |  |  | 		// .debounce(..)
 | 
					
						
							|  |  |  | 		'debounce-action-timeout': 200, | 
					
						
							| 
									
										
										
										
											2018-02-12 01:35:35 +03:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX should we store more metadata (ms?) and provide introspection 
 | 
					
						
							|  |  |  | 	// 		for these???
 | 
					
						
							|  |  |  | 	__timeouts: null, | 
					
						
							|  |  |  | 	__intervals: null, | 
					
						
							|  |  |  | 	__persistent_intervals: null, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Introspection...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: these are not editable...
 | 
					
						
							|  |  |  | 	get timeouts(){ | 
					
						
							|  |  |  | 		return Object.assign({}, this.__timeouts || {}) }, | 
					
						
							|  |  |  | 	get intervals(){ | 
					
						
							|  |  |  | 		return { | 
					
						
							|  |  |  | 			volatile: Object.assign({}, this.__intervals || {}), | 
					
						
							|  |  |  | 			persistent: JSON.parse(JSON.stringify( | 
					
						
							|  |  |  | 				this.config['persistent-intervals'] || {})), | 
					
						
							|  |  |  | 		} }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX should these be  actions???
 | 
					
						
							|  |  |  | 	isTimeout: function(id){ | 
					
						
							|  |  |  | 		return id in (this.__timeouts || {}) }, | 
					
						
							| 
									
										
										
										
											2018-02-13 01:14:35 +03:00
										 |  |  | 	isInterval: function(id){ | 
					
						
							|  |  |  | 		return id in (this.__intervals || {}) }, | 
					
						
							|  |  |  | 	isPersistentInterval: function(id){ | 
					
						
							|  |  |  | 		return id in (this.config['persistent-intervals'] || {}) }, | 
					
						
							|  |  |  | 	isPersistentIntervalActive: function(id){ | 
					
						
							|  |  |  | 		return this.isPersistentInterval(id)  | 
					
						
							|  |  |  | 			&& (id in (this.__persistent_intervals || {})) }, | 
					
						
							| 
									
										
										
										
											2018-02-12 01:35:35 +03:00
										 |  |  | 	isTimer: function(id){ | 
					
						
							| 
									
										
										
										
											2018-02-13 01:14:35 +03:00
										 |  |  | 		return this.isInterval(id)  | 
					
						
							|  |  |  | 			|| this.isPersistentInterval(id) | 
					
						
							|  |  |  | 			|| this.isTimeout(id) }, | 
					
						
							| 
									
										
										
										
											2018-02-12 01:35:35 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// General API...
 | 
					
						
							|  |  |  | 	// 
 | 
					
						
							|  |  |  | 	// NOTE: we are not trying to re-implement the native scheduler here
 | 
					
						
							|  |  |  | 	// 		just extend it and unify it's uses...
 | 
					
						
							|  |  |  | 	setTimeout: ['- System/',  | 
					
						
							|  |  |  | 		function(id, func, ms){ | 
					
						
							|  |  |  | 			var timeouts = this.__timeouts = this.__timeouts || {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			this.clearTimeout(id) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			timeouts[id] = setTimeout( | 
					
						
							|  |  |  | 				function(){ | 
					
						
							|  |  |  | 					// cleanup...
 | 
					
						
							|  |  |  | 					// NOTE: we are doing this before we run to avoid 
 | 
					
						
							|  |  |  | 					// 		leakage due to errors...
 | 
					
						
							|  |  |  | 					delete timeouts[id] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					// run...
 | 
					
						
							|  |  |  | 					func instanceof Function ?  | 
					
						
							|  |  |  | 						func.call(this)  | 
					
						
							|  |  |  | 						: this.call(func) | 
					
						
							|  |  |  | 				}.bind(this), | 
					
						
							|  |  |  | 				ms || 0) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	clearTimeout: ['- System/', | 
					
						
							|  |  |  | 		function(id){ | 
					
						
							|  |  |  | 			var timeouts = this.__timeouts = this.__timeouts || {} | 
					
						
							|  |  |  | 			clearTimeout(timeouts[id]) | 
					
						
							|  |  |  | 			delete timeouts[id]	 | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	setInterval: ['- System/', | 
					
						
							|  |  |  | 		function(id, func, ms){ | 
					
						
							|  |  |  | 			var intervals = this.__intervals = this.__intervals || {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			id in  intervals | 
					
						
							|  |  |  | 				&& clearInterval(intervals[id]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-13 01:14:35 +03:00
										 |  |  | 			intervals[id] = setInterval( | 
					
						
							| 
									
										
										
										
											2018-02-12 01:35:35 +03:00
										 |  |  | 				(func instanceof Function ? func : function(){ this.call(func) }) | 
					
						
							|  |  |  | 					.bind(this), | 
					
						
							|  |  |  | 				ms || 0) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	clearInterval: ['- System/', | 
					
						
							|  |  |  | 		function(id){ | 
					
						
							|  |  |  | 			var intervals = this.__intervals = this.__intervals || {} | 
					
						
							|  |  |  | 			clearInterval(intervals[id]) | 
					
						
							|  |  |  | 			delete intervals[id]	 | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	setPersistentInterval: ['- System/', | 
					
						
							|  |  |  | 		doc`
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Restart interval id... | 
					
						
							|  |  |  | 			.setPersistentInterval(id) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Save/start interval id... | 
					
						
							|  |  |  | 			.setPersistentInterval(id, action, ms) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		`,
 | 
					
						
							|  |  |  | 		function(id, action, ms){ | 
					
						
							|  |  |  | 			var intervals =  | 
					
						
							|  |  |  | 				this.__persistent_intervals =  | 
					
						
							|  |  |  | 					this.__persistent_intervals || {} | 
					
						
							|  |  |  | 			// NOTE: we set this later iff we make a change...
 | 
					
						
							|  |  |  | 			var cfg = this.config['persistent-intervals'] || {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// get defaults...
 | 
					
						
							|  |  |  | 			action = action ? action : cfg[id].action | 
					
						
							|  |  |  | 			ms = ms ? ms : cfg[id].ms | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// checks...
 | 
					
						
							|  |  |  | 			if(!ms || !action){ | 
					
						
							|  |  |  | 				console.error('Persistent interval: both action and ms must be set.') | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if(typeof(action) != typeof('str')){ | 
					
						
							|  |  |  | 				console.error('Persistent interval: handler must be a string.') | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			id in  intervals | 
					
						
							|  |  |  | 				&& clearInterval(intervals[id]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			this.config['persistent-intervals'] = cfg | 
					
						
							|  |  |  | 			cfg[id] = { | 
					
						
							|  |  |  | 				action: action,  | 
					
						
							|  |  |  | 				ms: ms, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-14 16:49:44 +03:00
										 |  |  | 			intervals[id] = setInterval( | 
					
						
							| 
									
										
										
										
											2018-02-12 01:35:35 +03:00
										 |  |  | 				function(){ this.call(action) }.bind(this),  | 
					
						
							|  |  |  | 				ms || 0) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	clearPersistentInterval: ['- System/', | 
					
						
							|  |  |  | 		function(id, stop_only){ | 
					
						
							|  |  |  | 			var intervals =  | 
					
						
							|  |  |  | 				this.__persistent_intervals =  | 
					
						
							|  |  |  | 					this.__persistent_intervals || {} | 
					
						
							|  |  |  | 			clearInterval(intervals[id]) | 
					
						
							|  |  |  | 			delete intervals[id]	 | 
					
						
							|  |  |  | 			if(!stop_only){ | 
					
						
							|  |  |  | 				delete this.config['persistent-intervals'][id] | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	// XXX revise name (???)
 | 
					
						
							|  |  |  | 	// XXX do we need actions other than start/stop ???
 | 
					
						
							|  |  |  | 	persistentIntervals: ['- System/', | 
					
						
							|  |  |  | 		doc`
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Start/restart all persistent interval timers... | 
					
						
							|  |  |  | 			.persistentIntervals('start') | 
					
						
							|  |  |  | 			.persistentIntervals('restart') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Stop all persistent interval timers... | 
					
						
							|  |  |  | 			.persistentIntervals('stop') | 
					
						
							|  |  |  | 		 | 
					
						
							|  |  |  | 		NOTE: 'start' and 'restart' are the same, both exist for mnemonics. | 
					
						
							|  |  |  | 		`,
 | 
					
						
							|  |  |  | 		function(action){ | 
					
						
							|  |  |  | 			var ids = Object.keys(this.config['persistent-intervals'] || {}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// start/restart...
 | 
					
						
							|  |  |  | 			;(action == 'start' || action == 'restart') ? | 
					
						
							|  |  |  | 				ids.forEach(function(id){ | 
					
						
							|  |  |  | 					this.setPersistentInterval(id) }.bind(this)) | 
					
						
							|  |  |  | 			// stop...
 | 
					
						
							|  |  |  | 			: action == 'stop' ? | 
					
						
							|  |  |  | 				ids.forEach(function(id){ | 
					
						
							|  |  |  | 					this.clearPersistentInterval(id, true) }.bind(this)) | 
					
						
							|  |  |  | 			// unknown action...
 | 
					
						
							|  |  |  | 			: console.error('persistentIntervals: unknown action:', action) | 
					
						
							| 
									
										
										
										
											2018-02-13 01:14:35 +03:00
										 |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Events...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2018-02-14 11:27:45 +03:00
										 |  |  | 	// XXX should these be "aligned" to real time???
 | 
					
						
							|  |  |  | 	// 		...i.e. everyHour is triggered on the XX:00:00 and not relative
 | 
					
						
							|  |  |  | 	// 		to start time?
 | 
					
						
							|  |  |  | 	// XXX should we macro these???
 | 
					
						
							| 
									
										
										
										
											2018-02-13 01:14:35 +03:00
										 |  |  | 	/*/ XXX would be nice to trigger these ONLY if there are handlers... | 
					
						
							| 
									
										
										
										
											2018-02-14 11:27:45 +03:00
										 |  |  | 	everySecond: ['- System/', | 
					
						
							| 
									
										
										
										
											2018-02-13 01:14:35 +03:00
										 |  |  | 		Event(function(){ | 
					
						
							| 
									
										
										
										
											2018-02-14 11:27:45 +03:00
										 |  |  | 			// XXX
 | 
					
						
							| 
									
										
										
										
											2018-02-13 01:14:35 +03:00
										 |  |  | 		})], | 
					
						
							| 
									
										
										
										
											2018-02-14 11:27:45 +03:00
										 |  |  | 	//*/
 | 
					
						
							|  |  |  | 	everyMinute: ['- System/', | 
					
						
							| 
									
										
										
										
											2018-02-13 01:14:35 +03:00
										 |  |  | 		Event(function(){ | 
					
						
							| 
									
										
										
										
											2018-02-14 11:27:45 +03:00
										 |  |  | 			// XXX
 | 
					
						
							| 
									
										
										
										
											2018-02-13 01:14:35 +03:00
										 |  |  | 		})], | 
					
						
							| 
									
										
										
										
											2018-02-14 11:27:45 +03:00
										 |  |  | 	every2Minutes: ['- System/', | 
					
						
							| 
									
										
										
										
											2018-02-13 01:14:35 +03:00
										 |  |  | 		Event(function(){ | 
					
						
							| 
									
										
										
										
											2018-02-14 11:27:45 +03:00
										 |  |  | 			// XXX
 | 
					
						
							| 
									
										
										
										
											2018-02-13 01:14:35 +03:00
										 |  |  | 		})], | 
					
						
							| 
									
										
										
										
											2018-02-14 11:27:45 +03:00
										 |  |  | 	every5Minutes: ['- System/', | 
					
						
							| 
									
										
										
										
											2018-02-13 01:14:35 +03:00
										 |  |  | 		Event(function(){ | 
					
						
							| 
									
										
										
										
											2018-02-14 11:27:45 +03:00
										 |  |  | 			// XXX
 | 
					
						
							|  |  |  | 		})], | 
					
						
							|  |  |  | 	every10Minutes: ['- System/', | 
					
						
							|  |  |  | 		Event(function(){ | 
					
						
							|  |  |  | 			// XXX
 | 
					
						
							|  |  |  | 		})], | 
					
						
							|  |  |  | 	every30Minutes: ['- System/', | 
					
						
							|  |  |  | 		Event(function(){ | 
					
						
							|  |  |  | 			// XXX
 | 
					
						
							|  |  |  | 		})], | 
					
						
							|  |  |  | 	everyHour: ['- System/', | 
					
						
							|  |  |  | 		Event(function(){ | 
					
						
							|  |  |  | 			// XXX
 | 
					
						
							| 
									
										
										
										
											2018-02-13 01:14:35 +03:00
										 |  |  | 		})], | 
					
						
							| 
									
										
										
										
											2018-09-01 20:19:12 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-01 23:59:28 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-01 20:19:12 +03:00
										 |  |  | 	// Action debounce...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	debounce: ['- System/', | 
					
						
							|  |  |  | 		doc`Debounce action call...
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-01 23:59:28 +03:00
										 |  |  | 		Debouncing prevents an action from being called more than once  | 
					
						
							|  |  |  | 		every timeout milliseconds. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-01 20:19:12 +03:00
										 |  |  | 			Debounce call an action... | 
					
						
							|  |  |  | 			.debounce(action, ...) | 
					
						
							|  |  |  | 			.debounce(timeout, action, ...) | 
					
						
							|  |  |  | 			.debounce(tag, action, ...) | 
					
						
							|  |  |  | 			.debounce(timeout, tag, action, ...) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Debounce call a function... | 
					
						
							|  |  |  | 			.debounce(tag, func, ...) | 
					
						
							|  |  |  | 			.debounce(timeout, tag, func, ...) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-01 23:45:00 +03:00
										 |  |  | 			Generic debounce: | 
					
						
							|  |  |  | 			.debounce(options, action, ...) | 
					
						
							|  |  |  | 			.debounce(options, func, ...) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		options format: | 
					
						
							|  |  |  | 			{ | 
					
						
							| 
									
										
										
										
											2018-09-01 23:59:28 +03:00
										 |  |  | 				// debounce timeout...
 | 
					
						
							| 
									
										
										
										
											2018-09-01 23:45:00 +03:00
										 |  |  | 				timeout: <milliseconds>, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-01 23:59:28 +03:00
										 |  |  | 				// tag to group action call debouncing (optional)
 | 
					
						
							|  |  |  | 				tag: <string>, | 
					
						
							| 
									
										
										
										
											2018-09-01 23:45:00 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-01 23:59:28 +03:00
										 |  |  | 				// controls how the return value is handled:
 | 
					
						
							|  |  |  | 				// 	'cached'	- during the timeout the first return value
 | 
					
						
							|  |  |  | 				// 					is cached and re-returned on each call
 | 
					
						
							|  |  |  | 				// 					during the timeout.
 | 
					
						
							|  |  |  | 				// 	'dropped'	- all return values are ignored/dropped
 | 
					
						
							|  |  |  | 				//
 | 
					
						
							|  |  |  | 				// NOTE: these, by design, enable only stable/uniform behavior
 | 
					
						
							|  |  |  | 				// 		without introducing any special cases and gotchas...
 | 
					
						
							|  |  |  | 				returns: 'cached' | 'dropped', | 
					
						
							| 
									
										
										
										
											2018-09-01 20:19:12 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-01 23:59:28 +03:00
										 |  |  | 				// if true the action will get retriggered after the timeout
 | 
					
						
							|  |  |  | 				// is over but only if it was triggered during the timeout...
 | 
					
						
							|  |  |  | 				//
 | 
					
						
							|  |  |  | 				// NOTE: if the action is triggered more often than timeout/200
 | 
					
						
							|  |  |  | 				// 		times, then it will not retrigger, this prevents an extra 
 | 
					
						
							|  |  |  | 				// 		call after, for example, sitting on a key and triggering
 | 
					
						
							|  |  |  | 				// 		key repeat...
 | 
					
						
							|  |  |  | 				retrigger: <bool>, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// a function, if given will be called when the timeout is up.
 | 
					
						
							|  |  |  | 				callback: function(<retrigger-count>, <args>), | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-09-01 20:19:12 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		NOTE: when using a tag, it must not resolve to and action, i.e. | 
					
						
							|  |  |  | 			this[tag] must not be callable... | 
					
						
							|  |  |  | 		NOTE: this ignores action return value and returns this... | 
					
						
							| 
									
										
										
										
											2018-09-01 23:59:28 +03:00
										 |  |  | 		NOTE: this uses core.debounce(..) adding a retrigger option to it... | 
					
						
							| 
									
										
										
										
											2018-09-01 20:19:12 +03:00
										 |  |  | 		`,
 | 
					
						
							|  |  |  | 		function(...args){ | 
					
						
							|  |  |  | 			// parse the args...
 | 
					
						
							| 
									
										
										
										
											2018-09-01 23:45:00 +03:00
										 |  |  | 			if(!(args[0] instanceof Function  | 
					
						
							|  |  |  | 					|| typeof(args[0]) == typeof(123) | 
					
						
							|  |  |  | 					|| typeof(args[0]) == typeof('str'))){ | 
					
						
							|  |  |  | 				var options = args.shift() | 
					
						
							|  |  |  | 				var tag = options.tag || args[0].name || args[0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				var options = { | 
					
						
							|  |  |  | 					timeout: typeof(args[0]) == typeof(123) ? | 
					
						
							|  |  |  | 						args.shift() | 
					
						
							|  |  |  | 						: (this.config['debounce-action-timeout'] || 200), | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// NOTE: this[tag] must not be callable, otherwise we treat it
 | 
					
						
							|  |  |  | 				// 		as an action...
 | 
					
						
							|  |  |  | 				var tag = (args[0] instanceof Function  | 
					
						
							|  |  |  | 						|| this[args[0]] instanceof Function) ?  | 
					
						
							|  |  |  | 					args[0]  | 
					
						
							|  |  |  | 					: args.shift() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// sanity check: when debouncing a function a tag is required...
 | 
					
						
							|  |  |  | 			if(tag instanceof Function){ | 
					
						
							|  |  |  | 				throw new TypeError('debounce: when passing a function a tag is required.') | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-01 20:19:12 +03:00
										 |  |  | 			var action = args.shift() | 
					
						
							| 
									
										
										
										
											2018-09-01 23:45:00 +03:00
										 |  |  | 			var attr = '__debounce_'+ tag | 
					
						
							| 
									
										
										
										
											2018-09-01 20:19:12 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-01 23:45:00 +03:00
										 |  |  | 			options = Object.assign(Object.create(options), { | 
					
						
							|  |  |  | 				callback: function(retriggered, args){ | 
					
						
							|  |  |  | 					// cleanup...
 | 
					
						
							|  |  |  | 					delete this[attr] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					// call the original callback...
 | 
					
						
							|  |  |  | 					options.__proto__.callback | 
					
						
							|  |  |  | 						&& options.__proto__.callback.call(that, ...args) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					if(options.retrigger  | 
					
						
							|  |  |  | 							&& retriggered > 0  | 
					
						
							|  |  |  | 							// this prevents an extra action after "sitting" 
 | 
					
						
							|  |  |  | 							// on the keyboard and triggering key repeat...
 | 
					
						
							|  |  |  | 							&& retriggered < (options.timeout || 200) / 200){ | 
					
						
							|  |  |  | 						var func = this[attr] = this[attr] || debounce(options, action) | 
					
						
							|  |  |  | 						func.call(this, ...args) | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				}, | 
					
						
							| 
									
										
										
										
											2018-09-01 20:19:12 +03:00
										 |  |  | 			}) | 
					
						
							| 
									
										
										
										
											2018-09-01 23:45:00 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			var func = this[attr] = this[attr] || debounce(options, action) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return func.call(this, ...args) | 
					
						
							| 
									
										
										
										
											2018-09-01 20:19:12 +03:00
										 |  |  | 		}], | 
					
						
							| 
									
										
										
										
											2018-02-12 01:35:35 +03:00
										 |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Timers =  | 
					
						
							|  |  |  | module.Timers = ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: '', | 
					
						
							|  |  |  | 	doc: '', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'timers', | 
					
						
							|  |  |  | 	depends: [ | 
					
						
							|  |  |  | 	], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	actions: TimersActions, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	handlers: [ | 
					
						
							| 
									
										
										
										
											2018-02-13 01:14:35 +03:00
										 |  |  | 		// start persistent timers...
 | 
					
						
							| 
									
										
										
										
											2018-02-12 01:35:35 +03:00
										 |  |  | 		// XXX should this be start or ready???
 | 
					
						
							|  |  |  | 		['start',  | 
					
						
							|  |  |  | 			function(){ this.persistentIntervals('start') }], | 
					
						
							| 
									
										
										
										
											2018-02-13 01:14:35 +03:00
										 |  |  | 		// stop all timers...
 | 
					
						
							| 
									
										
										
										
											2018-02-12 01:35:35 +03:00
										 |  |  | 		['stop',  | 
					
						
							| 
									
										
										
										
											2018-02-13 01:14:35 +03:00
										 |  |  | 			function(){  | 
					
						
							|  |  |  | 				Object.keys(this.__intervals || {}) | 
					
						
							|  |  |  | 					.forEach(function(id){ this.clearInterval(id) }.bind(this)) | 
					
						
							|  |  |  | 				Object.keys(this.__timeouts || {}) | 
					
						
							|  |  |  | 					.forEach(function(id){ this.clearTimeout(id) }.bind(this)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				this.persistentIntervals('stop')  | 
					
						
							|  |  |  | 			}], | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-14 11:27:45 +03:00
										 |  |  | 		// fixed timer actions...
 | 
					
						
							|  |  |  | 		// XXX not sure about these...
 | 
					
						
							| 
									
										
										
										
											2018-02-13 01:14:35 +03:00
										 |  |  | 		['start', | 
					
						
							|  |  |  | 			function(){ | 
					
						
							| 
									
										
										
										
											2018-02-14 11:27:45 +03:00
										 |  |  | 				var m = 1000*60 | 
					
						
							| 
									
										
										
										
											2018-02-13 01:14:35 +03:00
										 |  |  | 				this | 
					
						
							| 
									
										
										
										
											2018-02-14 11:27:45 +03:00
										 |  |  | 					.setInterval('everyMinute', 'everyMinute', m) | 
					
						
							|  |  |  | 					.setInterval('every2Minutes', 'every2Minutes', m*2) | 
					
						
							|  |  |  | 					.setInterval('every5Minutes', 'every5Minutes', m*5) | 
					
						
							|  |  |  | 					.setInterval('every10Minutes', 'every10Minutes', m*10) | 
					
						
							|  |  |  | 					.setInterval('every30Minutes', 'every30Minutes', m*30) | 
					
						
							|  |  |  | 					.setInterval('everyHour', 'everyHour', m*60) | 
					
						
							| 
									
										
										
										
											2018-02-13 01:14:35 +03:00
										 |  |  | 			}], | 
					
						
							| 
									
										
										
										
											2018-02-12 01:35:35 +03:00
										 |  |  | 	], | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | // Journal...
 | 
					
						
							| 
									
										
										
										
											2017-01-02 01:36:23 +03:00
										 |  |  | //
 | 
					
						
							|  |  |  | // This feature logs actions that either have the journal attribute set 
 | 
					
						
							|  |  |  | // to true or have an undo method/alias...
 | 
					
						
							|  |  |  | // 
 | 
					
						
							|  |  |  | //	Example:
 | 
					
						
							|  |  |  | // 		someAction: ['Path/to/Some action',
 | 
					
						
							|  |  |  | // 			// just journal the action, but it can't be undone...
 | 
					
						
							|  |  |  | // 			{journal: true},
 | 
					
						
							|  |  |  | // 			function(){ 
 | 
					
						
							|  |  |  | // 				... 
 | 
					
						
							|  |  |  | // 			}],
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 		otherAction: ['Path/to/Other action',
 | 
					
						
							|  |  |  | // 			// journal and provide undo functionality...
 | 
					
						
							|  |  |  | // 			{undo: function(){ 
 | 
					
						
							|  |  |  | // 				...
 | 
					
						
							|  |  |  | // 			}},
 | 
					
						
							|  |  |  | // 			function(){ 
 | 
					
						
							|  |  |  | // 				... 
 | 
					
						
							|  |  |  | // 			}],
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // 		someOtherAction: ['Path/to/Some other action',
 | 
					
						
							|  |  |  | // 			// use .otherAction(..) to undo...
 | 
					
						
							|  |  |  | // 			{undo: 'otherAction'},
 | 
					
						
							|  |  |  | // 			function(){ 
 | 
					
						
							|  |  |  | // 				... 
 | 
					
						
							|  |  |  | // 			}],
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // NOTE: .undo has priority over .journal, so there is no point of 
 | 
					
						
							|  |  |  | // 		defining both .journal and .undo action attributes, one is 
 | 
					
						
							|  |  |  | // 		enough.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | // XXX would be great to add a mechanism define how to reverse actions...
 | 
					
						
							|  |  |  | // 		...one way to do this at this point is to revert to last state
 | 
					
						
							|  |  |  | // 		and re-run the journal until the desired event...
 | 
					
						
							|  |  |  | // XXX need to define a clear journaling strategy in the lines of:
 | 
					
						
							|  |  |  | // 		- save state clears journal and adds a state load action
 | 
					
						
							|  |  |  | // 		- .load(..) clears journal
 | 
					
						
							| 
									
										
										
										
											2017-12-02 04:04:10 +03:00
										 |  |  | // XXX need a way to store additional info in the journal...
 | 
					
						
							|  |  |  | // 		can either be done as: 
 | 
					
						
							|  |  |  | // 			- a hook (action handler and/or attr)
 | 
					
						
							|  |  |  | // 			- inline code inside the action...
 | 
					
						
							|  |  |  | //		can't say I like #2 as it will mess the code up...
 | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | // XXX needs careful testing...
 | 
					
						
							|  |  |  | var JournalActions = actions.Actions({ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	clone: [function(full){ | 
					
						
							|  |  |  | 			return function(res){ | 
					
						
							|  |  |  | 				res.rjournal = null | 
					
						
							|  |  |  | 				res.journal = null | 
					
						
							|  |  |  | 				if(full && this.hasOwnProperty('journal') && this.journal){ | 
					
						
							|  |  |  | 					res.journal = JSON.parse(JSON.stringify(this.journal)) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	journal: null, | 
					
						
							|  |  |  | 	rjournal: null, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	journalable: null, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-02 04:04:10 +03:00
										 |  |  | 	// XXX doc supported attrs:
 | 
					
						
							|  |  |  | 	// 		undo
 | 
					
						
							|  |  |  | 	// 		undoable
 | 
					
						
							|  |  |  | 	//		getUndoState
 | 
					
						
							| 
									
										
										
										
											2017-12-01 20:59:58 +03:00
										 |  |  | 	// XXX should the action have control over what gets journaled and how???
 | 
					
						
							| 
									
										
										
										
											2017-12-28 06:15:16 +03:00
										 |  |  | 	// XXX should aliases support explicit undo???
 | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | 	updateJournalableActions: ['System/Update list of journalable actions', | 
					
						
							| 
									
										
										
										
											2017-12-28 06:15:16 +03:00
										 |  |  | 		doc`
 | 
					
						
							|  |  |  | 		 | 
					
						
							|  |  |  | 		NOTE: action aliases can not handle undo. | 
					
						
							|  |  |  | 		`,
 | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | 		function(){ | 
					
						
							|  |  |  | 			var that = this | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var handler = function(action){ | 
					
						
							|  |  |  | 				return function(){ | 
					
						
							|  |  |  | 					var cur = this.current | 
					
						
							| 
									
										
										
										
											2018-11-12 23:04:00 +03:00
										 |  |  | 					var args = [...arguments] | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-30 14:07:50 +03:00
										 |  |  | 					var data = { | 
					
						
							|  |  |  | 						type: 'basic', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						action: action,  | 
					
						
							|  |  |  | 						args: args, | 
					
						
							|  |  |  | 						// the current image before the action...
 | 
					
						
							|  |  |  | 						current: cur,  | 
					
						
							|  |  |  | 						// the target (current) image after action...
 | 
					
						
							|  |  |  | 						target: this.current,  | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | 					} | 
					
						
							| 
									
										
										
										
											2017-03-30 14:07:50 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 					// test if we need to journal this action signature...
 | 
					
						
							|  |  |  | 					var test = that.getActionAttr(action, 'undoable') | 
					
						
							|  |  |  | 					if(test && !test.call(that, data)){ | 
					
						
							|  |  |  | 						return | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-02 04:04:10 +03:00
										 |  |  | 					// get additional undo state...
 | 
					
						
							|  |  |  | 					var update = that.getActionAttr(action, 'getUndoState') | 
					
						
							| 
									
										
										
										
											2017-12-30 05:44:50 +03:00
										 |  |  | 					while(typeof(update) == typeof('str')){ | 
					
						
							|  |  |  | 						update = that.getActionAttr(update, 'getUndoState') | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2017-12-02 04:04:10 +03:00
										 |  |  | 					update  | 
					
						
							|  |  |  | 						&& update instanceof Function | 
					
						
							|  |  |  | 						&& update.call(that, data) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-30 14:07:50 +03:00
										 |  |  | 					// journal after the action is done...
 | 
					
						
							|  |  |  | 					return function(){ this.journalPush(data) } | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			this.journalable = this.actions | 
					
						
							|  |  |  | 				.filter(function(action){ | 
					
						
							| 
									
										
										
										
											2017-12-28 06:15:16 +03:00
										 |  |  | 					// skip aliases...
 | 
					
						
							|  |  |  | 					return !(that[action] instanceof actions.Alias) | 
					
						
							|  |  |  | 						&& (!!that.getActionAttr(action, 'undo')  | 
					
						
							|  |  |  | 							|| !!that.getActionAttr(action, 'journal')) | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | 				}) | 
					
						
							|  |  |  | 				// reset the handler
 | 
					
						
							|  |  |  | 				.map(function(action){ | 
					
						
							|  |  |  | 					that | 
					
						
							|  |  |  | 						.off(action+'.pre', 'journal-handler') | 
					
						
							|  |  |  | 						.on(action+'.pre', 'journal-handler', handler(action)) | 
					
						
							|  |  |  | 					return action | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	journalPush: ['- System/Journal/Add an item to journal', | 
					
						
							|  |  |  | 		function(data){ | 
					
						
							|  |  |  | 			// clear the reverse journal...
 | 
					
						
							|  |  |  | 			this.rjournal | 
					
						
							|  |  |  | 				&& (this.rjournal = null) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			this.journal = (this.hasOwnProperty('journal') || this.journal) ?  | 
					
						
							|  |  |  | 				this.journal || [] | 
					
						
							|  |  |  | 				: [] | 
					
						
							|  |  |  | 			this.journal.push(data) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	clearJournal: ['System/Journal/Clear the action journal', | 
					
						
							|  |  |  | 		function(){ | 
					
						
							|  |  |  | 			// NOTE: overwriting here is better as it will keep
 | 
					
						
							|  |  |  | 			// 		shadowing the parent's .journal in case we 
 | 
					
						
							|  |  |  | 			// 		are cloned.
 | 
					
						
							|  |  |  | 			// NOTE: either way this will have no effect as we 
 | 
					
						
							|  |  |  | 			// 		only use the local .journal but the user may
 | 
					
						
							|  |  |  | 			// 		get confused...
 | 
					
						
							|  |  |  | 			//delete this.journal
 | 
					
						
							|  |  |  | 			this.journal | 
					
						
							|  |  |  | 				&& (this.journal = null) | 
					
						
							|  |  |  | 			this.rjournal | 
					
						
							|  |  |  | 				&& (this.rjournal = null) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	runJournal: ['- System/Journal/Run journal', | 
					
						
							|  |  |  | 		//{journal: true},
 | 
					
						
							|  |  |  | 		function(journal){ | 
					
						
							|  |  |  | 			var that = this | 
					
						
							|  |  |  | 			journal.forEach(function(e){ | 
					
						
							|  |  |  | 				// load state...
 | 
					
						
							|  |  |  | 				that | 
					
						
							|  |  |  | 					.focusImage(e.current) | 
					
						
							|  |  |  | 					// run action...
 | 
					
						
							|  |  |  | 					[e.action].apply(that, e.args) | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX needs very careful revision...
 | 
					
						
							|  |  |  | 	// 		- should this be thread safe??? (likely not)
 | 
					
						
							|  |  |  | 	// 		- should the undo action have side-effects on the 
 | 
					
						
							|  |  |  | 	// 			journal/rjournal or should we clean them out??? 
 | 
					
						
							|  |  |  | 	// 			(currently cleaned)
 | 
					
						
							| 
									
										
										
										
											2017-12-01 20:59:58 +03:00
										 |  |  | 	// XXX should we control what gets pushed to the journal???
 | 
					
						
							| 
									
										
										
										
											2017-12-28 06:15:16 +03:00
										 |  |  | 	// XXX should we run undo of every action that supports it in the chain???
 | 
					
						
							|  |  |  | 	// 		...i.e. multiple extending actions can support undo
 | 
					
						
							|  |  |  | 	// 		XXX will also need to handle aliases in chain...
 | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | 	undo: ['Edit/Undo', | 
					
						
							| 
									
										
										
										
											2017-01-29 04:10:10 +03:00
										 |  |  | 		doc`Undo last action from .journal that can be undone
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			.undo() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		This will shift the action from .journal to .rjournal preparing  | 
					
						
							|  |  |  | 		it for .redo() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		NOTE: this will remove all the non undoable actions from the  | 
					
						
							|  |  |  | 			.journal up until and including the undone action. | 
					
						
							|  |  |  | 		NOTE: only the undone action is pushed to .rjournal | 
					
						
							|  |  |  | 		`,
 | 
					
						
							| 
									
										
										
										
											2020-05-20 20:31:19 +03:00
										 |  |  | 		{mode: function(){  | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | 			return (this.journal && this.journal.length > 0) || 'disabled' }}, | 
					
						
							|  |  |  | 		function(){ | 
					
						
							|  |  |  | 			var journal = this.journal.slice() || [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var rjournal = this.rjournal =  | 
					
						
							|  |  |  | 				(this.hasOwnProperty('rjournal') || this.rjournal) ?  | 
					
						
							|  |  |  | 					this.rjournal || []  | 
					
						
							|  |  |  | 					: [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for(var i = journal.length-1; i >= 0; i--){ | 
					
						
							|  |  |  | 				var a = journal[i] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// see if the action has an explicit undo attr...
 | 
					
						
							| 
									
										
										
										
											2017-01-03 02:55:25 +03:00
										 |  |  | 				var undo = this.getActionAttr(a.action, 'undo') | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				// general undo...
 | 
					
						
							|  |  |  | 				if(undo){ | 
					
						
							|  |  |  | 					// restore focus to where it was when the action 
 | 
					
						
							|  |  |  | 					// was called...
 | 
					
						
							|  |  |  | 					this.focusImage(a.current) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					// call the undo method/action...
 | 
					
						
							|  |  |  | 					// NOTE: this is likely to have side-effect on the 
 | 
					
						
							|  |  |  | 					// 		journal and maybe even rjournal...
 | 
					
						
							|  |  |  | 					// NOTE: these side-effects are cleaned out later.
 | 
					
						
							|  |  |  | 					var undo = undo instanceof Function ? | 
					
						
							|  |  |  | 							// pass the action name...
 | 
					
						
							|  |  |  | 							undo.call(this, a) | 
					
						
							|  |  |  | 						: typeof(undo) == typeof('str') ?  | 
					
						
							| 
									
										
										
										
											2017-12-30 05:44:50 +03:00
										 |  |  | 							// XXX pass journal structure as-is... (???)
 | 
					
						
							| 
									
										
										
										
											2017-12-29 18:33:59 +03:00
										 |  |  | 							this[undo].apply(this, a.args) | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | 						: null | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					// push the undone command to the reverse journal...
 | 
					
						
							|  |  |  | 					rjournal.push(journal.splice(i, 1)[0]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					// restore journal state...
 | 
					
						
							|  |  |  | 					// NOTE: calling the undo action would have cleared
 | 
					
						
							|  |  |  | 					// 		the rjournal and added stuff to the journal
 | 
					
						
							|  |  |  | 					// 		so we will need to restore things...
 | 
					
						
							|  |  |  | 					this.journal = journal | 
					
						
							|  |  |  | 					this.rjournal = rjournal | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					break | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	redo: ['Edit/Redo', | 
					
						
							| 
									
										
										
										
											2017-01-29 04:10:10 +03:00
										 |  |  | 		doc`Redo an action from .rjournal
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			.redo() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		Essentially this will remove and re-run the last action in .rjournal | 
					
						
							|  |  |  | 		`,
 | 
					
						
							| 
									
										
										
										
											2020-05-20 20:31:19 +03:00
										 |  |  | 		{mode: function(){  | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | 			return (this.rjournal && this.rjournal.length > 0) || 'disabled' }}, | 
					
						
							|  |  |  | 		function(){ | 
					
						
							|  |  |  | 			if(!this.rjournal || this.rjournal.length == 0){ | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			this.runJournal([this.rjournal.pop()]) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Journal =  | 
					
						
							|  |  |  | module.Journal = ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: 'Action Journal', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'journal', | 
					
						
							| 
									
										
										
										
											2017-08-27 22:17:47 +03:00
										 |  |  | 	depends: [ | 
					
						
							|  |  |  | 		'serialization', | 
					
						
							|  |  |  | 	], | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	actions: JournalActions, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX need to drop journal on save...
 | 
					
						
							|  |  |  | 	// XXX rotate/truncate journal???
 | 
					
						
							|  |  |  | 	// XXX need to check that all the listed actions are clean -- i.e.
 | 
					
						
							|  |  |  | 	// 		running the journal will produce the same results as user 
 | 
					
						
							|  |  |  | 	// 		actions that generated the journal.
 | 
					
						
							|  |  |  | 	handlers: [ | 
					
						
							|  |  |  | 		// log state, action and its args... 
 | 
					
						
							|  |  |  | 		['start', | 
					
						
							|  |  |  | 			function(){ this.updateJournalableActions() }], | 
					
						
							|  |  |  | 	], | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-13 05:47:14 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							| 
									
										
										
										
											2020-12-05 03:40:55 +03:00
										 |  |  | // Changes... 
 | 
					
						
							| 
									
										
										
										
											2017-02-13 05:47:14 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | var ChangesActions = actions.Actions({ | 
					
						
							|  |  |  | 	// This can be:
 | 
					
						
							|  |  |  | 	// 	- null/undefined	- write all
 | 
					
						
							|  |  |  | 	// 	- true				- write all
 | 
					
						
							|  |  |  | 	// 	- false				- write nothing
 | 
					
						
							|  |  |  | 	// 	- {
 | 
					
						
							|  |  |  | 	//		// write/skip data...
 | 
					
						
							|  |  |  | 	//		data: <bool>,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//		// write/skip images or write a diff including the given 
 | 
					
						
							|  |  |  | 	//		// <gid>s only...
 | 
					
						
							|  |  |  | 	//		images: <bool> | [ <gid>, ... ],
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//		// write/skip tags...
 | 
					
						
							|  |  |  | 	//		tags: <bool>,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//		// write/skip bookmarks...
 | 
					
						
							|  |  |  | 	//		bookmarked: <bool>,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//		// write/skip selected...
 | 
					
						
							|  |  |  | 	//		selected: <bool>,
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//		// feature specific custom flags...
 | 
					
						
							|  |  |  | 	//		...
 | 
					
						
							|  |  |  | 	// 	  }
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: in the complex format all fields ar optional; if a field 
 | 
					
						
							|  |  |  | 	// 		is not included it is not written (same as when set to false)
 | 
					
						
							|  |  |  | 	// NOTE: .current is written always.
 | 
					
						
							| 
									
										
										
										
											2018-04-12 01:40:26 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX this should be a prop to enable correct changes tracking via 
 | 
					
						
							|  |  |  | 	// 		events...
 | 
					
						
							| 
									
										
										
										
											2017-02-13 05:47:14 +03:00
										 |  |  | 	chages: null, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-12 22:59:22 +03:00
										 |  |  | 	get _changes(){ | 
					
						
							|  |  |  | 		return this.__changes }, | 
					
						
							|  |  |  | 	// XXX proxy to .markChanged(..)
 | 
					
						
							|  |  |  | 	set _changes(value){}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-13 05:47:14 +03:00
										 |  |  | 	clone: [function(full){ | 
					
						
							|  |  |  | 			return function(res){ | 
					
						
							|  |  |  | 				res.changes = null | 
					
						
							|  |  |  | 				if(full && this.hasOwnProperty('changes') && this.changes){ | 
					
						
							|  |  |  | 					res.changes = JSON.parse(JSON.stringify(this.changes)) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-12 01:40:26 +03:00
										 |  |  | 	// XXX this should also track .changes...
 | 
					
						
							| 
									
										
										
										
											2018-04-12 22:59:22 +03:00
										 |  |  | 	// 		...would also need to make this applicable to changes, 
 | 
					
						
							|  |  |  | 	// 		i.e. x.markChanged(x.changes)
 | 
					
						
							| 
									
										
										
										
											2017-02-13 05:47:14 +03:00
										 |  |  | 	markChanged: ['- System/', | 
					
						
							|  |  |  | 		doc`Mark data sections as changed...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Mark everything changed... | 
					
						
							|  |  |  | 			.markChanged('all') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Mark nothing changed... | 
					
						
							|  |  |  | 			.markChanged('none') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Mark section(s) as changed... | 
					
						
							|  |  |  | 			.markChanged(<section>) | 
					
						
							|  |  |  | 			.markChanged(<section>, ..) | 
					
						
							|  |  |  | 			.markChanged([<section>, ..]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Mark item(s) of section as changed... | 
					
						
							|  |  |  | 			.markChanged(<section>, [<item>, .. ]) | 
					
						
							| 
									
										
										
										
											2018-03-23 02:22:42 +03:00
										 |  |  | 				NOTE: items must be strings... | 
					
						
							| 
									
										
										
										
											2017-02-13 05:47:14 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-15 03:28:18 +03:00
										 |  |  | 		NOTE: when marking section items, the new items will be added to | 
					
						
							|  |  |  | 			the set of already marked items. | 
					
						
							| 
									
										
										
										
											2017-02-13 05:47:14 +03:00
										 |  |  | 		NOTE: when .changes is null (i.e. everything changed, marked via | 
					
						
							| 
									
										
										
										
											2017-02-15 03:28:18 +03:00
										 |  |  | 			.markChanged('all')) then calling this with anything other  | 
					
						
							|  |  |  | 			than 'none' will have no effect. | 
					
						
							| 
									
										
										
										
											2017-02-13 05:47:14 +03:00
										 |  |  | 		`,
 | 
					
						
							|  |  |  | 		function(section, items){ | 
					
						
							|  |  |  | 			var that = this | 
					
						
							|  |  |  | 			var args = section instanceof Array ?  | 
					
						
							|  |  |  | 				section  | 
					
						
							| 
									
										
										
										
											2018-11-12 23:04:00 +03:00
										 |  |  | 				: [...arguments] | 
					
						
							| 
									
										
										
										
											2017-02-13 05:47:14 +03:00
										 |  |  | 			//var changes = this.changes = 
 | 
					
						
							|  |  |  | 			var changes =  | 
					
						
							|  |  |  | 				this.hasOwnProperty('changes') ? | 
					
						
							|  |  |  | 					this.changes || {} | 
					
						
							|  |  |  | 					: {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// all...
 | 
					
						
							|  |  |  | 			if(args.length == 1 && args[0] == 'all'){ | 
					
						
							| 
									
										
										
										
											2017-10-03 04:34:19 +03:00
										 |  |  | 				this.changes = true | 
					
						
							| 
									
										
										
										
											2017-02-13 05:47:14 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// none...
 | 
					
						
							|  |  |  | 			} else if(args.length == 1 && args[0] == 'none'){ | 
					
						
							|  |  |  | 				this.changes = false  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// everything is marked changed, everything will be saved
 | 
					
						
							|  |  |  | 			// anyway...
 | 
					
						
							|  |  |  | 			// NOTE: to reset this use .markChanged('none') and then 
 | 
					
						
							|  |  |  | 			// 		manually add the desired changes...
 | 
					
						
							|  |  |  | 			} else if(this.changes == null){ | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// section items...
 | 
					
						
							|  |  |  | 			} else if(items instanceof Array) { | 
					
						
							| 
									
										
										
										
											2017-09-19 01:37:31 +03:00
										 |  |  | 				if(changes[section] === true){ | 
					
						
							|  |  |  | 					return | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2018-03-23 00:47:39 +03:00
										 |  |  | 				changes[section] = (changes[section] || []) | 
					
						
							|  |  |  | 					.concat(items) | 
					
						
							| 
									
										
										
										
											2018-04-03 00:07:38 +03:00
										 |  |  | 					.unique() | 
					
						
							| 
									
										
										
										
											2017-02-13 05:47:14 +03:00
										 |  |  | 				this.changes = changes | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// section(s)...
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				args.forEach(function(arg){ | 
					
						
							|  |  |  | 					changes[arg] = true | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 				this.changes = changes | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Changes =  | 
					
						
							|  |  |  | module.Changes = ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: '', | 
					
						
							|  |  |  | 	doc: '', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'changes', | 
					
						
							| 
									
										
										
										
											2017-08-27 22:17:47 +03:00
										 |  |  | 	depends: [ | 
					
						
							|  |  |  | 		'serialization', | 
					
						
							|  |  |  | 	], | 
					
						
							| 
									
										
										
										
											2017-02-13 05:47:14 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	actions: ChangesActions, | 
					
						
							| 
									
										
										
										
											2017-10-02 01:35:09 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	handlers: [ | 
					
						
							|  |  |  | 		// handle changes...
 | 
					
						
							|  |  |  | 		['json', | 
					
						
							|  |  |  | 			function(res, mode){ | 
					
						
							|  |  |  | 				if(this.changes != null){ | 
					
						
							|  |  |  | 					res.changes = JSON.parse(JSON.stringify(this.changes)) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}], | 
					
						
							|  |  |  | 		['load', | 
					
						
							|  |  |  | 			function(_, data){ | 
					
						
							|  |  |  | 				if(data.changes){ | 
					
						
							|  |  |  | 					this.changes = JSON.parse(JSON.stringify(data.changes)) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}], | 
					
						
							| 
									
										
										
										
											2020-11-27 23:11:10 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// clear caches relating to stuff we just changed...
 | 
					
						
							|  |  |  | 		['markChanged', | 
					
						
							|  |  |  | 			function(_, section){ | 
					
						
							|  |  |  | 				section = (section instanceof Array ? | 
					
						
							|  |  |  | 						section | 
					
						
							|  |  |  | 						: [section]) | 
					
						
							|  |  |  | 					.map(function(section){  | 
					
						
							|  |  |  | 						return '.*-'+section }) | 
					
						
							|  |  |  | 				this.clearCache(section, '*') }], | 
					
						
							| 
									
										
										
										
											2017-10-02 01:35:09 +03:00
										 |  |  | 	], | 
					
						
							| 
									
										
										
										
											2017-02-13 05:47:14 +03:00
										 |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							| 
									
										
										
										
											2016-04-27 08:59:13 +03:00
										 |  |  | // Workspace...
 | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | //
 | 
					
						
							|  |  |  | // Basic protocol:
 | 
					
						
							|  |  |  | // 	A participating feature should:
 | 
					
						
							|  |  |  | // 	- react to .saveWorkspace(..) by saving it's relevant state data to the 
 | 
					
						
							|  |  |  | // 		object returned by the .saveWorkspace() action.
 | 
					
						
							|  |  |  | // 		NOTE: it is recommended that a feature save its relevant .config
 | 
					
						
							|  |  |  | // 			data as-is.
 | 
					
						
							| 
									
										
										
										
											2016-03-28 22:12:22 +03:00
										 |  |  | // 		NOTE: no other action or state change should be triggered by this.
 | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | // 	- react to .loadWorkspace(..) by loading it's state from the returned
 | 
					
						
							|  |  |  | // 		object...
 | 
					
						
							| 
									
										
										
										
											2016-03-28 22:12:22 +03:00
										 |  |  | // 		NOTE: this can be active, i.e. a feature may call actions when 
 | 
					
						
							|  |  |  | // 			handling this.
 | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | // 	- react to .toggleChrome(..) and switch on and off the chrome 
 | 
					
						
							|  |  |  | // 		visibility... (XXX)
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-29 00:31:50 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Helpers...
 | 
					
						
							|  |  |  | var makeWorkspaceConfigWriter = | 
					
						
							|  |  |  | module.makeWorkspaceConfigWriter = function(keys, callback){ | 
					
						
							|  |  |  | 	return function(workspace){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-20 03:51:53 +03:00
										 |  |  | 		var data = keys instanceof Function ? keys.call(this) : keys | 
					
						
							| 
									
										
										
										
											2016-03-29 00:31:50 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-10 20:09:43 +03:00
										 |  |  | 		// store data...
 | 
					
						
							| 
									
										
										
										
											2016-11-20 03:51:53 +03:00
										 |  |  | 		data.forEach(function(key){ | 
					
						
							| 
									
										
										
										
											2016-03-29 00:31:50 +03:00
										 |  |  | 			workspace[key] = JSON.parse(JSON.stringify(that.config[key])) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		callback && callback.call(this, workspace) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // XXX should this delete a prop if it's not in the loading workspace???
 | 
					
						
							| 
									
										
										
										
											2016-06-10 20:09:43 +03:00
										 |  |  | // XXX only replace a prop if it has changed???
 | 
					
						
							| 
									
										
										
										
											2016-11-20 00:57:26 +03:00
										 |  |  | // XXX handle defaults -- when a workspace was just created...
 | 
					
						
							| 
									
										
										
										
											2016-03-29 00:31:50 +03:00
										 |  |  | var makeWorkspaceConfigLoader = | 
					
						
							|  |  |  | module.makeWorkspaceConfigLoader = function(keys, callback){ | 
					
						
							|  |  |  | 	return function(workspace){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-20 03:51:53 +03:00
										 |  |  | 		var data = keys instanceof Function ? keys.call(this) : keys | 
					
						
							| 
									
										
										
										
											2016-03-29 00:31:50 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-11 03:22:39 +03:00
										 |  |  | 		// load data...
 | 
					
						
							| 
									
										
										
										
											2016-11-20 03:51:53 +03:00
										 |  |  | 		data.forEach(function(key){ | 
					
						
							| 
									
										
										
										
											2016-03-29 00:31:50 +03:00
										 |  |  | 			// the key exists...
 | 
					
						
							|  |  |  | 			if(key in workspace){ | 
					
						
							|  |  |  | 				that.config[key] = JSON.parse(JSON.stringify(workspace[key])) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// no key set...
 | 
					
						
							|  |  |  | 			// XXX is this the right way to go???
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				delete that.config[key] | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-21 17:34:26 +03:00
										 |  |  | 		callback  | 
					
						
							|  |  |  | 			&& callback.call(this, workspace) | 
					
						
							| 
									
										
										
										
											2016-03-29 00:31:50 +03:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-20 00:57:26 +03:00
										 |  |  | // XXX need a way to handle defaults...
 | 
					
						
							| 
									
										
										
										
											2016-03-28 22:12:22 +03:00
										 |  |  | var WorkspaceActions = actions.Actions({ | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | 	config: { | 
					
						
							| 
									
										
										
										
											2016-05-07 00:47:52 +03:00
										 |  |  | 		'load-workspace': 'default', | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | 		'workspace': 'default', | 
					
						
							| 
									
										
										
										
											2016-03-29 00:31:50 +03:00
										 |  |  | 		'workspaces': {}, | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	get workspace(){ | 
					
						
							| 
									
										
										
										
											2016-11-20 03:51:53 +03:00
										 |  |  | 		return this.config.workspace }, | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | 	set workspace(value){ | 
					
						
							| 
									
										
										
										
											2016-11-20 03:51:53 +03:00
										 |  |  | 		this.loadWorkspace(value) }, | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-29 00:31:50 +03:00
										 |  |  | 	get workspaces(){ | 
					
						
							| 
									
										
										
										
											2016-11-20 03:51:53 +03:00
										 |  |  | 		return this.config.workspaces }, | 
					
						
							| 
									
										
										
										
											2016-03-29 00:31:50 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | 	getWorkspace: ['- Workspace/', | 
					
						
							|  |  |  | 		function(){ return this.saveWorkspace(null) }], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// NOTE: these are mainly triggers for other features to save/load
 | 
					
						
							|  |  |  | 	// 		their specific states...
 | 
					
						
							|  |  |  | 	// NOTE: handlers should only set data on the workspace object passively,
 | 
					
						
							|  |  |  | 	// 		no activity is recommended.
 | 
					
						
							|  |  |  | 	// NOTE: if null is passed this will only get the data, but will 
 | 
					
						
							|  |  |  | 	// 		save nothing. this us useful for introspection and temporary
 | 
					
						
							|  |  |  | 	// 		context storage.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2016-03-29 00:58:20 +03:00
										 |  |  | 	// XXX for some reason this does not get saved with .config...
 | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | 	saveWorkspace: ['Workspace/Save Workspace', | 
					
						
							|  |  |  | 		function(name){ | 
					
						
							| 
									
										
										
										
											2016-03-29 03:39:23 +03:00
										 |  |  | 			if(!this.config.hasOwnProperty('workspaces')){ | 
					
						
							|  |  |  | 				this.config['workspaces'] = JSON.parse(JSON.stringify(this.config['workspaces'])) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			var res = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if(name !== null){ | 
					
						
							| 
									
										
										
										
											2016-03-29 00:31:50 +03:00
										 |  |  | 				this.config['workspaces'][name || this.config.workspace] = res | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return res | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	// NOTE: merging the state data is the responsibility of the feature
 | 
					
						
							|  |  |  | 	// 		...this is done so as not to restrict the feature to one 
 | 
					
						
							|  |  |  | 	// 		specific way to do stuff...
 | 
					
						
							|  |  |  | 	loadWorkspace: ['Workspace/Load Workspace', | 
					
						
							|  |  |  | 		function(name){ | 
					
						
							| 
									
										
										
										
											2016-03-29 03:39:23 +03:00
										 |  |  | 			name = name || this.config.workspace | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-29 00:31:50 +03:00
										 |  |  | 			// get a workspace by name and load it...
 | 
					
						
							|  |  |  | 			if(typeof(name) == typeof('str')){ | 
					
						
							|  |  |  | 				this.config.workspace = name | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-29 05:17:50 +03:00
										 |  |  | 				return this.workspaces[name] || {} | 
					
						
							| 
									
										
										
										
											2016-03-29 00:31:50 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// we got the workspace object...
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				return name | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-29 05:17:50 +03:00
										 |  |  | 	// NOTE: this will not save the current workspace...
 | 
					
						
							| 
									
										
										
										
											2017-01-05 03:06:06 +03:00
										 |  |  | 	toggleWorkspace: ['Workspace/workspace', | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | 		makeConfigToggler('workspace', | 
					
						
							| 
									
										
										
										
											2016-03-29 00:31:50 +03:00
										 |  |  | 			function(){ return Object.keys(this.config['workspaces']) }, | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | 			function(state){ this.loadWorkspace(state) })], | 
					
						
							| 
									
										
										
										
											2016-05-07 00:47:52 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-09 04:59:36 +03:00
										 |  |  | 	// XXX should we keep the stack unique???
 | 
					
						
							| 
									
										
										
										
											2016-05-07 00:47:52 +03:00
										 |  |  | 	pushWorkspace: ['- Workspace/', | 
					
						
							|  |  |  | 		function(name){ | 
					
						
							|  |  |  | 			name = name || this.workspace | 
					
						
							|  |  |  | 			var stack = this.__workspace_stack = this.__workspace_stack || [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			this.saveWorkspace() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-09 04:59:36 +03:00
										 |  |  | 			if(stack.slice(-1)[0] == name){ | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-07 00:47:52 +03:00
										 |  |  | 			this.workspace != name && this.loadWorkspace(name) | 
					
						
							|  |  |  | 			stack.push(name) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	popWorkspace: ['- Workspace/', | 
					
						
							|  |  |  | 		function(){ | 
					
						
							|  |  |  | 			var stack = this.__workspace_stack | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if(!stack || stack.length == 0){ | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			this.saveWorkspace() | 
					
						
							|  |  |  | 			this.loadWorkspace(stack.pop()) | 
					
						
							|  |  |  | 		}], | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Workspace =  | 
					
						
							|  |  |  | module.Workspace = ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: '', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'workspace', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	depends: [ | 
					
						
							|  |  |  | 		'lifecycle', | 
					
						
							|  |  |  | 	], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	actions: WorkspaceActions, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	handlers: [ | 
					
						
							| 
									
										
										
										
											2016-05-07 00:47:52 +03:00
										 |  |  | 		['start',  | 
					
						
							|  |  |  | 			function(){  | 
					
						
							|  |  |  | 				this.loadWorkspace(this.config['load-workspace'] || 'default') }], | 
					
						
							| 
									
										
										
										
											2018-03-21 17:34:26 +03:00
										 |  |  | 		// NOTE: this needs to be done before the .config is saved...
 | 
					
						
							|  |  |  | 		['stop.pre',  | 
					
						
							| 
									
										
										
										
											2016-05-07 00:47:52 +03:00
										 |  |  | 			function(){  | 
					
						
							|  |  |  | 				this.saveWorkspace() }], | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | 	], | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-27 08:59:13 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | // Tasks and Queues...
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | // Task wrapper...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This simply makes tasks actions discoverable...
 | 
					
						
							|  |  |  | var Task = | 
					
						
							|  |  |  | module.Task = | 
					
						
							|  |  |  | function(func){ | 
					
						
							|  |  |  | 	func.__task__ = true | 
					
						
							|  |  |  | 	return func } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Task action helpers...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // NOTE: for examples see:
 | 
					
						
							|  |  |  | // 		features/examples.js: 
 | 
					
						
							|  |  |  | // 			ExampleActions.exampleTask(..)
 | 
					
						
							|  |  |  | // 			ExampleActions.exampleSessionTask(..)
 | 
					
						
							|  |  |  | // NOTE: we can pass sync/async to this in two places, in definition:
 | 
					
						
							|  |  |  | // 			var action = taskAction('some title', 'sync', function(..){ .. })
 | 
					
						
							|  |  |  | // 		or
 | 
					
						
							|  |  |  | // 			var action = taskAction('sync', 'some title', function(..){ .. })
 | 
					
						
							|  |  |  | // 		and on call:
 | 
					
						
							|  |  |  | // 			action('sync', ..)
 | 
					
						
							|  |  |  | // 		during the later form 'sync' is passed to .Task(..) in the correct
 | 
					
						
							|  |  |  | // 		position...
 | 
					
						
							|  |  |  | // 		(see ig-types' runner.TaskManager(..) for more info)
 | 
					
						
							|  |  |  | var taskAction = | 
					
						
							|  |  |  | module.taskAction = | 
					
						
							|  |  |  | function(title, func){ | 
					
						
							|  |  |  | 	var pre_args = [...arguments] | 
					
						
							|  |  |  | 	func = pre_args.pop() | 
					
						
							|  |  |  | 	title = pre_args | 
					
						
							|  |  |  | 		.filter(function(t){  | 
					
						
							|  |  |  | 			return t != 'sync' && t != 'async' }) | 
					
						
							|  |  |  | 		.pop() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var action | 
					
						
							| 
									
										
										
										
											2020-12-04 06:16:54 +03:00
										 |  |  | 	return (object.mixin( | 
					
						
							|  |  |  | 		action = Task(function(...args){ | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | 			if(args[0] == 'sync' || args[0] == 'async'){ | 
					
						
							|  |  |  | 				pre_args = [args.shift(), title] } | 
					
						
							| 
									
										
										
										
											2020-12-05 03:26:42 +03:00
										 |  |  | 			return Object.assign( | 
					
						
							|  |  |  | 				this.tasks.Task(...pre_args, func.bind(this), ...args),  | 
					
						
							|  |  |  | 				// make this searchable by .tasks.named(..)...
 | 
					
						
							| 
									
										
										
										
											2020-12-16 03:50:06 +03:00
										 |  |  | 				{  | 
					
						
							| 
									
										
										
										
											2020-12-16 22:40:10 +03:00
										 |  |  | 					__session_task__: !!action.__session_task__, | 
					
						
							| 
									
										
										
										
											2020-12-16 03:50:06 +03:00
										 |  |  | 					name: action.name,  | 
					
						
							|  |  |  | 				}) }), | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | 		{ | 
					
						
							| 
									
										
										
										
											2020-12-04 14:38:16 +03:00
										 |  |  | 			title, | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | 			toString: function(){ | 
					
						
							| 
									
										
										
										
											2020-12-04 14:38:16 +03:00
										 |  |  | 				return `core.taskAction('${ action.name }', \n\t${  | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | 					object.normalizeIndent('\t'+func.toString()) })` },
 | 
					
						
							|  |  |  | 		})) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var sessionTaskAction = | 
					
						
							|  |  |  | module.sessionTaskAction = | 
					
						
							|  |  |  | function(title, func){ | 
					
						
							|  |  |  | 	return object.mixin( | 
					
						
							|  |  |  | 		taskAction(...arguments), | 
					
						
							|  |  |  | 		{ __session_task__: true }) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | 
					
						
							| 
									
										
										
										
											2020-12-03 20:58:39 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-02 03:45:23 +03:00
										 |  |  | // Queued wrapper...
 | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | var Queued = | 
					
						
							|  |  |  | module.Queued = | 
					
						
							|  |  |  | function(func){ | 
					
						
							|  |  |  | 	func.__queued__ = true | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | 	return Task(func) } | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-03 20:58:39 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | // Queued action...
 | 
					
						
							| 
									
										
										
										
											2020-12-02 04:47:53 +03:00
										 |  |  | // 
 | 
					
						
							| 
									
										
										
										
											2020-12-04 14:38:16 +03:00
										 |  |  | //	queuedAction(title, func)
 | 
					
						
							|  |  |  | //	queuedAction(title, options, func)
 | 
					
						
							| 
									
										
										
										
											2020-12-02 04:47:53 +03:00
										 |  |  | //		-> action
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //	func(..)
 | 
					
						
							|  |  |  | //		-> res
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //	action(..)
 | 
					
						
							|  |  |  | //		-> promise(res)
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | // The idea here is that each time a queued action is called it is run 
 | 
					
						
							|  |  |  | // in a queue, and while it is running all consecutive calls are queued
 | 
					
						
							|  |  |  | // and run according to the queue policy.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2020-12-02 03:45:23 +03:00
										 |  |  | // NOTE: for examples see:
 | 
					
						
							|  |  |  | // 		features/examples.js: 
 | 
					
						
							|  |  |  | // 			ExampleActions.exampleQueuedAction(..)
 | 
					
						
							|  |  |  | // 			ExampleActions.exampleMultipleQueuedAction(..)
 | 
					
						
							| 
									
										
										
										
											2020-12-02 04:47:53 +03:00
										 |  |  | //
 | 
					
						
							| 
									
										
										
										
											2020-12-02 06:12:39 +03:00
										 |  |  | // XXX handle errors... (???)
 | 
					
						
							| 
									
										
										
										
											2020-12-02 18:49:45 +03:00
										 |  |  | // XXX revise logging and logger passing...
 | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | var queuedAction =  | 
					
						
							|  |  |  | module.queuedAction = | 
					
						
							| 
									
										
										
										
											2020-12-04 14:38:16 +03:00
										 |  |  | function(title, func){ | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 	var args = [...arguments] | 
					
						
							|  |  |  | 	func = args.pop()	 | 
					
						
							| 
									
										
										
										
											2020-12-04 14:38:16 +03:00
										 |  |  | 	var [title, opts] = args | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-04 06:16:54 +03:00
										 |  |  | 	var action | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 	return object.mixin( | 
					
						
							| 
									
										
										
										
											2020-12-04 06:16:54 +03:00
										 |  |  | 		action = Queued(function(...args){ | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 			var that = this | 
					
						
							| 
									
										
										
										
											2020-12-02 04:47:53 +03:00
										 |  |  | 			return new Promise(function(resolve, reject){ | 
					
						
							| 
									
										
										
										
											2020-12-04 06:16:54 +03:00
										 |  |  | 				Object.assign( | 
					
						
							| 
									
										
										
										
											2020-12-04 14:38:16 +03:00
										 |  |  | 					that.queue(title, opts || {}) | 
					
						
							| 
									
										
										
										
											2020-12-04 06:16:54 +03:00
										 |  |  | 						.push(function(){ | 
					
						
							|  |  |  | 							var res = func.call(that, ...args)  | 
					
						
							|  |  |  | 							resolve(res) | 
					
						
							|  |  |  | 							return res }), | 
					
						
							| 
									
										
										
										
											2020-12-16 03:50:06 +03:00
										 |  |  | 			   		{  | 
					
						
							| 
									
										
										
										
											2020-12-16 22:40:10 +03:00
										 |  |  | 						__session_task__: !!action.__session_task__, | 
					
						
							| 
									
										
										
										
											2020-12-16 03:50:06 +03:00
										 |  |  | 						title: action.name,  | 
					
						
							|  |  |  | 					}) }) }), | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  |    		{ | 
					
						
							| 
									
										
										
										
											2020-12-04 14:38:16 +03:00
										 |  |  | 			title, | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 			toString: function(){ | 
					
						
							| 
									
										
										
										
											2020-12-04 14:38:16 +03:00
										 |  |  | 				return `core.queuedAction('${action.name}',\n\t${  | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 					object.normalizeIndent( '\t'+ func.toString() ) })` },
 | 
					
						
							|  |  |  | 		}) } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-03 20:58:39 +03:00
										 |  |  | var sessionQueueAction = | 
					
						
							|  |  |  | module.sessionQueueAction = | 
					
						
							| 
									
										
										
										
											2020-12-04 14:38:16 +03:00
										 |  |  | function(title, func){ | 
					
						
							| 
									
										
										
										
											2020-12-03 20:58:39 +03:00
										 |  |  | 	return object.mixin( | 
					
						
							|  |  |  | 		queuedAction(...arguments), | 
					
						
							|  |  |  | 		{ __session_task__: true }) } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | // Queue action handler...
 | 
					
						
							| 
									
										
										
										
											2020-12-02 18:49:45 +03:00
										 |  |  | //
 | 
					
						
							| 
									
										
										
										
											2020-12-04 14:38:16 +03:00
										 |  |  | //	queueHandler(title[, opts][, arg_handler], func)
 | 
					
						
							| 
									
										
										
										
											2020-12-02 18:49:45 +03:00
										 |  |  | //		-> action
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | //	Prepare args...
 | 
					
						
							|  |  |  | //	arg_handler(queue, items, ...args)
 | 
					
						
							|  |  |  | //		-> [items, ...args]
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //	Prepare args in sync mode...
 | 
					
						
							| 
									
										
										
										
											2020-12-17 18:57:14 +03:00
										 |  |  | //	arg_handler('sync', items, ...args)
 | 
					
						
							| 
									
										
										
										
											2020-12-02 18:49:45 +03:00
										 |  |  | //		-> [items, ...args]
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | //	Call action...
 | 
					
						
							| 
									
										
										
										
											2020-12-02 18:49:45 +03:00
										 |  |  | //	action(items, ...args)
 | 
					
						
							|  |  |  | //		-> promise
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | //	Call action in sync mode...
 | 
					
						
							| 
									
										
										
										
											2020-12-02 18:49:45 +03:00
										 |  |  | //	action('sync', items, ...args)
 | 
					
						
							|  |  |  | //		-> promise
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | //	Action function...
 | 
					
						
							| 
									
										
										
										
											2020-12-02 18:49:45 +03:00
										 |  |  | //	func(item, ...args)
 | 
					
						
							|  |  |  | //		-> res
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | // This is different from queuedAction(..) in that what is queued is not
 | 
					
						
							|  |  |  | // the action itself but rather the first argument to that action and the
 | 
					
						
							| 
									
										
										
										
											2020-12-17 18:57:14 +03:00
										 |  |  | // action is used by the queue to handle each item. The rest of the 
 | 
					
						
							|  |  |  | // arguments are passed to each call.
 | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | //
 | 
					
						
							|  |  |  | // In 'sync' mode the action is run outside of queue/task right away, this
 | 
					
						
							|  |  |  | // is done because for a queue we can only control the sync start, i.e. 
 | 
					
						
							| 
									
										
										
										
											2020-12-17 18:57:14 +03:00
										 |  |  | // the first task execution, the rest depends on queue configuration 
 | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | // thus making the final behaviour unpredictable.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // NOTE: sync-mode actions do not externally log anything, basic progress 
 | 
					
						
							|  |  |  | // 		logging is handled by the queue/task which is not created in sync
 | 
					
						
							|  |  |  | // 		mode.
 | 
					
						
							| 
									
										
										
										
											2020-12-04 05:22:57 +03:00
										 |  |  | // NOTE: since the sync-mode can block it must be used very carefully.
 | 
					
						
							| 
									
										
										
										
											2020-12-17 18:57:14 +03:00
										 |  |  | // NOTE: for an example of chaining several queues see features/examples's:
 | 
					
						
							|  |  |  | // 			.exampleChainedQueueHandler(..)
 | 
					
						
							|  |  |  | // NOTE: when chaining queues, in 'sync' mode all queues in the chain will
 | 
					
						
							|  |  |  | // 		be run sync...
 | 
					
						
							|  |  |  | // NOTE: when chaining arg_handler(..) will get one queue per level of 
 | 
					
						
							|  |  |  | // 		chaining, but in 'sync' mode only one 'sync' is passed...
 | 
					
						
							| 
									
										
										
										
											2021-01-12 05:15:25 +03:00
										 |  |  | // NOTE: when calling this multiple times for the same queue each call 
 | 
					
						
							|  |  |  | // 		will call all the stages but since items are processes async the 
 | 
					
						
							|  |  |  | // 		later calls' later stages may end up with empty input queues, 
 | 
					
						
							|  |  |  | // 		e.g. for:
 | 
					
						
							|  |  |  | // 			[1,2,3].map(e => ig.exampleChainedQueueHandler(e))
 | 
					
						
							|  |  |  | // 		.exampleChainedQueueHandler(..) is called once per input and thus
 | 
					
						
							|  |  |  | // 		the first two stages are called sync and by the time the last 
 | 
					
						
							|  |  |  | // 		stage of the first call is triggered (async) all the inputs are 
 | 
					
						
							|  |  |  | // 		ready thus the first call will process all the inputs and the 
 | 
					
						
							|  |  |  | // 		later calls will get empty inputs (unless any new inputs are while 
 | 
					
						
							|  |  |  | // 		processing added)...
 | 
					
						
							| 
									
										
										
										
											2021-01-13 18:22:56 +03:00
										 |  |  | // 		i.e. within a queue/task async processing model there is no guarantee
 | 
					
						
							|  |  |  | // 		that the item will be processed in the same call tree that it 
 | 
					
						
							|  |  |  | // 		was added in...
 | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | //
 | 
					
						
							| 
									
										
										
										
											2020-12-05 03:40:55 +03:00
										 |  |  | // XXX might be a good idea to split this into a generic and domain parts 
 | 
					
						
							|  |  |  | // 		and move the generic part into types/runner...
 | 
					
						
							| 
									
										
										
										
											2021-01-14 23:52:37 +03:00
										 |  |  | // XXX check if item is already in queue (???)
 | 
					
						
							|  |  |  | // 		...how do we identify item uniqueness??
 | 
					
						
							| 
									
										
										
										
											2020-12-02 06:12:39 +03:00
										 |  |  | var queueHandler = | 
					
						
							|  |  |  | module.queueHandler = | 
					
						
							| 
									
										
										
										
											2020-12-04 14:38:16 +03:00
										 |  |  | function(title, func){ | 
					
						
							| 
									
										
										
										
											2020-12-02 06:12:39 +03:00
										 |  |  | 	var args = [...arguments] | 
					
						
							|  |  |  | 	func = args.pop()	 | 
					
						
							| 
									
										
										
										
											2020-12-02 18:49:45 +03:00
										 |  |  | 	var arg_handler =  | 
					
						
							|  |  |  | 		typeof(args.last()) == 'function'  | 
					
						
							|  |  |  | 			&& args.pop() | 
					
						
							| 
									
										
										
										
											2020-12-04 14:38:16 +03:00
										 |  |  | 	var [title, opts] = args | 
					
						
							| 
									
										
										
										
											2020-12-02 06:12:39 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-04 06:16:54 +03:00
										 |  |  | 	var action | 
					
						
							| 
									
										
										
										
											2020-12-02 06:12:39 +03:00
										 |  |  | 	return object.mixin( | 
					
						
							| 
									
										
										
										
											2020-12-04 06:16:54 +03:00
										 |  |  | 		action = Queued(function(items, ...args){ | 
					
						
							| 
									
										
										
										
											2020-12-02 06:12:39 +03:00
										 |  |  | 			var that = this | 
					
						
							| 
									
										
										
										
											2020-12-15 05:36:37 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-02 18:49:45 +03:00
										 |  |  | 			// sync start...
 | 
					
						
							| 
									
										
										
										
											2020-12-08 02:35:06 +03:00
										 |  |  | 			if(arguments[0] == 'sync' || arguments[0] == 'async'){ | 
					
						
							| 
									
										
										
										
											2020-12-02 18:49:45 +03:00
										 |  |  | 				var [sync, items, ...args] = arguments } | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-15 05:36:37 +03:00
										 |  |  | 			var q | 
					
						
							|  |  |  | 			var inputs = [items, ...args] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-21 03:41:11 +03:00
										 |  |  | 			// pre-process args...
 | 
					
						
							|  |  |  | 			arg_handler | 
					
						
							|  |  |  | 				&& (inputs = arg_handler.call(this,  | 
					
						
							|  |  |  | 					sync == 'sync' ?  | 
					
						
							|  |  |  | 						sync  | 
					
						
							|  |  |  | 						: q,  | 
					
						
							|  |  |  | 					...inputs)) | 
					
						
							|  |  |  | 			// special-case: empty inputs -- no need to handle anything...
 | 
					
						
							|  |  |  | 			if(inputs instanceof Array  | 
					
						
							|  |  |  | 					&& inputs[0]  | 
					
						
							|  |  |  | 					&& inputs[0].length == 0){ | 
					
						
							|  |  |  | 				return Promise.resolve(inputs) } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-15 05:36:37 +03:00
										 |  |  | 			// Define the runner and prepare...
 | 
					
						
							|  |  |  | 			//
 | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | 			// sync mode -- run action outside of queue...
 | 
					
						
							|  |  |  | 			// NOTE: running the queue in sync mode is not practical as
 | 
					
						
							|  |  |  | 			// 		the results may depend on queue configuration and 
 | 
					
						
							|  |  |  | 			// 		size...
 | 
					
						
							| 
									
										
										
										
											2020-12-08 02:35:06 +03:00
										 |  |  | 			if(sync == 'sync'){ | 
					
						
							| 
									
										
										
										
											2020-12-15 05:36:37 +03:00
										 |  |  | 				var run = function([items, ...args]){ | 
					
						
							|  |  |  | 					return Promise.all( | 
					
						
							|  |  |  | 						(items instanceof Array ?  | 
					
						
							|  |  |  | 							items  | 
					
						
							|  |  |  | 							: [items]) | 
					
						
							|  |  |  | 						.map(function(item){ | 
					
						
							| 
									
										
										
										
											2020-12-17 18:57:14 +03:00
										 |  |  | 							var res = func.call(that, item, ...args)  | 
					
						
							|  |  |  | 							return res === runner.SKIP ?  | 
					
						
							|  |  |  | 								[] | 
					
						
							|  |  |  | 								: [res] }) | 
					
						
							|  |  |  | 						.flat()) } | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | 			// queue mode...
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				// prep queue...
 | 
					
						
							| 
									
										
										
										
											2020-12-15 05:36:37 +03:00
										 |  |  | 				q = that.queue(title, | 
					
						
							|  |  |  | 					Object.assign( | 
					
						
							|  |  |  | 						{}, | 
					
						
							|  |  |  | 						opts || {}, | 
					
						
							|  |  |  | 						{  | 
					
						
							| 
									
										
										
										
											2020-12-16 22:40:10 +03:00
										 |  |  | 							__session_task__: !!action.__session_task__, | 
					
						
							| 
									
										
										
										
											2020-12-15 05:36:37 +03:00
										 |  |  | 							handler: function([item, args]){ | 
					
						
							|  |  |  | 								return func.call(that, item, ...(args || [])) },  | 
					
						
							|  |  |  | 						})) | 
					
						
							|  |  |  | 				q.title = action.name  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				var run = function([items, ...args]){ | 
					
						
							|  |  |  | 					// fill the queue...
 | 
					
						
							|  |  |  | 					// NOTE: we are also adding a ref to args here to keep things consistent...
 | 
					
						
							|  |  |  | 					args.length > 0 | 
					
						
							|  |  |  | 						&& (args = [args]) | 
					
						
							| 
									
										
										
										
											2020-12-22 18:25:14 +03:00
										 |  |  | 					q.add(items instanceof Array ?  | 
					
						
							| 
									
										
										
										
											2021-01-12 05:15:25 +03:00
										 |  |  | 						items | 
					
						
							|  |  |  | 							// move the inputs out of the input array...
 | 
					
						
							|  |  |  | 							// NOTE: this will prevent the items from getting 
 | 
					
						
							|  |  |  | 							// 		processed multiple times when the action 
 | 
					
						
							|  |  |  | 							// 		is called multiple times...
 | 
					
						
							|  |  |  | 							.splice(0, items.length) | 
					
						
							|  |  |  | 							.map(function(e){  | 
					
						
							| 
									
										
										
										
											2020-12-15 05:36:37 +03:00
										 |  |  | 								return [e, ...args] })  | 
					
						
							| 
									
										
										
										
											2020-12-22 19:22:35 +03:00
										 |  |  | 						: [[items, ...args]]) | 
					
						
							| 
									
										
										
										
											2020-12-16 03:50:06 +03:00
										 |  |  | 					return q.promise() } }  | 
					
						
							| 
									
										
										
										
											2020-12-15 05:36:37 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// run...
 | 
					
						
							| 
									
										
										
										
											2020-12-16 22:40:10 +03:00
										 |  |  | 			return (inputs instanceof Promise  | 
					
						
							|  |  |  | 					|| inputs instanceof runner.FinalizableQueue) ? | 
					
						
							| 
									
										
										
										
											2020-12-16 03:50:06 +03:00
										 |  |  | 				inputs.then( | 
					
						
							|  |  |  | 					function(items){ | 
					
						
							|  |  |  | 						return run([items, ...args]) }, | 
					
						
							|  |  |  | 					function(){ | 
					
						
							|  |  |  | 						q && q.abort() }) | 
					
						
							| 
									
										
										
										
											2020-12-15 05:36:37 +03:00
										 |  |  | 				: run(inputs) }), | 
					
						
							| 
									
										
										
										
											2020-12-02 06:12:39 +03:00
										 |  |  |    		{ | 
					
						
							| 
									
										
										
										
											2020-12-04 14:38:16 +03:00
										 |  |  | 			title, | 
					
						
							| 
									
										
										
										
											2020-12-17 23:19:42 +03:00
										 |  |  | 			arg_handler, | 
					
						
							|  |  |  | 			handler: func, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-02 06:12:39 +03:00
										 |  |  | 			toString: function(){ | 
					
						
							| 
									
										
										
										
											2021-01-12 04:41:49 +03:00
										 |  |  | 				// XXX add opts if given...
 | 
					
						
							| 
									
										
										
										
											2020-12-17 18:57:14 +03:00
										 |  |  | 				return `core.queueHandler('${action.name}',\n${  | 
					
						
							| 
									
										
										
										
											2020-12-06 04:39:42 +03:00
										 |  |  | 					(arg_handler ? | 
					
						
							| 
									
										
										
										
											2020-12-17 18:57:14 +03:00
										 |  |  | 						object.normalizeIndent('\t'+arg_handler.toString()).indent('\t') + ',\n' | 
					
						
							| 
									
										
										
										
											2020-12-06 04:39:42 +03:00
										 |  |  | 						: '') | 
					
						
							| 
									
										
										
										
											2020-12-17 18:57:14 +03:00
										 |  |  | 					+ object.normalizeIndent('\t'+func.toString()).indent('\t') })` },
 | 
					
						
							| 
									
										
										
										
											2020-12-02 06:12:39 +03:00
										 |  |  | 		}) } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | var sessionQueueHandler = | 
					
						
							|  |  |  | module.sessionQueueHandler = | 
					
						
							| 
									
										
										
										
											2020-12-04 14:38:16 +03:00
										 |  |  | function(title, func){ | 
					
						
							| 
									
										
										
										
											2020-11-26 00:44:46 +03:00
										 |  |  | 	return object.mixin( | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | 		queueHandler(...arguments), | 
					
						
							| 
									
										
										
										
											2020-11-26 00:44:46 +03:00
										 |  |  | 		{ __session_task__: true }) } | 
					
						
							| 
									
										
										
										
											2020-11-06 05:17:39 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | 
					
						
							| 
									
										
										
										
											2016-04-27 08:59:13 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-03 20:58:39 +03:00
										 |  |  | // XXX revise logging and logger passing...
 | 
					
						
							| 
									
										
										
										
											2020-11-27 01:25:31 +03:00
										 |  |  | // XXX add a task manager UI...
 | 
					
						
							| 
									
										
										
										
											2020-12-07 03:00:20 +03:00
										 |  |  | // XXX might be a good idea to confirm session task stops when loading a 
 | 
					
						
							|  |  |  | // 		new index...
 | 
					
						
							| 
									
										
										
										
											2016-04-27 08:59:13 +03:00
										 |  |  | var TaskActions = actions.Actions({ | 
					
						
							| 
									
										
										
										
											2020-12-27 12:07:48 +03:00
										 |  |  | 	config: { | 
					
						
							|  |  |  | 		'context-exclude-attrs': [ | 
					
						
							|  |  |  | 			'features', | 
					
						
							|  |  |  | 		], | 
					
						
							|  |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Tasks...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	isTask: function(action){ | 
					
						
							|  |  |  | 		return !!this.getActionAttr(action, '__task__') }, | 
					
						
							|  |  |  | 	isSessionTask: function(action){ | 
					
						
							|  |  |  | 		return !!this.getActionAttr(action, '__session_task__') }, | 
					
						
							|  |  |  | 	// list actions that generate tasks...
 | 
					
						
							|  |  |  | 	// XXX cache these???
 | 
					
						
							|  |  |  | 	get taskActions(){ | 
					
						
							| 
									
										
										
										
											2020-12-28 06:41:06 +03:00
										 |  |  | 		return this.cache('taskActions', function(data){ | 
					
						
							|  |  |  | 			return data  | 
					
						
							|  |  |  | 				|| this.actions.filter(this.isTask.bind(this)) }) }, | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | 	get sessionTaskActions(){ | 
					
						
							| 
									
										
										
										
											2020-12-28 06:41:06 +03:00
										 |  |  | 		return this.cache('sessionTaskActions', function(data){ | 
					
						
							|  |  |  | 			return data  | 
					
						
							|  |  |  | 				|| this.actions.filter(this.isSessionTask.bind(this)) }) }, | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// task manager...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	__task_manager__: runner.TaskManager, | 
					
						
							|  |  |  | 	__tasks: null, | 
					
						
							|  |  |  | 	get tasks(){ | 
					
						
							|  |  |  | 		return (this.__tasks =  | 
					
						
							|  |  |  | 			this.__tasks  | 
					
						
							|  |  |  | 				|| this.__task_manager__()) }, | 
					
						
							|  |  |  | 	// session tasks are stopped when the index is cleared...
 | 
					
						
							| 
									
										
										
										
											2020-12-04 06:16:54 +03:00
										 |  |  | 	// XXX need to get running tasks by action name...
 | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | 	get sessionTasks(){ | 
					
						
							| 
									
										
										
										
											2020-12-16 03:50:06 +03:00
										 |  |  | 		//return this.tasks.titled(...this.sessionTaskActions) },
 | 
					
						
							| 
									
										
										
										
											2020-12-28 06:41:06 +03:00
										 |  |  | 		return this.cache('sessionTasks', function(data){ | 
					
						
							|  |  |  | 			return data  | 
					
						
							|  |  |  | 				|| this.tasks | 
					
						
							|  |  |  | 					.filter(function(task){ | 
					
						
							|  |  |  | 						return task.__session_task__ }) }) }, | 
					
						
							| 
									
										
										
										
											2016-04-27 08:59:13 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | 	// Queue (task)...
 | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	isQueued: function(action){ | 
					
						
							|  |  |  | 		return !!this.getActionAttr(action, '__queued__') }, | 
					
						
							|  |  |  | 	// XXX cache this???
 | 
					
						
							| 
									
										
										
										
											2020-12-04 06:16:54 +03:00
										 |  |  | 	// XXX need to get running tasks by action name...
 | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 	get queuedActions(){ | 
					
						
							| 
									
										
										
										
											2020-12-28 06:41:06 +03:00
										 |  |  | 		return this.cache('queuedActions', function(data){ | 
					
						
							|  |  |  | 			return data  | 
					
						
							|  |  |  | 				|| this.actions.filter(this.isQueued.bind(this)) }) }, | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// XXX need a way to reference the queue again...
 | 
					
						
							|  |  |  | 	// 		.tasks.titled(name) will return a list...
 | 
					
						
							|  |  |  | 	__queues: null, | 
					
						
							|  |  |  | 	get queues(){ | 
					
						
							|  |  |  | 		return (this.__queues = this.__queues || {}) }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-06 05:48:31 +03:00
										 |  |  | 	// XXX test hidden progress...
 | 
					
						
							| 
									
										
										
										
											2020-12-02 18:49:45 +03:00
										 |  |  | 	// XXX revise logging and logger passing...
 | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 	// XXX need better error flow...
 | 
					
						
							|  |  |  | 	queue: doc('Get or create a queue task', | 
					
						
							|  |  |  | 		doc`Get or create a queue task...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			.queue(name) | 
					
						
							| 
									
										
										
										
											2020-12-02 18:49:45 +03:00
										 |  |  | 			.queue(name, options) | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 				-> queue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		If a queue with the given name already exits it will be returned  | 
					
						
							|  |  |  | 		and options and logger are ignored. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		options format: | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				nonAbortable: <bool>, | 
					
						
							|  |  |  | 				quiet: <bool>, | 
					
						
							| 
									
										
										
										
											2020-12-06 05:48:31 +03:00
										 |  |  | 				hideProgress: <bool>, | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 				... | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | 		NOTE: when a task queue is stopped it will clear and cleanup, this is  | 
					
						
							|  |  |  | 			different to how normal queue behaves. | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 		NOTE: for queue-specific options see ig-types/runner's Queue(..) | 
					
						
							|  |  |  | 		`,
 | 
					
						
							| 
									
										
										
										
											2020-12-02 18:49:45 +03:00
										 |  |  | 		function(name, options){ | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 			var that = this | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var queue = this.queues[name] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// create a new queue...
 | 
					
						
							|  |  |  | 			if(queue == null){ | 
					
						
							|  |  |  | 				var abort = function(){ | 
					
						
							|  |  |  | 					options.nonAbortable | 
					
						
							| 
									
										
										
										
											2020-12-16 03:50:06 +03:00
										 |  |  | 						|| queue | 
					
						
							| 
									
										
										
										
											2020-12-16 22:40:10 +03:00
										 |  |  | 							.abort() } | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 				var cleanup = function(){ | 
					
						
							|  |  |  | 					return function(){ | 
					
						
							| 
									
										
										
										
											2020-12-03 04:03:20 +03:00
										 |  |  | 						queue.stop() | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 						// XXX handle error state...
 | 
					
						
							|  |  |  | 						//logger
 | 
					
						
							|  |  |  | 						//	&& logger.emit('close')
 | 
					
						
							|  |  |  | 						delete that.queues[name] } } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-02 18:49:45 +03:00
										 |  |  | 				options = options || {} | 
					
						
							|  |  |  | 				var logger = options.logger || this.logger | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 				//logger = logger && logger.push(name)
 | 
					
						
							|  |  |  | 				logger = logger  | 
					
						
							|  |  |  | 					&& logger.push(name, {onclose: abort, quiet: !!options.quiet}) | 
					
						
							| 
									
										
										
										
											2020-12-02 18:49:45 +03:00
										 |  |  | 				logger  | 
					
						
							|  |  |  | 					&& (options.logger = logger) | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				queue = this.queues[name] =  | 
					
						
							| 
									
										
										
										
											2020-12-16 22:40:10 +03:00
										 |  |  | 					runner.FinalizableQueue(options || {}) | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				// setup logging...
 | 
					
						
							| 
									
										
										
										
											2020-12-06 05:48:31 +03:00
										 |  |  | 				var suffix = (options || {}).hideProgress ?  | 
					
						
							|  |  |  | 					' (hidden)'  | 
					
						
							|  |  |  | 					: '' | 
					
						
							| 
									
										
										
										
											2020-12-02 18:49:45 +03:00
										 |  |  | 				queue | 
					
						
							|  |  |  | 					.on('tasksAdded', function(evt, t){  | 
					
						
							| 
									
										
										
										
											2020-12-06 05:48:31 +03:00
										 |  |  | 						this.logger && this.logger.emit('added'+suffix, t) }) | 
					
						
							| 
									
										
										
										
											2020-12-21 05:29:17 +03:00
										 |  |  | 					// NOTE: t can be anything including an array, so to 
 | 
					
						
							|  |  |  | 					// 		avoid confusion we wrap it in an array this 
 | 
					
						
							|  |  |  | 					// 		one call means one emit...
 | 
					
						
							|  |  |  | 					.on('taskCompleted', function(evt, t, r){  | 
					
						
							|  |  |  | 						this.logger && this.logger.emit('done'+suffix, [t]) })  | 
					
						
							| 
									
										
										
										
											2020-12-02 18:49:45 +03:00
										 |  |  | 					.on('taskFailed', function(evt, t, err){  | 
					
						
							| 
									
										
										
										
											2020-12-06 05:48:31 +03:00
										 |  |  | 						this.logger && this.logger.emit('skipped'+suffix, t, err) })  | 
					
						
							| 
									
										
										
										
											2020-12-03 20:51:54 +03:00
										 |  |  | 					.on('stop', function(){ | 
					
						
							| 
									
										
										
										
											2020-12-16 22:40:10 +03:00
										 |  |  | 						this.logger && this.logger.emit('reset') }) | 
					
						
							| 
									
										
										
										
											2020-12-16 03:50:06 +03:00
										 |  |  | 					.on('abort', function(){ | 
					
						
							| 
									
										
										
										
											2020-12-16 22:40:10 +03:00
										 |  |  | 						this.logger && this.logger.emit('reset') }) | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 				// cleanup...
 | 
					
						
							|  |  |  | 				queue | 
					
						
							|  |  |  | 					.then( | 
					
						
							| 
									
										
										
										
											2020-12-04 06:16:54 +03:00
										 |  |  | 						cleanup('done'),  | 
					
						
							| 
									
										
										
										
											2020-12-02 03:32:53 +03:00
										 |  |  | 						cleanup('error')) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// add queue as task...
 | 
					
						
							|  |  |  | 			this.tasks.includes(queue) | 
					
						
							|  |  |  | 				|| this.tasks.Task(name, queue)  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return queue }), | 
					
						
							| 
									
										
										
										
											2020-12-19 14:59:34 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-22 05:18:33 +03:00
										 |  |  | 	// contexts (XXX EXPERIMENTAL)
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX would be nice to have a context manager:
 | 
					
						
							|  |  |  | 	// 		- context id's (index? ...sparse array?)
 | 
					
						
							|  |  |  | 	// 		- manager API
 | 
					
						
							|  |  |  | 	// 			- create/remove
 | 
					
						
							|  |  |  | 	// 		- context api (feature) 
 | 
					
						
							|  |  |  | 	// 			.then(..)/.catch(..)/.finally(..)
 | 
					
						
							|  |  |  | 	// XXX is peer stuff just a special context???
 | 
					
						
							|  |  |  | 	// 		...feels like yes
 | 
					
						
							|  |  |  | 	// XXX is context manager a special case of task manager???
 | 
					
						
							|  |  |  | 	// XXX move to a separate feature...
 | 
					
						
							| 
									
										
										
										
											2020-12-26 19:04:47 +03:00
										 |  |  | 	__contexts: null, | 
					
						
							| 
									
										
										
										
											2020-12-22 05:18:33 +03:00
										 |  |  | 	get contexts(){}, | 
					
						
							| 
									
										
										
										
											2020-12-27 12:07:48 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// XXX this should delete the clone when done...
 | 
					
						
							|  |  |  | 	// XXX need a common context API to make management possible...
 | 
					
						
							|  |  |  | 	ContextTask: ['- System/', | 
					
						
							|  |  |  | 		doc``, | 
					
						
							|  |  |  | 		function(type, action, ...args){ | 
					
						
							|  |  |  | 			var that = this | 
					
						
							|  |  |  | 			var context = this[type] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var res = context[action](...args) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var cleanup = function(){ | 
					
						
							|  |  |  | 				// XXX
 | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-29 02:49:07 +03:00
										 |  |  | 			res.finally ? | 
					
						
							| 
									
										
										
										
											2020-12-27 12:07:48 +03:00
										 |  |  | 				res.finally(cleanup) | 
					
						
							|  |  |  | 				: cleanup() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return res === context ?  | 
					
						
							|  |  |  | 				undefined  | 
					
						
							|  |  |  | 				: res }], | 
					
						
							| 
									
										
										
										
											2020-12-19 14:59:34 +03:00
										 |  |  | 	 | 
					
						
							| 
									
										
										
										
											2020-12-26 19:04:47 +03:00
										 |  |  | 	// Links...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2020-12-27 12:07:48 +03:00
										 |  |  | 	// XXX after this is stabilized, do we need session tasks and its complexities??? 
 | 
					
						
							| 
									
										
										
										
											2020-12-26 19:04:47 +03:00
										 |  |  | 	__links: null, | 
					
						
							|  |  |  | 	get links(){ | 
					
						
							|  |  |  | 		var links = this.__linked = this.__linked || {} | 
					
						
							|  |  |  | 		// remove 'current' if it does not match the current index...
 | 
					
						
							|  |  |  | 		// XXX revise the test...
 | 
					
						
							|  |  |  | 		var c = links.current | 
					
						
							|  |  |  | 		if(c && (c.data !== this.data || c.images !== this.images)){ | 
					
						
							|  |  |  | 			links.previous = c | 
					
						
							|  |  |  | 			delete links.current } | 
					
						
							|  |  |  | 		return links }, | 
					
						
							|  |  |  | 	get linked(){ | 
					
						
							|  |  |  | 		return this.link() }, | 
					
						
							|  |  |  | 	link: ['- System/', | 
					
						
							|  |  |  | 		doc`Get/create links...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Get/create link to current state... | 
					
						
							|  |  |  | 			.link() | 
					
						
							|  |  |  | 			.link('current') | 
					
						
							|  |  |  | 				-> current-link | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Get link to previous state if present... | 
					
						
							|  |  |  | 			.link('previous') | 
					
						
							|  |  |  | 				-> previous-link | 
					
						
							|  |  |  | 				-> undefined | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Get/create a titled link... | 
					
						
							|  |  |  | 			.link(title) | 
					
						
							|  |  |  | 				-> link | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		A link is a separate ImageGrid instance that links to the parent's | 
					
						
							|  |  |  | 		state and explicitly disabled ui features. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		A link will reflect the data changes but when the main index is  | 
					
						
							|  |  |  | 		cleared or reloaded it will retain the old data. | 
					
						
							|  |  |  | 		Care must be taken as this is true in both directions and changes  | 
					
						
							|  |  |  | 		to link state are reflected on the link .parent, this is useful | 
					
						
							|  |  |  | 		when updating state in the background but can bite the user if not | 
					
						
							|  |  |  | 		used carefully.  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		This effectively enables us to isolate a context for long running  | 
					
						
							|  |  |  | 		actions/tasks and make them independent of the main state. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		Example: | 
					
						
							|  |  |  | 			ig.linked.readAllMetadata() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		NOTE: links are relatively cheap as almost no data is copied but | 
					
						
							|  |  |  | 			they can be a source of a memory "leak" if not cleaned out  | 
					
						
							|  |  |  | 			as they prevent data from being garbage collected... | 
					
						
							|  |  |  | 		NOTE: 'current' and 'previous' links are reserved. | 
					
						
							|  |  |  | 		NOTE: 'previous' are a special case as they can not be created  | 
					
						
							|  |  |  | 			via .link(..). | 
					
						
							|  |  |  | 		`,
 | 
					
						
							|  |  |  | 		function(title='current'){ | 
					
						
							|  |  |  | 			var that = this | 
					
						
							|  |  |  | 			var links = this.links | 
					
						
							|  |  |  | 			// get link already created...
 | 
					
						
							|  |  |  | 			// NOTE: 'current' and 'previous' links are handled by the 
 | 
					
						
							|  |  |  | 			// 		.links prop...
 | 
					
						
							|  |  |  | 			var link = links[title] | 
					
						
							|  |  |  | 			if(link){ | 
					
						
							|  |  |  | 				return link } | 
					
						
							|  |  |  | 			// prevent creating previous links...
 | 
					
						
							|  |  |  | 			if(title == 'previous'){ | 
					
						
							|  |  |  | 				return actions.UNDEFINED } | 
					
						
							|  |  |  | 			// create a link...
 | 
					
						
							|  |  |  | 			// NOTE: we intentionally disable ui here and do not trigger .start()...
 | 
					
						
							|  |  |  | 			return (links[title] =  | 
					
						
							|  |  |  | 				Object.assign( | 
					
						
							|  |  |  | 					// XXX add a 'link' feature...
 | 
					
						
							|  |  |  | 					ImageGridFeatures.setup([ | 
					
						
							|  |  |  | 						...this.features.input,  | 
					
						
							|  |  |  | 						'-ui', | 
					
						
							| 
									
										
										
										
											2020-12-27 12:07:48 +03:00
										 |  |  | 						'link-context', | 
					
						
							| 
									
										
										
										
											2020-12-26 19:04:47 +03:00
										 |  |  | 					]), | 
					
						
							| 
									
										
										
										
											2020-12-27 12:07:48 +03:00
										 |  |  | 					// NOTE: this can shadow parts of the new base object 
 | 
					
						
							|  |  |  | 					// 		so we'll need to exclude some stuff...
 | 
					
						
							|  |  |  | 					Object.assign({}, this) | 
					
						
							|  |  |  | 						.run(function(){ | 
					
						
							|  |  |  | 							// remove excluded attrs...
 | 
					
						
							|  |  |  | 							;(that.config['context-exclude-attrs']  | 
					
						
							|  |  |  | 									|| [ 'features' ]) | 
					
						
							|  |  |  | 								.forEach(function(key){ | 
					
						
							|  |  |  | 									delete this[key] }.bind(this)) }), | 
					
						
							| 
									
										
										
										
											2020-12-26 19:04:47 +03:00
										 |  |  | 					{ | 
					
						
							|  |  |  | 						// link metadata...
 | 
					
						
							|  |  |  | 						parent: this, | 
					
						
							|  |  |  | 						title: title, | 
					
						
							|  |  |  | 						// XXX change this when data/images changes...
 | 
					
						
							|  |  |  | 						// 		...a prop in the link feature...
 | 
					
						
							|  |  |  | 						type: 'link', | 
					
						
							|  |  |  | 						// link configuration...
 | 
					
						
							|  |  |  | 						logger: that.logger | 
					
						
							|  |  |  | 							.push(`Linked ${ Object.keys(links).length }`), | 
					
						
							|  |  |  | 					})) }], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-19 14:59:34 +03:00
										 |  |  | 	// XXX would be nice to have an ability to partially clone the instance...
 | 
					
						
							|  |  |  | 	// 		...currently we can do a full clone and remove things we do 
 | 
					
						
							|  |  |  | 	// 		not want but that still takes time and memory...
 | 
					
						
							| 
									
										
										
										
											2020-12-20 06:12:47 +03:00
										 |  |  | 	// XXX this does not copy aliases...
 | 
					
						
							|  |  |  | 	// XXX might be a good idea to add a 'IsolatedTask' feature/mixin to
 | 
					
						
							|  |  |  | 	// 		handle cleanup (via .done() action)
 | 
					
						
							|  |  |  | 	// XXX should this be a prop -- .isolated???
 | 
					
						
							|  |  |  | 	__isolated: null, | 
					
						
							|  |  |  | 	get isolated(){ | 
					
						
							|  |  |  | 		return (this.__isolated = this.__isolated || []) }, | 
					
						
							| 
									
										
										
										
											2020-12-19 14:59:34 +03:00
										 |  |  | 	isolate: ['- System/', | 
					
						
							|  |  |  | 		function(){ | 
					
						
							| 
									
										
										
										
											2020-12-20 06:12:47 +03:00
										 |  |  | 			var clones = this.isolated | 
					
						
							| 
									
										
										
										
											2020-12-19 14:59:34 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			var clone = this.clone(true) | 
					
						
							|  |  |  | 			// reset actions to exclude UI...
 | 
					
						
							|  |  |  | 			clone.__proto__ = ImageGridFeatures.setup([...this.features.input, '-ui']) | 
					
						
							| 
									
										
										
										
											2020-12-20 06:12:47 +03:00
										 |  |  | 			clone.parent = this | 
					
						
							| 
									
										
										
										
											2020-12-19 14:59:34 +03:00
										 |  |  | 			// link clone in...
 | 
					
						
							|  |  |  | 			clone.logger = this.logger.push(['Task', clones.length].join(' ')) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-22 05:18:33 +03:00
										 |  |  | 			clone.context_id = clones.push(clone) | 
					
						
							| 
									
										
										
										
											2020-12-19 14:59:34 +03:00
										 |  |  | 			return clone }], | 
					
						
							| 
									
										
										
										
											2020-12-20 06:12:47 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-27 08:59:13 +03:00
										 |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Tasks =  | 
					
						
							|  |  |  | module.Tasks = ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: '', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'tasks', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	depends: [ ], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	actions: TaskActions, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	handlers: [ | 
					
						
							| 
									
										
										
										
											2020-12-03 20:58:39 +03:00
										 |  |  | 		// stop session tasks...
 | 
					
						
							| 
									
										
										
										
											2020-11-26 00:44:46 +03:00
										 |  |  | 		['clear', | 
					
						
							| 
									
										
										
										
											2020-12-16 03:50:06 +03:00
										 |  |  | 			// XXX BUG: for some reason calling .abort here does not work...
 | 
					
						
							|  |  |  | 			//'sessionTasks.stop'],
 | 
					
						
							|  |  |  | 			'sessionTasks.abort'], | 
					
						
							| 
									
										
										
										
											2016-04-27 08:59:13 +03:00
										 |  |  | 	], | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-11 22:32:16 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | // Self test framework...
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-10 14:09:01 +03:00
										 |  |  | // Indicate an action to be a self-test action...
 | 
					
						
							|  |  |  | // 
 | 
					
						
							|  |  |  | // Self test actions are run by .selfTest(..)
 | 
					
						
							|  |  |  | // 
 | 
					
						
							|  |  |  | // XXX should we set an action attr or a func attr here???
 | 
					
						
							| 
									
										
										
										
											2017-01-11 22:32:16 +03:00
										 |  |  | var selfTest = | 
					
						
							|  |  |  | module.selfTest = function(func){ | 
					
						
							|  |  |  | 	func.__self_test__ = true | 
					
						
							| 
									
										
										
										
											2020-12-30 16:54:23 +03:00
										 |  |  | 	return func } | 
					
						
							| 
									
										
										
										
											2017-01-11 22:32:16 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | var SelfTestActions = actions.Actions({ | 
					
						
							|  |  |  | 	config: { | 
					
						
							|  |  |  | 		'run-selftest-on-start': true, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-10 14:09:01 +03:00
										 |  |  | 	selfTest: ['System/Run self test', | 
					
						
							| 
									
										
										
										
											2017-01-11 22:32:16 +03:00
										 |  |  | 		selfTest(function(mode){ | 
					
						
							|  |  |  | 			var that = this | 
					
						
							|  |  |  | 			var logger = this.logger && this.logger.push('Self test') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var tests = this.actions | 
					
						
							|  |  |  | 				.filter(function(action){  | 
					
						
							| 
									
										
										
										
											2017-08-10 14:09:01 +03:00
										 |  |  | 					return action != 'selfTest' | 
					
						
							| 
									
										
										
										
											2017-01-11 22:32:16 +03:00
										 |  |  | 			   			&& (that[action].func.__self_test__  | 
					
						
							|  |  |  | 							|| that.getActionAttr(action, 'self_test'))}) | 
					
						
							|  |  |  | 			logger  | 
					
						
							|  |  |  | 				&& tests.forEach(function(action){  | 
					
						
							|  |  |  | 					logger.emit('found', action) }) | 
					
						
							|  |  |  | 			tests.forEach(function(action){ | 
					
						
							|  |  |  | 				that[action]() | 
					
						
							| 
									
										
										
										
											2017-12-09 01:39:06 +03:00
										 |  |  | 				logger  | 
					
						
							| 
									
										
										
										
											2020-12-30 16:54:23 +03:00
										 |  |  | 					&& logger.emit('done', action) }) })], | 
					
						
							| 
									
										
										
										
											2017-01-11 22:32:16 +03:00
										 |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var SelfTest =  | 
					
						
							|  |  |  | module.SelfTest = ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: '', | 
					
						
							|  |  |  | 	doc: '', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'self-test', | 
					
						
							|  |  |  | 	depends: [ | 
					
						
							|  |  |  | 		'lifecycle'	 | 
					
						
							|  |  |  | 	], | 
					
						
							| 
									
										
										
										
											2020-04-11 01:09:49 +03:00
										 |  |  | 	suggested: [ | 
					
						
							|  |  |  | 		'logger', | 
					
						
							|  |  |  | 	], | 
					
						
							| 
									
										
										
										
											2017-01-11 22:32:16 +03:00
										 |  |  | 	priority: 'low', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	actions: SelfTestActions,  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	handlers: [ | 
					
						
							|  |  |  | 		['start', | 
					
						
							|  |  |  | 			function(){  | 
					
						
							|  |  |  | 				this.config['run-selftest-on-start']  | 
					
						
							| 
									
										
										
										
											2017-08-10 14:09:01 +03:00
										 |  |  | 					&& this.selfTest() }] | 
					
						
							| 
									
										
										
										
											2017-01-11 22:32:16 +03:00
										 |  |  | 	], | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | /********************************************************************** | 
					
						
							| 
									
										
										
										
											2016-08-20 22:49:36 +03:00
										 |  |  | * vim:set ts=4 sw=4 :                               */ return module }) |