mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Add auto-advance options to deck preset (#2765)
* Move stop-timer-on-answer strings to correct section * Add auto-advance options to deck preset * Implement answer actions * Fix error when last card is answered before timeout * Fix deserialization of answerAction * Add answerAction to reserved key list * Fix inverted boolean * Add option to wait for audio to finish * Add auto-advance toggle * Add shortcut * Disable auto-advance when main window state changes * Start auto-advance timer after option is toggled * Disable auto-advance when main window loses focus * Use existing translations * Add Answer Hard and Show Reminder
This commit is contained in:
parent
46890fbbaa
commit
ae7b14bf40
12 changed files with 311 additions and 7 deletions
|
@ -222,6 +222,17 @@ deck-config-maximum-answer-secs-tooltip =
|
||||||
deck-config-show-answer-timer-tooltip =
|
deck-config-show-answer-timer-tooltip =
|
||||||
In the review screen, show a timer that counts the number of seconds you're
|
In the review screen, show a timer that counts the number of seconds you're
|
||||||
taking to review each card.
|
taking to review each card.
|
||||||
|
deck-config-stop-timer-on-answer = Stop timer on answer
|
||||||
|
deck-config-stop-timer-on-answer-tooltip =
|
||||||
|
Whether to stop the timer when the answer is revealed.
|
||||||
|
This doesn't affect statistics.
|
||||||
|
deck-config-seconds-to-show-question = Seconds to show question
|
||||||
|
deck-config-seconds-to-show-question-tooltip = The number of seconds to wait before automatically advancing to the next question. Set to 0 to disable.
|
||||||
|
deck-config-seconds-to-show-answer = Seconds to show answer
|
||||||
|
deck-config-seconds-to-show-answer-tooltip = The number of seconds to wait before automatically revealing the answer. Set to 0 to disable.
|
||||||
|
deck-config-answer-action = Answer action
|
||||||
|
deck-config-answer-action-tooltip = The action to perform on the current card before automatically advancing to the next one.
|
||||||
|
deck-config-wait-for-audio-tooltip = Wait for audio to finish before automatically revealing answer or next question
|
||||||
|
|
||||||
## Audio section
|
## Audio section
|
||||||
|
|
||||||
|
@ -234,11 +245,6 @@ deck-config-skip-question-when-replaying = Skip question when replaying answer
|
||||||
deck-config-always-include-question-audio-tooltip =
|
deck-config-always-include-question-audio-tooltip =
|
||||||
Whether the question audio should be included when the Replay action is
|
Whether the question audio should be included when the Replay action is
|
||||||
used while looking at the answer side of a card.
|
used while looking at the answer side of a card.
|
||||||
deck-config-stop-timer-on-answer = Stop timer on answer
|
|
||||||
deck-config-stop-timer-on-answer-tooltip =
|
|
||||||
Whether to stop the timer when the answer is revealed.
|
|
||||||
This doesn't affect statistics.
|
|
||||||
|
|
||||||
## Advanced section
|
## Advanced section
|
||||||
|
|
||||||
deck-config-advanced-title = Advanced
|
deck-config-advanced-title = Advanced
|
||||||
|
|
|
@ -56,3 +56,4 @@ studying-minute =
|
||||||
[one] { $count } minute.
|
[one] { $count } minute.
|
||||||
*[other] { $count } minutes.
|
*[other] { $count } minutes.
|
||||||
}
|
}
|
||||||
|
studying-answer-time-elapsed = Answer time elapsed
|
||||||
|
|
|
@ -91,6 +91,13 @@ message DeckConfig {
|
||||||
LEECH_ACTION_SUSPEND = 0;
|
LEECH_ACTION_SUSPEND = 0;
|
||||||
LEECH_ACTION_TAG_ONLY = 1;
|
LEECH_ACTION_TAG_ONLY = 1;
|
||||||
}
|
}
|
||||||
|
enum AnswerAction {
|
||||||
|
ANSWER_ACTION_BURY_CARD = 0;
|
||||||
|
ANSWER_ACTION_ANSWER_AGAIN = 1;
|
||||||
|
ANSWER_ACTION_ANSWER_GOOD = 2;
|
||||||
|
ANSWER_ACTION_ANSWER_HARD = 3;
|
||||||
|
ANSWER_ACTION_SHOW_REMINDER = 4;
|
||||||
|
}
|
||||||
|
|
||||||
repeated float learn_steps = 1;
|
repeated float learn_steps = 1;
|
||||||
repeated float relearn_steps = 2;
|
repeated float relearn_steps = 2;
|
||||||
|
@ -133,6 +140,10 @@ message DeckConfig {
|
||||||
uint32 cap_answer_time_to_secs = 24;
|
uint32 cap_answer_time_to_secs = 24;
|
||||||
bool show_timer = 25;
|
bool show_timer = 25;
|
||||||
bool stop_timer_on_answer = 38;
|
bool stop_timer_on_answer = 38;
|
||||||
|
float seconds_to_show_question = 41;
|
||||||
|
float seconds_to_show_answer = 42;
|
||||||
|
AnswerAction answer_action = 43;
|
||||||
|
bool wait_for_audio = 44;
|
||||||
bool skip_question_when_replaying_answer = 26;
|
bool skip_question_when_replaying_answer = 26;
|
||||||
|
|
||||||
bool bury_new = 27;
|
bool bury_new = 27;
|
||||||
|
|
|
@ -161,6 +161,11 @@ class MainWebView(AnkiWebView):
|
||||||
self.mw.bottomWeb.hide_timer.start()
|
self.mw.bottomWeb.hide_timer.start()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
if evt.type() == QEvent.Type.FocusOut:
|
||||||
|
self.mw._auto_advance_was_enabled = self.mw.reviewer.auto_advance_enabled
|
||||||
|
self.mw.reviewer.auto_advance_enabled = False
|
||||||
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@ -189,6 +194,7 @@ class AnkiQt(QMainWindow):
|
||||||
self.app = app
|
self.app = app
|
||||||
self.pm = profileManager
|
self.pm = profileManager
|
||||||
self.fullscreen = False
|
self.fullscreen = False
|
||||||
|
self._auto_advance_was_enabled = False
|
||||||
# init rest of app
|
# init rest of app
|
||||||
self.safeMode = (
|
self.safeMode = (
|
||||||
bool(self.app.queryKeyboardModifiers() & Qt.KeyboardModifier.ShiftModifier)
|
bool(self.app.queryKeyboardModifiers() & Qt.KeyboardModifier.ShiftModifier)
|
||||||
|
@ -822,6 +828,8 @@ class AnkiQt(QMainWindow):
|
||||||
if new_focus and new_focus.window() == self:
|
if new_focus and new_focus.window() == self:
|
||||||
if self.state == "review":
|
if self.state == "review":
|
||||||
self.reviewer.refresh_if_needed()
|
self.reviewer.refresh_if_needed()
|
||||||
|
self.reviewer.auto_advance_enabled = self._auto_advance_was_enabled
|
||||||
|
self.reviewer.auto_advance_if_enabled()
|
||||||
elif self.state == "overview":
|
elif self.state == "overview":
|
||||||
self.overview.refresh_if_needed()
|
self.overview.refresh_if_needed()
|
||||||
elif self.state == "deckBrowser":
|
elif self.state == "deckBrowser":
|
||||||
|
@ -1021,6 +1029,7 @@ title="{}" {}>{}</button>""".format(
|
||||||
from aqt.reviewer import Reviewer
|
from aqt.reviewer import Reviewer
|
||||||
|
|
||||||
self.reviewer = Reviewer(self)
|
self.reviewer = Reviewer(self)
|
||||||
|
self._auto_advance_was_enabled = self.reviewer.auto_advance_enabled
|
||||||
|
|
||||||
# Syncing
|
# Syncing
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
|
@ -129,6 +129,14 @@ class V3CardInfo:
|
||||||
return CardAnswer.EASY
|
return CardAnswer.EASY
|
||||||
|
|
||||||
|
|
||||||
|
class AnswerAction(Enum):
|
||||||
|
BURY_CARD = 0
|
||||||
|
ANSWER_AGAIN = 1
|
||||||
|
ANSWER_GOOD = 2
|
||||||
|
ANSWER_HARD = 3
|
||||||
|
SHOW_REMINDER = 4
|
||||||
|
|
||||||
|
|
||||||
class Reviewer:
|
class Reviewer:
|
||||||
def __init__(self, mw: AnkiQt) -> None:
|
def __init__(self, mw: AnkiQt) -> None:
|
||||||
self.mw = mw
|
self.mw = mw
|
||||||
|
@ -147,6 +155,10 @@ class Reviewer:
|
||||||
self._previous_card_info = PreviousReviewerCardInfo(self.mw)
|
self._previous_card_info = PreviousReviewerCardInfo(self.mw)
|
||||||
self._states_mutated = True
|
self._states_mutated = True
|
||||||
self._reps: int = None
|
self._reps: int = None
|
||||||
|
self._show_question_timer: QTimer | None = None
|
||||||
|
self._show_answer_timer: QTimer | None = None
|
||||||
|
self.auto_advance_enabled = False
|
||||||
|
gui_hooks.av_player_did_end_playing.append(self._on_av_player_did_end_playing)
|
||||||
|
|
||||||
def show(self) -> None:
|
def show(self) -> None:
|
||||||
if self.mw.col.sched_ver() == 1 or not self.mw.col.v3_scheduler():
|
if self.mw.col.sched_ver() == 1 or not self.mw.col.v3_scheduler():
|
||||||
|
@ -175,6 +187,7 @@ class Reviewer:
|
||||||
def cleanup(self) -> None:
|
def cleanup(self) -> None:
|
||||||
gui_hooks.reviewer_will_end()
|
gui_hooks.reviewer_will_end()
|
||||||
self.card = None
|
self.card = None
|
||||||
|
self.auto_advance_enabled = False
|
||||||
|
|
||||||
def refresh_if_needed(self) -> None:
|
def refresh_if_needed(self) -> None:
|
||||||
if self._refresh_needed is RefreshNeeded.QUEUES:
|
if self._refresh_needed is RefreshNeeded.QUEUES:
|
||||||
|
@ -282,6 +295,21 @@ class Reviewer:
|
||||||
replay_audio(self.card, False)
|
replay_audio(self.card, False)
|
||||||
gui_hooks.audio_will_replay(self.web, self.card, self.state == "question")
|
gui_hooks.audio_will_replay(self.web, self.card, self.state == "question")
|
||||||
|
|
||||||
|
def _on_av_player_did_end_playing(self, *args) -> None:
|
||||||
|
def task() -> None:
|
||||||
|
if av_player.queue_is_empty():
|
||||||
|
if self._show_question_timer and not sip.isdeleted(
|
||||||
|
self._show_question_timer
|
||||||
|
):
|
||||||
|
self._on_show_question_timeout()
|
||||||
|
elif self._show_answer_timer and not sip.isdeleted(
|
||||||
|
self._show_answer_timer
|
||||||
|
):
|
||||||
|
self._on_show_answer_timeout()
|
||||||
|
|
||||||
|
# Allow time for audio queue to update
|
||||||
|
self.mw.taskman.run_on_main(lambda: self.mw.progress.single_shot(100, task))
|
||||||
|
|
||||||
# Initializing the webview
|
# Initializing the webview
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
@ -363,6 +391,35 @@ class Reviewer:
|
||||||
self.mw.web.setFocus()
|
self.mw.web.setFocus()
|
||||||
# user hook
|
# user hook
|
||||||
gui_hooks.reviewer_did_show_question(c)
|
gui_hooks.reviewer_did_show_question(c)
|
||||||
|
self._auto_advance_to_answer_if_enabled()
|
||||||
|
|
||||||
|
def _auto_advance_to_answer_if_enabled(self) -> None:
|
||||||
|
if self.auto_advance_enabled:
|
||||||
|
conf = self.mw.col.decks.config_dict_for_deck_id(
|
||||||
|
self.card.current_deck_id()
|
||||||
|
)
|
||||||
|
timer = None
|
||||||
|
if conf["secondsToShowAnswer"]:
|
||||||
|
timer = self._show_answer_timer = self.mw.progress.timer(
|
||||||
|
int(conf["secondsToShowAnswer"] * 1000),
|
||||||
|
lambda: self._on_show_answer_timeout(timer),
|
||||||
|
repeat=False,
|
||||||
|
parent=self.mw,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _on_show_answer_timeout(self, timer: QTimer | None = None) -> None:
|
||||||
|
if self.card is None:
|
||||||
|
return
|
||||||
|
conf = self.mw.col.decks.config_dict_for_deck_id(self.card.current_deck_id())
|
||||||
|
if (conf["waitForAudio"] and av_player.current_player) or (
|
||||||
|
timer and self._show_answer_timer != timer
|
||||||
|
):
|
||||||
|
return
|
||||||
|
if self._show_answer_timer is not None:
|
||||||
|
self._show_answer_timer.deleteLater()
|
||||||
|
if not self.auto_advance_enabled:
|
||||||
|
return
|
||||||
|
self._showAnswer()
|
||||||
|
|
||||||
def autoplay(self, card: Card) -> bool:
|
def autoplay(self, card: Card) -> bool:
|
||||||
print("use card.autoplay() instead of reviewer.autoplay(card)")
|
print("use card.autoplay() instead of reviewer.autoplay(card)")
|
||||||
|
@ -404,6 +461,48 @@ class Reviewer:
|
||||||
self.mw.web.setFocus()
|
self.mw.web.setFocus()
|
||||||
# user hook
|
# user hook
|
||||||
gui_hooks.reviewer_did_show_answer(c)
|
gui_hooks.reviewer_did_show_answer(c)
|
||||||
|
self._auto_advance_to_question_if_enabled()
|
||||||
|
|
||||||
|
def _auto_advance_to_question_if_enabled(self) -> None:
|
||||||
|
if self.auto_advance_enabled:
|
||||||
|
conf = self.mw.col.decks.config_dict_for_deck_id(
|
||||||
|
self.card.current_deck_id()
|
||||||
|
)
|
||||||
|
timer = None
|
||||||
|
if conf["secondsToShowQuestion"]:
|
||||||
|
timer = self._show_question_timer = self.mw.progress.timer(
|
||||||
|
int(conf["secondsToShowQuestion"] * 1000),
|
||||||
|
lambda: self._on_show_question_timeout(timer),
|
||||||
|
repeat=False,
|
||||||
|
parent=self.mw,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _on_show_question_timeout(self, timer: QTimer | None = None) -> None:
|
||||||
|
if self.card is None:
|
||||||
|
return
|
||||||
|
conf = self.mw.col.decks.config_dict_for_deck_id(self.card.current_deck_id())
|
||||||
|
if (conf["waitForAudio"] and av_player.current_player) or (
|
||||||
|
timer and self._show_question_timer != timer
|
||||||
|
):
|
||||||
|
return
|
||||||
|
if self._show_question_timer is not None:
|
||||||
|
self._show_question_timer.deleteLater()
|
||||||
|
if not self.auto_advance_enabled:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
answer_action = list(AnswerAction)[conf["answerAction"]]
|
||||||
|
except IndexError:
|
||||||
|
answer_action = AnswerAction.ANSWER_GOOD
|
||||||
|
if answer_action == AnswerAction.BURY_CARD:
|
||||||
|
self.bury_current_card()
|
||||||
|
elif answer_action == AnswerAction.ANSWER_AGAIN:
|
||||||
|
self._answerCard(1)
|
||||||
|
elif answer_action == AnswerAction.ANSWER_HARD:
|
||||||
|
self._answerCard(2)
|
||||||
|
elif answer_action == AnswerAction.SHOW_REMINDER:
|
||||||
|
tooltip(tr.studying_answer_time_elapsed())
|
||||||
|
else:
|
||||||
|
self._answerCard(3)
|
||||||
|
|
||||||
# Answering a card
|
# Answering a card
|
||||||
############################################################
|
############################################################
|
||||||
|
@ -507,6 +606,7 @@ class Reviewer:
|
||||||
("5", self.on_pause_audio),
|
("5", self.on_pause_audio),
|
||||||
("6", self.on_seek_backward),
|
("6", self.on_seek_backward),
|
||||||
("7", self.on_seek_forward),
|
("7", self.on_seek_forward),
|
||||||
|
("Shift+A", self.toggle_auto_advance),
|
||||||
*self.korean_shortcuts(),
|
*self.korean_shortcuts(),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -883,6 +983,12 @@ timerStopped = false;
|
||||||
[tr.studying_audio_and5s(), "7", self.on_seek_forward],
|
[tr.studying_audio_and5s(), "7", self.on_seek_forward],
|
||||||
[tr.studying_record_own_voice(), "Shift+V", self.onRecordVoice],
|
[tr.studying_record_own_voice(), "Shift+V", self.onRecordVoice],
|
||||||
[tr.studying_replay_own_voice(), "V", self.onReplayRecorded],
|
[tr.studying_replay_own_voice(), "V", self.onReplayRecorded],
|
||||||
|
[
|
||||||
|
tr.actions_auto_advance(),
|
||||||
|
"Shift+A",
|
||||||
|
self.toggle_auto_advance,
|
||||||
|
dict(checked=self.auto_advance_enabled),
|
||||||
|
],
|
||||||
]
|
]
|
||||||
return opts
|
return opts
|
||||||
|
|
||||||
|
@ -1039,6 +1145,16 @@ timerStopped = false;
|
||||||
return
|
return
|
||||||
av_player.play_file(self._recordedAudio)
|
av_player.play_file(self._recordedAudio)
|
||||||
|
|
||||||
|
def toggle_auto_advance(self) -> None:
|
||||||
|
self.auto_advance_enabled = not self.auto_advance_enabled
|
||||||
|
self.auto_advance_if_enabled()
|
||||||
|
|
||||||
|
def auto_advance_if_enabled(self) -> None:
|
||||||
|
if self.state == "question":
|
||||||
|
self._auto_advance_to_answer_if_enabled()
|
||||||
|
elif self.state == "answer":
|
||||||
|
self._auto_advance_to_question_if_enabled()
|
||||||
|
|
||||||
# legacy
|
# legacy
|
||||||
|
|
||||||
onBuryCard = bury_current_card
|
onBuryCard = bury_current_card
|
||||||
|
|
|
@ -155,7 +155,7 @@ class AVPlayer:
|
||||||
self._play_next_if_idle()
|
self._play_next_if_idle()
|
||||||
|
|
||||||
def queue_is_empty(self) -> bool:
|
def queue_is_empty(self) -> bool:
|
||||||
return bool(self._enqueued)
|
return not bool(self._enqueued)
|
||||||
|
|
||||||
def stop_and_clear_queue(self) -> None:
|
def stop_and_clear_queue(self) -> None:
|
||||||
self._enqueued = []
|
self._enqueued = []
|
||||||
|
|
|
@ -6,6 +6,7 @@ mod service;
|
||||||
pub(crate) mod undo;
|
pub(crate) mod undo;
|
||||||
mod update;
|
mod update;
|
||||||
|
|
||||||
|
pub use anki_proto::deck_config::deck_config::config::AnswerAction;
|
||||||
pub use anki_proto::deck_config::deck_config::config::LeechAction;
|
pub use anki_proto::deck_config::deck_config::config::LeechAction;
|
||||||
pub use anki_proto::deck_config::deck_config::config::NewCardGatherPriority;
|
pub use anki_proto::deck_config::deck_config::config::NewCardGatherPriority;
|
||||||
pub use anki_proto::deck_config::deck_config::config::NewCardInsertOrder;
|
pub use anki_proto::deck_config::deck_config::config::NewCardInsertOrder;
|
||||||
|
@ -63,6 +64,10 @@ const DEFAULT_DECK_CONFIG_INNER: DeckConfigInner = DeckConfigInner {
|
||||||
cap_answer_time_to_secs: 60,
|
cap_answer_time_to_secs: 60,
|
||||||
show_timer: false,
|
show_timer: false,
|
||||||
stop_timer_on_answer: false,
|
stop_timer_on_answer: false,
|
||||||
|
seconds_to_show_question: 0.0,
|
||||||
|
seconds_to_show_answer: 0.0,
|
||||||
|
answer_action: AnswerAction::BuryCard as i32,
|
||||||
|
wait_for_audio: true,
|
||||||
skip_question_when_replaying_answer: false,
|
skip_question_when_replaying_answer: false,
|
||||||
bury_new: false,
|
bury_new: false,
|
||||||
bury_reviews: false,
|
bury_reviews: false,
|
||||||
|
|
|
@ -24,6 +24,10 @@ use crate::serde::default_on_invalid;
|
||||||
use crate::timestamp::TimestampSecs;
|
use crate::timestamp::TimestampSecs;
|
||||||
use crate::types::Usn;
|
use crate::types::Usn;
|
||||||
|
|
||||||
|
fn wait_for_audio_default() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct DeckConfSchema11 {
|
pub struct DeckConfSchema11 {
|
||||||
|
@ -72,6 +76,14 @@ pub struct DeckConfSchema11 {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
stop_timer_on_answer: bool,
|
stop_timer_on_answer: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
seconds_to_show_question: f32,
|
||||||
|
#[serde(default)]
|
||||||
|
seconds_to_show_answer: f32,
|
||||||
|
#[serde(default)]
|
||||||
|
answer_action: AnswerAction,
|
||||||
|
#[serde(default = "wait_for_audio_default")]
|
||||||
|
wait_for_audio: bool,
|
||||||
|
#[serde(default)]
|
||||||
reschedule_fsrs_cards: bool,
|
reschedule_fsrs_cards: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
sm2_retention: f32,
|
sm2_retention: f32,
|
||||||
|
@ -80,6 +92,18 @@ pub struct DeckConfSchema11 {
|
||||||
other: HashMap<String, Value>,
|
other: HashMap<String, Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Eq, Clone)]
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub enum AnswerAction {
|
||||||
|
#[default]
|
||||||
|
BuryCard = 0,
|
||||||
|
AnswerAgain = 1,
|
||||||
|
AnswerGood = 2,
|
||||||
|
AnswerHard = 3,
|
||||||
|
ShowReminder = 4,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct NewConfSchema11 {
|
pub struct NewConfSchema11 {
|
||||||
|
@ -249,6 +273,10 @@ impl Default for DeckConfSchema11 {
|
||||||
autoplay: true,
|
autoplay: true,
|
||||||
timer: 0,
|
timer: 0,
|
||||||
stop_timer_on_answer: false,
|
stop_timer_on_answer: false,
|
||||||
|
seconds_to_show_question: 0.0,
|
||||||
|
seconds_to_show_answer: 0.0,
|
||||||
|
answer_action: AnswerAction::BuryCard,
|
||||||
|
wait_for_audio: true,
|
||||||
replayq: true,
|
replayq: true,
|
||||||
dynamic: false,
|
dynamic: false,
|
||||||
new: Default::default(),
|
new: Default::default(),
|
||||||
|
@ -331,6 +359,10 @@ impl From<DeckConfSchema11> for DeckConfig {
|
||||||
cap_answer_time_to_secs: c.max_taken.max(0) as u32,
|
cap_answer_time_to_secs: c.max_taken.max(0) as u32,
|
||||||
show_timer: c.timer != 0,
|
show_timer: c.timer != 0,
|
||||||
stop_timer_on_answer: c.stop_timer_on_answer,
|
stop_timer_on_answer: c.stop_timer_on_answer,
|
||||||
|
seconds_to_show_question: c.seconds_to_show_question,
|
||||||
|
seconds_to_show_answer: c.seconds_to_show_answer,
|
||||||
|
answer_action: c.answer_action as i32,
|
||||||
|
wait_for_audio: c.wait_for_audio,
|
||||||
skip_question_when_replaying_answer: !c.replayq,
|
skip_question_when_replaying_answer: !c.replayq,
|
||||||
bury_new: c.new.bury,
|
bury_new: c.new.bury,
|
||||||
bury_reviews: c.rev.bury,
|
bury_reviews: c.rev.bury,
|
||||||
|
@ -385,6 +417,16 @@ impl From<DeckConfig> for DeckConfSchema11 {
|
||||||
autoplay: !i.disable_autoplay,
|
autoplay: !i.disable_autoplay,
|
||||||
timer: i.show_timer.into(),
|
timer: i.show_timer.into(),
|
||||||
stop_timer_on_answer: i.stop_timer_on_answer,
|
stop_timer_on_answer: i.stop_timer_on_answer,
|
||||||
|
seconds_to_show_question: i.seconds_to_show_question,
|
||||||
|
seconds_to_show_answer: i.seconds_to_show_answer,
|
||||||
|
answer_action: match i.answer_action {
|
||||||
|
0 => AnswerAction::BuryCard,
|
||||||
|
1 => AnswerAction::AnswerAgain,
|
||||||
|
3 => AnswerAction::AnswerHard,
|
||||||
|
4 => AnswerAction::ShowReminder,
|
||||||
|
_ => AnswerAction::AnswerGood,
|
||||||
|
},
|
||||||
|
wait_for_audio: i.wait_for_audio,
|
||||||
replayq: !i.skip_question_when_replaying_answer,
|
replayq: !i.skip_question_when_replaying_answer,
|
||||||
dynamic: false,
|
dynamic: false,
|
||||||
new: NewConfSchema11 {
|
new: NewConfSchema11 {
|
||||||
|
@ -459,6 +501,10 @@ static RESERVED_DECKCONF_KEYS: Set<&'static str> = phf_set! {
|
||||||
"fsrsWeights",
|
"fsrsWeights",
|
||||||
"desiredRetention",
|
"desiredRetention",
|
||||||
"stopTimerOnAnswer",
|
"stopTimerOnAnswer",
|
||||||
|
"secondsToShowQuestion",
|
||||||
|
"secondsToShowAnswer",
|
||||||
|
"answerAction",
|
||||||
|
"waitForAudio",
|
||||||
"rescheduleFsrsCards",
|
"rescheduleFsrsCards",
|
||||||
"sm2Retention",
|
"sm2Retention",
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
export let defaultValue: number;
|
export let defaultValue: number;
|
||||||
export let min = 0;
|
export let min = 0;
|
||||||
export let max = 9999;
|
export let max = 9999;
|
||||||
|
export let step = 0.01;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Row --cols={13}>
|
<Row --cols={13}>
|
||||||
|
@ -21,7 +22,7 @@
|
||||||
</Col>
|
</Col>
|
||||||
<Col --col-size={6} breakpoint="xs">
|
<Col --col-size={6} breakpoint="xs">
|
||||||
<ConfigInput>
|
<ConfigInput>
|
||||||
<SpinBox bind:value {min} {max} step={0.01} />
|
<SpinBox bind:value {min} {max} {step} />
|
||||||
<RevertButton slot="revert" bind:value {defaultValue} />
|
<RevertButton slot="revert" bind:value {defaultValue} />
|
||||||
</ConfigInput>
|
</ConfigInput>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
|
@ -9,13 +9,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import type Modal from "bootstrap/js/dist/modal";
|
import type Modal from "bootstrap/js/dist/modal";
|
||||||
|
|
||||||
import DynamicallySlottable from "../components/DynamicallySlottable.svelte";
|
import DynamicallySlottable from "../components/DynamicallySlottable.svelte";
|
||||||
|
import EnumSelectorRow from "../components/EnumSelectorRow.svelte";
|
||||||
import HelpModal from "../components/HelpModal.svelte";
|
import HelpModal from "../components/HelpModal.svelte";
|
||||||
import Item from "../components/Item.svelte";
|
import Item from "../components/Item.svelte";
|
||||||
import SettingTitle from "../components/SettingTitle.svelte";
|
import SettingTitle from "../components/SettingTitle.svelte";
|
||||||
import SwitchRow from "../components/SwitchRow.svelte";
|
import SwitchRow from "../components/SwitchRow.svelte";
|
||||||
import TitledContainer from "../components/TitledContainer.svelte";
|
import TitledContainer from "../components/TitledContainer.svelte";
|
||||||
import type { HelpItem } from "../components/types";
|
import type { HelpItem } from "../components/types";
|
||||||
|
import { answerChoices } from "./choices";
|
||||||
import type { DeckOptionsState } from "./lib";
|
import type { DeckOptionsState } from "./lib";
|
||||||
|
import SpinBoxFloatRow from "./SpinBoxFloatRow.svelte";
|
||||||
import SpinBoxRow from "./SpinBoxRow.svelte";
|
import SpinBoxRow from "./SpinBoxRow.svelte";
|
||||||
import Warning from "./Warning.svelte";
|
import Warning from "./Warning.svelte";
|
||||||
|
|
||||||
|
@ -43,6 +46,22 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
title: tr.deckConfigStopTimerOnAnswer(),
|
title: tr.deckConfigStopTimerOnAnswer(),
|
||||||
help: tr.deckConfigStopTimerOnAnswerTooltip(),
|
help: tr.deckConfigStopTimerOnAnswerTooltip(),
|
||||||
},
|
},
|
||||||
|
secondsToShowQuestion: {
|
||||||
|
title: tr.deckConfigSecondsToShowQuestion(),
|
||||||
|
help: tr.deckConfigSecondsToShowQuestionTooltip(),
|
||||||
|
},
|
||||||
|
secondsToShowAnswer: {
|
||||||
|
title: tr.deckConfigSecondsToShowAnswer(),
|
||||||
|
help: tr.deckConfigSecondsToShowAnswerTooltip(),
|
||||||
|
},
|
||||||
|
waitForAudio: {
|
||||||
|
title: tr.deckConfigWaitForAudio(),
|
||||||
|
help: tr.deckConfigWaitForAudioTooltip(),
|
||||||
|
},
|
||||||
|
answerAction: {
|
||||||
|
title: tr.deckConfigAnswerAction(),
|
||||||
|
help: tr.deckConfigAnswerActionTooltip(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const helpSections = Object.values(settings) as HelpItem[];
|
const helpSections = Object.values(settings) as HelpItem[];
|
||||||
|
|
||||||
|
@ -125,5 +144,68 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</SwitchRow>
|
</SwitchRow>
|
||||||
</div>
|
</div>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
|
<Item>
|
||||||
|
<SpinBoxFloatRow
|
||||||
|
bind:value={$config.secondsToShowQuestion}
|
||||||
|
defaultValue={defaults.secondsToShowQuestion}
|
||||||
|
step={1}
|
||||||
|
>
|
||||||
|
<SettingTitle
|
||||||
|
on:click={() =>
|
||||||
|
openHelpModal(
|
||||||
|
Object.keys(settings).indexOf("secondsToShowQuestion"),
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{settings.secondsToShowQuestion.title}
|
||||||
|
</SettingTitle>
|
||||||
|
</SpinBoxFloatRow>
|
||||||
|
</Item>
|
||||||
|
|
||||||
|
<Item>
|
||||||
|
<SpinBoxFloatRow
|
||||||
|
bind:value={$config.secondsToShowAnswer}
|
||||||
|
defaultValue={defaults.secondsToShowAnswer}
|
||||||
|
step={1}
|
||||||
|
>
|
||||||
|
<SettingTitle
|
||||||
|
on:click={() =>
|
||||||
|
openHelpModal(
|
||||||
|
Object.keys(settings).indexOf("secondsToShowAnswer"),
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{settings.secondsToShowAnswer.title}
|
||||||
|
</SettingTitle>
|
||||||
|
</SpinBoxFloatRow>
|
||||||
|
</Item>
|
||||||
|
|
||||||
|
<Item>
|
||||||
|
<SwitchRow
|
||||||
|
bind:value={$config.waitForAudio}
|
||||||
|
defaultValue={defaults.waitForAudio}
|
||||||
|
>
|
||||||
|
<SettingTitle
|
||||||
|
on:click={() =>
|
||||||
|
openHelpModal(Object.keys(settings).indexOf("waitForAudio"))}
|
||||||
|
>
|
||||||
|
{settings.waitForAudio.title}
|
||||||
|
</SettingTitle>
|
||||||
|
</SwitchRow>
|
||||||
|
</Item>
|
||||||
|
|
||||||
|
<Item>
|
||||||
|
<EnumSelectorRow
|
||||||
|
bind:value={$config.answerAction}
|
||||||
|
defaultValue={defaults.answerAction}
|
||||||
|
choices={answerChoices()}
|
||||||
|
>
|
||||||
|
<SettingTitle
|
||||||
|
on:click={() =>
|
||||||
|
openHelpModal(Object.keys(settings).indexOf("answerAction"))}
|
||||||
|
>
|
||||||
|
{settings.answerAction.title}
|
||||||
|
</SettingTitle>
|
||||||
|
</EnumSelectorRow>
|
||||||
|
</Item>
|
||||||
</DynamicallySlottable>
|
</DynamicallySlottable>
|
||||||
</TitledContainer>
|
</TitledContainer>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
DeckConfig_Config_AnswerAction,
|
||||||
DeckConfig_Config_LeechAction,
|
DeckConfig_Config_LeechAction,
|
||||||
DeckConfig_Config_NewCardGatherPriority,
|
DeckConfig_Config_NewCardGatherPriority,
|
||||||
DeckConfig_Config_NewCardInsertOrder,
|
DeckConfig_Config_NewCardInsertOrder,
|
||||||
|
@ -149,3 +150,28 @@ export function newInsertOrderChoices(): Choice<DeckConfig_Config_NewCardInsertO
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function answerChoices(): Choice<DeckConfig_Config_AnswerAction>[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: tr.studyingBuryCard(),
|
||||||
|
value: DeckConfig_Config_AnswerAction.BURY_CARD,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: tr.deckConfigAnswerAgain(),
|
||||||
|
value: DeckConfig_Config_AnswerAction.ANSWER_AGAIN,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: tr.deckConfigAnswerGood(),
|
||||||
|
value: DeckConfig_Config_AnswerAction.ANSWER_GOOD,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: tr.deckConfigAnswerHard(),
|
||||||
|
value: DeckConfig_Config_AnswerAction.ANSWER_HARD,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: tr.deckConfigShowReminder(),
|
||||||
|
value: DeckConfig_Config_AnswerAction.SHOW_REMINDER,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ const i18n = setupI18n({
|
||||||
ModuleName.ACTIONS,
|
ModuleName.ACTIONS,
|
||||||
ModuleName.DECK_CONFIG,
|
ModuleName.DECK_CONFIG,
|
||||||
ModuleName.KEYBOARD,
|
ModuleName.KEYBOARD,
|
||||||
|
ModuleName.STUDYING,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue