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:
Abdo 2023-11-13 03:41:51 +03:00 committed by GitHub
parent 46890fbbaa
commit ae7b14bf40
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 311 additions and 7 deletions

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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
########################################################################## ##########################################################################

View file

@ -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

View file

@ -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 = []

View file

@ -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,

View file

@ -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",
}; };

View file

@ -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>

View file

@ -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>

View file

@ -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,
},
];
}

View file

@ -22,6 +22,7 @@ const i18n = setupI18n({
ModuleName.ACTIONS, ModuleName.ACTIONS,
ModuleName.DECK_CONFIG, ModuleName.DECK_CONFIG,
ModuleName.KEYBOARD, ModuleName.KEYBOARD,
ModuleName.STUDYING,
], ],
}); });