remove deck protobuf from frontend

Like the previous change, avoid exposing the protobuf as a public API
for now. It requires more thought, and is probably better done with
either extra helper accessors like decks.name(), or via a native class.
This commit is contained in:
Damien Elmes 2021-05-31 16:24:51 +10:00
parent bb323615dd
commit 29c4869aef
10 changed files with 57 additions and 59 deletions

View file

@ -39,7 +39,7 @@ from anki.cards import Card, CardId
from anki.config import Config, ConfigManager from anki.config import Config, ConfigManager
from anki.consts import * from anki.consts import *
from anki.dbproxy import DBProxy from anki.dbproxy import DBProxy
from anki.decks import Deck, DeckId, DeckManager from anki.decks import DeckId, DeckManager
from anki.errors import AbortSchemaModification, DBError from anki.errors import AbortSchemaModification, DBError
from anki.lang import FormatTimeSpan from anki.lang import FormatTimeSpan
from anki.media import MediaManager, media_paths_from_col_path from anki.media import MediaManager, media_paths_from_col_path
@ -335,14 +335,6 @@ class Collection:
note=note._to_backend_note(), skip_undo_entry=False note=note._to_backend_note(), skip_undo_entry=False
) )
def get_deck(self, id: DeckId) -> Deck:
"Get a new-style deck object."
return self._backend.get_deck(id)
def update_deck(self, deck: Deck) -> OpChanges:
"Save updates to an existing deck."
return self._backend.update_deck(deck)
getCard = get_card getCard = get_card
getNote = get_note getNote = get_note

View file

@ -33,9 +33,6 @@ defaultDynamicDeck = 1
DeckDict = Dict[str, Any] DeckDict = Dict[str, Any]
DeckConfigDict = Dict[str, Any] DeckConfigDict = Dict[str, Any]
# currently only supports read-only access
Deck = _pb.Deck
DeckId = NewType("DeckId", int) DeckId = NewType("DeckId", int)
DeckConfigId = NewType("DeckConfigId", int) DeckConfigId = NewType("DeckConfigId", int)
@ -290,6 +287,9 @@ class DeckManager:
deck=to_json_bytes(g), preserve_usn_and_mtime=preserve_usn deck=to_json_bytes(g), preserve_usn_and_mtime=preserve_usn
) )
def update_dict(self, deck: DeckDict) -> OpChanges:
return self.col._backend.update_deck_legacy(json=to_json_bytes(deck))
def rename(self, deck: Union[DeckDict, DeckId], new_name: str) -> OpChanges: def rename(self, deck: Union[DeckDict, DeckId], new_name: str) -> OpChanges:
"Rename deck prefix to NAME if not exists. Updates children." "Rename deck prefix to NAME if not exists. Updates children."
if isinstance(deck, int): if isinstance(deck, int):
@ -455,15 +455,12 @@ class DeckManager:
# Deck selection # Deck selection
############################################################# #############################################################
def get_current(self) -> Deck:
return self.col._backend.get_current_deck()
def set_current(self, deck: DeckId) -> OpChanges: def set_current(self, deck: DeckId) -> OpChanges:
return self.col._backend.set_current_deck(deck) return self.col._backend.set_current_deck(deck)
def get_current_id(self) -> DeckId: def get_current_id(self) -> DeckId:
"The currently selected deck ID." "The currently selected deck ID."
return DeckId(self.get_current().id) return DeckId(self.col._backend.get_current_deck().id)
# legacy # legacy
@ -528,7 +525,7 @@ class DeckManager:
) )
def deck_and_child_ids(self, deck_id: DeckId) -> List[DeckId]: def deck_and_child_ids(self, deck_id: DeckId) -> List[DeckId]:
parent_name = self.col.get_deck(deck_id).name parent_name = self.name(deck_id)
out = [deck_id] out = [deck_id]
out.extend(self.child_ids(parent_name)) out.extend(self.child_ids(parent_name))
return out return out

View file

@ -13,7 +13,7 @@ from anki.collection import (
SearchJoiner, SearchJoiner,
SearchNode, SearchNode,
) )
from anki.decks import Deck, DeckCollapseScope, DeckId, DeckTreeNode from anki.decks import DeckCollapseScope, DeckId, DeckTreeNode
from anki.models import NotetypeId from anki.models import NotetypeId
from anki.notes import Note from anki.notes import Note
from anki.tags import TagTreeNode from anki.tags import TagTreeNode
@ -902,8 +902,8 @@ class SidebarTreeView(QTreeView):
full_name = item.name_prefix + new_name full_name = item.name_prefix + new_name
deck_id = DeckId(item.id) deck_id = DeckId(item.id)
def after_fetch(deck: Deck) -> None: def after_fetch(name: str) -> None:
if full_name == deck.name: if full_name == name:
return return
rename_deck( rename_deck(
@ -914,7 +914,7 @@ class SidebarTreeView(QTreeView):
QueryOp( QueryOp(
parent=self.browser, parent=self.browser,
op=lambda col: col.get_deck(deck_id), op=lambda col: col.decks.name(deck_id),
success=after_fetch, success=after_fetch,
).run_in_background() ).run_in_background()

View file

@ -9,7 +9,7 @@ from typing import Any, Optional
import aqt import aqt
from anki.collection import OpChanges from anki.collection import OpChanges
from anki.decks import Deck, DeckCollapseScope, DeckId, DeckTreeNode from anki.decks import DeckCollapseScope, DeckId, DeckTreeNode
from aqt import AnkiQt, gui_hooks from aqt import AnkiQt, gui_hooks
from aqt.deckoptions import display_options_for_deck_id from aqt.deckoptions import display_options_for_deck_id
from aqt.operations import QueryOp from aqt.operations import QueryOp
@ -278,9 +278,9 @@ class DeckBrowser:
self.mw.onExport(did=did) self.mw.onExport(did=did)
def _rename(self, did: DeckId) -> None: def _rename(self, did: DeckId) -> None:
def prompt(deck: Deck) -> None: def prompt(name: str) -> None:
new_name = getOnlyText(tr.decks_new_deck_name(), default=deck.name) new_name = getOnlyText(tr.decks_new_deck_name(), default=name)
if not new_name or new_name == deck.name: if not new_name or new_name == name:
return return
else: else:
rename_deck( rename_deck(
@ -288,7 +288,7 @@ class DeckBrowser:
).run_in_background() ).run_in_background()
QueryOp( QueryOp(
parent=self.mw, op=lambda col: col.get_deck(did), success=prompt parent=self.mw, op=lambda col: col.decks.name(did), success=prompt
).run_in_background() ).run_in_background()
def _options(self, did: DeckId) -> None: def _options(self, did: DeckId) -> None:

View file

@ -4,9 +4,9 @@
from __future__ import annotations from __future__ import annotations
import aqt import aqt
from anki.decks import Deck from anki.decks import DeckDict
from aqt.operations import QueryOp from aqt.operations import QueryOp
from aqt.operations.deck import update_deck from aqt.operations.deck import update_deck_dict
from aqt.qt import * from aqt.qt import *
from aqt.utils import addCloseShortcut, disable_help_button, restoreGeom, saveGeom, tr from aqt.utils import addCloseShortcut, disable_help_button, restoreGeom, saveGeom, tr
@ -21,16 +21,16 @@ class DeckDescriptionDialog(QDialog):
self.mw = mw self.mw = mw
# set on success # set on success
self.deck: Deck self.deck: DeckDict
QueryOp( QueryOp(
parent=self.mw, parent=self.mw,
op=lambda col: col.decks.get_current(), op=lambda col: col.decks.current(),
success=self._setup_and_show, success=self._setup_and_show,
).run_in_background() ).run_in_background()
def _setup_and_show(self, deck: Deck) -> None: def _setup_and_show(self, deck: DeckDict) -> None:
if deck.WhichOneof("kind") != "normal": if deck["dyn"]:
return return
self.deck = deck self.deck = deck
@ -53,11 +53,11 @@ class DeckDescriptionDialog(QDialog):
self.enable_markdown = QCheckBox(tr.deck_config_description_markdown()) self.enable_markdown = QCheckBox(tr.deck_config_description_markdown())
self.enable_markdown.setToolTip(tr.deck_config_description_markdown_hint()) self.enable_markdown.setToolTip(tr.deck_config_description_markdown_hint())
self.enable_markdown.setChecked(self.deck.normal.markdown_description) self.enable_markdown.setChecked(self.deck.get("md", False))
box.addWidget(self.enable_markdown) box.addWidget(self.enable_markdown)
self.description = QPlainTextEdit() self.description = QPlainTextEdit()
self.description.setPlainText(self.deck.normal.description) self.description.setPlainText(self.deck.get("desc", ""))
box.addWidget(self.description) box.addWidget(self.description)
button_box = QDialogButtonBox() button_box = QDialogButtonBox()
@ -69,10 +69,10 @@ class DeckDescriptionDialog(QDialog):
self.show() self.show()
def save_and_accept(self) -> None: def save_and_accept(self) -> None:
self.deck.normal.description = self.description.toPlainText() self.deck["desc"] = self.description.toPlainText()
self.deck.normal.markdown_description = self.enable_markdown.isChecked() self.deck["md"] = self.enable_markdown.isChecked()
update_deck(parent=self, deck=self.deck).success( update_deck_dict(parent=self, deck=self.deck).success(
lambda _: self.accept() lambda _: self.accept()
).run_in_background() ).run_in_background()

View file

@ -8,7 +8,7 @@ from typing import List, Optional
import aqt import aqt
import aqt.deckconf import aqt.deckconf
from anki.cards import Card from anki.cards import Card
from anki.decks import Deck, DeckId from anki.decks import DeckDict, DeckId
from anki.lang import without_unicode_isolation from anki.lang import without_unicode_isolation
from aqt import gui_hooks from aqt import gui_hooks
from aqt.qt import * from aqt.qt import *
@ -29,7 +29,7 @@ class DeckOptionsDialog(QDialog):
TITLE = "deckOptions" TITLE = "deckOptions"
silentlyClose = True silentlyClose = True
def __init__(self, mw: aqt.main.AnkiQt, deck: Deck) -> None: def __init__(self, mw: aqt.main.AnkiQt, deck: DeckDict) -> None:
QDialog.__init__(self, mw, Qt.Window) QDialog.__init__(self, mw, Qt.Window)
self.mw = mw self.mw = mw
self._deck = deck self._deck = deck
@ -54,10 +54,10 @@ class DeckOptionsDialog(QDialog):
self.web.eval( self.web.eval(
f"""const $deckOptions = anki.deckOptions( f"""const $deckOptions = anki.deckOptions(
document.getElementById('main'), {self._deck.id});""" document.getElementById('main'), {self._deck["id"]});"""
) )
self.setWindowTitle( self.setWindowTitle(
without_unicode_isolation(tr.actions_options_for(val=self._deck.name)) without_unicode_isolation(tr.actions_options_for(val=self._deck["name"]))
) )
gui_hooks.deck_options_did_load(self) gui_hooks.deck_options_did_load(self)
@ -68,28 +68,28 @@ class DeckOptionsDialog(QDialog):
def confirm_deck_then_display_options(active_card: Optional[Card] = None) -> None: def confirm_deck_then_display_options(active_card: Optional[Card] = None) -> None:
decks = [aqt.mw.col.decks.get_current()] decks = [aqt.mw.col.decks.current()]
if card := active_card: if card := active_card:
if card.odid and card.odid != decks[0].id: if card.odid and card.odid != decks[0]["id"]:
decks.append(aqt.mw.col.get_deck(card.odid)) decks.append(aqt.mw.col.decks.get(card.odid))
if not any(d.id == card.did for d in decks): if not any(d["id"] == card.did for d in decks):
decks.append(aqt.mw.col.get_deck(card.odid)) decks.append(aqt.mw.col.decks.get(card.odid))
if len(decks) == 1: if len(decks) == 1:
display_options_for_deck(decks[0]) display_options_for_deck(decks[0])
else: else:
decks.sort(key=lambda x: x.WhichOneof("kind") == "filtered") decks.sort(key=lambda x: x["dyn"])
_deck_prompt_dialog(decks) _deck_prompt_dialog(decks)
def _deck_prompt_dialog(decks: List[Deck]) -> None: def _deck_prompt_dialog(decks: List[DeckDict]) -> None:
diag = QDialog(aqt.mw.app.activeWindow()) diag = QDialog(aqt.mw.app.activeWindow())
diag.setWindowTitle("Anki") diag.setWindowTitle("Anki")
box = QVBoxLayout() box = QVBoxLayout()
box.addWidget(QLabel(tr.deck_config_which_deck())) box.addWidget(QLabel(tr.deck_config_which_deck()))
for deck in decks: for deck in decks:
button = QPushButton(deck.name) button = QPushButton(deck["name"])
qconnect(button.clicked, lambda _, deck=deck: display_options_for_deck(deck)) qconnect(button.clicked, lambda _, deck=deck: display_options_for_deck(deck))
qconnect(button.clicked, diag.close) qconnect(button.clicked, diag.close)
box.addWidget(button) box.addWidget(button)
@ -101,15 +101,15 @@ def _deck_prompt_dialog(decks: List[Deck]) -> None:
def display_options_for_deck_id(deck_id: DeckId) -> None: def display_options_for_deck_id(deck_id: DeckId) -> None:
display_options_for_deck(aqt.mw.col.get_deck(deck_id)) display_options_for_deck(aqt.mw.col.decks.get(deck_id))
def display_options_for_deck(deck: Deck) -> None: def display_options_for_deck(deck: DeckDict) -> None:
if deck.WhichOneof("kind") == "normal": if not deck["dyn"]:
if KeyboardModifiersPressed().shift or aqt.mw.col.schedVer() == 1: if KeyboardModifiersPressed().shift or aqt.mw.col.schedVer() == 1:
deck_legacy = aqt.mw.col.decks.get(DeckId(deck.id)) deck_legacy = aqt.mw.col.decks.get(DeckId(deck["id"]))
aqt.deckconf.DeckConf(aqt.mw, deck_legacy) aqt.deckconf.DeckConf(aqt.mw, deck_legacy)
else: else:
DeckOptionsDialog(aqt.mw, deck) DeckOptionsDialog(aqt.mw, deck)
else: else:
aqt.dialogs.open("FilteredDeckConfigDialog", aqt.mw, deck_id=deck.id) aqt.dialogs.open("FilteredDeckConfigDialog", aqt.mw, deck_id=deck["id"])

View file

@ -6,7 +6,7 @@ from __future__ import annotations
from typing import Optional, Sequence from typing import Optional, Sequence
from anki.collection import OpChanges, OpChangesWithCount, OpChangesWithId from anki.collection import OpChanges, OpChangesWithCount, OpChangesWithId
from anki.decks import Deck, DeckCollapseScope, DeckId, UpdateDeckConfigs from anki.decks import DeckCollapseScope, DeckDict, DeckId, UpdateDeckConfigs
from aqt import QWidget from aqt import QWidget
from aqt.operations import CollectionOp from aqt.operations import CollectionOp
from aqt.utils import getOnlyText, tooltip, tr from aqt.utils import getOnlyText, tooltip, tr
@ -88,5 +88,5 @@ def update_deck_configs(
return CollectionOp(parent, lambda col: col.decks.update_deck_configs(input)) return CollectionOp(parent, lambda col: col.decks.update_deck_configs(input))
def update_deck(*, parent: QWidget, deck: Deck) -> CollectionOp[OpChanges]: def update_deck_dict(*, parent: QWidget, deck: DeckDict) -> CollectionOp[OpChanges]:
return CollectionOp(parent, lambda col: col.update_deck(deck)) return CollectionOp(parent, lambda col: col.decks.update_dict(deck))

View file

@ -94,7 +94,7 @@ class Overview:
elif url == "anki": elif url == "anki":
print("anki menu") print("anki menu")
elif url == "opts": elif url == "opts":
display_options_for_deck(self.mw.col.decks.get_current()) display_options_for_deck(self.mw.col.decks.current())
elif url == "cram": elif url == "cram":
aqt.dialogs.open("FilteredDeckConfigDialog", self.mw) aqt.dialogs.open("FilteredDeckConfigDialog", self.mw)
elif url == "refresh": elif url == "refresh":
@ -117,7 +117,7 @@ class Overview:
def _shortcutKeys(self) -> List[Tuple[str, Callable]]: def _shortcutKeys(self) -> List[Tuple[str, Callable]]:
return [ return [
("o", lambda: display_options_for_deck(self.mw.col.decks.get_current())), ("o", lambda: display_options_for_deck(self.mw.col.decks.current())),
("r", self.rebuild_current_filtered_deck), ("r", self.rebuild_current_filtered_deck),
("e", self.empty_current_filtered_deck), ("e", self.empty_current_filtered_deck),
("c", self.onCustomStudyKey), ("c", self.onCustomStudyKey),

View file

@ -144,6 +144,7 @@ service DecksService {
rpc GetDeckIdByName(String) returns (DeckId); rpc GetDeckIdByName(String) returns (DeckId);
rpc GetDeck(DeckId) returns (Deck); rpc GetDeck(DeckId) returns (Deck);
rpc UpdateDeck(Deck) returns (OpChanges); rpc UpdateDeck(Deck) returns (OpChanges);
rpc UpdateDeckLegacy(Json) returns (OpChanges);
rpc SetDeckCollapsed(SetDeckCollapsedIn) returns (OpChanges); rpc SetDeckCollapsed(SetDeckCollapsedIn) returns (OpChanges);
rpc GetDeckLegacy(DeckId) returns (Json); rpc GetDeckLegacy(DeckId) returns (Json);
rpc GetDeckNames(GetDeckNamesIn) returns (DeckNames); rpc GetDeckNames(GetDeckNamesIn) returns (DeckNames);

View file

@ -97,6 +97,14 @@ impl DecksService for Backend {
}) })
} }
fn update_deck_legacy(&self, input: pb::Json) -> Result<pb::OpChanges> {
self.with_col(|col| {
let deck: DeckSchema11 = serde_json::from_slice(&input.json)?;
let mut deck = deck.into();
col.update_deck(&mut deck).map(Into::into)
})
}
fn get_deck_legacy(&self, input: pb::DeckId) -> Result<pb::Json> { fn get_deck_legacy(&self, input: pb::DeckId) -> Result<pb::Json> {
self.with_col(|col| { self.with_col(|col| {
let deck: DeckSchema11 = col let deck: DeckSchema11 = col