mirror of
https://github.com/aljazceru/notedeck.git
synced 2026-02-19 23:44:19 +01:00
Updated navigation to use a custom title renderer for more flexible rendering of navigation titles. This change decouples the rendering logic from predefined formats, enabling dynamic title compositions based on application context and data. This includes: - Refactoring `NavResponse` to introduce `NotedeckNavResponse` for handling unified navigation response data. - Adding `NavTitle` in `ui/column/header.rs` to handle rendering of navigation titles and profile images dynamically. - Updating route and timeline logic to support new rendering pipeline. - Replacing hardcoded title rendering with data-driven approaches. Benefits: - Simplifies navigation handling by consolidating title and action management. - Improves scalability for new navigation features without modifying core logic. - Enhances visual customization capabilities. Signed-off-by: William Casarin <jb55@jb55.com>
306 lines
9.1 KiB
Rust
306 lines
9.1 KiB
Rust
use crate::draft::{Draft, Drafts};
|
|
use crate::imgcache::ImageCache;
|
|
use crate::notecache::NoteCache;
|
|
use crate::post::NewPost;
|
|
use crate::ui::{self, Preview, PreviewConfig, View};
|
|
use crate::Result;
|
|
use egui::widgets::text_edit::TextEdit;
|
|
use egui::{Frame, Layout};
|
|
use enostr::{FilledKeypair, FullKeypair, NoteId, RelayPool};
|
|
use nostrdb::{Config, Ndb, Transaction};
|
|
use tracing::info;
|
|
|
|
use super::contents::render_note_preview;
|
|
|
|
pub struct PostView<'a> {
|
|
ndb: &'a Ndb,
|
|
draft: &'a mut Draft,
|
|
post_type: PostType,
|
|
img_cache: &'a mut ImageCache,
|
|
note_cache: &'a mut NoteCache,
|
|
poster: FilledKeypair<'a>,
|
|
id_source: Option<egui::Id>,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub enum PostType {
|
|
New,
|
|
Quote(NoteId),
|
|
Reply(NoteId),
|
|
}
|
|
|
|
pub struct PostAction {
|
|
post_type: PostType,
|
|
post: NewPost,
|
|
}
|
|
|
|
impl PostAction {
|
|
pub fn new(post_type: PostType, post: NewPost) -> Self {
|
|
PostAction { post_type, post }
|
|
}
|
|
|
|
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, "ing)
|
|
}
|
|
};
|
|
|
|
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(())
|
|
}
|
|
}
|
|
|
|
pub struct PostResponse {
|
|
pub action: Option<PostAction>,
|
|
pub edit_response: egui::Response,
|
|
}
|
|
|
|
impl<'a> PostView<'a> {
|
|
pub fn new(
|
|
ndb: &'a Ndb,
|
|
draft: &'a mut Draft,
|
|
post_type: PostType,
|
|
img_cache: &'a mut ImageCache,
|
|
note_cache: &'a mut NoteCache,
|
|
poster: FilledKeypair<'a>,
|
|
) -> Self {
|
|
let id_source: Option<egui::Id> = None;
|
|
PostView {
|
|
ndb,
|
|
draft,
|
|
img_cache,
|
|
note_cache,
|
|
poster,
|
|
id_source,
|
|
post_type,
|
|
}
|
|
}
|
|
|
|
pub fn id_source(mut self, id_source: impl std::hash::Hash) -> Self {
|
|
self.id_source = Some(egui::Id::new(id_source));
|
|
self
|
|
}
|
|
|
|
fn editbox(&mut self, txn: &nostrdb::Transaction, ui: &mut egui::Ui) -> egui::Response {
|
|
ui.spacing_mut().item_spacing.x = 12.0;
|
|
|
|
let pfp_size = 24.0;
|
|
|
|
// TODO: refactor pfp control to do all of this for us
|
|
let poster_pfp = self
|
|
.ndb
|
|
.get_profile_by_pubkey(txn, self.poster.pubkey.bytes())
|
|
.as_ref()
|
|
.ok()
|
|
.and_then(|p| Some(ui::ProfilePic::from_profile(self.img_cache, p)?.size(pfp_size)));
|
|
|
|
if let Some(pfp) = poster_pfp {
|
|
ui.add(pfp);
|
|
} else {
|
|
ui.add(
|
|
ui::ProfilePic::new(self.img_cache, ui::ProfilePic::no_pfp_url()).size(pfp_size),
|
|
);
|
|
}
|
|
|
|
let response = ui.add_sized(
|
|
ui.available_size(),
|
|
TextEdit::multiline(&mut self.draft.buffer)
|
|
.hint_text(egui::RichText::new("Write a banger note here...").weak())
|
|
.frame(false),
|
|
);
|
|
|
|
let focused = response.has_focus();
|
|
|
|
ui.ctx().data_mut(|d| d.insert_temp(self.id(), focused));
|
|
|
|
response
|
|
}
|
|
|
|
fn focused(&self, ui: &egui::Ui) -> bool {
|
|
ui.ctx()
|
|
.data(|d| d.get_temp::<bool>(self.id()).unwrap_or(false))
|
|
}
|
|
|
|
fn id(&self) -> egui::Id {
|
|
self.id_source.unwrap_or_else(|| egui::Id::new("post"))
|
|
}
|
|
|
|
pub fn outer_margin() -> f32 {
|
|
16.0
|
|
}
|
|
|
|
pub fn inner_margin() -> f32 {
|
|
12.0
|
|
}
|
|
|
|
pub fn ui(&mut self, txn: &nostrdb::Transaction, ui: &mut egui::Ui) -> PostResponse {
|
|
let focused = self.focused(ui);
|
|
let stroke = if focused {
|
|
ui.visuals().selection.stroke
|
|
} else {
|
|
//ui.visuals().selection.stroke
|
|
ui.visuals().noninteractive().bg_stroke
|
|
};
|
|
|
|
let mut frame = egui::Frame::default()
|
|
.inner_margin(egui::Margin::same(PostView::inner_margin()))
|
|
.outer_margin(egui::Margin::same(PostView::outer_margin()))
|
|
.fill(ui.visuals().extreme_bg_color)
|
|
.stroke(stroke)
|
|
.rounding(12.0);
|
|
|
|
if focused {
|
|
frame = frame.shadow(egui::epaint::Shadow {
|
|
offset: egui::vec2(0.0, 0.0),
|
|
blur: 8.0,
|
|
spread: 0.0,
|
|
color: stroke.color,
|
|
});
|
|
}
|
|
|
|
frame
|
|
.show(ui, |ui| {
|
|
ui.vertical(|ui| {
|
|
let edit_response = ui.horizontal(|ui| self.editbox(txn, ui)).inner;
|
|
|
|
let action = ui
|
|
.horizontal(|ui| {
|
|
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| {
|
|
ui.vertical(|ui| {
|
|
ui.set_max_width(avail_size.x * 0.8);
|
|
render_note_preview(
|
|
ui,
|
|
self.ndb,
|
|
self.note_cache,
|
|
self.img_cache,
|
|
txn,
|
|
id.bytes(),
|
|
nostrdb::NoteKey::new(0),
|
|
);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
ui.with_layout(egui::Layout::right_to_left(egui::Align::BOTTOM), |ui| {
|
|
if ui
|
|
.add_sized(
|
|
[91.0, 32.0],
|
|
post_button(!self.draft.buffer.is_empty()),
|
|
)
|
|
.clicked()
|
|
{
|
|
let new_post = NewPost::new(
|
|
self.draft.buffer.clone(),
|
|
self.poster.to_full(),
|
|
);
|
|
Some(PostAction::new(self.post_type.clone(), new_post))
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.inner
|
|
})
|
|
.inner;
|
|
|
|
PostResponse {
|
|
action,
|
|
edit_response,
|
|
}
|
|
})
|
|
.inner
|
|
})
|
|
.inner
|
|
}
|
|
}
|
|
|
|
fn post_button(interactive: bool) -> impl egui::Widget {
|
|
move |ui: &mut egui::Ui| {
|
|
let button = egui::Button::new("Post now");
|
|
if interactive {
|
|
ui.add(button)
|
|
} else {
|
|
ui.add(
|
|
button
|
|
.sense(egui::Sense::hover())
|
|
.fill(ui.visuals().widgets.noninteractive.bg_fill)
|
|
.stroke(ui.visuals().widgets.noninteractive.bg_stroke),
|
|
)
|
|
.on_hover_cursor(egui::CursorIcon::NotAllowed)
|
|
}
|
|
}
|
|
}
|
|
|
|
mod preview {
|
|
use super::*;
|
|
|
|
pub struct PostPreview {
|
|
ndb: Ndb,
|
|
img_cache: ImageCache,
|
|
note_cache: NoteCache,
|
|
draft: Draft,
|
|
poster: FullKeypair,
|
|
}
|
|
|
|
impl PostPreview {
|
|
fn new() -> Self {
|
|
let ndb = Ndb::new(".", &Config::new()).expect("ndb");
|
|
|
|
PostPreview {
|
|
ndb,
|
|
img_cache: ImageCache::new(".".into()),
|
|
note_cache: NoteCache::default(),
|
|
draft: Draft::new(),
|
|
poster: FullKeypair::generate(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl View for PostPreview {
|
|
fn ui(&mut self, ui: &mut egui::Ui) {
|
|
let txn = Transaction::new(&self.ndb).expect("txn");
|
|
PostView::new(
|
|
&self.ndb,
|
|
&mut self.draft,
|
|
PostType::New,
|
|
&mut self.img_cache,
|
|
&mut self.note_cache,
|
|
self.poster.to_filled(),
|
|
)
|
|
.ui(&txn, ui);
|
|
}
|
|
}
|
|
|
|
impl Preview for PostView<'_> {
|
|
type Prev = PostPreview;
|
|
|
|
fn preview(_cfg: PreviewConfig) -> Self::Prev {
|
|
PostPreview::new()
|
|
}
|
|
}
|
|
}
|