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