Skip to content

Commit

Permalink
feat: support cases add and delete (#22)
Browse files Browse the repository at this point in the history
* fix: markdown code style

* feat: support cases add and delete

* feat: add reset cases button
  • Loading branch information
ZLY201 committed Nov 4, 2023
1 parent a04a5e0 commit 708bd47
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 63 deletions.
20 changes: 20 additions & 0 deletions config/i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,5 +114,25 @@
"request_error": {
"en": "Network error, please check your network or refresh the page",
"zhCN": "网络请求失败,请检查网络状况后刷新重试"
},
"confirm_title": {
"en": "Confirm",
"zhCN": "确认"
},
"confirm_btn": {
"en": "confirm",
"zhCN": "确认"
},
"cancel_btn": {
"en": "cancel",
"zhCN": "取消"
},
"confirm_reset_code": {
"en": "Are you sure to reset code?",
"zhCN": "确认重置代码?"
},
"confirm_reset_cases": {
"en": "Are you sure to reset cases?",
"zhCN": "确认重置示例?"
}
}
2 changes: 1 addition & 1 deletion src/components/Markdown/index.module.less
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ blockquote {
margin-bottom: 4px;
}
}
code {
p > code {
margin: 0 2px;
padding: 2px 4px;
vertical-align: middle;
Expand Down
14 changes: 8 additions & 6 deletions src/modules/Editor/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Modal, Skeleton } from '@arco-design/web-react';
import { Modal, Skeleton, Tooltip } from '@arco-design/web-react';
import { IconCode, IconUndo } from '@arco-design/web-react/icon';
import debounce from 'lodash.debounce';
import { useCallback, useContext, useEffect, useState } from 'react';
Expand Down Expand Up @@ -44,10 +44,10 @@ function Editor() {

function resetCode() {
const modal = Modal.confirm({
title: 'Confirm',
content: 'Are you sure to reset code?',
okText: 'confirm',
cancelText: 'cancel',
title: i18nJson['confirm_title'][setting.language],
content: i18nJson['confirm_reset_code'][setting.language],
okText: i18nJson['confirm_btn'][setting.language],
cancelText: i18nJson['cancel_btn'][setting.language],
onOk: async function () {
setLoading(true);
localCache.setProblemCache(currentProblem.key, {
Expand Down Expand Up @@ -75,7 +75,9 @@ function Editor() {
{i18nJson['code'][setting.language]}
</span>
<a onClick={resetCode} className={styles.reset}>
<IconUndo />
<Tooltip mini={true} content={'reset'}>
<IconUndo />
</Tooltip>
</a>
</div>
<Skeleton
Expand Down
222 changes: 166 additions & 56 deletions src/modules/Results/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import type { editor } from 'monaco-editor';
import dayjs from 'dayjs';
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { Button, Input, Skeleton } from '@arco-design/web-react';
import {
Button,
Input,
Modal,
Skeleton,
TabsProps,
Tooltip,
} from '@arco-design/web-react';
import debounce from 'lodash.debounce';
import { IconUndo } from '@arco-design/web-react/icon';
import { CustomTabs } from '@src/components/CustomTabs';
import localCache, { PROBLEM_STATUS } from '@src/utils/local-cache';
import emitter from '@src/utils/emit';
Expand All @@ -20,6 +28,7 @@ import {
monacoInstance,
validateMonacoModel,
} from '@src/utils/monaco';
import { Setting } from '@src/utils/setting';
import styles from './index.module.less';

const enum MainTab {
Expand All @@ -35,8 +44,65 @@ function formatErrorFromMarkers(markers: editor.IMarker[]) {
});
}

function createResultError(status: string[]) {
return (
<div className={styles['result-errors']}>
<div className={styles['result-error-title']}>Compilation Error</div>
<div className={styles['result-error-info']}>
{status.map(function (error) {
return (
<div key={error} className={styles['result-error-item']}>
{error}
</div>
);
})}
</div>
</div>
);
}

function createCasesError(
cases: NonNullable<Problem['cases']>,
casesErrors: string[][],
language: Setting['language'],
) {
return (
<CustomTabs className={styles['case-tabs']}>
{cases.map(function (_, index) {
const result = casesErrors[index];
return (
<CustomTabs.TabPane
key={index}
title={`${i18nJson['case'][language]} ${index + 1}`}
>
{result.length > 0 && (
<div className={styles['result-error-info']}>
{result.map(function (error) {
return (
<div key={error} className={styles['result-error-item']}>
{error}
</div>
);
})}
</div>
)}
{result.length === 0 && (
<div className={styles['result-pass']}>Pass!</div>
)}
</CustomTabs.TabPane>
);
})}
</CustomTabs>
);
}

const Results = function () {
const [{ currentProblem, setting }] = useContext(Context);
const [
{
currentProblem,
setting: { language },
},
] = useContext(Context);
const [loading, setLoading] = useState(true);
const [activeMainTab, setActiveMainTab] = useState<string>(MainTab.cases);
const [status, setStatus] = useState<string[] | 'Accept!'>([]);
Expand All @@ -46,6 +112,7 @@ const Results = function () {
const [testRaw, setTestRaw] = useState<string | undefined>(undefined);
const [casesErrors, setCasesErrors] = useState<string[][]>([]);
const [btnDisabled, setBtnDisabled] = useState(true);
const [activeCase, setActiveCase] = useState<string>('0');

const monacoEditorStatusListener = useCallback(
() => setBtnDisabled(false),
Expand All @@ -58,6 +125,7 @@ const Results = function () {
setTestRaw(raw);
setStatus([]);
setCasesErrors([]);
setActiveCase('0');
setCases(problem.cases || [NULL_CASE]);
setActiveMainTab(MainTab.cases);
setLoading(false);
Expand Down Expand Up @@ -170,65 +238,93 @@ const Results = function () {
if (typeof status === 'string') {
return <div className={styles['result-accept']}>Accepted!</div>;
} else if (Array.isArray(status) && status.length > 0) {
return (
<div className={styles['result-errors']}>
<div className={styles['result-error-title']}>
Compilation Error
</div>
<div className={styles['result-error-info']}>
{status.map(function (error) {
return (
<div key={error} className={styles['result-error-item']}>
{error}
</div>
);
})}
</div>
</div>
);
return createResultError(status);
} else if (casesErrors.length > 0) {
return (
<CustomTabs className={styles['case-tabs']}>
{cases.map(function (_, index) {
const result = casesErrors[index];
return (
<CustomTabs.TabPane
key={index}
title={`${i18nJson['case'][setting.language]} ${index + 1}`}
>
{result.length > 0 && (
<div className={styles['result-error-info']}>
{result.map(function (error) {
return (
<div
key={error}
className={styles['result-error-item']}
>
{error}
</div>
);
})}
</div>
)}
{result.length === 0 && (
<div className={styles['result-pass']}>Pass!</div>
)}
</CustomTabs.TabPane>
);
})}
</CustomTabs>
);
return createCasesError(cases, casesErrors, language);
} else {
return (
<div className={styles['result-empty']}>
{i18nJson['please_run_or_submit_first'][setting.language]}
{i18nJson['please_run_or_submit_first'][language]}
</div>
);
}
},
[cases, casesErrors, status],
);

function onAddCase() {
if (cases.length >= 5) return;
setActiveCase(String(cases.length));
setCases([...cases, NULL_CASE]);
}

function onDeleteCase(key: string) {
if (cases.length <= 1) return;
if (String(cases.length - 1) === activeCase) {
setActiveCase(String(cases.length - 2));
}
setCases(cases.filter((_, index) => String(index) !== key));
}

function onChangeCase(
key: number,
newCase: Partial<NonNullable<Problem['cases']>[number]>,
) {
setCases(
cases.map(function (originCase, index) {
if (key === index) {
return {
...originCase,
...newCase,
};
}
return originCase;
}),
);
}

function resetCases() {
const modal = Modal.confirm({
title: i18nJson['confirm_title'][language],
content: i18nJson['confirm_reset_cases'][language],
okText: i18nJson['confirm_btn'][language],
cancelText: i18nJson['cancel_btn'][language],
onOk: async function () {
setActiveCase('0');
setCases(originCases.length == 0 ? [NULL_CASE] : originCases);
modal.close();
},
});
}

const renderTabHeader: TabsProps['renderTabHeader'] = function (
tabProps,
DefaultTabHeader,
) {
if (noCases) {
return <DefaultTabHeader {...tabProps} />;
}
return (
<div
style={{
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<div style={{ flex: 1 }}>
<DefaultTabHeader {...tabProps} />
</div>
<a onClick={resetCases}>
<Tooltip mini={true} content={'reset'}>
<IconUndo />
</Tooltip>
</a>
</div>
);
};

return (
<Skeleton
loading={loading}
Expand All @@ -244,28 +340,42 @@ const Results = function () {
>
<CustomTabs.TabPane
key={MainTab.cases}
title={i18nJson[MainTab.cases][setting.language]}
title={i18nJson[MainTab.cases][language]}
>
<CustomTabs className={styles['case-tabs']}>
<CustomTabs
editable={!noCases}
onAddTab={onAddCase}
onDeleteTab={onDeleteCase}
activeTab={activeCase}
onChange={setActiveCase}
className={styles['case-tabs']}
renderTabHeader={renderTabHeader}
>
{cases.map(function ({ source, target }, index) {
return (
<CustomTabs.TabPane
key={index}
title={`${i18nJson['case'][setting.language]} ${index + 1}`}
title={`${i18nJson['case'][language]} ${index + 1}`}
>
<div className={styles['case-header']}>Source</div>
<Input.TextArea
value={source}
autoSize={true}
readOnly={noCases}
className={styles['case-input']}
onChange={newSource =>
onChangeCase(index, { source: newSource })
}
/>
<div className={styles['case-header']}>Target</div>
<Input.TextArea
value={target}
autoSize={true}
readOnly={noCases}
className={styles['case-input']}
onChange={newTarget =>
onChangeCase(index, { target: newTarget })
}
/>
</CustomTabs.TabPane>
);
Expand All @@ -274,7 +384,7 @@ const Results = function () {
</CustomTabs.TabPane>
<CustomTabs.TabPane
key={MainTab.result}
title={i18nJson[MainTab.result][setting.language]}
title={i18nJson[MainTab.result][language]}
>
{resultContent}
</CustomTabs.TabPane>
Expand All @@ -286,7 +396,7 @@ const Results = function () {
disabled={btnDisabled}
className={styles.btn}
>
{i18nJson['run_code'][setting.language]}
{i18nJson['run_code'][language]}
</Button>
<Button
type={'primary'}
Expand All @@ -295,7 +405,7 @@ const Results = function () {
disabled={btnDisabled}
className={styles.btn}
>
{i18nJson['submit_code'][setting.language]}
{i18nJson['submit_code'][language]}
</Button>
</div>
</div>
Expand Down

0 comments on commit 708bd47

Please sign in to comment.