33import styles from "./CodeEditor.module.css" ;
44import ctx from "classnames" ;
55import { GeistMono } from "geist/font/mono" ;
6- import Editor from "@monaco-editor/react" ;
6+ import Editor , { Monaco } from "@monaco-editor/react" ;
77import { Flex , useColorMode } from "@chakra-ui/react" ;
88import { useEffect , useState , useRef } from "react" ;
99import MyBtn from "../MyBtn" ;
10- import { CodeFile , OutputResult } from "@/lib/types" ;
11- import { OutputReducerAction } from "@/lib/reducers" ;
1210import { tryFormattingCode , validateCode } from "@/lib/client-functions" ;
1311import FiChevronRight from "@/app/styles/icons/HiChevronRightGreen" ;
1412import { useRouter } from "next/navigation" ;
1513import { useUserSolutionStore , useEditorStore } from "@/lib/stores" ;
1614import { sendGAEvent } from "@next/third-parties/google" ;
15+ import { CodeFile , OutputResult } from "@/lib/types" ;
16+ import { OutputReducerAction } from "@/lib/reducers" ;
1717
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" ) => {
4420 useEffect ( ( ) => {
4521 if ( monaco ) {
4622 monaco . editor . defineTheme ( "my-theme" , {
@@ -54,31 +30,42 @@ export default function CodeEditor({
5430 monaco . editor . setTheme ( colorMode === "light" ? "light" : "my-theme" ) ;
5531 }
5632 } , [ monaco , colorMode ] ) ;
33+ } ;
34+
35+ // Custom hook for keyboard shortcuts
36+ const useValidationShortcut = (
37+ handleValidate : ( ) => void ,
38+ codeString : string ,
39+ ) => {
5740 useEffect ( ( ) => {
5841 const handleKeyDown = ( event : KeyboardEvent ) => {
59- if ( event . key == "Enter" && event . shiftKey ) {
42+ if ( event . key === "Enter" && event . shiftKey ) {
6043 sendGAEvent ( "event" , "buttonClicked" , {
6144 value : "Validate (through shortcut)" ,
6245 } ) ;
6346 event . preventDefault ( ) ;
64- tryFormattingCode ( editorRef , setCodeString ) ;
65- validateCode (
66- codeString ,
67- codeFile ,
68- dispatchOutput ,
69- stepIndex ,
70- chapterIndex ,
71- ) ;
47+ handleValidate ( ) ;
7248 }
7349 } ;
7450
7551 document . addEventListener ( "keydown" , handleKeyDown ) ;
76-
7752 return ( ) => {
7853 document . removeEventListener ( "keydown" , handleKeyDown ) ;
7954 } ;
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 ( ) ;
8167
68+ // Load saved code
8269 useEffect ( ( ) => {
8370 const savedCode = userSolutionStore . getSavedUserSolutionByLesson (
8471 chapterIndex ,
@@ -89,6 +76,7 @@ export default function CodeEditor({
8976 }
9077 } , [ chapterIndex , stepIndex ] ) ;
9178
79+ // Save code changes
9280 useEffect ( ( ) => {
9381 userSolutionStore . saveUserSolutionForLesson (
9482 chapterIndex ,
@@ -97,11 +85,135 @@ export default function CodeEditor({
9785 ) ;
9886 } , [ codeString , chapterIndex , stepIndex ] ) ;
9987
88+ // Initialize code if no saved solutions
10089 useEffect ( ( ) => {
101- if ( Object . keys ( userSolutionStore . userSolutionsByLesson ) . length == 0 ) {
90+ if ( Object . keys ( userSolutionStore . userSolutionsByLesson ) . length === 0 ) {
10291 setCodeString ( JSON . stringify ( codeFile . code , null , 2 ) ) ;
10392 }
10493 } , [ 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+ } ;
105217
106218 return (
107219 < >
@@ -111,74 +223,24 @@ export default function CodeEditor({
111223 defaultValue = { codeString }
112224 theme = { colorMode === "light" ? "light" : "my-theme" }
113225 value = { codeString }
114- height = { "100%" }
226+ height = "100%"
115227 onChange = { ( codeString ) => setCodeString ( codeString ?? "" ) }
116228 options = { {
117229 minimap : { enabled : false } ,
118-
119230 fontSize : 14 ,
120231 formatOnPaste : true ,
121232 formatOnType : true ,
122233 } }
123- onMount = { ( editor , monaco ) => {
124- setMonaco ( monaco ) ;
125- editorRef . current = editor ;
126- editorStore . setEditor ( editor ) ;
127- editorStore . setMonaco ( monaco ) ;
128- } }
234+ onMount = { handleEditorMount }
129235 />
130236 </ 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+ />
182244 </ >
183245 ) ;
184246}
0 commit comments