mirror of
				https://github.com/flynx/ImageGrid.git
				synced 2025-10-31 03:10:07 +00:00 
			
		
		
		
	refactoring: splitup data.js into several modules, still not clean-enough...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
		
							parent
							
								
									cf9d6b301f
								
							
						
					
					
						commit
						d2a065b09a
					
				
							
								
								
									
										193
									
								
								ui/crop.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										193
									
								
								ui/crop.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,193 @@ | |||||||
|  | /********************************************************************** | ||||||
|  | *  | ||||||
|  | * Ribbon Crop API | ||||||
|  | * | ||||||
|  | * | ||||||
|  | **********************************************************************/ | ||||||
|  | 
 | ||||||
|  | var CROP_STACK = [] | ||||||
|  | 
 | ||||||
|  | var CROP_MODES = [] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /******************************************************* Crop Data ***/ | ||||||
|  | 
 | ||||||
|  | function isViewCropped(){ | ||||||
|  | 	return CROP_STACK.length != 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | function getAllData(){ | ||||||
|  | 	if(!isViewCropped()){ | ||||||
|  | 		return DATA | ||||||
|  | 	} else { | ||||||
|  | 		return CROP_STACK[0] | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // NOTE: this will not update .current state...
 | ||||||
|  | // NOTE: when keep_ribbons is set, this may generate empty ribbons...
 | ||||||
|  | //
 | ||||||
|  | // XXX should this set the .current to anything but null or the first elem???
 | ||||||
|  | function makeCroppedData(gids, keep_ribbons){ | ||||||
|  | 	var res = { | ||||||
|  | 		varsion: '2.0', | ||||||
|  | 		current: null, | ||||||
|  | 		ribbons: [], | ||||||
|  | 		order: DATA.order.slice(), | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// flat single ribbon crop...
 | ||||||
|  | 	if(!keep_ribbons){ | ||||||
|  | 		res.ribbons[0] = gids | ||||||
|  | 
 | ||||||
|  | 	// keep the ribbon structure...
 | ||||||
|  | 	} else { | ||||||
|  | 		$.each(DATA.ribbons, function(_, e){ | ||||||
|  | 			e = e.filter(function(ee){ return gids.indexOf(ee) >= 0 }) | ||||||
|  | 			// skip empty ribbons...
 | ||||||
|  | 			if(e.length != 0){ | ||||||
|  | 				res.ribbons.push(e) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return res | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // NOTE: if keep_ribbons is not set this will ALWAYS build a single ribbon
 | ||||||
|  | // 		data-set...
 | ||||||
|  | function cropDataTo(gids, keep_ribbons){ | ||||||
|  | 	var prev_state = DATA | ||||||
|  | 	var cur = DATA.current | ||||||
|  | 	var r = getRibbonIndex() | ||||||
|  | 
 | ||||||
|  | 	var new_data = makeCroppedData(gids, keep_ribbons) | ||||||
|  | 
 | ||||||
|  | 	// do nothing if there is no change...
 | ||||||
|  | 	// XXX is there a better way to compare states???
 | ||||||
|  | 	if(JSON.stringify(DATA.ribbons) == JSON.stringify(new_data.ribbons)){ | ||||||
|  | 		return DATA | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	CROP_STACK.push(prev_state) | ||||||
|  | 	DATA = new_data | ||||||
|  | 
 | ||||||
|  | 	cur = getGIDBefore(cur, keep_ribbons ? r : 0) | ||||||
|  | 	cur = cur == null ? gids[0] : cur | ||||||
|  | 	DATA.current = cur  | ||||||
|  | 
 | ||||||
|  | 	reloadViewer() | ||||||
|  | 	updateImages() | ||||||
|  | 
 | ||||||
|  | 	return prev_state | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | function uncropData(){ | ||||||
|  | 	if(!isViewCropped()){ | ||||||
|  | 		return DATA | ||||||
|  | 	} | ||||||
|  | 	var prev_state = DATA | ||||||
|  | 	var cur = DATA.current | ||||||
|  | 
 | ||||||
|  | 	DATA = CROP_STACK.pop() | ||||||
|  | 
 | ||||||
|  | 	// check if cur exists in data being loaded...
 | ||||||
|  | 	if($.map(DATA.ribbons,  | ||||||
|  | 			function(e, i){ return e.indexOf(cur) >= 0 }).indexOf(true) >= 0){ | ||||||
|  | 		// keep the current position...
 | ||||||
|  | 		DATA.current = cur | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	reloadViewer() | ||||||
|  | 	updateImages() | ||||||
|  | 
 | ||||||
|  | 	return prev_state | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | function showAllData(){ | ||||||
|  | 	var prev_state = DATA | ||||||
|  | 	var cur = DATA.current | ||||||
|  | 
 | ||||||
|  | 	if(CROP_STACK.length != 0){ | ||||||
|  | 		DATA = getAllData() | ||||||
|  | 		CROP_STACK = [] | ||||||
|  | 
 | ||||||
|  | 		// XXX do we need to check if this exists???
 | ||||||
|  | 		// 		...in theory, as long as there are no global destructive 
 | ||||||
|  | 		// 		operations, no.
 | ||||||
|  | 		// keep the current position...
 | ||||||
|  | 		DATA.current = cur | ||||||
|  | 
 | ||||||
|  | 		reloadViewer() | ||||||
|  | 		updateImages() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return prev_state | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // Helpers for making crop modes and using crop...
 | ||||||
|  | 
 | ||||||
|  | // Make a generic crop mode toggler
 | ||||||
|  | //
 | ||||||
|  | // NOTE: This will add the toggler to CROP_MODES, for use by 
 | ||||||
|  | // 		uncropLastState(...)
 | ||||||
|  | // NOTE: crop modes are exclusive -- it is not possible to enter one crop
 | ||||||
|  | // 		mode from a different crop mode
 | ||||||
|  | function makeCropModeToggler(cls, crop){ | ||||||
|  | 	var res = createCSSClassToggler( | ||||||
|  | 			'.viewer', | ||||||
|  | 			//cls + ' cropped-mode',
 | ||||||
|  | 			cls, | ||||||
|  | 			function(action){ | ||||||
|  | 				// prevent mixing marked-only and single-ribbon modes...
 | ||||||
|  | 				if(action == 'on'  | ||||||
|  | 						&& isViewCropped() | ||||||
|  | 						&& res('?') != 'on'){ | ||||||
|  | 					return false | ||||||
|  | 				} | ||||||
|  | 			}, | ||||||
|  | 			function(action){ | ||||||
|  | 				if(action == 'on'){ | ||||||
|  | 					showStatusQ('Cropping current ribbon...') | ||||||
|  | 					crop() | ||||||
|  | 				} else { | ||||||
|  | 					showStatusQ('Uncropping to all data...') | ||||||
|  | 					showAllData() | ||||||
|  | 				} | ||||||
|  | 			}) | ||||||
|  | 	CROP_MODES.push(res) | ||||||
|  | 	return res | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // Uncrop to last state and there is no states to uncrop then exit 
 | ||||||
|  | // cropped mode.
 | ||||||
|  | //
 | ||||||
|  | // NOTE: this will exit all crop modes when uncropping the last step.
 | ||||||
|  | function uncropLastState(){ | ||||||
|  | 	// do nothing if we aren't in a crop mode...
 | ||||||
|  | 	if(!isViewCropped()){ | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// exit cropped all modes...
 | ||||||
|  | 	if(CROP_STACK.length == 1){ | ||||||
|  | 		$.each(CROP_MODES, function(_, e){ e('off') }) | ||||||
|  | 
 | ||||||
|  | 	// ucrop one state...
 | ||||||
|  | 	} else { | ||||||
|  | 		showStatusQ('Uncropping...') | ||||||
|  | 		uncropData() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /********************************************************************** | ||||||
|  | * vim:set ts=4 sw=4 :                                                */ | ||||||
							
								
								
									
										415
									
								
								ui/data.js
									
									
									
									
									
								
							
							
						
						
									
										415
									
								
								ui/data.js
									
									
									
									
									
								
							| @ -1,7 +1,11 @@ | |||||||
| /********************************************************************** | /********************************************************************** | ||||||
| *  | *  | ||||||
|  | * Data API and Data DOM connections... | ||||||
| * | * | ||||||
| * TODO move DATA to a more logical context avoiding the global vars... | * TODO move DATA to a more logical context avoiding the global vars... | ||||||
|  | * TODO try and split this into: | ||||||
|  | * 		- data.js -- pure DATA API | ||||||
|  | * 		- data-ribbons.js -- DATA and Ribbon API mashup... | ||||||
| * | * | ||||||
| **********************************************************************/ | **********************************************************************/ | ||||||
| 
 | 
 | ||||||
| @ -109,10 +113,6 @@ var IMAGES_CREATED = false | |||||||
| 
 | 
 | ||||||
| var MARKED = [] | var MARKED = [] | ||||||
| 
 | 
 | ||||||
| var CROP_STACK = [] |  | ||||||
| 
 |  | ||||||
| var CROP_MODES = [] |  | ||||||
| 
 |  | ||||||
| // NOTE: these are named: <mode>-<feature>
 | // NOTE: these are named: <mode>-<feature>
 | ||||||
| var SETTINGS = { | var SETTINGS = { | ||||||
| 	'global-theme': null, | 	'global-theme': null, | ||||||
| @ -146,14 +146,6 @@ var UPDATE_SORT_ENABLED = false | |||||||
| // XXX for some reason the sync version appears to work faster...
 | // XXX for some reason the sync version appears to work faster...
 | ||||||
| var UPDATE_SYNC = false | var UPDATE_SYNC = false | ||||||
| 
 | 
 | ||||||
| // object to register all the worker queues...
 |  | ||||||
| var WORKERS = {} |  | ||||||
| 
 |  | ||||||
| // Used in sortImagesByFileNameSeqWithOverflow
 |  | ||||||
| var PROXIMITY = 30 |  | ||||||
| var CHECK_1ST_PROXIMITY = false |  | ||||||
| var OVERFLOW_GAP = PROXIMITY * 5 |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| /********************************************************************** | /********************************************************************** | ||||||
| @ -1596,221 +1588,6 @@ function getPrevLocation(){ | |||||||
| * Actions... | * Actions... | ||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| /******************************************************* Crop Data ***/ |  | ||||||
| 
 |  | ||||||
| function isViewCropped(){ |  | ||||||
| 	return CROP_STACK.length != 0 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| function getAllData(){ |  | ||||||
| 	if(!isViewCropped()){ |  | ||||||
| 		return DATA |  | ||||||
| 	} else { |  | ||||||
| 		return CROP_STACK[0] |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| // NOTE: this will not update .current state...
 |  | ||||||
| // NOTE: when keep_ribbons is set, this may generate empty ribbons...
 |  | ||||||
| //
 |  | ||||||
| // XXX should this set the .current to anything but null or the first elem???
 |  | ||||||
| function makeCroppedData(gids, keep_ribbons){ |  | ||||||
| 	var res = { |  | ||||||
| 		varsion: '2.0', |  | ||||||
| 		current: null, |  | ||||||
| 		ribbons: [], |  | ||||||
| 		order: DATA.order.slice(), |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// flat single ribbon crop...
 |  | ||||||
| 	if(!keep_ribbons){ |  | ||||||
| 		res.ribbons[0] = gids |  | ||||||
| 
 |  | ||||||
| 	// keep the ribbon structure...
 |  | ||||||
| 	} else { |  | ||||||
| 		$.each(DATA.ribbons, function(_, e){ |  | ||||||
| 			e = e.filter(function(ee){ return gids.indexOf(ee) >= 0 }) |  | ||||||
| 			// skip empty ribbons...
 |  | ||||||
| 			if(e.length != 0){ |  | ||||||
| 				res.ribbons.push(e) |  | ||||||
| 			} |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return res |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| // NOTE: if keep_ribbons is not set this will ALWAYS build a single ribbon
 |  | ||||||
| // 		data-set...
 |  | ||||||
| function cropDataTo(gids, keep_ribbons){ |  | ||||||
| 	var prev_state = DATA |  | ||||||
| 	var cur = DATA.current |  | ||||||
| 	var r = getRibbonIndex() |  | ||||||
| 
 |  | ||||||
| 	var new_data = makeCroppedData(gids, keep_ribbons) |  | ||||||
| 
 |  | ||||||
| 	// do nothing if there is no change...
 |  | ||||||
| 	// XXX is there a better way to compare states???
 |  | ||||||
| 	if(JSON.stringify(DATA.ribbons) == JSON.stringify(new_data.ribbons)){ |  | ||||||
| 		return DATA |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	CROP_STACK.push(prev_state) |  | ||||||
| 	DATA = new_data |  | ||||||
| 
 |  | ||||||
| 	cur = getGIDBefore(cur, keep_ribbons ? r : 0) |  | ||||||
| 	cur = cur == null ? gids[0] : cur |  | ||||||
| 	DATA.current = cur  |  | ||||||
| 
 |  | ||||||
| 	reloadViewer() |  | ||||||
| 	updateImages() |  | ||||||
| 
 |  | ||||||
| 	return prev_state |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| function uncropData(){ |  | ||||||
| 	if(!isViewCropped()){ |  | ||||||
| 		return DATA |  | ||||||
| 	} |  | ||||||
| 	var prev_state = DATA |  | ||||||
| 	var cur = DATA.current |  | ||||||
| 
 |  | ||||||
| 	DATA = CROP_STACK.pop() |  | ||||||
| 
 |  | ||||||
| 	// check if cur exists in data being loaded...
 |  | ||||||
| 	if($.map(DATA.ribbons,  |  | ||||||
| 			function(e, i){ return e.indexOf(cur) >= 0 }).indexOf(true) >= 0){ |  | ||||||
| 		// keep the current position...
 |  | ||||||
| 		DATA.current = cur |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	reloadViewer() |  | ||||||
| 	updateImages() |  | ||||||
| 
 |  | ||||||
| 	return prev_state |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| function showAllData(){ |  | ||||||
| 	var prev_state = DATA |  | ||||||
| 	var cur = DATA.current |  | ||||||
| 
 |  | ||||||
| 	if(CROP_STACK.length != 0){ |  | ||||||
| 		DATA = getAllData() |  | ||||||
| 		CROP_STACK = [] |  | ||||||
| 
 |  | ||||||
| 		// XXX do we need to check if this exists???
 |  | ||||||
| 		// 		...in theory, as long as there are no global destructive 
 |  | ||||||
| 		// 		operations, no.
 |  | ||||||
| 		// keep the current position...
 |  | ||||||
| 		DATA.current = cur |  | ||||||
| 
 |  | ||||||
| 		reloadViewer() |  | ||||||
| 		updateImages() |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return prev_state |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| // Helpers for making crop modes and using crop...
 |  | ||||||
| 
 |  | ||||||
| // Make a generic crop mode toggler
 |  | ||||||
| //
 |  | ||||||
| // NOTE: This will add the toggler to CROP_MODES, for use by 
 |  | ||||||
| // 		uncropLastState(...)
 |  | ||||||
| // NOTE: crop modes are exclusive -- it is not possible to enter one crop
 |  | ||||||
| // 		mode from a different crop mode
 |  | ||||||
| function makeCropModeToggler(cls, crop){ |  | ||||||
| 	var res = createCSSClassToggler( |  | ||||||
| 			'.viewer', |  | ||||||
| 			//cls + ' cropped-mode',
 |  | ||||||
| 			cls, |  | ||||||
| 			function(action){ |  | ||||||
| 				// prevent mixing marked-only and single-ribbon modes...
 |  | ||||||
| 				if(action == 'on'  |  | ||||||
| 						&& isViewCropped() |  | ||||||
| 						&& res('?') != 'on'){ |  | ||||||
| 					return false |  | ||||||
| 				} |  | ||||||
| 			}, |  | ||||||
| 			function(action){ |  | ||||||
| 				if(action == 'on'){ |  | ||||||
| 					showStatusQ('Cropping current ribbon...') |  | ||||||
| 					crop() |  | ||||||
| 				} else { |  | ||||||
| 					showStatusQ('Uncropping to all data...') |  | ||||||
| 					showAllData() |  | ||||||
| 				} |  | ||||||
| 			}) |  | ||||||
| 	CROP_MODES.push(res) |  | ||||||
| 	return res |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| // Uncrop to last state and there is no states to uncrop then exit 
 |  | ||||||
| // cropped mode.
 |  | ||||||
| //
 |  | ||||||
| // NOTE: this will exit all crop modes when uncropping the last step.
 |  | ||||||
| function uncropLastState(){ |  | ||||||
| 	// do nothing if we aren't in a crop mode...
 |  | ||||||
| 	if(!isViewCropped()){ |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// exit cropped all modes...
 |  | ||||||
| 	if(CROP_STACK.length == 1){ |  | ||||||
| 		$.each(CROP_MODES, function(_, e){ e('off') }) |  | ||||||
| 
 |  | ||||||
| 	// ucrop one state...
 |  | ||||||
| 	} else { |  | ||||||
| 		showStatusQ('Uncropping...') |  | ||||||
| 		uncropData() |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| /********************************************************* Workers ***/ |  | ||||||
| 
 |  | ||||||
| // get/create a named worker queue...
 |  | ||||||
| function getWorkerQueue(name, no_auto_start){ |  | ||||||
| 
 |  | ||||||
| 	// create a new worker queue...
 |  | ||||||
| 	if(WORKERS[name] == null){ |  | ||||||
| 		var queue = makeDeferredsQ() |  | ||||||
| 		WORKERS[name] = queue |  | ||||||
| 		// start if needed...
 |  | ||||||
| 		if(!no_auto_start){ |  | ||||||
| 			queue.start() |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 	// return existing worker queue...
 |  | ||||||
| 	} else { |  | ||||||
| 		var queue = WORKERS[name] |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return queue |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| // kill all worker queues...
 |  | ||||||
| function killAllWorkers(){ |  | ||||||
| 	for(var k in WORKERS){ |  | ||||||
| 		if(WORKERS[k].isWorking()){ |  | ||||||
| 			console.log('Worker: Stopped:', k) |  | ||||||
| 		} |  | ||||||
| 		WORKERS[k].kill() |  | ||||||
| 	} |  | ||||||
| 	WORKERS = {} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| /******************************************************* Extension ***/ | /******************************************************* Extension ***/ | ||||||
| 
 | 
 | ||||||
| // Open image in an external editor/viewer
 | // Open image in an external editor/viewer
 | ||||||
| @ -1833,189 +1610,5 @@ function openImageWith(prog){ | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| /********************************************************* Sorting ***/ |  | ||||||
| 
 |  | ||||||
| function reverseImageOrder(){ |  | ||||||
| 	DATA.order.reverse() |  | ||||||
| 	updateRibbonOrder() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| // NOTE: using imageOrderCmp as a cmp function here will yield odd 
 |  | ||||||
| // 		results -- in-place sorting a list based on relative element 
 |  | ||||||
| // 		positions within itself is fun ;)
 |  | ||||||
| function sortImages(cmp, reverse){ |  | ||||||
| 	cmp = cmp == null ? imageDateCmp : cmp |  | ||||||
| 	DATA.order.sort(cmp) |  | ||||||
| 	if(reverse){ |  | ||||||
| 		DATA.order.reverse() |  | ||||||
| 	} |  | ||||||
| 	updateRibbonOrder() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| // shorthands...
 |  | ||||||
| function sortImagesByDate(reverse){ |  | ||||||
| 	return sortImages(reverse) |  | ||||||
| } |  | ||||||
| function sortImagesByFileName(reverse){ |  | ||||||
| 	return sortImages(imageNameCmp, reverse) |  | ||||||
| } |  | ||||||
| function sortImagesByFileSeqOrName(reverse){ |  | ||||||
| 	return sortImages(imageSeqOrNameCmp, reverse) |  | ||||||
| } |  | ||||||
| function sortImagesByFileNameXPStyle(reverse){ |  | ||||||
| 	return sortImages(imageXPStyleFileNameCmp, reverse) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| // Sort images by name while taking into account sequence overflows
 |  | ||||||
| //
 |  | ||||||
| // A name sequence overflow is when file name sequence overflows over
 |  | ||||||
| // *9999 and then resets to *0001...
 |  | ||||||
| //
 |  | ||||||
| // For this to be applicable:
 |  | ||||||
| // 	- ALL filenames must contain a sequence number
 |  | ||||||
| // 		XXX do we need to make this more strict?
 |  | ||||||
| // 			...for example make name.split(<seq>) equal for all files
 |  | ||||||
| // 	- the total number of files in sequence is < 10K
 |  | ||||||
| // 		XXX a simplification...
 |  | ||||||
| // 			there could be more than 10K images but then we will need to
 |  | ||||||
| // 			either take dates or folder names into account...
 |  | ||||||
| // 	- the lowest filename in set must be near seq 0001
 |  | ||||||
| // 	- the highest filename in set must be near seq 9999
 |  | ||||||
| // 	- there must be a gap somewhere in the set
 |  | ||||||
| // 		this gap size is roughly close to 10K - N where N is the total 
 |  | ||||||
| // 		number of files in set
 |  | ||||||
| // 		XXX a simplification...
 |  | ||||||
| //
 |  | ||||||
| // The gap size must be above overflow_gap, and if it is set to false 
 |  | ||||||
| // then no limit is used (default: OVERFLOW_GAP).
 |  | ||||||
| // If check_1st is false then also check the lowest sequence number
 |  | ||||||
| // for proximity to 0001 (default: CHECK_1ST_PROXIMITY).
 |  | ||||||
| //
 |  | ||||||
| // NOTE: if any of the above conditions is not applicable this will
 |  | ||||||
| // 		essentially revert to sortImagesByFileSeqOrName(...)
 |  | ||||||
| // NOTE: this will cut at the largest gap between sequence numbers.
 |  | ||||||
| //
 |  | ||||||
| // XXX it would be a good idea to account for folder name sequencing...
 |  | ||||||
| // XXX it's also a good idea to write an image serial number sort...
 |  | ||||||
| // XXX is this overcomplicated???
 |  | ||||||
| //
 |  | ||||||
| // NOTE: I like this piece if code, if it works correctly no one will 
 |  | ||||||
| // 		ever know it's here, if we replace it with the thee line dumb
 |  | ||||||
| // 		sortImagesByFileName(...) then things get "annoying" every 10K 
 |  | ||||||
| // 		images :)
 |  | ||||||
| function sortImagesByFileNameSeqWithOverflow(reverse, proximity, overflow_gap, check_1st){ |  | ||||||
| 	proximity = proximity == null ? PROXIMITY : proximity |  | ||||||
| 	overflow_gap = overflow_gap == null ? OVERFLOW_GAP : overflow_gap |  | ||||||
| 	check_1st = check_1st == null ? CHECK_1ST_PROXIMITY : check_1st |  | ||||||
| 
 |  | ||||||
| 	// prepare to sort and check names...
 |  | ||||||
| 	// NOTE: we do not usually have a filename seq 0000...
 |  | ||||||
| 	if(DATA.order.length < 9999){ |  | ||||||
| 		var need_to_fix = true |  | ||||||
| 
 |  | ||||||
| 		function cmp(a, b){ |  | ||||||
| 			if(need_to_fix){ |  | ||||||
| 				if(typeof(getImageNameSeq(a)) == typeof('str')  |  | ||||||
| 						|| typeof(getImageNameSeq(b)) == typeof('str')){ |  | ||||||
| 					need_to_fix = false |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			return imageSeqOrNameCmp(a, b) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 	// revert to normal sort my name...
 |  | ||||||
| 	} else { |  | ||||||
| 		// XXX make this more cleaver -- split the set into 10K chunks and
 |  | ||||||
| 		// 		sort the chunks too...
 |  | ||||||
| 		return sortImagesByFileName(reverse) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	DATA.order.sort(cmp) |  | ||||||
| 
 |  | ||||||
| 	// find and fix the gap...
 |  | ||||||
| 	if(need_to_fix  |  | ||||||
| 			// check if first and last are close to 0001 and 9999 resp.
 |  | ||||||
| 			&& (!check_1st || getImageNameSeq(DATA.order[0]) <= proximity) |  | ||||||
| 			&& getImageNameSeq(DATA.order[DATA.order.length-1]) >= 9999-proximity){ |  | ||||||
| 		// find the largest gap position...
 |  | ||||||
| 		var pos = null |  | ||||||
| 		var gap = 0 |  | ||||||
| 		for(var i=1; i<DATA.order.length; i++){ |  | ||||||
| 			var n_gap = Math.max(getImageNameSeq(DATA.order[i])-getImageNameSeq(DATA.order[i-1]), gap) |  | ||||||
| 			if(n_gap != gap){ |  | ||||||
| 				pos = i |  | ||||||
| 				gap = n_gap |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		// split and rearrange the order chunks...
 |  | ||||||
| 		if(overflow_gap === false || gap > overflow_gap){ |  | ||||||
| 			DATA.order = DATA.order.splice(pos).concat(DATA.order) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if(reverse){ |  | ||||||
| 		DATA.order.reverse() |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	updateRibbonOrder() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| /************************************************** Manual sorting ***/ |  | ||||||
| 
 |  | ||||||
| // Ordering images...
 |  | ||||||
| // NOTE: this a bit more complicated than simply shifting an image 
 |  | ||||||
| // 		left/right the DATA.order, we have to put it before or after
 |  | ||||||
| // 		the prev/next image...
 |  | ||||||
| function horizontalShiftImage(image, direction){ |  | ||||||
| 	image = image == null ? getImage() : $(image) |  | ||||||
| 	var gid = getImageGID(image) |  | ||||||
| 	var r = getRibbonIndex(image) |  | ||||||
| 	var ri = DATA.ribbons[r].indexOf(gid) |  | ||||||
| 
 |  | ||||||
| 	// the image we are going to move relative to...
 |  | ||||||
| 	var target = DATA.ribbons[r][ri + (direction == 'next' ? 1 : -1)] |  | ||||||
| 
 |  | ||||||
| 	// we can hit the end or start of the ribbon...
 |  | ||||||
| 	if(target == null){ |  | ||||||
| 		return image |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// update the order...
 |  | ||||||
| 	// NOTE: this is a critical section and must be done as fast as possible,
 |  | ||||||
| 	// 		this is why we are using the memory to first do the work and 
 |  | ||||||
| 	// 		then push it in...
 |  | ||||||
| 	// NOTE: in a race condition this may still overwrite the order someone
 |  | ||||||
| 	// 		else is working on, the data will be consistent...
 |  | ||||||
| 	var order = DATA.order.slice() |  | ||||||
| 	order.splice(order.indexOf(gid), 1) |  | ||||||
| 	order.splice(order.indexOf(target) + (direction == 'next'? 1 : 0), 0, gid) |  | ||||||
| 	// do the dirty work...
 |  | ||||||
| 	DATA.order.splice.apply(DATA.order, [0, DATA.order.length].concat(order)) |  | ||||||
| 
 |  | ||||||
| 	// just update the ribbons, no reloading needed...
 |  | ||||||
| 	updateRibbonOrder(true) |  | ||||||
| 
 |  | ||||||
| 	// shift the images...
 |  | ||||||
| 	getImage(target)[direction == 'prev' ? 'before' : 'after'](image) |  | ||||||
| 
 |  | ||||||
| 	// update stuff that changed, mainly order...
 |  | ||||||
| 	updateImages() |  | ||||||
| 
 |  | ||||||
| 	return image |  | ||||||
| } |  | ||||||
| function shiftImageLeft(image){ |  | ||||||
| 	return horizontalShiftImage(image, 'prev') |  | ||||||
| } |  | ||||||
| function shiftImageRight(image){ |  | ||||||
| 	return horizontalShiftImage(image, 'next') |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| /********************************************************************** | /********************************************************************** | ||||||
| * vim:set ts=4 sw=4 spell :                                          */ | * vim:set ts=4 sw=4 spell :                                          */ | ||||||
|  | |||||||
| @ -21,8 +21,11 @@ | |||||||
| 
 | 
 | ||||||
| <script src="compatibility.js"></script> | <script src="compatibility.js"></script> | ||||||
| 
 | 
 | ||||||
| <script src="base.js"></script> | <script src="ribbons.js"></script> | ||||||
| <script src="data.js"></script> | <script src="data.js"></script> | ||||||
|  | <script src="crop.js"></script> | ||||||
|  | <script src="sort.js"></script> | ||||||
|  | <script src="workers.js"></script> | ||||||
| <script src="modes.js"></script> | <script src="modes.js"></script> | ||||||
| <script src="marks.js"></script> | <script src="marks.js"></script> | ||||||
| <script src="files.js"></script> | <script src="files.js"></script> | ||||||
|  | |||||||
							
								
								
									
										208
									
								
								ui/sort.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										208
									
								
								ui/sort.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,208 @@ | |||||||
|  | /********************************************************************** | ||||||
|  | *  | ||||||
|  | * Image Sorting API | ||||||
|  | * | ||||||
|  | * | ||||||
|  | **********************************************************************/ | ||||||
|  | 
 | ||||||
|  | // Used in sortImagesByFileNameSeqWithOverflow
 | ||||||
|  | var PROXIMITY = 30 | ||||||
|  | var CHECK_1ST_PROXIMITY = false | ||||||
|  | var OVERFLOW_GAP = PROXIMITY * 5 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /********************************************************* Sorting ***/ | ||||||
|  | 
 | ||||||
|  | function reverseImageOrder(){ | ||||||
|  | 	DATA.order.reverse() | ||||||
|  | 	updateRibbonOrder() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // NOTE: using imageOrderCmp as a cmp function here will yield odd 
 | ||||||
|  | // 		results -- in-place sorting a list based on relative element 
 | ||||||
|  | // 		positions within itself is fun ;)
 | ||||||
|  | function sortImages(cmp, reverse){ | ||||||
|  | 	cmp = cmp == null ? imageDateCmp : cmp | ||||||
|  | 	DATA.order.sort(cmp) | ||||||
|  | 	if(reverse){ | ||||||
|  | 		DATA.order.reverse() | ||||||
|  | 	} | ||||||
|  | 	updateRibbonOrder() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // shorthands...
 | ||||||
|  | function sortImagesByDate(reverse){ | ||||||
|  | 	return sortImages(reverse) | ||||||
|  | } | ||||||
|  | function sortImagesByFileName(reverse){ | ||||||
|  | 	return sortImages(imageNameCmp, reverse) | ||||||
|  | } | ||||||
|  | function sortImagesByFileSeqOrName(reverse){ | ||||||
|  | 	return sortImages(imageSeqOrNameCmp, reverse) | ||||||
|  | } | ||||||
|  | function sortImagesByFileNameXPStyle(reverse){ | ||||||
|  | 	return sortImages(imageXPStyleFileNameCmp, reverse) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // Sort images by name while taking into account sequence overflows
 | ||||||
|  | //
 | ||||||
|  | // A name sequence overflow is when file name sequence overflows over
 | ||||||
|  | // *9999 and then resets to *0001...
 | ||||||
|  | //
 | ||||||
|  | // For this to be applicable:
 | ||||||
|  | // 	- ALL filenames must contain a sequence number
 | ||||||
|  | // 		XXX do we need to make this more strict?
 | ||||||
|  | // 			...for example make name.split(<seq>) equal for all files
 | ||||||
|  | // 	- the total number of files in sequence is < 10K
 | ||||||
|  | // 		XXX a simplification...
 | ||||||
|  | // 			there could be more than 10K images but then we will need to
 | ||||||
|  | // 			either take dates or folder names into account...
 | ||||||
|  | // 	- the lowest filename in set must be near seq 0001
 | ||||||
|  | // 	- the highest filename in set must be near seq 9999
 | ||||||
|  | //		 XXX alternatively check the difference between first and 
 | ||||||
|  | // 			last elements, if it is far greater than the number 
 | ||||||
|  | // 			of elements then it's likely that we need to split...
 | ||||||
|  | // 	- there must be a gap somewhere in the set
 | ||||||
|  | // 		this gap size is roughly close to 10K - N where N is the total 
 | ||||||
|  | // 		number of files in set
 | ||||||
|  | // 		XXX a simplification...
 | ||||||
|  | //
 | ||||||
|  | // The gap size must be above overflow_gap, and if it is set to false 
 | ||||||
|  | // then no limit is used (default: OVERFLOW_GAP).
 | ||||||
|  | // If check_1st is false then also check the lowest sequence number
 | ||||||
|  | // for proximity to 0001 (default: CHECK_1ST_PROXIMITY).
 | ||||||
|  | //
 | ||||||
|  | // NOTE: if any of the above conditions is not applicable this will
 | ||||||
|  | // 		essentially revert to sortImagesByFileSeqOrName(...)
 | ||||||
|  | // NOTE: this will cut at the largest gap between sequence numbers.
 | ||||||
|  | //
 | ||||||
|  | // XXX it would be a good idea to account for folder name sequencing...
 | ||||||
|  | // XXX it's also a good idea to write an image serial number sort...
 | ||||||
|  | // XXX is this overcomplicated???
 | ||||||
|  | //
 | ||||||
|  | // NOTE: I like this piece if code, if it works correctly no one will 
 | ||||||
|  | // 		ever know it's here, if we replace it with the thee line dumb
 | ||||||
|  | // 		sortImagesByFileName(...) then things get "annoying" every 10K 
 | ||||||
|  | // 		images :)
 | ||||||
|  | function sortImagesByFileNameSeqWithOverflow(reverse, proximity, overflow_gap, check_1st){ | ||||||
|  | 	proximity = proximity == null ? PROXIMITY : proximity | ||||||
|  | 	overflow_gap = overflow_gap == null ? OVERFLOW_GAP : overflow_gap | ||||||
|  | 	check_1st = check_1st == null ? CHECK_1ST_PROXIMITY : check_1st | ||||||
|  | 
 | ||||||
|  | 	// prepare to sort and check names...
 | ||||||
|  | 	// NOTE: we do not usually have a filename seq 0000...
 | ||||||
|  | 	if(DATA.order.length < 9999){ | ||||||
|  | 		var need_to_fix = true | ||||||
|  | 
 | ||||||
|  | 		function cmp(a, b){ | ||||||
|  | 			if(need_to_fix){ | ||||||
|  | 				if(typeof(getImageNameSeq(a)) == typeof('str')  | ||||||
|  | 						|| typeof(getImageNameSeq(b)) == typeof('str')){ | ||||||
|  | 					need_to_fix = false | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			return imageSeqOrNameCmp(a, b) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	// revert to normal sort my name...
 | ||||||
|  | 	} else { | ||||||
|  | 		// XXX make this more cleaver -- split the set into 10K chunks and
 | ||||||
|  | 		// 		sort the chunks too...
 | ||||||
|  | 		return sortImagesByFileName(reverse) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	DATA.order.sort(cmp) | ||||||
|  | 
 | ||||||
|  | 	// find and fix the gap...
 | ||||||
|  | 	if(need_to_fix  | ||||||
|  | 			// check if first and last are close to 0001 and 9999 resp.
 | ||||||
|  | 			// XXX alternatively check the difference between first and 
 | ||||||
|  | 			// 		last elements, if it is far greater than the number 
 | ||||||
|  | 			// 		of elements then it's likely that we need to split...
 | ||||||
|  | 			&& (!check_1st || getImageNameSeq(DATA.order[0]) <= proximity) | ||||||
|  | 			&& getImageNameSeq(DATA.order[DATA.order.length-1]) >= 9999-proximity){ | ||||||
|  | 		// find the largest gap position...
 | ||||||
|  | 		var pos = null | ||||||
|  | 		var gap = 0 | ||||||
|  | 		for(var i=1; i<DATA.order.length; i++){ | ||||||
|  | 			var n_gap = Math.max(getImageNameSeq(DATA.order[i])-getImageNameSeq(DATA.order[i-1]), gap) | ||||||
|  | 			if(n_gap != gap){ | ||||||
|  | 				pos = i | ||||||
|  | 				gap = n_gap | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		// split and rearrange the order chunks...
 | ||||||
|  | 		if(overflow_gap === false || gap > overflow_gap){ | ||||||
|  | 			DATA.order = DATA.order.splice(pos).concat(DATA.order) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if(reverse){ | ||||||
|  | 		DATA.order.reverse() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	updateRibbonOrder() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /************************************************** Manual sorting ***/ | ||||||
|  | 
 | ||||||
|  | // Ordering images...
 | ||||||
|  | // NOTE: this a bit more complicated than simply shifting an image 
 | ||||||
|  | // 		left/right the DATA.order, we have to put it before or after
 | ||||||
|  | // 		the prev/next image...
 | ||||||
|  | function horizontalShiftImage(image, direction){ | ||||||
|  | 	image = image == null ? getImage() : $(image) | ||||||
|  | 	var gid = getImageGID(image) | ||||||
|  | 	var r = getRibbonIndex(image) | ||||||
|  | 	var ri = DATA.ribbons[r].indexOf(gid) | ||||||
|  | 
 | ||||||
|  | 	// the image we are going to move relative to...
 | ||||||
|  | 	var target = DATA.ribbons[r][ri + (direction == 'next' ? 1 : -1)] | ||||||
|  | 
 | ||||||
|  | 	// we can hit the end or start of the ribbon...
 | ||||||
|  | 	if(target == null){ | ||||||
|  | 		return image | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// update the order...
 | ||||||
|  | 	// NOTE: this is a critical section and must be done as fast as possible,
 | ||||||
|  | 	// 		this is why we are using the memory to first do the work and 
 | ||||||
|  | 	// 		then push it in...
 | ||||||
|  | 	// NOTE: in a race condition this may still overwrite the order someone
 | ||||||
|  | 	// 		else is working on, the data will be consistent...
 | ||||||
|  | 	var order = DATA.order.slice() | ||||||
|  | 	order.splice(order.indexOf(gid), 1) | ||||||
|  | 	order.splice(order.indexOf(target) + (direction == 'next'? 1 : 0), 0, gid) | ||||||
|  | 	// do the dirty work...
 | ||||||
|  | 	DATA.order.splice.apply(DATA.order, [0, DATA.order.length].concat(order)) | ||||||
|  | 
 | ||||||
|  | 	// just update the ribbons, no reloading needed...
 | ||||||
|  | 	updateRibbonOrder(true) | ||||||
|  | 
 | ||||||
|  | 	// shift the images...
 | ||||||
|  | 	getImage(target)[direction == 'prev' ? 'before' : 'after'](image) | ||||||
|  | 
 | ||||||
|  | 	// update stuff that changed, mainly order...
 | ||||||
|  | 	updateImages() | ||||||
|  | 
 | ||||||
|  | 	return image | ||||||
|  | } | ||||||
|  | function shiftImageLeft(image){ | ||||||
|  | 	return horizontalShiftImage(image, 'prev') | ||||||
|  | } | ||||||
|  | function shiftImageRight(image){ | ||||||
|  | 	return horizontalShiftImage(image, 'next') | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /********************************************************************** | ||||||
|  | * vim:set ts=4 sw=4 :                                                */ | ||||||
							
								
								
									
										52
									
								
								ui/workers.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										52
									
								
								ui/workers.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,52 @@ | |||||||
|  | /********************************************************************** | ||||||
|  | *  | ||||||
|  | * Deferred worker API | ||||||
|  | * | ||||||
|  | * NOTE: at this point this only contains a worker queue... | ||||||
|  | * | ||||||
|  | * | ||||||
|  | **********************************************************************/ | ||||||
|  | 
 | ||||||
|  | // object to register all the worker queues...
 | ||||||
|  | var WORKERS = {} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /********************************************************* Workers ***/ | ||||||
|  | 
 | ||||||
|  | // get/create a named worker queue...
 | ||||||
|  | function getWorkerQueue(name, no_auto_start){ | ||||||
|  | 
 | ||||||
|  | 	// create a new worker queue...
 | ||||||
|  | 	if(WORKERS[name] == null){ | ||||||
|  | 		var queue = makeDeferredsQ() | ||||||
|  | 		WORKERS[name] = queue | ||||||
|  | 		// start if needed...
 | ||||||
|  | 		if(!no_auto_start){ | ||||||
|  | 			queue.start() | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	// return existing worker queue...
 | ||||||
|  | 	} else { | ||||||
|  | 		var queue = WORKERS[name] | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return queue | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // kill all worker queues...
 | ||||||
|  | function killAllWorkers(){ | ||||||
|  | 	for(var k in WORKERS){ | ||||||
|  | 		if(WORKERS[k].isWorking()){ | ||||||
|  | 			console.log('Worker: Stopped:', k) | ||||||
|  | 		} | ||||||
|  | 		WORKERS[k].kill() | ||||||
|  | 	} | ||||||
|  | 	WORKERS = {} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /********************************************************************** | ||||||
|  | * vim:set ts=4 sw=4 :                                                */ | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user