mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Implement changing sort order
This commit is contained in:
parent
50f058ed86
commit
bae7f40165
@ -16,6 +16,7 @@ from calibre.srv.errors import HTTPNotFound, HTTPBadRequest
|
|||||||
from calibre.srv.metadata import book_as_json
|
from calibre.srv.metadata import book_as_json
|
||||||
from calibre.srv.routes import endpoint, json
|
from calibre.srv.routes import endpoint, json
|
||||||
from calibre.utils.icu import sort_key
|
from calibre.utils.icu import sort_key
|
||||||
|
from calibre.utils.search_query_parser import ParseException
|
||||||
|
|
||||||
html_cache = {}
|
html_cache = {}
|
||||||
cache_lock = Lock()
|
cache_lock = Lock()
|
||||||
@ -67,7 +68,9 @@ def get_basic_query_data(ctx, query):
|
|||||||
sorts, orders = [], []
|
sorts, orders = [], []
|
||||||
for x in query.get('sort', '').split(','):
|
for x in query.get('sort', '').split(','):
|
||||||
if x:
|
if x:
|
||||||
s, o = x.partition('.')[::2]
|
s, o = x.rpartition('.')[::2]
|
||||||
|
if o and not s:
|
||||||
|
s, o = o, ''
|
||||||
if o not in ('asc', 'desc'):
|
if o not in ('asc', 'desc'):
|
||||||
o = 'asc'
|
o = 'asc'
|
||||||
if s.startswith('_'):
|
if s.startswith('_'):
|
||||||
@ -170,3 +173,29 @@ def set_session_data(ctx, rd):
|
|||||||
ud = ctx.user_manager.get_session_data(rd.username)
|
ud = ctx.user_manager.get_session_data(rd.username)
|
||||||
ud.update(new_data)
|
ud.update(new_data)
|
||||||
ctx.user_manager.set_session_data(rd.username, ud)
|
ctx.user_manager.set_session_data(rd.username, ud)
|
||||||
|
|
||||||
|
@endpoint('/interface-data/get-books', postprocess=json)
|
||||||
|
def get_books(ctx, rd):
|
||||||
|
'''
|
||||||
|
Get books for the specified query
|
||||||
|
|
||||||
|
Optional: ?library_id=<default library>&num=50&sort=timestamp.desc&search=''
|
||||||
|
'''
|
||||||
|
library_id, db, sorts, orders = get_basic_query_data(ctx, rd.query)
|
||||||
|
try:
|
||||||
|
num = int(rd.query.get('num', DEFAULT_NUMBER_OF_BOOKS))
|
||||||
|
except Exception:
|
||||||
|
raise HTTPNotFound('Invalid number of books: %r' % rd.query.get('num'))
|
||||||
|
searchq = rd.query.get('search', '')
|
||||||
|
db = get_library_data(ctx, rd.query)[0]
|
||||||
|
ans = {}
|
||||||
|
mdata = ans['metadata'] = {}
|
||||||
|
with db.safe_read_lock:
|
||||||
|
try:
|
||||||
|
ans['search_result'] = search_result(ctx, rd, db, searchq, num, 0, ','.join(sorts), ','.join(orders))
|
||||||
|
except ParseException as err:
|
||||||
|
raise HTTPBadRequest('Invalid search expression: %s' % as_unicode(err))
|
||||||
|
for book_id in ans['search_result']['book_ids']:
|
||||||
|
data = book_as_json(db, book_id)
|
||||||
|
mdata[book_id] = data
|
||||||
|
return ans
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
from book_list.ui import UI
|
from book_list.ui import UI
|
||||||
from modals import error_dialog
|
from modals import error_dialog
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
|
from book_list.globals import get_session_data
|
||||||
|
|
||||||
class Boss:
|
class Boss:
|
||||||
|
|
||||||
@ -29,3 +30,14 @@ class Boss:
|
|||||||
return True
|
return True
|
||||||
except:
|
except:
|
||||||
console.error('There was an error in the unhandled exception handler')
|
console.error('There was an error in the unhandled exception handler')
|
||||||
|
|
||||||
|
def change_books(self, data):
|
||||||
|
data.search_result.sort = str.split(data.search_result.sort, ',')[:2].join(',')
|
||||||
|
data.search_result.sort_order = str.split(data.search_result.sort_order, ',')[:2].join(',')
|
||||||
|
sval = ''
|
||||||
|
for field, order in zip(str.split(data.search_result.sort, ','), str.split(data.search_result.sort_order, ',')):
|
||||||
|
sval += field + '.' + order + ','
|
||||||
|
get_session_data().set('sort', str.rstrip(sval, ','))
|
||||||
|
self.interface_data.metadata = data.metadata
|
||||||
|
self.interface_data.search_result = data.search_result
|
||||||
|
self.ui.books_view.refresh()
|
||||||
|
@ -30,10 +30,11 @@ class ClosePanelBar(BarState):
|
|||||||
|
|
||||||
class UIState:
|
class UIState:
|
||||||
|
|
||||||
def __init__(self, top_bar_state=None, main_panel=None, panel_data=None):
|
def __init__(self, top_bar_state=None, main_panel=None, panel_data=None, is_cacheable=True):
|
||||||
self.top_bar_state = top_bar_state
|
self.top_bar_state = top_bar_state
|
||||||
self.main_panel = main_panel or get_boss().ui.items_view
|
self.main_panel = main_panel or get_boss().ui.items_view
|
||||||
self.panel_data = panel_data
|
self.panel_data = panel_data
|
||||||
|
self.is_cacheable = is_cacheable
|
||||||
|
|
||||||
def add_button(self, **kw):
|
def add_button(self, **kw):
|
||||||
self.top_bar_state.add_button(**kw)
|
self.top_bar_state.add_button(**kw)
|
||||||
@ -43,7 +44,9 @@ panels = {}
|
|||||||
def panel(key):
|
def panel(key):
|
||||||
ans = panels[key]
|
ans = panels[key]
|
||||||
if not ans:
|
if not ans:
|
||||||
ans = panels[key] = create_panel[key]()
|
ans = create_panel[key]()
|
||||||
|
if ans.is_cacheable:
|
||||||
|
panels[key] = ans
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def close_panel():
|
def close_panel():
|
||||||
@ -65,8 +68,12 @@ create_panel = {
|
|||||||
,
|
,
|
||||||
|
|
||||||
'booklist-mode-menu': def booklist_mode_menu():
|
'booklist-mode-menu': def booklist_mode_menu():
|
||||||
return UIState(ClosePanelBar(_('Book List Mode')), panel_data=[
|
return UIState(ClosePanelBar(_('Book List Mode')), panel_data=[])
|
||||||
])
|
,
|
||||||
|
|
||||||
|
'booklist-sort-menu': def change_booklist_sort():
|
||||||
|
data = get_boss().ui.books_view.sort_panel_data(create_item)
|
||||||
|
return UIState(ClosePanelBar(_('Sort books')), panel_data=data, is_cacheable=False)
|
||||||
,
|
,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +89,7 @@ class UI:
|
|||||||
self.books_view = BooksView(interface_data)
|
self.books_view = BooksView(interface_data)
|
||||||
self.items_view = ItemsView(interface_data)
|
self.items_view = ItemsView(interface_data)
|
||||||
ibs = BarState(run_animation=True)
|
ibs = BarState(run_animation=True)
|
||||||
ibs.add_button(icon_name='sort-alpha-asc', tooltip=_('Sort books'))
|
ibs.add_button(icon_name='sort-amount-desc', tooltip=_('Sort books'), action=show_panel_action('booklist-sort-menu'))
|
||||||
ibs.add_button(icon_name='search', tooltip=_('Search for books'))
|
ibs.add_button(icon_name='search', tooltip=_('Search for books'))
|
||||||
ibs.add_button(icon_name='ellipsis-v', tooltip=_('More actions'), action=show_panel_action('more-actions-menu'))
|
ibs.add_button(icon_name='ellipsis-v', tooltip=_('More actions'), action=show_panel_action('more-actions-menu'))
|
||||||
self.states.append(UIState(ibs, self.books_view))
|
self.states.append(UIState(ibs, self.books_view))
|
||||||
|
@ -5,9 +5,9 @@ from ajax import ajax_send
|
|||||||
from dom import set_css, build_rule, clear
|
from dom import set_css, build_rule, clear
|
||||||
from elementmaker import E
|
from elementmaker import E
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
from modals import error_dialog
|
from modals import error_dialog, ajax_progress_dialog
|
||||||
|
|
||||||
from book_list.globals import get_session_data
|
from book_list.globals import get_session_data, get_boss
|
||||||
from widgets import create_button, create_spinner
|
from widgets import create_button, create_spinner
|
||||||
|
|
||||||
THUMBNAIL_MAX_WIDTH = 300
|
THUMBNAIL_MAX_WIDTH = 300
|
||||||
@ -148,7 +148,7 @@ class BooksView:
|
|||||||
except Exception as err:
|
except Exception as err:
|
||||||
error_dialog(_('Could not get more books'), _('Server returned an invalid response'), err.stack or err.toString())
|
error_dialog(_('Could not get more books'), _('Server returned an invalid response'), err.stack or err.toString())
|
||||||
elif end_type != 'abort':
|
elif end_type != 'abort':
|
||||||
error_dialog(_('Could not get more books'), xhr.error_string)
|
error_dialog(_('Could not get more books'), xhr.error_html)
|
||||||
|
|
||||||
# Cover grid {{{
|
# Cover grid {{{
|
||||||
|
|
||||||
@ -186,3 +186,51 @@ class BooksView:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
def sort_panel_data(self, create_item):
|
||||||
|
current_sorted_field = str.partition(self.interface_data.search_result.sort, ',')[0]
|
||||||
|
current_sorted_field_order = str.partition(self.interface_data.search_result.sort_order, ',')[0]
|
||||||
|
new_sort_order = 'desc' if current_sorted_field_order == 'asc' else 'asc'
|
||||||
|
if current_sorted_field == 'date':
|
||||||
|
current_sorted_field = 'timestamp'
|
||||||
|
ans = []
|
||||||
|
ans.subtitle = _('Change how the list of books is sorted')
|
||||||
|
for field, name in self.interface_data.sortable_fields:
|
||||||
|
subtitle = icon_name = None
|
||||||
|
if field == current_sorted_field:
|
||||||
|
subtitle = _('Reverse current sort order')
|
||||||
|
icon_name = 'sort-amount-asc' if current_sorted_field_order == 'asc' else 'sort-amount-desc'
|
||||||
|
action = self.change_sort.bind(self, field, new_sort_order)
|
||||||
|
else:
|
||||||
|
action = self.change_sort.bind(self, field, None)
|
||||||
|
ans.push(create_item(name, subtitle=subtitle, icon_name=icon_name, action=action))
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def change_sort(self, field, order):
|
||||||
|
key = 'sort-order-for-' + field
|
||||||
|
sd = get_session_data()
|
||||||
|
order = order or sd.get(key, 'asc')
|
||||||
|
order = 'asc' if order == 'asc' else 'desc'
|
||||||
|
sd.set(key, order)
|
||||||
|
sr = self.interface_data.search_result
|
||||||
|
sort = field + '.' + order + ',' + sr.sort + '.' + sr.order
|
||||||
|
data = {'search':sr.query or '', 'sort':sort, 'num':self.shown_book_ids.length, 'library_id':self.interface_data.library_id}
|
||||||
|
ajax_progress_dialog('interface-data/get-books', self.sort_change_completed.bind(self), _(
|
||||||
|
'Fetching data from server, please wait') + '…', query=data)
|
||||||
|
|
||||||
|
def sort_change_completed(self, end_type, xhr, ev):
|
||||||
|
if end_type == 'load':
|
||||||
|
boss = get_boss()
|
||||||
|
try:
|
||||||
|
data = JSON.parse(xhr.responseText)
|
||||||
|
boss.change_books(data)
|
||||||
|
except Exception as err:
|
||||||
|
return error_dialog(_('Could not change sort order'), err + '', details=err.stack)
|
||||||
|
boss.ui.close_panel()
|
||||||
|
window.scrollTo(0, 0)
|
||||||
|
elif end_type != 'abort':
|
||||||
|
error_dialog(_('Could not change sort order'), xhr.error_html)
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
self.clear()
|
||||||
|
self.render_ids()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user