Start work on UI for new server

This commit is contained in:
Kovid Goyal 2015-10-15 13:58:19 +05:30
parent c8ad7b203a
commit 0bbc30c07f
7 changed files with 110 additions and 25 deletions

Binary file not shown.

View File

@ -7,6 +7,8 @@ __license__ = 'GPL v3'
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
from functools import partial from functools import partial
from future_builtins import zip
from itertools import cycle
from calibre import force_unicode from calibre import force_unicode
from calibre.library.field_metadata import category_icon_map from calibre.library.field_metadata import category_icon_map
@ -15,6 +17,7 @@ from calibre.ebooks.metadata import title_sort
from calibre.ebooks.metadata.book.json_codec import JsonCodec from calibre.ebooks.metadata.book.json_codec import JsonCodec
from calibre.srv.errors import HTTPNotFound from calibre.srv.errors import HTTPNotFound
from calibre.srv.routes import endpoint, json from calibre.srv.routes import endpoint, json
from calibre.srv.session import defaults
from calibre.srv.content import get as get_content, icon as get_icon from calibre.srv.content import get as get_content, icon as get_icon
from calibre.srv.utils import http_date, custom_fields_to_display, encode_name, decode_name from calibre.srv.utils import http_date, custom_fields_to_display, encode_name, decode_name
from calibre.utils.config import prefs, tweaks from calibre.utils.config import prefs, tweaks
@ -520,6 +523,29 @@ def books_in(ctx, rd, encoded_category, encoded_item, library_id):
return result return result
# }}} # }}}
# Search {{{
def _search(ctx, rd, db, query, num, offset, sort, sort_order):
multisort = [(sanitize_sort_field_name(db.field_metadata, s), ensure_val(o, 'asc', 'desc'))
for s, o in zip(sort.split(','), cycle(sort_order.split(',')))]
skeys = db.field_metadata.sortable_field_keys()
for sfield, sorder in multisort:
if sfield not in skeys:
raise HTTPNotFound('%s is not a valid sort field'%sort)
if not query:
ids = ctx.allowed_book_ids(rd, db)
else:
ids = ctx.search(rd, db, query)
ids = db.multisort(fields=multisort, ids_to_sort=ids)
total_num = len(ids)
ids = ids[offset:offset+num]
return {
'total_num': total_num, 'sort_order':sort_order,
'offset':offset, 'num':len(ids), 'sort':sort,
'base_url':ctx.url_for(search, library_id=db.server_library_id),
'query': query,
'book_ids':ids
}
@endpoint('/ajax/search/{library_id=None}', postprocess=json) @endpoint('/ajax/search/{library_id=None}', postprocess=json)
def search(ctx, rd, library_id): def search(ctx, rd, library_id):
@ -529,26 +555,43 @@ def search(ctx, rd, library_id):
Optional: ?num=100&offset=0&sort=title&sort_order=asc&query= Optional: ?num=100&offset=0&sort=title&sort_order=asc&query=
''' '''
db = get_db(ctx, library_id) db = get_db(ctx, library_id)
query = rd.query.get('query')
num, offset = get_pagination(rd.query)
with db.safe_read_lock: with db.safe_read_lock:
query = rd.query.get('query') return _search(ctx, rd, db, query, num, offset, rd.query.get('sort', 'title'), rd.query.get('sort_order', 'asc'))
num, offset = get_pagination(rd.query) # }}}
sort, sort_order = rd.query.get('sort', 'title'), rd.query.get('sort_order')
sort_order = ensure_val(sort_order, 'asc', 'desc')
sfield = sanitize_sort_field_name(db.field_metadata, sort)
if sfield not in db.field_metadata.sortable_field_keys():
raise HTTPNotFound('%s is not a valid sort field'%sort)
if not query:
ids = ctx.allowed_book_ids(rd, db) @endpoint('/ajax/interface-data/{library_id=None}', postprocess=json)
else: def interface_data(ctx, rd, library_id):
ids = ctx.search(rd, db, query) '''
ids = db.multisort(fields=[(sfield, sort_order == 'asc')], ids_to_sort=ids) Return the data needed to create the server main UI
total_num = len(ids)
ids = ids[offset:offset+num] Optional: ?num=75
return { '''
'total_num': total_num, 'sort_order':sort_order, session = rd.session
'offset':offset, 'num':len(ids), 'sort':sort, ans = {'session_data': {k:session[k] for k in defaults.iterkeys()}}
'base_url':ctx.url_for(search, library_id=db.server_library_id), sorts, orders = [], []
'query': query, for x in ans['session_data']['sort'].split(','):
'book_ids':ids s, o = x.partition(':')[::2]
} sorts.append(s.strip()), orders.append(o.strip())
sort, sort_order = ans['session_data']['sort'].partition(',')[0].partition(':')[::2]
try:
num = int(rd.query.get('num', 75))
except Exception:
raise HTTPNotFound('Invalid number of books: %r' % rd.query.get('num'))
last_modified = None
db = get_db(ctx, library_id)
with db.safe_read_lock:
ans['search_result'] = _search(ctx, rd, db, '', num, 0, ','.join(sorts), ','.join(orders))
ans['field_metadata'] = db.field_metadata
# ans['categories'] = ctx.get_categories(rd, db)
mdata = ans['metadata'] = {}
for book_id in ans['search_result']['book_ids']:
data, lm = book_to_json(ctx, rd, db, book_id)
last_modified = lm if last_modified is None else max(lm, last_modified)
mdata[book_id] = data
if last_modified is not None:
rd.outheaders['Last-Modified'] = http_date(timestampfromdt(last_modified))
return ans

View File

@ -92,8 +92,8 @@ class Context(object):
return self.library_broker.get(library_id) return self.library_broker.get(library_id)
def allowed_book_ids(self, data, db): def allowed_book_ids(self, data, db):
# TODO: Implement this based on data.username caching result on the # TODO: Implement this based on data.username for per-user
# data object # restrictions. Cache result on the data object
with self.lock: with self.lock:
ans = data.allowed_book_ids.get(db.server_library_id) ans = data.allowed_book_ids.get(db.server_library_id)
if ans is None: if ans is None:

View File

@ -11,8 +11,9 @@ from threading import Lock
from calibre.utils.lru_cache import lru_cache from calibre.utils.lru_cache import lru_cache
defaults = { defaults = {
'sort': 'date:asc', 'sort': 'date:desc',
'library_id': None, 'library_id': None,
'view_mode': 'cover_grid',
} }
class Session(object): class Session(object):

14
src/pyj/ajax.pyj Normal file
View File

@ -0,0 +1,14 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
def ajax(path, on_complete, on_progress=None, bypass_cache=True):
xhr = XMLHttpRequest()
if bypass_cache:
path += ('&' if '?' in path else '?') + Date().getTime()
if on_progress:
xhr.addEventListener('progress', on_progress)
xhr.addEventListener('abort', def(ev): on_complete('abort', this, xhr);)
xhr.addEventListener('error', def(ev): on_complete('error', this, xhr);)
xhr.addEventListener('load', def(ev): on_complete('load', this, xhr);)
return xhr

17
src/pyj/dom.pyj Normal file
View File

@ -0,0 +1,17 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
simple_vendor_prefixes = {
'transform': v"['webkit', 'ms']",
}
def set_css(elem, **kw):
s = elem.style
if s:
for prop in kw:
name, val = str.replace(prop, '_', '-'), kw[prop]
s.setProperty(name, val)
prefixes = simple_vendor_prefixes[name]
if prefixes:
for prefix in prefixes:
s.setProperty('-' + prefix + '-' + name, val)

View File

@ -1,5 +1,15 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
alert('hello world!') from elementmaker import E
def create_page_load_progress_bar():
E.progress(id='library_load_bar')
def on_document_loaded():
alert(11111111)
# We wait for all page elements to load, since this is a single page app
# with a largely empty starting document, we can use this to preload any resources
# we know are going to be needed immediately.
window.addEventListener("load", on_document_loaded)