mirror of
https://github.com/flynx/ImageGrid.git
synced 2025-10-28 18:00:09 +00:00
added action chaining and added the resize protocol (see: features/ui.js#600)...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
9059b615fd
commit
d02795935e
@ -310,7 +310,8 @@ module.CurrentImageIndicator = core.ImageGridFeatures.Feature({
|
||||
// - before animation when scaling up
|
||||
// - after when scaling down
|
||||
// This is done to make the visuals consistent...
|
||||
['fitImage.pre fitRibbon.pre setScale.pre',
|
||||
//['fitImage.pre fitRibbon.pre setScale.pre',
|
||||
['resizing.pre',
|
||||
function(w1){
|
||||
var w0 = this.screenwidth
|
||||
w1 = w1 || 1
|
||||
|
||||
@ -206,8 +206,8 @@ var SingleImageActions = actions.Actions({
|
||||
// Set scale 'units' for different viewes...
|
||||
//
|
||||
// NOTE: the units are actually properties used to get/set the values.
|
||||
'single-image-scale-unit': 'screenwidth',
|
||||
'ribbon-scale-unit': 'screenfit',
|
||||
'single-image-scale-unit': 'screenfit',
|
||||
'ribbon-scale-unit': 'screenwidth',
|
||||
|
||||
// The threshold from which the image block starts to tend to
|
||||
// screen proportions...
|
||||
@ -252,30 +252,7 @@ module.SingleImageView = core.ImageGridFeatures.Feature({
|
||||
actions: SingleImageActions,
|
||||
|
||||
handlers:[
|
||||
// XXX HACK: force browser to redraw off-screen images...
|
||||
// ...it appears that chrome cheats by not resizing off-screen
|
||||
// images properly after changing scale...
|
||||
// XXX this is still not perfect...
|
||||
// ...if needed do a .reload() / ctrl-r
|
||||
// XXX try doing this a timeout after resize....
|
||||
['focusImage',
|
||||
function(){
|
||||
var img = this.ribbons.getImage()
|
||||
var d = Math.max(img.attr('preview-width')*1, img.attr('preview-width')*1)
|
||||
var D = this.ribbons.getVisibleImageSize('max')
|
||||
|
||||
if(this.config['-single-image-redraw-on-focus']
|
||||
// NOTE: redraw only when close to original preview
|
||||
// size -- this is where chrome cheats and
|
||||
// shows images blurry...
|
||||
// XXX this causes some images to jump a bit, aligning
|
||||
// to nearest pixel might fix this...
|
||||
&& Math.abs(D-d)/D < 0.30
|
||||
&& this.toggleSingleImage('?') == 'on'){
|
||||
this.scale = this.scale
|
||||
}
|
||||
}],
|
||||
['fitImage.post setScale.post',
|
||||
['resizing.post',
|
||||
function(){
|
||||
// prevent this from doing anything while no viewer...
|
||||
if(!this.ribbons
|
||||
@ -321,6 +298,7 @@ module.SingleImageView = core.ImageGridFeatures.Feature({
|
||||
if(state != pre_state){
|
||||
this.config['ribbon-scale'] =
|
||||
this[this.config['ribbon-scale-unit']]
|
||||
|
||||
this[this.config['single-image-scale-unit']] =
|
||||
this.config['single-image-scale']
|
||||
|| this[this.config['single-image-scale-unit']]
|
||||
@ -346,6 +324,7 @@ module.SingleImageView = core.ImageGridFeatures.Feature({
|
||||
if(state != pre_state){
|
||||
this.config['single-image-scale'] =
|
||||
this[this.config['single-image-scale-unit']]
|
||||
|
||||
this[this.config['ribbon-scale-unit']] =
|
||||
this.config['ribbon-scale']
|
||||
|| this[this.config['ribbon-scale-unit']]
|
||||
@ -353,6 +332,43 @@ module.SingleImageView = core.ImageGridFeatures.Feature({
|
||||
}
|
||||
}
|
||||
}],
|
||||
|
||||
// Force browser to redraw off-screen images...
|
||||
//
|
||||
// This appears that chrome cheats by not resizing off-screen
|
||||
// images properly after changing scale...
|
||||
//
|
||||
// XXX this is still not perfect...
|
||||
// ...if needed do a .reload() / ctrl-r
|
||||
[[
|
||||
'resizing.post',
|
||||
'toggleSingleImage.pre',
|
||||
],
|
||||
function(){
|
||||
this.__did_resize = true
|
||||
}],
|
||||
[[
|
||||
'focusImage',
|
||||
'toggleSingleImage',
|
||||
],
|
||||
function(){
|
||||
var img = this.ribbons.getImage()
|
||||
var d = Math.max(img.attr('preview-width')*1, img.attr('preview-width')*1)
|
||||
var D = this.ribbons.getVisibleImageSize('max')
|
||||
|
||||
if(this.config['-single-image-redraw-on-focus']
|
||||
&& this.toggleSingleImage('?') == 'on'
|
||||
&& this.__did_resize
|
||||
// only when close to original preview size
|
||||
&& Math.abs(D-d)/D < 0.30){
|
||||
|
||||
// this forces chrome to redraw off-screen images...
|
||||
this.scale = this.scale
|
||||
|
||||
// reset the resize flag...
|
||||
delete this.__did_resize
|
||||
}
|
||||
}],
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
@ -175,41 +175,60 @@ module.ViewerActions = actions.Actions({
|
||||
'ribbon-focus-mode': 'visual',
|
||||
},
|
||||
|
||||
// Images...
|
||||
// XXX this seems like a hack...
|
||||
// ...should this be here???
|
||||
get images(){
|
||||
return this.ribbons != null ? this.ribbons.images : null
|
||||
// Ribbons...
|
||||
//
|
||||
// NOTE: this expects that ribbons will maintain .parent.images...
|
||||
// NOTE: when getting rid of ribbons need to also remove the .parent
|
||||
// reference...
|
||||
get ribbons(){
|
||||
return this.__ribbons },
|
||||
set ribbons(ribbons){
|
||||
this.__ribbons = ribbons
|
||||
ribbons.parent = this
|
||||
},
|
||||
// NOTE: if ribbons are null this will have no effect...
|
||||
set images(value){
|
||||
if(this.ribbons != null){
|
||||
this.ribbons.images = value
|
||||
|
||||
// Current image data...
|
||||
//
|
||||
// XXX experimental...
|
||||
get image(){
|
||||
return this.images && this.images[this.current] },
|
||||
set image(val){
|
||||
if(this.images){
|
||||
this.images[this.current] = val
|
||||
}
|
||||
},
|
||||
|
||||
// Scaling...
|
||||
//
|
||||
// Normal scale...
|
||||
get scale(){
|
||||
return this.ribbons != null ? this.ribbons.scale() : null
|
||||
},
|
||||
return this.ribbons != null ? this.ribbons.scale() : null },
|
||||
set scale(s){
|
||||
this.setScale(s)
|
||||
},
|
||||
|
||||
this.setScale(s) },
|
||||
|
||||
// Screen width in image blocks...
|
||||
//
|
||||
// NOTE: this will change depending on image block sizing...
|
||||
// NOTE: this not usable for image blocks of different sizes...
|
||||
get screenwidth(){
|
||||
return this.ribbons != null ? this.ribbons.getScreenWidthImages() : null
|
||||
},
|
||||
return this.ribbons != null ? this.ribbons.getScreenWidthImages() : null },
|
||||
set screenwidth(n){
|
||||
this.fitImage(n)
|
||||
},
|
||||
this.fitImage(n, false) },
|
||||
|
||||
// Screen height in image blocks...
|
||||
get screenheight(){
|
||||
return this.ribbons != null ? this.ribbons.getScreenHeightRibbons() : null
|
||||
},
|
||||
return this.ribbons != null ? this.ribbons.getScreenHeightRibbons() : null },
|
||||
set screenheight(n){
|
||||
this.fitRibbon(n, false)
|
||||
},
|
||||
this.fitRibbon(n, false) },
|
||||
|
||||
// this is the size in image radii on the narrow side of the screen...
|
||||
// Screen size in image radii on the narrow side of the screen...
|
||||
//
|
||||
// E.g.
|
||||
//
|
||||
// min(screenwidth, screenheight)
|
||||
// screenfit = --------------------------------------
|
||||
// min(image.width, image.height)
|
||||
//
|
||||
get screenfit(){
|
||||
if(!this.ribbons || !this.ribbons.viewer){
|
||||
return null
|
||||
@ -569,16 +588,65 @@ module.ViewerActions = actions.Actions({
|
||||
this.nextImage(w)
|
||||
}],
|
||||
|
||||
// zooming...
|
||||
|
||||
// Zooming/scaling root action...
|
||||
//
|
||||
// Protocol:
|
||||
// - all root zoom/scale action bust be wrapped in the .resizing action
|
||||
//
|
||||
// This will enable clients to attach to a single in/out point.
|
||||
//
|
||||
// NOTE: not intended for direct use...
|
||||
resizing: ['- Zoom/', function(){}],
|
||||
|
||||
// Root zoom/sclae actions...
|
||||
//
|
||||
// XXX need to account for animations...
|
||||
setScale: ['- Zoom/',
|
||||
function(scale){
|
||||
this.ribbons && scale && this.ribbons.scale(scale)
|
||||
|
||||
this.refresh()
|
||||
this.resizing.chainCall(this, function(){
|
||||
this.ribbons && scale && this.ribbons.scale(scale)
|
||||
this.refresh()
|
||||
})
|
||||
}],
|
||||
fitOrig: ['Zoom/Fit to original scale',
|
||||
function(){
|
||||
this.resizing.chainCall(this, function(){
|
||||
this.ribbons.scale(1)
|
||||
this.refresh()
|
||||
})
|
||||
}],
|
||||
// NOTE: if this gets a count argument it will fit count images,
|
||||
// default is one.
|
||||
// NOTE: this will add .config['fit-overflow'] to odd counts if no
|
||||
// overflow if passed.
|
||||
// ...this is done to add ability to control scroll indication.
|
||||
fitImage: ['Zoom/Fit image',
|
||||
function(count, overflow){
|
||||
this.resizing.chainCall(this, function(){
|
||||
if(count != null){
|
||||
overflow = overflow == false ? 0 : overflow
|
||||
var o = overflow != null ? overflow
|
||||
: count % 2 != 1 ? 0
|
||||
: (this.config['fit-overflow'] || 0)
|
||||
count += o
|
||||
}
|
||||
this.ribbons.fitImage(count)
|
||||
this.refresh()
|
||||
})
|
||||
}],
|
||||
// NOTE: this does not accout for ribbon spacing...
|
||||
fitRibbon: ['Zoom/Fit ribbon vertically',
|
||||
function(count, whole){
|
||||
this.resizing.chainCall(this, function(){
|
||||
this.ribbons.fitRibbon(count, whole)
|
||||
this.refresh()
|
||||
})
|
||||
}],
|
||||
|
||||
|
||||
// Zooming...
|
||||
//
|
||||
// Zooming is done by multiplying the current scale by config['zoom-step']
|
||||
// and rounding to nearest discrete number of images to fit on screen.
|
||||
zoomIn: ['Zoom/Zoom in',
|
||||
@ -609,51 +677,19 @@ module.ViewerActions = actions.Actions({
|
||||
this.scale /= (this.config['zoom-step'] || 1.2)
|
||||
}
|
||||
}],
|
||||
fitOrig: ['Zoom/Fit to original scale',
|
||||
function(){
|
||||
this.ribbons.scale(1)
|
||||
this.refresh()
|
||||
}],
|
||||
// NOTE: if this gets a count argument it will fit count images,
|
||||
// default is one.
|
||||
// NOTE: this will add .config['fit-overflow'] to odd counts if no
|
||||
// overflow if passed.
|
||||
// ...this is done to add ability to control scroll indication.
|
||||
//
|
||||
// XXX make these neutral to screen and image proportions...
|
||||
fitImage: ['Zoom/Fit image',
|
||||
function(count, overflow){
|
||||
if(count != null){
|
||||
overflow = overflow == false ? 0 : overflow
|
||||
var o = overflow != null ? overflow
|
||||
: count % 2 != 1 ? 0
|
||||
: (this.config['fit-overflow'] || 0)
|
||||
count += o
|
||||
}
|
||||
this.ribbons.fitImage(count)
|
||||
this.refresh()
|
||||
}],
|
||||
|
||||
// Scale presets...
|
||||
//
|
||||
fitMax: ['Zoom/Fit the maximum number of images',
|
||||
function(){ this.fitImage(this.config['max-screen-images']) }],
|
||||
|
||||
// XXX make this viewer/image proportion independent....
|
||||
fitSmall: ['Zoom/Show small image',
|
||||
function(){ this.screenfit = 4 }],
|
||||
// XXX make this viewer/image proportion independent....
|
||||
fitNormal: ['Zoom/Show normal image',
|
||||
function(){ this.screenfit = 1.2 }],
|
||||
fitScreen: ['Zoom/Fit image to screen',
|
||||
function(){ this.screenfit = 1 }],
|
||||
|
||||
|
||||
fitRibbon: ['Zoom/Fit ribbon vertically',
|
||||
function(count, whole){
|
||||
this.ribbons.fitRibbon(count, whole)
|
||||
this.refresh()
|
||||
}],
|
||||
|
||||
|
||||
// NOTE: these work by getting the target position from .data...
|
||||
shiftImageTo: [
|
||||
function(target){ return updateImagePosition(this, target) }],
|
||||
@ -1456,7 +1492,7 @@ module.PartialRibbons = core.ImageGridFeatures.Feature({
|
||||
this.updateRibbon('current', this.screenwidth / s || 1)
|
||||
//this.preCacheJumpTargets()
|
||||
}],
|
||||
['fitImage.pre',
|
||||
['resizing.pre',
|
||||
function(n){
|
||||
this.updateRibbon('current', n || 1)
|
||||
//this.preCacheJumpTargets()
|
||||
@ -2088,7 +2124,7 @@ module.AutoSingleImage = core.ImageGridFeatures.Feature({
|
||||
},
|
||||
|
||||
handlers: [
|
||||
['fitImage.pre',
|
||||
['resizing.pre',
|
||||
function(count){
|
||||
count = count || 1
|
||||
|
||||
|
||||
@ -270,70 +270,14 @@ function Action(name, doc, ldoc, func){
|
||||
|
||||
// create the actual instance we will be returning...
|
||||
var meth = function(){
|
||||
var that = this
|
||||
var args = args2array(arguments)
|
||||
var res = this
|
||||
|
||||
var getHandlers = this.getHandlers || MetaActions.getHandlers
|
||||
var isToggler = this.isToggler || MetaActions.isToggler
|
||||
|
||||
// get the handler list...
|
||||
var handlers = getHandlers.call(this, name)
|
||||
|
||||
// special case: toggler -- do not handle special args...
|
||||
if(isToggler.call(this, name)
|
||||
&& args.length == 1
|
||||
&& (args[0] == '?' || args[0] == '??')){
|
||||
return handlers.slice(-1)[0].pre.apply(this, args)
|
||||
}
|
||||
|
||||
// handlers: pre phase...
|
||||
handlers
|
||||
// NOTE: current action will get included and called by the code
|
||||
// above and below, so no need to explicitly call func...
|
||||
// NOTE: pre handlers are called FIFO, i.e. the last defined first...
|
||||
.map(function(a){
|
||||
if(a.pre){
|
||||
res = a.pre.apply(that, args)
|
||||
|
||||
// if a handler returns a function or a deferred,
|
||||
// register is as a post handler...
|
||||
if(res
|
||||
&& res !== that
|
||||
&& (res instanceof Function
|
||||
|| res.resolve instanceof Function)){
|
||||
a.post = res
|
||||
|
||||
// reset the result...
|
||||
res = that
|
||||
}
|
||||
}
|
||||
return a
|
||||
})
|
||||
|
||||
// return this if nothing specific is returned...
|
||||
res = res === undefined ? this : res
|
||||
// the post handlers get the result as the first argument...
|
||||
args.splice(0, 0, res)
|
||||
|
||||
// handlers: post phase...
|
||||
handlers
|
||||
// NOTE: post handlers are called LIFO -- last defined last...
|
||||
.reverse()
|
||||
.forEach(function(a){
|
||||
a.post
|
||||
&& (a.post.resolve ?
|
||||
a.post.resolve.apply(a.post, args)
|
||||
: a.post.apply(that, args))
|
||||
})
|
||||
|
||||
// action result...
|
||||
return res
|
||||
}
|
||||
return meth.chainApply(this, null, arguments) }
|
||||
meth.__proto__ = this.__proto__
|
||||
|
||||
// populate the action attributes...
|
||||
meth.name = name
|
||||
//meth.name = name
|
||||
Object.defineProperty(meth, 'name', {
|
||||
value: name,
|
||||
})
|
||||
meth.doc = doc
|
||||
meth.long_doc = ldoc
|
||||
|
||||
@ -344,6 +288,84 @@ function Action(name, doc, ldoc, func){
|
||||
// this will make action instances behave like real functions...
|
||||
Action.prototype.__proto__ = Function
|
||||
|
||||
// XXX revise the structure....
|
||||
// ...is it a better idea to define these in an object and assign that???
|
||||
Action.prototype.chainApply = function(context, inner, args){
|
||||
args = [].slice.call(args || [])
|
||||
var res = context
|
||||
var outer = this.name
|
||||
|
||||
var getHandlers = context.getHandlers || MetaActions.getHandlers
|
||||
var isToggler = context.isToggler || MetaActions.isToggler
|
||||
|
||||
// get the handler list...
|
||||
var handlers = getHandlers.call(context, outer)
|
||||
|
||||
// special case: toggler -- do not handle special args...
|
||||
if(isToggler.call(context, outer)
|
||||
&& args.length == 1
|
||||
&& (args[0] == '?' || args[0] == '??')){
|
||||
return handlers.slice(-1)[0].pre.apply(context, args)
|
||||
}
|
||||
|
||||
// handlers: pre phase...
|
||||
handlers
|
||||
// NOTE: current action will get included and called by the code
|
||||
// above and below, so no need to explicitly call func...
|
||||
// NOTE: pre handlers are called FIFO, i.e. the last defined first...
|
||||
.map(function(a){
|
||||
if(a.pre){
|
||||
res = a.pre.apply(context, args)
|
||||
|
||||
// if a handler returns a function or a deferred,
|
||||
// register is as a post handler...
|
||||
if(res
|
||||
&& res !== context
|
||||
&& (res instanceof Function
|
||||
|| res.resolve instanceof Function)){
|
||||
a.post = res
|
||||
|
||||
// reset the result...
|
||||
res = context
|
||||
}
|
||||
}
|
||||
return a
|
||||
})
|
||||
|
||||
// call the inner action/function if preset....
|
||||
if(inner){
|
||||
//res = inner instanceof Function ?
|
||||
inner instanceof Function ?
|
||||
inner.call(context, args)
|
||||
: inner instanceof Array && inner.length > 0 ?
|
||||
context[inner.pop()].chainCall(context, inner, args)
|
||||
: typeof(inner) == typeof('str') ?
|
||||
context[inner].chainCall(context, null, args)
|
||||
: null
|
||||
}
|
||||
|
||||
// return this if nothing specific is returned...
|
||||
res = res === undefined ? context : res
|
||||
// the post handlers get the result as the first argument...
|
||||
args.splice(0, 0, res)
|
||||
|
||||
// handlers: post phase...
|
||||
handlers
|
||||
// NOTE: post handlers are called LIFO -- last defined last...
|
||||
.reverse()
|
||||
.forEach(function(a){
|
||||
a.post
|
||||
&& (a.post.resolve ?
|
||||
a.post.resolve.apply(a.post, args)
|
||||
: a.post.apply(context, args))
|
||||
})
|
||||
|
||||
// action result...
|
||||
return res
|
||||
}
|
||||
Action.prototype.chainCall = function(context, inner){
|
||||
return this.chainApply(context, inner, args2array(arguments).slice(2))
|
||||
}
|
||||
|
||||
|
||||
// A base action-set object...
|
||||
@ -403,7 +425,7 @@ module.MetaActions = {
|
||||
// go up the proto chain...
|
||||
while(cur.__proto__ != null){
|
||||
if(cur[n] != null && cur[n].doc != null){
|
||||
res[n] = [ cur[n].doc, cur[n].long_doc ]
|
||||
res[n] = [ cur[n].doc, cur[n].long_doc, cur[n].name ]
|
||||
break
|
||||
}
|
||||
cur = cur.__proto__
|
||||
@ -516,7 +538,6 @@ module.MetaActions = {
|
||||
isToggler: doWithRootAction(function(action){
|
||||
return action instanceof toggler.Toggler }),
|
||||
|
||||
|
||||
// Register an action callback...
|
||||
//
|
||||
// Register a post action callback
|
||||
@ -704,6 +725,36 @@ module.MetaActions = {
|
||||
return this
|
||||
},
|
||||
|
||||
// Apply/call a function/action "inside" an action...
|
||||
//
|
||||
// .chainApply(outer, inner)
|
||||
// .chainApply(outer, inner, arguments)
|
||||
// -> result
|
||||
//
|
||||
// .chainCall(outer, inner)
|
||||
// .chainCall(outer, inner, ..)
|
||||
// -> result
|
||||
//
|
||||
//
|
||||
// The inner action call is completely nested as base of the outer
|
||||
// action.
|
||||
//
|
||||
// Outer action o-------x o-------x
|
||||
// v ^
|
||||
// Inner action o---|---x
|
||||
//
|
||||
// The given arguments are passed as-is to both the outer and inner
|
||||
// actions.
|
||||
// The base inner action return value is passed to the outer action
|
||||
// .post handlers.
|
||||
//
|
||||
// NOTE: these call the action's .chainApply(..) and .chainCall(..)
|
||||
// methods, thus is not compatible with non-action methods...
|
||||
// NOTE: .chianCall('action', ..) is equivalent to .action.chianCall(..)
|
||||
chainApply: function(outer, inner, args){
|
||||
return this[outer].chainApply(this, inner, args) },
|
||||
chainCall: function(outer, inner){
|
||||
return this[outer].chainApply(this, inner, args2array(arguments).slice(2)) },
|
||||
|
||||
// Get mixin object in inheritance chain...
|
||||
//
|
||||
|
||||
@ -300,6 +300,34 @@ var RibbonsPrototype = {
|
||||
setElemGID: RibbonsClassPrototype.setElemGID,
|
||||
|
||||
|
||||
get parent(){
|
||||
return this.__parent
|
||||
},
|
||||
// NOTE: this will reset locally referenced .images to .parent.images
|
||||
set parent(parent){
|
||||
this.__parent = parent
|
||||
|
||||
delete this.__images
|
||||
},
|
||||
|
||||
// maintain images in .parent.images if available...
|
||||
//
|
||||
// NOTE: images can be stored locally if no parent is set but will
|
||||
// get overridden as soon as .parent is set.
|
||||
get images(){
|
||||
return this.parent ? this.parent.images : this.__images
|
||||
},
|
||||
set images(images){
|
||||
if(this.parent){
|
||||
this.parent.images = images
|
||||
delete this.__images
|
||||
|
||||
} else {
|
||||
this.__images = images
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// Helpers...
|
||||
|
||||
// Prevent CSS transitions...
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user