mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
handle default deck and filtered deck suppression in the backend
This commit is contained in:
parent
769bf04f75
commit
964a69e54e
15 changed files with 131 additions and 138 deletions
|
@ -84,7 +84,7 @@ message BackendInput {
|
||||||
Empty get_empty_cards = 70;
|
Empty get_empty_cards = 70;
|
||||||
int64 get_deck_legacy = 71;
|
int64 get_deck_legacy = 71;
|
||||||
string get_deck_id_by_name = 72;
|
string get_deck_id_by_name = 72;
|
||||||
Empty get_deck_names = 73;
|
GetDeckNamesIn get_deck_names = 73;
|
||||||
AddOrUpdateDeckLegacyIn add_or_update_deck_legacy = 74;
|
AddOrUpdateDeckLegacyIn add_or_update_deck_legacy = 74;
|
||||||
bool new_deck_legacy = 75;
|
bool new_deck_legacy = 75;
|
||||||
int64 remove_deck = 76;
|
int64 remove_deck = 76;
|
||||||
|
@ -784,3 +784,9 @@ message Preferences {
|
||||||
message ClozeNumbersInNoteOut {
|
message ClozeNumbersInNoteOut {
|
||||||
repeated uint32 numbers = 1;
|
repeated uint32 numbers = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message GetDeckNamesIn {
|
||||||
|
bool skip_empty_default = 1;
|
||||||
|
// if unset, implies skip_empty_default
|
||||||
|
bool include_filtered = 2;
|
||||||
|
}
|
||||||
|
|
|
@ -4,8 +4,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import unicodedata
|
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
|
||||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
||||||
|
|
||||||
import anki # pylint: disable=unused-import
|
import anki # pylint: disable=unused-import
|
||||||
import anki.backend_pb2 as pb
|
import anki.backend_pb2 as pb
|
||||||
|
@ -186,17 +185,13 @@ class DeckManager:
|
||||||
if did in self.active():
|
if did in self.active():
|
||||||
self.select(self.all_names_and_ids()[0].id)
|
self.select(self.all_names_and_ids()[0].id)
|
||||||
|
|
||||||
def allNames(self, dyn: bool = True, force_default: bool = True) -> List:
|
def all_names_and_ids(
|
||||||
"An unsorted list of all deck names."
|
self, skip_empty_default=False, include_filtered=True
|
||||||
if dyn:
|
) -> Sequence[pb.DeckNameID]:
|
||||||
return [x["name"] for x in self.all(force_default=force_default)]
|
"A sorted sequence of deck names and IDs."
|
||||||
else:
|
return self.col.backend.get_deck_names_and_ids(
|
||||||
return [
|
skip_empty_default, include_filtered
|
||||||
x["name"] for x in self.all(force_default=force_default) if not x["dyn"]
|
)
|
||||||
]
|
|
||||||
|
|
||||||
def all_names_and_ids(self) -> List[pb.DeckNameID]:
|
|
||||||
return self.col.backend.get_deck_names_and_ids()
|
|
||||||
|
|
||||||
def id_for_name(self, name: str) -> Optional[int]:
|
def id_for_name(self, name: str) -> Optional[int]:
|
||||||
return self.col.backend.get_deck_id_by_name(name)
|
return self.col.backend.get_deck_id_by_name(name)
|
||||||
|
@ -221,29 +216,28 @@ class DeckManager:
|
||||||
def deck_tree(self) -> pb.DeckTreeNode:
|
def deck_tree(self) -> pb.DeckTreeNode:
|
||||||
return self.col.backend.deck_tree(include_counts=False)
|
return self.col.backend.deck_tree(include_counts=False)
|
||||||
|
|
||||||
def all(self, force_default: bool = True) -> List:
|
def all(self) -> List:
|
||||||
"""A list of all decks.
|
"All decks. Expensive; prefer all_names_and_ids()"
|
||||||
|
return self.get_all_legacy()
|
||||||
list contains default deck if either:
|
|
||||||
* force_default is True
|
|
||||||
* there are no other deck
|
|
||||||
* default deck contains a card
|
|
||||||
* default deck has a child (assumed not to be the case if assume_no_child)
|
|
||||||
"""
|
|
||||||
decks = self.get_all_legacy()
|
|
||||||
if not force_default and not self.should_default_be_displayed(force_default):
|
|
||||||
decks = [deck for deck in decks if deck["id"] != 1]
|
|
||||||
return decks
|
|
||||||
|
|
||||||
def allIds(self) -> List[str]:
|
def allIds(self) -> List[str]:
|
||||||
|
print("decks.allIds() is deprecated, use .all_names_and_ids()")
|
||||||
return [str(x.id) for x in self.all_names_and_ids()]
|
return [str(x.id) for x in self.all_names_and_ids()]
|
||||||
|
|
||||||
|
def allNames(self, dyn: bool = True, force_default: bool = True) -> List:
|
||||||
|
print("decks.allNames() is deprecated, use .all_names_and_ids()")
|
||||||
|
return [
|
||||||
|
x.name
|
||||||
|
for x in self.all_names_and_ids(
|
||||||
|
skip_empty_default=not force_default, include_filtered=dyn
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
def collapse(self, did) -> None:
|
def collapse(self, did) -> None:
|
||||||
deck = self.get(did)
|
deck = self.get(did)
|
||||||
deck["collapsed"] = not deck["collapsed"]
|
deck["collapsed"] = not deck["collapsed"]
|
||||||
self.save(deck)
|
self.save(deck)
|
||||||
|
|
||||||
# fixme
|
|
||||||
def collapseBrowser(self, did) -> None:
|
def collapseBrowser(self, did) -> None:
|
||||||
deck = self.get(did)
|
deck = self.get(did)
|
||||||
collapsed = deck.get("browserCollapsed", False)
|
collapsed = deck.get("browserCollapsed", False)
|
||||||
|
@ -504,57 +498,6 @@ class DeckManager:
|
||||||
def for_card_ids(self, cids: List[int]) -> List[int]:
|
def for_card_ids(self, cids: List[int]) -> List[int]:
|
||||||
return self.col.db.list(f"select did from cards where id in {ids2str(cids)}")
|
return self.col.db.list(f"select did from cards where id in {ids2str(cids)}")
|
||||||
|
|
||||||
# fixme
|
|
||||||
def _recoverOrphans(self) -> None:
|
|
||||||
pass
|
|
||||||
# dids = list(self.decks.keys())
|
|
||||||
# mod = self.col.db.mod
|
|
||||||
# self.col.db.execute(
|
|
||||||
# "update cards set did = 1 where did not in " + ids2str(dids)
|
|
||||||
# )
|
|
||||||
# self.col.db.mod = mod
|
|
||||||
|
|
||||||
def checkIntegrity(self) -> None:
|
|
||||||
self._recoverOrphans()
|
|
||||||
|
|
||||||
def should_deck_be_displayed(
|
|
||||||
self, deck, force_default: bool = True, assume_no_child: bool = False
|
|
||||||
) -> bool:
|
|
||||||
"""Whether the deck should appear in main window, browser side list, filter, deck selection...
|
|
||||||
|
|
||||||
True, except for empty default deck without children"""
|
|
||||||
if deck["id"] != "1":
|
|
||||||
return True
|
|
||||||
return self.should_default_be_displayed(force_default, assume_no_child)
|
|
||||||
|
|
||||||
def should_default_be_displayed(
|
|
||||||
self,
|
|
||||||
force_default: bool = True,
|
|
||||||
assume_no_child: bool = False,
|
|
||||||
default_deck: Optional[Dict[str, Any]] = None,
|
|
||||||
) -> bool:
|
|
||||||
"""Whether the default deck should appear in main window, browser side list, filter, deck selection...
|
|
||||||
|
|
||||||
True, except for empty default deck (without children)"""
|
|
||||||
if force_default:
|
|
||||||
return True
|
|
||||||
if self.col.db.scalar("select 1 from cards where did = 1 limit 1"):
|
|
||||||
return True
|
|
||||||
# fixme
|
|
||||||
return False
|
|
||||||
# if len(self.all_names_and_ids()) == 1:
|
|
||||||
# return True
|
|
||||||
# # looking for children
|
|
||||||
# if assume_no_child:
|
|
||||||
# return False
|
|
||||||
# if default_deck is None:
|
|
||||||
# default_deck = self.get(1)
|
|
||||||
# defaultName = default_deck["name"]
|
|
||||||
# for name in self.allNames():
|
|
||||||
# if name.startswith(f"{defaultName}::"):
|
|
||||||
# return True
|
|
||||||
# return False
|
|
||||||
|
|
||||||
# Deck selection
|
# Deck selection
|
||||||
#############################################################
|
#############################################################
|
||||||
|
|
||||||
|
@ -673,11 +616,3 @@ class DeckManager:
|
||||||
|
|
||||||
def isDyn(self, did: Union[int, str]) -> Any:
|
def isDyn(self, did: Union[int, str]) -> Any:
|
||||||
return self.get(did)["dyn"]
|
return self.get(did)["dyn"]
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def normalizeName(name: str) -> str:
|
|
||||||
return unicodedata.normalize("NFC", name.lower())
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def equalName(name1: str, name2: str) -> bool:
|
|
||||||
return DeckManager.normalizeName(name1) == DeckManager.normalizeName(name2)
|
|
||||||
|
|
|
@ -686,12 +686,17 @@ class RustBackend:
|
||||||
except NotFoundError:
|
except NotFoundError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_deck_names_and_ids(self) -> List[pb.DeckNameID]:
|
def get_deck_names_and_ids(
|
||||||
return list(
|
self, skip_empty_default: bool, include_filtered: bool = True
|
||||||
self._run_command(
|
) -> Sequence[pb.DeckNameID]:
|
||||||
pb.BackendInput(get_deck_names=pb.Empty())
|
return self._run_command(
|
||||||
).get_deck_names.entries
|
pb.BackendInput(
|
||||||
)
|
get_deck_names=pb.GetDeckNamesIn(
|
||||||
|
skip_empty_default=skip_empty_default,
|
||||||
|
include_filtered=include_filtered,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).get_deck_names.entries
|
||||||
|
|
||||||
def add_or_update_deck_legacy(
|
def add_or_update_deck_legacy(
|
||||||
self, deck: Dict[str, Any], preserve_usn: bool
|
self, deck: Dict[str, Any], preserve_usn: bool
|
||||||
|
|
|
@ -395,7 +395,9 @@ limit %d"""
|
||||||
else:
|
else:
|
||||||
# benchmarks indicate it's about 10x faster to search all decks
|
# benchmarks indicate it's about 10x faster to search all decks
|
||||||
# with the index than scan the table
|
# with the index than scan the table
|
||||||
extra = " and did in " + ids2str(self.col.decks.allIds())
|
extra = " and did in " + ids2str(
|
||||||
|
d.id for d in self.col.decks.all_names_and_ids()
|
||||||
|
)
|
||||||
# review cards in relearning
|
# review cards in relearning
|
||||||
self.col.db.execute(
|
self.col.db.execute(
|
||||||
f"""
|
f"""
|
||||||
|
|
|
@ -65,9 +65,10 @@ def test_rename():
|
||||||
# should be able to rename into a completely different branch, creating
|
# should be able to rename into a completely different branch, creating
|
||||||
# parents as necessary
|
# parents as necessary
|
||||||
d.decks.rename(d.decks.get(id), "foo::bar")
|
d.decks.rename(d.decks.get(id), "foo::bar")
|
||||||
assert "foo" in d.decks.allNames()
|
names = [n.name for n in d.decks.all_names_and_ids()]
|
||||||
assert "foo::bar" in d.decks.allNames()
|
assert "foo" in names
|
||||||
assert "hello::world" not in d.decks.allNames()
|
assert "foo::bar" in names
|
||||||
|
assert "hello::world" not in names
|
||||||
# create another deck
|
# create another deck
|
||||||
id = d.decks.id("tmp")
|
id = d.decks.id("tmp")
|
||||||
# we can't rename it if it conflicts
|
# we can't rename it if it conflicts
|
||||||
|
@ -76,8 +77,9 @@ def test_rename():
|
||||||
d.decks.id("one::two::three")
|
d.decks.id("one::two::three")
|
||||||
id = d.decks.id("one")
|
id = d.decks.id("one")
|
||||||
d.decks.rename(d.decks.get(id), "yo")
|
d.decks.rename(d.decks.get(id), "yo")
|
||||||
|
names = [n.name for n in d.decks.all_names_and_ids()]
|
||||||
for n in "yo", "yo::two", "yo::two::three":
|
for n in "yo", "yo::two", "yo::two::three":
|
||||||
assert n in d.decks.allNames()
|
assert n in names
|
||||||
# over filtered
|
# over filtered
|
||||||
filteredId = d.decks.newDyn("filtered")
|
filteredId = d.decks.newDyn("filtered")
|
||||||
filtered = d.decks.get(filteredId)
|
filtered = d.decks.get(filteredId)
|
||||||
|
@ -96,7 +98,7 @@ def test_renameForDragAndDrop():
|
||||||
d = getEmptyCol()
|
d = getEmptyCol()
|
||||||
|
|
||||||
def deckNames():
|
def deckNames():
|
||||||
return [name for name in sorted(d.decks.allNames()) if name != "Default"]
|
return [n.name for n in d.decks.all_names_and_ids(skip_empty_default=True)]
|
||||||
|
|
||||||
languages_did = d.decks.id("Languages")
|
languages_did = d.decks.id("Languages")
|
||||||
chinese_did = d.decks.id("Chinese")
|
chinese_did = d.decks.id("Chinese")
|
||||||
|
@ -151,16 +153,3 @@ def test_renameForDragAndDrop():
|
||||||
# '' is a convenient alias for the top level DID
|
# '' is a convenient alias for the top level DID
|
||||||
d.decks.renameForDragAndDrop(hsk_did, "")
|
d.decks.renameForDragAndDrop(hsk_did, "")
|
||||||
assert deckNames() == ["Chinese", "HSK", "Languages"]
|
assert deckNames() == ["Chinese", "HSK", "Languages"]
|
||||||
|
|
||||||
|
|
||||||
def test_check():
|
|
||||||
d = getEmptyCol()
|
|
||||||
|
|
||||||
# currently disabled - see 5418af00f733ca62b0c087d1422feae01d6571b0
|
|
||||||
# foo_did = d.decks.id("foo")
|
|
||||||
# FOO_did = d.decks.id("bar")
|
|
||||||
# FOO = d.decks.byName("bar")
|
|
||||||
# FOO["name"] = "FOO"
|
|
||||||
# d.decks.save(FOO)
|
|
||||||
# d.decks._checkDeckTree()
|
|
||||||
# assert "foo" not in d.decks.allNames() or "FOO" not in d.decks.allNames()
|
|
||||||
|
|
|
@ -441,9 +441,9 @@ def test_review_limits():
|
||||||
c.flush()
|
c.flush()
|
||||||
|
|
||||||
tree = d.sched.deckDueTree()
|
tree = d.sched.deckDueTree()
|
||||||
# (('Default', 1, 0, 0, 0, ()), ('parent', 1514457677462, 5, 0, 0, (('child', 1514457677463, 5, 0, 0, ()),)))
|
# (('parent', 1514457677462, 5, 0, 0, (('child', 1514457677463, 5, 0, 0, ()),)))
|
||||||
assert tree[1][2] == 5 # parent
|
assert tree[0][2] == 5 # parent
|
||||||
assert tree[1][5][0][2] == 5 # child
|
assert tree[0][5][0][2] == 5 # child
|
||||||
|
|
||||||
# .counts() should match
|
# .counts() should match
|
||||||
d.decks.select(child["id"])
|
d.decks.select(child["id"])
|
||||||
|
@ -456,8 +456,8 @@ def test_review_limits():
|
||||||
assert d.sched.counts() == (0, 0, 4)
|
assert d.sched.counts() == (0, 0, 4)
|
||||||
|
|
||||||
tree = d.sched.deckDueTree()
|
tree = d.sched.deckDueTree()
|
||||||
assert tree[1][2] == 4 # parent
|
assert tree[0][2] == 4 # parent
|
||||||
assert tree[1][5][0][2] == 4 # child
|
assert tree[0][5][0][2] == 4 # child
|
||||||
|
|
||||||
|
|
||||||
def test_button_spacing():
|
def test_button_spacing():
|
||||||
|
|
|
@ -1148,11 +1148,6 @@ QTableView {{ gridline-color: {grid} }}
|
||||||
|
|
||||||
def fillGroups(root, nodes: Sequence[DeckTreeNode], head=""):
|
def fillGroups(root, nodes: Sequence[DeckTreeNode], head=""):
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
if node.deck_id == 1 and not node.children:
|
|
||||||
if not self.mw.col.decks.should_default_be_displayed(
|
|
||||||
force_default=False, assume_no_child=True
|
|
||||||
):
|
|
||||||
continue
|
|
||||||
|
|
||||||
def set_filter():
|
def set_filter():
|
||||||
full_name = head + node.name # pylint: disable=cell-var-from-loop
|
full_name = head + node.name # pylint: disable=cell-var-from-loop
|
||||||
|
|
|
@ -49,7 +49,8 @@ class ExportDialog(QDialog):
|
||||||
self.exporterChanged(idx)
|
self.exporterChanged(idx)
|
||||||
# deck list
|
# deck list
|
||||||
if self.cids is None:
|
if self.cids is None:
|
||||||
self.decks = [_("All Decks")] + sorted(self.col.decks.allNames())
|
self.decks = [_("All Decks")]
|
||||||
|
self.decks.extend(d.name for d in self.col.decks.all_names_and_ids())
|
||||||
else:
|
else:
|
||||||
self.decks = [_("Selected Notes")]
|
self.decks = [_("Selected Notes")]
|
||||||
self.frm.deck.addItems(self.decks)
|
self.frm.deck.addItems(self.decks)
|
||||||
|
|
|
@ -1115,8 +1115,7 @@ title="%s" %s>%s</button>""" % (
|
||||||
if not search:
|
if not search:
|
||||||
if not deck["dyn"]:
|
if not deck["dyn"]:
|
||||||
search = 'deck:"%s" ' % deck["name"]
|
search = 'deck:"%s" ' % deck["name"]
|
||||||
decks = self.col.decks.allNames()
|
while self.col.decks.id_for_name(_("Filtered Deck %d") % n):
|
||||||
while _("Filtered Deck %d") % n in decks:
|
|
||||||
n += 1
|
n += 1
|
||||||
name = _("Filtered Deck %d") % n
|
name = _("Filtered Deck %d") % n
|
||||||
did = self.col.decks.newDyn(name)
|
did = self.col.decks.newDyn(name)
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
from anki.decks import DeckManager
|
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from aqt import gui_hooks
|
from aqt import gui_hooks
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
|
@ -52,10 +51,12 @@ class StudyDeck(QDialog):
|
||||||
if title:
|
if title:
|
||||||
self.setWindowTitle(title)
|
self.setWindowTitle(title)
|
||||||
if not names:
|
if not names:
|
||||||
names = sorted(
|
names = [
|
||||||
self.mw.col.decks.allNames(dyn=dyn, force_default=False),
|
d.name
|
||||||
key=DeckManager._path,
|
for d in self.mw.col.decks.all_names_and_ids(
|
||||||
)
|
include_filtered=dyn, skip_empty_default=True
|
||||||
|
)
|
||||||
|
]
|
||||||
self.nameFunc = None
|
self.nameFunc = None
|
||||||
self.origNames = names
|
self.origNames = names
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -32,9 +32,9 @@ class TagEdit(QLineEdit):
|
||||||
"Set the current col, updating list of available tags."
|
"Set the current col, updating list of available tags."
|
||||||
self.col = col
|
self.col = col
|
||||||
if self.type == 0:
|
if self.type == 0:
|
||||||
l = sorted(self.col.tags.all())
|
l = self.col.tags.all()
|
||||||
else:
|
else:
|
||||||
l = sorted(self.col.decks.allNames())
|
l = (d.name for d in self.col.decks.all_names_and_ids())
|
||||||
self.model.setStringList(l)
|
self.model.setStringList(l)
|
||||||
|
|
||||||
def focusInEvent(self, evt):
|
def focusInEvent(self, evt):
|
||||||
|
|
|
@ -341,7 +341,7 @@ impl Backend {
|
||||||
Value::GetDeckIdByName(name) => {
|
Value::GetDeckIdByName(name) => {
|
||||||
OValue::GetDeckIdByName(self.get_deck_id_by_name(&name)?)
|
OValue::GetDeckIdByName(self.get_deck_id_by_name(&name)?)
|
||||||
}
|
}
|
||||||
Value::GetDeckNames(_) => OValue::GetDeckNames(self.get_deck_names()?),
|
Value::GetDeckNames(input) => OValue::GetDeckNames(self.get_deck_names(input)?),
|
||||||
Value::AddOrUpdateDeckLegacy(input) => {
|
Value::AddOrUpdateDeckLegacy(input) => {
|
||||||
OValue::AddOrUpdateDeckLegacy(self.add_or_update_deck_legacy(input)?)
|
OValue::AddOrUpdateDeckLegacy(self.add_or_update_deck_legacy(input)?)
|
||||||
}
|
}
|
||||||
|
@ -1011,9 +1011,13 @@ impl Backend {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_deck_names(&self) -> Result<pb::DeckNames> {
|
fn get_deck_names(&self, input: pb::GetDeckNamesIn) -> Result<pb::DeckNames> {
|
||||||
self.with_col(|col| {
|
self.with_col(|col| {
|
||||||
let names = col.storage.get_all_deck_names()?;
|
let names = if input.include_filtered {
|
||||||
|
col.get_all_deck_names(input.skip_empty_default)?
|
||||||
|
} else {
|
||||||
|
col.get_all_normal_deck_names()?
|
||||||
|
};
|
||||||
Ok(pb::DeckNames {
|
Ok(pb::DeckNames {
|
||||||
entries: names
|
entries: names
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
|
@ -187,6 +187,10 @@ fn immediate_parent_name(machine_name: &str) -> Option<&str> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Collection {
|
impl Collection {
|
||||||
|
pub(crate) fn default_deck_is_empty(&self) -> Result<bool> {
|
||||||
|
self.storage.deck_is_empty(DeckID(1))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn add_or_update_deck(&mut self, deck: &mut Deck, preserve_usn: bool) -> Result<()> {
|
pub(crate) fn add_or_update_deck(&mut self, deck: &mut Deck, preserve_usn: bool) -> Result<()> {
|
||||||
// fixme: vet cache clearing
|
// fixme: vet cache clearing
|
||||||
self.state.deck_cache.clear();
|
self.state.deck_cache.clear();
|
||||||
|
@ -416,6 +420,32 @@ impl Collection {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_all_deck_names(&self, skip_empty_default: bool) -> Result<Vec<(DeckID, String)>> {
|
||||||
|
if skip_empty_default && self.default_deck_is_empty()? {
|
||||||
|
Ok(self
|
||||||
|
.storage
|
||||||
|
.get_all_deck_names()?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|(id, _name)| id.0 != 1)
|
||||||
|
.collect())
|
||||||
|
} else {
|
||||||
|
self.storage.get_all_deck_names()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_all_normal_deck_names(&mut self) -> Result<Vec<(DeckID, String)>> {
|
||||||
|
Ok(self
|
||||||
|
.storage
|
||||||
|
.get_all_deck_names()?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|(id, _name)| id.0 != 1)
|
||||||
|
.filter(|(id, _name)| match self.get_deck(*id) {
|
||||||
|
Ok(Some(deck)) => !deck.is_filtered(),
|
||||||
|
_ => true,
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -151,6 +151,21 @@ fn remaining_counts_for_deck(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hide_default_deck(node: &mut DeckTreeNode) {
|
||||||
|
for (idx, child) in node.children.iter().enumerate() {
|
||||||
|
// we can hide the default if it has no children
|
||||||
|
if child.deck_id == 1 && child.children.is_empty() {
|
||||||
|
if child.level == 1 && node.children.len() == 1 {
|
||||||
|
// can't remove if there are no other decks
|
||||||
|
} else {
|
||||||
|
// safe to remove
|
||||||
|
node.children.remove(idx);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize_tuple)]
|
#[derive(Serialize_tuple)]
|
||||||
pub(crate) struct LegacyDueCounts {
|
pub(crate) struct LegacyDueCounts {
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -187,6 +202,9 @@ impl Collection {
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
add_collapsed(&mut tree, &decks_map, !counts);
|
add_collapsed(&mut tree, &decks_map, !counts);
|
||||||
|
if self.default_deck_is_empty()? {
|
||||||
|
hide_default_deck(&mut tree);
|
||||||
|
}
|
||||||
|
|
||||||
if counts {
|
if counts {
|
||||||
let counts = self.due_counts()?;
|
let counts = self.due_counts()?;
|
||||||
|
@ -251,8 +269,7 @@ mod test {
|
||||||
|
|
||||||
let tree = col.deck_tree(false)?;
|
let tree = col.deck_tree(false)?;
|
||||||
|
|
||||||
// 4 including default
|
assert_eq!(tree.children.len(), 3);
|
||||||
assert_eq!(tree.children.len(), 4);
|
|
||||||
|
|
||||||
assert_eq!(tree.children[1].name, "2");
|
assert_eq!(tree.children[1].name, "2");
|
||||||
assert_eq!(tree.children[1].children[0].name, "a");
|
assert_eq!(tree.children[1].children[0].name, "a");
|
||||||
|
@ -274,7 +291,7 @@ mod test {
|
||||||
col.storage.remove_deck(col.get_deck_id("2::3")?.unwrap())?;
|
col.storage.remove_deck(col.get_deck_id("2::3")?.unwrap())?;
|
||||||
|
|
||||||
let tree = col.deck_tree(false)?;
|
let tree = col.deck_tree(false)?;
|
||||||
assert_eq!(tree.children.len(), 2);
|
assert_eq!(tree.children.len(), 1);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -178,6 +178,15 @@ impl SqliteStorage {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn deck_is_empty(&self, did: DeckID) -> Result<bool> {
|
||||||
|
self.db
|
||||||
|
.prepare_cached("select null from cards where did=?")?
|
||||||
|
.query(&[did])?
|
||||||
|
.next()
|
||||||
|
.map(|o| o.is_none())
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
// Upgrading/downgrading/legacy
|
// Upgrading/downgrading/legacy
|
||||||
|
|
||||||
pub(super) fn add_default_deck(&self, i18n: &I18n) -> Result<()> {
|
pub(super) fn add_default_deck(&self, i18n: &I18n) -> Result<()> {
|
||||||
|
|
Loading…
Reference in a new issue