Redesigned the editor, and added the canvas library

This commit is contained in:
2025-12-25 15:24:35 +00:00
parent 60e02da1e7
commit 168472cff8
2 changed files with 495 additions and 114 deletions

View File

@@ -1,124 +1,225 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Code Editor</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm/css/xterm.css" />
<script src="https://cdn.jsdelivr.net/npm/xterm/lib/xterm.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit/lib/xterm-addon-fit.js"></script>
<script src="http://skulpt.org/js/skulpt.min.js"></script>
<script src="http://skulpt.org/js/skulpt-stdlib.js"></script>
<script src="https://unpkg.com/litecanvas/dist/dist.dev.js"></script>
</head>
<body>
<style>
:root {
--header-height: 48px;
--bg-dark: #1e1e1e;
--bg-editor: #272822;
/* Monokai background matches ACE default */
--bg-canvas: #0e0e0e;
--text-color: #f0f0f0;
--btn-bg: #f8f8f8;
--btn-hover: #e0e0e0;
--icon-color: #333;
}
body {
margin: 0;
padding: 0;
width: 100vw;
height: 100vh;
background-color: var(--bg-dark);
color: var(--text-color);
font-family: 'Inter', 'Segoe UI', Tahoma, sans-serif;
display: flex;
height: calc(100vh-1.5vw);
background-color: #131212;
margin: 1.5vw;
padding: 0px;
/*overflow: hidden;*/
}
p{
color: white;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 1vw;
}
.codeEditor{
width: 37vw;
height: 85vh;
border-radius: 10px;
overflow: hidden;
margin-bottom: 1vw;
}
.documentationLookup{
width: 37vw;
height: 7vh;
border-radius: 10px;
flex-direction: column;
overflow: hidden;
}
.canvas{
width: 58vw;
/* height: 33.75vw; */
aspect-ratio: 16/9;
border-radius: 10px;
overflow: hidden;
margin-bottom: 1vw;
/* Header */
header {
height: var(--header-height);
background-color: #2c292d;
/* Slightly different dark */
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 12px;
border-bottom: 1px solid #111;
z-index: 100;
}
#canvas{
.header-left,
.header-right {
display: flex;
gap: 8px;
}
.label-btn {
height: 34px;
background-color: #f0f0e6;
border: 1px solid #ccc;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background 0.2s;
padding: 2;
}
.label-btn p {
font-size: 16px;
font-weight: bold;
}
.icon-btn {
width: 34px;
height: 34px;
background-color: #f0f0e6;
border: 1px solid #ccc;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background 0.2s;
padding: 0;
}
.icon-btn:hover {
background-color: #fff;
}
.icon-btn svg {
width: 20px;
height: 20px;
fill: #222;
}
main {
display: flex;
flex: 1;
height: calc(100vh - var(--header-height));
overflow: hidden;
}
#editor-container {
width: 50%;
height: 100%;
width:100%;
background: white;
display: flex;
flex-direction: column;
background-color: var(--bg-editor);
border-right: 2px solid #333;
}
.terminal{
width: 58vw;
height: 8vw;
border-radius: 10px;
overflow: hidden;
}
#terminal{
#editor {
flex: 1;
width: 100%;
height: 100%;
}
.xterm-screen{
padding: 0.75vw;
background: #2F3129;
overflow: scroll;
}
.left {
#preview-container {
width: 50%;
height: 100%;
background-color: var(--bg-canvas);
position: relative;
display: flex;
margin-right: 1vw;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
}
.right {
display: flex;
flex-direction: column;
canvas {
width: 100%;
height: 100%;
display: block;
}
.overlay-text {
position: absolute;
pointer-events: none;
color: rgba(255, 255, 255, 0.7);
font-family: 'Segoe UI', sans-serif;
font-weight: 500;
}
#terminal-container {
height: 150px;
width: 100%;
background-color: #000;
border-top: 2px solid #333;
overflow: hidden;
display: block;
}
#terminal {
width: 100%;
height: 100%;
}
</style>
<div class="left">
<div class="canvas">
<canvas id="canvas" style="height: 100%;width:100%;background: white;"></canvas>
<header>
<div class="header-left">
<button class="label-btn" title="PyStudio">
<p>PyStudio</p>
</button>
<button class="icon-btn" title="Run Code" onclick="runCurrentCode()">
<svg viewBox="0 0 24 24">
<path d="M8 5v14l11-7z" />
</svg>
</button>
<button class="icon-btn" title="Stop Code" onclick="stopCurrentCode()">
<svg viewBox="0 0 24 24">
<path d="M6 6h12v12H6z" />
</svg>
</button>
</div>
<div class="terminal">
<div id="terminal"></div>
</div>
</div>
</header>
<div class="right">
<div class="codeEditor">
<div style="overflow: hidden; height: 3vw;width: 100%;background: #2F3129;">
<p style="float: left;">main.py</p>
<button onclick="stopCurrentCode()" style="height: 100%; aspect-ratio: 1/1; float: right; border-radius: 0px; border: none;padding: 0.75vw; background: #282923;"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="#aaa" d="M64 32l320 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96C0 60.7 28.7 32 64 32z"/></svg></button>
<button onclick="runCurrentCode()" style="height: 100%; aspect-ratio: 1/1; float: right; border-radius: 0px; border: none;padding: 0.75vw; background: #282923;"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="#aaa" d="M91.2 36.9c-12.4-6.8-27.4-6.5-39.6 .7S32 57.9 32 72l0 368c0 14.1 7.5 27.2 19.6 34.4s27.2 7.5 39.6 .7l336-184c12.8-7 20.8-20.5 20.8-35.1s-8-28.1-20.8-35.1l-336-184z"/></svg></button>
<main>
<div id="editor-container">
<div id="editor"></div>
<div id="terminal-container">
<div id="terminal"></div>
</div>
<div id='editor' style="height: calc(100% - 3vw);width:100%;"></div>
</div>
<div class="documentationLookup">
<input type="text" placeholder="Documentation" style="height: 100%;width: 100%;background: #2F3129;border: 0px;color: white;padding-left: 1vw;"></input>
<div id="preview-container">
<canvas id="canvas"></canvas>
</div>
</div>
</main>
<script src="https://www.unpkg.com/ace-builds@latest/src-noconflict/ace.js" crossorigin="anonymous"></script>
<script>
codeEditor = ace.edit("editor", {
theme: "ace/theme/monokai",
mode: "ace/mode/python",
value: "print('Hello world!')"
value: "import screen as s\n\ndef draw():\n s.cls(0)\n s.text(20, 20, \"Hello World!\", 3)\n\ns.start({\n \"loop\":{\n \"draw\":draw\n }\n})",
fontFamily: 'Consolas, monospace',
fontSize: '14px'
});
codeEditor.setShowPrintMargin(false);
let StopExecution;
let term;
let inputResolve;
let inputBuffer = '';
let acceptingInput = false;
term = new Terminal({ cursorBlink: true });
term = new Terminal({
cursorBlink: true,
theme: {
background: '#1e1e1e'
}
});
term.open(document.getElementById('terminal'));
term.fit();
function outf(text) {
term.write(text.replace(/\n/g, '\r\n'));
@@ -129,67 +230,121 @@
inputBuffer = '';
inputResolve = resolve;
acceptingInput = true;
term.write(promptText);
console.log("Input requested: " + promptText);
});
}
term.onKey(e => {
if (!acceptingInput) return;
const key = e.key;
const ev = e.domEvent;
if (ev.key === 'Enter') {
term.write('\r\n');
if (inputResolve) {
inputResolve(inputBuffer);
inputResolve = null;
acceptingInput = false;
}
} else if (ev.key === 'Backspace') {
if (inputBuffer.length > 0) {
inputBuffer = inputBuffer.slice(0, -1);
term.write('\b \b');
}
} else if (key.length === 1) {
inputBuffer += key;
term.write(key);
}
function runCode(code) {
term.clear();
Sk.configure({
output: outf,
read: x => Sk.builtinFiles.files[x],
inputfun: inputf,
inputfunTakesPrompt: true
});
Sk.misceval.asyncToPromise(() => Sk.importMainWithBody('<stdin>', false, code, true))
.catch(err => outf(err.toString() + '\n'));
}
});
function stopCurrentCode() {
StopExecution = true;
if (window.__lc_instance) {
if (window.__lc_instance.pause) window.__lc_instance.pause();
window.__lc_instance = null;
}
const oldCanvas = document.getElementById('canvas');
if (oldCanvas) {
const newCanvas = oldCanvas.cloneNode(true);
oldCanvas.parentNode.replaceChild(newCanvas, oldCanvas);
const ctx = newCanvas.getContext('2d');
if (ctx) {
ctx.fillStyle = '#0e0e0e';
ctx.fillRect(0, 0, newCanvas.width, newCanvas.height);
}
}
const globals = ['W', 'H', 'T', 'MX', 'MY', 'CENTERX', 'CENTERY', 'ENGINE', 'WIDTH', 'HEIGHT'];
globals.forEach(g => {
delete window[g];
window[g] = undefined;
});
}
function builtinRead(x) {
if (x === 'screen' || x === './screen.js') {
const xhr = new XMLHttpRequest();
xhr.open("GET", document.location.href + "screen.js", false);
xhr.send();
if (xhr.status === 200) return xhr.responseText;
else throw "Could not load screen.js";
}
if (Sk.builtinFiles === undefined || Sk.builtinFiles["files"][x] === undefined)
if (x === 'src/builtin/sys.js') return '';
else throw "File not found: '" + x + "'";
return Sk.builtinFiles["files"][x];
}
function runCurrentCode() {
stopCurrentCode();
if (term) term.clear();
StopExecution = false;
code = codeEditor.getValue();
term.clear();
var code = codeEditor.getValue();
const canvas = document.getElementById('canvas');
const container = document.getElementById('preview-container');
if (canvas && container) {
const w = Math.floor(container.clientWidth);
const h = Math.floor(container.clientHeight);
canvas.width = w;
canvas.height = h;
}
Sk.configure({
output: outf,
read: x => Sk.builtinFiles.files[x],
read: builtinRead,
inputfun: inputf,
inputfunTakesPrompt: true
});
Sk.misceval.asyncToPromise(() => Sk.importMainWithBody('<stdin>', false, code, true), {
"*": () => {
if (StopExecution) throw "Execution interrupted"
}
})
.catch(err => outf(err.toString() + '\n'));
.catch(err => {
if (err !== "Execution interrupted") outf(err.toString() + "\n");
});
}
function toggleEditor() {
runCurrentCode();
}
function takeScreenshot() {
const canvas = document.getElementById('canvas');
if (canvas) {
const link = document.createElement('a');
link.download = 'litecanvas-screenshot.png';
link.href = canvas.toDataURL();
link.click();
}
}
document.addEventListener('keydown', function (e) {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
runCurrentCode();
}
});
window.addEventListener('load', () => {
setTimeout(runCurrentCode, 100);
});
window.addEventListener('resize', () => {
const canvas = document.getElementById('canvas');
const container = document.getElementById('preview-container');
if (canvas && container) {
canvas.width = container.clientWidth;
canvas.height = container.clientHeight;
}
if (term) {
term.fit();
}
});
</script>
</body>
</html>