Skip to content

Commit 3d4a54b

Browse files
authored
Merge pull request #161 from arduino/development
Development to Main > Release
2 parents adfef3a + 8d7586a commit 3d4a54b

23 files changed

+752
-229
lines changed

backend/ipc.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
const fs = require('fs')
2+
const registerMenu = require('./menu.js')
3+
const serial = require('./serial/serial.js').sharedInstance
4+
25
const {
36
openFolderDialog,
47
listFolder,
@@ -7,6 +10,8 @@ const {
710
} = require('./helpers.js')
811

912
module.exports = function registerIPCHandlers(win, ipcMain, app, dialog) {
13+
serial.win = win // Required to send callback messages to renderer
14+
1015
ipcMain.handle('open-folder', async (event) => {
1116
console.log('ipcMain', 'open-folder')
1217
const folder = await openFolderDialog(win)
@@ -129,9 +134,18 @@ module.exports = function registerIPCHandlers(win, ipcMain, app, dialog) {
129134
return response != opt.cancelId
130135
})
131136

137+
ipcMain.handle('update-menu-state', (event, state) => {
138+
registerMenu(win, state)
139+
})
140+
132141
win.on('close', (event) => {
133142
console.log('BrowserWindow', 'close')
134143
event.preventDefault()
135144
win.webContents.send('check-before-close')
136145
})
146+
147+
ipcMain.handle('serial', (event, command, ...args) => {
148+
console.debug('Handling IPC serial command:', command, ...args)
149+
return serial[command](...args)
150+
})
137151
}

backend/menu.js

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
const { app, Menu } = require('electron')
22
const path = require('path')
3+
const serial = require('./serial/serial.js').sharedInstance
34
const openAboutWindow = require('about-window').default
5+
const shortcuts = require('./shortcuts.js')
6+
const { type } = require('os')
47

5-
module.exports = function registerMenu(win) {
8+
module.exports = function registerMenu(win, state = {}) {
69
const isMac = process.platform === 'darwin'
710
const template = [
811
...(isMac ? [{
912
label: app.name,
1013
submenu: [
1114
{ role: 'about'},
1215
{ type: 'separator' },
13-
{ role: 'services' },
1416
{ type: 'separator' },
15-
{ role: 'hide' },
17+
{ role: 'hide', accelerator: 'CmdOrCtrl+Shift+H' },
1618
{ role: 'hideOthers' },
1719
{ role: 'unhide' },
1820
{ type: 'separator' },
@@ -35,7 +37,6 @@ module.exports = function registerMenu(win) {
3537
{ role: 'copy' },
3638
{ role: 'paste' },
3739
...(isMac ? [
38-
{ role: 'pasteAndMatchStyle' },
3940
{ role: 'selectAll' },
4041
{ type: 'separator' },
4142
{
@@ -51,11 +52,66 @@ module.exports = function registerMenu(win) {
5152
])
5253
]
5354
},
55+
{
56+
label: 'Board',
57+
submenu: [
58+
{
59+
label: 'Connect',
60+
accelerator: shortcuts.menu.CONNECT,
61+
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.CONNECT)
62+
},
63+
{
64+
label: 'Disconnect',
65+
accelerator: shortcuts.menu.DISCONNECT,
66+
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.DISCONNECT)
67+
},
68+
{ type: 'separator' },
69+
{
70+
label: 'Run',
71+
accelerator: shortcuts.menu.RUN,
72+
enabled: state.isConnected && state.view === 'editor',
73+
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.RUN)
74+
},
75+
{
76+
label: 'Run selection',
77+
accelerator: isMac ? shortcuts.menu.RUN_SELECTION : shortcuts.menu.RUN_SELECTION_WL,
78+
enabled: state.isConnected && state.view === 'editor',
79+
click: () => win.webContents.send('shortcut-cmd', (isMac ? shortcuts.global.RUN_SELECTION : shortcuts.global.RUN_SELECTION_WL))
80+
},
81+
{
82+
label: 'Stop',
83+
accelerator: shortcuts.menu.STOP,
84+
enabled: state.isConnected && state.view === 'editor',
85+
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.STOP)
86+
},
87+
{
88+
label: 'Reset',
89+
accelerator: shortcuts.menu.RESET,
90+
enabled: state.isConnected && state.view === 'editor',
91+
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.RESET)
92+
},
93+
{ type: 'separator' }
94+
]
95+
},
5496
{
5597
label: 'View',
5698
submenu: [
57-
{ role: 'reload' },
58-
{ role: 'toggleDevTools' },
99+
{
100+
label: 'Editor',
101+
accelerator: shortcuts.menu.EDITOR_VIEW,
102+
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.EDITOR_VIEW,)
103+
},
104+
{
105+
label: 'Files',
106+
accelerator: shortcuts.menu.FILES_VIEW,
107+
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.FILES_VIEW)
108+
},
109+
{
110+
label: 'Clear terminal',
111+
accelerator: shortcuts.menu.CLEAR_TERMINAL,
112+
enabled: state.isConnected && state.view === 'editor',
113+
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.CLEAR_TERMINAL)
114+
},
59115
{ type: 'separator' },
60116
{ role: 'resetZoom' },
61117
{ role: 'zoomIn' },
@@ -67,6 +123,20 @@ module.exports = function registerMenu(win) {
67123
{
68124
label: 'Window',
69125
submenu: [
126+
{
127+
label: 'Reload',
128+
accelerator: '',
129+
click: async () => {
130+
try {
131+
await serial.disconnect()
132+
win.reload()
133+
} catch(e) {
134+
console.error('Reload from menu failed:', e)
135+
}
136+
}
137+
},
138+
{ role: 'toggleDevTools'},
139+
{ type: 'separator' },
70140
{ role: 'minimize' },
71141
{ role: 'zoom' },
72142
...(isMac ? [
@@ -75,7 +145,7 @@ module.exports = function registerMenu(win) {
75145
{ type: 'separator' },
76146
{ role: 'window' }
77147
] : [
78-
{ role: 'close' }
148+
79149
])
80150
]
81151
},
@@ -102,7 +172,6 @@ module.exports = function registerMenu(win) {
102172
openAboutWindow({
103173
icon_path: path.resolve(__dirname, '../ui/arduino/media/about_image.png'),
104174
css_path: path.resolve(__dirname, '../ui/arduino/views/about.css'),
105-
// about_page_dir: path.resolve(__dirname, '../ui/arduino/views/'),
106175
copyright: '© Arduino SA 2022',
107176
package_json_dir: path.resolve(__dirname, '..'),
108177
bug_report_url: "https://github.com/arduino/lab-micropython-editor/issues",

backend/serial/serial-bridge.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
const { ipcRenderer } = require('electron')
2+
const path = require('path')
3+
4+
const SerialBridge = {
5+
loadPorts: async () => {
6+
return await ipcRenderer.invoke('serial', 'loadPorts')
7+
},
8+
connect: async (path) => {
9+
return await ipcRenderer.invoke('serial', 'connect', path)
10+
},
11+
disconnect: async () => {
12+
return await ipcRenderer.invoke('serial', 'disconnect')
13+
},
14+
run: async (code) => {
15+
return await ipcRenderer.invoke('serial', 'run', code)
16+
},
17+
execFile: async (path) => {
18+
return await ipcRenderer.invoke('serial', 'execFile', path)
19+
},
20+
getPrompt: async () => {
21+
return await ipcRenderer.invoke('serial', 'getPrompt')
22+
},
23+
keyboardInterrupt: async () => {
24+
await ipcRenderer.invoke('serial', 'keyboardInterrupt')
25+
return Promise.resolve()
26+
},
27+
reset: async () => {
28+
await ipcRenderer.invoke('serial', 'reset')
29+
return Promise.resolve()
30+
},
31+
eval: (d) => {
32+
return ipcRenderer.invoke('serial', 'eval', d)
33+
},
34+
onData: (callback) => {
35+
// Remove all previous listeners
36+
if (ipcRenderer.listeners("serial-on-data").length > 0) {
37+
ipcRenderer.removeAllListeners("serial-on-data")
38+
}
39+
ipcRenderer.on('serial-on-data', (event, data) => {
40+
callback(data)
41+
})
42+
},
43+
listFiles: async (folder) => {
44+
return await ipcRenderer.invoke('serial', 'listFiles', folder)
45+
},
46+
ilistFiles: async (folder) => {
47+
return await ipcRenderer.invoke('serial', 'ilistFiles', folder)
48+
},
49+
loadFile: async (file) => {
50+
return await ipcRenderer.invoke('serial', 'loadFile', file)
51+
},
52+
removeFile: async (file) => {
53+
return await ipcRenderer.invoke('serial', 'removeFile', file)
54+
},
55+
saveFileContent: async (filename, content, dataConsumer) => {
56+
return await ipcRenderer.invoke('serial', 'saveFileContent', filename, content, dataConsumer)
57+
},
58+
uploadFile: async (src, dest, dataConsumer) => {
59+
return await ipcRenderer.invoke('serial', 'uploadFile', src, dest, dataConsumer)
60+
},
61+
downloadFile: async (src, dest) => {
62+
let contents = await ipcRenderer.invoke('serial', 'loadFile', src)
63+
return ipcRenderer.invoke('save-file', dest, contents)
64+
},
65+
renameFile: async (oldName, newName) => {
66+
return await ipcRenderer.invoke('serial', 'renameFile', oldName, newName)
67+
},
68+
onConnectionClosed: async (callback) => {
69+
// Remove all previous listeners
70+
if (ipcRenderer.listeners("serial-on-connection-closed").length > 0) {
71+
ipcRenderer.removeAllListeners("serial-on-connection-closed")
72+
}
73+
ipcRenderer.on('serial-on-connection-closed', (event) => {
74+
callback()
75+
})
76+
},
77+
createFolder: async (folder) => {
78+
return await ipcRenderer.invoke('serial', 'createFolder', folder)
79+
},
80+
removeFolder: async (folder) => {
81+
return await ipcRenderer.invoke('serial', 'removeFolder', folder)
82+
},
83+
getNavigationPath: (navigation, target) => {
84+
return path.posix.join(navigation, target)
85+
},
86+
getFullPath: (root, navigation, file) => {
87+
return path.posix.join(root, navigation, file)
88+
},
89+
getParentPath: (navigation) => {
90+
return path.posix.dirname(navigation)
91+
},
92+
fileExists: async (filePath) => {
93+
return await ipcRenderer.invoke('serial', 'fileExists', filePath)
94+
}
95+
}
96+
97+
module.exports = SerialBridge

backend/serial/serial.js

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
const MicroPython = require('micropython.js')
2+
3+
class Serial {
4+
constructor(win = null) {
5+
this.win = win
6+
this.board = new MicroPython()
7+
this.board.chunk_size = 192
8+
this.board.chunk_sleep = 200
9+
}
10+
11+
async loadPorts() {
12+
let ports = await this.board.list_ports()
13+
return ports.filter(p => p.vendorId && p.productId)
14+
}
15+
16+
async connect(path) {
17+
await this.board.open(path)
18+
this.registerCallbacks()
19+
}
20+
21+
async disconnect() {
22+
return await this.board.close()
23+
}
24+
25+
async run(code) {
26+
return await this.board.run(code)
27+
}
28+
29+
async execFile(path) {
30+
return await this.board.execfile(path)
31+
}
32+
33+
async getPrompt() {
34+
return await this.board.get_prompt()
35+
}
36+
37+
async keyboardInterrupt() {
38+
await this.board.stop()
39+
return Promise.resolve()
40+
}
41+
42+
async reset() {
43+
await this.board.stop()
44+
await this.board.exit_raw_repl()
45+
await this.board.reset()
46+
return Promise.resolve()
47+
}
48+
49+
async eval(d) {
50+
return await this.board.eval(d)
51+
}
52+
53+
registerCallbacks() {
54+
this.board.serial.on('data', (data) => {
55+
this.win.webContents.send('serial-on-data', data)
56+
})
57+
58+
this.board.serial.on('close', () => {
59+
this.board.serial.removeAllListeners("data")
60+
this.board.serial.removeAllListeners("close")
61+
this.win.webContents.send('serial-on-connection-closed')
62+
})
63+
}
64+
65+
async listFiles(folder) {
66+
return await this.board.fs_ls(folder)
67+
}
68+
69+
async ilistFiles(folder) {
70+
return await this.board.fs_ils(folder)
71+
}
72+
73+
async loadFile(file) {
74+
const output = await this.board.fs_cat_binary(file)
75+
return output || ''
76+
}
77+
78+
async removeFile(file) {
79+
return await this.board.fs_rm(file)
80+
}
81+
82+
async saveFileContent(filename, content, dataConsumer) {
83+
return await this.board.fs_save(content || ' ', filename, dataConsumer)
84+
}
85+
86+
async uploadFile(src, dest, dataConsumer) {
87+
return await this.board.fs_put(src, dest.replaceAll(path.win32.sep, path.posix.sep), dataConsumer)
88+
}
89+
90+
async renameFile(oldName, newName) {
91+
return await this.board.fs_rename(oldName, newName)
92+
}
93+
94+
async createFolder(folder) {
95+
return await this.board.fs_mkdir(folder)
96+
}
97+
98+
async removeFolder(folder) {
99+
return await this.board.fs_rmdir(folder)
100+
}
101+
102+
async fileExists(filePath) {
103+
const output = await this.board.run(`
104+
import os
105+
try:
106+
os.stat("${filePath}")
107+
print(0)
108+
except OSError:
109+
print(1)
110+
`)
111+
return output[2] === '0'
112+
}
113+
}
114+
115+
const sharedInstance = new Serial()
116+
117+
module.exports = {sharedInstance, Serial}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy