ImageGrid/ui (gen4)/images.js

526 lines
12 KiB
JavaScript
Raw Normal View History

/**********************************************************************
*
*
*
**********************************************************************/
define(function(require){ var module = {}
console.log('>>> images')
//var DEBUG = DEBUG != null ? DEBUG : true
/*********************************************************************/
// A stub image, also here for documentation...
var STUB_IMAGE_DATA =
module.STUB_IMAGE_DATA = {
// Entity GID...
id: 'STUB-GID',
// Entity type
//
// can be:
// - 'image'
// - 'group'
type: 'image',
// Entity state
//
// can be:
// - 'single'
// - 'grouped'
// - 'hidden'
// - ...
state: 'single',
// Creation time...
ctime: 0,
// Original path...
path: './images/900px/SIZE.jpg',
// Previews...
// NOTE: the actual values depend on specific image and can be
// any size...
preview: {
'150px': './images/150px/SIZE.jpg',
'350px': './images/350px/SIZE.jpg',
'900px': './images/900px/SIZE.jpg',
},
// Classes
// XXX currently unused...
classes: '',
// Image orientation (optional)
//
// can be:
// - null/undefined - same as 0
// - 0 (default) - load as-is
// - 90 - rotate 90deg CW
// - 180 - rotate 180deg CW
// - 270 - rotate 270deg CW (90deg CCW)
//
// NOTE: use orientationExif2ImageGrid(..) to convert from EXIF
// orientation format to ImageGrid format...
orientation: 0,
// Image flip state (optional)
//
// can be:
// - null/undefined
// - array
//
// can contain:
// - 'vertical'
// - 'horizontal'
//
// NOTE: use orientationExif2ImageGrid(..) to convert from EXIF
// orientation format to ImageGrid format...
flipped: null,
// Image comment (optional)
//
// can be:
// - null/undefined
// - string
comment: null,
// List of image tags (optional)
//
// can be:
// - null/undefined
// - array
tags: null,
}
// Calculate relative rotation angle...
//
// Calculate rotation angle relative to from:
// calcRelativeRotation(from, 'cw')
// calcRelativeRotation(from, 'ccw')
// -> 0 | 90 | 180 | 270
//
// Validate an angle:
// calcRelativeRotation(angle)
// calcRelativeRotation(from, angle)
// -> 0 | 90 | 180 | 270
// -> null
//
//
module.calcRelativeRotation = function(from, to){
if(to == null){
to = from
from = 0
}
to = to == 'cw' ? 1
: to == 'ccw' ? -1
: [0, 90, 180, 270].indexOf(to*1) >= 0 ? to*1
: [-90, -180, -270].indexOf(to*1) >= 0 ? 360+(to*1)
: null
// relative rotation...
if(to == 1 || to == -1){
var res = from
res = res == null ? 0 : res*1
res += 90*to
res = res < 0 ? 270
: res > 270 ? 0
: res
// explicit direction...
} else {
var res = to
}
return res
}
/*********************************************************************/
// cmp functions...
// XXX is this the right way to seporate these???
module.makeImageDateCmp = function(data, get){
return function(a, b){
if(get != null){
a = get(a)
b = get(b)
}
b = data[b].ctime
a = data[a].ctime
if(a == b){
return 0
} else if(a < b){
return -1
} else {
return +1
}
}
}
// NOTE: this expects gids...
module.makeImageNameCmp = function(data, get){
return function(a, b){
if(get != null){
a = get(a)
b = get(b)
}
a = data.getImageFileName(a)
b = data.getImageFileName(b)
if(a == b){
return 0
} else if(a < b){
return -1
} else {
return +1
}
}
}
module.makeImageSeqOrNameCmp = function(data, get, seq){
seq = seq == null ? data.getImageNameSeq : seq
return function(a, b){
// XXX this is ugly and non-generic...
if(get != null){
a = get(a)
b = get(b)
}
// XXX this is ugly and non-generic...
var aa = seq.call(data, a)
var bb = seq.call(data, b)
// special case: seq, name
if(typeof(aa) == typeof(123) && typeof(bb) == typeof('str')){ return -1 }
// special case: name, seq
if(typeof(aa) == typeof('str') && typeof(bb) == typeof(123)){ return +1 }
// get the names if there are no sequence numbers...
// NOTE: at this point both a and b are either numbers or NaN's...
a = isNaN(aa) ? data.getImageFileName(a) : aa
b = isNaN(bb) ? data.getImageFileName(b) : bb
// do the actual comparison
if(a == b){
return 0
} else if(a < b){
return -1
} else {
return +1
}
}
}
/*********************************************************************/
var ImagesClassPrototype =
module.ImagesClassPrototype = {
fromJSON: function(data){
return new this().loadJSON(data)
},
}
var ImagesPrototype =
module.ImagesPrototype = {
// Generic iterators...
//
// function format:
// function(key, value, index, object)
//
// this will be set to the value...
//
//
// XXX are these slower than doing it manualy via Object.keys(..)
forEach: function(func){
var i = 0
for(var key in this){
func.call(this[key], key, this[key], i++, this)
}
return this
},
map: function(func){
var res = this.constructor()
var i = 0
for(var key in this){
res[k] = func.call(this[key], key, this[key], i++, this)
}
return res
},
filter: function(func){
var res = this.constructor()
var i = 0
for(var key in this){
if(func.call(this[key], key, this[key], i++, this)){
res[key] = this[key]
}
}
return res
},
keys: function(){
return Object.keys(this)
},
// Build an image index relative to an attribute...
//
// Format:
// {
// <attr-value> : [
// <gid>,
// ...
// ],
// ...
// }
//
// XXX test out the attr list functionality...
makeIndex: function(attr){
var res = {}
attr = attr.constructor !== Array ? [attr] : attr
// buld the index...
var that = this
this.forEach(function(key){
var n = attr.map(function(n){ return that[n] })
n = JSON.stringify(n.length == 1 ? n[0] : n)
// XXX is this the right way to go?
.replace(/^"(.*)"$/g, '$1')
res[n] = n in res ? res[n].concat(key) : [key]
})
return res
},
// Image data helpers...
// XXX see: ribbons.js for details...
getBestPreview: function(gid, size, img_data){
//gid = gid == null ? getImageGID(): gid
//size = size == null ? getVisibleImageSize('max') : size
img_data = img_data == null ? this[gid] : img_data
var s
var url = img_data.path
var preview_size = 'Original'
var p = Infinity
for(var k in img_data.preview){
s = parseInt(k)
if(s < p && s > size){
preview_size = k
p = s
url = img_data.preview[k]
}
}
return {
//url: normalizePath(url),
url: url,
size: preview_size
}
},
// Get image filename...
getImageFileName: function(gid, do_unescape){
do_unescape = do_unescape == null ? true : do_unescape
if(do_unescape){
return unescape(this[gid].path.split('/').pop())
} else {
return this[gid].path.split('/').pop()
}
},
// Get the first sequence of numbers in the file name...
getImageNameSeq: function(gid){
var n = this.getImageFileName(gid)
var r = /([0-9]+)/m.exec(n)
return r == null ? n : parseInt(r[1])
},
// Get the sequence of numbers in the file name but only if it is
// at the filename start...
getImageNameLeadingSeq: function(gid){
var n = this.getImageFileName(gid)
var r = /^([0-9]+)/g.exec(n)
return r == null ? n : parseInt(r[1])
},
// Gid sorters...
// XXX might be a good idea to add caching...
// XXX chainCmp(..) is loaded from lib/jli.js
sortImages: function(gids, cmp, reverse){
gids = gids == null ? Object.keys(this) : gids
cmp = cmp == null ? module.makeImageDateCmp(this) : cmp
cmp = cmp.constructor === Array ? chainCmp(cmp) : cmp
gids = gids.sort(cmp)
gids = reverse ? gids.reverse() : gids
return gids
},
// Shorthands...
// XXX these seem a bit messy...
sortByDate: function(gids, reverse){ return this.sortImages(gids, null, reverse) },
sortByName: function(gids, reverse){
return this.sortImages(gids, module.makeImageNameCmp(this), reverse) },
sortBySeqOrName: function(gids, reverse){
return this.sortImages(gids, module.makeImageSeqOrNameCmp(this), reverse) },
sortByNameXPStyle: function(gids, reverse){
return this.sortImages(gids,
module.makeImageSeqOrNameCmp(this, null, this.getImageNameLeadingSeq),
reverse) },
sortByDateOrSeqOrName: function(gids, reverse){
return this.sortImages(gids, [
module.makeImageDateCmp(this),
module.makeImageSeqOrNameCmp(this)
], reverse)
},
// XXX
sortedImagesByFileNameSeqWithOverflow: function(gids, reverse){
// XXX see ../ui/sort.js
},
// Actions...
// Rotate image...
//
// Rotate image clockwise:
// .rotateImage(target, 'cw')
// -> images
//
// Rotate image counterclockwise:
// .rotateImage(target, 'ccw')
// -> images
//
// Set explicit image rotation angle:
// .rotateImage(target, 0|90|180|270)
// .rotateImage(target, -90|-180|-270)
// -> images
//
// NOTE: target can be a gid or a list of gids...
rotateImage: function(gids, direction){
gids = gids.constructor !== Array ? [gids] : gids
// validate direction...
if(module.calcRelativeRotation(direction) == null){
return this
}
var that = this
gids.forEach(function(key){
var img = that[key]
var o = direction == 'cw' || direction == 'ccw'
? module.calcRelativeRotation(img.orientation, direction)
: direction*1
if(o == 0){
delete img.orientation
} else {
img.orientation = o
}
// account for proportions...
//that.correctImageProportionsForRotation(img)
// XXX this is a bit of an overkill but it will update the
// preview if needed...
//that.updateImage(img)
})
return this
},
// Flip image...
//
// .flipImage(target, 'horizontal')
// .flipImage(target, 'vertical')
// -> images
//
flipImage: function(gids, direction){
gids = gids.constructor !== Array ? [gids] : gids
var that = this
gids.forEach(function(key){
var img = that[key]
var state = img.flipped
state = state == null ? [] : state
// toggle the specific state...
var i = state.indexOf(direction)
if(i >= 0){
state.splice(i, 1)
} else {
state.push(direction)
}
if(state.length == 0){
delete img.flipped
} else {
img.flipped = state
}
})
return this
},
// serialization...
loadJSON: function(data){
data = typeof(data) == typeof('str')
? JSON.parse(data)
: JSON.parse(JSON.stringify(data))
for(var k in data){
this[k] = data[k]
}
return this
},
dumpJSON: function(data){
return JSON.parse(JSON.stringify(this))
},
_reset: function(){
},
}
/*********************************************************************/
// Main Images object...
//
var Images =
module.Images =
function Images(json){
// in case this is called as a function (without new)...
if(this.constructor !== Images){
return new Images(json)
}
// load initial state...
if(json != null){
this.loadJSON(json)
} else {
this._reset()
}
return this
}
Images.__proto__ = ImagesClassPrototype
Images.prototype = ImagesPrototype
Images.prototype.constructor = Images
/*********************************************************************/
// XXX keep this here or move this to a different module???
module.setupActionHandlers = function(context){
// XXX
}
/**********************************************************************
* vim:set ts=4 sw=4 : */
return module })