mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 18:54: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
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from base64 import standard_b64decode
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from io import BytesIO
|
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.ebooks.metadata.meta import get_metadata
|
||||||
from calibre.srv.changes import books_added, books_deleted, metadata
|
from calibre.srv.changes import books_added, books_deleted, metadata
|
||||||
from calibre.srv.errors import HTTPBadRequest, HTTPForbidden, HTTPNotFound
|
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.routes import endpoint, json, msgpack_or_json
|
||||||
from calibre.srv.utils import get_db, get_library_data
|
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.utils.serialize import MSGPACK_MIME, json_loads, msgpack_loads
|
||||||
from calibre.srv.metadata import book_as_json
|
|
||||||
|
|
||||||
receive_data_methods = {'GET', 'POST'}
|
receive_data_methods = {'GET', 'POST'}
|
||||||
|
|
||||||
@ -110,6 +112,17 @@ def cdb_delete_book(ctx, rd, book_ids, library_id):
|
|||||||
return {}
|
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},
|
@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')
|
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):
|
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:
|
except Exception:
|
||||||
raise HTTPBadRequest('Invalid encoded data')
|
raise HTTPBadRequest('Invalid encoded data')
|
||||||
dirtied = set()
|
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():
|
for field, value in changes.iteritems():
|
||||||
dirtied |= db.set_field(field, {book_id: value})
|
dirtied |= db.set_field(field, {book_id: value})
|
||||||
metadata(dirtied)
|
metadata(dirtied)
|
||||||
|
@ -12,8 +12,8 @@ from book_list.book_details import (
|
|||||||
report_load_failure
|
report_load_failure
|
||||||
)
|
)
|
||||||
from book_list.library_data import (
|
from book_list.library_data import (
|
||||||
book_metadata, current_library_id, field_names_for, library_data, load_status,
|
book_metadata, cover_url, current_library_id, field_names_for, library_data,
|
||||||
loaded_book_ids, set_book_metadata
|
load_status, loaded_book_ids, set_book_metadata
|
||||||
)
|
)
|
||||||
from book_list.router import back
|
from book_list.router import back
|
||||||
from book_list.theme import get_color
|
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 book_list.ui import set_panel_handler, show_panel
|
||||||
from date import UNDEFINED_DATE_ISO, format_date
|
from date import UNDEFINED_DATE_ISO, format_date
|
||||||
from dom import add_extra_css, build_rule, clear, svgicon
|
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 modals import error_dialog
|
||||||
from session import get_interface_data
|
from session import get_interface_data
|
||||||
from utils import (
|
from utils import (
|
||||||
@ -474,6 +477,29 @@ def bool_edit(container_id, book_id, field, fm, div, mi):
|
|||||||
return val_map[x]
|
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):
|
def edit_field(container_id, book_id, field):
|
||||||
nonlocal value_to_json
|
nonlocal value_to_json
|
||||||
fm = library_data.field_metadata[field]
|
fm = library_data.field_metadata[field]
|
||||||
@ -491,6 +517,8 @@ def edit_field(container_id, book_id, field):
|
|||||||
update_completions.prefix = ''
|
update_completions.prefix = ''
|
||||||
if field is 'authors':
|
if field is 'authors':
|
||||||
multiple_line_edit(' & ', '&', container_id, book_id, field, fm, d, mi)
|
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':
|
elif fm.datatype is 'series':
|
||||||
series_edit(container_id, book_id, field, fm, d, mi)
|
series_edit(container_id, book_id, field, fm, d, mi)
|
||||||
elif fm.datatype is 'datetime':
|
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)
|
print('Failed to render metadata field: ' + field)
|
||||||
traceback.print_exc()
|
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)
|
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):
|
def submit_changes(container_id, book_id):
|
||||||
c = document.getElementById(container_id)
|
c = document.getElementById(container_id)
|
||||||
d = c.querySelector('div[data-ctype="show"]')
|
d = c.querySelector('div[data-ctype="show"]')
|
||||||
clear(d)
|
clear(d)
|
||||||
d.appendChild(E.div(style='margin: 1ex 1rem', _('Uploading changes to server, please wait...')))
|
d.appendChild(E.div(style='margin: 1ex 1rem', _('Uploading changes to server, please wait...')))
|
||||||
data = {'changes': changes, 'loaded_book_ids': loaded_book_ids()}
|
data = {'changes': changes, 'loaded_book_ids': loaded_book_ids()}
|
||||||
|
w = upload_status_widget()
|
||||||
|
d.appendChild(w)
|
||||||
ajax_send(
|
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):
|
def show_book(container_id, book_id):
|
||||||
|
@ -5,11 +5,12 @@ from __python__ import bound_methods, hash_literals
|
|||||||
from elementmaker import E
|
from elementmaker import E
|
||||||
from gettext import gettext as _
|
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):
|
def upload_files_widget(container, proceed, msg, single_file=False, accept_extensions=None):
|
||||||
container_id = container.id
|
container_id = ensure_id(container, 'upload-files')
|
||||||
|
|
||||||
def files_selected():
|
def files_selected():
|
||||||
files = this.files
|
files = this.files
|
||||||
@ -19,7 +20,11 @@ def upload_files_widget(container, proceed, msg):
|
|||||||
proceed(container_id, files)
|
proceed(container_id, files)
|
||||||
|
|
||||||
msg = msg or _('Upload books by <a>selecting the book files</a> or drag and drop of the files here')
|
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.minHeight = '80vh'
|
||||||
c.style.padding = '1rem'
|
c.style.padding = '1rem'
|
||||||
c.style.borderBottom = 'solid 1px currentColor'
|
c.style.borderBottom = 'solid 1px currentColor'
|
||||||
@ -58,7 +63,8 @@ def update_status_widget(w, sent, total):
|
|||||||
|
|
||||||
def upload_status_widget(name, job_id):
|
def upload_status_widget(name, job_id):
|
||||||
ans = E.div(style='padding: 1rem 1ex;', data_job='' + 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.progress())
|
||||||
ans.appendChild(E.span())
|
ans.appendChild(E.span())
|
||||||
return ans
|
return ans
|
||||||
|
Loading…
x
Reference in New Issue
Block a user