nav.rs (14340B)
1 use crate::{ 2 accounts::render_accounts_route, 3 actionbar::NoteAction, 4 app::{get_active_columns, get_active_columns_mut, get_decks_mut}, 5 column::ColumnsAction, 6 deck_state::DeckState, 7 decks::{Deck, DecksAction}, 8 notes_holder::NotesHolder, 9 profile::Profile, 10 relay_pool_manager::RelayPoolManager, 11 route::Route, 12 thread::Thread, 13 timeline::{ 14 route::{render_timeline_route, TimelineRoute}, 15 Timeline, 16 }, 17 ui::{ 18 self, 19 add_column::render_add_column_routes, 20 column::NavTitle, 21 configure_deck::ConfigureDeckView, 22 edit_deck::{EditDeckResponse, EditDeckView}, 23 note::{PostAction, PostType}, 24 support::SupportView, 25 RelayView, View, 26 }, 27 Damus, 28 }; 29 30 use notedeck::{AccountsAction, AppContext}; 31 32 use egui_nav::{Nav, NavAction, NavResponse, NavUiType}; 33 use nostrdb::{Ndb, Transaction}; 34 use tracing::{error, info}; 35 36 #[allow(clippy::enum_variant_names)] 37 pub enum RenderNavAction { 38 Back, 39 RemoveColumn, 40 PostAction(PostAction), 41 NoteAction(NoteAction), 42 SwitchingAction(SwitchingAction), 43 } 44 45 pub enum SwitchingAction { 46 Accounts(AccountsAction), 47 Columns(ColumnsAction), 48 Decks(crate::decks::DecksAction), 49 } 50 51 impl SwitchingAction { 52 /// process the action, and return whether switching occured 53 pub fn process(&self, app: &mut Damus, ctx: &mut AppContext<'_>) -> bool { 54 match &self { 55 SwitchingAction::Accounts(account_action) => match *account_action { 56 AccountsAction::Switch(index) => ctx.accounts.select_account(index), 57 AccountsAction::Remove(index) => ctx.accounts.remove_account(index), 58 }, 59 SwitchingAction::Columns(columns_action) => match *columns_action { 60 ColumnsAction::Remove(index) => { 61 get_active_columns_mut(ctx.accounts, &mut app.decks_cache).delete_column(index) 62 } 63 }, 64 SwitchingAction::Decks(decks_action) => match *decks_action { 65 DecksAction::Switch(index) => { 66 get_decks_mut(ctx.accounts, &mut app.decks_cache).set_active(index) 67 } 68 DecksAction::Removing(index) => { 69 get_decks_mut(ctx.accounts, &mut app.decks_cache).remove_deck(index) 70 } 71 }, 72 } 73 true 74 } 75 } 76 77 impl From<PostAction> for RenderNavAction { 78 fn from(post_action: PostAction) -> Self { 79 Self::PostAction(post_action) 80 } 81 } 82 83 impl From<NoteAction> for RenderNavAction { 84 fn from(note_action: NoteAction) -> RenderNavAction { 85 Self::NoteAction(note_action) 86 } 87 } 88 89 pub type NotedeckNavResponse = NavResponse<Option<RenderNavAction>>; 90 91 pub struct RenderNavResponse { 92 column: usize, 93 response: NotedeckNavResponse, 94 } 95 96 impl RenderNavResponse { 97 #[allow(private_interfaces)] 98 pub fn new(column: usize, response: NotedeckNavResponse) -> Self { 99 RenderNavResponse { column, response } 100 } 101 102 #[must_use = "Make sure to save columns if result is true"] 103 pub fn process_render_nav_response(&self, app: &mut Damus, ctx: &mut AppContext<'_>) -> bool { 104 let mut switching_occured: bool = false; 105 let col = self.column; 106 107 if let Some(action) = self 108 .response 109 .response 110 .as_ref() 111 .or(self.response.title_response.as_ref()) 112 { 113 // start returning when we're finished posting 114 match action { 115 RenderNavAction::Back => { 116 app.columns_mut(ctx.accounts) 117 .column_mut(col) 118 .router_mut() 119 .go_back(); 120 } 121 122 RenderNavAction::RemoveColumn => { 123 let tl = app 124 .columns(ctx.accounts) 125 .find_timeline_for_column_index(col); 126 if let Some(timeline) = tl { 127 unsubscribe_timeline(ctx.ndb, timeline); 128 } 129 130 app.columns_mut(ctx.accounts).delete_column(col); 131 switching_occured = true; 132 } 133 134 RenderNavAction::PostAction(post_action) => { 135 let txn = Transaction::new(ctx.ndb).expect("txn"); 136 let _ = post_action.execute(ctx.ndb, &txn, ctx.pool, &mut app.drafts); 137 get_active_columns_mut(ctx.accounts, &mut app.decks_cache) 138 .column_mut(col) 139 .router_mut() 140 .go_back(); 141 } 142 143 RenderNavAction::NoteAction(note_action) => { 144 let txn = Transaction::new(ctx.ndb).expect("txn"); 145 146 note_action.execute_and_process_result( 147 ctx.ndb, 148 get_active_columns_mut(ctx.accounts, &mut app.decks_cache), 149 col, 150 &mut app.threads, 151 &mut app.profiles, 152 ctx.note_cache, 153 ctx.pool, 154 &txn, 155 &ctx.accounts.mutefun(), 156 ); 157 } 158 159 RenderNavAction::SwitchingAction(switching_action) => { 160 switching_occured = switching_action.process(app, ctx); 161 } 162 } 163 } 164 165 if let Some(action) = self.response.action { 166 match action { 167 NavAction::Returned => { 168 let r = app 169 .columns_mut(ctx.accounts) 170 .column_mut(col) 171 .router_mut() 172 .pop(); 173 let txn = Transaction::new(ctx.ndb).expect("txn"); 174 if let Some(Route::Timeline(TimelineRoute::Thread(id))) = r { 175 let root_id = { 176 notedeck::note::root_note_id_from_selected_id( 177 ctx.ndb, 178 ctx.note_cache, 179 &txn, 180 id.bytes(), 181 ) 182 }; 183 Thread::unsubscribe_locally( 184 &txn, 185 ctx.ndb, 186 ctx.note_cache, 187 &mut app.threads, 188 ctx.pool, 189 root_id, 190 &ctx.accounts.mutefun(), 191 ); 192 } 193 194 if let Some(Route::Timeline(TimelineRoute::Profile(pubkey))) = r { 195 Profile::unsubscribe_locally( 196 &txn, 197 ctx.ndb, 198 ctx.note_cache, 199 &mut app.profiles, 200 ctx.pool, 201 pubkey.bytes(), 202 &ctx.accounts.mutefun(), 203 ); 204 } 205 206 switching_occured = true; 207 } 208 209 NavAction::Navigated => { 210 let cur_router = app.columns_mut(ctx.accounts).column_mut(col).router_mut(); 211 cur_router.navigating = false; 212 if cur_router.is_replacing() { 213 cur_router.remove_previous_routes(); 214 } 215 switching_occured = true; 216 } 217 218 NavAction::Dragging => {} 219 NavAction::Returning => {} 220 NavAction::Resetting => {} 221 NavAction::Navigating => {} 222 } 223 } 224 225 switching_occured 226 } 227 } 228 229 fn render_nav_body( 230 ui: &mut egui::Ui, 231 app: &mut Damus, 232 ctx: &mut AppContext<'_>, 233 top: &Route, 234 col: usize, 235 ) -> Option<RenderNavAction> { 236 match top { 237 Route::Timeline(tlr) => render_timeline_route( 238 ctx.ndb, 239 get_active_columns_mut(ctx.accounts, &mut app.decks_cache), 240 &mut app.drafts, 241 ctx.img_cache, 242 ctx.unknown_ids, 243 ctx.note_cache, 244 &mut app.threads, 245 &mut app.profiles, 246 ctx.accounts, 247 *tlr, 248 col, 249 app.textmode, 250 ui, 251 ), 252 Route::Accounts(amr) => { 253 let mut action = render_accounts_route( 254 ui, 255 ctx.ndb, 256 col, 257 ctx.img_cache, 258 ctx.accounts, 259 &mut app.decks_cache, 260 &mut app.view_state.login, 261 *amr, 262 ); 263 let txn = Transaction::new(ctx.ndb).expect("txn"); 264 action.process_action(ctx.unknown_ids, ctx.ndb, &txn); 265 action 266 .accounts_action 267 .map(|f| RenderNavAction::SwitchingAction(SwitchingAction::Accounts(f))) 268 } 269 Route::Relays => { 270 let manager = RelayPoolManager::new(ctx.pool); 271 RelayView::new(manager).ui(ui); 272 None 273 } 274 Route::ComposeNote => { 275 let kp = ctx.accounts.get_selected_account()?.to_full()?; 276 let draft = app.drafts.compose_mut(); 277 278 let txn = Transaction::new(ctx.ndb).expect("txn"); 279 let post_response = ui::PostView::new( 280 ctx.ndb, 281 draft, 282 PostType::New, 283 ctx.img_cache, 284 ctx.note_cache, 285 kp, 286 ) 287 .ui(&txn, ui); 288 289 post_response.action.map(Into::into) 290 } 291 Route::AddColumn(route) => { 292 render_add_column_routes(ui, app, ctx, col, route); 293 294 None 295 } 296 Route::Support => { 297 SupportView::new(&mut app.support).show(ui); 298 None 299 } 300 Route::NewDeck => { 301 let id = ui.id().with("new-deck"); 302 let new_deck_state = app.view_state.id_to_deck_state.entry(id).or_default(); 303 let mut resp = None; 304 if let Some(config_resp) = ConfigureDeckView::new(new_deck_state).ui(ui) { 305 if let Some(cur_acc) = ctx.accounts.get_selected_account() { 306 app.decks_cache.add_deck( 307 cur_acc.pubkey, 308 Deck::new(config_resp.icon, config_resp.name), 309 ); 310 311 // set new deck as active 312 let cur_index = get_decks_mut(ctx.accounts, &mut app.decks_cache) 313 .decks() 314 .len() 315 - 1; 316 resp = Some(RenderNavAction::SwitchingAction(SwitchingAction::Decks( 317 DecksAction::Switch(cur_index), 318 ))); 319 } 320 321 new_deck_state.clear(); 322 get_active_columns_mut(ctx.accounts, &mut app.decks_cache) 323 .get_first_router() 324 .go_back(); 325 } 326 resp 327 } 328 Route::EditDeck(index) => { 329 let mut action = None; 330 let cur_deck = get_decks_mut(ctx.accounts, &mut app.decks_cache) 331 .decks_mut() 332 .get_mut(*index) 333 .expect("index wasn't valid"); 334 let id = ui.id().with(( 335 "edit-deck", 336 ctx.accounts.get_selected_account().map(|k| k.pubkey), 337 index, 338 )); 339 let deck_state = app 340 .view_state 341 .id_to_deck_state 342 .entry(id) 343 .or_insert_with(|| DeckState::from_deck(cur_deck)); 344 if let Some(resp) = EditDeckView::new(deck_state).ui(ui) { 345 match resp { 346 EditDeckResponse::Edit(configure_deck_response) => { 347 cur_deck.edit(configure_deck_response); 348 } 349 EditDeckResponse::Delete => { 350 action = Some(RenderNavAction::SwitchingAction(SwitchingAction::Decks( 351 DecksAction::Removing(*index), 352 ))); 353 } 354 } 355 get_active_columns_mut(ctx.accounts, &mut app.decks_cache) 356 .get_first_router() 357 .go_back(); 358 } 359 360 action 361 } 362 } 363 } 364 365 #[must_use = "RenderNavResponse must be handled by calling .process_render_nav_response(..)"] 366 pub fn render_nav( 367 col: usize, 368 app: &mut Damus, 369 ctx: &mut AppContext<'_>, 370 ui: &mut egui::Ui, 371 ) -> RenderNavResponse { 372 let col_id = get_active_columns(ctx.accounts, &app.decks_cache).get_column_id_at_index(col); 373 // TODO(jb55): clean up this router_mut mess by using Router<R> in egui-nav directly 374 375 let nav_response = Nav::new( 376 &app.columns(ctx.accounts) 377 .column(col) 378 .router() 379 .routes() 380 .clone(), 381 ) 382 .navigating( 383 app.columns_mut(ctx.accounts) 384 .column_mut(col) 385 .router_mut() 386 .navigating, 387 ) 388 .returning( 389 app.columns_mut(ctx.accounts) 390 .column_mut(col) 391 .router_mut() 392 .returning, 393 ) 394 .id_source(egui::Id::new(col_id)) 395 .show_mut(ui, |ui, render_type, nav| match render_type { 396 NavUiType::Title => NavTitle::new( 397 ctx.ndb, 398 ctx.img_cache, 399 get_active_columns_mut(ctx.accounts, &mut app.decks_cache), 400 ctx.accounts.get_selected_account().map(|a| &a.pubkey), 401 nav.routes(), 402 ) 403 .show(ui), 404 NavUiType::Body => render_nav_body(ui, app, ctx, nav.routes().last().expect("top"), col), 405 }); 406 407 RenderNavResponse::new(col, nav_response) 408 } 409 410 fn unsubscribe_timeline(ndb: &mut Ndb, timeline: &Timeline) { 411 if let Some(sub_id) = timeline.subscription { 412 if let Err(e) = ndb.unsubscribe(sub_id) { 413 error!("unsubscribe error: {}", e); 414 } else { 415 info!( 416 "successfully unsubscribed from timeline {} with sub id {}", 417 timeline.id, 418 sub_id.id() 419 ); 420 } 421 } 422 }