integrate new threads conception
Signed-off-by: kernelkind <kernelkind@gmail.com>
This commit is contained in:
@@ -41,8 +41,9 @@ struct NoteActionResponse {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn execute_note_action(
|
||||
action: NoteAction,
|
||||
ndb: &Ndb,
|
||||
ndb: &mut Ndb,
|
||||
timeline_cache: &mut TimelineCache,
|
||||
threads: &mut Threads,
|
||||
note_cache: &mut NoteCache,
|
||||
pool: &mut RelayPool,
|
||||
txn: &Transaction,
|
||||
@@ -52,6 +53,7 @@ fn execute_note_action(
|
||||
images: &mut Images,
|
||||
router_type: RouterType,
|
||||
ui: &mut egui::Ui,
|
||||
col: usize,
|
||||
) -> NoteActionResponse {
|
||||
let mut timeline_res = None;
|
||||
let mut router_action = None;
|
||||
@@ -74,13 +76,16 @@ fn execute_note_action(
|
||||
break 'ex;
|
||||
};
|
||||
|
||||
let kind = TimelineKind::Thread(thread_selection);
|
||||
router_action = Some(RouterAction::route_to(Route::Timeline(kind.clone())));
|
||||
// NOTE!!: you need the note_id to timeline root id thing
|
||||
timeline_res = threads
|
||||
.open(ndb, txn, pool, &thread_selection, preview, col)
|
||||
.map(NotesOpenResult::Thread);
|
||||
|
||||
timeline_res = timeline_cache
|
||||
.open(ndb, note_cache, txn, pool, &kind)
|
||||
.map(NotesOpenResult::Timeline);
|
||||
let route = Route::Thread(thread_selection);
|
||||
|
||||
router_action = Some(RouterAction::Overlay {
|
||||
route,
|
||||
make_new: preview,
|
||||
});
|
||||
}
|
||||
NoteAction::Hashtag(htag) => {
|
||||
let kind = TimelineKind::Hashtag(htag.clone());
|
||||
@@ -151,10 +156,11 @@ fn execute_note_action(
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn execute_and_process_note_action(
|
||||
action: NoteAction,
|
||||
ndb: &Ndb,
|
||||
ndb: &mut Ndb,
|
||||
columns: &mut Columns,
|
||||
col: usize,
|
||||
timeline_cache: &mut TimelineCache,
|
||||
threads: &mut Threads,
|
||||
note_cache: &mut NoteCache,
|
||||
pool: &mut RelayPool,
|
||||
txn: &Transaction,
|
||||
@@ -179,6 +185,7 @@ pub fn execute_and_process_note_action(
|
||||
action,
|
||||
ndb,
|
||||
timeline_cache,
|
||||
threads,
|
||||
note_cache,
|
||||
pool,
|
||||
txn,
|
||||
@@ -188,6 +195,7 @@ pub fn execute_and_process_note_action(
|
||||
images,
|
||||
router_type,
|
||||
ui,
|
||||
col,
|
||||
);
|
||||
|
||||
if let Some(br) = resp.timeline_res {
|
||||
@@ -195,7 +203,9 @@ pub fn execute_and_process_note_action(
|
||||
NotesOpenResult::Timeline(timeline_open_result) => {
|
||||
timeline_open_result.process(ndb, note_cache, txn, timeline_cache, unknown_ids);
|
||||
}
|
||||
NotesOpenResult::Thread(new_thread_notes) => todo!(),
|
||||
NotesOpenResult::Thread(thread_open_result) => {
|
||||
thread_open_result.process(threads, ndb, txn, unknown_ids, note_cache);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,7 +268,7 @@ impl NewNotes {
|
||||
unknown_ids: &mut UnknownIds,
|
||||
note_cache: &mut NoteCache,
|
||||
) {
|
||||
let reversed = matches!(&self.id, TimelineKind::Thread(_));
|
||||
let reversed = false;
|
||||
|
||||
let timeline = if let Some(profile) = timeline_cache.timelines.get_mut(&self.id) {
|
||||
profile
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
storage,
|
||||
subscriptions::{SubKind, Subscriptions},
|
||||
support::Support,
|
||||
timeline::{self, TimelineCache},
|
||||
timeline::{self, thread::Threads, TimelineCache},
|
||||
ui::{self, DesktopSidePanel},
|
||||
view_state::ViewState,
|
||||
Result,
|
||||
@@ -45,6 +45,7 @@ pub struct Damus {
|
||||
pub subscriptions: Subscriptions,
|
||||
pub support: Support,
|
||||
pub jobs: JobsCache,
|
||||
pub threads: Threads,
|
||||
|
||||
//frame_history: crate::frame_history::FrameHistory,
|
||||
|
||||
@@ -443,6 +444,8 @@ impl Damus {
|
||||
|
||||
ctx.accounts.with_fallback(FALLBACK_PUBKEY());
|
||||
|
||||
let threads = Threads::default();
|
||||
|
||||
Self {
|
||||
subscriptions: Subscriptions::default(),
|
||||
since_optimize: parsed_args.since_optimize,
|
||||
@@ -458,6 +461,7 @@ impl Damus {
|
||||
debug,
|
||||
unrecognized_args,
|
||||
jobs,
|
||||
threads,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,6 +506,7 @@ impl Damus {
|
||||
decks_cache,
|
||||
unrecognized_args: BTreeSet::default(),
|
||||
jobs: JobsCache::default(),
|
||||
threads: Threads::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,10 @@ use crate::{
|
||||
profile_state::ProfileState,
|
||||
relay_pool_manager::RelayPoolManager,
|
||||
route::{Route, Router, SingletonRouter},
|
||||
timeline::{route::render_timeline_route, TimelineCache},
|
||||
timeline::{
|
||||
route::{render_thread_route, render_timeline_route},
|
||||
TimelineCache,
|
||||
},
|
||||
ui::{
|
||||
self,
|
||||
add_column::render_add_column_routes,
|
||||
@@ -210,7 +213,7 @@ fn process_nav_resp(
|
||||
|
||||
if let Some(action) = response.action {
|
||||
match action {
|
||||
NavAction::Returned(_) => {
|
||||
NavAction::Returned(return_type) => {
|
||||
let r = app
|
||||
.columns_mut(ctx.accounts)
|
||||
.column_mut(col)
|
||||
@@ -223,6 +226,12 @@ fn process_nav_resp(
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(Route::Thread(selection)) = &r {
|
||||
tracing::info!("Return type: {:?}", return_type);
|
||||
app.threads
|
||||
.close(ctx.ndb, ctx.pool, selection, return_type, col);
|
||||
}
|
||||
|
||||
process_result = Some(ProcessNavResult::SwitchOccurred);
|
||||
}
|
||||
|
||||
@@ -355,6 +364,7 @@ fn process_render_nav_action(
|
||||
get_active_columns_mut(ctx.accounts, &mut app.decks_cache),
|
||||
col,
|
||||
&mut app.timeline_cache,
|
||||
&mut app.threads,
|
||||
ctx.note_cache,
|
||||
ctx.pool,
|
||||
&txn,
|
||||
@@ -426,6 +436,17 @@ fn render_nav_body(
|
||||
&mut note_context,
|
||||
&mut app.jobs,
|
||||
),
|
||||
Route::Thread(selection) => render_thread_route(
|
||||
ctx.unknown_ids,
|
||||
&mut app.threads,
|
||||
ctx.accounts,
|
||||
selection,
|
||||
col,
|
||||
app.note_options,
|
||||
ui,
|
||||
&mut note_context,
|
||||
&mut app.jobs,
|
||||
),
|
||||
Route::Accounts(amr) => {
|
||||
let mut action = render_accounts_route(
|
||||
ui,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use enostr::{NoteId, Pubkey};
|
||||
use notedeck::{NoteZapTargetOwned, WalletType};
|
||||
use notedeck::{NoteZapTargetOwned, RootNoteIdBuf, WalletType};
|
||||
use std::{
|
||||
fmt::{self},
|
||||
ops::Range,
|
||||
@@ -20,6 +20,7 @@ use tokenator::{ParseError, TokenParser, TokenSerializable, TokenWriter};
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
pub enum Route {
|
||||
Timeline(TimelineKind),
|
||||
Thread(ThreadSelection),
|
||||
Accounts(AccountsRoute),
|
||||
Reply(NoteId),
|
||||
Quote(NoteId),
|
||||
@@ -53,7 +54,7 @@ impl Route {
|
||||
}
|
||||
|
||||
pub fn thread(thread_selection: ThreadSelection) -> Self {
|
||||
Route::Timeline(TimelineKind::Thread(thread_selection))
|
||||
Route::Thread(thread_selection)
|
||||
}
|
||||
|
||||
pub fn profile(pubkey: Pubkey) -> Self {
|
||||
@@ -79,6 +80,18 @@ impl Route {
|
||||
pub fn serialize_tokens(&self, writer: &mut TokenWriter) {
|
||||
match self {
|
||||
Route::Timeline(timeline_kind) => timeline_kind.serialize_tokens(writer),
|
||||
Route::Thread(selection) => {
|
||||
writer.write_token("thread");
|
||||
|
||||
if let Some(reply) = selection.selected_note {
|
||||
writer.write_token("root");
|
||||
writer.write_token(&NoteId::new(*selection.root_id.bytes()).hex());
|
||||
writer.write_token("reply");
|
||||
writer.write_token(&reply.hex());
|
||||
} else {
|
||||
writer.write_token(&NoteId::new(*selection.root_id.bytes()).hex());
|
||||
}
|
||||
}
|
||||
Route::Accounts(routes) => routes.serialize_tokens(writer),
|
||||
Route::AddColumn(routes) => routes.serialize_tokens(writer),
|
||||
Route::Search => writer.write_token("search"),
|
||||
@@ -199,6 +212,31 @@ impl Route {
|
||||
Ok(Route::Search)
|
||||
})
|
||||
},
|
||||
|p| {
|
||||
p.parse_all(|p| {
|
||||
p.parse_token("thread")?;
|
||||
p.parse_token("root")?;
|
||||
|
||||
let root = tokenator::parse_hex_id(p)?;
|
||||
|
||||
p.parse_token("reply")?;
|
||||
|
||||
let selected = tokenator::parse_hex_id(p)?;
|
||||
|
||||
Ok(Route::Thread(ThreadSelection {
|
||||
root_id: RootNoteIdBuf::new_unsafe(root),
|
||||
selected_note: Some(NoteId::new(selected)),
|
||||
}))
|
||||
})
|
||||
},
|
||||
|p| {
|
||||
p.parse_all(|p| {
|
||||
p.parse_token("thread")?;
|
||||
Ok(Route::Thread(ThreadSelection::from_root_id(
|
||||
RootNoteIdBuf::new_unsafe(tokenator::parse_hex_id(p)?),
|
||||
)))
|
||||
})
|
||||
},
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -206,6 +244,7 @@ impl Route {
|
||||
pub fn title(&self) -> ColumnTitle<'_> {
|
||||
match self {
|
||||
Route::Timeline(kind) => kind.to_title(),
|
||||
Route::Thread(_) => ColumnTitle::simple("Thread"),
|
||||
Route::Reply(_id) => ColumnTitle::simple("Reply"),
|
||||
Route::Quote(_id) => ColumnTitle::simple("Quote"),
|
||||
Route::Relays => ColumnTitle::simple("Relays"),
|
||||
@@ -423,9 +462,9 @@ impl fmt::Display for Route {
|
||||
TimelineKind::Generic(_) => write!(f, "Custom"),
|
||||
TimelineKind::Search(_) => write!(f, "Search"),
|
||||
TimelineKind::Hashtag(ht) => write!(f, "Hashtag ({})", ht),
|
||||
TimelineKind::Thread(_id) => write!(f, "Thread"),
|
||||
TimelineKind::Profile(_id) => write!(f, "Profile"),
|
||||
},
|
||||
Route::Thread(_) => write!(f, "Thread"),
|
||||
Route::Reply(_id) => write!(f, "Reply"),
|
||||
Route::Quote(_id) => write!(f, "Quote"),
|
||||
Route::Relays => write!(f, "Relays"),
|
||||
@@ -482,3 +521,30 @@ impl<R: Clone> Default for SingletonRouter<R> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use enostr::NoteId;
|
||||
use tokenator::{TokenParser, TokenWriter};
|
||||
|
||||
use crate::{timeline::ThreadSelection, Route};
|
||||
use enostr::Pubkey;
|
||||
use notedeck::RootNoteIdBuf;
|
||||
|
||||
#[test]
|
||||
fn test_thread_route_serialize() {
|
||||
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 = Route::parse(&mut parser, &Pubkey::new(*note_id.bytes())).unwrap();
|
||||
let expected = Route::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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,8 +208,6 @@ pub enum TimelineKind {
|
||||
|
||||
Profile(Pubkey),
|
||||
|
||||
Thread(ThreadSelection),
|
||||
|
||||
Universe,
|
||||
|
||||
/// Generic filter, references a hash of a filter
|
||||
@@ -266,7 +264,6 @@ impl Display for TimelineKind {
|
||||
TimelineKind::Profile(_) => f.write_str("Profile"),
|
||||
TimelineKind::Universe => f.write_str("Universe"),
|
||||
TimelineKind::Hashtag(_) => f.write_str("Hashtag"),
|
||||
TimelineKind::Thread(_) => f.write_str("Thread"),
|
||||
TimelineKind::Search(_) => f.write_str("Search"),
|
||||
}
|
||||
}
|
||||
@@ -282,7 +279,6 @@ impl TimelineKind {
|
||||
TimelineKind::Universe => None,
|
||||
TimelineKind::Generic(_) => None,
|
||||
TimelineKind::Hashtag(_ht) => None,
|
||||
TimelineKind::Thread(_ht) => None,
|
||||
TimelineKind::Search(query) => query.author(),
|
||||
}
|
||||
}
|
||||
@@ -298,7 +294,6 @@ impl TimelineKind {
|
||||
TimelineKind::Universe => true,
|
||||
TimelineKind::Generic(_) => true,
|
||||
TimelineKind::Hashtag(_ht) => true,
|
||||
TimelineKind::Thread(_ht) => true,
|
||||
TimelineKind::Search(_q) => true,
|
||||
}
|
||||
}
|
||||
@@ -321,10 +316,6 @@ impl TimelineKind {
|
||||
writer.write_token("profile");
|
||||
PubkeySource::pubkey(*pk).serialize_tokens(writer);
|
||||
}
|
||||
TimelineKind::Thread(root_note_id) => {
|
||||
writer.write_token("thread");
|
||||
writer.write_token(&root_note_id.root_id.hex());
|
||||
}
|
||||
TimelineKind::Universe => {
|
||||
writer.write_token("universe");
|
||||
}
|
||||
@@ -377,12 +368,6 @@ impl TimelineKind {
|
||||
TokenParser::alt(
|
||||
parser,
|
||||
&[
|
||||
|p| {
|
||||
p.parse_token("thread")?;
|
||||
Ok(TimelineKind::Thread(ThreadSelection::from_root_id(
|
||||
RootNoteIdBuf::new_unsafe(tokenator::parse_hex_id(p)?),
|
||||
)))
|
||||
},
|
||||
|p| {
|
||||
p.parse_token("universe")?;
|
||||
Ok(TimelineKind::Universe)
|
||||
@@ -425,10 +410,6 @@ impl TimelineKind {
|
||||
TimelineKind::Profile(pk)
|
||||
}
|
||||
|
||||
pub fn thread(selected_note: ThreadSelection) -> Self {
|
||||
TimelineKind::Thread(selected_note)
|
||||
}
|
||||
|
||||
pub fn is_notifications(&self) -> bool {
|
||||
matches!(self, TimelineKind::Notifications(_))
|
||||
}
|
||||
@@ -474,17 +455,6 @@ impl TimelineKind {
|
||||
todo!("implement generic filter lookups")
|
||||
}
|
||||
|
||||
TimelineKind::Thread(selection) => FilterState::ready(vec![
|
||||
nostrdb::Filter::new()
|
||||
.kinds([1])
|
||||
.event(selection.root_id.bytes())
|
||||
.build(),
|
||||
nostrdb::Filter::new()
|
||||
.ids([selection.root_id.bytes()])
|
||||
.limit(1)
|
||||
.build(),
|
||||
]),
|
||||
|
||||
TimelineKind::Profile(pk) => FilterState::ready(vec![Filter::new()
|
||||
.authors([pk.bytes()])
|
||||
.kinds([1])
|
||||
@@ -510,8 +480,6 @@ impl TimelineKind {
|
||||
TimelineTab::full_tabs(),
|
||||
)),
|
||||
|
||||
TimelineKind::Thread(root_id) => Some(Timeline::thread(root_id)),
|
||||
|
||||
TimelineKind::Generic(_filter_id) => {
|
||||
warn!("you can't convert a TimelineKind::Generic to a Timeline");
|
||||
// TODO: you actually can! just need to look up the filter id
|
||||
@@ -609,7 +577,6 @@ impl TimelineKind {
|
||||
},
|
||||
TimelineKind::Notifications(_pubkey_source) => ColumnTitle::simple("Notifications"),
|
||||
TimelineKind::Profile(_pubkey_source) => ColumnTitle::needs_db(self),
|
||||
TimelineKind::Thread(_root_id) => ColumnTitle::simple("Thread"),
|
||||
TimelineKind::Universe => ColumnTitle::simple("Universe"),
|
||||
TimelineKind::Generic(_) => ColumnTitle::simple("Custom"),
|
||||
TimelineKind::Hashtag(hashtag) => ColumnTitle::formatted(hashtag.to_string()),
|
||||
|
||||
@@ -214,24 +214,6 @@ impl Timeline {
|
||||
))
|
||||
}
|
||||
|
||||
pub fn thread(selection: ThreadSelection) -> Self {
|
||||
let filter = vec![
|
||||
nostrdb::Filter::new()
|
||||
.kinds([1])
|
||||
.event(selection.root_id.bytes())
|
||||
.build(),
|
||||
nostrdb::Filter::new()
|
||||
.ids([selection.root_id.bytes()])
|
||||
.limit(1)
|
||||
.build(),
|
||||
];
|
||||
Timeline::new(
|
||||
TimelineKind::Thread(selection),
|
||||
FilterState::ready(filter),
|
||||
TimelineTab::only_notes_and_replies(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn last_per_pubkey(list: &Note, list_kind: &ListKind) -> Result<Self> {
|
||||
let kind = 1;
|
||||
let notes_per_pk = 1;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
nav::RenderNavAction,
|
||||
profile::ProfileAction,
|
||||
timeline::{TimelineCache, TimelineKind},
|
||||
timeline::{thread::Threads, ThreadSelection, TimelineCache, TimelineKind},
|
||||
ui::{self, ProfileView},
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@ pub fn render_timeline_route(
|
||||
accounts: &mut Accounts,
|
||||
kind: &TimelineKind,
|
||||
col: usize,
|
||||
mut note_options: NoteOptions,
|
||||
note_options: NoteOptions,
|
||||
depth: usize,
|
||||
ui: &mut egui::Ui,
|
||||
note_context: &mut NoteContext,
|
||||
@@ -74,32 +74,40 @@ pub fn render_timeline_route(
|
||||
note_action.map(RenderNavAction::NoteAction)
|
||||
}
|
||||
}
|
||||
|
||||
TimelineKind::Thread(id) => {
|
||||
// don't truncate thread notes for now, since they are
|
||||
// default truncated everywher eelse
|
||||
note_options.set_truncate(false);
|
||||
|
||||
// text is selectable in threads
|
||||
note_options.set_selectable_text(true);
|
||||
|
||||
ui::ThreadView::new(
|
||||
timeline_cache,
|
||||
unknown_ids,
|
||||
id.selected_or_root(),
|
||||
note_options,
|
||||
&accounts.mutefun(),
|
||||
note_context,
|
||||
&accounts.get_selected_account().map(|a| (&a.key).into()),
|
||||
jobs,
|
||||
)
|
||||
.id_source(egui::Id::new(("threadscroll", col)))
|
||||
.ui(ui)
|
||||
.map(Into::into)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn render_thread_route(
|
||||
unknown_ids: &mut UnknownIds,
|
||||
threads: &mut Threads,
|
||||
accounts: &mut Accounts,
|
||||
selection: &ThreadSelection,
|
||||
col: usize,
|
||||
mut note_options: NoteOptions,
|
||||
ui: &mut egui::Ui,
|
||||
note_context: &mut NoteContext,
|
||||
jobs: &mut JobsCache,
|
||||
) -> Option<RenderNavAction> {
|
||||
// don't truncate thread notes for now, since they are
|
||||
// default truncated everywher eelse
|
||||
note_options.set_truncate(false);
|
||||
|
||||
ui::ThreadView::new(
|
||||
threads,
|
||||
unknown_ids,
|
||||
selection.selected_or_root(),
|
||||
note_options,
|
||||
&accounts.mutefun(),
|
||||
note_context,
|
||||
&accounts.get_selected_account().map(|a| (&a.key).into()),
|
||||
jobs,
|
||||
)
|
||||
.id_source(col)
|
||||
.ui(ui)
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn render_profile_route(
|
||||
pubkey: &Pubkey,
|
||||
@@ -139,30 +147,3 @@ pub fn render_profile_route(
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use enostr::NoteId;
|
||||
use tokenator::{TokenParser, TokenWriter};
|
||||
|
||||
use crate::timeline::{ThreadSelection, TimelineKind};
|
||||
use enostr::Pubkey;
|
||||
use notedeck::RootNoteIdBuf;
|
||||
|
||||
#[test]
|
||||
fn test_timeline_route_serialize() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::column::ColumnsAction;
|
||||
use crate::nav::RenderNavAction;
|
||||
use crate::nav::SwitchingAction;
|
||||
use crate::timeline::ThreadSelection;
|
||||
use crate::{
|
||||
column::Columns,
|
||||
route::Route,
|
||||
@@ -437,11 +438,6 @@ impl<'a> NavTitle<'a> {
|
||||
|
||||
TimelineKind::Profile(pubkey) => Some(self.show_profile(ui, pubkey, pfp_size)),
|
||||
|
||||
TimelineKind::Thread(_) => {
|
||||
// no pfp for threads
|
||||
None
|
||||
}
|
||||
|
||||
TimelineKind::Search(_sq) => {
|
||||
// TODO: show author pfp if author field set?
|
||||
|
||||
@@ -467,6 +463,9 @@ impl<'a> NavTitle<'a> {
|
||||
Route::Search => Some(ui.add(ui::side_panel::search_button())),
|
||||
Route::Wallet(_) => None,
|
||||
Route::CustomizeZapAmount(_) => None,
|
||||
Route::Thread(thread_selection) => {
|
||||
Some(self.thread_pfp(ui, thread_selection, pfp_size))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,6 +487,23 @@ impl<'a> NavTitle<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn thread_pfp(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
selection: &ThreadSelection,
|
||||
pfp_size: f32,
|
||||
) -> egui::Response {
|
||||
let txn = Transaction::new(self.ndb).unwrap();
|
||||
|
||||
if let Ok(note) = self.ndb.get_note_by_id(&txn, selection.selected_or_root()) {
|
||||
if let Some(mut pfp) = self.pubkey_pfp(&txn, note.pubkey(), pfp_size) {
|
||||
return ui.add(&mut pfp);
|
||||
}
|
||||
}
|
||||
|
||||
ui.add(&mut ProfilePic::new(self.img_cache, notedeck::profile::no_pfp_url()).size(pfp_size))
|
||||
}
|
||||
|
||||
fn title_label_value(title: &str) -> egui::Label {
|
||||
egui::Label::new(RichText::new(title).text_style(NotedeckTextStyle::Body.text_style()))
|
||||
.selectable(false)
|
||||
|
||||
@@ -3,21 +3,19 @@ use egui_virtual_list::VirtualList;
|
||||
use enostr::KeypairUnowned;
|
||||
use nostrdb::{Note, Transaction};
|
||||
use notedeck::note::root_note_id_from_selected_id;
|
||||
use notedeck::{MuteFun, NoteAction, NoteContext, RootNoteId, UnknownIds};
|
||||
use notedeck::{MuteFun, NoteAction, NoteContext, UnknownIds};
|
||||
use notedeck_ui::jobs::JobsCache;
|
||||
use notedeck_ui::note::NoteResponse;
|
||||
use notedeck_ui::{NoteOptions, NoteView};
|
||||
use tracing::error;
|
||||
|
||||
use crate::timeline::thread::NoteSeenFlags;
|
||||
use crate::timeline::{ThreadSelection, TimelineCache, TimelineKind};
|
||||
use crate::ui::timeline::TimelineTabView;
|
||||
use crate::timeline::thread::{NoteSeenFlags, ParentState, Threads};
|
||||
|
||||
pub struct ThreadView<'a, 'd> {
|
||||
timeline_cache: &'a mut TimelineCache,
|
||||
threads: &'a mut Threads,
|
||||
unknown_ids: &'a mut UnknownIds,
|
||||
selected_note_id: &'a [u8; 32],
|
||||
note_options: NoteOptions,
|
||||
col: usize,
|
||||
id_source: egui::Id,
|
||||
is_muted: &'a MuteFun,
|
||||
note_context: &'a mut NoteContext<'d>,
|
||||
@@ -28,7 +26,7 @@ pub struct ThreadView<'a, 'd> {
|
||||
impl<'a, 'd> ThreadView<'a, 'd> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
timeline_cache: &'a mut TimelineCache,
|
||||
threads: &'a mut Threads,
|
||||
unknown_ids: &'a mut UnknownIds,
|
||||
selected_note_id: &'a [u8; 32],
|
||||
note_options: NoteOptions,
|
||||
@@ -39,7 +37,7 @@ impl<'a, 'd> ThreadView<'a, 'd> {
|
||||
) -> Self {
|
||||
let id_source = egui::Id::new("threadscroll_threadview");
|
||||
ThreadView {
|
||||
timeline_cache,
|
||||
threads,
|
||||
unknown_ids,
|
||||
selected_note_id,
|
||||
note_options,
|
||||
@@ -48,11 +46,13 @@ impl<'a, 'd> ThreadView<'a, 'd> {
|
||||
note_context,
|
||||
cur_acc,
|
||||
jobs,
|
||||
col: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id_source(mut self, id: egui::Id) -> Self {
|
||||
self.id_source = id;
|
||||
pub fn id_source(mut self, col: usize) -> Self {
|
||||
self.col = col;
|
||||
self.id_source = egui::Id::new(("threadscroll", col));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -73,62 +73,88 @@ impl<'a, 'd> ThreadView<'a, 'd> {
|
||||
scroll_area = scroll_area.vertical_scroll_offset(offset);
|
||||
}
|
||||
|
||||
let output = scroll_area.show(ui, |ui| {
|
||||
let root_id = match RootNoteId::new(
|
||||
self.note_context.ndb,
|
||||
self.note_context.note_cache,
|
||||
&txn,
|
||||
self.selected_note_id,
|
||||
) {
|
||||
Ok(root_id) => root_id,
|
||||
|
||||
Err(err) => {
|
||||
ui.label(format!("Error loading thread: {:?}", err));
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let thread_timeline = self
|
||||
.timeline_cache
|
||||
.notes(
|
||||
self.note_context.ndb,
|
||||
self.note_context.note_cache,
|
||||
&txn,
|
||||
&TimelineKind::Thread(ThreadSelection::from_root_id(root_id.to_owned())),
|
||||
)
|
||||
.get_ptr();
|
||||
|
||||
// TODO(jb55): skip poll if ThreadResult is fresh?
|
||||
|
||||
let reversed = true;
|
||||
// poll for new notes and insert them into our existing notes
|
||||
if let Err(err) = thread_timeline.poll_notes_into_view(
|
||||
self.note_context.ndb,
|
||||
&txn,
|
||||
self.unknown_ids,
|
||||
self.note_context.note_cache,
|
||||
reversed,
|
||||
) {
|
||||
error!("error polling notes into thread timeline: {err}");
|
||||
}
|
||||
|
||||
TimelineTabView::new(
|
||||
thread_timeline.current_view(),
|
||||
true,
|
||||
self.note_options,
|
||||
&txn,
|
||||
self.is_muted,
|
||||
self.note_context,
|
||||
self.cur_acc,
|
||||
self.jobs,
|
||||
)
|
||||
.show(ui)
|
||||
});
|
||||
let output = scroll_area.show(ui, |ui| self.notes(ui, &txn));
|
||||
|
||||
ui.data_mut(|d| d.insert_temp(offset_id, output.state.offset.y));
|
||||
|
||||
output.inner
|
||||
}
|
||||
|
||||
fn notes(&mut self, ui: &mut egui::Ui, txn: &Transaction) -> Option<NoteAction> {
|
||||
let Ok(cur_note) = self
|
||||
.note_context
|
||||
.ndb
|
||||
.get_note_by_id(txn, self.selected_note_id)
|
||||
else {
|
||||
let id = *self.selected_note_id;
|
||||
tracing::error!("ndb: Did not find note {}", enostr::NoteId::new(id).hex());
|
||||
return None;
|
||||
};
|
||||
|
||||
self.threads.update(
|
||||
&cur_note,
|
||||
self.note_context.note_cache,
|
||||
self.note_context.ndb,
|
||||
txn,
|
||||
self.unknown_ids,
|
||||
self.col,
|
||||
);
|
||||
|
||||
let cur_node = self.threads.threads.get(&self.selected_note_id).unwrap();
|
||||
|
||||
let full_chain = cur_node.have_all_ancestors;
|
||||
let mut note_builder = ThreadNoteBuilder::new(cur_note);
|
||||
|
||||
let mut parent_state = cur_node.prev.clone();
|
||||
while let ParentState::Parent(id) = parent_state {
|
||||
if let Ok(note) = self.note_context.ndb.get_note_by_id(txn, id.bytes()) {
|
||||
note_builder.add_chain(note);
|
||||
if let Some(res) = self.threads.threads.get(&id.bytes()) {
|
||||
parent_state = res.prev.clone();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
parent_state = ParentState::Unknown;
|
||||
}
|
||||
|
||||
for note_ref in &cur_node.replies {
|
||||
if let Ok(note) = self.note_context.ndb.get_note_by_key(txn, note_ref.key) {
|
||||
note_builder.add_reply(note);
|
||||
}
|
||||
}
|
||||
|
||||
let list = &mut self
|
||||
.threads
|
||||
.threads
|
||||
.get_mut(&self.selected_note_id)
|
||||
.unwrap()
|
||||
.list;
|
||||
|
||||
let notes = note_builder.into_notes(&mut self.threads.seen_flags);
|
||||
|
||||
if !full_chain {
|
||||
// TODO(kernelkind): insert UI denoting we don't have the full chain yet
|
||||
ui.colored_label(ui.visuals().error_fg_color, "LOADING NOTES");
|
||||
}
|
||||
|
||||
let zapping_acc = self
|
||||
.cur_acc
|
||||
.as_ref()
|
||||
.filter(|_| self.note_context.current_account_has_wallet)
|
||||
.or(self.cur_acc.as_ref());
|
||||
|
||||
show_notes(
|
||||
ui,
|
||||
list,
|
||||
¬es,
|
||||
self.note_context,
|
||||
zapping_acc,
|
||||
self.note_options,
|
||||
self.jobs,
|
||||
txn,
|
||||
self.is_muted,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
||||
Reference in New Issue
Block a user