UI rework and fixes, in preparation for three feeds

This commit is contained in:
Mike Dilger 2023-01-03 10:03:06 +13:00
parent 9c10c8a56c
commit f8bb4d74b0
11 changed files with 613 additions and 478 deletions

View File

@ -10,6 +10,9 @@ use egui::{
use linkify::{LinkFinder, LinkKind};
use nostr_types::{EventKind, Id, PublicKeyHex};
mod person;
mod thread;
struct FeedPostParams {
id: Id,
indent: usize,
@ -18,161 +21,181 @@ struct FeedPostParams {
}
pub(super) fn update(app: &mut GossipUi, ctx: &Context, frame: &mut eframe::Frame, ui: &mut Ui) {
let feed = GLOBALS.feed.blocking_lock().get();
Globals::trim_desired_events_sync();
let desired_count: isize = match GLOBALS.desired_events.try_read() {
Ok(v) => v.len() as isize,
Err(_) => -1,
};
let incoming_count: isize = match GLOBALS.incoming_events.try_read() {
Ok(v) => v.len() as isize,
Err(_) => -1,
};
ui.with_layout(Layout::right_to_left(Align::TOP), |ui| {
if ui
.button(&format!("QM {}", desired_count))
.on_hover_text("Query Relays for Missing Events")
.clicked()
{
let tx = GLOBALS.to_overlord.clone();
let _ = tx.send(BusMessage {
target: "overlord".to_string(),
kind: "get_missing_events".to_string(),
json_payload: serde_json::to_string("").unwrap(),
});
ui.horizontal(|ui| {
ui.selectable_value(&mut app.page, Page::FeedGeneral, "Following");
ui.separator();
if app.feed_thread_id.is_some() {
ui.selectable_value(&mut app.page, Page::FeedThread, "Thread");
ui.separator();
}
if ui
.button(&format!("PQ {}", incoming_count))
.on_hover_text("Process Queue of Incoming Events")
.clicked()
{
let tx = GLOBALS.to_overlord.clone();
let _ = tx.send(BusMessage {
target: "overlord".to_string(),
kind: "process_incoming_events".to_string(),
json_payload: serde_json::to_string("").unwrap(),
});
}
if ui.button("close all").clicked() {
app.hides = feed.clone();
}
if ui.button("open all").clicked() {
app.hides.clear();
}
ui.label(&format!(
"RIF={}",
GLOBALS
.fetcher
.requests_in_flight
.load(std::sync::atomic::Ordering::Relaxed)
));
});
ui.vertical(|ui| {
if !GLOBALS.signer.blocking_read().is_ready() {
ui.horizontal(|ui| {
ui.label("You need to ");
if ui.link("setup your identity").clicked() {
app.page = Page::You;
}
ui.label(" to post.");
});
} else if !GLOBALS.relays.blocking_read().iter().any(|(_, r)| r.post) {
ui.horizontal(|ui| {
ui.label("You need to ");
if ui.link("choose relays").clicked() {
app.page = Page::Relays;
}
ui.label(" to post.");
});
} else {
if let Some(id) = app.replying_to {
render_post_actual(
app,
ctx,
frame,
ui,
FeedPostParams {
id,
indent: 0,
as_reply_to: true,
threaded: false,
},
);
}
ui.with_layout(Layout::right_to_left(Align::TOP), |ui| {
if ui.button("Send").clicked() && !app.draft.is_empty() {
let tx = GLOBALS.to_overlord.clone();
match app.replying_to {
Some(_id) => {
let _ = tx.send(BusMessage {
target: "overlord".to_string(),
kind: "post_reply".to_string(),
json_payload: serde_json::to_string(&(
&app.draft,
&app.replying_to,
))
.unwrap(),
});
}
None => {
let _ = tx.send(BusMessage {
target: "overlord".to_string(),
kind: "post_textnote".to_string(),
json_payload: serde_json::to_string(&app.draft).unwrap(),
});
}
}
app.draft = "".to_owned();
app.replying_to = None;
}
if ui.button("Cancel").clicked() {
app.draft = "".to_owned();
app.replying_to = None;
}
ui.add(
TextEdit::multiline(&mut app.draft)
.hint_text("Type your message here")
.desired_width(f32::INFINITY)
.lock_focus(true),
);
});
if app.feed_person_pubkey.is_some() {
ui.selectable_value(&mut app.page, Page::FeedPerson, "Person");
ui.separator();
}
});
ui.separator();
let threaded = GLOBALS.settings.blocking_read().view_threaded;
if app.page == Page::FeedGeneral {
let feed = GLOBALS.feed.blocking_lock().get();
ScrollArea::vertical().show(ui, |ui| {
let bgcolor = if ctx.style().visuals.dark_mode {
Color32::BLACK
} else {
Color32::WHITE
Globals::trim_desired_events_sync();
let desired_count: isize = match GLOBALS.desired_events.try_read() {
Ok(v) => v.len() as isize,
Err(_) => -1,
};
Frame::none().fill(bgcolor).show(ui, |ui| {
for id in feed.iter() {
render_post_maybe_fake(
app,
ctx,
frame,
ui,
FeedPostParams {
id: *id,
indent: 0,
as_reply_to: false,
threaded,
},
);
let incoming_count: isize = match GLOBALS.incoming_events.try_read() {
Ok(v) => v.len() as isize,
Err(_) => -1,
};
ui.with_layout(Layout::right_to_left(Align::TOP), |ui| {
if ui
.button(&format!("QM {}", desired_count))
.on_hover_text("Query Relays for Missing Events")
.clicked()
{
let tx = GLOBALS.to_overlord.clone();
let _ = tx.send(BusMessage {
target: "overlord".to_string(),
kind: "get_missing_events".to_string(),
json_payload: serde_json::to_string("").unwrap(),
});
}
if ui
.button(&format!("PQ {}", incoming_count))
.on_hover_text("Process Queue of Incoming Events")
.clicked()
{
let tx = GLOBALS.to_overlord.clone();
let _ = tx.send(BusMessage {
target: "overlord".to_string(),
kind: "process_incoming_events".to_string(),
json_payload: serde_json::to_string("").unwrap(),
});
}
if ui.button("close all").clicked() {
app.hides = feed.clone();
}
if ui.button("open all").clicked() {
app.hides.clear();
}
ui.label(&format!(
"RIF={}",
GLOBALS
.fetcher
.requests_in_flight
.load(std::sync::atomic::Ordering::Relaxed)
));
});
ui.vertical(|ui| {
if !GLOBALS.signer.blocking_read().is_ready() {
ui.horizontal(|ui| {
ui.label("You need to ");
if ui.link("setup your identity").clicked() {
app.page = Page::You;
}
ui.label(" to post.");
});
} else if !GLOBALS.relays.blocking_read().iter().any(|(_, r)| r.post) {
ui.horizontal(|ui| {
ui.label("You need to ");
if ui.link("choose relays").clicked() {
app.page = Page::Relays;
}
ui.label(" to post.");
});
} else {
if let Some(id) = app.replying_to {
render_post_actual(
app,
ctx,
frame,
ui,
FeedPostParams {
id,
indent: 0,
as_reply_to: true,
threaded: false,
},
);
}
ui.with_layout(Layout::right_to_left(Align::TOP), |ui| {
if ui.button("Send").clicked() && !app.draft.is_empty() {
let tx = GLOBALS.to_overlord.clone();
match app.replying_to {
Some(_id) => {
let _ = tx.send(BusMessage {
target: "overlord".to_string(),
kind: "post_reply".to_string(),
json_payload: serde_json::to_string(&(
&app.draft,
&app.replying_to,
))
.unwrap(),
});
}
None => {
let _ = tx.send(BusMessage {
target: "overlord".to_string(),
kind: "post_textnote".to_string(),
json_payload: serde_json::to_string(&app.draft).unwrap(),
});
}
}
app.draft = "".to_owned();
app.replying_to = None;
}
if ui.button("Cancel").clicked() {
app.draft = "".to_owned();
app.replying_to = None;
}
ui.add(
TextEdit::multiline(&mut app.draft)
.hint_text("Type your message here")
.desired_width(f32::INFINITY)
.lock_focus(true),
);
});
}
});
});
ui.separator();
let threaded = GLOBALS.settings.blocking_read().view_threaded;
ScrollArea::vertical().show(ui, |ui| {
let bgcolor = if ctx.style().visuals.dark_mode {
Color32::BLACK
} else {
Color32::WHITE
};
Frame::none().fill(bgcolor).show(ui, |ui| {
for id in feed.iter() {
render_post_maybe_fake(
app,
ctx,
frame,
ui,
FeedPostParams {
id: *id,
indent: 0,
as_reply_to: false,
threaded,
},
);
}
});
});
} else if app.page == Page::FeedThread {
thread::update(app, ctx, frame, ui);
} else if app.page == Page::FeedPerson {
person::update(app, ctx, frame, ui);
}
}
fn render_post_maybe_fake(

7
src/ui/feed/person.rs Normal file
View File

@ -0,0 +1,7 @@
use super::GossipUi;
use eframe::egui;
use egui::{Context, Ui};
pub(super) fn update(_app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
ui.label("Person TBD");
}

7
src/ui/feed/thread.rs Normal file
View File

@ -0,0 +1,7 @@
use super::GossipUi;
use eframe::egui;
use egui::{Context, Ui};
pub(super) fn update(_app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
ui.label("Thread TBD");
}

71
src/ui/help/about.rs Normal file
View File

@ -0,0 +1,71 @@
use super::GossipUi;
use eframe::egui;
use egui::{Align, Context, Layout, RichText, TextStyle, Ui};
pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
ui.with_layout(Layout::top_down(Align::Center), |ui| {
ui.add_space(30.0);
ui.image(&app.icon, app.icon.size_vec2());
ui.add_space(15.0);
ui.label(
RichText::new(&app.about.name).strong()
);
ui.add_space(15.0);
ui.label(
RichText::new(&app.about.version)
.text_style(TextStyle::Body)
);
ui.add_space(15.0);
ui.label(
RichText::new(&app.about.description)
.text_style(TextStyle::Body)
);
ui.add_space(35.0);
ui.label(
RichText::new(format!("nostr is a protocol and specification for storing and retrieving social media events onto servers called relays. Many users store their events onto multiple relays for reliability, censorship resistance, and to spread their reach. If you didn't store an event on a particular relay, don't expect anyone to find it there because relays normally don't share events with each other.
Users are defined by their keypair, and are known by the public key of that pair. All events they generate are signed by their private key, and verifiable by their public key.
We are storing data on your system in this file: {}. This data is only used locally by this client - the nostr protocol does not use clients as a store of data for other people. We are storing your settings, your private and public key, information about relays, and a cache of events. We cache events in your feed so that we don't have to ask relays for them again, which means less network traffic and faster startup times.
", app.about.storage_path))
.text_style(TextStyle::Body)
);
ui.add_space(22.0);
ui.hyperlink_to("Learn More about Nostr", "https://github.com/nostr-protocol/nostr");
ui.add_space(30.0);
ui.hyperlink_to("Source Code", &app.about.homepage);
ui.label(
RichText::new("by")
.text_style(TextStyle::Small)
);
ui.label(
RichText::new(&app.about.authors)
.text_style(TextStyle::Small)
);
ui.add_space(15.0);
ui.label(
RichText::new("This program comes with absolutely no warranty.")
.text_style(TextStyle::Small)
);
ui.label(
RichText::new("See the MIT License for details.")
.text_style(TextStyle::Small)
);
});
}

View File

@ -1,16 +1,20 @@
use super::{GossipUi, Page};
use eframe::egui;
use egui::{Align, Context, Layout, RichText, ScrollArea, TextStyle, TopBottomPanel, Ui};
use egui::{Context, ScrollArea, Ui};
mod about;
mod stats;
pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
TopBottomPanel::top("help_menu").show(ctx, |ui| {
ui.horizontal(|ui| {
ui.selectable_value(&mut app.page, Page::HelpHelp, "Help");
ui.separator();
ui.selectable_value(&mut app.page, Page::HelpAbout, "About");
ui.separator();
});
ui.horizontal(|ui| {
ui.selectable_value(&mut app.page, Page::HelpHelp, "Help");
ui.separator();
ui.selectable_value(&mut app.page, Page::HelpStats, "Stats");
ui.separator();
ui.selectable_value(&mut app.page, Page::HelpAbout, "About");
ui.separator();
});
ui.separator();
if app.page == Page::HelpHelp {
ui.add_space(24.0);
@ -127,71 +131,9 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra
ui.add_space(10.0);
});
} else if app.page == Page::HelpStats {
stats::update(app, ctx, _frame, ui);
} else if app.page == Page::HelpAbout {
ui.with_layout(Layout::top_down(Align::Center), |ui| {
ui.add_space(30.0);
ui.image(&app.icon, app.icon.size_vec2());
ui.add_space(15.0);
ui.label(
RichText::new(&app.about.name).strong()
);
ui.add_space(15.0);
ui.label(
RichText::new(&app.about.version)
.text_style(TextStyle::Body)
);
ui.add_space(15.0);
ui.label(
RichText::new(&app.about.description)
.text_style(TextStyle::Body)
);
ui.add_space(35.0);
ui.label(
RichText::new(format!("nostr is a protocol and specification for storing and retrieving social media events onto servers called relays. Many users store their events onto multiple relays for reliability, censorship resistance, and to spread their reach. If you didn't store an event on a particular relay, don't expect anyone to find it there because relays normally don't share events with each other.
Users are defined by their keypair, and are known by the public key of that pair. All events they generate are signed by their private key, and verifiable by their public key.
We are storing data on your system in this file: {}. This data is only used locally by this client - the nostr protocol does not use clients as a store of data for other people. We are storing your settings, your private and public key, information about relays, and a cache of events. We cache events in your feed so that we don't have to ask relays for them again, which means less network traffic and faster startup times.
", app.about.storage_path))
.text_style(TextStyle::Body)
);
ui.add_space(22.0);
ui.hyperlink_to("Learn More about Nostr", "https://github.com/nostr-protocol/nostr");
ui.add_space(30.0);
ui.hyperlink_to("Source Code", &app.about.homepage);
ui.label(
RichText::new("by")
.text_style(TextStyle::Small)
);
ui.label(
RichText::new(&app.about.authors)
.text_style(TextStyle::Small)
);
ui.add_space(15.0);
ui.label(
RichText::new("This program comes with absolutely no warranty.")
.text_style(TextStyle::Small)
);
ui.label(
RichText::new("See the MIT License for details.")
.text_style(TextStyle::Small)
);
});
about::update(app, ctx, _frame, ui);
}
}

View File

@ -3,7 +3,6 @@ mod help;
mod people;
mod relays;
mod settings;
mod stats;
mod style;
mod widgets;
mod you;
@ -16,8 +15,8 @@ use crate::settings::Settings;
use crate::ui::widgets::CopyButton;
use eframe::{egui, IconData, Theme};
use egui::{
ColorImage, Context, ImageData, Label, RichText, Sense, TextStyle, TextureHandle,
TextureOptions, Ui,
ColorImage, Context, ImageData, Label, RichText, SelectableLabel, Sense, TextStyle,
TextureHandle, TextureOptions, Ui,
};
use nostr_types::{Id, PublicKey, PublicKeyHex};
use std::collections::HashMap;
@ -55,15 +54,17 @@ pub fn run() -> Result<(), Error> {
#[derive(PartialEq)]
enum Page {
Feed,
PeopleFollow,
FeedGeneral,
FeedThread,
FeedPerson,
PeopleList,
PeopleFollow,
Person,
You,
Relays,
Settings,
Stats,
HelpHelp,
HelpStats,
HelpAbout,
}
@ -88,6 +89,8 @@ struct GossipUi {
person_view_pubkey: Option<PublicKeyHex>,
avatars: HashMap<PublicKeyHex, TextureHandle>,
new_relay_url: String,
feed_thread_id: Option<Id>,
feed_person_pubkey: Option<PublicKeyHex>,
}
impl Drop for GossipUi {
@ -140,7 +143,7 @@ impl GossipUi {
GossipUi {
next_frame: Instant::now(),
page: Page::Feed,
page: Page::FeedGeneral,
status:
"Welcome to Gossip. Status messages will appear here. Click them to dismiss them."
.to_owned(),
@ -161,6 +164,8 @@ impl GossipUi {
person_view_pubkey: None,
avatars: HashMap::new(),
new_relay_url: "".to_owned(),
feed_thread_id: None,
feed_person_pubkey: None,
}
}
}
@ -187,19 +192,65 @@ impl eframe::App for GossipUi {
egui::TopBottomPanel::top("menu").show(ctx, |ui| {
ui.horizontal(|ui| {
ui.selectable_value(&mut self.page, Page::Feed, "Feed");
if ui
.add(SelectableLabel::new(
self.page == Page::FeedGeneral
|| self.page == Page::FeedThread
|| self.page == Page::FeedPerson,
"Feed",
))
.clicked()
{
self.page = Page::FeedGeneral;
}
ui.separator();
ui.selectable_value(&mut self.page, Page::PeopleList, "People");
if ui
.add(SelectableLabel::new(
self.page == Page::PeopleList
|| self.page == Page::PeopleFollow
|| self.page == Page::Person,
"People",
))
.clicked()
{
self.page = Page::PeopleList;
}
ui.separator();
ui.selectable_value(&mut self.page, Page::You, "You");
if ui
.add(SelectableLabel::new(self.page == Page::You, "You"))
.clicked()
{
self.page = Page::You;
}
ui.separator();
ui.selectable_value(&mut self.page, Page::Relays, "Relays");
if ui
.add(SelectableLabel::new(self.page == Page::Relays, "Relays"))
.clicked()
{
self.page = Page::Relays;
}
ui.separator();
ui.selectable_value(&mut self.page, Page::Settings, "Settings");
if ui
.add(SelectableLabel::new(
self.page == Page::Settings,
"Settings",
))
.clicked()
{
self.page = Page::Settings;
}
ui.separator();
ui.selectable_value(&mut self.page, Page::Stats, "Stats");
ui.separator();
ui.selectable_value(&mut self.page, Page::HelpHelp, "Help");
if ui
.add(SelectableLabel::new(
self.page == Page::HelpHelp
|| self.page == Page::HelpStats
|| self.page == Page::HelpAbout,
"Help",
))
.clicked()
{
self.page = Page::HelpHelp;
}
ui.separator();
});
});
@ -216,16 +267,18 @@ impl eframe::App for GossipUi {
});
egui::CentralPanel::default().show(ctx, |ui| match self.page {
Page::Feed => feed::update(self, ctx, frame, ui),
Page::PeopleList => people::update(self, ctx, frame, ui),
Page::PeopleFollow => people::update(self, ctx, frame, ui),
Page::Person => people::update(self, ctx, frame, ui),
Page::FeedGeneral | Page::FeedThread | Page::FeedPerson => {
feed::update(self, ctx, frame, ui)
}
Page::PeopleList | Page::PeopleFollow | Page::Person => {
people::update(self, ctx, frame, ui)
}
Page::You => you::update(self, ctx, frame, ui),
Page::Relays => relays::update(self, ctx, frame, ui),
Page::Settings => settings::update(self, ctx, frame, ui, darkmode),
Page::Stats => stats::update(self, ctx, frame, ui),
Page::HelpHelp => help::update(self, ctx, frame, ui),
Page::HelpAbout => help::update(self, ctx, frame, ui),
Page::HelpHelp | Page::HelpStats | Page::HelpAbout => {
help::update(self, ctx, frame, ui)
}
});
}
}

View File

@ -1,237 +0,0 @@
use super::{GossipUi, Page};
use crate::comms::BusMessage;
use crate::db::DbPerson;
use crate::globals::GLOBALS;
use eframe::egui;
use egui::{Context, Image, RichText, ScrollArea, Sense, TextEdit, TopBottomPanel, Ui, Vec2};
pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
let maybe_person = if let Some(pubkeyhex) = &app.person_view_pubkey {
GLOBALS.people.blocking_write().get(pubkeyhex)
} else {
None
};
TopBottomPanel::top("people_menu").show(ctx, |ui| {
ui.horizontal(|ui| {
ui.selectable_value(&mut app.page, Page::PeopleList, "Followed");
ui.separator();
ui.selectable_value(&mut app.page, Page::PeopleFollow, "Follow Someone New");
ui.separator();
if let Some(person) = &maybe_person {
ui.selectable_value(&mut app.page, Page::Person, get_name(person));
ui.separator();
}
});
});
if app.page == Page::PeopleFollow {
ui.add_space(30.0);
ui.heading("NOTICE: Gossip doesn't update the filters when you follow someone yet, so you have to restart the client to fetch their events. Will fix soon.");
ui.heading("NOTICE: Gossip is not synchronizing with data on the nostr relays. This is a separate list and it won't overwrite anything.");
ui.label("NOTICE: use CTRL-V to paste (middle/right click wont work)");
ui.add_space(10.0);
ui.separator();
ui.add_space(10.0);
ui.heading("NIP-35: Follow a DNS ID");
ui.horizontal(|ui| {
ui.label("Enter user@domain");
ui.add(TextEdit::singleline(&mut app.nip35follow).hint_text("user@domain"));
});
if ui.button("follow").clicked() {
let tx = GLOBALS.to_overlord.clone();
let _ = tx.send(BusMessage {
target: "overlord".to_string(),
kind: "follow_nip35".to_string(),
json_payload: serde_json::to_string(&app.nip35follow).unwrap(),
});
app.nip35follow = "".to_owned();
}
ui.add_space(10.0);
ui.separator();
ui.add_space(10.0);
ui.heading("Follow a bech32 public key");
ui.horizontal(|ui| {
ui.label("Enter bech32 public key");
ui.add(TextEdit::singleline(&mut app.follow_bech32_pubkey).hint_text("npub1..."));
});
ui.horizontal(|ui| {
ui.label("Enter a relay URL where we can find them");
ui.add(TextEdit::singleline(&mut app.follow_pubkey_at_relay).hint_text("wss://..."));
});
if ui.button("follow").clicked() {
let tx = GLOBALS.to_overlord.clone();
let _ = tx.send(BusMessage {
target: "overlord".to_string(),
kind: "follow_bech32".to_string(),
json_payload: serde_json::to_string(&(
&app.follow_bech32_pubkey,
&app.follow_pubkey_at_relay,
))
.unwrap(),
});
app.follow_bech32_pubkey = "".to_owned();
app.follow_pubkey_at_relay = "".to_owned();
}
ui.add_space(10.0);
ui.separator();
ui.add_space(10.0);
ui.heading("Follow a hex public key");
ui.horizontal(|ui| {
ui.label("Enter hex-encoded public key");
ui.add(
TextEdit::singleline(&mut app.follow_hex_pubkey).hint_text("0123456789abcdef..."),
);
});
ui.horizontal(|ui| {
ui.label("Enter a relay URL where we can find them");
ui.add(TextEdit::singleline(&mut app.follow_pubkey_at_relay).hint_text("wss://..."));
});
if ui.button("follow").clicked() {
let tx = GLOBALS.to_overlord.clone();
let _ = tx.send(BusMessage {
target: "overlord".to_string(),
kind: "follow_hexkey".to_string(),
json_payload: serde_json::to_string(&(
&app.follow_hex_pubkey,
&app.follow_pubkey_at_relay,
))
.unwrap(),
});
app.follow_hex_pubkey = "".to_owned();
app.follow_pubkey_at_relay = "".to_owned();
}
} else if app.page == Page::PeopleList {
ui.add_space(24.0);
ui.heading("NOTICE: Gossip is not synchronizing with data on the nostr relays. This is a separate list and it won't overwrite anything.");
ui.add_space(10.0);
ui.separator();
ui.add_space(10.0);
ui.heading("People Followed");
ui.add_space(18.0);
let people = GLOBALS.people.blocking_write().get_all();
ScrollArea::vertical().show(ui, |ui| {
for person in people.iter() {
if person.followed != 1 {
continue;
}
ui.horizontal(|ui| {
// Avatar first
let avatar = if let Some(avatar) = app.try_get_avatar(ctx, &person.pubkey) {
avatar
} else {
app.placeholder_avatar.clone()
};
if ui
.add(
Image::new(
&avatar,
Vec2 {
x: crate::AVATAR_SIZE_F32,
y: crate::AVATAR_SIZE_F32,
},
)
.sense(Sense::click()),
)
.clicked()
{
set_person_view(app, person);
};
ui.vertical(|ui| {
ui.label(RichText::new(GossipUi::hex_pubkey_short(&person.pubkey)).weak());
GossipUi::render_person_name_line(ui, Some(person));
});
});
ui.add_space(4.0);
ui.separator();
}
});
} else if app.page == Page::Person {
if maybe_person.is_none() || app.person_view_pubkey.is_none() {
ui.label("ERROR");
} else {
let person = maybe_person.as_ref().unwrap();
let pubkeyhex = app.person_view_pubkey.as_ref().unwrap().clone();
ui.add_space(24.0);
ui.heading(get_name(person));
ui.horizontal(|ui| {
// Avatar first
let avatar = if let Some(avatar) = app.try_get_avatar(ctx, &pubkeyhex) {
avatar
} else {
app.placeholder_avatar.clone()
};
ui.image(&avatar, Vec2 { x: 36.0, y: 36.0 });
ui.vertical(|ui| {
ui.label(RichText::new(GossipUi::hex_pubkey_short(&pubkeyhex)).weak());
GossipUi::render_person_name_line(ui, Some(person));
});
});
ui.add_space(12.0);
if let Some(about) = person.about.as_deref() {
ui.label(about);
}
ui.add_space(12.0);
#[allow(clippy::collapsible_else_if)]
if person.followed == 0 {
if ui.button("FOLLOW").clicked() {
GLOBALS.people.blocking_write().follow(&pubkeyhex, true);
}
} else {
if ui.button("UNFOLLOW").clicked() {
GLOBALS.people.blocking_write().follow(&pubkeyhex, false);
}
}
if ui.button("UPDATE METADATA").clicked() {
let _ = GLOBALS.to_overlord.send(BusMessage {
target: "overlord".to_string(),
kind: "update_metadata".to_string(),
json_payload: serde_json::to_string(&pubkeyhex).unwrap(),
});
}
}
}
}
fn get_name(person: &DbPerson) -> String {
if let Some(name) = &person.name {
name.to_owned()
} else {
GossipUi::hex_pubkey_short(&person.pubkey)
}
}
fn set_person_view(app: &mut GossipUi, person: &DbPerson) {
app.person_view_pubkey = Some(person.pubkey.clone());
app.page = Page::Person;
}

93
src/ui/people/follow.rs Normal file
View File

@ -0,0 +1,93 @@
use super::GossipUi;
use crate::comms::BusMessage;
use crate::globals::GLOBALS;
use eframe::egui;
use egui::{Context, TextEdit, Ui};
pub(super) fn update(app: &mut GossipUi, _ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
ui.add_space(30.0);
ui.heading("NOTICE: Gossip doesn't update the filters when you follow someone yet, so you have to restart the client to fetch their events. Will fix soon.");
ui.heading("NOTICE: Gossip is not synchronizing with data on the nostr relays. This is a separate list and it won't overwrite anything.");
ui.label("NOTICE: use CTRL-V to paste (middle/right click wont work)");
ui.add_space(10.0);
ui.separator();
ui.add_space(10.0);
ui.heading("NIP-35: Follow a DNS ID");
ui.horizontal(|ui| {
ui.label("Enter user@domain");
ui.add(TextEdit::singleline(&mut app.nip35follow).hint_text("user@domain"));
});
if ui.button("follow").clicked() {
let tx = GLOBALS.to_overlord.clone();
let _ = tx.send(BusMessage {
target: "overlord".to_string(),
kind: "follow_nip35".to_string(),
json_payload: serde_json::to_string(&app.nip35follow).unwrap(),
});
app.nip35follow = "".to_owned();
}
ui.add_space(10.0);
ui.separator();
ui.add_space(10.0);
ui.heading("Follow a bech32 public key");
ui.horizontal(|ui| {
ui.label("Enter bech32 public key");
ui.add(TextEdit::singleline(&mut app.follow_bech32_pubkey).hint_text("npub1..."));
});
ui.horizontal(|ui| {
ui.label("Enter a relay URL where we can find them");
ui.add(TextEdit::singleline(&mut app.follow_pubkey_at_relay).hint_text("wss://..."));
});
if ui.button("follow").clicked() {
let tx = GLOBALS.to_overlord.clone();
let _ = tx.send(BusMessage {
target: "overlord".to_string(),
kind: "follow_bech32".to_string(),
json_payload: serde_json::to_string(&(
&app.follow_bech32_pubkey,
&app.follow_pubkey_at_relay,
))
.unwrap(),
});
app.follow_bech32_pubkey = "".to_owned();
app.follow_pubkey_at_relay = "".to_owned();
}
ui.add_space(10.0);
ui.separator();
ui.add_space(10.0);
ui.heading("Follow a hex public key");
ui.horizontal(|ui| {
ui.label("Enter hex-encoded public key");
ui.add(TextEdit::singleline(&mut app.follow_hex_pubkey).hint_text("0123456789abcdef..."));
});
ui.horizontal(|ui| {
ui.label("Enter a relay URL where we can find them");
ui.add(TextEdit::singleline(&mut app.follow_pubkey_at_relay).hint_text("wss://..."));
});
if ui.button("follow").clicked() {
let tx = GLOBALS.to_overlord.clone();
let _ = tx.send(BusMessage {
target: "overlord".to_string(),
kind: "follow_hexkey".to_string(),
json_payload: serde_json::to_string(&(
&app.follow_hex_pubkey,
&app.follow_pubkey_at_relay,
))
.unwrap(),
});
app.follow_hex_pubkey = "".to_owned();
app.follow_pubkey_at_relay = "".to_owned();
}
}

101
src/ui/people/mod.rs Normal file
View File

@ -0,0 +1,101 @@
use super::{GossipUi, Page};
use crate::db::DbPerson;
use crate::globals::GLOBALS;
use eframe::egui;
use egui::{Context, Image, RichText, ScrollArea, Sense, Ui, Vec2};
mod follow;
mod person;
pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
let maybe_person = if let Some(pubkeyhex) = &app.person_view_pubkey {
GLOBALS.people.blocking_write().get(pubkeyhex)
} else {
None
};
ui.horizontal(|ui| {
ui.selectable_value(&mut app.page, Page::PeopleList, "Followed");
ui.separator();
ui.selectable_value(&mut app.page, Page::PeopleFollow, "Follow Someone New");
ui.separator();
if let Some(person) = &maybe_person {
ui.selectable_value(&mut app.page, Page::Person, get_name(person));
ui.separator();
}
});
ui.separator();
if app.page == Page::PeopleList {
ui.add_space(24.0);
ui.heading("NOTICE: Gossip is not synchronizing with data on the nostr relays. This is a separate list and it won't overwrite anything.");
ui.add_space(10.0);
ui.separator();
ui.add_space(10.0);
ui.heading("People Followed");
ui.add_space(18.0);
let people = GLOBALS.people.blocking_write().get_all();
ScrollArea::vertical().show(ui, |ui| {
for person in people.iter() {
if person.followed != 1 {
continue;
}
ui.horizontal(|ui| {
// Avatar first
let avatar = if let Some(avatar) = app.try_get_avatar(ctx, &person.pubkey) {
avatar
} else {
app.placeholder_avatar.clone()
};
if ui
.add(
Image::new(
&avatar,
Vec2 {
x: crate::AVATAR_SIZE_F32,
y: crate::AVATAR_SIZE_F32,
},
)
.sense(Sense::click()),
)
.clicked()
{
set_person_view(app, person);
};
ui.vertical(|ui| {
ui.label(RichText::new(GossipUi::hex_pubkey_short(&person.pubkey)).weak());
GossipUi::render_person_name_line(ui, Some(person));
});
});
ui.add_space(4.0);
ui.separator();
}
});
} else if app.page == Page::PeopleFollow {
follow::update(app, ctx, _frame, ui);
} else if app.page == Page::Person {
person::update(app, ctx, _frame, ui);
}
}
fn get_name(person: &DbPerson) -> String {
if let Some(name) = &person.name {
name.to_owned()
} else {
GossipUi::hex_pubkey_short(&person.pubkey)
}
}
fn set_person_view(app: &mut GossipUi, person: &DbPerson) {
app.person_view_pubkey = Some(person.pubkey.clone());
app.page = Page::Person;
}

75
src/ui/people/person.rs Normal file
View File

@ -0,0 +1,75 @@
use super::GossipUi;
use crate::comms::BusMessage;
use crate::db::DbPerson;
use crate::globals::GLOBALS;
use eframe::egui;
use egui::{Context, RichText, Ui, Vec2};
pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Frame, ui: &mut Ui) {
let maybe_person = if let Some(pubkeyhex) = &app.person_view_pubkey {
GLOBALS.people.blocking_write().get(pubkeyhex)
} else {
None
};
if maybe_person.is_none() || app.person_view_pubkey.is_none() {
ui.label("ERROR");
} else {
let person = maybe_person.as_ref().unwrap();
let pubkeyhex = app.person_view_pubkey.as_ref().unwrap().clone();
ui.add_space(24.0);
ui.heading(get_name(person));
ui.horizontal(|ui| {
// Avatar first
let avatar = if let Some(avatar) = app.try_get_avatar(ctx, &pubkeyhex) {
avatar
} else {
app.placeholder_avatar.clone()
};
ui.image(&avatar, Vec2 { x: 36.0, y: 36.0 });
ui.vertical(|ui| {
ui.label(RichText::new(GossipUi::hex_pubkey_short(&pubkeyhex)).weak());
GossipUi::render_person_name_line(ui, Some(person));
});
});
ui.add_space(12.0);
if let Some(about) = person.about.as_deref() {
ui.label(about);
}
ui.add_space(12.0);
#[allow(clippy::collapsible_else_if)]
if person.followed == 0 {
if ui.button("FOLLOW").clicked() {
GLOBALS.people.blocking_write().follow(&pubkeyhex, true);
}
} else {
if ui.button("UNFOLLOW").clicked() {
GLOBALS.people.blocking_write().follow(&pubkeyhex, false);
}
}
if ui.button("UPDATE METADATA").clicked() {
let _ = GLOBALS.to_overlord.send(BusMessage {
target: "overlord".to_string(),
kind: "update_metadata".to_string(),
json_payload: serde_json::to_string(&pubkeyhex).unwrap(),
});
}
}
}
fn get_name(person: &DbPerson) -> String {
if let Some(name) = &person.name {
name.to_owned()
} else {
GossipUi::hex_pubkey_short(&person.pubkey)
}
}