mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
Allow changing cover on the EM page
This commit is contained in:
parent
4c01e9eb65
commit
0e63bcc709
@ -5,6 +5,7 @@
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
import os
|
||||
from base64 import standard_b64decode
|
||||
from functools import partial
|
||||
from io import BytesIO
|
||||
|
||||
@ -13,10 +14,11 @@ from calibre.db.cli import module_for_cmd
|
||||
from calibre.ebooks.metadata.meta import get_metadata
|
||||
from calibre.srv.changes import books_added, books_deleted, metadata
|
||||
from calibre.srv.errors import HTTPBadRequest, HTTPForbidden, HTTPNotFound
|
||||
from calibre.srv.metadata import book_as_json
|
||||
from calibre.srv.routes import endpoint, json, msgpack_or_json
|
||||
from calibre.srv.utils import get_db, get_library_data
|
||||
from calibre.utils.imghdr import what
|
||||
from calibre.utils.serialize import MSGPACK_MIME, json_loads, msgpack_loads
|
||||
from calibre.srv.metadata import book_as_json
|
||||
|
||||
receive_data_methods = {'GET', 'POST'}
|
||||
|
||||
@ -110,6 +112,17 @@ def cdb_delete_book(ctx, rd, book_ids, library_id):
|
||||
return {}
|
||||
|
||||
|
||||
@endpoint('/cdb/set-cover/{book_id}/{library_id=None}', types={'book_id': int},
|
||||
needs_db_write=True, postprocess=json, methods=receive_data_methods, cache_control='no-cache')
|
||||
def cdb_set_cover(ctx, rd, book_id, library_id):
|
||||
db = get_db(ctx, rd, library_id)
|
||||
if ctx.restriction_for(rd, db):
|
||||
raise HTTPForbidden('Cannot use the add book interface with a user who has per library restrictions')
|
||||
rd.request_body_file.seek(0)
|
||||
dirtied = db.set_cover({book_id: rd.request_body_file})
|
||||
return tuple(dirtied)
|
||||
|
||||
|
||||
@endpoint('/cdb/set-fields/{book_id}/{library_id=None}', types={'book_id': int},
|
||||
needs_db_write=True, postprocess=msgpack_or_json, methods=receive_data_methods, cache_control='no-cache')
|
||||
def cdb_set_fields(ctx, rd, book_id, library_id):
|
||||
@ -130,6 +143,21 @@ def cdb_set_fields(ctx, rd, book_id, library_id):
|
||||
except Exception:
|
||||
raise HTTPBadRequest('Invalid encoded data')
|
||||
dirtied = set()
|
||||
cdata = changes.pop('cover', False)
|
||||
if cdata is not False:
|
||||
if cdata is not None:
|
||||
try:
|
||||
cdata = standard_b64decode(cdata.split(',', 1)[-1].encode('ascii'))
|
||||
except Exception:
|
||||
raise HTTPBadRequest('Cover data is not valid base64 encoded data')
|
||||
try:
|
||||
fmt = what(None, cdata)
|
||||
except Exception:
|
||||
fmt = None
|
||||
if fmt not in ('jpeg', 'png'):
|
||||
raise HTTPBadRequest('Cover data must be either JPEG or PNG')
|
||||
dirtied |= db.set_cover({book_id: cdata})
|
||||
|
||||
for field, value in changes.iteritems():
|
||||
dirtied |= db.set_field(field, {book_id: value})
|
||||
metadata(dirtied)
|
||||
|
@ -12,8 +12,8 @@ from book_list.book_details import (
|
||||
report_load_failure
|
||||
)
|
||||
from book_list.library_data import (
|
||||
book_metadata, current_library_id, field_names_for, library_data, load_status,
|
||||
loaded_book_ids, set_book_metadata
|
||||
book_metadata, cover_url, current_library_id, field_names_for, library_data,
|
||||
load_status, loaded_book_ids, set_book_metadata
|
||||
)
|
||||
from book_list.router import back
|
||||
from book_list.theme import get_color
|
||||
@ -21,6 +21,9 @@ from book_list.top_bar import create_top_bar, set_title
|
||||
from book_list.ui import set_panel_handler, show_panel
|
||||
from date import UNDEFINED_DATE_ISO, format_date
|
||||
from dom import add_extra_css, build_rule, clear, svgicon
|
||||
from file_uploads import (
|
||||
update_status_widget, upload_files_widget, upload_status_widget
|
||||
)
|
||||
from modals import error_dialog
|
||||
from session import get_interface_data
|
||||
from utils import (
|
||||
@ -474,6 +477,29 @@ def bool_edit(container_id, book_id, field, fm, div, mi):
|
||||
return val_map[x]
|
||||
# }}}
|
||||
|
||||
# Cover edit {{{
|
||||
|
||||
def cover_chosen(top_container_id, book_id, container_id, files):
|
||||
nonlocal has_changes
|
||||
container = document.getElementById(container_id)
|
||||
if not container:
|
||||
return
|
||||
if not files[0]:
|
||||
return
|
||||
file = files[0]
|
||||
changes.cover = file
|
||||
has_changes = True
|
||||
on_close(top_container_id)
|
||||
cover_chosen.counter = 0
|
||||
|
||||
|
||||
def cover_edit(container_id, book_id, field, fm, div, mi):
|
||||
upload_files_widget(div, cover_chosen.bind(None, container_id, book_id), _(
|
||||
'Change the cover by <a>selecting the cover image</a> or drag and drop of the cover image here.'),
|
||||
single_file=True, accept_extensions='png jpeg jpg')
|
||||
|
||||
# }}}
|
||||
|
||||
def edit_field(container_id, book_id, field):
|
||||
nonlocal value_to_json
|
||||
fm = library_data.field_metadata[field]
|
||||
@ -491,6 +517,8 @@ def edit_field(container_id, book_id, field):
|
||||
update_completions.prefix = ''
|
||||
if field is 'authors':
|
||||
multiple_line_edit(' & ', '&', container_id, book_id, field, fm, d, mi)
|
||||
elif field is 'cover':
|
||||
cover_edit(container_id, book_id, field, fm, d, mi)
|
||||
elif fm.datatype is 'series':
|
||||
series_edit(container_id, book_id, field, fm, d, mi)
|
||||
elif fm.datatype is 'datetime':
|
||||
@ -710,6 +738,21 @@ def render_metadata(mi, table, container_id, book_id): # {{{
|
||||
print('Failed to render metadata field: ' + field)
|
||||
traceback.print_exc()
|
||||
|
||||
current_edit_action = edit_field.bind(None, container_id, book_id, 'cover')
|
||||
table.appendChild(E.tr(onclick=current_edit_action, E.td(_('Cover') + ':'), E.td()))
|
||||
img = E.img(
|
||||
style='max-width: 300px; max-height: 400px',
|
||||
)
|
||||
if changes.cover:
|
||||
r = FileReader()
|
||||
r.onload = def(evt):
|
||||
img.src = evt.target.result
|
||||
changes.cover = evt.target.result
|
||||
r.readAsDataURL(changes.cover)
|
||||
v'delete changes.cover'
|
||||
else:
|
||||
img.src = cover_url(book_id)
|
||||
table.lastChild.lastChild.appendChild(img)
|
||||
# }}}
|
||||
|
||||
|
||||
@ -735,14 +778,22 @@ def changes_submitted(container_id, book_id, end_type, xhr, ev):
|
||||
on_close(container_id)
|
||||
|
||||
|
||||
def on_progress(container_id, book_id, loaded, total, xhr):
|
||||
container = document.getElementById(container_id)
|
||||
if container and total:
|
||||
update_status_widget(container, loaded, total)
|
||||
|
||||
|
||||
def submit_changes(container_id, book_id):
|
||||
c = document.getElementById(container_id)
|
||||
d = c.querySelector('div[data-ctype="show"]')
|
||||
clear(d)
|
||||
d.appendChild(E.div(style='margin: 1ex 1rem', _('Uploading changes to server, please wait...')))
|
||||
data = {'changes': changes, 'loaded_book_ids': loaded_book_ids()}
|
||||
w = upload_status_widget()
|
||||
d.appendChild(w)
|
||||
ajax_send(
|
||||
f'cdb/set-fields/{book_id}/{current_library_id()}', data, changes_submitted.bind(None, container_id, book_id))
|
||||
f'cdb/set-fields/{book_id}/{current_library_id()}', data, changes_submitted.bind(None, container_id, book_id), on_progress.bind(None, container_id, book_id))
|
||||
|
||||
|
||||
def show_book(container_id, book_id):
|
||||
|
@ -5,11 +5,12 @@ from __python__ import bound_methods, hash_literals
|
||||
from elementmaker import E
|
||||
from gettext import gettext as _
|
||||
|
||||
from utils import safe_set_inner_html, human_readable
|
||||
from dom import ensure_id
|
||||
from utils import human_readable, safe_set_inner_html
|
||||
|
||||
|
||||
def upload_files_widget(container, proceed, msg):
|
||||
container_id = container.id
|
||||
def upload_files_widget(container, proceed, msg, single_file=False, accept_extensions=None):
|
||||
container_id = ensure_id(container, 'upload-files')
|
||||
|
||||
def files_selected():
|
||||
files = this.files
|
||||
@ -19,7 +20,11 @@ def upload_files_widget(container, proceed, msg):
|
||||
proceed(container_id, files)
|
||||
|
||||
msg = msg or _('Upload books by <a>selecting the book files</a> 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 = E.div(E.span(), E.input(type='file', style='display:none', onchange=files_selected))
|
||||
if not single_file:
|
||||
c.lastChild.setAttribute('multiple', 'multiple')
|
||||
if accept_extensions:
|
||||
c.lastChild.setAttribute('accept', ', '.join(['.' + x for x in accept_extensions.split(' ')]))
|
||||
c.style.minHeight = '80vh'
|
||||
c.style.padding = '1rem'
|
||||
c.style.borderBottom = 'solid 1px currentColor'
|
||||
@ -58,7 +63,8 @@ def update_status_widget(w, sent, 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)))
|
||||
if name:
|
||||
ans.appendChild(E.h3(E.b(name)))
|
||||
ans.appendChild(E.progress())
|
||||
ans.appendChild(E.span())
|
||||
return ans
|
||||
|
Loading…
x
Reference in New Issue
Block a user