notedeck

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

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