Cloned DMOJ
This commit is contained in:
parent
f623974b58
commit
49dc9ff10c
513 changed files with 132349 additions and 39 deletions
451
templates/problem/data.html
Normal file
451
templates/problem/data.html
Normal file
|
@ -0,0 +1,451 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block js_media %}
|
||||
<script type="text/javascript">
|
||||
window.valid_files = {{valid_files_json}};
|
||||
|
||||
$(function () {
|
||||
});
|
||||
</script>
|
||||
<script type="text/javascript" src="{{ static('libs/jquery-sortable.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ static('libs/featherlight/featherlight.min.js') }}"></script>
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
function update_select2() {
|
||||
$('tbody:not(.extra-row-body) .type-column select').select2({
|
||||
minimumResultsForSearch: -1
|
||||
});
|
||||
}
|
||||
|
||||
update_select2();
|
||||
|
||||
function autofill_if_exists($select, file) {
|
||||
if (!$select.val() && ~window.valid_files.indexOf(file))
|
||||
$select.val(file).trigger('change');
|
||||
}
|
||||
|
||||
var $table = $('#case-table');
|
||||
$table.on('add-row', function (e, $tr) {
|
||||
update_select2();
|
||||
$tr.find('input').filter('[id$=file]').each(function () {
|
||||
var $select, val = $(this).replaceWith($select = $('<select>').attr({
|
||||
id: $(this).attr('id'),
|
||||
name: $(this).attr('name'),
|
||||
style: 'width: 100%'
|
||||
})).val();
|
||||
$select.select2({
|
||||
data: window.valid_files,
|
||||
allowClear: true,
|
||||
placeholder: ''
|
||||
}).val(val).trigger('change').on('change', function () {
|
||||
var val = $select.val();
|
||||
if (val) {
|
||||
if ($select.attr('id').endsWith('input_file'))
|
||||
autofill_if_exists($tr.find('select[id$=output_file]'), val.replace(/in(?!.*?in)/, 'out'));
|
||||
else
|
||||
autofill_if_exists($tr.find('select[id$=input_file]'), val.replace(/out(?!.*?out)/, 'in'));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var order = 0;
|
||||
|
||||
function handle_table_reorder() {
|
||||
var in_batch = false;
|
||||
$table.find('tbody:first tr').each(function () {
|
||||
switch ($(this).attr('data-type')) {
|
||||
case 'C':
|
||||
$(this).find('input[id$=points], input[id$=pretest]').toggle(!in_batch);
|
||||
break;
|
||||
case 'S':
|
||||
in_batch = true;
|
||||
break;
|
||||
case 'E':
|
||||
in_batch = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function try_parse_json(json) {
|
||||
try {
|
||||
return JSON.parse(json);
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function checker_precision($checker) {
|
||||
var $td = $checker.parent();
|
||||
var $args = $td.find('input');
|
||||
var $precision = $('<input>', {
|
||||
type: 'number',
|
||||
value: try_parse_json($args.val()).precision || 6,
|
||||
title: 'precision (decimal digits)',
|
||||
style: 'width: 4em'
|
||||
}).change(function () {
|
||||
if ($checker.val().startsWith('floats'))
|
||||
$args.val(JSON.stringify({precision: parseInt($(this).val())}));
|
||||
else
|
||||
$args.val('');
|
||||
}).appendTo($td);
|
||||
|
||||
$checker.change(function () {
|
||||
$precision.toggle($checker.val().startsWith('floats')).change();
|
||||
}).change();
|
||||
}
|
||||
|
||||
function swap_row($a, $b) {
|
||||
var $a_order = $a.find('input[id$=order]'), $b_order = $b.find('input[id$=order]');
|
||||
var order = $a_order.val();
|
||||
$a_order.val($b_order.val());
|
||||
$b_order.val(order);
|
||||
$b.after($a);
|
||||
$a.find('span.order').text($a_order.val());
|
||||
$b.find('span.order').text($b_order.val());
|
||||
handle_table_reorder();
|
||||
}
|
||||
|
||||
checker_precision($('#id_problem-data-checker'));
|
||||
|
||||
$table.on('add-row', function (e, $tr) {
|
||||
var $order = $tr.find('input').filter('[id$=order]').attr('type', 'hidden').val(++order);
|
||||
$order.after($('<span>', {'class': 'order'}).text($order.val()))
|
||||
.after($('<i>', {'class': 'fa fa-fw fa-lg fa-ellipsis-v'}));
|
||||
|
||||
var $opts = $tr.find('input').slice(2, 6);
|
||||
var $files = $tr.find('select').slice(1, 3);
|
||||
var $checker = $files.end().last();
|
||||
$tr.find('select[id$=type]').change(function () {
|
||||
var $this = $(this), val = $this.val(), disabled;
|
||||
switch (val) {
|
||||
case 'S':
|
||||
case 'E':
|
||||
disabled = val == 'S';
|
||||
$opts.toggle(val == 'S');
|
||||
$files.siblings('.select2').hide();
|
||||
$checker.toggle(val == 'S');
|
||||
break;
|
||||
default:
|
||||
$opts.toggle(val == 'C');
|
||||
$files.siblings('.select2').toggle(val == 'C');
|
||||
$checker.toggle(val == 'C');
|
||||
var $prevs = $tr.prevAll('tr[data-type=S], tr[data-type=E]');
|
||||
disabled = $prevs.length && $prevs.get(0).getAttribute('data-type') == 'S';
|
||||
$tr.find('input[id$=points], input[id$=pretest]').toggle(val == 'C' && !disabled);
|
||||
}
|
||||
$tr.attr('data-type', val).nextUntil('tr[data-type=S], tr[data-type=E], tr[data-type=""]')
|
||||
.find('input[id$=points], input[id$=pretest]').toggle(!disabled);
|
||||
}).change();
|
||||
|
||||
var tooltip_classes = 'tooltipped tooltipped-s';
|
||||
$tr.find('a.edit-generator-args').mouseover(function () {
|
||||
switch ($tr.attr('data-type')) {
|
||||
case 'C':
|
||||
case 'S':
|
||||
var $this = $(this).addClass(tooltip_classes);
|
||||
$this.attr('aria-label', $this.prev().val() || '(none)');
|
||||
}
|
||||
}).mouseout(function () {
|
||||
$(this).removeClass(tooltip_classes).removeAttr('aria-label');
|
||||
}).featherlight($('.generator-args-editor'), {
|
||||
beforeOpen: function () {
|
||||
switch ($tr.attr('data-type')) {
|
||||
case 'C':
|
||||
case 'S':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
afterOpen: function () {
|
||||
var $input = this.$currentTarget.prev();
|
||||
this.$instance.find('.generator-args-editor')
|
||||
.find('textarea').val($input.val()).end()
|
||||
.find('.button').click(function () {
|
||||
$input.val($(this).prev().val());
|
||||
$.featherlight.current().close();
|
||||
}).end()
|
||||
.show();
|
||||
}
|
||||
});
|
||||
|
||||
checker_precision($tr.find('select[id$=checker]'));
|
||||
}).find('tbody:first').find('tr').each(function () {
|
||||
$table.trigger('add-row', [$(this)]);
|
||||
});
|
||||
|
||||
$('form').submit(function () {
|
||||
$table.find('tbody:first').find('tr').each(function () {
|
||||
var filled = false;
|
||||
$(this).find('input, select').each(function () {
|
||||
var $this = $(this);
|
||||
if (!$this.attr('name'))
|
||||
return;
|
||||
if ($this.attr('type') === 'checkbox')
|
||||
filled |= $this.is(':checked');
|
||||
else if (!$this.attr('name').endsWith('order'))
|
||||
filled |= !!$this.val();
|
||||
});
|
||||
if (!filled)
|
||||
$(this).find('input[id$=order]').val('');
|
||||
});
|
||||
});
|
||||
|
||||
var $total = $('#id_cases-TOTAL_FORMS');
|
||||
|
||||
$('a#add-case-row').click(function () {
|
||||
var $tr;
|
||||
$table.find('tbody:first').append($tr = $($table.find('.extra-row-body').html()
|
||||
.replace(/__prefix__/g, $total.val())));
|
||||
$tr.find('.type-column select option[value="C"]').attr('selected', true);
|
||||
$total.val(parseInt($total.val()) + 1);
|
||||
$table.trigger('add-row', [$tr]);
|
||||
window.scrollBy(0, $tr.height());
|
||||
return false;
|
||||
});
|
||||
|
||||
var oldIndex;
|
||||
$table.sortable({
|
||||
containerSelector: 'table',
|
||||
itemPath: '> tbody:first',
|
||||
itemSelector: 'tr',
|
||||
handle: 'i.fa-ellipsis-v',
|
||||
placeholder: '<tr class="placeholder">',
|
||||
onDragStart: function ($item, container, _super) {
|
||||
oldIndex = $item.index();
|
||||
_super($item, container);
|
||||
},
|
||||
onDrop: function ($item, container, _super) {
|
||||
var newIndex = $item.index();
|
||||
if (newIndex > oldIndex) {
|
||||
var order = parseInt($item.parent().children().slice(oldIndex, newIndex).each(function () {
|
||||
var $order = $(this).find('input[id$=order]');
|
||||
$order.val(parseInt($order.val()) - 1).siblings('span.order').text($order.val());
|
||||
}).last().after($item).find('input[id$=order]').val());
|
||||
$item.find('input[id$=order]').val(order + 1).siblings('span.order').text(order + 1);
|
||||
} else if (newIndex < oldIndex) {
|
||||
var order = parseInt($item.parent().children().slice(newIndex + 1, oldIndex + 1).each(function () {
|
||||
var $order = $(this).find('input[id$=order]');
|
||||
$order.val(parseInt($order.val()) + 1).siblings('span.order').text($order.val());
|
||||
}).first().before($item).find('input[id$=order]').val());
|
||||
$item.find('input[id$=order]').val(order - 1).siblings('span.order').text(order - 1);
|
||||
}
|
||||
if (newIndex != oldIndex)
|
||||
handle_table_reorder();
|
||||
_super($item, container);
|
||||
}
|
||||
});
|
||||
|
||||
var $controls = $('#column-visible');
|
||||
var problem = $controls.attr('data-problem');
|
||||
$controls.find('input').change(function () {
|
||||
var $this = $(this), suffix = $this.attr('data-suffix'), checked = $this.is(':checked');
|
||||
$table.find('.' + suffix.replace(/_/g, '-')).toggle(checked);
|
||||
localStorage.setItem('data-visible:' + problem + ':' + suffix, checked ? '1' : '0')
|
||||
}).each(function () {
|
||||
var $this = $(this), suffix = $this.attr('data-suffix'), filled = false;
|
||||
filled = localStorage.getItem('data-visible:' + problem + ':' + suffix);
|
||||
if (filled !== null)
|
||||
filled = filled == '1';
|
||||
else {
|
||||
filled = false;
|
||||
$table.find('[id$=' + suffix + ']').each(function () {
|
||||
filled |= !!$(this).val();
|
||||
});
|
||||
}
|
||||
$this.prop('checked', filled).trigger('change');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block media %}
|
||||
<style>
|
||||
#case-table .select2 {
|
||||
text-align: initial;
|
||||
}
|
||||
|
||||
.order-column {
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.bad-file input, .bad-file .select2-selection {
|
||||
border-color: red;
|
||||
}
|
||||
|
||||
span.order {
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
|
||||
body.dragging, body.dragging * {
|
||||
cursor: move !important;
|
||||
}
|
||||
|
||||
.dragged {
|
||||
position: absolute;
|
||||
opacity: 0.5;
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
tr.placeholder {
|
||||
display: block;
|
||||
background: red;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
tr.placeholder:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 5px solid transparent;
|
||||
border-left-color: red;
|
||||
margin-top: -5px;
|
||||
left: -5px;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
i.fa-ellipsis-v {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.edit-generator-args {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.generator-args-editor textarea {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-bottom: 0.5em;
|
||||
height: 8em;
|
||||
}
|
||||
|
||||
.generator-args-editor .button {
|
||||
display: block;
|
||||
float: right;
|
||||
}
|
||||
|
||||
#case-table tbody td {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.type-column {
|
||||
width: 8em;
|
||||
}
|
||||
|
||||
ul.errorlist {
|
||||
border: 3px red solid;
|
||||
border-radius: 5px;
|
||||
padding-top: 1em;
|
||||
padding-bottom: 1em;
|
||||
background: #e99;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
{% if data_form.instance.has_yml %}
|
||||
<div class="title-line-action">
|
||||
[<a href="{{ url('problem_data_init', problem.code) }}">{{ _('View YAML') }}</a>]
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{% if data_form.instance.feedback %}
|
||||
<ul class="errorlist">
|
||||
<li>{{ data_form.instance.feedback }}</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
<form action="" method="POST" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ cases_formset.management_form }}
|
||||
<table class="table">{{ data_form.as_table() }}</table>
|
||||
<div id="column-visible" data-problem="{{ problem.code }}">
|
||||
<strong>{{ _('Show columns:') }}</strong>
|
||||
<label>
|
||||
<input type="checkbox" data-suffix="output_prefix">
|
||||
{{ _('Output prefix') }}
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" data-suffix="output_limit">
|
||||
{{ _('Output limit') }}
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" data-suffix="checker">
|
||||
{{ _('Checker') }}
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" data-suffix="generator_args">
|
||||
{{ _('Generator args') }}
|
||||
</label>
|
||||
</div>
|
||||
<table id="case-table" class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="order-column"></th>
|
||||
<th class="type-column">{{ _('Type') }}</th>
|
||||
<th>{{ _('Input file') }}</th>
|
||||
<th>{{ _('Output file') }}</th>
|
||||
<th>{{ _('Points') }}</th>
|
||||
<th>{{ _('Pretest?') }}</th>
|
||||
<th class="output-prefix">{{ _('Output prefix') }}</th>
|
||||
<th class="output-limit">{{ _('Output limit') }}</th>
|
||||
<th class="checker">{{ _('Checker') }}</th>
|
||||
<th class="generator-args">{{ _('Generator args') }}</th>
|
||||
{% if cases_formset.can_delete %}
|
||||
<th>{{ _('Delete?') }}</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for form in all_case_forms %}
|
||||
{% if form.non_field_errors() %}
|
||||
<tr>
|
||||
<td colspan="{{ 9 + cases_formset.can_delete }}">{{ form.non_field_errors() }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if form.prefix and '__prefix__' in form.prefix %}
|
||||
</tbody>
|
||||
<tbody class="extra-row-body" style="display: none">
|
||||
{% endif %}
|
||||
<tr data-type="{{ form['type'].value() }}">
|
||||
<td>{{ form.id }}{{ form.order.errors }}{{ form.order }}</td>
|
||||
<td class="type-column">{{ form.type.errors }}{{ form.type }}</td>
|
||||
<td{% if not (form.empty_permitted or form['type'].value() != 'C' or
|
||||
form['input_file'].value() in valid_files) %} class="bad-file"{% endif %}>
|
||||
{{ form.input_file.errors }}{{ form.input_file }}
|
||||
</td>
|
||||
<td{% if not (form.empty_permitted or form['type'].value() != 'C' or
|
||||
form['output_file'].value() in valid_files) %} class="bad-file"{% endif %}>
|
||||
{{ form.output_file.errors }}{{ form.output_file }}
|
||||
</td>
|
||||
<td>{{ form.points.errors }}{{ form.points }}</td>
|
||||
<td>{{ form.is_pretest.errors }}{{ form.is_pretest }}</td>
|
||||
<td class="output-prefix">{{ form.output_prefix.errors }}{{ form.output_prefix }}</td>
|
||||
<td class="output-limit">{{ form.output_limit.errors }}{{ form.output_limit }}</td>
|
||||
<td class="checker">
|
||||
{{ form.checker.errors }}{{ form.checker }}{{ form.checker_args.errors }}{{ form.checker_args }}
|
||||
</td>
|
||||
<td class="generator-args">{{ form.generator_args.errors }}{{ form.generator_args }}
|
||||
<a href="javascript:void(0)" class="edit-generator-args">
|
||||
<i class="fa fa-pencil"></i>
|
||||
{{ _('Edit') }}
|
||||
</a>
|
||||
</td>
|
||||
{% if cases_formset.can_delete %}
|
||||
<td>{{ form.DELETE }}</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<input type="submit" value="{{ _('Submit!') }}" class="button">
|
||||
<a id="add-case-row" href="#"><i class="fa fa-plus"></i> {{ _('Add new case') }}</a>
|
||||
</form>
|
||||
<div style="display: none" class="generator-args-editor"><textarea></textarea><a class="button">Save</a></div>
|
||||
{% endblock %}
|
Loading…
Add table
Add a link
Reference in a new issue