more experimenting with ranges + new actions and action browse features...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2016-12-29 23:23:05 +03:00
parent 72e1cad416
commit fdb280d519
12 changed files with 452 additions and 330 deletions

View File

@ -47,6 +47,12 @@ body {
z-index: 5000;
}
.viewer:not(.no-transitions) .range-offscreen-indicator:not(.no-transitions) {
transition: all 0.1s linear;
}
/* basic animation... */
.viewer:not(.no-transitions) {
-webkit-transition: background-color 0.8s ease;

View File

@ -1772,6 +1772,30 @@ stretching in width... */
}
.range-offscreen-indicator {
display: none;
position: fixed;
width: @image-tile-size / 16;
height: @image-tile-size / 4;
margin-top: -@image-tile-size / 8;
box-sizing: border-box;
border-top: solid @image-tile-size / 8 transparent;
border-bottom: solid @image-tile-size / 8 transparent;
}
.range-offscreen-indicator.left {
border-right: solid @image-tile-size / 16 yellow;
border-left: solid @image-border * 2 transparent;
}
.range-offscreen-indicator.right {
border-left: solid @image-tile-size / 16 yellow;
border-right: solid @image-border * 2 transparent;
}
/*************************************************** Progress bars ***/

View File

@ -3,8 +3,8 @@
*
*
**********************************************************************/
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)(
function(require){ var module={} // makes module AMD/node compatible...
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
(function(require){ var module={} // make module AMD/node compatible...
/*********************************************************************/
var actions = require('lib/actions')

View File

@ -25,6 +25,7 @@ require('features/ui-progress')
require('features/keyboard')
require('features/ui-status')
require('features/ui-marks')
require('features/ui-ranges')
require('features/ui-widgets')
require('features/ui-slideshow')
require('features/external-editor')

View File

@ -889,7 +889,7 @@ module.CropActions = actions.Actions({
this.data = this.data.crop(list, flatten)
}
}],
uncrop: ['Crop/Uncrop ribbons',
uncrop: ['Crop/Uncrop',
function(level, restore_current, keep_crop_order){
level = level || 1
@ -936,7 +936,7 @@ module.CropActions = actions.Actions({
// - the order is simple and already done above...
// - I think that levels should be relative to images, the
// only problem here is how to deal with new ribbons...
mergeCrop: ['Crop|Edit/Merge crop',
mergeCrop: ['- Crop|Edit/Merge crop',
function(){
// XXX
}],

View File

@ -133,6 +133,17 @@ module.GLOBAL_KEYBOARD = {
},
},
'Range': {
doc: 'Range editing',
pattern: '.brace',
// XXX add:
// - range navigation
// - range manipulation
Esc: 'clearRange',
},
// XXX add "save as collection..." (???)
// XXX cleanup...
'Viewer': {

View File

@ -120,7 +120,7 @@ var LocationActions = actions.Actions({
// NOTE: .location will be set by the .load handler...
//
// XXX not sure about where to set the .__location -- see inside...
loadLocation: ['File/Load location',
loadLocation: ['- File/Load location',
function(location){
location = location || this.location

346
ui (gen4)/features/ui-ranges.js Executable file
View File

@ -0,0 +1,346 @@
/**********************************************************************
*
*
*
**********************************************************************/
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
(function(require){ var module={} // make module AMD/node compatible...
/*********************************************************************/
var actions = require('lib/actions')
var features = require('lib/features')
var core = require('features/core')
/*********************************************************************/
var RangeActions = actions.Actions({
// .makeBrace('open')
// .makeBrace('open', image)
// .makeBrace('close')
// .makeBrace('close', image)
//
// XXX this should not be here...
makeBrace: ['- Range/',
function(type, gid){
var cls = type == 'open' ? 'brace-open' : 'brace-close'
var r = this.ribbons.viewer.find('.ribbon')
var brace = this.ribbons.getRibbon(gid).find('.mark.'+cls)
if(brace.length == 0){
brace = $('<span>')
.addClass('mark brace '+cls)
} else if(brace.length > 1){
brace = brace.detach().first()
}
brace
.attr('gid', gid)
this.ribbons.getImage(gid)[type == 'open' ? 'before' : 'after'](brace)
// XXX this does not work for non-current images ...
this.ribbons.preventTransitions(r)
// XXX is this correct here???
this.focusImage()
this.ribbons.restoreTransitions(r)
}],
// XXX add "brace off screen" indicators....
updateRangeIndicators: ['- Range/',
function(){
var update = false
var range = this.data.__range
// XXX not sure if this sweeping action is the right way to
// go but it sure makes things simpler...
if(range == null){
update = true
this.ribbons.viewer
.find('.ribbon .mark.brace')
.remove()
} else {
var that = this
this.data.ribbon_order.forEach(function(r){
var a = that.data.getImage(range[0], 'after', r)
var b = that.data.getImage(range[1], 'before', r)
// only draw braces if some part of the ribbon is
// in range...
if(a != null && b != null){
that
.makeBrace('open', a)
.makeBrace('close', b)
// remove braces from ribbon...
} else {
update = true
that.ribbons.getRibbon(r)
.find('.mark.brace')
.remove()
}
})
}
if(update){
var r = this.ribbons.viewer.find('.ribbon')
// XXX this does not work for non-current images ...
this.ribbons.preventTransitions(r)
// XXX is this correct here???
this.focusImage()
this.ribbons.restoreTransitions(r)
}
}],
clearRange: ['Range/Clear range',
// XXX not sure if it is best to hide things or make them do a
// sensible default thing...
//{isDisabled: function(){ return !this.data.__range }},
function(image){
var r = this.ribbons.viewer.find('.ribbon')
delete this.data.__range
this.updateRangeIndicators()
}],
// procedure:
// - set brace
// when no braces set:
// - sets two braces around target image
// When a brace is set:
// - check brace orientation and set open/close to target
// - update braces on all ribbons
setRangeBorder: ['Range/Set range border',
function(image, type){
var image = this.data.getImage(image)
var range = this.data.__range = this.data.__range || []
// no range...
if(range.length == 0){
range.push(image)
range.push(image)
// range set...
} else {
var a = this.data.getImageOrder(range[0])
var b = this.data.getImageOrder(range[1])
var t = this.data.getImageOrder(image)
var i =
// type/range conflict...
type == 'close' && t < a ? null
: type == 'open' && t > b ? null
// extend left/right...
: t <= a ? 0
: t >= b ? 1
// set left/right limit...
: type == 'open' ? 0
: type == 'close' ? 1
// narrow to the closest brace...
: a - t < b - t ? 0
: 1
if(i == null){
return
}
range[i] = image
}
this.updateRangeIndicators()
}],
openRange: ['Range/Open range',
function(image){ this.setRangeBorder(image, 'open') }],
closeRange: ['Range/Close range',
function(image){ this.setRangeBorder(image, 'close') }],
cropRange: ['Range|Crop/Crop range',
// XXX not sure if it is best to hide things or make them do a
// sensible default thing...
//{isDisabled: function(){ return !this.data.__range }},
function(){
var range = this.data.__range
var order = this.data.order
range ?
this.crop(order.slice(
order.indexOf(range[0]),
order.indexOf(range[1])+1))
: this.crop([])
}],
cropRangeOut: ['Range|Crop/Crop out range',
// XXX not sure if it is best to hide things or make them do a
// sensible default thing...
//{isDisabled: function(){ return !this.data.__range }},
function(){
var range = this.data.__range
var order = this.data.order
range ?
this.crop(order
.slice(0, order.indexOf(range[0]))
.concat(order.slice(order.indexOf(range[1])+1)))
: this.crop()
}],
})
var Range =
module.Range = core.ImageGridFeatures.Feature({
title: '',
doc: '',
tag: 'ui-range',
depends: [
'ui',
],
actions: RangeActions,
handlers: [
[[
'crop',
'reload',
],
function(){ this.updateRangeIndicators() }],
['updateImage',
function(_, gid){
var range = this.data.__range
if(this.ribbons && range && gid){
var r = this.data.getRibbon(gid)
var a = gid == range[0] ?
this.makeBrace('open', gid)
: this.data.getImage(range[0], 'after', r)
var b = gid == range[1] ?
this.makeBrace('close', gid)
: this.data.getImage(range[1], 'before', r)
if(a != null && b != null){
gid == a
&& this.makeBrace('open', gid)
gid == b
&& this.makeBrace('close', gid)
}
}
}],
['shiftImage.pre',
function(gid){
var range = this.data.__range
if(this.ribbons && range){
this.ribbons.getImageMarks(gid).filter('.brace').remove()
return function(){
this.updateRangeIndicators()
}
}
}],
// show/hide off-screen indicators...
// XXX STUB: should we animate indicators???
['setScale.pre',
function(scale){
var range = this.data.__range
if(!this.ribbons || !range){
return
}
this.ribbons.getRibbonLocator()
.find('.range-offscreen-indicator')
.hide()
}],
[[
'focusImage',
'setScale',
'updateRangeIndicators',
],
function(_, gid){
gid = gid || this.current
var that = this
var locator = this.ribbons.getRibbonLocator()
var range = this.data.__range
if(!this.ribbons || !range){
locator.find('.range-offscreen-indicator').remove()
return
}
var Wr = this.ribbons.viewer.width()
var W = (Wr / this.scale) / 2
var a = this.data.getImageOrder(range[0])
var b = this.data.getImageOrder(range[1])
var _make = function(gid, ribbon, direction){
var t = ribbon[0].offsetTop
var h = ribbon[0].offsetHeight / 2
var i = that.data.getImageOrder(gid)
var indicator = locator
.find('.range-offscreen-indicator.'+direction+'[gid="'+gid+'"]')
// XXX this only works if brace is loaded...
if(direction == 'left'){
var brace = ribbon.find('.mark.brace-open')
if(brace.length == 0 || brace.offset().left >= 0){
return indicator.remove()
}
} else if(direction == 'right'){
var brace = ribbon.find('.mark.brace-close')
if(brace.length == 0 || brace.offset().left < Wr){
return indicator.remove()
}
}
if(indicator.length == 0){
locator.append($('<div>')
.addClass('range-offscreen-indicator '+direction)
.attr('gid', gid))
}
var css = {}
css.left = (direction == 'left' ?
-W
: W-(indicator[0] && indicator[0].offsetWidth)) + 'px'
css.top = (t + h) + 'px'
that.ribbons.preventTransitions(indicator)
indicator
.css(css)
.show()
that.ribbons.restoreTransitions(indicator)
return indicator
}
setTimeout(function(){
that.data.ribbon_order.forEach(function(gid){
var ribbon = that.ribbons.getRibbon(gid)
_make(gid, ribbon, 'left')
_make(gid, ribbon, 'right')
})
}, 400)
}],
],
})
/**********************************************************************
* vim:set ts=4 sw=4 : */ return module })

View File

@ -157,29 +157,31 @@ var StatusBarActions = actions.Actions({
var type = item.attr('type')
}
// update...
// NOTE: using .toggleStatusBarIndexMode(..) here will fall
// into an infinite recursion...
var cls = (that.config['status-bar-index'] || {})['mode'] || 'normal'
item
.addClass(cls)
.removeClass(cls != 'normal' ? 'normal' : 'global')
// global index...
if(cls == 'global'){
item.find('.position:not(:focus)')
.text(this.data ? this.data.getImageOrder(gid)+1 : 0)
item.find('.length')
.text('/'+ (this.data ? this.data.length : 0))
var i = this.data ? this.data.getImageOrder(gid) : -1
var l = this.data ? this.data.length : 0
// ribbon index...
} else {
item.find('.position:not(:focus)')
.text(this.data ? this.data.getImageOrder('ribbon', gid)+1 : 0)
item.find('.length')
.text('/'+ (this.data ? this.data.getImages(gid).len : 0))
var i = this.data ? this.data.getImageOrder('ribbon', gid) : -1
var l = this.data ? this.data.getImages(gid).len : 0
}
// update...
item
.addClass(cls)
.removeClass(cls != 'normal' ? 'normal' : 'global')
.find('.position:not(:focus)')
.text(i >= 0 ? i+1 : '-')
.end()
.find('.length')
.text(l > 0 ? ('/' + l) : '')
return item
},
ribbon: function(item, gid, img){
@ -188,7 +190,7 @@ var StatusBarActions = actions.Actions({
// get ribbon number...
var n = (this.data && this.data.ribbon_order.length > 0) ?
this.data.getRibbonOrder(gid || this.current)
: '-'
: null
var t = (this.config['status-bar-ribbon-count'] && this.data) ?
this.data.ribbon_order.length
: null
@ -217,7 +219,7 @@ var StatusBarActions = actions.Actions({
item
.find('.ribbon-number')
.html(n+1)
.html(n != null ? n+1 : '-')
.end()
.find('.ribbon-count')
.html(t || '')

View File

@ -763,6 +763,14 @@ var BrowseActionsActions = actions.Actions({
'80:Edit',
'70:Navigate',
'60:Image',
'50:Crop',
'Crop/80:Crop marked images',
'Crop/80:Crop bookmarked images',
'Crop/70:Flatten',
// ...
'Crop/-80:Uncrop and keep crop image order',
'Crop/-81:Uncrop all',
'Crop/-82:Uncrop',
'-50:Interface',
'-60:Workspace',
@ -798,6 +806,20 @@ var BrowseActionsActions = actions.Actions({
// with greater or no priority.
//
//
// An action can also be disabled dynamically:
// - .isDisabled() action method is called with actions as base if
// action is not disabled.
// Example:
// someAction: ['Path/To/Some action',
// {isDisabled: function(){ ... }},
// function(){
// ...
// }],
//
// NOTE: disabling an action path has priority over the action
// .isDisabled() predicate...
//
//
// NOTE: if the action returns an instance of overlay.Overlay this
// will not close right away but rather bind to:
// overlay.close -> self.focus()
@ -913,18 +935,25 @@ var BrowseActionsActions = actions.Actions({
buildTree(path, leaf, null, null, tree)
})
// buld the tree...
// build the tree...
var paths = this.getPath()
Object.keys(paths).forEach(function(key){
// handle disabled flag...
var action = paths[key][0]
var disabled = key.split(/^- /)
var path = disabled.pop()
disabled = disabled.length > 0
// prepare to handle disabled action predicate...
disabled = (!disabled && actions[action].isDisabled) ?
actions[action].isDisabled
: disabled
path = path.split(/[\\\/]/g)
var leaf = path.pop()
buildTree(path, leaf, paths[key][0], disabled, tree)
buildTree(path, leaf, action, disabled, tree)
})
//console.log('!!!!', tree)
@ -952,7 +981,12 @@ var BrowseActionsActions = actions.Actions({
&& actions.isToggler && actions.isToggler(cur[0])){
var action = cur[0]
var disabled = cur[1]
// handle disabled predicate...
disabled = disabled instanceof Function ?
disabled.call(actions)
: disabled
var cur_state = actions[action]('?')
var states = actions[action]('??')
@ -1016,6 +1050,11 @@ var BrowseActionsActions = actions.Actions({
var action = cur[key][0]
var disabled = cur[key][1]
// handle disabled predicate...
disabled = disabled instanceof Function ?
disabled.call(actions)
: disabled
// Action: toggler -> add toggle button...
if(actions.isToggler && actions.isToggler(action)){
make(text + '/', {
@ -1627,311 +1666,6 @@ module.WidgetTest = core.ImageGridFeatures.Feature({
//---------------------------------------------------------------------
// XXX move this to a more appropriate place...
var RangeActions = actions.Actions({
// .makeBrace('open')
// .makeBrace('open', image)
// .makeBrace('close')
// .makeBrace('close', image)
//
// XXX this should not be here...
makeBrace: ['- Range/',
function(type, gid){
var cls = type == 'open' ? 'brace-open' : 'brace-close'
var r = this.ribbons.viewer.find('.ribbon')
var brace = this.ribbons.getRibbon(gid).find('.mark.'+cls)
if(brace.length == 0){
brace = $('<span>')
.addClass('mark brace '+cls)
} else if(brace.length > 1){
brace = brace.detach().first()
}
brace
.attr('gid', gid)
this.ribbons.getImage(gid)[type == 'open' ? 'before' : 'after'](brace)
// XXX this does not work for non-current images ...
this.ribbons.preventTransitions(r)
// XXX is this correct here???
this.focusImage()
this.ribbons.restoreTransitions(r)
}],
// XXX add "brace off screen" indicators....
updateRangeIndicators: ['- Range/',
function(){
var update = false
var range = this.data.__range
// XXX not sure if this sweeping action is the right way to
// go but it sure makes things simpler...
if(range == null){
update = true
this.ribbons.viewer
.find('.ribbon .mark.brace')
.remove()
} else {
var that = this
this.data.ribbon_order.forEach(function(r){
var a = that.data.getImage(range[0], 'after', r)
var b = that.data.getImage(range[1], 'before', r)
// only draw braces if some part of the ribbon is
// in range...
if(a != null && b != null){
that
.makeBrace('open', a)
.makeBrace('close', b)
// remove braces from ribbon...
} else {
update = true
that.ribbons.getRibbon(r)
.find('.mark.brace')
.remove()
}
})
}
if(update){
var r = this.ribbons.viewer.find('.ribbon')
// XXX this does not work for non-current images ...
this.ribbons.preventTransitions(r)
// XXX is this correct here???
this.focusImage()
this.ribbons.restoreTransitions(r)
}
}],
clearRange: ['Range/Clear range',
function(image){
var r = this.ribbons.viewer.find('.ribbon')
delete this.data.__range
this.updateRangeIndicators()
}],
// procedure:
// - set brace
// when no braces set:
// - sets two braces around target image
// When a brace is set:
// - check brace orientation and set open/close to target
// - update braces on all ribbons
setRangeBorder: ['Range/Set range border',
function(image, type){
var image = this.data.getImage(image)
var range = this.data.__range = this.data.__range || []
// no range...
if(range.length == 0){
range.push(image)
range.push(image)
// range set...
} else {
var a = this.data.getImageOrder(range[0])
var b = this.data.getImageOrder(range[1])
var t = this.data.getImageOrder(image)
var i =
// type/range conflict...
type == 'close' && t < a ? null
: type == 'open' && t > b ? null
// extend left/right...
: t <= a ? 0
: t >= b ? 1
// set left/right limit...
: type == 'open' ? 0
: type == 'close' ? 1
// narrow to the closest brace...
: a - t < b - t ? 0
: 1
if(i == null){
return
}
range[i] = image
}
this.updateRangeIndicators()
}],
openRange: ['Range/Open range',
function(image){ this.setRangeBorder(image, 'open') }],
closeRange: ['Range/Close range',
function(image){ this.setRangeBorder(image, 'close') }],
cropRange: ['Range|Crop/Crop range',
function(){
var range = this.data.__range
var order = this.data.order
range
&& this.crop(order.slice(
order.indexOf(range[0]),
order.indexOf(range[1])+1))
}],
cropRangeOut: ['Range|Crop/Crop out range',
function(){
var range = this.data.__range
var order = this.data.order
range
&& this.crop(order
.slice(0, order.indexOf(range[0])-1)
.concat(order.slice(order.indexOf(range[1])+1)))
}],
})
var Range =
module.Range = core.ImageGridFeatures.Feature({
title: '',
doc: '',
tag: 'ui-range',
depends: [
'ui',
],
actions: RangeActions,
handlers: [
[[
'crop',
'reload',
],
function(){ this.updateRangeIndicators() }],
['updateImage',
function(_, gid){
var range = this.data.__range
if(this.ribbons && range && gid){
var r = this.data.getRibbon(gid)
var a = gid == range[0] ?
this.makeBrace('open', gid)
: this.data.getImage(range[0], 'after', r)
var b = gid == range[1] ?
this.makeBrace('close', gid)
: this.data.getImage(range[1], 'before', r)
if(a != null && b != null){
gid == a
&& this.makeBrace('open', gid)
gid == b
&& this.makeBrace('close', gid)
}
}
}],
['shiftImage.pre',
function(gid){
var range = this.data.__range
if(this.ribbons && range){
this.ribbons.getImageMarks(gid).filter('.brace').remove()
return function(){
this.updateRangeIndicators()
}
}
}],
// show/hide off-screen indicators...
// XXX STUB...
[[
'focusImage',
'setScale',
'updateRangeIndicators',
],
function(_, gid){
gid = gid || this.current
var that = this
var locator = this.ribbons.getRibbonLocator()
var range = this.data.__range
if(!this.ribbons || !range){
locator.find('.range-offscreen-indicator').remove()
return
}
var Wr = this.ribbons.viewer.width()
var W = (Wr / this.scale) / 2
var _make = function(gid, ribbon, direction){
var t = ribbon[0].offsetTop
var h = ribbon[0].offsetHeight / 2
var css = {}
// XXX STUB...
css = {
display: 'none',
position: 'fixed',
width: '100px',
height: '100px',
marginTop: '-50px',
background: 'blue',
zIndex: 9000,
transition: 'all 0.1s ease',
}
var indicator = locator
.find('.range-offscreen-indicator.'+direction+'[gid="'+gid+'"]')
if(direction == 'left'){
var brace = ribbon.find('.mark.brace-open')
if(brace.length == 0 || brace.offset().left >= 0){
return indicator.remove()
}
} else if(direction == 'right'){
var brace = ribbon.find('.mark.brace-close')
if(brace.length == 0 || brace.offset().left < Wr){
return indicator.remove()
}
}
if(indicator.length == 0){
locator.append($('<div>')
.addClass('range-offscreen-indicator '+direction)
.attr('gid', gid))
}
css.left = (direction == 'left'? -W : W-100) + 'px'
css.top = (t + h) + 'px'
return indicator
.css(css)
.show(0)
}
setTimeout(function(){
that.data.ribbon_order.forEach(function(gid){
var ribbon = that.ribbons.getRibbon(gid)
_make(gid, ribbon, 'left')
_make(gid, ribbon, 'right')
})
}, 400)
}],
],
})
/**********************************************************************
* vim:set ts=4 sw=4 : */ return module })

View File

@ -23,7 +23,7 @@
"glob": "^4.0.6",
"guarantee-events": "^1.0.0",
"ig-features": "^2.0.0",
"ig-actions": "^1.0.0",
"ig-actions": "^1.8.0",
"ig-object": "^1.0.1",
"openseadragon": "^2.1.0",
"requirejs": "^2.1.23",

View File

@ -115,8 +115,6 @@ $(function(){
//'-commandline',
//'-ui-partial-ribbons',
'-ui-clickable',
// XXX BUG: disabling features on this level does not
// work, yet works deeper down...
// Example: