Skip to content

Development to Main > Release #161

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 53 commits into from
Dec 16, 2024
Merged
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
5eb412f
Hide navigation dots (..) when navigation path is root.
ubidefeo Dec 7, 2024
e159d5f
Implemented keyboard shortcuts.
ubidefeo Dec 8, 2024
bfcc2c6
Renamed method to onKeyboardShortcut.
ubidefeo Dec 8, 2024
c6d8d91
Removed whitespace.
ubidefeo Dec 8, 2024
45a582a
Removed whitespace.
ubidefeo Dec 8, 2024
4c648a3
Removed leftover test code from main.js.
ubidefeo Dec 8, 2024
ed7f839
Removed unnecessary shortcutsContext member.
ubidefeo Dec 8, 2024
9a490c2
Removed accelerator shortcuts from Menu items for Reload and Dev Tools.
ubidefeo Dec 8, 2024
177b7d1
Implemented Board menu item.
ubidefeo Dec 9, 2024
c6ec180
Menu cleanup.
ubidefeo Dec 9, 2024
e9c8a2e
Merge branch 'fix/hide-navigation-dots' into feature/shortcuts
ubidefeo Dec 9, 2024
c5b49c3
Merge pull request #152 from arduino/fix/hide-navigation-dots
ubidefeo Dec 9, 2024
1fb588e
Added ESC key listener. Enabled it on connection dialog.
ubidefeo Dec 9, 2024
b021b25
Ability to run code selection. Selecting whitespace runs the whole sc…
ubidefeo Dec 9, 2024
861b658
Added Clear Terminal shortcut Meta+k.
ubidefeo Dec 9, 2024
99930a1
Polished icons.
ubidefeo Dec 9, 2024
da271ae
Icons: updated folder.
ubidefeo Dec 9, 2024
78cd3da
Merge pull request #155 from arduino/feature/icons-update
ubidefeo Dec 10, 2024
243c58e
Updated micropython.js to v1.5.1
ubidefeo Dec 10, 2024
afadd3f
Testing disconnect before manual reload.
ubidefeo Dec 10, 2024
9baed62
Merge pull request #156 from arduino/backend/micropyton-js-v1.5.1
ubidefeo Dec 10, 2024
1560f07
Merge branch 'development' into feature/shortcuts
ubidefeo Dec 10, 2024
e60703b
Fixed folder background.
ubidefeo Dec 10, 2024
5ce485f
Merge pull request #157 from arduino/feature/icons-update
ubidefeo Dec 10, 2024
7c6cdde
Merge branch 'development' into feature/shortcuts
ubidefeo Dec 10, 2024
a24311d
Merge pull request #154 from arduino/feature/run-code-selection
ubidefeo Dec 10, 2024
9a58c11
Merge branch 'development' into feature/shortcuts
ubidefeo Dec 10, 2024
88fe9a2
Implemented ALT option to run code selection.
ubidefeo Dec 11, 2024
9908d4a
Refactored menu and global shortcuts into constants file.
ubidefeo Dec 12, 2024
0fa99b1
Changed 'selection' to 'onlySelected'.
ubidefeo Dec 12, 2024
530dbc6
Moved registerMenu to file scope.
ubidefeo Dec 12, 2024
a019c99
Only save if openFile (tab) has changes.
ubidefeo Dec 13, 2024
b051120
Added view switch, replaced editor icon, updated shortcuts.
ubidefeo Dec 13, 2024
bd3cc3b
Moved 'Clear Terminal' to 'View'
ubidefeo Dec 13, 2024
9231661
Removed comments with dev notes.
ubidefeo Dec 13, 2024
9759ed7
Replaced Win/Linux Ctrl+Alt+R ignored shortcut with Ctrl+Alt+S
ubidefeo Dec 13, 2024
6432226
Amended shortcut handling in store.js
ubidefeo Dec 13, 2024
2bd51bb
Merge pull request #153 from arduino/feature/shortcuts
ubidefeo Dec 13, 2024
5c153c0
Update Board menu when opening files from File view.
ubidefeo Dec 13, 2024
bc53b85
Merge pull request #159 from arduino/fix/board-menu-update-when-openi…
ubidefeo Dec 13, 2024
48881ce
Use better name for callback
sebromero Dec 11, 2024
42f8f9c
Move serial logic to separate file
sebromero Dec 11, 2024
cc5a8ee
Decouple UI and logic of disconnection event
sebromero Dec 11, 2024
88e0bda
Use serial bridge to execute serial commands
sebromero Dec 11, 2024
041468f
Use shared instance of Serial in menu handler
sebromero Dec 11, 2024
0792f2f
Remove residues from rebase
sebromero Dec 13, 2024
c3e2bff
Remove code to handle reload event
sebromero Dec 13, 2024
18c4929
Rename folder
sebromero Dec 13, 2024
612d0b9
Remove duplicated event handler
sebromero Dec 14, 2024
4248c1e
Remove event handlers on connection close
sebromero Dec 16, 2024
d7422b1
Replace existing event listeners in serial bridge
sebromero Dec 16, 2024
b5b5219
Add code documentation
sebromero Dec 16, 2024
8d7586a
Merge pull request #160 from arduino/maintenance/serial-refactoring
sebromero Dec 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions backend/ipc.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
const fs = require('fs')
const registerMenu = require('./menu.js')
const serial = require('./serial/serial.js').sharedInstance

const {
openFolderDialog,
listFolder,
@@ -7,6 +10,8 @@ const {
} = require('./helpers.js')

module.exports = function registerIPCHandlers(win, ipcMain, app, dialog) {
serial.win = win // Required to send callback messages to renderer

ipcMain.handle('open-folder', async (event) => {
console.log('ipcMain', 'open-folder')
const folder = await openFolderDialog(win)
@@ -129,9 +134,18 @@ module.exports = function registerIPCHandlers(win, ipcMain, app, dialog) {
return response != opt.cancelId
})

ipcMain.handle('update-menu-state', (event, state) => {
registerMenu(win, state)
})

win.on('close', (event) => {
console.log('BrowserWindow', 'close')
event.preventDefault()
win.webContents.send('check-before-close')
})

ipcMain.handle('serial', (event, command, ...args) => {
console.debug('Handling IPC serial command:', command, ...args)
return serial[command](...args)
})
}
85 changes: 77 additions & 8 deletions backend/menu.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
const { app, Menu } = require('electron')
const path = require('path')
const serial = require('./serial/serial.js').sharedInstance
const openAboutWindow = require('about-window').default
const shortcuts = require('./shortcuts.js')
const { type } = require('os')

module.exports = function registerMenu(win) {
module.exports = function registerMenu(win, state = {}) {
const isMac = process.platform === 'darwin'
const template = [
...(isMac ? [{
label: app.name,
submenu: [
{ role: 'about'},
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hide', accelerator: 'CmdOrCtrl+Shift+H' },
{ role: 'hideOthers' },
{ role: 'unhide' },
{ type: 'separator' },
@@ -35,7 +37,6 @@ module.exports = function registerMenu(win) {
{ role: 'copy' },
{ role: 'paste' },
...(isMac ? [
{ role: 'pasteAndMatchStyle' },
{ role: 'selectAll' },
{ type: 'separator' },
{
@@ -51,11 +52,66 @@ module.exports = function registerMenu(win) {
])
]
},
{
label: 'Board',
submenu: [
{
label: 'Connect',
accelerator: shortcuts.menu.CONNECT,
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.CONNECT)
},
{
label: 'Disconnect',
accelerator: shortcuts.menu.DISCONNECT,
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.DISCONNECT)
},
{ type: 'separator' },
{
label: 'Run',
accelerator: shortcuts.menu.RUN,
enabled: state.isConnected && state.view === 'editor',
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.RUN)
},
{
label: 'Run selection',
accelerator: isMac ? shortcuts.menu.RUN_SELECTION : shortcuts.menu.RUN_SELECTION_WL,
enabled: state.isConnected && state.view === 'editor',
click: () => win.webContents.send('shortcut-cmd', (isMac ? shortcuts.global.RUN_SELECTION : shortcuts.global.RUN_SELECTION_WL))
},
{
label: 'Stop',
accelerator: shortcuts.menu.STOP,
enabled: state.isConnected && state.view === 'editor',
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.STOP)
},
{
label: 'Reset',
accelerator: shortcuts.menu.RESET,
enabled: state.isConnected && state.view === 'editor',
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.RESET)
},
{ type: 'separator' }
]
},
{
label: 'View',
submenu: [
{ role: 'reload' },
{ role: 'toggleDevTools' },
{
label: 'Editor',
accelerator: shortcuts.menu.EDITOR_VIEW,
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.EDITOR_VIEW,)
},
{
label: 'Files',
accelerator: shortcuts.menu.FILES_VIEW,
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.FILES_VIEW)
},
{
label: 'Clear terminal',
accelerator: shortcuts.menu.CLEAR_TERMINAL,
enabled: state.isConnected && state.view === 'editor',
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.CLEAR_TERMINAL)
},
{ type: 'separator' },
{ role: 'resetZoom' },
{ role: 'zoomIn' },
@@ -67,6 +123,20 @@ module.exports = function registerMenu(win) {
{
label: 'Window',
submenu: [
{
label: 'Reload',
accelerator: '',
click: async () => {
try {
await serial.disconnect()
win.reload()
} catch(e) {
console.error('Reload from menu failed:', e)
}
}
},
{ role: 'toggleDevTools'},
{ type: 'separator' },
{ role: 'minimize' },
{ role: 'zoom' },
...(isMac ? [
@@ -75,7 +145,7 @@ module.exports = function registerMenu(win) {
{ type: 'separator' },
{ role: 'window' }
] : [
{ role: 'close' }

])
]
},
@@ -102,7 +172,6 @@ module.exports = function registerMenu(win) {
openAboutWindow({
icon_path: path.resolve(__dirname, '../ui/arduino/media/about_image.png'),
css_path: path.resolve(__dirname, '../ui/arduino/views/about.css'),
// about_page_dir: path.resolve(__dirname, '../ui/arduino/views/'),
copyright: '© Arduino SA 2022',
package_json_dir: path.resolve(__dirname, '..'),
bug_report_url: "https://github.com/arduino/lab-micropython-editor/issues",
97 changes: 97 additions & 0 deletions backend/serial/serial-bridge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
const { ipcRenderer } = require('electron')
const path = require('path')

const SerialBridge = {
loadPorts: async () => {
return await ipcRenderer.invoke('serial', 'loadPorts')
},
connect: async (path) => {
return await ipcRenderer.invoke('serial', 'connect', path)
},
disconnect: async () => {
return await ipcRenderer.invoke('serial', 'disconnect')
},
run: async (code) => {
return await ipcRenderer.invoke('serial', 'run', code)
},
execFile: async (path) => {
return await ipcRenderer.invoke('serial', 'execFile', path)
},
getPrompt: async () => {
return await ipcRenderer.invoke('serial', 'getPrompt')
},
keyboardInterrupt: async () => {
await ipcRenderer.invoke('serial', 'keyboardInterrupt')
return Promise.resolve()
},
reset: async () => {
await ipcRenderer.invoke('serial', 'reset')
return Promise.resolve()
},
eval: (d) => {
return ipcRenderer.invoke('serial', 'eval', d)
},
onData: (callback) => {
// Remove all previous listeners
if (ipcRenderer.listeners("serial-on-data").length > 0) {
ipcRenderer.removeAllListeners("serial-on-data")
}
ipcRenderer.on('serial-on-data', (event, data) => {
callback(data)
})
},
listFiles: async (folder) => {
return await ipcRenderer.invoke('serial', 'listFiles', folder)
},
ilistFiles: async (folder) => {
return await ipcRenderer.invoke('serial', 'ilistFiles', folder)
},
loadFile: async (file) => {
return await ipcRenderer.invoke('serial', 'loadFile', file)
},
removeFile: async (file) => {
return await ipcRenderer.invoke('serial', 'removeFile', file)
},
saveFileContent: async (filename, content, dataConsumer) => {
return await ipcRenderer.invoke('serial', 'saveFileContent', filename, content, dataConsumer)
},
uploadFile: async (src, dest, dataConsumer) => {
return await ipcRenderer.invoke('serial', 'uploadFile', src, dest, dataConsumer)
},
downloadFile: async (src, dest) => {
let contents = await ipcRenderer.invoke('serial', 'loadFile', src)
return ipcRenderer.invoke('save-file', dest, contents)
},
renameFile: async (oldName, newName) => {
return await ipcRenderer.invoke('serial', 'renameFile', oldName, newName)
},
onConnectionClosed: async (callback) => {
// Remove all previous listeners
if (ipcRenderer.listeners("serial-on-connection-closed").length > 0) {
ipcRenderer.removeAllListeners("serial-on-connection-closed")
}
ipcRenderer.on('serial-on-connection-closed', (event) => {
callback()
})
},
createFolder: async (folder) => {
return await ipcRenderer.invoke('serial', 'createFolder', folder)
},
removeFolder: async (folder) => {
return await ipcRenderer.invoke('serial', 'removeFolder', folder)
},
getNavigationPath: (navigation, target) => {
return path.posix.join(navigation, target)
},
getFullPath: (root, navigation, file) => {
return path.posix.join(root, navigation, file)
},
getParentPath: (navigation) => {
return path.posix.dirname(navigation)
},
fileExists: async (filePath) => {
return await ipcRenderer.invoke('serial', 'fileExists', filePath)
}
}

module.exports = SerialBridge
117 changes: 117 additions & 0 deletions backend/serial/serial.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
const MicroPython = require('micropython.js')

class Serial {
constructor(win = null) {
this.win = win
this.board = new MicroPython()
this.board.chunk_size = 192
this.board.chunk_sleep = 200
}

async loadPorts() {
let ports = await this.board.list_ports()
return ports.filter(p => p.vendorId && p.productId)
}

async connect(path) {
await this.board.open(path)
this.registerCallbacks()
}

async disconnect() {
return await this.board.close()
}

async run(code) {
return await this.board.run(code)
}

async execFile(path) {
return await this.board.execfile(path)
}

async getPrompt() {
return await this.board.get_prompt()
}

async keyboardInterrupt() {
await this.board.stop()
return Promise.resolve()
}

async reset() {
await this.board.stop()
await this.board.exit_raw_repl()
await this.board.reset()
return Promise.resolve()
}

async eval(d) {
return await this.board.eval(d)
}

registerCallbacks() {
this.board.serial.on('data', (data) => {
this.win.webContents.send('serial-on-data', data)
})

this.board.serial.on('close', () => {
this.board.serial.removeAllListeners("data")
this.board.serial.removeAllListeners("close")
this.win.webContents.send('serial-on-connection-closed')
})
}

async listFiles(folder) {
return await this.board.fs_ls(folder)
}

async ilistFiles(folder) {
return await this.board.fs_ils(folder)
}

async loadFile(file) {
const output = await this.board.fs_cat_binary(file)
return output || ''
}

async removeFile(file) {
return await this.board.fs_rm(file)
}

async saveFileContent(filename, content, dataConsumer) {
return await this.board.fs_save(content || ' ', filename, dataConsumer)
}

async uploadFile(src, dest, dataConsumer) {
return await this.board.fs_put(src, dest.replaceAll(path.win32.sep, path.posix.sep), dataConsumer)
}

async renameFile(oldName, newName) {
return await this.board.fs_rename(oldName, newName)
}

async createFolder(folder) {
return await this.board.fs_mkdir(folder)
}

async removeFolder(folder) {
return await this.board.fs_rmdir(folder)
}

async fileExists(filePath) {
const output = await this.board.run(`
import os
try:
os.stat("${filePath}")
print(0)
except OSError:
print(1)
`)
return output[2] === '0'
}
}

const sharedInstance = new Serial()

module.exports = {sharedInstance, Serial}
Loading