implement bury/suspend undo

This commit is contained in:
Damien Elmes 2021-03-04 21:40:59 +10:00
parent b466f0ce90
commit 41779c1aad
9 changed files with 82 additions and 62 deletions

View file

@ -1262,9 +1262,9 @@ where id in %s"""
self.editor.saveNow(self._onSuspend) self.editor.saveNow(self._onSuspend)
def _onSuspend(self) -> None: def _onSuspend(self) -> None:
sus = not self.isSuspended() want_suspend = not self.isSuspended()
c = self.selectedCards() c = self.selectedCards()
if sus: if want_suspend:
self.col.sched.suspend_cards(c) self.col.sched.suspend_cards(c)
else: else:
self.col.sched.unsuspend_cards(c) self.col.sched.unsuspend_cards(c)

View file

@ -292,10 +292,10 @@ class Reviewer:
("Ctrl+3", lambda: self.setFlag(3)), ("Ctrl+3", lambda: self.setFlag(3)),
("Ctrl+4", lambda: self.setFlag(4)), ("Ctrl+4", lambda: self.setFlag(4)),
("*", self.onMark), ("*", self.onMark),
("=", self.onBuryNote), ("=", self.bury_current_note),
("-", self.onBuryCard), ("-", self.bury_current_card),
("!", self.onSuspend), ("!", self.suspend_current_note),
("@", self.onSuspendCard), ("@", self.suspend_current_card),
("Ctrl+Delete", self.onDelete), ("Ctrl+Delete", self.onDelete),
("Ctrl+Shift+D", self.on_set_due), ("Ctrl+Shift+D", self.on_set_due),
("v", self.onReplayRecorded), ("v", self.onReplayRecorded),
@ -727,11 +727,11 @@ time = %(time)d;
], ],
], ],
[tr(TR.STUDYING_MARK_NOTE), "*", self.onMark], [tr(TR.STUDYING_MARK_NOTE), "*", self.onMark],
[tr(TR.STUDYING_BURY_CARD), "-", self.onBuryCard], [tr(TR.STUDYING_BURY_CARD), "-", self.bury_current_card],
[tr(TR.STUDYING_BURY_NOTE), "=", self.onBuryNote], [tr(TR.STUDYING_BURY_NOTE), "=", self.bury_current_note],
[tr(TR.ACTIONS_SET_DUE_DATE), "Ctrl+Shift+D", self.on_set_due], [tr(TR.ACTIONS_SET_DUE_DATE), "Ctrl+Shift+D", self.on_set_due],
[tr(TR.ACTIONS_SUSPEND_CARD), "@", self.onSuspendCard], [tr(TR.ACTIONS_SUSPEND_CARD), "@", self.suspend_current_card],
[tr(TR.STUDYING_SUSPEND_NOTE), "!", self.onSuspend], [tr(TR.STUDYING_SUSPEND_NOTE), "!", self.suspend_current_note],
[tr(TR.STUDYING_DELETE_NOTE), "Ctrl+Delete", self.onDelete], [tr(TR.STUDYING_DELETE_NOTE), "Ctrl+Delete", self.onDelete],
[tr(TR.ACTIONS_OPTIONS), "O", self.onOptions], [tr(TR.ACTIONS_OPTIONS), "O", self.onOptions],
None, None,
@ -808,17 +808,25 @@ time = %(time)d;
on_done=self.mw.reset, on_done=self.mw.reset,
) )
def onSuspend(self) -> None: def suspend_current_note(self) -> None:
self.mw.checkpoint(tr(TR.STUDYING_SUSPEND))
self.mw.col.sched.suspend_cards([c.id for c in self.card.note().cards()]) self.mw.col.sched.suspend_cards([c.id for c in self.card.note().cards()])
self.mw.reset()
tooltip(tr(TR.STUDYING_NOTE_SUSPENDED)) tooltip(tr(TR.STUDYING_NOTE_SUSPENDED))
self.mw.reset()
def onSuspendCard(self) -> None: def suspend_current_card(self) -> None:
self.mw.checkpoint(tr(TR.STUDYING_SUSPEND))
self.mw.col.sched.suspend_cards([self.card.id]) self.mw.col.sched.suspend_cards([self.card.id])
tooltip(tr(TR.STUDYING_CARD_SUSPENDED))
self.mw.reset() self.mw.reset()
tooltip(tr(TR.STUDYING_CARD_SUSPENDED))
def bury_current_card(self) -> None:
self.mw.col.sched.bury_cards([self.card.id])
self.mw.reset()
tooltip(tr(TR.STUDYING_CARD_BURIED))
def bury_current_note(self) -> None:
self.mw.col.sched.bury_note(self.card.note())
self.mw.reset()
tooltip(tr(TR.STUDYING_NOTE_BURIED))
def onDelete(self) -> None: def onDelete(self) -> None:
# need to check state because the shortcut is global to the main # need to check state because the shortcut is global to the main
@ -831,18 +839,6 @@ time = %(time)d;
self.mw.reset() self.mw.reset()
tooltip(tr(TR.STUDYING_NOTE_AND_ITS_CARD_DELETED, count=cnt)) tooltip(tr(TR.STUDYING_NOTE_AND_ITS_CARD_DELETED, count=cnt))
def onBuryCard(self) -> None:
self.mw.checkpoint(tr(TR.STUDYING_BURY))
self.mw.col.sched.bury_cards([self.card.id])
self.mw.reset()
tooltip(tr(TR.STUDYING_CARD_BURIED))
def onBuryNote(self) -> None:
self.mw.checkpoint(tr(TR.STUDYING_BURY))
self.mw.col.sched.bury_note(self.card.note())
self.mw.reset()
tooltip(tr(TR.STUDYING_NOTE_BURIED))
def onRecordVoice(self) -> None: def onRecordVoice(self) -> None:
def after_record(path: str) -> None: def after_record(path: str) -> None:
self._recordedAudio = path self._recordedAudio = path
@ -855,3 +851,10 @@ time = %(time)d;
tooltip(tr(TR.STUDYING_YOU_HAVENT_RECORDED_YOUR_VOICE_YET)) tooltip(tr(TR.STUDYING_YOU_HAVENT_RECORDED_YOUR_VOICE_YET))
return return
av_player.play_file(self._recordedAudio) av_player.play_file(self._recordedAudio)
# legacy
onBuryCard = bury_current_card
onBuryNote = bury_current_note
onSuspend = suspend_current_note
onSuspendCard = suspend_current_card

View file

@ -707,7 +707,7 @@ impl BackendService for Backend {
fn clear_card_queues(&self, _input: pb::Empty) -> BackendResult<pb::Empty> { fn clear_card_queues(&self, _input: pb::Empty) -> BackendResult<pb::Empty> {
self.with_col(|col| { self.with_col(|col| {
col.clear_queues(); col.clear_study_queues();
Ok(().into()) Ok(().into())
}) })
} }

View file

@ -1,6 +1,8 @@
// Copyright: Ankitects Pty Ltd and contributors // Copyright: Ankitects Pty Ltd and contributors
// 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
mod op;
use crate::i18n::I18n; use crate::i18n::I18n;
use crate::log::Logger; use crate::log::Logger;
use crate::types::Usn; use crate::types::Usn;
@ -11,6 +13,7 @@ use crate::{
undo::UndoManager, undo::UndoManager,
}; };
use crate::{err::Result, scheduler::queue::CardQueues}; use crate::{err::Result, scheduler::queue::CardQueues};
pub use op::CollectionOp;
use std::{collections::HashMap, path::PathBuf, sync::Arc}; use std::{collections::HashMap, path::PathBuf, sync::Arc};
pub fn open_collection<P: Into<PathBuf>>( pub fn open_collection<P: Into<PathBuf>>(
@ -78,12 +81,6 @@ pub struct Collection {
pub(crate) state: CollectionState, pub(crate) state: CollectionState,
} }
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CollectionOp {
UpdateCard,
AnswerCard,
}
impl Collection { impl Collection {
/// Execute the provided closure in a transaction, rolling back if /// Execute the provided closure in a transaction, rolling back if
/// an error is returned. /// an error is returned.

View file

@ -0,0 +1,25 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use crate::prelude::*;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CollectionOp {
UpdateCard,
AnswerCard,
Bury,
Suspend,
}
impl Collection {
pub fn describe_collection_op(&self, op: CollectionOp) -> String {
let key = match op {
CollectionOp::UpdateCard => todo!(),
CollectionOp::AnswerCard => TR::UndoAnswerCard,
CollectionOp::Bury => TR::StudyingBury,
CollectionOp::Suspend => TR::StudyingSuspend,
};
self.i18n.tr(key).to_string()
}
}

View file

@ -66,7 +66,7 @@ mod test {
col.storage.update_card(&card)?; col.storage.update_card(&card)?;
// fail it, which should cause it to be marked as a leech // fail it, which should cause it to be marked as a leech
col.clear_queues(); col.clear_study_queues();
let queued = col.next_card()?.unwrap(); let queued = col.next_card()?.unwrap();
dbg!(&queued); dbg!(&queued);
col.answer_card(&CardAnswer { col.answer_card(&CardAnswer {

View file

@ -12,7 +12,10 @@ use crate::{
}; };
use super::timing::SchedTimingToday; use super::timing::SchedTimingToday;
use pb::unbury_cards_in_current_deck_in::Mode as UnburyDeckMode; use pb::{
bury_or_suspend_cards_in::Mode as BuryOrSuspendMode,
unbury_cards_in_current_deck_in::Mode as UnburyDeckMode,
};
impl Card { impl Card {
/// True if card was buried/suspended prior to the call. /// True if card was buried/suspended prior to the call.
@ -86,20 +89,16 @@ impl Collection {
/// Bury/suspend cards in search table, and clear it. /// Bury/suspend cards in search table, and clear it.
/// Marks the cards as modified. /// Marks the cards as modified.
fn bury_or_suspend_searched_cards( fn bury_or_suspend_searched_cards(&mut self, mode: BuryOrSuspendMode) -> Result<()> {
&mut self,
mode: pb::bury_or_suspend_cards_in::Mode,
) -> Result<()> {
use pb::bury_or_suspend_cards_in::Mode;
let usn = self.usn()?; let usn = self.usn()?;
let sched = self.scheduler_version(); let sched = self.scheduler_version();
for original in self.storage.all_searched_cards()? { for original in self.storage.all_searched_cards()? {
let mut card = original.clone(); let mut card = original.clone();
let desired_queue = match mode { let desired_queue = match mode {
Mode::Suspend => CardQueue::Suspended, BuryOrSuspendMode::Suspend => CardQueue::Suspended,
Mode::BurySched => CardQueue::SchedBuried, BuryOrSuspendMode::BurySched => CardQueue::SchedBuried,
Mode::BuryUser => { BuryOrSuspendMode::BuryUser => {
if sched == SchedulerVersion::V1 { if sched == SchedulerVersion::V1 {
// v1 scheduler only had one bury type // v1 scheduler only had one bury type
CardQueue::SchedBuried CardQueue::SchedBuried
@ -124,9 +123,14 @@ impl Collection {
pub fn bury_or_suspend_cards( pub fn bury_or_suspend_cards(
&mut self, &mut self,
cids: &[CardID], cids: &[CardID],
mode: pb::bury_or_suspend_cards_in::Mode, mode: BuryOrSuspendMode,
) -> Result<()> { ) -> Result<()> {
self.transact(None, |col| { let op = match mode {
BuryOrSuspendMode::Suspend => CollectionOp::Suspend,
BuryOrSuspendMode::BurySched | BuryOrSuspendMode::BuryUser => CollectionOp::Bury,
};
self.transact(Some(op), |col| {
col.clear_study_queues();
col.storage.set_search_table_to_card_ids(cids, false)?; col.storage.set_search_table_to_card_ids(cids, false)?;
col.bury_or_suspend_searched_cards(mode) col.bury_or_suspend_searched_cards(mode)
}) })
@ -139,10 +143,9 @@ impl Collection {
include_new: bool, include_new: bool,
include_reviews: bool, include_reviews: bool,
) -> Result<()> { ) -> Result<()> {
use pb::bury_or_suspend_cards_in::Mode;
self.storage self.storage
.search_siblings_for_bury(cid, nid, include_new, include_reviews)?; .search_siblings_for_bury(cid, nid, include_new, include_reviews)?;
self.bury_or_suspend_searched_cards(Mode::BurySched) self.bury_or_suspend_searched_cards(BuryOrSuspendMode::BurySched)
} }
} }

View file

@ -132,11 +132,12 @@ impl Collection {
} }
} }
pub(crate) fn clear_queues(&mut self) { pub(crate) fn clear_study_queues(&mut self) {
// clearing the queue will remove any undone reviews from the undo queue,
// causing problems if we then try to redo them
self.state.undo.clear_redo();
self.state.card_queues = None; self.state.card_queues = None;
// clearing the queue will remove any undone reviews from the undo queue,
// causing problems if we then try to redo them, so we need to clear the
// redo queue as well
self.state.undo.clear_redo();
} }
pub(crate) fn update_queues_after_answering_card( pub(crate) fn update_queues_after_answering_card(

View file

@ -2,7 +2,6 @@
// 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
use crate::backend_proto as pb; use crate::backend_proto as pb;
use crate::i18n::TR;
use crate::{ use crate::{
collection::{Collection, CollectionOp}, collection::{Collection, CollectionOp},
err::Result, err::Result,
@ -145,14 +144,6 @@ impl Collection {
self.state.undo.save(item) self.state.undo.save(item)
} }
pub fn describe_collection_op(&self, op: CollectionOp) -> String {
match op {
CollectionOp::UpdateCard => todo!(),
CollectionOp::AnswerCard => self.i18n.tr(TR::UndoAnswerCard),
}
.to_string()
}
pub fn undo_status(&self) -> pb::UndoStatus { pub fn undo_status(&self) -> pb::UndoStatus {
pb::UndoStatus { pb::UndoStatus {
undo: self undo: self