mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
Start work on UI for new server
This commit is contained in:
parent
c8ad7b203a
commit
0bbc30c07f
Binary file not shown.
@ -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,29 +523,20 @@ def books_in(ctx, rd, encoded_category, encoded_item, library_id):
|
|||||||
return result
|
return result
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
# Search {{{
|
||||||
@endpoint('/ajax/search/{library_id=None}', postprocess=json)
|
def _search(ctx, rd, db, query, num, offset, sort, sort_order):
|
||||||
def search(ctx, rd, library_id):
|
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(',')))]
|
||||||
Return the books (as list of ids) matching the specified search query.
|
skeys = db.field_metadata.sortable_field_keys()
|
||||||
|
for sfield, sorder in multisort:
|
||||||
Optional: ?num=100&offset=0&sort=title&sort_order=asc&query=
|
if sfield not in skeys:
|
||||||
'''
|
|
||||||
db = get_db(ctx, library_id)
|
|
||||||
with db.safe_read_lock:
|
|
||||||
query = rd.query.get('query')
|
|
||||||
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)
|
raise HTTPNotFound('%s is not a valid sort field'%sort)
|
||||||
|
|
||||||
if not query:
|
if not query:
|
||||||
ids = ctx.allowed_book_ids(rd, db)
|
ids = ctx.allowed_book_ids(rd, db)
|
||||||
else:
|
else:
|
||||||
ids = ctx.search(rd, db, query)
|
ids = ctx.search(rd, db, query)
|
||||||
ids = db.multisort(fields=[(sfield, sort_order == 'asc')], ids_to_sort=ids)
|
ids = db.multisort(fields=multisort, ids_to_sort=ids)
|
||||||
total_num = len(ids)
|
total_num = len(ids)
|
||||||
ids = ids[offset:offset+num]
|
ids = ids[offset:offset+num]
|
||||||
return {
|
return {
|
||||||
@ -552,3 +546,52 @@ def search(ctx, rd, library_id):
|
|||||||
'query': query,
|
'query': query,
|
||||||
'book_ids':ids
|
'book_ids':ids
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@endpoint('/ajax/search/{library_id=None}', postprocess=json)
|
||||||
|
def search(ctx, rd, library_id):
|
||||||
|
'''
|
||||||
|
Return the books (as list of ids) matching the specified search query.
|
||||||
|
|
||||||
|
Optional: ?num=100&offset=0&sort=title&sort_order=asc&query=
|
||||||
|
'''
|
||||||
|
db = get_db(ctx, library_id)
|
||||||
|
query = rd.query.get('query')
|
||||||
|
num, offset = get_pagination(rd.query)
|
||||||
|
with db.safe_read_lock:
|
||||||
|
return _search(ctx, rd, db, query, num, offset, rd.query.get('sort', 'title'), rd.query.get('sort_order', 'asc'))
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
|
@endpoint('/ajax/interface-data/{library_id=None}', postprocess=json)
|
||||||
|
def interface_data(ctx, rd, library_id):
|
||||||
|
'''
|
||||||
|
Return the data needed to create the server main UI
|
||||||
|
|
||||||
|
Optional: ?num=75
|
||||||
|
'''
|
||||||
|
session = rd.session
|
||||||
|
ans = {'session_data': {k:session[k] for k in defaults.iterkeys()}}
|
||||||
|
sorts, orders = [], []
|
||||||
|
for x in ans['session_data']['sort'].split(','):
|
||||||
|
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
|
||||||
|
@ -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:
|
||||||
|
@ -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
14
src/pyj/ajax.pyj
Normal 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
17
src/pyj/dom.pyj
Normal 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)
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user