diff --git a/src/main.rs b/src/main.rs index 89bca23..d22aa23 100644 --- a/src/main.rs +++ b/src/main.rs @@ -190,21 +190,21 @@ impl Default for App { let (view_stack, config_status) = match &loaded { Some(cfg) if cfg.access_token.is_some() => ( vec![View::Categories], - format!("auth: token present ({})", cfg.base_url), + format!("Connected: ({})", cfg.base_url), ), Some(cfg) => ( vec![View::Login], - format!("auth: needs login ({})", cfg.base_url), + format!("Not Connected: ({})", cfg.base_url), ), None => ( vec![View::Login], - "config: missing/invalid (need base_url)".to_string(), + "Missing Configuration".to_string(), ), }; Self { should_exit: false, - status: "Press q to quit".to_string(), + status: String::new(), config_status, config: loaded, @@ -486,7 +486,7 @@ fn handle_key(app: &mut App, code: KeyCode) { if !matches!(app.current_view(), View::Categories) { app.view_stack = vec![View::Categories]; app.selected = 0; - app.status = "Home".into(); + app.status = "$".into(); return; } else { app.should_exit = true; @@ -702,7 +702,7 @@ fn handle_key(app: &mut App, code: KeyCode) { page_size: PAGE_SIZE, loading: false, }); - app.status = "Loaded".into(); + app.status = "$".into(); } Err(e) => app.status = format!("Fetch failed: {e}"), } @@ -782,7 +782,7 @@ fn handle_key(app: &mut App, code: KeyCode) { page_size: PAGE_SIZE, loading: false, }); - app.status = "Loaded".into(); + app.status = "$".into(); } Err(e) => app.status = format!("Playlist load failed: {e}"), } @@ -791,7 +791,7 @@ fn handle_key(app: &mut App, code: KeyCode) { // Playables -> mpv // Playables: do nothing on Enter (use 'p' to play) if ty == "Episode" || ty == "Audio" || ty == "Movie" { - app.status = "Press 'p' to play".into(); + app.status = "Press 'p' to play, or 'P' for autoplay.".into(); return; } @@ -806,7 +806,7 @@ fn handle_key(app: &mut App, code: KeyCode) { KeyCode::Esc | KeyCode::Backspace => { if app.view_stack.len() > 1 { app.view_stack.pop(); - app.status = "Went back".to_string(); + app.status = "$".to_string(); app.selected = 0; } } @@ -816,7 +816,8 @@ fn handle_key(app: &mut App, code: KeyCode) { if app.items.is_empty() { return; } if app.selected == 0 { app.selected = app.items.len() - 1; } else { app.selected -= 1; } - app.status = format!("Selected: {}", app.items[app.selected]); + //app.status = format!("Selected: {}", app.items[app.selected]); + app.status = "$".into(); } View::Library { items, selected, total, .. } => { if items.is_empty() { return; } @@ -846,7 +847,8 @@ fn handle_key(app: &mut App, code: KeyCode) { View::Categories => { if app.items.is_empty() { return; } app.selected = (app.selected + 1) % app.items.len(); - app.status = format!("Selected: {}", app.items[app.selected]); + //app.status = format!("Selected: {}", app.items[app.selected]); + app.status = "$".into(); } View::Library { items, selected, total, .. } => { if items.is_empty() { return; } @@ -1125,9 +1127,29 @@ fn ui(frame: &mut Frame, app: &App) { let panes = layout(area); // Top + //frame.render_widget( + // Paragraph::new("cj-jftui").block(Block::bordered()), + // panes.top, + //); + + let top_block = Block::bordered(); + frame.render_widget(top_block.clone(), panes.top); + + let top_inner = top_block.inner(panes.top); + let [top_l, top_r] = Layout::horizontal([Constraint::Fill(1), Constraint::Length(40)]) + .areas(top_inner); + + frame.render_widget(Paragraph::new("cj-jftui"), top_l); + let server = app + .config + .as_ref() + .map(|c| c.base_url.clone()) + .unwrap_or_else(|| "-".into()); + frame.render_widget( - Paragraph::new("cj-jftui").block(Block::bordered()), - panes.top, + Paragraph::new(server) + .alignment(ratatui::layout::Alignment::Right), + top_r, ); // Main @@ -1163,44 +1185,60 @@ fn ui(frame: &mut Frame, app: &App) { } // Bottom Left - let status_text = if let Some(s) = &app.search { + // ---- Bottom Left: prompt / mpv ---- + let left_text = if let Some(s) = &app.search { + // search takes over temporarily let scope = match &s.scope { SearchScope::All => "All".to_string(), SearchScope::Library { title, .. } => title.clone(), }; - let mode = s.mode.label(); - if s.input.is_empty() { format!("/ ({scope}) [{mode}]") } else { format!("/ ({scope}) [{mode}] {}", s.input) } + } else if app.mpv.is_some() { + // mpv persists while browsing + if app.debug_mpv { + fmt_mpv_state(&app.mpv_state) + } else if let Some(p) = app.mpv_state.percent { + if p.is_finite() { format!("mpv: {p:5.1}%") } else { "mpv: playing".into() } + } else { + "mpv: playing".into() + } } else { - app.status.clone() + "$".into() }; frame.render_widget( - Paragraph::new(status_text) - .block(Block::bordered().title("Status").merge_borders(MergeStrategy::Exact)), + Paragraph::new(left_text) + // no title + .block(Block::bordered().merge_borders(MergeStrategy::Exact)), panes.left_output, ); - - // Bottom Right - let info = match app.current_view() { - View::Library { items, total, loading, .. } => { - if *loading { - format!("loading… {}/{}", items.len(), total) - } else { - format!("{}/{}", items.len(), total) - } - } - _ => app.config_status.clone(), + + // ---- Bottom Right: always-on status/help ---- + let connected = if app.config.as_ref().and_then(|c| c.access_token.as_ref()).is_some() { + "Connected ✅" + } else { + "Disconnected ❌" }; + let server = app + .config + .as_ref() + .map(|c| c.base_url.as_str()) + .unwrap_or("-"); + + let right_text = format!( + "{connected}\n\np/P: play/autoplay\nd: download\nq: quit\n/help: more help" + ); + frame.render_widget( - Paragraph::new(info) - .block(Block::bordered().title("Info").merge_borders(MergeStrategy::Exact)), + Paragraph::new(right_text) + .wrap(Wrap { trim: false }) + .block(Block::bordered().merge_borders(MergeStrategy::Exact)), panes.right_output, ); } @@ -1247,15 +1285,9 @@ fn render_categories(frame: &mut Frame, app: &App, left: Rect, right: Rect) { frame.render_stateful_widget(list, left, &mut state); // Right pane details (use the real selected item) - let detail = if len > 0 { - format!("Details for:\n\n{}", app.items[selected]) - } else { - "No items".to_string() - }; - frame.render_widget( - Paragraph::new(detail) - .block(Block::bordered().title("Details").merge_borders(MergeStrategy::Exact)), + Paragraph::new("") + .block(Block::bordered().merge_borders(MergeStrategy::Exact)), right, ); } @@ -1399,7 +1431,7 @@ fn open_children(app: &mut App, title: String, query: core::jellyfin::LibraryQue page_size: PAGE_SIZE, loading: false, }); - app.status = "Loaded".into(); + app.status = "$".into(); } Err(e) => app.status = format!("Fetch failed: {e}"), } @@ -1694,7 +1726,7 @@ fn autoplay_tick(app: &mut App) { Some(i) => i, None => { app.autoplay = false; - app.status = "autoplay finished".into(); + app.status = "$".into(); return; } }; @@ -1704,7 +1736,7 @@ fn autoplay_tick(app: &mut App) { _ => { app.autoplay = false; app.autoplay_next_index = None; - app.status = "autoplay stopped".into(); + app.status = "$".into(); return; } }; @@ -1741,7 +1773,7 @@ fn autoplay_tick(app: &mut App) { app.autoplay_prev_time_pos = None; if !app.debug_mpv { - app.status = "autoplay next".into(); + app.status = "$".into(); } }