judge: support generate testdata (#695)

Co-authored-by: panda <panda_dtdyy@outlook.com>
This commit is contained in:
undefinedandpanda authored and GitHub committed 2023-12-14 17:03:42 +08:00
1 parent b83b2e792e
commit 55b4e1aaf0
27 files changed
+454 -68

No files matched your search

@@ -0,0 +1,63 @@
import { assign } from 'lodash';
import React from 'react';
import ReactDOM from 'react-dom/client';
import DOMAttachedObject from 'vj/components/DOMAttachedObject';
import AutoComplete, { AutoCompleteOptions } from '.';
import FileSelectAutoCompleteFC from './components/FileSelectAutoComplete';
interface FileSelectOptions {
data: {
id: string;
name: string;
}[];
}
const Component = React.forwardRef<any, any>((props, ref) => {
const [value, setValue] = React.useState(props.value);
return (
<FileSelectAutoCompleteFC
ref={ref as any}
height="auto"
selectedKeys={value.split(',').map((i) => i.trim()).filter((i) => i)}
onChange={(v) => {
setValue(v);
props.onChange(v);
}}
data={props.data}
multi={props.multi}
/>
);
});
export default class FileSelectAutoComplete<Multi extends boolean> extends AutoComplete {
static DOMAttachKey = 'ucwFileSelectAutoCompleteInstance';
options: FileSelectOptions & AutoCompleteOptions;
constructor($dom, options: FileSelectOptions & AutoCompleteOptions<Multi>) {
super($dom, {
classes: 'custom-select',
...options,
});
}
value(): Multi extends true ? number[] : string {
if (this.options.multi) return this.ref?.getSelectedItemKeys().map((i) => +i) ?? this.$dom.val();
return this.ref?.getSelectedItems()[0] ?? null;
}
attach() {
const value = this.$dom.val();
ReactDOM.createRoot(this.container).render(
<Component
ref={(ref) => { this.ref = ref; }}
data={this.options.data}
value={value}
multi={this.options.multi}
onChange={this.onChange}
/>,
);
}
}
assign(FileSelectAutoComplete, DOMAttachedObject);
window.Hydro.components.FileSelectAutoComplete = FileSelectAutoComplete;
@@ -100,7 +100,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(class ScratchpadTool
}
render() {
let canUsePretest = ['default', 'fileio'].includes(UiContext.pdoc.config?.type);
let canUsePretest = UiContext.pdoc.config?.type === 'default';
const langInfo = availableLangs[this.props.editorLang];
if (UiContext.pdoc.config?.type === 'remote_judge' && langInfo) {
if (langInfo.pretest) canUsePretest = true;
@@ -3,7 +3,7 @@ import { i18n } from 'vj/utils';
export default function reducer(state = {
pretest: {
visible: ['default', 'fileio'].includes(UiContext.pdoc.config?.type)
visible: UiContext.pdoc.config?.type === 'default'
? localStorage.getItem('scratchpad/pretest') === 'true'
: false,
},
+1 -1
View File
@@ -1,4 +1,5 @@
__langname: English
'File IO: {0}': 'File IO: {0}'
Can be Extended For: Extension
Current Status: Status
Handled Requests: Req
@@ -27,7 +28,6 @@ problem_main: Problem Set
problem_solution: Problem Solution
problem_submit: Problem Submit
problem_type.default: 'Type: Default'
problem_type.fileio: 'Type: FileIO ({0})'
problem_type.interactive: 'Type: Interactive'
problem_type.objective: 'Type: Objective'
problem_type.remote_judge: 'Type: RemoteJudge'
+9 -2
View File
@@ -27,7 +27,9 @@ __langname: 简体中文
'Current dataset: {0}': '当前测试数据: {0}'
'Discussion: Terms Of Service': 讨论区服务条款
'Drag and drop testcases here:': '拖拽测试点到此处:'
'Enter a new name for the file: ': 请输入新的文件名:
'Failed to get credential: {0}': '获取认证数据失败: {0}'
'File IO: {0}': 文件IO:{0}
'File upload failed: {0}': 文件上传失败:{0}
'Format: category 1, category 2, ..., category n': 格式:分类1, 分类2, ..., 分类n
'Hello, {0}! You can click following link to active your new email of your {1} account:': 您好,{0}!您可以点击以下链接来激活您 {1} 账户的新电子邮件地址:
@@ -50,6 +52,8 @@ __langname: 简体中文
(None): (无)
(Not changed): 未更改
6-Digit Code: 6位代码
A data generator is a program that generates testdata. It should output result to stdout.: 数据生成器是一个生成测试数据的程序。它应该将结果输出到标准输出。
A standard program is a program that solves the problem. It should output answer to stdout.: 标准程序是一个解决问题的程序。它应该将答案输出到标准输出。
Aborted: 已放弃
About {0}: 关于 {0}
About Markdown: 关于 Markdown
@@ -245,6 +249,7 @@ Current Status: 当前状态
currently offline: 目前离线
Daily update check: 每日自动检查更新
Dark: 暗色
Data Generator: 数据生成器
Data of Problem {0} not found.: 题目 {0} 的数据缺失。
Data of problem {1} not found.: 题目 {1} 的数据未找到。
Data of record {0} not found.: 记录 {0} 的数据未找到。
@@ -358,7 +363,6 @@ End Time: 结束时间
Enroll Training: 参加训练
Enrolled: 已参加
Enrollees: 参加人数
'Enter a new name for the file: ': 请输入新的文件名:
error: 错误
Errors detected in the config. Confirm save?: 配置中存在错误。确认保存?
Evaluated difficulty: 估计的难度
@@ -398,6 +402,9 @@ Freeze scoreboard with N minutes remaining: 在赛事剩余N分钟时封榜
fs_upload: 上传文件
Gender Visibility: 性别可见性
Gender: 性别
Generate testdata is not supported for this problem type.: 此题目类型不支持生成测试数据。
Generate Testdata: 生成测试数据
Generate: 生成
Gravatar URL Prefix: Gravatar地址前缀
Group / UID: 小组或 UID
Group Name: 名称
@@ -686,7 +693,6 @@ problem_main: 题库
problem_solution: 题解
problem_submit: 递交代码
problem_type.default: 传统题
problem_type.fileio: '文件IO ({0})'
problem_type.interactive: 交互题
problem_type.objective: 客观题
problem_type.remote_judge: 远端评测题
@@ -845,6 +851,7 @@ Sorry: 对不起
Sort by: 排序
Source type: 来源类型
Standard mode: 标准模式
Standard Program: 标准程序
Star Topic: 收藏讨论
Star: 收藏
Starred Users: 关注用户
@@ -2,7 +2,8 @@ import $ from 'jquery';
import { map } from 'lodash';
import React from 'react';
import ReactDOM from 'react-dom/client';
import { ActionDialog, ConfirmDialog } from 'vj/components/dialog/index';
import FileSelectAutoComplete from 'vj/components/autocomplete/FileSelectAutoComplete';
import { ActionDialog, ConfirmDialog, InfoDialog } from 'vj/components/dialog/index';
import createHint from 'vj/components/hint';
import Notification from 'vj/components/notification';
import { previewFile } from 'vj/components/preview/preview.page';
@@ -318,6 +319,48 @@ const page = new NamedPage('problem_files', () => {
handleClickUpload(type, files);
}
async function handleGenerateTestdata(ev) {
ev.preventDefault();
const gen = $('[name=gen]').val();
const std = $('[name=std]').val();
if (!gen) {
Notification.error(i18n('Please input the generator.'));
return;
}
if (!std) {
Notification.error(i18n('Please input the standard program.'));
return;
}
try {
const res = await request.post('', {
operation: 'generate_testdata',
gen,
std,
});
if (res.error) Notification.error(res.error);
else {
Notification.success(i18n('Generating...'));
await new InfoDialog({
$body: tpl`
<div class="typo">
<iframe src="${res.url}"
scrolling="yes" border="0" frameborder="no" framespacing="0" width="100%" style="height: 70vh;"></iframe>
</div>`,
width: `${window.innerWidth - 200}px`,
height: `${window.innerHeight - 100}px`,
}).open();
}
} catch (error) {
Notification.error([error.message, ...error.params].join(' '));
}
}
if (UiContext.pdoc) {
FileSelectAutoComplete.getOrConstruct($('[name=gen]'), { data: UiContext.pdoc.data });
FileSelectAutoComplete.getOrConstruct($('[name=std]'), { data: UiContext.pdoc.data });
$('[name="generate_testdata"]').on('click', handleGenerateTestdata);
}
if ($('[name="upload_testdata"]').length) {
$(document).on('click', '[name="upload_testdata"]', () => handleClickUpload('testdata'));
$(document).on('click', '[name="upload_file"]', () => handleClickUpload('additional_file'));
@@ -69,6 +69,9 @@
<span class="bp5-tag bp5-large bp5-minimal problem__tag-item">{{ _('ID') }}: {{ pdoc.docId }}</span>
{% endif %}
<span class="bp5-tag bp5-large bp5-minimal problem__tag-item icon icon-book">{{ _('problem_type.' + pdoc.config.type).format(pdoc.config.subType) }}</span>
{% if pdoc.config.type == 'default' and pdoc.config.subType %}
<span class="bp5-tag bp5-large bp5-minimal problem__tag-item icon icon-book">{{ _('File IO: {0}').format(pdoc.config.subType) }}</span>
{% endif %}
{% if pdoc.config.type != 'objective' and pdoc.config.type != 'submit_answer' %}
<span class="bp5-tag bp5-large bp5-minimal problem__tag-item icon icon-stopwatch">{{ pdoc.config.timeMax if pdoc.config.timeMax == pdoc.config.timeMin else pdoc.config.timeMin+'~'+pdoc.config.timeMax }}ms</span>
<span class="bp5-tag bp5-large bp5-minimal problem__tag-item icon icon-comparison">{{ pdoc.config.memoryMax if pdoc.config.memoryMax == pdoc.config.memoryMin else pdoc.config.memoryMin+'~'+pdoc.config.memoryMax }}MiB</span>
@@ -1,3 +1,4 @@
{% import "components/nothing.html" as nothing with context %}
{% extends "layout/basic.html" %}
{% block content %}
<div class="row">
@@ -27,6 +28,36 @@
</div>
{% endif %}
</div>
{% if not reference and (handler.user.own(pdoc, perm.PERM_EDIT_PROBLEM_SELF) or handler.user.hasPerm(perm.PERM_EDIT_PROBLEM)) %}
{{ set(UiContext, {
pdoc: pdoc
}) }}
<div class="section">
<div class="section__header">
<h1 class="section__title">{{ _('Generate Testdata') }}(Beta)</h1>
</div>
{{ noscript_note.render() }}
<div class="section__body generate">
{% if pdoc.config.type === 'default' %}
{{ form.form_text({
columns: 12,
label:'Data Generator',
name:'gen',
help_text:'A data generator is a program that generates testdata. It should output result to stdout.'
}) }}
{{ form.form_text({
columns: 12,
label:'Standard Program',
name:'std',
help_text:'A standard program is a program that solves the problem. It should output answer to stdout.'
}) }}
<button class="rounded button" name="generate_testdata">{{ _('Generate') }}</button>
{% else %}
{{ nothing.render('Generate testdata is not supported for this problem type.') }}
{% endif %}
</div>
</div>
{% endif %}
</div>
<div class="medium-4 columns">
<div class="section">