lots of minor chnages, fixes and cleanup + refactoring...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2017-08-29 20:59:00 +03:00
parent 76cb68a725
commit 267d5f705b
10 changed files with 336 additions and 123 deletions

View File

@ -258,6 +258,14 @@ body {
cursor: text;
}
/* Collection list */
.browse-widget.collection-list .list .item .text[cropped]:after {
content: "(cropped)";
margin-left: 5px;
opacity: 0.5;
font-style: italic;
}
/* External Editor List */
.browse-widget.editor-list .list .item:first-child .text:after {

View File

@ -1112,6 +1112,9 @@ module.TagsEditActions = actions.Actions({
gids = gids.constructor !== Array ? [gids] : gids
tags = tags.constructor !== Array ? [tags] : tags
var that = this
gids = gids.map(function(gid){ return that.data.getImage(gid) })
// data...
this.data.tag(tags, gids)
@ -1190,7 +1193,9 @@ module.TagsEditActions = actions.Actions({
source = source || 'both'
mode = mode || 'merge'
images = this.images
var images = this.images
if(typeof(source) != typeof('str')){
images = source
source = 'images'
@ -1415,16 +1420,6 @@ module.CropActions = actions.Actions({
crop_stack: null,
// load the crop stack if present...
load: [function(data){
return function(){
if(data.crop_stack){
this.crop_stack = data.crop_stack.map(function(j){
return data.Data(j)
})
}
}
}],
clear: [function(){
delete this.crop_stack }],
@ -1458,17 +1453,29 @@ module.CropActions = actions.Actions({
}
}
}],
// load the crop stack if present...
load: [function(state){
return function(){
var that = this
if(!('crop_stack' in state)){
return
}
// load...
if(state.crop_stack){
that.crop_stack = (state.crop_stack || [])
.map(function(d){
return d instanceof data.Data ? d : data.Data(d) })
this.crop_stack = state.crop_stack
.map(function(d){
return d instanceof data.Data ?
d
: data.Data(d) })
// merge the tags...
that.crop_stack.forEach(function(d){ d.tags = that.data.tags })
this.crop_stack.forEach(function(d){ d.tags = that.data.tags })
// remove...
} else {
delete this.crop_stack
}
}
}],

View File

@ -26,7 +26,7 @@ var widgets = require('features/ui-widgets')
// logical, would simplify control, etc.
//
var MAIN_COLLECTION_TITLE = 'All'
var MAIN_COLLECTION_TITLE = 'ALL'
// XXX things we need to do to collections:
// - auto-collections
@ -101,19 +101,30 @@ var CollectionActions = actions.Actions({
.unique()
.reverse()
// keep MAIN_COLLECTION_TITLE out of the collection order...
var m = res.indexOf(MAIN_COLLECTION_TITLE)
m >= 0
&& res.splice(m, 1)
// remove stuff not present...
if(res.length > keys.length){
res = res.filter(function(e){ return e in collections })
}
this.__collection_order.splice.apply(this.__collection_order,
[0, this.__collection_order.length].concat(res))
this.__collection_order.splice(0, this.__collection_order.length, ...res)
return this.__collection_order
},
set collection_order(value){
this.__collection_order = value },
get collections_length(){
var c = (this.collections || {})
return MAIN_COLLECTION_TITLE in c ?
Object.keys(c).length - 1
: Object.keys(c).length
},
// Format:
// {
// // NOTE: this is always the first handler...
@ -207,6 +218,7 @@ var CollectionActions = actions.Actions({
var handlers = this.collection_handlers
// save current collection state...
//
// main view -> save it...
if(this.collection == null){
var main = this.collections[MAIN_COLLECTION_TITLE] = {
@ -222,13 +234,12 @@ var CollectionActions = actions.Actions({
} else {
//this.saveCollection(this.collection, 'crop')
main.data = this.data
main.crop_stack = this.crop_stack
main.crop_stack = this.crop_stack
&& this.crop_stack.slice()
}
} else if(crop_mode == 'all'){
this.saveCollection(this.collection, 'crop')
//this.collections[this.collection].data = this.data
//this.collections[this.collection].crop_stack = this.crop_stack
}
// load collection...
@ -257,15 +268,21 @@ var CollectionActions = actions.Actions({
// data needs to be updated as collections
// may contain different numbers/orders of
// images...
data.updateImagePositions()
// XXX
//data.updateImagePositions()
data.sortTags()
that.load({
data: data,
crop_stack: collection_data.crop_stack,
crop_stack: collection_data.crop_stack
&& collection_data.crop_stack.slice(),
collections: that.collections,
collection_order: that.collection_order,
// NOTE: we do not need to pass collections
// and order here as they stay in from
// the last .load(..) in merge mode...
//collections: that.collections,
//collection_order: that.collection_order,
}, true)
// maintain the .collection state...
@ -375,15 +392,15 @@ var CollectionActions = actions.Actions({
: mode == 'crop' ?
this.data.clone()
: this.data.clone()
.removeUnloadedGIDs())
.run(function(){
this.collection = collection
.run(function(){
this.collection = collection
// NOTE: we are doing this manually after .removeUnloadedGIDs(..)
// as the later will mess-up the structures
// inherited from the main .data, namely tags...
//this.tags = that.data.tags
}),
// optimization:
// avoid processing .tags as we'll
// overwrite them anyway later...
delete this.tags
})
.removeUnloadedGIDs()),
}
if(mode == 'crop' && this.crop_stack && depth != 0){
@ -410,10 +427,11 @@ var CollectionActions = actions.Actions({
var that = this
gid = this.data.getImage(gid)
//return Object.keys(this.collections || {})
return this.collection_order
return (this.collection_order || [])
.filter(function(c){
return !gid
|| that.collections[c].data.getImage(gid) })
return c != MAIN_COLLECTION_TITLE
&& (!gid
|| that.collections[c].data.getImage(gid)) })
}],
collect: ['- Collections/',
@ -484,6 +502,7 @@ var CollectionActions = actions.Actions({
this.saveCollection(collection)
}
}],
// XXX BUG: .uncollect(..) from crop messes up global tags...
uncollect: ['Collections|Image/$Uncollect image',
{browseMode: function(){ return !this.collection && 'disabled' }},
function(gids, collection){
@ -505,31 +524,30 @@ var CollectionActions = actions.Actions({
: [that.data.getImage(gid)] })
.reduce(function(a, b){ return a.concat(b) }, [])
/*/ NOTE: we are not using .data.updateImagePositions(gids, 'hide')
// here because it will remove the gids from everything
// while we need them removed only from ribbons...
var hideGIDs = function(){
var d = this
gids.forEach(function(gid){
var i = d.order.indexOf(gid)
Object.keys(d.ribbons).forEach(function(r){
delete d.ribbons[r][i]
})
})
}
//*/
if(this.collection == collection){
// need to keep this from updating .tags...
// XXX this seems a bit hacky...
var tags = this.data.tags
delete this.data.tags
this.data
//.run(hideGIDs)
.removeGIDs(gids)
.removeEmptyRibbons()
.run(function(){
this.tags = tags
this.sortTags()
})
}
// NOTE: we do both this and the above iff data is cloned...
// NOTE: if tags are saved to the collection it means that
// those tags are local to the collection and we do not
// need to protect them...
if(this.data !== this.collections[collection].data){
this.collections[collection].data
.removeGIDs(gids)
.removeEmptyRibbons()
}
this.collections[collection].data
//.run(hideGIDs)
.removeGIDs(gids)
.removeEmptyRibbons()
}],
@ -541,6 +559,7 @@ var CollectionActions = actions.Actions({
// their data on load as needed.
load: [function(json){
var that = this
var collections = {}
var c = json.collections || {}
var order = json.collection_order || Object.keys(c)
@ -550,6 +569,7 @@ var CollectionActions = actions.Actions({
}
Object.keys(c).forEach(function(title){
// load data...
var d = c[title].data instanceof data.Data ?
c[title].data
: data.Data
@ -564,6 +584,18 @@ var CollectionActions = actions.Actions({
data: d,
}
// NOTE: this can be done lazily when loading each collection
// but doing so will make the system more complex and
// confuse (or complicate) some code that expects
// .collections[*].crop_stack[*] to be instances of Data.
if(c[title].crop_stack){
state.crop_stack = c[title].crop_stack
.map(function(c){
return c instanceof data.Data ?
c
: data.Data(c) })
}
// copy the rest of collection data as-is...
Object.keys(c[title])
.forEach(function(key){
@ -761,6 +793,10 @@ module.Collection = core.ImageGridFeatures.Feature({
// XXX show collections in image metadata...
var UICollectionActions = actions.Actions({
browseCollections: ['Collections|Crop/$Collec$tions...',
core.doc`Collection list...
NOTE: collections are added live and not on dialog close...
`,
widgets.makeUIDialog(function(action){
var that = this
var to_remove = []
@ -774,34 +810,66 @@ var UICollectionActions = actions.Actions({
.addClass('highlighted')
})
var openHandler = function(_, title){
var gid = that.current
action ?
action.call(that, title)
: that.loadCollection(title)
that.focusImage(gid)
dialog.close()
}
var setCroppedState = function(title){
// indicate collection crop...
var cs =
title == (that.collection || MAIN_COLLECTION_TITLE) ?
that.crop_stack
: (that.collections || {})[title] ?
that.collections[title].crop_stack
: null
cs
&& this.find('.text').last()
.attr('cropped', cs.length)
}
//var collections = Object.keys(that.collections || {})
var collections = that.collection_order = that.collection_order || []
// main collection...
!action && collections.indexOf(MAIN_COLLECTION_TITLE) < 0
&& make([
MAIN_COLLECTION_TITLE,
],
{ events: {
update: function(_, title){
// make this look almost like a list element...
// XXX hack???
$(this).find('.text:first-child')
.before($('<span>')
.css('color', 'transparent')
.addClass('sort-handle')
.html('&#x2630;'))
setCroppedState
.call($(this), title)
},
open: openHandler,
}})
// collection list...
make.EditableList(collections,
{
unique: true,
sortable: 'y',
to_remove: to_remove,
itemopen: function(title){
var gid = that.current
action ?
action.call(that, title)
: that.loadCollection(title)
that.focusImage(gid)
dialog.close()
},
itemopen: openHandler,
normalize: function(title){
return title.trim() },
check: function(title){
return title.length > 0 },
// remove the 'x' button from main collection...
// XXX is this the correct way to go???
each: function(title){
title == MAIN_COLLECTION_TITLE
&& this.find('.button-container').remove() },
each: setCroppedState,
// XXX should this be "on close"???
itemadded: function(title){
action ?
that.newCollection(title)
@ -810,6 +878,7 @@ var UICollectionActions = actions.Actions({
disabled: action ? [MAIN_COLLECTION_TITLE] : false,
})
}, {
cls: 'collection-list',
// focus current collection...
selected: that.collection || MAIN_COLLECTION_TITLE,
})
@ -869,7 +938,7 @@ var UICollectionActions = actions.Actions({
dialog.update()
},
})
: make.Empty()
: make.Empty('No collections...')
})
.close(function(){
all.forEach(function(title){

View File

@ -56,6 +56,10 @@ var demo_images =
module.demo_images = {
a: {
orientation: 90,
tags: ['test'],
},
d: {
tags: ['test', 'bookmark']
},
f: {
orientation: 270,
@ -68,6 +72,13 @@ module.demo_images = {
},
}
// sync tags with images...
//demo_data = data.Data(demo_data)
// .tagsToImages(demo_images, 'merge')
// .tagsFromImages(demo_images, 'merge')
// .dumpJSON()
/*********************************************************************/

View File

@ -138,7 +138,7 @@ module.GLOBAL_KEYBOARD = {
'Collection': {
pattern: '.collection-mode',
Esc: 'loadCollection: "All" -- Load all images',
Esc: 'loadCollection: "ALL" -- Load all images',
},
'Range': {

View File

@ -567,6 +567,7 @@ core.ImageGridFeatures.Feature({
'reverseImages',
'reverseRibbons',
'cropGroup',
'syncTags',
],
function(target){ return this.reload() }],
],

View File

@ -1846,8 +1846,14 @@ module.Buttons = core.ImageGridFeatures.Feature({
}],
// update crop button status...
['load clear reload',
[[
'load',
'clear',
'reload',
],
function(){
var l = (this.crop_stack || []).length
$('.main-buttons.buttons .crop.button sub')
// XXX should this be here or in CSS???
.css({
@ -1855,11 +1861,38 @@ module.Buttons = core.ImageGridFeatures.Feature({
'width': '0px',
'overflow': 'visible',
})
.text(this.crop_stack ? this.crop_stack.length : '')
.text(l == 0 ? ''
: l > 99 ? '99+'
: l)
}],
// update collection button status...
['load clear reload collectionLoaded collectionUnloaded',
[[
'load',
'clear',
'reload',
'saveCollection',
'collectionLoaded',
'collectionUnloaded',
],
function(){
$('.main-buttons.buttons .collections.button')
.css({
'color': this.collection ? 'yellow' : '',
//'text-decoration': this.collection ? 'underline': '',
})
var l = this.collections_length
$('.main-buttons.buttons .collections.button sub')
.css({
'display': 'inline-block',
'width': '0px',
'overflow': 'visible',
//'color': this.collection ? 'yellow' : '',
})
.text(l > 99 ? '99+'
: l == 0 ? ''
: l)
/*
$('.main-buttons.buttons .collections.button sub')
// XXX should this be here or in CSS???
.css({
@ -1869,6 +1902,7 @@ module.Buttons = core.ImageGridFeatures.Feature({
'color': 'yellow',
})
.html(this.collection ? '&#9679;' : '')
//*/
}],
// update zoom button status...
['viewScale',

View File

@ -222,9 +222,44 @@ var DataPrototype = {
// Make a sparse list of image gids...
//
// Make sparse list out of gids...
// .makeSparseImages(gids)
// -> list
//
// Make sparse list out of gids and drop gids not in .order...
// .makeSparseImages(gids, true)
// .makeSparseImages(gids, null, null, true)
// -> list
// NOTE: this sets drop_non_order_gids...
//
// Plase gids into their .order positions into target...
// .makeSparseImages(gids, target)
// -> list
// NOTE: items in target on given gid .order positions will
// get overwritten...
//
// Plase gids into their .order positions into target and reposition
// overwritten target items...
// .makeSparseImages(gids, target, true)
// -> list
// NOTE: this sets keep_target_items...
//
// Plase gids into their .order positions into target and reposition
// overwritten target items and drop gids not in .order...
// .makeSparseImages(gids, target, true, true)
// -> list
// NOTE: this sets keep_target_items and drop_non_order_gids...
//
//
// This uses .order as the base for ordering the list.
//
// If target is given then it will get updated with the input gids.
// By default items in gids that are not present in .order are
// appended to the output/target tail after .order.length, which ever
// is greater (this puts these items out of reach of further calls
// of .makeSparseImages(..)).
// Setting drop_non_order_gids to true will drop these items from
// output.
//
//
// NOTE: this can be used to re-sort sections of a target ribbon,
// but care must be taken not to overwrite existing data...
@ -234,44 +269,65 @@ var DataPrototype = {
// (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){
// NOTE: keep_target_items has no effect if target is not given...
makeSparseImages: function(gids, target, keep_target_items, drop_non_order_gids){
if(arguments.length == 2 && target === true){
drop_non_order_gids = true
target = null
}
// avoid mutating gids...
gids = gids === target || keep_target_items ?
gids.slice()
: gids
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()
}
var rest = []
gids.forEach(function(e, i){
// if the element is in its place alredy do nothing...
if(e == order[i] && e == target[i]){
return
for(var i=0; i < gids.length; i++){
var e = gids[i]
// skip undefined...
if(e === undefined
// if the element is in its place alredy do nothing...
|| (e == order[i] && e == target[i])){
continue
}
// NOTE: try and avoid the expensive .indexOf(..) as much as
// possible...
i = e != order[i] ? order.indexOf(e) : i
if(i >= 0){
var o = target[i]
// try and avoid the expensive .indexOf(..) as much as possible...
var j = e != order[i] ? order.indexOf(e) : i
if(j >= 0){
// 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)
}
var o = target[j]
keep_target_items
&& o != null
// if the item is already in gids, forget it...
// NOTE: this is to avoid juggling loops...
&& gids.indexOf(o) < 0
// look at o again later...
// NOTE: we should not loop endlessly here as target
// will eventually get exhausted...
&& gids.push(o)
target[i] = e
target[j] = e
// handle elements in gids that are not in .order
} else if(!drop_non_order_gids){
rest.push(e)
}
})
}
// avoid duplicating target items...
rest = rest
.filter(function(e){ return target.indexOf(e) < 0 })
if(rest.length > 0){
target.length = Math.max(order.length, target.length)
target.splice(target.length, 0, ...rest)
}
return target
},
@ -1271,22 +1327,31 @@ var DataPrototype = {
// .updateImagePositions()
// -> data
//
// Reposition item(s)
// Full sort and remove items not in .order
// .updateImagePositions('remove')
// -> data
//
// Reposition specific item(s)...
// .updateImagePositions(gid|index)
// .updateImagePositions([gid|index, .. ])
// -> data
//
// Reposition item(s) and the item(s) they replace
// Reposition item(s) and the item(s) they replace...
// .updateImagePositions(gid|index, 'keep')
// .updateImagePositions([gid|index, ..], 'keep')
// -> data
//
// Hide item(s) from lists
// Hide item(s) from lists...
// .updateImagePositions(gid|index, 'hide')
// .updateImagePositions([gid|index, ..], 'hide')
// -> data
//
// Remove item(s) from lists
// Remove item(s) from lists...
// .updateImagePositions(gid|index, 'remove')
// .updateImagePositions([gid|index, ..], 'remove')
// -> data
//
//
// NOTE: hide will not change the order of other items while remove
// will do a full sort...
// NOTE: in any case other that the first this will not try to
@ -1295,6 +1360,10 @@ var DataPrototype = {
// XXX needs more thought....
// do we need to move images by this???
updateImagePositions: function(from, mode, direction){
if(['keep', 'hide', 'remove'].indexOf(from) >= 0){
mode = from
from = null
}
from = from != null && from.constructor !== Array ? [from] : from
var r = this.getRibbon('current')
@ -1304,7 +1373,9 @@ var DataPrototype = {
// resort...
if(from == null){
set[key] = this.makeSparseImages(cur)
set[key] = mode == 'remove' ?
this.makeSparseImages(cur, true)
: this.makeSparseImages(cur)
// remove/hide elements...
} else if(mode == 'remove' || mode == 'hide'){
@ -1313,7 +1384,7 @@ var DataPrototype = {
})
// if we are removing we'll also need to resort...
if(mode == 'remove'){
set[key] = this.makeSparseImages(cur)
set[key] = this.makeSparseImages(cur, true)
}
// place and keep existing...
@ -1328,7 +1399,7 @@ var DataPrototype = {
// maintain focus...
if(from && from.indexOf(this.current) >= 0){
this.focusImage('r')
this.focusImage(r)
}
return this
@ -2613,7 +2684,7 @@ var DataPrototype = {
// NOTE: this may result in empty ribbons...
removeUnloadedGIDs: function(){
this.order = this.getImages('loaded')
this.updateImagePositions()
this.updateImagePositions('remove')
return this
},
@ -2647,7 +2718,8 @@ var DataPrototype = {
}
this.order = order
this.updateImagePositions()
this.updateImagePositions('remove')
return this
},

View File

@ -559,11 +559,11 @@ function(data, options){
//
// length_limit: <number>,
//
// // Called when an item is opend...
// // Item open event handler...
// //
// // NOTE: this is simpler that binding to the global open event
// // and filtering through the results...
// itemopen: function(value){ ... },
// itemopen: function(evt, value){ ... },
//
// // Check input value...
// check: function(value){ ... },
@ -910,7 +910,7 @@ function(list, options){
})
options.itemopen
&& res.on('open', function(){ options.itemopen(dialog.selected) })
&& res.on('open', function(evt){ options.itemopen(evt, dialog.selected) })
res = res.toArray()
@ -2026,6 +2026,13 @@ var BrowserPrototype = {
//
// // event handlers...
// events: {
// // item-specific update events...
// //
// // item added to dom by .update(..)...
// // NOTE: this is not propagated up, thus it will not trigger
// // the list update.
// update: <handler>,
//
// <event>: <handler>,
// ...
// },
@ -2477,6 +2484,7 @@ var BrowserPrototype = {
})
//--------------------------------- user event handlers ---
res.on('update', function(evt){ evt.stopPropagation() })
Object.keys(opts.events || {})
.forEach(function(evt){
res.on(evt, opts.events[evt]) })
@ -2498,8 +2506,11 @@ var BrowserPrototype = {
res.appendTo(l)
}
}
//---------------------------------------------------------
//------------------------------- item lifecycle events ---
res.trigger('update', txt)
//---------------------------------------------------------
return res
}

View File

@ -238,12 +238,12 @@ $(function(){
// load some testing data if nothing else loaded...
if(!this.url_history || Object.keys(this.url_history).length == 0){
// NOTE: we can (and do) load this in parts...
this.loadDemoIndex()
// this is needed when loading legacy sources that do not have tags
// synced...
// do not do for actual data...
//.syncTags()
this
.loadDemoIndex()
// this is needed when loading legacy sources that do not have tags
// synced...
// do not do for actual data...
.syncTags('both')
}
})
.start()