Skip to content

Commit f192619

Browse files
committed
Adding editor
1 parent d82c886 commit f192619

3 files changed

Lines changed: 493 additions & 2 deletions

File tree

index.html

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,46 @@ <h1 id="folder-title"></h1>
3434
<div id="problem-view" style="display: none;"></div>
3535
</main>
3636

37+
<!-- NOUVEAU: Modal pour l'éditeur interactif -->
38+
<div id="code-editor-modal" class="modal">
39+
<div class="modal-content">
40+
<div class="modal-header">
41+
<h2 id="modal-title">Interactive Python Editor</h2>
42+
<div class="modal-actions">
43+
<button id="run-code-btn" class="action-btn run-btn">▶️ Run Code</button>
44+
<button id="reset-code-btn" class="action-btn">↺ Reset</button>
45+
<button id="download-code-btn" class="action-btn">⬇️ Download</button>
46+
<button id="close-modal-btn" class="action-btn close-btn">✕ Close</button>
47+
</div>
48+
</div>
49+
<div class="modal-body">
50+
<div class="editor-container">
51+
<div class="editor-section">
52+
<div class="section-header">Python Code</div>
53+
<textarea id="code-editor" spellcheck="false"></textarea>
54+
</div>
55+
<div class="editor-section">
56+
<div class="section-header">
57+
<span>Console Output</span>
58+
<button id="clear-output-btn" class="clear-btn">Clear</button>
59+
</div>
60+
<div id="code-output"></div>
61+
</div>
62+
</div>
63+
<div class="test-input-section">
64+
<div class="section-header">Test Input (optional - paste test data here)</div>
65+
<textarea id="test-input" placeholder="Paste your test input here..."></textarea>
66+
</div>
67+
</div>
68+
<div id="pyodide-loading" class="pyodide-status">
69+
<div class="loader-small"></div>
70+
<span>Loading Python environment...</span>
71+
</div>
72+
</div>
73+
</div>
74+
75+
<!-- MODIFIÉ: Ajout de Pyodide -->
76+
<script src="https://cdn.jsdelivr.net/pyodide/v0.24.1/full/pyodide.js"></script>
3777
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
3878
<script src="script.js"></script>
3979
</body>

script.js

Lines changed: 171 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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
13154
function getRepoPath() {
14155
const parts = window.location.pathname.split('/');
@@ -247,6 +388,9 @@ function renderFolder() {
247388
});
248389
}
249390

391+
392+
// CONTINUATION DE script.js
393+
250394
async 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+
474625
function 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

Comments
 (0)