mirror of
https://github.com/flynx/ImageGrid.git
synced 2025-10-29 02:10:08 +00:00
refactoring + a several util methods in tags.js
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
158da70086
commit
1667632227
@ -132,9 +132,8 @@ if(typeof(sha1) != 'undefined'){
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
// Data...
|
||||
|
||||
// Data class methods and API...
|
||||
//
|
||||
var DataClassPrototype = {
|
||||
// NOTE: we consider the input list sorted...
|
||||
fromArray: function(list){
|
||||
@ -157,12 +156,6 @@ var DataClassPrototype = {
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
|
||||
// Data object methods and API...
|
||||
//
|
||||
var DataPrototype = {
|
||||
|
||||
get version(){
|
||||
@ -231,6 +224,9 @@ var DataPrototype = {
|
||||
//
|
||||
/*****************************************************************/
|
||||
|
||||
ribbon_order: null,
|
||||
ribbons: null,
|
||||
|
||||
get current(){
|
||||
return this.__current = this.__current
|
||||
|| this.getImages(this.ribbon_order[0])[0]
|
||||
@ -238,11 +234,12 @@ var DataPrototype = {
|
||||
set current(value){
|
||||
this.focusImage(value) },
|
||||
|
||||
// XXX should this default to top or bottom ribbon???
|
||||
get base(){
|
||||
return this.__base || this.ribbon_order[0] },
|
||||
set base(value){
|
||||
this.__base = value },
|
||||
this.__base = value in this.ribbons ?
|
||||
this.getRibbon(value)
|
||||
: value },
|
||||
|
||||
get order(){
|
||||
return this.__order },
|
||||
@ -427,7 +424,6 @@ var DataPrototype = {
|
||||
return res
|
||||
},
|
||||
|
||||
|
||||
// Remove duplicate items from list in-place...
|
||||
//
|
||||
// NOTE: only the first occurrence is kept...
|
||||
@ -1554,10 +1550,7 @@ var DataPrototype = {
|
||||
// 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
|
||||
}
|
||||
this.base = this.getRibbon(target, offset)
|
||||
return this
|
||||
},
|
||||
|
||||
@ -2858,7 +2851,6 @@ var DataPrototype = {
|
||||
var that = this
|
||||
data = typeof(data) == typeof('str') ? JSON.parse(data) : data
|
||||
data = formats.updateData(data, DATA_VERSION)
|
||||
this.base = data.base
|
||||
this.order = data.order.slice()
|
||||
this.ribbon_order = data.ribbon_order.slice()
|
||||
|
||||
@ -2876,6 +2868,7 @@ var DataPrototype = {
|
||||
})
|
||||
|
||||
this.current = data.current
|
||||
this.base = data.base
|
||||
|
||||
// extra data...
|
||||
!clean
|
||||
@ -2926,8 +2919,17 @@ var DataPrototype = {
|
||||
}
|
||||
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
/*********************************************************************/
|
||||
var BaseData =
|
||||
module.BaseData =
|
||||
object.makeConstructor('BaseData',
|
||||
DataClassPrototype,
|
||||
DataPrototype)
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
// XXX make a API compatible replacement to the above -- to access
|
||||
// compatibility and performance...
|
||||
@ -3220,7 +3222,17 @@ var DataWithTagsPrototype = {
|
||||
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
var DataWithTags =
|
||||
module.DataWithTags =
|
||||
object.makeConstructor('DataWithTags',
|
||||
DataClassPrototype,
|
||||
DataWithTagsPrototype)
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
// Proxy Data API to one of the target data objects...
|
||||
var DataProxyPrototype = {
|
||||
@ -3236,28 +3248,13 @@ var DataProxyPrototype = {
|
||||
}
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
|
||||
// Main Data object...
|
||||
var BaseData =
|
||||
module.BaseData =
|
||||
object.makeConstructor('BaseData',
|
||||
DataClassPrototype,
|
||||
DataPrototype)
|
||||
|
||||
|
||||
var DataWithTags =
|
||||
module.DataWithTags =
|
||||
object.makeConstructor('DataWithTags',
|
||||
DataClassPrototype,
|
||||
DataWithTagsPrototype)
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
var Data =
|
||||
module.Data = DataWithTags
|
||||
|
||||
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* vim:set ts=4 sw=4 : */ return module })
|
||||
|
||||
@ -49,7 +49,7 @@ var normalizeSplit = function(args){
|
||||
|
||||
/*********************************************************************/
|
||||
|
||||
var TagsClassPrototype = {
|
||||
var BaseTagsClassPrototype = {
|
||||
// Utils...
|
||||
//
|
||||
// .normalize(tag)
|
||||
@ -207,74 +207,42 @@ var TagsClassPrototype = {
|
||||
// ...there are two ways to think of this:
|
||||
// 1) both (a-la flickr) -- keep both, use normalized internally
|
||||
// 2) only normalized -- simpler but may surprise the user and not be as pretty...
|
||||
var TagsPrototype = {
|
||||
// XXX should we split out the non-basic stuff???
|
||||
// like:
|
||||
// .makePathsPersistent()
|
||||
// .optimizeTags()
|
||||
// ...
|
||||
var BaseTagsPrototype = {
|
||||
config: {
|
||||
tagRemovedChars: '[\\s-_]',
|
||||
},
|
||||
|
||||
// data...
|
||||
// Tag index...
|
||||
//
|
||||
// Format:
|
||||
// Set([ <tag>, ... ])
|
||||
//
|
||||
// XXX Q: should these be normalized???
|
||||
__persistent_tags: null,
|
||||
|
||||
// Format:
|
||||
// {
|
||||
// <alias>: <normalized-tag>,
|
||||
// }
|
||||
//
|
||||
// XXX need introspection for this...
|
||||
// ...should this be .aliases ???
|
||||
__aliases: null,
|
||||
|
||||
// Format:
|
||||
// {
|
||||
// <tag>: [ <item>, ... ],
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
__index: null,
|
||||
|
||||
|
||||
// Persistent tags...
|
||||
//
|
||||
// Format:
|
||||
// Set([ <tag>, ... ])
|
||||
//
|
||||
persistent: null,
|
||||
|
||||
// XXX EXPERIMENTAL...
|
||||
// XXX need a way to edit the compound tag...
|
||||
__special_tag_handlers__: {
|
||||
'*persistent*': function(action, tag, value){
|
||||
// XXX remove the tag...
|
||||
// XXX add the tag to .__persistent_tags
|
||||
// XXX return the new tag for normal handling...
|
||||
},
|
||||
},
|
||||
handleSpecialTag: function(action, tag, value){
|
||||
var that = this
|
||||
var handlers = this.__special_tag_handlers__ || {}
|
||||
|
||||
// get the matching handler key...
|
||||
var key = Object.keys(handlers)
|
||||
.filter(function(k){
|
||||
return that.match(k, tag) })
|
||||
// XXX should we handle multiple matches???
|
||||
.shift()
|
||||
|
||||
// resolve handler aliases...
|
||||
var match = key
|
||||
do {
|
||||
match = handlers[match]
|
||||
} while(!(match instanceof Function) && match in handlers)
|
||||
|
||||
// no handler...
|
||||
if(!(match instanceof Function)){
|
||||
// XXX
|
||||
return false
|
||||
}
|
||||
|
||||
// XXX remove key from tag...
|
||||
|
||||
return match.call(this, action, tag, value)
|
||||
},
|
||||
|
||||
// Tag aliases...
|
||||
//
|
||||
// Format:
|
||||
// {
|
||||
// <alias>: <normalized-tag>,
|
||||
// }
|
||||
//
|
||||
aliases: null,
|
||||
|
||||
|
||||
// Utils...
|
||||
@ -569,6 +537,41 @@ var TagsPrototype = {
|
||||
//return that.directMatch(query, t, cmp) }) },
|
||||
return that.match(query, t, cmp) }) },
|
||||
|
||||
// Keep only the longest matching paths...
|
||||
//
|
||||
// .uniquePaths(path, ..)
|
||||
// .uniquePaths([path, ..])
|
||||
// -> paths
|
||||
//
|
||||
// Algorithm:
|
||||
// - sort by path length descending
|
||||
// - take top path (head)
|
||||
// - remove all paths that match it after it (tail)
|
||||
// - next path
|
||||
//
|
||||
uniquePaths: function(...list){
|
||||
var that = this
|
||||
return that.normalize(normalizeSplit(list))
|
||||
// sort by number of path elements (longest first)...
|
||||
.map(function(p){ return p.split(/[\\\/]/g) })
|
||||
.sort(function(a, b){ return b.length - a.length })
|
||||
.map(function(p){ return p.join('/') })
|
||||
// remove all paths in tail that match the current...
|
||||
.map(function(p, i, list){
|
||||
// skip []...
|
||||
!(p instanceof Array)
|
||||
&& list
|
||||
// only handle the tail...
|
||||
.slice(i+1)
|
||||
.forEach(function(o, j){
|
||||
// skip []...
|
||||
!(p instanceof Array)
|
||||
&& that.directMatch(o, p)
|
||||
// match -> replace the matching elem with []
|
||||
&& (list[i+j+1] = []) })
|
||||
return p })
|
||||
.flat() },
|
||||
|
||||
|
||||
// Introspection and Access API...
|
||||
//
|
||||
@ -611,7 +614,7 @@ var TagsPrototype = {
|
||||
// -> bool
|
||||
//
|
||||
//
|
||||
// NOTE: this includes all the .persistent tags as well as all the
|
||||
// NOTE: this includes all the persistent tags as well as all the
|
||||
// tags actually used.
|
||||
//
|
||||
// XXX should this return split values???
|
||||
@ -641,7 +644,7 @@ var TagsPrototype = {
|
||||
// get all tags...
|
||||
} else {
|
||||
return Object.keys(this.__index || {})
|
||||
.concat([...(this.__persistent_tags || [])]
|
||||
.concat([...(this.persistent || [])]
|
||||
.map(function(t){
|
||||
return that.normalize(t) }))
|
||||
.unique()
|
||||
@ -700,7 +703,7 @@ var TagsPrototype = {
|
||||
// -> this
|
||||
//
|
||||
alias: function(tag, value){
|
||||
var aliases = this.__aliases = this.__aliases || {}
|
||||
var aliases = this.aliases = this.aliases || {}
|
||||
// XXX this seems a bit ugly...
|
||||
var resolve = function(tag, seen){
|
||||
seen = seen || []
|
||||
@ -903,8 +906,8 @@ var TagsPrototype = {
|
||||
tags = normalizeSplit(tags)
|
||||
|
||||
var persistent =
|
||||
this.__persistent_tags =
|
||||
this.__persistent_tags || new Set()
|
||||
this.persistent =
|
||||
this.persistent || new Set()
|
||||
|
||||
return this.normalize(tags)
|
||||
.map(function(tag){
|
||||
@ -1004,9 +1007,9 @@ var TagsPrototype = {
|
||||
|
||||
// rename actual data...
|
||||
} else {
|
||||
patchSet(this.__persistent_tags || [])
|
||||
patchSet(this.persistent || [])
|
||||
patchObj(this.__index || {})
|
||||
patchObj(this.__aliases || {}, true)
|
||||
patchObj(this.aliases || {}, true)
|
||||
}
|
||||
|
||||
return this
|
||||
@ -1051,8 +1054,45 @@ var TagsPrototype = {
|
||||
return res
|
||||
},
|
||||
|
||||
// Keep only the longest tag paths per value...
|
||||
//
|
||||
// Optimize tags for all values...
|
||||
// .optimizeTags()
|
||||
// -> values
|
||||
//
|
||||
// Optimize tags for specific values...
|
||||
// .optimizeTags(value, ..)
|
||||
// .optimizeTags([value, ..])
|
||||
// -> values
|
||||
//
|
||||
//
|
||||
// Example:
|
||||
// var ts = new Tags()
|
||||
// ts.tag(['a/b/c', 'a/c'], x)
|
||||
//
|
||||
// ts.optimizeTags() // will remove 'a/c' form x as it is fully
|
||||
// // contained within 'a/b/c'...
|
||||
//
|
||||
// XXX should this be done on .tag(..) and friends???
|
||||
optimizeTags: function(...values){
|
||||
var that = this
|
||||
return (normalizeSplit(values) || this.values())
|
||||
.filter(function(value){
|
||||
var tags = new Set(that.paths(value))
|
||||
tags = [...tags.subtract(that.uniquePaths(...tags))]
|
||||
tags.length > 0
|
||||
&& that.untag(tags, value)
|
||||
return tags.length > 0 }) },
|
||||
|
||||
// Tags - Tags API...
|
||||
// Make all paths persistent...
|
||||
//
|
||||
// NOTE: this will add only longest unique paths (see: .uniquePaths(..))
|
||||
makePathsPersistent: function(){
|
||||
this.persistent = new Set(this.uniquePaths(this.paths()))
|
||||
return this },
|
||||
|
||||
|
||||
// Tags-Tags API...
|
||||
//
|
||||
// Join 1 or more Tags objects...
|
||||
//
|
||||
@ -1379,12 +1419,12 @@ var TagsPrototype = {
|
||||
var res = {}
|
||||
|
||||
// aliases...
|
||||
this.__aliases && Object.keys(this.__aliases).length > 0
|
||||
&& (res.aliases = Object.assign({}, this.__aliases))
|
||||
this.aliases && Object.keys(this.aliases).length > 0
|
||||
&& (res.aliases = Object.assign({}, this.aliases))
|
||||
|
||||
// persistent tags...
|
||||
this.__persistent_tags && this.__persistent_tags.size > 0
|
||||
&& (res.persistent = [...this.__persistent_tags])
|
||||
this.persistent && this.persistent.size > 0
|
||||
&& (res.persistent = [...this.persistent])
|
||||
|
||||
// tags...
|
||||
res.tags = {}
|
||||
@ -1400,11 +1440,11 @@ var TagsPrototype = {
|
||||
|
||||
// aliases...
|
||||
json.aliases
|
||||
&& (this.__aliases = Object.assign({}, json.aliases))
|
||||
&& (this.aliases = Object.assign({}, json.aliases))
|
||||
|
||||
// persistent tags...
|
||||
json.persistent
|
||||
&& (this.__persistent_tags = new Set(json.persistent))
|
||||
&& (this.persistent = new Set(json.persistent))
|
||||
|
||||
// tags...
|
||||
json.tags
|
||||
@ -1424,11 +1464,90 @@ var TagsPrototype = {
|
||||
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
var Tags =
|
||||
module.Tags =
|
||||
object.makeConstructor('Tags',
|
||||
TagsClassPrototype,
|
||||
TagsPrototype)
|
||||
var BaseTags =
|
||||
module.BaseTags =
|
||||
object.makeConstructor('BaseTags',
|
||||
BaseTagsClassPrototype,
|
||||
BaseTagsPrototype)
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
// XXX EXPERIMENTAL...
|
||||
var TagsWithHandlersPrototype = {
|
||||
__proto__: BaseTagsPrototype,
|
||||
|
||||
// XXX docs...
|
||||
__special_tag_handlers__: {
|
||||
// print and remove tag...
|
||||
'test': function(tag, action, value){
|
||||
console.log('TEST TAG:', tag, action, value)
|
||||
return this.removeTag('test', tag)[0]
|
||||
},
|
||||
// terminate handling...
|
||||
'stop': function(tag, action, value){
|
||||
console.log('STOP:', tag, action, value)
|
||||
return false
|
||||
},
|
||||
// print the tag...
|
||||
'*': function(tag, action, value){
|
||||
console.log('TAG:', tag, action, value)
|
||||
return tag
|
||||
},
|
||||
},
|
||||
|
||||
// NOTE: handlers are called in order of handler occurrence and not
|
||||
// in the order the tags are in the given chain/path...
|
||||
handleSpecialTag: function(tag, ...args){
|
||||
var that = this
|
||||
var handlers = this.__special_tag_handlers__
|
||||
tag = this.normalize(tag)
|
||||
return Object.keys(handlers)
|
||||
.filter(function(p){
|
||||
// keep only valid tag patterns...
|
||||
// NOTE: this enables us to create special handlers
|
||||
// that will not be used for matching but are more
|
||||
// mnemonic...
|
||||
return p == that.normalize(p)
|
||||
// get the matching handler keys...
|
||||
&& that.directMatch(p, tag)
|
||||
})
|
||||
// resolve handler aliases...
|
||||
.map(function(match){
|
||||
do {
|
||||
match = handlers[match]
|
||||
} while(!(match instanceof Function) && match in handlers)
|
||||
|
||||
return match instanceof Function ?
|
||||
match
|
||||
// no handler...
|
||||
: [] })
|
||||
.flat()
|
||||
// call the handlers...
|
||||
// NOTE: we are threading tag through the handlers...
|
||||
.reduce(function(tag, handler){
|
||||
return tag && handler.call(that, tag, ...args) }, tag)
|
||||
// no handlers -> return as-is...
|
||||
|| tag },
|
||||
|
||||
}
|
||||
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
var TagsWithHandlers =
|
||||
module.TagsWithHandlers =
|
||||
object.makeConstructor('TagsWithHandlers',
|
||||
BaseTagsClassPrototype,
|
||||
TagsWithHandlersPrototype)
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
var Tags =
|
||||
module.Tags = TagsWithHandlers
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user