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

Lines changed: 3 additions & 2 deletions
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

Lines changed: 62 additions & 0 deletions
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

Lines changed: 110 additions & 0 deletions
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

Lines changed: 143 additions & 0 deletions
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)
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