//! Upload screen — staged files, metadata editing, publish flow. use ratatui::Frame; use ratatui::layout::{Constraint, Layout}; use ratatui::style::{Color, Modifier, Style}; use ratatui::text::{Line, Span}; use ratatui::widgets::{Block, Borders, Paragraph, Row}; use crate::format; use crate::staging; use super::App; use super::widgets; pub fn render(frame: &mut Frame, app: &App) { let area = frame.area(); let title = Line::from(vec![ Span::styled(" Makenot.work ", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" ── "), Span::styled("Upload", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" "), ]); let block = Block::default() .title(title) .borders(Borders::ALL) .border_style(Style::default().fg(Color::Gray)); let inner = block.inner(area); frame.render_widget(block, area); let chunks = Layout::vertical([ Constraint::Length(1), // spacer Constraint::Length(1), // storage info Constraint::Length(1), // spacer Constraint::Length(1), // section header Constraint::Min(3), // file list Constraint::Length(1), // status line Constraint::Length(1), // keybindings ]) .split(inner); // Storage info render_storage_line(frame, app, chunks[1]); // Section header let file_count = app.staged_files.len(); let header = Paragraph::new(Line::from(vec![ Span::raw(" "), Span::styled("Staged Files", Style::default().add_modifier(Modifier::BOLD)), if file_count == 0 { Span::raw("") } else { Span::raw(format!(" ({})", file_count)) }, ])); frame.render_widget(header, chunks[3]); // File list if app.loading { let loading = Paragraph::new(" Loading..."); frame.render_widget(loading, chunks[4]); } else if app.staged_files.is_empty() { let empty = Paragraph::new(Line::from(vec![ Span::raw(" No staged files. Upload with: "), Span::styled( "scp file.wav cli.makenot.work:upload/", Style::default().add_modifier(Modifier::BOLD), ), ])); frame.render_widget(empty, chunks[4]); } else { render_file_table(frame, app, chunks[4]); } // Status line (publish progress, errors, etc.) if let Some(ref status) = app.upload_status { let style = if status.starts_with("Error") { Style::default().fg(Color::Red) } else if status.starts_with("Published") { Style::default().fg(Color::Green) } else { Style::default().fg(Color::Yellow) }; let status_line = Paragraph::new(Line::from(vec![ Span::raw(" "), Span::styled(status.as_str(), style), ])); frame.render_widget(status_line, chunks[5]); } // Keybindings let mut key_spans = vec![ Span::raw(" "), Span::styled("[j/k]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Nav "), ]; if app.editing_field.is_some() { key_spans.extend([ Span::styled("[Enter]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Confirm "), Span::styled("[Esc]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Cancel"), ]); } else { if !app.staged_files.is_empty() { key_spans.extend([ Span::styled("[e]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Edit "), Span::styled("[p]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Publish "), Span::styled("[d]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Delete "), ]); } key_spans.extend([ Span::styled("[r]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Refresh "), Span::styled("[Esc]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Back"), ]); } let keys = Paragraph::new(Line::from(key_spans)).style(Style::default().fg(Color::DarkGray)); frame.render_widget(keys, chunks[6]); } fn render_storage_line(frame: &mut Frame, app: &App, area: ratatui::layout::Rect) { let text = if let Some(ref info) = app.storage_info { let used = staging::format_bytes(info.storage_used_bytes as u64); let max = staging::format_bytes(info.max_storage_bytes as u64); let tier_label = app .user .creator_tier .as_deref() .map(format::format_tier) .unwrap_or("No tier"); if info.allows_file_uploads { format!(" Storage: {} / {} ({})", used, max, tier_label) } else { format!(" {} -- file uploads not available", tier_label) } } else { " Storage: --".to_string() }; frame.render_widget(Paragraph::new(text), area); } fn render_file_table(frame: &mut Frame, app: &App, area: ratatui::layout::Rect) { let selected = app.selected_index; let rows: Vec = app .staged_files .iter() .enumerate() .map(|(i, sf)| { let file_type = sf .classification .map(|c| c.item_type) .unwrap_or("?"); // Check if user has edited metadata for this file let meta = app.file_metadata.get(i); let title = meta .and_then(|m| m.title.clone()) .unwrap_or_else(|| staging::derive_title(&sf.filename)); let project = meta .and_then(|m| m.project_name.as_deref()) .unwrap_or("[none]"); let price = meta .map(|m| format::format_price(m.price_cents)) .unwrap_or_else(|| "Free".to_string()); // Show edit indicator for editing field let editing_marker = if app.editing_field.is_some() && i == selected { "*" } else { " " }; Row::new(vec![ format!("{}{}", editing_marker, sf.filename), staging::format_bytes(sf.size), file_type.to_string(), title.to_string(), project.to_string(), price, ]) .style(widgets::selected_style(i, Some(app.selected_index))) }) .collect(); let widths = [ Constraint::Min(16), Constraint::Length(10), Constraint::Length(8), Constraint::Length(16), Constraint::Length(14), Constraint::Length(8), ]; widgets::render_table(frame, area, &[" File", "Size", "Type", "Title", "Project", "Price"], &widths, rows); }