refactor: unify note, post and nav actions

There was a bunch of redundant responses. Let's unify them under
the RenderNavAction enum. We unify all action processing under this
type.

This also centralizes all of our side effects into a single function
instead of scattering them everywhere
This commit is contained in:
William Casarin
2024-11-19 11:07:42 -08:00
parent d97c957e67
commit 7f234935cc
15 changed files with 372 additions and 404 deletions

View File

@@ -1,4 +1,4 @@
use crate::actionbar::NoteActionResponse;
use crate::actionbar::NoteAction;
use crate::images::ImageType;
use crate::imgcache::ImageCache;
use crate::notecache::NoteCache;
@@ -17,7 +17,7 @@ pub struct NoteContents<'a> {
note: &'a Note<'a>,
note_key: NoteKey,
options: NoteOptions,
action: NoteActionResponse,
action: Option<NoteAction>,
}
impl<'a> NoteContents<'a> {
@@ -38,11 +38,11 @@ impl<'a> NoteContents<'a> {
note,
note_key,
options,
action: NoteActionResponse::default(),
action: None,
}
}
pub fn action(&self) -> &NoteActionResponse {
pub fn action(&self) -> &Option<NoteAction> {
&self.action
}
}
@@ -212,7 +212,7 @@ fn render_note_contents(
let note_action = if let Some((id, block_str)) = inline_note {
render_note_preview(ui, ndb, note_cache, img_cache, txn, id, block_str).action
} else {
NoteActionResponse::default()
None
};
if !images.is_empty() && !options.has_textmode() {

View File

@@ -8,12 +8,12 @@ pub mod reply;
pub use contents::NoteContents;
pub use context::{NoteContextButton, NoteContextSelection};
pub use options::NoteOptions;
pub use post::{PostAction, PostResponse, PostView};
pub use post::{PostAction, PostResponse, PostType, PostView};
pub use quote_repost::QuoteRepostView;
pub use reply::PostReplyView;
use crate::{
actionbar::{BarAction, NoteActionResponse},
actionbar::NoteAction,
app_style::NotedeckTextStyle,
colors,
imgcache::ImageCache,
@@ -38,7 +38,7 @@ pub struct NoteView<'a> {
pub struct NoteResponse {
pub response: egui::Response,
pub context_selection: Option<NoteContextSelection>,
pub action: NoteActionResponse,
pub action: Option<NoteAction>,
}
impl NoteResponse {
@@ -46,11 +46,11 @@ impl NoteResponse {
Self {
response,
context_selection: None,
action: NoteActionResponse::default(),
action: None,
}
}
pub fn with_action(mut self, action: NoteActionResponse) -> Self {
pub fn with_action(mut self, action: Option<NoteAction>) -> Self {
self.action = action;
self
}
@@ -437,8 +437,7 @@ impl<'a> NoteView<'a> {
let note_key = self.note.key().expect("todo: support non-db notes");
let txn = self.note.txn().expect("todo: support non-db notes");
let mut open_profile: Option<Pubkey> = None;
let mut bar_action: Option<BarAction> = None;
let mut note_action: Option<NoteAction> = None;
let mut selected_option: Option<NoteContextSelection> = None;
let profile = self.ndb.get_profile_by_pubkey(txn, self.note.pubkey());
@@ -454,7 +453,7 @@ impl<'a> NoteView<'a> {
let response = if self.options().has_wide() {
ui.horizontal(|ui| {
if self.pfp(note_key, &profile, ui).clicked() {
open_profile = Some(Pubkey::new(*self.note.pubkey()));
note_action = Some(NoteAction::OpenProfile(Pubkey::new(*self.note.pubkey())));
};
let size = ui.available_size();
@@ -498,12 +497,15 @@ impl<'a> NoteView<'a> {
self.options(),
);
let resp = ui.add(&mut contents);
bar_action = bar_action.or(contents.action().bar_action);
open_profile = open_profile.or(contents.action().open_profile);
if let Some(action) = contents.action() {
note_action = Some(*action);
}
if self.options().has_actionbar() {
let ab = render_note_actionbar(ui, self.note.id(), note_key);
bar_action = bar_action.or(ab.inner);
if let Some(action) = render_note_actionbar(ui, self.note.id(), note_key).inner {
note_action = Some(action);
}
}
resp
@@ -511,7 +513,7 @@ impl<'a> NoteView<'a> {
// main design
ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| {
if self.pfp(note_key, &profile, ui).clicked() {
open_profile = Some(Pubkey::new(*self.note.pubkey()));
note_action = Some(NoteAction::OpenProfile(Pubkey::new(*self.note.pubkey())));
};
ui.with_layout(egui::Layout::top_down(egui::Align::LEFT), |ui| {
@@ -548,32 +550,34 @@ impl<'a> NoteView<'a> {
self.options(),
);
ui.add(&mut contents);
bar_action = bar_action.or(contents.action().bar_action);
open_profile = open_profile.or(contents.action().open_profile);
if let Some(action) = contents.action() {
note_action = Some(*action);
}
if self.options().has_actionbar() {
let ab = render_note_actionbar(ui, self.note.id(), note_key);
bar_action = bar_action.or(ab.inner);
if let Some(action) =
render_note_actionbar(ui, self.note.id(), note_key).inner
{
note_action = Some(action);
}
}
});
})
.response
};
bar_action = check_note_hitbox(
note_action = check_note_hitbox(
ui,
self.note.id(),
note_key,
&response,
maybe_hitbox,
bar_action,
note_action,
);
NoteResponse::new(response)
.with_action(NoteActionResponse {
bar_action,
open_profile,
})
.with_action(note_action)
.select_option(selected_option)
}
}
@@ -630,8 +634,8 @@ fn check_note_hitbox(
note_key: NoteKey,
note_response: &Response,
maybe_hitbox: Option<Response>,
prior_action: Option<BarAction>,
) -> Option<BarAction> {
prior_action: Option<NoteAction>,
) -> Option<NoteAction> {
// Stash the dimensions of the note content so we can render the
// hitbox in the next frame
ui.ctx().data_mut(|d| {
@@ -640,7 +644,7 @@ fn check_note_hitbox(
// If there was an hitbox and it was clicked open the thread
match maybe_hitbox {
Some(hitbox) if hitbox.clicked() => Some(BarAction::OpenThread(NoteId::new(*note_id))),
Some(hitbox) if hitbox.clicked() => Some(NoteAction::OpenThread(NoteId::new(*note_id))),
_ => prior_action,
}
}
@@ -649,15 +653,15 @@ fn render_note_actionbar(
ui: &mut egui::Ui,
note_id: &[u8; 32],
note_key: NoteKey,
) -> egui::InnerResponse<Option<BarAction>> {
) -> egui::InnerResponse<Option<NoteAction>> {
ui.horizontal(|ui| {
let reply_resp = reply_button(ui, note_key);
let quote_resp = quote_repost_button(ui, note_key);
if reply_resp.clicked() {
Some(BarAction::Reply(NoteId::new(*note_id)))
Some(NoteAction::Reply(NoteId::new(*note_id)))
} else if quote_resp.clicked() {
Some(BarAction::Quote(NoteId::new(*note_id)))
Some(NoteAction::Quote(NoteId::new(*note_id)))
} else {
None
}

View File

@@ -1,13 +1,14 @@
use crate::draft::{Draft, DraftSource};
use crate::draft::{Draft, Drafts};
use crate::imgcache::ImageCache;
use crate::notecache::NoteCache;
use crate::post::NewPost;
use crate::ui;
use crate::ui::{Preview, PreviewConfig, View};
use crate::Result;
use egui::widgets::text_edit::TextEdit;
use egui::{Frame, Layout};
use enostr::{FilledKeypair, FullKeypair, RelayPool};
use nostrdb::{Config, Ndb, Note, Transaction};
use enostr::{FilledKeypair, FullKeypair, NoteId, RelayPool};
use nostrdb::{Config, Ndb, Transaction};
use tracing::info;
use super::contents::render_note_preview;
@@ -15,35 +16,59 @@ use super::contents::render_note_preview;
pub struct PostView<'a> {
ndb: &'a Ndb,
draft: &'a mut Draft,
draft_source: DraftSource<'a>,
post_type: PostType,
img_cache: &'a mut ImageCache,
note_cache: &'a mut NoteCache,
poster: FilledKeypair<'a>,
id_source: Option<egui::Id>,
}
pub enum PostAction {
Post(NewPost),
#[derive(Clone)]
pub enum PostType {
New,
Quote(NoteId),
Reply(NoteId),
}
pub struct PostAction {
post_type: PostType,
post: NewPost,
}
impl PostAction {
pub fn execute<'b>(
poster: FilledKeypair<'_>,
action: &'b PostAction,
pool: &mut RelayPool,
draft: &mut Draft,
get_note: impl Fn(&'b NewPost, &[u8; 32]) -> Note<'b>,
) {
match action {
PostAction::Post(np) => {
let note = get_note(np, &poster.secret_key.to_secret_bytes());
pub fn new(post_type: PostType, post: NewPost) -> Self {
PostAction { post_type, post }
}
let raw_msg = format!("[\"EVENT\",{}]", note.json().unwrap());
info!("sending {}", raw_msg);
pool.send(&enostr::ClientMessage::raw(raw_msg));
draft.clear();
pub fn execute(
&self,
ndb: &Ndb,
txn: &Transaction,
pool: &mut RelayPool,
drafts: &mut Drafts,
) -> Result<()> {
let seckey = self.post.account.secret_key.to_secret_bytes();
let note = match self.post_type {
PostType::New => self.post.to_note(&seckey),
PostType::Reply(target) => {
let replying_to = ndb.get_note_by_id(txn, target.bytes())?;
self.post.to_reply(&seckey, &replying_to)
}
}
PostType::Quote(target) => {
let quoting = ndb.get_note_by_id(txn, target.bytes())?;
self.post.to_quote(&seckey, &quoting)
}
};
let raw_msg = format!("[\"EVENT\",{}]", note.json().unwrap());
info!("sending {}", raw_msg);
pool.send(&enostr::ClientMessage::raw(raw_msg));
drafts.get_from_post_type(&self.post_type).clear();
Ok(())
}
}
@@ -56,7 +81,7 @@ impl<'a> PostView<'a> {
pub fn new(
ndb: &'a Ndb,
draft: &'a mut Draft,
draft_source: DraftSource<'a>,
post_type: PostType,
img_cache: &'a mut ImageCache,
note_cache: &'a mut NoteCache,
poster: FilledKeypair<'a>,
@@ -69,7 +94,7 @@ impl<'a> PostView<'a> {
note_cache,
poster,
id_source,
draft_source,
post_type,
}
}
@@ -162,7 +187,7 @@ impl<'a> PostView<'a> {
let action = ui
.horizontal(|ui| {
if let DraftSource::Quote(id) = self.draft_source {
if let PostType::Quote(id) = self.post_type {
let avail_size = ui.available_size_before_wrap();
ui.with_layout(Layout::left_to_right(egui::Align::TOP), |ui| {
Frame::none().show(ui, |ui| {
@@ -174,7 +199,7 @@ impl<'a> PostView<'a> {
self.note_cache,
self.img_cache,
txn,
id,
id.bytes(),
"",
);
});
@@ -187,10 +212,11 @@ impl<'a> PostView<'a> {
.add_sized([91.0, 32.0], egui::Button::new("Post now"))
.clicked()
{
Some(PostAction::Post(NewPost::new(
let new_post = NewPost::new(
self.draft.buffer.clone(),
self.poster.to_full(),
)))
);
Some(PostAction::new(self.post_type.clone(), new_post))
} else {
None
}
@@ -241,7 +267,7 @@ mod preview {
PostView::new(
&self.ndb,
&mut self.draft,
DraftSource::Compose,
PostType::New,
&mut self.img_cache,
&mut self.note_cache,
self.poster.to_filled(),

View File

@@ -1,9 +1,9 @@
use enostr::FilledKeypair;
use enostr::{FilledKeypair, NoteId};
use nostrdb::Ndb;
use crate::{draft::Draft, imgcache::ImageCache, notecache::NoteCache, ui};
use super::PostResponse;
use super::{PostResponse, PostType};
pub struct QuoteRepostView<'a> {
ndb: &'a Ndb,
@@ -43,7 +43,7 @@ impl<'a> QuoteRepostView<'a> {
ui::PostView::new(
self.ndb,
self.draft,
crate::draft::DraftSource::Quote(quoting_note_id),
PostType::Quote(NoteId::new(quoting_note_id.to_owned())),
self.img_cache,
self.note_cache,
self.poster,

View File

@@ -2,8 +2,8 @@ use crate::draft::Draft;
use crate::imgcache::ImageCache;
use crate::notecache::NoteCache;
use crate::ui;
use crate::ui::note::PostResponse;
use enostr::FilledKeypair;
use crate::ui::note::{PostResponse, PostType};
use enostr::{FilledKeypair, NoteId};
use nostrdb::Ndb;
pub struct PostReplyView<'a> {
@@ -79,7 +79,7 @@ impl<'a> PostReplyView<'a> {
ui::PostView::new(
self.ndb,
self.draft,
crate::draft::DraftSource::Reply(replying_to),
PostType::Reply(NoteId::new(*replying_to)),
self.img_cache,
self.note_cache,
self.poster,

View File

@@ -9,7 +9,7 @@ pub use picture::ProfilePic;
pub use preview::ProfilePreview;
use crate::{
actionbar::NoteActionResponse, imgcache::ImageCache, notecache::NoteCache,
actionbar::NoteAction, imgcache::ImageCache, notecache::NoteCache,
notes_holder::NotesHolderStorage, profile::Profile,
};
@@ -46,7 +46,7 @@ impl<'a> ProfileView<'a> {
}
}
pub fn ui(&mut self, ui: &mut egui::Ui) -> NoteActionResponse {
pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<NoteAction> {
let scroll_id = egui::Id::new(("profile_scroll", self.col_id, self.pubkey));
ScrollArea::vertical()

View File

@@ -1,5 +1,5 @@
use crate::{
actionbar::NoteActionResponse,
actionbar::NoteAction,
imgcache::ImageCache,
notecache::NoteCache,
notes_holder::{NotesHolder, NotesHolderStorage},
@@ -52,7 +52,7 @@ impl<'a> ThreadView<'a> {
self
}
pub fn ui(&mut self, ui: &mut egui::Ui) -> NoteActionResponse {
pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<NoteAction> {
let txn = Transaction::new(self.ndb).expect("txn");
let selected_note_key = if let Ok(key) = self
@@ -63,7 +63,7 @@ impl<'a> ThreadView<'a> {
key
} else {
// TODO: render 404 ?
return NoteActionResponse::default();
return None;
};
ui.label(
@@ -80,7 +80,7 @@ impl<'a> ThreadView<'a> {
let note = if let Ok(note) = self.ndb.get_note_by_key(&txn, selected_note_key) {
note
} else {
return NoteActionResponse::default();
return None;
};
let root_id = {

View File

@@ -1,4 +1,4 @@
use crate::actionbar::{BarAction, NoteActionResponse};
use crate::actionbar::NoteAction;
use crate::timeline::TimelineTab;
use crate::{
column::Columns, imgcache::ImageCache, notecache::NoteCache, timeline::TimelineId, ui,
@@ -41,7 +41,7 @@ impl<'a> TimelineView<'a> {
}
}
pub fn ui(&mut self, ui: &mut egui::Ui) -> NoteActionResponse {
pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<NoteAction> {
timeline_ui(
ui,
self.ndb,
@@ -70,7 +70,7 @@ fn timeline_ui(
img_cache: &mut ImageCache,
reversed: bool,
note_options: NoteOptions,
) -> NoteActionResponse {
) -> Option<NoteAction> {
//padding(4.0, ui, |ui| ui.heading("Notifications"));
/*
let font_id = egui::TextStyle::Body.resolve(ui.style());
@@ -85,7 +85,7 @@ fn timeline_ui(
error!("tried to render timeline in column, but timeline was missing");
// TODO (jb55): render error when timeline is missing?
// this shouldn't happen...
return NoteActionResponse::default();
return None;
};
timeline.selected_view = tabs_ui(ui);
@@ -108,7 +108,7 @@ fn timeline_ui(
error!("tried to render timeline in column, but timeline was missing");
// TODO (jb55): render error when timeline is missing?
// this shouldn't happen...
return NoteActionResponse::default();
return None;
};
let txn = Transaction::new(ndb).expect("failed to create txn");
@@ -241,9 +241,8 @@ impl<'a> TimelineTabView<'a> {
}
}
pub fn show(&mut self, ui: &mut egui::Ui) -> NoteActionResponse {
let mut open_profile = None;
let mut bar_action: Option<BarAction> = None;
pub fn show(&mut self, ui: &mut egui::Ui) -> Option<NoteAction> {
let mut action: Option<NoteAction> = None;
let len = self.tab.notes.len();
self.tab
@@ -274,8 +273,9 @@ impl<'a> TimelineTabView<'a> {
.note_options(self.note_options)
.show(ui);
bar_action = bar_action.or(resp.action.bar_action);
open_profile = open_profile.or(resp.action.open_profile);
if let Some(note_action) = resp.action {
action = Some(note_action)
}
if let Some(context) = resp.context_selection {
context.process(ui, &note);
@@ -288,9 +288,6 @@ impl<'a> TimelineTabView<'a> {
1
});
NoteActionResponse {
open_profile,
bar_action,
}
action
}
}