mirror of
				https://github.com/flynx/ImageGrid.git
				synced 2025-10-31 19:30:07 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			620 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			HTML
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			620 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			HTML
		
	
	
		
			Executable File
		
	
	
	
	
| <html>
 | |
| <head>
 | |
| <title>ImageGrid.Viewer</title>
 | |
| 
 | |
| 
 | |
| <style>
 | |
| 
 | |
| .viewer {
 | |
| 	position: relative;
 | |
| 	width: 800px;
 | |
| 	height: 600px;
 | |
| 	overflow: hidden;
 | |
| 
 | |
| 	border: solid blue 1px;
 | |
| }
 | |
| 
 | |
| 
 | |
| .ribbon-set {
 | |
| 	position: absolute;
 | |
| }
 | |
| .ribbon-set:empty:after {
 | |
| 	display: block;
 | |
| 	top: 0px;
 | |
| 	left: 0px;
 | |
| 	width: 100%;
 | |
| 	height: 100%;
 | |
| 	content: "Empty";
 | |
| 	text-align: center;
 | |
| }
 | |
| 
 | |
| 
 | |
| .ribbon {
 | |
| 	position: relative;
 | |
| 	display: block;
 | |
| 	height: auto;
 | |
| 	min-width: 0px;
 | |
| 	overflow: visible;
 | |
| 	white-space: nowrap;
 | |
| 	font-size: 0;
 | |
| 
 | |
| 	margin-top: 20px;
 | |
| 	margin-bottom: 20px;
 | |
| }
 | |
| .ribbon:empty {
 | |
| 	display: none;
 | |
| }
 | |
| .ribbon:first-child {
 | |
| 	margin-top: 0px;
 | |
| }
 | |
| .ribbon:last-child {
 | |
| 	margin-bottom: 0px;
 | |
| }
 | |
| 
 | |
| .image {
 | |
| 	position: relative;
 | |
| 	display: inline-block;
 | |
| 	vertical-align: middle;
 | |
| 	text-align;left;
 | |
| 	width: 300px;
 | |
| 	height: 300px;
 | |
| 	font-size: 12pt;
 | |
| 	overflow: hidden;
 | |
| 
 | |
| 	background: black;
 | |
| 	box-sizing: border-box;
 | |
| 	border: solid gray 1px;
 | |
| 	color: white;
 | |
| }
 | |
| .current.image {
 | |
| 	background: red;
 | |
| }
 | |
| 
 | |
| /* dot mark... */
 | |
| .marked.image:after {
 | |
| 	display: block;
 | |
| 	position: absolute;
 | |
| 	content: "";
 | |
| 	font-size: 0pt;
 | |
| 	border: none;
 | |
| 
 | |
| 	width: 15px;
 | |
| 	height: 15px;
 | |
| 
 | |
| 	bottom: 5px;
 | |
| 	right: 5px;
 | |
| 
 | |
| 	border-radius: 50%;
 | |
| 	background: blue;
 | |
| }
 | |
| 
 | |
| /* corner mark... (a-la bookmarks in PortableMag) */
 | |
| /*
 | |
| .marked.image:after {
 | |
| 	display: block;
 | |
| 	position: absolute;
 | |
| 	content: "";
 | |
| 	font-size: 0pt;
 | |
| 	border: none;
 | |
| 
 | |
| 	width: 30px;
 | |
| 	height: 30px;
 | |
| 
 | |
| 	top: -15px;
 | |
| 	right: -15px;
 | |
| 
 | |
| 	background: blue;
 | |
| 
 | |
| 	-webkit-transform: rotate(45deg);
 | |
| 	-moz-transform: rotate(45deg);
 | |
| 	-o-transform: rotate(45deg);
 | |
| 	-ms-transform: rotate(45deg);
 | |
| 	transform: rotate(45deg);
 | |
| }
 | |
| */
 | |
| 
 | |
| 
 | |
| .marked-only.viewer:after {
 | |
| 	display: block;
 | |
| 	position: absolute;
 | |
| 	content: "Showing marked images only";
 | |
| 	font-size: 14pt;
 | |
| 	border: none;
 | |
| 	color: blue;
 | |
| 	width: auto;
 | |
| 	height: auto;
 | |
| 
 | |
| 	top: 10px;
 | |
| 	right: 10px;
 | |
| 
 | |
| }
 | |
| .marked-only .image:not(.marked) {
 | |
| 	display: none;
 | |
| }
 | |
| .marked-only .marked.image:after {
 | |
| 	display: none;
 | |
| }
 | |
| 
 | |
| 
 | |
| </style>
 | |
| 
 | |
| 
 | |
| 
 | |
| <script src="jquery.js"></script>
 | |
| 
 | |
| <script src="lib/jli.js"></script>
 | |
| <script src="lib/keyboard.js"></script>
 | |
| 
 | |
| <script>
 | |
| 
 | |
| /*
 | |
| 
 | |
| Viewer Generation III
 | |
| 
 | |
| Split the API into the following sections:
 | |
| 	- main control actions
 | |
| 		do main domain tasks like image and ribbon manipulation.
 | |
| 	- serialization and deserialization
 | |
| 		load and save data
 | |
| 	- UI
 | |
| 		basic align, animation and modes
 | |
| 
 | |
| */
 | |
| 
 | |
| 
 | |
| var toggleMarkedOnlyView = createCSSClassToggler('.viewer', 'marked-only',
 | |
| 	function(){
 | |
| 		var cur = $('.current.image')
 | |
| 		// current is marked...
 | |
| 		if(cur.hasClass('marked')){
 | |
| 			centerImage(null, 'css')
 | |
| 			return
 | |
| 		} 
 | |
| 		// there is a marked image in this ribbon...
 | |
| 		var target = getImageBefore(cur, null, true)
 | |
| 		if(target.length > 0){
 | |
| 			centerImage(focusImage(target), 'css')
 | |
| 			return
 | |
| 		}
 | |
| 		// get marked image from other ribbons...
 | |
| 		prevRibbon()
 | |
| 		if($('.current.image').hasClass('marked')){
 | |
| 			return
 | |
| 		}
 | |
| 		nextRibbon()
 | |
| 	})
 | |
| 
 | |
| 
 | |
| // XXX add ability to take all marked images and open them in a separate view...
 | |
| 
 | |
| 
 | |
| 
 | |
| var toggleImageMark = createCSSClassToggler('.current.image', 'marked')
 | |
| 
 | |
| // mode can be:
 | |
| //	- 'ribbon'
 | |
| //	- 'all'
 | |
| function removeImageMarks(mode){
 | |
| 	// remove marks from current ribbon (default)...
 | |
| 	if(mode == 'ribbon' || mode == null){
 | |
| 		return $('.current.image')
 | |
| 			.closest('.ribbon')
 | |
| 				.find('.marked')
 | |
| 					.removeClass('marked')
 | |
| 
 | |
| 	// remove all marks...
 | |
| 	} else if(mode == 'all'){
 | |
| 		return $('.marked')
 | |
| 			.removeClass('marked')
 | |
| 	} 
 | |
| }
 | |
| 
 | |
| function markAll(mode){
 | |
| 	// remove marks from current ribbon (default)...
 | |
| 	if(mode == 'ribbon' || mode == null){
 | |
| 		return $('.current.image')
 | |
| 			.closest('.ribbon')
 | |
| 				.find('.image:not(.marked)')
 | |
| 					.addClass('marked')
 | |
| 
 | |
| 	// remove all marks...
 | |
| 	} else if(mode == 'all'){
 | |
| 		return $('.image:not(.marked)').addClass('marked')
 | |
| 	}
 | |
| }
 | |
| 
 | |
| function invertImageMarks(){
 | |
| 	return $('.current.image')
 | |
| 		.closest('.ribbon')
 | |
| 			.find('.image')
 | |
| 				.toggleClass('marked')
 | |
| }
 | |
| 
 | |
| // this will toggle marks in the current continuous section of marked 
 | |
| // or unmarked images...
 | |
| function toggleImageMarkBlock(image){
 | |
| 	if(image == null){
 | |
| 		image = $('.current.image')
 | |
| 	}
 | |
| 	// we need to invert this...
 | |
| 	var state = toggleImageMark()
 | |
| 	var _convert = function(){
 | |
| 		if(toggleImageMark(this, '?') == state){
 | |
| 			return false
 | |
| 		}
 | |
| 		toggleImageMark(this, state)
 | |
| 	}
 | |
| 	image.nextAll('.image').each(_convert)
 | |
| 	image.prevAll('.image').each(_convert)
 | |
| 	return state
 | |
| }
 | |
| 
 | |
| function nextMarkedImage(){
 | |
| 	// XXX
 | |
| }
 | |
| function prevMarkedImage(){
 | |
| 	// XXX
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| // XXX should we use the createCSSClassToggler for this?
 | |
| // XXX revise: does extra stuff...
 | |
| function toggleImageProportions(mode){
 | |
| 	var image = $('.image')
 | |
| 	var h = image.outerHeight(true)
 | |
| 	var w = image.outerWidth(true)
 | |
| 
 | |
| 	if(mode == '?'){
 | |
| 		return h != w ? 'viewer' : 'square'
 | |
| 
 | |
| 	// square...
 | |
| 	} else if(h != w || mode == 'square'){
 | |
| 		var size = Math.min(w, h)
 | |
| 		image.css({
 | |
| 			width: size,
 | |
| 			height: size
 | |
| 		})
 | |
| 		centerImage(null, 'css')
 | |
| 		return 'square'
 | |
| 
 | |
| 	// viewer size...
 | |
| 	} else {
 | |
| 		var viewer = $('.viewer')
 | |
| 		var W = viewer.innerWidth()
 | |
| 		var H = viewer.innerHeight()
 | |
| 
 | |
| 		if(W > H){
 | |
| 			image.css('width', W * h/H)
 | |
| 		} else {
 | |
| 			image.css('height', H * w/W)
 | |
| 		}
 | |
| 		centerImage(null, 'css')
 | |
| 		return 'viewer'
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| // NOTE: to avoid state sync problems this should clone an image if 
 | |
| //		one is available...
 | |
| function createImage(n){
 | |
| 	if(n == null){
 | |
| 		if(window._n == null){
 | |
| 			window._n = 0
 | |
| 		}
 | |
| 		n = _n
 | |
| 		_n += 1
 | |
| 	}
 | |
| 	var img = $('.image')
 | |
| 	if(img.length > 0){
 | |
| 		return img.first().clone()
 | |
| 					.attr({
 | |
| 						'order': n,
 | |
| 						// need to strip extra classes...
 | |
| 						'class': 'image'
 | |
| 					})
 | |
| 	} else {
 | |
| 		return $('<div order="'+n+'" class="image"/>')
 | |
| 	}
 | |
| }
 | |
| function createRibbon(){
 | |
| 	return $('<div class="ribbon"/>')
 | |
| }
 | |
| 
 | |
| 
 | |
| // NOTE: if this returns null, it means that the element is smallest in 
 | |
| //		target ribbon -- first position.
 | |
| function getImageBefore(image, ribbon, visible_only){
 | |
| 	image = $(image)
 | |
| 	if(ribbon == null){
 | |
| 		ribbon = image.closest('.ribbon')
 | |
| 	}
 | |
| 	// pre marked-only mode...
 | |
| 	//var images = $(ribbon).find('.image')
 | |
| 	if(visible_only){
 | |
| 		var images = $(ribbon).find('.image').filter(':visible')
 | |
| 	} else {
 | |
| 		var images = $(ribbon).find('.image')
 | |
| 	}
 | |
| 	var order = image.attr('order')
 | |
| 	var prev = null
 | |
| 
 | |
| 	images.each(function(){
 | |
| 		if(order < $(this).attr('order')){
 | |
| 			return false
 | |
| 		}
 | |
| 		prev = this
 | |
| 	})
 | |
| 
 | |
| 	return $(prev)
 | |
| }
 | |
| 
 | |
| 
 | |
| // basic navigation actions...
 | |
| function nextImage(){
 | |
| 	return centerImage(
 | |
| 		focusImage(
 | |
| 			// pre marked-only mode...
 | |
| 			//$('.current.image').next('.image')))
 | |
| 			$('.current.image').next('.image:visible')))
 | |
| }
 | |
| function prevImage(){
 | |
| 	return centerImage(
 | |
| 		focusImage(
 | |
| 			// pre marked-only mode...
 | |
| 			//$('.current.image').prev('.image')))
 | |
| 			$('.current.image').prev('.image:visible')))
 | |
| }
 | |
| function firstImage(){
 | |
| 	return centerImage(
 | |
| 		focusImage(
 | |
| 			// pre marked-only mode...
 | |
| 			//$('.current.image').closest('.ribbon').find('.image').first()))
 | |
| 			$('.current.image').closest('.ribbon').find('.image').filter(':visible').first()))
 | |
| }
 | |
| function lastImage(){
 | |
| 	return centerImage(
 | |
| 		focusImage(
 | |
| 			// pre marked-only mode...
 | |
| 			//$('.current.image').closest('.ribbon').find('.image').last()))
 | |
| 			$('.current.image').closest('.ribbon').find('.image').filter(':visible').last()))
 | |
| }
 | |
| 
 | |
| // NOTE: if moving is 'next' these will chose the image after the current's order.
 | |
| // NOTE: if an image with the same order is found, moving argument has no effect.
 | |
| function prevRibbon(moving){
 | |
| 	var cur = $('.current.image')
 | |
| 	// pre marked-only mode...
 | |
| 	//var target = getImageBefore(cur, cur.closest('.ribbon').prev('.ribbon'))
 | |
| 	var target = getImageBefore(cur, cur.closest('.ribbon').prev('.ribbon:visible'), true)
 | |
| 	if(moving == 'next' && cur.attr('order') != target.attr('order')){
 | |
| 		var next = target.next('.image')
 | |
| 		target = next.length > 0 ? next : target
 | |
| 	}
 | |
| 	return centerImage(focusImage(target))
 | |
| }
 | |
| function nextRibbon(moving){
 | |
| 	var cur = $('.current.image')
 | |
| 	// pre marked-only mode...
 | |
| 	//var target = getImageBefore(cur, cur.closest('.ribbon').next('.ribbon'))
 | |
| 	var target = getImageBefore(cur, cur.closest('.ribbon').next('.ribbon:visible'), true)
 | |
| 	if(moving == 'next' && cur.attr('order') != target.attr('order')){
 | |
| 		var next = target.next('.image')
 | |
| 		target = next.length > 0 ? next : target
 | |
| 	}
 | |
| 	return centerImage(focusImage(target))
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| function shiftTo(image, ribbon){
 | |
| 	var target = getImageBefore(image, ribbon)
 | |
| 	var cur_ribbon = image.closest('.ribbon')
 | |
| 
 | |
| 	// insert before the first image if nothing is before the target...
 | |
| 	if(target == null){
 | |
| 		image.insertBefore($(ribbon).find('.image').first())
 | |
| 
 | |
| 	} else {
 | |
| 		image.insertAfter(target)
 | |
| 	}
 | |
| 
 | |
| 	// if removing last image out of a ribbon, remove the ribbon....
 | |
| 	if(cur_ribbon.find('.image').length == 0){
 | |
| 		cur_ribbon.remove()
 | |
| 	}
 | |
| 
 | |
| 	return image
 | |
| }
 | |
| 
 | |
| function shiftImage(direction, image, force_create_ribbon){
 | |
| 	if(image == null){
 | |
| 		// XXX need to make this context specific...
 | |
| 		image = $('.current.image')
 | |
| 	} else {
 | |
| 		image = $(image)
 | |
| 	}
 | |
| 	var ribbon = image.closest('.ribbon')[direction]('.ribbon')
 | |
| 
 | |
| 	// need to create a new ribbon...
 | |
| 	if(ribbon.length == 0 || force_create_ribbon == true){
 | |
| 		ribbon = createRibbon()['insert' + (direction == 'prev' 
 | |
| 												? 'Before' 
 | |
| 												: 'After')](image.closest('.ribbon'))
 | |
| 		ribbon.append(image)
 | |
| 	} else {
 | |
| 		shiftTo(image, ribbon)
 | |
| 	}
 | |
| 	return image
 | |
| }
 | |
| 
 | |
| // short-hand methods...
 | |
| function shiftImageUp(image){
 | |
| 	return shiftImage('prev', image)
 | |
| }
 | |
| function shiftImageDown(image){
 | |
| 	return shiftImage('next', image)
 | |
| }
 | |
| function shiftImageUpNewRibbon(image){
 | |
| 	return shiftImage('prev', image, true)
 | |
| }
 | |
| function shiftImageDownNewRibbon(image){
 | |
| 	return shiftImage('next', image, true)
 | |
| }
 | |
| 
 | |
| 
 | |
| // TODO manual image ordering (shiftLeft/shiftRight functions)
 | |
| // XXX
 | |
| 
 | |
| 
 | |
| function focusImage(image){
 | |
| 	image.closest('.viewer').find('.current.image').removeClass('current')
 | |
| 	return image.addClass('current')
 | |
| }
 | |
| 
 | |
| 
 | |
| // Alignment API...
 | |
| // ...tried to make this as brain-dead-stupidly-simple as possible...
 | |
| function relativeVisualPosition(outer, inner){
 | |
| 	outer = $(outer).offset()
 | |
| 	inner = $(inner).offset()
 | |
| 	return {
 | |
| 		top: inner.top - outer.top,
 | |
| 		left: inner.left - outer.left
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // This appears to work well with scaling...
 | |
| // XXX make this more configurable...
 | |
| // XXX this only works for square images...
 | |
| function centerImage(image, mode){
 | |
| 	if(mode == null){
 | |
| 		//mode = 'css'
 | |
| 		mode = 'animate'
 | |
| 	}
 | |
| 	if(image == null || image.length == 0){
 | |
| 		image = $('.current.image')
 | |
| 	}
 | |
| 	var viewer = $('.viewer')
 | |
| 	// XXX should these be "inner"???
 | |
| 	var W = viewer.innerWidth()
 | |
| 	var H = viewer.innerHeight()
 | |
| 
 | |
| 	var ribbons = $('.ribbon-set')
 | |
| 	var scale = getElementScale(ribbons)
 | |
| 	// NOTE: these are scalable, this needs to get normalized...
 | |
| 	var w = image.outerWidth()*scale
 | |
| 	var h = image.outerHeight()*scale
 | |
| 
 | |
| 	var pos = relativeVisualPosition(viewer, image)
 | |
| 
 | |
| 	// zero out top/left if set to anything other than a specific number...
 | |
| 	var t = parseFloat(ribbons.css('top'))
 | |
| 	t = t ? t : 0
 | |
| 	var l = parseFloat(ribbons.css('left'))
 | |
| 	l = l ? l : 0
 | |
| 
 | |
| 	// do the actual work...
 | |
| 	return ribbons[mode]({
 | |
| 		'top': t - pos.top + (H - h)/2,
 | |
| 		'left': l - pos.left + (W - w)/2
 | |
| 	})
 | |
| }
 | |
| 
 | |
| function fitNImages(n){
 | |
| 	var image = $('.current.image')
 | |
| 	var size = image.outerHeight(true)
 | |
| 
 | |
| 	var viewer = $('.viewer')
 | |
| 	var W = viewer.innerWidth()
 | |
| 	var H = viewer.innerHeight()
 | |
| 
 | |
| 	var scale = Math.min(W / (size * n), H / size)
 | |
| 
 | |
| 	// XXX if animating, the next two likes must be animated together...
 | |
| 	setElementScale($('.ribbon-set'), scale)
 | |
| 	centerImage(image, 'css')
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| // NOTE: this is on purpose done relative...
 | |
| function clickHandler(evt){
 | |
| 	var img = $(evt.target).closest('.image')
 | |
| 
 | |
| 	centerImage(
 | |
| 		focusImage(img))
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| // setup...
 | |
| $(function(){
 | |
| 
 | |
| 	// populate the viewer...
 | |
| 	var r = createRibbon()
 | |
| 	var images = []
 | |
| 	for(var i=0; i < 40; i++){
 | |
| 		images.push(createImage().text(i)[0]) 
 | |
| 	}
 | |
| 	r.append($(images))
 | |
| 	var rr = r.clone()
 | |
| 	var rrr = r.clone()
 | |
| 
 | |
| 	$('.ribbon-set')
 | |
| 		.append(r)
 | |
| 		.append(rr)
 | |
| 		.append(rrr)
 | |
| 
 | |
| 	// NOTE: this is global so as to not to add any extra complexity to 
 | |
| 	//		the internal workings...
 | |
| 	$('.viewer').click(clickHandler)
 | |
| 
 | |
| 
 | |
| })
 | |
| 
 | |
| 
 | |
| </script>
 | |
| 
 | |
| </head>
 | |
| <body>
 | |
| 
 | |
| <!-- This is the basic viewer structure...
 | |
| 
 | |
| Unpopulated
 | |
| NOTE: there can be only .ribbon-set element.
 | |
| 
 | |
| <div class="viewer">
 | |
| 	<div class="ribbon-set"></div>
 | |
| </div>
 | |
| 
 | |
| 
 | |
| Populated
 | |
| 
 | |
| <div class="viewer">
 | |
| 	<div class="ribbon-set">
 | |
| 		<div class="ribbon">
 | |
| 			<div class="image"></div>
 | |
| 			<div class="image"></div>
 | |
| 		</div>
 | |
| 		<div class="ribbon">
 | |
| 			<div class="image"></div>
 | |
| 			<div class="current image"></div>
 | |
| 			<div class="image"></div>
 | |
| 			<div class="image"></div>
 | |
| 		</div>
 | |
| 	</div>
 | |
| </div>
 | |
| -->
 | |
| 
 | |
| <div class="viewer">
 | |
| 	<div class="ribbon-set"></div>
 | |
| </div>
 | |
| 
 | |
| 
 | |
| <!-- vim:set ts=4 sw=4 spell : -->
 | |
| </body>
 | |
| </html>
 |