From 38635106c058c93eccc46e92372a4192e902d58f Mon Sep 17 00:00:00 2001 From: Baoshuo Date: Sat, 4 Feb 2023 21:52:59 +0800 Subject: [PATCH 1/8] feat(problem/manage/data): new data configure page --- .../controllers/problem_data_configure.php | 23 ++ web/app/controllers/problem_data_manage.php | 158 +------------- web/app/libs/uoj-data-lib.php | 8 +- web/app/models/UOJForm.php | 35 ++- web/app/models/UOJProblem.php | 8 + web/app/models/UOJProblemConfigure.php | 202 ++++++++++++++++++ web/app/route.php | 1 + 7 files changed, 269 insertions(+), 166 deletions(-) create mode 100644 web/app/controllers/problem_data_configure.php create mode 100644 web/app/models/UOJProblemConfigure.php diff --git a/web/app/controllers/problem_data_configure.php b/web/app/controllers/problem_data_configure.php new file mode 100644 index 00000000..934be197 --- /dev/null +++ b/web/app/controllers/problem_data_configure.php @@ -0,0 +1,23 @@ +userCanManage(Auth::user()) || UOJResponse::page403(); + +$problem = UOJProblem::info(); +$problem_configure = new UOJProblemConfigure(UOJProblem::cur()); +$problem_configure->runAtServer(); +?> + +getTitle(['with' => 'id']))) ?> + +

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

+ +printHTML() ?> + + diff --git a/web/app/controllers/problem_data_manage.php b/web/app/controllers/problem_data_manage.php index 88edaa63..7c63d965 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..e99b2618 --- /dev/null +++ b/web/app/models/UOJProblemConfigure.php @@ -0,0 +1,202 @@ + '自定义校验器', + '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) { + 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'); + + $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); + $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->succ_href = $this->href; + $this->simple_form->config['form']['class'] = 'row gy-3 mt-2'; + $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', + 'label_class' => 'col-form-label col-4', + 'select_div_class' => 'col-8', + 'default_value' => $this->problem_conf->getVal($key, $default_val), + ] + $cfg); + } + + 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', + '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); + } + + 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', + '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); + } + + public function addTextInput(UOJForm $form, $key, $label, $default_val = '', $cfg = []) { + $this->conf_keys[$key] = true; + $form->addInput($key, [ + 'label' => $label, + 'div_class' => 'row', + '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); + } + + public function runAtServer() { + $this->simple_form->runAtServer(); + } + + public function onUpload(array &$vdata) { + $conf = $this->problem_conf->conf; + + foreach (array_keys($this->conf_keys) as $key) { + $val = UOJRequest::post($key); + 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; + } + } + } + + $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'); From c00dc85e68263f4d7ae46502d4e20a39ca6a1d91 Mon Sep 17 00:00:00 2001 From: Baoshuo Date: Sun, 5 Feb 2023 14:43:54 +0800 Subject: [PATCH 2/8] feat(problem/manage/data): point_score configure --- .../controllers/problem_data_configure.php | 1 - web/app/models/UOJProblemConfigure.php | 74 +++++++++++++++++-- web/js/uoj.js | 62 ++++++++++++++++ 3 files changed, 131 insertions(+), 6 deletions(-) diff --git a/web/app/controllers/problem_data_configure.php b/web/app/controllers/problem_data_configure.php index 934be197..e78f9b2a 100644 --- a/web/app/controllers/problem_data_configure.php +++ b/web/app/controllers/problem_data_configure.php @@ -7,7 +7,6 @@ UOJProblem::init(UOJRequest::get('id')) || UOJResponse::page404(); UOJProblem::cur()->userCanManage(Auth::user()) || UOJResponse::page403(); -$problem = UOJProblem::info(); $problem_configure = new UOJProblemConfigure(UOJProblem::cur()); $problem_configure->runAtServer(); ?> diff --git a/web/app/models/UOJProblemConfigure.php b/web/app/models/UOJProblemConfigure.php index e99b2618..4d61ca17 100644 --- a/web/app/models/UOJProblemConfigure.php +++ b/web/app/models/UOJProblemConfigure.php @@ -34,14 +34,14 @@ class UOJProblemConfigure { 'real-8' => '实数,四舍五入到小数点后 8 位', ]; - private static function getCardHeader($title) { + private static function getCardHeader($title, $body_class = 'vstack gap-3') { return <<
{$title}
-
+
EOD; } @@ -65,6 +65,13 @@ public function __construct(UOJProblem $problem) { $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'); @@ -74,7 +81,7 @@ public function __construct(UOJProblem $problem) { $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); + $this->addNumberInput($this->simple_form, 'n_sample_tests', '样例数据点个数', 0, ['help' => '样例数据点为额外数据点的前 x 个数据点。']); $this->simple_form->appendHTML(static::getCardFooter()); $this->simple_form->appendHTML(static::getCardHeader('文件配置')); @@ -90,6 +97,29 @@ public function __construct(UOJProblem $problem) { $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() { + problem_conf['score_type'] = $(this).val(); + $('.uoj-problem-configure-point-score-input', $('#div-point-score-container')).first().trigger('change'); + }); + + $('#div-point-score-container').problem_configure_point_scores(problem_conf); + }); + + EOD); + $this->simple_form->succ_href = $this->href; $this->simple_form->config['form']['class'] = 'row gy-3 mt-2'; $this->simple_form->config['submit_container']['class'] = 'col-12 text-center mt-3'; @@ -109,6 +139,13 @@ public function addSelect(UOJForm $form, $key, $options, $label, $default_val = '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(); + }); + + EOD); } public function addNumberInput(UOJForm $form, $key, $label, $default_val = '', $cfg = []) { @@ -124,6 +161,13 @@ public function addNumberInput(UOJForm $form, $key, $label, $default_val = '', $ return validateInt($x) ? '' : '必须为一个整数'; }, ] + $cfg); + $form->appendHTML(<< + $('#input-{$key}').change(function() { + problem_conf['{$key}'] = $(this).val(); + }); + + EOD); } public function addTimeLimitInput(UOJForm $form, $key, $label, $default_val = '', $cfg = []) { @@ -146,6 +190,13 @@ public function addTimeLimitInput(UOJForm $form, $key, $label, $default_val = '' } }, ] + $cfg); + $form->appendHTML(<< + $('#input-{$key}').change(function() { + problem_conf['{$key}'] = $(this).val(); + }); + + EOD); } public function addTextInput(UOJForm $form, $key, $label, $default_val = '', $cfg = []) { @@ -160,6 +211,13 @@ public function addTextInput(UOJForm $form, $key, $label, $default_val = '', $cf return ctype_graph($x) ? '' : '必须仅包含除空格以外的可见字符'; }, ] + $cfg); + $form->appendHTML(<< + $('#input-{$key}').change(function() { + problem_conf['{$key}'] = $(this).val(); + }); + + EOD); } public function runAtServer() { @@ -168,9 +226,15 @@ public function 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))); + + for ($i = 1; $i <= $n_tests; $i++) { + $conf_keys["point_score_$i"] = true; + } - foreach (array_keys($this->conf_keys) as $key) { - $val = UOJRequest::post($key); + foreach (array_keys($conf_keys) as $key) { + $val = UOJRequest::post($key, 'is_string', ''); if ($key === 'use_builtin_judger') { if ($val === 'off') { unset($conf[$key]); diff --git a/web/js/uoj.js b/web/js/uoj.js index 2d74f7ad..f53677c1 100644 --- a/web/js/uoj.js +++ b/web/js/uoj.js @@ -1177,6 +1177,68 @@ $.fn.remote_submit_type_group = function(oj, pid, url, submit_type) { }); } +// 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).html(''); + + if (isNaN(n_tests) || n_tests <= 0) { + $(this).html('不可用。'); + } + + 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]); + } + + $(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'); + }); +}; + // custom test function custom_test_onsubmit(response_text, div_result, url) { if (response_text != '') { From b3113238a2851b9871c55a8f2f82e86b439b1f49 Mon Sep 17 00:00:00 2001 From: Baoshuo Date: Sun, 5 Feb 2023 15:37:08 +0800 Subject: [PATCH 3/8] feat(problem/manage/data): problem.conf preview --- .../controllers/problem_data_configure.php | 32 +++++++++++++- web/app/models/UOJProblemConfigure.php | 15 +++++-- web/js/uoj.js | 44 +++++++++++++++++-- 3 files changed, 83 insertions(+), 8 deletions(-) diff --git a/web/app/controllers/problem_data_configure.php b/web/app/controllers/problem_data_configure.php index e78f9b2a..83a96c78 100644 --- a/web/app/controllers/problem_data_configure.php +++ b/web/app/controllers/problem_data_configure.php @@ -9,6 +9,19 @@ $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']))) ?> @@ -17,6 +30,23 @@ getTitle(['with' => 'id']) ?> 数据配置 -printHTML() ?> +
+
+
+
problem.conf 预览
+ +
+
+
+ + +
+
+
+ printHTML() ?> +
+
diff --git a/web/app/models/UOJProblemConfigure.php b/web/app/models/UOJProblemConfigure.php index 4d61ca17..ca3d82ba 100644 --- a/web/app/models/UOJProblemConfigure.php +++ b/web/app/models/UOJProblemConfigure.php @@ -81,7 +81,7 @@ public function __construct(UOJProblem $problem) { $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->addNumberInput($this->simple_form, 'n_sample_tests', '样例数据点个数', 0, ['help' => '样例数据点为额外数据点中的前 x 个数据点。']); $this->simple_form->appendHTML(static::getCardFooter()); $this->simple_form->appendHTML(static::getCardHeader('文件配置')); @@ -99,7 +99,10 @@ public function __construct(UOJProblem $problem) { $this->simple_form->appendHTML(static::getCardHeader('测试点分值', '')); $this->simple_form->appendHTML(<<
+
+ 展开/收起全部 +
+
EOD); $this->simple_form->appendHTML(static::getCardFooter()); $this->simple_form->appendHTML(<<simple_form->succ_href = $this->href; - $this->simple_form->config['form']['class'] = 'row gy-3 mt-2'; + $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'; @@ -143,6 +146,7 @@ public function addSelect(UOJForm $form, $key, $options, $label, $default_val = EOD); @@ -165,6 +169,7 @@ public function addNumberInput(UOJForm $form, $key, $label, $default_val = '', $ EOD); @@ -194,6 +199,7 @@ public function addTimeLimitInput(UOJForm $form, $key, $label, $default_val = '' EOD); @@ -215,6 +221,7 @@ public function addTextInput(UOJForm $form, $key, $label, $default_val = '', $cf EOD); @@ -250,6 +257,8 @@ public function onUpload(array &$vdata) { } else { if ($val !== '') { $conf[$key] = $val; + } else if (isset($conf[$key])) { + unset($conf[$key]); } } } diff --git a/web/js/uoj.js b/web/js/uoj.js index f53677c1..7345e94c 100644 --- a/web/js/uoj.js +++ b/web/js/uoj.js @@ -1177,6 +1177,35 @@ $.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() { @@ -1190,17 +1219,24 @@ $.fn.problem_configure_point_scores = function(problem_conf) { } for (var i = 1; i <= n_tests; i++) { - var input_point_score = $(''); + 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)) + $('
') + .append($('
').append('')) + .append($('
').append(input_point_score)) ) ); } From 6c24bd9bf6cdd7ef747b867e98d76916e8668449 Mon Sep 17 00:00:00 2001 From: Baoshuo Date: Sun, 5 Feb 2023 18:19:45 +0800 Subject: [PATCH 4/8] feat(problem/manage/data): subtasks configure --- web/app/models/UOJProblemConfigure.php | 93 ++++++++++++++-- web/js/uoj.js | 148 ++++++++++++++++++++++++- 2 files changed, 231 insertions(+), 10 deletions(-) diff --git a/web/app/models/UOJProblemConfigure.php b/web/app/models/UOJProblemConfigure.php index ca3d82ba..79e1303b 100644 --- a/web/app/models/UOJProblemConfigure.php +++ b/web/app/models/UOJProblemConfigure.php @@ -37,7 +37,7 @@ class UOJProblemConfigure { private static function getCardHeader($title, $body_class = 'vstack gap-3') { return << -
+
{$title}
@@ -99,10 +99,11 @@ public function __construct(UOJProblem $problem) { $this->simple_form->appendHTML(static::getCardHeader('测试点分值', '')); $this->simple_form->appendHTML(<< - 展开/收起全部 -
- +
+ 展开/收起全部 +
+
+ EOD); $this->simple_form->appendHTML(static::getCardFooter()); $this->simple_form->appendHTML(<< 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'; @@ -137,7 +190,7 @@ public function addSelect(UOJForm $form, $key, $options, $label, $default_val = $form->addSelect($key, [ 'options' => $options, 'label' => $label, - 'div_class' => 'row', + '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), @@ -157,7 +210,7 @@ public function addNumberInput(UOJForm $form, $key, $label, $default_val = '', $ $form->addInput($key, [ 'type' => 'number', 'label' => $label, - 'div_class' => 'row', + '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), @@ -181,7 +234,7 @@ public function addTimeLimitInput(UOJForm $form, $key, $label, $default_val = '' 'type' => 'number', 'label' => $label, 'input_attrs' => ['step' => 0.001], - 'div_class' => 'row', + '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), @@ -209,7 +262,7 @@ public function addTextInput(UOJForm $form, $key, $label, $default_val = '', $cf $this->conf_keys[$key] = true; $form->addInput($key, [ 'label' => $label, - 'div_class' => 'row', + '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), @@ -235,11 +288,33 @@ 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') { diff --git a/web/js/uoj.js b/web/js/uoj.js index 7345e94c..e9baaa72 100644 --- a/web/js/uoj.js +++ b/web/js/uoj.js @@ -1212,10 +1212,12 @@ $.fn.problem_configure_point_scores = function(problem_conf) { var _this = this; var n_tests = parseInt(problem_conf['n_tests']); - $(this).html(''); + $(this).empty(); if (isNaN(n_tests) || n_tests <= 0) { $(this).html('不可用。'); + + return; } for (var i = 1; i <= n_tests; i++) { @@ -1275,6 +1277,150 @@ $.fn.problem_configure_point_scores = function(problem_conf) { }); }; +// 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'); + 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 = $(''); + var input_point_score = $(''); if (problem_conf['point_score_' + i]) { input_point_score.val(problem_conf['point_score_' + i]); @@ -1312,12 +1312,13 @@ $.fn.problem_configure_subtasks = function(problem_conf) { 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_end = $(''); + var input_subtask_score = $(''); var input_subtask_used_time_type = $('