diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index d46fd75d2..2f3c1e6ce 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -681,7 +681,7 @@ class Collection: else: return SearchNode.Group.Joiner.OR - # Browser rows + # Browser Table ########################################################################## def browser_row_for_id( @@ -695,6 +695,30 @@ class Collection: row.font_size, ) + def load_browser_card_columns(self) -> List[str]: + """Return the stored card column names and ensure the backend columns are set and in sync.""" + columns = self.get_config( + "activeCols", ["noteFld", "template", "cardDue", "deck"] + ) + self._backend.set_desktop_browser_card_columns(columns) + return columns + + def set_browser_card_columns(self, columns: List[str]) -> None: + self.set_config("activeCols", columns) + self._backend.set_desktop_browser_card_columns(columns) + + def load_browser_note_columns(self) -> List[str]: + """Return the stored note column names and ensure the backend columns are set and in sync.""" + columns = self.get_config( + "activeNoteCols", ["noteFld", "note", "noteCards", "noteTags"] + ) + self._backend.set_desktop_browser_note_columns(columns) + return columns + + def set_browser_note_columns(self, columns: List[str]) -> None: + self.set_config("activeNoteCols", columns) + self._backend.set_desktop_browser_note_columns(columns) + # Config ########################################################################## diff --git a/qt/aqt/table.py b/qt/aqt/table.py index 6173a533c..f7141298c 100644 --- a/qt/aqt/table.py +++ b/qt/aqt/table.py @@ -618,7 +618,7 @@ class CardState(ItemState): def __init__(self, col: Collection) -> None: super().__init__(col) self._load_columns() - self._load_active_columns() + self._active_columns = self.col.load_browser_card_columns() self._sort_column = self.col.get_config("sortType") self._sort_backwards = self.col.get_config_bool( Config.Bool.BROWSER_SORT_BACKWARDS @@ -644,11 +644,6 @@ class CardState(ItemState): ] self._columns.sort(key=itemgetter(1)) - def _load_active_columns(self) -> None: - self._active_columns = self.col.get_config( - "activeCols", ["noteFld", "template", "cardDue", "deck"] - ) - @property def columns(self) -> List[Tuple[str, str]]: return self._columns @@ -662,7 +657,7 @@ class CardState(ItemState): self._active_columns.remove(column) else: self._active_columns.append(column) - self.col.set_config("activeCols", self._active_columns) + self.col.set_browser_card_columns(self._active_columns) @property def sort_column(self) -> str: @@ -711,7 +706,7 @@ class NoteState(ItemState): def __init__(self, col: Collection) -> None: super().__init__(col) self._load_columns() - self._load_active_columns() + self._active_columns = self.col.load_browser_note_columns() self._sort_column = self.col.get_config("noteSortType") self._sort_backwards = self.col.get_config_bool( Config.Bool.BROWSER_NOTE_SORT_BACKWARDS @@ -731,11 +726,6 @@ class NoteState(ItemState): ] self._columns.sort(key=itemgetter(1)) - def _load_active_columns(self) -> None: - self._active_columns = self.col.get_config( - "activeNoteCols", ["noteFld", "note", "noteTags", "noteMod"] - ) - @property def columns(self) -> List[Tuple[str, str]]: return self._columns @@ -749,7 +739,7 @@ class NoteState(ItemState): self._active_columns.remove(column) else: self._active_columns.append(column) - self.col.set_config("activeNoteCols", self._active_columns) + self.col.set_browser_note_columns(self._active_columns) @property def sort_column(self) -> str: diff --git a/rslib/backend.proto b/rslib/backend.proto index e87af0a61..c8fd113f4 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -243,6 +243,8 @@ service SearchService { rpc ReplaceSearchNode(ReplaceSearchNodeIn) returns (String); rpc FindAndReplace(FindAndReplaceIn) returns (OpChangesWithCount); rpc BrowserRowForId(Int64) returns (BrowserRow); + rpc SetDesktopBrowserCardColumns(StringList) returns (Empty); + rpc SetDesktopBrowserNoteColumns(StringList) returns (Empty); } service StatsService { diff --git a/rslib/src/backend/search/browser_row.rs b/rslib/src/backend/search/browser_row.rs index d985cabb3..44404ded2 100644 --- a/rslib/src/backend/search/browser_row.rs +++ b/rslib/src/backend/search/browser_row.rs @@ -3,6 +3,39 @@ use crate::{backend_proto as pb, browser_rows}; +impl From for Vec { + fn from(input: pb::StringList) -> Self { + input.vals.into_iter().map(Into::into).collect() + } +} + +impl From for browser_rows::Column { + fn from(text: String) -> Self { + match text.as_str() { + "question" => browser_rows::Column::Question, + "answer" => browser_rows::Column::Answer, + "deck" => browser_rows::Column::CardDeck, + "cardDue" => browser_rows::Column::CardDue, + "cardEase" => browser_rows::Column::CardEase, + "cardLapses" => browser_rows::Column::CardLapses, + "cardIvl" => browser_rows::Column::CardInterval, + "cardMod" => browser_rows::Column::CardMod, + "cardReps" => browser_rows::Column::CardReps, + "template" => browser_rows::Column::CardTemplate, + "noteCards" => browser_rows::Column::NoteCards, + "noteCrt" => browser_rows::Column::NoteCreation, + "noteEase" => browser_rows::Column::NoteEase, + "noteFld" => browser_rows::Column::NoteField, + "noteLapses" => browser_rows::Column::NoteLapses, + "noteMod" => browser_rows::Column::NoteMod, + "noteReps" => browser_rows::Column::NoteReps, + "noteTags" => browser_rows::Column::NoteTags, + "note" => browser_rows::Column::Notetype, + _ => browser_rows::Column::Custom, + } + } +} + impl From for pb::BrowserRow { fn from(row: browser_rows::Row) -> Self { pb::BrowserRow { diff --git a/rslib/src/backend/search/mod.rs b/rslib/src/backend/search/mod.rs index 8a5e781cc..948583457 100644 --- a/rslib/src/backend/search/mod.rs +++ b/rslib/src/backend/search/mod.rs @@ -92,6 +92,16 @@ impl SearchService for Backend { fn browser_row_for_id(&self, input: pb::Int64) -> Result { self.with_col(|col| col.browser_row_for_id(input.val).map(Into::into)) } + + fn set_desktop_browser_card_columns(&self, input: pb::StringList) -> Result { + self.with_col(|col| col.set_desktop_browser_card_columns(input.into()))?; + Ok(().into()) + } + + fn set_desktop_browser_note_columns(&self, input: pb::StringList) -> Result { + self.with_col(|col| col.set_desktop_browser_note_columns(input.into()))?; + Ok(().into()) + } } impl From for SortKind { diff --git a/rslib/src/browser_rows.rs b/rslib/src/browser_rows.rs index 06388db7a..e699c8257 100644 --- a/rslib/src/browser_rows.rs +++ b/rslib/src/browser_rows.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use itertools::Itertools; +use serde_repr::{Deserialize_repr, Serialize_repr}; use crate::err::{AnkiError, Result}; use crate::i18n::I18n; @@ -20,7 +21,30 @@ use crate::{ timestamp::{TimestampMillis, TimestampSecs}, }; -const CARD_RENDER_COLUMNS: [&str; 2] = ["question", "answer"]; +#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Clone, Copy)] +#[repr(u8)] +pub enum Column { + Custom = 0, + Question = 1, + Answer = 2, + CardDeck = 3, + CardDue = 4, + CardEase = 5, + CardLapses = 6, + CardInterval = 7, + CardMod = 8, + CardReps = 9, + CardTemplate = 10, + NoteCards = 11, + NoteCreation = 12, + NoteEase = 13, + NoteField = 14, + NoteLapses = 15, + NoteMod = 16, + NoteReps = 17, + NoteTags = 18, + Notetype = 19, +} #[derive(Debug, PartialEq)] pub struct Row { @@ -53,13 +77,13 @@ pub struct Font { } trait RowContext { - fn get_cell_text(&mut self, column: &str) -> Result; + fn get_cell_text(&mut self, column: &Column) -> Result; fn get_row_color(&self) -> Color; fn get_row_font(&self) -> Result; fn note(&self) -> &Note; fn notetype(&self) -> &Notetype; - fn get_cell(&mut self, column: &str) -> Result { + fn get_cell(&mut self, column: &Column) -> Result { Ok(Cell { text: self.get_cell_text(column)?, is_rtl: self.get_is_rtl(column), @@ -77,9 +101,9 @@ trait RowContext { html_to_text_line(&self.note().fields()[index]).into() } - fn get_is_rtl(&self, column: &str) -> bool { + fn get_is_rtl(&self, column: &Column) -> bool { match column { - "noteFld" => { + Column::NoteField => { let index = self.notetype().config.sort_field_idx as usize; self.notetype().fields[index].config.rtl } @@ -87,7 +111,7 @@ trait RowContext { } } - fn browser_row_for_id(&mut self, columns: &[String]) -> Result { + fn browser_row_for_id(&mut self, columns: &[Column]) -> Result { Ok(Row { cells: columns .iter() @@ -125,20 +149,27 @@ struct NoteRowContext<'a> { tr: &'a I18n, } -fn card_render_required(columns: &[String]) -> bool { +fn card_render_required(columns: &[Column]) -> bool { columns .iter() - .any(|c| CARD_RENDER_COLUMNS.contains(&c.as_str())) + .any(|c| matches!(c, Column::Question | Column::Answer)) } impl Collection { pub fn browser_row_for_id(&mut self, id: i64) -> Result { if self.get_bool(BoolKey::BrowserTableShowNotesMode) { - let columns = self.get_desktop_browser_note_columns(); + let columns = + self.get_desktop_browser_note_columns() + .ok_or(AnkiError::InvalidInput { + info: "Note columns not set.".into(), + })?; NoteRowContext::new(self, id)?.browser_row_for_id(&columns) } else { - // this is inefficient; we may want to use an enum in the future - let columns = self.get_desktop_browser_card_columns(); + let columns = + self.get_desktop_browser_card_columns() + .ok_or(AnkiError::InvalidInput { + info: "Card columns not set.".into(), + })?; CardRowContext::new(self, id, card_render_required(&columns))? .browser_row_for_id(&columns) } @@ -329,23 +360,23 @@ impl<'a> CardRowContext<'a> { } impl RowContext for CardRowContext<'_> { - fn get_cell_text(&mut self, column: &str) -> Result { + fn get_cell_text(&mut self, column: &Column) -> Result { Ok(match column { - "answer" => self.answer_str(), - "cardDue" => self.card_due_str(), - "cardEase" => self.card_ease_str(), - "cardIvl" => self.card_interval_str(), - "cardLapses" => self.card.lapses.to_string(), - "cardMod" => self.card.mtime.date_string(), - "cardReps" => self.card.reps.to_string(), - "deck" => self.deck_str()?, - "note" => self.notetype.name.to_owned(), - "noteCrt" => self.note_creation_str(), - "noteFld" => self.note_field_str(), - "noteMod" => self.note.mtime.date_string(), - "noteTags" => self.note.tags.join(" "), - "question" => self.question_str(), - "template" => self.template_str()?, + Column::Question => self.question_str(), + Column::Answer => self.answer_str(), + Column::CardDeck => self.deck_str()?, + Column::CardDue => self.card_due_str(), + Column::CardEase => self.card_ease_str(), + Column::CardInterval => self.card_interval_str(), + Column::CardLapses => self.card.lapses.to_string(), + Column::CardMod => self.card.mtime.date_string(), + Column::CardReps => self.card.reps.to_string(), + Column::CardTemplate => self.template_str()?, + Column::NoteCreation => self.note_creation_str(), + Column::NoteField => self.note_field_str(), + Column::NoteMod => self.note.mtime.date_string(), + Column::NoteTags => self.note.tags.join(" "), + Column::Notetype => self.notetype.name.to_owned(), _ => "".to_string(), }) } @@ -421,17 +452,17 @@ impl<'a> NoteRowContext<'a> { } impl RowContext for NoteRowContext<'_> { - fn get_cell_text(&mut self, column: &str) -> Result { + fn get_cell_text(&mut self, column: &Column) -> Result { Ok(match column { - "note" => self.notetype.name.to_owned(), - "noteCards" => self.cards.len().to_string(), - "noteCrt" => self.note_creation_str(), - "noteEase" => self.note_ease_str(), - "noteFld" => self.note_field_str(), - "noteLapses" => self.cards.iter().map(|c| c.lapses).sum::().to_string(), - "noteMod" => self.note.mtime.date_string(), - "noteReps" => self.cards.iter().map(|c| c.reps).sum::().to_string(), - "noteTags" => self.note.tags.join(" "), + Column::NoteCards => self.cards.len().to_string(), + Column::NoteCreation => self.note_creation_str(), + Column::NoteEase => self.note_ease_str(), + Column::NoteField => self.note_field_str(), + Column::NoteLapses => self.cards.iter().map(|c| c.lapses).sum::().to_string(), + Column::NoteMod => self.note.mtime.date_string(), + Column::NoteReps => self.cards.iter().map(|c| c.reps).sum::().to_string(), + Column::NoteTags => self.note.tags.join(" "), + Column::Notetype => self.notetype.name.to_owned(), _ => "".to_string(), }) } diff --git a/rslib/src/config/mod.rs b/rslib/src/config/mod.rs index 0c61b3f77..1c3372e07 100644 --- a/rslib/src/config/mod.rs +++ b/rslib/src/config/mod.rs @@ -9,6 +9,7 @@ mod string; pub(crate) mod undo; pub use self::{bool::BoolKey, string::StringKey}; +use crate::browser_rows; use crate::prelude::*; use serde::{de::DeserializeOwned, Serialize}; use serde_derive::Deserialize; @@ -65,9 +66,7 @@ pub(crate) enum ConfigKey { #[strum(to_string = "schedVer")] SchedulerVersion, - #[strum(to_string = "activeCols")] DesktopBrowserCardColumns, - #[strum(to_string = "activeNoteCols")] DesktopBrowserNoteColumns, } @@ -140,28 +139,26 @@ impl Collection { self.get_config_default(ConfigKey::BrowserNoteSortKind) } - pub(crate) fn get_desktop_browser_card_columns(&self) -> Vec { + pub(crate) fn get_desktop_browser_card_columns(&self) -> Option> { self.get_config_optional(ConfigKey::DesktopBrowserCardColumns) - .unwrap_or_else(|| { - vec![ - "noteFld".to_string(), - "template".to_string(), - "cardDue".to_string(), - "deck".to_string(), - ] - }) } - pub(crate) fn get_desktop_browser_note_columns(&self) -> Vec { + pub(crate) fn set_desktop_browser_card_columns( + &mut self, + columns: Vec, + ) -> Result<()> { + self.set_config(ConfigKey::DesktopBrowserCardColumns, &columns) + } + + pub(crate) fn get_desktop_browser_note_columns(&self) -> Option> { self.get_config_optional(ConfigKey::DesktopBrowserNoteColumns) - .unwrap_or_else(|| { - vec![ - "noteFld".to_string(), - "note".to_string(), - "noteTags".to_string(), - "noteMod".to_string(), - ] - }) + } + + pub(crate) fn set_desktop_browser_note_columns( + &mut self, + columns: Vec, + ) -> Result<()> { + self.set_config(ConfigKey::DesktopBrowserNoteColumns, &columns) } pub(crate) fn get_creation_utc_offset(&self) -> Option {