diff --git a/ftl/core/decks.ftl b/ftl/core/decks.ftl index 409ec7fbe..cb1d0b48d 100644 --- a/ftl/core/decks.ftl +++ b/ftl/core/decks.ftl @@ -47,9 +47,8 @@ decks-decreasing-intervals = Decreasing intervals decks-oldest-seen-first = Oldest seen first # Combobox entry: Sort the cards in random order decks-random = Random +# Combobox entry: Sort the cards by relative overdueness, in descending order (most overdue to least overdue) +decks-relative-overdueness = Relative overdueness ## These strings are no longer used - you do not need to translate them if they ## are not already translated. - -# Combobox entry: Sort the cards by relative overdueness, in descending order (most overdue to least overdue) -decks-relative-overdueness = Relative overdueness diff --git a/proto/anki/deck_config.proto b/proto/anki/deck_config.proto index 5ed02423e..181ed4e7d 100644 --- a/proto/anki/deck_config.proto +++ b/proto/anki/deck_config.proto @@ -104,6 +104,7 @@ message DeckConfig { REVIEW_CARD_ORDER_EASE_DESCENDING = 6; REVIEW_CARD_ORDER_RETRIEVABILITY_ASCENDING = 7; REVIEW_CARD_ORDER_RETRIEVABILITY_DESCENDING = 11; + REVIEW_CARD_ORDER_RELATIVE_OVERDUENESS = 12; REVIEW_CARD_ORDER_RANDOM = 8; REVIEW_CARD_ORDER_ADDED = 9; REVIEW_CARD_ORDER_REVERSE_ADDED = 10; diff --git a/proto/anki/decks.proto b/proto/anki/decks.proto index b244eb4a1..e971e6946 100644 --- a/proto/anki/decks.proto +++ b/proto/anki/decks.proto @@ -101,6 +101,7 @@ message Deck { REVERSE_ADDED = 7; RETRIEVABILITY_ASCENDING = 8; RETRIEVABILITY_DESCENDING = 9; + RELATIVE_OVERDUENESS = 10; } string search = 1; diff --git a/rslib/src/decks/filtered.rs b/rslib/src/decks/filtered.rs index ef77076af..250df60ae 100644 --- a/rslib/src/decks/filtered.rs +++ b/rslib/src/decks/filtered.rs @@ -66,6 +66,7 @@ fn search_order_label(order: FilteredSearchOrder, tr: &I18n) -> String { FilteredSearchOrder::RetrievabilityDescending => { tr.deck_config_sort_order_retrievability_descending() } + FilteredSearchOrder::RelativeOverdueness => tr.decks_relative_overdueness(), } .into() } diff --git a/rslib/src/scheduler/fsrs/simulator.rs b/rslib/src/scheduler/fsrs/simulator.rs index b18e86a77..3314b3c42 100644 --- a/rslib/src/scheduler/fsrs/simulator.rs +++ b/rslib/src/scheduler/fsrs/simulator.rs @@ -117,7 +117,7 @@ fn create_review_priority_fn( } // Not implemented yet - Added | ReverseAdded => None, + Added | ReverseAdded | RelativeOverdueness => None, } } diff --git a/rslib/src/scheduler/queue/builder/mod.rs b/rslib/src/scheduler/queue/builder/mod.rs index 064220bce..fd0ae750c 100644 --- a/rslib/src/scheduler/queue/builder/mod.rs +++ b/rslib/src/scheduler/queue/builder/mod.rs @@ -469,7 +469,7 @@ mod test { cards.push(card); } col.update_cards_maybe_undoable(cards, false)?; - col.set_deck_review_order(&mut deck, ReviewCardOrder::RetrievabilityAscending); + col.set_deck_review_order(&mut deck, ReviewCardOrder::RelativeOverdueness); assert_eq!(col.queue_as_due_and_ivl(deck.id), expected_queue); Ok(()) diff --git a/rslib/src/storage/card/filtered.rs b/rslib/src/storage/card/filtered.rs index 03f845f4e..f6d83b990 100644 --- a/rslib/src/storage/card/filtered.rs +++ b/rslib/src/storage/card/filtered.rs @@ -1,7 +1,6 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -use crate::card::CardQueue; use crate::decks::FilteredSearchOrder; use crate::decks::FilteredSearchTerm; use crate::scheduler::timing::SchedTimingToday; @@ -40,6 +39,11 @@ pub(crate) fn order_and_limit_for_search( build_retrievability_query(fsrs, today, next_day_at, now, SqlSortOrder::Descending); &temp_string } + FilteredSearchOrder::RelativeOverdueness => { + temp_string = + format!("extract_fsrs_relative_retrievability(data, case when odue !=0 then odue else due end, ivl, {today}, {next_day_at}, {now}) asc"); + &temp_string + } }; format!("{}, fnvhash(c.id, c.mod) limit {}", order, term.limit) @@ -54,15 +58,9 @@ fn build_retrievability_query( ) -> String { if fsrs { format!( - "extract_fsrs_relative_retrievability(c.data, case when c.odue !=0 then c.odue else c.due end, ivl, {today}, {next_day_at}, {now}) {order}" + "extract_fsrs_retrievability(c.data, case when c.odue !=0 then c.odue else c.due end, ivl, {today}, {next_day_at}, {now}) {order}" ) } else { - format!( - " -(case when queue={rev_queue} and due <= {today} -then (ivl / cast({today}-due+0.001 as real)) else 100000+due end) {order}", - rev_queue = CardQueue::Review as i8, - today = today - ) + String::new() } } diff --git a/rslib/src/storage/card/mod.rs b/rslib/src/storage/card/mod.rs index 9e06edf07..0a919ae42 100644 --- a/rslib/src/storage/card/mod.rs +++ b/rslib/src/storage/card/mod.rs @@ -800,14 +800,14 @@ pub(crate) enum ReviewOrderSubclause { DifficultyAscending, /// FSRS DifficultyDescending, - RetrievabilitySm2 { - today: u32, - order: SqlSortOrder, - }, RetrievabilityFsrs { timing: SchedTimingToday, order: SqlSortOrder, }, + RelativeOverdueness { + fsrs: bool, + timing: SchedTimingToday, + }, Added, ReverseAdded, } @@ -825,19 +825,26 @@ impl fmt::Display for ReviewOrderSubclause { ReviewOrderSubclause::EaseDescending => "factor desc", ReviewOrderSubclause::DifficultyAscending => "extract_fsrs_variable(data, 'd') asc", ReviewOrderSubclause::DifficultyDescending => "extract_fsrs_variable(data, 'd') desc", - ReviewOrderSubclause::RetrievabilitySm2 { today, order } => { - temp_string = format!( - // - (elapsed days+0.001)/(scheduled interval) - "-(1 + cast({today}-due+0.001 as real)/ivl) {order}" - ); - &temp_string - } ReviewOrderSubclause::RetrievabilityFsrs { timing, order } => { let today = timing.days_elapsed; let next_day_at = timing.next_day_at.0; let now = timing.now.0; temp_string = - format!("extract_fsrs_relative_retrievability(data, case when odue !=0 then odue else due end, ivl, {today}, {next_day_at}, {now}) {order}"); + format!("extract_fsrs_retrievability(data, case when odue !=0 then odue else due end, ivl, {today}, {next_day_at}, {now}) {order}"); + &temp_string + } + ReviewOrderSubclause::RelativeOverdueness { fsrs, timing } => { + let today = timing.days_elapsed; + let next_day_at = timing.next_day_at.0; + let now = timing.now.0; + temp_string = if *fsrs { + format!("extract_fsrs_relative_retrievability(data, case when odue !=0 then odue else due end, ivl, {today}, {next_day_at}, {now}) asc") + } else { + format!( + // - (elapsed days+0.001)/(scheduled interval) + "-(1 + cast({today}-due+0.001 as real)/ivl) asc" + ) + }; &temp_string } ReviewOrderSubclause::Added => "nid asc, ord asc", @@ -867,10 +874,19 @@ fn review_order_sql(order: ReviewCardOrder, timing: SchedTimingToday, fsrs: bool ReviewOrderSubclause::EaseDescending }], ReviewCardOrder::RetrievabilityAscending => { - build_retrievability_clauses(fsrs, timing, SqlSortOrder::Ascending) + vec![ReviewOrderSubclause::RetrievabilityFsrs { + timing, + order: SqlSortOrder::Ascending, + }] } ReviewCardOrder::RetrievabilityDescending => { - build_retrievability_clauses(fsrs, timing, SqlSortOrder::Descending) + vec![ReviewOrderSubclause::RetrievabilityFsrs { + timing, + order: SqlSortOrder::Descending, + }] + } + ReviewCardOrder::RelativeOverdueness => { + vec![ReviewOrderSubclause::RelativeOverdueness { fsrs, timing }] } ReviewCardOrder::Random => vec![], ReviewCardOrder::Added => vec![ReviewOrderSubclause::Added], @@ -885,21 +901,6 @@ fn review_order_sql(order: ReviewCardOrder, timing: SchedTimingToday, fsrs: bool v.join(", ") } -fn build_retrievability_clauses( - fsrs: bool, - timing: SchedTimingToday, - order: SqlSortOrder, -) -> Vec { - vec![if fsrs { - ReviewOrderSubclause::RetrievabilityFsrs { timing, order } - } else { - ReviewOrderSubclause::RetrievabilitySm2 { - today: timing.days_elapsed, - order, - } - }] -} - #[derive(Debug, Clone, Copy)] pub(crate) enum NewCardSorting { /// Ascending position, consecutive siblings, diff --git a/rslib/src/storage/sqlite.rs b/rslib/src/storage/sqlite.rs index 95853afc9..7f68496b4 100644 --- a/rslib/src/storage/sqlite.rs +++ b/rslib/src/storage/sqlite.rs @@ -654,7 +654,7 @@ impl SqliteStorage { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] pub enum SqlSortOrder { Ascending, Descending, @@ -686,8 +686,7 @@ mod test { col.answer_easy(); let timing = col.timing_today()?; - let order = SqlSortOrder::Ascending; - let sql_func = ReviewOrderSubclause::RetrievabilityFsrs { timing, order } + let sql_func = ReviewOrderSubclause::RelativeOverdueness { fsrs: true, timing } .to_string() .replace(" asc", ""); let sql = format!("select {sql_func} from cards"); diff --git a/ts/routes/deck-options/choices.ts b/ts/routes/deck-options/choices.ts index 6f34eae0e..07a55aad2 100644 --- a/ts/routes/deck-options/choices.ts +++ b/ts/routes/deck-options/choices.ts @@ -69,7 +69,9 @@ export function newSortOrderChoices(): Choice[] { +export function reviewOrderChoices( + fsrs: boolean, +): Choice[] { return [ ...[ { @@ -94,14 +96,11 @@ export function reviewOrderChoices(fsrs: boolean): Choice[] { const order = [ { - label: fsrs ? tr.deckConfigSortOrderDescendingDifficulty() : tr.deckConfigSortOrderAscendingEase(), + label: fsrs + ? tr.deckConfigSortOrderDescendingDifficulty() + : tr.deckConfigSortOrderAscendingEase(), value: DeckConfig_Config_ReviewCardOrder.EASE_ASCENDING, }, { - label: fsrs ? tr.deckConfigSortOrderAscendingDifficulty() : tr.deckConfigSortOrderDescendingEase(), + label: fsrs + ? tr.deckConfigSortOrderAscendingDifficulty() + : tr.deckConfigSortOrderDescendingEase(), value: DeckConfig_Config_ReviewCardOrder.EASE_DESCENDING, }, ]; @@ -215,3 +218,21 @@ function difficultyOrders(fsrs: boolean): Choice[] { + if (!fsrs) { + return []; + } + return [ + { + label: tr.deckConfigSortOrderRetrievabilityAscending(), + value: DeckConfig_Config_ReviewCardOrder.RETRIEVABILITY_ASCENDING, + }, + { + label: tr.deckConfigSortOrderRetrievabilityDescending(), + value: DeckConfig_Config_ReviewCardOrder.RETRIEVABILITY_DESCENDING, + }, + ]; +}