Implemented filter and update all for libs/boards.

Closes #177
Closes #1188

Co-authored-by: Francesco Spissu <f.spissu@arduino.cc>
Co-authored-by: Per Tillisch <p.tillisch@arduino.cc>
Co-authored-by: Akos Kitta <a.kitta@arduino.cc>

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
Akos Kitta
2022-08-27 12:04:35 +02:00
committed by Akos Kitta
parent 7828cc11ac
commit 5cb9166c83
37 changed files with 1700 additions and 224 deletions

View File

@@ -314,7 +314,7 @@ import { FirstStartupInstaller } from './contributions/first-startup-installer';
import { OpenSketchFiles } from './contributions/open-sketch-files';
import { InoLanguage } from './contributions/ino-language';
import { SelectedBoard } from './contributions/selected-board';
import { CheckForUpdates } from './contributions/check-for-updates';
import { CheckForIDEUpdates } from './contributions/check-for-ide-updates';
import { OpenBoardsConfig } from './contributions/open-boards-config';
import { SketchFilesTracker } from './contributions/sketch-files-tracker';
import { MonacoThemeServiceIsReady } from './utils/window';
@@ -323,6 +323,15 @@ import { StatusBarImpl } from './theia/core/status-bar';
import { StatusBarImpl as TheiaStatusBarImpl } from '@theia/core/lib/browser';
import { EditorMenuContribution } from './theia/editor/editor-file';
import { EditorMenuContribution as TheiaEditorMenuContribution } from '@theia/editor/lib/browser/editor-menu';
import { PreferencesEditorWidget as TheiaPreferencesEditorWidget } from '@theia/preferences/lib/browser/views/preference-editor-widget';
import { PreferencesEditorWidget } from './theia/preferences/preference-editor-widget';
import { PreferencesWidget } from '@theia/preferences/lib/browser/views/preference-widget';
import { createPreferencesWidgetContainer } from '@theia/preferences/lib/browser/views/preference-widget-bindings';
import {
BoardsFilterRenderer,
LibraryFilterRenderer,
} from './widgets/component-list/filter-renderer';
import { CheckForUpdates } from './contributions/check-for-updates';
const registerArduinoThemes = () => {
const themes: MonacoThemeJson[] = [
@@ -364,6 +373,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
// Renderer for both the library and the core widgets.
bind(ListItemRenderer).toSelf().inSingletonScope();
bind(LibraryFilterRenderer).toSelf().inSingletonScope();
bind(BoardsFilterRenderer).toSelf().inSingletonScope();
// Library service
bind(LibraryService)
@@ -737,9 +748,10 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
Contribution.configure(bind, OpenSketchFiles);
Contribution.configure(bind, InoLanguage);
Contribution.configure(bind, SelectedBoard);
Contribution.configure(bind, CheckForUpdates);
Contribution.configure(bind, CheckForIDEUpdates);
Contribution.configure(bind, OpenBoardsConfig);
Contribution.configure(bind, SketchFilesTracker);
Contribution.configure(bind, CheckForUpdates);
// Disabled the quick-pick customization from Theia when multiple formatters are available.
// Use the default VS Code behavior, and pick the first one. In the IDE2, clang-format has `exclusive` selectors.
@@ -845,6 +857,18 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(DockPanelRenderer).toSelf();
rebind(TheiaDockPanelRenderer).toService(DockPanelRenderer);
// Avoid running the "reset scroll" interval tasks until the preference editor opens.
rebind(PreferencesWidget)
.toDynamicValue(({ container }) => {
const child = createPreferencesWidgetContainer(container);
child.bind(PreferencesEditorWidget).toSelf().inSingletonScope();
child
.rebind(TheiaPreferencesEditorWidget)
.toService(PreferencesEditorWidget);
return child.get(PreferencesWidget);
})
.inSingletonScope();
// Preferences
bindArduinoPreferences(bind);

View File

@@ -241,6 +241,14 @@ export const ArduinoConfigSchema: PreferenceSchema = {
),
default: false,
},
'arduino.checkForUpdates': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/checkForUpdate',
"Receive notifications of available updates for the IDE, boards, and libraries. Requires an IDE restart after change. It's true by default."
),
default: true,
},
},
};
@@ -270,6 +278,7 @@ export interface ArduinoConfiguration {
'arduino.auth.registerUri': string;
'arduino.survey.notification': boolean;
'arduino.cli.daemon.debug': boolean;
'arduino.checkForUpdates': boolean;
}
export const ArduinoPreferences = Symbol('ArduinoPreferences');

View File

@@ -12,6 +12,7 @@ import { Installable, ResponseServiceClient } from '../../common/protocol';
import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution';
import { nls } from '@theia/core/lib/common';
import { NotificationCenter } from '../notification-center';
import { InstallManually } from '../../common/nls';
interface AutoInstallPromptAction {
// isAcceptance, whether or not the action indicates acceptance of auto-install proposal
@@ -231,19 +232,18 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
candidate: BoardsPackage
): AutoInstallPromptActions {
const yes = nls.localize('vscode/extensionsUtils/yes', 'Yes');
const manualInstall = nls.localize(
'arduino/board/installManually',
'Install Manually'
);
const actions: AutoInstallPromptActions = [
{
key: manualInstall,
key: InstallManually,
handler: () => {
this.boardsManagerFrontendContribution
.openView({ reveal: true })
.then((widget) =>
widget.refresh(candidate.name.toLocaleLowerCase())
widget.refresh({
query: candidate.name.toLocaleLowerCase(),
type: 'All',
})
);
},
},

View File

@@ -4,22 +4,24 @@ import {
postConstruct,
} from '@theia/core/shared/inversify';
import {
BoardSearch,
BoardsPackage,
BoardsService,
} from '../../common/protocol/boards-service';
import { ListWidget } from '../widgets/component-list/list-widget';
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
import { nls } from '@theia/core/lib/common';
import { BoardsFilterRenderer } from '../widgets/component-list/filter-renderer';
@injectable()
export class BoardsListWidget extends ListWidget<BoardsPackage> {
export class BoardsListWidget extends ListWidget<BoardsPackage, BoardSearch> {
static WIDGET_ID = 'boards-list-widget';
static WIDGET_LABEL = nls.localize('arduino/boardsManager', 'Boards Manager');
constructor(
@inject(BoardsService) protected service: BoardsService,
@inject(ListItemRenderer)
protected itemRenderer: ListItemRenderer<BoardsPackage>
@inject(BoardsService) service: BoardsService,
@inject(ListItemRenderer) itemRenderer: ListItemRenderer<BoardsPackage>,
@inject(BoardsFilterRenderer) filterRenderer: BoardsFilterRenderer
) {
super({
id: BoardsListWidget.WIDGET_ID,
@@ -30,6 +32,8 @@ export class BoardsListWidget extends ListWidget<BoardsPackage> {
itemLabel: (item: BoardsPackage) => item.name,
itemDeprecated: (item: BoardsPackage) => item.deprecated,
itemRenderer,
filterRenderer,
defaultSearchOptions: { query: '', type: 'All' },
});
}

View File

@@ -1,10 +1,16 @@
import { injectable } from '@theia/core/shared/inversify';
import { BoardsListWidget } from './boards-list-widget';
import { BoardsPackage } from '../../common/protocol/boards-service';
import type {
BoardSearch,
BoardsPackage,
} from '../../common/protocol/boards-service';
import { ListWidgetFrontendContribution } from '../widgets/component-list/list-widget-frontend-contribution';
@injectable()
export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution<BoardsPackage> {
export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution<
BoardsPackage,
BoardSearch
> {
constructor() {
super({
widgetId: BoardsListWidget.WIDGET_ID,

View File

@@ -0,0 +1,68 @@
import { nls } from '@theia/core/lib/common/nls';
import { LocalStorageService } from '@theia/core/lib/browser/storage-service';
import { inject, injectable } from '@theia/core/shared/inversify';
import {
IDEUpdater,
SKIP_IDE_VERSION,
} from '../../common/protocol/ide-updater';
import { IDEUpdaterDialog } from '../dialogs/ide-updater/ide-updater-dialog';
import { Contribution } from './contribution';
@injectable()
export class CheckForIDEUpdates extends Contribution {
@inject(IDEUpdater)
private readonly updater: IDEUpdater;
@inject(IDEUpdaterDialog)
private readonly updaterDialog: IDEUpdaterDialog;
@inject(LocalStorageService)
private readonly localStorage: LocalStorageService;
override onStart(): void {
this.preferences.onPreferenceChanged(
({ preferenceName, newValue, oldValue }) => {
if (newValue !== oldValue) {
switch (preferenceName) {
case 'arduino.ide.updateChannel':
case 'arduino.ide.updateBaseUrl':
this.updater.init(
this.preferences.get('arduino.ide.updateChannel'),
this.preferences.get('arduino.ide.updateBaseUrl')
);
}
}
}
);
}
override onReady(): void {
const checkForUpdates = this.preferences['arduino.checkForUpdates'];
if (!checkForUpdates) {
return;
}
this.updater
.init(
this.preferences.get('arduino.ide.updateChannel'),
this.preferences.get('arduino.ide.updateBaseUrl')
)
.then(() => this.updater.checkForUpdates(true))
.then(async (updateInfo) => {
if (!updateInfo) return;
const versionToSkip = await this.localStorage.getData<string>(
SKIP_IDE_VERSION
);
if (versionToSkip === updateInfo.version) return;
this.updaterDialog.open(updateInfo);
})
.catch((e) => {
this.messageService.error(
nls.localize(
'arduino/ide-updater/errorCheckingForUpdates',
'Error while checking for Arduino IDE updates.\n{0}',
e.message
)
);
});
}
}

View File

@@ -1,64 +1,221 @@
import type { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
import { nls } from '@theia/core/lib/common/nls';
import { LocalStorageService } from '@theia/core/lib/browser/storage-service';
import { inject, injectable } from '@theia/core/shared/inversify';
import { InstallManually, Later } from '../../common/nls';
import {
IDEUpdater,
SKIP_IDE_VERSION,
} from '../../common/protocol/ide-updater';
import { IDEUpdaterDialog } from '../dialogs/ide-updater/ide-updater-dialog';
import { Contribution } from './contribution';
ArduinoComponent,
BoardsPackage,
BoardsService,
LibraryPackage,
LibraryService,
ResponseServiceClient,
Searchable,
} from '../../common/protocol';
import { Installable } from '../../common/protocol/installable';
import { ExecuteWithProgress } from '../../common/protocol/progressible';
import { BoardsListWidgetFrontendContribution } from '../boards/boards-widget-frontend-contribution';
import { LibraryListWidgetFrontendContribution } from '../library/library-widget-frontend-contribution';
import { WindowServiceExt } from '../theia/core/window-service-ext';
import type { ListWidget } from '../widgets/component-list/list-widget';
import { Command, CommandRegistry, Contribution } from './contribution';
const NoUpdates = nls.localize(
'arduino/checkForUpdates/noUpdates',
'There are no recent updates available.'
);
const PromptUpdateBoards = nls.localize(
'arduino/checkForUpdates/promptUpdateBoards',
'Updates are available for some of your boards.'
);
const PromptUpdateLibraries = nls.localize(
'arduino/checkForUpdates/promptUpdateLibraries',
'Updates are available for some of your libraries.'
);
const UpdatingBoards = nls.localize(
'arduino/checkForUpdates/updatingBoards',
'Updating boards...'
);
const UpdatingLibraries = nls.localize(
'arduino/checkForUpdates/updatingLibraries',
'Updating libraries...'
);
const InstallAll = nls.localize(
'arduino/checkForUpdates/installAll',
'Install All'
);
interface Task<T extends ArduinoComponent> {
readonly run: () => Promise<void>;
readonly item: T;
}
const Updatable = { type: 'Updatable' } as const;
@injectable()
export class CheckForUpdates extends Contribution {
@inject(IDEUpdater)
private readonly updater: IDEUpdater;
@inject(WindowServiceExt)
private readonly windowService: WindowServiceExt;
@inject(ResponseServiceClient)
private readonly responseService: ResponseServiceClient;
@inject(BoardsService)
private readonly boardsService: BoardsService;
@inject(LibraryService)
private readonly libraryService: LibraryService;
@inject(BoardsListWidgetFrontendContribution)
private readonly boardsContribution: BoardsListWidgetFrontendContribution;
@inject(LibraryListWidgetFrontendContribution)
private readonly librariesContribution: LibraryListWidgetFrontendContribution;
@inject(IDEUpdaterDialog)
private readonly updaterDialog: IDEUpdaterDialog;
@inject(LocalStorageService)
private readonly localStorage: LocalStorageService;
override onStart(): void {
this.preferences.onPreferenceChanged(
({ preferenceName, newValue, oldValue }) => {
if (newValue !== oldValue) {
switch (preferenceName) {
case 'arduino.ide.updateChannel':
case 'arduino.ide.updateBaseUrl':
this.updater.init(
this.preferences.get('arduino.ide.updateChannel'),
this.preferences.get('arduino.ide.updateBaseUrl')
);
}
}
}
);
override registerCommands(register: CommandRegistry): void {
register.registerCommand(CheckForUpdates.Commands.CHECK_FOR_UPDATES, {
execute: () => this.checkForUpdates(false),
});
}
override onReady(): void {
this.updater
.init(
this.preferences.get('arduino.ide.updateChannel'),
this.preferences.get('arduino.ide.updateBaseUrl')
)
.then(() => this.updater.checkForUpdates(true))
.then(async (updateInfo) => {
if (!updateInfo) return;
const versionToSkip = await this.localStorage.getData<string>(
SKIP_IDE_VERSION
);
if (versionToSkip === updateInfo.version) return;
this.updaterDialog.open(updateInfo);
})
.catch((e) => {
this.messageService.error(
nls.localize(
'arduino/ide-updater/errorCheckingForUpdates',
'Error while checking for Arduino IDE updates.\n{0}',
e.message
)
);
override async onReady(): Promise<void> {
const checkForUpdates = this.preferences['arduino.checkForUpdates'];
if (checkForUpdates) {
this.windowService.isFirstWindow().then((firstWindow) => {
if (firstWindow) {
this.checkForUpdates();
}
});
}
}
private async checkForUpdates(silent = true) {
const [boardsPackages, libraryPackages] = await Promise.all([
this.boardsService.search(Updatable),
this.libraryService.search(Updatable),
]);
this.promptUpdateBoards(boardsPackages);
this.promptUpdateLibraries(libraryPackages);
if (!libraryPackages.length && !boardsPackages.length && !silent) {
this.messageService.info(NoUpdates);
}
}
private promptUpdateBoards(items: BoardsPackage[]): void {
this.prompt({
items,
installable: this.boardsService,
viewContribution: this.boardsContribution,
viewSearchOptions: { query: '', ...Updatable },
promptMessage: PromptUpdateBoards,
updatingMessage: UpdatingBoards,
});
}
private promptUpdateLibraries(items: LibraryPackage[]): void {
this.prompt({
items,
installable: this.libraryService,
viewContribution: this.librariesContribution,
viewSearchOptions: { query: '', topic: 'All', ...Updatable },
promptMessage: PromptUpdateLibraries,
updatingMessage: UpdatingLibraries,
});
}
private prompt<
T extends ArduinoComponent,
S extends Searchable.Options
>(options: {
items: T[];
installable: Installable<T>;
viewContribution: AbstractViewContribution<ListWidget<T, S>>;
viewSearchOptions: S;
promptMessage: string;
updatingMessage: string;
}): void {
const {
items,
installable,
viewContribution,
promptMessage: message,
viewSearchOptions,
updatingMessage,
} = options;
if (!items.length) {
return;
}
this.messageService
.info(message, Later, InstallManually, InstallAll)
.then((answer) => {
if (answer === InstallAll) {
const tasks = items.map((item) =>
this.createInstallTask(item, installable)
);
this.executeTasks(updatingMessage, tasks);
} else if (answer === InstallManually) {
viewContribution
.openView({ reveal: true })
.then((widget) => widget.refresh(viewSearchOptions));
}
});
}
private async executeTasks(
message: string,
tasks: Task<ArduinoComponent>[]
): Promise<void> {
if (tasks.length) {
return ExecuteWithProgress.withProgress(
message,
this.messageService,
async (progress) => {
try {
const total = tasks.length;
let count = 0;
for (const { run, item } of tasks) {
try {
await run(); // runs update sequentially. // TODO: is parallel update desired?
} catch (err) {
console.error(err);
this.messageService.error(
`Failed to update ${item.name}. ${err}`
);
} finally {
progress.report({ work: { total, done: ++count } });
}
}
} finally {
progress.cancel();
}
}
);
}
}
private createInstallTask<T extends ArduinoComponent>(
item: T,
installable: Installable<T>
): Task<T> {
const latestVersion = item.availableVersions[0];
return {
item,
run: () =>
Installable.installWithProgress({
installable,
item,
version: latestVersion,
messageService: this.messageService,
responseService: this.responseService,
keepOutput: true,
}),
};
}
}
export namespace CheckForUpdates {
export namespace Commands {
export const CHECK_FOR_UPDATES: Command = Command.toLocalizedCommand(
{
id: 'arduino-check-for-updates',
label: 'Check for Arduino Updates',
category: 'Arduino',
},
'arduino/checkForUpdates/checkForUpdates'
);
}
}

View File

@@ -1,6 +1,7 @@
import { nls } from '@theia/core/lib/common/nls';
import { injectable } from '@theia/core/shared/inversify';
import type { EditorOpenerOptions } from '@theia/editor/lib/browser/editor-manager';
import { Later } from '../../common/nls';
import { SketchesError } from '../../common/protocol';
import {
Command,
@@ -41,20 +42,18 @@ export class OpenSketchFiles extends SketchContribution {
sketch.name
);
const yes = nls.localize('vscode/extensionsUtils/yes', 'Yes');
this.messageService
.info(message, nls.localize('arduino/common/later', 'Later'), yes)
.then(async (answer) => {
if (answer === yes) {
this.commandService.executeCommand(
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
{
execOnlyIfTemp: false,
openAfterMove: true,
wipeOriginal: false,
}
);
}
});
this.messageService.info(message, Later, yes).then((answer) => {
if (answer === yes) {
this.commandService.executeCommand(
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
{
execOnlyIfTemp: false,
openAfterMove: true,
wipeOriginal: false,
}
);
}
});
}
} catch (err) {
if (SketchesError.NotFound.is(err)) {

View File

@@ -54,8 +54,8 @@ export class IDEUpdaterCommands implements CommandContribution {
export namespace IDEUpdaterCommands {
export const CHECK_FOR_UPDATES: Command = Command.toLocalizedCommand(
{
id: 'arduino-ide-check-for-updates',
label: 'Check for Arduino IDE updates',
id: 'arduino-check-for-ide-updates',
label: 'Check for Arduino IDE Updates',
category: 'Arduino',
},
'arduino/ide-updater/checkForUpdates'

View File

@@ -1,19 +1,28 @@
import { injectable, postConstruct, inject } from '@theia/core/shared/inversify';
import {
injectable,
postConstruct,
inject,
} from '@theia/core/shared/inversify';
import { Message } from '@theia/core/shared/@phosphor/messaging';
import { addEventListener } from '@theia/core/lib/browser/widgets/widget';
import { DialogProps } from '@theia/core/lib/browser/dialogs';
import { AbstractDialog } from '../theia/dialogs/dialogs';
import {
LibraryPackage,
LibrarySearch,
LibraryService,
} from '../../common/protocol/library-service';
import { ListWidget } from '../widgets/component-list/list-widget';
import { Installable } from '../../common/protocol';
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
import { nls } from '@theia/core/lib/common';
import { LibraryFilterRenderer } from '../widgets/component-list/filter-renderer';
@injectable()
export class LibraryListWidget extends ListWidget<LibraryPackage> {
export class LibraryListWidget extends ListWidget<
LibraryPackage,
LibrarySearch
> {
static WIDGET_ID = 'library-list-widget';
static WIDGET_LABEL = nls.localize(
'arduino/library/title',
@@ -21,9 +30,9 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
);
constructor(
@inject(LibraryService) protected service: LibraryService,
@inject(ListItemRenderer)
protected itemRenderer: ListItemRenderer<LibraryPackage>
@inject(LibraryService) private service: LibraryService,
@inject(ListItemRenderer) itemRenderer: ListItemRenderer<LibraryPackage>,
@inject(LibraryFilterRenderer) filterRenderer: LibraryFilterRenderer
) {
super({
id: LibraryListWidget.WIDGET_ID,
@@ -34,6 +43,8 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
itemLabel: (item: LibraryPackage) => item.name,
itemDeprecated: (item: LibraryPackage) => item.deprecated,
itemRenderer,
filterRenderer,
defaultSearchOptions: { query: '', type: 'All', topic: 'All' },
});
}
@@ -41,7 +52,9 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
protected override init(): void {
super.init();
this.toDispose.pushAll([
this.notificationCenter.onLibraryDidInstall(() => this.refresh(undefined)),
this.notificationCenter.onLibraryDidInstall(() =>
this.refresh(undefined)
),
this.notificationCenter.onLibraryDidUninstall(() =>
this.refresh(undefined)
),

View File

@@ -8,13 +8,35 @@
}
.arduino-list-widget .search-bar {
margin: 0px 10px 10px 15px;
margin: 0px 10px 5px 15px;
}
.arduino-list-widget .search-bar:focus {
border-color: var(--theia-focusBorder);
}
.arduino-list-widget .filter-bar {
margin: 0px 10px 5px 15px;
}
.arduino-list-widget .filter-bar > * {
padding: 5px 5px 0px 0px;
}
.arduino-list-widget .filter-bar .filter {
display: flex;
align-items: center;
}
.arduino-list-widget .filter-bar .filter > select {
width: 120px;
}
.arduino-list-widget .filter-bar .filter-label {
display: flex;
width: 50px;
}
.filterable-list-container {
display: flex;
flex-direction: column;
@@ -23,33 +45,24 @@
}
.filterable-list-container .items-container {
height: 100%; /* This has to be propagated down from the widget. */
position: relative; /* To fix the `top` of the vertical toolbar. */
padding-bottom: calc(2 * var(--theia-statusBar-height));
}
.filterable-list-container .items-container > div:nth-child(odd) {
.filterable-list-container .items-container > div > div:nth-child(odd) {
background-color: var(--theia-sideBar-background);
filter: contrast(105%);
}
.filterable-list-container .items-container > div:nth-child(even) {
.filterable-list-container .items-container > div > div:nth-child(even) {
background-color: var(--theia-sideBar-background);
filter: contrast(95%);
}
.filterable-list-container .items-container > div:hover {
.filterable-list-container .items-container > div > div:hover {
background-color: var(--theia-sideBar-background);
filter: contrast(90%);
}
/* 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 .filterable-list-container .items-container .ps__rail-y {
z-index: 1;
}
.component-list-item {
padding: 10px 10px 10px 15px;
font-size: var(--theia-ui-font-size1);
@@ -113,7 +126,7 @@ https://github.com/arduino/arduino-pro-ide/issues/82 */
.component-list-item[min-width~="170px"] .footer {
padding: 5px 5px 0px 0px;
min-height: 30px;
min-height: 35px;
display: flex;
flex-direction: row-reverse;
}
@@ -122,10 +135,6 @@ https://github.com/arduino/arduino-pro-ide/issues/82 */
flex-direction: column-reverse;
}
.component-list-item .footer > * {
display: none
}
.component-list-item:hover .footer > * {
display: inline-block;
margin: 5px 0px 0px 10px;
@@ -155,4 +164,4 @@ https://github.com/arduino/arduino-pro-ide/issues/82 */
.hc-black.hc-theia.theia-hc .component-list-item .header .installed:before {
border: 1px solid var(--theia-button-border);
}
}

View File

@@ -0,0 +1,19 @@
import { injectable } from '@theia/core/shared/inversify';
import { PreferencesEditorWidget as TheiaPreferencesEditorWidget } from '@theia/preferences/lib/browser/views/preference-editor-widget';
@injectable()
export class PreferencesEditorWidget extends TheiaPreferencesEditorWidget {
protected override resetScroll(
nodeIDToScrollTo?: string,
filterWasCleared = false
): void {
if (this.scrollBar) {
// Absent on widget creation
this.doResetScroll(nodeIDToScrollTo, filterWasCleared);
} else {
// NOOP
// Unlike Theia, IDE2 does not start multiple tasks to check if the scrollbar is ready to reset it.
// If the "scroll reset" request arrived before the existence of the scrollbar, what to reset?
}
}
}

View File

@@ -14,11 +14,38 @@ export class ComponentListItem<
)[0];
this.state = {
selectedVersion: version,
focus: false,
};
}
}
protected async install(item: T): Promise<void> {
override componentDidUpdate(
prevProps: ComponentListItem.Props<T>,
prevState: ComponentListItem.State
): void {
if (this.state.focus !== prevState.focus) {
this.props.onFocusDidChange();
}
}
override render(): React.ReactNode {
const { item, itemRenderer } = this.props;
return (
<div
onMouseEnter={() => this.setState({ focus: true })}
onMouseLeave={() => this.setState({ focus: false })}
>
{itemRenderer.renderItem(
Object.assign(this.state, { item }),
this.install.bind(this),
this.uninstall.bind(this),
this.onVersionChange.bind(this)
)}
</div>
);
}
private async install(item: T): Promise<void> {
const toInstall = this.state.selectedVersion;
const version = this.props.item.availableVersions.filter(
(version) => version !== this.state.selectedVersion
@@ -35,23 +62,13 @@ export class ComponentListItem<
}
}
protected async uninstall(item: T): Promise<void> {
private async uninstall(item: T): Promise<void> {
await this.props.uninstall(item);
}
protected onVersionChange(version: Installable.Version) {
private onVersionChange(version: Installable.Version): void {
this.setState({ selectedVersion: version });
}
override render(): React.ReactNode {
const { item, itemRenderer } = this.props;
return itemRenderer.renderItem(
Object.assign(this.state, { item }),
this.install.bind(this),
this.uninstall.bind(this),
this.onVersionChange.bind(this)
);
}
}
export namespace ComponentListItem {
@@ -60,9 +77,11 @@ export namespace ComponentListItem {
readonly install: (item: T, version?: Installable.Version) => Promise<void>;
readonly uninstall: (item: T) => Promise<void>;
readonly itemRenderer: ListItemRenderer<T>;
readonly onFocusDidChange: () => void;
}
export interface State {
selectedVersion?: Installable.Version;
focus: boolean;
}
}

View File

@@ -1,43 +1,141 @@
import * as React from '@theia/core/shared/react';
import { Installable } from '../../../common/protocol/installable';
import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';
import {
CellMeasurer,
CellMeasurerCache,
} from 'react-virtualized/dist/commonjs/CellMeasurer';
import type {
ListRowProps,
ListRowRenderer,
} from 'react-virtualized/dist/commonjs/List';
import List from 'react-virtualized/dist/commonjs/List';
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
import { Installable } from '../../../common/protocol/installable';
import { ComponentListItem } from './component-list-item';
import { ListItemRenderer } from './list-item-renderer';
function sameAs<T>(left: T[], right: T[], key: (item: T) => string): boolean {
if (left === right) {
return true;
}
const leftLength = left.length;
if (leftLength !== right.length) {
return false;
}
for (let i = 0; i < leftLength; i++) {
const leftKey = key(left[i]);
const rightKey = key(right[i]);
if (leftKey !== rightKey) {
return false;
}
}
return true;
}
export class ComponentList<T extends ArduinoComponent> extends React.Component<
ComponentList.Props<T>
> {
protected container?: HTMLElement;
private readonly cache: CellMeasurerCache;
private resizeAllFlag: boolean;
private list: List | undefined;
private mostRecentWidth: number | undefined;
constructor(props: ComponentList.Props<T>) {
super(props);
this.cache = new CellMeasurerCache({
defaultHeight: 300,
fixedWidth: true,
});
}
override render(): React.ReactNode {
return (
<div className={'items-container'} ref={this.setRef}>
{this.props.items.map((item) => this.createItem(item))}
</div>
<AutoSizer>
{({ width, height }) => {
if (this.mostRecentWidth && this.mostRecentWidth !== width) {
this.resizeAllFlag = true;
setTimeout(() => this.clearAll(), 0);
}
this.mostRecentWidth = width;
return (
<List
className={'items-container'}
rowRenderer={this.createItem}
height={height}
width={width}
rowCount={this.props.items.length}
rowHeight={this.cache.rowHeight}
deferredMeasurementCache={this.cache}
ref={this.setListRef}
/>
);
}}
</AutoSizer>
);
}
override componentDidMount(): void {
if (this.container && this.props.resolveContainer) {
this.props.resolveContainer(this.container);
override componentDidUpdate(prevProps: ComponentList.Props<T>): void {
if (
this.resizeAllFlag ||
!sameAs(this.props.items, prevProps.items, this.props.itemLabel)
) {
this.clearAll(true);
}
}
protected setRef = (element: HTMLElement | null) => {
this.container = element || undefined;
private setListRef = (ref: List | null): void => {
this.list = ref || 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}
/>
);
private clearAll(scrollToTop = false): void {
this.resizeAllFlag = false;
this.cache.clearAll();
if (this.list) {
this.list.recomputeRowHeights();
if (scrollToTop) {
this.list.scrollToPosition(0);
}
}
}
private clear(index: number): void {
this.cache.clear(index, 0);
this.list?.recomputeRowHeights(index);
// Update the last item if the if the one before was updated
if (index === this.props.items.length - 2) {
this.cache.clear(index + 1, 0);
this.list?.recomputeRowHeights(index + 1);
}
}
private createItem: ListRowRenderer = ({
index,
parent,
key,
style,
}: ListRowProps): React.ReactNode => {
const item = this.props.items[index];
return (
<CellMeasurer
cache={this.cache}
columnIndex={0}
key={key}
rowIndex={index}
parent={parent}
>
<div style={style}>
<ComponentListItem<T>
key={this.props.itemLabel(item)}
item={item}
itemRenderer={this.props.itemRenderer}
install={this.props.install}
uninstall={this.props.uninstall}
onFocusDidChange={() => this.clear(index)}
/>
</div>
</CellMeasurer>
);
};
}
export namespace ComponentList {
@@ -48,6 +146,5 @@ export namespace ComponentList {
readonly itemRenderer: ListItemRenderer<T>;
readonly install: (item: T, version?: Installable.Version) => Promise<void>;
readonly uninstall: (item: T) => Promise<void>;
readonly resolveContainer: (element: HTMLElement) => void;
}
}

View File

@@ -0,0 +1,121 @@
import { injectable } from '@theia/core/shared/inversify';
import * as React from '@theia/core/shared/react';
import {
BoardSearch,
LibrarySearch,
Searchable,
} from '../../../common/protocol';
@injectable()
export abstract class FilterRenderer<S extends Searchable.Options> {
render(
options: S,
handlePropChange: (prop: keyof S, value: S[keyof S]) => void
): React.ReactNode {
const props = this.props();
return (
<div className="filter-bar">
{Object.entries(options)
.filter(([prop]) => props.includes(prop as keyof S))
.map(([prop, value]) => (
<div key={prop} className="filter">
<div className="filter-label">
{`${this.propertyLabel(prop as keyof S)}:`}
</div>
<select
className="theia-select"
value={value}
onChange={(event) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
handlePropChange(prop as keyof S, event.target.value as any)
}
>
{this.options(prop as keyof S).map((key) => (
<option key={key} value={key}>
{this.valueLabel(prop as keyof S, key)}
</option>
))}
</select>
</div>
))}
</div>
);
}
protected abstract props(): (keyof S)[];
protected abstract options(prop: keyof S): string[];
protected abstract valueLabel(prop: keyof S, key: string): string;
protected abstract propertyLabel(prop: keyof S): string;
}
@injectable()
export class BoardsFilterRenderer extends FilterRenderer<BoardSearch> {
protected props(): (keyof BoardSearch)[] {
return ['type'];
}
protected options(prop: keyof BoardSearch): string[] {
switch (prop) {
case 'type':
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return BoardSearch.TypeLiterals as any;
default:
throw new Error(`Unexpected prop: ${prop}`);
}
}
protected valueLabel(prop: keyof BoardSearch, key: string): string {
switch (prop) {
case 'type':
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (BoardSearch.TypeLabels as any)[key];
default:
throw new Error(`Unexpected key: ${prop}`);
}
}
protected propertyLabel(prop: keyof BoardSearch): string {
switch (prop) {
case 'type':
return BoardSearch.PropertyLabels[prop];
default:
throw new Error(`Unexpected key: ${prop}`);
}
}
}
@injectable()
export class LibraryFilterRenderer extends FilterRenderer<LibrarySearch> {
protected props(): (keyof LibrarySearch)[] {
return ['type', 'topic'];
}
protected options(prop: keyof LibrarySearch): string[] {
switch (prop) {
case 'type':
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return LibrarySearch.TypeLiterals as any;
case 'topic':
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return LibrarySearch.TopicLiterals as any;
default:
throw new Error(`Unexpected prop: ${prop}`);
}
}
protected propertyLabel(prop: keyof LibrarySearch): string {
switch (prop) {
case 'type':
case 'topic':
return LibrarySearch.PropertyLabels[prop];
default:
throw new Error(`Unexpected key: ${prop}`);
}
}
protected valueLabel(prop: keyof LibrarySearch, key: string): string {
switch (prop) {
case 'type':
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (LibrarySearch.TypeLabels as any)[key] as any;
case 'topic':
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (LibrarySearch.TopicLabels as any)[key] as any;
default:
throw new Error(`Unexpected prop: ${prop}`);
}
}
}

View File

@@ -14,25 +14,30 @@ import { ComponentList } from './component-list';
import { ListItemRenderer } from './list-item-renderer';
import { ResponseServiceClient } from '../../../common/protocol';
import { nls } from '@theia/core/lib/common';
import { FilterRenderer } from './filter-renderer';
export class FilterableListContainer<
T extends ArduinoComponent
T extends ArduinoComponent,
S extends Searchable.Options
> extends React.Component<
FilterableListContainer.Props<T>,
FilterableListContainer.State<T>
FilterableListContainer.Props<T, S>,
FilterableListContainer.State<T, S>
> {
constructor(props: Readonly<FilterableListContainer.Props<T>>) {
constructor(props: Readonly<FilterableListContainer.Props<T, S>>) {
super(props);
this.state = {
filterText: '',
searchOptions: props.defaultSearchOptions,
items: [],
};
}
override componentDidMount(): void {
this.search = debounce(this.search, 500);
this.handleFilterTextChange('');
this.props.filterTextChangeEvent(this.handleFilterTextChange.bind(this));
this.search(this.state.searchOptions);
this.props.searchOptionsDidChange((newSearchOptions) => {
const { searchOptions } = this.state;
this.setSearchOptionsAndUpdate({ ...searchOptions, ...newSearchOptions });
});
}
override componentDidUpdate(): void {
@@ -44,30 +49,38 @@ export class FilterableListContainer<
override render(): React.ReactNode {
return (
<div className={'filterable-list-container'}>
{this.renderSearchFilter()}
{this.renderSearchBar()}
{this.renderSearchFilter()}
{this.renderComponentList()}
</div>
);
}
protected renderSearchFilter(): React.ReactNode {
return undefined;
return (
<>
{this.props.filterRenderer.render(
this.state.searchOptions,
this.handlePropChange.bind(this)
)}
</>
);
}
protected renderSearchBar(): React.ReactNode {
return (
<SearchBar
resolveFocus={this.props.resolveFocus}
filterText={this.state.filterText}
onFilterTextChanged={this.handleFilterTextChange}
filterText={this.state.searchOptions.query ?? ''}
onFilterTextChanged={(query) =>
this.handlePropChange('query', query as S['query'])
}
/>
);
}
protected renderComponentList(): React.ReactNode {
const { itemLabel, itemDeprecated, resolveContainer, itemRenderer } =
this.props;
const { itemLabel, itemDeprecated, itemRenderer } = this.props;
return (
<ComponentList<T>
items={this.state.items}
@@ -76,22 +89,26 @@ export class FilterableListContainer<
itemRenderer={itemRenderer}
install={this.install.bind(this)}
uninstall={this.uninstall.bind(this)}
resolveContainer={resolveContainer}
/>
);
}
protected handleFilterTextChange = (
filterText: string = this.state.filterText
) => {
this.setState({ filterText });
this.search(filterText);
protected handlePropChange = (prop: keyof S, value: S[keyof S]): void => {
const searchOptions = {
...this.state.searchOptions,
[prop]: value,
};
this.setSearchOptionsAndUpdate(searchOptions);
};
protected search(query: string): void {
private setSearchOptionsAndUpdate(searchOptions: S) {
this.setState({ searchOptions }, () => this.search(searchOptions));
}
protected search(searchOptions: S): void {
const { searchable } = this.props;
searchable
.search({ query: query.trim() })
.search(searchOptions)
.then((items) => this.setState({ items: this.sort(items) }));
}
@@ -119,7 +136,7 @@ export class FilterableListContainer<
` ${item.name}:${version}`,
run: ({ progressId }) => install({ item, progressId, version }),
});
const items = await searchable.search({ query: this.state.filterText });
const items = await searchable.search(this.state.searchOptions);
this.setState({ items: this.sort(items) });
}
@@ -147,21 +164,25 @@ export class FilterableListContainer<
}`,
run: ({ progressId }) => uninstall({ item, progressId }),
});
const items = await searchable.search({ query: this.state.filterText });
const items = await searchable.search(this.state.searchOptions);
this.setState({ items: this.sort(items) });
}
}
export namespace FilterableListContainer {
export interface Props<T extends ArduinoComponent> {
readonly container: ListWidget<T>;
readonly searchable: Searchable<T>;
export interface Props<
T extends ArduinoComponent,
S extends Searchable.Options
> {
readonly defaultSearchOptions: S;
readonly container: ListWidget<T, S>;
readonly searchable: Searchable<T, S>;
readonly itemLabel: (item: T) => string;
readonly itemDeprecated: (item: T) => boolean;
readonly itemRenderer: ListItemRenderer<T>;
readonly resolveContainer: (element: HTMLElement) => void;
readonly filterRenderer: FilterRenderer<S>;
readonly resolveFocus: (element: HTMLElement | undefined) => void;
readonly filterTextChangeEvent: Event<string | undefined>;
readonly searchOptionsDidChange: Event<Partial<S> | undefined>;
readonly messageService: MessageService;
readonly responseService: ResponseServiceClient;
readonly install: ({
@@ -183,8 +204,8 @@ export namespace FilterableListContainer {
readonly commandService: CommandService;
}
export interface State<T> {
filterText: string;
export interface State<T, S extends Searchable.Options> {
searchOptions: S;
items: T[];
}
}

View File

@@ -14,7 +14,7 @@ export class ListItemRenderer<T extends ArduinoComponent> {
protected onMoreInfoClick = (
event: React.SyntheticEvent<HTMLAnchorElement, Event>
) => {
): void => {
const { target } = event.nativeEvent;
if (target instanceof HTMLAnchorElement) {
this.windowService.openNewWindow(target.href, { external: true });
@@ -28,7 +28,7 @@ export class ListItemRenderer<T extends ArduinoComponent> {
uninstall: (item: T) => Promise<void>,
onVersionChange: (version: Installable.Version) => void
): React.ReactNode {
const { item } = input;
const { item, focus } = input;
let nameAndAuthor: JSX.Element;
if (item.name && item.author) {
const name = <span className="name">{item.name}</span>;
@@ -120,10 +120,12 @@ export class ListItemRenderer<T extends ArduinoComponent> {
{description}
</div>
<div className="info">{moreInfo}</div>
<div className="footer">
{versions}
{installButton}
</div>
{focus && (
<div className="footer">
{versions}
{installButton}
</div>
)}
</div>
);
}

View File

@@ -3,13 +3,20 @@ import { FrontendApplicationContribution } from '@theia/core/lib/browser/fronten
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
import { ListWidget } from './list-widget';
import { Searchable } from '../../../common/protocol';
@injectable()
export abstract class ListWidgetFrontendContribution<T extends ArduinoComponent>
extends AbstractViewContribution<ListWidget<T>>
export abstract class ListWidgetFrontendContribution<
T extends ArduinoComponent,
S extends Searchable.Options
>
extends AbstractViewContribution<ListWidget<T, S>>
implements FrontendApplicationContribution
{
async initializeLayout(): Promise<void> {}
async initializeLayout(): Promise<void> {
// TS requires at least one method from `FrontendApplicationContribution`.
// Expected to be empty.
}
override registerMenus(): void {
// NOOP

View File

@@ -6,9 +6,7 @@ import {
} from '@theia/core/shared/inversify';
import { Widget } from '@theia/core/shared/@phosphor/widgets';
import { Message } from '@theia/core/shared/@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 { CommandService } from '@theia/core/lib/common/command';
import { MessageService } from '@theia/core/lib/common/message-service';
@@ -21,10 +19,12 @@ import {
import { FilterableListContainer } from './filterable-list-container';
import { ListItemRenderer } from './list-item-renderer';
import { NotificationCenter } from '../../notification-center';
import { FilterRenderer } from './filter-renderer';
@injectable()
export abstract class ListWidget<
T extends ArduinoComponent
T extends ArduinoComponent,
S extends Searchable.Options
> extends ReactWidget {
@inject(MessageService)
protected readonly messageService: MessageService;
@@ -42,9 +42,8 @@ export abstract class ListWidget<
* 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 | undefined
protected readonly searchOptionsChangeEmitter = new Emitter<
Partial<S> | undefined
>();
/**
* Instead of running an `update` from the `postConstruct` `init` method,
@@ -52,7 +51,7 @@ export abstract class ListWidget<
*/
protected firstActivate = true;
constructor(protected options: ListWidget.Options<T>) {
constructor(protected options: ListWidget.Options<T, S>) {
super();
const { id, label, iconClass } = options;
this.id = id;
@@ -62,10 +61,8 @@ export abstract class ListWidget<
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);
this.scrollOptions = undefined;
this.toDispose.push(this.searchOptionsChangeEmitter);
}
@postConstruct()
@@ -77,10 +74,6 @@ export abstract class ListWidget<
]);
}
protected override getScrollContainer(): MaybePromise<HTMLElement> {
return this.deferredContainer.promise;
}
protected override onAfterShow(message: Message): void {
this.maybeUpdateOnFirstRender();
super.onAfterShow(message);
@@ -109,7 +102,7 @@ export abstract class ListWidget<
this.updateScrollBar();
}
protected onFocusResolved = (element: HTMLElement | undefined) => {
protected onFocusResolved = (element: HTMLElement | undefined): void => {
this.focusNode = element;
};
@@ -137,9 +130,9 @@ export abstract class ListWidget<
render(): React.ReactNode {
return (
<FilterableListContainer<T>
<FilterableListContainer<T, S>
defaultSearchOptions={this.options.defaultSearchOptions}
container={this}
resolveContainer={this.deferredContainer.resolve}
resolveFocus={this.onFocusResolved}
searchable={this.options.searchable}
install={this.install.bind(this)}
@@ -147,7 +140,8 @@ export abstract class ListWidget<
itemLabel={this.options.itemLabel}
itemDeprecated={this.options.itemDeprecated}
itemRenderer={this.options.itemRenderer}
filterTextChangeEvent={this.filterTextChangeEmitter.event}
filterRenderer={this.options.filterRenderer}
searchOptionsDidChange={this.searchOptionsChangeEmitter.event}
messageService={this.messageService}
commandService={this.commandService}
responseService={this.responseService}
@@ -159,10 +153,8 @@ export abstract class ListWidget<
* If `filterText` is defined, sets the filter text to the argument.
* If it is `undefined`, updates the view state by re-running the search with the current `filterText` term.
*/
refresh(filterText: string | undefined): void {
this.deferredContainer.promise.then(() =>
this.filterTextChangeEmitter.fire(filterText)
);
refresh(searchOptions: Partial<S> | undefined): void {
this.searchOptionsChangeEmitter.fire(searchOptions);
}
updateScrollBar(): void {
@@ -173,14 +165,19 @@ export abstract class ListWidget<
}
export namespace ListWidget {
export interface Options<T extends ArduinoComponent> {
export interface Options<
T extends ArduinoComponent,
S extends Searchable.Options
> {
readonly id: string;
readonly label: string;
readonly iconClass: string;
readonly installable: Installable<T>;
readonly searchable: Searchable<T>;
readonly searchable: Searchable<T, S>;
readonly itemLabel: (item: T) => string;
readonly itemDeprecated: (item: T) => boolean;
readonly itemRenderer: ListItemRenderer<T>;
readonly filterRenderer: FilterRenderer<S>;
readonly defaultSearchOptions: S;
}
}

View File

@@ -1,3 +1,21 @@
import { nls } from '@theia/core/lib/common/nls';
export const Unknown = nls.localize('arduino/common/unknown', 'Unknown');
export const Later = nls.localize('arduino/common/later', 'Later');
export const Updatable = nls.localize('arduino/common/updateable', 'Updatable');
export const All = nls.localize('arduino/common/all', 'All');
export const Type = nls.localize('arduino/common/type', 'Type');
export const Partner = nls.localize('arduino/common/partner', 'Partner');
export const Contributed = nls.localize(
'arduino/common/contributed',
'Contributed'
);
export const Recommended = nls.localize(
'arduino/common/recommended',
'Recommended'
);
export const Retired = nls.localize('arduino/common/retired', 'Retired');
export const InstallManually = nls.localize(
'arduino/common/installManually',
'Install Manually'
);

View File

@@ -7,11 +7,13 @@ export interface ArduinoComponent {
readonly summary: string;
readonly description: string;
readonly moreInfoLink?: string;
readonly availableVersions: Installable.Version[];
readonly installable: boolean;
readonly installedVersion?: Installable.Version;
/**
* This is the `Type` in IDE (1.x) UI.
*/
readonly types: string[];
}
export namespace ArduinoComponent {
export function is(arg: any): arg is ArduinoComponent {

View File

@@ -2,6 +2,8 @@ import { naturalCompare } from './../utils';
import { Searchable } from './searchable';
import { Installable } from './installable';
import { ArduinoComponent } from './arduino-component';
import { nls } from '@theia/core/lib/common/nls';
import { All, Contributed, Partner, Type, Updatable } from '../nls';
export type AvailablePorts = Record<string, [Port, Array<Board>]>;
export namespace AvailablePorts {
@@ -131,7 +133,7 @@ export const BoardsServicePath = '/services/boards-service';
export const BoardsService = Symbol('BoardsService');
export interface BoardsService
extends Installable<BoardsPackage>,
Searchable<BoardsPackage> {
Searchable<BoardsPackage, BoardSearch> {
getState(): Promise<AvailablePorts>;
getBoardDetails(options: { fqbn: string }): Promise<BoardDetails | undefined>;
getBoardPackage(options: { id: string }): Promise<BoardsPackage | undefined>;
@@ -145,6 +147,40 @@ export interface BoardsService
}): Promise<BoardUserField[]>;
}
export interface BoardSearch extends Searchable.Options {
readonly type?: BoardSearch.Type;
}
export namespace BoardSearch {
export const TypeLiterals = [
'All',
'Updatable',
'Arduino',
'Contributed',
'Arduino Certified',
'Partner',
'Arduino@Heart',
] as const;
export type Type = typeof TypeLiterals[number];
export const TypeLabels: Record<Type, string> = {
All: All,
Updatable: Updatable,
Arduino: 'Arduino',
Contributed: Contributed,
'Arduino Certified': nls.localize(
'arduino/boardsType/arduinoCertified',
'Arduino Certified'
),
Partner: Partner,
'Arduino@Heart': 'Arduino@Heart',
};
export const PropertyLabels: Record<
keyof Omit<BoardSearch, 'query'>,
string
> = {
type: Type,
};
}
export interface Port {
readonly address: string;
readonly addressLabel: string;

View File

@@ -36,6 +36,31 @@ export namespace Installable {
};
}
export const Installed = <T extends ArduinoComponent>({
installedVersion,
}: T): boolean => {
return !!installedVersion;
};
export const Updateable = <T extends ArduinoComponent>(item: T): boolean => {
const { installedVersion } = item;
if (!installedVersion) {
return false;
}
const latestVersion = item.availableVersions[0];
if (!latestVersion) {
console.warn(
`Installed version ${installedVersion} is available for ${item.name}, but no available versions were available. Skipping.`
);
return false;
}
const result = Installable.Version.COMPARATOR(
latestVersion,
installedVersion
);
return result > 0;
};
export async function installWithProgress<
T extends ArduinoComponent
>(options: {
@@ -44,6 +69,7 @@ export namespace Installable {
responseService: ResponseServiceClient;
item: T;
version: Installable.Version;
keepOutput?: boolean;
}): Promise<void> {
const { item, version } = options;
return ExecuteWithProgress.doWithProgress({
@@ -65,6 +91,7 @@ export namespace Installable {
messageService: MessageService;
responseService: ResponseServiceClient;
item: T;
keepOutput?: boolean;
}): Promise<void> {
const { item } = options;
return ExecuteWithProgress.doWithProgress({

View File

@@ -1,13 +1,24 @@
import { Searchable } from './searchable';
import { Installable } from './installable';
import { ArduinoComponent } from './arduino-component';
import { nls } from '@theia/core/lib/common/nls';
import {
All,
Contributed,
Partner,
Recommended,
Retired,
Type,
Updatable,
} from '../nls';
export const LibraryServicePath = '/services/library-service';
export const LibraryService = Symbol('LibraryService');
export interface LibraryService
extends Installable<LibraryPackage>,
Searchable<LibraryPackage> {
Searchable<LibraryPackage, LibrarySearch> {
list(options: LibraryService.List.Options): Promise<LibraryPackage[]>;
search(options: LibrarySearch): Promise<LibraryPackage[]>;
/**
* When `installDependencies` is not set, it is `true` by default. If you want to skip the installation of required dependencies, set it to `false`.
*/
@@ -38,6 +49,86 @@ export interface LibraryService
}): Promise<LibraryDependency[]>;
}
export interface LibrarySearch extends Searchable.Options {
readonly type?: LibrarySearch.Type;
readonly topic?: LibrarySearch.Topic;
}
export namespace LibrarySearch {
export const TypeLiterals = [
'All',
'Updatable',
'Installed',
'Arduino',
'Partner',
'Recommended',
'Contributed',
'Retired',
] as const;
export type Type = typeof TypeLiterals[number];
export const TypeLabels: Record<Type, string> = {
All: All,
Updatable: Updatable,
Installed: nls.localize('arduino/libraryType/installed', 'Installed'),
Arduino: 'Arduino',
Partner: Partner,
Recommended: Recommended,
Contributed: Contributed,
Retired: Retired,
};
export const TopicLiterals = [
'All',
'Communication',
'Data Processing',
'Data Storage',
'Device Control',
'Display',
'Other',
'Sensors',
'Signal Input/Output',
'Timing',
'Uncategorized',
] as const;
export type Topic = typeof TopicLiterals[number];
export const TopicLabels: Record<Topic, string> = {
All: All,
Communication: nls.localize(
'arduino/libraryTopic/communication',
'Communication'
),
'Data Processing': nls.localize(
'arduino/libraryTopic/dataProcessing',
'Data Processing'
),
'Data Storage': nls.localize(
'arduino/libraryTopic/dataStorage',
'Data Storage'
),
'Device Control': nls.localize(
'arduino/libraryTopic/deviceControl',
'Device Control'
),
Display: nls.localize('arduino/libraryTopic/display', 'Display'),
Other: nls.localize('arduino/libraryTopic/other', 'Other'),
Sensors: nls.localize('arduino/libraryTopic/sensors', 'Sensors'),
'Signal Input/Output': nls.localize(
'arduino/libraryTopic/signalInputOutput',
'Signal Input/Output'
),
Timing: nls.localize('arduino/libraryTopic/timing', 'Timing'),
Uncategorized: nls.localize(
'arduino/libraryTopic/uncategorized',
'Uncategorized'
),
};
export const PropertyLabels: Record<
keyof Omit<LibrarySearch, 'query'>,
string
> = {
topic: nls.localize('arduino/librarySearchProperty/topic', 'Topic'),
type: Type,
};
}
export namespace LibraryService {
export namespace List {
export interface Options {
@@ -85,6 +176,10 @@ export interface LibraryPackage extends ArduinoComponent {
readonly exampleUris: string[];
readonly location: LibraryLocation;
readonly installDirUri?: string;
/**
* This is the `Topic` in the IDE (1.x) UI.
*/
readonly category: string;
}
export namespace LibraryPackage {
export function is(arg: any): arg is LibraryPackage {

View File

@@ -39,7 +39,7 @@ export namespace ExecuteWithProgress {
);
}
async function withProgress<T>(
export async function withProgress<T>(
text: string,
messageService: MessageService,
cb: (progress: Progress, token: CancellationToken) => Promise<T>

View File

@@ -1,5 +1,5 @@
export interface Searchable<T> {
search(options: Searchable.Options): Promise<T[]>;
export interface Searchable<T, O extends Searchable.Options> {
search(options: O): Promise<T[]>;
}
export namespace Searchable {
export interface Options {

View File

@@ -16,6 +16,7 @@ import {
AvailablePorts,
BoardWithPackage,
BoardUserField,
BoardSearch,
} from '../common/protocol';
import {
PlatformInstallRequest,
@@ -264,7 +265,7 @@ export class BoardsServiceImpl
}));
}
async search(options: { query?: string }): Promise<BoardsPackage[]> {
async search(options: BoardSearch): Promise<BoardsPackage[]> {
const coreClient = await this.coreClient;
const { client, instance } = coreClient;
@@ -310,6 +311,7 @@ export class BoardsServiceImpl
.map((b) => b.getName())
.join(', '),
installable: true,
types: platform.getTypeList(),
deprecated: platform.getDeprecated(),
summary: nls.localize(
'arduino/component/boardsIncluded',
@@ -380,7 +382,29 @@ export class BoardsServiceImpl
}
}
return [...packages.values()];
const filter = this.typePredicate(options);
return [...packages.values()].filter(filter);
}
private typePredicate(
options: BoardSearch
): (item: BoardsPackage) => boolean {
const { type } = options;
if (!type || type === 'All') {
return () => true;
}
switch (options.type) {
case 'Updatable':
return Installable.Updateable;
case 'Arduino':
case 'Partner':
case 'Arduino@Heart':
case 'Contributed':
case 'Arduino Certified':
return ({ types }: BoardsPackage) => !!types && types?.includes(type);
default:
throw new Error(`Unhandled type: ${options.type}`);
}
}
async install(options: {

View File

@@ -49,6 +49,7 @@ interface IArduinoCoreServiceService extends grpc.ServiceDefinition<grpc.Untyped
platformList: IArduinoCoreServiceService_IPlatformList;
libraryDownload: IArduinoCoreServiceService_ILibraryDownload;
libraryInstall: IArduinoCoreServiceService_ILibraryInstall;
libraryUpgrade: IArduinoCoreServiceService_ILibraryUpgrade;
zipLibraryInstall: IArduinoCoreServiceService_IZipLibraryInstall;
gitLibraryInstall: IArduinoCoreServiceService_IGitLibraryInstall;
libraryUninstall: IArduinoCoreServiceService_ILibraryUninstall;
@@ -348,6 +349,15 @@ interface IArduinoCoreServiceService_ILibraryInstall extends grpc.MethodDefiniti
responseSerialize: grpc.serialize<cc_arduino_cli_commands_v1_lib_pb.LibraryInstallResponse>;
responseDeserialize: grpc.deserialize<cc_arduino_cli_commands_v1_lib_pb.LibraryInstallResponse>;
}
interface IArduinoCoreServiceService_ILibraryUpgrade extends grpc.MethodDefinition<cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest, cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeResponse> {
path: "/cc.arduino.cli.commands.v1.ArduinoCoreService/LibraryUpgrade";
requestStream: false;
responseStream: true;
requestSerialize: grpc.serialize<cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest>;
requestDeserialize: grpc.deserialize<cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest>;
responseSerialize: grpc.serialize<cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeResponse>;
responseDeserialize: grpc.deserialize<cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeResponse>;
}
interface IArduinoCoreServiceService_IZipLibraryInstall extends grpc.MethodDefinition<cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallRequest, cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallResponse> {
path: "/cc.arduino.cli.commands.v1.ArduinoCoreService/ZipLibraryInstall";
requestStream: false;
@@ -465,6 +475,7 @@ export interface IArduinoCoreServiceServer {
platformList: grpc.handleUnaryCall<cc_arduino_cli_commands_v1_core_pb.PlatformListRequest, cc_arduino_cli_commands_v1_core_pb.PlatformListResponse>;
libraryDownload: grpc.handleServerStreamingCall<cc_arduino_cli_commands_v1_lib_pb.LibraryDownloadRequest, cc_arduino_cli_commands_v1_lib_pb.LibraryDownloadResponse>;
libraryInstall: grpc.handleServerStreamingCall<cc_arduino_cli_commands_v1_lib_pb.LibraryInstallRequest, cc_arduino_cli_commands_v1_lib_pb.LibraryInstallResponse>;
libraryUpgrade: grpc.handleServerStreamingCall<cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest, cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeResponse>;
zipLibraryInstall: grpc.handleServerStreamingCall<cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallRequest, cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallResponse>;
gitLibraryInstall: grpc.handleServerStreamingCall<cc_arduino_cli_commands_v1_lib_pb.GitLibraryInstallRequest, cc_arduino_cli_commands_v1_lib_pb.GitLibraryInstallResponse>;
libraryUninstall: grpc.handleServerStreamingCall<cc_arduino_cli_commands_v1_lib_pb.LibraryUninstallRequest, cc_arduino_cli_commands_v1_lib_pb.LibraryUninstallResponse>;
@@ -557,6 +568,8 @@ export interface IArduinoCoreServiceClient {
libraryDownload(request: cc_arduino_cli_commands_v1_lib_pb.LibraryDownloadRequest, metadata?: grpc.Metadata, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.LibraryDownloadResponse>;
libraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.LibraryInstallRequest, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.LibraryInstallResponse>;
libraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.LibraryInstallRequest, metadata?: grpc.Metadata, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.LibraryInstallResponse>;
libraryUpgrade(request: cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeResponse>;
libraryUpgrade(request: cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest, metadata?: grpc.Metadata, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeResponse>;
zipLibraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallRequest, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallResponse>;
zipLibraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallRequest, metadata?: grpc.Metadata, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallResponse>;
gitLibraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.GitLibraryInstallRequest, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.GitLibraryInstallResponse>;
@@ -663,6 +676,8 @@ export class ArduinoCoreServiceClient extends grpc.Client implements IArduinoCor
public libraryDownload(request: cc_arduino_cli_commands_v1_lib_pb.LibraryDownloadRequest, metadata?: grpc.Metadata, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.LibraryDownloadResponse>;
public libraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.LibraryInstallRequest, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.LibraryInstallResponse>;
public libraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.LibraryInstallRequest, metadata?: grpc.Metadata, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.LibraryInstallResponse>;
public libraryUpgrade(request: cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeResponse>;
public libraryUpgrade(request: cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest, metadata?: grpc.Metadata, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeResponse>;
public zipLibraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallRequest, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallResponse>;
public zipLibraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallRequest, metadata?: grpc.Metadata, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallResponse>;
public gitLibraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.GitLibraryInstallRequest, options?: Partial<grpc.CallOptions>): grpc.ClientReadableStream<cc_arduino_cli_commands_v1_lib_pb.GitLibraryInstallResponse>;

View File

@@ -489,6 +489,28 @@ function deserialize_cc_arduino_cli_commands_v1_LibraryUpgradeAllResponse(buffer
return cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeAllResponse.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_cc_arduino_cli_commands_v1_LibraryUpgradeRequest(arg) {
if (!(arg instanceof cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest)) {
throw new Error('Expected argument of type cc.arduino.cli.commands.v1.LibraryUpgradeRequest');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_cc_arduino_cli_commands_v1_LibraryUpgradeRequest(buffer_arg) {
return cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_cc_arduino_cli_commands_v1_LibraryUpgradeResponse(arg) {
if (!(arg instanceof cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeResponse)) {
throw new Error('Expected argument of type cc.arduino.cli.commands.v1.LibraryUpgradeResponse');
}
return Buffer.from(arg.serializeBinary());
}
function deserialize_cc_arduino_cli_commands_v1_LibraryUpgradeResponse(buffer_arg) {
return cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeResponse.deserializeBinary(new Uint8Array(buffer_arg));
}
function serialize_cc_arduino_cli_commands_v1_ListProgrammersAvailableForUploadRequest(arg) {
if (!(arg instanceof cc_arduino_cli_commands_v1_upload_pb.ListProgrammersAvailableForUploadRequest)) {
throw new Error('Expected argument of type cc.arduino.cli.commands.v1.ListProgrammersAvailableForUploadRequest');
@@ -1325,6 +1347,18 @@ libraryInstall: {
responseSerialize: serialize_cc_arduino_cli_commands_v1_LibraryInstallResponse,
responseDeserialize: deserialize_cc_arduino_cli_commands_v1_LibraryInstallResponse,
},
// Upgrade a library to the newest version available.
libraryUpgrade: {
path: '/cc.arduino.cli.commands.v1.ArduinoCoreService/LibraryUpgrade',
requestStream: false,
responseStream: true,
requestType: cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest,
responseType: cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeResponse,
requestSerialize: serialize_cc_arduino_cli_commands_v1_LibraryUpgradeRequest,
requestDeserialize: deserialize_cc_arduino_cli_commands_v1_LibraryUpgradeRequest,
responseSerialize: serialize_cc_arduino_cli_commands_v1_LibraryUpgradeResponse,
responseDeserialize: deserialize_cc_arduino_cli_commands_v1_LibraryUpgradeResponse,
},
// Install a library from a Zip File
zipLibraryInstall: {
path: '/cc.arduino.cli.commands.v1.ArduinoCoreService/ZipLibraryInstall',

View File

@@ -159,6 +159,11 @@ export class Platform extends jspb.Message {
getDeprecated(): boolean;
setDeprecated(value: boolean): Platform;
clearTypeList(): void;
getTypeList(): Array<string>;
setTypeList(value: Array<string>): Platform;
addType(value: string, index?: number): string;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): Platform.AsObject;
@@ -182,6 +187,7 @@ export namespace Platform {
boardsList: Array<Board.AsObject>,
manuallyInstalled: boolean,
deprecated: boolean,
typeList: Array<string>,
}
}

View File

@@ -987,7 +987,7 @@ proto.cc.arduino.cli.commands.v1.Programmer.prototype.setName = function(value)
* @private {!Array<number>}
* @const
*/
proto.cc.arduino.cli.commands.v1.Platform.repeatedFields_ = [8];
proto.cc.arduino.cli.commands.v1.Platform.repeatedFields_ = [8,11];
@@ -1030,7 +1030,8 @@ proto.cc.arduino.cli.commands.v1.Platform.toObject = function(includeInstance, m
boardsList: jspb.Message.toObjectList(msg.getBoardsList(),
proto.cc.arduino.cli.commands.v1.Board.toObject, includeInstance),
manuallyInstalled: jspb.Message.getBooleanFieldWithDefault(msg, 9, false),
deprecated: jspb.Message.getBooleanFieldWithDefault(msg, 10, false)
deprecated: jspb.Message.getBooleanFieldWithDefault(msg, 10, false),
typeList: (f = jspb.Message.getRepeatedField(msg, 11)) == null ? undefined : f
};
if (includeInstance) {
@@ -1108,6 +1109,10 @@ proto.cc.arduino.cli.commands.v1.Platform.deserializeBinaryFromReader = function
var value = /** @type {boolean} */ (reader.readBool());
msg.setDeprecated(value);
break;
case 11:
var value = /** @type {string} */ (reader.readString());
msg.addType(value);
break;
default:
reader.skipField();
break;
@@ -1208,6 +1213,13 @@ proto.cc.arduino.cli.commands.v1.Platform.serializeBinaryToWriter = function(mes
f
);
}
f = message.getTypeList();
if (f.length > 0) {
writer.writeRepeatedString(
11,
f
);
}
};
@@ -1411,6 +1423,43 @@ proto.cc.arduino.cli.commands.v1.Platform.prototype.setDeprecated = function(val
};
/**
* repeated string type = 11;
* @return {!Array<string>}
*/
proto.cc.arduino.cli.commands.v1.Platform.prototype.getTypeList = function() {
return /** @type {!Array<string>} */ (jspb.Message.getRepeatedField(this, 11));
};
/**
* @param {!Array<string>} value
* @return {!proto.cc.arduino.cli.commands.v1.Platform} returns this
*/
proto.cc.arduino.cli.commands.v1.Platform.prototype.setTypeList = function(value) {
return jspb.Message.setField(this, 11, value || []);
};
/**
* @param {string} value
* @param {number=} opt_index
* @return {!proto.cc.arduino.cli.commands.v1.Platform} returns this
*/
proto.cc.arduino.cli.commands.v1.Platform.prototype.addType = function(value, opt_index) {
return jspb.Message.addToRepeatedField(this, 11, value, opt_index);
};
/**
* Clears the list making it empty but non-null.
* @return {!proto.cc.arduino.cli.commands.v1.Platform} returns this
*/
proto.cc.arduino.cli.commands.v1.Platform.prototype.clearTypeList = function() {
return this.setTypeList([]);
};

View File

@@ -134,6 +134,69 @@ export namespace LibraryInstallResponse {
}
}
export class LibraryUpgradeRequest extends jspb.Message {
hasInstance(): boolean;
clearInstance(): void;
getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined;
setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): LibraryUpgradeRequest;
getName(): string;
setName(value: string): LibraryUpgradeRequest;
getNoDeps(): boolean;
setNoDeps(value: boolean): LibraryUpgradeRequest;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): LibraryUpgradeRequest.AsObject;
static toObject(includeInstance: boolean, msg: LibraryUpgradeRequest): LibraryUpgradeRequest.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: LibraryUpgradeRequest, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): LibraryUpgradeRequest;
static deserializeBinaryFromReader(message: LibraryUpgradeRequest, reader: jspb.BinaryReader): LibraryUpgradeRequest;
}
export namespace LibraryUpgradeRequest {
export type AsObject = {
instance?: cc_arduino_cli_commands_v1_common_pb.Instance.AsObject,
name: string,
noDeps: boolean,
}
}
export class LibraryUpgradeResponse extends jspb.Message {
hasProgress(): boolean;
clearProgress(): void;
getProgress(): cc_arduino_cli_commands_v1_common_pb.DownloadProgress | undefined;
setProgress(value?: cc_arduino_cli_commands_v1_common_pb.DownloadProgress): LibraryUpgradeResponse;
hasTaskProgress(): boolean;
clearTaskProgress(): void;
getTaskProgress(): cc_arduino_cli_commands_v1_common_pb.TaskProgress | undefined;
setTaskProgress(value?: cc_arduino_cli_commands_v1_common_pb.TaskProgress): LibraryUpgradeResponse;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): LibraryUpgradeResponse.AsObject;
static toObject(includeInstance: boolean, msg: LibraryUpgradeResponse): LibraryUpgradeResponse.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: LibraryUpgradeResponse, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): LibraryUpgradeResponse;
static deserializeBinaryFromReader(message: LibraryUpgradeResponse, reader: jspb.BinaryReader): LibraryUpgradeResponse;
}
export namespace LibraryUpgradeResponse {
export type AsObject = {
progress?: cc_arduino_cli_commands_v1_common_pb.DownloadProgress.AsObject,
taskProgress?: cc_arduino_cli_commands_v1_common_pb.TaskProgress.AsObject,
}
}
export class LibraryUninstallRequest extends jspb.Message {
hasInstance(): boolean;

View File

@@ -42,6 +42,8 @@ goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LibraryUninstallRequest', nu
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LibraryUninstallResponse', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LibraryUpgradeAllRequest', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LibraryUpgradeAllResponse', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.SearchedLibrary', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.ZipLibraryInstallRequest', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.ZipLibraryInstallResponse', null, global);
@@ -129,6 +131,48 @@ if (goog.DEBUG && !COMPILED) {
*/
proto.cc.arduino.cli.commands.v1.LibraryInstallResponse.displayName = 'proto.cc.arduino.cli.commands.v1.LibraryInstallResponse';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.displayName = 'proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.displayName = 'proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
@@ -1408,6 +1452,419 @@ proto.cc.arduino.cli.commands.v1.LibraryInstallResponse.prototype.hasTaskProgres
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.prototype.toObject = function(opt_includeInstance) {
return proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.toObject = function(includeInstance, msg) {
var f, obj = {
instance: (f = msg.getInstance()) && cc_arduino_cli_commands_v1_common_pb.Instance.toObject(includeInstance, f),
name: jspb.Message.getFieldWithDefault(msg, 2, ""),
noDeps: jspb.Message.getBooleanFieldWithDefault(msg, 3, false)
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest}
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest;
return proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest}
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = new cc_arduino_cli_commands_v1_common_pb.Instance;
reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.Instance.deserializeBinaryFromReader);
msg.setInstance(value);
break;
case 2:
var value = /** @type {string} */ (reader.readString());
msg.setName(value);
break;
case 3:
var value = /** @type {boolean} */ (reader.readBool());
msg.setNoDeps(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getInstance();
if (f != null) {
writer.writeMessage(
1,
f,
cc_arduino_cli_commands_v1_common_pb.Instance.serializeBinaryToWriter
);
}
f = message.getName();
if (f.length > 0) {
writer.writeString(
2,
f
);
}
f = message.getNoDeps();
if (f) {
writer.writeBool(
3,
f
);
}
};
/**
* optional Instance instance = 1;
* @return {?proto.cc.arduino.cli.commands.v1.Instance}
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.prototype.getInstance = function() {
return /** @type{?proto.cc.arduino.cli.commands.v1.Instance} */ (
jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.Instance, 1));
};
/**
* @param {?proto.cc.arduino.cli.commands.v1.Instance|undefined} value
* @return {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest} returns this
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.prototype.setInstance = function(value) {
return jspb.Message.setWrapperField(this, 1, value);
};
/**
* Clears the message field making it undefined.
* @return {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest} returns this
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.prototype.clearInstance = function() {
return this.setInstance(undefined);
};
/**
* Returns whether this field is set.
* @return {boolean}
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.prototype.hasInstance = function() {
return jspb.Message.getField(this, 1) != null;
};
/**
* optional string name = 2;
* @return {string}
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.prototype.getName = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
};
/**
* @param {string} value
* @return {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest} returns this
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.prototype.setName = function(value) {
return jspb.Message.setProto3StringField(this, 2, value);
};
/**
* optional bool no_deps = 3;
* @return {boolean}
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.prototype.getNoDeps = function() {
return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 3, false));
};
/**
* @param {boolean} value
* @return {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest} returns this
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeRequest.prototype.setNoDeps = function(value) {
return jspb.Message.setProto3BooleanField(this, 3, value);
};
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.prototype.toObject = function(opt_includeInstance) {
return proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.toObject = function(includeInstance, msg) {
var f, obj = {
progress: (f = msg.getProgress()) && cc_arduino_cli_commands_v1_common_pb.DownloadProgress.toObject(includeInstance, f),
taskProgress: (f = msg.getTaskProgress()) && cc_arduino_cli_commands_v1_common_pb.TaskProgress.toObject(includeInstance, f)
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse}
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse;
return proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse}
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = new cc_arduino_cli_commands_v1_common_pb.DownloadProgress;
reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.DownloadProgress.deserializeBinaryFromReader);
msg.setProgress(value);
break;
case 2:
var value = new cc_arduino_cli_commands_v1_common_pb.TaskProgress;
reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.TaskProgress.deserializeBinaryFromReader);
msg.setTaskProgress(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getProgress();
if (f != null) {
writer.writeMessage(
1,
f,
cc_arduino_cli_commands_v1_common_pb.DownloadProgress.serializeBinaryToWriter
);
}
f = message.getTaskProgress();
if (f != null) {
writer.writeMessage(
2,
f,
cc_arduino_cli_commands_v1_common_pb.TaskProgress.serializeBinaryToWriter
);
}
};
/**
* optional DownloadProgress progress = 1;
* @return {?proto.cc.arduino.cli.commands.v1.DownloadProgress}
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.prototype.getProgress = function() {
return /** @type{?proto.cc.arduino.cli.commands.v1.DownloadProgress} */ (
jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.DownloadProgress, 1));
};
/**
* @param {?proto.cc.arduino.cli.commands.v1.DownloadProgress|undefined} value
* @return {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse} returns this
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.prototype.setProgress = function(value) {
return jspb.Message.setWrapperField(this, 1, value);
};
/**
* Clears the message field making it undefined.
* @return {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse} returns this
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.prototype.clearProgress = function() {
return this.setProgress(undefined);
};
/**
* Returns whether this field is set.
* @return {boolean}
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.prototype.hasProgress = function() {
return jspb.Message.getField(this, 1) != null;
};
/**
* optional TaskProgress task_progress = 2;
* @return {?proto.cc.arduino.cli.commands.v1.TaskProgress}
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.prototype.getTaskProgress = function() {
return /** @type{?proto.cc.arduino.cli.commands.v1.TaskProgress} */ (
jspb.Message.getWrapperField(this, cc_arduino_cli_commands_v1_common_pb.TaskProgress, 2));
};
/**
* @param {?proto.cc.arduino.cli.commands.v1.TaskProgress|undefined} value
* @return {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse} returns this
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.prototype.setTaskProgress = function(value) {
return jspb.Message.setWrapperField(this, 2, value);
};
/**
* Clears the message field making it undefined.
* @return {!proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse} returns this
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.prototype.clearTaskProgress = function() {
return this.setTaskProgress(undefined);
};
/**
* Returns whether this field is set.
* @return {boolean}
*/
proto.cc.arduino.cli.commands.v1.LibraryUpgradeResponse.prototype.hasTaskProgress = function() {
return jspb.Message.getField(this, 2) != null;
};
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.

View File

@@ -3,6 +3,7 @@ import {
LibraryDependency,
LibraryLocation,
LibraryPackage,
LibrarySearch,
LibraryService,
} from '../common/protocol/library-service';
import { CoreClientAware } from './core-client-provider';
@@ -26,6 +27,7 @@ import { ILogger, notEmpty } from '@theia/core';
import { FileUri } from '@theia/core/lib/node';
import { ResponseService, NotificationServiceServer } from '../common/protocol';
import { ExecuteWithProgress } from './grpc-progressible';
import { duration } from '../common/decorators';
@injectable()
export class LibraryServiceImpl
@@ -44,7 +46,8 @@ export class LibraryServiceImpl
@inject(NotificationServiceServer)
protected readonly notificationServer: NotificationServiceServer;
async search(options: { query?: string }): Promise<LibraryPackage[]> {
@duration()
async search(options: LibrarySearch): Promise<LibraryPackage[]> {
const coreClient = await this.coreClient;
const { client, instance } = coreClient;
@@ -78,7 +81,6 @@ export class LibraryServiceImpl
const items = resp
.getLibrariesList()
.filter((item) => !!item.getLatest())
.slice(0, 50)
.map((item) => {
// TODO: This seems to contain only the latest item instead of all of the items.
const availableVersions = item
@@ -103,7 +105,42 @@ export class LibraryServiceImpl
);
});
return items;
const typePredicate = this.typePredicate(options);
const topicPredicate = this.topicPredicate(options);
return items.filter((item) => typePredicate(item) && topicPredicate(item));
}
private typePredicate(
options: LibrarySearch
): (item: LibraryPackage) => boolean {
const { type } = options;
if (!type || type === 'All') {
return () => true;
}
switch (options.type) {
case 'Installed':
return Installable.Installed;
case 'Updatable':
return Installable.Updateable;
case 'Arduino':
case 'Partner':
case 'Recommended':
case 'Contributed':
case 'Retired':
return ({ types }: LibraryPackage) => !!types && types.includes(type);
default:
throw new Error(`Unhandled type: ${options.type}`);
}
}
private topicPredicate(
options: LibrarySearch
): (item: LibraryPackage) => boolean {
const { topic } = options;
if (!topic || topic === 'All') {
return () => true;
}
return (item: LibraryPackage) => item.category === topic;
}
async list({
@@ -408,5 +445,7 @@ function toLibrary(
description: lib.getSentence(),
moreInfoLink: lib.getWebsite(),
summary: lib.getParagraph(),
category: lib.getCategory(),
types: lib.getTypesList(),
};
}

View File

@@ -53,6 +53,7 @@ export const aPackage: BoardsPackage = {
moreInfoLink: 'http://www.some-url.lol/',
name: 'Some Arduino Package',
summary: 'Boards included in this package:',
types: ['Arduino'],
};
export const anInstalledPackage: BoardsPackage = {