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