reworked collection handlers, added auto-collection-tags, tweaks, fixes and cleanup...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2017-09-02 20:58:49 +03:00
parent b48e31f37d
commit c825cb050c
2 changed files with 231 additions and 84 deletions

View File

@ -140,7 +140,7 @@ var CollectionActions = actions.Actions({
// //
// XXX should these get auto-sorted??? // XXX should these get auto-sorted???
get collection_handlers(){ get collection_handlers(){
var handlers = this.__collection_handlers || {} var handlers = this.__collection_handlers = this.__collection_handlers || {}
if(Object.keys(handlers).length == 0){ if(Object.keys(handlers).length == 0){
var that = this var that = this
@ -153,9 +153,15 @@ var CollectionActions = actions.Actions({
}) })
} }
// cleanup...
if(handlers['data'] == null){
delete handlers['data']
}
return handlers return handlers
}, },
/*/ XXX do we actually need this????
collectionDataLoader: ['- Collections/', collectionDataLoader: ['- Collections/',
core.doc`Collection data loader core.doc`Collection data loader
@ -173,7 +179,10 @@ var CollectionActions = actions.Actions({
{collectionFormat: 'data'}, {collectionFormat: 'data'},
function(title, data){ function(title, data){
return new Promise(function(resolve){ resolve(data.data) }) }], return new Promise(function(resolve){ resolve(data.data) }) }],
//*/
// XXX revise loader protocol...
// ...should it be cooperative???
loadCollection: ['- Collections/', loadCollection: ['- Collections/',
core.doc`Load collection... core.doc`Load collection...
@ -204,6 +213,8 @@ var CollectionActions = actions.Actions({
To invalidate such a cache .data should simply be deleted. To invalidate such a cache .data should simply be deleted.
NOTE: cached collection state is persistent. NOTE: cached collection state is persistent.
NOTE: when current collection is removed from .collections this
will not save state when loading another collection...
`, `,
function(collection){ function(collection){
var that = this var that = this
@ -224,6 +235,8 @@ var CollectionActions = actions.Actions({
// save current collection state... // save current collection state...
// //
// main view... // main view...
// NOTE: we save here unconditionally because MAIN_COLLECTION_TITLE
// is stored ONLY when we load some other collection...
if(this.collection == null){ if(this.collection == null){
this.saveCollection( this.saveCollection(
MAIN_COLLECTION_TITLE, MAIN_COLLECTION_TITLE,
@ -231,74 +244,79 @@ var CollectionActions = actions.Actions({
true) true)
// collection... // collection...
} else { // NOTE: we only save if the current collection exists, it
// may not exist if it was just removed...
} else if(this.collection in this.collections){
this.saveCollection( this.saveCollection(
this.collection, this.collection,
crop_mode == 'all' ? 'crop': null) crop_mode == 'all' ? 'crop': null)
} }
// load collection... // load collection...
for(var format in handlers){ Promise
if(collection_data[format]){ .all(Object.keys(handlers)
return this[handlers[format]](collection, collection_data) .filter(function(format){
.then(function(data){ return format == '*' || collection_data[format] })
if(!data){ .map(function(format){
return return that[handlers[format]](collection, collection_data) }))
} .then(function(){
var data = collection_data.data
// current... if(!data){
data.current = data.getImage(current) return
// current is not in collection -> try and keep }
// the ribbon context...
|| that.data.getImage(
current,
data.getImages(that.data.getImages(ribbon)))
// get closest image from collection...
|| that.data.getImage(current, data.order)
|| data.current
that // current...
.collectionLoading.chainCall(that, data.current = data.getImage(current)
// current is not in collection -> try and keep
// the ribbon context...
|| that.data.getImage(
current,
data.getImages(that.data.getImages(ribbon)))
// get closest image from collection...
|| that.data.getImage(current, data.order)
|| data.current
that
.collectionLoading.chainCall(that,
function(){
// do the actual load...
that.load.chainCall(that,
function(){ function(){
// do the actual load... that.collectionUnloaded(
that.load.chainCall(that, prev || MAIN_COLLECTION_TITLE)
function(){ }, {
that.collectionUnloaded( data: data,
prev || MAIN_COLLECTION_TITLE)
}, {
data: data,
crop_stack: collection_data.crop_stack crop_stack: collection_data.crop_stack
&& collection_data.crop_stack.slice(), && collection_data.crop_stack.slice(),
// NOTE: we do not need to pass collections // NOTE: we do not need to pass collections
// and order here as they stay in from // and order here as they stay in from
// the last .load(..) in merge mode... // the last .load(..) in merge mode...
//collections: that.collections, //collections: that.collections,
//collection_order: that.collection_order, //collection_order: that.collection_order,
}, true) }, true)
// maintain the .collection state... // maintain the .collection state...
if(collection == MAIN_COLLECTION_TITLE){ if(collection == MAIN_COLLECTION_TITLE){
// no need to maintain the main data in two // no need to maintain the main data in two
// locations... // locations...
delete that.collections[MAIN_COLLECTION_TITLE] delete that.collections[MAIN_COLLECTION_TITLE]
delete this.location.collection delete this.location.collection
} else { } else {
that.data.collection = that.data.collection =
that.location.collection = that.location.collection =
collection collection
// cleanup... // cleanup...
if(collection == null){ if(collection == null){
delete this.location.collection delete this.location.collection
} }
} }
}, },
collection) collection)
}) })
}
}
}], }],
// events... // events...
@ -332,15 +350,23 @@ var CollectionActions = actions.Actions({
saveCollection: ['- Collections/', saveCollection: ['- Collections/',
core.doc`Save current state to collection core.doc`Save current state to collection
Save Current state to current collection Save current state to current collection
.saveCollection() .saveCollection()
.saveCollection('current') .saveCollection('current')
-> this -> this
NOTE: this will do nothing if no collection is loaded. NOTE: this will do nothing if no collection is loaded.
Save Current state as collection Save state as collection...
.saveCollection(collection) .saveCollection(collection)
-> this -> this
NOTE: if saving to self the default mode is 'crop' else
it is 'current' (see below for info on respective
modes)...
Save current state as collection ignoring crop stack
.saveCollection(collection, 0)
.saveCollection(collection, 'current')
-> this
Save new empty collection Save new empty collection
.saveCollection(collection, 'empty') .saveCollection(collection, 'empty')
@ -358,6 +384,13 @@ var CollectionActions = actions.Actions({
.saveCollection(collection, 'base') .saveCollection(collection, 'base')
-> this -> this
NOTE: this will overwrite collection .data and .crop_stack only,
the rest of the data is untouched...
NOTE: if it is needed to overwrite an existing collection then
first remove it then save anew:
this
.removeCollection(x)
.saveCollection(x, 'crop')
`, `,
function(collection, mode, force){ function(collection, mode, force){
var that = this var that = this
@ -365,48 +398,64 @@ var CollectionActions = actions.Actions({
collection = collection == 'current' ? this.collection : collection collection = collection == 'current' ? this.collection : collection
if(!force if(!force
&& (collection == null && (collection == null || collection == MAIN_COLLECTION_TITLE)){
|| collection == MAIN_COLLECTION_TITLE)){
return return
} }
var collections = this.collections = this.collections || {}
var depth = typeof(mode) == typeof(123) ? mode : null var depth = typeof(mode) == typeof(123) ? mode : null
mode = depth == 0 ? null mode = depth == 0 ? 'current'
: depth ? 'crop' : depth ? 'crop'
: mode : mode
// default mode -- if saving to self then 'crop' else 'current'
var collections = this.collections = this.collections || {} if(!mode){
mode = ((collection in collections
var state = collections[collection] = { && collection == this.collection)
title: collection, || collection == MAIN_COLLECTION_TITLE) ?
'crop'
// NOTE: we do not need to care about tags here as they : 'current'
// will get overwritten on load...
data: (mode == 'empty' ?
(new this.data.constructor())
: mode == 'base' && this.crop_stack ?
(this.crop_stack[0] || this.data.clone())
: mode == 'crop' ?
this.data.clone()
: this.data.clone()
.run(function(){
var d = this
this.collection = collection
})
.clear('unloaded')),
} }
// save the data...
var state = collections[collection] = collections[collection] || {}
state.title = state.title || collection
// NOTE: we do not need to care about tags here as they
// will get overwritten on load...
state.data = (mode == 'empty' ?
(new this.data.constructor())
: mode == 'base' && this.crop_stack ?
(this.crop_stack[0] || this.data.clone())
: mode == 'crop' ?
this.data.clone()
// current...
: this.data.clone()
.run(function(){
var d = this
this.collection = collection
})
.clear('unloaded'))
// crop mode -> handle crop stack...
if(mode == 'crop' && this.crop_stack && depth != 0){ if(mode == 'crop' && this.crop_stack && depth != 0){
depth = depth || this.crop_stack.length depth = depth || this.crop_stack.length
depth = this.crop_stack.length - Math.min(depth, this.crop_stack.length) depth = this.crop_stack.length - Math.min(depth, this.crop_stack.length)
state.crop_stack = this.crop_stack.slice(depth) state.crop_stack = this.crop_stack.slice(depth)
// other modes...
} else {
delete state.crop_stack
} }
}], }],
newCollection: ['- Collections/', newCollection: ['- Collections/',
function(collection){ return this.saveCollection(collection, 'empty') }], function(collection){ return this.saveCollection(collection, 'empty') }],
// XXX should we do anything special if collection is loaded??? // XXX should we do anything special if collection is loaded???
removeCollection: ['- Collections/', removeCollection: ['- Collections/',
core.doc`
NOTE: when removing the currently loaded collection this will
just remove it from .collections and do nothing...`,
function(collection){ function(collection){
if(collection == MAIN_COLLECTION_TITLE){ if(collection == MAIN_COLLECTION_TITLE){
return return
@ -714,6 +763,8 @@ module.Collection = core.ImageGridFeatures.Feature({
], ],
suggested: [ suggested: [
'collection-tags', 'collection-tags',
'auto-collection-tags',
'ui-collections', 'ui-collections',
'fs-collections', 'fs-collections',
], ],
@ -721,6 +772,9 @@ module.Collection = core.ImageGridFeatures.Feature({
actions: CollectionActions, actions: CollectionActions,
handlers: [ handlers: [
// XXX do we need this???
['json.pre',
function(){ this.saveCollection() }],
// XXX maintain changes... // XXX maintain changes...
// - collection-level: mark collections as changed... // - collection-level: mark collections as changed...
// - in-collection: // - in-collection:
@ -848,6 +902,8 @@ module.CollectionTags = core.ImageGridFeatures.Feature({
tags[tag] = local_tags[tag] || [] tags[tag] = local_tags[tag] || []
}) })
;(this.crop_stack || [])
.forEach(function(d){ d.tags = tags })
this.data.tags = tags this.data.tags = tags
this.data.sortTags() this.data.sortTags()
} }
@ -864,6 +920,7 @@ module.CollectionTags = core.ImageGridFeatures.Feature({
['saveCollection.pre', ['saveCollection.pre',
function(title, mode, force){ function(title, mode, force){
var that = this var that = this
title = title || this.collection || MAIN_COLLECTION_TITLE
var local_tag_names = this.config['collection-local-tags'] || [] var local_tag_names = this.config['collection-local-tags'] || []
// do not do anything for main collection unless force is true... // do not do anything for main collection unless force is true...
@ -911,11 +968,32 @@ module.CollectionTags = core.ImageGridFeatures.Feature({
// NOTE: we do not need to explicitly load anything as .load() // NOTE: we do not need to explicitly load anything as .load()
// will load everything we need and .collectionLoading(..) // will load everything we need and .collectionLoading(..)
// will .sortTags() for us... // will .sortTags() for us...
//
// XXX handle 'base' mode...
['json', ['json',
function(res, mode){ function(res, mode){
var c = this.collections var c = this.collections
var rc = res.collections var rc = res.collections
// in 'base' mode set .data.tags and .local_tags to
// the base collection data...
if(mode == 'base'
&& this.collection != null
&& this.collection != MAIN_COLLECTION_TITLE){
// NOTE: at this point .crop_stack is handled, so we
// do not need to care about it...
var tags = c[MAIN_COLLECTION_TITLE].local_tags || {}
var rtags =
res.data.tags =
res.collections[this.collection].data.tags || {}
// compact and overwrite the local tags for the base...
Object.keys(tags)
.forEach(function(tag){
rtags[tag] = tags[tag].compact() })
}
// clear and compact tags for all collections...
rc rc
&& Object.keys(rc || {}) && Object.keys(rc || {})
.forEach(function(title){ .forEach(function(title){
@ -923,7 +1001,7 @@ module.CollectionTags = core.ImageGridFeatures.Feature({
var rtags = rc[title].local_tags = {} var rtags = rc[title].local_tags = {}
// compact the local tags... // compact the local tags...
Object.keys(c[title].local_tags) Object.keys(tags)
.forEach(function(tag){ .forEach(function(tag){
rtags[tag] = tags[tag].compact() }) rtags[tag] = tags[tag].compact() })
@ -937,6 +1015,61 @@ module.CollectionTags = core.ImageGridFeatures.Feature({
//---------------------------------------------------------------------
var AutoTagCollectionsActions = actions.Actions({
// initial load...
// XXX should this be a real tag query???
collectionAutoTagsLoader: ['- Collections/',
core.doc`
NOTE: this will ignore local tags.
NOTE: this will prepend new matching items to the saved state.
`,
{collectionFormat: 'tag_query'},
function(title, state){
return new Promise((function(resolve){
var local_tag_names = this.config['collection-local-tags'] || []
var tags = (state.tag_query || [])
// filter out local tags...
.filter(function(tag){ return local_tag_names.indexOf(tag) < 0 })
// XXX should this be a real tag query???
var gids = this.data.getTaggedByAll(tags)
// get unmatching...
var remove = state.data ?
state.data.order
.filter(function(gid){ return gids.indexOf(gid) < 0 })
: []
// build data...
state.data = data.Data.fromArray(gids)
// join with saved state...
.join(state.data || data.Data())
// remove unmatching...
.clear(remove)
resolve(state.data)
}).bind(this)) }],
})
var AutoTagCollections =
module.AutoTagCollections = core.ImageGridFeatures.Feature({
title: 'Collection tag handling',
doc: core.doc``,
tag: 'auto-collection-tags',
depends: [
'collections',
],
actions: AutoTagCollectionsActions,
})
//--------------------------------------------------------------------- //---------------------------------------------------------------------
// XXX show collections in image metadata... // XXX show collections in image metadata...

View File

@ -1761,7 +1761,7 @@ var ButtonsActions = actions.Actions({
// } // }
'main-buttons': { 'main-buttons': {
'&#x2630;': ['menu', 'browseActions -- Action menu...'], '&#x2630;': ['menu', 'browseActions -- Action menu...'],
'&#9714;<sub/>': ['collections', 'browseCollections -- Collections...'], '&#9714;<sub/><sup/>': ['collections', 'browseCollections -- Collections...'],
'C<sub/>': ['crop', 'browseActions: "Crop/" -- Crop menu...'], 'C<sub/>': ['crop', 'browseActions: "Crop/" -- Crop menu...'],
//'&#9636;<sub/>': ['collections', 'browseCollections -- Collections...'], //'&#9636;<sub/>': ['collections', 'browseCollections -- Collections...'],
//'&#9974;': ['view', 'toggleSingleImage -- Single image / ribbon toggle'], //'&#9974;': ['view', 'toggleSingleImage -- Single image / ribbon toggle'],
@ -1871,6 +1871,7 @@ module.Buttons = core.ImageGridFeatures.Feature({
'clear', 'clear',
'reload', 'reload',
'saveCollection', 'saveCollection',
'removeCollection',
'collectionLoaded', 'collectionLoaded',
'collectionUnloaded', 'collectionUnloaded',
], ],
@ -1882,12 +1883,25 @@ module.Buttons = core.ImageGridFeatures.Feature({
}) })
var l = this.collections_length var l = this.collections_length
// current collection unsaved indicator...
$('.main-buttons.buttons .collections.button sup')
.css({
'display': 'inline-block',
'position': 'absolute',
'margin-top': '-0.3em',
'overflow': 'visible',
})
.text((this.collection && !(this.collection in this.collections)) ?
'*'
: '')
// collection count...
$('.main-buttons.buttons .collections.button sub') $('.main-buttons.buttons .collections.button sub')
.css({ .css({
'display': 'inline-block', 'display': 'inline-block',
'width': '0px', 'width': '0px',
'overflow': 'visible', 'overflow': 'visible',
//'color': this.collection ? 'yellow' : '',
}) })
.text(l > 99 ? '99+' .text(l > 99 ? '99+'
: l == 0 ? '' : l == 0 ? ''