use crate::{ column::Columns, route::{Route, Router}, timeline::{ThreadSelection, TimelineCache, TimelineKind}, }; use enostr::{Pubkey, RelayPool}; use nostrdb::{Ndb, NoteKey, Transaction}; use notedeck::{ get_wallet_for_mut, Accounts, GlobalWallet, NoteAction, NoteCache, NoteZapTargetOwned, UnknownIds, ZapAction, ZapTarget, ZappingError, Zaps, }; use tracing::error; pub struct NewNotes { pub id: TimelineKind, pub notes: Vec, } pub enum TimelineOpenResult { NewNotes(NewNotes), } /// The note action executor for notedeck_columns #[allow(clippy::too_many_arguments)] fn execute_note_action( action: &NoteAction, ndb: &Ndb, router: &mut Router, timeline_cache: &mut TimelineCache, note_cache: &mut NoteCache, pool: &mut RelayPool, txn: &Transaction, accounts: &mut Accounts, global_wallet: &mut GlobalWallet, zaps: &mut Zaps, ui: &mut egui::Ui, ) -> Option { match action { NoteAction::Reply(note_id) => { router.route_to(Route::reply(*note_id)); None } NoteAction::Profile(pubkey) => { let kind = TimelineKind::Profile(*pubkey); router.route_to(Route::Timeline(kind.clone())); timeline_cache.open(ndb, note_cache, txn, pool, &kind) } NoteAction::Note(note_id) => 'ex: { let Ok(thread_selection) = ThreadSelection::from_note_id(ndb, note_cache, txn, *note_id) else { tracing::error!("No thread selection for {}?", hex::encode(note_id.bytes())); break 'ex None; }; let kind = TimelineKind::Thread(thread_selection); router.route_to(Route::Timeline(kind.clone())); // NOTE!!: you need the note_id to timeline root id thing timeline_cache.open(ndb, note_cache, txn, pool, &kind) } NoteAction::Hashtag(htag) => { let kind = TimelineKind::Hashtag(htag.clone()); router.route_to(Route::Timeline(kind.clone())); timeline_cache.open(ndb, note_cache, txn, pool, &kind) } NoteAction::Quote(note_id) => { router.route_to(Route::quote(*note_id)); None } NoteAction::Zap(zap_action) => 's: { let Some(cur_acc) = accounts.get_selected_account_mut() else { break 's None; }; let sender = cur_acc.key.pubkey; match zap_action { ZapAction::Send(target) => { if get_wallet_for_mut(accounts, global_wallet, sender.bytes()).is_some() { send_zap(&sender, zaps, pool, target) } else { zaps.send_error( sender.bytes(), ZapTarget::Note(target.into()), ZappingError::SenderNoWallet, ); } } ZapAction::ClearError(target) => clear_zap_error(&sender, zaps, target), } None } NoteAction::Context(context) => { match ndb.get_note_by_key(txn, context.note_key) { Err(err) => tracing::error!("{err}"), Ok(note) => { context.action.process(ui, ¬e, pool); } } None } } } /// Execute a NoteAction and process the result #[allow(clippy::too_many_arguments)] pub fn execute_and_process_note_action( action: &NoteAction, ndb: &Ndb, columns: &mut Columns, col: usize, timeline_cache: &mut TimelineCache, note_cache: &mut NoteCache, pool: &mut RelayPool, txn: &Transaction, unknown_ids: &mut UnknownIds, accounts: &mut Accounts, global_wallet: &mut GlobalWallet, zaps: &mut Zaps, ui: &mut egui::Ui, ) { let router = columns.column_mut(col).router_mut(); if let Some(br) = execute_note_action( action, ndb, router, timeline_cache, note_cache, pool, txn, accounts, global_wallet, zaps, ui, ) { br.process(ndb, note_cache, txn, timeline_cache, unknown_ids); } } fn send_zap(sender: &Pubkey, zaps: &mut Zaps, pool: &RelayPool, target: &NoteZapTargetOwned) { let default_zap_msats = 10_000; // TODO(kernelkind): allow the user to set this default let zap_target = ZapTarget::Note(target.into()); let sender_relays: Vec = pool.relays.iter().map(|r| r.url().to_string()).collect(); zaps.send_zap(sender.bytes(), sender_relays, zap_target, default_zap_msats); } fn clear_zap_error(sender: &Pubkey, zaps: &mut Zaps, target: &NoteZapTargetOwned) { zaps.clear_error_for(sender.bytes(), ZapTarget::Note(target.into())); } impl TimelineOpenResult { pub fn new_notes(notes: Vec, id: TimelineKind) -> Self { Self::NewNotes(NewNotes::new(notes, id)) } pub fn process( &self, ndb: &Ndb, note_cache: &mut NoteCache, txn: &Transaction, storage: &mut TimelineCache, unknown_ids: &mut UnknownIds, ) { match self { // update the thread for next render if we have new notes TimelineOpenResult::NewNotes(new_notes) => { new_notes.process(storage, ndb, txn, unknown_ids, note_cache); } } } } impl NewNotes { pub fn new(notes: Vec, id: TimelineKind) -> Self { NewNotes { notes, id } } /// Simple helper for processing a NewThreadNotes result. It simply /// inserts/merges the notes into the corresponding timeline cache pub fn process( &self, timeline_cache: &mut TimelineCache, ndb: &Ndb, txn: &Transaction, unknown_ids: &mut UnknownIds, note_cache: &mut NoteCache, ) { let reversed = matches!(&self.id, TimelineKind::Thread(_)); let timeline = if let Some(profile) = timeline_cache.timelines.get_mut(&self.id) { profile } else { error!("NewNotes: could not get timeline for key {}", self.id); return; }; if let Err(err) = timeline.insert(&self.notes, ndb, txn, unknown_ids, note_cache, reversed) { error!("error inserting notes into profile timeline: {err}") } } }