notedeck

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

column.rs (8050B)


      1 use crate::route::{Route, Router};
      2 use crate::timeline::{SerializableTimeline, Timeline, TimelineId, TimelineRoute};
      3 use indexmap::IndexMap;
      4 use nostrdb::Ndb;
      5 use serde::{Deserialize, Deserializer, Serialize};
      6 use std::iter::Iterator;
      7 use std::sync::atomic::{AtomicU32, Ordering};
      8 use tracing::{error, warn};
      9 
     10 #[derive(Clone)]
     11 pub struct Column {
     12     router: Router<Route>,
     13 }
     14 
     15 impl Column {
     16     pub fn new(routes: Vec<Route>) -> Self {
     17         let router = Router::new(routes);
     18         Column { router }
     19     }
     20 
     21     pub fn router(&self) -> &Router<Route> {
     22         &self.router
     23     }
     24 
     25     pub fn router_mut(&mut self) -> &mut Router<Route> {
     26         &mut self.router
     27     }
     28 }
     29 
     30 impl serde::Serialize for Column {
     31     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     32     where
     33         S: serde::Serializer,
     34     {
     35         self.router.routes().serialize(serializer)
     36     }
     37 }
     38 
     39 impl<'de> Deserialize<'de> for Column {
     40     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     41     where
     42         D: Deserializer<'de>,
     43     {
     44         let routes = Vec::<Route>::deserialize(deserializer)?;
     45 
     46         Ok(Column {
     47             router: Router::new(routes),
     48         })
     49     }
     50 }
     51 
     52 #[derive(Default)]
     53 pub struct Columns {
     54     /// Columns are simply routers into settings, timelines, etc
     55     columns: IndexMap<u32, Column>,
     56 
     57     /// Timeline state is not tied to routing logic separately, so that
     58     /// different columns can navigate to and from settings to timelines,
     59     /// etc.
     60     pub timelines: IndexMap<u32, Timeline>,
     61 
     62     /// The selected column for key navigation
     63     selected: i32,
     64 }
     65 static UIDS: AtomicU32 = AtomicU32::new(0);
     66 
     67 impl Columns {
     68     pub fn new() -> Self {
     69         Columns::default()
     70     }
     71 
     72     pub fn add_new_timeline_column(&mut self, timeline: Timeline) {
     73         let id = Self::get_new_id();
     74         let routes = vec![Route::timeline(timeline.id)];
     75         self.timelines.insert(id, timeline);
     76         self.columns.insert(id, Column::new(routes));
     77     }
     78 
     79     pub fn add_timeline_to_column(&mut self, col: usize, timeline: Timeline) {
     80         let col_id = self.get_column_id_at_index(col);
     81         self.column_mut(col)
     82             .router_mut()
     83             .route_to_replaced(Route::timeline(timeline.id));
     84         self.timelines.insert(col_id, timeline);
     85     }
     86 
     87     pub fn new_column_picker(&mut self) {
     88         self.add_column(Column::new(vec![Route::AddColumn(
     89             crate::ui::add_column::AddColumnRoute::Base,
     90         )]));
     91     }
     92 
     93     fn get_new_id() -> u32 {
     94         UIDS.fetch_add(1, Ordering::Relaxed)
     95     }
     96 
     97     pub fn add_column_at(&mut self, column: Column, index: u32) {
     98         self.columns.insert(index, column);
     99     }
    100 
    101     pub fn add_column(&mut self, column: Column) {
    102         self.columns.insert(Self::get_new_id(), column);
    103     }
    104 
    105     pub fn columns_mut(&mut self) -> Vec<&mut Column> {
    106         self.columns.values_mut().collect()
    107     }
    108 
    109     pub fn num_columns(&self) -> usize {
    110         self.columns.len()
    111     }
    112 
    113     // Get the first router in the columns if there are columns present.
    114     // Otherwise, create a new column picker and return the router
    115     pub fn get_first_router(&mut self) -> &mut Router<Route> {
    116         if self.columns.is_empty() {
    117             self.new_column_picker();
    118         }
    119         self.columns
    120             .get_index_mut(0)
    121             .expect("There should be at least one column")
    122             .1
    123             .router_mut()
    124     }
    125 
    126     pub fn timeline_mut(&mut self, timeline_ind: usize) -> &mut Timeline {
    127         self.timelines
    128             .get_index_mut(timeline_ind)
    129             .expect("expected index to be in bounds")
    130             .1
    131     }
    132 
    133     pub fn column(&self, ind: usize) -> &Column {
    134         self.columns
    135             .get_index(ind)
    136             .expect("Expected index to be in bounds")
    137             .1
    138     }
    139 
    140     pub fn columns(&self) -> Vec<&Column> {
    141         self.columns.values().collect()
    142     }
    143 
    144     pub fn get_column_id_at_index(&self, ind: usize) -> u32 {
    145         *self
    146             .columns
    147             .get_index(ind)
    148             .expect("expected index to be within bounds")
    149             .0
    150     }
    151 
    152     pub fn selected(&mut self) -> &mut Column {
    153         self.columns
    154             .get_index_mut(self.selected as usize)
    155             .expect("Expected selected index to be in bounds")
    156             .1
    157     }
    158 
    159     pub fn timelines_mut(&mut self) -> Vec<&mut Timeline> {
    160         self.timelines.values_mut().collect()
    161     }
    162 
    163     pub fn timelines(&self) -> Vec<&Timeline> {
    164         self.timelines.values().collect()
    165     }
    166 
    167     pub fn find_timeline_mut(&mut self, id: TimelineId) -> Option<&mut Timeline> {
    168         self.timelines_mut().into_iter().find(|tl| tl.id == id)
    169     }
    170 
    171     pub fn find_timeline(&self, id: TimelineId) -> Option<&Timeline> {
    172         self.timelines().into_iter().find(|tl| tl.id == id)
    173     }
    174 
    175     pub fn column_mut(&mut self, ind: usize) -> &mut Column {
    176         self.columns
    177             .get_index_mut(ind)
    178             .expect("Expected index to be in bounds")
    179             .1
    180     }
    181 
    182     pub fn find_timeline_for_column_index(&self, ind: usize) -> Option<&Timeline> {
    183         let col_id = self.get_column_id_at_index(ind);
    184         self.timelines.get(&col_id)
    185     }
    186 
    187     pub fn select_down(&mut self) {
    188         warn!("todo: implement select_down");
    189     }
    190 
    191     pub fn select_up(&mut self) {
    192         warn!("todo: implement select_up");
    193     }
    194 
    195     pub fn select_left(&mut self) {
    196         if self.selected - 1 < 0 {
    197             return;
    198         }
    199         self.selected -= 1;
    200     }
    201 
    202     pub fn select_right(&mut self) {
    203         if self.selected + 1 >= self.columns.len() as i32 {
    204             return;
    205         }
    206         self.selected += 1;
    207     }
    208 
    209     pub fn delete_column(&mut self, index: usize) {
    210         if let Some((key, _)) = self.columns.get_index_mut(index) {
    211             self.timelines.shift_remove(key);
    212         }
    213 
    214         self.columns.shift_remove_index(index);
    215 
    216         if self.columns.is_empty() {
    217             self.new_column_picker();
    218         }
    219     }
    220 
    221     pub fn as_serializable_columns(&self) -> SerializableColumns {
    222         SerializableColumns {
    223             columns: self.columns.values().cloned().collect(),
    224             timelines: self
    225                 .timelines
    226                 .values()
    227                 .map(|t| t.as_serializable_timeline())
    228                 .collect(),
    229         }
    230     }
    231 }
    232 
    233 #[derive(Serialize, Deserialize)]
    234 pub struct SerializableColumns {
    235     pub columns: Vec<Column>,
    236     pub timelines: Vec<SerializableTimeline>,
    237 }
    238 
    239 impl SerializableColumns {
    240     pub fn into_columns(self, ndb: &Ndb, deck_pubkey: Option<&[u8; 32]>) -> Columns {
    241         let mut columns = Columns::default();
    242 
    243         for column in self.columns {
    244             let id = Columns::get_new_id();
    245             let mut routes = Vec::new();
    246             for route in column.router.routes() {
    247                 match route {
    248                     Route::Timeline(TimelineRoute::Timeline(timeline_id)) => {
    249                         if let Some(serializable_tl) =
    250                             self.timelines.iter().find(|tl| tl.id == *timeline_id)
    251                         {
    252                             let tl = serializable_tl.clone().into_timeline(ndb, deck_pubkey);
    253                             if let Some(tl) = tl {
    254                                 routes.push(Route::Timeline(TimelineRoute::Timeline(tl.id)));
    255                                 columns.timelines.insert(id, tl);
    256                             } else {
    257                                 error!("Problem deserializing timeline {:?}", serializable_tl);
    258                             }
    259                         }
    260                     }
    261                     Route::Timeline(TimelineRoute::Thread(_thread)) => {
    262                         // TODO: open thread before pushing route
    263                     }
    264                     Route::Profile(_profile) => {
    265                         // TODO: open profile before pushing route
    266                     }
    267                     _ => routes.push(*route),
    268                 }
    269             }
    270             columns.add_column_at(Column::new(routes), id);
    271         }
    272 
    273         columns
    274     }
    275 }