mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
ui: add configYamlFormat & fix bugs
This commit is contained in:
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;
|
||||||
|
|||||||
@@ -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: 忘记密码
|
||||||
|
|||||||
Reference in new issue
Block a user