actionbar.rs (6507B)
1 use crate::{ 2 column::Columns, 3 route::{Route, Router}, 4 timeline::{TimelineCache, TimelineCacheKey}, 5 }; 6 7 use enostr::{NoteId, Pubkey, RelayPool}; 8 use nostrdb::{Ndb, NoteKey, Transaction}; 9 use notedeck::{note::root_note_id_from_selected_id, NoteCache, RootIdError, UnknownIds}; 10 use tracing::error; 11 12 #[derive(Debug, Eq, PartialEq, Copy, Clone)] 13 pub enum NoteAction { 14 Reply(NoteId), 15 Quote(NoteId), 16 OpenThread(NoteId), 17 OpenProfile(Pubkey), 18 } 19 20 pub struct NewNotes<'a> { 21 pub id: TimelineCacheKey<'a>, 22 pub notes: Vec<NoteKey>, 23 } 24 25 pub enum TimelineOpenResult<'a> { 26 NewNotes(NewNotes<'a>), 27 } 28 29 /// open_thread is called when a note is selected and we need to navigate 30 /// to a thread It is responsible for managing the subscription and 31 /// making sure the thread is up to date. In a sense, it's a model for 32 /// the thread view. We don't have a concept of model/view/controller etc 33 /// in egui, but this is the closest thing to that. 34 #[allow(clippy::too_many_arguments)] 35 fn open_thread<'txn>( 36 ndb: &Ndb, 37 txn: &'txn Transaction, 38 router: &mut Router<Route>, 39 note_cache: &mut NoteCache, 40 pool: &mut RelayPool, 41 timeline_cache: &mut TimelineCache, 42 selected_note: &'txn [u8; 32], 43 ) -> Option<TimelineOpenResult<'txn>> { 44 router.route_to(Route::thread(NoteId::new(selected_note.to_owned()))); 45 46 match root_note_id_from_selected_id(ndb, note_cache, txn, selected_note) { 47 Ok(root_id) => timeline_cache.open( 48 ndb, 49 note_cache, 50 txn, 51 pool, 52 TimelineCacheKey::thread(root_id), 53 ), 54 55 Err(RootIdError::NoteNotFound) => { 56 error!( 57 "open_thread: note not found: {}", 58 hex::encode(selected_note) 59 ); 60 None 61 } 62 63 Err(RootIdError::NoRootId) => { 64 error!( 65 "open_thread: note has no root id: {}", 66 hex::encode(selected_note) 67 ); 68 None 69 } 70 } 71 } 72 73 impl NoteAction { 74 #[allow(clippy::too_many_arguments)] 75 pub fn execute<'txn, 'a>( 76 &'a self, 77 ndb: &Ndb, 78 router: &mut Router<Route>, 79 timeline_cache: &mut TimelineCache, 80 note_cache: &mut NoteCache, 81 pool: &mut RelayPool, 82 txn: &'txn Transaction, 83 ) -> Option<TimelineOpenResult<'txn>> 84 where 85 'a: 'txn, 86 { 87 match self { 88 NoteAction::Reply(note_id) => { 89 router.route_to(Route::reply(*note_id)); 90 None 91 } 92 93 NoteAction::OpenThread(note_id) => open_thread( 94 ndb, 95 txn, 96 router, 97 note_cache, 98 pool, 99 timeline_cache, 100 note_id.bytes(), 101 ), 102 103 NoteAction::OpenProfile(pubkey) => { 104 router.route_to(Route::profile(*pubkey)); 105 timeline_cache.open( 106 ndb, 107 note_cache, 108 txn, 109 pool, 110 TimelineCacheKey::profile(pubkey.as_ref()), 111 ) 112 } 113 114 NoteAction::Quote(note_id) => { 115 router.route_to(Route::quote(*note_id)); 116 None 117 } 118 } 119 } 120 121 /// Execute the NoteAction and process the TimelineOpenResult 122 #[allow(clippy::too_many_arguments)] 123 pub fn execute_and_process_result( 124 self, 125 ndb: &Ndb, 126 columns: &mut Columns, 127 col: usize, 128 timeline_cache: &mut TimelineCache, 129 note_cache: &mut NoteCache, 130 pool: &mut RelayPool, 131 txn: &Transaction, 132 unknown_ids: &mut UnknownIds, 133 ) { 134 let router = columns.column_mut(col).router_mut(); 135 if let Some(br) = self.execute(ndb, router, timeline_cache, note_cache, pool, txn) { 136 br.process(ndb, note_cache, txn, timeline_cache, unknown_ids); 137 } 138 } 139 } 140 141 impl<'a> TimelineOpenResult<'a> { 142 pub fn new_notes(notes: Vec<NoteKey>, id: TimelineCacheKey<'a>) -> Self { 143 Self::NewNotes(NewNotes::new(notes, id)) 144 } 145 146 pub fn process( 147 &self, 148 ndb: &Ndb, 149 note_cache: &mut NoteCache, 150 txn: &Transaction, 151 storage: &mut TimelineCache, 152 unknown_ids: &mut UnknownIds, 153 ) { 154 match self { 155 // update the thread for next render if we have new notes 156 TimelineOpenResult::NewNotes(new_notes) => { 157 new_notes.process(storage, ndb, txn, unknown_ids, note_cache); 158 } 159 } 160 } 161 } 162 163 impl<'a> NewNotes<'a> { 164 pub fn new(notes: Vec<NoteKey>, id: TimelineCacheKey<'a>) -> Self { 165 NewNotes { notes, id } 166 } 167 168 /// Simple helper for processing a NewThreadNotes result. It simply 169 /// inserts/merges the notes into the corresponding timeline cache 170 pub fn process( 171 &self, 172 timeline_cache: &mut TimelineCache, 173 ndb: &Ndb, 174 txn: &Transaction, 175 unknown_ids: &mut UnknownIds, 176 note_cache: &mut NoteCache, 177 ) { 178 match self.id { 179 TimelineCacheKey::Profile(pubkey) => { 180 let profile = if let Some(profile) = timeline_cache.profiles.get_mut(pubkey.bytes()) 181 { 182 profile 183 } else { 184 return; 185 }; 186 187 let reversed = false; 188 189 if let Err(err) = profile.timeline.insert( 190 &self.notes, 191 ndb, 192 txn, 193 unknown_ids, 194 note_cache, 195 reversed, 196 ) { 197 error!("error inserting notes into profile timeline: {err}") 198 } 199 } 200 201 TimelineCacheKey::Thread(root_id) => { 202 // threads are chronological, ie reversed from reverse-chronological, the default. 203 let reversed = true; 204 let thread = if let Some(thread) = timeline_cache.threads.get_mut(root_id.bytes()) { 205 thread 206 } else { 207 return; 208 }; 209 210 if let Err(err) = 211 thread 212 .timeline 213 .insert(&self.notes, ndb, txn, unknown_ids, note_cache, reversed) 214 { 215 error!("error inserting notes into thread timeline: {err}") 216 } 217 } 218 } 219 } 220 }