| 
									
										
										
										
											2023-07-17 13:37:10 +03:00
										 |  |  | //=====================================================================
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // TODO:
 | 
					
						
							| 
									
										
										
										
											2023-09-10 13:21:29 +03:00
										 |  |  | // 	- ui for cropping...
 | 
					
						
							|  |  |  | // 	- drag-n-drop for touch devices...
 | 
					
						
							| 
									
										
										
										
											2023-09-10 15:55:51 +03:00
										 |  |  | // 	- handle url-hash
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:37:10 +03:00
										 |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //=====================================================================
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-11 15:52:48 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | // Generic stuff...
 | 
					
						
							| 
									
										
										
										
											2023-08-07 17:16:24 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-22 20:48:36 +03:00
										 |  |  | // This compansates for any resize rounding errors in patchFlexRows(..).
 | 
					
						
							|  |  |  | var PATCH_MARGIN = 2 | 
					
						
							| 
									
										
										
										
											2023-09-10 13:24:52 +03:00
										 |  |  | // XXX do this partially (ignoring prevent_row_expansion)...
 | 
					
						
							| 
									
										
										
										
											2023-08-22 21:42:00 +03:00
										 |  |  | var patchFlexRows =  | 
					
						
							| 
									
										
										
										
											2023-08-07 17:21:55 +03:00
										 |  |  | function(elems,  | 
					
						
							|  |  |  | 		prevent_row_expansion=false,  | 
					
						
							|  |  |  | 		last_row_resize=1.5,  | 
					
						
							|  |  |  | 		patch_margin=PATCH_MARGIN){ | 
					
						
							| 
									
										
										
										
											2023-07-28 19:41:44 +03:00
										 |  |  | 	if(elems.length == 0){ | 
					
						
							|  |  |  | 		return } | 
					
						
							| 
									
										
										
										
											2023-07-21 14:40:30 +03:00
										 |  |  | 	// NOTE: -1 here is to compensate for rounding errors...
 | 
					
						
							| 
									
										
										
										
											2023-08-07 17:21:55 +03:00
										 |  |  | 	var W = elems[0].parentElement.clientWidth - patch_margin | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 	var w = 0 | 
					
						
							|  |  |  | 	var h | 
					
						
							|  |  |  | 	var row = [] | 
					
						
							|  |  |  | 	var top = elems[0].offsetTop | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 	// modes:
 | 
					
						
							|  |  |  | 	// 		'as-is' | false
 | 
					
						
							|  |  |  | 	// 		'expand'
 | 
					
						
							|  |  |  | 	// 		'close'
 | 
					
						
							|  |  |  | 	// closure: W, w, h, row, elem
 | 
					
						
							|  |  |  | 	var max = 0 | 
					
						
							|  |  |  | 	var handleRow = function(mode='justify'){ | 
					
						
							|  |  |  | 		if(!mode || mode == 'as-is'){ | 
					
						
							|  |  |  | 			return false } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if(typeof(mode) == 'number'){ | 
					
						
							|  |  |  | 			var r = Math.min( | 
					
						
							|  |  |  | 				W / w,  | 
					
						
							|  |  |  | 				max * mode) | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			var r = W / w } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var expanded | 
					
						
							|  |  |  | 		if(mode == 'expand'){ | 
					
						
							|  |  |  | 			var r2 = W / (w + elem.offsetWidth) | 
					
						
							|  |  |  | 			// NOTE: we are checking which will require a lesser resize
 | 
					
						
							|  |  |  | 			//		the current row or it with the next image...
 | 
					
						
							|  |  |  | 			if(1/r < r2){ | 
					
						
							|  |  |  | 				expanded = true | 
					
						
							|  |  |  | 				var r = r2 | 
					
						
							|  |  |  | 				row.push(elem) } } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		max = Math.max(max, r)  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// patch the row...
 | 
					
						
							|  |  |  | 		var nw = 0 | 
					
						
							|  |  |  | 		for(var e of row){ | 
					
						
							| 
									
										
										
										
											2023-08-07 17:16:24 +03:00
										 |  |  | 			if(r == 0 || h == 0){ | 
					
						
							|  |  |  | 				e.style.height = '' | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				e.style.height = (h * r) + 'px' } | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 			nw += e.offsetWidth } | 
					
						
							|  |  |  | 		return !!expanded } | 
					
						
							|  |  |  | 	// NOTE: this will by design skip the last row...
 | 
					
						
							|  |  |  | 	// 		...because we handle the row only when we see an image at a 
 | 
					
						
							|  |  |  | 	// 		different vertical offset...
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 	for(var elem of elems){ | 
					
						
							|  |  |  | 		elem.style.height = '' | 
					
						
							|  |  |  | 		elem.style.width = '' | 
					
						
							|  |  |  | 		h = h  | 
					
						
							|  |  |  | 			?? elem.offsetHeight | 
					
						
							|  |  |  | 		top = top  | 
					
						
							|  |  |  | 			?? elem.offsetTop | 
					
						
							|  |  |  | 		// collect row...
 | 
					
						
							|  |  |  | 		if(elem.offsetTop == top){ | 
					
						
							|  |  |  | 			w += elem.offsetWidth | 
					
						
							|  |  |  | 			row.push(elem) | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 		// row done + prep for next...
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2023-07-21 14:40:30 +03:00
										 |  |  | 			var expanded_row =  | 
					
						
							|  |  |  | 				prevent_row_expansion ? | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 					handleRow() | 
					
						
							|  |  |  | 					: handleRow('expand') | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 			// prep for next row...
 | 
					
						
							|  |  |  | 			if(!expanded_row){ | 
					
						
							|  |  |  | 				w = elem.offsetWidth | 
					
						
							|  |  |  | 				h = elem.offsetHeight  | 
					
						
							|  |  |  | 				top = elem.offsetTop | 
					
						
							|  |  |  | 				row = [elem]  | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				w = 0 | 
					
						
							|  |  |  | 				h = null | 
					
						
							|  |  |  | 				top = null | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 				row = [] }}} | 
					
						
							|  |  |  | 	// handle last row...
 | 
					
						
							|  |  |  | 	last_row_resize | 
					
						
							|  |  |  | 		&& handleRow(last_row_resize) } | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | var getScrollParent =  | 
					
						
							|  |  |  | function(elem){ | 
					
						
							|  |  |  | 	var parent = elem.parentElement | 
					
						
							|  |  |  | 	while(parent !== document.body  | 
					
						
							|  |  |  | 			&& parent.scrollHeight > parent.clientHeight){ | 
					
						
							|  |  |  | 		parent = elem.parentElement } | 
					
						
							|  |  |  | 	return parent } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-18 09:11:58 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | // - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - -
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | // XXX also need to check if scrolled under something...
 | 
					
						
							|  |  |  | var isVisible = | 
					
						
							|  |  |  | function(elem) { | 
					
						
							|  |  |  |     const rect = elem.getBoundingClientRect() | 
					
						
							|  |  |  |     return rect.top >= 0  | 
					
						
							|  |  |  | 		&& rect.left >= 0  | 
					
						
							|  |  |  | 		&& rect.bottom <= (window.innerHeight  | 
					
						
							|  |  |  | 			|| document.documentElement.clientHeight)  | 
					
						
							|  |  |  | 		&& rect.right <= (window.innerWidth  | 
					
						
							|  |  |  | 			|| document.documentElement.clientWidth) } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-18 09:11:58 +03:00
										 |  |  | // - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - -
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-22 20:48:36 +03:00
										 |  |  | var _ruler | 
					
						
							| 
									
										
										
										
											2023-08-17 11:24:30 +03:00
										 |  |  | var px2rem = function(px){ | 
					
						
							| 
									
										
										
										
											2023-08-22 20:48:36 +03:00
										 |  |  | 	if(!_ruler){ | 
					
						
							|  |  |  | 		_ruler = document.createElement('div') | 
					
						
							|  |  |  | 		document.body.append(_ruler) } | 
					
						
							|  |  |  | 	_ruler.style.width = '1rem' | 
					
						
							|  |  |  | 	var c = _ruler.offsetWidth | 
					
						
							| 
									
										
										
										
											2023-08-17 11:24:30 +03:00
										 |  |  | 	return px / c } | 
					
						
							|  |  |  | var rem2px = function(rem){ | 
					
						
							| 
									
										
										
										
											2023-08-22 20:48:36 +03:00
										 |  |  | 	if(!_ruler){ | 
					
						
							|  |  |  | 		_ruler = document.createElement('div') | 
					
						
							|  |  |  | 		document.body.append(_ruler) } | 
					
						
							|  |  |  | 	_ruler.style.width = rem + 'em' | 
					
						
							|  |  |  | 	var px = _ruler.offsetWidth | 
					
						
							| 
									
										
										
										
											2023-08-17 11:24:30 +03:00
										 |  |  | 	return px } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-18 09:11:58 +03:00
										 |  |  | // - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - -
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | var getTouch =  | 
					
						
							|  |  |  | function(evt, id){ | 
					
						
							| 
									
										
										
										
											2023-08-17 11:24:30 +03:00
										 |  |  | 	if(id != null && id !== false && evt.targetTouches){ | 
					
						
							|  |  |  | 		for(var k in evt.targetTouches){ | 
					
						
							|  |  |  | 			if(evt.targetTouches[k]?.identifier == id){ | 
					
						
							|  |  |  | 				return evt.targetTouches[k] } } } } | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-18 09:11:58 +03:00
										 |  |  | // - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - -
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-18 13:23:27 +03:00
										 |  |  | // XXX constrain this to:
 | 
					
						
							|  |  |  | // 		viewport -- DONE but needs to be smoother...
 | 
					
						
							|  |  |  | // 		parent
 | 
					
						
							| 
									
										
										
										
											2023-08-19 18:25:33 +03:00
										 |  |  | // 		element
 | 
					
						
							|  |  |  | // 		...might be a good idea to write a fast/generic containment 
 | 
					
						
							|  |  |  | // 		tester...
 | 
					
						
							| 
									
										
										
										
											2023-08-18 09:11:58 +03:00
										 |  |  | // XXX might be a good idea to allow bounds to be:
 | 
					
						
							|  |  |  | // 		string		- css selector
 | 
					
						
							|  |  |  | // 		element		- 
 | 
					
						
							|  |  |  | // XXX might be a good idea to either:
 | 
					
						
							|  |  |  | // 		- scroll into view the dragged element
 | 
					
						
							|  |  |  | // 		- bound it by screen
 | 
					
						
							| 
									
										
										
										
											2023-08-17 17:16:49 +03:00
										 |  |  | // XXX need to check if element already set as movable...
 | 
					
						
							| 
									
										
										
										
											2023-08-20 23:25:15 +03:00
										 |  |  | // XXX does it make sence to abstract out the bounds checking code???
 | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | // XXX docs...
 | 
					
						
							|  |  |  | HTMLElement.prototype.moveable =  | 
					
						
							|  |  |  | function(options={}){ | 
					
						
							|  |  |  | 	var elem = this | 
					
						
							| 
									
										
										
										
											2023-08-17 17:16:49 +03:00
										 |  |  | 	if(elem.dataset.movable){ | 
					
						
							|  |  |  | 		throw new Error('element already movable.') } | 
					
						
							|  |  |  | 	elem.dataset.movable = true | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 	// options...
 | 
					
						
							| 
									
										
										
										
											2023-08-17 17:16:49 +03:00
										 |  |  | 	var { | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 		// CSS class added to element while it is dragged...
 | 
					
						
							|  |  |  | 		cls = 'movable', | 
					
						
							|  |  |  | 		// Drag handle element -- watches the drag events...
 | 
					
						
							|  |  |  | 		handle = elem, | 
					
						
							|  |  |  | 		// Blement bounding box used to check bounds...
 | 
					
						
							|  |  |  | 		box = elem, | 
					
						
							|  |  |  | 		// Bounds object...
 | 
					
						
							|  |  |  | 		// can be:
 | 
					
						
							|  |  |  | 		// 	false
 | 
					
						
							|  |  |  | 		// 	{
 | 
					
						
							|  |  |  | 		// 		top: <top>,
 | 
					
						
							|  |  |  | 		// 		left: <left>,
 | 
					
						
							|  |  |  | 		// 		bottom: <bottom>,
 | 
					
						
							|  |  |  | 		// 		<right>: <right>,
 | 
					
						
							|  |  |  | 		// 	}
 | 
					
						
							|  |  |  | 		// XXX add support for:
 | 
					
						
							|  |  |  | 		// 		css selector
 | 
					
						
							|  |  |  | 		// 		element
 | 
					
						
							| 
									
										
										
										
											2023-08-17 17:16:49 +03:00
										 |  |  | 		bounds, | 
					
						
							| 
									
										
										
										
											2023-08-18 13:23:27 +03:00
										 |  |  | 		// can be:
 | 
					
						
							|  |  |  | 		// 	'x'
 | 
					
						
							|  |  |  | 		// 	'y'
 | 
					
						
							|  |  |  | 		// 	undefined
 | 
					
						
							|  |  |  | 		lock, | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 		//	start(<elem>, <data>)
 | 
					
						
							|  |  |  | 		//		-> undefined
 | 
					
						
							|  |  |  | 		//		-> <data>
 | 
					
						
							|  |  |  | 		start, | 
					
						
							| 
									
										
										
										
											2023-08-18 13:23:27 +03:00
										 |  |  | 		// can be:
 | 
					
						
							|  |  |  | 		// 	'strict'
 | 
					
						
							|  |  |  | 		// 	'scroll' / true
 | 
					
						
							|  |  |  | 		// 	false
 | 
					
						
							|  |  |  | 		keepInView = true, | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 		//	move(<elem>, <data>)
 | 
					
						
							| 
									
										
										
										
											2023-08-17 17:16:49 +03:00
										 |  |  | 		move = function(elem, data){ | 
					
						
							| 
									
										
										
										
											2023-08-18 13:23:27 +03:00
										 |  |  | 			data.x != null | 
					
						
							|  |  |  | 				&& (elem.style.left = data.x + 'px') | 
					
						
							|  |  |  | 			data.y != null | 
					
						
							|  |  |  | 				&& (elem.style.top = data.y + 'px') }, | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 		//	end(<elem>, <data>)
 | 
					
						
							| 
									
										
										
										
											2023-08-17 17:16:49 +03:00
										 |  |  | 		end, | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 		ignoreBounds = false, | 
					
						
							| 
									
										
										
										
											2023-08-17 17:16:49 +03:00
										 |  |  | 	} = options | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 	handle =  | 
					
						
							|  |  |  | 		typeof(handle) == 'string' ? | 
					
						
							| 
									
										
										
										
											2023-08-17 17:16:49 +03:00
										 |  |  | 			elem.querySelector(handle) | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 		: handle | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-17 17:16:49 +03:00
										 |  |  | 	var data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var handleMoveStart = function(evt){ | 
					
						
							|  |  |  | 		evt.preventDefault() | 
					
						
							|  |  |  | 		evt.stopPropagation() | 
					
						
							|  |  |  | 		if(!data){ | 
					
						
							|  |  |  | 			cls | 
					
						
							|  |  |  | 				&& elem.classList.add(cls) | 
					
						
							|  |  |  | 			var x = evt.clientX  | 
					
						
							|  |  |  | 				?? evt.targetTouches[0].clientX | 
					
						
							|  |  |  | 			var y = evt.clientY | 
					
						
							|  |  |  | 				?? evt.targetTouches[0].clientY | 
					
						
							|  |  |  | 			data = { | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 				bounds: bounds, | 
					
						
							| 
									
										
										
										
											2023-08-17 17:16:49 +03:00
										 |  |  | 				offset: { | 
					
						
							|  |  |  | 					x: x - elem.offsetLeft,  | 
					
						
							|  |  |  | 					y: y - elem.offsetTop, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 				touch: evt.targetTouches ? | 
					
						
							|  |  |  | 					evt.targetTouches[0].identifier | 
					
						
							|  |  |  | 					: undefined, | 
					
						
							|  |  |  | 			}  | 
					
						
							|  |  |  | 			if(typeof(start) == 'function'){ | 
					
						
							|  |  |  | 				var res = start(elem, data) | 
					
						
							|  |  |  | 				data = res != null ? | 
					
						
							|  |  |  | 					res | 
					
						
							|  |  |  | 					: data } } } | 
					
						
							|  |  |  | 	var handleMove = function(evt){ | 
					
						
							|  |  |  | 		if(data){ | 
					
						
							|  |  |  | 			var src = data.touch != null ? | 
					
						
							|  |  |  | 				getTouch(evt, data.touch) | 
					
						
							|  |  |  | 				: evt | 
					
						
							|  |  |  | 			if(!src){ | 
					
						
							|  |  |  | 				return } | 
					
						
							|  |  |  | 			evt.preventDefault() | 
					
						
							|  |  |  | 			evt.stopPropagation() | 
					
						
							| 
									
										
										
										
											2023-08-18 13:23:27 +03:00
										 |  |  | 			// viewport bounding box in elem coordinates...
 | 
					
						
							| 
									
										
										
										
											2023-08-17 17:16:49 +03:00
										 |  |  | 			var x = src.clientX  | 
					
						
							|  |  |  | 			var y = src.clientY | 
					
						
							|  |  |  | 			data.x = | 
					
						
							| 
									
										
										
										
											2023-08-18 13:23:27 +03:00
										 |  |  | 				lock == 'x' ? | 
					
						
							|  |  |  | 					null | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 				: (!ignoreBounds  | 
					
						
							|  |  |  | 						&& data.bounds != null) ? | 
					
						
							| 
									
										
										
										
											2023-08-18 13:23:27 +03:00
										 |  |  | 					Math.min( | 
					
						
							|  |  |  | 						data.bounds.right, | 
					
						
							|  |  |  | 						Math.max( | 
					
						
							|  |  |  | 							data.bounds.left,  | 
					
						
							| 
									
										
										
										
											2023-08-17 17:16:49 +03:00
										 |  |  | 							x - data.offset.x)) | 
					
						
							| 
									
										
										
										
											2023-08-18 13:23:27 +03:00
										 |  |  | 				: x - data.offset.x | 
					
						
							| 
									
										
										
										
											2023-08-17 17:16:49 +03:00
										 |  |  | 			data.y = | 
					
						
							| 
									
										
										
										
											2023-08-18 13:23:27 +03:00
										 |  |  | 				lock == 'y' ? | 
					
						
							|  |  |  | 					null | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 				: (!ignoreBounds  | 
					
						
							|  |  |  | 						&& data.bounds != null) ? | 
					
						
							| 
									
										
										
										
											2023-08-18 13:23:27 +03:00
										 |  |  | 					Math.min( | 
					
						
							|  |  |  | 						data.bounds.bottom, | 
					
						
							|  |  |  | 						Math.max( | 
					
						
							|  |  |  | 							data.bounds.top,  | 
					
						
							| 
									
										
										
										
											2023-08-17 17:16:49 +03:00
										 |  |  | 							y - data.offset.y)) | 
					
						
							| 
									
										
										
										
											2023-08-18 13:23:27 +03:00
										 |  |  | 				: y - data.offset.y | 
					
						
							|  |  |  | 			// restrict to viewport...
 | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 			if(!ignoreBounds  | 
					
						
							|  |  |  | 					&& keepInView == 'strict'){ | 
					
						
							|  |  |  | 				var t, l | 
					
						
							| 
									
										
										
										
											2023-08-18 13:23:27 +03:00
										 |  |  | 				var bb = elem.getBoundingClientRect() | 
					
						
							|  |  |  | 				var screen = { | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 					top: t = box.offsetTop - bb.top, | 
					
						
							|  |  |  | 					left: l = box.offsetLeft - bb.left, | 
					
						
							|  |  |  | 					bottom: window.innerHeight + t - box.offsetHeight, | 
					
						
							|  |  |  | 					right: window.innerWidth + l - box.offsetWidth, | 
					
						
							| 
									
										
										
										
											2023-08-18 13:23:27 +03:00
										 |  |  | 				} | 
					
						
							|  |  |  | 				data.x = Math.min( | 
					
						
							|  |  |  | 					screen.right, | 
					
						
							|  |  |  | 					Math.max( | 
					
						
							|  |  |  | 						screen.left, | 
					
						
							|  |  |  | 						data.x)) | 
					
						
							|  |  |  | 				data.y = Math.min( | 
					
						
							|  |  |  | 					screen.bottom, | 
					
						
							|  |  |  | 					Math.max( | 
					
						
							|  |  |  | 						screen.top, | 
					
						
							|  |  |  | 						data.y)) } | 
					
						
							| 
									
										
										
										
											2023-08-17 17:16:49 +03:00
										 |  |  | 			// NOTE: we only allow a single requestAnimationFrame(..)
 | 
					
						
							|  |  |  | 			// 		to run per frame...
 | 
					
						
							|  |  |  | 			if(!data.animate){ | 
					
						
							|  |  |  | 				data.animate = requestAnimationFrame(function(){ | 
					
						
							|  |  |  | 					if(!data){ | 
					
						
							|  |  |  | 						return } | 
					
						
							| 
									
										
										
										
											2023-08-18 13:23:27 +03:00
										 |  |  | 					move | 
					
						
							|  |  |  | 						&& move(elem, data) | 
					
						
							|  |  |  | 					// keep in view...
 | 
					
						
							|  |  |  | 					// NOTE: this works best with CSS's scroll-margin...
 | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 					!ignoreBounds | 
					
						
							|  |  |  | 						&& (keepInView == 'scroll'  | 
					
						
							| 
									
										
										
										
											2023-08-18 13:23:27 +03:00
										 |  |  | 							|| keepInView === true) | 
					
						
							|  |  |  | 						&& elem.scrollIntoView({ block: 'nearest' })  | 
					
						
							| 
									
										
										
										
											2023-08-17 17:16:49 +03:00
										 |  |  | 					delete data.animate }) } } } | 
					
						
							|  |  |  | 	var handleMoveEnd = function(evt){ | 
					
						
							|  |  |  | 		if(data){ | 
					
						
							|  |  |  | 			if(evt.targetTouches | 
					
						
							|  |  |  | 					&& (evt.targetTouches.length == 0  | 
					
						
							|  |  |  | 						|| getTouch(evt, data.touch))){ | 
					
						
							|  |  |  | 				return } | 
					
						
							|  |  |  | 			evt.preventDefault() | 
					
						
							|  |  |  | 			evt.stopPropagation() | 
					
						
							|  |  |  | 			cls | 
					
						
							|  |  |  | 				&& elem.classList.remove(cls) | 
					
						
							|  |  |  | 			end | 
					
						
							|  |  |  | 				&& end(elem, data)  | 
					
						
							|  |  |  | 			data = false } } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX can we reuse these???
 | 
					
						
							|  |  |  | 	// 		...i.e. keep the data in the element???
 | 
					
						
							|  |  |  | 	handle.addEventListener('mousedown', handleMoveStart) | 
					
						
							|  |  |  | 	handle.addEventListener('touchstart', handleMoveStart) | 
					
						
							|  |  |  | 	document.addEventListener('mousemove', handleMove) | 
					
						
							|  |  |  | 	document.addEventListener('touchmove', handleMove) | 
					
						
							|  |  |  | 	document.addEventListener('touchend', handleMoveEnd) | 
					
						
							|  |  |  | 	document.addEventListener('mouseup', handleMoveEnd) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-19 18:25:33 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-22 15:33:55 +03:00
										 |  |  | // XXX add shift+arrow to select...
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | // XXX add home/end, pageup/pagedown...
 | 
					
						
							| 
									
										
										
										
											2023-08-03 15:20:42 +03:00
										 |  |  | // XXX need a real ui stack -- close things top to bottom (Enter/Escape/...)...
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | var keyboard = { | 
					
						
							|  |  |  | 	ArrowLeft: function(){ | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 		gallery.prev() }, | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 	ArrowRight: function(){ | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 		gallery.next() }, | 
					
						
							| 
									
										
										
										
											2023-07-24 21:04:42 +03:00
										 |  |  | 	// NOTE: up/down will not prevent the default scrolling behavior 
 | 
					
						
							|  |  |  | 	// 		when at top/bottom of the gallery.
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 	ArrowUp: function(evt){ | 
					
						
							| 
									
										
										
										
											2023-07-24 21:04:42 +03:00
										 |  |  | 		gallery.__at_top_row  | 
					
						
							|  |  |  | 			|| evt.preventDefault() | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 		;(gallery.dom.classList.contains('lightboxed') | 
					
						
							|  |  |  | 				|| gallery.dom.classList.contains('detailed')) | 
					
						
							|  |  |  | 			&& evt.preventDefault() | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		gallery.lightbox.shown | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 			|| gallery.details.shown | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 			|| gallery.up() }, | 
					
						
							|  |  |  | 	ArrowDown: function(evt){ | 
					
						
							| 
									
										
										
										
											2023-07-24 21:04:42 +03:00
										 |  |  | 		gallery.__at_bottom_row  | 
					
						
							|  |  |  | 			|| evt.preventDefault() | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 		;(gallery.dom.classList.contains('lightboxed') | 
					
						
							|  |  |  | 				|| gallery.dom.classList.contains('detailed')) | 
					
						
							|  |  |  | 			&& evt.preventDefault() | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		gallery.lightbox.shown | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 			|| gallery.details.shown | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 			|| gallery.down() }, | 
					
						
							|  |  |  | 	Enter: function(){ | 
					
						
							| 
									
										
										
										
											2023-08-03 15:20:42 +03:00
										 |  |  | 		gallery.details.shown ? | 
					
						
							|  |  |  | 			gallery.details.hide() | 
					
						
							|  |  |  | 			: gallery.lightbox.toggle() }, | 
					
						
							| 
									
										
										
										
											2023-07-28 14:47:24 +03:00
										 |  |  | 	Escape: function(evt){ | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 		gallery.details.shown ? | 
					
						
							|  |  |  | 			gallery.details.hide()  | 
					
						
							|  |  |  | 		: gallery.lightbox.shown ? | 
					
						
							| 
									
										
										
										
											2023-07-23 06:46:14 +03:00
										 |  |  | 			gallery.lightbox.hide()  | 
					
						
							|  |  |  | 		// XXX should we remember which image was current and select 
 | 
					
						
							|  |  |  | 		// 		it again when needed???
 | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 		: gallery.unmark_current ? | 
					
						
							| 
									
										
										
										
											2023-07-23 06:46:14 +03:00
										 |  |  | 			(gallery.current = null)  | 
					
						
							|  |  |  | 		: null }, | 
					
						
							| 
									
										
										
										
											2023-08-12 00:51:18 +03:00
										 |  |  | 	// del (image) -> mark current for deletion
 | 
					
						
							|  |  |  | 	// del (marked) -> toggle marked for deletion all marked
 | 
					
						
							|  |  |  | 	// shift-del (none marked for deletion) -> delete current
 | 
					
						
							|  |  |  | 	// shift-del (1+ marked for deletion) -> delete marked for deletion
 | 
					
						
							| 
									
										
										
										
											2023-08-12 00:13:17 +03:00
										 |  |  | 	Delete: function(evt){ | 
					
						
							| 
									
										
										
										
											2023-08-12 00:51:18 +03:00
										 |  |  | 		// remove...
 | 
					
						
							|  |  |  | 		if(evt.shiftKey){ | 
					
						
							| 
									
										
										
										
											2023-08-13 00:23:13 +03:00
										 |  |  | 			var to_remove = gallery.toBeRemoved | 
					
						
							| 
									
										
										
										
											2023-08-12 00:51:18 +03:00
										 |  |  | 			// remove marked...
 | 
					
						
							|  |  |  | 			if(to_remove.length > 0){ | 
					
						
							| 
									
										
										
										
											2023-08-13 00:23:13 +03:00
										 |  |  | 				gallery.removeQueued() | 
					
						
							| 
									
										
										
										
											2023-08-12 00:51:18 +03:00
										 |  |  | 			// remove current...
 | 
					
						
							|  |  |  | 			} else if(gallery.current){ | 
					
						
							| 
									
										
										
										
											2023-08-13 00:23:13 +03:00
										 |  |  | 				to_remove = gallery.current | 
					
						
							| 
									
										
										
										
											2023-08-12 00:51:18 +03:00
										 |  |  | 				if(gallery.marked.includes(to_remove)){ | 
					
						
							|  |  |  | 					gallery.remove(...gallery.marked) | 
					
						
							|  |  |  | 				} else { | 
					
						
							| 
									
										
										
										
											2023-08-13 00:23:13 +03:00
										 |  |  | 					// move focus...
 | 
					
						
							| 
									
										
										
										
											2023-08-12 00:51:18 +03:00
										 |  |  | 					gallery.next() | 
					
						
							|  |  |  | 					gallery.current === to_remove | 
					
						
							|  |  |  | 						&& gallery.prev() | 
					
						
							|  |  |  | 					gallery.remove(to_remove) } }  | 
					
						
							|  |  |  | 		// mark for removal...
 | 
					
						
							|  |  |  | 		} else if(gallery.current){ | 
					
						
							|  |  |  | 			var cur = gallery.current | 
					
						
							|  |  |  | 			// toggle marked...
 | 
					
						
							|  |  |  | 			if(gallery.marked.includes(cur)){ | 
					
						
							| 
									
										
										
										
											2023-08-13 00:23:13 +03:00
										 |  |  | 				gallery.toggleQueueRemoval('marked')  | 
					
						
							| 
									
										
										
										
											2023-08-12 00:51:18 +03:00
										 |  |  | 			// toggle current...
 | 
					
						
							|  |  |  | 			} else { | 
					
						
							| 
									
										
										
										
											2023-08-13 00:23:13 +03:00
										 |  |  | 				gallery.toggleQueueRemoval() } } }, | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 	// selection...
 | 
					
						
							|  |  |  | 	' ': function(evt){ | 
					
						
							|  |  |  | 		gallery.current | 
					
						
							|  |  |  | 			&& evt.preventDefault() | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 		gallery.toggleMark() }, | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 	// XXX use key codes...
 | 
					
						
							|  |  |  | 	'a': function(evt){ | 
					
						
							|  |  |  | 		evt.preventDefault() | 
					
						
							|  |  |  | 		if(evt.ctrlKey){ | 
					
						
							| 
									
										
										
										
											2023-08-13 00:23:13 +03:00
										 |  |  | 			gallery.markAll() } }, | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 	'd': function(evt){ | 
					
						
							|  |  |  | 		evt.preventDefault() | 
					
						
							|  |  |  | 		if(evt.ctrlKey){ | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 			gallery.unmarkAll() } }, | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 	'i': function(evt){ | 
					
						
							|  |  |  | 		evt.preventDefault() | 
					
						
							|  |  |  | 		if(evt.ctrlKey){ | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 			gallery.markInverse() } }, | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Gallery = { | 
					
						
							| 
									
										
										
										
											2023-07-21 14:40:30 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-24 21:04:42 +03:00
										 |  |  | 	// Options...
 | 
					
						
							| 
									
										
										
										
											2023-07-22 15:33:55 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 	unmark_current: true, | 
					
						
							| 
									
										
										
										
											2023-07-24 21:04:42 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// If true navigation will loop over top/bottom rows and first/last 
 | 
					
						
							|  |  |  | 	// images...
 | 
					
						
							|  |  |  | 	// This is mainly usefull for small galleries that do not need paging.
 | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 	// XXX might be a good idea to auto-disable this when paging is required.
 | 
					
						
							| 
									
										
										
										
											2023-07-24 21:04:42 +03:00
										 |  |  | 	loop_images: false, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 	// If true for each row two versions will be compared, as-is and with 
 | 
					
						
							|  |  |  | 	// one image from the next row and the closest resiae will be chosen.
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// This produses a more uniform image grid but will have greater 
 | 
					
						
							|  |  |  | 	// effect on gallery height / slower.
 | 
					
						
							| 
									
										
										
										
											2023-07-21 14:40:30 +03:00
										 |  |  | 	allow_row_expansion: true, | 
					
						
							| 
									
										
										
										
											2023-07-24 21:04:42 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 	// define how to handle last row
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// can be:
 | 
					
						
							|  |  |  | 	// 	'as-is'		- do not resize
 | 
					
						
							|  |  |  | 	// 	'justify'	- fit width
 | 
					
						
							|  |  |  | 	// 	<number>	- maximum ration relative to largest row (must be >=1)
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// Recomended value close to 1
 | 
					
						
							|  |  |  | 	last_row_resize: 1, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// If true first click on image will select it the second click will
 | 
					
						
							|  |  |  | 	// open it...
 | 
					
						
							| 
									
										
										
										
											2023-07-22 00:11:20 +03:00
										 |  |  | 	click_to_select: true, | 
					
						
							| 
									
										
										
										
											2023-07-21 14:40:30 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-27 17:00:34 +03:00
										 |  |  | 	exit_fullscreen_on_lightbox_close: true, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 21:57:18 +03:00
										 |  |  | 	// Mode to select the above/below image...
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 					 	   	   
 | 
					
						
							|  |  |  | 	// 		     - -----+-------------+
 | 
					
						
							|  |  |  | 	// 					|	   .	  |
 | 
					
						
							|  |  |  | 	// 					|   current	  |
 | 
					
						
							|  |  |  | 	// 					|	   .	  |
 | 
					
						
							|  |  |  | 	// 		- --+-------+---.---+--.--+
 | 
					
						
							|  |  |  | 	// 			|		.	    |  .  |			
 | 
					
						
							|  |  |  | 	// 			|	    B	    |  A  |			
 | 
					
						
							|  |  |  | 	// 			|		.	    |  .  |			
 | 
					
						
							|  |  |  | 	// 		- --+---------------+-----+
 | 
					
						
							|  |  |  | 	// 			 		^   ^      ^
 | 
					
						
							|  |  |  | 	// 			 		c   i      c   			
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// Here, A has the closest center (c) to current but B has the closest 
 | 
					
						
							|  |  |  | 	// center of intersection (i), thus the two approaches will yield 
 | 
					
						
							|  |  |  | 	// different results, moving down from current:
 | 
					
						
							|  |  |  | 	// 		current ----(center)----> A
 | 
					
						
							|  |  |  | 	// 		current -(intersection)-> B
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// can be:
 | 
					
						
							|  |  |  | 	// 	'intersection'	- closest center of intersecting part to center 
 | 
					
						
							|  |  |  | 	// 						of current image.
 | 
					
						
							|  |  |  | 	// 	'center'		- closest center of image to current image center
 | 
					
						
							|  |  |  | 	// XXX remove this when/if the selected options feels natural...
 | 
					
						
							|  |  |  | 	vertical_navigate_mode: 'intersection', | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-22 20:27:52 +03:00
										 |  |  | 	// a timeout to let the resize settle before we handle dragover...
 | 
					
						
							|  |  |  | 	// XXX this is too long -- we can pickup and drag an image within this 
 | 
					
						
							|  |  |  | 	// 		timeout...
 | 
					
						
							|  |  |  | 	// 		need to make it more specific (handle only vertical drag???)
 | 
					
						
							|  |  |  | 	resize_settle_timeout: 16, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-21 03:16:20 +03:00
										 |  |  | 	code: `
 | 
					
						
							|  |  |  | 		<div class="gallery"> | 
					
						
							|  |  |  | 			<!-- gallery: content --> | 
					
						
							|  |  |  | 			<div class="images"> | 
					
						
							|  |  |  | 			</div> | 
					
						
							|  |  |  | 			<!-- lightbox --> | 
					
						
							|  |  |  | 			<div class="lightbox"> | 
					
						
							|  |  |  | 				<img> | 
					
						
							|  |  |  | 				<div class="button close"></div> | 
					
						
							|  |  |  | 			</div> | 
					
						
							|  |  |  | 		</div>`, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 	dom: undefined, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-18 09:11:58 +03:00
										 |  |  | 	// XXX these are the same....
 | 
					
						
							| 
									
										
										
										
											2023-09-03 20:33:41 +03:00
										 |  |  | 	// XXX add support for multiple toolbars...
 | 
					
						
							|  |  |  | 	__toolbars: undefined, | 
					
						
							|  |  |  | 	get toolbars(){ | 
					
						
							| 
									
										
										
										
											2023-08-18 09:11:58 +03:00
										 |  |  | 		if(this.dom){ | 
					
						
							| 
									
										
										
										
											2023-09-03 20:33:41 +03:00
										 |  |  | 			var that = this | 
					
						
							|  |  |  | 			return this.__toolbars  | 
					
						
							|  |  |  | 				?? (this.__toolbars =  | 
					
						
							|  |  |  | 					[...this.dom.querySelectorAll('.toolbar')] | 
					
						
							|  |  |  | 						.map(function(toolbar){ | 
					
						
							|  |  |  | 							return {__proto__: Toolbar} | 
					
						
							|  |  |  | 								.setup( | 
					
						
							|  |  |  | 									toolbar,  | 
					
						
							|  |  |  | 									that) })) } | 
					
						
							|  |  |  | 		delete this.__toolbars | 
					
						
							| 
									
										
										
										
											2023-08-18 09:11:58 +03:00
										 |  |  | 		return undefined }, | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 	__lightbox: undefined, | 
					
						
							|  |  |  | 	get lightbox(){ | 
					
						
							|  |  |  | 		if(this.dom){ | 
					
						
							|  |  |  | 			return this.__lightbox  | 
					
						
							|  |  |  | 				?? (this.__lightbox = { __proto__: Lightbox } | 
					
						
							|  |  |  | 					.setup( | 
					
						
							|  |  |  | 						this.dom.querySelector('.lightbox'), | 
					
						
							|  |  |  | 						this)) } | 
					
						
							|  |  |  | 		delete this.__lightbox  | 
					
						
							|  |  |  | 		return undefined }, | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 	__details: undefined, | 
					
						
							|  |  |  | 	get details(){ | 
					
						
							|  |  |  | 		if(this.dom){ | 
					
						
							|  |  |  | 			return this.__details  | 
					
						
							|  |  |  | 				?? (this.__details = { __proto__: Details } | 
					
						
							|  |  |  | 					.setup( | 
					
						
							|  |  |  | 						this.dom.querySelector('.details'), | 
					
						
							|  |  |  | 						this)) } | 
					
						
							|  |  |  | 		delete this.__details  | 
					
						
							|  |  |  | 		return undefined }, | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-24 21:04:42 +03:00
										 |  |  | 	__at_top_row: undefined, | 
					
						
							|  |  |  | 	__at_bottom_row: undefined, | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 	get current(){ | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 		return this.dom.querySelector('.images img.current') }, | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 	set current(img){ | 
					
						
							| 
									
										
										
										
											2023-07-23 06:46:14 +03:00
										 |  |  | 		// unset...
 | 
					
						
							|  |  |  | 		if(img == null){ | 
					
						
							|  |  |  | 			this.current?.classList.remove('current') | 
					
						
							|  |  |  | 			return } | 
					
						
							|  |  |  | 		// set...
 | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 		for(var i of this.dom.querySelectorAll('.images img.current')){ | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 			i.classList.remove('current') } | 
					
						
							|  |  |  | 		img.classList.add('current')  | 
					
						
							| 
									
										
										
										
											2023-07-22 15:33:55 +03:00
										 |  |  | 		// XXX add offsets from borders...
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		img.scrollIntoView({ | 
					
						
							|  |  |  | 			behavior: 'smooth', | 
					
						
							|  |  |  | 			block: 'nearest', | 
					
						
							| 
									
										
										
										
											2023-07-24 21:04:42 +03:00
										 |  |  | 		})  | 
					
						
							|  |  |  | 		// helpers...
 | 
					
						
							|  |  |  | 		this.__at_top_row = !this.getRow('above') | 
					
						
							|  |  |  | 		this.__at_bottom_row = !this.getRow('below') }, | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 	// XXX should this be writable???
 | 
					
						
							|  |  |  | 	get images(){ | 
					
						
							|  |  |  | 		return [...this.dom.querySelectorAll('.images img')] }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-20 22:06:51 +03:00
										 |  |  | 	get urls(){ | 
					
						
							|  |  |  | 		return this.images | 
					
						
							|  |  |  | 			.map(function(img){  | 
					
						
							|  |  |  | 				return img.src }) }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 	get marked(){ | 
					
						
							|  |  |  | 		return [...this.dom.querySelectorAll('.images img.marked')] }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 21:57:18 +03:00
										 |  |  | 	get length(){ | 
					
						
							|  |  |  | 		return this.images.length }, | 
					
						
							|  |  |  | 	get index(){ | 
					
						
							|  |  |  | 		return this.images.indexOf(this.current) }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-02 12:48:47 +03:00
										 |  |  | 	getRow: function(img, direction='current', images){ | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		if(['above', 'current', 'below'].includes(img)){ | 
					
						
							| 
									
										
										
										
											2023-08-02 12:48:47 +03:00
										 |  |  | 			images = direction | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 			direction = img | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 			img = this.current } | 
					
						
							| 
									
										
										
										
											2023-08-02 12:48:47 +03:00
										 |  |  | 		if(direction instanceof Array){ | 
					
						
							|  |  |  | 			images = direction | 
					
						
							|  |  |  | 			direction = null } | 
					
						
							|  |  |  | 		direction ??= 'current' | 
					
						
							|  |  |  | 		images ??= this.images | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		// get above/below row...
 | 
					
						
							|  |  |  | 		// XXX these are wastefull...
 | 
					
						
							|  |  |  | 		if(direction == 'above'){ | 
					
						
							|  |  |  | 			var row = this.getRow(img) | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 			var e = row[0].previousElementSibling | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 			while(e && e.tagName != 'IMG'){ | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 				e = e.previousElementSibling } | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 			return e ? | 
					
						
							| 
									
										
										
										
											2023-07-24 21:04:42 +03:00
										 |  |  | 					this.getRow(e) | 
					
						
							|  |  |  | 				: this.loop_images ? | 
					
						
							| 
									
										
										
										
											2023-08-02 12:48:47 +03:00
										 |  |  | 					this.getRow(images.at(-1)) | 
					
						
							| 
									
										
										
										
											2023-07-24 21:04:42 +03:00
										 |  |  | 				: undefined | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		} else if(direction == 'below'){ | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 			// special case: nothing marked...
 | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 			if(img == null){ | 
					
						
							|  |  |  | 				return this.getRow() } | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 			var row = this.getRow(img) | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 			var e = row.at(-1).nextElementSibling | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 			while(e && e.tagName != 'IMG'){ | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 				e = e.nextElementSibling } | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 			return e ? | 
					
						
							| 
									
										
										
										
											2023-07-24 21:04:42 +03:00
										 |  |  | 					this.getRow(e)  | 
					
						
							|  |  |  | 				: this.loop_images ? | 
					
						
							| 
									
										
										
										
											2023-08-02 12:48:47 +03:00
										 |  |  | 					this.getRow(images[0]) | 
					
						
							| 
									
										
										
										
											2023-07-24 21:04:42 +03:00
										 |  |  | 				: undefined } | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		// get current row...
 | 
					
						
							|  |  |  | 		var cur = img  | 
					
						
							|  |  |  | 			?? this.current | 
					
						
							|  |  |  | 		if(cur == null){ | 
					
						
							|  |  |  | 			var scroll = getScrollParent(this.dom).scrollTop | 
					
						
							| 
									
										
										
										
											2023-08-02 12:48:47 +03:00
										 |  |  | 			var images = images  | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 			for(cur of images){ | 
					
						
							|  |  |  | 				if(cur.offsetTop >= scroll){ | 
					
						
							|  |  |  | 					break } } } | 
					
						
							|  |  |  | 		var top = cur.offsetTop | 
					
						
							|  |  |  | 		var row = [] | 
					
						
							|  |  |  | 		var e = cur | 
					
						
							|  |  |  | 		while(e && e.offsetTop == top){ | 
					
						
							|  |  |  | 			row.push(e) | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 			e = e.nextElementSibling | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 			while(e && e.tagName != 'IMG'){ | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 				e = e.nextElementSibling } } | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		e = cur | 
					
						
							|  |  |  | 		while(e && e.offsetTop == top){ | 
					
						
							|  |  |  | 			e === cur | 
					
						
							|  |  |  | 				|| row.unshift(e) | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 			e = e.previousElementSibling | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 			while(e && e.tagName != 'IMG'){ | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 				e = e.previousElementSibling } } | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		return row }, | 
					
						
							| 
									
										
										
										
											2023-07-24 21:04:42 +03:00
										 |  |  | 	// XXX add .loop_images support???
 | 
					
						
							| 
									
										
										
										
											2023-08-02 12:48:47 +03:00
										 |  |  | 	getImage: function(img, direction='current', images){ | 
					
						
							|  |  |  | 		// .getImage(direction[, images])
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		if(['left', 'above', 'current', 'below', 'right'].includes(img)){ | 
					
						
							| 
									
										
										
										
											2023-08-02 12:48:47 +03:00
										 |  |  | 			images = direction | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 			direction = img | 
					
						
							|  |  |  | 			img = null } | 
					
						
							| 
									
										
										
										
											2023-08-02 12:48:47 +03:00
										 |  |  | 		// .getImage(img, images)
 | 
					
						
							|  |  |  | 		if(direction instanceof Array){ | 
					
						
							|  |  |  | 			images = direction | 
					
						
							|  |  |  | 			direction = null } | 
					
						
							|  |  |  | 		direction ??= 'current' | 
					
						
							|  |  |  | 		images ??= this.images | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		// current...
 | 
					
						
							|  |  |  | 		if(direction == 'current'){ | 
					
						
							|  |  |  | 			return img  | 
					
						
							|  |  |  | 				?? this.current  | 
					
						
							| 
									
										
										
										
											2023-08-02 12:48:47 +03:00
										 |  |  | 				?? this.getRow(img, images)[0] | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		// above/below...
 | 
					
						
							| 
									
										
										
										
											2023-07-25 21:57:18 +03:00
										 |  |  | 		// get image with closest center to target image center...
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		} else if(direction == 'above' || direction == 'below'){ | 
					
						
							| 
									
										
										
										
											2023-08-02 12:48:47 +03:00
										 |  |  | 			var row = this.getRow(direction, images) | 
					
						
							| 
									
										
										
										
											2023-07-24 21:04:42 +03:00
										 |  |  | 			if(row == null){ | 
					
						
							|  |  |  | 				return undefined } | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 			var cur = this.current  | 
					
						
							|  |  |  | 				?? row[0] | 
					
						
							| 
									
										
										
										
											2023-07-25 21:57:18 +03:00
										 |  |  | 			// image center point...
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 			var c = cur.offsetLeft + cur.offsetWidth/2 | 
					
						
							|  |  |  | 			var target | 
					
						
							|  |  |  | 			var min | 
					
						
							|  |  |  | 			for(var img of row){ | 
					
						
							| 
									
										
										
										
											2023-07-25 21:57:18 +03:00
										 |  |  | 				// length of intersection...
 | 
					
						
							|  |  |  | 				if(this.vertical_navigate_mode == 'intersection'){ | 
					
						
							|  |  |  | 					var l = Math.max( | 
					
						
							|  |  |  | 						img.offsetLeft,  | 
					
						
							|  |  |  | 						cur.offsetLeft) | 
					
						
							|  |  |  | 					var r = Math.min( | 
					
						
							|  |  |  | 						img.offsetLeft + img.offsetWidth, | 
					
						
							|  |  |  | 						cur.offsetLeft + cur.offsetWidth) | 
					
						
							|  |  |  | 					var w = r - l | 
					
						
							|  |  |  | 					var n = l + w/2 | 
					
						
							|  |  |  | 				// closest center...
 | 
					
						
							|  |  |  | 				} else { | 
					
						
							|  |  |  | 					var n = img.offsetLeft + img.offsetWidth/2 } | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 				var d = Math.abs(n - c) | 
					
						
							|  |  |  | 				min = min ?? d | 
					
						
							|  |  |  | 				if(d <= min){ | 
					
						
							|  |  |  | 					min = d | 
					
						
							|  |  |  | 					target = img } }  | 
					
						
							|  |  |  | 		// left/right...
 | 
					
						
							|  |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2023-08-02 12:48:47 +03:00
										 |  |  | 			var row = this.getRow(img, images) | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 			var i = row.indexOf( | 
					
						
							|  |  |  | 				img  | 
					
						
							|  |  |  | 					?? this.current  | 
					
						
							|  |  |  | 					?? row[0]) | 
					
						
							|  |  |  | 			i += direction == 'left' ? | 
					
						
							|  |  |  | 				-1 | 
					
						
							|  |  |  | 				: +1 | 
					
						
							|  |  |  | 			i = i < 0 ? | 
					
						
							|  |  |  | 					row.length-1 | 
					
						
							|  |  |  | 				: i >= row.length-1 ? | 
					
						
							|  |  |  | 					0 | 
					
						
							|  |  |  | 				: i | 
					
						
							|  |  |  | 			var target = row[i] } | 
					
						
							|  |  |  | 		return target }, | 
					
						
							| 
									
										
										
										
											2023-08-13 00:23:13 +03:00
										 |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.getImages()
 | 
					
						
							|  |  |  | 	// 		-> []
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.getImages('*')
 | 
					
						
							|  |  |  | 	// 	.getImages('all')
 | 
					
						
							|  |  |  | 	// 		-> <all-images>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.getImages('marked')
 | 
					
						
							|  |  |  | 	// 		-> <marked-images>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.getImages(<img>, ..)
 | 
					
						
							|  |  |  | 	// 	.getImages([<img>, ..])
 | 
					
						
							|  |  |  | 	// 		-> <images>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// 	.getImages('sorted', ..)
 | 
					
						
							|  |  |  | 	// 	.getImages(.., 'sorted')
 | 
					
						
							|  |  |  | 	// 		-> <sorted-images>
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// <img> ::=
 | 
					
						
							|  |  |  | 	// 		ImageElement
 | 
					
						
							|  |  |  | 	// 		| <index>
 | 
					
						
							|  |  |  | 	// 		| 'current'
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	getImages: function(...images){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		var sorted =  | 
					
						
							|  |  |  | 			images.at(-1) == 'sorted' ? | 
					
						
							|  |  |  | 				!!images.pop() | 
					
						
							|  |  |  | 			: images[0] == 'sorted' ? | 
					
						
							|  |  |  | 				!!images.shift() | 
					
						
							|  |  |  | 			: false | 
					
						
							|  |  |  | 		images = images.flat() | 
					
						
							|  |  |  | 		if(images.includes('all')  | 
					
						
							|  |  |  | 				|| images.includes('*')){ | 
					
						
							|  |  |  | 			return this.images } | 
					
						
							|  |  |  | 		images = [...new Set(images | 
					
						
							|  |  |  | 			.map(function(img){ | 
					
						
							|  |  |  | 				return ((typeof(img) == 'string'  | 
					
						
							|  |  |  | 								&& img.trim() == '') ? | 
					
						
							|  |  |  | 							[] | 
					
						
							|  |  |  | 						: !isNaN(img*1) ? | 
					
						
							|  |  |  | 							that.images.at(img*1) | 
					
						
							|  |  |  | 						: img == 'marked' ? | 
					
						
							|  |  |  | 							that.marked | 
					
						
							|  |  |  | 						: img == 'current' ? | 
					
						
							|  |  |  | 							that.current | 
					
						
							|  |  |  | 						: typeof(img) == 'string' ? | 
					
						
							|  |  |  | 							[] | 
					
						
							|  |  |  | 						: img) | 
					
						
							|  |  |  | 					?? [] }) | 
					
						
							|  |  |  |    			.flat())]  | 
					
						
							|  |  |  | 		if(sorted){ | 
					
						
							|  |  |  | 			var order = new Map( | 
					
						
							|  |  |  | 				Object.entries(this.images) | 
					
						
							|  |  |  | 					.map(function(e){  | 
					
						
							|  |  |  | 						return e.reverse() })) | 
					
						
							|  |  |  | 			images | 
					
						
							|  |  |  | 				.sort(function(a, b){ | 
					
						
							|  |  |  | 					return order.get(a) - order.get(b) }) } | 
					
						
							|  |  |  | 		return images }, | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// XXX cache image list???
 | 
					
						
							| 
									
										
										
										
											2023-08-02 12:48:47 +03:00
										 |  |  | 	prev: function(images){ | 
					
						
							|  |  |  | 		images ??= this.images | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		var i = this.current == null ?  | 
					
						
							|  |  |  | 			images.length-1 | 
					
						
							|  |  |  | 			: images.indexOf(this.current)-1 | 
					
						
							| 
									
										
										
										
											2023-07-24 21:04:42 +03:00
										 |  |  | 		i = i >= 0 ? | 
					
						
							|  |  |  | 				i | 
					
						
							|  |  |  | 			: this.loop_images ? | 
					
						
							|  |  |  | 				images.length-1 | 
					
						
							|  |  |  | 			: 0 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		this.current = images[i] | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 		this.update() | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2023-08-02 12:48:47 +03:00
										 |  |  | 	next: function(images){ | 
					
						
							|  |  |  | 		images ??= this.images | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		var i = this.current == null ?  | 
					
						
							|  |  |  | 			0 | 
					
						
							|  |  |  | 			: images.indexOf(this.current)+1 | 
					
						
							| 
									
										
										
										
											2023-07-24 21:04:42 +03:00
										 |  |  | 		i = i < images.length ? | 
					
						
							|  |  |  | 				i | 
					
						
							|  |  |  | 			: this.loop_images ? | 
					
						
							|  |  |  | 				0 | 
					
						
							|  |  |  | 			: images.length-1 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		this.current = images[i] | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 		this.update() | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		return this }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// navigate images visually...
 | 
					
						
							| 
									
										
										
										
											2023-08-03 09:52:15 +03:00
										 |  |  | 	// XXX BUG: these seem not to work with passed list of images...
 | 
					
						
							| 
									
										
										
										
											2023-08-02 12:48:47 +03:00
										 |  |  | 	left: function(images){ | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		var cur = this.current | 
					
						
							| 
									
										
										
										
											2023-08-02 12:48:47 +03:00
										 |  |  | 		var row = this.getRow(cur, images) | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		var i = row.indexOf(cur) - 1 | 
					
						
							|  |  |  | 		this.current = row[i < 0 ? | 
					
						
							| 
									
										
										
										
											2023-07-23 06:46:14 +03:00
										 |  |  | 			row.length-1 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 			: i] | 
					
						
							|  |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2023-08-02 12:48:47 +03:00
										 |  |  | 	right: function(images){ | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		var cur = this.current | 
					
						
							| 
									
										
										
										
											2023-08-02 12:48:47 +03:00
										 |  |  | 		var row = this.getRow(cur, images) | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		var i = row.indexOf(cur) + 1 | 
					
						
							|  |  |  | 		this.current = row[i >= row.length ? | 
					
						
							| 
									
										
										
										
											2023-07-23 06:46:14 +03:00
										 |  |  | 			0 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 			: i] | 
					
						
							|  |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2023-08-02 12:48:47 +03:00
										 |  |  | 	up: function(images){ | 
					
						
							|  |  |  | 		var img = this.getImage('above', images) | 
					
						
							| 
									
										
										
										
											2023-07-24 21:04:42 +03:00
										 |  |  | 		img  | 
					
						
							|  |  |  | 			&& (this.current = img) | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2023-08-02 12:48:47 +03:00
										 |  |  | 	down: function(images){ | 
					
						
							|  |  |  | 		var img = this.getImage('below', images) | 
					
						
							| 
									
										
										
										
											2023-07-24 21:04:42 +03:00
										 |  |  | 		img  | 
					
						
							|  |  |  | 			&& (this.current = img) | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		return this }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 	// selection...
 | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2023-07-20 22:06:51 +03:00
										 |  |  | 	// NOTE: this is here because we can't use :before / :after directly 
 | 
					
						
							|  |  |  | 	// 		on the img tag...
 | 
					
						
							| 
									
										
										
										
											2023-07-22 15:33:55 +03:00
										 |  |  | 	// XXX make this generic and use a .marks list...
 | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 	updateMarkers: function(){ | 
					
						
							| 
									
										
										
										
											2023-07-22 15:33:55 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 		// select...
 | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 		for(var img of this.dom.querySelectorAll('.images img.marked')){ | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 			var mark = img.nextElementSibling | 
					
						
							|  |  |  | 			while(mark && mark.tagName != 'IMG' && !mark.classList.contains('mark')){ | 
					
						
							|  |  |  | 				mark = img.nextElementSibling } | 
					
						
							|  |  |  | 			if(!mark || !mark.classList.contains('mark')){ | 
					
						
							|  |  |  | 				mark = document.createElement('div') | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 				mark.classList.add('marked', 'mark') | 
					
						
							| 
									
										
										
										
											2023-07-22 15:33:55 +03:00
										 |  |  | 				mark.addEventListener('click', function(evt){ | 
					
						
							|  |  |  | 					evt.stopPropagation() | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 					that.unmark(mark) }) | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 				img.after(mark) } } | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 		// clear unmarked...
 | 
					
						
							|  |  |  | 		for(var mark of this.dom.querySelectorAll('.images img:not(.marked)+.mark')){ | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 			mark.remove() } | 
					
						
							| 
									
										
										
										
											2023-07-21 14:40:30 +03:00
										 |  |  | 		// update lightbox...
 | 
					
						
							| 
									
										
										
										
											2023-07-22 15:33:55 +03:00
										 |  |  | 		this.lightbox.shown | 
					
						
							|  |  |  | 			&& this.lightbox.update() | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 	mark: function(img){ | 
					
						
							| 
									
										
										
										
											2023-07-22 15:33:55 +03:00
										 |  |  | 		img = img ?? this.current | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 		img?.classList.add('marked') | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 		return this.updateMarkers() }, | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 	unmark: function(img){ | 
					
						
							| 
									
										
										
										
											2023-07-22 15:33:55 +03:00
										 |  |  | 		img = img ?? this.current | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 		img?.classList.remove('marked') | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 		return this.updateMarkers() }, | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 	toggleMark: function(img){ | 
					
						
							| 
									
										
										
										
											2023-07-22 15:33:55 +03:00
										 |  |  | 		img = img ?? this.current | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 		img?.classList.toggle('marked') | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 		this.updateMarkers() | 
					
						
							|  |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2023-08-13 00:23:13 +03:00
										 |  |  | 	markAll: function(){ | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 		for(var img of this.images){ | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 			img.classList.add('marked') } | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 		return this.updateMarkers() }, | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 	unmarkAll: function(){ | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 		for(var img of this.images){ | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 			img.classList.remove('marked') } | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 		return this.updateMarkers() }, | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 	markInverse: function(){ | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 		for(var img of this.images){ | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 			img.classList.toggle('marked') } | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 		return this.updateMarkers() }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-22 20:48:36 +03:00
										 |  |  | 	// XXX should this be an interface to something like .splash???
 | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 	get loading(){ | 
					
						
							|  |  |  | 		return !this.dom.classList.contains('ready') }, | 
					
						
							|  |  |  | 	showLoading: function(){ | 
					
						
							|  |  |  | 		this.dom.classList.remove('ready') | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 	hideLoading: function(){ | 
					
						
							|  |  |  | 		this.dom.classList.add('ready') | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 	toggleLoading: function(){ | 
					
						
							|  |  |  | 		this.dom.classList.toggle('ready') | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-22 20:48:36 +03:00
										 |  |  | 	// show current image in lightbox...
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 	show: function(){ | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 		this.lightbox.show() | 
					
						
							|  |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-07 17:16:24 +03:00
										 |  |  | 	__update_grid_size_timeout: undefined, | 
					
						
							|  |  |  | 	__update_grid_size_running: false, | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 	__update_grid_size: function(){ | 
					
						
							| 
									
										
										
										
											2023-08-07 17:16:24 +03:00
										 |  |  | 		// if still running then delay untill done...
 | 
					
						
							|  |  |  | 		// NOTE: this will keep only one call no matter how many times 
 | 
					
						
							|  |  |  | 		// 		the method was actually called...
 | 
					
						
							|  |  |  | 		if(this.__update_grid_size_running){ | 
					
						
							|  |  |  | 			var that = this | 
					
						
							|  |  |  | 			this.__update_grid_size_timeout | 
					
						
							|  |  |  | 				&& clearTimeout(this.__update_grid_size_timeout) | 
					
						
							|  |  |  | 			this.__update_grid_size_timeout = setTimeout(function(){ | 
					
						
							|  |  |  | 				that.__update_grid_size()  | 
					
						
							|  |  |  | 				delete that.__update_grid_size_timeout }, 10) | 
					
						
							|  |  |  | 			return this } | 
					
						
							|  |  |  | 		// do the update...
 | 
					
						
							|  |  |  | 		this.__update_grid_size_running = true | 
					
						
							|  |  |  | 		try{ | 
					
						
							|  |  |  | 			patchFlexRows(this.images,  | 
					
						
							|  |  |  | 				!this.allow_row_expansion,  | 
					
						
							|  |  |  | 				this.last_row_resize ?? 1.2) | 
					
						
							|  |  |  | 		}catch(err){ } | 
					
						
							|  |  |  | 		delete this.__update_grid_size_running | 
					
						
							| 
									
										
										
										
											2023-07-20 22:06:51 +03:00
										 |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 	update: function(){ | 
					
						
							|  |  |  | 		this.__update_grid_size() | 
					
						
							| 
									
										
										
										
											2023-08-11 13:21:01 +03:00
										 |  |  | 		// XXX should this update markers???
 | 
					
						
							|  |  |  | 		//this.updateMarkers()
 | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 		this.lightbox.shown | 
					
						
							|  |  |  | 			&& this.lightbox.update() | 
					
						
							|  |  |  | 		this.details.shown | 
					
						
							|  |  |  | 			&& this.details.update() | 
					
						
							|  |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2023-07-20 22:06:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-28 19:41:44 +03:00
										 |  |  | 	//	.load(<image>)
 | 
					
						
							|  |  |  | 	//	.load(<images>)
 | 
					
						
							|  |  |  | 	//	.load(<image>, <index>)
 | 
					
						
							|  |  |  | 	//	.load(<images>, <index>)
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	//	<images> ::=
 | 
					
						
							|  |  |  | 	//		<image>
 | 
					
						
							|  |  |  | 	//		| [ <image>, .. ]
 | 
					
						
							|  |  |  | 	//	<image> ::=
 | 
					
						
							|  |  |  | 	//		<url>
 | 
					
						
							|  |  |  | 	//		| [ <url>, <caption>, .. ]
 | 
					
						
							|  |  |  | 	//		| { url: <url>, caption: <caption>, .. }
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							| 
									
										
										
										
											2023-08-03 18:09:56 +03:00
										 |  |  | 	// XXX BUG: for some reason this breaks the gallery:
 | 
					
						
							|  |  |  | 	// 			gallery.load(gallery.json('marked'))
 | 
					
						
							| 
									
										
										
										
											2023-07-28 19:41:44 +03:00
										 |  |  | 	// XXX do we handle previews here???
 | 
					
						
							|  |  |  | 	load: function(images, index=undefined){ | 
					
						
							| 
									
										
										
										
											2023-08-07 17:16:24 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2023-07-28 19:41:44 +03:00
										 |  |  | 		images = images instanceof Array ? | 
					
						
							|  |  |  | 			images | 
					
						
							|  |  |  | 			: [images] | 
					
						
							|  |  |  | 		// create images...
 | 
					
						
							|  |  |  | 		var elems = [] | 
					
						
							|  |  |  | 		for(var data of images){ | 
					
						
							|  |  |  | 			if(typeof(data) == 'string'){ | 
					
						
							| 
									
										
										
										
											2023-08-07 17:16:24 +03:00
										 |  |  | 				var [url, data] = [data, {}] | 
					
						
							| 
									
										
										
										
											2023-07-28 19:41:44 +03:00
										 |  |  | 			} else if(data instanceof Array){ | 
					
						
							| 
									
										
										
										
											2023-08-07 17:16:24 +03:00
										 |  |  | 				var [url, caption] = data | 
					
						
							|  |  |  | 				data = {} | 
					
						
							|  |  |  | 				caption  | 
					
						
							|  |  |  | 					?? (data.caption = caption) | 
					
						
							| 
									
										
										
										
											2023-07-28 19:41:44 +03:00
										 |  |  | 			} else { | 
					
						
							|  |  |  | 				var {url, ...data} = data } | 
					
						
							|  |  |  | 			var elem = document.createElement('img') | 
					
						
							| 
									
										
										
										
											2023-08-07 17:16:24 +03:00
										 |  |  | 			elem.onload = function(){ | 
					
						
							|  |  |  | 				that.update() } | 
					
						
							| 
									
										
										
										
											2023-07-28 19:41:44 +03:00
										 |  |  | 			elem.src = url | 
					
						
							| 
									
										
										
										
											2023-07-28 21:23:00 +03:00
										 |  |  | 			elem.setAttribute('draggable', 'true') | 
					
						
							| 
									
										
										
										
											2023-07-28 19:41:44 +03:00
										 |  |  | 			for(var [key, value] of Object.entries(data)){ | 
					
						
							|  |  |  | 				value | 
					
						
							| 
									
										
										
										
											2023-08-03 18:06:30 +03:00
										 |  |  | 					// XXX is this a good way to destinguish classes and attrs???
 | 
					
						
							|  |  |  | 					&& (typeof(value) == 'boolean' ?  | 
					
						
							|  |  |  | 						elem.classList.add(key) | 
					
						
							|  |  |  | 						: elem.setAttribute(key, value)) } | 
					
						
							| 
									
										
										
										
											2023-07-28 19:41:44 +03:00
										 |  |  | 			elems.push(elem) } | 
					
						
							|  |  |  | 		// add to gallery...
 | 
					
						
							|  |  |  | 		if(index == null){ | 
					
						
							|  |  |  | 			this.clear() } | 
					
						
							|  |  |  | 		if(index == null  | 
					
						
							| 
									
										
										
										
											2023-08-11 17:33:35 +03:00
										 |  |  | 				&& this.length == 0){ | 
					
						
							| 
									
										
										
										
											2023-07-28 19:41:44 +03:00
										 |  |  | 			this.dom.querySelector('.images') | 
					
						
							|  |  |  | 				.append(...elems)  | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			var sibling = this.images.at(index) | 
					
						
							|  |  |  | 			index < 0 ? | 
					
						
							|  |  |  | 				sibling.after(...elems) | 
					
						
							|  |  |  | 				: sibling.before(...elems) } | 
					
						
							|  |  |  | 		return this | 
					
						
							| 
									
										
										
										
											2023-08-03 18:06:30 +03:00
										 |  |  | 			.updateMarkers() | 
					
						
							| 
									
										
										
										
											2023-07-28 19:41:44 +03:00
										 |  |  | 			.update() }, | 
					
						
							|  |  |  | 	__image_attributes__: [ | 
					
						
							|  |  |  | 		'caption', | 
					
						
							| 
									
										
										
										
											2023-08-11 15:41:11 +03:00
										 |  |  | 		'filename', | 
					
						
							| 
									
										
										
										
											2023-07-28 19:41:44 +03:00
										 |  |  | 	], | 
					
						
							| 
									
										
										
										
											2023-08-03 18:06:30 +03:00
										 |  |  | 	__image_classes__: [ | 
					
						
							|  |  |  | 		// XXX should this be here or set as a root attribute???
 | 
					
						
							|  |  |  | 		'current', | 
					
						
							|  |  |  | 		'marked', | 
					
						
							|  |  |  | 	], | 
					
						
							| 
									
										
										
										
											2023-08-11 15:41:11 +03:00
										 |  |  | 	// XXX add option to include images as data urls...
 | 
					
						
							| 
									
										
										
										
											2023-07-28 19:41:44 +03:00
										 |  |  | 	// XXX do we handle previews here???
 | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 	json: function(images=undefined){ | 
					
						
							| 
									
										
										
										
											2023-07-28 19:41:44 +03:00
										 |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2023-08-13 00:23:13 +03:00
										 |  |  | 		return this.getImages(images ?? this.images) | 
					
						
							| 
									
										
										
										
											2023-08-03 18:06:30 +03:00
										 |  |  | 			.map(function(img, i){ | 
					
						
							| 
									
										
										
										
											2023-07-28 19:41:44 +03:00
										 |  |  | 				var res = { url: img.src } | 
					
						
							| 
									
										
										
										
											2023-08-03 18:06:30 +03:00
										 |  |  | 				for(var key of that.__image_attributes__ ?? []){ | 
					
						
							| 
									
										
										
										
											2023-07-28 19:41:44 +03:00
										 |  |  | 					var value = img.getAttribute(key) | 
					
						
							|  |  |  | 					value  | 
					
						
							|  |  |  | 						&& (res[key] = value) } | 
					
						
							| 
									
										
										
										
											2023-08-03 18:06:30 +03:00
										 |  |  | 				for(var key of that.__image_classes__ ?? []){ | 
					
						
							|  |  |  | 					img.classList.contains(key) | 
					
						
							|  |  |  | 						&& (res[key] = true) } | 
					
						
							| 
									
										
										
										
											2023-07-28 19:41:44 +03:00
										 |  |  | 				return res }) }, | 
					
						
							| 
									
										
										
										
											2023-09-02 17:04:38 +03:00
										 |  |  | 	// XXX add support for .load(..)...
 | 
					
						
							|  |  |  | 	editorJson: function(){ | 
					
						
							|  |  |  | 		var data = { | 
					
						
							|  |  |  | 			varsion: 1, | 
					
						
							|  |  |  | 			gallery: this.json(), | 
					
						
							|  |  |  | 		}  | 
					
						
							|  |  |  | 		// XXX get serilization handlers...
 | 
					
						
							|  |  |  | 		var handlers = [] | 
					
						
							|  |  |  | 		for(var handler of handlers){ | 
					
						
							|  |  |  | 			handler.call(this, data) } | 
					
						
							|  |  |  | 		return data }, | 
					
						
							| 
									
										
										
										
											2023-07-28 19:41:44 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-11 15:41:11 +03:00
										 |  |  | 	// XXX
 | 
					
						
							|  |  |  | 	zip: function(){ | 
					
						
							|  |  |  | 		var json = this.json() | 
					
						
							|  |  |  | 		// XXX
 | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-13 00:23:13 +03:00
										 |  |  | 	// removal queue...
 | 
					
						
							|  |  |  | 	get toBeRemoved(){ | 
					
						
							|  |  |  | 		return [...gallery.dom.querySelectorAll('.images img.to-remove')] }, | 
					
						
							|  |  |  | 	set toBeRemoved(lst){ | 
					
						
							|  |  |  | 		this.queueRemoval(lst) }, | 
					
						
							| 
									
										
										
										
											2023-09-01 16:15:57 +03:00
										 |  |  | 	// XXX should this be writable...
 | 
					
						
							|  |  |  | 	get toBeKeept(){ | 
					
						
							|  |  |  | 		return [...gallery.dom.querySelectorAll('.images img:not(.to-remove)')] }, | 
					
						
							|  |  |  | 	set toBeKeept(lst){ | 
					
						
							|  |  |  | 		this.unqueueRemoval(lst) }, | 
					
						
							| 
									
										
										
										
											2023-08-13 00:23:13 +03:00
										 |  |  | 	queueRemoval: function(...images){ | 
					
						
							|  |  |  | 		images = images.length == 0 ? | 
					
						
							|  |  |  | 			['current'] | 
					
						
							|  |  |  | 			: images | 
					
						
							|  |  |  | 		for(var img of this.getImages(...images)){ | 
					
						
							|  |  |  | 			img.classList.add('to-remove') }  | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 	unqueueRemoval: function(...images){ | 
					
						
							|  |  |  | 		images = images.length == 0 ? | 
					
						
							|  |  |  | 			['current'] | 
					
						
							|  |  |  | 			: images | 
					
						
							|  |  |  | 		for(var img of this.getImages(...images)){ | 
					
						
							|  |  |  | 			img.classList.remove('to-remove') }  | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 	toggleQueueRemoval: function(...images){ | 
					
						
							|  |  |  | 		images = images.length == 0 ? | 
					
						
							|  |  |  | 			['current'] | 
					
						
							|  |  |  | 			: images | 
					
						
							|  |  |  | 		for(var img of this.getImages(...images)){ | 
					
						
							|  |  |  | 			img.classList.toggle('to-remove') }  | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 	removeQueued: function(){ | 
					
						
							|  |  |  | 		return this.remove(...this.toBeRemoved) }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-28 21:23:00 +03:00
										 |  |  | 	remove: function(...images){ | 
					
						
							| 
									
										
										
										
											2023-08-13 00:23:13 +03:00
										 |  |  | 		if(images.includes('all')  | 
					
						
							|  |  |  | 				|| images.includes('*')){ | 
					
						
							| 
									
										
										
										
											2023-07-28 21:23:00 +03:00
										 |  |  | 			return this.clear() } | 
					
						
							|  |  |  | 		// NOTE: we need to remove images from the end so as not to be 
 | 
					
						
							|  |  |  | 		// 		affected by shifed indexes...
 | 
					
						
							| 
									
										
										
										
											2023-08-13 00:23:13 +03:00
										 |  |  | 		images = this.getImages('sorted', ...images) | 
					
						
							| 
									
										
										
										
											2023-07-28 21:23:00 +03:00
										 |  |  | 			.reverse() | 
					
						
							|  |  |  | 		for(var img of images){ | 
					
						
							|  |  |  | 			typeof(img) == 'number' ? | 
					
						
							|  |  |  | 				this.images.at(img)?.remove()  | 
					
						
							|  |  |  | 			: img instanceof Element ? | 
					
						
							| 
									
										
										
										
											2023-08-12 00:13:17 +03:00
										 |  |  | 				(this.images.includes(img)  | 
					
						
							| 
									
										
										
										
											2023-07-28 21:23:00 +03:00
										 |  |  | 					&& img.remove()) | 
					
						
							|  |  |  | 			: null } | 
					
						
							| 
									
										
										
										
											2023-07-28 19:41:44 +03:00
										 |  |  | 		return this | 
					
						
							| 
									
										
										
										
											2023-08-12 00:31:21 +03:00
										 |  |  | 			.updateMarkers() | 
					
						
							| 
									
										
										
										
											2023-07-28 21:23:00 +03:00
										 |  |  | 			.update() }, | 
					
						
							| 
									
										
										
										
											2023-07-20 22:06:51 +03:00
										 |  |  | 	clear: function(){ | 
					
						
							|  |  |  | 		this.dom.querySelector('.images').innerHTML = '' | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-01 16:15:57 +03:00
										 |  |  | 	// crop...
 | 
					
						
							|  |  |  | 	__crop_stack: undefined, | 
					
						
							|  |  |  | 	crop: function(lst){ | 
					
						
							|  |  |  | 		var stack = this.__crop_stack ??= [] | 
					
						
							|  |  |  | 		var state = this.json() | 
					
						
							|  |  |  | 		stack.push(state) | 
					
						
							|  |  |  | 		if(lst){ | 
					
						
							| 
									
										
										
										
											2023-09-01 16:25:21 +03:00
										 |  |  | 			state = this.json(this.getImages(lst)) } | 
					
						
							| 
									
										
										
										
											2023-09-01 16:15:57 +03:00
										 |  |  | 		this.load(state) | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 	uncrop: function(lst){ | 
					
						
							|  |  |  | 		var state = this.__crop_stack?.pop() | 
					
						
							| 
									
										
										
										
											2023-09-01 16:25:21 +03:00
										 |  |  | 		state  | 
					
						
							|  |  |  | 			&& state.length == 0 | 
					
						
							| 
									
										
										
										
											2023-09-01 16:15:57 +03:00
										 |  |  | 			&& (delete this.__crop_stack) | 
					
						
							|  |  |  | 		state  | 
					
						
							|  |  |  | 			&& this.load(state) | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// named crops...
 | 
					
						
							|  |  |  | 	// XXX add these to serilization...
 | 
					
						
							| 
									
										
										
										
											2023-09-01 16:26:45 +03:00
										 |  |  | 	// XXX add default loaded crop...
 | 
					
						
							|  |  |  | 	// XXX might be fun to explore using crops as history...
 | 
					
						
							| 
									
										
										
										
											2023-09-01 16:15:57 +03:00
										 |  |  | 	__saved_crops: undefined, | 
					
						
							|  |  |  | 	get savedCropNames(){ | 
					
						
							|  |  |  | 		return Object.keys(this.__saved_crops ?? {}) }, | 
					
						
							|  |  |  | 	saveCrop: function(name){ | 
					
						
							|  |  |  | 		var saved = this.__saved_crops ??= {} | 
					
						
							|  |  |  | 		saved[name] = this.json() | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 	loadCrop: function(name){ | 
					
						
							|  |  |  | 		var state = (this.__saved_crops ?? {})[name] | 
					
						
							|  |  |  | 		state  | 
					
						
							|  |  |  | 			&& this.load(state) | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 	removeCrop: function(name){ | 
					
						
							|  |  |  | 		delete (this.__saved_crops ?? {})[name] | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 	setup: function(dom){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		this.dom = dom | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-03 20:33:41 +03:00
										 |  |  | 		this.toolbars | 
					
						
							| 
									
										
										
										
											2023-08-15 20:45:42 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-13 22:52:18 +03:00
										 |  |  | 		// image clicks...
 | 
					
						
							| 
									
										
										
										
											2023-07-21 03:16:20 +03:00
										 |  |  | 		this.dom.querySelector('.images') | 
					
						
							| 
									
										
										
										
											2023-07-20 22:06:51 +03:00
										 |  |  | 			.addEventListener('click', function(evt){ | 
					
						
							| 
									
										
										
										
											2023-07-23 06:46:14 +03:00
										 |  |  | 				evt.stopPropagation() | 
					
						
							| 
									
										
										
										
											2023-07-20 22:06:51 +03:00
										 |  |  | 				var target = evt.target | 
					
						
							| 
									
										
										
										
											2023-07-21 03:16:20 +03:00
										 |  |  | 				if(target.tagName == 'IMG'){ | 
					
						
							| 
									
										
										
										
											2023-07-22 15:33:55 +03:00
										 |  |  | 					// shift+click: toggle selections...
 | 
					
						
							|  |  |  | 					if(evt.shiftKey){ | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 						that.toggleMark(target) | 
					
						
							| 
									
										
										
										
											2023-07-22 00:11:20 +03:00
										 |  |  | 					// first click selects, second shows...
 | 
					
						
							| 
									
										
										
										
											2023-07-22 15:33:55 +03:00
										 |  |  | 					} else if(that.click_to_select){ | 
					
						
							| 
									
										
										
										
											2023-07-22 00:11:20 +03:00
										 |  |  | 						target.classList.contains('current') ? | 
					
						
							|  |  |  | 							that.show() | 
					
						
							|  |  |  | 							: (that.current = target) | 
					
						
							|  |  |  | 					// first click selects and shows...
 | 
					
						
							|  |  |  | 					} else { | 
					
						
							|  |  |  | 						that.current = target | 
					
						
							| 
									
										
										
										
											2023-07-23 06:46:14 +03:00
										 |  |  | 						that.show() }  | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 				} else if(that.unmark_current){ | 
					
						
							| 
									
										
										
										
											2023-07-23 06:46:14 +03:00
										 |  |  | 					that.current = null } }) | 
					
						
							|  |  |  | 		this.dom | 
					
						
							|  |  |  | 			.addEventListener('click', function(evt){ | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 				that.unmark_current | 
					
						
							| 
									
										
										
										
											2023-07-23 06:46:14 +03:00
										 |  |  | 					&& (that.current = null) }) | 
					
						
							| 
									
										
										
										
											2023-08-17 11:24:30 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 03:53:12 +03:00
										 |  |  | 		// drag/drop: sort...
 | 
					
						
							|  |  |  | 		var dragged | 
					
						
							| 
									
										
										
										
											2023-08-30 02:49:03 +03:00
										 |  |  | 		var dragged_over | 
					
						
							| 
									
										
										
										
											2023-08-10 03:53:12 +03:00
										 |  |  | 		this.dom | 
					
						
							|  |  |  | 			.addEventListener('dragstart', function(evt){ | 
					
						
							|  |  |  | 				var i = that.images.indexOf(evt.target) | 
					
						
							|  |  |  | 				if(i >= 0){ | 
					
						
							| 
									
										
										
										
											2023-08-11 13:21:01 +03:00
										 |  |  | 					dragged = evt.target  | 
					
						
							| 
									
										
										
										
											2023-08-17 11:24:30 +03:00
										 |  |  | 					dragged.classList.add('dragged')  | 
					
						
							| 
									
										
										
										
											2023-08-19 01:33:35 +03:00
										 |  |  | 					evt.dataTransfer.setData('text/uri-list', evt.target.src) | 
					
						
							|  |  |  | 					evt.dataTransfer.setData('text/plain', evt.target.src) | 
					
						
							| 
									
										
										
										
											2023-08-19 00:36:51 +03:00
										 |  |  | 					// XXX this is ugly in FF...
 | 
					
						
							|  |  |  | 					//evt.dataTransfer.setDragImage(dragged, evt.offsetX, evt.offsetY)
 | 
					
						
							| 
									
										
										
										
											2023-08-17 11:24:30 +03:00
										 |  |  | 					// XXX add
 | 
					
						
							|  |  |  | 				} }) | 
					
						
							| 
									
										
										
										
											2023-08-11 13:21:01 +03:00
										 |  |  | 		var skip_dragover = false | 
					
						
							| 
									
										
										
										
											2023-08-10 03:53:12 +03:00
										 |  |  | 		this.dom | 
					
						
							|  |  |  | 			.addEventListener('dragenter', function(evt){ | 
					
						
							| 
									
										
										
										
											2023-08-30 02:49:03 +03:00
										 |  |  | 				evt.preventDefault() | 
					
						
							|  |  |  | 				evt.stopPropagation() | 
					
						
							| 
									
										
										
										
											2023-08-11 13:28:12 +03:00
										 |  |  | 				// NOTE: this prevents jumping back and forth if moving 
 | 
					
						
							|  |  |  | 				// 		image causes a resize that causes the image to 
 | 
					
						
							|  |  |  | 				// 		move again...
 | 
					
						
							| 
									
										
										
										
											2023-08-11 13:21:01 +03:00
										 |  |  | 				if(skip_dragover){ | 
					
						
							|  |  |  | 					return } | 
					
						
							|  |  |  | 				var target = evt.target | 
					
						
							|  |  |  | 				var i = that.images.indexOf(target) | 
					
						
							| 
									
										
										
										
											2023-08-11 17:33:35 +03:00
										 |  |  | 				if(dragged  | 
					
						
							|  |  |  | 						&& i >= 0){ | 
					
						
							| 
									
										
										
										
											2023-08-11 13:21:01 +03:00
										 |  |  | 					evt.offsetX < target.offsetWidth / 2 ? | 
					
						
							| 
									
										
										
										
											2023-08-10 03:53:12 +03:00
										 |  |  | 						target.after(dragged) | 
					
						
							| 
									
										
										
										
											2023-08-10 03:56:03 +03:00
										 |  |  | 						: target.before(dragged)  | 
					
						
							| 
									
										
										
										
											2023-08-11 13:21:01 +03:00
										 |  |  | 					// skip a dragover event if triggered by (right 
 | 
					
						
							|  |  |  | 					// after) resize...
 | 
					
						
							|  |  |  | 					skip_dragover = true | 
					
						
							|  |  |  | 					setTimeout(function(){ | 
					
						
							| 
									
										
										
										
											2023-08-22 20:27:52 +03:00
										 |  |  | 						skip_dragover = false }, that.resize_settle_timeout) | 
					
						
							| 
									
										
										
										
											2023-08-11 13:21:01 +03:00
										 |  |  | 					that | 
					
						
							|  |  |  | 						.__update_grid_size() | 
					
						
							|  |  |  | 						.updateMarkers()  | 
					
						
							|  |  |  | 					dragged.scrollIntoView({ | 
					
						
							|  |  |  | 						behavior: 'smooth', | 
					
						
							|  |  |  | 						block: 'nearest', | 
					
						
							|  |  |  | 					}) } }) | 
					
						
							|  |  |  | 		// check if we just went out of the edge image...
 | 
					
						
							|  |  |  | 		// NOTE: this handles a special case:
 | 
					
						
							| 
									
										
										
										
											2023-08-11 15:47:42 +03:00
										 |  |  | 		// 			|[A][  B  ][C]   |
 | 
					
						
							|  |  |  | 		// 		when a narrow image (A, C) is at the edge and the 
 | 
					
						
							|  |  |  | 		// 		adjacent image is wide (b). 
 | 
					
						
							|  |  |  | 		// 		dragging the narrow image over the wide places it at 
 | 
					
						
							|  |  |  | 		// 		the other side of the wide image but the cursor is now 
 | 
					
						
							|  |  |  | 		// 		over the wide image so to drag back we either need to 
 | 
					
						
							|  |  |  | 		// 		drag out of it and drag over again (not intuitive) or 
 | 
					
						
							|  |  |  | 		// 		simply drag over the oppoiste edge of the wide image 
 | 
					
						
							|  |  |  | 		// 		(dragleave handler)
 | 
					
						
							| 
									
										
										
										
											2023-08-11 13:28:12 +03:00
										 |  |  | 		// NOTE: we are not implementing the whole drag process here 
 | 
					
						
							|  |  |  | 		// 		because dragging up/down here is far more complicated 
 | 
					
						
							|  |  |  | 		// 		than when doing it in dragover...
 | 
					
						
							| 
									
										
										
										
											2023-08-18 18:03:22 +03:00
										 |  |  | 		// XXX might be a good idea when dragged is null (dragging in files)
 | 
					
						
							|  |  |  | 		// 		to place a placeholder between images instead of styling
 | 
					
						
							|  |  |  | 		// 		the image below...
 | 
					
						
							| 
									
										
										
										
											2023-08-11 13:21:01 +03:00
										 |  |  | 		this.dom | 
					
						
							|  |  |  | 			.addEventListener('dragleave', function(evt){ | 
					
						
							| 
									
										
										
										
											2023-08-30 02:49:03 +03:00
										 |  |  | 				// cleanup on drag out...
 | 
					
						
							|  |  |  | 				// XXX this does not work sometimes...
 | 
					
						
							|  |  |  | 				if(evt.target === that.dom){ | 
					
						
							|  |  |  | 					that.dom.classList.remove('dragged-over') | 
					
						
							|  |  |  | 					dragged_over | 
					
						
							|  |  |  | 						&& dragged_over.classList.remove('dragged-over') } | 
					
						
							|  |  |  | 				// check edge...
 | 
					
						
							| 
									
										
										
										
											2023-08-18 18:00:54 +03:00
										 |  |  | 				if(!dragged  | 
					
						
							|  |  |  | 						|| skip_dragover){ | 
					
						
							| 
									
										
										
										
											2023-08-11 13:21:01 +03:00
										 |  |  | 					return } | 
					
						
							|  |  |  | 				var target = evt.target | 
					
						
							|  |  |  | 				var images = that.images | 
					
						
							|  |  |  | 				var i = images.indexOf(target) | 
					
						
							|  |  |  | 				if(dragged !== target  | 
					
						
							|  |  |  | 						&& i >= 0){ | 
					
						
							|  |  |  | 					// left edge...
 | 
					
						
							|  |  |  | 					if(evt.offsetX <= 0){ | 
					
						
							| 
									
										
										
										
											2023-08-11 13:28:12 +03:00
										 |  |  | 						var prev = images[i-1] | 
					
						
							|  |  |  | 						// adjacent image is not on the same offsetTop (edge)
 | 
					
						
							| 
									
										
										
										
											2023-08-11 13:21:01 +03:00
										 |  |  | 						if(prev == null  | 
					
						
							|  |  |  | 								|| prev.offsetTop != target.offsetTop){ | 
					
						
							| 
									
										
										
										
											2023-08-18 18:00:54 +03:00
										 |  |  | 							target.efore(dragged)  | 
					
						
							| 
									
										
										
										
											2023-08-11 13:21:01 +03:00
										 |  |  | 							that.updateMarkers() } | 
					
						
							|  |  |  | 					// right edge...
 | 
					
						
							|  |  |  | 					} else { | 
					
						
							| 
									
										
										
										
											2023-08-11 13:28:12 +03:00
										 |  |  | 						var next = images[i+1] | 
					
						
							|  |  |  | 						// adjacent image is not on the same offsetTop (edge)
 | 
					
						
							| 
									
										
										
										
											2023-08-11 13:21:01 +03:00
										 |  |  | 						if(next == null  | 
					
						
							|  |  |  | 								|| next.offsetTop != target.offsetTop){ | 
					
						
							|  |  |  | 							target.after(dragged) | 
					
						
							|  |  |  | 							that.updateMarkers() } } } }) | 
					
						
							| 
									
										
										
										
											2023-07-28 14:47:24 +03:00
										 |  |  | 		this.dom | 
					
						
							|  |  |  | 			.addEventListener('dragover', function(evt){ | 
					
						
							| 
									
										
										
										
											2023-08-07 17:16:24 +03:00
										 |  |  | 				evt.preventDefault() | 
					
						
							|  |  |  | 				evt.stopPropagation() | 
					
						
							| 
									
										
										
										
											2023-08-11 17:33:35 +03:00
										 |  |  | 				evt.dataTransfer.dropEffect = 'copy'  | 
					
						
							|  |  |  | 				var target = evt.target | 
					
						
							|  |  |  | 				var i = that.images.indexOf(target) | 
					
						
							|  |  |  | 				if(!dragged  | 
					
						
							|  |  |  | 						&& i >= 0){ | 
					
						
							| 
									
										
										
										
											2023-08-30 02:49:03 +03:00
										 |  |  | 					// XXX BUG: canceling drag leaves the classes in place...
 | 
					
						
							|  |  |  | 					// 		...handling dragend does not help...
 | 
					
						
							|  |  |  | 					// indicate replacing all...
 | 
					
						
							|  |  |  | 					if(!evt.shiftKey){ | 
					
						
							|  |  |  | 						that.dom.classList.add('dragged-over') | 
					
						
							|  |  |  | 						target.classList.remove('dragged-over')  | 
					
						
							|  |  |  | 					// indicate insertion...
 | 
					
						
							|  |  |  | 					} else { | 
					
						
							|  |  |  | 						that.dom.classList.remove('dragged-over') | 
					
						
							|  |  |  | 						dragged_over | 
					
						
							|  |  |  | 							&& dragged_over.classList.remove('dragged-over')  | 
					
						
							|  |  |  | 						dragged_over = target | 
					
						
							|  |  |  | 						dragged_over.classList.add('dragged-over') } } }, false) | 
					
						
							| 
									
										
										
										
											2023-08-10 03:53:12 +03:00
										 |  |  | 		// drag/drop: files...
 | 
					
						
							| 
									
										
										
										
											2023-08-11 17:33:35 +03:00
										 |  |  | 		// NOTE: if shift is pressed then this will add files to the 
 | 
					
						
							|  |  |  | 		// 		loaded list, otherwise it will replace the list...
 | 
					
						
							| 
									
										
										
										
											2023-08-10 03:53:12 +03:00
										 |  |  | 		// XXX handle serilized data...
 | 
					
						
							| 
									
										
										
										
											2023-07-28 14:47:24 +03:00
										 |  |  | 		this.dom | 
					
						
							|  |  |  | 			.addEventListener('drop', function(evt){ | 
					
						
							|  |  |  | 				evt.preventDefault() | 
					
						
							| 
									
										
										
										
											2023-08-07 17:16:24 +03:00
										 |  |  | 				evt.stopPropagation() | 
					
						
							| 
									
										
										
										
											2023-08-11 17:33:35 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				// non-local drag...
 | 
					
						
							|  |  |  | 				if(!dragged){ | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 					that.showLoading() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-11 17:33:35 +03:00
										 |  |  | 					var expand = (evt.shiftKey && dragged_over) ? | 
					
						
							|  |  |  | 						that.images.indexOf(dragged_over) | 
					
						
							|  |  |  | 						: undefined | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					var files =  | 
					
						
							|  |  |  | 						evt.dataTransfer.items ? | 
					
						
							|  |  |  | 							[...evt.dataTransfer.items] | 
					
						
							|  |  |  | 								.map(function(elem){ | 
					
						
							|  |  |  | 									return elem.kind == 'file' ? | 
					
						
							|  |  |  | 										[elem.getAsFile()] | 
					
						
							|  |  |  | 										: [] }) | 
					
						
							|  |  |  | 								.flat() | 
					
						
							|  |  |  | 							: [...evt.dataTransfer.files] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					Promise.all(files | 
					
						
							|  |  |  | 							.map(function(file){ | 
					
						
							|  |  |  | 								// XXX handle serilized data...
 | 
					
						
							|  |  |  | 								// XXX
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 								// images...
 | 
					
						
							|  |  |  | 								if(file.type.match('image.*')){ | 
					
						
							|  |  |  | 									// XXX TEST...
 | 
					
						
							|  |  |  | 									if(file.path){ | 
					
						
							|  |  |  | 										return { | 
					
						
							|  |  |  | 											url: file.path, | 
					
						
							|  |  |  | 											filename: file.name, | 
					
						
							|  |  |  | 										} | 
					
						
							|  |  |  | 									} else { | 
					
						
							|  |  |  | 										return new Promise(function(resolve, reject){ | 
					
						
							|  |  |  | 											var reader = new FileReader() | 
					
						
							|  |  |  | 											reader.onload = function(f){ | 
					
						
							|  |  |  | 												resolve({ | 
					
						
							|  |  |  | 													url: f.target.result, | 
					
						
							|  |  |  | 													filename: file.name, | 
					
						
							|  |  |  | 													// XXX any other metadata to include???
 | 
					
						
							|  |  |  | 												}) } | 
					
						
							|  |  |  | 											reader.readAsDataURL(file) }) } }  | 
					
						
							|  |  |  | 								// other files...
 | 
					
						
							|  |  |  | 								return [] }) | 
					
						
							|  |  |  | 							.flat()) | 
					
						
							|  |  |  | 						.then( | 
					
						
							|  |  |  | 							function(images){ | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 								that.hideLoading() | 
					
						
							| 
									
										
										
										
											2023-08-11 17:33:35 +03:00
										 |  |  | 								// no images...
 | 
					
						
							|  |  |  | 								if(images.length == 0){ | 
					
						
							|  |  |  | 									return } | 
					
						
							|  |  |  | 								return that.load(images, expand) },  | 
					
						
							|  |  |  | 							function(err){ | 
					
						
							|  |  |  | 								// XXX handle errors...
 | 
					
						
							| 
									
										
										
										
											2023-08-18 23:06:38 +03:00
										 |  |  | 								that.hideLoading() }) } | 
					
						
							| 
									
										
										
										
											2023-08-11 13:21:01 +03:00
										 |  |  | 				dragged | 
					
						
							| 
									
										
										
										
											2023-08-11 17:33:35 +03:00
										 |  |  | 					&& dragged.classList.remove('dragged') | 
					
						
							|  |  |  | 				dragged_over | 
					
						
							|  |  |  | 					&& dragged_over.classList.remove('dragged-over') | 
					
						
							| 
									
										
										
										
											2023-08-30 02:49:03 +03:00
										 |  |  | 				that.dom.classList.remove('dragged-over') | 
					
						
							| 
									
										
										
										
											2023-08-10 04:07:32 +03:00
										 |  |  | 				// XXX if this is used in the promise, move to the point 
 | 
					
						
							|  |  |  | 				// 		after we nned this...
 | 
					
						
							| 
									
										
										
										
											2023-08-11 17:33:35 +03:00
										 |  |  | 				dragged = undefined  | 
					
						
							|  |  |  | 				dragged_over = undefined }, false) | 
					
						
							| 
									
										
										
										
											2023-08-30 02:49:03 +03:00
										 |  |  | 		// drag/drom: cleanup...
 | 
					
						
							|  |  |  | 		// XXX BUG: this does not handle the situation when drag was cancelled 
 | 
					
						
							|  |  |  | 		// 		but while the browser was not focused leaving the class 
 | 
					
						
							|  |  |  | 		// 		on the element...
 | 
					
						
							|  |  |  | 		//		to reproduce:
 | 
					
						
							|  |  |  | 		//			- start drag of files
 | 
					
						
							|  |  |  | 		//			- drag over the gallery
 | 
					
						
							|  |  |  | 		//			- press esc to cancel drag
 | 
					
						
							|  |  |  | 		//		...this will leave the dragged-over classes in place...
 | 
					
						
							|  |  |  | 		var cleanupAfterDrag = function(evt){ | 
					
						
							|  |  |  | 			that.dom.classList.remove('dragged-over') | 
					
						
							|  |  |  | 			dragged_over | 
					
						
							|  |  |  | 				&& dragged_over.classList.remove('dragged-over') } | 
					
						
							|  |  |  | 		//this.dom.addEventListener('dragend', cleanupAfterDrag) 
 | 
					
						
							|  |  |  | 		// XXX HACK-ish...
 | 
					
						
							|  |  |  | 		document.body.addEventListener('dragenter', cleanupAfterDrag)  | 
					
						
							|  |  |  | 		// XXX HACK...
 | 
					
						
							|  |  |  | 		this.dom.addEventListener('mouseover', cleanupAfterDrag)  | 
					
						
							| 
									
										
										
										
											2023-07-20 22:06:51 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-28 21:23:00 +03:00
										 |  |  | 		// XXX
 | 
					
						
							|  |  |  | 		for(var img of this.images){ | 
					
						
							|  |  |  | 			img.setAttribute('draggable', 'true') } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-20 22:06:51 +03:00
										 |  |  | 		// handle resizing...
 | 
					
						
							|  |  |  | 		new ResizeObserver( | 
					
						
							|  |  |  | 			function(elems){ | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 				that.__update_grid_size() }) | 
					
						
							| 
									
										
										
										
											2023-07-20 22:06:51 +03:00
										 |  |  | 			.observe(this.dom) | 
					
						
							|  |  |  | 		 | 
					
						
							|  |  |  | 		return this | 
					
						
							| 
									
										
										
										
											2023-08-02 12:48:47 +03:00
										 |  |  | 			.updateMarkers() | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  |    			.update()  | 
					
						
							|  |  |  | 			.hideLoading() }, | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-18 09:11:58 +03:00
										 |  |  | //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-25 10:28:50 +03:00
										 |  |  | var ContextProto = { | 
					
						
							|  |  |  | 	dom: undefined, | 
					
						
							|  |  |  | 	parent: undefined, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	constructors: {}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	end: function(){ | 
					
						
							|  |  |  | 		return this.parent }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	setup: function(){ | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | var Context = function(name, constructors, extend={}){ | 
					
						
							|  |  |  | 	var obj = this instanceof Context ? | 
					
						
							|  |  |  | 		obj | 
					
						
							|  |  |  | 		: Reflect.construct( | 
					
						
							|  |  |  | 			Function,  | 
					
						
							|  |  |  | 			['return this.setup ? this.setup(...arguments) : this'],  | 
					
						
							|  |  |  | 			Context) | 
					
						
							|  |  |  | 	return obj } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-18 09:11:58 +03:00
										 |  |  | var Toolbar = { | 
					
						
							|  |  |  | 	dom: undefined, | 
					
						
							|  |  |  | 	gallery: undefined, | 
					
						
							|  |  |  | 	cls: 'toolbar', | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 	// time to hold the handle to toggle autohide...
 | 
					
						
							| 
									
										
										
										
											2023-08-18 09:11:58 +03:00
										 |  |  | 	hold: 300, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 	// autohide timeout...
 | 
					
						
							| 
									
										
										
										
											2023-08-18 09:11:58 +03:00
										 |  |  | 	toolbar_autohide: 2000, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX make these generic...
 | 
					
						
							| 
									
										
										
										
											2023-08-22 20:48:36 +03:00
										 |  |  | 	// XXX rename .toggle(..) to something like .toggleExpanded(..)
 | 
					
						
							| 
									
										
										
										
											2023-08-18 09:11:58 +03:00
										 |  |  | 	toggle: function(action='toggle'){ | 
					
						
							|  |  |  | 		var toolbar = this.dom?.classList | 
					
						
							|  |  |  | 		toolbar | 
					
						
							|  |  |  | 			&& (action == 'toggle' ? | 
					
						
							|  |  |  | 					toolbar.toggle('shown') | 
					
						
							|  |  |  | 				: action == 'on' ? | 
					
						
							|  |  |  | 					toolbar.add('shown') | 
					
						
							|  |  |  | 				: toolbar.remove('shown')) | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 	movable: function(action='toggle'){ | 
					
						
							|  |  |  | 		var toolbar = this.dom?.classList | 
					
						
							|  |  |  | 		toolbar | 
					
						
							|  |  |  | 			&& (action == 'toggle' ? | 
					
						
							|  |  |  | 					toolbar.toggle('fixed') | 
					
						
							|  |  |  | 				: action == 'off' ? | 
					
						
							|  |  |  | 					toolbar.add('fixed') | 
					
						
							|  |  |  | 				: toolbar.remove('fixed')) | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-22 20:48:36 +03:00
										 |  |  | 	get shown(){ | 
					
						
							|  |  |  | 		return this.dom.style.display != 'none' }, | 
					
						
							|  |  |  | 	show: function(){ | 
					
						
							|  |  |  | 		// XXX
 | 
					
						
							|  |  |  | 		this.dom.style.display = '' | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 	hide: function(){ | 
					
						
							|  |  |  | 		this.dom.style.display = 'none' | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-18 09:11:58 +03:00
										 |  |  | 	setup: function(dom, gallery){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		this.dom = dom | 
					
						
							|  |  |  | 		this.gallery = gallery | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// toolbar...
 | 
					
						
							|  |  |  | 		var toolbar = this.dom | 
					
						
							|  |  |  | 		var toolbar_moving = false | 
					
						
							|  |  |  | 		// prevent clicks in toolbar from affecting the gallery...
 | 
					
						
							|  |  |  | 		toolbar | 
					
						
							|  |  |  | 			.addEventListener('click', function(evt){ | 
					
						
							|  |  |  | 				evt.stopPropagation() }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// toolbar: collapse: click, hold to make sticky...
 | 
					
						
							|  |  |  | 		var hold_timeout | 
					
						
							|  |  |  | 		var holding_toggle | 
					
						
							|  |  |  | 		var handleInteractionStart = function(evt){ | 
					
						
							|  |  |  | 			holding_toggle = true | 
					
						
							|  |  |  | 			hold_timeout = setTimeout( | 
					
						
							|  |  |  | 				function(){ | 
					
						
							|  |  |  | 					hold_timeout = undefined | 
					
						
							|  |  |  | 					toolbar.classList.toggle('sticky') },  | 
					
						
							|  |  |  | 				that.hold ?? 300) } | 
					
						
							|  |  |  | 		var handleInteractionEnd = function(evt){ | 
					
						
							|  |  |  | 			if(holding_toggle){ | 
					
						
							|  |  |  | 				holding_toggle = false | 
					
						
							|  |  |  | 				evt.preventDefault() | 
					
						
							|  |  |  | 				if(hold_timeout){ | 
					
						
							|  |  |  | 					clearTimeout(hold_timeout) | 
					
						
							|  |  |  | 					hold_timeout = undefined  | 
					
						
							|  |  |  | 					that.toggle() } }} | 
					
						
							|  |  |  | 		var collapse_button = toolbar.querySelector('.collapse') | 
					
						
							|  |  |  | 		collapse_button.addEventListener('touchstart', handleInteractionStart) | 
					
						
							|  |  |  | 		collapse_button.addEventListener('mousedown', handleInteractionStart) | 
					
						
							|  |  |  | 		collapse_button.addEventListener('touchend', handleInteractionEnd)  | 
					
						
							|  |  |  | 		collapse_button.addEventListener('mouseup', handleInteractionEnd)  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// toolbar: autohide...
 | 
					
						
							|  |  |  | 		var hide_timeout | 
					
						
							|  |  |  | 		var toolbarAutoHideTimerStart = function(evt){ | 
					
						
							|  |  |  | 			if(that.autohide == 0  | 
					
						
							|  |  |  | 					|| toolbar.classList.contains('sticky')){ | 
					
						
							|  |  |  | 				return } | 
					
						
							|  |  |  | 			hide_timeout = setTimeout( | 
					
						
							|  |  |  | 				function(){ | 
					
						
							|  |  |  | 					hide_timeout = undefined | 
					
						
							|  |  |  | 					that.toggle('hide') },  | 
					
						
							|  |  |  | 				that.autohide ?? 1000) } | 
					
						
							|  |  |  | 		var toolbarAutoHideCancel = function(evt){ | 
					
						
							|  |  |  | 			hide_timeout | 
					
						
							|  |  |  | 				&& clearTimeout(hide_timeout)  | 
					
						
							|  |  |  | 			hide_timeout = undefined } | 
					
						
							|  |  |  | 		toolbar.addEventListener('mouseout', toolbarAutoHideTimerStart) | 
					
						
							|  |  |  | 		toolbar.addEventListener('touchend', toolbarAutoHideTimerStart) | 
					
						
							|  |  |  | 		toolbar.addEventListener('mouseover', toolbarAutoHideCancel) | 
					
						
							|  |  |  | 		toolbar.addEventListener('touchstart', toolbarAutoHideCancel) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// toolbar: move...
 | 
					
						
							|  |  |  | 		// XXX to drag anywhere on the elem we need to prevent 
 | 
					
						
							|  |  |  | 		// 		clicks while dragging...
 | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 		toolbar.moveable({ | 
					
						
							| 
									
										
										
										
											2023-08-18 09:11:58 +03:00
										 |  |  | 			cls: 'moving', | 
					
						
							|  |  |  | 			handle: '.drag-handle', | 
					
						
							|  |  |  | 			// set bounds...
 | 
					
						
							|  |  |  | 			start: function(elem, data){ | 
					
						
							|  |  |  | 				if(elem.classList.contains('fixed')){ | 
					
						
							|  |  |  | 					return false } | 
					
						
							|  |  |  | 				data.bounds = { | 
					
						
							|  |  |  | 					top: 0, | 
					
						
							|  |  |  | 					left: 0, | 
					
						
							|  |  |  | 					right: that.gallery.dom.offsetWidth - elem.offsetWidth - 20, | 
					
						
							|  |  |  | 					bottom: that.gallery.dom.offsetHeight - elem.offsetHeight - 20, | 
					
						
							|  |  |  | 				} }, | 
					
						
							| 
									
										
										
										
											2023-08-18 13:23:27 +03:00
										 |  |  | 			//keepInView: 'scroll',
 | 
					
						
							|  |  |  | 			keepInView: 'strict', | 
					
						
							| 
									
										
										
										
											2023-08-18 09:11:58 +03:00
										 |  |  | 			move: function(elem, data){ | 
					
						
							| 
									
										
										
										
											2023-08-18 13:23:27 +03:00
										 |  |  | 				data.x != null | 
					
						
							|  |  |  | 					&& elem.style.setProperty('--move-x', data.x + 'px')  | 
					
						
							|  |  |  | 				data.y != null | 
					
						
							|  |  |  | 					&& elem.style.setProperty('--move-y', data.y + 'px') }, }) | 
					
						
							| 
									
										
										
										
											2023-08-18 09:11:58 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 		// keep toolbar in view while scrolling...
 | 
					
						
							|  |  |  | 		// NOTE: top is taken care of by position: sticky...
 | 
					
						
							|  |  |  | 		window.addEventListener('scroll', function(evt){ | 
					
						
							|  |  |  | 			var bb = toolbar.getBoundingClientRect() | 
					
						
							|  |  |  | 			if(window.innerHeight <= bb.bottom){ | 
					
						
							|  |  |  | 				var top = toolbar.offsetTop - bb.top | 
					
						
							|  |  |  | 				var bottom = window.innerHeight + top - toolbar.offsetHeight | 
					
						
							|  |  |  | 				toolbar.style.setProperty('--move-y', bottom + 'px') } }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-18 09:11:58 +03:00
										 |  |  | 		return this }, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
					
						
							| 
									
										
										
										
											2023-08-03 15:20:42 +03:00
										 |  |  | // This is a prototype for all modal views...
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | var Overlay = { | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 	dom: undefined, | 
					
						
							|  |  |  | 	gallery: undefined, | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 	cls: 'overlay', | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	get url(){ | 
					
						
							|  |  |  | 		return this.dom.querySelector('img').src }, | 
					
						
							|  |  |  | 	set url(url){ | 
					
						
							|  |  |  | 		this.dom.querySelector('img').src = url | 
					
						
							|  |  |  | 			// remove preview dir...
 | 
					
						
							|  |  |  | 			.replace(/\/[0-9]+px\//, '/')  | 
					
						
							|  |  |  | 		// cache...
 | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 		this.cache | 
					
						
							|  |  |  | 			&& this.cache_count != 0 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 			&& this.cache() }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-15 05:49:08 +03:00
										 |  |  | 	__clicks_canceled: false, | 
					
						
							|  |  |  | 	__clicks_canceled_timeout: 500, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 	get shown(){ | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 		return this.gallery.dom.classList.contains(this.cls) }, | 
					
						
							|  |  |  | 	show: function(){ | 
					
						
							| 
									
										
										
										
											2023-08-15 05:49:08 +03:00
										 |  |  | 		var that = this | 
					
						
							|  |  |  | 		this.__clicks_canceled = true | 
					
						
							|  |  |  | 		setTimeout( | 
					
						
							|  |  |  | 			function(){ | 
					
						
							|  |  |  | 				that.__clicks_canceled = false },  | 
					
						
							|  |  |  | 			this.__clicks_canceled_timeout ?? 200) | 
					
						
							| 
									
										
										
										
											2023-07-21 14:40:30 +03:00
										 |  |  | 		this.update() | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 		this.gallery.dom.classList.add(this.cls) | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		return this }, | 
					
						
							|  |  |  | 	hide: function(){ | 
					
						
							| 
									
										
										
										
											2023-07-28 21:23:00 +03:00
										 |  |  | 		this.gallery.exit_fullscreen_on_lightbox_close | 
					
						
							|  |  |  | 			&& document.fullscreenElement | 
					
						
							|  |  |  | 			&& document.exitFullscreen() | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 		this.gallery.dom.classList.remove(this.cls) | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		return this }, | 
					
						
							|  |  |  | 	toggle: function(){ | 
					
						
							|  |  |  | 		return this.shown ? | 
					
						
							|  |  |  | 			this.hide() | 
					
						
							|  |  |  | 			: this.show() }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 	cache: null, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-21 14:40:30 +03:00
										 |  |  | 	update: function(){ | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 		this.url =  | 
					
						
							|  |  |  | 			(this.gallery.current | 
					
						
							|  |  |  | 				?? this.gallery.next().current | 
					
						
							|  |  |  | 				?? {}).src | 
					
						
							| 
									
										
										
										
											2023-07-25 21:57:18 +03:00
										 |  |  | 		var caption =  | 
					
						
							| 
									
										
										
										
											2023-07-21 14:40:30 +03:00
										 |  |  | 			(this.gallery.current | 
					
						
							|  |  |  | 					?? this.gallery.next().current | 
					
						
							|  |  |  | 					?? {}) | 
					
						
							|  |  |  | 				.getAttribute('caption')  | 
					
						
							| 
									
										
										
										
											2023-07-25 21:57:18 +03:00
										 |  |  | 					?? '' | 
					
						
							|  |  |  | 		var index = (this.gallery.index+1) +'/'+ this.gallery.length | 
					
						
							|  |  |  | 		// set caption...
 | 
					
						
							|  |  |  | 		// XXX should these be separate elements???
 | 
					
						
							|  |  |  | 		this.dom.setAttribute('caption', | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 			(this.caption_format ?? '${CAPTION}') | 
					
						
							| 
									
										
										
										
											2023-07-25 21:57:18 +03:00
										 |  |  | 				.replace(/\${CAPTION}/, caption) | 
					
						
							|  |  |  | 				.replace(/\${INDEX}/, index)) | 
					
						
							| 
									
										
										
										
											2023-07-21 14:40:30 +03:00
										 |  |  | 		// set selection...
 | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 		this.gallery.current.classList.contains('marked') ? | 
					
						
							|  |  |  | 			this.dom.classList.add('marked') | 
					
						
							|  |  |  | 			: this.dom.classList.remove('marked') | 
					
						
							| 
									
										
										
										
											2023-07-21 14:40:30 +03:00
										 |  |  | 		return this }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 	setup: function(dom, gallery){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		this.dom = dom | 
					
						
							|  |  |  | 		this.gallery = gallery | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// controls...
 | 
					
						
							|  |  |  | 		this.dom.querySelector('.close') | 
					
						
							|  |  |  | 			?.addEventListener('click', function(evt){ | 
					
						
							|  |  |  | 				evt.stopPropagation() | 
					
						
							| 
									
										
										
										
											2023-08-15 05:49:08 +03:00
										 |  |  | 				if(that.__clicks_canceled){ | 
					
						
							|  |  |  | 					return } | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 				that.hide() }) | 
					
						
							|  |  |  | 		this.dom.querySelector('.fullscreen') | 
					
						
							|  |  |  | 			?.addEventListener('click', function(evt){ | 
					
						
							|  |  |  | 				evt.stopPropagation() | 
					
						
							| 
									
										
										
										
											2023-08-15 05:49:08 +03:00
										 |  |  | 				if(that.__clicks_canceled){ | 
					
						
							|  |  |  | 					return } | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 				document.fullscreenElement ? | 
					
						
							|  |  |  | 					document.exitFullscreen() | 
					
						
							|  |  |  | 					: that.dom.requestFullscreen() }) | 
					
						
							|  |  |  | 		this.dom.querySelector('.info') | 
					
						
							|  |  |  | 			?.addEventListener('click', function(evt){ | 
					
						
							|  |  |  | 				evt.stopPropagation() | 
					
						
							| 
									
										
										
										
											2023-08-15 05:49:08 +03:00
										 |  |  | 				if(that.__clicks_canceled){ | 
					
						
							|  |  |  | 					return } | 
					
						
							| 
									
										
										
										
											2023-08-22 20:48:36 +03:00
										 |  |  | 				that.gallery.details.show() }) | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 		this.dom.querySelector('.prev') | 
					
						
							|  |  |  | 			?.addEventListener('click', function(evt){ | 
					
						
							|  |  |  | 				evt.stopPropagation() | 
					
						
							| 
									
										
										
										
											2023-08-15 05:49:08 +03:00
										 |  |  | 				if(that.__clicks_canceled){ | 
					
						
							|  |  |  | 					return } | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 				that.gallery.prev() }) | 
					
						
							|  |  |  | 		this.dom.querySelector('.next') | 
					
						
							|  |  |  | 			?.addEventListener('click', function(evt){ | 
					
						
							|  |  |  | 				evt.stopPropagation() | 
					
						
							| 
									
										
										
										
											2023-08-15 05:49:08 +03:00
										 |  |  | 				if(that.__clicks_canceled){ | 
					
						
							|  |  |  | 					return } | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 				that.gallery.next() }) | 
					
						
							|  |  |  | 		// stop body from scrolling...
 | 
					
						
							|  |  |  | 		var stop = function(evt){ | 
					
						
							|  |  |  | 			evt.stopPropagation() | 
					
						
							|  |  |  | 			evt.preventDefault() | 
					
						
							|  |  |  | 			return false } | 
					
						
							|  |  |  | 		this.dom.addEventListener('touchmove', stop) | 
					
						
							|  |  |  | 		this.dom.addEventListener('mousewheel', stop) | 
					
						
							|  |  |  | 		this.dom.addEventListener('wheel', stop) | 
					
						
							|  |  |  | 		this.dom.addEventListener('scroll', stop) | 
					
						
							|  |  |  | 		// drag...
 | 
					
						
							|  |  |  | 		this.dom | 
					
						
							|  |  |  | 			.addEventListener('dragover', function(evt){ | 
					
						
							|  |  |  | 				that.gallery.dom.scrollIntoView({ | 
					
						
							|  |  |  | 					behavior: 'smooth', | 
					
						
							|  |  |  | 					block: 'nearest', | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 				that.hide() }) | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		return this }, | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Lightbox = { | 
					
						
							|  |  |  | 	__proto__: Overlay, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cls: 'lightboxed', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	caption_format: '${INDEX} ${CAPTION}', | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-15 05:49:08 +03:00
										 |  |  | 	navigation_deadzone: 0.3, | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 	caption_hysteresis: 10, | 
					
						
							|  |  |  | 	cache_count: 1, | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	__cache: undefined, | 
					
						
							|  |  |  | 	cache: function(){ | 
					
						
							|  |  |  | 		var cache = [] | 
					
						
							|  |  |  | 		var _cache = this.__cache = [] | 
					
						
							|  |  |  | 		var cur = this.gallery.current | 
					
						
							|  |  |  | 		var images = [...this.gallery.dom.querySelectorAll('img')].slice(1) | 
					
						
							|  |  |  | 		var i = images.indexOf(cur) | 
					
						
							|  |  |  | 		var c = this.cache_count ?? 2 | 
					
						
							|  |  |  | 		for(var j=i+1; j<=i+c; j++){ | 
					
						
							|  |  |  | 			cache.push(j >= images.length ?  | 
					
						
							|  |  |  | 				j % images.length  | 
					
						
							|  |  |  | 				: j) } | 
					
						
							|  |  |  | 		for(var j=i-1; j>=i-c; j--){ | 
					
						
							|  |  |  | 			cache.unshift(j < 0 ? | 
					
						
							|  |  |  | 				images.length+j | 
					
						
							|  |  |  | 				: j) } | 
					
						
							|  |  |  | 		for(i of cache){ | 
					
						
							|  |  |  | 			var img = document.createElement('img') | 
					
						
							|  |  |  | 			img.src = images[i].src | 
					
						
							|  |  |  | 				.replace(/\/[0-9]+px\//, '/') | 
					
						
							|  |  |  | 			_cache.push(img) }  | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 	 | 
					
						
							|  |  |  | 	setup: function(dom, gallery){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		Overlay.setup.call(this, ...arguments) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// click zones...
 | 
					
						
							| 
									
										
										
										
											2023-08-15 05:49:08 +03:00
										 |  |  | 		var dblclick_canceled = false | 
					
						
							|  |  |  | 		var deadzone = this.navigation_deadzone ?? 0.3 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		this.dom | 
					
						
							|  |  |  | 			.addEventListener('click', function(evt){ | 
					
						
							| 
									
										
										
										
											2023-07-23 06:46:14 +03:00
										 |  |  | 				evt.stopPropagation() | 
					
						
							| 
									
										
										
										
											2023-08-15 05:49:08 +03:00
										 |  |  | 				if(that.__clicks_canceled){ | 
					
						
							|  |  |  | 					return } | 
					
						
							|  |  |  | 				var d = that.dom.offsetWidth * deadzone | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 				// click left/right side of view...
 | 
					
						
							|  |  |  | 				// NOTE: this is vewport-relative...
 | 
					
						
							| 
									
										
										
										
											2023-08-15 05:49:08 +03:00
										 |  |  | 				dblclick_canceled = false | 
					
						
							|  |  |  | 				evt.clientX < that.dom.offsetWidth / 2 - d/2 | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 					&& that.gallery.prev() | 
					
						
							| 
									
										
										
										
											2023-08-15 05:49:08 +03:00
										 |  |  | 					&& (dblclick_canceled = true) | 
					
						
							|  |  |  | 				evt.clientX > that.dom.offsetWidth / 2 + d/2 | 
					
						
							|  |  |  | 					&& that.gallery.next()  | 
					
						
							|  |  |  | 					&& (dblclick_canceled = true) }) | 
					
						
							|  |  |  | 		this.dom | 
					
						
							|  |  |  | 			.addEventListener('dblclick', function(evt){ | 
					
						
							|  |  |  | 				evt.stopPropagation() | 
					
						
							|  |  |  | 				dblclick_canceled | 
					
						
							|  |  |  | 					|| that.__clicks_canceled | 
					
						
							|  |  |  | 					|| that.hide() }) | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | 		// hover zones...
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		var hysteresis = this.caption_hysteresis ?? 10 | 
					
						
							|  |  |  | 		this.dom | 
					
						
							|  |  |  | 			.addEventListener('mousemove', function(evt){ | 
					
						
							| 
									
										
										
										
											2023-08-15 05:49:08 +03:00
										 |  |  | 				var d = that.dom.offsetWidth * deadzone | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 				// indicate action...
 | 
					
						
							| 
									
										
										
										
											2023-08-15 05:49:08 +03:00
										 |  |  | 				if(evt.clientX < that.dom.offsetWidth / 2 - d/2){ | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 					that.dom.classList.contains('clickable') | 
					
						
							|  |  |  | 						|| that.dom.classList.add('clickable') | 
					
						
							| 
									
										
										
										
											2023-08-15 05:49:08 +03:00
										 |  |  | 				} else if( evt.clientX > that.dom.offsetWidth / 2 + d/2){ | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 					that.dom.classList.contains('clickable') | 
					
						
							|  |  |  | 						|| that.dom.classList.add('clickable') | 
					
						
							|  |  |  | 				} else { | 
					
						
							|  |  |  | 					that.dom.classList.contains('clickable') | 
					
						
							|  |  |  | 						&& that.dom.classList.remove('clickable') } | 
					
						
							|  |  |  | 				// show/hide caption...
 | 
					
						
							|  |  |  | 				// hysteresis:
 | 
					
						
							|  |  |  | 				//		     +---+-- off
 | 
					
						
							|  |  |  | 				//		     |	 |
 | 
					
						
							|  |  |  | 				//		     v	 ^
 | 
					
						
							|  |  |  | 				//		     |	 |
 | 
					
						
							|  |  |  | 				// 		on  -+---+
 | 
					
						
							|  |  |  | 				evt.clientY > that.dom.offsetHeight / 2 + hysteresis | 
					
						
							|  |  |  | 					&& that.dom.classList.add('show-caption') | 
					
						
							|  |  |  | 				evt.clientY < that.dom.offsetHeight / 2 - hysteresis | 
					
						
							|  |  |  | 					&& that.dom.classList.remove('show-caption') }) | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-03 14:10:59 +03:00
										 |  |  | //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var Details = { | 
					
						
							|  |  |  | 	__proto__: Overlay, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cls: 'detailed', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var setupGallery = function(gallery){ | 
					
						
							|  |  |  | 	return {__proto__: Gallery} | 
					
						
							|  |  |  | 		.setup(gallery) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var setup = function(){ | 
					
						
							|  |  |  | 	var galleries = document.body.querySelectorAll('.gallery') | 
					
						
							|  |  |  | 	for(var gallery of galleries){ | 
					
						
							| 
									
										
										
										
											2023-07-28 23:31:57 +03:00
										 |  |  | 		// XXX this is wrong -- handle multiple galleries...
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		window.gallery = setupGallery(gallery) }  | 
					
						
							|  |  |  | 	// keyboard...
 | 
					
						
							|  |  |  | 	document.addEventListener('keydown', function(evt){ | 
					
						
							| 
									
										
										
										
											2023-08-18 17:48:53 +03:00
										 |  |  | 		if(window.gallery.loading){ | 
					
						
							|  |  |  | 			return } | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		var key = evt.key | 
					
						
							|  |  |  | 		if(key in keyboard){ | 
					
						
							| 
									
										
										
										
											2023-07-20 22:06:51 +03:00
										 |  |  | 			keyboard[key](evt) } }) } | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 	 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | // vim:set ts=4 sw=4 :
 |