Multiple feeds working

This commit is contained in:
Mike Dilger 2023-01-03 13:46:57 +13:00
parent 238c429017
commit 611b5ab12c
6 changed files with 287 additions and 222 deletions

View File

@ -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<Vec<Id>>,
current_feed_kind: RwLock<FeedKind>,
general_feed: RwLock<Vec<Id>>,
// We only recompute the feed at specified intervals
interval_ms: RwLock<u32>,
@ -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<Id> {
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<Id> {
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<Id> {
let mut events: Vec<Event> = 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<Event> = 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();
}
}

View File

@ -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<Id>,
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(

View File

@ -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");
}

View File

@ -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");
}

View File

@ -85,12 +85,9 @@ struct GossipUi {
import_bech32: String,
import_hex: String,
replying_to: Option<Id>,
hides: Vec<Id>,
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 {
@ -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,
}
}
}

View File

@ -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;
}
}
}