Files
Gotcha/crates/app/src/presentation/details.rs
2026-08-03 17:54:07 +02:00

257 lines
8.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use super::{helpers::*, *};
pub fn issue_page(details: IssueDetails) -> IssuePage {
IssuePage {
title: details
.issue
.title
.clone()
.unwrap_or_else(|| "Untitled issue".into()),
state: work_item_state(details.issue.state.as_deref()),
meta: issue_meta(&details.issue),
milestone: issue_milestone(&details.issue),
labels: details
.issue
.labels
.as_deref()
.unwrap_or_default()
.iter()
.filter_map(label_row)
.collect(),
body: details
.issue
.body
.filter(|body| !body.is_empty())
.unwrap_or_else(|| "No description provided.".into()),
comments: details
.comments
.iter()
.map(|comment| comment_row(comment, details.viewer_id))
.collect(),
has_more: details.has_more,
}
}
pub fn issue_editor_page(data: IssueEditorData) -> IssueEditorPage {
let selected_labels: std::collections::BTreeSet<_> = data
.issue
.as_ref()
.and_then(|issue| issue.labels.as_deref())
.unwrap_or_default()
.iter()
.filter_map(|label| label.id)
.collect();
let selected_milestone = data
.issue
.as_ref()
.and_then(|issue| issue.milestone.as_ref())
.and_then(|milestone| milestone.id);
let mut labels: Vec<_> = data
.labels
.into_iter()
.filter_map(|label| {
let id = label.id?;
Some(IssueEditorLabel {
id,
name: label.name?,
selected: selected_labels.contains(&id),
})
})
.collect();
labels.sort_by_key(|label| label.name.to_lowercase());
let mut milestones: Vec<_> = data
.milestones
.into_iter()
.filter(|milestone| {
milestone.state.as_deref() != Some("closed") || milestone.id == selected_milestone
})
.filter_map(|milestone| {
let id = milestone.id?;
Some(IssueEditorMilestone {
id,
title: milestone.title?,
selected: Some(id) == selected_milestone,
})
})
.collect();
milestones.sort_by_key(|milestone| milestone.title.to_lowercase());
IssueEditorPage {
title: data
.issue
.as_ref()
.and_then(|issue| issue.title.clone())
.unwrap_or_default(),
body: data
.issue
.as_ref()
.and_then(|issue| issue.body.clone())
.unwrap_or_default(),
due_date: data
.issue
.as_ref()
.and_then(|issue| issue.due_date.as_deref())
.and_then(parse_api_date),
closed: data.issue.as_ref().and_then(|issue| issue.state.as_deref()) == Some("closed"),
labels,
milestones,
}
}
pub fn pull_page(details: PullDetails) -> PullPage {
let pull = &details.pull;
let author = pull
.user
.as_ref()
.and_then(|user| user.login.as_deref())
.unwrap_or("unknown");
let head = pull
.head
.as_ref()
.and_then(|branch| branch.label.as_deref().or(branch.r#ref.as_deref()))
.unwrap_or("head");
let base = pull
.base
.as_ref()
.and_then(|branch| branch.label.as_deref().or(branch.r#ref.as_deref()))
.unwrap_or("base");
let state = pull_state(pull);
PullPage {
title: pull
.title
.clone()
.unwrap_or_else(|| "Untitled pull request".into()),
state: work_item_state(pull.state.as_deref()),
meta: format!(
"#{} · {state} · {author} · {head} → {base}\n{} files · +{} {} · {} comments",
pull.number.unwrap_or_default(),
pull.changed_files.unwrap_or_default(),
pull.additions.unwrap_or_default(),
pull.deletions.unwrap_or_default(),
pull.comments.unwrap_or_default(),
),
body: pull
.body
.clone()
.filter(|body| !body.is_empty())
.unwrap_or_else(|| "No description provided.".into()),
files_ref: pull_files_ref(pull),
files: details.files.iter().filter_map(file_row).collect(),
comments: details
.comments
.iter()
.map(|comment| comment_row(comment, None))
.collect(),
has_more: details.has_more,
}
}
pub fn file_rows(files: Vec<models::ChangedFile>) -> Vec<FileRow> {
files.iter().filter_map(file_row).collect()
}
pub fn commit_file_rows(files: Vec<models::CommitAffectedFiles>) -> Vec<FileRow> {
files
.into_iter()
.filter_map(|file| {
Some(FileRow {
path: file.filename?,
status: file.status.unwrap_or_else(|| "modified".into()),
})
})
.collect()
}
pub fn commit_details_page(commit: models::Commit, branch: Option<String>) -> CommitDetailsPage {
let details = commit.commit.as_deref();
let message = details
.and_then(|details| details.message.as_deref())
.unwrap_or("Commit");
let mut lines = message.lines();
let title = lines.next().unwrap_or("Commit").to_string();
let description = lines.collect::<Vec<_>>().join("\n").trim().to_string();
let author = details
.and_then(|details| details.author.as_deref())
.and_then(commit_user)
.or_else(|| {
commit
.author
.as_deref()
.and_then(|author| author.login.clone())
});
let committer = details
.and_then(|details| details.committer.as_deref())
.and_then(commit_user)
.or_else(|| {
commit
.committer
.as_deref()
.and_then(|committer| committer.login.clone())
});
let date = details
.and_then(|details| details.committer.as_deref())
.and_then(|committer| committer.date.as_deref())
.or_else(|| {
details
.and_then(|details| details.author.as_deref())
.and_then(|author| author.date.as_deref())
})
.or(commit.created.as_deref());
let files = commit.files.unwrap_or_default();
let mut metadata = Vec::new();
if let Some(author) = author {
metadata.push(metadata_row("Author", author, false));
}
if let Some(committer) = committer {
metadata.push(metadata_row("Committer", committer, false));
}
if date.is_some() {
metadata.push(metadata_row("Committed", compact_date(date), false));
}
if let Some(branch) = branch.filter(|branch| !branch.is_empty()) {
metadata.push(metadata_row("Branch", branch, false));
}
metadata.push(metadata_row(
"Commit",
commit.sha.clone().unwrap_or_else(|| "unknown".into()),
true,
));
let verification = details.and_then(|details| details.verification.as_deref());
metadata.push(metadata_row(
"Signature",
match verification.filter(|verification| {
verification
.signature
.as_deref()
.is_some_and(|signature| !signature.is_empty())
}) {
Some(verification) if verification.verified.unwrap_or(false) => "Verified".into(),
Some(_) => "Unverified".into(),
None => "Unsigned".into(),
},
false,
));
CommitDetailsPage {
title,
description,
metadata,
files: commit_file_rows(files),
}
}
fn commit_user(user: &models::CommitUser) -> Option<String> {
match (user.name.as_deref(), user.email.as_deref()) {
(Some(name), Some(email)) => Some(format!("{name} <{email}>")),
(Some(name), None) => Some(name.into()),
(None, Some(email)) => Some(email.into()),
(None, None) => None,
}
}
fn metadata_row(label: &str, value: String, monospaced: bool) -> CommitMetadataRow {
CommitMetadataRow {
label: label.into(),
value,
monospaced,
}
}