mirror of
https://github.com/kovidgoyal/calibre.git
synced 2026-02-06 11:03:30 -05:00
851 lines
34 KiB
Plaintext
851 lines
34 KiB
Plaintext
# vim:fileencoding=utf-8
|
|
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
|
from __python__ import bound_methods, hash_literals
|
|
|
|
from elementmaker import E
|
|
|
|
from book_list.book_details import CLASS_NAME as BD_CLASS_NAME, render_metadata
|
|
from book_list.globals import get_session_data
|
|
from book_list.library_data import (
|
|
current_library_id, set_book_metadata, sync_library_books
|
|
)
|
|
from book_list.router import home
|
|
from book_list.theme import get_color
|
|
from book_list.top_bar import create_top_bar
|
|
from book_list.ui import query_as_href, show_panel
|
|
from dom import (
|
|
add_extra_css, build_rule, clear, ensure_id, set_css, svgicon, unique_id
|
|
)
|
|
from gettext import gettext as _
|
|
from modals import error_dialog, question_dialog
|
|
from read_book.bookmarks import create_bookmarks_panel
|
|
from read_book.globals import runtime, ui_operations
|
|
from read_book.goto import create_goto_panel, create_location_overlay
|
|
from read_book.highlights import create_highlights_panel
|
|
from read_book.open_book import create_open_book
|
|
from read_book.prefs.font_size import create_font_size_panel
|
|
from read_book.prefs.head_foot import time_formatter
|
|
from read_book.prefs.main import create_prefs_panel
|
|
from read_book.toc import create_toc_panel
|
|
from read_book.word_actions import create_word_actions_panel
|
|
from session import defaults as session_defaults, get_device_uuid
|
|
from utils import (
|
|
default_context_menu_should_be_allowed, full_screen_element,
|
|
full_screen_supported, is_ios, safe_set_inner_html
|
|
)
|
|
from uuid import short_uuid
|
|
from widgets import create_button, create_spinner
|
|
|
|
|
|
class LoadingMessage: # {{{
|
|
|
|
def __init__(self, msg, current_color_scheme):
|
|
self.msg = msg or ''
|
|
self.current_color_scheme = current_color_scheme
|
|
self.is_not_escapable = True # prevent Esc key from closing
|
|
|
|
def show(self, container):
|
|
self.container_id = container.getAttribute('id')
|
|
container.style.backgroundColor = self.current_color_scheme.background
|
|
container.style.color = self.current_color_scheme.foreground
|
|
container.appendChild(
|
|
E.div(
|
|
style='text-align:center',
|
|
E.div(create_spinner('100px', '100px')),
|
|
E.h2()
|
|
))
|
|
safe_set_inner_html(container.firstChild.lastChild, self.msg)
|
|
set_css(container.firstChild, position='relative', top='50%', transform='translateY(-50%)')
|
|
|
|
def set_msg(self, msg):
|
|
self.msg = msg
|
|
container = document.getElementById(self.container_id)
|
|
safe_set_inner_html(container.firstChild.lastChild, self.msg)
|
|
|
|
def on_container_click(self, evt):
|
|
pass # Dont allow panel to be closed by a click
|
|
# }}}
|
|
|
|
class DeleteBook: # {{{
|
|
|
|
def __init__(self, overlay, question, ok_icon, ok_text, reload_book):
|
|
self.overlay = overlay
|
|
self.question = question or _(
|
|
'Are you sure you want to remove this book from local storage? You will have to re-download it from calibre if you want to read it again.')
|
|
self.ok_icon = ok_icon or 'trash'
|
|
self.ok_text = ok_text or _('Delete book')
|
|
self.reload_book = reload_book
|
|
|
|
def show(self, container):
|
|
self.container_id = container.getAttribute('id')
|
|
set_css(container, background_color=get_color('window-background'))
|
|
container.appendChild(E.div(
|
|
style='display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100%',
|
|
tabindex='0',
|
|
E.div(style='margin:1ex 1em; max-width: 80%',
|
|
E.h2(self.question),
|
|
E.div(style='display:flex; justify-content:flex-end; margin-top:1rem',
|
|
create_button(self.ok_text, self.ok_icon, action=self.delete_book, highlight=True),
|
|
E.span('\xa0'),
|
|
create_button(_('Cancel'), action=self.cancel),
|
|
)
|
|
),
|
|
onkeyup=def(ev):
|
|
if ev.key is 'Escape':
|
|
ev.stopPropagation(), ev.preventDefault()
|
|
self.cancel()
|
|
elif ev.key is 'Enter' or ev.key is 'Return':
|
|
ev.stopPropagation(), ev.preventDefault()
|
|
self.delete_book()
|
|
))
|
|
window.setTimeout(self.focus_me, 0)
|
|
|
|
def focus_me(self):
|
|
document.getElementById(self.container_id).lastChild.focus()
|
|
|
|
def show_working(self):
|
|
container = document.getElementById(self.container_id)
|
|
clear(container)
|
|
container.appendChild(
|
|
E.div(
|
|
style='text-align:center',
|
|
E.div(create_spinner('100px', '100px')),
|
|
E.h2()
|
|
))
|
|
safe_set_inner_html(container.lastChild.lastChild, _('Deleting local book copy, please wait...'))
|
|
|
|
def on_container_click(self, evt):
|
|
pass # Dont allow panel to be closed by a click
|
|
|
|
def delete_book(self):
|
|
if runtime.is_standalone_viewer:
|
|
if self.reload_book:
|
|
self.overlay.view.reload_book()
|
|
return
|
|
self.show_working()
|
|
view = self.overlay.view
|
|
ui_operations.delete_book(view.book, def(book, errmsg):
|
|
self.overlay.hide_current_panel()
|
|
if errmsg:
|
|
view.ui.show_error(_('Failed to delete book'), _('Failed to delete book from local storage, click "Show details" for more information.'), errmsg)
|
|
else:
|
|
if self.reload_book:
|
|
self.overlay.view.reload_book()
|
|
else:
|
|
home()
|
|
)
|
|
|
|
def cancel(self):
|
|
self.overlay.hide_current_panel()
|
|
# }}}
|
|
|
|
class SyncBook: # {{{
|
|
|
|
def __init__(self, overlay):
|
|
self.overlay = overlay
|
|
self.canceled = False
|
|
|
|
def show(self, container):
|
|
self.container_id = container.getAttribute('id')
|
|
set_css(container, background_color=get_color('window-background'))
|
|
book = self.overlay.view.book
|
|
to_sync = v'[[book.key, new Date(0)]]'
|
|
self.overlay.view.annotations_manager.sync_annots_to_server()
|
|
sync_library_books(book.key[0], to_sync, self.sync_data_received)
|
|
container.appendChild(E.div(
|
|
style='display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100%',
|
|
E.div(style='margin:1ex 1em; max-width: 80vw',
|
|
E.h2(_('Syncing last read position and annotations')),
|
|
E.p(_('Downloading data from server, please wait...')),
|
|
E.div(style='display:flex; justify-content:flex-end',
|
|
create_button(_('Cancel'), action=self.cancel),
|
|
)
|
|
)
|
|
))
|
|
|
|
def on_container_click(self, evt):
|
|
pass # Dont allow panel to be closed by a click
|
|
|
|
def cancel(self):
|
|
self.canceled = True
|
|
self.overlay.hide_current_panel()
|
|
|
|
def sync_data_received(self, library_id, lrmap, load_type, xhr, ev):
|
|
if self.canceled:
|
|
return
|
|
self.overlay.hide()
|
|
if load_type is not 'load':
|
|
if xhr.responseText is 'login required for sync':
|
|
error_dialog(_('Failed to fetch sync data'), _('You must setup user accounts and login to use the sync functionality'))
|
|
else:
|
|
error_dialog(_('Failed to fetch sync data'), _('Failed to download sync data from server, click "Show details" for more information.'), xhr.error_html)
|
|
return
|
|
data = JSON.parse(xhr.responseText)
|
|
book = self.overlay.view.book
|
|
dev = get_device_uuid()
|
|
epoch = 0
|
|
ans = None
|
|
new_annotations_map = None
|
|
for key in data:
|
|
book_id, fmt = key.partition(':')[::2]
|
|
if book_id is str(book.key[1]) and fmt.upper() is book.key[2].upper():
|
|
new_vals = data[key]
|
|
last_read_positions = new_vals.last_read_positions
|
|
new_annotations_map = new_vals.annotations_map
|
|
for d in last_read_positions:
|
|
if d.device is not dev and d.epoch > epoch:
|
|
epoch = d.epoch
|
|
ans = d
|
|
break
|
|
cfi = ans?.cfi
|
|
if new_annotations_map or cfi:
|
|
self.overlay.view.sync_data_received(cfi, new_annotations_map)
|
|
|
|
|
|
# }}}
|
|
|
|
# CSS {{{
|
|
MAIN_OVERLAY_TS_CLASS = 'read-book-main-overlay-top-section'
|
|
MAIN_OVERLAY_ACTIONS_CLASS = 'read-book-main-overlay-actions'
|
|
|
|
|
|
def timer_id():
|
|
if not timer_id.ans:
|
|
timer_id.ans = unique_id()
|
|
return timer_id.ans
|
|
|
|
|
|
add_extra_css(def():
|
|
style = ''
|
|
sel = '.' + MAIN_OVERLAY_TS_CLASS + ' > .' + MAIN_OVERLAY_ACTIONS_CLASS + ' '
|
|
style += build_rule(sel, overflow='hidden')
|
|
sel += '> ul '
|
|
style += build_rule(sel, display='flex', list_style='none', border_bottom='1px solid currentColor')
|
|
sel += '> li'
|
|
style += build_rule(sel, border_right='1px solid currentColor', padding='0.5em 1ex', display='flex', flex_wrap='wrap', align_items='center', cursor='pointer')
|
|
style += build_rule(sel + ':last-child', border_right_style='none')
|
|
style += build_rule(sel + ':hover > *:first-child, .main-overlay-button:hover > *:first-child',
|
|
color=get_color('window-hover-foreground'))
|
|
style += build_rule(sel + ':active > *:first-child, .main-overlay-button:active > *:first-child', transform='scale(1.8)')
|
|
style += f'@media screen and (max-width: 350px) {{ #{timer_id()} {{ display: none; }} }}'
|
|
return style
|
|
)
|
|
# }}}
|
|
|
|
def simple_overlay_title(title, overlay, container, close_action):
|
|
container.style.backgroundColor = get_color('window-background')
|
|
def action():
|
|
event = this
|
|
event.stopPropagation()
|
|
overlay.hide_current_panel(event)
|
|
|
|
create_top_bar(container, title=title, icon='close', action=close_action or action)
|
|
|
|
|
|
class MainOverlay: # {{{
|
|
|
|
def __init__(self, overlay, elements):
|
|
self.overlay = overlay
|
|
self.elements = elements or {}
|
|
self.timer = None
|
|
if window.Intl?.DateTimeFormat:
|
|
self.date_formatter = window.Intl.DateTimeFormat(undefined, {'hour':'numeric', 'minute':'numeric'})
|
|
else:
|
|
self.date_formatter = {'format': def(date):
|
|
return '{}:{}'.format(date.getHours(), date.getMinutes())
|
|
}
|
|
|
|
def show(self, container):
|
|
self.container_id = container.getAttribute('id')
|
|
icon_size = '3.5ex'
|
|
sd = get_session_data()
|
|
|
|
def ac(text, tooltip, action, icon, is_text_button):
|
|
if is_text_button:
|
|
icon = E.span(icon, style='font-size: 175%; font-weight: bold')
|
|
else:
|
|
icon = svgicon(icon, icon_size, icon_size) if icon else ''
|
|
icon.style.marginRight = '0.5ex'
|
|
|
|
return E.li(icon, text, onclick=action, title=tooltip)
|
|
|
|
sync_action = ac(_('Sync'), _('Get last read position and annotations from the server'), self.overlay.sync_book, 'cloud-download')
|
|
delete_action = ac(_('Delete'), _('Delete this book from local storage'), self.overlay.delete_book, 'trash')
|
|
reload_action = ac(_('Reload'), _('Reload this book from the {}').format( _('computer') if runtime.is_standalone_viewer else _('server')), self.overlay.reload_book, 'refresh')
|
|
back_action = ac(_('Back'), _('Go back'), self.back, 'arrow-left')
|
|
forward_action = ac(_('Forward'), _('Go forward'), self.forward, 'arrow-right')
|
|
nav_actions = E.ul(back_action, forward_action)
|
|
if runtime.is_standalone_viewer:
|
|
reload_actions = E.ul(
|
|
ac(_('Open book'), _('Open a book'), self.overlay.open_book, 'book'),
|
|
reload_action
|
|
)
|
|
else:
|
|
reload_actions = E.ul(sync_action, delete_action, reload_action)
|
|
|
|
bookmarks_action = ac(_('Bookmarks'), _('Browse all bookmarks'), self.overlay.show_bookmarks, 'bookmark')
|
|
highlights_action = ac(
|
|
_('Highlights'), _('Browse all highlights'), def():
|
|
self.overlay.show_highlights()
|
|
, 'image')
|
|
|
|
toc_actions = E.ul(ac(_('Table of Contents'), _('Browse the Table of Contents'), self.overlay.show_toc, 'toc'))
|
|
toc_actions.appendChild(ac(_('Reference mode'), _('Toggle the Reference mode'), self.overlay.toggle_reference_mode, 'reference-mode'))
|
|
|
|
actions_div = E.div( # actions
|
|
nav_actions,
|
|
|
|
E.ul(
|
|
ac(_('Search'), _('Search for text in this book'), self.overlay.show_search, 'search'),
|
|
ac(_('Go to'), _('Go to a specific location in the book'), self.overlay.show_goto, 'chevron-right'),
|
|
),
|
|
|
|
reload_actions,
|
|
|
|
toc_actions,
|
|
|
|
E.ul(highlights_action, bookmarks_action),
|
|
|
|
E.ul(
|
|
ac(_('Font size'), _('Change text size'), self.overlay.show_font_size_chooser, 'Aa', True),
|
|
ac(_('Preferences'), _('Configure the E-book viewer'), self.overlay.show_prefs, 'cogs'),
|
|
),
|
|
|
|
class_=MAIN_OVERLAY_ACTIONS_CLASS
|
|
)
|
|
if not runtime.is_standalone_viewer:
|
|
home_action = ac(_('Home'), _('Return to the home page'), def():
|
|
home()
|
|
ui_operations.close_book()
|
|
, 'home')
|
|
library_action = ac(_('Library'), _('Return to the library page'), self.overlay.show_library, 'library')
|
|
actions_div.insertBefore(E.ul(home_action, library_action), actions_div.firstChild)
|
|
full_screen_actions = []
|
|
if runtime.is_standalone_viewer:
|
|
text = _('Exit full screen') if runtime.viewer_in_full_screen else _('Enter full screen')
|
|
full_screen_actions.push(
|
|
ac(text, _('Toggle full screen mode'), def(): self.overlay.hide(), ui_operations.toggle_full_screen();, 'full-screen'))
|
|
full_screen_actions.push(
|
|
ac(_('Print'), _('Print book to PDF'), def(): self.overlay.hide(), ui_operations.print_book();, 'print'))
|
|
else:
|
|
if not is_ios and full_screen_supported():
|
|
text = _('Exit full screen') if full_screen_element() else _('Enter full screen')
|
|
# No fullscreen on iOS, see http://caniuse.com/#search=fullscreen
|
|
full_screen_actions.push(
|
|
ac(text, _('Toggle full screen mode'), def(): self.overlay.hide(), ui_operations.toggle_full_screen();, 'full-screen')
|
|
)
|
|
if sd.get('read_mode') is 'flow':
|
|
asa = self.overlay.view.autoscroll_active
|
|
full_screen_actions.append(ac(
|
|
_('Stop auto scroll') if asa else _('Auto scroll'), _('Toggle auto-scrolling'), def():
|
|
self.overlay.hide()
|
|
window.setTimeout(self.overlay.view.toggle_autoscroll, 0)
|
|
, 'auto-scroll'))
|
|
|
|
actions_div.appendChild(E.ul(
|
|
ac(_('Read aloud'), _('Read the book aloud'), def():
|
|
self.overlay.hide()
|
|
self.overlay.view.start_read_aloud()
|
|
, 'bullhorn')
|
|
))
|
|
|
|
if full_screen_actions.length:
|
|
actions_div.appendChild(E.ul(*full_screen_actions))
|
|
|
|
no_selection_bar = not sd.get('show_selection_bar')
|
|
if runtime.is_standalone_viewer:
|
|
if no_selection_bar:
|
|
actions_div.appendChild(E.ul(
|
|
ac(_('Lookup/search word'), _('Lookup or search for the currently selected word'),
|
|
def(): self.overlay.hide(), ui_operations.toggle_lookup(True);, 'library')
|
|
))
|
|
copy_actions = E.ul()
|
|
if self.elements.link:
|
|
copy_actions.appendChild(ac(_('Copy link'), _('Copy the current link'), def():
|
|
self.overlay.hide(), ui_operations.copy_selection(self.elements.link)
|
|
, 'link'))
|
|
if self.elements.img:
|
|
copy_actions.appendChild(ac(_('View image'), _('View the current image'), def():
|
|
self.overlay.hide(), ui_operations.view_image(self.elements.img)
|
|
, 'image'))
|
|
copy_actions.appendChild(ac(_('Copy image'), _('Copy the current image'), def():
|
|
self.overlay.hide(), ui_operations.copy_image(self.elements.img)
|
|
, 'copy'))
|
|
if copy_actions.childNodes.length:
|
|
actions_div.appendChild(copy_actions)
|
|
|
|
actions_div.appendChild(E.ul(
|
|
ac(_('Inspector'), _('Show the content inspector'),
|
|
def(): self.overlay.hide(), ui_operations.toggle_inspector();, 'bug'),
|
|
ac(_('Reset interface'), _('Reset E-book viewer panels, toolbars and scrollbars to defaults'),
|
|
def():
|
|
question_dialog(
|
|
_('Are you sure?'), _(
|
|
'Are you sure you want to reset the viewer interface'
|
|
' to its default appearance?'
|
|
),
|
|
def (yes):
|
|
if yes:
|
|
self.overlay.hide()
|
|
ui_operations.reset_interface()
|
|
sd = get_session_data()
|
|
sd.set('skipped_dialogs', session_defaults.skipped_dialogs)
|
|
)
|
|
|
|
|
|
, 'window-restore'),
|
|
ac(_('Quit'), _('Close the E-book viewer'),
|
|
def(): self.overlay.hide(), ui_operations.quit();, 'remove'),
|
|
))
|
|
else:
|
|
copy_actions = E.ul()
|
|
if self.elements.link:
|
|
copy_actions.appendChild(ac(_('Copy link'), _('Copy the current link'), def():
|
|
self.overlay.hide(), ui_operations.copy_selection(self.elements.link)
|
|
, 'link'))
|
|
if self.elements.img:
|
|
copy_actions.appendChild(ac(_('View image'), _('View the current image'), def():
|
|
self.overlay.hide(), ui_operations.view_image(self.elements.img)
|
|
, 'image'))
|
|
if window.navigator.clipboard:
|
|
copy_actions.appendChild(ac(_('Copy image'), _('Copy the current image'), def():
|
|
self.overlay.hide(), ui_operations.copy_image(self.elements.img)
|
|
, 'copy'))
|
|
if copy_actions.childNodes.length:
|
|
actions_div.appendChild(copy_actions)
|
|
|
|
container.appendChild(set_css(E.div(class_=MAIN_OVERLAY_TS_CLASS, # top section
|
|
onclick=def (evt):evt.stopPropagation();,
|
|
|
|
set_css(E.div( # top row
|
|
E.div(self.overlay.view.book?.metadata?.title or _('Unknown'), style='max-width: 90%; text-overflow: ellipsis; font-weight: bold; white-space: nowrap; overflow: hidden'),
|
|
E.div(time_formatter.format(Date()), id=timer_id(), style='max-width: 9%; white-space: nowrap; overflow: hidden'),
|
|
),
|
|
display='flex', justify_content='space-between', align_items='baseline', font_size='smaller', padding='0.5ex 1ex', border_bottom='solid 1px currentColor'
|
|
),
|
|
actions_div,
|
|
), user_select='none', background_color=get_color('window-background')))
|
|
|
|
container.appendChild(
|
|
E.div( # bottom bar
|
|
style='position: fixed; width: 100%; bottom: 0; display: flex; justify-content: space-between; align-items: center;'
|
|
'user-select: none; background-color: {}'.format(get_color('window-background')),
|
|
E.div(
|
|
style='display: flex; justify-content: space-between; align-items: center;',
|
|
E.div(
|
|
style='display: flex; align-items: center; cursor: pointer; padding: 0.5ex 1rem', class_='main-overlay-button',
|
|
title=_('Close the viewer controls'),
|
|
svgicon('close', icon_size, icon_size), '\xa0', _('Close')
|
|
),
|
|
E.div(
|
|
style='display: flex; align-items: center; cursor: pointer; padding: 0.5ex 1rem', class_='main-overlay-button',
|
|
svgicon('help', icon_size, icon_size), '\xa0', _('Help'), onclick=def(ev):
|
|
ui_operations.show_help('viewer')
|
|
)
|
|
),
|
|
E.div(style='padding: 0.5ex 1rem', '\xa0'), E.div(style='padding: 0.5ex 1rem', '\xa0'),
|
|
),
|
|
)
|
|
renderer = self.overlay.view.create_template_renderer()
|
|
if renderer:
|
|
c = container.lastChild.lastChild
|
|
b = c.previousSibling
|
|
renderer(b, 'controls_footer', 'middle', '')
|
|
renderer(c, 'controls_footer', 'right', '')
|
|
|
|
self.on_hide()
|
|
self.timer = setInterval(self.update_time, 1000)
|
|
|
|
def update_time(self):
|
|
tm = document.getElementById(timer_id())
|
|
if tm:
|
|
tm.textContent = self.date_formatter.format(Date())
|
|
|
|
def on_hide(self):
|
|
if self.timer is not None:
|
|
clearInterval(self.timer)
|
|
self.timer = None
|
|
|
|
def back(self):
|
|
window.history.back()
|
|
self.overlay.hide()
|
|
|
|
def forward(self):
|
|
window.history.forward()
|
|
self.overlay.hide()
|
|
|
|
# }}}
|
|
|
|
class SimpleOverlay: # {{{
|
|
|
|
def __init__(self, overlay, create_func, title):
|
|
self.overlay = overlay
|
|
self.create_func = create_func
|
|
self.title = title
|
|
|
|
def on_container_click(self, evt):
|
|
pass # Dont allow panel to be closed by a click
|
|
|
|
def show(self, container):
|
|
simple_overlay_title(self.title, self.overlay, container)
|
|
self.create_func(self.overlay, container)
|
|
# }}}
|
|
|
|
class TOCOverlay(SimpleOverlay): # {{{
|
|
|
|
def __init__(self, overlay, create_func, title):
|
|
self.overlay = overlay
|
|
self.create_func = create_func or create_toc_panel
|
|
self.title = title or _('Table of Contents')
|
|
|
|
def show(self, container):
|
|
container.appendChild(E.div())
|
|
container = container.firstChild
|
|
simple_overlay_title(self.title, self.overlay, container)
|
|
self.create_func(self.overlay.view.book, container, self.handle_activate)
|
|
|
|
def handle_activate(self, dest, frag):
|
|
self.overlay.hide()
|
|
if jstype(dest) is 'function':
|
|
dest(self.overlay.view, frag)
|
|
else:
|
|
self.overlay.view.goto_named_destination(dest, frag)
|
|
# }}}
|
|
|
|
class WordActionsOverlay: # {{{
|
|
|
|
def __init__(self, word, overlay):
|
|
self.word = word
|
|
self.overlay = overlay
|
|
|
|
def on_container_click(self, evt):
|
|
pass # Dont allow panel to be closed by a click
|
|
|
|
def show(self, container):
|
|
simple_overlay_title(_('Lookup: {}').format(self.word), self.overlay, container)
|
|
create_word_actions_panel(container, self.word, self.overlay.hide)
|
|
# }}}
|
|
|
|
class PrefsOverlay: # {{{
|
|
|
|
def __init__(self, overlay):
|
|
self.overlay = overlay
|
|
self.changes_occurred = False
|
|
|
|
def on_container_click(self, evt):
|
|
pass # Dont allow panel to be closed by a click
|
|
|
|
def show(self, container):
|
|
self.changes_occurred = False
|
|
container.style.backgroundColor = get_color('window-background')
|
|
self.prefs = create_prefs_panel(container, self.overlay.hide_current_panel, self.on_change)
|
|
|
|
def on_change(self):
|
|
self.changes_occurred = True
|
|
|
|
def handle_escape(self):
|
|
self.prefs.onclose()
|
|
|
|
def on_hide(self):
|
|
if self.changes_occurred:
|
|
self.changes_occurred = False
|
|
self.overlay.view.preferences_changed()
|
|
|
|
# }}}
|
|
|
|
class FontSizeOverlay: # {{{
|
|
|
|
def __init__(self, overlay):
|
|
self.overlay = overlay
|
|
|
|
def show(self, container):
|
|
create_font_size_panel(container, self.overlay.hide_current_panel)
|
|
# }}}
|
|
|
|
class OpenBook: # {{{
|
|
|
|
def __init__(self, overlay, closeable):
|
|
self.overlay = overlay
|
|
self.closeable = closeable
|
|
|
|
def handle_escape(self):
|
|
if self.closeable:
|
|
self.overlay.hide_current_panel()
|
|
else:
|
|
if self.overlay.handling_context_menu_event is None:
|
|
ui_operations.quit()
|
|
|
|
def on_container_click(self, evt):
|
|
pass # Dont allow panel to be closed by a click
|
|
|
|
def show(self, container):
|
|
simple_overlay_title(_('Open a book'), self.overlay, container, def ():
|
|
ev = this
|
|
ev.stopPropagation(), ev.preventDefault()
|
|
if self.closeable:
|
|
self.overlay.hide_current_panel(ev)
|
|
else:
|
|
ui_operations.quit()
|
|
)
|
|
create_open_book(container, self.overlay.view?.book)
|
|
# }}}
|
|
|
|
|
|
|
|
class Overlay:
|
|
|
|
def __init__(self, view):
|
|
self.view = view
|
|
self.handling_context_menu_event = None
|
|
c = self.clear_container()
|
|
c.addEventListener('click', self.container_clicked)
|
|
c.addEventListener('contextmenu', self.oncontextmenu, {'passive': False})
|
|
self.panels = []
|
|
|
|
def oncontextmenu(self, evt):
|
|
if default_context_menu_should_be_allowed(evt):
|
|
return
|
|
evt.preventDefault()
|
|
self.handling_context_menu_event = evt
|
|
self.handle_escape()
|
|
self.handling_context_menu_event = None
|
|
|
|
def clear_container(self):
|
|
c = self.container
|
|
clear(c)
|
|
c.style.backgroundColor = 'transparent'
|
|
c.style.color = get_color('window-foreground')
|
|
if c.style.display is not 'block':
|
|
c.style.display = 'block'
|
|
return c
|
|
|
|
@property
|
|
def container(self):
|
|
return document.getElementById('book-overlay')
|
|
|
|
@property
|
|
def is_visible(self):
|
|
return self.container.style.display is not 'none'
|
|
|
|
def update_visibility(self):
|
|
if self.panels.length:
|
|
self.container.style.display = 'block'
|
|
self.view.overlay_visibility_changed(True)
|
|
elif self.container.style.display is 'block':
|
|
self.container.style.display = 'none'
|
|
self.view.focus_iframe()
|
|
self.view.overlay_visibility_changed(False)
|
|
|
|
def container_clicked(self, evt):
|
|
if self.panels.length and jstype(self.panels[-1].on_container_click) is 'function':
|
|
self.panels[-1].on_container_click(evt)
|
|
else:
|
|
self.hide_current_panel()
|
|
|
|
def show_loading_message(self, msg):
|
|
if ui_operations.show_loading_message:
|
|
ui_operations.show_loading_message(msg)
|
|
else:
|
|
lm = LoadingMessage(msg, self.view.current_color_scheme)
|
|
self.panels.push(lm)
|
|
self.show_current_panel()
|
|
|
|
def hide_loading_message(self):
|
|
if ui_operations.show_loading_message:
|
|
ui_operations.show_loading_message(None)
|
|
else:
|
|
self.panels = [p for p in self.panels if not isinstance(p, LoadingMessage)]
|
|
self.show_current_panel()
|
|
|
|
def hide_current_panel(self):
|
|
p = self.panels.pop()
|
|
if p and callable(p.on_hide):
|
|
p.on_hide()
|
|
self.show_current_panel()
|
|
|
|
def handle_escape(self):
|
|
if self.panels.length:
|
|
p = self.panels[-1]
|
|
if not p.is_not_escapable:
|
|
if p.handle_escape:
|
|
p.handle_escape()
|
|
else:
|
|
self.hide_current_panel()
|
|
return True
|
|
return False
|
|
|
|
def show_current_panel(self):
|
|
if self.panels.length:
|
|
c = self.clear_container()
|
|
self.panels[-1].show(c)
|
|
self.update_visibility()
|
|
|
|
def show(self, elements):
|
|
self.panels = [MainOverlay(self, elements)]
|
|
self.show_current_panel()
|
|
|
|
def hide(self):
|
|
while self.panels.length:
|
|
self.hide_current_panel()
|
|
self.update_visibility()
|
|
|
|
def delete_book(self):
|
|
self.hide_current_panel()
|
|
self.panels = [DeleteBook(self)]
|
|
self.show_current_panel()
|
|
|
|
def reload_book(self):
|
|
self.hide_current_panel()
|
|
self.panels = [DeleteBook(self, _('Are you sure you want to reload this book?'), 'refresh', _('Reload book'), True)]
|
|
self.show_current_panel()
|
|
|
|
def open_book(self, closeable):
|
|
self.hide_current_panel()
|
|
if jstype(closeable) is not 'boolean':
|
|
closeable = True
|
|
self.panels = [OpenBook(self, closeable)]
|
|
self.show_current_panel()
|
|
|
|
def sync_book(self):
|
|
self.hide_current_panel()
|
|
self.panels = [SyncBook(self)]
|
|
self.show_current_panel()
|
|
|
|
def show_toc(self):
|
|
self.hide_current_panel()
|
|
if runtime.is_standalone_viewer:
|
|
ui_operations.toggle_toc()
|
|
return
|
|
self.panels.push(TOCOverlay(self))
|
|
self.show_current_panel()
|
|
|
|
def toggle_reference_mode(self):
|
|
self.hide_current_panel()
|
|
self.view.toggle_reference_mode()
|
|
|
|
def show_bookmarks(self):
|
|
self.hide_current_panel()
|
|
if runtime.is_standalone_viewer:
|
|
ui_operations.toggle_bookmarks()
|
|
return
|
|
def do_it(action, data):
|
|
self.panels.push(TOCOverlay(self, create_bookmarks_panel.bind(None, self.view.annotations_manager, data), _('Bookmarks')))
|
|
self.show_current_panel()
|
|
self.view.get_current_cfi('show-bookmarks', do_it)
|
|
|
|
def show_highlights(self):
|
|
self.hide_current_panel()
|
|
if ui_operations.toggle_highlights:
|
|
self.hide()
|
|
ui_operations.toggle_highlights()
|
|
return
|
|
self.panels.push(TOCOverlay(self, create_highlights_panel.bind(None, self.view.annotations_manager, self.hide_current_panel), _('Highlights')))
|
|
self.show_current_panel()
|
|
|
|
def show_goto(self):
|
|
self.hide_current_panel()
|
|
self.panels.push(TOCOverlay(self, create_goto_panel.bind(None, self.view.current_position_data), _('Go to…')))
|
|
self.show_current_panel()
|
|
|
|
def show_metadata(self):
|
|
self.hide_current_panel()
|
|
from_read_book = short_uuid()
|
|
|
|
def show_metadata_overlay(mi, pathtoebook, lname, book_id, overlay, container):
|
|
container_id = ensure_id(container)
|
|
container.appendChild(E.div(class_=BD_CLASS_NAME, style='padding: 1ex 1em'))
|
|
table = E.table(class_='metadata')
|
|
|
|
def handle_message(msg):
|
|
data = msg.data
|
|
if data.from_read_book is not from_read_book:
|
|
return
|
|
if data.type is 'edit_metadata_closed':
|
|
ui_operations.stop_waiting_for_messages_from(this)
|
|
self.hide_current_panel()
|
|
return
|
|
if data.type is 'update_cached_book_metadata' and data.metadata:
|
|
if current_library_id() is data.library_id:
|
|
mi = data.metadata
|
|
book_id = int(data.book_id)
|
|
set_book_metadata(book_id, mi)
|
|
book = self.view.book
|
|
if book and book.key[0] is data.library_id and book.key[1] is book_id:
|
|
ui_operations.update_metadata(book, mi)
|
|
|
|
if not runtime.is_standalone_viewer and lname:
|
|
|
|
container.lastChild.appendChild(E.div(
|
|
style='text-align: right',
|
|
E.a(_('Edit the metadata for this book in the library'), class_='blue-link', onclick=def():
|
|
container = document.getElementById(container_id)
|
|
clear(container)
|
|
container.appendChild(E.iframe(
|
|
style='position: absolute; left: 0; top: 0; width: 100vw; height: 100vh; border-width: 0',
|
|
src=query_as_href({
|
|
'from_read_book': from_read_book, 'library_id': lname, 'book_id': book_id + ''}, 'edit_metadata')
|
|
))
|
|
w = container.lastChild.contentWindow
|
|
ui_operations.wait_for_messages_from(w, handle_message.bind(w))
|
|
)
|
|
))
|
|
container.lastChild.appendChild(table)
|
|
render_metadata(mi, table, None, f'html {{ font-size: {document.documentElement.style.fontSize} }}')
|
|
for a in table.querySelectorAll('a[href]'):
|
|
a.removeAttribute('href')
|
|
a.removeAttribute('title')
|
|
a.classList.remove('blue-link')
|
|
if pathtoebook:
|
|
container.lastChild.appendChild(E.div(
|
|
style='margin-top: 1ex; padding-top: 1ex; border-top: solid 1px',
|
|
_('Path:'), ' ',
|
|
E.a(
|
|
href='javascript: void(0)',
|
|
class_='blue-link', title=_('Click to open the folder the book is in'),
|
|
onclick=def():
|
|
ui_operations.show_book_folder()
|
|
,
|
|
pathtoebook)
|
|
)
|
|
)
|
|
|
|
book = self.view.book
|
|
key = book.key or v'[null, 0]'
|
|
lname, book_id = key
|
|
book_id = int(book_id or 0)
|
|
panel = SimpleOverlay(self, show_metadata_overlay.bind(None, book.metadata, book.manifest.pathtoebook, lname, book_id), self.view.book.metadata.title)
|
|
panel.from_read_book = from_read_book
|
|
self.panels.push(panel)
|
|
self.show_current_panel()
|
|
|
|
def show_ask_for_location(self):
|
|
self.hide_current_panel()
|
|
self.panels.push(SimpleOverlay(
|
|
self, create_location_overlay.bind(None, self.view.current_position_data, self.view.book), _('Go to location, position or reference…')))
|
|
self.show_current_panel()
|
|
|
|
def show_search(self):
|
|
self.hide()
|
|
self.view.show_search()
|
|
|
|
def show_prefs(self):
|
|
self.hide_current_panel()
|
|
self.panels.push(PrefsOverlay(self))
|
|
self.show_current_panel()
|
|
|
|
def show_library(self):
|
|
self.hide_current_panel()
|
|
book = self.view.book
|
|
show_panel('book_list', {'library_id': book.key[0], 'book_id': book.key[1] + ''}, replace=False)
|
|
ui_operations.close_book()
|
|
|
|
def show_font_size_chooser(self):
|
|
self.hide_current_panel()
|
|
self.panels.push(FontSizeOverlay(self))
|
|
self.show_current_panel()
|
|
|
|
def show_word_actions(self, word):
|
|
self.hide_current_panel()
|
|
self.panels.push(WordActionsOverlay(word, self))
|
|
self.show_current_panel()
|