3
3
import styles from "./CodeEditor.module.css" ;
4
4
import ctx from "classnames" ;
5
5
import { GeistMono } from "geist/font/mono" ;
6
- import Editor from "@monaco-editor/react" ;
6
+ import Editor , { Monaco } from "@monaco-editor/react" ;
7
7
import { Flex , useColorMode } from "@chakra-ui/react" ;
8
8
import { useEffect , useState , useRef } from "react" ;
9
9
import MyBtn from "../MyBtn" ;
10
- import { CodeFile , OutputResult } from "@/lib/types" ;
11
- import { OutputReducerAction } from "@/lib/reducers" ;
12
10
import { tryFormattingCode , validateCode } from "@/lib/client-functions" ;
13
11
import FiChevronRight from "@/app/styles/icons/HiChevronRightGreen" ;
14
12
import { useRouter } from "next/navigation" ;
15
13
import { useUserSolutionStore , useEditorStore } from "@/lib/stores" ;
16
14
import { sendGAEvent } from "@next/third-parties/google" ;
15
+ import { CodeFile , OutputResult } from "@/lib/types" ;
16
+ import { OutputReducerAction } from "@/lib/reducers" ;
17
17
18
- export default function CodeEditor ( {
19
- codeString,
20
- setCodeString,
21
- codeFile,
22
- dispatchOutput,
23
- nextStepPath,
24
- stepIndex,
25
- chapterIndex,
26
- outputResult,
27
- } : {
28
- codeString : string ;
29
- setCodeString : ( codeString : string ) => void ;
30
- codeFile : CodeFile ;
31
- dispatchOutput : React . Dispatch < OutputReducerAction > ;
32
- nextStepPath : string | undefined ;
33
- stepIndex : number ;
34
- chapterIndex : number ;
35
- outputResult : OutputResult ;
36
- } ) {
37
- const { colorMode } = useColorMode ( ) ;
38
- const [ monaco , setMonaco ] = useState < any > ( null ) ;
39
- const router = useRouter ( ) ;
40
- const editorStore = useEditorStore ( ) ;
41
- const userSolutionStore = useUserSolutionStore ( ) ;
42
- const editorRef = useRef < any > ( null ) ;
43
-
18
+ // Custom hook for editor theme setup
19
+ const useEditorTheme = ( monaco : Monaco , colorMode : "dark" | "light" ) => {
44
20
useEffect ( ( ) => {
45
21
if ( monaco ) {
46
22
monaco . editor . defineTheme ( "my-theme" , {
@@ -54,31 +30,42 @@ export default function CodeEditor({
54
30
monaco . editor . setTheme ( colorMode === "light" ? "light" : "my-theme" ) ;
55
31
}
56
32
} , [ monaco , colorMode ] ) ;
33
+ } ;
34
+
35
+ // Custom hook for keyboard shortcuts
36
+ const useValidationShortcut = (
37
+ handleValidate : ( ) => void ,
38
+ codeString : string ,
39
+ ) => {
57
40
useEffect ( ( ) => {
58
41
const handleKeyDown = ( event : KeyboardEvent ) => {
59
- if ( event . key == "Enter" && event . shiftKey ) {
42
+ if ( event . key === "Enter" && event . shiftKey ) {
60
43
sendGAEvent ( "event" , "buttonClicked" , {
61
44
value : "Validate (through shortcut)" ,
62
45
} ) ;
63
46
event . preventDefault ( ) ;
64
- tryFormattingCode ( editorRef , setCodeString ) ;
65
- validateCode (
66
- codeString ,
67
- codeFile ,
68
- dispatchOutput ,
69
- stepIndex ,
70
- chapterIndex ,
71
- ) ;
47
+ handleValidate ( ) ;
72
48
}
73
49
} ;
74
50
75
51
document . addEventListener ( "keydown" , handleKeyDown ) ;
76
-
77
52
return ( ) => {
78
53
document . removeEventListener ( "keydown" , handleKeyDown ) ;
79
54
} ;
80
- } , [ codeString ] ) ;
55
+ } , [ handleValidate , codeString ] ) ;
56
+ } ;
57
+
58
+ // Custom hook for code persistence
59
+ const useCodePersistence = (
60
+ chapterIndex : number ,
61
+ stepIndex : number ,
62
+ codeString : string ,
63
+ setCodeString : ( value : string ) => void ,
64
+ codeFile : CodeFile ,
65
+ ) => {
66
+ const userSolutionStore = useUserSolutionStore ( ) ;
81
67
68
+ // Load saved code
82
69
useEffect ( ( ) => {
83
70
const savedCode = userSolutionStore . getSavedUserSolutionByLesson (
84
71
chapterIndex ,
@@ -89,6 +76,7 @@ export default function CodeEditor({
89
76
}
90
77
} , [ chapterIndex , stepIndex ] ) ;
91
78
79
+ // Save code changes
92
80
useEffect ( ( ) => {
93
81
userSolutionStore . saveUserSolutionForLesson (
94
82
chapterIndex ,
@@ -97,11 +85,135 @@ export default function CodeEditor({
97
85
) ;
98
86
} , [ codeString , chapterIndex , stepIndex ] ) ;
99
87
88
+ // Initialize code if no saved solutions
100
89
useEffect ( ( ) => {
101
- if ( Object . keys ( userSolutionStore . userSolutionsByLesson ) . length == 0 ) {
90
+ if ( Object . keys ( userSolutionStore . userSolutionsByLesson ) . length === 0 ) {
102
91
setCodeString ( JSON . stringify ( codeFile . code , null , 2 ) ) ;
103
92
}
104
93
} , [ userSolutionStore ] ) ;
94
+ } ;
95
+
96
+ // EditorControls component for the buttons section
97
+ const EditorControls = ( {
98
+ handleValidate,
99
+ isValidating,
100
+ resetCode,
101
+ nextStepPath,
102
+ outputResult,
103
+ } : {
104
+ handleValidate : ( ) => void ;
105
+ isValidating : boolean ;
106
+ resetCode : ( ) => void ;
107
+ nextStepPath : string | undefined ;
108
+ outputResult : OutputResult ;
109
+ } ) => {
110
+ const router = useRouter ( ) ;
111
+
112
+ return (
113
+ < div className = { styles . buttonsWrapper } >
114
+ < Flex dir = "row" gap = "8px" alignItems = "end" >
115
+ < MyBtn
116
+ onClick = { handleValidate }
117
+ variant = {
118
+ outputResult . validityStatus === "valid" ? "success" : "default"
119
+ }
120
+ isDisabled = { isValidating }
121
+ tooltip = "Shift + Enter"
122
+ >
123
+ { isValidating ? "Validating ..." : "Validate" }
124
+ </ MyBtn >
125
+
126
+ < MyBtn onClick = { resetCode } variant = "error" >
127
+ Reset
128
+ </ MyBtn >
129
+ </ Flex >
130
+ < MyBtn
131
+ onClick = { ( ) => {
132
+ if ( nextStepPath ) router . push ( "/" + nextStepPath ) ;
133
+ } }
134
+ variant = {
135
+ outputResult . validityStatus === "valid" ? "default" : "success"
136
+ }
137
+ isDisabled = { ! nextStepPath }
138
+ size = { outputResult . validityStatus === "valid" ? "sm" : "xs" }
139
+ >
140
+ Next < span style = { { marginLeft : "4px" } } > </ span >
141
+ < FiChevronRight
142
+ color = {
143
+ outputResult . validityStatus === "valid"
144
+ ? "white"
145
+ : "hsl(var(--success))"
146
+ }
147
+ />
148
+ </ MyBtn >
149
+ </ div >
150
+ ) ;
151
+ } ;
152
+
153
+ export default function CodeEditor ( {
154
+ codeString,
155
+ setCodeString,
156
+ codeFile,
157
+ dispatchOutput,
158
+ nextStepPath,
159
+ stepIndex,
160
+ chapterIndex,
161
+ outputResult,
162
+ } : {
163
+ codeString : string ;
164
+ setCodeString : ( codeString : string ) => void ;
165
+ codeFile : CodeFile ;
166
+ dispatchOutput : React . Dispatch < OutputReducerAction > ;
167
+ nextStepPath : string | undefined ;
168
+ stepIndex : number ;
169
+ chapterIndex : number ;
170
+ outputResult : OutputResult ;
171
+ } ) {
172
+ const { colorMode } = useColorMode ( ) ;
173
+ const [ monaco , setMonaco ] = useState < any > ( null ) ;
174
+ const [ isValidating , setIsValidating ] = useState ( false ) ;
175
+ const editorStore = useEditorStore ( ) ;
176
+ const editorRef = useRef < any > ( null ) ;
177
+
178
+ // Apply custom hooks
179
+ useEditorTheme ( monaco , colorMode ) ;
180
+
181
+ const handleValidate = ( ) => {
182
+ setIsValidating ( true ) ;
183
+ setTimeout ( ( ) => {
184
+ tryFormattingCode ( editorRef , setCodeString ) ;
185
+ validateCode (
186
+ codeString ,
187
+ codeFile ,
188
+ dispatchOutput ,
189
+ stepIndex ,
190
+ chapterIndex ,
191
+ ) ;
192
+ setIsValidating ( false ) ;
193
+ } , 500 ) ;
194
+ } ;
195
+
196
+ useValidationShortcut ( handleValidate , codeString ) ;
197
+ useCodePersistence (
198
+ chapterIndex ,
199
+ stepIndex ,
200
+ codeString ,
201
+ setCodeString ,
202
+ codeFile ,
203
+ ) ;
204
+
205
+ const resetCode = ( ) => {
206
+ setCodeString ( JSON . stringify ( codeFile . code , null , 2 ) ) ;
207
+ dispatchOutput ( { type : "RESET" } ) ;
208
+ } ;
209
+
210
+ const handleEditorMount = ( editor : any , monaco : Monaco ) => {
211
+ setMonaco ( monaco ) ;
212
+
213
+ editorRef . current = editor ;
214
+ editorStore . setEditor ( editor ) ;
215
+ editorStore . setMonaco ( monaco ) ;
216
+ } ;
105
217
106
218
return (
107
219
< >
@@ -111,74 +223,24 @@ export default function CodeEditor({
111
223
defaultValue = { codeString }
112
224
theme = { colorMode === "light" ? "light" : "my-theme" }
113
225
value = { codeString }
114
- height = { "100%" }
226
+ height = "100%"
115
227
onChange = { ( codeString ) => setCodeString ( codeString ?? "" ) }
116
228
options = { {
117
229
minimap : { enabled : false } ,
118
-
119
230
fontSize : 14 ,
120
231
formatOnPaste : true ,
121
232
formatOnType : true ,
122
233
} }
123
- onMount = { ( editor , monaco ) => {
124
- setMonaco ( monaco ) ;
125
- editorRef . current = editor ;
126
- editorStore . setEditor ( editor ) ;
127
- editorStore . setMonaco ( monaco ) ;
128
- } }
234
+ onMount = { handleEditorMount }
129
235
/>
130
236
</ div >
131
- < div className = { styles . buttonsWrapper } >
132
- < Flex dir = "row" gap = { "8px" } alignItems = { "end" } >
133
- < MyBtn
134
- onClick = { async ( ) => {
135
- tryFormattingCode ( editorRef , setCodeString ) ;
136
- validateCode (
137
- codeString ,
138
- codeFile ,
139
- dispatchOutput ,
140
- stepIndex ,
141
- chapterIndex ,
142
- ) ;
143
- } }
144
- variant = {
145
- outputResult . validityStatus === "valid" ? "success" : "default"
146
- }
147
- tooltip = "Shift + Enter"
148
- >
149
- Validate
150
- </ MyBtn >
151
-
152
- < MyBtn
153
- onClick = { ( ) => {
154
- setCodeString ( JSON . stringify ( codeFile . code , null , 2 ) ) ;
155
- dispatchOutput ( { type : "RESET" } ) ;
156
- } }
157
- variant = { "error" }
158
- >
159
- Reset
160
- </ MyBtn >
161
- </ Flex >
162
- < MyBtn
163
- onClick = { ( ) => {
164
- if ( nextStepPath ) router . push ( "/" + nextStepPath ) ;
165
- } }
166
- variant = {
167
- outputResult . validityStatus === "valid" ? "default" : "success"
168
- }
169
- isDisabled = { ! nextStepPath }
170
- size = { outputResult . validityStatus === "valid" ? "sm" : "xs" }
171
- >
172
- Next < span style = { { marginLeft : "4px" } } > </ span >
173
- < FiChevronRight
174
- color = {
175
- outputResult . validityStatus === "valid"
176
- ? "white"
177
- : "hsl(var(--success))"
178
- }
179
- />
180
- </ MyBtn >
181
- </ div >
237
+ < EditorControls
238
+ handleValidate = { handleValidate }
239
+ isValidating = { isValidating }
240
+ resetCode = { resetCode }
241
+ nextStepPath = { nextStepPath }
242
+ outputResult = { outputResult }
243
+ />
182
244
</ >
183
245
) ;
184
246
}
0 commit comments