mirror of
https://github.com/flynx/ImageGrid.git
synced 2025-10-28 18:00:09 +00:00
628 lines
16 KiB
JavaScript
Executable File
628 lines
16 KiB
JavaScript
Executable File
/**********************************************************************
|
|
*
|
|
*
|
|
*
|
|
**********************************************************************/
|
|
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
|
|
(function(require){ var module={} // make module AMD/node compatible...
|
|
/*********************************************************************/
|
|
|
|
var toggler = require('lib/toggler')
|
|
var actions = require('lib/actions')
|
|
var features = require('lib/features')
|
|
|
|
var data = require('imagegrid/data')
|
|
var images = require('imagegrid/images')
|
|
|
|
var core = require('features/core')
|
|
var base = require('features/base')
|
|
var ui = require('features/ui')
|
|
|
|
|
|
|
|
/*********************************************************************/
|
|
|
|
// XXX should we rename this to "select"???
|
|
|
|
// target can be:
|
|
// 'all'
|
|
// 'loaded'
|
|
// 'ribbon' - current ribbon
|
|
// ribbon - specific ribbon (gid)
|
|
// Array
|
|
//
|
|
// NOTE: of no data is defined this will not have any effect...
|
|
// NOTE: we are not using the vanilla toggler here as it can't handle
|
|
// toggling independently multiple elements...
|
|
//
|
|
// XXX this is really slow on large sets of images...
|
|
function makeTagTogglerAction(tag){
|
|
// get actual target gids...
|
|
var _getTarget = function(target){
|
|
target = target || this.current
|
|
target = (target == 'all'
|
|
|| target == 'loaded'
|
|
|| target in this.data.ribbons) ?
|
|
this.data.getImages(target)
|
|
: target == 'ribbon' ?
|
|
this.data.getImages('current')
|
|
: target
|
|
return target == null ?
|
|
[]
|
|
: target instanceof Array ?
|
|
target
|
|
: [target]
|
|
}
|
|
|
|
// the toggler...
|
|
var _tagToggler = toggler.Toggler('current',
|
|
function(target, action){
|
|
target = _getTarget.call(this, target)
|
|
// get state...
|
|
if(action == null){
|
|
var res = this.data.toggleTag(tag, target, '?')
|
|
return res.length == 1 ? res[0] : res
|
|
|
|
} else if(action == 'on'){
|
|
this.tag(tag, target)
|
|
|
|
} else if(action == 'off'){
|
|
this.untag(tag, target)
|
|
}
|
|
},
|
|
['off', 'on'])
|
|
|
|
// the action...
|
|
var action = function(target, action){
|
|
// special case: no data...
|
|
if(this.data == null){
|
|
return action == '??' ? ['off', 'on'] : 'off'
|
|
|
|
// special case: multiple targets and toggle action...
|
|
} else if((target == 'all' || target == 'loaded' || target == 'ribbon'
|
|
|| target instanceof Array)
|
|
&& (action == null || action == 'next' || action == 'prev'
|
|
|| action == '!')){
|
|
var res = []
|
|
var that = this
|
|
var on = []
|
|
var off = []
|
|
target = _getTarget.call(this, target)
|
|
target
|
|
.forEach(function(gid){
|
|
if((that.data.getTags(gid).indexOf(tag) < 0)
|
|
// invert check if action is '!'...
|
|
+ (action == '!' ? -1 : 0)){
|
|
on.push(gid)
|
|
res.push('on')
|
|
|
|
} else {
|
|
off.push(gid)
|
|
res.push('off')
|
|
}
|
|
})
|
|
|
|
that.tag(tag, on)
|
|
that.untag(tag, off)
|
|
|
|
return res.length == 1 ? res[0] : res
|
|
}
|
|
|
|
// normal case...
|
|
return _tagToggler.call(this, target, action)
|
|
}
|
|
|
|
// cheating a bit...
|
|
action.__proto__ = toggler.Toggler.prototype
|
|
action.constructor = toggler.Toggler
|
|
return action
|
|
}
|
|
|
|
|
|
// Build a tag toggler undo set of attrs...
|
|
//
|
|
// This will add:
|
|
// 'undoable' - predicate to check if we need to undo, to handle
|
|
// introspection calls correctly...
|
|
// 'undo' - undo function...
|
|
//
|
|
var undoTag = function(action){
|
|
return {
|
|
// do not journal calls that have no side-effects, e.g. toggler
|
|
// introspection...
|
|
// XXX should this be a generic predicate???
|
|
undoable: function(a){
|
|
// handle ribbon-wide operations...
|
|
// NOTE: this is specific to .toggleMark(..)
|
|
if(a.args[0] == 'ribbon' && action == 'toggleMark'){
|
|
a.state = this.markedInRibbon()
|
|
return true
|
|
}
|
|
// skip introspection...
|
|
return a.args.indexOf('?') < 0
|
|
&& a.args.indexOf('??') < 0
|
|
},
|
|
undo: function(a){
|
|
// restore state...
|
|
if(a.state){
|
|
this[action]('ribbon', 'off')
|
|
this[action](a.state, 'on')
|
|
|
|
// reverse state...
|
|
} else {
|
|
this[action].apply(this,
|
|
// XXX is argument handling here too optimistic???
|
|
a.args.map(function(e){
|
|
return e == 'on' ? 'off'
|
|
: e == 'off' ? 'on'
|
|
: e }))
|
|
}
|
|
},
|
|
} }
|
|
|
|
|
|
// Shift marked image action constructor...
|
|
//
|
|
// Shift marked images up/down
|
|
// shiftMarked('up')
|
|
// shiftMarked('down')
|
|
// -> action
|
|
//
|
|
// The resulting action affects only images in current ribbon...
|
|
//
|
|
// NOTE: this specific to marked/selected images...
|
|
var shiftMarked = function(direction){
|
|
return function(ribbon){
|
|
var that = this
|
|
var marked = this.markedInRibbon(ribbon)
|
|
var next
|
|
|
|
// need to shift focus...
|
|
if(marked.indexOf(this.current) >= 0){
|
|
var d = this.direction == 'right' ? 'next' : 'prev'
|
|
|
|
var getNext = function(direction){
|
|
var next = that.data.getImage(direction)
|
|
while(next != null && marked.indexOf(next) >= 0){
|
|
next = that.data.getImage(next, direction)
|
|
}
|
|
return next
|
|
}
|
|
|
|
next = getNext(d)
|
|
|| getNext(d == 'next' ? 'prev' : 'next')
|
|
|
|
next != null
|
|
&& this.data.focusImage(next)
|
|
}
|
|
|
|
// shift the image...
|
|
this.data['shiftImage'+ direction.capitalize()](marked)
|
|
|
|
// obey the shiftImage protocol...
|
|
this.shiftImage.apply(this, marked)
|
|
}
|
|
}
|
|
|
|
|
|
// Shift undo function constructor...
|
|
//
|
|
// NOTE: this is specific to shiftMarkedUp/shiftMarkedDown...
|
|
var undoShift = function(undo){
|
|
return function(a){
|
|
this[undo](this.data.getRibbon(
|
|
undo == 'shiftMarkedUp' ? 'next' : 'prev',
|
|
a.args.length == 0 ? a.current : a.args[0])) }}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------
|
|
|
|
var ImageMarkActions = actions.Actions({
|
|
|
|
// a shorthand...
|
|
// NOTE: this will return a copy...
|
|
//
|
|
// XXX should we add a caching scheme here???
|
|
// ...it would require invalidation on tagging...
|
|
// the problem is that on large sets this may take up quite a
|
|
// chunk of memory...
|
|
get marked(){
|
|
if(this.data == null
|
|
|| this.data.tags == null
|
|
|| !('marked' in this.data.tags)){
|
|
return []
|
|
}
|
|
//return this.data.tags['marked'].slice()
|
|
return this.data.getImages(this.data.tags['marked'])
|
|
},
|
|
|
|
markedInRibbon: ['- Mark|Ribbon/',
|
|
function(ribbon){
|
|
var ribbon = this.data.getRibbon(ribbon)
|
|
var images = this.data.makeSparseImages(this.data.getImages(ribbon))
|
|
|
|
return this.data.makeSparseImages(this.marked)
|
|
// NOTE: this will also filter out undefined positions...
|
|
.filter(function(img, i){ return images[i] != null })
|
|
}],
|
|
|
|
prevMarked: ['Mark|Navigate/Previous marked image',
|
|
{browseMode: function(target){
|
|
return this.data.getImage('current', 'before', this.marked) == null && 'disabled' }},
|
|
function(mode){ this.prevTagged('marked', mode) }],
|
|
nextMarked: ['Mark|Navigate/Next marked image',
|
|
{browseMode: function(target){
|
|
return this.data.getImage('current', 'after', this.marked) == null && 'disabled' }},
|
|
function(mode){ this.nextTagged('marked', mode) }],
|
|
|
|
cropMarked: ['Mark|Crop/Crop $marked images',
|
|
{browseMode: function(target){
|
|
return this.marked.length == 0 && 'disabled' }},
|
|
function(flatten){ this.cropTagged('marked', 'any', flatten) }],
|
|
|
|
removeMarkedFromCrop: ['Mark|Crop/Remove marked from crop',
|
|
{browseMode: function(target){
|
|
return (this.marked.length == 0 || !this.cropped) && 'disabled' }},
|
|
'removeFromCrop: marked'],
|
|
|
|
rotateMarkedCW: ['Mark/Rotate marked clockwise',
|
|
{browseMode: 'cropMarked'},
|
|
'rotateCW: marked'],
|
|
rotateMarkedCCW: ['Mark/Rotate marked counterclockwise',
|
|
{browseMode: 'cropMarked'},
|
|
'rotateCCW: marked'],
|
|
flipMarkedVertical: ['Mark/Flip marked vertically',
|
|
{browseMode: 'cropMarked'},
|
|
'flipVertical: marked'],
|
|
flipMarkedHorizontal: ['Mark/Flip marked horizontally',
|
|
{browseMode: 'cropMarked'},
|
|
'flipHorizontal: marked'],
|
|
})
|
|
|
|
|
|
// NOTE: this is usable without ribbons...
|
|
var ImageMarks =
|
|
module.ImageMarks = core.ImageGridFeatures.Feature({
|
|
title: '',
|
|
doc: '',
|
|
|
|
tag: 'image-marks',
|
|
|
|
depends: [
|
|
'base',
|
|
],
|
|
suggested: [
|
|
'image-marks-edit',
|
|
'ui-image-marks',
|
|
'image-marks-groups',
|
|
],
|
|
|
|
actions: ImageMarkActions,
|
|
})
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
var ImageMarkEditActions = actions.Actions({
|
|
// Common use-cases:
|
|
// Toggle mark on current image
|
|
// .toggleMark()
|
|
//
|
|
// Mark current ribbon
|
|
// .toggleMark('ribbon', 'on')
|
|
//
|
|
// Unmark all loaded images
|
|
// .toggleMark('loaded', 'off')
|
|
//
|
|
// Invert marks on current ribbon
|
|
// .toggleMark('ribbon')
|
|
//
|
|
toggleMark: ['Mark|Image/Image $mark',
|
|
undoTag('toggleMark'),
|
|
makeTagTogglerAction('marked')],
|
|
toggleMarkBlock: ['Mark/Invert $block marks',
|
|
core.doc`A block is a set of adjacent images either marked on unmarked
|
|
in the same way
|
|
`,
|
|
function(target){
|
|
target = this.data.getImage(target)
|
|
|
|
var ribbon = this.data.makeSparseImages(this.data.getImages(target))
|
|
var marked = this.data.makeSparseImages(this.markedInRibbon(target))
|
|
|
|
var c = ribbon.indexOf(target)
|
|
var state = !!marked[c]
|
|
|
|
var block = [target]
|
|
|
|
// pre block...
|
|
var i = c-1
|
|
while(i >= 0
|
|
// NOTE: we are avoiding mixing up a tag not set condition
|
|
// with image i not in ribbon...
|
|
&& (!ribbon[i] || !!marked[i] == state)){
|
|
ribbon[i]
|
|
&& block.splice(0, 0, ribbon[i])
|
|
i--
|
|
}
|
|
|
|
// post block...
|
|
var i = c+1
|
|
while(i < ribbon.length
|
|
// NOTE: we are avoiding mixing up a tag not set condition
|
|
// with image i not in ribbon...
|
|
&& (!ribbon[i] || !!marked[i] == state)){
|
|
ribbon[i]
|
|
&& block.push(ribbon[i])
|
|
i++
|
|
}
|
|
|
|
// do the marking...
|
|
return this.toggleMark(block, state ? 'off' : 'on')
|
|
}],
|
|
toggleMarkRibbon: ['Mark/$Invert ribbon marks',
|
|
'toggleMark: "ribbon" ...' ],
|
|
toggleMarkLoaded: ['Mark/Invert marks',
|
|
'toggleMark: "loaded" ...' ],
|
|
|
|
markRibbon: ['Mark/Mark $ribbon',
|
|
'toggleMark: "ribbon" "on"' ],
|
|
markLoaded: ['Mark/Mark $all',
|
|
'toggleMark: "loaded" "on"' ],
|
|
|
|
markTagged: ['- Mark/Mark images by tags',
|
|
function(tags, mode){
|
|
var selector = mode == 'any' ? 'getTaggedByAny' : 'getTaggedByAll'
|
|
|
|
var that = this
|
|
this.data[selector](tags).forEach(function(gid){
|
|
that.toggleMark(gid, 'on')
|
|
})
|
|
}],
|
|
|
|
shiftMarkedUp: ['Mark/Shift marked u$p',
|
|
{undo: undoShift('shiftMarkedDown'),
|
|
browseMode: 'cropMarked'},
|
|
shiftMarked('up')],
|
|
shiftMarkedDown: ['Mark/Shift marked $down',
|
|
{undo: undoShift('shiftMarkedUp'),
|
|
browseMode: 'cropMarked'},
|
|
shiftMarked('down')],
|
|
|
|
// XXX undo...
|
|
shiftMarkedAfter: ['Mark|Image/Shift marked $after',
|
|
{browseMode: 'cropMarked'},
|
|
function(target){
|
|
this.shiftImageTo(this.marked, target || 'current', 'after') }],
|
|
// XXX undo...
|
|
shiftMarkedBefore: ['Mark|Image/Shift marked $b$efore',
|
|
{browseMode: 'cropMarked'},
|
|
function(target){
|
|
this.shiftImageTo(this.marked, target || 'current', 'before') }],
|
|
|
|
unmarkRibbon: ['Mark/Unmark ribbon',
|
|
{browseMode: 'cropMarked'},
|
|
'toggleMark: "ribbon" "off"'],
|
|
unmarkLoaded: ['Mark/$Unmark all',
|
|
{browseMode: 'cropMarked'},
|
|
'toggleMark: "loaded" "off"'],
|
|
})
|
|
|
|
var ImageEditMarks =
|
|
module.ImageEditMarks = core.ImageGridFeatures.Feature({
|
|
title: '',
|
|
doc: '',
|
|
|
|
tag: 'image-marks-edit',
|
|
|
|
depends: [
|
|
'tags-edit',
|
|
],
|
|
suggested: [
|
|
],
|
|
|
|
actions: ImageMarkEditActions,
|
|
})
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
var ImageMarkGroupActions = actions.Actions({
|
|
// NOTE: this will only group loaded images...
|
|
groupMarked: ['Group|Mark/-70:Group loaded marked images',
|
|
{journal: true,
|
|
browseMode: 'cropMarked'},
|
|
function(){
|
|
this.group(this.data.getImages(this.marked)) }],
|
|
})
|
|
|
|
var ImageMarkGroup =
|
|
module.ImageMarkGroup = core.ImageGridFeatures.Feature({
|
|
title: '',
|
|
doc: '',
|
|
|
|
tag: 'image-marks-groups',
|
|
|
|
depends: [
|
|
'image-marks-edit',
|
|
'image-group-edit',
|
|
],
|
|
suggested: [
|
|
],
|
|
|
|
actions: ImageMarkGroupActions,
|
|
})
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
var ImageMarksUI =
|
|
module.ImageMarksUI = core.ImageGridFeatures.Feature({
|
|
title: '',
|
|
doc: '',
|
|
|
|
tag: 'ui-image-marks',
|
|
|
|
depends: [
|
|
'ui',
|
|
'image-marks',
|
|
],
|
|
|
|
handlers: [
|
|
// XXX is a full reload a good thing here???
|
|
[[
|
|
'shiftMarkedUp',
|
|
'shiftMarkedDown',
|
|
],
|
|
function(ribbon){ this.reload(true) }],
|
|
|
|
// XXX is this the right way to go???
|
|
['updateImage', function(_, gid, img){
|
|
// NOTE: we are not using .toggleMark(..) here as this
|
|
// does not need to depend on the 'edit' feature...
|
|
this.ribbons
|
|
&& this.ribbons
|
|
.toggleImageMark(
|
|
gid,
|
|
'marked',
|
|
this.data.hasTag(gid, 'marked') ? 'on' : 'off')
|
|
}],
|
|
],
|
|
})
|
|
|
|
|
|
//---------------------------------------------------------------------
|
|
|
|
var ImageBookmarkActions = actions.Actions({
|
|
|
|
// a shorthand...
|
|
//
|
|
// NOTE: this will return a copy...
|
|
//
|
|
// XXX should we add a caching scheme here???
|
|
// ...it would require invalidation on tagging...
|
|
// the problem is that on large sets this may take up quite a
|
|
// chunk of memory...
|
|
get bookmarked(){
|
|
if(this.data == null
|
|
|| this.data.tags == null
|
|
|| !('bookmark' in this.data.tags)){
|
|
return []
|
|
}
|
|
//return this.data.tags['bookmark'].slice()
|
|
return this.data.getImages(this.data.tags['bookmark'])
|
|
},
|
|
|
|
prevBookmarked: ['Bookmark|Navigate/Previous bookmarked image',
|
|
{browseMode: function(target){
|
|
return this.data.getImage('current', 'before', this.bookmarked) == null && 'disabled' }},
|
|
function(mode){ this.prevTagged('bookmark', mode) }],
|
|
nextBookmarked: ['Bookmark|Navigate/Next bookmarked image',
|
|
{browseMode: function(target){
|
|
return this.data.getImage('current', 'after', this.bookmarked) == null && 'disabled' }},
|
|
function(mode){ this.nextTagged('bookmark', mode) }],
|
|
|
|
cropBookmarked: ['Bookmark|Crop/Crop $bookmarked images',
|
|
{browseMode: function(target){
|
|
return this.bookmarked.length == 0 && 'disabled' }},
|
|
function(flatten){ this.cropTagged('bookmark', 'any', flatten) }],
|
|
})
|
|
|
|
// NOTE: this is usable without ribbons...
|
|
var ImageBookmarks =
|
|
module.ImageBookmarks = core.ImageGridFeatures.Feature({
|
|
title: '',
|
|
doc: '',
|
|
|
|
tag: 'image-bookmarks',
|
|
|
|
depends: [
|
|
'base',
|
|
],
|
|
suggested: [
|
|
'image-bookmarks-edit',
|
|
'ui-image-bookmarks',
|
|
],
|
|
|
|
actions: ImageBookmarkActions,
|
|
|
|
})
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
var ImageBookmarkEditActions = actions.Actions({
|
|
toggleBookmark: ['Bookmark|Image/Image $bookmark',
|
|
undoTag('toggleBookmark'),
|
|
makeTagTogglerAction('bookmark')],
|
|
// action can be:
|
|
// 'on' - toggle all on
|
|
// 'off' - toggle all off
|
|
// 'next' - toggle each image to next state
|
|
toggleBookmarkOnMarked: ['Bookmark|Mark/-70:Toggle bookmark on maked images',
|
|
{browseMode: 'cropMarked'},
|
|
function(action){
|
|
return this.toggleBookmark(this.data.getTaggedByAny('marked'), action)
|
|
}],
|
|
})
|
|
|
|
var ImageBookmarksEdit =
|
|
module.ImageBookmarksEdit = core.ImageGridFeatures.Feature({
|
|
title: '',
|
|
doc: '',
|
|
|
|
tag: 'image-bookmarks-edit',
|
|
|
|
depends: [
|
|
'tags-edit',
|
|
],
|
|
suggested: [
|
|
'ui-image-bookmarks',
|
|
],
|
|
|
|
actions: ImageBookmarkEditActions,
|
|
})
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
var ImageBookmarksUI =
|
|
module.ImageBookmarksUI = core.ImageGridFeatures.Feature({
|
|
title: '',
|
|
doc: '',
|
|
|
|
tag: 'ui-image-bookmarks',
|
|
|
|
depends: [
|
|
'ui',
|
|
'image-bookmarks',
|
|
],
|
|
|
|
handlers: [
|
|
// XXX is this the right way to go???
|
|
['updateImage', function(_, gid, img){
|
|
this.ribbons
|
|
&& this.ribbons
|
|
.toggleImageMark(
|
|
gid,
|
|
'bookmark',
|
|
this.data.hasTag(gid, 'bookmark') ? 'on' : 'off')
|
|
}],
|
|
],
|
|
})
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------
|
|
|
|
core.ImageGridFeatures.Feature('marks', [
|
|
'image-marks',
|
|
'image-bookmarks',
|
|
])
|
|
|
|
|
|
|
|
/**********************************************************************
|
|
* vim:set ts=4 sw=4 : */ return module })
|