esp32tool 1.6.1 → 1.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apple-touch-icon.png +0 -0
- package/dist/esp_loader.js +20 -32
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/partition.d.ts +0 -4
- package/dist/partition.js +0 -7
- package/dist/web/index.js +1 -1
- package/electron/main.cjs +78 -83
- package/icons/icon-128.png +0 -0
- package/icons/icon-144.png +0 -0
- package/icons/icon-152.png +0 -0
- package/icons/icon-192.png +0 -0
- package/icons/icon-384.png +0 -0
- package/icons/icon-512.png +0 -0
- package/icons/icon-72.png +0 -0
- package/icons/icon-96.png +0 -0
- package/js/modules/esptool.js +1 -1
- package/js/script.js +30 -9
- package/package.json +10 -8
- package/screenshots/desktop.png +0 -0
- package/screenshots/mobile.png +0 -0
- package/src/esp_loader.ts +20 -32
- package/src/index.ts +1 -5
- package/src/partition.ts +0 -8
- package/sw.js +1 -1
- package/electron/main.js +0 -338
package/js/script.js
CHANGED
|
@@ -1986,8 +1986,18 @@ async function clickNVSEditor() {
|
|
|
1986
1986
|
nvsEditorInstance.initProgressUI();
|
|
1987
1987
|
nvsEditorInstance.showProgress('Reading partition table...', 0);
|
|
1988
1988
|
|
|
1989
|
-
const
|
|
1990
|
-
|
|
1989
|
+
const MAX_PT_ATTEMPTS = 10;
|
|
1990
|
+
let partitions = [];
|
|
1991
|
+
for (let attempt = 0; attempt < MAX_PT_ATTEMPTS; attempt++) {
|
|
1992
|
+
const offset = PARTITION_TABLE_OFFSET + attempt * 0x1000;
|
|
1993
|
+
logMsg(`Reading partition table from 0x${offset.toString(16)}...`);
|
|
1994
|
+
const ptData = await espStub.readFlash(offset, PARTITION_TABLE_SIZE);
|
|
1995
|
+
partitions = parsePartitionTable(ptData);
|
|
1996
|
+
if (partitions.length > 0) break;
|
|
1997
|
+
}
|
|
1998
|
+
if (partitions.length === 0) {
|
|
1999
|
+
throw new Error('No valid partition table found after ' + MAX_PT_ATTEMPTS + ' attempts');
|
|
2000
|
+
}
|
|
1991
2001
|
|
|
1992
2002
|
// Step 2: Find NVS partition (type=0x01 data, subtype=0x02 nvs)
|
|
1993
2003
|
const nvsPartition = partitions.find(p => p.type === 0x01 && p.subtype === 0x02);
|
|
@@ -2107,17 +2117,28 @@ async function clickReadPartitions() {
|
|
|
2107
2117
|
butReadFlash.disabled = true;
|
|
2108
2118
|
|
|
2109
2119
|
try {
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2120
|
+
const MAX_ATTEMPTS = 10;
|
|
2121
|
+
let partitions = [];
|
|
2122
|
+
let foundOffset = null;
|
|
2123
|
+
|
|
2124
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
2125
|
+
const offset = PARTITION_TABLE_OFFSET + attempt * 0x1000;
|
|
2126
|
+
logMsg(`Reading partition table from 0x${offset.toString(16)}...`);
|
|
2127
|
+
const data = await espStub.readFlash(offset, PARTITION_TABLE_SIZE);
|
|
2128
|
+
partitions = parsePartitionTable(data);
|
|
2129
|
+
|
|
2130
|
+
if (partitions.length > 0) {
|
|
2131
|
+
foundOffset = offset;
|
|
2132
|
+
break;
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2115
2136
|
if (partitions.length === 0) {
|
|
2116
|
-
errorMsg("No valid partition table found");
|
|
2137
|
+
errorMsg("No valid partition table found after " + MAX_ATTEMPTS + " attempts");
|
|
2117
2138
|
return;
|
|
2118
2139
|
}
|
|
2119
2140
|
|
|
2120
|
-
logMsg(`Found ${partitions.length} partition(s)`);
|
|
2141
|
+
logMsg(`Found ${partitions.length} partition(s) at 0x${foundOffset.toString(16)}`);
|
|
2121
2142
|
|
|
2122
2143
|
// Display partitions
|
|
2123
2144
|
displayPartitions(partitions);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "esp32tool",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Flash & Read ESP devices using WebSerial, Electron, and also Android mobile via WebUSB",
|
|
6
6
|
"main": "electron/main.cjs",
|
|
@@ -49,27 +49,27 @@
|
|
|
49
49
|
"@electron-forge/maker-zip": "^7.11.1",
|
|
50
50
|
"@electron-forge/plugin-auto-unpack-natives": "^7.11.1",
|
|
51
51
|
"@electron/fuses": "^2.0.0",
|
|
52
|
-
"@eslint/js": "^9.39.
|
|
52
|
+
"@eslint/js": "^9.39.3",
|
|
53
53
|
"@rollup/plugin-json": "^6.1.0",
|
|
54
54
|
"@rollup/plugin-node-resolve": "^16.0.0",
|
|
55
55
|
"@rollup/plugin-terser": "^0.4.4",
|
|
56
56
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
57
|
-
"@types/node": "^25.
|
|
57
|
+
"@types/node": "^25.3.0",
|
|
58
58
|
"@types/pako": "^2.0.4",
|
|
59
59
|
"@types/serialport": "^10.2.0",
|
|
60
60
|
"@types/w3c-web-serial": "^1.0.7",
|
|
61
61
|
"archiver": "^7.0.1",
|
|
62
|
-
"electron": "^40.
|
|
62
|
+
"electron": "^40.6.0",
|
|
63
63
|
"electron-squirrel-startup": "^1.0.1",
|
|
64
|
-
"eslint": "^
|
|
64
|
+
"eslint": "^10.0.2",
|
|
65
65
|
"eslint-config-prettier": "^10.1.8",
|
|
66
66
|
"eslint-plugin-prettier": "^5.5.5",
|
|
67
67
|
"npm-run-all": "^4.1.5",
|
|
68
68
|
"prettier": "^3.8.1",
|
|
69
|
-
"rollup": "^4.
|
|
69
|
+
"rollup": "^4.59.0",
|
|
70
70
|
"serve": "^14.2.5",
|
|
71
71
|
"typescript": "^5.7.3",
|
|
72
|
-
"typescript-eslint": "^8.56.
|
|
72
|
+
"typescript-eslint": "^8.56.1"
|
|
73
73
|
},
|
|
74
74
|
"dependencies": {
|
|
75
75
|
"pako": "^2.1.0",
|
|
@@ -79,8 +79,10 @@
|
|
|
79
79
|
"overrides": {
|
|
80
80
|
"tmp": "^0.2.4",
|
|
81
81
|
"tar": "^7.5.6",
|
|
82
|
-
"ajv": "^8.18.0",
|
|
83
82
|
"minimatch": "^10.2.1",
|
|
83
|
+
"serve": {
|
|
84
|
+
"ajv": "^8.18.0"
|
|
85
|
+
},
|
|
84
86
|
"@electron/asar": "^4.0.1"
|
|
85
87
|
}
|
|
86
88
|
}
|
package/screenshots/desktop.png
CHANGED
|
Binary file
|
package/screenshots/mobile.png
CHANGED
|
Binary file
|
package/src/esp_loader.ts
CHANGED
|
@@ -1161,14 +1161,11 @@ export class ESPLoader extends EventTarget {
|
|
|
1161
1161
|
* Classic reset with shorter delays for WebUSB (Android)
|
|
1162
1162
|
*/
|
|
1163
1163
|
async hardResetClassicShortDelayWebUSB() {
|
|
1164
|
-
await this.
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
await sleep(25);
|
|
1170
|
-
await this.setDTRWebUSB(false); // IO0=HIGH, done
|
|
1171
|
-
await sleep(100);
|
|
1164
|
+
await this.runSignalSequence([
|
|
1165
|
+
{ dtr: false, rts: true, delayMs: 50 },
|
|
1166
|
+
{ dtr: true, rts: false, delayMs: 25 },
|
|
1167
|
+
{ dtr: false, delayMs: 100 },
|
|
1168
|
+
]);
|
|
1172
1169
|
}
|
|
1173
1170
|
|
|
1174
1171
|
/**
|
|
@@ -1176,14 +1173,11 @@ export class ESPLoader extends EventTarget {
|
|
|
1176
1173
|
* Inverted reset sequence for WebUSB (Android) - both signals inverted
|
|
1177
1174
|
*/
|
|
1178
1175
|
async hardResetInvertedWebUSB() {
|
|
1179
|
-
await this.
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
await sleep(50);
|
|
1185
|
-
await this.setDTRWebUSB(true); // IO0=HIGH, done (inverted)
|
|
1186
|
-
await sleep(200);
|
|
1176
|
+
await this.runSignalSequence([
|
|
1177
|
+
{ dtr: true, rts: false, delayMs: 100 },
|
|
1178
|
+
{ dtr: false, rts: true, delayMs: 50 },
|
|
1179
|
+
{ dtr: true, delayMs: 200 },
|
|
1180
|
+
]);
|
|
1187
1181
|
}
|
|
1188
1182
|
|
|
1189
1183
|
/**
|
|
@@ -1191,14 +1185,11 @@ export class ESPLoader extends EventTarget {
|
|
|
1191
1185
|
* Only DTR inverted for WebUSB (Android)
|
|
1192
1186
|
*/
|
|
1193
1187
|
async hardResetInvertedDTRWebUSB() {
|
|
1194
|
-
await this.
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
await sleep(50);
|
|
1200
|
-
await this.setDTRWebUSB(true); // IO0=HIGH, done (DTR inverted)
|
|
1201
|
-
await sleep(200);
|
|
1188
|
+
await this.runSignalSequence([
|
|
1189
|
+
{ dtr: true, rts: true, delayMs: 100 },
|
|
1190
|
+
{ dtr: false, rts: false, delayMs: 50 },
|
|
1191
|
+
{ dtr: true, delayMs: 200 },
|
|
1192
|
+
]);
|
|
1202
1193
|
}
|
|
1203
1194
|
|
|
1204
1195
|
/**
|
|
@@ -1206,14 +1197,11 @@ export class ESPLoader extends EventTarget {
|
|
|
1206
1197
|
* Only RTS inverted for WebUSB (Android)
|
|
1207
1198
|
*/
|
|
1208
1199
|
async hardResetInvertedRTSWebUSB() {
|
|
1209
|
-
await this.
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
await sleep(50);
|
|
1215
|
-
await this.setDTRWebUSB(false); // IO0=HIGH, done (DTR normal)
|
|
1216
|
-
await sleep(200);
|
|
1200
|
+
await this.runSignalSequence([
|
|
1201
|
+
{ dtr: false, rts: false, delayMs: 100 },
|
|
1202
|
+
{ dtr: true, rts: true, delayMs: 50 },
|
|
1203
|
+
{ dtr: false, delayMs: 200 },
|
|
1204
|
+
]);
|
|
1217
1205
|
}
|
|
1218
1206
|
|
|
1219
1207
|
/**
|
package/src/index.ts
CHANGED
|
@@ -100,11 +100,7 @@ export const connectWithPort = async (port: SerialPort, logger: Logger) => {
|
|
|
100
100
|
return new ESPLoader(port, logger);
|
|
101
101
|
};
|
|
102
102
|
|
|
103
|
-
export {
|
|
104
|
-
parsePartitionTable,
|
|
105
|
-
getPartitionTableOffset,
|
|
106
|
-
formatSize,
|
|
107
|
-
} from "./partition";
|
|
103
|
+
export { parsePartitionTable, formatSize } from "./partition";
|
|
108
104
|
export type { Partition } from "./partition";
|
|
109
105
|
|
|
110
106
|
// Export utility functions for use in UI code
|
package/src/partition.ts
CHANGED
|
@@ -58,7 +58,6 @@ const DATA_SUBTYPES: { [key: number]: string } = {
|
|
|
58
58
|
0x83: "littlefs",
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
-
const PARTITION_TABLE_OFFSET = 0x8000; // Default partition table offset
|
|
62
61
|
const PARTITION_ENTRY_SIZE = 32;
|
|
63
62
|
const PARTITION_MAGIC = 0x50aa;
|
|
64
63
|
|
|
@@ -137,13 +136,6 @@ export function parsePartitionTable(data: Uint8Array): Partition[] {
|
|
|
137
136
|
return partitions;
|
|
138
137
|
}
|
|
139
138
|
|
|
140
|
-
/**
|
|
141
|
-
* Get the default partition table offset
|
|
142
|
-
*/
|
|
143
|
-
export function getPartitionTableOffset(): number {
|
|
144
|
-
return PARTITION_TABLE_OFFSET;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
139
|
/**
|
|
148
140
|
* Format size in human-readable format
|
|
149
141
|
*/
|
package/sw.js
CHANGED
package/electron/main.js
DELETED
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
const { app, BrowserWindow, Menu, session, ipcMain, dialog } = require('electron');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
|
|
5
|
-
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
|
6
|
-
// Only required for Windows Squirrel installer
|
|
7
|
-
if (process.platform === 'win32') {
|
|
8
|
-
try {
|
|
9
|
-
if (require('electron-squirrel-startup')) {
|
|
10
|
-
app.quit();
|
|
11
|
-
}
|
|
12
|
-
} catch (e) {
|
|
13
|
-
// Module not available, ignore
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
let mainWindow;
|
|
18
|
-
|
|
19
|
-
// Store granted serial port devices
|
|
20
|
-
const grantedDevices = new Map();
|
|
21
|
-
|
|
22
|
-
function createWindow() {
|
|
23
|
-
mainWindow = new BrowserWindow({
|
|
24
|
-
width: 1200,
|
|
25
|
-
height: 800,
|
|
26
|
-
minWidth: 800,
|
|
27
|
-
minHeight: 600,
|
|
28
|
-
webPreferences: {
|
|
29
|
-
nodeIntegration: false,
|
|
30
|
-
contextIsolation: true,
|
|
31
|
-
preload: path.join(__dirname, 'preload.js'),
|
|
32
|
-
},
|
|
33
|
-
icon: path.join(__dirname, 'icons', 'icon.png'),
|
|
34
|
-
title: 'ESP32Tool',
|
|
35
|
-
autoHideMenuBar: false,
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// Load the index.html of the app
|
|
39
|
-
mainWindow.loadFile(path.join(__dirname, '..', 'index.html'));
|
|
40
|
-
|
|
41
|
-
// Open DevTools in development
|
|
42
|
-
if (process.env.NODE_ENV === 'development') {
|
|
43
|
-
mainWindow.webContents.openDevTools();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
mainWindow.on('closed', () => {
|
|
47
|
-
mainWindow = null;
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
// Setup serial port handlers for this window's session
|
|
51
|
-
setupSerialPortHandlers(mainWindow.webContents.session);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function setupSerialPortHandlers(ses) {
|
|
55
|
-
let lastSelectedPort = null;
|
|
56
|
-
let esp32s2ReconnectPending = false;
|
|
57
|
-
let portSelectionQueue = [];
|
|
58
|
-
|
|
59
|
-
// Handle serial port selection - shows when navigator.serial.requestPort() is called
|
|
60
|
-
ses.on('select-serial-port', (event, portList, webContents, callback) => {
|
|
61
|
-
event.preventDefault();
|
|
62
|
-
|
|
63
|
-
console.log('Available serial ports:', portList.map(p => ({
|
|
64
|
-
portId: p.portId,
|
|
65
|
-
portName: p.portName,
|
|
66
|
-
displayName: p.displayName
|
|
67
|
-
})));
|
|
68
|
-
|
|
69
|
-
if (portList && portList.length > 0) {
|
|
70
|
-
// Try to find ESP-compatible port
|
|
71
|
-
const espPort = portList.find(port => {
|
|
72
|
-
const name = (port.displayName || port.portName || '').toLowerCase();
|
|
73
|
-
return name.includes('cp2102') ||
|
|
74
|
-
name.includes('cp2103') ||
|
|
75
|
-
name.includes('cp2104') ||
|
|
76
|
-
name.includes('cp2105') ||
|
|
77
|
-
name.includes('cp2108') ||
|
|
78
|
-
name.includes('ch9102') ||
|
|
79
|
-
name.includes('ch9104') ||
|
|
80
|
-
name.includes('ch340') ||
|
|
81
|
-
name.includes('ch341') ||
|
|
82
|
-
name.includes('ch343') ||
|
|
83
|
-
name.includes('ftdi') ||
|
|
84
|
-
name.includes('usb') ||
|
|
85
|
-
name.includes('uart') ||
|
|
86
|
-
name.includes('silicon labs') ||
|
|
87
|
-
name.includes('esp');
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Select ESP-compatible port or first available
|
|
91
|
-
const selectedPort = espPort || portList[0];
|
|
92
|
-
console.log('Selected port:', selectedPort.portId, selectedPort.displayName || selectedPort.portName);
|
|
93
|
-
lastSelectedPort = selectedPort;
|
|
94
|
-
|
|
95
|
-
callback(selectedPort.portId);
|
|
96
|
-
} else {
|
|
97
|
-
console.log('No serial ports available - queuing selection');
|
|
98
|
-
// No ports available yet - queue this callback for when a port appears
|
|
99
|
-
portSelectionQueue.push(callback);
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
// Track port additions - handle ESP32-S2 reconnect
|
|
104
|
-
ses.on('serial-port-added', (event, port) => {
|
|
105
|
-
console.log('Serial port added:', port);
|
|
106
|
-
|
|
107
|
-
// If we have queued port selections, handle them now
|
|
108
|
-
if (portSelectionQueue.length > 0) {
|
|
109
|
-
console.log('Processing queued port selection');
|
|
110
|
-
const callback = portSelectionQueue.shift();
|
|
111
|
-
callback(port.portId);
|
|
112
|
-
lastSelectedPort = port;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Check if this looks like an ESP32-S2 CDC port appearing after ROM port disappeared
|
|
116
|
-
if (lastSelectedPort && port.portName !== lastSelectedPort.portName) {
|
|
117
|
-
const name = (port.displayName || port.portName || '').toLowerCase();
|
|
118
|
-
if (name.includes('esp') || name.includes('usb') || name.includes('uart')) {
|
|
119
|
-
console.log('ESP32-S2 reconnect detected - new CDC port available');
|
|
120
|
-
esp32s2ReconnectPending = true;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
// Track port removals - detect ESP32-S2 disconnect
|
|
126
|
-
ses.on('serial-port-removed', (event, port) => {
|
|
127
|
-
console.log('Serial port removed:', port);
|
|
128
|
-
|
|
129
|
-
// If the last selected port was removed, prepare for reconnect
|
|
130
|
-
if (lastSelectedPort && port.portId === lastSelectedPort.portId) {
|
|
131
|
-
console.log('Last selected port removed - may be ESP32-S2 mode switch');
|
|
132
|
-
// Don't clear lastSelectedPort yet, we might need it for comparison
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
// Grant permission for serial port access checks
|
|
137
|
-
ses.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {
|
|
138
|
-
if (permission === 'serial') {
|
|
139
|
-
return true;
|
|
140
|
-
}
|
|
141
|
-
return true;
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
// Handle device permission requests
|
|
145
|
-
ses.setDevicePermissionHandler((details) => {
|
|
146
|
-
if (details.deviceType === 'serial') {
|
|
147
|
-
if (details.device) {
|
|
148
|
-
grantedDevices.set(details.device.deviceId, details.device);
|
|
149
|
-
}
|
|
150
|
-
return true;
|
|
151
|
-
}
|
|
152
|
-
return true;
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Create application menu
|
|
157
|
-
function createMenu() {
|
|
158
|
-
const template = [
|
|
159
|
-
{
|
|
160
|
-
label: 'File',
|
|
161
|
-
submenu: [
|
|
162
|
-
{ role: 'quit' }
|
|
163
|
-
]
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
label: 'Edit',
|
|
167
|
-
submenu: [
|
|
168
|
-
{ role: 'undo' },
|
|
169
|
-
{ role: 'redo' },
|
|
170
|
-
{ type: 'separator' },
|
|
171
|
-
{ role: 'cut' },
|
|
172
|
-
{ role: 'copy' },
|
|
173
|
-
{ role: 'paste' },
|
|
174
|
-
{ role: 'selectAll' }
|
|
175
|
-
]
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
label: 'View',
|
|
179
|
-
submenu: [
|
|
180
|
-
{ role: 'reload' },
|
|
181
|
-
{ role: 'forceReload' },
|
|
182
|
-
{ role: 'toggleDevTools' },
|
|
183
|
-
{ type: 'separator' },
|
|
184
|
-
{ role: 'resetZoom' },
|
|
185
|
-
{ role: 'zoomIn' },
|
|
186
|
-
{ role: 'zoomOut' },
|
|
187
|
-
{ type: 'separator' },
|
|
188
|
-
{ role: 'togglefullscreen' }
|
|
189
|
-
]
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
label: 'Help',
|
|
193
|
-
submenu: [
|
|
194
|
-
{
|
|
195
|
-
label: 'About ESP32Tool',
|
|
196
|
-
click: async () => {
|
|
197
|
-
const { shell } = require('electron');
|
|
198
|
-
await shell.openExternal('https://github.com/Jason2866/esp32tool');
|
|
199
|
-
}
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
label: 'ESP32 Documentation',
|
|
203
|
-
click: async () => {
|
|
204
|
-
const { shell } = require('electron');
|
|
205
|
-
await shell.openExternal('https://docs.espressif.com/');
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
]
|
|
209
|
-
}
|
|
210
|
-
];
|
|
211
|
-
|
|
212
|
-
// macOS specific menu adjustments
|
|
213
|
-
if (process.platform === 'darwin') {
|
|
214
|
-
template.unshift({
|
|
215
|
-
label: app.getName(),
|
|
216
|
-
submenu: [
|
|
217
|
-
{ role: 'about' },
|
|
218
|
-
{ type: 'separator' },
|
|
219
|
-
{ role: 'services' },
|
|
220
|
-
{ type: 'separator' },
|
|
221
|
-
{ role: 'hide' },
|
|
222
|
-
{ role: 'hideOthers' },
|
|
223
|
-
{ role: 'unhide' },
|
|
224
|
-
{ type: 'separator' },
|
|
225
|
-
{ role: 'quit' }
|
|
226
|
-
]
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const menu = Menu.buildFromTemplate(template);
|
|
231
|
-
Menu.setApplicationMenu(menu);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// This method will be called when Electron has finished initialization
|
|
235
|
-
app.whenReady().then(() => {
|
|
236
|
-
createMenu();
|
|
237
|
-
createWindow();
|
|
238
|
-
|
|
239
|
-
app.on('activate', () => {
|
|
240
|
-
// On macOS re-create a window when dock icon is clicked and no windows are open
|
|
241
|
-
if (BrowserWindow.getAllWindows().length === 0) {
|
|
242
|
-
createWindow();
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
// Quit when all windows are closed, except on macOS
|
|
248
|
-
app.on('window-all-closed', () => {
|
|
249
|
-
if (process.platform !== 'darwin') {
|
|
250
|
-
app.quit();
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
// ============================================
|
|
255
|
-
// IPC Handlers for File Operations
|
|
256
|
-
// ============================================
|
|
257
|
-
|
|
258
|
-
// Save file dialog and write data
|
|
259
|
-
ipcMain.handle('save-file', async (event, { data, defaultFilename, filters }) => {
|
|
260
|
-
const result = await dialog.showSaveDialog(mainWindow, {
|
|
261
|
-
defaultPath: defaultFilename,
|
|
262
|
-
filters: filters || [
|
|
263
|
-
{ name: 'Binary Files', extensions: ['bin'] },
|
|
264
|
-
{ name: 'All Files', extensions: ['*'] }
|
|
265
|
-
]
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
if (result.canceled || !result.filePath) {
|
|
269
|
-
return { success: false, canceled: true };
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
try {
|
|
273
|
-
// Convert data to Buffer if it's a Uint8Array
|
|
274
|
-
const buffer = Buffer.from(data);
|
|
275
|
-
fs.writeFileSync(result.filePath, buffer);
|
|
276
|
-
return { success: true, filePath: result.filePath };
|
|
277
|
-
} catch (error) {
|
|
278
|
-
return { success: false, error: error.message };
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
// Open file dialog and read data
|
|
283
|
-
ipcMain.handle('open-file', async (event, { filters }) => {
|
|
284
|
-
const result = await dialog.showOpenDialog(mainWindow, {
|
|
285
|
-
properties: ['openFile'],
|
|
286
|
-
filters: filters || [
|
|
287
|
-
{ name: 'Binary Files', extensions: ['bin'] },
|
|
288
|
-
{ name: 'All Files', extensions: ['*'] }
|
|
289
|
-
]
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
if (result.canceled || result.filePaths.length === 0) {
|
|
293
|
-
return { success: false, canceled: true };
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
try {
|
|
297
|
-
const filePath = result.filePaths[0];
|
|
298
|
-
const data = fs.readFileSync(filePath);
|
|
299
|
-
const filename = path.basename(filePath);
|
|
300
|
-
return {
|
|
301
|
-
success: true,
|
|
302
|
-
filePath,
|
|
303
|
-
filename,
|
|
304
|
-
data: Array.from(data) // Convert Buffer to array for IPC transfer
|
|
305
|
-
};
|
|
306
|
-
} catch (error) {
|
|
307
|
-
return { success: false, error: error.message };
|
|
308
|
-
}
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
// Show input dialog (replacement for prompt())
|
|
312
|
-
ipcMain.handle('show-prompt', async (event, { message, defaultValue }) => {
|
|
313
|
-
// Use a simple approach - return the default value
|
|
314
|
-
// In Electron, we use the save dialog instead of prompt
|
|
315
|
-
return defaultValue;
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
// Show message box
|
|
319
|
-
ipcMain.handle('show-message', async (event, { type, title, message, buttons }) => {
|
|
320
|
-
const result = await dialog.showMessageBox(mainWindow, {
|
|
321
|
-
type: type || 'info',
|
|
322
|
-
title: title || 'ESP32Tool',
|
|
323
|
-
message: message,
|
|
324
|
-
buttons: buttons || ['OK']
|
|
325
|
-
});
|
|
326
|
-
return result.response;
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
// Show confirm dialog
|
|
330
|
-
ipcMain.handle('show-confirm', async (event, { message }) => {
|
|
331
|
-
const result = await dialog.showMessageBox(mainWindow, {
|
|
332
|
-
type: 'question',
|
|
333
|
-
title: 'Confirm',
|
|
334
|
-
message: message,
|
|
335
|
-
buttons: ['OK', 'Cancel']
|
|
336
|
-
});
|
|
337
|
-
return result.response === 0; // true if OK clicked
|
|
338
|
-
});
|