Skip to content

Commit 3dfd0fa

Browse files
committed
refactor(tui): extract shared detail rendering function
`draw_detail_overlay` now reuses `build_detail_lines` instead of duplicating ~150 lines of rendering logic. the shared function takes an optional `selected_dep` parameter to enable numbered prefixes and preview in the detail pane.
1 parent ddc7214 commit 3dfd0fa

File tree

1 file changed

+14
-154
lines changed

1 file changed

+14
-154
lines changed

src/tui/ui.rs

Lines changed: 14 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -1272,7 +1272,7 @@ fn draw_detail(f: &mut Frame, area: Rect, app: &mut App) {
12721272
.border_style(Style::default().fg(border_color));
12731273

12741274
// build the content lines first, collecting all needed data
1275-
let lines = build_detail_lines(app);
1275+
let lines = build_detail_lines(app, app.detail_dep_selected);
12761276

12771277
if lines.is_empty() {
12781278
let text = Paragraph::new("no issue selected").block(block.clone().title(" Detail "));
@@ -1324,7 +1324,8 @@ fn draw_detail(f: &mut Frame, area: Rect, app: &mut App) {
13241324
}
13251325

13261326
/// build content lines for the detail pane, returning empty vec if no issue selected.
1327-
fn build_detail_lines(app: &App) -> Vec<Line<'static>> {
1327+
/// `selected_dep` enables numbered prefixes and preview for the detail pane; pass None for overlay.
1328+
fn build_detail_lines(app: &App, selected_dep: Option<usize>) -> Vec<Line<'static>> {
13281329
let Some(issue) = app.selected_issue() else {
13291330
return Vec::new();
13301331
};
@@ -1397,19 +1398,22 @@ fn build_detail_lines(app: &App) -> Vec<Line<'static>> {
13971398

13981399
// deps
13991400
if !issue.deps().is_empty() {
1400-
let selected_dep = app.detail_dep_selected;
14011401
lines.push(Line::from(""));
14021402
lines.push(Line::from(Span::styled(
14031403
"Dependencies:",
14041404
Style::default().fg(Color::DarkGray),
14051405
)));
14061406
for (idx, dep_id) in issue.deps().iter().enumerate() {
1407-
let is_selected = Some(idx) == selected_dep;
1408-
// show number for selection (1-9), or bullet for overflow
1409-
let prefix = if idx < 9 {
1410-
format!("{}", idx + 1)
1407+
let is_selected = selected_dep == Some(idx);
1408+
// show number for selection (1-9) when selection enabled, otherwise bullet
1409+
let prefix = if selected_dep.is_some() {
1410+
if idx < 9 {
1411+
format!("{}", idx + 1)
1412+
} else {
1413+
"-".to_string()
1414+
}
14111415
} else {
1412-
"-".to_string()
1416+
" ".to_string()
14131417
};
14141418

14151419
let (symbol, status_text, base_color) = if let Some(dep_issue) = app.issues.get(dep_id)
@@ -1536,155 +1540,11 @@ fn draw_detail_overlay(f: &mut Frame, area: Rect, app: &App) {
15361540
.borders(Borders::ALL)
15371541
.border_style(Style::default().fg(Color::Yellow));
15381542

1539-
let Some(issue) = app.selected_issue() else {
1543+
let lines = build_detail_lines(app, None);
1544+
if lines.is_empty() {
15401545
let text = Paragraph::new("no issue selected").block(block);
15411546
f.render_widget(text, overlay_area);
15421547
return;
1543-
};
1544-
1545-
let derived = app.derived_state(issue);
1546-
1547-
let mut lines: Vec<Line> = vec![
1548-
Line::from(vec![
1549-
Span::styled("ID: ", Style::default().fg(Color::DarkGray)),
1550-
Span::raw(issue.id()),
1551-
]),
1552-
Line::from(vec![
1553-
Span::styled("Title: ", Style::default().fg(Color::DarkGray)),
1554-
Span::raw(issue.title()),
1555-
]),
1556-
Line::from(vec![
1557-
Span::styled("Priority: ", Style::default().fg(Color::DarkGray)),
1558-
Span::raw(issue.priority().to_string()),
1559-
]),
1560-
Line::from(vec![
1561-
Span::styled("Status: ", Style::default().fg(Color::DarkGray)),
1562-
Span::styled(
1563-
issue.status().to_string(),
1564-
match issue.status() {
1565-
crate::issue::Status::Done => Style::default().fg(Color::Green),
1566-
crate::issue::Status::Doing => Style::default().fg(Color::Yellow),
1567-
crate::issue::Status::Open => Style::default(),
1568-
crate::issue::Status::Skip => Style::default().fg(Color::DarkGray),
1569-
},
1570-
),
1571-
]),
1572-
];
1573-
1574-
if let Some(owner) = &issue.frontmatter.owner {
1575-
lines.push(Line::from(vec![
1576-
Span::styled("Owner: ", Style::default().fg(Color::DarkGray)),
1577-
Span::raw(owner.as_str()),
1578-
]));
1579-
}
1580-
1581-
// tags
1582-
if !issue.frontmatter.tags.is_empty() {
1583-
let tags = issue
1584-
.frontmatter
1585-
.tags
1586-
.iter()
1587-
.map(|t| format!("#{}", t))
1588-
.collect::<Vec<_>>()
1589-
.join(" ");
1590-
lines.push(Line::from(vec![
1591-
Span::styled("Tags: ", Style::default().fg(Color::DarkGray)),
1592-
Span::styled(tags, Style::default().fg(Color::Cyan)),
1593-
]));
1594-
}
1595-
1596-
// state
1597-
let state_text = if derived.is_ready {
1598-
Span::styled("READY", Style::default().fg(Color::Green))
1599-
} else if derived.is_blocked {
1600-
Span::styled("BLOCKED", Style::default().fg(Color::Red))
1601-
} else {
1602-
Span::raw("")
1603-
};
1604-
if !state_text.content.is_empty() {
1605-
lines.push(Line::from(vec![
1606-
Span::styled("State: ", Style::default().fg(Color::DarkGray)),
1607-
state_text,
1608-
]));
1609-
}
1610-
1611-
// deps
1612-
if !issue.deps().is_empty() {
1613-
lines.push(Line::from(""));
1614-
lines.push(Line::from(Span::styled(
1615-
"Dependencies:",
1616-
Style::default().fg(Color::DarkGray),
1617-
)));
1618-
for dep_id in issue.deps() {
1619-
let (symbol, status_text, base_color) = if let Some(dep_issue) = app.issues.get(dep_id)
1620-
{
1621-
match dep_issue.status() {
1622-
Status::Done => ("✓", "done", Color::Green),
1623-
Status::Skip => ("⊘", "skip", Color::DarkGray),
1624-
Status::Doing => ("→", "doing", Color::Yellow),
1625-
Status::Open => ("○", "open", Color::White),
1626-
}
1627-
} else {
1628-
("?", "missing", Color::Red)
1629-
};
1630-
1631-
lines.push(Line::from(Span::styled(
1632-
format!(" {} {} ({})", symbol, dep_id, status_text),
1633-
Style::default().fg(base_color),
1634-
)));
1635-
}
1636-
}
1637-
1638-
// dependents (reverse deps - issues that depend on this one)
1639-
let dependents = get_dependents(issue.id(), &app.issues);
1640-
if !dependents.is_empty() {
1641-
lines.push(Line::from(""));
1642-
lines.push(Line::from(Span::styled(
1643-
"Dependents:",
1644-
Style::default().fg(Color::DarkGray),
1645-
)));
1646-
for dep_id in &dependents {
1647-
let (symbol, status_text, base_color) = if let Some(dep_issue) = app.issues.get(dep_id)
1648-
{
1649-
match dep_issue.status() {
1650-
Status::Done => ("✓", "done", Color::Green),
1651-
Status::Skip => ("⊘", "skip", Color::DarkGray),
1652-
Status::Doing => ("→", "doing", Color::Yellow),
1653-
Status::Open => ("○", "open", Color::White),
1654-
}
1655-
} else {
1656-
("?", "missing", Color::Red)
1657-
};
1658-
1659-
lines.push(Line::from(Span::styled(
1660-
format!(" {} {} ({})", symbol, dep_id, status_text),
1661-
Style::default().fg(base_color),
1662-
)));
1663-
}
1664-
}
1665-
1666-
// acceptance
1667-
if !issue.frontmatter.acceptance.is_empty() {
1668-
lines.push(Line::from(""));
1669-
lines.push(Line::from(Span::styled(
1670-
"Acceptance:",
1671-
Style::default().fg(Color::DarkGray),
1672-
)));
1673-
for ac in &issue.frontmatter.acceptance {
1674-
lines.push(Line::from(format!(" - {}", ac)));
1675-
}
1676-
}
1677-
1678-
// body
1679-
if !issue.body.is_empty() {
1680-
lines.push(Line::from(""));
1681-
lines.push(Line::from(Span::styled(
1682-
"Description:",
1683-
Style::default().fg(Color::DarkGray),
1684-
)));
1685-
for line in issue.body.lines() {
1686-
lines.push(Line::from(format!(" {}", line)));
1687-
}
16881548
}
16891549

16901550
let text = Text::from(lines);

0 commit comments

Comments
 (0)