| 
									
										
										
										
											2023-07-17 13:37:10 +03:00
										 |  |  | //=====================================================================
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // TODO:
 | 
					
						
							|  |  |  | // 	- drag-n-drop
 | 
					
						
							|  |  |  | // 	- sort/move
 | 
					
						
							|  |  |  | // 	- crop selection
 | 
					
						
							|  |  |  | // 	- make the gallery into a web component
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //=====================================================================
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var patchFlexRows =  | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | function(elems, prevent_row_expansion=false, last_row_resize=1.5){ | 
					
						
							| 
									
										
										
										
											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...
 | 
					
						
							|  |  |  | 	var W = elems[0].parentElement.clientWidth - 1 | 
					
						
							| 
									
										
										
										
											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){ | 
					
						
							|  |  |  | 			e.style.height = (h * r) + 'px'  | 
					
						
							|  |  |  | 			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 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // 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-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...
 | 
					
						
							|  |  |  | var keyboard = { | 
					
						
							|  |  |  | 	ArrowLeft: function(){ | 
					
						
							|  |  |  | 		gallery.lightbox.shown ? | 
					
						
							|  |  |  | 			gallery.lightbox.prev() | 
					
						
							|  |  |  | 			: gallery.prev() }, | 
					
						
							|  |  |  | 	ArrowRight: function(){ | 
					
						
							|  |  |  | 		gallery.lightbox.shown ? | 
					
						
							|  |  |  | 			gallery.lightbox.next() | 
					
						
							|  |  |  | 			: 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-07-17 13:13:09 +03:00
										 |  |  | 		gallery.lightbox.shown | 
					
						
							|  |  |  | 			|| gallery.up() }, | 
					
						
							|  |  |  | 	ArrowDown: function(evt){ | 
					
						
							| 
									
										
										
										
											2023-07-24 21:04:42 +03:00
										 |  |  | 		gallery.__at_bottom_row  | 
					
						
							|  |  |  | 			|| evt.preventDefault() | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		gallery.lightbox.shown | 
					
						
							|  |  |  | 			|| gallery.down() }, | 
					
						
							|  |  |  | 	Enter: function(){ | 
					
						
							|  |  |  | 		gallery.lightbox.toggle() }, | 
					
						
							| 
									
										
										
										
											2023-07-28 14:47:24 +03:00
										 |  |  | 	Escape: function(evt){ | 
					
						
							| 
									
										
										
										
											2023-07-23 06:46:14 +03:00
										 |  |  | 		gallery.lightbox.shown ? | 
					
						
							|  |  |  | 			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-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){ | 
					
						
							|  |  |  | 			gallery.selectAll() } }, | 
					
						
							|  |  |  | 	'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-07-22 15:33:55 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	__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-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){  | 
					
						
							|  |  |  | 				// XXX not sure if we should remove the preview dir...
 | 
					
						
							|  |  |  | 				return img.src }) }, | 
					
						
							|  |  |  | 				/*/ | 
					
						
							|  |  |  | 				return img.src  | 
					
						
							|  |  |  | 					// remove preview dir...
 | 
					
						
							|  |  |  | 					.replace(/\/[0-9]+px\//, '/') }) }, | 
					
						
							|  |  |  | 				//*/
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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-07-17 13:13:09 +03:00
										 |  |  | 	getRow: function(img, direction='current'){ | 
					
						
							|  |  |  | 		if(['above', 'current', 'below'].includes(img)){ | 
					
						
							|  |  |  | 			direction = img | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 			img = this.current } | 
					
						
							| 
									
										
										
										
											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 ? | 
					
						
							|  |  |  | 					this.getRow(this.images.at(-1)) | 
					
						
							|  |  |  | 				: 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 ? | 
					
						
							|  |  |  | 					this.getRow(this.images[0]) | 
					
						
							|  |  |  | 				: 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-07-20 21:42:17 +03:00
										 |  |  | 			var images = this.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-07-17 13:13:09 +03:00
										 |  |  | 	getImage: function(img, direction='current'){ | 
					
						
							|  |  |  | 		if(['left', 'above', 'current', 'below', 'right'].includes(img)){ | 
					
						
							|  |  |  | 			direction = img | 
					
						
							|  |  |  | 			img = null } | 
					
						
							|  |  |  | 		// current...
 | 
					
						
							|  |  |  | 		if(direction == 'current'){ | 
					
						
							|  |  |  | 			return img  | 
					
						
							|  |  |  | 				?? this.current  | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 				?? this.getRow(img)[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'){ | 
					
						
							|  |  |  | 			var row = this.getRow(direction) | 
					
						
							| 
									
										
										
										
											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 { | 
					
						
							|  |  |  | 			var row = this.getRow(img) | 
					
						
							|  |  |  | 			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 }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// XXX cache image list???
 | 
					
						
							|  |  |  | 	prev: function(){ | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 		var 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] | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 	next: function(){ | 
					
						
							| 
									
										
										
										
											2023-07-20 21:42:17 +03:00
										 |  |  | 		var 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] | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// navigate images visually...
 | 
					
						
							|  |  |  | 	left: function(){ | 
					
						
							|  |  |  | 		var cur = this.current | 
					
						
							|  |  |  | 		var row = this.getRow(cur) | 
					
						
							|  |  |  | 		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 }, | 
					
						
							|  |  |  | 	right: function(){ | 
					
						
							|  |  |  | 		var cur = this.current | 
					
						
							|  |  |  | 		var row = this.getRow(cur) | 
					
						
							|  |  |  | 		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 }, | 
					
						
							|  |  |  | 	up: function(){ | 
					
						
							| 
									
										
										
										
											2023-07-24 21:04:42 +03:00
										 |  |  | 		var img = this.getImage('above') | 
					
						
							|  |  |  | 		img  | 
					
						
							|  |  |  | 			&& (this.current = img) | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		return this }, | 
					
						
							|  |  |  | 	down: function(){ | 
					
						
							| 
									
										
										
										
											2023-07-24 21:04:42 +03:00
										 |  |  | 		var img = this.getImage('below') | 
					
						
							|  |  |  | 		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 }, | 
					
						
							|  |  |  | 	selectAll: function(){ | 
					
						
							|  |  |  | 		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-07-17 13:13:09 +03:00
										 |  |  | 	show: function(){ | 
					
						
							|  |  |  | 		this.lightbox.show() | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-20 22:06:51 +03:00
										 |  |  | 	update: function(){ | 
					
						
							| 
									
										
										
										
											2023-07-28 23:19:39 +03:00
										 |  |  | 		patchFlexRows(this.images,  | 
					
						
							|  |  |  | 			!this.allow_row_expansion,  | 
					
						
							|  |  |  | 			this.last_row_resize ?? 1.2) | 
					
						
							| 
									
										
										
										
											2023-07-20 22:06:51 +03:00
										 |  |  | 		return this }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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>, .. }
 | 
					
						
							|  |  |  | 	//
 | 
					
						
							|  |  |  | 	// XXX do we handle previews here???
 | 
					
						
							|  |  |  | 	load: function(images, index=undefined){ | 
					
						
							|  |  |  | 		images = images instanceof Array ? | 
					
						
							|  |  |  | 			images | 
					
						
							|  |  |  | 			: [images] | 
					
						
							|  |  |  | 		// create images...
 | 
					
						
							|  |  |  | 		var elems = [] | 
					
						
							|  |  |  | 		for(var data of images){ | 
					
						
							|  |  |  | 			if(typeof(data) == 'string'){ | 
					
						
							|  |  |  | 				var [url] = [data] | 
					
						
							|  |  |  | 			} else if(data instanceof Array){ | 
					
						
							|  |  |  | 				var [url, ...data] = data | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				var {url, ...data} = data } | 
					
						
							|  |  |  | 			var elem = document.createElement('img') | 
					
						
							|  |  |  | 			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 | 
					
						
							|  |  |  | 					&& elem.setAttribute(key, value) } | 
					
						
							|  |  |  | 			elems.push(elem) } | 
					
						
							|  |  |  | 		// add to gallery...
 | 
					
						
							|  |  |  | 		if(index == null){ | 
					
						
							|  |  |  | 			this.clear() } | 
					
						
							|  |  |  | 		if(index == null  | 
					
						
							|  |  |  | 				|| this.length > 0){ | 
					
						
							|  |  |  | 			this.dom.querySelector('.images') | 
					
						
							|  |  |  | 				.append(...elems)  | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			var sibling = this.images.at(index) | 
					
						
							|  |  |  | 			index < 0 ? | 
					
						
							|  |  |  | 				sibling.after(...elems) | 
					
						
							|  |  |  | 				: sibling.before(...elems) } | 
					
						
							|  |  |  | 		return this | 
					
						
							|  |  |  | 			.update() }, | 
					
						
							|  |  |  | 	__image_attributes__: [ | 
					
						
							|  |  |  | 		'caption', | 
					
						
							|  |  |  | 	], | 
					
						
							|  |  |  | 	// 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-07-28 23:19:39 +03:00
										 |  |  | 		images =  | 
					
						
							|  |  |  | 			(images == 'all' || images == '*') ? | 
					
						
							|  |  |  | 				this.images | 
					
						
							|  |  |  | 			: images == 'marked' ? | 
					
						
							|  |  |  | 				this.marked | 
					
						
							|  |  |  | 			: !images ? | 
					
						
							|  |  |  | 				this.images | 
					
						
							|  |  |  | 			: images | 
					
						
							|  |  |  | 		return images | 
					
						
							| 
									
										
										
										
											2023-07-28 19:41:44 +03:00
										 |  |  | 			.map(function(img){ | 
					
						
							|  |  |  | 				var res = { url: img.src } | 
					
						
							|  |  |  | 				for(var key of that.__image_attributes__){ | 
					
						
							|  |  |  | 					var value = img.getAttribute(key) | 
					
						
							|  |  |  | 					value  | 
					
						
							|  |  |  | 						&& (res[key] = value) } | 
					
						
							|  |  |  | 				return res }) }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-28 21:23:00 +03:00
										 |  |  | 	remove: function(...images){ | 
					
						
							|  |  |  | 		if(images.includes('all')){ | 
					
						
							|  |  |  | 			return this.clear() } | 
					
						
							|  |  |  | 		// NOTE: we need to remove images from the end so as not to be 
 | 
					
						
							|  |  |  | 		// 		affected by shifed indexes...
 | 
					
						
							|  |  |  | 		images | 
					
						
							|  |  |  | 			.sort() | 
					
						
							|  |  |  | 			.reverse() | 
					
						
							|  |  |  | 		for(var img of images){ | 
					
						
							|  |  |  | 			typeof(img) == 'number' ? | 
					
						
							|  |  |  | 				this.images.at(img)?.remove()  | 
					
						
							|  |  |  | 			: img instanceof Element ? | 
					
						
							|  |  |  | 				(this.images.contains(img)  | 
					
						
							|  |  |  | 					&& img.remove()) | 
					
						
							|  |  |  | 			: null } | 
					
						
							| 
									
										
										
										
											2023-07-28 19:41:44 +03:00
										 |  |  | 		return this | 
					
						
							| 
									
										
										
										
											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-07-17 13:13:09 +03:00
										 |  |  | 	setup: function(dom){ | 
					
						
							|  |  |  | 		var that = this | 
					
						
							|  |  |  | 		this.dom = dom | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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-07-28 14:47:24 +03:00
										 |  |  | 		// drag...
 | 
					
						
							|  |  |  | 		this.dom | 
					
						
							|  |  |  | 			.addEventListener('dragover', function(evt){ | 
					
						
							|  |  |  | 				// XXX
 | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		this.dom | 
					
						
							|  |  |  | 			.addEventListener('drop', function(evt){ | 
					
						
							|  |  |  | 				evt.preventDefault() | 
					
						
							|  |  |  | 				// XXX
 | 
					
						
							|  |  |  | 			}) | 
					
						
							| 
									
										
										
										
											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){ | 
					
						
							|  |  |  | 				that.update() }) | 
					
						
							|  |  |  | 			.observe(this.dom) | 
					
						
							|  |  |  | 		 | 
					
						
							|  |  |  | 		return this | 
					
						
							|  |  |  |    			.update() }, | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // XXX ignore click from blur...
 | 
					
						
							|  |  |  | var Lightbox = { | 
					
						
							|  |  |  | 	dom: undefined, | 
					
						
							|  |  |  | 	gallery: undefined, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-25 21:57:18 +03:00
										 |  |  | 	caption_format: '${INDEX} ${CAPTION}', | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 	navigation_deadzone: 100, | 
					
						
							|  |  |  | 	caption_hysteresis: 10, | 
					
						
							|  |  |  | 	cache_count: 1, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	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...
 | 
					
						
							|  |  |  | 		this.cache_count != 0 | 
					
						
							|  |  |  | 			&& this.cache() }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	get shown(){ | 
					
						
							| 
									
										
										
										
											2023-07-22 15:33:55 +03:00
										 |  |  | 		return this.gallery.dom.classList.contains('lightboxed') }, | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 	show: function(url){ | 
					
						
							|  |  |  | 		this.url = url  | 
					
						
							|  |  |  | 			?? (this.gallery.current | 
					
						
							|  |  |  | 				?? this.gallery.next().current | 
					
						
							|  |  |  | 				?? {}).src | 
					
						
							| 
									
										
										
										
											2023-07-21 14:40:30 +03:00
										 |  |  | 		this.update() | 
					
						
							| 
									
										
										
										
											2023-07-22 15:33:55 +03:00
										 |  |  | 		this.gallery.dom.classList.add('lightboxed') | 
					
						
							| 
									
										
										
										
											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-07-22 15:33:55 +03:00
										 |  |  | 		this.gallery.dom.classList.remove('lightboxed') | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		return this }, | 
					
						
							|  |  |  | 	toggle: function(){ | 
					
						
							|  |  |  | 		return this.shown ? | 
					
						
							|  |  |  | 			this.hide() | 
					
						
							|  |  |  | 			: this.show() }, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-21 14:40:30 +03:00
										 |  |  | 	update: function(){ | 
					
						
							| 
									
										
										
										
											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', | 
					
						
							|  |  |  | 			this.caption_format | 
					
						
							|  |  |  | 				.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-07-17 13:13:09 +03:00
										 |  |  | 	prev: function(){ | 
					
						
							|  |  |  | 		this.gallery.prev().show() | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 	next: function(){ | 
					
						
							|  |  |  | 		this.gallery.next().show() | 
					
						
							|  |  |  | 		return this }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	__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 | 
					
						
							|  |  |  | 		this.dom = dom | 
					
						
							|  |  |  | 		this.gallery = gallery | 
					
						
							|  |  |  | 		// controls...
 | 
					
						
							|  |  |  | 		this.dom.querySelector('.close') | 
					
						
							|  |  |  | 			.addEventListener('click', function(evt){ | 
					
						
							|  |  |  | 				evt.stopPropagation() | 
					
						
							|  |  |  | 				that.hide() }) | 
					
						
							| 
									
										
										
										
											2023-07-27 13:27:25 +03:00
										 |  |  | 		this.dom.querySelector('.fullscreen') | 
					
						
							|  |  |  | 			.addEventListener('click', function(evt){ | 
					
						
							|  |  |  | 				evt.stopPropagation() | 
					
						
							| 
									
										
										
										
											2023-07-27 15:36:56 +03:00
										 |  |  | 				document.fullscreenElement ? | 
					
						
							|  |  |  | 					document.exitFullscreen() | 
					
						
							|  |  |  | 					: that.dom.requestFullscreen() }) | 
					
						
							| 
									
										
										
										
											2023-07-28 14:47:24 +03:00
										 |  |  | 		// 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
										 |  |  | 		// click...
 | 
					
						
							|  |  |  | 		var deadzone = this.navigation_deadzone ?? 100 | 
					
						
							|  |  |  | 		this.dom | 
					
						
							|  |  |  | 			.addEventListener('click', function(evt){ | 
					
						
							| 
									
										
										
										
											2023-07-23 06:46:14 +03:00
										 |  |  | 				evt.stopPropagation() | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 				// click left/right side of view...
 | 
					
						
							|  |  |  | 				// NOTE: this is vewport-relative...
 | 
					
						
							|  |  |  | 				evt.clientX < that.dom.offsetWidth / 2 - deadzone/2 | 
					
						
							|  |  |  | 					&& that.prev() | 
					
						
							|  |  |  | 				evt.clientX > that.dom.offsetWidth / 2 + deadzone/2 | 
					
						
							|  |  |  | 					&& that.next() }) | 
					
						
							| 
									
										
										
										
											2023-07-21 03:16:20 +03:00
										 |  |  | 		// mousemove...
 | 
					
						
							| 
									
										
										
										
											2023-07-17 13:13:09 +03:00
										 |  |  | 		var hysteresis = this.caption_hysteresis ?? 10 | 
					
						
							|  |  |  | 		this.dom | 
					
						
							|  |  |  | 			.addEventListener('mousemove', function(evt){ | 
					
						
							|  |  |  | 				// indicate action...
 | 
					
						
							|  |  |  | 				if(evt.clientX < that.dom.offsetWidth / 2 - deadzone/2){ | 
					
						
							|  |  |  | 					that.dom.classList.contains('clickable') | 
					
						
							|  |  |  | 						|| that.dom.classList.add('clickable') | 
					
						
							|  |  |  | 				} else if( evt.clientX > that.dom.offsetWidth / 2 + deadzone/2){ | 
					
						
							|  |  |  | 					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 }, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //---------------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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){ | 
					
						
							|  |  |  | 		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 :
 |