diff --git a/Cargo.lock b/Cargo.lock index 5f36c7b..a77c4f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2556,8 +2556,8 @@ dependencies = [ [[package]] name = "nostrdb" -version = "0.3.2" -source = "git+https://github.com/damus-io/nostrdb-rs?rev=1489a5aee49996d8a6a54b4c3c9c8397e3e8d40f#1489a5aee49996d8a6a54b4c3c9c8397e3e8d40f" +version = "0.3.3" +source = "git+https://github.com/damus-io/nostrdb-rs?rev=99d8296fcba5957245ed883e2f3b1c0d1cb16397#99d8296fcba5957245ed883e2f3b1c0d1cb16397" dependencies = [ "bindgen", "cc", diff --git a/Cargo.toml b/Cargo.toml index 3fa2596..d456aa1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,8 +31,8 @@ serde_json = "1.0.89" env_logger = "0.10.0" puffin_egui = { version = "0.27.0", optional = true } puffin = { version = "0.19.0", optional = true } -nostrdb = { git = "https://github.com/damus-io/nostrdb-rs", rev = "1489a5aee49996d8a6a54b4c3c9c8397e3e8d40f" } -#nostrdb = "0.3.2" +nostrdb = { git = "https://github.com/damus-io/nostrdb-rs", rev = "99d8296fcba5957245ed883e2f3b1c0d1cb16397" } +#nostrdb = "0.3.3" hex = "0.4.3" base32 = "0.4.0" nostr-sdk = "0.29.0" diff --git a/src/app.rs b/src/app.rs index b0d0cfd..4a2b752 100644 --- a/src/app.rs +++ b/src/app.rs @@ -499,10 +499,10 @@ impl Damus { } } - pub fn get_note_cache_mut(&mut self, note_key: NoteKey, created_at: u64) -> &mut NoteCache { + pub fn get_note_cache_mut(&mut self, note_key: NoteKey, note: &Note<'_>) -> &mut NoteCache { self.note_cache .entry(note_key) - .or_insert_with(|| NoteCache::new(created_at)) + .or_insert_with(|| NoteCache::new(note)) } } diff --git a/src/notecache.rs b/src/notecache.rs index cf17e9b..66fe2ce 100644 --- a/src/notecache.rs +++ b/src/notecache.rs @@ -1,20 +1,28 @@ use crate::time::time_ago_since; use crate::timecache::TimeCached; +use nostrdb::{Note, NoteReply, NoteReplyBuf}; use std::time::Duration; pub struct NoteCache { reltime: TimeCached, + pub reply: NoteReplyBuf, pub bar_open: bool, } impl NoteCache { - pub fn new(created_at: u64) -> Self { + pub fn new(note: &Note<'_>) -> Self { + let created_at = note.created_at(); let reltime = TimeCached::new( Duration::from_secs(1), Box::new(move || time_ago_since(created_at)), ); + let reply = NoteReply::new(note.tags()).to_owned(); let bar_open = false; - NoteCache { reltime, bar_open } + NoteCache { + reltime, + reply, + bar_open, + } } pub fn reltime_str(&mut self) -> &str { diff --git a/src/ui/mention.rs b/src/ui/mention.rs new file mode 100644 index 0000000..7fb15f3 --- /dev/null +++ b/src/ui/mention.rs @@ -0,0 +1,61 @@ +use crate::{colors, ui, Damus}; +use nostrdb::Transaction; + +pub struct Mention<'a> { + app: &'a mut Damus, + txn: &'a Transaction, + pk: &'a [u8; 32], + size: f32, +} + +impl<'a> Mention<'a> { + pub fn new(app: &'a mut Damus, txn: &'a Transaction, pk: &'a [u8; 32]) -> Self { + let size = 16.0; + Mention { app, txn, pk, size } + } + + pub fn size(mut self, size: f32) -> Self { + self.size = size; + self + } +} + +impl<'a> egui::Widget for Mention<'a> { + fn ui(self, ui: &mut egui::Ui) -> egui::Response { + mention_ui(self.app, self.txn, self.pk, ui, self.size) + } +} + +fn mention_ui( + app: &mut Damus, + txn: &Transaction, + pk: &[u8; 32], + ui: &mut egui::Ui, + size: f32, +) -> egui::Response { + #[cfg(feature = "profiling")] + puffin::profile_function!(); + + ui.horizontal(|ui| { + let profile = app.ndb.get_profile_by_pubkey(txn, pk).ok(); + + let name: String = + if let Some(name) = profile.as_ref().and_then(crate::profile::get_profile_name) { + format!("@{}", name.username()) + } else { + "??".to_string() + }; + + let resp = ui.add(egui::Label::new( + egui::RichText::new(name).color(colors::PURPLE).size(size), + )); + + if let Some(rec) = profile.as_ref() { + resp.on_hover_ui_at_pointer(|ui| { + ui.set_max_width(300.0); + ui.add(ui::ProfilePreview::new(rec, &mut app.img_cache)); + }); + } + }) + .response +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 60c3ad0..2ddbe4b 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,10 +1,12 @@ pub mod anim; +pub mod mention; pub mod note; pub mod preview; pub mod profile; pub mod relay; pub mod username; +pub use mention::Mention; pub use note::Note; pub use preview::{Preview, PreviewApp}; pub use profile::{ProfilePic, ProfilePreview}; diff --git a/src/ui/note/contents.rs b/src/ui/note/contents.rs index 5894e5d..621ea08 100644 --- a/src/ui/note/contents.rs +++ b/src/ui/note/contents.rs @@ -97,29 +97,6 @@ fn render_note_preview( .response } -fn mention_ui(app: &mut Damus, txn: &Transaction, pk: &[u8; 32], ui: &mut egui::Ui) { - #[cfg(feature = "profiling")] - puffin::profile_function!(); - - let profile = app.ndb.get_profile_by_pubkey(txn, pk).ok(); - - let name: String = - if let Some(name) = profile.as_ref().and_then(crate::profile::get_profile_name) { - format!("@{}", name.username()) - } else { - "??".to_string() - }; - - let resp = ui.colored_label(colors::PURPLE, &name); - - if let Some(rec) = profile.as_ref() { - resp.on_hover_ui_at_pointer(|ui| { - ui.set_max_width(300.0); - ui.add(ui::ProfilePreview::new(rec, &mut app.img_cache)); - }); - } -} - fn render_note_contents( ui: &mut egui::Ui, damus: &mut Damus, @@ -149,11 +126,11 @@ fn render_note_contents( match block.blocktype() { BlockType::MentionBech32 => match block.as_mention().unwrap() { Mention::Profile(profile) => { - mention_ui(damus, txn, profile.pubkey(), ui); + ui.add(ui::Mention::new(damus, txn, profile.pubkey())); } Mention::Pubkey(npub) => { - mention_ui(damus, txn, npub.pubkey(), ui); + ui.add(ui::Mention::new(damus, txn, npub.pubkey())); } Mention::Note(note) if options.has_note_previews() => { diff --git a/src/ui/note/mod.rs b/src/ui/note/mod.rs index eb3b255..0bdf40e 100644 --- a/src/ui/note/mod.rs +++ b/src/ui/note/mod.rs @@ -6,6 +6,7 @@ pub use options::NoteOptions; use crate::{colors, ui, Damus}; use egui::{Label, RichText, Sense}; +use nostrdb::{NoteKey, Transaction}; use std::hash::{Hash, Hasher}; pub struct Note<'a> { @@ -38,6 +39,90 @@ impl Hash for ProfileAnimId { } } +fn reply_desc( + ui: &mut egui::Ui, + txn: &Transaction, + app: &mut Damus, + note_key: NoteKey, + note: &nostrdb::Note<'_>, +) { + #[cfg(feature = "profiling")] + puffin::profile_function!(); + + let note_reply = app + .get_note_cache_mut(note_key, note) + .reply + .borrow(note.tags()); + + let reply = if let Some(reply) = note_reply.reply() { + reply + } else { + // not a reply, nothing to do here + return; + }; + + ui.add(Label::new( + RichText::new("replying to") + .size(10.0) + .color(colors::GRAY_SECONDARY), + )); + + let reply_note = if let Ok(reply_note) = app.ndb.get_note_by_id(txn, reply.id) { + reply_note + } else { + ui.add(Label::new( + RichText::new("a note") + .size(10.0) + .color(colors::GRAY_SECONDARY), + )); + return; + }; + + if note_reply.is_reply_to_root() { + // We're replying to the root, let's show this + ui.add(ui::Mention::new(app, txn, reply_note.pubkey()).size(10.0)); + ui.add(Label::new( + RichText::new("'s note") + .size(10.0) + .color(colors::GRAY_SECONDARY), + )); + } else if let Some(root) = note_reply.root() { + // replying to another post in a thread, not the root + + if let Ok(root_note) = app.ndb.get_note_by_id(txn, root.id) { + if root_note.pubkey() == reply_note.pubkey() { + // simply "replying to bob's note" when replying to bob in his thread + ui.add(ui::Mention::new(app, txn, reply_note.pubkey()).size(10.0)); + ui.add(Label::new( + RichText::new("'s note") + .size(10.0) + .color(colors::GRAY_SECONDARY), + )); + } else { + // replying to bob in alice's thread + + ui.add(ui::Mention::new(app, txn, reply_note.pubkey()).size(10.0)); + ui.add(Label::new( + RichText::new("in").size(10.0).color(colors::GRAY_SECONDARY), + )); + ui.add(ui::Mention::new(app, txn, root_note.pubkey()).size(10.0)); + ui.add(Label::new( + RichText::new("'s thread") + .size(10.0) + .color(colors::GRAY_SECONDARY), + )); + } + } else { + ui.add(ui::Mention::new(app, txn, reply_note.pubkey()).size(10.0)); + ui.add(Label::new( + RichText::new("in someone's thread") + .size(10.0) + .color(colors::GRAY_SECONDARY), + )); + } + } +} + impl<'a> Note<'a> { pub fn new(app: &'a mut Damus, note: &'a nostrdb::Note<'a>) -> Self { let flags = NoteOptions::actionbar | NoteOptions::note_previews; @@ -69,32 +154,30 @@ impl<'a> Note<'a> { ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| { let profile = self.app.ndb.get_profile_by_pubkey(txn, self.note.pubkey()); - ui.horizontal(|ui| { - ui.spacing_mut().item_spacing.x = 2.0; + //ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 2.0; - let note_cache = self - .app - .get_note_cache_mut(note_key, self.note.created_at()); + let note_cache = self.app.get_note_cache_mut(note_key, self.note); - let (_id, rect) = ui.allocate_space(egui::vec2(50.0, 20.0)); - ui.allocate_rect(rect, Sense::hover()); - ui.put(rect, |ui: &mut egui::Ui| { - render_reltime(ui, note_cache, false).response - }); - let (_id, rect) = ui.allocate_space(egui::vec2(150.0, 20.0)); - ui.allocate_rect(rect, Sense::hover()); - ui.put(rect, |ui: &mut egui::Ui| { - ui.add( - ui::Username::new(profile.as_ref().ok(), self.note.pubkey()) - .abbreviated(8) - .pk_colored(true), - ) - }); - - ui.add(NoteContents::new( - self.app, txn, self.note, note_key, self.flags, - )); + let (_id, rect) = ui.allocate_space(egui::vec2(50.0, 20.0)); + ui.allocate_rect(rect, Sense::hover()); + ui.put(rect, |ui: &mut egui::Ui| { + render_reltime(ui, note_cache, false).response }); + let (_id, rect) = ui.allocate_space(egui::vec2(150.0, 20.0)); + ui.allocate_rect(rect, Sense::hover()); + ui.put(rect, |ui: &mut egui::Ui| { + ui.add( + ui::Username::new(profile.as_ref().ok(), self.note.pubkey()) + .abbreviated(8) + .pk_colored(true), + ) + }); + + ui.add(NoteContents::new( + self.app, txn, self.note, note_key, self.flags, + )); + //}); }) .response } @@ -163,12 +246,15 @@ impl<'a> Note<'a> { .abbreviated(20), ); - let note_cache = self - .app - .get_note_cache_mut(note_key, self.note.created_at()); + let note_cache = self.app.get_note_cache_mut(note_key, self.note); render_reltime(ui, note_cache, true); }); + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 2.0; + reply_desc(ui, txn, self.app, note_key, self.note); + }); + ui.add(NoteContents::new( self.app, txn, @@ -210,6 +296,12 @@ fn render_note_actionbar(ui: &mut egui::Ui) -> egui::InnerResponse<()> { }) } +fn secondary_label(ui: &mut egui::Ui, s: impl Into) { + ui.add(Label::new( + RichText::new(s).size(10.0).color(colors::GRAY_SECONDARY), + )); +} + fn render_reltime( ui: &mut egui::Ui, note_cache: &mut crate::notecache::NoteCache, @@ -220,19 +312,13 @@ fn render_reltime( ui.horizontal(|ui| { if before { - ui.add(Label::new( - RichText::new("⋅").size(10.0).color(colors::GRAY_SECONDARY), - )); + secondary_label(ui, "⋅"); } - ui.add(Label::new( - RichText::new(note_cache.reltime_str()) - .size(10.0) - .color(colors::GRAY_SECONDARY), - )); + + secondary_label(ui, note_cache.reltime_str()); + if !before { - ui.add(Label::new( - RichText::new("⋅").size(10.0).color(colors::GRAY_SECONDARY), - )); + secondary_label(ui, "⋅"); } }) } diff --git a/src/ui/relay.rs b/src/ui/relay.rs index 329f826..7ce321b 100644 --- a/src/ui/relay.rs +++ b/src/ui/relay.rs @@ -115,13 +115,16 @@ fn add_relay_button() -> egui::Button<'static> { Button::new("+ Add relay").min_size(Vec2::new(0.0, 32.0)) } -fn delete_button(dark_mode: bool) -> egui::Button<'static> { +fn delete_button(_dark_mode: bool) -> egui::Button<'static> { + /* let img_data = if dark_mode { egui::include_image!("../../assets/icons/delete_icon_4x.png") } else { // TODO: use light delete icon egui::include_image!("../../assets/icons/delete_icon_4x.png") }; + */ + let img_data = egui::include_image!("../../assets/icons/delete_icon_4x.png"); egui::Button::image(egui::Image::new(img_data).max_width(10.0)).frame(false) }