diff --git a/frontend/src/base.html b/frontend/src/base.html index f123ac7ac..0a261404e 100755 --- a/frontend/src/base.html +++ b/frontend/src/base.html @@ -62,8 +62,8 @@
{{ nb }} | +|
{{ nb }} |
|
diff --git a/frontend/src/common.js b/frontend/src/common.js
index 4c8bc8d29..753a6a00e 100644
--- a/frontend/src/common.js
+++ b/frontend/src/common.js
@@ -1,5 +1,5 @@
import Mustache from "mustache";
-import { buildRoute, readRoute, updateRoute } from "./route.js";
+import { buildRoute, readRoute } from "./route.js";
import { ZERO_COVERAGE_FILTERS } from "./zero_coverage_report.js";
export const REV_LATEST = "latest";
@@ -18,14 +18,12 @@ export async function main(load, display) {
// Wait for DOM to be ready before displaying
await DOM_READY;
await display(data);
- monitorOptions();
// Full workflow, loading then displaying data
// used for following updates
const full = async function() {
const data = await load();
await display(data);
- monitorOptions();
};
// React to url changes
@@ -176,34 +174,6 @@ export function isEnabled(opt) {
return value === "on";
}
-function monitorOptions() {
- // Monitor input & select changes
- const fields = document.querySelectorAll("input, select");
- for (const field of fields) {
- if (field.type === "text") {
- // React on enter
- field.onkeydown = async evt => {
- if (evt.keyCode === 13) {
- const params = {};
- params[evt.target.name] = evt.target.value;
- updateRoute(params);
- }
- };
- } else {
- // React on change
- field.onchange = async evt => {
- let value = evt.target.value;
- if (evt.target.type === "checkbox") {
- value = evt.target.checked ? "on" : "off";
- }
- const params = {};
- params[evt.target.name] = value;
- updateRoute(params);
- };
- }
- }
-}
-
// hgmo.
const sourceCache = {};
export async function getSource(file, revision) {
diff --git a/frontend/src/index.js b/frontend/src/index.js
index 1ffa8751a..1c06bee32 100644
--- a/frontend/src/index.js
+++ b/frontend/src/index.js
@@ -13,7 +13,7 @@ import {
getSource,
getFilters
} from "./common.js";
-import { buildRoute, readRoute, updateRoute } from "./route.js";
+import { buildRoute, monitorOptions, readRoute, updateRoute } from "./route.js";
import {
zeroCoverageDisplay,
zeroCoverageMenu
@@ -25,7 +25,8 @@ import Chartist from "chartist";
import "chartist/dist/chartist.css";
const VIEW_ZERO_COVERAGE = "zero";
-const VIEW_BROWSER = "browser";
+const VIEW_DIRECTORY = "directory";
+const VIEW_FILE = "file";
function browserMenu(revision, filters, route) {
const context = {
@@ -111,7 +112,8 @@ async function showDirectory(dir, revision, files) {
navbar: buildNavbar(dir, revision),
files: files.map(file => {
file.route = buildRoute({
- path: file.path
+ path: file.path,
+ view: file.type
});
// Calc decimal range to make a nice coloration
@@ -128,8 +130,8 @@ async function showDirectory(dir, revision, files) {
render("file_browser", context, "output");
}
-async function showFile(file, revision) {
- const source = await getSource(file.path, revision);
+async function showFile(source, file, revision, selectedLine) {
+ selectedLine = selectedLine !== undefined ? parseInt(selectedLine) : -1;
let language;
if (file.path.endsWith("cpp") || file.path.endsWith("h")) {
@@ -148,7 +150,6 @@ async function showFile(file, revision) {
const context = {
navbar: buildNavbar(file.path, revision),
- revision: revision || REV_LATEST,
language,
lines: source.map((line, nb) => {
const coverage = file.coverage[nb];
@@ -175,12 +176,18 @@ async function showFile(file, revision) {
};
}
}
+
+ // Override css class when selected
+ if (nb === selectedLine) {
+ cssClass = "selected";
+ }
return {
nb,
hits,
coverage,
line: line || " ",
- covered: cssClass
+ css_class: cssClass,
+ route: buildRoute({ line: nb })
};
})
};
@@ -189,6 +196,15 @@ async function showFile(file, revision) {
hide("history");
const output = render("file_coverage", context, "output");
+ // Scroll to line
+ if (selectedLine > 0) {
+ const line = output.querySelector("#l" + selectedLine);
+ line.scrollIntoView({
+ behavior: "smooth",
+ block: "center"
+ });
+ }
+
// Highlight source code once displayed
Prism.highlightAll(output);
}
@@ -218,11 +234,20 @@ async function load() {
};
}
+ // Default to directory view on home
+ if (!route.view) {
+ route.view = VIEW_DIRECTORY;
+ }
+
try {
- var [coverage, history, filters] = await Promise.all([
+ const viewContent =
+ route.view === VIEW_DIRECTORY
+ ? getHistory(route.path, route.platform, route.suite)
+ : getSource(route.path, route.revision);
+ var [coverage, filters, viewData] = await Promise.all([
getPathCoverage(route.path, route.revision, route.platform, route.suite),
- getHistory(route.path, route.platform, route.suite),
- getFilters()
+ getFilters(),
+ viewContent
]);
} catch (err) {
console.warn("Failed to load coverage", err);
@@ -230,37 +255,40 @@ async function load() {
message("error", "Failed to load coverage: " + err.message);
throw err;
}
-
return {
- view: VIEW_BROWSER,
+ view: route.view,
path: route.path,
revision: route.revision,
route,
coverage,
- history,
- filters
+ filters,
+ viewData
};
}
-async function display(data) {
+export async function display(data) {
if (data.view === VIEW_ZERO_COVERAGE) {
await zeroCoverageMenu(data.route);
await zeroCoverageDisplay(data.zeroCoverage, data.path);
- } else if (data.view === VIEW_BROWSER) {
+ } else if (data.view === VIEW_DIRECTORY) {
+ hide("message");
browserMenu(data.revision, data.filters, data.route);
-
- if (data.coverage.type === "directory") {
- hide("message");
- await graphHistory(data.history, data.path);
- await showDirectory(data.path, data.revision, data.coverage.children);
- } else if (data.coverage.type === "file") {
- await showFile(data.coverage, data.revision);
- } else {
- message("error", "Invalid file type: " + data.coverate.type);
- }
+ await graphHistory(data.viewData, data.path);
+ await showDirectory(data.path, data.revision, data.coverage.children);
+ } else if (data.view === VIEW_FILE) {
+ browserMenu(data.revision, data.filters, data.route);
+ await showFile(
+ data.viewData,
+ data.coverage,
+ data.revision,
+ data.route.line
+ );
} else {
message("error", "Invalid view : " + data.view);
}
+
+ // Always monitor options on newly rendered output
+ monitorOptions(data);
}
main(load, display);
diff --git a/frontend/src/route.js b/frontend/src/route.js
index 58eb2de0b..81328afa2 100644
--- a/frontend/src/route.js
+++ b/frontend/src/route.js
@@ -1,4 +1,5 @@
import { REV_LATEST } from "./common.js";
+import { display } from "./index.js";
export function readRoute() {
// Reads all filters from current URL hash
@@ -42,5 +43,51 @@ export function buildRoute(params) {
export function updateRoute(params) {
// Update full hash with an updated url
+ // Will trigger full load + display update
window.location.hash = buildRoute(params);
}
+
+export async function updateRouteImmediate(hash, data) {
+ // Will trigger only a display update, no remote data will be fetched
+
+ // Update route without reloading content
+ history.pushState(null, null, hash);
+
+ // Update the route stored in data
+ data.route = readRoute();
+ await display(data);
+}
+
+export function monitorOptions(currentData) {
+ // Monitor input & select changes
+ const fields = document.querySelectorAll("input, select, a.scroll");
+ for (const field of fields) {
+ if (field.classList.contains("scroll")) {
+ // On a scroll event, update display without any data loading
+ field.onclick = async evt => {
+ evt.preventDefault();
+ updateRouteImmediate(evt.target.hash, currentData);
+ };
+ } else if (field.type === "text") {
+ // React on enter
+ field.onkeydown = async evt => {
+ if (evt.keyCode === 13) {
+ const params = {};
+ params[evt.target.name] = evt.target.value;
+ updateRoute(params);
+ }
+ };
+ } else {
+ // React on change
+ field.onchange = async evt => {
+ let value = evt.target.value;
+ if (evt.target.type === "checkbox") {
+ value = evt.target.checked ? "on" : "off";
+ }
+ const params = {};
+ params[evt.target.name] = value;
+ updateRoute(params);
+ };
+ }
+ }
+}
diff --git a/frontend/src/style.scss b/frontend/src/style.scss
index 58fa85449..2f5449ad4 100644
--- a/frontend/src/style.scss
+++ b/frontend/src/style.scss
@@ -8,6 +8,7 @@ $footer_height: 60px;
$coverage_low: #d91a47;
$coverage_warn: #ff9a36;
$coverage_good: #438718;
+$highlighted: #f7f448;
$small_screen: 1900px;
body {
@@ -352,6 +353,17 @@ $samp_size: 20px;
background: $uncovered_color;
}
}
+
+ &.selected {
+ font-weight: bold;
+ td {
+ background: $highlighted;
+ }
+
+ pre {
+ background: $highlighted;
+ }
+ }
}
}
}