From 611b5ab12c7ce189e63852f4d4f112a59fab4591 Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Tue, 3 Jan 2023 13:46:57 +1300 Subject: [PATCH] Multiple feeds working --- src/feed.rs | 103 +++++++-- src/ui/{feed/mod.rs => feed.rs} | 379 +++++++++++++++++--------------- src/ui/feed/person.rs | 7 - src/ui/feed/thread.rs | 7 - src/ui/mod.rs | 6 - src/ui/people/person.rs | 7 +- 6 files changed, 287 insertions(+), 222 deletions(-) rename src/ui/{feed/mod.rs => feed.rs} (59%) delete mode 100644 src/ui/feed/person.rs delete mode 100644 src/ui/feed/thread.rs diff --git a/src/feed.rs b/src/feed.rs index f9fdfefc..87e41514 100644 --- a/src/feed.rs +++ b/src/feed.rs @@ -1,10 +1,20 @@ use crate::globals::GLOBALS; +use nostr_types::PublicKeyHex; use nostr_types::{Event, EventKind, Id}; -use std::time::{Duration, Instant}; use parking_lot::RwLock; +use std::time::{Duration, Instant}; + +#[derive(Clone, Debug)] +pub enum FeedKind { + General, + Thread(Id), + Person(PublicKeyHex), +} pub struct Feed { - feed: RwLock>, + current_feed_kind: RwLock, + + general_feed: RwLock>, // We only recompute the feed at specified intervals interval_ms: RwLock, @@ -18,7 +28,8 @@ pub struct Feed { impl Feed { pub fn new() -> Feed { Feed { - feed: RwLock::new(Vec::new()), + current_feed_kind: RwLock::new(FeedKind::General), + general_feed: RwLock::new(Vec::new()), interval_ms: RwLock::new(1000), // Every second, until we load from settings last_computed: RwLock::new(Instant::now()), my_event_ids: RwLock::new(Vec::new()), @@ -26,14 +37,77 @@ impl Feed { } } - pub fn get(&self) -> Vec { + pub fn set_feed_to_general(&self) { + *self.current_feed_kind.write() = FeedKind::General; + } + + pub fn set_feed_to_thread(&self, id: Id) { + // get parent? + *self.current_feed_kind.write() = FeedKind::Thread(id); + } + + pub fn set_feed_to_person(&self, pubkey: PublicKeyHex) { + // FIXME - TRIGGER OVERLORD TO FETCH THEIR EVENTS FURTHER BACK + *self.current_feed_kind.write() = FeedKind::Person(pubkey); + } + + pub fn get_feed_kind(&self) -> FeedKind { + self.current_feed_kind.read().to_owned() + } + + pub fn get_general(&self) -> Vec { let now = Instant::now(); - if *self.last_computed.read() + Duration::from_millis(*self.interval_ms.read() as u64) < now { + if *self.last_computed.read() + Duration::from_millis(*self.interval_ms.read() as u64) < now + { self.recompute(); *self.last_computed.write() = now; } - self.feed.read().clone() + self.general_feed.read().clone() + } + + pub fn get_thread_parent(&self, id: Id) -> Id { + // FIXME - TRIGGER OVERLORD TO FETCH THIS FEED + let mut event = match GLOBALS.events.blocking_read().get(&id).cloned() { + None => return id, + Some(e) => e, + }; + + // Try for root + if let Some((root, _)) = event.replies_to_root() { + if GLOBALS.events.blocking_read().contains_key(&root) { + return root; + } + } + + // Climb parents as high as we can + while let Some((parent, _)) = event.replies_to() { + if let Some(e) = GLOBALS.events.blocking_read().get(&parent) { + event = e.to_owned(); + } else { + break; + } + } + + // The highest event id we have + event.id + } + + pub fn get_person_feed(&self, person: PublicKeyHex) -> Vec { + let mut events: Vec = GLOBALS + .events + .blocking_read() + .iter() + .map(|(_, e)| e) + .filter(|e| e.kind == EventKind::TextNote) + .filter(|e| e.pubkey.as_hex_string() == person.0) + .filter(|e| !GLOBALS.dismissed.blocking_read().contains(&e.id)) + .map(|e| e.to_owned()) + .collect(); + + events.sort_unstable_by(|a, b| b.created_at.cmp(&a.created_at)); + + events.iter().map(|e| e.id).collect() } #[allow(dead_code)] @@ -88,26 +162,11 @@ impl Feed { let mut events: Vec = events .iter() .filter(|e| !GLOBALS.dismissed.blocking_read().contains(&e.id)) - //.filter(|e| { for Threaded - //e.replies_to().is_none() - //}) .cloned() .collect(); - /* for threaded - if settings.view_threaded { - events.sort_unstable_by(|a, b| { - let a_last = GLOBALS.last_reply.blocking_read().get(&a.id).cloned(); - let b_last = GLOBALS.last_reply.blocking_read().get(&b.id).cloned(); - let a_time = a_last.unwrap_or(a.created_at); - let b_time = b_last.unwrap_or(b.created_at); - b_time.cmp(&a_time) - }); - } - */ - events.sort_unstable_by(|a, b| b.created_at.cmp(&a.created_at)); - *self.feed.write() = events.iter().map(|e| e.id).collect(); + *self.general_feed.write() = events.iter().map(|e| e.id).collect(); } } diff --git a/src/ui/feed/mod.rs b/src/ui/feed.rs similarity index 59% rename from src/ui/feed/mod.rs rename to src/ui/feed.rs index ba533049..0d3e1510 100644 --- a/src/ui/feed/mod.rs +++ b/src/ui/feed.rs @@ -1,18 +1,16 @@ use super::{GossipUi, Page}; use crate::comms::BusMessage; +use crate::feed::FeedKind; use crate::globals::{Globals, GLOBALS}; use crate::ui::widgets::{CopyButton, ReplyButton}; use eframe::egui; use egui::{ - Align, Color32, Context, Frame, Image, Label, Layout, RichText, ScrollArea, Sense, TextEdit, - Ui, Vec2, + Align, Color32, Context, Frame, Image, Layout, RichText, ScrollArea, SelectableLabel, Sense, + TextEdit, Ui, Vec2, }; use linkify::{LinkFinder, LinkKind}; use nostr_types::{EventKind, Id, PublicKeyHex}; -mod person; -mod thread; - struct FeedPostParams { id: Id, indent: usize, @@ -21,183 +19,208 @@ struct FeedPostParams { } pub(super) fn update(app: &mut GossipUi, ctx: &Context, frame: &mut eframe::Frame, ui: &mut Ui) { + let mut feed_kind = GLOBALS.feed.get_feed_kind(); + app.page = match feed_kind { + FeedKind::General => Page::FeedGeneral, + FeedKind::Thread(_) => Page::FeedThread, + FeedKind::Person(_) => Page::FeedPerson, + }; + ui.horizontal(|ui| { - ui.selectable_value(&mut app.page, Page::FeedGeneral, "Following"); + if ui + .add(SelectableLabel::new( + app.page == Page::FeedGeneral, + "Following", + )) + .clicked() + { + app.page = Page::FeedGeneral; + GLOBALS.feed.set_feed_to_general(); + feed_kind = FeedKind::General; + } ui.separator(); - if app.feed_thread_id.is_some() { + if matches!(feed_kind, FeedKind::Thread(..)) { ui.selectable_value(&mut app.page, Page::FeedThread, "Thread"); ui.separator(); } - if app.feed_person_pubkey.is_some() { + if matches!(feed_kind, FeedKind::Person(..)) { ui.selectable_value(&mut app.page, Page::FeedPerson, "Person"); ui.separator(); } }); ui.separator(); - if app.page == Page::FeedGeneral { - let feed = GLOBALS.feed.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, + }; - 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(), - }); - } - - 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 = false; - - 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, - }, - ); - } + 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(), }); - }); - } else if app.page == Page::FeedThread { - thread::update(app, ctx, frame, ui); - } else if app.page == Page::FeedPerson { - person::update(app, ctx, frame, ui); + } + + 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(), + }); + } + + 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(); + + match feed_kind { + FeedKind::General => { + let feed = GLOBALS.feed.get_general(); + render_a_feed(app, ctx, frame, ui, feed, false); + } + FeedKind::Thread(id) => { + let parent = GLOBALS.feed.get_thread_parent(id); + render_a_feed(app, ctx, frame, ui, vec![parent], true); + } + FeedKind::Person(pubkeyhex) => { + let feed = GLOBALS.feed.get_person_feed(pubkeyhex); + render_a_feed(app, ctx, frame, ui, feed, false); + } } } +fn render_a_feed( + app: &mut GossipUi, + ctx: &Context, + frame: &mut eframe::Frame, + ui: &mut Ui, + feed: Vec, + threaded: bool, +) { + 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, + }, + ); + } + }); + }); +} + fn render_post_maybe_fake( app: &mut GossipUi, ctx: &Context, @@ -238,7 +261,7 @@ fn render_post_maybe_fake( ui.add_space(height); // Yes, and we need to fake render threads to get their approx height too. - if threaded && !as_reply_to && !app.hides.contains(&id) { + if threaded && !as_reply_to { let replies = Globals::get_replies_sync(event.id); for reply_id in replies { render_post_maybe_fake( @@ -352,17 +375,6 @@ fn render_post_actual( ui.horizontal(|ui| { // Indents first (if threaded) if threaded { - #[allow(clippy::collapsible_else_if)] - if app.hides.contains(&id) { - if ui.add(Label::new("▶").sense(Sense::click())).clicked() { - app.hides.retain(|e| *e != id) - } - } else { - if ui.add(Label::new("▼").sense(Sense::click())).clicked() { - app.hides.push(id); - } - } - let space = 16.0 * (10.0 - (100.0 / (indent as f32 + 10.0))); ui.add_space(space); if indent > 0 { @@ -406,6 +418,10 @@ fn render_post_actual( ui.with_layout(Layout::right_to_left(Align::TOP), |ui| { ui.menu_button(RichText::new("≡").size(28.0), |ui| { + if ui.button("View Thread").clicked() { + GLOBALS.feed.set_feed_to_thread(event.id); + app.page = Page::FeedThread; + } if ui.button("Copy ID").clicked() { ui.output().copied_text = event.id.as_hex_string(); } @@ -424,6 +440,11 @@ fn render_post_actual( } }); + if ui.button("➤").clicked() { + GLOBALS.feed.set_feed_to_thread(event.id); + app.page = Page::FeedThread; + } + ui.label( RichText::new(crate::date_ago::date_ago(event.created_at)) .italics() @@ -475,7 +496,7 @@ fn render_post_actual( ui.separator(); - if threaded && !as_reply_to && !app.hides.contains(&id) { + if threaded && !as_reply_to { let replies = Globals::get_replies_sync(event.id); for reply_id in replies { render_post_maybe_fake( diff --git a/src/ui/feed/person.rs b/src/ui/feed/person.rs deleted file mode 100644 index f5c32dff..00000000 --- a/src/ui/feed/person.rs +++ /dev/null @@ -1,7 +0,0 @@ -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"); -} diff --git a/src/ui/feed/thread.rs b/src/ui/feed/thread.rs deleted file mode 100644 index efc07ce2..00000000 --- a/src/ui/feed/thread.rs +++ /dev/null @@ -1,7 +0,0 @@ -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"); -} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 6c5c88ce..922031db 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -85,12 +85,9 @@ struct GossipUi { import_bech32: String, import_hex: String, replying_to: Option, - hides: Vec, person_view_pubkey: Option, avatars: HashMap, new_relay_url: String, - feed_thread_id: Option, - feed_person_pubkey: Option, } impl Drop for GossipUi { @@ -160,12 +157,9 @@ impl GossipUi { import_bech32: "".to_owned(), import_hex: "".to_owned(), replying_to: None, - hides: Vec::new(), person_view_pubkey: None, avatars: HashMap::new(), new_relay_url: "".to_owned(), - feed_thread_id: None, - feed_person_pubkey: None, } } } diff --git a/src/ui/people/person.rs b/src/ui/people/person.rs index 119ff17f..6ef4f720 100644 --- a/src/ui/people/person.rs +++ b/src/ui/people/person.rs @@ -1,4 +1,4 @@ -use super::GossipUi; +use super::{GossipUi, Page}; use crate::comms::BusMessage; use crate::db::DbPerson; use crate::globals::GLOBALS; @@ -63,6 +63,11 @@ pub(super) fn update(app: &mut GossipUi, ctx: &Context, _frame: &mut eframe::Fra json_payload: serde_json::to_string(&pubkeyhex).unwrap(), }); } + + if ui.button("VIEW THEIR FEED").clicked() { + GLOBALS.feed.set_feed_to_person(pubkeyhex.clone()); + app.page = Page::FeedPerson; + } } }