ui: add rendering for NoteUnits
Signed-off-by: kernelkind <kernelkind@gmail.com>
This commit is contained in:
@@ -1,13 +1,19 @@
|
|||||||
use egui::containers::scroll_area::ScrollBarVisibility;
|
use egui::containers::scroll_area::ScrollBarVisibility;
|
||||||
use egui::{vec2, Direction, Layout, Pos2, Stroke};
|
use egui::{vec2, Direction, Layout, Pos2, ScrollArea, Sense, Stroke};
|
||||||
use egui_tabs::TabColor;
|
use egui_tabs::TabColor;
|
||||||
use nostrdb::Transaction;
|
use enostr::Pubkey;
|
||||||
|
use nostrdb::{ProfileRecord, Transaction};
|
||||||
|
use notedeck::name::get_display_name;
|
||||||
use notedeck::ui::is_narrow;
|
use notedeck::ui::is_narrow;
|
||||||
use notedeck::JobsCache;
|
use notedeck::{JobsCache, Muted, NoteRef};
|
||||||
|
use notedeck_ui::app_images::like_image;
|
||||||
|
use notedeck_ui::{padding, ProfilePic};
|
||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
use tracing::{error, warn};
|
use tracing::{error, warn};
|
||||||
|
|
||||||
use crate::timeline::{TimelineCache, TimelineKind, TimelineTab, ViewFilter};
|
use crate::timeline::{
|
||||||
|
CompositeUnit, NoteUnit, ReactionUnit, TimelineCache, TimelineKind, TimelineTab, ViewFilter,
|
||||||
|
};
|
||||||
use notedeck::{
|
use notedeck::{
|
||||||
note::root_note_id_from_selected_id, tr, Localization, NoteAction, NoteContext, ScrollInfo,
|
note::root_note_id_from_selected_id, tr, Localization, NoteAction, NoteContext, ScrollInfo,
|
||||||
};
|
};
|
||||||
@@ -467,4 +473,198 @@ impl<'a, 'd> TimelineTabView<'a, 'd> {
|
|||||||
|
|
||||||
action
|
action
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_entry(
|
||||||
|
&mut self,
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
entry: &NoteUnit,
|
||||||
|
mute: &std::sync::Arc<Muted>,
|
||||||
|
) -> RenderEntryResponse {
|
||||||
|
match entry {
|
||||||
|
NoteUnit::Single(note_ref) => render_note(
|
||||||
|
ui,
|
||||||
|
self.note_context,
|
||||||
|
self.note_options,
|
||||||
|
self.jobs,
|
||||||
|
mute,
|
||||||
|
self.txn,
|
||||||
|
note_ref,
|
||||||
|
),
|
||||||
|
NoteUnit::Composite(composite) => match composite {
|
||||||
|
CompositeUnit::Reaction(reaction_unit) => render_reaction_cluster(
|
||||||
|
ui,
|
||||||
|
self.note_context,
|
||||||
|
self.note_options,
|
||||||
|
self.jobs,
|
||||||
|
mute,
|
||||||
|
self.txn,
|
||||||
|
reaction_unit,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_note(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
note_context: &mut NoteContext,
|
||||||
|
note_options: NoteOptions,
|
||||||
|
jobs: &mut JobsCache,
|
||||||
|
mute: &std::sync::Arc<Muted>,
|
||||||
|
txn: &Transaction,
|
||||||
|
note_ref: &NoteRef,
|
||||||
|
) -> RenderEntryResponse {
|
||||||
|
let note = if let Ok(note) = note_context.ndb.get_note_by_key(txn, note_ref.key) {
|
||||||
|
note
|
||||||
|
} else {
|
||||||
|
warn!("failed to query note {:?}", note_ref.key);
|
||||||
|
return RenderEntryResponse::Unsuccessful;
|
||||||
|
};
|
||||||
|
|
||||||
|
let muted = if let Ok(root_id) =
|
||||||
|
root_note_id_from_selected_id(note_context.ndb, note_context.note_cache, txn, note.id())
|
||||||
|
{
|
||||||
|
mute.is_muted(¬e, root_id.bytes())
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
if muted {
|
||||||
|
return RenderEntryResponse::Success(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut action = None;
|
||||||
|
notedeck_ui::padding(8.0, ui, |ui| {
|
||||||
|
let resp = NoteView::new(note_context, ¬e, note_options, jobs).show(ui);
|
||||||
|
|
||||||
|
if let Some(note_action) = resp.action {
|
||||||
|
action = Some(note_action);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
notedeck_ui::hline(ui);
|
||||||
|
|
||||||
|
RenderEntryResponse::Success(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_reaction_cluster(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
note_context: &mut NoteContext,
|
||||||
|
note_options: NoteOptions,
|
||||||
|
jobs: &mut JobsCache,
|
||||||
|
mute: &std::sync::Arc<Muted>,
|
||||||
|
txn: &Transaction,
|
||||||
|
reaction: &ReactionUnit,
|
||||||
|
) -> RenderEntryResponse {
|
||||||
|
let reacted_to_key = reaction.note_reacted_to.key;
|
||||||
|
let reacted_to_note = if let Ok(note) = note_context.ndb.get_note_by_key(txn, reacted_to_key) {
|
||||||
|
note
|
||||||
|
} else {
|
||||||
|
warn!("failed to query note {:?}", reacted_to_key);
|
||||||
|
return RenderEntryResponse::Unsuccessful;
|
||||||
|
};
|
||||||
|
|
||||||
|
let profiles_to_show: Vec<ProfileEntry> = reaction
|
||||||
|
.reactions
|
||||||
|
.values()
|
||||||
|
.filter(|r| !mute.is_pk_muted(r.sender.bytes()))
|
||||||
|
.map(|r| &r.sender)
|
||||||
|
.map(|p| ProfileEntry {
|
||||||
|
record: note_context.ndb.get_profile_by_pubkey(txn, p.bytes()).ok(),
|
||||||
|
pk: p,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let first_name = get_display_name(profiles_to_show.iter().find_map(|opt| opt.record.as_ref()))
|
||||||
|
.name()
|
||||||
|
.to_string();
|
||||||
|
let num_profiles_other = profiles_to_show.len() - 1;
|
||||||
|
|
||||||
|
let mut action = None;
|
||||||
|
padding(8.0, ui, |ui| {
|
||||||
|
ui.allocate_ui_with_layout(
|
||||||
|
vec2(ui.available_width(), 32.0),
|
||||||
|
Layout::left_to_right(egui::Align::Center),
|
||||||
|
|ui| {
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.add_space(16.0);
|
||||||
|
ui.add_sized(vec2(32.0, 32.0), like_image());
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(16.0);
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ScrollArea::horizontal()
|
||||||
|
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||||
|
.show(ui, |ui| {
|
||||||
|
for entry in profiles_to_show {
|
||||||
|
let resp = ui.add(
|
||||||
|
&mut ProfilePic::from_profile_or_default(
|
||||||
|
note_context.img_cache,
|
||||||
|
entry.record.as_ref(),
|
||||||
|
)
|
||||||
|
.sense(Sense::click()),
|
||||||
|
);
|
||||||
|
|
||||||
|
if resp.clicked() {
|
||||||
|
action = Some(NoteAction::Profile(*entry.pk))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let note_type_desc = if note_context
|
||||||
|
.accounts
|
||||||
|
.get_selected_account()
|
||||||
|
.key
|
||||||
|
.pubkey
|
||||||
|
.bytes()
|
||||||
|
!= reacted_to_note.pubkey()
|
||||||
|
{
|
||||||
|
"note you were tagged in"
|
||||||
|
} else {
|
||||||
|
"your note"
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.add_space(2.0);
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add_space(52.0);
|
||||||
|
|
||||||
|
ui.horizontal_wrapped(|ui| {
|
||||||
|
if num_profiles_other > 0 {
|
||||||
|
ui.label(format!(
|
||||||
|
"{first_name} and {num_profiles_other} others reacted to {note_type_desc}",
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
ui.label(format!("{first_name} reacted to {note_type_desc}"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(16.0);
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.add_space(48.0);
|
||||||
|
let resp = NoteView::new(note_context, &reacted_to_note, note_options, jobs).show(ui);
|
||||||
|
|
||||||
|
if let Some(note_action) = resp.action {
|
||||||
|
action = Some(note_action);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
notedeck_ui::hline(ui);
|
||||||
|
RenderEntryResponse::Success(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RenderEntryResponse {
|
||||||
|
Unsuccessful,
|
||||||
|
Success(Option<NoteAction>),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ProfileEntry<'a> {
|
||||||
|
record: Option<ProfileRecord<'a>>,
|
||||||
|
pk: &'a Pubkey,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user