diff --git a/rslib/backend.proto b/rslib/backend.proto index 06fe920e1..0d5b2cb55 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -248,6 +248,8 @@ service SearchService { rpc JoinSearchNodes(JoinSearchNodesIn) returns (String); rpc ReplaceSearchNode(ReplaceSearchNodeIn) returns (String); rpc FindAndReplace(FindAndReplaceIn) returns (OpChangesWithCount); + rpc AllBrowserCardColumns(Empty) returns (BrowserColumns); + rpc AllBrowserNoteColumns(Empty) returns (BrowserColumns); rpc BrowserRowForId(Int64) returns (BrowserRow); rpc SetDesktopBrowserCardColumns(StringList) returns (Empty); rpc SetDesktopBrowserNoteColumns(StringList) returns (Empty); @@ -1052,6 +1054,19 @@ message FindAndReplaceIn { string field_name = 6; } +message BrowserColumns { + repeated BrowserColumn columns = 1; +} + +message BrowserColumn { + string key = 1; + string label = 2; + bool is_sortable = 3; + bool sorts_reversed = 4; + bool uses_cell_font = 5; + bool aligns_centered = 6; +} + message BrowserRow { message Cell { string text = 1; diff --git a/rslib/src/backend/search/browser_table.rs b/rslib/src/backend/search/browser_table.rs index cb405769a..f53c57845 100644 --- a/rslib/src/backend/search/browser_table.rs +++ b/rslib/src/backend/search/browser_table.rs @@ -1,41 +1,129 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -use crate::{backend_proto as pb, browser_table}; +use std::str::FromStr; -impl From for Vec { - fn from(input: pb::StringList) -> Self { - input.vals.into_iter().map(Into::into).collect() +use crate::{backend_proto as pb, browser_table, collection::Collection, i18n::I18n}; + +const CARD_COLUMNS: [browser_table::Column; 15] = [ + browser_table::Column::Question, + browser_table::Column::Answer, + browser_table::Column::CardDeck, + browser_table::Column::CardDue, + browser_table::Column::CardEase, + browser_table::Column::CardLapses, + browser_table::Column::CardInterval, + browser_table::Column::CardMod, + browser_table::Column::CardReps, + browser_table::Column::CardTemplate, + browser_table::Column::NoteCreation, + browser_table::Column::NoteField, + browser_table::Column::NoteMod, + browser_table::Column::NoteTags, + browser_table::Column::Notetype, +]; + +const NOTE_COLUMNS: [browser_table::Column; 11] = [ + browser_table::Column::NoteCards, + browser_table::Column::NoteCreation, + browser_table::Column::NoteDue, + browser_table::Column::NoteEase, + browser_table::Column::NoteField, + browser_table::Column::NoteInterval, + browser_table::Column::NoteLapses, + browser_table::Column::NoteMod, + browser_table::Column::NoteReps, + browser_table::Column::NoteTags, + browser_table::Column::Notetype, +]; + +impl Collection { + pub(crate) fn all_browser_card_columns(&self) -> pb::BrowserColumns { + self.to_pb_columns(&CARD_COLUMNS) + } + + pub(crate) fn all_browser_note_columns(&self) -> pb::BrowserColumns { + self.to_pb_columns(&NOTE_COLUMNS) + } + + fn to_pb_columns(&self, columns: &[browser_table::Column]) -> pb::BrowserColumns { + let mut columns: Vec = + columns.iter().map(|c| c.to_pb_column(&self.tr)).collect(); + columns.sort_by(|c1, c2| c1.label.cmp(&c2.label)); + pb::BrowserColumns { columns } } } -impl From for browser_table::Column { - fn from(text: String) -> Self { - match text.as_str() { - "question" => browser_table::Column::Question, - "answer" => browser_table::Column::Answer, - "deck" => browser_table::Column::CardDeck, - "cardDue" => browser_table::Column::CardDue, - "cardEase" => browser_table::Column::CardEase, - "cardLapses" => browser_table::Column::CardLapses, - "cardIvl" => browser_table::Column::CardInterval, - "cardMod" => browser_table::Column::CardMod, - "cardReps" => browser_table::Column::CardReps, - "template" => browser_table::Column::CardTemplate, - "noteCards" => browser_table::Column::NoteCards, - "noteCrt" => browser_table::Column::NoteCreation, - "noteDue" => browser_table::Column::NoteDue, - "noteEase" => browser_table::Column::NoteEase, - "noteFld" => browser_table::Column::NoteField, - "noteIvl" => browser_table::Column::NoteInterval, - "noteLapses" => browser_table::Column::NoteLapses, - "noteMod" => browser_table::Column::NoteMod, - "noteReps" => browser_table::Column::NoteReps, - "noteTags" => browser_table::Column::NoteTags, - "note" => browser_table::Column::Notetype, - _ => browser_table::Column::Custom, +impl browser_table::Column { + fn to_pb_column(self, i18n: &I18n) -> pb::BrowserColumn { + pb::BrowserColumn { + key: self.to_string(), + label: self.localized_label(i18n), + is_sortable: self.is_sortable(), + sorts_reversed: self == browser_table::Column::NoteField, + uses_cell_font: self.uses_cell_font(), + aligns_centered: self.aligns_centered(), } } + + fn is_sortable(self) -> bool { + !matches!(self, Self::Question | Self::Answer | Self::Custom) + } + + fn uses_cell_font(self) -> bool { + matches!(self, Self::Question | Self::Answer | Self::NoteField) + } + + fn aligns_centered(self) -> bool { + !matches!( + self, + Self::Question + | Self::Answer + | Self::CardTemplate + | Self::CardDeck + | Self::NoteField + | Self::Notetype + | Self::NoteTags + ) + } + + fn localized_label(self, i18n: &I18n) -> String { + match self { + Self::Custom => i18n.browsing_addon(), + Self::Question => i18n.browsing_question(), + Self::Answer => i18n.browsing_answer(), + Self::CardDeck => i18n.decks_deck(), + Self::CardDue => i18n.statistics_due_date(), + Self::CardEase => i18n.browsing_ease(), + Self::CardInterval => i18n.browsing_interval(), + Self::CardLapses => i18n.scheduling_lapses(), + Self::CardMod => i18n.search_card_modified(), + Self::CardReps => i18n.scheduling_reviews(), + Self::CardTemplate => i18n.browsing_card(), + Self::NoteCards => i18n.editing_cards(), + Self::NoteCreation => i18n.browsing_created(), + Self::NoteDue => i18n.statistics_due_date(), + Self::NoteEase => i18n.browsing_average_ease(), + Self::NoteField => i18n.browsing_sort_field(), + Self::NoteInterval => i18n.browsing_average_interval(), + Self::NoteMod => i18n.search_note_modified(), + Self::NoteLapses => i18n.scheduling_lapses(), + Self::NoteReps => i18n.scheduling_reviews(), + Self::NoteTags => i18n.editing_tags(), + Self::Notetype => i18n.browsing_note(), + } + .into() + } +} + +impl From for Vec { + fn from(input: pb::StringList) -> Self { + input + .vals + .iter() + .map(|c| browser_table::Column::from_str(c).unwrap_or_default()) + .collect() + } } impl From for pb::BrowserRow { diff --git a/rslib/src/backend/search/mod.rs b/rslib/src/backend/search/mod.rs index 5aaf25780..5a09b3427 100644 --- a/rslib/src/backend/search/mod.rs +++ b/rslib/src/backend/search/mod.rs @@ -89,6 +89,14 @@ impl SearchService for Backend { }) } + fn all_browser_card_columns(&self, _input: pb::Empty) -> Result { + self.with_col(|col| Ok(col.all_browser_card_columns())) + } + + fn all_browser_note_columns(&self, _input: pb::Empty) -> Result { + self.with_col(|col| Ok(col.all_browser_note_columns())) + } + fn browser_row_for_id(&self, input: pb::Int64) -> Result { self.with_col(|col| col.browser_row_for_id(input.val).map(Into::into)) }