diff --git a/Cargo.lock b/Cargo.lock index fe7d3de..f27d42d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2354,8 +2354,9 @@ dependencies = [ [[package]] name = "objdiff-core" -version = "2.0.0-beta.6" -source = "git+https://github.com/encounter/objdiff?tag=v2.0.0-beta.6#e1ae369d172240091735c7063f85d54c5052ac62" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5a4a551c6f2c06663c06c4b8f5829e7ed4e12c15e10f644d76021f2287bf5a8" dependencies = [ "anyhow", "byteorder", diff --git a/Cargo.toml b/Cargo.toml index e4af10a..e7fceae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ mime = "0.3" minijinja = { version = "2.2", features = ["loader", "json"] } minijinja-autoreload = "2.2" moka = { version = "0.12", features = ["future"] } -objdiff-core = { git = "https://github.com/encounter/objdiff", tag = "v2.0.0-beta.6", features = ["bindings"] } +objdiff-core = { version = "2.0", features = ["bindings"] } #objdiff-core = { path = "../objdiff/objdiff-core", features = ["bindings"] } octocrab = "0.39" oxc = { version = "0.27", features = ["codegen", "minifier", "transformer", "semantic"] } diff --git a/css/main.scss b/css/main.scss index 1e37c4c..50567d3 100644 --- a/css/main.scss +++ b/css/main.scss @@ -97,12 +97,16 @@ $breakpoints: ( :root:not([data-theme=dark]) { --pico-code-kbd-color: #000; --pico-code-kbd-background-color: #fff; + --progress-background-color: #{$slate-100}; + --progress-stripe-color: rgba(0, 0, 0, 0.15); } @mixin pico-theme-dark { --pico-background-color: #181c25; --pico-code-kbd-color: #fff; --pico-code-kbd-background-color: #333; + --progress-background-color: #{$slate-800}; + --progress-stripe-color: rgba(255, 255, 255, 0.15); } [data-theme=dark] { @@ -163,17 +167,17 @@ $progress-height: 2rem; height: $progress-height; overflow: hidden; border-radius: var(--pico-border-radius); - background-color: $slate-800; + background-color: var(--progress-background-color); .progress-section.striped { //animation: progress-stripes 1s linear infinite; background-image: linear-gradient( 45deg, - rgba(255, 255, 255, .15) 25%, + var(--progress-stripe-color) 25%, transparent 25%, transparent 50%, - rgba(255, 255, 255, .15) 50%, - rgba(255, 255, 255, .15) 75%, + var(--progress-stripe-color) 50%, + var(--progress-stripe-color) 75%, transparent 75%, transparent); background-size: calc(1.25rem) calc(1.25rem); @@ -190,7 +194,7 @@ $progress-height: 2rem; } .progress-section:nth-child(3) { - background-color: $slate-800; + background-color: transparent; } } @@ -259,4 +263,5 @@ $unit-size: 0.75rem; --font-size: 0.875rem; --font-family: var(--pico-font-family); --tooltip-background: var(--pico-tooltip-background-color); + --tooltip-color: var(--pico-tooltip-color); } diff --git a/js/graph.ts b/js/graph.ts index 6630855..5f89b9a 100644 --- a/js/graph.ts +++ b/js/graph.ts @@ -28,6 +28,7 @@ const drawTooltip = (ctx: CanvasRenderingContext2D, unit: Unit, width: number, h const fontSize = style.getPropertyValue('--font-size') || '16px'; const fontFamily = style.getPropertyValue('--font-family') || 'sans-serif'; const tooltipBackground = style.getPropertyValue('--tooltip-background') || "#fff" + const tooltipColor = style.getPropertyValue('--tooltip-color') || "#000" ctx.font = `${fontWeight} ${fontSize} ${fontFamily}`; ctx.textBaseline = "middle"; @@ -65,18 +66,34 @@ const drawTooltip = (ctx: CanvasRenderingContext2D, unit: Unit, width: number, h ctx.lineTo(ax - margin, y - margin); } ctx.fill(); - ctx.fillStyle = "#000"; + ctx.fillStyle = tooltipColor; ctx.fillText(text, bx + PADDING_W, by + bh / 2); }; let hovered = null; let dirty = false; let isTouch = false; +let cachedCanvas: HTMLCanvasElement = null; + +const setup = (ctx: CanvasRenderingContext2D, ratio: number, width: number, height: number) => { + ctx.setTransform(ratio, 0, 0, ratio, 0, 0); // Scale to device pixel ratio + ctx.clearRect(0, 0, width, height); + ctx.lineWidth = 1; + ctx.strokeStyle = "#000"; +} + +const drawUnits = (ctx: CanvasRenderingContext2D, units: Unit[], width: number, height: number) => { + for (const unit of units) { + const {x, y, w, h} = unitBounds(unit, width, height); + ctx.fillStyle = unit.color; + ctx.beginPath(); + ctx.rect(x, y, w, h); + ctx.fill(); + ctx.stroke(); + } +} const draw = (canvas: HTMLCanvasElement, units: Unit[]) => { - if (!canvas.getContext) { - return; - } const {width, height} = canvas.getBoundingClientRect(); const ratio = window.devicePixelRatio; const renderWidth = width * ratio; @@ -91,19 +108,25 @@ const draw = (canvas: HTMLCanvasElement, units: Unit[]) => { canvas.width = renderWidth; canvas.height = renderHeight; } - const ctx = canvas.getContext("2d"); - ctx.setTransform(ratio, 0, 0, ratio, 0, 0); // Scale to device pixel ratio - ctx.clearRect(0, 0, width, height); - ctx.lineWidth = 1; - ctx.strokeStyle = "#000"; - for (const unit of units) { - const {x, y, w, h} = unitBounds(unit, width, height); - ctx.fillStyle = unit.color; - ctx.beginPath(); - ctx.rect(x, y, w, h); - ctx.fill(); - ctx.stroke(); + // Update cached canvas if needed + if (cachedCanvas.width !== renderWidth || cachedCanvas.height !== renderHeight) { + cachedCanvas.width = renderWidth; + cachedCanvas.height = renderHeight; + const cachedCtx = cachedCanvas.getContext("2d"); + if (!cachedCtx) { + return; + } + setup(cachedCtx, ratio, width, height); + drawUnits(cachedCtx, units, width, height); } + const ctx = canvas.getContext("2d"); + if (!ctx) { + return; + } + // Use 1:1 scale for rendering cached canvas + setup(ctx, 1, renderWidth, renderHeight); + ctx.drawImage(cachedCanvas, 0, 0); + ctx.scale(ratio, ratio); // Restore device scale if (hovered) { const {x, y, w, h} = unitBounds(hovered, width, height); ctx.lineWidth = 2; @@ -115,9 +138,12 @@ const draw = (canvas: HTMLCanvasElement, units: Unit[]) => { const drawGraph = (id: string, units: Unit[]) => { const canvas = document.getElementById(id) as HTMLCanvasElement; - if (!canvas) { + if (!canvas || !canvas.getContext) { return; } + if (!cachedCanvas) { + cachedCanvas = document.createElement("canvas"); + } const queueDraw = () => requestAnimationFrame(() => draw(canvas, units)); const resizeObserver = new ResizeObserver(queueDraw); resizeObserver.observe(canvas); diff --git a/src/handlers/css.rs b/src/handlers/css.rs index 0798e28..367e6d3 100644 --- a/src/handlers/css.rs +++ b/src/handlers/css.rs @@ -1,4 +1,4 @@ -use std::{ffi::OsStr, path::PathBuf}; +use std::ffi::OsStr; use anyhow::anyhow; use axum::{ @@ -8,9 +8,10 @@ use axum::{ }; use super::AppError; +use crate::util::join_normalized; pub async fn get_css(Path(filename): Path) -> Result { - let mut path = PathBuf::from(format!("css/{filename}")); + let mut path = join_normalized("css", &filename); if path.extension() != Some(OsStr::new("css")) { return Err(AppError::Status(StatusCode::NOT_FOUND)); } diff --git a/src/handlers/js.rs b/src/handlers/js.rs index 87a4da5..d7c9338 100644 --- a/src/handlers/js.rs +++ b/src/handlers/js.rs @@ -1,4 +1,4 @@ -use std::{ffi::OsStr, path::PathBuf}; +use std::ffi::OsStr; use anyhow::{anyhow, Result}; use axum::{ @@ -16,10 +16,10 @@ use oxc::{ transformer::{EnvOptions, Targets, TransformOptions, Transformer}, }; -use crate::handlers::AppError; +use crate::{handlers::AppError, util::join_normalized}; pub async fn get_js(Path(filename): Path) -> Result { - let mut path = PathBuf::from(format!("js/{filename}")); + let mut path = join_normalized("js", &filename); #[derive(Debug, Copy, Clone, Eq, PartialEq)] enum ResponseType { Js, @@ -82,7 +82,7 @@ fn transform( .into_symbol_table_and_scope_tree(); let transform_options = TransformOptions::from_preset_env(&EnvOptions { - targets: Targets::from_query("es2020"), + targets: Targets::from_query("defaults"), ..EnvOptions::default() }) .map_err(|v| anyhow!("{}", v.first().unwrap()))?; diff --git a/src/handlers/report.rs b/src/handlers/report.rs index c369835..3abc13c 100644 --- a/src/handlers/report.rs +++ b/src/handlers/report.rs @@ -2,7 +2,7 @@ use std::{iter, str::FromStr, time::Instant}; use anyhow::{Context, Result}; use axum::{ - extract::{ Path, Query, State}, + extract::{Path, Query, State}, http::{header, HeaderMap, StatusCode, Uri}, response::{Html, IntoResponse, Response}, Json, diff --git a/src/util.rs b/src/util.rs index 2375c49..26a0b68 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,3 +1,5 @@ +use std::path::{Path, PathBuf}; + use url::Url; pub trait UrlExt { @@ -48,3 +50,10 @@ impl UrlExt for Url { #[inline] fn path_and_query(&self) -> &str { &self[url::Position::BeforePath..] } } + +/// Join two paths, only including the normal components. +pub fn join_normalized(base: impl AsRef, path: impl AsRef) -> PathBuf { + let mut out = base.as_ref().to_path_buf(); + out.extend(path.as_ref().components().filter(|v| matches!(v, std::path::Component::Normal(_)))); + out +} diff --git a/templates/projects.html b/templates/projects.html index 0b5a60f..58fee2b 100644 --- a/templates/projects.html +++ b/templates/projects.html @@ -61,7 +61,7 @@ data-tooltip="{{ measures.fuzzy_match_percent | round(2) }}% fuzzy match"> - Last updated: {{ project.timestamp | timeago }} + Updated {{ project.timestamp | timeago }} in commit {{ project.commit[:7] }} diff --git a/templates/report.html b/templates/report.html index 8d818f1..9d8ed94 100644 --- a/templates/report.html +++ b/templates/report.html @@ -7,11 +7,11 @@ {{ project_short_name }}{% if current_category.id != "all" %} ({{ current_category.name }}){% endif %} • Progress Report - + - +