Renamed editor to index
This commit is contained in:
360
index.html
Normal file
360
index.html
Normal file
@@ -0,0 +1,360 @@
|
||||
<!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;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
.header-left,
|
||||
.header-right {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.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%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--bg-editor);
|
||||
border-right: 2px solid #333;
|
||||
}
|
||||
|
||||
#editor {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#preview-container {
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
background-color: var(--bg-canvas);
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<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>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div id="editor-container">
|
||||
<div id="editor"></div>
|
||||
<div id="terminal-container">
|
||||
<div id="terminal"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="preview-container">
|
||||
<canvas id="canvas"></canvas>
|
||||
</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: "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 fitAddon;
|
||||
let inputResolve;
|
||||
|
||||
term = new Terminal({
|
||||
cursorBlink: true,
|
||||
theme: {
|
||||
background: '#1e1e1e'
|
||||
},
|
||||
scrollback: 10000,
|
||||
allowProposedApi: true
|
||||
});
|
||||
fitAddon = new FitAddon.FitAddon();
|
||||
term.loadAddon(fitAddon);
|
||||
term.open(document.getElementById('terminal'));
|
||||
|
||||
// Use a small timeout to ensure the DOM is ready before fitting
|
||||
setTimeout(() => fitAddon.fit(), 0);
|
||||
|
||||
function outf(text) {
|
||||
term.write(text.replace(/\n/g, '\r\n'));
|
||||
}
|
||||
|
||||
function inputf(promptText = '') {
|
||||
return new Promise(resolve => {
|
||||
inputBuffer = '';
|
||||
inputResolve = resolve;
|
||||
acceptingInput = true;
|
||||
console.log("Input requested: " + promptText);
|
||||
});
|
||||
}
|
||||
|
||||
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 url = new URL('screen.js?t=' + Date.now(), window.location.href).href;
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", url, 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;
|
||||
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: builtinRead,
|
||||
inputfun: inputf,
|
||||
inputfunTakesPrompt: true
|
||||
});
|
||||
|
||||
Sk.misceval.asyncToPromise(() => Sk.importMainWithBody('<stdin>', false, code, true), {
|
||||
"*": () => {
|
||||
if (StopExecution) throw "Execution interrupted"
|
||||
}
|
||||
})
|
||||
.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 (fitAddon) {
|
||||
fitAddon.fit();
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user