notedeck

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

app.rs (8395B)


      1 use crate::{app_size::AppSizeHandler, persist_zoom::ZoomHandler, setup::setup_cc, theme};
      2 
      3 use notedeck::{
      4     Accounts, AppContext, Args, DataPath, DataPathType, Directory, FileKeyStorage, ImageCache,
      5     KeyStorageType, NoteCache, ThemeHandler, UnknownIds,
      6 };
      7 
      8 use enostr::RelayPool;
      9 use nostrdb::{Config, Ndb, Transaction};
     10 use notedeck_columns::ui::relay_debug::RelayDebugView;
     11 use std::cell::RefCell;
     12 use std::path::Path;
     13 use std::rc::Rc;
     14 use tracing::{error, info};
     15 
     16 /// Our browser app state
     17 pub struct Notedeck {
     18     ndb: Ndb,
     19     img_cache: ImageCache,
     20     unknown_ids: UnknownIds,
     21     pool: RelayPool,
     22     note_cache: NoteCache,
     23     accounts: Accounts,
     24     path: DataPath,
     25     args: Args,
     26     theme: ThemeHandler,
     27     tabs: Tabs,
     28     app_rect_handler: AppSizeHandler,
     29     zoom_handler: ZoomHandler,
     30 }
     31 
     32 fn margin_top(narrow: bool) -> f32 {
     33     #[cfg(target_os = "android")]
     34     {
     35         // FIXME - query the system bar height and adjust more precisely
     36         let _ = narrow; // suppress compiler warning on android
     37         40.0
     38     }
     39     #[cfg(not(target_os = "android"))]
     40     {
     41         if narrow {
     42             50.0
     43         } else {
     44             0.0
     45         }
     46     }
     47 }
     48 
     49 /// Our chrome, which is basically nothing
     50 fn main_panel(style: &egui::Style, narrow: bool) -> egui::CentralPanel {
     51     let inner_margin = egui::Margin {
     52         top: margin_top(narrow),
     53         left: 0.0,
     54         right: 0.0,
     55         bottom: 0.0,
     56     };
     57     egui::CentralPanel::default().frame(egui::Frame {
     58         inner_margin,
     59         fill: style.visuals.panel_fill,
     60         ..Default::default()
     61     })
     62 }
     63 
     64 impl eframe::App for Notedeck {
     65     /// Called by the frame work to save state before shutdown.
     66     fn save(&mut self, _storage: &mut dyn eframe::Storage) {
     67         //eframe::set_value(storage, eframe::APP_KEY, self);
     68     }
     69 
     70     fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
     71         // TODO: render chrome
     72         #[cfg(feature = "profiling")]
     73         puffin::GlobalProfiler::lock().new_frame();
     74 
     75         main_panel(&ctx.style(), notedeck::ui::is_narrow(ctx)).show(ctx, |ui| {
     76             // render app
     77             if let Some(app) = &self.tabs.app {
     78                 let app = app.clone();
     79                 app.borrow_mut().update(&mut self.app_context(), ui);
     80             }
     81         });
     82 
     83         self.app_rect_handler.try_save_app_size(ctx);
     84         self.zoom_handler.try_save_zoom_factor(ctx);
     85 
     86         if self.args.relay_debug {
     87             if self.pool.debug.is_none() {
     88                 self.pool.use_debug();
     89             }
     90 
     91             if let Some(debug) = &mut self.pool.debug {
     92                 RelayDebugView::window(ctx, debug);
     93             }
     94         }
     95 
     96         #[cfg(feature = "profiling")]
     97         puffin_egui::profiler_window(ctx);
     98     }
     99 }
    100 
    101 #[cfg(feature = "profiling")]
    102 fn setup_profiling() {
    103     puffin::set_scopes_on(true); // tell puffin to collect data
    104 }
    105 
    106 impl Notedeck {
    107     pub fn new<P: AsRef<Path>>(ctx: &egui::Context, data_path: P, args: &[String]) -> Self {
    108         #[cfg(feature = "profiling")]
    109         setup_profiling();
    110 
    111         let parsed_args = Args::parse(args);
    112         let is_mobile = parsed_args
    113             .is_mobile
    114             .unwrap_or(notedeck::ui::is_compiled_as_mobile());
    115 
    116         // Some people have been running notedeck in debug, let's catch that!
    117         if !parsed_args.tests && cfg!(debug_assertions) && !parsed_args.debug {
    118             println!("--- WELCOME TO DAMUS NOTEDECK! ---");
    119             println!("It looks like are running notedeck in debug mode, unless you are a developer, this is not likely what you want.");
    120             println!("If you are a developer, run `cargo run -- --debug` to skip this message.");
    121             println!("For everyone else, try again with `cargo run --release`. Enjoy!");
    122             println!("---------------------------------");
    123             panic!();
    124         }
    125 
    126         setup_cc(ctx, is_mobile, parsed_args.light);
    127 
    128         let data_path = parsed_args
    129             .datapath
    130             .clone()
    131             .unwrap_or(data_path.as_ref().to_str().expect("db path ok").to_string());
    132         let path = DataPath::new(&data_path);
    133         let dbpath_str = parsed_args
    134             .dbpath
    135             .clone()
    136             .unwrap_or_else(|| path.path(DataPathType::Db).to_str().unwrap().to_string());
    137 
    138         let _ = std::fs::create_dir_all(&dbpath_str);
    139 
    140         let imgcache_dir = path.path(DataPathType::Cache).join(ImageCache::rel_dir());
    141         let _ = std::fs::create_dir_all(imgcache_dir.clone());
    142 
    143         let mapsize = if cfg!(target_os = "windows") {
    144             // 16 Gib on windows because it actually creates the file
    145             1024usize * 1024usize * 1024usize * 16usize
    146         } else {
    147             // 1 TiB for everything else since its just virtually mapped
    148             1024usize * 1024usize * 1024usize * 1024usize
    149         };
    150 
    151         let theme = ThemeHandler::new(&path);
    152         ctx.options_mut(|o| {
    153             let cur_theme = theme.load();
    154             info!("Loaded theme {:?} from disk", cur_theme);
    155             o.theme_preference = cur_theme;
    156         });
    157         ctx.set_visuals_of(
    158             egui::Theme::Dark,
    159             theme::dark_mode(notedeck::ui::is_compiled_as_mobile()),
    160         );
    161         ctx.set_visuals_of(egui::Theme::Light, theme::light_mode());
    162 
    163         let config = Config::new().set_ingester_threads(4).set_mapsize(mapsize);
    164 
    165         let keystore = if parsed_args.use_keystore {
    166             let keys_path = path.path(DataPathType::Keys);
    167             let selected_key_path = path.path(DataPathType::SelectedKey);
    168             KeyStorageType::FileSystem(FileKeyStorage::new(
    169                 Directory::new(keys_path),
    170                 Directory::new(selected_key_path),
    171             ))
    172         } else {
    173             KeyStorageType::None
    174         };
    175 
    176         let mut accounts = Accounts::new(keystore, parsed_args.relays.clone());
    177 
    178         let num_keys = parsed_args.keys.len();
    179 
    180         let mut unknown_ids = UnknownIds::default();
    181         let ndb = Ndb::new(&dbpath_str, &config).expect("ndb");
    182 
    183         {
    184             let txn = Transaction::new(&ndb).expect("txn");
    185             for key in &parsed_args.keys {
    186                 info!("adding account: {}", &key.pubkey);
    187                 accounts
    188                     .add_account(key.clone())
    189                     .process_action(&mut unknown_ids, &ndb, &txn);
    190             }
    191         }
    192 
    193         if num_keys != 0 {
    194             accounts.select_account(0);
    195         }
    196 
    197         // AccountManager will setup the pool on first update
    198         let mut pool = RelayPool::new();
    199         {
    200             let ctx = ctx.clone();
    201             if let Err(err) = pool.add_multicast_relay(move || ctx.request_repaint()) {
    202                 error!("error setting up multicast relay: {err}");
    203             }
    204         }
    205 
    206         let img_cache = ImageCache::new(imgcache_dir);
    207         let note_cache = NoteCache::default();
    208         let unknown_ids = UnknownIds::default();
    209         let tabs = Tabs::new(None);
    210         let app_rect_handler = AppSizeHandler::new(&path);
    211         let zoom_handler = ZoomHandler::new(&path);
    212 
    213         if let Some(zoom_factor) = zoom_handler.get_zoom_factor() {
    214             ctx.set_zoom_factor(zoom_factor);
    215         }
    216 
    217         // migrate
    218         if let Err(e) = img_cache.migrate_v0() {
    219             error!("error migrating image cache: {e}");
    220         }
    221 
    222         Self {
    223             ndb,
    224             img_cache,
    225             app_rect_handler,
    226             unknown_ids,
    227             pool,
    228             note_cache,
    229             accounts,
    230             path: path.clone(),
    231             args: parsed_args,
    232             theme,
    233             tabs,
    234             zoom_handler,
    235         }
    236     }
    237 
    238     pub fn app_context(&mut self) -> AppContext<'_> {
    239         AppContext {
    240             ndb: &mut self.ndb,
    241             img_cache: &mut self.img_cache,
    242             unknown_ids: &mut self.unknown_ids,
    243             pool: &mut self.pool,
    244             note_cache: &mut self.note_cache,
    245             accounts: &mut self.accounts,
    246             path: &self.path,
    247             args: &self.args,
    248             theme: &mut self.theme,
    249         }
    250     }
    251 
    252     pub fn add_app<T: notedeck::App + 'static>(&mut self, app: T) {
    253         self.tabs.app = Some(Rc::new(RefCell::new(app)));
    254     }
    255 }
    256 
    257 struct Tabs {
    258     app: Option<Rc<RefCell<dyn notedeck::App>>>,
    259 }
    260 
    261 impl Tabs {
    262     pub fn new(app: Option<Rc<RefCell<dyn notedeck::App>>>) -> Self {
    263         Self { app }
    264     }
    265 }