| 
									
										
										
										
											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: | 
					
						
							| 
									
										
										
										
											2017-01-26 21:49:32 +03:00
										 |  |  | * 	- introspection | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | * 	- lifecycle | 
					
						
							|  |  |  | * 		base life-cycle events (start/stop/..) | 
					
						
							| 
									
										
										
										
											2017-08-27 22:17:47 +03:00
										 |  |  | *	- serialization | 
					
						
							|  |  |  | *		base methods to handle loading, serialization and cloning... | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | * 	- util | 
					
						
							|  |  |  | * 	- 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 | 
					
						
							|  |  |  | * 		XXX not yet used | 
					
						
							| 
									
										
										
										
											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??? | 
					
						
							| 
									
										
										
										
											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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*********************************************************************/ | 
					
						
							| 
									
										
										
										
											2016-05-01 21:54:44 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-07 18:21:32 +03:00
										 |  |  | // Make a protocol implementation action...
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // For more docs see: docs for actions.js and .chainApply(..)
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // XXX might be good to move this to actions.js
 | 
					
						
							| 
									
										
										
										
											2016-11-08 17:27:27 +03:00
										 |  |  | // XXX might also be a good idea to mark the actual protocol definition
 | 
					
						
							|  |  |  | // 		and not just the implementation...
 | 
					
						
							| 
									
										
										
										
											2016-11-07 18:21:32 +03:00
										 |  |  | var protocol = | 
					
						
							| 
									
										
										
										
											2016-05-01 21:54:44 +03:00
										 |  |  | module.protocol = function(protocol, func){ | 
					
						
							|  |  |  | 	return function(){ | 
					
						
							| 
									
										
										
										
											2016-11-07 18:21:32 +03:00
										 |  |  | 		return this[protocol].chainApply(this, func, arguments) | 
					
						
							| 
									
										
										
										
											2016-05-01 21:54:44 +03:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-07 18:21:32 +03:00
										 |  |  | // NOTE: if no toggler state is set this assumes that the first state 
 | 
					
						
							|  |  |  | // 		is the default...
 | 
					
						
							| 
									
										
										
										
											2016-11-26 14:29:11 +03:00
										 |  |  | // NOTE: default states is [false, true]
 | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | var makeConfigToggler =  | 
					
						
							|  |  |  | module.makeConfigToggler =  | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | function(attr, states, a, b){ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-26 14:29:11 +03:00
										 |  |  | 	states = states || [false, true] | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 	var pre = a | 
					
						
							| 
									
										
										
										
											2016-11-20 00:04:41 +03:00
										 |  |  | 	// XXX is this a good default???
 | 
					
						
							|  |  |  | 	//var post = b || function(action){ action != null && this.focusImage() }
 | 
					
						
							|  |  |  | 	var post = b | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | 	return toggler.Toggler(null, | 
					
						
							|  |  |  | 		function(_, action){ | 
					
						
							| 
									
										
										
										
											2017-09-04 22:53:29 +03:00
										 |  |  | 			var lst = states instanceof Array ? states  | 
					
						
							| 
									
										
										
										
											2016-11-26 14:29:11 +03:00
										 |  |  | 				: states instanceof Function ? states.call(this) | 
					
						
							|  |  |  | 				: states | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			if(action == null){ | 
					
						
							| 
									
										
										
										
											2016-11-26 14:29:11 +03:00
										 |  |  | 				var cfg = this.config[attr] | 
					
						
							|  |  |  | 				return cfg == null ?  | 
					
						
							|  |  |  | 					(lst[lst.indexOf('none')] || lst[0]) | 
					
						
							|  |  |  | 					: cfg  | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				this.config[attr] = action | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 		states, pre, post) | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +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 = { | 
					
						
							|  |  |  | 	// Test if the action is a Toggler...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	isToggler: actions.doWithRootAction(function(action){ | 
					
						
							|  |  |  | 		return action instanceof toggler.Toggler }), | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 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), | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}), | 
					
						
							|  |  |  | }  | 
					
						
							|  |  |  | ImageGridMetaActions.__proto__ = actions.MetaActions | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-27 18:56:08 +03:00
										 |  |  | var ImageGrid =  | 
					
						
							| 
									
										
										
										
											2016-05-27 23:04:15 +03:00
										 |  |  | module.ImageGrid =  | 
					
						
							| 
									
										
										
										
											2016-08-21 01:21:03 +03:00
										 |  |  | 	object.makeConstructor('ImageGrid', ImageGridMetaActions) | 
					
						
							| 
									
										
										
										
											2016-05-27 18:41:28 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Root ImageGrid feature set....
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | var ImageGridFeatures = | 
					
						
							| 
									
										
										
										
											2016-05-27 18:41:28 +03:00
										 |  |  | module.ImageGridFeatures = new features.FeatureSet() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // setup base instance constructor...
 | 
					
						
							| 
									
										
										
										
											2016-05-27 18:56:08 +03:00
										 |  |  | ImageGridFeatures.__actions__ =  | 
					
						
							|  |  |  | 	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...
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-09 18:39:01 +03:00
										 |  |  | // nw or node...
 | 
					
						
							| 
									
										
										
										
											2015-12-29 07:28:19 +03:00
										 |  |  | if(typeof(process) != 'undefined'){ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-16 23:18:19 +03:00
										 |  |  | 	// Electron...
 | 
					
						
							|  |  |  | 	if(process.versions['electron'] != null){ | 
					
						
							|  |  |  | 		ImageGridFeatures.runtime = 'electron' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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'){ | 
					
						
							| 
									
										
										
										
											2016-04-09 18:39:01 +03:00
										 |  |  | 		ImageGridFeatures.runtime = 'nw' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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 { | 
					
						
							|  |  |  | 		ImageGridFeatures.runtime = 'node' | 
					
						
							| 
									
										
										
										
											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)
 | 
					
						
							| 
									
										
										
										
											2016-04-09 18:39:01 +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...
 | 
					
						
							| 
									
										
										
										
											2015-12-31 10:37:21 +03:00
										 |  |  | } else if(typeof(window) != 'undefined'){ | 
					
						
							| 
									
										
										
										
											2015-12-29 07:28:19 +03:00
										 |  |  | 	ImageGridFeatures.runtime = 'browser' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // unknown...
 | 
					
						
							| 
									
										
										
										
											2016-04-09 18:39:01 +03:00
										 |  |  | // XXX do we need to detect chrome app???
 | 
					
						
							| 
									
										
										
										
											2015-12-29 07:28:19 +03:00
										 |  |  | } else { | 
					
						
							|  |  |  | 	ImageGridFeatures.runtime = 'unknown' | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | /*********************************************************************/ | 
					
						
							| 
									
										
										
										
											2017-01-26 07:01:20 +03:00
										 |  |  | // Introspection...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Normalize doc strings...
 | 
					
						
							|  |  |  | // 
 | 
					
						
							|  |  |  | // This will remove indent padding from all lines in a doc string.
 | 
					
						
							|  |  |  | // 
 | 
					
						
							|  |  |  | // This is useful for documenting actions using ES6 template/multi-line
 | 
					
						
							|  |  |  | // strings and keep them sane in terms of indent...
 | 
					
						
							|  |  |  | // 
 | 
					
						
							|  |  |  | // 	Example:
 | 
					
						
							|  |  |  | // 		someAction: ['Test/Some action title',
 | 
					
						
							|  |  |  | // 			core.doc`This is an example...
 | 
					
						
							|  |  |  | // 			mult-iline...
 | 
					
						
							|  |  |  | // 			...doc string that will be normalized and look the same but`
 | 
					
						
							|  |  |  | // 			without the indent...`,
 | 
					
						
							|  |  |  | // 			function(){ ... }]
 | 
					
						
							|  |  |  | // 			
 | 
					
						
							|  |  |  | // NOTE: this will ignore the first line's indent so it can be started 
 | 
					
						
							|  |  |  | // 		right at the string start.
 | 
					
						
							|  |  |  | var doc =  | 
					
						
							|  |  |  | module.doc = | 
					
						
							|  |  |  | function(strings, ...values){ | 
					
						
							|  |  |  | 	var lines = strings | 
					
						
							|  |  |  | 		.map(function(s, i){ return s + (values[i] || '') }) | 
					
						
							|  |  |  | 		.join('') | 
					
						
							|  |  |  | 		.split(/\n/g) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// get the common whitespae offset...
 | 
					
						
							|  |  |  | 	var l = -1 | 
					
						
							|  |  |  | 	lines.forEach(function(line, i){  | 
					
						
							|  |  |  | 		if(line.trim().length == 0){ | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// get the indent...
 | 
					
						
							|  |  |  | 		var a = line.length - line.trimLeft().length | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// if line 0 is not indented, ignore it...
 | 
					
						
							|  |  |  | 		if(i == 0 && a == 0){ | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		l = l < 0 ? a : Math.min(l, a) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return lines | 
					
						
							|  |  |  | 		.map(function(line, i){ return i == 0 ? line : line.slice(l) }) | 
					
						
							|  |  |  | 		.join('\n') | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // 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 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var IntrospectionActions = actions.Actions({ | 
					
						
							|  |  |  | 	// user-callable actions...
 | 
					
						
							|  |  |  | 	get useractions(){ | 
					
						
							|  |  |  | 		return this.actions.filter(this.isUserCallable.bind(this)) }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// check if action is callable by user...
 | 
					
						
							|  |  |  | 	isUserCallable: ['- System/', | 
					
						
							| 
									
										
										
										
											2017-02-17 06:57:32 +03:00
										 |  |  | 		doc`Test if an action is callable by user.
 | 
					
						
							| 
									
										
										
										
											2017-01-29 04:10:10 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			.isUserCallable(<action-name>) | 
					
						
							|  |  |  | 		`,
 | 
					
						
							| 
									
										
										
										
											2017-02-19 05:55:53 +03:00
										 |  |  | 		// XXX should this check only the root action or the whole set???
 | 
					
						
							| 
									
										
										
										
											2017-03-05 06:30:17 +03:00
										 |  |  | 		// 		...in other words: can we make an action non-user-callable
 | 
					
						
							|  |  |  | 		// 		anywhere other than the root action?
 | 
					
						
							|  |  |  | 		// 		IMO no...
 | 
					
						
							| 
									
										
										
										
											2017-02-19 05:55:53 +03:00
										 |  |  | 		//function(action){ 
 | 
					
						
							|  |  |  | 		//	return this.getActionAttr(action, '__not_user_callable__') != true }],
 | 
					
						
							| 
									
										
										
										
											2017-01-26 07:01:20 +03:00
										 |  |  | 		actions.doWithRootAction(function(action){ | 
					
						
							|  |  |  | 			return action.__not_user_callable__ != true })], | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Introspection =  | 
					
						
							|  |  |  | module.Introspection = ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: '', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'introspection', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	actions: IntrospectionActions, | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //---------------------------------------------------------------------
 | 
					
						
							| 
									
										
										
										
											2016-04-27 08:59:13 +03:00
										 |  |  | // System life-cycle...
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | // XXX should his have state???
 | 
					
						
							|  |  |  | // 		...if so, should this be a toggler???
 | 
					
						
							|  |  |  | var LifeCycleActions = actions.Actions({ | 
					
						
							|  |  |  | 	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() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		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. | 
					
						
							|  |  |  | 		`,
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 		function(){ | 
					
						
							|  |  |  | 			var that = this | 
					
						
							|  |  |  | 			this.logger && this.logger.emit('start') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// 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 { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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...
 | 
					
						
							|  |  |  | 			if(runtime == 'nw'){ | 
					
						
							|  |  |  | 				// 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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-29 07:28:19 +03:00
										 |  |  | 			// node.js...
 | 
					
						
							|  |  |  | 			} else if(runtime == 'node'){ | 
					
						
							|  |  |  | 				process.on('exit', stop) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// browser...
 | 
					
						
							|  |  |  | 			} 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
 | 
					
						
							| 
									
										
										
										
											2016-07-03 02:21:33 +03:00
										 |  |  | 				console.warn('Unknown runtime:', runtime) | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2017-01-26 07:01:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// handler ready event...
 | 
					
						
							|  |  |  | 			// ...if no one requested to do it.
 | 
					
						
							|  |  |  | 			if(this.__ready_announce_requested == null | 
					
						
							|  |  |  | 					|| this.__ready_announce_requested <= 0){ | 
					
						
							|  |  |  | 				if(runtime == 'nw'){ | 
					
						
							|  |  |  | 					$(function(){ that.declareReady() }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				} else if(runtime == 'node'){ | 
					
						
							|  |  |  | 					this.declareReady() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				} else if(runtime == 'browser'){ | 
					
						
							|  |  |  | 					$(function(){ that.declareReady() }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				} else { | 
					
						
							|  |  |  | 					this.declareReady() | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	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. | 
					
						
							|  |  |  | 		`,
 | 
					
						
							|  |  |  | 	   	notUserCallable(function(){ | 
					
						
							|  |  |  | 			// System ready event...
 | 
					
						
							|  |  |  | 			//
 | 
					
						
							|  |  |  | 			// Not intended for direct use, use .declareReady() to initiate.
 | 
					
						
							| 
									
										
										
										
											2017-01-26 21:49:32 +03:00
										 |  |  | 			this.logger && this.logger.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. | 
					
						
							|  |  |  | 		`,
 | 
					
						
							| 
									
										
										
										
											2017-01-26 07:01:20 +03:00
										 |  |  | 		function(){ | 
					
						
							|  |  |  | 			this.__ready_announce_requested | 
					
						
							|  |  |  | 				&& (this.__ready_announce_requested -= 1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if(!this.__ready_announce_requested  | 
					
						
							|  |  |  | 					|| this.__ready_announce_requested == 0){ | 
					
						
							|  |  |  | 				this.__ready = this.__ready || !!this.ready()  | 
					
						
							|  |  |  | 				delete this.__ready_announce_requested | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	requestReadyAnnounce: ['- System/', | 
					
						
							| 
									
										
										
										
											2017-01-29 04:10:10 +03:00
										 |  |  | 		doc`Request to announce the .ready() event.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			.requestReadyAnnounce() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		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. | 
					
						
							|  |  |  | 		`,
 | 
					
						
							| 
									
										
										
										
											2017-01-26 07:01:20 +03:00
										 |  |  | 		function(){ | 
					
						
							|  |  |  | 			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(){ | 
					
						
							|  |  |  | 			// browser & nw...
 | 
					
						
							|  |  |  | 			if(this.__stop_handler  | 
					
						
							|  |  |  | 					&& (this.runtime == 'browser' || this.runtime == 'nw')){ | 
					
						
							|  |  |  | 				$(window).off('beforeunload', this.__stop_handler) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// nw...
 | 
					
						
							|  |  |  | 			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...
 | 
					
						
							|  |  |  | 			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 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			this.logger && this.logger.emit('stop') | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var LifeCycle =  | 
					
						
							|  |  |  | module.LifeCycle = ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: '', | 
					
						
							|  |  |  | 	doc: '', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'lifecycle', | 
					
						
							| 
									
										
										
										
											2016-01-03 04:49:00 +03:00
										 |  |  | 	priority: 'high', | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	actions: LifeCycleActions, | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-27 22:17:47 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var SerializationActions = actions.Actions({ | 
					
						
							|  |  |  | 	clone: ['- System/', | 
					
						
							|  |  |  | 		function(full){ return actions.MetaActions.clone.call(this, full) }], | 
					
						
							|  |  |  | 	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, | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-20 03:51:53 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var UtilActions = actions.Actions({ | 
					
						
							|  |  |  | 	mergeConfig: ['- System/',  | 
					
						
							| 
									
										
										
										
											2017-01-29 04:10:10 +03:00
										 |  |  | 		doc`Merge a config object into .config
 | 
					
						
							|  |  |  | 		`,
 | 
					
						
							| 
									
										
										
										
											2016-11-20 03:51:53 +03:00
										 |  |  | 		function(config){ | 
					
						
							|  |  |  | 			config = config instanceof Function ? config.call(this) | 
					
						
							|  |  |  | 				: typeof(config) == typeof('str') ? this.config[config] | 
					
						
							|  |  |  | 				: config | 
					
						
							|  |  |  | 			var that = this | 
					
						
							|  |  |  | 			Object.keys(config).forEach(function(key){ | 
					
						
							|  |  |  | 				that.config[key] = config[key] | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Util =  | 
					
						
							|  |  |  | module.Util = ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: '', | 
					
						
							|  |  |  | 	doc: '', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'util', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	actions: UtilActions, | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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
 | 
					
						
							|  |  |  | // 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, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	updateJournalableActions: ['System/Update list of journalable actions', | 
					
						
							|  |  |  | 		function(){ | 
					
						
							|  |  |  | 			var that = this | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var handler = function(action){ | 
					
						
							|  |  |  | 				return function(){ | 
					
						
							|  |  |  | 					var cur = this.current | 
					
						
							| 
									
										
										
										
											2017-05-15 17:26:30 +03:00
										 |  |  | 					var args = util.args2array(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 | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					// 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-01-03 02:55:25 +03:00
										 |  |  | 					return !!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)
 | 
					
						
							|  |  |  | 	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 | 
					
						
							|  |  |  | 		`,
 | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | 		{browseMode: function(){  | 
					
						
							|  |  |  | 			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') ?  | 
					
						
							|  |  |  | 							// pass journal structure as-is...
 | 
					
						
							|  |  |  | 							this[undo].apply(this, a) | 
					
						
							|  |  |  | 						: 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 | 
					
						
							|  |  |  | 		`,
 | 
					
						
							| 
									
										
										
										
											2017-01-02 01:23:33 +03:00
										 |  |  | 		{browseMode: function(){  | 
					
						
							|  |  |  | 			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
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | // Changes API... 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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.
 | 
					
						
							|  |  |  | 	chages: null, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	clone: [function(full){ | 
					
						
							|  |  |  | 			return function(res){ | 
					
						
							|  |  |  | 				res.changes = null | 
					
						
							|  |  |  | 				if(full && this.hasOwnProperty('changes') && this.changes){ | 
					
						
							|  |  |  | 					res.changes = JSON.parse(JSON.stringify(this.changes)) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	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>, .. ]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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  | 
					
						
							|  |  |  | 				: util.args2array(arguments) | 
					
						
							|  |  |  | 			//var changes = this.changes = 
 | 
					
						
							|  |  |  | 			var changes =  | 
					
						
							|  |  |  | 				this.hasOwnProperty('changes') ? | 
					
						
							|  |  |  | 					this.changes || {} | 
					
						
							|  |  |  | 					: {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// all...
 | 
					
						
							|  |  |  | 			if(args.length == 1 && args[0] == 'all'){ | 
					
						
							|  |  |  | 				// NOTE: this is better than delete as it will shadow 
 | 
					
						
							|  |  |  | 				// 		the parent's changes in case we got cloned from
 | 
					
						
							|  |  |  | 				// 		a live instance...
 | 
					
						
							|  |  |  | 				//delete this.changes
 | 
					
						
							|  |  |  | 				this.changes = null | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// 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-02-15 03:28:18 +03:00
										 |  |  | 				changes[section] = (changes[section] || []).concat(items).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, | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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] | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		callback && callback.call(this, workspace) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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') }], | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | 		['stop',  | 
					
						
							| 
									
										
										
										
											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
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | // Tasks...
 | 
					
						
							|  |  |  | // XXX should this be a separate module???
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var tasks = require('lib/tasks') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // XXX see if a protocol can be practical here to:
 | 
					
						
							|  |  |  | // 		- serialize/restore jobs
 | 
					
						
							|  |  |  | // 		- ...
 | 
					
						
							|  |  |  | var TaskActions = actions.Actions({ | 
					
						
							|  |  |  | 	config: { | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	get jobs(){ | 
					
						
							|  |  |  | 		return this.__jobs | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	getJob: ['- Jobs/', | 
					
						
							|  |  |  | 		function(name){ | 
					
						
							|  |  |  | 			name = name || this.data.newGid() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// get/init task dict...
 | 
					
						
							|  |  |  | 			var t = this.__jobs = this.__jobs || {} | 
					
						
							|  |  |  | 			// get/init task...
 | 
					
						
							|  |  |  | 			var job = t[name] = t[name] || tasks.Queue() | 
					
						
							|  |  |  | 			job.name = name | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return job | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX stop
 | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Tasks =  | 
					
						
							|  |  |  | module.Tasks = ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: '', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'tasks', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	depends: [ ], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	actions: TaskActions, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	handlers: [ | 
					
						
							|  |  |  | 		['start',  | 
					
						
							|  |  |  | 			function(){  | 
					
						
							|  |  |  | 				// XXX prepare for recovery and recover...
 | 
					
						
							|  |  |  | 			}], | 
					
						
							|  |  |  | 		['stop',  | 
					
						
							|  |  |  | 			function(){  | 
					
						
							|  |  |  | 				// XXX stop tasks and prepare for recovery...
 | 
					
						
							|  |  |  | 			}], | 
					
						
							|  |  |  | 	], | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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 | 
					
						
							|  |  |  | 	return func | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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]() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				logger.emit('done', action) | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		})], | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var SelfTest =  | 
					
						
							|  |  |  | module.SelfTest = ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: '', | 
					
						
							|  |  |  | 	doc: '', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'self-test', | 
					
						
							|  |  |  | 	depends: [ | 
					
						
							|  |  |  | 		'lifecycle'	 | 
					
						
							|  |  |  | 	], | 
					
						
							|  |  |  | 	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 }) |