@@ -6,9 +6,150 @@ const state = {
66 treeData : { } ,
77 currentView : 'loading' ,
88 currentPath : [ ] ,
9- currentItem : null
9+ currentItem : null ,
10+ pyodide : null , // NOUVEAU
11+ pyodideLoading : false , // NOUVEAU
12+ originalCode : '' // NOUVEAU
1013} ;
1114
15+ // NOUVEAU: Pyodide Management
16+ async function loadPyodide ( ) {
17+ if ( state . pyodide ) return state . pyodide ;
18+ if ( state . pyodideLoading ) {
19+ while ( state . pyodideLoading ) {
20+ await new Promise ( resolve => setTimeout ( resolve , 100 ) ) ;
21+ }
22+ return state . pyodide ;
23+ }
24+
25+ state . pyodideLoading = true ;
26+ document . getElementById ( 'pyodide-loading' ) . style . display = 'flex' ;
27+
28+ try {
29+ state . pyodide = await loadPyodide ( ) ;
30+ await state . pyodide . loadPackage ( [ 'micropip' ] ) ;
31+ console . log ( 'Pyodide loaded successfully' ) ;
32+ document . getElementById ( 'pyodide-loading' ) . style . display = 'none' ;
33+ state . pyodideLoading = false ;
34+ return state . pyodide ;
35+ } catch ( err ) {
36+ console . error ( 'Failed to load Pyodide:' , err ) ;
37+ document . getElementById ( 'pyodide-loading' ) . innerHTML = '<span style="color: red;">Failed to load Python. Please refresh.</span>' ;
38+ state . pyodideLoading = false ;
39+ return null ;
40+ }
41+ }
42+
43+ // NOUVEAU: Open Interactive Editor
44+ function openEditor ( pythonCode , problemTitle ) {
45+ state . originalCode = pythonCode ;
46+ const modal = document . getElementById ( 'code-editor-modal' ) ;
47+ const editor = document . getElementById ( 'code-editor' ) ;
48+ const title = document . getElementById ( 'modal-title' ) ;
49+
50+ title . textContent = `Interactive Editor - ${ problemTitle } ` ;
51+ editor . value = pythonCode ;
52+ modal . style . display = 'flex' ;
53+
54+ // Load Pyodide in background
55+ loadPyodide ( ) ;
56+ }
57+
58+ // NOUVEAU: Close Editor
59+ function closeEditor ( ) {
60+ document . getElementById ( 'code-editor-modal' ) . style . display = 'none' ;
61+ document . getElementById ( 'code-output' ) . innerHTML = '' ;
62+ }
63+
64+ // NOUVEAU: Run Code
65+ async function runCode ( ) {
66+ const code = document . getElementById ( 'code-editor' ) . value ;
67+ const testInput = document . getElementById ( 'test-input' ) . value ;
68+ const output = document . getElementById ( 'code-output' ) ;
69+ const runBtn = document . getElementById ( 'run-code-btn' ) ;
70+
71+ output . innerHTML = '<div style="color: #888;">Running...</div>' ;
72+ runBtn . disabled = true ;
73+
74+ const pyodide = await loadPyodide ( ) ;
75+ if ( ! pyodide ) {
76+ output . innerHTML = '<div style="color: red;">Python environment not loaded</div>' ;
77+ runBtn . disabled = false ;
78+ return ;
79+ }
80+
81+ try {
82+ // Redirect stdout
83+ let outputText = '' ;
84+ pyodide . setStdout ( {
85+ batched : ( text ) => {
86+ outputText += text + '\n' ;
87+ output . innerHTML = `<pre>${ escapeHtml ( outputText ) } </pre>` ;
88+ }
89+ } ) ;
90+
91+ // If there's test input, make it available via input() mock
92+ if ( testInput ) {
93+ const inputLines = testInput . split ( '\n' ) ;
94+ let inputIndex = 0 ;
95+ pyodide . globals . set ( '__test_input__' , inputLines ) ;
96+ const inputMock = `
97+ import builtins
98+ _input_lines = __test_input__
99+ _input_index = 0
100+
101+ def mock_input(prompt=''):
102+ global _input_index
103+ if prompt:
104+ print(prompt, end='')
105+ if _input_index < len(_input_lines):
106+ line = _input_lines[_input_index]
107+ _input_index += 1
108+ print(line)
109+ return line
110+ return ''
111+
112+ builtins.input = mock_input
113+ ` ;
114+ await pyodide . runPythonAsync ( inputMock ) ;
115+ }
116+
117+ // Run the code
118+ await pyodide . runPythonAsync ( code ) ;
119+
120+ if ( ! outputText ) {
121+ output . innerHTML = '<div style="color: #4CAF50;">✓ Code executed successfully (no output)</div>' ;
122+ }
123+ } catch ( err ) {
124+ output . innerHTML = `<div style="color: red;">Error:\n${ escapeHtml ( err . message ) } </div>` ;
125+ }
126+
127+ runBtn . disabled = false ;
128+ }
129+
130+ // NOUVEAU: Reset Code
131+ function resetCode ( ) {
132+ document . getElementById ( 'code-editor' ) . value = state . originalCode ;
133+ document . getElementById ( 'code-output' ) . innerHTML = '' ;
134+ }
135+
136+ // NOUVEAU: Download Code
137+ function downloadCode ( ) {
138+ const code = document . getElementById ( 'code-editor' ) . value ;
139+ const blob = new Blob ( [ code ] , { type : 'text/plain' } ) ;
140+ const url = URL . createObjectURL ( blob ) ;
141+ const a = document . createElement ( 'a' ) ;
142+ a . href = url ;
143+ a . download = 'solution.py' ;
144+ a . click ( ) ;
145+ URL . revokeObjectURL ( url ) ;
146+ }
147+
148+ // NOUVEAU: Clear Output
149+ function clearOutput ( ) {
150+ document . getElementById ( 'code-output' ) . innerHTML = '' ;
151+ }
152+
12153// Utility
13154function getRepoPath ( ) {
14155 const parts = window . location . pathname . split ( '/' ) ;
@@ -247,6 +388,9 @@ function renderFolder() {
247388 } ) ;
248389}
249390
391+
392+ // CONTINUATION DE script.js
393+
250394async function renderProblem ( ) {
251395 const view = document . getElementById ( 'problem-view' ) ;
252396 view . style . display = 'block' ;
@@ -338,13 +482,15 @@ async function renderProblem() {
338482 ? `https://github.com/${ getRepoPath ( ) } /blob/main/${ pathStr } /${ pyFiles [ 0 ] . name } `
339483 : `https://github.com/${ getRepoPath ( ) } /tree/main/${ pathStr } ` ;
340484
485+ // MODIFIÉ: Ajout du bouton "Run Code"
341486 let html = `
342487 <div class="problem-header">
343488 <div class="problem-nav">
344489 <button onclick="window.goBack()" class="nav-link nav-button">← Back</button>
345490 <a href="#" class="nav-link">🏠 Home</a>
346491 ${ problemUrl ? `<a href="${ problemUrl } " target="_blank" class="nav-link">🔗 Problem</a>` : '' }
347492 <a href="${ githubFileUrl } " target="_blank" class="nav-link">📂 GitHub File</a>
493+ ${ pythonCode ? `<button onclick="window.openEditor(\`${ escapeBackticks ( pythonCode ) } \`, '${ escapeHtml ( title ) } ')" class="nav-link nav-button run-code-nav">▶️ Run Code</button>` : '' }
348494 </div>
349495 <h1 class="problem-title">${ title } </h1>
350496 <p class="problem-subtitle">${ subtitle } </p>
@@ -471,6 +617,11 @@ function escapeHtml(text) {
471617 return div . innerHTML ;
472618}
473619
620+ // NOUVEAU: Escape backticks for template literals
621+ function escapeBackticks ( text ) {
622+ return text . replace ( / ` / g, '\\`' ) . replace ( / \$ / g, '\\$' ) ;
623+ }
624+
474625function getDefaultEmoji ( platformName ) {
475626 const emojis = {
476627 leetcode : '💡' ,
@@ -490,6 +641,23 @@ window.addEventListener('hashchange', () => {
490641 render ( ) ;
491642} ) ;
492643
644+ // NOUVEAU: Modal event listeners
645+ document . addEventListener ( 'DOMContentLoaded' , ( ) => {
646+ // Close modal when clicking outside
647+ document . getElementById ( 'code-editor-modal' ) . addEventListener ( 'click' , ( e ) => {
648+ if ( e . target . id === 'code-editor-modal' ) {
649+ closeEditor ( ) ;
650+ }
651+ } ) ;
652+
653+ // Button handlers
654+ document . getElementById ( 'close-modal-btn' ) . addEventListener ( 'click' , closeEditor ) ;
655+ document . getElementById ( 'run-code-btn' ) . addEventListener ( 'click' , runCode ) ;
656+ document . getElementById ( 'reset-code-btn' ) . addEventListener ( 'click' , resetCode ) ;
657+ document . getElementById ( 'download-code-btn' ) . addEventListener ( 'click' , downloadCode ) ;
658+ document . getElementById ( 'clear-output-btn' ) . addEventListener ( 'click' , clearOutput ) ;
659+ } ) ;
660+
493661// Initialize
494662( async ( ) => {
495663 console . log ( 'Initializing app...' ) ;
@@ -503,8 +671,9 @@ window.addEventListener('hashchange', () => {
503671 return ;
504672 }
505673
506- // Make goBack globally available
674+ // Make functions globally available
507675 window . goBack = goBack ;
676+ window . openEditor = openEditor ; // NOUVEAU
508677
509678 // Load platforms from data.json
510679 await loadPlatforms ( ) ;
0 commit comments