mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-10-24 18:48:33 +00:00
Compare commits
220 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41bf1ce6dc | ||
|
|
1aa944b25e | ||
|
|
b220ce4c5f | ||
|
|
15c66442cc | ||
|
|
3a56c16ab2 | ||
|
|
cea62e315a | ||
|
|
6618816330 | ||
|
|
2577451c15 | ||
|
|
a1ab42d282 | ||
|
|
8dbcb8bbb6 | ||
|
|
1be76aa264 | ||
|
|
21cd580e41 | ||
|
|
8839e318f4 | ||
|
|
bef9185c6c | ||
|
|
b8bd444def | ||
|
|
b8fdb03433 | ||
|
|
4290497edc | ||
|
|
7a89a8048f | ||
|
|
84be804df0 | ||
|
|
f07441a7fc | ||
|
|
08f127711f | ||
|
|
d1ae15a838 | ||
|
|
7ca8830a02 | ||
|
|
13b7a7e1d8 | ||
|
|
c5c9b8674b | ||
|
|
6447191bf5 | ||
|
|
e78ed85761 | ||
|
|
9bc520ccf9 | ||
|
|
cfdb00529c | ||
|
|
8ccea24452 | ||
|
|
ad563d26ba | ||
|
|
f0a628534e | ||
|
|
1b95242ad1 | ||
|
|
729588770e | ||
|
|
6b2046e090 | ||
|
|
80673ad18f | ||
|
|
2f33038695 | ||
|
|
6154d1e8d5 | ||
|
|
557ec2ae42 | ||
|
|
8c49c04359 | ||
|
|
29ebf055e6 | ||
|
|
71842abfa3 | ||
|
|
ed660ccd64 | ||
|
|
6af22ec9b8 | ||
|
|
79f588d067 | ||
|
|
e3e4a96db3 | ||
|
|
4129544738 | ||
|
|
a866bde4d1 | ||
|
|
92b6208a76 | ||
|
|
12deceef19 | ||
|
|
f635751a8c | ||
|
|
85bf50213d | ||
|
|
5aeb2d388e | ||
|
|
b6b4c75718 | ||
|
|
c4a8062df4 | ||
|
|
6e89e89738 | ||
|
|
c7242ca34f | ||
|
|
a4e5e65286 | ||
|
|
80549db289 | ||
|
|
eb7b3ad683 | ||
|
|
9efcbcf2ae | ||
|
|
d22c0b9e55 | ||
|
|
5d2f09354d | ||
|
|
fcd6c792e3 | ||
|
|
94233a1a19 | ||
|
|
7fb32766ca | ||
|
|
85cf8757c4 | ||
|
|
41c56c1126 | ||
|
|
4c503c0c5e | ||
|
|
3f180b6059 | ||
|
|
6a8a76f720 | ||
|
|
9a27252d91 | ||
|
|
4e683b237d | ||
|
|
a2a9cbb02e | ||
|
|
dd10436051 | ||
|
|
e79d42d6bd | ||
|
|
a9c9dcde7b | ||
|
|
62b18ccbed | ||
|
|
0a8b6bc41e | ||
|
|
b1388be5f9 | ||
|
|
b4848f62fa | ||
|
|
f359843635 | ||
|
|
6448b447b3 | ||
|
|
c7bb3abf19 | ||
|
|
c3e2aa4feb | ||
|
|
63cd2701b4 | ||
|
|
35ac73181b | ||
|
|
840cde872c | ||
|
|
c2008460b0 | ||
|
|
435fdcdf7f | ||
|
|
7e6343e60e | ||
|
|
fdda4a72d0 | ||
|
|
7077303a36 | ||
|
|
acd9bf1354 | ||
|
|
d92fc25769 | ||
|
|
f9a98d708e | ||
|
|
df33c5689f | ||
|
|
2dc73eb3b5 | ||
|
|
f6444b2570 | ||
|
|
186180800f | ||
|
|
20bc3c6f13 | ||
|
|
125ee70fa3 | ||
|
|
3cfb1450c0 | ||
|
|
9643dd397f | ||
|
|
7c1ebf273c | ||
|
|
05850b5f27 | ||
|
|
21dedd4b09 | ||
|
|
90d7d88162 | ||
|
|
3efb5a4e08 | ||
|
|
4353bfb5b9 | ||
|
|
8971dc4c5f | ||
|
|
2be54944bf | ||
|
|
2f84b5c6b7 | ||
|
|
cebe15ef69 | ||
|
|
1dda5dd95b | ||
|
|
de1caf1451 | ||
|
|
fb6785c5d3 | ||
|
|
de1f341d19 | ||
|
|
817a28291b | ||
|
|
06ef598806 | ||
|
|
cc6a0ae212 | ||
|
|
65a58ce2be | ||
|
|
6331b7ddfd | ||
|
|
f3667f0270 | ||
|
|
aa4f216544 | ||
|
|
065f9f042b | ||
|
|
765fcdfba7 | ||
|
|
476e658fea | ||
|
|
a96ed31a45 | ||
|
|
ed4f23a32a | ||
|
|
4949df7395 | ||
|
|
55923be7fd | ||
|
|
2ef0d1d0db | ||
|
|
7244694bd3 | ||
|
|
d9e71c7e0d | ||
|
|
daedae1ba7 | ||
|
|
ac4e877a10 | ||
|
|
09243ff74d | ||
|
|
5496edbb42 | ||
|
|
62eff29172 | ||
|
|
2220e66f4b | ||
|
|
c98ec29810 | ||
|
|
768958dfd5 | ||
|
|
8e747e19a6 | ||
|
|
b1c69aef9f | ||
|
|
ec6b5ed3f3 | ||
|
|
60bf58ac0f | ||
|
|
c2675efea4 | ||
|
|
7d04c7efb8 | ||
|
|
fb542e2e40 | ||
|
|
3e0842e93a | ||
|
|
90add23dae | ||
|
|
6ff5405337 | ||
|
|
c564572718 | ||
|
|
79731304c1 | ||
|
|
2046c0bdee | ||
|
|
3eebd580d8 | ||
|
|
d8454456a9 | ||
|
|
dac9c6437e | ||
|
|
7f33b62e0b | ||
|
|
459e55a69a | ||
|
|
f76f4543e9 | ||
|
|
9b255ac072 | ||
|
|
592086466c | ||
|
|
c81ee1ede2 | ||
|
|
6d2816a7f3 | ||
|
|
dd69092afd | ||
|
|
76d0f5a464 | ||
|
|
206b65f138 | ||
|
|
6d590cd111 | ||
|
|
e8e3c3dc4c | ||
|
|
e6e042c856 | ||
|
|
9298a8cc55 | ||
|
|
98764b56aa | ||
|
|
9f7aec4091 | ||
|
|
e636e06a7e | ||
|
|
c6311ecb1d | ||
|
|
cd94608aee | ||
|
|
b82d5e4f0b | ||
|
|
9ae721292d | ||
|
|
41c603937c | ||
|
|
d5589c435f | ||
|
|
d809daa20a | ||
|
|
f9641a3d76 | ||
|
|
59553bf81f | ||
|
|
037efbaba2 | ||
|
|
a936e4c505 | ||
|
|
7c2a295631 | ||
|
|
c5796677f8 | ||
|
|
b6306c330f | ||
|
|
692c3f6e3f | ||
|
|
8d79bb3ffb | ||
|
|
ec50ea673c | ||
|
|
502e9042ad | ||
|
|
66f429c478 | ||
|
|
0dc45daf01 | ||
|
|
3fcf5a6ac9 | ||
|
|
b24d440e22 | ||
|
|
af9b9fbeab | ||
|
|
37db6c4b43 | ||
|
|
9d5ad9b003 | ||
|
|
ded838b4e8 | ||
|
|
82df8a6add | ||
|
|
2914379586 | ||
|
|
6b25659fa6 | ||
|
|
27dc6f9816 | ||
|
|
b78ddbeb64 | ||
|
|
db78c8ac2d | ||
|
|
95c5536060 | ||
|
|
0aa34b1169 | ||
|
|
b7d951b809 | ||
|
|
e11d9e0c78 | ||
|
|
436e660d47 | ||
|
|
23a967bd4c | ||
|
|
17fab651e5 | ||
|
|
df7225c32b | ||
|
|
69f63668b2 | ||
|
|
d29ed24e49 | ||
|
|
a5294417c3 | ||
|
|
d6637c44e5 |
10
.gitignore
vendored
10
.gitignore
vendored
@@ -2,12 +2,16 @@ node_modules/
|
|||||||
# .node_modules is a hack for the electron builder.
|
# .node_modules is a hack for the electron builder.
|
||||||
.node_modules/
|
.node_modules/
|
||||||
lib/
|
lib/
|
||||||
build/
|
|
||||||
downloads/
|
downloads/
|
||||||
|
build/
|
||||||
!electron/build/
|
!electron/build/
|
||||||
src-gen/
|
src-gen/
|
||||||
arduino-ide-*/webpack.config.js
|
browser-app/webpack.config.js
|
||||||
.DS_Store
|
electron-app/webpack.config.js
|
||||||
/workspace/static
|
/workspace/static
|
||||||
|
.DS_Store
|
||||||
# switching from `electron` to `browser` in dev mode.
|
# switching from `electron` to `browser` in dev mode.
|
||||||
.browser_modules
|
.browser_modules
|
||||||
|
# LS logs
|
||||||
|
inols*.log
|
||||||
|
yarn-error.log
|
||||||
|
|||||||
@@ -3,12 +3,16 @@ image:
|
|||||||
|
|
||||||
ports:
|
ports:
|
||||||
- port: 3000
|
- port: 3000
|
||||||
onOpen: open-browser
|
onOpen: open-preview
|
||||||
|
- port: 5900
|
||||||
|
onOpen: ignore
|
||||||
|
- port: 6080
|
||||||
|
onOpen: ignore
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- init: >
|
- init: >
|
||||||
yarn &&
|
yarn &&
|
||||||
yarn --cwd ./arduino-ide-browser start
|
yarn --cwd ./browser-app start
|
||||||
|
|
||||||
github:
|
github:
|
||||||
prebuilds:
|
prebuilds:
|
||||||
|
|||||||
93
.vscode/launch.json
vendored
93
.vscode/launch.json
vendored
@@ -6,19 +6,29 @@
|
|||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"type": "node",
|
"type": "node",
|
||||||
"request": "launch",
|
"request": "attach",
|
||||||
"name": "Launch Node.js Program",
|
"name": "Attach by Process ID",
|
||||||
"program": "${file}"
|
"processId": "${command:PickProcess}"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "node",
|
"type": "node",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Launch Backend",
|
"name": "App (Electron)",
|
||||||
"program": "${workspaceRoot}/arduino-ide-browser/src-gen/backend/main.js",
|
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
|
||||||
|
"windows": {
|
||||||
|
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd",
|
||||||
|
"env": {
|
||||||
|
"NODE_ENV": "development",
|
||||||
|
"NODE_PRESERVE_SYMLINKS": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"program": "${workspaceRoot}/electron-app/src-gen/frontend/electron-main.js",
|
||||||
|
"protocol": "inspector",
|
||||||
"args": [
|
"args": [
|
||||||
"--hostname=0.0.0.0",
|
"--log-level=debug",
|
||||||
"--port=3000",
|
"--hostname=localhost",
|
||||||
"--no-cluster",
|
"--no-cluster",
|
||||||
|
"--remote-debugging-port=9222",
|
||||||
"--no-app-auto-install"
|
"--no-app-auto-install"
|
||||||
],
|
],
|
||||||
"env": {
|
"env": {
|
||||||
@@ -26,13 +36,76 @@
|
|||||||
},
|
},
|
||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"outFiles": [
|
"outFiles": [
|
||||||
"${workspaceRoot}/arduino-ide-browser/src-gen/backend/*.js",
|
"${workspaceRoot}/electron-app/src-gen/backend/*.js",
|
||||||
"${workspaceRoot}/arduino-ide-browser/lib/**/*.js",
|
"${workspaceRoot}/electron-app/src-gen/frontend/*.js",
|
||||||
"${workspaceRoot}/arduino-ide-extension/*/lib/**/*.js"
|
"${workspaceRoot}/electron-app/lib/**/*.js",
|
||||||
|
"${workspaceRoot}/arduino-ide-extension/lib/**/*.js"
|
||||||
],
|
],
|
||||||
"smartStep": true,
|
"smartStep": true,
|
||||||
"internalConsoleOptions": "openOnSessionStart",
|
"internalConsoleOptions": "openOnSessionStart",
|
||||||
"outputCapture": "std"
|
"outputCapture": "std"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "App (Browser)",
|
||||||
|
"program": "${workspaceRoot}/browser-app/src-gen/backend/main.js",
|
||||||
|
"args": [
|
||||||
|
"--hostname=0.0.0.0",
|
||||||
|
"--port=3000",
|
||||||
|
"--no-cluster",
|
||||||
|
"--no-app-auto-install"
|
||||||
|
],
|
||||||
|
"windows": {
|
||||||
|
"env": {
|
||||||
|
"NODE_ENV": "development",
|
||||||
|
"NODE_PRESERVE_SYMLINKS": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"NODE_ENV": "development"
|
||||||
|
},
|
||||||
|
"sourceMaps": true,
|
||||||
|
"outFiles": [
|
||||||
|
"${workspaceRoot}/browser-app/src-gen/backend/*.js",
|
||||||
|
"${workspaceRoot}/browser-app/lib/**/*.js",
|
||||||
|
"${workspaceRoot}/arduino-ide-extension/lib/**/*.js"
|
||||||
|
],
|
||||||
|
"smartStep": true,
|
||||||
|
"internalConsoleOptions": "openOnSessionStart",
|
||||||
|
"outputCapture": "std"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "App (Browser - Debug CLI daemon)",
|
||||||
|
"program": "${workspaceRoot}/browser-app/src-gen/backend/main.js",
|
||||||
|
"args": [
|
||||||
|
"--hostname=0.0.0.0",
|
||||||
|
"--port=3000",
|
||||||
|
"--no-cluster",
|
||||||
|
"--no-app-auto-install",
|
||||||
|
"--debug-cli=true"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"NODE_ENV": "development"
|
||||||
|
},
|
||||||
|
"sourceMaps": true,
|
||||||
|
"outFiles": [
|
||||||
|
"${workspaceRoot}/browser-app/src-gen/backend/*.js",
|
||||||
|
"${workspaceRoot}/browser-app/lib/**/*.js",
|
||||||
|
"${workspaceRoot}/arduino-ide-extension/lib/**/*.js"
|
||||||
|
],
|
||||||
|
"smartStep": true,
|
||||||
|
"internalConsoleOptions": "openOnSessionStart",
|
||||||
|
"outputCapture": "std"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Packager",
|
||||||
|
"program": "${workspaceRoot}/electron/packager/index.js",
|
||||||
|
"cwd": "${workspaceFolder}/electron/packager"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
21
.vscode/settings.json
vendored
Normal file
21
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"tslint.enable": true,
|
||||||
|
"tslint.configFile": "./tslint.json",
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"files.exclude": {
|
||||||
|
"**/lib": false
|
||||||
|
},
|
||||||
|
"editor.insertSpaces": true,
|
||||||
|
"editor.detectIndentation": false,
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.tabSize": 4
|
||||||
|
},
|
||||||
|
"[json]": {
|
||||||
|
"editor.tabSize": 2
|
||||||
|
},
|
||||||
|
"[jsonc]": {
|
||||||
|
"editor.tabSize": 2
|
||||||
|
},
|
||||||
|
"files.insertFinalNewline": true,
|
||||||
|
"typescript.tsdk": "node_modules/typescript/lib"
|
||||||
|
}
|
||||||
16
.vscode/tasks.json
vendored
16
.vscode/tasks.json
vendored
@@ -4,9 +4,9 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
{
|
||||||
"label": "Arduino-PoC - Start Browser Example",
|
"label": "Arduino Editor - Start Browser Example",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "yarn --cwd ./arduino-ide-browser start",
|
"command": "yarn --cwd ./browser-app start",
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"reveal": "always",
|
"reveal": "always",
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Arduino-PoC - Watch Theia Extension",
|
"label": "Arduino Editor - Watch Theia Extension",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "yarn --cwd ./arduino-ide-extension watch",
|
"command": "yarn --cwd ./arduino-ide-extension watch",
|
||||||
"group": "build",
|
"group": "build",
|
||||||
@@ -26,9 +26,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Arduino-PoC - Watch Browser Example",
|
"label": "Arduino Editor - Watch Browser Example",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "yarn --cwd ./arduino-ide-browser watch",
|
"command": "yarn --cwd ./browser-app watch",
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"reveal": "always",
|
"reveal": "always",
|
||||||
@@ -37,11 +37,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Arduino-PoC - Watch All",
|
"label": "Arduino Editor - Watch All",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"dependsOn": [
|
"dependsOn": [
|
||||||
"Arduino-PoC - Watch Theia Extension",
|
"Arduino Editor - Watch Theia Extension",
|
||||||
"Arduino-PoC - Watch Browser Example"
|
"Arduino Editor - Watch Browser Example"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM gitpod/workspace-full
|
FROM gitpod/workspace-full-vnc
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
RUN apt-get update -q --fix-missing && \
|
RUN apt-get update -q --fix-missing && \
|
||||||
@@ -7,7 +7,8 @@ RUN apt-get update -q --fix-missing && \
|
|||||||
build-essential \
|
build-essential \
|
||||||
libssl-dev \
|
libssl-dev \
|
||||||
golang-go \
|
golang-go \
|
||||||
libxkbfile-dev
|
libxkbfile-dev \
|
||||||
|
libnss3-dev
|
||||||
|
|
||||||
RUN set -ex && \
|
RUN set -ex && \
|
||||||
tmpdir=$(mktemp -d) && \
|
tmpdir=$(mktemp -d) && \
|
||||||
|
|||||||
20
README.md
20
README.md
@@ -1,4 +1,4 @@
|
|||||||
# Arduino IDE PoC
|
# Arduino Pro IDE
|
||||||
|
|
||||||
[](https://dev.azure.com/typefox/Arduino/_build/latest?definitionId=4&branchName=master)
|
[](https://dev.azure.com/typefox/Arduino/_build/latest?definitionId=4&branchName=master)
|
||||||
|
|
||||||
@@ -9,12 +9,15 @@ It's built on top of a [fork of the arduino-cli](https://github.com/typefox/ardu
|
|||||||
|
|
||||||
## How to try (offline)
|
## How to try (offline)
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
You should be able to build Theia locally. The requirements are defined [here](https://github.com/theia-ide/theia/blob/master/doc/Developing.md#prerequisites).
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone https://github.com/bcmi-labs/arduino-editor
|
git clone https://github.com/bcmi-labs/arduino-editor
|
||||||
cd arduino-editor
|
cd arduino-editor
|
||||||
yarn
|
yarn
|
||||||
yarn rebuild:electron
|
yarn rebuild:electron
|
||||||
yarn --cwd arduino-ide-electron start
|
yarn --cwd electron-app start
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to switch back to the browser-based example, execute the following in the repository root
|
If you want to switch back to the browser-based example, execute the following in the repository root
|
||||||
@@ -23,13 +26,16 @@ yarn rebuild:browser
|
|||||||
```
|
```
|
||||||
Then you can start the browser example again:
|
Then you can start the browser example again:
|
||||||
```
|
```
|
||||||
yarn --cwd arduino-ide-browser start
|
yarn --cwd browser-app start
|
||||||
```
|
```
|
||||||
|
|
||||||
## Arduino-PoC Electron Application
|
Click [here](./arduino-ide-extension/README.md) for more details on various IDE services, and the Arduino Pro IDE implementation in general.
|
||||||
|
|
||||||
|
|
||||||
|
## Arduino Pro IDE Electron Application
|
||||||
The project is built on [Azure DevOps](https://dev.azure.com/typefox/Arduino).
|
The project is built on [Azure DevOps](https://dev.azure.com/typefox/Arduino).
|
||||||
|
|
||||||
Currently, we provide the Arduino-PoC for the following platforms:
|
Currently, we provide the Arduino Pro IDE for the following platforms:
|
||||||
- Windows,
|
- Windows,
|
||||||
- macOS, and
|
- macOS, and
|
||||||
- Linux.
|
- Linux.
|
||||||
@@ -38,7 +44,7 @@ You can download the latest release applications fom [here](https://github.com/b
|
|||||||
If you want to get a nightly build, go to the [Azure DevOps page](https://dev.azure.com/typefox/Arduino/_build?definitionId=4),
|
If you want to get a nightly build, go to the [Azure DevOps page](https://dev.azure.com/typefox/Arduino/_build?definitionId=4),
|
||||||
and follow the steps from below.
|
and follow the steps from below.
|
||||||
|
|
||||||

|
<img width="500" src="static/download_01.gif">
|
||||||

|
<img width="500" src="static/download_02.gif">
|
||||||
|
|
||||||
Click [here](./electron/README.md) for more details on the CI/CD, the GitHub release, and the build process in general.
|
Click [here](./electron/README.md) for more details on the CI/CD, the GitHub release, and the build process in general.
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
{
|
|
||||||
"private": true,
|
|
||||||
"name": "arduino-ide-electron",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@theia/core": "next",
|
|
||||||
"@theia/editor": "next",
|
|
||||||
"@theia/electron": "next",
|
|
||||||
"@theia/file-search": "next",
|
|
||||||
"@theia/filesystem": "next",
|
|
||||||
"@theia/languages": "next",
|
|
||||||
"@theia/messages": "next",
|
|
||||||
"@theia/monaco": "next",
|
|
||||||
"@theia/navigator": "next",
|
|
||||||
"@theia/preferences": "next",
|
|
||||||
"@theia/process": "next",
|
|
||||||
"@theia/terminal": "next",
|
|
||||||
"@theia/workspace": "next",
|
|
||||||
"@theia/textmate-grammars": "next",
|
|
||||||
"arduino-ide-extension": "0.0.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@theia/cli": "next",
|
|
||||||
"electron": "^4.2.0"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"prepare": "theia build --mode development",
|
|
||||||
"start": "theia start --root-dir=../workspace",
|
|
||||||
"watch": "theia build --watch --mode development"
|
|
||||||
},
|
|
||||||
"theia": {
|
|
||||||
"target": "electron",
|
|
||||||
"frontend": {
|
|
||||||
"config": {
|
|
||||||
"applicationName": "Arduino-PoC",
|
|
||||||
"defaultTheme": "arduino-theme",
|
|
||||||
"preferences": {
|
|
||||||
"editor.autoSave": "on"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"generator": {
|
|
||||||
"config": {
|
|
||||||
"preloadTemplate": "<div class='theia-preload' style='background-color: rgb(237, 241, 242);'></div>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
52
arduino-ide-extension/README.md
Normal file
52
arduino-ide-extension/README.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
## Arduino IDE Extension
|
||||||
|
|
||||||
|
Arduino Pro IDE is based on Theia, and most of its IDE features, UIs and customizations are implemented in this Theia extension.
|
||||||
|
|
||||||
|
### IDE Services
|
||||||
|
|
||||||
|
IDE services typically have a backend part in [src/node/](./src/node/) and a front-end part in [src/browser/](./src/browser/).
|
||||||
|
|
||||||
|
#### Boards Service
|
||||||
|
|
||||||
|
The Boards Service continuously checks the computer's ports, in order to detect when you connect or disconnect an Arduino board.
|
||||||
|
|
||||||
|
The Boards Manager lists all the known board types, and allows downloading new cores to get additional board types.
|
||||||
|
|
||||||
|
- [src/common/protocol/boards-service.ts](./src/common/protocol/boards-service.ts) implements the common classes and interfaces
|
||||||
|
- [src/node/boards-service-impl.ts](./src/node/boards-service-impl.ts) implements the service backend:
|
||||||
|
- discovering ports & boards
|
||||||
|
- searching for compatible board types
|
||||||
|
- installing new board types
|
||||||
|
- [src/browser/boards/boards-list-widget.ts](./src/browser/boards/boards-service-client-impl.ts) implements the Boards Manager front-end:
|
||||||
|
- browsing/searching available board types
|
||||||
|
- installing new board types
|
||||||
|
|
||||||
|
#### Core Service
|
||||||
|
|
||||||
|
The Core Service is responsible for building your sketches and uploading them to a board.
|
||||||
|
|
||||||
|
- [src/common/protocol/core-service.ts](./src/common/protocol/core-service.ts) implements the common classes and interfaces
|
||||||
|
- [src/node/core-service-impl.ts](./src/node/core-service-impl.ts) implements the service backend:
|
||||||
|
- compiling a sketch for a selected board type
|
||||||
|
- uploading a sketch to a connected board
|
||||||
|
|
||||||
|
#### Monitor Service
|
||||||
|
|
||||||
|
The Monitor Service allows getting information back from sketches running on your Arduino boards.
|
||||||
|
|
||||||
|
- [src/common/protocol/monitor-service.ts](./src/common/protocol/monitor-service.ts) implements the common classes and interfaces
|
||||||
|
- [src/node/monitor-service-impl.ts](./src/node/monitor-service-impl.ts) implements the service backend:
|
||||||
|
- connecting to / disconnecting from a board
|
||||||
|
- receiving and sending data
|
||||||
|
- [src/browser/monitor/monitor-widget.tsx](./src/browser/monitor/monitor-widget.tsx) implements the serial monitor front-end:
|
||||||
|
- viewing the output from a connected board
|
||||||
|
- entering data to send to the board
|
||||||
|
|
||||||
|
#### Config Service
|
||||||
|
|
||||||
|
The Config Service knows about your system, like for example the default sketch locations.
|
||||||
|
|
||||||
|
- [src/common/protocol/config-service.ts](./src/common/protocol/config-service.ts) implements the common classes and interfaces
|
||||||
|
- [src/node/config-service-impl.ts](./src/node/config-service-impl.ts) implements the service backend:
|
||||||
|
- getting the `arduino-cli` version and configuration
|
||||||
|
- checking whether a file is in a data or sketch directory
|
||||||
@@ -1,49 +1,85 @@
|
|||||||
{
|
{
|
||||||
"name": "arduino-ide-extension",
|
"name": "arduino-ide-extension",
|
||||||
"version": "0.0.1",
|
"version": "0.0.4",
|
||||||
"description": "An extension for Theia building the Arduino IDE",
|
"description": "An extension for Theia building the Arduino IDE",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.10.0"
|
"node": ">=10.10.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grpc/grpc-js": "^0.4.0",
|
"@grpc/grpc-js": "^0.6.12",
|
||||||
|
"@theia/application-package": "next",
|
||||||
"@theia/core": "next",
|
"@theia/core": "next",
|
||||||
|
"@theia/cpp": "next",
|
||||||
"@theia/editor": "next",
|
"@theia/editor": "next",
|
||||||
"@theia/filesystem": "next",
|
"@theia/filesystem": "next",
|
||||||
|
"@theia/git": "next",
|
||||||
"@theia/languages": "next",
|
"@theia/languages": "next",
|
||||||
"@theia/markers": "next",
|
"@theia/markers": "next",
|
||||||
"@theia/monaco": "next",
|
"@theia/monaco": "next",
|
||||||
"@theia/outline-view": "next",
|
|
||||||
"@theia/workspace": "next",
|
|
||||||
"@theia/navigator": "next",
|
"@theia/navigator": "next",
|
||||||
|
"@theia/outline-view": "next",
|
||||||
|
"@theia/search-in-workspace": "next",
|
||||||
"@theia/terminal": "next",
|
"@theia/terminal": "next",
|
||||||
|
"@theia/workspace": "next",
|
||||||
|
"@types/dateformat": "^3.0.1",
|
||||||
|
"@types/google-protobuf": "^3.7.1",
|
||||||
|
"@types/ps-tree": "^1.1.0",
|
||||||
|
"@types/react-select": "^3.0.0",
|
||||||
"@types/which": "^1.3.1",
|
"@types/which": "^1.3.1",
|
||||||
"css-element-queries": "^1.2.0",
|
"css-element-queries": "^1.2.0",
|
||||||
|
"dateformat": "^3.0.3",
|
||||||
|
"google-protobuf": "^3.11.0",
|
||||||
"p-queue": "^5.0.0",
|
"p-queue": "^5.0.0",
|
||||||
|
"ps-tree": "^1.2.0",
|
||||||
|
"react-select": "^3.0.4",
|
||||||
|
"semver": "^6.3.0",
|
||||||
|
"string-natural-compare": "^2.0.3",
|
||||||
|
"tree-kill": "^1.2.1",
|
||||||
|
"upath": "^1.1.2",
|
||||||
"which": "^1.3.1"
|
"which": "^1.3.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "yarn download-cli && yarn run clean && yarn run build",
|
"prepare": "yarn download-cli && yarn download-ls && yarn run clean && yarn run build",
|
||||||
"clean": "rimraf lib",
|
"clean": "rimraf lib",
|
||||||
"download-cli": "node ./scripts/download-cli.js",
|
"download-cli": "node ./scripts/download-cli.js",
|
||||||
|
"download-ls": "node ./scripts/download-ls.js",
|
||||||
"generate-protocol": "node ./scripts/generate-protocol.js",
|
"generate-protocol": "node ./scripts/generate-protocol.js",
|
||||||
"lint": "tslint -c ./tslint.json --project ./tsconfig.json",
|
"lint": "tslint -c ./tslint.json --project ./tsconfig.json",
|
||||||
"build": "tsc && ncp ./src/node/cli-protocol/ ./lib/node/cli-protocol/ && yarn lint",
|
"build": "tsc && ncp ./src/node/cli-protocol/ ./lib/node/cli-protocol/ && yarn lint",
|
||||||
"watch": "tsc -w"
|
"watch": "tsc -w",
|
||||||
|
"test": "mocha \"./test/**/*.test.ts\""
|
||||||
|
},
|
||||||
|
"mocha": {
|
||||||
|
"require": [
|
||||||
|
"ts-node/register",
|
||||||
|
"reflect-metadata/Reflect"
|
||||||
|
],
|
||||||
|
"reporter": "spec",
|
||||||
|
"colors": true,
|
||||||
|
"watch-extensions": "ts,tsx",
|
||||||
|
"timeout": 10000
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/chai": "^4.2.7",
|
||||||
|
"@types/chai-string": "^1.4.2",
|
||||||
|
"@types/mocha": "^5.2.7",
|
||||||
|
"chai": "^4.2.0",
|
||||||
|
"chai-string": "^1.5.0",
|
||||||
"decompress": "^4.2.0",
|
"decompress": "^4.2.0",
|
||||||
"decompress-tarbz2": "^4.1.1",
|
"decompress-targz": "^4.1.1",
|
||||||
"decompress-unzip": "^4.0.1",
|
"decompress-unzip": "^4.0.1",
|
||||||
"download": "^7.1.0",
|
"download": "^7.1.0",
|
||||||
"grpc-tools": "^1.7.3",
|
"grpc-tools": "^1.8.0",
|
||||||
"grpc_tools_node_protoc_ts": "^2.5.0",
|
"grpc_tools_node_protoc_ts": "^2.5.8",
|
||||||
|
"mocha": "^7.0.0",
|
||||||
|
"moment": "^2.24.0",
|
||||||
"ncp": "^2.0.0",
|
"ncp": "^2.0.0",
|
||||||
"rimraf": "^2.6.1",
|
"rimraf": "^2.6.1",
|
||||||
"shelljs": "^0.8.3",
|
"shelljs": "^0.8.3",
|
||||||
|
"ts-node": "^8.6.2",
|
||||||
"tslint": "^5.5.0",
|
"tslint": "^5.5.0",
|
||||||
"typescript": "2.9.1",
|
"typescript": "3.5.3",
|
||||||
"uuid": "^3.2.1",
|
"uuid": "^3.2.1",
|
||||||
"yargs": "^11.1.0"
|
"yargs": "^11.1.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,41 +1,26 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
// The links to the downloads as of today (11.08.) are the followings:
|
// The links to the downloads as of today (02.09.) are the followings:
|
||||||
// - https://downloads.arduino.cc/arduino-cli/nightly/arduino-cli-nightly-latest-${FILE_NAME}
|
// In order to get the latest nightly build for your platform use the following links replacing <DATE> with the current date, using the format YYYYMMDD (i.e for 2019/Aug/06 use 20190806 )
|
||||||
// - https://downloads.arduino.cc/arduino-cli/arduino-cli-latest-${FILE_NAME}
|
// Linux 64 bit: https://downloads.arduino.cc/arduino-cli/nightly/arduino-cli_nightly-<DATE>_Linux_64bit.tar.gz
|
||||||
|
// Linux ARM 64 bit: https://downloads.arduino.cc/arduino-cli/nightly/arduino-cli_nightly-<DATE>_Linux_ARM64.tar.gz
|
||||||
|
// Windows 64 bit: https://downloads.arduino.cc/arduino-cli/nightly/arduino-cli_nightly-<DATE>_Windows_64bit.zip
|
||||||
|
// Mac OSX: https://downloads.arduino.cc/arduino-cli/nightly/arduino-cli_nightly-<DATE>_macOS_64bit.tar.gz
|
||||||
|
// [...]
|
||||||
|
// redirecting to latest generated builds by replacing latest with the latest available build date, using the format YYYYMMDD (i.e for 2019/Aug/06 latest is replaced with 20190806
|
||||||
|
|
||||||
(async () => {
|
(() => {
|
||||||
|
|
||||||
const DEFAULT_VERSION = 'nightly';
|
const DEFAULT_VERSION = '0.7.1'; // require('moment')().format('YYYYMMDD');
|
||||||
|
|
||||||
const os = require('os');
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const shell = require('shelljs');
|
const shell = require('shelljs');
|
||||||
const download = require('download');
|
const downloader = require('./downloader');
|
||||||
const decompress = require('decompress');
|
|
||||||
const unzip = require('decompress-unzip');
|
|
||||||
const untarbz = require('decompress-tarbz2');
|
|
||||||
|
|
||||||
process.on('unhandledRejection', (reason, _) => {
|
|
||||||
shell.echo(String(reason));
|
|
||||||
shell.exit(1);
|
|
||||||
throw reason;
|
|
||||||
});
|
|
||||||
process.on('uncaughtException', error => {
|
|
||||||
shell.echo(String(error));
|
|
||||||
shell.exit(1);
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
|
|
||||||
const yargs = require('yargs')
|
const yargs = require('yargs')
|
||||||
.option('cli-version', {
|
.option('cli-version', {
|
||||||
alias: 'cv',
|
alias: 'cv',
|
||||||
default: DEFAULT_VERSION,
|
default: DEFAULT_VERSION,
|
||||||
choices: [
|
describe: `The version of the 'arduino-cli' to download, or 'nightly-latest'. Defaults to ${DEFAULT_VERSION}.`
|
||||||
// 'latest', // TODO: How do we get the source for `latest`. Currently, `latest` is the `0.3.7-alpha.preview`.
|
|
||||||
'nightly'
|
|
||||||
],
|
|
||||||
describe: `The version of the 'arduino-cli' to download. Defaults to ${DEFAULT_VERSION}.`
|
|
||||||
})
|
})
|
||||||
.option('force-download', {
|
.option('force-download', {
|
||||||
alias: 'fd',
|
alias: 'fd',
|
||||||
@@ -49,32 +34,16 @@
|
|||||||
const { platform, arch } = process;
|
const { platform, arch } = process;
|
||||||
|
|
||||||
const build = path.join(__dirname, '..', 'build');
|
const build = path.join(__dirname, '..', 'build');
|
||||||
const downloads = path.join(__dirname, '..', 'downloads');
|
const cli = path.join(build, `arduino-cli${platform === 'win32' ? '.exe' : ''}`);
|
||||||
const cli = path.join(build, `arduino-cli${os.platform() === 'win32' ? '.exe' : ''}`);
|
|
||||||
|
|
||||||
if (fs.existsSync(cli) && !force) {
|
|
||||||
shell.echo(`The 'arduino-cli' already exists at ${cli}. Skipping download.`);
|
|
||||||
shell.exit(0);
|
|
||||||
}
|
|
||||||
if (!fs.existsSync(build)) {
|
|
||||||
if (shell.mkdir('-p', build).code !== 0) {
|
|
||||||
shell.echo('Could not create new directory.');
|
|
||||||
shell.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (shell.rm('-rf', cli, downloads).code !== 0) {
|
|
||||||
shell.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const suffix = (() => {
|
const suffix = (() => {
|
||||||
switch (platform) {
|
switch (platform) {
|
||||||
case 'darwin': return 'macosx.zip';
|
case 'darwin': return 'macOS_64bit.tar.gz';
|
||||||
case 'win32': return 'windows.zip';
|
case 'win32': return 'Windows_64bit.zip';
|
||||||
case 'linux': {
|
case 'linux': {
|
||||||
switch (arch) {
|
switch (arch) {
|
||||||
case 'arm64': return 'linuxarm.tar.bz2';
|
case 'arm64': return 'Linux_ARM64.tar.gz';
|
||||||
case 'x32': return 'linux32.tar.bz2';
|
case 'x64': return 'Linux_64bit.tar.gz';
|
||||||
case 'x64': return 'linux64.tar.bz2';
|
|
||||||
default: return undefined;
|
default: return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,32 +55,7 @@
|
|||||||
shell.exit(1);
|
shell.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = `https://downloads.arduino.cc/arduino-cli/${version === 'nightly' ? 'nightly/' : ''}arduino-cli-${version}-latest-${suffix}`;
|
const url = `https://downloads.arduino.cc/arduino-cli${version.startsWith('nightly-') ? '/nightly' : ''}/arduino-cli_${version}_${suffix}`;
|
||||||
shell.echo(`>>> Downloading 'arduino-cli' from '${url}'...`);
|
downloader.downloadUnzipFile(url, cli, 'arduino-cli', force);
|
||||||
const data = await download(url);
|
|
||||||
shell.echo(`<<< Download succeeded.`);
|
|
||||||
shell.echo('>>> Decompressing CLI...');
|
|
||||||
const files = await decompress(data, downloads, {
|
|
||||||
plugins: [
|
|
||||||
unzip(),
|
|
||||||
untarbz()
|
|
||||||
]
|
|
||||||
});
|
|
||||||
shell.echo('<<< Decompressing succeeded.');
|
|
||||||
|
|
||||||
if (files.length !== 1) {
|
|
||||||
shell.echo('Error ocurred when decompressing the CLI.');
|
|
||||||
shell.exit(1);
|
|
||||||
}
|
|
||||||
if (shell.mv('-f', path.join(downloads, files[0].path), cli).code !== 0) {
|
|
||||||
shell.echo(`Could not move file to ${cli}.`);
|
|
||||||
shell.exit(1);
|
|
||||||
}
|
|
||||||
if (!fs.existsSync(cli)) {
|
|
||||||
shell.echo(`Could not find CLI at ${cli}.`);
|
|
||||||
shell.exit(1);
|
|
||||||
} else {
|
|
||||||
shell.echo('Done.');
|
|
||||||
}
|
|
||||||
|
|
||||||
})();
|
})();
|
||||||
72
arduino-ide-extension/scripts/download-ls.js
Executable file
72
arduino-ide-extension/scripts/download-ls.js
Executable file
@@ -0,0 +1,72 @@
|
|||||||
|
// @ts-check
|
||||||
|
// The links to the downloads as of today (28.08.2019) are the following:
|
||||||
|
// - https://downloads.arduino.cc/arduino-language-server/nightly/arduino-language-server_${SUFFIX}
|
||||||
|
// - https://downloads.arduino.cc/arduino-language-server/clangd/clangd_${VERSION}_${SUFFIX}
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
|
||||||
|
const DEFAULT_ALS_VERSION = 'nightly';
|
||||||
|
const DEFAULT_CLANGD_VERSION = '9.0.0';
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const shell = require('shelljs');
|
||||||
|
const downloader = require('./downloader');
|
||||||
|
|
||||||
|
const yargs = require('yargs')
|
||||||
|
.option('ls-version', {
|
||||||
|
alias: 'lv',
|
||||||
|
default: DEFAULT_ALS_VERSION,
|
||||||
|
choices: ['nightly'],
|
||||||
|
describe: `The version of the 'arduino-language-server' to download. Defaults to ${DEFAULT_ALS_VERSION}.`
|
||||||
|
})
|
||||||
|
.option('clangd-version', {
|
||||||
|
alias: 'cv',
|
||||||
|
default: DEFAULT_CLANGD_VERSION,
|
||||||
|
choices: ['8.0.1', '9.0.0'],
|
||||||
|
describe: `The version of 'clangd' to download. Defaults to ${DEFAULT_CLANGD_VERSION}.`
|
||||||
|
})
|
||||||
|
.option('force-download', {
|
||||||
|
alias: 'fd',
|
||||||
|
default: false,
|
||||||
|
describe: `If set, this script force downloads the 'arduino-language-server' even if it already exists on the file system.`
|
||||||
|
})
|
||||||
|
.version(false).parse();
|
||||||
|
|
||||||
|
const alsVersion = yargs['ls-version'];
|
||||||
|
const clangdVersion = yargs['clangd-version']
|
||||||
|
const force = yargs['force-download'];
|
||||||
|
const { platform, arch } = process;
|
||||||
|
|
||||||
|
const build = path.join(__dirname, '..', 'build');
|
||||||
|
const alsTarget = path.join(build, `arduino-language-server${platform === 'win32' ? '.exe' : ''}`);
|
||||||
|
|
||||||
|
let clangdTarget, alsSuffix, clangdSuffix;
|
||||||
|
switch (platform) {
|
||||||
|
case 'darwin':
|
||||||
|
clangdTarget = path.join(build, 'bin', 'clangd')
|
||||||
|
alsSuffix = 'Darwin_amd64.zip';
|
||||||
|
clangdSuffix = 'macos.zip';
|
||||||
|
break;
|
||||||
|
case 'linux':
|
||||||
|
clangdTarget = path.join(build, 'bin', 'clangd')
|
||||||
|
alsSuffix = 'Linux_amd64.zip';
|
||||||
|
clangdSuffix = 'linux.zip'
|
||||||
|
break;
|
||||||
|
case 'win32':
|
||||||
|
clangdTarget = path.join(build, 'clangd.exe')
|
||||||
|
alsSuffix = 'Windows_NT_amd64.zip';
|
||||||
|
clangdSuffix = 'windows.zip';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!alsSuffix) {
|
||||||
|
shell.echo(`The arduino-language-server is not available for ${platform} ${arch}.`);
|
||||||
|
shell.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const alsUrl = `https://downloads.arduino.cc/arduino-language-server/${alsVersion === 'nightly' ? 'nightly/arduino-language-server' : 'arduino-language-server_' + alsVersion}_${alsSuffix}`;
|
||||||
|
downloader.downloadUnzipAll(alsUrl, build, alsTarget, force);
|
||||||
|
|
||||||
|
const clangdUrl = `https://downloads.arduino.cc/arduino-language-server/clangd/clangd_${clangdVersion}_${clangdSuffix}`;
|
||||||
|
downloader.downloadUnzipAll(clangdUrl, build, clangdTarget, force);
|
||||||
|
|
||||||
|
})();
|
||||||
116
arduino-ide-extension/scripts/downloader.js
Normal file
116
arduino-ide-extension/scripts/downloader.js
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const shell = require('shelljs');
|
||||||
|
const download = require('download');
|
||||||
|
const decompress = require('decompress');
|
||||||
|
const unzip = require('decompress-unzip');
|
||||||
|
const untargz = require('decompress-targz');
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (reason, _) => {
|
||||||
|
shell.echo(String(reason));
|
||||||
|
shell.exit(1);
|
||||||
|
throw reason;
|
||||||
|
});
|
||||||
|
process.on('uncaughtException', error => {
|
||||||
|
shell.echo(String(error));
|
||||||
|
shell.exit(1);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param url {string} Download URL
|
||||||
|
* @param targetFile {string} Path to the file to copy from the decompressed archive
|
||||||
|
* @param filePrefix {string} Prefix of the file name found in the archive
|
||||||
|
* @param force {boolean} Whether to download even if the target file exists
|
||||||
|
*/
|
||||||
|
exports.downloadUnzipFile = async (url, targetFile, filePrefix, force) => {
|
||||||
|
if (fs.existsSync(targetFile) && !force) {
|
||||||
|
shell.echo(`Skipping download because file already exists: ${targetFile}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!fs.existsSync(path.dirname(targetFile))) {
|
||||||
|
if (shell.mkdir('-p', path.dirname(targetFile)).code !== 0) {
|
||||||
|
shell.echo('Could not create new directory.');
|
||||||
|
shell.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloads = path.join(__dirname, '..', 'downloads');
|
||||||
|
if (shell.rm('-rf', targetFile, downloads).code !== 0) {
|
||||||
|
shell.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
shell.echo(`>>> Downloading from '${url}'...`);
|
||||||
|
const data = await download(url);
|
||||||
|
shell.echo(`<<< Download succeeded.`);
|
||||||
|
|
||||||
|
shell.echo('>>> Decompressing...');
|
||||||
|
const files = await decompress(data, downloads, {
|
||||||
|
plugins: [
|
||||||
|
unzip(),
|
||||||
|
untargz()
|
||||||
|
]
|
||||||
|
});
|
||||||
|
if (files.length === 0) {
|
||||||
|
shell.echo('Error ocurred while decompressing the archive.');
|
||||||
|
shell.exit(1);
|
||||||
|
}
|
||||||
|
const fileIndex = files.findIndex(f => f.path.startsWith(filePrefix));
|
||||||
|
if (fileIndex === -1) {
|
||||||
|
shell.echo(`The downloaded artifact does not contain any file with prefix ${filePrefix}.`);
|
||||||
|
shell.exit(1);
|
||||||
|
}
|
||||||
|
shell.echo('<<< Decompressing succeeded.');
|
||||||
|
|
||||||
|
if (shell.mv('-f', path.join(downloads, files[fileIndex].path), targetFile).code !== 0) {
|
||||||
|
shell.echo(`Could not move file to target path: ${targetFile}`);
|
||||||
|
shell.exit(1);
|
||||||
|
}
|
||||||
|
if (!fs.existsSync(targetFile)) {
|
||||||
|
shell.echo(`Could not find file: ${targetFile}`);
|
||||||
|
shell.exit(1);
|
||||||
|
}
|
||||||
|
shell.echo(`Done: ${targetFile}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param url {string} Download URL
|
||||||
|
* @param targetDir {string} Directory into which to decompress the archive
|
||||||
|
* @param targetFile {string} Path to the main file expected after decompressing
|
||||||
|
* @param force {boolean} Whether to download even if the target file exists
|
||||||
|
*/
|
||||||
|
exports.downloadUnzipAll = async (url, targetDir, targetFile, force) => {
|
||||||
|
if (fs.existsSync(targetFile) && !force) {
|
||||||
|
shell.echo(`Skipping download because file already exists: ${targetFile}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!fs.existsSync(targetDir)) {
|
||||||
|
if (shell.mkdir('-p', targetDir).code !== 0) {
|
||||||
|
shell.echo('Could not create new directory.');
|
||||||
|
shell.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shell.echo(`>>> Downloading from '${url}'...`);
|
||||||
|
const data = await download(url);
|
||||||
|
shell.echo(`<<< Download succeeded.`);
|
||||||
|
|
||||||
|
shell.echo('>>> Decompressing...');
|
||||||
|
const files = await decompress(data, targetDir, {
|
||||||
|
plugins: [
|
||||||
|
unzip(),
|
||||||
|
untargz()
|
||||||
|
]
|
||||||
|
});
|
||||||
|
if (files.length === 0) {
|
||||||
|
shell.echo('Error ocurred while decompressing the archive.');
|
||||||
|
shell.exit(1);
|
||||||
|
}
|
||||||
|
shell.echo('<<< Decompressing succeeded.');
|
||||||
|
|
||||||
|
if (!fs.existsSync(targetFile)) {
|
||||||
|
shell.echo(`Could not find file: ${targetFile}`);
|
||||||
|
shell.exit(1);
|
||||||
|
}
|
||||||
|
shell.echo(`Done: ${targetFile}`);
|
||||||
|
}
|
||||||
@@ -35,17 +35,11 @@ export namespace ArduinoCommands {
|
|||||||
category: 'File'
|
category: 'File'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const REFRESH_BOARDS: Command = {
|
|
||||||
id: "arduino-refresh-attached-boards",
|
|
||||||
label: "Refresh attached boards"
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SELECT_BOARD: Command = {
|
|
||||||
id: "arduino-select-board"
|
|
||||||
}
|
|
||||||
|
|
||||||
export const OPEN_BOARDS_DIALOG: Command = {
|
export const OPEN_BOARDS_DIALOG: Command = {
|
||||||
id: "arduino-open-boards-dialog"
|
id: "arduino-open-boards-dialog"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const TOGGLE_ADVANCED_MODE: Command = {
|
||||||
|
id: "arduino-toggle-advanced-mode"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import { injectable, inject } from "inversify";
|
|
||||||
import { MenuContribution, MenuModelRegistry, MenuPath } from "@theia/core";
|
|
||||||
import { CommonMenus } from "@theia/core/lib/browser";
|
|
||||||
import { ArduinoCommands } from "./arduino-commands";
|
|
||||||
|
|
||||||
export namespace ArduinoToolbarContextMenu {
|
|
||||||
export const OPEN_SKETCH_PATH: MenuPath = ['arduino-open-sketch-context-menu'];
|
|
||||||
export const OPEN_GROUP: MenuPath = [...OPEN_SKETCH_PATH, '1_open'];
|
|
||||||
export const WS_SKETCHES_GROUP: MenuPath = [...OPEN_SKETCH_PATH, '2_sketches'];
|
|
||||||
export const EXAMPLE_SKETCHES_GROUP: MenuPath = [...OPEN_SKETCH_PATH, '3_examples'];
|
|
||||||
}
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class ArduinoToolbarMenuContribution implements MenuContribution {
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@inject(MenuModelRegistry) protected readonly menuRegistry: MenuModelRegistry) {
|
|
||||||
}
|
|
||||||
|
|
||||||
registerMenus(registry: MenuModelRegistry) {
|
|
||||||
registry.registerMenuAction([...CommonMenus.FILE, '0_new_sletch'], {
|
|
||||||
commandId: ArduinoCommands.NEW_SKETCH.id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,51 +3,67 @@ import { injectable, inject, postConstruct } from 'inversify';
|
|||||||
import URI from '@theia/core/lib/common/uri';
|
import URI from '@theia/core/lib/common/uri';
|
||||||
import { EditorWidget } from '@theia/editor/lib/browser/editor-widget';
|
import { EditorWidget } from '@theia/editor/lib/browser/editor-widget';
|
||||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||||
import { CommandContribution, CommandRegistry, Command } from '@theia/core/lib/common/command';
|
import { CommandContribution, CommandRegistry, Command, CommandHandler } from '@theia/core/lib/common/command';
|
||||||
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||||
import { BoardsService, Board } from '../common/protocol/boards-service';
|
import { BoardsService } from '../common/protocol/boards-service';
|
||||||
import { ArduinoCommands } from './arduino-commands';
|
import { ArduinoCommands } from './arduino-commands';
|
||||||
import { ConnectedBoards } from './components/connected-boards';
|
|
||||||
import { CoreService } from '../common/protocol/core-service';
|
import { CoreService } from '../common/protocol/core-service';
|
||||||
import { WorkspaceServiceExt } from './workspace-service-ext';
|
import { WorkspaceServiceExt } from './workspace-service-ext';
|
||||||
import { ToolOutputServiceClient } from '../common/protocol/tool-output-service';
|
import { BoardsServiceClientImpl } from './boards/boards-service-client-impl';
|
||||||
import { QuickPickService } from '@theia/core/lib/common/quick-pick-service';
|
|
||||||
import { BoardsListWidgetFrontendContribution } from './boards/boards-widget-frontend-contribution';
|
|
||||||
import { BoardsNotificationService } from './boards-notification-service';
|
|
||||||
import { WorkspaceRootUriAwareCommandHandler, WorkspaceCommands } from '@theia/workspace/lib/browser/workspace-commands';
|
import { WorkspaceRootUriAwareCommandHandler, WorkspaceCommands } from '@theia/workspace/lib/browser/workspace-commands';
|
||||||
import { SelectionService, MenuContribution, MenuModelRegistry, MAIN_MENU_BAR } from '@theia/core';
|
import { SelectionService, MenuContribution, MenuModelRegistry, MAIN_MENU_BAR, MenuPath } from '@theia/core';
|
||||||
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
|
||||||
import { SketchFactory } from './sketch-factory';
|
|
||||||
import { ArduinoToolbar } from './toolbar/arduino-toolbar';
|
import { ArduinoToolbar } from './toolbar/arduino-toolbar';
|
||||||
import { EditorManager, EditorMainMenu } from '@theia/editor/lib/browser';
|
import { EditorManager, EditorMainMenu } from '@theia/editor/lib/browser';
|
||||||
import { ContextMenuRenderer, OpenerService, Widget, StatusBar } from '@theia/core/lib/browser';
|
import {
|
||||||
|
ContextMenuRenderer, Widget, StatusBar, StatusBarAlignment, FrontendApplicationContribution,
|
||||||
|
FrontendApplication, KeybindingContribution, KeybindingRegistry
|
||||||
|
} from '@theia/core/lib/browser';
|
||||||
import { OpenFileDialogProps, FileDialogService } from '@theia/filesystem/lib/browser/file-dialog';
|
import { OpenFileDialogProps, FileDialogService } from '@theia/filesystem/lib/browser/file-dialog';
|
||||||
import { FileSystem, FileStat } from '@theia/filesystem/lib/common';
|
import { FileSystem, FileStat } from '@theia/filesystem/lib/common';
|
||||||
import { ArduinoToolbarContextMenu } from './arduino-file-menu';
|
|
||||||
import { Sketch, SketchesService } from '../common/protocol/sketches-service';
|
import { Sketch, SketchesService } from '../common/protocol/sketches-service';
|
||||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
import { ToolOutputServiceClient } from '../common/protocol/tool-output-service';
|
||||||
import { CommonCommands, CommonMenus } from '@theia/core/lib/browser/common-frontend-contribution';
|
import { CommonCommands, CommonMenus } from '@theia/core/lib/browser/common-frontend-contribution';
|
||||||
import { FileSystemCommands } from '@theia/filesystem/lib/browser/filesystem-frontend-contribution';
|
import { FileSystemCommands } from '@theia/filesystem/lib/browser/filesystem-frontend-contribution';
|
||||||
import { FileDownloadCommands } from '@theia/filesystem/lib/browser/download/file-download-command-contribution';
|
import { FileDownloadCommands } from '@theia/filesystem/lib/browser/download/file-download-command-contribution';
|
||||||
import { MonacoMenus } from '@theia/monaco/lib/browser/monaco-menu';
|
import { MonacoMenus } from '@theia/monaco/lib/browser/monaco-menu';
|
||||||
import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-contribution';
|
import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-contribution';
|
||||||
import { MaybePromise } from '@theia/core/lib/common/types';
|
import { MaybePromise } from '@theia/core/lib/common/types';
|
||||||
import { SelectBoardDialog } from './boards/select-board-dialog';
|
import { BoardsConfigDialog } from './boards/boards-config-dialog';
|
||||||
import { BoardsToolBarItem } from './boards/boards-toolbar-item';
|
import { BoardsToolBarItem } from './boards/boards-toolbar-item';
|
||||||
|
import { BoardsConfig } from './boards/boards-config';
|
||||||
|
import { MonitorConnection } from './monitor/monitor-connection';
|
||||||
|
import { MonitorViewContribution } from './monitor/monitor-view-contribution';
|
||||||
|
import { ArduinoWorkspaceService } from './arduino-workspace-service';
|
||||||
|
import { FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
|
||||||
|
import { OutputContribution } from '@theia/output/lib/browser/output-contribution';
|
||||||
|
import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
|
||||||
|
import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
|
||||||
|
import { ScmContribution } from '@theia/scm/lib/browser/scm-contribution';
|
||||||
|
import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
|
||||||
|
import { FileNavigatorCommands } from '@theia/navigator/lib/browser/navigator-contribution';
|
||||||
|
import { EditorMode } from './editor-mode';
|
||||||
|
|
||||||
export namespace ArduinoMenus {
|
export namespace ArduinoMenus {
|
||||||
export const SKETCH = [...MAIN_MENU_BAR, '3_sketch'];
|
export const SKETCH = [...MAIN_MENU_BAR, '3_sketch'];
|
||||||
export const TOOLS = [...MAIN_MENU_BAR, '4_tools'];
|
export const TOOLS = [...MAIN_MENU_BAR, '4_tools'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export namespace ArduinoToolbarContextMenu {
|
||||||
|
export const OPEN_SKETCH_PATH: MenuPath = ['arduino-open-sketch-context-menu'];
|
||||||
|
export const OPEN_GROUP: MenuPath = [...OPEN_SKETCH_PATH, '1_open'];
|
||||||
|
export const WS_SKETCHES_GROUP: MenuPath = [...OPEN_SKETCH_PATH, '2_sketches'];
|
||||||
|
export const EXAMPLE_SKETCHES_GROUP: MenuPath = [...OPEN_SKETCH_PATH, '3_examples'];
|
||||||
|
}
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class ArduinoFrontendContribution implements TabBarToolbarContribution, CommandContribution, MenuContribution {
|
export class ArduinoFrontendContribution implements FrontendApplicationContribution,
|
||||||
|
TabBarToolbarContribution, CommandContribution, MenuContribution, KeybindingContribution {
|
||||||
|
|
||||||
@inject(MessageService)
|
@inject(MessageService)
|
||||||
protected readonly messageService: MessageService;
|
protected readonly messageService: MessageService;
|
||||||
|
|
||||||
@inject(BoardsService)
|
@inject(BoardsService)
|
||||||
protected readonly boardService: BoardsService;
|
protected readonly boardsService: BoardsService;
|
||||||
|
|
||||||
@inject(CoreService)
|
@inject(CoreService)
|
||||||
protected readonly coreService: CoreService;
|
protected readonly coreService: CoreService;
|
||||||
@@ -58,21 +74,12 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
|||||||
@inject(ToolOutputServiceClient)
|
@inject(ToolOutputServiceClient)
|
||||||
protected readonly toolOutputServiceClient: ToolOutputServiceClient;
|
protected readonly toolOutputServiceClient: ToolOutputServiceClient;
|
||||||
|
|
||||||
@inject(QuickPickService)
|
@inject(BoardsServiceClientImpl)
|
||||||
protected readonly quickPickService: QuickPickService;
|
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
||||||
|
|
||||||
@inject(BoardsListWidgetFrontendContribution)
|
|
||||||
protected readonly boardsListWidgetFrontendContribution: BoardsListWidgetFrontendContribution;
|
|
||||||
|
|
||||||
@inject(BoardsNotificationService)
|
|
||||||
protected readonly boardsNotificationService: BoardsNotificationService;
|
|
||||||
|
|
||||||
@inject(SelectionService)
|
@inject(SelectionService)
|
||||||
protected readonly selectionService: SelectionService;
|
protected readonly selectionService: SelectionService;
|
||||||
|
|
||||||
@inject(SketchFactory)
|
|
||||||
protected readonly sketchFactory: SketchFactory;
|
|
||||||
|
|
||||||
@inject(EditorManager)
|
@inject(EditorManager)
|
||||||
protected readonly editorManager: EditorManager;
|
protected readonly editorManager: EditorManager;
|
||||||
|
|
||||||
@@ -85,86 +92,176 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
|||||||
@inject(FileSystem)
|
@inject(FileSystem)
|
||||||
protected readonly fileSystem: FileSystem;
|
protected readonly fileSystem: FileSystem;
|
||||||
|
|
||||||
@inject(OpenerService)
|
|
||||||
protected readonly openerService: OpenerService;
|
|
||||||
|
|
||||||
@inject(WindowService)
|
|
||||||
protected readonly windowService: WindowService;
|
|
||||||
|
|
||||||
@inject(SketchesService)
|
@inject(SketchesService)
|
||||||
protected readonly sketches: SketchesService;
|
protected readonly sketchService: SketchesService;
|
||||||
|
|
||||||
@inject(SelectBoardDialog)
|
@inject(BoardsConfigDialog)
|
||||||
protected readonly selectBoardsDialog: SelectBoardDialog;
|
protected readonly boardsConfigDialog: BoardsConfigDialog;
|
||||||
|
|
||||||
@inject(MenuModelRegistry)
|
@inject(MenuModelRegistry)
|
||||||
protected readonly menuRegistry: MenuModelRegistry;
|
protected readonly menuRegistry: MenuModelRegistry;
|
||||||
|
|
||||||
@inject(CommandRegistry)
|
@inject(CommandRegistry)
|
||||||
protected readonly commands: CommandRegistry;
|
protected readonly commandRegistry: CommandRegistry;
|
||||||
|
|
||||||
@inject(StatusBar)
|
@inject(StatusBar)
|
||||||
protected readonly statusBar: StatusBar;
|
protected readonly statusBar: StatusBar;
|
||||||
|
|
||||||
protected boardsToolbarItem: BoardsToolBarItem | null;
|
@inject(ArduinoWorkspaceService)
|
||||||
protected wsSketchCount: number = 0;
|
protected readonly workspaceService: ArduinoWorkspaceService;
|
||||||
|
|
||||||
constructor(@inject(WorkspaceService) protected readonly workspaceService: WorkspaceService) {
|
@inject(MonitorConnection)
|
||||||
this.workspaceService.onWorkspaceChanged(() => {
|
protected readonly monitorConnection: MonitorConnection;
|
||||||
if (this.workspaceService.workspace) {
|
|
||||||
this.registerSketchesInMenu(this.menuRegistry);
|
@inject(FileNavigatorContribution)
|
||||||
}
|
protected readonly fileNavigatorContributions: FileNavigatorContribution;
|
||||||
})
|
|
||||||
}
|
@inject(OutputContribution)
|
||||||
|
protected readonly outputContribution: OutputContribution;
|
||||||
|
|
||||||
|
@inject(OutlineViewContribution)
|
||||||
|
protected readonly outlineContribution: OutlineViewContribution;
|
||||||
|
|
||||||
|
@inject(ProblemContribution)
|
||||||
|
protected readonly problemContribution: ProblemContribution;
|
||||||
|
|
||||||
|
@inject(ScmContribution)
|
||||||
|
protected readonly scmContribution: ScmContribution;
|
||||||
|
|
||||||
|
@inject(SearchInWorkspaceFrontendContribution)
|
||||||
|
protected readonly siwContribution: SearchInWorkspaceFrontendContribution;
|
||||||
|
|
||||||
|
@inject(EditorMode)
|
||||||
|
protected readonly editorMode: EditorMode;
|
||||||
|
|
||||||
|
protected application: FrontendApplication;
|
||||||
|
protected wsSketchCount: number = 0; // TODO: this does not belong here, does it?
|
||||||
|
|
||||||
@postConstruct()
|
@postConstruct()
|
||||||
protected async init(): Promise<void> {
|
protected async init(): Promise<void> {
|
||||||
// This is a hack. Otherwise, the backend services won't bind.
|
// This is a hack. Otherwise, the backend services won't bind.
|
||||||
await this.workspaceServiceExt.roots();
|
await this.workspaceServiceExt.roots();
|
||||||
|
|
||||||
|
const updateStatusBar = (config: BoardsConfig.Config) => {
|
||||||
|
this.statusBar.setElement('arduino-selected-board', {
|
||||||
|
alignment: StatusBarAlignment.RIGHT,
|
||||||
|
text: BoardsConfig.Config.toString(config)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.boardsServiceClient.onBoardsConfigChanged(updateStatusBar);
|
||||||
|
updateStatusBar(this.boardsServiceClient.boardsConfig);
|
||||||
|
|
||||||
|
this.registerSketchesInMenu(this.menuRegistry);
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
this.boardsService.getAttachedBoards(),
|
||||||
|
this.boardsService.getAvailablePorts()
|
||||||
|
]).then(([{ boards }, { ports }]) => this.boardsServiceClient.tryReconnect(boards, ports));
|
||||||
|
}
|
||||||
|
|
||||||
|
onStart(app: FrontendApplication): void {
|
||||||
|
this.application = app;
|
||||||
|
// Initialize all `pro-mode` widgets. This is a NOOP if in normal mode.
|
||||||
|
for (const viewContribution of [
|
||||||
|
this.fileNavigatorContributions,
|
||||||
|
this.outputContribution,
|
||||||
|
this.outlineContribution,
|
||||||
|
this.problemContribution,
|
||||||
|
this.scmContribution,
|
||||||
|
this.siwContribution] as Array<FrontendApplicationContribution>) {
|
||||||
|
|
||||||
|
if (viewContribution.initializeLayout) {
|
||||||
|
viewContribution.initializeLayout(this.application);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
||||||
registry.registerItem({
|
registry.registerItem({
|
||||||
id: ArduinoCommands.VERIFY.id,
|
id: ArduinoCommands.VERIFY.id,
|
||||||
command: ArduinoCommands.VERIFY.id,
|
command: ArduinoCommands.VERIFY.id,
|
||||||
tooltip: 'Verify',
|
tooltip: 'Verify'
|
||||||
text: '$(check)'
|
|
||||||
});
|
});
|
||||||
registry.registerItem({
|
registry.registerItem({
|
||||||
id: ArduinoCommands.UPLOAD.id,
|
id: ArduinoCommands.UPLOAD.id,
|
||||||
command: ArduinoCommands.UPLOAD.id,
|
command: ArduinoCommands.UPLOAD.id,
|
||||||
tooltip: 'Upload',
|
tooltip: 'Upload'
|
||||||
text: '$(arrow-right)'
|
|
||||||
});
|
});
|
||||||
registry.registerItem({
|
registry.registerItem({
|
||||||
id: ArduinoCommands.SHOW_OPEN_CONTEXT_MENU.id,
|
id: ArduinoCommands.SHOW_OPEN_CONTEXT_MENU.id,
|
||||||
command: ArduinoCommands.SHOW_OPEN_CONTEXT_MENU.id,
|
command: ArduinoCommands.SHOW_OPEN_CONTEXT_MENU.id,
|
||||||
tooltip: 'Open',
|
tooltip: 'Open'
|
||||||
text: '$(arrow-up)'
|
|
||||||
});
|
});
|
||||||
registry.registerItem({
|
registry.registerItem({
|
||||||
id: ArduinoCommands.SAVE_SKETCH.id,
|
id: ArduinoCommands.SAVE_SKETCH.id,
|
||||||
command: ArduinoCommands.SAVE_SKETCH.id,
|
command: ArduinoCommands.SAVE_SKETCH.id,
|
||||||
tooltip: 'Save',
|
tooltip: 'Save'
|
||||||
text: '$(arrow-down)'
|
|
||||||
});
|
});
|
||||||
registry.registerItem({
|
registry.registerItem({
|
||||||
id: ConnectedBoards.TOOLBAR_ID,
|
id: BoardsToolBarItem.TOOLBAR_ID,
|
||||||
render: () => <BoardsToolBarItem
|
render: () => <BoardsToolBarItem
|
||||||
key='boardsToolbarItem'
|
key='boardsToolbarItem'
|
||||||
ref={ref => this.boardsToolbarItem = ref}
|
commands={this.commandRegistry}
|
||||||
commands={this.commands}
|
boardsServiceClient={this.boardsServiceClient}
|
||||||
statusBar={this.statusBar}
|
boardService={this.boardsService} />,
|
||||||
contextMenuRenderer={this.contextMenuRenderer}
|
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left'
|
||||||
boardsNotificationService={this.boardsNotificationService}
|
});
|
||||||
boardService={this.boardService} />,
|
registry.registerItem({
|
||||||
isVisible: widget => this.isArduinoToolbar(widget)
|
id: 'toggle-serial-monitor',
|
||||||
})
|
command: MonitorViewContribution.OPEN_SERIAL_MONITOR,
|
||||||
|
tooltip: 'Toggle Serial Monitor',
|
||||||
|
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'right'
|
||||||
|
});
|
||||||
|
|
||||||
|
registry.registerItem({
|
||||||
|
id: ArduinoCommands.TOGGLE_ADVANCED_MODE.id,
|
||||||
|
command: ArduinoCommands.TOGGLE_ADVANCED_MODE.id,
|
||||||
|
tooltip: 'Toggle Advanced Mode',
|
||||||
|
text: (this.editorMode.proMode ? '$(toggle-on)' : '$(toggle-off)'),
|
||||||
|
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'right'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
registerCommands(registry: CommandRegistry): void {
|
registerCommands(registry: CommandRegistry): void {
|
||||||
|
// TODO: use proper API https://github.com/eclipse-theia/theia/pull/6599
|
||||||
|
const allHandlers: { [id: string]: CommandHandler[] } = (registry as any)._handlers;
|
||||||
|
|
||||||
|
// Make sure to reveal the `Explorer` before executing `New File` and `New Folder`.
|
||||||
|
for (const command of [WorkspaceCommands.NEW_FILE, WorkspaceCommands.NEW_FOLDER]) {
|
||||||
|
const { id } = command;
|
||||||
|
const handlers = allHandlers[id].slice();
|
||||||
|
registry.unregisterCommand(id);
|
||||||
|
registry.registerCommand(command);
|
||||||
|
for (const handler of handlers) {
|
||||||
|
const wrapper: CommandHandler = {
|
||||||
|
execute: (...args: any[]) => {
|
||||||
|
this.fileNavigatorContributions.openView({ reveal: true }).then(() => handler.execute(args));
|
||||||
|
},
|
||||||
|
isVisible: (...args: any[]) => {
|
||||||
|
return handler.isVisible!(args);
|
||||||
|
},
|
||||||
|
isEnabled: (args: any[]) => {
|
||||||
|
return handler.isEnabled!(args);
|
||||||
|
},
|
||||||
|
isToggled: (args: any[]) => {
|
||||||
|
return handler.isToggled!(args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (!handler.isEnabled) {
|
||||||
|
delete wrapper.isEnabled;
|
||||||
|
}
|
||||||
|
if (!handler.isToggled) {
|
||||||
|
delete wrapper.isToggled;
|
||||||
|
}
|
||||||
|
if (!handler.isVisible) {
|
||||||
|
delete wrapper.isVisible;
|
||||||
|
}
|
||||||
|
registry.registerHandler(id, wrapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
registry.registerCommand(ArduinoCommands.VERIFY, {
|
registry.registerCommand(ArduinoCommands.VERIFY, {
|
||||||
isVisible: widget => this.isArduinoToolbar(widget),
|
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||||
isEnabled: widget => true,
|
isEnabled: widget => true,
|
||||||
execute: async () => {
|
execute: async () => {
|
||||||
const widget = this.getCurrentWidget();
|
const widget = this.getCurrentWidget();
|
||||||
@@ -178,14 +275,24 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.coreService.compile({ uri: uri.toString() });
|
const { boardsConfig } = this.boardsServiceClient;
|
||||||
|
if (!boardsConfig || !boardsConfig.selectedBoard) {
|
||||||
|
throw new Error('No boards selected. Please select a board.');
|
||||||
|
}
|
||||||
|
if (!boardsConfig.selectedBoard.fqbn) {
|
||||||
|
throw new Error(`No core is installed for ${boardsConfig.selectedBoard.name}. Please install the board.`);
|
||||||
|
}
|
||||||
|
// Reveal the Output view asynchronously (don't await it)
|
||||||
|
this.outputContribution.openView({ reveal: true });
|
||||||
|
await this.coreService.compile({ uri: uri.toString(), board: boardsConfig.selectedBoard });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await this.messageService.error(e.toString());
|
await this.messageService.error(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
registry.registerCommand(ArduinoCommands.UPLOAD, {
|
registry.registerCommand(ArduinoCommands.UPLOAD, {
|
||||||
isVisible: widget => this.isArduinoToolbar(widget),
|
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||||
isEnabled: widget => true,
|
isEnabled: widget => true,
|
||||||
execute: async () => {
|
execute: async () => {
|
||||||
const widget = this.getCurrentWidget();
|
const widget = this.getCurrentWidget();
|
||||||
@@ -198,16 +305,36 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const monitorConfig = this.monitorConnection.monitorConfig;
|
||||||
|
if (monitorConfig) {
|
||||||
|
await this.monitorConnection.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.coreService.upload({ uri: uri.toString() });
|
const { boardsConfig } = this.boardsServiceClient;
|
||||||
|
if (!boardsConfig || !boardsConfig.selectedBoard) {
|
||||||
|
throw new Error('No boards selected. Please select a board.');
|
||||||
|
}
|
||||||
|
const { selectedPort } = boardsConfig;
|
||||||
|
if (!selectedPort) {
|
||||||
|
throw new Error('No ports selected. Please select a port.');
|
||||||
|
}
|
||||||
|
// Reveal the Output view asynchronously (don't await it)
|
||||||
|
this.outputContribution.openView({ reveal: true });
|
||||||
|
await this.coreService.upload({ uri: uri.toString(), board: boardsConfig.selectedBoard, port: selectedPort.address });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await this.messageService.error(e.toString());
|
await this.messageService.error(e.toString());
|
||||||
|
} finally {
|
||||||
|
if (monitorConfig) {
|
||||||
|
await this.monitorConnection.connect(monitorConfig);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
registry.registerCommand(ArduinoCommands.SHOW_OPEN_CONTEXT_MENU, {
|
registry.registerCommand(ArduinoCommands.SHOW_OPEN_CONTEXT_MENU, {
|
||||||
isVisible: widget => this.isArduinoToolbar(widget),
|
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||||
isEnabled: widget => this.isArduinoToolbar(widget),
|
isEnabled: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||||
execute: async (widget: Widget, target: EventTarget) => {
|
execute: async (widget: Widget, target: EventTarget) => {
|
||||||
if (this.wsSketchCount) {
|
if (this.wsSketchCount) {
|
||||||
const el = (target as HTMLElement).parentElement;
|
const el = (target as HTMLElement).parentElement;
|
||||||
@@ -218,27 +345,31 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.commands.executeCommand(ArduinoCommands.OPEN_FILE_NAVIGATOR.id);
|
this.commandRegistry.executeCommand(ArduinoCommands.OPEN_FILE_NAVIGATOR.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
registry.registerCommand(ArduinoCommands.OPEN_FILE_NAVIGATOR, {
|
registry.registerCommand(ArduinoCommands.OPEN_FILE_NAVIGATOR, {
|
||||||
isEnabled: () => true,
|
isEnabled: () => true,
|
||||||
execute: () => this.doOpenFile()
|
execute: () => this.doOpenFile()
|
||||||
})
|
});
|
||||||
|
|
||||||
registry.registerCommand(ArduinoCommands.OPEN_SKETCH, {
|
registry.registerCommand(ArduinoCommands.OPEN_SKETCH, {
|
||||||
isEnabled: () => true,
|
isEnabled: () => true,
|
||||||
execute: async (sketch: Sketch) => {
|
execute: async (sketch: Sketch) => {
|
||||||
this.openSketchFilesInNewWindow(sketch.uri);
|
this.workspaceService.open(new URI(sketch.uri));
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
registry.registerCommand(ArduinoCommands.SAVE_SKETCH, {
|
registry.registerCommand(ArduinoCommands.SAVE_SKETCH, {
|
||||||
isEnabled: widget => this.isArduinoToolbar(widget),
|
isEnabled: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||||
isVisible: widget => this.isArduinoToolbar(widget),
|
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
|
||||||
execute: async (sketch: Sketch) => {
|
execute: async (sketch: Sketch) => {
|
||||||
registry.executeCommand(CommonCommands.SAVE_ALL.id);
|
registry.executeCommand(CommonCommands.SAVE_ALL.id);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
registry.registerCommand(ArduinoCommands.NEW_SKETCH, new WorkspaceRootUriAwareCommandHandler(this.workspaceService, this.selectionService, {
|
registry.registerCommand(ArduinoCommands.NEW_SKETCH, new WorkspaceRootUriAwareCommandHandler(this.workspaceService, this.selectionService, {
|
||||||
execute: async uri => {
|
execute: async uri => {
|
||||||
try {
|
try {
|
||||||
@@ -247,47 +378,50 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
|||||||
uri = uri.withPath(uri.path.dir.dir);
|
uri = uri.withPath(uri.path.dir.dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.sketchFactory.createNewSketch(uri);
|
const sketch = await this.sketchService.createNewSketch(uri.toString());
|
||||||
|
this.workspaceService.open(new URI(sketch.uri));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await this.messageService.error(e.toString());
|
await this.messageService.error(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
registry.registerCommand(ArduinoCommands.REFRESH_BOARDS, {
|
|
||||||
isEnabled: () => true,
|
|
||||||
execute: () => this.boardsNotificationService.notifyBoardsInstalled()
|
|
||||||
});
|
|
||||||
registry.registerCommand(ArduinoCommands.SELECT_BOARD, {
|
|
||||||
isEnabled: () => true,
|
|
||||||
execute: async (board: Board) => {
|
|
||||||
this.selectBoard(board);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
registry.registerCommand(ArduinoCommands.OPEN_BOARDS_DIALOG, {
|
registry.registerCommand(ArduinoCommands.OPEN_BOARDS_DIALOG, {
|
||||||
isEnabled: () => true,
|
isEnabled: () => true,
|
||||||
execute: async () => {
|
execute: async () => {
|
||||||
const boardAndPort = await this.selectBoardsDialog.open();
|
const boardsConfig = await this.boardsConfigDialog.open();
|
||||||
if (boardAndPort && boardAndPort.board) {
|
if (boardsConfig) {
|
||||||
this.selectBoard(boardAndPort.board);
|
this.boardsServiceClient.boardsConfig = boardsConfig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
|
||||||
|
|
||||||
protected async selectBoard(board: Board) {
|
registry.registerCommand(ArduinoCommands.TOGGLE_ADVANCED_MODE, {
|
||||||
await this.boardService.selectBoard(board);
|
execute: () => this.editorMode.toggle(),
|
||||||
if (this.boardsToolbarItem) {
|
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'right',
|
||||||
this.boardsToolbarItem.setSelectedBoard(board);
|
isToggled: () => this.editorMode.proMode
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
registerMenus(registry: MenuModelRegistry) {
|
registerMenus(registry: MenuModelRegistry) {
|
||||||
|
if (!this.editorMode.proMode) {
|
||||||
|
// If are not in pro-mode, we have to disable the context menu for the tabs.
|
||||||
|
// Such as `Close`, `Close All`, etc.
|
||||||
|
for (const command of [
|
||||||
|
CommonCommands.CLOSE_TAB,
|
||||||
|
CommonCommands.CLOSE_OTHER_TABS,
|
||||||
|
CommonCommands.CLOSE_RIGHT_TABS,
|
||||||
|
CommonCommands.CLOSE_ALL_TABS,
|
||||||
|
CommonCommands.COLLAPSE_PANEL,
|
||||||
|
CommonCommands.TOGGLE_MAXIMIZED,
|
||||||
|
FileNavigatorCommands.REVEAL_IN_NAVIGATOR
|
||||||
|
]) {
|
||||||
|
registry.unregisterMenuAction(command);
|
||||||
|
}
|
||||||
|
|
||||||
registry.unregisterMenuAction(FileSystemCommands.UPLOAD);
|
registry.unregisterMenuAction(FileSystemCommands.UPLOAD);
|
||||||
registry.unregisterMenuAction(FileDownloadCommands.DOWNLOAD);
|
registry.unregisterMenuAction(FileDownloadCommands.DOWNLOAD);
|
||||||
|
|
||||||
registry.unregisterMenuAction(WorkspaceCommands.NEW_FILE);
|
|
||||||
registry.unregisterMenuAction(WorkspaceCommands.NEW_FOLDER);
|
|
||||||
|
|
||||||
registry.unregisterMenuAction(WorkspaceCommands.OPEN_FOLDER);
|
registry.unregisterMenuAction(WorkspaceCommands.OPEN_FOLDER);
|
||||||
registry.unregisterMenuAction(WorkspaceCommands.OPEN_WORKSPACE);
|
registry.unregisterMenuAction(WorkspaceCommands.OPEN_WORKSPACE);
|
||||||
registry.unregisterMenuAction(WorkspaceCommands.OPEN_RECENT_WORKSPACE);
|
registry.unregisterMenuAction(WorkspaceCommands.OPEN_RECENT_WORKSPACE);
|
||||||
@@ -298,7 +432,7 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
|||||||
registry.getMenu(MAIN_MENU_BAR).removeNode(this.getMenuId(EditorMainMenu.GO));
|
registry.getMenu(MAIN_MENU_BAR).removeNode(this.getMenuId(EditorMainMenu.GO));
|
||||||
registry.getMenu(MAIN_MENU_BAR).removeNode(this.getMenuId(TerminalMenus.TERMINAL));
|
registry.getMenu(MAIN_MENU_BAR).removeNode(this.getMenuId(TerminalMenus.TERMINAL));
|
||||||
registry.getMenu(MAIN_MENU_BAR).removeNode(this.getMenuId(CommonMenus.VIEW));
|
registry.getMenu(MAIN_MENU_BAR).removeNode(this.getMenuId(CommonMenus.VIEW));
|
||||||
registry.getMenu(MAIN_MENU_BAR).removeNode(this.getMenuId(CommonMenus.HELP));
|
}
|
||||||
|
|
||||||
registry.registerSubmenu(ArduinoMenus.SKETCH, 'Sketch');
|
registry.registerSubmenu(ArduinoMenus.SKETCH, 'Sketch');
|
||||||
registry.registerMenuAction(ArduinoMenus.SKETCH, {
|
registry.registerMenuAction(ArduinoMenus.SKETCH, {
|
||||||
@@ -317,6 +451,15 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
|||||||
});
|
});
|
||||||
|
|
||||||
registry.registerSubmenu(ArduinoMenus.TOOLS, 'Tools');
|
registry.registerSubmenu(ArduinoMenus.TOOLS, 'Tools');
|
||||||
|
|
||||||
|
registry.registerMenuAction(CommonMenus.HELP, {
|
||||||
|
commandId: ArduinoCommands.TOGGLE_ADVANCED_MODE.id,
|
||||||
|
label: 'Advanced Mode'
|
||||||
|
});
|
||||||
|
|
||||||
|
registry.registerMenuAction([...CommonMenus.FILE, '0_new_sketch'], {
|
||||||
|
commandId: ArduinoCommands.NEW_SKETCH.id
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getMenuId(menuPath: string[]): string {
|
protected getMenuId(menuPath: string[]): string {
|
||||||
@@ -325,15 +468,26 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
|||||||
return menuId;
|
return menuId;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected registerSketchesInMenu(registry: MenuModelRegistry) {
|
registerKeybindings(keybindings: KeybindingRegistry): void {
|
||||||
this.getWorkspaceSketches().then(sketches => {
|
keybindings.registerKeybinding({
|
||||||
|
command: ArduinoCommands.VERIFY.id,
|
||||||
|
keybinding: 'ctrlcmd+alt+v'
|
||||||
|
});
|
||||||
|
keybindings.registerKeybinding({
|
||||||
|
command: ArduinoCommands.UPLOAD.id,
|
||||||
|
keybinding: 'ctrlcmd+alt+u'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async registerSketchesInMenu(registry: MenuModelRegistry): Promise<void> {
|
||||||
|
this.sketchService.getSketches().then(sketches => {
|
||||||
this.wsSketchCount = sketches.length;
|
this.wsSketchCount = sketches.length;
|
||||||
sketches.forEach(sketch => {
|
sketches.forEach(sketch => {
|
||||||
const command: Command = {
|
const command: Command = {
|
||||||
id: 'openSketch' + sketch.name
|
id: 'openSketch' + sketch.name
|
||||||
}
|
}
|
||||||
this.commands.registerCommand(command, {
|
this.commandRegistry.registerCommand(command, {
|
||||||
execute: () => this.commands.executeCommand(ArduinoCommands.OPEN_SKETCH.id, sketch)
|
execute: () => this.commandRegistry.executeCommand(ArduinoCommands.OPEN_SKETCH.id, sketch)
|
||||||
});
|
});
|
||||||
|
|
||||||
registry.registerMenuAction(ArduinoToolbarContextMenu.WS_SKETCHES_GROUP, {
|
registry.registerMenuAction(ArduinoToolbarContextMenu.WS_SKETCHES_GROUP, {
|
||||||
@@ -344,25 +498,12 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getWorkspaceSketches(): Promise<Sketch[]> {
|
async openSketchFiles(uri: string): Promise<void> {
|
||||||
const sketches = this.sketches.getSketches(this.workspaceService.workspace);
|
this.sketchService.getSketchFiles(uri).then(uris => {
|
||||||
return sketches;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async openSketchFilesInNewWindow(uri: string) {
|
|
||||||
const location = new URL(window.location.href);
|
|
||||||
location.searchParams.set('sketch', uri);
|
|
||||||
this.windowService.openNewWindow(location.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
async openSketchFiles(uri: string) {
|
|
||||||
const fileStat = await this.fileSystem.getFileStat(uri);
|
|
||||||
if (fileStat) {
|
|
||||||
const uris = await this.sketches.getSketchFiles(fileStat);
|
|
||||||
for (const uri of uris) {
|
for (const uri of uris) {
|
||||||
this.editorManager.open(new URI(uri));
|
this.editorManager.open(new URI(uri));
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -386,7 +527,7 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
|||||||
if (destinationFile && !destinationFile.isDirectory) {
|
if (destinationFile && !destinationFile.isDirectory) {
|
||||||
const message = await this.validate(destinationFile);
|
const message = await this.validate(destinationFile);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
await this.openSketchFilesInNewWindow(destinationFileUri.toString());
|
this.workspaceService.open(destinationFileUri);
|
||||||
return destinationFileUri;
|
return destinationFileUri;
|
||||||
} else {
|
} else {
|
||||||
this.messageService.warn(message);
|
this.messageService.warn(message);
|
||||||
@@ -423,32 +564,6 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// private async onNoBoardsInstalled() {
|
|
||||||
// const action = await this.messageService.info("You have no boards installed. Use the boards manager to install one.", "Open Boards Manager");
|
|
||||||
// if (!action) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// this.boardsListWidgetFrontendContribution.openView({ reveal: true });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// private async onUnknownBoard() {
|
|
||||||
// const action = await this.messageService.warn("There's a board connected for which you need to install software." +
|
|
||||||
// " If this were not a PoC we would offer you the right package now.", "Open Boards Manager");
|
|
||||||
// if (!action) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// this.boardsListWidgetFrontendContribution.openView({ reveal: true });
|
|
||||||
// }
|
|
||||||
|
|
||||||
private isArduinoToolbar(maybeToolbarWidget: any): boolean {
|
|
||||||
if (maybeToolbarWidget instanceof ArduinoToolbar) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private toUri(arg: any): URI | undefined {
|
private toUri(arg: any): URI | undefined {
|
||||||
if (arg instanceof URI) {
|
if (arg instanceof URI) {
|
||||||
return arg;
|
return arg;
|
||||||
|
|||||||
@@ -7,13 +7,14 @@ import { TabBarToolbarContribution } from '@theia/core/lib/browser/shell/tab-bar
|
|||||||
import { WebSocketConnectionProvider } from '@theia/core/lib/browser/messaging/ws-connection-provider';
|
import { WebSocketConnectionProvider } from '@theia/core/lib/browser/messaging/ws-connection-provider';
|
||||||
import { FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser/frontend-application'
|
import { FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser/frontend-application'
|
||||||
import { LanguageGrammarDefinitionContribution } from '@theia/monaco/lib/browser/textmate';
|
import { LanguageGrammarDefinitionContribution } from '@theia/monaco/lib/browser/textmate';
|
||||||
|
import { LanguageClientContribution } from '@theia/languages/lib/browser';
|
||||||
|
import { ArduinoLanguageClientContribution } from './language/arduino-language-client-contribution';
|
||||||
import { LibraryListWidget } from './library/library-list-widget';
|
import { LibraryListWidget } from './library/library-list-widget';
|
||||||
import { ArduinoFrontendContribution } from './arduino-frontend-contribution';
|
import { ArduinoFrontendContribution } from './arduino-frontend-contribution';
|
||||||
import { ArduinoLanguageGrammarContribution } from './language/arduino-language-grammar-contribution';
|
import { ArduinoLanguageGrammarContribution } from './language/arduino-language-grammar-contribution';
|
||||||
import { LibraryService, LibraryServicePath } from '../common/protocol/library-service';
|
import { LibraryService, LibraryServicePath } from '../common/protocol/library-service';
|
||||||
import { BoardsService, BoardsServicePath } from '../common/protocol/boards-service';
|
import { BoardsService, BoardsServicePath, BoardsServiceClient } from '../common/protocol/boards-service';
|
||||||
import { SketchesService, SketchesServicePath } from '../common/protocol/sketches-service';
|
import { SketchesService, SketchesServicePath } from '../common/protocol/sketches-service';
|
||||||
import { LibraryListWidgetFrontendContribution } from './library/list-widget-frontend-contribution';
|
|
||||||
import { CoreService, CoreServicePath } from '../common/protocol/core-service';
|
import { CoreService, CoreServicePath } from '../common/protocol/core-service';
|
||||||
import { BoardsListWidget } from './boards/boards-list-widget';
|
import { BoardsListWidget } from './boards/boards-list-widget';
|
||||||
import { BoardsListWidgetFrontendContribution } from './boards/boards-widget-frontend-contribution';
|
import { BoardsListWidgetFrontendContribution } from './boards/boards-widget-frontend-contribution';
|
||||||
@@ -22,34 +23,55 @@ import { WorkspaceServiceExtImpl } from './workspace-service-ext-impl';
|
|||||||
import { ToolOutputServiceClient } from '../common/protocol/tool-output-service';
|
import { ToolOutputServiceClient } from '../common/protocol/tool-output-service';
|
||||||
import { ToolOutputService } from '../common/protocol/tool-output-service';
|
import { ToolOutputService } from '../common/protocol/tool-output-service';
|
||||||
import { ToolOutputServiceClientImpl } from './tool-output/client-service-impl';
|
import { ToolOutputServiceClientImpl } from './tool-output/client-service-impl';
|
||||||
import { BoardsNotificationService } from './boards-notification-service';
|
import { BoardsServiceClientImpl } from './boards/boards-service-client-impl';
|
||||||
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||||
import { AWorkspaceService } from './arduino-workspace-service';
|
import { ArduinoWorkspaceService } from './arduino-workspace-service';
|
||||||
import { ThemeService } from '@theia/core/lib/browser/theming';
|
import { ThemeService } from '@theia/core/lib/browser/theming';
|
||||||
import { ArduinoTheme } from './arduino-theme';
|
import { ArduinoTheme } from './arduino-theme';
|
||||||
import { ArduinoToolbarMenuContribution } from './arduino-file-menu';
|
|
||||||
import { MenuContribution } from '@theia/core';
|
|
||||||
import { SketchFactory } from './sketch-factory';
|
|
||||||
import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
|
import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
|
||||||
import { SilentOutlineViewContribution } from './customization/silent-outline-contribution';
|
import { ArduinoOutlineViewContribution } from './customization/arduino-outline-contribution';
|
||||||
import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
|
import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
|
||||||
import { SilentProblemContribution } from './customization/silent-problem-contribution';
|
import { ArduinoProblemContribution } from './customization/arduino-problem-contribution';
|
||||||
import { SilentNavigatorContribution } from './customization/silent-navigator-contribution';
|
import { ArduinoNavigatorContribution } from './customization/arduino-navigator-contribution';
|
||||||
import { FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
|
import { FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
|
||||||
import { ArduinoToolbarContribution } from './toolbar/arduino-toolbar-contribution';
|
import { ArduinoToolbarContribution } from './toolbar/arduino-toolbar-contribution';
|
||||||
import { OutputToolbarContribution } from '@theia/output/lib/browser/output-toolbar-contribution';
|
import { OutputToolbarContribution } from '@theia/output/lib/browser/output-toolbar-contribution';
|
||||||
import { ArduinoOutputToolContribution } from './customization/silent-output-tool-contribution';
|
import { ArduinoOutputToolContribution } from './customization/arduino-output-tool-contribution';
|
||||||
import { EditorContribution } from '@theia/editor/lib/browser/editor-contribution';
|
import { EditorContribution } from '@theia/editor/lib/browser/editor-contribution';
|
||||||
import { CustomEditorContribution } from './customization/custom-editor-contribution';
|
import { ArduinoEditorContribution } from './customization/arduino-editor-contribution';
|
||||||
import { MonacoStatusBarContribution } from '@theia/monaco/lib/browser/monaco-status-bar-contribution';
|
import { MonacoStatusBarContribution } from '@theia/monaco/lib/browser/monaco-status-bar-contribution';
|
||||||
import { SilentMonacoStatusBarContribution } from './customization/silent-monaco-status-bar-contribution';
|
import { ArduinoMonacoStatusBarContribution } from './customization/arduino-monaco-status-bar-contribution';
|
||||||
import { ApplicationShell } from '@theia/core/lib/browser';
|
import { ApplicationShell, ShellLayoutRestorer, KeybindingContribution } from '@theia/core/lib/browser';
|
||||||
import { CustomApplicationShell } from './customization/custom-application-shell';
|
import { MenuContribution } from '@theia/core/lib/common/menu';
|
||||||
import { CustomFrontendApplication } from './customization/custom-frontend-application';
|
import { ArduinoApplicationShell } from './customization/arduino-application-shell';
|
||||||
import { EditorWidgetFactory } from '@theia/editor/lib/browser/editor-widget-factory';
|
import { ArduinoFrontendApplication } from './customization/arduino-frontend-application';
|
||||||
import { CustomEditorWidgetFactory } from './customization/custom-editor-widget-factory';
|
import { BoardsConfigDialog, BoardsConfigDialogProps } from './boards/boards-config-dialog';
|
||||||
import { SelectBoardDialog, SelectBoardDialogProps } from './boards/select-board-dialog';
|
import { BoardsConfigDialogWidget } from './boards/boards-config-dialog-widget';
|
||||||
import { SelectBoardDialogWidget } from './boards/select-board-dialog-widget';
|
import { ScmContribution } from '@theia/scm/lib/browser/scm-contribution';
|
||||||
|
import { ArduinoScmContribution } from './customization/arduino-scm-contribution';
|
||||||
|
import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
|
||||||
|
import { ArduinoSearchInWorkspaceContribution } from './customization/arduino-search-in-workspace-contribution';
|
||||||
|
import { LibraryListWidgetFrontendContribution } from './library/library-widget-frontend-contribution';
|
||||||
|
import { MonitorServiceClientImpl } from './monitor/monitor-service-client-impl';
|
||||||
|
import { MonitorServicePath, MonitorService, MonitorServiceClient } from '../common/protocol/monitor-service';
|
||||||
|
import { ConfigService, ConfigServicePath } from '../common/protocol/config-service';
|
||||||
|
import { MonitorWidget } from './monitor/monitor-widget';
|
||||||
|
import { MonitorViewContribution } from './monitor/monitor-view-contribution';
|
||||||
|
import { MonitorConnection } from './monitor/monitor-connection';
|
||||||
|
import { MonitorModel } from './monitor/monitor-model';
|
||||||
|
import { MonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider';
|
||||||
|
import { ArduinoMonacoEditorProvider } from './editor/arduino-monaco-editor-provider';
|
||||||
|
import { TabBarDecoratorService } from '@theia/core/lib/browser/shell/tab-bar-decorator';
|
||||||
|
import { ArduinoTabBarDecoratorService } from './shell/arduino-tab-bar-decorator';
|
||||||
|
import { ProblemManager } from '@theia/markers/lib/browser';
|
||||||
|
import { ArduinoProblemManager } from './markers/arduino-problem-manager';
|
||||||
|
import { BoardsAutoInstaller } from './boards/boards-auto-installer';
|
||||||
|
import { AboutDialog } from '@theia/core/lib/browser/about-dialog';
|
||||||
|
import { ArduinoAboutDialog } from './customization/arduino-about-dialog';
|
||||||
|
import { ArduinoShellLayoutRestorer } from './shell/arduino-shell-layout-restorer';
|
||||||
|
import { EditorMode } from './editor-mode';
|
||||||
|
import { ListItemRenderer } from './components/component-list/list-item-renderer';
|
||||||
|
|
||||||
const ElementQueries = require('css-element-queries/src/ElementQueries');
|
const ElementQueries = require('css-element-queries/src/ElementQueries');
|
||||||
|
|
||||||
export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => {
|
export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => {
|
||||||
@@ -61,18 +83,21 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
|
|||||||
bind(CommandContribution).toService(ArduinoFrontendContribution);
|
bind(CommandContribution).toService(ArduinoFrontendContribution);
|
||||||
bind(MenuContribution).toService(ArduinoFrontendContribution);
|
bind(MenuContribution).toService(ArduinoFrontendContribution);
|
||||||
bind(TabBarToolbarContribution).toService(ArduinoFrontendContribution);
|
bind(TabBarToolbarContribution).toService(ArduinoFrontendContribution);
|
||||||
|
bind(KeybindingContribution).toService(ArduinoFrontendContribution);
|
||||||
bind(FrontendApplicationContribution).toService(ArduinoFrontendContribution);
|
bind(FrontendApplicationContribution).toService(ArduinoFrontendContribution);
|
||||||
bind(MenuContribution).to(ArduinoToolbarMenuContribution).inSingletonScope();
|
|
||||||
|
|
||||||
bind(ArduinoToolbarContribution).toSelf().inSingletonScope();
|
bind(ArduinoToolbarContribution).toSelf().inSingletonScope();
|
||||||
bind(FrontendApplicationContribution).toService(ArduinoToolbarContribution);
|
bind(FrontendApplicationContribution).toService(ArduinoToolbarContribution);
|
||||||
|
|
||||||
// `ino` TextMate grammar
|
// `ino` TextMate grammar and language client
|
||||||
bind(LanguageGrammarDefinitionContribution).to(ArduinoLanguageGrammarContribution).inSingletonScope();
|
bind(LanguageGrammarDefinitionContribution).to(ArduinoLanguageGrammarContribution).inSingletonScope();
|
||||||
|
bind(LanguageClientContribution).to(ArduinoLanguageClientContribution).inSingletonScope();
|
||||||
|
|
||||||
|
// Renderer for both the library and the core widgets.
|
||||||
|
bind(ListItemRenderer).toSelf().inSingletonScope();
|
||||||
|
|
||||||
// Library service
|
// Library service
|
||||||
bind(LibraryService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, LibraryServicePath)).inSingletonScope();
|
bind(LibraryService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, LibraryServicePath)).inSingletonScope();
|
||||||
|
|
||||||
// Library list widget
|
// Library list widget
|
||||||
bind(LibraryListWidget).toSelf();
|
bind(LibraryListWidget).toSelf();
|
||||||
bindViewContribution(bind, LibraryListWidgetFrontendContribution);
|
bindViewContribution(bind, LibraryListWidgetFrontendContribution);
|
||||||
@@ -85,12 +110,27 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
|
|||||||
// Sketch list service
|
// Sketch list service
|
||||||
bind(SketchesService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, SketchesServicePath)).inSingletonScope();
|
bind(SketchesService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, SketchesServicePath)).inSingletonScope();
|
||||||
|
|
||||||
// Boards Notification service for updating boards list
|
// Config service
|
||||||
// TODO (post-PoC): move this to boards service/backend
|
bind(ConfigService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, ConfigServicePath)).inSingletonScope();
|
||||||
bind(BoardsNotificationService).toSelf().inSingletonScope();
|
|
||||||
|
|
||||||
// Boards service
|
// Boards service
|
||||||
bind(BoardsService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, BoardsServicePath)).inSingletonScope();
|
bind(BoardsService).toDynamicValue(context => {
|
||||||
|
const connection = context.container.get(WebSocketConnectionProvider);
|
||||||
|
const client = context.container.get(BoardsServiceClientImpl);
|
||||||
|
return connection.createProxy(BoardsServicePath, client);
|
||||||
|
}).inSingletonScope();
|
||||||
|
// Boards service client to receive and delegate notifications from the backend.
|
||||||
|
bind(BoardsServiceClientImpl).toSelf().inSingletonScope();
|
||||||
|
bind(FrontendApplicationContribution).toService(BoardsServiceClientImpl);
|
||||||
|
bind(BoardsServiceClient).toDynamicValue(context => {
|
||||||
|
const client = context.container.get(BoardsServiceClientImpl);
|
||||||
|
WebSocketConnectionProvider.createProxy(context.container, BoardsServicePath, client);
|
||||||
|
return client;
|
||||||
|
}).inSingletonScope();
|
||||||
|
|
||||||
|
// boards auto-installer
|
||||||
|
bind(BoardsAutoInstaller).toSelf().inSingletonScope();
|
||||||
|
bind(FrontendApplicationContribution).toService(BoardsAutoInstaller);
|
||||||
|
|
||||||
// Boards list widget
|
// Boards list widget
|
||||||
bind(BoardsListWidget).toSelf();
|
bind(BoardsListWidget).toSelf();
|
||||||
@@ -102,9 +142,9 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
|
|||||||
bind(FrontendApplicationContribution).toService(BoardsListWidgetFrontendContribution);
|
bind(FrontendApplicationContribution).toService(BoardsListWidgetFrontendContribution);
|
||||||
|
|
||||||
// Board select dialog
|
// Board select dialog
|
||||||
bind(SelectBoardDialogWidget).toSelf().inSingletonScope();
|
bind(BoardsConfigDialogWidget).toSelf().inSingletonScope();
|
||||||
bind(SelectBoardDialog).toSelf().inSingletonScope();
|
bind(BoardsConfigDialog).toSelf().inSingletonScope();
|
||||||
bind(SelectBoardDialogProps).toConstantValue({
|
bind(BoardsConfigDialogProps).toConstantValue({
|
||||||
title: 'Select Board'
|
title: 'Select Board'
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -122,7 +162,7 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
|
|||||||
}).inSingletonScope();
|
}).inSingletonScope();
|
||||||
|
|
||||||
// The workspace service extension
|
// The workspace service extension
|
||||||
bind(WorkspaceServiceExt).to(WorkspaceServiceExtImpl).inSingletonScope().onActivation(({ container }, workspaceServiceExt) => {
|
bind(WorkspaceServiceExt).to(WorkspaceServiceExtImpl).inSingletonScope().onActivation(({ container }, workspaceServiceExt: WorkspaceServiceExt) => {
|
||||||
WebSocketConnectionProvider.createProxy(container, WorkspaceServiceExtPath, workspaceServiceExt);
|
WebSocketConnectionProvider.createProxy(container, WorkspaceServiceExtPath, workspaceServiceExt);
|
||||||
// Eagerly active the core, library, and boards services.
|
// Eagerly active the core, library, and boards services.
|
||||||
container.get(CoreService);
|
container.get(CoreService);
|
||||||
@@ -132,30 +172,68 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
|
|||||||
return workspaceServiceExt;
|
return workspaceServiceExt;
|
||||||
});
|
});
|
||||||
|
|
||||||
bind(AWorkspaceService).toSelf().inSingletonScope();
|
// Serial Monitor
|
||||||
rebind(WorkspaceService).to(AWorkspaceService).inSingletonScope();
|
bind(MonitorModel).toSelf().inSingletonScope();
|
||||||
bind(SketchFactory).toSelf().inSingletonScope();
|
bind(FrontendApplicationContribution).toService(MonitorModel);
|
||||||
|
bind(MonitorWidget).toSelf();
|
||||||
|
bindViewContribution(bind, MonitorViewContribution);
|
||||||
|
bind(TabBarToolbarContribution).toService(MonitorViewContribution);
|
||||||
|
bind(WidgetFactory).toDynamicValue(context => ({
|
||||||
|
id: MonitorWidget.ID,
|
||||||
|
createWidget: () => context.container.get(MonitorWidget)
|
||||||
|
}));
|
||||||
|
// Frontend binding for the monitor service
|
||||||
|
bind(MonitorService).toDynamicValue(context => {
|
||||||
|
const connection = context.container.get(WebSocketConnectionProvider);
|
||||||
|
const client = context.container.get(MonitorServiceClientImpl);
|
||||||
|
return connection.createProxy(MonitorServicePath, client);
|
||||||
|
}).inSingletonScope();
|
||||||
|
bind(MonitorConnection).toSelf().inSingletonScope();
|
||||||
|
// Monitor service client to receive and delegate notifications from the backend.
|
||||||
|
bind(MonitorServiceClientImpl).toSelf().inSingletonScope();
|
||||||
|
bind(MonitorServiceClient).toDynamicValue(context => {
|
||||||
|
const client = context.container.get(MonitorServiceClientImpl);
|
||||||
|
WebSocketConnectionProvider.createProxy(context.container, MonitorServicePath, client);
|
||||||
|
return client;
|
||||||
|
}).inSingletonScope();
|
||||||
|
|
||||||
|
bind(ArduinoWorkspaceService).toSelf().inSingletonScope();
|
||||||
|
rebind(WorkspaceService).toService(ArduinoWorkspaceService);
|
||||||
|
|
||||||
const themeService = ThemeService.get();
|
const themeService = ThemeService.get();
|
||||||
themeService.register(...ArduinoTheme.themes);
|
themeService.register(...ArduinoTheme.themes);
|
||||||
|
|
||||||
// customizing default theia
|
// Customizing default Theia layout based on the editor mode: `pro-mode` or `classic`.
|
||||||
unbind(OutlineViewContribution);
|
bind(EditorMode).toSelf().inSingletonScope();
|
||||||
bind(OutlineViewContribution).to(SilentOutlineViewContribution).inSingletonScope();
|
bind(FrontendApplicationContribution).toService(EditorMode);
|
||||||
unbind(ProblemContribution);
|
rebind(OutlineViewContribution).to(ArduinoOutlineViewContribution).inSingletonScope();
|
||||||
bind(ProblemContribution).to(SilentProblemContribution).inSingletonScope();
|
rebind(ProblemContribution).to(ArduinoProblemContribution).inSingletonScope();
|
||||||
unbind(FileNavigatorContribution);
|
rebind(FileNavigatorContribution).to(ArduinoNavigatorContribution).inSingletonScope();
|
||||||
bind(FileNavigatorContribution).to(SilentNavigatorContribution).inSingletonScope();
|
rebind(OutputToolbarContribution).to(ArduinoOutputToolContribution).inSingletonScope();
|
||||||
unbind(OutputToolbarContribution);
|
rebind(EditorContribution).to(ArduinoEditorContribution).inSingletonScope();
|
||||||
bind(OutputToolbarContribution).to(ArduinoOutputToolContribution).inSingletonScope();
|
rebind(MonacoStatusBarContribution).to(ArduinoMonacoStatusBarContribution).inSingletonScope();
|
||||||
unbind(EditorContribution);
|
rebind(ApplicationShell).to(ArduinoApplicationShell).inSingletonScope();
|
||||||
bind(EditorContribution).to(CustomEditorContribution).inSingletonScope();
|
rebind(ScmContribution).to(ArduinoScmContribution).inSingletonScope();
|
||||||
unbind(MonacoStatusBarContribution);
|
rebind(SearchInWorkspaceFrontendContribution).to(ArduinoSearchInWorkspaceContribution).inSingletonScope();
|
||||||
bind(MonacoStatusBarContribution).to(SilentMonacoStatusBarContribution).inSingletonScope();
|
rebind(FrontendApplication).to(ArduinoFrontendApplication).inSingletonScope();
|
||||||
unbind(ApplicationShell);
|
|
||||||
bind(ApplicationShell).to(CustomApplicationShell).inSingletonScope();
|
// Monaco customizations
|
||||||
unbind(FrontendApplication);
|
bind(ArduinoMonacoEditorProvider).toSelf().inSingletonScope();
|
||||||
bind(FrontendApplication).to(CustomFrontendApplication).inSingletonScope();
|
rebind(MonacoEditorProvider).toService(ArduinoMonacoEditorProvider);
|
||||||
unbind(EditorWidgetFactory);
|
|
||||||
bind(EditorWidgetFactory).to(CustomEditorWidgetFactory).inSingletonScope();
|
// Decorator customizations
|
||||||
|
bind(ArduinoTabBarDecoratorService).toSelf().inSingletonScope();
|
||||||
|
rebind(TabBarDecoratorService).toService(ArduinoTabBarDecoratorService);
|
||||||
|
|
||||||
|
// Problem markers
|
||||||
|
bind(ArduinoProblemManager).toSelf().inSingletonScope();
|
||||||
|
rebind(ProblemManager).toService(ArduinoProblemManager);
|
||||||
|
|
||||||
|
// About dialog to show the CLI version
|
||||||
|
bind(ArduinoAboutDialog).toSelf().inSingletonScope();
|
||||||
|
rebind(AboutDialog).toService(ArduinoAboutDialog);
|
||||||
|
|
||||||
|
// Customized layout restorer that can restore the state in async way: https://github.com/eclipse-theia/theia/issues/6579
|
||||||
|
bind(ArduinoShellLayoutRestorer).toSelf().inSingletonScope();
|
||||||
|
rebind(ShellLayoutRestorer).toService(ArduinoShellLayoutRestorer);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const ARDUINO_JSON = MonacoThemeRegistry.SINGLETON.register(
|
|||||||
export class ArduinoTheme {
|
export class ArduinoTheme {
|
||||||
|
|
||||||
static readonly arduino: Theme = {
|
static readonly arduino: Theme = {
|
||||||
|
type: 'light',
|
||||||
id: 'arduino-theme',
|
id: 'arduino-theme',
|
||||||
label: 'Arduino Light Theme',
|
label: 'Arduino Light Theme',
|
||||||
description: 'Arduino Light Theme',
|
description: 'Arduino Light Theme',
|
||||||
@@ -19,9 +20,10 @@ export class ArduinoTheme {
|
|||||||
deactivate() {
|
deactivate() {
|
||||||
ARDUINO_CSS.unuse();
|
ARDUINO_CSS.unuse();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
static readonly themes: Theme[] = [
|
static readonly themes: Theme[] = [
|
||||||
ArduinoTheme.arduino
|
ArduinoTheme.arduino
|
||||||
]
|
];
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { toUnix } from 'upath';
|
||||||
|
import URI from '@theia/core/lib/common/uri';
|
||||||
|
import { isWindows } from '@theia/core/lib/common/os';
|
||||||
|
import { notEmpty } from '@theia/core/lib/common/objects';
|
||||||
|
import { MaybePromise } from '@theia/core/lib/common/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for determining the default workspace location from the
|
||||||
|
* `location.hash`, the historical workspace locations, and recent sketch files.
|
||||||
|
*
|
||||||
|
* The following logic is used for determining the default workspace location:
|
||||||
|
* - `hash` points to an exists in location?
|
||||||
|
* - Yes
|
||||||
|
* - `validate location`. Is valid sketch location?
|
||||||
|
* - Yes
|
||||||
|
* - Done.
|
||||||
|
* - No
|
||||||
|
* - `try open recent workspace roots`, then `try open last modified sketches`, finally `create new sketch`.
|
||||||
|
* - No
|
||||||
|
* - `try open recent workspace roots`, then `try open last modified sketches`, finally `create new sketch`.
|
||||||
|
*/
|
||||||
|
namespace ArduinoWorkspaceRootResolver {
|
||||||
|
export interface InitOptions {
|
||||||
|
readonly isValid: (uri: string) => MaybePromise<boolean>;
|
||||||
|
}
|
||||||
|
export interface ResolveOptions {
|
||||||
|
readonly hash?: string
|
||||||
|
readonly recentWorkspaces: string[];
|
||||||
|
// Gathered from the default sketch folder. The default sketch folder is defined by the CLI.
|
||||||
|
readonly recentSketches: string[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ArduinoWorkspaceRootResolver {
|
||||||
|
|
||||||
|
constructor(protected options: ArduinoWorkspaceRootResolver.InitOptions) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async resolve(options: ArduinoWorkspaceRootResolver.ResolveOptions): Promise<{ uri: string } | undefined> {
|
||||||
|
const { hash, recentWorkspaces, recentSketches } = options;
|
||||||
|
for (const uri of [this.hashToUri(hash), ...recentWorkspaces, ...recentSketches].filter(notEmpty)) {
|
||||||
|
const valid = await this.isValid(uri);
|
||||||
|
if (valid) {
|
||||||
|
return { uri };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected isValid(uri: string): MaybePromise<boolean> {
|
||||||
|
return this.options.isValid(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: here, the `hash` was defined as new `URI(yourValidFsPath).path` so we have to map it to a valid FS path first.
|
||||||
|
// This is important for Windows only and a NOOP on POSIX.
|
||||||
|
// Note: we set the `new URI(myValidUri).path.toString()` as the `hash`. See:
|
||||||
|
// - https://github.com/eclipse-theia/theia/blob/8196e9dcf9c8de8ea0910efeb5334a974f426966/packages/workspace/src/browser/workspace-service.ts#L143 and
|
||||||
|
// - https://github.com/eclipse-theia/theia/blob/8196e9dcf9c8de8ea0910efeb5334a974f426966/packages/workspace/src/browser/workspace-service.ts#L423
|
||||||
|
protected hashToUri(hash: string | undefined): string | undefined {
|
||||||
|
if (hash
|
||||||
|
&& hash.length > 1
|
||||||
|
&& hash.startsWith('#')) {
|
||||||
|
const path = hash.slice(1); // Trim the leading `#`.
|
||||||
|
return new URI(toUnix(path.slice(isWindows && hash.startsWith('/') ? 1 : 0))).withScheme('file').toString();
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,50 +1,79 @@
|
|||||||
import { WorkspaceService } from "@theia/workspace/lib/browser/workspace-service";
|
import { injectable, inject } from 'inversify';
|
||||||
import { injectable, inject } from "inversify";
|
import { MessageService } from '@theia/core';
|
||||||
import { WorkspaceServer } from "@theia/workspace/lib/common";
|
import { LabelProvider } from '@theia/core/lib/browser';
|
||||||
import { FileSystem, FileStat } from "@theia/filesystem/lib/common";
|
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||||
import URI from "@theia/core/lib/common/uri";
|
import { ConfigService } from '../common/protocol/config-service';
|
||||||
import { SketchFactory } from "./sketch-factory";
|
import { SketchesService } from '../common/protocol/sketches-service';
|
||||||
|
import { ArduinoWorkspaceRootResolver } from './arduino-workspace-resolver';
|
||||||
|
import { EditorMode } from './editor-mode';
|
||||||
|
|
||||||
/**
|
|
||||||
* This is workaround to have custom frontend binding for the default workspace, although we
|
|
||||||
* already have a custom binding for the backend.
|
|
||||||
*/
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class AWorkspaceService extends WorkspaceService {
|
export class ArduinoWorkspaceService extends WorkspaceService {
|
||||||
|
|
||||||
@inject(WorkspaceServer)
|
@inject(SketchesService)
|
||||||
protected readonly workspaceServer: WorkspaceServer;
|
protected readonly sketchService: SketchesService;
|
||||||
|
|
||||||
@inject(FileSystem)
|
@inject(ConfigService)
|
||||||
protected readonly fileSystem: FileSystem;
|
protected readonly configService: ConfigService;
|
||||||
|
|
||||||
@inject(SketchFactory)
|
@inject(LabelProvider)
|
||||||
protected readonly sketchFactory: SketchFactory;
|
protected readonly labelProvider: LabelProvider;
|
||||||
|
|
||||||
protected async getDefaultWorkspacePath(): Promise<string | undefined> {
|
@inject(EditorMode)
|
||||||
let result = await super.getDefaultWorkspacePath();
|
protected readonly editorMode: EditorMode;
|
||||||
if (!result) {
|
|
||||||
const userHome = await this.fileSystem.getCurrentUserHome();
|
@inject(MessageService)
|
||||||
if (!userHome) {
|
protected readonly messageService: MessageService;
|
||||||
return;
|
|
||||||
|
private workspaceUri?: Promise<string | undefined>;
|
||||||
|
|
||||||
|
protected getDefaultWorkspaceUri(): Promise<string | undefined> {
|
||||||
|
if (this.workspaceUri) {
|
||||||
|
// Avoid creating a new sketch twice
|
||||||
|
return this.workspaceUri;
|
||||||
|
}
|
||||||
|
this.workspaceUri = (async () => {
|
||||||
|
try {
|
||||||
|
const hash = window.location.hash;
|
||||||
|
const [recentWorkspaces, recentSketches] = await Promise.all([
|
||||||
|
this.server.getRecentWorkspaces(),
|
||||||
|
this.sketchService.getSketches().then(sketches => sketches.map(s => s.uri))
|
||||||
|
]);
|
||||||
|
const toOpen = await new ArduinoWorkspaceRootResolver({
|
||||||
|
isValid: this.isValid.bind(this)
|
||||||
|
}).resolve({ hash, recentWorkspaces, recentSketches });
|
||||||
|
if (toOpen) {
|
||||||
|
const { uri } = toOpen;
|
||||||
|
await this.server.setMostRecentlyUsedWorkspace(uri);
|
||||||
|
return toOpen.uri;
|
||||||
|
}
|
||||||
|
const { sketchDirUri } = (await this.configService.getConfiguration());
|
||||||
|
this.logger.info(`No valid workspace URI found. Creating new sketch in ${sketchDirUri}`)
|
||||||
|
return (await this.sketchService.createNewSketch(sketchDirUri)).uri;
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.fatal(`Failed to determine the sketch directory: ${err}`)
|
||||||
|
this.messageService.error(
|
||||||
|
'There was an error creating the sketch directory. ' +
|
||||||
|
'See the log for more details. ' +
|
||||||
|
'The application will probably not work as expected.')
|
||||||
|
return super.getDefaultWorkspaceUri();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
return this.workspaceUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The backend has created this location if it was missing.
|
private async isValid(uri: string): Promise<boolean> {
|
||||||
result = new URI(userHome.uri).resolve('Arduino-PoC').resolve('Sketches').toString();
|
const exists = await this.fileSystem.exists(uri);
|
||||||
|
if (!exists) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
// The workspace root location must exist. However, when opening a workspace root in pro-mode,
|
||||||
const stat = await this.fileSystem.getFileStat(result);
|
// the workspace root must not be a sketch folder. It can be the default sketch directory, or any other directories, for instance.
|
||||||
if (!stat) {
|
if (this.editorMode.proMode) {
|
||||||
// workspace does not exist yet, create it
|
return true;
|
||||||
await this.fileSystem.createFolder(result);
|
|
||||||
await this.sketchFactory.createNewSketch(new URI(result));
|
|
||||||
}
|
}
|
||||||
|
const sketchFolder = await this.sketchService.isSketchFolder(uri);
|
||||||
return result;
|
return sketchFolder;
|
||||||
}
|
|
||||||
|
|
||||||
protected async setWorkspace(workspaceStat: FileStat | undefined): Promise<void> {
|
|
||||||
await super.setWorkspace(workspaceStat);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { EventEmitter } from "events";
|
|
||||||
import { injectable } from "inversify";
|
|
||||||
|
|
||||||
// TODO (post-PoC): move this to the backend / BoardsService
|
|
||||||
@injectable()
|
|
||||||
export class BoardsNotificationService {
|
|
||||||
|
|
||||||
protected readonly emitter = new EventEmitter();
|
|
||||||
|
|
||||||
public on(event: 'boards-installed', listener: (...args: any[]) => void): this {
|
|
||||||
this.emitter.on(event, listener);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public notifyBoardsInstalled() {
|
|
||||||
this.emitter.emit('boards-installed');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { injectable, inject } from 'inversify';
|
||||||
|
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||||
|
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||||
|
import { BoardsService, Board } from '../../common/protocol/boards-service';
|
||||||
|
import { BoardsServiceClientImpl } from './boards-service-client-impl';
|
||||||
|
import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution';
|
||||||
|
import { InstallationProgressDialog } from '../components/progress-dialog';
|
||||||
|
import { BoardsConfig } from './boards-config';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens on `BoardsConfig.Config` changes, if a board is selected which does not
|
||||||
|
* have the corresponding core installed, it proposes the user to install the core.
|
||||||
|
*/
|
||||||
|
@injectable()
|
||||||
|
export class BoardsAutoInstaller implements FrontendApplicationContribution {
|
||||||
|
|
||||||
|
@inject(MessageService)
|
||||||
|
protected readonly messageService: MessageService;
|
||||||
|
|
||||||
|
@inject(BoardsService)
|
||||||
|
protected readonly boardsService: BoardsService;
|
||||||
|
|
||||||
|
@inject(BoardsServiceClientImpl)
|
||||||
|
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
||||||
|
|
||||||
|
@inject(BoardsListWidgetFrontendContribution)
|
||||||
|
protected readonly boardsManagerFrontendContribution: BoardsListWidgetFrontendContribution;
|
||||||
|
|
||||||
|
onStart(): void {
|
||||||
|
this.boardsServiceClient.onBoardsConfigChanged(this.ensureCoreExists.bind(this));
|
||||||
|
this.ensureCoreExists(this.boardsServiceClient.boardsConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ensureCoreExists(config: BoardsConfig.Config): void {
|
||||||
|
const { selectedBoard } = config;
|
||||||
|
if (selectedBoard) {
|
||||||
|
this.boardsService.search({}).then(({ items }) => {
|
||||||
|
const candidates = items
|
||||||
|
.filter(item => item.boards.some(board => Board.sameAs(board, selectedBoard)))
|
||||||
|
.filter(({ installable, installedVersion }) => installable && !installedVersion);
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
// tslint:disable-next-line:max-line-length
|
||||||
|
this.messageService.info(`The \`"${candidate.name}"\` core has to be installed for the currently selected \`"${selectedBoard.name}"\` board. Do you want to install it now?`, 'Install Manually', 'Yes').then(async answer => {
|
||||||
|
if (answer === 'Yes') {
|
||||||
|
const dialog = new InstallationProgressDialog(candidate.name, candidate.availableVersions[0]);
|
||||||
|
dialog.open();
|
||||||
|
try {
|
||||||
|
await this.boardsService.install({ item: candidate });
|
||||||
|
} finally {
|
||||||
|
dialog.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (answer) {
|
||||||
|
this.boardsManagerFrontendContribution.openView({ reveal: true }).then(widget => widget.refresh(candidate.name.toLocaleLowerCase()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { injectable, inject } from 'inversify';
|
||||||
|
import { Emitter } from '@theia/core/lib/common/event';
|
||||||
|
import { ReactWidget, Message } from '@theia/core/lib/browser';
|
||||||
|
import { BoardsService } from '../../common/protocol/boards-service';
|
||||||
|
import { BoardsConfig } from './boards-config';
|
||||||
|
import { BoardsServiceClientImpl } from './boards-service-client-impl';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class BoardsConfigDialogWidget extends ReactWidget {
|
||||||
|
|
||||||
|
@inject(BoardsService)
|
||||||
|
protected readonly boardsService: BoardsService;
|
||||||
|
|
||||||
|
@inject(BoardsServiceClientImpl)
|
||||||
|
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
||||||
|
|
||||||
|
protected readonly onBoardConfigChangedEmitter = new Emitter<BoardsConfig.Config>();
|
||||||
|
readonly onBoardConfigChanged = this.onBoardConfigChangedEmitter.event;
|
||||||
|
|
||||||
|
protected focusNode: HTMLElement | undefined;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.id = 'select-board-dialog';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fireConfigChanged = (config: BoardsConfig.Config) => {
|
||||||
|
this.onBoardConfigChangedEmitter.fire(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected setFocusNode = (element: HTMLElement | undefined) => {
|
||||||
|
this.focusNode = element;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): React.ReactNode {
|
||||||
|
return <div className='selectBoardContainer'>
|
||||||
|
<BoardsConfig
|
||||||
|
boardsService={this.boardsService}
|
||||||
|
boardsServiceClient={this.boardsServiceClient}
|
||||||
|
onConfigChange={this.fireConfigChanged}
|
||||||
|
onFocusNodeSet={this.setFocusNode} />
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onActivateRequest(msg: Message): void {
|
||||||
|
super.onActivateRequest(msg);
|
||||||
|
if (this.focusNode instanceof HTMLInputElement) {
|
||||||
|
this.focusNode.select();
|
||||||
|
}
|
||||||
|
(this.focusNode || this.node).focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
113
arduino-ide-extension/src/browser/boards/boards-config-dialog.ts
Normal file
113
arduino-ide-extension/src/browser/boards/boards-config-dialog.ts
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import { injectable, inject, postConstruct } from 'inversify';
|
||||||
|
import { Message } from '@phosphor/messaging';
|
||||||
|
import { AbstractDialog, DialogProps, Widget, DialogError } from '@theia/core/lib/browser';
|
||||||
|
import { BoardsService } from '../../common/protocol/boards-service';
|
||||||
|
import { BoardsConfig } from './boards-config';
|
||||||
|
import { BoardsConfigDialogWidget } from './boards-config-dialog-widget';
|
||||||
|
import { BoardsServiceClientImpl } from './boards-service-client-impl';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class BoardsConfigDialogProps extends DialogProps {
|
||||||
|
}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class BoardsConfigDialog extends AbstractDialog<BoardsConfig.Config> {
|
||||||
|
|
||||||
|
@inject(BoardsConfigDialogWidget)
|
||||||
|
protected readonly widget: BoardsConfigDialogWidget;
|
||||||
|
|
||||||
|
@inject(BoardsService)
|
||||||
|
protected readonly boardService: BoardsService;
|
||||||
|
|
||||||
|
@inject(BoardsServiceClientImpl)
|
||||||
|
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
||||||
|
|
||||||
|
protected config: BoardsConfig.Config = {};
|
||||||
|
|
||||||
|
constructor(@inject(BoardsConfigDialogProps) protected readonly props: BoardsConfigDialogProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.contentNode.classList.add('select-board-dialog');
|
||||||
|
this.contentNode.appendChild(this.createDescription());
|
||||||
|
|
||||||
|
this.appendCloseButton('CANCEL');
|
||||||
|
this.appendAcceptButton('OK');
|
||||||
|
}
|
||||||
|
|
||||||
|
@postConstruct()
|
||||||
|
protected init(): void {
|
||||||
|
this.toDispose.push(this.boardsServiceClient.onBoardsConfigChanged(config => {
|
||||||
|
this.config = config;
|
||||||
|
this.update();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createDescription(): HTMLElement {
|
||||||
|
const head = document.createElement('div');
|
||||||
|
head.classList.add('head');
|
||||||
|
|
||||||
|
const title = document.createElement('div');
|
||||||
|
title.textContent = 'Select Other Board & Port';
|
||||||
|
title.classList.add('title');
|
||||||
|
head.appendChild(title);
|
||||||
|
|
||||||
|
const text = document.createElement('div');
|
||||||
|
text.classList.add('text');
|
||||||
|
head.appendChild(text);
|
||||||
|
|
||||||
|
for (const paragraph of [
|
||||||
|
'Select both a Board and a Port if you want to upload a sketch.',
|
||||||
|
'If you only select a Board you will be able just to compile, but not to upload your sketch.'
|
||||||
|
]) {
|
||||||
|
const p = document.createElement('p');
|
||||||
|
p.textContent = paragraph;
|
||||||
|
text.appendChild(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onAfterAttach(msg: Message): void {
|
||||||
|
if (this.widget.isAttached) {
|
||||||
|
Widget.detach(this.widget);
|
||||||
|
}
|
||||||
|
Widget.attach(this.widget, this.contentNode);
|
||||||
|
this.toDisposeOnDetach.push(this.widget.onBoardConfigChanged(config => {
|
||||||
|
this.config = config;
|
||||||
|
this.update();
|
||||||
|
}));
|
||||||
|
super.onAfterAttach(msg);
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onUpdateRequest(msg: Message) {
|
||||||
|
super.onUpdateRequest(msg);
|
||||||
|
this.widget.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onActivateRequest(msg: Message): void {
|
||||||
|
super.onActivateRequest(msg);
|
||||||
|
this.widget.activate();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handleEnter(event: KeyboardEvent): boolean | void {
|
||||||
|
if (event.target instanceof HTMLTextAreaElement) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected isValid(value: BoardsConfig.Config): DialogError {
|
||||||
|
if (!value.selectedBoard) {
|
||||||
|
if (value.selectedPort) {
|
||||||
|
return 'Please pick a board connected to the port you have selected.';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
get value(): BoardsConfig.Config {
|
||||||
|
return this.config;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
295
arduino-ide-extension/src/browser/boards/boards-config.tsx
Normal file
295
arduino-ide-extension/src/browser/boards/boards-config.tsx
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { DisposableCollection } from '@theia/core';
|
||||||
|
import { BoardsService, Board, Port, AttachedSerialBoard, AttachedBoardsChangeEvent } from '../../common/protocol/boards-service';
|
||||||
|
import { BoardsServiceClientImpl } from './boards-service-client-impl';
|
||||||
|
|
||||||
|
export namespace BoardsConfig {
|
||||||
|
|
||||||
|
export interface Config {
|
||||||
|
selectedBoard?: Board;
|
||||||
|
selectedPort?: Port;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
readonly boardsService: BoardsService;
|
||||||
|
readonly boardsServiceClient: BoardsServiceClientImpl;
|
||||||
|
readonly onConfigChange: (config: Config) => void;
|
||||||
|
readonly onFocusNodeSet: (element: HTMLElement | undefined) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface State extends Config {
|
||||||
|
searchResults: Array<Board & { packageName: string }>;
|
||||||
|
knownPorts: Port[];
|
||||||
|
showAllPorts: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class Item<T> extends React.Component<{
|
||||||
|
item: T,
|
||||||
|
label: string,
|
||||||
|
selected: boolean,
|
||||||
|
onClick: (item: T) => void,
|
||||||
|
missing?: boolean,
|
||||||
|
detail?: string
|
||||||
|
}> {
|
||||||
|
|
||||||
|
render(): React.ReactNode {
|
||||||
|
const { selected, label, missing, detail } = this.props;
|
||||||
|
const classNames = ['item'];
|
||||||
|
if (selected) {
|
||||||
|
classNames.push('selected');
|
||||||
|
}
|
||||||
|
if (missing === true) {
|
||||||
|
classNames.push('missing')
|
||||||
|
}
|
||||||
|
return <div onClick={this.onClick} className={classNames.join(' ')} title={`${label}${!detail ? '' : detail}`}>
|
||||||
|
<div className='label'>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
{!detail ? '' : <div className='detail'>{detail}</div>}
|
||||||
|
{!selected ? '' : <div className='selected-icon'><i className='fa fa-check' /></div>}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onClick = () => {
|
||||||
|
this.props.onClick(this.props.item);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BoardsConfig extends React.Component<BoardsConfig.Props, BoardsConfig.State> {
|
||||||
|
|
||||||
|
protected toDispose = new DisposableCollection();
|
||||||
|
|
||||||
|
constructor(props: BoardsConfig.Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
const { boardsConfig } = props.boardsServiceClient;
|
||||||
|
this.state = {
|
||||||
|
searchResults: [],
|
||||||
|
knownPorts: [],
|
||||||
|
showAllPorts: false,
|
||||||
|
...boardsConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.updateBoards();
|
||||||
|
this.props.boardsService.getAvailablePorts().then(({ ports }) => this.updatePorts(ports));
|
||||||
|
const { boardsServiceClient: client } = this.props;
|
||||||
|
this.toDispose.pushAll([
|
||||||
|
client.onBoardsChanged(event => this.updatePorts(event.newState.ports, AttachedBoardsChangeEvent.diff(event).detached.ports)),
|
||||||
|
client.onBoardsConfigChanged(({ selectedBoard, selectedPort }) => {
|
||||||
|
this.setState({ selectedBoard, selectedPort }, () => this.fireConfigChanged());
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount(): void {
|
||||||
|
this.toDispose.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fireConfigChanged() {
|
||||||
|
const { selectedBoard, selectedPort } = this.state;
|
||||||
|
this.props.onConfigChange({ selectedBoard, selectedPort });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updateBoards = (eventOrQuery: React.ChangeEvent<HTMLInputElement> | string = '') => {
|
||||||
|
const query = (typeof eventOrQuery === 'string'
|
||||||
|
? eventOrQuery
|
||||||
|
: eventOrQuery.target.value.toLowerCase()
|
||||||
|
).trim();
|
||||||
|
this.queryBoards({ query }).then(({ searchResults }) => this.setState({ searchResults }));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updatePorts = (ports: Port[] = [], removedPorts: Port[] = []) => {
|
||||||
|
this.queryPorts(Promise.resolve({ ports })).then(({ knownPorts }) => {
|
||||||
|
let { selectedPort } = this.state;
|
||||||
|
// If the currently selected port is not available anymore, unset the selected port.
|
||||||
|
if (removedPorts.some(port => Port.equals(port, selectedPort))) {
|
||||||
|
selectedPort = undefined;
|
||||||
|
}
|
||||||
|
this.setState({ knownPorts, selectedPort }, () => this.fireConfigChanged());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected queryBoards = (options: { query?: string } = {}): Promise<{ searchResults: Array<Board & { packageName: string }> }> => {
|
||||||
|
const { boardsService } = this.props;
|
||||||
|
const query = (options.query || '').toLocaleLowerCase();
|
||||||
|
return new Promise<{ searchResults: Array<Board & { packageName: string }> }>(resolve => {
|
||||||
|
boardsService.search(options)
|
||||||
|
.then(({ items }) => items
|
||||||
|
.map(item => item.boards.map(board => ({ ...board, packageName: item.name })))
|
||||||
|
.reduce((acc, curr) => acc.concat(curr), [])
|
||||||
|
.filter(board => board.name.toLocaleLowerCase().indexOf(query) !== -1)
|
||||||
|
.sort(Board.compare))
|
||||||
|
.then(searchResults => resolve({ searchResults }));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get attachedBoards(): Promise<{ boards: Board[] }> {
|
||||||
|
return this.props.boardsService.getAttachedBoards();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get availablePorts(): Promise<{ ports: Port[] }> {
|
||||||
|
return this.props.boardsService.getAvailablePorts();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected queryPorts = (availablePorts: Promise<{ ports: Port[] }> = this.availablePorts) => {
|
||||||
|
return new Promise<{ knownPorts: Port[] }>(resolve => {
|
||||||
|
availablePorts
|
||||||
|
.then(({ ports }) => ports
|
||||||
|
.sort(Port.compare))
|
||||||
|
.then(knownPorts => resolve({ knownPorts }));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected toggleFilterPorts = () => {
|
||||||
|
this.setState({ showAllPorts: !this.state.showAllPorts });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected selectPort = (selectedPort: Port | undefined) => {
|
||||||
|
this.setState({ selectedPort }, () => this.fireConfigChanged());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected selectBoard = (selectedBoard: Board & { packageName: string } | undefined) => {
|
||||||
|
this.setState({ selectedBoard }, () => this.fireConfigChanged());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected focusNodeSet = (element: HTMLElement | null) => {
|
||||||
|
this.props.onFocusNodeSet(element || undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): React.ReactNode {
|
||||||
|
return <div className='body'>
|
||||||
|
{this.renderContainer('boards', this.renderBoards.bind(this))}
|
||||||
|
{this.renderContainer('ports', this.renderPorts.bind(this), this.renderPortsFooter.bind(this))}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderContainer(title: string, contentRenderer: () => React.ReactNode, footerRenderer?: () => React.ReactNode): React.ReactNode {
|
||||||
|
return <div className='container'>
|
||||||
|
<div className='content'>
|
||||||
|
<div className='title'>
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
{contentRenderer()}
|
||||||
|
<div className='footer'>
|
||||||
|
{(footerRenderer ? footerRenderer() : '')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderBoards(): React.ReactNode {
|
||||||
|
const { selectedBoard, searchResults } = this.state;
|
||||||
|
// Board names are not unique. We show the corresponding core name as a detail.
|
||||||
|
// https://github.com/arduino/arduino-cli/pull/294#issuecomment-513764948
|
||||||
|
const distinctBoardNames = new Map<string, number>();
|
||||||
|
for (const { name } of searchResults) {
|
||||||
|
const counter = distinctBoardNames.get(name) || 0;
|
||||||
|
distinctBoardNames.set(name, counter + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Due to the non-unique board names, we have to check the package name as well.
|
||||||
|
const selected = (board: Board & { packageName: string }) => {
|
||||||
|
if (!!selectedBoard) {
|
||||||
|
if (Board.equals(board, selectedBoard)) {
|
||||||
|
if ('packageName' in selectedBoard) {
|
||||||
|
return board.packageName === (selectedBoard as any).packageName;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <React.Fragment>
|
||||||
|
<div className='search'>
|
||||||
|
<input type='search' placeholder='SEARCH BOARD' onChange={this.updateBoards} ref={this.focusNodeSet} />
|
||||||
|
<i className='fa fa-search'></i>
|
||||||
|
</div>
|
||||||
|
<div className='boards list'>
|
||||||
|
{this.state.searchResults.map(board => <Item<Board & { packageName: string }>
|
||||||
|
key={`${board.name}-${board.packageName}`}
|
||||||
|
item={board}
|
||||||
|
label={board.name}
|
||||||
|
detail={(distinctBoardNames.get(board.name) || 0) > 1 ? ` - ${board.packageName}` : undefined}
|
||||||
|
selected={selected(board)}
|
||||||
|
onClick={this.selectBoard}
|
||||||
|
missing={!Board.installed(board)}
|
||||||
|
/>)}
|
||||||
|
</div>
|
||||||
|
</React.Fragment>;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderPorts(): React.ReactNode {
|
||||||
|
const filter = this.state.showAllPorts ? () => true : Port.isBoardPort;
|
||||||
|
const ports = this.state.knownPorts.filter(filter);
|
||||||
|
return !ports.length ?
|
||||||
|
(
|
||||||
|
<div className='loading noselect'>
|
||||||
|
No ports discovered
|
||||||
|
</div>
|
||||||
|
) :
|
||||||
|
(
|
||||||
|
<div className='ports list'>
|
||||||
|
{ports.map(port => <Item<Port>
|
||||||
|
key={Port.toString(port)}
|
||||||
|
item={port}
|
||||||
|
label={Port.toString(port)}
|
||||||
|
selected={Port.equals(this.state.selectedPort, port)}
|
||||||
|
onClick={this.selectPort}
|
||||||
|
/>)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderPortsFooter(): React.ReactNode {
|
||||||
|
return <div className='noselect'>
|
||||||
|
<label
|
||||||
|
title='Shows all available ports when enabled'>
|
||||||
|
<input
|
||||||
|
type='checkbox'
|
||||||
|
defaultChecked={this.state.showAllPorts}
|
||||||
|
onChange={this.toggleFilterPorts}
|
||||||
|
/>
|
||||||
|
<span>Show all ports</span>
|
||||||
|
</label>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace BoardsConfig {
|
||||||
|
|
||||||
|
export namespace Config {
|
||||||
|
|
||||||
|
export function sameAs(config: Config, other: Config | AttachedSerialBoard): boolean {
|
||||||
|
const { selectedBoard, selectedPort } = config;
|
||||||
|
if (AttachedSerialBoard.is(other)) {
|
||||||
|
return !!selectedBoard
|
||||||
|
&& Board.equals(other, selectedBoard)
|
||||||
|
&& Port.sameAs(selectedPort, other.port);
|
||||||
|
}
|
||||||
|
return sameAs(config, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function equals(left: Config, right: Config): boolean {
|
||||||
|
return left.selectedBoard === right.selectedBoard
|
||||||
|
&& left.selectedPort === right.selectedPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toString(config: Config, options: { default: string } = { default: '' }): string {
|
||||||
|
const { selectedBoard, selectedPort: port } = config;
|
||||||
|
if (!selectedBoard) {
|
||||||
|
return options.default;
|
||||||
|
}
|
||||||
|
const { name } = selectedBoard;
|
||||||
|
return `${name}${port ? ' at ' + Port.toString(port) : ''}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { inject, injectable } from 'inversify';
|
||||||
|
import { BoardPackage, BoardsService } from '../../common/protocol/boards-service';
|
||||||
|
import { ListWidget } from '../components/component-list/list-widget';
|
||||||
|
import { ListItemRenderer } from '../components/component-list/list-item-renderer';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class BoardsListWidget extends ListWidget<BoardPackage> {
|
||||||
|
|
||||||
|
static WIDGET_ID = 'boards-list-widget';
|
||||||
|
static WIDGET_LABEL = 'Boards Manager';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@inject(BoardsService) protected service: BoardsService,
|
||||||
|
@inject(ListItemRenderer) protected itemRenderer: ListItemRenderer<BoardPackage>) {
|
||||||
|
|
||||||
|
super({
|
||||||
|
id: BoardsListWidget.WIDGET_ID,
|
||||||
|
label: BoardsListWidget.WIDGET_LABEL,
|
||||||
|
iconClass: 'fa fa-microchip',
|
||||||
|
searchable: service,
|
||||||
|
installable: service,
|
||||||
|
itemLabel: (item: BoardPackage) => item.name,
|
||||||
|
itemRenderer
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { ListWidget } from './list-widget';
|
|
||||||
|
|
||||||
export class BoardsListWidget extends ListWidget {
|
|
||||||
|
|
||||||
static WIDGET_ID = 'boards-list-widget';
|
|
||||||
static WIDGET_LABEL = 'Boards Manager';
|
|
||||||
|
|
||||||
protected widgetProps(): ListWidget.Props {
|
|
||||||
return {
|
|
||||||
id: BoardsListWidget.WIDGET_ID,
|
|
||||||
title: BoardsListWidget.WIDGET_LABEL,
|
|
||||||
iconClass: 'fa fa-microchip'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
import { injectable, inject } from 'inversify';
|
||||||
|
import { Emitter } from '@theia/core/lib/common/event';
|
||||||
|
import { ILogger } from '@theia/core/lib/common/logger';
|
||||||
|
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||||
|
import { LocalStorageService } from '@theia/core/lib/browser/storage-service';
|
||||||
|
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||||
|
import { RecursiveRequired } from '../../common/types';
|
||||||
|
import { BoardsServiceClient, AttachedBoardsChangeEvent, BoardInstalledEvent, AttachedSerialBoard, Board, Port, BoardUninstalledEvent } from '../../common/protocol/boards-service';
|
||||||
|
import { BoardsConfig } from './boards-config';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class BoardsServiceClientImpl implements BoardsServiceClient, FrontendApplicationContribution {
|
||||||
|
|
||||||
|
@inject(ILogger)
|
||||||
|
protected logger: ILogger;
|
||||||
|
|
||||||
|
@inject(MessageService)
|
||||||
|
protected messageService: MessageService;
|
||||||
|
|
||||||
|
@inject(LocalStorageService)
|
||||||
|
protected storageService: LocalStorageService;
|
||||||
|
|
||||||
|
protected readonly onBoardInstalledEmitter = new Emitter<BoardInstalledEvent>();
|
||||||
|
protected readonly onBoardUninstalledEmitter = new Emitter<BoardUninstalledEvent>();
|
||||||
|
protected readonly onAttachedBoardsChangedEmitter = new Emitter<AttachedBoardsChangeEvent>();
|
||||||
|
protected readonly onSelectedBoardsConfigChangedEmitter = new Emitter<BoardsConfig.Config>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for the auto-reconnecting. Sometimes, the attached board gets disconnected after uploading something to it.
|
||||||
|
* It happens with certain boards on Windows. For example, the `MKR1000` boards is selected on post `COM5` on Windows,
|
||||||
|
* perform an upload, the board automatically disconnects and reconnects, but on another port, `COM10`.
|
||||||
|
* We have to listen on such changes and auto-reconnect the same board on another port.
|
||||||
|
* See: https://arduino.slack.com/archives/CJJHJCJSJ/p1568645417013000?thread_ts=1568640504.009400&cid=CJJHJCJSJ
|
||||||
|
*/
|
||||||
|
protected latestValidBoardsConfig: RecursiveRequired<BoardsConfig.Config> | undefined = undefined;
|
||||||
|
protected _boardsConfig: BoardsConfig.Config = {};
|
||||||
|
|
||||||
|
readonly onBoardsChanged = this.onAttachedBoardsChangedEmitter.event;
|
||||||
|
readonly onBoardInstalled = this.onBoardInstalledEmitter.event;
|
||||||
|
readonly onBoardUninstalled = this.onBoardUninstalledEmitter.event;
|
||||||
|
readonly onBoardsConfigChanged = this.onSelectedBoardsConfigChangedEmitter.event;
|
||||||
|
|
||||||
|
async onStart(): Promise<void> {
|
||||||
|
return this.loadState();
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void {
|
||||||
|
this.logger.info('Attached boards and available ports changed: ', JSON.stringify(event));
|
||||||
|
const { detached, attached } = AttachedBoardsChangeEvent.diff(event);
|
||||||
|
const { selectedPort, selectedBoard } = this.boardsConfig;
|
||||||
|
this.onAttachedBoardsChangedEmitter.fire(event);
|
||||||
|
// Dynamically unset the port if is not available anymore. A port can be "detached" when removing a board.
|
||||||
|
if (detached.ports.some(port => Port.equals(selectedPort, port))) {
|
||||||
|
this.boardsConfig = {
|
||||||
|
selectedBoard,
|
||||||
|
selectedPort: undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Try to reconnect.
|
||||||
|
this.tryReconnect(attached.boards, attached.ports);
|
||||||
|
}
|
||||||
|
|
||||||
|
async tryReconnect(attachedBoards: Board[], availablePorts: Port[]): Promise<boolean> {
|
||||||
|
if (this.latestValidBoardsConfig && !this.canUploadTo(this.boardsConfig)) {
|
||||||
|
for (const board of attachedBoards.filter(AttachedSerialBoard.is)) {
|
||||||
|
if (this.latestValidBoardsConfig.selectedBoard.fqbn === board.fqbn
|
||||||
|
&& this.latestValidBoardsConfig.selectedBoard.name === board.name
|
||||||
|
&& Port.sameAs(this.latestValidBoardsConfig.selectedPort, board.port)) {
|
||||||
|
|
||||||
|
this.boardsConfig = this.latestValidBoardsConfig;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we could not find an exact match, we compare the board FQBN-name pairs and ignore the port, as it might have changed.
|
||||||
|
// See documentation on `latestValidBoardsConfig`.
|
||||||
|
for (const board of attachedBoards.filter(AttachedSerialBoard.is)) {
|
||||||
|
if (this.latestValidBoardsConfig.selectedBoard.fqbn === board.fqbn
|
||||||
|
&& this.latestValidBoardsConfig.selectedBoard.name === board.name) {
|
||||||
|
|
||||||
|
this.boardsConfig = {
|
||||||
|
...this.latestValidBoardsConfig,
|
||||||
|
selectedPort: availablePorts.find(port => Port.sameAs(port, board.port))
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyBoardInstalled(event: BoardInstalledEvent): void {
|
||||||
|
this.logger.info('Board installed: ', JSON.stringify(event));
|
||||||
|
this.onBoardInstalledEmitter.fire(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyBoardUninstalled(event: BoardUninstalledEvent): void {
|
||||||
|
this.logger.info('Board uninstalled: ', JSON.stringify(event));
|
||||||
|
this.onBoardUninstalledEmitter.fire(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
set boardsConfig(config: BoardsConfig.Config) {
|
||||||
|
this.logger.info('Board config changed: ', JSON.stringify(config));
|
||||||
|
this._boardsConfig = config;
|
||||||
|
if (this.canUploadTo(this._boardsConfig)) {
|
||||||
|
this.latestValidBoardsConfig = this._boardsConfig;
|
||||||
|
}
|
||||||
|
this.saveState().then(() => this.onSelectedBoardsConfigChangedEmitter.fire(this._boardsConfig));
|
||||||
|
}
|
||||||
|
|
||||||
|
get boardsConfig(): BoardsConfig.Config {
|
||||||
|
return this._boardsConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `true` if the `config.selectedBoard` is defined; hence can compile against the board. Otherwise, `false`.
|
||||||
|
*/
|
||||||
|
canVerify(
|
||||||
|
config: BoardsConfig.Config | undefined = this.boardsConfig,
|
||||||
|
options: { silent: boolean } = { silent: true }): config is BoardsConfig.Config & { selectedBoard: Board } {
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.selectedBoard) {
|
||||||
|
if (!options.silent) {
|
||||||
|
this.messageService.warn('No boards selected.', { timeout: 3000 });
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `true` if the `canVerify` and the `config.selectedPort` is also set with FQBN, hence can upload to board. Otherwise, `false`.
|
||||||
|
*/
|
||||||
|
canUploadTo(
|
||||||
|
config: BoardsConfig.Config | undefined = this.boardsConfig,
|
||||||
|
options: { silent: boolean } = { silent: true }): config is RecursiveRequired<BoardsConfig.Config> {
|
||||||
|
|
||||||
|
if (!this.canVerify(config, options)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name } = config.selectedBoard;
|
||||||
|
if (!config.selectedPort) {
|
||||||
|
if (!options.silent) {
|
||||||
|
this.messageService.warn(`No ports selected for board: '${name}'.`, { timeout: 3000 });
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.selectedBoard.fqbn) {
|
||||||
|
if (!options.silent) {
|
||||||
|
this.messageService.warn(`The FQBN is not available for the selected board ${name}. Do you have the corresponding core installed?`, { timeout: 3000 });
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected saveState(): Promise<void> {
|
||||||
|
return this.storageService.setData('latest-valid-boards-config', this.latestValidBoardsConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async loadState(): Promise<void> {
|
||||||
|
const storedValidBoardsConfig = await this.storageService.getData<RecursiveRequired<BoardsConfig.Config>>('latest-valid-boards-config');
|
||||||
|
if (storedValidBoardsConfig) {
|
||||||
|
this.latestValidBoardsConfig = storedValidBoardsConfig;
|
||||||
|
if (this.canUploadTo(this.latestValidBoardsConfig)) {
|
||||||
|
this.boardsConfig = this.latestValidBoardsConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,61 +1,42 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { BoardsService, Board, AttachedSerialBoard } from '../../common/protocol/boards-service';
|
import * as ReactDOM from 'react-dom';
|
||||||
import { ContextMenuRenderer, StatusBar, StatusBarAlignment } from '@theia/core/lib/browser';
|
import { CommandRegistry, DisposableCollection } from '@theia/core';
|
||||||
import { BoardsNotificationService } from '../boards-notification-service';
|
import { BoardsService, Board, AttachedSerialBoard, Port } from '../../common/protocol/boards-service';
|
||||||
import { Command, CommandRegistry } from '@theia/core';
|
|
||||||
import { ArduinoCommands } from '../arduino-commands';
|
import { ArduinoCommands } from '../arduino-commands';
|
||||||
import ReactDOM = require('react-dom');
|
import { BoardsServiceClientImpl } from './boards-service-client-impl';
|
||||||
|
import { BoardsConfig } from './boards-config';
|
||||||
|
|
||||||
export interface BoardsDropdownItem {
|
export interface BoardsDropDownListCoords {
|
||||||
label: string;
|
readonly top: number;
|
||||||
commandExecutor: () => void;
|
readonly left: number;
|
||||||
isSelected: () => boolean;
|
readonly width: number;
|
||||||
}
|
readonly paddingTop: number;
|
||||||
|
|
||||||
export interface BoardsDropDownListCoord {
|
|
||||||
top: number;
|
|
||||||
left: number;
|
|
||||||
width: number;
|
|
||||||
paddingTop: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace BoardsDropdownItemComponent {
|
|
||||||
export interface Props {
|
|
||||||
label: string;
|
|
||||||
onClick: () => void;
|
|
||||||
isSelected: boolean;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BoardsDropdownItemComponent extends React.Component<BoardsDropdownItemComponent.Props> {
|
|
||||||
render() {
|
|
||||||
return <div className={`arduino-boards-dropdown-item ${this.props.isSelected ? 'selected' : ''}`} onClick={this.props.onClick}>
|
|
||||||
<div>{this.props.label}</div>
|
|
||||||
{this.props.isSelected ? <span className='fa fa-check'></span> : ''}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace BoardsDropDown {
|
export namespace BoardsDropDown {
|
||||||
export interface Props {
|
export interface Props {
|
||||||
readonly coords: BoardsDropDownListCoord;
|
readonly coords: BoardsDropDownListCoords | 'hidden';
|
||||||
readonly isOpen: boolean;
|
readonly items: Item[];
|
||||||
readonly dropDownItems: BoardsDropdownItem[];
|
readonly openBoardsConfig: () => void;
|
||||||
readonly openDialog: () => void;
|
}
|
||||||
|
export interface Item {
|
||||||
|
readonly label: string;
|
||||||
|
readonly selected: boolean;
|
||||||
|
readonly onClick: () => void;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BoardsDropDown extends React.Component<BoardsDropDown.Props> {
|
export class BoardsDropDown extends React.Component<BoardsDropDown.Props> {
|
||||||
protected dropdownId: string = 'boards-dropdown-container';
|
|
||||||
protected dropdownElement: HTMLElement;
|
protected dropdownElement: HTMLElement;
|
||||||
|
|
||||||
constructor(props: BoardsDropDown.Props) {
|
constructor(props: BoardsDropDown.Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
let list = document.getElementById(this.dropdownId);
|
let list = document.getElementById('boards-dropdown-container');
|
||||||
if (!list) {
|
if (!list) {
|
||||||
list = document.createElement('div');
|
list = document.createElement('div');
|
||||||
list.id = this.dropdownId;
|
list.id = 'boards-dropdown-container';
|
||||||
document.body.appendChild(list);
|
document.body.appendChild(list);
|
||||||
this.dropdownElement = list;
|
this.dropdownElement = list;
|
||||||
}
|
}
|
||||||
@@ -65,179 +46,157 @@ export class BoardsDropDown extends React.Component<BoardsDropDown.Props> {
|
|||||||
return ReactDOM.createPortal(this.renderNode(), this.dropdownElement);
|
return ReactDOM.createPortal(this.renderNode(), this.dropdownElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderNode(): React.ReactNode {
|
protected renderNode(): React.ReactNode {
|
||||||
if (this.props.isOpen) {
|
const { coords, items } = this.props;
|
||||||
|
if (coords === 'hidden') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
items.push({
|
||||||
|
label: 'Select Other Board & Port',
|
||||||
|
selected: false,
|
||||||
|
onClick: () => this.props.openBoardsConfig()
|
||||||
|
})
|
||||||
return <div className='arduino-boards-dropdown-list'
|
return <div className='arduino-boards-dropdown-list'
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: this.props.coords.top,
|
...coords
|
||||||
left: this.props.coords.left,
|
|
||||||
width: this.props.coords.width,
|
|
||||||
paddingTop: this.props.coords.paddingTop
|
|
||||||
}}>
|
}}>
|
||||||
{
|
{items.map(this.renderItem)}
|
||||||
this.props.dropDownItems.map(item => {
|
|
||||||
return <React.Fragment key={item.label}>
|
|
||||||
<BoardsDropdownItemComponent isSelected={item.isSelected()} label={item.label} onClick={item.commandExecutor}></BoardsDropdownItemComponent>
|
|
||||||
</React.Fragment>;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
<BoardsDropdownItemComponent isSelected={false} label={'Select Other Board & Port'} onClick={this.props.openDialog}></BoardsDropdownItemComponent>
|
|
||||||
</div>
|
</div>
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected renderItem(item: BoardsDropDown.Item): React.ReactNode {
|
||||||
|
const { label, selected, onClick } = item;
|
||||||
|
return <div key={label} className={`arduino-boards-dropdown-item ${selected ? 'selected' : ''}`} onClick={onClick}>
|
||||||
|
<div>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
{selected ? <span className='fa fa-check'/> : ''}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace BoardsToolBarItem {
|
export namespace BoardsToolBarItem {
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
readonly contextMenuRenderer: ContextMenuRenderer;
|
|
||||||
readonly boardsNotificationService: BoardsNotificationService;
|
|
||||||
readonly boardService: BoardsService;
|
readonly boardService: BoardsService;
|
||||||
|
readonly boardsServiceClient: BoardsServiceClientImpl;
|
||||||
readonly commands: CommandRegistry;
|
readonly commands: CommandRegistry;
|
||||||
readonly statusBar: StatusBar;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
selectedBoard?: Board;
|
boardsConfig: BoardsConfig.Config;
|
||||||
selectedIsAttached: boolean;
|
attachedBoards: Board[];
|
||||||
boardItems: BoardsDropdownItem[];
|
availablePorts: Port[];
|
||||||
isOpen: boolean;
|
coords: BoardsDropDownListCoords | 'hidden';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BoardsToolBarItem extends React.Component<BoardsToolBarItem.Props, BoardsToolBarItem.State> {
|
export class BoardsToolBarItem extends React.Component<BoardsToolBarItem.Props, BoardsToolBarItem.State> {
|
||||||
|
|
||||||
protected attachedBoards: Board[];
|
static TOOLBAR_ID: 'boards-toolbar';
|
||||||
protected dropDownListCoord: BoardsDropDownListCoord;
|
|
||||||
|
protected readonly toDispose: DisposableCollection = new DisposableCollection();
|
||||||
|
|
||||||
constructor(props: BoardsToolBarItem.Props) {
|
constructor(props: BoardsToolBarItem.Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
selectedBoard: undefined,
|
boardsConfig: this.props.boardsServiceClient.boardsConfig,
|
||||||
selectedIsAttached: true,
|
attachedBoards: [],
|
||||||
boardItems: [],
|
availablePorts: [],
|
||||||
isOpen: false
|
coords: 'hidden'
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener('click', () => {
|
document.addEventListener('click', () => {
|
||||||
this.setState({ isOpen: false });
|
this.setState({ coords: 'hidden' });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.setAttachedBoards();
|
const { boardsServiceClient: client, boardService } = this.props;
|
||||||
|
this.toDispose.pushAll([
|
||||||
|
client.onBoardsConfigChanged(boardsConfig => this.setState({ boardsConfig })),
|
||||||
|
client.onBoardsChanged(({ newState }) => this.setState({ attachedBoards: newState.boards, availablePorts: newState.ports }))
|
||||||
|
]);
|
||||||
|
Promise.all([
|
||||||
|
boardService.getAttachedBoards(),
|
||||||
|
boardService.getAvailablePorts()
|
||||||
|
]).then(([{boards: attachedBoards}, { ports: availablePorts }]) => {
|
||||||
|
this.setState({ attachedBoards, availablePorts })
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelectedBoard(board: Board) {
|
componentWillUnmount(): void {
|
||||||
if (this.attachedBoards && this.attachedBoards.length) {
|
this.toDispose.dispose();
|
||||||
this.setState({ selectedIsAttached: !!this.attachedBoards.find(attachedBoard => attachedBoard.name === board.name) });
|
|
||||||
}
|
|
||||||
this.setState({ selectedBoard: board });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async setAttachedBoards() {
|
protected readonly show = (event: React.MouseEvent<HTMLElement>) => {
|
||||||
const { boards } = await this.props.boardService.getAttachedBoards();
|
const { currentTarget: element } = event;
|
||||||
this.attachedBoards = boards;
|
if (element instanceof HTMLElement) {
|
||||||
if (this.attachedBoards.length) {
|
if (this.state.coords === 'hidden') {
|
||||||
await this.createBoardDropdownItems();
|
const rect = element.getBoundingClientRect();
|
||||||
await this.props.boardService.selectBoard(this.attachedBoards[0]);
|
this.setState({
|
||||||
this.setSelectedBoard(this.attachedBoards[0]);
|
coords: {
|
||||||
}
|
top: rect.top,
|
||||||
}
|
left: rect.left,
|
||||||
|
width: rect.width,
|
||||||
protected createBoardDropdownItems() {
|
paddingTop: rect.height
|
||||||
const boardItems: BoardsDropdownItem[] = [];
|
|
||||||
this.attachedBoards.forEach(board => {
|
|
||||||
const { commands } = this.props;
|
|
||||||
const port = this.getPort(board);
|
|
||||||
const command: Command = {
|
|
||||||
id: 'selectBoard' + port
|
|
||||||
}
|
|
||||||
commands.registerCommand(command, {
|
|
||||||
execute: () => {
|
|
||||||
commands.executeCommand(ArduinoCommands.SELECT_BOARD.id, board);
|
|
||||||
this.setState({ isOpen: false, selectedBoard: board });
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
boardItems.push({
|
} else {
|
||||||
commandExecutor: () => commands.executeCommand(command.id),
|
this.setState({ coords: 'hidden'});
|
||||||
label: board.name + ' at ' + port,
|
|
||||||
isSelected: () => this.doIsSelectedBoard(board)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this.setState({ boardItems });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected doIsSelectedBoard = (board: Board) => this.isSelectedBoard(board);
|
|
||||||
protected isSelectedBoard(board: Board): boolean {
|
|
||||||
return AttachedSerialBoard.is(board) &&
|
|
||||||
!!this.state.selectedBoard &&
|
|
||||||
AttachedSerialBoard.is(this.state.selectedBoard) &&
|
|
||||||
board.port === this.state.selectedBoard.port &&
|
|
||||||
board.fqbn === this.state.selectedBoard.fqbn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getPort(board: Board): string {
|
|
||||||
if (AttachedSerialBoard.is(board)) {
|
|
||||||
return board.port;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected readonly doShowSelectBoardsMenu = (event: React.MouseEvent<HTMLElement>) => {
|
|
||||||
this.showSelectBoardsMenu(event);
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.nativeEvent.stopImmediatePropagation();
|
event.nativeEvent.stopImmediatePropagation();
|
||||||
};
|
};
|
||||||
protected showSelectBoardsMenu(event: React.MouseEvent<HTMLElement>) {
|
|
||||||
const el = (event.currentTarget as HTMLElement);
|
|
||||||
if (el) {
|
|
||||||
this.dropDownListCoord = {
|
|
||||||
top: el.getBoundingClientRect().top,
|
|
||||||
left: el.getBoundingClientRect().left,
|
|
||||||
paddingTop: el.getBoundingClientRect().height,
|
|
||||||
width: el.getBoundingClientRect().width
|
|
||||||
}
|
|
||||||
this.setState({ isOpen: !this.state.isOpen });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): React.ReactNode {
|
render(): React.ReactNode {
|
||||||
const selectedBoard = this.state.selectedBoard;
|
const { boardsConfig, coords, attachedBoards, availablePorts } = this.state;
|
||||||
const port = selectedBoard ? this.getPort(selectedBoard) : undefined;
|
const title = BoardsConfig.Config.toString(boardsConfig, { default: 'no board selected' });
|
||||||
const boardTxt = selectedBoard && `${selectedBoard.name}${port ? ' at ' + port : ''}` || '';
|
const configuredBoard = attachedBoards
|
||||||
this.props.statusBar.setElement('arduino-selected-board', {
|
.filter(AttachedSerialBoard.is)
|
||||||
alignment: StatusBarAlignment.RIGHT,
|
.filter(board => availablePorts.some(port => Port.sameAs(port, board.port)))
|
||||||
text: boardTxt
|
.filter(board => BoardsConfig.Config.sameAs(boardsConfig, board)).shift();
|
||||||
});
|
|
||||||
|
const items = attachedBoards.filter(AttachedSerialBoard.is).map(board => ({
|
||||||
|
label: `${board.name} at ${board.port}`,
|
||||||
|
selected: configuredBoard === board,
|
||||||
|
onClick: () => {
|
||||||
|
this.props.boardsServiceClient.boardsConfig = {
|
||||||
|
selectedBoard: board,
|
||||||
|
selectedPort: availablePorts.find(port => Port.sameAs(port, board.port))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
<div className='arduino-boards-toolbar-item-container'>
|
<div className='arduino-boards-toolbar-item-container'>
|
||||||
<div className='arduino-boards-toolbar-item' title={boardTxt}>
|
<div className='arduino-boards-toolbar-item' title={title}>
|
||||||
<div className='inner-container' onClick={this.doShowSelectBoardsMenu}>
|
<div className='inner-container' onClick={this.show}>
|
||||||
<span className={!selectedBoard || !this.state.selectedIsAttached ? 'fa fa-times notAttached' : ''}></span>
|
<span className={!configuredBoard ? 'fa fa-times notAttached' : ''}/>
|
||||||
<div className='label noWrapInfo'>
|
<div className='label noWrapInfo'>
|
||||||
<div className='noWrapInfo noselect'>
|
<div className='noWrapInfo noselect'>
|
||||||
{selectedBoard ? boardTxt : 'no board selected'}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span className='fa fa-caret-down caret'></span>
|
<span className='fa fa-caret-down caret'/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<BoardsDropDown
|
<BoardsDropDown
|
||||||
isOpen={this.state.isOpen}
|
coords={coords}
|
||||||
coords={this.dropDownListCoord}
|
items={items}
|
||||||
dropDownItems={this.state.boardItems}
|
openBoardsConfig={this.openDialog}>
|
||||||
openDialog={this.openDialog}>
|
|
||||||
</BoardsDropDown>
|
</BoardsDropDown>
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected openDialog = () => {
|
protected openDialog = () => {
|
||||||
this.props.commands.executeCommand(ArduinoCommands.OPEN_BOARDS_DIALOG.id);
|
this.props.commands.executeCommand(ArduinoCommands.OPEN_BOARDS_DIALOG.id);
|
||||||
this.setState({ isOpen: false });
|
this.setState({ coords: 'hidden' });
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,12 @@
|
|||||||
import { injectable } from 'inversify';
|
import { injectable } from 'inversify';
|
||||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
|
||||||
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
|
|
||||||
import { ListWidget } from './list-widget';
|
|
||||||
import { BoardsListWidget } from './boards-list-widget';
|
|
||||||
import { MenuModelRegistry } from '@theia/core';
|
import { MenuModelRegistry } from '@theia/core';
|
||||||
|
import { BoardsListWidget } from './boards-list-widget';
|
||||||
import { ArduinoMenus } from '../arduino-frontend-contribution';
|
import { ArduinoMenus } from '../arduino-frontend-contribution';
|
||||||
|
import { BoardPackage } from '../../common/protocol/boards-service';
|
||||||
|
import { ListWidgetFrontendContribution } from '../components/component-list/list-widget-frontend-contribution';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export abstract class ListWidgetFrontendContribution extends AbstractViewContribution<ListWidget> implements FrontendApplicationContribution {
|
export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution<BoardPackage> {
|
||||||
|
|
||||||
async initializeLayout(): Promise<void> {
|
|
||||||
// await this.openView();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution {
|
|
||||||
|
|
||||||
static readonly OPEN_MANAGER = `${BoardsListWidget.WIDGET_ID}:toggle`;
|
static readonly OPEN_MANAGER = `${BoardsListWidget.WIDGET_ID}:toggle`;
|
||||||
|
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { inject, injectable, postConstruct } from 'inversify';
|
|
||||||
import { Message } from '@phosphor/messaging';
|
|
||||||
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
|
|
||||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
|
||||||
import { FilterableListContainer } from '../components/component-list/filterable-list-container';
|
|
||||||
import { BoardsService, Board, BoardPackage } from '../../common/protocol/boards-service';
|
|
||||||
import { BoardsNotificationService } from '../boards-notification-service';
|
|
||||||
import { LibraryService } from '../../common/protocol/library-service';
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export abstract class ListWidget extends ReactWidget {
|
|
||||||
|
|
||||||
@inject(BoardsService)
|
|
||||||
protected readonly boardsService: BoardsService;
|
|
||||||
|
|
||||||
@inject(WindowService)
|
|
||||||
protected readonly windowService: WindowService;
|
|
||||||
|
|
||||||
@inject(BoardsNotificationService)
|
|
||||||
protected readonly boardsNotificationService: BoardsNotificationService;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
const { id, title, iconClass } = this.widgetProps();
|
|
||||||
this.id = id;
|
|
||||||
this.title.label = title;
|
|
||||||
this.title.caption = title;
|
|
||||||
this.title.iconClass = iconClass;
|
|
||||||
this.title.closable = true;
|
|
||||||
this.addClass(ListWidget.Styles.LIST_WIDGET_CLASS);
|
|
||||||
this.node.tabIndex = 0; // To be able to set the focus on the widget.
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract widgetProps(): ListWidget.Props;
|
|
||||||
|
|
||||||
@postConstruct()
|
|
||||||
protected init(): void {
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onActivateRequest(msg: Message): void {
|
|
||||||
super.onActivateRequest(msg);
|
|
||||||
this.node.focus();
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onUpdateRequest(msg: Message): void {
|
|
||||||
super.onUpdateRequest(msg);
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): React.ReactNode {
|
|
||||||
const boardsServiceDelegate = this.boardsService;
|
|
||||||
const boardsService: BoardsService = {
|
|
||||||
getAttachedBoards: () => boardsServiceDelegate.getAttachedBoards(),
|
|
||||||
selectBoard: (board: Board) => boardsServiceDelegate.selectBoard(board),
|
|
||||||
getSelectBoard: () => boardsServiceDelegate.getSelectBoard(),
|
|
||||||
search: (options: { query?: string, props?: LibraryService.Search.Props }) => boardsServiceDelegate.search(options),
|
|
||||||
install: async (item: BoardPackage) => {
|
|
||||||
await boardsServiceDelegate.install(item);
|
|
||||||
this.boardsNotificationService.notifyBoardsInstalled();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return <FilterableListContainer
|
|
||||||
service={boardsService}
|
|
||||||
windowService={this.windowService}
|
|
||||||
/>;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace ListWidget {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Props for customizing the abstract list widget.
|
|
||||||
*/
|
|
||||||
export interface Props {
|
|
||||||
readonly id: string;
|
|
||||||
readonly title: string;
|
|
||||||
readonly iconClass: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace Styles {
|
|
||||||
export const LIST_WIDGET_CLASS = 'arduino-list-widget'
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,305 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { ReactWidget } from '@theia/core/lib/browser';
|
|
||||||
import { injectable, inject } from 'inversify';
|
|
||||||
import { BoardsService, Board, BoardPackage, AttachedSerialBoard } from '../../common/protocol/boards-service';
|
|
||||||
import { BoardsNotificationService } from '../boards-notification-service';
|
|
||||||
import { Emitter, Event } from '@theia/core';
|
|
||||||
|
|
||||||
export interface BoardAndPortSelection {
|
|
||||||
board?: Board;
|
|
||||||
port?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace BoardAndPortSelectableItem {
|
|
||||||
export interface Props {
|
|
||||||
item: BoardAndPortSelection,
|
|
||||||
selected: boolean,
|
|
||||||
onSelect: (selection: BoardAndPortSelection) => void
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BoardAndPortSelectableItem extends React.Component<BoardAndPortSelectableItem.Props> {
|
|
||||||
|
|
||||||
render(): React.ReactNode {
|
|
||||||
if (this.props.item.board || this.props.item.port) {
|
|
||||||
return <div onClick={this.select} className={`item ${this.props.selected ? 'selected' : ''}`}>
|
|
||||||
{this.props.item.board ? this.props.item.board.name : this.props.item.port}
|
|
||||||
{this.props.selected ? <i className='fa fa-check'></i> : ''}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected readonly select = (() => {
|
|
||||||
this.props.onSelect({ board: this.props.item.board, port: this.props.item.port })
|
|
||||||
}).bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace BoardAndPortSelectionList {
|
|
||||||
export interface Props {
|
|
||||||
type: 'boards' | 'ports';
|
|
||||||
list: BoardAndPortSelection[];
|
|
||||||
onSelect: (selection: BoardAndPortSelection) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface State {
|
|
||||||
selection: BoardAndPortSelection
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BoardAndPortSelectionList extends React.Component<BoardAndPortSelectionList.Props, BoardAndPortSelectionList.State> {
|
|
||||||
|
|
||||||
constructor(props: BoardAndPortSelectionList.Props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
selection: {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reset(): void {
|
|
||||||
this.setState({ selection: {} });
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): React.ReactNode {
|
|
||||||
return <div className={`${this.props.type} list`}>
|
|
||||||
{this.props.list.map(item => <BoardAndPortSelectableItem
|
|
||||||
key={item.board ? item.board.name : item.port}
|
|
||||||
onSelect={this.doSelect}
|
|
||||||
item={item}
|
|
||||||
selected={this.isSelectedItem(item)}
|
|
||||||
/>)}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
protected readonly doSelect = (boardAndPortSelection: BoardAndPortSelection) => {
|
|
||||||
this.setState({ selection: boardAndPortSelection });
|
|
||||||
this.props.onSelect(boardAndPortSelection);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected readonly isSelectedItem = ((item: BoardAndPortSelection) => {
|
|
||||||
if (this.state.selection.board) {
|
|
||||||
return (this.state.selection.board === item.board);
|
|
||||||
} else if (this.state.selection.port) {
|
|
||||||
return (this.state.selection.port === item.port);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
protected readonly isSelectedPort = ((port: string) => {
|
|
||||||
return (this.state.selection.port && this.state.selection.port === port) || false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace BoardAndPortSelectionComponent {
|
|
||||||
export interface Props {
|
|
||||||
boardsService: BoardsService;
|
|
||||||
onSelect: (selection: BoardAndPortSelection) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface State {
|
|
||||||
boards: Board[];
|
|
||||||
ports: string[];
|
|
||||||
selection: BoardAndPortSelection;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BoardAndPortSelectionComponent extends React.Component<BoardAndPortSelectionComponent.Props, BoardAndPortSelectionComponent.State> {
|
|
||||||
|
|
||||||
protected allBoards: Board[] = [];
|
|
||||||
protected boardListComponent: BoardAndPortSelectionList | null;
|
|
||||||
protected portListComponent: BoardAndPortSelectionList | null;
|
|
||||||
|
|
||||||
constructor(props: BoardAndPortSelectionComponent.Props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
boards: [],
|
|
||||||
ports: [],
|
|
||||||
selection: {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.searchAvailableBoards();
|
|
||||||
this.setPorts();
|
|
||||||
}
|
|
||||||
|
|
||||||
reset(): void {
|
|
||||||
if (this.boardListComponent) {
|
|
||||||
this.boardListComponent.reset();
|
|
||||||
}
|
|
||||||
if (this.portListComponent) {
|
|
||||||
this.portListComponent.reset();
|
|
||||||
}
|
|
||||||
this.setState({ selection: {} });
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): React.ReactNode {
|
|
||||||
return <React.Fragment>
|
|
||||||
<div className='body'>
|
|
||||||
<div className='left container'>
|
|
||||||
<div className='content'>
|
|
||||||
<div className='title'>
|
|
||||||
BOARDS
|
|
||||||
</div>
|
|
||||||
<div className='search'>
|
|
||||||
<input type='search' placeholder='SEARCH BOARD' onChange={this.doFilter} />
|
|
||||||
<i className='fa fa-search'></i>
|
|
||||||
</div>
|
|
||||||
<BoardAndPortSelectionList
|
|
||||||
ref={ref => { this.boardListComponent = ref }}
|
|
||||||
type='boards'
|
|
||||||
onSelect={this.doSelect}
|
|
||||||
list={this.state.boards.map<BoardAndPortSelection>(board => ({ board }))} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='right container'>
|
|
||||||
<div className='content'>
|
|
||||||
<div className='title'>
|
|
||||||
PORTS
|
|
||||||
</div>
|
|
||||||
{
|
|
||||||
this.state.ports.length ?
|
|
||||||
<BoardAndPortSelectionList
|
|
||||||
ref={ref => { this.portListComponent = ref }}
|
|
||||||
type='ports'
|
|
||||||
onSelect={this.doSelect}
|
|
||||||
list={this.state.ports.map<BoardAndPortSelection>(port => ({ port }))} /> : 'loading ports...'
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</React.Fragment>
|
|
||||||
}
|
|
||||||
|
|
||||||
protected sort(items: Board[]): Board[] {
|
|
||||||
return items.sort((a, b) => {
|
|
||||||
if (a.name < b.name) {
|
|
||||||
return -1;
|
|
||||||
} else if (a.name === b.name) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected readonly doSelect = (boardAndPortSelection: BoardAndPortSelection) => {
|
|
||||||
const selection = this.state.selection;
|
|
||||||
if (boardAndPortSelection.board) {
|
|
||||||
selection.board = boardAndPortSelection.board;
|
|
||||||
}
|
|
||||||
if (boardAndPortSelection.port) {
|
|
||||||
selection.port = boardAndPortSelection.port;
|
|
||||||
}
|
|
||||||
this.setState({ selection });
|
|
||||||
this.props.onSelect(this.state.selection);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected readonly doFilter = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const boards = this.allBoards.filter(board => board.name.toLowerCase().indexOf(event.target.value.toLowerCase()) >= 0);
|
|
||||||
this.setState({ boards })
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async searchAvailableBoards() {
|
|
||||||
const boardPkg = await this.props.boardsService.search({});
|
|
||||||
const boards = [].concat.apply([], boardPkg.items.map<Board[]>(item => item.boards)) as Board[];
|
|
||||||
this.allBoards = this.sort(boards);
|
|
||||||
this.setState({ boards: this.allBoards });
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async setPorts() {
|
|
||||||
const ports: string[] = [];
|
|
||||||
const { boards } = await this.props.boardsService.getAttachedBoards();
|
|
||||||
boards.forEach(board => {
|
|
||||||
if (AttachedSerialBoard.is(board)) {
|
|
||||||
ports.push(board.port);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.setState({ ports });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class SelectBoardDialogWidget extends ReactWidget {
|
|
||||||
@inject(BoardsService)
|
|
||||||
protected readonly boardsService: BoardsService;
|
|
||||||
@inject(BoardsNotificationService)
|
|
||||||
protected readonly boardsNotificationService: BoardsNotificationService;
|
|
||||||
|
|
||||||
protected readonly onChangedEmitter = new Emitter<BoardAndPortSelection>();
|
|
||||||
protected boardAndPortSelectionComponent: BoardAndPortSelectionComponent | null;
|
|
||||||
protected attachedBoards: Promise<{ boards: Board[] }>;
|
|
||||||
|
|
||||||
boardAndPort: BoardAndPortSelection = {};
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.id = 'select-board-dialog';
|
|
||||||
|
|
||||||
this.toDispose.push(this.onChangedEmitter);
|
|
||||||
}
|
|
||||||
|
|
||||||
get onChanged(): Event<BoardAndPortSelection> {
|
|
||||||
return this.onChangedEmitter.event;
|
|
||||||
}
|
|
||||||
|
|
||||||
reset(): void {
|
|
||||||
if (this.boardAndPortSelectionComponent) {
|
|
||||||
this.boardAndPortSelectionComponent.reset();
|
|
||||||
}
|
|
||||||
this.boardAndPort = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
setAttachedBoards(attachedBoards: Promise<{ boards: Board[] }>): void {
|
|
||||||
this.attachedBoards = attachedBoards;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fireChanged(boardAndPort: BoardAndPortSelection): void {
|
|
||||||
this.onChangedEmitter.fire(boardAndPort);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): React.ReactNode {
|
|
||||||
let content: React.ReactNode;
|
|
||||||
|
|
||||||
const boardsServiceDelegate = this.boardsService;
|
|
||||||
const attachedBoards = this.attachedBoards;
|
|
||||||
const boardsService: BoardsService = {
|
|
||||||
getAttachedBoards: () => attachedBoards,
|
|
||||||
selectBoard: (board: Board) => boardsServiceDelegate.selectBoard(board),
|
|
||||||
getSelectBoard: () => boardsServiceDelegate.getSelectBoard(),
|
|
||||||
search: (options: { query?: string }) => boardsServiceDelegate.search(options),
|
|
||||||
install: async (item: BoardPackage) => {
|
|
||||||
await boardsServiceDelegate.install(item);
|
|
||||||
this.boardsNotificationService.notifyBoardsInstalled();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
content = <React.Fragment>
|
|
||||||
<div className='selectBoardContainer'>
|
|
||||||
<div className='head'>
|
|
||||||
<div className='title'>
|
|
||||||
Select Other Board & Port
|
|
||||||
</div>
|
|
||||||
<div className='text'>
|
|
||||||
<p>Select both a BOARD and a PORT if you want to upload a sketch.</p>
|
|
||||||
<p>If you only select a BOARD you will be able just to compile,</p>
|
|
||||||
<p>but not to upload your sketch.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<BoardAndPortSelectionComponent
|
|
||||||
ref={ref => this.boardAndPortSelectionComponent = ref}
|
|
||||||
boardsService={boardsService}
|
|
||||||
onSelect={this.onSelect} />
|
|
||||||
</div>
|
|
||||||
</React.Fragment>
|
|
||||||
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected readonly onSelect = (selection: BoardAndPortSelection) => { this.doOnSelect(selection) };
|
|
||||||
protected doOnSelect(selection: BoardAndPortSelection) {
|
|
||||||
this.boardAndPort = selection;
|
|
||||||
this.fireChanged(this.boardAndPort);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
import { AbstractDialog, DialogProps, Widget, Panel, DialogError } from '@theia/core/lib/browser';
|
|
||||||
import { injectable, inject } from 'inversify';
|
|
||||||
import { SelectBoardDialogWidget, BoardAndPortSelection } from './select-board-dialog-widget';
|
|
||||||
import { Message } from '@phosphor/messaging';
|
|
||||||
import { Disposable } from '@theia/core';
|
|
||||||
import { Board, BoardsService, AttachedSerialBoard } from '../../common/protocol/boards-service';
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class SelectBoardDialogProps extends DialogProps {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class SelectBoardDialog extends AbstractDialog<BoardAndPortSelection> {
|
|
||||||
|
|
||||||
protected readonly dialogPanel: Panel;
|
|
||||||
protected attachedBoards: Board[];
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@inject(SelectBoardDialogProps) protected readonly props: SelectBoardDialogProps,
|
|
||||||
@inject(SelectBoardDialogWidget) protected readonly widget: SelectBoardDialogWidget,
|
|
||||||
@inject(BoardsService) protected readonly boardService: BoardsService
|
|
||||||
) {
|
|
||||||
super({ title: props.title });
|
|
||||||
|
|
||||||
this.dialogPanel = new Panel();
|
|
||||||
this.dialogPanel.addWidget(this.widget);
|
|
||||||
|
|
||||||
this.contentNode.classList.add('select-board-dialog');
|
|
||||||
|
|
||||||
this.toDispose.push(this.widget.onChanged(() => this.update()));
|
|
||||||
this.toDispose.push(this.dialogPanel);
|
|
||||||
|
|
||||||
this.attachedBoards = [];
|
|
||||||
this.init();
|
|
||||||
|
|
||||||
this.appendCloseButton('CANCEL');
|
|
||||||
this.appendAcceptButton('OK');
|
|
||||||
}
|
|
||||||
|
|
||||||
protected init() {
|
|
||||||
const boards = this.boardService.getAttachedBoards();
|
|
||||||
boards.then(b => this.attachedBoards = b.boards);
|
|
||||||
this.widget.setAttachedBoards(boards);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onAfterAttach(msg: Message): void {
|
|
||||||
Widget.attach(this.dialogPanel, this.contentNode);
|
|
||||||
|
|
||||||
this.toDisposeOnDetach.push(Disposable.create(() => {
|
|
||||||
Widget.detach(this.dialogPanel);
|
|
||||||
}))
|
|
||||||
|
|
||||||
super.onAfterAttach(msg);
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onUpdateRequest(msg: Message) {
|
|
||||||
super.onUpdateRequest(msg);
|
|
||||||
this.widget.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onActivateRequest(msg: Message): void {
|
|
||||||
this.widget.activate();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected handleEnter(event: KeyboardEvent): boolean | void {
|
|
||||||
if (event.target instanceof HTMLTextAreaElement) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected isValid(value: BoardAndPortSelection): DialogError {
|
|
||||||
if (!value.board) {
|
|
||||||
if (value.port) {
|
|
||||||
return 'Please pick the Board connected to the Port you have selected';
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
get value(): BoardAndPortSelection {
|
|
||||||
const boardAndPortSelection = this.widget.boardAndPort;
|
|
||||||
if (this.attachedBoards.length) {
|
|
||||||
boardAndPortSelection.board = this.attachedBoards.find(b => {
|
|
||||||
const isAttachedBoard = !!boardAndPortSelection.board &&
|
|
||||||
b.name === boardAndPortSelection.board.name &&
|
|
||||||
b.fqbn === boardAndPortSelection.board.fqbn;
|
|
||||||
if (boardAndPortSelection.port) {
|
|
||||||
return isAttachedBoard &&
|
|
||||||
AttachedSerialBoard.is(b) &&
|
|
||||||
b.port === boardAndPortSelection.port;
|
|
||||||
} else {
|
|
||||||
return isAttachedBoard;
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|| boardAndPortSelection.board;
|
|
||||||
}
|
|
||||||
return boardAndPortSelection;
|
|
||||||
}
|
|
||||||
|
|
||||||
close(): void {
|
|
||||||
this.widget.reset();
|
|
||||||
super.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
onAfterDetach(msg: Message) {
|
|
||||||
this.widget.reset();
|
|
||||||
super.onAfterDetach(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import Select from 'react-select';
|
||||||
|
import { Styles } from 'react-select/src/styles';
|
||||||
|
import { Props } from 'react-select/src/components';
|
||||||
|
import { ThemeConfig } from 'react-select/src/theme';
|
||||||
|
|
||||||
|
export class ArduinoSelect<T> extends Select<T> {
|
||||||
|
|
||||||
|
constructor(props: Readonly<Props<T>>) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): React.ReactNode {
|
||||||
|
const controlHeight = 27; // from `monitor.css` -> `.serial-monitor-container .head` (`height: 27px;`)
|
||||||
|
const styles: Styles = {
|
||||||
|
control: styles => ({
|
||||||
|
...styles,
|
||||||
|
minWidth: 120,
|
||||||
|
color: 'var(--theia-ui-font-color1)'
|
||||||
|
}),
|
||||||
|
dropdownIndicator: styles => ({
|
||||||
|
...styles,
|
||||||
|
padding: 0
|
||||||
|
}),
|
||||||
|
indicatorSeparator: () => ({
|
||||||
|
display: 'none'
|
||||||
|
}),
|
||||||
|
indicatorsContainer: () => ({
|
||||||
|
padding: '0px 5px'
|
||||||
|
}),
|
||||||
|
menu: styles => ({
|
||||||
|
...styles,
|
||||||
|
marginTop: 0
|
||||||
|
})
|
||||||
|
};
|
||||||
|
const theme: ThemeConfig = theme => ({
|
||||||
|
...theme,
|
||||||
|
borderRadius: 0,
|
||||||
|
spacing: {
|
||||||
|
controlHeight,
|
||||||
|
baseUnit: 2,
|
||||||
|
menuGutter: 4
|
||||||
|
}, colors: {
|
||||||
|
...theme.colors,
|
||||||
|
// `primary50`??? it's crazy but apparently, without this, we would get a light-blueish
|
||||||
|
// color when selecting an option in the select by clicking and then not releasing the button.
|
||||||
|
// https://react-select.com/styles#overriding-the-theme
|
||||||
|
primary50: 'var(--theia-accent-color4)',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const DropdownIndicator = () => <span className='fa fa-caret-down caret' />;
|
||||||
|
return <Select
|
||||||
|
{...this.props}
|
||||||
|
components={{ DropdownIndicator }}
|
||||||
|
theme={theme}
|
||||||
|
styles={styles}
|
||||||
|
classNamePrefix='arduino-select'
|
||||||
|
isSearchable={false}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,78 +1,66 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
import { Installable } from '../../../common/protocol/installable';
|
||||||
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||||
|
import { ListItemRenderer } from './list-item-renderer';
|
||||||
|
|
||||||
export class ComponentListItem extends React.Component<ComponentListItem.Props> {
|
export class ComponentListItem<T extends ArduinoComponent> extends React.Component<ComponentListItem.Props<T>, ComponentListItem.State> {
|
||||||
|
|
||||||
protected onClick = (event: React.SyntheticEvent<HTMLAnchorElement, Event>) => {
|
constructor(props: ComponentListItem.Props<T>) {
|
||||||
const { target } = event.nativeEvent;
|
super(props);
|
||||||
if (target instanceof HTMLAnchorElement) {
|
if (props.item.installable) {
|
||||||
this.props.windowService.openNewWindow(target.href);
|
const version = props.item.availableVersions.filter(version => version !== props.item.installedVersion)[0];
|
||||||
event.nativeEvent.preventDefault();
|
this.state = {
|
||||||
|
selectedVersion: version
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async install(item: ArduinoComponent): Promise<void> {
|
protected async install(item: T): Promise<void> {
|
||||||
await this.props.install(item);
|
const toInstall = this.state.selectedVersion;
|
||||||
|
const version = this.props.item.availableVersions.filter(version => version !== this.state.selectedVersion)[0];
|
||||||
|
this.setState({
|
||||||
|
selectedVersion: version
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await this.props.install(item, toInstall);
|
||||||
|
} catch {
|
||||||
|
this.setState({
|
||||||
|
selectedVersion: toInstall
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async uninstall(item: T): Promise<void> {
|
||||||
|
await this.props.uninstall(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onVersionChange(version: Installable.Version) {
|
||||||
|
this.setState({ selectedVersion: version });
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): React.ReactNode {
|
render(): React.ReactNode {
|
||||||
const { item } = this.props;
|
const { item, itemRenderer } = this.props;
|
||||||
|
return itemRenderer.renderItem(
|
||||||
const style = ComponentListItem.Styles;
|
Object.assign(this.state, { item }),
|
||||||
const name = <span className={style.NAME_CLASS}>{item.name}</span>;
|
this.install.bind(this),
|
||||||
const author = <span className={style.AUTHOR_CLASS}>{item.author}</span>;
|
this.uninstall.bind(this),
|
||||||
const installedVersion = !!item.installedVersion && <div className={style.VERSION_INFO_CLASS}>
|
this.onVersionChange.bind(this)
|
||||||
<span className={style.VERSION_CLASS}>Version {item.installedVersion}</span>
|
);
|
||||||
<span className={style.INSTALLED_CLASS}>INSTALLED</span>
|
|
||||||
</div>;
|
|
||||||
|
|
||||||
const summary = <div className={style.SUMMARY_CLASS}>{item.summary}</div>;
|
|
||||||
|
|
||||||
const moreInfo = !!item.moreInfoLink && <a href={item.moreInfoLink} onClick={this.onClick}>More info</a>;
|
|
||||||
const install = this.props.install && item.installable && !item.installedVersion &&
|
|
||||||
<button className={style.INSTALL_BTN_CLASS} onClick={this.install.bind(this, item)}>INSTALL</button>;
|
|
||||||
|
|
||||||
return <div className={[style.LIST_ITEM_CLASS, style.NO_SELECT_CLASS].join(' ')}>
|
|
||||||
<div className={style.HEADER_CLASS}>
|
|
||||||
<span>{name} by {author}</span>
|
|
||||||
{installedVersion}
|
|
||||||
</div>
|
|
||||||
<div className={style.CONTENT_CLASS}>
|
|
||||||
{summary}
|
|
||||||
</div>
|
|
||||||
<div className={style.FOOTER_CLASS}>
|
|
||||||
{moreInfo}
|
|
||||||
{install}
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace ComponentListItem {
|
export namespace ComponentListItem {
|
||||||
|
|
||||||
export interface Props {
|
export interface Props<T extends ArduinoComponent> {
|
||||||
readonly item: ArduinoComponent;
|
readonly item: T;
|
||||||
readonly windowService: WindowService;
|
readonly install: (item: T, version?: Installable.Version) => Promise<void>;
|
||||||
readonly install: (comp: ArduinoComponent) => Promise<void>;
|
readonly uninstall: (item: T) => Promise<void>;
|
||||||
|
readonly itemRenderer: ListItemRenderer<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Styles {
|
export interface State {
|
||||||
export const LIST_ITEM_CLASS = 'component-list-item';
|
selectedVersion?: Installable.Version;
|
||||||
export const HEADER_CLASS = 'header';
|
|
||||||
export const VERSION_INFO_CLASS = 'version-info';
|
|
||||||
export const CONTENT_CLASS = 'content';
|
|
||||||
export const FOOTER_CLASS = 'footer';
|
|
||||||
export const INSTALLED_CLASS = 'installed';
|
|
||||||
export const NO_SELECT_CLASS = 'noselect';
|
|
||||||
|
|
||||||
export const NAME_CLASS = 'name';
|
|
||||||
export const AUTHOR_CLASS = 'author';
|
|
||||||
export const VERSION_CLASS = 'version';
|
|
||||||
export const SUMMARY_CLASS = 'summary';
|
|
||||||
export const DESCRIPTION_CLASS = 'description';
|
|
||||||
export const INSTALL_BTN_CLASS = 'install';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
import { Installable } from '../../../common/protocol/installable';
|
||||||
import { ComponentListItem } from './component-list-item';
|
|
||||||
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||||
|
import { ComponentListItem } from './component-list-item';
|
||||||
|
import { ListItemRenderer } from './list-item-renderer';
|
||||||
|
|
||||||
export class ComponentList extends React.Component<ComponentList.Props> {
|
export class ComponentList<T extends ArduinoComponent> extends React.Component<ComponentList.Props<T>> {
|
||||||
|
|
||||||
protected container?: HTMLElement;
|
protected container?: HTMLElement;
|
||||||
|
|
||||||
render(): React.ReactNode {
|
render(): React.ReactNode {
|
||||||
return <div
|
return <div
|
||||||
className={'items-container'}
|
className={'items-container'}
|
||||||
ref={element => this.container = element || undefined}>
|
ref={this.setRef}>
|
||||||
{this.props.items.map(item => this.createItem(item))}
|
{this.props.items.map(item => this.createItem(item))}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
@@ -21,19 +22,30 @@ export class ComponentList extends React.Component<ComponentList.Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createItem(item: ArduinoComponent): React.ReactNode {
|
protected setRef = (element: HTMLElement | null) => {
|
||||||
return <ComponentListItem key={item.name} item={item} windowService={this.props.windowService} install={this.props.install} />
|
this.container = element || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createItem(item: T): React.ReactNode {
|
||||||
|
return <ComponentListItem<T>
|
||||||
|
key={this.props.itemLabel(item)}
|
||||||
|
item={item}
|
||||||
|
itemRenderer={this.props.itemRenderer}
|
||||||
|
install={this.props.install}
|
||||||
|
uninstall={this.props.uninstall} />
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace ComponentList {
|
export namespace ComponentList {
|
||||||
|
|
||||||
export interface Props {
|
export interface Props<T extends ArduinoComponent> {
|
||||||
readonly items: ArduinoComponent[];
|
readonly items: T[];
|
||||||
readonly windowService: WindowService;
|
readonly itemLabel: (item: T) => string;
|
||||||
readonly install: (comp: ArduinoComponent) => Promise<void>;
|
readonly itemRenderer: ListItemRenderer<T>;
|
||||||
readonly resolveContainer?: (element: HTMLElement) => void;
|
readonly install: (item: T, version?: Installable.Version) => Promise<void>;
|
||||||
|
readonly uninstall: (item: T) => Promise<void>;
|
||||||
|
readonly resolveContainer: (element: HTMLElement) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,36 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
import debounce = require('lodash.debounce');
|
||||||
import { SearchBar } from './search-bar';
|
import { Event } from '@theia/core/lib/common/event';
|
||||||
import { ComponentList } from './component-list';
|
import { ConfirmDialog } from '@theia/core/lib/browser/dialogs';
|
||||||
import { LibraryService } from '../../../common/protocol/library-service';
|
import { Searchable } from '../../../common/protocol/searchable';
|
||||||
|
import { Installable } from '../../../common/protocol/installable';
|
||||||
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||||
import { InstallationProgressDialog } from '../installation-progress-dialog';
|
import { InstallationProgressDialog, UninstallationProgressDialog } from '../progress-dialog';
|
||||||
|
import { SearchBar } from './search-bar';
|
||||||
|
import { ListWidget } from './list-widget';
|
||||||
|
import { ComponentList } from './component-list';
|
||||||
|
import { ListItemRenderer } from './list-item-renderer';
|
||||||
|
|
||||||
export class FilterableListContainer extends React.Component<FilterableListContainer.Props, FilterableListContainer.State> {
|
export class FilterableListContainer<T extends ArduinoComponent> extends React.Component<FilterableListContainer.Props<T>, FilterableListContainer.State<T>> {
|
||||||
|
|
||||||
constructor(props: Readonly<FilterableListContainer.Props>) {
|
constructor(props: Readonly<FilterableListContainer.Props<T>>) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
filterText: '',
|
filterText: '',
|
||||||
items: []
|
items: []
|
||||||
};
|
};
|
||||||
this.handleFilterTextChange = this.handleFilterTextChange.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount(): void {
|
componentDidMount(): void {
|
||||||
|
this.search = debounce(this.search, 500);
|
||||||
this.handleFilterTextChange('');
|
this.handleFilterTextChange('');
|
||||||
|
this.props.filterTextChangeEvent(this.handleFilterTextChange.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(): void {
|
||||||
|
// See: arduino/arduino-pro-ide#101
|
||||||
|
// Resets the top of the perfect scroll-bar's thumb.
|
||||||
|
this.props.container.updateScrollBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): React.ReactNode {
|
render(): React.ReactNode {
|
||||||
@@ -42,36 +54,66 @@ export class FilterableListContainer extends React.Component<FilterableListConta
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected renderComponentList(): React.ReactNode {
|
protected renderComponentList(): React.ReactNode {
|
||||||
return <ComponentList
|
const { itemLabel, resolveContainer, itemRenderer } = this.props;
|
||||||
|
return <ComponentList<T>
|
||||||
items={this.state.items}
|
items={this.state.items}
|
||||||
|
itemLabel={itemLabel}
|
||||||
|
itemRenderer={itemRenderer}
|
||||||
install={this.install.bind(this)}
|
install={this.install.bind(this)}
|
||||||
windowService={this.props.windowService}
|
uninstall={this.uninstall.bind(this)}
|
||||||
resolveContainer={this.props.resolveContainer}
|
resolveContainer={resolveContainer}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleFilterTextChange(filterText: string): void {
|
protected handleFilterTextChange = (filterText: string) => {
|
||||||
const { props } = this.state;
|
this.setState({ filterText });
|
||||||
this.props.service.search({ query: filterText, props }).then(result => {
|
this.search(filterText);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected search(query: string): void {
|
||||||
|
const { searchable } = this.props;
|
||||||
|
searchable.search({ query: query.trim() }).then(result => {
|
||||||
const { items } = result;
|
const { items } = result;
|
||||||
this.setState({
|
this.setState({
|
||||||
filterText,
|
|
||||||
items: this.sort(items)
|
items: this.sort(items)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected sort(items: ArduinoComponent[]): ArduinoComponent[] {
|
protected sort(items: T[]): T[] {
|
||||||
return items.sort((left, right) => left.name.localeCompare(right.name));
|
const { itemLabel } = this.props;
|
||||||
|
return items.sort((left, right) => itemLabel(left).localeCompare(itemLabel(right)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async install(comp: ArduinoComponent): Promise<void> {
|
protected async install(item: T, version: Installable.Version): Promise<void> {
|
||||||
const dialog = new InstallationProgressDialog(comp.name);
|
const { installable, searchable, itemLabel } = this.props;
|
||||||
|
const dialog = new InstallationProgressDialog(itemLabel(item), version);
|
||||||
dialog.open();
|
dialog.open();
|
||||||
try {
|
try {
|
||||||
await this.props.service.install(comp);
|
await installable.install({ item, version });
|
||||||
const { props } = this.state;
|
const { items } = await searchable.search({ query: this.state.filterText });
|
||||||
const { items } = await this.props.service.search({ query: this.state.filterText, props });
|
this.setState({ items: this.sort(items) });
|
||||||
|
} finally {
|
||||||
|
dialog.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async uninstall(item: T): Promise<void> {
|
||||||
|
const uninstall = await new ConfirmDialog({
|
||||||
|
title: 'Uninstall',
|
||||||
|
msg: `Do you want to uninstall ${item.name}?`,
|
||||||
|
ok: 'Yes',
|
||||||
|
cancel: 'No'
|
||||||
|
}).open();
|
||||||
|
if (!uninstall) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { installable, searchable, itemLabel } = this.props;
|
||||||
|
const dialog = new UninstallationProgressDialog(itemLabel(item));
|
||||||
|
dialog.open();
|
||||||
|
try {
|
||||||
|
await installable.uninstall({ item });
|
||||||
|
const { items } = await searchable.search({ query: this.state.filterText });
|
||||||
this.setState({ items: this.sort(items) });
|
this.setState({ items: this.sort(items) });
|
||||||
} finally {
|
} finally {
|
||||||
dialog.close();
|
dialog.close();
|
||||||
@@ -82,23 +124,20 @@ export class FilterableListContainer extends React.Component<FilterableListConta
|
|||||||
|
|
||||||
export namespace FilterableListContainer {
|
export namespace FilterableListContainer {
|
||||||
|
|
||||||
export interface Props {
|
export interface Props<T extends ArduinoComponent> {
|
||||||
readonly service: ComponentSource;
|
readonly container: ListWidget<T>;
|
||||||
readonly windowService: WindowService;
|
readonly installable: Installable<T>;
|
||||||
readonly resolveContainer?: (element: HTMLElement) => void;
|
readonly searchable: Searchable<T>;
|
||||||
readonly resolveFocus?: (element: HTMLElement | undefined) => void;
|
readonly itemLabel: (item: T) => string;
|
||||||
|
readonly itemRenderer: ListItemRenderer<T>;
|
||||||
|
readonly resolveContainer: (element: HTMLElement) => void;
|
||||||
|
readonly resolveFocus: (element: HTMLElement | undefined) => void;
|
||||||
|
readonly filterTextChangeEvent: Event<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface State {
|
export interface State<T> {
|
||||||
filterText: string;
|
filterText: string;
|
||||||
items: ArduinoComponent[];
|
items: T[];
|
||||||
props?: LibraryService.Search.Props;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ComponentSource {
|
|
||||||
search(req: { query: string, props?: LibraryService.Search.Props }): Promise<{ items: ArduinoComponent[] }>
|
|
||||||
install(board: ArduinoComponent): Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { inject, injectable } from 'inversify';
|
||||||
|
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
||||||
|
import { Installable } from '../../../common/protocol/installable';
|
||||||
|
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||||
|
import { ComponentListItem } from './component-list-item';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class ListItemRenderer<T extends ArduinoComponent> {
|
||||||
|
|
||||||
|
@inject(WindowService)
|
||||||
|
protected windowService: WindowService;
|
||||||
|
|
||||||
|
protected onMoreInfoClick = (event: React.SyntheticEvent<HTMLAnchorElement, Event>) => {
|
||||||
|
const { target } = event.nativeEvent;
|
||||||
|
if (target instanceof HTMLAnchorElement) {
|
||||||
|
this.windowService.openNewWindow(target.href, { external: true });
|
||||||
|
event.nativeEvent.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderItem(
|
||||||
|
input: ComponentListItem.State & { item: T },
|
||||||
|
install: (item: T) => Promise<void>,
|
||||||
|
uninstall: (item: T) => Promise<void>,
|
||||||
|
onVersionChange: (version: Installable.Version) => void
|
||||||
|
): React.ReactNode {
|
||||||
|
|
||||||
|
const { item } = input;
|
||||||
|
let nameAndAuthor: JSX.Element;
|
||||||
|
if (item.name && item.author) {
|
||||||
|
const name = <span className='name'>{item.name}</span>;
|
||||||
|
const author = <span className='author'>{item.author}</span>;
|
||||||
|
nameAndAuthor = <span>{name} by {author}</span>
|
||||||
|
} else if (item.name) {
|
||||||
|
nameAndAuthor = <span className='name'>{item.name}</span>;
|
||||||
|
} else if ((item as any).id) {
|
||||||
|
nameAndAuthor = <span className='name'>{(item as any).id}</span>;
|
||||||
|
} else {
|
||||||
|
nameAndAuthor = <span className='name'>Unknown</span>;
|
||||||
|
}
|
||||||
|
const onClickUninstall = () => uninstall(item);
|
||||||
|
const installedVersion = !!item.installedVersion && <div className='version-info'>
|
||||||
|
<span className='version'>Version {item.installedVersion}</span>
|
||||||
|
<span className='installed' onClick={onClickUninstall} />
|
||||||
|
</div>;
|
||||||
|
|
||||||
|
const summary = <div className='summary'>{item.summary}</div>;
|
||||||
|
const description = <div className='summary'>{item.description}</div>;
|
||||||
|
|
||||||
|
const moreInfo = !!item.moreInfoLink && <a href={item.moreInfoLink} onClick={this.onMoreInfoClick}>More info</a>;
|
||||||
|
const onClickInstall = () => install(item);
|
||||||
|
const installButton = item.installable &&
|
||||||
|
<button className='install' onClick={onClickInstall}>INSTALL</button>;
|
||||||
|
|
||||||
|
const onSelectChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
const version = event.target.value;
|
||||||
|
if (version) {
|
||||||
|
onVersionChange(version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const versions = (() => {
|
||||||
|
const { availableVersions } = item;
|
||||||
|
if (availableVersions.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
} else if (availableVersions.length === 1) {
|
||||||
|
return <label>{availableVersions[0]}</label>
|
||||||
|
} else {
|
||||||
|
return <select
|
||||||
|
value={input.selectedVersion}
|
||||||
|
onChange={onSelectChange}>
|
||||||
|
{
|
||||||
|
item.availableVersions
|
||||||
|
.filter(version => version !== item.installedVersion) // Filter the version that is currently installed.
|
||||||
|
.map(version => <option value={version} key={version}>{version}</option>)
|
||||||
|
}
|
||||||
|
</select>;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return <div className='component-list-item noselect'>
|
||||||
|
<div className='header'>
|
||||||
|
{nameAndAuthor}
|
||||||
|
{installedVersion}
|
||||||
|
</div>
|
||||||
|
<div className='content'>
|
||||||
|
{summary}
|
||||||
|
{description}
|
||||||
|
</div>
|
||||||
|
<div className='info'>
|
||||||
|
{moreInfo}
|
||||||
|
</div>
|
||||||
|
<div className='footer'>
|
||||||
|
{installButton}
|
||||||
|
{versions}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { injectable } from 'inversify';
|
||||||
|
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||||
|
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
|
||||||
|
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||||
|
import { ListWidget } from './list-widget';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export abstract class ListWidgetFrontendContribution<T extends ArduinoComponent> extends AbstractViewContribution<ListWidget<T>> implements FrontendApplicationContribution {
|
||||||
|
|
||||||
|
async initializeLayout(): Promise<void> {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { injectable, postConstruct } from 'inversify';
|
||||||
|
import { Message } from '@phosphor/messaging';
|
||||||
|
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||||
|
import { Emitter } from '@theia/core/lib/common/event';
|
||||||
|
import { MaybePromise } from '@theia/core/lib/common/types';
|
||||||
|
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
|
||||||
|
import { Installable } from '../../../common/protocol/installable';
|
||||||
|
import { Searchable } from '../../../common/protocol/searchable';
|
||||||
|
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||||
|
import { FilterableListContainer } from './filterable-list-container';
|
||||||
|
import { ListItemRenderer } from './list-item-renderer';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export abstract class ListWidget<T extends ArduinoComponent> extends ReactWidget {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do not touch or use it. It is for setting the focus on the `input` after the widget activation.
|
||||||
|
*/
|
||||||
|
protected focusNode: HTMLElement | undefined;
|
||||||
|
protected readonly deferredContainer = new Deferred<HTMLElement>();
|
||||||
|
protected readonly filterTextChangeEmitter = new Emitter<string>();
|
||||||
|
|
||||||
|
constructor(protected options: ListWidget.Options<T>) {
|
||||||
|
super();
|
||||||
|
const { id, label, iconClass } = options;
|
||||||
|
this.id = id;
|
||||||
|
this.title.label = label;
|
||||||
|
this.title.caption = label;
|
||||||
|
this.title.iconClass = iconClass
|
||||||
|
this.title.closable = true;
|
||||||
|
this.addClass('arduino-list-widget');
|
||||||
|
this.node.tabIndex = 0; // To be able to set the focus on the widget.
|
||||||
|
this.scrollOptions = {
|
||||||
|
suppressScrollX: true
|
||||||
|
}
|
||||||
|
this.toDispose.push(this.filterTextChangeEmitter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@postConstruct()
|
||||||
|
protected init(): void {
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getScrollContainer(): MaybePromise<HTMLElement> {
|
||||||
|
return this.deferredContainer.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onActivateRequest(msg: Message): void {
|
||||||
|
super.onActivateRequest(msg);
|
||||||
|
(this.focusNode || this.node).focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onUpdateRequest(msg: Message): void {
|
||||||
|
super.onUpdateRequest(msg);
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onFocusResolved = (element: HTMLElement | undefined) => {
|
||||||
|
this.focusNode = element;
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): React.ReactNode {
|
||||||
|
return <FilterableListContainer<T>
|
||||||
|
container={this}
|
||||||
|
resolveContainer={this.deferredContainer.resolve}
|
||||||
|
resolveFocus={this.onFocusResolved}
|
||||||
|
searchable={this.options.searchable}
|
||||||
|
installable={this.options.installable}
|
||||||
|
itemLabel={this.options.itemLabel}
|
||||||
|
itemRenderer={this.options.itemRenderer}
|
||||||
|
filterTextChangeEvent={this.filterTextChangeEmitter.event}/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh(filterText: string): void {
|
||||||
|
this.deferredContainer.promise.then(() => this.filterTextChangeEmitter.fire(filterText));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateScrollBar(): void {
|
||||||
|
if (this.scrollBar) {
|
||||||
|
this.scrollBar.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace ListWidget {
|
||||||
|
export interface Options<T extends ArduinoComponent> {
|
||||||
|
readonly id: string;
|
||||||
|
readonly label: string;
|
||||||
|
readonly iconClass: string;
|
||||||
|
readonly installable: Installable<T>;
|
||||||
|
readonly searchable: Searchable<T>;
|
||||||
|
readonly itemLabel: (item: T) => string;
|
||||||
|
readonly itemRenderer: ListItemRenderer<T>;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { BoardsService, Board } from '../../common/protocol/boards-service';
|
|
||||||
// import { SelectBoardDialog } from './select-board-dialog';
|
|
||||||
import { QuickPickService } from '@theia/core/lib/common/quick-pick-service';
|
|
||||||
import { BoardsNotificationService } from '../boards-notification-service';
|
|
||||||
import { ARDUINO_TOOLBAR_ITEM_CLASS } from '../toolbar/arduino-toolbar';
|
|
||||||
|
|
||||||
export class ConnectedBoards extends React.Component<ConnectedBoards.Props, ConnectedBoards.State> {
|
|
||||||
static TOOLBAR_ID: 'connected-boards-toolbar';
|
|
||||||
|
|
||||||
constructor(props: ConnectedBoards.Props) {
|
|
||||||
super(props);
|
|
||||||
this.state = { boardsLoading: false };
|
|
||||||
|
|
||||||
props.boardsNotificationService.on('boards-installed', () => this.onBoardsInstalled());
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): React.ReactNode {
|
|
||||||
let content = [];
|
|
||||||
if (!!this.state.boards && this.state.boards.length > 0) {
|
|
||||||
content = this.state.boards.map((b, i) => <option value={i} key={i}>{b.name}</option>);
|
|
||||||
} else {
|
|
||||||
let label;
|
|
||||||
if (this.state.boardsLoading) {
|
|
||||||
label = "Loading ...";
|
|
||||||
} else {
|
|
||||||
label = "No board attached";
|
|
||||||
}
|
|
||||||
content = [ <option key="loading" value="0">{label}</option> ];
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div key='arduino-connected-boards' className={`${ARDUINO_TOOLBAR_ITEM_CLASS} item ${ConnectedBoards.Styles.CONNECTED_BOARDS_CLASS}`}>
|
|
||||||
<select key='arduino-connected-boards-select' disabled={!this.state.boards}
|
|
||||||
onChange={this.onBoardSelect.bind(this)}
|
|
||||||
value={this.state.selection}>
|
|
||||||
<optgroup key='arduino-connected-boards-select-opt-group' label="Attached boards">
|
|
||||||
{ content }
|
|
||||||
</optgroup>
|
|
||||||
<optgroup label="_________" key='arduino-connected-boards-select-opt-group2'>
|
|
||||||
{ !!this.state.otherBoard && <option value="selected-other" key="selected-other">{this.state.otherBoard.name} (not attached)</option> }
|
|
||||||
<option value="select-other" key="select-other">Select other Board</option>
|
|
||||||
</optgroup>
|
|
||||||
</select>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount(): void {
|
|
||||||
this.reloadBoards();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onBoardsInstalled() {
|
|
||||||
if (!!this.findUnknownBoards()) {
|
|
||||||
this.reloadBoards();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected findUnknownBoards(): Board[] {
|
|
||||||
if (!this.state || !this.state.boards) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.state.boards.filter(b => !b.fqbn || b.name === "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async reloadBoards() {
|
|
||||||
const prevSelection = this.state.selection;
|
|
||||||
this.setState({ boardsLoading: true, boards: undefined, selection: "loading" });
|
|
||||||
const { boards } = await this.props.boardsService.getAttachedBoards()
|
|
||||||
this.setState({ boards, boardsLoading: false, selection: prevSelection });
|
|
||||||
|
|
||||||
if (boards) {
|
|
||||||
this.setState({ selection: "0" });
|
|
||||||
await this.props.boardsService.selectBoard(boards[0]);
|
|
||||||
|
|
||||||
const unknownBoards = this.findUnknownBoards();
|
|
||||||
if (unknownBoards && unknownBoards.length > 0) {
|
|
||||||
this.props.onUnknownBoard(unknownBoards[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async onBoardSelect(evt: React.ChangeEvent<HTMLSelectElement>) {
|
|
||||||
const selection = evt.target.value;
|
|
||||||
if (selection === "select-other" || selection === "selected-other") {
|
|
||||||
let selectedBoard = this.state.otherBoard;
|
|
||||||
if (selection === "select-other" || !selectedBoard) {
|
|
||||||
selectedBoard = await this.selectedInstalledBoard();
|
|
||||||
}
|
|
||||||
if (!selectedBoard) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.props.boardsService.selectBoard(selectedBoard);
|
|
||||||
this.setState({otherBoard: selectedBoard, selection: "selected-other"});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedBoard = (this.state.boards || [])[parseInt(selection, 10)];
|
|
||||||
if (!selectedBoard) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await this.props.boardsService.selectBoard(selectedBoard);
|
|
||||||
this.setState({selection});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async selectedInstalledBoard(): Promise<Board | undefined> {
|
|
||||||
const {items} = await this.props.boardsService.search({});
|
|
||||||
|
|
||||||
const idx = new Map<string, Board>();
|
|
||||||
items.filter(pkg => !!pkg.installedVersion).forEach(pkg => pkg.boards.forEach(brd => idx.set(`${brd.name}`, brd) ));
|
|
||||||
|
|
||||||
if (idx.size === 0) {
|
|
||||||
this.props.onNoBoardsInstalled();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selection = await this.props.quickPickService.show(Array.from(idx.keys()));
|
|
||||||
if (!selection) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return idx.get(selection);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace ConnectedBoards {
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
readonly boardsService: BoardsService;
|
|
||||||
readonly boardsNotificationService: BoardsNotificationService;
|
|
||||||
readonly quickPickService: QuickPickService;
|
|
||||||
readonly onNoBoardsInstalled: () => void;
|
|
||||||
readonly onUnknownBoard: (board: Board) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface State {
|
|
||||||
boardsLoading: boolean;
|
|
||||||
boards?: Board[];
|
|
||||||
otherBoard?: Board;
|
|
||||||
selection?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace Styles {
|
|
||||||
export const CONNECTED_BOARDS_CLASS = 'connected-boards';
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { AbstractDialog } from "@theia/core/lib/browser";
|
|
||||||
|
|
||||||
|
|
||||||
export class InstallationProgressDialog extends AbstractDialog<string> {
|
|
||||||
readonly value: "does-not-matter";
|
|
||||||
|
|
||||||
constructor(componentName: string) {
|
|
||||||
super({ title: 'Installation in progress' });
|
|
||||||
this.contentNode.textContent = `Installing ${componentName}. Please wait.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { AbstractDialog } from '@theia/core/lib/browser';
|
||||||
|
|
||||||
|
export class InstallationProgressDialog extends AbstractDialog<undefined> {
|
||||||
|
|
||||||
|
readonly value = undefined;
|
||||||
|
|
||||||
|
constructor(componentName: string, version: string) {
|
||||||
|
super({ title: 'Installation in progress' });
|
||||||
|
this.contentNode.textContent = `Installing ${componentName} [${version}]. Please wait...`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UninstallationProgressDialog extends AbstractDialog<undefined> {
|
||||||
|
|
||||||
|
readonly value = undefined;
|
||||||
|
|
||||||
|
constructor(componentName: string) {
|
||||||
|
super({ title: 'Uninstallation in progress' });
|
||||||
|
this.contentNode.textContent = `Uninstalling ${componentName}. Please wait...`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { injectable, inject, postConstruct } from 'inversify';
|
||||||
|
import { AboutDialog, ABOUT_CONTENT_CLASS } from '@theia/core/lib/browser/about-dialog';
|
||||||
|
import { ConfigService } from '../../common/protocol/config-service';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class ArduinoAboutDialog extends AboutDialog {
|
||||||
|
|
||||||
|
@inject(ConfigService)
|
||||||
|
protected readonly configService: ConfigService;
|
||||||
|
|
||||||
|
@postConstruct()
|
||||||
|
protected async init(): Promise<void> {
|
||||||
|
const [, version] = await Promise.all([super.init(), this.configService.getVersion()]);
|
||||||
|
if (version) {
|
||||||
|
const { firstChild } = this.contentNode;
|
||||||
|
if (firstChild instanceof HTMLElement && firstChild.classList.contains(ABOUT_CONTENT_CLASS)) {
|
||||||
|
const cliVersion = document.createElement('div');
|
||||||
|
cliVersion.textContent = version;
|
||||||
|
firstChild.appendChild(cliVersion);
|
||||||
|
// TODO: anchor to the commit in the `arduino-cli` repository.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { ApplicationShell, Widget, Saveable, FocusTracker, Message } from '@theia/core/lib/browser';
|
||||||
|
import { EditorWidget } from '@theia/editor/lib/browser';
|
||||||
|
import { injectable, inject } from 'inversify';
|
||||||
|
import { EditorMode } from '../editor-mode';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class ArduinoApplicationShell extends ApplicationShell {
|
||||||
|
|
||||||
|
@inject(EditorMode)
|
||||||
|
protected readonly editorMode: EditorMode;
|
||||||
|
|
||||||
|
protected refreshBottomPanelToggleButton() {
|
||||||
|
if (this.editorMode.proMode) {
|
||||||
|
super.refreshBottomPanelToggleButton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async track(widget: Widget): Promise<void> {
|
||||||
|
if (this.editorMode.proMode) {
|
||||||
|
super.track(widget);
|
||||||
|
} else {
|
||||||
|
const tracker = (this as any).tracker as FocusTracker<Widget>;
|
||||||
|
tracker.add(widget);
|
||||||
|
this.disableClose(Saveable.apply(widget));
|
||||||
|
if (ApplicationShell.TrackableWidgetProvider.is(widget)) {
|
||||||
|
for (const toTrack of await widget.getTrackableWidgets()) {
|
||||||
|
tracker.add(toTrack);
|
||||||
|
this.disableClose(Saveable.apply(toTrack));
|
||||||
|
}
|
||||||
|
if (widget.onDidChangeTrackableWidgets) {
|
||||||
|
widget.onDidChangeTrackableWidgets(widgets => widgets.forEach(w => this.track(w)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private disableClose(widget: Widget | undefined): void {
|
||||||
|
if (widget instanceof EditorWidget) {
|
||||||
|
const onCloseRequest = (_: Message) => {
|
||||||
|
// NOOP
|
||||||
|
};
|
||||||
|
(widget as any).onCloseRequest = onCloseRequest.bind(widget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,7 +2,8 @@ import {EditorContribution} from '@theia/editor/lib/browser/editor-contribution'
|
|||||||
import { TextEditor } from '@theia/editor/lib/browser';
|
import { TextEditor } from '@theia/editor/lib/browser';
|
||||||
import { StatusBarAlignment } from '@theia/core/lib/browser';
|
import { StatusBarAlignment } from '@theia/core/lib/browser';
|
||||||
|
|
||||||
export class CustomEditorContribution extends EditorContribution {
|
export class ArduinoEditorContribution extends EditorContribution {
|
||||||
|
|
||||||
protected updateLanguageStatus(editor: TextEditor | undefined): void {
|
protected updateLanguageStatus(editor: TextEditor | undefined): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,4 +19,5 @@ export class CustomEditorContribution extends EditorContribution {
|
|||||||
priority: 100
|
priority: 100
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { injectable, inject } from 'inversify';
|
||||||
|
import { FileSystem } from '@theia/filesystem/lib/common/filesystem';
|
||||||
|
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||||
|
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||||
|
import { EditorMode } from '../editor-mode';
|
||||||
|
import { ArduinoFrontendContribution } from '../arduino-frontend-contribution';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class ArduinoFrontendApplication extends FrontendApplication {
|
||||||
|
|
||||||
|
@inject(FileSystem)
|
||||||
|
protected readonly fileSystem: FileSystem;
|
||||||
|
|
||||||
|
@inject(WorkspaceService)
|
||||||
|
protected readonly workspaceService: WorkspaceService;
|
||||||
|
|
||||||
|
@inject(ArduinoFrontendContribution)
|
||||||
|
protected readonly frontendContribution: ArduinoFrontendContribution;
|
||||||
|
|
||||||
|
@inject(EditorMode)
|
||||||
|
protected readonly editorMode: EditorMode;
|
||||||
|
|
||||||
|
protected async initializeLayout(): Promise<void> {
|
||||||
|
return super.initializeLayout().then(() => {
|
||||||
|
// If not in PRO mode, we open the sketch file with all the related files.
|
||||||
|
// Otherwise, we reuse the workbench's restore functionality and we do not open anything at all.
|
||||||
|
// TODO: check `otherwise`. Also, what if we check for opened editors, instead of blindly opening them?
|
||||||
|
if (!this.editorMode.proMode) {
|
||||||
|
this.workspaceService.roots.then(roots => {
|
||||||
|
for (const root of roots) {
|
||||||
|
this.fileSystem.exists(root.uri).then(exists => {
|
||||||
|
if (exists) {
|
||||||
|
this.frontendContribution.openSketchFiles(root.uri);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { MonacoStatusBarContribution } from '@theia/monaco/lib/browser/monaco-status-bar-contribution';
|
||||||
|
|
||||||
|
export class ArduinoMonacoStatusBarContribution extends MonacoStatusBarContribution {
|
||||||
|
|
||||||
|
protected setConfigTabSizeWidget() {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected setLineEndingWidget() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { injectable, inject } from 'inversify';
|
||||||
|
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||||
|
import { FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
|
||||||
|
import { EditorMode } from '../editor-mode';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class ArduinoNavigatorContribution extends FileNavigatorContribution {
|
||||||
|
|
||||||
|
@inject(EditorMode)
|
||||||
|
protected readonly editorMode: EditorMode;
|
||||||
|
|
||||||
|
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||||
|
if (this.editorMode.proMode) {
|
||||||
|
return super.initializeLayout(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { injectable, inject } from 'inversify';
|
||||||
|
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||||
|
import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
|
||||||
|
import { EditorMode } from '../editor-mode';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class ArduinoOutlineViewContribution extends OutlineViewContribution {
|
||||||
|
|
||||||
|
@inject(EditorMode)
|
||||||
|
protected readonly editorMode: EditorMode;
|
||||||
|
|
||||||
|
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||||
|
if (this.editorMode.proMode) {
|
||||||
|
return super.initializeLayout(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { inject, injectable } from 'inversify';
|
||||||
|
import { TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||||
|
import { OutputToolbarContribution } from '@theia/output/lib/browser/output-toolbar-contribution';
|
||||||
|
import { EditorMode } from '../editor-mode';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class ArduinoOutputToolContribution extends OutputToolbarContribution {
|
||||||
|
|
||||||
|
@inject(EditorMode)
|
||||||
|
protected readonly editorMode: EditorMode;
|
||||||
|
|
||||||
|
async registerToolbarItems(toolbarRegistry: TabBarToolbarRegistry): Promise<void> {
|
||||||
|
if (this.editorMode.proMode) {
|
||||||
|
super.registerToolbarItems(toolbarRegistry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { inject, injectable } from 'inversify';
|
||||||
|
import { KeybindingRegistry } from '@theia/core/lib/browser';
|
||||||
|
import { ProblemStat } from '@theia/markers/lib/browser/problem/problem-manager';
|
||||||
|
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||||
|
import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
|
||||||
|
import { EditorMode } from '../editor-mode';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class ArduinoProblemContribution extends ProblemContribution {
|
||||||
|
|
||||||
|
@inject(EditorMode)
|
||||||
|
protected readonly editorMode: EditorMode;
|
||||||
|
|
||||||
|
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||||
|
if (this.editorMode.proMode) {
|
||||||
|
return super.initializeLayout(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected setStatusBarElement(problemStat: ProblemStat): void {
|
||||||
|
if (this.editorMode.proMode) {
|
||||||
|
super.setStatusBarElement(problemStat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerKeybindings(keybindings: KeybindingRegistry): void {
|
||||||
|
if (this.toggleCommand) {
|
||||||
|
keybindings.registerKeybinding({
|
||||||
|
command: this.toggleCommand.id,
|
||||||
|
keybinding: 'ctrlcmd+alt+shift+m'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { inject, injectable } from 'inversify';
|
||||||
|
import { ScmContribution } from '@theia/scm/lib/browser/scm-contribution';
|
||||||
|
import { StatusBarEntry } from '@theia/core/lib/browser/status-bar/status-bar';
|
||||||
|
import { EditorMode } from '../editor-mode';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class ArduinoScmContribution extends ScmContribution {
|
||||||
|
|
||||||
|
@inject(EditorMode)
|
||||||
|
protected readonly editorMode: EditorMode;
|
||||||
|
|
||||||
|
async initializeLayout(): Promise<void> {
|
||||||
|
if (this.editorMode.proMode) {
|
||||||
|
return super.initializeLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected setStatusBarEntry(id: string, entry: StatusBarEntry): void {
|
||||||
|
if (this.editorMode.proMode) {
|
||||||
|
super.setStatusBarEntry(id, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { inject, injectable } from 'inversify';
|
||||||
|
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||||
|
import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
|
||||||
|
import { EditorMode } from '../editor-mode';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class ArduinoSearchInWorkspaceContribution extends SearchInWorkspaceFrontendContribution {
|
||||||
|
|
||||||
|
@inject(EditorMode)
|
||||||
|
protected readonly editorMode: EditorMode;
|
||||||
|
|
||||||
|
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||||
|
if (this.editorMode.proMode) {
|
||||||
|
return super.initializeLayout(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { ApplicationShell } from "@theia/core/lib/browser";
|
|
||||||
|
|
||||||
export class CustomApplicationShell extends ApplicationShell {
|
|
||||||
protected refreshBottomPanelToggleButton() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import { injectable } from "inversify";
|
|
||||||
import { CommonFrontendContribution, CommonMenus, CommonCommands } from "@theia/core/lib/browser";
|
|
||||||
import { MenuModelRegistry } from "@theia/core";
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class CustomCommonFrontendContribution extends CommonFrontendContribution {
|
|
||||||
registerMenus(registry: MenuModelRegistry): void {
|
|
||||||
registry.registerSubmenu(CommonMenus.FILE, 'File');
|
|
||||||
registry.registerSubmenu(CommonMenus.EDIT, 'Edit');
|
|
||||||
|
|
||||||
registry.registerSubmenu(CommonMenus.FILE_SETTINGS_SUBMENU, 'Settings');
|
|
||||||
|
|
||||||
registry.registerMenuAction(CommonMenus.EDIT_UNDO, {
|
|
||||||
commandId: CommonCommands.UNDO.id,
|
|
||||||
order: '0'
|
|
||||||
});
|
|
||||||
registry.registerMenuAction(CommonMenus.EDIT_UNDO, {
|
|
||||||
commandId: CommonCommands.REDO.id,
|
|
||||||
order: '1'
|
|
||||||
});
|
|
||||||
|
|
||||||
registry.registerMenuAction(CommonMenus.EDIT_FIND, {
|
|
||||||
commandId: CommonCommands.FIND.id,
|
|
||||||
order: '0'
|
|
||||||
});
|
|
||||||
registry.registerMenuAction(CommonMenus.EDIT_FIND, {
|
|
||||||
commandId: CommonCommands.REPLACE.id,
|
|
||||||
order: '1'
|
|
||||||
});
|
|
||||||
|
|
||||||
registry.registerMenuAction(CommonMenus.EDIT_CLIPBOARD, {
|
|
||||||
commandId: CommonCommands.CUT.id,
|
|
||||||
order: '0'
|
|
||||||
});
|
|
||||||
registry.registerMenuAction(CommonMenus.EDIT_CLIPBOARD, {
|
|
||||||
commandId: CommonCommands.COPY.id,
|
|
||||||
order: '1'
|
|
||||||
});
|
|
||||||
registry.registerMenuAction(CommonMenus.EDIT_CLIPBOARD, {
|
|
||||||
commandId: CommonCommands.PASTE.id,
|
|
||||||
order: '2'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { injectable } from "inversify";
|
|
||||||
import { EditorWidgetFactory } from "@theia/editor/lib/browser/editor-widget-factory";
|
|
||||||
import URI from "@theia/core/lib/common/uri";
|
|
||||||
import { EditorWidget } from "@theia/editor/lib/browser";
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class CustomEditorWidgetFactory extends EditorWidgetFactory {
|
|
||||||
|
|
||||||
protected async createEditor(uri: URI): Promise<EditorWidget> {
|
|
||||||
const icon = await this.labelProvider.getIcon(uri);
|
|
||||||
return this.editorProvider(uri).then(textEditor => {
|
|
||||||
const newEditor = new EditorWidget(textEditor, this.selectionService);
|
|
||||||
newEditor.id = this.id + ':' + uri.toString();
|
|
||||||
newEditor.title.closable = false;
|
|
||||||
newEditor.title.label = this.labelProvider.getName(uri);
|
|
||||||
newEditor.title.iconClass = icon + ' file-icon';
|
|
||||||
newEditor.title.caption = this.labelProvider.getLongName(uri);
|
|
||||||
return newEditor;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { injectable } from "inversify";
|
|
||||||
import { FileMenuContribution } from "@theia/workspace/lib/browser";
|
|
||||||
import { MenuModelRegistry } from "@theia/core";
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class CustomFileMenuContribution extends FileMenuContribution {
|
|
||||||
registerMenus(registry: MenuModelRegistry) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { injectable, inject } from "inversify";
|
|
||||||
import { FrontendApplication } from "@theia/core/lib/browser";
|
|
||||||
import { ArduinoFrontendContribution } from "../arduino-frontend-contribution";
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class CustomFrontendApplication extends FrontendApplication {
|
|
||||||
|
|
||||||
@inject(ArduinoFrontendContribution)
|
|
||||||
protected readonly frontendContribution: ArduinoFrontendContribution;
|
|
||||||
|
|
||||||
protected async initializeLayout(): Promise<void> {
|
|
||||||
const location = new URL(window.location.href);
|
|
||||||
const sketchPath = location.searchParams.get('sketch');
|
|
||||||
if (sketchPath) {
|
|
||||||
this.frontendContribution.openSketchFiles(decodeURIComponent(sketchPath));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { injectable } from "inversify";
|
|
||||||
import { MonacoEditorMenuContribution } from "@theia/monaco/lib/browser/monaco-menu";
|
|
||||||
import { MenuModelRegistry } from "@theia/core";
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class CustomMonacoEditorMenuContribution extends MonacoEditorMenuContribution {
|
|
||||||
registerMenus(registry: MenuModelRegistry) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import {MonacoStatusBarContribution} from '@theia/monaco/lib/browser/monaco-status-bar-contribution';
|
|
||||||
|
|
||||||
export class SilentMonacoStatusBarContribution extends MonacoStatusBarContribution {
|
|
||||||
protected setConfigTabSizeWidget() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected setLineEndingWidget() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { injectable } from "inversify";
|
|
||||||
import { FileNavigatorContribution } from "@theia/navigator/lib/browser/navigator-contribution";
|
|
||||||
import { FrontendApplication } from "@theia/core/lib/browser";
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class SilentNavigatorContribution extends FileNavigatorContribution {
|
|
||||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
|
||||||
// await this.openView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
/********************************************************************************
|
|
||||||
* Copyright (C) 2017 TypeFox and others.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0.
|
|
||||||
*
|
|
||||||
* This Source Code may also be made available under the following Secondary
|
|
||||||
* Licenses when the conditions for such availability set forth in the Eclipse
|
|
||||||
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
||||||
* with the GNU Classpath Exception which is available at
|
|
||||||
* https://www.gnu.org/software/classpath/license.html.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
||||||
********************************************************************************/
|
|
||||||
|
|
||||||
import { injectable } from 'inversify';
|
|
||||||
import { OutlineViewContribution } from '@theia/outline-view/lib/browser/outline-view-contribution';
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class SilentOutlineViewContribution extends OutlineViewContribution {
|
|
||||||
|
|
||||||
async initializeLayout(): Promise<void> {
|
|
||||||
// await this.openView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { OutputToolbarContribution } from "@theia/output/lib/browser/output-toolbar-contribution";
|
|
||||||
import { TabBarToolbarRegistry } from "@theia/core/lib/browser/shell/tab-bar-toolbar";
|
|
||||||
import { injectable } from "inversify";
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class ArduinoOutputToolContribution extends OutputToolbarContribution {
|
|
||||||
async registerToolbarItems(toolbarRegistry: TabBarToolbarRegistry): Promise<void> {
|
|
||||||
// register nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { injectable } from 'inversify';
|
|
||||||
import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
|
|
||||||
import { ProblemStat } from '@theia/markers/lib/browser/problem/problem-manager';
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class SilentProblemContribution extends ProblemContribution {
|
|
||||||
|
|
||||||
async initializeLayout(): Promise<void> {
|
|
||||||
// await this.openView();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected setStatusBarElement(problemStat: ProblemStat) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
48
arduino-ide-extension/src/browser/editor-mode.ts
Normal file
48
arduino-ide-extension/src/browser/editor-mode.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { injectable } from 'inversify';
|
||||||
|
import { ApplicationShell, FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser';
|
||||||
|
import { ArduinoShellLayoutRestorer } from './shell/arduino-shell-layout-restorer';
|
||||||
|
import { OutputWidget } from '@theia/output/lib/browser/output-widget';
|
||||||
|
import { EditorWidget } from '@theia/editor/lib/browser';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class EditorMode implements FrontendApplicationContribution {
|
||||||
|
|
||||||
|
protected app: FrontendApplication;
|
||||||
|
|
||||||
|
onStart(app: FrontendApplication): void {
|
||||||
|
this.app = app;
|
||||||
|
if (this.proMode) {
|
||||||
|
// We use this CSS class on the body to modify the visibility of the close button for the editors and views.
|
||||||
|
document.body.classList.add(EditorMode.PRO_MODE_KEY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get proMode(): boolean {
|
||||||
|
const value = window.localStorage.getItem(EditorMode.PRO_MODE_KEY);
|
||||||
|
return value === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
async toggle(): Promise<void> {
|
||||||
|
const oldState = this.proMode;
|
||||||
|
const inAdvancedMode = !oldState;
|
||||||
|
window.localStorage.setItem(EditorMode.PRO_MODE_KEY, String(inAdvancedMode));
|
||||||
|
if (!inAdvancedMode) {
|
||||||
|
const { shell } = this.app;
|
||||||
|
// Close all widget that is neither editor nor `Output`.
|
||||||
|
for (const area of ['left', 'right', 'bottom', 'main'] as Array<ApplicationShell.Area>) {
|
||||||
|
shell.closeTabs(area, ({ owner }) => !(owner instanceof EditorWidget || owner instanceof OutputWidget));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// `storeLayout` has a sync API but the implementation is async, we store the layout manually before we reload the page.
|
||||||
|
// See: https://github.com/eclipse-theia/theia/issues/6579
|
||||||
|
// XXX: hack instead of injecting the `ArduinoShellLayoutRestorer` we have to retrieve it from the application to avoid DI cycle.
|
||||||
|
const layoutRestorer = (this.app as any).layoutRestorer as ArduinoShellLayoutRestorer
|
||||||
|
await layoutRestorer.storeLayoutAsync(this.app);
|
||||||
|
window.location.reload(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace EditorMode {
|
||||||
|
export const PRO_MODE_KEY = 'arduino-advanced-mode';
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { inject, injectable } from 'inversify';
|
||||||
|
import URI from '@theia/core/lib/common/uri';
|
||||||
|
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||||
|
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
||||||
|
import { MonacoEditorModel } from '@theia/monaco/lib/browser/monaco-editor-model';
|
||||||
|
import { MonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider';
|
||||||
|
import { ConfigService } from '../../common/protocol/config-service';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class ArduinoMonacoEditorProvider extends MonacoEditorProvider {
|
||||||
|
|
||||||
|
@inject(ConfigService)
|
||||||
|
protected readonly configService: ConfigService;
|
||||||
|
protected dataDirUri: string | undefined;
|
||||||
|
|
||||||
|
protected async getModel(uri: URI, toDispose: DisposableCollection): Promise<MonacoEditorModel> {
|
||||||
|
// `createMonacoEditorOptions` is not `async` so we ask the `dataDirUri` here.
|
||||||
|
// https://github.com/eclipse-theia/theia/issues/6234
|
||||||
|
const { dataDirUri } = await this.configService.getConfiguration()
|
||||||
|
this.dataDirUri = dataDirUri;
|
||||||
|
return super.getModel(uri, toDispose);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createMonacoEditorOptions(model: MonacoEditorModel): MonacoEditor.IOptions {
|
||||||
|
const options = this.createOptions(this.preferencePrefixes, model.uri, model.languageId);
|
||||||
|
options.model = model.textEditorModel;
|
||||||
|
options.readOnly = model.readOnly;
|
||||||
|
if (this.dataDirUri) {
|
||||||
|
options.readOnly = new URI(this.dataDirUri).isEqualOrParent(new URI(model.uri));
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 962 B After Width: | Height: | Size: 962 B |
70
arduino-ide-extension/src/browser/icons/mask-buttons.svg
Normal file
70
arduino-ide-extension/src/browser/icons/mask-buttons.svg
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!--Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)-->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg" id="Layer_1" width="198px" height="99px" x="0px" y="0px" enable-background="new 0 0 198 99" version="1.1" viewBox="0 0 198 99" inkscape:version="0.91 r13725" sodipodi:docname="buttons.svg" xml:space="preserve">
|
||||||
|
<metadata id="metadata327">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work rdf:about="">
|
||||||
|
<dc:format>
|
||||||
|
image/svg+xml
|
||||||
|
</dc:format>
|
||||||
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||||
|
<dc:title/>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<defs id="defs325"/>
|
||||||
|
<sodipodi:namedview id="namedview323" bordercolor="#666666" borderopacity="1" gridtolerance="10" guidetolerance="10" objecttolerance="10" pagecolor="#ffffff" showgrid="false" showguides="true" inkscape:current-layer="Layer_1" inkscape:cx="45.252385" inkscape:cy="36.224987" inkscape:guide-bbox="true" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-height="1000" inkscape:window-maximized="1" inkscape:window-width="1215" inkscape:window-x="65" inkscape:window-y="24" inkscape:zoom="4"/>
|
||||||
|
<g id="g5" transform="translate(-0.12000084,0)">
|
||||||
|
<polyline id="polyline9" stroke-miterlimit="10" points="21.453,12.745 15.788,20.571 11.775,16.658 " style="fill:none;stroke:#000000;stroke-width:2.30489993;stroke-miterlimit:10"/>
|
||||||
|
</g>
|
||||||
|
<g id="g23" transform="translate(-0.26100159,0)">
|
||||||
|
<polygon id="polygon27" points="45.412,15.313 49.307,15.313 49.307,11.53 54.701,16.875 49.331,22.245 49.331,18.563 45.412,18.539 " style="fill:#000000"/>
|
||||||
|
</g>
|
||||||
|
<g id="g41" transform="translate(-0.54399872,0)">
|
||||||
|
<polygon id="polygon45" points="114.44,19.083 114.44,15.116 110.586,15.116 116.032,9.621 121.502,15.091 117.751,15.091 117.726,19.083 " style="fill:#000000"/>
|
||||||
|
<rect id="rect47" width="1" height="1" x="110.511" y="22.193001" style="fill:#000000"/>
|
||||||
|
<rect id="rect49" width="1" height="1" x="112.518" y="22.193001" style="fill:#000000"/>
|
||||||
|
<rect id="rect51" width="1" height="1" x="114.517" y="22.193001" style="fill:#000000"/>
|
||||||
|
<rect id="rect53" width="1" height="1" x="116.525" y="22.193001" style="fill:#000000"/>
|
||||||
|
<rect id="rect55" width="1" height="1" x="118.524" y="22.193001" style="fill:#000000"/>
|
||||||
|
<rect id="rect57" width="1" height="1" x="120.531" y="22.193001" style="fill:#000000"/>
|
||||||
|
</g>
|
||||||
|
<g id="g59" transform="translate(-0.68600464,0)">
|
||||||
|
<polygon id="polygon63" points="150.79,9.621 150.79,13.588 154.644,13.588 149.198,19.083 143.728,13.612 147.479,13.613 147.504,9.621 " style="fill:#000000"/>
|
||||||
|
<rect id="rect65" width="1" height="1" x="143.65199" y="22.193001" style="fill:#000000"/>
|
||||||
|
<rect id="rect67" width="1" height="1" x="145.66" y="22.193001" style="fill:#000000"/>
|
||||||
|
<rect id="rect69" width="1" height="1" x="147.659" y="22.193001" style="fill:#000000"/>
|
||||||
|
<rect id="rect71" width="1" height="1" x="149.666" y="22.193001" style="fill:#000000"/>
|
||||||
|
<rect id="rect73" width="1" height="1" x="151.666" y="22.193001" style="fill:#000000"/>
|
||||||
|
<rect id="rect75" width="1" height="1" x="153.673" y="22.193001" style="fill:#000000"/>
|
||||||
|
</g>
|
||||||
|
<g id="g149" transform="translate(-0.40299988,0)">
|
||||||
|
<path id="path151" d="M 87.445,22.097" style="fill:#000000" inkscape:connector-curvature="0"/>
|
||||||
|
<polygon id="polygon155" points="83.44,10.094 84.441,10.094 88.421,14.079 88.421,15.057 87.445,15.057 83.44,15.057 " style="fill:#000000"/>
|
||||||
|
<polygon id="polygon157" points="78.404,11.093 78.404,22.097 87.445,22.097 87.445,14.87 88.421,14.87 88.421,23.134 77.399,23.134 77.399,10.094 83.562,10.094 83.568,11.093 " style="fill:#000000"/>
|
||||||
|
<rect id="rect159" width="0.995" height="0.99599999" x="79.399002" y="12.111" style="fill:#000000"/>
|
||||||
|
<rect id="rect161" width="0.995" height="0.99599999" x="81.394997" y="12.111" style="fill:#000000"/>
|
||||||
|
<rect id="rect163" width="0.995" height="0.99599999" x="79.399002" y="14.103" style="fill:#000000"/>
|
||||||
|
<rect id="rect165" width="0.995" height="0.99599999" x="81.394997" y="14.103" style="fill:#000000"/>
|
||||||
|
<rect id="rect167" width="0.995" height="0.99599999" x="79.399002" y="16.115999" style="fill:#000000"/>
|
||||||
|
<rect id="rect169" width="0.995" height="0.99599999" x="81.394997" y="16.115999" style="fill:#000000"/>
|
||||||
|
<rect id="rect171" width="0.995" height="0.99599999" x="83.403" y="16.115999" style="fill:#000000"/>
|
||||||
|
<rect id="rect173" width="0.995" height="0.99599999" x="85.400002" y="16.115999" style="fill:#000000"/>
|
||||||
|
<rect id="rect175" width="0.995" height="0.99599999" x="79.399002" y="18.118" style="fill:#000000"/>
|
||||||
|
<rect id="rect177" width="0.995" height="0.99599999" x="81.394997" y="18.118" style="fill:#000000"/>
|
||||||
|
<rect id="rect179" width="0.995" height="0.99599999" x="79.399002" y="20.132" style="fill:#000000"/>
|
||||||
|
<rect id="rect181" width="0.995" height="0.99599999" x="81.394997" y="20.132" style="fill:#000000"/>
|
||||||
|
<rect id="rect183" width="0.995" height="0.99599999" x="83.403" y="18.118" style="fill:#000000"/>
|
||||||
|
<rect id="rect185" width="0.995" height="0.99599999" x="85.400002" y="18.118" style="fill:#000000"/>
|
||||||
|
<rect id="rect187" width="0.995" height="0.99599999" x="83.403" y="20.132" style="fill:#000000"/>
|
||||||
|
<rect id="rect189" width="0.995" height="0.99599999" x="85.400002" y="20.132" style="fill:#000000"/>
|
||||||
|
</g>
|
||||||
|
<g id="g275" transform="translate(-0.82800293,0)">
|
||||||
|
<rect id="rect279" width="0.99900001" height="0.99800003" x="187.819" y="16.101" style="fill:#000000"/>
|
||||||
|
<rect id="rect281" width="0.99900001" height="0.99800003" x="189.825" y="16.101" style="fill:#000000"/>
|
||||||
|
<rect id="rect283" width="0.99900001" height="0.99800003" x="174.83299" y="16.101" style="fill:#000000"/>
|
||||||
|
<circle id="circle285" cx="181.80299" cy="16.101" r="4.0900002" stroke-miterlimit="10" style="fill:none;stroke:#000000;stroke-width:1.95439994;stroke-miterlimit:10"/>
|
||||||
|
<rect id="rect287" width="2.6800001" height="4.4229999" x="175.895" y="18.427999" style="fill:#000000" transform="matrix(0.6915,0.7224,-0.7224,0.6915,69.5827,-121.6599)"/>
|
||||||
|
<rect id="rect289" width="2.0280001" height="1.9960001" x="180.789" y="15.103" style="fill:#000000"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 6.4 KiB |
@@ -0,0 +1,53 @@
|
|||||||
|
import { injectable, inject, postConstruct } from 'inversify';
|
||||||
|
import { BaseLanguageClientContribution } from '@theia/languages/lib/browser';
|
||||||
|
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
|
||||||
|
import { BoardsConfig } from '../boards/boards-config';
|
||||||
|
import { Board, BoardPackage } from '../../common/protocol/boards-service';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class ArduinoLanguageClientContribution extends BaseLanguageClientContribution {
|
||||||
|
|
||||||
|
readonly id = 'ino';
|
||||||
|
readonly name = 'Arduino';
|
||||||
|
|
||||||
|
protected get documentSelector(): string[] {
|
||||||
|
return ['ino'];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get globPatterns() {
|
||||||
|
return ['**/*.ino'];
|
||||||
|
}
|
||||||
|
|
||||||
|
@inject(BoardsServiceClientImpl)
|
||||||
|
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
||||||
|
|
||||||
|
protected boardConfig?: BoardsConfig.Config;
|
||||||
|
|
||||||
|
@postConstruct()
|
||||||
|
protected init() {
|
||||||
|
this.boardsServiceClient.onBoardsConfigChanged(this.selectBoard.bind(this));
|
||||||
|
const restartIfAffected = (pkg: BoardPackage) => {
|
||||||
|
if (!this.boardConfig) {
|
||||||
|
this.restart();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { selectedBoard } = this.boardConfig;
|
||||||
|
if (selectedBoard && pkg.boards.some(board => Board.sameAs(board, selectedBoard))) {
|
||||||
|
this.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.boardsServiceClient.onBoardInstalled(({ pkg }) => restartIfAffected(pkg));
|
||||||
|
this.boardsServiceClient.onBoardUninstalled(({ pkg }) => restartIfAffected(pkg));
|
||||||
|
}
|
||||||
|
|
||||||
|
selectBoard(config: BoardsConfig.Config): void {
|
||||||
|
this.boardConfig = config;
|
||||||
|
// Force a restart to send the new board config to the language server
|
||||||
|
this.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getStartParameters(): BoardsConfig.Config | undefined {
|
||||||
|
return this.boardConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { ComponentListItem } from '../components/component-list/component-list-item';
|
|
||||||
|
|
||||||
export class LibraryComponentListItem extends ComponentListItem {
|
|
||||||
|
|
||||||
render(): React.ReactNode {
|
|
||||||
const { item } = this.props;
|
|
||||||
|
|
||||||
const name = <span className={'name'}>{item.name}</span>;
|
|
||||||
const author = <span className={'author'}>by {item.author}</span>;
|
|
||||||
const installedVersion = !!item.installedVersion && <div className={'version-info'}>
|
|
||||||
<span className={'version'}>Version {item.installedVersion}</span>
|
|
||||||
<span className={'installed'}>INSTALLED</span>
|
|
||||||
</div>;
|
|
||||||
|
|
||||||
const summary = <div className={'summary'}>{item.summary}</div>;
|
|
||||||
|
|
||||||
const moreInfo = !!item.moreInfoLink && <a href={item.moreInfoLink} onClick={this.onClick}>More info</a>;
|
|
||||||
const install = this.props.install && item.installable && !item.installedVersion &&
|
|
||||||
<button className={'install'} onClick={this.install.bind(this, item)}>INSTALL</button>;
|
|
||||||
const versions = (() => {
|
|
||||||
const { availableVersions } = item;
|
|
||||||
if (availableVersions.length === 0) {
|
|
||||||
return undefined;
|
|
||||||
} else if (availableVersions.length === 1) {
|
|
||||||
return <label>{availableVersions[0]}</label>
|
|
||||||
} else {
|
|
||||||
return <select>{item.availableVersions.map(version => <option value={version} key={version}>{version}</option>)}</select>;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
return <div className={'component-list-item noselect'}>
|
|
||||||
<div className={'header'}>
|
|
||||||
<span>{name} {author}</span>
|
|
||||||
{installedVersion}
|
|
||||||
</div>
|
|
||||||
<div className={'content'}>
|
|
||||||
{summary}
|
|
||||||
</div>
|
|
||||||
<div className={'info'}>
|
|
||||||
{moreInfo}
|
|
||||||
</div>
|
|
||||||
<div className={'footer'}>
|
|
||||||
{install}
|
|
||||||
{versions}
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { ArduinoComponent } from '../../common/protocol/arduino-component';
|
|
||||||
import { ComponentList } from '../components/component-list/component-list';
|
|
||||||
import { LibraryComponentListItem } from './library-component-list-item';
|
|
||||||
|
|
||||||
export class LibraryComponentList extends ComponentList {
|
|
||||||
|
|
||||||
createItem(item: ArduinoComponent): React.ReactNode {
|
|
||||||
return <LibraryComponentListItem
|
|
||||||
key={item.name}
|
|
||||||
item={item}
|
|
||||||
windowService={this.props.windowService}
|
|
||||||
install={this.props.install}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { FilterableListContainer } from '../components/component-list/filterable-list-container';
|
|
||||||
import { LibraryComponentList } from './library-component-list';
|
|
||||||
|
|
||||||
export class LibraryFilterableListContainer extends FilterableListContainer {
|
|
||||||
|
|
||||||
constructor(props: Readonly<FilterableListContainer.Props>) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
filterText: '',
|
|
||||||
items: [],
|
|
||||||
props: {
|
|
||||||
topic: this.topics[0],
|
|
||||||
type: this.types[0]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected renderSearchFilter(): React.ReactNode {
|
|
||||||
const types = this.types.map(type => <option value={type} key={type}>{type}</option>);
|
|
||||||
let type = this.types[0];
|
|
||||||
if (this.state.props) {
|
|
||||||
const currentType = this.types.find(t => t === this.state.props!.type) || this.types[0];
|
|
||||||
if (currentType) {
|
|
||||||
type = currentType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const topics = this.topics.map(topic => <option value={topic} key={topic}>{topic}</option>);
|
|
||||||
let topic = this.topics[0];
|
|
||||||
if (this.state.props) {
|
|
||||||
const currentTopic = this.topics.find(t => t === this.state.props!.topic) || this.topics[0];
|
|
||||||
if (currentTopic) {
|
|
||||||
topic = currentTopic;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return <div className={'search-filters'}>
|
|
||||||
<div className={'filter'}>
|
|
||||||
<div className={'title'} style={{ minWidth: '32.088px' }}>Type</div> {/** TODO: do `minWidth` better! */}
|
|
||||||
<select
|
|
||||||
value={type}
|
|
||||||
onChange={this.onTypeChange}>
|
|
||||||
{types}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className={'filter'}>
|
|
||||||
<div className={'title'}>Topic</div>
|
|
||||||
<select
|
|
||||||
value={topic}
|
|
||||||
onChange={this.onTopicChange}>
|
|
||||||
{topics}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onTypeChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
|
||||||
const type = event.target.value;
|
|
||||||
const props = { ...(this.state.props || {}), ...{ type } };
|
|
||||||
this.setState({
|
|
||||||
props
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onTopicChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
|
||||||
const topic = event.target.value;
|
|
||||||
const props = { ...(this.state.props || {}), ...{ topic } };
|
|
||||||
this.setState({
|
|
||||||
props
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected renderComponentList(): React.ReactNode {
|
|
||||||
return <LibraryComponentList
|
|
||||||
items={this.state.items}
|
|
||||||
install={this.install.bind(this)}
|
|
||||||
windowService={this.props.windowService}
|
|
||||||
resolveContainer={this.props.resolveContainer}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
private get topics(): string[] {
|
|
||||||
return [
|
|
||||||
'All',
|
|
||||||
'Communication',
|
|
||||||
'Data Processing',
|
|
||||||
'Data Storage',
|
|
||||||
'Device Control',
|
|
||||||
'Display',
|
|
||||||
'Other',
|
|
||||||
'Sensor',
|
|
||||||
'Signal Input/Output',
|
|
||||||
'Timing',
|
|
||||||
'Uncategorized'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private get types(): string[] {
|
|
||||||
return [
|
|
||||||
'All',
|
|
||||||
'Updatable',
|
|
||||||
'Installed',
|
|
||||||
'Arduino',
|
|
||||||
'Partner',
|
|
||||||
'Recommended',
|
|
||||||
'Contributed',
|
|
||||||
'Retired'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { inject, injectable } from 'inversify';
|
||||||
|
import { Library, LibraryService } from '../../common/protocol/library-service';
|
||||||
|
import { ListWidget } from '../components/component-list/list-widget';
|
||||||
|
import { ListItemRenderer } from '../components/component-list/list-item-renderer';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class LibraryListWidget extends ListWidget<Library> {
|
||||||
|
|
||||||
|
static WIDGET_ID = 'library-list-widget';
|
||||||
|
static WIDGET_LABEL = 'Library Manager';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@inject(LibraryService) protected service: LibraryService,
|
||||||
|
@inject(ListItemRenderer) protected itemRenderer: ListItemRenderer<Library>) {
|
||||||
|
|
||||||
|
super({
|
||||||
|
id: LibraryListWidget.WIDGET_ID,
|
||||||
|
label: LibraryListWidget.WIDGET_LABEL,
|
||||||
|
iconClass: 'library-tab-icon',
|
||||||
|
searchable: service,
|
||||||
|
installable: service,
|
||||||
|
itemLabel: (item: Library) => item.name,
|
||||||
|
itemRenderer
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { inject, injectable, postConstruct } from 'inversify';
|
|
||||||
import { Message } from '@phosphor/messaging';
|
|
||||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
|
||||||
import { MaybePromise } from '@theia/core/lib/common/types';
|
|
||||||
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
|
|
||||||
import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
|
||||||
import { LibraryFilterableListContainer } from './library-filterable-list-container';
|
|
||||||
import { LibraryService } from '../../common/protocol/library-service';
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class LibraryListWidget extends ReactWidget {
|
|
||||||
|
|
||||||
static WIDGET_ID = 'library-list-widget';
|
|
||||||
static WIDGET_LABEL = 'Library Manager';
|
|
||||||
|
|
||||||
@inject(LibraryService)
|
|
||||||
protected readonly libraryService: LibraryService;
|
|
||||||
|
|
||||||
@inject(WindowService)
|
|
||||||
protected readonly windowService: WindowService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Do not touch or use it. It is for setting the focus on the `input` after the widget activation.
|
|
||||||
*/
|
|
||||||
protected focusNode: HTMLElement | undefined;
|
|
||||||
protected readonly deferredContainer = new Deferred<HTMLElement>();
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.id = LibraryListWidget.WIDGET_ID
|
|
||||||
this.title.label = LibraryListWidget.WIDGET_LABEL;
|
|
||||||
this.title.caption = LibraryListWidget.WIDGET_LABEL
|
|
||||||
this.title.iconClass = 'library-tab-icon';
|
|
||||||
this.title.closable = true;
|
|
||||||
this.addClass('arduino-list-widget');
|
|
||||||
this.node.tabIndex = 0; // To be able to set the focus on the widget.
|
|
||||||
this.scrollOptions = {
|
|
||||||
suppressScrollX: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@postConstruct()
|
|
||||||
protected init(): void {
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getScrollContainer(): MaybePromise<HTMLElement> {
|
|
||||||
return this.deferredContainer.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onActivateRequest(msg: Message): void {
|
|
||||||
super.onActivateRequest(msg);
|
|
||||||
(this.focusNode || this.node).focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onUpdateRequest(msg: Message): void {
|
|
||||||
super.onUpdateRequest(msg);
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onFocusResolved = (element: HTMLElement | undefined) => {
|
|
||||||
this.focusNode = element;
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): React.ReactNode {
|
|
||||||
return <LibraryFilterableListContainer
|
|
||||||
resolveContainer={this.deferredContainer.resolve}
|
|
||||||
resolveFocus={this.onFocusResolved}
|
|
||||||
service={this.libraryService}
|
|
||||||
windowService={this.windowService}
|
|
||||||
/>;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace ListWidget {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Props for customizing the abstract list widget.
|
|
||||||
*/
|
|
||||||
export interface Props {
|
|
||||||
readonly id: string;
|
|
||||||
readonly title: string;
|
|
||||||
readonly iconClass: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { inject, injectable, postConstruct } from 'inversify';
|
||||||
|
import { Diagnostic } from 'vscode-languageserver-types';
|
||||||
|
import URI from '@theia/core/lib/common/uri';
|
||||||
|
import { ILogger } from '@theia/core';
|
||||||
|
import { Marker } from '@theia/markers/lib/common/marker';
|
||||||
|
import { ProblemManager } from '@theia/markers/lib/browser/problem/problem-manager';
|
||||||
|
import { ConfigService } from '../../common/protocol/config-service';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class ArduinoProblemManager extends ProblemManager {
|
||||||
|
|
||||||
|
@inject(ConfigService)
|
||||||
|
protected readonly configService: ConfigService;
|
||||||
|
|
||||||
|
@inject(ILogger)
|
||||||
|
protected readonly logger: ILogger;
|
||||||
|
|
||||||
|
protected dataDirUri: URI | undefined;
|
||||||
|
|
||||||
|
@postConstruct()
|
||||||
|
protected init(): void {
|
||||||
|
super.init();
|
||||||
|
this.configService.getConfiguration()
|
||||||
|
.then(({ dataDirUri }) => this.dataDirUri = new URI(dataDirUri))
|
||||||
|
.catch(err => this.logger.error(`Failed to determine the data directory: ${err}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
setMarkers(uri: URI, owner: string, data: Diagnostic[]): Marker<Diagnostic>[] {
|
||||||
|
if (this.dataDirUri && this.dataDirUri.isEqualOrParent(uri)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return super.setMarkers(uri, owner, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
import { injectable } from "inversify";
|
import { injectable } from 'inversify';
|
||||||
import { BrowserMenuBarContribution } from "@theia/core/lib/browser/menu/browser-menu-plugin";
|
import { FrontendApplication } from '@theia/core/lib/browser';
|
||||||
import { FrontendApplication } from "@theia/core/lib/browser";
|
import { BrowserMenuBarContribution } from '@theia/core/lib/browser/menu/browser-menu-plugin';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class ArduinoMenuContribution extends BrowserMenuBarContribution {
|
export class ArduinoMenuContribution extends BrowserMenuBarContribution {
|
||||||
|
|
||||||
onStart(app: FrontendApplication): void {
|
onStart(app: FrontendApplication): void {
|
||||||
const menu = this.factory.createMenuBar();
|
const menu = this.factory.createMenuBar();
|
||||||
app.shell.addWidget(menu, { area: 'top' });
|
app.shell.addWidget(menu, { area: 'top' });
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -7,4 +7,4 @@ import '../../../src/browser/style/browser-menu.css'
|
|||||||
export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind) => {
|
export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind) => {
|
||||||
unbind(BrowserMenuBarContribution);
|
unbind(BrowserMenuBarContribution);
|
||||||
bind(BrowserMenuBarContribution).to(ArduinoMenuContribution).inSingletonScope();
|
bind(BrowserMenuBarContribution).to(ArduinoMenuContribution).inSingletonScope();
|
||||||
})
|
});
|
||||||
|
|||||||
265
arduino-ide-extension/src/browser/monitor/monitor-connection.ts
Normal file
265
arduino-ide-extension/src/browser/monitor/monitor-connection.ts
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
import { injectable, inject, postConstruct } from 'inversify';
|
||||||
|
import { Emitter, Event } from '@theia/core/lib/common/event';
|
||||||
|
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||||
|
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||||
|
import { MonitorService, MonitorConfig, MonitorError, Status, MonitorReadEvent } from '../../common/protocol/monitor-service';
|
||||||
|
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
|
||||||
|
import { Port, Board, BoardsService, AttachedSerialBoard, AttachedBoardsChangeEvent } from '../../common/protocol/boards-service';
|
||||||
|
import { MonitorServiceClientImpl } from './monitor-service-client-impl';
|
||||||
|
import { BoardsConfig } from '../boards/boards-config';
|
||||||
|
import { MonitorModel } from './monitor-model';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class MonitorConnection {
|
||||||
|
|
||||||
|
@inject(MonitorModel)
|
||||||
|
protected readonly monitorModel: MonitorModel;
|
||||||
|
|
||||||
|
@inject(MonitorService)
|
||||||
|
protected readonly monitorService: MonitorService;
|
||||||
|
|
||||||
|
@inject(MonitorServiceClientImpl)
|
||||||
|
protected readonly monitorServiceClient: MonitorServiceClientImpl;
|
||||||
|
|
||||||
|
@inject(BoardsService)
|
||||||
|
protected readonly boardsService: BoardsService;
|
||||||
|
|
||||||
|
@inject(BoardsServiceClientImpl)
|
||||||
|
protected boardsServiceClient: BoardsServiceClientImpl;
|
||||||
|
|
||||||
|
@inject(MessageService)
|
||||||
|
protected messageService: MessageService;
|
||||||
|
|
||||||
|
@inject(FrontendApplicationStateService)
|
||||||
|
protected readonly applicationState: FrontendApplicationStateService;
|
||||||
|
|
||||||
|
protected state: MonitorConnection.State | undefined;
|
||||||
|
/**
|
||||||
|
* Note: The idea is to toggle this property from the UI (`Monitor` view)
|
||||||
|
* and the boards config and the boards attachment/detachment logic can be at on place, here.
|
||||||
|
*/
|
||||||
|
protected _autoConnect: boolean = false;
|
||||||
|
protected readonly onConnectionChangedEmitter = new Emitter<MonitorConnection.State | undefined>();
|
||||||
|
/**
|
||||||
|
* This emitter forwards all read events **iff** the connection is established.
|
||||||
|
*/
|
||||||
|
protected readonly onReadEmitter = new Emitter<MonitorReadEvent>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array for storing previous monitor errors received from the server, and based on the number of elements in this array,
|
||||||
|
* we adjust the reconnection delay.
|
||||||
|
* Super naive way: we wait `array.length * 1000` ms. Once we hit 10 errors, we do not try to reconnect and clean the array.
|
||||||
|
*/
|
||||||
|
protected monitorErrors: MonitorError[] = [];
|
||||||
|
protected reconnectTimeout?: number;
|
||||||
|
|
||||||
|
@postConstruct()
|
||||||
|
protected init(): void {
|
||||||
|
// Forward the messages from the board **iff** connected.
|
||||||
|
this.monitorServiceClient.onRead(event => {
|
||||||
|
if (this.connected) {
|
||||||
|
this.onReadEmitter.fire(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.monitorServiceClient.onError(async error => {
|
||||||
|
let shouldReconnect = false;
|
||||||
|
if (this.state) {
|
||||||
|
const { code, config } = error;
|
||||||
|
const { board, port } = config;
|
||||||
|
const options = { timeout: 3000 };
|
||||||
|
switch (code) {
|
||||||
|
case MonitorError.ErrorCodes.CLIENT_CANCEL: {
|
||||||
|
console.debug(`Connection was canceled by client: ${MonitorConnection.State.toString(this.state)}.`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MonitorError.ErrorCodes.DEVICE_BUSY: {
|
||||||
|
this.messageService.warn(`Connection failed. Serial port is busy: ${Port.toString(port)}.`, options);
|
||||||
|
shouldReconnect = this.autoConnect;
|
||||||
|
this.monitorErrors.push(error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MonitorError.ErrorCodes.DEVICE_NOT_CONFIGURED: {
|
||||||
|
this.messageService.info(`Disconnected ${Board.toString(board, { useFqbn: false })} from ${Port.toString(port)}.`, options);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case undefined: {
|
||||||
|
this.messageService.error(`Unexpected error. Reconnecting ${Board.toString(board)} on port ${Port.toString(port)}.`, options);
|
||||||
|
console.error(JSON.stringify(error));
|
||||||
|
shouldReconnect = this.connected && this.autoConnect;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const oldState = this.state;
|
||||||
|
this.state = undefined;
|
||||||
|
this.onConnectionChangedEmitter.fire(this.state);
|
||||||
|
if (shouldReconnect) {
|
||||||
|
if (this.monitorErrors.length >= 10) {
|
||||||
|
this.messageService.warn(`Failed to reconnect ${Board.toString(board, { useFqbn: false })} to the the serial-monitor after 10 consecutive attempts. The ${Port.toString(port)} serial port is busy. after 10 consecutive attempts.`);
|
||||||
|
this.monitorErrors.length = 0;
|
||||||
|
} else {
|
||||||
|
const attempts = (this.monitorErrors.length || 1);
|
||||||
|
if (this.reconnectTimeout !== undefined) {
|
||||||
|
// Clear the previous timer.
|
||||||
|
window.clearTimeout(this.reconnectTimeout);
|
||||||
|
}
|
||||||
|
const timeout = attempts * 1000;
|
||||||
|
this.messageService.warn(`Reconnecting ${Board.toString(board, { useFqbn: false })} to ${Port.toString(port)} in ${attempts} seconds...`, { timeout });
|
||||||
|
this.reconnectTimeout = window.setTimeout(() => this.connect(oldState.config), timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.boardsServiceClient.onBoardsConfigChanged(this.handleBoardConfigChange.bind(this));
|
||||||
|
this.boardsServiceClient.onBoardsChanged(event => {
|
||||||
|
if (this.autoConnect && this.connected) {
|
||||||
|
const { boardsConfig } = this.boardsServiceClient;
|
||||||
|
if (this.boardsServiceClient.canUploadTo(boardsConfig, { silent: false })) {
|
||||||
|
const { attached } = AttachedBoardsChangeEvent.diff(event);
|
||||||
|
if (attached.boards.some(board => AttachedSerialBoard.is(board) && BoardsConfig.Config.sameAs(boardsConfig, board))) {
|
||||||
|
const { selectedBoard: board, selectedPort: port } = boardsConfig;
|
||||||
|
const { baudRate } = this.monitorModel;
|
||||||
|
this.disconnect()
|
||||||
|
.then(() => this.connect({ board, port, baudRate }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Handles the `baudRate` changes by reconnecting if required.
|
||||||
|
this.monitorModel.onChange(({ property }) => {
|
||||||
|
if (property === 'baudRate' && this.autoConnect && this.connected) {
|
||||||
|
const { boardsConfig } = this.boardsServiceClient;
|
||||||
|
this.handleBoardConfigChange(boardsConfig);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get connected(): boolean {
|
||||||
|
return !!this.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
get monitorConfig(): MonitorConfig | undefined {
|
||||||
|
return this.state ? this.state.config : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
get autoConnect(): boolean {
|
||||||
|
return this._autoConnect;
|
||||||
|
}
|
||||||
|
|
||||||
|
set autoConnect(value: boolean) {
|
||||||
|
const oldValue = this._autoConnect;
|
||||||
|
this._autoConnect = value;
|
||||||
|
// When we enable the auto-connect, we have to connect
|
||||||
|
if (!oldValue && value) {
|
||||||
|
// We have to make sure the previous boards config has been restored.
|
||||||
|
// Otherwise, we might start the auto-connection without configured boards.
|
||||||
|
this.applicationState.reachedState('started_contributions').then(() => {
|
||||||
|
const { boardsConfig } = this.boardsServiceClient;
|
||||||
|
this.handleBoardConfigChange(boardsConfig);
|
||||||
|
});
|
||||||
|
} else if (oldValue && !value) {
|
||||||
|
if (this.reconnectTimeout !== undefined) {
|
||||||
|
window.clearTimeout(this.reconnectTimeout);
|
||||||
|
this.monitorErrors.length = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect(config: MonitorConfig): Promise<Status> {
|
||||||
|
if (this.connected) {
|
||||||
|
const disconnectStatus = await this.disconnect();
|
||||||
|
if (!Status.isOK(disconnectStatus)) {
|
||||||
|
return disconnectStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.info(`>>> Creating serial monitor connection for ${Board.toString(config.board)} on port ${Port.toString(config.port)}...`);
|
||||||
|
const connectStatus = await this.monitorService.connect(config);
|
||||||
|
if (Status.isOK(connectStatus)) {
|
||||||
|
this.state = { config };
|
||||||
|
console.info(`<<< Serial monitor connection created for ${Board.toString(config.board, { useFqbn: false })} on port ${Port.toString(config.port)}.`);
|
||||||
|
}
|
||||||
|
this.onConnectionChangedEmitter.fire(this.state);
|
||||||
|
return Status.isOK(connectStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
async disconnect(): Promise<Status> {
|
||||||
|
if (!this.state) { // XXX: we user `this.state` instead of `this.connected` to make the type checker happy.
|
||||||
|
return Status.OK;
|
||||||
|
}
|
||||||
|
console.log('>>> Disposing existing monitor connection...');
|
||||||
|
const status = await this.monitorService.disconnect();
|
||||||
|
if (Status.isOK(status)) {
|
||||||
|
console.log(`<<< Disposed connection. Was: ${MonitorConnection.State.toString(this.state)}`);
|
||||||
|
} else {
|
||||||
|
console.warn(`<<< Could not dispose connection. Activate connection: ${MonitorConnection.State.toString(this.state)}`);
|
||||||
|
}
|
||||||
|
this.state = undefined;
|
||||||
|
this.onConnectionChangedEmitter.fire(this.state);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the data to the connected serial monitor.
|
||||||
|
* The desired EOL is appended to `data`, you do not have to add it.
|
||||||
|
* It is a NOOP if connected.
|
||||||
|
*/
|
||||||
|
async send(data: string): Promise<Status> {
|
||||||
|
if (!this.connected) {
|
||||||
|
return Status.NOT_CONNECTED;
|
||||||
|
}
|
||||||
|
return new Promise<Status>(resolve => {
|
||||||
|
this.monitorService.send(data + this.monitorModel.lineEnding)
|
||||||
|
.then(() => resolve(Status.OK));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get onConnectionChanged(): Event<MonitorConnection.State | undefined> {
|
||||||
|
return this.onConnectionChangedEmitter.event;
|
||||||
|
}
|
||||||
|
|
||||||
|
get onRead(): Event<MonitorReadEvent> {
|
||||||
|
return this.onReadEmitter.event;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async handleBoardConfigChange(boardsConfig: BoardsConfig.Config): Promise<void> {
|
||||||
|
if (this.autoConnect) {
|
||||||
|
if (this.boardsServiceClient.canUploadTo(boardsConfig, { silent: false })) {
|
||||||
|
// Instead of calling `getAttachedBoards` and filtering for `AttachedSerialBoard` we have to check the available ports.
|
||||||
|
// The connected board might be unknown. See: https://github.com/arduino/arduino-pro-ide/issues/127#issuecomment-563251881
|
||||||
|
this.boardsService.getAvailablePorts().then(({ ports }) => {
|
||||||
|
if (ports.some(port => Port.equals(port, boardsConfig.selectedPort))) {
|
||||||
|
new Promise<void>(resolve => {
|
||||||
|
// First, disconnect if connected.
|
||||||
|
if (this.connected) {
|
||||||
|
this.disconnect().then(() => resolve());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
}).then(() => {
|
||||||
|
// Then (re-)connect.
|
||||||
|
const { selectedBoard: board, selectedPort: port } = boardsConfig;
|
||||||
|
const { baudRate } = this.monitorModel;
|
||||||
|
this.connect({ board, port, baudRate });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace MonitorConnection {
|
||||||
|
|
||||||
|
export interface State {
|
||||||
|
readonly config: MonitorConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace State {
|
||||||
|
export function toString(state: State): string {
|
||||||
|
const { config } = state;
|
||||||
|
const { board, port } = config;
|
||||||
|
return `${Board.toString(board)} ${Port.toString(port)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
119
arduino-ide-extension/src/browser/monitor/monitor-model.ts
Normal file
119
arduino-ide-extension/src/browser/monitor/monitor-model.ts
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import { injectable, inject } from 'inversify';
|
||||||
|
import { Emitter, Event } from '@theia/core/lib/common/event';
|
||||||
|
import { MonitorConfig } from '../../common/protocol/monitor-service';
|
||||||
|
import { FrontendApplicationContribution, LocalStorageService } from '@theia/core/lib/browser';
|
||||||
|
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class MonitorModel implements FrontendApplicationContribution {
|
||||||
|
|
||||||
|
protected static STORAGE_ID = 'arduino-monitor-model';
|
||||||
|
|
||||||
|
@inject(LocalStorageService)
|
||||||
|
protected readonly localStorageService: LocalStorageService;
|
||||||
|
|
||||||
|
@inject(BoardsServiceClientImpl)
|
||||||
|
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
||||||
|
|
||||||
|
protected readonly onChangeEmitter: Emitter<MonitorModel.State.Change<keyof MonitorModel.State>>;
|
||||||
|
protected _autoscroll: boolean;
|
||||||
|
protected _timestamp: boolean;
|
||||||
|
protected _baudRate: MonitorConfig.BaudRate;
|
||||||
|
protected _lineEnding: MonitorModel.EOL;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._autoscroll = true;
|
||||||
|
this._timestamp = false;
|
||||||
|
this._baudRate = MonitorConfig.BaudRate.DEFAULT;
|
||||||
|
this._lineEnding = MonitorModel.EOL.DEFAULT;
|
||||||
|
this.onChangeEmitter = new Emitter<MonitorModel.State.Change<keyof MonitorModel.State>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
onStart(): void {
|
||||||
|
this.localStorageService.getData<MonitorModel.State>(MonitorModel.STORAGE_ID).then(state => {
|
||||||
|
if (state) {
|
||||||
|
this.restoreState(state);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get onChange(): Event<MonitorModel.State.Change<keyof MonitorModel.State>> {
|
||||||
|
return this.onChangeEmitter.event;
|
||||||
|
}
|
||||||
|
|
||||||
|
get autoscroll(): boolean {
|
||||||
|
return this._autoscroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleAutoscroll(): void {
|
||||||
|
this._autoscroll = !this._autoscroll;
|
||||||
|
this.storeState();
|
||||||
|
this.storeState().then(() => this.onChangeEmitter.fire({ property: 'autoscroll', value: this._autoscroll }));
|
||||||
|
}
|
||||||
|
|
||||||
|
get timestamp(): boolean {
|
||||||
|
return this._timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleTimestamp(): void {
|
||||||
|
this._timestamp = !this._timestamp;
|
||||||
|
this.storeState().then(() => this.onChangeEmitter.fire({ property: 'timestamp', value: this._timestamp }));
|
||||||
|
}
|
||||||
|
|
||||||
|
get baudRate(): MonitorConfig.BaudRate {
|
||||||
|
return this._baudRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
set baudRate(baudRate: MonitorConfig.BaudRate) {
|
||||||
|
this._baudRate = baudRate;
|
||||||
|
this.storeState().then(() => this.onChangeEmitter.fire({ property: 'baudRate', value: this._baudRate }));
|
||||||
|
}
|
||||||
|
|
||||||
|
get lineEnding(): MonitorModel.EOL {
|
||||||
|
return this._lineEnding;
|
||||||
|
}
|
||||||
|
|
||||||
|
set lineEnding(lineEnding: MonitorModel.EOL) {
|
||||||
|
this._lineEnding = lineEnding;
|
||||||
|
this.storeState().then(() => this.onChangeEmitter.fire({ property: 'lineEnding', value: this._lineEnding }));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected restoreState(state: MonitorModel.State): void {
|
||||||
|
this._autoscroll = state.autoscroll;
|
||||||
|
this._timestamp = state.timestamp;
|
||||||
|
this._baudRate = state.baudRate;
|
||||||
|
this._lineEnding = state.lineEnding;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async storeState(): Promise<void> {
|
||||||
|
return this.localStorageService.setData(MonitorModel.STORAGE_ID, {
|
||||||
|
autoscroll: this._autoscroll,
|
||||||
|
timestamp: this._timestamp,
|
||||||
|
baudRate: this._baudRate,
|
||||||
|
lineEnding: this._lineEnding
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace MonitorModel {
|
||||||
|
|
||||||
|
export interface State {
|
||||||
|
autoscroll: boolean;
|
||||||
|
timestamp: boolean;
|
||||||
|
baudRate: MonitorConfig.BaudRate;
|
||||||
|
lineEnding: EOL;
|
||||||
|
}
|
||||||
|
export namespace State {
|
||||||
|
export interface Change<K extends keyof State> {
|
||||||
|
readonly property: K;
|
||||||
|
readonly value: State[K];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EOL = '' | '\n' | '\r' | '\r\n';
|
||||||
|
export namespace EOL {
|
||||||
|
export const DEFAULT: EOL = '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { injectable } from 'inversify';
|
||||||
|
import { Emitter } from '@theia/core/lib/common/event';
|
||||||
|
import { MonitorServiceClient, MonitorReadEvent, MonitorError } from '../../common/protocol/monitor-service';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class MonitorServiceClientImpl implements MonitorServiceClient {
|
||||||
|
|
||||||
|
protected readonly onReadEmitter = new Emitter<MonitorReadEvent>();
|
||||||
|
protected readonly onErrorEmitter = new Emitter<MonitorError>();
|
||||||
|
readonly onRead = this.onReadEmitter.event;
|
||||||
|
readonly onError = this.onErrorEmitter.event;
|
||||||
|
|
||||||
|
notifyRead(event: MonitorReadEvent): void {
|
||||||
|
this.onReadEmitter.fire(event);
|
||||||
|
const { data } = event;
|
||||||
|
console.debug(`Received data: ${data}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyError(error: MonitorError): void {
|
||||||
|
this.onErrorEmitter.fire(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { injectable, inject } from "inversify";
|
||||||
|
import { AbstractViewContribution } from "@theia/core/lib/browser";
|
||||||
|
import { MonitorWidget } from "./monitor-widget";
|
||||||
|
import { MenuModelRegistry, Command, CommandRegistry } from "@theia/core";
|
||||||
|
import { ArduinoMenus } from "../arduino-frontend-contribution";
|
||||||
|
import { TabBarToolbarContribution, TabBarToolbarRegistry } from "@theia/core/lib/browser/shell/tab-bar-toolbar";
|
||||||
|
import { MonitorModel } from './monitor-model';
|
||||||
|
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
|
||||||
|
|
||||||
|
export namespace SerialMonitor {
|
||||||
|
export namespace Commands {
|
||||||
|
export const AUTOSCROLL: Command = {
|
||||||
|
id: 'serial-monitor-autoscroll',
|
||||||
|
label: 'Autoscroll'
|
||||||
|
}
|
||||||
|
export const TIMESTAMP: Command = {
|
||||||
|
id: 'serial-monitor-timestamp',
|
||||||
|
label: 'Timestamp'
|
||||||
|
}
|
||||||
|
export const CLEAR_OUTPUT: Command = {
|
||||||
|
id: 'serial-monitor-clear-output',
|
||||||
|
label: 'Clear Output',
|
||||||
|
iconClass: 'clear-all'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class MonitorViewContribution extends AbstractViewContribution<MonitorWidget> implements TabBarToolbarContribution {
|
||||||
|
|
||||||
|
static readonly OPEN_SERIAL_MONITOR = MonitorWidget.ID + ':toggle';
|
||||||
|
|
||||||
|
@inject(MonitorModel) protected readonly model: MonitorModel;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
widgetId: MonitorWidget.ID,
|
||||||
|
widgetName: 'Serial Monitor',
|
||||||
|
defaultWidgetOptions: {
|
||||||
|
area: 'bottom'
|
||||||
|
},
|
||||||
|
toggleCommandId: MonitorViewContribution.OPEN_SERIAL_MONITOR,
|
||||||
|
toggleKeybinding: 'ctrlcmd+shift+m'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
registerMenus(menus: MenuModelRegistry): void {
|
||||||
|
if (this.toggleCommand) {
|
||||||
|
menus.registerMenuAction(ArduinoMenus.TOOLS, {
|
||||||
|
commandId: this.toggleCommand.id,
|
||||||
|
label: 'Serial Monitor'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
||||||
|
registry.registerItem({
|
||||||
|
id: 'monitor-autoscroll',
|
||||||
|
render: () => this.renderAutoScrollButton(),
|
||||||
|
isVisible: widget => widget instanceof MonitorWidget,
|
||||||
|
onDidChange: this.model.onChange as any // XXX: it's a hack. See: https://github.com/eclipse-theia/theia/pull/6696/
|
||||||
|
});
|
||||||
|
registry.registerItem({
|
||||||
|
id: 'monitor-timestamp',
|
||||||
|
render: () => this.renderTimestampButton(),
|
||||||
|
isVisible: widget => widget instanceof MonitorWidget,
|
||||||
|
onDidChange: this.model.onChange as any // XXX: it's a hack. See: https://github.com/eclipse-theia/theia/pull/6696/
|
||||||
|
});
|
||||||
|
registry.registerItem({
|
||||||
|
id: SerialMonitor.Commands.CLEAR_OUTPUT.id,
|
||||||
|
command: SerialMonitor.Commands.CLEAR_OUTPUT.id,
|
||||||
|
tooltip: 'Clear Output'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
registerCommands(commands: CommandRegistry): void {
|
||||||
|
commands.registerCommand(SerialMonitor.Commands.CLEAR_OUTPUT, {
|
||||||
|
isEnabled: widget => widget instanceof MonitorWidget,
|
||||||
|
isVisible: widget => widget instanceof MonitorWidget,
|
||||||
|
execute: widget => {
|
||||||
|
if (widget instanceof MonitorWidget) {
|
||||||
|
widget.clearConsole();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (this.toggleCommand) {
|
||||||
|
commands.registerCommand(this.toggleCommand, {
|
||||||
|
execute: () => this.openView({
|
||||||
|
toggle: true,
|
||||||
|
activate: true
|
||||||
|
}),
|
||||||
|
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'right'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderAutoScrollButton(): React.ReactNode {
|
||||||
|
return <React.Fragment key='autoscroll-toolbar-item'>
|
||||||
|
<div
|
||||||
|
title='Toggle Autoscroll'
|
||||||
|
className={`item enabled fa fa-angle-double-down arduino-monitor ${this.model.autoscroll ? 'toggled' : ''}`}
|
||||||
|
onClick={this.toggleAutoScroll}
|
||||||
|
></div>
|
||||||
|
</React.Fragment>;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly toggleAutoScroll = () => this.doToggleAutoScroll();
|
||||||
|
protected async doToggleAutoScroll(): Promise<void> {
|
||||||
|
this.model.toggleAutoscroll();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderTimestampButton(): React.ReactNode {
|
||||||
|
return <React.Fragment key='line-ending-toolbar-item'>
|
||||||
|
<div
|
||||||
|
title='Toggle Timestamp'
|
||||||
|
className={`item enabled fa fa-clock-o arduino-monitor ${this.model.timestamp ? 'toggled' : ''}`}
|
||||||
|
onClick={this.toggleTimestamp}
|
||||||
|
></div>
|
||||||
|
</React.Fragment>;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly toggleTimestamp = () => this.doToggleTimestamp();
|
||||||
|
protected async doToggleTimestamp(): Promise<void> {
|
||||||
|
this.model.toggleTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
333
arduino-ide-extension/src/browser/monitor/monitor-widget.tsx
Normal file
333
arduino-ide-extension/src/browser/monitor/monitor-widget.tsx
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import * as dateFormat from 'dateformat';
|
||||||
|
import { postConstruct, injectable, inject } from 'inversify';
|
||||||
|
import { OptionsType } from 'react-select/src/types';
|
||||||
|
import { isOSX } from '@theia/core/lib/common/os';
|
||||||
|
import { Event, Emitter } from '@theia/core/lib/common/event';
|
||||||
|
import { Key, KeyCode } from '@theia/core/lib/browser/keys';
|
||||||
|
import { DisposableCollection } from '@theia/core/lib/common/disposable'
|
||||||
|
import { ReactWidget, Message, Widget, MessageLoop } from '@theia/core/lib/browser/widgets';
|
||||||
|
import { Board, Port } from '../../common/protocol/boards-service';
|
||||||
|
import { MonitorConfig } from '../../common/protocol/monitor-service';
|
||||||
|
import { ArduinoSelect } from '../components/arduino-select';
|
||||||
|
import { MonitorModel } from './monitor-model';
|
||||||
|
import { MonitorConnection } from './monitor-connection';
|
||||||
|
import { MonitorServiceClientImpl } from './monitor-service-client-impl';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class MonitorWidget extends ReactWidget {
|
||||||
|
|
||||||
|
static readonly ID = 'serial-monitor';
|
||||||
|
|
||||||
|
@inject(MonitorModel)
|
||||||
|
protected readonly monitorModel: MonitorModel;
|
||||||
|
|
||||||
|
@inject(MonitorConnection)
|
||||||
|
protected readonly monitorConnection: MonitorConnection;
|
||||||
|
|
||||||
|
@inject(MonitorServiceClientImpl)
|
||||||
|
protected readonly monitorServiceClient: MonitorServiceClientImpl;
|
||||||
|
|
||||||
|
protected widgetHeight: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do not touch or use it. It is for setting the focus on the `input` after the widget activation.
|
||||||
|
*/
|
||||||
|
protected focusNode: HTMLElement | undefined;
|
||||||
|
/**
|
||||||
|
* Guard against re-rendering the view after the close was requested.
|
||||||
|
* See: https://github.com/eclipse-theia/theia/issues/6704
|
||||||
|
*/
|
||||||
|
protected closing = false;
|
||||||
|
protected readonly clearOutputEmitter = new Emitter<void>();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.id = MonitorWidget.ID;
|
||||||
|
this.title.label = 'Serial Monitor';
|
||||||
|
this.title.iconClass = 'arduino-serial-monitor-tab-icon';
|
||||||
|
this.title.closable = true;
|
||||||
|
this.scrollOptions = undefined;
|
||||||
|
this.toDispose.push(this.clearOutputEmitter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@postConstruct()
|
||||||
|
protected init(): void {
|
||||||
|
this.update();
|
||||||
|
this.toDispose.push(this.monitorConnection.onConnectionChanged(() => this.clearConsole()));
|
||||||
|
}
|
||||||
|
|
||||||
|
clearConsole(): void {
|
||||||
|
this.clearOutputEmitter.fire(undefined);
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onAfterAttach(msg: Message): void {
|
||||||
|
super.onAfterAttach(msg);
|
||||||
|
this.monitorConnection.autoConnect = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onCloseRequest(msg: Message): void {
|
||||||
|
this.closing = true;
|
||||||
|
this.monitorConnection.autoConnect = false;
|
||||||
|
if (this.monitorConnection.connected) {
|
||||||
|
this.monitorConnection.disconnect();
|
||||||
|
}
|
||||||
|
super.onCloseRequest(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onUpdateRequest(msg: Message): void {
|
||||||
|
// TODO: `this.isAttached`
|
||||||
|
// See: https://github.com/eclipse-theia/theia/issues/6704#issuecomment-562574713
|
||||||
|
if (!this.closing && this.isAttached) {
|
||||||
|
super.onUpdateRequest(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onResize(msg: Widget.ResizeMessage): void {
|
||||||
|
super.onResize(msg);
|
||||||
|
this.widgetHeight = msg.height;
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onActivateRequest(msg: Message): void {
|
||||||
|
super.onActivateRequest(msg);
|
||||||
|
(this.focusNode || this.node).focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onFocusResolved = (element: HTMLElement | undefined) => {
|
||||||
|
this.focusNode = element;
|
||||||
|
requestAnimationFrame(() => MessageLoop.sendMessage(this, Widget.Msg.ActivateRequest));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get lineEndings(): OptionsType<SelectOption<MonitorModel.EOL>> {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'No Line Ending',
|
||||||
|
value: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'New Line',
|
||||||
|
value: '\n'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Carriage Return',
|
||||||
|
value: '\r'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Both NL & CR',
|
||||||
|
value: '\r\n'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get baudRates(): OptionsType<SelectOption<MonitorConfig.BaudRate>> {
|
||||||
|
const baudRates: Array<MonitorConfig.BaudRate> = [300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200];
|
||||||
|
return baudRates.map(baudRate => ({ label: baudRate + ' baud', value: baudRate }));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): React.ReactNode {
|
||||||
|
const { baudRates, lineEndings } = this;
|
||||||
|
const lineEnding = lineEndings.find(item => item.value === this.monitorModel.lineEnding) || lineEndings[1]; // Defaults to `\n`.
|
||||||
|
const baudRate = baudRates.find(item => item.value === this.monitorModel.baudRate) || baudRates[4]; // Defaults to `9600`.
|
||||||
|
return <div className='serial-monitor'>
|
||||||
|
<div className='head'>
|
||||||
|
<div className='send'>
|
||||||
|
<SerialMonitorSendInput
|
||||||
|
monitorConfig={this.monitorConnection.monitorConfig}
|
||||||
|
resolveFocus={this.onFocusResolved}
|
||||||
|
onSend={this.onSend} />
|
||||||
|
</div>
|
||||||
|
<div className='config'>
|
||||||
|
<div className='select'>
|
||||||
|
<ArduinoSelect
|
||||||
|
maxMenuHeight={this.widgetHeight - 40}
|
||||||
|
options={lineEndings}
|
||||||
|
defaultValue={lineEnding}
|
||||||
|
onChange={this.onChangeLineEnding} />
|
||||||
|
</div>
|
||||||
|
<div className='select'>
|
||||||
|
<ArduinoSelect
|
||||||
|
className='select'
|
||||||
|
maxMenuHeight={this.widgetHeight - 40}
|
||||||
|
options={baudRates}
|
||||||
|
defaultValue={baudRate}
|
||||||
|
onChange={this.onChangeBaudRate} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='body'>
|
||||||
|
<SerialMonitorOutput
|
||||||
|
monitorModel={this.monitorModel}
|
||||||
|
monitorConnection={this.monitorConnection}
|
||||||
|
clearConsoleEvent={this.clearOutputEmitter.event} />
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly onSend = (value: string) => this.doSend(value);
|
||||||
|
protected async doSend(value: string): Promise<void> {
|
||||||
|
this.monitorConnection.send(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly onChangeLineEnding = (option: SelectOption<MonitorModel.EOL>) => {
|
||||||
|
this.monitorModel.lineEnding = option.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly onChangeBaudRate = async (option: SelectOption<MonitorConfig.BaudRate>) => {
|
||||||
|
this.monitorModel.baudRate = option.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace SerialMonitorSendInput {
|
||||||
|
export interface Props {
|
||||||
|
readonly monitorConfig?: MonitorConfig;
|
||||||
|
readonly onSend: (text: string) => void;
|
||||||
|
readonly resolveFocus: (element: HTMLElement | undefined) => void;
|
||||||
|
}
|
||||||
|
export interface State {
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SerialMonitorSendInput extends React.Component<SerialMonitorSendInput.Props, SerialMonitorSendInput.State> {
|
||||||
|
|
||||||
|
constructor(props: Readonly<SerialMonitorSendInput.Props>) {
|
||||||
|
super(props);
|
||||||
|
this.state = { text: '' };
|
||||||
|
this.onChange = this.onChange.bind(this);
|
||||||
|
this.onSend = this.onSend.bind(this);
|
||||||
|
this.onKeyDown = this.onKeyDown.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): React.ReactNode {
|
||||||
|
return <input
|
||||||
|
ref={this.setRef}
|
||||||
|
type='text'
|
||||||
|
className={this.props.monitorConfig ? '' : 'warning'}
|
||||||
|
placeholder={this.placeholder}
|
||||||
|
value={this.state.text}
|
||||||
|
onChange={this.onChange}
|
||||||
|
onKeyDown={this.onKeyDown} />
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get placeholder(): string {
|
||||||
|
const { monitorConfig } = this.props;
|
||||||
|
if (!monitorConfig) {
|
||||||
|
return 'Not connected. Select a board and a port to connect automatically.'
|
||||||
|
}
|
||||||
|
const { board, port } = monitorConfig;
|
||||||
|
return `Message (${isOSX ? '⌘' : 'Ctrl'}+Enter to send message to '${Board.toString(board, { useFqbn: false })}' on '${Port.toString(port)}')`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected setRef = (element: HTMLElement | null) => {
|
||||||
|
if (this.props.resolveFocus) {
|
||||||
|
this.props.resolveFocus(element || undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||||
|
this.setState({ text: event.target.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onSend(): void {
|
||||||
|
this.props.onSend(this.state.text);
|
||||||
|
this.setState({ text: '' });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
|
||||||
|
const keyCode = KeyCode.createKeyCode(event.nativeEvent);
|
||||||
|
if (keyCode) {
|
||||||
|
const { key, meta, ctrl } = keyCode;
|
||||||
|
if (key === Key.ENTER && ((isOSX && meta) || (!isOSX && ctrl))) {
|
||||||
|
this.onSend();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace SerialMonitorOutput {
|
||||||
|
export interface Props {
|
||||||
|
readonly monitorModel: MonitorModel;
|
||||||
|
readonly monitorConnection: MonitorConnection;
|
||||||
|
readonly clearConsoleEvent: Event<void>;
|
||||||
|
}
|
||||||
|
export interface State {
|
||||||
|
content: string;
|
||||||
|
timestamp: boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SerialMonitorOutput extends React.Component<SerialMonitorOutput.Props, SerialMonitorOutput.State> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do not touch it. It is used to be able to "follow" the serial monitor log.
|
||||||
|
*/
|
||||||
|
protected anchor: HTMLElement | null;
|
||||||
|
protected toDisposeBeforeUnmount = new DisposableCollection();
|
||||||
|
|
||||||
|
constructor(props: Readonly<SerialMonitorOutput.Props>) {
|
||||||
|
super(props);
|
||||||
|
this.state = { content: '', timestamp: this.props.monitorModel.timestamp };
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): React.ReactNode {
|
||||||
|
return <React.Fragment>
|
||||||
|
<div style={({ whiteSpace: 'pre', fontFamily: 'monospace' })}>
|
||||||
|
{this.state.content}
|
||||||
|
</div>
|
||||||
|
<div style={{ float: 'left', clear: 'both' }} ref={element => { this.anchor = element; }} />
|
||||||
|
</React.Fragment>;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
this.scrollToBottom();
|
||||||
|
let chunk = '';
|
||||||
|
this.toDisposeBeforeUnmount.pushAll([
|
||||||
|
this.props.monitorConnection.onRead(({ data }) => {
|
||||||
|
chunk += data;
|
||||||
|
const eolIndex = chunk.indexOf('\n');
|
||||||
|
if (eolIndex !== -1) {
|
||||||
|
const line = chunk.substring(0, eolIndex + 1);
|
||||||
|
chunk = chunk.slice(eolIndex + 1);
|
||||||
|
const content = `${this.state.content}${this.state.timestamp ? `${dateFormat(new Date(), 'H:M:ss.l')} -> ` : ''}${line}`;
|
||||||
|
this.setState({ content });
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
this.props.clearConsoleEvent(() => this.setState({ content: '' })),
|
||||||
|
this.props.monitorModel.onChange(({ property }) => {
|
||||||
|
if (property === 'timestamp') {
|
||||||
|
const { timestamp } = this.props.monitorModel;
|
||||||
|
this.setState({ timestamp });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(): void {
|
||||||
|
this.scrollToBottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount(): void {
|
||||||
|
// TODO: "Your preferred browser's local storage is almost full." Discard `content` before saving layout?
|
||||||
|
this.toDisposeBeforeUnmount.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected scrollToBottom(): void {
|
||||||
|
if (this.props.monitorModel.autoscroll && this.anchor) {
|
||||||
|
this.anchor.scrollIntoView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SelectOption<T> {
|
||||||
|
readonly label: string;
|
||||||
|
readonly value: T;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { injectable } from 'inversify';
|
||||||
|
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||||
|
import { ShellLayoutRestorer } from '@theia/core/lib/browser/shell/shell-layout-restorer';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class ArduinoShellLayoutRestorer extends ShellLayoutRestorer {
|
||||||
|
|
||||||
|
// Workaround for https://github.com/eclipse-theia/theia/issues/6579.
|
||||||
|
async storeLayoutAsync(app: FrontendApplication): Promise<void> {
|
||||||
|
if (this.shouldStoreLayout) {
|
||||||
|
try {
|
||||||
|
this.logger.info('>>> Storing the layout...');
|
||||||
|
const layoutData = app.shell.getLayoutData();
|
||||||
|
const serializedLayoutData = this.deflate(layoutData);
|
||||||
|
await this.storageService.setData(this.storageKey, serializedLayoutData);
|
||||||
|
this.logger.info('<<< The layout has been successfully stored.');
|
||||||
|
} catch (error) {
|
||||||
|
await this.storageService.setData(this.storageKey, undefined);
|
||||||
|
this.logger.error('Error during serialization of layout data', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { inject, injectable, postConstruct } from 'inversify';
|
||||||
|
import URI from '@theia/core/lib/common/uri';
|
||||||
|
import { Title, Widget } from '@phosphor/widgets';
|
||||||
|
import { ILogger } from '@theia/core';
|
||||||
|
import { WidgetDecoration } from '@theia/core/lib/browser/widget-decoration';
|
||||||
|
import { TabBarDecoratorService } from '@theia/core/lib/browser/shell/tab-bar-decorator';
|
||||||
|
import { ConfigService } from '../../common/protocol/config-service';
|
||||||
|
import { EditorWidget } from '@theia/editor/lib/browser';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class ArduinoTabBarDecoratorService extends TabBarDecoratorService {
|
||||||
|
|
||||||
|
@inject(ConfigService)
|
||||||
|
protected readonly configService: ConfigService;
|
||||||
|
|
||||||
|
@inject(ILogger)
|
||||||
|
protected readonly logger: ILogger;
|
||||||
|
|
||||||
|
|
||||||
|
protected dataDirUri: URI | undefined;
|
||||||
|
|
||||||
|
@postConstruct()
|
||||||
|
protected init(): void {
|
||||||
|
super.init();
|
||||||
|
this.configService.getConfiguration()
|
||||||
|
.then(({ dataDirUri }) => this.dataDirUri = new URI(dataDirUri))
|
||||||
|
.catch(err => this.logger.error(`Failed to determine the data directory: ${err}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
getDecorations(title: Title<Widget>): WidgetDecoration.Data[] {
|
||||||
|
if (title.owner instanceof EditorWidget) {
|
||||||
|
const editor = title.owner.editor;
|
||||||
|
if (this.dataDirUri && this.dataDirUri.isEqualOrParent(editor.uri)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.getDecorations(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import { injectable, inject } from "inversify";
|
|
||||||
import URI from "@theia/core/lib/common/uri";
|
|
||||||
import { FileSystem } from "@theia/filesystem/lib/common";
|
|
||||||
import { WindowService } from "@theia/core/lib/browser/window/window-service";
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class SketchFactory {
|
|
||||||
|
|
||||||
@inject(FileSystem)
|
|
||||||
protected readonly fileSystem: FileSystem;
|
|
||||||
|
|
||||||
@inject(WindowService)
|
|
||||||
protected readonly windowService: WindowService;
|
|
||||||
|
|
||||||
public async createNewSketch(parent: URI): Promise<void> {
|
|
||||||
const monthNames = ["january", "february", "march", "april", "may", "june",
|
|
||||||
"july", "august", "september", "october", "november", "december"
|
|
||||||
];
|
|
||||||
const today = new Date();
|
|
||||||
|
|
||||||
const sketchBaseName = `sketch_${monthNames[today.getMonth()]}${today.getDay()}`;
|
|
||||||
let sketchName: string | undefined;
|
|
||||||
for (let i = 97; i < 97 + 26; i++) {
|
|
||||||
let sketchNameCandidate = `${sketchBaseName}${String.fromCharCode(i)}`;
|
|
||||||
if (await this.fileSystem.exists(parent.resolve(sketchNameCandidate).toString())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
sketchName = sketchNameCandidate;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sketchName) {
|
|
||||||
throw new Error("Cannot create a unique sketch name");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const sketchDir = parent.resolve(sketchName);
|
|
||||||
const sketchFile = sketchDir.resolve(`${sketchName}.ino`);
|
|
||||||
this.fileSystem.createFolder(sketchDir.toString());
|
|
||||||
this.fileSystem.createFile(sketchFile.toString(), { content: `
|
|
||||||
void setup() {
|
|
||||||
// put your setup code here, to run once:
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
// put your main code here, to run repeatedly:
|
|
||||||
|
|
||||||
}
|
|
||||||
` });
|
|
||||||
const location = new URL(window.location.href);
|
|
||||||
location.searchParams.set('sketch', sketchFile.toString());
|
|
||||||
this.windowService.openNewWindow(location.toString());
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error("Cannot create new sketch: " + e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
45
arduino-ide-extension/src/browser/style/arduino-select.css
Normal file
45
arduino-ide-extension/src/browser/style/arduino-select.css
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
.arduino-select__control {
|
||||||
|
border: var(--theia-layout-color2) var(--theia-border-width) solid !important;
|
||||||
|
background: var(--theia-layout-color2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-select__control:hover {
|
||||||
|
border: var(--theia-layout-color2) var(--theia-border-width) solid !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-select__control--is-focused {
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-select__option--is-selected {
|
||||||
|
background-color: var(--theia-accent-color3) !important;
|
||||||
|
color: var(--theia-content-font-color0) !important;
|
||||||
|
border-color: var(--theia-accent-color3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-select__option--is-focused {
|
||||||
|
background-color: var(--theia-accent-color4) !important;
|
||||||
|
border-color: var(--theia-accent-color3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-select__menu {
|
||||||
|
background-color: var(--theia-layout-color2) !important;
|
||||||
|
border: 1px solid var(--theia-accent-color3) !important;
|
||||||
|
top: auto !important; /* to align the top of the menu with the bottom of the control */
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-select__control.arduino-select__control--menu-is-open {
|
||||||
|
border: 1px solid !important;
|
||||||
|
border-color: var(--theia-accent-color3) !important;
|
||||||
|
border-bottom-color: var(--theia-layout-color2) !important; /* if the bottom border color has the same color as the background of the control, we make the border "invisible" */
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-select__value-container .arduino-select__single-value {
|
||||||
|
color: var(--theia-ui-font-color1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-select__menu-list {
|
||||||
|
padding-top: 0 !important;
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
}
|
||||||
@@ -18,47 +18,57 @@ is not optimized for dense, information rich UIs.
|
|||||||
|
|
||||||
:root {
|
:root {
|
||||||
/* Custom Theme Colors */
|
/* Custom Theme Colors */
|
||||||
--theia-arduino-light: rgb(0, 102, 102);
|
--theia-arduino-light: rgb(0, 100, 104);
|
||||||
--theia-arduino-light1: rgb(0, 153, 153);
|
--theia-arduino-light1: rgb(23, 161, 165);
|
||||||
--theia-arduino-light2: rgb(218, 226, 228);
|
--theia-arduino-light2: rgb(218, 226, 228);
|
||||||
--theia-arduino-light3: rgb(237, 241, 242);
|
--theia-arduino-light3: rgb(237, 241, 242);
|
||||||
--theia-arduino-terminal: rgb(0, 0, 0);
|
--theia-arduino-terminal: rgb(0, 0, 0);
|
||||||
|
|
||||||
/* Borders: Width and color (bright to dark) */
|
/* Borders: Width and color (bright to dark) */
|
||||||
|
|
||||||
--theia-border-width: 1px;
|
--theia-border-width: 1px;
|
||||||
--theia-panel-border-width: 2px;
|
--theia-panel-border-width: 2px;
|
||||||
--theia-border-color0: var(--md-grey-100);
|
--theia-border-color0: var(--md-grey-100);
|
||||||
--theia-border-color1: var(--md-grey-200);
|
--theia-border-color1: var(--md-grey-200);
|
||||||
--theia-border-color2: var(--md-grey-300);
|
--theia-border-color2: var(--md-grey-300);
|
||||||
--theia-border-color3: var(--md-grey-400);
|
--theia-border-color3: var(--md-grey-400);
|
||||||
|
|
||||||
|
|
||||||
/* UI fonts: Family, size and color (dark to bright)
|
/* UI fonts: Family, size and color (dark to bright)
|
||||||
---------------------------------------------------
|
---------------------------------------------------
|
||||||
The UI font CSS variables are used for the typography all of the Theia
|
The UI font CSS variables are used for the typography all of the Theia
|
||||||
user interface elements that are not directly user-generated content.
|
user interface elements that are not directly user-generated content.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
--theia-ui-font-scale-factor: 1.2;
|
--theia-ui-font-scale-factor: 1.2;
|
||||||
--theia-ui-font-size0: calc(var(--theia-ui-font-size1) / var(--theia-ui-font-scale-factor));
|
--theia-ui-font-size0: calc(var(--theia-ui-font-size1) / var(--theia-ui-font-scale-factor));
|
||||||
--theia-ui-font-size1: 13px;
|
--theia-ui-font-size1: 13px; /* Base font size */
|
||||||
/* Base font size */
|
|
||||||
--theia-ui-font-size2: calc(var(--theia-ui-font-size1) * var(--theia-ui-font-scale-factor));
|
--theia-ui-font-size2: calc(var(--theia-ui-font-size1) * var(--theia-ui-font-scale-factor));
|
||||||
--theia-ui-font-size3: calc(var(--theia-ui-font-size2) * var(--theia-ui-font-scale-factor));
|
--theia-ui-font-size3: calc(var(--theia-ui-font-size2) * var(--theia-ui-font-scale-factor));
|
||||||
--theia-ui-icon-font-size: 14px;
|
--theia-ui-icon-font-size: 14px; /* Ensures px perfect FontAwesome icons */
|
||||||
/* Ensures px perfect FontAwesome icons */
|
|
||||||
--theia-ui-font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
--theia-ui-font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
|
|
||||||
--theia-ui-font-color0: var(--md-grey-900);
|
--theia-ui-font-color0: var(--md-grey-900);
|
||||||
--theia-ui-font-color1: var(--md-grey-700);
|
--theia-ui-font-color1: var(--md-grey-700);
|
||||||
--theia-ui-font-color2: var(--md-grey-500);
|
--theia-ui-font-color2: var(--md-grey-500);
|
||||||
--theia-ui-font-color3: var(--md-grey-300);
|
--theia-ui-font-color3: var(--md-grey-300);
|
||||||
|
|
||||||
/* Special font colors */
|
/* Special font colors */
|
||||||
|
|
||||||
--theia-ui-icon-font-color: #ffffff;
|
--theia-ui-icon-font-color: #ffffff;
|
||||||
--theia-ui-bar-font-color0: var(--theia-ui-font-color0);
|
--theia-ui-bar-font-color0: var(--theia-ui-font-color0);
|
||||||
--theia-ui-bar-font-color1: var(--theia-inverse-ui-font-color0); /* var(--theia-ui-font-color1); */
|
--theia-ui-bar-font-color1: var(--theia-inverse-ui-font-color0); /* var(--theia-ui-font-color1); */
|
||||||
|
|
||||||
/* Use the inverse UI colors against the brand/accent/warn/error colors. */
|
/* Use the inverse UI colors against the brand/accent/warn/error colors. */
|
||||||
|
|
||||||
--theia-inverse-ui-font-color0: rgba(255, 255, 255, 1.0);
|
--theia-inverse-ui-font-color0: rgba(255, 255, 255, 1.0);
|
||||||
--theia-inverse-ui-font-color2: rgba(255, 255, 255, 0.7);
|
--theia-inverse-ui-font-color2: rgba(255, 255, 255, 0.7);
|
||||||
--theia-inverse-ui-font-color3: rgba(255, 255, 255, 0.5);
|
--theia-inverse-ui-font-color3: rgba(255, 255, 255, 0.5);
|
||||||
|
|
||||||
/* Content fonts: Family, size and color (dark to bright)
|
/* Content fonts: Family, size and color (dark to bright)
|
||||||
Content font variables are used for typography of user-generated content.
|
Content font variables are used for typography of user-generated content.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
--theia-content-font-size: 13px;
|
--theia-content-font-size: 13px;
|
||||||
--theia-content-line-height: 1.5;
|
--theia-content-line-height: 1.5;
|
||||||
--theia-content-font-color0: black;
|
--theia-content-font-color0: black;
|
||||||
@@ -71,75 +81,101 @@ is not optimized for dense, information rich UIs.
|
|||||||
--theia-code-font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback";
|
--theia-code-font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback";
|
||||||
--theia-terminal-font-family: monospace;
|
--theia-terminal-font-family: monospace;
|
||||||
--theia-ui-padding: 6px;
|
--theia-ui-padding: 6px;
|
||||||
|
|
||||||
/* Tab Icon Colors */
|
/* Tab Icon Colors */
|
||||||
--theia-tab-icon-color: var(--theia-ui-font-color1);
|
--theia-tab-icon-color: var(--theia-ui-font-color1);
|
||||||
--theia-tab-font-color: #000;
|
--theia-tab-font-color: #000;
|
||||||
|
|
||||||
/* Main layout colors (bright to dark)
|
/* Main layout colors (bright to dark)
|
||||||
------------------------------------ */
|
------------------------------------ */
|
||||||
|
|
||||||
--theia-layout-color0: #ffffff;
|
--theia-layout-color0: #ffffff;
|
||||||
--theia-layout-color1: var(--theia-arduino-light1);
|
--theia-layout-color1: #f7f9f9;
|
||||||
--theia-layout-color2: #ececec;
|
--theia-layout-color2: #ececec;
|
||||||
--theia-layout-color3: var(--theia-arduino-light);
|
--theia-layout-color3: var(--theia-arduino-light2);
|
||||||
--theia-layout-color4: #dcdcdc;
|
--theia-layout-color4: #dcdcdc;
|
||||||
|
|
||||||
/* Brand colors */
|
/* Brand colors */
|
||||||
--theia-brand-color0: var(--md-blue-700);
|
|
||||||
--theia-brand-color1: var(--md-blue-500);
|
--theia-brand-color0: var(--theia-arduino-light);
|
||||||
--theia-brand-color2: var(--md-blue-300);
|
--theia-brand-color1: var(--theia-arduino-light1);
|
||||||
--theia-brand-color3: var(--md-blue-100);
|
--theia-brand-color2: var(--theia-arduino-light2);
|
||||||
|
--theia-brand-color3: var(--theia-arduino-light3);
|
||||||
|
|
||||||
/* Secondary Brand colors */
|
/* Secondary Brand colors */
|
||||||
|
|
||||||
--theia-secondary-brand-color0: var(--md-grey-700);
|
--theia-secondary-brand-color0: var(--md-grey-700);
|
||||||
--theia-secondary-brand-color1: #b5c8c9;
|
--theia-secondary-brand-color1: #b5c8c9;
|
||||||
--theia-secondary-brand-color2: var(--md-grey-300);
|
--theia-secondary-brand-color2: var(--theia-arduino-light2);
|
||||||
--theia-secondary-brand-color3: var(--md-grey-100);
|
--theia-secondary-brand-color3: var(--theia-arduino-light3);
|
||||||
|
|
||||||
/* Accent colors (dark to bright): Use these to create contrast to layout colors. */
|
/* Accent colors (dark to bright): Use these to create contrast to layout colors. */
|
||||||
--theia-accent-color0: rgb(0, 102, 105);
|
|
||||||
--theia-accent-color1: rgb(0, 164, 167, 1.0);
|
--theia-accent-color0: var(--theia-arduino-light);
|
||||||
--theia-accent-color2: rgb(0, 164, 167, 0.8);
|
--theia-accent-color1: rgb(77, 183, 187, 1.0);
|
||||||
--theia-accent-color3: rgb(0, 164, 167, 0.6);
|
--theia-accent-color2: rgb(77, 183, 187, 0.8);
|
||||||
--theia-accent-color4: rgb(0, 164, 167, 0.4);
|
--theia-accent-color3: rgb(77, 183, 187, 0.6);
|
||||||
|
--theia-accent-color4: rgba(77, 183, 187, 0.4);
|
||||||
|
|
||||||
/* Accent colors with opacity */
|
/* Accent colors with opacity */
|
||||||
|
|
||||||
--theia-transparent-accent-color0: hsla(210, 63%, 46%, 0.3);
|
--theia-transparent-accent-color0: hsla(210, 63%, 46%, 0.3);
|
||||||
--theia-transparent-accent-color1: hsla(207, 66%, 56%, 0.3);
|
--theia-transparent-accent-color1: hsla(207, 66%, 56%, 0.3);
|
||||||
--theia-transparent-accent-color2: hsla(207, 64%, 85%, 0.3);
|
--theia-transparent-accent-color2: hsla(207, 64%, 85%, 0.3);
|
||||||
--theia-transparent-accent-color3: hsla(205, 70%, 91%, 0.3);
|
--theia-transparent-accent-color3: hsla(205, 70%, 91%, 0.3);
|
||||||
|
|
||||||
/* State colors (warn, error, success, info)
|
/* State colors (warn, error, success, info)
|
||||||
------------------------------------------ */
|
------------------------------------------ */
|
||||||
--theia-warn-color0: var(--md-amber-500);
|
--theia-warn-color0: var(--md-amber-500);
|
||||||
--theia-warn-color1: var(--md-amber-400);
|
--theia-warn-color1: var(--md-amber-400);
|
||||||
--theia-warn-color2: var(--md-amber-300);
|
--theia-warn-color2: var(--md-amber-300);
|
||||||
--theia-warn-color3: var(--md-amber-200);
|
--theia-warn-color3: var(--md-amber-200);
|
||||||
|
|
||||||
--theia-warn-font-color0: var(--md-grey-900);
|
--theia-warn-font-color0: var(--md-grey-900);
|
||||||
|
|
||||||
--theia-error-color0: var(--md-red-400);
|
--theia-error-color0: var(--md-red-400);
|
||||||
--theia-error-color1: #da5b4a;
|
--theia-error-color1: #da5b4a;
|
||||||
--theia-error-color2: var(--md-red-200);
|
--theia-error-color2: var(--md-red-200);
|
||||||
--theia-error-color3: var(--md-red-100);
|
--theia-error-color3: var(--md-red-100);
|
||||||
|
|
||||||
--theia-error-font-color0: var(--md-grey-300);
|
--theia-error-font-color0: var(--md-grey-300);
|
||||||
|
|
||||||
--theia-success-color0: var(--md-green-500);
|
--theia-success-color0: var(--md-green-500);
|
||||||
--theia-success-color1: var(--md-green-300);
|
--theia-success-color1: var(--md-green-300);
|
||||||
--theia-success-color2: var(--md-green-100);
|
--theia-success-color2: var(--md-green-100);
|
||||||
--theia-success-color3: var(--md-green-50);
|
--theia-success-color3: var(--md-green-50);
|
||||||
|
|
||||||
--theia-success-font-color0: var(--md-grey-300);
|
--theia-success-font-color0: var(--md-grey-300);
|
||||||
|
|
||||||
--theia-info-color0: var(--md-cyan-700);
|
--theia-info-color0: var(--md-cyan-700);
|
||||||
--theia-info-color1: var(--md-cyan-500);
|
--theia-info-color1: var(--md-cyan-500);
|
||||||
--theia-info-color2: var(--md-cyan-300);
|
--theia-info-color2: var(--md-cyan-300);
|
||||||
--theia-info-color3: var(--md-cyan-100);
|
--theia-info-color3: var(--md-cyan-100);
|
||||||
|
|
||||||
--theia-info-font-color0: var(--md-grey-300);
|
--theia-info-font-color0: var(--md-grey-300);
|
||||||
|
|
||||||
--theia-disabled-color0: var(--md-grey-500);
|
--theia-disabled-color0: var(--md-grey-500);
|
||||||
--theia-disabled-color1: var(--md-grey-300);
|
--theia-disabled-color1: var(--md-grey-300);
|
||||||
--theia-disabled-color2: var(--md-grey-200);
|
--theia-disabled-color2: var(--md-grey-200);
|
||||||
--theia-disabled-color3: var(--md-grey-50);
|
--theia-disabled-color3: var(--md-grey-50);
|
||||||
|
|
||||||
--theia-added-color0: rgba(0, 255, 0, 0.8);
|
--theia-added-color0: rgba(0, 255, 0, 0.8);
|
||||||
--theia-removed-color0: rgba(230, 0, 0, 0.8);
|
--theia-removed-color0: rgba(230, 0, 0, 0.8);
|
||||||
--theia-modified-color0: rgba(0, 100, 150, 0.8);
|
--theia-modified-color0: rgba(0, 100, 150, 0.8);
|
||||||
|
|
||||||
/* Background for selected text */
|
/* Background for selected text */
|
||||||
--theia-selected-text-background: var(--theia-accent-color3);
|
--theia-selected-text-background: var(--theia-accent-color3);
|
||||||
|
|
||||||
/* Colors to highlight words in widgets like tree or editors */
|
/* Colors to highlight words in widgets like tree or editors */
|
||||||
|
|
||||||
--theia-word-highlight-color0: rgba(168, 172, 148, 0.7);
|
--theia-word-highlight-color0: rgba(168, 172, 148, 0.7);
|
||||||
--theia-word-highlight-color1: rgba(253, 255, 0, 0.2);
|
--theia-word-highlight-color1: rgba(253, 255, 0, 0.2);
|
||||||
--theia-word-highlight-match-color0: rgba(234, 92, 0, 0.33);
|
--theia-word-highlight-match-color0: rgba(234, 92, 0, 0.33);
|
||||||
--theia-word-highlight-match-color1: rgba(234, 92, 0, 0.5);
|
--theia-word-highlight-match-color1: rgba(234, 92, 0, 0.5);
|
||||||
--theia-word-highlight-replace-color0: rgba(155, 185, 85, 0.2);
|
--theia-word-highlight-replace-color0: rgba(155, 185, 85, 0.2);
|
||||||
|
|
||||||
/* Scroll-bars */
|
/* Scroll-bars */
|
||||||
|
|
||||||
--theia-scrollbar-width: 10px;
|
--theia-scrollbar-width: 10px;
|
||||||
--theia-scrollbar-rail-width: 10px;
|
--theia-scrollbar-rail-width: 10px;
|
||||||
--theia-scrollbar-thumb-color: hsla(0, 0%, 61%, .4);
|
--theia-scrollbar-thumb-color: hsla(0, 0%, 61%, .4);
|
||||||
@@ -147,7 +183,7 @@ is not optimized for dense, information rich UIs.
|
|||||||
--theia-scrollbar-active-thumb-color: hsla(0, 0%, 39%, .4);
|
--theia-scrollbar-active-thumb-color: hsla(0, 0%, 39%, .4);
|
||||||
--theia-scrollbar-active-rail-color: transparent;
|
--theia-scrollbar-active-rail-color: transparent;
|
||||||
/* Menu */
|
/* Menu */
|
||||||
--theia-menu-color0: var(--theia-layout-color3);
|
--theia-menu-color0: var(--theia-arduino-light);
|
||||||
--theia-menu-color1: var(--theia-layout-color0);
|
--theia-menu-color1: var(--theia-layout-color0);
|
||||||
--theia-menu-color2: #dae3e3;
|
--theia-menu-color2: #dae3e3;
|
||||||
/* Statusbar */
|
/* Statusbar */
|
||||||
@@ -155,9 +191,9 @@ is not optimized for dense, information rich UIs.
|
|||||||
--theia-statusBar-font-color: var(--theia-inverse-ui-font-color0);
|
--theia-statusBar-font-color: var(--theia-inverse-ui-font-color0);
|
||||||
--theia-statusBar-font-size: 12px;
|
--theia-statusBar-font-size: 12px;
|
||||||
/* Buttons */
|
/* Buttons */
|
||||||
--theia-ui-button-color: var(--theia-arduino-light);
|
--theia-ui-button-color: var(--theia-accent-color1);
|
||||||
--theia-ui-button-color-hover: var(--theia-arduino-light1);
|
--theia-ui-button-color-hover: var(--theia-accent-color2);
|
||||||
--theia-ui-button-font-color: var(--theia-inverse-ui-font-color0);
|
--theia-ui-button-font-color: var(--theia-arduino-light);
|
||||||
--theia-ui-button-color-secondary: var(--theia-secondary-brand-color1);
|
--theia-ui-button-color-secondary: var(--theia-secondary-brand-color1);
|
||||||
--theia-ui-button-color-secondary-hover: var(--theia-menu-color2);
|
--theia-ui-button-color-secondary-hover: var(--theia-menu-color2);
|
||||||
--theia-ui-button-font-color-secondary: var(--theia-inverse-ui-font-color0);
|
--theia-ui-button-font-color-secondary: var(--theia-inverse-ui-font-color0);
|
||||||
@@ -197,4 +233,8 @@ is not optimized for dense, information rich UIs.
|
|||||||
--theia-ansi-magenta-background-color: darkmagenta;
|
--theia-ansi-magenta-background-color: darkmagenta;
|
||||||
--theia-ansi-cyan-background-color: darkcyan;
|
--theia-ansi-cyan-background-color: darkcyan;
|
||||||
--theia-ansi-white-background-color: #BDBDBD;
|
--theia-ansi-white-background-color: #BDBDBD;
|
||||||
|
|
||||||
|
/* Output */
|
||||||
|
--theia-output-font-color: var(--theia-ui-font-color3);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -7,11 +7,11 @@ div#select-board-dialog .selectBoardContainer .body {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#select-board-dialog .selectBoardContainer .head {
|
div.dialogContent.select-board-dialog > div.head {
|
||||||
margin-bottom: 10px;
|
padding-left: 21px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#select-board-dialog .selectBoardContainer .head .title {
|
div.dialogContent.select-board-dialog > div.head .title {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
letter-spacing: .02em;
|
letter-spacing: .02em;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
@@ -31,11 +31,11 @@ div#select-board-dialog .selectBoardContainer .body .list .item.selected i{
|
|||||||
color: var(--theia-arduino-light);
|
color: var(--theia-arduino-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .search input,
|
#select-board-dialog .selectBoardContainer .search,
|
||||||
#select-board-dialog .selectBoardContainer .body .boards.list,
|
#select-board-dialog .selectBoardContainer .search input,
|
||||||
#select-board-dialog .selectBoardContainer .body .search,
|
#select-board-dialog .selectBoardContainer .list,
|
||||||
#select-board-dialog .selectBoardContainer .body .ports.list {
|
#select-board-dialog .selectBoardContainer .list {
|
||||||
background: white;
|
background: var(--theia-layout-color0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .search input {
|
#select-board-dialog .selectBoardContainer .body .search input {
|
||||||
@@ -43,7 +43,7 @@ div#select-board-dialog .selectBoardContainer .body .list .item.selected i{
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
max-height: 37px;
|
max-height: 37px;
|
||||||
padding: 10px 8px;
|
padding: 10px 5px 10px 10px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -56,6 +56,7 @@ div#select-board-dialog .selectBoardContainer .body .list .item.selected i{
|
|||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .container {
|
#select-board-dialog .selectBoardContainer .body .container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
padding: 0px 10px 0px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .left.container .content {
|
#select-board-dialog .selectBoardContainer .body .left.container .content {
|
||||||
@@ -68,25 +69,59 @@ div#select-board-dialog .selectBoardContainer .body .list .item.selected i{
|
|||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .container .content .title {
|
#select-board-dialog .selectBoardContainer .body .container .content .title {
|
||||||
color: #7f8c8d;
|
color: #7f8c8d;
|
||||||
margin-bottom: 10px;
|
padding: 0px 0px 10px 0px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
#select-board-dialog .selectBoardContainer .body .container .content .footer {
|
||||||
|
padding: 10px 5px 10px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#select-board-dialog .selectBoardContainer .body .container .content .loading {
|
||||||
|
font-size: var(--theia-ui-font-size1);
|
||||||
|
color: #7f8c8d;
|
||||||
|
padding: 10px 5px 10px 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
/* The max, min-height comes from `.body .list` 265px + 47px top padding - 2 * 10px top padding */
|
||||||
|
max-height: 292px;
|
||||||
|
min-height: 292px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .list .item {
|
#select-board-dialog .selectBoardContainer .body .list .item {
|
||||||
padding: 10px 5px 10px 20px;
|
padding: 10px 5px 10px 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#select-board-dialog .selectBoardContainer .body .list .item .selected-icon {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#select-board-dialog .selectBoardContainer .body .list .item .detail {
|
||||||
|
font-size: var(--theia-ui-font-size1);
|
||||||
|
color: var(--theia-disabled-color0);
|
||||||
|
width: 155px; /* used heuristics for the calculation */
|
||||||
|
white-space: pre;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
#select-board-dialog .selectBoardContainer .body .list .item.missing {
|
||||||
|
color: var(--theia-disabled-color0);
|
||||||
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .list .item:hover {
|
#select-board-dialog .selectBoardContainer .body .list .item:hover {
|
||||||
background: var(--theia-ui-button-color-secondary-hover);
|
background: var(--theia-ui-button-color-secondary-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .list {
|
#select-board-dialog .selectBoardContainer .body .list {
|
||||||
max-height: 265px;
|
max-height: 265px;
|
||||||
|
min-height: 265px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .boards.list {
|
#select-board-dialog .selectBoardContainer .body .ports.list {
|
||||||
min-height: 265px;
|
margin: 47px 0px 0px 0px /* 47 is 37 as input height for the `Boards`, plus 10 margin bottom. */
|
||||||
}
|
}
|
||||||
|
|
||||||
#select-board-dialog .selectBoardContainer .body .search {
|
#select-board-dialog .selectBoardContainer .body .search {
|
||||||
@@ -112,8 +147,8 @@ button.theia-button.secondary {
|
|||||||
|
|
||||||
button.theia-button.main {
|
button.theia-button.main {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #00979c;
|
/* background-color: #00979c; */
|
||||||
box-shadow: 0 4px #005c5f;
|
box-shadow: 0 4px var(--theia-accent-color0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialogControl {
|
.dialogControl {
|
||||||
@@ -149,7 +184,6 @@ button.theia-button.main {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
margin: 0 5px;
|
margin: 0 5px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-weight: bold;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.arduino-boards-toolbar-item .caret {
|
.arduino-boards-toolbar-item .caret {
|
||||||
@@ -158,7 +192,7 @@ button.theia-button.main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.arduino-boards-toolbar-item {
|
.arduino-boards-toolbar-item {
|
||||||
background: white;
|
background: var(--theia-layout-color1);
|
||||||
height: 22px;
|
height: 22px;
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -166,9 +200,9 @@ button.theia-button.main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.arduino-boards-dropdown-list {
|
.arduino-boards-dropdown-list {
|
||||||
background: #f7f7f7;
|
|
||||||
border: 3px solid var(--theia-border-color2);
|
border: 3px solid var(--theia-border-color2);
|
||||||
margin: -3px;
|
margin: -3px;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arduino-boards-dropdown-item {
|
.arduino-boards-dropdown-item {
|
||||||
@@ -176,13 +210,16 @@ button.theia-button.main {
|
|||||||
display: flex;
|
display: flex;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
color: var(--theia-ui-font-color1);
|
||||||
|
background: var(--theia-layout-color1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.arduino-boards-dropdown-item .fa-check {
|
.arduino-boards-dropdown-item .fa-check {
|
||||||
color: var(--theia-accent-color2);
|
color: var(--theia-accent-color1);
|
||||||
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arduino-boards-dropdown-item.selected,
|
.arduino-boards-dropdown-item.selected,
|
||||||
.arduino-boards-dropdown-item:hover {
|
.arduino-boards-dropdown-item:hover {
|
||||||
background: var(--theia-ui-button-color-secondary-hover);
|
background: var(--theia-layout-color3);
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-TabBar-toolbar {
|
#theia-top-panel .p-TabBar-toolbar {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
|
|||||||
13
arduino-ide-extension/src/browser/style/editor.css
Normal file
13
arduino-ide-extension/src/browser/style/editor.css
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/* Do not show the `close` icon for editor, but show the dirty state if not in pro-mode. */
|
||||||
|
body:not(.arduino-advanced-mode) .p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable:hover > .p-TabBar-tabCloseIcon,
|
||||||
|
body:not(.arduino-advanced-mode) .p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-current > .p-TabBar-tabCloseIcon {
|
||||||
|
background-image: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
body:not(.arduino-advanced-mode) .p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable.theia-mod-dirty:hover > .p-TabBar-tabCloseIcon,
|
||||||
|
body:not(.arduino-advanced-mode) .p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable.theia-mod-dirty > .p-TabBar-tabCloseIcon:hover {
|
||||||
|
background-size: 10px;
|
||||||
|
background-image: var(--theia-icon-circle);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
@@ -1,3 +1,43 @@
|
|||||||
@import './list-widget.css';
|
@import './list-widget.css';
|
||||||
@import './board-select-dialog.css';
|
@import './board-select-dialog.css';
|
||||||
@import './main.css';
|
@import './main.css';
|
||||||
|
@import './editor.css';
|
||||||
|
@import './monitor.css';
|
||||||
|
@import './arduino-select.css';
|
||||||
|
|
||||||
|
input:focus {
|
||||||
|
outline-width: 1px;
|
||||||
|
outline-style: solid;
|
||||||
|
outline-offset: -1px;
|
||||||
|
opacity: 1 !important;
|
||||||
|
outline-color: var(--theia-accent-color3);
|
||||||
|
}
|
||||||
|
|
||||||
|
input.warning:focus {
|
||||||
|
outline-width: 1px;
|
||||||
|
outline-style: solid;
|
||||||
|
outline-offset: -1px;
|
||||||
|
opacity: 1 !important;
|
||||||
|
color: var(--theia-warn-font-color0);
|
||||||
|
background-color: var(--theia-warn-color0);
|
||||||
|
}
|
||||||
|
|
||||||
|
input.warning {
|
||||||
|
background-color: var(--theia-warn-color0);
|
||||||
|
}
|
||||||
|
|
||||||
|
input.warning::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
|
||||||
|
color: var(--theia-warn-font-color0);
|
||||||
|
background-color: var(--theia-warn-color0);
|
||||||
|
opacity: 1; /* Firefox */
|
||||||
|
}
|
||||||
|
|
||||||
|
input.warning:-ms-input-placeholder { /* Internet Explorer 10-11 */
|
||||||
|
color: var(--theia-warn-font-color0);
|
||||||
|
background-color: var(--theia-warn-color0);
|
||||||
|
}
|
||||||
|
|
||||||
|
input.warning::-ms-input-placeholder { /* Microsoft Edge */
|
||||||
|
color: var(--theia-warn-font-color0);
|
||||||
|
background-color: var(--theia-warn-color0);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.library-tab-icon {
|
.library-tab-icon {
|
||||||
-webkit-mask: url('library-tab-icon.svg');
|
-webkit-mask: url('../icons/library-tab-icon.svg');
|
||||||
mask: url('library-tab-icon.svg');
|
mask: url('../icons/library-tab-icon.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
.arduino-list-widget {
|
.arduino-list-widget {
|
||||||
@@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
.arduino-list-widget .search-bar {
|
.arduino-list-widget .search-bar {
|
||||||
margin: 0px 10px 10px 15px;
|
margin: 0px 10px 10px 15px;
|
||||||
border-color: var(--theia-border-color3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.arduino-list-widget .search-filters {
|
.arduino-list-widget .search-filters {
|
||||||
@@ -50,17 +49,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.filterable-list-container .items-container > div:nth-child(odd) {
|
.filterable-list-container .items-container > div:nth-child(odd) {
|
||||||
background-color: var(--theia-layout-color2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.filterable-list-container .items-container > div:nth-child(even) {
|
|
||||||
background-color: var(--theia-layout-color0);
|
background-color: var(--theia-layout-color0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.filterable-list-container .items-container > div:hover {
|
.filterable-list-container .items-container > div:nth-child(even) {
|
||||||
background-color: var(--theia-layout-color1);
|
background-color: var(--theia-layout-color1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filterable-list-container .items-container > div:hover {
|
||||||
|
background-color: var(--theia-layout-color2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Perfect scrollbar does not like if we explicitly set the `background-color` of the contained elements.
|
||||||
|
See above: `.filterable-list-container .items-container > div:nth-child(odd|event)`.
|
||||||
|
We have to increase `z-index` of the scroll-bar thumb. Otherwise, the thumb is not visible.
|
||||||
|
https://github.com/arduino/arduino-pro-ide/issues/82 */
|
||||||
|
.arduino-list-widget .ps__rail-y > .ps__thumb-y {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.component-list-item {
|
.component-list-item {
|
||||||
padding: 10px 10px 10px 15px;
|
padding: 10px 10px 10px 15px;
|
||||||
font-size: var(--theia-ui-font-size1);
|
font-size: var(--theia-ui-font-size1);
|
||||||
@@ -108,15 +115,23 @@
|
|||||||
color: var(--theia-ui-font-color2);
|
color: var(--theia-ui-font-color2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.component-list-item .header .installed {
|
.component-list-item .header .installed:before {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
|
display: inline-block;
|
||||||
justify-self: end;
|
justify-self: end;
|
||||||
background-color: var(--theia-accent-color2);
|
background-color: var(--theia-accent-color1);
|
||||||
padding: 2px 4px 2px 4px;
|
padding: 2px 4px 2px 4px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
max-height: calc(1em + 4px);
|
max-height: calc(1em + 4px);
|
||||||
color: var(--theia-inverse-ui-font-color0);
|
color: var(--theia-inverse-ui-font-color0);
|
||||||
|
content: 'INSTALLED';
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-list-item .header .installed:hover:before {
|
||||||
|
background-color: var(--theia-inverse-ui-font-color0);
|
||||||
|
color: var(--theia-accent-color1);
|
||||||
|
content: 'UNINSTALL';
|
||||||
}
|
}
|
||||||
|
|
||||||
.component-list-item[min-width~="170px"] .footer {
|
.component-list-item[min-width~="170px"] .footer {
|
||||||
|
|||||||
@@ -7,56 +7,85 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#outputView {
|
#outputView {
|
||||||
color: var(--theia-ui-font-color3);
|
cursor: text;
|
||||||
}
|
|
||||||
|
|
||||||
#arduino-verify.arduino-tool-icon:hover,
|
|
||||||
#arduino-save-file.arduino-tool-icon:hover,
|
|
||||||
#arduino-show-open-context-menu.arduino-tool-icon:hover,
|
|
||||||
#arduino-upload.arduino-tool-icon:hover {
|
|
||||||
background-position-y: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#arduino-verify.arduino-tool-icon,
|
|
||||||
#arduino-save-file.arduino-tool-icon,
|
|
||||||
#arduino-show-open-context-menu.arduino-tool-icon,
|
|
||||||
#arduino-upload.arduino-tool-icon {
|
|
||||||
height: 24px;
|
|
||||||
width: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#arduino-save-file {
|
|
||||||
background: url(../icons/buttons.svg);
|
|
||||||
background-size: 800%;
|
|
||||||
background-position-y: 28px;
|
|
||||||
background-position-x: 59px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#arduino-verify {
|
|
||||||
background: url(../icons/buttons.svg);
|
|
||||||
background-size: 800%;
|
|
||||||
background-position-y: 28px;
|
|
||||||
background-position-x: 188px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#arduino-upload {
|
|
||||||
background: url(../icons/buttons.svg);
|
|
||||||
background-size: 800%;
|
|
||||||
background-position-y: 28px;
|
|
||||||
background-position-x: 156px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#arduino-show-open-context-menu {
|
|
||||||
background: url(../icons/buttons.svg);
|
|
||||||
background-size: 800%;
|
|
||||||
background-position-y: 28px;
|
|
||||||
background-position-x: 92px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-TabBar-toolbar .item.arduino-tool-item {
|
.p-TabBar-toolbar .item.arduino-tool-item {
|
||||||
margin-left: 3px;
|
margin-left: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-TabBar-toolbar .item.arduino-tool-item > div {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
background: var(--theia-ui-button-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-TabBar-toolbar .item.arduino-tool-item > div:hover {
|
||||||
|
background: var(--theia-ui-button-color-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-verify, .arduino-upload {
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-tool-icon {
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
background: var(--theia-ui-button-font-color);
|
||||||
|
-webkit-mask: url(../icons/mask-buttons.svg);
|
||||||
|
mask: url(../icons/mask-buttons.svg);
|
||||||
|
-webkit-mask-size: 800%;
|
||||||
|
mask-size: 800%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-save-file-icon {
|
||||||
|
-webkit-mask-position: 59px -4px;
|
||||||
|
mask-position: 59px -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-verify-icon {
|
||||||
|
-webkit-mask-position: 188px -4px;
|
||||||
|
mask-position: 188px -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-upload-icon {
|
||||||
|
-webkit-mask-position: 156px -4px;
|
||||||
|
mask-position: 156px -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-show-open-context-menu-icon {
|
||||||
|
-webkit-mask-position: 92px -4px;
|
||||||
|
mask-position: 92px -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-serial-monitor-icon {
|
||||||
|
-webkit-mask-position: 28px -4px;
|
||||||
|
mask-position: 28px -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#arduino-toolbar-container {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-TabBar-toolbar.theia-arduino-toolbar {
|
||||||
|
flex: 1;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#theia-top-panel .p-TabBar-toolbar.theia-arduino-toolbar.right {
|
||||||
|
justify-content: flex-start;
|
||||||
|
min-width: 190px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#theia-top-panel .p-TabBar-toolbar.theia-arduino-toolbar.left {
|
||||||
|
min-width: 398px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
.arduino-tool-item.item.connected-boards {
|
.arduino-tool-item.item.connected-boards {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
@@ -75,10 +104,6 @@
|
|||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-Widget.p-TabBar.theia-app-centers.theia-app-bottom .p-TabBar-content-container.ps {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arduino-toolbar-tooltip {
|
.arduino-toolbar-tooltip {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -86,6 +111,24 @@
|
|||||||
color: var(--theia-ui-font-color3);
|
color: var(--theia-ui-font-color3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-TabBar-toolbar .item > div.arduino-toggle-advanced-mode {
|
||||||
|
display: flex;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arduino-toggle-advanced-mode-icon {
|
||||||
|
mask: none;
|
||||||
|
-webkit-mask: none;
|
||||||
|
background: none;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--theia-ui-button-font-color);
|
||||||
|
}
|
||||||
|
|
||||||
.monaco-editor .margin {
|
.monaco-editor .margin {
|
||||||
border-right: 2px solid var(--theia-border-color1);
|
border-right: 2px solid var(--theia-border-color1);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|||||||
63
arduino-ide-extension/src/browser/style/monitor.css
Normal file
63
arduino-ide-extension/src/browser/style/monitor.css
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
.p-TabBar.theia-app-centers .p-TabBar-tabIcon.arduino-serial-monitor-tab-icon {
|
||||||
|
background: url(../icons/buttons.svg);
|
||||||
|
background-size: 800%;
|
||||||
|
background-position-y: 41px;
|
||||||
|
background-position-x: 19px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor .head {
|
||||||
|
display: flex;
|
||||||
|
padding: 5px;
|
||||||
|
background: var(--theia-layout-color0);
|
||||||
|
height: 27px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor .head .send {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor .head .send > input {
|
||||||
|
line-height: var(--theia-content-line-height);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor .head .send > input:focus {
|
||||||
|
border-color: var(--theia-accent-color3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor .head .config {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor .head .config .select {
|
||||||
|
margin-left: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor .body {
|
||||||
|
overflow: auto;
|
||||||
|
flex: 1;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-TabBar-toolbar .item.arduino-monitor {
|
||||||
|
width: 24px;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: medium;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-TabBar-toolbar .item.arduino-monitor.toggled {
|
||||||
|
background: var(--theia-secondary-brand-color1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-TabBar-toolbar .item .clear-all {
|
||||||
|
background: var(--theia-icon-clear) no-repeat;
|
||||||
|
}
|
||||||
@@ -13,11 +13,11 @@ export class ToolOutputServiceClientImpl implements ToolOutputServiceClient {
|
|||||||
protected readonly outputContribution: OutputContribution;
|
protected readonly outputContribution: OutputContribution;
|
||||||
|
|
||||||
onNewOutput(tool: string, chunk: string): void {
|
onNewOutput(tool: string, chunk: string): void {
|
||||||
this.outputContribution.openView({ reveal: true }).then(() => {
|
this.outputContribution.openView({ activate: true }).then(() => {
|
||||||
const channel = this.outputChannelManager.getChannel(`Arduino: ${tool}`);
|
const channel = this.outputChannelManager.getChannel(`Arduino: ${tool}`);
|
||||||
channel.setVisibility(true);
|
channel.setVisibility(true);
|
||||||
channel.append(chunk);
|
channel.append(chunk);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,26 +1,45 @@
|
|||||||
import { FrontendApplicationContribution, FrontendApplication } from "@theia/core/lib/browser";
|
import { FrontendApplicationContribution, FrontendApplication, Widget, Message } from "@theia/core/lib/browser";
|
||||||
import { injectable, inject } from "inversify";
|
import { injectable, inject } from "inversify";
|
||||||
import { ArduinoToolbar } from "./arduino-toolbar";
|
import { ArduinoToolbar } from "./arduino-toolbar";
|
||||||
import { TabBarToolbarRegistry } from "@theia/core/lib/browser/shell/tab-bar-toolbar";
|
import { TabBarToolbarRegistry } from "@theia/core/lib/browser/shell/tab-bar-toolbar";
|
||||||
import { CommandRegistry } from "@theia/core";
|
import { CommandRegistry } from "@theia/core";
|
||||||
import { LabelParser } from "@theia/core/lib/browser/label-parser";
|
import { LabelParser } from "@theia/core/lib/browser/label-parser";
|
||||||
|
|
||||||
|
export class ArduinoToolbarContainer extends Widget {
|
||||||
|
|
||||||
|
protected toolbars: ArduinoToolbar[];
|
||||||
|
|
||||||
|
constructor(...toolbars: ArduinoToolbar[]) {
|
||||||
|
super();
|
||||||
|
this.id = 'arduino-toolbar-container';
|
||||||
|
this.toolbars = toolbars;
|
||||||
|
}
|
||||||
|
|
||||||
|
onAfterAttach(msg: Message) {
|
||||||
|
for (const toolbar of this.toolbars) {
|
||||||
|
Widget.attach(toolbar, this.node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class ArduinoToolbarContribution implements FrontendApplicationContribution {
|
export class ArduinoToolbarContribution implements FrontendApplicationContribution {
|
||||||
|
|
||||||
protected toolbarWidget: ArduinoToolbar;
|
protected arduinoToolbarContainer: ArduinoToolbarContainer;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@inject(TabBarToolbarRegistry) protected tabBarToolBarRegistry: TabBarToolbarRegistry,
|
@inject(TabBarToolbarRegistry) protected tabBarToolBarRegistry: TabBarToolbarRegistry,
|
||||||
@inject(CommandRegistry) protected commandRegistry: CommandRegistry,
|
@inject(CommandRegistry) protected commandRegistry: CommandRegistry,
|
||||||
@inject(LabelParser) protected labelParser: LabelParser) {
|
@inject(LabelParser) protected labelParser: LabelParser) {
|
||||||
this.toolbarWidget = new ArduinoToolbar(tabBarToolBarRegistry, commandRegistry, labelParser);
|
const leftToolbarWidget = new ArduinoToolbar(tabBarToolBarRegistry, commandRegistry, labelParser, 'left');
|
||||||
|
const rightToolbarWidget = new ArduinoToolbar(tabBarToolBarRegistry, commandRegistry, labelParser, 'right');
|
||||||
|
this.arduinoToolbarContainer = new ArduinoToolbarContainer(leftToolbarWidget, rightToolbarWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onStart(app: FrontendApplication) {
|
onStart(app: FrontendApplication) {
|
||||||
app.shell.addWidget(this.toolbarWidget, {
|
app.shell.addWidget(this.arduinoToolbarContainer, {
|
||||||
area: 'top'
|
area: 'top'
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,14 +2,16 @@ import * as React from 'react';
|
|||||||
import { TabBarToolbar, TabBarToolbarRegistry, TabBarToolbarItem, ReactTabBarToolbarItem } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
import { TabBarToolbar, TabBarToolbarRegistry, TabBarToolbarItem, ReactTabBarToolbarItem } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||||
import { CommandRegistry } from '@theia/core/lib/common/command';
|
import { CommandRegistry } from '@theia/core/lib/common/command';
|
||||||
import { ReactWidget } from '@theia/core/lib/browser';
|
import { ReactWidget } from '@theia/core/lib/browser';
|
||||||
import { LabelParser } from '@theia/core/lib/browser/label-parser';
|
import { LabelParser, LabelIcon } from '@theia/core/lib/browser/label-parser';
|
||||||
|
|
||||||
export const ARDUINO_TOOLBAR_ITEM_CLASS = 'arduino-tool-item';
|
export const ARDUINO_TOOLBAR_ITEM_CLASS = 'arduino-tool-item';
|
||||||
|
|
||||||
export namespace ArduinoToolbarComponent {
|
export namespace ArduinoToolbarComponent {
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
side: 'left' | 'right',
|
||||||
items: (TabBarToolbarItem | ReactTabBarToolbarItem)[],
|
items: (TabBarToolbarItem | ReactTabBarToolbarItem)[],
|
||||||
commands: CommandRegistry,
|
commands: CommandRegistry,
|
||||||
|
labelParser: LabelParser,
|
||||||
commandIsEnabled: (id: string) => boolean,
|
commandIsEnabled: (id: string) => boolean,
|
||||||
executeCommand: (e: React.MouseEvent<HTMLElement>) => void
|
executeCommand: (e: React.MouseEvent<HTMLElement>) => void
|
||||||
}
|
}
|
||||||
@@ -26,14 +28,24 @@ export class ArduinoToolbarComponent extends React.Component<ArduinoToolbarCompo
|
|||||||
|
|
||||||
protected renderItem = (item: TabBarToolbarItem) => {
|
protected renderItem = (item: TabBarToolbarItem) => {
|
||||||
let innerText = '';
|
let innerText = '';
|
||||||
|
let className = `arduino-tool-icon ${item.id}-icon`;
|
||||||
|
if (item.text) {
|
||||||
|
for (const labelPart of this.props.labelParser.parse(item.text)) {
|
||||||
|
if (typeof labelPart !== 'string' && LabelIcon.is(labelPart)) {
|
||||||
|
className += ` fa fa-${labelPart.name}`;
|
||||||
|
} else {
|
||||||
|
innerText = labelPart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
const command = this.props.commands.getCommand(item.command);
|
const command = this.props.commands.getCommand(item.command);
|
||||||
const cls = `${ARDUINO_TOOLBAR_ITEM_CLASS} ${TabBarToolbar.Styles.TAB_BAR_TOOLBAR_ITEM} ${command && this.props.commandIsEnabled(command.id) ? ' enabled' : ''}`
|
const cls = `${ARDUINO_TOOLBAR_ITEM_CLASS} ${TabBarToolbar.Styles.TAB_BAR_TOOLBAR_ITEM} ${command && this.props.commandIsEnabled(command.id) ? ' enabled' : ''}`
|
||||||
return <div key={item.id}
|
return <div key={item.id} className={cls} >
|
||||||
className={cls} >
|
<div className={item.id}>
|
||||||
<div
|
<div
|
||||||
key={item.id + '-icon'}
|
key={item.id + '-icon'}
|
||||||
id={item.id}
|
id={item.id}
|
||||||
className={`${item.id} arduino-tool-icon`}
|
className={className}
|
||||||
onClick={this.props.executeCommand}
|
onClick={this.props.executeCommand}
|
||||||
onMouseOver={() => this.setState({ tooltip: item.tooltip || '' })}
|
onMouseOver={() => this.setState({ tooltip: item.tooltip || '' })}
|
||||||
onMouseOut={() => this.setState({ tooltip: '' })}
|
onMouseOut={() => this.setState({ tooltip: '' })}
|
||||||
@@ -41,13 +53,22 @@ export class ArduinoToolbarComponent extends React.Component<ArduinoToolbarCompo
|
|||||||
{innerText}
|
{innerText}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): React.ReactNode {
|
render(): React.ReactNode {
|
||||||
return <React.Fragment>
|
const tooltip = <div key='arduino-toolbar-tooltip' className={'arduino-toolbar-tooltip'}>{this.state.tooltip}</div>;
|
||||||
<div key='arduino-toolbar-tooltip' className={'arduino-toolbar-tooltip'}>{this.state.tooltip}</div>
|
const items = [
|
||||||
|
<React.Fragment key={this.props.side + '-arduino-toolbar-tooltip'}>
|
||||||
{[...this.props.items].map(item => TabBarToolbarItem.is(item) ? this.renderItem(item) : item.render())}
|
{[...this.props.items].map(item => TabBarToolbarItem.is(item) ? this.renderItem(item) : item.render())}
|
||||||
</React.Fragment>;
|
</React.Fragment>
|
||||||
|
]
|
||||||
|
if (this.props.side === 'left') {
|
||||||
|
items.unshift(tooltip);
|
||||||
|
} else {
|
||||||
|
items.push(tooltip)
|
||||||
|
}
|
||||||
|
return items;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,10 +79,11 @@ export class ArduinoToolbar extends ReactWidget {
|
|||||||
constructor(
|
constructor(
|
||||||
protected readonly tabBarToolbarRegistry: TabBarToolbarRegistry,
|
protected readonly tabBarToolbarRegistry: TabBarToolbarRegistry,
|
||||||
protected readonly commands: CommandRegistry,
|
protected readonly commands: CommandRegistry,
|
||||||
protected readonly labelParser: LabelParser
|
protected readonly labelParser: LabelParser,
|
||||||
|
public readonly side: 'left' | 'right'
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.id = 'arduino-toolbar';
|
this.id = side + '-arduino-toolbar';
|
||||||
this.addClass(TabBarToolbar.Styles.TAB_BAR_TOOLBAR);
|
this.addClass(TabBarToolbar.Styles.TAB_BAR_TOOLBAR);
|
||||||
this.init();
|
this.init();
|
||||||
this.tabBarToolbarRegistry.onDidChange(() => this.updateToolbar());
|
this.tabBarToolbarRegistry.onDidChange(() => this.updateToolbar());
|
||||||
@@ -82,7 +104,7 @@ export class ArduinoToolbar extends ReactWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected init(): void {
|
protected init(): void {
|
||||||
this.node.classList.add('theia-arduino-toolbar');
|
this.node.classList.add('theia-arduino-toolbar', this.side);
|
||||||
this.update();
|
this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,6 +115,9 @@ export class ArduinoToolbar extends ReactWidget {
|
|||||||
|
|
||||||
protected render(): React.ReactNode {
|
protected render(): React.ReactNode {
|
||||||
return <ArduinoToolbarComponent
|
return <ArduinoToolbarComponent
|
||||||
|
key='arduino-toolbar-component'
|
||||||
|
side={this.side}
|
||||||
|
labelParser={this.labelParser}
|
||||||
items={[...this.items.values()]}
|
items={[...this.items.values()]}
|
||||||
commands={this.commands}
|
commands={this.commands}
|
||||||
commandIsEnabled={this.doCommandIsEnabled}
|
commandIsEnabled={this.doCommandIsEnabled}
|
||||||
@@ -107,3 +132,9 @@ export class ArduinoToolbar extends ReactWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export namespace ArduinoToolbar {
|
||||||
|
export function is(maybeToolbarWidget: any): maybeToolbarWidget is ArduinoToolbar {
|
||||||
|
return maybeToolbarWidget instanceof ArduinoToolbar;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Installable } from './installable';
|
||||||
|
|
||||||
export interface ArduinoComponent {
|
export interface ArduinoComponent {
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
@@ -6,8 +7,8 @@ export interface ArduinoComponent {
|
|||||||
readonly description: string;
|
readonly description: string;
|
||||||
readonly moreInfoLink?: string;
|
readonly moreInfoLink?: string;
|
||||||
|
|
||||||
readonly availableVersions: string[];
|
readonly availableVersions: Installable.Version[];
|
||||||
readonly installable: boolean;
|
readonly installable: boolean;
|
||||||
|
|
||||||
readonly installedVersion?: string;
|
readonly installedVersion?: Installable.Version;
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user