If deck options are modified, ask before closing (#3410)

* If deck options are modified, ask before closing

This imitates the way the note editor behaves. If a user assumes by
error that chanhges are automatically saved, it ensures they won't
lose them.

Also, this will eventually allows to have the same feature on
AnkiDroid. While, currently, we always ask the user whether they want
to close the deck options, even when there are no modification, which
seems to regularly frustate users (including myself).

I'm new to Svelte, please let me know whether there is a better way to
obtain the information from Svelte state that I missed.

Note that I ensured that only a boolean can be obtained. I didn't
cause the whole state to be accessible. May be useful for some
add-ons, I guess, but risks breaking too much things.

Regarding the deckoptions.py, I tried to imitate addcards.py way to
check whether the add card view can be closed. Reusing the same
function and variable name when possible.

* Update qt/aqt/deckoptions.py (dae)
This commit is contained in:
Arthur Milchior 2024-09-22 10:07:24 +02:00 committed by GitHub
parent 163c10191f
commit b08e454f57
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 56 additions and 0 deletions

View file

@ -14,6 +14,7 @@ from aqt.qt import *
from aqt.utils import ( from aqt.utils import (
KeyboardModifiersPressed, KeyboardModifiersPressed,
addCloseShortcut, addCloseShortcut,
ask_user_dialog,
disable_help_button, disable_help_button,
restoreGeom, restoreGeom,
saveGeom, saveGeom,
@ -33,6 +34,8 @@ class DeckOptionsDialog(QDialog):
self.mw = mw self.mw = mw
self._deck = deck self._deck = deck
self._setup_ui() self._setup_ui()
self._close_event_has_cleaned_up = False
self._ready = False
def _setup_ui(self) -> None: def _setup_ui(self) -> None:
self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setWindowModality(Qt.WindowModality.ApplicationModal)
@ -57,8 +60,44 @@ class DeckOptionsDialog(QDialog):
def _on_bridge_cmd(self, cmd: str) -> None: def _on_bridge_cmd(self, cmd: str) -> None:
if cmd == "deckOptionsReady": if cmd == "deckOptionsReady":
self.ready = True
gui_hooks.deck_options_did_load(self) gui_hooks.deck_options_did_load(self)
def closeEvent(self, evt: QCloseEvent) -> None:
if self._close_event_has_cleaned_up:
evt.accept()
return
evt.ignore()
self.if_can_close()
def _close(self):
"""Close. Ensure the closeEvent is not ignored."""
self._close_event_has_cleaned_up = True
self.close()
def if_can_close(self):
"""Close if there was no modification. Otherwise ask for confirmation first."""
def callbackWithUserChoice(choice: int):
if choice == 0:
# The user accepted to discard current input.
self._close()
def if_can_close_callback_with_data_information(has_modified_dataData: bool):
if has_modified_dataData:
ask_user_dialog(
tr.card_templates_discard_changes(),
callback=callbackWithUserChoice,
buttons=[
QMessageBox.StandardButton.Discard,
(tr.adding_keep_editing(), QMessageBox.ButtonRole.RejectRole),
],
)
else:
self._close()
self.has_modified_data(if_can_close_callback_with_data_information)
def reject(self) -> None: def reject(self) -> None:
self.mw.col.set_wants_abort() self.mw.col.set_wants_abort()
self.web.cleanup() self.web.cleanup()
@ -66,6 +105,15 @@ class DeckOptionsDialog(QDialog):
saveGeom(self, self.TITLE) saveGeom(self, self.TITLE)
QDialog.reject(self) QDialog.reject(self)
def has_modified_data(self, callback: Callable[[bool], None]):
"""Calls `callback` with the information of whether any deck options are modified."""
if self.ready:
self.web.evalWithCallback(
"anki.deckOptionsPendingChanges()", callback
)
else:
callback(False)
def confirm_deck_then_display_options(active_card: Card | None = None) -> None: def confirm_deck_then_display_options(active_card: Card | None = None) -> None:
decks = [aqt.mw.col.decks.current()] decks = [aqt.mw.col.decks.current()]

View file

@ -11,6 +11,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export let data: PageData; export let data: PageData;
let page: DeckOptionsPage; let page: DeckOptionsPage;
globalThis.anki ||= {};
globalThis.anki.deckOptionsPendingChanges = () => {
return data.state.isModified();
};
onMount(() => { onMount(() => {
globalThis.$deckOptions = new Promise((resolve, _reject) => { globalThis.$deckOptions = new Promise((resolve, _reject) => {
resolve(page); resolve(page);

View file

@ -292,6 +292,10 @@ export class DeckOptionsState {
}); });
return list; return list;
} }
isModified(): boolean {
return this.removedConfigs.length > 0 || this.modifiedConfigs.size > 0;
}
} }
function bytesToObject(bytes: Uint8Array): Record<string, unknown> { function bytesToObject(bytes: Uint8Array): Record<string, unknown> {