diff --git a/web/app/controllers/problem_data_configure.php b/web/app/controllers/problem_data_configure.php new file mode 100644 index 00000000..a38d23b2 --- /dev/null +++ b/web/app/controllers/problem_data_configure.php @@ -0,0 +1,62 @@ +userCanManage(Auth::user()) || UOJResponse::page403(); + +$problem_configure = new UOJProblemConfigure(UOJProblem::cur()); +$problem_configure->runAtServer(); +$problem_conf_str = ''; + +foreach ($problem_configure->problem_conf->conf as $key => $val) { + if ($key == 'use_builtin_judger' && $val == 'off') { + continue; + } + + if ($key == 'use_builtin_checker' && $val == 'ownchk') { + continue; + } + + $problem_conf_str .= "{$key} {$val}\n"; +} +?> + +getTitle(['with' => 'id']))) ?> + +

+ getTitle(['with' => 'id']) ?> 数据配置 +

+ +
+
+
+
problem.conf 预览
+ +
+
+
+ + +
+
+
功能说明
+ +
+

此处可以对 传统题 进行快速配置,在填写完成之后点击页面底部的「提交」按钮即可替换现有配置文件并进行数据同步。

+

目前暂不支持对交互题、提交答案题、通信题进行配置,对于此类题目请手动编写配置文件。

+

如需要配置子任务依赖,也可以基于此处生成的配置文件进行修改后再手动上传。

+

关于数据配置的更多帮助,请查阅 帮助文档

+
+
+
+
+ printHTML() ?> +
+
+ + diff --git a/web/app/controllers/problem_data_manage.php b/web/app/controllers/problem_data_manage.php index 88edaa63..6315a7b3 100644 --- a/web/app/controllers/problem_data_manage.php +++ b/web/app/controllers/problem_data_manage.php @@ -90,58 +90,6 @@ function echoFilePre($file_name) { } } -// 添加配置文件 -if ($_POST['problem_settings_file_submit'] == 'submit') { - if ($_POST['use_builtin_checker'] and $_POST['n_tests']) { - $set_filename = "/var/uoj_data/upload/{$problem['id']}/problem.conf"; - $has_legacy = false; - if (file_exists($set_filename)) { - $has_legacy = true; - unlink($set_filename); - } - $setfile = fopen($set_filename, "w"); - fwrite($setfile, "use_builtin_judger on\n"); - if ($_POST['use_builtin_checker'] != 'ownchk') { - fwrite($setfile, "use_builtin_checker " . $_POST['use_builtin_checker'] . "\n"); - } - fwrite($setfile, "n_tests " . $_POST['n_tests'] . "\n"); - if ($_POST['n_ex_tests']) { - fwrite($setfile, "n_ex_tests " . $_POST['n_ex_tests'] . "\n"); - } else { - fwrite($setfile, "n_ex_tests 0\n"); - } - if ($_POST['n_sample_tests']) { - fwrite($setfile, "n_sample_tests " . $_POST['n_sample_tests'] . "\n"); - } else { - fwrite($setfile, "n_sample_tests 0\n"); - } - if (isset($_POST['input_pre'])) { - fwrite($setfile, "input_pre " . $_POST['input_pre'] . "\n"); - } - if (isset($_POST['input_suf'])) { - fwrite($setfile, "input_suf " . $_POST['input_suf'] . "\n"); - } - if (isset($_POST['output_pre'])) { - fwrite($setfile, "output_pre " . $_POST['output_pre'] . "\n"); - } - if (isset($_POST['output_suf'])) { - fwrite($setfile, "output_suf " . $_POST['output_suf'] . "\n"); - } - fwrite($setfile, "time_limit " . ($_POST['time_limit'] ?: 1) . "\n"); - fwrite($setfile, "memory_limit " . ($_POST['memory_limit'] ?: 256) . "\n"); - fclose($setfile); - if (!$has_legacy) { - echo ""; - } else { - echo ""; - } - } else { - $errmsg = "添加配置文件失败,请检查是否所有必填输入框都已填写!"; - becomeMsgPage('
' . $errmsg . '
返回'); - } -} - - $info_form = new UOJForm('info'); $attachment_url = UOJProblem::cur()->getAttachmentUri(); $info_form->appendHTML(<<上传数据
- + 数据配置
@@ -705,108 +653,4 @@ function(data) { -getProblemConf() ?> - - - diff --git a/web/app/libs/uoj-data-lib.php b/web/app/libs/uoj-data-lib.php index 8fe456b0..aad2fb78 100644 --- a/web/app/libs/uoj-data-lib.php +++ b/web/app/libs/uoj-data-lib.php @@ -201,7 +201,9 @@ private function makefile_at_prepare() { public function _updateProblemConf($new_problem_conf) { try { - putUOJConf("{$this->data_dir}/problem.conf", $new_problem_conf); + putUOJConf("{$this->upload_dir}/problem.conf", $new_problem_conf); + + $this->_sync(); return ''; } catch (Exception $e) { return $e->getMessage(); @@ -506,3 +508,7 @@ function dataAddHackPoint($problem, $uploaded_input_file, $uploaded_output_file, return (new SyncProblemDataHandler($problem, $user))->addHackPoint($uploaded_input_file, $uploaded_output_file, $reason); } + +function dataUpdateProblemConf($problem, $new_problem_conf) { + return (new SyncProblemDataHandler($problem))->updateProblemConf($new_problem_conf); +} diff --git a/web/app/models/UOJForm.php b/web/app/models/UOJForm.php index b6d2c515..78dabf6c 100644 --- a/web/app/models/UOJForm.php +++ b/web/app/models/UOJForm.php @@ -125,6 +125,8 @@ public function addInput($name, $config) { 'type' => 'text', 'div_class' => '', 'input_class' => 'form-control', + 'input_attrs' => [], + 'input_div_class' => null, 'default_value' => '', 'label' => '', 'label_class' => 'form-label', @@ -148,6 +150,10 @@ public function addInput($name, $config) { ], $config['label']); } + if ($config['input_div_class'] !== null) { + $html .= HTML::tag_begin('div', ['class' => $config['input_div_class']]); + } + $html .= HTML::empty_tag('input', [ 'class' => $config['input_class'], 'type' => $config['type'], @@ -155,13 +161,17 @@ public function addInput($name, $config) { 'id' => "input-$name", 'value' => $config['default_value'], 'placeholder' => $config['placeholder'], - ]); + ] + $config['input_attrs']); $html .= HTML::tag('div', ['class' => 'invalid-feedback', 'id' => "help-$name"], ''); if ($config['help']) { $html .= HTML::tag('div', ['class' => $config['help_class']], $config['help']); } + if ($config['input_div_class'] !== null) { + $html .= HTML::tag_end('div'); + } + $html .= HTML::tag_end('div'); $this->add($name, $html, $config['validator_php'], $config['validator_js']); @@ -255,6 +265,7 @@ public function addSelect($name, $config) { $config += [ 'div_class' => '', 'select_class' => 'form-select', + 'select_div_class' => null, 'options' => [], 'default_value' => '', 'label' => '', @@ -275,6 +286,10 @@ public function addSelect($name, $config) { ], $config['label']); } + if ($config['select_div_class'] !== null) { + $html .= HTML::tag_begin('div', ['class' => $config['select_div_class']]); + } + // Select $html .= HTML::tag_begin('select', ['id' => "input-$name", 'name' => $name, 'class' => $config['select_class']]); @@ -293,6 +308,10 @@ public function addSelect($name, $config) { $html .= HTML::tag('div', ['class' => $config['help_class']], $config['help']); } + if ($config['select_div_class'] !== null) { + $html .= HTML::tag_end('div'); + } + $html .= HTML::tag_end('div'); $this->add( @@ -503,6 +522,13 @@ public function printHTML() { if (!$this->config['no_submit']) { echo HTML::tag_begin('div', ['class' => $this->config['submit_container']['class']]); + if ($this->config['back_button']['href'] !== null) { + echo HTML::tag('a', [ + 'class' => $this->config['back_button']['class'], + 'href' => $this->config['back_button']['href'] + ], '返回'); + } + echo HTML::tag('button', [ 'type' => 'submit', 'id' => "button-submit-{$this->form_name}", @@ -511,13 +537,6 @@ public function printHTML() { 'class' => $this->config['submit_button']['class'] ], $this->config['submit_button']['text']); - if ($this->config['back_button']['href'] !== null) { - echo HTML::tag('a', [ - 'class' => $this->config['back_button']['class'], - 'href' => $this->config['back_button']['href'] - ], '返回'); - } - echo HTML::tag_end('div'); } diff --git a/web/app/models/UOJProblem.php b/web/app/models/UOJProblem.php index ee0da7da..87c38705 100644 --- a/web/app/models/UOJProblem.php +++ b/web/app/models/UOJProblem.php @@ -663,6 +663,10 @@ public function getDataFolderPath() { return "/var/uoj_data/{$this->info['id']}"; } + public function getUploadFolderPath() { + return "/var/uoj_data/upload/{$this->info['id']}"; + } + public function getDataZipPath() { return "/var/uoj_data/{$this->info['id']}.zip"; } @@ -672,6 +676,10 @@ public function getDataFilePath($name = '') { return "{$this->getDataFolderPath()}/$name"; } + public function getUploadFilePath($name = '') { + return "{$this->getUploadFolderPath()}/$name"; + } + public function getResourcesFolderPath() { return UOJContext::storagePath() . "/problem_resources/" . $this->info['id']; } diff --git a/web/app/models/UOJProblemConfigure.php b/web/app/models/UOJProblemConfigure.php new file mode 100644 index 00000000..bfa9a87f --- /dev/null +++ b/web/app/models/UOJProblemConfigure.php @@ -0,0 +1,369 @@ + '自定义校验器', + 'ncmp' => 'ncmp: 单行或多行整数序列', + 'wcmp' => 'wcmp: 单行或多行字符串序列', + 'fcmp' => 'fcmp: 单行或多行数据(不忽略行末空格,但忽略文末回车)', + 'bcmp' => 'bcmp: 逐字节比较', + 'uncmp' => 'uncmp: 单行或多行整数集合', + 'yesno' => 'yesno: YES、NO 序列(不区分大小写)', + 'rcmp4' => 'rcmp4: 浮点数序列,绝对或相对误差在 1e-4 以内则视为答案正确', + 'rcmp6' => 'rcmp6: 浮点数序列,绝对或相对误差在 1e-6 以内则视为答案正确', + 'rcmp9' => 'rcmp9: 浮点数序列,绝对或相对误差在 1e-9 以内则视为答案正确', + ]; + + public static $supported_score_types = [ + 'int' => '整数,每个测试点的部分分向下取整', + 'real-0' => '整数,每个测试点的部分分四舍五入到整数', + 'real-1' => '实数,四舍五入到小数点后 1 位', + 'real-2' => '实数,四舍五入到小数点后 2 位', + 'real-3' => '实数,四舍五入到小数点后 3 位', + 'real-4' => '实数,四舍五入到小数点后 4 位', + 'real-5' => '实数,四舍五入到小数点后 5 位', + 'real-6' => '实数,四舍五入到小数点后 6 位', + 'real-7' => '实数,四舍五入到小数点后 7 位', + 'real-8' => '实数,四舍五入到小数点后 8 位', + ]; + + private static function getCardHeader($title, $body_class = 'vstack gap-3') { + return << +
+
+ {$title} +
+
+ EOD; + } + + private static function getCardFooter() { + return << +
+
+ EOD; + } + + public function __construct(UOJProblem $problem) { + $this->problem = $problem; + $problem_conf = $this->problem->getProblemConf('data'); + if (!($problem_conf instanceof UOJProblemConf)) { + $problem_conf = new UOJProblemConf([]); + } + $this->problem_conf = $problem_conf; + + $this->href = "/problem/{$this->problem->info['id']}/manage/data"; + + $this->simple_form = new UOJForm('simple'); + + $encoded_problem_conf = json_encode($problem_conf->conf, JSON_FORCE_OBJECT); + $this->simple_form->appendHTML(<< + var problem_conf = {$encoded_problem_conf}; + + EOD); + + $this->simple_form->appendHTML(static::getCardHeader('基本信息')); + $this->addSelect($this->simple_form, 'use_builtin_judger', ['on' => '默认', 'off' => '自定义 Judger'], '测评逻辑', 'on'); + $this->addSelect($this->simple_form, 'use_builtin_checker', self::$supported_checkers, '比对函数', 'ncmp'); + $this->addSelect($this->simple_form, 'score_type', self::$supported_score_types, '测试点分数数值类型', 'int'); + $this->simple_form->appendHTML(static::getCardFooter()); + + $this->simple_form->appendHTML(static::getCardHeader('数据配置')); + $this->addNumberInput($this->simple_form, 'n_tests', '数据点个数', 10); + $this->addNumberInput($this->simple_form, 'n_ex_tests', '额外数据点个数', 0); + $this->addNumberInput($this->simple_form, 'n_sample_tests', '样例数据点个数', 0, ['help' => '样例数据点为额外数据点中的前 x 个数据点。']); + $this->simple_form->appendHTML(static::getCardFooter()); + + $this->simple_form->appendHTML(static::getCardHeader('文件配置')); + $this->addTextInput($this->simple_form, 'input_pre', '输入文件名称', ''); + $this->addTextInput($this->simple_form, 'input_suf', '输入文件后缀', ''); + $this->addTextInput($this->simple_form, 'output_pre', '输出文件名称', ''); + $this->addTextInput($this->simple_form, 'output_suf', '输出文件后缀', ''); + $this->simple_form->appendHTML(static::getCardFooter()); + + $this->simple_form->appendHTML(static::getCardHeader('运行时限制')); + $this->addTimeLimitInput($this->simple_form, 'time_limit', '时间限制', 1, ['help' => '单位为秒,至多三位小数。']); + $this->addNumberInput($this->simple_form, 'memory_limit', '内存限制', 256, ['help' => '单位为 MiB。']); + $this->addNumberInput($this->simple_form, 'output_limit', '输出长度限制', 64, ['help' => '单位为 MiB。']); + $this->simple_form->appendHTML(static::getCardFooter()); + + $this->simple_form->appendHTML(static::getCardHeader('测试点分值', '')); + $this->simple_form->appendHTML(<< + 展开/收起全部 +
+ + + EOD); + $this->simple_form->appendHTML(static::getCardFooter()); + $this->simple_form->appendHTML(<< + $(document).ready(function() { + $('#input-n_tests').change(function() { + problem_conf['n_tests'] = $(this).val(); + $('#div-point-score-container').problem_configure_point_scores(problem_conf); + }); + + $('#input-score_type').change(function() { + var score_type = $(this).val(); + var step = '1'; + problem_conf['score_type'] = score_type; + + if (score_type == 'int') { + step = '1'; + } else { + var decimal_places = parseInt(score_type.substring(5)); + + if (decimal_places == 0) { + step = '1'; + } else { + step = (0).toFixed(decimal_places - 1) + '1'; + } + } + + $('.uoj-problem-configure-point-score-input', $('#div-point-score-container')).attr('step', step); + $('.uoj-problem-configure-point-score-input', $('#div-point-score-container')).first().trigger('change'); + + $('.uoj-problem-configure-subtask-score-input', $('#div-point-score-container')).attr('step', step); + $('.uoj-problem-configure-subtask-score-input', $('#div-point-score-container')).first().trigger('change'); + }); + + $('#div-point-score-container').problem_configure_point_scores(problem_conf); + }); + + EOD); + + $this->simple_form->appendHTML(static::getCardHeader('Subtask 配置', 'p-0')); + $this->simple_form->appendHTML(<< + + + +
+ EOD); + $this->simple_form->appendHTML(static::getCardFooter()); + $this->simple_form->appendHTML(<< + $(document).ready(function() { + $('#input-enable_subtasks').change(function() { + if (this.checked) { + $('#div-point-score-container-outer').hide(); + $('#div-point-score-unavailable').show(); + $('#div-subtasks-container').problem_configure_subtasks(problem_conf); + } else { + $('#div-point-score-container-outer').show(); + $('#div-point-score-unavailable').hide(); + $('#div-subtasks-container').empty(); + $('.uoj-problem-configure-point-score-input').val(''); + + var subtask_keys = Object.keys(problem_conf).filter(function(key) { + return /^subtask_/.test(key); + }); + + for (var i = 0; i < subtask_keys.length; ++i) { + problem_conf[subtask_keys[i]] = ''; + } + + problem_conf['n_subtasks'] = ''; + } + + $('#problem-conf-preview').problem_conf_preview(problem_conf); + }); + + if (problem_conf['n_subtasks']) { + $('#input-enable_subtasks').prop('checked', true).trigger('change'); + } + }); + + EOD); + + $this->simple_form->appendHTML(<< + $(document).on("keydown", "form", function(event) { + return event.key != "Enter"; + }); + + EOD); + + $this->simple_form->succ_href = $this->href; + $this->simple_form->config['form']['class'] = 'row gy-3'; + $this->simple_form->config['submit_container']['class'] = 'col-12 text-center mt-3'; + $this->simple_form->config['back_button']['href'] = $this->href; + $this->simple_form->config['back_button']['class'] = 'btn btn-secondary me-2'; + + $this->simple_form->handle = fn (&$vdata) => $this->onUpload($vdata); + } + + public function addSelect(UOJForm $form, $key, $options, $label, $default_val = '', $cfg = []) { + $this->conf_keys[$key] = true; + $form->addSelect($key, [ + 'options' => $options, + 'label' => $label, + 'div_class' => 'row gx-2', + 'label_class' => 'col-form-label col-4', + 'select_div_class' => 'col-8', + 'default_value' => $this->problem_conf->getVal($key, $default_val), + ] + $cfg); + $form->appendHTML(<< + $('#input-{$key}').change(function() { + problem_conf['{$key}'] = $(this).val(); + $('#problem-conf-preview').problem_conf_preview(problem_conf); + }); + + EOD); + } + + public function addNumberInput(UOJForm $form, $key, $label, $default_val = '', $cfg = []) { + $this->conf_keys[$key] = true; + $form->addInput($key, [ + 'type' => 'number', + 'label' => $label, + 'div_class' => 'row gx-2', + 'label_class' => 'col-form-label col-4', + 'input_div_class' => 'col-8', + 'default_value' => $this->problem_conf->getVal($key, $default_val), + 'validator_php' => function ($x) { + return validateInt($x) ? '' : '必须为一个整数'; + }, + ] + $cfg); + $form->appendHTML(<< + $('#input-{$key}').change(function() { + problem_conf['{$key}'] = $(this).val(); + $('#problem-conf-preview').problem_conf_preview(problem_conf); + }); + + EOD); + } + + public function addTimeLimitInput(UOJForm $form, $key, $label, $default_val = '', $cfg = []) { + $this->conf_keys[$key] = true; + $form->addInput($key, [ + 'type' => 'number', + 'label' => $label, + 'input_attrs' => ['step' => 0.001], + 'div_class' => 'row gx-2', + 'label_class' => 'col-form-label col-4', + 'input_div_class' => 'col-8', + 'default_value' => $this->problem_conf->getVal($key, $default_val), + 'validator_php' => function ($x) { + if (!validateUFloat($x)) { + return '必须为整数或小数,且值大于等于零'; + } elseif (round($x * 1000) != $x * 1000) { + return '至多包含三位小数'; + } else { + return ''; + } + }, + ] + $cfg); + $form->appendHTML(<< + $('#input-{$key}').change(function() { + problem_conf['{$key}'] = $(this).val(); + $('#problem-conf-preview').problem_conf_preview(problem_conf); + }); + + EOD); + } + + public function addTextInput(UOJForm $form, $key, $label, $default_val = '', $cfg = []) { + $this->conf_keys[$key] = true; + $form->addInput($key, [ + 'label' => $label, + 'div_class' => 'row gx-2', + 'label_class' => 'col-form-label col-4', + 'input_div_class' => 'col-8', + 'default_value' => $this->problem_conf->getVal($key, $default_val), + 'validator_php' => function ($x) { + return ctype_graph($x) ? '' : '必须仅包含除空格以外的可见字符'; + }, + ] + $cfg); + $form->appendHTML(<< + $('#input-{$key}').change(function() { + problem_conf['{$key}'] = $(this).val(); + $('#problem-conf-preview').problem_conf_preview(problem_conf); + }); + + EOD); + } + + public function runAtServer() { + $this->simple_form->runAtServer(); + } + + public function onUpload(array &$vdata) { + $conf = $this->problem_conf->conf; + $conf_keys = $this->conf_keys; + $n_tests = intval(UOJRequest::post('n_tests', 'validateUInt', $this->problem_conf->getVal('n_tests', 10))); + $n_subtasks = intval(UOJRequest::post('n_subtasks', 'validateUInt', $this->problem_conf->getVal('n_subtasks', 0))); + + for ($i = 1; $i <= $n_tests; $i++) { + $conf_keys["point_score_$i"] = true; + } + + $conf_keys['n_subtasks'] = true; + for ($i = 1; $i <= $n_subtasks; $i++) { + $conf_keys["subtask_type_$i"] = true; + $conf_keys["subtask_score_$i"] = true; + $conf_keys["subtask_end_$i"] = true; + $conf_keys["subtask_used_time_type_$i"] = true; + + // $conf_keys["subtask_dependence_$i"] = true; + + // $subtask_dependence_str = UOJRequest::post("subtask_dependence_$i", 'is_string', ''); + + // if ($subtask_dependence_str == 'many') { + // $subtask_dependence_cnt = 0; + + // while (UOJRequest::post("subtask_dependence_{$i}_{$subtask_dependence_cnt}", 'is_string', '') != '') { + // $subtask_dependence_cnt++; + // $conf_keys["subtask_dependence_{$i}_{$subtask_dependence_cnt}"] = true; + // } + // } + } + + foreach (array_keys($conf_keys) as $key) { + $val = UOJRequest::post($key, 'is_string', ''); + if ($key === 'use_builtin_judger') { + if ($val === 'off') { + unset($conf[$key]); + } else { + $conf[$key] = $val; + } + } elseif ($key === 'use_builtin_checker') { + if ($val === 'ownchk') { + unset($conf[$key]); + } else { + $conf[$key] = $val; + } + } else { + if ($val !== '') { + $conf[$key] = $val; + } else if (isset($conf[$key])) { + unset($conf[$key]); + } + } + } + + $err = dataUpdateProblemConf($this->problem->info, $conf); + if ($err) { + UOJResponse::message('
' . $err . '
返回'); + } + } + + public function printHTML() { + $this->simple_form->printHTML(); + } +} diff --git a/web/app/route.php b/web/app/route.php index b276b930..ab0d7260 100644 --- a/web/app/route.php +++ b/web/app/route.php @@ -27,6 +27,7 @@ function () { Route::any('/problem/{id}/manage/statement', '/problem_statement_manage.php'); Route::any('/problem/{id}/manage/permissions', '/problem_permissions_manage.php'); Route::any('/problem/{id}/manage/data', '/problem_data_manage.php'); + Route::any('/problem/{id}/manage/data/configure', '/problem_data_configure.php'); Route::any('/download/testlib.h', '/download.php?type=testlib.h'); Route::any('/download/problem/{id}/data.zip', '/download.php?type=problem'); Route::any('/download/problem/{id}/attachment.zip', '/download.php?type=attachment'); diff --git a/web/js/uoj.js b/web/js/uoj.js index 2d74f7ad..ccabb32a 100644 --- a/web/js/uoj.js +++ b/web/js/uoj.js @@ -1177,6 +1177,267 @@ $.fn.remote_submit_type_group = function(oj, pid, url, submit_type) { }); } +// problem_configure: print problem.conf +$.fn.problem_conf_preview = function(problem_conf) { + return $(this).each(function() { + var keys = Object.keys(problem_conf); + var res = ''; + + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + var value = problem_conf[key]; + + if (!value) { + continue; + } + + if (key == 'use_builtin_judger' && value == 'off') { + continue; + } + + if (key == 'use_builtin_checker' && value == 'ownchk') { + continue; + } + + res += key + ' ' + value + '\n'; + } + + $(this).html('
' + res + '
'); + }); +} + +// problem_configure: point scores +$.fn.problem_configure_point_scores = function(problem_conf) { + return $(this).each(function() { + var _this = this; + var n_tests = parseInt(problem_conf['n_tests']); + + $(this).empty(); + + if (isNaN(n_tests) || n_tests <= 0) { + $(this).html('不可用。'); + + return; + } + + for (var i = 1; i <= n_tests; i++) { + var input_point_score = $(''); + + if (problem_conf['point_score_' + i]) { + input_point_score.val(problem_conf['point_score_' + i]); + } + + (function(i){ + input_point_score.change(function() { + problem_conf['point_score_' + i] = $(this).val(); + $('#problem-conf-preview').problem_conf_preview(problem_conf); + }); + })(i); + + $(this).append( + $('
').append( + $('
') + .append($('
').append('')) + .append($('
').append(input_point_score)) + ) + ); + } + + $('.uoj-problem-configure-point-score-input', this).change(function() { + var full_score = 100; + var rest_tests = parseInt(problem_conf['n_tests'] || '10'); + var score_type = problem_conf['score_type'] || 'int'; + + $('.uoj-problem-configure-point-score-input', _this).each(function() { + var point_score = parseInt($(this).val()); + if (!isNaN(point_score)) { + full_score -= point_score; + rest_tests--; + } + }); + + $('.uoj-problem-configure-point-score-input', _this).each(function() { + if ($(this).val() == '') { + var val = full_score / rest_tests; + + if (score_type == 'int') { + val = Math.floor(val); + } else { + var decimal_places = parseInt(score_type.substring(5)); + + val = val.toFixed(decimal_places); + } + + $(this).attr('placeholder', val); + } + }); + }); + + $('.uoj-problem-configure-point-score-input', this).first().trigger('change'); + }); +}; + +// problem_configure: subtasks +$.fn.problem_configure_subtasks = function(problem_conf) { + return $(this).each(function() { + var _this = this; + var n_subtasks = parseInt(problem_conf['n_subtasks'] || '0'); + + $(this).empty(); + + if (isNaN(n_subtasks)) { + $(this).html('不可用。'); + + return; + } + + var input_n_subtasks = $(''); + var div_subtasks = $('
'); + + if (n_subtasks) { + input_n_subtasks.val(n_subtasks); + } + + $(this).append( + $('
').append( + $('
').append( + $('
').append('') + ).append( + $('
').append(input_n_subtasks) + ) + ) + ).append(div_subtasks); + + input_n_subtasks.change(function() { + div_subtasks.empty(); + + var n_subtasks = parseInt(input_n_subtasks.val() || '0'); + var n_tests = parseInt(problem_conf['n_tests'] || '10'); + problem_conf['n_subtasks'] = input_n_subtasks.val(); + + for (var i = 1; i <= n_subtasks; i++) { + var input_subtask_type = $(''); + var input_subtask_score = $(''); + var input_subtask_used_time_type = $('