From 3473c3f6b5696a42deb8e04dc65c50f7e2cc86c4 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Tue, 10 Nov 2026 20:38:42 +0000 Subject: [PATCH] Timeline: Fix styling of benchmark list and add search --- codespeed/static/css/timeline.css | 50 +++++++++++++++++++++++++++++++ codespeed/static/js/timeline.js | 40 +++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/codespeed/static/css/timeline.css b/codespeed/static/css/timeline.css index 937a177e..43b53d4f 100644 --- a/codespeed/static/css/timeline.css +++ b/codespeed/static/css/timeline.css @@ -1,3 +1,53 @@ select#baseline { width: 100%; } + +/* Benchmark picker: a search box over a list that scrolls inside its card */ +div#benchmarksearchbox { position: relative; margin: 0.2em 0 0.6em; } +div#benchmarksearchbox::before { + /* Bootstrap Icons search (MIT, twbs/icons), coloured via mask */ + content: ''; + position: absolute; + left: 0.55em; + top: 50%; + width: 0.85em; + height: 0.85em; + transform: translateY(-50%); + background-color: var(--text-faint); + -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.044.06.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1 1 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0'/%3E%3C/svg%3E") no-repeat center / contain; + mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.044.06.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1 1 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0'/%3E%3C/svg%3E") no-repeat center / contain; + pointer-events: none; +} +div#benchmarksearchbox:focus-within::before { background-color: var(--text-muted); } +input#benchmarksearch { + display: block; + box-sizing: border-box; + width: 100%; + margin: 0; + font: inherit; + font-size: 0.93em; + color: var(--text); + padding: 0.25em 0.5em 0.25em 1.8em; + background-color: var(--surface); + border: 1px solid var(--border-strong); + border-radius: var(--radius-sm); +} +div#benchmarklist { + max-height: min(60vh, 34em); + overflow-y: auto; + overscroll-behavior: contain; + /* let the scrollbar sit in the card's padding rather than over the names */ + margin-right: -0.6em; + padding-right: 0.3em; +} +div#benchmarklist ul:last-of-type { margin-bottom: 0; } +/* Radio and name side by side, so a long name wraps under itself instead of + dropping below the radio and running out of the card */ +div#benchmark li { display: flex; align-items: flex-start; gap: 0.4em; } +div#benchmark li input { flex: none; margin-top: 0.25em; } +div#benchmark li label { min-width: 0; overflow-wrap: anywhere; } +/* While searching, matches show even in folded suites */ +div#benchmarklist.searching li { display: flex !important; } +div#benchmarklist.searching li.nomatch, +div#benchmarklist.searching ul.nomatch { display: none !important; } +p#benchmarknomatch { margin: 0.2em 0; color: var(--text-muted); } diff --git a/codespeed/static/js/timeline.js b/codespeed/static/js/timeline.js index 0642a0dc..50ed2352 100644 --- a/codespeed/static/js/timeline.js +++ b/codespeed/static/js/timeline.js @@ -651,10 +651,50 @@ function updateEnvIndicators() { }); } +function initBenchmarkSearch() { + var list = $("#benchmarklist"); + var search = $("#benchmarksearch"); + var groups = list.children("ul"); + + search.on("input", function() { + var terms = $(this).val().toLowerCase().split(/\s+/).filter(Boolean); + var anyMatch = false; + groups.each(function() { + var groupMatch = false; + $(this).children("li").each(function() { + var name = $(this).children("label").text().toLowerCase(); + var match = terms.every(function(t) { return name.indexOf(t) !== -1; }); + $(this).toggleClass("nomatch", !match); + groupMatch = groupMatch || match; + }); + $(this).toggleClass("nomatch", !groupMatch); + anyMatch = anyMatch || groupMatch; + }); + list.toggleClass("searching", terms.length > 0); + $("#benchmarknomatch").prop("hidden", anyMatch); + list.scrollTop(0); + }).on("keydown", function(e) { + if (e.key === "Enter") { + // pick the first match + list.find("li:not(.nomatch) input").first().prop("checked", true).change(); + } else if (e.key === "Escape" && $(this).val()) { + $(this).val("").trigger("input"); + } + }); + + // bring the selected benchmark into view inside the scrolling list + var checked = list.find("input:checked").closest("li"); + if (checked.length) { + list.scrollTop(checked.position().top - list.children().first().position().top - + list.height() / 2); + } +} + function initializeSite() { var params = getUrlParams(); setExeColors(); setValuesOfInputFields(params); + initBenchmarkSearch(); var onChange = function() { updateUrl(); refreshContent(); };