diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml
index e2b0e41..fcfb44a 100644
--- a/.gitea/workflows/deploy.yaml
+++ b/.gitea/workflows/deploy.yaml
@@ -15,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..."
diff --git a/codestorage/default.php b/codestorage/default.php
new file mode 100644
index 0000000..69eb1e1
--- /dev/null
+++ b/codestorage/default.php
@@ -0,0 +1,93 @@
+ 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;
+}
+?>
\ No newline at end of file
diff --git a/index.html b/index.html
index 42ab3c3..18f054d 100644
--- a/index.html
+++ b/index.html
@@ -183,6 +183,19 @@
+
@@ -331,11 +344,112 @@
}
}
+ // 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();
+ }
});
window.addEventListener('load', () => {