ImageGrid/ui/gallery.html
Alex A. Naanou 5a4a7ceb05 added basic image loader...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2012-06-08 16:43:44 +04:00

717 lines
17 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()
// load images...
// XXX not allowed...
//$.getJSON('images.js', loadImages})
// XXX STUB
loadImages({
images:[
'images/350px/DSC_3501.jpg',
'images/350px/DSC_3503.jpg',
'images/350px/DSC_3504.jpg',
'images/350px/DSC_3506.jpg',
]
})
});
function loadImages(json){
// XXX STUB
var images = json.images
var ribbon = $('.ribbon').last()
$('.image').remove()
for(var i = 0; i < images.length; i++){
$('<div class="image mock-image"></div>')
.css({
background : 'url('+images[i]+') no-repeat 50% black',
'background-size': 'contain',
})
.click(handleClick)
.appendTo(ribbon)
}
ribbon.children().first().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">&lt;</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">&gt;</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 : -->