mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Allow applying limits of inactive parents (#2824)
* Allow applying limits of inactive parents * Tweak label/help text (dae)
This commit is contained in:
parent
2c83ec9e14
commit
39a60bc3a4
10 changed files with 83 additions and 14 deletions
|
@ -44,6 +44,11 @@ deck-config-new-cards-ignore-review-limit-tooltip =
|
|||
By default, the review limit also applies to new cards, and no new cards will be
|
||||
shown when the review limit has been reached. If this option is enabled, new cards
|
||||
will be shown regardless of the review limit.
|
||||
deck-config-apply-all-parent-limits = Limits start from top
|
||||
deck-config-apply-all-parent-limits-tooltip =
|
||||
By default, limits start from the deck you select. If this option is enabled, the limits will
|
||||
start from the top-level deck instead, which can be useful if you wish to study individual
|
||||
sub-decks, while enforcing a total limit on cards/day.
|
||||
deck-config-affects-entire-collection = Affects the entire collection.
|
||||
|
||||
## Daily limit tabs: please try to keep these as short as the English version,
|
||||
|
|
|
@ -196,6 +196,7 @@ message DeckConfigsForUpdate {
|
|||
// only applies to v3 scheduler
|
||||
bool new_cards_ignore_review_limit = 7;
|
||||
bool fsrs = 8;
|
||||
bool apply_all_parent_limits = 9;
|
||||
}
|
||||
|
||||
message UpdateDeckConfigsRequest {
|
||||
|
@ -209,4 +210,5 @@ message UpdateDeckConfigsRequest {
|
|||
DeckConfigsForUpdate.CurrentDeck.Limits limits = 6;
|
||||
bool new_cards_ignore_review_limit = 7;
|
||||
bool fsrs = 8;
|
||||
bool apply_all_parent_limits = 9;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ use crate::prelude::*;
|
|||
#[derive(Debug, Clone, Copy, IntoStaticStr)]
|
||||
#[strum(serialize_all = "camelCase")]
|
||||
pub enum BoolKey {
|
||||
ApplyAllParentLimits,
|
||||
BrowserTableShowNotesMode,
|
||||
CardCountsSeparateInactive,
|
||||
CollapseCardState,
|
||||
|
|
|
@ -102,6 +102,7 @@ impl From<anki_proto::deck_config::UpdateDeckConfigsRequest> for UpdateDeckConfi
|
|||
card_state_customizer: c.card_state_customizer,
|
||||
limits: c.limits.unwrap_or_default(),
|
||||
new_cards_ignore_review_limit: c.new_cards_ignore_review_limit,
|
||||
apply_all_parent_limits: c.apply_all_parent_limits,
|
||||
fsrs: c.fsrs,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ pub struct UpdateDeckConfigsRequest {
|
|||
pub card_state_customizer: String,
|
||||
pub limits: Limits,
|
||||
pub new_cards_ignore_review_limit: bool,
|
||||
pub apply_all_parent_limits: bool,
|
||||
pub fsrs: bool,
|
||||
}
|
||||
|
||||
|
@ -52,6 +53,7 @@ impl Collection {
|
|||
.schema_changed_since_sync(),
|
||||
card_state_customizer: self.get_config_string(StringKey::CardStateCustomizer),
|
||||
new_cards_ignore_review_limit: self.get_config_bool(BoolKey::NewCardsIgnoreReviewLimit),
|
||||
apply_all_parent_limits: self.get_config_bool(BoolKey::ApplyAllParentLimits),
|
||||
fsrs: self.get_config_bool(BoolKey::Fsrs),
|
||||
})
|
||||
}
|
||||
|
@ -255,6 +257,7 @@ impl Collection {
|
|||
BoolKey::NewCardsIgnoreReviewLimit,
|
||||
req.new_cards_ignore_review_limit,
|
||||
)?;
|
||||
self.set_config_bool_inner(BoolKey::ApplyAllParentLimits, req.apply_all_parent_limits)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -350,6 +353,7 @@ mod test {
|
|||
// add the keys so it doesn't trigger a change below
|
||||
col.set_config_string_inner(StringKey::CardStateCustomizer, "")?;
|
||||
col.set_config_bool_inner(BoolKey::NewCardsIgnoreReviewLimit, false)?;
|
||||
col.set_config_bool_inner(BoolKey::ApplyAllParentLimits, false)?;
|
||||
|
||||
// pretend we're in sync
|
||||
let stamps = col.storage.get_collection_timestamps()?;
|
||||
|
@ -383,6 +387,7 @@ mod test {
|
|||
card_state_customizer: "".to_string(),
|
||||
limits: Limits::default(),
|
||||
new_cards_ignore_review_limit: false,
|
||||
apply_all_parent_limits: false,
|
||||
fsrs: false,
|
||||
};
|
||||
assert!(!col.update_deck_configs(input.clone())?.changes.had_change());
|
||||
|
|
|
@ -229,25 +229,24 @@ pub(crate) struct LimitTreeMap {
|
|||
}
|
||||
|
||||
impl LimitTreeMap {
|
||||
/// Child [Deck]s must be sorted by name.
|
||||
/// [Deck]s must be sorted by name.
|
||||
pub(crate) fn build(
|
||||
root_deck: &Deck,
|
||||
child_decks: Vec<Deck>,
|
||||
decks: &[Deck],
|
||||
config: &HashMap<DeckConfigId, DeckConfig>,
|
||||
today: u32,
|
||||
new_cards_ignore_review_limit: bool,
|
||||
) -> Self {
|
||||
let root_limits = NodeLimits::new(root_deck, config, today, new_cards_ignore_review_limit);
|
||||
let root_limits = NodeLimits::new(&decks[0], config, today, new_cards_ignore_review_limit);
|
||||
let mut tree = Tree::new();
|
||||
let root_id = tree
|
||||
.insert(Node::new(root_limits), InsertBehavior::AsRoot)
|
||||
.unwrap();
|
||||
|
||||
let mut map = HashMap::new();
|
||||
map.insert(root_deck.id, root_id.clone());
|
||||
map.insert(decks[0].id, root_id.clone());
|
||||
|
||||
let mut limits = Self { tree, map };
|
||||
let mut remaining_decks = child_decks.into_iter().peekable();
|
||||
let mut remaining_decks = decks[1..].iter().peekable();
|
||||
limits.add_child_nodes(
|
||||
root_id,
|
||||
&mut remaining_decks,
|
||||
|
@ -264,10 +263,10 @@ impl LimitTreeMap {
|
|||
/// Given [Deck]s are assumed to arrive in depth-first order.
|
||||
/// The tree-from-deck-list logic is taken from
|
||||
/// [crate::decks::tree::add_child_nodes].
|
||||
fn add_child_nodes(
|
||||
fn add_child_nodes<'d>(
|
||||
&mut self,
|
||||
parent_node_id: NodeId,
|
||||
remaining_decks: &mut Peekable<impl Iterator<Item = Deck>>,
|
||||
remaining_decks: &mut Peekable<impl Iterator<Item = &'d Deck>>,
|
||||
config: &HashMap<DeckConfigId, DeckConfig>,
|
||||
today: u32,
|
||||
new_cards_ignore_review_limit: bool,
|
||||
|
|
|
@ -142,11 +142,16 @@ impl AddAssign for NodeCountsV3 {
|
|||
fn sum_counts_and_apply_limits_v3(
|
||||
node: &mut DeckTreeNode,
|
||||
limits: &HashMap<DeckId, RemainingLimits>,
|
||||
mut parent_limits: Option<RemainingLimits>,
|
||||
) -> NodeCountsV3 {
|
||||
let remaining = limits
|
||||
let mut remaining = limits
|
||||
.get(&DeckId(node.deck_id))
|
||||
.copied()
|
||||
.unwrap_or_default();
|
||||
if let Some(parent_remaining) = parent_limits {
|
||||
remaining.cap_to(parent_remaining);
|
||||
parent_limits.replace(remaining);
|
||||
}
|
||||
|
||||
// initialize with this node's values
|
||||
let mut this_node_uncapped = NodeCountsV3 {
|
||||
|
@ -160,7 +165,7 @@ fn sum_counts_and_apply_limits_v3(
|
|||
|
||||
// add capped child counts / uncapped total
|
||||
for child in &mut node.children {
|
||||
this_node_uncapped += sum_counts_and_apply_limits_v3(child, limits);
|
||||
this_node_uncapped += sum_counts_and_apply_limits_v3(child, limits, parent_limits);
|
||||
total_including_children += child.total_including_children;
|
||||
}
|
||||
|
||||
|
@ -266,6 +271,9 @@ impl Collection {
|
|||
let learn_cutoff = (timestamp.0 as u32) + self.learn_ahead_secs();
|
||||
let new_cards_ignore_review_limit =
|
||||
self.get_config_bool(BoolKey::NewCardsIgnoreReviewLimit);
|
||||
let parent_limits = self
|
||||
.get_config_bool(BoolKey::ApplyAllParentLimits)
|
||||
.then(Default::default);
|
||||
let counts = self.due_counts(days_elapsed, learn_cutoff)?;
|
||||
let dconf = self.storage.get_deck_config_map()?;
|
||||
add_counts(&mut tree, &counts);
|
||||
|
@ -275,7 +283,7 @@ impl Collection {
|
|||
days_elapsed,
|
||||
new_cards_ignore_review_limit,
|
||||
);
|
||||
sum_counts_and_apply_limits_v3(&mut tree, &limits);
|
||||
sum_counts_and_apply_limits_v3(&mut tree, &limits, parent_limits);
|
||||
}
|
||||
|
||||
Ok(tree)
|
||||
|
|
|
@ -125,12 +125,18 @@ impl QueueBuilder {
|
|||
pub(super) fn new(col: &mut Collection, deck_id: DeckId) -> Result<Self> {
|
||||
let timing = col.timing_for_timestamp(TimestampSecs::now())?;
|
||||
let new_cards_ignore_review_limit = col.get_config_bool(BoolKey::NewCardsIgnoreReviewLimit);
|
||||
let apply_all_parent_limits = col.get_config_bool(BoolKey::ApplyAllParentLimits);
|
||||
let config_map = col.storage.get_deck_config_map()?;
|
||||
let root_deck = col.storage.get_deck(deck_id)?.or_not_found(deck_id)?;
|
||||
let child_decks = col.storage.child_decks(&root_deck)?;
|
||||
let mut decks = col.storage.child_decks(&root_deck)?;
|
||||
decks.insert(0, root_deck.clone());
|
||||
if apply_all_parent_limits {
|
||||
for parent in col.storage.parent_decks(&root_deck)? {
|
||||
decks.insert(0, parent);
|
||||
}
|
||||
}
|
||||
let limits = LimitTreeMap::build(
|
||||
&root_deck,
|
||||
child_decks,
|
||||
&decks,
|
||||
&config_map,
|
||||
timing.days_elapsed,
|
||||
new_cards_ignore_review_limit,
|
||||
|
@ -502,4 +508,20 @@ mod test {
|
|||
CardAdder::new().siblings(2).due_dates(["0"]).add(&mut col);
|
||||
assert_eq!(col.card_queue_len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn may_apply_parent_limits() {
|
||||
let mut col = Collection::new_v3();
|
||||
col.set_config_bool(BoolKey::ApplyAllParentLimits, true, false)
|
||||
.unwrap();
|
||||
col.update_default_deck_config(|config| {
|
||||
config.new_per_day = 0;
|
||||
});
|
||||
let child = DeckAdder::new("Default::child")
|
||||
.with_config(|_| ())
|
||||
.add(&mut col);
|
||||
CardAdder::new().deck(child.id).add(&mut col);
|
||||
col.set_current_deck(child.id).unwrap();
|
||||
assert_eq!(col.card_queue_len(), 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
const limits = state.deckLimits;
|
||||
const defaults = state.defaults;
|
||||
const newCardsIgnoreReviewLimit = state.newCardsIgnoreReviewLimit;
|
||||
const applyAllParentLimits = state.applyAllParentLimits;
|
||||
|
||||
const v3Extra =
|
||||
"\n\n" + tr.deckConfigLimitDeckV3() + "\n\n" + tr.deckConfigTabDescription();
|
||||
|
@ -53,6 +54,10 @@
|
|||
tr.deckConfigAffectsEntireCollection() +
|
||||
"\n\n" +
|
||||
tr.deckConfigNewCardsIgnoreReviewLimitTooltip();
|
||||
const applyAllParentLimitsHelp =
|
||||
tr.deckConfigAffectsEntireCollection() +
|
||||
"\n\n" +
|
||||
tr.deckConfigApplyAllParentLimitsTooltip();
|
||||
|
||||
$: reviewsTooLow =
|
||||
Math.min(9999, newValue * 10) > reviewsValue
|
||||
|
@ -129,6 +134,11 @@
|
|||
help: newCardsIgnoreReviewLimitHelp,
|
||||
url: HelpPage.DeckOptions.newCardsday,
|
||||
},
|
||||
applyAllParentLimits: {
|
||||
title: tr.deckConfigApplyAllParentLimits(),
|
||||
help: applyAllParentLimitsHelp,
|
||||
url: HelpPage.DeckOptions.newCardsday,
|
||||
},
|
||||
};
|
||||
const helpSections = Object.values(settings) as HelpItem[];
|
||||
|
||||
|
@ -193,5 +203,18 @@
|
|||
</SettingTitle>
|
||||
</SwitchRow>
|
||||
</Item>
|
||||
|
||||
<Item>
|
||||
<SwitchRow bind:value={$applyAllParentLimits} defaultValue={false}>
|
||||
<SettingTitle
|
||||
on:click={() =>
|
||||
openHelpModal(
|
||||
Object.keys(settings).indexOf("applyAllParentLimits"),
|
||||
)}
|
||||
>
|
||||
{settings.applyAllParentLimits.title}
|
||||
</SettingTitle>
|
||||
</SwitchRow>
|
||||
</Item>
|
||||
</DynamicallySlottable>
|
||||
</TitledContainer>
|
||||
|
|
|
@ -41,6 +41,7 @@ export class DeckOptionsState {
|
|||
readonly defaults: DeckConfig_Config;
|
||||
readonly addonComponents: Writable<DynamicSvelteComponent[]>;
|
||||
readonly newCardsIgnoreReviewLimit: Writable<boolean>;
|
||||
readonly applyAllParentLimits: Writable<boolean>;
|
||||
readonly fsrs: Writable<boolean>;
|
||||
readonly currentPresetName: Writable<string>;
|
||||
|
||||
|
@ -73,6 +74,7 @@ export class DeckOptionsState {
|
|||
this.cardStateCustomizer = writable(data.cardStateCustomizer);
|
||||
this.deckLimits = writable(data.currentDeck?.limits ?? createLimits());
|
||||
this.newCardsIgnoreReviewLimit = writable(data.newCardsIgnoreReviewLimit);
|
||||
this.applyAllParentLimits = writable(data.applyAllParentLimits);
|
||||
this.fsrs = writable(data.fsrs);
|
||||
|
||||
// decrement the use count of the starting item, as we'll apply +1 to currently
|
||||
|
@ -199,6 +201,7 @@ export class DeckOptionsState {
|
|||
cardStateCustomizer: get(this.cardStateCustomizer),
|
||||
limits: get(this.deckLimits),
|
||||
newCardsIgnoreReviewLimit: get(this.newCardsIgnoreReviewLimit),
|
||||
applyAllParentLimits: get(this.applyAllParentLimits),
|
||||
fsrs: get(this.fsrs),
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue