pinakes-tui: add book management view and api key authentication
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I20f205d9e06a93a89e8f4433ed6f80576a6a6964
This commit is contained in:
parent
3d9f8933d2
commit
66861b8a20
18 changed files with 917 additions and 251 deletions
|
|
@ -1,4 +1,5 @@
|
|||
pub mod audit;
|
||||
pub mod books;
|
||||
pub mod collections;
|
||||
pub mod database;
|
||||
pub mod detail;
|
||||
|
|
@ -24,6 +25,10 @@ use ratatui::{
|
|||
use crate::app::{AppState, View};
|
||||
|
||||
/// Format a file size in bytes into a human-readable string.
|
||||
#[expect(
|
||||
clippy::cast_precision_loss,
|
||||
reason = "file sizes beyond 2^52 bytes are unlikely in practice"
|
||||
)]
|
||||
pub fn format_size(bytes: u64) -> String {
|
||||
if bytes < 1024 {
|
||||
format!("{bytes} B")
|
||||
|
|
@ -37,6 +42,11 @@ pub fn format_size(bytes: u64) -> String {
|
|||
}
|
||||
|
||||
/// Format duration in seconds into hh:mm:ss format.
|
||||
#[expect(
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::cast_sign_loss,
|
||||
reason = "duration seconds are always non-negative and within u64 range"
|
||||
)]
|
||||
pub fn format_duration(secs: f64) -> String {
|
||||
let total = secs as u64;
|
||||
let h = total / 3600;
|
||||
|
|
@ -98,6 +108,7 @@ pub fn render(f: &mut Frame, state: &AppState) {
|
|||
View::Queue => queue::render(f, state, chunks[1]),
|
||||
View::Statistics => statistics::render(f, state, chunks[1]),
|
||||
View::Tasks => tasks::render(f, state, chunks[1]),
|
||||
View::Books => books::render(f, state, chunks[1]),
|
||||
}
|
||||
|
||||
render_status_bar(f, state, chunks[2]);
|
||||
|
|
@ -110,6 +121,7 @@ fn render_tabs(f: &mut Frame, state: &AppState, area: Rect) {
|
|||
"Tags",
|
||||
"Collections",
|
||||
"Audit",
|
||||
"Books",
|
||||
"Queue",
|
||||
"Stats",
|
||||
"Tasks",
|
||||
|
|
@ -128,9 +140,10 @@ fn render_tabs(f: &mut Frame, state: &AppState, area: Rect) {
|
|||
View::Tags => 2,
|
||||
View::Collections => 3,
|
||||
View::Audit | View::Duplicates | View::Database => 4,
|
||||
View::Queue => 5,
|
||||
View::Statistics => 6,
|
||||
View::Tasks => 7,
|
||||
View::Books => 5,
|
||||
View::Queue => 6,
|
||||
View::Statistics => 7,
|
||||
View::Tasks => 8,
|
||||
};
|
||||
|
||||
let tabs = Tabs::new(titles)
|
||||
|
|
@ -147,50 +160,60 @@ fn render_tabs(f: &mut Frame, state: &AppState, area: Rect) {
|
|||
}
|
||||
|
||||
fn render_status_bar(f: &mut Frame, state: &AppState, area: Rect) {
|
||||
let status = if let Some(ref msg) = state.status_message {
|
||||
msg.clone()
|
||||
} else {
|
||||
match state.current_view {
|
||||
View::Tags => {
|
||||
" q:Quit j/k:Nav Home/End:Top/Bot n:New d:Delete r:Refresh \
|
||||
Tab:Switch"
|
||||
.to_string()
|
||||
},
|
||||
View::Collections => {
|
||||
" q:Quit j/k:Nav Home/End:Top/Bot d:Delete r:Refresh Tab:Switch"
|
||||
.to_string()
|
||||
},
|
||||
View::Audit => {
|
||||
" q:Quit j/k:Nav Home/End:Top/Bot r:Refresh Tab:Switch".to_string()
|
||||
},
|
||||
View::Detail => {
|
||||
" q:Quit Esc:Back o:Open e:Edit +:Tag -:Untag r:Refresh ?:Help"
|
||||
.to_string()
|
||||
},
|
||||
View::Import => {
|
||||
" Enter:Import Esc:Cancel s:Scan libraries ?:Help".to_string()
|
||||
},
|
||||
View::Settings => " q:Quit Esc:Back ?:Help".to_string(),
|
||||
View::Duplicates => " q:Quit j/k:Nav r:Refresh Esc:Back".to_string(),
|
||||
View::Database => " q:Quit v:Vacuum r:Refresh Esc:Back".to_string(),
|
||||
View::MetadataEdit => {
|
||||
" Tab:Next field Enter:Save Esc:Cancel".to_string()
|
||||
},
|
||||
View::Queue => {
|
||||
" q:Quit j/k:Nav Enter:Play d:Remove N:Next P:Prev R:Repeat \
|
||||
S:Shuffle C:Clear"
|
||||
.to_string()
|
||||
},
|
||||
View::Statistics => " q:Quit r:Refresh Esc:Back ?:Help".to_string(),
|
||||
View::Tasks => {
|
||||
" q:Quit j/k:Nav Enter:Toggle R:Run Now r:Refresh Esc:Back"
|
||||
.to_string()
|
||||
},
|
||||
_ => " q:Quit /:Search i:Import o:Open t:Tags c:Coll a:Audit \
|
||||
D:Dupes B:DB Q:Queue X:Stats T:Tasks ?:Help"
|
||||
.to_string(),
|
||||
}
|
||||
};
|
||||
let status = state.status_message.as_ref().map_or_else(
|
||||
|| {
|
||||
match state.current_view {
|
||||
View::Tags => {
|
||||
" q:Quit j/k:Nav Home/End:Top/Bot n:New d:Delete r:Refresh \
|
||||
Tab:Switch"
|
||||
.to_string()
|
||||
},
|
||||
View::Collections => {
|
||||
" q:Quit j/k:Nav Home/End:Top/Bot d:Delete r:Refresh Tab:Switch"
|
||||
.to_string()
|
||||
},
|
||||
View::Audit => {
|
||||
" q:Quit j/k:Nav Home/End:Top/Bot r:Refresh Tab:Switch"
|
||||
.to_string()
|
||||
},
|
||||
View::Detail => {
|
||||
" q:Quit Esc:Back o:Open e:Edit p:Page +:Tag -:Untag \
|
||||
r:Refresh ?:Help"
|
||||
.to_string()
|
||||
},
|
||||
View::Import => {
|
||||
" Enter:Import Esc:Cancel s:Scan libraries ?:Help".to_string()
|
||||
},
|
||||
View::Settings => " q:Quit Esc:Back ?:Help".to_string(),
|
||||
View::Duplicates => " q:Quit j/k:Nav r:Refresh Esc:Back".to_string(),
|
||||
View::Database => " q:Quit v:Vacuum r:Refresh Esc:Back".to_string(),
|
||||
View::MetadataEdit => {
|
||||
" Tab:Next field Enter:Save Esc:Cancel".to_string()
|
||||
},
|
||||
View::Queue => {
|
||||
" q:Quit j/k:Nav Enter:Play d:Remove N:Next P:Prev R:Repeat \
|
||||
S:Shuffle C:Clear"
|
||||
.to_string()
|
||||
},
|
||||
View::Statistics => " q:Quit r:Refresh Esc:Back ?:Help".to_string(),
|
||||
View::Tasks => {
|
||||
" q:Quit j/k:Nav Enter:Toggle R:Run Now r:Refresh Esc:Back"
|
||||
.to_string()
|
||||
},
|
||||
View::Books => {
|
||||
" q:Quit j/k:Nav Home/End:Top/Bot Tab:Sub-view r:Refresh \
|
||||
Esc:Back"
|
||||
.to_string()
|
||||
},
|
||||
_ => {
|
||||
" q:Quit /:Search i:Import o:Open t:Tags c:Coll a:Audit \
|
||||
b:Books D:Dupes B:DB Q:Queue X:Stats T:Tasks ?:Help"
|
||||
.to_string()
|
||||
},
|
||||
}
|
||||
},
|
||||
String::clone,
|
||||
);
|
||||
|
||||
let paragraph = Paragraph::new(Line::from(Span::styled(
|
||||
status,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue