| 
									
										
										
										
											2024-10-27 11:01:12 +03:00
										 |  |  | /********************************************************************** | 
					
						
							|  |  |  | *  | 
					
						
							|  |  |  | * | 
					
						
							|  |  |  | * | 
					
						
							|  |  |  | **********************************************************************/ | 
					
						
							|  |  |  | ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) | 
					
						
							|  |  |  | (function(require){ var module={} // make module AMD/node compatible...
 | 
					
						
							|  |  |  | /*********************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var actions = require('lib/actions') | 
					
						
							|  |  |  | var features = require('lib/features') | 
					
						
							|  |  |  | var toggler = require('lib/toggler') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var core = require('features/core') | 
					
						
							|  |  |  | var widgets = require('features/ui-widgets') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var overlay = require('lib/widget/overlay') | 
					
						
							|  |  |  | var browse = require('lib/widget/browse') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*********************************************************************/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // XXX add sorting on load....
 | 
					
						
							|  |  |  | // XXX save sort cache??? 
 | 
					
						
							|  |  |  | // XXX should this be split into edit/view???
 | 
					
						
							|  |  |  | var SortActions =  | 
					
						
							|  |  |  | module.SortActions = actions.Actions({ | 
					
						
							|  |  |  | 	config: { | 
					
						
							|  |  |  | 		// Default sort method...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// 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 ctime'
 | 
					
						
							|  |  |  | 		'default-sort': 'Date', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Default sort order...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// can be: 'default', 'reverse')
 | 
					
						
							|  |  |  | 		'default-sort-order': 'default', | 
					
						
							|  |  |  | 		 | 
					
						
							|  |  |  | 		// Sort methods...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// 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')
 | 
					
						
							|  |  |  | 		// NOTE: all sort methods are terminated with 'keep-position' so 
 | 
					
						
							|  |  |  | 		// 		as to prevent shuffling of images that are not usable with
 | 
					
						
							|  |  |  | 		// 		the previous methods in chain...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// XXX should 'reverse' position have an effect???
 | 
					
						
							|  |  |  | 		// 		...currently only arity is used...
 | 
					
						
							|  |  |  | 		'sort-methods': { | 
					
						
							|  |  |  | 			'none': '', | 
					
						
							|  |  |  | 			// NOTE: for when date resolution is not good enough this 
 | 
					
						
							|  |  |  | 			// 		also takes into account file sequence number...
 | 
					
						
							|  |  |  | 			// NOTE: this is descending by default...
 | 
					
						
							|  |  |  | 			// NOTE: if a method starts with a lower case letter it is 
 | 
					
						
							|  |  |  | 			// 		considered an alias and will be hidden from the UI...
 | 
					
						
							|  |  |  | 			'Date':  | 
					
						
							|  |  |  | 				'image-date name-sequence reverse', | 
					
						
							|  |  |  | 			'File date':  | 
					
						
							|  |  |  | 				'file-date reverse', | 
					
						
							|  |  |  | 			'File sequence number (with overflow)':  | 
					
						
							|  |  |  | 				'name-sequence-overflow name path', | 
					
						
							|  |  |  | 			'File sequence number':  | 
					
						
							|  |  |  | 				'name-sequence name path', | 
					
						
							|  |  |  | 			'Name':  | 
					
						
							|  |  |  | 				'name path', | 
					
						
							|  |  |  | 			'Name (natural number order)':  | 
					
						
							|  |  |  | 				'name-leading-sequence name path', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			'Ribbon order': | 
					
						
							|  |  |  | 				'sort-via-ribbons keep-position', | 
					
						
							|  |  |  | 			'Reverse ribbon order': | 
					
						
							|  |  |  | 				'sort-via-ribbons-reverse keep-position', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// aliases...
 | 
					
						
							|  |  |  | 			'example-sort-alias': | 
					
						
							|  |  |  | 				'Date "File date" Name', | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	toggleDefaultSortOrder: ['- Edit|Sort/Default sort order', | 
					
						
							|  |  |  | 		core.makeConfigToggler('default-sort-order', ['default', 'reverse'])], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// helpers...
 | 
					
						
							|  |  |  | 	// XXX should these be actions???
 | 
					
						
							|  |  |  | 	// XXX de we need this to be recursive???
 | 
					
						
							|  |  |  | 	// 		...or do we need a recursive expansion action???
 | 
					
						
							|  |  |  | 	getSortMethods: ['- Sort/', | 
					
						
							|  |  |  | 		core.doc`Get sort method value...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Get all methods... | 
					
						
							|  |  |  | 			.getSortMethods() | 
					
						
							|  |  |  | 				-> methods | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Get one specific method... | 
					
						
							|  |  |  | 			.getSortMethods(method) | 
					
						
							|  |  |  | 				-> [method, ..] | 
					
						
							|  |  |  | 				-> null | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Get specific methods... | 
					
						
							|  |  |  | 			.getSortMethods(method, method, ..) | 
					
						
							|  |  |  | 			.getSortMethods([method, method, ..]) | 
					
						
							|  |  |  | 				-> methods | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		methods format: | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				<method-name>: [method, ...], | 
					
						
							|  |  |  | 				... | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		NOTE: this is non-recursive... | 
					
						
							|  |  |  | 		`,
 | 
					
						
							|  |  |  | 		function(...methods){ | 
					
						
							|  |  |  | 			var that = this | 
					
						
							|  |  |  | 			// normalize args...
 | 
					
						
							|  |  |  | 			methods = methods.length == 0 ? | 
					
						
							|  |  |  | 					Object.keys(this.config['sort-methods'] || {}) | 
					
						
							|  |  |  | 				: methods.length == 1 ? | 
					
						
							|  |  |  | 					methods.pop() | 
					
						
							|  |  |  | 				: methods | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var splitMethods = function(m){ | 
					
						
							|  |  |  | 				return (m instanceof Array ?  | 
					
						
							|  |  |  | 							m  | 
					
						
							|  |  |  | 						: typeof(m) == typeof('str') ? | 
					
						
							|  |  |  | 							m | 
					
						
							|  |  |  | 								.split(/'([^']*)'|"([^"]*)"| +/) | 
					
						
							|  |  |  | 								.filter(function(e){  | 
					
						
							|  |  |  | 									return e && e.trim() != '' && !/['"]/.test(e) })  | 
					
						
							|  |  |  | 						: []) | 
					
						
							|  |  |  | 					.flat() } | 
					
						
							|  |  |  | 			var get = function(name){ | 
					
						
							|  |  |  | 				// normalize name...
 | 
					
						
							|  |  |  | 				name = name | 
					
						
							|  |  |  | 					.trim() | 
					
						
							|  |  |  | 					// remove quotes...
 | 
					
						
							|  |  |  | 					.replace(/^(["'])([^'"]*)(\1)$/, '$2') | 
					
						
							|  |  |  | 				var m = that.config['sort-methods'][name] | 
					
						
							|  |  |  | 					|| (that.__sort_methods__  | 
					
						
							|  |  |  | 						&& that.__sort_methods__[name]) | 
					
						
							|  |  |  | 					|| SortActions.__sort_methods__[name] | 
					
						
							|  |  |  | 				return typeof(m) == typeof('str') ?  | 
					
						
							|  |  |  | 					splitMethods(m)  | 
					
						
							|  |  |  | 					: m } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// return a single method...
 | 
					
						
							|  |  |  | 			if(!(methods instanceof Array)){ | 
					
						
							|  |  |  | 				return actions.ASIS( | 
					
						
							|  |  |  | 					get(methods)  | 
					
						
							|  |  |  | 					|| that.getSortMethods(splitMethods(methods)) | 
					
						
							|  |  |  | 					|| null) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// return multiple methods...
 | 
					
						
							|  |  |  | 			var res = {} | 
					
						
							|  |  |  | 			methods | 
					
						
							|  |  |  | 				.forEach(function(m){  | 
					
						
							|  |  |  | 					res[m] = m == 'reverse' ? | 
					
						
							|  |  |  | 						(res[m] || []).concat([m]) | 
					
						
							|  |  |  | 						: get(m) }) | 
					
						
							|  |  |  | 			return res }], | 
					
						
							|  |  |  | 	// XXX should this count 'reverese' arity???
 | 
					
						
							|  |  |  | 	expandSortMethod: ['- Sort/', | 
					
						
							|  |  |  | 		core.doc`Build list of basic sort methods...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			.expandSortMethod(method) | 
					
						
							|  |  |  | 				-> methods | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		The resulting list will contain either field names or method names  | 
					
						
							|  |  |  | 		contained in .__sort_methods__ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		NOTE: this will not remove repeating methods. | 
					
						
							|  |  |  | 		`,
 | 
					
						
							|  |  |  | 		function(method, seen){ | 
					
						
							|  |  |  | 			var that = this | 
					
						
							|  |  |  | 			var reverse = false | 
					
						
							|  |  |  | 			seen = seen || [] | 
					
						
							|  |  |  | 			if(seen.indexOf(method) >= 0){ | 
					
						
							|  |  |  | 				throw new Error('Sort method loop detected.') } | 
					
						
							|  |  |  | 			var methods = that.config['sort-methods'] || [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return (method instanceof Array ?  | 
					
						
							|  |  |  | 					method  | 
					
						
							|  |  |  | 					: that.getSortMethods(method) | 
					
						
							|  |  |  | 						.run(function(){ | 
					
						
							|  |  |  | 							return Object.entries(this) | 
					
						
							|  |  |  | 								.map(function([key, value]){ | 
					
						
							|  |  |  | 									return value == null ?  | 
					
						
							|  |  |  | 										key  | 
					
						
							|  |  |  | 										: value }) | 
					
						
							|  |  |  | 								.flat() })) | 
					
						
							|  |  |  | 				.map(function(method){  | 
					
						
							|  |  |  | 					var a = SortActions.__sort_methods__[method] | 
					
						
							|  |  |  | 						|| (that.__sort_methods__ && that.__sort_methods__[method]) | 
					
						
							|  |  |  | 					// expand local aliases...
 | 
					
						
							|  |  |  | 					return method in methods ? | 
					
						
							|  |  |  | 						   that.expandSortMethod(methods[method], seen.concat([method]))  | 
					
						
							|  |  |  | 						// expand system aliases...
 | 
					
						
							|  |  |  | 						: typeof(a) == typeof('str') ?  | 
					
						
							|  |  |  | 							that.expandSortMethod(a, seen.concat([method])) | 
					
						
							|  |  |  | 						: a instanceof Array ? | 
					
						
							|  |  |  | 							a | 
					
						
							|  |  |  | 						: method }) | 
					
						
							|  |  |  | 				// count reverse arity...
 | 
					
						
							|  |  |  | 				.filter(function(e){ | 
					
						
							|  |  |  | 					reverse = e == 'reverse' ? !reverse : reverse | 
					
						
							|  |  |  | 					return e != 'reverse' }) | 
					
						
							|  |  |  | 				.concat(reverse ?  | 
					
						
							|  |  |  | 					'reverse'  | 
					
						
							|  |  |  | 					: []) | 
					
						
							|  |  |  | 				.flat() }], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Custom sort methods...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// Format:
 | 
					
						
							|  |  |  | 	// 	{
 | 
					
						
							|  |  |  | 	// 		<method-name>: function(){
 | 
					
						
							|  |  |  | 	// 			...
 | 
					
						
							|  |  |  | 	// 			return function(a, b){ ... }
 | 
					
						
							|  |  |  | 	// 		},
 | 
					
						
							|  |  |  | 	// 		...
 | 
					
						
							|  |  |  | 	// 	}
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// The methods are cmp constructors rather than direct cmp functions
 | 
					
						
							|  |  |  | 	// to enable index construction and other more complicated sort 
 | 
					
						
							|  |  |  | 	// approaches...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// NOTE: the cmp function is called in the actions context.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX add progress...
 | 
					
						
							|  |  |  | 	// XXX add doc support -- make this an action-set???...
 | 
					
						
							|  |  |  | 	// XXX add alias and string support...
 | 
					
						
							|  |  |  | 	__sort_methods__: { | 
					
						
							|  |  |  | 		// aliases...
 | 
					
						
							|  |  |  | 		'image-date': | 
					
						
							|  |  |  | 			'image-create-date', | 
					
						
							|  |  |  | 		'file-date': | 
					
						
							|  |  |  | 			'file-create-date', | 
					
						
							|  |  |  | 		// XXX
 | 
					
						
							|  |  |  | 		//'modify-date':
 | 
					
						
							|  |  |  | 		//	'image-modify-date',
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		'image-create-date': | 
					
						
							|  |  |  | 			'metadata.date/timeOriginal file-date name-sequence', | 
					
						
							|  |  |  | 		//	'metadata.createDate birthtime ctime name-sequence',
 | 
					
						
							|  |  |  | 		 | 
					
						
							|  |  |  | 		'file-create-date': | 
					
						
							|  |  |  | 				'birthtime ctime', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// XXX make sequence sort methods compatible with repeating numbers,
 | 
					
						
							|  |  |  | 		// 		i.e. for file names like DSC_1234 sorting more than 10K files
 | 
					
						
							|  |  |  | 		// 		should split the repeating numbers by some other means, like
 | 
					
						
							|  |  |  | 		// 		date...
 | 
					
						
							|  |  |  | 		// NOTE: these will not sort items that have no seq in name...
 | 
					
						
							|  |  |  | 		'name-leading-sequence': function(){ | 
					
						
							|  |  |  | 			return 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(){ | 
					
						
							|  |  |  | 			return 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 } }, | 
					
						
							|  |  |  | 		// NOTE: this will actually sort twice, stage one build sort index and
 | 
					
						
							|  |  |  | 		// 		second stage is a O(n) lookup cmp...
 | 
					
						
							|  |  |  | 		// 		XXX not sure if this is the best way to go...
 | 
					
						
							|  |  |  | 		// XXX add ability to rotate sections...
 | 
					
						
							|  |  |  | 		'name-sequence-overflow': function(){ | 
					
						
							|  |  |  | 			var that = this | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var logger = this.logger  | 
					
						
							|  |  |  | 				&& this.logger.push('Sort indexing') | 
					
						
							|  |  |  | 			logger && logger.emit('queued', 'sequence sections') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// gap and gap length...
 | 
					
						
							|  |  |  | 			var gap = -1 | 
					
						
							|  |  |  | 			var l = 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var lst = this.images | 
					
						
							|  |  |  | 				.map(function(gid){  | 
					
						
							|  |  |  | 					return [gid, that.images.getImageNameSeq(gid)] }) | 
					
						
							|  |  |  | 				// keep only items with actual sequence numbers...
 | 
					
						
							|  |  |  | 				.filter(function([_, s]){ | 
					
						
							|  |  |  | 					return typeof(s) == typeof(123) }) | 
					
						
							|  |  |  | 				// sort by sequence...
 | 
					
						
							|  |  |  | 				.sort(function([x, a], [y, b]){  | 
					
						
							|  |  |  | 					return a - b }) | 
					
						
							|  |  |  | 				// find largest gaps...
 | 
					
						
							|  |  |  | 				.map(function(e, i, lst){ | 
					
						
							|  |  |  | 					var c = (lst[i+1] || e)[1] - e[1] | 
					
						
							|  |  |  | 					if(c > l){ | 
					
						
							|  |  |  | 						l = c | 
					
						
							|  |  |  | 						gap = i } | 
					
						
							|  |  |  | 					return e }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// calc gap between min/max accounting for overflow...
 | 
					
						
							|  |  |  | 			var a = lst[0][1] | 
					
						
							|  |  |  | 			var b = lst.slice(-1)[0][1] | 
					
						
							|  |  |  | 			var c = a + ( 10 ** (''+b).length ) - b - 2 | 
					
						
							|  |  |  | 			if(c > l){ | 
					
						
							|  |  |  | 				l = c | 
					
						
							|  |  |  | 				gap = -1 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// rotate index blocks...
 | 
					
						
							|  |  |  | 			if(l > 1 && gap >= 0){ | 
					
						
							|  |  |  | 				var tail = lst.splice(gap+1, lst.length) | 
					
						
							|  |  |  | 				lst = tail.concat(lst) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// build the actual lookup table...
 | 
					
						
							|  |  |  | 			var index = {} | 
					
						
							|  |  |  | 			lst.forEach(function(e, i){ | 
					
						
							|  |  |  | 				index[e[0]] = i }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			logger && logger.emit('done', 'sequence sections') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// return the lookup cmp...
 | 
					
						
							|  |  |  | 			return function(a, b){ | 
					
						
							|  |  |  | 				return (index[a] || 0) - (index[b] || 0) } }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Keep image order in each ribbon the same but sort ribbons (i.e. 
 | 
					
						
							|  |  |  | 		// images within ribbons) in ribbon order...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// e.g. all images in ribbon N are after images of ribbon <N 
 | 
					
						
							|  |  |  | 		// and before images in ribbons >N
 | 
					
						
							|  |  |  | 		'sort-via-ribbons': function(reverse){ | 
					
						
							|  |  |  | 			var that = this | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var logger = this.logger  | 
					
						
							|  |  |  | 				&& this.logger.push('Sort indexing') | 
					
						
							|  |  |  | 			logger && logger.emit('queued', 'ribbon order') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var index = new Map( | 
					
						
							|  |  |  | 				this.data.ribbon_order | 
					
						
							|  |  |  | 					// reverse?
 | 
					
						
							|  |  |  | 					.run(function(){ | 
					
						
							|  |  |  | 						return reverse ? | 
					
						
							|  |  |  | 							this.slice().reverse() | 
					
						
							|  |  |  | 							: this }) | 
					
						
							|  |  |  | 					.map(function(gid){ | 
					
						
							|  |  |  | 						return that.data.ribbons[gid] }) | 
					
						
							|  |  |  | 					.flat() | 
					
						
							|  |  |  | 					.compact() | 
					
						
							|  |  |  | 					.map(function(e, i){  | 
					
						
							|  |  |  | 						return [e, i] })) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			logger && logger.emit('done', 'ribbon order') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return function(a, b){ | 
					
						
							|  |  |  | 				a = index.get(a) | 
					
						
							|  |  |  | 				b = index.get(b) | 
					
						
							|  |  |  | 				return (a === undefined || b === undefined) ? | 
					
						
							|  |  |  | 					0 | 
					
						
							|  |  |  | 					: a - b } }, | 
					
						
							|  |  |  | 		'sort-via-ribbons-reverse': function(){ | 
					
						
							|  |  |  | 			return SortActions.__sort_methods__['sort-via-ribbons'].call(this, true) }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// This is specifically designed to terminate sort methods to prevent
 | 
					
						
							|  |  |  | 		// images that are not relevant to the previous order to stay in place
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// If this is explicitly included then 'reverse' order is ignored.
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		// XXX need to test how will this affect a set of images where part
 | 
					
						
							|  |  |  | 		// 		of the set is sortable an part is not...
 | 
					
						
							|  |  |  | 		// XXX legacy: this is added to every sort automatically...
 | 
					
						
							|  |  |  | 		// 		...do we still need this here???
 | 
					
						
							|  |  |  | 		'keep-position': function(){ | 
					
						
							|  |  |  | 			var order = this.data.order_index | 
					
						
							|  |  |  | 			return function(a, b){ | 
					
						
							|  |  |  | 				return order[a] - order[b] } }, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	// XXX would be nice to be able to sort a list of gids or a section
 | 
					
						
							|  |  |  | 	// 		of images...
 | 
					
						
							|  |  |  | 	// XXX should this handle manual sort order???
 | 
					
						
							|  |  |  | 	// XXX should reverse position have an effect???
 | 
					
						
							|  |  |  | 	// 		...currently only reverse arity is used...
 | 
					
						
							|  |  |  | 	sortImages: ['- Edit|Sort/Sort images', | 
					
						
							|  |  |  | 		core.doc`Sort images...
 | 
					
						
							|  |  |  | 	 | 
					
						
							|  |  |  | 		Sort using the default sort method | 
					
						
							|  |  |  | 		 .sortImages() | 
					
						
							|  |  |  | 			NOTE: the actual sort method used is set via  | 
					
						
							|  |  |  | 				.config['default-sort'] and .config['default-sort-order'] | 
					
						
							|  |  |  | 		 | 
					
						
							|  |  |  | 		Sort using a specific method(s): | 
					
						
							|  |  |  | 		.sortImages(<method>) | 
					
						
							|  |  |  | 		.sortImages(<method>, <reverse>) | 
					
						
							|  |  |  | 		 | 
					
						
							|  |  |  | 		.sortImages('<method> ..') | 
					
						
							|  |  |  | 		.sortImages('<method> ..', <reverse>) | 
					
						
							|  |  |  | 		 | 
					
						
							|  |  |  | 		.sortImages([<method>, ..]) | 
					
						
							|  |  |  | 		.sortImages([<method>, ..], <reverse>) | 
					
						
							|  |  |  | 			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'] | 
					
						
							|  |  |  | 			NOTE: if it is needed to reverse the method by default just | 
					
						
							|  |  |  | 				add 'reverse' to it's string. | 
					
						
							|  |  |  | 		 | 
					
						
							|  |  |  | 		Update current sort order: | 
					
						
							|  |  |  | 		.sortImages('update') | 
					
						
							|  |  |  | 			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 | 
					
						
							|  |  |  | 		 | 
					
						
							|  |  |  | 		Reverse image order: | 
					
						
							|  |  |  | 		.sortImages('reverse') | 
					
						
							|  |  |  | 		 | 
					
						
							|  |  |  | 		 | 
					
						
							|  |  |  | 		NOTE: if a sort method name contains a space it must be quoted either | 
					
						
							|  |  |  | 			in '"'s or in "'"s. | 
					
						
							|  |  |  | 		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 ctime' + ' reverse'  | 
					
						
							|  |  |  | 			will reverse the result's order while: | 
					
						
							|  |  |  | 				'metadata.createDate birthtime ctime reverse' + ' reverese'  | 
					
						
							|  |  |  | 			will cancel reversal. | 
					
						
							|  |  |  | 		NOTE: with empty images this will not do anything. | 
					
						
							|  |  |  | 		`,
 | 
					
						
							|  |  |  | 		function(method, reverse){  | 
					
						
							|  |  |  | 			var that = this | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if(method == 'reverse'){ | 
					
						
							|  |  |  | 				method = 'update' | 
					
						
							|  |  |  | 				reverse = true } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			reverse = reverse == null ? false  | 
					
						
							|  |  |  | 				: reverse == 'reverse'  | 
					
						
							|  |  |  | 				|| reverse | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// special case: 'update'
 | 
					
						
							|  |  |  | 			method = method == 'update' ? [] : method | 
					
						
							|  |  |  | 			// defaults...
 | 
					
						
							|  |  |  | 			method = method  | 
					
						
							|  |  |  | 				|| ((this.config['default-sort'] || 'image-date') | 
					
						
							|  |  |  | 					+ (this.config['default-sort-order'] == 'reverse' ? ' reverse' : '')) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// set sort method in data...
 | 
					
						
							|  |  |  | 			this.data.sort_method = typeof(method) == typeof('str') ?  | 
					
						
							|  |  |  | 				method  | 
					
						
							|  |  |  | 				: method.join(' ') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			method = this.expandSortMethod(method + (reverse ? ' reverse' : '')) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// get the reverse arity...
 | 
					
						
							|  |  |  | 			reverse = method[method.length - 1] == 'reverse' | 
					
						
							|  |  |  | 			reverse | 
					
						
							|  |  |  | 				&& method.pop() | 
					
						
							|  |  |  | 			reverse = reverse  | 
					
						
							|  |  |  | 				&& !method.includes('keep-position') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// can't sort if we know nothing about .images
 | 
					
						
							|  |  |  | 			if(method && method.length > 0 && (!this.images || this.images.length == 0)){ | 
					
						
							|  |  |  | 				return } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// build the compare routine...
 | 
					
						
							|  |  |  | 			method = method | 
					
						
							|  |  |  | 				// remove duplicate methods...
 | 
					
						
							|  |  |  | 				.unique() | 
					
						
							|  |  |  | 				.concat(['keep-position']) | 
					
						
							|  |  |  | 				.tailUnique() | 
					
						
							|  |  |  | 				.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) | 
					
						
							|  |  |  | 							// get attr...
 | 
					
						
							|  |  |  | 							var _get = function(obj){ | 
					
						
							|  |  |  | 								if(obj == null){ | 
					
						
							|  |  |  | 									return null } | 
					
						
							|  |  |  | 								for(var i=0; i<p.length; i++){ | 
					
						
							|  |  |  | 									obj = obj[p[i]] | 
					
						
							|  |  |  | 									if(obj === undefined){ | 
					
						
							|  |  |  | 										return null } } | 
					
						
							|  |  |  | 								return obj } | 
					
						
							|  |  |  | 							return function(a, b){ | 
					
						
							|  |  |  | 								a = _get(this.images[a]) | 
					
						
							|  |  |  | 								b = _get(this.images[b]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 								if(a == b | 
					
						
							|  |  |  | 										// not enough data to compare items, test next...
 | 
					
						
							|  |  |  | 										|| (a == null && b == null)){ | 
					
						
							|  |  |  | 									return 0 | 
					
						
							|  |  |  | 								} else if(a < b  | 
					
						
							|  |  |  | 										// keep stuff without value at the end...
 | 
					
						
							|  |  |  | 										|| a == null){ | 
					
						
							|  |  |  | 									return -1 | 
					
						
							|  |  |  | 								} else { | 
					
						
							|  |  |  | 									return +1 } | 
					
						
							|  |  |  | 							}}).call(that) }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// prepare the cmp function...
 | 
					
						
							|  |  |  | 			var cmp = method.length == 1 ?  | 
					
						
							|  |  |  | 				method[0]  | 
					
						
							|  |  |  | 				// chain compare -- return first non equal (non-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)...
 | 
					
						
							|  |  |  | 			if(method && method.length > 0 && this.images){ | 
					
						
							|  |  |  | 				this.data.order =  | 
					
						
							|  |  |  | 					reverse ?  | 
					
						
							|  |  |  | 						this.data.order.slice().sort(cmp.bind(this)).reverse() | 
					
						
							|  |  |  | 						: this.data.order.slice().sort(cmp.bind(this)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// just reverse...
 | 
					
						
							|  |  |  | 			} else if(method.length <= 0 && reverse) { | 
					
						
							|  |  |  | 				this.data.order.reverse() } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			this.data.updateImagePositions() }], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX should we merge manual order handling with .sortImages(..)???
 | 
					
						
							|  |  |  | 	// XXX currently this will not toggle past 'none'
 | 
					
						
							|  |  |  | 	toggleImageSort: ['- Edit|Sort/Image sort method', | 
					
						
							|  |  |  | 		core.doc`Toggle sort modes...
 | 
					
						
							|  |  |  | 		 | 
					
						
							|  |  |  | 		This is similar to sort images but it will also maintain  | 
					
						
							|  |  |  | 		.data.manual_order state. | 
					
						
							|  |  |  | 		 | 
					
						
							|  |  |  | 		NOTE: a state can be passed appended with reverse, e.g. | 
					
						
							|  |  |  | 			.toggleImageSort('Date') and .toggleImageSort('Date reverse') | 
					
						
							|  |  |  | 			both will set the sort method to 'Date' but the later will  | 
					
						
							|  |  |  | 			also reverse it. | 
					
						
							|  |  |  | 		`,
 | 
					
						
							|  |  |  | 		toggler.Toggler(null, | 
					
						
							|  |  |  | 			function(){  | 
					
						
							|  |  |  | 				return (this.data  | 
					
						
							|  |  |  | 					&& this.data.sort_method | 
					
						
							|  |  |  | 					&& (this.data.sort_method | 
					
						
							|  |  |  | 						.split(/'([^']*)'|"([^"]*)"| +/) | 
					
						
							|  |  |  | 							.filter(function(e){  | 
					
						
							|  |  |  | 								return e && e.trim() != '' && !/['"]/.test(e) })[0])) | 
					
						
							|  |  |  | 					|| 'none' }, | 
					
						
							|  |  |  | 			function(){  | 
					
						
							|  |  |  | 				return Object.keys(this.config['sort-methods']) | 
					
						
							|  |  |  | 					// manual...
 | 
					
						
							|  |  |  | 					.concat(this.data.sort_method == 'Manual' ? ['Manual'] : []) | 
					
						
							|  |  |  | 					// list saved sorts...
 | 
					
						
							|  |  |  | 					.concat(Object.keys(this.data.sort_order || {})) | 
					
						
							|  |  |  | 					.unique() }, | 
					
						
							|  |  |  | 			// prevent setting 'none' as mode...
 | 
					
						
							|  |  |  | 			function(mode){  | 
					
						
							|  |  |  | 				return !!this.images  | 
					
						
							|  |  |  | 					&& (mode != 'none'  | 
					
						
							|  |  |  | 						|| (mode == 'Manual' && (this.data.sort_cache || {})['Manual'])) }, | 
					
						
							|  |  |  | 			// XXX need to refactor the toggler a bit to make the 
 | 
					
						
							|  |  |  | 			// 		signature simpler... (???)
 | 
					
						
							|  |  |  | 			function(mode, _, reverse){  | 
					
						
							|  |  |  | 				reverse = reverse || '' | 
					
						
							|  |  |  | 				reverse = reverse === true ? 'reverse' : reverse | 
					
						
							|  |  |  | 				var cache = this.data.sort_cache = this.data.sort_cache || {} | 
					
						
							|  |  |  | 				var method = this.data.sort_method | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// cache sort order...
 | 
					
						
							|  |  |  | 				method == 'Manual' | 
					
						
							|  |  |  | 					&& this.saveOrder(method) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				var sort = `"${mode}" `+ reverse | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// saved sort order...
 | 
					
						
							|  |  |  | 				;(this.data.sort_order  | 
					
						
							|  |  |  | 						&& mode in this.data.sort_order) ? | 
					
						
							|  |  |  | 					this.loadOrder(mode, reverse == 'reverse') | 
					
						
							|  |  |  | 					: this.sortImages(sort) })], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX add drop/load actions...
 | 
					
						
							|  |  |  | 	saveOrder: ['- Sort/', | 
					
						
							|  |  |  | 		function(title){ | 
					
						
							|  |  |  | 			title = title || 'Manual' | 
					
						
							|  |  |  | 			var cache = this.data.sort_order = this.data.sort_order || {} | 
					
						
							|  |  |  | 			cache[title] = this.data.order.slice() }], | 
					
						
							|  |  |  | 	loadOrder: ['- Sort/', | 
					
						
							|  |  |  | 		function(title, reverse){ | 
					
						
							|  |  |  | 			var order = (this.data.sort_order || {})[title] | 
					
						
							|  |  |  | 			if(order){ | 
					
						
							|  |  |  | 				this.data.order = order.slice() | 
					
						
							|  |  |  | 				this.sortImages('update' + (reverse ? ' reverse' : '')) | 
					
						
							|  |  |  | 				this.data.sort_method = title } }], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX add drop/load actions...
 | 
					
						
							|  |  |  | 	cacheOrder: ['- Sort/', | 
					
						
							|  |  |  | 		function(){ | 
					
						
							|  |  |  | 			var method = this.data.sort_method | 
					
						
							|  |  |  | 			if(method){ | 
					
						
							|  |  |  | 				var cache = this.data.sort_cache = this.data.sort_cache || {} | 
					
						
							|  |  |  | 				cache[method] = this.data.order.slice() } }], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Store/load sort data:
 | 
					
						
							|  |  |  | 	// 	.data.sort_method		- current sort mode (optional)
 | 
					
						
							|  |  |  | 	// 	.data.sort_order		- saved sort order (optional)
 | 
					
						
							|  |  |  | 	// 	.data.sort_cache		- cached sort order (optional)
 | 
					
						
							|  |  |  | 	load: [function(data){ | 
					
						
							|  |  |  | 		return function(){ | 
					
						
							|  |  |  | 			var that = this | 
					
						
							|  |  |  | 			data.data | 
					
						
							|  |  |  | 				&& ['sort_method', 'sort_order', 'sort_cache'] | 
					
						
							|  |  |  | 					.forEach(function(attr){ | 
					
						
							|  |  |  | 						if(data.data[attr]){ | 
					
						
							|  |  |  | 							that.data[attr] = data.data[attr] } }) } }], | 
					
						
							|  |  |  | 	json: [function(){ | 
					
						
							|  |  |  | 		return function(res){ | 
					
						
							|  |  |  | 			var that = this | 
					
						
							|  |  |  | 			;['sort_method', 'sort_order', 'sort_cache'] | 
					
						
							|  |  |  | 				.forEach(function(attr){ | 
					
						
							|  |  |  | 					if(that.data[attr]){ | 
					
						
							|  |  |  | 						res.data[attr] = that.data[attr] } }) | 
					
						
							|  |  |  | 			// special case: unsaved manual order...
 | 
					
						
							|  |  |  | 			if(this.toggleImageSort('?') == 'Manual'){ | 
					
						
							|  |  |  | 				res.data.sort_order = res.sort_order || {} | 
					
						
							|  |  |  | 				res.data.sort_order['Manual'] = this.data.order.slice() } } }], | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Sort = | 
					
						
							|  |  |  | module.Sort = core.ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: '', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'sort', | 
					
						
							|  |  |  | 	depends: [ | 
					
						
							|  |  |  | 		'base', | 
					
						
							|  |  |  | 		// XXX should we split this to edit/view???
 | 
					
						
							|  |  |  | 		'edit', | 
					
						
							|  |  |  | 	], | 
					
						
							|  |  |  | 	suggested: [ | 
					
						
							|  |  |  | 		'ui-sort', | 
					
						
							|  |  |  | 	], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	actions: SortActions, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	handlers: [ | 
					
						
							|  |  |  | 		['shiftImageRight shiftImageLeft', | 
					
						
							|  |  |  | 			function(){ | 
					
						
							|  |  |  | 				this.data.sort_method = 'Manual' }], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// maintain .sort_order and .sort_cache separately from .data in
 | 
					
						
							|  |  |  | 		// the store...
 | 
					
						
							|  |  |  | 		['prepareIndexForWrite', | 
					
						
							|  |  |  | 			function(res){ | 
					
						
							|  |  |  | 				var c = res.changes | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if(!c){ | 
					
						
							|  |  |  | 					return } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				;['sort_order', 'sort_cache'] | 
					
						
							|  |  |  | 					.forEach(function(attr){ | 
					
						
							|  |  |  | 						if(!res.raw.data){ | 
					
						
							|  |  |  | 							return } | 
					
						
							|  |  |  | 						if((c === true || c[attr]) && res.raw.data[attr]){ | 
					
						
							|  |  |  | 							// full save...
 | 
					
						
							|  |  |  | 							if(c === true){ | 
					
						
							|  |  |  | 								res.index[attr] = res.raw.data[attr]  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 							// build diff...
 | 
					
						
							|  |  |  | 							} else { | 
					
						
							|  |  |  | 								var diff = {} | 
					
						
							|  |  |  | 								c[attr].forEach(function(k){  | 
					
						
							|  |  |  | 									diff[k] = res.raw.data[attr][k] }) | 
					
						
							|  |  |  | 								res.index[attr +'-diff'] = diff } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 							// cleanup...
 | 
					
						
							|  |  |  | 							delete res.index.data[attr] } }) }], | 
					
						
							|  |  |  | 		['prepareIndexForLoad', | 
					
						
							|  |  |  | 			function(res){ | 
					
						
							|  |  |  | 				['sort_order', 'sort_cache'] | 
					
						
							|  |  |  | 					.forEach(function(attr){ | 
					
						
							|  |  |  | 						if(res[attr]){ | 
					
						
							|  |  |  | 							res.data[attr] = res[attr] } }) }], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// manage changes...
 | 
					
						
							|  |  |  | 		['sortImages', | 
					
						
							|  |  |  | 			function(_, target){ this.markChanged('data') }], | 
					
						
							|  |  |  | 		// NOTE: this always saves to 'Manual' this is correct regardless
 | 
					
						
							|  |  |  | 		// 		of save mode as in the current logic, the only mode that 
 | 
					
						
							|  |  |  | 		// 		results from a manual shift is a manual sort...
 | 
					
						
							|  |  |  | 		// 		XXX this may pose a problem with saved sorts, the question
 | 
					
						
							|  |  |  | 		// 			is whether a saved mode can be edited or just saved or
 | 
					
						
							|  |  |  | 		// 			updated...
 | 
					
						
							|  |  |  | 		['shiftImageOrder', | 
					
						
							|  |  |  | 			function(){ this.markChanged('sort_order', ['Manual']) }], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		['saveOrder',  | 
					
						
							|  |  |  | 			function(_, title){ this.markChanged('sort_order', [title]) }], | 
					
						
							|  |  |  | 		['cacheOrder',  | 
					
						
							|  |  |  | 			function(){ this.markChanged('sort_cache', [this.data.sort_method]) }], | 
					
						
							|  |  |  | 	], | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // XXX add ability to partition ribbons in different modes...
 | 
					
						
							|  |  |  | // 		- by hour/day/month/year in date modes...
 | 
					
						
							|  |  |  | // 		- ???
 | 
					
						
							|  |  |  | var SortUIActions = actions.Actions({ | 
					
						
							|  |  |  | 	config: { | 
					
						
							|  |  |  | 		// If true expand the sort method alias tree...
 | 
					
						
							|  |  |  | 		//
 | 
					
						
							|  |  |  | 		'sort-doc-expand-methods': true, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX add links from method names to their expansions and actual 
 | 
					
						
							|  |  |  | 	// 		methods (docs)...
 | 
					
						
							|  |  |  | 	// 		...method docs do not exist at this point...
 | 
					
						
							|  |  |  | 	// XXX do a better action calling scheme...
 | 
					
						
							|  |  |  | 	// XXX should we also have a doc text??
 | 
					
						
							|  |  |  | 	// 		...if yes then it's a question where/how to store it...
 | 
					
						
							|  |  |  | 	showSortMethodDoc: ['- Sort/', | 
					
						
							|  |  |  | 		core.doc`Show sort method documentation...
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Show sort method doc... | 
					
						
							|  |  |  | 			.showSortMethodDoc(method) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Show sort method doc with expanded method list... | 
					
						
							|  |  |  | 			.showSortMethodDoc(method, true) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			Show sort method doc with flat method list... | 
					
						
							|  |  |  | 			.showSortMethodDoc(method, false) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		This will add actions with .sortMethod attribute as sort methods... | 
					
						
							|  |  |  | 		`,
 | 
					
						
							|  |  |  | 		widgets.makeUIDialog(function(method, expand, indent){ | 
					
						
							|  |  |  | 			var that = this | 
					
						
							|  |  |  | 			expand = expand || this.config['sort-doc-expand-methods'] || false | 
					
						
							|  |  |  | 			indent = indent || '  ' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var expandMethods = function(method){ | 
					
						
							|  |  |  | 				var methods = that.getSortMethods(method) | 
					
						
							|  |  |  | 				return [ methods instanceof Array || typeof(methods) == typeof('str') ?  | 
					
						
							|  |  |  | 						`<a href="javascript:ig.showSortMethodDoc('${method}', ${expand})">${method}</a>` | 
					
						
							|  |  |  | 						: method ] | 
					
						
							|  |  |  | 					.concat( | 
					
						
							|  |  |  | 						methods instanceof Array ? | 
					
						
							|  |  |  | 							methods | 
					
						
							|  |  |  | 								.map(!expand ?  | 
					
						
							|  |  |  | 									// !expand -> keep only the first/root item...
 | 
					
						
							|  |  |  | 									function(e){  | 
					
						
							|  |  |  | 										return expandMethods(e)[0] || [] }  | 
					
						
							|  |  |  | 									: expandMethods) | 
					
						
							|  |  |  | 								.flat() | 
					
						
							|  |  |  | 								.map(function(e){  | 
					
						
							|  |  |  | 									return indent + e }) | 
					
						
							|  |  |  | 						: typeof(methods) == typeof('str') ? | 
					
						
							|  |  |  | 							[indent + methods] | 
					
						
							|  |  |  | 						: []) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return $('<div class="help-dialog">') | 
					
						
							|  |  |  | 				.append($('<div class="sort-method">') | 
					
						
							|  |  |  | 					.prop('tabindex', true) | 
					
						
							|  |  |  | 					.append($('<h2>') | 
					
						
							|  |  |  | 						.text(method)) | 
					
						
							|  |  |  | 					.append($('<hr>')) | 
					
						
							|  |  |  | 					.append($('<pre>') | 
					
						
							|  |  |  | 						.html( | 
					
						
							|  |  |  | 							'Sort order:\n  ' | 
					
						
							|  |  |  | 							+ this.expandSortMethod(method) | 
					
						
							|  |  |  | 								.unique() | 
					
						
							|  |  |  | 								.join(', ') | 
					
						
							|  |  |  | 							+'\n\n' | 
					
						
							|  |  |  | 							+'Sort method tree:\n' | 
					
						
							|  |  |  | 							+ expandMethods(method) | 
					
						
							|  |  |  | 								// ignore the first item as we mention 
 | 
					
						
							|  |  |  | 								// it in the title...
 | 
					
						
							|  |  |  | 								.slice(1) | 
					
						
							|  |  |  | 								.join('\n')))) })], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX should we be able to edit modes??? 
 | 
					
						
							|  |  |  | 	// XXX should this be a toggler???
 | 
					
						
							|  |  |  | 	// XXX add "New from current order..."
 | 
					
						
							|  |  |  | 	sortDialog: ['Edit|Sort/Sort images...', | 
					
						
							|  |  |  | 		widgets.makeUIDialog(function(){ | 
					
						
							|  |  |  | 			var that = this | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var dfl = this.config['default-sort']  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var sort_actions = new Map( | 
					
						
							|  |  |  | 				that.actions | 
					
						
							|  |  |  | 					.filter(function(e){  | 
					
						
							|  |  |  | 						return that.getActionAttr(e, 'sortMethod') }) | 
					
						
							|  |  |  | 					.map(function(e){ | 
					
						
							|  |  |  | 						return [ | 
					
						
							|  |  |  | 							(that.getActionAttr(e, 'doc') || e).split(/[\\\/:]/).pop(), | 
					
						
							|  |  |  | 							e,  | 
					
						
							|  |  |  | 						] })) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// XXX might be a good idea to make this generic...
 | 
					
						
							|  |  |  | 			var _makeTogglHandler = function(toggler){ | 
					
						
							|  |  |  | 				return function(){ | 
					
						
							|  |  |  | 					var txt = $(this).find('.text').first().text() | 
					
						
							|  |  |  | 					that[toggler]() | 
					
						
							|  |  |  | 					o.update() | 
					
						
							|  |  |  | 						.then(function(){ o.select(txt) }) | 
					
						
							|  |  |  | 					that.toggleSlideshow('?') == 'on'  | 
					
						
							|  |  |  | 						&& o.parent.close() } } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var o = browse.makeLister(null, function(path, make){ | 
					
						
							|  |  |  | 				var lister = this | 
					
						
							|  |  |  | 				var cur = that.toggleImageSort('?') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// normal sort methods...
 | 
					
						
							|  |  |  | 				that.toggleImageSort('??') | 
					
						
							|  |  |  | 					.forEach(function(mode){ | 
					
						
							|  |  |  | 						// skip 'none'...
 | 
					
						
							|  |  |  | 						if(mode == 'none'){ | 
					
						
							|  |  |  | 							return } | 
					
						
							|  |  |  | 						make(mode, { | 
					
						
							|  |  |  | 							cls: [ | 
					
						
							|  |  |  | 								(mode == cur ? 'highlighted selected' : ''), | 
					
						
							|  |  |  | 								(mode == dfl ? 'default' : ''), | 
					
						
							|  |  |  | 							].join(' '), | 
					
						
							|  |  |  | 							// show only modes starting with upper case...
 | 
					
						
							|  |  |  | 							hidden: mode[0].toUpperCase() != mode[0], | 
					
						
							|  |  |  | 						}) | 
					
						
							|  |  |  | 						.on('open', function(){ | 
					
						
							|  |  |  | 							that.toggleImageSort(null, mode,  | 
					
						
							|  |  |  | 								that.config['default-sort-order'] == 'reverse') | 
					
						
							|  |  |  | 							lister.parent.close() }) })	 | 
					
						
							|  |  |  | 				// action sort methods...
 | 
					
						
							|  |  |  | 				if(sort_actions.size > 0){ | 
					
						
							|  |  |  | 					// XXX do we need this??
 | 
					
						
							|  |  |  | 					//make('---', {style: { opacity: 0.1, }})
 | 
					
						
							|  |  |  | 					;[...sort_actions.entries()] | 
					
						
							|  |  |  | 						.forEach(function([n, a]){ | 
					
						
							|  |  |  | 							make(n, { | 
					
						
							|  |  |  | 								disabled: that.getActionMode(a) == 'disabled', | 
					
						
							|  |  |  | 							}) | 
					
						
							|  |  |  | 							.on('open', function(){ | 
					
						
							|  |  |  | 								that[a]() | 
					
						
							|  |  |  | 								make.dialog.close() }) }) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// Commands...
 | 
					
						
							|  |  |  | 				make('---') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				make('$Reverse images') | 
					
						
							|  |  |  | 					.on('open', function(){ | 
					
						
							|  |  |  | 						that.reverseImages() | 
					
						
							|  |  |  | 						lister.parent.close() }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// Settings...
 | 
					
						
							|  |  |  | 				make('---') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				make(['Default order: ', that.config['default-sort-order'] || 'ascending']) | 
					
						
							|  |  |  | 					.on('open', _makeTogglHandler('toggleDefaultSortOrder')) | 
					
						
							|  |  |  | 					.addClass('item-value-view') | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			.run(function(){ | 
					
						
							|  |  |  | 				// handle '?' button to browse path...
 | 
					
						
							|  |  |  | 				this.showDoc = function(){ | 
					
						
							|  |  |  | 					var method = this.select('!').text() | 
					
						
							|  |  |  | 					method = sort_actions.get(method) || method | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					// normal sort method...
 | 
					
						
							|  |  |  | 					if(method in that.config['sort-methods']){ | 
					
						
							|  |  |  | 						that.showSortMethodDoc(method)  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					// sort action...
 | 
					
						
							|  |  |  | 					} else if(method in that){ | 
					
						
							|  |  |  | 						that.showDoc(method) } } | 
					
						
							|  |  |  | 				this.keyboard.handler('General', '?', 'showDoc') }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return o })], | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var SortUI =  | 
					
						
							|  |  |  | module.SortUI = core.ImageGridFeatures.Feature({ | 
					
						
							|  |  |  | 	title: '', | 
					
						
							|  |  |  | 	doc: '', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tag: 'ui-sort', | 
					
						
							|  |  |  | 	depends: [ | 
					
						
							|  |  |  | 		'ui', | 
					
						
							|  |  |  | 		'sort', | 
					
						
							|  |  |  | 	], | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	actions: SortUIActions, | 
					
						
							|  |  |  | }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /********************************************************************** | 
					
						
							|  |  |  | * vim:set ts=4 sw=4 :                               */ return module }) |