From a4c2f18e2300430804e28dcba9ce2e86a7d9bfe4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 9 Feb 2017 13:42:34 +0530 Subject: [PATCH] Start porting of book details panel --- src/pyj/book_list/book_details.pyj | 309 +++++++++++++++-------------- src/pyj/book_list/library_data.pyj | 21 +- src/pyj/book_list/main.pyj | 1 + src/pyj/book_list/views.pyj | 6 + 4 files changed, 188 insertions(+), 149 deletions(-) diff --git a/src/pyj/book_list/book_details.pyj b/src/pyj/book_list/book_details.pyj index b02c7cb49e..9ba0d8e764 100644 --- a/src/pyj/book_list/book_details.pyj +++ b/src/pyj/book_list/book_details.pyj @@ -4,16 +4,21 @@ from __python__ import hash_literals import traceback from ajax import ajax -from book_list.globals import get_current_query from book_list.theme import get_font_size from dom import clear, build_rule, svgicon, add_extra_css from elementmaker import E from gettext import gettext as _ -from book_list.globals import get_boss from modals import error_dialog from widgets import create_spinner, create_button from date import format_date -from utils import fmt_sidx +from session import get_interface_data +from utils import fmt_sidx, parse_url_params + +from book_list.router import back +from book_list.library_data import book_metadata, cover_url, set_book_metadata, current_library_id, library_data, download_url +from book_list.top_bar import create_top_bar, set_title, add_button +from book_list.ui import set_panel_handler +from book_list.views import search bd_counter = 0 @@ -49,23 +54,31 @@ def field_sorter(field_metadata): fm = (field_metadata[field] or {})[field] or {} return lvl + (fm.name or 'zzzzz') + def execute_search(ev): - name, val = JSON.parse(ev.currentTarget.getAttribute('data-search')) - search = '{}:"={}"'.format(name, str.replace(val, '"', r'\"')) - get_boss().ui.books_view.change_search(search) + name, val = JSON.parse(ev.currentTarget.dataset.search) + query = '{}:"={}"'.format(name, str.replace(val, '"', r'\"')) + search(query) -def download_format(ev): - fmt = ev.currentTarget.getAttribute('data-format') - get_boss().ui.book_details_panel.download_format(fmt) -def read_format(ev): - fmt = ev.currentTarget.getAttribute('data-format') - get_boss().ui.book_details_panel.read_format(fmt) +def on_download_format(ev): + fmt = ev.currentTarget.dataset.format + book_id = ev.currentTarget.dataset.bookId + download_format(int(book_id), fmt) -def render_metadata(mi, interface_data, table, field_list=None): + +def on_read_format(ev): + fmt = ev.currentTarget.dataset.format + book_id = ev.currentTarget.dataset.bookId + read_format(int(book_id), fmt) + + +def render_metadata(mi, table, book_id, field_list=None): # {{{ + field_metadata = library_data.field_metadata + interface_data = get_interface_data() def allowed_fields(field): if field.endswith('_index'): - fm = interface_data.field_metadata[field[:-len('_index')]] + fm = field_metadata[field[:-len('_index')]] if fm and fm.datatype is 'series': return False if field.startswith('#'): @@ -74,7 +87,7 @@ def render_metadata(mi, interface_data, table, field_list=None): return False return True - fields = field_list or sorted(filter(allowed_fields, mi), key=field_sorter(interface_data.field_metadata)) + fields = field_list or sorted(filter(allowed_fields, mi), key=field_sorter(field_metadata)) comments = {} def add_row(name, val, is_searchable=False, is_html=False, join=None): @@ -130,13 +143,13 @@ def render_metadata(mi, interface_data, table, field_list=None): title=_('Read this book in the {} format').format(fmt), href='javascript:void(0)', style='padding-left: 1em', svgicon('book'), - onclick=read_format, data_format=fmt + onclick=on_read_format, data_format=fmt, data_book_id='' + book_id, )) td.lastChild.appendChild(E.a( title=_('Download the {} format of this book').format(fmt), href='javascript:void(0)', style='padding-left: 1em', svgicon('cloud-download'), - onclick=download_format, data_format=fmt + onclick=on_download_format, data_format=fmt, data_book_id='' + book_id )) if fmt is not val[-1]: td.lastChild.appendChild(document.createTextNode(',')) @@ -245,7 +258,7 @@ def render_metadata(mi, interface_data, table, field_list=None): add_row(name, val) for field in fields: - fm = interface_data.field_metadata[field] + fm = field_metadata[field] if not fm: continue try: @@ -255,7 +268,7 @@ def render_metadata(mi, interface_data, table, field_list=None): traceback.print_exc() for i, field in enumerate(sorted(comments)): - fm = interface_data.field_metadata[field] + fm = field_metadata[field] comment = comments[field] div = E.div() div.innerHTML = comment @@ -265,6 +278,7 @@ def render_metadata(mi, interface_data, table, field_list=None): table.parentNode.appendChild(div) if i is 0: div.style.marginTop = '2ex' +# }}} CLASS_NAME = 'book-details-panel' @@ -277,141 +291,140 @@ add_extra_css(def(): return style ) -class BookDetailsPanel: +current_fetch = None - def __init__(self, interface_data, book_list_container): - nonlocal bd_counter - bd_counter += 1 - self.container_id = 'book-details-panel-' + bd_counter - div = E.div( - id=self.container_id, style='display:none', class_=CLASS_NAME, - E.div(), - ) - book_list_container.appendChild(div) - self.interface_data = interface_data - self.current_book_id = None +def no_book(container): + container.appendChild(E.div( + style='margin: 1ex 1em', + _('No book found') + )) - @property - def container(self): - return document.getElementById(self.container_id).lastChild - @property - def is_visible(self): - self.container.parentNode.style.display is 'block' - - @is_visible.setter - def is_visible(self, val): - self.container.parentNode.style.display = 'block' if val else 'none' - - def init(self, data): - self.current_book_id = None - c = self.container - clear(c) - book_id = get_current_query()['book-id'] - if book_id is undefined or book_id is None: - self.no_book() - elif book_id in self.interface_data.metadata: - self.render_book(book_id) - else: - self.fetch_metadata(book_id) - get_boss().ui.set_button_visibility('random', not book_id or book_id is '0') - - def show_random(self): - self.fetch_metadata('0') - - def no_book(self, book_id): - self.container.appendChild(E.div( - style='margin: 1ex 1em', - _('No book found') - )) - - def fetch_metadata(self, book_id): - if self.is_fetching: - self.is_fetching.abort() - self.is_fetching = ajax('interface-data/book-metadata/' + book_id, self.metadata_fetched.bind(self), - query={'library_id':self.interface_data.library_id}) - self.is_fetching.send() - self.container.appendChild(E.div( - style='margin: 1ex 1em', - create_spinner(), '\xa0' + _('Fetching metadata for the book, please wait') + '…', - )) - - def metadata_fetched(self, end_type, xhr, event): - if self.is_fetching is None or self.is_fetching is not xhr: - return # Fetching was aborted - self.is_fetching = None - c = self.container - if end_type is 'load': - try: - data = JSON.parse(xhr.responseText) - except Exception as err: - error_dialog(_('Could not fetch metadata for book'), _('Server returned an invalid response'), err.toString()) - return - clear(c) - book_id = data['id'] - self.interface_data.metadata[book_id] = data - self.render_book(book_id) - elif end_type is not 'abort': - clear(c) - c.appendChild(E.div( - style='margin: 1ex 1em', - _('Could not fetch metadata for book'), - E.div(style='margin: 1ex 1em') - )) - c.lastChild.lastChild.innerHTML = xhr.error_html - - def render_book(self, book_id): - self.current_book_id = int(book_id) - metadata = self.interface_data.metadata[book_id] - get_boss().ui.set_title(metadata.title) - cover_url = 'get/cover/{}/{}'.format(book_id, self.interface_data['library_id']) - alt = _('{} by {}').format(metadata['title'], metadata['authors'].join(' & ')) - imgdiv = E.div( - E.img( - src=cover_url, alt=alt, title=alt, data_title=metadata['title'], data_authors=metadata['authors'].join(' & '), - style='border-radius: 20px; max-width: calc(100vw - 2em); max-height: calc(100vh - 4ex - {}); display: block; width:auto; height:auto; border-radius: 20px'.format(get_font_size('title') - )) - ) - imgdiv.firstChild.onerror = self.on_img_err.bind(self) - c = self.container - c.appendChild(E.div( - style='display:flex; padding: 1ex 1em; align-items: flex-start; justify-content: flex-start; flex-wrap: wrap', - E.div(style='margin-right: 1em; flex-grow: 3; max-width: 500px', data_book_id='' + book_id), - imgdiv - )) - container = c.lastChild.firstChild - read_button = create_button(_('Read'), 'book', self.read_book.bind(self), _('Read this book')) - download_button = create_button(_('Download'), 'cloud-download', self.download_book.bind(self), - _('Download this book in the {} format').format(self.preferred_format(book_id))) - row = E.div(read_button, '\xa0\xa0\xa0', download_button, style='margin-bottom: 1ex') - if not metadata.formats or not metadata.formats.length: - row.style.display = 'none' - container.appendChild(row) - md = E.div(style='margin-bottom: 2ex') - table = E.table(class_='metadata') - container.appendChild(md) - md.appendChild(table) - render_metadata(metadata, self.interface_data, table) - - def on_img_err(self, err): - img = err.target +def on_img_err(err): + img = err.target + if img.parentNode: img.parentNode.style.display = 'none' - def preferred_format(self, book_id): - return get_preferred_format(self.interface_data.metadata[book_id], self.interface_data.output_format, self.interface_data.input_formats) +def preferred_format(book_id): + interface_data = get_interface_data() + return get_preferred_format(book_metadata(book_id), interface_data.output_format, interface_data.input_formats) - def download_format(self, fmt): - window.location = 'get/{}/{}/{}'.format(fmt, self.current_book_id, self.interface_data.library_id) - def download_book(self): - book_id = self.current_book_id - fmt = self.preferred_format(book_id) - self.download_format(fmt) +def read_format(book_id, fmt): + get_boss().read_book(self.current_book_id, fmt, self.interface_data.metadata[self.current_book_id]) - def read_format(self, fmt): - get_boss().read_book(self.current_book_id, fmt, self.interface_data.metadata[self.current_book_id]) - def read_book(self): - book_id = self.current_book_id - fmt = self.preferred_format(book_id) - self.read_format(fmt) +def read_book(book_id): + fmt = preferred_format(book_id) + read_format(book_id, fmt) + + +def download_format(book_id, fmt): + window.location = download_url(book_id, fmt) + + +def download_book(book_id): + fmt = preferred_format(book_id) + download_format(fmt) + + +def render_book(container_id, book_id): + c = document.getElementById(container_id) + if not c: + return + metadata = book_metadata(book_id) + set_title(c, metadata.title) + alt = _('{} by {}').format(metadata.title, metadata.authors.join(' & ')) + imgdiv = E.div( + E.img( + alt=alt, title=alt, data_title=metadata.title, data_authors=metadata.authors.join(' & '), + style='border-radius: 20px; max-width: calc(100vw - 2em); max-height: calc(100vh - 4ex - {}); display: block; width:auto; height:auto; border-radius: 20px'.format(get_font_size('title') + )) + ) + imgdiv.firstChild.onerror = on_img_err + imgdiv.firstChild.src = cover_url(book_id) + c = c.lastChild + c.appendChild(E.div( + style='display:flex; padding: 1ex 1em; align-items: flex-start; justify-content: flex-start; flex-wrap: wrap', + E.div(style='margin-right: 1em; flex-grow: 3; max-width: 500px', data_book_id='' + book_id), + imgdiv + )) + container = c.lastChild.firstChild + read_button = create_button(_('Read'), 'book', read_book.bind(None, book_id), _('Read this book')) + download_button = create_button(_('Download'), 'cloud-download', download_book.bind(None, book_id), + _('Download this book in the {} format').format(preferred_format(book_id))) + row = E.div(read_button, '\xa0\xa0\xa0', download_button, style='margin-bottom: 1ex') + if not metadata.formats or not metadata.formats.length: + row.style.display = 'none' + container.appendChild(row) + md = E.div(style='margin-bottom: 2ex') + table = E.table(class_='metadata') + container.appendChild(md) + md.appendChild(table) + render_metadata(metadata, table, book_id) + + +def metadata_fetched(container_id, book_id, end_type, xhr, event): + nonlocal current_fetch + if current_fetch is None or current_fetch is not xhr: + return # Fetching was aborted + current_fetch = None + c = document.getElementById(container_id) + if not c: + return + c = c.lastChild + if end_type is 'load': + try: + data = JSON.parse(xhr.responseText) + except Exception as err: + error_dialog(_('Could not fetch metadata for book'), _('Server returned an invalid response'), err.toString()) + return + clear(c) + book_id = int(data['id']) + set_book_metadata(book_id, data) + render_book(container_id, book_id) + elif end_type is not 'abort': + clear(c) + c.appendChild(E.div( + style='margin: 1ex 1em', + _('Could not fetch metadata for book'), + E.div(style='margin: 1ex 1em') + )) + c.lastChild.lastChild.innerHTML = xhr.error_html + +def fetch_metadata(container_id, book_id): + nonlocal current_fetch + container = document.getElementById(container_id) + if not container: + return + if current_fetch: + current_fetch.abort() + current_fetch = ajax('interface-data/book-metadata/' + book_id, metadata_fetched.bind(None, container_id, book_id), + query={'library_id':current_library_id().library_id}) + current_fetch.send() + container = container.lastChild + clear(container) + container.appendChild(E.div( + style='margin: 1ex 1em', + create_spinner(), '\xa0' + _('Fetching metadata for the book, please wait') + '…', + )) + +def init(container_id): + q = parse_url_params() + current_book_id = q.book_id + container = document.getElementById(container_id) + create_top_bar(container, title=_('Book details'), action=back, icon='close') + if current_book_id is undefined or current_book_id is None: + no_book(container) + return + container.appendChild(E.div()) + current_book_id = int(current_book_id) + if current_book_id is not 0 and book_metadata(current_book_id): + render_book(container_id, current_book_id) + else: + if current_book_id is 0: + add_button(container, 'random', def(): fetch_metadata(container_id, 0);) + fetch_metadata(container_id, current_book_id) + + +set_panel_handler('book_details', init) diff --git a/src/pyj/book_list/library_data.pyj b/src/pyj/book_list/library_data.pyj index 1bdefc2b50..ba72ad79af 100644 --- a/src/pyj/book_list/library_data.pyj +++ b/src/pyj/book_list/library_data.pyj @@ -13,10 +13,15 @@ load_status = {'loading':True, 'ok':False, 'error_html':None, 'current_fetch': N library_data = {} +def current_library_id(): + q = parse_url_params() + return q.library_id or get_interface_data().default_library_id + + def url_books_query(sd): q = parse_url_params() sd = sd or get_session_data() - lid = q.library_id or get_interface_data().default_library_id + lid = current_library_id() return { 'library_id': lid, 'sort': q.sort or sd.get_library_option(lid, 'sort'), @@ -82,10 +87,24 @@ def thumbnail_url(book_id, width, height): return 'get/thumb/{}/{}?sz={}x{}'.format(book_id, loaded_books_query().library_id, Math.ceil(width * window.devicePixelRatio), Math.ceil(height * window.devicePixelRatio)) +def cover_url(book_id): + lid = current_library_id() + return f'get/cover/{book_id}/{lid}' + + +def download_url(book_id, fmt): + lid = current_library_id() + return f'get/{fmt}/{book_id}/{lid}}' + + def book_metadata(book_id): return library_data.metadata[book_id] +def set_book_metadata(book_id, value): + library_data.metadata[book_id] = value + + def ensure_current_library_data(): q = url_books_query() loaded = loaded_books_query() diff --git a/src/pyj/book_list/main.pyj b/src/pyj/book_list/main.pyj index e070ba0214..0c83399e60 100644 --- a/src/pyj/book_list/main.pyj +++ b/src/pyj/book_list/main.pyj @@ -22,6 +22,7 @@ from read_book.ui import ReadUI # Register the various panels import book_list.home # noqa: unused-import import book_list.views # noqa: unused-import +import book_list.book_details # noqa: unused-import def remove_initial_progress_bar(): diff --git a/src/pyj/book_list/views.pyj b/src/pyj/book_list/views.pyj index 9dc07c5596..f527a2b548 100644 --- a/src/pyj/book_list/views.pyj +++ b/src/pyj/book_list/views.pyj @@ -235,6 +235,12 @@ def create_sort_panel(container_id): create_item_list(container.lastChild, items, _('Change how the list of books is sorted')) # }}} +# Searching {{{ + +def search(query, replace=False): + pass + +# }}} set_panel_handler('book_list', init) set_panel_handler('book_list^sort', create_sort_panel)