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>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@
|
|||||||
perf.data
|
perf.data
|
||||||
perf.data.old
|
perf.data.old
|
||||||
target
|
target
|
||||||
|
queries/damus-notifs.json
|
||||||
.git
|
.git
|
||||||
cache
|
cache
|
||||||
/dist
|
/dist
|
||||||
|
|||||||
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -2557,8 +2557,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostrdb"
|
name = "nostrdb"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/damus-io/nostrdb-rs?rev=b6c5d8fb9f3f79f14d69dd2d6eca2712e0f0a42d#b6c5d8fb9f3f79f14d69dd2d6eca2712e0f0a42d"
|
||||||
checksum = "a4d574f011d7bca4fcb7167da332da7a73d5612b24a187df08f5a000de08d3f4"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bindgen",
|
"bindgen",
|
||||||
"cc",
|
"cc",
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ serde_json = "1.0.89"
|
|||||||
env_logger = "0.10.0"
|
env_logger = "0.10.0"
|
||||||
puffin_egui = { version = "0.27.0", optional = true }
|
puffin_egui = { version = "0.27.0", optional = true }
|
||||||
puffin = { version = "0.19.0", optional = true }
|
puffin = { version = "0.19.0", optional = true }
|
||||||
#nostrdb = { git = "https://github.com/damus-io/nostrdb-rs", rev = "2675e7244554e40c9ee10d82b42bc647fef4c17d" }
|
nostrdb = { git = "https://github.com/damus-io/nostrdb-rs", rev = "b6c5d8fb9f3f79f14d69dd2d6eca2712e0f0a42d" }
|
||||||
nostrdb = "0.3.2"
|
#nostrdb = "0.3.2"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
base32 = "0.4.0"
|
base32 = "0.4.0"
|
||||||
nostr-sdk = "0.29.0"
|
nostr-sdk = "0.29.0"
|
||||||
|
|||||||
2
preview
Executable file
2
preview
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
cargo run --bin ui_preview -- "$@"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use nostrdb::ProfileRecord;
|
use nostrdb::ProfileRecord;
|
||||||
|
|
||||||
pub fn get_profile_name<'a>(record: &'a ProfileRecord) -> Option<&'a str> {
|
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 display_name = profile.display_name();
|
||||||
let name = profile.name();
|
let name = profile.name();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use enostr::RelayPool;
|
use enostr::RelayPool;
|
||||||
|
use nostrdb::ProfileRecord;
|
||||||
|
|
||||||
#[allow(unused_must_use)]
|
#[allow(unused_must_use)]
|
||||||
pub fn sample_pool() -> RelayPool {
|
pub fn sample_pool() -> RelayPool {
|
||||||
@@ -17,3 +18,34 @@ pub fn sample_pool() -> RelayPool {
|
|||||||
|
|
||||||
pool
|
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()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
pub mod note;
|
pub mod note;
|
||||||
pub mod preview;
|
pub mod preview;
|
||||||
|
pub mod profile;
|
||||||
pub mod relay;
|
pub mod relay;
|
||||||
pub mod username;
|
pub mod username;
|
||||||
|
|
||||||
pub use note::Note;
|
pub use note::Note;
|
||||||
pub use preview::{Preview, PreviewApp};
|
pub use preview::{Preview, PreviewApp};
|
||||||
|
pub use profile::ProfilePreview;
|
||||||
pub use relay::RelayView;
|
pub use relay::RelayView;
|
||||||
pub use username::Username;
|
pub use username::Username;
|
||||||
|
|
||||||
|
|||||||
@@ -123,16 +123,27 @@ fn render_note_contents(
|
|||||||
match block.blocktype() {
|
match block.blocktype() {
|
||||||
BlockType::MentionBech32 => match block.as_mention().unwrap() {
|
BlockType::MentionBech32 => match block.as_mention().unwrap() {
|
||||||
Mention::Pubkey(npub) => {
|
Mention::Pubkey(npub) => {
|
||||||
ui.colored_label(colors::PURPLE, "@");
|
ui.horizontal(|ui| {
|
||||||
let profile = damus.ndb.get_profile_by_pubkey(txn, npub.pubkey()).ok();
|
let profile = damus.ndb.get_profile_by_pubkey(txn, npub.pubkey()).ok();
|
||||||
if let Some(name) = profile
|
|
||||||
.as_ref()
|
let name: String = if let Some(name) =
|
||||||
.and_then(|p| crate::profile::get_profile_name(p))
|
profile.as_ref().and_then(crate::profile::get_profile_name)
|
||||||
{
|
{
|
||||||
ui.colored_label(colors::PURPLE, name);
|
format!("@{}", name)
|
||||||
} else {
|
} else {
|
||||||
ui.colored_label(colors::PURPLE, "nostrich");
|
"@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() => {
|
Mention::Note(note) if options.has_note_previews() => {
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ impl<'a> Note<'a> {
|
|||||||
match profile
|
match profile
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|p| p.record.profile()?.picture())
|
.and_then(|p| p.record().profile()?.picture())
|
||||||
{
|
{
|
||||||
// these have different lifetimes and types,
|
// these have different lifetimes and types,
|
||||||
// so the calls must be separate
|
// so the calls must be separate
|
||||||
|
|||||||
3
src/ui/profile/mod.rs
Normal file
3
src/ui/profile/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod preview;
|
||||||
|
|
||||||
|
pub use preview::ProfilePreview;
|
||||||
65
src/ui/profile/preview.rs
Normal file
65
src/ui/profile/preview.rs
Normal file
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,7 +44,7 @@ impl<'a> Widget for Username<'a> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(profile) = self.profile {
|
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() != "" {
|
if prof.display_name().is_some() && prof.display_name().unwrap() != "" {
|
||||||
ui_abbreviate_name(ui, prof.display_name().unwrap(), self.abbrev, color);
|
ui_abbreviate_name(ui, prof.display_name().unwrap(), self.abbrev, color);
|
||||||
} else if let Some(name) = prof.name() {
|
} else if let Some(name) = prof.name() {
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ use notedeck::account_login_view::AccountLoginView;
|
|||||||
use notedeck::app_creation::{
|
use notedeck::app_creation::{
|
||||||
generate_mobile_emulator_native_options, generate_native_options, setup_cc,
|
generate_mobile_emulator_native_options, generate_native_options, setup_cc,
|
||||||
};
|
};
|
||||||
use notedeck::relay_view::RelayView;
|
use notedeck::ui::{Preview, PreviewApp, ProfilePreview, RelayView};
|
||||||
use notedeck::ui::{Preview, PreviewApp};
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
struct PreviewRunner {
|
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]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let mut name: Option<String> = None;
|
let mut name: Option<String> = None;
|
||||||
@@ -60,13 +73,5 @@ async fn main() {
|
|||||||
|
|
||||||
let runner = PreviewRunner::new(is_mobile);
|
let runner = PreviewRunner::new(is_mobile);
|
||||||
|
|
||||||
match name.as_ref() {
|
previews!(runner, name, RelayView, AccountLoginView, ProfilePreview,);
|
||||||
"AccountLoginView" => {
|
|
||||||
runner.run(AccountLoginView::preview()).await;
|
|
||||||
}
|
|
||||||
"RelayView" => {
|
|
||||||
runner.run(RelayView::preview()).await;
|
|
||||||
}
|
|
||||||
_ => println!("Component not found."),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user