From 0e7c3a32fd219212a4e498adf5b605dbd5de579a Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Wed, 26 Apr 2017 00:32:22 +0300 Subject: [PATCH] experementing with virtual-dom... Signed-off-by: Alex A. Naanou --- ui (gen4)/features/all.js | 1 + ui (gen4)/features/meta.js | 3 +- ui (gen4)/features/ui-partial-ribbons-vdom.js | 368 ++++++++++++++++++ ui (gen4)/index.html | 2 + 4 files changed, 373 insertions(+), 1 deletion(-) create mode 100755 ui (gen4)/features/ui-partial-ribbons-vdom.js diff --git a/ui (gen4)/features/all.js b/ui (gen4)/features/all.js index 291fd6e0..790713f4 100755 --- a/ui (gen4)/features/all.js +++ b/ui (gen4)/features/all.js @@ -23,6 +23,7 @@ require('features/ui') require('features/ui-partial-ribbons-precache') require('features/ui-partial-ribbons') require('features/ui-partial-ribbons-2') +require('features/ui-partial-ribbons-vdom') require('features/ui-single-image') require('features/ui-chrome') require('features/ui-progress') diff --git a/ui (gen4)/features/meta.js b/ui (gen4)/features/meta.js index 16d9a0bc..c59c83d4 100755 --- a/ui (gen4)/features/meta.js +++ b/ui (gen4)/features/meta.js @@ -65,7 +65,8 @@ core.ImageGridFeatures.Feature('viewer-testing', [ 'ui-single-image', //'ui-partial-ribbons', // XXX this still has problems... - 'ui-partial-ribbons-2', + //'ui-partial-ribbons-2', + 'ui-partial-ribbons-vdom', 'marks', 'ui-range', diff --git a/ui (gen4)/features/ui-partial-ribbons-vdom.js b/ui (gen4)/features/ui-partial-ribbons-vdom.js new file mode 100755 index 00000000..d30b2d35 --- /dev/null +++ b/ui (gen4)/features/ui-partial-ribbons-vdom.js @@ -0,0 +1,368 @@ +/********************************************************************** +* +* +* +**********************************************************************/ +((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) +(function(require){ var module={} // make module AMD/node compatible... +/*********************************************************************/ + +var vdom = require('ext-lib/virtual-dom') + +var actions = require('lib/actions') +var features = require('lib/features') + +var core = require('features/core') + + + +/*********************************************************************/ +// XXX EXPERIMENT: use virtual-dom to do ribbon updates... +// - create and maintain a full ribbon view from .ribbon-set and down... +// - sync with dom only when needed... +// - on direct edits (use .update() / .reload() ???) +// - on .updateRibbon(..) -- lazily and when needed... +// - see if we can offload the vdom logic to a worker... +// XXX using virtual-dom... +// - make the below functions into methods... +// - add .sync() to sync-up the DOM with virtual dom... +// ...this would lead to .updateRibbon(..) to only need to +// figure out when to call .sync() +// XXX Q: should this be a special imagegrid/ribbons.js implementation +// or a different level API?? +// ...maybe: imagegrid/ribbons-vdom.js as a completely standalone +// module that would be mixed with imagegrid/ribbons.js -- sounds +// a bit too complicated, overkill?? +// XXX Q: how should we handle "sync" stuff??? +// things like toggling marks or rotating an image... +// +// +//--------------------------------------------------------------------- + +window.vdom = vdom + + + +//--------------------------------------------------------------------- + +// attribute hooks... +function GID(value){ + this.value = JSON.stringify(value) + .replace(/^"(.*)"$/g, '$1') } +GID.prototype.hook = function(elem, prop){ + elem.setAttribute(prop, this.value) } + +function VALUE(value){ + this.value = value || '' } +VALUE.prototype.hook = function(elem, prop){ + this.value != '' + && elem.setAttribute(prop, this.value) } + + + +//--------------------------------------------------------------------- + +// XXX get vertical offset and scale... +var makeView = +window.makeView = +function(data, images, count){ + // XXX + var s = 1 + var x = 0 + + var ribbons = data.ribbon_order + .map(function(gid){ + return makeRibbon(gid, data, images, count) }) + + return vdom.h('div.ribbon-set', { + key: 'ribbon-set', + style: { + transform: 'scale('+ s +', '+ s +')', + } + }, [ + vdom.h('div.ribbon-locator', { + key: 'ribbon-locator', + style: { + transform: 'translate3d(0px, '+ x +'px, 0px)', + }, + }, + ribbons) + ]) +} + + +// XXX calc/get count... +var makeRibbon = +window.makeRibbon = +function(gid, data, images, count){ + var imgs = [] + + data.getImages(gid, count, 'total') + .forEach(function(gid){ + imgs.push(makeImage(gid, data, images)) + makeImageMarks(gid, data, images) + .forEach(function(mark){ + imgs.push(mark) }) + }) + return vdom.h('div.ribbon', { + key: 'ribbon-'+gid, + + gid: new GID(gid), + }, + imgs) +} + + +// XXX handle previews -- hook??? +// NOTE: at this point this does not account for previews at all... +var makeImage = +window.makeImage = +function(gid, data, images){ + var image = (images || {})[gid] || {} + var current = data.current == gid ? '.current' : '' + + gid = JSON.stringify(gid).slice(1, -1) + + return vdom.h('div.image'+current, { + key: 'image-'+gid, + + gid: new GID(gid), + + orientation: new VALUE(image.orientation), + flipped: new VALUE(image.flipped), + + // XXX preview stuff??? + }) +} + +// XXX +var makeImageMarks = +window.makeImageMarks = +function(gid, data, images){ + gid = JSON.stringify(gid).slice(1, -1) + var marks = [] + + // XXX + + return marks +} + + + +/*********************************************************************/ + +var PartialRibbonsActions = actions.Actions({ + config: { + // Number of screen widths to load... + 'ribbon-size-screens': 7, + + // Amount of screen widths to keep around the current image... + 'ribbon-update-threshold': 1.2, + + // 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': 21, + + // can be: + // 'hybrid' + // 'resize' + 'ribbons-in-place-update-mode': 'resize', + + 'ribbons-in-place-update-timeout': 100, + + // XXX + 'ribbon-update-timeout': 120, + }, + + updateRibbon: ['- Interface/Update partial ribbon size', + function(target, w, size, threshold, preload){ + 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'] + || 9) * w + threshold = threshold == 0 ? threshold + : (threshold + || this.config['ribbon-resize-threshold'] + || 2) + var update_threshold = (this.config['ribbon-update-threshold'] || 2) * w + preload = preload === undefined ? true : preload + var data = this.data + var ribbons = this.ribbons + + var t = Date.now() + this.__last_ribbon_update = this.__last_ribbon_update || t + var timeout = this.config['ribbons-in-place-update-timeout'] + var update_timeout = this.config['ribbon-update-timeout'] + + // 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) + + // 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 + var loaded = nl + pl + 1 + + // next/prev available... + // NOTE: we do not include target in counts... + var gids = this.data.getImages(target, size, 'total') + var na = gids.slice(gids.indexOf(target)+1).length + var pa = gids.slice(0, gids.indexOf(target)).length + + // full resize... + 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 + // passed hard threshold -- too close to edge... + || (nl < w && na > nl) || (pl < w && pa > pl)){ + //console.log('RESIZE (sync)') + this.resizeRibbon(target, size) + + // more complex cases... + // passed threshold on the right... + } else if((nl < update_threshold && na > nl) + // passed threshold on the left... + || (pl < update_threshold && pa > pl) + // loaded more than we need by threshold... + || nl + pl + 1 > size + update_threshold){ + // resize... + if(this.config['ribbons-in-place-update-mode'] == 'resize' + // no ribbon loaded... + || r.length == 0 + // only if we are going slow... + || (timeout != null + && (t - this.__last_ribbon_update > timeout)) + // full screen... + || (this.toggleSingleImage + && this.toggleSingleImage('?') == 'on')){ + return function(){ + 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... + // XXX this is faster than .resizeRibbon(..) but it's not + // used unconditionally because I can't get rid of + // sync up images being replaced... + // ...note that .resizeRibbon(..) is substantially + // slower (updates DOM), i.e. introduces a lag, but + // the results look OK... + // XXX approaches to try: + // - wait for images to preload and only then update... + // - preload images in part of a ribbon and when ready update... + // ...this is like the first but we wait for less images... + } else { + //console.log('UPDATE', t - this.__last_ribbon_update) + var c = gids.indexOf(data.getImage('current', r_gid)) + var t = gids.indexOf(target) + + ribbons + .preventTransitions(r) + .updateRibbonInPlace(gids, r_gid, target) + .restoreTransitions(r, true) + } + } + + this.__last_ribbon_update = t + }], +}) + +var PartialRibbons = +module.PartialRibbons = core.ImageGridFeatures.Feature({ + title: '', + doc: '', + + priority: 'high', + + tag: 'ui-partial-ribbons-vdom', + exclusive: ['ui-partial-ribbons'], + depends: [ + 'ui', + ], + suggested: [ + 'ui-partial-ribbons-precache', + ], + + 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) + }], + ['resizing.post', + function(_, unit, size){ + // 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'){ + 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) + } + }], + ], +}) + + + +/********************************************************************** +* vim:set ts=4 sw=4 : */ return module }) diff --git a/ui (gen4)/index.html b/ui (gen4)/index.html index 651e588a..8cd1fd71 100755 --- a/ui (gen4)/index.html +++ b/ui (gen4)/index.html @@ -54,6 +54,8 @@ if(window.require && window.nw){ + +