| 
									
										
										
										
											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') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NOTE: if not state is set this assumes that the first state is the 
 | 
					
						
							|  |  |  | // 		default...
 | 
					
						
							|  |  |  | var makeConfigToggler =  | 
					
						
							|  |  |  | module.makeConfigToggler =  | 
					
						
							|  |  |  | function(attr, states, callback){ | 
					
						
							|  |  |  | 	return Toggler(null, | 
					
						
							|  |  |  | 		function(_, action){ | 
					
						
							|  |  |  | 			var lst = states.constructor === Array ? states : states.call(this) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			//console.log('action', action)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if(action == null){ | 
					
						
							|  |  |  | 				return this.config[attr] || lst[lst.indexOf('none')] || lst[0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				this.config[attr] = action | 
					
						
							|  |  |  | 				//this.focusImage()
 | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		states, | 
					
						
							|  |  |  | 		callback || function(action){ action != null && this.focusImage() }) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*********************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // 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', | 
					
						
							| 
									
										
										
										
											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', | 
					
						
							|  |  |  | 		makeConfigToggler('ribbon-focus-mode',  | 
					
						
							|  |  |  | 			function(){ return this.config['ribbon-focus-modes'] })], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// basic life-cycle actions...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX do we need to call .syncTags(..) here???
 | 
					
						
							|  |  |  | 	load: ['- File|Interface/', | 
					
						
							|  |  |  | 		function(d){ | 
					
						
							|  |  |  | 			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...
 | 
					
						
							|  |  |  | 	loadURLs: ['File/Load a URL list', | 
					
						
							|  |  |  | 		function(lst, base){ | 
					
						
							|  |  |  | 			this.clear() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			this.images = images.Images.fromArray(lst, base) | 
					
						
							|  |  |  | 			this.data = data.Data.fromArray(this.images.keys()) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 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 | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 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
 | 
					
						
							|  |  |  | 	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 04:29:44 +03:00
										 |  |  | 			// unknown mode...
 | 
					
						
							| 
									
										
										
										
											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))]) | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX should these be here???
 | 
					
						
							|  |  |  | 	prevTagged: ['- Navigate/Previous image tagged with tag', | 
					
						
							|  |  |  | 		makeTagWalker('prev')], | 
					
						
							|  |  |  | 	nextTagged: ['- Navigate/Next image tagged with tag', | 
					
						
							|  |  |  | 		makeTagWalker('next')], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	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...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX this also requires images...
 | 
					
						
							|  |  |  | 	sortImages: ['Sort/', | 
					
						
							|  |  |  | 		function(method){  | 
					
						
							|  |  |  | 		}], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 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') }], | 
					
						
							| 
									
										
										
										
											2015-12-20 08:12:56 +03:00
										 |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Base = | 
					
						
							|  |  |  | module.Base = core.ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: 'ImageGrid base', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'base', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	actions: BaseActions, | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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 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
										 |  |  | 
 | 
					
						
							|  |  |  | 	// 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', | 
					
						
							|  |  |  | 	'crop', | 
					
						
							|  |  |  | 	'image-group', | 
					
						
							|  |  |  | ]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-17 03:34:20 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | /********************************************************************** | 
					
						
							|  |  |  | * vim:set ts=4 sw=4 :                                                */ | 
					
						
							|  |  |  | return module }) |