mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
Preferences for the CS TB
This commit is contained in:
parent
bef332b6cb
commit
a98a4d7e59
@ -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
213
src/pyj/book_list/prefs.pyj
Normal 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())
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user