notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

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 }