diff --git a/ui/TODO.otl b/ui/TODO.otl
index 766c1f4e..0e5cd935 100755
--- a/ui/TODO.otl
+++ b/ui/TODO.otl
@@ -111,57 +111,6 @@ Roadmap
[_] 32% Gen 3 current todo
[_] 65% High priority
[_] make buildcache sort images via data AND file name...
- [X] BUG: sorting (dialog) will mess up the order...
- | Procedure:
- | - shift-s + sort in a way that changes the order
- | - move next till the spot where the order changed
- | -- the next/prev action will jump around...
- |
- | probably due to a bug in reloading...
- |
- | NOTE: sorting is done correctly...
- |
- | Workaround:
- | - sort, save, then F5
- |
- [X] BUG: sorting breaks when at or near the end of a ribbon...
- |
- | Race condition...
- |
- | Procedure:
- | - go to end of a ribbon
- | - shift-s
- | - select a sort method
- | - click "OK"
- |
- | NOTE: this will not break of sorting will not change the order of
- | visible images...
- | thus, if this the above procedure does not break do one of:
- | - ctrl-r (reverse) before sorting
- | - check "Descending" in the sort dialog
- | NOTE: this breaks because current the current image is not
- | yet loaded/created when reloadViewer(..) tries to focus it...
- |
- | Temporary workaround:
- | because there is nothing wrong with sorting itself, just the
- | UI, the resulting state can be fixed by simply reloading the
- | viewer (reloadViewer(true) or ctrl-alt-r)
- |
- | NOTE: appears to affect beginning of the ribbon too...
- [X] BUG: sorting mis-aligns ribbons in some cases...
- | Example:
- | oooo... --[reverse]-> ...oooo
- | ...oooo[o]oooo... ...oooo[o]oooo...
- |
- | Should be:
- | oooo... --[reverse]-> ...oooo
- | ...oooo[o]oooo... ...oooo[o]oooo...
- |
- | The above can happen when, for example, sorting the images via data
- | and then sorting them in the same way with reverse checked...
- |
- | XXX is this related to?
- | BUG: sorting breaks when at or near the end of a ribbon...
[_] BUG: panels: open/close events get triggered on panel drag/sort...
[_] buildcache: add option to control image sort...
[X] buildcache: add ability to process multiple dirs...
@@ -561,6 +510,57 @@ Roadmap
| drops to last placeholder
|
[_] single image mode transition (alpha-blend/fade/none)
+ [X] BUG: sorting (dialog) will mess up the order...
+ | Procedure:
+ | - shift-s + sort in a way that changes the order
+ | - move next till the spot where the order changed
+ | -- the next/prev action will jump around...
+ |
+ | probably due to a bug in reloading...
+ |
+ | NOTE: sorting is done correctly...
+ |
+ | Workaround:
+ | - sort, save, then F5
+ |
+ [X] BUG: sorting breaks when at or near the end of a ribbon...
+ |
+ | Race condition...
+ |
+ | Procedure:
+ | - go to end of a ribbon
+ | - shift-s
+ | - select a sort method
+ | - click "OK"
+ |
+ | NOTE: this will not break of sorting will not change the order of
+ | visible images...
+ | thus, if this the above procedure does not break do one of:
+ | - ctrl-r (reverse) before sorting
+ | - check "Descending" in the sort dialog
+ | NOTE: this breaks because current the current image is not
+ | yet loaded/created when reloadViewer(..) tries to focus it...
+ |
+ | Temporary workaround:
+ | because there is nothing wrong with sorting itself, just the
+ | UI, the resulting state can be fixed by simply reloading the
+ | viewer (reloadViewer(true) or ctrl-alt-r)
+ |
+ | NOTE: appears to affect beginning of the ribbon too...
+ [X] BUG: sorting mis-aligns ribbons in some cases...
+ | Example:
+ | oooo... --[reverse]-> ...oooo
+ | ...oooo[o]oooo... ...oooo[o]oooo...
+ |
+ | Should be:
+ | oooo... --[reverse]-> ...oooo
+ | ...oooo[o]oooo... ...oooo[o]oooo...
+ |
+ | The above can happen when, for example, sorting the images via data
+ | and then sorting them in the same way with reverse checked...
+ |
+ | XXX is this related to?
+ | BUG: sorting breaks when at or near the end of a ribbon...
[X] BUG: shifting image left/right marks and bookmarks it...
[X] Might be a good idea to use sparse arrays for things like marks...
| eliminate:
diff --git a/ui/files.js b/ui/files.js
index 09d0c62b..0203747d 100755
--- a/ui/files.js
+++ b/ui/files.js
@@ -144,6 +144,8 @@ function statusProgress(msg, tracker){
// Bubble up actions in the deferred chain
//
+// i.e. proxy them form one deferred (from) to the next (to)...
+//
// Will chain progress/notify and if only_progress is not set, also
// done/resolve and fail/reject from "from" to "to" deferred objects.
//
diff --git a/ui/index.html b/ui/index.html
index 64e21914..c73e302c 100755
--- a/ui/index.html
+++ b/ui/index.html
@@ -20,6 +20,7 @@
+
diff --git a/ui/lib/dialogs.js b/ui/lib/dialogs.js
new file mode 100755
index 00000000..01d24d2e
--- /dev/null
+++ b/ui/lib/dialogs.js
@@ -0,0 +1,632 @@
+/**********************************************************************
+*
+*
+*
+**********************************************************************/
+
+//var DEBUG = DEBUG != null ? DEBUG : true
+
+
+/**********************************************************************
+* Modal dialogs...
+*/
+
+/********************************************************* Helpers ***/
+
+// Set element text and tooltip
+//
+// NOTE: when text is a list, we will only use the first and the last
+// elements...
+// NOTE: if tip_elem is not given then both the text and tip will be set
+// on text_elem
+//
+// XXX add support for quoted '|'...
+function setTextWithTooltip(text, text_elem, tip_elem){
+ text_elem = $(text_elem)
+ tip_elem = tip_elem == null ? text_elem : tip_elem
+
+ if(typeof(text) != typeof('str')){
+ tip = text
+ } else {
+ var tip = text.split(/\s*\|\s*/)
+ }
+
+ // set elemnt text...
+ text_elem
+ .html(tip[0])
+
+ // do the tooltip...
+ tip = tip.slice(1)
+ tip = tip[tip.length-1]
+ if(tip != null && tip.trim().length > 0){
+ $(' * ')
+ .attr('tooltip', tip)
+ .appendTo(tip_elem)
+ }
+
+ return text_elem
+}
+
+
+function getOverlay(root){
+ root = $(root)
+ var overlay = root.find('.overlay-block')
+ if(overlay.length == 0){
+ return $('
').appendTo(root)
+ }
+ return overlay
+}
+
+
+function showInOverlay(root, data){
+ root = $(root)
+
+ var overlay = getOverlay(root)
+
+
+ if(data != null){
+ var container = $('')
+ var dialog = container.find('.dialog')
+
+ //overlay.find('.background')
+ // .click(function(){ hideOverlay(root) })
+
+ dialog
+ .append(data)
+ .on('click', function(evt){
+ evt.stopPropagation()
+ })
+ overlay.find('.content')
+ .on('click', function(){
+ overlay.trigger('close')
+ hideOverlay(root)
+ })
+ .on('close accept', function(){
+ //hideOverlay(root)
+ })
+ .append(container)
+ }
+
+ root.addClass('overlay')
+
+ return overlay
+}
+
+
+function hideOverlay(root){
+ root.removeClass('overlay')
+ root.find('.overlay-block')
+ .trigger('close')
+ .remove()
+}
+
+function isOverlayVisible(root){
+ return getOverlay(root).css('display') != 'none'
+}
+
+
+/**********************************************************************
+* Field definitions...
+*/
+
+var FIELD_TYPES = {
+ // a simple hr...
+ //
+ // format:
+ // '---'
+ // Three or more '-'s
+ hr: {
+ type: 'hr',
+ text: null,
+ default: false,
+ html: ' ',
+ test: function(val){
+ return /\-\-\-+/.test(val)
+ },
+ },
+ // a simple br...
+ //
+ // format:
+ // ' '
+ // Three or more spaces
+ br: {
+ type: 'br',
+ text: null,
+ default: false,
+ html: ' ',
+ test: function(val){
+ return /\s\s\s+/.test(val)
+ },
+ },
+ // format:
+ // {
+ // html:
+ // }
+ html: {
+ type: 'html',
+ text: null,
+ default: false,
+ html: '
',
+ test: function(val){
+ return val.html != null
+ },
+ set: function(field, value){
+ if(typeof(value.html) == typeof('str')){
+ field.html(value.html)
+ } else {
+ field.append(value.html)
+ }
+ },
+ },
+
+ // format:
+ // string
+ // XXX add datalist option...
+ // XXX make this textarea compatible...
+ text: {
+ type: 'text',
+ text: null,
+ default: '',
+ html: ''+
+ ' '+
+ ' '+
+ '
',
+ test: function(val){
+ return typeof(val) == typeof('abc')
+ },
+ set: function(field, value){
+ $(field).find('.value').attr('value', value)
+ },
+ get: function(field){
+ return $(field).find('.value').attr('value')
+ },
+ },
+
+ // format:
+ // true | false
+ bool: {
+ type: 'bool',
+ text: null,
+ default: false,
+ html: ''+
+ ' '+
+ ' '+
+ '
',
+ test: function(val){
+ return val === true || val === false
+ },
+ set: function(field, value){
+ if(value){
+ $(field).find('.value').attr('checked', '')
+ } else {
+ $(field).find('.value').removeAttr('checked')
+ }
+ },
+ get: function(field){
+ return $(field).find('.value').attr('checked') == 'checked'
+ },
+ },
+
+ // NOTE: this will not work without node-webkit...
+ // format:
+ // { dir: }
+ dir: {
+ type: 'dir',
+ text: null,
+ default: false,
+ html: ''+
+ ' '+
+ ' '+
+ '
',
+ test: function(val){
+ return typeof(val) == typeof({}) && 'dir' in val
+ },
+ set: function(field, value){
+ field.find('.value').attr('nwworkingdir', value.dir)
+ },
+ get: function(field){
+ var f = $(field).find('.value')[0].files
+ if(f.length == 0){
+ return ''
+ }
+ return f[0].path
+ },
+ },
+
+ // NOTE: this will not work without node-webkit...
+ // format:
+ // { dir: }
+ // XXX add datalist option...
+ ndir: {
+ type: 'ndir',
+ text: null,
+ default: false,
+ html: ''+
+ ' '+
+ ' '+
+ 'Browse '+
+ '
',
+ test: function(val){
+ return typeof(val) == typeof({}) && 'ndir' in val
+ },
+ set: function(field, value){
+ var that = this
+
+ // NOTE: we are attaching the file browser to body to avoid
+ // click events on it closing the dialog...
+ // ...for some reason stopPropagation(...) does not do
+ // the job...
+ var file = $(' ')
+ .attr('nwworkingdir', value.ndir)
+ .change(function(){
+ var p = file[0].files
+ if(p.length != 0){
+ field.find('.path').val(p[0].path)
+ }
+ file.detach()
+ // focus+select the path field...
+ // NOTE: this is here to enable fast select-open
+ // keyboard cycle (tab, enter, ,
+ // enter, enter)...
+ field.find('.path')
+ .focus()
+ .select()
+ })
+ .hide()
+ field.find('.path').val(value.ndir)
+
+ field.find('.browse').click(function(){
+ file
+ // load user input path...
+ .attr('nwworkingdir', field.find('.path').val())
+ .appendTo($('body'))
+ .click()
+ })
+
+ },
+ get: function(field){
+ return field.find('.path').val()
+ },
+ },
+
+ // format:
+ // ['a', 'b', 'c', ...]
+ //
+ // an item can be of the folowing format:
+ // ['|' 'default' | 'disabled' ] [ '|' ]
+ //
+ // NOTE: only one 'default' item should be present.
+ // NOTE: if no defaults are set, then the first item is checked.
+ choice: {
+ type: 'choice',
+ text: null,
+ default: false,
+ html: ''+
+ '
'+
+ '
'+
+ ' '+
+ ' '+
+ '
'+
+ '
',
+ test: function(val){
+ return typeof(val) == typeof([]) && val.constructor.name == 'Array'
+ },
+ set: function(field, value){
+ var t = field.find('.text').html()
+ t = t == '' ? Math.random()+'' : t
+ var item = field.find('.item').last()
+ for(var i=0; i < value.length; i++){
+ // get options...
+ var opts = value[i]
+ .split(/\|/g)
+ .map(function(e){ return e.trim() })
+
+ var val = item.find('.value')
+ val.val(opts[0])
+
+ // set checked state...
+ if(opts.slice(1).indexOf('default') >= 0){
+ val.prop('checked', true)
+ opts.splice(opts.indexOf('default'), 1)
+ } else {
+ val.prop('checked', false)
+ }
+
+ // set disabled state...
+ if(opts.slice(1).indexOf('disabled') >= 0){
+ val.prop('disabled', true)
+ opts.splice(opts.indexOf('disabled'), 1)
+ item.addClass('disabled')
+ } else {
+ val.prop('disabled', false)
+ item.removeClass('disabled')
+ }
+
+ setTextWithTooltip(opts, item.find('.item-text'))
+
+ item.appendTo(field)
+
+ item = item.clone()
+ }
+ var values = field.find('.value')
+ .attr('name', t)
+ // set the default...
+ if(values.filter(':checked:not([disabled])').length == 0){
+ values.filter(':not([disabled])').first()
+ .prop('checked', true)
+ }
+ },
+ get: function(field){
+ return $(field).find('.value:checked').val()
+ },
+ },
+
+ // format:
+ // {
+ // select: ['a', 'b', 'c', ...]
+ // // default option (optional)...
+ // default: |
+ // }
+ select: {
+ type: 'select',
+ text: null,
+ default: false,
+ html: ''+
+ ' '+
+ ''+
+ ' '+
+ ' '+
+ '
',
+ test: function(val){
+ return 'select' in val
+ },
+ set: function(field, value){
+ var t = field.find('.text').text()
+ var item = field.find('.option').last()
+ var select = field.find('select')
+ for(var i=0; i < value.select.length; i++){
+ item
+ .html(value.select[i])
+ .val(value.select[i])
+ item.appendTo(select)
+
+ item = item.clone()
+ }
+ if(value.default != null){
+ if(typeof(value.default) == typeof(123)){
+ field.find('.option')
+ .eq(value.default)
+ .attr('selected', '')
+ } else {
+ field.find('.option[value="'+ value.default +'"]')
+ .attr('selected', '')
+ }
+ }
+ },
+ get: function(field){
+ return $(field).find('.option:selected').val()
+ },
+ },
+
+ // NOTE: a button can have state...
+ // format:
+ // {
+ // // click event handler...
+ // button: ,
+ // // optional, button text (default 'OK')...
+ // text: ,
+ // // optional, initial state setup...
+ // default: ,
+ // }
+ button: {
+ type: 'button',
+ text: null,
+ default: false,
+ html: ''+
+ ' '+
+ ' '+
+ '
',
+ test: function(val){
+ return 'button' in val
+ },
+ set: function(field, value){
+ var btn = $(field).find('button')
+ .click(value.button)
+ .html(value.text == null ? 'OK' : value.text)
+ if('default' in value){
+ value.default(btn)
+ }
+ },
+ get: function(field){
+ return $(field).attr('state')
+ },
+ },
+
+}
+
+
+
+/**********************************************************************
+* Constructors...
+*/
+
+// Show a complex form dialog
+//
+// This will build a form and collect it's data on "accept" specified by
+// the config object...
+//
+// config format:
+// {
+// // simple field...
+// : ,
+//
+// ...
+// }
+//
+// and split in two with a "|" the section before will
+// show as the field text and the text after as the tooltip.
+// Example:
+// "field text | field tooltip..."
+//
+// field's default value determines it's type:
+// bool - checkbox
+// string - textarea
+//
+// see FIELD_TYPES for supported field types.
+//
+// NOTE: if btn is set to false explicitly then no button will be
+// rendered in the form dialog.
+// NOTE: to include a literal "|" in just escape it
+// like this: "\|"
+//
+// XXX add form testing...
+// XXX add undefined field handling/reporting...
+function formDialog(root, message, config, btn, cls){
+ cls = cls == null ? '' : cls
+ btn = btn == null ? 'OK' : btn
+ root = root == null ? $('.viewer') : root
+
+ var form = $('
')
+ var data = {}
+ var res = $.Deferred()
+
+ // handle message and btn...
+ if(message.trim().length > 0){
+ setTextWithTooltip(message, $('
'))
+ .appendTo(form)
+ }
+
+ // build the form...
+ for(var t in config){
+ var did_handling = false
+ for(var f in FIELD_TYPES){
+ if(FIELD_TYPES[f].test(config[t])){
+ var field = FIELD_TYPES[f]
+ var html = $(field.html)
+
+ // setup text and data...
+ setTextWithTooltip(t, html.find('.text'), html)
+
+ if(field.set != null){
+ field.set(html, config[t])
+ }
+
+ if(field.get != null){
+ // NOTE: this is here to isolate t and field.get values...
+ // ...is there a better way???
+ var _ = (function(title, getter){
+ html.on('resolve', function(evt, e){
+ data[title] = getter(e)
+ })
+ })(t, field.get)
+ }
+
+ form.append(html)
+
+ did_handling = true
+ break
+ }
+ }
+
+ // handle unresolved fields...
+ if(!did_handling){
+ console.warn('formDialog: not all fields understood.')
+ // XXX skipping field...
+ // XXX
+ }
+ }
+
+ // add button...
+ if(btn !== false){
+ var button = $(''+btn+' ')
+ form.append(button)
+ } else {
+ var button = null
+ }
+
+ var overlay = showInOverlay(root, form)
+ .addClass('dialog ' + cls)
+ .on('accept', function(){
+ form.find('.field').each(function(_, e){
+ $(e).trigger('resolve', [$(e)])
+ })
+
+ // XXX test if all required stuff is filled...
+ res.resolve(data, form)
+
+ hideOverlay(root)
+ })
+ .on('close', function(){
+ res.reject()
+
+ })
+
+ if(button != null){
+ button.click(function(){
+ overlay.trigger('accept')
+ })
+ }
+
+ // focus an element...
+ // NOTE: if first element is a radio button set, focus the checked
+ // element, else focus the first input...
+ form.ready(function(){
+ // NOTE: we are using a timeout to avoid the user input that opened
+ // the dialog to end up in the first field...
+ setTimeout(function(){
+ var elem = form.find('.field input').first()
+ if(elem.attr('type') == 'radio'){
+ form.find('.field input:checked')
+ .focus()
+ .select()
+ } else {
+ elem
+ .focus()
+ .select()
+ }
+ }, 100)
+ })
+
+ return res
+}
+
+
+
+/************************************************ Standard dialogs ***/
+// NOTE: these return a deferred that will reflect the state of the
+// dialog, and the progress of the operations that it riggers...
+//
+// XXX might be a good idea to be able to block the ui (overlay + progress
+// bar?) until some long/critical operations finish, to prevent the
+// user from breaking things while the ui is inconsistent...
+
+function alertDialog(){
+ var message = $.makeArray(arguments).join(' ')
+ return formDialog(null, String(message), {}, false, 'alert')
+}
+
+
+function promptDialog(message, dfl, btn){
+ btn = btn == null ? 'OK' : btn
+ var res = $.Deferred()
+ formDialog(null, message, {'': ''+(dfl == null ? '' : dfl)}, btn, 'prompt')
+ .done(function(data){ res.resolve(data['']) })
+ .fail(function(){ res.reject() })
+ return res
+}
+
+
+/*
+function confirmDialog(){
+}
+*/
+
+
+
+/**********************************************************************
+* vim:set ts=4 sw=4 : */
diff --git a/ui/ui.js b/ui/ui.js
index 8689a3e2..602b2a38 100755
--- a/ui/ui.js
+++ b/ui/ui.js
@@ -14,6 +14,7 @@ var STATUS_QUEUE_TIME = 200
var CONTEXT_INDICATOR_UPDATERS = []
+
// this can be:
// - 'floating'
// - 'panel'
@@ -613,617 +614,9 @@ function closeProgressBar(name){
/**********************************************************************
-* Modal dialogs...
+* Dialogs...
*/
-/********************************************************* Helpers ***/
-
-// Set element text and tooltip
-//
-// NOTE: when text is a list, we will only use the first and the last
-// elements...
-// NOTE: if tip_elem is not given then both the text and tip will be set
-// on text_elem
-//
-// XXX add support for quoted '|'...
-function setTextWithTooltip(text, text_elem, tip_elem){
- text_elem = $(text_elem)
- tip_elem = tip_elem == null ? text_elem : tip_elem
-
- if(typeof(text) != typeof('str')){
- tip = text
- } else {
- var tip = text.split(/\s*\|\s*/)
- }
-
- // set elemnt text...
- text_elem
- .html(tip[0])
-
- // do the tooltip...
- tip = tip.slice(1)
- tip = tip[tip.length-1]
- if(tip != null && tip.trim().length > 0){
- $(' * ')
- .attr('tooltip', tip)
- .appendTo(tip_elem)
- }
-
- return text_elem
-}
-
-
-function getOverlay(root){
- root = $(root)
- var overlay = root.find('.overlay-block')
- if(overlay.length == 0){
- return $('').appendTo(root)
- }
- return overlay
-}
-
-
-function showInOverlay(root, data){
- root = $(root)
-
- var overlay = getOverlay(root)
-
-
- if(data != null){
- var container = $('')
- var dialog = container.find('.dialog')
-
- //overlay.find('.background')
- // .click(function(){ hideOverlay(root) })
-
- dialog
- .append(data)
- .on('click', function(evt){
- evt.stopPropagation()
- })
- overlay.find('.content')
- .on('click', function(){
- overlay.trigger('close')
- hideOverlay(root)
- })
- .on('close accept', function(){
- //hideOverlay(root)
- })
- .append(container)
- }
-
- root.addClass('overlay')
-
- return overlay
-}
-
-
-function hideOverlay(root){
- root.removeClass('overlay')
- root.find('.overlay-block')
- .trigger('close')
- .remove()
-}
-
-function isOverlayVisible(root){
- return getOverlay(root).css('display') != 'none'
-}
-
-
-var FIELD_TYPES = {
- // a simple hr...
- //
- // format:
- // '---'
- // Three or more '-'s
- hr: {
- type: 'hr',
- text: null,
- default: false,
- html: ' ',
- test: function(val){
- return /\-\-\-+/.test(val)
- },
- },
- // a simple br...
- //
- // format:
- // ' '
- // Three or more spaces
- br: {
- type: 'br',
- text: null,
- default: false,
- html: ' ',
- test: function(val){
- return /\s\s\s+/.test(val)
- },
- },
- // format:
- // {
- // html:
- // }
- html: {
- type: 'html',
- text: null,
- default: false,
- html: '
',
- test: function(val){
- return val.html != null
- },
- set: function(field, value){
- if(typeof(value.html) == typeof('str')){
- field.html(value.html)
- } else {
- field.append(value.html)
- }
- },
- },
-
- // format:
- // string
- // XXX add datalist option...
- // XXX make this textarea compatible...
- text: {
- type: 'text',
- text: null,
- default: '',
- html: ''+
- ' '+
- ' '+
- '
',
- test: function(val){
- return typeof(val) == typeof('abc')
- },
- set: function(field, value){
- $(field).find('.value').attr('value', value)
- },
- get: function(field){
- return $(field).find('.value').attr('value')
- },
- },
-
- // format:
- // true | false
- bool: {
- type: 'bool',
- text: null,
- default: false,
- html: ''+
- ' '+
- ' '+
- '
',
- test: function(val){
- return val === true || val === false
- },
- set: function(field, value){
- if(value){
- $(field).find('.value').attr('checked', '')
- } else {
- $(field).find('.value').removeAttr('checked')
- }
- },
- get: function(field){
- return $(field).find('.value').attr('checked') == 'checked'
- },
- },
-
- // NOTE: this will not work without node-webkit...
- // format:
- // { dir: }
- dir: {
- type: 'dir',
- text: null,
- default: false,
- html: ''+
- ' '+
- ' '+
- '
',
- test: function(val){
- return typeof(val) == typeof({}) && 'dir' in val
- },
- set: function(field, value){
- field.find('.value').attr('nwworkingdir', value.dir)
- },
- get: function(field){
- var f = $(field).find('.value')[0].files
- if(f.length == 0){
- return ''
- }
- return f[0].path
- },
- },
-
- // NOTE: this will not work without node-webkit...
- // format:
- // { dir: }
- // XXX add datalist option...
- ndir: {
- type: 'ndir',
- text: null,
- default: false,
- html: ''+
- ' '+
- ' '+
- 'Browse '+
- '
',
- test: function(val){
- return typeof(val) == typeof({}) && 'ndir' in val
- },
- set: function(field, value){
- var that = this
-
- // NOTE: we are attaching the file browser to body to avoid
- // click events on it closing the dialog...
- // ...for some reason stopPropagation(...) does not do
- // the job...
- var file = $(' ')
- .attr('nwworkingdir', value.ndir)
- .change(function(){
- var p = file[0].files
- if(p.length != 0){
- field.find('.path').val(p[0].path)
- }
- file.detach()
- // focus+select the path field...
- // NOTE: this is here to enable fast select-open
- // keyboard cycle (tab, enter, ,
- // enter, enter)...
- field.find('.path')
- .focus()
- .select()
- })
- .hide()
- field.find('.path').val(value.ndir)
-
- field.find('.browse').click(function(){
- file
- // load user input path...
- .attr('nwworkingdir', field.find('.path').val())
- .appendTo($('body'))
- .click()
- })
-
- },
- get: function(field){
- return field.find('.path').val()
- },
- },
-
- // format:
- // ['a', 'b', 'c', ...]
- //
- // an item can be of the folowing format:
- // ['|' 'default' | 'disabled' ] [ '|' ]
- //
- // NOTE: only one 'default' item should be present.
- // NOTE: if no defaults are set, then the first item is checked.
- choice: {
- type: 'choice',
- text: null,
- default: false,
- html: ''+
- '
'+
- '
'+
- ' '+
- ' '+
- '
'+
- '
',
- test: function(val){
- return typeof(val) == typeof([]) && val.constructor.name == 'Array'
- },
- set: function(field, value){
- var t = field.find('.text').html()
- t = t == '' ? Math.random()+'' : t
- var item = field.find('.item').last()
- for(var i=0; i < value.length; i++){
- // get options...
- var opts = value[i]
- .split(/\|/g)
- .map(function(e){ return e.trim() })
-
- var val = item.find('.value')
- val.val(opts[0])
-
- // set checked state...
- if(opts.slice(1).indexOf('default') >= 0){
- val.prop('checked', true)
- opts.splice(opts.indexOf('default'), 1)
- } else {
- val.prop('checked', false)
- }
-
- // set disabled state...
- if(opts.slice(1).indexOf('disabled') >= 0){
- val.prop('disabled', true)
- opts.splice(opts.indexOf('disabled'), 1)
- item.addClass('disabled')
- } else {
- val.prop('disabled', false)
- item.removeClass('disabled')
- }
-
- setTextWithTooltip(opts, item.find('.item-text'))
-
- item.appendTo(field)
-
- item = item.clone()
- }
- var values = field.find('.value')
- .attr('name', t)
- // set the default...
- if(values.filter(':checked:not([disabled])').length == 0){
- values.filter(':not([disabled])').first()
- .prop('checked', true)
- }
- },
- get: function(field){
- return $(field).find('.value:checked').val()
- },
- },
-
- // format:
- // {
- // select: ['a', 'b', 'c', ...]
- // // default option (optional)...
- // default: |
- // }
- select: {
- type: 'select',
- text: null,
- default: false,
- html: ''+
- ' '+
- ''+
- ' '+
- ' '+
- '
',
- test: function(val){
- return 'select' in val
- },
- set: function(field, value){
- var t = field.find('.text').text()
- var item = field.find('.option').last()
- var select = field.find('select')
- for(var i=0; i < value.select.length; i++){
- item
- .html(value.select[i])
- .val(value.select[i])
- item.appendTo(select)
-
- item = item.clone()
- }
- if(value.default != null){
- if(typeof(value.default) == typeof(123)){
- field.find('.option')
- .eq(value.default)
- .attr('selected', '')
- } else {
- field.find('.option[value="'+ value.default +'"]')
- .attr('selected', '')
- }
- }
- },
- get: function(field){
- return $(field).find('.option:selected').val()
- },
- },
-
- // NOTE: a button can have state...
- // format:
- // {
- // // click event handler...
- // button: ,
- // // optional, button text (default 'OK')...
- // text: ,
- // // optional, initial state setup...
- // default: ,
- // }
- button: {
- type: 'button',
- text: null,
- default: false,
- html: ''+
- ' '+
- ' '+
- '
',
- test: function(val){
- return 'button' in val
- },
- set: function(field, value){
- var btn = $(field).find('button')
- .click(value.button)
- .html(value.text == null ? 'OK' : value.text)
- if('default' in value){
- value.default(btn)
- }
- },
- get: function(field){
- return $(field).attr('state')
- },
- },
-
-}
-
-// Show a complex form dialog
-//
-// This will build a form and collect it's data on "accept" specified by
-// the config object...
-//
-// config format:
-// {
-// // simple field...
-// : ,
-//
-// ...
-// }
-//
-// and split in two with a "|" the section before will
-// show as the field text and the text after as the tooltip.
-// Example:
-// "field text | field tooltip..."
-//
-// field's default value determines it's type:
-// bool - checkbox
-// string - textarea
-//
-// see FIELD_TYPES for supported field types.
-//
-// NOTE: if btn is set to false explicitly then no button will be
-// rendered in the form dialog.
-// NOTE: to include a literal "|" in just escape it
-// like this: "\|"
-//
-// XXX add form testing...
-// XXX add undefined field handling/reporting...
-function formDialog(root, message, config, btn, cls){
- cls = cls == null ? '' : cls
- btn = btn == null ? 'OK' : btn
- root = root == null ? $('.viewer') : root
-
- var form = $('
')
- var data = {}
- var res = $.Deferred()
-
- // handle message and btn...
- if(message.trim().length > 0){
- setTextWithTooltip(message, $('
'))
- .appendTo(form)
- }
-
- // build the form...
- for(var t in config){
- var did_handling = false
- for(var f in FIELD_TYPES){
- if(FIELD_TYPES[f].test(config[t])){
- var field = FIELD_TYPES[f]
- var html = $(field.html)
-
- // setup text and data...
- setTextWithTooltip(t, html.find('.text'), html)
-
- if(field.set != null){
- field.set(html, config[t])
- }
-
- if(field.get != null){
- // NOTE: this is here to isolate t and field.get values...
- // ...is there a better way???
- var _ = (function(title, getter){
- html.on('resolve', function(evt, e){
- data[title] = getter(e)
- })
- })(t, field.get)
- }
-
- form.append(html)
-
- did_handling = true
- break
- }
- }
-
- // handle unresolved fields...
- if(!did_handling){
- console.warn('formDialog: not all fields understood.')
- // XXX skipping field...
- // XXX
- }
- }
-
- // add button...
- if(btn !== false){
- var button = $(''+btn+' ')
- form.append(button)
- } else {
- var button = null
- }
-
- var overlay = showInOverlay(root, form)
- .addClass('dialog ' + cls)
- .on('accept', function(){
- form.find('.field').each(function(_, e){
- $(e).trigger('resolve', [$(e)])
- })
-
- // XXX test if all required stuff is filled...
- res.resolve(data, form)
-
- hideOverlay(root)
- })
- .on('close', function(){
- res.reject()
-
- })
-
- if(button != null){
- button.click(function(){
- overlay.trigger('accept')
- })
- }
-
- // focus an element...
- // NOTE: if first element is a radio button set, focus the checked
- // element, else focus the first input...
- form.ready(function(){
- // NOTE: we are using a timeout to avoid the user input that opened
- // the dialog to end up in the first field...
- setTimeout(function(){
- var elem = form.find('.field input').first()
- if(elem.attr('type') == 'radio'){
- form.find('.field input:checked')
- .focus()
- .select()
- } else {
- elem
- .focus()
- .select()
- }
- }, 100)
- })
-
- return res
-}
-
-
-
-/************************************************ Standard dialogs ***/
-// NOTE: these return a deferred that will reflect the state of the
-// dialog, and the progress of the operations that it riggers...
-//
-// XXX might be a good idea to be able to block the ui (overlay + progress
-// bar?) until some long/critical operations finish, to prevent the
-// user from breaking things while the ui is inconsistent...
-
-var _alert = alert
-function alert(){
- var message = Array.apply(null, arguments).join(' ')
- return formDialog(null, String(message), {}, false, 'alert')
-}
-
-
-var _prompt = prompt
-function prompt(message, dfl, btn){
- btn = btn == null ? 'OK' : btn
- var res = $.Deferred()
- formDialog(null, message, {'': ''+(dfl == null ? '' : dfl)}, btn, 'prompt')
- .done(function(data){ res.resolve(data['']) })
- .fail(function(){ res.reject() })
- return res
-}
-
-
-/*
-function confirm(){
-}
-*/
-
-
function detailedAlert(text, description, button){
return formDialog(null, '', {'': {
html: $(' ')