diff --git a/ui (gen4)/features/all.js b/ui (gen4)/features/all.js index e268bfd2..09700605 100755 --- a/ui (gen4)/features/all.js +++ b/ui (gen4)/features/all.js @@ -10,26 +10,29 @@ define(function(require){ var module = {} // import features... var core = require('features/core') -var base = require('features/base') -var location = require('features/location') -var history = require('features/history') -var app = require('features/app') -var ui = require('features/ui') -var keyboard = require('features/keyboard') -var status = require('features/ui-status') -var marks = require('features/ui-marks') -var widgets = require('features/ui-widgets') -var slideshow = require('features/ui-slideshow') -var exteditor = require('features/external-editor') -var metadata = require('features/metadata') -var meta = require('features/meta') -var experimental = require('features/experimental') -var demo = require('features/demo') +require('features/base') +require('features/location') +require('features/history') +require('features/app') +require('features/ui') +require('features/ui-single-image') +require('features/ui-chrome') +require('features/keyboard') +require('features/ui-status') +require('features/ui-marks') +require('features/ui-widgets') +require('features/ui-slideshow') +require('features/external-editor') +require('features/metadata') +require('features/meta') + +require('features/experimental') +require('features/demo') if(window.nodejs != null){ - var filesystem = require('features/filesystem') - var cli = require('features/cli') + require('features/filesystem') + require('features/cli') } diff --git a/ui (gen4)/features/base.js b/ui (gen4)/features/base.js index 005415f1..91efc549 100755 --- a/ui (gen4)/features/base.js +++ b/ui (gen4)/features/base.js @@ -928,6 +928,269 @@ core.ImageGridFeatures.Feature('base-full', [ +//--------------------------------------------------------------------- +// Journal... + +function logImageShift(action){ + return [action.slice(-4) != '.pre' ? + action + '.pre' + : action, + function(target){ + target = this.data.getImage(target) + var args = args2array(arguments) + + var o = this.data.getImageOrder(target) + var r = this.data.getRibbon(target) + var current = this.current + + return function(){ + var on = this.data.getImageOrder(target) + var rn = this.data.getRibbon(target) + + if(o == on || r == rn){ + /* + this.journalPush( + this.current, + action, + args, + { + before: [r, o], + after: [rn, on], + }) + */ + this.journalPush({ + type: 'shift', + current: current, + target: target, + action: action, + args: args, + undo: journalActions[action], + diff: { + before: [r, o], + after: [rn, on], + }, + }) + } + + } + }] +} + + +// Format: +// { +// : | | null, +// ... +// } +var journalActions = { + clear: null, + load: null, + + setBaseRibbon: null, + + // XXX need to account for position change, i.e. if action had no + // effect then do nothing... + // ...take target position before and after... + shiftImageTo: null, + + shiftImageUp: 'shiftImageDown', + shiftImageDown: 'shiftImageUp', + shiftImageLeft: 'shiftImageRight', + shiftImageRight: 'shiftImageLeft', + shiftRibbonUp: 'shiftRibbonDown', + shiftRibbonDown: 'shiftRibbonUp', + + rotateCW: 'rotateCCW', + rotateCCW: 'rotateCW', + flipHorizontal: 'flipHorizontal', + flipVertical: 'flipVertical', + + sortImages: null, + reverseImages: 'reverseImages', + reverseRibbons: 'reverseRibbons', + + crop: null, + uncrop: null, + + tag: null, + untag: null, + + group: null, + ungroup: null, + expandGroup: null, + collapseGroup: null, + + runJournal: null, +} + + +// XXX is this the right level for this??? +// ...data seems to be a better candidate... +// XXX would be great to add a mechanism define how to reverse actions... +// ...one way to do this at this point is to revert to last state +// and re-run the journal until the desired event... +// XXX need to define a clear journaling strategy in the lines of: +// - save state clears journal and adds a state load action +// - .load(..) clears journal +// XXX needs careful testing... +var Journal = +module.Journal = core.ImageGridFeatures.Feature({ + title: 'Action Journal', + + tag: 'system-journal', + + depends: ['base'], + + actions: actions.Actions({ + + journal: null, + rjournal: null, + + clone: [function(full){ + return function(res){ + res.rjournal = null + res.journal = null + if(full && this.hasOwnProperty('journal') && this.journal){ + res.journal = JSON.parse(JSON.stringify(this.journal)) + } + } + }], + + // XXX might be good to add some kind of metadata to journal... + journalPush: ['- Journal/Add an item to journal', + function(data){ + this.journal = (this.hasOwnProperty('journal') + || this.journal) ? + this.journal + : [] + this.journal.push(data) + }], + clearJournal: ['Journal/Clear the action journal', + function(){ + if(this.journal){ + // NOTE: overwriting here is better as it will keep + // shadowing the parent's .journal in case we + // are cloned. + // NOTE: either way this will have no effect as we + // only use the local .journal but the user may + // get confused... + //delete this.journal + this.journal = null + } + }], + runJournal: ['- Journal/Run journal', + function(journal){ + var that = this + journal.forEach(function(e){ + // load state... + that + .focusImage(e.current) + // run action... + [e.action].apply(that, e.args) + }) + }], + + // XXX need to clear the rjournal as soon as we do something... + // ...at this point it is really easy to mess things up by + // undoing something, and after some actions doing a + // .redoLast(..) + // XXX this is not ready for production... + undoLast: ['Journal/Undo last', + function(){ + var journal = this.journal + this.rjournal = (this.hasOwnProperty('rjournal') + || this.rjournal) ? + this.rjournal + : [] + + for(var i = journal.length-1; i >= 0; i--){ + var a = journal[i] + + // we undo only a very specific set of actions... + if(a.undo && a.type == 'shift' && a.args.length == 0){ + this + .focusImage(a.current) + [a.undo].call(this, a.target) + + // pop the undo command... + this.journal.pop() + this.rjournal.push(journal.splice(i, 1)[0]) + break + } + } + }], + _redoLast: ['Journal/Redo last', + function(){ + if(!this.rjournal || this.rjournal.length == 0){ + return + } + + this.runJournal([this.rjournal.pop()]) + }], + }), + + // log state, action and its args... + // XXX need to drop journal on save... + // XXX rotate/truncate journal??? + // XXX need to check that all the listed actions are clean -- i.e. + // running the journal will produce the same results as user + // actions that generated the journal. + // XXX would be good if we could know the name of the action in the + // handler, thus enabling us to define a single handler rather + // than generating a custom handler per action... + handlers: [ + logImageShift('shiftImageTo'), + logImageShift('shiftImageUp'), + logImageShift('shiftImageDown'), + logImageShift('shiftImageLeft'), + logImageShift('shiftImageRight'), + logImageShift('shiftRibbonUp'), + logImageShift('shiftRibbonDown'), + + ].concat([ + 'clear', + 'load', + + 'setBaseRibbon', + + 'rotateCW', + 'rotateCCW', + 'flipHorizontal', + 'flipVertical', + + 'sortImages', + 'reverseImages', + 'reverseRibbons', + + 'crop', + 'uncrop', + + 'tag', + 'untag', + + 'group', + 'ungroup', + 'expandGroup', + 'collapseGroup', + + //'runJournal', + ].map(function(action){ + return [ + action+'.pre', + function(){ + this.journalPush({ + type: 'basic', + current: this.current, + action: action, + args: args2array(arguments), + }) + }] + })), +}) + + + + /********************************************************************** * vim:set ts=4 sw=4 : */ return module }) diff --git a/ui (gen4)/features/external-editor.js b/ui (gen4)/features/external-editor.js index 082b9f72..6210357d 100755 --- a/ui (gen4)/features/external-editor.js +++ b/ui (gen4)/features/external-editor.js @@ -95,6 +95,9 @@ module.ExternalEditor = core.ImageGridFeatures.Feature({ depends: [ 'base', ], + suggested: [ + 'ui-external-editor', + ], isApplicable: function(){ return this.runtime == 'nw' || this.runtime == 'node' }, diff --git a/ui (gen4)/features/filesystem.js b/ui (gen4)/features/filesystem.js index 3f9d7a19..0b0592b0 100755 --- a/ui (gen4)/features/filesystem.js +++ b/ui (gen4)/features/filesystem.js @@ -320,6 +320,10 @@ module.FileSystemLoader = core.ImageGridFeatures.Feature({ depends: [ 'location', ], + suggested: [ + 'ui-fs-loader', + 'fs-url-history', + ], actions: FileSystemLoaderActions, @@ -505,6 +509,9 @@ module.FileSystemLoaderURLHistory = core.ImageGridFeatures.Feature({ 'fs-loader', 'url-history', ], + suggested: [ + 'ui-fs-url-history', + ], handlers: [ pushToHistory('loadImages'), @@ -841,7 +848,12 @@ module.FileSystemWriter = core.ImageGridFeatures.Feature({ tag: 'fs-writer', // NOTE: this is mostly because of the base path handling... - depends: ['fs-loader'], + depends: [ + 'fs-loader' + ], + suggested: [ + 'ui-fs-writer', + ], actions: FileSystemWriterActions, @@ -998,6 +1010,13 @@ module.FileSystemWriterUI = core.ImageGridFeatures.Feature({ +//--------------------------------------------------------------------- + +core.ImageGridFeatures.Feature('fs', [ + 'fs-loader', + 'fs-writer', +]) + /********************************************************************** * vim:set ts=4 sw=4 : */ diff --git a/ui (gen4)/features/history.js b/ui (gen4)/features/history.js index de7d6e9a..764ddf7d 100755 --- a/ui (gen4)/features/history.js +++ b/ui (gen4)/features/history.js @@ -182,6 +182,11 @@ module.URLHistory = core.ImageGridFeatures.Feature({ depends: [ 'location', ], + suggested: [ + 'ui-url-history', + 'url-history-local-storage', + 'url-history-fs-writer', + ], actions: URLHistoryActions, }) diff --git a/ui (gen4)/features/meta.js b/ui (gen4)/features/meta.js index 34f089c3..0cb302c1 100755 --- a/ui (gen4)/features/meta.js +++ b/ui (gen4)/features/meta.js @@ -38,11 +38,9 @@ core.ImageGridFeatures.Feature('viewer-minimal', [ 'image-marks', 'image-bookmarks', - 'fs-loader', - 'fs-writer', + 'fs', 'metadata', - 'fs-metadata', ]) @@ -62,7 +60,7 @@ core.ImageGridFeatures.Feature('viewer-testing', [ //'ui-ribbon-align-to-first', //'ui-ribbon-manual-align', - 'ui-single-image-view', + 'ui-single-image', 'ui-partial-ribbons', // XXX @@ -70,30 +68,16 @@ core.ImageGridFeatures.Feature('viewer-testing', [ //'ui-direct-control', //'ui-indirect-control', - 'image-marks', - 'image-bookmarks', - + 'marks', // local storage + url... 'config-local-storage', 'ui-url-hash', - 'url-history-local-storage', - 'url-history-fs-writer', + 'url-history', 'ui-single-image-view-local-storage', - // fs... - 'ui-fs-loader', - 'fs-url-history', - 'ui-fs-url-history', - 'ui-fs-writer', - - 'metadata', - 'fs-metadata', - 'ui-metadata', - 'external-editor', - 'ui-external-editor', // chrome... 'ui-status-log', @@ -118,8 +102,6 @@ core.ImageGridFeatures.Feature('viewer-testing', [ // ui control... 'ui-clickable', - //'ui-autohide-cursor', - 'ui-autohide-cursor-single-image-view', //'ui-direct-control-jquery', // XXX BUG: on touch down and first move this gets offset by a distance // not sure why... diff --git a/ui (gen4)/features/metadata.js b/ui (gen4)/features/metadata.js index d4c997f2..5e81ebda 100755 --- a/ui (gen4)/features/metadata.js +++ b/ui (gen4)/features/metadata.js @@ -75,6 +75,10 @@ module.Metadata = core.ImageGridFeatures.Feature({ depends: [ 'base', ], + suggested: [ + 'fs-metadata', + 'ui-metadata', + ], actions: MetadataActions, }) diff --git a/ui (gen4)/features/ui-chrome.js b/ui (gen4)/features/ui-chrome.js new file mode 100755 index 00000000..e2967a57 --- /dev/null +++ b/ui (gen4)/features/ui-chrome.js @@ -0,0 +1,619 @@ +/********************************************************************** +* +* +* +**********************************************************************/ + +define(function(require){ var module = {} + +//var DEBUG = DEBUG != null ? DEBUG : true + +var actions = require('lib/actions') +var features = require('lib/features') +var toggler = require('lib/toggler') + +var core = require('features/core') + + + +/*********************************************************************/ + +// helper... +function didAdvance(indicator){ + return function(){ + var img = this.data.current + return function(){ + if(img == this.data.current){ + this.flashIndicator(indicator) + } + } + } +} + +var BoundsIndicatorsActions = actions.Actions({ + flashIndicator: ['- Interface/Flash an indicator', + function(direction){ + if(this.ribbons.getRibbonSet().length == 0){ + return + } + var cls = { + // shift up/down... + up: '.up-indicator', + down: '.down-indicator', + // hit start/end/top/bottom of view... + start: '.start-indicator', + end: '.end-indicator', + top: '.top-indicator', + bottom: '.bottom-indicator', + }[direction] + + var indicator = this.ribbons.viewer.find(cls) + + if(indicator.length == 0){ + indicator = $('
') + .addClass(cls.replace('.', '') +' '+ this.tag) + .appendTo(this.ribbons.viewer) + } + + return indicator + // NOTE: this needs to be visible in all cases and key press + // rhythms... + .show() + .delay(100) + .fadeOut(300) + }], +}) + +var BoundsIndicators = +module.BoundsIndicators = core.ImageGridFeatures.Feature({ + title: '', + doc: '', + + tag: 'ui-bounds-indicators', + depends: ['ui'], + + actions: BoundsIndicatorsActions, + + handlers: [ + // basic navigation... + ['nextImage.pre lastImage.pre', didAdvance('end')], + ['prevImage.pre firstImage.pre', didAdvance('start')], + ['nextRibbon.pre lastRibbon.pre', didAdvance('bottom')], + ['prevRibbon.pre firstRibbon.pre', didAdvance('top')], + + // vertical shifting... + ['shiftImageUp.pre', + function(target){ + target = target || this.current + var r = this.data.getRibbonOrder(target) + + var l = this.data.getImages(r).length + var l0 = this.data.getImages(0).length + + return function(){ + // when shifting last image of top ribbon (i.e. length == 1) + // up the state essentially will not change... + if((r == 0 && l == 1) + // we are shifting to a new empty ribbon... + || (r == 1 && l == 1 && l0 == 0)){ + this.flashIndicator('top') + } else { + this.flashIndicator('up') + } + } + }], + ['shiftImageDown.pre', + function(target){ + target = target || this.current + var r0 = this.data.getRibbonOrder(target) + var l = this.data.getImages(r0).length + + return function(){ + var r1 = this.data.getRibbonOrder(target) + if(r0 == r1 && r0 == this.data.ribbon_order.length-1 && l == 1){ + this.flashIndicator('bottom') + } else { + this.flashIndicator('down') + } + } + }], + + // horizontal shifting... + ['shiftImageLeft.pre', + function(target){ + if(target == null + //&& actions.data.getImageOrder('ribbon') == 0){ + && this.data.getImage('prev') == null){ + this.flashIndicator('start') + } + }], + ['shiftImageRight.pre', + function(target){ + if(target == null + && this.data.getImage('next') == null){ + this.flashIndicator('end') + } + }], + ], +}) + + + +//--------------------------------------------------------------------- + +var CurrentImageIndicatorActions = actions.Actions({ + config: { + 'current-image-border': 3, + 'current-image-min-border': 2, + + 'current-image-border-timeout': 200, + 'current-image-shift-timeout': 200, + + 'current-image-indicator-fadein': 500, + + 'current-image-indicator-hide-timeout': 250, + + // this can be: + // 'hide' - simply hide on next/prev screen action + // and show on focus image. + // 'hide-show' - hide on fast scroll through screens and + // show when slowing down. + 'current-image-indicator-screen-nav-mode': 'hide', + }, + + updateCurrentImageIndicator: ['- Interface/Update current image indicator', + function(target, update_border){ + var ribbon_set = this.ribbons.getRibbonSet() + + if(ribbon_set.length == 0){ + return + } + + var scale = this.ribbons.scale() + var cur = this.ribbons.getImage(target) + // NOTE: cur may be unloaded... + var ribbon = this.ribbons.getRibbon(cur.length > 0 ? target : this.currentRibbon) + + var marker = ribbon.find('.current-marker') + + // remove marker if current image is not loaded... + if(cur.length == 0){ + marker.remove() + return + } + + // get config... + var border = this.config['current-image-border'] + var min_border = this.config['current-image-min-border'] + var border_timeout = this.config['current-image-border-timeout'] + var fadein = this.config['current-image-indicator-fadein'] + + // no marker found -- either in different ribbon or not created yet... + if(marker.length == 0){ + // get marker globally... + marker = this.ribbons.viewer.find('.current-marker') + + // no marker exists -- create a marker... + if(marker.length == 0){ + var marker = $('
') + .addClass('current-marker ui-current-image-indicator') + .css({ + opacity: '0', + // NOTE: these are not used for positioning + // but are needed for correct absolute + // placement... + top: '0px', + left: '0px', + }) + .appendTo(ribbon) + .animate({ + 'opacity': 1 + }, fadein) + this.ribbons.dom.setOffset(marker, 0, 0) + + // add marker to current ribbon... + } else { + marker.appendTo(ribbon) + } + } + + // NOTE: we will update only the attrs that need to be updated... + var css = {} + + var w = cur.outerWidth(true) + var h = cur.outerHeight(true) + + // keep size same as the image... + if(marker.outerWidth() != w || marker.outerHeight() != h){ + css.width = w + css.height = h + } + + // update border... + if(update_border !== false){ + var border = Math.max(min_border, border / scale) + + // set border right away... + if(update_border == 'before'){ + css.borderWidth = border + + // set border with a delay... + // NOTE: this is to prevent the ugly border resize before + // the scale on scale down animation starts... + } else { + setTimeout(function(){ + marker.css({ borderWidth: border }) + }, border_timeout) + } + } + + //css.left = cur[0].offsetLeft + this.ribbons.dom.setOffset(marker, cur[0].offsetLeft, 0) + + marker.css(css) + }], +}) + +var CurrentImageIndicator = +module.CurrentImageIndicator = core.ImageGridFeatures.Feature({ + title: '', + doc: '', + + tag: 'ui-current-image-indicator', + depends: ['ui'], + + actions: CurrentImageIndicatorActions, + + handlers: [ + // move marker to current image... + ['focusImage.post', + function(){ this.updateCurrentImageIndicator() }], + // prevent animations when focusing ribbons... + ['focusRibbon.pre', + function(){ + var m = this.ribbons.viewer.find('.current-marker') + this.ribbons.preventTransitions(m) + return function(){ + this.ribbons.restoreTransitions(m) + } + }], + // this is here to compensate for position change on ribbon + // resize... + // NOTE: hide/show of indicator on resize appears to have solved + // the jumpy animation issue. + // this might cause some blinking on slow resizes (visible + // only on next/prev screen)... + // ...still not sure why .preventTransitions(m) did not + // do the job. + ['resizeRibbon.pre', + function(target, s){ + var m = this.ribbons.viewer.find('.current-marker') + // only update if marker exists and we are in current ribbon... + if(m.length != 0 && this.currentRibbon == this.data.getRibbon(target)){ + //this.ribbons.preventTransitions(m) + m.hide() + + return function(){ + this.updateCurrentImageIndicator(target, false) + //this.ribbons.restoreTransitions(m, true) + m + .show() + // NOTE: keeping display in inline style will + // prevent the element from being hidden + // by css... + .css({display: ''}) + } + } + }], + // Change border size in the appropriate spot in the animation: + // - before animation when scaling up + // - after when scaling down + // This is done to make the visuals consistent... + ['fitImage.pre fitRibbon.pre', + function(w1){ + var w0 = this.screenwidth + w1 = w1 || 1 + return function(){ + this.updateCurrentImageIndicator(null, w0 > w1 ? 'before' : 'after') + } + }], + ['shiftImageLeft.pre shiftImageRight.pre', + function(){ + this.ribbons.viewer.find('.current-marker').hide() + if(this._current_image_indicator_timeout != null){ + clearTimeout(this._current_image_indicator_timeout) + delete this._current_image_indicator_timeout + } + return function(){ + var ribbons = this.ribbons + var fadein = this.config['current-image-indicator-fadein'] + this._current_image_indicator_timeout = setTimeout(function(){ + ribbons.viewer.find('.current-marker').fadeIn(fadein) + }, this.config['current-image-shift-timeout']) + } + }], + ], +}) + + +var CurrentImageIndicatorHideOnFastScreenNav = +module.CurrentImageIndicatorHideOnFastScreenNav = core.ImageGridFeatures.Feature({ + title: '', + doc: '', + + tag: 'ui-current-image-indicator-hide-on-fast-screen-nav', + + + depends: [ + 'ui', + 'ui-current-image-indicator' + ], + exclusive: ['ui-current-image-indicator-hide'], + + + handlers: [ + // hide indicator on screen next/prev... + // + // XXX experimental -- not sure if we need this... + // XXX need to think about the trigger mechanics here and make + // them more natural... + ['prevScreen.pre nextScreen.pre', + function(){ + var m = this.ribbons.viewer.find('.current-marker') + var t = this.config['current-image-indicator-hide-timeout'] + + var cur = this.current + + return function(){ + var that = this + + // delay fadeout... + if(cur != this.current + && m.css('opacity') == 1 + && this.__current_indicator_t0 == null){ + this.__current_indicator_t0 = setTimeout(function(){ + delete that.__current_indicator_t0 + + m.css({ opacity: 0 }) + }, t) + } + + // cancel/delay previous fadein... + if(this.__current_indicator_t1 != null){ + clearTimeout(this.__current_indicator_t1) + } + + // cancel fadeout and do fadein... + this.__current_indicator_t1 = setTimeout(function(){ + delete that.__current_indicator_t1 + + // cancel fadeout... + if(that.__current_indicator_t0 != null){ + clearTimeout(that.__current_indicator_t0) + delete that.__current_indicator_t0 + } + + // show... + m.animate({ opacity: '1' }) + }, t-50) + } + }], + ], +}) + +var CurrentImageIndicatorHideOnScreenNav = +module.CurrentImageIndicatorHideOnScreenNav = core.ImageGridFeatures.Feature({ + title: '', + doc: '', + + tag: 'ui-current-image-indicator-hide-on-screen-nav', + + + depends: [ + 'ui', + 'ui-current-image-indicator' + ], + exclusive: ['ui-current-image-indicator-hide'], + + + handlers: [ + // this does the following: + // - hide on screen jump + // - show on any other action + // + // 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') + + m.css({ opacity: 0 }) + }], + ['focusImage.post', + function(){ + var m = this.ribbons.viewer.find('.current-marker') + + m.css({ opacity: '' }) + }], + ], +}) + + + +//--------------------------------------------------------------------- +// XXX this should: +// - float to the left of a ribbon if image #1 is fully visible (working) +// - float at left of viewer if image #1 is off screen... +// - float on the same level as the base ribbon... + +// XXX make this an action... +var updateBaseRibbonIndicator = function(img){ + var scale = this.ribbons.scale() + var base = this.ribbons.getRibbon('base') + img = this.ribbons.getImage(img) + var m = base.find('.base-ribbon-marker') + + if(base.length == 0){ + return + } + + if(m.length == 0){ + m = this.ribbons.viewer.find('.base-ribbon-marker') + + // make the indicator... + if(m.length == 0){ + m = $('
') + .addClass('base-ribbon-marker') + .text('base ribbon') + } + + m.prependTo(base) + } + + // 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) + + } else { + m.css('left', '') + } +} + +var BaseRibbonIndicator = +module.BaseRibbonIndicator = core.ImageGridFeatures.Feature({ + title: '', + doc: '', + + tag: 'ui-base-ribbon-indicator', + depends: ['ui'], + + handlers: [ + // move marker to current image... + ['focusImage.pre', + function(target){ + updateBaseRibbonIndicator.call(this, target) + }], + // prevent animations when focusing ribbons... + ['focusRibbon.pre setBaseRibbon', + function(){ + updateBaseRibbonIndicator.call(this) + + /* + this.ribbons.preventTransitions(m) + return function(){ + this.ribbons.restoreTransitions(m) + } + */ + }], + ] +}) + + +var PassiveBaseRibbonIndicator = +module.PassiveBaseRibbonIndicator = core.ImageGridFeatures.Feature({ + title: '', + doc: '', + + tag: 'ui-passive-base-ribbon-indicator', + depends: ['ui'], + + config: { + 'ui-show-passive-base-ribbon-indicator': true, + }, + + actions: actions.Actions({ + togglePassiveBaseRibbonIndicator: ['Interface/Toggle passive base ribbon indicator', + toggler.CSSClassToggler( + function(){ return this.ribbons.viewer }, + 'show-passive-base-ribbon-indicator', + function(state){ + this.config['ui-show-passive-base-ribbon-indicator'] = state == 'on' }) ], + }), + + handlers: [ + ['start', + function(){ + this.togglePassiveBaseRibbonIndicator( + this.config['ui-show-passive-base-ribbon-indicator'] ? + 'on' : 'off') + }] + ], +}) + + + +//--------------------------------------------------------------------- + +// XXX make this work in browser +var UIScaleActions = actions.Actions({ + config: { + // XXX + 'ui-scale-modes': { + desktop: 0, + touch: 3, + }, + }, + + // XXX need to account for scale in PartialRibbons + // XXX should this be browser API??? + // XXX this does not re-scale the ribbons correctly in nw0.13 + toggleInterfaceScale: ['Interface/Toggle interface modes', + core.makeConfigToggler('ui-scale-mode', + function(){ return Object.keys(this.config['ui-scale-modes']) }, + function(state){ + var gui = requirejs('nw.gui') + var win = gui.Window.get() + + + this.ribbons.preventTransitions() + + var w = this.screenwidth + + // NOTE: scale = Math.pow(1.2, zoomLevel) + // XXX in nw0.13 this appears to be async... + win.zoomLevel = this.config['ui-scale-modes'][state] || 0 + + this.screenwidth = w + this.centerViewer() + + this.ribbons.restoreTransitions() + })], +}) + + +// XXX enable scale loading... +// ...need to make this play nice with restoring scale on startup... +var UIScale = +module.UIScale = core.ImageGridFeatures.Feature({ + title: '', + doc: '', + + tag: 'ui-scale', + depends: [ + 'ui', + ], + + actions: UIScaleActions, + + // XXX test if in: + // - chrome app + // - nw + // - mobile + isApplicable: function(){ return this.runtime == 'nw' }, + + // XXX show main window... + handlers: [ + ['start', + function(){ + // XXX this messes up ribbon scale... + // ...too close/fast? + //this.toggleInterfaceScale('!') + }], + ], +}) + + + + +/********************************************************************** +* vim:set ts=4 sw=4 : */ +return module }) diff --git a/ui (gen4)/features/ui-marks.js b/ui (gen4)/features/ui-marks.js index 004d3ab8..c994fee7 100755 --- a/ui (gen4)/features/ui-marks.js +++ b/ui (gen4)/features/ui-marks.js @@ -292,6 +292,15 @@ module.ImageBookmarks = core.ImageGridFeatures.Feature({ +//--------------------------------------------------------------------- + +core.ImageGridFeatures.Feature('marks', [ + 'image-marks', + 'image-bookmarks', +]) + + + /********************************************************************** * 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 new file mode 100755 index 00000000..603f02d1 --- /dev/null +++ b/ui (gen4)/features/ui-single-image.js @@ -0,0 +1,299 @@ +/********************************************************************** +* +* +* +**********************************************************************/ + +define(function(require){ var module = {} + +//var DEBUG = DEBUG != null ? DEBUG : true + +var actions = require('lib/actions') +var features = require('lib/features') +var toggler = require('lib/toggler') + +var core = require('features/core') + + + +/*********************************************************************/ +// helper... +// XXX should this be an action??? +function updateImageProportions(){ + // XXX +} + + +//--------------------------------------------------------------------- + +var SingleImageActions = actions.Actions({ + config: { + // NOTE: these will get overwritten if/when the user changes the scale... + 'single-image-scale': null, + 'ribbon-scale': null, + }, + + toggleSingleImage: ['Interface/Toggle single image view', + toggler.CSSClassToggler( + function(){ return this.ribbons.viewer }, + 'single-image-mode') ], +}) + + +// XXX an ideal case would be: +// +// A) +// viewer +// +---------------+ +// | image | - small image +// | +---+ | - square image block +// | | | | - smaller than this the block is always square +// | +---+ | - we just change scale +// | | +// +---------------+ +// +// +// B) +// viewer +// +---------------+ +// | +-----------+ | - bigger image +// | | image | | - block close to viewer proportion +// | | <--> | | - image block growing parallel to viewer +// | | | | longer side +// | +-----------+ | - this stage is not affected specific by image +// +---------------+ proportions and can be done in bulk +// +// +// C) +// viewer +// +---------------+ +// | image | - image block same size as viewer +// | | - need to account for chrome +// | | +// | | +// | | +// +---------------+ +// +// +// D) +// image +// + - - - - - - - + +// . . +// +---------------+ +// | viewer | - image bigger than viewer in one dimension +// | ^ | - block grows to fit image proportions +// | | | - need to account for individual image +// | v | proportions +// | | - drag enabled +// +---------------+ +// . . +// + - - - - - - - + +// +// +// E) +// image +// + - - - - - - - - - + +// . . +// . +---------------+ . +// . | viewer | . - image bigger than viewer +// . | | . - image block same proportion as image +// . | | . - we just change scale +// . | | . - drag enabled +// . | | . +// . +---------------+ . +// . . +// + - - - - - - - - - + +// +// +var SingleImageView = +module.SingleImageView = core.ImageGridFeatures.Feature({ + title: '', + doc: '', + + tag: 'ui-single-image-view', + depends: [ + 'ui' + ], + + actions: SingleImageActions, + + handlers:[ + ['fitImage.post', + function(){ + + // singe image mode -- set image proportions... + if(this.toggleSingleImage('?') == 'on'){ + updateImageProportions.call(this) + + this.config['single-image-scale'] = this.screenwidth + + } else { + this.config['ribbon-scale'] = this.screenwidth + } + }], + // NOTE: this is not part of the actual action above because we + // need to see if the state has changed and doing this with + // two separate pre/post callbacks (toggler callbacks) is + // harder than with two nested callbacks (action callbacks) + // XXX this uses .screenwidth for scale, is this the right way to go? + ['toggleSingleImage.pre', + function(){ + var pre_state = this.toggleSingleImage('?') + + return function(){ + var state = this.toggleSingleImage('?') + + // singe image mode -- set image proportions... + if(state == 'on'){ + updateImageProportions.call(this) + + // update scale... + if(state != pre_state){ + var w = this.screenwidth + this.config['ribbon-scale'] = w + this.screenwidth = this.config['single-image-scale'] || w + } + + // ribbon mode -- restore original image size... + } else { + this.ribbons.viewer.find('.image:not(.clone)').css({ + width: '', + height: '' + }) + + // update scale... + if(state != pre_state){ + var w = this.screenwidth + this.config['single-image-scale'] = w + this.screenwidth = this.config['ribbon-scale'] || w + } + } + } + }], + ], +}) + + +var SingleImageViewLocalStorage = +module.SingleImageViewLocalStorage = core.ImageGridFeatures.Feature({ + title: '', + doc: '', + + tag: 'ui-single-image-view-local-storage', + depends: [ + 'ui-single-image-view', + 'config-local-storage', + ], + + handlers:[ + // set scale... + ['load', + function(){ + // prevent this from doing anything while no viewer... + if(!this.ribbons || !this.ribbons.viewer || this.ribbons.viewer.length == 0){ + return + } + + if(this.toggleSingleImage('?') == 'on'){ + this.screenwidth = this.config['single-image-scale'] || this.screenwidth + + } else { + this.screenwidth = this.config['ribbon-scale'] || this.screenwidth + } + }], + ], +}) + + + +//--------------------------------------------------------------------- + +// This will store/restore autohide state for single-image and ribbon +// views... +// +// NOTE: chrome 49 + devtools open appears to prevent the cursor from being hidden... +// +// XXX hiding cursor on navigation for some reason does not work... +var SingleImageAutoHideCursor = +module.SingleImageAutoHideCursor = core.ImageGridFeatures.Feature({ + title: '', + doc: '', + + tag: 'ui-single-image-view-autohide-cursor', + depends: [ + 'ui-autohide-cursor', + 'ui-single-image-view', + ], + + config: { + 'cursor-autohide-single-image-view': 'on', + 'cursor-autohide-ribbon-view': 'off', + + //'cursor-autohide-on-navigate': true, + }, + + handlers: [ + // setup... + ['load', + function(){ + var mode = this.toggleSingleImage('?') == 'on' ? + 'cursor-autohide-single-image-view' + : 'cursor-autohide-ribbon-view' + + this.toggleAutoHideCursor(this.config[mode] || 'off') + }], + // store state for each mode... + ['toggleAutoHideCursor', + function(){ + var mode = this.toggleSingleImage('?') == 'on' ? + 'cursor-autohide-single-image-view' + : 'cursor-autohide-ribbon-view' + + this.config[mode] = this.toggleAutoHideCursor('?') + }], + // restore state per mode... + ['toggleSingleImage', + function(){ + if(this.toggleSingleImage('?') == 'on'){ + this.toggleAutoHideCursor(this.config['cursor-autohide-single-image-view']) + + } else { + this.toggleAutoHideCursor(this.config['cursor-autohide-ribbon-view']) + } + }], + /* XXX for some reason this does not work... + // autohide on navigation... + ['focusImage', + function(){ + //if(this.config['cursor-autohide-on-navigate'] + // && this.toggleAutoHideCursor('?') == 'on'){ + // this.toggleAutoHideCursor('on') + //} + if(this.config['cursor-autohide-on-navigate'] + && this.toggleAutoHideCursor('?') == 'on' + && this.ribbons.viewer.prop('cursor-autohide')){ + this.ribbons.viewer + .addClass('cursor-hidden') + } + }], + */ + ] +}) + + + +//--------------------------------------------------------------------- + +core.ImageGridFeatures.Feature('ui-single-image', [ + 'ui-single-image-view', + 'ui-single-image-view-local-storage', + + 'ui-single-image-view-autohide-cursor', +]) + + + + +/********************************************************************** +* vim:set ts=4 sw=4 : */ +return module }) diff --git a/ui (gen4)/features/ui.js b/ui (gen4)/features/ui.js index 85cd7cce..1e2a2713 100755 --- a/ui (gen4)/features/ui.js +++ b/ui (gen4)/features/ui.js @@ -759,266 +759,6 @@ module.Viewer = core.ImageGridFeatures.Feature({ -//--------------------------------------------------------------------- - -// Format: -// { -// : | | null, -// ... -// } -var journalActions = { - clear: null, - load: null, - - setBaseRibbon: null, - - // XXX need to account for position change, i.e. if action had no - // effect then do nothing... - // ...take target position before and after... - shiftImageTo: null, - - shiftImageUp: 'shiftImageDown', - shiftImageDown: 'shiftImageUp', - shiftImageLeft: 'shiftImageRight', - shiftImageRight: 'shiftImageLeft', - shiftRibbonUp: 'shiftRibbonDown', - shiftRibbonDown: 'shiftRibbonUp', - - rotateCW: 'rotateCCW', - rotateCCW: 'rotateCW', - flipHorizontal: 'flipHorizontal', - flipVertical: 'flipVertical', - - sortImages: null, - reverseImages: 'reverseImages', - reverseRibbons: 'reverseRibbons', - - crop: null, - uncrop: null, - - tag: null, - untag: null, - - group: null, - ungroup: null, - expandGroup: null, - collapseGroup: null, - - runJournal: null, -} - -function logImageShift(action){ - return [action.slice(-4) != '.pre' ? - action + '.pre' - : action, - function(target){ - target = this.data.getImage(target) - var args = args2array(arguments) - - var o = this.data.getImageOrder(target) - var r = this.data.getRibbon(target) - var current = this.current - - return function(){ - var on = this.data.getImageOrder(target) - var rn = this.data.getRibbon(target) - - if(o == on || r == rn){ - /* - this.journalPush( - this.current, - action, - args, - { - before: [r, o], - after: [rn, on], - }) - */ - this.journalPush({ - type: 'shift', - current: current, - target: target, - action: action, - args: args, - undo: journalActions[action], - diff: { - before: [r, o], - after: [rn, on], - }, - }) - } - - } - }] -} - - -// XXX is this the right level for this??? -// ...data seems to be a better candidate... -// XXX would be great to add a mechanism define how to reverse actions... -// ...one way to do this at this point is to revert to last state -// and re-run the journal until the desired event... -// XXX need to define a clear journaling strategy in the lines of: -// - save state clears journal and adds a state load action -// - .load(..) clears journal -// XXX needs careful testing... -var Journal = -module.Journal = core.ImageGridFeatures.Feature({ - title: 'Action Journal', - - tag: 'system-journal', - - depends: ['base'], - - actions: actions.Actions({ - - journal: null, - rjournal: null, - - clone: [function(full){ - return function(res){ - res.rjournal = null - res.journal = null - if(full && this.hasOwnProperty('journal') && this.journal){ - res.journal = JSON.parse(JSON.stringify(this.journal)) - } - } - }], - - // XXX might be good to add some kind of metadata to journal... - journalPush: ['- Journal/Add an item to journal', - function(data){ - this.journal = (this.hasOwnProperty('journal') - || this.journal) ? - this.journal - : [] - this.journal.push(data) - }], - clearJournal: ['Journal/Clear the action journal', - function(){ - if(this.journal){ - // NOTE: overwriting here is better as it will keep - // shadowing the parent's .journal in case we - // are cloned. - // NOTE: either way this will have no effect as we - // only use the local .journal but the user may - // get confused... - //delete this.journal - this.journal = null - } - }], - runJournal: ['- Journal/Run journal', - function(journal){ - var that = this - journal.forEach(function(e){ - // load state... - that - .focusImage(e.current) - // run action... - [e.action].apply(that, e.args) - }) - }], - - // XXX need to clear the rjournal as soon as we do something... - // ...at this point it is really easy to mess things up by - // undoing something, and after some actions doing a - // .redoLast(..) - // XXX this is not ready for production... - undoLast: ['Journal/Undo last', - function(){ - var journal = this.journal - this.rjournal = (this.hasOwnProperty('rjournal') - || this.rjournal) ? - this.rjournal - : [] - - for(var i = journal.length-1; i >= 0; i--){ - var a = journal[i] - - // we undo only a very specific set of actions... - if(a.undo && a.type == 'shift' && a.args.length == 0){ - this - .focusImage(a.current) - [a.undo].call(this, a.target) - - // pop the undo command... - this.journal.pop() - this.rjournal.push(journal.splice(i, 1)[0]) - break - } - } - }], - _redoLast: ['Journal/Redo last', - function(){ - if(!this.rjournal || this.rjournal.length == 0){ - return - } - - this.runJournal([this.rjournal.pop()]) - }], - }), - - // log state, action and its args... - // XXX need to drop journal on save... - // XXX rotate/truncate journal??? - // XXX need to check that all the listed actions are clean -- i.e. - // running the journal will produce the same results as user - // actions that generated the journal. - // XXX would be good if we could know the name of the action in the - // handler, thus enabling us to define a single handler rather - // than generating a custom handler per action... - handlers: [ - logImageShift('shiftImageTo'), - logImageShift('shiftImageUp'), - logImageShift('shiftImageDown'), - logImageShift('shiftImageLeft'), - logImageShift('shiftImageRight'), - logImageShift('shiftRibbonUp'), - logImageShift('shiftRibbonDown'), - - ].concat([ - 'clear', - 'load', - - 'setBaseRibbon', - - 'rotateCW', - 'rotateCCW', - 'flipHorizontal', - 'flipVertical', - - 'sortImages', - 'reverseImages', - 'reverseRibbons', - - 'crop', - 'uncrop', - - 'tag', - 'untag', - - 'group', - 'ungroup', - 'expandGroup', - 'collapseGroup', - - //'runJournal', - ].map(function(action){ - return [ - action+'.pre', - function(){ - this.journalPush({ - type: 'basic', - current: this.current, - action: action, - args: args2array(arguments), - }) - }] - })), -}) - - - //--------------------------------------------------------------------- // XXX add setup/taredown... @@ -1081,7 +821,9 @@ module.AutoHideCursor = core.ImageGridFeatures.Feature({ doc: '', tag: 'ui-autohide-cursor', - depends: ['ui'], + depends: [ + 'ui' + ], config: { 'cursor-autohide-timeout': 1000, @@ -1165,79 +907,6 @@ module.AutoHideCursor = core.ImageGridFeatures.Feature({ }) -// This will store/restore autohide state for single-image and ribbon -// views... -// -// NOTE: chrome 49 + devtools open appears to prevent the cursor from being hidden... -// -// XXX hiding cursor on navigation for some reason does not work... -var AutoHideCursorSingleImage = -module.AutoHideCursorSingleImage = core.ImageGridFeatures.Feature({ - title: '', - doc: '', - - tag: 'ui-autohide-cursor-single-image-view', - depends: [ - 'ui-autohide-cursor', - 'ui-single-image-view', - ], - - config: { - 'cursor-autohide-single-image-view': 'on', - 'cursor-autohide-ribbon-view': 'off', - - //'cursor-autohide-on-navigate': true, - }, - - handlers: [ - // setup... - ['load', - function(){ - var mode = this.toggleSingleImage('?') == 'on' ? - 'cursor-autohide-single-image-view' - : 'cursor-autohide-ribbon-view' - - this.toggleAutoHideCursor(this.config[mode] || 'off') - }], - // store state for each mode... - ['toggleAutoHideCursor', - function(){ - var mode = this.toggleSingleImage('?') == 'on' ? - 'cursor-autohide-single-image-view' - : 'cursor-autohide-ribbon-view' - - this.config[mode] = this.toggleAutoHideCursor('?') - }], - // restore state per mode... - ['toggleSingleImage', - function(){ - if(this.toggleSingleImage('?') == 'on'){ - this.toggleAutoHideCursor(this.config['cursor-autohide-single-image-view']) - - } else { - this.toggleAutoHideCursor(this.config['cursor-autohide-ribbon-view']) - } - }], - /* XXX for some reason this does not work... - // autohide on navigation... - ['focusImage', - function(){ - //if(this.config['cursor-autohide-on-navigate'] - // && this.toggleAutoHideCursor('?') == 'on'){ - // this.toggleAutoHideCursor('on') - //} - if(this.config['cursor-autohide-on-navigate'] - && this.toggleAutoHideCursor('?') == 'on' - && this.ribbons.viewer.prop('cursor-autohide')){ - this.ribbons.viewer - .addClass('cursor-hidden') - } - }], - */ - ] -}) - - //--------------------------------------------------------------------- @@ -1808,191 +1477,6 @@ module.PartialRibbons = core.ImageGridFeatures.Feature({ -//--------------------------------------------------------------------- - -var SingleImageActions = actions.Actions({ - config: { - // NOTE: these will get overwritten if/when the user changes the scale... - 'single-image-scale': null, - 'ribbon-scale': null, - }, - - toggleSingleImage: ['Interface/Toggle single image view', - toggler.CSSClassToggler( - function(){ return this.ribbons.viewer }, - 'single-image-mode') ], -}) - -// helper... -// XXX should this be an action??? -function updateImageProportions(){ - // XXX -} - - -// XXX an ideal case would be: -// -// A) -// viewer -// +---------------+ -// | image | - small image -// | +---+ | - square image block -// | | | | - smaller than this the block is always square -// | +---+ | - we just change scale -// | | -// +---------------+ -// -// -// B) -// viewer -// +---------------+ -// | +-----------+ | - bigger image -// | | image | | - block close to viewer proportion -// | | <--> | | - image block growing parallel to viewer -// | | | | longer side -// | +-----------+ | - this stage is not affected specific by image -// +---------------+ proportions and can be done in bulk -// -// -// C) -// viewer -// +---------------+ -// | image | - image block same size as viewer -// | | - need to account for chrome -// | | -// | | -// | | -// +---------------+ -// -// -// D) -// image -// + - - - - - - - + -// . . -// +---------------+ -// | viewer | - image bigger than viewer in one dimension -// | ^ | - block grows to fit image proportions -// | | | - need to account for individual image -// | v | proportions -// | | - drag enabled -// +---------------+ -// . . -// + - - - - - - - + -// -// -// E) -// image -// + - - - - - - - - - + -// . . -// . +---------------+ . -// . | viewer | . - image bigger than viewer -// . | | . - image block same proportion as image -// . | | . - we just change scale -// . | | . - drag enabled -// . | | . -// . +---------------+ . -// . . -// + - - - - - - - - - + -// -// -var SingleImageView = -module.SingleImageView = core.ImageGridFeatures.Feature({ - title: '', - doc: '', - - tag: 'ui-single-image-view', - depends: ['ui'], - - actions: SingleImageActions, - - handlers:[ - ['fitImage.post', - function(){ - - // singe image mode -- set image proportions... - if(this.toggleSingleImage('?') == 'on'){ - updateImageProportions.call(this) - - this.config['single-image-scale'] = this.screenwidth - - } else { - this.config['ribbon-scale'] = this.screenwidth - } - }], - // NOTE: this is not part of the actual action above because we - // need to see if the state has changed and doing this with - // two separate pre/post callbacks (toggler callbacks) is - // harder than with two nested callbacks (action callbacks) - // XXX this uses .screenwidth for scale, is this the right way to go? - ['toggleSingleImage.pre', - function(){ - var pre_state = this.toggleSingleImage('?') - - return function(){ - var state = this.toggleSingleImage('?') - - // singe image mode -- set image proportions... - if(state == 'on'){ - updateImageProportions.call(this) - - // update scale... - if(state != pre_state){ - var w = this.screenwidth - this.config['ribbon-scale'] = w - this.screenwidth = this.config['single-image-scale'] || w - } - - // ribbon mode -- restore original image size... - } else { - this.ribbons.viewer.find('.image:not(.clone)').css({ - width: '', - height: '' - }) - - // update scale... - if(state != pre_state){ - var w = this.screenwidth - this.config['single-image-scale'] = w - this.screenwidth = this.config['ribbon-scale'] || w - } - } - } - }], - ], -}) - - -var SingleImageViewLocalStorage = -module.SingleImageViewLocalStorage = core.ImageGridFeatures.Feature({ - title: '', - doc: '', - - tag: 'ui-single-image-view-local-storage', - depends: [ - 'ui-single-image-view', - 'config-local-storage', - ], - - handlers:[ - // set scale... - ['load', - function(){ - // prevent this from doing anything while no viewer... - if(!this.ribbons || !this.ribbons.viewer || this.ribbons.viewer.length == 0){ - return - } - - if(this.toggleSingleImage('?') == 'on'){ - this.screenwidth = this.config['single-image-scale'] || this.screenwidth - - } else { - this.screenwidth = this.config['ribbon-scale'] || this.screenwidth - } - }], - ], -}) - - //--------------------------------------------------------------------- // These feature glue traverse and ribbon alignment... @@ -2165,531 +1649,6 @@ module.ShiftAnimation = core.ImageGridFeatures.Feature({ -//--------------------------------------------------------------------- - -var BoundsIndicatorsActions = actions.Actions({ - flashIndicator: ['- Interface/Flash an indicator', - function(direction){ - if(this.ribbons.getRibbonSet().length == 0){ - return - } - var cls = { - // shift up/down... - up: '.up-indicator', - down: '.down-indicator', - // hit start/end/top/bottom of view... - start: '.start-indicator', - end: '.end-indicator', - top: '.top-indicator', - bottom: '.bottom-indicator', - }[direction] - - var indicator = this.ribbons.viewer.find(cls) - - if(indicator.length == 0){ - indicator = $('
') - .addClass(cls.replace('.', '') +' '+ this.tag) - .appendTo(this.ribbons.viewer) - } - - return indicator - // NOTE: this needs to be visible in all cases and key press - // rhythms... - .show() - .delay(100) - .fadeOut(300) - }], -}) - -// helper... -function didAdvance(indicator){ - return function(){ - var img = this.data.current - return function(){ - if(img == this.data.current){ - this.flashIndicator(indicator) - } - } - } -} - -var BoundsIndicators = -module.BoundsIndicators = core.ImageGridFeatures.Feature({ - title: '', - doc: '', - - tag: 'ui-bounds-indicators', - depends: ['ui'], - - actions: BoundsIndicatorsActions, - - handlers: [ - // basic navigation... - ['nextImage.pre lastImage.pre', didAdvance('end')], - ['prevImage.pre firstImage.pre', didAdvance('start')], - ['nextRibbon.pre lastRibbon.pre', didAdvance('bottom')], - ['prevRibbon.pre firstRibbon.pre', didAdvance('top')], - - // vertical shifting... - ['shiftImageUp.pre', - function(target){ - target = target || this.current - var r = this.data.getRibbonOrder(target) - - var l = this.data.getImages(r).length - var l0 = this.data.getImages(0).length - - return function(){ - // when shifting last image of top ribbon (i.e. length == 1) - // up the state essentially will not change... - if((r == 0 && l == 1) - // we are shifting to a new empty ribbon... - || (r == 1 && l == 1 && l0 == 0)){ - this.flashIndicator('top') - } else { - this.flashIndicator('up') - } - } - }], - ['shiftImageDown.pre', - function(target){ - target = target || this.current - var r0 = this.data.getRibbonOrder(target) - var l = this.data.getImages(r0).length - - return function(){ - var r1 = this.data.getRibbonOrder(target) - if(r0 == r1 && r0 == this.data.ribbon_order.length-1 && l == 1){ - this.flashIndicator('bottom') - } else { - this.flashIndicator('down') - } - } - }], - - // horizontal shifting... - ['shiftImageLeft.pre', - function(target){ - if(target == null - //&& actions.data.getImageOrder('ribbon') == 0){ - && this.data.getImage('prev') == null){ - this.flashIndicator('start') - } - }], - ['shiftImageRight.pre', - function(target){ - if(target == null - && this.data.getImage('next') == null){ - this.flashIndicator('end') - } - }], - ], -}) - - - -//--------------------------------------------------------------------- - -var CurrentImageIndicatorActions = actions.Actions({ - config: { - 'current-image-border': 3, - 'current-image-min-border': 2, - - 'current-image-border-timeout': 200, - 'current-image-shift-timeout': 200, - - 'current-image-indicator-fadein': 500, - - 'current-image-indicator-hide-timeout': 250, - - // this can be: - // 'hide' - simply hide on next/prev screen action - // and show on focus image. - // 'hide-show' - hide on fast scroll through screens and - // show when slowing down. - 'current-image-indicator-screen-nav-mode': 'hide', - }, - - updateCurrentImageIndicator: ['- Interface/Update current image indicator', - function(target, update_border){ - var ribbon_set = this.ribbons.getRibbonSet() - - if(ribbon_set.length == 0){ - return - } - - var scale = this.ribbons.scale() - var cur = this.ribbons.getImage(target) - // NOTE: cur may be unloaded... - var ribbon = this.ribbons.getRibbon(cur.length > 0 ? target : this.currentRibbon) - - var marker = ribbon.find('.current-marker') - - // remove marker if current image is not loaded... - if(cur.length == 0){ - marker.remove() - return - } - - // get config... - var border = this.config['current-image-border'] - var min_border = this.config['current-image-min-border'] - var border_timeout = this.config['current-image-border-timeout'] - var fadein = this.config['current-image-indicator-fadein'] - - // no marker found -- either in different ribbon or not created yet... - if(marker.length == 0){ - // get marker globally... - marker = this.ribbons.viewer.find('.current-marker') - - // no marker exists -- create a marker... - if(marker.length == 0){ - var marker = $('
') - .addClass('current-marker ui-current-image-indicator') - .css({ - opacity: '0', - // NOTE: these are not used for positioning - // but are needed for correct absolute - // placement... - top: '0px', - left: '0px', - }) - .appendTo(ribbon) - .animate({ - 'opacity': 1 - }, fadein) - this.ribbons.dom.setOffset(marker, 0, 0) - - // add marker to current ribbon... - } else { - marker.appendTo(ribbon) - } - } - - // NOTE: we will update only the attrs that need to be updated... - var css = {} - - var w = cur.outerWidth(true) - var h = cur.outerHeight(true) - - // keep size same as the image... - if(marker.outerWidth() != w || marker.outerHeight() != h){ - css.width = w - css.height = h - } - - // update border... - if(update_border !== false){ - var border = Math.max(min_border, border / scale) - - // set border right away... - if(update_border == 'before'){ - css.borderWidth = border - - // set border with a delay... - // NOTE: this is to prevent the ugly border resize before - // the scale on scale down animation starts... - } else { - setTimeout(function(){ - marker.css({ borderWidth: border }) - }, border_timeout) - } - } - - //css.left = cur[0].offsetLeft - this.ribbons.dom.setOffset(marker, cur[0].offsetLeft, 0) - - marker.css(css) - }], -}) - -var CurrentImageIndicator = -module.CurrentImageIndicator = core.ImageGridFeatures.Feature({ - title: '', - doc: '', - - tag: 'ui-current-image-indicator', - depends: ['ui'], - - actions: CurrentImageIndicatorActions, - - handlers: [ - // move marker to current image... - ['focusImage.post', - function(){ this.updateCurrentImageIndicator() }], - // prevent animations when focusing ribbons... - ['focusRibbon.pre', - function(){ - var m = this.ribbons.viewer.find('.current-marker') - this.ribbons.preventTransitions(m) - return function(){ - this.ribbons.restoreTransitions(m) - } - }], - // this is here to compensate for position change on ribbon - // resize... - // NOTE: hide/show of indicator on resize appears to have solved - // the jumpy animation issue. - // this might cause some blinking on slow resizes (visible - // only on next/prev screen)... - // ...still not sure why .preventTransitions(m) did not - // do the job. - ['resizeRibbon.pre', - function(target, s){ - var m = this.ribbons.viewer.find('.current-marker') - // only update if marker exists and we are in current ribbon... - if(m.length != 0 && this.currentRibbon == this.data.getRibbon(target)){ - //this.ribbons.preventTransitions(m) - m.hide() - - return function(){ - this.updateCurrentImageIndicator(target, false) - //this.ribbons.restoreTransitions(m, true) - m - .show() - // NOTE: keeping display in inline style will - // prevent the element from being hidden - // by css... - .css({display: ''}) - } - } - }], - // Change border size in the appropriate spot in the animation: - // - before animation when scaling up - // - after when scaling down - // This is done to make the visuals consistent... - ['fitImage.pre fitRibbon.pre', - function(w1){ - var w0 = this.screenwidth - w1 = w1 || 1 - return function(){ - this.updateCurrentImageIndicator(null, w0 > w1 ? 'before' : 'after') - } - }], - ['shiftImageLeft.pre shiftImageRight.pre', - function(){ - this.ribbons.viewer.find('.current-marker').hide() - if(this._current_image_indicator_timeout != null){ - clearTimeout(this._current_image_indicator_timeout) - delete this._current_image_indicator_timeout - } - return function(){ - var ribbons = this.ribbons - var fadein = this.config['current-image-indicator-fadein'] - this._current_image_indicator_timeout = setTimeout(function(){ - ribbons.viewer.find('.current-marker').fadeIn(fadein) - }, this.config['current-image-shift-timeout']) - } - }], - ], -}) - - -var CurrentImageIndicatorHideOnFastScreenNav = -module.CurrentImageIndicatorHideOnFastScreenNav = core.ImageGridFeatures.Feature({ - title: '', - doc: '', - - tag: 'ui-current-image-indicator-hide-on-fast-screen-nav', - - - depends: [ - 'ui', - 'ui-current-image-indicator' - ], - exclusive: ['ui-current-image-indicator-hide'], - - - handlers: [ - // hide indicator on screen next/prev... - // - // XXX experimental -- not sure if we need this... - // XXX need to think about the trigger mechanics here and make - // them more natural... - ['prevScreen.pre nextScreen.pre', - function(){ - var m = this.ribbons.viewer.find('.current-marker') - var t = this.config['current-image-indicator-hide-timeout'] - - var cur = this.current - - return function(){ - var that = this - - // delay fadeout... - if(cur != this.current - && m.css('opacity') == 1 - && this.__current_indicator_t0 == null){ - this.__current_indicator_t0 = setTimeout(function(){ - delete that.__current_indicator_t0 - - m.css({ opacity: 0 }) - }, t) - } - - // cancel/delay previous fadein... - if(this.__current_indicator_t1 != null){ - clearTimeout(this.__current_indicator_t1) - } - - // cancel fadeout and do fadein... - this.__current_indicator_t1 = setTimeout(function(){ - delete that.__current_indicator_t1 - - // cancel fadeout... - if(that.__current_indicator_t0 != null){ - clearTimeout(that.__current_indicator_t0) - delete that.__current_indicator_t0 - } - - // show... - m.animate({ opacity: '1' }) - }, t-50) - } - }], - ], -}) - -var CurrentImageIndicatorHideOnScreenNav = -module.CurrentImageIndicatorHideOnScreenNav = core.ImageGridFeatures.Feature({ - title: '', - doc: '', - - tag: 'ui-current-image-indicator-hide-on-screen-nav', - - - depends: [ - 'ui', - 'ui-current-image-indicator' - ], - exclusive: ['ui-current-image-indicator-hide'], - - - handlers: [ - // this does the following: - // - hide on screen jump - // - show on any other action - // - // 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') - - m.css({ opacity: 0 }) - }], - ['focusImage.post', - function(){ - var m = this.ribbons.viewer.find('.current-marker') - - m.css({ opacity: '' }) - }], - ], -}) - - - -//--------------------------------------------------------------------- -// XXX this should: -// - float to the left of a ribbon if image #1 is fully visible (working) -// - float at left of viewer if image #1 is off screen... -// - float on the same level as the base ribbon... - -// XXX make this an action... -var updateBaseRibbonIndicator = function(img){ - var scale = this.ribbons.scale() - var base = this.ribbons.getRibbon('base') - img = this.ribbons.getImage(img) - var m = base.find('.base-ribbon-marker') - - if(base.length == 0){ - return - } - - if(m.length == 0){ - m = this.ribbons.viewer.find('.base-ribbon-marker') - - // make the indicator... - if(m.length == 0){ - m = $('
') - .addClass('base-ribbon-marker') - .text('base ribbon') - } - - m.prependTo(base) - } - - // 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) - - } else { - m.css('left', '') - } -} - -var BaseRibbonIndicator = -module.BaseRibbonIndicator = core.ImageGridFeatures.Feature({ - title: '', - doc: '', - - tag: 'ui-base-ribbon-indicator', - depends: ['ui'], - - handlers: [ - // move marker to current image... - ['focusImage.pre', - function(target){ - updateBaseRibbonIndicator.call(this, target) - }], - // prevent animations when focusing ribbons... - ['focusRibbon.pre setBaseRibbon', - function(){ - updateBaseRibbonIndicator.call(this) - - /* - this.ribbons.preventTransitions(m) - return function(){ - this.ribbons.restoreTransitions(m) - } - */ - }], - ] -}) - - -var PassiveBaseRibbonIndicator = -module.PassiveBaseRibbonIndicator = core.ImageGridFeatures.Feature({ - title: '', - doc: '', - - tag: 'ui-passive-base-ribbon-indicator', - depends: ['ui'], - - config: { - 'ui-show-passive-base-ribbon-indicator': true, - }, - - actions: actions.Actions({ - togglePassiveBaseRibbonIndicator: ['Interface/Toggle passive base ribbon indicator', - toggler.CSSClassToggler( - function(){ return this.ribbons.viewer }, - 'show-passive-base-ribbon-indicator', - function(state){ - this.config['ui-show-passive-base-ribbon-indicator'] = state == 'on' }) ], - }), - - handlers: [ - ['start', - function(){ - this.togglePassiveBaseRibbonIndicator( - this.config['ui-show-passive-base-ribbon-indicator'] ? - 'on' : 'off') - }] - ], -}) - - - //--------------------------------------------------------------------- // XXX experimental... @@ -3050,78 +2009,6 @@ module.URLHash = core.ImageGridFeatures.Feature({ -//--------------------------------------------------------------------- - -// XXX make this work in browser -var UIScaleActions = actions.Actions({ - config: { - // XXX - 'ui-scale-modes': { - desktop: 0, - touch: 3, - }, - }, - - // XXX need to account for scale in PartialRibbons - // XXX should this be browser API??? - // XXX this does not re-scale the ribbons correctly in nw0.13 - toggleInterfaceScale: ['Interface/Toggle interface modes', - core.makeConfigToggler('ui-scale-mode', - function(){ return Object.keys(this.config['ui-scale-modes']) }, - function(state){ - var gui = requirejs('nw.gui') - var win = gui.Window.get() - - - this.ribbons.preventTransitions() - - var w = this.screenwidth - - // NOTE: scale = Math.pow(1.2, zoomLevel) - // XXX in nw0.13 this appears to be async... - win.zoomLevel = this.config['ui-scale-modes'][state] || 0 - - this.screenwidth = w - this.centerViewer() - - this.ribbons.restoreTransitions() - })], -}) - - -// XXX enable scale loading... -// ...need to make this play nice with restoring scale on startup... -var UIScale = -module.UIScale = core.ImageGridFeatures.Feature({ - title: '', - doc: '', - - tag: 'ui-scale', - depends: [ - 'ui', - ], - - actions: UIScaleActions, - - // XXX test if in: - // - chrome app - // - nw - // - mobile - isApplicable: function(){ return this.runtime == 'nw' }, - - // XXX show main window... - handlers: [ - ['start', - function(){ - // XXX this messes up ribbon scale... - // ...too close/fast? - //this.toggleInterfaceScale('!') - }], - ], -}) - - - //---------------------------------------------------------------------