add routine to set deck collapse state
Updating a deck via protobuf is now exposed on the backend, but not currently on the frontend - I suspect we'll be better off writing separate routines for the actions we need instead, and we get a better undo description for free. This is currently causing an ugly redraw in the browse screen, which will need fixing.
This commit is contained in:
parent
42a4d11416
commit
2168dfe63d
@ -23,3 +23,4 @@ undo-update-deck = Update Deck
|
|||||||
undo-forget-card = Forget Card
|
undo-forget-card = Forget Card
|
||||||
undo-set-flag = Set Flag
|
undo-set-flag = Set Flag
|
||||||
undo-build-filtered-deck = Build Deck
|
undo-build-filtered-deck = Build Deck
|
||||||
|
undo-expand-collapse = Expand/Collapse
|
||||||
|
@ -10,7 +10,8 @@ ignored-classes=
|
|||||||
UnburyCardsInCurrentDeckIn,
|
UnburyCardsInCurrentDeckIn,
|
||||||
BuryOrSuspendCardsIn,
|
BuryOrSuspendCardsIn,
|
||||||
NoteIsDuplicateOrEmptyOut,
|
NoteIsDuplicateOrEmptyOut,
|
||||||
BackendError
|
BackendError,
|
||||||
|
SetDeckCollapsedIn,
|
||||||
|
|
||||||
[REPORTS]
|
[REPORTS]
|
||||||
output-format=colorized
|
output-format=colorized
|
||||||
|
@ -36,6 +36,9 @@ from . import backend_pb2 as pb
|
|||||||
from . import rsbridge
|
from . import rsbridge
|
||||||
from .fluent import GeneratedTranslations, LegacyTranslationEnum
|
from .fluent import GeneratedTranslations, LegacyTranslationEnum
|
||||||
|
|
||||||
|
# the following comment is required to suppress a warning that only shows up
|
||||||
|
# when there are other pylint failures
|
||||||
|
# pylint: disable=c-extension-no-member
|
||||||
assert rsbridge.buildhash() == anki.buildinfo.buildhash
|
assert rsbridge.buildhash() == anki.buildinfo.buildhash
|
||||||
|
|
||||||
|
|
||||||
|
@ -332,9 +332,13 @@ class Collection:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_deck(self, id: DeckId) -> Deck:
|
def get_deck(self, id: DeckId) -> Deck:
|
||||||
"Get a new-style deck object. Currently read-only."
|
"Get a new-style deck object."
|
||||||
return self._backend.get_deck(id)
|
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)
|
||||||
|
|
||||||
def get_deck_config(self, id: DeckConfigId) -> DeckConfig:
|
def get_deck_config(self, id: DeckConfigId) -> DeckConfig:
|
||||||
"Get a new-style deck config object. Currently read-only."
|
"Get a new-style deck config object. Currently read-only."
|
||||||
return self._backend.get_deck_config(id)
|
return self._backend.get_deck_config(id)
|
||||||
|
@ -21,6 +21,7 @@ from anki.utils import from_json_bytes, ids2str, intTime, legacy_func, to_json_b
|
|||||||
DeckTreeNode = _pb.DeckTreeNode
|
DeckTreeNode = _pb.DeckTreeNode
|
||||||
DeckNameId = _pb.DeckNameId
|
DeckNameId = _pb.DeckNameId
|
||||||
FilteredDeckConfig = _pb.Deck.Filtered
|
FilteredDeckConfig = _pb.Deck.Filtered
|
||||||
|
DeckCollapseScope = _pb.SetDeckCollapsedIn.Scope
|
||||||
|
|
||||||
# legacy code may pass this in as the type argument to .id()
|
# legacy code may pass this in as the type argument to .id()
|
||||||
defaultDeck = 0
|
defaultDeck = 0
|
||||||
@ -224,6 +225,13 @@ class DeckManager:
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def set_collapsed(
|
||||||
|
self, deck_id: DeckId, collapsed: bool, scope: DeckCollapseScope.V
|
||||||
|
) -> OpChanges:
|
||||||
|
return self.col._backend.set_deck_collapsed(
|
||||||
|
deck_id=deck_id, collapsed=collapsed, scope=scope
|
||||||
|
)
|
||||||
|
|
||||||
def collapse(self, did: DeckId) -> None:
|
def collapse(self, did: DeckId) -> None:
|
||||||
deck = self.get(did)
|
deck = self.get(did)
|
||||||
deck["collapsed"] = not deck["collapsed"]
|
deck["collapsed"] = not deck["collapsed"]
|
||||||
|
@ -9,7 +9,7 @@ from typing import Any
|
|||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
from anki.collection import OpChanges
|
from anki.collection import OpChanges
|
||||||
from anki.decks import Deck, DeckId, DeckTreeNode
|
from anki.decks import Deck, DeckCollapseScope, DeckId, DeckTreeNode
|
||||||
from anki.utils import intTime
|
from anki.utils import intTime
|
||||||
from aqt import AnkiQt, gui_hooks
|
from aqt import AnkiQt, gui_hooks
|
||||||
from aqt.operations.deck import (
|
from aqt.operations.deck import (
|
||||||
@ -17,6 +17,7 @@ from aqt.operations.deck import (
|
|||||||
remove_decks,
|
remove_decks,
|
||||||
rename_deck,
|
rename_deck,
|
||||||
reparent_decks,
|
reparent_decks,
|
||||||
|
set_deck_collapsed,
|
||||||
)
|
)
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.sound import av_player
|
from aqt.sound import av_player
|
||||||
@ -286,11 +287,16 @@ class DeckBrowser:
|
|||||||
self.mw.onDeckConf()
|
self.mw.onDeckConf()
|
||||||
|
|
||||||
def _collapse(self, did: DeckId) -> None:
|
def _collapse(self, did: DeckId) -> None:
|
||||||
self.mw.col.decks.collapse(did)
|
|
||||||
node = self.mw.col.decks.find_deck_in_tree(self._dueTree, did)
|
node = self.mw.col.decks.find_deck_in_tree(self._dueTree, did)
|
||||||
if node:
|
if node:
|
||||||
node.collapsed = not node.collapsed
|
node.collapsed = not node.collapsed
|
||||||
self._renderPage(reuse=True)
|
set_deck_collapsed(
|
||||||
|
mw=self.mw,
|
||||||
|
deck_id=did,
|
||||||
|
collapsed=node.collapsed,
|
||||||
|
scope=DeckCollapseScope.REVIEWER,
|
||||||
|
)
|
||||||
|
self._renderPage(reuse=True)
|
||||||
|
|
||||||
def _handle_drag_and_drop(self, source: DeckId, target: DeckId) -> None:
|
def _handle_drag_and_drop(self, source: DeckId, target: DeckId) -> None:
|
||||||
reparent_decks(mw=self.mw, parent=self.mw, deck_ids=[source], new_parent=target)
|
reparent_decks(mw=self.mw, parent=self.mw, deck_ids=[source], new_parent=target)
|
||||||
|
@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Callable, Sequence
|
from typing import Callable, Sequence
|
||||||
|
|
||||||
from anki.decks import DeckId
|
from anki.decks import DeckCollapseScope, DeckId
|
||||||
from aqt import AnkiQt, QWidget
|
from aqt import AnkiQt, QWidget
|
||||||
from aqt.main import PerformOpOptionalSuccessCallback
|
from aqt.main import PerformOpOptionalSuccessCallback
|
||||||
from aqt.utils import getOnlyText, tooltip, tr
|
from aqt.utils import getOnlyText, tooltip, tr
|
||||||
@ -68,3 +68,13 @@ def add_deck(
|
|||||||
lambda: mw.col.decks.add_normal_deck_with_name(name),
|
lambda: mw.col.decks.add_normal_deck_with_name(name),
|
||||||
success=success,
|
success=success,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def set_deck_collapsed(
|
||||||
|
*, mw: AnkiQt, deck_id: DeckId, collapsed: bool, scope: DeckCollapseScope.V
|
||||||
|
) -> None:
|
||||||
|
mw.perform_op(
|
||||||
|
lambda: mw.col.decks.set_collapsed(
|
||||||
|
deck_id=deck_id, collapsed=collapsed, scope=scope
|
||||||
|
)
|
||||||
|
)
|
||||||
|
@ -8,7 +8,7 @@ from typing import Dict, Iterable, List, Optional, Tuple, cast
|
|||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
from anki.collection import Config, OpChanges, SearchJoiner, SearchNode
|
from anki.collection import Config, OpChanges, SearchJoiner, SearchNode
|
||||||
from anki.decks import Deck, DeckId, DeckTreeNode
|
from anki.decks import Deck, 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
|
||||||
@ -16,7 +16,12 @@ from anki.types import assert_exhaustive
|
|||||||
from aqt import colors, gui_hooks
|
from aqt import colors, gui_hooks
|
||||||
from aqt.clayout import CardLayout
|
from aqt.clayout import CardLayout
|
||||||
from aqt.models import Models
|
from aqt.models import Models
|
||||||
from aqt.operations.deck import remove_decks, rename_deck, reparent_decks
|
from aqt.operations.deck import (
|
||||||
|
remove_decks,
|
||||||
|
rename_deck,
|
||||||
|
reparent_decks,
|
||||||
|
set_deck_collapsed,
|
||||||
|
)
|
||||||
from aqt.operations.tag import remove_tags_from_all_notes, rename_tag, reparent_tags
|
from aqt.operations.tag import remove_tags_from_all_notes, rename_tag, reparent_tags
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.theme import ColoredIcon, theme_manager
|
from aqt.theme import ColoredIcon, theme_manager
|
||||||
@ -965,17 +970,20 @@ class SidebarTreeView(QTreeView):
|
|||||||
def render(
|
def render(
|
||||||
root: SidebarItem, nodes: Iterable[DeckTreeNode], head: str = ""
|
root: SidebarItem, nodes: Iterable[DeckTreeNode], head: str = ""
|
||||||
) -> None:
|
) -> None:
|
||||||
|
def toggle_expand(node: DeckTreeNode) -> Callable[[bool], None]:
|
||||||
|
return lambda expanded: set_deck_collapsed(
|
||||||
|
mw=self.mw,
|
||||||
|
deck_id=DeckId(node.deck_id),
|
||||||
|
collapsed=not expanded,
|
||||||
|
scope=DeckCollapseScope.BROWSER,
|
||||||
|
)
|
||||||
|
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
|
|
||||||
def toggle_expand() -> Callable[[bool], None]:
|
|
||||||
did = DeckId(node.deck_id) # pylint: disable=cell-var-from-loop
|
|
||||||
return lambda _: self.mw.col.decks.collapseBrowser(did)
|
|
||||||
|
|
||||||
item = SidebarItem(
|
item = SidebarItem(
|
||||||
name=node.name,
|
name=node.name,
|
||||||
icon=icon,
|
icon=icon,
|
||||||
search_node=SearchNode(deck=head + node.name),
|
search_node=SearchNode(deck=head + node.name),
|
||||||
on_expanded=toggle_expand(),
|
on_expanded=toggle_expand(node),
|
||||||
expanded=not node.collapsed,
|
expanded=not node.collapsed,
|
||||||
item_type=SidebarItemType.DECK,
|
item_type=SidebarItemType.DECK,
|
||||||
id=node.deck_id,
|
id=node.deck_id,
|
||||||
|
@ -143,6 +143,8 @@ service DecksService {
|
|||||||
rpc GetAllDecksLegacy(Empty) returns (Json);
|
rpc GetAllDecksLegacy(Empty) returns (Json);
|
||||||
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 SetDeckCollapsed(SetDeckCollapsedIn) returns (OpChanges);
|
||||||
rpc GetDeckLegacy(DeckId) returns (Json);
|
rpc GetDeckLegacy(DeckId) returns (Json);
|
||||||
rpc GetDeckNames(GetDeckNamesIn) returns (DeckNames);
|
rpc GetDeckNames(GetDeckNamesIn) returns (DeckNames);
|
||||||
rpc NewDeckLegacy(Bool) returns (Json);
|
rpc NewDeckLegacy(Bool) returns (Json);
|
||||||
@ -916,6 +918,17 @@ message SetTagExpandedIn {
|
|||||||
bool expanded = 2;
|
bool expanded = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message SetDeckCollapsedIn {
|
||||||
|
enum Scope {
|
||||||
|
REVIEWER = 0;
|
||||||
|
BROWSER = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64 deck_id = 1;
|
||||||
|
bool collapsed = 2;
|
||||||
|
Scope scope = 3;
|
||||||
|
}
|
||||||
|
|
||||||
message GetChangedTagsOut {
|
message GetChangedTagsOut {
|
||||||
repeated string tags = 1;
|
repeated string tags = 1;
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// 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
|
||||||
|
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use super::Backend;
|
use super::Backend;
|
||||||
use crate::{
|
use crate::{
|
||||||
backend_proto::{self as pb},
|
backend_proto::{self as pb},
|
||||||
decks::{Deck, DeckId, DeckSchema11, FilteredSearchOrder},
|
decks::{
|
||||||
|
human_deck_name_to_native, native_deck_name_to_human, Deck, DeckId, DeckSchema11,
|
||||||
|
FilteredSearchOrder,
|
||||||
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
scheduler::filtered::FilteredDeckForUpdate,
|
scheduler::filtered::FilteredDeckForUpdate,
|
||||||
};
|
};
|
||||||
@ -89,6 +94,13 @@ impl DecksService for Backend {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_deck(&self, input: pb::Deck) -> Result<pb::OpChanges> {
|
||||||
|
self.with_col(|col| {
|
||||||
|
let mut deck = Deck::try_from(input)?;
|
||||||
|
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
|
||||||
@ -168,6 +180,13 @@ impl DecksService for Backend {
|
|||||||
fn filtered_deck_order_labels(&self, _input: pb::Empty) -> Result<pb::StringList> {
|
fn filtered_deck_order_labels(&self, _input: pb::Empty) -> Result<pb::StringList> {
|
||||||
Ok(FilteredSearchOrder::labels(&self.tr).into())
|
Ok(FilteredSearchOrder::labels(&self.tr).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_deck_collapsed(&self, input: pb::SetDeckCollapsedIn) -> Result<pb::OpChanges> {
|
||||||
|
self.with_col(|col| {
|
||||||
|
col.set_deck_collapsed(input.deck_id.into(), input.collapsed, input.scope())
|
||||||
|
})
|
||||||
|
.map(Into::into)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<pb::DeckId> for DeckId {
|
impl From<pb::DeckId> for DeckId {
|
||||||
@ -208,8 +227,54 @@ impl From<pb::FilteredDeckForUpdate> for FilteredDeckForUpdate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// before we can switch to returning protobuf, we need to make sure we're converting the
|
impl From<Deck> for pb::Deck {
|
||||||
// deck separators
|
fn from(d: Deck) -> Self {
|
||||||
|
pb::Deck {
|
||||||
|
id: d.id.0,
|
||||||
|
name: native_deck_name_to_human(&d.name),
|
||||||
|
mtime_secs: d.mtime_secs.0,
|
||||||
|
usn: d.usn.0,
|
||||||
|
common: Some(d.common),
|
||||||
|
kind: Some(d.kind.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<pb::Deck> for Deck {
|
||||||
|
type Error = AnkiError;
|
||||||
|
|
||||||
|
fn try_from(d: pb::Deck) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Deck {
|
||||||
|
id: DeckId(d.id),
|
||||||
|
name: human_deck_name_to_native(&d.name),
|
||||||
|
mtime_secs: TimestampSecs(d.mtime_secs),
|
||||||
|
usn: Usn(d.usn),
|
||||||
|
common: d.common.unwrap_or_default(),
|
||||||
|
kind: d
|
||||||
|
.kind
|
||||||
|
.ok_or_else(|| AnkiError::invalid_input("missing kind"))?
|
||||||
|
.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DeckKind> for pb::deck::Kind {
|
||||||
|
fn from(k: DeckKind) -> Self {
|
||||||
|
match k {
|
||||||
|
DeckKind::Normal(n) => pb::deck::Kind::Normal(n),
|
||||||
|
DeckKind::Filtered(f) => pb::deck::Kind::Filtered(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<pb::deck::Kind> for DeckKind {
|
||||||
|
fn from(kind: pb::deck::Kind) -> Self {
|
||||||
|
match kind {
|
||||||
|
pb::deck::Kind::Normal(normal) => DeckKind::Normal(normal),
|
||||||
|
pb::deck::Kind::Filtered(filtered) => DeckKind::Filtered(filtered),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// fn new_deck(&self, input: pb::Bool) -> Result<pb::Deck> {
|
// fn new_deck(&self, input: pb::Bool) -> Result<pb::Deck> {
|
||||||
// let deck = if input.val {
|
// let deck = if input.val {
|
||||||
@ -219,28 +284,3 @@ impl From<pb::FilteredDeckForUpdate> for FilteredDeckForUpdate {
|
|||||||
// };
|
// };
|
||||||
// Ok(deck.into())
|
// Ok(deck.into())
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// impl From<pb::Deck> for Deck {
|
|
||||||
// fn from(deck: pb::Deck) -> Self {
|
|
||||||
// Self {
|
|
||||||
// id: deck.id.into(),
|
|
||||||
// name: deck.name,
|
|
||||||
// mtime_secs: deck.mtime_secs.into(),
|
|
||||||
// usn: deck.usn.into(),
|
|
||||||
// common: deck.common.unwrap_or_default(),
|
|
||||||
// kind: deck
|
|
||||||
// .kind
|
|
||||||
// .map(Into::into)
|
|
||||||
// .unwrap_or_else(|| DeckKind::Normal(NormalDeck::default())),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl From<pb::deck::Kind> for DeckKind {
|
|
||||||
// fn from(kind: pb::deck::Kind) -> Self {
|
|
||||||
// match kind {
|
|
||||||
// pb::deck::Kind::Normal(normal) => DeckKind::Normal(normal),
|
|
||||||
// pb::deck::Kind::Filtered(filtered) => DeckKind::Filtered(filtered),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
@ -207,6 +207,10 @@ pub(crate) fn human_deck_name_to_native(name: &str) -> String {
|
|||||||
out.trim_end_matches('\x1f').into()
|
out.trim_end_matches('\x1f').into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn native_deck_name_to_human(name: &str) -> String {
|
||||||
|
name.replace('\x1f', "::")
|
||||||
|
}
|
||||||
|
|
||||||
impl Collection {
|
impl Collection {
|
||||||
pub(crate) fn get_deck(&mut self, did: DeckId) -> Result<Option<Arc<Deck>>> {
|
pub(crate) fn get_deck(&mut self, did: DeckId) -> Result<Option<Arc<Deck>>> {
|
||||||
if let Some(deck) = self.state.deck_cache.get(&did) {
|
if let Some(deck) = self.state.deck_cache.get(&did) {
|
||||||
@ -222,28 +226,6 @@ impl Collection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Deck> for DeckProto {
|
|
||||||
fn from(d: Deck) -> Self {
|
|
||||||
DeckProto {
|
|
||||||
id: d.id.0,
|
|
||||||
name: d.name,
|
|
||||||
mtime_secs: d.mtime_secs.0,
|
|
||||||
usn: d.usn.0,
|
|
||||||
common: Some(d.common),
|
|
||||||
kind: Some(d.kind.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<DeckKind> for pb::deck::Kind {
|
|
||||||
fn from(k: DeckKind) -> Self {
|
|
||||||
match k {
|
|
||||||
DeckKind::Normal(n) => pb::deck::Kind::Normal(n),
|
|
||||||
DeckKind::Filtered(f) => pb::deck::Kind::Filtered(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn immediate_parent_name(machine_name: &str) -> Option<&str> {
|
pub(crate) fn immediate_parent_name(machine_name: &str) -> Option<&str> {
|
||||||
machine_name.rsplitn(2, '\x1f').nth(1)
|
machine_name.rsplitn(2, '\x1f').nth(1)
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
use super::DeckId;
|
use super::DeckId;
|
||||||
use super::{
|
use super::{
|
||||||
human_deck_name_to_native, Deck, DeckCommon, DeckKind, FilteredDeck, FilteredSearchTerm,
|
human_deck_name_to_native, native_deck_name_to_human, Deck, DeckCommon, DeckKind, FilteredDeck,
|
||||||
NormalDeck,
|
FilteredSearchTerm, NormalDeck,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
serde::{default_on_invalid, deserialize_bool_from_anything, deserialize_number_from_string},
|
serde::{default_on_invalid, deserialize_bool_from_anything, deserialize_number_from_string},
|
||||||
@ -363,7 +363,7 @@ impl From<Deck> for DeckCommonSchema11 {
|
|||||||
DeckCommonSchema11 {
|
DeckCommonSchema11 {
|
||||||
id: deck.id,
|
id: deck.id,
|
||||||
mtime: deck.mtime_secs,
|
mtime: deck.mtime_secs,
|
||||||
name: deck.name.replace("\x1f", "::"),
|
name: native_deck_name_to_human(&deck.name),
|
||||||
usn: deck.usn,
|
usn: deck.usn,
|
||||||
today: (&deck).into(),
|
today: (&deck).into(),
|
||||||
study_collapsed: deck.common.study_collapsed,
|
study_collapsed: deck.common.study_collapsed,
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// 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
|
||||||
|
|
||||||
use super::{Deck, DeckKind, DueCounts};
|
use super::{Deck, DeckKind, DueCounts};
|
||||||
|
pub use crate::backend_proto::set_deck_collapsed_in::Scope as DeckCollapseScope;
|
||||||
use crate::{
|
use crate::{
|
||||||
backend_proto::DeckTreeNode,
|
backend_proto::DeckTreeNode,
|
||||||
collection::Collection,
|
collection::Collection,
|
||||||
@ -9,7 +10,9 @@ use crate::{
|
|||||||
deckconf::{DeckConf, DeckConfId},
|
deckconf::{DeckConf, DeckConfId},
|
||||||
decks::DeckId,
|
decks::DeckId,
|
||||||
error::Result,
|
error::Result,
|
||||||
|
ops::OpOutput,
|
||||||
timestamp::TimestampSecs,
|
timestamp::TimestampSecs,
|
||||||
|
undo::Op,
|
||||||
};
|
};
|
||||||
use serde_tuple::Serialize_tuple;
|
use serde_tuple::Serialize_tuple;
|
||||||
use std::{
|
use std::{
|
||||||
@ -312,6 +315,26 @@ impl Collection {
|
|||||||
Ok(get_subnode(tree, target))
|
Ok(get_subnode(tree, target))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_deck_collapsed(
|
||||||
|
&mut self,
|
||||||
|
did: DeckId,
|
||||||
|
collapsed: bool,
|
||||||
|
scope: DeckCollapseScope,
|
||||||
|
) -> Result<OpOutput<()>> {
|
||||||
|
self.transact(Op::ExpandCollapse, |col| {
|
||||||
|
if let Some(mut deck) = col.storage.get_deck(did)? {
|
||||||
|
let original = deck.clone();
|
||||||
|
let c = &mut deck.common;
|
||||||
|
match scope {
|
||||||
|
DeckCollapseScope::Reviewer => c.study_collapsed = collapsed,
|
||||||
|
DeckCollapseScope::Browser => c.browser_collapsed = collapsed,
|
||||||
|
};
|
||||||
|
col.update_deck_inner(&mut deck, original, col.usn()?)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn legacy_deck_tree(&mut self) -> Result<LegacyDueCounts> {
|
pub(crate) fn legacy_deck_tree(&mut self) -> Result<LegacyDueCounts> {
|
||||||
let tree = self.deck_tree(Some(TimestampSecs::now()), None)?;
|
let tree = self.deck_tree(Some(TimestampSecs::now()), None)?;
|
||||||
Ok(LegacyDueCounts::from(tree))
|
Ok(LegacyDueCounts::from(tree))
|
||||||
|
@ -12,6 +12,7 @@ pub enum Op {
|
|||||||
Bury,
|
Bury,
|
||||||
ClearUnusedTags,
|
ClearUnusedTags,
|
||||||
EmptyFilteredDeck,
|
EmptyFilteredDeck,
|
||||||
|
ExpandCollapse,
|
||||||
FindAndReplace,
|
FindAndReplace,
|
||||||
RebuildFilteredDeck,
|
RebuildFilteredDeck,
|
||||||
RemoveDeck,
|
RemoveDeck,
|
||||||
@ -66,6 +67,7 @@ impl Op {
|
|||||||
Op::BuildFilteredDeck => tr.undo_build_filtered_deck(),
|
Op::BuildFilteredDeck => tr.undo_build_filtered_deck(),
|
||||||
Op::RebuildFilteredDeck => tr.undo_build_filtered_deck(),
|
Op::RebuildFilteredDeck => tr.undo_build_filtered_deck(),
|
||||||
Op::EmptyFilteredDeck => tr.studying_empty(),
|
Op::EmptyFilteredDeck => tr.studying_empty(),
|
||||||
|
Op::ExpandCollapse => tr.undo_expand_collapse(),
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user