b7cd2b1
use export::http::{Path, Response, State};
use maud::html;
use crate::handler::html::{HtmlRepoPath, archived_class, resolve, response_html};
use crate::service::GitService;
use crate::share::access::BasicAuth;
#[utoipa::path(
get,
path = "/repos/{space_name}/{repo_name}/html/languages",
tag = "html",
description = "渲染语言占比色条;缺少统计时按需触发计算。",
operation_id = "render_languages",
params(HtmlRepoPath),
responses(
(status = 200, description = "languages analysis html fragment", content_type = "text/html"),
(status = 404, description = "repo not found")
)
)]
pub async fn render_languages(
State(service): State<GitService>,
Path(path): Path<HtmlRepoPath>,
auth: BasicAuth,
) -> Response {
let ctx = match resolve(service.clone(), path, auth).await {
Ok(c) => c,
Err(resp) => return resp,
};
let mut langs = service
.analysis_languages_read(ctx.space_name.clone(), ctx.repo_name.clone())
.await
.unwrap_or_default();
if langs.is_empty() {
langs = service
.analysis_languages(ctx.space_name.clone(), ctx.repo_name.clone())
.await
.unwrap_or_default();
}
let base =
"git-languages flex flex-col gap-2 rounded-lg border border-zinc-200 bg-white p-3 text-xs";
let cls = archived_class(&ctx.dto, base);
let markup = html! {
div class=(cls)
data-component="git-languages"
data-space=(ctx.space_name)
data-repo=(ctx.repo_name)
data-archived=(if ctx.dto.is_archived { "true" } else { "false" }) {
div class="flex items-center justify-between font-medium text-zinc-700" {
span { "Languages" }
@if let Some(primary) = langs.iter().find(|l| l.is_primary) {
span class="text-[11px] text-zinc-500" { "Primary: " (primary.language) }
}
}
@if langs.is_empty() {
p class="text-zinc-400 text-xs py-1" { "No language data available" }
} @else {
div class="flex h-2 w-full overflow-hidden rounded-full bg-zinc-100" {
@for l in &langs {
@let pct = (l.percentage_bp as f32) / 100.0;
@let color = language_color(&l.language);
div class="h-full first:rounded-l-full last:rounded-r-full"
style=(format!("width: {pct}%; background-color: {color};"))
title=(format!("{}: {pct:.1}%", l.language)) {}
}
}
div class="flex flex-wrap items-center gap-x-4 gap-y-1.5 pt-1 text-xs text-zinc-600" {
@for l in &langs {
@let pct = (l.percentage_bp as f32) / 100.0;
@let color = language_color(&l.language);
div class="flex items-center gap-1.5" {
span class="h-2 w-2 rounded-full shrink-0"
style=(format!("background-color: {color};")) {}
span class="font-medium text-zinc-800" { (l.language) }
span class="text-zinc-400 text-[11px]" { (format!("{pct:.1}%")) }
}
}
}
}
}
};
response_html(markup, 30)
}
fn language_color(lang: &str) -> &'static str {
match lang {
"Rust" => "#dea584",
"Go" => "#00add8",
"Python" => "#3572A5",
"JavaScript" => "#f1e05a",
"TypeScript" => "#3178c6",
"C" => "#555555",
"C++" => "#f34b7d",
"C#" => "#178600",
"Java" => "#b07219",
"PHP" => "#4F5D95",
"Ruby" => "#701516",
"Swift" => "#F05138",
"Kotlin" => "#A97BFF",
"HTML" => "#e34c26",
"CSS" | "SCSS" => "#563d7c",
"Shell" => "#89e051",
"SQL" => "#e38c00",
"Vue" => "#41b883",
"Lua" => "#000080",
"Dart" => "#00B4AB",
"Markdown" => "#083fa1",
"TOML" | "YAML" | "JSON" => "#cb171e",
_ => "#8b949e",
}
}