Alex A. Naanou 031168bb2f some cleanup and tweaking...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2018-01-14 22:23:51 +03:00

860 lines
22 KiB
JavaScript
Executable File

/**********************************************************************
*
*
*
**********************************************************************/
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
(function(require){ var module={} // make module AMD/node compatible...
/*********************************************************************/
var toggler = require('lib/toggler')
var actions = require('lib/actions')
var features = require('lib/features')
var core = require('features/core')
var widgets = require('features/ui-widgets')
var widget = require('lib/widget/widget')
var browse = require('lib/widget/browse')
var overlay = require('lib/widget/overlay')
var drawer = require('lib/widget/drawer')
var browseWalk = require('lib/widget/browse-walk')
/*********************************************************************/
var ExampleActions = actions.Actions({
config: {
// XXX stuff for togglers...
},
// NOTE: the path / short doc is optional but it is not recommended
// to to omit it unless defining a non-root action...
// XXX should an action be able to overload the doc???
// ...the intuitive thing to do here is make the doc "write-once"
// i.e. once defined it can't be overwritten...
exampleAction: ['Test/Action',
function(){
console.log('>>>', [].slice.call(arguments))
return function(){
console.log('<<<', [].slice.call(arguments)) }}],
exampleActionFull: ['- Test/',
core.doc`Example full action long documentation string
`,
// action attributes...
{},
function(){
// XXX
}],
// a normal method...
exampleMethod: function(){
console.log('example method:', [].slice.call(arguments))
return 'example result'
},
// XXX does not work -- see actions.Actions(..) for details...
exampleAlias: ['Test/Action alias',
'focusImage: "prev"'],
// action constructor for testing...
makeExampleAction: ['- Test/',
function(name){
this[name] = actions.Action.apply(actions.Action, arguments) }],
// promise handling...
//
// also see corresponding Example.handlers
exampleSyncAction: ['- Test/',
//{await: true},
function(t){
return new Promise(function(resolve){
setTimeout(function(){ resolve() }, t || 1000) })
}],
exampleAsyncAction: ['- Test/',
{await: false},
function(t){
return new Promise(function(resolve){
setTimeout(function(){ resolve() }, t || 1000) })
}],
// Togglers...
//
// There are two state change strategies generally used:
// 1) state accessor changes state (this example)
// 2) callbacks change state
//
// XXX add example argument handling...
exampleToggler: ['Test/Example toggler',
core.doc`Example toggler...
A toggler is any function that adheres to the toggler protocol
and (optionally) inherits from toggler.Toggler
toggler.Toggler(..) is also a convenient toggler constructor,
see: .exampleToggler(..) and .exampleTogglerFull(..) as examples
of its use.
General toggler protocol:
Change to the next state...
.exampleToggler()
.exampleToggler('next')
-> state
Change to the previous state...
.exampleToggler('prev')
-> state
Change to specific state...
.exampleToggler(state)
-> state
For bool togglers, set state on/off...
.exampleToggler('on')
.exampleToggler('off')
-> state
Get current state...
.exampleToggler('?')
-> state
Get possible states...
.exampleToggler('??')
-> state
It is also possible to pass an argument to a toggler, the recommended
semantics for this is to change state of the entity passed as argument
a good example would be .toggleMark(..)
Handle state of arg (recommended semantics)...
.exampleToggler(arg, ...)
-> state
Utilities:
Check if an action is a toggler...
.isToggler('exampleToggler')
-> bool
NOTE: it is not required to use toggler.Toggler(..) as constructor
to build a toggler, a simple function that adheres to the above
protocol is enough, though it is recommended to inherit from
toggler.Toggler so as to enable support functionality that
utilizes the protocol...
NOTE: see lib/toggler.js and toggler.Toggler(..) for more details.
`,
toggler.Toggler(null,
// state accessor...
// NOTE: this may get called multiple times per state change.
function(_, state){
// get the state...
// NOTE: this section should have no side-effects nor
// should it affect the state in any way...
if(state == null){
return this.__example_toggler_state || 'none'
// handle state changing...
} else if(state == 'none'){
delete this.__example_toggler_state
} else {
this.__example_toggler_state = state
}
},
// List of states...
// NOTE: this can be a string for bool states and a list for
// togglers with multiple states...
'A')],
exampleTogglerFull: ['Test/Example toggler (full)',
core.doc``,
toggler.Toggler(
// target...
// XXX more docs!
null,
// state accessor...
function(_, state){
// get the state...
if(state == null){
return this.__example_full_toggler_state || 'A'
} else if(state == 'A'){
delete this.__example_full_toggler_state
} else {
this.__example_full_toggler_state = state
}
},
// List of states...
['A', 'B', 'C'],
// pre-callback (optional)
function(){
console.log('Changing state from:', this.exampleTogglerFull('?'))
},
// post-callback...
function(){
console.log('Changing state to:', this.exampleTogglerFull('?'))
})],
// XXX docs...
// XXX BUG? false is not shown in the dialog button...
exampleConfigTogglerMin: ['- Test/',
core.doc`Minimal config toggler...
This will toggle between true and false.
`,
core.makeConfigToggler('example-option-min')],
// XXX docs...
exampleConfigToggler: ['- Test/',
core.makeConfigToggler(
// option name...
'example-option',
// option states...
//
// NOTE: 'none' represents an undefined value, but when
// setting 'none' state, 'none' is explicitly written as
// option value.
// This is done intentionally as deleting the attribute
// can expose the shadowed option value.
['A', 'B', 'C', 'none'],
// post-callback (optional)...
function(state){
console.log('exampleConfigToggler: callback: shifting state to:', state)
})],
// XXX docs...
exampleConfigTogglerFull: ['- Test/',
core.makeConfigToggler(
// option name...
'example-option-full',
// option states...
function(){
return ['A', 'B', 'C', 'none']
},
// pre-callback...
function(state){
if(state == 'C'){
console.log('exampleConfigToggler: pre-callback: preventing shift to:', state)
// we can prevent a state change by returning false...
// XXX should we be able to return a different state here???
return false
}
console.log('exampleConfigToggler: pre-callback: shifting state to:', state)
},
// post-callback...
function(state){
console.log('exampleConfigToggler: post-callback: shifting state to:', state)
})],
// XXX event and event use...
// XXX inner/outer action...
})
var Example =
module.Example = core.ImageGridFeatures.Feature({
title: '',
doc: '',
tag: 'action-examples',
depends: [
],
actions: ExampleActions,
// XXX make this not applicable in production...
handlers: [
['exampleAsyncAction.pre exampleSyncAction.pre',
function(){
console.log('PRE')
return function(){
console.log('POST') }
}],
],
})
//---------------------------------------------------------------------
var ExampleUIActions = actions.Actions({
// XXX move this to a ui-dependant feature...
exampleCSSClassToggler: ['- Test/',
function(){
}],
exampleActionDisabled: ['Test/$Disabled example action',
{browseMode: function(){ return 'disabled' }},
function(){
console.log('Disabled action called:', [].slice.call(arguments)) }],
// Usage Examples:
// .exampleDrawer() - show html in base drawer...
// .exampleDrawer('Header', 'paragraph') - show html with custom text...
// .exampleDrawer('Overlay') - show html in overlay...
// .exampleDrawer('Overlay', 'Header', 'paragraph')
// - show html in overlay with
// custom text...
exampleDrawer: ['Test/99: D$rawer widget example...',
widgets.makeUIDialog('Drawer',
function(h, txt){
return $('<div>')
.css({
position: 'relative',
background: 'white',
height: '300px',
padding: '20px',
})
.append($('<h1>')
.text(h || 'Drawer example...'))
.append($('<p>')
.text(txt || 'With some text.'))
},
// pass custom configuration to container...
{
background: 'white',
focusable: true,
})],
// XXX show new features...
exampleBrowse: ['Test/-99: Demo $new style dialog...',
widgets.makeUIDialog(function(){
var actions = this
console.log('>>> args:', [].slice.call(arguments))
return browse.makeLister(null, function(path, make){
var that = this
make('select last')
.on('open', function(){
that.select(-1)
})
make('do nothing')
.addClass('selected')
make('nested dialog...',
{
shortcut_key: 'n',
})
.on('open', function(){
actions.exampleBrowse()
})
make('---')
make('$close parent')
.on('open', function(){
that.parent.close()
})
make('...')
// NOTE: the dialog's .parent is not yet set at this point...
// This will finalize the dialog...
//
// NOTE: this is not needed here as the dialog is drawn
// on sync, but for async dialogs this will align
// the selected field correctly.
make.done()
})
// NOTE: this is not a dialog event, it is defined by the
// container to notify us that we are closing...
.on('close', function(){
console.log('Dialog closing...')
})
})],
exampleBrowseCloud: ['Test/Demo $cloud dialog...',
widgets.makeUIDialog(function(){
var actions = this
console.log('>>> args:', [].slice.call(arguments))
return browse.makeLister(null, function(path, make){
var that = this
var words = 'Lorem ipsum dolor sit amet, audiam sensibus '
+'an mea. Accusam blandit ius in, te magna dolorum '
+'moderatius pro, sit id dicant imperdiet definiebas. '
+'Ad duo quod mediocrem, movet laudem discere te mel, '
+'sea ipsum habemus gloriatur at. Sonet prodesset '
+'democritum in vis, brute vitae recusabo pri ad, '
+'--- '
+'latine civibus efficiantur at his. At duo lorem '
+'legimus, errem constituam contentiones sed ne, '
+'cu has corpora definitionem.'
var res = []
words
.split(/\s+/g)
.unique()
.forEach(function(c){
var e = make(c)
// toggle opacity...
.on('open', function(){
var e = $(this).find('.text')
e.css('opacity',
e.css('opacity') == 0.3 ? '' : 0.3)
})
res.push(e[0])
})
$(res).parent()
.append($('<div>')
.sortable()
.append($(res)))
make.done()
},
// make the dialog a cloud...
{ cloudView: true })
// NOTE: this is not a dialog event, it is defined by the
// container to notify us that we are closing...
.on('close', function(){
console.log('Dialog closing...')
})
})],
exampleBrowsrItems: ['Test/-99: Demo browse $items...',
widgets.makeUIDialog(function(){
var actions = this
var editable_list = ['x', 'y', 'z']
return browse.makeLister(null, function(path, make){
var that = this
make.Heading('Heading:', {
doc: 'Heading doc string...',
})
make.Group([
make('Normal item'),
// this is the same as make('...')
make.Separator(),
make.Editable('Editable (Select to edit)'),
make.Editable('Editable (Enter to edit, cleared)...', {
start_on: 'open',
clear_on_edit: true,
}),
])
make.Heading('List:')
make.List(['a', 'b', 'c'])
make.Heading(' Editable list:')
make.EditableList(editable_list)
make.Heading('More:')
make.Action('Editable list demos...')
.on('open', function(){ actions.exampleList() })
make.Action('Pinned list demo...')
.on('open', function(){ actions.examplePinnedList() })
// NOTE: the dialog's .parent is not yet set at this point...
// This will finalize the dialog...
//
// NOTE: this is not needed here as the dialog is drawn
// on sync, but for async dialogs this will align
// the selected field correctly.
make.done()
})
// NOTE: this is not a dialog event, it is defined by the
// container to notify us that we are closing...
.on('close', function(){
})
})],
exampleList: ['Test/-99: Demo $lists editors in dialog...',
widgets.makeUIDialog(function(){
var actions = this
// NOTE: passing things other than strings into a list editor
// is not supported...
var numbers = ['1', '2', '3', '4']
var letters = ['a', 'b', 'c', 'd']
return browse.makeLister(null, function(path, make){
var that = this
make.Heading('Numbers:', {
doc: 'List editor with all the buttons enabled...',
})
make.EditableList(numbers, {
list_id: 'numbers',
item_order_buttons: true,
to_top_button: true,
to_bottom_button: true,
})
make.Heading('Letters:', {
doc: 'Sortable list, use sort handle to the right to sort...'
})
make.EditableList(letters, {
list_id: 'letters',
sortable: 'y',
})
// NOTE: the dialog's .parent is not yet set at this point...
// This will finalize the dialog...
//
// NOTE: this is not needed here as the dialog is drawn
// on sync, but for async dialogs this will align
// the selected field correctly.
make.done()
})
// NOTE: this is not a dialog event, it is defined by the
// container to notify us that we are closing...
.on('close', function(){
console.log(core.doc`Lists:
- Numbers: ${numbers.join(', ')}
- Letters: ${letters.join(', ')}`)
})
})],
examplePinnedList: ['Test/-99: Demo $pinned lists in dialog...',
widgets.makeUIDialog(function(){
var actions = this
// NOTE: passing things other than strings into a list editor
// is not supported...
var pins = ['b', 'a']
var letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
return browse.makeLister(null, function(path, make){
var that = this
make.Heading('Numbers:', {
doc: 'List editor with all the buttons enabled...',
})
make.EditablePinnedList(letters, pins, {
list_id: 'letters',
//pins_sortable: false,
})
// NOTE: the dialog's .parent is not yet set at this point...
// This will finalize the dialog...
//
// NOTE: this is not needed here as the dialog is drawn
// on sync, but for async dialogs this will align
// the selected field correctly.
make.done()
})
// NOTE: this is not a dialog event, it is defined by the
// container to notify us that we are closing...
.on('close', function(){
console.log(core.doc`Lists:
- Pins: ${pins.join(', ')}
- Letters: ${letters.join(', ')}`)
})
})],
exampleProgress: ['Test/Demo $progress bar...',
function(text){
var done = 0
var max = 10
var text = text || 'Progress bar demo'
var that = this
this.showProgress(text)
var step = function(){
that.showProgress(text, done++, max)
max = done < 8 ? max + 5
: max <= 50 && done < 30 ? max + 2
: done > 30 ? max - 3
: max
// NOTE: we add 10 here to compensate for changing max value...
done < max+10
&& setTimeout(step, 200)
}
setTimeout(step, 1000)
}],
// XXX make this a toggler....
partitionByMonth: ['Test/',
function(){
var that = this
this.toggleImageSort('?') != 'Date' && this.sortImages('Date')
this.on('updateImage', function(_, gid){ this.placeMonthPartition(gid) })
}],
// XXX this should be .updateImage(..) in a real feature...
placeMonthPartition: ['Test/',
function(image){
var month = [
'January', 'February', 'March', 'April',
'May', 'June', 'July', 'August',
'September', 'October', 'November', 'December'
]
var gid = this.data.getImage(image)
var next = this.data.getImage(gid, 'next')
cur = this.images[gid]
next = this.images[next]
if(cur && next && cur.birthtime.getMonth() != next.birthtime.getMonth()){
this.ribbons.getImageMarks(gid).filter('.partition').remove()
this.ribbons.getImage(gid)
.after(this.ribbons.elemGID($('<div class="mark partition">'), gid)
.attr('text', month[next.birthtime.getMonth()]))
}
}],
exampleEmbededLister: ['Test/50: Lister example (embeded)/*',
function(path, make){
make('a/')
make('b/')
make('c/')
}],
exampleFloatingLister: ['Test/50:Lister example (floating)...',
function(path){
// we got an argument and can exit...
if(path){
console.log('PATH:', path)
return
}
// load the UI...
var that = this
var list = function(path, make){
make('a/')
make('b/')
make('c/')
}
var o = overlay.Overlay(this.dom,
browse.makePathList(null, {
'a/*': list,
'b/*': list,
'c/*': list,
})
.open(function(evt, path){
o.close()
that.exampleFloatingLister(path)
}))
return o
}],
// XXX BUG: right limit indicator can get covered by the scrollbar...
// XXX migrate to the dialog framework...
// XXX use this.dom as base...
// XXX BUG: this breaks keyboard handling when closed...
showTaggedInDrawer: ['- Test/Show tagged in drawer',
function(tag){
tag = tag || 'bookmark'
var that = this
var H = '200px'
var viewer = $('<div class="viewer">')
.css({
height: H,
background: 'black',
})
.attr('tabindex', '0')
// XXX use this.dom as base...
// XXX when using viewer zoom and other stuff get leaked...
var widget = drawer.Drawer($('body'),
$('<div>')
.addClass('image-list-widget')
.css({
position: 'relative',
height: H,
})
.append(viewer),
{
focusable: true,
})
.on('close', function(){
if(that.nested){
that.nested.stop()
delete that.nested
}
})
var data = this.data.crop(this.data.getTaggedByAll(tag), true)
// setup the viewer...
this.nested = core.ImageGridFeatures
// setup actions...
.setup([
'imagegrid-ui-preview',
])
.run(function(){
this.close = function(){ widget.close() }
this.config['keyboard-event-source'] = viewer
// XXX hack -- need a better way to set this (a setter?)...
this.__keyboard_config = {
'Basic Control': {
pattern: '*',
Home: 'firstImage!',
End: 'lastImage!',
Left: 'prevImage!',
ctrl_Left: 'prevScreen!',
meta_Left: 'prevScreen!',
PgUp: 'prevScreen!',
PgDown: 'nextScreen!',
Right: 'nextImage!',
ctrl_Right: 'nextScreen!',
meta_Right: 'nextScreen!',
Esc: 'close!',
},
}
})
// load some testing data...
.load({
viewer: viewer,
data: data,
images: this.images,
})
.fitImage(1)
// XXX for some reason this is not called...
.refresh()
// link navigation...
.on('focusImage', function(){
that.focusImage(this.current) })
// start things up...
.start()
.focusImage()
// XXX need to focus widget -- use a real trigger event instead of timer...
setTimeout(function(){
that.nested.dom.focus()
}, 200)
return this.nested
}],
showBookmarkedInDrawer: ['Test/Show bookmarked in drawer',
function(){ this.showTaggedInDrawer('bookmark') }],
showSelectedInDrawer: ['Test/Show selected in drawer',
function(){ this.showTaggedInDrawer('selected') }],
makePartitionAfter: ['Test/Make Partition after image',
function(image, text){
var gid = this.data.getImage(image || 'current')
var attrs = {
gid: gid
}
if(text){
attrs.text = text
}
this.ribbons.getImage(gid)
.after($('<span>')
.addClass('mark partition')
.attr(attrs))
}],
// Combined dialog/lister...
//
// XXX should this be made into a constructor???
exampleDialogLister: ['Test/Combined dialog & lister (dialog mode)',
widgets.makeUIDialog(function(path, make){
var makeList = function(_, make){
make('A')
make('B')
make('C')
}
return make instanceof Function ?
makeList(path, make)
: browse.makeLister(null, makeList)
})],
exampleDialogListerL: ['Test/Combined dialog & lister (lister mode)/*',
'exampleDialogLister: ...'],
testList: ['Test/List/*',
function(path, make){ return function(){
make('A')
make('B')
make('C')
} }],
})
var ExampleUI =
module.ExampleUI = core.ImageGridFeatures.Feature({
title: '',
doc: '',
tag: 'ui-action-examples',
depends: [
'ui-browse-actions',
],
suggested: [
'ui-action-examples-2',
],
actions: ExampleUIActions,
handlers: [
],
})
//---------------------------------------------------------------------
var ExampleUI2Actions = actions.Actions({
testList: ['Test/List/*',
function(path, make){ return function(){
make('---')
make('X')
make('Y')
make('Z')
} }],
})
var ExampleUI =
module.ExampleUI = core.ImageGridFeatures.Feature({
title: '',
doc: '',
tag: 'ui-action-examples-2',
depends: [
'ui-action-examples',
],
actions: ExampleUI2Actions,
handlers: [
],
})
//---------------------------------------------------------------------
core.ImageGridFeatures.Feature('examples', [
'action-examples',
'ui-action-examples',
])
/**********************************************************************
* vim:set ts=4 sw=4 : */ return module })