mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Work on refactoring the books view
This commit is contained in:
parent
8cb4545b96
commit
ab332d0b99
@ -113,6 +113,52 @@ def update_interface_data(ctx, rd):
|
||||
return basic_interface_data(ctx, rd)
|
||||
|
||||
|
||||
def get_library_init_data(ctx, rd, db, num, sorts, orders):
|
||||
ans = {}
|
||||
with db.safe_read_lock:
|
||||
try:
|
||||
ans['search_result'] = search_result(ctx, rd, db, rd.query.get('search', ''), num, 0, ','.join(sorts), ','.join(orders))
|
||||
except ParseException:
|
||||
ans['search_result'] = search_result(ctx, rd, db, '', num, 0, ','.join(sorts), ','.join(orders))
|
||||
sf = db.field_metadata.ui_sortable_field_keys()
|
||||
sf.pop('ondevice', None)
|
||||
ans['sortable_fields'] = sorted(((
|
||||
sanitize_sort_field_name(db.field_metadata, k), v) for k, v in sf.iteritems()),
|
||||
key=lambda (field, name):sort_key(name))
|
||||
ans['field_metadata'] = db.field_metadata.all_metadata()
|
||||
mdata = ans['metadata'] = {}
|
||||
try:
|
||||
extra_books = set(int(x) for x in rd.query.get('extra_books', '').split(','))
|
||||
except Exception:
|
||||
extra_books = ()
|
||||
for coll in (ans['search_result']['book_ids'], extra_books):
|
||||
for book_id in coll:
|
||||
if book_id not in mdata:
|
||||
data = book_as_json(db, book_id)
|
||||
if data is not None:
|
||||
mdata[book_id] = data
|
||||
return ans
|
||||
|
||||
|
||||
@endpoint('/interface-data/books-init', postprocess=json)
|
||||
def books(ctx, rd):
|
||||
'''
|
||||
Get data to create list of books
|
||||
|
||||
Optional: ?num=50&sort=timestamp.desc&library_id=<default library>
|
||||
&search=''&extra_books=''
|
||||
'''
|
||||
ans = {}
|
||||
try:
|
||||
num = int(rd.query.get('num', DEFAULT_NUMBER_OF_BOOKS))
|
||||
except Exception:
|
||||
raise HTTPNotFound('Invalid number of books: %r' % rd.query.get('num'))
|
||||
library_id, db, sorts, orders = get_basic_query_data(ctx, rd)
|
||||
ans = get_library_init_data(ctx, rd, db, num, sorts, orders)
|
||||
ans['library_id'] = library_id
|
||||
return ans
|
||||
|
||||
|
||||
@endpoint('/interface-data/init', postprocess=json)
|
||||
def interface_data(ctx, rd):
|
||||
'''
|
||||
@ -139,29 +185,7 @@ def interface_data(ctx, rd):
|
||||
num = int(rd.query.get('num', DEFAULT_NUMBER_OF_BOOKS))
|
||||
except Exception:
|
||||
raise HTTPNotFound('Invalid number of books: %r' % rd.query.get('num'))
|
||||
with db.safe_read_lock:
|
||||
try:
|
||||
ans['search_result'] = search_result(ctx, rd, db, rd.query.get('search', ''), num, 0, ','.join(sorts), ','.join(orders))
|
||||
except ParseException:
|
||||
ans['search_result'] = search_result(ctx, rd, db, '', num, 0, ','.join(sorts), ','.join(orders))
|
||||
sf = db.field_metadata.ui_sortable_field_keys()
|
||||
sf.pop('ondevice', None)
|
||||
ans['sortable_fields'] = sorted(((
|
||||
sanitize_sort_field_name(db.field_metadata, k), v) for k, v in sf.iteritems()),
|
||||
key=lambda (field, name):sort_key(name))
|
||||
ans['field_metadata'] = db.field_metadata.all_metadata()
|
||||
mdata = ans['metadata'] = {}
|
||||
try:
|
||||
extra_books = set(int(x) for x in rd.query.get('extra_books', '').split(','))
|
||||
except Exception:
|
||||
extra_books = ()
|
||||
for coll in (ans['search_result']['book_ids'], extra_books):
|
||||
for book_id in coll:
|
||||
if book_id not in mdata:
|
||||
data = book_as_json(db, book_id)
|
||||
if data is not None:
|
||||
mdata[book_id] = data
|
||||
|
||||
ans.update(get_library_init_data(ctx, rd, db, num, sorts, orders))
|
||||
return ans
|
||||
|
||||
|
||||
|
@ -11,7 +11,7 @@ THUMBNAIL_MAX_HEIGHT = 400
|
||||
BORDER_RADIUS = 10
|
||||
|
||||
def cover_grid_css():
|
||||
sel = '#' + this
|
||||
sel = '.' + this
|
||||
ans = build_rule(sel, display='flex', flex_wrap='wrap', justify_content='space-around', align_items='flex-end', align_content='flex-start', user_select='none', overflow='hidden')
|
||||
|
||||
# Container for an individual cover
|
||||
@ -65,4 +65,3 @@ def create_item(book_id, interface_data, onclick):
|
||||
def append_item(container, item):
|
||||
first_filler = container.lastChild.querySelector('.cover-grid-filler')
|
||||
container.lastChild.insertBefore(item, first_filler)
|
||||
|
||||
|
@ -4,10 +4,12 @@ from __python__ import hash_literals, bound_methods
|
||||
|
||||
from dom import ensure_id
|
||||
from elementmaker import E
|
||||
from session import get_interface_data
|
||||
from gettext import gettext as _
|
||||
|
||||
from book_list.globals import get_db
|
||||
from book_list.top_bar import create_top_bar
|
||||
from book_list.ui import set_default_panel_handler
|
||||
from book_list.ui import set_default_panel_handler, show_panel
|
||||
|
||||
|
||||
def show_recent():
|
||||
@ -23,9 +25,27 @@ def show_recent():
|
||||
def init(container_id):
|
||||
create_top_bar(container_id, run_animation=True)
|
||||
container = document.getElementById(container_id)
|
||||
interface_data = get_interface_data()
|
||||
|
||||
# Recent books
|
||||
recent = E.div(style='display:none')
|
||||
recent_container_id = ensure_id(recent)
|
||||
container.appendChild(recent)
|
||||
window.setTimeout(show_recent.bind(recent_container_id), 5)
|
||||
|
||||
# Choose library
|
||||
cl = E.div(
|
||||
E.h2(_('Choose the calibre library to browse...'))
|
||||
)
|
||||
container.appendChild(cl)
|
||||
lids = sorted(interface_data.library_map, key=def(x): return interface_data.library_map[x];)
|
||||
for library_id in lids:
|
||||
library_name = interface_data.library_map[library_id]
|
||||
if library_name:
|
||||
cl.appendChild(E.div(E.a(library_name, href='javascript: void(0)', title=library_name, data_lid=library_id, onclick=def(ev):
|
||||
lib_id = ev.currentTarget.dataSet.lid
|
||||
show_panel('book_list', {'library_id': lib_id})
|
||||
)))
|
||||
|
||||
|
||||
set_default_panel_handler(init)
|
||||
|
46
src/pyj/book_list/library_data.pyj
Normal file
46
src/pyj/book_list/library_data.pyj
Normal file
@ -0,0 +1,46 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
from __python__ import hash_literals, bound_methods
|
||||
|
||||
from ajax import ajax
|
||||
from session import get_interface_data
|
||||
from utils import parse_url_params
|
||||
|
||||
|
||||
load_status = {'loading':True, 'ok':False, 'error_html':None, 'current_fetch': None}
|
||||
library_data = {}
|
||||
current_fetch = None
|
||||
|
||||
|
||||
def current_library_id():
|
||||
return parse_url_params().library_id or get_interface_data().library_id
|
||||
|
||||
|
||||
def update_library_data(data):
|
||||
load_status.loading = False
|
||||
load_status.ok = True
|
||||
load_status.error_html = None
|
||||
for key in 'search_result sortable_fields field_metadata metadata'.split(' '):
|
||||
library_data[key] = data[key]
|
||||
|
||||
|
||||
def on_data_loaded(end_type, xhr, ev):
|
||||
load_status.current_fetch = None
|
||||
if end_type is 'load':
|
||||
data = JSON.parse(xhr.responseText)
|
||||
update_library_data(data)
|
||||
else:
|
||||
load_status.ok = False
|
||||
load_status.loading = False
|
||||
load_status.error_html = xhr.error_html
|
||||
|
||||
|
||||
def fetch_init_data():
|
||||
if load_status.current_fetch:
|
||||
load_status.current_fetch.abort()
|
||||
query = {'library_id': current_library_id()}
|
||||
url_query = parse_url_params()
|
||||
for key in url_query:
|
||||
query[key] = url_query[key]
|
||||
load_status.current_fetch = ajax('interface-data/books-init', on_data_loaded, query=query)
|
||||
load_status.current_fetch.send()
|
@ -13,14 +13,16 @@ from popups import install_event_filters
|
||||
from utils import parse_url_params
|
||||
|
||||
from book_list.constants import book_list_container_id, read_book_container_id
|
||||
from book_list.library_data import fetch_init_data
|
||||
from book_list.theme import get_color
|
||||
from book_list.router import update_window_title, set_default_mode_handler, apply_url, set_mode_handler
|
||||
from book_list.router import update_window_title, set_default_mode_handler, apply_url, set_mode_handler, on_pop_state
|
||||
from book_list.globals import get_db, set_session_data
|
||||
from book_list.ui import apply_url_state as book_list_mode_handler
|
||||
from read_book.ui import ReadUI
|
||||
|
||||
# Register the various panels
|
||||
import book_list.home # noqa: unused-import
|
||||
import book_list.views # noqa: unused-import
|
||||
|
||||
|
||||
def remove_initial_progress_bar():
|
||||
@ -48,6 +50,10 @@ def init_ui():
|
||||
install_event_filters()
|
||||
set_default_mode_handler(book_list_mode_handler)
|
||||
window.onerror = onerror
|
||||
setTimeout(def():
|
||||
window.onpopstate = on_pop_state
|
||||
, 0) # We do this after event loop ticks over to avoid catching popstate events that some browsers send on page load
|
||||
|
||||
translations = get_translations()
|
||||
if translations:
|
||||
install(translations)
|
||||
@ -65,6 +71,7 @@ def init_ui():
|
||||
set_mode_handler('read_book', read_ui.apply_url_state.bind(read_ui))
|
||||
apply_url()
|
||||
|
||||
|
||||
def on_data_loaded(end_type, xhr, ev):
|
||||
remove_initial_progress_bar()
|
||||
if end_type is 'load':
|
||||
@ -84,11 +91,13 @@ def on_data_loaded(end_type, xhr, ev):
|
||||
p.innerHTML = xhr.error_html
|
||||
document.body.appendChild(p)
|
||||
|
||||
|
||||
def on_data_load_progress(loaded, total):
|
||||
p = document.querySelector('#page_load_progress > progress')
|
||||
p.max = total
|
||||
p.value = loaded
|
||||
|
||||
|
||||
def load_interface_data():
|
||||
temp = UserSessionData(None, {}) # So that settings for anonymous users are preserved
|
||||
query = {}
|
||||
@ -102,7 +111,7 @@ def load_interface_data():
|
||||
ajax('interface-data/init', on_data_loaded, on_data_load_progress, query=query).send()
|
||||
|
||||
|
||||
def on_update_interface_data():
|
||||
def do_update_interface_data():
|
||||
ajax('interface-data/update', def (end_type, xhr, ev):
|
||||
if end_type is 'load':
|
||||
data = JSON.parse(xhr.responseText)
|
||||
@ -117,5 +126,6 @@ def main():
|
||||
else:
|
||||
sd = UserSessionData(interface_data.username, interface_data.user_session_data)
|
||||
set_session_data(sd)
|
||||
on_update_interface_data()
|
||||
do_update_interface_data()
|
||||
fetch_init_data()
|
||||
init_ui()
|
||||
|
@ -2,6 +2,8 @@
|
||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
from __python__ import hash_literals, bound_methods
|
||||
|
||||
from ajax import encode_query
|
||||
|
||||
from book_list.constants import read_book_container_id, book_list_container_id
|
||||
from book_list.globals import get_current_query
|
||||
from utils import parse_url_params
|
||||
@ -26,7 +28,7 @@ def update_window_title(subtitle, title='calibre', sep=' :: '):
|
||||
|
||||
|
||||
def is_reading_book():
|
||||
cq = get_current_query
|
||||
cq = get_current_query()
|
||||
return cq and cq.mode is read_book_mode
|
||||
|
||||
|
||||
@ -36,17 +38,42 @@ def apply_mode(mode):
|
||||
div.style.display = 'block' if div.id is divid else 'none'
|
||||
|
||||
|
||||
def apply_url():
|
||||
def apply_url(ignore_handler):
|
||||
data = parse_url_params()
|
||||
data.mode = data.mode or 'book_list'
|
||||
get_current_query(data)
|
||||
apply_mode()
|
||||
handler = mode_handlers[data.mode] or default_mode_handler
|
||||
handler(data)
|
||||
if not ignore_handler:
|
||||
handler = mode_handlers[data.mode] or default_mode_handler
|
||||
handler(data)
|
||||
|
||||
|
||||
def push_state(query, replace=False, mode='book_list'):
|
||||
history_count = 0
|
||||
|
||||
|
||||
def push_state(query, replace=False, mode='book_list', call_handler=True):
|
||||
nonlocal history_count
|
||||
query = {k:query[k] for k in query}
|
||||
if mode is not 'book_list':
|
||||
query.mode = mode
|
||||
# TODO: Implement this (see push_state in boos.pyj)
|
||||
query = encode_query(query) or '?'
|
||||
if replace:
|
||||
window.history.replaceState(None, '', query)
|
||||
else:
|
||||
window.history.pushState(None, '', query)
|
||||
history_count += 1
|
||||
apply_url(not call_handler)
|
||||
|
||||
|
||||
def on_pop_state(ev):
|
||||
nonlocal history_count
|
||||
history_count = max(0, history_count - 1)
|
||||
apply_url()
|
||||
|
||||
|
||||
def back():
|
||||
nonlocal history_count
|
||||
if history_count > 0:
|
||||
window.back()
|
||||
else:
|
||||
push_state({}, replace=True)
|
||||
|
@ -7,6 +7,8 @@ from elementmaker import E
|
||||
|
||||
from book_list.constants import book_list_container_id
|
||||
from book_list.globals import get_current_query
|
||||
from book_list.router import push_state, back as router_back
|
||||
from book_list.library_data import current_library_id
|
||||
|
||||
|
||||
panel_handlers = {}
|
||||
@ -38,13 +40,51 @@ def develop_panel(container):
|
||||
set_panel_handler('develop-widgets', develop_panel)
|
||||
|
||||
|
||||
def currently_showing_panel():
|
||||
c = document.getElementById(book_list_container_id)
|
||||
return c.dataset.panel
|
||||
|
||||
def number_of_subpanels():
|
||||
c = document.getElementById(book_list_container_id)
|
||||
return c.lastChild.childNodes.length
|
||||
|
||||
|
||||
def show_panel(panel, query_data, replace=False):
|
||||
if currently_showing_panel() is panel:
|
||||
return
|
||||
query = {k:query_data[k] for k in query}
|
||||
if panel is not 'home':
|
||||
lid = current_library_id()
|
||||
if lid:
|
||||
query.library_id = lid
|
||||
push_state(query, replace=replace)
|
||||
|
||||
|
||||
def close_subpanel():
|
||||
c = document.getElementById(book_list_container_id).lastChild
|
||||
panel_to_remove = c.lastChild
|
||||
if panel_to_remove:
|
||||
c.remove(panel_to_remove)
|
||||
if c.lastChild:
|
||||
c.lastChild.style.display = 'block'
|
||||
else:
|
||||
c.style.display = 'none'
|
||||
|
||||
|
||||
def back():
|
||||
if number_of_subpanels() > 0:
|
||||
return close_subpanel()
|
||||
router_back()
|
||||
|
||||
|
||||
def apply_url_state(state):
|
||||
panel = state.panel or 'home'
|
||||
c = document.getElementById(book_list_container_id)
|
||||
if c.dataset.panel is panel:
|
||||
if currently_showing_panel() is panel:
|
||||
return
|
||||
c = document.getElementById(book_list_container_id)
|
||||
clear(c)
|
||||
c.appendChild(E.div()), c.appendChild(E.div(style='display:none'))
|
||||
c.appendChild(E.div())
|
||||
c.appendChild(E.div(style='display:none'))
|
||||
c.dataset.panel = panel
|
||||
handler = panel_handlers[panel] or default_panel_handler
|
||||
handler(ensure_id(c.firstChild, 'panel'))
|
||||
|
@ -4,25 +4,31 @@ from __python__ import hash_literals
|
||||
|
||||
import traceback
|
||||
from ajax import ajax_send
|
||||
from dom import set_css, add_extra_css, unique_id
|
||||
from dom import set_css, add_extra_css
|
||||
from elementmaker import E
|
||||
from gettext import gettext as _
|
||||
from modals import error_dialog, ajax_progress_dialog
|
||||
|
||||
from book_list.globals import get_session_data, get_boss
|
||||
from book_list.globals import get_session_data
|
||||
from book_list.cover_grid import cover_grid_css, create_item as create_cover_grid_item, init as init_cover_grid, append_item as cover_grid_append_item
|
||||
from book_list.top_bar import create_top_bar
|
||||
from book_list.ui import back, set_panel_handler
|
||||
from widgets import create_button, create_spinner
|
||||
|
||||
bv_counter = 0
|
||||
|
||||
CLASS_NAME = 'books-main-list'
|
||||
COVER_GRID = unique_id('cover-grid')
|
||||
COVER_GRID_CLASS = 'book-list-cover-grid'
|
||||
|
||||
add_extra_css(def():
|
||||
ans = cover_grid_css.call(COVER_GRID)
|
||||
ans = cover_grid_css.call(COVER_GRID_CLASS)
|
||||
return ans
|
||||
)
|
||||
|
||||
def init(container_id):
|
||||
create_top_bar(container_id, title=_('Books'), action=back, icon='close')
|
||||
|
||||
|
||||
set_panel_handler('book_list', init)
|
||||
|
||||
|
||||
class BooksView:
|
||||
|
||||
def __init__(self, interface_data, book_list_container):
|
||||
|
@ -25,13 +25,18 @@ def debounce(func, wait, immediate=False):
|
||||
func.apply(context, args)
|
||||
|
||||
def parse_url_params(url=None, allow_multiple=False):
|
||||
cache = parse_url_params.cache
|
||||
url = url or window.location.href
|
||||
if cache[url]:
|
||||
return parse_url_params.cache[url]
|
||||
qs = url.indexOf('?')
|
||||
ans = {}
|
||||
if qs < 0:
|
||||
cache[url] = ans
|
||||
return ans
|
||||
q = url.slice(qs + 1, ((url.indexOf('#') + 1) or (url.length + 1)))
|
||||
if not q:
|
||||
cache[url] = ans
|
||||
return ans
|
||||
pairs = q.replace(/\+/g, " ").split("&")
|
||||
for pair in pairs:
|
||||
@ -43,7 +48,9 @@ def parse_url_params(url=None, allow_multiple=False):
|
||||
ans[key].append(val)
|
||||
else:
|
||||
ans[key] = val
|
||||
cache[url] = ans
|
||||
return ans
|
||||
parse_url_params.cache = {}
|
||||
|
||||
_roman = list(zip(
|
||||
[1000,900,500,400,100,90,50,40,10,9,5,4,1],
|
||||
|
Loading…
x
Reference in New Issue
Block a user