From d48c8e7a2be09ced85446b871cbb4eddafaa87d7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 23 Jan 2018 16:17:42 +0530 Subject: [PATCH] More work on adding books in the server UI --- src/pyj/ajax.pyj | 15 ++++++- src/pyj/book_list/add.pyj | 83 +++++++++++++++++++++++++++++++++++++ src/pyj/book_list/views.pyj | 9 ++-- src/pyj/file_uploads.pyj | 29 +++++++++---- 4 files changed, 124 insertions(+), 12 deletions(-) create mode 100644 src/pyj/book_list/add.pyj diff --git a/src/pyj/ajax.pyj b/src/pyj/ajax.pyj index 102c83e09e..70cdbc4820 100644 --- a/src/pyj/ajax.pyj +++ b/src/pyj/ajax.pyj @@ -113,7 +113,7 @@ def ajax(path, on_complete, on_progress=None, bypass_cache=True, method='GET', q xhr.timeout = timeout * 1000 # IE requires timeout to be set after open return xhr -def ajax_send(path, data, on_complete, on_progress=None, query=None, timeout=30*1000, ok_code=200): +def ajax_send(path, data, on_complete, on_progress=None, query=None, timeout=None, ok_code=200): # Unfortunately, browsers do not allow sending of data with HTTP GET, except # as query parameters, so we have to use POST xhr = ajax(path, on_complete, on_progress, False, 'POST', query, timeout, ok_code) @@ -122,6 +122,19 @@ def ajax_send(path, data, on_complete, on_progress=None, query=None, timeout=30* return xhr +def ajax_send_file(path, file, on_complete, on_progress, query=None, timeout=None, ok_code=200): + xhr = ajax(path, on_complete, on_progress, False, 'POST', query, timeout, ok_code) + if file.name: + xhr.setRequestHeader('Calibre-Filename', file.name) + if file.type: + xhr.overrideMimeType(file.type) + r = FileReader() + r.onload = def(evt): + xhr.send(evt.target.result) + r.readAsBinaryString(file) + return xhr + + def console_print(*args): ρσ_print(*args) # noqa: undef data = ' '.join(map(str, args)) + '\n' diff --git a/src/pyj/book_list/add.pyj b/src/pyj/book_list/add.pyj new file mode 100644 index 0000000000..2724ae0ab0 --- /dev/null +++ b/src/pyj/book_list/add.pyj @@ -0,0 +1,83 @@ +# vim:fileencoding=utf-8 +# License: GPL v3 Copyright: 2018, Kovid Goyal +from __python__ import bound_methods, hash_literals + +from gettext import gettext as _ + +from ajax import ajax_send_file +from book_list.router import back +from book_list.top_bar import create_top_bar +from dom import ensure_id +from file_uploads import ( + update_status_widget, upload_files_widget, upload_status_widget +) + +state = { + 'in_progress': False, + 'container_id': None, + 'counter': 0, + 'fake_send': False, +} + +def on_close(): + back() + + +def get_job_container(container_id, job_id): + container = document.getElementById(container_id) + if not container: + return + return container.querySelector(f'[data-job="{job_id}"]') + + +def on_progress(container_id, job_id): + container = get_job_container(container_id, job_id) + if container is None: + return + + +def on_complete(container_id, job_id): + container = get_job_container(container_id, job_id) + if container is None: + return + + +def fake_send(container_id, job_id): + container = get_job_container(container_id, job_id) + prev = parseInt(container.dataset.fake or '0') + container.dataset.fake = prev + 10 + update_status_widget(container, container.dataset.fake, 100) + if parseInt(container.dataset.fake or '0') < 100: + setTimeout(fake_send.bind(None, container_id, job_id), 1000) + + +def files_chosen(container_id, files): + container = document.getElementById(container_id) + if not container: + return + for file in files: + state.counter += 1 + job_id = state.counter + w = upload_status_widget(file.name, job_id) + container.appendChild(w) + w.style.borderBottom = 'solid 1px currentColor' + if state.fake_send: + setTimeout(fake_send.bind(None, container_id, job_id), 100) + else: + ajax_send_file('/add-book', file, on_complete.bind(None, container_id, job_id), on_progress.bind(None, container_id, job_id), query={'id': '' + job_id}) + + +def add_books_panel(container_id): + container = document.getElementById(container_id) + create_top_bar(container, title=_('Add books'), action=on_close, icon='close') + state.in_progress = True + state.container_id = container_id + state.fake_send = False + return upload_files_widget(container, files_chosen) + + +def develop(container): + c = add_books_panel(ensure_id(container)) + state.fake_send = True + container.removeChild(c) + files_chosen(container.id, [{'name': 'test1.epub', 'type': 'application/epub+zip'}, {'name': 'xxxx.yyy'}]) diff --git a/src/pyj/book_list/views.pyj b/src/pyj/book_list/views.pyj index e74decb853..11e1eb463d 100644 --- a/src/pyj/book_list/views.pyj +++ b/src/pyj/book_list/views.pyj @@ -7,6 +7,7 @@ from elementmaker import E from gettext import gettext as _ from ajax import ajax_send +from book_list.add import add_books_panel from book_list.cover_grid import ( DESCRIPTION as COVER_GRID_DESCRIPTION, append_item as cover_grid_append_item, cover_grid_css, create_item as create_cover_grid_item, init as init_cover_grid @@ -35,7 +36,6 @@ from book_list.search import ( from book_list.top_bar import add_button, create_top_bar from book_list.ui import query_as_href, set_panel_handler, show_panel from dom import add_extra_css, build_rule, clear, ensure_id, set_css -from file_uploads import upload_files_widget from modals import error_dialog from session import get_interface_data from utils import conditional_timeout, parse_url_params, safe_set_inner_html @@ -452,8 +452,11 @@ def create_more_actions_panel(container_id): # }}} # Adding books {{{ -def implement_me(): - upload_files_widget() +def add_books(container_id): + if not library_data.sortable_fields: + show_panel('book_list', replace=True) + return + add_books_panel(container_id) # }}} diff --git a/src/pyj/file_uploads.pyj b/src/pyj/file_uploads.pyj index 56f3a189a5..762b1071a6 100644 --- a/src/pyj/file_uploads.pyj +++ b/src/pyj/file_uploads.pyj @@ -5,7 +5,7 @@ from __python__ import bound_methods, hash_literals from elementmaker import E from gettext import gettext as _ -from utils import safe_set_inner_html +from utils import safe_set_inner_html, human_readable def upload_files_widget(container, proceed, msg): @@ -15,12 +15,13 @@ def upload_files_widget(container, proceed, msg): files = this.files container = document.getElementById(container_id) container.removeChild(container.lastChild) - if callable(proceed): - proceed(container, files) + if callable(proceed) and files.length: + proceed(container_id, files) msg = msg or _('Upload books by selecting the book files or drag and drop of the files here') c = E.div(E.span(), E.input(type='file', multiple=True, style='display:none', onchange=files_selected)) - c.style.minHeight = '40vh' + c.style.minHeight = '80vh' + c.style.padding = '1rem' c.style.borderBottom = 'solid 1px currentColor' safe_set_inner_html(c.firstChild, msg) @@ -47,9 +48,21 @@ def upload_files_widget(container, proceed, msg): return c +def update_status_widget(w, sent, total): + if total: + p = w.getElementsByTagName('progress')[0] + p.setAttribute('value', '' + sent) + p.setAttribute('max', '' + total) + w.lastChild.textContent = _(' {percent:.0%} of {total}').format(percent=sent / total, total=human_readable(total)) + + +def upload_status_widget(name, job_id): + ans = E.div(style='padding: 1rem 1ex;', data_job='' + job_id) + ans.appendChild(E.h3(E.b(name))) + ans.appendChild(E.progress()) + ans.appendChild(E.span()) + return ans + + def start_send(container, files): pass - - -def develop(container): - upload_files_widget(container)