notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

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(&note_id.hex());
     83             }
     84             Route::Quote(note_id) => {
     85                 writer.write_token("quote");
     86                 writer.write_token(&note_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 }