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, Path(path): Path, 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.ctx) .await .unwrap_or_default(); if langs.is_empty() { langs = service .analysis_languages(&ctx.ctx) .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", } }