Preferences for the CS TB

This commit is contained in:
Kovid Goyal 2016-02-11 15:08:09 +05:30
parent bef332b6cb
commit a98a4d7e59
4 changed files with 297 additions and 9 deletions

View File

@ -157,7 +157,7 @@ def categories_settings(query, db):
if partition_method not in {'first letter', 'disable', 'partition'}: if partition_method not in {'first letter', 'disable', 'partition'}:
partition_method = 'first letter' partition_method = 'first letter'
try: try:
collapse_at = max(0, int(query.get('collapse_at', 25))) collapse_at = max(0, int(float(query.get('collapse_at', 25))))
except Exception: except Exception:
collapse_at = 25 collapse_at = 25
sort_by = query.get('sort_tags_by', 'name') sort_by = query.get('sort_tags_by', 'name')

213
src/pyj/book_list/prefs.pyj Normal file
View File

@ -0,0 +1,213 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from dom import clear
from elementmaker import E
from book_list.globals import get_session_data
# from book_list.theme import get_font_size, get_color
pp_counter = 0
widget_counter = 0
class ConfigItem:
def __init__(self, item_data):
nonlocal widget_counter
widget_counter += 1
self.widget_id = 'pref-widget-' + widget_counter
self.item_data = item_data
self.ignore_ui_value_changed = False
def initialize(self):
self.ignore_ui_value_changed = True
try:
self.to_ui(self.from_storage())
finally:
self.ignore_ui_value_changed = False
return self
@property
def container(self):
return document.getElementById(self.widget_id)
@property
def control(self):
return self.container.lastChild
def from_storage(self):
val = get_session_data().get(self.item_data.name)
if self.item_data.from_storage:
val = self.item_data.from_storage(val)
return val
def to_storage(self, val):
if self.item_data.to_storage:
val = self.item_data.to_storage(val)
get_session_data().set(self.item_data.name, val)
def defval(self):
return get_session_data().defval(self.item_data.name)
def ui_value_changed(self):
if self.ignore_ui_value_changed:
return
self.to_storage(self.from_ui())
def to_ui(self, val):
pass
def from_ui(self):
pass
class Choices(ConfigItem):
def __init__(self, item_data, container, onfocus):
ConfigItem.__init__(self, item_data)
div = E.div(
id=self.widget_id,
E.span(item_data.text + ': ', style='white-space:pre'),
E.select(required='1')
)
container.appendChild(div)
select = div.lastChild
for choice, text in item_data.choices:
select.appendChild(E.option(text, value=choice))
select.addEventListener('change', self.ui_value_changed.bind(self))
select.addEventListener('focus', onfocus)
def to_ui(self, val):
self.control.value = val
def from_ui(self):
return self.control.value
class CheckBox(ConfigItem):
def __init__(self, item_data, container, onfocus):
ConfigItem.__init__(self, item_data)
div = E.div(
id=self.widget_id,
E.input(type='checkbox'),
E.span(' ' + item_data.text, style='white-space:pre')
)
container.appendChild(div)
control = div.firstChild
control.addEventListener('change', self.ui_value_changed.bind(self))
control.addEventListener('focus', onfocus)
@property
def control(self):
return self.container.firstChild
def to_ui(self, val):
self.control.checked = bool(val)
def from_ui(self):
return bool(self.control.checked)
class SpinBox(ConfigItem):
def __init__(self, item_data, container, onfocus):
ConfigItem.__init__(self, item_data)
div = E.div(
id=self.widget_id,
E.span(item_data.text + ': ', style='white-space:pre'),
E.input(type='number', step='any', min='1', max='100')
)
container.appendChild(div)
control = div.lastChild
for attr in str.split('min max step'):
val = item_data[attr]
if val is not undefined and val is not None:
control.setAttribute(attr, '' + val)
control.addEventListener('change', self.ui_value_changed.bind(self))
control.addEventListener('focus', onfocus)
def to_ui(self, val):
self.control.value = val
def from_ui(self):
return self.control.value
class LineEdit(ConfigItem):
def __init__(self, item_data, container, onfocus):
ConfigItem.__init__(self, item_data)
div = E.div(
id=self.widget_id,
E.span(item_data.text + ': ', style='white-space:pre'),
E.input(type='text')
)
container.appendChild(div)
control = div.lastChild
control.addEventListener('change', self.ui_value_changed.bind(self))
control.addEventListener('focus', onfocus)
def to_ui(self, val):
self.control.value = val or ''
def from_ui(self):
return self.control.value or ''
class PrefsPanel:
def __init__(self, interface_data, book_list_container):
nonlocal iv_counter
pp_counter += 1
self.container_id = 'prefs-panel-' + pp_counter
style = ''
div = E.div(
id=self.container_id, style='display:none',
E.style(style, type='text/css')
)
book_list_container.appendChild(div)
self.widgets = []
@property
def container(self):
return document.getElementById(self.container_id)
@property
def is_visible(self):
self.container.style.display == 'block'
@is_visible.setter
def is_visible(self, val):
self.container.style.display = 'block' if val else 'none'
def init(self, data):
c = self.container
clear(c)
self.widgets = []
def onfocus(name):
return def(ev):
c = self.container
div = c.querySelector(str.format('div[data-name="{}"]', name))
div.lastChild.style.display = 'block'
for item in data:
div = E.div(
style='margin-bottom:1ex; padding: 1ex 1em; border-bottom: solid 1px currentColor',
title=item.tooltip,
data_name=item.name,
E.div(),
E.div(
item.tooltip or '',
style='font-size:0.8rem; font-style: italic; margin-top:1ex; display:none'
)
)
c.appendChild(div)
val = get_session_data().get(item.name)
if item.from_storage:
val = item.from_storage(val)
if item.choices:
cls = Choices
elif val is True or val is False:
cls = CheckBox
elif type(val) == 'number':
cls = SpinBox
else:
cls = LineEdit
self.widgets.append((new cls(item, div.firstChild, onfocus(item.name))).initialize())

View File

@ -85,20 +85,22 @@ class SearchPanel:
self.tag_path = [] self.tag_path = []
self.active_nodes = {} self.active_nodes = {}
if not self.initial_load_started: if not self.initial_load_started:
self.initial_load_started = True
self.refresh() self.refresh()
else: else:
self.render_tag_browser() self.render_tag_browser()
def refresh(self): def refresh(self):
if self.currently_loading is not None: if self.currently_loading is not None:
return self.currently_loading.abort()
self.initial_load_started = True self.currently_loading = None
sd = get_session_data() sd = get_session_data()
query = {'library_id': self.interface_data.library_id} query = {'library_id': self.interface_data.library_id}
for k in str.split('sort_tags_by partition_method collapse_at dont_collapse hide_empty_categories'): for k in str.split('sort_tags_by partition_method collapse_at dont_collapse hide_empty_categories'):
query[k] = sd.get(k) + '' query[k] = sd.get(k) + ''
self.currently_loading = ajax('interface-data/tag-browser', self.on_data_fetched.bind(self), query=query, bypass_cache=False) xhr = ajax('interface-data/tag-browser', self.on_data_fetched.bind(self), query=query, bypass_cache=False)
self.currently_loading.send() xhr.send()
self.currently_loading = xhr
def on_data_fetched(self, end_type, xhr, ev): def on_data_fetched(self, end_type, xhr, ev):
self.currently_loading = None self.currently_loading = None
@ -383,6 +385,68 @@ class SearchPanel:
parts.push(expr) parts.push(expr)
self.search_control.value = parts.join(' ') self.search_control.value = parts.join(' ')
def get_prefs(self):
return [
{
'name': 'sort_tags_by',
'text': _('Sort tags by'),
'choices': [('name', _('Name')), ('popularity', _('Popularity (number of books)')), ('rating', _('Average rating'))],
'tooltip': _('Change how the tags/authors/etc. are sorted in the Tag Browser'),
},
{
'name':'partition_method',
'text':_('Tags browser category partitioning method'),
'choices':[('first letter', _('First Letter')), ('disable', _('Disable')), ('partition', _('Partition'))],
'tooltip':_('Choose how tag browser subcategories are displayed when'
' there are more items than the limit. Select by first'
' letter to see an A, B, C list. Choose partitioned to'
' have a list of fixed-sized groups. Set to disabled'
' if you never want subcategories.'),
},
{
'name':'collapse_at',
'text':_('Collapse when more items than'),
'min': 5, 'max':10000, 'step':5,
'from_storage':int, 'to_storage':int,
'tooltip': _('If a Tag Browser category has more than this number of items, it is divided'
' up into subcategories. If the partition method is set to disable, this value is ignored.'),
},
{
'name': 'dont_collapse',
'text': _('Categories not to partition'),
'tooltip': _('A comma-separated list of categories in which items containing'
' periods are displayed in the tag browser trees. For example, if'
" this box contains 'tags' then tags of the form 'Mystery.English'"
" and 'Mystery.Thriller' will be displayed with English and Thriller"
" both under 'Mystery'. If 'tags' is not in this box,"
' then the tags will be displayed each on their own line.'),
},
{
'name': 'hide_empty_categories',
'text': _('Hide empty categories (columns)'),
'from_storage': def(x): return x.toLowerCase() == 'yes';,
'to_storage': def(x): return 'yes' if x else 'no';,
'tooltip':_('When checked, calibre will automatically hide any category'
' (a column, custom or standard) that has no items to show. For example, some'
' categories might not have values when using virtual libraries. Checking this'
' box will cause these empty categories to be hidden.'),
},
]
def apply_prefs(self):
container = self.tb_container
clear(container)
container.appendChild(E.div(
style='margin: 1ex 1em',
_('Reloading tag browser with updated settings, please wait...'))
)
self.refresh()
@property @property
def container(self): def container(self):
return document.getElementById(self.container_id) return document.getElementById(self.container_id)

View File

@ -6,6 +6,7 @@ from book_list.search import SearchPanel
from book_list.top_bar import TopBar from book_list.top_bar import TopBar
from book_list.views import BooksView from book_list.views import BooksView
from book_list.item_list import ItemsView, create_item from book_list.item_list import ItemsView, create_item
from book_list.prefs import PrefsPanel
from gettext import gettext as _ from gettext import gettext as _
from utils import debounce from utils import debounce
@ -20,9 +21,13 @@ class BarState:
class ClosePanelBar(BarState): class ClosePanelBar(BarState):
def __init__(self, title, tooltip=''): def __init__(self, title, tooltip='', close_callback=None):
tooltip = tooltip or _('Close this panel') tooltip = tooltip or _('Close this panel')
BarState.__init__(self, title=title, tooltip=tooltip, action=close_panel, icon_name='times') def action():
close_panel()
if close_callback is not None:
close_callback()
BarState.__init__(self, title=title, tooltip=tooltip, action=action, icon_name='times')
class UIState: class UIState:
@ -69,8 +74,9 @@ class UI:
self.top_bar = TopBar(book_list_container) self.top_bar = TopBar(book_list_container)
self.books_view = BooksView(interface_data, book_list_container) self.books_view = BooksView(interface_data, book_list_container)
self.items_view = ItemsView(interface_data, book_list_container) self.items_view = ItemsView(interface_data, book_list_container)
self.prefs_panel = PrefsPanel(interface_data, book_list_container)
self.search_panel = SearchPanel(interface_data, book_list_container) self.search_panel = SearchPanel(interface_data, book_list_container)
self.panels = [self.books_view, self.items_view, self.search_panel] self.panels = [self.books_view, self.items_view, self.search_panel, self.prefs_panel]
self.panel_map = {self.ROOT_PANEL: UIState(create_book_view_top_bar_state(self.books_view), main_panel=self.books_view)} self.panel_map = {self.ROOT_PANEL: UIState(create_book_view_top_bar_state(self.books_view), main_panel=self.books_view)}
self.current_panel = self.ROOT_PANEL self.current_panel = self.ROOT_PANEL
window.addEventListener('resize', debounce(self.on_resize.bind(self), 250)) window.addEventListener('resize', debounce(self.on_resize.bind(self), 250))
@ -84,8 +90,13 @@ class UI:
self.panel_map['booklist-sort-menu'] = UIState(ClosePanelBar(_('Sort books')), panel_data=def(): self.panel_map['booklist-sort-menu'] = UIState(ClosePanelBar(_('Sort books')), panel_data=def():
return self.books_view.sort_panel_data(create_item) return self.books_view.sort_panel_data(create_item)
) )
self.panel_map['booklist-config-tb'] = UIState(
ClosePanelBar(_('Configure Tag Browser'), close_callback=self.search_panel.apply_prefs.bind(self.search_panel)),
main_panel=self.prefs_panel, panel_data=self.search_panel.get_prefs.bind(self.search_panel))
self.panel_map['booklist-search'] = UIState(ClosePanelBar(_('Search for books')), main_panel=self.search_panel) bss = ClosePanelBar(_('Search for books'))
bss.add_button(icon_name='cogs', tooltip=_('Configure Tag Browser'), action=show_panel_action('booklist-config-tb'))
self.panel_map['booklist-search'] = UIState(bss, main_panel=self.search_panel)
def on_resize(self): def on_resize(self):
pass pass