notedeck

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

commit 69a6bf3664761864ff0fd00131283f84771ea0a7
parent 5b4c7a63715569dcc79dafbf271fc6f6c1ce01ce
Author: kernelkind <kernelkind@gmail.com>
Date:   Mon, 16 Dec 2024 18:01:36 -0500

column: add individual column

A column for following a single user

Closes: https://github.com/damus-io/notedeck/pull/583
Signed-off-by: kernelkind <kernelkind@gmail.com>

Diffstat:
Mcrates/notedeck_columns/src/route.rs | 6++++++
Mcrates/notedeck_columns/src/storage/decks.rs | 27+++++++++++++++++++++------
Mcrates/notedeck_columns/src/timeline/kind.rs | 2+-
Mcrates/notedeck_columns/src/ui/add_column.rs | 108++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
4 files changed, 133 insertions(+), 10 deletions(-)

diff --git a/crates/notedeck_columns/src/route.rs b/crates/notedeck_columns/src/route.rs @@ -94,6 +94,12 @@ impl Route { Cow::Borrowed("Add External Notifications Column") } AddColumnRoute::Hashtag => Cow::Borrowed("Add Hashtag Column"), + AddColumnRoute::UndecidedIndividual => { + Cow::Borrowed("Subscribe to someone's notes") + } + AddColumnRoute::ExternalIndividual => { + Cow::Borrowed("Subscribe to someone else's notes") + } }, Route::Support => Cow::Borrowed("Damus Support"), Route::NewDeck => Cow::Borrowed("Add Deck"), diff --git a/crates/notedeck_columns/src/storage/decks.rs b/crates/notedeck_columns/src/storage/decks.rs @@ -304,12 +304,7 @@ fn deserialize_columns(ndb: &Ndb, deck_user: &[u8; 32], serialized: Vec<Vec<Stri match &ir { IntermediaryRoute::Route(Route::Timeline(TimelineRoute::Thread(_))) | IntermediaryRoute::Route(Route::Timeline(TimelineRoute::Profile(_))) => { - // Do nothing. Threads & Profiles not yet supported for deserialization - } - IntermediaryRoute::Timeline(tl) - if matches!(tl.kind, TimelineKind::Profile(_)) => - { - // Do nothing. Profiles aren't yet supported for deserialization + // Do nothing. TimelineRoute Threads & Profiles not yet supported for deserialization } _ => cur_routes.push(ir), } @@ -361,6 +356,8 @@ enum Keyword { Support, Deck, Edit, + IndividualSelection, + ExternalIndividualSelection, } impl Keyword { @@ -526,6 +523,12 @@ fn serialize_route(route: &Route, columns: &Columns) -> Option<String> { AddColumnRoute::Hashtag => { selections.push(Selection::Keyword(Keyword::HashtagSelection)) } + AddColumnRoute::UndecidedIndividual => { + selections.push(Selection::Keyword(Keyword::IndividualSelection)) + } + AddColumnRoute::ExternalIndividual => { + selections.push(Selection::Keyword(Keyword::ExternalIndividualSelection)) + } } } Route::Support => selections.push(Selection::Keyword(Keyword::Support)), @@ -717,6 +720,16 @@ fn selections_to_route(selections: Vec<Selection>) -> Option<CleanIntermediaryRo Selection::Keyword(Keyword::HashtagSelection) => Some(CleanIntermediaryRoute::ToRoute( Route::AddColumn(AddColumnRoute::Hashtag), )), + Selection::Keyword(Keyword::IndividualSelection) => { + Some(CleanIntermediaryRoute::ToRoute(Route::AddColumn( + AddColumnRoute::UndecidedIndividual, + ))) + } + Selection::Keyword(Keyword::ExternalIndividualSelection) => { + Some(CleanIntermediaryRoute::ToRoute(Route::AddColumn( + AddColumnRoute::ExternalIndividual, + ))) + } _ => None, }, Selection::Keyword(Keyword::Support) => { @@ -746,6 +759,8 @@ fn selections_to_route(selections: Vec<Selection>) -> Option<CleanIntermediaryRo | Selection::Keyword(Keyword::NotificationSelection) | Selection::Keyword(Keyword::ExternalNotifSelection) | Selection::Keyword(Keyword::HashtagSelection) + | Selection::Keyword(Keyword::IndividualSelection) + | Selection::Keyword(Keyword::ExternalIndividualSelection) | Selection::Keyword(Keyword::Edit) => None, } } diff --git a/crates/notedeck_columns/src/timeline/kind.rs b/crates/notedeck_columns/src/timeline/kind.rs @@ -200,7 +200,7 @@ impl TimelineKind { ListKind::Contact(_pubkey_source) => Cow::Borrowed("Contacts"), }, TimelineKind::Notifications(_pubkey_source) => Cow::Borrowed("Notifications"), - TimelineKind::Profile(_pubkey_source) => Cow::Borrowed("Profile"), + TimelineKind::Profile(_pubkey_source) => Cow::Borrowed("Notes"), TimelineKind::Universe => Cow::Borrowed("Universe"), TimelineKind::Generic => Cow::Borrowed("Custom"), TimelineKind::Hashtag(hashtag) => Cow::Owned(format!("#{}", hashtag)), diff --git a/crates/notedeck_columns/src/ui/add_column.rs b/crates/notedeck_columns/src/ui/add_column.rs @@ -5,6 +5,7 @@ use egui::{ pos2, vec2, Align, Button, Color32, FontId, Id, ImageSource, Margin, Pos2, Rect, RichText, Separator, Ui, Vec2, }; +use enostr::Pubkey; use nostrdb::Ndb; use tracing::error; @@ -24,6 +25,8 @@ pub enum AddColumnResponse { UndecidedNotification, ExternalNotification, Hashtag, + UndecidedIndividual, + ExternalIndividual, } pub enum NotificationColumnType { @@ -40,6 +43,9 @@ enum AddColumnOption { Home(PubkeySource), UndecidedHashtag, Hashtag(String), + UndecidedIndividual, + ExternalIndividual, + Individual(PubkeySource), } #[derive(Clone, Copy, Eq, PartialEq, Debug)] @@ -48,6 +54,8 @@ pub enum AddColumnRoute { UndecidedNotification, ExternalNotification, Hashtag, + UndecidedIndividual, + ExternalIndividual, } impl AddColumnOption { @@ -76,6 +84,13 @@ impl AddColumnOption { AddColumnOption::Hashtag(hashtag) => TimelineKind::Hashtag(hashtag) .into_timeline(ndb, None) .map(AddColumnResponse::Timeline), + AddColumnOption::UndecidedIndividual => Some(AddColumnResponse::UndecidedIndividual), + AddColumnOption::ExternalIndividual => Some(AddColumnResponse::ExternalIndividual), + AddColumnOption::Individual(pubkey_source) => { + let tlk = TimelineKind::profile(pubkey_source); + tlk.into_timeline(ndb, cur_account.map(|a| a.pubkey.bytes())) + .map(AddColumnResponse::Timeline) + } } } } @@ -128,8 +143,42 @@ impl<'a> AddColumnView<'a> { } fn external_notification_ui(&mut self, ui: &mut Ui) -> Option<AddColumnResponse> { + let id = ui.id().with("external_notif"); + + self.external_ui(ui, id, |pubkey| { + AddColumnOption::Notification(PubkeySource::Explicit(pubkey)) + }) + } + + fn individual_ui(&mut self, ui: &mut Ui) -> Option<AddColumnResponse> { + let mut selected_option: Option<AddColumnResponse> = None; + for column_option_data in self.get_individual_options() { + let option = column_option_data.option.clone(); + if self.column_option_ui(ui, column_option_data).clicked() { + selected_option = option.take_as_response(self.ndb, self.cur_account); + } + + ui.add(Separator::default().spacing(0.0)); + } + + selected_option + } + + fn external_individual_ui(&mut self, ui: &mut Ui) -> Option<AddColumnResponse> { + let id = ui.id().with("external_individual"); + + self.external_ui(ui, id, |pubkey| { + AddColumnOption::Individual(PubkeySource::Explicit(pubkey)) + }) + } + + fn external_ui( + &mut self, + ui: &mut Ui, + id: egui::Id, + to_tl: fn(Pubkey) -> AddColumnOption, + ) -> Option<AddColumnResponse> { padding(16.0, ui, |ui| { - let id = ui.id().with("external_notif"); let key_state = self.key_state_map.entry(id).or_default(); let text_edit = key_state.get_acquire_textedit(|text| { @@ -164,8 +213,7 @@ impl<'a> AddColumnView<'a> { if let Some(keypair) = key_state.check_for_successful_login() { key_state.should_create_new(); - AddColumnOption::Notification(PubkeySource::Explicit(keypair.pubkey)) - .take_as_response(self.ndb, self.cur_account) + to_tl(keypair.pubkey).take_as_response(self.ndb, self.cur_account) } else { None } @@ -309,6 +357,12 @@ impl<'a> AddColumnView<'a> { icon: egui::include_image!("../../../../assets/icons/notifications_icon_dark_4x.png"), option: AddColumnOption::UndecidedHashtag, }); + vec.push(ColumnOptionData { + title: "Individual", + description: "Stay up to date with someone's notes & replies", + icon: egui::include_image!("../../../../assets/icons/notifications_icon_dark_4x.png"), + option: AddColumnOption::UndecidedIndividual, + }); vec } @@ -342,6 +396,36 @@ impl<'a> AddColumnView<'a> { vec } + + fn get_individual_options(&self) -> Vec<ColumnOptionData> { + let mut vec = Vec::new(); + + if let Some(acc) = self.cur_account { + let source = if acc.secret_key.is_some() { + PubkeySource::DeckAuthor + } else { + PubkeySource::Explicit(acc.pubkey) + }; + + vec.push(ColumnOptionData { + title: "Your Notes", + description: "Keep track of your notes & replies", + icon: egui::include_image!( + "../../../../assets/icons/notifications_icon_dark_4x.png" + ), + option: AddColumnOption::Individual(source), + }); + } + + vec.push(ColumnOptionData { + title: "Someone else's Notes", + description: "Stay up to date with someone else's notes & replies", + icon: egui::include_image!("../../../../assets/icons/notifications_icon_dark_4x.png"), + option: AddColumnOption::ExternalIndividual, + }); + + vec + } } struct ColumnOptionData { @@ -368,6 +452,8 @@ pub fn render_add_column_routes( AddColumnRoute::UndecidedNotification => add_column_view.notifications_ui(ui), AddColumnRoute::ExternalNotification => add_column_view.external_notification_ui(ui), AddColumnRoute::Hashtag => hashtag_ui(ui, ctx.ndb, &mut app.view_state.id_string_map), + AddColumnRoute::UndecidedIndividual => add_column_view.individual_ui(ui), + AddColumnRoute::ExternalIndividual => add_column_view.external_individual_ui(ui), }; if let Some(resp) = resp { @@ -407,6 +493,22 @@ pub fn render_add_column_routes( .router_mut() .route_to(crate::route::Route::AddColumn(AddColumnRoute::Hashtag)); } + AddColumnResponse::UndecidedIndividual => { + app.columns_mut(ctx.accounts) + .column_mut(col) + .router_mut() + .route_to(crate::route::Route::AddColumn( + AddColumnRoute::UndecidedIndividual, + )); + } + AddColumnResponse::ExternalIndividual => { + app.columns_mut(ctx.accounts) + .column_mut(col) + .router_mut() + .route_to(crate::route::Route::AddColumn( + AddColumnRoute::ExternalIndividual, + )); + } }; } }