Created
September 12, 2025 16:19
-
-
Save dunossauro/f0003effa1ed92dc85edb9706165972f to your computer and use it in GitHub Desktop.
aceodide
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="pt-br"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Editor de Exercícios</title> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.36.5/ace.js"></script> | |
| <script src="https://cdn.jsdelivr.net/pyodide/v0.26.4/full/pyodide.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/python.min.js"></script> | |
| <script src="test.js"></script> | |
| <link rel="stylesheet" href="test_css.css"> | |
| </head> | |
| <body> | |
| <h1>Editor de Exercícios</h1> | |
| <code-exercice source="" test="assert xpto"></code-exercice> | |
| </body> | |
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| hljs.highlightAll(); | |
| class CodeExercice extends HTMLElement { | |
| constructor() { | |
| super(); | |
| this.source = this.getAttribute('source') || '' | |
| if (this.hasAttribute('testfilepath')) { | |
| this.testPath = this.attributes.testfilepath.value; | |
| this.hasTest = true; | |
| } else { | |
| this.testPath = this.id; | |
| this.hasTest = false; | |
| } | |
| this.darkTheme = 'github_dark' | |
| this.lightTheme = 'github_light_default' | |
| } | |
| // Função para codificar o código em Base64 | |
| encodeCode(code) { | |
| return btoa(code); // btoa codifica a string em Base64 | |
| } | |
| // Função para decodificar o código da URL | |
| decodeCodeFromUrl() { | |
| const params = new URLSearchParams(window.location.search); | |
| const encodedCode = params.get("code"); // Espera algo como ?code=base64_encoded_code | |
| if (encodedCode) { | |
| return atob(encodedCode); // atob decodifica a string Base64 | |
| } | |
| return ""; | |
| } | |
| // Função para gerar o link de compartilhamento | |
| generateShareLink(code) { | |
| const encodedCode = this.encodeCode(code); | |
| const currentUrl = window.location.origin + window.location.pathname; | |
| return `${currentUrl}?code=${encodedCode}`; | |
| } | |
| setTheme(currentTheme) { | |
| if (currentTheme === "default") { | |
| this.editor.setTheme("ace/theme/" + this.lightTheme); | |
| } else if (currentTheme === "slate") { | |
| this.editor.setTheme("ace/theme/" + this.darkTheme); | |
| } | |
| } | |
| getTheme() { | |
| var theme = document.body.getAttribute('data-md-color-scheme') | |
| if (theme == 'default') { | |
| return this.lightTheme | |
| } | |
| return this.darkTheme | |
| } | |
| saveCode() { | |
| function dataUrl(data) { | |
| return "data:x-application/text;charset=utf-8," + escape(data); | |
| } | |
| var downloadLink = document.createElement("a"); | |
| downloadLink.href = dataUrl(this.editor.getValue()); | |
| downloadLink.download = 'exercicio.py'; | |
| document.body.appendChild(downloadLink); | |
| downloadLink.click(); | |
| document.body.removeChild(downloadLink); | |
| } | |
| copyCode() { | |
| navigator.clipboard.writeText(this.editor.getValue()).then(function () { | |
| console.log('Async: Copying to clipboard was successful!'); | |
| }, function (err) { | |
| console.error('Async: Could not copy text: ', err); | |
| }); | |
| } | |
| cleanOutput() { | |
| this.result.innerHTML = ''; | |
| } | |
| async initPyodide() { | |
| this.pyodide = await loadPyodide({ packages: ['pytest'] }); | |
| this.cleanOutput(); | |
| this.pyodide.setStdin( | |
| { stdin: () => prompt() } | |
| ); | |
| this.pyodide.setStdout( | |
| { batched: (string) => { this.writeOutput(string.toString()) } } | |
| ); | |
| this.pyodide.setStderr( | |
| { batched: (string) => { this.writeOutput(string.toString()) } } | |
| ); | |
| } | |
| async runCode() { | |
| let pyodide = await this.pyodideReadyPromise; | |
| this.cleanOutput(); | |
| this.pyodide.setStdout( | |
| { | |
| batched: (string) => { | |
| this.writeOutput(string.toString()); | |
| } | |
| } | |
| ); | |
| if (this.editor.getValue()) { | |
| try { | |
| let output = this.pyodide.runPython(this.editor.getValue()); | |
| } catch (err) { | |
| this.writeOutput(err.toString()); | |
| } | |
| } else { | |
| this.writeOutput('Não existe código no editor...'); | |
| } | |
| } | |
| async writeOutput(value) { | |
| let code = hljs.highlight(value, { language: "python" }).value | |
| this.result.innerHTML += `<pre><code>${code}</code></pre>`; | |
| } | |
| loadAce() { | |
| this.editor = ace.edit(`${this.code}`, { | |
| mode: "ace/mode/python", | |
| minLines: 10, | |
| maxLines: 20, | |
| fontSize: '.8rem', | |
| theme: `ace/theme/${this.getTheme()}` | |
| }); | |
| } | |
| connectedCallback() { | |
| this.getTheme() | |
| this.code = `code-${this.testPath}` | |
| this.innerHTML = ` | |
| <button id="play-${this.code}" class="editor-btn">Play!</button> | |
| <button id="test-${this.code}" class="editor-btn" disabled>Test!</button> | |
| <button id="clean-${this.code}" class="editor-btn">Clean!</button> | |
| <button id="save-${this.code}" class="editor-btn">Save!</button> | |
| <button id="copy-${this.code}" class="editor-btn">Copy!</button> | |
| <button id="share-${this.code}" class="editor-btn">Share!</button> | |
| <div id="${this.code}" class="editor"></div> | |
| <div id="result-${this.code}" class="result">Iniciando...</div> | |
| `; | |
| this.loadAce(); | |
| this.result = document.getElementById(`result-${this.code}`); | |
| const playBtn = document.getElementById(`play-${this.code}`); | |
| const testBtn = document.getElementById(`test-${this.code}`); | |
| const cleanBtn = document.getElementById(`clean-${this.code}`); | |
| const saveBtn = document.getElementById(`save-${this.code}`); | |
| const copyBtn = document.getElementById(`copy-${this.code}`); | |
| const shareBtn = document.getElementById(`share-${this.code}`); | |
| if (this.hasTest) { | |
| testBtn.disabled = false; | |
| } | |
| playBtn.addEventListener('click', () => this.runCode()) | |
| testBtn.addEventListener('click', () => this.testCode()) | |
| cleanBtn.addEventListener('click', () => this.cleanOutput()) | |
| copyBtn.addEventListener('click', () => this.copyCode()) | |
| saveBtn.addEventListener('click', () => this.saveCode()) | |
| shareBtn.addEventListener('click', () => { | |
| const code = this.editor.getValue(); | |
| const shareLink = this.generateShareLink(code); | |
| this.writeOutput(`Link para compartilhar: \n${shareLink}`); | |
| }); | |
| this.pyodideReadyPromise = this.initPyodide(); | |
| // Preenche o editor com o código da URL (se existir) | |
| const codeFromUrl = this.decodeCodeFromUrl(); | |
| if (codeFromUrl) { | |
| this.editor.setValue(codeFromUrl); | |
| } | |
| } | |
| async testCode(exercice) { | |
| let pyodide = await this.pyodideReadyPromise; | |
| this.writeOutput('Testing...'); | |
| this.cleanOutput() | |
| this.pyodide.setStdout( | |
| { batched: (string) => { | |
| console.log(string) | |
| if (string.startsWith('FAILED')) | |
| this.writeOutput( | |
| `${string.split('-')[1]}` | |
| ) | |
| }} | |
| ); | |
| const globals = this.pyodide.toPy( | |
| { | |
| editor: this.editor, | |
| testPath: this.testPath | |
| } | |
| ); | |
| let output = this.pyodide.runPythonAsync(` | |
| import pytest | |
| from js import window | |
| from pyodide.http import pyfetch | |
| from pathlib import Path | |
| async def test(exercice: str) -> None: | |
| with open("sut.py", "w") as f: | |
| f.write(editor.getValue()) | |
| # checar se o arquivo .py já não existe | |
| if not Path('{exercice}.py').exists(): | |
| response = await pyfetch( | |
| f"{window.location.origin}/tests/{exercice}.py" | |
| ) | |
| with open(f"{exercice}.py", "wb") as f: | |
| f.write(await response.bytes()) | |
| pytest.main( | |
| [ | |
| f'{exercice}.py', | |
| '--tb=no', | |
| '--no-header', | |
| '--capture=no', | |
| '-q', | |
| ] | |
| ) | |
| test(testPath) | |
| `, { globals }) | |
| } | |
| } | |
| customElements.define('code-exercice', CodeExercice); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| :root{ | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment