From 43338d61232c51ed024bd96d2be6be151dab7363 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 21 Feb 2024 17:21:24 +0530 Subject: [PATCH] More work on profiles --- src/calibre/gui2/viewer/config.py | 9 +++- src/calibre/gui2/viewer/web_view.py | 17 ++++--- src/pyj/read_book/profiles.pyj | 78 ++++++++++++++++++++++------- src/pyj/viewer-main.pyj | 38 +++++++------- 4 files changed, 94 insertions(+), 48 deletions(-) diff --git a/src/calibre/gui2/viewer/config.py b/src/calibre/gui2/viewer/config.py index ef62fbd72e..afb41d9618 100644 --- a/src/calibre/gui2/viewer/config.py +++ b/src/calibre/gui2/viewer/config.py @@ -113,13 +113,18 @@ def save_viewer_profile(profile_name, profile, *user_names: str): user_names = expand_profile_user_names(user_names) if isinstance(profile, (str, bytes)): profile = json.loads(profile) - profile['__timestamp__'] = isoformat(utcnow()) + if isinstance(profile, dict): + profile['__timestamp__'] = isoformat(utcnow()) try: with open(os.path.join(viewer_config_dir, 'profiles.json'), 'rb') as f: raw = json.loads(f.read()) except FileNotFoundError: raw = {} for name in user_names: - raw.setdefault(name, {})[profile_name] = profile + if isinstance(profile, dict): + raw.setdefault(name, {})[profile_name] = profile + else: + if name in raw: + raw[name].pop(profile_name, None) with open(os.path.join(viewer_config_dir, 'profiles.json'), 'wb') as f: f.write(json.dumps(raw, indent=2, sort_keys=True).encode()) diff --git a/src/calibre/gui2/viewer/web_view.py b/src/calibre/gui2/viewer/web_view.py index c563f89696..8acc486e12 100644 --- a/src/calibre/gui2/viewer/web_view.py +++ b/src/calibre/gui2/viewer/web_view.py @@ -190,9 +190,6 @@ class UrlSchemeHandler(QWebEngineUrlSchemeHandler): encoded_fname = name[len('reader-background-'):] mt, data = background_image(encoded_fname) send_reply(rq, mt, data) if data else rq.fail(QWebEngineUrlRequestJob.Error.UrlNotFound) - elif name == 'all-profiles': - vp = load_viewer_profiles('viewer:', as_json_string=True) - send_reply(rq, 'application/json', vp.encode()) elif name.startswith('mathjax/'): handle_mathjax_request(rq, name) elif not name: @@ -287,7 +284,7 @@ class ViewerBridge(Bridge): show_book_folder = from_js() show_help = from_js(object) update_reading_rates = from_js(object) - save_profile = from_js(object, object) + profile_op = from_js(object, object, object) create_view = to_js() start_book_load = to_js() @@ -307,6 +304,7 @@ class ViewerBridge(Bridge): repair_after_fullscreen_switch = to_js() viewer_font_size_changed = to_js() tts_event = to_js() + profile_response = to_js() def apply_font_settings(page_or_view): @@ -552,7 +550,7 @@ class WebView(RestartingWebEngineView): self.bridge.close_prep_finished.connect(self.close_prep_finished) self.bridge.highlights_changed.connect(self.highlights_changed) self.bridge.update_reading_rates.connect(self.update_reading_rates) - self.bridge.save_profile.connect(self.save_profile) + self.bridge.profile_op.connect(self.profile_op) self.bridge.edit_book.connect(self.edit_book) self.bridge.show_book_folder.connect(self.show_book_folder) self.bridge.show_help.connect(self.show_help) @@ -572,8 +570,13 @@ class WebView(RestartingWebEngineView): self.inspector = Inspector(parent.inspector_dock.toggleViewAction(), self) parent.inspector_dock.setWidget(self.inspector) - def save_profile(self, name, settings): - save_viewer_profile(name, settings, 'viewer:') + def profile_op(self, which, profile_name, settings): + if which == 'all-profiles': + vp = load_viewer_profiles('viewer:') + self.execute_when_ready('profile_response', 'all-profiles', vp) + elif which == 'save-profile': + save_viewer_profile(profile_name, settings, 'viewer:') + self.execute_when_ready('profile_response', 'save-profile', profile_name) def link_hovered(self, url): if url == 'javascript:void(0)': diff --git a/src/pyj/read_book/profiles.pyj b/src/pyj/read_book/profiles.pyj index 02825a3b9a..a2139f47b1 100644 --- a/src/pyj/read_book/profiles.pyj +++ b/src/pyj/read_book/profiles.pyj @@ -5,8 +5,10 @@ from __python__ import bound_methods, hash_literals from elementmaker import E from book_list.globals import get_session_data +from book_list.item_list import create_item, create_item_list, create_side_action from dom import clear, unique_id from gettext import gettext as _ +from modals import question_dialog from read_book.globals import ui_operations from session import settings_for_reader_profile from widgets import create_button @@ -19,29 +21,72 @@ def save_profile(container_id, hide_panel): name = container.querySelector('[name=profile_name]').value sd = get_session_data() settings = settings_for_reader_profile(sd) - ui_operations.save_profile(name, settings, def(err_msg): - if err_msg: - ui_operations.show_error(_('Unhandled error'), _('Could not save profile with error: ') + err_msg) - else: - container = document.getElementById(container_id) - if container: - r = container.querySelector('.saved_msg') - if r: - clear(r) - r.appendChild(E.div(E.i(_('Saved: ') + name), style='margin-top: 1rem; margin-bottom: 1rem')) + ui_operations.save_profile(name, settings, def(): + container = document.getElementById(container_id) + if container: + r = container.querySelector('.saved_msg') + if r: + clear(r) + r.appendChild(E.div(E.i(_('Saved: ') + name), style='margin-top: 1rem; margin-bottom: 1rem')) + load_profiles(container_id) ) - -def got_all_profiles(hide_panel, container_id, all_profiles): +def use_profile(container_id, profile_name, profile_data): container = document.getElementById(container_id) if not container: return - print(all_profiles) + container.dispatchEvent(new Event('close_panel')) + + +def delete_profile(container_id, profile_name): + question_dialog(_('Are you sure?'), _('Are you sure you want to delete the saved profile named: {}?').format(profile_name), + def (ok): + if ok: + ui_operations.save_profile(profile_name, None, def(): + load_profiles(container_id) + ) + ) + + +def got_all_profiles(container_id, all_profiles): + container = document.getElementById(container_id) + if not container: + return + all_profiles['__default__'] = {} + names = list_wrap(Object.keys(all_profiles)) + names.pysort(def(x): return '' if x is '__default__' else x.toLowerCase();) + pl = container.querySelector('.profiles_list_container') + clear(pl) + pl.appendChild(E.div( + _('Load settings from one of the previously saved profiles below…') + )) + pl.appendChild(E.div()) + items = [] + for name in names: + display_name = name + if name is '__default__': + display_name = _('Restore settings to default values') + side_actions = [] + else: + side_actions = [create_side_action('trash', delete_profile.bind(None, container_id, name), _('Delete this profile'))] + items.push(create_item(display_name, action=use_profile.bind(None, container_id, name, all_profiles[name]), side_actions=side_actions)) + create_item_list(pl.lastChild, items) + + +def load_profiles(container_id): + container = document.getElementById(container_id) + if not container: + return + pl = container.querySelector('.profiles_list_container') + clear(pl) + pl.appendChild(E.div(_('Loading saved profiles, please wait…'), style='padding-bottom: 1rem')) + ui_operations.get_all_profiles(got_all_profiles.bind(None, container_id)) def create_profiles_panel(hide_panel, book, container, onclick): c = E.div(style='margin: 1rem', id=unique_id('placeholder-prefs')) + c.addEventListener('close_panel', def(): hide_panel();, False) container_id = c.id a = E.div( style='display:flex', @@ -61,8 +106,5 @@ def create_profiles_panel(hide_panel, book, container, onclick): c.appendChild(a) c.appendChild(E.div(class_='saved_msg')) c.appendChild(E.hr()) - c.appendChild(E.div( - style='padding-bottom: 1rem', class_='loading_msg', - _('Loading saved profiles, please wait…') - )) - ui_operations.get_all_profiles(got_all_profiles.bind(None, hide_panel, container.id) + c.appendChild(E.div(class_='profiles_list_container')) + load_profiles(container_id) diff --git a/src/pyj/viewer-main.pyj b/src/pyj/viewer-main.pyj index 27e576cfa8..9c69d92f5f 100644 --- a/src/pyj/viewer-main.pyj +++ b/src/pyj/viewer-main.pyj @@ -72,28 +72,26 @@ def get_file(book, name, proceed): xhr.send() -def profiles_recevied(proceed, end_type, xhr, ev): - end_type = workaround_qt_bug(xhr, end_type) - if end_type is 'abort': - return - if end_type is not 'load': - show_error(_('Failed to load profiles'), _( - 'Could not load viewer profiles with error: {}').format(xhr.error_html)) - return - if not xhr.responseType or xhr.responseType is 'text': - result = JSON.parse(xhr.responseText) - else: - show_error(_('Failed to load profiles'), _( - 'Could not load viewer profiles: unknown response type: {}').format(xhr.responseType)) - return +profiles_callbacks = {'all': None, 'saved': None} - proceed(result) +@from_python +def profile_response(which, x): + if which is 'all-profiles': + profiles_callbacks.all(x) + profiles_callbacks.all = None + elif which is 'save-profile': + profiles_callbacks.saved() + profiles_callbacks.saved = None def get_all_profiles(proceed): - xhr = ajax('all-profiles', profiles_recevied.bind(None, proceed), ok_code=0) - xhr.responseType = 'text' - xhr.send() + profiles_callbacks.all = proceed + to_python.profile_op('all-profiles', '', None) + + +def save_profile(name, settings, proceed): + profiles_callbacks.saved = proceed + to_python.profile_op('save-profile', name, settings) def get_mathjax_files(proceed): @@ -351,9 +349,7 @@ if window is window.top: if TRANSLATIONS_DATA: install(TRANSLATIONS_DATA) ui_operations.get_all_profiles = get_all_profiles - ui_operations.save_profile = def(name, settings, proceed): - to_python.save_profile(name, settings) - proceed() + ui_operations.save_profile = save_profile ui_operations.get_file = get_file ui_operations.get_url_for_book_file_name = get_url_for_book_file_name ui_operations.get_mathjax_files = get_mathjax_files