notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit 482a3cb818950ad37ca540b44dea37246a8c7635
parent 47e0b0ed52e92aa5954ad005aa36a223107d3060
Author: William Casarin <jb55@jb55.com>
Date:   Tue, 17 Dec 2024 10:18:19 -0800

columns: move from Cow<'static, str> to ColumnTitle<'a>

This further deliminates our column titles to those that are simple,
and to those that require additional information from the database.

This allows us to avoid creating many transactions pointlessly if we
don't need to.

Changelog-Changed: Show usernames in user columns

Diffstat:
Mcrates/notedeck_columns/src/route.rs | 45++++++++++++++++++++++-----------------------
Mcrates/notedeck_columns/src/timeline/kind.rs | 73++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mcrates/notedeck_columns/src/timeline/mod.rs | 2+-
Mcrates/notedeck_columns/src/ui/column/header.rs | 59+++++++++++++++++++++++++++++++++++++++++------------------
4 files changed, 130 insertions(+), 49 deletions(-)

diff --git a/crates/notedeck_columns/src/route.rs b/crates/notedeck_columns/src/route.rs @@ -1,13 +1,10 @@ use enostr::{NoteId, Pubkey}; -use std::{ - borrow::Cow, - fmt::{self}, -}; +use std::fmt::{self}; use crate::{ accounts::AccountsRoute, column::Columns, - timeline::{TimelineId, TimelineRoute}, + timeline::{kind::ColumnTitle, TimelineId, TimelineRoute}, ui::add_column::AddColumnRoute, }; @@ -65,7 +62,7 @@ impl Route { Route::Accounts(AccountsRoute::AddAccount) } - pub fn title(&self, columns: &Columns) -> Cow<'static, str> { + pub fn title<'a>(&self, columns: &'a Columns) -> ColumnTitle<'a> { match self { Route::Timeline(tlr) => match tlr { TimelineRoute::Timeline(id) => { @@ -74,36 +71,38 @@ impl Route { .expect("expected to find timeline"); timeline.kind.to_title() } - TimelineRoute::Thread(_id) => Cow::Borrowed("Thread"), - TimelineRoute::Reply(_id) => Cow::Borrowed("Reply"), - TimelineRoute::Quote(_id) => Cow::Borrowed("Quote"), - TimelineRoute::Profile(_pubkey) => Cow::Borrowed("Profile"), + TimelineRoute::Thread(_id) => ColumnTitle::simple("Thread"), + TimelineRoute::Reply(_id) => ColumnTitle::simple("Reply"), + TimelineRoute::Quote(_id) => ColumnTitle::simple("Quote"), + TimelineRoute::Profile(_pubkey) => ColumnTitle::simple("Profile"), }, - Route::Relays => Cow::Borrowed("Relays"), + Route::Relays => ColumnTitle::simple("Relays"), Route::Accounts(amr) => match amr { - AccountsRoute::Accounts => Cow::Borrowed("Accounts"), - AccountsRoute::AddAccount => Cow::Borrowed("Add Account"), + AccountsRoute::Accounts => ColumnTitle::simple("Accounts"), + AccountsRoute::AddAccount => ColumnTitle::simple("Add Account"), }, - Route::ComposeNote => Cow::Borrowed("Compose Note"), + Route::ComposeNote => ColumnTitle::simple("Compose Note"), Route::AddColumn(c) => match c { - AddColumnRoute::Base => Cow::Borrowed("Add Column"), - AddColumnRoute::UndecidedNotification => Cow::Borrowed("Add Notifications Column"), + AddColumnRoute::Base => ColumnTitle::simple("Add Column"), + AddColumnRoute::UndecidedNotification => { + ColumnTitle::simple("Add Notifications Column") + } AddColumnRoute::ExternalNotification => { - Cow::Borrowed("Add External Notifications Column") + ColumnTitle::simple("Add External Notifications Column") } - AddColumnRoute::Hashtag => Cow::Borrowed("Add Hashtag Column"), + AddColumnRoute::Hashtag => ColumnTitle::simple("Add Hashtag Column"), AddColumnRoute::UndecidedIndividual => { - Cow::Borrowed("Subscribe to someone's notes") + ColumnTitle::simple("Subscribe to someone's notes") } AddColumnRoute::ExternalIndividual => { - Cow::Borrowed("Subscribe to someone else's notes") + ColumnTitle::simple("Subscribe to someone else's notes") } }, - Route::Support => Cow::Borrowed("Damus Support"), - Route::NewDeck => Cow::Borrowed("Add Deck"), - Route::EditDeck(_) => Cow::Borrowed("Edit Deck"), + Route::Support => ColumnTitle::simple("Damus Support"), + Route::NewDeck => ColumnTitle::simple("Add Deck"), + Route::EditDeck(_) => ColumnTitle::simple("Edit Deck"), } } } diff --git a/crates/notedeck_columns/src/timeline/kind.rs b/crates/notedeck_columns/src/timeline/kind.rs @@ -194,16 +194,75 @@ impl TimelineKind { } } - pub fn to_title(&self) -> Cow<'static, str> { + pub fn to_title(&self) -> ColumnTitle<'_> { match self { TimelineKind::List(list_kind) => match list_kind { - ListKind::Contact(_pubkey_source) => Cow::Borrowed("Contacts"), + ListKind::Contact(_pubkey_source) => ColumnTitle::simple("Contacts"), }, - TimelineKind::Notifications(_pubkey_source) => Cow::Borrowed("Notifications"), - TimelineKind::Profile(_pubkey_source) => Cow::Borrowed("Notes"), - TimelineKind::Universe => Cow::Borrowed("Universe"), - TimelineKind::Generic => Cow::Borrowed("Custom"), - TimelineKind::Hashtag(hashtag) => Cow::Owned(format!("#{}", hashtag)), + TimelineKind::Notifications(_pubkey_source) => ColumnTitle::simple("Notifications"), + TimelineKind::Profile(_pubkey_source) => ColumnTitle::needs_db(self), + TimelineKind::Universe => ColumnTitle::simple("Universe"), + TimelineKind::Generic => ColumnTitle::simple("Custom"), + TimelineKind::Hashtag(hashtag) => ColumnTitle::formatted(format!("#{}", hashtag)), } } } + +#[derive(Debug)] +pub struct TitleNeedsDb<'a> { + kind: &'a TimelineKind, +} + +impl<'a> TitleNeedsDb<'a> { + pub fn new(kind: &'a TimelineKind) -> Self { + TitleNeedsDb { kind } + } + + pub fn title<'txn>( + &self, + txn: &'txn Transaction, + ndb: &Ndb, + deck_author: Option<&Pubkey>, + ) -> &'txn str { + if let TimelineKind::Profile(pubkey_source) = self.kind { + if let Some(deck_author) = deck_author { + let pubkey = pubkey_source.to_pubkey(deck_author); + let profile = ndb.get_profile_by_pubkey(txn, pubkey); + let m_name = profile + .ok() + .as_ref() + .and_then(|p| crate::profile::get_profile_name(p)) + .map(|display_name| display_name.username()); + + m_name.unwrap_or("Profile") + } else { + // why would be there be no deck author? weird + "nostrich" + } + } else { + "Unknown" + } + } +} + +/// This saves us from having to construct a transaction if we don't need to +/// for a particular column when rendering the title +#[derive(Debug)] +pub enum ColumnTitle<'a> { + Simple(Cow<'static, str>), + NeedsDb(TitleNeedsDb<'a>), +} + +impl<'a> ColumnTitle<'a> { + pub fn simple(title: &'static str) -> Self { + Self::Simple(Cow::Borrowed(title)) + } + + pub fn formatted(title: String) -> Self { + Self::Simple(Cow::Owned(title)) + } + + pub fn needs_db(kind: &'a TimelineKind) -> ColumnTitle<'a> { + Self::NeedsDb(TitleNeedsDb::new(kind)) + } +} diff --git a/crates/notedeck_columns/src/timeline/mod.rs b/crates/notedeck_columns/src/timeline/mod.rs @@ -26,7 +26,7 @@ use tracing::{debug, error, info, warn}; pub mod kind; pub mod route; -pub use kind::{PubkeySource, TimelineKind}; +pub use kind::{ColumnTitle, PubkeySource, TimelineKind}; pub use route::TimelineRoute; #[derive(Debug, Hash, Copy, Clone, Eq, PartialEq)] diff --git a/crates/notedeck_columns/src/ui/column/header.rs b/crates/notedeck_columns/src/ui/column/header.rs @@ -2,7 +2,7 @@ use crate::{ column::Columns, nav::RenderNavAction, route::Route, - timeline::{TimelineId, TimelineRoute}, + timeline::{ColumnTitle, TimelineId, TimelineRoute}, ui::{ self, anim::{AnimationHelper, ICON_EXPANSION_MULTIPLE}, @@ -105,17 +105,29 @@ impl<'a> NavTitle<'a> { // not it looks cool self.title_pfp(ui, prev, 32.0); - let back_label = ui.add( - egui::Label::new( - RichText::new(prev.title(self.columns).to_string()) - .color(color) - .text_style(NotedeckTextStyle::Body.text_style()), - ) - .selectable(false) - .sense(egui::Sense::click()), - ); + let column_title = prev.title(self.columns); + + let back_resp = match &column_title { + ColumnTitle::Simple(title) => ui.add(Self::back_label(title, color)), + + ColumnTitle::NeedsDb(need_db) => { + let txn = Transaction::new(self.ndb).unwrap(); + let title = need_db.title(&txn, self.ndb, self.deck_author); + ui.add(Self::back_label(title, color)) + } + }; + + back_resp.union(chev_resp) + } - back_label.union(chev_resp) + fn back_label(title: &str, color: egui::Color32) -> egui::Label { + egui::Label::new( + RichText::new(title.to_string()) + .color(color) + .text_style(NotedeckTextStyle::Body.text_style()), + ) + .selectable(false) + .sense(egui::Sense::click()) } fn delete_column_button(&self, ui: &mut egui::Ui, icon_width: f32) -> egui::Response { @@ -209,14 +221,25 @@ impl<'a> NavTitle<'a> { } } + fn title_label_value(title: &str) -> egui::Label { + egui::Label::new(RichText::new(title).text_style(NotedeckTextStyle::Body.text_style())) + .selectable(false) + } + fn title_label(&self, ui: &mut egui::Ui, top: &Route) { - ui.add( - egui::Label::new( - RichText::new(top.title(self.columns)) - .text_style(NotedeckTextStyle::Body.text_style()), - ) - .selectable(false), - ); + let column_title = top.title(self.columns); + + match &column_title { + ColumnTitle::Simple(title) => { + ui.add(Self::title_label_value(title)); + } + + ColumnTitle::NeedsDb(need_db) => { + let txn = Transaction::new(self.ndb).unwrap(); + let title = need_db.title(&txn, self.ndb, self.deck_author); + ui.add(Self::title_label_value(title)); + } + }; } fn title(