Compare commits
13 Commits
5a3da96b58
...
prod
| Author | SHA1 | Date | |
|---|---|---|---|
| c9d19538da | |||
| 4ecdd3a419 | |||
| 0d3d6d5abf | |||
| bf7570055e | |||
| ab164256df | |||
| 09d60b29b3 | |||
| 175b6a19e0 | |||
| c7c5a95a04 | |||
| d4ab56171d | |||
| 72b7caa57f | |||
| ec720711d4 | |||
| c072581e94 | |||
| 097c729df2 |
@@ -1,5 +1,6 @@
|
||||
name: FTP Upload on Push
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- prod
|
||||
@@ -14,7 +15,7 @@ jobs:
|
||||
- name: Upload specific files via FTP
|
||||
shell: bash
|
||||
run: |
|
||||
FILES=("index.html" "screen.js")
|
||||
FILES=("index.html" "screen.js" "codestorage/default.php")
|
||||
|
||||
for FILE in "${FILES[@]}"; do
|
||||
echo "Uploading $FILE..."
|
||||
|
||||
18
.gitea/workflows/release.yaml
Normal file
18
.gitea/workflows/release.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
name: FTP Upload on Push
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- prod
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Add screen.js to releases
|
||||
uses: https://git.haaxman.co.uk/actions/gitea-release-action@v1
|
||||
with:
|
||||
files: screen.js
|
||||
93
codestorage/default.php
Normal file
93
codestorage/default.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
require_once 'secrets.php';
|
||||
|
||||
|
||||
$host = DB_HOST;
|
||||
$db_name = DB_NAME;
|
||||
$username = DB_USER;
|
||||
$password = DB_PASS;
|
||||
|
||||
try {
|
||||
$pdo = new PDO("mysql:host=$host;dbname=$db_name;charset=utf8mb4", $username, $password, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]);
|
||||
|
||||
$createTableSql = "
|
||||
CREATE TABLE IF NOT EXISTS codeData (
|
||||
id BIGINT PRIMARY KEY,
|
||||
data LONGTEXT NOT NULL
|
||||
) ENGINE=InnoDB;
|
||||
";
|
||||
$pdo->exec($createTableSql);
|
||||
|
||||
// 3. Handle Request Methods
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
if ($method === 'GET') {
|
||||
// --- READ LOGIC ---
|
||||
if (isset($_GET['id'])) {
|
||||
$id = (int) $_GET['id'];
|
||||
|
||||
$stmt = $pdo->prepare("SELECT data FROM codeData WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$row = $stmt->fetch();
|
||||
|
||||
if ($row) {
|
||||
header('Content-Type: text/plain');
|
||||
echo $row['data'];
|
||||
} else {
|
||||
http_response_code(404);
|
||||
echo "Error: Record with ID $id not found.";
|
||||
}
|
||||
} else {
|
||||
http_response_code(400);
|
||||
echo "Error: Missing 'id' parameter in query string.";
|
||||
}
|
||||
|
||||
} elseif ($method === 'POST') {
|
||||
// --- WRITE LOGIC ---
|
||||
// Get raw POST body
|
||||
$inputData = file_get_contents('php://input');
|
||||
|
||||
if (!empty($inputData)) {
|
||||
$idGenerated = false;
|
||||
$newId = 0;
|
||||
|
||||
// Generate a unique random ID and ensure it doesn't collide
|
||||
while (!$idGenerated) {
|
||||
$newId = mt_rand(100000, 999999999); // Range for the random ID
|
||||
|
||||
// Check if ID exists
|
||||
$checkStmt = $pdo->prepare("SELECT id FROM codeData WHERE id = ?");
|
||||
$checkStmt->execute([$newId]);
|
||||
if (!$checkStmt->fetch()) {
|
||||
$idGenerated = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the new row
|
||||
$insertStmt = $pdo->prepare("INSERT INTO codeData (id, data) VALUES (?, ?)");
|
||||
$insertStmt->execute([$newId, $inputData]);
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['id' => $newId, 'status' => 'success']);
|
||||
} else {
|
||||
http_response_code(400);
|
||||
echo "Error: POST body is empty.";
|
||||
}
|
||||
|
||||
} else {
|
||||
// Unsupported Method
|
||||
http_response_code(405);
|
||||
echo "Error: Method $method not allowed.";
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
// Handle connection or query errors
|
||||
http_response_code(500);
|
||||
echo "Database Error: " . $e->getMessage();
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
360
editor.html
360
editor.html
@@ -1,360 +0,0 @@
|
||||
<!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>
|
||||
766
index.html
Normal file
766
index.html
Normal file
@@ -0,0 +1,766 @@
|
||||
<!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;
|
||||
transition: width 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
#editor-container.collapsed {
|
||||
width: 0;
|
||||
transform: translateX(-100%);
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
#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;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
#preview-container.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
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%;
|
||||
}
|
||||
|
||||
/* Share Modal */
|
||||
.modal-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
z-index: 1000;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-overlay.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: #2c292d;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
color: #f0f0f0;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.link-container {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.link-input {
|
||||
flex: 1;
|
||||
padding: 10px 12px;
|
||||
background-color: #1e1e1e;
|
||||
border: 1px solid #444;
|
||||
border-radius: 4px;
|
||||
color: #f0f0f0;
|
||||
font-family: 'Consolas', monospace;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
padding: 10px 20px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.copy-btn.copied {
|
||||
background-color: #2196F3;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
padding: 10px 20px;
|
||||
background-color: #555;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
color: #aaa;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
color: #ff6b6b;
|
||||
margin-top: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<header>
|
||||
<div class="header-left">
|
||||
<button class="label-btn" title="PyStudio">
|
||||
<p>PyStudio</p>
|
||||
</button>
|
||||
<button id="runButton" 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>
|
||||
<button class="icon-btn" id="toggleEditorBtn" title="Toggle Code Editor" onclick="toggleEditorLayout()">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M4 4h16v16H4V4zm2 2v12h12V6H6zm2 2h4v8H8V8z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<button class="icon-btn" title="Share Code" onclick="shareCode()">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button class="icon-btn" title="Open File" onclick="openFile()">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button class="icon-btn" title="Save File (Ctrl+S)" onclick="saveFile()">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z" />
|
||||
</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>
|
||||
|
||||
<!-- Share Modal -->
|
||||
<div id="shareModal" class="modal-overlay">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">Share Your Code</div>
|
||||
<div class="modal-body">
|
||||
<div id="modalMessage">
|
||||
<p class="loading-text">Uploading code...</p>
|
||||
</div>
|
||||
<div id="linkSection" style="display: none;">
|
||||
<p>Your code has been shared! Use this link:</p>
|
||||
<div class="link-container">
|
||||
<input type="text" id="shareLink" class="link-input" readonly>
|
||||
<button class="copy-btn" onclick="copyShareLink()">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="errorSection" style="display: none;">
|
||||
<p class="error-text" id="errorMessage"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="close-btn" onclick="closeShareModal()">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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();
|
||||
|
||||
document.getElementById('runButton').blur();
|
||||
document.querySelector('canvas').focus();
|
||||
|
||||
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 toggleEditorLayout() {
|
||||
const editorContainer = document.getElementById('editor-container');
|
||||
const previewContainer = document.getElementById('preview-container');
|
||||
const toggleBtn = document.getElementById('toggleEditorBtn');
|
||||
|
||||
editorContainer.classList.toggle('collapsed');
|
||||
previewContainer.classList.toggle('full-width');
|
||||
|
||||
// Toggle icon/title
|
||||
if (editorContainer.classList.contains('collapsed')) {
|
||||
toggleBtn.title = "Show Code Editor";
|
||||
toggleBtn.innerHTML = `<svg viewBox="0 0 24 24"><path d="M4 4h16v16H4V4zm2 2v12h12V6H6zm10 2h-4v8h4V8z" /></svg>`;
|
||||
} else {
|
||||
toggleBtn.title = "Hide Code Editor";
|
||||
toggleBtn.innerHTML = `<svg viewBox="0 0 24 24"><path d="M4 4h16v16H4V4zm2 2v12h12V6H6zm2 2h4v8H8V8z" /></svg>`;
|
||||
}
|
||||
|
||||
// Trigger resize after transition
|
||||
setTimeout(() => {
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
}, 300);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// File System Access API support detection
|
||||
let currentFileHandle = null;
|
||||
|
||||
async function saveFile() {
|
||||
try {
|
||||
// Check if File System Access API is supported
|
||||
if ('showSaveFilePicker' in window) {
|
||||
// Use existing file handle if available, otherwise prompt for new file
|
||||
if (!currentFileHandle) {
|
||||
const options = {
|
||||
types: [{
|
||||
description: 'Python Files',
|
||||
accept: { 'text/x-python': ['.py'] },
|
||||
}],
|
||||
suggestedName: 'sketch.py'
|
||||
};
|
||||
currentFileHandle = await window.showSaveFilePicker(options);
|
||||
}
|
||||
|
||||
const writable = await currentFileHandle.createWritable();
|
||||
await writable.write(codeEditor.getValue());
|
||||
await writable.close();
|
||||
|
||||
term.write('\r\n✓ File saved successfully\r\n');
|
||||
} else {
|
||||
// Fallback to download for browsers that don't support File System Access API
|
||||
downloadCode();
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.name !== 'AbortError') {
|
||||
console.error('Save failed:', err);
|
||||
term.write('\r\n✗ Save failed: ' + err.message + '\r\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function openFile() {
|
||||
try {
|
||||
if ('showOpenFilePicker' in window) {
|
||||
const [fileHandle] = await window.showOpenFilePicker({
|
||||
types: [{
|
||||
description: 'Python Files',
|
||||
accept: { 'text/x-python': ['.py'] },
|
||||
}],
|
||||
multiple: false
|
||||
});
|
||||
|
||||
currentFileHandle = fileHandle;
|
||||
const file = await fileHandle.getFile();
|
||||
const contents = await file.text();
|
||||
codeEditor.setValue(contents, -1);
|
||||
|
||||
term.write('\r\n✓ File opened: ' + file.name + '\r\n');
|
||||
} else {
|
||||
// Fallback to file input for browsers that don't support File System Access API
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.py';
|
||||
input.onchange = async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
const contents = await file.text();
|
||||
codeEditor.setValue(contents, -1);
|
||||
term.write('\r\n✓ File opened: ' + file.name + '\r\n');
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.name !== 'AbortError') {
|
||||
console.error('Open failed:', err);
|
||||
term.write('\r\n✗ Open failed: ' + err.message + '\r\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function downloadCode() {
|
||||
const code = codeEditor.getValue();
|
||||
const blob = new Blob([code], { type: 'text/x-python' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = 'sketch.py';
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
term.write('\r\n✓ File downloaded\r\n');
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', function (e) {
|
||||
// Ctrl+Enter or Cmd+Enter to run code
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
runCurrentCode();
|
||||
}
|
||||
|
||||
// Ctrl+S or Cmd+S to save file
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
|
||||
e.preventDefault();
|
||||
saveFile();
|
||||
}
|
||||
|
||||
// Ctrl+O or Cmd+O to open file
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'o') {
|
||||
e.preventDefault();
|
||||
openFile();
|
||||
}
|
||||
});
|
||||
|
||||
async function shareCode() {
|
||||
const modal = document.getElementById('shareModal');
|
||||
const modalMessage = document.getElementById('modalMessage');
|
||||
const linkSection = document.getElementById('linkSection');
|
||||
const errorSection = document.getElementById('errorSection');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
|
||||
modalMessage.style.display = 'block';
|
||||
modalMessage.innerHTML = '<p class="loading-text">Uploading code...</p>';
|
||||
linkSection.style.display = 'none';
|
||||
errorSection.style.display = 'none';
|
||||
|
||||
modal.classList.add('active');
|
||||
|
||||
try {
|
||||
const code = codeEditor.getValue();
|
||||
|
||||
const response = await fetch('/codestorage/default.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain'
|
||||
},
|
||||
body: code
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.status === 'success' && data.id) {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set('id', data.id);
|
||||
const shareableLink = url.toString();
|
||||
|
||||
modalMessage.style.display = 'none';
|
||||
linkSection.style.display = 'block';
|
||||
document.getElementById('shareLink').value = shareableLink;
|
||||
|
||||
term.write('\r\n✓ Code shared successfully! ID: ' + data.id + '\r\n');
|
||||
} else {
|
||||
throw new Error('Invalid response from server');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Share failed:', error);
|
||||
modalMessage.style.display = 'none';
|
||||
errorSection.style.display = 'block';
|
||||
errorMessage.textContent = 'Failed to share code: ' + error.message;
|
||||
term.write('\r\n✗ Share failed: ' + error.message + '\r\n');
|
||||
}
|
||||
}
|
||||
|
||||
function copyShareLink() {
|
||||
const linkInput = document.getElementById('shareLink');
|
||||
const copyBtn = event.target;
|
||||
|
||||
linkInput.select();
|
||||
linkInput.setSelectionRange(0, 99999);
|
||||
|
||||
navigator.clipboard.writeText(linkInput.value).then(() => {
|
||||
const originalText = copyBtn.textContent;
|
||||
copyBtn.textContent = 'Copied!';
|
||||
copyBtn.classList.add('copied');
|
||||
|
||||
setTimeout(() => {
|
||||
copyBtn.textContent = originalText;
|
||||
copyBtn.classList.remove('copied');
|
||||
}, 2000);
|
||||
|
||||
term.write('\r\n✓ Link copied to clipboard\r\n');
|
||||
}).catch(err => {
|
||||
console.error('Copy failed:', err);
|
||||
term.write('\r\n✗ Failed to copy link\r\n');
|
||||
});
|
||||
}
|
||||
|
||||
function closeShareModal() {
|
||||
const modal = document.getElementById('shareModal');
|
||||
modal.classList.remove('active');
|
||||
}
|
||||
|
||||
document.getElementById('shareModal').addEventListener('click', function (e) {
|
||||
if (e.target === this) {
|
||||
closeShareModal();
|
||||
}
|
||||
});
|
||||
|
||||
async function loadSharedCode() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const id = urlParams.get('id');
|
||||
|
||||
if (id) {
|
||||
try {
|
||||
const response = await fetch(`/codestorage/default.php?id=${id}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const code = await response.text();
|
||||
codeEditor.setValue(code, -1);
|
||||
|
||||
term.write('\r\n✓ Loaded shared code (ID: ' + id + ')\r\n');
|
||||
} catch (error) {
|
||||
console.error('Failed to load shared code:', error);
|
||||
term.write('\r\n✗ Failed to load shared code: ' + error.message + '\r\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
loadSharedCode().then(() => {
|
||||
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>
|
||||
13
screen.js
13
screen.js
@@ -223,6 +223,7 @@ var $builtinmodule = function (name) {
|
||||
mod.circ = new Sk.builtin.func((x, y, r, c) => { circ(toJS(x), toJS(y), toJS(r), c !== undefined ? toJS(c) : 0); return Sk.builtin.none.none$; });
|
||||
mod.text = new Sk.builtin.func((x, y, t, c, s) => { text(toJS(x), toJS(y), toJS(t), c !== undefined ? toJS(c) : 3, s !== undefined ? toJS(s) : 'normal'); return Sk.builtin.none.none$; });
|
||||
mod.textsize = new Sk.builtin.func((s) => { textsize(toJS(s)); return Sk.builtin.none.none$; });
|
||||
mod.textalign = new Sk.builtin.func((h, v) => { textalign(toJS(h), v !== undefined ? toJS(v) : undefined); return Sk.builtin.none.none$; });
|
||||
|
||||
mod.ovalfill = new Sk.builtin.func((x, y, rx, ry, c) => { ovalfill(toJS(x), toJS(y), toJS(rx), toJS(ry), c !== undefined ? toJS(c) : 0); return Sk.builtin.none.none$; });
|
||||
mod.oval = new Sk.builtin.func((x, y, rx, ry, c) => { oval(toJS(x), toJS(y), toJS(rx), toJS(ry), c !== undefined ? toJS(c) : 0); return Sk.builtin.none.none$; });
|
||||
@@ -249,6 +250,18 @@ var $builtinmodule = function (name) {
|
||||
mod.sqrt = new Sk.builtin.func((n) => toPy(Math.sqrt(toJS(n))));
|
||||
mod.pause = new Sk.builtin.func(() => { pause(); return Sk.builtin.none.none$; });
|
||||
mod.resume = new Sk.builtin.func(() => { resume(); return Sk.builtin.none.none$; });
|
||||
mod.pal = new Sk.builtin.func((colors, textColor) => {
|
||||
pal(colors !== undefined ? toJS(colors) : undefined, textColor !== undefined ? toJS(textColor) : 3);
|
||||
return Sk.builtin.none.none$;
|
||||
});
|
||||
mod.palc = new Sk.builtin.func((a, b) => {
|
||||
palc(a !== undefined ? toJS(a) : undefined, b !== undefined ? toJS(b) : undefined);
|
||||
return Sk.builtin.none.none$;
|
||||
});
|
||||
mod.paint = new Sk.builtin.func((width, height, data, options) => {
|
||||
var result = paint(toJS(width), toJS(height), toJS(data), options !== undefined ? toJS(options) : undefined);
|
||||
return result ? toPy(result) : Sk.builtin.none.none$;
|
||||
});
|
||||
|
||||
return mod;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user