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"),
}