ui: add configYamlFormat & fix bugs

This commit is contained in:
panda committed 2022-05-10 01:52:52 +08:00
1 parent 4ccb838a06
commit 3f37b02b79
5 files changed
+167 -114

No files matched your search

@@ -128,8 +128,10 @@ const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, re
else onChange(selectedKeys.join(',')); else onChange(selectedKeys.join(','));
}; };
let first = !multi;
useEffect(() => { useEffect(() => {
dispatchChange(); if (first) first = false;
else dispatchChange();
const ids = []; const ids = [];
for (const key of selectedKeys) if (!valueCache[key]) ids.push(key); for (const key of selectedKeys) if (!valueCache[key]) ids.push(key);
if (!ids.length) return; if (!ids.length) return;
@@ -4,6 +4,7 @@ import { connect } from 'react-redux';
import { load } from 'vj/components/monaco/loader'; import { load } from 'vj/components/monaco/loader';
import Editor from 'vj/components/editor'; import Editor from 'vj/components/editor';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import type { ProblemConfigFile, TestCaseConfig } from 'hydrooj/src/interface';
const mapStateToProps = (state) => ({ const mapStateToProps = (state) => ({
config: state.config, config: state.config,
@@ -22,10 +23,62 @@ interface Props {
handleUpdateCode: Function; handleUpdateCode: Function;
} }
const configKey = [
'type', 'subType', 'target', 'score', 'time',
'memory', 'filename', 'checker_type', 'checker', 'interactor',
'user_extra_files', 'judge_extra_files', 'detail', 'outputs', 'redirect',
'cases', 'subtasks', 'langs',
];
const subtasksKey = [
'time', 'memory', 'score', 'if', 'id',
'type', 'cases',
];
const casesKey = ['time', 'memory', 'input', 'output'];
function configYamlFormat(config: ProblemConfigFile) {
const formatConfig: ProblemConfigFile = {};
configKey.forEach((key) => {
if (config[key] !== undefined) {
if (key === 'checker_type' && config.type !== 'default') return;
if (key === 'checker'
&& (['default', 'strict'].includes(formatConfig.checker_type) || formatConfig.checker_type === undefined)) return;
if (key === 'interactor' && config.type !== 'interactive') return;
if (key === 'subtasks') {
formatConfig[key] = [];
config[key].forEach((subtask) => {
const formatSubtask: object = {};
subtasksKey.forEach((subtaskKey) => {
if (subtask[subtaskKey] !== undefined) {
formatSubtask[subtaskKey] = subtask[subtaskKey];
}
});
formatConfig[key].push(formatSubtask);
});
} else if (key === 'cases') {
formatConfig[key] = [];
config[key].forEach((caseItem) => {
const formatCase: TestCaseConfig = {
time: 1000, memory: 256, input: '', output: '',
};
casesKey.forEach((caseKey) => {
if (caseItem[caseKey] !== undefined) formatCase[caseKey] = caseItem[caseKey];
else delete formatCase[caseKey];
});
formatConfig[key].push(formatCase);
});
} else formatConfig[key] = config[key];
}
});
return formatConfig;
}
export default connect(mapStateToProps, mapDispatchToProps)(class MonacoEditor extends React.PureComponent<Props> { export default connect(mapStateToProps, mapDispatchToProps)(class MonacoEditor extends React.PureComponent<Props> {
disposable = []; disposable = [];
containerElement: HTMLElement; containerElement: HTMLElement;
private __preventUpdate = false; private __preventUpdate = false;
private __preventFormat = false;
editor: editor.IStandaloneCodeEditor; editor: editor.IStandaloneCodeEditor;
model: editor.ITextModel; model: editor.ITextModel;
@@ -40,7 +93,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(class MonacoEditor e
model: this.model, model: this.model,
onChange: (value: string) => { onChange: (value: string) => {
this.__preventUpdate = true; this.__preventUpdate = true;
this.props.handleUpdateCode(value); if (!this.__preventFormat) this.props.handleUpdateCode(value);
this.__preventUpdate = false; this.__preventUpdate = false;
}, },
}) as Editor; }) as Editor;
@@ -50,14 +103,16 @@ export default connect(mapStateToProps, mapDispatchToProps)(class MonacoEditor e
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if (this.__preventUpdate) return; if (this.__preventUpdate) return;
if (yaml.dump(prevProps.config) !== yaml.dump(this.props.config)) { if (yaml.dump(prevProps.config) !== yaml.dump(this.props.config)) {
this.__preventFormat = true;
this.model?.pushEditOperations( this.model?.pushEditOperations(
[], [],
[{ [{
range: this.model.getFullModelRange(), range: this.model.getFullModelRange(),
text: yaml.dump(this.props.config), text: yaml.dump(configYamlFormat(this.props.config)),
}], }],
undefined, undefined,
); );
this.__preventFormat = false;
} }
} }
@@ -1,4 +1,4 @@
import React, { useRef, useState } from 'react'; import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import i18n from 'vj/utils/i18n'; import i18n from 'vj/utils/i18n';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
@@ -13,8 +13,14 @@ const SelectValue = {
task_type: ['min', 'max', 'sum'], task_type: ['min', 'max', 'sum'],
}; };
const eq = (a: SubtaskConfig[], b: SubtaskConfig[]) => isEqual(a, b); const eq = (a: TestCaseConfig, b: TestCaseConfig) => isEqual(a, b);
const eq1 = (a: TestCaseConfig, b: TestCaseConfig) => isEqual(a, b); const eqId = (a: SubtaskConfig[], b: SubtaskConfig[]) => {
if (a.length !== b.length) return true;
for (let i = 0; i < a.length; i++) {
if (a[i].id !== b[i].id) return true;
}
return false;
};
function FormItem({ function FormItem({
columns, label, children, helpText = '', disableLabel = false, ...props columns, label, children, helpText = '', disableLabel = false, ...props
@@ -120,7 +126,7 @@ function BasicInfo() {
? ( ? (
<Switch <Switch
checked={checkerType === 'strict'} checked={checkerType === 'strict'}
label="Don't ignore space and enter." label={i18n('Don\'t ignore space and enter.')}
onChange={ onChange={
() => { () => {
dispatch({ type: 'CONFIG_FORM_UPDATE', key: 'checker_type', value: checkerType === 'strict' ? 'default' : 'strict' }); dispatch({ type: 'CONFIG_FORM_UPDATE', key: 'checker_type', value: checkerType === 'strict' ? 'default' : 'strict' });
@@ -153,12 +159,12 @@ function ExtraFilesConfig() {
const dispatch = useDispatch(); const dispatch = useDispatch();
return ( return (
<FormItem columns={12} label="ExtraFilesTabs" disableLabel> <FormItem columns={12} label="ExtraFilesTabs" disableLabel>
<Switch checked={showTab} label="Extra Files Config" onChange={() => setshowTab(!showTab)} /> <Switch checked={showTab} label={i18n('Extra Files Config')} onChange={() => setshowTab(!showTab)} />
<div style={!showTab ? { display: 'none' } : {}}> <div style={!showTab ? { display: 'none' } : {}}>
<Tabs id="ExtraFilesTabs"> <Tabs id="ExtraFilesTabs">
<Tab <Tab
id="user_extra_files" id="user_extra_files"
title="user_extra_files" title={i18n('user_extra_files')}
panel={( panel={(
<CustomSelectAutoComplete <CustomSelectAutoComplete
data={Files} data={Files}
@@ -170,7 +176,7 @@ function ExtraFilesConfig() {
/> />
<Tab <Tab
id="judge_extra_files" id="judge_extra_files"
title="judge_extra_files" title={i18n('judge_extra_files')}
panel={( panel={(
<CustomSelectAutoComplete <CustomSelectAutoComplete
data={Files} data={Files}
@@ -188,7 +194,7 @@ function ExtraFilesConfig() {
function CasesSubCasesTable({ index, subindex }) { function CasesSubCasesTable({ index, subindex }) {
const subcases = useSelector((state: RootState) => (index === -1 const subcases = useSelector((state: RootState) => (index === -1
? state.config.cases[subindex] : state.config.subtasks[index].cases[subindex]), eq1); ? state.config.cases[subindex] : state.config.subtasks[index].cases[subindex]), eq);
const Files = useSelector((state: RootState) => state.testdata); const Files = useSelector((state: RootState) => state.testdata);
const dispatch = useDispatch(); const dispatch = useDispatch();
const dispatcher = (casesKey: string): React.ChangeEventHandler<HTMLInputElement | HTMLSelectElement> => (ev) => { const dispatcher = (casesKey: string): React.ChangeEventHandler<HTMLInputElement | HTMLSelectElement> => (ev) => {
@@ -262,14 +268,7 @@ function CasesSubCasesTable({ index, subindex }) {
} }
function CasesTable({ index }) { function CasesTable({ index }) {
const [time, setTime] = useState(''); const casesLength = useSelector((state: RootState) => (index === -1 ? state.config.cases?.length : state.config.subtasks[index].cases?.length));
const [memory, setMemory] = useState('');
const [input, setInput] = useState('');
const [output, setOutput] = useState('');
const cases = useSelector((state: RootState) => (index === -1 ? state.config.cases : state.config.subtasks[index].cases), eq);
const Files = useSelector((state: RootState) => state.testdata);
const inputRef = useRef(null);
const outputRef = useRef(null);
const dispatch = useDispatch(); const dispatch = useDispatch();
return ( return (
<table className="data-table"> <table className="data-table">
@@ -279,73 +278,67 @@ function CasesTable({ index }) {
index === -1 && ( index === -1 && (
<> <>
<th> <th>
{i18n('Time')}<br /> {i18n('Time')}
<input
value={time || ''}
onChange={(ev) => setTime(ev.currentTarget.value)}
className="textbox"
/>
</th> </th>
<th> <th>
{i18n('Memory')}<br /> {i18n('Memory')}
<input
value={memory || ''}
onChange={(ev) => setMemory(ev.currentTarget.value)}
className="textbox"
/>
</th> </th>
</> </>
) )
} }
<th> <th>
{i18n('Input')}<br /> {i18n('Input')}
<CustomSelectAutoComplete
ref={inputRef}
width="100%"
data={Files}
onChange={setInput}
/>
</th> </th>
<th> <th>
{i18n('Output')}<br /> {i18n('Output')}
<CustomSelectAutoComplete
ref={outputRef}
width="100%"
data={Files}
onChange={setOutput}
/>
</th> </th>
<th className="col--operation"> <th className="col--operation">
<span className="icon icon-wrench"></span><br />
<a <a
onClick={() => { onClick={() => dispatch({
setTime(''); type: index === -1 ? 'CONFIG_CASES_UPDATE' : 'CONFIG_SUBTASK_UPDATE',
setMemory(''); id: index,
inputRef?.current.setQuery(''); key: 'cases-add',
outputRef?.current.setQuery(''); value: index === -1 ? {
dispatch({ time: 1000, memory: 256, input: '', output: '',
type: index === -1 ? 'CONFIG_CASES_UPDATE' : 'CONFIG_SUBTASK_UPDATE', } : { input: '', output: '' },
id: index, })}
key: 'cases-add',
value: index === -1 ? {
time, memory, input, output,
} : { input, output },
});
}}
><span className="icon icon-add" /> ><span className="icon icon-add" />
</a> </a>
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{cases && cases.map((k, v) => <CasesSubCasesTable index={index} subindex={v} key={JSON.stringify(k)} />)} {casesLength && [...Array(casesLength).keys()].map((i) => <CasesSubCasesTable index={index} subindex={i} key={i} />)}
</tbody> </tbody>
</table> </table>
); );
} }
function SubtasksTable({ data, index }) { function SubtasksIds({ index }) {
const subtasks = useSelector((state: RootState) => state.config.subtasks, eq); const subtasks = useSelector((state: RootState) => state.config.subtasks, eqId);
const subtaskIf = useSelector((state: RootState) => state.config.subtasks[index].if);
const dispatch = useDispatch();
const subtasksIds = subtasks.map((i) => i.id).filter((i) => i !== undefined);
return (
<tr>
<td>if</td>
<td colSpan={4}>
<CustomSelectAutoComplete
data={subtasksIds}
selectedKeys={subtaskIf?.join(',').split(',') || []}
itemKey={(item) => `${item}`}
onChange={(val) => dispatch({
type: 'CONFIG_SUBTASK_UPDATE', id: index, key: 'if', value: val.split(','),
})}
multi
/>
</td>
</tr>
);
}
function SubtasksTable({ index }) {
const subtask = (key: string) => useSelector((state: RootState) => state.config.subtasks[index][key]);
const dispatch = useDispatch(); const dispatch = useDispatch();
const dispatcher = (key: string): React.ChangeEventHandler<HTMLInputElement | HTMLSelectElement> => (ev) => { const dispatcher = (key: string): React.ChangeEventHandler<HTMLInputElement | HTMLSelectElement> => (ev) => {
dispatch({ dispatch({
@@ -353,7 +346,7 @@ function SubtasksTable({ data, index }) {
}); });
}; };
return ( return (
<div key={index}> <>
<span>Subtasks #{index + 1} </span> <span>Subtasks #{index + 1} </span>
<a onClick={() => { dispatch({ type: 'CONFIG_SUBTASK_UPDATE', id: index, key: 'add' }); }}><span className="icon icon-add"></span></a> <a onClick={() => { dispatch({ type: 'CONFIG_SUBTASK_UPDATE', id: index, key: 'add' }); }}><span className="icon icon-add"></span></a>
<a <a
@@ -375,21 +368,21 @@ function SubtasksTable({ data, index }) {
<tr> <tr>
<td> <td>
<input <input
value={data.id} value={subtask('id')?.toString() || ''}
onChange={dispatcher('id')} onChange={dispatcher('id')}
className="textbox" className="textbox"
/> />
</td> </td>
<td> <td>
<input <input
value={data.score || ''} value={subtask('score') || ''}
onChange={dispatcher('score')} onChange={dispatcher('score')}
className="textbox" className="textbox"
/> />
</td> </td>
<td> <td>
<select <select
value={data.type} value={subtask('type') || ''}
onChange={dispatcher('type')} onChange={dispatcher('type')}
className="select" className="select"
> >
@@ -399,42 +392,30 @@ function SubtasksTable({ data, index }) {
</td> </td>
<td> <td>
<input <input
value={data.time || ''} value={subtask('time') || ''}
onChange={dispatcher('time')} onChange={dispatcher('time')}
className="textbox" className="textbox"
/> />
</td> </td>
<td> <td>
<input <input
value={data.memory || ''} value={subtask('memory') || ''}
onChange={dispatcher('memory')} onChange={dispatcher('memory')}
className="textbox" className="textbox"
/> />
</td> </td>
</tr> </tr>
<tr> <SubtasksIds index={index} />
<td>if</td>
<td colSpan={4}>
<CustomSelectAutoComplete
data={subtasks}
selectedKeys={data.if || []}
onChange={(val) => dispatch({
type: 'CONFIG_SUBTASK_UPDATE', id: index, key: 'if', value: val.split(','),
})}
multi
/>
</td>
</tr>
</tbody> </tbody>
</table> </table>
<CasesTable index={index} /> <CasesTable index={index} />
</div> </>
); );
} }
function TaskConfig({ onAutoLoad }) { function TaskConfig({ onAutoLoad }) {
const subtasks = useSelector((state: RootState) => state.config.subtasks, eq); const subtasksLength = useSelector((state: RootState) => state.config.subtasks?.length);
const cases = useSelector((state: RootState) => state.config.cases, eq); const casesLength = useSelector((state: RootState) => state.config.cases?.length);
const dispatch = useDispatch(); const dispatch = useDispatch();
return ( return (
<FormItem columns={12} label="Task Settings"> <FormItem columns={12} label="Task Settings">
@@ -449,11 +430,11 @@ function TaskConfig({ onAutoLoad }) {
<ManagedInput placeholder="Score" formKey="score" /> <ManagedInput placeholder="Score" formKey="score" />
</FormItem> </FormItem>
<FormItem columns={12} label="Cases Settings" disableLabel> <FormItem columns={12} label="Cases Settings" disableLabel>
{cases && <Switch label="Use Subtasks" onChange={() => dispatch({ type: 'CONFIG_SUBTASKS_SWITCH', value: true })} />} {casesLength && <Switch label={i18n('Use Subtasks Mode')} onChange={() => dispatch({ type: 'CONFIG_SUBTASKS_SWITCH', value: true })} />}
{ {
subtasks || cases subtasksLength || casesLength
? subtasks && <ul>{subtasks.map((k, v) => <SubtasksTable data={k} index={v} key={k.id} />)}</ul> ? subtasksLength && [...Array(subtasksLength).keys()].map((i) => <SubtasksTable index={i} key={i} />)
|| cases && (<CasesTable index={-1} />) || casesLength && (<CasesTable index={-1} />)
: ( : (
<a onClick={() => onAutoLoad()}> <a onClick={() => onAutoLoad()}>
<span className="icon icon-settings"> {i18n('Auto Read Tasks')}</span> <span className="icon icon-settings"> {i18n('Auto Read Tasks')}</span>
@@ -480,7 +461,7 @@ function LangConfig() {
}, [JSON.stringify(selectedKeys)]); }, [JSON.stringify(selectedKeys)]);
return ( return (
<FormItem columns={12} label="LangsTabs" disableLabel> <FormItem columns={12} label="LangsTabs" disableLabel>
<Switch checked={showTab} label="Langs Config" onChange={() => setshowTab(!showTab)} /> <Switch checked={showTab} label={i18n('Langs Config')} onChange={() => setshowTab(!showTab)} />
<FormItem columns={12} label="langs" style={!showTab ? { display: 'none' } : {}}> <FormItem columns={12} label="langs" style={!showTab ? { display: 'none' } : {}}>
<CustomSelectAutoComplete <CustomSelectAutoComplete
ref={ref} ref={ref}
@@ -10,18 +10,13 @@ export default function reducer(state = { type: 'default' } as ProblemConfigFile
} }
case 'CONFIG_FORM_UPDATE': { case 'CONFIG_FORM_UPDATE': {
const next = { ...state, [action.key]: action.value }; const next = { ...state, [action.key]: action.value };
if (action.key === 'score' && action.value) next.score = +next.score;
if (!action.value || (typeof action.value === 'object' && !action.value.join(''))) delete next[action.key]; if (!action.value || (typeof action.value === 'object' && !action.value.join(''))) delete next[action.key];
if (action.key === 'type' && action.value !== 'default') {
delete next.checker_type;
delete next.checker;
}
if (action.key === 'type' && action.value !== 'interactive') delete next.interactor;
if (action.key === 'checker_type' && ['default', 'strict'].includes(action.value)) delete next.checker;
return next; return next;
} }
case 'CONFIG_CODE_UPDATE': { case 'CONFIG_CODE_UPDATE': {
try { try {
return yaml.load(action.payload); return { ...state, ...yaml.load(action.payload) as object };
} catch { } catch {
return state; return state;
} }
@@ -44,39 +39,45 @@ export default function reducer(state = { type: 'default' } as ProblemConfigFile
} }
case 'CONFIG_SUBTASKS_SWITCH': { case 'CONFIG_SUBTASKS_SWITCH': {
const next = { ...state }; const next = { ...state };
next.subtasks = next.cases; next.subtasks = next.cases.map((k, v) => ({
id: v, time: k.time, memory: k.memory, cases: [{ input: k.input, output: k.output }],
}));
if (!next.subtasks.length) next.subtasks.push({ id: 0 }); if (!next.subtasks.length) next.subtasks.push({ id: 0 });
delete next.cases; delete next.cases;
return next; return next;
} }
case 'CONFIG_CASES_UPDATE': { case 'CONFIG_CASES_UPDATE': {
if (['time', 'memory'].includes(action.casesKey) && action.value !== '') action.value = +action.value;
if (action.key === 'cases-add') return { ...state, cases: [...state.cases, action.value] }; if (action.key === 'cases-add') return { ...state, cases: [...state.cases, action.value] };
if (action.key === 'cases-edit') { if (action.key === 'cases-delete') {
const cases = cloneDeep(state.cases);
cases[action.casesId][action.casesKey] = action.value;
return { ...state, cases };
} if (action.key === 'cases-delete') {
return { ...state, cases: state.cases.filter((k, v) => v !== action.value) }; return { ...state, cases: state.cases.filter((k, v) => v !== action.value) };
} }
return state; const cases = cloneDeep(state.cases);
cases[action.casesId][action.casesKey] = action.value;
if (action.value === '') delete cases[action.casesId][action.casesKey];
return { ...state, cases };
} }
case 'CONFIG_SUBTASK_UPDATE': { case 'CONFIG_SUBTASK_UPDATE': {
const subtasks = cloneDeep(state.subtasks);
const subsubtasks = cloneDeep(state.subtasks[action.id]);
if (action.value !== ''
&& (['time', 'memory'].includes(action.casesKey) || ['time', 'memory', 'score', 'id'].includes(action.key))
) action.value = +action.value;
if (action.key === 'if' && action.value.join('') !== '') action.value = action.value.map((i) => +i);
if (action.key.split('-')[0] === 'cases') { if (action.key.split('-')[0] === 'cases') {
const subtasks = cloneDeep(state.subtasks);
const subsubtasks = cloneDeep(state.subtasks[action.id]);
if (action.key === 'cases-add') subsubtasks.cases.push(action.value); if (action.key === 'cases-add') subsubtasks.cases.push(action.value);
else if (action.key === 'cases-edit') subsubtasks.cases[action.casesId][action.casesKey] = action.value; else if (action.key === 'cases-edit') subsubtasks.cases[action.casesId][action.casesKey] = action.value;
else if (action.key === 'cases-delete') { else if (action.key === 'cases-delete') {
subsubtasks.cases = subsubtasks[action.id].cases.filter((k, v) => v !== action.value); subsubtasks.cases = subsubtasks.cases.filter((k, v) => v !== action.value);
} }
subtasks[action.id] = subsubtasks; } else if (action.key === 'add') subtasks.push({ time: 1000, memory: 256, cases: [] });
return { ...state, subtasks }; else if (action.key === 'delete') delete subtasks[action.key];
else {
if (action.value === '' || (action.key === 'if' && action.value.join('') === '')) delete subsubtasks[action.key];
else subsubtasks[action.key] = action.value;
} }
const next = { ...state }; if (action.key !== 'delete') subtasks[action.id] = subsubtasks;
if (action.key === 'add') next.subtasks.splice(action.id, 0, { id: 0 }); return { ...state, subtasks };
else if (action.key === 'delete') delete next.subtasks[action.key];
else next.subtasks[action.id][action.key] = action.value;
return next;
} }
default: default:
return state; return state;
+14
View File
@@ -86,6 +86,7 @@ Attend Contest: 参加比赛
Attend contests: 参加比赛 Attend contests: 参加比赛
Attended: 已参加 Attended: 已参加
author: 作者 author: 作者
Auto Read Tasks: 自动识别任务
Be Copied: 被复制 Be Copied: 被复制
Begin at: 开始于 Begin at: 开始于
Begin Date: 开始日期 Begin Date: 开始日期
@@ -117,6 +118,8 @@ Change Email: 修改电子邮件
Change Password: 修改密码 Change Password: 修改密码
Change Theme: 切换主题 Change Theme: 切换主题
Check In: 签到 Check In: 签到
CheckerType: 比较器类型
Checker: 比较器
Chinese: 中文 Chinese: 中文
Choose the background image in your profile page.: 选择您资料页面的背景图片。 Choose the background image in your profile page.: 选择您资料页面的背景图片。
Claim homework: 认领作业 Claim homework: 认领作业
@@ -267,6 +270,7 @@ domain_user: 管理用户
domain: 域 domain: 域
Domain: 域 Domain: 域
Don't have an account?: 还没有账户? Don't have an account?: 还没有账户?
Don't ignore space and enter.: 不忽略行末回车与空格。
Done: 已结束 Done: 已结束
Download All: 下载全部 Download All: 下载全部
Download Dataset: 下载数据集 Download Dataset: 下载数据集
@@ -319,6 +323,7 @@ Export as CSV: 导出为 CSV
Export as HTML: 导出为 HTML Export as HTML: 导出为 HTML
Extension (days): 最长延期 (日) Extension (days): 最长延期 (日)
Extension Score Penalty: 延期递交扣分规则 Extension Score Penalty: 延期递交扣分规则
Extra Files Config: 额外文件设置
Failed to join the domain. You are already a member.: 加入域失败,您已是该域的成员。 Failed to join the domain. You are already a member.: 加入域失败,您已是该域的成员。
Feedback: 反馈 Feedback: 反馈
Field {0} or {1} validation failed.: 字段 {0} 或 {1} 验证失败。 Field {0} or {1} validation failed.: 字段 {0} 或 {1} 验证失败。
@@ -401,6 +406,7 @@ Information: 信息
Initialize: 初始化 Initialize: 初始化
Inline codes: 行内代码 Inline codes: 行内代码
Inline formulas: 行内公式 Inline formulas: 行内公式
Input: 输入
Insert Code: 插入代码 Insert Code: 插入代码
Insert Image: 插入图像 Insert Image: 插入图像
Installed Modules: 已安装的模块 Installed Modules: 已安装的模块
@@ -418,11 +424,14 @@ Join: 加入
Judge Config: 评测设置 Judge Config: 评测设置
Judge Status: 评测状态 Judge Status: 评测状态
judge_playground: 评测游乐场 judge_playground: 评测游乐场
judge_extra_files: 评测额外文件
Judged At: 评测时间 Judged At: 评测时间
Judged By: 评测机 Judged By: 评测机
Judging Queue: 评测队列 Judging Queue: 评测队列
Keep current expiration: 保持当前过期设置 Keep current expiration: 保持当前过期设置
langs: 语言
Language: 语言 Language: 语言
Langs Config: 语言设置
last active at: 最后活动于 last active at: 最后活动于
last login at: 最后登录于 last login at: 最后登录于
Last Submit At: 最后递交于 Last Submit At: 最后递交于
@@ -533,6 +542,7 @@ Or, with automatically filled invitation code: 或者,这是可以自动填写
Ordered List: 有序列表 Ordered List: 有序列表
Original Score: 原始分数 Original Score: 原始分数
OS Info: 系统信息 OS Info: 系统信息
Output: 输出
Over {0} problems: 超过 {0} 道题目 Over {0} problems: 超过 {0} 道题目
Owner: 所有者 Owner: 所有者
page.problem_detail.sidebar.show_category: 点击显示 page.problem_detail.sidebar.show_category: 点击显示
@@ -585,6 +595,7 @@ Problem Solution: 题解列表
Problem Tags Visibility: 题目标签可见性 Problem Tags Visibility: 题目标签可见性
Problem Tags: 题目标签 Problem Tags: 题目标签
problem_create: 创建题目 problem_create: 创建题目
problem_config: 评测设置
problem_edit: 编辑题目 problem_edit: 编辑题目
problem_import: 导入题目 problem_import: 导入题目
problem_main: 题库 problem_main: 题库
@@ -758,6 +769,7 @@ Support: 支持
Switch account: 切换账户 Switch account: 切换账户
Tags: 标签 Tags: 标签
tasks_list: 任务列表 tasks_list: 任务列表
Task Settings: 任务设置
Technical Information: 技术信息 Technical Information: 技术信息
Temporary session: 临时会话 Temporary session: 临时会话
Terms of Service: 服务条款 Terms of Service: 服务条款
@@ -832,12 +844,14 @@ Usage exceeded.: 用量超限。
Use admin specificed: 由管理员指定 Use admin specificed: 由管理员指定
Use algorithm calculated: 使用算法计算 Use algorithm calculated: 使用算法计算
Use average of above: 使用上面的平均值 Use average of above: 使用上面的平均值
Use Subtasks Mode: 使用子任务模式
User {0} already exists.: 用户 {0} 已存在。 User {0} already exists.: 用户 {0} 已存在。
User {0} not found.: 用户 {0} 不存在。 User {0} not found.: 用户 {0} 不存在。
User can join this domain by visiting the following URL: 用户可以访问此链接来加入此域 User can join this domain by visiting the following URL: 用户可以访问此链接来加入此域
User ID: 用户 ID User ID: 用户 ID
User will no longer be allowed to join the domain after expiration.: 过期后,用户将不能加入此域。 User will no longer be allowed to join the domain after expiration.: 过期后,用户将不能加入此域。
user_detail: 用户 user_detail: 用户
user_extra_files: 编译额外文件
user_login: 登录 user_login: 登录
user_logout: 登出 user_logout: 登出
user_lostpass: 忘记密码 user_lostpass: 忘记密码