notedeck

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

post.rs (8011B)


      1 use crate::draft::{Draft, DraftSource};
      2 use crate::imgcache::ImageCache;
      3 use crate::notecache::NoteCache;
      4 use crate::post::NewPost;
      5 use crate::ui;
      6 use crate::ui::{Preview, PreviewConfig, View};
      7 use egui::widgets::text_edit::TextEdit;
      8 use egui::{Frame, Layout};
      9 use enostr::{FilledKeypair, FullKeypair, RelayPool};
     10 use nostrdb::{Config, Ndb, Note, Transaction};
     11 use tracing::info;
     12 
     13 use super::contents::render_note_preview;
     14 
     15 pub struct PostView<'a> {
     16     ndb: &'a Ndb,
     17     draft: &'a mut Draft,
     18     draft_source: DraftSource<'a>,
     19     img_cache: &'a mut ImageCache,
     20     note_cache: &'a mut NoteCache,
     21     poster: FilledKeypair<'a>,
     22     id_source: Option<egui::Id>,
     23 }
     24 
     25 pub enum PostAction {
     26     Post(NewPost),
     27 }
     28 
     29 impl PostAction {
     30     pub fn execute<'b>(
     31         poster: FilledKeypair<'_>,
     32         action: &'b PostAction,
     33         pool: &mut RelayPool,
     34         draft: &mut Draft,
     35         get_note: impl Fn(&'b NewPost, &[u8; 32]) -> Note<'b>,
     36     ) {
     37         match action {
     38             PostAction::Post(np) => {
     39                 let note = get_note(np, &poster.secret_key.to_secret_bytes());
     40 
     41                 let raw_msg = format!("[\"EVENT\",{}]", note.json().unwrap());
     42                 info!("sending {}", raw_msg);
     43                 pool.send(&enostr::ClientMessage::raw(raw_msg));
     44                 draft.clear();
     45             }
     46         }
     47     }
     48 }
     49 
     50 pub struct PostResponse {
     51     pub action: Option<PostAction>,
     52     pub edit_response: egui::Response,
     53 }
     54 
     55 impl<'a> PostView<'a> {
     56     pub fn new(
     57         ndb: &'a Ndb,
     58         draft: &'a mut Draft,
     59         draft_source: DraftSource<'a>,
     60         img_cache: &'a mut ImageCache,
     61         note_cache: &'a mut NoteCache,
     62         poster: FilledKeypair<'a>,
     63     ) -> Self {
     64         let id_source: Option<egui::Id> = None;
     65         PostView {
     66             ndb,
     67             draft,
     68             img_cache,
     69             note_cache,
     70             poster,
     71             id_source,
     72             draft_source,
     73         }
     74     }
     75 
     76     pub fn id_source(mut self, id_source: impl std::hash::Hash) -> Self {
     77         self.id_source = Some(egui::Id::new(id_source));
     78         self
     79     }
     80 
     81     fn editbox(&mut self, txn: &nostrdb::Transaction, ui: &mut egui::Ui) -> egui::Response {
     82         ui.spacing_mut().item_spacing.x = 12.0;
     83 
     84         let pfp_size = 24.0;
     85 
     86         // TODO: refactor pfp control to do all of this for us
     87         let poster_pfp = self
     88             .ndb
     89             .get_profile_by_pubkey(txn, self.poster.pubkey.bytes())
     90             .as_ref()
     91             .ok()
     92             .and_then(|p| Some(ui::ProfilePic::from_profile(self.img_cache, p)?.size(pfp_size)));
     93 
     94         if let Some(pfp) = poster_pfp {
     95             ui.add(pfp);
     96         } else {
     97             ui.add(
     98                 ui::ProfilePic::new(self.img_cache, ui::ProfilePic::no_pfp_url()).size(pfp_size),
     99             );
    100         }
    101 
    102         let response = ui.add_sized(
    103             ui.available_size(),
    104             TextEdit::multiline(&mut self.draft.buffer)
    105                 .hint_text(egui::RichText::new("Write a banger note here...").weak())
    106                 .frame(false),
    107         );
    108 
    109         let focused = response.has_focus();
    110 
    111         ui.ctx().data_mut(|d| d.insert_temp(self.id(), focused));
    112 
    113         response
    114     }
    115 
    116     fn focused(&self, ui: &egui::Ui) -> bool {
    117         ui.ctx()
    118             .data(|d| d.get_temp::<bool>(self.id()).unwrap_or(false))
    119     }
    120 
    121     fn id(&self) -> egui::Id {
    122         self.id_source.unwrap_or_else(|| egui::Id::new("post"))
    123     }
    124 
    125     pub fn outer_margin() -> f32 {
    126         16.0
    127     }
    128 
    129     pub fn inner_margin() -> f32 {
    130         12.0
    131     }
    132 
    133     pub fn ui(&mut self, txn: &nostrdb::Transaction, ui: &mut egui::Ui) -> PostResponse {
    134         let focused = self.focused(ui);
    135         let stroke = if focused {
    136             ui.visuals().selection.stroke
    137         } else {
    138             //ui.visuals().selection.stroke
    139             ui.visuals().noninteractive().bg_stroke
    140         };
    141 
    142         let mut frame = egui::Frame::default()
    143             .inner_margin(egui::Margin::same(PostView::inner_margin()))
    144             .outer_margin(egui::Margin::same(PostView::outer_margin()))
    145             .fill(ui.visuals().extreme_bg_color)
    146             .stroke(stroke)
    147             .rounding(12.0);
    148 
    149         if focused {
    150             frame = frame.shadow(egui::epaint::Shadow {
    151                 offset: egui::vec2(0.0, 0.0),
    152                 blur: 8.0,
    153                 spread: 0.0,
    154                 color: stroke.color,
    155             });
    156         }
    157 
    158         frame
    159             .show(ui, |ui| {
    160                 ui.vertical(|ui| {
    161                     let edit_response = ui.horizontal(|ui| self.editbox(txn, ui)).inner;
    162 
    163                     let action = ui
    164                         .horizontal(|ui| {
    165                             if let DraftSource::Quote(id) = self.draft_source {
    166                                 let avail_size = ui.available_size_before_wrap();
    167                                 ui.with_layout(Layout::left_to_right(egui::Align::TOP), |ui| {
    168                                     Frame::none().show(ui, |ui| {
    169                                         ui.vertical(|ui| {
    170                                             ui.set_max_width(avail_size.x * 0.8);
    171                                             render_note_preview(
    172                                                 ui,
    173                                                 self.ndb,
    174                                                 self.note_cache,
    175                                                 self.img_cache,
    176                                                 txn,
    177                                                 id,
    178                                                 "",
    179                                             );
    180                                         });
    181                                     });
    182                                 });
    183                             }
    184 
    185                             ui.with_layout(egui::Layout::right_to_left(egui::Align::BOTTOM), |ui| {
    186                                 if ui
    187                                     .add_sized([91.0, 32.0], egui::Button::new("Post now"))
    188                                     .clicked()
    189                                 {
    190                                     Some(PostAction::Post(NewPost::new(
    191                                         self.draft.buffer.clone(),
    192                                         self.poster.to_full(),
    193                                     )))
    194                                 } else {
    195                                     None
    196                                 }
    197                             })
    198                             .inner
    199                         })
    200                         .inner;
    201 
    202                     PostResponse {
    203                         action,
    204                         edit_response,
    205                     }
    206                 })
    207                 .inner
    208             })
    209             .inner
    210     }
    211 }
    212 
    213 mod preview {
    214     use super::*;
    215 
    216     pub struct PostPreview {
    217         ndb: Ndb,
    218         img_cache: ImageCache,
    219         note_cache: NoteCache,
    220         draft: Draft,
    221         poster: FullKeypair,
    222     }
    223 
    224     impl PostPreview {
    225         fn new() -> Self {
    226             let ndb = Ndb::new(".", &Config::new()).expect("ndb");
    227 
    228             PostPreview {
    229                 ndb,
    230                 img_cache: ImageCache::new(".".into()),
    231                 note_cache: NoteCache::default(),
    232                 draft: Draft::new(),
    233                 poster: FullKeypair::generate(),
    234             }
    235         }
    236     }
    237 
    238     impl View for PostPreview {
    239         fn ui(&mut self, ui: &mut egui::Ui) {
    240             let txn = Transaction::new(&self.ndb).expect("txn");
    241             PostView::new(
    242                 &self.ndb,
    243                 &mut self.draft,
    244                 DraftSource::Compose,
    245                 &mut self.img_cache,
    246                 &mut self.note_cache,
    247                 self.poster.to_filled(),
    248             )
    249             .ui(&txn, ui);
    250         }
    251     }
    252 
    253     impl<'a> Preview for PostView<'a> {
    254         type Prev = PostPreview;
    255 
    256         fn preview(_cfg: PreviewConfig) -> Self::Prev {
    257             PostPreview::new()
    258         }
    259     }
    260 }