Switch to unified timeline cache via TimelineKinds
This is a fairly large rewrite which unifies our threads, timelines and profiles. Now all timelines have a MultiSubscriber, and can be added and removed to columns just like Threads and Profiles. Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
@@ -1,124 +1,44 @@
|
||||
use crate::{
|
||||
column::Columns,
|
||||
draft::Drafts,
|
||||
nav::RenderNavAction,
|
||||
profile::ProfileAction,
|
||||
timeline::{TimelineCache, TimelineId, TimelineKind},
|
||||
ui::{
|
||||
self,
|
||||
note::{NoteOptions, QuoteRepostView},
|
||||
profile::ProfileView,
|
||||
},
|
||||
timeline::{TimelineCache, TimelineKind},
|
||||
ui::{self, note::NoteOptions, profile::ProfileView},
|
||||
};
|
||||
|
||||
use tokenator::{ParseError, TokenParser, TokenSerializable, TokenWriter};
|
||||
|
||||
use enostr::{NoteId, Pubkey};
|
||||
use nostrdb::{Ndb, Transaction};
|
||||
use enostr::Pubkey;
|
||||
use nostrdb::Ndb;
|
||||
use notedeck::{Accounts, ImageCache, MuteFun, NoteCache, UnknownIds};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub enum TimelineRoute {
|
||||
Timeline(TimelineId),
|
||||
Thread(NoteId),
|
||||
Profile(Pubkey),
|
||||
Reply(NoteId),
|
||||
Quote(NoteId),
|
||||
}
|
||||
|
||||
fn parse_pubkey<'a>(parser: &mut TokenParser<'a>) -> Result<Pubkey, ParseError<'a>> {
|
||||
let hex = parser.pull_token()?;
|
||||
Pubkey::from_hex(hex).map_err(|_| ParseError::HexDecodeFailed)
|
||||
}
|
||||
|
||||
fn parse_note_id<'a>(parser: &mut TokenParser<'a>) -> Result<NoteId, ParseError<'a>> {
|
||||
let hex = parser.pull_token()?;
|
||||
NoteId::from_hex(hex).map_err(|_| ParseError::HexDecodeFailed)
|
||||
}
|
||||
|
||||
impl TokenSerializable for TimelineRoute {
|
||||
fn serialize_tokens(&self, writer: &mut TokenWriter) {
|
||||
match self {
|
||||
TimelineRoute::Profile(pk) => {
|
||||
writer.write_token("profile");
|
||||
writer.write_token(&pk.hex());
|
||||
}
|
||||
TimelineRoute::Thread(note_id) => {
|
||||
writer.write_token("thread");
|
||||
writer.write_token(¬e_id.hex());
|
||||
}
|
||||
TimelineRoute::Reply(note_id) => {
|
||||
writer.write_token("reply");
|
||||
writer.write_token(¬e_id.hex());
|
||||
}
|
||||
TimelineRoute::Quote(note_id) => {
|
||||
writer.write_token("quote");
|
||||
writer.write_token(¬e_id.hex());
|
||||
}
|
||||
TimelineRoute::Timeline(_tlid) => {
|
||||
todo!("tlid")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_from_tokens<'a>(parser: &mut TokenParser<'a>) -> Result<Self, ParseError<'a>> {
|
||||
TokenParser::alt(
|
||||
parser,
|
||||
&[
|
||||
|p| {
|
||||
p.parse_token("profile")?;
|
||||
Ok(TimelineRoute::Profile(parse_pubkey(p)?))
|
||||
},
|
||||
|p| {
|
||||
p.parse_token("thread")?;
|
||||
Ok(TimelineRoute::Thread(parse_note_id(p)?))
|
||||
},
|
||||
|p| {
|
||||
p.parse_token("reply")?;
|
||||
Ok(TimelineRoute::Reply(parse_note_id(p)?))
|
||||
},
|
||||
|p| {
|
||||
p.parse_token("quote")?;
|
||||
Ok(TimelineRoute::Quote(parse_note_id(p)?))
|
||||
},
|
||||
|_p| todo!("handle timeline parsing"),
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn render_timeline_route(
|
||||
ndb: &Ndb,
|
||||
columns: &mut Columns,
|
||||
drafts: &mut Drafts,
|
||||
img_cache: &mut ImageCache,
|
||||
unknown_ids: &mut UnknownIds,
|
||||
note_cache: &mut NoteCache,
|
||||
timeline_cache: &mut TimelineCache,
|
||||
accounts: &mut Accounts,
|
||||
route: TimelineRoute,
|
||||
kind: &TimelineKind,
|
||||
col: usize,
|
||||
textmode: bool,
|
||||
depth: usize,
|
||||
ui: &mut egui::Ui,
|
||||
) -> Option<RenderNavAction> {
|
||||
match route {
|
||||
TimelineRoute::Timeline(timeline_id) => {
|
||||
let note_options = {
|
||||
let is_universe = if let Some(timeline) = columns.find_timeline(timeline_id) {
|
||||
timeline.kind == TimelineKind::Universe
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let mut options = NoteOptions::new(is_universe);
|
||||
options.set_textmode(textmode);
|
||||
options
|
||||
};
|
||||
let note_options = {
|
||||
let mut options = NoteOptions::new(kind == &TimelineKind::Universe);
|
||||
options.set_textmode(textmode);
|
||||
options
|
||||
};
|
||||
|
||||
match kind {
|
||||
TimelineKind::List(_)
|
||||
| TimelineKind::Algo(_)
|
||||
| TimelineKind::Notifications(_)
|
||||
| TimelineKind::Universe
|
||||
| TimelineKind::Hashtag(_)
|
||||
| TimelineKind::Generic(_) => {
|
||||
let note_action = ui::TimelineView::new(
|
||||
timeline_id,
|
||||
columns,
|
||||
kind,
|
||||
timeline_cache,
|
||||
ndb,
|
||||
note_cache,
|
||||
img_cache,
|
||||
@@ -130,89 +50,50 @@ pub fn render_timeline_route(
|
||||
note_action.map(RenderNavAction::NoteAction)
|
||||
}
|
||||
|
||||
TimelineRoute::Thread(id) => ui::ThreadView::new(
|
||||
TimelineKind::Profile(pubkey) => {
|
||||
if depth > 1 {
|
||||
render_profile_route(
|
||||
pubkey,
|
||||
accounts,
|
||||
ndb,
|
||||
timeline_cache,
|
||||
img_cache,
|
||||
note_cache,
|
||||
unknown_ids,
|
||||
col,
|
||||
ui,
|
||||
&accounts.mutefun(),
|
||||
)
|
||||
} else {
|
||||
// we render profiles like timelines if they are at the root
|
||||
let note_action = ui::TimelineView::new(
|
||||
kind,
|
||||
timeline_cache,
|
||||
ndb,
|
||||
note_cache,
|
||||
img_cache,
|
||||
note_options,
|
||||
&accounts.mutefun(),
|
||||
)
|
||||
.ui(ui);
|
||||
|
||||
note_action.map(RenderNavAction::NoteAction)
|
||||
}
|
||||
}
|
||||
|
||||
TimelineKind::Thread(id) => ui::ThreadView::new(
|
||||
timeline_cache,
|
||||
ndb,
|
||||
note_cache,
|
||||
unknown_ids,
|
||||
img_cache,
|
||||
id.bytes(),
|
||||
id.selected_or_root(),
|
||||
textmode,
|
||||
&accounts.mutefun(),
|
||||
)
|
||||
.id_source(egui::Id::new(("threadscroll", col)))
|
||||
.ui(ui)
|
||||
.map(Into::into),
|
||||
|
||||
TimelineRoute::Reply(id) => {
|
||||
let txn = if let Ok(txn) = Transaction::new(ndb) {
|
||||
txn
|
||||
} else {
|
||||
ui.label("Reply to unknown note");
|
||||
return None;
|
||||
};
|
||||
|
||||
let note = if let Ok(note) = ndb.get_note_by_id(&txn, id.bytes()) {
|
||||
note
|
||||
} else {
|
||||
ui.label("Reply to unknown note");
|
||||
return None;
|
||||
};
|
||||
|
||||
let id = egui::Id::new(("post", col, note.key().unwrap()));
|
||||
let poster = accounts.selected_or_first_nsec()?;
|
||||
|
||||
let action = {
|
||||
let draft = drafts.reply_mut(note.id());
|
||||
|
||||
let response = egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
ui::PostReplyView::new(ndb, poster, draft, note_cache, img_cache, ¬e)
|
||||
.id_source(id)
|
||||
.show(ui)
|
||||
});
|
||||
|
||||
response.inner.action
|
||||
};
|
||||
|
||||
action.map(Into::into)
|
||||
}
|
||||
|
||||
TimelineRoute::Profile(pubkey) => render_profile_route(
|
||||
&pubkey,
|
||||
accounts,
|
||||
ndb,
|
||||
timeline_cache,
|
||||
img_cache,
|
||||
note_cache,
|
||||
unknown_ids,
|
||||
col,
|
||||
ui,
|
||||
&accounts.mutefun(),
|
||||
),
|
||||
|
||||
TimelineRoute::Quote(id) => {
|
||||
let txn = Transaction::new(ndb).expect("txn");
|
||||
|
||||
let note = if let Ok(note) = ndb.get_note_by_id(&txn, id.bytes()) {
|
||||
note
|
||||
} else {
|
||||
ui.label("Quote of unknown note");
|
||||
return None;
|
||||
};
|
||||
|
||||
let id = egui::Id::new(("post", col, note.key().unwrap()));
|
||||
|
||||
let poster = accounts.selected_or_first_nsec()?;
|
||||
let draft = drafts.quote_mut(note.id());
|
||||
|
||||
let response = egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
QuoteRepostView::new(ndb, poster, note_cache, img_cache, draft, ¬e)
|
||||
.id_source(id)
|
||||
.show(ui)
|
||||
});
|
||||
|
||||
response.inner.action.map(Into::into)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,22 +143,26 @@ mod tests {
|
||||
use enostr::NoteId;
|
||||
use tokenator::{TokenParser, TokenSerializable, TokenWriter};
|
||||
|
||||
use crate::timeline::{ThreadSelection, TimelineKind};
|
||||
use enostr::Pubkey;
|
||||
use notedeck::RootNoteIdBuf;
|
||||
|
||||
#[test]
|
||||
fn test_timeline_route_serialize() {
|
||||
use super::TimelineRoute;
|
||||
use super::TimelineKind;
|
||||
|
||||
{
|
||||
let note_id_hex = "1c54e5b0c386425f7e017d9e068ddef8962eb2ce1bb08ed27e24b93411c12e60";
|
||||
let note_id = NoteId::from_hex(note_id_hex).unwrap();
|
||||
let data_str = format!("thread:{}", note_id_hex);
|
||||
let data = &data_str.split(":").collect::<Vec<&str>>();
|
||||
let mut token_writer = TokenWriter::default();
|
||||
let mut parser = TokenParser::new(&data);
|
||||
let parsed = TimelineRoute::parse_from_tokens(&mut parser).unwrap();
|
||||
let expected = TimelineRoute::Thread(note_id);
|
||||
parsed.serialize_tokens(&mut token_writer);
|
||||
assert_eq!(expected, parsed);
|
||||
assert_eq!(token_writer.str(), data_str);
|
||||
}
|
||||
let note_id_hex = "1c54e5b0c386425f7e017d9e068ddef8962eb2ce1bb08ed27e24b93411c12e60";
|
||||
let note_id = NoteId::from_hex(note_id_hex).unwrap();
|
||||
let data_str = format!("thread:{}", note_id_hex);
|
||||
let data = &data_str.split(":").collect::<Vec<&str>>();
|
||||
let mut token_writer = TokenWriter::default();
|
||||
let mut parser = TokenParser::new(&data);
|
||||
let parsed = TimelineKind::parse(&mut parser, &Pubkey::new(*note_id.bytes())).unwrap();
|
||||
let expected = TimelineKind::Thread(ThreadSelection::from_root_id(
|
||||
RootNoteIdBuf::new_unsafe(*note_id.bytes()),
|
||||
));
|
||||
parsed.serialize_tokens(&mut token_writer);
|
||||
assert_eq!(expected, parsed);
|
||||
assert_eq!(token_writer.str(), data_str);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user