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:
Damien Elmes 2020-05-16 10:52:14 +10:00
parent 75d3eebafe
commit f2086fc2e3
5 changed files with 69 additions and 44 deletions

View file

@ -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 {

View file

@ -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
########################################################################## ##########################################################################

View file

@ -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,10 +147,7 @@ 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:
return ""
if depth == 0:
buf = """ 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>""" % (
@ -155,41 +156,40 @@ where id > ?""",
_("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 "&nbsp;" * 6 * depth return "&nbsp;" * 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)

View file

@ -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

View file

@ -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);
} }