1+ import React , { useState , useEffect , useRef } from 'react'
2+
3+ export default function LevelComponent ( ) {
4+ const [ code , setCode ] = useState ( "Enter code here!" )
5+ const [ output , setOutput ] = useState ( "" )
6+ const [ pyodideReady , setPyodideReady ] = useState ( false )
7+ const pyodideRef = useRef < any > ( null )
8+
9+ // Load Pyodide on mount
10+ useEffect ( ( ) => {
11+ const loadPyodide = async ( ) => {
12+ // @ts -ignore
13+ pyodideRef . current = await window . loadPyodide ( )
14+ setPyodideReady ( true )
15+ }
16+ loadPyodide ( )
17+ } , [ ] )
18+
19+ const runCode = async ( ) => {
20+ if ( ! pyodideRef . current ) return
21+
22+ // Capture stdout
23+ await pyodideRef . current . runPythonAsync ( `
24+ import sys
25+ import io
26+ sys.stdout = io.StringIO()
27+ ` )
28+
29+ try {
30+ await pyodideRef . current . runPythonAsync ( code )
31+ const result = await pyodideRef . current . runPythonAsync ( `sys.stdout.getvalue()` )
32+ setOutput ( result )
33+ } catch ( err : any ) {
34+ setOutput ( err . message )
35+ }
36+ }
37+
38+ const handleKeyDown = ( e : React . KeyboardEvent < HTMLTextAreaElement > ) => {
39+ if ( e . key === "Tab" ) {
40+ e . preventDefault ( )
41+ const el = e . target as HTMLTextAreaElement
42+ const { selectionStart, selectionEnd } = el
43+ const newValue = code . slice ( 0 , selectionStart ) + " " + code . slice ( selectionEnd )
44+ setCode ( newValue )
45+ requestAnimationFrame ( ( ) => {
46+ el . selectionStart = el . selectionEnd = selectionStart + 4
47+ } )
48+ }
49+ }
50+
51+ return (
52+ < div className = "w-screen h-screen grid grid-cols-2 font-mono bg-gray-100" >
53+
54+ { /* Left panel */ }
55+ < div className = "flex flex-col gap-3 p-4" >
56+ < div className = "flex gap-3" >
57+ < div className = "flex-1 bg-green-950 border-2 border-white rounded-2xl p-3 text-white" >
58+ < div className = "italic font-bold mb-2" > PYTHON CODE</ div >
59+ < pre className = "text-sm leading-relaxed m-0" >
60+ { `nums = [1,2,3]
61+ sum = 0
62+ for i in nums:
63+ sum += i
64+ print(sum)` }
65+ </ pre >
66+ </ div >
67+ < div className = "flex-1 bg-green-950 border-[3px] border-white rounded-2xl p-3 text-white" >
68+ < div className = "italic font-bold mb-2" > ALIEN CODE</ div >
69+ < pre className = "text-sm leading-relaxed m-0" >
70+ { `plz make nums [1,2,3] ty
71+ plz make sums 0 ty
72+ plz i go through nums ty
73+ plz add i to sum ty
74+ plz print sum ty` }
75+ </ pre >
76+ </ div >
77+ </ div >
78+ < div className = "bg-green-950 rounded-lg px-5 py-4 text-white text-sm" >
79+ GOAL: IN < strong className = "font-mono" > ALIEN CODE</ strong > , print the square of all numbers.
80+ </ div >
81+
82+ { /* Output */ }
83+ < div className = "bg-zinc-900 rounded-lg p-4 text-green-400 text-sm font-mono h-32 overflow-auto" >
84+ < div className = "text-zinc-500 mb-1" > Output:</ div >
85+ < pre > { output } </ pre >
86+ </ div >
87+
88+ </ div >
89+
90+ { /* Right panel */ }
91+ < div className = "flex flex-col gap-3 m-4" >
92+
93+ { /* Editor */ }
94+ < div className = "flex-1 bg-zinc-700 rounded-lg overflow-hidden" >
95+ < textarea
96+ value = { code }
97+ onChange = { ( e ) => setCode ( e . target . value ) }
98+ onKeyDown = { handleKeyDown }
99+ spellCheck = { false }
100+ className = "w-full h-full bg-transparent text-white text-sm font-mono p-4 resize-none outline-none"
101+ />
102+ </ div >
103+
104+ { /* Run button */ }
105+ < button
106+ onClick = { runCode }
107+ disabled = { ! pyodideReady }
108+ className = "bg-green-700 hover:bg-green-600 disabled:bg-zinc-500 text-white font-bold py-2 rounded-lg"
109+ >
110+ { pyodideReady ? "Run" : "Loading Python..." }
111+ </ button >
112+
113+ </ div >
114+
115+ </ div >
116+ )
117+ }
0 commit comments