Skip to content

Commit 2bd51bb

Browse files
authored
Merge pull request #153 from arduino/feature/shortcuts
Feature/shortcuts
2 parents a24311d + 6432226 commit 2bd51bb

File tree

11 files changed

+324
-30
lines changed

11 files changed

+324
-30
lines changed

backend/ipc.js

+11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
const fs = require('fs')
2+
const registerMenu = require('./menu.js')
3+
24
const {
35
openFolderDialog,
46
listFolder,
@@ -129,9 +131,18 @@ module.exports = function registerIPCHandlers(win, ipcMain, app, dialog) {
129131
return response != opt.cancelId
130132
})
131133

134+
ipcMain.handle('update-menu-state', (event, state) => {
135+
registerMenu(win, state)
136+
})
137+
132138
win.on('close', (event) => {
133139
console.log('BrowserWindow', 'close')
134140
event.preventDefault()
135141
win.webContents.send('check-before-close')
136142
})
143+
144+
// handle disconnection before reload
145+
ipcMain.handle('prepare-reload', async (event) => {
146+
return win.webContents.send('before-reload')
147+
})
137148
}

backend/menu.js

+78-8
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
const { app, Menu } = require('electron')
22
const path = require('path')
33
const openAboutWindow = require('about-window').default
4+
const shortcuts = require('./shortcuts.js')
5+
const { type } = require('os')
46

5-
module.exports = function registerMenu(win) {
7+
module.exports = function registerMenu(win, state = {}) {
68
const isMac = process.platform === 'darwin'
79
const template = [
810
...(isMac ? [{
911
label: app.name,
1012
submenu: [
1113
{ role: 'about'},
1214
{ type: 'separator' },
13-
{ role: 'services' },
1415
{ type: 'separator' },
15-
{ role: 'hide' },
16+
{ role: 'hide', accelerator: 'CmdOrCtrl+Shift+H' },
1617
{ role: 'hideOthers' },
1718
{ role: 'unhide' },
1819
{ type: 'separator' },
@@ -35,7 +36,6 @@ module.exports = function registerMenu(win) {
3536
{ role: 'copy' },
3637
{ role: 'paste' },
3738
...(isMac ? [
38-
{ role: 'pasteAndMatchStyle' },
3939
{ role: 'selectAll' },
4040
{ type: 'separator' },
4141
{
@@ -51,11 +51,66 @@ module.exports = function registerMenu(win) {
5151
])
5252
]
5353
},
54+
{
55+
label: 'Board',
56+
submenu: [
57+
{
58+
label: 'Connect',
59+
accelerator: shortcuts.menu.CONNECT,
60+
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.CONNECT)
61+
},
62+
{
63+
label: 'Disconnect',
64+
accelerator: shortcuts.menu.DISCONNECT,
65+
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.DISCONNECT)
66+
},
67+
{ type: 'separator' },
68+
{
69+
label: 'Run',
70+
accelerator: shortcuts.menu.RUN,
71+
enabled: state.isConnected && state.view === 'editor',
72+
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.RUN)
73+
},
74+
{
75+
label: 'Run selection',
76+
accelerator: isMac ? shortcuts.menu.RUN_SELECTION : shortcuts.menu.RUN_SELECTION_WL,
77+
enabled: state.isConnected && state.view === 'editor',
78+
click: () => win.webContents.send('shortcut-cmd', (isMac ? shortcuts.global.RUN_SELECTION : shortcuts.global.RUN_SELECTION_WL))
79+
},
80+
{
81+
label: 'Stop',
82+
accelerator: shortcuts.menu.STOP,
83+
enabled: state.isConnected && state.view === 'editor',
84+
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.STOP)
85+
},
86+
{
87+
label: 'Reset',
88+
accelerator: shortcuts.menu.RESET,
89+
enabled: state.isConnected && state.view === 'editor',
90+
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.RESET)
91+
},
92+
{ type: 'separator' }
93+
]
94+
},
5495
{
5596
label: 'View',
5697
submenu: [
57-
{ role: 'reload' },
58-
{ role: 'toggleDevTools' },
98+
{
99+
label: 'Editor',
100+
accelerator: shortcuts.menu.EDITOR_VIEW,
101+
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.EDITOR_VIEW,)
102+
},
103+
{
104+
label: 'Files',
105+
accelerator: shortcuts.menu.FILES_VIEW,
106+
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.FILES_VIEW)
107+
},
108+
{
109+
label: 'Clear terminal',
110+
accelerator: shortcuts.menu.CLEAR_TERMINAL,
111+
enabled: state.isConnected && state.view === 'editor',
112+
click: () => win.webContents.send('shortcut-cmd', shortcuts.global.CLEAR_TERMINAL)
113+
},
59114
{ type: 'separator' },
60115
{ role: 'resetZoom' },
61116
{ role: 'zoomIn' },
@@ -67,6 +122,22 @@ module.exports = function registerMenu(win) {
67122
{
68123
label: 'Window',
69124
submenu: [
125+
{
126+
label: 'Reload',
127+
accelerator: '',
128+
click: async () => {
129+
try {
130+
win.webContents.send('cleanup-before-reload')
131+
setTimeout(() => {
132+
win.reload()
133+
}, 500)
134+
} catch(e) {
135+
console.error('Reload from menu failed:', e)
136+
}
137+
}
138+
},
139+
{ role: 'toggleDevTools'},
140+
{ type: 'separator' },
70141
{ role: 'minimize' },
71142
{ role: 'zoom' },
72143
...(isMac ? [
@@ -75,7 +146,7 @@ module.exports = function registerMenu(win) {
75146
{ type: 'separator' },
76147
{ role: 'window' }
77148
] : [
78-
{ role: 'close' }
149+
79150
])
80151
]
81152
},
@@ -102,7 +173,6 @@ module.exports = function registerMenu(win) {
102173
openAboutWindow({
103174
icon_path: path.resolve(__dirname, '../ui/arduino/media/about_image.png'),
104175
css_path: path.resolve(__dirname, '../ui/arduino/views/about.css'),
105-
// about_page_dir: path.resolve(__dirname, '../ui/arduino/views/'),
106176
copyright: '© Arduino SA 2022',
107177
package_json_dir: path.resolve(__dirname, '..'),
108178
bug_report_url: "https://github.com/arduino/lab-micropython-editor/issues",

backend/shortcuts.js

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
module.exports = {
2+
global: {
3+
CONNECT: 'CommandOrControl+Shift+C',
4+
DISCONNECT: 'CommandOrControl+Shift+D',
5+
SAVE: 'CommandOrControl+S',
6+
RUN: 'CommandOrControl+R',
7+
RUN_SELECTION: 'CommandOrControl+Alt+R',
8+
RUN_SELECTION_WL: 'CommandOrControl+Alt+S',
9+
STOP: 'CommandOrControl+H',
10+
RESET: 'CommandOrControl+Shift+R',
11+
CLEAR_TERMINAL: 'CommandOrControl+L',
12+
EDITOR_VIEW: 'CommandOrControl+Alt+1',
13+
FILES_VIEW: 'CommandOrControl+Alt+2',
14+
ESC: 'Escape'
15+
},
16+
menu: {
17+
CONNECT: 'CmdOrCtrl+Shift+C',
18+
DISCONNECT: 'CmdOrCtrl+Shift+D',
19+
SAVE: 'CmdOrCtrl+S',
20+
RUN: 'CmdOrCtrl+R',
21+
RUN_SELECTION: 'CmdOrCtrl+Alt+R',
22+
RUN_SELECTION_WL: 'CmdOrCtrl+Alt+S',
23+
STOP: 'CmdOrCtrl+H',
24+
RESET: 'CmdOrCtrl+Shift+R',
25+
CLEAR_TERMINAL: 'CmdOrCtrl+L',
26+
EDITOR_VIEW: 'CmdOrCtrl+Alt+1',
27+
FILES_VIEW: 'CmdOrCtrl+Alt+2'
28+
}
29+
}

index.js

+51-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
const { app, BrowserWindow, ipcMain, dialog } = require('electron')
1+
const { app, BrowserWindow, ipcMain, dialog, globalShortcut } = require('electron')
22
const path = require('path')
33
const fs = require('fs')
4+
const shortcuts = require('./backend/shortcuts.js').global
45

56
const registerIPCHandlers = require('./backend/ipc.js')
67
const registerMenu = require('./backend/menu.js')
@@ -49,12 +50,59 @@ function createWindow () {
4950
win.show()
5051
})
5152

53+
win.webContents.on('before-reload', async (event) => {
54+
// Prevent the default reload behavior
55+
event.preventDefault()
56+
57+
try {
58+
// Tell renderer to do cleanup
59+
win.webContents.send('cleanup-before-reload')
60+
61+
// Wait for cleanup then reload
62+
setTimeout(() => {
63+
// This will trigger a page reload, but won't trigger 'before-reload' again
64+
win.reload()
65+
}, 500)
66+
} catch(e) {
67+
console.error('Reload preparation failed:', e)
68+
}
69+
})
70+
71+
const initialMenuState = {
72+
isConnected: false,
73+
view: 'editor'
74+
}
75+
5276
registerIPCHandlers(win, ipcMain, app, dialog)
53-
registerMenu(win)
77+
registerMenu(win, initialMenuState)
5478

5579
app.on('activate', () => {
5680
if (BrowserWindow.getAllWindows().length === 0) createWindow()
5781
})
5882
}
5983

60-
app.on('ready', createWindow)
84+
function shortcutAction(key) {
85+
win.webContents.send('shortcut-cmd', key);
86+
}
87+
88+
// Shortcuts
89+
function registerShortcuts() {
90+
Object.entries(shortcuts).forEach(([command, shortcut]) => {
91+
globalShortcut.register(shortcut, () => {
92+
shortcutAction(shortcut)
93+
});
94+
})
95+
}
96+
97+
app.on('ready', () => {
98+
createWindow()
99+
registerShortcuts()
100+
101+
win.on('focus', () => {
102+
registerShortcuts()
103+
})
104+
win.on('blur', () => {
105+
globalShortcut.unregisterAll()
106+
})
107+
108+
})

preload.js

+30-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
console.log('preload')
22
const { contextBridge, ipcRenderer } = require('electron')
33
const path = require('path')
4-
4+
const shortcuts = require('./backend/shortcuts.js').global
55
const MicroPython = require('micropython.js')
6+
const { emit, platform } = require('process')
7+
68
const board = new MicroPython()
79
board.chunk_size = 192
810
board.chunk_sleep = 200
@@ -155,12 +157,37 @@ const Window = {
155157
setWindowSize: (minWidth, minHeight) => {
156158
ipcRenderer.invoke('set-window-size', minWidth, minHeight)
157159
},
160+
onKeyboardShortcut: (callback, key) => {
161+
ipcRenderer.on('shortcut-cmd', (event, k) => {
162+
callback(k);
163+
})
164+
},
165+
166+
onBeforeReload: (callback) => {
167+
ipcRenderer.on('cleanup-before-reload', async () => {
168+
try {
169+
await callback()
170+
} catch(e) {
171+
console.error('Cleanup before reload failed:', e)
172+
}
173+
})
174+
},
175+
158176
beforeClose: (callback) => ipcRenderer.on('check-before-close', callback),
159177
confirmClose: () => ipcRenderer.invoke('confirm-close'),
160178
isPackaged: () => ipcRenderer.invoke('is-packaged'),
161-
openDialog: (opt) => ipcRenderer.invoke('open-dialog', opt)
162-
}
179+
openDialog: (opt) => ipcRenderer.invoke('open-dialog', opt),
180+
181+
getOS: () => platform,
182+
isWindows: () => platform === 'win32',
183+
isMac: () => platform === 'darwin',
184+
isLinux: () => platform === 'linux',
163185

186+
updateMenuState: (state) => {
187+
return ipcRenderer.invoke('update-menu-state', state)
188+
},
189+
getShortcuts: () => shortcuts
190+
}
164191

165192
contextBridge.exposeInMainWorld('BridgeSerial', Serial)
166193
contextBridge.exposeInMainWorld('BridgeDisk', Disk)

ui/arduino/main.js

-2
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,9 @@ window.addEventListener('load', () => {
4646
app.use(store);
4747
app.route('*', App)
4848
app.mount('#app')
49-
5049
app.emitter.on('DOMContentLoaded', () => {
5150
if (app.state.diskNavigationRoot) {
5251
app.emitter.emit('refresh-files')
5352
}
5453
})
55-
5654
})

ui/arduino/media/code.svg

+3
Loading

0 commit comments

Comments
 (0)