mirror of
https://github.com/flynx/ImageGrid.git
synced 2025-11-01 20:00:10 +00:00
reorgonized the gen3 code...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
75b2147cd2
commit
bb636f9c91
614
ui/ImageGrid.js
614
ui/ImageGrid.js
@ -1,149 +1,555 @@
|
|||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
*
|
*
|
||||||
|
* 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
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*
|
* TODO group all actions into an object, referencing the viewer...
|
||||||
* This should work over three contexts:
|
* ...this will make this reusable multiple times.
|
||||||
* - archive (full)
|
|
||||||
* full data available remotely
|
|
||||||
* handle global operations
|
|
||||||
* - local data (full or partial)
|
|
||||||
* full or partial set of data available locally
|
|
||||||
* handle global operations (if full data-set is available)
|
|
||||||
* handle local operations (if enough data is available)
|
|
||||||
* - local view (partial)
|
|
||||||
* only the rendered UI and cache
|
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
|
|
||||||
var Context = {
|
|
||||||
// the selection query used to get data...
|
|
||||||
// NOTE: this should support operations to get next and prev batches if it's partial
|
|
||||||
// XXX we do not care about this yet
|
|
||||||
query: null,
|
|
||||||
|
|
||||||
// this can be:
|
/**********************************************************************
|
||||||
// 'full' - indicating that all the data is available locally
|
* Constructors
|
||||||
// 'partial' - indicating that only part of the data is available
|
*/
|
||||||
data_state: 'full',
|
|
||||||
|
|
||||||
data: {
|
// NOTE: to avoid state sync problems this should clone an image if
|
||||||
// current image...
|
// one is available...
|
||||||
current: null,
|
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"/>')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// images, hashed by GUID...
|
// This will create a set of new images, reusing a list of existing
|
||||||
images: {
|
// elements if given.
|
||||||
},
|
// XXX do we need this???
|
||||||
|
function createImages(need, have){
|
||||||
|
have = have == null ? [] : have
|
||||||
|
|
||||||
// list of ribbons...
|
// we have enough elements in the cache...
|
||||||
ribbons: [
|
if(have.length >= need){
|
||||||
// list of GUIDs in sort order...
|
return $(have.splice(0, need))
|
||||||
[]
|
|
||||||
],
|
// need to create additional elements...
|
||||||
// list of marked GUIDs...
|
} else {
|
||||||
marked: [
|
return $(have.toArray().concat(new Array(need - have.length)))
|
||||||
],
|
.map(function(i, elem){
|
||||||
|
if(elem != null){
|
||||||
|
return elem
|
||||||
|
}
|
||||||
|
return createImage()[0]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRibbon(){
|
||||||
|
return $('<div class="ribbon"/>')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**********************************************************************
|
||||||
|
* Helpers
|
||||||
|
*/
|
||||||
|
|
||||||
|
function flashIndicator(direction){
|
||||||
|
$(direction == 'prev' ? '.up-indicator' : '.down-indicator').fadeIn(200).fadeOut(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ...tried to make this as brain-dead-stupidly-simple as possible...
|
||||||
|
function getRelativeVisualPosition(outer, inner){
|
||||||
|
outer = $(outer).offset()
|
||||||
|
inner = $(inner).offset()
|
||||||
|
return {
|
||||||
|
top: inner.top - outer.top,
|
||||||
|
left: inner.left - outer.left
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NOTE: if this returns null, it means that the element is smallest in
|
||||||
|
// target ribbon -- first position.
|
||||||
|
function getImageBefore(image, ribbon, mode){
|
||||||
|
if(mode == null){
|
||||||
|
mode = NAV_DEFAULT
|
||||||
|
}
|
||||||
|
image = $(image)
|
||||||
|
if(ribbon == null){
|
||||||
|
ribbon = image.closest('.ribbon')
|
||||||
|
}
|
||||||
|
var images = $(ribbon).find('.image').filter(mode)
|
||||||
|
var order = image.attr('order')
|
||||||
|
var prev = null
|
||||||
|
|
||||||
|
images.each(function(){
|
||||||
|
if(order < $(this).attr('order')){
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
prev = this
|
||||||
|
})
|
||||||
|
|
||||||
|
return $(prev)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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.length == 0){
|
||||||
|
image.prependTo($(ribbon))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
image.insertAfter(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
view: null,
|
// 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 old_ribbon = image.closest('.ribbon')
|
||||||
|
var ribbon = old_ribbon[direction]('.ribbon')
|
||||||
|
|
||||||
/**********************************************************************
|
// need to create a new ribbon...
|
||||||
* Helpers...
|
if(ribbon.length == 0 || force_create_ribbon == true){
|
||||||
*/
|
ribbon = createRibbon()['insert' + (direction == 'prev'
|
||||||
|
? 'Before'
|
||||||
// retrun viewer width in images...
|
: 'After')](old_ribbon)
|
||||||
function getViewImages(){
|
shiftTo(image, ribbon)
|
||||||
// XXX
|
} else {
|
||||||
|
shiftTo(image, ribbon)
|
||||||
|
}
|
||||||
|
return image
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* User actions...
|
* Modes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Focus an image...
|
var toggleMarkedOnlyView = createCSSClassToggler('.viewer', 'marked-only',
|
||||||
*
|
function(){
|
||||||
* n can be:
|
var cur = $('.current.image')
|
||||||
* - position relative to current
|
// current is marked...
|
||||||
* -1 is previous image, +1 next
|
if(cur.hasClass('marked')){
|
||||||
* - GUID
|
centerImage(null, 'css')
|
||||||
* if GUID is present in context select it.
|
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...
|
||||||
|
|
||||||
|
|
||||||
|
// 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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**********************************************************************
|
||||||
|
* Layout
|
||||||
*/
|
*/
|
||||||
function focusImage(n){
|
|
||||||
// XXX
|
function focusImage(image){
|
||||||
|
image.closest('.viewer').find('.current.image').removeClass('current')
|
||||||
|
return image.addClass('current')
|
||||||
}
|
}
|
||||||
|
|
||||||
// shorthands...
|
|
||||||
function nextImage(){
|
|
||||||
return focusImage(1)
|
|
||||||
}
|
|
||||||
function prevImage(){
|
|
||||||
return focusImage(-1)
|
|
||||||
}
|
|
||||||
// NOTE: here n is the multiplier to the screen width of images...
|
|
||||||
function nextViewImages(n){
|
|
||||||
n = n == null ? 1 : n
|
|
||||||
return focusImage(getViewImages()*n)
|
|
||||||
}
|
|
||||||
function prevViewImages(n){
|
|
||||||
n = n == null ? -1 : -n
|
|
||||||
return focusImage(getViewImages()*n)
|
|
||||||
}
|
|
||||||
|
|
||||||
function firstImage(){
|
// This appears to work well with scaling...
|
||||||
// XXX
|
// XXX make this more configurable...
|
||||||
}
|
// XXX this only works for square images...
|
||||||
function lastImage(){
|
function centerImage(image, mode){
|
||||||
// XXX
|
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 = getRelativeVisualPosition(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
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**********************************************************************
|
||||||
/* Focus a ribbon...
|
* Infinite ribbon machinery
|
||||||
*
|
|
||||||
* n can be:
|
|
||||||
* - position relative to current
|
|
||||||
* -1 is previous image, +1 next
|
|
||||||
* - GUID (???)
|
|
||||||
* if GUID is present in context select it.
|
|
||||||
*
|
|
||||||
* NOTE: this will also focus the closest image...
|
|
||||||
*/
|
*/
|
||||||
function focusRibbon(n){
|
|
||||||
// XXX
|
// XXX need mechanics to populate the images or to connect such
|
||||||
|
// functionality...
|
||||||
|
// ...this is to be done in the loader...
|
||||||
|
|
||||||
|
// NOTE: negative left or right will contract the ribbon...
|
||||||
|
function extendRibbon(left, right, ribbon){
|
||||||
|
ribbon = ribbon == null ?
|
||||||
|
$('.current.image').closest('.ribbon')
|
||||||
|
: $(ribbon)
|
||||||
|
left = left == null ? 0 : left
|
||||||
|
right = right == null ? 0 : right
|
||||||
|
var images = ribbon.children('.image')
|
||||||
|
var removed = []
|
||||||
|
var res = {
|
||||||
|
left: [],
|
||||||
|
right: []
|
||||||
|
}
|
||||||
|
|
||||||
|
// truncate...
|
||||||
|
// NOTE: we save the detached elements to reuse them on extending,
|
||||||
|
// if needed...
|
||||||
|
if(left < 0){
|
||||||
|
removed = $(images.splice(0, -left)).detach()
|
||||||
|
}
|
||||||
|
if(right < 0){
|
||||||
|
var l = images.length
|
||||||
|
removed = $(images.splice(l+right, l)).detach()
|
||||||
|
}
|
||||||
|
|
||||||
|
// extend...
|
||||||
|
if (left > 0){
|
||||||
|
res.left = createImages(left, removed).prependTo(ribbon)
|
||||||
|
}
|
||||||
|
if (right > 0){
|
||||||
|
res.right = createImages(right, removed).appendTo(ribbon)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// shorthands...
|
|
||||||
function ribbonAbove(n){
|
|
||||||
n = n == null ? -1 : n
|
|
||||||
return focusRibbon(n)
|
|
||||||
}
|
|
||||||
function ribbonBelow(n){
|
|
||||||
n = n == null ? 1 : -n
|
|
||||||
return focusRibbon(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
function topRibbon(){
|
// Roll the ribbon n positions to the left.
|
||||||
// XXX
|
//
|
||||||
}
|
// NOTE: if n is negative the ribbon will be rolled right.
|
||||||
function bottomRibbon(){
|
// NOTE: rollRibbon(N, R) is equivalent to extendRibbon(-N, N, R)
|
||||||
// XXX
|
// NOTE: this will return a single list of relocated elements...
|
||||||
|
function rollRibbon(n, ribbon){
|
||||||
|
var res = extendRibbon(-n, n, ribbon)
|
||||||
|
return n > 0 ? res.right : res.left
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Marking...
|
|
||||||
*
|
/**********************************************************************
|
||||||
* NOTE: n can be null, then current image is marked.
|
* User actions
|
||||||
*/
|
*/
|
||||||
function toggleMark(n){
|
|
||||||
// XXX
|
// NOTE: NAV_ALL might not be practical...
|
||||||
|
var NAV_ALL = '*'
|
||||||
|
var NAV_VISIBLE = ':visible'
|
||||||
|
var NAV_MARKED = '.marked:visible'
|
||||||
|
|
||||||
|
var NAV_DEFAULT = NAV_VISIBLE
|
||||||
|
|
||||||
|
|
||||||
|
// basic navigation actions...
|
||||||
|
function nextImage(mode){
|
||||||
|
if(mode == null){
|
||||||
|
mode = NAV_DEFAULT
|
||||||
|
}
|
||||||
|
return centerImage(
|
||||||
|
focusImage(
|
||||||
|
$('.current.image').nextAll('.image' + mode).first()))
|
||||||
|
}
|
||||||
|
function prevImage(mode){
|
||||||
|
if(mode == null){
|
||||||
|
mode = NAV_DEFAULT
|
||||||
|
}
|
||||||
|
return centerImage(
|
||||||
|
focusImage(
|
||||||
|
$('.current.image').prevAll('.image' + mode).first()))
|
||||||
|
}
|
||||||
|
function firstImage(mode){
|
||||||
|
if(mode == null){
|
||||||
|
mode = NAV_DEFAULT
|
||||||
|
}
|
||||||
|
return centerImage(
|
||||||
|
focusImage(
|
||||||
|
$('.current.image').closest('.ribbon').find('.image').filter(mode).first()))
|
||||||
|
}
|
||||||
|
function lastImage(mode){
|
||||||
|
if(mode == null){
|
||||||
|
mode = NAV_DEFAULT
|
||||||
|
}
|
||||||
|
return centerImage(
|
||||||
|
focusImage(
|
||||||
|
$('.current.image').closest('.ribbon').find('.image').filter(mode).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.
|
||||||
|
// XXX get move direction...
|
||||||
|
function prevRibbon(moving, mode){
|
||||||
|
if(mode == null){
|
||||||
|
mode = NAV_DEFAULT
|
||||||
|
}
|
||||||
|
var cur = $('.current.image')
|
||||||
|
// pre marked-only mode...
|
||||||
|
//var target = getImageBefore(cur, cur.closest('.ribbon').prev('.ribbon'))
|
||||||
|
var target = getImageBefore(cur, cur.closest('.ribbon').prevAll('.ribbon' + mode).first(), true)
|
||||||
|
if(moving == 'next' && cur.attr('order') != target.attr('order')){
|
||||||
|
var next = target.nextAll('.image' + mode).first()
|
||||||
|
target = next.length > 0 ? next : target
|
||||||
|
}
|
||||||
|
return centerImage(focusImage(target))
|
||||||
|
}
|
||||||
|
// XXX get move direction...
|
||||||
|
function nextRibbon(moving, mode){
|
||||||
|
if(mode == null){
|
||||||
|
mode = NAV_DEFAULT
|
||||||
|
}
|
||||||
|
var cur = $('.current.image')
|
||||||
|
// pre marked-only mode...
|
||||||
|
//var target = getImageBefore(cur, cur.closest('.ribbon').next('.ribbon'))
|
||||||
|
var target = getImageBefore(cur, cur.closest('.ribbon').nextAll('.ribbon' + mode).first(), true)
|
||||||
|
if(moving == 'next' && cur.attr('order') != target.attr('order')){
|
||||||
|
var next = target.nextAll('.image' + mode).first()
|
||||||
|
target = next.length > 0 ? next : target
|
||||||
|
}
|
||||||
|
return centerImage(focusImage(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// XXX get move direction...
|
||||||
|
function _shiftImageTo(image, direction, moving, force_create_ribbon){
|
||||||
|
if(image == null){
|
||||||
|
image = $('.current.image')
|
||||||
|
}
|
||||||
|
|
||||||
|
// account move for direction...
|
||||||
|
// XXX get the value from some place more logical than the argument...
|
||||||
|
var a = moving == 'prev' ? 'prev' : 'next'
|
||||||
|
var b = moving == 'prev' ? 'next' : 'prev'
|
||||||
|
var target = image[a]('.image')
|
||||||
|
|
||||||
|
target = target.length == 0 ? image[b]() : target
|
||||||
|
|
||||||
|
// XXX should this be in here or coupled later via an event???
|
||||||
|
flashIndicator(direction)
|
||||||
|
|
||||||
|
shiftImage(direction, image, force_create_ribbon)
|
||||||
|
// XXX does this need to be animated???
|
||||||
|
return centerImage(focusImage(target), 'css')
|
||||||
|
}
|
||||||
|
function shiftImageUp(image){
|
||||||
|
return _shiftImageTo(image, 'prev')
|
||||||
|
}
|
||||||
|
function shiftImageDown(image){
|
||||||
|
return _shiftImageTo(image, 'next')
|
||||||
|
}
|
||||||
|
function shiftImageUpNewRibbon(image){
|
||||||
|
return _shiftImageTo(image, 'prev', true)
|
||||||
|
}
|
||||||
|
function shiftImageDownNewRibbon(image){
|
||||||
|
return _shiftImageTo(image, 'prev', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO manual image ordering (shiftLeft/shiftRight functions)
|
||||||
|
// XXX
|
||||||
|
|
||||||
|
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')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Marks...
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**********************************************************************
|
||||||
|
* Event handlers...
|
||||||
|
*/
|
||||||
|
|
||||||
|
// NOTE: this is on purpose done relative...
|
||||||
|
function clickHandler(evt){
|
||||||
|
var img = $(evt.target).closest('.image')
|
||||||
|
|
||||||
|
centerImage(
|
||||||
|
focusImage(img))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
538
ui/index.html
538
ui/index.html
@ -194,544 +194,10 @@
|
|||||||
<script src="lib/jli.js"></script>
|
<script src="lib/jli.js"></script>
|
||||||
<script src="lib/keyboard.js"></script>
|
<script src="lib/keyboard.js"></script>
|
||||||
|
|
||||||
|
<script src="ImageGrid.js"></script>
|
||||||
|
|
||||||
<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
|
|
||||||
|
|
||||||
|
|
||||||
TODO group all actions into an object, referencing the viewer...
|
|
||||||
...this will make this reusable multiple times.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
function flashIndicator(direction){
|
|
||||||
$(direction == 'prev' ? '.up-indicator' : '.down-indicator').fadeIn(200).fadeOut(200)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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"/>')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This will create a set of new images, reusing a list of existing
|
|
||||||
// elements if given.
|
|
||||||
// XXX do we need this???
|
|
||||||
function createImages(need, have){
|
|
||||||
have = have == null ? [] : have
|
|
||||||
|
|
||||||
// we have enough elements in the cache...
|
|
||||||
if(have.length >= need){
|
|
||||||
return $(have.splice(0, need))
|
|
||||||
|
|
||||||
// need to create additional elements...
|
|
||||||
} else {
|
|
||||||
return $(have.toArray().concat(new Array(need - have.length)))
|
|
||||||
.map(function(i, elem){
|
|
||||||
if(elem != null){
|
|
||||||
return elem
|
|
||||||
}
|
|
||||||
return createImage()[0]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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.length == 0){
|
|
||||||
image.prependTo($(ribbon))
|
|
||||||
|
|
||||||
} 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 old_ribbon = image.closest('.ribbon')
|
|
||||||
var ribbon = old_ribbon[direction]('.ribbon')
|
|
||||||
|
|
||||||
// need to create a new ribbon...
|
|
||||||
if(ribbon.length == 0 || force_create_ribbon == true){
|
|
||||||
ribbon = createRibbon()['insert' + (direction == 'prev'
|
|
||||||
? 'Before'
|
|
||||||
: 'After')](old_ribbon)
|
|
||||||
shiftTo(image, ribbon)
|
|
||||||
} else {
|
|
||||||
shiftTo(image, ribbon)
|
|
||||||
}
|
|
||||||
return image
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**********************************************************************
|
|
||||||
* Infinite ribbon machinery
|
|
||||||
*/
|
|
||||||
|
|
||||||
// XXX need mechanics to populate the images or to connect such
|
|
||||||
// functionality...
|
|
||||||
// ...this is to be done in the loader...
|
|
||||||
|
|
||||||
// NOTE: negative left or right will contract the ribbon...
|
|
||||||
function extendRibbon(left, right, ribbon){
|
|
||||||
ribbon = ribbon == null ?
|
|
||||||
$('.current.image').closest('.ribbon')
|
|
||||||
: $(ribbon)
|
|
||||||
left = left == null ? 0 : left
|
|
||||||
right = right == null ? 0 : right
|
|
||||||
var images = ribbon.children('.image')
|
|
||||||
var removed = []
|
|
||||||
var res = {
|
|
||||||
left: [],
|
|
||||||
right: []
|
|
||||||
}
|
|
||||||
|
|
||||||
// truncate...
|
|
||||||
// NOTE: we save the detached elements to reuse them on extending,
|
|
||||||
// if needed...
|
|
||||||
if(left < 0){
|
|
||||||
removed = $(images.splice(0, -left)).detach()
|
|
||||||
}
|
|
||||||
if(right < 0){
|
|
||||||
var l = images.length
|
|
||||||
removed = $(images.splice(l+right, l)).detach()
|
|
||||||
}
|
|
||||||
|
|
||||||
// extend...
|
|
||||||
if (left > 0){
|
|
||||||
res.left = createImages(left, removed).prependTo(ribbon)
|
|
||||||
}
|
|
||||||
if (right > 0){
|
|
||||||
res.right = createImages(right, removed).appendTo(ribbon)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Roll the ribbon n positions to the left.
|
|
||||||
//
|
|
||||||
// NOTE: if n is negative the ribbon will be rolled right.
|
|
||||||
// NOTE: rollRibbon(N, R) is equivalent to extendRibbon(-N, N, R)
|
|
||||||
// NOTE: this will return a single list of relocated elements...
|
|
||||||
function rollRibbon(n, ribbon){
|
|
||||||
var res = extendRibbon(-n, n, ribbon)
|
|
||||||
return n > 0 ? res.right : res.left
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**********************************************************************
|
|
||||||
* User actions
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 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()))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function nextMarkedImage(){
|
|
||||||
return centerImage(
|
|
||||||
focusImage(
|
|
||||||
// pre marked-only mode...
|
|
||||||
//$('.current.image').next('.marked.image')))
|
|
||||||
$('.current.image').next('.marked.image:visible')))
|
|
||||||
}
|
|
||||||
function prevMarkedImage(){
|
|
||||||
return centerImage(
|
|
||||||
focusImage(
|
|
||||||
// pre marked-only mode...
|
|
||||||
//$('.current.image').prev('.marked.image')))
|
|
||||||
$('.current.image').prev('.marked.image:visible')))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
// XXX get move direction...
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
// XXX get move direction...
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// XXX get move direction...
|
|
||||||
function _shiftImageTo(image, direction, moving, force_create_ribbon){
|
|
||||||
if(image == null){
|
|
||||||
image = $('.current.image')
|
|
||||||
}
|
|
||||||
|
|
||||||
// account move for direction...
|
|
||||||
// XXX get the value from some place more logical than the argument...
|
|
||||||
var a = moving == 'prev' ? 'prev' : 'next'
|
|
||||||
var b = moving == 'prev' ? 'next' : 'prev'
|
|
||||||
var target = image[a]('.image')
|
|
||||||
|
|
||||||
target = target.length == 0 ? image[b]() : target
|
|
||||||
|
|
||||||
// XXX should this be in here or coupled later via an event???
|
|
||||||
flashIndicator(direction)
|
|
||||||
|
|
||||||
shiftImage(direction, image, force_create_ribbon)
|
|
||||||
// XXX does this need to be animated???
|
|
||||||
return centerImage(focusImage(target), 'css')
|
|
||||||
}
|
|
||||||
function shiftImageUp(image){
|
|
||||||
return _shiftImageTo(image, 'prev')
|
|
||||||
}
|
|
||||||
function shiftImageDown(image){
|
|
||||||
return _shiftImageTo(image, 'next')
|
|
||||||
}
|
|
||||||
function shiftImageUpNewRibbon(image){
|
|
||||||
return _shiftImageTo(image, 'prev', true)
|
|
||||||
}
|
|
||||||
function shiftImageDownNewRibbon(image){
|
|
||||||
return _shiftImageTo(image, 'prev', false)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// TODO manual image ordering (shiftLeft/shiftRight functions)
|
|
||||||
// XXX
|
|
||||||
|
|
||||||
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')
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**********************************************************************
|
|
||||||
* Event handlers...
|
|
||||||
*/
|
|
||||||
|
|
||||||
// NOTE: this is on purpose done relative...
|
|
||||||
function clickHandler(evt){
|
|
||||||
var img = $(evt.target).closest('.image')
|
|
||||||
|
|
||||||
centerImage(
|
|
||||||
focusImage(img))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// setup...
|
// setup...
|
||||||
$(function(){
|
$(function(){
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user