/******************************************* Actions (EXPERIMENTAL) **/ // XXX this set of features is experimental... // // this gives us: // - namespace cleanup // - auto-generated help // // the main questions are: // - is this overcomplicating things? // - are the benefits worth the code bloat? // var ImageGrid = { // this can be serialized... // NOTE: to load a serialized set of options use ImageGrid.set(options)... actions: {}, option: {}, option_props: {}, option_groups: [], // define an action... // the two values that are obligatory are: // title - name of the action // call - callable // XXX revise... ACTION: function(obj, func){ if(func != null){ obj = $.extend(obj, { id: func.name != '' ? func.name : obj.id, call: func }) } // add all the attrs to the function... if(this._type_handler[obj.type] != null){ this._type_handler[obj.type](obj) } var call = obj.call for(i in obj){ if(i == 'doc' && call.doc != null){ call.func_doc = call.doc } call[i] = obj[i] } this[obj.id] = call this.actions[obj.id] = call return call }, // shorthand: each argument is an action, the group of each will be set the same... GROUP: function(group){ for(var i=1; i b ? 1 : a < b ? -1 : 0 }) } // build the HTML... var ui = $('
') for(var g in tree){ var group = null for(var i=0; i') .append($('
').text(g)) } var option group.append( option = $('
').append($([ $('
').text(tree[g][i][0])[0], $('
').html( elem.doc?elem.doc.replace(/\n/g, '
'):'')[0], $('
').text(get_value(elem))[0] ]))) if(elem.display == false){ option.addClass('disabled') } else { // handler... var handler = get_handler(elem) if(handler != null){ option.click(handler) } } } if(group != null){ groups_ui[g] = group } } // put the Other group in the back... var i = groups.indexOf('Other') if(i != -1){ groups.splice(i, 1) groups.push('Other') } // buildup the sorted groups... for(var i=0; i id){ return prev } prev = $(images[i]) } return prev } // generic binery search for element just before the id... // NOTE: if id is in lst, this will return the element just before it. // NOTE: lst must be sorted. function binarySearch(id, lst, get_order){ if(get_order == null){ get_order = function(o){return o} } // empty list... if(lst.length == 0){ return null } // current section length var l = Math.round((lst.length-1)/2) // current position... var i = l while(true){ var i_id = get_order(lst[i]) // beginning of the array... if(i == 0){ if(id > i_id){ return i } return null } // we got a hit... if(i_id == id){ return i-1 } // we are at the end... if(i == lst.length-1 && id > i_id){ return i } var ii_id = get_order(lst[i+1]) // test if id is between i and i+1... if( i_id < id && id < ii_id ){ return i } // prepare for next iteration... // NOTE: we saturate the values so we will never get out of bounds. l = Math.round(l/2) if(id < i_id){ // lower half... i = Math.max(0, i-l) } else { // upper half... i = Math.min(i+l, lst.length-1) } } } // wrapper around binarySearch. // this is here to make binarySearch simpler to test and debug... function getImageBefore_bin(id, ribbon, get_order){ if(get_order == null){ get_order = getImageOrder } var images = ribbon.children('.image') var i = binarySearch(id, images, get_order) if(i == null){ return null } return $(images[i]) } // set the default search... var getImageBefore = getImageBefore_bin /* * The folowing two functions will get the vertical and horizontal * distance components between the points a and A, centers of the small * and large squares respectively. * One of the squares is .field and the other is .container, * which is small or big is not important. * * +---------------+-------+ * | | | * | | | * | + a . . | . . . | . + * | . | | +- getCurrentVerticalOffset(...) * | . + A | . . . | . + * +---------------+ | * | . . | * | . . | * | . . | * +-----------------------+ * . . * +-+-+ * +------------------- getCurrentHorizontalOffset(...) * * * Adding this distance to margins of one of the sqares will effectively * allign the two points. * * NOTE: neither function accunts for field margins. * */ // get the vertical offset of the center of square from center of container // NOTE: this does not account for field margins function getCurrentVerticalOffset(image){ if(image == null){ image = $('.image.current') } var scale = getElementScale($('.field')) var ribbons = $('.ribbon') var ribbon = image.parents('.ribbon') var images = ribbon.children('.image') // vertical... var H = $('.container').height() var h = ribbons.outerHeight(true) // margin... var mh = h - ribbons.outerHeight() // current ribbon position (1-based) var rn = ribbons.index(ribbon) + 1 // relative position to field... // XXX is there a better way to get this? var t = rn * (h - mh/2) return -t + H/2 + h/2 } // get the horizontal offset of the center of square from center of container // NOTE: this does not account for field margins function getCurrentHorizontalOffset(image){ if(image == null){ image = $('.image.current') } var ribbon = image.parents('.ribbon') var images = ribbon.children('.image') var W = $('.container').width() var w = images.outerWidth(true) // margin... var mw = w - images.outerWidth() // current square position (1-based) var sn = images.index(image) + 1 var l = sn * (w - mw/2) return -l + W/2 + w/2 } // XXX some minor inacuracies... function centerIndicator(){ // XXX something odd going on with the border here... var i_border = Math.abs($('.current-indicator').outerHeight() - $('.current-indicator').height())/2 $('.current-indicator').css({ 'top': ($('.ribbon').index($('.current.ribbon'))) * $('.ribbon').outerHeight() - i_border, 'left': ($('.viewer').outerWidth() - $('.current-indicator').outerWidth())/2, }) } function centerSquare(){ $('.field').css({ 'margin-top': getCurrentVerticalOffset() }) // horizontal... alignRibbon() ImageGrid.centerCurrentImage() } function alignRibbon(image, position){ // default values... if(image == null){ image = $('.image.current') } if(position == null){ position = 'center' } var ribbon = image.parents('.ribbon') // account for margined field... // NOTE: this enables us to cheat and shift all the ribbons just // by changing field margin-left... var cml = parseFloat($('.field').css('margin-left')) if(!cml){ cml = 0 } var h_offset = getCurrentHorizontalOffset(image) - cml var w = $('.image').outerWidth(true) switch(position){ case 'before': ribbon.css({'margin-left': h_offset - w/2}) return true case 'center': ribbon.css({'margin-left': h_offset}) return true case 'after': ribbon.css({'margin-left': h_offset + w/2}) return true } return false } // center other ribbons relative to current image... // NOTE: only two ribbons are positioned at this point... function alignRibbons(get_order){ if(get_order == null){ get_order = getImageOrder } // XXX might be good to move this to a more generic location... var id = get_order($('.current.image')) var directions = ['prev', 'next'] for(var i in directions){ var ribbon = $('.current.ribbon')[directions[i]]('.ribbon') if(ribbon.length == 1){ var img = getImageBefore(id, ribbon) if(img != null){ alignRibbon(img, 'before') } else { // there are no images before... alignRibbon(ribbon.children('.image').first(), 'after') } } } } /************************************************** Setup Functions **/ // XXX is this a correct place for these? function setDefaultInitialState(){ if($('.current.ribbon').length == 0){ $('.ribbon').first().addClass('current') } if($('.current.image').length == 0){ $('.current.ribbon').children('.image').first().addClass('current') } } function setupEvents(){ // resize... $(window).resize(function() { // XXX HACK $('.current.image').click() }) // keyboard... if(DEBUG){ $(document) .keydown(makeKeyboardHandler(keybindings, function(k){alert(k)})) } else { $(document) .keydown(makeKeyboardHandler(keybindings)) } // swipe... $('.viewer') .swipe({ swipeLeft: ImageGrid.nextImage, swipeRight: ImageGrid.prevImage, swipeUp: ImageGrid.shiftImageUp, swipeDown: ImageGrid.shiftImageDown }) // dragging... // XXX make this work seamlessly with touchSwipe... // XXX cancel clicks while dragging... // XXX this does not work on android... $('.field').draggable() } function setupControlElements(){ // images... // NOTE: when the images are loaded, the actual handlers will be set by the loader... setupImageEventHandlers($(".image")) // buttons... $('.screen-button.next-image').mousedown(ImageGrid.nextImage) $('.screen-button.prev-image').mousedown(ImageGrid.prevImage) // XXX rename classes to "shift-image-up" and "shift-image-down"... $('.screen-button.demote').mousedown(ImageGrid.shiftImageUp) $('.screen-button.promote').mousedown(ImageGrid.shiftImageDown) $('.screen-button.zoom-in').mousedown(ImageGrid.scaleContainerUp) $('.screen-button.zoom-out').mousedown(ImageGrid.scaleContainerDown) // XXX $('.screen-button.toggle-wide').mousedown(function(){ImageGrid.scaleContainerBy(0.2)}) $('.screen-button.toggle-single').mousedown(ImageGrid.toggleSingleImageMode) $('.screen-button.fit-three').mousedown(ImageGrid.fitThreeImages) $('.screen-button.show-controls').mousedown(function(){ImageGrid.toggleControls('on')}) $('.screen-button.settings').mousedown(ImageGrid.showKeyboardBindings) } /**************************************************** Serialization **/ // setup image event handlers... function setupImageEventHandlers(image){ return (image .click(handleImageClick) .dblclick(function(e){ $(this).click() ImageGrid.toggleSingleImageMode() })) } // build an image element... function makeImage(url, order, set_order){ if(set_order == null){ set_order = setImageOrder } return (setupImageEventHandlers( set_order($('
') .css({ 'background-image': 'url('+url+')' }), order))) } function loadImages(json){ var images = json.images var ribbon = $('.ribbon').last() $('.image').remove() for(var i = 0; i < images.length; i++){ makeImage(images[i], i) .appendTo(ribbon) } ribbon.children().first().click() } /* bulid a JSON object from current state... * * format: * { * ribbons: [ * : { * url: , * }, * ... * ] * } */ // XXX add incremental or partial updates... function buildJSON(get_order){ if(get_order == null){ get_order = getImageOrder } var ribbons = $('.ribbon') res = { ribbons: [] } for(var i=0; i < ribbons.length; i++){ var images = $(ribbons[i]).children('.image') var ribbon = {} res.ribbons[res.ribbons.length] = ribbon for(var j=0; j < images.length; j++){ var image = $(images[j]) var id = get_order(image) ribbon[id] = { // unwrap the url... // XXX would be nice to make this a relative path... (???) url: /url\((.*)\)/.exec(image.css('background-image'))[1], } } } return res } // XXX use this instead of loadImages(...) // XXX might be good to add images in packs here, not one by one... function loadJSON(data, set_order){ if(set_order == null){ set_order = setImageOrder } var ribbons = data.ribbons var field = $('.field') // drop all old content... field.children('.ribbon').remove() for(var i=0; i < ribbons.length; i++){ var images = ribbons[i] // create ribbon... var ribbon = $('
') .appendTo(field) for(var j in images){ var image = $(images[j]) // create image... makeImage(image.attr('url'), j, set_order) .appendTo(ribbon) } } $('.image').first().click() } /*************************************************** Event Handlers **/ // handle click for images... function handleImageClick(){ // set classes... $('.current').removeClass('current') $(this) .addClass('current') .parents('.ribbon') .addClass('current') // position the field and ribbons... centerSquare() centerIndicator() alignRibbons() } // if set to false the event handlers will always return false... var KEYBOARD_HANDLER_PROPAGATE = false /* Basic key format: * : , * : { * 'default': , * // a modifier can be any single modifier, like shift or a * // combination of modifers like 'ctrl+shift', given in order * // of priority. * // supported modifiers are (in order of priority): * // - ctrl * // - alt * // - shift * : [...] * }, * : [ * // this can be any type of handler except for an alias... * , * * ], * // alias... * : , * * XXX might need to add meta information to generate sensible help... */ function makeKeyboardHandler(keybindings, unhandled){ if(unhandled == null){ unhandled = function(){return false} } return function(evt){ var key = evt.keyCode // XXX ugly... var modifers = evt.ctrlKey ? 'ctrl' : '' modifers += evt.altKey ? (modifers != '' ? '+alt' : 'alt') : '' modifers += evt.shiftKey ? (modifers != '' ? '+shift' : 'shift') : '' var handler = keybindings[key] // alias... while (typeof(handler) == typeof(123)) { handler = keybindings[handler] } // no handler... if(handler == null){ return unhandled(key) } // Array, lisp style with docs... // XXX for some odd reason in chrome typeof([]) == typeof({})!!! if(typeof(handler) == typeof([]) && handler.constructor.name == 'Array'){ // we do not care about docs here, so just get the handler... handler = handler[0] } // complex handler... if(typeof(handler) == typeof({})){ var callback = handler[modifers] if(callback == null){ callback = handler['default'] } if(callback != null){ var res = callback() return KEYBOARD_HANDLER_PROPAGATE&&res?true:false } } else { // simple callback... var res = handler() return KEYBOARD_HANDLER_PROPAGATE&&res?true:false } return unhandled(key) } } /************************************************ Mode & UI Actions **/ ImageGrid.GROUP('Mode: All', ImageGrid.ACTION({ title: 'Get the background mode', display: false, }, function getBackgroundMode(){ var mode = null var BACKGROUND_MODES = ImageGrid.option.BACKGROUND_MODES // find a mode to set... for(var i = 0; i < BACKGROUND_MODES.length; i++){ // we found our mode... if( $('.' + BACKGROUND_MODES[i]).length > 0 ){ return BACKGROUND_MODES[i] } } return mode }), ImageGrid.ACTION({ title: 'Set the background mode', doc: 'NOTE: passing null will set the default.', display: false, }, function setBackgroundMode(mode){ var BACKGROUND_MODES = ImageGrid.option.BACKGROUND_MODES var cur = BACKGROUND_MODES.indexOf(mode) // invalid mode... if( cur == -1 && mode != null ){ return null } // set the mode... if(mode != null){ $('.viewer').addClass(mode) } // remove all others... for(var i = 0; i < BACKGROUND_MODES.length; i++){ if( i == cur ){ continue } mode = BACKGROUND_MODES[i] $('.' + mode).removeClass(mode) } }), ImageGrid.ACTION({ title: 'Toggle background modes', doc: 'Toggle through background theems: none -> dark -> black\n\n'+ 'NOTE: modes are toggled independently for single image and '+ 'rinbon modes', type: 'toggle', }, function toggleBackgroundModes(action){ if(action == '?'){ return ImageGrid.getBackgroundMode() } else if(action != null){ return ImageGrid.setBackgroundMode(action) } else { var BACKGROUND_MODES = ImageGrid.option.BACKGROUND_MODES var mode = ImageGrid.getBackgroundMode() // default -> first if(mode == null){ ImageGrid.setBackgroundMode(BACKGROUND_MODES[0]) // last -> default... } else if(mode == BACKGROUND_MODES[BACKGROUND_MODES.length-1]){ ImageGrid.setBackgroundMode() // next... } else { ImageGrid.setBackgroundMode(BACKGROUND_MODES[BACKGROUND_MODES.indexOf(mode)+1]) } } }), ImageGrid.ACTION({ id: 'toggleControls', title: 'Toggle keyboard-oriented interface', doc: 'Toggle Touch/Keyboard UI controls.', type: 'toggle', }, createCSSClassToggler('.viewer', 'hidden-controls')), ImageGrid.ACTION({ id: 'toggleTransitions', title: 'Global transitions', doc: 'Toggle global transitions.', type: 'toggle', }, createCSSClassToggler('.viewer', 'transitions-enabled'))) ImageGrid.GROUP('Configuration and Help', ImageGrid.ACTION({ title: 'Close overlay' }, function closeOverlay(){ $('.overlay').click() }), // XXX use order and priority of options... // XXX make history work for this... // XXX should this be a toggle?? ImageGrid.ACTION({ title: 'Settings', doc: 'Show setup interface.', }, function showSetup(){ showOptionsUI(ImageGrid.option_props, function(e){ // XXX need to update a value here... return ImageGrid.option[e.name] }, function(e){return e.click_handler}) }), ImageGrid.ACTION({ title: 'Keyboard configuration', doc: 'Show keyboard configuration interface.', }, function showKeyboardBindings(){ // build reverse key index... var bindings = {} for(var k in keybindings){ var id var v = keybindings[k] // alias... while (typeof(v) == typeof(123)) { v = keybindings[v] } // Array, lisp style with docs... if(typeof(v) == typeof([]) && v.constructor.name == 'Array'){ // XXX what do we do here??? } // function... if(typeof(v) == typeof(function(){})){ id = v.id != null ? v.id : v.name } // complex handler... // NOTE: this can contain several key bindings... if(typeof(v) == typeof({})){ for(var m in v){ id = v[m].id != null ? v[m].id : v[m].name if(bindings[id] == null){ bindings[id] = [] } bindings[id].push((m=='default'?'':m+'+') + toKeyName(k)) } continue } if(bindings[id] == null){ bindings[id] = [] } bindings[id].push(toKeyName(k)) } showOptionsUI(ImageGrid.actions, function(e){ return (bindings[e.id]!=null?bindings[e.id]:'None') .toString() .replace(/,/g, ', ') }, // XXX function(e){}) })) ImageGrid.GROUP('Mode: Single Image', ImageGrid.ACTION({ id: 'toggleSingleImageMode', title: 'Single image mode', doc: 'Toggle single image mode.', type: 'toggle', }, createCSSClassToggler('.viewer', 'single-image-mode', // pre... function(action){ if(action == 'on'){ ImageGrid.option.NORMAL_MODE_BG = ImageGrid.getBackgroundMode() ImageGrid.option.ORIGINAL_FIELD_SCALE = getElementScale($('.field')) // do this only when coming out of single image mode... } else if(ImageGrid.toggleSingleImageMode('?') == 'on'){ ImageGrid.option.SINGLE_IMAGE_MODE_BG = ImageGrid.getBackgroundMode() } }, // post... function(action){ if(action == 'on'){ ImageGrid.fitImage() ImageGrid.setBackgroundMode(ImageGrid.option.SINGLE_IMAGE_MODE_BG) } else { ImageGrid.setContainerScale(ImageGrid.option.ORIGINAL_FIELD_SCALE) ImageGrid.setBackgroundMode(ImageGrid.option.NORMAL_MODE_BG) } clickAfterTransitionsDone() })), // XXX for some reason this is backwords... (says 'on' when it's off ans 'off' when on) // ...and needs an extra click to sync with state... ImageGrid.ACTION({ id: 'toggleSingleImageModeTransitions', title: 'Disable single image mode transitions', doc: 'Toggle transitions in single image mode.', type: 'toggle', }, createCSSClassToggler('.viewer', 'no-single-image-transitions'))) ImageGrid.GROUP('Mode: Ribbon', ImageGrid.ACTION({ id: 'toggleSingleRibbonMode', title: 'Single ribbon mode', doc: 'Show/hide other ribbons.', type: 'toggle', }, createCSSClassToggler('.viewer', 'single-ribbon-mode')), ImageGrid.ACTION({ id: 'toggleCurrentRibbonOpacity', title: 'Current ribbon opacity', doc: 'Toggle other image transparancy/opacity in current ribbon.', type: 'toggle', }, createCSSClassToggler('.viewer', 'opaque-current-ribbon')), ImageGrid.ACTION({ id: 'toggleIndicatorDot', title: 'Dot indicator', doc: 'Toggle indicator between dot and frame modes.\n\n'+ 'NOTE: this is visible only when the indicator is visible.', type: 'toggle', }, createCSSClassToggler('.viewer', 'dot-indicator')), // XXX this can be done in two ways: // - keep all images when promoting, just add a class to them that // will hide them until we enable their display... // + very fast to show/hide // - will complicate reversing ribbons allot // - add/remove these images on demand // + a tad complicated... ImageGrid.ACTION({ id: 'toggleDisplayShiftedUpImages', title: 'Display shifted up images', doc: 'Toggle display of shifted images.', display: false, type: 'toggle', }, createCSSClassToggler('.viewer', 'show-shifted-up-images'))) /********************************************************* Movement **/ ImageGrid.GROUP('Movement', ImageGrid.ACTION({ title: 'Center origin', doc: 'Set the transform-origin to the center of the current view.', display: false, }, function centerOrigin(){ var mt = parseFloat($('.field').css('margin-top')) var ml = parseFloat($('.field').css('margin-left')) var cml = parseFloat($('.current.ribbon').css('margin-left')) var t = parseFloat($('.field').css('top')) var l = parseFloat($('.field').css('left')) var w = $('.field').width() var h = $('.field').height() var W = $('.container').width() var H = $('.container').height() var ot = -getCurrentVerticalOffset() + H/2 - t var ol = -ml + W/2 - l $('.field').css({ 'transform-origin': ol + 'px ' + ot + 'px', '-o-transform-origin': ol + 'px ' + ot + 'px', '-moz-transform-origin': ol + 'px ' + ot + 'px', '-webkit-transform-origin': ol + 'px ' + ot + 'px', '-ms-transform-origin': ol + 'px ' + ot + 'px' }) // XXX for debugging... $('.origin-marker').css({ 'top': ot, 'left': ol }) }), // XXX these work oddly when page is scaled in maxthon... // XXX virtually identical, see of can be merged... ImageGrid.ACTION({ title: 'Move view up', }, function moveViewUp(){ var t = parseInt($('.field').css('top')) $('.field').css({'top': t-(ImageGrid.option.MOVE_DELTA)}) }), ImageGrid.ACTION({ title: 'Move view down', }, function moveViewDown(){ var t = parseInt($('.field').css('top')) $('.field').css({'top': t+(ImageGrid.option.MOVE_DELTA)}) }), ImageGrid.ACTION({ title: 'Move view left', }, function moveViewLeft(){ var l = parseInt($('.field').css('left')) $('.field').css({'left': l-(ImageGrid.option.MOVE_DELTA)}) }), ImageGrid.ACTION({ title: 'Move view right', }, function moveViewRight(){ var l = parseInt($('.field').css('left')) $('.field').css({'left': l+(ImageGrid.option.MOVE_DELTA)}) }), ImageGrid.ACTION({ title: 'Center current image', }, function centerCurrentImage(){ $('.field') .css({ 'top': 0, 'left': 0 }) // do this after animations are done... .one("webkitTransitionEnd oTransitionEnd msTransitionEnd transitionend", ImageGrid.centerOrigin) // this is repeated intentionally... // ...needed for small shifts, while the after-animation event // is for large moves. ImageGrid.centerOrigin() })) /******************************************************* Navigation **/ ImageGrid.GROUP('Navigation', // basic navigation... ImageGrid.ACTION({ title: 'Go to first image', }, function firstImage(){ return $('.current.ribbon').children('.image').first().click() }), ImageGrid.ACTION({ title: 'Go to previous image', }, function prevImage(){ return $('.current.image').prev('.image').click() }), ImageGrid.ACTION({ title: 'Go to next image', }, function nextImage(){ return $('.current.image').next('.image').click() }), ImageGrid.ACTION({ title: 'Go to last image', }, function lastImage(){ return $('.current.ribbon').children('.image').last().click() }), ImageGrid.ACTION({ title: 'Skip screen images', doc: 'Skip screen-width images in specified direction', display: false, }, function skipScreenImages(direction){ // calculate screen width in images... var W = $('.viewer').width() var w = $('.current.image').width() var scale = getElementScale($('.field')) var n = Math.max(Math.floor(W/(w*scale))-1, 0) var img = $('.current.image')[direction + 'All']('.image').eq(n) if(img.length > 0){ return img.click() } else if(direction == 'next'){ return ImageGrid.lastImage() } else if(direction == 'prev'){ return ImageGrid.firstImage() } }), ImageGrid.ACTION({ title: 'Skip next screen images', }, function nextScreenImages(){ return ImageGrid.skipScreenImages('next') }), ImageGrid.ACTION({ title: 'Skip screen images backwards', }, function prevScreenImages(){ return ImageGrid.skipScreenImages('prev') }), ImageGrid.ACTION({ title: 'Focus ribbon', doc: 'Focus ribbon in specified direction', display: false, }, function focusRibbon(direction, get_order){ if(get_order == null){ get_order = getImageOrder } var id = get_order($('.current.image')) var prev = getImageBefore(id, $('.current.ribbon')[direction]('.ribbon')) if(prev){ var next = prev.next() // NOTE: direction is accounted for to make the up/down shifts // symmetrical in the general case... if(next.length == 0 || direction == 'next'){ return prev.click() } else { return next.click() } } else { return $('.current.ribbon')[direction]('.ribbon').children('.image').first().click() } }), ImageGrid.ACTION({ title: 'Focus ribbon above', }, function focusAboveRibbon(){ return ImageGrid.focusRibbon('prev') }), ImageGrid.ACTION({ title: 'Focus ribbon below', }, function focusBelowRibbon(){ return ImageGrid.focusRibbon('next') })) /********************************************************** Zooming **/ ImageGrid.GROUP('Zooming', ImageGrid.ACTION({ title: 'Scale container by factor', display: false, }, function scaleContainerBy(factor){ return ImageGrid.setContainerScale(getElementScale($('.field'))*factor) }), ImageGrid.ACTION({ title: 'Scale container up', }, function scaleContainerUp(){ return ImageGrid.scaleContainerBy(ImageGrid.option.ZOOM_FACTOR) }), ImageGrid.ACTION({ title: 'Scale container down', }, function scaleContainerDown(){ return ImageGrid.scaleContainerBy(1/ImageGrid.option.ZOOM_FACTOR) }), ImageGrid.ACTION({ title: 'Set container scale', display: false, }, function setContainerScale(scale){ return setElementScale($('.field'), scale) }), ImageGrid.ACTION({ title: 'Fit N images to container width/height', display: false, }, function fitNImages(n){ var H = $('.container').height() var W = $('.container').width() var h = $('.image.current').height() // NOTE: this is cheating, need to get actual three widths... var w = $('.image.current').width()*n var f = Math.min(H/h, W/w) ImageGrid.setContainerScale(f) }), // the fit N image pack, for 1 <= N <= 9 ImageGrid.ACTION({ title: 'Fit 1 image' }, function fitImage(){ImageGrid.fitNImages(1)}), ImageGrid.ACTION({ title: 'Fit 2 images' }, function fitTwoImages(){ImageGrid.fitNImages(2)}), ImageGrid.ACTION({ title: 'Fit 3 images' }, function fitThreeImages(){ImageGrid.fitNImages(3)}), ImageGrid.ACTION({ title: 'Fit 4 images' }, function fitFourImages(){ImageGrid.fitNImages(4)}), ImageGrid.ACTION({ title: 'Fit 5 images' }, function fitFiveImages(){ImageGrid.fitNImages(5)}), ImageGrid.ACTION({ title: 'Fit 6 images' }, function fitSixImages(){ImageGrid.fitNImages(6)}), ImageGrid.ACTION({ title: 'Fit 7 images' }, function fitSevenImages(){ImageGrid.fitNImages(7)}), ImageGrid.ACTION({ title: 'Fit 8 images' }, function fitEightImages(){ImageGrid.fitNImages(8)}), ImageGrid.ACTION({ title: 'Fit 9 images' }, function fitNineImages(){ImageGrid.fitNImages(9)}) ) /*************************************************** Ribbon Actions **/ // basic actions... // NOTE: below 'direction' argument is meant in the html sence, // i.e. next/prev... ImageGrid.GROUP('Ribbon manipulations', // XXX adding a ribbon above the current is still jumpy, need to devise // a cleaner way to do this... ImageGrid.ACTION({ title: 'Create a ribbon above/below current', display: false, }, function createRibbon(direction){ if(direction == 'next'){ var insert = 'insertAfter' } else if(direction == 'prev') { var insert = 'insertBefore' } else { return false } // adding a new ribbon above the current effectively pushes the // whole view down, so we need to compensate for this. // NOTE: the problem is partly caused by clicks fiering BEFORE the // animation is done... $('.field').addClass('unanimated') if(direction == 'prev'){ $('.field').css({ 'margin-top': parseInt($('.field').css('margin-top')) - $('.ribbon').outerHeight() }) } // the actual insert... var res = $('
')[insert]('.current.ribbon') // restore the animated state... $('.field').removeClass('unanimated') return res }), // XXX this uses jquery animation... // XXX one way to optimise this is to add the lesser ribbon to the // greater disregarding their actual order... // XXX think about using $(...).sortChildren(...) / sortImages() ImageGrid.ACTION({ title: 'Merge current and direction ribbon.', doc: 'NOTE: this will take all the elements from direction '+ 'ribbon and add them to current.', display: false, }, function mergeRibbons(direction, get_order){ if(get_order == null){ get_order = getImageOrder } var current_ribbon = $('.current.ribbon') var images = $('.current.ribbon')[direction]('.ribbon').children() for(var i=0; i < images.length; i++){ var image = $(images[i]) // get previous element after which we need to put the current... var prev_elem = getImageBefore(get_order(image), current_ribbon) // check if we need to be before the first element... if(prev_elem == null){ image .detach() .insertBefore(current_ribbon.children('.image').first()) } else { image .detach() .insertAfter(prev_elem) } } // animate... $('.current.ribbon')[direction]('.ribbon') .slideUp(function(){ $(this).remove() $('.current.image').click() }) }), ImageGrid.ACTION({ title: 'Reverse ribbon order', doc: 'NOTE: this is like flipping the field vertically.', }, function reverseRibbons(){ // reverse... $('.field').reverseChildren() // compensate for offset cange... $('.current.image').click() })) /**************************************************** Image Actions **/ ImageGrid.GROUP('Image manipulation', ImageGrid.ACTION({ title: 'Shift image in direction', display: false, }, function shiftImage(direction, get_order){ if(get_order == null){ get_order = getImageOrder } if($('.current.ribbon')[direction]('.ribbon').length == 0){ ImageGrid.createRibbon(direction) } // get previous element after which we need to put the current... var prev_elem = getImageBefore( get_order($('.current.image')), $('.current.ribbon')[direction]('.ribbon')) // last image in ribbon, merge... if($('.current.ribbon').children('.image').length == 1){ ImageGrid.mergeRibbons(direction) } else { img = $('.current.image') if(img.next('.image').length == 0){ ImageGrid.prevImage() } else { ImageGrid.nextImage() } // do the actual move... if(prev_elem){ // insert element after current... img .detach() .insertAfter(prev_elem) } else { // empty ribbon or fisrt element... img .detach() .prependTo($('.current.ribbon')[direction]('.ribbon')) } } $('.current.image').click() }), // shift image... ImageGrid.ACTION({ title: 'Shift image up', }, function shiftImageUp(){ return ImageGrid.shiftImage('prev') }), ImageGrid.ACTION({ title: 'Shift image down', }, function shiftImageDown(){ return ImageGrid.shiftImage('next') }), // shift image to new ribbon... ImageGrid.ACTION({ title: 'Shift image up to new ribbon', }, function shiftImageUpNewRibbon(){ ImageGrid.createRibbon('prev') ImageGrid.shiftImageUp() }), ImageGrid.ACTION({ title: 'Shift image down to new ribbon', }, function shiftImageDownNewRibbon(){ ImageGrid.createRibbon('next') ImageGrid.shiftImageDown() }), // sorting... ImageGrid.ACTION({ title: 'Sort images via criteria', doc: 'Use the cmp function to update image id\'s and resort.', display: false, }, function sortImagesVia(cmp){ // reverse ID order... $($('.image').get().sort(cmp)) .each(function(i, e){$(e).attr({'id': i})}) // resort the images... ImageGrid.sortImages() }), ImageGrid.ACTION({ title: 'Sort images', doc: 'Sort images in all ribbons\n\n'+ 'NOTE: this will only realign three ribbons.' }, function sortImages(){ $('.ribbon').sortChildren(cmpImageOrder) // compensate for offset cange... $('.current.image').click() }), ImageGrid.ACTION({ title: 'Reverse order of images', doc: 'this will reverse image order in all ribbons.', }, function reverseImageOrder(){ // this is done by reversing their id attr ImageGrid.sortImagesVia(function(a, b){return cmpImageOrder(b, a)}) }), ImageGrid.ACTION({ title: 'Sort images by their full path', }, // XXX this should use a normalized path... function sortImagesByPath(){ ImageGrid.sortImagesVia(function(a, b){ a = $(a).css('background-image') b = $(b).css('background-image') return a > b ? 1 : a < b ? -1 : 0 }) })) // XXX group images in ribbon and merge down/up // // grouping will make the images in a ribbon adjacent to each // other... // // the group's position will be the same as current images i.e. // between the below/above two images... // XXX shift group/image right/left... /*********************************************************************/ // vim:set ts=4 sw=4 nowrap :