From a47453d5f32d88afde7170a3b819c352a2184845 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sat, 16 Oct 2021 23:32:00 +0200 Subject: [PATCH 01/19] Implement reactively updating Card Info --- ts/card-info/CardInfo.svelte | 127 +++++++++++++++++++---------------- ts/card-info/Revlog.svelte | 5 +- ts/card-info/index.ts | 18 +++++ 3 files changed, 90 insertions(+), 60 deletions(-) diff --git a/ts/card-info/CardInfo.svelte b/ts/card-info/CardInfo.svelte index c0c980f59..e1f8e1e97 100644 --- a/ts/card-info/CardInfo.svelte +++ b/ts/card-info/CardInfo.svelte @@ -19,67 +19,80 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html value: string | number; } - const statsRows: StatsRow[] = []; + function rowsFromStats(stats: Stats.CardStatsResponse): StatsRow[] { + const statsRows: StatsRow[] = []; - statsRows.push({ label: tr2.cardStatsAdded(), value: dateString(stats.added) }); + statsRows.push({ label: tr2.cardStatsAdded(), value: dateString(stats.added) }); - const firstReview = unwrapOptionalNumber(stats.firstReview); - if (firstReview !== undefined) { - statsRows.push({ - label: tr2.cardStatsFirstReview(), - value: dateString(firstReview), - }); - } - const latestReview = unwrapOptionalNumber(stats.latestReview); - if (latestReview !== undefined) { - statsRows.push({ - label: tr2.cardStatsLatestReview(), - value: dateString(latestReview), - }); + const firstReview = unwrapOptionalNumber(stats.firstReview); + if (firstReview !== undefined) { + statsRows.push({ + label: tr2.cardStatsFirstReview(), + value: dateString(firstReview), + }); + } + const latestReview = unwrapOptionalNumber(stats.latestReview); + if (latestReview !== undefined) { + statsRows.push({ + label: tr2.cardStatsLatestReview(), + value: dateString(latestReview), + }); + } + + const dueDate = unwrapOptionalNumber(stats.dueDate); + if (dueDate !== undefined) { + statsRows.push({ + label: tr2.statisticsDueDate(), + value: dateString(dueDate), + }); + } + const duePosition = unwrapOptionalNumber(stats.duePosition); + if (duePosition !== undefined) { + statsRows.push({ + label: tr2.cardStatsNewCardPosition(), + value: dateString(duePosition), + }); + } + + if (stats.interval) { + statsRows.push({ + label: tr2.cardStatsInterval(), + value: timeSpan(stats.interval * DAY), + }); + } + if (stats.ease) { + statsRows.push({ + label: tr2.cardStatsEase(), + value: `${stats.ease / 10}%`, + }); + } + + statsRows.push({ label: tr2.cardStatsReviewCount(), value: stats.reviews }); + statsRows.push({ label: tr2.cardStatsLapseCount(), value: stats.lapses }); + + if (stats.totalSecs) { + statsRows.push({ + label: tr2.cardStatsAverageTime(), + value: timeSpan(stats.averageSecs), + }); + statsRows.push({ + label: tr2.cardStatsTotalTime(), + value: timeSpan(stats.totalSecs), + }); + } + + statsRows.push({ label: tr2.cardStatsCardTemplate(), value: stats.cardType }); + statsRows.push({ label: tr2.cardStatsNoteType(), value: stats.notetype }); + statsRows.push({ label: tr2.cardStatsDeckName(), value: stats.deck }); + + statsRows.push({ label: tr2.cardStatsCardId(), value: stats.cardId }); + statsRows.push({ label: tr2.cardStatsNoteId(), value: stats.noteId }); + + return statsRows; } - const dueDate = unwrapOptionalNumber(stats.dueDate); - if (dueDate !== undefined) { - statsRows.push({ label: tr2.statisticsDueDate(), value: dateString(dueDate) }); - } - const duePosition = unwrapOptionalNumber(stats.duePosition); - if (duePosition !== undefined) { - statsRows.push({ - label: tr2.cardStatsNewCardPosition(), - value: dateString(duePosition), - }); - } - - if (stats.interval) { - statsRows.push({ - label: tr2.cardStatsInterval(), - value: timeSpan(stats.interval * DAY), - }); - } - if (stats.ease) { - statsRows.push({ label: tr2.cardStatsEase(), value: `${stats.ease / 10}%` }); - } - - statsRows.push({ label: tr2.cardStatsReviewCount(), value: stats.reviews }); - statsRows.push({ label: tr2.cardStatsLapseCount(), value: stats.lapses }); - - if (stats.totalSecs) { - statsRows.push({ - label: tr2.cardStatsAverageTime(), - value: timeSpan(stats.averageSecs), - }); - statsRows.push({ - label: tr2.cardStatsTotalTime(), - value: timeSpan(stats.totalSecs), - }); - } - - statsRows.push({ label: tr2.cardStatsCardTemplate(), value: stats.cardType }); - statsRows.push({ label: tr2.cardStatsNoteType(), value: stats.notetype }); - statsRows.push({ label: tr2.cardStatsDeckName(), value: stats.deck }); - - statsRows.push({ label: tr2.cardStatsCardId(), value: stats.cardId }); - statsRows.push({ label: tr2.cardStatsNoteId(), value: stats.noteId }); + let statsRows: StatsRow[]; + $: statsRows = rowsFromStats(stats);
diff --git a/ts/card-info/Revlog.svelte b/ts/card-info/Revlog.svelte index b5e6f6f0b..f8ec555c4 100644 --- a/ts/card-info/Revlog.svelte +++ b/ts/card-info/Revlog.svelte @@ -72,9 +72,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html }; } - const revlogRows: RevlogRow[] = stats.revlog.map((entry) => - revlogRowFromEntry(entry) - ); + let revlogRows: RevlogRow[]; + $: revlogRows = stats.revlog.map((entry) => revlogRowFromEntry(entry)); {#if stats.revlog.length} diff --git a/ts/card-info/index.ts b/ts/card-info/index.ts index 61cd7d9ac..db972562c 100644 --- a/ts/card-info/index.ts +++ b/ts/card-info/index.ts @@ -7,6 +7,8 @@ import { checkNightMode } from "../lib/nightmode"; import CardInfo from "./CardInfo.svelte"; +const _updatingQueue: Promise = Promise.resolve(); + export async function cardInfo( target: HTMLDivElement, cardId: number, @@ -31,3 +33,19 @@ export async function cardInfo( props: { stats }, }); } + +export async function updateCardInfo( + cardInfo: Promise, + cardId: number, + includeRevlog: boolean +): Promise { + _updatingQueue.then(async () => { + cardInfo.then(async (cardInfo) => { + const stats = await getCardStats(cardId); + if (!includeRevlog) { + stats.revlog = []; + } + cardInfo.$set({ stats }); + }); + }); +} From 859b1d1a396dd5c4b4c06f0cc2c3b63dfa167ff1 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sat, 16 Oct 2021 23:33:58 +0200 Subject: [PATCH 02/19] Make Card Info Dialog non-modal --- qt/aqt/browser/card_info.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/qt/aqt/browser/card_info.py b/qt/aqt/browser/card_info.py index b059bfaa5..c3e4520f3 100644 --- a/qt/aqt/browser/card_info.py +++ b/qt/aqt/browser/card_info.py @@ -3,6 +3,8 @@ from __future__ import annotations +from typing import Callable + import aqt from anki.cards import Card, CardId from aqt.qt import * @@ -21,18 +23,32 @@ class CardInfoDialog(QDialog): GEOMETRY_KEY = "revlog" silentlyClose = True - def __init__(self, parent: QWidget, mw: aqt.AnkiQt, card: Card) -> None: + def __init__( + self, + parent: QWidget | None, + mw: aqt.AnkiQt, + card: Card, + on_close: Callable | None = None, + geometry_key: str | None = None, + window_title: str | None = None, + ) -> None: super().__init__(parent) self.mw = mw + self._on_close = on_close + self.GEOMETRY_KEY = geometry_key or self.GEOMETRY_KEY + if window_title: + self.setWindowTitle(window_title) self._setup_ui(card.id) self.show() def _setup_ui(self, card_id: CardId) -> None: - self.setWindowModality(Qt.WindowModality.ApplicationModal) self.mw.garbage_collect_on_dialog_finish(self) disable_help_button(self) restoreGeom(self, self.GEOMETRY_KEY) addCloseShortcut(self) + icon = QIcon() + icon.addPixmap(QPixmap("icons:anki.png"), QIcon.Mode.Normal, QIcon.State.Off) + self.setWindowIcon(icon) self.web = AnkiWebView(title=self.TITLE) self.web.setVisible(False) @@ -47,10 +63,15 @@ class CardInfoDialog(QDialog): self.setLayout(layout) self.web.eval( - f"anki.cardInfo(document.getElementById('main'), {card_id}, true);" + f"let cardInfo = anki.cardInfo(document.getElementById('main'), {card_id}, true);" ) + def update_card(self, card_id: CardId) -> None: + self.web.eval(f"anki.updateCardInfo(cardInfo, {card_id}, true);") + def reject(self) -> None: + if self._on_close: + self._on_close() self.web = None saveGeom(self, self.GEOMETRY_KEY) return QDialog.reject(self) From 13024fccccdd3b1161294c9aea06582b610fab8b Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sat, 16 Oct 2021 23:36:54 +0200 Subject: [PATCH 03/19] Add update handler for Card Info Dialog --- qt/aqt/browser/card_info.py | 41 +++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/qt/aqt/browser/card_info.py b/qt/aqt/browser/card_info.py index c3e4520f3..e74656ff7 100644 --- a/qt/aqt/browser/card_info.py +++ b/qt/aqt/browser/card_info.py @@ -75,3 +75,44 @@ class CardInfoDialog(QDialog): self.web = None saveGeom(self, self.GEOMETRY_KEY) return QDialog.reject(self) + + +class CardInfoManager: + """Wrapper class to conveniently toggle, update and close a card info dialog.""" + + def __init__(self, mw: aqt.AnkiQt, geometry_key: str, window_title: str): + self.mw = mw + self.geometry_key = geometry_key + self.window_title = window_title + self._card: Card | None = None + self._dialog: CardInfoDialog | None = None + + def toggle(self) -> None: + """Opening requires a card to be set.""" + if not self._dialog and self._card: + self._dialog = CardInfoDialog( + None, + self.mw, + self._card, + self._on_close, + self.geometry_key, + self.window_title, + ) + elif self._dialog: + self._dialog.reject() + + def set_card(self, card: Card | None) -> None: + """Closes the dialog if card is None.""" + self._card = card + if self._dialog and self._card: + self._dialog.update_card(card.id) + elif self._dialog: + self._dialog.reject() + + def close(self) -> None: + if self._dialog: + self.toggle() + + def _on_close(self) -> None: + self._dialog = None + From f0d7e6f4d1c92aa9045e6d0d459a1c025d01f138 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sat, 16 Oct 2021 23:38:11 +0200 Subject: [PATCH 04/19] Use updating card infos in browser and reviewer --- ftl/core/card-stats.ftl | 6 ++++++ qt/aqt/browser/browser.py | 12 ++++++++---- qt/aqt/browser/card_info.py | 17 +++++++++++++++++ qt/aqt/reviewer.py | 13 ++++++++----- 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/ftl/core/card-stats.ftl b/ftl/core/card-stats.ftl index af08defcd..9276aea44 100644 --- a/ftl/core/card-stats.ftl +++ b/ftl/core/card-stats.ftl @@ -22,3 +22,9 @@ card-stats-review-log-type-review = Review card-stats-review-log-type-relearn = Relearn card-stats-review-log-type-filtered = Filtered card-stats-review-log-type-manual = Manual + +## Window Titles + +card-stats-browser-card = Browser Card +card-stats-reviewer-card = Reviewer Card +card-stats-previous-reviewer-card = Previous Reviewer Card diff --git a/qt/aqt/browser/browser.py b/qt/aqt/browser/browser.py index 1c90beeaf..4b66b2ef9 100644 --- a/qt/aqt/browser/browser.py +++ b/qt/aqt/browser/browser.py @@ -58,7 +58,7 @@ from aqt.utils import ( ) from ..changenotetype import change_notetype_dialog -from .card_info import CardInfoDialog +from .card_info import BrowserCardInfo from .find_and_replace import FindAndReplaceDialog from .previewer import BrowserPreviewer as PreviewDialog from .previewer import Previewer @@ -110,6 +110,7 @@ class Browser(QMainWindow): self.lastFilter = "" self.focusTo: int | None = None self._previewer: Previewer | None = None + self._card_info = BrowserCardInfo(self.mw) self._closeEventHasCleanedUp = False self.form = aqt.forms.browser.Ui_Dialog() self.form.setupUi(self) @@ -155,6 +156,7 @@ class Browser(QMainWindow): if changes.browser_table and changes.card: self.card = self.table.get_single_selected_card() self.current_card = self.table.get_current_card() + self._update_card_info() self._update_current_actions() # changes.card is required for updating flag icon @@ -236,6 +238,7 @@ class Browser(QMainWindow): def _closeWindow(self) -> None: self._cleanup_preview() + self._card_info.close() self.editor.cleanup() self.table.cleanup() self.sidebar.cleanup() @@ -447,6 +450,7 @@ class Browser(QMainWindow): return self.current_card = self.table.get_current_card() self._update_current_actions() + self._update_card_info() def _update_row_actions(self) -> None: has_rows = bool(self.table.len()) @@ -545,10 +549,10 @@ class Browser(QMainWindow): ###################################################################### def showCardInfo(self) -> None: - if not self.current_card: - return + self._card_info.toggle() - CardInfoDialog(parent=self, mw=self.mw, card=self.current_card) + def _update_card_info(self) -> None: + self._card_info.set_card(self.current_card) # Menu helpers ###################################################################### diff --git a/qt/aqt/browser/card_info.py b/qt/aqt/browser/card_info.py index e74656ff7..26f99426c 100644 --- a/qt/aqt/browser/card_info.py +++ b/qt/aqt/browser/card_info.py @@ -14,6 +14,7 @@ from aqt.utils import ( qconnect, restoreGeom, saveGeom, + tr, ) from aqt.webview import AnkiWebView @@ -116,3 +117,19 @@ class CardInfoManager: def _on_close(self) -> None: self._dialog = None + +class BrowserCardInfo(CardInfoManager): + def __init__(self, mw: aqt.AnkiQt): + super().__init__(mw, "revlog", tr.card_stats_browser_card()) + + +class ReviewerCardInfo(CardInfoManager): + def __init__(self, mw: aqt.AnkiQt): + super().__init__(mw, "reviewerCardInfo", tr.card_stats_reviewer_card()) + + +class PreviousReviewerCardInfo(CardInfoManager): + def __init__(self, mw: aqt.AnkiQt): + super().__init__( + mw, "previousReviewerCardInfo", tr.card_stats_previous_reviewer_card() + ) diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index afc309655..47167363d 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -21,7 +21,7 @@ from anki.scheduler.v3 import Scheduler as V3Scheduler from anki.tags import MARKED_TAG from anki.utils import stripHTML from aqt import AnkiQt, gui_hooks -from aqt.browser.card_info import CardInfoDialog +from aqt.browser.card_info import PreviousReviewerCardInfo, ReviewerCardInfo from aqt.deckoptions import confirm_deck_then_display_options from aqt.operations.card import set_card_flag from aqt.operations.note import remove_notes @@ -126,6 +126,8 @@ class Reviewer: self._v3: V3CardInfo | None = None self._state_mutation_key = str(random.randint(0, 2 ** 64 - 1)) self.bottom = BottomBar(mw, mw.bottomWeb) + self._card_info = ReviewerCardInfo(self.mw) + self._previous_card_info = PreviousReviewerCardInfo(self.mw) hooks.card_did_leech.append(self.onLeech) def show(self) -> None: @@ -197,6 +199,9 @@ class Reviewer: else: self._get_next_v3_card() + self._previous_card_info.set_card(self.previous_card) + self._card_info.set_card(self.card) + if not self.card: self.mw.moveToState("overview") return @@ -958,12 +963,10 @@ time = %(time)d; confirm_deck_then_display_options(self.card) def on_previous_card_info(self) -> None: - if self.previous_card: - CardInfoDialog(parent=self.mw, mw=self.mw, card=self.previous_card) + self._previous_card_info.toggle() def on_card_info(self) -> None: - if self.card: - CardInfoDialog(parent=self.mw, mw=self.mw, card=self.card) + self._card_info.toggle() def set_flag_on_current_card(self, desired_flag: int) -> None: def redraw_flag(out: OpChangesWithCount) -> None: From 7eec241c82bf11643b532a25cc6436f7c8870e0f Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sun, 17 Oct 2021 17:56:40 +0200 Subject: [PATCH 05/19] Add utility func for setting window icon --- qt/aqt/browser/card_info.py | 5 ++--- qt/aqt/browser/previewer.py | 8 ++------ qt/aqt/utils.py | 6 ++++++ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/qt/aqt/browser/card_info.py b/qt/aqt/browser/card_info.py index 26f99426c..9b67e2c36 100644 --- a/qt/aqt/browser/card_info.py +++ b/qt/aqt/browser/card_info.py @@ -14,6 +14,7 @@ from aqt.utils import ( qconnect, restoreGeom, saveGeom, + setWindowIcon, tr, ) from aqt.webview import AnkiWebView @@ -47,9 +48,7 @@ class CardInfoDialog(QDialog): disable_help_button(self) restoreGeom(self, self.GEOMETRY_KEY) addCloseShortcut(self) - icon = QIcon() - icon.addPixmap(QPixmap("icons:anki.png"), QIcon.Mode.Normal, QIcon.State.Off) - self.setWindowIcon(icon) + setWindowIcon(self) self.web = AnkiWebView(title=self.TITLE) self.web.setVisible(False) diff --git a/qt/aqt/browser/previewer.py b/qt/aqt/browser/previewer.py index 190d65ecb..01b6c253f 100644 --- a/qt/aqt/browser/previewer.py +++ b/qt/aqt/browser/previewer.py @@ -17,9 +17,7 @@ from aqt.qt import ( QCheckBox, QDialog, QDialogButtonBox, - QIcon, QKeySequence, - QPixmap, QShortcut, Qt, QTimer, @@ -30,7 +28,7 @@ from aqt.qt import ( from aqt.reviewer import replay_audio from aqt.sound import av_player, play_clicked_audio from aqt.theme import theme_manager -from aqt.utils import disable_help_button, restoreGeom, saveGeom, tr +from aqt.utils import disable_help_button, restoreGeom, saveGeom, setWindowIcon, tr from aqt.webview import AnkiWebView LastStateAndMod = tuple[str, int, int] @@ -52,10 +50,8 @@ class Previewer(QDialog): self._parent = parent self._close_callback = on_close self.mw = mw - icon = QIcon() - icon.addPixmap(QPixmap("icons:anki.png"), QIcon.Mode.Normal, QIcon.State.Off) disable_help_button(self) - self.setWindowIcon(icon) + setWindowIcon(self) def card(self) -> Card | None: raise NotImplementedError diff --git a/qt/aqt/utils.py b/qt/aqt/utils.py index ab3163e9e..28f1242dc 100644 --- a/qt/aqt/utils.py +++ b/qt/aqt/utils.py @@ -406,6 +406,12 @@ def disable_help_button(widget: QWidget) -> None: ) +def setWindowIcon(widget: QWidget) -> None: + icon = QIcon() + icon.addPixmap(QPixmap("icons:anki.png"), QIcon.Mode.Normal, QIcon.State.Off) + widget.setWindowIcon(icon) + + # File handling ###################################################################### From 1d63253b4f600aedf50f51749cde93c6851d0147 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sun, 17 Oct 2021 17:58:21 +0200 Subject: [PATCH 06/19] Make window titles more user-friendly --- ftl/core/card-stats.ftl | 5 ++--- qt/aqt/browser/card_info.py | 23 ++++++++++++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/ftl/core/card-stats.ftl b/ftl/core/card-stats.ftl index 9276aea44..f6dc983c6 100644 --- a/ftl/core/card-stats.ftl +++ b/ftl/core/card-stats.ftl @@ -25,6 +25,5 @@ card-stats-review-log-type-manual = Manual ## Window Titles -card-stats-browser-card = Browser Card -card-stats-reviewer-card = Reviewer Card -card-stats-previous-reviewer-card = Previous Reviewer Card +card-stats-current-card = Current Card ({ $context }) +card-stats-previous-card = Previous Card ({ $context }) diff --git a/qt/aqt/browser/card_info.py b/qt/aqt/browser/card_info.py index 9b67e2c36..59429a232 100644 --- a/qt/aqt/browser/card_info.py +++ b/qt/aqt/browser/card_info.py @@ -7,6 +7,7 @@ from typing import Callable import aqt from anki.cards import Card, CardId +from anki.lang import without_unicode_isolation from aqt.qt import * from aqt.utils import ( addCloseShortcut, @@ -119,16 +120,32 @@ class CardInfoManager: class BrowserCardInfo(CardInfoManager): def __init__(self, mw: aqt.AnkiQt): - super().__init__(mw, "revlog", tr.card_stats_browser_card()) + super().__init__( + mw, + "revlog", + without_unicode_isolation( + tr.card_stats_current_card(context=tr.qt_misc_browse()) + ), + ) class ReviewerCardInfo(CardInfoManager): def __init__(self, mw: aqt.AnkiQt): - super().__init__(mw, "reviewerCardInfo", tr.card_stats_reviewer_card()) + super().__init__( + mw, + "reviewerCardInfo", + without_unicode_isolation( + tr.card_stats_current_card(context=tr.decks_study()) + ), + ) class PreviousReviewerCardInfo(CardInfoManager): def __init__(self, mw: aqt.AnkiQt): super().__init__( - mw, "previousReviewerCardInfo", tr.card_stats_previous_reviewer_card() + mw, + "previousReviewerCardInfo", + without_unicode_isolation( + tr.card_stats_previous_card(context=tr.decks_study()) + ), ) From 4b5ea6c11029cae225189ee4433e145607bf7bee Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sun, 17 Oct 2021 18:38:28 +0200 Subject: [PATCH 07/19] Make CardStats a separate component --- ts/card-info/CardInfo.svelte | 105 +------------------------------ ts/card-info/CardStats.svelte | 113 ++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 102 deletions(-) create mode 100644 ts/card-info/CardStats.svelte diff --git a/ts/card-info/CardInfo.svelte b/ts/card-info/CardInfo.svelte index e1f8e1e97..c6ba56fa9 100644 --- a/ts/card-info/CardInfo.svelte +++ b/ts/card-info/CardInfo.svelte @@ -3,108 +3,16 @@ Copyright: Ankitects Pty Ltd and contributors License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -->
- - {#each statsRows as row, _index} - - - - - {/each} -
{row.label}{row.value}
+
@@ -113,11 +21,4 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html .container { max-width: 40em; } - - .stats-table { - width: 100%; - border-spacing: 1em 0; - border-collapse: collapse; - text-align: start; - } diff --git a/ts/card-info/CardStats.svelte b/ts/card-info/CardStats.svelte new file mode 100644 index 000000000..ccaef6672 --- /dev/null +++ b/ts/card-info/CardStats.svelte @@ -0,0 +1,113 @@ + + + + + {#each statsRows as row, _index} + + + + + {/each} +
{row.label}{row.value}
+ + From 506202497416f8db45f067e18538fad40ec1a92d Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sun, 17 Oct 2021 20:29:32 +0200 Subject: [PATCH 08/19] Move update logic into CardInfo.svelte --- ftl/core/card-stats.ftl | 1 + qt/aqt/browser/card_info.py | 6 +++-- ts/card-info/CardInfo.svelte | 32 +++++++++++++++++++++++--- ts/card-info/index.ts | 44 ++++-------------------------------- 4 files changed, 38 insertions(+), 45 deletions(-) diff --git a/ftl/core/card-stats.ftl b/ftl/core/card-stats.ftl index f6dc983c6..82a0dbee2 100644 --- a/ftl/core/card-stats.ftl +++ b/ftl/core/card-stats.ftl @@ -22,6 +22,7 @@ card-stats-review-log-type-review = Review card-stats-review-log-type-relearn = Relearn card-stats-review-log-type-filtered = Filtered card-stats-review-log-type-manual = Manual +card-stats-no-card = (No card to display.) ## Window Titles diff --git a/qt/aqt/browser/card_info.py b/qt/aqt/browser/card_info.py index 59429a232..6addffb05 100644 --- a/qt/aqt/browser/card_info.py +++ b/qt/aqt/browser/card_info.py @@ -64,11 +64,13 @@ class CardInfoDialog(QDialog): self.setLayout(layout) self.web.eval( - f"let cardInfo = anki.cardInfo(document.getElementById('main'), {card_id}, true);" + "let cardInfo = anki.cardInfo(document.getElementById('main'));\n" \ + "cardInfo.then((c) => c.$set({ includeRevlog: true }));" ) + self.update_card(card_id) def update_card(self, card_id: CardId) -> None: - self.web.eval(f"anki.updateCardInfo(cardInfo, {card_id}, true);") + self.web.eval(f"cardInfo.then((c) => c.$set({{ cardId: {card_id} }}));") def reject(self) -> None: if self._on_close: diff --git a/ts/card-info/CardInfo.svelte b/ts/card-info/CardInfo.svelte index c6ba56fa9..c17643f9f 100644 --- a/ts/card-info/CardInfo.svelte +++ b/ts/card-info/CardInfo.svelte @@ -3,17 +3,39 @@ Copyright: Ankitects Pty Ltd and contributors License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -->
- - + {#if stats} + + {#if includeRevlog} + + {/if} + {:else} + {tr.cardStatsNoCard()} + {/if}
@@ -21,4 +43,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html .container { max-width: 40em; } + + .placeholder { + text-align: center; + } diff --git a/ts/card-info/index.ts b/ts/card-info/index.ts index db972562c..2473c285f 100644 --- a/ts/card-info/index.ts +++ b/ts/card-info/index.ts @@ -1,51 +1,15 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import { getCardStats } from "./lib"; import { setupI18n, ModuleName } from "../lib/i18n"; import { checkNightMode } from "../lib/nightmode"; import CardInfo from "./CardInfo.svelte"; -const _updatingQueue: Promise = Promise.resolve(); - -export async function cardInfo( - target: HTMLDivElement, - cardId: number, - includeRevlog: boolean -): Promise { +export async function cardInfo(target: HTMLDivElement): Promise { checkNightMode(); - const [stats] = await Promise.all([ - getCardStats(cardId), - setupI18n({ - modules: [ - ModuleName.CARD_STATS, - ModuleName.SCHEDULING, - ModuleName.STATISTICS, - ], - }), - ]); - if (!includeRevlog) { - stats.revlog = []; - } - return new CardInfo({ - target, - props: { stats }, - }); -} - -export async function updateCardInfo( - cardInfo: Promise, - cardId: number, - includeRevlog: boolean -): Promise { - _updatingQueue.then(async () => { - cardInfo.then(async (cardInfo) => { - const stats = await getCardStats(cardId); - if (!includeRevlog) { - stats.revlog = []; - } - cardInfo.$set({ stats }); - }); + await setupI18n({ + modules: [ModuleName.CARD_STATS, ModuleName.SCHEDULING, ModuleName.STATISTICS], }); + return new CardInfo({ target }); } From 3c5e14917699b0e03c374babf30996a0817929fe Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sun, 17 Oct 2021 21:08:19 +0200 Subject: [PATCH 09/19] Move update logic into CardInfo.svelte Also use a simpler and faster way to avoid race conditions. --- ts/card-info/CardInfo.svelte | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/ts/card-info/CardInfo.svelte b/ts/card-info/CardInfo.svelte index c17643f9f..0db6f23bd 100644 --- a/ts/card-info/CardInfo.svelte +++ b/ts/card-info/CardInfo.svelte @@ -13,17 +13,18 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html export let includeRevlog: boolean | undefined = undefined; let stats: Stats.CardStatsResponse | undefined = undefined; - let _updatingQueue: Promise = Promise.resolve(); - $: _updatingQueue = _updatingQueue.then(() => { - if (cardId === undefined) { - stats = undefined; - } else { - getCardStats(cardId).then((s) => { + $: if (cardId === undefined) { + stats = undefined; + } else { + const sentCardId = cardId; + getCardStats(sentCardId).then((s) => { + /* Skip if another update has been triggered in the meantime. */ + if (sentCardId === cardId) { stats = s; - }); - } - }); + } + }); + };
From ec02a4218e9bdfa8bcf0b1576d5dbb084bb06583 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sun, 17 Oct 2021 21:13:46 +0200 Subject: [PATCH 10/19] Keep Card Info Dialog open even if no card is set --- qt/aqt/browser/card_info.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/qt/aqt/browser/card_info.py b/qt/aqt/browser/card_info.py index 6addffb05..4c05112c9 100644 --- a/qt/aqt/browser/card_info.py +++ b/qt/aqt/browser/card_info.py @@ -30,7 +30,7 @@ class CardInfoDialog(QDialog): self, parent: QWidget | None, mw: aqt.AnkiQt, - card: Card, + card: Card | None, on_close: Callable | None = None, geometry_key: str | None = None, window_title: str | None = None, @@ -41,10 +41,10 @@ class CardInfoDialog(QDialog): self.GEOMETRY_KEY = geometry_key or self.GEOMETRY_KEY if window_title: self.setWindowTitle(window_title) - self._setup_ui(card.id) + self._setup_ui(card and card.id) self.show() - def _setup_ui(self, card_id: CardId) -> None: + def _setup_ui(self, card_id: CardId | None) -> None: self.mw.garbage_collect_on_dialog_finish(self) disable_help_button(self) restoreGeom(self, self.GEOMETRY_KEY) @@ -69,8 +69,9 @@ class CardInfoDialog(QDialog): ) self.update_card(card_id) - def update_card(self, card_id: CardId) -> None: - self.web.eval(f"cardInfo.then((c) => c.$set({{ cardId: {card_id} }}));") + def update_card(self, card_id: CardId | None) -> None: + val = "undefined" if card_id is None else card_id + self.web.eval(f"cardInfo.then((c) => c.$set({{ cardId: {val} }}));") def reject(self) -> None: if self._on_close: @@ -91,8 +92,9 @@ class CardInfoManager: self._dialog: CardInfoDialog | None = None def toggle(self) -> None: - """Opening requires a card to be set.""" - if not self._dialog and self._card: + if self._dialog: + self._dialog.reject() + else: self._dialog = CardInfoDialog( None, self.mw, @@ -101,16 +103,11 @@ class CardInfoManager: self.geometry_key, self.window_title, ) - elif self._dialog: - self._dialog.reject() def set_card(self, card: Card | None) -> None: - """Closes the dialog if card is None.""" self._card = card - if self._dialog and self._card: - self._dialog.update_card(card.id) - elif self._dialog: - self._dialog.reject() + if self._dialog: + self._dialog.update_card(card and card.id) def close(self) -> None: if self._dialog: From bbba21126f8b35e5bda2df9c11e650e8a80b2805 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sun, 17 Oct 2021 21:23:11 +0200 Subject: [PATCH 11/19] Improve clarity in card info code a tiny little bit --- qt/aqt/browser/card_info.py | 2 +- ts/card-info/CardInfo.svelte | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qt/aqt/browser/card_info.py b/qt/aqt/browser/card_info.py index 4c05112c9..370ef8e4c 100644 --- a/qt/aqt/browser/card_info.py +++ b/qt/aqt/browser/card_info.py @@ -64,7 +64,7 @@ class CardInfoDialog(QDialog): self.setLayout(layout) self.web.eval( - "let cardInfo = anki.cardInfo(document.getElementById('main'));\n" \ + "const cardInfo = anki.cardInfo(document.getElementById('main'));\n" \ "cardInfo.then((c) => c.$set({ includeRevlog: true }));" ) self.update_card(card_id) diff --git a/ts/card-info/CardInfo.svelte b/ts/card-info/CardInfo.svelte index 0db6f23bd..a28f84a4f 100644 --- a/ts/card-info/CardInfo.svelte +++ b/ts/card-info/CardInfo.svelte @@ -12,7 +12,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html export let cardId: number | undefined = undefined; export let includeRevlog: boolean | undefined = undefined; - let stats: Stats.CardStatsResponse | undefined = undefined; + let stats: Stats.CardStatsResponse | undefined; $: if (cardId === undefined) { stats = undefined; From cbeb992175921a527cd1d42518312d3165416e90 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sun, 17 Oct 2021 21:48:14 +0200 Subject: [PATCH 12/19] Fix `_legacy_card_stats()` --- pylib/anki/stats.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pylib/anki/stats.py b/pylib/anki/stats.py index 2b9cd01d1..61ff1726d 100644 --- a/pylib/anki/stats.py +++ b/pylib/anki/stats.py @@ -28,6 +28,7 @@ def _legacy_card_stats( ) -> str: "A quick hack to preserve compatibility with the old HTML string API." random_id = f"cardinfo-{base62(random.randint(0, 2 ** 64 - 1))}" + varName = random_id.replace('-', '') return f"""
@@ -38,7 +39,8 @@ def _legacy_card_stats( if ({1 if _legacy_nightmode else 0}) {{ document.documentElement.className = "night-mode"; }} - anki.cardInfo(document.getElementById('{random_id}'), {card_id}, {include_revlog}); + const {varName} = anki.cardInfo(document.getElementById('{random_id}')); + {varName}.then((c) => c.$set({{ cardId: {card_id}, includeRevlog: {str(include_revlog).lower()} }})); """ From b6104fa10b5276e4047cff6ae0a88deb12f7a83d Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sun, 17 Oct 2021 21:53:43 +0200 Subject: [PATCH 13/19] Use language that mypy understands --- qt/aqt/browser/card_info.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qt/aqt/browser/card_info.py b/qt/aqt/browser/card_info.py index 370ef8e4c..eeb6f47e5 100644 --- a/qt/aqt/browser/card_info.py +++ b/qt/aqt/browser/card_info.py @@ -41,7 +41,7 @@ class CardInfoDialog(QDialog): self.GEOMETRY_KEY = geometry_key or self.GEOMETRY_KEY if window_title: self.setWindowTitle(window_title) - self._setup_ui(card and card.id) + self._setup_ui(card.id if card else None) self.show() def _setup_ui(self, card_id: CardId | None) -> None: @@ -107,7 +107,7 @@ class CardInfoManager: def set_card(self, card: Card | None) -> None: self._card = card if self._dialog: - self._dialog.update_card(card and card.id) + self._dialog.update_card(card.id if card else None) def close(self) -> None: if self._dialog: From 2a93868922dd2adaee155cda24c0953e86ab4d71 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sun, 17 Oct 2021 22:06:44 +0200 Subject: [PATCH 14/19] Center placeholder --- ts/card-info/CardInfo.svelte | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/ts/card-info/CardInfo.svelte b/ts/card-info/CardInfo.svelte index a28f84a4f..72a9a81aa 100644 --- a/ts/card-info/CardInfo.svelte +++ b/ts/card-info/CardInfo.svelte @@ -24,21 +24,21 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html stats = s; } }); - }; + } -
-
- {#if stats} +{#if stats} +
+
{#if includeRevlog} {/if} - {:else} - {tr.cardStatsNoCard()} - {/if} +
-
+{:else} +
{tr.cardStatsNoCard()}
+{/if} From ca57cb964c67925d868d96dd8e5bbe8d313f53c1 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sun, 17 Oct 2021 22:14:44 +0200 Subject: [PATCH 15/19] Format --- pylib/anki/stats.py | 2 +- qt/aqt/browser/card_info.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pylib/anki/stats.py b/pylib/anki/stats.py index 61ff1726d..013f6088c 100644 --- a/pylib/anki/stats.py +++ b/pylib/anki/stats.py @@ -28,7 +28,7 @@ def _legacy_card_stats( ) -> str: "A quick hack to preserve compatibility with the old HTML string API." random_id = f"cardinfo-{base62(random.randint(0, 2 ** 64 - 1))}" - varName = random_id.replace('-', '') + varName = random_id.replace("-", "") return f"""
diff --git a/qt/aqt/browser/card_info.py b/qt/aqt/browser/card_info.py index eeb6f47e5..1aff13f10 100644 --- a/qt/aqt/browser/card_info.py +++ b/qt/aqt/browser/card_info.py @@ -64,7 +64,7 @@ class CardInfoDialog(QDialog): self.setLayout(layout) self.web.eval( - "const cardInfo = anki.cardInfo(document.getElementById('main'));\n" \ + "const cardInfo = anki.cardInfo(document.getElementById('main'));\n" "cardInfo.then((c) => c.$set({ includeRevlog: true }));" ) self.update_card(card_id) From dc4f5adc44e5435efabdb53453bdab8924885cde Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 18 Oct 2021 09:04:49 +0200 Subject: [PATCH 16/19] Default to `includeRevlog = true` --- qt/aqt/browser/card_info.py | 3 +-- ts/card-info/CardInfo.svelte | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/qt/aqt/browser/card_info.py b/qt/aqt/browser/card_info.py index 1aff13f10..08def5750 100644 --- a/qt/aqt/browser/card_info.py +++ b/qt/aqt/browser/card_info.py @@ -64,8 +64,7 @@ class CardInfoDialog(QDialog): self.setLayout(layout) self.web.eval( - "const cardInfo = anki.cardInfo(document.getElementById('main'));\n" - "cardInfo.then((c) => c.$set({ includeRevlog: true }));" + "const cardInfo = anki.cardInfo(document.getElementById('main'));" ) self.update_card(card_id) diff --git a/ts/card-info/CardInfo.svelte b/ts/card-info/CardInfo.svelte index 72a9a81aa..6dec99e15 100644 --- a/ts/card-info/CardInfo.svelte +++ b/ts/card-info/CardInfo.svelte @@ -10,7 +10,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import Revlog from "./Revlog.svelte"; export let cardId: number | undefined = undefined; - export let includeRevlog: boolean | undefined = undefined; + export let includeRevlog: boolean = true; let stats: Stats.CardStatsResponse | undefined; From 8eed005db6d2e3209ab9626c1c91ef9613857e38 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 18 Oct 2021 09:11:00 +0200 Subject: [PATCH 17/19] Use `null` for unset cardId --- qt/aqt/browser/card_info.py | 6 ++++-- ts/card-info/CardInfo.svelte | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/qt/aqt/browser/card_info.py b/qt/aqt/browser/card_info.py index 08def5750..61bdd2274 100644 --- a/qt/aqt/browser/card_info.py +++ b/qt/aqt/browser/card_info.py @@ -3,6 +3,7 @@ from __future__ import annotations +import json from typing import Callable import aqt @@ -69,8 +70,9 @@ class CardInfoDialog(QDialog): self.update_card(card_id) def update_card(self, card_id: CardId | None) -> None: - val = "undefined" if card_id is None else card_id - self.web.eval(f"cardInfo.then((c) => c.$set({{ cardId: {val} }}));") + self.web.eval( + f"cardInfo.then((c) => c.$set({{ cardId: {json.dumps(card_id)} }}));" + ) def reject(self) -> None: if self._on_close: diff --git a/ts/card-info/CardInfo.svelte b/ts/card-info/CardInfo.svelte index 6dec99e15..5ec718efb 100644 --- a/ts/card-info/CardInfo.svelte +++ b/ts/card-info/CardInfo.svelte @@ -9,12 +9,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import CardStats from "./CardStats.svelte"; import Revlog from "./Revlog.svelte"; - export let cardId: number | undefined = undefined; + export let cardId: number | null = null; export let includeRevlog: boolean = true; let stats: Stats.CardStatsResponse | undefined; - $: if (cardId === undefined) { + $: if (cardId === null) { stats = undefined; } else { const sentCardId = cardId; From 0b3b3a5f33b627317ac564d006f0102dd15fca5b Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 18 Oct 2021 09:12:10 +0200 Subject: [PATCH 18/19] sentCardId -> requestedCardId --- ts/card-info/CardInfo.svelte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ts/card-info/CardInfo.svelte b/ts/card-info/CardInfo.svelte index 5ec718efb..b63bee1db 100644 --- a/ts/card-info/CardInfo.svelte +++ b/ts/card-info/CardInfo.svelte @@ -17,10 +17,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html $: if (cardId === null) { stats = undefined; } else { - const sentCardId = cardId; - getCardStats(sentCardId).then((s) => { + const requestedCardId = cardId; + getCardStats(requestedCardId).then((s) => { /* Skip if another update has been triggered in the meantime. */ - if (sentCardId === cardId) { + if (requestedCardId === cardId) { stats = s; } }); From 9b1d53e359315236a280723a56181a8bbf7ea444 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 18 Oct 2021 09:29:33 +0200 Subject: [PATCH 19/19] Use `null` for missing stats --- ts/card-info/CardInfo.svelte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ts/card-info/CardInfo.svelte b/ts/card-info/CardInfo.svelte index b63bee1db..41d6a7ce9 100644 --- a/ts/card-info/CardInfo.svelte +++ b/ts/card-info/CardInfo.svelte @@ -12,10 +12,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html export let cardId: number | null = null; export let includeRevlog: boolean = true; - let stats: Stats.CardStatsResponse | undefined; + let stats: Stats.CardStatsResponse | null = null; $: if (cardId === null) { - stats = undefined; + stats = null; } else { const requestedCardId = cardId; getCardStats(requestedCardId).then((s) => { @@ -27,7 +27,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html } -{#if stats} +{#if stats !== null}