mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Use inheritance for reviewer
This commit is contained in:
parent
4bf38ec2af
commit
d81ec73205
2 changed files with 181 additions and 23 deletions
63
qt/aqt/data/web/js/reviewer-bottom.ts
Normal file
63
qt/aqt/data/web/js/reviewer-bottom.ts
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/* Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
||||||
|
|
||||||
|
/* eslint
|
||||||
|
@typescript-eslint/no-unused-vars: "off",
|
||||||
|
*/
|
||||||
|
|
||||||
|
let time: number; // set in python code
|
||||||
|
let timerStopped = false;
|
||||||
|
|
||||||
|
let maxTime = 0;
|
||||||
|
|
||||||
|
function updateTime(): void {
|
||||||
|
const timeNode = document.getElementById("time");
|
||||||
|
if (maxTime === 0) {
|
||||||
|
timeNode.textContent = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
time = Math.min(maxTime, time);
|
||||||
|
const m = Math.floor(time / 60);
|
||||||
|
const s = time % 60;
|
||||||
|
const sStr = String(s).padStart(2, "0");
|
||||||
|
const timeString = `${m}:${sStr}`;
|
||||||
|
|
||||||
|
if (maxTime === time) {
|
||||||
|
timeNode.innerHTML = `<font color=red>${timeString}</font>`;
|
||||||
|
} else {
|
||||||
|
timeNode.textContent = timeString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let intervalId: number | undefined;
|
||||||
|
|
||||||
|
function showQuestion(txt: string, maxTime_: number): void {
|
||||||
|
showAnswer(txt);
|
||||||
|
time = 0;
|
||||||
|
maxTime = maxTime_;
|
||||||
|
updateTime();
|
||||||
|
|
||||||
|
if (intervalId !== undefined) {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
intervalId = setInterval(function() {
|
||||||
|
if (!timerStopped) {
|
||||||
|
time += 1;
|
||||||
|
updateTime();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAnswer(txt: string, stopTimer = false): void {
|
||||||
|
document.getElementById("middle").innerHTML = txt;
|
||||||
|
timerStopped = stopTimer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectedAnswerButton(): string {
|
||||||
|
const node = document.activeElement as HTMLElement;
|
||||||
|
if (!node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return node.dataset.ease;
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ from collections.abc import Callable, Generator, Sequence
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import Any, Literal, Match, cast
|
from typing import Any, Literal, Match, Union, cast
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
import aqt.browser
|
import aqt.browser
|
||||||
|
@ -341,12 +341,26 @@ class Reviewer:
|
||||||
def _initWeb(self) -> None:
|
def _initWeb(self) -> None:
|
||||||
self._reps = 0
|
self._reps = 0
|
||||||
# main window
|
# main window
|
||||||
self.web.load_sveltekit_page("reviewer")
|
self.web.stdHtml(
|
||||||
|
self.revHtml(),
|
||||||
|
css=["css/reviewer.css"],
|
||||||
|
js=[
|
||||||
|
"js/mathjax.js",
|
||||||
|
"js/vendor/mathjax/tex-chtml-full.js",
|
||||||
|
"js/reviewer.js",
|
||||||
|
],
|
||||||
|
context=self,
|
||||||
|
)
|
||||||
# block default drag & drop behavior while allowing drop events to be received by JS handlers
|
# block default drag & drop behavior while allowing drop events to be received by JS handlers
|
||||||
self.web.allow_drops = True
|
self.web.allow_drops = True
|
||||||
self.web.eval("_blockDefaultDragDropBehavior();")
|
self.web.eval("_blockDefaultDragDropBehavior();")
|
||||||
# show answer / ease buttons
|
# show answer / ease buttons
|
||||||
self.bottom.web = self.web
|
self.bottom.web.stdHtml(
|
||||||
|
self._bottomHTML(),
|
||||||
|
css=["css/toolbar-bottom.css", "css/reviewer-bottom.css"],
|
||||||
|
js=["js/vendor/jquery.min.js", "js/reviewer-bottom.js"],
|
||||||
|
context=ReviewerBottomBar(self),
|
||||||
|
)
|
||||||
|
|
||||||
# Showing the question
|
# Showing the question
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
@ -667,8 +681,6 @@ class Reviewer:
|
||||||
self.mw.onEditCurrent()
|
self.mw.onEditCurrent()
|
||||||
elif url == "more":
|
elif url == "more":
|
||||||
self.showContextMenu()
|
self.showContextMenu()
|
||||||
elif url == "bottomReady":
|
|
||||||
self._remaining()
|
|
||||||
elif url.startswith("play:"):
|
elif url.startswith("play:"):
|
||||||
play_clicked_audio(url, self.card)
|
play_clicked_audio(url, self.card)
|
||||||
elif url.startswith("updateToolbar"):
|
elif url.startswith("updateToolbar"):
|
||||||
|
@ -825,13 +837,22 @@ timerStopped = false;
|
||||||
)
|
)
|
||||||
|
|
||||||
def _showAnswerButton(self) -> None:
|
def _showAnswerButton(self) -> None:
|
||||||
|
middle = """
|
||||||
|
<button title="{}" id="ansbut" onclick='pycmd("ans");'>{}<span class=stattxt>{}</span></button>""".format(
|
||||||
|
tr.actions_shortcut_key(val=tr.studying_space()),
|
||||||
|
tr.studying_show_answer(),
|
||||||
|
self._remaining(),
|
||||||
|
)
|
||||||
# wrap it in a table so it has the same top margin as the ease buttons
|
# wrap it in a table so it has the same top margin as the ease buttons
|
||||||
|
middle = (
|
||||||
|
"<table cellpadding=0><tr><td class=stat2 align=center>%s</td></tr></table>"
|
||||||
|
% middle
|
||||||
|
)
|
||||||
if self.card.should_show_timer():
|
if self.card.should_show_timer():
|
||||||
maxTime = self.card.time_limit() / 1000
|
maxTime = self.card.time_limit() / 1000
|
||||||
else:
|
else:
|
||||||
maxTime = 0
|
maxTime = 0
|
||||||
self._remaining()
|
self.bottom.web.eval("showQuestion(%s,%d);" % (json.dumps(middle), maxTime))
|
||||||
self.bottom.web.eval("_showQuestion(%s,%d);" % ("", maxTime))
|
|
||||||
|
|
||||||
def _showEaseButtons(self) -> None:
|
def _showEaseButtons(self) -> None:
|
||||||
if not self._states_mutated:
|
if not self._states_mutated:
|
||||||
|
@ -840,15 +861,23 @@ timerStopped = false;
|
||||||
middle = self._answerButtons()
|
middle = self._answerButtons()
|
||||||
conf = self.mw.col.decks.config_dict_for_deck_id(self.card.current_deck_id())
|
conf = self.mw.col.decks.config_dict_for_deck_id(self.card.current_deck_id())
|
||||||
self.bottom.web.eval(
|
self.bottom.web.eval(
|
||||||
f"_showAnswer({json.dumps(middle)}, {json.dumps(conf['stopTimerOnAnswer'])});"
|
f"showAnswer({json.dumps(middle)}, {json.dumps(conf['stopTimerOnAnswer'])});"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _remaining(self):
|
def _remaining(self) -> str:
|
||||||
if not self.mw.col.conf["dueCounts"]:
|
if not self.mw.col.conf["dueCounts"]:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
idx, counts = self._v3.counts()
|
counts: list[int | str]
|
||||||
self.bottom.web.eval(f"_updateRemaining({json.dumps(counts)},{idx})")
|
idx, counts_ = self._v3.counts()
|
||||||
|
counts = cast(list[Union[int, str]], counts_)
|
||||||
|
counts[idx] = f"<u>{counts[idx]}</u>"
|
||||||
|
|
||||||
|
return f"""
|
||||||
|
<span class=new-count>{counts[0]}</span> +
|
||||||
|
<span class=learn-count>{counts[1]}</span> +
|
||||||
|
<span class=review-count>{counts[2]}</span>
|
||||||
|
"""
|
||||||
|
|
||||||
def _defaultEase(self) -> Literal[2, 3]:
|
def _defaultEase(self) -> Literal[2, 3]:
|
||||||
return 3
|
return 3
|
||||||
|
@ -878,32 +907,39 @@ timerStopped = false;
|
||||||
)
|
)
|
||||||
return buttons_tuple
|
return buttons_tuple
|
||||||
|
|
||||||
def _answerButtons(self):
|
def _answerButtons(self) -> str:
|
||||||
default = self._defaultEase()
|
default = self._defaultEase()
|
||||||
|
|
||||||
assert isinstance(self.mw.col.sched, V3Scheduler)
|
assert isinstance(self.mw.col.sched, V3Scheduler)
|
||||||
labels = self.mw.col.sched.describe_next_states(self._v3.states)
|
labels = self.mw.col.sched.describe_next_states(self._v3.states)
|
||||||
|
|
||||||
def but(i: int, label: str):
|
def but(i: int, label: str) -> str:
|
||||||
if i == default:
|
if i == default:
|
||||||
id = "defease"
|
extra = """id="defease" """
|
||||||
else:
|
else:
|
||||||
id = ""
|
extra = ""
|
||||||
due = self._buttonTime(i, v3_labels=labels)
|
due = self._buttonTime(i, v3_labels=labels)
|
||||||
key = (
|
key = (
|
||||||
tr.actions_shortcut_key(val=aqt.mw.pm.get_answer_key(i))
|
tr.actions_shortcut_key(val=aqt.mw.pm.get_answer_key(i))
|
||||||
if aqt.mw.pm.get_answer_key(i)
|
if aqt.mw.pm.get_answer_key(i)
|
||||||
else ""
|
else ""
|
||||||
)
|
)
|
||||||
return {
|
return """
|
||||||
"id": id,
|
<td align=center><button %s title="%s" data-ease="%s" onclick='pycmd("ease%d");'>\
|
||||||
"key": key,
|
%s%s</button></td>""" % (
|
||||||
"i": i,
|
extra,
|
||||||
"label": label,
|
key,
|
||||||
"due": due,
|
i,
|
||||||
}
|
i,
|
||||||
|
label,
|
||||||
|
due,
|
||||||
|
)
|
||||||
|
|
||||||
return [but(ease, label) for ease, label in self._answerButtonList()]
|
buf = "<center><table cellpadding=0 cellspacing=0><tr>"
|
||||||
|
for ease, label in self._answerButtonList():
|
||||||
|
buf += but(ease, label)
|
||||||
|
buf += "</tr></table>"
|
||||||
|
return buf
|
||||||
|
|
||||||
def _buttonTime(self, i: int, v3_labels: Sequence[str]) -> str:
|
def _buttonTime(self, i: int, v3_labels: Sequence[str]) -> str:
|
||||||
if self.mw.col.conf["estTimes"]:
|
if self.mw.col.conf["estTimes"]:
|
||||||
|
@ -1191,6 +1227,65 @@ timerStopped = false;
|
||||||
onMark = toggle_mark_on_current_note
|
onMark = toggle_mark_on_current_note
|
||||||
setFlag = set_flag_on_current_card
|
setFlag = set_flag_on_current_card
|
||||||
|
|
||||||
|
class SvelteReviewer(Reviewer):
|
||||||
|
def _answerButtons(self) -> str:
|
||||||
|
default = self._defaultEase()
|
||||||
|
|
||||||
|
assert isinstance(self.mw.col.sched, V3Scheduler)
|
||||||
|
labels = self.mw.col.sched.describe_next_states(self._v3.states)
|
||||||
|
|
||||||
|
def but(i: int, label: str):
|
||||||
|
if i == default:
|
||||||
|
id = "defease"
|
||||||
|
else:
|
||||||
|
id = ""
|
||||||
|
due = self._buttonTime(i, v3_labels=labels)
|
||||||
|
key = (
|
||||||
|
tr.actions_shortcut_key(val=aqt.mw.pm.get_answer_key(i))
|
||||||
|
if aqt.mw.pm.get_answer_key(i)
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"id": id,
|
||||||
|
"key": key,
|
||||||
|
"i": i,
|
||||||
|
"label": label,
|
||||||
|
"due": due,
|
||||||
|
}
|
||||||
|
|
||||||
|
return [but(ease, label) for ease, label in self._answerButtonList()]
|
||||||
|
|
||||||
|
def _remaining(self) -> str:
|
||||||
|
if not self.mw.col.conf["dueCounts"]:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
idx, counts = self._v3.counts()
|
||||||
|
self.bottom.web.eval(f"_updateRemaining({json.dumps(counts)},{idx})")
|
||||||
|
|
||||||
|
def _showAnswerButton(self) -> None:
|
||||||
|
if self.card.should_show_timer():
|
||||||
|
maxTime = self.card.time_limit() / 1000
|
||||||
|
else:
|
||||||
|
maxTime = 0
|
||||||
|
self._remaining()
|
||||||
|
self.bottom.web.eval("_showQuestion(%s,%d);" % ("", maxTime))
|
||||||
|
|
||||||
|
def _linkHandler(self, url: str) -> None:
|
||||||
|
if url == "bottomReady":
|
||||||
|
self._remaining()
|
||||||
|
return
|
||||||
|
super()._linkHandler(url)
|
||||||
|
|
||||||
|
def _initWeb(self) -> None:
|
||||||
|
self._reps = 0
|
||||||
|
# main window
|
||||||
|
self.web.load_sveltekit_page("reviewer")
|
||||||
|
# block default drag & drop behavior while allowing drop events to be received by JS handlers
|
||||||
|
self.web.allow_drops = True
|
||||||
|
self.web.eval("_blockDefaultDragDropBehavior();")
|
||||||
|
# ensure bottom web functions trigger
|
||||||
|
self.bottom.web = self.web
|
||||||
|
|
||||||
|
|
||||||
# if the last element is a comment, then the RUN_STATE_MUTATION code
|
# if the last element is a comment, then the RUN_STATE_MUTATION code
|
||||||
# breaks due to the comment wrongly commenting out python code.
|
# breaks due to the comment wrongly commenting out python code.
|
||||||
|
|
Loading…
Reference in a new issue