Skip to content

Commit 2b560af

Browse files
authored
Merge pull request #113 from arduino/bugfix/about-page
Fix about page and split `index.js` into backend helper files
2 parents 9160342 + 8792acc commit 2b560af

File tree

9 files changed

+417
-304
lines changed

9 files changed

+417
-304
lines changed

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ This project is sponsored by Arduino, based on original work by [Murilo Polese](
3333

3434
## Technical overview
3535

36-
Arduino Lab for MicroPython is an [Electron](https://www.electronjs.org/) app that has its main purpose to communicate over serial with a microprocessor running [MicroPython](https://micropython.org/). All Electron code is at `/index.js`.
36+
Arduino Lab for MicroPython is an [Electron](https://www.electronjs.org/) app that has its main purpose to communicate over serial with a microprocessor running [MicroPython](https://micropython.org/). The Electron code is at `/index.js` and inside the folder `/backend`.
3737

38-
All operations over serial are abstracted and packaged on `/micropython.js` which is an attempt of porting `pyboard.py`. The port has its [own repository](https://github.com/arduino/micropython.js) but for the sake of simplicity and transparency, `micropython.js` is committed as source code.
38+
All operations over serial are abstracted and packaged on `micropython.js` which is an attempt of porting `pyboard.py`. The module has its [own repository](https://github.com/arduino/micropython.js) with documentation and examples of usage.
3939

4040
The User Interface (UI) source code stays inside `/ui` folder and is completely independent of the Electron code.
4141

@@ -49,6 +49,7 @@ At the root of the repository you will find:
4949
- `/build_resources`: Icons and other assets used during the build process.
5050
- `/ui`: Available user interfaces.
5151
- `/index.js`: Main Electron code.
52+
- `/backend`: Electron helpers.
5253
- `/preload.js`: Creates Disk, Serial and Window APIs on Electron's main process and exposes it to Electron's renderer process (context bridge).
5354

5455
## User interface

backend/helpers.js

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
const { dialog } = require('electron')
2+
const fs = require('fs')
3+
const path = require('path')
4+
5+
async function openFolderDialog(win) {
6+
// https://stackoverflow.com/questions/46027287/electron-open-folder-dialog
7+
let dir = await dialog.showOpenDialog(win, { properties: [ 'openDirectory' ] })
8+
return dir.filePaths[0] || null
9+
}
10+
11+
function listFolder(folder) {
12+
files = fs.readdirSync(path.resolve(folder))
13+
// Filter out directories
14+
files = files.filter(f => {
15+
let filePath = path.resolve(folder, f)
16+
return !fs.lstatSync(filePath).isDirectory()
17+
})
18+
return files
19+
}
20+
21+
function ilistFolder(folder) {
22+
let files = fs.readdirSync(path.resolve(folder))
23+
files = files.filter(f => {
24+
let filePath = path.resolve(folder, f)
25+
return !fs.lstatSync(filePath).isSymbolicLink()
26+
})
27+
files = files.map(f => {
28+
let filePath = path.resolve(folder, f)
29+
return {
30+
path: f,
31+
type: fs.lstatSync(filePath).isDirectory() ? 'folder' : 'file'
32+
}
33+
})
34+
// Filter out dot files
35+
files = files.filter(f => f.path.indexOf('.') !== 0)
36+
return files
37+
}
38+
39+
function getAllFiles(dirPath, arrayOfFiles) {
40+
// https://coderrocketfuel.com/article/recursively-list-all-the-files-in-a-directory-using-node-js
41+
files = ilistFolder(dirPath)
42+
arrayOfFiles = arrayOfFiles || []
43+
files.forEach(function(file) {
44+
const p = path.join(dirPath, file.path)
45+
const stat = fs.statSync(p)
46+
arrayOfFiles.push({
47+
path: p,
48+
type: stat.isDirectory() ? 'folder' : 'file'
49+
})
50+
if (stat.isDirectory()) {
51+
arrayOfFiles = getAllFiles(p, arrayOfFiles)
52+
}
53+
})
54+
return arrayOfFiles
55+
}
56+
57+
module.exports = {
58+
openFolderDialog,
59+
listFolder,
60+
ilistFolder,
61+
getAllFiles
62+
}

backend/ipc.js

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
const fs = require('fs')
2+
const {
3+
openFolderDialog,
4+
listFolder,
5+
ilistFolder,
6+
getAllFiles
7+
} = require('./helpers.js')
8+
9+
module.exports = function registerIPCHandlers(win, ipcMain) {
10+
ipcMain.handle('open-folder', async (event) => {
11+
console.log('ipcMain', 'open-folder')
12+
const folder = await openFolderDialog(win)
13+
let files = []
14+
if (folder) {
15+
files = listFolder(folder)
16+
}
17+
return { folder, files }
18+
})
19+
20+
ipcMain.handle('list-files', async (event, folder) => {
21+
console.log('ipcMain', 'list-files', folder)
22+
if (!folder) return []
23+
return listFolder(folder)
24+
})
25+
26+
ipcMain.handle('ilist-files', async (event, folder) => {
27+
console.log('ipcMain', 'ilist-files', folder)
28+
if (!folder) return []
29+
return ilistFolder(folder)
30+
})
31+
32+
ipcMain.handle('ilist-all-files', (event, folder) => {
33+
console.log('ipcMain', 'ilist-all-files', folder)
34+
if (!folder) return []
35+
return getAllFiles(folder)
36+
})
37+
38+
ipcMain.handle('load-file', (event, filePath) => {
39+
console.log('ipcMain', 'load-file', filePath)
40+
let content = fs.readFileSync(filePath)
41+
return content
42+
})
43+
44+
ipcMain.handle('save-file', (event, filePath, content) => {
45+
console.log('ipcMain', 'save-file', filePath, content)
46+
fs.writeFileSync(filePath, content, 'utf8')
47+
return true
48+
})
49+
50+
ipcMain.handle('update-folder', (event, folder) => {
51+
console.log('ipcMain', 'update-folder', folder)
52+
let files = fs.readdirSync(path.resolve(folder))
53+
// Filter out directories
54+
files = files.filter(f => {
55+
let filePath = path.resolve(folder, f)
56+
return !fs.lstatSync(filePath).isDirectory()
57+
})
58+
return { folder, files }
59+
})
60+
61+
ipcMain.handle('remove-file', (event, filePath) => {
62+
console.log('ipcMain', 'remove-file', filePath)
63+
fs.unlinkSync(filePath)
64+
return true
65+
})
66+
67+
ipcMain.handle('rename-file', (event, filePath, newFilePath) => {
68+
console.log('ipcMain', 'rename-file', filePath, newFilePath)
69+
fs.renameSync(filePath, newFilePath)
70+
return true
71+
})
72+
73+
ipcMain.handle('create-folder', (event, folderPath) => {
74+
console.log('ipcMain', 'create-folder', folderPath)
75+
try {
76+
fs.mkdirSync(folderPath, { recursive: true })
77+
} catch(e) {
78+
console.log('error', e)
79+
return false
80+
}
81+
return true
82+
})
83+
84+
ipcMain.handle('remove-folder', (event, folderPath) => {
85+
console.log('ipcMain', 'remove-folder', folderPath)
86+
fs.rmdirSync(folderPath, { recursive: true, force: true })
87+
return true
88+
})
89+
90+
ipcMain.handle('file-exists', (event, filePath) => {
91+
console.log('ipcMain', 'file-exists', filePath)
92+
try {
93+
fs.accessSync(filePath, fs.constants.F_OK)
94+
return true
95+
} catch(err) {
96+
return false
97+
}
98+
})
99+
// WINDOW MANAGEMENT
100+
101+
ipcMain.handle('set-window-size', (event, minWidth, minHeight) => {
102+
console.log('ipcMain', 'set-window-size', minWidth, minHeight)
103+
if (!win) {
104+
console.log('No window defined')
105+
return false
106+
}
107+
108+
win.setMinimumSize(minWidth, minHeight)
109+
})
110+
}

backend/menu.js

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
const { app, Menu } = require('electron')
2+
const path = require('path')
3+
const openAboutWindow = require('about-window').default
4+
5+
module.exports = function registerMenu(win) {
6+
const isMac = process.platform === 'darwin'
7+
const isDev = !app.isPackaged
8+
const template = [
9+
...(isMac ? [{
10+
label: app.name,
11+
submenu: [
12+
{ role: 'about'},
13+
{ type: 'separator' },
14+
{ role: 'services' },
15+
{ type: 'separator' },
16+
{ role: 'hide' },
17+
{ role: 'hideOthers' },
18+
{ role: 'unhide' },
19+
{ type: 'separator' },
20+
{ role: 'quit' }
21+
]
22+
}] : []),
23+
{
24+
label: 'File',
25+
submenu: [
26+
isMac ? { role: 'close' } : { role: 'quit' }
27+
]
28+
},
29+
{
30+
label: 'Edit',
31+
submenu: [
32+
{ role: 'undo' },
33+
{ role: 'redo' },
34+
{ type: 'separator' },
35+
{ role: 'cut' },
36+
{ role: 'copy' },
37+
{ role: 'paste' },
38+
...(isMac ? [
39+
{ role: 'pasteAndMatchStyle' },
40+
{ role: 'selectAll' },
41+
{ type: 'separator' },
42+
{
43+
label: 'Speech',
44+
submenu: [
45+
{ role: 'startSpeaking' },
46+
{ role: 'stopSpeaking' }
47+
]
48+
}
49+
] : [
50+
{ type: 'separator' },
51+
{ role: 'selectAll' }
52+
])
53+
]
54+
},
55+
{
56+
label: 'View',
57+
submenu: [
58+
{ role: 'reload' },
59+
{ type: 'separator' },
60+
{ role: 'resetZoom' },
61+
{ role: 'zoomIn' },
62+
{ role: 'zoomOut' },
63+
{ type: 'separator' },
64+
{ role: 'togglefullscreen' },
65+
...(isDev ? [
66+
{ type: 'separator' },
67+
{ role: 'toggleDevTools' },
68+
]:[
69+
])
70+
]
71+
},
72+
{
73+
label: 'Window',
74+
submenu: [
75+
{ role: 'minimize' },
76+
{ role: 'zoom' },
77+
...(isMac ? [
78+
{ type: 'separator' },
79+
{ role: 'front' },
80+
{ type: 'separator' },
81+
{ role: 'window' }
82+
] : [
83+
{ role: 'close' }
84+
])
85+
]
86+
},
87+
{
88+
role: 'help',
89+
submenu: [
90+
{
91+
label: 'Learn More',
92+
click: async () => {
93+
const { shell } = require('electron')
94+
await shell.openExternal('https://github.com/arduino/lab-micropython-editor')
95+
}
96+
},
97+
{
98+
label: 'Report an issue',
99+
click: async () => {
100+
const { shell } = require('electron')
101+
await shell.openExternal('https://github.com/arduino/lab-micropython-editor/issues')
102+
}
103+
},
104+
{
105+
label:'Info about this app',
106+
click: () => {
107+
openAboutWindow({
108+
icon_path: path.resolve(__dirname, '../ui/arduino/media/about_image.png'),
109+
css_path: path.resolve(__dirname, '../ui/arduino/views/about.css'),
110+
// about_page_dir: path.resolve(__dirname, '../ui/arduino/views/'),
111+
copyright: '© Arduino SA 2022',
112+
package_json_dir: path.resolve(__dirname, '..'),
113+
bug_report_url: "https://github.com/arduino/lab-micropython-editor/issues",
114+
bug_link_text: "report an issue",
115+
homepage: "https://labs.arduino.cc",
116+
use_version_info: false,
117+
win_options: {
118+
parent: win,
119+
modal: true,
120+
},
121+
show_close_button: 'Close',
122+
})
123+
}
124+
},
125+
]
126+
}
127+
]
128+
129+
const menu = Menu.buildFromTemplate(template)
130+
131+
app.setAboutPanelOptions({
132+
applicationName: app.name,
133+
applicationVersion: app.getVersion(),
134+
copyright: app.copyright,
135+
credits: '(See "Info about this app" in the Help menu)',
136+
authors: ['Arduino'],
137+
website: 'https://arduino.cc',
138+
iconPath: path.join(__dirname, '../assets/image.png'),
139+
})
140+
141+
Menu.setApplicationMenu(menu)
142+
143+
}

0 commit comments

Comments
 (0)