Skip to content

Commit 77dfd45

Browse files
authored
Merge pull request #897 from multiplex55/codex/add-metadata-toggle-to-notepanel
Add hide/show details toggle to NotePanel metadata
2 parents 986e394 + c04f652 commit 77dfd45

1 file changed

Lines changed: 175 additions & 63 deletions

File tree

src/gui/note_panel.rs

Lines changed: 175 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use url::Url;
3030
const BACKLINK_PAGE_SIZE: usize = 12;
3131
const HEAVY_RECOMPUTE_IDLE_DEBOUNCE: Duration = Duration::from_millis(250);
3232

33-
#[derive(Clone, Copy, PartialEq, Eq)]
33+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3434
enum BacklinkTab {
3535
LinkedTodos,
3636
RelatedNotes,
@@ -161,6 +161,7 @@ pub struct NotePanel {
161161
image_cache: HashMap<std::path::PathBuf, egui::TextureHandle>,
162162
overwrite_prompt: bool,
163163
show_open_with_menu: bool,
164+
show_metadata: bool,
164165
tags_expanded: bool,
165166
links_expanded: bool,
166167
backlink_tab: BacklinkTab,
@@ -183,6 +184,17 @@ pub struct NotePanel {
183184
last_todo_revision: u64,
184185
#[cfg(test)]
185186
heavy_recompute_count: usize,
187+
#[cfg(test)]
188+
last_ui_sections: NotePanelUiSections,
189+
}
190+
191+
#[cfg(test)]
192+
#[derive(Default, Clone, Copy)]
193+
struct NotePanelUiSections {
194+
tags_visible: bool,
195+
links_visible: bool,
196+
backlinks_visible: bool,
197+
content_visible: bool,
186198
}
187199

188200
#[derive(Default, Clone)]
@@ -197,6 +209,14 @@ struct NoteDerivedView {
197209
}
198210

199211
impl NotePanel {
212+
fn details_toggle_label(&self) -> &'static str {
213+
if self.show_metadata {
214+
"Hide Details"
215+
} else {
216+
"Show Details"
217+
}
218+
}
219+
200220
pub fn from_note(note: Note) -> Self {
201221
let mut panel = Self {
202222
open: true,
@@ -209,6 +229,7 @@ impl NotePanel {
209229
image_cache: HashMap::new(),
210230
overwrite_prompt: false,
211231
show_open_with_menu: false,
232+
show_metadata: true,
212233
tags_expanded: false,
213234
links_expanded: false,
214235
backlink_tab: BacklinkTab::LinkedTodos,
@@ -227,6 +248,8 @@ impl NotePanel {
227248
last_todo_revision: 0,
228249
#[cfg(test)]
229250
heavy_recompute_count: 0,
251+
#[cfg(test)]
252+
last_ui_sections: NotePanelUiSections::default(),
230253
};
231254
panel.refresh_fast_derived();
232255
panel.refresh_heavy_derived(true);
@@ -340,6 +363,10 @@ impl NotePanel {
340363
.max_height(max_height)
341364
.movable(true)
342365
.show(ctx, |ui| {
366+
#[cfg(test)]
367+
{
368+
self.last_ui_sections = NotePanelUiSections::default();
369+
}
343370
if ui
344371
.ctx()
345372
.input(|i| i.modifiers.ctrl && i.key_pressed(Key::Equals))
@@ -420,6 +447,16 @@ impl NotePanel {
420447
ui.ctx().memory_mut(|m| m.surrender_focus(id));
421448
}
422449
}
450+
if ui.button(self.details_toggle_label()).clicked() {
451+
self.show_metadata = !self.show_metadata;
452+
let was_focused = self
453+
.last_textedit_id
454+
.map(|id| ui.ctx().memory(|m| m.has_focus(id)))
455+
.unwrap_or(false);
456+
if was_focused {
457+
self.focus_textedit_next_frame = true;
458+
}
459+
}
423460
ui.separator();
424461
if ui.button("A-").clicked() {
425462
app.note_font_size = (app.note_font_size - 1.0).max(8.0);
@@ -432,7 +469,11 @@ impl NotePanel {
432469
self.refresh_fast_derived();
433470
}
434471
self.maybe_refresh_heavy_derived(ctx);
435-
if !self.derived.tags.is_empty() {
472+
if self.show_metadata && !self.derived.tags.is_empty() {
473+
#[cfg(test)]
474+
{
475+
self.last_ui_sections.tags_visible = true;
476+
}
436477
let was_focused = self
437478
.last_textedit_id
438479
.map(|id| ui.ctx().memory(|m| m.has_focus(id)))
@@ -477,7 +518,11 @@ impl NotePanel {
477518
.into_iter()
478519
.map(|(label, url)| LinkKind::Url(label, url)),
479520
);
480-
if !all_links.is_empty() {
521+
if self.show_metadata && !all_links.is_empty() {
522+
#[cfg(test)]
523+
{
524+
self.last_ui_sections.links_visible = true;
525+
}
481526
let was_focused = self
482527
.last_textedit_id
483528
.map(|id| ui.ctx().memory(|m| m.has_focus(id)))
@@ -513,74 +558,91 @@ impl NotePanel {
513558
}
514559
});
515560
}
516-
ui.separator();
517-
ui.label("Backlinks");
518-
ui.horizontal(|ui| {
519-
for tab in [
520-
BacklinkTab::LinkedTodos,
521-
BacklinkTab::RelatedNotes,
522-
BacklinkTab::Mentions,
523-
] {
524-
if ui
525-
.selectable_label(self.backlink_tab == tab, tab.label())
526-
.clicked()
527-
{
528-
self.backlink_tab = tab;
529-
self.backlink_page = 0;
530-
}
561+
if self.show_metadata {
562+
#[cfg(test)]
563+
{
564+
self.last_ui_sections.backlinks_visible = true;
531565
}
532-
});
533-
let rows = self.backlink_rows_for_active_tab();
534-
let total_pages = (rows.len() + BACKLINK_PAGE_SIZE - 1) / BACKLINK_PAGE_SIZE;
535-
let page_start = self.backlink_page * BACKLINK_PAGE_SIZE;
536-
let page_end = (page_start + BACKLINK_PAGE_SIZE).min(rows.len());
537-
if rows.is_empty() {
538-
ui.small("No backlinks in this category.");
539-
} else {
540-
for (idx, row) in rows[page_start..page_end].iter().enumerate() {
541-
ui.push_id(("backlink_row", idx, page_start), |ui| {
542-
let resp = ui.selectable_label(false, &row.title);
543-
if resp.clicked() {
544-
if let Some(slug) = &row.note_slug {
545-
app.open_note_panel(slug, None);
546-
} else if let Some(todo_id) = &row.todo_id {
547-
let todos = load_todos(TODO_FILE).unwrap_or_default();
548-
if let Some((todo_idx, _)) =
549-
todos.iter().enumerate().find(|(_, t)| &t.id == todo_id)
550-
{
551-
app.todo_view_dialog.open_edit(todo_idx);
552-
} else {
553-
app.todo_view_dialog.open();
566+
ui.separator();
567+
ui.label("Backlinks");
568+
ui.horizontal(|ui| {
569+
for tab in [
570+
BacklinkTab::LinkedTodos,
571+
BacklinkTab::RelatedNotes,
572+
BacklinkTab::Mentions,
573+
] {
574+
if ui
575+
.selectable_label(self.backlink_tab == tab, tab.label())
576+
.clicked()
577+
{
578+
self.backlink_tab = tab;
579+
self.backlink_page = 0;
580+
}
581+
}
582+
});
583+
let rows = self.backlink_rows_for_active_tab();
584+
let total_pages = (rows.len() + BACKLINK_PAGE_SIZE - 1) / BACKLINK_PAGE_SIZE;
585+
let page_start = self.backlink_page * BACKLINK_PAGE_SIZE;
586+
let page_end = (page_start + BACKLINK_PAGE_SIZE).min(rows.len());
587+
if rows.is_empty() {
588+
ui.small("No backlinks in this category.");
589+
} else {
590+
for (idx, row) in rows[page_start..page_end].iter().enumerate() {
591+
ui.push_id(("backlink_row", idx, page_start), |ui| {
592+
let resp = ui.selectable_label(false, &row.title);
593+
if resp.clicked() {
594+
if let Some(slug) = &row.note_slug {
595+
app.open_note_panel(slug, None);
596+
} else if let Some(todo_id) = &row.todo_id {
597+
let todos = load_todos(TODO_FILE).unwrap_or_default();
598+
if let Some((todo_idx, _)) =
599+
todos.iter().enumerate().find(|(_, t)| &t.id == todo_id)
600+
{
601+
app.todo_view_dialog.open_edit(todo_idx);
602+
} else {
603+
app.todo_view_dialog.open();
604+
}
554605
}
555606
}
556-
}
557-
if resp.has_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) {
558-
if let Some(slug) = &row.note_slug {
559-
app.open_note_panel(slug, None);
607+
if resp.has_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter))
608+
{
609+
if let Some(slug) = &row.note_slug {
610+
app.open_note_panel(slug, None);
611+
}
560612
}
561-
}
562-
ui.horizontal_wrapped(|ui| {
563-
ui.small(format!("[{}]", row.type_badge));
564-
ui.small(format!("updated {}", row.updated));
613+
ui.horizontal_wrapped(|ui| {
614+
ui.small(format!("[{}]", row.type_badge));
615+
ui.small(format!("updated {}", row.updated));
616+
});
617+
ui.small(&row.snippet);
565618
});
566-
ui.small(&row.snippet);
567-
});
568-
ui.separator();
569-
}
570-
if total_pages > 1 {
571-
ui.horizontal(|ui| {
572-
if ui.button("Prev").clicked() && self.backlink_page > 0 {
573-
self.backlink_page -= 1;
574-
}
575-
ui.small(format!("Page {}/{}", self.backlink_page + 1, total_pages));
576-
if ui.button("Next").clicked() && self.backlink_page + 1 < total_pages {
577-
self.backlink_page += 1;
578-
}
579-
});
619+
ui.separator();
620+
}
621+
if total_pages > 1 {
622+
ui.horizontal(|ui| {
623+
if ui.button("Prev").clicked() && self.backlink_page > 0 {
624+
self.backlink_page -= 1;
625+
}
626+
ui.small(format!(
627+
"Page {}/{}",
628+
self.backlink_page + 1,
629+
total_pages
630+
));
631+
if ui.button("Next").clicked()
632+
&& self.backlink_page + 1 < total_pages
633+
{
634+
self.backlink_page += 1;
635+
}
636+
});
637+
}
580638
}
639+
ui.separator();
581640
}
582-
ui.separator();
583641
let remaining = ui.available_height();
642+
#[cfg(test)]
643+
{
644+
self.last_ui_sections.content_visible = true;
645+
}
584646
let resp = egui::ScrollArea::vertical()
585647
.id_source(scroll_id_source)
586648
.max_height(remaining)
@@ -1774,6 +1836,12 @@ mod tests {
17741836
}
17751837
}
17761838

1839+
fn render_panel_once(ctx: &egui::Context, panel: &mut NotePanel, app: &mut LauncherApp) {
1840+
let _ = ctx.run(Default::default(), |ctx| {
1841+
panel.ui(ctx, app);
1842+
});
1843+
}
1844+
17771845
#[test]
17781846
fn wrap_selection_preserves_range() {
17791847
let ctx = egui::Context::default();
@@ -2068,6 +2136,50 @@ mod tests {
20682136
assert!(mentions.len() >= 1);
20692137
}
20702138

2139+
#[test]
2140+
fn toggle_hides_metadata_sections_in_ui() {
2141+
let ctx = egui::Context::default();
2142+
let mut app = new_app(&ctx);
2143+
let mut panel = NotePanel::from_note(empty_note(
2144+
"#tag [[linked-note]] https://example.com\n\nBody visible always",
2145+
));
2146+
panel.preview_mode = false;
2147+
2148+
render_panel_once(&ctx, &mut panel, &mut app);
2149+
assert!(panel.last_ui_sections.tags_visible);
2150+
assert!(panel.last_ui_sections.links_visible);
2151+
assert!(panel.last_ui_sections.backlinks_visible);
2152+
assert!(panel.last_ui_sections.content_visible);
2153+
2154+
panel.show_metadata = false;
2155+
render_panel_once(&ctx, &mut panel, &mut app);
2156+
assert!(!panel.last_ui_sections.tags_visible);
2157+
assert!(!panel.last_ui_sections.links_visible);
2158+
assert!(!panel.last_ui_sections.backlinks_visible);
2159+
assert!(panel.last_ui_sections.content_visible);
2160+
}
2161+
2162+
#[test]
2163+
fn toggle_button_label_reflects_state() {
2164+
let mut panel = NotePanel::from_note(empty_note("body"));
2165+
assert_eq!(panel.details_toggle_label(), "Hide Details");
2166+
panel.show_metadata = false;
2167+
assert_eq!(panel.details_toggle_label(), "Show Details");
2168+
}
2169+
2170+
#[test]
2171+
fn toggle_preserves_tab_and_pagination_state() {
2172+
let mut panel = NotePanel::from_note(empty_note("body"));
2173+
panel.backlink_tab = BacklinkTab::Mentions;
2174+
panel.backlink_page = 2;
2175+
2176+
panel.show_metadata = false;
2177+
panel.show_metadata = true;
2178+
2179+
assert_eq!(panel.backlink_tab, BacklinkTab::Mentions);
2180+
assert_eq!(panel.backlink_page, 2);
2181+
}
2182+
20712183
#[test]
20722184
fn derived_metadata_is_reused_without_save() {
20732185
let ctx = egui::Context::default();

0 commit comments

Comments
 (0)