mirror of
https://github.com/flynx/ImageGrid.git
synced 2025-10-29 02:10:08 +00:00
experementing with protocols + experimenting with wheel event...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
31f51ff6ac
commit
3fa4d22446
@ -342,7 +342,7 @@ var URLHistoryLocalStorageActions = actions.Actions({
|
|||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
|
||||||
saveURLHistory: ['History/',
|
storeURLHistory: ['History/',
|
||||||
function(){
|
function(){
|
||||||
var history = this.config['url-history-local-storage-key']
|
var history = this.config['url-history-local-storage-key']
|
||||||
if(history != null){
|
if(history != null){
|
||||||
@ -350,9 +350,9 @@ var URLHistoryLocalStorageActions = actions.Actions({
|
|||||||
JSON.stringify(this.url_history)
|
JSON.stringify(this.url_history)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.saveLocation()
|
this.storeLocation()
|
||||||
}],
|
}],
|
||||||
saveLocation: ['History/',
|
storeLocation: ['History/',
|
||||||
function(){
|
function(){
|
||||||
var loaded = this.config['url-history-loaded-local-storage-key']
|
var loaded = this.config['url-history-loaded-local-storage-key']
|
||||||
|
|
||||||
@ -404,16 +404,16 @@ module.URLHistoryLocalStorage = core.ImageGridFeatures.Feature({
|
|||||||
function(){ this.loadLastSavedBasePath() }],
|
function(){ this.loadLastSavedBasePath() }],
|
||||||
|
|
||||||
['stop.pre',
|
['stop.pre',
|
||||||
function(){ this.saveURLHistory() }],
|
function(){ this.storeURLHistory() }],
|
||||||
|
|
||||||
// save base_path...
|
// save base_path...
|
||||||
['load',
|
['load',
|
||||||
function(){ this.location && this.location.path && this.saveLocation() }],
|
function(){ this.location && this.location.path && this.storeLocation() }],
|
||||||
|
|
||||||
// save...
|
// save...
|
||||||
['pushURLToHistory dropURLFromHistory setTopURLHistory',
|
['pushURLToHistory dropURLFromHistory setTopURLHistory',
|
||||||
function(){
|
function(){
|
||||||
this.saveURLHistory()
|
this.storeURLHistory()
|
||||||
}],
|
}],
|
||||||
// clear...
|
// clear...
|
||||||
['clearURLHistory.pre',
|
['clearURLHistory.pre',
|
||||||
@ -465,7 +465,7 @@ module.URLHistoryFSWriter = core.ImageGridFeatures.Feature({
|
|||||||
var e = that.url_history[l.path]
|
var e = that.url_history[l.path]
|
||||||
if(e != null){
|
if(e != null){
|
||||||
e.open = l.method
|
e.open = l.method
|
||||||
that.saveURLHistory()
|
that.storeURLHistory()
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
that.pushURLToHistory(l.path, l.method)
|
that.pushURLToHistory(l.path, l.method)
|
||||||
|
|||||||
@ -97,8 +97,7 @@ var LocationActions = actions.Actions({
|
|||||||
// NOTE: the method is needed to enable us to get the action return
|
// NOTE: the method is needed to enable us to get the action return
|
||||||
// value...
|
// value...
|
||||||
set location(value){
|
set location(value){
|
||||||
this.loadLocation(value)
|
this.loadLocation(value) },
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
clearLoaction: ['File/Clear location',
|
clearLoaction: ['File/Clear location',
|
||||||
@ -178,74 +177,124 @@ var LocationActions = actions.Actions({
|
|||||||
return res
|
return res
|
||||||
}],
|
}],
|
||||||
|
|
||||||
// XXX need a way to get save method...
|
// XXX
|
||||||
// - rename .location.method to .location.load and add .location.save
|
// XXX should these have the same effect as .dispatch('location:*:load', location)???
|
||||||
// - treat method as protocol and add a method registry API
|
// ...another way to put it is should we call this from dispatch?
|
||||||
// this is more flexible as we can add as many methods per
|
// ...feels like yes -- this will turn into another Action-like
|
||||||
// protocol as we need and add a command action:
|
// protocol...
|
||||||
// - execute command in current protocol
|
// ...or we can use this to wrap the actual matching action...
|
||||||
// .locationCall("command", ...)
|
_loadLocation: ['- File/Save location',
|
||||||
// - execute command in specific protocol
|
{protocol: 'location:*:load'},
|
||||||
// .locationCall('protocol:command', ..)
|
function(location){
|
||||||
// - show current protocol
|
this.location.method = this.locationMethod(location)
|
||||||
// .locationCall("?")
|
this.dispatch('location:*:load', location)
|
||||||
// - list commands
|
}],
|
||||||
// .locationCall("??")
|
_saveLocation: ['- File/Save location',
|
||||||
// .locationCall('protocol:??')
|
{protocol: 'location:*:save'},
|
||||||
// we can implicitly define protocols via action attrs:
|
function(location){
|
||||||
// loadIndex: ['...',
|
this.location.method = this.locationMethod(location)
|
||||||
// {locationProtocol: 'file:load'},
|
this.dispatch('location:*:save', location) }],
|
||||||
// function(){ ... }],
|
_locationMethod: ['- File/',
|
||||||
locationDispatch: ['- File/',
|
{protocol: 'location:?'},
|
||||||
|
function(location){
|
||||||
|
return (location || this.location).method || null }],
|
||||||
|
|
||||||
|
|
||||||
|
// format:
|
||||||
|
// {
|
||||||
|
// 'protocol:method': 'actionName',
|
||||||
|
//
|
||||||
|
// 'family:protocol:method': 'actionName',
|
||||||
|
//
|
||||||
|
// 'family:*': 'actionName',
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
get protocols(){
|
||||||
|
var cache = this.__location_protocol_cache = this.__location_protocol_cache
|
||||||
|
|| this.cacheProtocols()
|
||||||
|
return cache
|
||||||
|
},
|
||||||
|
cacheProtocols: ['- File/',
|
||||||
|
function(){
|
||||||
|
var that = this
|
||||||
|
var res = {}
|
||||||
|
this.actions.forEach(function(n){
|
||||||
|
var proto = that.getActionAttr(n, 'protocol')
|
||||||
|
if(proto){
|
||||||
|
res[proto] = n
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}],
|
||||||
|
dispatch: ['- File/',
|
||||||
|
core.doc`
|
||||||
|
|
||||||
|
Execute command in specific protocol...
|
||||||
|
.dispatch('protocol:command', ..)
|
||||||
|
.dispatch('family:protocol:command', ..)
|
||||||
|
-> result
|
||||||
|
|
||||||
|
XXX defaults...
|
||||||
|
|
||||||
|
XXX introspection...
|
||||||
|
`,
|
||||||
function(spec){
|
function(spec){
|
||||||
spec = spec instanceof Array ? spec : spec.split(':')
|
|
||||||
var args = [].slice.call(arguments, 1)
|
var args = [].slice.call(arguments, 1)
|
||||||
|
spec = spec instanceof Array ? spec : spec.split(':')
|
||||||
|
|
||||||
var action = spec.pop()
|
var cache = this.protocols
|
||||||
var protocol = spec.shift() || this.location.method
|
var protocols = Object.keys(cache)
|
||||||
|
|
||||||
// format:
|
// get all matching paths...
|
||||||
// {
|
var matches = protocols.slice()
|
||||||
// 'protocol:method': 'actionName',
|
.map(function(p){ return p.split(':') })
|
||||||
// ...
|
spec.forEach(function(e, i){
|
||||||
// }
|
matches = matches
|
||||||
var cache = this.__location_protocol_cache =
|
.filter(function(p){ return e == '*' || p[i] == e }) })
|
||||||
this.__location_protocol_cache || this.cacheLocationProtocols()
|
matches = matches
|
||||||
|
// remove matches ending with '*'... (XXX ???)
|
||||||
|
.filter(function(p){ return p.slice(-1)[0] != '*' })
|
||||||
|
.map(function(p){ return p.join(':') })
|
||||||
|
|
||||||
// get protocol...
|
// fill in the gaps...
|
||||||
if(action == '?'){
|
var i = spec.indexOf('*')
|
||||||
return protocol
|
while(spec.indexOf('*') >= 0){
|
||||||
|
var handler = cache[spec.slice(0, i).concat('?').join(':')]
|
||||||
|
if(handler){
|
||||||
|
spec[i] = this[handler].apply(this, args)
|
||||||
|
i = spec.indexOf('*')
|
||||||
|
|
||||||
// get available methods for protocol...
|
// error...
|
||||||
} else if(action == '??'){
|
// XXX how do we break out of this???
|
||||||
return Object.keys(cache)
|
} else {
|
||||||
.filter(function(e){ return e.startsWith(protocol + ':') })
|
throw ('No default defined for: '+ spec.slice(0, i+1).join(':'))
|
||||||
.map(function(e){ return e.split(':').pop() })
|
}
|
||||||
|
}
|
||||||
// list all protocols...
|
|
||||||
} else if(protocol == '??' && action == '*'){
|
|
||||||
return Object.keys(cache)
|
|
||||||
.map(function(e){ return e.split(':').pop() })
|
|
||||||
.unique()
|
|
||||||
|
|
||||||
// list protocols implementing specific action...
|
// introspection...
|
||||||
} else if(protocol == '??'){
|
// XXX this supports only one '??'
|
||||||
return Object.keys(cache)
|
var i = spec.indexOf('??')
|
||||||
.filter(function(e){ return e.endsWith(':'+ action) })
|
if(i >= 0){
|
||||||
.map(function(e){ return e.split(':')[0] })
|
var head = spec.slice(0, i).join(':')
|
||||||
.unique()
|
var tail = spec.slice(i+1).join(':')
|
||||||
|
console.log(head, tail)
|
||||||
|
return protocols
|
||||||
|
.filter(function(p){
|
||||||
|
return p.startsWith(head)
|
||||||
|
&& (tail == ''
|
||||||
|
|| (p.endsWith(tail)
|
||||||
|
&& p.length > (head.length + tail.length + 2))) })
|
||||||
|
|
||||||
// call method...
|
// call method...
|
||||||
} else {
|
} else {
|
||||||
// XXX args???
|
var m = spec.join(':')
|
||||||
this[cache[protocol +':'+ action]].call(this)
|
console.log('>>>', m)
|
||||||
|
|
||||||
|
// XXX take all the matches and chain call them...
|
||||||
|
return this[cache[m]].apply(this, args)
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
saveLocation: ['- File/Save location',
|
|
||||||
function(location){
|
|
||||||
// XXX
|
|
||||||
this.locationDispatch('save')
|
|
||||||
}],
|
|
||||||
})
|
})
|
||||||
|
|
||||||
module.Location = core.ImageGridFeatures.Feature({
|
module.Location = core.ImageGridFeatures.Feature({
|
||||||
|
|||||||
@ -64,6 +64,7 @@ core.ImageGridFeatures.Feature('viewer-testing', [
|
|||||||
|
|
||||||
'ui-single-image',
|
'ui-single-image',
|
||||||
//'ui-partial-ribbons',
|
//'ui-partial-ribbons',
|
||||||
|
// XXX this still has problems...
|
||||||
'ui-partial-ribbons-2',
|
'ui-partial-ribbons-2',
|
||||||
|
|
||||||
'marks',
|
'marks',
|
||||||
|
|||||||
@ -35,7 +35,10 @@ var PartialRibbonsActions = actions.Actions({
|
|||||||
// 'resize'
|
// 'resize'
|
||||||
'ribbons-in-place-update-mode': 'resize',
|
'ribbons-in-place-update-mode': 'resize',
|
||||||
|
|
||||||
'ribbons-in-place-update-timeout': 200,
|
'ribbons-in-place-update-timeout': 100,
|
||||||
|
|
||||||
|
// XXX
|
||||||
|
'ribbon-update-timeout': 120,
|
||||||
},
|
},
|
||||||
|
|
||||||
updateRibbon: ['- Interface/Update partial ribbon size',
|
updateRibbon: ['- Interface/Update partial ribbon size',
|
||||||
@ -63,6 +66,7 @@ var PartialRibbonsActions = actions.Actions({
|
|||||||
var t = Date.now()
|
var t = Date.now()
|
||||||
this.__last_ribbon_update = this.__last_ribbon_update || t
|
this.__last_ribbon_update = this.__last_ribbon_update || t
|
||||||
var timeout = this.config['ribbons-in-place-update-timeout']
|
var timeout = this.config['ribbons-in-place-update-timeout']
|
||||||
|
var update_timeout = this.config['ribbon-update-timeout']
|
||||||
|
|
||||||
// localize transition prevention...
|
// localize transition prevention...
|
||||||
// NOTE: we can't get ribbon via target directly here as
|
// NOTE: we can't get ribbon via target directly here as
|
||||||
@ -93,8 +97,10 @@ var PartialRibbonsActions = actions.Actions({
|
|||||||
// ribbon shorter than we expect...
|
// ribbon shorter than we expect...
|
||||||
|| (loaded < size && na + pa > loaded)
|
|| (loaded < size && na + pa > loaded)
|
||||||
// ribbon too long...
|
// ribbon too long...
|
||||||
|| loaded > size * threshold){
|
|| loaded > size * threshold
|
||||||
//console.log('RESIZE')
|
// passed hard threshold -- too close to edge...
|
||||||
|
|| (nl < w && na > nl) || (pl < w && pa > pl)){
|
||||||
|
//console.log('RESIZE (sync)')
|
||||||
this.resizeRibbon(target, size)
|
this.resizeRibbon(target, size)
|
||||||
|
|
||||||
// more complex cases...
|
// more complex cases...
|
||||||
@ -104,7 +110,7 @@ var PartialRibbonsActions = actions.Actions({
|
|||||||
|| (pl < update_threshold && pa > pl)
|
|| (pl < update_threshold && pa > pl)
|
||||||
// loaded more than we need by threshold...
|
// loaded more than we need by threshold...
|
||||||
|| nl + pl + 1 > size + update_threshold){
|
|| nl + pl + 1 > size + update_threshold){
|
||||||
// resize mode...
|
// resize...
|
||||||
if(this.config['ribbons-in-place-update-mode'] == 'resize'
|
if(this.config['ribbons-in-place-update-mode'] == 'resize'
|
||||||
// no ribbon loaded...
|
// no ribbon loaded...
|
||||||
|| r.length == 0
|
|| r.length == 0
|
||||||
@ -114,26 +120,36 @@ var PartialRibbonsActions = actions.Actions({
|
|||||||
// full screen...
|
// full screen...
|
||||||
|| (this.toggleSingleImage
|
|| (this.toggleSingleImage
|
||||||
&& this.toggleSingleImage('?') == 'on')){
|
&& this.toggleSingleImage('?') == 'on')){
|
||||||
//console.log('RESIZE', t-this.__last_ribbon_update)
|
return function(){
|
||||||
this.resizeRibbon(target, size)
|
var that = this
|
||||||
|
// sync update...
|
||||||
|
if(update_timeout == null){
|
||||||
|
//console.log('RESIZE (post)', t-this.__last_ribbon_update)
|
||||||
|
this.resizeRibbon(target, size)
|
||||||
|
|
||||||
|
// async update...
|
||||||
|
} else {
|
||||||
|
this.__update_timeout
|
||||||
|
&& clearTimeout(this.__update_timeout)
|
||||||
|
this.__update_timeout = setTimeout(function(){
|
||||||
|
//console.log('RESIZE (timeout)', t-this.__last_ribbon_update)
|
||||||
|
delete that.__update_timeout
|
||||||
|
that.resizeRibbon(target, size)
|
||||||
|
}, update_timeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// in-place update...
|
// in-place update...
|
||||||
// XXX this is faster than .resizeRibbon(..) but it's not
|
// XXX this is faster than .resizeRibbon(..) but it's not
|
||||||
// used unconditionally because I can't get rid or
|
// used unconditionally because I can't get rid of
|
||||||
// sync up images being replaced...
|
// sync up images being replaced...
|
||||||
// ...note that .resizeRibbon(..) is substantially
|
// ...note that .resizeRibbon(..) is substantially
|
||||||
// slower (updates DOM), i.e. introduces a lag, but
|
// slower (updates DOM), i.e. introduces a lag, but
|
||||||
// the results look OK...
|
// the results look OK...
|
||||||
//
|
// XXX approaches to try:
|
||||||
// Approaches:
|
// - wait for images to preload and only then update...
|
||||||
// - preloading a target section off-screen
|
// - preload images in part of a ribbon and when ready update...
|
||||||
// ...results in two freezes instead of one
|
// ...this is like the first but we wait for less images...
|
||||||
// - CSS will-change: background-image (???)
|
|
||||||
// - revise .updateImage(..)
|
|
||||||
//
|
|
||||||
// Q: can this be done within 1/60s???
|
|
||||||
// XXX one approach here might be:
|
|
||||||
// wait for images to preload and only then update...
|
|
||||||
} else {
|
} else {
|
||||||
//console.log('UPDATE', t - this.__last_ribbon_update)
|
//console.log('UPDATE', t - this.__last_ribbon_update)
|
||||||
var c = gids.indexOf(data.getImage('current', r_gid))
|
var c = gids.indexOf(data.getImage('current', r_gid))
|
||||||
|
|||||||
@ -1927,6 +1927,8 @@ var ControlActions = actions.Actions({
|
|||||||
// if true and ribbon is panned off screen, the image will be
|
// if true and ribbon is panned off screen, the image will be
|
||||||
// centered, else behave just like partially off screen...
|
// centered, else behave just like partially off screen...
|
||||||
'center-off-screen-paned-images': false,
|
'center-off-screen-paned-images': false,
|
||||||
|
|
||||||
|
'mouse-wheel-scale': 0.5,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Image click events...
|
// Image click events...
|
||||||
@ -2386,6 +2388,112 @@ var ControlActions = actions.Actions({
|
|||||||
}
|
}
|
||||||
})],
|
})],
|
||||||
|
|
||||||
|
// XXX need:
|
||||||
|
// - prevent ribbon from scrolling off screen...
|
||||||
|
// - handle acceleration -- stop and update just before scrolling off the edge...
|
||||||
|
// - update...
|
||||||
|
// XXX might be a good idea to use the viewer instead of ribbons as source...
|
||||||
|
// ...this will prevent losing control of the ribbon when it goes out
|
||||||
|
// from under the cursor...
|
||||||
|
// ...detect via cursor within the vertical band of the ribbon...
|
||||||
|
toggleMouseWheelHandling: ['Interface/Mouse wheel handling',
|
||||||
|
toggler.Toggler(null,
|
||||||
|
function(){
|
||||||
|
return this.ribbons
|
||||||
|
&& this.ribbons.viewer
|
||||||
|
&& this.ribbons.viewer.hasClass('mouse-wheel-scroll') ?
|
||||||
|
'handling-mouse-wheel'
|
||||||
|
: 'none' },
|
||||||
|
'handling-mouse-wheel',
|
||||||
|
function(state){
|
||||||
|
var that = this
|
||||||
|
|
||||||
|
/*
|
||||||
|
var focus_central = function(rgid){
|
||||||
|
// see if we need to change focus...
|
||||||
|
var current_ribbon = that.data.getRibbon()
|
||||||
|
if(current_ribbon == rgid){
|
||||||
|
var central = that.ribbons.getImageByPosition('center', r)
|
||||||
|
var gid = that.ribbons.getElemGID(central)
|
||||||
|
// silently focus central image...
|
||||||
|
if(that.config['focus-central-image'] == 'silent'){
|
||||||
|
that.data.focusImage(gid)
|
||||||
|
that.ribbons.focusImage(that.current)
|
||||||
|
|
||||||
|
// focus central image in a normal manner...
|
||||||
|
} else if(that.config['focus-central-image']){
|
||||||
|
that.data.focusImage(gid)
|
||||||
|
that.focusImage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
var setup = this.__wheel_handler_setup = this.__wheel_handler_setup
|
||||||
|
|| function(_, target){
|
||||||
|
var that = this
|
||||||
|
|
||||||
|
var r = this.ribbons.getRibbon(target)
|
||||||
|
var rgid = this.ribbons.getElemGID(r)
|
||||||
|
|
||||||
|
// XXX vertical scroll...
|
||||||
|
this.ribbons.viewer
|
||||||
|
.on('wheel', function(){
|
||||||
|
})
|
||||||
|
|
||||||
|
// horizontal scroll...
|
||||||
|
r.on('wheel', function(){
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
var s = that.config['mouse-wheel-scale'] || 1
|
||||||
|
var vmin = Math.min(document.body.offsetWidth, document.body.offsetHeight)
|
||||||
|
var left = parseFloat(($(this).transform('translate3d') || [0])[0])/100 * vmin
|
||||||
|
|
||||||
|
// XXX inertia problem -- it's too easy to scroll a ribbon off the screen...
|
||||||
|
// try:
|
||||||
|
// - limit speed
|
||||||
|
// - limit distance
|
||||||
|
// 1-2 screens -> stop for timeout before continue
|
||||||
|
// ...need to keep track of "scroll sessions"
|
||||||
|
|
||||||
|
// XXX prevent scroll off screen....
|
||||||
|
|
||||||
|
// XXX prevent scroll off loaded edge...
|
||||||
|
|
||||||
|
// XXX focus_central(rgid) when scroll slows down...
|
||||||
|
// (small deltaX or longer time between triggerings)...
|
||||||
|
|
||||||
|
// XXX do we need to do requestAnimationFrame(..) render...
|
||||||
|
// ...see toggleRibbonPanHandling(..) for an implementation...
|
||||||
|
|
||||||
|
// do the actual move...
|
||||||
|
r.transform({
|
||||||
|
x: ((left - (event.deltaX * s)) / vmin * 100) + 'vmin',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// on...
|
||||||
|
if(state == 'on'){
|
||||||
|
this.ribbons.viewer.addClass('mouse-wheel-scroll')
|
||||||
|
// NOTE: we are resetting this to avoid multiple setting
|
||||||
|
// handlers...
|
||||||
|
this.off('updateRibbon', setup)
|
||||||
|
this.on('updateRibbon', setup)
|
||||||
|
|
||||||
|
this.data.ribbon_order.forEach(function(gid){
|
||||||
|
setup.call(that, null, gid) })
|
||||||
|
|
||||||
|
// off...
|
||||||
|
} else {
|
||||||
|
this.ribbons.viewer.removeClass('mouse-wheel-scroll')
|
||||||
|
this.off('updateRibbon', setup)
|
||||||
|
|
||||||
|
this.data.ribbon_order.forEach(function(gid){
|
||||||
|
that.ribbons.getRibbon(gid).off('wheel') })
|
||||||
|
}
|
||||||
|
})],
|
||||||
|
|
||||||
togglePinchHandling: ['Interface/Pinch zoom handling',
|
togglePinchHandling: ['Interface/Pinch zoom handling',
|
||||||
function(){
|
function(){
|
||||||
|
|||||||
@ -58,7 +58,6 @@ if(window.require && window.nw){
|
|||||||
<script src="ext-lib/hammer.min.js"></script>
|
<script src="ext-lib/hammer.min.js"></script>
|
||||||
<script src="ext-lib/jquery.hammer.js"></script>
|
<script src="ext-lib/jquery.hammer.js"></script>
|
||||||
|
|
||||||
|
|
||||||
<script src="lib/jli.js"></script>
|
<script src="lib/jli.js"></script>
|
||||||
|
|
||||||
<script data-main="ui" src="ext-lib/require.js"></script>
|
<script data-main="ui" src="ext-lib/require.js"></script>
|
||||||
|
|||||||
@ -299,7 +299,11 @@ var transformEditor = function(){
|
|||||||
|
|
||||||
var aliases = Object.keys(spec)
|
var aliases = Object.keys(spec)
|
||||||
|
|
||||||
var r = reduce == 'sum' ? function(a, b){ return a + b }
|
var r = reduce == 'sum' ? function(a, b){
|
||||||
|
return a == 0 ? b
|
||||||
|
: b == 0 ? a
|
||||||
|
: a == 0 && b == 0 ? 0
|
||||||
|
: a + b }
|
||||||
: reduce == 'mul' ? function(a, b){ return a * b }
|
: reduce == 'mul' ? function(a, b){ return a * b }
|
||||||
: reduce == 'last' ? function(a, b){ return b != null ? b : a }
|
: reduce == 'last' ? function(a, b){ return b != null ? b : a }
|
||||||
: reduce
|
: reduce
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user