Skip to content

Commit d4e3eae

Browse files
author
rmen527
committed
Finish textboxes and code functionality
1 parent 7547dc0 commit d4e3eae

7 files changed

Lines changed: 171 additions & 14 deletions

File tree

package-lock.json

Lines changed: 41 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"dependencies": {
1313
"@fontsource/fira-code": "^5.2.7",
1414
"@tailwindcss/vite": "^4.2.2",
15+
"pyodide": "^0.29.3",
1516
"react": "^19.2.4",
1617
"react-dom": "^19.2.4",
1718
"react-router": "^7.14.1",

public/logo.png

66.4 KB
Loading

src/components/Codebox.tsx

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { useState, useEffect, useRef } from "react";
2+
import { loadPyodide, type PyodideInterface } from "pyodide";
3+
4+
interface CodeboxProps {
5+
lightcol: string;
6+
darkcol: string;
7+
headerimage: string;
8+
onOutput: (output: string) => void;
9+
}
10+
11+
export default function Codebox(props: CodeboxProps) {
12+
13+
const [code, setCode] = useState("");
14+
15+
////////
16+
const [isRunning, setIsRunning] = useState(false);
17+
const [pyodideReady, setPyodideReady] = useState(false);
18+
19+
// Store the Pyodide instance so we only load it once
20+
const pyodideRef = useRef<PyodideInterface | null>(null);
21+
22+
// ←←← LOAD PYODIDE ONLY ONCE ←←←
23+
useEffect(() => {
24+
let mounted = true;
25+
26+
const initialize = async () => {
27+
try {
28+
const pyodide = await loadPyodide({
29+
indexURL: "https://cdn.jsdelivr.net/pyodide/v0.29.3/full/",
30+
});
31+
32+
if (mounted) {
33+
pyodideRef.current = pyodide;
34+
setPyodideReady(true);
35+
}
36+
} catch (err) {
37+
console.error("Failed to load Pyodide", err);
38+
props.onOutput?.("Failed to load Python runtime 😢");
39+
}
40+
};
41+
42+
initialize();
43+
44+
return () => {
45+
mounted = false;
46+
};
47+
}, [props.onOutput]);
48+
49+
const runCode = async () => {
50+
const pyodide = pyodideRef.current;
51+
if (!pyodide) {
52+
props.onOutput?.("Python runtime is still loading...");
53+
return;
54+
}
55+
56+
setIsRunning(true);
57+
let output = "";
58+
59+
try {
60+
// Capture print() output
61+
pyodide.setStdout({
62+
batched: (msg: string) => {
63+
output += msg + "\n";
64+
},
65+
});
66+
67+
const result = await pyodide.runPythonAsync(code);
68+
69+
const fullOutput =
70+
output.trim() +
71+
(result !== undefined ? "\n" + String(result) : "");
72+
73+
props.onOutput?.(fullOutput || "Code executed successfully (no output)");
74+
} catch (err: any) {
75+
props.onOutput?.(`Error: ${err.message}`);
76+
console.error(err);
77+
} finally {
78+
setIsRunning(false);
79+
}
80+
};
81+
////////
82+
83+
return (
84+
<div style={{ backgroundColor: props.lightcol, boxShadow: `0 12px 0 ${props.darkcol}` }} className={`rounded-2xl overflow-hidden flex flex-col mt-25`}>
85+
<div className="flex items-center justify-center p-4">
86+
<img src={"/" + props.headerimage} alt={props.headerimage} className="relative h-12 w-auto object-contain" />
87+
</div>
88+
89+
<textarea style={{ fontFamily: "'Fira Code', monospace", color: "white", backgroundColor: props.darkcol}} value={code} onChange={(e) => setCode(e.target.value)} placeholder="Write code here!"
90+
className="p-6 text-2xl mx-4 rounded-lg resize-none min-h-[300px]"></textarea>
91+
92+
<div className="m-4 mx-4 h-14">
93+
<button
94+
onClick={runCode}
95+
disabled={!pyodideReady || isRunning}
96+
style={{ backgroundColor: "#00A93D", boxShadow: `0 6px 0 #00531F`, opacity: !pyodideReady || isRunning ? 0.7 : 1}}
97+
className="rounded-2xl hover:brightness-110 active:brightness-90"
98+
>
99+
<img src="/run.png" alt="" className="h-14 px-6 py-3"/>
100+
</button>
101+
</div>
102+
</div>
103+
)
104+
}

src/components/Textbox.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,13 @@ interface TextboxProps {
99

1010
export default function Textbox(props: TextboxProps) {
1111
return (
12-
<div style={{ backgroundColor: props.lightcol }}>
13-
<div>
14-
<img src={"/" + props.headerimage} alt={props.headerimage} />
12+
<div style={{ backgroundColor: props.lightcol, boxShadow: `0 12px 0 ${props.darkcol}` }} className={`rounded-2xl overflow-hidden`}>
13+
<div className="flex items-center justify-center p-4">
14+
<img src={"/" + props.headerimage} alt={props.headerimage} className="relative h-12 w-auto object-contain" />
1515
</div>
16-
<div style={{ backgroundColor: props.darkcol }}>
17-
<p style={{ fontFamily: "'Fira Code', Arial"}}>{props.text}</p>
16+
<div style={{ backgroundColor: props.darkcol }} className="p-4 pb-12 mb-4 mx-4 rounded-lg">
17+
<p style={{ fontFamily: "'Fira Code', monospace", color: "white"}} className="text-xl">{props.text}</p>
1818
</div>
19-
2019
</div>
2120
)
2221
}

src/pages/home/Home.tsx

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
1+
import Codebox from "../../components/Codebox";
12
import Textbox from "../../components/Textbox";
2-
3+
import {useState} from "react"
34

45
function Home() {
6+
// code output
7+
const [output, setOutput] = useState("Waiting for code...");
8+
59
return (
610
// bg image
711
<div className="relative min-h-screen bg-[url('/bg.png')] bg-cover bg-center bg-no-repeat overflow-hidden">
8-
<div className="flex h-full w-full">
12+
<div className="flex max-w-[1600px] mx-auto px-8 gap-10">
913
{/* left */}
10-
<div className="flex-1 flex flex-col">
14+
<div className="flex-1 flex flex-col gap-6">
1115

1216
{/* logo */}
13-
<div><img src="logo.png" alt="Logo" /></div>
17+
<div><img src="logo.png" alt="Logo" className="mx-auto w-140"/></div>
1418

1519
{/* python and alien code */}
16-
<div className="flex">
20+
<div className="flex gap-6">
1721
<div className="flex-1">
1822
<Textbox
1923
lightcol="#005DA9"
@@ -44,13 +48,18 @@ function Home() {
4448
</div>
4549

4650
{/* right */}
47-
<div className="flex-1">
48-
<div>Code</div>
51+
<div className="flex-1 flex flex-col gap-6">
52+
<div>
53+
<Codebox
54+
lightcol="#5E5E5E"
55+
darkcol="#2E2E2E"
56+
headerimage="code.png" onOutput={setOutput}></Codebox>
57+
</div>
4958
<div>
5059
<Textbox
5160
lightcol="#5E5E5E"
5261
darkcol="#2E2E2E"
53-
text="Output"
62+
text={output}
5463
headerimage="output.png"
5564
/>
5665
</div>

vite.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@ export default defineConfig({
88
base: process.env.GITHUB_REPOSITORY
99
? `/${process.env.GITHUB_REPOSITORY.split('/')[1]}/`
1010
: '/',
11+
server: {
12+
host: true
13+
}
1114
})

0 commit comments

Comments
 (0)