nav.rs (9104B)
1 use crate::{ 2 accounts::render_accounts_route, 3 actionbar::NoteAction, 4 notes_holder::NotesHolder, 5 profile::Profile, 6 relay_pool_manager::RelayPoolManager, 7 route::Route, 8 thread::Thread, 9 timeline::{ 10 route::{render_timeline_route, TimelineRoute}, 11 Timeline, 12 }, 13 ui::{ 14 self, 15 add_column::render_add_column_routes, 16 column::NavTitle, 17 note::{PostAction, PostType}, 18 support::SupportView, 19 RelayView, View, 20 }, 21 Damus, 22 }; 23 24 use egui_nav::{Nav, NavAction, NavResponse, NavUiType}; 25 use nostrdb::{Ndb, Transaction}; 26 use tracing::{error, info}; 27 28 pub enum RenderNavAction { 29 Back, 30 RemoveColumn, 31 PostAction(PostAction), 32 NoteAction(NoteAction), 33 } 34 35 impl From<PostAction> for RenderNavAction { 36 fn from(post_action: PostAction) -> Self { 37 Self::PostAction(post_action) 38 } 39 } 40 41 impl From<NoteAction> for RenderNavAction { 42 fn from(note_action: NoteAction) -> RenderNavAction { 43 Self::NoteAction(note_action) 44 } 45 } 46 47 pub type NotedeckNavResponse = NavResponse<Option<RenderNavAction>>; 48 49 pub struct RenderNavResponse { 50 column: usize, 51 response: NotedeckNavResponse, 52 } 53 54 impl RenderNavResponse { 55 #[allow(private_interfaces)] 56 pub fn new(column: usize, response: NotedeckNavResponse) -> Self { 57 RenderNavResponse { column, response } 58 } 59 60 #[must_use = "Make sure to save columns if result is true"] 61 pub fn process_render_nav_response(&self, app: &mut Damus) -> bool { 62 let mut col_changed: bool = false; 63 let col = self.column; 64 65 if let Some(action) = self 66 .response 67 .response 68 .as_ref() 69 .or(self.response.title_response.as_ref()) 70 { 71 // start returning when we're finished posting 72 match action { 73 RenderNavAction::Back => { 74 app.columns_mut().column_mut(col).router_mut().go_back(); 75 } 76 77 RenderNavAction::RemoveColumn => { 78 let tl = app.columns().find_timeline_for_column_index(col); 79 if let Some(timeline) = tl { 80 unsubscribe_timeline(app.ndb(), timeline); 81 } 82 83 app.columns_mut().delete_column(col); 84 col_changed = true; 85 } 86 87 RenderNavAction::PostAction(post_action) => { 88 let txn = Transaction::new(&app.ndb).expect("txn"); 89 let _ = post_action.execute(&app.ndb, &txn, &mut app.pool, &mut app.drafts); 90 app.columns_mut().column_mut(col).router_mut().go_back(); 91 } 92 93 RenderNavAction::NoteAction(note_action) => { 94 let txn = Transaction::new(&app.ndb).expect("txn"); 95 96 note_action.execute_and_process_result( 97 &app.ndb, 98 &mut app.columns, 99 col, 100 &mut app.threads, 101 &mut app.profiles, 102 &mut app.note_cache, 103 &mut app.pool, 104 &txn, 105 &app.accounts.mutefun(), 106 ); 107 } 108 } 109 } 110 111 if let Some(action) = self.response.action { 112 match action { 113 NavAction::Returned => { 114 let r = app.columns_mut().column_mut(col).router_mut().pop(); 115 let txn = Transaction::new(&app.ndb).expect("txn"); 116 if let Some(Route::Timeline(TimelineRoute::Thread(id))) = r { 117 let root_id = { 118 crate::note::root_note_id_from_selected_id( 119 &app.ndb, 120 &mut app.note_cache, 121 &txn, 122 id.bytes(), 123 ) 124 }; 125 Thread::unsubscribe_locally( 126 &txn, 127 &app.ndb, 128 &mut app.note_cache, 129 &mut app.threads, 130 &mut app.pool, 131 root_id, 132 &app.accounts.mutefun(), 133 ); 134 } 135 136 if let Some(Route::Timeline(TimelineRoute::Profile(pubkey))) = r { 137 Profile::unsubscribe_locally( 138 &txn, 139 &app.ndb, 140 &mut app.note_cache, 141 &mut app.profiles, 142 &mut app.pool, 143 pubkey.bytes(), 144 &app.accounts.mutefun(), 145 ); 146 } 147 col_changed = true; 148 } 149 150 NavAction::Navigated => { 151 let cur_router = app.columns_mut().column_mut(col).router_mut(); 152 cur_router.navigating = false; 153 if cur_router.is_replacing() { 154 cur_router.remove_previous_routes(); 155 } 156 col_changed = true; 157 } 158 159 NavAction::Dragging => {} 160 NavAction::Returning => {} 161 NavAction::Resetting => {} 162 NavAction::Navigating => {} 163 } 164 } 165 166 col_changed 167 } 168 } 169 170 fn render_nav_body( 171 ui: &mut egui::Ui, 172 app: &mut Damus, 173 top: &Route, 174 col: usize, 175 ) -> Option<RenderNavAction> { 176 match top { 177 Route::Timeline(tlr) => render_timeline_route( 178 &app.ndb, 179 &mut app.columns, 180 &mut app.drafts, 181 &mut app.img_cache, 182 &mut app.unknown_ids, 183 &mut app.note_cache, 184 &mut app.threads, 185 &mut app.profiles, 186 &mut app.accounts, 187 *tlr, 188 col, 189 app.textmode, 190 ui, 191 ), 192 Route::Accounts(amr) => { 193 let action = render_accounts_route( 194 ui, 195 &app.ndb, 196 col, 197 &mut app.columns, 198 &mut app.img_cache, 199 &mut app.accounts, 200 &mut app.view_state.login, 201 *amr, 202 ); 203 let txn = Transaction::new(&app.ndb).expect("txn"); 204 action.process_action(&mut app.unknown_ids, &app.ndb, &txn); 205 None 206 } 207 Route::Relays => { 208 let manager = RelayPoolManager::new(app.pool_mut()); 209 RelayView::new(manager).ui(ui); 210 None 211 } 212 Route::ComposeNote => { 213 let kp = app.accounts.get_selected_account()?.to_full()?; 214 let draft = app.drafts.compose_mut(); 215 216 let txn = Transaction::new(&app.ndb).expect("txn"); 217 let post_response = ui::PostView::new( 218 &app.ndb, 219 draft, 220 PostType::New, 221 &mut app.img_cache, 222 &mut app.note_cache, 223 kp, 224 ) 225 .ui(&txn, ui); 226 227 post_response.action.map(Into::into) 228 } 229 Route::AddColumn(route) => { 230 render_add_column_routes(ui, app, col, route); 231 232 None 233 } 234 235 Route::Support => { 236 SupportView::new(&mut app.support).show(ui); 237 None 238 } 239 } 240 } 241 242 #[must_use = "RenderNavResponse must be handled by calling .process_render_nav_response(..)"] 243 pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) -> RenderNavResponse { 244 let col_id = app.columns.get_column_id_at_index(col); 245 // TODO(jb55): clean up this router_mut mess by using Router<R> in egui-nav directly 246 247 let nav_response = Nav::new(&app.columns().column(col).router().routes().clone()) 248 .navigating(app.columns_mut().column_mut(col).router_mut().navigating) 249 .returning(app.columns_mut().column_mut(col).router_mut().returning) 250 .id_source(egui::Id::new(col_id)) 251 .show_mut(ui, |ui, render_type, nav| match render_type { 252 NavUiType::Title => NavTitle::new( 253 &app.ndb, 254 &mut app.img_cache, 255 &app.columns, 256 app.accounts.get_selected_account().map(|a| &a.pubkey), 257 nav.routes(), 258 ) 259 .show(ui), 260 NavUiType::Body => render_nav_body(ui, app, nav.routes().last().expect("top"), col), 261 }); 262 263 RenderNavResponse::new(col, nav_response) 264 } 265 266 fn unsubscribe_timeline(ndb: &Ndb, timeline: &Timeline) { 267 if let Some(sub_id) = timeline.subscription { 268 if let Err(e) = ndb.unsubscribe(sub_id) { 269 error!("unsubscribe error: {}", e); 270 } else { 271 info!( 272 "successfully unsubscribed from timeline {} with sub id {}", 273 timeline.id, 274 sub_id.id() 275 ); 276 } 277 } 278 }