mirror of
				https://github.com/flynx/ImageGrid.git
				synced 2025-10-31 19:30:07 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			686 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			HTML
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			686 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			HTML
		
	
	
		
			Executable File
		
	
	
	
	
| 
 | |
| <!--
 | |
| TODO:
 | |
| - basic structure
 | |
| 	ribbons					DONE
 | |
| 	images					DONE
 | |
| 	indicators
 | |
| - basic control elements
 | |
| 	touch zones / buttons
 | |
| 		next				DONE
 | |
| 		prev				DONE
 | |
| 		shift up			DONE
 | |
| 		shift down			DONE
 | |
| 		promote				DONE
 | |
| 		demote				DONE
 | |
| 		zoom in				~		need real zooming...
 | |
| 		zoom out			~		need real zooming...
 | |
| 		toggle single image	DONE
 | |
| - image sorting
 | |
| 	- will affect:
 | |
| 		- promote
 | |
| 		- demote
 | |
| 		- shift up
 | |
| 		- shift down
 | |
| 		- ribbon merging
 | |
| - add promote/demote events (to attach structure editors)...
 | |
| - add real images...
 | |
| - make all the code relative to the current selection (multiple instances on a page support)
 | |
| - make this into a jquery plugin...
 | |
| - add dynamic loading and unloading for very large sets...
 | |
| - gesture support...
 | |
| 
 | |
| - first stage refactoring:
 | |
| 	- merge almost identical functions...
 | |
| 
 | |
| - multiple groups to promote/demote
 | |
| 	ways to go:
 | |
| 		- promote/demote and tag
 | |
| 
 | |
| ISSUES:
 | |
| 	- jumping on focus up/down...
 | |
| 	- demoting a first element (a ribbon is created) positions the field incorrectly (see demoteImage() for details)...
 | |
| -->
 | |
| 
 | |
| <script src="jquery.js"></script>
 | |
| 
 | |
| <!-- script src="jquery.wipetouch.js"></script-->
 | |
| 
 | |
| <!-- XXX this does not work on android... --> 
 | |
| <script src="jquery.gestures.js"></script>
 | |
| 
 | |
| <!-- XXX need to figure out how to disable all the bling -->
 | |
| <!-- script src="jquery.mobile.js"></script-->
 | |
| <script>
 | |
| 
 | |
| $(document).ready(function() {
 | |
| 	// current state...
 | |
| 	if($('.current-ribbon').length == 0){
 | |
| 		$('.ribbon').first().addClass('current-ribbon')
 | |
| 	}
 | |
| 	if($('.current-image').length == 0){
 | |
| 		$('.current-ribbon').children('.image').first().addClass('current-image')
 | |
| 	}
 | |
| 
 | |
| 	// setup event handlers...
 | |
| 	$(document)
 | |
| 		.keydown(handleKeys)
 | |
| 	$('.viewer')
 | |
| 		// XXX this is flaky and breaks some of my code...
 | |
| 		/*.wipetouch({
 | |
| 			wipeLeft: nextImage,
 | |
| 			wipeRight: prevImage,
 | |
| 			wipeUp: demoteImage,
 | |
| 			wipeDown: promoteImage,
 | |
| 
 | |
| 			tapToClick: true
 | |
| 		})*/
 | |
| 		// XXX does not work on android...
 | |
| 		.gestures({eventHandler: handleGestures})
 | |
| 		/* XXX jquery.mobile handlers... (with this I'm getting way too much bling)
 | |
| 		.bind('swipeleft', function(e){
 | |
| 			nextImage()
 | |
| 			e.preventDefault()
 | |
| 			return false
 | |
| 		})
 | |
| 		.bind('swiperight', function(e){
 | |
| 			prevImage()
 | |
| 			e.preventDefault()
 | |
| 			return false
 | |
| 		})
 | |
| 		*/
 | |
| 	$(".image").click(handleClick)
 | |
| 
 | |
| 	$('.next-image').click(nextImage)
 | |
| 	$('.prev-image').click(prevImage)
 | |
| 	$('.demote').click(demoteImage)
 | |
| 	$('.promote').click(promoteImage)
 | |
| 	$('.toggle-wide').click(toggleWideView)
 | |
| 	$('.toggle-single').click(toggleRibbonView)
 | |
| 
 | |
| 	// set the default position...
 | |
| 	$('.current-image').click()
 | |
| });
 | |
| 
 | |
| // XXX jquery.gestures handler...
 | |
| function handleGestures(e){
 | |
| 	switch (e){
 | |
| 		case 'N':
 | |
| 			demoteImage()
 | |
| 			break
 | |
| 		case 'S':
 | |
| 			promoteImage()
 | |
| 			break
 | |
| 		case 'E':
 | |
| 			prevImage()
 | |
| 			break
 | |
| 		case 'W':
 | |
| 			nextImage()
 | |
| 			break
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| function handleClick(e) {
 | |
| 
 | |
| 	var cur = $(this)
 | |
| 
 | |
| 	// switch classes...
 | |
| 	cur.parents().siblings().children(".image").removeClass("current-image")
 | |
| 	cur.siblings(".image").removeClass("current-image")
 | |
| 
 | |
| 	cur.siblings().children(".image").removeClass("current-image")
 | |
| 	cur.parents().siblings(".ribbon").removeClass("current-ribbon")
 | |
| 
 | |
| 	cur.addClass("current-image")
 | |
| 	cur.parents(".ribbon").addClass("current-ribbon")
 | |
| 
 | |
| 
 | |
| 	var container = cur.parents('.container')
 | |
| 	var field = cur.parents(".field")
 | |
| 
 | |
| 	var image_offset = cur.offset()
 | |
| 	var field_offset = field.offset()
 | |
| 
 | |
| 	// center the current image...
 | |
| 	field.css({
 | |
| 		left: field_offset.left - image_offset.left + (container.innerWidth() - cur.innerWidth())/2, 
 | |
| 		top: field_offset.top - image_offset.top + (container.innerHeight() - cur.innerHeight())/2 
 | |
| 	})
 | |
| 
 | |
| 
 | |
| 	// XXX do I need this???
 | |
| 	e.preventDefault();
 | |
| }
 | |
| 
 | |
| var keys = {
 | |
| 	toggleHelpKeys: [72],
 | |
| 	toggleRibbonView: [32],
 | |
| 	closeKeys: [27, 88, 67],
 | |
| 
 | |
| 	firstKeys: [36],
 | |
| 	lastKeys: [35],
 | |
| 	previousKeys: [37, 80],
 | |
| 	nextKeys: [39, 78],
 | |
| 	promoteKeys: [40],
 | |
| 	// XXX add del (46) to demote...
 | |
| 	demoteKeys: [38],
 | |
| 
 | |
| 	ignoreKeys: [16, 17, 18],
 | |
| 
 | |
| 	helpShowOnUnknownKey: true
 | |
| }
 | |
| 
 | |
| function handleKeys(event){
 | |
| 	var code = event.keyCode, fn = $.inArray;
 | |
| 	var _ = (fn(code, keys.closeKeys) >= 0) ? function(){}()
 | |
| 		: (fn(code, keys.firstKeys) >= 0) ? firstImage()
 | |
| 		: (fn(code, keys.nextKeys) >= 0) ? nextImage()
 | |
| 		: (fn(code, keys.previousKeys) >= 0) ? prevImage()
 | |
| 		: (fn(code, keys.lastKeys) >= 0) ? lastImage()
 | |
| 		: (fn(code, keys.promoteKeys) >= 0) ? function(){
 | |
| 			if(event.shiftKey){
 | |
| 				if(event.ctrlKey){
 | |
| 					createRibbonBelow()
 | |
| 				}
 | |
| 				promoteImage()
 | |
| 			} else {
 | |
| 				focusBelowRibbon()
 | |
| 			}
 | |
| 		}()
 | |
| 		: (fn(code, keys.demoteKeys) >= 0) ? function(){
 | |
| 			if(event.shiftKey){
 | |
| 				if(event.ctrlKey){
 | |
| 					createRibbonAbove()
 | |
| 				}
 | |
| 				demoteImage()
 | |
| 			} else {
 | |
| 				focusAboveRibbon()
 | |
| 			}
 | |
| 		}()
 | |
| 		: (fn(code, keys.toggleRibbonView) >= 0) ? toggleRibbonView()
 | |
| 		: (fn(code, keys.ignoreKeys) >= 0) ? false
 | |
| 		// XXX
 | |
| 		: (keys.helpShowOnUnknownKey) ? function(){alert(code)}()
 | |
| 		: false;
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| // modes...
 | |
| function showRibbon(){
 | |
| 	$('.single-image-mode').removeClass('single-image-mode')
 | |
| }
 | |
| function showSingle(){
 | |
| 	$('.viewer').not('.single-image-mode').addClass('single-image-mode')
 | |
| }
 | |
| function toggleRibbonView(){
 | |
| 	if($('.single-image-mode').length > 0){
 | |
| 		showRibbon()
 | |
| 	} else {
 | |
| 		showSingle()
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // XXX need to reposition the whole thing correctly...
 | |
| function toggleWideView(){
 | |
| 	if($('.wide-view-mode').length > 0){
 | |
| 		$('.wide-view-mode')
 | |
| 			.removeClass('wide-view-mode')
 | |
| 			.one("webkitTransitionEnd oTransitionEnd msTransitionEnd transitionend", function(){
 | |
| 				$('.current-image').click()
 | |
| 				return true
 | |
| 			});
 | |
| 		
 | |
| 	} else {
 | |
| 		showRibbon()
 | |
| 		//$('.container')
 | |
| 		$('.viewer')
 | |
| 			.not('.wide-view-mode')
 | |
| 				.addClass('wide-view-mode')
 | |
| 				.one("webkitTransitionEnd oTransitionEnd msTransitionEnd transitionend", function(){
 | |
| 					$('.current-image').click()
 | |
| 					return true
 | |
| 				});
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // basic navigation...
 | |
| function firstImage(){
 | |
| 	$('.current-ribbon').children('.image').first().click()
 | |
| }
 | |
| 
 | |
| function prevImage(){
 | |
| 	$('.current-image')
 | |
| 		.prev('.image')
 | |
| 			.click()
 | |
| }
 | |
| 
 | |
| function nextImage(){
 | |
| 	$('.current-image')
 | |
| 		.next('.image')
 | |
| 			.click()
 | |
| }
 | |
| 
 | |
| function lastImage(){
 | |
| 	$('.current-ribbon').children('.image').last().click()
 | |
| }
 | |
| 
 | |
| // XXX select appropriate image...
 | |
| function focusAboveRibbon(){
 | |
| 	$('.current-ribbon').prev('.ribbon').children('.image').first().click()
 | |
| }
 | |
| 
 | |
| // XXX select appropriate image...
 | |
| function focusBelowRibbon(){
 | |
| 	$('.current-ribbon').next('.ribbon').children('.image').first().click()
 | |
| }
 | |
| 
 | |
| 
 | |
| // create ribbon above/below helpers...
 | |
| // XXX NOTE: this will shift the content downwards...
 | |
| function createRibbonAbove(){
 | |
| 	var res = $('<div class="new-ribbon"></div>')
 | |
| 		.insertBefore('.current-ribbon')
 | |
| 		// HACK: without this, the class change below will not animate...
 | |
| 		.show()
 | |
| 		.addClass('ribbon')
 | |
| 		.removeClass('new-ribbon')
 | |
| 	// XXX find a better way to do this...
 | |
| 	$('.field').css({
 | |
| 		top: $('.field').position().top - $('.current-ribbon').outerHeight()
 | |
| 	})
 | |
| 	return res
 | |
| }
 | |
| 
 | |
| function createRibbonBelow(){
 | |
| 	return $('<div class="new-ribbon"></div>')
 | |
| 		.insertAfter('.current-ribbon')
 | |
| 		// HACK: without this, the class change below will not animate...
 | |
| 		.show()
 | |
| 		.addClass('ribbon')
 | |
| 		.removeClass('new-ribbon')
 | |
| }
 | |
| 
 | |
| // Modifiers...
 | |
| 
 | |
| // XXX sort elements correctly...
 | |
| function mergeRibbonsUp(){
 | |
| 	$('.current-ribbon')
 | |
| 		.prev('.ribbon')
 | |
| 			.children()
 | |
| 				.detach()
 | |
| 				.insertAfter('.current-image')
 | |
| 	$('.current-ribbon')
 | |
| 		.prev('.ribbon')
 | |
| 			.slideUp(function(){
 | |
| 				$(this).remove()
 | |
| 				$('.current-image').click()
 | |
| 			})
 | |
| }
 | |
| 
 | |
| // XXX sort elements correctly...
 | |
| function mergeRibbonsDown(){
 | |
| 	$('.current-ribbon')
 | |
| 		.next('.ribbon')
 | |
| 			.children()
 | |
| 				.detach()
 | |
| 				.insertAfter('.current-image')
 | |
| 	$('.current-ribbon')
 | |
| 		.next('.ribbon')
 | |
| 			.slideUp(function(){
 | |
| 				$(this).remove()
 | |
| 				$('.current-image').click()
 | |
| 			})
 | |
| }
 | |
| 
 | |
| // XXX sort elements correctly...
 | |
| // XXX do animations...
 | |
| function promoteImage(){
 | |
| 	if($('.current-ribbon').next('.ribbon').length == 0){
 | |
| 		createRibbonBelow()
 | |
| 	}
 | |
| 	// XXX sort elements correctly...
 | |
| 	if($('.current-ribbon').children('.image').length == 1){
 | |
| 		// XXX this adds image to the head while the below portion adds it to the tail...
 | |
| 		mergeRibbonsDown()
 | |
| 	} else {
 | |
| 		img = $('.current-image')
 | |
| 		if(img.next('.image').length == 0){
 | |
| 			prevImage()
 | |
| 		} else {
 | |
| 			nextImage()
 | |
| 		}
 | |
| 		img
 | |
| 			.detach()
 | |
| 			.appendTo($('.current-ribbon').next('.ribbon'))
 | |
| 	}
 | |
| 	$('.current-image').click()
 | |
| }
 | |
| 
 | |
| // XXX sort elements correctly...
 | |
| // XXX do animations...
 | |
| // XXX BUG: when demoting first image (new ribbon created) it gets focused...
 | |
| //		REASON: .click() gets called in several places BEFORE the animation is done...
 | |
| //		NOTE: this bog does not affect promoteImage -- adding a lower element does not affect current positioning...
 | |
| function demoteImage(){
 | |
| 	if($('.current-ribbon').prev('.ribbon').length == 0){
 | |
| 		var new_ribbon = createRibbonAbove()
 | |
| 	}
 | |
| 	// XXX sort elements correctly...
 | |
| 	if($('.current-ribbon').children('.image').length == 1){
 | |
| 		// XXX this adds image to the head while the below portion adds it to the tail...
 | |
| 		mergeRibbonsUp()
 | |
| 	} else {
 | |
| 		img = $('.current-image')
 | |
| 		if(img.next('.image').length == 0){
 | |
| 			// XXX in case when we've just created an empty ribbon, the click in this fires BEFORE it is fully expanded...
 | |
| 			prevImage()
 | |
| 		} else {
 | |
| 			// XXX in case when we've just created an empty ribbon, the click in this fires BEFORE it is fully expanded...
 | |
| 			nextImage()
 | |
| 		}
 | |
| 		img
 | |
| 			.detach()
 | |
| 			.appendTo($('.current-ribbon').prev('.ribbon'))
 | |
| 	}
 | |
| 	// XXX in case when we've just created an empty ribbon, the click in this fires BEFORE it is fully expanded...
 | |
| 	$('.current-image').click()
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| </script>
 | |
| 
 | |
| <style>
 | |
| 	.image {
 | |
| 		position: relative;
 | |
| 		display: inline-block;
 | |
| 
 | |
| 		opacity: 0.3;
 | |
| 
 | |
| 		-webkit-transition: all 0.5s ease;
 | |
| 		-moz-transition: all 0.5s ease;
 | |
| 		-o-transition: all 0.5s ease;
 | |
| 		-ms-transition: all 0.5s ease;	
 | |
| 		transition: all 0.5s ease;
 | |
| 
 | |
| 		cursor: hand;
 | |
| 	}
 | |
| 
 | |
| 	.mock-image {
 | |
| 		width: 350px;
 | |
| 		height: 350px;
 | |
| 
 | |
| 		background: blue;
 | |
| 	}
 | |
| 
 | |
| 	.demo-buttons {
 | |
| 		margin: 15px
 | |
| 		border: groove 2px;
 | |
| 		
 | |
| 		opacity: 0.2;
 | |
| 
 | |
| 		-webkit-transition: all 0.5s ease;
 | |
| 		-moz-transition: all 0.5s ease;
 | |
| 		-o-transition: all 0.5s ease;
 | |
| 		-ms-transition: all 0.5s ease;	
 | |
| 		transition: all 0.5s ease;
 | |
| 	}
 | |
| 	.demo-buttons:hover {
 | |
| 		opacity: 1;
 | |
| 	}
 | |
| 
 | |
| 	.viewer {
 | |
| 		width: 900px;
 | |
| 		height: 500px;
 | |
| 		border: solid blue 5px;
 | |
| 		margin: 20px; 
 | |
| 	}
 | |
| 	.controller {
 | |
| 		height: 500px;
 | |
| 		width: 50px;
 | |
| 		background: silver;
 | |
| 		float: left;
 | |
| 
 | |
| 		-webkit-transition: all 0.5s ease;
 | |
| 		-moz-transition: all 0.5s ease;
 | |
| 		-o-transition: all 0.5s ease;
 | |
| 		-ms-transition: all 0.5s ease;	
 | |
| 		transition: all 0.5s ease;
 | |
| 	}
 | |
| 	.single-image-mode .controller {
 | |
| 		opacity: 0.2;
 | |
| 	}
 | |
| 	.promote, .next-image, .prev-image, .demote, .toggle-wide, .toggle-single {
 | |
| 		text-align: center;
 | |
| 		vertical-align: middle;
 | |
| 		width: 100%;
 | |
| 		height: 150px; 
 | |
| 		background: gray;
 | |
| 
 | |
| 	   -moz-user-select: none;
 | |
| 	   -webkit-user-select: none;
 | |
| 	   -o-user-select: none;
 | |
| 	   -ms-user-select: none;
 | |
| 	   user-select: none;
 | |
| 	}
 | |
| 	.next-image, .prev-image, .toggle-wide, .toggle-single {
 | |
| 		background: silver;
 | |
| 	}
 | |
| 	.toggle-wide, .toggle-single {
 | |
| 		height:50px
 | |
| 	}
 | |
| 	.promote {
 | |
| 	}
 | |
| 	.next-image {
 | |
| 	}
 | |
| 	.prev-image {
 | |
| 	}
 | |
| 	.demote {
 | |
| 	}
 | |
| 	.toggle-wide {
 | |
| 	}
 | |
| 	.toggle-single {
 | |
| 	}
 | |
| 
 | |
| 	.container {
 | |
| 		float: left;
 | |
| 		overflow: hidden;
 | |
| 		width: 800px;
 | |
| 		height: 500px;
 | |
| 	}
 | |
| 
 | |
| 	.field {
 | |
| 		position: relative;
 | |
| 		overflow: visible;
 | |
| 		top: 0px;
 | |
| 		left: -100px;
 | |
| 
 | |
| 		-webkit-transition: all 0.5s ease;
 | |
| 		-moz-transition: all 0.5s ease;
 | |
| 		-o-transition: all 0.5s ease;
 | |
| 		-ms-transition: all 0.5s ease;	
 | |
| 		transition: all 0.5s ease;
 | |
| 	}
 | |
| 
 | |
| 	.ribbon {
 | |
| 		height: 360px;
 | |
| 		/* XXX make this expand dynamically */
 | |
| 		width: 10000px;
 | |
| 		overflow: visible;
 | |
| 		padding-top: 2px;
 | |
| 		padding-bottom: 2px;
 | |
| 		text-align: center;
 | |
| 		opacity: 0.2;
 | |
| 
 | |
| 		-webkit-transition: all 0.5s ease;
 | |
| 		-moz-transition: all 0.5s ease;
 | |
| 		-o-transition: all 0.5s ease;
 | |
| 		-ms-transition: all 0.5s ease;	
 | |
| 		transition: all 0.5s ease;
 | |
| 
 | |
| 	}
 | |
| 	.new-ribbon {
 | |
| 		height: 0px;
 | |
| 
 | |
| 		-webkit-transition: all 0.5s ease;
 | |
| 		-moz-transition: all 0.5s ease;
 | |
| 		-o-transition: all 0.5s ease;
 | |
| 		-ms-transition: all 0.5s ease;	
 | |
| 		transition: all 0.5s ease;
 | |
| 	}
 | |
| 
 | |
| 	.current-image {
 | |
| 		opacity: 1.0;
 | |
| 	}
 | |
| 
 | |
| 	.current-ribbon {
 | |
| 		padding-top: 20px;
 | |
| 		padding-bottom: 20px;
 | |
| 
 | |
| 		opacity: 1.0;
 | |
| 
 | |
| 		-webkit-transition: all 0.5s ease;
 | |
| 		-moz-transition: all 0.5s ease;
 | |
| 		-o-transition: all 0.5s ease;
 | |
| 		-ms-transition: all 0.5s ease;	
 | |
| 		transition: all 0.5s ease;
 | |
| 	}
 | |
| 
 | |
| 	.current-ribbon .image {
 | |
| 	}
 | |
| 
 | |
| 
 | |
| 	/* single image theme (start everything with .single-image-mode) 
 | |
| 	 *
 | |
| 	 * XXX need to make this touch friendly...
 | |
| 	 */
 | |
| 	.single-image-mode .image {
 | |
| 		opacity: 0.0;
 | |
| 	}
 | |
| 
 | |
| 	.single-image-mode .image:hover {
 | |
| 		opacity: 0.5;
 | |
| 	}
 | |
| 
 | |
| 	.single-image-mode .current-image:hover, .single-image-mode .current-image {
 | |
| 		opacity: 1.0;
 | |
| 	}
 | |
| 
 | |
| 
 | |
| 	/* wide view mode */
 | |
| 
 | |
| 	/* XXX not yet working correctly...
 | |
| 	.wide-view-mode {
 | |
| 		transform: scale(0.2,0.2);
 | |
| 		-ms-transform: scale(0.2,0.2);
 | |
| 		-webkit-transform: scale(0.2,0.2);
 | |
| 		-o-transform: scale(0.2,0.2);
 | |
| 		-moz-transform: scale(0.2,0.2);
 | |
| 	}
 | |
| 	*/
 | |
| 	.wide-view-mode .mock-image {
 | |
| 		width: 50px;
 | |
| 		height: 50px;
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	.wide-view-mode .ribbon {
 | |
| 		height: 60px;
 | |
| 	}
 | |
| 
 | |
| 
 | |
| 
 | |
| </style>
 | |
| 
 | |
| 
 | |
| <div class="viewer">
 | |
| 	<div class="controller">
 | |
| 		<div class="demote">^</div>
 | |
| 		<div class="prev-image"><</div>
 | |
| 		<div class="promote">v</div>
 | |
| 		<div class="toggle-single">[ ]</div>
 | |
| 	</div>
 | |
| 	<div class="container">
 | |
| 		<div class="field">
 | |
| 			<div class="ribbon">
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 				<div class="image mock-image"></div>
 | |
| 			</div>
 | |
| 		</div>
 | |
| 	</div>
 | |
| 	<div class="controller">
 | |
| 		<div class="demote">^</div>
 | |
| 		<div class="next-image">></div>
 | |
| 		<div class="promote">v</div>
 | |
| 		<div class="toggle-wide">+/-</div>
 | |
| 	</div>
 | |
| </div>
 | |
| 
 | |
| <div class="demo-buttons">
 | |
| 
 | |
| 	<button onclick="firstImage()">first (home)</button>
 | |
| 	<button onclick="prevImage()">prev (left)</button>
 | |
| 	<button onclick="nextImage()">next (right)</button>
 | |
| 	<button onclick="lastImage()">last (end)</button>
 | |
| 
 | |
| 	<br><br>
 | |
| 
 | |
| 	<button onclick="showSingle()">single</button>
 | |
| 	<button onclick="showRibbon()">ribbon</button>
 | |
| 	<button onclick="toggleRibbonView()">toggle ribbon view (space)</button>
 | |
| 
 | |
| 	<br><br>
 | |
| 
 | |
| 	<button onclick="toggleWideView()">toggle wide view</button>
 | |
| 
 | |
| 	<br><br>
 | |
| 
 | |
| 	<button onclick="createRibbonAbove()" disabled>create ribbon above (helper)</button><br>
 | |
| 	<button onclick="createRibbonBelow()" disabled>create ribbon below (helper)</button>
 | |
| 
 | |
| 	<br><br>
 | |
| 
 | |
| 	<button onclick="mergeRibbonsUp()">merge ribbons up</button><br>
 | |
| 	<button onclick="mergeRibbonsDown()">merge ribbons down</button>
 | |
| 
 | |
| 	<br><br>
 | |
| 
 | |
| 	<button onclick="demoteImage()">demote image (shift-up)</button><br>
 | |
| 	<button onclick="promoteImage()">promote image (shift-down)</button><br>
 | |
| 	NOTE: ctrl-shift-up / ctrl-shift-down will demote / promote an image to a new empty ribbon (the default if no ribbon exists)
 | |
| 
 | |
| 	<br><br>
 | |
| 
 | |
| 	<button onclick="focusAboveRibbon()">focus above ribbon (up)</button><br>
 | |
| 	<button onclick="focusBelowRibbon()">focus below ribbon (down)</button>
 | |
| 
 | |
| </div>
 | |
| 
 | |
| <!-- vim:set ts=4 sw=4 nowrap : -->
 |