diff --git a/ftl/core/decks.ftl b/ftl/core/decks.ftl index b2ae2655e..a3777645d 100644 --- a/ftl/core/decks.ftl +++ b/ftl/core/decks.ftl @@ -2,6 +2,7 @@ decks-add-new-deck-ctrlandn = Add New Deck (Ctrl+N) decks-build = Build decks-cards-selected-by = cards selected by decks-create-deck = Create Deck +decks_create_even_if_empty = Create/update this deck even if empty decks-custom-steps-in-minutes = Custom steps (in minutes) decks-deck = Deck decks-decreasing-intervals = Decreasing intervals diff --git a/proto/anki/decks.proto b/proto/anki/decks.proto index 6ecd55ad8..aff2a9d21 100644 --- a/proto/anki/decks.proto +++ b/proto/anki/decks.proto @@ -212,4 +212,5 @@ message FilteredDeckForUpdate { int64 id = 1; string name = 2; Deck.Filtered config = 3; + bool allow_empty = 4; } diff --git a/qt/aqt/filtered_deck.py b/qt/aqt/filtered_deck.py index 489928743..af87b2e6d 100644 --- a/qt/aqt/filtered_deck.py +++ b/qt/aqt/filtered_deck.py @@ -79,6 +79,8 @@ class FilteredDeckConfigDialog(QDialog): self.form.order.addItems(order_labels) self.form.order_2.addItems(order_labels) + qconnect(self.form.allow_empty.stateChanged, self._on_allow_empty_toggled) + qconnect(self.form.resched.stateChanged, self._onReschedToggled) qconnect(self.form.search_button.clicked, self.on_search_button) @@ -233,6 +235,9 @@ class FilteredDeckConfigDialog(QDialog): def _onReschedToggled(self, _state: int) -> None: self.form.previewDelayWidget.setVisible(not self.form.resched.isChecked()) + def _on_allow_empty_toggled(self) -> None: + self.deck.allow_empty = self.form.allow_empty.isChecked() + def _update_deck(self) -> bool: """Update our stored deck with the details from the GUI. If false, abort adding.""" diff --git a/qt/aqt/forms/filtered_deck.ui b/qt/aqt/forms/filtered_deck.ui index 87277b4f5..40e7bb8d8 100644 --- a/qt/aqt/forms/filtered_deck.ui +++ b/qt/aqt/forms/filtered_deck.ui @@ -255,6 +255,13 @@ + + + + decks_create_even_if_empty + + + diff --git a/rslib/src/decks/service.rs b/rslib/src/decks/service.rs index f4b51b4e5..b5ef2a494 100644 --- a/rslib/src/decks/service.rs +++ b/rslib/src/decks/service.rs @@ -251,6 +251,7 @@ impl From for anki_proto::decks::FilteredDeckForUpdate { id: deck.id.into(), name: deck.human_name, config: Some(deck.config), + allow_empty: deck.allow_empty, } } } @@ -261,6 +262,7 @@ impl From for FilteredDeckForUpdate { id: deck.id.into(), human_name: deck.name, config: deck.config.unwrap_or_default(), + allow_empty: deck.allow_empty, } } } diff --git a/rslib/src/scheduler/filtered/custom_study.rs b/rslib/src/scheduler/filtered/custom_study.rs index 32a32950d..4b8c16700 100644 --- a/rslib/src/scheduler/filtered/custom_study.rs +++ b/rslib/src/scheduler/filtered/custom_study.rs @@ -184,6 +184,7 @@ impl Collection { id, human_name, config, + allow_empty: false, }; self.add_or_update_filtered_deck_inner(deck) diff --git a/rslib/src/scheduler/filtered/mod.rs b/rslib/src/scheduler/filtered/mod.rs index 5a158dff8..60083f6ac 100644 --- a/rslib/src/scheduler/filtered/mod.rs +++ b/rslib/src/scheduler/filtered/mod.rs @@ -21,6 +21,7 @@ pub struct FilteredDeckForUpdate { pub id: DeckId, pub human_name: String, pub config: FilteredDeck, + pub allow_empty: bool, } pub(crate) struct DeckFilterContext<'a> { @@ -144,6 +145,7 @@ impl Collection { mut update: FilteredDeckForUpdate, ) -> Result { let usn = self.usn()?; + let allow_empty = update.allow_empty; // check the searches are valid, and normalize them for term in &mut update.config.search_terms { @@ -167,7 +169,7 @@ impl Collection { let count = self.rebuild_filtered_deck_inner(&deck, usn)?; // if it failed to match any cards, we revert the changes - if count == 0 { + if count == 0 && !allow_empty { Err(FilteredDeckError::SearchReturnedNoCards.into()) } else { // update current deck and return id @@ -233,6 +235,7 @@ impl TryFrom for FilteredDeckForUpdate { id: value.id, human_name, config: filtered, + allow_empty: false, }), _ => invalid_input!("not filtered"), }