mirror of
https://github.com/flynx/ImageGrid.git
synced 2025-10-28 18:00:09 +00:00
1484 lines
40 KiB
JavaScript
Executable File
1484 lines
40 KiB
JavaScript
Executable File
/**********************************************************************
|
|
*
|
|
* Data generation 4 implementation.
|
|
*
|
|
*
|
|
**********************************************************************/
|
|
|
|
// General format info...
|
|
//
|
|
// Version format:
|
|
// <major>.<minor>
|
|
//
|
|
// Major version changes mean a significant incompatibility.
|
|
//
|
|
// Minor version changes mean some detail changed and can be handled
|
|
// by it's specific handler seamlessly. Backwards compatible.
|
|
//
|
|
//
|
|
// For more info see:
|
|
// DATA - main data
|
|
// IMAGES - image data
|
|
// MARKED - marks data
|
|
// BOOKMARKS - bookmarks data
|
|
// BOOKMARKS_DATA - bookmarks metadata
|
|
// TAGS - tag data
|
|
//
|
|
//
|
|
// Data format change history:
|
|
// 3.0 - Gen4 DATA format, introduced several backwards incompatible
|
|
// changes:
|
|
// - added ribbon GIDs, .ribbons now is a gid indexed object
|
|
// - added .ribbon_order
|
|
// - added .base ribbon
|
|
// - ribbons are now sparse in memory but can be compact when
|
|
// serialized.
|
|
// - auto-convert from gen1 (no version) and gen3 (2.*) on load
|
|
// 2.3 - Minor update to sorting restrictions
|
|
// - now MARKED and BOOKMARKS do not need to be sorted
|
|
// explicitly in json, they are now sorted as a side-effect
|
|
// of being sparse.
|
|
// This negates some restrictions posed in 2.1, including
|
|
// conversion of 2.0 data.
|
|
// NOTE: TAGS gid sets are still compact lists, thus are
|
|
// actively maintained sorted.
|
|
// ...still thinking of whether making them sparse is
|
|
// worth the work...
|
|
// 2.2 - Minor update to how data is handled and saved
|
|
// - now DATA.current is saved separately in current.json,
|
|
// loading is done from current.json and if not found from
|
|
// data.json.
|
|
// the file is optional.
|
|
// - data, marks, bookmarks, tags are now saved only if updated
|
|
// 2.1 - Minor update to format spec,
|
|
// - MARKED now maintained sorted, live,
|
|
// - will auto-sort marks on load of 2.0 data and change
|
|
// data version to 2.1, will need a re-save,
|
|
// 2.0 - Gen3 data format, still experimental,
|
|
// - completely new and incompatible structure,
|
|
// - use convertDataGen1(..) to convert Gen1 to 2.0
|
|
// - used for my archive, not public,
|
|
// - auto-convert form gen1 on load...
|
|
// none - Gen1 data format, mostly experimental,
|
|
// - has no explicit version set,
|
|
// - not used for real data.
|
|
//
|
|
//
|
|
// NOTE: Gen1 and Gen3 refer to code generations rather than data format
|
|
// iterations, Gen2 is skipped here as it is a different project
|
|
// (PortableMag) started on the same code base as ImageGrid.Viewer
|
|
// generation 1 and advanced from there, back-porting some of the
|
|
// results eventually formed Gen3...
|
|
//
|
|
//
|
|
/*********************************************************************/
|
|
//
|
|
// TODO save current crop/state as JSON (named)...
|
|
// TODO save current order (named)...
|
|
// TODO auto-save manual sort -- on re-sort...
|
|
//
|
|
//
|
|
/*********************************************************************/
|
|
|
|
// Data class methods and API...
|
|
//
|
|
var DataClassPrototype = {
|
|
// NOTE: we consider the input list sorted...
|
|
fromList: function(list){
|
|
var res = new Data()
|
|
// XXX make a real ribbon gid...
|
|
var gid = res.newGid('R')
|
|
res.order = list
|
|
res.ribbon_order.push(gid)
|
|
res.ribbons[gid] = list.slice()
|
|
return res
|
|
},
|
|
// XXX is this the right way to construct data???
|
|
fromJSON: function(data){
|
|
return new Data().loadJSON(data)
|
|
},
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************/
|
|
|
|
// Data object methods and API...
|
|
//
|
|
var DataPrototype = {
|
|
|
|
/*****************************************************************/
|
|
//
|
|
// Base Terminology:
|
|
// - gen1 methods
|
|
// - use the data API/Format
|
|
// - use other gen1 methods
|
|
// - gen2 methods
|
|
// - do NOT use the data API/Format
|
|
// - use other methods from any of gen1 and gen2
|
|
//
|
|
// NOTE: only gen2 methods are marked.
|
|
//
|
|
//
|
|
/****************************************************** Format ***/
|
|
//
|
|
// .current (gid)
|
|
// gid of the current image
|
|
//
|
|
// .base (gid)
|
|
// gid of the base ribbon
|
|
//
|
|
// .order
|
|
// List of image gids setting the image order
|
|
//
|
|
// format:
|
|
// [ gid, .. ]
|
|
//
|
|
// NOTE: this list may contain gids not loaded at the moment,
|
|
// a common case for this is when data is cropped.
|
|
//
|
|
// .ribbon_order
|
|
// List of ribbon gids setting the ribbon order.
|
|
//
|
|
// format:
|
|
// [ gid, .. ]
|
|
//
|
|
// .ribbons
|
|
// Dict of ribbons, indexed by ribbon gid, each ribbon is a
|
|
// sparse list of image gids.
|
|
//
|
|
// format:
|
|
// { gid: [ gid, .. ], .. }
|
|
//
|
|
// NOTE: ribbons are sparse...
|
|
// NOTE: ribbons can be compact when serialized...
|
|
//
|
|
//
|
|
/******************************************************* Utils ***/
|
|
|
|
// Make a sparse list of image gids...
|
|
//
|
|
// This uses .order as the base for ordering the list.
|
|
//
|
|
// If target is given then it will get updated with the input gids.
|
|
//
|
|
// NOTE: this can be used to re-sort sections of a target ribbon,
|
|
// but care must be taken not to overwrite existing data...
|
|
// NOTE: if target is given some items in it might get pushed out
|
|
// by the new gids, especially if target is out of sync with
|
|
// .order, this can be avoided by setting keep_target_items
|
|
// (see next for more info).
|
|
// Another way to deal with this is to .makeSparseImages(target)
|
|
// before using it as a target.
|
|
// NOTE: if keep_target_items is set items that are overwritten in
|
|
// the target will get pushed to gids.
|
|
// This flag has no effect if target is an empty list (default).
|
|
makeSparseImages: function(gids, target, keep_target_items){
|
|
target = target == null ? [] : target
|
|
keep_target_items = keep_target_items == null ? false : keep_target_items
|
|
order = this.order
|
|
|
|
// avoid directly updating self...
|
|
if(gids === target){
|
|
gids = gids.slice()
|
|
}
|
|
|
|
gids.forEach(function(e){
|
|
i = order.indexOf(e)
|
|
if(i >= 0){
|
|
var o = target[i]
|
|
// save overwritten target items if keep_target_items
|
|
// is set...
|
|
if(keep_target_items
|
|
&& o != null
|
|
// if the items is already in gids, forget it...
|
|
// NOTE: this is to avoid juggling loops...
|
|
&& gids.indexOf(o) < 0){
|
|
gids.push(o)
|
|
}
|
|
|
|
target[i] = e
|
|
}
|
|
})
|
|
|
|
return target
|
|
},
|
|
|
|
// Remove duplicate items from list in-place...
|
|
//
|
|
// NOTE: only the first occurrence is kept...
|
|
// NOTE: this is slow-ish...
|
|
removeDuplicates: function(lst, skip_undefined){
|
|
skip_undefined = skip_undefined == null ? true : skip_undefined
|
|
for(var i=0; i < lst.length; i++){
|
|
if(skip_undefined && lst[i] == null){
|
|
continue
|
|
}
|
|
if(lst.indexOf(lst[i]) != i){
|
|
lst.splice(i, 1)
|
|
i -= 1
|
|
}
|
|
}
|
|
return lst
|
|
},
|
|
|
|
// Generate a unique GID...
|
|
//
|
|
// XXX generate a real gid...
|
|
newGid: function(prefix){
|
|
prefix = prefix == null ? 'G' : prefix
|
|
var gid = prefix + Date.now()
|
|
while(this.ribbon_order.indexOf(gid) >= 0
|
|
|| this.order.indexOf(gid) >= 0
|
|
|| this._gid_cache.indexOf(gid) >= 0){
|
|
gid = prefix + Date.now()
|
|
}
|
|
// XXX not sure if this is a good idea...
|
|
this._gid_cache.push(gid)
|
|
return gid
|
|
},
|
|
|
|
|
|
|
|
/*********************************************** Introspection ***/
|
|
|
|
// Get image
|
|
//
|
|
// Get current image:
|
|
// .getImage()
|
|
// .getImage('current')
|
|
// -> gid
|
|
//
|
|
// Check if image is loaded/exists:
|
|
// .getImage(gid|order)
|
|
// .getImage(gid|order, list|ribbon)
|
|
// -> gid
|
|
// -> null
|
|
// NOTE: null is returned if image does not exist.
|
|
// NOTE: the second argument must not be an int (ribbon order)
|
|
// to avoid conflict with the offset case below.
|
|
// NOTE: image order can be negative, thus getting an image
|
|
// from the tail.
|
|
//
|
|
// Get first or last image in ribbon:
|
|
// .getImage('first'[, ribbon])
|
|
// .getImage('last'[, ribbon])
|
|
// -> gid
|
|
// -> null (XXX ???)
|
|
// NOTE: the second argument must be .getRibbon(..) compatible.
|
|
// NOTE: to get global first/last image use the index, e.g.:
|
|
// .getImage(0) / .getImage(-1)
|
|
//
|
|
// Get image closest to current in list/ribbon:
|
|
// .getImage(list|ribbon[, 'before'|'after'])
|
|
// -> gid
|
|
// -> null
|
|
// NOTE: null is returned if there is no image before/after the
|
|
// current image in the given list/ribbon, e.g. the
|
|
// current image is first/last resp.
|
|
// NOTE: 'before' is default.
|
|
// NOTE: the first argument must not be a number.
|
|
//
|
|
// Get image closest to current or a specific image:
|
|
// .getImage('before'[, list|ribbon])
|
|
// .getImage(gid|order, 'before'[, list|ribbon])
|
|
// -> gid
|
|
// -> null
|
|
// .getImage('after'[, list|ribbon])
|
|
// .getImage(gid|order, 'after'[, list|ribbon])
|
|
// -> gid
|
|
// -> null
|
|
// NOTE: null is returned if there is no image before/after the
|
|
// current image in the given list/ribbon, e.g. the
|
|
// current image is first/last resp.
|
|
// NOTE: in both the above cases if gid|order is found explicitly
|
|
// it will be returned.
|
|
//
|
|
// Get image at an offset from a given image:
|
|
// .getImage(gid|order, offset[, list|ribbon])
|
|
// -> gid
|
|
// -> null
|
|
// NOTE: null is returned if there is no image at given offset.
|
|
// NOTE: offset is calculated within the same ribbon...
|
|
//
|
|
// NOTE: If gid|order is not given, current image is assumed.
|
|
// Similarly, if list|ribbon is not given then the current
|
|
// ribbon is used.
|
|
// NOTE: if input gid is invalid this will return -1 (XXX is this good???)
|
|
// NOTE: the folowing are equivalent:
|
|
// D.getImage('current', -1, R)
|
|
// D.getImage('before', R)
|
|
// D.getImage('current', 'before', R)
|
|
// where D is a Data object and R a ribbon id/index different
|
|
// from the current ribbon (see next note for details).
|
|
// NOTE: in before/after modes, if the target image is found then it
|
|
// will be returned, use offset to explicitly get the image
|
|
// before/after target.
|
|
//
|
|
// XXX most of the complexity here comes from argument DSL parsing,
|
|
// might be good to revise argument syntax and handling...
|
|
getImage: function(target, mode, list){
|
|
// no args...
|
|
if(target == null && mode == null && list == null){
|
|
return this.current
|
|
}
|
|
|
|
// current image shorthand...
|
|
if(target == 'current'){
|
|
target = this.current
|
|
}
|
|
|
|
// order -> gid special case...
|
|
if(typeof(target) == typeof(123)){
|
|
if(target >= this.order.length){
|
|
return null
|
|
}
|
|
target = target < 0 ?
|
|
this.order[this.order.length+target]
|
|
: this.order[target]
|
|
}
|
|
|
|
// first/last special case...
|
|
if(target == 'first'){
|
|
list = this.ribbons[this.getRibbon(mode)]
|
|
for(var res in list){
|
|
return list[res]
|
|
}
|
|
return null
|
|
}
|
|
if(target == 'last'){
|
|
list = this.ribbons[this.getRibbon(mode)]
|
|
for(var i=list.length; i >= 0; i--){
|
|
if(list[i] != null){
|
|
return list[i]
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
// normalize target...
|
|
if(target in this.ribbons || target.constructor.name == 'Array'){
|
|
list = target
|
|
target = this.current
|
|
} else if(target == 'before' || target == 'after'){
|
|
list = mode
|
|
mode = target
|
|
target = this.current
|
|
}
|
|
|
|
// normalize mode...
|
|
if(mode != null
|
|
&& mode.constructor.name == 'Array'
|
|
|| mode in this.ribbons){
|
|
list = mode
|
|
mode = null
|
|
}
|
|
// relative mode and offset...
|
|
if(typeof(mode) == typeof(123)){
|
|
var offset = mode
|
|
mode = offset < 0 ? 'before'
|
|
: offset > 0 ? 'after'
|
|
: mode
|
|
offset = Math.abs(offset)
|
|
} else {
|
|
var offset = 0
|
|
mode = mode == null ? 'before' : mode
|
|
}
|
|
|
|
var i = this.order.indexOf(target)
|
|
|
|
// invalid gid...
|
|
// XXX need a better way to report errors...
|
|
if(i == -1){
|
|
return -1
|
|
}
|
|
|
|
// normalize the list to a sparse list of gids...
|
|
list = list == null ?
|
|
this.ribbons[this.getRibbon(target)]
|
|
: list.constructor.name == 'Array' ?
|
|
this.makeSparseImages(list)
|
|
: this.ribbons[this.getRibbon(list)]
|
|
|
|
var res = list[i]
|
|
// we have a direct hit...
|
|
if(res != null && offset == 0){
|
|
return res
|
|
}
|
|
|
|
// prepare for the search...
|
|
if(mode == 'before'){
|
|
var step = -1
|
|
|
|
} else if(mode == 'after'){
|
|
var step = 1
|
|
|
|
// strict -- no hit means there is no point in searching...
|
|
} else {
|
|
return null
|
|
}
|
|
|
|
// skip the current elem...
|
|
i += step
|
|
// get the first non-null, also accounting for offset...
|
|
// NOTE: we are using this.order.length here as ribbons might
|
|
// be truncated...
|
|
for(; i >= 0 && i < this.order.length; i+=step){
|
|
var cur = list[i]
|
|
if(cur == null){
|
|
continue
|
|
}
|
|
offset -= 1
|
|
if(offset <= 0){
|
|
return cur
|
|
}
|
|
}
|
|
// target is either first or last...
|
|
return null
|
|
},
|
|
|
|
// Get image order...
|
|
//
|
|
// This is similar to .getImage(..) but adds an optional context.
|
|
//
|
|
// The context can be:
|
|
// 'all' - global order (default)
|
|
// 'loaded' - order in loaded images
|
|
// 'ribbon' - order in ribbon
|
|
//
|
|
// NOTE: acquiring the gid is exactly the same as with .getImage(..)
|
|
// next, that gid is used to get the order, in case of the
|
|
// 'ribbon' context, the order is relative to the ribbon where
|
|
// the image is located.
|
|
// To get the order of an image in a different ribbon, get an
|
|
// appropriate before/after image in that ribbon and get it's
|
|
// order.
|
|
getImageOrder: function(context, target, mode, list){
|
|
if(context == 'loaded'){
|
|
return this.getImages('loaded').indexOf(this.getImage(target, mode, list))
|
|
|
|
} else if(context == 'ribbon'){
|
|
var gid = this.getImage(target, mode, list)
|
|
return this.getImages(gid).indexOf(gid)
|
|
|
|
} else if(context == 'all'){
|
|
return this.order.indexOf(this.getImage(target, mode, list))
|
|
}
|
|
|
|
return this.order.indexOf(this.getImage(context, target, mode))
|
|
},
|
|
|
|
// Get a list of image gids...
|
|
//
|
|
// Get list of loaded images:
|
|
// .getImages()
|
|
// .getImages('loaded')
|
|
// -> list
|
|
//
|
|
// Get all images, both loaded and not:
|
|
// .getImages('all')
|
|
// -> list
|
|
//
|
|
// Get list of images in current ribbon:
|
|
// .getImages('current')
|
|
// -> list
|
|
//
|
|
// Filter the list and return only loaded images from it:
|
|
// .getImages(list)
|
|
// -> list
|
|
//
|
|
// Get loaded images from ribbon:
|
|
// .getImages(gid|order|ribbon)
|
|
// -> list
|
|
//
|
|
// Get count gids around (default) before or after the target image:
|
|
// .getImages(gid|order|ribbon, count)
|
|
// .getImages(gid|order|ribbon, count, 'around')
|
|
// .getImages(gid|order|ribbon, count, 'after')
|
|
// .getImages(gid|order|ribbon, count, 'before')
|
|
// -> list
|
|
//
|
|
//
|
|
// If no image is given the current image/ribbon is assumed as target.
|
|
//
|
|
// This will always return count images if there is enough images
|
|
// in ribbon from the requested sides of target.
|
|
//
|
|
// NOTE: this expects ribbon order and not image order.
|
|
// NOTE: if count is even, it will return 1 more image to the left
|
|
// (before) the target.
|
|
// NOTE: if the target is present in the image-set it will be included
|
|
// in the result regardless of mode...
|
|
// NOTE: to get a set of image around a specific (non-current) image
|
|
// in a specific (non-current) ribbon first get an apropriate image
|
|
// via. .getImage(..) and then get the list with this...
|
|
// D.getImages(D.getImage(gid, ribbon_gid), N, 'around')
|
|
//
|
|
// XXX for some reason negative target number (ribbon number)
|
|
// breaks this...
|
|
getImages: function(target, count, mode){
|
|
target = (target == null && count == null) ? 'loaded' : target
|
|
mode = mode == null ? 'around' : mode
|
|
var list
|
|
|
|
// normalize target and build the source list...
|
|
|
|
// 'current' ribbon...
|
|
target = target == 'current' ? this.current : target
|
|
|
|
// get all gids...
|
|
if(target == 'all'){
|
|
list = this.order
|
|
target = null
|
|
|
|
// get loaded only gids...
|
|
} else if(target == 'loaded'){
|
|
var res = []
|
|
var ribbons = this.ribbons
|
|
for(var k in ribbons){
|
|
this.makeSparseImages(ribbons[k], res)
|
|
}
|
|
list = res.compact()
|
|
target = null
|
|
|
|
// filter out the unloaded gids from given list...
|
|
} else if(target != null && target.constructor.name == 'Array'){
|
|
var loaded = this.getImages('loaded')
|
|
list = target.filter(function(e){
|
|
return loaded.indexOf(e) >= 0
|
|
})
|
|
target = null
|
|
|
|
// target is ribbon gid...
|
|
} else if(target in this.ribbons){
|
|
list = this.ribbons[target]
|
|
}
|
|
|
|
// NOTE: list can be null if we got an image gid or ribbon order...
|
|
// get the ribbon gids...
|
|
if(list == null){
|
|
list = this.ribbons[this.getRibbon(target)]
|
|
list = list != null ? list.compact() : []
|
|
}
|
|
|
|
if(count == null){
|
|
return list.slice()
|
|
}
|
|
|
|
target = this.getImage(target)
|
|
var i = list.indexOf(target)
|
|
|
|
// prepare to slice the list...
|
|
if(mode == 'around'){
|
|
count = count/2
|
|
var from = i - Math.floor(count)
|
|
var to = i + Math.ceil(count)
|
|
|
|
} else if(mode == 'before'){
|
|
// NOTE: we are shifting by 1 to include the target...
|
|
var from = (i - count) + 1
|
|
var to = i + 1
|
|
|
|
} else if(mode == 'after'){
|
|
var from = i
|
|
var to = i + count
|
|
|
|
} else {
|
|
// XXX bad mode....
|
|
return null
|
|
}
|
|
|
|
// get the actual bounds...
|
|
from = Math.max(0, from)
|
|
to = Math.min(list.length-1, to)
|
|
|
|
return list.slice(from, to)
|
|
},
|
|
|
|
// Get ribbon...
|
|
//
|
|
// Get current ribbon:
|
|
// .getRibbon()
|
|
// .getRibbon('current')
|
|
// -> ribbon gid
|
|
//
|
|
// Get base ribbon:
|
|
// .getRibbon('base')
|
|
// -> base ribbon gid
|
|
//
|
|
// Get ribbon by target image/ribbon:
|
|
// .getRibbon(ribbon|order|gid)
|
|
// -> ribbon gid
|
|
// -> null -- invalid target
|
|
// NOTE: if ribbon gid is given this will return it as-is.
|
|
//
|
|
// Get ribbon before/after target:
|
|
// .getRibbon(ribbon|order|gid, 'before')
|
|
// .getRibbon(ribbon|order|gid, 'after')
|
|
// -> ribbon gid
|
|
// -> null -- invalid target
|
|
//
|
|
// Get ribbon at offset from target:
|
|
// .getRibbon(ribbon|order|gid, offset)
|
|
// -> ribbon gid
|
|
// -> null -- invalid target
|
|
//
|
|
//
|
|
// NOTE: this expects ribbon order and not image order.
|
|
getRibbon: function(target, offset){
|
|
target = target == null ? this.current : target
|
|
offset = offset == null ? 0 : offset
|
|
offset = offset == 'before' ? -1 : offset
|
|
offset = offset == 'after' ? 1 : offset
|
|
|
|
// special keywords...
|
|
if(target == 'base'){
|
|
return this.base
|
|
} else if(target == 'current'){
|
|
target = this.current
|
|
}
|
|
|
|
var ribbons = this.ribbons
|
|
var o
|
|
|
|
// we got a ribbon gid...
|
|
if(target in ribbons){
|
|
o = this.ribbon_order.indexOf(target)
|
|
|
|
// we got a ribbon order...
|
|
} else if(typeof(target) == typeof(123)){
|
|
o = target
|
|
|
|
// image gid...
|
|
} else {
|
|
var i = this.order.indexOf(target)
|
|
var k
|
|
for(k in ribbons){
|
|
if(ribbons[k][i] != null){
|
|
o = this.ribbon_order.indexOf(k)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if(o != null){
|
|
o += offset
|
|
if(o < 0 || o > this.ribbon_order.length){
|
|
// ERROR: offset out of bounds...
|
|
return null
|
|
}
|
|
return this.ribbon_order[o]
|
|
}
|
|
|
|
// ERROR: invalid target...
|
|
return null
|
|
},
|
|
// same as .getRibbon(..) but returns ribbon order...
|
|
getRibbonOrder: function(target, offset){
|
|
return this.ribbon_order.indexOf(this.getRibbon(target, offset))
|
|
},
|
|
|
|
|
|
|
|
/******************************************************** Edit ***/
|
|
|
|
// Focus an image -- make it current...
|
|
//
|
|
// This is signature compatible with .getImage(..), see it for more
|
|
// info...
|
|
focusImage: function(target, mode, list){
|
|
var current = this.getImage(target, mode, list)
|
|
if(this.order.indexOf(current) >= 0){
|
|
this.current = current
|
|
}
|
|
return this
|
|
},
|
|
|
|
// Shorthand methods...
|
|
//
|
|
// XXX should these be here???
|
|
focusBaseRibbon: function(){ return this.focusImage(this.base) },
|
|
focusImageOffset: function(offset){
|
|
offset = offset == null ? 0 : offset
|
|
|
|
var min = -this.getImageOrder('ribbon')
|
|
var max = this.getImages('current').length-1
|
|
|
|
offset = Math.max(min, Math.min(max, offset))
|
|
|
|
return this.focusImage('current', offset)
|
|
},
|
|
nextImage: function(){ return this.focusImageOffset(1) }, // Gen2
|
|
prevImage: function(){ return this.focusImageOffset(-1) }, // Gen2
|
|
firstImage: function(){ return this.focusImage('first') }, // Gen2
|
|
lastImage: function(){ return this.focusImage('last') }, // Gen2
|
|
focusRibbonOffset: function(offset){
|
|
var c = this.getRibbonOrder()
|
|
var t = c+offset
|
|
t = Math.max(0, Math.min(this.ribbon_order.length-1, t))
|
|
|
|
// NOTE: the modes here are different for directions to balance
|
|
// up/down navigation...
|
|
return this.focusImage('current', (t < c ? 'after' : 'before'), t)
|
|
},
|
|
nextRibbon: function(){ return this.focusRibbonOffset(1) }, // Gen2
|
|
prevRibbon: function(){ return this.focusRibbonOffset(-1) }, // Gen2
|
|
|
|
// Set base ribbon...
|
|
//
|
|
// This is signature compatible with .getRibbon(..), see it for more
|
|
// info...
|
|
setBase: function(target, offset){
|
|
var base = this.getRibbon(target, offset)
|
|
if(base in this.ribbons){
|
|
this.base = base
|
|
}
|
|
return this
|
|
},
|
|
|
|
// Create empty ribbon...
|
|
//
|
|
// If mode is 'below' this will create a new ribbon below the target,
|
|
// otherwise the new ribbon will be created above.
|
|
newRibbon: function(target, mode){
|
|
var gid = this.newGid('R')
|
|
var i = this.getRibbonOrder(target)
|
|
|
|
i = mode == 'below' ? i+1 : i
|
|
|
|
this.ribbon_order.splice(i, 0, gid)
|
|
this.ribbons[gid] = []
|
|
|
|
return gid
|
|
},
|
|
|
|
// Merge ribbons
|
|
//
|
|
// .mergeRibbons('all')
|
|
// .mergeRibbons(ribbon, ribbon, ...)
|
|
// -> data
|
|
//
|
|
// If 'all' is the first argument, this will merge all the ribbons.
|
|
//
|
|
// This will merge the ribbons into the first.
|
|
mergeRibbons: function(target){
|
|
var targets = target == 'all' ? this.ribbon_order.slice() : arguments
|
|
var base = targets[0]
|
|
|
|
for(var i=1; i < targets.length; i++){
|
|
var r = targets[i]
|
|
this.makeSparseImages(this.ribbons[r], this.ribbons[base])
|
|
|
|
delete this.ribbons[r]
|
|
this.ribbon_order.splice(this.ribbon_order.indexOf(r), 1)
|
|
}
|
|
|
|
// update .base if that gid got merged in...
|
|
if(this.ribbon_order.indexOf(this.base) < 0){
|
|
this.base = base
|
|
}
|
|
|
|
return this
|
|
},
|
|
|
|
// Sort images in ribbons via .order...
|
|
//
|
|
// NOTE: this sorts in-place
|
|
sortImages: function(){
|
|
var ribbons = this.ribbons
|
|
for(k in ribbons){
|
|
ribbons[k] = this.makeSparseImages(ribbons[k])
|
|
}
|
|
return this
|
|
},
|
|
|
|
// Reverse .order and all the ribbons...
|
|
//
|
|
// NOTE: this sorts in-place
|
|
//
|
|
// NOTE: this depends on setting length of an array, it works in
|
|
// Chrome but will it work the same in other systems???
|
|
reverseImages: function(){
|
|
this.order.reverse()
|
|
var l = this.order.length
|
|
for(k in ribbons){
|
|
// XXX will this work everywhere???
|
|
// NOTE: ribbons may be truncated, so we need to explicitly
|
|
// set their length...
|
|
ribbons[k].length = l
|
|
ribbons[k].reverse()
|
|
}
|
|
return this
|
|
},
|
|
|
|
// Shift image...
|
|
//
|
|
// Shift image to target position:
|
|
// .shiftImage(from, gid|order|ribbon)
|
|
// .shiftImage(from, gid|order|ribbon, 'before')
|
|
// .shiftImage(from, gid|order|ribbon, 'after')
|
|
// -> data
|
|
//
|
|
// Shift image by offset:
|
|
// .shiftImage(from, offset, 'offset')
|
|
// -> data
|
|
//
|
|
//
|
|
// order is expected to be ribbon order.
|
|
//
|
|
// from must be one of:
|
|
// - a .getImage(..) compatible object. usually an image gid, order,
|
|
// or null, see .getImage(..) for more info.
|
|
// - a list of .getImage(..) compatible objects.
|
|
//
|
|
// When shifting a set of gids horizontally this will pack them
|
|
// together in order.
|
|
//
|
|
//
|
|
// NOTE: this will not create new ribbons.
|
|
// NOTE: .getImage(..) defaults to 'before' thus this to defaults
|
|
// to 'after'
|
|
//
|
|
// XXX check for corner cases:
|
|
// - first/last in ribbon offset
|
|
// - first/last in order offset
|
|
// - first/last ribbon up/down
|
|
// do we create new ribbons and round???
|
|
// XXX when shifting groups of images we are using the first as a
|
|
// base, should we use last as a base for right shifting???
|
|
// ...another way to go could be using current as a reference
|
|
// XXX test vertical..
|
|
shiftImage: function(from, target, mode){
|
|
from = from == null ? this.current : from
|
|
from = from == 'current' ? this.current : from
|
|
from = from.constructor.name != 'Array' ? [from] : from
|
|
mode = mode == null ? 'after' : mode
|
|
var ribbons = this.ribbons
|
|
var order = this.order
|
|
|
|
first = this.getImage(from[0])
|
|
var f = order.indexOf(first)
|
|
|
|
// target is an offset...
|
|
if(mode == 'offset'){
|
|
// XXX check that we can place an elem at first and last positions...
|
|
var t = this.getImageOrder(this.getImage(first, target))
|
|
|
|
var ribbon = this.getRibbon(first)
|
|
|
|
// target is ribbon order...
|
|
// XXX range checking???
|
|
} else if(typeof(target) == typeof(123)){
|
|
var t = f
|
|
|
|
// normalize the target...
|
|
// XXX is this the correct way to go???
|
|
target = Math.max(0, Math.min(this.ribbon_order.length-1, target))
|
|
|
|
var ribbon = this.ribbon_order[target]
|
|
|
|
// target is a ribbon gid...
|
|
} else if(target in this.ribbons){
|
|
var t = f
|
|
|
|
var ribbon = target
|
|
|
|
// target is a gid or order...
|
|
} else {
|
|
target = this.getImage(target)
|
|
var t = order.indexOf(target)
|
|
t = mode == 'after' ? t+1 : t
|
|
|
|
var ribbon = this.getRibbon(target)
|
|
}
|
|
|
|
var from_ribbon = this.getRibbon(first)
|
|
|
|
// do vertical shift...
|
|
// NOTE: image order here is not changed...
|
|
if(ribbon != from_ribbon || from.length > 1){
|
|
var that = this
|
|
from.forEach(function(e){
|
|
var i = order.indexOf(e)
|
|
var from_ribbon = that.getRibbon(e)
|
|
|
|
that.ribbons[ribbon][i] = e
|
|
delete that.ribbons[from_ribbon][i]
|
|
})
|
|
}
|
|
|
|
// do horizontal shift...
|
|
// NOTE: images are packed horizontally together...
|
|
if(f != t){
|
|
for(var i=0; i<from.length; i++){
|
|
f = order.indexOf(from[i])
|
|
|
|
// update order...
|
|
order.splice(t+i, 0, this.order.splice(f, 1)[0])
|
|
|
|
// update ribbons...
|
|
for(k in ribbons){
|
|
ribbons[k].splice(t+i, 0, ribbons[k].splice(f, 1)[0])
|
|
}
|
|
}
|
|
}
|
|
|
|
return this
|
|
},
|
|
|
|
// Shorthand actions...
|
|
//
|
|
// NOTE: shiftImageUp/shiftImageDown will create new ribbons when
|
|
// shifting from first/last ribbons respectively.
|
|
// NOTE: none of these change .current
|
|
//
|
|
// XXX should these be here??
|
|
shiftImageLeft: function(gid){ return this.shiftImage(gid, -1, 'offset') }, // Gen2
|
|
shiftImageRight: function(gid){ return this.shiftImage(gid, 1, 'offset') }, // Gen2
|
|
// XXX test...
|
|
shiftImageUp: function(gid){
|
|
var g = gid.constructor.name == 'Array' ? gid[0] : gid
|
|
// check if we need to create a ribbon here...
|
|
if(this.getRibbonOrder(g) == 0){
|
|
this.newRibbon(g)
|
|
}
|
|
return this.shiftImage(gid, this.getRibbonOrder(g)-1)
|
|
},
|
|
// XXX test...
|
|
shiftImageDown: function(gid){
|
|
var g = gid.constructor.name == 'Array' ? gid[0] : gid
|
|
// check if we need to create a ribbon here...
|
|
if(this.getRibbonOrder(g) == this.ribbon_order.length-1){
|
|
this.newRibbon(g, 'below')
|
|
}
|
|
return this.shiftImage(gid, this.getRibbonOrder(g)+1)
|
|
},
|
|
|
|
// Shift ribbon vertically...
|
|
//
|
|
// Shift ribbon to position...
|
|
// .shiftRibbon(gid, gid)
|
|
// .shiftRibbon(gid, gid, 'before')
|
|
// .shiftRibbon(gid, gid, 'after')
|
|
// -> data
|
|
// NOTE: 'before' is default.
|
|
//
|
|
// Shift ribbon by offset...
|
|
// .shiftRibbon(gid, offset, 'offset')
|
|
// -> data
|
|
//
|
|
//
|
|
// XXX test...
|
|
shiftRibbon: function(gid, to, mode){
|
|
var i = this.getRibbonOrder(gid)
|
|
|
|
// to is an offset...
|
|
if(mode == 'offset'){
|
|
to = i + to
|
|
|
|
// to is a gid...
|
|
} else {
|
|
to = this.getRibbonOrder(to)
|
|
if(mode == 'after'){
|
|
to += 1
|
|
}
|
|
}
|
|
|
|
// normalize to...
|
|
to = Math.max(0, Math.min(this.ribbon_order.length-1, to))
|
|
|
|
this.ribbon_order.splice(to, 0, this.ribbon_order.splice(i, 1)[0])
|
|
|
|
return this
|
|
},
|
|
|
|
// Shorthand actions...
|
|
//
|
|
// XXX should these be here??
|
|
shiftRibbonUp: function(gid){ return this.shiftRibbon(gid, -1, 'offset') }, // Gen2
|
|
shiftRibbonDown: function(gid){ return this.shiftRibbon(gid, 1, 'offset') }, // Gen2
|
|
|
|
|
|
|
|
/********************************************* Data-level edit ***/
|
|
|
|
// Split data into sections...
|
|
//
|
|
// .split(target, ..)
|
|
// .split([target, ..])
|
|
// -> list
|
|
//
|
|
//
|
|
// This will "split" the data just before each target, i.e. target N
|
|
// will get the head of N+1 section.
|
|
//
|
|
// Data Data Data
|
|
// [...oooooXooooo...] -> [...ooooo] [Xooooo...]
|
|
// ^ ^
|
|
// target target
|
|
//
|
|
//
|
|
// Special case: target is .order.length
|
|
// This will indicate that the last data section will be empty.
|
|
//
|
|
// Data Data Data
|
|
// [...oooooooooooo] -> [...oooooooooooo] []
|
|
// ^ ^
|
|
// target target
|
|
//
|
|
//
|
|
// Targets MUST be listed in order of occurrence.
|
|
//
|
|
// NOTE: this will not affect the original data object...
|
|
// NOTE: this might result in empty ribbons, if no images are in a
|
|
// given ribbon in the section to be split...
|
|
// NOTE: target must be a .getImage(..) compatible value.
|
|
// NOTE: if no target is given this will assume the current image.
|
|
split: function(target){
|
|
if(arguments.length > 1){
|
|
target = Array.apply(null, arguments)
|
|
} else if(target == null
|
|
|| target.constructor.name != 'Array'){
|
|
target = [ target ]
|
|
}
|
|
var res = []
|
|
var tail = this.clone()
|
|
var that = this
|
|
|
|
// NOTE: we modify tail here on each iteration...
|
|
target.forEach(function(i){
|
|
i = i >= that.order.length
|
|
? tail.order.length
|
|
: tail.getImageOrder(that.getImage(i))
|
|
var n = new Data()
|
|
n.base = tail.base
|
|
n.ribbon_order = tail.ribbon_order.slice()
|
|
n.order = tail.order.splice(0, i)
|
|
for(var k in tail.ribbons){
|
|
n.ribbons[k] = tail.ribbons[k].splice(0, i)
|
|
}
|
|
n.current = n.order.indexOf(tail.current) >= 0 ? tail.current : n.order[0]
|
|
|
|
res.push(n)
|
|
})
|
|
|
|
// update .current of the last element...
|
|
tail.current = tail.order.indexOf(tail.current) >= 0 ? tail.current : tail.order[0]
|
|
|
|
res.push(tail)
|
|
return res
|
|
},
|
|
|
|
// Join data objects into the current object...
|
|
//
|
|
// .join(data, ..)
|
|
// .join([ data, .. ])
|
|
// -> data with all the other data objects merged in, aligned
|
|
// via base ribbon.
|
|
//
|
|
// .join(align, data, ..)
|
|
// .join(align, [ data, .. ])
|
|
// -> data with all the other data objects merged in, via align
|
|
//
|
|
//
|
|
// align can be:
|
|
// 'base' - base ribbons (default)
|
|
// 'top' - top ribbons
|
|
// 'bottom' - bottom ribbons
|
|
//
|
|
// NOTE: data can be both a list of arguments or an array.
|
|
// NOTE: this will merge the items in-place, into the method's object;
|
|
// if it is needed to keep the original intact, just .clone() it...
|
|
//
|
|
// XXX test more complex cases...
|
|
join: function(){
|
|
var args = Array.apply(null, arguments)
|
|
var align = typeof(args[0]) == typeof('str') ? args.splice(0, 1)[0] : 'base'
|
|
args = args[0].constructor.name == 'Array' ? args[0] : args
|
|
|
|
var base = this
|
|
|
|
args.forEach(function(data){
|
|
// calculate align offset...
|
|
if(align == 'base'){
|
|
var d = base.getRibbonOrder('base') - data.getRibbonOrder('base')
|
|
|
|
} else if(align == 'top'){
|
|
var d = 0
|
|
|
|
} else if(align == 'bottom'){
|
|
var d = base.ribbon_order.length - data.ribbon_order.length
|
|
}
|
|
|
|
var t = 0
|
|
|
|
// merge order...
|
|
base.order = base.order.concat(data.order)
|
|
|
|
// merge ribbons...
|
|
for(var i=0; i < data.ribbon_order.length; i++){
|
|
var g = data.ribbon_order[i]
|
|
var r = data.ribbons[g]
|
|
|
|
// push the new ribbon just before the base...
|
|
if(d < 0){
|
|
// see if g is unique...
|
|
if(g in base.ribbons || base.order.indexOf(g) >= 0){
|
|
g = base.newGid('R')
|
|
}
|
|
base.ribbon_order.splice(t, 0, g)
|
|
base.ribbons[g] = r
|
|
t += 1
|
|
d -= 1
|
|
|
|
// append ribbons...
|
|
} else if(d < base.ribbon_order.length){
|
|
var tg = base.ribbon_order[d]
|
|
base.ribbons[tg] = base.ribbons[tg].concat(r)
|
|
|
|
// push the new ribbon to the end...
|
|
} else {
|
|
// see if g is unique...
|
|
if(g in base.ribbons || base.order.indexOf(g) >= 0){
|
|
g = base.newGid('R')
|
|
}
|
|
base.ribbon_order.push(g)
|
|
base.ribbons[g] = r
|
|
}
|
|
|
|
d += 1
|
|
}
|
|
})
|
|
|
|
// XXX this is slow-ish...
|
|
base.removeDuplicateGIDs()
|
|
|
|
return base
|
|
},
|
|
|
|
// Align data to ribbon...
|
|
//
|
|
// NOTE: if either start or end is not given this will infer the
|
|
// missing values via the ribbon above.
|
|
// NOTE: if either start or end is not given this can only align
|
|
// downward, needing a ribbon above the target to infer the
|
|
// values.
|
|
//
|
|
// XXX test
|
|
alignToRibbon: function(ribbon, start, end){
|
|
ribbon = ribbon == null ? this.base : this.getRibbon(ribbon)
|
|
|
|
if(start == null || end == null){
|
|
var r = this.getRibbonOrder(ribbon)
|
|
// ribbon is top ribbon, nothing to do...
|
|
if(r <= 0){
|
|
return this
|
|
}
|
|
|
|
var above = this.getRibbon(r-1)
|
|
}
|
|
|
|
start = start == null
|
|
? this.getImageOrder('first', above)
|
|
: this.getImageOrder(start)
|
|
end = end == null
|
|
// NOTE: we need to exclude the last image in ribbon from
|
|
// the next section, this the offset.
|
|
? this.getImageOrder('last', above)+1
|
|
: this.getImageOrder(end)
|
|
|
|
// split the data into three sections...
|
|
var res = this.split(start, end)
|
|
var rest = res.splice(1)
|
|
|
|
// set the base ribbon on the middle section...
|
|
rest[0].setBase(0)
|
|
|
|
// join the resulting data to the base ribbon...
|
|
res = res.join(rest)
|
|
|
|
// transfer data to new data object...
|
|
res.current = this.current
|
|
res.base = this.base
|
|
|
|
return res
|
|
},
|
|
|
|
// Crop the data...
|
|
//
|
|
// NOTE: this will not affect the original data object...
|
|
// NOTE: this may result in empty ribbons...
|
|
// NOTE: this will not crop the .order...
|
|
//
|
|
// XXX should this link to .root and .parent data???
|
|
crop: function(list){
|
|
var crop = this.clone()
|
|
list = crop.makeSparseImages(list)
|
|
for(var k in crop.ribbons){
|
|
crop.ribbons[k] = crop.makeSparseImages(crop.ribbons[k].filter(function(_, i){
|
|
return list[i] != null
|
|
}))
|
|
}
|
|
|
|
// XXX ???
|
|
//crop.parent = this
|
|
//crop.root = this.root == null ? this : this.root
|
|
|
|
return crop
|
|
},
|
|
|
|
// Merge changes from crop into data...
|
|
//
|
|
// NOTE: this may result in empty ribbons...
|
|
//
|
|
// XXX sync ribbon order???
|
|
// XXX should we be able to align a merged crop???
|
|
// XXX test
|
|
mergeCrop: function(crop){
|
|
var that = this
|
|
|
|
this.order = crop.order.slice()
|
|
// XXX sync these???
|
|
this.ribbon_order = crop.ribbon_order.slice()
|
|
this.sortImages()
|
|
|
|
//
|
|
for(var k in crop.ribbons){
|
|
var local = k in this.ribbons ? this.ribbons[k] : []
|
|
var remote = crop.ribbons[k]
|
|
|
|
this.ribbons[k] = local
|
|
|
|
remote.forEach(function(e){
|
|
// add gid to local ribbon...
|
|
if(local.indexOf(e) < 0){
|
|
this.shiftImage(e, k)
|
|
}
|
|
})
|
|
}
|
|
|
|
return this
|
|
},
|
|
|
|
// Create a sortable ribbon representation...
|
|
//
|
|
// .cropRibbons()
|
|
// .cropRibbons(mode)
|
|
// -> Data
|
|
//
|
|
// mode controls which images represent each ribbon, it can be:
|
|
// 'current' - the closest to current image (default)
|
|
// 'first' - first image in ribbon
|
|
// 'last' - last ribbon in image
|
|
// func(ribbon) -> gid
|
|
// - a function that will get a ribbon gid and return
|
|
// an apropriate image gid
|
|
//
|
|
// NOTE: the images used with a given string mode are the same as
|
|
// the returned via .getImage(mode, ribbon)
|
|
//
|
|
// v
|
|
// oooooo|a|ooooooo
|
|
// ooooooo|A|ooooooooooooo -> aAa
|
|
// ooo|a|ooooo
|
|
//
|
|
//
|
|
// The resulting data will contain a single ribbon, each image in
|
|
// which represents a ribbon in the source data.
|
|
// This view allows convenient sorting of ribbons as images.
|
|
//
|
|
// The crop can be merged back into the source ribbon via the
|
|
// .mergeRibbonCrop(..) method.
|
|
//
|
|
// XXX should there be a way to set the base ribbon???
|
|
// XXX should this link to .root and .parent data???
|
|
// XXX do these belong here???
|
|
cropRibbons: function(mode){
|
|
mode = mode == null ? 'current' : mode
|
|
var crop = new Data()
|
|
|
|
// get image representations from each ribbon...
|
|
var that = this
|
|
var images = this.ribbon_order.map(
|
|
typeof(mode) == typeof('str')
|
|
? function(e){ return that.getImage(mode, e) }
|
|
: mode)
|
|
|
|
var r = crop.newRibbon()
|
|
|
|
crop.ribbons[r] = images
|
|
crop.order = images.slice()
|
|
crop.base = r
|
|
crop.current = images[0]
|
|
|
|
// XXX ???
|
|
//crop.parent = this
|
|
//crop.root = this.root == null ? this : this.root
|
|
|
|
return crop
|
|
},
|
|
|
|
// Merge the sortable ribbon representation into data...
|
|
//
|
|
// This will take the image order from the crop and merge it into
|
|
// the .ribbon_order of this, essentially sorting ribbons...
|
|
//
|
|
// NOTE: see .cropRibbons(..) for more details...
|
|
// NOTE: this will set the base to the top ribbon, but only if base
|
|
// was the top ribbon (default) in the first place...
|
|
// XXX is this correct???
|
|
//
|
|
// XXX should there be a way to set the base ribbon???
|
|
mergeRibbonCrop: function(crop){
|
|
var b = this.ribbon_order.indexOf(this.base)
|
|
var that = this
|
|
this.ribbon_order = crop.order.map(function(e){
|
|
return that.getRibbon(e)
|
|
})
|
|
// set the base to the first/top ribbon...
|
|
// XXX is this the correct way???
|
|
if(b == 0){
|
|
this.base = this.ribbon_order[0]
|
|
}
|
|
return this
|
|
},
|
|
|
|
|
|
// Clone/copy the data object...
|
|
//
|
|
clone: function(){
|
|
var res = new Data()
|
|
res.base = this.base
|
|
res.current = this.current
|
|
res.order = this.order.slice()
|
|
res.ribbon_order = this.ribbon_order.slice()
|
|
res.ribbons = {}
|
|
// make ribbons sparse...
|
|
for(var k in this.ribbons){
|
|
res.ribbons[k] = this.ribbons[k].slice()
|
|
}
|
|
return res
|
|
},
|
|
|
|
// Reset the state to empty...
|
|
//
|
|
_reset: function(){
|
|
this.base = null
|
|
this.current = null
|
|
this.order = []
|
|
this.ribbon_order = []
|
|
this.ribbons = {}
|
|
|
|
this._gid_cache = []
|
|
|
|
return this
|
|
},
|
|
|
|
// Remove duplicate gids...
|
|
//
|
|
// NOTE: this is slow-ish...
|
|
removeDuplicateGIDs: function(){
|
|
this.removeDuplicates(this.order)
|
|
this.sortImages()
|
|
return this
|
|
},
|
|
|
|
|
|
|
|
/****************************************** JSON serialization ***/
|
|
|
|
// Convert gen3 data to gen4 json...
|
|
convertDataGen3: function(data){
|
|
data = data.version == null ? convertDataGen1(data) : data
|
|
var that = this
|
|
var res = {}
|
|
res.version = '3.0'
|
|
res.current = data.current
|
|
res.order = data.order.slice()
|
|
res.ribbon_order = []
|
|
res.ribbons = {}
|
|
// generate gids...
|
|
data.ribbons.forEach(function(e){
|
|
var gid = that.newGid('R')
|
|
res.ribbon_order.push(gid)
|
|
res.ribbons[gid] = e.slice()
|
|
})
|
|
// we set the base to the first ribbon...
|
|
res.base = res.ribbon_order[0]
|
|
return res
|
|
},
|
|
|
|
// Load data from JSON...
|
|
//
|
|
// NOTE: this loads in-place, use .fromJSON(..) to create new data...
|
|
loadJSON: function(data){
|
|
if(typeof(data) == typeof('str')){
|
|
data = JSON.parse(data)
|
|
}
|
|
data = data.version < '3.0' ? this.convertDataGen3(data) : data
|
|
this.base = data.base
|
|
this.current = data.current
|
|
this.order = data.order.slice()
|
|
this.ribbon_order = data.ribbon_order.slice()
|
|
this.ribbons = {}
|
|
// make ribbons sparse...
|
|
for(var k in data.ribbons){
|
|
this.ribbons[k] = this.makeSparseImages(data.ribbons[k])
|
|
}
|
|
return this
|
|
},
|
|
|
|
// Generate JSON from data...
|
|
//
|
|
// NOTE: if mode is either 'str' or 'string' then this will stringify
|
|
// the result...
|
|
dumpJSON: function(mode){
|
|
var res = {
|
|
varsion: '3.0',
|
|
base: this.base,
|
|
current: this.current,
|
|
order: this.order.slice(),
|
|
ribbon_order: this.ribbon_order.slice(),
|
|
ribbons: {},
|
|
}
|
|
// compact ribbons...
|
|
for(var k in this.ribbons){
|
|
res.ribbons[k] = this.ribbons[k].compact()
|
|
}
|
|
if(mode == 'string' || mode == 'str'){
|
|
res = JSON.stringify(res)
|
|
}
|
|
return res
|
|
},
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************/
|
|
|
|
// Main Data object...
|
|
//
|
|
function Data(json){
|
|
// in case this is called as a function (without new)...
|
|
if(this.constructor.name != 'Data'){
|
|
return new Data(json)
|
|
}
|
|
|
|
// load initial state...
|
|
if(json != null){
|
|
this.loadJSON(json)
|
|
} else {
|
|
this._reset()
|
|
}
|
|
|
|
return this
|
|
}
|
|
Data.__proto__ = DataClassPrototype
|
|
Data.prototype = DataPrototype
|
|
Data.prototype.constructor = Data
|
|
|
|
|
|
|
|
/**********************************************************************
|
|
* vim:set ts=4 sw=4 : */
|