diff --git a/ui/TODO.otl b/ui/TODO.otl new file mode 100755 index 00000000..d3a2e68a --- /dev/null +++ b/ui/TODO.otl @@ -0,0 +1,47 @@ +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... +add basic actions: + rotate left + rotate right + ... +add info: + number of images in ribbon + position in ribbon + + +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)... diff --git a/ui/gallery-prototype.js b/ui/gallery-prototype.js new file mode 100755 index 00000000..d6cef7da --- /dev/null +++ b/ui/gallery-prototype.js @@ -0,0 +1,356 @@ +$(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 does not work on android... (might need to add tap event handling) + .gestures({eventHandler: handleGestures}) + // XXX this is flaky and breaks some of my code... + /*.wipetouch({ + wipeLeft: nextImage, + wipeRight: prevImage, + wipeUp: demoteImage, + wipeDown: promoteImage, + + tapToClick: true + })*/ + /* 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) + + // control elements... + $('.next-image').click(nextImage) + $('.prev-image').click(prevImage) + $('.demote').click(demoteImage) + $('.promote').click(promoteImage) + $('.toggle-wide').click(toggleWideView) + $('.toggle-single').click(toggleRibbonView) + + // load images... + // XXX not allowed... + //$.getJSON('images.js', loadImages}) + // XXX STUB + loadImages(image_list) + + // set the default position and init... + $('.current-image').click() + +}); + +function loadImages(json){ + var images = json.images + var ribbon = $('.ribbon').last() + + $('.image').remove() + + for(var i = 0; i < images.length; i++){ + $('
') + .css({ 'background-image': 'url('+images[i]+')' }) + .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 = $('
') + .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 $('
') + .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() +} + + +// vim:set ts=4 sw=4 nowrap : diff --git a/ui/gallery.css b/ui/gallery.css old mode 100644 new mode 100755 index e69de29b..a8cdc9c2 --- a/ui/gallery.css +++ b/ui/gallery.css @@ -0,0 +1,199 @@ +.image { + position: relative; + + width: 350px; + height: 350px; + + display: inline-block; + background: no-repeat 50% black; + background-size: contain; + + 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 { + 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: 100000px; + 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 .image { + width: 50px; + height: 50px; + +} + +.wide-view-mode .ribbon { + height: 60px; +} diff --git a/ui/gallery.html b/ui/gallery.html index c35e0db4..c642b0fc 100755 --- a/ui/gallery.html +++ b/ui/gallery.html @@ -1,635 +1,28 @@ + - + + + + + - - -
^