diff --git a/.github/workflows/check-i18n-task.yml b/.github/workflows/check-i18n-task.yml new file mode 100644 index 000000000..121a9a844 --- /dev/null +++ b/.github/workflows/check-i18n-task.yml @@ -0,0 +1,38 @@ +name: Check Internationalization + +# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - '.github/workflows/check-i18n-task.ya?ml' + - '**/package.json' + - '**.ts' + - 'i18n/**' + pull_request: + paths: + - '.github/workflows/check-i18n-task.ya?ml' + - '**/package.json' + - '**.ts' + - 'i18n/**' + workflow_dispatch: + repository_dispatch: + +jobs: + check: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install Node.js 12.x + uses: actions/setup-node@v2 + with: + node-version: '12.14.1' + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + run: yarn + + - name: Check for errors + run: yarn i18n:check diff --git a/.github/workflows/i18n-nightly-push.yml b/.github/workflows/i18n-nightly-push.yml new file mode 100644 index 000000000..c62f16a7f --- /dev/null +++ b/.github/workflows/i18n-nightly-push.yml @@ -0,0 +1,30 @@ +name: i18n-nightly-push + +on: + schedule: + # run every day at 1AM + - cron: '0 1 * * *' + +jobs: + push-to-transifex: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Node.js 12.x + uses: actions/setup-node@v2 + with: + node-version: '12.14.1' + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + run: yarn + + - name: Run i18n:push script + run: yarn run i18n:push + env: + TRANSIFEX_ORGANIZATION: ${{ secrets.TRANSIFEX_ORGANIZATION }} + TRANSIFEX_PROJECT: ${{ secrets.TRANSIFEX_PROJECT }} + TRANSIFEX_RESOURCE: ${{ secrets.TRANSIFEX_RESOURCE }} + TRANSIFEX_API_KEY: ${{ secrets.TRANSIFEX_API_KEY }} diff --git a/.github/workflows/i18n-weekly-pull.yml b/.github/workflows/i18n-weekly-pull.yml new file mode 100644 index 000000000..1a361febe --- /dev/null +++ b/.github/workflows/i18n-weekly-pull.yml @@ -0,0 +1,38 @@ +name: i18n-weekly-pull + +on: + schedule: + # run every monday at 2AM + - cron: '0 2 * * 1' + +jobs: + pull-from-transifex: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Node.js 12.x + uses: actions/setup-node@v2 + with: + node-version: '12.14.1' + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + run: yarn + + - name: Run i18n:pull script + run: yarn run i18n:pull + env: + TRANSIFEX_ORGANIZATION: ${{ secrets.TRANSIFEX_ORGANIZATION }} + TRANSIFEX_PROJECT: ${{ secrets.TRANSIFEX_PROJECT }} + TRANSIFEX_RESOURCE: ${{ secrets.TRANSIFEX_RESOURCE }} + TRANSIFEX_API_KEY: ${{ secrets.TRANSIFEX_API_KEY }} + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v3 + with: + commit-message: Updated translation files + title: Update translation files + branch: i18n/translations-update + author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index d6252692a..f4c245eaf 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -92,8 +92,14 @@ jobs: - name: Determine whether to dry run id: dry-run if: > - github.event == 'pull_request' || - github.ref != format('refs/heads/{0}', github.event.repository.default_branch) + github.event_name == 'pull_request' || + ( + ( + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' + ) && + github.ref != format('refs/heads/{0}', github.event.repository.default_branch) + ) run: | # Use of this flag in the github-label-sync command will cause it to only check the validity of the # configuration. diff --git a/arduino-ide-extension/README.md b/arduino-ide-extension/README.md index 1758bfc7a..ff25fd6eb 100644 --- a/arduino-ide-extension/README.md +++ b/arduino-ide-extension/README.md @@ -30,17 +30,20 @@ The Core Service is responsible for building your sketches and uploading them to - compiling a sketch for a selected board type - uploading a sketch to a connected board -#### Monitor Service +#### Serial Service -The Monitor Service allows getting information back from sketches running on your Arduino boards. +The Serial 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/monitor-service-impl.ts](./src/node/monitor/monitor-service-impl.ts) implements the service backend: +- [src/common/protocol/serial-service.ts](./src/common/protocol/serial-service.ts) implements the common classes and interfaces +- [src/node/serial/serial-service-impl.ts](./src/node/serial/serial-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: +- [src/browser/serial/serial-connection-manager.ts](./src/browser/serial/serial-connection-manager.ts) handles the serial connection in the frontend +- [src/browser/serial/monitor/monitor-widget.tsx](./src/browser/serial/monitor/monitor-widget.tsx) implements the serial monitor front-end: - viewing the output from a connected board - entering data to send to the board +- [src/browser/serial/plotter/plotter-frontend-contribution.ts](./src/browser/serial/plotter/plotter-frontend-contribution.ts) implements the serial plotter front-end: + - opening a new window running the [Serial Plotter Web App](https://github.com/arduino/arduino-serial-plotter-webapp) #### Config Service diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index 6bb9166b8..9a46e057e 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -1,13 +1,14 @@ { "name": "arduino-ide-extension", - "version": "2.0.0-beta.12", + "version": "2.0.0-rc1", "description": "An extension for Theia building the Arduino IDE", "license": "AGPL-3.0-or-later", "scripts": { - "prepare": "yarn download-cli && yarn download-fwuploader && yarn download-ls && yarn clean && yarn download-examples && yarn build && yarn test", + "prepare": "yarn download-cli && yarn download-fwuploader && yarn download-ls && yarn copy-serial-plotter && yarn clean && yarn download-examples && yarn build && yarn test", "clean": "rimraf lib", "download-cli": "node ./scripts/download-cli.js", "download-fwuploader": "node ./scripts/download-fwuploader.js", + "copy-serial-plotter": "npx ncp ../node_modules/arduino-serial-plotter-webapp ./build/arduino-serial-plotter-webapp", "download-ls": "node ./scripts/download-ls.js", "download-examples": "node ./scripts/download-examples.js", "generate-protocol": "node ./scripts/generate-protocol.js", @@ -18,23 +19,24 @@ "test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\"" }, "dependencies": { + "arduino-serial-plotter-webapp": "0.0.15", "@grpc/grpc-js": "^1.3.7", - "@theia/application-package": "1.18.0", - "@theia/core": "1.18.0", - "@theia/editor": "1.18.0", - "@theia/editor-preview": "1.18.0", - "@theia/filesystem": "1.18.0", - "@theia/git": "1.18.0", - "@theia/keymaps": "1.18.0", - "@theia/markers": "1.18.0", - "@theia/monaco": "1.18.0", - "@theia/navigator": "1.18.0", - "@theia/outline-view": "1.18.0", - "@theia/output": "1.18.0", - "@theia/preferences": "1.18.0", - "@theia/search-in-workspace": "1.18.0", - "@theia/terminal": "1.18.0", - "@theia/workspace": "1.18.0", + "@theia/application-package": "1.19.0", + "@theia/core": "1.19.0", + "@theia/editor": "1.19.0", + "@theia/editor-preview": "1.19.0", + "@theia/filesystem": "1.19.0", + "@theia/git": "1.19.0", + "@theia/keymaps": "1.19.0", + "@theia/markers": "1.19.0", + "@theia/monaco": "1.19.0", + "@theia/navigator": "1.19.0", + "@theia/outline-view": "1.19.0", + "@theia/output": "1.19.0", + "@theia/preferences": "1.19.0", + "@theia/search-in-workspace": "1.19.0", + "@theia/terminal": "1.19.0", + "@theia/workspace": "1.19.0", "@tippyjs/react": "^4.2.5", "@types/atob": "^2.1.2", "@types/auth0-js": "^9.14.0", @@ -77,6 +79,7 @@ "open": "^8.0.6", "p-queue": "^5.0.0", "ps-tree": "^1.2.0", + "query-string": "^7.0.1", "react-disable": "^0.1.0", "react-select": "^3.0.4", "react-tabs": "^3.1.2", @@ -146,7 +149,7 @@ ], "arduino": { "cli": { - "version": "0.19.1" + "version": "0.20.1" }, "fwuploader": { "version": "2.0.0" diff --git a/arduino-ide-extension/scripts/download-ls.js b/arduino-ide-extension/scripts/download-ls.js index 0d2bfdc06..6e53f9dcc 100755 --- a/arduino-ide-extension/scripts/download-ls.js +++ b/arduino-ide-extension/scripts/download-ls.js @@ -4,69 +4,78 @@ // - https://downloads.arduino.cc/arduino-language-server/clangd/clangd_${VERSION}_${SUFFIX} (() => { + const DEFAULT_ALS_VERSION = '0.5.0-rc2'; + const DEFAULT_CLANGD_VERSION = 'snapshot_20210124'; - const DEFAULT_ALS_VERSION = 'nightly'; - const DEFAULT_CLANGD_VERSION = 'snapshot_20210124'; + const path = require('path'); + const shell = require('shelljs'); + const downloader = require('./downloader'); - 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, + 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: ['snapshot_20210124'], + 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 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: ['snapshot_20210124'], - 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 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 lsExecutablePath = path.join( + build, + `arduino-language-server${platform === 'win32' ? '.exe' : ''}` + ); - const build = path.join(__dirname, '..', 'build'); - const lsExecutablePath = path.join(build, `arduino-language-server${platform === 'win32' ? '.exe' : ''}`); + let clangdExecutablePath, lsSuffix, clangdPrefix; + switch (platform) { + case 'darwin': + clangdExecutablePath = path.join(build, 'bin', 'clangd'); + lsSuffix = 'macOS_64bit.tar.gz'; + clangdPrefix = 'mac'; + break; + case 'linux': + clangdExecutablePath = path.join(build, 'bin', 'clangd'); + lsSuffix = 'Linux_64bit.tar.gz'; + clangdPrefix = 'linux'; + break; + case 'win32': + clangdExecutablePath = path.join(build, 'bin', 'clangd.exe'); + lsSuffix = 'Windows_64bit.zip'; + clangdPrefix = 'windows'; + break; + } + if (!lsSuffix) { + shell.echo( + `The arduino-language-server is not available for ${platform} ${arch}.` + ); + shell.exit(1); + } - let clangdExecutablePath, lsSuffix, clangdPrefix; - switch (platform) { - case 'darwin': - clangdExecutablePath = path.join(build, 'bin', 'clangd') - lsSuffix = 'macOS_amd64.zip'; - clangdPrefix = 'mac'; - break; - case 'linux': - clangdExecutablePath = path.join(build, 'bin', 'clangd') - lsSuffix = 'Linux_amd64.zip'; - clangdPrefix = 'linux' - break; - case 'win32': - clangdExecutablePath = path.join(build, 'bin', 'clangd.exe') - lsSuffix = 'Windows_amd64.zip'; - clangdPrefix = 'windows'; - break; - } - if (!lsSuffix) { - 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}_${lsSuffix}`; - downloader.downloadUnzipAll(alsUrl, build, lsExecutablePath, force); - - const clangdUrl = `https://downloads.arduino.cc/arduino-language-server/clangd/clangd-${clangdPrefix}-${clangdVersion}.zip`; - downloader.downloadUnzipAll(clangdUrl, build, clangdExecutablePath, force, { strip: 1 }); // `strip`: the new clangd (12.x) is zipped into a folder, so we have to strip the outmost folder. + const alsUrl = `https://downloads.arduino.cc/arduino-language-server/${ + alsVersion === 'nightly' + ? 'nightly/arduino-language-server' + : 'arduino-language-server_' + alsVersion + }_${lsSuffix}`; + downloader.downloadUnzipAll(alsUrl, build, lsExecutablePath, force); + const clangdUrl = `https://downloads.arduino.cc/arduino-language-server/clangd/clangd-${clangdPrefix}-${clangdVersion}.zip`; + downloader.downloadUnzipAll(clangdUrl, build, clangdExecutablePath, force, { + strip: 1, + }); // `strip`: the new clangd (12.x) is zipped into a folder, so we have to strip the outmost folder. })(); diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index 5106640b8..3cfc49fd8 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -1,21 +1,29 @@ +import { inject, injectable, postConstruct } from 'inversify'; +import * as React from 'react'; +import { remote } from 'electron'; +import { + BoardsService, + Port, + SketchesService, + ExecutableService, + Sketch, +} from '../common/protocol'; import { Mutex } from 'async-mutex'; import { MAIN_MENU_BAR, MenuContribution, MenuModelRegistry, - SelectionService, ILogger, DisposableCollection, } from '@theia/core'; import { - ContextMenuRenderer, FrontendApplication, FrontendApplicationContribution, LocalStorageService, - OpenerService, StatusBar, StatusBarAlignment, } from '@theia/core/lib/browser'; +import { nls } from '@theia/core/lib/common'; import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution'; import { ColorRegistry } from '@theia/core/lib/browser/color-registry'; import { CommonMenus } from '@theia/core/lib/browser/common-frontend-contribution'; @@ -34,7 +42,6 @@ import { EditorManager, EditorOpenerOptions, } from '@theia/editor/lib/browser'; -import { FileDialogService } from '@theia/filesystem/lib/browser/file-dialog'; import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution'; import { MonacoMenus } from '@theia/monaco/lib/browser/monaco-menu'; import { FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution'; @@ -43,41 +50,23 @@ import { OutputContribution } from '@theia/output/lib/browser/output-contributio import { ScmContribution } from '@theia/scm/lib/browser/scm-contribution'; import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution'; import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-contribution'; -import { inject, injectable, postConstruct } from 'inversify'; -import * as React from 'react'; -import { remote } from 'electron'; -import { MainMenuManager } from '../common/main-menu-manager'; -import { - BoardsService, - CoreService, - Port, - SketchesService, - ExecutableService, - Sketch, -} from '../common/protocol'; -import { ArduinoDaemon } from '../common/protocol/arduino-daemon'; +import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin'; +import { FileService } from '@theia/filesystem/lib/browser/file-service'; +import { FileChangeType } from '@theia/filesystem/lib/browser'; +import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; import { ConfigService } from '../common/protocol/config-service'; -import { FileSystemExt } from '../common/protocol/filesystem-ext'; import { ArduinoCommands } from './arduino-commands'; import { BoardsConfig } from './boards/boards-config'; import { BoardsConfigDialog } from './boards/boards-config-dialog'; -import { BoardsDataStore } from './boards/boards-data-store'; import { BoardsServiceProvider } from './boards/boards-service-provider'; import { BoardsToolBarItem } from './boards/boards-toolbar-item'; import { EditorMode } from './editor-mode'; import { ArduinoMenus } from './menu/arduino-menus'; -import { MonitorConnection } from './monitor/monitor-connection'; -import { MonitorViewContribution } from './monitor/monitor-view-contribution'; -import { WorkspaceService } from './theia/workspace/workspace-service'; +import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution'; import { ArduinoToolbar } from './toolbar/arduino-toolbar'; -import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin'; -import { FileService } from '@theia/filesystem/lib/browser/file-service'; -import { ResponseService } from '../common/protocol/response-service'; import { ArduinoPreferences } from './arduino-preferences'; import { SketchesServiceClientImpl } from '../common/protocol/sketches-service-client-impl'; import { SaveAsSketch } from './contributions/save-as-sketch'; -import { FileChangeType } from '@theia/filesystem/lib/browser'; -import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; import { SketchbookWidgetContribution } from './widgets/sketchbook/sketchbook-widget-contribution'; const INIT_AVR_PACKAGES = 'initializedAvrPackages'; @@ -100,24 +89,12 @@ export class ArduinoFrontendContribution @inject(BoardsService) protected readonly boardsService: BoardsService; - @inject(CoreService) - protected readonly coreService: CoreService; - @inject(BoardsServiceProvider) protected readonly boardsServiceClientImpl: BoardsServiceProvider; - @inject(SelectionService) - protected readonly selectionService: SelectionService; - @inject(EditorManager) protected readonly editorManager: EditorManager; - @inject(ContextMenuRenderer) - protected readonly contextMenuRenderer: ContextMenuRenderer; - - @inject(FileDialogService) - protected readonly fileDialogService: FileDialogService; - @inject(FileService) protected readonly fileService: FileService; @@ -127,21 +104,12 @@ export class ArduinoFrontendContribution @inject(BoardsConfigDialog) protected readonly boardsConfigDialog: BoardsConfigDialog; - @inject(MenuModelRegistry) - protected readonly menuRegistry: MenuModelRegistry; - @inject(CommandRegistry) protected readonly commandRegistry: CommandRegistry; @inject(StatusBar) protected readonly statusBar: StatusBar; - @inject(WorkspaceService) - protected readonly workspaceService: WorkspaceService; - - @inject(MonitorConnection) - protected readonly monitorConnection: MonitorConnection; - @inject(FileNavigatorContribution) protected readonly fileNavigatorContributions: FileNavigatorContribution; @@ -166,40 +134,21 @@ export class ArduinoFrontendContribution @inject(EditorMode) protected readonly editorMode: EditorMode; - @inject(ArduinoDaemon) - protected readonly daemon: ArduinoDaemon; - - @inject(OpenerService) - protected readonly openerService: OpenerService; - @inject(ConfigService) protected readonly configService: ConfigService; - @inject(BoardsDataStore) - protected readonly boardsDataStore: BoardsDataStore; - - @inject(MainMenuManager) - protected readonly mainMenuManager: MainMenuManager; - - @inject(FileSystemExt) - protected readonly fileSystemExt: FileSystemExt; - @inject(HostedPluginSupport) protected hostedPluginSupport: HostedPluginSupport; @inject(ExecutableService) protected executableService: ExecutableService; - @inject(ResponseService) - protected readonly responseService: ResponseService; - @inject(ArduinoPreferences) protected readonly arduinoPreferences: ArduinoPreferences; @inject(SketchesServiceClientImpl) protected readonly sketchServiceClient: SketchesServiceClientImpl; - @inject(FrontendApplicationStateService) protected readonly appStateService: FrontendApplicationStateService; @inject(LocalStorageService) @@ -225,7 +174,10 @@ export class ArduinoFrontendContribution if (!window.navigator.onLine) { // tslint:disable-next-line:max-line-length this.messageService.warn( - 'You appear to be offline. Without an Internet connection, the Arduino CLI might not be able to download the required resources and could cause malfunction. Please connect to the Internet and restart the application.' + nls.localize( + 'arduino/common/offlineIndicator', + 'You appear to be offline. Without an Internet connection, the Arduino CLI might not be able to download the required resources and could cause malfunction. Please connect to the Internet and restart the application.' + ) ); } const updateStatusBar = ({ @@ -236,15 +188,22 @@ export class ArduinoFrontendContribution alignment: StatusBarAlignment.RIGHT, text: selectedBoard ? `$(microchip) ${selectedBoard.name}` - : '$(close) no board selected', + : `$(close) ${nls.localize( + 'arduino/common/noBoardSelected', + 'No board selected' + )}`, className: 'arduino-selected-board', }); if (selectedBoard) { this.statusBar.setElement('arduino-selected-port', { alignment: StatusBarAlignment.RIGHT, text: selectedPort - ? `on ${Port.toString(selectedPort)}` - : '[not connected]', + ? nls.localize( + 'arduino/common/selectedOn', + 'on {0}', + Port.toString(selectedPort) + ) + : nls.localize('arduino/common/notConnected', '[not connected]'), className: 'arduino-selected-port', }); } @@ -381,15 +340,14 @@ export class ArduinoFrontendContribution ); } } - const { clangdUri, cliUri, lsUri } = await this.executableService.list(); - const [clangdPath, cliPath, lsPath, cliConfigPath] = await Promise.all([ + const { clangdUri, lsUri } = await this.executableService.list(); + const [clangdPath, lsPath] = await Promise.all([ this.fileService.fsPath(new URI(clangdUri)), - this.fileService.fsPath(new URI(cliUri)), this.fileService.fsPath(new URI(lsUri)), - this.fileService.fsPath( - new URI(await this.configService.getCliConfigFileUri()) - ), ]); + + const config = await this.configService.getConfiguration(); + this.languageServerFqbn = await Promise.race([ new Promise((_, reject) => setTimeout( @@ -401,10 +359,10 @@ export class ArduinoFrontendContribution 'arduino.languageserver.start', { lsPath, - cliPath, + cliDaemonAddr: `localhost:${config.daemon.port}`, clangdPath, log: currentSketchPath ? currentSketchPath : log, - cliConfigPath, + cliDaemonInstance: '1', board: { fqbn, name: name ? `"${name}"` : undefined, @@ -437,7 +395,7 @@ export class ArduinoFrontendContribution registry.registerItem({ id: 'toggle-serial-monitor', command: MonitorViewContribution.TOGGLE_SERIAL_MONITOR_TOOLBAR, - tooltip: 'Serial Monitor', + tooltip: nls.localize('arduino/common/serialMonitor', 'Serial Monitor'), }); } @@ -472,12 +430,21 @@ export class ArduinoFrontendContribution registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(TerminalMenus.TERMINAL)); registry.getMenu(MAIN_MENU_BAR).removeNode(menuId(CommonMenus.VIEW)); - registry.registerSubmenu(ArduinoMenus.SKETCH, 'Sketch'); - registry.registerSubmenu(ArduinoMenus.TOOLS, 'Tools'); + registry.registerSubmenu( + ArduinoMenus.SKETCH, + nls.localize('arduino/menu/sketch', 'Sketch') + ); + registry.registerSubmenu( + ArduinoMenus.TOOLS, + nls.localize('arduino/menu/tools', 'Tools') + ); registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, { commandId: ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG.id, - label: 'Optimize for Debugging', - order: '4', + label: nls.localize( + 'arduino/debug/optimizeForDebugging', + 'Optimize for Debugging' + ), + order: '5', }); } @@ -488,13 +455,17 @@ export class ArduinoFrontendContribution for (const uri of [mainFileUri, ...rootFolderFileUris]) { await this.ensureOpened(uri); } - await this.ensureOpened(mainFileUri, true); if (mainFileUri.endsWith('.pde')) { - const message = `The '${sketch.name}' still uses the old \`.pde\` format. Do you want to switch to the new \`.ino\` extension?`; + const message = nls.localize( + 'arduino/common/oldFormat', + "The '{0}' still uses the old `.pde` format. Do you want to switch to the new `.ino` extension?", + sketch.name + ); + const yes = nls.localize('vscode/extensionsUtils/yes', 'Yes'); this.messageService - .info(message, 'Later', 'Yes') + .info(message, nls.localize('arduino/common/later', 'Later'), yes) .then(async (answer) => { - if (answer === 'Yes') { + if (answer === yes) { this.commandRegistry.executeCommand( SaveAsSketch.Commands.SAVE_AS_SKETCH.id, { diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index 4e126a561..700012bbd 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -69,20 +69,20 @@ import { ScmContribution } from './theia/scm/scm-contribution'; import { SearchInWorkspaceFrontendContribution as TheiaSearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution'; import { SearchInWorkspaceFrontendContribution } from './theia/search-in-workspace/search-in-workspace-frontend-contribution'; import { LibraryListWidgetFrontendContribution } from './library/library-widget-frontend-contribution'; -import { MonitorServiceClientImpl } from './monitor/monitor-service-client-impl'; +import { SerialServiceClientImpl } from './serial/serial-service-client-impl'; import { - MonitorServicePath, - MonitorService, - MonitorServiceClient, -} from '../common/protocol/monitor-service'; + SerialServicePath, + SerialService, + SerialServiceClient, +} from '../common/protocol/serial-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 { MonitorWidget } from './serial/monitor/monitor-widget'; +import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution'; +import { SerialConnectionManager } from './serial/serial-connection-manager'; +import { SerialModel } from './serial/serial-model'; import { TabBarDecoratorService as TheiaTabBarDecoratorService } from '@theia/core/lib/browser/shell/tab-bar-decorator'; import { TabBarDecoratorService } from './theia/core/tab-bar-decorator'; import { ProblemManager as TheiaProblemManager } from '@theia/markers/lib/browser'; @@ -154,7 +154,7 @@ import { } from '../common/protocol/examples-service'; import { BuiltInExamples, LibraryExamples } from './contributions/examples'; import { IncludeLibrary } from './contributions/include-library'; -import { OutputChannelManager as TheiaOutputChannelManager } from '@theia/output/lib/common/output-channel'; +import { OutputChannelManager as TheiaOutputChannelManager } from '@theia/output/lib/browser/output-channel'; import { OutputChannelManager } from './theia/output/output-channel'; import { OutputChannelRegistryMainImpl as TheiaOutputChannelRegistryMainImpl, @@ -190,12 +190,12 @@ import { BoardSelection } from './contributions/board-selection'; import { OpenRecentSketch } from './contributions/open-recent-sketch'; import { Help } from './contributions/help'; import { bindArduinoPreferences } from './arduino-preferences'; +import { SettingsService } from './dialogs/settings/settings'; import { - SettingsService, SettingsDialog, SettingsWidget, SettingsDialogProps, -} from './settings'; +} from './dialogs/settings/settings-dialog'; import { AddFile } from './contributions/add-file'; import { ArchiveSketch } from './contributions/archive-sketch'; import { OutputToolbarContribution as TheiaOutputToolbarContribution } from '@theia/output/lib/browser/output-toolbar-contribution'; @@ -207,6 +207,8 @@ import { DebugConfigurationManager } from './theia/debug/debug-configuration-man import { DebugConfigurationManager as TheiaDebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager'; import { SearchInWorkspaceWidget as TheiaSearchInWorkspaceWidget } from '@theia/search-in-workspace/lib/browser/search-in-workspace-widget'; import { SearchInWorkspaceWidget } from './theia/search-in-workspace/search-in-workspace-widget'; +import { SearchInWorkspaceFactory as TheiaSearchInWorkspaceFactory } from '@theia/search-in-workspace/lib/browser/search-in-workspace-factory'; +import { SearchInWorkspaceFactory } from './theia/search-in-workspace/search-in-workspace-factory'; import { SearchInWorkspaceResultTreeWidget as TheiaSearchInWorkspaceResultTreeWidget } from '@theia/search-in-workspace/lib/browser/search-in-workspace-result-tree-widget'; import { SearchInWorkspaceResultTreeWidget } from './theia/search-in-workspace/search-in-workspace-result-tree-widget'; import { MonacoEditorProvider } from './theia/monaco/monaco-editor-provider'; @@ -253,6 +255,13 @@ import { UploadCertificateDialogProps, UploadCertificateDialogWidget, } from './dialogs/certificate-uploader/certificate-uploader-dialog'; +import { PlotterFrontendContribution } from './serial/plotter/plotter-frontend-contribution'; +import { + UserFieldsDialog, + UserFieldsDialogProps, + UserFieldsDialogWidget, +} from './dialogs/user-fields/user-fields-dialog'; +import { nls } from '@theia/core/lib/common'; const ElementQueries = require('css-element-queries/src/ElementQueries'); @@ -371,7 +380,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(BoardsConfigDialogWidget).toSelf().inSingletonScope(); bind(BoardsConfigDialog).toSelf().inSingletonScope(); bind(BoardsConfigDialogProps).toConstantValue({ - title: 'Select Board', + title: nls.localize('arduino/common/selectBoard', 'Select Board'), }); // Core service @@ -385,8 +394,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { .inSingletonScope(); // Serial monitor - bind(MonitorModel).toSelf().inSingletonScope(); - bind(FrontendApplicationContribution).toService(MonitorModel); + bind(SerialModel).toSelf().inSingletonScope(); + bind(FrontendApplicationContribution).toService(SerialModel); bind(MonitorWidget).toSelf(); bindViewContribution(bind, MonitorViewContribution); bind(TabBarToolbarContribution).toService(MonitorViewContribution); @@ -394,18 +403,19 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { id: MonitorWidget.ID, createWidget: () => context.container.get(MonitorWidget), })); - // Frontend binding for the serial monitor service - bind(MonitorService) + // Frontend binding for the serial service + bind(SerialService) .toDynamicValue((context) => { const connection = context.container.get(WebSocketConnectionProvider); const client = - context.container.get(MonitorServiceClient); - return connection.createProxy(MonitorServicePath, client); + context.container.get(SerialServiceClient); + return connection.createProxy(SerialServicePath, client); }) .inSingletonScope(); - bind(MonitorConnection).toSelf().inSingletonScope(); - // Serial monitor service client to receive and delegate notifications from the backend. - bind(MonitorServiceClient).to(MonitorServiceClientImpl).inSingletonScope(); + bind(SerialConnectionManager).toSelf().inSingletonScope(); + + // Serial service client to receive and delegate notifications from the backend. + bind(SerialServiceClient).to(SerialServiceClientImpl).inSingletonScope(); bind(WorkspaceService).toSelf().inSingletonScope(); rebind(TheiaWorkspaceService).toService(WorkspaceService); @@ -484,6 +494,12 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(SearchInWorkspaceWidget).toSelf(); rebind(TheiaSearchInWorkspaceWidget).toService(SearchInWorkspaceWidget); + + // replace search icon + rebind(TheiaSearchInWorkspaceFactory) + .to(SearchInWorkspaceFactory) + .inSingletonScope(); + rebind(TheiaSearchInWorkspaceResultTreeWidget).toDynamicValue( ({ container }) => { const childContainer = createTreeContainer(container); @@ -596,6 +612,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { Contribution.configure(bind, AddFile); Contribution.configure(bind, ArchiveSketch); Contribution.configure(bind, AddZipLibrary); + Contribution.configure(bind, PlotterFrontendContribution); bind(ResponseServiceImpl) .toSelf() @@ -669,7 +686,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(SettingsWidget).toSelf().inSingletonScope(); bind(SettingsDialog).toSelf().inSingletonScope(); bind(SettingsDialogProps).toConstantValue({ - title: 'Preferences', + title: nls.localize( + 'vscode/preferences.contribution/preferences', + 'Preferences' + ), }); bind(StorageWrapper).toSelf().inSingletonScope(); @@ -735,4 +755,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(UploadCertificateDialogProps).toConstantValue({ title: 'UploadCertificate', }); + + bind(UserFieldsDialogWidget).toSelf().inSingletonScope(); + bind(UserFieldsDialog).toSelf().inSingletonScope(); + bind(UserFieldsDialogProps).toConstantValue({ + title: 'UserFields', + }); }); diff --git a/arduino-ide-extension/src/browser/arduino-preferences.ts b/arduino-ide-extension/src/browser/arduino-preferences.ts index d9aed5958..5e1013a1d 100644 --- a/arduino-ide-extension/src/browser/arduino-preferences.ts +++ b/arduino-ide-extension/src/browser/arduino-preferences.ts @@ -6,6 +6,7 @@ import { PreferenceContribution, PreferenceSchema, } from '@theia/core/lib/browser/preferences'; +import { nls } from '@theia/core/lib/common'; import { CompilerWarningLiterals, CompilerWarnings } from '../common/protocol'; export const ArduinoConfigSchema: PreferenceSchema = { @@ -13,24 +14,34 @@ export const ArduinoConfigSchema: PreferenceSchema = { properties: { 'arduino.language.log': { type: 'boolean', - description: - "True if the Arduino Language Server should generate log files into the sketch folder. Otherwise, false. It's false by default.", + description: nls.localize( + 'arduino/preferences/language.log', + "True if the Arduino Language Server should generate log files into the sketch folder. Otherwise, false. It's false by default." + ), default: false, }, 'arduino.compile.verbose': { type: 'boolean', - description: 'True for verbose compile output. False by default', + description: nls.localize( + 'arduino/preferences/compile.verbose', + 'True for verbose compile output. False by default' + ), default: false, }, 'arduino.compile.warnings': { enum: [...CompilerWarningLiterals], - description: - "Tells gcc which warning level to use. It's 'None' by default", + description: nls.localize( + 'arduino/preferences/compile.warnings', + "Tells gcc which warning level to use. It's 'None' by default" + ), default: 'None', }, 'arduino.upload.verbose': { type: 'boolean', - description: 'True for verbose upload output. False by default.', + description: nls.localize( + 'arduino/preferences/upload.verbose', + 'True for verbose upload output. False by default.' + ), default: false, }, 'arduino.upload.verify': { @@ -39,81 +50,114 @@ export const ArduinoConfigSchema: PreferenceSchema = { }, 'arduino.window.autoScale': { type: 'boolean', - description: - 'True if the user interface automatically scales with the font size.', + description: nls.localize( + 'arduino/preferences/window.autoScale', + 'True if the user interface automatically scales with the font size.' + ), default: true, }, 'arduino.window.zoomLevel': { type: 'number', - description: - 'Adjust the zoom level of the window. The original size is 0 and each increment above (e.g. 1) or below (e.g. -1) represents zooming 20% larger or smaller. You can also enter decimals to adjust the zoom level with a finer granularity.', + description: nls.localize( + 'arduino/preferences/window.zoomLevel', + 'Adjust the zoom level of the window. The original size is 0 and each increment above (e.g. 1) or below (e.g. -1) represents zooming 20% larger or smaller. You can also enter decimals to adjust the zoom level with a finer granularity.' + ), default: 0, }, 'arduino.ide.autoUpdate': { type: 'boolean', - description: - 'True to enable automatic update checks. The IDE will check for updates automatically and periodically.', + description: nls.localize( + 'arduino/preferences/ide.autoUpdate', + 'True to enable automatic update checks. The IDE will check for updates automatically and periodically.' + ), default: true, }, 'arduino.board.certificates': { type: 'string', - description: 'List of certificates that can be uploaded to boards', + description: nls.localize( + 'arduino/preferences/board.certificates', + 'List of certificates that can be uploaded to boards' + ), default: '', }, 'arduino.sketchbook.showAllFiles': { type: 'boolean', - description: - 'True to show all sketch files inside the sketch. It is false by default.', + description: nls.localize( + 'arduino/preferences/sketchbook.showAllFiles', + 'True to show all sketch files inside the sketch. It is false by default.' + ), default: false, }, 'arduino.cloud.enabled': { type: 'boolean', - description: - 'True if the sketch sync functions are enabled. Defaults to true.', + description: nls.localize( + 'arduino/preferences/cloud.enabled', + 'True if the sketch sync functions are enabled. Defaults to true.' + ), default: true, }, 'arduino.cloud.pull.warn': { type: 'boolean', - description: - 'True if users should be warned before pulling a cloud sketch. Defaults to true.', + description: nls.localize( + 'arduino/preferences/cloud.pull.warn', + 'True if users should be warned before pulling a cloud sketch. Defaults to true.' + ), default: true, }, 'arduino.cloud.push.warn': { type: 'boolean', - description: - 'True if users should be warned before pushing a cloud sketch. Defaults to true.', + description: nls.localize( + 'arduino/preferences/cloud.push.warn', + 'True if users should be warned before pushing a cloud sketch. Defaults to true.' + ), default: true, }, 'arduino.cloud.pushpublic.warn': { type: 'boolean', - description: - 'True if users should be warned before pushing a public sketch to the cloud. Defaults to true.', + description: nls.localize( + 'arduino/preferences/cloud.pushpublic.warn', + 'True if users should be warned before pushing a public sketch to the cloud. Defaults to true.' + ), default: true, }, 'arduino.cloud.sketchSyncEnpoint': { type: 'string', - description: - 'The endpoint used to push and pull sketches from a backend. By default it points to Arduino Cloud API.', + description: nls.localize( + 'arduino/preferences/cloud.sketchSyncEnpoint', + 'The endpoint used to push and pull sketches from a backend. By default it points to Arduino Cloud API.' + ), default: 'https://api2.arduino.cc/create', }, 'arduino.auth.clientID': { type: 'string', - description: 'The OAuth2 client ID.', + description: nls.localize( + 'arduino/preferences/auth.clientID', + 'The OAuth2 client ID.' + ), default: 'C34Ya6ex77jTNxyKWj01lCe1vAHIaPIo', }, 'arduino.auth.domain': { type: 'string', - description: 'The OAuth2 domain.', + description: nls.localize( + 'arduino/preferences/auth.domain', + 'The OAuth2 domain.' + ), default: 'login.arduino.cc', }, 'arduino.auth.audience': { type: 'string', - description: 'The 0Auth2 audience.', + description: nls.localize( + 'arduino/preferences/auth.audience', + 'The OAuth2 audience.' + ), default: 'https://api.arduino.cc', }, 'arduino.auth.registerUri': { type: 'string', - description: 'The URI used to register a new user.', + description: nls.localize( + 'arduino/preferences/auth.registerUri', + 'The URI used to register a new user.' + ), default: 'https://auth.arduino.cc/login#/register', }, }, diff --git a/arduino-ide-extension/src/browser/auth/cloud-user-commands.ts b/arduino-ide-extension/src/browser/auth/cloud-user-commands.ts index 7a4d6c89e..165d5bda7 100644 --- a/arduino-ide-extension/src/browser/auth/cloud-user-commands.ts +++ b/arduino-ide-extension/src/browser/auth/cloud-user-commands.ts @@ -1,15 +1,21 @@ import { Command } from '@theia/core/lib/common/command'; export namespace CloudUserCommands { - export const LOGIN: Command = { - id: 'arduino-cloud--login', - label: 'Sign in', - }; + export const LOGIN = Command.toLocalizedCommand( + { + id: 'arduino-cloud--login', + label: 'Sign in', + }, + 'arduino/cloud/signIn' + ); - export const LOGOUT: Command = { - id: 'arduino-cloud--logout', - label: 'Sign Out', - }; + export const LOGOUT = Command.toLocalizedCommand( + { + id: 'arduino-cloud--logout', + label: 'Sign Out', + }, + 'arduino/cloud/signOut' + ); export const OPEN_PROFILE_CONTEXT_MENU: Command = { id: 'arduino-cloud-sketchbook--open-profile-menu', diff --git a/arduino-ide-extension/src/browser/boards/boards-auto-installer.ts b/arduino-ide-extension/src/browser/boards/boards-auto-installer.ts index 61a5c26e3..97fefb84d 100644 --- a/arduino-ide-extension/src/browser/boards/boards-auto-installer.ts +++ b/arduino-ide-extension/src/browser/boards/boards-auto-installer.ts @@ -10,6 +10,7 @@ import { BoardsServiceProvider } from './boards-service-provider'; import { BoardsConfig } from './boards-config'; import { Installable, ResponseServiceArduino } from '../../common/protocol'; import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution'; +import { nls } from '@theia/core/lib/common'; /** * Listens on `BoardsConfig.Config` changes, if a board is selected which does not @@ -81,12 +82,23 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution { const version = candidate.availableVersions[0] ? `[v ${candidate.availableVersions[0]}]` : ''; + const yes = nls.localize('vscode/extensionsUtils/yes', 'Yes'); + const manualInstall = nls.localize( + 'arduino/board/installManually', + 'Install Manually' + ); // tslint:disable-next-line:max-line-length this.messageService .info( - `The \`"${candidate.name} ${version}"\` core has to be installed for the currently selected \`"${selectedBoard.name}"\` board. Do you want to install it now?`, - 'Install Manually', - 'Yes' + nls.localize( + 'arduino/board/installNow', + 'The "{0} {1}" core has to be installed for the currently selected "{2}" board. Do you want to install it now?', + candidate.name, + version, + selectedBoard.name + ), + manualInstall, + yes ) .then(async (answer) => { const index = this.notifications.findIndex((board) => @@ -95,7 +107,7 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution { if (index !== -1) { this.notifications.splice(index, 1); } - if (answer === 'Yes') { + if (answer === yes) { await Installable.installWithProgress({ installable: this.boardsService, item: candidate, @@ -105,7 +117,7 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution { }); return; } - if (answer === 'Install Manually') { + if (answer === manualInstall) { this.boardsManagerFrontendContribution .openView({ reveal: true }) .then((widget) => diff --git a/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts b/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts index 48ccc828a..0ef4bad3c 100644 --- a/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts +++ b/arduino-ide-extension/src/browser/boards/boards-config-dialog.ts @@ -6,6 +6,7 @@ import { BoardsConfig } from './boards-config'; import { BoardsService } from '../../common/protocol/boards-service'; import { BoardsServiceProvider } from './boards-service-provider'; import { BoardsConfigDialogWidget } from './boards-config-dialog-widget'; +import { nls } from '@theia/core/lib/common'; @injectable() export class BoardsConfigDialogProps extends DialogProps {} @@ -32,8 +33,10 @@ export class BoardsConfigDialog extends AbstractDialog { this.contentNode.classList.add('select-board-dialog'); this.contentNode.appendChild(this.createDescription()); - this.appendCloseButton('CANCEL'); - this.appendAcceptButton('OK'); + this.appendCloseButton( + nls.localize('vscode/issueMainService/cancel', 'Cancel') + ); + this.appendAcceptButton(nls.localize('vscode/issueMainService/ok', 'OK')); } @postConstruct() @@ -63,7 +66,10 @@ export class BoardsConfigDialog extends AbstractDialog { head.classList.add('head'); const title = document.createElement('div'); - title.textContent = 'Select Other Board & Port'; + title.textContent = nls.localize( + 'arduino/board/configDialogTitle', + 'Select Other Board & Port' + ); title.classList.add('title'); head.appendChild(title); @@ -72,8 +78,14 @@ export class BoardsConfigDialog extends AbstractDialog { 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.', + nls.localize( + 'arduino/board/configDialog1', + 'Select both a Board and a Port if you want to upload a sketch.' + ), + nls.localize( + 'arduino/board/configDialog2', + 'If you only select a Board you will be able just to compile, but not to upload your sketch.' + ), ]) { const p = document.createElement('div'); p.textContent = paragraph; @@ -117,7 +129,10 @@ export class BoardsConfigDialog extends AbstractDialog { 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 nls.localize( + 'arduino/board/pleasePickBoard', + 'Please pick a board connected to the port you have selected.' + ); } return false; } diff --git a/arduino-ide-extension/src/browser/boards/boards-config.tsx b/arduino-ide-extension/src/browser/boards/boards-config.tsx index d4772a2e6..6d614745c 100644 --- a/arduino-ide-extension/src/browser/boards/boards-config.tsx +++ b/arduino-ide-extension/src/browser/boards/boards-config.tsx @@ -10,7 +10,12 @@ import { BoardWithPackage, } from '../../common/protocol/boards-service'; import { NotificationCenter } from '../notification-center'; -import { BoardsServiceProvider } from './boards-service-provider'; +import { + AvailableBoard, + BoardsServiceProvider, +} from './boards-service-provider'; +import { naturalCompare } from '../../common/utils'; +import { nls } from '@theia/core/lib/common'; export namespace BoardsConfig { export interface Config { @@ -183,11 +188,50 @@ export class BoardsConfig extends React.Component< .filter(notEmpty); } + protected get availableBoards(): AvailableBoard[] { + return this.props.boardsServiceProvider.availableBoards; + } + protected queryPorts = async ( availablePorts: MaybePromise = this.availablePorts ) => { - const ports = await availablePorts; - return { knownPorts: ports.sort(Port.compare) }; + // Available ports must be sorted in this order: + // 1. Serial with recognized boards + // 2. Serial with guessed boards + // 3. Serial with incomplete boards + // 4. Network with recognized boards + // 5. Other protocols with recognized boards + const ports = (await availablePorts).sort((left: Port, right: Port) => { + if (left.protocol === 'serial' && right.protocol !== 'serial') { + return -1; + } else if (left.protocol !== 'serial' && right.protocol === 'serial') { + return 1; + } else if (left.protocol === 'network' && right.protocol !== 'network') { + return -1; + } else if (left.protocol !== 'network' && right.protocol === 'network') { + return 1; + } else if (left.protocol === right.protocol) { + // We show ports, including those that have guessed + // or unrecognized boards, so we must sort those too. + const leftBoard = this.availableBoards.find((board) => + Port.sameAs(board.port, left) + ); + const rightBoard = this.availableBoards.find((board) => + Port.sameAs(board.port, right) + ); + if (leftBoard && !rightBoard) { + return -1; + } else if (!leftBoard && rightBoard) { + return 1; + } else if (leftBoard?.state! < rightBoard?.state!) { + return -1; + } else if (leftBoard?.state! > rightBoard?.state!) { + return 1; + } + } + return naturalCompare(left.address, right.address); + }); + return { knownPorts: ports }; }; protected toggleFilterPorts = () => { @@ -265,7 +309,7 @@ export class BoardsConfig extends React.Component<
{Array.from(distinctBoards.values()).map((board) => ( - key={`${board.name}-${board.packageName}`} + key={toKey(board)} item={board} label={board.name} details={board.details} @@ -280,8 +324,24 @@ export class BoardsConfig extends React.Component< } protected renderPorts(): React.ReactNode { - const filter = this.state.showAllPorts ? () => true : Port.isBoardPort; - const ports = this.state.knownPorts.filter(filter); + let ports = [] as Port[]; + if (this.state.showAllPorts) { + ports = this.state.knownPorts; + } else { + ports = this.state.knownPorts.filter((port) => { + if (port.protocol === 'serial') { + return true; + } + // All other ports with different protocol are + // only shown if there is a recognized board + // connected + for (const board of this.availableBoards) { + if (board.port?.address === port.address) { + return true; + } + } + }); + } return !ports.length ? (
No ports discovered
) : ( @@ -302,7 +362,12 @@ export class BoardsConfig extends React.Component< protected renderPortsFooter(): React.ReactNode { return (
-