More work on the conversion setting UI for the server

This commit is contained in:
Kovid Goyal 2018-07-02 13:37:40 +05:30
parent d2de878849
commit f85c5228c2
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 221 additions and 6 deletions

View File

@ -151,7 +151,7 @@ OptionRecommendation(name='base_font_size',
help=_('The base font size in pts. All font sizes in the produced book '
'will be rescaled based on this size. By choosing a larger '
'size you can make the fonts in the output bigger and vice '
'versa. By default, the base font size is chosen based on '
'versa. By default, when the value is zero, the base font size is chosen based on '
'the output profile you chose.'
)
),
@ -849,6 +849,16 @@ OptionRecommendation(name='search_replace',
if help is not None:
return help.replace('%default', str(rec.recommended_value))
def get_all_help(self):
ans = {}
for group in (self.input_options, self.pipeline_options,
self.output_options, self.all_format_options):
for rec in group:
help = getattr(rec, 'help', None)
if help is not None:
ans[rec.option.name] = help
return ans
def merge_plugin_recs(self, plugin):
for name, val, level in plugin.recommendations:
rec = self.get_option_by_name(name)

View File

@ -209,7 +209,7 @@ def get_conversion_options(input_fmt, output_fmt, book_id, db):
from calibre.customize.conversion import OptionRecommendation
plumber = create_dummy_plumber(input_fmt, output_fmt)
specifics = load_specifics(db, book_id)
ans = {'options': {}, 'disabled': set(), 'defaults': {}}
ans = {'options': {}, 'disabled': set(), 'defaults': {}, 'help': {}}
def merge_group(group_name, option_names):
if not group_name or group_name in ('debug', 'metadata'):
@ -227,6 +227,7 @@ def get_conversion_options(input_fmt, output_fmt, book_id, db):
ans['options'].update(defs['options'])
ans['disabled'] |= set(defs['disabled'])
ans['defaults'].update(defaults)
ans['help'] = plumber.get_all_help()
for group_name, option_names in OPTIONS['pipe'].iteritems():
merge_group(group_name, option_names)

View File

@ -2,6 +2,178 @@
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
from __python__ import bound_methods, hash_literals
from elementmaker import E
from gettext import gettext as _
def create_option_group(group_name, container):
pass
from dom import ensure_id, add_extra_css, build_rule
CLASS_NAME = 'conversion-option-group'
add_extra_css(def():
style = ''
sel = '.' + CLASS_NAME + ' '
style += build_rule(sel + ' [data-option-name]', margin_bottom='1ex')
return style
)
# globals {{{
container_id = None
get_option_value = get_option_default_value = set_option_value = is_option_disabled = get_option_help = None
entry_points = {}
registry = {}
listeners = {}
def ep(func):
entry_points[func.name] = func
return func
def add_listener(name, callback):
if not listeners[name]:
listeners[name] = v'[]'
listeners[name].push(callback)
def on_change(name):
if listeners[name]:
for callback in listeners[name]:
callback(name)
def sanitize_accelerator(text):
return text.replace('&', '')
# }}}
def create_simple_widget(name, text, tooltip, input_widget_, getter, setter, reverse): # {{{
if not text.endswith(':'):
text = text + ':'
div = E.div(
data_option_name=name,
title=tooltip or get_option_help(name),
(
E.label(input_widget_, '\xa0' + sanitize_accelerator(text)) if reverse else
E.label(sanitize_accelerator(text) + '\xa0', input_widget_)
)
)
def straight_input_widget(container):
return container.firstChild.lastChild
def reverse_input_widget(container):
return container.firstChild.firstChild
input_widget = reverse_input_widget if reverse else straight_input_widget
input_widget(div).addEventListener('change', on_change.bind(None, name))
ops = {
'get': def (container):
return getter(input_widget(container))
,
'set': def (container, val):
setter(input_widget(container), val)
,
'set_disabled': def (container, val):
if val:
container.classList.add('disabled')
input_widget(container).setAttribute('disabled', 'disabled')
else:
container.classList.remove('disabled')
input_widget(container).removeAttribute('disabled')
}
registry[name] = ops
ops.set(div, get_option_value(name))
ops.set(div, get_option_value(name))
if is_option_disabled(name):
ops.set_disabled(div, True)
return div
# }}}
def checkbox(name, text, tooltip): # {{{
return create_simple_widget(name, text, tooltip, E.input(type='checkbox'),
def getter(w): # noqa: unused-local
return bool(w.checked)
,
def setter(w, val): # noqa: unused-local
w.checked = bool(val)
,
True
)
# }}}
def lineedit(name, text, tooltip): # {{{
return create_simple_widget(name, text, tooltip, E.input(type='text'),
def getter(w): # noqa: unused-local
ans = w.value
if ans and ans.strip():
return ans.strip()
,
def setter(w, val): # noqa: unused-local
w.value = val or ''
)
# }}}
def float_spin(name, text, tooltip=None, step=0.1, min=0, max=100): # {{{
f = E.input(type='number', step=str(step), min=str(min), max=str(max), required=True)
defval = get_option_default_value(name)
return create_simple_widget(name, text, tooltip, f,
def getter(w): # noqa: unused-local
try:
return float(w.value)
except:
return defval
,
def setter(w, val): # noqa: unused-local
w.value = str(float(val))
)
# }}}
def container_for_option(name):
return document.getElementById(container_id).querySelector(f'[data-option-name="{name}"]')
def get(name):
return registry[name].get(container_for_option(name))
def set(name, val):
registry[name].set(container_for_option(name), val)
def set_disabled(name, val):
registry[name].set_disabled(container_for_option(name), val)
# Look & feel {{{
@ep
def look_and_feel(container):
def subhead(text):
container.appendChild(E.div(
style='border-bottom: solid 1px currentColor; margin-bottom: 1ex; max-width: 30em', E.b(sanitize_accelerator(text))))
subhead(_('&Fonts'))
add_listener('disable_font_rescaling', def (name):
disabled = get('disable_font_rescaling')
for dname in 'font_size_mapping', 'base_font_size':
set_disabled(dname, disabled)
)
container.appendChild(checkbox('disable_font_rescaling', _('&Disable font size rescaling')))
container.appendChild(float_spin('base_font_size', _('Base font si&ze:'), step=0.1, min=0, max=50))
container.appendChild(lineedit('font_size_mapping', _('Font size &key:')))
# }}}
def create_option_group(group_name, container, get_option_value_, get_option_default_value_, is_option_disabled_, get_option_help_):
nonlocal get_option_value, get_option_default_value, set_option_value, is_option_disabled, container_id, registry, listeners, get_option_help
get_option_value, get_option_default_value, is_option_disabled, get_option_help = get_option_value_, get_option_default_value_, is_option_disabled_, get_option_help_
registry = {}
listeners = {}
container_id = ensure_id(container)
container.classList.add(CLASS_NAME)
entry_points[group_name](container)
def commit_changes(set_option_value):
for name in registry:
set_option_value(name, get(name))

View File

@ -7,7 +7,7 @@ from gettext import gettext as _
from ajax import ajax, ajax_send
from book_list.book_details import report_load_failure
from book_list.conversion_widgets import create_option_group
from book_list.conversion_widgets import create_option_group, entry_points, commit_changes
from book_list.library_data import download_url, load_status, url_books_query
from book_list.router import back, open_book, report_a_load_failure
from book_list.top_bar import create_top_bar, set_title
@ -240,6 +240,8 @@ def create_configuring_markup():
ans = E.li(E.a(class_='simple-link', href='javascript: void(0)'))
ans.dataset.group = name
ans.firstChild.addEventListener('click', show_group)
if not entry_points[name]:
ans.style.display = 'none'
return ans
GROUP_TITLES = {
@ -290,6 +292,32 @@ def create_configuring_markup():
return ans, initialize
def get_option_value(name, defval):
ans = conversion_data.conversion_options.options[name]
if ans is undefined:
ans = defval
return ans
def get_option_default_value(name, defval):
ans = conversion_data.conversion_options.defaults[name]
if ans is undefined:
ans = defval
return ans
def set_option_value(name, val):
conversion_data.conversion_options.options[name] = val
def is_option_disabled(name):
return conversion_data.disabled_map[name] is True
def get_option_help(name):
return conversion_data.conversion_options.help[name] or ''
def create_configure_group_markup():
ans = E.div()
@ -300,7 +328,7 @@ def create_configure_group_markup():
_('Configuring {} settings').format(conversion_data.configuring_group_title)))
panel = E.div()
container.appendChild(panel)
create_option_group(conversion_data.configuring_group, container)
create_option_group(conversion_data.configuring_group, container, get_option_value, get_option_default_value, is_option_disabled, get_option_help)
return ans, init
@ -320,6 +348,9 @@ def on_data_loaded(end_type, xhr, ev):
if end_type is 'load':
conversion_data = JSON.parse(xhr.responseText)
conversion_data.disabled_map = {}
for name in conversion_data.conversion_options.disabled:
conversion_data.disabled_map[name] = True
elif end_type is 'abort':
pass
else:
@ -346,6 +377,7 @@ def fetch_conversion_data(book_id, input_fmt, output_fmt):
def on_close(container_id):
nonlocal current_state
if current_state is 'configure-group':
commit_changes(set_option_value)
current_state = 'configuring'
apply_state_to_markup()
return