Skip to content

Commit

Permalink
ui: customizable tabs per column view
Browse files Browse the repository at this point in the history
This reduces the number of choices the user needs to make. Some of these
filters were redundant anyways. This also saves memory.

Universe: Notes
Notificaitons: Notes & Replies
Everything else: Notes, Notes & Replies

Changelog-Changed: Simplified tab selections on some columns
Fixes: #517
  • Loading branch information
jb55 committed Dec 19, 2024
1 parent cb2330a commit 8025be8
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 46 deletions.
3 changes: 2 additions & 1 deletion crates/notedeck_columns/src/args.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use notedeck::FilterState;

use crate::timeline::{PubkeySource, Timeline, TimelineKind};
use crate::timeline::{PubkeySource, Timeline, TimelineKind, TimelineTab};
use enostr::{Filter, Pubkey};
use nostrdb::Ndb;
use tracing::{debug, error, info};
Expand Down Expand Up @@ -151,6 +151,7 @@ impl ArgColumn {
ArgColumn::Generic(filters) => Some(Timeline::new(
TimelineKind::Generic,
FilterState::ready(filters),
TimelineTab::full_tabs(),
)),
ArgColumn::Timeline(tk) => tk.into_timeline(ndb, user),
}
Expand Down
9 changes: 6 additions & 3 deletions crates/notedeck_columns/src/profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use notedeck::{filter::default_limit, FilterState, MuteFun, NoteCache, NoteRef};
use crate::{
multi_subscriber::MultiSubscriber,
notes_holder::NotesHolder,
timeline::{copy_notes_into_timeline, PubkeySource, Timeline, TimelineKind},
timeline::{copy_notes_into_timeline, PubkeySource, Timeline, TimelineKind, TimelineTab},
};

pub enum DisplayName<'a> {
Expand Down Expand Up @@ -62,8 +62,11 @@ impl Profile {
notes: Vec<NoteRef>,
is_muted: &MuteFun,
) -> Self {
let mut timeline =
Timeline::new(TimelineKind::profile(source), FilterState::ready(filters));
let mut timeline = Timeline::new(
TimelineKind::profile(source),
FilterState::ready(filters),
TimelineTab::full_tabs(),
);

copy_notes_into_timeline(&mut timeline, txn, ndb, note_cache, notes, is_muted);

Expand Down
7 changes: 6 additions & 1 deletion crates/notedeck_columns/src/timeline/kind.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::error::Error;
use crate::timeline::Timeline;
use crate::timeline::{Timeline, TimelineTab};
use enostr::{Filter, Pubkey};
use nostrdb::{Ndb, Transaction};
use notedeck::{filter::default_limit, FilterError, FilterState};
Expand Down Expand Up @@ -119,6 +119,7 @@ impl TimelineKind {
.kinds([1])
.limit(default_limit())
.build()]),
TimelineTab::no_replies(),
)),

TimelineKind::Generic => {
Expand All @@ -141,6 +142,7 @@ impl TimelineKind {
Some(Timeline::new(
TimelineKind::profile(pk_src),
FilterState::ready(vec![filter]),
TimelineTab::full_tabs(),
))
}

Expand All @@ -159,6 +161,7 @@ impl TimelineKind {
Some(Timeline::new(
TimelineKind::notifications(pk_src),
FilterState::ready(vec![notifications_filter]),
TimelineTab::only_notes_and_replies(),
))
}

Expand All @@ -181,6 +184,7 @@ impl TimelineKind {
return Some(Timeline::new(
TimelineKind::contact_list(pk_src),
FilterState::needs_remote(vec![contact_filter.clone()]),
TimelineTab::full_tabs(),
));
}

Expand All @@ -189,6 +193,7 @@ impl TimelineKind {
Some(Timeline::new(
TimelineKind::contact_list(pk_src),
FilterState::needs_remote(vec![contact_filter]),
TimelineTab::full_tabs(),
))
}
Err(e) => {
Expand Down
73 changes: 42 additions & 31 deletions crates/notedeck_columns/src/timeline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,6 @@ impl ViewFilter {
}
}

pub fn index(&self) -> usize {
match self {
ViewFilter::Notes => 0,
ViewFilter::NotesAndReplies => 1,
}
}

pub fn filter_notes(cache: &CachedNote, note: &Note) -> bool {
!cache.reply.borrow(note.tags()).is_reply()
}
Expand Down Expand Up @@ -100,6 +93,21 @@ impl TimelineTab {
TimelineTab::new_with_capacity(filter, 1000)
}

pub fn only_notes_and_replies() -> Vec<Self> {
vec![TimelineTab::new(ViewFilter::NotesAndReplies)]
}

pub fn no_replies() -> Vec<Self> {
vec![TimelineTab::new(ViewFilter::Notes)]
}

pub fn full_tabs() -> Vec<Self> {
vec![
TimelineTab::new(ViewFilter::Notes),
TimelineTab::new(ViewFilter::NotesAndReplies),
]
}

pub fn new_with_capacity(filter: ViewFilter, cap: usize) -> Self {
let selection = 0i32;
let mut list = VirtualList::new();
Expand Down Expand Up @@ -179,7 +187,7 @@ pub struct Timeline {
// that codepaths have to explicitly handle it
pub filter: FilterStates,
pub views: Vec<TimelineTab>,
pub selected_view: i32,
pub selected_view: usize,

/// Our nostrdb subscription
pub subscription: Option<Subscription>,
Expand All @@ -198,6 +206,7 @@ impl Timeline {
Ok(Timeline::new(
TimelineKind::contact_list(pk_src),
FilterState::ready(filter),
TimelineTab::full_tabs(),
))
}

Expand All @@ -211,26 +220,24 @@ impl Timeline {
Timeline::new(
TimelineKind::Hashtag(hashtag),
FilterState::ready(vec![filter]),
TimelineTab::full_tabs(),
)
}

pub fn make_view_id(id: TimelineId, selected_view: i32) -> egui::Id {
pub fn make_view_id(id: TimelineId, selected_view: usize) -> egui::Id {
egui::Id::new((id, selected_view))
}

pub fn view_id(&self) -> egui::Id {
Timeline::make_view_id(self.id, self.selected_view)
}

pub fn new(kind: TimelineKind, filter_state: FilterState) -> Self {
pub fn new(kind: TimelineKind, filter_state: FilterState, views: Vec<TimelineTab>) -> Self {
// global unique id for all new timelines
static UIDS: AtomicU32 = AtomicU32::new(0);

let filter = FilterStates::new(filter_state);
let subscription: Option<Subscription> = None;
let notes = TimelineTab::new(ViewFilter::Notes);
let replies = TimelineTab::new(ViewFilter::NotesAndReplies);
let views = vec![notes, replies];
let selected_view = 0;
let id = TimelineId::new(UIDS.fetch_add(1, Ordering::Relaxed));

Expand All @@ -245,23 +252,32 @@ impl Timeline {
}

pub fn current_view(&self) -> &TimelineTab {
&self.views[self.selected_view as usize]
&self.views[self.selected_view]
}

pub fn current_view_mut(&mut self) -> &mut TimelineTab {
&mut self.views[self.selected_view as usize]
&mut self.views[self.selected_view]
}

pub fn notes(&self, view: ViewFilter) -> &[NoteRef] {
&self.views[view.index()].notes
/// Get the note refs for NotesAndReplies. If we only have Notes, then
/// just return that instead
pub fn all_or_any_notes(&self) -> &[NoteRef] {
self.notes(ViewFilter::NotesAndReplies).unwrap_or_else(|| {
self.notes(ViewFilter::Notes)
.expect("should have at least notes")
})
}

pub fn view(&self, view: ViewFilter) -> &TimelineTab {
&self.views[view.index()]
pub fn notes(&self, view: ViewFilter) -> Option<&[NoteRef]> {
self.view(view).map(|v| &*v.notes)
}

pub fn view_mut(&mut self, view: ViewFilter) -> &mut TimelineTab {
&mut self.views[view.index()]
pub fn view(&self, view: ViewFilter) -> Option<&TimelineTab> {
self.views.iter().find(|tab| tab.filter == view)
}

pub fn view_mut(&mut self, view: ViewFilter) -> Option<&mut TimelineTab> {
self.views.iter_mut().find(|tab| tab.filter == view)
}

pub fn poll_notes_into_view(
Expand Down Expand Up @@ -314,21 +330,18 @@ impl Timeline {
let reversed = false;

// ViewFilter::NotesAndReplies
{
if let Some(view) = timeline.view_mut(ViewFilter::NotesAndReplies) {
let refs: Vec<NoteRef> = new_refs.iter().map(|(_note, nr)| *nr).collect();

let reversed = false;
timeline
.view_mut(ViewFilter::NotesAndReplies)
.insert(&refs, reversed);
view.insert(&refs, reversed);
}

//
// handle the filtered case (ViewFilter::Notes, no replies)
//
// TODO(jb55): this is mostly just copied from above, let's just use a loop
// I initially tried this but ran into borrow checker issues
{
if let Some(view) = timeline.view_mut(ViewFilter::Notes) {
let mut filtered_refs = Vec::with_capacity(new_refs.len());
for (note, nr) in &new_refs {
let cached_note = note_cache.cached_note_or_insert(nr.key, note);
Expand All @@ -338,9 +351,7 @@ impl Timeline {
}
}

timeline
.view_mut(ViewFilter::Notes)
.insert(&filtered_refs, reversed);
view.insert(&filtered_refs, reversed);
}

Ok(())
Expand Down Expand Up @@ -478,7 +489,7 @@ pub fn send_initial_timeline_filter(
filter = filter.limit_mut(lim);
}

let notes = timeline.notes(ViewFilter::NotesAndReplies);
let notes = timeline.all_or_any_notes();

// Should we since optimize? Not always. For example
// if we only have a few notes locally. One way to
Expand Down
3 changes: 2 additions & 1 deletion crates/notedeck_columns/src/ui/profile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ impl<'a> ProfileView<'a> {
)
.get_ptr();

profile.timeline.selected_view = tabs_ui(ui);
profile.timeline.selected_view =
tabs_ui(ui, profile.timeline.selected_view, &profile.timeline.views);

// poll for new notes and insert them into our existing notes
if let Err(e) = profile.poll_notes_into_view(&txn, self.ndb, is_muted) {
Expand Down
22 changes: 15 additions & 7 deletions crates/notedeck_columns/src/ui/timeline.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use crate::actionbar::NoteAction;
use crate::timeline::TimelineTab;
use crate::{column::Columns, timeline::TimelineId, ui, ui::note::NoteOptions};
use crate::{
column::Columns,
timeline::{TimelineId, ViewFilter},
ui,
ui::note::NoteOptions,
};
use egui::containers::scroll_area::ScrollBarVisibility;
use egui::{Direction, Layout};
use egui_tabs::TabColor;
Expand Down Expand Up @@ -86,7 +91,7 @@ fn timeline_ui(
return None;
};

timeline.selected_view = tabs_ui(ui);
timeline.selected_view = tabs_ui(ui, timeline.selected_view, &timeline.views);

// need this for some reason??
ui.add_space(3.0);
Expand Down Expand Up @@ -124,11 +129,11 @@ fn timeline_ui(
.inner
}

pub fn tabs_ui(ui: &mut egui::Ui) -> i32 {
pub fn tabs_ui(ui: &mut egui::Ui, selected: usize, views: &[TimelineTab]) -> usize {
ui.spacing_mut().item_spacing.y = 0.0;

let tab_res = egui_tabs::Tabs::new(2)
.selected(1)
let tab_res = egui_tabs::Tabs::new(views.len() as i32)
.selected(selected as i32)
.hover_bg(TabColor::none())
.selected_fg(TabColor::none())
.selected_bg(TabColor::none())
Expand All @@ -141,7 +146,10 @@ pub fn tabs_ui(ui: &mut egui::Ui) -> i32 {

let ind = state.index();

let txt = if ind == 0 { "Notes" } else { "Notes & Replies" };
let txt = match views[ind as usize].filter {
ViewFilter::Notes => "Notes",
ViewFilter::NotesAndReplies => "Notes & Replies",
};

let res = ui.add(egui::Label::new(txt).selectable(false));

Expand Down Expand Up @@ -189,7 +197,7 @@ pub fn tabs_ui(ui: &mut egui::Ui) -> i32 {

ui.painter().hline(underline, underline_y, stroke);

sel
sel as usize
}

fn get_label_width(ui: &mut egui::Ui, text: &str) -> f32 {
Expand Down
4 changes: 2 additions & 2 deletions crates/notedeck_columns/src/unknowns.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{column::Columns, timeline::ViewFilter, Result};
use crate::{column::Columns, Result};
use nostrdb::{Ndb, NoteKey, Transaction};
use notedeck::{CachedNote, NoteCache, UnknownIds};
use tracing::error;
Expand Down Expand Up @@ -37,7 +37,7 @@ pub fn get_unknown_ids(
let mut new_cached_notes: Vec<(NoteKey, CachedNote)> = vec![];

for timeline in columns.timelines() {
for noteref in timeline.notes(ViewFilter::NotesAndReplies) {
for noteref in timeline.all_or_any_notes() {
let note = ndb.get_note_by_key(txn, noteref.key)?;
let note_key = note.key().unwrap();
let cached_note = note_cache.cached_note(noteref.key);
Expand Down

0 comments on commit 8025be8

Please sign in to comment.