new design for rendertest
This commit is contained in:
parent
d20af78fa7
commit
8b075645e7
307
render/base.css
Normal file
307
render/base.css
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
html, body {
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-family: system-ui;
|
||||||
|
/*font-variant: all-petite-caps;*/
|
||||||
|
}
|
||||||
|
*, *:before, *:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
h1,h2,h3,h4,h5 {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji:after {
|
||||||
|
font-variant-emoji: emoji; /* experimental and only in firefox :( */
|
||||||
|
content: '\FE0F'; /* fuck off, give me emoji // https://codepoints.net/U+FE0F */
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
text-align: center;
|
||||||
|
color: #888;
|
||||||
|
margin: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-items {
|
||||||
|
z-index: 99;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-item {
|
||||||
|
background-color: #fefefe;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-item:hover {
|
||||||
|
background-color: #e9e9e9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-inactive {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-inactive .group {
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-active {
|
||||||
|
padding: 5px;
|
||||||
|
background-color: DodgerBlue !important;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-active .group {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
table, tr, td, th {
|
||||||
|
border: 1px solid #888;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--caption-size: 2rem;
|
||||||
|
--outline-width: 2px;
|
||||||
|
--outline-width-: calc(0px - var(--outline-width));
|
||||||
|
--options-height: 18.6em; /* FIXME */
|
||||||
|
--options-height-min: 17em;
|
||||||
|
--image-height: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.grid-container {
|
||||||
|
display: grid;
|
||||||
|
width: 100%;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
grid-template-areas: "sidebar content";
|
||||||
|
column-gap: 10px;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
.grid-container.reversed {
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
grid-template-areas: "content sidebar";
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
grid-area: sidebar;
|
||||||
|
/*position: fixed;*/
|
||||||
|
height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
white-space: nowrap; /* ugly firefox */
|
||||||
|
overflow-block: scroll; /* ugly firefox */
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 2px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
#options {
|
||||||
|
height: var(--options-height);
|
||||||
|
}
|
||||||
|
#table { }
|
||||||
|
#fail_images {
|
||||||
|
display: grid;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
display: block;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.panel > div {
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
.sticky {
|
||||||
|
top: 0;
|
||||||
|
position: sticky;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
grid-area: content;
|
||||||
|
overflow: auto;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.min { /* split feature */
|
||||||
|
width: 75%; margin: 0 auto; /* FIXME */
|
||||||
|
}
|
||||||
|
.min .sidebar {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.min .sidebar .panel > div {
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
.min .sidebar #options.panel {
|
||||||
|
height: var(--options-height-min);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@keyframes flash {
|
||||||
|
0% {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
49% {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.block {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
max-height: var(--image-height);
|
||||||
|
}
|
||||||
|
.block:after {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 25px;
|
||||||
|
|
||||||
|
font-family: Impact;
|
||||||
|
font-size: var(--caption-size);
|
||||||
|
/*-webkit-text-stroke: 1px black;*/ /*ugly*/
|
||||||
|
text-shadow:
|
||||||
|
var(--outline-width) var(--outline-width) 0 #000,
|
||||||
|
var(--outline-width-) var(--outline-width) 0 #000,
|
||||||
|
var(--outline-width-) var(--outline-width-) 0 #000,
|
||||||
|
var(--outline-width) var(--outline-width-) 0 #000;
|
||||||
|
}
|
||||||
|
.block-test:after {
|
||||||
|
/*content: "TEST 🧪\FE0F";*/
|
||||||
|
content: "TEST";
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.block-gold:after {
|
||||||
|
/*content: "GOLD 🏆\FE0F";*/
|
||||||
|
content: "GOLD";
|
||||||
|
color: gold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
.block:before {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 20px;
|
||||||
|
}
|
||||||
|
.block-test:before {
|
||||||
|
content: "🧪\FE0F";
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.block-gold:before {
|
||||||
|
content: "🏆\FE0F";
|
||||||
|
color: gold;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
.block-gold {
|
||||||
|
position: absolute;
|
||||||
|
animation: flash var(--animation-duration) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--animation-duration: 0.7s;
|
||||||
|
--height-image: calc((100vh - 800px) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-container,
|
||||||
|
.meta-block {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-block h2 {
|
||||||
|
/*display: none;*/
|
||||||
|
font-family: monospace;
|
||||||
|
/*font-family: "Gill Sans", sans-serif;*/
|
||||||
|
padding-top: 10px;
|
||||||
|
margin-top: revert;
|
||||||
|
}
|
||||||
|
.meta-block h2:before {
|
||||||
|
content: "🎯\FE0F"; /* for hedging her bets; see .emoji for details */
|
||||||
|
}
|
||||||
|
#fail_images.padding .meta-block {
|
||||||
|
/*margin-block: 100vh;*/
|
||||||
|
padding-top: calc(var(--height-image));
|
||||||
|
/*padding-bottom: calc(50% - var(--height-image) );*/
|
||||||
|
}
|
||||||
|
#fail_images.padding .meta-block h2, .meta-block:first-child h2 {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-diff {
|
||||||
|
display: none;
|
||||||
|
/*visibility: collapse;*/
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
/*z-index: 10;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-diff.show_diff {
|
||||||
|
display: block;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.image-container, img, .block {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
border-collapse: collapse;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
padding: 0.15rem;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
|
||||||
|
}
|
||||||
|
th.table-sticky {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
#options.sticky + #table th.table-sticky {
|
||||||
|
top: calc(var(--options-height) + 0.07em); /* FIXME */
|
||||||
|
}
|
||||||
|
.min .sidebar #options.sticky + #table th.table-sticky {
|
||||||
|
top: calc(var(--options-height-min) + 0.07em); /* FIXME */
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=range] { vertical-align: middle; }
|
||||||
|
|
||||||
|
a, a:visited {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ul.list {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
ul.list li { display: inline-block; margin-left: 10px; } /* FIXME: flex or grid */
|
||||||
|
ul.list li:first-child { margin-left: 0;}
|
303
render/index.js
303
render/index.js
@ -9,13 +9,13 @@ function makeId(d) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function rowAttribs(d) {
|
function rowAttribs(d) {
|
||||||
return d.fail ? {style: `background-color: rgb(128, 0, 0)`} : null;
|
return d.fail ? {"class": "fail"} : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sectionLink(value, d) {
|
function sectionLink(value, d) {
|
||||||
const id = makeId(d);
|
const id = makeId(d);
|
||||||
const a = Tag('a', {href: '#'+id}, value);
|
const a = Tag('a', {href: '#'+id}, value);
|
||||||
a.addEventListener('click', () => { $(id).setAttribute('open', true); });
|
//a.addEventListener('click', () => { $(id).setAttribute('open', true); });
|
||||||
return [a];
|
return [a];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ function makeTable(fields, data, attrs_func) {
|
|||||||
Tag('tr', null, null, (() => {
|
Tag('tr', null, null, (() => {
|
||||||
let tds = [];
|
let tds = [];
|
||||||
for (const f of fields) {
|
for (const f of fields) {
|
||||||
tds.push(Tag('td', null, null, [Text(f.label)]));
|
tds.push(Tag('th', {"class": "table-sticky"}, null, [Text(f.label)]));
|
||||||
}
|
}
|
||||||
return tds;
|
return tds;
|
||||||
})()),
|
})()),
|
||||||
@ -36,12 +36,17 @@ function makeTable(fields, data, attrs_func) {
|
|||||||
table.appendChild(Tag('tr', attrs, null, (() => {
|
table.appendChild(Tag('tr', attrs, null, (() => {
|
||||||
let ret = [];
|
let ret = [];
|
||||||
for (const f of fields) {
|
for (const f of fields) {
|
||||||
const value = d[f.field];
|
let value = d[f.field];
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
ret.push(Tag('td'));
|
ret.push(Tag('td'));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: remove ugly hack
|
||||||
|
if (f.field == "fail") {
|
||||||
|
value = value ? "❌" : "✅";
|
||||||
|
}
|
||||||
|
|
||||||
const field_func = f.tags_func ? f.tags_func : fieldFuncDefault;
|
const field_func = f.tags_func ? f.tags_func : fieldFuncDefault;
|
||||||
const tags = field_func(value, d);
|
const tags = field_func(value, d);
|
||||||
let attrs = null;
|
let attrs = null;
|
||||||
@ -78,8 +83,10 @@ function buildTestResultsTable(data) {
|
|||||||
// Filter out all success
|
// Filter out all success
|
||||||
function filterData(data) {
|
function filterData(data) {
|
||||||
return data
|
return data
|
||||||
.filter((d) => {return d.diff_pct != 0;})
|
.filter((d) => { return d.diff_pct !== 0; })
|
||||||
.sort((l, r) => {return l.diff_pct < r.diff_pct;});
|
.sort((l, r) => {
|
||||||
|
return l.diff_pct < r.diff_pct ? 1 : -1; // RTFM
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSummary(data) {
|
function buildSummary(data) {
|
||||||
@ -91,20 +98,286 @@ function buildSummary(data) {
|
|||||||
|
|
||||||
function buildTestResultImages(data) {
|
function buildTestResultImages(data) {
|
||||||
return data.flatMap((d) => {
|
return data.flatMap((d) => {
|
||||||
return Tag('details', {id: makeId(d)}, null, [
|
//return Tag('details', {id: makeId(d)}, null, [
|
||||||
Tag('summary', null, `${d.test}/${d.channel} δ=${d.diff_pct}`),
|
return Tag('div', {id: makeId(d), "class": "meta-block"}, null, [
|
||||||
Tag('img', {src: d.image_diff}),
|
//Tag('summary', null, `${d.test}/${d.channel} δ=${d.diff_pct}`),
|
||||||
Tag('img', {src: d.image_flip}),
|
Tag('h2', null, `${d.test}/${d.channel} δ=${d.diff_pct}`),
|
||||||
|
Tag('div', {"class": "image-container"}, null, [
|
||||||
|
Tag('div', {"class": "block block-gold"}, null, [Tag('img', {src: d.image_gold, loading: "lazy", "class": "gold"})]),
|
||||||
|
Tag('div', {"class": "block block-test"}, null, [Tag('img', {src: d.image_test, loading: "lazy", "class": "test"})]),
|
||||||
|
Tag('div', {"class": "block block-diff"}, null, [Tag('img', {src: d.image_diff, loading: "lazy", "class": "diff"})]),
|
||||||
|
])
|
||||||
|
//Tag('img', {src: d.image_flip}),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onload = () => {
|
|
||||||
$('summary').replaceChildren(...buildSummary(data));
|
|
||||||
|
|
||||||
const filtered_data = filterData(data);
|
|
||||||
$('fail_table').replaceChildren(...buildTestResultsTable(filtered_data));
|
function buildData(table, images, data, sort) {
|
||||||
$('fail_images').replaceChildren(...buildTestResultImages(filtered_data));
|
if (sort) {
|
||||||
|
data = filterData(data);
|
||||||
|
}
|
||||||
|
table.replaceChildren(...buildTestResultsTable(data));
|
||||||
|
images.replaceChildren(...buildTestResultImages(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = () => {
|
||||||
|
const linkElements = document.querySelectorAll('link[rel^="stylesheet"][data-theme]');
|
||||||
|
const themeOptionsDiv = document.querySelector(".theme-options");
|
||||||
|
|
||||||
|
function updateLinkRel(linkElements, id) {
|
||||||
|
for (const link of linkElements) {
|
||||||
|
const themeName = link.getAttribute("data-theme");
|
||||||
|
link.disabled = themeName !== id;
|
||||||
|
link.rel = themeName === id && link.rel.includes("alternate")
|
||||||
|
? link.rel.replace(" alternate", "")
|
||||||
|
: link.rel + " alternate";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// themes
|
||||||
|
let themes = [];
|
||||||
|
for (const linkElement of linkElements) {
|
||||||
|
const themeName = linkElement.getAttribute("data-theme");
|
||||||
|
const checked = !linkElement.rel.includes("alternate") ? {"checked": "checked"} : {};
|
||||||
|
themes.push(
|
||||||
|
Tag("label", null, null, [
|
||||||
|
Tag("input", {"type": "radio", "class": "theme-button", "id": themeName, "name": "theme", ...checked}, null, null,
|
||||||
|
"click", (e) => {
|
||||||
|
updateLinkRel(linkElements, e.target.id);
|
||||||
|
}),
|
||||||
|
Text(linkElement.title)
|
||||||
|
])
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function changeAnimationDuration(newDuration) {
|
||||||
|
document.documentElement.style.setProperty('--animation-duration', newDuration + 'ms');
|
||||||
|
}
|
||||||
|
const saveToLocalStorage = debounce((key, value) => {
|
||||||
|
localStorage.setItem(key, value);
|
||||||
|
});
|
||||||
|
const loadFromLocalStorage = localStorage.getItem;
|
||||||
|
|
||||||
|
|
||||||
|
function syncSliderValues(event) {
|
||||||
|
changeAnimationDuration(event.target.value);
|
||||||
|
const value = event.target.value;
|
||||||
|
const input = event.target;
|
||||||
|
const otherInput = input === rangeInput ? numberInput : rangeInput;
|
||||||
|
otherInput.value = value;
|
||||||
|
saveToLocalStorage("rendertest_switchfrequency", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
let isPaused = false;
|
||||||
|
let gridContainer, sidebarOptions, numberInput, rangeInput, table, images;
|
||||||
|
|
||||||
|
gridContainer = Tag("div", {"class": "grid-container"}, null, [
|
||||||
|
Tag("div", {"class": "sidebar"}, null, [
|
||||||
|
sidebarOptions = Tag("div", {"class": "panel", "id": "options"}, null, [
|
||||||
|
Tag("div", {"class": "theme-options"}, null, [
|
||||||
|
Tag("b", null, null, [
|
||||||
|
Tag("span", {"class": "emoji"}, "🎨"),
|
||||||
|
Text(" Theme")
|
||||||
|
]),
|
||||||
|
...themes
|
||||||
|
]),
|
||||||
|
Tag("div", null, null, [
|
||||||
|
Tag("b", null, null, [
|
||||||
|
Tag("span", {"class": "emoji"}, "🌈"),
|
||||||
|
Text(" Show diff mode on")
|
||||||
|
]),
|
||||||
|
Tag("label", null, null, [
|
||||||
|
Tag("input", {"type": "radio", "name": "diff_mode", "value": "click", "checked": "checked"}),
|
||||||
|
Tag("span", {"class": "emoji"}, "🖱"),
|
||||||
|
Text(" Click (LMB)")
|
||||||
|
]),
|
||||||
|
Tag("label", null, null, [
|
||||||
|
Tag("input", {"type": "radio", "name": "diff_mode", "value": "separate", "disabled": "disabled"}),
|
||||||
|
Tag("span", {"class": "emoji"}, "🎏"),
|
||||||
|
Text(" Separate")
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
Tag("div", null, null, [
|
||||||
|
Tag("b", null, null, [
|
||||||
|
Tag("span", {"class": "emoji"}, "🤼"),
|
||||||
|
Text(" Image compare mode")
|
||||||
|
]),
|
||||||
|
Tag("label", null, null, [
|
||||||
|
Tag("input", {"type": "radio", "name": "compare_mode", "value": "switch", "checked": "checked"}),
|
||||||
|
Tag("span", {"class": "emoji"}, "🎞"),
|
||||||
|
Text(" Switch images")
|
||||||
|
]),
|
||||||
|
Tag("label", {"title": "under construction"}, null, [
|
||||||
|
Tag("input", {"type": "radio", "name": "compare_mode", "value": "split", "disabled": "disabled"}),
|
||||||
|
Tag("span", {"class": "emoji"}, "🪓"),
|
||||||
|
Text(" Split images")
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
Tag("div", null, null, [
|
||||||
|
Tag("b", null, null, [
|
||||||
|
Tag("span", {"class": "emoji"}, "🎢"),
|
||||||
|
Text(" Speed switch image")
|
||||||
|
]),
|
||||||
|
rangeInput = Tag("input", {"type": "range", "style": "width: 140px;", "min": "10", "max": "1000", "value": "700"}, null, null, "input", syncSliderValues),
|
||||||
|
numberInput = Tag("input", {"type": "number", "size": "5", "min": "10", "max": "1000", "value": "700"}, null, null, "input", syncSliderValues),
|
||||||
|
Text(" ms "),
|
||||||
|
Tag("button", null, "pause", null, "click", (e) => {
|
||||||
|
//e.target.textContent = isPaused ? "❄ pause" : "🔥 resume";
|
||||||
|
e.target.textContent = isPaused ? "pause" : "resume";
|
||||||
|
changeAnimationDuration(isPaused ? numberInput.value : 0);
|
||||||
|
isPaused = !isPaused;
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
Tag("div", null, null, [
|
||||||
|
Tag("b", null, null, [
|
||||||
|
Tag("span", {"class": "emoji"}, "🧻"),
|
||||||
|
Text(" Sidebar")
|
||||||
|
]),
|
||||||
|
Tag("label", null, null, [
|
||||||
|
Tag("input", {"type": "radio", "name": "sidebar-mode", "value": "minimize", "checked": "checked"}, null, null, "input", (e) => {
|
||||||
|
sidebarOptions.classList.toggle("sticky")
|
||||||
|
}),
|
||||||
|
Text(" auto minimize")
|
||||||
|
]),
|
||||||
|
Tag("label", null, null, [
|
||||||
|
Tag("input", {"type": "radio", "name": "sidebar-mode", "value": "always"}, null, null, "input", (e) => {
|
||||||
|
sidebarOptions.classList.toggle("sticky")
|
||||||
|
}),
|
||||||
|
Text(" show always")
|
||||||
|
]),
|
||||||
|
Tag("ul", {"class": "list"}, null, [
|
||||||
|
Tag("li", null, null, [
|
||||||
|
Tag("span", {"class": "emoji"}, "🧲"),
|
||||||
|
Text(" position"),
|
||||||
|
Tag("label", null, null, [
|
||||||
|
Tag("input", {"type": "radio", "name": "sidebar_pos", "value": "left", "checked": "checked"}, null, null, "input", (e) => {
|
||||||
|
console.log(e);
|
||||||
|
gridContainer.classList.toggle("reversed");
|
||||||
|
}),
|
||||||
|
Text(" left")
|
||||||
|
]),
|
||||||
|
Tag("label", null, null, [
|
||||||
|
Tag("input", {"type": "radio", "name": "sidebar_pos", "value": "right"}, null, null, "input", (e) => {
|
||||||
|
console.log(e);
|
||||||
|
gridContainer.classList.toggle("reversed");
|
||||||
|
}),
|
||||||
|
Text(" right")
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
Tag("li", null, null, [
|
||||||
|
Tag("span", {"class": "emoji"}, "📏"),
|
||||||
|
Text(" size"),
|
||||||
|
Tag("label", null, null, [
|
||||||
|
Tag("input", {"type": "radio", "name": "sidebar-size", "value": "always", "checked": "checked"}, null, null, "input", (e) => {
|
||||||
|
document.body.classList.toggle("min");
|
||||||
|
}),
|
||||||
|
Text(" default")
|
||||||
|
]),
|
||||||
|
Tag("label", null, null, [
|
||||||
|
Tag("input", {"type": "radio", "name": "sidebar-size", "value": "minimize"}, null, null, "input", (e) => {
|
||||||
|
document.body.classList.toggle("min");
|
||||||
|
}),
|
||||||
|
Text(" mini")
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
Tag("div", null, null, [
|
||||||
|
Tag("b", null, null, [
|
||||||
|
Tag("span", {"class": "emoji"}, "⚖"),
|
||||||
|
Text(" Image position")
|
||||||
|
]),
|
||||||
|
Tag("label", null, null, [
|
||||||
|
Tag("input", {"type": "radio", "name": "image_position", "value": "relative", "disabled": "disabled"}),
|
||||||
|
Text(" relative")
|
||||||
|
]),
|
||||||
|
Tag("label", null, null, [
|
||||||
|
Tag("input", {"type": "radio", "name": "image_position", "value": "center", "checked": "checked"}),
|
||||||
|
Text(" center")
|
||||||
|
]),
|
||||||
|
Tag("b", null, null, [
|
||||||
|
Tag("span", {"class": "emoji"}, "🖼"),
|
||||||
|
Tag("label", null, null, [
|
||||||
|
Text(" Image padding"),
|
||||||
|
Tag("input", {"type": "checkbox", "name": "all_diff", "checked": "checked"}, null, null, "input", (e) => {
|
||||||
|
images.classList.toggle("padding");
|
||||||
|
})
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
Tag("div", null, null, [
|
||||||
|
Tag("b", null, null, [
|
||||||
|
Tag("span", {"class": "emoji"}, "📷"),
|
||||||
|
Tag("label", null, null, [
|
||||||
|
Text(" Toggle diff for all"),
|
||||||
|
Tag("input", {"type": "checkbox", "name": "all_diff", "disabled": "disabled"})
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
Tag("div", null, null, [
|
||||||
|
Tag("b", null, null, [
|
||||||
|
Tag("span", {"class": "emoji"}, "📊"),
|
||||||
|
Text(" Sort table by Failed")
|
||||||
|
]),
|
||||||
|
Tag("label", null, null, [
|
||||||
|
Tag("input", {"type": "radio", "name": "sort_table", "value": "yes", "checked": "checked"}, null, null, "input", (e) => {
|
||||||
|
buildData(table, images, data, true);
|
||||||
|
}),
|
||||||
|
Text(" yes")
|
||||||
|
]),
|
||||||
|
Tag("label", null, null, [
|
||||||
|
Tag("input", {"type": "radio", "name": "sort_table", "value": "no"}, null, null, "input", (e) => {
|
||||||
|
buildData(table, images, data, false);
|
||||||
|
}),
|
||||||
|
Text(" no")
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
Tag("div", {"class": "panel", "id": "table"}, null, [
|
||||||
|
Tag("h2", null, "List of things that are not perfect"),
|
||||||
|
table = Tag("div", {"id": "fail_table"})
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
Tag("div", {"class": "content"}, null, [
|
||||||
|
Tag("h1", null, "Rendertest report"),
|
||||||
|
Tag("h2", null, "Summary"),
|
||||||
|
Tag("div", {"id": "summary"}, null, [...buildSummary(data)]),
|
||||||
|
Tag("h2", null, "Images of things that are not perfect"),
|
||||||
|
images = Tag("div", {"id": "fail_images", "class": "padding"})
|
||||||
|
])
|
||||||
|
])
|
||||||
|
document.body.appendChild(gridContainer);
|
||||||
|
|
||||||
|
buildData(table, images, data, true);
|
||||||
|
|
||||||
|
for (let block of document.querySelectorAll(".image-container")) {
|
||||||
|
block.addEventListener("click", function() {
|
||||||
|
let diffElement = this.querySelector(".block-diff");
|
||||||
|
diffElement.classList.toggle('show_diff');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function handleCheckboxChange(checkboxId, key) {
|
||||||
|
var checkbox = document.getElementById(checkboxId);
|
||||||
|
saveToLocalStorage(key, checkbox.checked);
|
||||||
|
}
|
||||||
|
function handleRadioChange(radioGroupId, key) {
|
||||||
|
var radios = document.getElementsByName(radioGroupId);
|
||||||
|
for (var i = 0; i < radios.length; i++) {
|
||||||
|
if (radios[i].checked) {
|
||||||
|
saveToLocalStorage(key, radios[i].value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ import subprocess
|
|||||||
|
|
||||||
ROOT = os.path.dirname(os.path.abspath(__file__))
|
ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||||
imagecompare = f'{ROOT}/imagecompare'
|
imagecompare = f'{ROOT}/imagecompare'
|
||||||
|
#convert = f'convert' # set path for imagemagick convert if need
|
||||||
|
convert = f'A:/imagemagick-q8/convert'
|
||||||
WORKDIR = f'{ROOT}/work'
|
WORKDIR = f'{ROOT}/work'
|
||||||
REPORT_ROOT = f'{ROOT}' # FIXME should be workdir?
|
REPORT_ROOT = f'{ROOT}' # FIXME should be workdir?
|
||||||
|
|
||||||
@ -150,21 +152,25 @@ def compare():
|
|||||||
mkdir_p(WORKDIR)
|
mkdir_p(WORKDIR)
|
||||||
screenshot_base = f'{args.xash_dir}/valve/rendertest'
|
screenshot_base = f'{args.xash_dir}/valve/rendertest'
|
||||||
diffs = []
|
diffs = []
|
||||||
|
command_png()
|
||||||
|
print(f'Compare...')
|
||||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||||
for test in args.tests:
|
for test in args.tests:
|
||||||
for channel, _ in channels.items():
|
for channel, _ in channels.items():
|
||||||
image_base = f'{test}_{channel}'
|
image_base = f'{test}_{channel}'
|
||||||
image_test = f'{screenshot_base}/{image_base}.tga'
|
#image_test = f'{screenshot_base}/{image_base}.tga'
|
||||||
|
image_test = f'{ROOT}/work/gold/{image_base}.png'
|
||||||
image_gold = f'{ROOT}/gold/{image_base}.png'
|
image_gold = f'{ROOT}/gold/{image_base}.png'
|
||||||
image_diff = f'{WORKDIR}/{image_base}_diff.png'
|
image_diff = f'{WORKDIR}/{image_base}_diff.png'
|
||||||
image_flip = f'{WORKDIR}/{image_base}_flip.gif'
|
image_flip = f'{WORKDIR}/{image_base}_flip.gif'
|
||||||
|
|
||||||
diffs.append(executor.submit(compare_one, test, channel, image_base, image_gold, image_test, image_diff, image_flip))
|
diffs.append(executor.submit(compare_one, test, channel, image_base, image_gold, image_test, image_diff, image_flip))
|
||||||
|
|
||||||
executor.submit(subprocess.run, ['convert',
|
# legacy
|
||||||
'(', image_gold, '-bordercolor', 'gold', '-border', '2x2', '-gravity', 'SouthWest', '-font', 'Impact', '-pointsize', '24', '-fill', 'gold', '-stroke', 'black', '-annotate', '0', 'GOLD', ')',
|
#executor.submit(subprocess.run, [convert,
|
||||||
'(', image_test, '-bordercolor', 'white', '-border', '2x2', '-fill', 'white', '-annotate', '0', 'TEST', ')',
|
# '(', image_gold, '-bordercolor', 'gold', '-border', '2x2', '-gravity', 'SouthWest', '-font', 'Impact', '-pointsize', '24', '-fill', 'gold', '-stroke', 'black', '-annotate', '0', 'GOLD', ')',
|
||||||
'-loop', '0', '-set', 'delay', '100', image_flip], check=True)
|
# '(', image_test, '-bordercolor', 'white', '-border', '2x2', '-fill', 'white', '-annotate', '0', 'TEST', ')',
|
||||||
|
# '-loop', '0', '-set', 'delay', '100', image_flip], check=True)
|
||||||
|
|
||||||
results = [diff.result() for diff in diffs]
|
results = [diff.result() for diff in diffs]
|
||||||
# json.dump(results, open(f'{WORKDIR}/data.json', 'w'))
|
# json.dump(results, open(f'{WORKDIR}/data.json', 'w'))
|
||||||
@ -187,8 +193,8 @@ def command_png():
|
|||||||
image_test = f'{screenshot_base}/{image_base}.tga'
|
image_test = f'{screenshot_base}/{image_base}.tga'
|
||||||
image_new_gold = f'{new_gold_base}/{image_base}.png'
|
image_new_gold = f'{new_gold_base}/{image_base}.png'
|
||||||
|
|
||||||
print(f'{image_new_gold}')
|
print(f'convert to {image_new_gold}')
|
||||||
executor.submit(subprocess.run, ['convert', image_test, image_new_gold], check=True)
|
executor.submit(subprocess.run, [convert, "-auto-orient", image_test, image_new_gold], check=True)
|
||||||
|
|
||||||
def command_run():
|
def command_run():
|
||||||
compile()
|
compile()
|
||||||
@ -216,4 +222,3 @@ match args.command:
|
|||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
# - settings object to pass as an argument
|
# - settings object to pass as an argument
|
||||||
# - HTML report
|
|
||||||
|
@ -3,22 +3,15 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Rendertest report</title>
|
<title>Rendertest report</title>
|
||||||
<link rel="stylesheet" href="style.css">
|
<link rel="stylesheet" type="text/css" href="base.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="theme-dark.css"; data-theme="theme-dark" title="🌑 Dark theme">
|
||||||
|
<link rel="stylesheet alternate" type="text/css" href="theme-light.css" disabled="true" data-theme="theme-light" title="☀️ Light theme">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<script src="utils.js"></script>
|
<script src="utils.js"></script>
|
||||||
<script src="work/data.js"></script>
|
<script src="work/data.js"></script>
|
||||||
<script src="index.js"></script>
|
<script src="index.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- TODO have a date and build number or whatever -->
|
<!-- TODO: have a date and build number or whatever -->
|
||||||
<h1>Rendertest report</h1>
|
|
||||||
<h2>Summary</h2>
|
|
||||||
<div id="summary"></div>
|
|
||||||
|
|
||||||
<h2>List of things that are not perfect</h2>
|
|
||||||
<div id="fail_table"></div>
|
|
||||||
|
|
||||||
<h2>Images of things that are not perfect</h2>
|
|
||||||
<div id="fail_images"></div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
45
render/theme-dark.css
Normal file
45
render/theme-dark.css
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
body {
|
||||||
|
background: black;
|
||||||
|
color: #EEE;
|
||||||
|
scrollbar-color: #67005c #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
background: #222;
|
||||||
|
border: #333 1px solid;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: cornflowerblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-block h2 {
|
||||||
|
color: gold;
|
||||||
|
border-top: 3px gray solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
color: #eb4300;
|
||||||
|
border: #e100ca 1px solid;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
table tr:hover {
|
||||||
|
background: oklch(76.47% 0.279 334.17);
|
||||||
|
background: #512649;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
/*background: oklch(62.5% 0.291 308.12);*/
|
||||||
|
background: linear-gradient(0deg,#b540ff,cornflowerblue);
|
||||||
|
box-shadow: 3px 3px 3px 3px rgba(0, 0, 0, 0.5);
|
||||||
|
text-shadow: 2px 1px 3px #000A;
|
||||||
|
}
|
||||||
|
a, a:visited {
|
||||||
|
color: #e100ca;
|
||||||
|
color: oklch(62.5% 0.291 334.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/*--font-gradient: linear-gradient(90deg,#7209d4,#2832d4 33%,#00a5b2);*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
scrollbar-color: #e100ca #222;
|
||||||
|
}
|
35
render/theme-light.css
Normal file
35
render/theme-light.css
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
body {
|
||||||
|
background: white;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
background: #FFF;
|
||||||
|
border: #EEE 1px solid;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: cornflowerblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-block h2 {
|
||||||
|
color: goldenrod;
|
||||||
|
border-top: 3px lightgray solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
color: #eb4300;
|
||||||
|
border: #e100ca 1px solid;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
background: oklch(62.5% 0.291 308.12);
|
||||||
|
background: linear-gradient(0deg,#b540ff,cornflowerblue);
|
||||||
|
/* box-shadow: 3px 3px 3px 3px rgba(0, 0, 0, 0.5);*/
|
||||||
|
}
|
||||||
|
a, a:visited {
|
||||||
|
color: #e100ca;
|
||||||
|
color: oklch(62.5% 0.291 334.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/*--font-gradient: linear-gradient(90deg,#7209d4,#2832d4 33%,#00a5b2);*/
|
||||||
|
}
|
@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
function $(name) { return document.getElementById(name); }
|
function $(name) { return document.getElementById(name); }
|
||||||
|
|
||||||
function Tag(name, attrs, body, children) {
|
function Tag(name, attrs, body, children, eventName, eventHandler) {
|
||||||
let elem = document.createElement(name);
|
let elem = document.createElement(name);
|
||||||
if (body) {
|
if (body) {
|
||||||
elem.innerHTML = body;
|
// elem.innerHTML = body; // innerHTML is ugly (slow, overwrite childNodes)
|
||||||
|
let textNode = document.createTextNode(body);
|
||||||
|
elem.appendChild(textNode);
|
||||||
}
|
}
|
||||||
for (let k in attrs) {
|
for (let k in attrs) {
|
||||||
elem.setAttribute(k, attrs[k]);
|
elem.setAttribute(k, attrs[k]);
|
||||||
@ -13,6 +15,11 @@ function Tag(name, attrs, body, children) {
|
|||||||
for (let i in children) {
|
for (let i in children) {
|
||||||
elem.appendChild(children[i]);
|
elem.appendChild(children[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (eventName && eventHandler) {
|
||||||
|
elem.addEventListener(eventName, eventHandler);
|
||||||
|
}
|
||||||
|
|
||||||
return elem;
|
return elem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,7 +27,7 @@ function Text(text) {
|
|||||||
return document.createTextNode(text);
|
return document.createTextNode(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
function debounce(func, cancelFunc, timeout = 500) {
|
function debounce(func, cancelFunc = ()=>{}, timeout = 500) {
|
||||||
let timer;
|
let timer;
|
||||||
return (...args) => {
|
return (...args) => {
|
||||||
cancelFunc();
|
cancelFunc();
|
||||||
|
Loading…
Reference in New Issue
Block a user