Skip to content

Commit 8821dc3

Browse files
authored
Merge pull request #141 from ezcode-my/refactoring/code-history
Refactoring/code history
2 parents 082713b + 0e1f733 commit 8821dc3

File tree

7 files changed

+104
-13
lines changed

7 files changed

+104
-13
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,15 @@
2525
"@tanstack/react-query": "^5.80.7",
2626
"@tanstack/react-query-devtools": "^5.81.2",
2727
"@types/js-cookie": "^3.0.6",
28+
"@types/lodash.debounce": "^4.0.9",
2829
"@uiw/react-codemirror": "^4.23.14",
2930
"class-variance-authority": "^0.7.1",
3031
"clsx": "^2.1.1",
3132
"date-fns": "^4.1.0",
3233
"jest": "^30.0.5",
3334
"jest-environment-jsdom": "^30.0.5",
3435
"js-cookie": "^3.0.5",
36+
"lodash.debounce": "^4.0.8",
3537
"lucide-react": "^0.515.0",
3638
"next": "15.3.3",
3739
"next-auth": "^4.24.11",

pnpm-lock.yaml

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/entities/submitCode/submission/model/query/submitCode.query.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@ import { ProblemId } from '@/shared';
55
import { useQuery } from '@tanstack/react-query';
66
import { ISubmitPrepareData } from './submitCode.query.type';
77
import { useProblemWebSocketStoreActions } from '@/features/submitCode/submission/model/useProblemWebSocketStore';
8-
import Cookies from 'js-cookie';
98

109
/**코드 제출시 필요한 세션키, initcaseIds 를 리스폰스로 받음 */
1110

12-
export const useGetSubmitPrepareData = (problemId: ProblemId) => {
13-
const accessToken = Cookies.get('accessToken');
11+
export const useGetSubmitPrepareData = (problemId: ProblemId, hasAccessToken: boolean) => {
1412
const { setPrepareData } = useProblemWebSocketStoreActions();
1513

1614
const path = getProblemIdPath(problemId, 'submit-prepare');
@@ -31,6 +29,6 @@ export const useGetSubmitPrepareData = (problemId: ProblemId) => {
3129
return { sessionKey: null, testcaseIds: null };
3230
}
3331
},
34-
enabled: !!accessToken,
32+
enabled: hasAccessToken,
3533
});
3634
};

src/features/submitCode/submission/ui/CodeEditor.tsx

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,47 @@ import {
88
SOURCECODE,
99
} from '@/shared';
1010
import { Select } from '@/shared/ui/select/Select';
11+
import { useEffect } from 'react';
12+
import { useAutoSave } from '@/shared/util/saveLocalStorage';
13+
import { ISourceCode } from '@/entities/submitCode';
1114

1215
interface ICodeEditorProps {
1316
onChangeSourceCodeData: (key: 'sourceCode' | 'languageId', value: number | string) => void;
14-
languageId: number;
17+
sourceCodeData: ISourceCode;
18+
problemId: string;
1519
}
16-
export default function CodeEditor({ onChangeSourceCodeData, languageId }: ICodeEditorProps) {
17-
const handleChangeLanguage = (value: string) => {
18-
const languageId = Number(value); //select option required only string type value ㅠ
20+
export default function CodeEditor({
21+
onChangeSourceCodeData,
22+
sourceCodeData,
23+
problemId,
24+
}: ICodeEditorProps) {
25+
const { languageId } = sourceCodeData;
26+
27+
const debouncedSave = useAutoSave(2000);
1928

29+
const handleChangeLanguage = (value: string) => {
30+
const languageId = Number(value);
31+
debouncedSave(`sourceCodeData-${problemId}`, {
32+
sourceCode: SOURCECODE[languageId],
33+
languageId: languageId,
34+
});
2035
onChangeSourceCodeData('languageId', languageId);
2136
onChangeSourceCodeData('sourceCode', SOURCECODE[languageId]);
2237
};
2338

39+
const handleChangeCode = (value: string) => {
40+
onChangeSourceCodeData('sourceCode', value);
41+
};
42+
43+
useEffect(() => {
44+
if (sourceCodeData.sourceCode !== SOURCECODE[languageId]) {
45+
debouncedSave(`sourceCodeData-${problemId}`, {
46+
sourceCode: sourceCodeData.sourceCode,
47+
languageId: languageId,
48+
});
49+
}
50+
}, [sourceCodeData.sourceCode]);
51+
2452
return (
2553
<section className="flex-1 flex flex-col h-full gap-4">
2654
<Select
@@ -29,11 +57,12 @@ export default function CodeEditor({ onChangeSourceCodeData, languageId }: ICode
2957
option={LANGUAGE_SELECTOR_OPTIONS}
3058
setValue={(value) => handleChangeLanguage(value)}
3159
/>
60+
3261
<CodeMirror
3362
basicSetup={CodeMirrorBasicSetup}
34-
value={SOURCECODE[languageId]}
63+
value={sourceCodeData.sourceCode}
3564
theme={'dark'}
36-
onChange={(value) => onChangeSourceCodeData('sourceCode', value)}
65+
onChange={(value) => handleChangeCode(value)}
3766
extensions={[CODEMIRROR_EXTENSIONS[languageId]]}
3867
aria-autocomplete="none"
3968
autoCapitalize="off"

src/features/submitCode/submission/ui/ProblemWorksSection.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import TerminalOutput from './TerminalOutput';
55
import TerminalPanel from './TerminalPanel';
66
import { ISourceCode } from '@/entities/submitCode';
77
import { useUserStore } from '@/entities/user/model/store';
8-
import { fetchSourceCodeData, INITIAL_SOURCE_CODE_DATA } from '@/shared';
8+
import { fetchSourceCodeData, INITIAL_SOURCE_CODE_DATA, SOURCECODE } from '@/shared';
99

1010
interface IProblemWorksSectionProps {
1111
problemId: string;
@@ -23,16 +23,34 @@ export default function ProblemWorksSection({ problemId }: IProblemWorksSectionP
2323

2424
useEffect(() => {
2525
if (!user) return;
26+
const storedData = localStorage.getItem(`sourceCodeData-${problemId}`);
2627

2728
const fetchedSourceCodeData = fetchSourceCodeData(user?.language?.id as number);
29+
if (
30+
storedData &&
31+
JSON.parse(storedData).sourceCode !== SOURCECODE[JSON.parse(storedData).languageId]
32+
)
33+
return;
2834
setSourceCodeData(fetchedSourceCodeData);
2935
}, [user?.language, user]);
3036

37+
useEffect(() => {
38+
const storedData = localStorage.getItem(`sourceCodeData-${problemId}`);
39+
40+
setSourceCodeData(
41+
storedData ? (JSON.parse(storedData) as ISourceCode) : INITIAL_SOURCE_CODE_DATA
42+
);
43+
if (!storedData) {
44+
localStorage.setItem(`sourceCodeData-${problemId}`, JSON.stringify(sourceCodeData));
45+
}
46+
}, [problemId]);
47+
3148
return (
3249
<section className="flex flex-col gap-5 h-full">
3350
<CodeEditor
51+
problemId={problemId}
52+
sourceCodeData={sourceCodeData}
3453
onChangeSourceCodeData={handleChangeSourceCodeData}
35-
languageId={sourceCodeData.languageId}
3654
/>
3755
<div className="flex flex-1 flex-col bg-secondary-background rounded-[10px] shadow-lg ">
3856
<TerminalPanel

src/features/submitCode/submission/ui/TerminalPanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export default function TerminalPanel({
2929
const searchParams = useSearchParams();
3030
const accessToken = Cookies.get('accessToken');
3131

32-
useGetSubmitPrepareData(problemId);
32+
useGetSubmitPrepareData(problemId, !!accessToken);
3333
const { submitPrepareData } = useProblemWebSocketStore();
3434

3535
const authGuardTrigger = () => {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { useMemo } from 'react';
2+
import debounce from 'lodash.debounce';
3+
4+
export type AutoSaveStatus = 'idle' | 'saving' | 'saved' | 'error';
5+
type SetStatusType = (status: AutoSaveStatus) => void;
6+
7+
/**@param delay- debounce 딜레이 */
8+
/**로컬스토리지 자동 저장 훅 */
9+
export function useAutoSave(delay = 2000) {
10+
const debouncedSave = useMemo(
11+
() =>
12+
debounce((key: string, value: unknown, setStatus?: SetStatusType) => {
13+
try {
14+
setStatus?.('saving');
15+
const serializedValue = JSON.stringify(value);
16+
localStorage.setItem(key, serializedValue);
17+
setStatus?.('saved');
18+
} catch (err) {
19+
setStatus?.('error');
20+
}
21+
}, delay),
22+
[delay]
23+
);
24+
25+
return debouncedSave;
26+
}

0 commit comments

Comments
 (0)