| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | /********************************************************************** | 
					
						
							|  |  |  | *  | 
					
						
							|  |  |  | * All the base features... | 
					
						
							|  |  |  | * | 
					
						
							|  |  |  | * | 
					
						
							|  |  |  | **********************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | define(function(require){ var module = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //var DEBUG = DEBUG != null ? DEBUG : true
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var actions = require('lib/actions') | 
					
						
							|  |  |  | var features = require('lib/features') | 
					
						
							| 
									
										
										
										
											2015-12-31 10:37:21 +03:00
										 |  |  | var toggler = require('lib/toggler') | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | var data = require('data') | 
					
						
							|  |  |  | var images = require('images') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var core = require('features/core') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*********************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Helpers and meta stuff...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // mode can be:
 | 
					
						
							|  |  |  | // 	"ribbon"	- next marked in current ribbon (default)
 | 
					
						
							|  |  |  | // 	"all"		- next marked in sequence
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // XXX add support for tag lists...
 | 
					
						
							|  |  |  | var makeTagWalker = | 
					
						
							|  |  |  | module.makeTagWalker = | 
					
						
							|  |  |  | function(direction, dfl_tag){ | 
					
						
							|  |  |  | 	var meth = direction == 'next' ? 'nextImage' : 'prevImage' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return function(tag, mode){ | 
					
						
							|  |  |  | 		mode = mode == null ? 'all' : mode | 
					
						
							|  |  |  | 		tag = tag || dfl_tag | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// account for no tags or no images tagged...
 | 
					
						
							|  |  |  | 		var lst = this.data.tags != null ? this.data.tags[tag] : [] | 
					
						
							|  |  |  | 		lst = lst || [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if(mode == 'ribbon'){ | 
					
						
							|  |  |  | 			this[meth](this.data.getImages(lst, 'current')) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			this[meth](lst) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*********************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // XXX split this into read and write actions...
 | 
					
						
							|  |  |  | var BaseActions =  | 
					
						
							|  |  |  | module.BaseActions =  | 
					
						
							|  |  |  | actions.Actions({ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	config: { | 
					
						
							|  |  |  | 		// XXX should this be here???
 | 
					
						
							|  |  |  | 		version: 'gen4', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// see .direction for details...
 | 
					
						
							|  |  |  | 		'steps-to-change-direction': 3, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Determines the image selection mode when focusing or moving 
 | 
					
						
							|  |  |  | 		// between ribbons...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// supported modes:
 | 
					
						
							| 
									
										
										
										
											2015-12-19 09:34:22 +03:00
										 |  |  | 		//
 | 
					
						
							|  |  |  | 		// XXX should this be here???
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 		'ribbon-focus-modes': [ | 
					
						
							|  |  |  | 			'order',	// select image closest to current in order
 | 
					
						
							|  |  |  | 			'first',	// select first image
 | 
					
						
							|  |  |  | 			'last',		// select last image
 | 
					
						
							|  |  |  | 		], | 
					
						
							| 
									
										
										
										
											2015-12-20 04:29:44 +03:00
										 |  |  | 		'ribbon-focus-mode': 'order', | 
					
						
							| 
									
										
										
										
											2016-04-20 16:17:12 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	 | 
					
						
							|  |  |  | 	// XXX
 | 
					
						
							|  |  |  | 	get version(){ | 
					
						
							|  |  |  | 		return this.config.version | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// basic state...
 | 
					
						
							|  |  |  | 	// NOTE: the setters in the following use the appropriate actions
 | 
					
						
							|  |  |  | 	// 		so to avoid recursion do not use these in the specific 
 | 
					
						
							|  |  |  | 	// 		actions...
 | 
					
						
							|  |  |  | 	 | 
					
						
							|  |  |  | 	// Base ribbon...
 | 
					
						
							|  |  |  | 	get base(){ | 
					
						
							|  |  |  | 		return this.data == null ? null : this.data.base | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	set base(value){ | 
					
						
							|  |  |  | 		this.setBaseRibbon(value) | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Current image...
 | 
					
						
							|  |  |  | 	get current(){ | 
					
						
							|  |  |  | 		return this.data == null ? null : this.data.current | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	set current(value){ | 
					
						
							|  |  |  | 		this.focusImage(value) | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Current ribbon...
 | 
					
						
							|  |  |  | 	get currentRibbon(){ | 
					
						
							|  |  |  | 		return this.data == null ? null : this.data.getRibbon() | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	set currentRibbon(value){ | 
					
						
							|  |  |  | 		this.focusRibbon(value) | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Default direction...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// This can be 'left' or 'right', other values are ignored.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// The system has inertial direction change, after >N steps of 
 | 
					
						
							|  |  |  | 	// movement in one direction it takes N steps to reverse the default
 | 
					
						
							|  |  |  | 	// direction.
 | 
					
						
							|  |  |  | 	// The number of steps (N) is set in:
 | 
					
						
							|  |  |  | 	// 		.config['steps-to-change-direction']
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: to force direction change append a '!' to the direction.
 | 
					
						
							|  |  |  | 	// 		e.g. X.direction = 'left!'
 | 
					
						
							|  |  |  | 	get direction(){ | 
					
						
							|  |  |  | 		return this._direction >= 0 ? 'right' | 
					
						
							|  |  |  | 			: this._direction < 0 ? 'left' | 
					
						
							|  |  |  | 			: 'right' | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	set direction(value){ | 
					
						
							|  |  |  | 		// force direction change...
 | 
					
						
							|  |  |  | 		if(value.slice(-1) == '!'){ | 
					
						
							|  |  |  | 			this._direction = value == 'left!' ? -1 | 
					
						
							|  |  |  | 				: value == 'right!' ? 0 | 
					
						
							|  |  |  | 				: this._direction | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// 'update' direction...
 | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			value = value == 'left' ? -1  | 
					
						
							|  |  |  | 				: value == 'right' ? 1 | 
					
						
							|  |  |  | 				: 0 | 
					
						
							|  |  |  | 			var d = (this._direction || 0) + value | 
					
						
							|  |  |  | 			var s = this.config['steps-to-change-direction'] | 
					
						
							|  |  |  | 			s = s < 1 ? 1 : s | 
					
						
							|  |  |  | 			// cap the direction value between -s and s-1...
 | 
					
						
							|  |  |  | 			// NOTE: we use s-1 instead of s as 0/null is a positive 
 | 
					
						
							|  |  |  | 			// 		direction...
 | 
					
						
							|  |  |  | 			d = d >= s ? s-1 : d | 
					
						
							|  |  |  | 			d = d < -s ? -s : d | 
					
						
							|  |  |  | 			this._direction = d | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	toggleRibbonFocusMode : ['Interface/Toggle ribbon focus mode', | 
					
						
							| 
									
										
										
										
											2016-03-28 22:07:38 +03:00
										 |  |  | 		core.makeConfigToggler('ribbon-focus-mode',  | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 			function(){ return this.config['ribbon-focus-modes'] })], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// basic life-cycle actions...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX do we need to call .syncTags(..) here???
 | 
					
						
							|  |  |  | 	load: ['- File|Interface/', | 
					
						
							|  |  |  | 		function(d){ | 
					
						
							| 
									
										
										
										
											2015-12-29 01:02:57 +03:00
										 |  |  | 			this.clear() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 			this.images = images.Images(d.images) | 
					
						
							|  |  |  | 			this.data = data.Data(d.data) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	clear: ['File|Interface/Clear viewer', | 
					
						
							|  |  |  | 		function(){ | 
					
						
							|  |  |  | 			delete this.data | 
					
						
							|  |  |  | 			delete this.images | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// NOTE: for complete isolation it is best to completely copy the 
 | 
					
						
							|  |  |  | 	// 		.config...
 | 
					
						
							|  |  |  | 	clone: ['- File/', | 
					
						
							|  |  |  | 		function(full){ | 
					
						
							|  |  |  | 			var res = actions.MetaActions.clone.call(this, full) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if(this.data){ | 
					
						
							|  |  |  | 				res.data = this.data.clone() | 
					
						
							|  |  |  | 			}  | 
					
						
							|  |  |  | 			if(this.images){ | 
					
						
							|  |  |  | 				res.images = this.images.clone() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return res | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX should this be here???
 | 
					
						
							|  |  |  | 	// XXX should this use .load(..)
 | 
					
						
							|  |  |  | 	// 		...note if we use this it breaks, need to rethink...
 | 
					
						
							| 
									
										
										
										
											2016-01-08 04:25:54 +03:00
										 |  |  | 	loadURLs: ['- File/Load a URL list', | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 		function(lst, base){ | 
					
						
							| 
									
										
										
										
											2015-12-29 01:02:57 +03:00
										 |  |  | 			var imgs = images.Images.fromArray(lst, base) | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-29 01:02:57 +03:00
										 |  |  | 			this.load({ | 
					
						
							|  |  |  | 				images: imgs, | 
					
						
							|  |  |  | 				data: data.Data.fromArray(imgs.keys()), | 
					
						
							|  |  |  | 			}) | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX experimental...
 | 
					
						
							|  |  |  | 	// 		...the bad thing about this is that we can not extend this,
 | 
					
						
							|  |  |  | 	// 		adding new items to the resulting structure...
 | 
					
						
							|  |  |  | 	// XXX is this the correct way to go???
 | 
					
						
							|  |  |  | 	// 		...can we save simple attribute values???
 | 
					
						
							|  |  |  | 	json: ['- File/Dump state as JSON object', | 
					
						
							|  |  |  | 		'This will collect JSON data from every available attribute ' | 
					
						
							|  |  |  | 			+'supporting the .dumpJSON() method.', | 
					
						
							|  |  |  | 		function(mode){ | 
					
						
							|  |  |  | 			var res = {} | 
					
						
							|  |  |  | 			for(var k in this){ | 
					
						
							| 
									
										
										
										
											2015-12-20 08:12:56 +03:00
										 |  |  | 				if(this[k] != null && this[k].dumpJSON != null){ | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 					res[k] = this[k].dumpJSON() | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return res | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-28 07:09:06 +03:00
										 |  |  | 	replaceGid: ['- System/Replace image gid', | 
					
						
							|  |  |  | 		function(from, to){ | 
					
						
							|  |  |  | 			from = this.data.getImage(from) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// data...
 | 
					
						
							|  |  |  | 			var res = this.data.replaceGid(from, to) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if(res == null){ | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// images...
 | 
					
						
							|  |  |  | 			this.images && this.images.replaceGid(from, to) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// basic navigation...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	focusImage: ['- Navigate/Focus image', | 
					
						
							|  |  |  | 		function(img, list){ | 
					
						
							|  |  |  | 			this.data.focusImage(img, list) }], | 
					
						
							|  |  |  | 	// Focuses a ribbon by selecting an image in it...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// modes supported:
 | 
					
						
							|  |  |  | 	// 	'order'			- focus closest image to current in order
 | 
					
						
							|  |  |  | 	// 	'first'/'last'	- focus first/last image in ribbon
 | 
					
						
							|  |  |  | 	// 	'visual'		- focus visually closest to current image
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: default mode is set in .config.ribbon-focus-mode
 | 
					
						
							| 
									
										
										
										
											2015-12-20 20:59:19 +03:00
										 |  |  | 	// NOTE: this explicitly does nothing if mode is unrecognised, this
 | 
					
						
							|  |  |  | 	// 		is done to add support for other custom modes...
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 	focusRibbon: ['- Navigate/Focus Ribbon', | 
					
						
							|  |  |  | 		function(target, mode){ | 
					
						
							|  |  |  | 			var data = this.data | 
					
						
							|  |  |  | 			var r = data.getRibbon(target) | 
					
						
							|  |  |  | 			if(r == null){ | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			var c = data.getRibbonOrder() | 
					
						
							|  |  |  | 			var i = data.getRibbonOrder(r) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			mode = mode || this.config['ribbon-focus-mode'] || 'order' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// NOTE: we are not changing the direction here based on 
 | 
					
						
							|  |  |  | 			// 		this.direction as swap will confuse the user...
 | 
					
						
							|  |  |  | 			var direction = c < i ? 'before' : 'after' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// closest image in order...
 | 
					
						
							|  |  |  | 			if(mode == 'order'){ | 
					
						
							|  |  |  | 				var t = data.getImage(r, direction) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// if there are no images in the requied direction, try the 
 | 
					
						
							|  |  |  | 				// other way...
 | 
					
						
							|  |  |  | 				t = t == null ? data.getImage(r, direction == 'before' ? 'after' : 'before') : t | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// first/last image...
 | 
					
						
							|  |  |  | 			} else if(mode == 'first' || mode == 'last'){ | 
					
						
							|  |  |  | 				var t = data.getImage(mode, r) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-20 20:59:19 +03:00
										 |  |  | 			// unknown mode -- do nothing...
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 			} else { | 
					
						
							| 
									
										
										
										
											2015-12-20 04:29:44 +03:00
										 |  |  | 				return | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			this.focusImage(t, r) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	setBaseRibbon: ['Edit/Set base ribbon', | 
					
						
							|  |  |  | 		function(target){ this.data.setBase(target) }], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// shorthands...
 | 
					
						
							|  |  |  | 	// XXX do we reset direction on these???
 | 
					
						
							|  |  |  | 	firstImage: ['Navigate/First image in current ribbon', | 
					
						
							|  |  |  | 		function(all){ this.focusImage(all == null ? 'first' : 0) }], | 
					
						
							|  |  |  | 	lastImage: ['Navigate/Last image in current ribbon', | 
					
						
							|  |  |  | 		function(all){ this.focusImage(all == null ? 'last' : -1) }], | 
					
						
							|  |  |  | 	// XXX these break if image at first/last position are not loaded (crop, group, ...)
 | 
					
						
							|  |  |  | 	// XXX do we actually need these???
 | 
					
						
							|  |  |  | 	firstGlobalImage: ['Navigate/First image globally', | 
					
						
							|  |  |  | 		function(){ this.firstImage(true) }], | 
					
						
							|  |  |  | 	lastGlobalImage: ['Navigate/Last image globally', | 
					
						
							|  |  |  | 		function(){ this.lastImage(true) }], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX skip unloaded images... (groups?)
 | 
					
						
							|  |  |  | 	// XXX the next two are almost identical...
 | 
					
						
							|  |  |  | 	prevImage: ['Navigate/Previous image', | 
					
						
							|  |  |  | 		function(a){  | 
					
						
							|  |  |  | 			// keep track of traverse direction...
 | 
					
						
							|  |  |  | 			this.direction = 'left' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if(typeof(a) == typeof(123)){ | 
					
						
							|  |  |  | 				// XXX should this force direction change???
 | 
					
						
							|  |  |  | 				this.focusImage(this.data.getImage('current', -a) | 
					
						
							|  |  |  | 						// go to the first image if it's closer than s...
 | 
					
						
							|  |  |  | 						|| this.data.getImage('first')) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				this.focusImage('prev', a)  | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	nextImage: ['Navigate/Next image', | 
					
						
							|  |  |  | 		function(a){  | 
					
						
							|  |  |  | 			// keep track of traverse direction...
 | 
					
						
							|  |  |  | 			this.direction = 'right' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if(typeof(a) == typeof(123)){ | 
					
						
							|  |  |  | 				// XXX should this force direction change???
 | 
					
						
							|  |  |  | 				this.focusImage(this.data.getImage('current', a) | 
					
						
							|  |  |  | 						// go to the first image if it's closer than s...
 | 
					
						
							|  |  |  | 						|| this.data.getImage('last')) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				this.focusImage('next', a)  | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX skip unloaded images... (groups?)
 | 
					
						
							|  |  |  | 	// XXX the next two are almost identical...
 | 
					
						
							|  |  |  | 	prevImageInOrder: ['Navigate/Previous image in order', | 
					
						
							|  |  |  | 		function(){  | 
					
						
							|  |  |  | 			// NOTE: this used to be algorithmically substantially slower
 | 
					
						
							|  |  |  | 			// 		than the code below but after .makeSparseImages(..)
 | 
					
						
							|  |  |  | 			// 		got updated the difference is far less... 
 | 
					
						
							|  |  |  | 			// 		...since I've already spent the time to write and 
 | 
					
						
							|  |  |  | 			// 		debug the long version and it gives a small advantage
 | 
					
						
							|  |  |  | 			// 		I'll keep it for now...
 | 
					
						
							|  |  |  | 			// 		(~15-20% @ 10K images, e.g 50ms vs 80ms on average)
 | 
					
						
							|  |  |  | 			//this.prevImage(this.data.getImages('loaded')) 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var c = {} | 
					
						
							|  |  |  | 			// get prev images for each ribbon...
 | 
					
						
							|  |  |  | 			for(var r in this.data.ribbons){ | 
					
						
							|  |  |  | 				var i = this.data.getImageOrder('prev', r) | 
					
						
							|  |  |  | 				if(i >= 0){ | 
					
						
							|  |  |  | 					c[i] = r | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			this.prevImage(c[Math.max.apply(null, Object.keys(c))]) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	nextImageInOrder: ['Navigate/Next image in order', | 
					
						
							|  |  |  | 		function(){  | 
					
						
							|  |  |  | 			// NOTE: this used to be algorithmically substantially slower
 | 
					
						
							|  |  |  | 			// 		than the code below but after .makeSparseImages(..)
 | 
					
						
							|  |  |  | 			// 		got updated the difference is far less... 
 | 
					
						
							|  |  |  | 			// 		...since I've already spent the time to write and 
 | 
					
						
							|  |  |  | 			// 		debug the long version and it gives a small advantage
 | 
					
						
							|  |  |  | 			// 		I'll keep it for now...
 | 
					
						
							|  |  |  | 			// 		(~15-20% @ 10K images)
 | 
					
						
							|  |  |  | 			//this.nextImage(this.data.getImages('loaded')) 
 | 
					
						
							|  |  |  | 	 | 
					
						
							|  |  |  | 			var c = {} | 
					
						
							|  |  |  | 			// get next images for each ribbon...
 | 
					
						
							|  |  |  | 			for(var r in this.data.ribbons){ | 
					
						
							|  |  |  | 				var i = this.data.getImageOrder('next', r) | 
					
						
							|  |  |  | 				if(i >= 0){ | 
					
						
							|  |  |  | 					c[i] = r | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			this.nextImage(c[Math.min.apply(null, Object.keys(c))]) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	firstRibbon: ['Navigate/First ribbon', | 
					
						
							|  |  |  | 		function(){ this.focusRibbon('first') }], | 
					
						
							|  |  |  | 	lastRibbon: ['Navigate/Last ribbon', | 
					
						
							|  |  |  | 		function(){ this.focusRibbon('last') }], | 
					
						
							|  |  |  | 	prevRibbon: ['Navigate/Previous ribbon', | 
					
						
							|  |  |  | 		function(){ this.focusRibbon('before') }], | 
					
						
							|  |  |  | 	nextRibbon: ['Navigate/Next ribbon', | 
					
						
							|  |  |  | 		function(){ this.focusRibbon('after') }], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// basic ribbon editing...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: for all of these, current/ribbon image is a default...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX to be used for things like mark/place and dragging...
 | 
					
						
							|  |  |  | 	// XXX revise...
 | 
					
						
							|  |  |  | 	shiftImageTo: ['- Edit|Sort/', | 
					
						
							|  |  |  | 		function(target, to){ this.data.shiftImageTo(target, to) }], | 
					
						
							|  |  |  | 	 | 
					
						
							|  |  |  | 	shiftImageUp: ['Edit/Shift image up', | 
					
						
							|  |  |  | 		'If implicitly shifting current image (i.e. no arguments), focus ' | 
					
						
							|  |  |  | 			+'will shift to the next or previous image in the current ' | 
					
						
							|  |  |  | 			+'ribbon depending on current direction.', | 
					
						
							|  |  |  | 		function(target){  | 
					
						
							|  |  |  | 			// by default we need to focus another image in the same ribbon...
 | 
					
						
							|  |  |  | 			if(target == null){ | 
					
						
							|  |  |  | 				var direction = this.direction == 'right' ? 'next' : 'prev' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				var cur = this.data.getImage() | 
					
						
							|  |  |  | 				var next = this.data.getImage(direction) | 
					
						
							|  |  |  | 				next = next == null  | 
					
						
							|  |  |  | 					? this.data.getImage(direction == 'next' ? 'prev' : 'next')  | 
					
						
							|  |  |  | 					: next | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				this.data.shiftImageUp(cur) | 
					
						
							|  |  |  | 				this.focusImage(next) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// if a specific target is given, just shift it...
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				this.data.shiftImageUp(target) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	shiftImageDown: ['Edit/Shift image down', | 
					
						
							|  |  |  | 		'If implicitly shifting current image (i.e. no arguments), focus ' | 
					
						
							|  |  |  | 			+'will shift to the next or previous image in the current ' | 
					
						
							|  |  |  | 			+'ribbon depending on current direction.', | 
					
						
							|  |  |  | 		function(target){  | 
					
						
							|  |  |  | 			// by default we need to focus another image in the same ribbon...
 | 
					
						
							|  |  |  | 			if(target == null){ | 
					
						
							|  |  |  | 				var direction = this.direction == 'right' ? 'next' : 'prev' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				var cur = this.data.getImage() | 
					
						
							|  |  |  | 				var next = this.data.getImage(direction) | 
					
						
							|  |  |  | 				next = next == null  | 
					
						
							|  |  |  | 					? this.data.getImage(direction == 'next' ? 'prev' : 'next')  | 
					
						
							|  |  |  | 					: next | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				this.data.shiftImageDown(cur) | 
					
						
							|  |  |  | 				this.focusImage(next) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// if a specific target is given, just shift it...
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				this.data.shiftImageDown(target) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	shiftImageUpNewRibbon: ['Edit/Shift image up to a new empty ribbon', | 
					
						
							|  |  |  | 		function(target){ | 
					
						
							|  |  |  | 			this.data.newRibbon(target) | 
					
						
							|  |  |  | 			this.shiftImageUp(target) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	shiftImageDownNewRibbon: ['Edit/Shift image down to a new empty ribbon', | 
					
						
							|  |  |  | 		function(target){ | 
					
						
							|  |  |  | 			this.data.newRibbon(target, 'below') | 
					
						
							|  |  |  | 			this.shiftImageDown(target) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	shiftImageLeft: ['Edit|Sort/Shift image left', | 
					
						
							|  |  |  | 		function(target){  | 
					
						
							|  |  |  | 			if(target == null){ | 
					
						
							|  |  |  | 				this.direction = 'left' | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			this.data.shiftImageLeft(target)  | 
					
						
							|  |  |  | 			this.focusImage() | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	shiftImageRight: ['Edit|Sort/Shift image right', | 
					
						
							|  |  |  | 		function(target){  | 
					
						
							|  |  |  | 			if(target == null){ | 
					
						
							|  |  |  | 				this.direction = 'right' | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			this.data.shiftImageRight(target)  | 
					
						
							|  |  |  | 			this.focusImage() | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	shiftRibbonUp: ['Edit/Shift ribbon up', | 
					
						
							|  |  |  | 		function(target){  | 
					
						
							|  |  |  | 			this.data.shiftRibbonUp(target)  | 
					
						
							|  |  |  | 			// XXX is this the right way to go/???
 | 
					
						
							|  |  |  | 			this.focusImage() | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	shiftRibbonDown: ['Edit/Shift ribbon down', | 
					
						
							|  |  |  | 		function(target){  | 
					
						
							|  |  |  | 			this.data.shiftRibbonDown(target) | 
					
						
							|  |  |  | 			// XXX is this the right way to go/???
 | 
					
						
							|  |  |  | 			this.focusImage() | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// these operate on the current image...
 | 
					
						
							|  |  |  | 	travelImageUp: ['Edit/Travel with the current image up (Shift up and keep focus)', | 
					
						
							|  |  |  | 		function(target){ | 
					
						
							|  |  |  | 			target = target || this.current | 
					
						
							|  |  |  | 			this.shiftImageUp(target) | 
					
						
							|  |  |  | 			this.focusImage(target) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	travelImageDown: ['Edit/Travel with the current image down (Shift down and keep focus)', | 
					
						
							|  |  |  | 		function(target){ | 
					
						
							|  |  |  | 			target = target || this.current | 
					
						
							|  |  |  | 			this.shiftImageDown(target) | 
					
						
							|  |  |  | 			this.focusImage(target) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	 | 
					
						
							|  |  |  | 	reverseImages: ['Edit|Sort/Reverse image order', | 
					
						
							|  |  |  | 		function(){ this.data.reverseImages() }], | 
					
						
							|  |  |  | 	reverseRibbons: ['Edit|Sort/Reverse ribbon order', | 
					
						
							|  |  |  | 		function(){ this.data.reverseRibbons() }], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX align to ribbon...
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 	// basic image editing...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX should we have .rotate(..) and .flip(..) generic actions???
 | 
					
						
							|  |  |  | 	rotateCW: ['Image|Edit/',  | 
					
						
							|  |  |  | 		function(target){  | 
					
						
							|  |  |  | 			this.images  | 
					
						
							|  |  |  | 				&& this.images.rotateImage(this.data.getImage(target), 'cw') }], | 
					
						
							|  |  |  | 	rotateCCW: ['Image|Edit/',  | 
					
						
							|  |  |  | 		function(target){  | 
					
						
							|  |  |  | 			this.images  | 
					
						
							|  |  |  | 				&& this.images.rotateImage(this.data.getImage(target), 'ccw') }], | 
					
						
							|  |  |  | 	flipVertical: ['Image|Edit/', | 
					
						
							|  |  |  | 		function(target){  | 
					
						
							|  |  |  | 			this.images  | 
					
						
							|  |  |  | 				&& this.images.flipImage(this.data.getImage(target), 'vertical') }], | 
					
						
							|  |  |  | 	flipHorizontal: ['Image|Edit/', | 
					
						
							|  |  |  | 		function(target){  | 
					
						
							|  |  |  | 			this.images | 
					
						
							|  |  |  | 				&& this.images.flipImage(this.data.getImage(target), 'horizontal') }], | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Base = | 
					
						
							|  |  |  | module.Base = core.ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: 'ImageGrid base', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'base', | 
					
						
							|  |  |  | 	/* XXX ??? | 
					
						
							|  |  |  | 	suggested: [ | 
					
						
							|  |  |  | 		'tags', | 
					
						
							|  |  |  | 		'sort', | 
					
						
							| 
									
										
										
										
											2016-04-27 08:59:13 +03:00
										 |  |  | 		'tasks', | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 	], | 
					
						
							|  |  |  | 	*/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	actions: BaseActions, | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | // Sort...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var SortActions =  | 
					
						
							|  |  |  | module.SortActions = actions.Actions({ | 
					
						
							|  |  |  | 	config: { | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 		// this can be:
 | 
					
						
							|  |  |  | 		// 	- sort mode name		- as set in .config['sort-mode'] key
 | 
					
						
							|  |  |  | 		// 								Example: 'Date'
 | 
					
						
							|  |  |  | 		// 	- explicit sort method	- as set in .config['sort-mode'] value
 | 
					
						
							|  |  |  | 		// 								Example: 'metadata.createDate birthtime'
 | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 		'default-sort': 'Date', | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 		 | 
					
						
							|  |  |  | 		// Format:
 | 
					
						
							|  |  |  | 		// 	The value is a space separated string of methods.
 | 
					
						
							|  |  |  | 		// 	A method is either a sort method defined in .__sort_methods__
 | 
					
						
							|  |  |  | 		// 	or a dot-separated image attribute path.
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// NOTE: 'Date' is descending by default
 | 
					
						
							|  |  |  | 		// NOTE: .toggleImageSort('?') may also show 'Manual' when 
 | 
					
						
							|  |  |  | 		// 		.data.manual_order is present.
 | 
					
						
							|  |  |  | 		// NOTE: 'Manual' mode is set after .shiftImageLeft(..)/.shiftImageRight(..)
 | 
					
						
							|  |  |  | 		// 		are called or when restoring a pre-existing .data.manual_order 
 | 
					
						
							|  |  |  | 		// 		via .toggleImageSort('Manual')
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// XXX need a natural way to reverse these...
 | 
					
						
							|  |  |  | 		'sort-methods': { | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 			'none': '', | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 			// NOTE: this is descending by default...
 | 
					
						
							|  |  |  | 			'Date': 'metadata.createDate birthtime reverse', | 
					
						
							| 
									
										
										
										
											2016-04-25 14:34:32 +03:00
										 |  |  | 			'File date': 'birthtime reverse', | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 			'Name (XP-style)': 'name-leading-sequence name path', | 
					
						
							| 
									
										
										
										
											2016-04-25 14:34:32 +03:00
										 |  |  | 			'File sequence number': 'name-sequence name path', | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 			'Name': 'name path', | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 			// XXX sequence number with overflow...
 | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 			//'File sequence number with overflow': 'name-leading-sequence name path',
 | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 	// Custom sort methods...
 | 
					
						
							| 
									
										
										
										
											2016-04-20 16:17:12 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// Format:
 | 
					
						
							|  |  |  | 	// 	{
 | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 	// 		<method-name>: function(a, b){ ... },
 | 
					
						
							| 
									
										
										
										
											2016-04-20 16:17:12 +03:00
										 |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	// 	}
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: the cmp function is called in the actions context.
 | 
					
						
							| 
									
										
										
										
											2016-04-23 00:07:04 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX sequence number with overflow...
 | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 	__sort_methods__: { | 
					
						
							| 
									
										
										
										
											2016-04-22 12:56:19 +03:00
										 |  |  | 		'name-leading-sequence': function(a, b){ | 
					
						
							|  |  |  | 			a = this.images.getImageNameLeadingSeq(a) | 
					
						
							|  |  |  | 			a = typeof(a) == typeof('str') ? 0 : a | 
					
						
							|  |  |  | 			b = this.images.getImageNameLeadingSeq(b) | 
					
						
							|  |  |  | 			b = typeof(b) == typeof('str') ? 0 : b | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return a - b | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		'name-sequence': function(a, b){ | 
					
						
							|  |  |  | 			a = this.images.getImageNameSeq(a) | 
					
						
							|  |  |  | 			a = typeof(a) == typeof('str') ? 0 : a | 
					
						
							|  |  |  | 			b = this.images.getImageNameSeq(b) | 
					
						
							|  |  |  | 			b = typeof(b) == typeof('str') ? 0 : b | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return a - b | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2016-04-20 16:17:12 +03:00
										 |  |  | 	}, | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 	//	Sort using the default sort method
 | 
					
						
							|  |  |  | 	//	.sortImages()
 | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 	//		NOTE: the actual sort method used is set via 
 | 
					
						
							|  |  |  | 	//			.config['default-sort']
 | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//	Sort using a specific method(s):
 | 
					
						
							|  |  |  | 	//	.sortImages(<method>)
 | 
					
						
							| 
									
										
										
										
											2016-04-22 18:11:22 +03:00
										 |  |  | 	//	.sortImages(<method>, <reverse>)
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	.sortImages('<method> ..')
 | 
					
						
							|  |  |  | 	//	.sortImages('<method> ..', <reverse>)
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 	//	.sortImages([<method>, ..])
 | 
					
						
							| 
									
										
										
										
											2016-04-22 18:11:22 +03:00
										 |  |  | 	//	.sortImages([<method>, ..], <reverse>)
 | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 	//		NOTE: <method> can either be one of:
 | 
					
						
							|  |  |  | 	//			1) method name (key) from .config['sort-methods']
 | 
					
						
							|  |  |  | 	//			2) a space separated string of methods or attribute paths
 | 
					
						
							|  |  |  | 	//				as in .config['sort-methods']'s values.
 | 
					
						
							|  |  |  | 	//			for more info se doc for: .config['sort-methods']
 | 
					
						
							| 
									
										
										
										
											2016-04-22 18:11:22 +03:00
										 |  |  | 	//		NOTE: if it is needed to reverse the method by default just
 | 
					
						
							|  |  |  | 	//			add 'reverse' to it's string.
 | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	//	Update current sort order:
 | 
					
						
							|  |  |  | 	//	.sortImages('update')
 | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 	//		NOTE: unless the sort order (.data.order) is changed manually
 | 
					
						
							|  |  |  | 	//			this will have no effect.
 | 
					
						
							|  |  |  | 	//		NOTE: this is designed to facilitate manual sorting of 
 | 
					
						
							|  |  |  | 	//			.data.order
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2016-04-22 18:11:22 +03:00
										 |  |  | 	//	Reverse image order:
 | 
					
						
							|  |  |  | 	//	.sortImages('reverse')
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: reverse is calculated by oddity -- if an odd number indicated
 | 
					
						
							|  |  |  | 	// 		then the result is reversed, otherwise it is not. 
 | 
					
						
							|  |  |  | 	// 		e.g. adding:
 | 
					
						
							|  |  |  | 	// 		 	'metadata.createDate birthtime' + ' reverse' 
 | 
					
						
							|  |  |  | 	// 		will reverse the result's order while:
 | 
					
						
							|  |  |  | 	// 		 	'metadata.createDate birthtime reverse' + ' reverese' 
 | 
					
						
							|  |  |  | 	// 		will cancel reversal.
 | 
					
						
							| 
									
										
										
										
											2016-04-23 00:07:04 +03:00
										 |  |  | 	// NOTE: with empty images this will not do anything.
 | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2016-04-20 03:56:19 +03:00
										 |  |  | 	// XXX cache order???
 | 
					
						
							| 
									
										
										
										
											2016-04-23 00:07:04 +03:00
										 |  |  | 	// XXX would be nice to be able to sort a list of gids or a section
 | 
					
						
							|  |  |  | 	// 		of images...
 | 
					
						
							|  |  |  | 	// XXX sorting with partial images will throw the images that do not
 | 
					
						
							|  |  |  | 	// 		exist or the ones that do not have the right attrs all over 
 | 
					
						
							|  |  |  | 	// 		the place...
 | 
					
						
							| 
									
										
										
										
											2016-04-20 16:17:12 +03:00
										 |  |  | 	sortImages: ['- Edit|Sort/Sort images', | 
					
						
							|  |  |  | 		function(method, reverse){  | 
					
						
							|  |  |  | 			var that = this | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if(method == 'reverse'){ | 
					
						
							| 
									
										
										
										
											2016-04-22 19:26:22 +03:00
										 |  |  | 				method = 'update' | 
					
						
							| 
									
										
										
										
											2016-04-20 16:17:12 +03:00
										 |  |  | 				reverse = true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 			reverse = reverse == null ? false  | 
					
						
							|  |  |  | 				: reverse == 'reverse'  | 
					
						
							| 
									
										
										
										
											2016-04-20 16:17:12 +03:00
										 |  |  | 				|| reverse | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-22 18:11:22 +03:00
										 |  |  | 			// special case: 'update'
 | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 			method = method == 'update' ? [] : method | 
					
						
							| 
									
										
										
										
											2016-04-22 18:11:22 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// defaults...
 | 
					
						
							| 
									
										
										
										
											2016-04-20 16:17:12 +03:00
										 |  |  | 			method = method  | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 				|| this.config['sort-methods'][this.config['default-sort']] | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 				|| this.config['default-sort']  | 
					
						
							| 
									
										
										
										
											2016-04-20 16:17:12 +03:00
										 |  |  | 				|| 'birthtime' | 
					
						
							| 
									
										
										
										
											2016-04-22 18:11:22 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// expand method names...
 | 
					
						
							|  |  |  | 			// XXX should this be recursive???
 | 
					
						
							|  |  |  | 			method = typeof(method) == typeof('str') ?  | 
					
						
							|  |  |  | 				method | 
					
						
							|  |  |  | 					.split(/ +/g) | 
					
						
							|  |  |  | 					.map(function(m){  | 
					
						
							|  |  |  | 						return that.config['sort-methods'][m] || m }) | 
					
						
							|  |  |  | 					.join(' ') | 
					
						
							|  |  |  | 				: method | 
					
						
							| 
									
										
										
										
											2016-04-20 16:17:12 +03:00
										 |  |  | 			method = typeof(method) == typeof('str') ? method.split(/ +/g) : method | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-22 18:11:22 +03:00
										 |  |  | 			// get the reverse arity...
 | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 			var i = method.indexOf('reverse') | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 			while(i >=0){ | 
					
						
							|  |  |  | 				reverse = !reverse | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-22 18:11:22 +03:00
										 |  |  | 				method.splice(i, 1) | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 				i = method.indexOf('reverse') | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-22 19:26:22 +03:00
										 |  |  | 			// can't sort if we know nothing about .images
 | 
					
						
							|  |  |  | 			if(method && method.length > 0 && (!this.images || this.images.length == 0)){ | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 			// build the compare routine...
 | 
					
						
							| 
									
										
										
										
											2016-04-22 18:11:22 +03:00
										 |  |  | 			method = method | 
					
						
							|  |  |  | 				// remove duplicate methods...
 | 
					
						
							|  |  |  | 				.unique() | 
					
						
							|  |  |  | 				.map(function(m){ | 
					
						
							|  |  |  | 					return SortActions.__sort_methods__[m]  | 
					
						
							|  |  |  | 						|| (that.__sort_methods__ && that.__sort_methods__[m]) | 
					
						
							|  |  |  | 						// sort by attr path...
 | 
					
						
							|  |  |  | 						|| (function(){ | 
					
						
							|  |  |  | 							var p = m.split(/\./g) | 
					
						
							|  |  |  | 							var _get = function(obj){ | 
					
						
							| 
									
										
										
										
											2016-04-22 19:26:22 +03:00
										 |  |  | 								if(obj == null){ | 
					
						
							|  |  |  | 									return null | 
					
						
							|  |  |  | 								} | 
					
						
							| 
									
										
										
										
											2016-04-22 18:11:22 +03:00
										 |  |  | 								for(var i=0; i<p.length; i++){ | 
					
						
							|  |  |  | 									obj = obj[p[i]] | 
					
						
							|  |  |  | 									if(obj === undefined){ | 
					
						
							|  |  |  | 										return null | 
					
						
							|  |  |  | 									} | 
					
						
							| 
									
										
										
										
											2016-04-20 16:17:12 +03:00
										 |  |  | 								} | 
					
						
							| 
									
										
										
										
											2016-04-22 18:11:22 +03:00
										 |  |  | 								return obj | 
					
						
							| 
									
										
										
										
											2016-04-20 16:17:12 +03:00
										 |  |  | 							} | 
					
						
							| 
									
										
										
										
											2016-04-22 18:11:22 +03:00
										 |  |  | 							return function(a, b){ | 
					
						
							|  |  |  | 								a = _get(this.images[a]) | 
					
						
							|  |  |  | 								b = _get(this.images[b]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 								if(a == b){ | 
					
						
							|  |  |  | 									return 0 | 
					
						
							|  |  |  | 								} else if(a < b){ | 
					
						
							|  |  |  | 									return -1 | 
					
						
							|  |  |  | 								} else { | 
					
						
							|  |  |  | 									return +1 | 
					
						
							|  |  |  | 								} | 
					
						
							|  |  |  | 							}})()  | 
					
						
							|  |  |  | 				}) | 
					
						
							| 
									
										
										
										
											2016-04-20 16:17:12 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// prepare the cmp function...
 | 
					
						
							|  |  |  | 			var cmp = method.length == 1 ?  | 
					
						
							|  |  |  | 				method[0]  | 
					
						
							|  |  |  | 				// chain compare -- return first non equal (0) result...
 | 
					
						
							|  |  |  | 				: function(a, b){ | 
					
						
							|  |  |  | 					var res = 0 | 
					
						
							|  |  |  | 					for(var i=0; i < method.length; i++){ | 
					
						
							|  |  |  | 						res = method[i].call(that, a, b) | 
					
						
							|  |  |  | 						if(res != 0){ | 
					
						
							|  |  |  | 							return res | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					return res | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// do the sort (in place)...
 | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 			if(method && method.length > 0 && this.images){ | 
					
						
							| 
									
										
										
										
											2016-04-20 16:17:12 +03:00
										 |  |  | 				this.data.order = this.data.order.slice() | 
					
						
							|  |  |  | 				reverse ?  | 
					
						
							|  |  |  | 					this.data.order.sort(cmp.bind(this)).reverse() | 
					
						
							|  |  |  | 					: this.data.order.sort(cmp.bind(this)) | 
					
						
							| 
									
										
										
										
											2016-04-22 18:11:22 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// just reverse...
 | 
					
						
							|  |  |  | 			} else if(method.length <= 0 && reverse) { | 
					
						
							|  |  |  | 				this.data.order.reverse() | 
					
						
							| 
									
										
										
										
											2016-04-20 03:56:19 +03:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			this.data.updateImagePositions() | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 	// XXX should this be a dialog with ability to edit modes???
 | 
					
						
							|  |  |  | 	// 		- toggle reverse sort
 | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 	// XXX currently this will not toggle past 'none'
 | 
					
						
							| 
									
										
										
										
											2016-04-28 17:50:55 +03:00
										 |  |  | 	toggleImageSort: ['- Edit|Sort/Toggle image sort method', | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 		toggler.Toggler(null, | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 			function(){ return this.data.sort_method || 'none' }, | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 			function(){  | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 				return Object.keys(this.config['sort-methods']) | 
					
						
							| 
									
										
										
										
											2016-04-28 17:50:55 +03:00
										 |  |  | 					.concat((this.data.manual_order  | 
					
						
							|  |  |  | 						|| this.data.sort_method == 'Manual') ? ['Manual'] : [])}, | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 			// prevent setting 'none' as mode...
 | 
					
						
							|  |  |  | 			function(mode){  | 
					
						
							| 
									
										
										
										
											2016-04-22 19:26:22 +03:00
										 |  |  | 				return !!this.images  | 
					
						
							|  |  |  | 					&& (mode != 'none'  | 
					
						
							|  |  |  | 						|| (mode == 'Manual' && this.data.manual_order)) }, | 
					
						
							| 
									
										
										
										
											2016-04-28 17:50:55 +03:00
										 |  |  | 			// XXX need to refactor the toggler a bit to make the 
 | 
					
						
							|  |  |  | 			// 		signature simpler...
 | 
					
						
							|  |  |  | 			function(mode, _, reverse){  | 
					
						
							|  |  |  | 				reverse = reverse == 'reverse' || reverse | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 				// save manual order...
 | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 				if(this.data.sort_method == 'Manual'){ | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 					this.data.manual_order = this.data.order.slice() | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// special case: manual order...
 | 
					
						
							|  |  |  | 				// XXX this does not use .sortImages(..) thus this does not update...
 | 
					
						
							|  |  |  | 				if(mode == 'Manual'){ | 
					
						
							|  |  |  | 					this.data.order = this.data.manual_order.slice() | 
					
						
							| 
									
										
										
										
											2016-04-28 17:50:55 +03:00
										 |  |  | 					this.sortImages('update' + (reverse ? ' reverse' : '')) | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				} else { | 
					
						
							| 
									
										
										
										
											2016-04-28 17:50:55 +03:00
										 |  |  | 					this.sortImages(mode + (reverse ? ' reverse' : '')) | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 				this.data.sort_method = mode | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 			})], | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 	// Store/load sort data:
 | 
					
						
							| 
									
										
										
										
											2016-04-23 00:07:04 +03:00
										 |  |  | 	// 	.data.sort_method		- current sort mode (optional)
 | 
					
						
							|  |  |  | 	// 	.data.manual_order		- manual sort order (optional)
 | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 	load: [function(data){ | 
					
						
							|  |  |  | 		return function(){ | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 			if(data.data && data.data.sort_method){ | 
					
						
							|  |  |  | 				this.data.sort_method = data.data.sort_method | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if(data.data && data.data.manual_order){ | 
					
						
							|  |  |  | 				this.data.manual_order = data.data.manual_order | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}], | 
					
						
							|  |  |  | 	json: [function(){ | 
					
						
							|  |  |  | 		return function(res){ | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 			if(this.data.sort_method){ | 
					
						
							|  |  |  | 				res.data.sort_method = this.data.sort_method | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if(this.data.manual_order){ | 
					
						
							|  |  |  | 				res.data.manual_order = this.data.manual_order | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			} else if(this.toggleImageSort('?') == 'Manual'){ | 
					
						
							|  |  |  | 				res.data.manual_order = this.data.order | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}], | 
					
						
							| 
									
										
										
										
											2015-12-20 08:12:56 +03:00
										 |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | var Sort = | 
					
						
							|  |  |  | module.Sort = core.ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: '', | 
					
						
							| 
									
										
										
										
											2015-12-20 08:12:56 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 	tag: 'sort', | 
					
						
							|  |  |  | 	depends: [ | 
					
						
							|  |  |  | 		'base', | 
					
						
							|  |  |  | 	], | 
					
						
							| 
									
										
										
										
											2015-12-20 08:12:56 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 	actions: SortActions, | 
					
						
							| 
									
										
										
										
											2015-12-20 08:12:56 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 	handlers: [ | 
					
						
							|  |  |  | 		['shiftImageRight shiftImageLeft', | 
					
						
							|  |  |  | 			function(){ | 
					
						
							| 
									
										
										
										
											2016-04-22 17:07:18 +03:00
										 |  |  | 				this.data.sort_method = 'Manual' | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 			}], | 
					
						
							|  |  |  | 	], | 
					
						
							| 
									
										
										
										
											2015-12-20 08:12:56 +03:00
										 |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-20 08:12:56 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | // Tags...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var TagsActions =  | 
					
						
							|  |  |  | module.TagsActions = actions.Actions({ | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 	// tags...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX mark updated...
 | 
					
						
							|  |  |  | 	tag: ['- Tag/Tag image(s)', | 
					
						
							|  |  |  | 		function(tags, gids){ | 
					
						
							|  |  |  | 			gids = gids || this.current | 
					
						
							|  |  |  | 			gids = gids.constructor !== Array ? [gids] : gids | 
					
						
							|  |  |  | 			tags = tags.constructor !== Array ? [tags] : tags | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// data...
 | 
					
						
							|  |  |  | 			this.data.tag(tags, gids) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// images...
 | 
					
						
							|  |  |  | 			var images = this.images | 
					
						
							|  |  |  | 			gids.forEach(function(gid){ | 
					
						
							|  |  |  | 				var img = images[gid] | 
					
						
							|  |  |  | 				if(img == null){ | 
					
						
							|  |  |  | 					img = images[gid] = {} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				if(img.tags == null){ | 
					
						
							|  |  |  | 					img.tags = [] | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				img.tags = img.tags.concat(tags).unique() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// XXX mark updated...
 | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	// XXX mark updated...
 | 
					
						
							|  |  |  | 	untag: ['- Tag/Untag image(s)', | 
					
						
							|  |  |  | 		function(tags, gids){ | 
					
						
							|  |  |  | 			gids = gids || this.current | 
					
						
							|  |  |  | 			gids = gids.constructor !== Array ? [gids] : gids | 
					
						
							|  |  |  | 			tags = tags.constructor !== Array ? [tags] : tags | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// data...
 | 
					
						
							|  |  |  | 			this.data.untag(tags, gids) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// images...
 | 
					
						
							|  |  |  | 			var images = this.images | 
					
						
							|  |  |  | 			gids.forEach(function(gid){ | 
					
						
							|  |  |  | 				var img = images[gid] | 
					
						
							|  |  |  | 				if(img == null || img.tags == null){ | 
					
						
							|  |  |  | 					return | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				img.tags = img.tags.filter(function(tag){ return tags.indexOf(tag) < 0 }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if(img.tags.length == 0){ | 
					
						
							|  |  |  | 					delete img.tags | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// XXX mark updated...
 | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	// Sync tags...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	Sync both ways...
 | 
					
						
							|  |  |  | 	//	.syncTags()
 | 
					
						
							|  |  |  | 	//	.syncTags('both')
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	Sync from .data
 | 
					
						
							|  |  |  | 	//	.syncTags('data')
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	Sync from .images
 | 
					
						
							|  |  |  | 	//	.syncTags('images')
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	Sync from <images> object
 | 
					
						
							|  |  |  | 	//	.syncTags(<images>)
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: mode is data.tagsToImages(..) / data.tagsFromImages(..) 
 | 
					
						
							|  |  |  | 	// 		compatible...
 | 
					
						
							|  |  |  | 	// NOTE: setting source to 'both' and mode to 'reset' is the same as
 | 
					
						
							|  |  |  | 	// 		'images' and 'reset' as all .data tags will be lost on first 
 | 
					
						
							|  |  |  | 	// 		pass...
 | 
					
						
							|  |  |  | 	syncTags: ['Tag/Synchoronize tags between data and images', | 
					
						
							|  |  |  | 		function(source, mode){ | 
					
						
							|  |  |  | 			// can't do anything if either .data or .images are not 
 | 
					
						
							|  |  |  | 			// defined...
 | 
					
						
							|  |  |  | 			if(this.data == null || this.images == null){ | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			source = source || 'both' | 
					
						
							|  |  |  | 			mode = mode || 'merge' | 
					
						
							|  |  |  | 			images = this.images | 
					
						
							|  |  |  | 			if(typeof(source) != typeof('str')){ | 
					
						
							|  |  |  | 				images = source | 
					
						
							|  |  |  | 				source = 'images' | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if(source == 'data' || source == 'both'){ | 
					
						
							|  |  |  | 				this.data.tagsToImages(images, mode) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if(source == 'images' || source == 'both'){ | 
					
						
							|  |  |  | 				this.data.tagsFromImages(images, mode) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}], | 
					
						
							| 
									
										
										
										
											2015-12-20 20:59:19 +03:00
										 |  |  | 	 | 
					
						
							|  |  |  | 	prevTagged: ['- Navigate/Previous image tagged with tag', | 
					
						
							|  |  |  | 		makeTagWalker('prev')], | 
					
						
							|  |  |  | 	nextTagged: ['- Navigate/Next image tagged with tag', | 
					
						
							|  |  |  | 		makeTagWalker('next')], | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-20 08:12:56 +03:00
										 |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Tags = | 
					
						
							|  |  |  | module.Tags = core.ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: '', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'tags', | 
					
						
							|  |  |  | 	depends: [ | 
					
						
							|  |  |  | 		'base', | 
					
						
							|  |  |  | 	], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	actions: TagsActions, | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | // Crop...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var CropActions = | 
					
						
							|  |  |  | module.CropActions = actions.Actions({ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	crop_stack: null, | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-20 08:12:56 +03:00
										 |  |  | 	// load the crop stack if present...
 | 
					
						
							|  |  |  | 	load: [function(data){ | 
					
						
							|  |  |  | 		if(data.crop_stack){ | 
					
						
							|  |  |  | 			this.crop_stack = data.crop_stack.map(function(j){ | 
					
						
							|  |  |  | 				return data.Data(j) | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}], | 
					
						
							|  |  |  | 	// store the root crop state instead of the current view...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// modes supported:
 | 
					
						
							|  |  |  | 	// 	- current	- store the current state/view
 | 
					
						
							|  |  |  | 	// 	- base		- store the base state/view
 | 
					
						
							|  |  |  | 	// 	- full		- store the crop stack
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX might need to revise the mode approach...
 | 
					
						
							|  |  |  | 	// XXX add support to loading the states...
 | 
					
						
							|  |  |  | 	json: [function(mode){ | 
					
						
							|  |  |  | 		mode = mode || 'current' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return function(res){ | 
					
						
							|  |  |  | 			if(mode == 'base'  | 
					
						
							|  |  |  | 					&& this.crop_stack  | 
					
						
							|  |  |  | 					&& this.crop_stack.length > 0){ | 
					
						
							|  |  |  | 				res.data = this.crop_stack[0].dumpJSON() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if(mode == 'full'  | 
					
						
							|  |  |  | 					&& this.crop_stack  | 
					
						
							|  |  |  | 					&& this.crop_stack.length > 0){ | 
					
						
							|  |  |  | 				res.crop_stack = this.crop_stack.map(function(c){ | 
					
						
							|  |  |  | 					return c.dumpJSON() | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}], | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-05 17:31:11 +03:00
										 |  |  | 	// true if current viewer is cropped...
 | 
					
						
							|  |  |  | 	get cropped(){ | 
					
						
							|  |  |  | 		return this.crop_stack != null | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 	// crop...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	crop: ['- Crop/Crop image list', | 
					
						
							|  |  |  | 		function(list, flatten){  | 
					
						
							|  |  |  | 			list = list || this.data.order | 
					
						
							|  |  |  | 			if(this.crop_stack == null){ | 
					
						
							|  |  |  | 				this.crop_stack = [] | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			this.crop_stack.push(this.data) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if(list instanceof data.Data){ | 
					
						
							|  |  |  | 				this.data = list  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				this.data = this.data.crop(list, flatten) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	uncrop: ['Crop/Uncrop ribbons', | 
					
						
							|  |  |  | 		function(level, restore_current, keep_crop_order){ | 
					
						
							|  |  |  | 			level = level || 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var cur = this.current | 
					
						
							|  |  |  | 			var order = this.data.order | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if(this.crop_stack == null){ | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// uncrop all...
 | 
					
						
							|  |  |  | 			if(level == 'all'){ | 
					
						
							|  |  |  | 				this.data = this.crop_stack[0] | 
					
						
							|  |  |  | 				this.crop_stack = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// get the element at level and drop the tail...
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				this.data = this.crop_stack.splice(-level, this.crop_stack.length)[0] | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// by default set the current from the crop...
 | 
					
						
							|  |  |  | 			if(!restore_current){ | 
					
						
							|  |  |  | 				this.data.focusImage(cur) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// restore order from the crop...
 | 
					
						
							|  |  |  | 			if(keep_crop_order){ | 
					
						
							|  |  |  | 				this.data.order = order | 
					
						
							|  |  |  | 				this.data.updateImagePositions() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// purge the stack...
 | 
					
						
							|  |  |  | 			if(this.crop_stack.length == 0){ | 
					
						
							|  |  |  | 				delete this.crop_stack | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	uncropAll: ['Crop/Uncrop all', | 
					
						
							|  |  |  | 		function(restore_current){ this.uncrop('all', restore_current) }], | 
					
						
							|  |  |  | 	// XXX see if we need to do this on this level??
 | 
					
						
							|  |  |  | 	// 		...might be a good idea to do this in data...
 | 
					
						
							|  |  |  | 	uncropAndKeepOrder: ['Crop|Edit/Uncrop and keep crop image order', | 
					
						
							|  |  |  | 		function(level, restore_current){ this.uncrop(level, restore_current, true) }], | 
					
						
							|  |  |  | 	// XXX same as uncrop but will also try and merge changes...
 | 
					
						
							|  |  |  | 	// 		- the order is simple and already done above...
 | 
					
						
							|  |  |  | 	// 		- I think that levels should be relative to images, the 
 | 
					
						
							|  |  |  | 	// 		  only problem here is how to deal with new ribbons...
 | 
					
						
							|  |  |  | 	mergeCrop: ['Crop|Edit/Merge crop', | 
					
						
							|  |  |  | 		function(){ | 
					
						
							|  |  |  | 			// XXX
 | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX save a crop (catalog)...
 | 
					
						
							|  |  |  | 	// XXX
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cropRibbon: ['Crop/Crop current ribbon', | 
					
						
							|  |  |  | 		function(ribbon, flatten){ | 
					
						
							|  |  |  | 			if(typeof(ribbon) == typeof(true)){ | 
					
						
							|  |  |  | 				flatten = ribbon | 
					
						
							|  |  |  | 				ribbon = null | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			ribbon = ribbon || 'current' | 
					
						
							|  |  |  | 			this.crop(this.data.getImages(ribbon), flatten) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	cropRibbonAndAbove: ['Crop/Crop current and above ribbons', | 
					
						
							|  |  |  | 		function(ribbon, flatten){ | 
					
						
							|  |  |  | 			if(typeof(ribbon) == typeof(true)){ | 
					
						
							|  |  |  | 				flatten = ribbon | 
					
						
							|  |  |  | 				ribbon = null | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			ribbon = ribbon || this.data.getRibbon() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var data = this.data | 
					
						
							|  |  |  | 			var that = this | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var i = data.ribbon_order.indexOf(ribbon) | 
					
						
							|  |  |  | 			var ribbons = data.ribbon_order.slice(0, i) | 
					
						
							|  |  |  | 			var images = ribbons | 
					
						
							|  |  |  | 				.reduce(function(a, b){  | 
					
						
							|  |  |  | 						return data.getImages(a).concat(data.getImages(b))  | 
					
						
							|  |  |  | 					}, data.getImages(ribbon)) | 
					
						
							|  |  |  | 				.compact() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			this.crop(data.getImages(images), flatten) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	 | 
					
						
							|  |  |  | 	// XXX should this be here???
 | 
					
						
							|  |  |  | 	cropTagged: ['Tag|Crop/Crop tagged images', | 
					
						
							|  |  |  | 		function(tags, mode, flatten){ | 
					
						
							|  |  |  | 			var selector = mode == 'any' ? 'getTaggedByAny' : 'getTaggedByAll' | 
					
						
							|  |  |  | 			this.crop(this.data[selector](tags), flatten) | 
					
						
							|  |  |  | 		}], | 
					
						
							| 
									
										
										
										
											2015-12-20 08:12:56 +03:00
										 |  |  | }) | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-20 08:12:56 +03:00
										 |  |  | var Crop = | 
					
						
							|  |  |  | module.Crop = core.ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: '', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'crop', | 
					
						
							|  |  |  | 	depends: [ | 
					
						
							|  |  |  | 		'base', | 
					
						
							|  |  |  | 	], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	actions: CropActions, | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | // Image Group...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var ImageGroupActions = | 
					
						
							|  |  |  | module.ImageGroupActions = actions.Actions({ | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 	// grouping...
 | 
					
						
							|  |  |  | 	// XXX need to tell .images about this...
 | 
					
						
							|  |  |  | 	group: ['- Group|Edit/Group images',  | 
					
						
							|  |  |  | 		function(gids, group){ this.data.group(gids, group) }], | 
					
						
							|  |  |  | 	ungroup: ['Group|Edit/Ungroup images',  | 
					
						
							|  |  |  | 		function(gids, group){ this.data.ungroup(gids, group) }], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// direction can be:
 | 
					
						
							|  |  |  | 	// 	'next'
 | 
					
						
							|  |  |  | 	// 	'prev'
 | 
					
						
							|  |  |  | 	groupTo: ['- Group|Edit/Group to',  | 
					
						
							|  |  |  | 		function(target, direction){ | 
					
						
							|  |  |  | 			target = this.data.getImage(target) | 
					
						
							|  |  |  | 			var other = this.data.getImage(target, direction == 'next' ? 1 : -1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// we are start/end of ribbon...
 | 
					
						
							|  |  |  | 			if(other == null){ | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			 | 
					
						
							|  |  |  | 			// add into an existing group...
 | 
					
						
							|  |  |  | 			if(this.data.isGroup(other)){ | 
					
						
							|  |  |  | 				this.group(target, other) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// new group...
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				this.group([target, other]) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 	// shorthands to .groupTo(..)
 | 
					
						
							|  |  |  | 	groupBack: ['Group|Edit/Group target image with the image or group before it',  | 
					
						
							|  |  |  | 		function(target){ this.groupTo(target, 'prev') }], | 
					
						
							|  |  |  | 	groupForward: ['Group|Edit/Group target image with the image or group after it',  | 
					
						
							|  |  |  | 		function(target){ this.groupTo(target, 'next') }], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// NOTE: this will only group loaded images...
 | 
					
						
							|  |  |  | 	groupMarked: ['Group|Mark/Group loaded marked images',  | 
					
						
							|  |  |  | 		function(){ this.group(this.data.getImages(this.data.getTaggedByAny('marked'))) }], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	expandGroup: ['Group/Expand group',  | 
					
						
							|  |  |  | 		function(target){ this.data.expandGroup(target || this.current) }], | 
					
						
							|  |  |  | 	collapseGroup: ['Group/Collapse group',  | 
					
						
							|  |  |  | 		function(target){ this.data.collapseGroup(target || this.current) }], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cropGroup: ['Crop|Group/Crop group',  | 
					
						
							|  |  |  | 		function(target){ this.crop(this.data.cropGroup(target || this.current)) }], | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-20 08:12:56 +03:00
										 |  |  | var ImageGroup = | 
					
						
							|  |  |  | module.ImageGroup = core.ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: '', | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-20 08:12:56 +03:00
										 |  |  | 	tag: 'image-group', | 
					
						
							|  |  |  | 	depends: [ | 
					
						
							|  |  |  | 		'base', | 
					
						
							|  |  |  | 	], | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-20 08:12:56 +03:00
										 |  |  | 	actions: ImageGroupActions, | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-20 08:12:56 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | // Meta base features...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // full features base...
 | 
					
						
							|  |  |  | core.ImageGridFeatures.Feature('base-full', [ | 
					
						
							|  |  |  | 	'base', | 
					
						
							|  |  |  | 	'tags', | 
					
						
							| 
									
										
										
										
											2016-04-22 16:13:20 +03:00
										 |  |  | 	'sort', | 
					
						
							| 
									
										
										
										
											2015-12-20 08:12:56 +03:00
										 |  |  | 	'crop', | 
					
						
							|  |  |  | 	'image-group', | 
					
						
							|  |  |  | ]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-02 19:30:48 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | // Journal...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function logImageShift(action){ | 
					
						
							|  |  |  | 	return [action.slice(-4) != '.pre' ?  | 
					
						
							|  |  |  | 			action + '.pre'  | 
					
						
							|  |  |  | 			: action, | 
					
						
							|  |  |  | 		function(target){ | 
					
						
							|  |  |  | 			target = this.data.getImage(target) | 
					
						
							|  |  |  | 			var args = args2array(arguments) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var o = this.data.getImageOrder(target) | 
					
						
							|  |  |  | 			var r = this.data.getRibbon(target) | 
					
						
							|  |  |  | 			var current = this.current | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return function(){ | 
					
						
							|  |  |  | 				var on = this.data.getImageOrder(target) | 
					
						
							|  |  |  | 				var rn = this.data.getRibbon(target) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if(o == on || r == rn){  | 
					
						
							|  |  |  | 					/* | 
					
						
							|  |  |  | 					this.journalPush( | 
					
						
							|  |  |  | 						this.current,  | 
					
						
							|  |  |  | 						action,  | 
					
						
							|  |  |  | 						args, | 
					
						
							|  |  |  | 						{ | 
					
						
							|  |  |  | 							before: [r, o], | 
					
						
							|  |  |  | 							after: [rn, on], | 
					
						
							|  |  |  | 						}) | 
					
						
							|  |  |  | 					*/ | 
					
						
							|  |  |  | 					this.journalPush({ | 
					
						
							|  |  |  | 						type: 'shift', | 
					
						
							|  |  |  | 						current: current,  | 
					
						
							|  |  |  | 						target: target, | 
					
						
							|  |  |  | 						action: action,  | 
					
						
							|  |  |  | 						args: args, | 
					
						
							|  |  |  | 						undo: journalActions[action], | 
					
						
							|  |  |  | 						diff: { | 
					
						
							|  |  |  | 							before: [r, o], | 
					
						
							|  |  |  | 							after: [rn, on], | 
					
						
							|  |  |  | 						}, | 
					
						
							|  |  |  | 					}) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				 | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}] | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Format:
 | 
					
						
							|  |  |  | // 	{
 | 
					
						
							|  |  |  | // 		<action>: <undo-action> | <undo-function> | null,
 | 
					
						
							|  |  |  | // 		...
 | 
					
						
							|  |  |  | // 	}
 | 
					
						
							|  |  |  | var journalActions = { | 
					
						
							|  |  |  | 	clear: null, | 
					
						
							|  |  |  | 	load: null, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	setBaseRibbon: null, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX need to account for position change, i.e. if action had no 
 | 
					
						
							|  |  |  | 	// 		effect then do nothing...
 | 
					
						
							|  |  |  | 	// 		...take target position before and after...
 | 
					
						
							|  |  |  | 	shiftImageTo: null, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	shiftImageUp: 'shiftImageDown', | 
					
						
							|  |  |  | 	shiftImageDown: 'shiftImageUp', | 
					
						
							|  |  |  | 	shiftImageLeft: 'shiftImageRight', | 
					
						
							|  |  |  | 	shiftImageRight: 'shiftImageLeft', | 
					
						
							|  |  |  | 	shiftRibbonUp: 'shiftRibbonDown', | 
					
						
							|  |  |  | 	shiftRibbonDown: 'shiftRibbonUp', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rotateCW: 'rotateCCW', | 
					
						
							|  |  |  | 	rotateCCW: 'rotateCW', | 
					
						
							|  |  |  | 	flipHorizontal: 'flipHorizontal', | 
					
						
							|  |  |  | 	flipVertical: 'flipVertical', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	sortImages: null, | 
					
						
							|  |  |  | 	reverseImages: 'reverseImages', | 
					
						
							|  |  |  | 	reverseRibbons: 'reverseRibbons', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	crop: null, | 
					
						
							|  |  |  | 	uncrop: null, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: null,  | 
					
						
							|  |  |  | 	untag: null, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	group: null, | 
					
						
							|  |  |  | 	ungroup: null, | 
					
						
							|  |  |  | 	expandGroup: null, | 
					
						
							|  |  |  | 	collapseGroup: null, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	runJournal: null, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // XXX is this the right level for this???
 | 
					
						
							|  |  |  | // 		...data seems to be a better candidate...
 | 
					
						
							|  |  |  | // 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 Journal =  | 
					
						
							|  |  |  | module.Journal = core.ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: 'Action Journal', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'system-journal', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	depends: ['base'], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	actions: actions.Actions({ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		journal: null, | 
					
						
							|  |  |  | 		rjournal: null, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		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)) | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// XXX might be good to add some kind of metadata to journal...
 | 
					
						
							|  |  |  | 		journalPush: ['- Journal/Add an item to journal', | 
					
						
							|  |  |  | 			function(data){ | 
					
						
							|  |  |  | 				this.journal = (this.hasOwnProperty('journal')  | 
					
						
							|  |  |  | 						|| this.journal) ?  | 
					
						
							|  |  |  | 					this.journal  | 
					
						
							|  |  |  | 					: [] | 
					
						
							|  |  |  | 				this.journal.push(data) | 
					
						
							|  |  |  | 			}], | 
					
						
							|  |  |  | 		clearJournal: ['Journal/Clear the action journal', | 
					
						
							|  |  |  | 			function(){ | 
					
						
							|  |  |  | 				if(this.journal){ | 
					
						
							|  |  |  | 					// 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 = null | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}], | 
					
						
							|  |  |  | 		runJournal: ['- Journal/Run journal', | 
					
						
							|  |  |  | 			function(journal){ | 
					
						
							|  |  |  | 				var that = this | 
					
						
							|  |  |  | 				journal.forEach(function(e){ | 
					
						
							|  |  |  | 					// load state...
 | 
					
						
							|  |  |  | 					that | 
					
						
							|  |  |  | 						.focusImage(e.current) | 
					
						
							|  |  |  | 						// run action...
 | 
					
						
							|  |  |  | 						[e.action].apply(that, e.args) | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// XXX need to clear the rjournal as soon as we do something...
 | 
					
						
							|  |  |  | 		// 		...at this point it is really easy to mess things up by
 | 
					
						
							|  |  |  | 		// 		undoing something, and after some actions doing a 
 | 
					
						
							|  |  |  | 		// 		.redoLast(..)
 | 
					
						
							|  |  |  | 		// XXX this is not ready for production...
 | 
					
						
							|  |  |  | 		undoLast: ['Journal/Undo last', | 
					
						
							|  |  |  | 			function(){ | 
					
						
							|  |  |  | 				var journal = this.journal | 
					
						
							|  |  |  | 				this.rjournal = (this.hasOwnProperty('rjournal')  | 
					
						
							|  |  |  | 						|| this.rjournal) ?  | 
					
						
							|  |  |  | 					this.rjournal  | 
					
						
							|  |  |  | 					: [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				for(var i = journal.length-1; i >= 0; i--){ | 
					
						
							|  |  |  | 					var a = journal[i] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					// we undo only a very specific set of actions...
 | 
					
						
							|  |  |  | 					if(a.undo && a.type == 'shift' && a.args.length == 0){ | 
					
						
							|  |  |  | 						this | 
					
						
							|  |  |  | 							.focusImage(a.current) | 
					
						
							|  |  |  | 							[a.undo].call(this, a.target) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						// pop the undo command...
 | 
					
						
							|  |  |  | 						this.journal.pop() | 
					
						
							|  |  |  | 						this.rjournal.push(journal.splice(i, 1)[0]) | 
					
						
							|  |  |  | 						break | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}], | 
					
						
							|  |  |  | 		_redoLast: ['Journal/Redo last', | 
					
						
							|  |  |  | 			function(){ | 
					
						
							|  |  |  | 				if(!this.rjournal || this.rjournal.length == 0){ | 
					
						
							|  |  |  | 					return | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				this.runJournal([this.rjournal.pop()]) | 
					
						
							|  |  |  | 			}], | 
					
						
							|  |  |  | 	}), | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// log state, action and its args... 
 | 
					
						
							|  |  |  | 	// 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.
 | 
					
						
							|  |  |  | 	// XXX would be good if we could know the name of the action in the 
 | 
					
						
							|  |  |  | 	// 		handler, thus enabling us to define a single handler rather
 | 
					
						
							|  |  |  | 	// 		than generating a custom handler per action...
 | 
					
						
							|  |  |  | 	handlers: [ | 
					
						
							|  |  |  | 		logImageShift('shiftImageTo'), | 
					
						
							|  |  |  | 		logImageShift('shiftImageUp'), | 
					
						
							|  |  |  | 		logImageShift('shiftImageDown'), | 
					
						
							|  |  |  | 		logImageShift('shiftImageLeft'), | 
					
						
							|  |  |  | 		logImageShift('shiftImageRight'), | 
					
						
							|  |  |  | 		logImageShift('shiftRibbonUp'), | 
					
						
							|  |  |  | 		logImageShift('shiftRibbonDown'), | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	].concat([ | 
					
						
							|  |  |  | 			'clear', | 
					
						
							|  |  |  | 			'load', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			'setBaseRibbon', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			'rotateCW', | 
					
						
							|  |  |  | 			'rotateCCW', | 
					
						
							|  |  |  | 			'flipHorizontal', | 
					
						
							|  |  |  | 			'flipVertical', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			'sortImages', | 
					
						
							|  |  |  | 			'reverseImages', | 
					
						
							|  |  |  | 			'reverseRibbons', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			'crop', | 
					
						
							|  |  |  | 			'uncrop', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			'tag',  | 
					
						
							|  |  |  | 			'untag', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			'group', | 
					
						
							|  |  |  | 			'ungroup', | 
					
						
							|  |  |  | 			'expandGroup', | 
					
						
							|  |  |  | 			'collapseGroup', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			//'runJournal',
 | 
					
						
							|  |  |  | 		].map(function(action){ | 
					
						
							|  |  |  | 			return [ | 
					
						
							|  |  |  | 				action+'.pre',  | 
					
						
							|  |  |  | 				function(){ | 
					
						
							|  |  |  | 					this.journalPush({ | 
					
						
							|  |  |  | 						type: 'basic', | 
					
						
							|  |  |  | 						current: this.current,  | 
					
						
							|  |  |  | 						action: action,  | 
					
						
							|  |  |  | 						args: args2array(arguments), | 
					
						
							|  |  |  | 					}) | 
					
						
							|  |  |  | 				}] | 
					
						
							|  |  |  | 		})),  | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | /********************************************************************** | 
					
						
							|  |  |  | * vim:set ts=4 sw=4 :                                                */ | 
					
						
							|  |  |  | return module }) |