ImageGrid/Viewer/lib/dialogs.js
Alex A. Naanou 27aef3db40 dos linefeeds converted to unix...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2024-10-27 11:01:12 +03:00

633 lines
14 KiB
JavaScript
Executable File

/**********************************************************************
*
*
*
**********************************************************************/
//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 : */