From 22627468eb3bb8fd55227e66e934ef62ab70502c Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Tue, 16 May 2017 00:26:37 +0300 Subject: [PATCH] refactoring of the ui/render/access -- started split of viewer into a renderer and viewer actions... Signed-off-by: Alex A. Naanou --- ui (gen4)/features/app.js | 6 +- ui (gen4)/features/external-editor.js | 4 +- ui (gen4)/features/keyboard.js | 6 +- ui (gen4)/features/meta.js | 6 +- ui (gen4)/features/ui-chrome.js | 26 +- ui (gen4)/features/ui-legacy.js | 10 +- ui (gen4)/features/ui-partial-ribbons-vdom.js | 6 +- ui (gen4)/features/ui-progress.js | 2 +- ui (gen4)/features/ui-ranges.js | 14 +- ui (gen4)/features/ui-react.js | 23 + ui (gen4)/features/ui-ribbons.js | 389 ++++++++++++++ ui (gen4)/features/ui-single-image.js | 8 +- ui (gen4)/features/ui-slideshow.js | 2 +- ui (gen4)/features/ui-status.js | 16 +- ui (gen4)/features/ui-virtual-dom.js | 495 +++++++++++++++++- ui (gen4)/features/ui-widgets.js | 32 +- ui (gen4)/features/ui.js | 478 ++++------------- ui (gen4)/ui.js | 32 +- 18 files changed, 1095 insertions(+), 460 deletions(-) diff --git a/ui (gen4)/features/app.js b/ui (gen4)/features/app.js index 4389dda5..4bf1db94 100755 --- a/ui (gen4)/features/app.js +++ b/ui (gen4)/features/app.js @@ -132,7 +132,7 @@ var AppControlActions = actions.Actions({ this.ribbons.preventTransitions() // hide the viewer to hide any animation crimes... - this.ribbons.viewer[0].style.visibility = 'hidden' + this.dom[0].style.visibility = 'hidden' // XXX async... // ...this complicates things as we need to do the next @@ -146,7 +146,7 @@ var AppControlActions = actions.Actions({ .ribbons .restoreTransitions() - that.ribbons.viewer[0].style.visibility = '' + that.dom[0].style.visibility = '' }, 100) } @@ -292,7 +292,7 @@ module.AppButtons = core.ImageGridFeatures.Feature({ ['start toggleFullScreen', function(){ var fullscreen = this.toggleFullScreen('?') - var buttons = this.ribbons.viewer.find('.app-buttons') + var buttons = this.dom.find('.app-buttons') // fullscreen button... buttons.find('.fullscreen.button') diff --git a/ui (gen4)/features/external-editor.js b/ui (gen4)/features/external-editor.js index a3f6c1c3..6dcabb85 100755 --- a/ui (gen4)/features/external-editor.js +++ b/ui (gen4)/features/external-editor.js @@ -175,7 +175,7 @@ var ExternalEditorUIActions = actions.Actions({ var path = e.find('.text').last().text() var txt = e.find('.text').first().text() - var b = overlay.Overlay(that.ribbons.viewer, + var b = overlay.Overlay(that.dom, browseWalk.makeWalk(null, path, // XXX '*+(exe|cmd|ps1|sh)', @@ -291,7 +291,7 @@ var ExternalEditorUIActions = actions.Actions({ make(['Add new editor...']) .on('open', function(){ closingPrevented = true - var b = overlay.Overlay(that.ribbons.viewer, + var b = overlay.Overlay(that.dom, browseWalk.makeWalk( null, '/', // XXX diff --git a/ui (gen4)/features/keyboard.js b/ui (gen4)/features/keyboard.js index f99d9fdf..af19acd3 100755 --- a/ui (gen4)/features/keyboard.js +++ b/ui (gen4)/features/keyboard.js @@ -458,7 +458,7 @@ var KeyboardActions = actions.Actions({ return that.__keyboard_config } }, - function(){ return that.ribbons.viewer }) + function(){ return that.dom }) return kb }, testKeyboardDoc: ['- Interface/', @@ -718,9 +718,9 @@ var KeyboardActions = actions.Actions({ // NOTE: the target element must be focusable... var target = this.__keyboard_event_source = - this.config['keyboard-event-source'] == null ? this.ribbons.viewer + this.config['keyboard-event-source'] == null ? this.dom : this.config['keyboard-event-source'] == 'window' ? $(window) - : this.config['keyboard-event-source'] == 'viewer' ? this.ribbons.viewer + : this.config['keyboard-event-source'] == 'viewer' ? this.dom : this.config['keyboard-event-source'] == 'document' ? $(document) : $(this.config['keyboard-event-source']) diff --git a/ui (gen4)/features/meta.js b/ui (gen4)/features/meta.js index 9f54f4f2..f5d220d9 100755 --- a/ui (gen4)/features/meta.js +++ b/ui (gen4)/features/meta.js @@ -66,10 +66,14 @@ core.ImageGridFeatures.Feature('viewer-testing', [ 'ui', 'keyboard', + // XXX BUG?: should this be indicated as a missing dependency??? + 'missing-feature', + // XXX 'ui-ribbons-render', 'ui-vdom-render', - 'ui-react-render', + //'ui-react-render', + //*/ // features... 'ui-cursor', diff --git a/ui (gen4)/features/ui-chrome.js b/ui (gen4)/features/ui-chrome.js index ff068fa5..8a7f4ec9 100755 --- a/ui (gen4)/features/ui-chrome.js +++ b/ui (gen4)/features/ui-chrome.js @@ -46,12 +46,12 @@ var BoundsIndicatorsActions = actions.Actions({ bottom: '.bottom-indicator', }[direction] - var indicator = this.ribbons.viewer.find(cls) + var indicator = this.dom.find(cls) if(indicator.length == 0){ indicator = $('
') .addClass(cls.replace('.', '')) - .appendTo(this.ribbons.viewer) + .appendTo(this.dom) } return indicator @@ -262,7 +262,7 @@ module.CurrentImageIndicator = core.ImageGridFeatures.Feature({ function(){ var fadein = this.config['current-image-indicator-fadein'] this.updateCurrentImageIndicator() - this.ribbons.viewer.find('.current-marker') + this.dom.find('.current-marker') .css({ display: 'block', opacity: 0, @@ -302,7 +302,7 @@ module.CurrentImageIndicator = core.ImageGridFeatures.Feature({ && clearTimeout(this.__current_image_indicator_restore_timeout) delete this.__current_image_indicator_restore_timeout - this.ribbons.viewer + this.dom .find('.current-marker') .velocity({opacity: 0}, { duration: 100 }) }], @@ -312,7 +312,7 @@ module.CurrentImageIndicator = core.ImageGridFeatures.Feature({ this.__current_image_indicator_restore_timeout = setTimeout(function(){ that.updateCurrentImageIndicator() - that.ribbons.viewer + that.dom .find('.current-marker') .velocity({opacity: 1}, { duration: 100 }) }, this.config['current-image-indicator-restore-delay'] || 500) @@ -322,12 +322,12 @@ module.CurrentImageIndicator = core.ImageGridFeatures.Feature({ ['toggleSingleImage', function(){ if(this.toggleSingleImage('?') == 'off'){ - this.ribbons.viewer.find('.current-marker') + this.dom.find('.current-marker') .delay(150) .animate({opacity: 1}, 100) } else { - this.ribbons.viewer.find('.current-marker') + this.dom.find('.current-marker') .css({ opacity: 0 }) } }], @@ -343,7 +343,7 @@ var makeIndicatorHiderOnFastAction = function(hide_timeout){ } var that = this - var m = this.ribbons.viewer.find('.current-marker') + var m = this.dom.find('.current-marker') var t = this.config[hide_timeout] var cur = this.current @@ -431,13 +431,13 @@ module.CurrentImageIndicatorHideOnScreenNav = core.ImageGridFeatures.Feature({ // NOTE: we use .pre events here to see if we have moved... ['prevScreen.post nextScreen.post', function(){ - var m = this.ribbons.viewer.find('.current-marker') + var m = this.dom.find('.current-marker') m.css({ opacity: 0 }) }], ['focusImage.post', function(){ - var m = this.ribbons.viewer.find('.current-marker') + var m = this.dom.find('.current-marker') m.css({ opacity: '' }) }], @@ -464,7 +464,7 @@ var updateBaseRibbonIndicator = function(img){ } if(m.length == 0){ - m = this.ribbons.viewer.find('.base-ribbon-marker') + m = this.dom.find('.base-ribbon-marker') // make the indicator... if(m.length == 0){ @@ -478,7 +478,7 @@ var updateBaseRibbonIndicator = function(img){ // XXX this is wrong -- need to calculate the offset after the move and not now... if(base.offset().left < 0){ - m.css('left', (img.position().left + img.width()/2 - this.ribbons.viewer.width()/2) / scale) + m.css('left', (img.position().left + img.width()/2 - this.dom.width()/2) / scale) } else { m.css('left', '') @@ -530,7 +530,7 @@ module.PassiveBaseRibbonIndicator = core.ImageGridFeatures.Feature({ actions: actions.Actions({ togglePassiveBaseRibbonIndicator: ['Interface/Passive base ribbon indicator', toggler.CSSClassToggler( - function(){ return this.ribbons.viewer }, + function(){ return this.dom }, 'show-passive-base-ribbon-indicator', function(state){ this.config['ui-show-passive-base-ribbon-indicator'] = state == 'on' }) ], diff --git a/ui (gen4)/features/ui-legacy.js b/ui (gen4)/features/ui-legacy.js index 154ad789..62a57bf3 100755 --- a/ui (gen4)/features/ui-legacy.js +++ b/ui (gen4)/features/ui-legacy.js @@ -153,7 +153,7 @@ module.DirectControlHammer = core.ImageGridFeatures.Feature({ // hide and remove current image indicator... // NOTE: it will be reconstructed on // next .focusImage(..) - var m = that.ribbons.viewer + var m = that.dom .find('.current-marker') .velocity({opacity: 0}, { duration: 100, @@ -245,14 +245,14 @@ module.IndirectControl = core.ImageGridFeatures.Feature({ if(state == null){ return (this.ribbons - && this.ribbons.viewer - && this.ribbons.viewer.data('hammer')) + && this.dom + && this.dom.data('hammer')) || 'none' // on... } else if(state == 'handling-swipes'){ var that = this - var viewer = this.ribbons.viewer + var viewer = this.dom // prevent multiple handlers... if(viewer.data('hammer') != null){ @@ -272,7 +272,7 @@ module.IndirectControl = core.ImageGridFeatures.Feature({ // off... } else { - this.ribbons.viewer + this.dom .off('swipeleft') .off('swiperight') .off('swipeup') diff --git a/ui (gen4)/features/ui-partial-ribbons-vdom.js b/ui (gen4)/features/ui-partial-ribbons-vdom.js index 5015ebdc..57e75ee3 100755 --- a/ui (gen4)/features/ui-partial-ribbons-vdom.js +++ b/ui (gen4)/features/ui-partial-ribbons-vdom.js @@ -435,9 +435,7 @@ var VirtualDOMRibbonsPrototype = { // concept, so we build the state on demand... // XXX get scale from config on initial load... sync: function(target, size){ - var dom = this.dom = this.dom - // get/create the ribbon-set... - || this.imagegrid.ribbons.getRibbonSet(true) + var dom = this.dom var state = this.state ? Object.create(this.state) : {} target && (state.target = target) @@ -447,7 +445,7 @@ var VirtualDOMRibbonsPrototype = { if(this.vdom == null){ var n = this.vdom = this.makeView(state, true) var v = vdom.create(n) - dom.replaceWith(v) + this.imagegrid.dom.append(v) this.dom = v // patch state... diff --git a/ui (gen4)/features/ui-progress.js b/ui (gen4)/features/ui-progress.js index 7ba2a83e..d1b97ae1 100755 --- a/ui (gen4)/features/ui-progress.js +++ b/ui (gen4)/features/ui-progress.js @@ -48,7 +48,7 @@ var ProgressActions = actions.Actions({ // XXX revise styles... showProgress: ['- Interface/Show progress bar...', function(text, value, max){ - var viewer = this.ribbons.viewer + var viewer = this.dom var that = this var msg = text instanceof Array ? text.slice(1).join(': ') : null diff --git a/ui (gen4)/features/ui-ranges.js b/ui (gen4)/features/ui-ranges.js index 247a2452..6299e00f 100755 --- a/ui (gen4)/features/ui-ranges.js +++ b/ui (gen4)/features/ui-ranges.js @@ -27,7 +27,7 @@ var RangeActions = actions.Actions({ makeBrace: ['- Range/', function(type, gid){ var cls = type == 'open' ? 'brace-open' : 'brace-close' - var r = this.ribbons.viewer.find('.ribbon') + var r = this.dom.find('.ribbon') var brace = this.ribbons.getRibbon(gid).find('.mark.'+cls) @@ -61,7 +61,7 @@ var RangeActions = actions.Actions({ // go but it sure makes things simpler... if(range == null){ update = true - this.ribbons.viewer + this.dom .find('.ribbon .mark.brace') .remove() @@ -90,7 +90,7 @@ var RangeActions = actions.Actions({ } if(update){ - var r = this.ribbons.viewer.find('.ribbon') + var r = this.dom.find('.ribbon') // XXX this does not work for non-current images ... this.ribbons.preventTransitions(r) @@ -103,7 +103,7 @@ var RangeActions = actions.Actions({ // XXX not sure if this is the right way to go... {browseMode: function(){ return !this.data.__range && 'disabled' }}, function(image){ - var r = this.ribbons.viewer.find('.ribbon') + var r = this.dom.find('.ribbon') delete this.data.__range this.updateRangeIndicators() @@ -246,7 +246,7 @@ module.Range = core.ImageGridFeatures.Feature({ // show/hide off-screen indicators... // XXX STUB: should we animate indicators??? - ['setScale.pre', + ['viewScale.pre', function(scale){ var range = this.data.__range if(!this.ribbons || !range){ @@ -259,7 +259,7 @@ module.Range = core.ImageGridFeatures.Feature({ }], [[ 'focusImage', - 'setScale', + 'viewScale', 'updateRangeIndicators', ], function(_, gid){ @@ -273,7 +273,7 @@ module.Range = core.ImageGridFeatures.Feature({ return } - var Wr = this.ribbons.viewer.width() + var Wr = this.dom.width() var W = (Wr / this.scale) / 2 var a = this.data.getImageOrder(range[0]) diff --git a/ui (gen4)/features/ui-react.js b/ui (gen4)/features/ui-react.js index 8c08493d..e5bb666e 100755 --- a/ui (gen4)/features/ui-react.js +++ b/ui (gen4)/features/ui-react.js @@ -7,6 +7,7 @@ (function(require){ var module={} // make module AMD/node compatible... /*********************************************************************/ +var object = require('lib/object') var actions = require('lib/actions') var features = require('lib/features') @@ -14,9 +15,31 @@ var core = require('features/core') +/*********************************************************************/ + +var ViewerClassPrototype = { +} + + +var ViewerPrototype = { + sync: function(){ + }, +} + + +var Viewer = +module.Viewer = +object.makeConstructor('Viewer', + ViewerClassPrototype, + ViewerPrototype) + + + /*********************************************************************/ var ReactActions = actions.Actions({ + get viewer(){ + }, }) var React = diff --git a/ui (gen4)/features/ui-ribbons.js b/ui (gen4)/features/ui-ribbons.js index c7c2e0ab..9fb0cb21 100755 --- a/ui (gen4)/features/ui-ribbons.js +++ b/ui (gen4)/features/ui-ribbons.js @@ -7,18 +7,356 @@ (function(require){ var module={} // make module AMD/node compatible... /*********************************************************************/ +var object = require('lib/object') var actions = require('lib/actions') var features = require('lib/features') var core = require('features/core') +var ribbons = require('imagegrid/ribbons') + /*********************************************************************/ var RibbonsActions = actions.Actions({ + + get dom(){ + return this.ribbons ? this.ribbons.viewer : undefined }, + + + // NOTE: this expects that ribbons will maintain .parent.images... + // NOTE: when getting rid of ribbons need to also remove the .parent + // reference... + // XXX remove... + get ribbons(){ + return this.__ribbons }, + set ribbons(ribbons){ + this.__ribbons = ribbons + ribbons.parent = this + }, + + + load: [ + function(data){ + return function(){ + // recycle the viewer if one is not given specifically... + var viewer = data.viewer + viewer = viewer == null && this.ribbons != null + ? this.dom + : viewer + + if(this.ribbons == null){ + this.ribbons = ribbons.Ribbons(viewer, this.images) + // XXX is this correct??? + this.ribbons.__image_updaters = [this.updateImage.bind(this)] + + } else { + this.ribbons.clear() + this.ribbons.images = this.images + } + + this.reload() + } + }], + // NOTE: this will pass the .ribbons.updateData(..) a custom ribbon + // updater if one is defined here as .updateRibbon(target) action + // + // XXX HACK: two sins: + // - actions.updateRibbon(..) and ribbons.updateRibbon(..) + // are NOT signature compatible... + // - we depend on the internals of a custom add-on feature + reload: ['Interface/Reload viewer', + function(force){ + // full reload... + if(force == 'full'){ + //this.stop() + /* + killAllWorkers() + .done(function(){ + reload() + }) + */ + location.reload() + } + + this.ribbons.preventTransitions() + + // NOTE: this essentially sets the update threshold to 0... + // XXX this should be a custom arg... + force = force ? 0 : null + + return function(){ + // see if we've got a custom ribbon updater... + var that = this + var settings = this.updateRibbon != null + // XXX this should be: { updateRibbon: this.updateRibbon.bind(this) } + ? { updateRibbon: function(_, ribbon){ + return that.updateRibbon(ribbon, null, null, force) + } } + : null + + this.ribbons.updateData(this.data, settings) + + this + // XXX should this be here??? + .refresh() + .focusImage() + + // XXX HACK to make browser redraw images... + this.scale = this.scale + + this.ribbons.restoreTransitions() + } + }], + // NOTE: this will trigger .updateImage hooks... + refresh: ['Interface/Refresh images without reloading', + function(gids, scale){ + gids = gids || '*' + var size = scale != null ? + this.ribbons.getVisibleImageSize('min', scale) + : null + + this.ribbons.updateImage(gids, null, size) + }], + clear: [ + function(){ this.ribbons && this.ribbons.clear() }], + // XXX do we need clone??? + clone: [function(full){ + return function(res){ + if(this.ribbons){ + // NOTE: this is a bit wasteful as .ribbons will clone + // their ref to .images that we will throw away... + res.ribbons = this.ribbons.clone() + res.ribbons.images = res.images + } + } + }], + + + replaceGid: [ + function(from, to){ + return function(res){ + res && this.ribbons.replaceGid(from, to) + } + }], + + + // This is called by .ribbons, the goal is to use it to hook into + // image updating from features and extensions... + // + // NOTE: not intended for calling manually, use .refresh(..) instead... + // + // XXX EXPERIMENTAL... + // ...need this to get triggered by .ribbons + // 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 (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. + })], + + // 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) + }], + + + // NOTE: this will align only a single image... + // XXX do we need these low level primitives here??? + centerImage: ['- Interface/Center an image in ribbon horizontally', + function(target, align, offset, scale){ + target = target instanceof jQuery + ? this.ribbons.getElemGID(target) + : target + + // align current ribbon... + this.ribbons.centerImage(target, align, offset, scale) + }], + centerRibbon: ['- Interface/Center a ribbon vertically', + function(target){ + target = target instanceof jQuery + ? this.ribbons.getElemGID(target) + : target + + // align current ribbon... + this.ribbons.centerRibbon(target) + }], + + + focusImage: [ + function(target, list){ + return function(){ + this.ribbons.focusImage(this.data != null ? this.current : target) } }], + focusRibbon: [ + function(target, mode){ + mode = mode || this.config['ribbon-focus-mode'] + + var c = this.data.getRibbonOrder() + var i = this.data.getRibbonOrder(target) + // NOTE: we are not changing the direction here based on + // this.direction as swap will confuse the user... + var direction = c < i ? 'before' : 'after' + + if(mode == 'visual'){ + var ribbons = this.ribbons + var r = this.data.getRibbon(target) + var t = ribbons.getImageByPosition('current', r) + + if(t.length > 1){ + t = t.eq(direction == 'before' ? 0 : 1) + } + + t = ribbons.getElemGID(t) + + this.focusImage(t, r) + } + }], + + // Zoom/scale protocol... + resizing: [ + 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: .viewScale(..) + + var that = this + // stop currently running transition... + this.ribbons.scale(this.ribbons.scale()) + + // transitionend handler... + if(!this.__resize_handler){ + this.__resize_handler = function(){ + that.__post_resize + && that.resizingDone() + delete that.__post_resize + } + } + this.ribbons.getRibbonSet() + .off('transitionend', this.__resize_handler) + .on('transitionend', this.__resize_handler) + + // timeout handler... + this.__post_resize && clearTimeout(this.__post_resize) + return function(){ + this.__post_resize = setTimeout( + this.__resize_handler, + this.config['resize-done-timeout'] || 300) + } + })], + + viewScale: ['- Zoom/', + function(scale){ + if(scale == null || scale == '?'){ + return this.ribbons != null ? this.ribbons.scale() : null + } + + this.resizing.chainCall(this, function(){ + this.ribbons + && scale + && this.ribbons.scale(scale) + // NOTE: we pass explicit scale here to compensate for animation... + this.refresh('*', scale) + }, 'scale', scale) + }], + // 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){ + if(count == '?'){ + return this.ribbons != null ? + this.ribbons.getScreenWidthImages() + : null + } + + 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 + } + // XXX .ribbons... + this.ribbons.fitImage(count) + // NOTE: we pass explicit scale here to compensate for animation... + this.refresh('*', this.ribbons.getScreenWidthImages(1) / count) + }, 'screenwidth', count, overflow) + }], + // NOTE: this does not account for ribbon spacing... + fitRibbon: ['Zoom/Fit ribbon vertically', + function(count, whole){ + if(count == '?'){ + return this.ribbons != null ? + this.ribbons.getScreenHeightRibbons() + : null + } + + this.resizing.chainCall(this, function(){ + // XXX .ribbons... + this.ribbons.fitRibbon(count, whole) + // NOTE: we pass explicit scale here to compensate for animation... + this.refresh('*', this.ribbons.getScreenHeightRibbons(1, whole) / count) + }, 'screenheight', count, whole) + }], + + + ribbonRotation: ['- Interface|Ribbon/', + function(a){ + if(arguments.length > 0){ + this.ribbons.rotate(a) + + } else { + return this.ribbons.rotate() || 0 + } + }], + + // XXX move all the stuff from UI that binds actions to ribbons... + // XXX }) + var Ribbons = module.Ribbons = core.ImageGridFeatures.Feature({ title: '', @@ -29,6 +367,9 @@ module.Ribbons = core.ImageGridFeatures.Feature({ depends: [ // XXX ], + suggested: [ + 'ui-ribbons-edit-render', + ], actions: RibbonsActions, @@ -37,6 +378,54 @@ module.Ribbons = core.ImageGridFeatures.Feature({ +//--------------------------------------------------------------------- +// XXX + +var PartialRibbonsActions = actions.Actions({ +}) + +var PartialRibbons = +module.PartialRibbons = core.ImageGridFeatures.Feature({ + title: '', + doc: '', + + tag: 'ui-partial-ribbons-render', + exclusive: ['ui-render'], + depends: [ + // XXX this will need to reuse part of the actions defined in Ribbons... + ], + suggested: [ + 'ui-ribbons-edit-render', + ], + + actions: PartialRibbonsActions, + + handlers: [], +}) + + +//--------------------------------------------------------------------- + +var RibbonsEditActions = actions.Actions({ +}) + + +var RibbonsEdit = +module.RibbonsEdit = core.ImageGridFeatures.Feature({ + title: '', + doc: '', + + tag: 'ui-ribbons-edit-render', + depends: [ + 'edit', + ], + + actions: RibbonsEditActions, + + handlers: [], +}) + + /********************************************************************** * vim:set ts=4 sw=4 : */ return module }) diff --git a/ui (gen4)/features/ui-single-image.js b/ui (gen4)/features/ui-single-image.js index 2c9bdbe2..3b5e5057 100755 --- a/ui (gen4)/features/ui-single-image.js +++ b/ui (gen4)/features/ui-single-image.js @@ -127,7 +127,7 @@ var SingleImageActions = actions.Actions({ return } - var viewer = this.ribbons.viewer + var viewer = this.dom var ribbon = this.ribbons.getRibbon() var images = viewer.find('.ribbon .image') @@ -239,7 +239,7 @@ var SingleImageActions = actions.Actions({ toggleSingleImage: ['Interface/Single image view', toggler.CSSClassToggler( - function(){ return this.ribbons.viewer }, + function(){ return this.dom }, 'single-image-mode', function(state){ if(state == 'on'){ @@ -344,7 +344,7 @@ module.SingleImageView = core.ImageGridFeatures.Feature({ function(){ // prevent this from doing anything while no viewer... if(!this.ribbons - || !this.ribbons.viewer + || !this.dom || this.ribbons.getRibbonSet().length == 0){ return } @@ -510,7 +510,7 @@ module.SingleImageViewLocalStorage = core.ImageGridFeatures.Feature({ return function(){ // prevent this from doing anything while no viewer... if(!this.ribbons - || !this.ribbons.viewer + || !this.dom || this.ribbons.getRibbonSet().length == 0){ return } diff --git a/ui (gen4)/features/ui-slideshow.js b/ui (gen4)/features/ui-slideshow.js index 3215988d..2e6724c4 100755 --- a/ui (gen4)/features/ui-slideshow.js +++ b/ui (gen4)/features/ui-slideshow.js @@ -145,7 +145,7 @@ var SlideshowActions = actions.Actions({ toggleSlideshow: ['Slideshow/Slideshow quick toggle', toggler.CSSClassToggler( - function(){ return this.ribbons.viewer }, + function(){ return this.dom }, 'slideshow-running', function(state){ // start... diff --git a/ui (gen4)/features/ui-status.js b/ui (gen4)/features/ui-status.js index 5730340a..cca26160 100755 --- a/ui (gen4)/features/ui-status.js +++ b/ui (gen4)/features/ui-status.js @@ -33,7 +33,7 @@ var makeStateIndicatorItem = function(container, type, text){ var item = $('
') .addClass('item '+ type || '') .attr('text', text) - this.ribbons.viewer.find('.state-indicator-container.'+container) + this.dom.find('.state-indicator-container.'+container) .append(item) return item } @@ -461,11 +461,11 @@ var StatusBarActions = actions.Actions({ // XXX change class... function(){ // no viewer yet... - if(!this.ribbons || !this.ribbons.viewer){ + if(!this.ribbons || !this.dom){ return $() } - var bar = this.ribbons.viewer.find('.state-indicator-container.global-info') + var bar = this.dom.find('.state-indicator-container.global-info') if(bar.length == 0){ bar = makeStateIndicator('global-info overlay-info statusbar') .addClass(this.config['status-bar-mode'] @@ -484,7 +484,7 @@ var StatusBarActions = actions.Actions({ .on('mouseout', function(){ bar.find('.info').empty() }) - .appendTo(this.ribbons.viewer) + .appendTo(this.dom) } return bar }, @@ -624,7 +624,7 @@ var StatusBarActions = actions.Actions({ this.toggleStatusBar('?') == 'none' && this.toggleStatusBar() // XXX do this better... - this.ribbons.viewer.find('.global-info .index .position').focus().click() + this.dom.find('.global-info .index .position').focus().click() } }], editStatusBarRibbon: ['- Interface/Edit ribbon focus position in statusbar', @@ -632,11 +632,11 @@ var StatusBarActions = actions.Actions({ this.toggleStatusBar('?') == 'none' && this.toggleStatusBar() // XXX do this better... - this.ribbons.viewer.find('.global-info .ribbon-number').focus().click() + this.dom.find('.global-info .ribbon-number').focus().click() }], toggleStatusBarIndexMode: ['Interface/Status bar index mode', toggler.CSSClassToggler( - function(){ return this.ribbons.viewer.find('.global-info .index') }, + function(){ return this.dom.find('.global-info .index') }, ['normal', 'global'], function(state){ this.toggleStatusBar('?') == 'none' && this.toggleStatusBar() @@ -652,7 +652,7 @@ var StatusBarActions = actions.Actions({ // XXX revise... showStatusBarInfo: ['- Interface/', function(text){ - var bar = this.ribbons.viewer.find('.state-indicator-container.global-info') + var bar = this.dom.find('.state-indicator-container.global-info') if(text){ bar.find('.info').text(text) diff --git a/ui (gen4)/features/ui-virtual-dom.js b/ui (gen4)/features/ui-virtual-dom.js index f89d50f0..54b7d60d 100755 --- a/ui (gen4)/features/ui-virtual-dom.js +++ b/ui (gen4)/features/ui-virtual-dom.js @@ -9,6 +9,7 @@ var vdom = require('ext-lib/virtual-dom') +var object = require('lib/object') var actions = require('lib/actions') var features = require('lib/features') @@ -16,9 +17,453 @@ var core = require('features/core') +/*********************************************************************/ + +/*/ XXX +var ViewerClassPrototype = { +} + + +var ViewerPrototype = { + sync: function(){ + }, +} + + +var Viewer = +module.Viewer = +object.makeConstructor('Viewer', + ViewerClassPrototype, + ViewerPrototype) +//*/ + + +//--------------------------------------------------------------------- +// +// - take care of DOM construction and update... +// - alignment is done via .centerRibbon(..) / .centerImage(..) +// - preview updates (XXX) +// - update onload (a-la .ribbons._loadImagePreviewURL(..)) + +var VirtualDOMRibbonsClassPrototype = { + // XXX ??? +} + +var VirtualDOMRibbonsPrototype = { + // XXX this is a circular ref -- I do not like it... + imagegrid: null, + + dom: null, + vdom: null, + + // Format: + // { + // count: , + // + // scale: , + // + // top: , + // + // ribbons: { + // : , + // ... + // }, + // } + state: null, + + + // XXX reuse these from ribbons??? + preventTransitions: function(){ + }, + restoreTransitions: function(){ + }, + getElemGID: function(){ + }, + getImage: function(){ + }, + getVisibleImageSize: function(){ + }, + focusImage: function(){ + }, + px2vmin: function(){ + }, + + + // constructors... + makeView: function(state, initial){ + state = state || {} + var that = this + var ig = this.imagegrid + + var target = state.target || ig.current + + this.state = this.state || {} + var count = state.count = state.count + || ig.screenwidth * (ig.config['ribbon-size-screens'] || 9) + var s = state.scale = state.scale + || ig.scale + + var data = ig.data + var images = ig.images + + var ribbons = data.ribbon_order + .map(function(gid){ + return that.makeRibbon(gid, target, count, state, initial) }) + + return vdom.h('div.ribbon-set', + { + //key: 'ribbon-set', + style: { + transform: 'scale('+ s +', '+ s +')', + } + }, [ + // current image indicator... + vdom.h('div.current-marker'), + + // ribbon locator... + vdom.h('div.ribbon-locator', + { + //key: 'ribbon-locator', + }, + ribbons), + ]) + }, + // XXX setup handlers (???) + // XXX STUB: make aligning more extensible... (???) + makeRibbon: function(gid, target, count, state, initial){ + state = state || {} + var that = this + var ig = this.imagegrid + var current = ig.current + target = target || state.target || current + var size = this.state.tile_size = state.tile_size + || this.state.tile_size + || this.getVisibleImageSize('max') + var scale = state.scale = state.scale + || ig.scale + var data = ig.data + var images = ig.images + // XXX + var ribbons = ig.ribbons + var base = data.base == gid ? '.base' : '' + var imgs = [] + + this.state = this.state || {} + //this.state.ribbons = this.state.ribbons || {} + + // XXX + var size = this.state.tile_size = + this.state.tile_size + || this.getVisibleImageSize('max') + + // calculate offset... + // XXX this accounts for only one offset mode... + // ...make this extensible... + // XXX + var vsize = this.px2vmin(size / scale) + var ref = data.getImage(target, 'before', gid) + var offset = ref == target ? vsize / 2 + : ref != null ? vsize + : 0 + ref = ref || data.getImage(target, 'after', gid) + + // build the images... + //var gids = data.getImages(gid, count, 'total') + var gids = data.getImages(ref, count, 'total') + gids + .forEach(function(gid){ + // image... + imgs.push(that.makeImage(gid, size)) + + // marks... + that.makeImageMarks(gid) + .forEach(function(mark){ imgs.push(mark) }) + }) + + // XXX not sure about this... + var style = initial ? { transform: 'translate3d(120vw, 0, 0)' } : {} + + return vdom.h('div.ribbon'+base, { + //key: 'ribbon-'+gid, + + // XXX events, hammer, ...??? + + attributes: { + gid: JSON.stringify(gid) + .replace(/^"(.*)"$/g, '$1'), + }, + + style: style, + }, + imgs) + }, + // XXX setup image handlers... + // XXX update image previews... + // XXX update image proportions for rotated images... (???) + makeImage: function(gid, size){ + var ig = this.imagegrid + //size = this.state.tile_size = size + size = size + || this.state.tile_size + || this.getVisibleImageSize('max') + var data = this.imagegrid.data + var images = this.imagegrid.images || {} + var current = data.current == gid ? '.current' : '' + + // resolve group preview cover... + var image = images[gid] || {} + var seen = [] + while(image.type == 'group'){ + // error, recursive group... + if(seen.indexOf(image.id) >= 0){ + image = images.IMAGE_DATA + console.error('Recursive group:', gid) + break + } + seen.push(image.id) + + image = that.images[image.cover] + } + var url = ig.images.getBestPreview(gid, size, image, true).url + + return vdom.h('div.image'+current, { + // XXX BUG: + // - setting this makes the images some times not change previews... + // - removing this breaks .current class setting... + key: 'image-'+gid, + + attributes: { + gid: JSON.stringify(gid) + .replace(/^"(.*)"$/g, '$1'), + orientation: image.orientation, + flipped: image.flipped, + + // XXX preview size -- get this onload from image... + //'preview-width': .., + //'preview-height': .., + }, + style: { + // XXX need to update this onload if changing preview + // of same image... + backgroundImage: 'url("'+ url +'")', + } + }) + }, + // XXX STUB: make marks handling extensible... (???) + makeImageMarks: function(gid){ + var that = this + var marks = [] + var tags = this.imagegrid.data.getTags(gid) + + // XXX STUB: make this extensible... + tags.indexOf('bookmark') >= 0 + && marks.push('bookmark') + tags.indexOf('selected') >= 0 + && marks.push('selected') + + return marks + .map(function(type){ + return vdom.h('div.mark.'+(type || ''), { + key: 'mark-'+type+'-'+gid, + attributes: { + gid: JSON.stringify(gid) + .replace(/^"(.*)"$/g, '$1'), + }, + }) + }) + }, + + // XXX add ability to hook in things like current image marker... + + + // XXX these need .getImage(..) / .getRibbon(..) / .getRibbonLocator(..) + centerRibbon: function(target){ + var ribbon = this.getRibbon(target) + var locator = this.getRibbonLocator() + + if(locator.length != 0 && ribbon.length != 0){ + var t = ribbon[0].offsetTop + var h = ribbon[0].offsetHeight + + locator.transform({ x: 0, y: this.px2vh(-(t + h/2)) + 'vh', z: 0 }) + } + return this + }, + centerImage: function(target, mode){ + target = this.getImage(target) + var ribbon = this.getRibbon(target) + + if(ribbon.length != 0){ + var l = target[0].offsetLeft + var w = target[0].offsetWidth + + var image_offset = mode == 'before' ? 0 + : mode == 'after' ? w + : w/2 + + ribbon.transform({x: -this.px2vmin(l + image_offset) + 'vmin', y: 0, z: 0}) + } + return this + }, + + scale: function(scale){ + if(scale){ + this.state.scale = scale + this.sync() + + } else { + return this.imagegrid.scale + } + }, + + // XXX not sure how to proceed with these... + setImageHandler: function(evt, handler){ + }, + setRibbonHandler: function(evt, handler){ + }, + + + + clear: function(){ + this.dom + && this.dom.remove() + + delete this.state + delete this.dom + delete this.vdom + + return this + }, + + // NOTE: virtual-dom architecture is designed around a fast-render-on-demand + // concept, so we build the state on demand... + // XXX get scale from config on initial load... + sync: function(target, size){ + var dom = this.dom + + var state = this.state ? Object.create(this.state) : {} + target && (state.target = target) + size && (state.count = size) + + // build initial state... + if(this.vdom == null){ + var n = this.vdom = this.makeView(state, true) + var v = vdom.create(n) + this.imagegrid.dom.append(v) + this.dom = v + + // patch state... + } else { + var n = this.makeView(state) + var diff = vdom.diff(this.vdom, n) + vdom.patch(dom, diff) + this.vdom = n + } + + return this + }, + // XXX should this do a full or partial .clear()??? + // XXX BUG: current image indicator resets but does not get shown... + reset: function(){ + delete this.dom + delete this.vdom + if(this.state){ + delete this.state.tile_size + } + + return this + .sync() + }, + + __init__: function(imagegrid){ + this.imagegrid = imagegrid + }, +} + +var VirtualDOMRibbons = +module.VirtualDOMRibbons = +object.makeConstructor('VirtualDOMRibbons', + VirtualDOMRibbonsClassPrototype, + VirtualDOMRibbonsPrototype) + + + + + /*********************************************************************/ var VirtualDomActions = actions.Actions({ + + get dom(){ + return this.__dom }, + set dom(value){ + this.__dom = value}, + get virtualdom(){ + return (this.__virtual_dom = this.__virtual_dom || VirtualDOMRibbons(this)) }, + + + load: [ + function(data){ + return function(){ + // recycle the viewer if one is not given specifically... + var viewer = data.viewer + viewer = viewer == null ? this.dom : viewer + + if(this.dom == null){ + this.dom = viewer + + } else { + this.virtualdom.clear() + } + + this.reload() + } + }], + reload: ['Interface/Reload viewer', + function(){ + this.virtualdom.reset() + this.focusImage() + }], + // XXX this ignores it's args... + refresh: ['Interface/Refresh images without reloading', + function(gids, scale){ + this.virtualdom.sync() + this.focusImage() + }], + + + // XXX + updateRibbon: ['- Interface/Update partial ribbon size', + function(target, w, size, threshold){ + target = target instanceof jQuery + ? this.virtualdom.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 + + // XXX DEBUG + //size = 5 + + // XXX for some reason this does not set the .current class + // on the right image... + this.virtualdom.sync(target, size) + + // XXX HACK: this fixes a bug in virtual-dom where .current + // is not synced correctly... + // ...one theory I have is that we change the class + // manually, dom gets diffed and no change is detected + // then the object gets recycled and the .current class + // ends up on a different element... + this.virtualdom.focusImage(target) + + this.centerViewer(target) + }], }) var VirtualDom = @@ -34,7 +479,55 @@ module.VirtualDom = core.ImageGridFeatures.Feature({ actions: VirtualDomActions, - handlers: [], + handlers: [ + ['clear', + function(){ this.virtualdom.clear() }], + ['fitImage toggleSingleImage', + function(){ delete this.virtualdom.state.tile_size }], + + // XXX account for fast navigation... + ['focusImage.pre', + function(target){ + var img = this.virtualdom.getImage(target) + + // in-place update... + if(img.length > 0){ + // XXX need to account for running out of images and + // not only on the current ribbon... + if(!this.__partial_ribbon_update){ + this.__partial_ribbon_update = setTimeout((function(){ + delete this.__partial_ribbon_update + this.virtualdom.preventTransitions() + + this + .updateRibbon(this.current) + // NOTE: we are doing this manually because we + // are running after the handler is done + // thus missing the base call... + .alignRibbons(null, null, true) + + this.virtualdom.restoreTransitions() + }).bind(this), 150) + } + + // long-jump... + } else { + if(this.__partial_ribbon_update){ + clearTimeout(this.__partial_ribbon_update) + delete this.__partial_ribbon_update + } + + this.updateRibbon(target) + } + }], + + // marks... + [[ + 'toggleMark', + 'toggleBookmark', + //], function(){ this.updateRibbon() }], + ], function(){ this.virtualdom.sync() }], + ], }) diff --git a/ui (gen4)/features/ui-widgets.js b/ui (gen4)/features/ui-widgets.js index 1ac6eb55..3af79913 100755 --- a/ui (gen4)/features/ui-widgets.js +++ b/ui (gen4)/features/ui-widgets.js @@ -45,7 +45,7 @@ function(context, cls, data){ cls = cls instanceof Array ? cls : cls.split(/\s+/g) // remove old versions... - context.ribbons.viewer.find('.'+ cls.join('.')).remove() + context.dom.find('.'+ cls.join('.')).remove() // make container... var controls = $('
') @@ -103,7 +103,7 @@ function(context, cls, data){ }) controls - .appendTo(context.ribbons.viewer) + .appendTo(context.dom) } // XXX write docs: @@ -121,7 +121,7 @@ function(cls, cfg, parent){ return toggler.Toggler(null, function(){ - parent = parent == null ? this.ribbons.viewer + parent = parent == null ? this.dom : parent instanceof Function ? parent.call(this) : parent return parent.find('.'+ cls.join('.')).length > 0 ? 'on' : 'off' @@ -135,7 +135,7 @@ function(cls, cfg, parent){ && makeButtonControls(this, cls, config) } else { - this.ribbons.viewer.find('.'+ cls.join('.')).remove() + this.dom.find('.'+ cls.join('.')).remove() } }) } @@ -341,7 +341,7 @@ module.makeUIContainer = function(make){ // is in a state where the window // is in focus but keys are not // tracked... - : that.ribbons.viewer.focus() + : that.dom.focus() }) // Compensate for click focusing the parent dialog when // a child is created... @@ -427,7 +427,7 @@ var makeDrawer = function(direction){ var that = this options = options || {} var parent = options.parentElement - parent = parent ? $(parent) : this.ribbons.viewer + parent = parent ? $(parent) : this.dom options.direction = direction || 'bottom' @@ -513,7 +513,7 @@ var DialogsActions = actions.Actions({ // element // null get modal(){ - var modal = this.ribbons.viewer + var modal = this.dom .find('.modal-widget') .last() return modal.data('widget-controller') @@ -540,7 +540,7 @@ var DialogsActions = actions.Actions({ Overlay: ['- Interface/', makeUIContainer(function(dialog, options){ var that = this - return overlay.Overlay(this.ribbons.viewer, dialog, options) + return overlay.Overlay(this.dom, dialog, options) // focus top modal on exit... .on('close', function(){ var o = that.modal @@ -575,7 +575,7 @@ var DialogsActions = actions.Actions({ client: dialog, dom: $('
') .append(dialog.dom || dialog) - .appendTo(this.ribbons.viewer) + .appendTo(this.dom) .draggable(), close: function(func){ if(func){ @@ -762,7 +762,7 @@ var DialogsActions = actions.Actions({ toggleOverlayBlur: ['Interface/Dialog overlay blur', toggler.CSSClassToggler( - function(){ return this.ribbons.viewer }, + function(){ return this.dom }, 'overlay-blur-enabled', function(state){ this.config['ui-overlay-blur'] = state }) ], }) @@ -1406,7 +1406,7 @@ module.ContextActionMenu = core.ImageGridFeatures.Feature({ ['load', function(){ var that = this - var viewer = this.ribbons.viewer + var viewer = this.dom !viewer.data('context-menu') && viewer @@ -1528,7 +1528,7 @@ module.Buttons = core.ImageGridFeatures.Feature({ $('.main-buttons.buttons .crop.button sub') .text(this.crop_stack ? this.crop_stack.length : '') }], // update zoom button status... - ['setScale', + ['viewScale', function(){ $('.secondary-buttons.buttons .zoom.button sub') .text(Math.round(this.screenwidth)) }], @@ -1897,7 +1897,7 @@ var WidgetTestActions = actions.Actions({ make('c/') } - var o = overlay.Overlay(this.ribbons.viewer, + var o = overlay.Overlay(this.dom, browse.makePathList(null, { 'a/*': list, 'b/*': list, @@ -1914,8 +1914,8 @@ var WidgetTestActions = actions.Actions({ // XXX migrate to the dialog framework... - // XXX use this.ribbons.viewer as base... - // XXX BUG: when using this.ribbons.viewer as base some actions leak + // XXX use this.dom as base... + // XXX BUG: when using this.dom as base some actions leak // between the two viewers... showTaggedInDrawer: ['- Test/Show tagged in drawer', function(tag){ @@ -1928,7 +1928,7 @@ var WidgetTestActions = actions.Actions({ height: H, background: 'black', }) - // XXX use this.ribbons.viewer as base... + // XXX use this.dom as base... // XXX when using viewer zoom and other stuff get leaked... var widget = drawer.Drawer($('body'), $('
') diff --git a/ui (gen4)/features/ui.js b/ui (gen4)/features/ui.js index fa4e71e7..ede1c8f5 100755 --- a/ui (gen4)/features/ui.js +++ b/ui (gen4)/features/ui.js @@ -22,7 +22,7 @@ * Experimental Features: * - ui-ribbons-placement * manage different low level ribbon placement mechanics -* XXX experimental... +* XXX EXPERIMENTAL... * - auto-single-image * - auto-ribbon * @@ -234,21 +234,11 @@ module.ViewerActions = actions.Actions({ 'ribbon-align-delay': 50, }, - // 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 - }, + // Viewer dom... + dom: null, // Current image data... // - // XXX experimental... get image(){ return this.images && this.images[this.current] }, set image(val){ @@ -259,28 +249,21 @@ module.ViewerActions = actions.Actions({ // Scaling... // - // Normal scale... + // NOTE: .screenwidth / .screenheight are measured in square image blocks... get scale(){ - return this.ribbons != null ? this.ribbons.scale() : null }, + return this.viewScale() }, set scale(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... + this.viewScale(s) }, get screenwidth(){ - return this.ribbons != null ? this.ribbons.getScreenWidthImages() : null }, + return this.fitImage('?') }, set screenwidth(n){ this.fitImage(n, false) }, - - // Screen height in image blocks... get screenheight(){ - return this.ribbons != null ? this.ribbons.getScreenHeightRibbons() : null }, + return this.fitRibbon('?') }, set screenheight(n){ this.fitRibbon(n, false) }, - // Screen 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. // @@ -289,10 +272,10 @@ module.ViewerActions = actions.Actions({ // min(image.width, image.height) // get screenfit(){ - if(!this.ribbons || !this.ribbons.viewer){ + if(!this.ribbons || !this.dom){ return null } - var viewer = this.ribbons.viewer + var viewer = this.dom var W = viewer.width() var H = viewer.height() @@ -301,7 +284,7 @@ module.ViewerActions = actions.Actions({ : this.screenheight }, set screenfit(n){ - var viewer = this.ribbons.viewer + var viewer = this.dom var W = viewer.width() var H = viewer.height() @@ -313,170 +296,12 @@ module.ViewerActions = actions.Actions({ } }, - load: [ - function(data){ - return function(){ - // recycle the viewer if one is not given specifically... - var viewer = data.viewer - viewer = viewer == null && this.ribbons != null - ? this.ribbons.viewer - : viewer - - if(this.ribbons == null){ - this.ribbons = ribbons.Ribbons(viewer, this.images) - // XXX is this correct??? - this.ribbons.__image_updaters = [this.updateImage.bind(this)] - - } else { - this.ribbons.clear() - this.ribbons.images = this.images - } - - this.reload() - } - }], - // NOTE: this will pass the .ribbons.updateData(..) a custom ribbon - // updater if one is defined here as .updateRibbon(target) action - // - // XXX HACK: two sins: - // - actions.updateRibbon(..) and ribbons.updateRibbon(..) - // are NOT signature compatible... - // - we depend on the internals of a custom add-on feature - reload: ['Interface/Reload viewer', - function(force){ - // full reload... - if(force == 'full'){ - //this.stop() - /* - killAllWorkers() - .done(function(){ - reload() - }) - */ - location.reload() - } - - this.ribbons.preventTransitions() - - // NOTE: this essentially sets the update threshold to 0... - // XXX this should be a custom arg... - force = force ? 0 : null - - return function(){ - // see if we've got a custom ribbon updater... - var that = this - var settings = this.updateRibbon != null - // XXX this should be: { updateRibbon: this.updateRibbon.bind(this) } - ? { updateRibbon: function(_, ribbon){ - return that.updateRibbon(ribbon, null, null, force) - } } - : null - - this.ribbons.updateData(this.data, settings) - - this - // XXX should this be here??? - .refresh() - .focusImage() - - // XXX HACK to make browser redraw images... - this.scale = this.scale - - this.ribbons.restoreTransitions() - } - }], - // NOTE: this will trigger .updateImage hooks... - refresh: ['Interface/Refresh images without reloading', - function(gids, scale){ - gids = gids || '*' - var size = scale != null ? - this.ribbons.getVisibleImageSize('min', scale) - : null - - this.ribbons.updateImage(gids, null, size) - }], - clear: [ - function(){ this.ribbons && this.ribbons.clear() }], - clone: [function(full){ - return function(res){ - if(this.ribbons){ - // NOTE: this is a bit wasteful as .ribbons will clone - // their ref to .images that we will throw away... - res.ribbons = this.ribbons.clone() - res.ribbons.images = res.images - } - } - }], - - - replaceGid: [ - function(from, to){ - return function(res){ - res && this.ribbons.replaceGid(from, to) - } - }], - - // This is called by .ribbons, the goal is to use it to hook into - // image updating from features and extensions... - // - // NOTE: not intended for calling manually, use .refresh(..) instead... - // - // XXX experimental... - // ...need this to get triggered by .ribbons - // 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 (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. - })], - - - // 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', toggler.CSSClassToggler( - function(){ return this.ribbons.viewer }, + function(){ return this.dom }, function(){ return this.config.themes }, function(state){ this.config.theme = state }) ], lighterTheme: ['Interface/Theme/Lighter theme', @@ -493,17 +318,65 @@ module.ViewerActions = actions.Actions({ }], toggleRibbonTheme: ['Interface/Theme/Ribbon theme', toggler.CSSClassToggler( - function(){ return this.ribbons.viewer }, + function(){ return this.dom }, function(){ return this.config['ribbon-themes'] }, function(state){ this.config['ribbon-theme'] = state }) ], toggleRibbonImageSepators: ['Interface/Theme/Ribbon image separators', toggler.CSSClassToggler( - function(){ return this.ribbons.viewer }, + function(){ return this.dom }, 'ribbon-image-separators', function(state){ this.config['ribbon-image-separators'] = state }) ], - // ribbon aligning... + // Navigation... + // + // NOTE: these prioritize whole images, i.e. each image will at least + // once be fully shown. + prevScreen: ['Navigate/Screen width back', + function(){ + // NOTE: the 0.2 is added to compensate for alignment/scaling + // errors -- 2.99 images wide counts as 3 while 2.5 as 2. + var w = Math.floor(this.screenwidth + 0.2) + w += (w % 2) - 1 + this.prevImage(w) + }], + nextScreen: ['Navigate/Screen width forward', + function(){ + var w = Math.floor(this.screenwidth + 0.2) + w += (w % 2) - 1 + this.nextImage(w) + }], + + + // Renderer API... + /*/ XXX do we need these here??? + centerImage: ['- Interface/Center an image in ribbon horizontally', + function(target, align, offset, scale){ + }], + centerRibbon: ['- Interface/Center a ribbon vertically', + function(target){ + }], + ribbonRotation: ['- Interface|Ribbon/', + function(a){ + }], + viewScale: ['- Zoom/', + function(scale){ + }], + fitImage: ['Zoom/Fit image', + function(count, overflow){ + }], + fitRibbon: ['Zoom/Fit ribbon vertically', + function(count, whole){ + }], + //*/ + + + // ribbon aligning and centering... + centerViewer: ['- Interface/Center the viewer', + function(target){ + this + .centerImage(target) + .centerRibbon(target) }], alignRibbons: ['Interface/Align ribbons', function(target, scale, now){ if(target == 'now'){ @@ -535,6 +408,7 @@ module.ViewerActions = actions.Actions({ // order... // XXX skip off-screen ribbons (???) // XXX should the timeout be configurable??? + // XXX remove dependency on .ribbons alignByOrder: ['Interface/Align ribbons by image order', function(target, scale, now){ if(target == 'now'){ @@ -649,34 +523,8 @@ module.ViewerActions = actions.Actions({ //}, 0) }], - // NOTE: this will align only a single image... - // XXX do we need these low level primitives here??? - centerImage: ['- Interface/Center an image in ribbon horizontally', - function(target, align, offset, scale){ - target = target instanceof jQuery - ? this.ribbons.getElemGID(target) - : target - - // align current ribbon... - this.ribbons.centerImage(target, align, offset, scale) - }], - centerRibbon: ['- Interface/Center a ribbon vertically', - function(target){ - target = target instanceof jQuery - ? this.ribbons.getElemGID(target) - : target - - // align current ribbon... - this.ribbons.centerRibbon(target) - }], - - centerViewer: ['- Interface/Center the viewer', - function(target){ - this - .centerImage(target) - .centerRibbon(target) - }], + // Viewer/window resize event... resizingWindow: ['- Interface/', core.doc`This is called by the window resize event handler... @@ -692,57 +540,13 @@ module.ViewerActions = actions.Actions({ // // Not for direct use. })], - - - focusImage: [ - function(target, list){ - return function(){ - this.ribbons.focusImage(this.data != null ? this.current : target) } }], - focusRibbon: [ - function(target, mode){ - mode = mode || this.config['ribbon-focus-mode'] - - var c = this.data.getRibbonOrder() - var i = this.data.getRibbonOrder(target) - // NOTE: we are not changing the direction here based on - // this.direction as swap will confuse the user... - var direction = c < i ? 'before' : 'after' - - if(mode == 'visual'){ - var ribbons = this.ribbons - var r = this.data.getRibbon(target) - var t = ribbons.getImageByPosition('current', r) - - if(t.length > 1){ - t = t.eq(direction == 'before' ? 0 : 1) - } - - t = ribbons.getElemGID(t) - - this.focusImage(t, r) - } - }], - - // NOTE: these prioritize whole images, i.e. each image will at least - // once be fully shown. - prevScreen: ['Navigate/Screen width back', - function(){ - // NOTE: the 0.2 is added to compensate for alignment/scaling - // errors -- 2.99 images wide counts as 3 while 2.5 as 2. - var w = Math.floor(this.screenwidth + 0.2) - w += (w % 2) - 1 - this.prevImage(w) - }], - nextScreen: ['Navigate/Screen width forward', - function(){ - var w = Math.floor(this.screenwidth + 0.2) - w += (w % 2) - 1 - this.nextImage(w) - }], - // XXX hide from user action list... (???) - // XXX need to check if a transition is running and delay timeout... + // Zoom/scale protocol... + // + // Events... + // NOTE: the implementation needs to call .resizingDone(..) when all + // animations are done... resizing: ['- Zoom/Scale root protocol action (not for direct use)', core.doc`Zooming/scaling root action... @@ -785,34 +589,7 @@ module.ViewerActions = actions.Actions({ // // This will never be used directly, but will wrap protocol user // functions. - // - // As an example see: .setScale(..) - - var that = this - // stop currently running transition... - this.ribbons.scale(this.ribbons.scale()) - - // transitionend handler... - if(!this.__resize_handler){ - this.__resize_handler = function(){ - that.__post_resize - && that.resizingDone() - delete that.__post_resize - } - } - this.ribbons.getRibbonSet() - .off('transitionend', this.__resize_handler) - .on('transitionend', this.__resize_handler) - - // timeout handler... - this.__post_resize && clearTimeout(this.__post_resize) - return function(){ - this.__post_resize = setTimeout( - this.__resize_handler, - this.config['resize-done-timeout'] || 300) - } })], - resizingDone: ['- Zoom/scale post-transition protocol action (not for direct use)', core.doc`Zooming/scaling post-transition action... @@ -835,56 +612,6 @@ module.ViewerActions = actions.Actions({ // be called before the transition is done. })], - // Zoom/scale protocol actions... - setScale: ['- Zoom/', - function(scale){ - this.resizing.chainCall(this, function(){ - this.ribbons && scale && this.ribbons.scale(scale) - // NOTE: we pass explicit scale here to compensate for animation... - this.refresh('*', scale) - }, 'scale', scale) - }], - fitOrig: ['Zoom/Fit to original scale', - function(){ - this.resizing.chainCall(this, function(){ - this.ribbons.scale(1) - // NOTE: we pass explicit scale here to compensate for animation... - this.refresh('*', 1) - }, 'scale', 1) - }], - // 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) - // NOTE: we pass explicit scale here to compensate for animation... - this.refresh('*', this.ribbons.getScreenWidthImages(1) / count) - }, 'screenwidth', count, overflow) - }], - // NOTE: this does not account for ribbon spacing... - fitRibbon: ['Zoom/Fit ribbon vertically', - function(count, whole){ - this.resizing.chainCall(this, function(){ - this.ribbons.fitRibbon(count, whole) - // NOTE: we pass explicit scale here to compensate for animation... - this.refresh('*', this.ribbons.getScreenHeightRibbons(1, whole) / count) - }, 'screenheight', count, whole) - }], - - - // 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', @@ -917,24 +644,16 @@ module.ViewerActions = actions.Actions({ }], // Scale presets... - // + fitOrig: ['Zoom/Fit to original scale', + function(){ this.viewScale(1) }], fitMax: ['Zoom/Fit the maximum number of images', - function(){ this.fitImage(this.config['max-screen-images']) }], + function(){ this.screenwidth = this.config['max-screen-images'] }], fitScreen: ['Zoom/Fit image to screen', function(){ this.screenfit = 1 }], - // ribbon rotation... + + // Ribbon rotation... // - ribbonRotation: ['- Interface|Ribbon/', - function(a){ - if(arguments.length > 0){ - this.ribbons.rotate(a) - - } else { - return this.ribbons.rotate() || 0 - } - }], - // Rotate ribbon CW/CCW... // // Rotate ribbon (default step) @@ -957,7 +676,7 @@ module.ViewerActions = actions.Actions({ function(){ this.ribbonRotation(0) }], - // XXX experimental: not sure if this is the right way to go... + // XXX EXPERIMENTAL: not sure if this is the right way to go... // XXX make this play nice with crops... // ...should this be a crop??? toggleRibbonList: ['Interface|Ribbon/Ribbons as images view', @@ -992,6 +711,7 @@ module.Viewer = core.ImageGridFeatures.Feature({ 'base', 'workspace', 'introspection', + 'ui-render', ], suggested: [ // XXX is this the right way??? @@ -1196,7 +916,7 @@ core.ImageGridFeatures.Feature({ // manage the .crop-mode css class... ['crop uncrop', function(){ - this.ribbons.viewer[this.cropped ? + this.dom[this.cropped ? 'addClass' : 'removeClass']('crop-mode') }], @@ -1546,11 +1266,11 @@ module.Cursor = core.ImageGridFeatures.Feature({ actions: actions.Actions({ toggleHiddenCursor: ['Interface/Cursor hidden', toggler.CSSClassToggler( - function(){ return this.ribbons.viewer }, + function(){ return this.dom }, 'cursor-hidden', function(state){ var that = this - var viewer = this.ribbons.viewer + var viewer = this.dom if(state == 'on'){ var x, y @@ -1619,12 +1339,12 @@ module.Cursor = core.ImageGridFeatures.Feature({ // toggleAutoHideCursor: ['Interface/Cursor auto-hide', toggler.CSSClassToggler( - function(){ return this.ribbons.viewer }, + function(){ return this.dom }, 'cursor-autohide', function(state){ var that = this - var viewer = this.ribbons.viewer + var viewer = this.dom // NOTE: this is handled by the keyboard feature... var kb_target = this.__keyboard_event_source || $(window) @@ -1654,7 +1374,7 @@ module.Cursor = core.ImageGridFeatures.Feature({ : -1 if(timeout && timeout > 0){ m_timer = setTimeout(function(){ - var viewer = that.ribbons.viewer + var viewer = that.dom // auto-hide is off -- restore... if(!viewer.hasClass('cursor-autohide')){ @@ -1680,7 +1400,7 @@ module.Cursor = core.ImageGridFeatures.Feature({ // avoid this from delaying the keyboard handler... kb_timer = setTimeout(function(){ kb_timer = null - var viewer = that.ribbons.viewer + var viewer = that.dom // get key... var key = keyboard.normalizeKey( @@ -1842,7 +1562,7 @@ var ControlActions = actions.Actions({ // nothing to do... null : (this.ribbons - && this.ribbons.viewer + && this.dom //&& this.ribbons.getRibbon().data('hammer') ? 'handling-click' : 'none' }, && this.ribbons.getRibbon().hasClass('clickable')) ? 'handling-click' @@ -1978,7 +1698,7 @@ var ControlActions = actions.Actions({ var cl = central && central.offset().left var w = central && central.outerWidth(true) - var W = this.ribbons.viewer.width() + var W = this.dom.width() var vmin = Math.min( document.body.offsetWidth, document.body.offsetHeight) @@ -2044,7 +1764,7 @@ var ControlActions = actions.Actions({ // nothing to do... null : (this.ribbons - && this.ribbons.viewer + && this.dom && this.ribbons.getRibbon().hasClass('draggable')) ? 'handling-pan' : 'none' }, @@ -2281,8 +2001,8 @@ var ControlActions = actions.Actions({ // nothing to do... null : (this.ribbons - && this.ribbons.viewer - && this.ribbons.viewer.hasClass('mouse-wheel-scroll')) ? + && this.dom + && this.dom.hasClass('mouse-wheel-scroll')) ? 'handling-mouse-wheel' : 'none' }, 'handling-mouse-wheel', @@ -2318,7 +2038,7 @@ var ControlActions = actions.Actions({ var rgid = this.ribbons.getElemGID(r) // XXX vertical scroll... - this.ribbons.viewer + this.dom .on('wheel', function(){ }) @@ -2357,7 +2077,7 @@ var ControlActions = actions.Actions({ // on... if(state == 'on'){ - this.ribbons.viewer.addClass('mouse-wheel-scroll') + this.dom.addClass('mouse-wheel-scroll') // NOTE: we are resetting this to avoid multiple setting // handlers... this.off('updateRibbon', setup) @@ -2368,7 +2088,7 @@ var ControlActions = actions.Actions({ // off... } else { - this.ribbons.viewer.removeClass('mouse-wheel-scroll') + this.dom.removeClass('mouse-wheel-scroll') this.off('updateRibbon', setup) this.data.ribbon_order.forEach(function(gid){ @@ -2390,13 +2110,13 @@ var ControlActions = actions.Actions({ return state ? null : (this.ribbons - && this.ribbons.viewer - && this.ribbons.viewer.data('hammer')) ? + && this.dom + && this.dom.data('hammer')) ? 'handling-swipes' : 'none' }, 'handling-swipes', function(state){ - var viewer = this.ribbons.viewer + var viewer = this.dom // on... if(state == 'on'){ @@ -2520,7 +2240,7 @@ module.Control = core.ImageGridFeatures.Feature({ }], // if panned image is off screen, center it... - ['setScale', + ['viewScale', function(){ var that = this Object.keys(this.data.ribbons).forEach(function(r){ @@ -2617,7 +2337,7 @@ module.PreviewFilters = core.ImageGridFeatures.Feature({ var cls = filters[state] var classes = Object.values(filters) .filter(function(c){ return c != cls }) - this.ribbons.viewer + this.dom .find('.'+ classes.join(', .')) .removeClass(classes.join(' ')) @@ -2641,7 +2361,7 @@ module.PreviewFilters = core.ImageGridFeatures.Feature({ /*********************************************************************/ -// XXX experimental... +// XXX EXPERIMENTAL... // ...not sure if this is the right way to go... // XXX need to get the minimal size and not the width as results will diff --git a/ui (gen4)/ui.js b/ui (gen4)/ui.js index 5e7d8f61..b4d7d8c9 100755 --- a/ui (gen4)/ui.js +++ b/ui (gen4)/ui.js @@ -102,22 +102,27 @@ $(function(){ m = Object.keys(m).filter(function(e){ return m[e] != null }) console.log('Modules (%d):', m.length, m) + try { + // setup actions... + window.ig = + window.ImageGrid = + viewer.ImageGridFeatures + .setup([ + 'viewer-testing', - // setup actions... - window.ig = - window.ImageGrid = - viewer.ImageGridFeatures - .setup([ - 'viewer-testing', + 'demo', - 'demo', + // XXX this is not for production... + 'experiments', - // XXX this is not for production... - 'experiments', + //'-commandline', + //'-ui-partial-ribbons', + ]) - //'-commandline', - //'-ui-partial-ribbons', - ]) + } catch(err){ + console.error(err) + return + } // used to switch experimental actions on (set to true) or off (unset or false)... @@ -130,6 +135,9 @@ $(function(){ && console.warn('Features excluded (%d):', ig.features.excluded.length, ig.features.excluded) + Object.keys(ig.features.missing).length > 0 + && console.warn('Features disabled (%d):', + ig.features.missing) ig.features.disabled.length > 0 && console.log('Features disabled (%d):', ig.features.disabled.length,