Select Git revision
-
Jiří Setnička authored
Vytvořen nový endpoint org_contest_task_create. Vše v pozadí obsluhuje stejná univerzální tabulka, jen na správných místech vypisuje správné formuláře ve správném módu (má teď mód zobrazení, zakládání řešení a bodování). Přidány odkazy z různých míst.
Jiří Setnička authoredVytvořen nový endpoint org_contest_task_create. Vše v pozadí obsluhuje stejná univerzální tabulka, jen na správných místech vypisuje správné formuláře ve správném módu (má teď mód zobrazení, zakládání řešení a bodování). Přidány odkazy z různých míst.
org_contest_scans_process.html 12.88 KiB
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block head %}
<script src="{{ asset_url('js/autocomplete.js') }}" type="text/javascript"></script>
{% endblock %}
{% set for_solutions = scans_type == 'solution' %}
{% set scans_title = 'odevzdaných řešení' if for_solutions else 'oprav' %}
{% block title %}
Třídění skenů {{ scans_title }} pro {{ ctx.round.name|lower }} kategorie {{ ctx.round.category }}
{% endblock %}
{% block breadcrumbs %}
{{ ctx.breadcrumbs(action="Třídění skenů " + scans_title) }}
{% endblock %}
{% block body %}
<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>
({{ 'řešení' if for_solutions else 'opravy' }} se uloží k soutěžícím, tuto akci nelze vzít zpět).
{% if errors or warnings %}
<div class="collapsible">
{% set error_count = errors | count %}
{% set warning_count = warnings | count %}
<input type="checkbox" class="toggle" id="messages-toggle" {% if errors %}checked{% endif %}>
<label for="messages-toggle" class="toggle">
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 -%}
)
</label>
<div class="collapsible-inner">
<div class="alert alert-warning">
<ul>
{% for msg in warnings %}
<li>Varování: {{ msg }}
{% endfor %}
{% for msg in errors %}
<li class="error"><b>Chyba: {{ msg }}</b>
{% endfor %}
</ul></div>
</div>
</div>
</div>
{% else %}
<p><span class="text-success">Skeny zkontrolovány, nenalezeny žádné chyby ani varování.</span></p>
{% endif %}
<form method="post" class="btn-group pull-right">
<input class="btn btn-primary" type="submit" name="save" value="Uložit" id="save-button" onclick="saveData()">
{{ process_form.csrf_token }}
<input type="hidden" name="data" id="save-data">
{% if errors %}
<input class="btn btn-primary" type="submit" value="Ukončit a zpracovat" title="Kontrola ukázala chyby, nejdříve je opravte a uložte pomocí [Uložit]" disabled>
{% else %}
<input class="btn btn-primary" type="submit" name="process_all" value="Ukončit a zpracovat" id="process-all-button" onclick="return confirm('Opravud ukončit a zpracovat? Nelze vzít zpět.');">
{% endif %}
</form>
<style>
#sort_wrapper {
display: grid;
grid-template-columns: auto 350px;
grid-template-rows: 100px auto;
gap: 5px;
}
#sort_wrapper div.controls {
border: 1px #bbbbbb solid;
background-color: #e5e5e5;
border-radius: 5px;
padding: 5px 10px;
}
#sort_wrapper div.scan {
position: relative;
}
#sort_wrapper div.scan #scan_loader {
position: absolute;
width: 100%;
height: 100%;
opacity: 0;
background-color: black;
transition: 0.5s;
}
#sort_wrapper div.scan img {
max-width: 100%;
max-height: 100%;
}
#sort_wrapper div.pages {
max-height: 80vh;
grid-row: span 2;
overflow-y: scroll;
position: relative;
}
#sort_wrapper div.pages table {
width: 100%;
margin: 0px;
}
#sort_wrapper div.pages table thead {
position: sticky;
top: 0px;
}
#sort_wrapper div.pages table tr {
cursor: pointer;
}
#sort_wrapper div.pages table tr.ok { background-color: lightgreen; }
#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.changed { background-color: yellow; }
#sort_wrapper div.pages table tr.active {
color: white;
background-color: black;
}
#sort_wrapper div.pages table tr.active.changed { color: yellow; }
</style>
<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é.
<div id="sort_wrapper">
<div class="controls form-horizontal">
<span tabindex=1 onfocus="document.getElementById('seq_input').focus();"></span>
<div class="form-group">
<label class="col-sm-2 control-label" for="user_input">Soutěžící:</label>
<div class="col-sm-10 autocomplete">
<input tabindex=2 class="form-control" type="text" id="user_input">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="task_input">Úloha:</label>
<div class="col-sm-4 autocomplete">
<input tabindex=3 class="form-control" type="text" id="task_input">
</div>
<label class="col-sm-2 control-label" for="seq_input">Stránka:</label>
<div class="col-sm-4 autocomplete">
<input tabindex=4 class="form-control" type="number" min="1" id="seq_input">
</div>
</div>
<span tabindex=5 onfocus="document.getElementById('user_input').focus();"></span>
</div>
<div class="pages" id="rows_scroller">
<table class="data">
<thead>
<th title="Číslo skenu">#
<th>Úloha
<th>Soutěžící
<th title="Stránka">St.
</thead>
<tbody id="pages_rows">
</tbody>
</table>
</div>
<div class="scan">
<div id="scan_loader"></div>
<img id="scan_img">
</div>
</div>
<script type="text/javascript">
var tasks = [
{% for task in tasks %}
{ id: {{ task.task_id }}, code: "{{ task.code }}", name: "{{ task.name }}" },
{% endfor %}
];
var users = [
{% for pion in pions %}
{ id: {{ pion.user_id }}, name: "{{ pion.user.first_name }} {{ pion.user.last_name }}" },
{% endfor %}
];
var pages = [
{% for page in pages %}
{
file_nr: {{ page.file_nr }},
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 }},
img_full: "{{ png_full(page) }}",
img_small: "{{ png_small(page) }}",
},
{% endfor %}
];
var tasks_map = {};
var tasks_autocomplete = [];
var users_map = {};
var users_autocomplete = [];
// Global elements
var tbody = document.getElementById('pages_rows');
var img = document.getElementById('scan_img');
var loader = document.getElementById('scan_loader');
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 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;
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 isError(p) { return (!isEmpty(p) && !isOk(p)); }
function anyChanged(p) { return pages.some(isChanged); }
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?";
}
function refreshButtons() {
if (anyChanged()) {
if (process_all_button) {
process_all_button.disabled = true;
process_all_button.title = "Neuložené změny, nejdříve je uložte vedlejším tlačítkem";
}
window.onbeforeunload = beforeUnload;
} else {
if (process_all_button) {
process_all_button.disabled = false;
process_all_button.title = '';
}
window.onbeforeunload = null;
}
}
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}));
save_data_field.value = JSON.stringify(data);
window.onbeforeunload = null;
}
function setRow(i) {
p = pages[i];
row = rows[i];
row.innerHTML = '';
row.className = '';
row.title = '';
cellScan = row.insertCell(-1);
cellTask = row.insertCell(-1);
cellUser = row.insertCell(-1);
cellSeq = row.insertCell(-1);
cellScan.innerHTML = p.human_nr;
if (p.task_id) {
task = tasks_map[p.task_id];
cellTask.innerHTML = task.code + ' ' + task.name;
}
if (p.user_id) {
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:
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 (isError(p)) {
row.classList.add('error');
row.title += "Neprázdná nerozpoznaná stránka, potřebuje opravit\n";
}
if (isChanged(p)) {
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;
if (page.user_id) {
user_input.value = users_map[page.user_id].name;
} else {
user_input.value = '';
}
if (page.task_id) {
task = tasks_map[page.task_id];
task_input.value = task.code + ' ' + task.name;
} else {
task_input.value = '';
}
if (page.seq_id >= 0) {
seq_input.value = page.seq_id;
} else {
seq_input.value = '';
}
}
img.src = page.img_full;
}
function refreshActiveRow() {
setRow(activeRow);
selectRow(activeRow);
}
// 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();
});
autocomplete(user_input, users_autocomplete, callback=function (id) {
pages[activeRow].user_id = parseInt(id);
refreshActiveRow();
});
seq_input.addEventListener("focus", closeAllAutocomplete);
seq_input.addEventListener("keydown", function(e) {
if (e.keyCode == 27) { // escape
this.blur();
}
e.stopPropagation();
});
seq_input.addEventListener("blur", function(e) {
x = parseInt(seq_input.value);
if (Number.isNaN(x)) x = 0
pages[activeRow].seq_id = x;
refreshActiveRow();
});
function scrollToRow(i) {
var top = rows[i].offsetTop;
var bottom = top + rows[i].offsetHeight;
if (rows_scroller.scrollTop > top - 45) {
rows_scroller.scrollTo({ top: top - 45, behavior: 'smooth'});
} else if (rows_scroller.scrollTop + rows_scroller.offsetHeight < bottom + 30) {
rows_scroller.scrollTo({ top: bottom - rows_scroller.offsetHeight + 30, behavior: 'smooth' });
}
}
function prevPage() {
if (activeRow > 0) {
selectRow(activeRow - 1);
scrollToRow(activeRow);
}
}
function nextPage() {
if (activeRow < pages.length - 1) {
selectRow(activeRow + 1);
scrollToRow(activeRow);
}
}
// Keyboard control
function checkKey(e) {
e = e || window.event;
if (e.keyCode == '38') {
prevPage();
} else if (e.keyCode == '40') {
nextPage();
} else if (e.key == 'e') {
user_input.focus();
} else if (e.key == 'x') {
page = pages[activeRow];
page.user_id = null;
page.task_id = null;
page.seq_id = 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;
refreshActiveRow();
} else if (e.key == 'f') {
if (activeRow > 1) {
prev = pages[activeRow-1];
page = pages[activeRow];
if (isOk(prev)) {
page.user_id = prev.user_id;
page.task_id = prev.task_id;
page.seq_id = prev.seq_id + 1;
refreshActiveRow();
nextPage();
} else {
alert('Nelze nastavit jako pokračování předchozí stránky, je nekompletní.');
}
}
} else {
return;
}
e.preventDefault();
}
document.onkeydown = checkKey;
// Start everything :)
for (task of tasks) {
tasks_map[task.id] = task;
tasks_autocomplete.push([task.id, task.code + ' ' + task.name]);
}
for (user of users) {
users_map[user.id] = user;
users_autocomplete.push([user.id, user.name]);
}
for (page of pages) { tbody.insertRow(); }
for (let i = 0; i < pages.length; i++) {
setRow(i);
rows[i].onclick = function() { selectRow(i); }
}
selectRow(0);
scrollToRow(0);
</script>
{% endblock %}