mirror of
https://github.com/flynx/ImageGrid.git
synced 2025-10-29 10:20:08 +00:00
split out the generic dialogs.js + some minor tweaks...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
4ead136b4d
commit
88d1bcc4ba
102
ui/TODO.otl
102
ui/TODO.otl
@ -111,57 +111,6 @@ Roadmap
|
|||||||
[_] 32% Gen 3 current todo
|
[_] 32% Gen 3 current todo
|
||||||
[_] 65% High priority
|
[_] 65% High priority
|
||||||
[_] make buildcache sort images via data AND file name...
|
[_] 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...
|
[_] BUG: panels: open/close events get triggered on panel drag/sort...
|
||||||
[_] buildcache: add option to control image sort...
|
[_] buildcache: add option to control image sort...
|
||||||
[X] buildcache: add ability to process multiple dirs...
|
[X] buildcache: add ability to process multiple dirs...
|
||||||
@ -561,6 +510,57 @@ Roadmap
|
|||||||
| drops to last placeholder
|
| drops to last placeholder
|
||||||
|
|
|
|
||||||
[_] single image mode transition (alpha-blend/fade/none)
|
[_] 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] BUG: shifting image left/right marks and bookmarks it...
|
||||||
[X] Might be a good idea to use sparse arrays for things like marks...
|
[X] Might be a good idea to use sparse arrays for things like marks...
|
||||||
| eliminate:
|
| eliminate:
|
||||||
|
|||||||
@ -144,6 +144,8 @@ function statusProgress(msg, tracker){
|
|||||||
|
|
||||||
// Bubble up actions in the deferred chain
|
// 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
|
// Will chain progress/notify and if only_progress is not set, also
|
||||||
// done/resolve and fail/reject from "from" to "to" deferred objects.
|
// done/resolve and fail/reject from "from" to "to" deferred objects.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
<script src="lib/jli.js"></script>
|
<script src="lib/jli.js"></script>
|
||||||
<script src="lib/keyboard.js"></script>
|
<script src="lib/keyboard.js"></script>
|
||||||
<script src="lib/scroller.js"></script>
|
<script src="lib/scroller.js"></script>
|
||||||
|
<script src="lib/dialogs.js"></script>
|
||||||
<script src="lib/panels.js"></script>
|
<script src="lib/panels.js"></script>
|
||||||
<script src="lib/editor.js"></script>
|
<script src="lib/editor.js"></script>
|
||||||
|
|
||||||
|
|||||||
632
ui/lib/dialogs.js
Executable file
632
ui/lib/dialogs.js
Executable file
@ -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){
|
||||||
|
$('<span class="tooltip-icon tooltip-right"> *</span>')
|
||||||
|
.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 $('<div class="overlay-block">'+
|
||||||
|
'<div class="background"/>'+
|
||||||
|
'<div class="content"/>'+
|
||||||
|
'</div>').appendTo(root)
|
||||||
|
}
|
||||||
|
return overlay
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function showInOverlay(root, data){
|
||||||
|
root = $(root)
|
||||||
|
|
||||||
|
var overlay = getOverlay(root)
|
||||||
|
|
||||||
|
|
||||||
|
if(data != null){
|
||||||
|
var container = $('<table width="100%" height="100%"><tr><td align="center" valign="center">'+
|
||||||
|
'<div class="dialog"/>'+
|
||||||
|
'</td></tr></table>')
|
||||||
|
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: '<hr>',
|
||||||
|
test: function(val){
|
||||||
|
return /\-\-\-+/.test(val)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// a simple br...
|
||||||
|
//
|
||||||
|
// format:
|
||||||
|
// ' '
|
||||||
|
// Three or more spaces
|
||||||
|
br: {
|
||||||
|
type: 'br',
|
||||||
|
text: null,
|
||||||
|
default: false,
|
||||||
|
html: '<br>',
|
||||||
|
test: function(val){
|
||||||
|
return /\s\s\s+/.test(val)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// format:
|
||||||
|
// {
|
||||||
|
// html: <html-block>
|
||||||
|
// }
|
||||||
|
html: {
|
||||||
|
type: 'html',
|
||||||
|
text: null,
|
||||||
|
default: false,
|
||||||
|
html: '<div class="html-block"/>',
|
||||||
|
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: '<div class="field string">'+
|
||||||
|
'<span class="text"></span>'+
|
||||||
|
'<input type="text" class="value">'+
|
||||||
|
'</div>',
|
||||||
|
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: '<div class="field checkbox">'+
|
||||||
|
'<label><input type="checkbox" class="value">'+
|
||||||
|
'<span class="text"></span></label>'+
|
||||||
|
'</div>',
|
||||||
|
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: <default-path> }
|
||||||
|
dir: {
|
||||||
|
type: 'dir',
|
||||||
|
text: null,
|
||||||
|
default: false,
|
||||||
|
html: '<div class="field checkbox">'+
|
||||||
|
'<span class="text"></span>'+
|
||||||
|
'<input type="file" class="value" nwdirectory />'+
|
||||||
|
'</div>',
|
||||||
|
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: <default-path> }
|
||||||
|
// XXX add datalist option...
|
||||||
|
ndir: {
|
||||||
|
type: 'ndir',
|
||||||
|
text: null,
|
||||||
|
default: false,
|
||||||
|
html: '<div class="field dir">'+
|
||||||
|
'<span class="text"></span>'+
|
||||||
|
'<input type="text" class="path"/>'+
|
||||||
|
'<button class="browse">Browse</button>'+
|
||||||
|
'</div>',
|
||||||
|
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 = $('<input type="file" class="value" nwdirectory/>')
|
||||||
|
.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, <select path>,
|
||||||
|
// 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:
|
||||||
|
// <text> ['|' 'default' | 'disabled' ] [ '|' <tool-tip> ]
|
||||||
|
//
|
||||||
|
// 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: '<div class="field choice">'+
|
||||||
|
'<span class="text"></span>'+
|
||||||
|
'<div class="item"><label>'+
|
||||||
|
'<input type="radio" class="value"/>'+
|
||||||
|
'<span class="item-text"></span>'+
|
||||||
|
'</label></div>'+
|
||||||
|
'</div>',
|
||||||
|
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: <number> | <text>
|
||||||
|
// }
|
||||||
|
select: {
|
||||||
|
type: 'select',
|
||||||
|
text: null,
|
||||||
|
default: false,
|
||||||
|
html: '<div class="field choice">'+
|
||||||
|
'<span class="text"></span>'+
|
||||||
|
'<select>'+
|
||||||
|
'<option class="option"></option>'+
|
||||||
|
'</select>'+
|
||||||
|
'</div>',
|
||||||
|
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: <function>,
|
||||||
|
// // optional, button text (default 'OK')...
|
||||||
|
// text: <button-label>,
|
||||||
|
// // optional, initial state setup...
|
||||||
|
// default: <function>,
|
||||||
|
// }
|
||||||
|
button: {
|
||||||
|
type: 'button',
|
||||||
|
text: null,
|
||||||
|
default: false,
|
||||||
|
html: '<div class="field button">'+
|
||||||
|
'<span class="text"></span>'+
|
||||||
|
'<button class="button"></button>'+
|
||||||
|
'</div>',
|
||||||
|
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...
|
||||||
|
// <field-description>: <default-value>,
|
||||||
|
//
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// <field-description> 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 <field-description> 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 = $('<div class="form"/>')
|
||||||
|
var data = {}
|
||||||
|
var res = $.Deferred()
|
||||||
|
|
||||||
|
// handle message and btn...
|
||||||
|
if(message.trim().length > 0){
|
||||||
|
setTextWithTooltip(message, $('<div class="text"/>'))
|
||||||
|
.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 = $('<button class="accept">'+btn+'</button>')
|
||||||
|
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 : */
|
||||||
611
ui/ui.js
611
ui/ui.js
@ -14,6 +14,7 @@ var STATUS_QUEUE_TIME = 200
|
|||||||
|
|
||||||
var CONTEXT_INDICATOR_UPDATERS = []
|
var CONTEXT_INDICATOR_UPDATERS = []
|
||||||
|
|
||||||
|
|
||||||
// this can be:
|
// this can be:
|
||||||
// - 'floating'
|
// - 'floating'
|
||||||
// - 'panel'
|
// - '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){
|
|
||||||
$('<span class="tooltip-icon tooltip-right"> *</span>')
|
|
||||||
.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 $('<div class="overlay-block">'+
|
|
||||||
'<div class="background"/>'+
|
|
||||||
'<div class="content"/>'+
|
|
||||||
'</div>').appendTo(root)
|
|
||||||
}
|
|
||||||
return overlay
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function showInOverlay(root, data){
|
|
||||||
root = $(root)
|
|
||||||
|
|
||||||
var overlay = getOverlay(root)
|
|
||||||
|
|
||||||
|
|
||||||
if(data != null){
|
|
||||||
var container = $('<table width="100%" height="100%"><tr><td align="center" valign="center">'+
|
|
||||||
'<div class="dialog"/>'+
|
|
||||||
'</td></tr></table>')
|
|
||||||
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: '<hr>',
|
|
||||||
test: function(val){
|
|
||||||
return /\-\-\-+/.test(val)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// a simple br...
|
|
||||||
//
|
|
||||||
// format:
|
|
||||||
// ' '
|
|
||||||
// Three or more spaces
|
|
||||||
br: {
|
|
||||||
type: 'br',
|
|
||||||
text: null,
|
|
||||||
default: false,
|
|
||||||
html: '<br>',
|
|
||||||
test: function(val){
|
|
||||||
return /\s\s\s+/.test(val)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// format:
|
|
||||||
// {
|
|
||||||
// html: <html-block>
|
|
||||||
// }
|
|
||||||
html: {
|
|
||||||
type: 'html',
|
|
||||||
text: null,
|
|
||||||
default: false,
|
|
||||||
html: '<div class="html-block"/>',
|
|
||||||
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: '<div class="field string">'+
|
|
||||||
'<span class="text"></span>'+
|
|
||||||
'<input type="text" class="value">'+
|
|
||||||
'</div>',
|
|
||||||
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: '<div class="field checkbox">'+
|
|
||||||
'<label><input type="checkbox" class="value">'+
|
|
||||||
'<span class="text"></span></label>'+
|
|
||||||
'</div>',
|
|
||||||
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: <default-path> }
|
|
||||||
dir: {
|
|
||||||
type: 'dir',
|
|
||||||
text: null,
|
|
||||||
default: false,
|
|
||||||
html: '<div class="field checkbox">'+
|
|
||||||
'<span class="text"></span>'+
|
|
||||||
'<input type="file" class="value" nwdirectory />'+
|
|
||||||
'</div>',
|
|
||||||
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: <default-path> }
|
|
||||||
// XXX add datalist option...
|
|
||||||
ndir: {
|
|
||||||
type: 'ndir',
|
|
||||||
text: null,
|
|
||||||
default: false,
|
|
||||||
html: '<div class="field dir">'+
|
|
||||||
'<span class="text"></span>'+
|
|
||||||
'<input type="text" class="path"/>'+
|
|
||||||
'<button class="browse">Browse</button>'+
|
|
||||||
'</div>',
|
|
||||||
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 = $('<input type="file" class="value" nwdirectory/>')
|
|
||||||
.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, <select path>,
|
|
||||||
// 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:
|
|
||||||
// <text> ['|' 'default' | 'disabled' ] [ '|' <tool-tip> ]
|
|
||||||
//
|
|
||||||
// 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: '<div class="field choice">'+
|
|
||||||
'<span class="text"></span>'+
|
|
||||||
'<div class="item"><label>'+
|
|
||||||
'<input type="radio" class="value"/>'+
|
|
||||||
'<span class="item-text"></span>'+
|
|
||||||
'</label></div>'+
|
|
||||||
'</div>',
|
|
||||||
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: <number> | <text>
|
|
||||||
// }
|
|
||||||
select: {
|
|
||||||
type: 'select',
|
|
||||||
text: null,
|
|
||||||
default: false,
|
|
||||||
html: '<div class="field choice">'+
|
|
||||||
'<span class="text"></span>'+
|
|
||||||
'<select>'+
|
|
||||||
'<option class="option"></option>'+
|
|
||||||
'</select>'+
|
|
||||||
'</div>',
|
|
||||||
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: <function>,
|
|
||||||
// // optional, button text (default 'OK')...
|
|
||||||
// text: <button-label>,
|
|
||||||
// // optional, initial state setup...
|
|
||||||
// default: <function>,
|
|
||||||
// }
|
|
||||||
button: {
|
|
||||||
type: 'button',
|
|
||||||
text: null,
|
|
||||||
default: false,
|
|
||||||
html: '<div class="field button">'+
|
|
||||||
'<span class="text"></span>'+
|
|
||||||
'<button class="button"></button>'+
|
|
||||||
'</div>',
|
|
||||||
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...
|
|
||||||
// <field-description>: <default-value>,
|
|
||||||
//
|
|
||||||
// ...
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// <field-description> 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 <field-description> 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 = $('<div class="form"/>')
|
|
||||||
var data = {}
|
|
||||||
var res = $.Deferred()
|
|
||||||
|
|
||||||
// handle message and btn...
|
|
||||||
if(message.trim().length > 0){
|
|
||||||
setTextWithTooltip(message, $('<div class="text"/>'))
|
|
||||||
.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 = $('<button class="accept">'+btn+'</button>')
|
|
||||||
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){
|
function detailedAlert(text, description, button){
|
||||||
return formDialog(null, '', {'': {
|
return formDialog(null, '', {'': {
|
||||||
html: $('<details/>')
|
html: $('<details/>')
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user