use crate::tui::app::App;
use ratatui::{
prelude::{Alignment, Constraint, Direction, Layout, Margin, Rect},
style::{Color, Modifier, Style},
text::{Line, Span, Text},
widgets::{Block, Borders, Gauge, LineGauge, List, ListItem, Paragraph, Wrap},
Frame,
};
pub fn render(app: &mut App, frame: &mut Frame<'_>) {
let size = frame.size();
let main_chunks = Layout::default()
.direction(Direction::Vertical)
.margin(1)
.constraints([
Constraint::Length(3), Constraint::Min(0), Constraint::Length(3), ])
.split(size);
render_enhanced_header(app, frame, main_chunks[0]);
let body_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage(30), Constraint::Percentage(70), ])
.split(main_chunks[1]);
render_advanced_sidebar(app, frame, body_chunks[0]);
let content_chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Percentage(50), Constraint::Percentage(50), ])
.split(body_chunks[1]);
let top_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage(50), Constraint::Percentage(50), ])
.split(content_chunks[0]);
render_enhanced_project_analysis(app, frame, top_chunks[0]);
render_advanced_health_dashboard(app, frame, top_chunks[1]);
render_comprehensive_dependencies(app, frame, content_chunks[1]);
render_enhanced_footer(app, frame, main_chunks[2]);
}
fn render_enhanced_header(app: &App, frame: &mut Frame<'_>, area: Rect) {
let project_name = app.project_name();
let project_desc = app.project_description();
let title_line = Line::from(vec![
Span::styled("RUST ", Style::default().fg(Color::Red)),
Span::styled(
project_name.unwrap_or("No project"),
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
),
Span::styled(" | OpenCrates Dashboard", Style::default().fg(Color::White)),
]);
let subtitle_line = Line::from(vec![
Span::styled("Desc: ", Style::default().fg(Color::Yellow)),
Span::styled(
project_desc.unwrap_or("No description"),
Style::default().fg(Color::Gray),
),
]);
let text = Text::from(vec![title_line, subtitle_line]);
let header = Paragraph::new(text)
.block(
Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(Color::Cyan))
.title(" OpenCrates TUI Dashboard ")
.title_alignment(Alignment::Center),
)
.alignment(Alignment::Center)
.wrap(Wrap { trim: true });
frame.render_widget(header, area);
}
fn render_advanced_sidebar(app: &App, frame: &mut Frame<'_>, area: Rect) {
let inner_area = area.inner(&Margin {
horizontal: 1,
vertical: 1,
});
let sidebar_chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(5), Constraint::Length(4), Constraint::Min(0), ])
.split(inner_area);
let dep_count = app.dependency_count();
let health_status = app.health_status();
let stats_text = Text::from(vec![
Line::from(vec![
Span::styled("DEPS: ", Style::default().fg(Color::Blue)),
Span::styled(
dep_count.to_string(),
Style::default()
.fg(Color::White)
.add_modifier(Modifier::BOLD),
),
]),
Line::from(vec![
Span::styled("Health: ", Style::default().fg(Color::Green)),
Span::styled(
&health_status,
Style::default()
.fg(match health_status.as_str() {
"Healthy" => Color::Green,
"Warning" => Color::Yellow,
_ => Color::Red,
})
.add_modifier(Modifier::BOLD),
),
]),
Line::from(vec![
Span::styled("Status: ", Style::default().fg(Color::Magenta)),
Span::styled(
"Active",
Style::default()
.fg(Color::Green)
.add_modifier(Modifier::BOLD),
),
]),
]);
let stats_widget = Paragraph::new(stats_text).wrap(Wrap { trim: true });
frame.render_widget(stats_widget, sidebar_chunks[0]);
let health_ratio = match health_status.as_str() {
"Healthy" => 1.0,
"Warning" => 0.6,
_ => 0.3,
};
let gauge = LineGauge::default()
.block(Block::default().borders(Borders::ALL).title("Health"))
.gauge_style(Style::default().fg(match health_status.as_str() {
"Healthy" => Color::Green,
"Warning" => Color::Yellow,
_ => Color::Red,
}))
.line_set(ratatui::symbols::line::THICK)
.ratio(health_ratio);
frame.render_widget(gauge, sidebar_chunks[1]);
let menu_items = vec![
ListItem::new(Line::from(vec![
Span::styled("SEARCH ", Style::default().fg(Color::Yellow)),
Span::styled("Analyze Project", Style::default().fg(Color::White)),
])),
ListItem::new(Line::from(vec![
Span::styled("GEN ", Style::default().fg(Color::Blue)),
Span::styled("Generate Crate", Style::default().fg(Color::White)),
])),
ListItem::new(Line::from(vec![
Span::styled("METRICS ", Style::default().fg(Color::Green)),
Span::styled("View Metrics", Style::default().fg(Color::White)),
])),
ListItem::new(Line::from(vec![
Span::styled("CONFIG ", Style::default().fg(Color::Magenta)),
Span::styled("Settings", Style::default().fg(Color::White)),
])),
];
let menu = List::new(menu_items)
.block(Block::default().borders(Borders::ALL).title("Menu"))
.style(Style::default().fg(Color::White));
frame.render_widget(menu, sidebar_chunks[2]);
let sidebar_block = Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(Color::Blue))
.title(" System ");
frame.render_widget(sidebar_block, area);
}
fn render_enhanced_project_analysis(app: &App, frame: &mut Frame<'_>, area: Rect) {
let analysis_text = if let Some(analysis) = &app.analysis {
vec![
Line::from(vec![
Span::styled("Name: ", Style::default().fg(Color::Blue)),
Span::styled(
&analysis.name,
Style::default()
.fg(Color::White)
.add_modifier(Modifier::BOLD),
),
]),
Line::from(vec![
Span::styled("Path: ", Style::default().fg(Color::Blue)),
Span::styled(
analysis.path.display().to_string(),
Style::default().fg(Color::Gray),
),
]),
Line::from(vec![
Span::styled("Total Files: ", Style::default().fg(Color::Green)),
Span::styled(
analysis.metrics.total_files.to_string(),
Style::default().fg(Color::White),
),
]),
Line::from(vec![
Span::styled("Rust Files: ", Style::default().fg(Color::Red)),
Span::styled(
analysis.metrics.rust_files.to_string(),
Style::default().fg(Color::White),
),
]),
Line::from(vec![
Span::styled("Test Files: ", Style::default().fg(Color::Yellow)),
Span::styled(
analysis.metrics.test_files.to_string(),
Style::default().fg(Color::White),
),
]),
Line::from(vec![
Span::styled("Dependencies: ", Style::default().fg(Color::Magenta)),
Span::styled(
analysis.dependencies.len().to_string(),
Style::default().fg(Color::White),
),
]),
Line::from(vec![
Span::styled("Lines of Code: ", Style::default().fg(Color::Cyan)),
Span::styled(
analysis.metrics.total_lines.to_string(),
Style::default().fg(Color::White),
),
]),
]
} else {
vec![
Line::from(vec![
Span::styled("LOADING ", Style::default().fg(Color::Yellow)),
Span::styled(
"Loading project analysis...",
Style::default().fg(Color::Gray),
),
]),
Line::from(vec![
Span::styled("KEY ", Style::default().fg(Color::Blue)),
Span::styled("Press 'r' to refresh", Style::default().fg(Color::White)),
]),
]
};
let text = Text::from(analysis_text);
let paragraph = Paragraph::new(text)
.block(
Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(Color::Green))
.title(" Project Analysis "),
)
.wrap(Wrap { trim: true });
frame.render_widget(paragraph, area);
}
fn render_advanced_health_dashboard(app: &App, frame: &mut Frame<'_>, area: Rect) {
let inner_area = area.inner(&Margin {
horizontal: 1,
vertical: 1,
});
let health_chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(2), Constraint::Length(3), Constraint::Min(0), ])
.split(inner_area);
let health_status = app.health_status();
let status_color = match health_status.as_str() {
"Healthy" => Color::Green,
"Warning" => Color::Yellow,
_ => Color::Red,
};
let status_icon = match health_status.as_str() {
"Healthy" => "[OK]",
"Warning" => "[WARN]",
_ => "[ERROR]",
};
let status_text = Text::from(Line::from(vec![
Span::styled(
format!("{status_icon} Status: "),
Style::default().fg(Color::White),
),
Span::styled(
&health_status,
Style::default()
.fg(status_color)
.add_modifier(Modifier::BOLD),
),
]));
let status_paragraph = Paragraph::new(status_text);
frame.render_widget(status_paragraph, health_chunks[0]);
let health_ratio = match health_status.as_str() {
"Healthy" => 1.0,
"Warning" => 0.6,
_ => 0.2,
};
let gauge = Gauge::default()
.block(Block::default().borders(Borders::NONE))
.gauge_style(
Style::default()
.fg(status_color)
.add_modifier(Modifier::BOLD),
)
.ratio(health_ratio)
.label(format!("{}%", (health_ratio * 100.0) as u8));
frame.render_widget(gauge, health_chunks[1]);
if let Some(health) = &app.health {
let status_text = format!(
"Status: {:?}, Uptime: {}s",
health.overall_status,
health.uptime.as_secs()
);
let status_paragraph = Paragraph::new(status_text)
.block(Block::default().borders(Borders::ALL).title("Health"));
let details_text = Text::from(vec![
Line::from(vec![
Span::styled("Uptime: ", Style::default().fg(Color::Blue)),
Span::styled(
format!("{}s", health.uptime.as_secs()),
Style::default().fg(Color::White),
),
]),
Line::from(vec![
Span::styled("Version: ", Style::default().fg(Color::Blue)),
Span::styled("0.1.1", Style::default().fg(Color::White)),
]),
Line::from(vec![
Span::styled("Components: ", Style::default().fg(Color::Blue)),
Span::styled(
health.checks.len().to_string(),
Style::default().fg(Color::White),
),
]),
]);
let details_paragraph = Paragraph::new(details_text).wrap(Wrap { trim: true });
frame.render_widget(status_paragraph, health_chunks[0]);
frame.render_widget(details_paragraph, health_chunks[2]);
} else {
frame.render_widget(
Paragraph::new(Text::from(Line::from(vec![
Span::styled("LOADING ", Style::default().fg(Color::Yellow)),
Span::styled(
"Loading health information...",
Style::default().fg(Color::Gray),
),
])))
.wrap(Wrap { trim: true }),
health_chunks[2],
);
}
let block = Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(status_color))
.title(format!(" System Health ({health_status}) "));
frame.render_widget(block, area);
}
fn render_comprehensive_dependencies(app: &App, frame: &mut Frame<'_>, area: Rect) {
let inner_area = area.inner(&Margin {
horizontal: 1,
vertical: 1,
});
let dep_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage(60), Constraint::Percentage(40), ])
.split(inner_area);
let items: Vec<ListItem<'_>> = app
.dependencies
.iter()
.enumerate()
.map(|(i, dep)| {
let prefix = match i % 4 {
0 => "[PKG]",
1 => "[TOOL]",
2 => "[FAST]",
_ => "[BUILD]",
};
ListItem::new(Line::from(vec![
Span::styled(format!("{prefix} "), Style::default().fg(Color::Blue)),
Span::styled(dep, Style::default().fg(Color::White)),
]))
})
.collect();
let list = List::new(items).style(Style::default().fg(Color::White));
frame.render_widget(list, dep_chunks[0]);
let dep_count = app.dependency_count();
let stats_text = Text::from(vec![
Line::from(vec![
Span::styled("Total: ", Style::default().fg(Color::Green)),
Span::styled(
dep_count.to_string(),
Style::default()
.fg(Color::White)
.add_modifier(Modifier::BOLD),
),
]),
Line::from(vec![
Span::styled("Active: ", Style::default().fg(Color::Blue)),
Span::styled(dep_count.to_string(), Style::default().fg(Color::Green)),
]),
Line::from(vec![
Span::styled("Outdated: ", Style::default().fg(Color::Yellow)),
Span::styled("0", Style::default().fg(Color::Green)),
]),
Line::from(vec![
Span::styled("Secure: ", Style::default().fg(Color::Green)),
Span::styled(
"YES",
Style::default()
.fg(Color::Green)
.add_modifier(Modifier::BOLD),
),
]),
]);
let stats_widget = Paragraph::new(stats_text).wrap(Wrap { trim: true });
frame.render_widget(stats_widget, dep_chunks[1]);
let block = Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(Color::Magenta))
.title(format!(" Dependencies ({dep_count}) "));
frame.render_widget(block, area);
}
fn render_enhanced_footer(_app: &App, frame: &mut Frame<'_>, area: Rect) {
let footer_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage(70), Constraint::Percentage(30), ])
.split(area);
let controls_text = Text::from(Line::from(vec![
Span::styled(
"Controls: ",
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD),
),
Span::styled("[", Style::default().fg(Color::Gray)),
Span::styled(
"q",
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
),
Span::styled("] Quit [", Style::default().fg(Color::Gray)),
Span::styled(
"r",
Style::default()
.fg(Color::Green)
.add_modifier(Modifier::BOLD),
),
Span::styled("] Refresh [", Style::default().fg(Color::Gray)),
Span::styled(
"h",
Style::default()
.fg(Color::Blue)
.add_modifier(Modifier::BOLD),
),
Span::styled("] Help [", Style::default().fg(Color::Gray)),
Span::styled(
"↑↓",
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
),
Span::styled("] Navigate", Style::default().fg(Color::Gray)),
]));
let controls = Paragraph::new(controls_text)
.block(
Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(Color::Yellow)),
)
.wrap(Wrap { trim: true });
frame.render_widget(controls, footer_chunks[0]);
let status_text = Text::from(vec![
Line::from(vec![Span::styled(
"OpenCrates v0.1.1",
Style::default().fg(Color::Cyan),
)]),
Line::from(vec![Span::styled(
"Ready",
Style::default()
.fg(Color::Green)
.add_modifier(Modifier::BOLD),
)]),
]);
let status = Paragraph::new(status_text)
.block(
Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(Color::Green)),
)
.alignment(Alignment::Center);
frame.render_widget(status, footer_chunks[1]);
}