Skip to content

Commit e8865f3

Browse files
committed
Fixed terminal "input" and changed style of it and also changed default docusaurus branding to our own
1 parent a5006fa commit e8865f3

6 files changed

Lines changed: 274 additions & 108 deletions

File tree

docusaurus.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,15 @@ const config = {
7878
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
7979
({
8080
// Replace with your project's social card
81-
image: 'img/docusaurus-social-card.jpg',
81+
image: 'img/logo.png',
8282
colorMode: {
8383
respectPrefersColorScheme: true,
8484
},
8585
navbar: {
8686
title: 'The Python Ledger',
8787
logo: {
8888
alt: 'The python Ledger logo',
89-
src: 'img/logo.svg',
89+
src: 'img/logo.png',
9090
},
9191
items: [
9292
{
Lines changed: 114 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useEffect } from 'react';
1+
import React, { useState, useEffect, useRef } from 'react';
22
import CodeMirror from '@uiw/react-codemirror';
33
import { python } from '@codemirror/lang-python';
44
import { oneDark } from '@codemirror/theme-one-dark';
@@ -7,23 +7,64 @@ import styles from './styles.module.css';
77
export default function InteractivePython({ children }) {
88
const initialCode = children.props.children.trim();
99
const [code, setCode] = useState(initialCode);
10-
const [output, setOutput] = useState("");
1110
const [isSkulptReady, setIsSkulptReady] = useState(false);
1211
const [isRunning, setIsRunning] = useState(false);
1312

13+
const outputRef = useRef(null);
14+
const inputContainerRef = useRef(null);
15+
const inputPromptRef = useRef(null);
16+
const inputFieldRef = useRef(null);
17+
const resolveInputRef = useRef(null);
18+
19+
const appendOutput = (text) => {
20+
if (outputRef.current) outputRef.current.textContent += text;
21+
};
22+
23+
const clearOutput = () => {
24+
if (outputRef.current) outputRef.current.textContent = '';
25+
};
26+
27+
const showInput = (prompt) => {
28+
if (!inputContainerRef.current) return;
29+
inputPromptRef.current.textContent = prompt;
30+
inputFieldRef.current.value = '';
31+
inputContainerRef.current.style.display = 'flex';
32+
inputFieldRef.current.focus();
33+
};
34+
35+
const hideInput = () => {
36+
if (!inputContainerRef.current) return;
37+
inputContainerRef.current.style.display = 'none';
38+
};
39+
40+
const submitInput = () => {
41+
const value = inputFieldRef.current.value;
42+
const prompt = inputPromptRef.current.textContent;
43+
hideInput();
44+
appendOutput(prompt + value + "\n");
45+
if (resolveInputRef.current) {
46+
const resolve = resolveInputRef.current;
47+
resolveInputRef.current = null;
48+
resolve(value);
49+
}
50+
};
51+
52+
useEffect(() => {
53+
if (inputContainerRef.current) {
54+
inputContainerRef.current.style.display = 'none';
55+
}
56+
}, []);
57+
1458
useEffect(() => {
15-
const loadScript = (src) => {
16-
return new Promise((resolve, reject) => {
17-
const script = document.createElement('script');
18-
script.src = src;
19-
script.async = true;
20-
script.onload = resolve;
21-
script.onerror = reject;
22-
document.body.appendChild(script);
23-
});
24-
};
25-
26-
// Sequential loading to prevent "Sk is not defined"
59+
const loadScript = (src) => new Promise((resolve, reject) => {
60+
const script = document.createElement('script');
61+
script.src = src;
62+
script.async = true;
63+
script.onload = resolve;
64+
script.onerror = reject;
65+
document.body.appendChild(script);
66+
});
67+
2768
async function initSkulpt() {
2869
try {
2970
if (!window.Sk) {
@@ -35,81 +76,93 @@ export default function InteractivePython({ children }) {
3576
console.error("Failed to load Skulpt scripts", err);
3677
}
3778
}
38-
3979
initSkulpt();
4080
}, []);
4181

4282
const runCode = () => {
43-
if (!isSkulptReady || !window.Sk) {
44-
setOutput("Engine is still loading...");
45-
return;
46-
}
83+
if (!isSkulptReady || !window.Sk) return;
4784

48-
setOutput("");
85+
clearOutput();
86+
hideInput();
4987
setIsRunning(true);
88+
resolveInputRef.current = null;
5089

5190
window.Sk.configure({
52-
output: (text) => setOutput((prev) => prev + text),
53-
read: (x) => {
91+
output: (text) => appendOutput(text),
92+
read: (x) => {
5493
if (window.Sk.builtinFiles === undefined || window.Sk.builtinFiles["files"][x] === undefined)
55-
throw "File not found: '" + x + "'";
94+
throw "File not found: '" + x + "'";
5695
return window.Sk.builtinFiles["files"][x];
57-
},
58-
// Adding yieldLimit helps prevent the browser from freezing on infinite loops
59-
yieldLimit: 100,
60-
execLimit: 5000, // 5 second timeout safety
61-
inputfun: (prompt) => window.prompt(prompt),
62-
inputfunTakesPrompt: true,
96+
},
97+
inputfunTakesPrompt: true,
98+
inputfun: (prompt) => {
99+
return new Promise((resolve) => {
100+
resolveInputRef.current = resolve;
101+
showInput(prompt);
102+
});
103+
},
104+
__future__: window.Sk.python3
63105
});
64106

65-
try {
66-
// We execute the code
67-
const result = window.Sk.importMainWithBody("<stdin>", false, code, true);
68-
69-
// Skulpt returns a 'suspension' or a promise-like object.
70-
// We use Promise.resolve to normalize it so .then() always works.
71-
Promise.resolve(result).then(
72-
() => {
73-
setIsRunning(false);
74-
console.log("Execution complete.");
75-
},
76-
(err) => {
77-
setOutput((prev) => prev + "\n" + err.toString());
78-
setIsRunning(false);
79-
}
80-
);
81-
} catch (e) {
82-
setOutput(e.toString());
107+
window.Sk.misceval.asyncToPromise(() =>
108+
window.Sk.importMainWithBody("<stdin>", false, code, true)
109+
).then(
110+
() => { setIsRunning(false); hideInput(); },
111+
(err) => {
112+
appendOutput("\n" + err.toString());
83113
setIsRunning(false);
84-
}
85-
};
114+
hideInput();
115+
}
116+
);
117+
};
86118

87119
return (
88120
<div className={styles.wrapper}>
121+
{/* ── Header ── */}
89122
<div className={styles.codeHeader}>
90-
Python Ledger Editor {isSkulptReady ? "✅ Ready" : "⏳ Loading..."}
123+
<span className={styles.codeHeaderText}>Python Ledger Editor</span>
124+
<span className={`${styles.codeHeaderStatus} ${isSkulptReady ? styles.ready : ''}`}>
125+
{isSkulptReady ? '● READY' : '○ LOADING...'}
126+
</span>
91127
</div>
92-
128+
93129
<CodeMirror
94130
value={code}
95131
theme={oneDark}
96132
extensions={[python()]}
97133
onChange={(value) => setCode(value)}
98134
/>
99-
100-
<button
101-
className={styles.runButton}
102-
onClick={runCode}
103-
disabled={!isSkulptReady}
135+
136+
{/* ── Run Button ── */}
137+
<button
138+
className={styles.runButton}
139+
onClick={runCode}
140+
disabled={!isSkulptReady || isRunning}
104141
>
105-
{isSkulptReady ? "▶ Run Code" : "Loading Engine..."}
142+
{isRunning ? '⏳ Running...' : '▶ Execute Program'}
106143
</button>
107144

108-
{output && (
109-
<div className={styles.console}>
110-
<pre className={styles.output}>{output}</pre>
145+
{/* ── Console ── */}
146+
<div className={styles.console}>
147+
<pre ref={outputRef} className={styles.output} />
148+
<div ref={inputContainerRef} className={styles.inputForm}>
149+
<span ref={inputPromptRef} className={styles.promptText} />
150+
<input
151+
ref={inputFieldRef}
152+
type="text"
153+
className={styles.terminalInput}
154+
onKeyDown={(e) => {
155+
if (e.key === 'Enter') {
156+
e.preventDefault();
157+
submitInput();
158+
}
159+
}}
160+
/>
161+
<button type="button" className={styles.submitInputBtn} onClick={submitInput}>
162+
Enter ↵
163+
</button>
111164
</div>
112-
)}
165+
</div>
113166
</div>
114167
);
115168
}

0 commit comments

Comments
 (0)