mirror of
https://github.com/aljazceru/notedeck.git
synced 2026-01-18 07:44:20 +01:00
Add 'more options' to each note
Signed-off-by: kernelkind <kernelkind@gmail.com>
This commit is contained in:
@@ -68,6 +68,10 @@ impl Pubkey {
|
||||
Ok(Pubkey(data.1.try_into().unwrap()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bech(&self) -> Option<String> {
|
||||
nostr::bech32::encode::<nostr::bech32::Bech32>(HRP_NPUB, &self.0).ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Pubkey {
|
||||
|
||||
@@ -23,6 +23,7 @@ pub mod login_manager;
|
||||
mod macos_key_storage;
|
||||
mod nav;
|
||||
mod note;
|
||||
mod note_options;
|
||||
mod notecache;
|
||||
mod post;
|
||||
mod post_action_executor;
|
||||
|
||||
40
src/note_options.rs
Normal file
40
src/note_options.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use enostr::{NoteId, Pubkey};
|
||||
use nostrdb::Note;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum NoteOptionSelection {
|
||||
CopyText,
|
||||
CopyPubkey,
|
||||
CopyNoteId,
|
||||
}
|
||||
|
||||
pub fn process_note_selection(
|
||||
ui: &mut egui::Ui,
|
||||
selection: Option<NoteOptionSelection>,
|
||||
note: &Note<'_>,
|
||||
) {
|
||||
if let Some(option) = selection {
|
||||
match option {
|
||||
NoteOptionSelection::CopyText => {
|
||||
ui.output_mut(|w| {
|
||||
w.copied_text = note.content().to_string();
|
||||
});
|
||||
}
|
||||
NoteOptionSelection::CopyPubkey => {
|
||||
ui.output_mut(|w| {
|
||||
if let Some(bech) = Pubkey::new(*note.pubkey()).to_bech() {
|
||||
w.copied_text = bech;
|
||||
}
|
||||
});
|
||||
}
|
||||
NoteOptionSelection::CopyNoteId => {
|
||||
ui.output_mut(|w| {
|
||||
if let Some(bech) = NoteId::new(*note.id()).to_bech() {
|
||||
w.copied_text = bech;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,6 @@ impl NoteCache {
|
||||
pub struct CachedNote {
|
||||
reltime: TimeCached<String>,
|
||||
pub reply: NoteReplyBuf,
|
||||
pub bar_open: bool,
|
||||
}
|
||||
|
||||
impl CachedNote {
|
||||
@@ -46,12 +45,7 @@ impl CachedNote {
|
||||
Box::new(move || time_ago_since(created_at)),
|
||||
);
|
||||
let reply = NoteReply::new(note.tags()).to_owned();
|
||||
let bar_open = false;
|
||||
CachedNote {
|
||||
reltime,
|
||||
reply,
|
||||
bar_open,
|
||||
}
|
||||
CachedNote { reltime, reply }
|
||||
}
|
||||
|
||||
pub fn reltime_str_mut(&mut self) -> &str {
|
||||
|
||||
@@ -108,6 +108,7 @@ pub fn render_note_preview(
|
||||
.small_pfp(true)
|
||||
.wide(true)
|
||||
.note_previews(false)
|
||||
.use_more_options_button(true)
|
||||
.show(ui);
|
||||
})
|
||||
.response
|
||||
|
||||
@@ -15,10 +15,11 @@ use crate::{
|
||||
app_style::NotedeckTextStyle,
|
||||
colors,
|
||||
imgcache::ImageCache,
|
||||
note_options::NoteOptionSelection,
|
||||
notecache::{CachedNote, NoteCache},
|
||||
ui::{self, View},
|
||||
};
|
||||
use egui::{Id, Label, Response, RichText, Sense};
|
||||
use egui::{Align, Id, Label, Layout, Response, RichText, Sense};
|
||||
use enostr::NoteId;
|
||||
use nostrdb::{Ndb, Note, NoteKey, NoteReply, Transaction};
|
||||
|
||||
@@ -30,11 +31,34 @@ pub struct NoteView<'a> {
|
||||
img_cache: &'a mut ImageCache,
|
||||
note: &'a nostrdb::Note<'a>,
|
||||
flags: NoteOptions,
|
||||
use_options: bool,
|
||||
}
|
||||
|
||||
pub struct NoteResponse {
|
||||
pub response: egui::Response,
|
||||
pub action: Option<BarAction>,
|
||||
pub option_selection: Option<NoteOptionSelection>,
|
||||
}
|
||||
|
||||
impl NoteResponse {
|
||||
pub fn new(response: egui::Response) -> Self {
|
||||
Self {
|
||||
response,
|
||||
action: None,
|
||||
option_selection: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_action(self, action: Option<BarAction>) -> Self {
|
||||
Self { action, ..self }
|
||||
}
|
||||
|
||||
pub fn select_option(self, option_selection: Option<NoteOptionSelection>) -> Self {
|
||||
Self {
|
||||
option_selection,
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> View for NoteView<'a> {
|
||||
@@ -177,6 +201,7 @@ impl<'a> NoteView<'a> {
|
||||
img_cache,
|
||||
note,
|
||||
flags,
|
||||
use_options: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,6 +240,13 @@ impl<'a> NoteView<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn use_more_options_button(self, enable: bool) -> Self {
|
||||
Self {
|
||||
use_options: enable,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn options(&self) -> NoteOptions {
|
||||
self.flags
|
||||
}
|
||||
@@ -324,10 +356,7 @@ impl<'a> NoteView<'a> {
|
||||
|
||||
pub fn show(&mut self, ui: &mut egui::Ui) -> NoteResponse {
|
||||
if self.options().has_textmode() {
|
||||
NoteResponse {
|
||||
response: self.textmode_ui(ui),
|
||||
action: None,
|
||||
}
|
||||
NoteResponse::new(self.textmode_ui(ui))
|
||||
} else {
|
||||
let txn = self.note.txn().expect("txn");
|
||||
if let Some(note_to_repost) = get_reposted_note(self.ndb, txn, self.note) {
|
||||
@@ -369,17 +398,29 @@ impl<'a> NoteView<'a> {
|
||||
note_cache: &mut NoteCache,
|
||||
note: &Note,
|
||||
profile: &Result<nostrdb::ProfileRecord<'_>, nostrdb::Error>,
|
||||
) -> egui::Response {
|
||||
use_options_button: bool,
|
||||
) -> NoteResponse {
|
||||
let note_key = note.key().unwrap();
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let inner_response = ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 2.0;
|
||||
ui.add(ui::Username::new(profile.as_ref().ok(), note.pubkey()).abbreviated(20));
|
||||
|
||||
let cached_note = note_cache.cached_note_or_insert_mut(note_key, note);
|
||||
render_reltime(ui, cached_note, true);
|
||||
})
|
||||
.response
|
||||
|
||||
if use_options_button {
|
||||
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
||||
let more_options_resp = more_options_button(ui, note_key, 8.0);
|
||||
options_context_menu(more_options_resp)
|
||||
})
|
||||
.inner
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
NoteResponse::new(inner_response.response).select_option(inner_response.inner)
|
||||
}
|
||||
|
||||
fn show_standard(&mut self, ui: &mut egui::Ui) -> NoteResponse {
|
||||
@@ -388,6 +429,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 note_action: Option<BarAction> = None;
|
||||
let mut selected_option: Option<NoteOptionSelection> = None;
|
||||
let profile = self.ndb.get_profile_by_pubkey(txn, self.note.pubkey());
|
||||
let maybe_hitbox = maybe_note_hitbox(ui, note_key);
|
||||
|
||||
@@ -400,7 +442,14 @@ impl<'a> NoteView<'a> {
|
||||
ui.vertical(|ui| {
|
||||
ui.add_sized([size.x, self.options().pfp_size()], |ui: &mut egui::Ui| {
|
||||
ui.horizontal_centered(|ui| {
|
||||
NoteView::note_header(ui, self.note_cache, self.note, &profile);
|
||||
selected_option = NoteView::note_header(
|
||||
ui,
|
||||
self.note_cache,
|
||||
self.note,
|
||||
&profile,
|
||||
self.use_options,
|
||||
)
|
||||
.option_selection;
|
||||
})
|
||||
.response
|
||||
});
|
||||
@@ -440,8 +489,14 @@ impl<'a> NoteView<'a> {
|
||||
self.pfp(note_key, &profile, ui);
|
||||
|
||||
ui.with_layout(egui::Layout::top_down(egui::Align::LEFT), |ui| {
|
||||
NoteView::note_header(ui, self.note_cache, self.note, &profile);
|
||||
|
||||
selected_option = NoteView::note_header(
|
||||
ui,
|
||||
self.note_cache,
|
||||
self.note,
|
||||
&profile,
|
||||
self.use_options,
|
||||
)
|
||||
.option_selection;
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 2.0;
|
||||
|
||||
@@ -483,10 +538,9 @@ impl<'a> NoteView<'a> {
|
||||
note_action,
|
||||
);
|
||||
|
||||
NoteResponse {
|
||||
response,
|
||||
action: note_action,
|
||||
}
|
||||
NoteResponse::new(response)
|
||||
.with_action(note_action)
|
||||
.select_option(selected_option)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -631,3 +685,66 @@ fn quote_repost_button(ui: &mut egui::Ui, note_key: NoteKey) -> egui::Response {
|
||||
|
||||
resp.union(put_resp)
|
||||
}
|
||||
|
||||
fn more_options_button(ui: &mut egui::Ui, note_key: NoteKey, max_height: f32) -> egui::Response {
|
||||
let id = ui.id().with(("more_options_anim", note_key));
|
||||
|
||||
let expansion_multiple = 2.0;
|
||||
let max_radius = max_height;
|
||||
let min_radius = max_radius / expansion_multiple;
|
||||
let max_distance_between_circles = 2.0;
|
||||
let min_distance_between_circles = max_distance_between_circles / expansion_multiple;
|
||||
let max_width = max_radius * 3.0 + max_distance_between_circles * 2.0;
|
||||
|
||||
let anim_speed = 0.05;
|
||||
let expanded_size = egui::vec2(max_width, max_height);
|
||||
let (rect, response) = ui.allocate_exact_size(expanded_size, egui::Sense::click());
|
||||
|
||||
let animation_progress = ui
|
||||
.ctx()
|
||||
.animate_bool_with_time(id, response.hovered(), anim_speed);
|
||||
let cur_distance = min_distance_between_circles
|
||||
+ (max_distance_between_circles - min_distance_between_circles) * animation_progress;
|
||||
let cur_radius = min_radius + (max_radius - min_radius) * animation_progress;
|
||||
|
||||
let center = rect.center();
|
||||
let left_circle_center = center - egui::vec2(cur_distance + cur_radius, 0.0);
|
||||
let right_circle_center = center + egui::vec2(cur_distance + cur_radius, 0.0);
|
||||
|
||||
let translated_radius = (cur_radius - 1.0) / 2.0;
|
||||
|
||||
let color = if ui.style().visuals.dark_mode {
|
||||
egui::Color32::WHITE
|
||||
} else {
|
||||
egui::Color32::BLACK
|
||||
};
|
||||
|
||||
// Draw circles
|
||||
let painter = ui.painter_at(rect);
|
||||
painter.circle_filled(left_circle_center, translated_radius, color);
|
||||
painter.circle_filled(center, translated_radius, color);
|
||||
painter.circle_filled(right_circle_center, translated_radius, color);
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
fn options_context_menu(more_options_button_resp: egui::Response) -> Option<NoteOptionSelection> {
|
||||
let mut selected_option: Option<NoteOptionSelection> = None;
|
||||
|
||||
more_options_button_resp.context_menu(|ui| {
|
||||
ui.set_max_width(200.0);
|
||||
if ui.button("Copy text").clicked() {
|
||||
selected_option = Some(NoteOptionSelection::CopyText);
|
||||
ui.close_menu();
|
||||
}
|
||||
if ui.button("Copy user public key").clicked() {
|
||||
selected_option = Some(NoteOptionSelection::CopyPubkey);
|
||||
ui.close_menu();
|
||||
}
|
||||
if ui.button("Copy note id").clicked() {
|
||||
selected_option = Some(NoteOptionSelection::CopyNoteId);
|
||||
ui.close_menu();
|
||||
}
|
||||
});
|
||||
selected_option
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ impl<'a> PostReplyView<'a> {
|
||||
ui::NoteView::new(self.ndb, self.note_cache, self.img_cache, self.note)
|
||||
.actionbar(false)
|
||||
.medium_pfp(true)
|
||||
.use_more_options_button(true)
|
||||
.show(ui);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::{
|
||||
actionbar::BarAction, imgcache::ImageCache, notecache::NoteCache, thread::Threads, ui,
|
||||
actionbar::BarAction, imgcache::ImageCache, note_options::process_note_selection,
|
||||
notecache::NoteCache, thread::Threads, ui,
|
||||
};
|
||||
use nostrdb::{Ndb, NoteKey, Transaction};
|
||||
use tracing::{error, warn};
|
||||
@@ -115,15 +116,17 @@ impl<'a> ThreadView<'a> {
|
||||
};
|
||||
|
||||
ui::padding(8.0, ui, |ui| {
|
||||
if let Some(bar_action) =
|
||||
let note_response =
|
||||
ui::NoteView::new(self.ndb, self.note_cache, self.img_cache, ¬e)
|
||||
.note_previews(!self.textmode)
|
||||
.textmode(self.textmode)
|
||||
.show(ui)
|
||||
.action
|
||||
{
|
||||
.use_more_options_button(!self.textmode)
|
||||
.show(ui);
|
||||
if let Some(bar_action) = note_response.action {
|
||||
action = Some(bar_action);
|
||||
}
|
||||
|
||||
process_note_selection(ui, note_response.option_selection, ¬e);
|
||||
});
|
||||
|
||||
ui::hline(ui);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::draft::Draft;
|
||||
use crate::note_options::process_note_selection;
|
||||
use crate::{
|
||||
actionbar::BarAction, column::Columns, imgcache::ImageCache, notecache::NoteCache,
|
||||
timeline::TimelineId, ui,
|
||||
@@ -149,6 +150,7 @@ fn timeline_ui(
|
||||
let resp = ui::NoteView::new(ndb, note_cache, img_cache, ¬e)
|
||||
.note_previews(!textmode)
|
||||
.selectable_text(false)
|
||||
.use_more_options_button(true)
|
||||
.show(ui);
|
||||
|
||||
if let Some(ba) = resp.action {
|
||||
@@ -156,6 +158,8 @@ fn timeline_ui(
|
||||
} else if resp.response.clicked() {
|
||||
debug!("clicked note");
|
||||
}
|
||||
|
||||
process_note_selection(ui, resp.option_selection, ¬e);
|
||||
});
|
||||
|
||||
ui::hline(ui);
|
||||
|
||||
Reference in New Issue
Block a user