calibre/src/pyj/modals.pyj
Kovid Goyal 13ddba374d Use icon instead of icon_name
More natural
2017-02-15 08:30:46 +05:30

254 lines
10 KiB
Plaintext

# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
from __python__ import hash_literals
from ajax import ajax, ajax_send
from elementmaker import E
from dom import set_css, clear, build_rule, svgicon, add_extra_css
from gettext import gettext as _
from book_list.theme import get_color, get_font_size
from popups import MODAL_Z_INDEX
modal_container = None
modal_count = 0
add_extra_css(def():
style = build_rule(
'#modal-container > div > a:hover',
color=get_color('dialog-foreground') + ' !important',
background_color=get_color('dialog-background') + ' !important'
)
style += build_rule(
'#modal-container a.dialog-simple-link:hover', color='red !important'
)
return style
)
class Modal:
def __init__(self, create_func, on_close, show_close):
nonlocal modal_count
self.create_func, self.on_close, self.show_close = create_func, on_close, show_close
modal_count += 1
self.id = modal_count
class ModalContainer:
def __init__(self):
div = E.div(id='modal-container',
E.div( # popup
E.div(), # content area
E.a(svgicon('close'), title=_('Close'))
)
)
document.body.appendChild(div)
# Container style
set_css(div,
position='fixed', top='0', right='0', bottom='0', left='0', # Stretch over entire window
background_color='rgba(0,0,0,0.8)', z_index=MODAL_Z_INDEX + '',
display='none', text_align='center', user_select='none'
)
# Popup style
set_css(div.firstChild,
position='relative', display='inline-block', top='50vh', transform='translateY(-50%)',
min_width='25vw', max_width='70vw', # Needed for iPhone 5
border_radius='1em', padding='1em 2em', margin_right='1em', margin_left='1em',
background=get_color('dialog-background'), color=get_color('dialog-foreground'),
background_image=get_color('dialog-background-image'), get_color('dialog-background'), get_color('dialog-background2')
)
# Close button style
set_css(div.firstChild.lastChild,
font_size='1.5em', line_height='100%', cursor='pointer', position='absolute',
right='-0.5em', top='-0.5em', width='1em', height='1em',
background_color=get_color('window-foreground'), color=get_color('window-background'), display='inline-box',
border_radius='50%', padding='4px', text_align='center', box_shadow='1px 1px 3px black'
)
div.firstChild.lastChild.addEventListener('click', def(event): event.preventDefault(), self.close_current_modal(event);)
# Content container style
set_css(div.firstChild.firstChild, user_select='text', max_height='60vh', overflow='auto')
self.modals = v'[]'
self.current_modal = None
self.hide = self.close_current_modal.bind(self)
@property
def modal_container(self):
return document.getElementById('modal-container')
def show_modal(self, create_func, on_close=None, show_close=True):
self.modals.push(Modal(create_func, on_close, show_close))
modal_id = self.modals[-1].id
self.update()
return modal_id
def hide_modal(self, modal_id):
if self.current_modal is not None and self.current_modal.id is modal_id:
self.clear_current_modal()
else:
doomed_modal = None
for i, modal in enumerate(self.modals):
if modal.id is modal_id:
doomed_modal = i
break
if doomed_modal is not None:
self.modals.splice(doomed_modal, 1)
def update(self):
if self.current_modal is None and self.modals:
self.current_modal = self.modals.shift()
c = self.modal_container
try:
self.current_modal.create_func(c.firstChild.firstChild, self.hide)
except:
self.current_modal = None
raise
if c.style.display is 'none':
set_css(c, display='block')
c.firstChild.lastChild.style.visibility = 'visible' if self.current_modal.show_close else 'hidden'
def clear_current_modal(self):
self.current_modal = None
c = self.modal_container
clear(c.firstChild.firstChild)
if self.modals.length is 0:
set_css(c, display='none')
else:
self.update()
def close_current_modal(self, event):
if self.current_modal is not None:
if self.current_modal.on_close is not None and self.current_modal.on_close(event) is True:
return
self.clear_current_modal()
def create_simple_dialog(title, msg, details, icon, prefix):
details = details or ''
def create_func(parent):
show_details = E.a(class_='dialog-simple-link', style='cursor:pointer; color: blue; padding-top:1em; display:inline-block; margin-left: auto', _('Show details'))
show_details.addEventListener('click', def():
show_details.style.display = 'none'
show_details.nextSibling.style.display = 'block'
)
is_html_msg = /<[a-zA-Z]/.test(msg)
html_container = E.div()
if is_html_msg:
html_container.innerHTML = msg
details_container = E.span()
if /<[a-zA-Z]/.test(details):
details_container.innerHTML = details
else:
details_container.textContent = details
parent.appendChild(
E.div(
style='max-width:60em; text-align: left',
E.h2(
E.span(svgicon(icon), style='color:red'), E.span('\xa0' + prefix + '\xa0', style='font-variant:small-caps'), title,
style='font-weight: bold; font-size: ' + get_font_size('title')
),
E.div((html_container if is_html_msg else msg), style='padding-top: 1em; margin-top: 1em; border-top: 1px solid currentColor'),
E.div(style='display: ' + ('block' if details else 'none'),
show_details,
E.div(details_container,
style='display:none; white-space:pre-wrap; font-size: smaller; margin-top: 1em; border-top: solid 1px currentColor; padding-top: 1em'
)
)
)
)
show_modal(create_func)
def create_progress_dialog(msg, on_close):
msg = msg or _('Loading, please wait...')
pbar, msg_div = E.progress(style='display:inline-block'), E.div(msg, style='text-align:center; padding-top:1ex')
def create_func(parent):
parent.appendChild(E.div(style='text-align: center', pbar, msg_div))
show_close = on_close is not None
modal_id = show_modal(create_func, on_close, show_close)
return {
'close': def(): modal_container.hide_modal(modal_id);,
'update_progress': def(amount, total): pbar.max, pbar.value = total, amount;,
'set_msg': def(new_msg): msg_div.innerHTML = new_msg;,
}
# def test_progress():
# counter = 0
# pd = progress_dialog('Testing progress dialog, please wait...', def():
# nonlocal counter
# counter = 101
# console.log('pd canceled')
# )
# def update():
# nonlocal counter
# counter += 1
# pd.update_progress(counter, 100)
# if counter < 100:
# setTimeout(update, 150)
# else:
# pd.close()
# update()
#
# def test_error():
# error_dialog(
# 'Hello, world!',
# 'Some long text to test rendering/line breaking in error dialogs that contain lots of text like this test error dialog from hell in a handbasket with fruits and muppets making a scene.',
# ['a word ' for i in range(10000)].join(' ')
# )
def create_modal_container():
nonlocal modal_container
if modal_container is None:
modal_container = ModalContainer()
# window.setTimeout(def():
# document.getElementById('books-view-1').addEventListener('click', test_progress)
# , 10)
return modal_container
def show_modal(create_func, on_close=None, show_close=True):
return modal_container.show_modal(create_func, on_close, show_close)
def error_dialog(title, msg, details=None):
create_simple_dialog(title, msg, details, 'bug', _('Error:'))
def warning_dialog(title, msg, details=None):
create_simple_dialog(title, msg, details, 'warning', _('Warning:'))
def progress_dialog(msg, on_close=None):
# Show a modal dialog with a progress bar and an optional close button.
# If the user clicks the close button, on_close is called and the dialog is closed.
# Returns an object with the methods: close(), update_progress(amount, total), set_msg()
# Call update_progress() to update the progress bar
# Call set_msg() to change the displayed message
# Call close() once the task being performed is finished
return create_progress_dialog(msg, on_close)
def ajax_progress_dialog(path, on_complete, msg, extra_data_for_callback=None, **kw):
pd = None
def on_complete_callback(event_type, xhr, ev):
nonlocal pd
pd.close()
pd = undefined
return on_complete(event_type, xhr, ev)
def on_progress_callback(loaded, total, xhr):
pd.update_progress(loaded, total)
xhr = ajax(path, on_complete_callback, on_progress=on_progress_callback, **kw)
xhr.extra_data_for_callback = extra_data_for_callback
pd = progress_dialog(msg, xhr.abort.bind(xhr))
xhr.send()
return xhr, pd
def ajax_send_progress_dialog(path, data, on_complete, msg, **kw):
pd = None
def on_complete_callback(event_type, xhr, ev):
nonlocal pd
pd.close()
pd = undefined
return on_complete(event_type, xhr, ev)
def on_progress_callback(loaded, total, xhr):
pd.update_progress(loaded, total)
xhr = ajax_send(path, data, on_complete_callback, on_progress=on_progress_callback, **kw)
pd = progress_dialog(msg, xhr.abort.bind(xhr))
return xhr, pd