From 410660990e40d03dfc922089cbe8c76d78973fe3 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 8 Jun 2021 14:01:46 +1000 Subject: [PATCH] add LIFO sorting options for new cards --- ftl/core/deck-config.ftl | 11 +++++--- rslib/backend.proto | 13 +++++---- rslib/src/deckconfig/mod.rs | 2 +- rslib/src/scheduler/queue/builder/mod.rs | 29 +++++++++++--------- rslib/src/scheduler/queue/builder/sorting.rs | 22 +++++++++++---- rslib/src/storage/card/mod.rs | 15 ++++++++-- ts/deckoptions/DisplayOrder.svelte | 9 ++++-- 7 files changed, 67 insertions(+), 34 deletions(-) diff --git a/ftl/core/deck-config.ftl b/ftl/core/deck-config.ftl index a9ba38dfc..bbc7ac222 100644 --- a/ftl/core/deck-config.ftl +++ b/ftl/core/deck-config.ftl @@ -97,18 +97,21 @@ deck-config-new-gather-priority-tooltip = to prioritize subdecks that are closer to the top. `Position`: gathers cards from all decks before they are sorted. This - ensures the oldest cards will be shown first, even if the parent limit is + ensures cards appear in strict position order, even if the parent limit is not high enough to see cards from all decks. deck-config-new-gather-priority-deck = Deck -deck-config-new-gather-priority-position = Position +deck-config-new-gather-priority-position-lowest-first = Position (lowest first) +deck-config-new-gather-priority-position-highest-first = Position (highest first) deck-config-new-card-sort-order = New card sort order deck-config-new-card-sort-order-tooltip = How cards are sorted after they have been gathered. By default, Anki sorts by card template first, to avoid multiple cards of the same note from being shown in succession. -deck-config-sort-order-card-template-then-position = Card template, then position +deck-config-sort-order-card-template-then-lowest-position = Card template, then lowest position +deck-config-sort-order-card-template-then-highest-position = Card template, then highest position deck-config-sort-order-card-template-then-random = Card template, then random -deck-config-sort-order-position = Position (siblings together) +deck-config-sort-order-lowest-position = Lowest position +deck-config-sort-order-highest-position = Highest position deck-config-sort-order-random = Random deck-config-new-review-priority = New/review priority deck-config-new-review-priority-tooltip = When to show new cards in relation to review cards. diff --git a/rslib/backend.proto b/rslib/backend.proto index e15ac9cc3..2b30c7319 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -320,13 +320,16 @@ message DeckConfig { } enum NewCardGatherPriority { NEW_CARD_GATHER_PRIORITY_DECK = 0; - NEW_CARD_GATHER_PRIORITY_POSITION = 1; + NEW_CARD_GATHER_PRIORITY_LOWEST_POSITION = 1; + NEW_CARD_GATHER_PRIORITY_HIGHEST_POSITION = 2; } enum NewCardSortOrder { - NEW_CARD_SORT_ORDER_TEMPLATE_THEN_DUE = 0; - NEW_CARD_SORT_ORDER_TEMPLATE_THEN_RANDOM = 1; - NEW_CARD_SORT_ORDER_DUE = 2; - NEW_CARD_SORT_ORDER_RANDOM = 3; + NEW_CARD_SORT_ORDER_TEMPLATE_THEN_LOWEST_POSITION = 0; + NEW_CARD_SORT_ORDER_TEMPLATE_THEN_HIGHEST_POSITION = 1; + NEW_CARD_SORT_ORDER_TEMPLATE_THEN_RANDOM = 2; + NEW_CARD_SORT_ORDER_LOWEST_POSITION = 3; + NEW_CARD_SORT_ORDER_HIGHEST_POSITION = 4; + NEW_CARD_SORT_ORDER_RANDOM = 5; } enum ReviewCardOrder { REVIEW_CARD_ORDER_DAY = 0; diff --git a/rslib/src/deckconfig/mod.rs b/rslib/src/deckconfig/mod.rs index 356881b6f..c5789502e 100644 --- a/rslib/src/deckconfig/mod.rs +++ b/rslib/src/deckconfig/mod.rs @@ -63,7 +63,7 @@ impl Default for DeckConfig { graduating_interval_easy: 4, new_card_insert_order: NewCardInsertOrder::Due as i32, new_card_gather_priority: NewCardGatherPriority::Deck as i32, - new_card_sort_order: NewCardSortOrder::TemplateThenDue as i32, + new_card_sort_order: NewCardSortOrder::TemplateThenLowestPosition as i32, review_order: ReviewCardOrder::Day as i32, new_mix: ReviewMix::MixWithReviews as i32, interday_learning_mix: ReviewMix::MixWithReviews as i32, diff --git a/rslib/src/scheduler/queue/builder/mod.rs b/rslib/src/scheduler/queue/builder/mod.rs index 629194133..6eedf3d84 100644 --- a/rslib/src/scheduler/queue/builder/mod.rs +++ b/rslib/src/scheduler/queue/builder/mod.rs @@ -209,7 +209,7 @@ impl Collection { .unwrap_or_else(|| { // filtered decks do not space siblings QueueSortOptions { - new_order: NewCardSortOrder::Due, + new_order: NewCardSortOrder::LowestPosition, ..Default::default() } }); @@ -272,25 +272,28 @@ impl Collection { } selected_deck_limits.new = selected_deck_limits.new.min(selected_deck_limits.review); let can_exit_early = sort_options.new_gather_priority == NewCardGatherPriority::Deck; + let reverse = sort_options.new_gather_priority == NewCardGatherPriority::HighestPosition; for deck in &decks { if can_exit_early && selected_deck_limits.new == 0 { break; } let limit = remaining.get_mut(&deck.id).unwrap(); if limit.new > 0 { - self.storage.for_each_new_card_in_deck(deck.id, |card| { - let bury = get_bury_mode(card.original_deck_id.or(deck.id)); - if limit.new != 0 { - if queues.add_new_card(card, bury) { - limit.new -= 1; - selected_deck_limits.new = selected_deck_limits.new.saturating_sub(1); - } + self.storage + .for_each_new_card_in_deck(deck.id, reverse, |card| { + let bury = get_bury_mode(card.original_deck_id.or(deck.id)); + if limit.new != 0 { + if queues.add_new_card(card, bury) { + limit.new -= 1; + selected_deck_limits.new = + selected_deck_limits.new.saturating_sub(1); + } - true - } else { - false - } - })?; + true + } else { + false + } + })?; } } diff --git a/rslib/src/scheduler/queue/builder/sorting.rs b/rslib/src/scheduler/queue/builder/sorting.rs index 549e27869..6777e6a95 100644 --- a/rslib/src/scheduler/queue/builder/sorting.rs +++ b/rslib/src/scheduler/queue/builder/sorting.rs @@ -10,14 +10,18 @@ use super::{NewCard, NewCardSortOrder, QueueBuilder}; impl QueueBuilder { pub(super) fn sort_new(&mut self) { match self.sort_options.new_order { - NewCardSortOrder::TemplateThenDue => { - self.new.sort_unstable_by(template_then_due); + NewCardSortOrder::TemplateThenLowestPosition => { + self.new.sort_unstable_by(template_then_lowest_position); + } + NewCardSortOrder::TemplateThenHighestPosition => { + self.new.sort_unstable_by(template_then_highest_position); } NewCardSortOrder::TemplateThenRandom => { self.new.iter_mut().for_each(NewCard::hash_id_and_mtime); self.new.sort_unstable_by(template_then_random); } - NewCardSortOrder::Due => self.new.sort_unstable_by(new_position), + NewCardSortOrder::LowestPosition => self.new.sort_unstable_by(lowest_position), + NewCardSortOrder::HighestPosition => self.new.sort_unstable_by(highest_position), NewCardSortOrder::Random => { self.new.iter_mut().for_each(NewCard::hash_id_and_mtime); self.new.sort_unstable_by(new_hash) @@ -26,18 +30,26 @@ impl QueueBuilder { } } -fn template_then_due(a: &NewCard, b: &NewCard) -> Ordering { +fn template_then_lowest_position(a: &NewCard, b: &NewCard) -> Ordering { (a.template_index, a.due).cmp(&(b.template_index, b.due)) } +fn template_then_highest_position(a: &NewCard, b: &NewCard) -> Ordering { + (a.template_index, b.due).cmp(&(b.template_index, a.due)) +} + fn template_then_random(a: &NewCard, b: &NewCard) -> Ordering { (a.template_index, a.hash).cmp(&(b.template_index, b.hash)) } -fn new_position(a: &NewCard, b: &NewCard) -> Ordering { +fn lowest_position(a: &NewCard, b: &NewCard) -> Ordering { a.due.cmp(&b.due) } +fn highest_position(a: &NewCard, b: &NewCard) -> Ordering { + b.due.cmp(&a.due) +} + fn new_hash(a: &NewCard, b: &NewCard) -> Ordering { a.hash.cmp(&b.hash) } diff --git a/rslib/src/storage/card/mod.rs b/rslib/src/storage/card/mod.rs index fbb4bacfa..16f3d670d 100644 --- a/rslib/src/storage/card/mod.rs +++ b/rslib/src/storage/card/mod.rs @@ -229,12 +229,21 @@ impl super::SqliteStorage { } /// Call func() for each new card, stopping when it returns false - /// or no more cards found. Cards will arrive in (deck_id, due) order. - pub(crate) fn for_each_new_card_in_deck(&self, deck: DeckId, mut func: F) -> Result<()> + /// or no more cards found. + pub(crate) fn for_each_new_card_in_deck( + &self, + deck: DeckId, + reverse: bool, + mut func: F, + ) -> Result<()> where F: FnMut(NewCard) -> bool, { - let mut stmt = self.db.prepare_cached(include_str!("new_cards.sql"))?; + let mut stmt = self.db.prepare_cached(&format!( + "{}{}", + include_str!("new_cards.sql"), + if reverse { " order by due desc" } else { "" } + ))?; let mut rows = stmt.query(params![deck])?; while let Some(row) = rows.next()? { if !func(NewCard { diff --git a/ts/deckoptions/DisplayOrder.svelte b/ts/deckoptions/DisplayOrder.svelte index a93bf9ca1..0b31e12f6 100644 --- a/ts/deckoptions/DisplayOrder.svelte +++ b/ts/deckoptions/DisplayOrder.svelte @@ -15,12 +15,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html const newGatherPriorityChoices = [ tr.deckConfigNewGatherPriorityDeck(), - tr.deckConfigNewGatherPriorityPosition(), + tr.deckConfigNewGatherPriorityPositionLowestFirst(), + tr.deckConfigNewGatherPriorityPositionHighestFirst(), ]; const newSortOrderChoices = [ - tr.deckConfigSortOrderCardTemplateThenPosition(), + tr.deckConfigSortOrderCardTemplateThenLowestPosition(), + tr.deckConfigSortOrderCardTemplateThenHighestPosition(), tr.deckConfigSortOrderCardTemplateThenRandom(), - tr.deckConfigSortOrderPosition(), + tr.deckConfigSortOrderLowestPosition(), + tr.deckConfigSortOrderHighestPosition(), tr.deckConfigSortOrderRandom(), ]; const reviewOrderChoices = [