mirror of
https://github.com/encounter/decomp.dev.git
synced 2026-07-10 03:18:48 -07:00
Platform icons & project sort options
This commit is contained in:
+11
-5
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n projects.id AS \"project_id!\",\n owner AS \"owner!\",\n repo AS \"repo!\",\n name,\n short_name,\n default_version,\n git_commit AS \"git_commit!\",\n MAX(timestamp) AS \"timestamp: chrono::NaiveDateTime\",\n JSON_GROUP_ARRAY(version ORDER BY version) AS versions\n FROM projects JOIN reports ON projects.id = reports.project_id\n WHERE timestamp = (\n SELECT MAX(timestamp)\n FROM reports\n WHERE project_id = projects.id\n )\n GROUP BY projects.id\n ORDER BY MAX(timestamp) DESC\n ",
|
||||
"query": "\n SELECT\n projects.id AS \"project_id!\",\n owner AS \"owner!\",\n repo AS \"repo!\",\n name,\n short_name,\n default_version,\n platform,\n git_commit AS \"git_commit!\",\n MAX(timestamp) AS \"timestamp: chrono::NaiveDateTime\",\n JSON_GROUP_ARRAY(version ORDER BY version) AS versions\n FROM projects JOIN reports ON projects.id = reports.project_id\n WHERE timestamp = (\n SELECT MAX(timestamp)\n FROM reports\n WHERE project_id = projects.id\n )\n GROUP BY projects.id\n ORDER BY MAX(timestamp) DESC\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -34,19 +34,24 @@
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "git_commit!",
|
||||
"name": "platform",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "timestamp: chrono::NaiveDateTime",
|
||||
"name": "git_commit!",
|
||||
"ordinal": 7,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "versions",
|
||||
"name": "timestamp: chrono::NaiveDateTime",
|
||||
"ordinal": 8,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "versions",
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -60,9 +65,10 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "76fc00ee61f9d0761931936dc4cc5cc533b79d79f78389b4642c03837b264733"
|
||||
"hash": "33ef6c68364ca0148a0cec67caba762484635c8e9881fe29b7f0e75cff235b31"
|
||||
}
|
||||
+8
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n reports.id as \"report_id!\",\n git_commit,\n timestamp,\n version,\n data,\n projects.id as \"project_id!\",\n owner,\n repo,\n name,\n short_name,\n default_version\n FROM reports JOIN projects ON reports.project_id = projects.id\n WHERE projects.owner = ? COLLATE NOCASE AND projects.repo = ? COLLATE NOCASE\n AND version = ? COLLATE NOCASE AND git_commit = ? COLLATE NOCASE\n ",
|
||||
"query": "\n SELECT\n reports.id as \"report_id!\",\n git_commit,\n timestamp,\n version,\n data,\n projects.id as \"project_id!\",\n owner,\n repo,\n name,\n short_name,\n default_version,\n platform\n FROM reports JOIN projects ON reports.project_id = projects.id\n WHERE projects.owner = ? COLLATE NOCASE AND projects.repo = ? COLLATE NOCASE\n AND version = ? COLLATE NOCASE AND git_commit = ? COLLATE NOCASE\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -57,6 +57,11 @@
|
||||
"name": "default_version",
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "platform",
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -73,8 +78,9 @@
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "d38ee2d117fd8d6e0628c790c138ef596317748a42165c899b81ab63b84ff819"
|
||||
"hash": "599d96163a6612745b70ff34a34a5bef3f0e26edceb2319cd5652c0cd679acb9"
|
||||
}
|
||||
+8
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT id AS \"id!\", owner, repo, name, short_name, default_version\n FROM projects\n WHERE owner = ? COLLATE NOCASE AND repo = ? COLLATE NOCASE\n ",
|
||||
"query": "\n SELECT id AS \"id!\", owner, repo, name, short_name, default_version, platform\n FROM projects\n WHERE owner = ? COLLATE NOCASE AND repo = ? COLLATE NOCASE\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -32,6 +32,11 @@
|
||||
"name": "default_version",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "platform",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -43,8 +48,9 @@
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "d4de02659c86068320c310dc8c6262c0086a2348ff8726e522aaee6999e61cad"
|
||||
"hash": "b5c315558b6853de6aeb2f3338ec8448f2e8d9aca51ac488d55d526bef7147e3"
|
||||
}
|
||||
Generated
+2
-2
@@ -2396,9 +2396,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-core"
|
||||
version = "2.0.0"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5a4a551c6f2c06663c06c4b8f5829e7ed4e12c15e10f644d76021f2287bf5a8"
|
||||
checksum = "e9c26c115905f9e65e7e63385680912aaf61963494ede73727cd73c04c78d3d3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"byteorder",
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ mime = "0.3"
|
||||
minijinja = { version = "2.2", features = ["loader", "json"] }
|
||||
minijinja-autoreload = "2.2"
|
||||
moka = { version = "0.12", features = ["future"] }
|
||||
objdiff-core = { version = "2.0", features = ["bindings"] }
|
||||
objdiff-core = { version = "2.2", features = ["bindings"] }
|
||||
#objdiff-core = { path = "../objdiff/objdiff-core", features = ["bindings"] }
|
||||
octocrab = "0.39"
|
||||
oxc = { version = "0.27", features = ["codegen", "minifier", "transformer", "semantic"] }
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 2 3.34 7v10L12 22l8.625-4.979.035-.023V7l-.373-.215-4.64 2.678L12 7.359 7.979 9.678v4.642L12 16.64l4.021-2.32v-1.386l1.67-.963v3.314L12 18.57l-5.69-3.285v-6.57L12 5.43l3.873 2.234 2.596-1.5v-.43h-.002L12 2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 315 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width=".2">
|
||||
<path d="m8.907 8.642 3.094 1.268v11.737l-2.904-1.19L4.85 12.73v5.984L2.059 17.57V5.836L4.977 7.03l3.93 7.624V8.642M19.13 18.725l-4.21-5.447v3.044l.962 1.275 3.249 1.128"/>
|
||||
<path d="m8.907 8.642 3.063-1.226 3.059 1.254L12 9.91 8.907 8.642m7.33-2.631 2.897-1.201 2.81 1.026-2.968 1.215-2.74-1.04M4.977 7.03 2.06 5.836l2.683-.885 2.792 1.046L4.977 7.03m6.989-2.712-2.81-1.025 2.705-.948 2.743.94-2.638 1.033"/>
|
||||
<path d="m14.049 8.266.98.404 3.947 5.608V7.051l-2.74-1.04-2.187 2.255m-5.142.509L7.601 6.138l1.555-2.845 2.81 1.025-2.353 4.043-.706.28v.134"/>
|
||||
<path fill="currentColor" d="M8.907 14.654 4.977 7.03l2.557-1.033 1.373 2.778v5.88m3.094-4.745 3.028-1.24 3.947 5.608V7.051l2.968-1.215v11.733l-2.813 1.156-4.211-5.447v7.171L12 21.647V9.91m-4.466 7.705L4.85 12.73v5.984Zm6.515-9.349.555-.573V3.286l-2.638 1.032-2.353 4.043 2.357-.945 2.079.85"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1009 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M9.5 4.27c1.38.26 3.4.87 4.5 1.23 2.75.95 3.69 2.13 3.69 4.79 0 2.6-1.6 3.58-3.64 2.6V8.05c0-.55-.1-1.08-.64-1.23-.41-.13-.65.25-.65.81v12.1L9.5 18.69V4.27m3.87 13.35 5.25-1.87c.6-.21.69-.51.21-.67-.49-.16-1.36-.11-1.96.1l-3.5 1.23v-1.96l.21-.07s1.01-.38 2.42-.51c1.43-.16 3.17.02 4.53.53 1.54.49 1.72 1.21 1.33 1.7-.4.5-1.36.85-1.36.85l-7.13 2.55v-1.88m-9.87-.2C1.93 17 1.66 16.05 2.38 15.5c.67-.5 1.8-.85 1.8-.85L8.86 13v1.88L5.5 16.09c-.6.21-.69.51-.21.67.48.16 1.36.12 1.95-.1l1.62-.58v1.69l-.32.06c-1.62.26-3.34.17-5.04-.41Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 634 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M10.04 20.4H7.12c-.93 0-1.82-.4-2.48-1.04C4 18.7 3.6 17.81 3.6 16.88V7.12c0-.93.4-1.82 1.04-2.48C5.3 4 6.19 3.62 7.12 3.62h2.92V20.4M7.12 2A5.12 5.12 0 0 0 2 7.12v9.76C2 19.71 4.29 22 7.12 22h4.53V2H7.12M5.11 8c0 1.04.84 1.88 1.89 1.88 1.03 0 1.87-.84 1.87-1.88S8.03 6.12 7 6.12c-1.05 0-1.89.84-1.89 1.88m12.5 3c1.11 0 2.01.89 2.01 2 0 1.12-.9 2-2.01 2-1.11 0-2.03-.88-2.03-2 0-1.11.92-2 2.03-2m-.73 11A5.12 5.12 0 0 0 22 16.88V7.12C22 4.29 19.71 2 16.88 2h-3.23v20h3.23Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 576 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M17.84 16.94h-1.87v-6.15h1.87v6.15M18 8.58c0 .61-.5 1.11-1.1 1.11a1.11 1.11 0 0 1-1.11-1.11c0-.62.5-1.12 1.11-1.12.6 0 1.1.5 1.1 1.12m3.82 8.36h-1.88v-6.15h1.88v6.15M22 8.58c0 .61-.5 1.11-1.12 1.11a1.11 1.11 0 0 1-1.11-1.11 1.116 1.116 0 1 1 2.23 0m-9.1-.53h2l-2.12 7.45s-.28 1.54-1.5 1.54c-1.21 0-1.49-1.54-1.49-1.54l-1.34-4.86-1.34 4.86s-.29 1.54-1.5 1.54-1.49-1.54-1.49-1.54L2 8.05h2l1.72 6.62L7.11 9.3c.32-1.35 1.34-1.33 1.34-1.33s1.02-.02 1.34 1.33l1.38 5.37 1.73-6.62Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 579 B |
@@ -107,6 +107,10 @@ $breakpoints: (
|
||||
--pico-code-kbd-background-color: #333;
|
||||
--progress-background-color: #{$slate-800};
|
||||
--progress-stripe-color: rgba(255, 255, 255, 0.15);
|
||||
|
||||
.platform-icon {
|
||||
filter: invert(1);
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme=dark] {
|
||||
@@ -265,3 +269,20 @@ $unit-size: 0.75rem;
|
||||
--tooltip-background: var(--pico-tooltip-background-color);
|
||||
--tooltip-color: var(--pico-tooltip-color);
|
||||
}
|
||||
|
||||
.project-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--pico-spacing);
|
||||
margin-bottom: var(--pico-typography-spacing-vertical);
|
||||
|
||||
.project-title {
|
||||
flex: 1 1 auto;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.platform-icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE projects ADD COLUMN platform TEXT;
|
||||
+12
@@ -56,3 +56,15 @@ pub async fn refresh_projects(state: &mut AppState) -> Result<()> {
|
||||
// github::run(&mut state, "Rainchus", "marioparty4", 10715620655)
|
||||
// .await
|
||||
// .expect("Failed to run GitHub client");
|
||||
// github::run(&mut state, "zeldaret", "ss", 10838420987)
|
||||
// .await
|
||||
// .expect("Failed to run GitHub client");
|
||||
// github::run(&mut state, "zeldaret", "oot-gc", 10964802446)
|
||||
// .await
|
||||
// .expect("Failed to run GitHub client");
|
||||
// github::run(&mut state, "projectPiki", "pikmin", 11066117942)
|
||||
// .await
|
||||
// .expect("Failed to run GitHub client");
|
||||
// github::run(&mut state, "NSMBW-Community","NSMBW-Decomp", 11094989546)
|
||||
// .await
|
||||
// .expect("Failed to run GitHub client");
|
||||
|
||||
+7
-2
@@ -165,7 +165,8 @@ impl Database {
|
||||
repo,
|
||||
name,
|
||||
short_name,
|
||||
default_version
|
||||
default_version,
|
||||
platform
|
||||
FROM reports JOIN projects ON reports.project_id = projects.id
|
||||
WHERE projects.owner = ? COLLATE NOCASE AND projects.repo = ? COLLATE NOCASE
|
||||
AND version = ? COLLATE NOCASE AND git_commit = ? COLLATE NOCASE
|
||||
@@ -190,6 +191,7 @@ impl Database {
|
||||
name: row.name,
|
||||
short_name: row.short_name,
|
||||
default_version: row.default_version,
|
||||
platform: row.platform,
|
||||
},
|
||||
Commit { sha: row.git_commit, timestamp: row.timestamp.and_utc() },
|
||||
row.version,
|
||||
@@ -269,7 +271,7 @@ impl Database {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
let project = match sqlx::query!(
|
||||
r#"
|
||||
SELECT id AS "id!", owner, repo, name, short_name, default_version
|
||||
SELECT id AS "id!", owner, repo, name, short_name, default_version, platform
|
||||
FROM projects
|
||||
WHERE owner = ? COLLATE NOCASE AND repo = ? COLLATE NOCASE
|
||||
"#,
|
||||
@@ -286,6 +288,7 @@ impl Database {
|
||||
name: row.name,
|
||||
short_name: row.short_name,
|
||||
default_version: row.default_version,
|
||||
platform: row.platform,
|
||||
},
|
||||
None => return Ok(None),
|
||||
};
|
||||
@@ -398,6 +401,7 @@ impl Database {
|
||||
name,
|
||||
short_name,
|
||||
default_version,
|
||||
platform,
|
||||
git_commit AS "git_commit!",
|
||||
MAX(timestamp) AS "timestamp: chrono::NaiveDateTime",
|
||||
JSON_GROUP_ARRAY(version ORDER BY version) AS versions
|
||||
@@ -422,6 +426,7 @@ impl Database {
|
||||
name: row.name,
|
||||
short_name: row.short_name,
|
||||
default_version: row.default_version,
|
||||
platform: row.platform,
|
||||
},
|
||||
commit: Commit { sha: row.git_commit, timestamp: row.timestamp.and_utc() },
|
||||
report_versions: serde_json::from_str(&row.versions).unwrap_or_default(),
|
||||
|
||||
+9
-8
@@ -48,6 +48,7 @@ pub async fn run(state: &mut AppState, owner: &str, repo: &str, stop_run_id: u64
|
||||
name: None,
|
||||
short_name: None,
|
||||
default_version: None,
|
||||
platform: None,
|
||||
});
|
||||
|
||||
let mut runs = vec![];
|
||||
@@ -285,14 +286,14 @@ async fn download_artifact(
|
||||
file.read_to_end(&mut contents)?;
|
||||
let mut report = Report::parse(&contents)?;
|
||||
report.migrate()?;
|
||||
// Split combined reports into individual reports TODO
|
||||
// if version.eq_ignore_ascii_case("combined") {
|
||||
// return Ok(report
|
||||
// .split()
|
||||
// .into_iter()
|
||||
// .map(|(version, report)| (version, Arc::new(report)))
|
||||
// .collect());
|
||||
// }
|
||||
// Split combined reports into individual reports
|
||||
if version.eq_ignore_ascii_case("combined") {
|
||||
return Ok(report
|
||||
.split()
|
||||
.into_iter()
|
||||
.map(|(version, report)| (version, Arc::new(report)))
|
||||
.collect());
|
||||
}
|
||||
return Ok(vec![(version, Arc::new(report))]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
use axum::{
|
||||
extract::Path,
|
||||
http::{header, StatusCode},
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
|
||||
use super::AppError;
|
||||
use crate::util::join_normalized;
|
||||
|
||||
pub async fn get_asset(Path(filename): Path<String>) -> Result<Response, AppError> {
|
||||
let path = join_normalized("assets", &filename);
|
||||
let Some(ext) = path.extension() else {
|
||||
return Err(AppError::Status(StatusCode::NOT_FOUND));
|
||||
};
|
||||
let content_type = if let Some(format) = image::ImageFormat::from_extension(ext) {
|
||||
format.to_mime_type()
|
||||
} else {
|
||||
match ext.to_str() {
|
||||
Some("svg") => mime::IMAGE_SVG.as_ref(),
|
||||
_ => return Err(AppError::Status(StatusCode::NOT_FOUND)),
|
||||
}
|
||||
};
|
||||
let output = tokio::fs::read(path).await?;
|
||||
Ok((
|
||||
[
|
||||
(header::CONTENT_TYPE, content_type),
|
||||
#[cfg(not(debug_assertions))]
|
||||
(header::CACHE_CONTROL, "public, max-age=3600"),
|
||||
#[cfg(debug_assertions)]
|
||||
(header::CACHE_CONTROL, "no-cache"),
|
||||
],
|
||||
output,
|
||||
)
|
||||
.into_response())
|
||||
}
|
||||
+6
-22
@@ -1,17 +1,12 @@
|
||||
use std::io::Cursor;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use image::{buffer::ConvertBuffer, ImageFormat, RgbImage, RgbaImage};
|
||||
use anyhow::{anyhow, Result};
|
||||
use image::ImageFormat;
|
||||
use objdiff_core::bindings::report::{Measures, ReportCategory};
|
||||
use resvg::{
|
||||
tiny_skia::{PixmapMut, Transform},
|
||||
usvg::{Options, Tree},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::models::ReportFile;
|
||||
use crate::{models::ReportFile, svg};
|
||||
|
||||
#[derive(Deserialize, Default, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ShieldParams {
|
||||
label: Option<String>,
|
||||
label_color: Option<String>,
|
||||
@@ -21,6 +16,7 @@ pub struct ShieldParams {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ShieldResponse {
|
||||
schema_version: u32,
|
||||
label: String,
|
||||
@@ -98,17 +94,5 @@ pub fn render_image(
|
||||
format: ImageFormat,
|
||||
) -> Result<Vec<u8>> {
|
||||
let svg = render_svg(report, measures, current_category, params)?;
|
||||
let opt = Options::default();
|
||||
let tree = Tree::from_str(&svg, &opt).context("Failed to parse SVG")?;
|
||||
let rect = tree.root().abs_layer_bounding_box();
|
||||
let w = rect.width() as u32;
|
||||
let h = rect.height() as u32;
|
||||
let mut image = RgbaImage::new(w, h);
|
||||
let mut pixmap = PixmapMut::from_bytes(image.as_mut(), w, h)
|
||||
.ok_or_else(|| anyhow!("Failed to create pixmap"))?;
|
||||
resvg::render(&tree, Transform::identity(), &mut pixmap);
|
||||
let mut bytes = Vec::new();
|
||||
let image: RgbImage = image.convert();
|
||||
image.write_to(&mut Cursor::new(&mut bytes), format)?;
|
||||
Ok(bytes)
|
||||
svg::render_image(&svg, format)
|
||||
}
|
||||
|
||||
+3
-1
@@ -16,15 +16,17 @@ use crate::AppState;
|
||||
|
||||
mod badge;
|
||||
mod css;
|
||||
mod graph;
|
||||
mod js;
|
||||
mod project;
|
||||
mod report;
|
||||
mod treemap;
|
||||
mod assets;
|
||||
|
||||
pub fn build_router() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/css/*filename", get(css::get_css))
|
||||
.route("/js/*filename", get(js::get_js))
|
||||
.route("/assets/*filename", get(assets::get_asset))
|
||||
.route("/", get(project::get_projects))
|
||||
.route("/:owner/:repo", get(report::get_report))
|
||||
.route("/:owner/:repo/:version", get(report::get_report))
|
||||
|
||||
+51
-5
@@ -2,12 +2,13 @@ use std::{sync::Arc, time::Instant};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use axum::{
|
||||
extract::State,
|
||||
extract::{Query, State},
|
||||
http::StatusCode,
|
||||
response::{Html, IntoResponse, Response},
|
||||
};
|
||||
use chrono::{DateTime, Utc};
|
||||
use objdiff_core::bindings::report::Measures;
|
||||
use serde::Serialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::{sync::Semaphore, task::JoinSet};
|
||||
|
||||
use super::AppError;
|
||||
@@ -16,6 +17,8 @@ use crate::{templates::render, AppState};
|
||||
#[derive(Serialize)]
|
||||
struct ProjectsTemplateContext {
|
||||
projects: Vec<ProjectInfoContext>,
|
||||
sort_options: &'static [SortOption],
|
||||
current_sort: SortOption,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -29,9 +32,30 @@ struct ProjectInfoContext {
|
||||
commit: String,
|
||||
timestamp: DateTime<Utc>,
|
||||
measures: Measures,
|
||||
platform: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn get_projects(State(state): State<AppState>) -> Result<Response, AppError> {
|
||||
#[derive(Deserialize)]
|
||||
pub struct ProjectsQuery {
|
||||
sort: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Copy, Clone)]
|
||||
struct SortOption {
|
||||
key: &'static str,
|
||||
name: &'static str,
|
||||
}
|
||||
|
||||
const SORT_OPTIONS: &[SortOption] = &[
|
||||
SortOption { key: "updated", name: "Last updated" },
|
||||
SortOption { key: "matched_code", name: "Matched Code" },
|
||||
SortOption { key: "name", name: "Name" },
|
||||
];
|
||||
|
||||
pub async fn get_projects(
|
||||
State(state): State<AppState>,
|
||||
Query(query): Query<ProjectsQuery>,
|
||||
) -> Result<Response, AppError> {
|
||||
let start = Instant::now();
|
||||
let projects = state.db.get_projects().await?;
|
||||
let mut out = projects
|
||||
@@ -46,6 +70,7 @@ pub async fn get_projects(State(state): State<AppState>) -> Result<Response, App
|
||||
commit: p.commit.sha.clone(),
|
||||
timestamp: p.commit.timestamp,
|
||||
measures: Default::default(),
|
||||
platform: p.project.platform.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -86,8 +111,29 @@ pub async fn get_projects(State(state): State<AppState>) -> Result<Response, App
|
||||
}
|
||||
}
|
||||
|
||||
let mut rendered =
|
||||
render(&state.templates, "projects.html", ProjectsTemplateContext { projects: out })?;
|
||||
let current_sort_key = query.sort.as_deref().unwrap_or("updated");
|
||||
let current_sort = SORT_OPTIONS
|
||||
.iter()
|
||||
.find(|s| s.key.eq_ignore_ascii_case(current_sort_key))
|
||||
.copied()
|
||||
.ok_or(AppError::Status(StatusCode::BAD_REQUEST))?;
|
||||
match current_sort.key {
|
||||
"name" => out.sort_by(|a, b| a.name.cmp(&b.name)),
|
||||
"updated" => out.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)),
|
||||
"matched_code" => out.sort_by(|a, b| {
|
||||
b.measures
|
||||
.matched_code_percent
|
||||
.partial_cmp(&a.measures.matched_code_percent)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
}),
|
||||
_ => return Err(AppError::Status(StatusCode::BAD_REQUEST)),
|
||||
}
|
||||
|
||||
let mut rendered = render(&state.templates, "projects.html", ProjectsTemplateContext {
|
||||
projects: out,
|
||||
sort_options: SORT_OPTIONS,
|
||||
current_sort,
|
||||
})?;
|
||||
let elapsed = start.elapsed();
|
||||
rendered = rendered.replace("[[time]]", &format!("{}ms", elapsed.as_millis()));
|
||||
Ok(Html(rendered).into_response())
|
||||
|
||||
@@ -13,7 +13,7 @@ use objdiff_core::bindings::report::{Measures, ReportCategory};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use super::{badge, graph, parse_accept, AppError, FullUri, Protobuf, PROTOBUF};
|
||||
use super::{badge, treemap, parse_accept, AppError, FullUri, Protobuf, PROTOBUF};
|
||||
use crate::{
|
||||
models::{Project, ProjectInfo, ReportFile},
|
||||
templates::render,
|
||||
@@ -33,6 +33,7 @@ const DEFAULT_IMAGE_WIDTH: u32 = 950;
|
||||
const DEFAULT_IMAGE_HEIGHT: u32 = 475;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReportQuery {
|
||||
mode: Option<String>,
|
||||
category: Option<String>,
|
||||
@@ -268,7 +269,7 @@ fn mode_report(
|
||||
return Ok(Protobuf(report.report).into_response());
|
||||
} else if mime.type_() == mime::IMAGE && mime.subtype() == mime::SVG {
|
||||
let (w, h) = query.size();
|
||||
let svg = graph::render_svg(&units, w, h, &state)?;
|
||||
let svg = treemap::render_svg(&units, w, h, &state)?;
|
||||
return Ok(([(header::CONTENT_TYPE, mime::IMAGE_SVG.as_ref())], svg).into_response());
|
||||
} else if mime.type_() == mime::IMAGE {
|
||||
let format = if mime.subtype() == mime::STAR {
|
||||
@@ -279,7 +280,7 @@ fn mode_report(
|
||||
.ok_or_else(|| AppError::Status(StatusCode::NOT_ACCEPTABLE))?
|
||||
};
|
||||
let (w, h) = query.size();
|
||||
let data = graph::render_image(&units, w, h, &state, format)?;
|
||||
let data = treemap::render_image(&units, w, h, &state, format)?;
|
||||
return Ok(([(header::CONTENT_TYPE, format.to_mime_type())], data).into_response());
|
||||
}
|
||||
}
|
||||
@@ -370,7 +371,7 @@ fn apply_category<'a>(
|
||||
}
|
||||
let (w, h) = query.size();
|
||||
let aspect = w as f32 / h as f32;
|
||||
let units = graph::layout_units(&report.report, w, h, |item| {
|
||||
let units = treemap::layout_units(&report.report, w, h, |item| {
|
||||
if let Some(category_id) = &category_id_filter {
|
||||
item.metadata
|
||||
.as_ref()
|
||||
@@ -395,7 +396,7 @@ fn apply_category<'a>(
|
||||
ReportTemplateUnit {
|
||||
name: &unit.name,
|
||||
fuzzy_match_percent: match_percent,
|
||||
color: graph::unit_color(match_percent),
|
||||
color: treemap::unit_color(match_percent),
|
||||
x: bounds.x,
|
||||
y: bounds.y,
|
||||
w: bounds.w,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user