2016-08-11 20:42:43 +05:30

269 lines
12 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 book_list.globals import get_session_data, get_boss
from dom import set_css
from elementmaker import E
from gettext import gettext as _
from read_book.globals import messenger, iframe_id
from read_book.resources import load_resources
from read_book.overlay import Overlay
from utils import parse_url_params, username_key
LOADING_DOC = '''
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<script type="text/javascript" id="bootstrap">
__SCRIPT__
end_script
</head>
<body>
<div style="font-family: sans-serif; font-size:larger; font-weight: bold; margin-top:48vh; text-align:center">
__BS__
</div>
</body>
</html>
'''.replace('end_script', '<' + '/script>') # cannot have a closing script tag as this is embedded inside a script tag in index.html
class View:
def __init__(self, container, ui):
self.ui = ui
self.loaded_resources = {}
sd = get_session_data()
container.appendChild(
E.div(style='width: 100vw; height: 100vh; overflow: hidden; display: flex; align-items: stretch', # container for horizontally aligned panels
E.div(style='display: flex; flex-direction: column; align-items: stretch; flex-grow:2', # container for iframe and any other panels in the same column
E.div(style='flex-grow: 2; display:flex; align-items: stretch', # container for iframe and its overlay
E.div(style='width:{}px; height:100%'.format(sd.get('margin_left', 20)), id='book-left-margin'),
E.div(style='flex-grow:2; display:flex; align-items:stretch; flex-direction: column', # container for top and bottom margins
E.div(style='height:{}px; width:100%; padding: 0'.format(sd.get('margin_top', 20)), id='book-top-margin'),
E.iframe(id=iframe_id, seamless=True, sandbox='allow-popups allow-scripts', style='flex-grow: 2'),
E.div(style='height:{}px; width:100%; padding: 0'.format(sd.get('margin_bottom', 20)), id='book-bottom-margin'),
),
E.div(style='width:{}px; height:100%'.format(sd.get('margin_right', 20)), id='book-right-margin'),
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none', id='book-overlay'), # overlay
)
)
)
)
self.overlay = Overlay(self)
self.src_doc = None
self.iframe_ready = False
self.pending_load = None
self.encrypted_communications = False
self.create_src_doc()
window.addEventListener('message', self.handle_message, False)
self.handlers = {
'ready': self.on_iframe_ready,
'error': self.on_iframe_error,
'next_spine_item': self.on_next_spine_item,
'goto_doc_boundary': self.goto_doc_boundary,
'scroll_to_anchor': self.on_scroll_to_anchor,
'update_cfi': self.on_update_cfi,
'content_loaded': self.on_content_loaded,
}
self.currently_showing = {}
document.getElementById('book-top-margin').addEventListener('click', self.top_margin_clicked)
@property
def iframe(self):
return document.getElementById(iframe_id)
def top_margin_clicked(self, event):
if event.button is 0:
event.preventDefault(), event.stopPropagation()
self.overlay.show()
def set_margins(self, no_margins):
sd = get_session_data()
margin_left = 0 if no_margins else sd.get('margin_left')
margin_right = 0 if no_margins else sd.get('margin_right')
margin_top = 0 if no_margins else sd.get('margin_top')
margin_bottom = 0 if no_margins else sd.get('margin_bottom')
max_text_height = sd.get('max_text_height')
th = window.innerHeight - margin_top - margin_bottom
if not no_margins and max_text_height > 100 and th > max_text_height:
extra = (th - max_text_height) // 2
margin_top += extra
margin_bottom += extra
max_text_width = sd.get('max_text_width')
tw = window.innerWidth - margin_left - margin_right
if not no_margins and max_text_width > 100 and tw > max_text_width:
extra = (tw - max_text_width) // 2
margin_left += extra
margin_right += extra
set_css(document.getElementById('book-top-margin'), height=margin_top + 'px')
set_css(document.getElementById('book-bottom-margin'), height=margin_bottom + 'px')
set_css(document.getElementById('book-left-margin'), width=margin_left + 'px')
set_css(document.getElementById('book-right-margin'), width=margin_right + 'px')
def create_src_doc(self):
iframe_script = self.ui.interface_data.main_js.replace(/is_running_in_iframe\s*=\s*false/, 'is_running_in_iframe = true')
self.ui.interface_data.main_js = None
self.src_doc = self.iframe.srcdoc = LOADING_DOC.replace(
'__BS__', _('Bootstrapping book reader...')).replace(
'__SCRIPT__', iframe_script)
def init_iframe(self, iframe_script):
self.encrypted_communications = False
self.iframe.srcdoc = self.src_doc
def send_message(self, action, **data):
data.action = action
if self.encrypted_communications:
data = messenger.encrypt(data)
self.iframe.contentWindow.postMessage(data, '*')
def handle_message(self, event):
if event.source is not self.iframe.contentWindow:
return
data = event.data
if self.encrypted_communications:
try:
data = messenger.decrypt(data)
except Exception as e:
print('Could not process message from iframe:')
console.log(e)
return
func = self.handlers[data.action]
if func:
func(data)
else:
print('Unknown action in message from iframe to parent: ' + data.action)
def on_iframe_ready(self, data):
messenger.reset()
self.send_message('initialize', secret=messenger.secret, translations=self.ui.interface_data.translations)
self.iframe_ready = True
if self.pending_load:
data = self.pending_load
self.pending_load = None
self.show_spine_item_stage2(data)
def on_iframe_error(self, data):
self.ui.show_error((data.title or _('There was an error processing the book')), data.msg, data.details)
def on_resize(self):
pass
def show_loading(self):
title = self.book.metadata.title
name = self.currently_showing.name
self.overlay.show_loading_message(_(
'Loading <i>{name}</i> from <i>{title}</i>, please wait...').format(name=name, title=title))
def hide_loading(self):
self.overlay.hide_loading_message()
def display_book(self, book):
self.book = book
self.ui.db.update_last_read_time(book)
self.loaded_resources = {}
pos = {'replace_history':True}
unkey = username_key(self.ui.interface_data.username)
name = book.manifest.spine[0]
cfi = None
q = parse_url_params()
if q.bookpos and q.bookpos.startswith('epubcfi(/'):
cfi = q.bookpos
elif book.last_read_position and book.last_read_position[unkey]:
cfi = book.last_read_position[unkey]
if cfi and cfi.startswith('epubcfi(/'):
cfi = cfi[len('epubcfi(/'):-1]
snum, rest = cfi.partition('/')[::2]
try:
snum = int(snum)
except Exception:
print('Invalid spine number in CFI:', snum)
if type(snum) == 'number':
name = book.manifest.spine[(int(snum) // 2) - 1] or name
pos.type, pos.cfi = 'cfi', '/' + rest
self.show_name(name, initial_position=pos)
def show_name(self, name, initial_position=None):
if self.currently_showing.loading:
return
sd = get_session_data()
settings={
'margin_left': 0 if name is self.book.manifest.title_page_name else sd.get('margin_left'),
'margin_right': 0 if name is self.book.manifest.title_page_name else sd.get('margin_right'),
'read_mode': sd.get('read_mode'),
'cols_per_screen': sd.get('cols_per_screen'),
}
initial_position = initial_position or {'replace_history':False}
self.currently_showing = {'name':name, 'settings':settings, 'initial_position':initial_position, 'loading':True}
self.show_loading()
spine = self.book.manifest.spine
idx = spine.indexOf(name)
if idx > -1:
self.currently_showing.bookpos = 'epubcfi(/{})'.format(2 * (idx +1))
self.set_margins(name is self.book.manifest.title_page_name)
load_resources(self.ui.db, self.book, name, self.loaded_resources, self.show_spine_item)
def goto_doc_boundary(self, data):
name = self.book.manifest.spine[0 if data.start else self.book.manifest.spine.length - 1]
self.show_name(name, initial_position={'type':'frac', 'frac':0 if data.start else 1, 'replace_history':False})
def on_scroll_to_anchor(self, data):
self.show_name(data.name, initial_position={'type':'anchor', 'anchor':data.frag, 'replace_history':False})
def goto_named_destination(self, name, frag):
if self.currently_showing.name is name:
self.send_message('scroll_to_anchor', frag=frag)
else:
spine = self.book.manifest.spine
idx = spine.indexOf(name)
if idx is -1:
self.ui.show_error(_('Destination does not exist'), _(
'The file {} does not exist in this book').format(name))
return
self.show_name(name, initial_position={'type':'anchor', 'anchor':frag, 'replace_history':False})
def on_next_spine_item(self, data):
spine = self.book.manifest.spine
idx = spine.indexOf(self.currently_showing.name)
if data.previous:
if idx is 0:
return
idx = min(spine.length - 1, max(idx - 1, 0))
self.show_name(spine[idx], initial_position={'type':'frac', 'frac':1, 'replace_history':True})
else:
if idx is spine.length - 1:
return
idx = max(0, min(spine.length - 1, idx + 1))
self.show_name(spine[idx], initial_position={'type':'frac', 'frac':0, 'replace_history':True})
def on_update_cfi(self, data):
self.currently_showing.bookpos = data.cfi
get_boss().push_state(replace=data.replace_history)
unkey = username_key(self.ui.interface_data.username)
if not self.book.last_read_position:
self.book.last_read_position = {}
self.book.last_read_position[unkey] = data.cfi
self.ui.db.update_last_read_time(self.book)
def show_spine_item(self, resource_data):
self.loaded_resources = resource_data
# Re-init the iframe to ensure any changes made to the environment by the last spine item are lost
self.init_iframe()
# Now wait for iframe to message that it is ready
self.pending_load = resource_data
def show_spine_item_stage2(self, resource_data):
self.currently_showing.loading = False
self.send_message('display',
resource_data=resource_data, book=self.book, name=self.currently_showing.name,
initial_position=self.currently_showing.initial_position,
settings=self.currently_showing.settings,
)
self.encrypted_communications = True
def on_content_loaded(self):
self.hide_loading()
# self.overlay.show()