diff --git a/ui (gen4)/features/all.js b/ui (gen4)/features/all.js index 693e109b..291fd6e0 100755 --- a/ui (gen4)/features/all.js +++ b/ui (gen4)/features/all.js @@ -20,6 +20,7 @@ require('features/history') require('features/app') require('features/peer') require('features/ui') +require('features/ui-partial-ribbons-precache') require('features/ui-partial-ribbons') require('features/ui-partial-ribbons-2') require('features/ui-single-image') diff --git a/ui (gen4)/features/ui-partial-ribbons-2.js b/ui (gen4)/features/ui-partial-ribbons-2.js index 5fb1b05d..d6f47bd2 100755 --- a/ui (gen4)/features/ui-partial-ribbons-2.js +++ b/ui (gen4)/features/ui-partial-ribbons-2.js @@ -21,15 +21,21 @@ var core = require('features/core') var PartialRibbonsActions = actions.Actions({ config: { + // Number of screen widths to load... 'ribbon-size-screens': 7, - // the amount of screen widths to keep around the current image... + // Amount of screen widths to keep around the current image... 'ribbon-update-threshold': 1.2, - // the oversize multiplier limit when we resize the ribbon down... + // Oversize multiplier limit when we resize the ribbon down... 'ribbon-resize-threshold': 2, + + // Sets size of ribbons in single image mode... + 'ribbons-resize-single-image': 13, }, + // XXX preload??? + updateRibbon: ['- Interface/Update partial ribbon size', function(target, w, size, threshold){ target = target instanceof jQuery @@ -73,15 +79,52 @@ var PartialRibbonsActions = actions.Actions({ var na = gids.slice(gids.indexOf(target)+1).length var pa = gids.slice(0, gids.indexOf(target)).length - //console.log(`-- loaded: ${loaded} size: ${size}`) - // full resize... - // ribbon not loaded... - if(r.length == 0 + if(threshold == 0 + // ribbon not loaded... + || img.length == 0 // ribbon shorter than we expect... || (loaded < size && na + pa > loaded) + // ribbon too long... || loaded > size * threshold){ + console.log('RESIZE') this.resizeRibbon(target, size) + //*/ + + /*/ XXX long jump condition...... + if(img.length != 0 + && (r.length == 0 + // ribbon shorter than we expect... + || (loaded < size && na + pa > loaded) + // ribbon too long... + || loaded > size * threshold)){ + console.log('RESIZE') + this.resizeRibbon(target, size) + + // image is off screen -- align off then animate... + // 1) initial state + // T <- [---|---x---|---------------] + // 2) load new state but align off screen + // [-------T-------|-------|---] + // 3) animate + // [---|---T---|---------------] + // XXX this makes the draw worse... + } else if(img.length == 0 ){ + console.log('LONG-JUMP') + r.length == 0 ? + // ribbon not loaded... + this.resizeRibbon(target, size) + // simply update... + : this.ribbons + .preventTransitions(r) + .updateRibbonInPlace( + gids, + r_gid, + data.getImageOrder(this.current) > data.getImageOrder(target) ? + gids[gids.length - w] + : gids[w]) + .restoreTransitions(r, true) + //*/ // in-place update... // passed threshold on the right... @@ -90,6 +133,7 @@ var PartialRibbonsActions = actions.Actions({ || (pl < update_threshold && pa > pl) // loaded more than we need by threshold... || nl + pl + 1 > size + update_threshold){ + console.log('UPDATE') r.length == 0 ? // ribbon not loaded... this.resizeRibbon(target, size) @@ -113,36 +157,6 @@ var PartialRibbonsActions = actions.Actions({ .restoreTransitions(r, true) } }], - 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 - - // localize transition prevention... - // 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 - } - // 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, 'total'), - r_gid, - target) - .restoreTransitions(r, true) - }], }) var PartialRibbons = @@ -157,6 +171,9 @@ module.PartialRibbons = core.ImageGridFeatures.Feature({ depends: [ 'ui', ], + suggested: [ + 'ui-partial-ribbons-precache', + ], actions: PartialRibbonsActions, @@ -172,7 +189,13 @@ module.PartialRibbons = core.ImageGridFeatures.Feature({ }], ['resizing.post', function(_, unit, size){ - if(unit == 'scale'){ + // keep constant size in single image... + if(this.toggleSingleImage && this.toggleSingleImage('?') == 'on'){ + this.updateRibbon( + 'current', + this.config['ribbons-resize-single-image'] || 13) + + } else if(unit == 'scale'){ this.updateRibbon('current', this.screenwidth / size || 1) } else if(unit == 'screenwidth'){ diff --git a/ui (gen4)/features/ui-partial-ribbons-precache.js b/ui (gen4)/features/ui-partial-ribbons-precache.js new file mode 100755 index 00000000..a8a90dc8 --- /dev/null +++ b/ui (gen4)/features/ui-partial-ribbons-precache.js @@ -0,0 +1,245 @@ +/********************************************************************** +* +* +* +**********************************************************************/ +((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) +(function(require){ var module={} // make module AMD/node compatible... +/*********************************************************************/ + +var actions = require('lib/actions') +var features = require('lib/features') + +var core = require('features/core') + + + +/*********************************************************************/ + +var PreCacheActions = actions.Actions({ + config: { + // 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) + } + }], +}) + +var PreCache = +module.PreCache = core.ImageGridFeatures.Feature({ + title: '', + doc: '', + + tag: 'ui-partial-ribbons-precache', + depends: [ + 'ui', + ], + + actions: PreCacheActions, + + handlers: [ + ['focusImage.post', + function(_, target){ + this.preCacheJumpTargets(target) }], + /*/ + ['resizing.pre', + function(unit, size){ + this.preCacheJumpTargets() }], + //*/ + ], +}) + + + + +/********************************************************************** +* vim:set ts=4 sw=4 : */ return module }) diff --git a/ui (gen4)/features/ui-partial-ribbons.js b/ui (gen4)/features/ui-partial-ribbons.js index 01d10349..7cbc5e91 100755 --- a/ui (gen4)/features/ui-partial-ribbons.js +++ b/ui (gen4)/features/ui-partial-ribbons.js @@ -24,212 +24,18 @@ var core = require('features/core') // XXX updateRibbon(..) is not signature compatible with data.updateRibbon(..) var PartialRibbonsActions = actions.Actions({ config: { - // number of screen widths to load... + // Number of screen widths to load... 'ribbon-size-screens': 7, - // number of screen widths to edge to trigger reload... + // Number of screen widths to edge to trigger reload... 'ribbon-resize-threshold': 1.5, - // timeout before a non-forced ribbon size update happens after + // 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 @@ -312,39 +118,6 @@ var PartialRibbonsActions = actions.Actions({ } } }], - // 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 @@ -370,6 +143,9 @@ module.PartialRibbons = core.ImageGridFeatures.Feature({ depends: [ 'ui' ], + suggested: [ + 'ui-partial-ribbons-precache', + ], actions: PartialRibbonsActions, @@ -384,14 +160,15 @@ module.PartialRibbons = core.ImageGridFeatures.Feature({ this.updateRibbon(target) }], - ['focusImage.post', - function(_, target){ - this.preCacheJumpTargets(target) - }], - ['resizing.pre', function(unit, size){ - if(unit == 'scale'){ + // keep constant size in single image... + if(this.toggleSingleImage && this.toggleSingleImage('?') == 'on'){ + this.updateRibbon( + 'current', + this.config['ribbons-resize-single-image'] || 13) + + } else if(unit == 'scale'){ this.updateRibbon('current', this.screenwidth / size || 1) } else if(unit == 'screenwidth'){ @@ -412,8 +189,6 @@ module.PartialRibbons = core.ImageGridFeatures.Feature({ this.updateRibbon('current', nw) } - - //this.preCacheJumpTargets() }], ], }) diff --git a/ui (gen4)/features/ui.js b/ui (gen4)/features/ui.js index 60b8229e..f15132d4 100755 --- a/ui (gen4)/features/ui.js +++ b/ui (gen4)/features/ui.js @@ -426,6 +426,41 @@ module.ViewerActions = actions.Actions({ })], + // NOTE: this not used directly, mainly designed as a utility to be + // used for various partial ribbon implementations... + // 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 + + // localize transition prevention... + // 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 + } + // 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, 'total'), + r_gid, + target) + .restoreTransitions(r, true) + }], + + // General UI stuff... // NOTE: this is applicable to all uses... toggleTheme: ['Interface/Theme/Viewer theme',