notedeck

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

commit 05fe164a49357f4e50e61ef9c61b46e30af1c709
parent 2d566cc637a4fd1f14cdc698e54bcba2f20a2387
Author: William Casarin <jb55@jb55.com>
Date:   Fri, 19 Apr 2024 22:00:19 -0700

ui: add initial Profile hover previews

The idea with these is that on notedeck you can just hover your cursor
over a profile link to see the profile. I just have a stub for now, but
full design coming soon after.

Also simplify the preview system even further with a macro. In the
future I imagine we can grep every preview in the codebase, and then
include this as a string inside this macro. This is some kind of
template metaprogramming insanity but in theory it could work.

Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
M.gitignore | 1+
MCargo.lock | 3+--
MCargo.toml | 4++--
Apreview | 2++
Msrc/profile.rs | 2+-
Msrc/test_data.rs | 32++++++++++++++++++++++++++++++++
Msrc/ui/mod.rs | 2++
Msrc/ui/note/contents.rs | 31+++++++++++++++++++++----------
Msrc/ui/note/mod.rs | 2+-
Asrc/ui/profile/mod.rs | 3+++
Asrc/ui/profile/preview.rs | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/ui/username.rs | 2+-
Msrc/ui_preview/main.rs | 27++++++++++++++++-----------
13 files changed, 148 insertions(+), 28 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -3,6 +3,7 @@ perf.data perf.data.old target +queries/damus-notifs.json .git cache /dist diff --git a/Cargo.lock b/Cargo.lock @@ -2557,8 +2557,7 @@ dependencies = [ [[package]] name = "nostrdb" version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4d574f011d7bca4fcb7167da332da7a73d5612b24a187df08f5a000de08d3f4" +source = "git+https://github.com/damus-io/nostrdb-rs?rev=b6c5d8fb9f3f79f14d69dd2d6eca2712e0f0a42d#b6c5d8fb9f3f79f14d69dd2d6eca2712e0f0a42d" dependencies = [ "bindgen", "cc", diff --git 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 = "2675e7244554e40c9ee10d82b42bc647fef4c17d" } -nostrdb = "0.3.2" +nostrdb = { git = "https://github.com/damus-io/nostrdb-rs", rev = "b6c5d8fb9f3f79f14d69dd2d6eca2712e0f0a42d" } +#nostrdb = "0.3.2" hex = "0.4.3" base32 = "0.4.0" nostr-sdk = "0.29.0" diff --git a/preview b/preview @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +cargo run --bin ui_preview -- "$@" diff --git a/src/profile.rs b/src/profile.rs @@ -1,7 +1,7 @@ use nostrdb::ProfileRecord; pub fn get_profile_name<'a>(record: &'a ProfileRecord) -> Option<&'a str> { - let profile = record.record.profile()?; + let profile = record.record().profile()?; let display_name = profile.display_name(); let name = profile.name(); diff --git a/src/test_data.rs b/src/test_data.rs @@ -1,4 +1,5 @@ use enostr::RelayPool; +use nostrdb::ProfileRecord; #[allow(unused_must_use)] pub fn sample_pool() -> RelayPool { @@ -17,3 +18,34 @@ pub fn sample_pool() -> RelayPool { pool } + +const TEST_PROFILE_DATA: [u8; 384] = [ + 0x04, 0x00, 0x00, 0x00, 0x94, 0xfe, 0xff, 0xff, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x19, 0xef, 0x9c, 0x65, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x57, 0x26, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x66, 0x69, 0x78, 0x6d, + 0x65, 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xe0, 0xff, 0xff, 0xff, + 0xd8, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0xb4, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, + 0x60, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x20, 0x00, + 0x1c, 0x00, 0x08, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6a, 0x62, 0x35, 0x35, + 0x00, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x64, 0x61, 0x6d, 0x75, 0x73, 0x2e, 0x69, 0x6f, + 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x2e, 0x20, 0x62, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, + 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6e, 0x6f, 0x73, 0x74, 0x72, 0x20, 0x64, 0x65, 0x76, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x57, 0x69, 0x6c, 0x6c, 0x69, 0x61, 0x6d, 0x20, 0x43, 0x61, 0x73, 0x61, + 0x72, 0x69, 0x6e, 0x20, 0xf0, 0x9f, 0x87, 0xa8, 0xf0, 0x9f, 0x87, 0xa6, 0xe2, 0x9a, 0xa1, 0xef, + 0xb8, 0x8f, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, + 0x63, 0x64, 0x6e, 0x2e, 0x6a, 0x62, 0x35, 0x35, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x6d, 0x67, + 0x2f, 0x72, 0x65, 0x64, 0x2d, 0x6d, 0x65, 0x2e, 0x6a, 0x70, 0x67, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x6a, 0x62, 0x35, 0x35, 0x40, 0x6a, 0x62, 0x35, 0x35, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x6c, 0x6e, 0x75, 0x72, + 0x6c, 0x31, 0x64, 0x70, 0x36, 0x38, 0x67, 0x75, 0x72, 0x6e, 0x38, 0x67, 0x68, 0x6a, 0x37, 0x75, + 0x6d, 0x39, 0x64, 0x65, 0x6a, 0x38, 0x78, 0x63, 0x74, 0x35, 0x77, 0x76, 0x68, 0x78, 0x63, 0x6d, + 0x6d, 0x76, 0x39, 0x75, 0x68, 0x38, 0x77, 0x65, 0x74, 0x76, 0x64, 0x73, 0x6b, 0x6b, 0x6b, 0x6d, + 0x6e, 0x30, 0x77, 0x61, 0x68, 0x7a, 0x37, 0x6d, 0x72, 0x77, 0x77, 0x34, 0x65, 0x78, 0x63, 0x75, + 0x70, 0x30, 0x64, 0x66, 0x33, 0x72, 0x32, 0x64, 0x67, 0x33, 0x6d, 0x6a, 0x34, 0x34, 0x34, 0x00, + 0x0c, 0x00, 0x24, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x1c, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + +pub fn test_profile_record() -> ProfileRecord<'static> { + ProfileRecord::new_owned(&TEST_PROFILE_DATA).unwrap() +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs @@ -1,10 +1,12 @@ pub mod note; pub mod preview; +pub mod profile; pub mod relay; pub mod username; pub use note::Note; pub use preview::{Preview, PreviewApp}; +pub use profile::ProfilePreview; pub use relay::RelayView; pub use username::Username; diff --git a/src/ui/note/contents.rs b/src/ui/note/contents.rs @@ -123,16 +123,27 @@ fn render_note_contents( match block.blocktype() { BlockType::MentionBech32 => match block.as_mention().unwrap() { Mention::Pubkey(npub) => { - ui.colored_label(colors::PURPLE, "@"); - let profile = damus.ndb.get_profile_by_pubkey(txn, npub.pubkey()).ok(); - if let Some(name) = profile - .as_ref() - .and_then(|p| crate::profile::get_profile_name(p)) - { - ui.colored_label(colors::PURPLE, name); - } else { - ui.colored_label(colors::PURPLE, "nostrich"); - } + ui.horizontal(|ui| { + let profile = damus.ndb.get_profile_by_pubkey(txn, npub.pubkey()).ok(); + + let name: String = if let Some(name) = + profile.as_ref().and_then(crate::profile::get_profile_name) + { + format!("@{}", name) + } else { + "@nostrich".to_string() + }; + + let resp = ui.colored_label(colors::PURPLE, &name); + + if let Some(rec) = profile.as_ref() { + resp.on_hover_ui_at_pointer(|ui| { + egui::Frame::default().show(ui, |ui| { + ui.add(ui::ProfilePreview::new(rec)); + }); + }); + } + }); } Mention::Note(note) if options.has_note_previews() => { diff --git a/src/ui/note/mod.rs b/src/ui/note/mod.rs @@ -106,7 +106,7 @@ impl<'a> Note<'a> { match profile .as_ref() .ok() - .and_then(|p| p.record.profile()?.picture()) + .and_then(|p| p.record().profile()?.picture()) { // these have different lifetimes and types, // so the calls must be separate diff --git a/src/ui/profile/mod.rs b/src/ui/profile/mod.rs @@ -0,0 +1,3 @@ +pub mod preview; + +pub use preview::ProfilePreview; diff --git a/src/ui/profile/preview.rs b/src/ui/profile/preview.rs @@ -0,0 +1,65 @@ +use nostrdb::ProfileRecord; + +pub struct ProfilePreview<'a> { + profile: &'a ProfileRecord<'a>, +} + +impl<'a> ProfilePreview<'a> { + pub fn new(profile: &'a ProfileRecord<'a>) -> Self { + ProfilePreview { profile } + } +} + +impl<'a> egui::Widget for ProfilePreview<'a> { + fn ui(self, ui: &mut egui::Ui) -> egui::Response { + ui.horizontal(|ui| { + ui.label("Profile"); + let name = if let Some(name) = crate::profile::get_profile_name(self.profile) { + name + } else { + "nostrich" + }; + ui.label(name); + }) + .response + } +} + +mod previews { + use super::*; + use crate::test_data::test_profile_record; + use crate::ui::{Preview, View}; + use egui::Widget; + + pub struct ProfilePreviewPreview<'a> { + profile: ProfileRecord<'a>, + } + + impl<'a> ProfilePreviewPreview<'a> { + pub fn new() -> Self { + let profile = test_profile_record(); + ProfilePreviewPreview { profile } + } + } + + impl<'a> Default for ProfilePreviewPreview<'a> { + fn default() -> Self { + ProfilePreviewPreview::new() + } + } + + impl<'a> View for ProfilePreviewPreview<'a> { + fn ui(&mut self, ui: &mut egui::Ui) { + ProfilePreview::new(&self.profile).ui(ui); + } + } + + impl<'a> Preview for ProfilePreview<'a> { + /// A preview of the profile preview :D + type Prev = ProfilePreviewPreview<'a>; + + fn preview() -> Self::Prev { + ProfilePreviewPreview::new() + } + } +} diff --git a/src/ui/username.rs b/src/ui/username.rs @@ -44,7 +44,7 @@ impl<'a> Widget for Username<'a> { }; if let Some(profile) = self.profile { - if let Some(prof) = profile.record.profile() { + if let Some(prof) = profile.record().profile() { if prof.display_name().is_some() && prof.display_name().unwrap() != "" { ui_abbreviate_name(ui, prof.display_name().unwrap(), self.abbrev, color); } else if let Some(name) = prof.name() { diff --git a/src/ui_preview/main.rs b/src/ui_preview/main.rs @@ -2,8 +2,7 @@ use notedeck::account_login_view::AccountLoginView; use notedeck::app_creation::{ generate_mobile_emulator_native_options, generate_native_options, setup_cc, }; -use notedeck::relay_view::RelayView; -use notedeck::ui::{Preview, PreviewApp}; +use notedeck::ui::{Preview, PreviewApp, ProfilePreview, RelayView}; use std::env; struct PreviewRunner { @@ -38,6 +37,20 @@ impl PreviewRunner { } } +macro_rules! previews { + // Accept a runner and name variable, followed by one or more identifiers for the views + ($runner:expr, $name:expr, $($view:ident),* $(,)?) => { + match $name.as_ref() { + $( + stringify!($view) => { + $runner.run($view::preview()).await; + } + )* + _ => println!("Component not found."), + } + }; +} + #[tokio::main] async fn main() { let mut name: Option<String> = None; @@ -60,13 +73,5 @@ async fn main() { let runner = PreviewRunner::new(is_mobile); - match name.as_ref() { - "AccountLoginView" => { - runner.run(AccountLoginView::preview()).await; - } - "RelayView" => { - runner.run(RelayView::preview()).await; - } - _ => println!("Component not found."), - } + previews!(runner, name, RelayView, AccountLoginView, ProfilePreview,); }