diff --git a/src/calibre/srv/TODO.rst b/src/calibre/srv/TODO.rst index 521d862835..db28113032 100644 --- a/src/calibre/srv/TODO.rst +++ b/src/calibre/srv/TODO.rst @@ -14,8 +14,6 @@ New features for the in-browser viewer - Allow loading fonts from the computer running calibre and using them for reading. -- Add a time left for chapter/book footer. - New features for the server generally --------------------------------------- diff --git a/src/pyj/read_book/iframe.pyj b/src/pyj/read_book/iframe.pyj index 6ade378da0..9d12f35fb4 100644 --- a/src/pyj/read_book/iframe.pyj +++ b/src/pyj/read_book/iframe.pyj @@ -273,7 +273,7 @@ class IframeBoss: if cfi != self.last_cfi: self.last_cfi = cfi self.send_message('update_cfi', cfi=cfi, replace_history=self.replace_history_on_next_cfi_update, - progress_frac=self.calculate_progress_frac(current_name, index)) + progress_frac=self.calculate_progress_frac(current_name, index), file_progress_frac=progress_frac()) self.replace_history_on_next_cfi_update = True def update_toc_position(self): diff --git a/src/pyj/read_book/paged_mode.pyj b/src/pyj/read_book/paged_mode.pyj index 22a15e0972..874c58fd13 100644 --- a/src/pyj/read_book/paged_mode.pyj +++ b/src/pyj/read_book/paged_mode.pyj @@ -466,7 +466,8 @@ def scroll_by_page(backward, by_screen): pos = previous_col_location() if backward else next_col_location() pages = 1 if pos is -1: - get_boss().report_human_scroll() + # dont report human scroll since we dont know if a full page was + # scrolled or not get_boss().send_message('next_spine_item', previous=backward) else: if not backward: diff --git a/src/pyj/read_book/prefs/head_foot.pyj b/src/pyj/read_book/prefs/head_foot.pyj index 5623191ef8..01811ad1a9 100644 --- a/src/pyj/read_book/prefs/head_foot.pyj +++ b/src/pyj/read_book/prefs/head_foot.pyj @@ -34,6 +34,9 @@ def create_item(region, label): opt(_('Current section'), 'section'), sep(), opt(_('Progress'), 'progress'), + opt(_('Time to read book'), 'time-book'), + opt(_('Time to read chapter'), 'time-chapter'), + opt(_('Time to read chapter and book'), 'time-chapter-book'), opt(_('Clock'), 'clock'), )) ) @@ -103,14 +106,26 @@ def commit_head_foot(onchange, container): if window.Intl?.DateTimeFormat: - date_formatter = window.Intl.DateTimeFormat(undefined, {'hour':'numeric', 'minute':'numeric'}) + time_formatter = window.Intl.DateTimeFormat(undefined, {'hour':'numeric', 'minute':'numeric'}) else: - date_formatter = {'format': def(date): + time_formatter = {'format': def(date): return '{}:{}'.format(date.getHours(), date.getMinutes()) } -def render_head_foot(div, which, region, progress_frac, metadata, current_toc_node, current_toc_toplevel_node): +def format_time_left(seconds): + hours, minutes = divmod(int(seconds / 60), 60) + if hours <= 0: + if minutes < 1: + return _('almost done') + minutes = minutes + return _('{} mins').format(minutes) + if not minutes: + return _('{} hours').format(hours) + return _('{} h {} mins').format(hours, minutes) + + +def render_head_foot(div, which, region, progress_frac, metadata, current_toc_node, current_toc_toplevel_node, book_time, chapter_time): template = get_session_data().get(which) or {} field = template[region] or 'empty' interface_data = get_interface_data() @@ -128,7 +143,7 @@ def render_head_foot(div, which, region, progress_frac, metadata, current_toc_no ival = fmt_sidx(ival, use_roman=interface_data.use_roman_numerals_for_series_number) text = _('{0} of {1}').format(ival, metadata.series) elif field is 'clock': - text = date_formatter.format(Date()) + text = time_formatter.format(Date()) has_clock = True elif field is 'section': text = current_toc_node.title if current_toc_node else '' @@ -136,6 +151,16 @@ def render_head_foot(div, which, region, progress_frac, metadata, current_toc_no text = current_toc_toplevel_node.title if current_toc_toplevel_node else '' if not text: text = current_toc_node.title if current_toc_node else '' + elif field.startswith('time-'): + if book_time is None or chapter_time is None: + text = _('Calculating...') + else: + if field is 'time-book': + text = format_time_left(book_time) + elif field is 'time-chapter': + text = format_time_left(chapter_time) + else: + text = '{} ({})'.format(format_time_left(chapter_time), format_time_left(book_time)) if text is not div.textContent: div.textContent = text div.style.display = 'block' if text else 'none' diff --git a/src/pyj/read_book/timers.pyj b/src/pyj/read_book/timers.pyj index 4897421417..c41b294486 100644 --- a/src/pyj/read_book/timers.pyj +++ b/src/pyj/read_book/timers.pyj @@ -31,8 +31,8 @@ class Timers: self.average = avg sq = 0 for v'var i = 0; i < rlen; i++': - x = rates[i] - sq += (x - avg) * (x - avg) + x = rates[i] - avg + sq += x * x self.stddev = Math.sqrt(sq / (rlen - 1)) else: self.average = self.stddev = 0 @@ -42,7 +42,7 @@ class Timers: self.last_scroll_at = now = window.performance.now() if last_scroll_at is None: return - time_since_last_scroll = now - last_scroll_at + time_since_last_scroll = (now - last_scroll_at) / 1000 if time_since_last_scroll <= 0 or time_since_last_scroll >= 300: return if is_large_scroll and time_since_last_scroll < 2: @@ -54,3 +54,8 @@ class Timers: self.rates.shift() self.rates.push(rate) self.calculate() + + def time_for(self, length): + if length >= 0 and self.rates.length >= THRESHOLD and self.average > 0: + return length / self.average + return None diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index b3c0b06ef2..a57f5833b8 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -119,7 +119,7 @@ class View: self.ui = ui self.timers = Timers() self.loaded_resources = {} - self.current_progress_frac = 0 + self.current_progress_frac = self.current_file_progress_frac = 0 self.current_toc_node = self.current_toc_toplevel_node = None self.clock_timer_id = 0 sd = get_session_data() @@ -504,6 +504,7 @@ class View: self.ui.db.update_last_read_time(self.book) lrd = {'device':get_device_uuid(), 'cfi':data.cfi, 'pos_frac':data.progress_frac} self.current_progress_frac = data.progress_frac + self.current_file_progress_frac = data.file_progress_frac self.update_header_footer() key = self.book.key if username: @@ -517,13 +518,24 @@ class View: sd = get_session_data() has_clock = False + if self.book?.manifest: + book_length = self.book.manifest.total_length or 0 + name = self.currently_showing.name + chapter_length = self.book.manifest.files[name]?.length or 0 + else: + book_length = chapter_length = 0 + book_length *= max(0, 1 - self.current_progress_frac) + chapter_length *= max(0, 1 - self.current_file_progress_frac) + book_time = self.timers.time_for(book_length) + chapter_time = self.timers.time_for(chapter_length) + def render_template(div, sz_attr, name): nonlocal has_clock if sd.get(sz_attr, 20) > 5: mi = self.book.metadata - texta, hca = render_head_foot(div.firstChild, name, 'left', self.current_progress_frac, mi, self.current_toc_node, self.current_toc_toplevel_node) - textb, hcb = render_head_foot(div.firstChild.nextSibling, name, 'middle', self.current_progress_frac, mi, self.current_toc_node, self.current_toc_toplevel_node) - textc, hcc = render_head_foot(div.lastChild, name, 'right', self.current_progress_frac, mi, self.current_toc_node, self.current_toc_toplevel_node) + texta, hca = render_head_foot(div.firstChild, name, 'left', self.current_progress_frac, mi, self.current_toc_node, self.current_toc_toplevel_node, book_time, chapter_time) + textb, hcb = render_head_foot(div.firstChild.nextSibling, name, 'middle', self.current_progress_frac, mi, self.current_toc_node, self.current_toc_toplevel_node, book_time, chapter_time) + textc, hcc = render_head_foot(div.lastChild, name, 'right', self.current_progress_frac, mi, self.current_toc_node, self.current_toc_toplevel_node, book_time, chapter_time) has_clock = hca or hcb or hcc if textc and not textb and not texta: # Want right-aligned