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