From b3c243257daa2e4281b36b436f2b65e1575dd55e Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Thu, 7 May 2026 00:35:03 +0200 Subject: [PATCH] feat: enhance diary navigation with LoadPrev action and pagination hints --- Cargo.lock | 19 +++++++++++++++++-- crates/tui/Cargo.toml | 2 +- crates/tui/src/app.rs | 31 ++++++++++++++++++++++++++----- crates/tui/src/main.rs | 9 +++++++++ crates/tui/src/ui.rs | 12 +++++++----- 5 files changed, 60 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 145e929..333c3af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1708,6 +1708,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" dependencies = [ "log", + "security-framework 2.11.1", + "security-framework 3.7.0", "zeroize", ] @@ -2887,7 +2889,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.7.0", ] [[package]] @@ -2915,7 +2917,7 @@ dependencies = [ "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki", - "security-framework", + "security-framework 3.7.0", "security-framework-sys", "webpki-root-certs", "windows-sys 0.61.2", @@ -2975,6 +2977,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.11.1", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + [[package]] name = "security-framework" version = "3.7.0" diff --git a/crates/tui/Cargo.toml b/crates/tui/Cargo.toml index 0e5edeb..ca4cc07 100644 --- a/crates/tui/Cargo.toml +++ b/crates/tui/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [dependencies] ratatui = "0.30.0" -keyring = "3" +keyring = { version = "3", features = ["apple-native"] } directories = "6" csv = "1" diff --git a/crates/tui/src/app.rs b/crates/tui/src/app.rs index 3a96f2f..d088129 100644 --- a/crates/tui/src/app.rs +++ b/crates/tui/src/app.rs @@ -173,7 +173,7 @@ pub enum Action { SetupSubmit, InputChar(char), Backspace, FocusNext, FocusPrev, LoginSubmit, - ScrollDown, ScrollUp, OpenHistory, LoadMore, + ScrollDown, ScrollUp, OpenHistory, LoadMore, LoadPrev, DeleteInit, DeleteConfirm, DeleteCancel, RatingUp, RatingDown, ReviewSubmit, BulkParseFile, BulkImportAll, BulkCancel, @@ -301,7 +301,7 @@ pub fn update(app: &mut App, action: Action) -> Vec { m.bulk_import.stage = BulkImportStage::EnterPath; } } - _ => {} + Tab::AddReview | Tab::Settings => { m.tab = Tab::Diary; } } } vec![] @@ -435,8 +435,14 @@ pub fn update(app: &mut App, action: Action) -> Vec { return vec![]; } app.api_url = url.clone(); - app.screen = Screen::Login(LoginState::default()); - return vec![Command::SaveConfig(url)]; + let cmds = if app.token.is_some() { + app.screen = Screen::Main(MainState::new(url.clone())); + vec![Command::SaveConfig(url), Command::LoadDiary { offset: 0 }] + } else { + app.screen = Screen::Login(LoginState::default()); + vec![Command::SaveConfig(url)] + }; + return cmds; } vec![] } @@ -515,6 +521,17 @@ pub fn update(app: &mut App, action: Action) -> Vec { vec![] } + Action::LoadPrev => { + if let Screen::Main(m) = &mut app.screen { + if m.diary.offset > 0 { + let prev = m.diary.offset.saturating_sub(20); + m.diary.offset = prev; + return vec![Command::LoadDiary { offset: prev }]; + } + } + vec![] + } + Action::DiaryLoaded { entries, total } => { app.loading = false; if let Screen::Main(m) = &mut app.screen { @@ -701,7 +718,11 @@ pub fn update(app: &mut App, action: Action) -> Vec { Action::BulkCancel => { if let Screen::Main(m) = &mut app.screen { - m.bulk_import = BulkImportState::default(); + if m.bulk_import.stage == BulkImportStage::EnterPath { + m.tab = Tab::Diary; + } else { + m.bulk_import = BulkImportState::default(); + } } vec![] } diff --git a/crates/tui/src/main.rs b/crates/tui/src/main.rs index fa691d8..025ec12 100644 --- a/crates/tui/src/main.rs +++ b/crates/tui/src/main.rs @@ -246,6 +246,7 @@ fn key_to_action(app: &App, key: ratatui::crossterm::event::KeyEvent) -> Option< KeyCode::Tab => Some(Action::TabNext), KeyCode::BackTab => Some(Action::TabPrev), KeyCode::Char('>') | KeyCode::Char('m') => Some(Action::LoadMore), + KeyCode::Char('<') | KeyCode::Char('b') => Some(Action::LoadPrev), KeyCode::Char('1') => Some(Action::TabSelect(Tab::Diary)), KeyCode::Char('2') => Some(Action::TabSelect(Tab::AddReview)), KeyCode::Char('3') => Some(Action::TabSelect(Tab::BulkImport)), @@ -279,6 +280,10 @@ fn key_to_action(app: &App, key: ratatui::crossterm::event::KeyEvent) -> Option< KeyCode::Tab if !in_path => Some(Action::TabNext), KeyCode::BackTab if !in_path => Some(Action::TabPrev), KeyCode::Char('q') if !in_path => Some(Action::Quit), + KeyCode::Char('1') if !in_path => Some(Action::TabSelect(Tab::Diary)), + KeyCode::Char('2') if !in_path => Some(Action::TabSelect(Tab::AddReview)), + KeyCode::Char('3') if !in_path => Some(Action::TabSelect(Tab::BulkImport)), + KeyCode::Char('4') if !in_path => Some(Action::TabSelect(Tab::Settings)), _ => None, } } @@ -296,6 +301,10 @@ fn key_to_action(app: &App, key: ratatui::crossterm::event::KeyEvent) -> Option< }, KeyCode::Esc => Some(Action::Escape), KeyCode::Char('q') => Some(Action::Quit), + KeyCode::Char('1') if !on_url => Some(Action::TabSelect(Tab::Diary)), + KeyCode::Char('2') if !on_url => Some(Action::TabSelect(Tab::AddReview)), + KeyCode::Char('3') if !on_url => Some(Action::TabSelect(Tab::BulkImport)), + KeyCode::Char('4') if !on_url => Some(Action::TabSelect(Tab::Settings)), _ => None, } } diff --git a/crates/tui/src/ui.rs b/crates/tui/src/ui.rs index d98d04e..ac58bde 100644 --- a/crates/tui/src/ui.rs +++ b/crates/tui/src/ui.rs @@ -215,11 +215,13 @@ fn draw_diary(frame: &mut Frame, area: Rect, state: &DiaryState) { .collect(); let can_load_more = (state.offset as u64 + state.entries.len() as u64) < state.total; - let list_title = if can_load_more { - format!(" Diary ({} entries) [m: load more] ", state.total) - } else { - format!(" Diary ({} entries) ", state.total) - }; + let can_load_prev = state.offset > 0; + let page = state.offset / 20 + 1; + let total_pages = state.total.div_ceil(20).max(1); + let mut hints = format!(" Diary ({} entries, page {}/{}) ", state.total, page, total_pages); + if can_load_prev { hints.push_str("[b: prev] "); } + if can_load_more { hints.push_str("[m: next] "); } + let list_title = hints; let mut list_state = ListState::default(); list_state.select(Some(state.selected)); let list = List::new(items).block(Block::default().title(list_title).borders(Borders::ALL));