commit d560e84eab3c64070fb99ffac7802c24cdf91190
parent f6753bae975cd0453eeb5426dbcefab37ac82008
Author: kernelkind <kernelkind@gmail.com>
Date: Tue, 17 Jun 2025 12:49:09 -0400
integrate new threads conception
Signed-off-by: kernelkind <kernelkind@gmail.com>
Diffstat:
10 files changed, 258 insertions(+), 183 deletions(-)
diff --git a/crates/notedeck_chrome/src/chrome.rs b/crates/notedeck_chrome/src/chrome.rs
@@ -625,6 +625,7 @@ fn chrome_handle_app_action(
cols,
0,
&mut columns.timeline_cache,
+ &mut columns.threads,
ctx.note_cache,
ctx.pool,
&txn,
diff --git a/crates/notedeck_columns/src/actionbar.rs b/crates/notedeck_columns/src/actionbar.rs
@@ -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
diff --git a/crates/notedeck_columns/src/app.rs b/crates/notedeck_columns/src/app.rs
@@ -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(),
}
}
diff --git a/crates/notedeck_columns/src/nav.rs b/crates/notedeck_columns/src/nav.rs
@@ -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,
diff --git a/crates/notedeck_columns/src/route.rs b/crates/notedeck_columns/src/route.rs
@@ -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);
+ }
+}
diff --git a/crates/notedeck_columns/src/timeline/kind.rs b/crates/notedeck_columns/src/timeline/kind.rs
@@ -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");
}
@@ -378,12 +369,6 @@ impl TimelineKind {
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()),
diff --git a/crates/notedeck_columns/src/timeline/mod.rs b/crates/notedeck_columns/src/timeline/mod.rs
@@ -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;
diff --git a/crates/notedeck_columns/src/timeline/route.rs b/crates/notedeck_columns/src/timeline/route.rs
@@ -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,30 +74,38 @@ 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);
+#[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(
- 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)
- }
- }
+ 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)]
@@ -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);
- }
-}
diff --git a/crates/notedeck_columns/src/ui/column/header.rs b/crates/notedeck_columns/src/ui/column/header.rs
@@ -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)
diff --git a/crates/notedeck_columns/src/ui/thread.rs b/crates/notedeck_columns/src/ui/thread.rs
@@ -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,61 +73,87 @@ 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 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;
}
- };
-
- 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}");
}
+ parent_state = ParentState::Unknown;
+ }
- TimelineTabView::new(
- thread_timeline.current_view(),
- true,
- self.note_options,
- &txn,
- self.is_muted,
- self.note_context,
- self.cur_acc,
- self.jobs,
- )
- .show(ui)
- });
+ 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);
+ }
+ }
- ui.data_mut(|d| d.insert_temp(offset_id, output.state.offset.y));
+ let list = &mut self
+ .threads
+ .threads
+ .get_mut(&self.selected_note_id)
+ .unwrap()
+ .list;
- output.inner
+ 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,
+ )
}
}