Skip to content

Commit 70296ad

Browse files
authored
u: added feat
1 parent 8361570 commit 70296ad

File tree

21 files changed

+653
-196
lines changed

21 files changed

+653
-196
lines changed

assets/retos/events.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { takeScreenshot, getRandomNumber } from "./utils.js";
2+
3+
const toggleTheme = document.getElementById("toggle-theme");
4+
const toggleMode = document.getElementById("toggle-mode");
5+
const vimToggle = document.getElementById("vim-toggle");
6+
const nextButton = document.getElementById("e-next");
7+
const prevButton = document.getElementById("e-prev");
8+
const runButton = document.getElementById("run");
9+
const clearButton = document.getElementById("clear");
10+
const numberInput = document.getElementById("e-number-input");
11+
const confirmButton = document.getElementById("e-confirm");
12+
const randomButton = document.getElementById("e-random");
13+
const cameraButton = document.getElementById("take-photo");
14+
15+
export function init(exercises, editor, output, callback) {
16+
toggleMode.addEventListener("click", () => {
17+
if (editor.getOption("keyMap") == "vim") {
18+
editor.setOption("keyMap", "default");
19+
vimToggle.textContent = "toggle_off";
20+
} else {
21+
editor.setOption("keyMap", "vim");
22+
vimToggle.textContent = "toggle_on";
23+
}
24+
});
25+
toggleTheme.addEventListener("click", () => {
26+
if (document.documentElement.classList.contains('dark')) {
27+
document.documentElement.classList.remove('dark');
28+
localStorage.setItem('color-theme', 'light');
29+
editor.setOption("theme", "default");
30+
document.body.classList.remove("default-mode")
31+
document.body.classList.add("dark-mode")
32+
} else {
33+
document.documentElement.classList.add('dark');
34+
localStorage.setItem('color-theme', 'dark');
35+
editor.setOption("theme", "dracula");
36+
document.body.classList.remove("dark-mode")
37+
document.body.classList.add("default-mode")
38+
}
39+
});
40+
nextButton.addEventListener("click", () => {
41+
exercises.nextIndex();
42+
exercises.createExercise();
43+
});
44+
prevButton.addEventListener("click", () => {
45+
exercises.prevIndex();
46+
exercises.createExercise();
47+
});
48+
runButton.addEventListener("click", () => {
49+
output.value = callback();
50+
});
51+
clearButton.addEventListener("click", () => {
52+
output.value = "";
53+
})
54+
confirmButton.addEventListener("click", () => {
55+
const max_ = exercises.getMaxIndex();
56+
let inputNumber = Number(numberInput.value);
57+
if (inputNumber < 0 || inputNumber > max_) {
58+
inputNumber = getRandomNumber(0, max_);
59+
}
60+
exercises.setIndex(inputNumber);
61+
exercises.createExercise();
62+
numberInput.value = "";
63+
})
64+
randomButton.addEventListener("click", () => {
65+
const max_ = exercises.getMaxIndex();
66+
const inputNumber = getRandomNumber(0, max_);
67+
exercises.setIndex(inputNumber);
68+
exercises.createExercise();
69+
numberInput.value = "";
70+
})
71+
cameraButton.addEventListener("click", () => {
72+
takeScreenshot();
73+
})
74+
}

assets/retos/exercises.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
class Exercise {
2+
constructor(id, attributes, maxIndex) {
3+
this.id = id;
4+
for (const key in attributes) {
5+
this[key] = attributes[key]
6+
}
7+
this.maxIndex = maxIndex;
8+
}
9+
10+
#pad (s, digits = 3, character = "0") {
11+
return s.toString().padStart(digits, character)
12+
}
13+
14+
percentage() {
15+
return `width: ${this.rating}%;`
16+
}
17+
18+
pageNumber() {
19+
return this.#pad(this.id)
20+
}
21+
22+
challenge() {
23+
return `Reto ${this.#pad(this.id)}/${this.#pad(this.maxIndex)}`;
24+
}
25+
26+
getTest() { return this.test; }
27+
}
28+
29+
export class Exercises {
30+
constructor() {
31+
this.exercises = [];
32+
this.index = 0;
33+
this.maxIndex = 0;
34+
}
35+
36+
getIndex() { return this.index; }
37+
38+
getMaxIndex() { return this.maxIndex; }
39+
40+
prevIndex() { if (this.index >= 1) this.index -= 1; }
41+
42+
nextIndex() { if (this.index <= this.maxIndex - 2) this.index += 1; }
43+
44+
setIndex(n) { if (n >= 1 && n <= this.maxIndex) this.index = n - 1; }
45+
46+
getCurrent() { return this.exercises[this.index]; }
47+
48+
getTest() { return this.getCurrent().test; }
49+
50+
loadData(filename) {
51+
return fetch(filename)
52+
.then(response => {
53+
if (!response.ok) throw new Error("Error!");
54+
return response.json();
55+
})
56+
.then(data => {
57+
const maxIndex = data["data"].length;
58+
this.exercises = data["data"].map(eData => {
59+
return new Exercise(eData.id, eData.attributes, maxIndex);
60+
})
61+
this.maxIndex = maxIndex;
62+
})
63+
.catch(error => {
64+
console.error("Error:", error);
65+
throw error;
66+
});
67+
}
68+
69+
createExercise() {
70+
const exercise = this.getCurrent()
71+
const table = ["title", "body", "prompt", "input", "output"];
72+
table.forEach(e => {
73+
document.getElementById(`e-${e}`).textContent = exercise[e];
74+
});
75+
document.getElementById("e-number").textContent = exercise.challenge();
76+
document.getElementById("e-rating").style.cssText = exercise.percentage();
77+
document.getElementById("e-current").textContent = exercise.pageNumber();
78+
let list = document.getElementById("e-hints");
79+
list.innerHTML = "";
80+
exercise.hints.forEach(el => {
81+
const li = document.createElement("li");
82+
li.textContent = el;
83+
li.classList.add("text-xs");
84+
list.appendChild(li);
85+
});
86+
}
87+
}

assets/retos/script.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { init } from './events.js'
2+
import { Exercises } from './exercises.js'
3+
import { showResult, handleError } from './utils.js';
4+
5+
let confettiInterval = null;
6+
const pre_code = '#!/usr/bin/python3\n\n\ndef main():\n # Code here...\n' +
7+
' pass\n\n\nif __name__ == "__main__":\n main()\n';
8+
const filename = "https://ayudaenpython.com/challenges/data/"
9+
const output = document.getElementById("output");
10+
output.value = "...\n";
11+
12+
const exercises = new Exercises();
13+
exercises.loadData(filename)
14+
.then(() => { exercises.createExercise(); } )
15+
.catch(error => { console.error("Error:", error); });
16+
17+
const editor = CodeMirror.fromTextArea(document.getElementById("code"), {
18+
mode: {
19+
name: "python",
20+
version: 3,
21+
singleLineStringErrors: false,
22+
},
23+
lineNumbers: true,
24+
indentUnit: 4,
25+
theme: "dracula",
26+
keyMap: "default",
27+
});
28+
editor.setOption("scrollbarStyle", "overlay");
29+
editor.setValue(pre_code);
30+
31+
init(exercises, editor, output, evaluateCode);
32+
33+
async function main() {
34+
let pyodide = await loadPyodide({
35+
indexURL: "https://cdn.jsdelivr.net/pyodide/v0.21.3/full/",
36+
});
37+
output.value = pyodide.runPython(`
38+
import sys
39+
sys.version
40+
`);
41+
output.value += "\nReady!\n";
42+
output.value = "";
43+
return pyodide;
44+
}
45+
46+
let pyodideReady = main();
47+
48+
function runCode(pyodide, code, input_) {
49+
pyodide.runPython(`
50+
import io
51+
sys.stdout = io.StringIO()
52+
input = lambda: "${input_}"
53+
`);
54+
pyodide.runPython(code);
55+
let result = pyodide.runPython("sys.stdout.getvalue()").trim();
56+
return result
57+
}
58+
59+
async function evaluateCode() {
60+
const results = [];
61+
let pyodide = await pyodideReady;
62+
try {
63+
const { inputs, outputs } = exercises.getTest();
64+
const code = editor.getValue();
65+
inputs.forEach((input_, i) => {
66+
const result = runCode(pyodide, code, input_);
67+
results.push(outputs[i] == result);
68+
});
69+
showResult(confettiInterval, Date.now(), results);
70+
} catch (err) {
71+
handleError(output, err);
72+
}
73+
}
74+
75+
document.addEventListener("keydown", (e) => {
76+
if (e.ctrlKey && e.altKey && e.key === "j") {
77+
evaluateCode();
78+
}
79+
});

assets/retos/styles.css

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
:root {
2+
--max-height: 60vh;
3+
--min-height-editor: 60vh;
4+
--min-height-output: 20vh;
5+
--max-font-size: 16px;
6+
--min-font-size: 12px;
7+
--charcoal: #231f20;
8+
--white: rgb(229 231 235);
9+
}
10+
11+
body {
12+
background-image:
13+
repeating-linear-gradient(to right,var(--charcoal),var(--charcoal) 1px,transparent 1px,transparent 30px),
14+
repeating-linear-gradient(to bottom,var(--charcoal),var(--charcoal) 1px,transparent 1px,transparent 30px);
15+
}
16+
17+
h1 {
18+
color: var(--charcoal);
19+
text-shadow:
20+
-1px -1px 0 #000,
21+
1px -1px 0 #000,
22+
-1px 1px 0 #000,
23+
1px 1px 0 #000;
24+
}
25+
26+
textarea {
27+
font-family: "Source Code Pro", monospace;
28+
font-size: var(--max-font-size);
29+
border-color: transparent;
30+
}
31+
32+
.CodeMirror {
33+
resize: vertical;
34+
font-family: "Source Code Pro", monospace;
35+
font-size: var(--max-font-size);
36+
height: var(--max-height);
37+
}
38+
39+
.dark .bg-gray-100 {
40+
background-color: rgb(17 24 39);
41+
color: #f9fafb;
42+
}
43+
44+
.dark .bg-gray-300 {
45+
background-color: rgb(55 65 81);
46+
color: #f9fafb;
47+
}
48+
49+
.dark #output {
50+
color: var(--white);
51+
}
52+
53+
body.dark-mode {
54+
background-image:
55+
repeating-linear-gradient(to right, var(--white), var(--white) 1px, transparent 1px, transparent 30px),
56+
repeating-linear-gradient(to bottom, var(--white), var(--white) 1px, transparent 1px, transparent 30px);
57+
}
58+
59+
body.default-mode {
60+
background-image:
61+
repeating-linear-gradient(to right,var(--charcoal),var(--charcoal) 1px,transparent 1px,transparent 30px),
62+
repeating-linear-gradient(to bottom,var(--charcoal),var(--charcoal) 1px,transparent 1px,transparent 30px);
63+
}
64+
65+
@media screen and (max-width: 765px){
66+
textarea {
67+
font-size: var(--min-font-size);
68+
}
69+
.CodeMirror {
70+
height: var(--min-height-editor);
71+
font-size: var(--min-font-size);
72+
}
73+
.material-icons-outlined {
74+
font-size: 16px;
75+
}
76+
}
77+
78+
.ratings {
79+
position: relative;
80+
vertical-align: middle;
81+
display: inline-block;
82+
color: #b1b1b1;
83+
overflow: hidden;
84+
}
85+
.full-stars {
86+
position: absolute;
87+
left: 0;
88+
top: 0;
89+
white-space: nowrap;
90+
overflow: hidden;
91+
color: #fde16d;
92+
}
93+
.empty-stars:before, .full-stars:before {
94+
content:"\2605\2605\2605\2605\2605";
95+
font-size: 14pt;
96+
}
97+
.empty-stars:before {
98+
-webkit-text-stroke: 1px #848484;
99+
}
100+
.full-stars:before {
101+
-webkit-text-stroke: 1px orange;
102+
}
103+
104+
@media (max-width: 768px) {
105+
span.material-symbols-outlined {
106+
font-size: 14px;
107+
}
108+
#current-exercise {
109+
font-size: 10px;
110+
}
111+
}

0 commit comments

Comments
 (0)