mirror of
https://github.com/flynx/ImageGrid.git
synced 2025-10-28 18:00:09 +00:00
445 lines
11 KiB
JavaScript
Executable File
445 lines
11 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...
|
|
function makeTagTogglerAction(tag){
|
|
// get actual target gids...
|
|
var _getTarget = function(target){
|
|
target = target || 'current'
|
|
target = target == 'all'
|
|
|| target == 'loaded'
|
|
|| target in this.data.ribbons
|
|
? this.data.getImages(target)
|
|
: target == 'ribbon' ? this.data.getImages('current')
|
|
: target
|
|
return target.constructor !== 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
|
|
target = _getTarget.call(this, target)
|
|
target.forEach(function(t){
|
|
if((that.data.getTags(t).indexOf(tag) < 0)
|
|
// invert check if action is '!'...
|
|
+ (action == '!' ? -1 : 0)){
|
|
that.tag(tag, t)
|
|
res.push('on')
|
|
|
|
} else {
|
|
that.untag(tag, t)
|
|
res.push('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){
|
|
return a.args.indexOf('?') < 0
|
|
&& a.args.indexOf('??') < 0 },
|
|
undo: function(a){
|
|
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])) }}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------
|
|
|
|
// XXX .toggleMarkBlock(..) not done yet...
|
|
var ImageMarkActions = actions.Actions({
|
|
|
|
// a shorthand...
|
|
// NOTE: this will return a copy...
|
|
get marked(){
|
|
if(this.data == null
|
|
|| this.data.tags == null
|
|
|| !('selected' in this.data.tags)){
|
|
return []
|
|
}
|
|
return this.data.tags['selected'].slice()
|
|
},
|
|
|
|
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 })
|
|
}],
|
|
|
|
// 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('selected')],
|
|
toggleMarkBlock: ['Mark/Mark $block',
|
|
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')
|
|
}],
|
|
|
|
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')
|
|
})
|
|
}],
|
|
|
|
prevMarked: ['Mark|Navigate/Previous marked image',
|
|
function(mode){ this.prevTagged('selected', mode) }],
|
|
nextMarked: ['Mark|Navigate/Next marked image',
|
|
function(mode){ this.nextTagged('selected', mode) }],
|
|
|
|
shiftMarkedUp: ['Mark|Ribbon/Shift marked $up',
|
|
{undo: undoShift('shiftMarkedDown')},
|
|
shiftMarked('up')],
|
|
shiftMarkedDown: ['Mark|Ribbon/Shift marked $down',
|
|
{undo: undoShift('shiftMarkedUp')},
|
|
shiftMarked('down')],
|
|
|
|
cropMarked: ['Mark|Crop/Crop $marked images',
|
|
function(flatten){ this.cropTagged('selected', 'any', flatten) }],
|
|
})
|
|
|
|
|
|
// NOTE: this is usable without ribbons...
|
|
var ImageMarks =
|
|
module.ImageMarks = core.ImageGridFeatures.Feature({
|
|
title: '',
|
|
doc: '',
|
|
|
|
tag: 'image-marks',
|
|
|
|
depends: [
|
|
'base'
|
|
],
|
|
suggested: [
|
|
'ui-image-marks',
|
|
],
|
|
|
|
actions: ImageMarkActions,
|
|
})
|
|
|
|
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){
|
|
// update only when ribbons are preset...
|
|
if(this.ribbons != null){
|
|
if(this.toggleMark(gid, '?') == 'on'){
|
|
this.ribbons.toggleImageMark(gid, 'selected', 'on')
|
|
} else {
|
|
this.ribbons.toggleImageMark(gid, 'selected', 'off')
|
|
}
|
|
}
|
|
}],
|
|
],
|
|
})
|
|
|
|
|
|
//---------------------------------------------------------------------
|
|
|
|
var ImageBookmarkActions = actions.Actions({
|
|
|
|
// a shorthand...
|
|
// NOTE: this will return a copy...
|
|
get bookmarked(){
|
|
if(this.data == null
|
|
|| this.data.tags == null
|
|
|| !('bookmark' in this.data.tags)){
|
|
return []
|
|
}
|
|
return this.data.tags['bookmark'].slice()
|
|
},
|
|
|
|
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/Bookmark on maked images',
|
|
function(action){
|
|
return this.toggleBookmark(this.data.getTaggedByAny('selected'), action)
|
|
}],
|
|
|
|
prevBookmarked: ['Bookmark|Navigate/Previous bookmarked image',
|
|
function(mode){ this.prevTagged('bookmark', mode) }],
|
|
nextBookmarked: ['Bookmark|Navigate/Next bookmarked image',
|
|
function(mode){ this.nextTagged('bookmark', mode) }],
|
|
|
|
cropBookmarked: ['Bookmark|Crop/Crop $bookmarked images',
|
|
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: [
|
|
'ui-image-bookmarks',
|
|
],
|
|
|
|
actions: ImageBookmarkActions,
|
|
|
|
})
|
|
|
|
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){
|
|
// update only when ribbons are preset...
|
|
if(this.ribbons != null){
|
|
if(this.toggleBookmark(gid, '?') == 'on'){
|
|
this.ribbons.toggleImageMark(gid, 'bookmark', 'on')
|
|
} else {
|
|
this.ribbons.toggleImageMark(gid, 'bookmark', 'off')
|
|
}
|
|
}
|
|
}],
|
|
],
|
|
})
|
|
|
|
|
|
//---------------------------------------------------------------------
|
|
|
|
core.ImageGridFeatures.Feature('marks', [
|
|
'image-marks',
|
|
'image-bookmarks',
|
|
])
|
|
|
|
|
|
|
|
/**********************************************************************
|
|
* vim:set ts=4 sw=4 : */ return module })
|