route.rs (11929B)
1 use enostr::{NoteId, Pubkey}; 2 use std::fmt::{self}; 3 4 use crate::{ 5 accounts::AccountsRoute, 6 timeline::{ 7 kind::{AlgoTimeline, ColumnTitle, ListKind}, 8 ThreadSelection, TimelineKind, 9 }, 10 ui::add_column::{AddAlgoRoute, AddColumnRoute}, 11 }; 12 13 use tokenator::{ParseError, TokenParser, TokenSerializable, TokenWriter}; 14 15 /// App routing. These describe different places you can go inside Notedeck. 16 #[derive(Clone, Eq, PartialEq, Debug)] 17 pub enum Route { 18 Timeline(TimelineKind), 19 Accounts(AccountsRoute), 20 Reply(NoteId), 21 Quote(NoteId), 22 Relays, 23 ComposeNote, 24 AddColumn(AddColumnRoute), 25 EditProfile(Pubkey), 26 Support, 27 NewDeck, 28 /// Search screen 29 Search, 30 EditDeck(usize), 31 } 32 33 impl Route { 34 pub fn timeline(timeline_kind: TimelineKind) -> Self { 35 Route::Timeline(timeline_kind) 36 } 37 38 pub fn timeline_id(&self) -> Option<&TimelineKind> { 39 if let Route::Timeline(tid) = self { 40 Some(tid) 41 } else { 42 None 43 } 44 } 45 46 pub fn relays() -> Self { 47 Route::Relays 48 } 49 50 pub fn thread(thread_selection: ThreadSelection) -> Self { 51 Route::Timeline(TimelineKind::Thread(thread_selection)) 52 } 53 54 pub fn profile(pubkey: Pubkey) -> Self { 55 Route::Timeline(TimelineKind::profile(pubkey)) 56 } 57 58 pub fn reply(replying_to: NoteId) -> Self { 59 Route::Reply(replying_to) 60 } 61 62 pub fn quote(quoting: NoteId) -> Self { 63 Route::Quote(quoting) 64 } 65 66 pub fn accounts() -> Self { 67 Route::Accounts(AccountsRoute::Accounts) 68 } 69 70 pub fn add_account() -> Self { 71 Route::Accounts(AccountsRoute::AddAccount) 72 } 73 74 pub fn serialize_tokens(&self, writer: &mut TokenWriter) { 75 match self { 76 Route::Timeline(timeline_kind) => timeline_kind.serialize_tokens(writer), 77 Route::Accounts(routes) => routes.serialize_tokens(writer), 78 Route::AddColumn(routes) => routes.serialize_tokens(writer), 79 Route::Search => writer.write_token("search"), 80 Route::Reply(note_id) => { 81 writer.write_token("reply"); 82 writer.write_token(¬e_id.hex()); 83 } 84 Route::Quote(note_id) => { 85 writer.write_token("quote"); 86 writer.write_token(¬e_id.hex()); 87 } 88 Route::EditDeck(ind) => { 89 writer.write_token("deck"); 90 writer.write_token("edit"); 91 writer.write_token(&ind.to_string()); 92 } 93 Route::EditProfile(pubkey) => { 94 writer.write_token("profile"); 95 writer.write_token("edit"); 96 writer.write_token(&pubkey.hex()); 97 } 98 Route::Relays => { 99 writer.write_token("relay"); 100 } 101 Route::ComposeNote => { 102 writer.write_token("compose"); 103 } 104 Route::Support => { 105 writer.write_token("support"); 106 } 107 Route::NewDeck => { 108 writer.write_token("deck"); 109 writer.write_token("new"); 110 } 111 } 112 } 113 114 pub fn parse<'a>( 115 parser: &mut TokenParser<'a>, 116 deck_author: &Pubkey, 117 ) -> Result<Self, ParseError<'a>> { 118 let tlkind = 119 parser.try_parse(|p| Ok(Route::Timeline(TimelineKind::parse(p, deck_author)?))); 120 121 if tlkind.is_ok() { 122 return tlkind; 123 } 124 125 TokenParser::alt( 126 parser, 127 &[ 128 |p| Ok(Route::Accounts(AccountsRoute::parse_from_tokens(p)?)), 129 |p| Ok(Route::AddColumn(AddColumnRoute::parse_from_tokens(p)?)), 130 |p| { 131 p.parse_all(|p| { 132 p.parse_token("deck")?; 133 p.parse_token("edit")?; 134 let ind_str = p.pull_token()?; 135 let parsed_index = ind_str 136 .parse::<usize>() 137 .map_err(|_| ParseError::DecodeFailed)?; 138 Ok(Route::EditDeck(parsed_index)) 139 }) 140 }, 141 |p| { 142 p.parse_all(|p| { 143 p.parse_token("profile")?; 144 p.parse_token("edit")?; 145 let pubkey = Pubkey::from_hex(p.pull_token()?) 146 .map_err(|_| ParseError::HexDecodeFailed)?; 147 Ok(Route::EditProfile(pubkey)) 148 }) 149 }, 150 |p| { 151 p.parse_all(|p| { 152 p.parse_token("relay")?; 153 Ok(Route::Relays) 154 }) 155 }, 156 |p| { 157 p.parse_all(|p| { 158 p.parse_token("quote")?; 159 Ok(Route::Quote(NoteId::new(tokenator::parse_hex_id(p)?))) 160 }) 161 }, 162 |p| { 163 p.parse_all(|p| { 164 p.parse_token("reply")?; 165 Ok(Route::Reply(NoteId::new(tokenator::parse_hex_id(p)?))) 166 }) 167 }, 168 |p| { 169 p.parse_all(|p| { 170 p.parse_token("compose")?; 171 Ok(Route::ComposeNote) 172 }) 173 }, 174 |p| { 175 p.parse_all(|p| { 176 p.parse_token("support")?; 177 Ok(Route::Support) 178 }) 179 }, 180 |p| { 181 p.parse_all(|p| { 182 p.parse_token("deck")?; 183 p.parse_token("new")?; 184 Ok(Route::NewDeck) 185 }) 186 }, 187 |p| { 188 p.parse_all(|p| { 189 p.parse_token("search")?; 190 Ok(Route::Search) 191 }) 192 }, 193 ], 194 ) 195 } 196 197 pub fn title(&self) -> ColumnTitle<'_> { 198 match self { 199 Route::Timeline(kind) => kind.to_title(), 200 201 Route::Reply(_id) => ColumnTitle::simple("Reply"), 202 Route::Quote(_id) => ColumnTitle::simple("Quote"), 203 204 Route::Relays => ColumnTitle::simple("Relays"), 205 206 Route::Accounts(amr) => match amr { 207 AccountsRoute::Accounts => ColumnTitle::simple("Accounts"), 208 AccountsRoute::AddAccount => ColumnTitle::simple("Add Account"), 209 }, 210 Route::ComposeNote => ColumnTitle::simple("Compose Note"), 211 Route::AddColumn(c) => match c { 212 AddColumnRoute::Base => ColumnTitle::simple("Add Column"), 213 AddColumnRoute::Algo(r) => match r { 214 AddAlgoRoute::Base => ColumnTitle::simple("Add Algo Column"), 215 AddAlgoRoute::LastPerPubkey => ColumnTitle::simple("Add Last Notes Column"), 216 }, 217 AddColumnRoute::UndecidedNotification => { 218 ColumnTitle::simple("Add Notifications Column") 219 } 220 AddColumnRoute::ExternalNotification => { 221 ColumnTitle::simple("Add External Notifications Column") 222 } 223 AddColumnRoute::Hashtag => ColumnTitle::simple("Add Hashtag Column"), 224 AddColumnRoute::UndecidedIndividual => { 225 ColumnTitle::simple("Subscribe to someone's notes") 226 } 227 AddColumnRoute::ExternalIndividual => { 228 ColumnTitle::simple("Subscribe to someone else's notes") 229 } 230 }, 231 Route::Support => ColumnTitle::simple("Damus Support"), 232 Route::NewDeck => ColumnTitle::simple("Add Deck"), 233 Route::EditDeck(_) => ColumnTitle::simple("Edit Deck"), 234 Route::EditProfile(_) => ColumnTitle::simple("Edit Profile"), 235 Route::Search => ColumnTitle::simple("Search"), 236 } 237 } 238 } 239 240 // TODO: add this to egui-nav so we don't have to deal with returning 241 // and navigating headaches 242 #[derive(Clone, Debug)] 243 pub struct Router<R: Clone> { 244 routes: Vec<R>, 245 pub returning: bool, 246 pub navigating: bool, 247 replacing: bool, 248 } 249 250 impl<R: Clone> Router<R> { 251 pub fn new(routes: Vec<R>) -> Self { 252 if routes.is_empty() { 253 panic!("routes can't be empty") 254 } 255 let returning = false; 256 let navigating = false; 257 let replacing = false; 258 Router { 259 routes, 260 returning, 261 navigating, 262 replacing, 263 } 264 } 265 266 pub fn route_to(&mut self, route: R) { 267 self.navigating = true; 268 self.routes.push(route); 269 } 270 271 // Route to R. Then when it is successfully placed, should call `remove_previous_routes` to remove all previous routes 272 pub fn route_to_replaced(&mut self, route: R) { 273 self.navigating = true; 274 self.replacing = true; 275 self.routes.push(route); 276 } 277 278 /// Go back, start the returning process 279 pub fn go_back(&mut self) -> Option<R> { 280 if self.returning || self.routes.len() == 1 { 281 return None; 282 } 283 self.returning = true; 284 self.prev().cloned() 285 } 286 287 /// Pop a route, should only be called on a NavRespose::Returned reseponse 288 pub fn pop(&mut self) -> Option<R> { 289 if self.routes.len() == 1 { 290 return None; 291 } 292 self.returning = false; 293 self.routes.pop() 294 } 295 296 pub fn remove_previous_routes(&mut self) { 297 let num_routes = self.routes.len(); 298 if num_routes <= 1 { 299 return; 300 } 301 302 self.returning = false; 303 self.replacing = false; 304 self.routes.drain(..num_routes - 1); 305 } 306 307 pub fn is_replacing(&self) -> bool { 308 self.replacing 309 } 310 311 pub fn top(&self) -> &R { 312 self.routes.last().expect("routes can't be empty") 313 } 314 315 pub fn prev(&self) -> Option<&R> { 316 self.routes.get(self.routes.len() - 2) 317 } 318 319 pub fn routes(&self) -> &Vec<R> { 320 &self.routes 321 } 322 } 323 324 impl fmt::Display for Route { 325 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 326 match self { 327 Route::Timeline(kind) => match kind { 328 TimelineKind::List(ListKind::Contact(_pk)) => write!(f, "Contacts"), 329 TimelineKind::Algo(AlgoTimeline::LastPerPubkey(ListKind::Contact(_))) => { 330 write!(f, "Last Per Pubkey (Contact)") 331 } 332 TimelineKind::Notifications(_) => write!(f, "Notifications"), 333 TimelineKind::Universe => write!(f, "Universe"), 334 TimelineKind::Generic(_) => write!(f, "Custom"), 335 TimelineKind::Search(_) => write!(f, "Search"), 336 TimelineKind::Hashtag(ht) => write!(f, "Hashtag ({})", ht), 337 TimelineKind::Thread(_id) => write!(f, "Thread"), 338 TimelineKind::Profile(_id) => write!(f, "Profile"), 339 }, 340 341 Route::Reply(_id) => write!(f, "Reply"), 342 Route::Quote(_id) => write!(f, "Quote"), 343 344 Route::Relays => write!(f, "Relays"), 345 346 Route::Accounts(amr) => match amr { 347 AccountsRoute::Accounts => write!(f, "Accounts"), 348 AccountsRoute::AddAccount => write!(f, "Add Account"), 349 }, 350 Route::ComposeNote => write!(f, "Compose Note"), 351 352 Route::AddColumn(_) => write!(f, "Add Column"), 353 Route::Support => write!(f, "Support"), 354 Route::NewDeck => write!(f, "Add Deck"), 355 Route::EditDeck(_) => write!(f, "Edit Deck"), 356 Route::EditProfile(_) => write!(f, "Edit Profile"), 357 Route::Search => write!(f, "Search"), 358 } 359 } 360 }