diff --git a/ui (gen4)/features/all.js b/ui (gen4)/features/all.js index d4f27560..735c588c 100755 --- a/ui (gen4)/features/all.js +++ b/ui (gen4)/features/all.js @@ -17,6 +17,7 @@ require('features/location') require('features/history') require('features/app') require('features/ui') +require('features/ui-partial-ribbons') require('features/ui-single-image') require('features/ui-chrome') require('features/keyboard') diff --git a/ui (gen4)/features/base.js b/ui (gen4)/features/base.js index 5f42acdd..adc72e2b 100755 --- a/ui (gen4)/features/base.js +++ b/ui (gen4)/features/base.js @@ -925,6 +925,7 @@ module.ImageGroup = core.ImageGridFeatures.Feature({ // full features base... core.ImageGridFeatures.Feature('base-full', [ + 'introspection', 'base', 'tags', 'sort', diff --git a/ui (gen4)/features/core.js b/ui (gen4)/features/core.js index ef4db0d8..db4e785a 100755 --- a/ui (gen4)/features/core.js +++ b/ui (gen4)/features/core.js @@ -226,6 +226,42 @@ module.LifeCycle = ImageGridFeatures.Feature({ +//--------------------------------------------------------------------- +// Introspection... + +// Indicate that an action is not intended for direct use... +// +// NOTE: this will not do anything but mark the action. +var notUserCallable = +module.notUserCallable = function(func){ + func.__not_user_callable__ = true + return func +} + + +var IntrospectionActions = actions.Actions({ + // user-callable actions... + get useractions(){ + return this.actions.filter(this.isUserCallable.bind(this)) }, + + // check if action is callable by user... + isUserCallable: ['- System/', + actions.doWithRootAction(function(action){ + return action.__not_user_callable__ != true })], +}) + + +var Introspection = +module.Introspection = ImageGridFeatures.Feature({ + title: '', + + tag: 'introspection', + + actions: IntrospectionActions, +}) + + + //--------------------------------------------------------------------- // Workspace... // diff --git a/ui (gen4)/features/ui-partial-ribbons.js b/ui (gen4)/features/ui-partial-ribbons.js new file mode 100755 index 00000000..cee19396 --- /dev/null +++ b/ui (gen4)/features/ui-partial-ribbons.js @@ -0,0 +1,423 @@ +/********************************************************************** +* +* +* +**********************************************************************/ + +define(function(require){ var module = {} + +//var DEBUG = DEBUG != null ? DEBUG : true + +var actions = require('lib/actions') +var features = require('lib/features') + +var core = require('features/core') + + + +/*********************************************************************/ + +// NOTE: this is split out to an action so as to enable ui elements to +// adapt to ribbon size changes... +// +// XXX try using .ribbons.resizeRibbon(..) for basic tasks... +// XXX try a strategy: load more in the direction of movement by an offset... +// XXX updateRibbon(..) is not signature compatible with data.updateRibbon(..) +var PartialRibbonsActions = actions.Actions({ + config: { + // number of screen widths to load... + 'ribbon-size-screens': 7, + + // number of screen widths to edge to trigger reload... + 'ribbon-resize-threshold': 1.5, + + // timeout before a non-forced ribbon size update happens after + // the action... + // NOTE: if set to null, the update will be sync... + 'ribbon-update-timeout': 120, + + // how many non-adjacent images to preload... + 'preload-radius': 5, + + // sources to preload... + 'preload-sources': ['bookmark', 'selected'], + }, + + // NOTE: this will not work from chrome when loading from a local fs... + // XXX experimental... + startCacheWorker: ['Interface/', + function(){ + // a worker is started already... + if(this.cacheWorker != null){ + return + } + + var b = new Blob([[ + 'addEventListener(\'message\', function(e) {', + ' var urls = e.data', + ' urls = urls.constructor !== Array ? [urls] : urls', + ' var l = urls.length', + ' urls.forEach(function(url){', + ' var xhr = new XMLHttpRequest()', + ' xhr.responseType = \'blob\'', + /* + ' xhr.onload = xhr.onerror = function(){', + ' l -= 1', + ' if(l <= 0){', + ' postMessage({status: \'done.\', urls: urls})', + ' }', + ' }', + */ + ' xhr.open(\'GET\', url, true)', + ' xhr.send()', + ' })', + '}, false)', + ].join('\n')]) + + var url = URL.createObjectURL(b) + + this.cacheWorker = new Worker(url) + this.cacheWorker.url = url + }], + stopCacheWorker: ['Interface/', + function(){ + if(this.cacheWorker){ + this.cacheWorker.terminate() + URL.revokeObjectURL(this.cacheWorker.url) + delete this.cacheWorker + } + }], + + + // Pre-load images... + // + // Sources supported: + // - pre-load images tagged with + // (default: ['bookmark', 'selected']) + // - pre-cache from a specific ribbon + // 'ribbon' - pre-cache from current ribbon + // 'order' - pre-cache from images in order + // + // NOTE: workers when loaded from file:// in a browser context + // will not have access to local images... + // + // XXX need a clear strategy to run this... + // XXX might be a good idea to make the worker queue the lists... + // ...this will need careful prioritization logic... + // - avoid loading the same url too often + // - load the most probable urls first + // - next targets + // - next/prev + // .preCacheJumpTargets(target, 'ribbon', this.screenwidth) + // - next/prev marked/bookmarked/order + // .preCacheJumpTargets(target, 'marked') + // .preCacheJumpTargets(target, 'bookmarked') + // .preCacheJumpTargets(target, 'order') + // - next/prev screen + // .preCacheJumpTargets(target, 'ribbon', + // this.config['preload-radius'] * this.screenwidth) + // - next/prev ribbon + // .preCacheJumpTargets(target, this.data.getRibbon(target, 1)) + // .preCacheJumpTargets(target, this.data.getRibbon(target, -1)) + // - next blocks + // - what resize ribbon does... + // XXX coordinate this with .resizeRibbon(..) + // XXX make this support an explicit list of gids.... + // XXX should this be here??? + preCacheJumpTargets: ['- Interface/Pre-cache potential jump target images', + function(target, sources, radius, size){ + target = target instanceof jQuery + ? this.ribbons.getElemGID(target) + // NOTE: data.getImage(..) can return null at start or end + // of ribbon, thus we need to account for this... + : (this.data.getImage(target) + || this.data.getImage(target, 'after')) + + sources = sources || this.config['preload-sources'] || ['bookmark', 'selected'] + sources = sources.constructor !== Array ? [sources] : sources + radius = radius || this.config['preload-radius'] || 9 + + var that = this + + // get preview... + var _getPreview = function(c){ + return that.images[c] + && that.images.getBestPreview(c, size, true).url + } + + // get a set of paths... + // NOTE: we are also ordering the resulting gids by their + // distance from target... + var _get = function(i, lst, source, radius, oddity, step){ + var found = oddity + var max = source.length + + for(var j = i+step; (step > 0 && j < max) || (step < 0 && j >= 0); j += step){ + var c = source[j] + + if(c == null || that.images[c] == null){ + continue + } + + // build the URL... + lst[found] = _getPreview(c) + + found += 2 + if(found >= radius*2){ + break + } + } + } + + // run the actual preload... + var _run = function(){ + sources.forEach(function(tag){ + // order... + if(tag == 'order'){ + var source = that.data.order + + // current ribbon... + }else if(tag == 'ribbon'){ + var source = that.data.ribbons[that.data.getRibbon()] + + // ribbon-gid... + } else if(tag in that.data.ribbons){ + var source = that.data.ribbons[tag] + + // nothing tagged then nothing to do... + } else if(that.data.tags == null + || that.data.tags[tag] == null + || that.data.tags[tag].length == 0){ + return + + // tag... + } else { + var source = that.data.tags[tag] + } + + size = size || that.ribbons.getVisibleImageSize() + + var i = that.data.order.indexOf(target) + var lst = [] + + // get the list of URLs before and after current... + _get(i ,lst, source, radius, 0, 1) + _get(i, lst, source, radius, 1, -1) + + // get target preview in case the target is not loaded... + var p = _getPreview(that.data.getImage(target)) + p && lst.splice(0, 0, p) + + // web worker... + if(that.cacheWorker != null){ + that.cacheWorker.postMessage(lst) + + // async inline... + } else { + // do the actual preloading... + lst.forEach(function(url){ + var img = new Image() + img.src = url + }) + } + }) + } + + if(that.cacheWorker != null){ + _run() + + } else { + setTimeout(_run, 0) + } + }], + + // NOTE: this will force sync resize if one of the following is true: + // - the target is not loaded + // - we are less than screen width from the edge + // - threshold is set to 0 + // XXX this is not signature compatible with data.updateRibbon(..) + // XXX do not do anything for off-screen ribbons... + updateRibbon: ['- Interface/Update partial ribbon size', + function(target, w, size, threshold){ + target = target instanceof jQuery + ? this.ribbons.getElemGID(target) + // NOTE: data.getImage(..) can return null at start or end + // of ribbon, thus we need to account for this... + : (this.data.getImage(target) + || this.data.getImage(target, 'after')) + + w = w || this.screenwidth + + // get config data and normalize... + size = (size + || this.config['ribbon-size-screens'] + || 5) * w + threshold = threshold == 0 ? threshold + : (threshold + || this.config['ribbon-resize-threshold'] + || 1) * w + + var timeout = this.config['ribbon-update-timeout'] + + // next/prev loaded... + var img = this.ribbons.getImage(target) + var nl = img.nextAll('.image:not(.clone)').length + var pl = img.prevAll('.image:not(.clone)').length + + // next/prev available... + // NOTE: we subtract 1 to remove the current and make these + // compatible with: nl, pl + var na = this.data.getImages(target, size, 'after').length - 1 + var pa = this.data.getImages(target, size, 'before').length - 1 + + // do the update... + // no threshold means force load... + if(threshold == 0 + // the target is not loaded... + || img.length == 0 + // passed hard threshold on the right... + || (nl < w && na > nl) + // passed hard threshold on the left... + || (pl < w && pa > pl)){ + + this.resizeRibbon(target, size) + + // do a late resize... + // loaded more than we need (crop?)... + } else if(na + pa < nl + pl + // passed threshold on the right... + || (nl < threshold && na > nl) + // passed threshold on the left... + || (pl < threshold && pa > pl) + // loaded more than we need by threshold... + || nl + pl + 1 > size + threshold){ + + return function(){ + // sync update... + if(timeout == null){ + this.resizeRibbon(target, size) + + // async update... + } else { + // XXX need to check if we are too close to the edge... + var that = this + //setTimeout(function(){ that.resizeRibbon(target, size) }, 0) + if(this.__update_timeout){ + clearTimeout(this.__update_timeout) + } + this.__update_timeout = setTimeout(function(){ + delete that.__update_timeout + that.resizeRibbon(target, size) + }, timeout) + } + } + } + }], + // XXX do we handle off-screen ribbons here??? + resizeRibbon: ['- Interface/Resize ribbon to n images', + function(target, size){ + size = size + || (this.config['ribbon-size-screens'] * this.screenwidth) + || (5 * this.screenwidth) + var data = this.data + var ribbons = this.ribbons + + // NOTE: we can't get ribbon via target directly here as + // the target might not be loaded... + var r_gid = data.getRibbon(target) + + if(r_gid == null){ + return + } + + // localize transition prevention... + // NOTE: for the initial load this may be empty... + var r = ribbons.getRibbon(r_gid) + + // XXX do we need to for example ignore unloaded (r.length == 0) + // ribbons here, for example not load ribbons too far off + // screen?? + + ribbons + .preventTransitions(r) + .updateRibbon( + data.getImages(target, size), + r_gid, + target) + .restoreTransitions(r, true) + }] +}) + +// NOTE: I do not fully understand it yet, but PartialRibbons must be +// setup BEFORE RibbonAlignToFirst, otherwise the later will break +// on shifting an image to a new ribbon... +// To reproduce: +// - setupe RibbonAlignToFirst first +// - go to top ribbon +// - shift image up +// XXX The two should be completely independent.... (???) +var PartialRibbons = +module.PartialRibbons = core.ImageGridFeatures.Feature({ + title: 'Partial Ribbons', + doc: 'Maintains partially loaded ribbons, this enables very lage ' + +'image sets to be hadled eficiently.', + + // NOTE: partial ribbons needs to be setup first... + // ...the reasons why things break otherwise is not too clear. + priority: 'high', + + tag: 'ui-partial-ribbons', + depends: ['ui'], + + + actions: PartialRibbonsActions, + + handlers: [ + ['focusImage.pre centerImage.pre', + function(target, list){ + // NOTE: we have to do this as we are called BEFORE the + // actual focus change happens... + // XXX is there a better way to do this??? + target = list != null ? target = this.data.getImage(target, list) : target + + this.updateRibbon(target) + }], + ['focusImage.post', + function(_, target){ + this.preCacheJumpTargets(target) + }], + + ['resizing.pre', + function(unit, size){ + if(unit == 'scale'){ + this.updateRibbon('current', this.screenwidth / size || 1) + + } else if(unit == 'screenwidth'){ + this.updateRibbon('current', size || 1) + + } else if(unit == 'screenheight'){ + size = size || 1 + + // convert target height in ribbons to width in images... + // NOTE: this does not account for compensation that + // .updateRibbon(..) makes for fitting whole image + // counts, this is a small enough error so as not + // to waste time on... + var s = this.ribbons.scale() + var h = this.ribbons.getScreenHeightRibbons() + var w = this.ribbons.getScreenWidthImages() + var nw = w / (h/size) + + this.updateRibbon('current', nw) + } + + //this.preCacheJumpTargets() + }], + ], +}) + + + +/********************************************************************** +* vim:set ts=4 sw=4 : */ +return module }) diff --git a/ui (gen4)/features/ui.js b/ui (gen4)/features/ui.js index 9c083ba5..47902901 100755 --- a/ui (gen4)/features/ui.js +++ b/ui (gen4)/features/ui.js @@ -351,11 +351,15 @@ module.ViewerActions = actions.Actions({ // at this point manually triggering this will not do anything... // XXX problem: need to either redesign this or distinguish from // other actions as I keep calling it expecting results... - // XXX hide from user action list... - updateImage: ['- Interface/Update image (This will do nothing)', - 'This will be called by .refresh(..) and intended for use as an ' - +'trigger for handlers, and not as a callable acation.', - function(gid, image){ }], + // XXX hide from user action list... (???) + updateImage: ['- Interface/Update image (do not use directly)', + 'This is called by .refresh(..) and intended for use as an ' + +'trigger for handlers, and not as a user-callable acation.', + core.notUserCallable(function(gid, image){ + // This is the image update protocol root function + // + // Not for direct use. + })], // General UI stuff... @@ -596,6 +600,11 @@ module.ViewerActions = actions.Actions({ // - a compliant action must pass the sizing unit, value and // overflow to the wrapping action. // + // Supported units: + // - scale + // - screenwidth + // - screenheight + // // Example: // actionName: ['...', // function(value){ @@ -614,16 +623,22 @@ module.ViewerActions = actions.Actions({ // This will enable clients to attach to a single in/out point. // // NOTE: not intended for direct use... - resizing: ['- Zoom/', function(unit, size, overflow){ - // This is a resizing protocol root function. - // - // This will never be used directly, but will wrap protocol user - // functions. - // - // As an example see: .setScale(..) - }], + // + // XXX hide from user action list... (???) + resizing: ['- Zoom/Zoom/scale root protocol action (not for direct use)', + 'This is called by zoom/scale protocol compliant actions and ' + +'intended for use as an trigger for handlers, and not as ' + +'a user-callable acation.', + core.notUserCallable(function(unit, size, overflow){ + // This is a resizing protocol root function. + // + // This will never be used directly, but will wrap protocol user + // functions. + // + // As an example see: .setScale(..) + })], - // Root zoom/sclae actions... + // Zoom/scale protocol actions... // // XXX need to account for animations... setScale: ['- Zoom/', @@ -839,6 +854,7 @@ module.Viewer = core.ImageGridFeatures.Feature({ 'lifecycle', 'base', 'workspace', + 'introspection', ], actions: ViewerActions, @@ -1142,411 +1158,6 @@ module.URLHash = core.ImageGridFeatures.Feature({ /*********************************************************************/ // Ribbons... -// NOTE: this is split out to an action so as to enable ui elements to -// adapt to ribbon size changes... -// -// XXX try using .ribbons.resizeRibbon(..) for basic tasks... -// XXX try a strategy: load more in the direction of movement by an offset... -// XXX updateRibbon(..) is not signature compatible with data.updateRibbon(..) -var PartialRibbonsActions = actions.Actions({ - config: { - // number of screen widths to load... - 'ribbon-size-screens': 7, - - // number of screen widths to edge to trigger reload... - 'ribbon-resize-threshold': 1.5, - - // timeout before a non-forced ribbon size update happens after - // the action... - // NOTE: if set to null, the update will be sync... - 'ribbon-update-timeout': 120, - - // how many non-adjacent images to preload... - 'preload-radius': 5, - - // sources to preload... - 'preload-sources': ['bookmark', 'selected'], - }, - - // NOTE: this will not work from chrome when loading from a local fs... - // XXX experimental... - startCacheWorker: ['Interface/', - function(){ - // a worker is started already... - if(this.cacheWorker != null){ - return - } - - var b = new Blob([[ - 'addEventListener(\'message\', function(e) {', - ' var urls = e.data', - ' urls = urls.constructor !== Array ? [urls] : urls', - ' var l = urls.length', - ' urls.forEach(function(url){', - ' var xhr = new XMLHttpRequest()', - ' xhr.responseType = \'blob\'', - /* - ' xhr.onload = xhr.onerror = function(){', - ' l -= 1', - ' if(l <= 0){', - ' postMessage({status: \'done.\', urls: urls})', - ' }', - ' }', - */ - ' xhr.open(\'GET\', url, true)', - ' xhr.send()', - ' })', - '}, false)', - ].join('\n')]) - - var url = URL.createObjectURL(b) - - this.cacheWorker = new Worker(url) - this.cacheWorker.url = url - }], - stopCacheWorker: ['Interface/', - function(){ - if(this.cacheWorker){ - this.cacheWorker.terminate() - URL.revokeObjectURL(this.cacheWorker.url) - delete this.cacheWorker - } - }], - - - // Pre-load images... - // - // Sources supported: - // - pre-load images tagged with - // (default: ['bookmark', 'selected']) - // - pre-cache from a specific ribbon - // 'ribbon' - pre-cache from current ribbon - // 'order' - pre-cache from images in order - // - // NOTE: workers when loaded from file:// in a browser context - // will not have access to local images... - // - // XXX need a clear strategy to run this... - // XXX might be a good idea to make the worker queue the lists... - // ...this will need careful prioritization logic... - // - avoid loading the same url too often - // - load the most probable urls first - // - next targets - // - next/prev - // .preCacheJumpTargets(target, 'ribbon', this.screenwidth) - // - next/prev marked/bookmarked/order - // .preCacheJumpTargets(target, 'marked') - // .preCacheJumpTargets(target, 'bookmarked') - // .preCacheJumpTargets(target, 'order') - // - next/prev screen - // .preCacheJumpTargets(target, 'ribbon', - // this.config['preload-radius'] * this.screenwidth) - // - next/prev ribbon - // .preCacheJumpTargets(target, this.data.getRibbon(target, 1)) - // .preCacheJumpTargets(target, this.data.getRibbon(target, -1)) - // - next blocks - // - what resize ribbon does... - // XXX coordinate this with .resizeRibbon(..) - // XXX make this support an explicit list of gids.... - // XXX should this be here??? - preCacheJumpTargets: ['- Interface/Pre-cache potential jump target images', - function(target, sources, radius, size){ - target = target instanceof jQuery - ? this.ribbons.getElemGID(target) - // NOTE: data.getImage(..) can return null at start or end - // of ribbon, thus we need to account for this... - : (this.data.getImage(target) - || this.data.getImage(target, 'after')) - - sources = sources || this.config['preload-sources'] || ['bookmark', 'selected'] - sources = sources.constructor !== Array ? [sources] : sources - radius = radius || this.config['preload-radius'] || 9 - - var that = this - - // get preview... - var _getPreview = function(c){ - return that.images[c] - && that.images.getBestPreview(c, size, true).url - } - - // get a set of paths... - // NOTE: we are also ordering the resulting gids by their - // distance from target... - var _get = function(i, lst, source, radius, oddity, step){ - var found = oddity - var max = source.length - - for(var j = i+step; (step > 0 && j < max) || (step < 0 && j >= 0); j += step){ - var c = source[j] - - if(c == null || that.images[c] == null){ - continue - } - - // build the URL... - lst[found] = _getPreview(c) - - found += 2 - if(found >= radius*2){ - break - } - } - } - - // run the actual preload... - var _run = function(){ - sources.forEach(function(tag){ - // order... - if(tag == 'order'){ - var source = that.data.order - - // current ribbon... - }else if(tag == 'ribbon'){ - var source = that.data.ribbons[that.data.getRibbon()] - - // ribbon-gid... - } else if(tag in that.data.ribbons){ - var source = that.data.ribbons[tag] - - // nothing tagged then nothing to do... - } else if(that.data.tags == null - || that.data.tags[tag] == null - || that.data.tags[tag].length == 0){ - return - - // tag... - } else { - var source = that.data.tags[tag] - } - - size = size || that.ribbons.getVisibleImageSize() - - var i = that.data.order.indexOf(target) - var lst = [] - - // get the list of URLs before and after current... - _get(i ,lst, source, radius, 0, 1) - _get(i, lst, source, radius, 1, -1) - - // get target preview in case the target is not loaded... - var p = _getPreview(that.data.getImage(target)) - p && lst.splice(0, 0, p) - - // web worker... - if(that.cacheWorker != null){ - that.cacheWorker.postMessage(lst) - - // async inline... - } else { - // do the actual preloading... - lst.forEach(function(url){ - var img = new Image() - img.src = url - }) - } - }) - } - - if(that.cacheWorker != null){ - _run() - - } else { - setTimeout(_run, 0) - } - }], - - // NOTE: this will force sync resize if one of the following is true: - // - the target is not loaded - // - we are less than screen width from the edge - // - threshold is set to 0 - // XXX this is not signature compatible with data.updateRibbon(..) - // XXX do not do anything for off-screen ribbons... - updateRibbon: ['- Interface/Update partial ribbon size', - function(target, w, size, threshold){ - target = target instanceof jQuery - ? this.ribbons.getElemGID(target) - // NOTE: data.getImage(..) can return null at start or end - // of ribbon, thus we need to account for this... - : (this.data.getImage(target) - || this.data.getImage(target, 'after')) - - w = w || this.screenwidth - - // get config data and normalize... - size = (size - || this.config['ribbon-size-screens'] - || 5) * w - threshold = threshold == 0 ? threshold - : (threshold - || this.config['ribbon-resize-threshold'] - || 1) * w - - var timeout = this.config['ribbon-update-timeout'] - - // next/prev loaded... - var img = this.ribbons.getImage(target) - var nl = img.nextAll('.image:not(.clone)').length - var pl = img.prevAll('.image:not(.clone)').length - - // next/prev available... - // NOTE: we subtract 1 to remove the current and make these - // compatible with: nl, pl - var na = this.data.getImages(target, size, 'after').length - 1 - var pa = this.data.getImages(target, size, 'before').length - 1 - - // do the update... - // no threshold means force load... - if(threshold == 0 - // the target is not loaded... - || img.length == 0 - // passed hard threshold on the right... - || (nl < w && na > nl) - // passed hard threshold on the left... - || (pl < w && pa > pl)){ - - this.resizeRibbon(target, size) - - // do a late resize... - // loaded more than we need (crop?)... - } else if(na + pa < nl + pl - // passed threshold on the right... - || (nl < threshold && na > nl) - // passed threshold on the left... - || (pl < threshold && pa > pl) - // loaded more than we need by threshold... - || nl + pl + 1 > size + threshold){ - - return function(){ - // sync update... - if(timeout == null){ - this.resizeRibbon(target, size) - - // async update... - } else { - // XXX need to check if we are too close to the edge... - var that = this - //setTimeout(function(){ that.resizeRibbon(target, size) }, 0) - if(this.__update_timeout){ - clearTimeout(this.__update_timeout) - } - this.__update_timeout = setTimeout(function(){ - delete that.__update_timeout - that.resizeRibbon(target, size) - }, timeout) - } - } - } - }], - // XXX do we handle off-screen ribbons here??? - resizeRibbon: ['- Interface/Resize ribbon to n images', - function(target, size){ - size = size - || (this.config['ribbon-size-screens'] * this.screenwidth) - || (5 * this.screenwidth) - var data = this.data - var ribbons = this.ribbons - - // NOTE: we can't get ribbon via target directly here as - // the target might not be loaded... - var r_gid = data.getRibbon(target) - - if(r_gid == null){ - return - } - - // localize transition prevention... - // NOTE: for the initial load this may be empty... - var r = ribbons.getRibbon(r_gid) - - // XXX do we need to for example ignore unloaded (r.length == 0) - // ribbons here, for example not load ribbons too far off - // screen?? - - ribbons - .preventTransitions(r) - .updateRibbon( - data.getImages(target, size), - r_gid, - target) - .restoreTransitions(r, true) - }] -}) - -// NOTE: I do not fully understand it yet, but PartialRibbons must be -// setup BEFORE RibbonAlignToFirst, otherwise the later will break -// on shifting an image to a new ribbon... -// To reproduce: -// - setupe RibbonAlignToFirst first -// - go to top ribbon -// - shift image up -// XXX The two should be completely independent.... (???) -var PartialRibbons = -module.PartialRibbons = core.ImageGridFeatures.Feature({ - title: 'Partial Ribbons', - doc: 'Maintains partially loaded ribbons, this enables very lage ' - +'image sets to be hadled eficiently.', - - // NOTE: partial ribbons needs to be setup first... - // ...the reasons why things break otherwise is not too clear. - priority: 'high', - - tag: 'ui-partial-ribbons', - depends: ['ui'], - - - actions: PartialRibbonsActions, - - handlers: [ - ['focusImage.pre centerImage.pre', - function(target, list){ - // NOTE: we have to do this as we are called BEFORE the - // actual focus change happens... - // XXX is there a better way to do this??? - target = list != null ? target = this.data.getImage(target, list) : target - - this.updateRibbon(target) - }], - ['focusImage.post', - function(_, target){ - this.preCacheJumpTargets(target) - }], - - ['resizing.pre', - function(unit, size){ - if(unit == 'scale'){ - this.updateRibbon('current', this.screenwidth / size || 1) - - } else if(unit == 'screenwidth'){ - this.updateRibbon('current', size || 1) - - } else if(unit == 'screenheight'){ - size = size || 1 - - // convert target height in ribbons to width in images... - // NOTE: this does not account for compensation that - // .updateRibbon(..) makes for fitting whole image - // counts, this is a small enough error so as not - // to waste time on... - var s = this.ribbons.scale() - var h = this.ribbons.getScreenHeightRibbons() - var w = this.ribbons.getScreenWidthImages() - var nw = w / (h/size) - - this.updateRibbon('current', nw) - } - - //this.preCacheJumpTargets() - }], - ], -}) - - - -//--------------------------------------------------------------------- -// These features glue traverse and ribbon alignment... - - // XXX manual align needs more work... var AutoAlignRibbons = module.AutoAlignRibbons = core.ImageGridFeatures.Feature({