Skip to content
Snippets Groups Projects

Vylepšení skenování - prázdné stránky, otáčení, vylepšení UI

Merged Jiří Setnička requested to merge jirka/scans into devel
All threads resolved!
1 file
+ 28
4
Compare changes
  • Side-by-side
  • Inline
@@ -16,6 +16,8 @@ Třídění skenů {{ scans_title }} pro {{ ctx.round.name|lower }} kategorie {{
{% endblock %}
{% block body %}
<div class="alert alert-info"><strong>Pozor:</strong> Skeny zatím nejsou uloženy, je potřeba je nejdříve zkontrolovat a pak odeslat ke zpracování.</div>
<p>Napravo můžete klikáním vybírat jednotlivé naskenované stránky a pomocí vrchních políček je přiřazovat jednotlivým úlohám a soutěžícím. Pokud
je nějaké řešení přes více stránek, musí na sebe navazovat číslování stránek. Až bude vše správně zatříděné, můžete aktuální stav uložit
tlačítkem <b>[Uložit]</b>. Poté můžete celou dávku odeslat ke zpracování pomocí <b>[Ukončit a zpracovat]</b>
@@ -28,7 +30,7 @@ tlačítkem <b>[Uložit]</b>. Poté můžete celou dávku odeslat ke zpracován
<input type="checkbox" class="toggle" id="messages-toggle" {% if errors %}checked{% endif %}>
<label for="messages-toggle" class="toggle">
Kontrola třídění (
Výsledek kontrola třídění (
{%- if warning_count > 0 %}{{ warning_count }} varování{% endif -%}
{%- if warning_count > 0 and error_count > 0 %}, {% endif %}
{%- if error_count > 0 %}<span class="error">{{ error_count|inflected('chyba', 'chyby', 'chyb') }}</span>{% endif -%}
@@ -66,15 +68,29 @@ tlačítkem <b>[Uložit]</b>. Poté můžete celou dávku odeslat ke zpracován
#sort_wrapper {
display: grid;
grid-template-columns: auto 350px;
grid-template-rows: 100px auto;
grid-template-rows: 110px auto;
gap: 5px;
}
#sort_wrapper div.controls {
border: 1px #bbbbbb solid;
background-color: #e5e5e5;
border-radius: 5px;
padding: 5px 10px;
}
padding: 4px 10px;
}
#sort_wrapper div.controls .form-group {
margin-bottom: 3px;
}
#sort_wrapper div.controls #text_state {
padding-top: 7px;
font-size: 80%;
font-style: italic;
}
#sort_wrapper div.controls #text_state.ok { color: green; font-weight: bold; }
#sort_wrapper div.controls #text_state.probably_ok { color: green; font-weight: bold; }
#sort_wrapper div.controls #text_state.unknown, #sort_wrapper div.controls #text_state.ufo { color: red; font-weight: bold; }
#sort_wrapper div.controls #text_state.empty { color: #444444; }
#sort_wrapper div.controls #text_state.probably_empty { color: #444444; }
#sort_wrapper div.controls #text_state.ok.changed { color: rgb(128, 109, 0); font-weight: bold; }
#sort_wrapper div.scan {
position: relative;
}
@@ -108,10 +124,12 @@ tlačítkem <b>[Uložit]</b>. Poté můžete celou dávku odeslat ke zpracován
cursor: pointer;
}
#sort_wrapper div.pages table tr.ok { background-color: lightgreen; }
#sort_wrapper div.pages table tr.probably_ok { background-color: #60ffdd; }
#sort_wrapper div.pages table tr.empty { background-color: #aabbff; }
#sort_wrapper div.pages table tr.error { background-color: #ff7777; }
#sort_wrapper div.pages table tr.probably_empty { background-color: #99eeff; }
#sort_wrapper div.pages table tr.unknown, #sort_wrapper div.pages table tr.ufo { background-color: #ff7777; }
#sort_wrapper div.pages table tr.changed { background-color: yellow; }
#sort_wrapper div.pages table tr.empty.changed { background-color: #00fff2; }
#sort_wrapper div.pages table tr.empty.changed { background-color: #00e1ff; }
#sort_wrapper div.pages table tr.active {
color: white;
background-color: black;
@@ -125,7 +143,11 @@ tlačítkem <b>[Uložit]</b>. Poté můžete celou dávku odeslat ke zpracován
<p><b>Ovládání klávesnicí:</b> <b><code></code></b> a <b><code></code></b> – posun ve skenech, <b><code>e</code></b> – přepnutí se na editaci,
<b><code>[esc]</code></b> – vyskočení z editačního políčka při editaci, <b><code>r</code></b> – reset (vrácení) změn u konkrétního skenu,
<b><code>x</code></b> – nastavit stránku jako prázdnou, <b><code>f</code></b> – nastavit stránku jako pokračování minulé.
<b><code>x</code></b> – nastavit stránku jako prázdnou, <b><code>f</code></b> – nastavit stránku jako pokračování minulé,
<b><code>k</code></b> – potvrdit autodetekci (jen u autodetekovaných stránek), <b><code>o</code></b> – otočit stránku o 90° doleva.
<p><b>Vysvětlivky:</b> <span class="icon">🔍</span> odhadnutá prázdná/navazující stránka (doporučujeme zkontrolovat),
<span class="icon"></span> stránka obsahující chyby, <span class="icon"></span> neuložené změny.</p>
<div id="sort_wrapper">
<div class="controls form-horizontal">
@@ -146,6 +168,8 @@ tlačítkem <b>[Uložit]</b>. Poté můžete celou dávku odeslat ke zpracován
<input tabindex=4 class="form-control" type="number" min="1" id="seq_input">
</div>
</div>
<label class="col-sm-2 control-label" for="user_input">Stav:</label>
<div class="col-sm-10" id="text_state"></div>
<span tabindex=5 onfocus="document.getElementById('user_input').focus();"></span>
</div>
<div class="pages" id="rows_scroller">
@@ -155,6 +179,7 @@ tlačítkem <b>[Uložit]</b>. Poté můžete celou dávku odeslat ke zpracován
<th>Úloha
<th>Soutěžící
<th title="Stránka">St.
<th title="Stav stránky, viz vysvětlivky">?
</thead>
<tbody id="pages_rows">
</tbody>
@@ -179,6 +204,8 @@ var users = [
{% endfor %}
];
var keys = ['user_id', 'task_id', 'seq_id', 'state', 'rotation'];
var pages = [
{% for page in pages %}
{
@@ -186,13 +213,20 @@ var pages = [
page_nr: {{ page.page_nr }},
human_nr: "{{ page.human_nr() }}",
user_id: {{ page.user_id if page.user_id else 'null' }},
orig_user_id: {{ page.user_id if page.user_id else 'null' }},
task_id: {{ page.task_id if page.task_id else 'null' }},
orig_task_id: {{ page.task_id if page.task_id else 'null' }},
seq_id: {{ page.seq_id }},
orig_seq_id: {{ page.seq_id }},
state: "{{ page.state.name }}",
rotation: {{ page.rotation }},
orig: {
user_id: {{ page.user_id if page.user_id else 'null' }},
task_id: {{ page.task_id if page.task_id else 'null' }},
seq_id: {{ page.seq_id }},
state: "{{ page.state.name }}",
rotation: {{ page.rotation }},
},
img_full: "{{ png_full(page) }}",
img_small: "{{ png_small(page) }}",
img_preloaded: false,
},
{% endfor %}
];
@@ -210,21 +244,46 @@ var rows = tbody.rows;
var user_input = document.getElementById('user_input');
var task_input = document.getElementById('task_input');
var seq_input = document.getElementById('seq_input');
var text_state = document.getElementById('text_state');
var process_all_button = document.getElementById('process-all-button');
var save_data_field = document.getElementById('save-data');
var rows_scroller = document.getElementById('rows_scroller');
PAGE_FIX = -1;
PAGE_EMPTY = -2;
PAGE_CONTINUE = -3;
PAGE_UFO = -4;
PAGE_OK = 'ok';
PAGE_PROBABLY_OK = 'probably_ok';
PAGE_EMPTY = 'empty';
PAGE_PROBABLY_EMPTY = 'probably_empty';
PAGE_UNKNOWN = 'unknown';
PAGE_UFO = 'ufo';
PRELOAD_COUNT = 3;
function isChanged(p) { return (p.orig_user_id != p.user_id || p.orig_task_id != p.task_id || p.orig_seq_id != p.seq_id); }
function isOk(p) { return (p.user_id && p.task_id && p.seq_id >= 0); }
function isEmpty(p) { return (p.user_id == null && p.task_id == null && p.seq_id == PAGE_EMPTY); }
function isChanged(p) {
for (k of keys) {
if (p[k] != p.orig[k]) { return true; }
}
return false;
}
function isOk(p) { return (p.user_id && p.task_id && p.seq_id > 0); }
function isEmpty(p) { return (p.state == PAGE_EMPTY || p.state == PAGE_PROBABLY_EMPTY); }
function isError(p) { return (!isEmpty(p) && !isOk(p)); }
function isGuess(p) { return (p.state == PAGE_PROBABLY_OK || p.state == PAGE_PROBABLY_EMPTY) }
function anyChanged(p) { return pages.some(isChanged); }
function genTextState(p) {
var out = "";
if (isGuess(p)) {
if (isEmpty(p)) { out = "Automaticky rozpoznaná prázdná stránka, nebude zařazena mezi řešení."; }
else if (isOk(p)) { out = "Automaticky rozpoznaná pokračování úlohy, prosím zkontrolujte." }
} else if (isEmpty(p)) { out = "Prázdná stránka, nebude zařazena mezi řešení."; }
else if (isOk(p)) { out = "Hodnoty správně vyplněny."; }
else if (p.state == PAGE_UFO) { out = "POZOR: Rozpoznán jiný kód. Opravdu skenujete správnou úlohu?"; }
else { out = "Neúplné nebo neplatné hodnoty, potřebuje opravit."; }
if (isChanged(p)) { out +=(" Obsahuje neuložené změny!"); }
return out;
}
function beforeUnload(e) {
e.preventDefault();
return e.returnValue = "Byly provedeny editace, opuštěním stránky je ztratíte. Skutečně opustit stránku?";
@@ -247,22 +306,28 @@ function refreshButtons() {
}
function saveData() {
data = pages.filter(isChanged).map(p => ({file_nr: p.file_nr, page_nr: p.page_nr, user_id: p.user_id, task_id: p.task_id, seq_id: p.seq_id}));
data = pages.filter(isChanged).map(p => {
out = {file_nr: p.file_nr, page_nr: p.page_nr};
keys.forEach(k => { out[k] = p[k] });
return out;
});
save_data_field.value = JSON.stringify(data);
window.onbeforeunload = null;
}
function setRow(i) {
function renderTableRow(i) {
p = pages[i];
row = rows[i];
row.innerHTML = '';
row.className = '';
row.title = '';
row.className = p.state;
cellScan = row.insertCell(-1);
cellTask = row.insertCell(-1);
cellUser = row.insertCell(-1);
cellSeq = row.insertCell(-1);
cellState = row.insertCell(-1);
row.title = genTextState(p);
cellScan.innerHTML = p.human_nr;
if (p.task_id) {
@@ -273,52 +338,24 @@ function setRow(i) {
user = users_map[p.user_id];
cellUser.innerHTML = user.name;
}
switch (p.seq_id) {
case PAGE_EMPTY:
cellSeq.innerHTML = '';
break
case PAGE_FIX:
case PAGE_CONTINUE:
case PAGE_UFO:
cellSeq.innerHTML = '?';
break;
default:
if (p.seq_id > 0) {
cellSeq.innerHTML = p.seq_id;
}
if (isOk(p)) {
row.classList.add('ok');
}
if (isEmpty(p)) {
row.classList.add('empty');
row.title += "Prázdná stránka, nebude se ukládat\n";
if (isGuess(p)) {
cellState.innerHTML += '<span class="icon">🔍</span>';
}
if (isError(p)) {
row.classList.add('error');
row.title += "Neprázdná nerozpoznaná stránka, potřebuje opravit\n";
cellState.innerHTML += '<span class="icon">⚠</span>';
}
if (isChanged(p)) {
cellState.innerHTML += '<span class="icon">✎</span>';
row.classList.add('changed');
row.title += "Neuložené změny\n";
}
refreshButtons();
}
var activeRow = 0;
function selectRow(i) {
page = pages[i];
user = null;
user_input.blur();
rows[activeRow].classList.remove('active');
rows[i].classList.add('active');
activeRow = i;
loader.style.opacity = 0.7;
img.onload = function() {
loader.style.opacity = 0;
function refreshCurrentData(page) {
if (page.user_id) {
user_input.value = users_map[page.user_id].name;
} else {
@@ -331,28 +368,112 @@ function selectRow(i) {
} else {
task_input.value = '';
}
if (page.seq_id >= 0) {
if (page.seq_id > 0) {
seq_input.value = page.seq_id;
} else {
seq_input.value = '';
}
text_state.innerHTML = genTextState(page);
text_state.className = page.state;
if (isChanged(p)) {
text_state.classList.add('changed');
}
var imgW = img.offsetWidth;
var imgH = img.offsetHeight;
var parentW = img.parentElement.offsetWidth;
var scale = parentW / imgH;
// ensure enough vertical space for rotation
img.parentElement.style.minHeight = (imgW*scale)+"px";
switch (page.rotation) {
case 0:
img.style.transformOrigin = null;
img.style.transform = "";
break;
case 1:
img.style.transformOrigin = "top left";
img.style.transform = "rotate(-90deg) scale("+scale+") translateX(-"+imgW+"px)";
break;
case 2:
img.style.transformOrigin = null;
img.style.transform = "rotate(-180deg)";
break;
case 3:
img.style.transformOrigin = "top left";
img.style.transform = "rotate(-270deg) scale("+scale+") translateY(-"+imgH+"px)";
break;
}
}
function preloadImg(i) {
page = pages[i];
if (page.img_preloaded) {
return;
}
var preloadImg = new Image();
preloadImg.src = page.img_full;
page.img_preloaded = true;
}
var activeRow = 0;
function selectRow(i) {
page = pages[i];
user = null;
user_input.blur();
rows[activeRow].classList.remove('active');
rows[i].classList.add('active');
activeRow = i;
img.style.transition = null; // no animation when changing rows
// Correct closure to avoid changing page param
onLoadFactory = function(page) {
return function() {
loader.style.opacity = 0;
refreshCurrentData(page);
}
}
loader.style.opacity = 0.7;
img.onload = onLoadFactory(page);
img.src = page.img_full;
page.img_preloaded = true;
// Preload previous and next images (if not preloaded yet)
for (let j = 1; j <= PRELOAD_COUNT; j++) {
if (i-j >= 0) { preloadImg(i-j); }
if (i+j < pages.length) { preloadImg(i+j); }
}
}
function refreshActiveRow() {
setRow(activeRow);
selectRow(activeRow);
renderTableRow(activeRow);
refreshCurrentData(pages[activeRow]);
rows[activeRow].classList.add('active');
refreshButtons();
}
function recordFormChanges() {
page = pages[activeRow];
if (isOk(page)) {
page.state = PAGE_OK;
} else {
page.state = PAGE_UNKNOWN;
}
refreshActiveRow();
}
// Activate autocomplete on two fields + prevent catching keys on third one
autocomplete(task_input, tasks_autocomplete, callback=function (id) {
pages[activeRow].task_id = parseInt(id);
refreshActiveRow();
recordFormChanges();
});
autocomplete(user_input, users_autocomplete, callback=function (id) {
pages[activeRow].user_id = parseInt(id);
refreshActiveRow();
recordFormChanges();
});
seq_input.addEventListener("focus", closeAllAutocomplete);
seq_input.addEventListener("keydown", function(e) {
@@ -361,12 +482,14 @@ seq_input.addEventListener("keydown", function(e) {
}
e.stopPropagation();
});
seq_input.addEventListener("blur", function(e) {
seq_input_change = function(e) {
x = parseInt(seq_input.value);
if (Number.isNaN(x)) x = 0
pages[activeRow].seq_id = x;
refreshActiveRow();
});
recordFormChanges();
};
seq_input.addEventListener("blur", seq_input_change);
seq_input.addEventListener("change", seq_input_change);
function scrollToRow(i) {
var top = rows[i].offsetTop;
@@ -406,14 +529,20 @@ function checkKey(e) {
page = pages[activeRow];
page.user_id = null;
page.task_id = null;
page.seq_id = PAGE_EMPTY;
page.seq_id = 0;
page.state = PAGE_EMPTY;
refreshActiveRow();
nextPage();
} else if (e.key == 'r') {
page = pages[activeRow];
page.user_id = page.orig_user_id;
page.task_id = page.orig_task_id;
page.seq_id = page.orig_seq_id;
for (k of keys) {
p[k] = p.orig[k]
}
refreshActiveRow();
} else if (e.key == 'o') {
page = pages[activeRow];
page.rotation = (page.rotation + 1) % 4;
img.style.transition = "0.5s"; // animate here
refreshActiveRow();
} else if (e.key == 'f') {
if (activeRow > 0) {
@@ -429,12 +558,24 @@ function checkKey(e) {
page.user_id = prev.user_id;
page.task_id = prev.task_id;
page.seq_id = prev.seq_id + 1;
page.state = PAGE_OK;
refreshActiveRow();
nextPage();
} else {
alert('Nelze nastavit jako pokračování předchozí stránky, je nekompletní.');
}
}
} else if (e.key == 'k') {
page = pages[activeRow];
if (page.state == PAGE_PROBABLY_OK) {
page.state = PAGE_OK;
refreshActiveRow();
nextPage();
} else if (page.state == PAGE_PROBABLY_EMPTY) {
page.state = PAGE_EMPTY;
refreshActiveRow();
nextPage();
}
} else {
return;
}
@@ -454,7 +595,7 @@ for (user of users) {
}
for (page of pages) { tbody.insertRow(); }
for (let i = 0; i < pages.length; i++) {
setRow(i);
renderTableRow(i);
rows[i].onclick = function() { selectRow(i); }
}
Loading