More work on the custom list mode

This commit is contained in:
Kovid Goyal 2017-07-18 10:29:49 +05:30
parent c1eb0438ab
commit 08da31ba3c
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 264 additions and 16 deletions

View File

@ -2,19 +2,256 @@
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
from __python__ import bound_methods, hash_literals
from elementmaker import E
from gettext import gettext as _
from book_list.details_list import THUMBNAIL_MAX_HEIGHT
from book_list.library_data import library_data
from date import format_date
from dom import build_rule, clear, set_css, svgicon
from session import get_interface_data
from utils import fmt_sidx, safe_set_inner_html, sandboxed_html
CUSTOM_LIST_CLASS = 'book-list-custom-list'
ITEM_CLASS = CUSTOM_LIST_CLASS + '-item'
DESCRIPTION = _('A customizable list')
def custom_list_css():
ans = ''
sel = '.' + CUSTOM_LIST_CLASS
ans += build_rule(sel, cursor='pointer', user_select='none')
sel += ' .' + ITEM_CLASS
ans += build_rule(sel, margin='1ex 1em', padding_bottom='1ex', overflow='hidden', border_bottom='solid 1px currentColor')
sel += ' iframe'
# To enable clicking anywhere on the item to load book details to work, we
# have to set pointer-events: none
# That has the side effect of disabling text selection
ans += build_rule(sel, flex_grow='10', cursor='pointer', pointer_events='none')
return ans
def default_template():
return {
'cover': True,
'comments': False,
'comments_fields': ['comments'],
'height': '12.5ex',
'lines': [
_('<b>{title}</b> by {authors}'),
_('{series_index} of {series}') + '|||{rating}',
'{tags}',
_('Date: {timestamp} Published: {pubdate}'),
]
}
if not default_template.ans:
default_template.ans = {
'thumbnail': True,
'thumbnail_height': THUMBNAIL_MAX_HEIGHT,
'height': 'auto',
'comments_fields': v"['comments']",
'lines': [
_('<b>{title}</b> by {authors}'),
_('{series_index} of {series}') + '|||{rating}',
'{tags}',
_('Date: {timestamp} Published: {pubdate}'),
]
}
return default_template.ans
def render_field(field, mi, book_id): # {{{
field_metadata = library_data.field_metadata
fm = field_metadata[field]
if not fm:
return
val = mi[field]
if val is undefined or val is None:
return
interface_data = get_interface_data()
def add_val(val, is_html=False, join=None):
if is_html and /[<>]/.test(val + ''):
return safe_set_inner_html(E.span(), val)
if join:
val = val.join(join)
else:
val += ''
return val
def process_composite(field, fm, name, val):
if fm.display and fm.display.contains_html:
return add_val(val, is_html=True)
if fm.is_multiple and fm.is_multiple.list_to_ui:
all_vals = filter(None, map(str.strip, val.split(fm.is_multiple.list_to_ui)))
return add_val(all_vals, join=fm.is_multiple.list_to_ui)
return add_val(val)
def process_authors(field, fm, name, val):
return add_val(val, join=' & ')
def process_publisher(field, fm, name, val):
return add_val(val)
def process_formats(field, fm, name, val):
return add_val(val, join=', ')
def process_rating(field, fm, name, val):
stars = E.span()
val = int(val or 0)
if val > 0:
for i in range(val // 2):
stars.appendChild(svgicon('star'))
if fm.display.allow_half_stars and (val % 2):
stars.appendChild(svgicon('star-half'))
return stars
def process_identifiers(field, fm, name, val):
if val:
keys = Object.keys(val)
if keys.length:
ans = v'[]'
for key in keys:
ans.push(key + ':' + val[key])
return add_val(ans, join=', ')
def process_languages(field, fm, name, val):
if val and val.length:
langs = [mi.lang_names[k] for k in val]
return add_val(langs, join=', ')
def process_datetime(field, fm, name, val):
if val:
fmt = interface_data['gui_' + field + '_display_format'] or (fm['display'] or {}).date_format
return add_val(format_date(val, fmt))
def process_series(field, fm, name, val):
if val:
return add_val(val)
def process_series_index(field, fm, name, val):
sval = mi[field[:-6]]
if sval:
return fmt_sidx(val or 1, use_roman=interface_data.use_roman_numerals_for_series_number)
name = fm.name or field
datatype = fm.datatype
if field is 'comments' or datatype is 'comments':
return
func = None
if datatype is 'composite':
func = process_composite
elif field is 'formats':
func = process_formats
elif datatype is 'rating':
func = process_rating
elif field is 'identifiers':
func = process_identifiers
elif field is 'authors':
func = process_authors
elif field is 'publisher':
func = process_publisher
elif field is 'languages':
func = process_languages
elif datatype is 'datetime':
func = process_datetime
elif datatype is 'series':
func = process_series
elif field.endswith('_index'):
func = process_series_index
ans = None
if func:
ans = func(field, fm, name, val)
else:
if datatype is 'text' or datatype is 'enumeration':
if val is not undefined and val is not None:
join = fm.is_multiple.list_to_ui if fm.is_multiple else None
ans = add_val(val, join=join)
elif datatype is 'bool':
ans = add_val(_('Yes') if val else _('No'))
elif datatype is 'int' or datatype is 'float':
if val is not undefined and val is not None:
fmt = (fm.display or {}).number_format
if fmt:
val = fmt.format(val)
else:
val += ''
ans = add_val(val)
return ans
# }}}
def render_part(part, template, book_id, metadata):
count = rendered_count = 0
ans = E.div()
for field in part.split(/({[_a-z0-9]+})/):
if field[0] is '{' and field[-1] is '}':
count += 1
val = render_field(field[1:-1], metadata, book_id)
if val:
rendered_count += 1
if jstype(val) is 'string':
val = document.createTextNode(val)
ans.appendChild(val)
else:
ans.appendChild(document.createTextNode(field))
if count and not rendered_count:
return
return ans
def render_line(line, template, book_id, metadata):
parts = v'[]'
for p in line.split(/\|\|\|/):
part = render_part(p, template, book_id, metadata)
if part:
parts.push(part)
if not parts.length:
return
ans = E.div(class_='custom-line')
for p in parts:
ans.appendChild(p)
if parts.length > 1:
set_css(ans, display='flex', justify_content='space-between')
return ans
def render_template_text(template, book_id, metadata):
ans = E.div()
for line in template.lines:
ldiv = render_line(line, template, book_id, metadata)
if ldiv:
ans.appendChild(ldiv)
if template.comments_fields.length:
html = ''
for f in template.comments_fields:
val = metadata[f]
if val:
html += f'<div style="margin-bottom:1.5ex">{val}</div>'
if html:
comments = sandboxed_html(html, 'html { overflow: hidden }')
ans.appendChild(comments)
return ans
def init(container):
clear(container)
container.appendChild(E.div(class_=CUSTOM_LIST_CLASS))
def create_item(book_id, metadata, create_image, show_book_details):
template = default_template()
text_data = render_template_text(template, book_id, metadata)
text_data.style.flexGrow = '10'
if template.thumbnail:
height = f'{template.thumbnail_height}px'
else:
if template.height is 'auto':
height = (template.lines.length * 2.5 + 1) + 'ex'
else:
height = template.height
if jstype(height) is 'number':
height += 'px'
ans = E.div(
style=f'height:{height}; display: flex',
class_=ITEM_CLASS,
)
if template.thumbnail:
pass
ans.appendChild(text_data)
ans.addEventListener('click', show_book_details, True)
return ans
def append_item(container, item):
container.lastChild.appendChild(item)

View File

@ -11,6 +11,11 @@ from book_list.cover_grid import (
DESCRIPTION as COVER_GRID_DESCRIPTION, append_item as cover_grid_append_item,
cover_grid_css, create_item as create_cover_grid_item, init as init_cover_grid
)
from book_list.custom_list import (
DESCRIPTION as CUSTOM_LIST_DESCRIPTION, append_item as custom_list_append_item,
create_item as create_custom_list_item, custom_list_css,
init as init_custom_list
)
from book_list.details_list import (
DESCRIPTION as DETAILS_LIST_DESCRIPTION, append_item as details_list_append_item,
create_item as create_details_list_item, details_list_css,
@ -19,9 +24,9 @@ from book_list.details_list import (
from book_list.globals import get_session_data
from book_list.item_list import create_item, create_item_list
from book_list.library_data import (
all_virtual_libraries, book_metadata, current_sorted_field,
ensure_current_library_data, library_data, load_status, loaded_books_query,
thumbnail_cache, url_books_query, add_more_books, current_book_ids
add_more_books, all_virtual_libraries, book_metadata, current_book_ids,
current_sorted_field, ensure_current_library_data, library_data, load_status,
loaded_books_query, thumbnail_cache, url_books_query
)
from book_list.router import back, home, push_state, update_window_title
from book_list.search import (
@ -37,7 +42,7 @@ from widgets import create_button, create_spinner
CLASS_NAME = 'book-list-container'
ITEM_CLASS_NAME = 'book-list-item'
ALLOWED_MODES = {'cover_grid', 'details_list'}
ALLOWED_MODES = {'cover_grid', 'details_list', 'custom_list'}
DEFAULT_MODE = 'cover_grid'
add_extra_css(def():
@ -45,6 +50,7 @@ add_extra_css(def():
ans = build_rule(sel + '[data-component="top_message"]', margin='1ex 1em')
ans += cover_grid_css()
ans += details_list_css()
ans += custom_list_css()
return ans
)
@ -134,6 +140,10 @@ def setup_view_mode(mode, book_list_data):
book_list_data.render_book = create_details_list_item
book_list_data.init_grid = init_details_list
book_list_data.append_item = details_list_append_item
elif mode is 'custom_list':
book_list_data.render_book = create_custom_list_item
book_list_data.init_grid = init_custom_list
book_list_data.append_item = custom_list_append_item
return mode
@ -409,6 +419,7 @@ def create_mode_panel(container_id):
ci(_('Cover grid'), COVER_GRID_DESCRIPTION, 'cover_grid')
ci(_('Detailed list'), DETAILS_LIST_DESCRIPTION, 'details_list')
ci(_('Custom list'), CUSTOM_LIST_DESCRIPTION, 'custom_list')
container.appendChild(E.div())
create_item_list(container.lastChild, items, _('Choose a display mode for the list of books from below'))