mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
switch to new deck tree in deck browser
Saves us having to look up collapsed/filtered as we render, and gives us type completion.
This commit is contained in:
parent
75d3eebafe
commit
f2086fc2e3
5 changed files with 69 additions and 44 deletions
|
@ -264,6 +264,8 @@ message DeckTreeNode {
|
||||||
uint32 review_count = 6;
|
uint32 review_count = 6;
|
||||||
uint32 learn_count = 7;
|
uint32 learn_count = 7;
|
||||||
uint32 new_count = 8;
|
uint32 new_count = 8;
|
||||||
|
|
||||||
|
bool filtered = 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
message RenderExistingCardIn {
|
message RenderExistingCardIn {
|
||||||
|
|
|
@ -15,7 +15,7 @@ from anki import hooks
|
||||||
from anki.cards import Card
|
from anki.cards import Card
|
||||||
from anki.consts import *
|
from anki.consts import *
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from anki.rsbackend import FormatTimeSpanContext, SchedTimingToday
|
from anki.rsbackend import DeckTreeNode, FormatTimeSpanContext, SchedTimingToday
|
||||||
from anki.utils import ids2str, intTime
|
from anki.utils import ids2str, intTime
|
||||||
|
|
||||||
# card types: 0=new, 1=lrn, 2=rev, 3=relrn
|
# card types: 0=new, 1=lrn, 2=rev, 3=relrn
|
||||||
|
@ -232,6 +232,10 @@ order by due"""
|
||||||
"List of (base name, did, rev, lrn, new, children)"
|
"List of (base name, did, rev, lrn, new, children)"
|
||||||
return self.col.backend.legacy_deck_tree()
|
return self.col.backend.legacy_deck_tree()
|
||||||
|
|
||||||
|
def deck_due_tree(self) -> DeckTreeNode:
|
||||||
|
"Returns a tree of decks with counts."
|
||||||
|
return self.col.backend.deck_tree(include_counts=True)
|
||||||
|
|
||||||
# Getting the next card
|
# Getting the next card
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,11 @@ from __future__ import annotations
|
||||||
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
from anki.errors import DeckRenameError
|
from anki.errors import DeckRenameError
|
||||||
from anki.lang import _, ngettext
|
from anki.lang import _, ngettext
|
||||||
from anki.rsbackend import TR
|
from anki.rsbackend import TR, DeckTreeNode
|
||||||
from anki.utils import ids2str
|
from anki.utils import ids2str
|
||||||
from aqt import AnkiQt, gui_hooks
|
from aqt import AnkiQt, gui_hooks
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
|
@ -39,8 +38,13 @@ class DeckBrowserContent:
|
||||||
stats: str
|
stats: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RenderDeckNodeContext:
|
||||||
|
current_deck_id: int
|
||||||
|
|
||||||
|
|
||||||
class DeckBrowser:
|
class DeckBrowser:
|
||||||
_dueTree: Any
|
_dueTree: DeckTreeNode
|
||||||
|
|
||||||
def __init__(self, mw: AnkiQt) -> None:
|
def __init__(self, mw: AnkiQt) -> None:
|
||||||
self.mw = mw
|
self.mw = mw
|
||||||
|
@ -83,7 +87,7 @@ class DeckBrowser:
|
||||||
draggedDeckDid, ontoDeckDid = arg.split(",")
|
draggedDeckDid, ontoDeckDid = arg.split(",")
|
||||||
self._dragDeckOnto(draggedDeckDid, ontoDeckDid)
|
self._dragDeckOnto(draggedDeckDid, ontoDeckDid)
|
||||||
elif cmd == "collapse":
|
elif cmd == "collapse":
|
||||||
self._collapse(arg)
|
self._collapse(int(arg))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _selDeck(self, did):
|
def _selDeck(self, did):
|
||||||
|
@ -106,7 +110,7 @@ class DeckBrowser:
|
||||||
|
|
||||||
def _renderPage(self, reuse=False):
|
def _renderPage(self, reuse=False):
|
||||||
if not reuse:
|
if not reuse:
|
||||||
self._dueTree = self.mw.col.sched.deckDueTree()
|
self._dueTree = self.mw.col.sched.deck_due_tree()
|
||||||
self.__renderPage(None)
|
self.__renderPage(None)
|
||||||
return
|
return
|
||||||
self.web.evalWithCallback("window.pageYOffset", self.__renderPage)
|
self.web.evalWithCallback("window.pageYOffset", self.__renderPage)
|
||||||
|
@ -143,53 +147,49 @@ where id > ?""",
|
||||||
buf = self.mw.col.backend.studied_today(cards, float(thetime))
|
buf = self.mw.col.backend.studied_today(cards, float(thetime))
|
||||||
return buf
|
return buf
|
||||||
|
|
||||||
def _renderDeckTree(self, nodes, depth=0):
|
def _renderDeckTree(self, top: DeckTreeNode) -> str:
|
||||||
if not nodes:
|
buf = """
|
||||||
return ""
|
|
||||||
if depth == 0:
|
|
||||||
buf = """
|
|
||||||
<tr><th colspan=5 align=left>%s</th><th class=count>%s</th>
|
<tr><th colspan=5 align=left>%s</th><th class=count>%s</th>
|
||||||
<th class=count>%s</th><th class=optscol></th></tr>""" % (
|
<th class=count>%s</th><th class=optscol></th></tr>""" % (
|
||||||
_("Deck"),
|
_("Deck"),
|
||||||
tr(TR.STATISTICS_DUE_COUNT),
|
tr(TR.STATISTICS_DUE_COUNT),
|
||||||
_("New"),
|
_("New"),
|
||||||
)
|
)
|
||||||
buf += self._topLevelDragRow()
|
buf += self._topLevelDragRow()
|
||||||
else:
|
|
||||||
buf = ""
|
ctx = RenderDeckNodeContext(current_deck_id=self.mw.col.conf["curDeck"])
|
||||||
for node in nodes:
|
|
||||||
buf += self._deckRow(node, depth)
|
for child in top.children:
|
||||||
if depth == 0:
|
buf += self._render_deck_node(child, ctx)
|
||||||
buf += self._topLevelDragRow()
|
|
||||||
return buf
|
return buf
|
||||||
|
|
||||||
def _deckRow(self, node, depth):
|
def _render_deck_node(self, node: DeckTreeNode, ctx: RenderDeckNodeContext) -> str:
|
||||||
name, did, due, lrn, new, children = node
|
if node.collapsed:
|
||||||
deck = self.mw.col.decks.get(did)
|
|
||||||
collapsed = self.mw.col.decks.get(did)["collapsed"]
|
|
||||||
if collapsed:
|
|
||||||
prefix = "+"
|
prefix = "+"
|
||||||
else:
|
else:
|
||||||
prefix = "-"
|
prefix = "-"
|
||||||
due += lrn
|
|
||||||
|
due = node.review_count + node.learn_count
|
||||||
|
|
||||||
def indent():
|
def indent():
|
||||||
return " " * 6 * depth
|
return " " * 6 * (node.level - 1)
|
||||||
|
|
||||||
if did == self.mw.col.conf["curDeck"]:
|
if node.deck_id == ctx.current_deck_id:
|
||||||
klass = "deck current"
|
klass = "deck current"
|
||||||
else:
|
else:
|
||||||
klass = "deck"
|
klass = "deck"
|
||||||
buf = "<tr class='%s' id='%d'>" % (klass, did)
|
|
||||||
|
buf = "<tr class='%s' id='%d'>" % (klass, node.deck_id)
|
||||||
# deck link
|
# deck link
|
||||||
if children:
|
if node.children:
|
||||||
collapse = (
|
collapse = (
|
||||||
"<a class=collapse href=# onclick='return pycmd(\"collapse:%d\")'>%s</a>"
|
"<a class=collapse href=# onclick='return pycmd(\"collapse:%d\")'>%s</a>"
|
||||||
% (did, prefix)
|
% (node.deck_id, prefix)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
collapse = "<span class=collapse></span>"
|
collapse = "<span class=collapse></span>"
|
||||||
if deck["dyn"]:
|
if node.filtered:
|
||||||
extraclass = "filtered"
|
extraclass = "filtered"
|
||||||
else:
|
else:
|
||||||
extraclass = ""
|
extraclass = ""
|
||||||
|
@ -200,8 +200,8 @@ where id > ?""",
|
||||||
indent(),
|
indent(),
|
||||||
collapse,
|
collapse,
|
||||||
extraclass,
|
extraclass,
|
||||||
did,
|
node.deck_id,
|
||||||
name,
|
node.name,
|
||||||
)
|
)
|
||||||
# due counts
|
# due counts
|
||||||
def nonzeroColour(cnt, klass):
|
def nonzeroColour(cnt, klass):
|
||||||
|
@ -211,16 +211,17 @@ where id > ?""",
|
||||||
|
|
||||||
buf += "<td align=right>%s</td><td align=right>%s</td>" % (
|
buf += "<td align=right>%s</td><td align=right>%s</td>" % (
|
||||||
nonzeroColour(due, "review-count"),
|
nonzeroColour(due, "review-count"),
|
||||||
nonzeroColour(new, "new-count"),
|
nonzeroColour(node.new_count, "new-count"),
|
||||||
)
|
)
|
||||||
# options
|
# options
|
||||||
buf += (
|
buf += (
|
||||||
"<td align=center class=opts><a onclick='return pycmd(\"opts:%d\");'>"
|
"<td align=center class=opts><a onclick='return pycmd(\"opts:%d\");'>"
|
||||||
"<img src='/_anki/imgs/gears.svg' class=gears></a></td></tr>" % did
|
"<img src='/_anki/imgs/gears.svg' class=gears></a></td></tr>" % node.deck_id
|
||||||
)
|
)
|
||||||
# children
|
# children
|
||||||
if not collapsed:
|
if not node.collapsed:
|
||||||
buf += self._renderDeckTree(children, depth + 1)
|
for child in node.children:
|
||||||
|
buf += self._render_deck_node(child, ctx)
|
||||||
return buf
|
return buf
|
||||||
|
|
||||||
def _topLevelDragRow(self):
|
def _topLevelDragRow(self):
|
||||||
|
@ -265,10 +266,21 @@ where id > ?""",
|
||||||
self.mw.col.decks.select(did)
|
self.mw.col.decks.select(did)
|
||||||
self.mw.onDeckConf()
|
self.mw.onDeckConf()
|
||||||
|
|
||||||
def _collapse(self, did):
|
def _collapse(self, did: int) -> None:
|
||||||
self.mw.col.decks.collapse(did)
|
self.mw.col.decks.collapse(did)
|
||||||
|
self._toggle_collapsed_in_node(did, self._dueTree)
|
||||||
self._renderPage(reuse=True)
|
self._renderPage(reuse=True)
|
||||||
|
|
||||||
|
def _toggle_collapsed_in_node(self, deck_id: int, node: DeckTreeNode) -> bool:
|
||||||
|
"Toggle collapsed on deck in tree. Returns true if found."
|
||||||
|
if node.deck_id == deck_id:
|
||||||
|
node.collapsed = not node.collapsed
|
||||||
|
return True
|
||||||
|
for child in node.children:
|
||||||
|
if self._toggle_collapsed_in_node(deck_id, child):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def _dragDeckOnto(self, draggedDeckDid, ontoDeckDid):
|
def _dragDeckOnto(self, draggedDeckDid, ontoDeckDid):
|
||||||
try:
|
try:
|
||||||
self.mw.col.decks.renameForDragAndDrop(draggedDeckDid, ontoDeckDid)
|
self.mw.col.decks.renameForDragAndDrop(draggedDeckDid, ontoDeckDid)
|
||||||
|
|
|
@ -62,3 +62,5 @@ check_untyped_defs=true
|
||||||
check_untyped_defs=true
|
check_untyped_defs=true
|
||||||
[mypy-aqt.preferences]
|
[mypy-aqt.preferences]
|
||||||
check_untyped_defs=true
|
check_untyped_defs=true
|
||||||
|
[mypy-aqt.deckbrowser]
|
||||||
|
check_untyped_defs=true
|
||||||
|
|
|
@ -62,16 +62,21 @@ fn add_child_nodes(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_collapsed(node: &mut DeckTreeNode, decks: &HashMap<DeckID, Deck>, browser: bool) {
|
fn add_collapsed_and_filtered(
|
||||||
|
node: &mut DeckTreeNode,
|
||||||
|
decks: &HashMap<DeckID, Deck>,
|
||||||
|
browser: bool,
|
||||||
|
) {
|
||||||
if let Some(deck) = decks.get(&DeckID(node.deck_id)) {
|
if let Some(deck) = decks.get(&DeckID(node.deck_id)) {
|
||||||
node.collapsed = if browser {
|
node.collapsed = if browser {
|
||||||
deck.common.browser_collapsed
|
deck.common.browser_collapsed
|
||||||
} else {
|
} else {
|
||||||
deck.common.study_collapsed
|
deck.common.study_collapsed
|
||||||
};
|
};
|
||||||
|
node.filtered = deck.is_filtered();
|
||||||
}
|
}
|
||||||
for child in &mut node.children {
|
for child in &mut node.children {
|
||||||
add_collapsed(child, decks, browser);
|
add_collapsed_and_filtered(child, decks, browser);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,7 +206,7 @@ impl Collection {
|
||||||
.map(|d| (d.id, d))
|
.map(|d| (d.id, d))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
add_collapsed(&mut tree, &decks_map, !counts);
|
add_collapsed_and_filtered(&mut tree, &decks_map, !counts);
|
||||||
if self.default_deck_is_empty()? {
|
if self.default_deck_is_empty()? {
|
||||||
hide_default_deck(&mut tree);
|
hide_default_deck(&mut tree);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue