route.rs (6682B)
1 use enostr::{NoteId, Pubkey}; 2 use nostrdb::Ndb; 3 use serde::{Deserialize, Serialize}; 4 use std::fmt::{self}; 5 6 use crate::{ 7 account_manager::AccountsRoute, 8 column::Columns, 9 timeline::{TimelineId, TimelineRoute}, 10 ui::{ 11 add_column::AddColumnRoute, 12 profile::preview::{get_note_users_displayname_string, get_profile_displayname_string}, 13 }, 14 }; 15 16 /// App routing. These describe different places you can go inside Notedeck. 17 #[derive(Clone, Copy, Eq, PartialEq, Debug, Serialize, Deserialize)] 18 pub enum Route { 19 Timeline(TimelineRoute), 20 Accounts(AccountsRoute), 21 Relays, 22 ComposeNote, 23 AddColumn(AddColumnRoute), 24 Profile(Pubkey), 25 Support, 26 } 27 28 #[derive(Clone)] 29 pub struct TitledRoute { 30 pub route: Route, 31 pub title: String, 32 } 33 34 impl fmt::Display for TitledRoute { 35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 36 write!(f, "{}", self.title) 37 } 38 } 39 40 impl Route { 41 pub fn timeline(timeline_id: TimelineId) -> Self { 42 Route::Timeline(TimelineRoute::Timeline(timeline_id)) 43 } 44 45 pub fn timeline_id(&self) -> Option<&TimelineId> { 46 if let Route::Timeline(TimelineRoute::Timeline(tid)) = self { 47 Some(tid) 48 } else { 49 None 50 } 51 } 52 53 pub fn relays() -> Self { 54 Route::Relays 55 } 56 57 pub fn thread(thread_root: NoteId) -> Self { 58 Route::Timeline(TimelineRoute::Thread(thread_root)) 59 } 60 61 pub fn reply(replying_to: NoteId) -> Self { 62 Route::Timeline(TimelineRoute::Reply(replying_to)) 63 } 64 65 pub fn quote(quoting: NoteId) -> Self { 66 Route::Timeline(TimelineRoute::Quote(quoting)) 67 } 68 69 pub fn accounts() -> Self { 70 Route::Accounts(AccountsRoute::Accounts) 71 } 72 73 pub fn add_account() -> Self { 74 Route::Accounts(AccountsRoute::AddAccount) 75 } 76 77 pub fn get_titled_route(&self, columns: &Columns, ndb: &Ndb) -> TitledRoute { 78 let title = match self { 79 Route::Timeline(tlr) => match tlr { 80 TimelineRoute::Timeline(id) => { 81 let timeline = columns 82 .find_timeline(*id) 83 .expect("expected to find timeline"); 84 timeline.kind.to_title(ndb) 85 } 86 TimelineRoute::Thread(id) => { 87 format!("{}'s Thread", get_note_users_displayname_string(ndb, id)) 88 } 89 TimelineRoute::Reply(id) => { 90 format!("{}'s Reply", get_note_users_displayname_string(ndb, id)) 91 } 92 TimelineRoute::Quote(id) => { 93 format!("{}'s Quote", get_note_users_displayname_string(ndb, id)) 94 } 95 }, 96 97 Route::Relays => "Relays".to_owned(), 98 99 Route::Accounts(amr) => match amr { 100 AccountsRoute::Accounts => "Accounts".to_owned(), 101 AccountsRoute::AddAccount => "Add Account".to_owned(), 102 }, 103 Route::ComposeNote => "Compose Note".to_owned(), 104 Route::AddColumn(c) => match c { 105 AddColumnRoute::Base => "Add Column".to_owned(), 106 AddColumnRoute::UndecidedNotification => "Add Notifications Column".to_owned(), 107 AddColumnRoute::ExternalNotification => { 108 "Add External Notifications Column".to_owned() 109 } 110 }, 111 Route::Profile(pubkey) => { 112 format!("{}'s Profile", get_profile_displayname_string(ndb, pubkey)) 113 } 114 Route::Support => "Damus Support".to_owned(), 115 }; 116 117 TitledRoute { 118 title, 119 route: *self, 120 } 121 } 122 } 123 124 // TODO: add this to egui-nav so we don't have to deal with returning 125 // and navigating headaches 126 #[derive(Clone)] 127 pub struct Router<R: Clone> { 128 routes: Vec<R>, 129 pub returning: bool, 130 pub navigating: bool, 131 replacing: bool, 132 } 133 134 impl<R: Clone> Router<R> { 135 pub fn new(routes: Vec<R>) -> Self { 136 if routes.is_empty() { 137 panic!("routes can't be empty") 138 } 139 let returning = false; 140 let navigating = false; 141 let replacing = false; 142 Router { 143 routes, 144 returning, 145 navigating, 146 replacing, 147 } 148 } 149 150 pub fn route_to(&mut self, route: R) { 151 self.navigating = true; 152 self.routes.push(route); 153 } 154 155 // Route to R. Then when it is successfully placed, should call `remove_previous_routes` to remove all previous routes 156 pub fn route_to_replaced(&mut self, route: R) { 157 self.navigating = true; 158 self.replacing = true; 159 self.routes.push(route); 160 } 161 162 /// Go back, start the returning process 163 pub fn go_back(&mut self) -> Option<R> { 164 if self.returning || self.routes.len() == 1 { 165 return None; 166 } 167 self.returning = true; 168 self.routes.get(self.routes.len() - 2).cloned() 169 } 170 171 /// Pop a route, should only be called on a NavRespose::Returned reseponse 172 pub fn pop(&mut self) -> Option<R> { 173 if self.routes.len() == 1 { 174 return None; 175 } 176 self.returning = false; 177 self.routes.pop() 178 } 179 180 pub fn remove_previous_routes(&mut self) { 181 let num_routes = self.routes.len(); 182 if num_routes <= 1 { 183 return; 184 } 185 186 self.returning = false; 187 self.replacing = false; 188 self.routes.drain(..num_routes - 1); 189 } 190 191 pub fn is_replacing(&self) -> bool { 192 self.replacing 193 } 194 195 pub fn top(&self) -> &R { 196 self.routes.last().expect("routes can't be empty") 197 } 198 199 pub fn routes(&self) -> &Vec<R> { 200 &self.routes 201 } 202 } 203 204 impl fmt::Display for Route { 205 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 206 match self { 207 Route::Timeline(tlr) => match tlr { 208 TimelineRoute::Timeline(name) => write!(f, "{}", name), 209 TimelineRoute::Thread(_id) => write!(f, "Thread"), 210 TimelineRoute::Reply(_id) => write!(f, "Reply"), 211 TimelineRoute::Quote(_id) => write!(f, "Quote"), 212 }, 213 214 Route::Relays => write!(f, "Relays"), 215 216 Route::Accounts(amr) => match amr { 217 AccountsRoute::Accounts => write!(f, "Accounts"), 218 AccountsRoute::AddAccount => write!(f, "Add Account"), 219 }, 220 Route::ComposeNote => write!(f, "Compose Note"), 221 222 Route::AddColumn(_) => write!(f, "Add Column"), 223 Route::Profile(_) => write!(f, "Profile"), 224 Route::Support => write!(f, "Support"), 225 } 226 } 227 }