@@ -5,16 +5,15 @@ import { oneDark } from '@codemirror/theme-one-dark';
55import styles from './styles.module.css' ;
66
77export default function InteractivePython ( { children } ) {
8- const initialCode = children . props . children . trim ( ) ;
8+ const initialCode = children ? .props ? .children ? .trim ( ) || '' ;
99 const [ code , setCode ] = useState ( initialCode ) ;
10- const [ isSkulptReady , setIsSkulptReady ] = useState ( false ) ;
1110 const [ isRunning , setIsRunning ] = useState ( false ) ;
1211
12+ const workerRef = useRef ( null ) ;
1313 const outputRef = useRef ( null ) ;
1414 const inputContainerRef = useRef ( null ) ;
1515 const inputPromptRef = useRef ( null ) ;
1616 const inputFieldRef = useRef ( null ) ;
17- const resolveInputRef = useRef ( null ) ;
1817
1918 const appendOutput = ( text ) => {
2019 if ( outputRef . current ) outputRef . current . textContent += text ;
@@ -42,90 +41,75 @@ export default function InteractivePython({ children }) {
4241 const prompt = inputPromptRef . current . textContent ;
4342 hideInput ( ) ;
4443 appendOutput ( prompt + value + "\n" ) ;
45- if ( resolveInputRef . current ) {
46- const resolve = resolveInputRef . current ;
47- resolveInputRef . current = null ;
48- resolve ( value ) ;
44+
45+ // Send the input back to the worker to resume execution
46+ if ( workerRef . current ) {
47+ workerRef . current . postMessage ( { type : 'INPUT_RESPONSE' , payload : value } ) ;
4948 }
5049 } ;
5150
51+ // Cleanup worker on component unmount
5252 useEffect ( ( ) => {
53- if ( inputContainerRef . current ) {
54- inputContainerRef . current . style . display = 'none' ;
55- }
53+ return ( ) => stopWorker ( ) ;
5654 } , [ ] ) ;
5755
58- useEffect ( ( ) => {
59- const loadScript = ( src ) => new Promise ( ( resolve , reject ) => {
60- const script = document . createElement ( 'script' ) ;
61- script . src = src ;
62- script . async = true ;
63- script . onload = resolve ;
64- script . onerror = reject ;
65- document . body . appendChild ( script ) ;
66- } ) ;
67-
68- async function initSkulpt ( ) {
69- try {
70- if ( ! window . Sk ) {
71- await loadScript ( "https://cdn.jsdelivr.net/npm/skulpt@1.2.0/dist/skulpt.min.js" ) ;
72- await loadScript ( "https://cdn.jsdelivr.net/npm/skulpt@1.2.0/dist/skulpt-stdlib.js" ) ;
73- }
74- setIsSkulptReady ( true ) ;
75- } catch ( err ) {
76- console . error ( "Failed to load Skulpt scripts" , err ) ;
77- }
56+ const stopWorker = ( ) => {
57+ if ( workerRef . current ) {
58+ workerRef . current . terminate ( ) ;
59+ workerRef . current = null ;
60+ setIsRunning ( false ) ;
61+ hideInput ( ) ;
62+ appendOutput ( "\n[Process Terminated]" ) ;
7863 }
79- initSkulpt ( ) ;
80- } , [ ] ) ;
64+ } ;
8165
8266 const runCode = ( ) => {
83- if ( ! isSkulptReady || ! window . Sk ) return ;
84-
8567 clearOutput ( ) ;
8668 hideInput ( ) ;
8769 setIsRunning ( true ) ;
88- resolveInputRef . current = null ;
89-
90- window . Sk . configure ( {
91- output : ( text ) => appendOutput ( text ) ,
92- read : ( x ) => {
93- if ( window . Sk . builtinFiles === undefined || window . Sk . builtinFiles [ "files" ] [ x ] === undefined )
94- throw "File not found: '" + x + "'" ;
95- return window . Sk . builtinFiles [ "files" ] [ x ] ;
96- } ,
97- inputfunTakesPrompt : true ,
98- execLimit : 10000 , // 10 sec
99- yieldLimit : 100 ,
100- inputfun : ( prompt ) => {
101- return new Promise ( ( resolve ) => {
102- resolveInputRef . current = resolve ;
103- showInput ( prompt ) ;
104- } ) ;
105- } ,
106- __future__ : window . Sk . python3
107- } ) ;
108-
109- window . Sk . misceval . asyncToPromise ( ( ) =>
110- window . Sk . importMainWithBody ( "<stdin>" , false , code , true )
111- ) . then (
112- ( ) => { setIsRunning ( false ) ; hideInput ( ) ; } ,
113- ( err ) => {
114- appendOutput ( "\n" + err . toString ( ) ) ;
115- setIsRunning ( false ) ;
116- hideInput ( ) ;
117- }
70+
71+ // Terminate any existing worker before starting a new one
72+ if ( workerRef . current ) {
73+ workerRef . current . terminate ( ) ;
74+ }
75+
76+ // Initialize the Web Worker
77+ workerRef . current = new Worker (
78+ new URL ( './skulpt.worker.js' , import . meta. url )
11879 ) ;
80+
81+ // Handle incoming messages from Skulpt
82+ workerRef . current . onmessage = ( e ) => {
83+ const { type, payload } = e . data ;
84+
85+ switch ( type ) {
86+ case 'OUTPUT' :
87+ appendOutput ( payload ) ;
88+ break ;
89+ case 'INPUT_PROMPT' :
90+ showInput ( payload ) ;
91+ break ;
92+ case 'FINISHED' :
93+ setIsRunning ( false ) ;
94+ appendOutput ( "\n[Program Finished]" ) ;
95+ break ;
96+ case 'ERROR' :
97+ setIsRunning ( false ) ;
98+ appendOutput ( "\n" + payload ) ;
99+ break ;
100+ default :
101+ break ;
102+ }
103+ } ;
104+
105+ // Send the code to the worker to start execution
106+ workerRef . current . postMessage ( { type : 'RUN_CODE' , payload : code } ) ;
119107 } ;
120108
121109 return (
122110 < div className = { styles . wrapper } >
123- { /* ── Header ── */ }
124111 < div className = { styles . codeHeader } >
125- < span className = { styles . codeHeaderText } > Python Ledger Editor</ span >
126- < span className = { `${ styles . codeHeaderStatus } ${ isSkulptReady ? styles . ready : '' } ` } >
127- { isSkulptReady ? '● READY' : '○ LOADING...' }
128- </ span >
112+ < span className = { styles . codeHeaderText } > Python Sandbox</ span >
129113 </ div >
130114
131115 < CodeMirror
@@ -135,19 +119,29 @@ export default function InteractivePython({ children }) {
135119 onChange = { ( value ) => setCode ( value ) }
136120 />
137121
138- { /* ── Run Button ── */ }
139- < button
140- className = { styles . runButton }
141- onClick = { runCode }
142- disabled = { ! isSkulptReady || isRunning }
143- >
144- { isRunning ? '⏳ Running...' : '▶ Execute Program' }
145- </ button >
122+ < div style = { { display : 'flex' , gap : '10px' , margin : '10px 0' } } >
123+ < button
124+ className = { styles . runButton }
125+ onClick = { runCode }
126+ disabled = { isRunning }
127+ >
128+ ▶ Execute Program
129+ </ button >
130+
131+ { /* The Kill Switch */ }
132+ < button
133+ className = { styles . runButton }
134+ onClick = { stopWorker }
135+ disabled = { ! isRunning }
136+ style = { { backgroundColor : isRunning ? '#d9534f' : '#555' } }
137+ >
138+ ■ Stop Execution
139+ </ button >
140+ </ div >
146141
147- { /* ── Console ── */ }
148142 < div className = { styles . console } >
149143 < pre ref = { outputRef } className = { styles . output } />
150- < div ref = { inputContainerRef } className = { styles . inputForm } >
144+ < div ref = { inputContainerRef } className = { styles . inputForm } style = { { display : 'none' } } >
151145 < span ref = { inputPromptRef } className = { styles . promptText } />
152146 < input
153147 ref = { inputFieldRef }
0 commit comments