mirror of
				https://github.com/flynx/ImageGrid.git
				synced 2025-11-03 04:40:10 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			825 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			825 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
/**********************************************************************
 | 
						|
* 
 | 
						|
*
 | 
						|
* Features:
 | 
						|
* 	- ui-ribbons-render
 | 
						|
* 	- ui-ribbons-edit-render
 | 
						|
* 	- ui-partial-ribbons
 | 
						|
*	- ui-animation
 | 
						|
*		manage UI non-css animations...
 | 
						|
*
 | 
						|
*
 | 
						|
**********************************************************************/
 | 
						|
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
 | 
						|
(function(require){ var module={} // make module AMD/node compatible...
 | 
						|
/*********************************************************************/
 | 
						|
 | 
						|
var object = require('lib/object')
 | 
						|
var actions = require('lib/actions')
 | 
						|
var features = require('lib/features')
 | 
						|
 | 
						|
var core = require('features/core')
 | 
						|
 | 
						|
var ribbons = require('imagegrid/ribbons')
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/*********************************************************************/
 | 
						|
// helpers...
 | 
						|
 | 
						|
// XXX make this compatible with multiple images...
 | 
						|
// XXX for muptiple targets this will just do a .reload()...
 | 
						|
var updateImagePosition =
 | 
						|
function updateImagePosition(actions, target){
 | 
						|
	var s = actions.ribbons.getRibbonLocator()
 | 
						|
 | 
						|
	if(s.length == 0){
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	target = target || actions.current
 | 
						|
	target = target instanceof jQuery 
 | 
						|
		? actions.ribbons.elemGID(target) 
 | 
						|
		: target
 | 
						|
 | 
						|
	var source_ribbon = actions.ribbons.elemGID(actions.ribbons.getRibbon(target))
 | 
						|
	var source_order = actions.data.getImageOrder(target)
 | 
						|
 | 
						|
	return function(){
 | 
						|
		actions.ribbons.preventTransitions(s)
 | 
						|
		var end = function(){
 | 
						|
			// XXX not sure why this does not work without a setTimeout(..)
 | 
						|
			//actions.ribbons.restoreTransitions(s, true)
 | 
						|
			setTimeout(function(){ 
 | 
						|
				actions.ribbons.restoreTransitions(s, true) }, 0) }
 | 
						|
 | 
						|
		// XXX hack???
 | 
						|
		if(target instanceof Array){
 | 
						|
			actions.reload(true)
 | 
						|
			return end()
 | 
						|
		}
 | 
						|
 | 
						|
		var target_ribbon = actions.data.getRibbon(target)
 | 
						|
 | 
						|
		// nothing changed...
 | 
						|
		if(source_ribbon == target_ribbon 
 | 
						|
				&& actions.data.getImageOrder(target) == source_order){
 | 
						|
			return end()
 | 
						|
		}
 | 
						|
 | 
						|
		// place image at position...
 | 
						|
		var to = actions.data.getImage(target, 'next')
 | 
						|
		if(to != null){
 | 
						|
			actions.ribbons.placeImage(target, to, 'before')
 | 
						|
 | 
						|
		} else {
 | 
						|
			// place image after position...
 | 
						|
			to = actions.data.getImage(target, 'prev')
 | 
						|
			if(to != null){
 | 
						|
				actions.ribbons.placeImage(target, to, 'after')
 | 
						|
 | 
						|
			// new ribbon...
 | 
						|
			} else {
 | 
						|
				to = actions.data.getRibbon(target)
 | 
						|
 | 
						|
				if(actions.ribbons.getRibbon(to).length == 0){
 | 
						|
					actions.ribbons
 | 
						|
						.placeRibbon(to, actions.data.getRibbonOrder(target))
 | 
						|
				}
 | 
						|
 | 
						|
				actions.ribbons.placeImage(target, to)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if(actions.data.getImages(source_ribbon).length == 0){
 | 
						|
			actions.ribbons.getRibbon(source_ribbon).remove()
 | 
						|
		}
 | 
						|
 | 
						|
		actions.focusImage()
 | 
						|
 | 
						|
		return end()
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/*********************************************************************/
 | 
						|
// Base ribbons viewer...
 | 
						|
 | 
						|
var RibbonsActions = 
 | 
						|
actions.Actions({
 | 
						|
 | 
						|
	get dom(){
 | 
						|
		return this.ribbons ? this.ribbons.viewer : undefined },
 | 
						|
	
 | 
						|
 | 
						|
	load: [
 | 
						|
		function(data){
 | 
						|
			return function(){
 | 
						|
				// recycle the viewer if one is not given specifically...
 | 
						|
				var viewer = data.viewer
 | 
						|
				viewer = viewer == null && this.ribbons != null 
 | 
						|
					? this.dom 
 | 
						|
					: viewer
 | 
						|
 | 
						|
				if(this.ribbons == null){
 | 
						|
					this.ribbons = ribbons.Ribbons(viewer, this.images)
 | 
						|
					// XXX is this correct???
 | 
						|
					this.ribbons.__image_updaters = [this.updateImage.bind(this)]
 | 
						|
 | 
						|
					this.dom.trigger('ig.attached')
 | 
						|
 | 
						|
				} else {
 | 
						|
					this.ribbons.clear()
 | 
						|
					this.ribbons.images = this.images
 | 
						|
				}
 | 
						|
 | 
						|
				this.reload()
 | 
						|
			}
 | 
						|
		}],
 | 
						|
	// NOTE: this will pass the .ribbons.updateData(..) a custom ribbon 
 | 
						|
	// 		updater if one is defined here as .updateRibbon(target) action
 | 
						|
	//
 | 
						|
	// XXX HACK: two sins:
 | 
						|
	// 		- actions.updateRibbon(..) and ribbons.updateRibbon(..)
 | 
						|
	// 		  are NOT signature compatible...
 | 
						|
	// 		- we depend on the internals of a custom add-on feature
 | 
						|
	reload: ['Interface/Reload viewer',
 | 
						|
		function(force){
 | 
						|
			// full reload...
 | 
						|
			if(force == 'full'){
 | 
						|
				//this.stop()
 | 
						|
				/*
 | 
						|
				killAllWorkers()
 | 
						|
					.done(function(){
 | 
						|
						reload() 
 | 
						|
					})
 | 
						|
				*/
 | 
						|
				return location.reload()
 | 
						|
			}
 | 
						|
 | 
						|
			this.ribbons.preventTransitions()
 | 
						|
 | 
						|
			// NOTE: this essentially sets the update threshold to 0...
 | 
						|
			// XXX this should be a custom arg...
 | 
						|
			force = force ? 0 : null
 | 
						|
 | 
						|
			return function(){
 | 
						|
				// see if we've got a custom ribbon updater...
 | 
						|
				var that = this
 | 
						|
				var settings = this.updateRibbon != null 
 | 
						|
					// XXX this should be: { updateRibbon: this.updateRibbon.bind(this) }
 | 
						|
					? { updateRibbon: function(_, ribbon){ 
 | 
						|
							return that.updateRibbon(ribbon, null, null, force) 
 | 
						|
						} }
 | 
						|
					: null
 | 
						|
 | 
						|
				this.ribbons.updateData(this.data, settings)
 | 
						|
 | 
						|
				this
 | 
						|
					// XXX should this be here???
 | 
						|
					.refresh()
 | 
						|
					.focusImage()
 | 
						|
 | 
						|
				// XXX HACK to make browser redraw images...
 | 
						|
				this.scale = this.scale
 | 
						|
 | 
						|
				this.ribbons.restoreTransitions()
 | 
						|
			}
 | 
						|
		}],
 | 
						|
	// NOTE: this will trigger .updateImage hooks...
 | 
						|
	refresh: ['Interface/Refresh images without reloading',
 | 
						|
		function(gids, scale){
 | 
						|
			gids = gids || '*'
 | 
						|
			var size = scale != null ? 
 | 
						|
				this.ribbons.getVisibleImageSize('min', scale)
 | 
						|
				: null
 | 
						|
 | 
						|
			this.ribbons.updateImage(gids, null, size)
 | 
						|
		}],
 | 
						|
	clear: [
 | 
						|
		function(){ this.ribbons && this.ribbons.clear() }],
 | 
						|
	// XXX do we need clone???
 | 
						|
	clone: [function(full){
 | 
						|
		return function(res){
 | 
						|
			if(this.ribbons){
 | 
						|
				// NOTE: this is a bit wasteful as .ribbons will clone 
 | 
						|
				// 		their ref to .images that we will throw away...
 | 
						|
				res.ribbons = this.ribbons.clone()
 | 
						|
				res.ribbons.images = res.images
 | 
						|
			} 
 | 
						|
		}
 | 
						|
	}],
 | 
						|
 | 
						|
 | 
						|
	replaceGID: [
 | 
						|
		function(from, to){
 | 
						|
			return function(res){
 | 
						|
				res && this.ribbons.replaceGID(from, to)
 | 
						|
			}
 | 
						|
		}],
 | 
						|
 | 
						|
 | 
						|
	// This is called by .ribbons, the goal is to use it to hook into 
 | 
						|
	// image updating from features and extensions...
 | 
						|
	//
 | 
						|
	// NOTE: not intended for calling manually, use .refresh(..) instead...
 | 
						|
	//
 | 
						|
	// XXX EXPERIMENTAL...
 | 
						|
	// 		...need this to get triggered by .ribbons
 | 
						|
	// 		at this point manually triggering this will not do anything...
 | 
						|
	// XXX problem: need to either redesign this or distinguish from 
 | 
						|
	// 		other actions as I keep calling it expecting results...
 | 
						|
	// XXX hide from user action list... (???)
 | 
						|
	updateImage: ['- Interface/Update image (do not use directly)',
 | 
						|
		'This is called by .refresh(..) and intended for use as an '
 | 
						|
			+'trigger for handlers, and not as a user-callable acation.',
 | 
						|
		core.Event(function(gid, image){
 | 
						|
			// This is the image update protocol root function
 | 
						|
			//
 | 
						|
			// Not for direct use.
 | 
						|
		})],
 | 
						|
 | 
						|
	// NOTE: this not used directly, mainly designed as a utility to be 
 | 
						|
	// 		used for various partial ribbon implementations...
 | 
						|
	// XXX do we handle off-screen ribbons here???
 | 
						|
	resizeRibbon: ['- Interface/Resize ribbon to n images',
 | 
						|
		function(target, size){
 | 
						|
			size = size 
 | 
						|
				|| (this.config['ribbon-size-screens'] * this.screenwidth)
 | 
						|
				|| (5 * this.screenwidth)
 | 
						|
			var data = this.data
 | 
						|
			var ribbons = this.ribbons
 | 
						|
 | 
						|
			// localize transition prevention... 
 | 
						|
			// NOTE: we can't get ribbon via target directly here as
 | 
						|
			// 		the target might not be loaded...
 | 
						|
			var r_gid = data.getRibbon(target)
 | 
						|
			if(r_gid == null){
 | 
						|
				return
 | 
						|
			}
 | 
						|
			// NOTE: for the initial load this may be empty...
 | 
						|
			var r = ribbons.getRibbon(r_gid)
 | 
						|
 | 
						|
			// XXX do we need to for example ignore unloaded (r.length == 0)
 | 
						|
			// 		ribbons here, for example not load ribbons too far off 
 | 
						|
			// 		screen??
 | 
						|
			
 | 
						|
			ribbons
 | 
						|
				.preventTransitions(r)
 | 
						|
				.updateRibbon(
 | 
						|
					data.getImages(target, size, 'total'), 
 | 
						|
					r_gid,
 | 
						|
					target)
 | 
						|
				.restoreTransitions(r, true)
 | 
						|
		}],
 | 
						|
 | 
						|
 | 
						|
	// NOTE: this will align only a single image...
 | 
						|
	// XXX do we need these low level primitives here???
 | 
						|
	centerImage: ['- Interface/Center an image in ribbon horizontally',
 | 
						|
		function(target, align, offset, scale){
 | 
						|
			target = target instanceof jQuery 
 | 
						|
				? this.ribbons.elemGID(target)
 | 
						|
				: target
 | 
						|
 | 
						|
			// align current ribbon...
 | 
						|
			this.ribbons.centerImage(target, align, offset, scale)
 | 
						|
		}],
 | 
						|
	centerRibbon: ['- Interface/Center a ribbon vertically',
 | 
						|
		function(target){
 | 
						|
			target = target instanceof jQuery 
 | 
						|
				? this.ribbons.elemGID(target)
 | 
						|
				: target
 | 
						|
 | 
						|
			// align current ribbon...
 | 
						|
			this.ribbons.centerRibbon(target)
 | 
						|
		}],
 | 
						|
 | 
						|
 | 
						|
	// XXX should these be here, in ui or in ribbons???
 | 
						|
	// XXX these are identical to features/ui-preact-render.js
 | 
						|
	focusImage: [
 | 
						|
		function(target, list){
 | 
						|
			return function(){
 | 
						|
				this.ribbons.focusImage(this.data != null ? this.current : target) } }],
 | 
						|
	focusRibbon: [
 | 
						|
		function(target, mode){
 | 
						|
			mode = mode || this.config['ribbon-focus-mode']
 | 
						|
 | 
						|
			var c = this.data.getRibbonOrder()
 | 
						|
			var i = this.data.getRibbonOrder(target)
 | 
						|
			// NOTE: we are not changing the direction here based on 
 | 
						|
			// 		this.direction as swap will confuse the user...
 | 
						|
			var direction = c < i ? 'before' : 'after'
 | 
						|
 | 
						|
			if(mode == 'visual'){
 | 
						|
				var ribbons = this.ribbons
 | 
						|
				var r = this.data.getRibbon(target)
 | 
						|
				var t = ribbons.getImageByPosition('current', r)
 | 
						|
 | 
						|
				if(t.length > 1){
 | 
						|
					t = t.eq(direction == 'before' ? 0 : 1)
 | 
						|
				}
 | 
						|
 | 
						|
				t = ribbons.elemGID(t)
 | 
						|
 | 
						|
				this.focusImage(t, r)
 | 
						|
			}
 | 
						|
		}],
 | 
						|
 | 
						|
 | 
						|
	// Zoom/scale protocol...
 | 
						|
	resizing: [
 | 
						|
		core.Event(function(unit, size, overflow){
 | 
						|
			// This is a resizing protocol root function.
 | 
						|
			//
 | 
						|
			// This will never be used directly, but will wrap protocol user
 | 
						|
			// functions.
 | 
						|
			//
 | 
						|
			// As an example see: .viewScale(..)
 | 
						|
 | 
						|
			var that = this
 | 
						|
			// stop currently running transition...
 | 
						|
			this.ribbons.scale(this.ribbons.scale())
 | 
						|
 | 
						|
			// transitionend handler...
 | 
						|
			if(!this.__resize_handler){
 | 
						|
				this.__resize_handler = function(){
 | 
						|
					that.__post_resize
 | 
						|
						&& that.resizingDone() 
 | 
						|
					delete that.__post_resize
 | 
						|
				}
 | 
						|
			}
 | 
						|
			this.ribbons.getRibbonSet()
 | 
						|
				.off('transitionend', this.__resize_handler)
 | 
						|
				.on('transitionend', this.__resize_handler)
 | 
						|
 | 
						|
			// timeout handler...
 | 
						|
			this.__post_resize && clearTimeout(this.__post_resize)
 | 
						|
			return function(){
 | 
						|
				this.__post_resize = setTimeout(
 | 
						|
					this.__resize_handler, 
 | 
						|
					this.config['resize-done-timeout'] || 300)
 | 
						|
			}
 | 
						|
		})],
 | 
						|
 | 
						|
	viewScale: ['- Zoom/',
 | 
						|
		function(scale){
 | 
						|
			if(scale == null || scale == '?'){
 | 
						|
				return this.ribbons != null ? this.ribbons.scale() : null
 | 
						|
			}
 | 
						|
 | 
						|
			this.resizing.chainCall(this, function(){
 | 
						|
				this.ribbons 
 | 
						|
					&& scale 
 | 
						|
					&& this.ribbons.scale(scale)
 | 
						|
				// NOTE: we pass explicit scale here to compensate for animation...
 | 
						|
				this.refresh('*', scale)
 | 
						|
			}, 'scale', scale)
 | 
						|
		}],
 | 
						|
	// NOTE: if this gets a count argument it will fit count images, 
 | 
						|
	// 		default is one.
 | 
						|
	// NOTE: this will add .config['fit-overflow'] to odd counts if no 
 | 
						|
	// 		overflow if passed.
 | 
						|
	// 		...this is done to add ability to control scroll indication.
 | 
						|
	fitImage: ['Zoom/Fit image',
 | 
						|
		function(count, overflow){
 | 
						|
			if(count == '?'){
 | 
						|
				return this.ribbons != null ? 
 | 
						|
					this.ribbons.getScreenWidthImages() 
 | 
						|
					: null
 | 
						|
			}
 | 
						|
 | 
						|
			this.resizing.chainCall(this, function(){
 | 
						|
				if(count != null){
 | 
						|
					overflow = overflow == false ? 0 : overflow
 | 
						|
					var o = overflow != null ? overflow 
 | 
						|
						: count % 2 != 1 ? 0
 | 
						|
						: (this.config['fit-overflow'] || 0)
 | 
						|
					count += o
 | 
						|
				}
 | 
						|
				// XXX .ribbons...
 | 
						|
				this.ribbons.fitImage(count)
 | 
						|
				// NOTE: we pass explicit scale here to compensate for animation...
 | 
						|
				this.refresh('*', this.ribbons.getScreenWidthImages(1) / count)
 | 
						|
			}, 'screenwidth', count, overflow)
 | 
						|
		}],
 | 
						|
	// NOTE: this does not account for ribbon spacing...
 | 
						|
	fitRibbon: ['Zoom/Fit ribbon vertically',
 | 
						|
		function(count, whole){
 | 
						|
			if(count == '?'){
 | 
						|
				return this.ribbons != null ? 
 | 
						|
					this.ribbons.getScreenHeightRibbons() 
 | 
						|
					: null
 | 
						|
			}
 | 
						|
 | 
						|
			this.resizing.chainCall(this, function(){
 | 
						|
				// XXX .ribbons...
 | 
						|
				this.ribbons.fitRibbon(count, whole)
 | 
						|
				// NOTE: we pass explicit scale here to compensate for animation...
 | 
						|
				this.refresh('*', this.ribbons.getScreenHeightRibbons(1, whole) / count)
 | 
						|
			}, 'screenheight', count, whole)
 | 
						|
		}],
 | 
						|
 | 
						|
 | 
						|
	ribbonRotation: ['- Interface|Ribbon/', 
 | 
						|
		function(a){ 
 | 
						|
			if(arguments.length > 0){
 | 
						|
				this.ribbons.rotate(a)
 | 
						|
 | 
						|
			} else {
 | 
						|
				return this.ribbons.rotate() || 0
 | 
						|
			}
 | 
						|
		}],
 | 
						|
})
 | 
						|
 | 
						|
 | 
						|
var Ribbons = 
 | 
						|
module.Ribbons = 
 | 
						|
core.ImageGridFeatures.Feature({
 | 
						|
	title: '',
 | 
						|
	doc: '',
 | 
						|
 | 
						|
	tag: 'ui-ribbons-render',
 | 
						|
	exclusive: ['ui-render'],
 | 
						|
	depends: [
 | 
						|
		// XXX BUG: for some reason this causes a dependency conflict...
 | 
						|
		//'ui',
 | 
						|
		'base',
 | 
						|
	],
 | 
						|
	suggested: [
 | 
						|
		'ui-animation',
 | 
						|
		'ui-ribbons-edit-render',
 | 
						|
		'ui-partial-ribbons',
 | 
						|
	],
 | 
						|
 | 
						|
	actions: RibbonsActions, 
 | 
						|
 | 
						|
	handlers: [],
 | 
						|
})
 | 
						|
 | 
						|
 | 
						|
 | 
						|
//---------------------------------------------------------------------
 | 
						|
// Edit...
 | 
						|
 | 
						|
var RibbonsEditActions = 
 | 
						|
actions.Actions({
 | 
						|
	setBaseRibbon: [
 | 
						|
		function(target){
 | 
						|
			var r = this.data.getRibbon(target)
 | 
						|
			r =  r == null ? this.ribbons.getRibbon(target) : r
 | 
						|
			this.ribbons.setBaseRibbon(r)
 | 
						|
		}],
 | 
						|
	shiftImageLeft: [
 | 
						|
		function(target){ this.ribbons.placeImage(target, -1) }],
 | 
						|
	shiftImageRight: [
 | 
						|
		function(target){ this.ribbons.placeImage(target, 1) }],
 | 
						|
	shiftRibbonUp: [
 | 
						|
		function(target){
 | 
						|
			target = this.ribbons.getRibbon(target)
 | 
						|
			var i = this.ribbons.getRibbonOrder(target)
 | 
						|
			if(i > 0){
 | 
						|
				this.ribbons.placeRibbon(target, i-1)
 | 
						|
			}
 | 
						|
		}],
 | 
						|
	shiftRibbonDown: [
 | 
						|
		function(target){
 | 
						|
			target = this.ribbons.getRibbon(target)
 | 
						|
			var i = this.ribbons.getRibbonOrder(target)
 | 
						|
			if(i < this.data.ribbon_order.length-1){
 | 
						|
				this.ribbons.placeRibbon(target, i+1)
 | 
						|
			}
 | 
						|
		}],
 | 
						|
 | 
						|
	// basic image editing...
 | 
						|
	//
 | 
						|
	// XXX should we have .rotate(..) and .flip(..) generic actions???
 | 
						|
	rotateCW: [ 
 | 
						|
		function(target){ this.ribbons.rotateCW(target) }],
 | 
						|
	rotateCCW: [ 
 | 
						|
		function(target){ this.ribbons.rotateCCW(target) }],
 | 
						|
	flipVertical: [ 
 | 
						|
		function(target){ this.ribbons.flipVertical(target, 'view') }],
 | 
						|
	flipHorizontal: [
 | 
						|
		function(target){ this.ribbons.flipHorizontal(target, 'view') }],
 | 
						|
 | 
						|
	// tags...
 | 
						|
	tag: [ 
 | 
						|
		function(tags, gids){ 
 | 
						|
			gids = (gids instanceof Array || gids == null) ? gids : [gids]
 | 
						|
			return function(){
 | 
						|
				//this.ribbons.updateImage(gids) 
 | 
						|
				this.refresh(gids)
 | 
						|
			}
 | 
						|
		}],
 | 
						|
	untag: [
 | 
						|
		function(tags, gids){ 
 | 
						|
			gids = (gids instanceof Array || gids == null) ? gids : [gids]
 | 
						|
			return function(){
 | 
						|
				//this.ribbons.updateImage(gids) 
 | 
						|
				this.refresh(gids)
 | 
						|
			}
 | 
						|
		}],
 | 
						|
})
 | 
						|
 | 
						|
var RibbonsEdit = 
 | 
						|
module.RibbonsEdit = 
 | 
						|
core.ImageGridFeatures.Feature({
 | 
						|
	title: '',
 | 
						|
	doc: '',
 | 
						|
 | 
						|
	tag: 'ui-ribbons-edit-render',
 | 
						|
	depends: [
 | 
						|
		'edit',
 | 
						|
		'tags',
 | 
						|
		'sort',
 | 
						|
		'crop',
 | 
						|
		'image-group',
 | 
						|
		'ui-ribbons-render',
 | 
						|
	],
 | 
						|
 | 
						|
	actions: RibbonsEditActions, 
 | 
						|
 | 
						|
	handlers: [
 | 
						|
		[[
 | 
						|
			'shiftImageTo.pre',
 | 
						|
			'shiftImageUp.pre',
 | 
						|
			'shiftImageDown.pre',
 | 
						|
		], 
 | 
						|
			function(target){ 
 | 
						|
				return updateImagePosition(this, target) }],
 | 
						|
 | 
						|
		// reloading and updating...
 | 
						|
		[[
 | 
						|
			'sortImages',
 | 
						|
			'alignToRibbon',
 | 
						|
			'group',
 | 
						|
			'ungroup',
 | 
						|
			'groupTo',
 | 
						|
			'groupMarked',
 | 
						|
			'expandGroup',
 | 
						|
			'collapseGroup',
 | 
						|
			'crop',
 | 
						|
			'uncrop',
 | 
						|
			'addToCrop',
 | 
						|
			'removeFromCrop',
 | 
						|
			'reverseImages',
 | 
						|
		], 
 | 
						|
			function(target){ 
 | 
						|
				return this.reload(true) }],
 | 
						|
		[[
 | 
						|
			'reverseRibbons',
 | 
						|
			'cropGroup',
 | 
						|
			'syncTags',
 | 
						|
		], 
 | 
						|
			function(target){ 
 | 
						|
				return this.reload() }],
 | 
						|
	],
 | 
						|
})
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/*********************************************************************/
 | 
						|
// Partial ribbons...
 | 
						|
 | 
						|
// XXX try using .ribbons.resizeRibbon(..) for basic tasks...
 | 
						|
// XXX try a strategy: load more in the direction of movement by an offset...
 | 
						|
// XXX .updateRibbon(..) is not signature compatible with data.updateRibbon(..)
 | 
						|
var PartialRibbonsActions = 
 | 
						|
actions.Actions({
 | 
						|
	config: {
 | 
						|
		// Number of screen widths to load...
 | 
						|
		'ribbon-size-screens': 7,
 | 
						|
 | 
						|
		// Number of screen widths to edge to trigger reload...
 | 
						|
		'ribbon-resize-threshold': 1.5,
 | 
						|
 | 
						|
		// Timeout before a non-forced ribbon size update happens after
 | 
						|
		// the action...
 | 
						|
		// NOTE: if set to null, the update will be sync...
 | 
						|
		'ribbon-update-timeout': 120,
 | 
						|
	},
 | 
						|
 | 
						|
	// NOTE: this will force sync resize if one of the following is true:
 | 
						|
	// 		- the target is not loaded
 | 
						|
	// 		- we are less than screen width from the edge
 | 
						|
	// 		- threshold is set to 0
 | 
						|
	// XXX this is not signature compatible with data.updateRibbon(..)
 | 
						|
	// XXX do not do anything for off-screen ribbons...
 | 
						|
	updateRibbon: ['- Interface/Update partial ribbon size', 
 | 
						|
		function(target, w, size, threshold){
 | 
						|
			target = target instanceof jQuery 
 | 
						|
				? this.ribbons.elemGID(target)
 | 
						|
				// NOTE: data.getImage(..) can return null at start or end
 | 
						|
				// 		of ribbon, thus we need to account for this...
 | 
						|
				: (this.data.getImage(target, 'before')
 | 
						|
					|| this.data.getImage(target, 'after'))
 | 
						|
 | 
						|
 | 
						|
			w = w || this.screenwidth
 | 
						|
 | 
						|
			// get config data and normalize...
 | 
						|
			size = (size 
 | 
						|
				|| this.config['ribbon-size-screens'] 
 | 
						|
				|| 5) * w
 | 
						|
			threshold = threshold == 0 ? threshold
 | 
						|
				: (threshold 
 | 
						|
					|| this.config['ribbon-resize-threshold'] 
 | 
						|
					|| 1) * w
 | 
						|
 | 
						|
			var timeout = this.config['ribbon-update-timeout']
 | 
						|
 | 
						|
			// next/prev loaded... 
 | 
						|
			var img = this.ribbons.getImage(target)
 | 
						|
			var nl = img.nextAll('.image:not(.clone)').length
 | 
						|
			var pl = img.prevAll('.image:not(.clone)').length
 | 
						|
 | 
						|
			// next/prev available...
 | 
						|
			// NOTE: we subtract 1 to remove the current and make these 
 | 
						|
			// 		compatible with: nl, pl
 | 
						|
			var na = this.data.getImages(target, size, 'after').length - 1
 | 
						|
			var pa = this.data.getImages(target, size, 'before').length - 1
 | 
						|
 | 
						|
			// do the update...
 | 
						|
			// no threshold means force load...
 | 
						|
			if(threshold == 0 
 | 
						|
					// the target is not loaded...
 | 
						|
					|| img.length == 0
 | 
						|
					// passed hard threshold on the right...
 | 
						|
					|| (nl < w && na > nl) 
 | 
						|
					// passed hard threshold on the left...
 | 
						|
					|| (pl < w && pa > pl)){
 | 
						|
 | 
						|
				this.resizeRibbon(target, size)
 | 
						|
 | 
						|
			// do a late resize...
 | 
						|
			// loaded more than we need (crop?)...
 | 
						|
			} else if(na + pa < nl + pl
 | 
						|
					// passed threshold on the right...
 | 
						|
					|| (nl < threshold && na > nl) 
 | 
						|
					// passed threshold on the left...
 | 
						|
					|| (pl < threshold && pa > pl) 
 | 
						|
					// loaded more than we need by threshold...
 | 
						|
					|| nl + pl + 1 > size + threshold){
 | 
						|
 | 
						|
				return function(){
 | 
						|
					// sync update...
 | 
						|
					if(timeout == null){
 | 
						|
						this.resizeRibbon(target, size)
 | 
						|
 | 
						|
					// async update...
 | 
						|
					} else {
 | 
						|
						// XXX need to check if we are too close to the edge...
 | 
						|
						var that = this
 | 
						|
						//setTimeout(function(){ that.resizeRibbon(target, size) }, 0)
 | 
						|
						if(this.__update_timeout){
 | 
						|
							clearTimeout(this.__update_timeout)
 | 
						|
						}
 | 
						|
						this.__update_timeout = setTimeout(function(){ 
 | 
						|
							delete that.__update_timeout
 | 
						|
							that.resizeRibbon(target, size) 
 | 
						|
						}, timeout)
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}],
 | 
						|
})
 | 
						|
 | 
						|
var PartialRibbons = 
 | 
						|
module.PartialRibbons = 
 | 
						|
core.ImageGridFeatures.Feature({
 | 
						|
	title: 'Partial Ribbons',
 | 
						|
	doc: core.doc`Maintains partially loaded ribbons, this enables very large
 | 
						|
	image sets to be handled efficiently.`,
 | 
						|
 | 
						|
	// NOTE: partial ribbons needs to be setup first...
 | 
						|
	// 		...the reasons why things break otherwise is not too clear.
 | 
						|
	priority: 'high',
 | 
						|
 | 
						|
	tag: 'ui-partial-ribbons',
 | 
						|
	exclusive: ['ui-partial-ribbons'],
 | 
						|
	depends: [
 | 
						|
		//'ui',
 | 
						|
		'ui-ribbons-render',
 | 
						|
	],
 | 
						|
	suggested: [
 | 
						|
		'ui-partial-ribbons-precache',
 | 
						|
	],
 | 
						|
 | 
						|
 | 
						|
	actions: PartialRibbonsActions,
 | 
						|
 | 
						|
	handlers: [
 | 
						|
		['focusImage.pre centerImage.pre', 
 | 
						|
			function(target, list){
 | 
						|
				// NOTE: we have to do this as we are called BEFORE the 
 | 
						|
				// 		actual focus change happens...
 | 
						|
				// XXX is there a better way to do this???
 | 
						|
				target = list != null ? this.data.getImage(target, list) : target
 | 
						|
 | 
						|
				this.updateRibbon(target)
 | 
						|
			}],
 | 
						|
		['resizing.pre',
 | 
						|
			function(unit, size){
 | 
						|
				// keep constant size in single image...
 | 
						|
				if(this.toggleSingleImage && this.toggleSingleImage('?') == 'on'){
 | 
						|
					this.updateRibbon(
 | 
						|
						'current', 
 | 
						|
						this.config['ribbons-resize-single-image'] || 13)
 | 
						|
 | 
						|
				} else if(unit == 'scale'){
 | 
						|
					this.updateRibbon('current', this.screenwidth / size || 1)
 | 
						|
 | 
						|
				} else if(unit == 'screenwidth'){
 | 
						|
					this.updateRibbon('current', size || 1)
 | 
						|
 | 
						|
				} else if(unit == 'screenheight'){
 | 
						|
					size = size || 1
 | 
						|
 | 
						|
					// convert target height in ribbons to width in images...
 | 
						|
					// NOTE: this does not account for compensation that 
 | 
						|
					// 		.updateRibbon(..) makes for fitting whole image
 | 
						|
					// 		counts, this is a small enough error so as not
 | 
						|
					// 		to waste time on...
 | 
						|
					var s = this.ribbons.scale()
 | 
						|
					var h = this.ribbons.getScreenHeightRibbons()
 | 
						|
					var w = this.ribbons.getScreenWidthImages()
 | 
						|
					var nw = w / (h/size)
 | 
						|
 | 
						|
					this.updateRibbon('current', nw)
 | 
						|
				}
 | 
						|
			}],
 | 
						|
	],
 | 
						|
})
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/*********************************************************************/
 | 
						|
// Animation...
 | 
						|
 | 
						|
// XXX at this point this does not support target lists...
 | 
						|
// XXX shift up/down to new ribbon is not too correct...
 | 
						|
// XXX depends on .ribbons.makeShadow(..)...
 | 
						|
var ShiftAnimation =
 | 
						|
module.ShiftAnimation = core.ImageGridFeatures.Feature({
 | 
						|
	title: '',
 | 
						|
	doc: '',
 | 
						|
 | 
						|
	tag: 'ui-animation',
 | 
						|
	depends: [
 | 
						|
		'ui-ribbons-render',
 | 
						|
	],
 | 
						|
	// NOTE: this will allow the animations to start as early as possible
 | 
						|
	// 		in the action call...
 | 
						|
	priority: 'high',
 | 
						|
 | 
						|
	config: {
 | 
						|
		// XXX make this duration...
 | 
						|
		'shadow-animation-delay': 200,
 | 
						|
		'shadow-animation-start-delay': 0,
 | 
						|
	},
 | 
						|
 | 
						|
	handlers: [
 | 
						|
		//['shiftImageUp.pre shiftImageDown.pre '
 | 
						|
		//		+'travelImageUp.pre travelImageDown.pre', 
 | 
						|
		['shiftImageUp.pre shiftImageDown.pre',
 | 
						|
			function(target){
 | 
						|
				// XXX do not do target lists...
 | 
						|
				if(target != null && target instanceof Array 
 | 
						|
						// do not animate in single image mode...
 | 
						|
						&& this.toggleSingleImage('?') == 'on'){
 | 
						|
					return
 | 
						|
				}
 | 
						|
				var s = this.ribbons.makeShadow(target, true, 
 | 
						|
					// XXX make this duration...
 | 
						|
					this.config['shadow-animation-delay'],
 | 
						|
					this.config['shadow-animation-start-delay'])
 | 
						|
				return function(){ s() }
 | 
						|
			}],
 | 
						|
		// NOTE: this will keep the shadow in place -- the shadow will not
 | 
						|
		// 		go to the mountain, the mountain will come to the shadow ;)
 | 
						|
		['shiftImageLeft.pre shiftImageRight.pre', 
 | 
						|
			function(target){
 | 
						|
				// XXX do not do target lists...
 | 
						|
				if(target != null && target instanceof Array
 | 
						|
						// do not animate in single image mode...
 | 
						|
						&& this.toggleSingleImage('?') == 'on'){
 | 
						|
					return
 | 
						|
				}
 | 
						|
				var s = this.ribbons.makeShadow(target, undefined,
 | 
						|
					// XXX make this duration...
 | 
						|
					this.config['shadow-animation-delay'],
 | 
						|
					this.config['shadow-animation-start-delay'])
 | 
						|
				return function(){ s() }
 | 
						|
			}],
 | 
						|
	],
 | 
						|
})
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/**********************************************************************
 | 
						|
* vim:set ts=4 sw=4 :                               */ return module })
 |