ui: new judge config (#533)

Co-authored-by: panda <panda_dtdyy@outlook.com>
This commit is contained in:
undefinedandpanda authored and GitHub committed 2023-03-15 21:15:04 +08:00
1 parent f4d8cd7a5d
commit 85cb1d6dff
26 files changed
+910 -222

No files matched your search

@@ -1,11 +1,22 @@
import { parseMemoryMB, parseTimeMS } from '@hydrooj/utils/lib/common';
import type { ProblemConfigFile } from 'hydrooj/src/interface';
import { parseMemoryMB, parseTimeMS, sortFiles } from '@hydrooj/utils/lib/common';
import Ajv from 'ajv';
import type { ProblemConfigFile, TestCaseConfig } from 'hydrooj/src/interface';
import yaml from 'js-yaml';
import { cloneDeep } from 'lodash';
import schema from '../../monaco/schema/problemconfig';
type State = ProblemConfigFile & { __loaded: boolean };
type State = ProblemConfigFile & {
__loaded: boolean;
__valid: boolean;
__errors: string[];
__cases: TestCaseConfig[];
};
const ajv = new Ajv();
const validate = ajv.compile(schema);
export default function reducer(state = { type: 'default', __loaded: false } as State, action: any = {}): State {
export default function reducer(state = {
type: 'default', __loaded: false, __valid: true, __errors: [], __cases: [],
} as State, action: any = {}): State {
switch (action.type) {
case 'CONFIG_LOAD_FULFILLED': {
return { ...state, ...yaml.load(action.payload.config) as object, __loaded: true };
@@ -19,9 +30,15 @@ export default function reducer(state = { type: 'default', __loaded: false } as
}
case 'CONFIG_CODE_UPDATE': {
try {
return { ...state, ...yaml.load(action.payload) as object };
} catch {
return state;
const data = yaml.load(action.payload);
if (!validate(data)) {
return { ...state, __valid: false, __errors: validate.errors.map((i) => `${i.instancePath}: ${i.message}`) };
}
return {
...state, ...data as object, __valid: true, __errors: [],
};
} catch (e) {
return { ...state, __valid: false, __errors: [e.message] };
}
}
case 'CONFIG_AUTOCASES_UPDATE': {
@@ -42,27 +59,72 @@ export default function reducer(state = { type: 'default', __loaded: false } as
}
case 'CONFIG_SUBTASK_UPDATE': {
const subtasks = cloneDeep(state.subtasks);
const subsubtasks = cloneDeep(state.subtasks[action.id]);
const subtask = state.subtasks.find((i) => i.id === action.id);
if (action.value !== '' && ['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 === 'cases-add') subsubtasks.cases.push(action.value);
if (action.key === 'cases-add') subtask.cases.push(action.value);
else if (action.key === 'cases-edit') {
if (action.value === '' && !['input', 'output'].includes(action.casesKey)) delete subsubtasks.cases[action.casesId][action.casesKey];
else subsubtasks.cases[action.casesId][action.casesKey] = action.value;
if (action.value === '' && !['input', 'output'].includes(action.casesKey)) delete subtask.cases[action.casesId][action.casesKey];
else subtask.cases[action.casesId][action.casesKey] = action.value;
} else if (action.key === 'cases-delete') {
subsubtasks.cases = subsubtasks.cases.filter((k, v) => v !== action.value);
subtask.cases = subtask.cases.filter((k, v) => v !== action.value);
}
} else if (action.key === 'add') {
return { ...state, subtasks: [...subtasks, { time: state.time || '1s', memory: state.memory || '256m', cases: [] }] };
subtasks.push({
time: state.time || '1s',
memory: state.memory || '256m',
cases: [],
score: 0,
id: Object.keys(subtasks).map((i) => subtasks[i].id).reduce((a, b) => Math.max(+a, +b), 0) + 1,
});
return { ...state, subtasks };
} else if (action.key === 'delete') return { ...state, subtasks: subtasks.filter((k, v) => v !== action.id) };
else {
if (action.value === '' || (action.key === 'if' && action.value.join('') === '')) delete subsubtasks[action.key];
else subsubtasks[action.key] = action.value;
if (action.value === '' || (action.key === 'if' && action.value.join('') === '')) delete subtask[action.key];
else subtask[action.key] = action.value;
}
subtasks[action.id] = subsubtasks;
return { ...state, subtasks };
}
case 'problemconfig/updateGlobalConfig': {
const n = { ...state, time: action.time, memory: action.memory };
if (!n.time) delete n.time;
if (!n.memory) delete n.memory;
return n;
}
case 'problemconfig/updateSubtaskConfig': {
if (!state.subtasks.find((i) => i.id === action.id)) return state;
const subtask = { ...state.subtasks.find((i) => i.id === action.id) };
const subtasks = [...state.subtasks];
subtasks.splice(state.subtasks.findIndex((i) => i.id === action.id), 1, subtask);
subtask.time = action.time;
subtask.memory = action.memory;
subtask.score = +action.score || 0;
if (!subtask.time) delete subtask.time;
if (!subtask.memory) delete subtask.memory;
return { ...state, subtasks };
}
case 'problemconfig/addTestcases': {
return { ...state, __cases: sortFiles([...state.__cases, ...action.cases], 'input') };
}
case 'problemconfig/moveTestcases': {
const testcases = action.payload.cases;
const subtasks = cloneDeep(state.subtasks);
const __cases = action.payload.source === -1
? state.__cases.filter((i) => !testcases.find((j) => i.input === j.input && i.output === j.output))
: action.payload.target === -1
? sortFiles([...state.__cases, ...testcases], 'input')
: state.__cases;
for (const key in subtasks) {
const subtask = subtasks[key];
if (subtask.id === action.payload.source) {
subtask.cases = subtask.cases.filter((i) => !testcases.find((j) => i.input === j.input && i.output === j.output));
} else if (subtask.id === action.payload.target) {
subtask.cases = sortFiles([...subtask.cases, ...testcases], 'input');
}
}
return { ...state, subtasks, __cases };
}
default:
return state;
}