blockly workspace now saves to device, loading still having issues
parent
fd6d061d36
commit
83c4988a2c
|
|
@ -28,6 +28,12 @@
|
||||||
<button id="btn-save" title="Save code to device as main.py" disabled>
|
<button id="btn-save" title="Save code to device as main.py" disabled>
|
||||||
<span class="icon">💾</span> Save
|
<span class="icon">💾</span> Save
|
||||||
</button>
|
</button>
|
||||||
|
<button id="btn-save-workspace" title="Save Blockly workspace to device" disabled>
|
||||||
|
<span class="icon">💾</span> Save WS
|
||||||
|
</button>
|
||||||
|
<button id="btn-load-workspace" title="Load Blockly workspace from device" disabled>
|
||||||
|
<span class="icon">📂</span> Load WS
|
||||||
|
</button>
|
||||||
<span id="connection-status" class="status-disconnected">Disconnected</span>
|
<span id="connection-status" class="status-disconnected">Disconnected</span>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
||||||
157
src/main.js
157
src/main.js
|
|
@ -4,7 +4,7 @@ import './blocks/esp32_blocks.js';
|
||||||
import './blocks/esp32_generators.js';
|
import './blocks/esp32_generators.js';
|
||||||
import { toolbox } from './blocks/toolbox.js';
|
import { toolbox } from './blocks/toolbox.js';
|
||||||
import { connect, disconnect, isConnected, onData, writeString } from './serial/connection.js';
|
import { connect, disconnect, isConnected, onData, writeString } from './serial/connection.js';
|
||||||
import { executeCode, stopExecution, saveToDevice } from './serial/repl.js';
|
import { executeCode, stopExecution, saveToDevice, writeFileToDevice } from './serial/repl.js';
|
||||||
import { flashFirmware } from './serial/flasher.js';
|
import { flashFirmware } from './serial/flasher.js';
|
||||||
import { appendToTerminal, clearTerminal } from './ui/terminal.js';
|
import { appendToTerminal, clearTerminal } from './ui/terminal.js';
|
||||||
import { initResizablePanels } from './ui/panels.js';
|
import { initResizablePanels } from './ui/panels.js';
|
||||||
|
|
@ -86,6 +86,8 @@ const btnFlash = document.getElementById('btn-flash');
|
||||||
const btnRun = document.getElementById('btn-run');
|
const btnRun = document.getElementById('btn-run');
|
||||||
const btnStop = document.getElementById('btn-stop');
|
const btnStop = document.getElementById('btn-stop');
|
||||||
const btnSave = document.getElementById('btn-save');
|
const btnSave = document.getElementById('btn-save');
|
||||||
|
const btnSaveWorkspace = document.getElementById('btn-save-workspace');
|
||||||
|
const btnLoadWorkspace = document.getElementById('btn-load-workspace');
|
||||||
const statusEl = document.getElementById('connection-status');
|
const statusEl = document.getElementById('connection-status');
|
||||||
const terminalInput = document.getElementById('terminal-input');
|
const terminalInput = document.getElementById('terminal-input');
|
||||||
|
|
||||||
|
|
@ -94,6 +96,8 @@ function setConnectedUI(connected) {
|
||||||
btnRun.disabled = !connected;
|
btnRun.disabled = !connected;
|
||||||
btnStop.disabled = !connected;
|
btnStop.disabled = !connected;
|
||||||
btnSave.disabled = !connected;
|
btnSave.disabled = !connected;
|
||||||
|
btnSaveWorkspace.disabled = !connected;
|
||||||
|
btnLoadWorkspace.disabled = !connected;
|
||||||
terminalInput.disabled = !connected;
|
terminalInput.disabled = !connected;
|
||||||
statusEl.textContent = connected ? 'Connected' : 'Disconnected';
|
statusEl.textContent = connected ? 'Connected' : 'Disconnected';
|
||||||
statusEl.className = connected ? 'status-connected' : 'status-disconnected';
|
statusEl.className = connected ? 'status-connected' : 'status-disconnected';
|
||||||
|
|
@ -101,7 +105,87 @@ function setConnectedUI(connected) {
|
||||||
|
|
||||||
// ─── Serial Event Listeners ──────────────────────────────
|
// ─── Serial Event Listeners ──────────────────────────────
|
||||||
|
|
||||||
onData((text) => appendToTerminal(text));
|
// Workspace loading state
|
||||||
|
let workspaceCaptureState = null;
|
||||||
|
|
||||||
|
onData((text) => {
|
||||||
|
// Check if we're capturing workspace XML
|
||||||
|
if (workspaceCaptureState) {
|
||||||
|
const { startMarker, endMarker } = workspaceCaptureState;
|
||||||
|
const startIdx = text.indexOf(startMarker);
|
||||||
|
const endIdx = text.indexOf(endMarker);
|
||||||
|
|
||||||
|
// PRIORITY 1: If already capturing, check for end marker first
|
||||||
|
if (workspaceCaptureState.capturing) {
|
||||||
|
if (endIdx !== -1) {
|
||||||
|
// Found end marker - extract content
|
||||||
|
workspaceCaptureState.buffer += text.substring(0, endIdx);
|
||||||
|
const xmlContent = workspaceCaptureState.buffer.trim();
|
||||||
|
|
||||||
|
// Parse and load XML
|
||||||
|
try {
|
||||||
|
const xmlDom = Blockly.Xml.textToDom(xmlContent);
|
||||||
|
Blockly.Xml.domToWorkspace(xmlDom, workspace);
|
||||||
|
appendToTerminal('Workspace loaded successfully!\n');
|
||||||
|
} catch (parseErr) {
|
||||||
|
appendToTerminal(`\nParse error: ${parseErr.message}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
workspaceCaptureState = null;
|
||||||
|
|
||||||
|
// Don't display the end marker, but show text after it
|
||||||
|
const afterEnd = text.substring(endIdx + endMarker.length);
|
||||||
|
if (afterEnd.trim()) {
|
||||||
|
appendToTerminal(afterEnd);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// Still capturing, accumulate buffer
|
||||||
|
workspaceCaptureState.buffer += text;
|
||||||
|
// Don't display captured content
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PRIORITY 2: Both markers in the same chunk (not yet capturing)
|
||||||
|
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
||||||
|
const xmlContent = text.substring(startIdx + startMarker.length, endIdx);
|
||||||
|
|
||||||
|
// Parse and load XML
|
||||||
|
try {
|
||||||
|
const xmlDom = Blockly.Xml.textToDom(xmlContent.trim());
|
||||||
|
Blockly.Xml.domToWorkspace(xmlDom, workspace);
|
||||||
|
appendToTerminal('Workspace loaded successfully!\n');
|
||||||
|
} catch (parseErr) {
|
||||||
|
appendToTerminal(`\nParse error: ${parseErr.message}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
workspaceCaptureState = null;
|
||||||
|
|
||||||
|
// Don't display the markers, but show text before/after
|
||||||
|
const beforeStart = text.substring(0, startIdx);
|
||||||
|
const afterEnd = text.substring(endIdx + endMarker.length);
|
||||||
|
if (beforeStart.trim() || afterEnd.trim()) {
|
||||||
|
appendToTerminal(beforeStart + afterEnd);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PRIORITY 3: Start marker found, start capturing
|
||||||
|
if (startIdx !== -1) {
|
||||||
|
workspaceCaptureState.capturing = true;
|
||||||
|
workspaceCaptureState.buffer = text.substring(startIdx + startMarker.length);
|
||||||
|
// Don't display the start marker or content after it
|
||||||
|
const beforeStart = text.substring(0, startIdx);
|
||||||
|
if (beforeStart.trim()) {
|
||||||
|
appendToTerminal(beforeStart);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
appendToTerminal(text);
|
||||||
|
});
|
||||||
|
|
||||||
// ─── Toolbar Buttons ─────────────────────────────────────
|
// ─── Toolbar Buttons ─────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -208,6 +292,75 @@ btnSave.addEventListener('click', async () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ─── Workspace Save/Load ───────────────────────────────────
|
||||||
|
|
||||||
|
async function saveWorkspaceToDevice() {
|
||||||
|
try {
|
||||||
|
// Convert workspace to XML
|
||||||
|
const xml = Blockly.Xml.workspaceToDom(workspace);
|
||||||
|
const xmlText = Blockly.Xml.domToText(xml);
|
||||||
|
|
||||||
|
appendToTerminal('\nSaving workspace to device...\n');
|
||||||
|
await writeFileToDevice(xmlText, 'workspace.xml');
|
||||||
|
appendToTerminal('Workspace saved to workspace.xml\n');
|
||||||
|
} catch (err) {
|
||||||
|
appendToTerminal(`\nSave workspace error: ${err.message}\n`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadWorkspaceFromDevice() {
|
||||||
|
try {
|
||||||
|
appendToTerminal('\nLoading workspace from device...\n');
|
||||||
|
|
||||||
|
// Use unique markers to identify workspace content
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const startMarker = `__WS_START_${timestamp}__`;
|
||||||
|
const endMarker = `__WS_END_${timestamp}__`;
|
||||||
|
|
||||||
|
// Set up capture state
|
||||||
|
workspaceCaptureState = {
|
||||||
|
startMarker,
|
||||||
|
endMarker,
|
||||||
|
buffer: '',
|
||||||
|
capturing: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const script = [
|
||||||
|
`try:`,
|
||||||
|
` f = open('workspace.xml', 'r')`,
|
||||||
|
` data = f.read()`,
|
||||||
|
` f.close()`,
|
||||||
|
` print('${startMarker}')`,
|
||||||
|
` print(data, end='')`,
|
||||||
|
` print('${endMarker}')`,
|
||||||
|
`except Exception as e:`,
|
||||||
|
` print('Error reading workspace.xml: ' + str(e))`,
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
await executeCode(script);
|
||||||
|
|
||||||
|
// Clean up capture state after timeout
|
||||||
|
setTimeout(() => {
|
||||||
|
if (workspaceCaptureState) {
|
||||||
|
appendToTerminal('\nTimeout waiting for workspace data\n');
|
||||||
|
workspaceCaptureState = null;
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
appendToTerminal(`\nLoad workspace error: ${err.message}\n`);
|
||||||
|
workspaceCaptureState = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
btnSaveWorkspace.addEventListener('click', async () => {
|
||||||
|
await saveWorkspaceToDevice();
|
||||||
|
});
|
||||||
|
|
||||||
|
btnLoadWorkspace.addEventListener('click', async () => {
|
||||||
|
await loadWorkspaceFromDevice();
|
||||||
|
});
|
||||||
|
|
||||||
terminalInput.addEventListener('keydown', async (e) => {
|
terminalInput.addEventListener('keydown', async (e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
const text = terminalInput.value;
|
const text = terminalInput.value;
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,73 @@ export async function saveToDevice(code, filename = 'main.py') {
|
||||||
await executeCode(script);
|
await executeCode(script);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function writeFileToDevice(content, filename) {
|
||||||
|
// Escape the content properly for Python string - handle newlines, quotes, backslashes
|
||||||
|
const escaped = content
|
||||||
|
.replace(/\\/g, '\\\\')
|
||||||
|
.replace(/'/g, "\\'")
|
||||||
|
.replace(/\n/g, '\\n')
|
||||||
|
.replace(/\r/g, '\\r');
|
||||||
|
|
||||||
|
const script = [
|
||||||
|
`f = open('${filename}', 'w')`,
|
||||||
|
`f.write('${escaped}')`,
|
||||||
|
`f.close()`,
|
||||||
|
`print('Saved ${filename}')`,
|
||||||
|
].join('\n');
|
||||||
|
await executeCode(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function readFileFromDevice(filename, onOutput) {
|
||||||
|
// Read file and print with markers so we can extract content
|
||||||
|
const startMarker = `__WS_START_${Date.now()}__`;
|
||||||
|
const endMarker = `__WS_END_${Date.now()}__`;
|
||||||
|
|
||||||
|
const script = [
|
||||||
|
`try:`,
|
||||||
|
` f = open('${filename}', 'r')`,
|
||||||
|
` data = f.read()`,
|
||||||
|
` f.close()`,
|
||||||
|
` print('${startMarker}')`,
|
||||||
|
` print(data, end='')`,
|
||||||
|
` print('${endMarker}')`,
|
||||||
|
`except Exception as e:`,
|
||||||
|
` print('Error: ' + str(e))`,
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
// Set up a listener to capture output between markers
|
||||||
|
let captured = '';
|
||||||
|
let capturing = false;
|
||||||
|
|
||||||
|
const listener = (text) => {
|
||||||
|
if (!onOutput) return;
|
||||||
|
|
||||||
|
const startIdx = text.indexOf(startMarker);
|
||||||
|
const endIdx = text.indexOf(endMarker);
|
||||||
|
|
||||||
|
if (startIdx !== -1) {
|
||||||
|
capturing = true;
|
||||||
|
captured = text.substring(startIdx + startMarker.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (capturing) {
|
||||||
|
if (endIdx !== -1) {
|
||||||
|
captured += text.substring(0, endIdx);
|
||||||
|
capturing = false;
|
||||||
|
onOutput(captured);
|
||||||
|
} else {
|
||||||
|
captured += text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// This will be handled by the caller setting up the listener
|
||||||
|
// For now, just execute and let caller handle output
|
||||||
|
await executeCode(script);
|
||||||
|
|
||||||
|
return { startMarker, endMarker };
|
||||||
|
}
|
||||||
|
|
||||||
function sleep(ms) {
|
function sleep(ms) {
|
||||||
return new Promise((r) => setTimeout(r, ms));
|
return new Promise((r) => setTimeout(r, ms));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue