From 00959d2550c958ab51f5f4e24ff9fe31421316ea Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Sat, 27 Aug 2022 12:04:35 +0200 Subject: [PATCH 01/22] Lib search API Signed-off-by: Akos Kitta --- .../src/common/protocol/library-service.ts | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/arduino-ide-extension/src/common/protocol/library-service.ts b/arduino-ide-extension/src/common/protocol/library-service.ts index 235ca9ef6..b34bb1c44 100644 --- a/arduino-ide-extension/src/common/protocol/library-service.ts +++ b/arduino-ide-extension/src/common/protocol/library-service.ts @@ -8,6 +8,7 @@ export interface LibraryService extends Installable, Searchable { list(options: LibraryService.List.Options): Promise; + search(options: LibrarySearch): Promise; /** * 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 +39,38 @@ export interface LibraryService }): Promise; } +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 TopicLiterals = [ + 'All', + 'Communication', + 'Data Processing', + 'Data Storage', + 'Device Control', + 'Display', + 'Others', + 'Sensors', + 'Signal Input/Output', + 'Timing', + 'Uncategorized', + ]; + export type Topic = typeof TopicLiterals[number]; +} + export namespace LibraryService { export namespace List { export interface Options { From 28a506b26614f42c8bb9fb91cfe2dd6bd9ded595 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Sat, 27 Aug 2022 18:43:58 +0200 Subject: [PATCH 02/22] Do not start tasks to reset scroll in pref editor. Signed-off-by: Akos Kitta --- .../browser/arduino-ide-frontend-module.ts | 16 ++++++++++++++++ .../preferences/preference-editor-widget.ts | 19 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 arduino-ide-extension/src/browser/theia/preferences/preference-editor-widget.ts 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 5c6c2bdea..a29287f03 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -323,6 +323,10 @@ 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'; const registerArduinoThemes = () => { const themes: MonacoThemeJson[] = [ @@ -845,6 +849,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); diff --git a/arduino-ide-extension/src/browser/theia/preferences/preference-editor-widget.ts b/arduino-ide-extension/src/browser/theia/preferences/preference-editor-widget.ts new file mode 100644 index 000000000..bcac5c084 --- /dev/null +++ b/arduino-ide-extension/src/browser/theia/preferences/preference-editor-widget.ts @@ -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? + } + } +} From 168bbf17494ffeae45528d6bb93495835d5b69ea Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Sat, 27 Aug 2022 18:45:45 +0200 Subject: [PATCH 03/22] Do not limit lib search results. Signed-off-by: Akos Kitta --- arduino-ide-extension/src/node/library-service-impl.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arduino-ide-extension/src/node/library-service-impl.ts b/arduino-ide-extension/src/node/library-service-impl.ts index c205826b5..805255df5 100644 --- a/arduino-ide-extension/src/node/library-service-impl.ts +++ b/arduino-ide-extension/src/node/library-service-impl.ts @@ -26,6 +26,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,6 +45,7 @@ export class LibraryServiceImpl @inject(NotificationServiceServer) protected readonly notificationServer: NotificationServiceServer; + @duration() async search(options: { query?: string }): Promise { const coreClient = await this.coreClient; const { client, instance } = coreClient; @@ -78,7 +80,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 From 5600f9dacf1c7e2a3f8b00aa57e561f29d38436b Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Sat, 27 Aug 2022 18:46:08 +0200 Subject: [PATCH 04/22] Virtualized list item rendering. Signed-off-by: Akos Kitta --- .../component-list/component-list-item.tsx | 2 +- .../widgets/component-list/component-list.tsx | 130 +++++++++++++++--- .../filterable-list-container.tsx | 8 +- .../widgets/component-list/list-widget.tsx | 18 +-- 4 files changed, 116 insertions(+), 42 deletions(-) diff --git a/arduino-ide-extension/src/browser/widgets/component-list/component-list-item.tsx b/arduino-ide-extension/src/browser/widgets/component-list/component-list-item.tsx index 39c0a2ce0..7069a0036 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/component-list-item.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/component-list-item.tsx @@ -39,7 +39,7 @@ export class ComponentListItem< await this.props.uninstall(item); } - protected onVersionChange(version: Installable.Version) { + protected onVersionChange(version: Installable.Version): void { this.setState({ selectedVersion: version }); } diff --git a/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx b/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx index 42dce70b8..b2394e68f 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx @@ -1,43 +1,129 @@ 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'; export class ComponentList extends React.Component< - ComponentList.Props + ComponentList.Props, + ComponentList.State > { - protected container?: HTMLElement; + private readonly cache: CellMeasurerCache; + private resizeAllFlag: boolean; + private list: List | undefined; + private mostRecentWidth: number | undefined; + + constructor(props: ComponentList.Props) { + super(props); + this.state = { focusIndex: 'none' }; + this.cache = new CellMeasurerCache({ + defaultHeight: 200, + fixedWidth: true, + }); + } override render(): React.ReactNode { return ( -
- {this.props.items.map((item) => this.createItem(item))} -
+ + {({ width, height }) => { + if (this.mostRecentWidth && this.mostRecentWidth !== width) { + this.resizeAllFlag = true; + setTimeout(this.clearAll, 0); + } + this.mostRecentWidth = width; + return ( + + ); + }} + ); } - override componentDidMount(): void { - if (this.container && this.props.resolveContainer) { - this.props.resolveContainer(this.container); + override componentDidUpdate( + prevProps: ComponentList.Props, + prevState: ComponentList.State + ): void { + if (this.resizeAllFlag || this.props.items !== prevProps.items) { + this.clearAll(); + } else if (this.state.focusIndex !== prevState.focusIndex) { + if (typeof this.state.focusIndex === 'number') { + this.clear(this.state.focusIndex); + } + if (typeof prevState.focusIndex === 'number') { + this.clear(prevState.focusIndex); + } } } - 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 { + private clearAll(): void { + this.resizeAllFlag = false; + this.cache.clearAll(); + if (this.list) { + this.list.recomputeRowHeights(); + } + } + + private clear(index: number): void { + this.cache.clear(index, 0); + if (this.list) { + this.list.recomputeRowHeights(index); + } + } + + private createItem: ListRowRenderer = ({ + index, + parent, + key, + style, + }: ListRowProps): React.ReactNode => { + const item = this.props.items[index]; return ( - - key={this.props.itemLabel(item)} - item={item} - itemRenderer={this.props.itemRenderer} - install={this.props.install} - uninstall={this.props.uninstall} - /> + +
this.setState({ focusIndex: index })} + onMouseLeave={() => this.setState({ focusIndex: 'none' })} + > + + key={this.props.itemLabel(item)} + item={item} + itemRenderer={this.props.itemRenderer} + install={this.props.install} + uninstall={this.props.uninstall} + /> +
+
); - } + }; } export namespace ComponentList { @@ -48,6 +134,8 @@ export namespace ComponentList { readonly itemRenderer: ListItemRenderer; readonly install: (item: T, version?: Installable.Version) => Promise; readonly uninstall: (item: T) => Promise; - readonly resolveContainer: (element: HTMLElement) => void; + } + export interface State { + focusIndex: number | 'none'; } } diff --git a/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx b/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx index ce132bc5f..494b12435 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx @@ -66,8 +66,7 @@ export class FilterableListContainer< } protected renderComponentList(): React.ReactNode { - const { itemLabel, itemDeprecated, resolveContainer, itemRenderer } = - this.props; + const { itemLabel, itemDeprecated, itemRenderer } = this.props; return ( items={this.state.items} @@ -76,14 +75,13 @@ export class FilterableListContainer< itemRenderer={itemRenderer} install={this.install.bind(this)} uninstall={this.uninstall.bind(this)} - resolveContainer={resolveContainer} /> ); } protected handleFilterTextChange = ( filterText: string = this.state.filterText - ) => { + ): void => { this.setState({ filterText }); this.search(filterText); }; @@ -159,7 +157,7 @@ export namespace FilterableListContainer { readonly itemLabel: (item: T) => string; readonly itemDeprecated: (item: T) => boolean; readonly itemRenderer: ListItemRenderer; - readonly resolveContainer: (element: HTMLElement) => void; + // readonly resolveContainer: (element: HTMLElement) => void; readonly resolveFocus: (element: HTMLElement | undefined) => void; readonly filterTextChangeEvent: Event; readonly messageService: MessageService; diff --git a/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx b/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx index 3e562c140..15010e9cc 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx @@ -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'; @@ -42,7 +40,7 @@ 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(); + // protected readonly deferredContainer = new Deferred(); protected readonly filterTextChangeEmitter = new Emitter< string | undefined >(); @@ -62,9 +60,6 @@ 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); } @@ -77,10 +72,6 @@ export abstract class ListWidget< ]); } - protected override getScrollContainer(): MaybePromise { - return this.deferredContainer.promise; - } - protected override onAfterShow(message: Message): void { this.maybeUpdateOnFirstRender(); super.onAfterShow(message); @@ -109,7 +100,7 @@ export abstract class ListWidget< this.updateScrollBar(); } - protected onFocusResolved = (element: HTMLElement | undefined) => { + protected onFocusResolved = (element: HTMLElement | undefined): void => { this.focusNode = element; }; @@ -139,7 +130,6 @@ export abstract class ListWidget< return ( container={this} - resolveContainer={this.deferredContainer.resolve} resolveFocus={this.onFocusResolved} searchable={this.options.searchable} install={this.install.bind(this)} @@ -160,9 +150,7 @@ export abstract class ListWidget< * 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) - ); + this.filterTextChangeEmitter.fire(filterText); } updateScrollBar(): void { From ca3b75484f038fbb4b530cde14a857e14d4f771d Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Sat, 27 Aug 2022 19:12:01 +0200 Subject: [PATCH 05/22] Disabled scrolling in widget. Signed-off-by: Akos Kitta --- .../src/browser/widgets/component-list/list-widget.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx b/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx index 15010e9cc..c2e95b045 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx @@ -60,6 +60,7 @@ 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 = undefined; this.toDispose.push(this.filterTextChangeEmitter); } From a4a91d645a44a21cf60b14e4599bfc5e303a187b Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Sat, 27 Aug 2022 19:16:54 +0200 Subject: [PATCH 06/22] Reset scrollbar on update. Signed-off-by: Akos Kitta --- .../src/browser/widgets/component-list/component-list.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx b/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx index b2394e68f..ec9726fcc 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx @@ -63,7 +63,7 @@ export class ComponentList extends React.Component< prevState: ComponentList.State ): void { if (this.resizeAllFlag || this.props.items !== prevProps.items) { - this.clearAll(); + this.clearAll(true); } else if (this.state.focusIndex !== prevState.focusIndex) { if (typeof this.state.focusIndex === 'number') { this.clear(this.state.focusIndex); @@ -78,11 +78,14 @@ export class ComponentList extends React.Component< this.list = ref || undefined; }; - private clearAll(): void { + private clearAll(scrollToTop = false): void { this.resizeAllFlag = false; this.cache.clearAll(); if (this.list) { this.list.recomputeRowHeights(); + if (scrollToTop) { + this.list.scrollToPosition(0); + } } } From 2b0d3976fa4743631d3abf86084360e522fc246f Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Sat, 27 Aug 2022 20:00:19 +0200 Subject: [PATCH 07/22] Generic type for the search options. Signed-off-by: Akos Kitta --- .../src/browser/boards/boards-list-widget.ts | 3 +- .../boards-widget-frontend-contribution.ts | 10 +++- .../browser/library/library-list-widget.ts | 16 ++++-- .../filterable-list-container.tsx | 52 +++++++++++-------- .../list-widget-frontend-contribution.ts | 13 +++-- .../widgets/component-list/list-widget.tsx | 14 +++-- .../src/common/protocol/boards-service.ts | 18 ++++++- .../src/common/protocol/library-service.ts | 2 +- .../src/common/protocol/searchable.ts | 4 +- 9 files changed, 92 insertions(+), 40 deletions(-) diff --git a/arduino-ide-extension/src/browser/boards/boards-list-widget.ts b/arduino-ide-extension/src/browser/boards/boards-list-widget.ts index f199063f8..0e38ede3e 100644 --- a/arduino-ide-extension/src/browser/boards/boards-list-widget.ts +++ b/arduino-ide-extension/src/browser/boards/boards-list-widget.ts @@ -4,6 +4,7 @@ import { postConstruct, } from '@theia/core/shared/inversify'; import { + BoardSearch, BoardsPackage, BoardsService, } from '../../common/protocol/boards-service'; @@ -12,7 +13,7 @@ import { ListItemRenderer } from '../widgets/component-list/list-item-renderer'; import { nls } from '@theia/core/lib/common'; @injectable() -export class BoardsListWidget extends ListWidget { +export class BoardsListWidget extends ListWidget { static WIDGET_ID = 'boards-list-widget'; static WIDGET_LABEL = nls.localize('arduino/boardsManager', 'Boards Manager'); diff --git a/arduino-ide-extension/src/browser/boards/boards-widget-frontend-contribution.ts b/arduino-ide-extension/src/browser/boards/boards-widget-frontend-contribution.ts index af31aff6e..a6e535d6f 100644 --- a/arduino-ide-extension/src/browser/boards/boards-widget-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/boards/boards-widget-frontend-contribution.ts @@ -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 { +export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution< + BoardsPackage, + BoardSearch +> { constructor() { super({ widgetId: BoardsListWidget.WIDGET_ID, diff --git a/arduino-ide-extension/src/browser/library/library-list-widget.ts b/arduino-ide-extension/src/browser/library/library-list-widget.ts index eaeacc520..3c325d20c 100644 --- a/arduino-ide-extension/src/browser/library/library-list-widget.ts +++ b/arduino-ide-extension/src/browser/library/library-list-widget.ts @@ -1,10 +1,15 @@ -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'; @@ -13,7 +18,10 @@ import { ListItemRenderer } from '../widgets/component-list/list-item-renderer'; import { nls } from '@theia/core/lib/common'; @injectable() -export class LibraryListWidget extends ListWidget { +export class LibraryListWidget extends ListWidget< + LibraryPackage, + LibrarySearch +> { static WIDGET_ID = 'library-list-widget'; static WIDGET_LABEL = nls.localize( 'arduino/library/title', @@ -41,7 +49,9 @@ export class LibraryListWidget extends ListWidget { 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) ), diff --git a/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx b/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx index 494b12435..2606fc895 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx @@ -16,23 +16,24 @@ import { ResponseServiceClient } from '../../../common/protocol'; import { nls } from '@theia/core/lib/common'; export class FilterableListContainer< - T extends ArduinoComponent + T extends ArduinoComponent, + S extends Searchable.Options > extends React.Component< - FilterableListContainer.Props, - FilterableListContainer.State + FilterableListContainer.Props, + FilterableListContainer.State > { - constructor(props: Readonly>) { + constructor(props: Readonly>) { super(props); this.state = { - filterText: '', + searchOptions: { query: '' } as S, items: [], }; } override componentDidMount(): void { this.search = debounce(this.search, 500); - this.handleFilterTextChange(''); - this.props.filterTextChangeEvent(this.handleFilterTextChange.bind(this)); + this.handleQueryChange(''); + this.props.filterTextChangeEvent(this.handleQueryChange.bind(this)); } override componentDidUpdate(): void { @@ -59,8 +60,8 @@ export class FilterableListContainer< return ( ); } @@ -79,17 +80,21 @@ export class FilterableListContainer< ); } - protected handleFilterTextChange = ( - filterText: string = this.state.filterText + protected handleQueryChange = ( + query: string = this.state.searchOptions.query ?? '' ): void => { - this.setState({ filterText }); - this.search(filterText); + const newSearchOptions = { + ...this.state.searchOptions, + query, + }; + this.setState({ searchOptions: newSearchOptions }); + this.search(newSearchOptions); }; - protected search(query: string): void { + protected search(searchOptions: S): void { const { searchable } = this.props; searchable - .search({ query: query.trim() }) + .search(searchOptions) .then((items) => this.setState({ items: this.sort(items) })); } @@ -117,7 +122,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) }); } @@ -145,15 +150,18 @@ 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 { - readonly container: ListWidget; - readonly searchable: Searchable; + export interface Props< + T extends ArduinoComponent, + S extends Searchable.Options + > { + readonly container: ListWidget; + readonly searchable: Searchable; readonly itemLabel: (item: T) => string; readonly itemDeprecated: (item: T) => boolean; readonly itemRenderer: ListItemRenderer; @@ -181,8 +189,8 @@ export namespace FilterableListContainer { readonly commandService: CommandService; } - export interface State { - filterText: string; + export interface State { + searchOptions: S; items: T[]; } } diff --git a/arduino-ide-extension/src/browser/widgets/component-list/list-widget-frontend-contribution.ts b/arduino-ide-extension/src/browser/widgets/component-list/list-widget-frontend-contribution.ts index ed9827919..6ec22ddfd 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/list-widget-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/widgets/component-list/list-widget-frontend-contribution.ts @@ -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 - extends AbstractViewContribution> +export abstract class ListWidgetFrontendContribution< + T extends ArduinoComponent, + S extends Searchable.Options + > + extends AbstractViewContribution> implements FrontendApplicationContribution { - async initializeLayout(): Promise {} + async initializeLayout(): Promise { + // TS requires at least one method from `FrontendApplicationContribution`. + // Expected to be empty. + } override registerMenus(): void { // NOOP diff --git a/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx b/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx index c2e95b045..0de6256ab 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx @@ -22,7 +22,8 @@ import { NotificationCenter } from '../../notification-center'; @injectable() export abstract class ListWidget< - T extends ArduinoComponent + T extends ArduinoComponent, + S extends Searchable.Options > extends ReactWidget { @inject(MessageService) protected readonly messageService: MessageService; @@ -50,7 +51,7 @@ export abstract class ListWidget< */ protected firstActivate = true; - constructor(protected options: ListWidget.Options) { + constructor(protected options: ListWidget.Options) { super(); const { id, label, iconClass } = options; this.id = id; @@ -129,7 +130,7 @@ export abstract class ListWidget< render(): React.ReactNode { return ( - + container={this} resolveFocus={this.onFocusResolved} searchable={this.options.searchable} @@ -162,12 +163,15 @@ export abstract class ListWidget< } export namespace ListWidget { - export interface Options { + export interface Options< + T extends ArduinoComponent, + S extends Searchable.Options + > { readonly id: string; readonly label: string; readonly iconClass: string; readonly installable: Installable; - readonly searchable: Searchable; + readonly searchable: Searchable; readonly itemLabel: (item: T) => string; readonly itemDeprecated: (item: T) => boolean; readonly itemRenderer: ListItemRenderer; diff --git a/arduino-ide-extension/src/common/protocol/boards-service.ts b/arduino-ide-extension/src/common/protocol/boards-service.ts index 6ff6e440f..7ecb074e5 100644 --- a/arduino-ide-extension/src/common/protocol/boards-service.ts +++ b/arduino-ide-extension/src/common/protocol/boards-service.ts @@ -131,7 +131,7 @@ export const BoardsServicePath = '/services/boards-service'; export const BoardsService = Symbol('BoardsService'); export interface BoardsService extends Installable, - Searchable { + Searchable { getState(): Promise; getBoardDetails(options: { fqbn: string }): Promise; getBoardPackage(options: { id: string }): Promise; @@ -145,6 +145,22 @@ export interface BoardsService }): Promise; } +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 interface Port { readonly address: string; readonly addressLabel: string; diff --git a/arduino-ide-extension/src/common/protocol/library-service.ts b/arduino-ide-extension/src/common/protocol/library-service.ts index b34bb1c44..be850f488 100644 --- a/arduino-ide-extension/src/common/protocol/library-service.ts +++ b/arduino-ide-extension/src/common/protocol/library-service.ts @@ -6,7 +6,7 @@ export const LibraryServicePath = '/services/library-service'; export const LibraryService = Symbol('LibraryService'); export interface LibraryService extends Installable, - Searchable { + Searchable { list(options: LibraryService.List.Options): Promise; search(options: LibrarySearch): Promise; /** diff --git a/arduino-ide-extension/src/common/protocol/searchable.ts b/arduino-ide-extension/src/common/protocol/searchable.ts index 30a056773..af6a2c02e 100644 --- a/arduino-ide-extension/src/common/protocol/searchable.ts +++ b/arduino-ide-extension/src/common/protocol/searchable.ts @@ -1,5 +1,5 @@ -export interface Searchable { - search(options: Searchable.Options): Promise; +export interface Searchable { + search(options: O): Promise; } export namespace Searchable { export interface Options { From 3ae1338fedcc2efa1bfea9e9b7de93bc30dbe848 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Sat, 27 Aug 2022 21:56:03 +0200 Subject: [PATCH 08/22] Restored raw stylings. Signed-off-by: Akos Kitta --- arduino-ide-extension/src/browser/style/list-widget.css | 6 +++--- .../src/browser/widgets/component-list/component-list.tsx | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/arduino-ide-extension/src/browser/style/list-widget.css b/arduino-ide-extension/src/browser/style/list-widget.css index 5d6babf9b..951a71e99 100644 --- a/arduino-ide-extension/src/browser/style/list-widget.css +++ b/arduino-ide-extension/src/browser/style/list-widget.css @@ -27,17 +27,17 @@ position: relative; /* To fix the `top` of the vertical toolbar. */ } -.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%); } diff --git a/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx b/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx index ec9726fcc..1824ebef3 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx @@ -43,6 +43,7 @@ export class ComponentList extends React.Component< this.mostRecentWidth = width; return ( Date: Sun, 28 Aug 2022 14:23:48 +0200 Subject: [PATCH 09/22] Moved focus tracing inside item. Signed-off-by: Akos Kitta --- .../src/browser/style/list-widget.css | 17 +----- .../component-list/component-list-item.tsx | 45 +++++++++++----- .../widgets/component-list/component-list.tsx | 54 ++++++++++--------- .../component-list/list-item-renderer.tsx | 14 ++--- 4 files changed, 70 insertions(+), 60 deletions(-) diff --git a/arduino-ide-extension/src/browser/style/list-widget.css b/arduino-ide-extension/src/browser/style/list-widget.css index 951a71e99..e86527715 100644 --- a/arduino-ide-extension/src/browser/style/list-widget.css +++ b/arduino-ide-extension/src/browser/style/list-widget.css @@ -23,8 +23,7 @@ } .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 > div:nth-child(odd) { @@ -42,14 +41,6 @@ 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 +104,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 +113,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; diff --git a/arduino-ide-extension/src/browser/widgets/component-list/component-list-item.tsx b/arduino-ide-extension/src/browser/widgets/component-list/component-list-item.tsx index 7069a0036..950df64bd 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/component-list-item.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/component-list-item.tsx @@ -14,11 +14,38 @@ export class ComponentListItem< )[0]; this.state = { selectedVersion: version, + focus: false, }; } } - protected async install(item: T): Promise { + override componentDidUpdate( + prevProps: ComponentListItem.Props, + prevState: ComponentListItem.State + ): void { + if (this.state.focus !== prevState.focus) { + this.props.onFocusDidChange(); + } + } + + override render(): React.ReactNode { + const { item, itemRenderer } = this.props; + return ( +
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) + )} +
+ ); + } + + private async install(item: T): Promise { 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 { + private async uninstall(item: T): Promise { await this.props.uninstall(item); } - protected onVersionChange(version: Installable.Version): void { + 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; readonly uninstall: (item: T) => Promise; readonly itemRenderer: ListItemRenderer; + readonly onFocusDidChange: () => void; } export interface State { selectedVersion?: Installable.Version; + focus: boolean; } } diff --git a/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx b/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx index 1824ebef3..aad9da5ae 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx @@ -14,9 +14,26 @@ import { Installable } from '../../../common/protocol/installable'; import { ComponentListItem } from './component-list-item'; import { ListItemRenderer } from './list-item-renderer'; +function sameAs(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 extends React.Component< - ComponentList.Props, - ComponentList.State + ComponentList.Props > { private readonly cache: CellMeasurerCache; private resizeAllFlag: boolean; @@ -25,9 +42,8 @@ export class ComponentList extends React.Component< constructor(props: ComponentList.Props) { super(props); - this.state = { focusIndex: 'none' }; this.cache = new CellMeasurerCache({ - defaultHeight: 200, + defaultHeight: 300, fixedWidth: true, }); } @@ -38,14 +54,13 @@ export class ComponentList extends React.Component< {({ width, height }) => { if (this.mostRecentWidth && this.mostRecentWidth !== width) { this.resizeAllFlag = true; - setTimeout(this.clearAll, 0); + setTimeout(() => this.clearAll(), 0); } this.mostRecentWidth = width; return ( extends React.Component< ); } - override componentDidUpdate( - prevProps: ComponentList.Props, - prevState: ComponentList.State - ): void { - if (this.resizeAllFlag || this.props.items !== prevProps.items) { + override componentDidUpdate(prevProps: ComponentList.Props): void { + if ( + this.resizeAllFlag || + !sameAs(this.props.items, prevProps.items, this.props.itemLabel) + ) { this.clearAll(true); - } else if (this.state.focusIndex !== prevState.focusIndex) { - if (typeof this.state.focusIndex === 'number') { - this.clear(this.state.focusIndex); - } - if (typeof prevState.focusIndex === 'number') { - this.clear(prevState.focusIndex); - } } } @@ -112,17 +120,14 @@ export class ComponentList extends React.Component< rowIndex={index} parent={parent} > -
this.setState({ focusIndex: index })} - onMouseLeave={() => this.setState({ focusIndex: 'none' })} - > +
key={this.props.itemLabel(item)} item={item} itemRenderer={this.props.itemRenderer} install={this.props.install} uninstall={this.props.uninstall} + onFocusDidChange={() => this.clear(index)} />
@@ -139,7 +144,4 @@ export namespace ComponentList { readonly install: (item: T, version?: Installable.Version) => Promise; readonly uninstall: (item: T) => Promise; } - export interface State { - focusIndex: number | 'none'; - } } diff --git a/arduino-ide-extension/src/browser/widgets/component-list/list-item-renderer.tsx b/arduino-ide-extension/src/browser/widgets/component-list/list-item-renderer.tsx index 62e1f3e1c..537f4d415 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/list-item-renderer.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/list-item-renderer.tsx @@ -14,7 +14,7 @@ export class ListItemRenderer { protected onMoreInfoClick = ( event: React.SyntheticEvent - ) => { + ): void => { const { target } = event.nativeEvent; if (target instanceof HTMLAnchorElement) { this.windowService.openNewWindow(target.href, { external: true }); @@ -28,7 +28,7 @@ export class ListItemRenderer { uninstall: (item: T) => Promise, 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 = {item.name}; @@ -120,10 +120,12 @@ export class ListItemRenderer { {description}
{moreInfo}
-
- {versions} - {installButton} -
+ {focus && ( +
+ {versions} + {installButton} +
+ )} ); } From d769858235c0edb0a66ca985f1917c27ee68d52d Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Sun, 28 Aug 2022 17:35:42 +0200 Subject: [PATCH 10/22] initial filter UI Signed-off-by: Akos Kitta --- .../browser/arduino-ide-frontend-module.ts | 6 ++ .../src/browser/boards/boards-list-widget.ts | 9 ++- .../browser/library/library-list-widget.ts | 9 ++- .../src/browser/style/list-widget.css | 7 +- .../component-list/filter-renderer.tsx | 76 +++++++++++++++++++ .../filterable-list-container.tsx | 36 +++++---- .../widgets/component-list/list-widget.tsx | 5 ++ 7 files changed, 127 insertions(+), 21 deletions(-) create mode 100644 arduino-ide-extension/src/browser/widgets/component-list/filter-renderer.tsx 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 a29287f03..dab0e7c5c 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -327,6 +327,10 @@ import { PreferencesEditorWidget as TheiaPreferencesEditorWidget } from '@theia/ 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'; const registerArduinoThemes = () => { const themes: MonacoThemeJson[] = [ @@ -368,6 +372,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) diff --git a/arduino-ide-extension/src/browser/boards/boards-list-widget.ts b/arduino-ide-extension/src/browser/boards/boards-list-widget.ts index 0e38ede3e..8b7626720 100644 --- a/arduino-ide-extension/src/browser/boards/boards-list-widget.ts +++ b/arduino-ide-extension/src/browser/boards/boards-list-widget.ts @@ -11,6 +11,7 @@ import { 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 { @@ -18,9 +19,9 @@ export class BoardsListWidget extends ListWidget { static WIDGET_LABEL = nls.localize('arduino/boardsManager', 'Boards Manager'); constructor( - @inject(BoardsService) protected service: BoardsService, - @inject(ListItemRenderer) - protected itemRenderer: ListItemRenderer + @inject(BoardsService) service: BoardsService, + @inject(ListItemRenderer) itemRenderer: ListItemRenderer, + @inject(BoardsFilterRenderer) filterRenderer: BoardsFilterRenderer ) { super({ id: BoardsListWidget.WIDGET_ID, @@ -31,6 +32,8 @@ export class BoardsListWidget extends ListWidget { itemLabel: (item: BoardsPackage) => item.name, itemDeprecated: (item: BoardsPackage) => item.deprecated, itemRenderer, + filterRenderer, + defaultSearchOptions: { query: '', type: 'All' }, }); } diff --git a/arduino-ide-extension/src/browser/library/library-list-widget.ts b/arduino-ide-extension/src/browser/library/library-list-widget.ts index 3c325d20c..5c654b615 100644 --- a/arduino-ide-extension/src/browser/library/library-list-widget.ts +++ b/arduino-ide-extension/src/browser/library/library-list-widget.ts @@ -16,6 +16,7 @@ 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< @@ -29,9 +30,9 @@ export class LibraryListWidget extends ListWidget< ); constructor( - @inject(LibraryService) protected service: LibraryService, - @inject(ListItemRenderer) - protected itemRenderer: ListItemRenderer + @inject(LibraryService) private service: LibraryService, + @inject(ListItemRenderer) itemRenderer: ListItemRenderer, + @inject(LibraryFilterRenderer) filterRenderer: LibraryFilterRenderer ) { super({ id: LibraryListWidget.WIDGET_ID, @@ -42,6 +43,8 @@ export class LibraryListWidget extends ListWidget< itemLabel: (item: LibraryPackage) => item.name, itemDeprecated: (item: LibraryPackage) => item.deprecated, itemRenderer, + filterRenderer, + defaultSearchOptions: { query: '', type: 'All', topic: 'All' }, }); } diff --git a/arduino-ide-extension/src/browser/style/list-widget.css b/arduino-ide-extension/src/browser/style/list-widget.css index e86527715..1ec99c64f 100644 --- a/arduino-ide-extension/src/browser/style/list-widget.css +++ b/arduino-ide-extension/src/browser/style/list-widget.css @@ -8,13 +8,18 @@ } .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 { + display: flex; + margin: 0px 10px 5px 15px; +} + .filterable-list-container { display: flex; flex-direction: column; diff --git a/arduino-ide-extension/src/browser/widgets/component-list/filter-renderer.tsx b/arduino-ide-extension/src/browser/widgets/component-list/filter-renderer.tsx new file mode 100644 index 000000000..97e8053bf --- /dev/null +++ b/arduino-ide-extension/src/browser/widgets/component-list/filter-renderer.tsx @@ -0,0 +1,76 @@ +import { injectable } from '@theia/core/shared/inversify'; +import * as React from '@theia/core/shared/react'; +import { + BoardSearch, + LibrarySearch, + Searchable, +} from '../../../common/protocol'; +import { firstToUpperCase } from '../../../common/utils'; + +@injectable() +export abstract class FilterRenderer { + render( + options: S, + handlePropChange: (prop: keyof S, value: S[keyof S]) => void + ): React.ReactNode { + const props = this.props(); + return ( +
+ {Object.entries(options) + .filter(([prop]) => props.includes(prop as keyof S)) + .map(([prop, value]) => ( +
+ {firstToUpperCase(prop)}: + +
+ ))} +
+ ); + } + protected abstract props(): (keyof S)[]; + protected abstract options(key: keyof S): string[]; +} + +@injectable() +export class BoardsFilterRenderer extends FilterRenderer { + protected props(): (keyof BoardSearch)[] { + return ['type']; + } + protected options(key: keyof BoardSearch): string[] { + switch (key) { + case 'type': + return BoardSearch.TypeLiterals as any; + default: + throw new Error(`Unexpected key: ${key}`); + } + } +} + +@injectable() +export class LibraryFilterRenderer extends FilterRenderer { + protected props(): (keyof LibrarySearch)[] { + return ['type', 'topic']; + } + protected options(key: keyof LibrarySearch): string[] { + switch (key) { + case 'type': + return LibrarySearch.TypeLiterals as any; + case 'topic': + return LibrarySearch.TopicLiterals as any; + default: + throw new Error(`Unexpected key: ${key}`); + } + } +} diff --git a/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx b/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx index 2606fc895..6ecd53ce6 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx @@ -14,6 +14,7 @@ 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, @@ -25,15 +26,15 @@ export class FilterableListContainer< constructor(props: Readonly>) { super(props); this.state = { - searchOptions: { query: '' } as S, + searchOptions: props.defaultSearchOptions, items: [], }; } override componentDidMount(): void { this.search = debounce(this.search, 500); - this.handleQueryChange(''); - this.props.filterTextChangeEvent(this.handleQueryChange.bind(this)); + this.search(this.state.searchOptions); + this.props.filterTextChangeEvent(this.handlePropChange.bind(this)); } override componentDidUpdate(): void { @@ -45,15 +46,22 @@ export class FilterableListContainer< override render(): React.ReactNode { return (
- {this.renderSearchFilter()} {this.renderSearchBar()} + {this.renderSearchFilter()} {this.renderComponentList()}
); } protected renderSearchFilter(): React.ReactNode { - return undefined; + return ( + <> + {this.props.filterRenderer.render( + this.state.searchOptions, + this.handlePropChange.bind(this) + )} + + ); } protected renderSearchBar(): React.ReactNode { @@ -61,7 +69,9 @@ export class FilterableListContainer< + this.handlePropChange('query', query as S['query']) + } /> ); } @@ -80,15 +90,12 @@ export class FilterableListContainer< ); } - protected handleQueryChange = ( - query: string = this.state.searchOptions.query ?? '' - ): void => { - const newSearchOptions = { + protected handlePropChange = (prop: keyof S, value: S[keyof S]): void => { + const searchOptions = { ...this.state.searchOptions, - query, + [prop]: value, }; - this.setState({ searchOptions: newSearchOptions }); - this.search(newSearchOptions); + this.setState({ searchOptions }, () => this.search(searchOptions)); }; protected search(searchOptions: S): void { @@ -160,12 +167,13 @@ export namespace FilterableListContainer { T extends ArduinoComponent, S extends Searchable.Options > { + readonly defaultSearchOptions: S; readonly container: ListWidget; readonly searchable: Searchable; readonly itemLabel: (item: T) => string; readonly itemDeprecated: (item: T) => boolean; readonly itemRenderer: ListItemRenderer; - // readonly resolveContainer: (element: HTMLElement) => void; + readonly filterRenderer: FilterRenderer; readonly resolveFocus: (element: HTMLElement | undefined) => void; readonly filterTextChangeEvent: Event; readonly messageService: MessageService; diff --git a/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx b/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx index 0de6256ab..4a427b9df 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx @@ -19,6 +19,7 @@ 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< @@ -131,6 +132,7 @@ export abstract class ListWidget< render(): React.ReactNode { return ( + defaultSearchOptions={this.options.defaultSearchOptions} container={this} resolveFocus={this.onFocusResolved} searchable={this.options.searchable} @@ -139,6 +141,7 @@ export abstract class ListWidget< itemLabel={this.options.itemLabel} itemDeprecated={this.options.itemDeprecated} itemRenderer={this.options.itemRenderer} + filterRenderer={this.options.filterRenderer} filterTextChangeEvent={this.filterTextChangeEmitter.event} messageService={this.messageService} commandService={this.commandService} @@ -175,5 +178,7 @@ export namespace ListWidget { readonly itemLabel: (item: T) => string; readonly itemDeprecated: (item: T) => boolean; readonly itemRenderer: ListItemRenderer; + readonly filterRenderer: FilterRenderer; + readonly defaultSearchOptions: S; } } From a0463a0ff005227774b3ca224c881446b1bf05d0 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Sun, 28 Aug 2022 19:34:12 +0200 Subject: [PATCH 11/22] Basic filter. Signed-off-by: Akos Kitta --- .../widgets/component-list/component-list.tsx | 7 ++- .../src/common/protocol/library-service.ts | 2 + .../src/node/library-service-impl.ts | 60 ++++++++++++++++++- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx b/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx index aad9da5ae..e142de629 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/component-list.tsx @@ -100,8 +100,11 @@ export class ComponentList extends React.Component< private clear(index: number): void { this.cache.clear(index, 0); - if (this.list) { - this.list.recomputeRowHeights(index); + 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); } } diff --git a/arduino-ide-extension/src/common/protocol/library-service.ts b/arduino-ide-extension/src/common/protocol/library-service.ts index be850f488..23b656ddc 100644 --- a/arduino-ide-extension/src/common/protocol/library-service.ts +++ b/arduino-ide-extension/src/common/protocol/library-service.ts @@ -118,6 +118,8 @@ export interface LibraryPackage extends ArduinoComponent { readonly exampleUris: string[]; readonly location: LibraryLocation; readonly installDirUri?: string; + readonly category?: string; + readonly maintainer?: string; } export namespace LibraryPackage { export function is(arg: any): arg is LibraryPackage { diff --git a/arduino-ide-extension/src/node/library-service-impl.ts b/arduino-ide-extension/src/node/library-service-impl.ts index 805255df5..17926e90f 100644 --- a/arduino-ide-extension/src/node/library-service-impl.ts +++ b/arduino-ide-extension/src/node/library-service-impl.ts @@ -3,6 +3,7 @@ import { LibraryDependency, LibraryLocation, LibraryPackage, + LibrarySearch, LibraryService, } from '../common/protocol/library-service'; import { CoreClientAware } from './core-client-provider'; @@ -46,7 +47,7 @@ export class LibraryServiceImpl protected readonly notificationServer: NotificationServiceServer; @duration() - async search(options: { query?: string }): Promise { + async search(options: LibrarySearch): Promise { const coreClient = await this.coreClient; const { client, instance } = coreClient; @@ -104,7 +105,60 @@ 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 { + if (!options.type) { + return () => true; + } + switch (options.type) { + case 'All': + return () => true; + case 'Arduino': + return ({ maintainer }: LibraryPackage) => + maintainer === 'Arduino '; + case 'Installed': + return ({ installedVersion }: LibraryPackage) => !!installedVersion; + case 'Retired': + return ({ deprecated }: LibraryPackage) => deprecated; + case 'Updatable': + return (item: LibraryPackage) => { + 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 available versions are mission. Skipping.` + ); + return false; + } + const result = Installable.Version.COMPARATOR( + latestVersion, + installedVersion + ); + return result > 0; + }; + default: { + console.error('unhandled type: ' + options.type); + return () => true; + } + } + } + + private topicPredicate( + options: LibrarySearch + ): (item: LibraryPackage) => boolean { + if (!options.topic || options.topic === 'All') { + return () => true; + } + return (item: LibraryPackage) => item.category === options.topic; } async list({ @@ -409,5 +463,7 @@ function toLibrary( description: lib.getSentence(), moreInfoLink: lib.getWebsite(), summary: lib.getParagraph(), + category: lib.getCategory(), + maintainer: lib.getMaintainer(), }; } From 6f4b6b5c16cd0defc99f09db462f66f515493fb2 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Sun, 28 Aug 2022 20:15:56 +0200 Subject: [PATCH 12/22] Basic style. Signed-off-by: Akos Kitta --- arduino-ide-extension/src/browser/style/list-widget.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arduino-ide-extension/src/browser/style/list-widget.css b/arduino-ide-extension/src/browser/style/list-widget.css index 1ec99c64f..03f938f84 100644 --- a/arduino-ide-extension/src/browser/style/list-widget.css +++ b/arduino-ide-extension/src/browser/style/list-widget.css @@ -20,6 +20,10 @@ margin: 0px 10px 5px 15px; } +.arduino-list-widget .filter-bar > * { + padding: 0px 5px 0px 0px; +} + .filterable-list-container { display: flex; flex-direction: column; From 0325182aaa6ae92efc5dcb4c727b616f61628f61 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Mon, 29 Aug 2022 15:43:45 +0200 Subject: [PATCH 13/22] check for updates contrib. Signed-off-by: Akos Kitta --- arduino-ide-extension/package.json | 4 +- .../browser/arduino-ide-frontend-module.ts | 6 +- .../src/browser/arduino-preferences.ts | 9 + .../browser/boards/boards-auto-installer.ts | 7 +- .../contributions/check-for-ide-updates.ts | 68 +++ .../contributions/check-for-updates.ts | 231 +++++++-- .../contributions/open-sketch-files.ts | 27 +- .../ide-updater/ide-updater-commands.ts | 4 +- .../component-list/filter-renderer.tsx | 43 +- arduino-ide-extension/src/common/nls.ts | 17 + .../src/common/protocol/arduino-component.ts | 6 +- .../src/common/protocol/boards-service.ts | 14 + .../src/common/protocol/installable.ts | 32 ++ .../src/common/protocol/library-service.ts | 56 ++- .../src/common/protocol/progressible.ts | 2 +- .../src/node/boards-service-impl.ts | 32 +- .../cli/commands/v1/commands_grpc_pb.d.ts | 15 + .../cli/commands/v1/commands_grpc_pb.js | 34 ++ .../cc/arduino/cli/commands/v1/common_pb.d.ts | 6 + .../cc/arduino/cli/commands/v1/common_pb.js | 53 +- .../cc/arduino/cli/commands/v1/lib_pb.d.ts | 63 +++ .../cc/arduino/cli/commands/v1/lib_pb.js | 457 ++++++++++++++++++ .../src/node/library-service-impl.ts | 54 +-- .../src/test/browser/fixtures/boards.ts | 1 + i18n/en.json | 38 +- 25 files changed, 1151 insertions(+), 128 deletions(-) create mode 100644 arduino-ide-extension/src/browser/contributions/check-for-ide-updates.ts diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index 93b258d67..7c5bcb5ae 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -161,9 +161,9 @@ "arduino": { "cli": { "version": { - "owner": "arduino", + "owner": "cmaglie", "repo": "arduino-cli", - "commitish": "63f1e18" + "commitish": "new_grpc_field_in_platform" } }, "fwuploader": { 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 dab0e7c5c..3f158f65a 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -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'; @@ -331,6 +331,7 @@ import { BoardsFilterRenderer, LibraryFilterRenderer, } from './widgets/component-list/filter-renderer'; +import { CheckForUpdates } from './contributions/check-for-updates'; const registerArduinoThemes = () => { const themes: MonacoThemeJson[] = [ @@ -747,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. diff --git a/arduino-ide-extension/src/browser/arduino-preferences.ts b/arduino-ide-extension/src/browser/arduino-preferences.ts index f9875a6fc..e608cd312 100644 --- a/arduino-ide-extension/src/browser/arduino-preferences.ts +++ b/arduino-ide-extension/src/browser/arduino-preferences.ts @@ -241,6 +241,14 @@ export const ArduinoConfigSchema: PreferenceSchema = { ), default: false, }, + 'arduino.checkForUpdates': { + type: 'boolean', + description: nls.localize( + 'arduino/preferences/checkForUpdate', + "Configure whether you receive automatic 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'); 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 845f97b59..e3e333648 100644 --- a/arduino-ide-extension/src/browser/boards/boards-auto-installer.ts +++ b/arduino-ide-extension/src/browser/boards/boards-auto-installer.ts @@ -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,14 +232,10 @@ 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 }) diff --git a/arduino-ide-extension/src/browser/contributions/check-for-ide-updates.ts b/arduino-ide-extension/src/browser/contributions/check-for-ide-updates.ts new file mode 100644 index 000000000..14ba623bf --- /dev/null +++ b/arduino-ide-extension/src/browser/contributions/check-for-ide-updates.ts @@ -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( + 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 + ) + ); + }); + } +} diff --git a/arduino-ide-extension/src/browser/contributions/check-for-updates.ts b/arduino-ide-extension/src/browser/contributions/check-for-updates.ts index 16db7a845..e2f10be82 100644 --- a/arduino-ide-extension/src/browser/contributions/check-for-updates.ts +++ b/arduino-ide-extension/src/browser/contributions/check-for-updates.ts @@ -1,64 +1,197 @@ 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 { Contribution, CommandRegistry, Command } from './contribution'; +import { Installable } from '../../common/protocol/installable'; +import { ExecuteWithProgress } from '../../common/protocol/progressible'; +import { WindowServiceExt } from '../theia/core/window-service-ext'; +import { BoardsListWidgetFrontendContribution } from '../boards/boards-widget-frontend-contribution'; +import { LibraryListWidgetFrontendContribution } from '../library/library-widget-frontend-contribution'; +import { ListWidget } from '../widgets/component-list/list-widget'; +import { AbstractViewContribution } from '@theia/core/lib/browser'; + +const NoUpdates = nls.localize( + 'arduino/checkForUpdates/noUpdates', + 'No updates were found.' +); +const UpdatesBoards = nls.localize( + 'arduino/checkForUpdates/updatedBoth', + 'Updates are available for some of your boards.' +); +const UpdatesLibraries = nls.localize( + 'arduino/checkForUpdates/updatedBoth', + 'Updates are available for some of your libraries.' +); +const InstallAll = nls.localize( + 'arduino/checkForUpdates/installAll', + 'Install All' +); + +interface Task { + run: () => Promise; + item: T; +} @injectable() export class CheckForUpdates extends Contribution { - @inject(IDEUpdater) - private readonly updater: IDEUpdater; + @inject(WindowServiceExt) + private readonly windowService: WindowServiceExt; + @inject(LibraryService) + private readonly libraryService: LibraryService; + @inject(BoardsService) + private readonly boardsService: BoardsService; + @inject(ResponseServiceClient) + private readonly responseService: ResponseServiceClient; + @inject(BoardsListWidgetFrontendContribution) + private readonly boardsContribution: BoardsListWidgetFrontendContribution; + @inject(LibraryListWidgetFrontendContribution) + private readonly librariesContribution: LibraryListWidgetFrontendContribution; - @inject(IDEUpdaterDialog) - private readonly updaterDialog: IDEUpdaterDialog; + override registerCommands(register: CommandRegistry): void { + register.registerCommand(CheckForUpdates.Commands.CHECK_FOR_UPDATES, { + execute: () => this.checkForUpdates(false), + }); + } - @inject(LocalStorageService) - private readonly localStorage: LocalStorageService; + override async onReady(): Promise { + const checkForUpdates = this.preferences['arduino.checkForUpdates']; + if (checkForUpdates) { + this.windowService.isFirstWindow().then((firstWindow) => { + if (firstWindow) { + this.checkForUpdates(); + } + }); + } + } + + private async checkForUpdates(silent = true) { + const [libraryPackages, boardsPackages] = await Promise.all([ + this.libraryService.updateables(), + this.boardsService.updateables(), + ]); + this.promptUpdateBoards(boardsPackages); + this.promptUpdateLibraries(libraryPackages); + // const args = this.infoArgs(libraryPackages, boardsPackages); + // if (args) { + // const { message, actions } = args; + // this.messageService.info(message, ...actions).then((answer) => { + // if (answer === InstallAll) { + // const tasks = this.installAllTasks( + // libraryPackages, + // boardsPackages, + // answer + // ); + // return this.executeTasks(tasks); + // } + // // Install manually is not available if both boards and libraries can be updated. + // if (answer === InstallManually) { + + // } + // }); + // } else if (!silent) { + // } + if (!libraryPackages.length && !boardsPackages.length && !silent) { + this.messageService.info(NoUpdates); + } + } + + private promptUpdateLibraries(items: LibraryPackage[]): void { + this.prompt( + items, + this.libraryService, + this.librariesContribution, + UpdatesLibraries + ); + } - 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') - ); + private promptUpdateBoards(items: BoardsPackage[]): void { + this.prompt( + items, + this.boardsService, + this.boardsContribution, + UpdatesBoards + ); + } + + private prompt( + items: T[], + installable: Installable, + viewContribution: AbstractViewContribution>, + message: string + ): void { + if (!items.length) { + return; + } + const actions = [Later, InstallManually, InstallAll]; + this.messageService.info(message, ...actions).then((answer) => { + if (answer === InstallAll) { + const tasks = items.map((item) => this.installTask(item, installable)); + return this.executeTasks(tasks); + } else if (answer === InstallManually) { + viewContribution + .openView({ reveal: true }) + .then((widget) => widget.refresh(candidate.name.toLocaleLowerCase())); + } + }); + } + + private async executeTasks(tasks: Task[]): Promise { + if (tasks.length) { + return ExecuteWithProgress.withProgress( + nls.localize('arduino/checkForUpdates/updating', 'Updating'), + this.messageService, + async (progress) => { + const total = tasks.length; + let count = 0; + for (const { run, item } of tasks) { + progress.report({ + message: item.name, + }); + await run(); + progress.report({ work: { total, done: ++count } }); } } - } - ); + ); + } } - 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( - 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 - ) - ); - }); + private installTask( + item: T, + installable: Installable + ): Task { + 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' + ); } } diff --git a/arduino-ide-extension/src/browser/contributions/open-sketch-files.ts b/arduino-ide-extension/src/browser/contributions/open-sketch-files.ts index 8fbea21ae..63cea8ca4 100644 --- a/arduino-ide-extension/src/browser/contributions/open-sketch-files.ts +++ b/arduino-ide-extension/src/browser/contributions/open-sketch-files.ts @@ -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)) { diff --git a/arduino-ide-extension/src/browser/ide-updater/ide-updater-commands.ts b/arduino-ide-extension/src/browser/ide-updater/ide-updater-commands.ts index 2ab2b754a..167c83120 100644 --- a/arduino-ide-extension/src/browser/ide-updater/ide-updater-commands.ts +++ b/arduino-ide-extension/src/browser/ide-updater/ide-updater-commands.ts @@ -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' diff --git a/arduino-ide-extension/src/browser/widgets/component-list/filter-renderer.tsx b/arduino-ide-extension/src/browser/widgets/component-list/filter-renderer.tsx index 97e8053bf..74cff6b82 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/filter-renderer.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/filter-renderer.tsx @@ -20,17 +20,19 @@ export abstract class FilterRenderer { .filter(([prop]) => props.includes(prop as keyof S)) .map(([prop, value]) => (
+ {/* TODO: translations? */} {firstToUpperCase(prop)}: @@ -40,7 +42,8 @@ export abstract class FilterRenderer { ); } protected abstract props(): (keyof S)[]; - protected abstract options(key: keyof S): string[]; + protected abstract options(prop: keyof S): string[]; + protected abstract label(prop: keyof S, key: string): string; } @injectable() @@ -48,12 +51,22 @@ export class BoardsFilterRenderer extends FilterRenderer { protected props(): (keyof BoardSearch)[] { return ['type']; } - protected options(key: keyof BoardSearch): string[] { - switch (key) { + 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 key: ${key}`); + throw new Error(`Unexpected prop: ${prop}`); + } + } + protected label(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}`); } } } @@ -63,14 +76,28 @@ export class LibraryFilterRenderer extends FilterRenderer { protected props(): (keyof LibrarySearch)[] { return ['type', 'topic']; } - protected options(key: keyof LibrarySearch): string[] { - switch (key) { + 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 key: ${key}`); + throw new Error(`Unexpected prop: ${prop}`); + } + } + protected label(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}`); } } } diff --git a/arduino-ide-extension/src/common/nls.ts b/arduino-ide-extension/src/common/nls.ts index 2435ff3b4..9485318d8 100644 --- a/arduino-ide-extension/src/common/nls.ts +++ b/arduino-ide-extension/src/common/nls.ts @@ -1,3 +1,20 @@ 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 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' +); diff --git a/arduino-ide-extension/src/common/protocol/arduino-component.ts b/arduino-ide-extension/src/common/protocol/arduino-component.ts index 4a32d869a..2cdfe38a2 100644 --- a/arduino-ide-extension/src/common/protocol/arduino-component.ts +++ b/arduino-ide-extension/src/common/protocol/arduino-component.ts @@ -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 { diff --git a/arduino-ide-extension/src/common/protocol/boards-service.ts b/arduino-ide-extension/src/common/protocol/boards-service.ts index 7ecb074e5..f0de985ed 100644 --- a/arduino-ide-extension/src/common/protocol/boards-service.ts +++ b/arduino-ide-extension/src/common/protocol/boards-service.ts @@ -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, Updatable } from '../nls'; export type AvailablePorts = Record]>; export namespace AvailablePorts { @@ -159,6 +161,18 @@ export namespace BoardSearch { 'Arduino@Heart', ] as const; export type Type = typeof TypeLiterals[number]; + export const TypeLabels: Record = { + All: All, + Updatable: Updatable, + Arduino: 'Arduino', + Contributed: Contributed, + 'Arduino Certified': nls.localize( + 'arduino/boardsType/arduinoCertified', + 'Arduino Certified' + ), + Partner: Partner, + 'Arduino@Heart': 'Arduino@Heart', + }; } export interface Port { diff --git a/arduino-ide-extension/src/common/protocol/installable.ts b/arduino-ide-extension/src/common/protocol/installable.ts index 527805697..59ae96edd 100644 --- a/arduino-ide-extension/src/common/protocol/installable.ts +++ b/arduino-ide-extension/src/common/protocol/installable.ts @@ -20,6 +20,11 @@ export interface Installable { * Uninstalls the given component. It is a NOOP if not installed. */ uninstall(options: { item: T; progressId?: string }): Promise; + + /** + * Returns with a promise which resolves all component that is installed but has a more recent version available. + */ + updateables(): Promise; } export namespace Installable { export type Version = string; @@ -36,6 +41,31 @@ export namespace Installable { }; } + export const Installed = ({ + installedVersion, + }: T): boolean => { + return !!installedVersion; + }; + + export const Updateable = (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 +74,7 @@ export namespace Installable { responseService: ResponseServiceClient; item: T; version: Installable.Version; + keepOutput?: boolean; }): Promise { const { item, version } = options; return ExecuteWithProgress.doWithProgress({ @@ -65,6 +96,7 @@ export namespace Installable { messageService: MessageService; responseService: ResponseServiceClient; item: T; + keepOutput?: boolean; }): Promise { const { item } = options; return ExecuteWithProgress.doWithProgress({ diff --git a/arduino-ide-extension/src/common/protocol/library-service.ts b/arduino-ide-extension/src/common/protocol/library-service.ts index 23b656ddc..fcc64e629 100644 --- a/arduino-ide-extension/src/common/protocol/library-service.ts +++ b/arduino-ide-extension/src/common/protocol/library-service.ts @@ -1,6 +1,15 @@ 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, + Updatable, +} from '../nls'; export const LibraryServicePath = '/services/library-service'; export const LibraryService = Symbol('LibraryService'); @@ -55,6 +64,16 @@ export namespace LibrarySearch { 'Retired', ] as const; export type Type = typeof TypeLiterals[number]; + export const TypeLabels: Record = { + 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', @@ -69,6 +88,37 @@ export namespace LibrarySearch { 'Uncategorized', ]; export type Topic = typeof TopicLiterals[number]; + export const TopicLabels: Record = { + 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', + 'Date Storage' + ), + 'Device Control': nls.localize( + 'arduino/libraryTopic/deviceControl', + 'Device Control' + ), + Display: nls.localize('arduino/libraryTopic/display', 'Display'), + Others: nls.localize('arduino/libraryTopic/others', 'Others'), + 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 namespace LibraryService { @@ -118,8 +168,10 @@ export interface LibraryPackage extends ArduinoComponent { readonly exampleUris: string[]; readonly location: LibraryLocation; readonly installDirUri?: string; - readonly category?: string; - readonly maintainer?: 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 { diff --git a/arduino-ide-extension/src/common/protocol/progressible.ts b/arduino-ide-extension/src/common/protocol/progressible.ts index 03e5141ef..c27737ccc 100644 --- a/arduino-ide-extension/src/common/protocol/progressible.ts +++ b/arduino-ide-extension/src/common/protocol/progressible.ts @@ -39,7 +39,7 @@ export namespace ExecuteWithProgress { ); } - async function withProgress( + export async function withProgress( text: string, messageService: MessageService, cb: (progress: Progress, token: CancellationToken) => Promise diff --git a/arduino-ide-extension/src/node/boards-service-impl.ts b/arduino-ide-extension/src/node/boards-service-impl.ts index 927a10e46..f3fff536e 100644 --- a/arduino-ide-extension/src/node/boards-service-impl.ts +++ b/arduino-ide-extension/src/node/boards-service-impl.ts @@ -16,6 +16,7 @@ import { AvailablePorts, BoardWithPackage, BoardUserField, + BoardSearch, } from '../common/protocol'; import { PlatformInstallRequest, @@ -189,6 +190,10 @@ export class BoardsServiceImpl ); } + updateables(): Promise { + return this.search({ type: 'Updatable' }); + } + async searchBoards({ query, }: { @@ -264,7 +269,7 @@ export class BoardsServiceImpl })); } - async search(options: { query?: string }): Promise { + async search(options: BoardSearch): Promise { const coreClient = await this.coreClient; const { client, instance } = coreClient; @@ -310,6 +315,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 +386,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: { diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb.d.ts index 9f34ff24f..65af18363 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb.d.ts @@ -49,6 +49,7 @@ interface IArduinoCoreServiceService extends grpc.ServiceDefinition; responseDeserialize: grpc.deserialize; } +interface IArduinoCoreServiceService_ILibraryUpgrade extends grpc.MethodDefinition { + path: "/cc.arduino.cli.commands.v1.ArduinoCoreService/LibraryUpgrade"; + requestStream: false; + responseStream: true; + requestSerialize: grpc.serialize; + requestDeserialize: grpc.deserialize; + responseSerialize: grpc.serialize; + responseDeserialize: grpc.deserialize; +} interface IArduinoCoreServiceService_IZipLibraryInstall extends grpc.MethodDefinition { path: "/cc.arduino.cli.commands.v1.ArduinoCoreService/ZipLibraryInstall"; requestStream: false; @@ -465,6 +475,7 @@ export interface IArduinoCoreServiceServer { platformList: grpc.handleUnaryCall; libraryDownload: grpc.handleServerStreamingCall; libraryInstall: grpc.handleServerStreamingCall; + libraryUpgrade: grpc.handleServerStreamingCall; zipLibraryInstall: grpc.handleServerStreamingCall; gitLibraryInstall: grpc.handleServerStreamingCall; libraryUninstall: grpc.handleServerStreamingCall; @@ -557,6 +568,8 @@ export interface IArduinoCoreServiceClient { libraryDownload(request: cc_arduino_cli_commands_v1_lib_pb.LibraryDownloadRequest, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; libraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.LibraryInstallRequest, options?: Partial): grpc.ClientReadableStream; libraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.LibraryInstallRequest, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; + libraryUpgrade(request: cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest, options?: Partial): grpc.ClientReadableStream; + libraryUpgrade(request: cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; zipLibraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallRequest, options?: Partial): grpc.ClientReadableStream; zipLibraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallRequest, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; gitLibraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.GitLibraryInstallRequest, options?: Partial): grpc.ClientReadableStream; @@ -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.ClientReadableStream; public libraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.LibraryInstallRequest, options?: Partial): grpc.ClientReadableStream; public libraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.LibraryInstallRequest, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; + public libraryUpgrade(request: cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest, options?: Partial): grpc.ClientReadableStream; + public libraryUpgrade(request: cc_arduino_cli_commands_v1_lib_pb.LibraryUpgradeRequest, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; public zipLibraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallRequest, options?: Partial): grpc.ClientReadableStream; public zipLibraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.ZipLibraryInstallRequest, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; public gitLibraryInstall(request: cc_arduino_cli_commands_v1_lib_pb.GitLibraryInstallRequest, options?: Partial): grpc.ClientReadableStream; diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb.js index 8358c89e2..2251692b1 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb.js @@ -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', diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts index a7bd5e06c..f79eb14c0 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.d.ts @@ -159,6 +159,11 @@ export class Platform extends jspb.Message { getDeprecated(): boolean; setDeprecated(value: boolean): Platform; + clearTypeList(): void; + getTypeList(): Array; + setTypeList(value: Array): Platform; + addType(value: string, index?: number): string; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): Platform.AsObject; @@ -182,6 +187,7 @@ export namespace Platform { boardsList: Array, manuallyInstalled: boolean, deprecated: boolean, + typeList: Array, } } diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.js index 8b3916047..4ffbe5d22 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/common_pb.js @@ -987,7 +987,7 @@ proto.cc.arduino.cli.commands.v1.Programmer.prototype.setName = function(value) * @private {!Array} * @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} + */ +proto.cc.arduino.cli.commands.v1.Platform.prototype.getTypeList = function() { + return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 11)); +}; + + +/** + * @param {!Array} 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([]); +}; + + diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.d.ts index d8f4dc9cd..c6573e28e 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.d.ts @@ -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}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + 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}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + 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; diff --git a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.js b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.js index be2398106..7aef8b4e8 100644 --- a/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/lib_pb.js @@ -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_, 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_, 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. diff --git a/arduino-ide-extension/src/node/library-service-impl.ts b/arduino-ide-extension/src/node/library-service-impl.ts index 17926e90f..d19073f22 100644 --- a/arduino-ide-extension/src/node/library-service-impl.ts +++ b/arduino-ide-extension/src/node/library-service-impl.ts @@ -113,52 +113,38 @@ export class LibraryServiceImpl private typePredicate( options: LibrarySearch ): (item: LibraryPackage) => boolean { - if (!options.type) { + const { type } = options; + if (!type || type === 'All') { return () => true; } switch (options.type) { - case 'All': - return () => true; - case 'Arduino': - return ({ maintainer }: LibraryPackage) => - maintainer === 'Arduino '; case 'Installed': - return ({ installedVersion }: LibraryPackage) => !!installedVersion; - case 'Retired': - return ({ deprecated }: LibraryPackage) => deprecated; + return Installable.Installed; case 'Updatable': - return (item: LibraryPackage) => { - 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 available versions are mission. Skipping.` - ); - return false; - } - const result = Installable.Version.COMPARATOR( - latestVersion, - installedVersion - ); - return result > 0; - }; - default: { - console.error('unhandled type: ' + options.type); - return () => true; - } + 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 { - if (!options.topic || options.topic === 'All') { + const { topic } = options; + if (!topic || topic === 'All') { return () => true; } - return (item: LibraryPackage) => item.category === options.topic; + return (item: LibraryPackage) => item.category === topic; + } + + async updateables(): Promise { + return this.search({ type: 'Updatable' }); } async list({ @@ -464,6 +450,6 @@ function toLibrary( moreInfoLink: lib.getWebsite(), summary: lib.getParagraph(), category: lib.getCategory(), - maintainer: lib.getMaintainer(), + types: lib.getTypesList(), }; } diff --git a/arduino-ide-extension/src/test/browser/fixtures/boards.ts b/arduino-ide-extension/src/test/browser/fixtures/boards.ts index d7ddc3126..0cedb5b77 100644 --- a/arduino-ide-extension/src/test/browser/fixtures/boards.ts +++ b/arduino-ide-extension/src/test/browser/fixtures/boards.ts @@ -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 = { diff --git a/i18n/en.json b/i18n/en.json index 65b02b5c6..d38d30264 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -14,7 +14,6 @@ "disconnected": "Disconnected", "getBoardInfo": "Get Board Info", "inSketchbook": " (in Sketchbook)", - "installManually": "Install Manually", "installNow": "The \"{0} {1}\" core has to be installed for the currently selected \"{2}\" board. Do you want to install it now?", "noFQBN": "The FQBN is not available for the selected board \"{0}\". Do you have the corresponding core installed?", "noPortsDiscovered": "No ports discovered", @@ -36,6 +35,9 @@ "succesfullyUninstalledPlatform": "Successfully uninstalled platform {0}:{1}" }, "boardsManager": "Boards Manager", + "boardsType": { + "arduinoCertified": "Arduino Certified" + }, "bootloader": { "burnBootloader": "Burn Bootloader", "burningBootloader": "Burning bootloader...", @@ -61,6 +63,13 @@ "uploadRootCertificates": "Upload SSL Root Certificates", "uploadingCertificates": "Uploading certificates." }, + "checkForUpdates": { + "checkForUpdates": "Check for Arduino Updates", + "installAll": "Install All", + "noUpdates": "No updates were found.", + "updatedBoth": "Updates are available for some of your libraries.", + "updating": "Updating" + }, "cli-error-parser": { "keyboardError": "'Keyboard' not found. Does your sketch include the line '#include '?", "mouseError": "'Mouse' not found. Does your sketch include the line '#include '?" @@ -103,15 +112,22 @@ "visitArduinoCloud": "Visit Arduino Cloud to create Cloud Sketches." }, "common": { + "all": "All", + "contributed": "Contributed", + "installManually": "Install Manually", "later": "Later", "noBoardSelected": "No board selected", "notConnected": "[not connected]", "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.", "oldFormat": "The '{0}' still uses the old `.pde` format. Do you want to switch to the new `.ino` extension?", + "partner": "Partner", "processing": "Processing", + "recommended": "Recommended", + "retired": "Retired", "selectedOn": "on {0}", "serialMonitor": "Serial Monitor", - "unknown": "Unknown" + "unknown": "Unknown", + "updateable": "Updatable" }, "compile": { "error": "Compilation error: {0}" @@ -197,7 +213,7 @@ "visit": "Visit Arduino.cc" }, "ide-updater": { - "checkForUpdates": "Check for Arduino IDE updates", + "checkForUpdates": "Check for Arduino IDE Updates", "closeAndInstallButton": "Close and Install", "closeToInstallNotice": "Close the software and install the update on your machine.", "downloadButton": "Download", @@ -235,6 +251,21 @@ "uninstalledSuccessfully": "Successfully uninstalled library {0}:{1}", "zipLibrary": "Library" }, + "libraryTopic": { + "communication": "Communication", + "dataProcessing": "Data Processing", + "dataStorage": "Date Storage", + "deviceControl": "Device Control", + "display": "Display", + "others": "Others", + "sensors": "Sensors", + "signalInputOutput": "Signal Input/Output", + "timing": "Timing", + "uncategorized": "Uncategorized" + }, + "libraryType": { + "installed": "Installed" + }, "menu": { "advanced": "Advanced", "sketch": "Sketch", @@ -253,6 +284,7 @@ "automatic": "Automatic", "board.certificates": "List of certificates that can be uploaded to boards", "browse": "Browse", + "checkForUpdate": "Configure whether you receive automatic updates for the IDE, boards, and libraries. Requires an IDE restart after change. It's true by default.", "choose": "Choose", "cli.daemonDebug": "Enable debug logging of the gRPC calls to the Arduino CLI. A restart of the IDE is needed for this setting to take effect. It's false by default.", "cloud.enabled": "True if the sketch sync functions are enabled. Defaults to true.", From ecef2084c58bc54f54b86d5e8618d5f3b208c3a5 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Mon, 29 Aug 2022 15:53:48 +0200 Subject: [PATCH 14/22] Translate search option props. Signed-off-by: Akos Kitta --- .../component-list/filter-renderer.tsx | 30 ++++++++++++++----- arduino-ide-extension/src/common/nls.ts | 1 + .../src/common/protocol/boards-service.ts | 8 ++++- .../src/common/protocol/library-service.ts | 8 +++++ i18n/en.json | 4 +++ 5 files changed, 43 insertions(+), 8 deletions(-) diff --git a/arduino-ide-extension/src/browser/widgets/component-list/filter-renderer.tsx b/arduino-ide-extension/src/browser/widgets/component-list/filter-renderer.tsx index 74cff6b82..58c0a5244 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/filter-renderer.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/filter-renderer.tsx @@ -5,7 +5,6 @@ import { LibrarySearch, Searchable, } from '../../../common/protocol'; -import { firstToUpperCase } from '../../../common/utils'; @injectable() export abstract class FilterRenderer { @@ -20,8 +19,7 @@ export abstract class FilterRenderer { .filter(([prop]) => props.includes(prop as keyof S)) .map(([prop, value]) => (
- {/* TODO: translations? */} - {firstToUpperCase(prop)}: + {this.propertyLabel(prop as keyof S)}: @@ -43,7 +41,8 @@ export abstract class FilterRenderer { } protected abstract props(): (keyof S)[]; protected abstract options(prop: keyof S): string[]; - protected abstract label(prop: keyof S, key: string): string; + protected abstract valueLabel(prop: keyof S, key: string): string; + protected abstract propertyLabel(prop: keyof S): string; } @injectable() @@ -60,7 +59,7 @@ export class BoardsFilterRenderer extends FilterRenderer { throw new Error(`Unexpected prop: ${prop}`); } } - protected label(prop: keyof BoardSearch, key: string): string { + protected valueLabel(prop: keyof BoardSearch, key: string): string { switch (prop) { case 'type': // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -69,6 +68,14 @@ export class BoardsFilterRenderer extends FilterRenderer { 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() @@ -88,7 +95,16 @@ export class LibraryFilterRenderer extends FilterRenderer { throw new Error(`Unexpected prop: ${prop}`); } } - protected label(prop: keyof LibrarySearch, key: string): string { + 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 diff --git a/arduino-ide-extension/src/common/nls.ts b/arduino-ide-extension/src/common/nls.ts index 9485318d8..a2e58b86a 100644 --- a/arduino-ide-extension/src/common/nls.ts +++ b/arduino-ide-extension/src/common/nls.ts @@ -4,6 +4,7 @@ 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', diff --git a/arduino-ide-extension/src/common/protocol/boards-service.ts b/arduino-ide-extension/src/common/protocol/boards-service.ts index f0de985ed..9e84f127f 100644 --- a/arduino-ide-extension/src/common/protocol/boards-service.ts +++ b/arduino-ide-extension/src/common/protocol/boards-service.ts @@ -3,7 +3,7 @@ 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, Updatable } from '../nls'; +import { All, Contributed, Partner, Type, Updatable } from '../nls'; export type AvailablePorts = Record]>; export namespace AvailablePorts { @@ -173,6 +173,12 @@ export namespace BoardSearch { Partner: Partner, 'Arduino@Heart': 'Arduino@Heart', }; + export const PropertyLabels: Record< + keyof Omit, + string + > = { + type: Type, + }; } export interface Port { diff --git a/arduino-ide-extension/src/common/protocol/library-service.ts b/arduino-ide-extension/src/common/protocol/library-service.ts index fcc64e629..2b2ec8c26 100644 --- a/arduino-ide-extension/src/common/protocol/library-service.ts +++ b/arduino-ide-extension/src/common/protocol/library-service.ts @@ -8,6 +8,7 @@ import { Partner, Recommended, Retired, + Type, Updatable, } from '../nls'; @@ -119,6 +120,13 @@ export namespace LibrarySearch { 'Uncategorized' ), }; + export const PropertyLabels: Record< + keyof Omit, + string + > = { + topic: nls.localize('arduino/librarySearchProperty/topic', 'Topic'), + type: Type, + }; } export namespace LibraryService { diff --git a/i18n/en.json b/i18n/en.json index d38d30264..8b127e458 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -126,6 +126,7 @@ "retired": "Retired", "selectedOn": "on {0}", "serialMonitor": "Serial Monitor", + "type": "Type", "unknown": "Unknown", "updateable": "Updatable" }, @@ -251,6 +252,9 @@ "uninstalledSuccessfully": "Successfully uninstalled library {0}:{1}", "zipLibrary": "Library" }, + "librarySearchProperty": { + "topic": "Topic" + }, "libraryTopic": { "communication": "Communication", "dataProcessing": "Data Processing", From e00fa2b89d9c4abdf68f1b194db81df330ae50e1 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Mon, 29 Aug 2022 16:06:58 +0200 Subject: [PATCH 15/22] Can reset types from an action. Signed-off-by: Akos Kitta --- .../browser/boards/boards-auto-installer.ts | 5 ++- .../contributions/check-for-updates.ts | 42 +++++++++++-------- .../filterable-list-container.tsx | 13 ++++-- .../widgets/component-list/list-widget.tsx | 13 +++--- .../src/common/protocol/library-service.ts | 2 +- 5 files changed, 46 insertions(+), 29 deletions(-) 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 e3e333648..f516258f6 100644 --- a/arduino-ide-extension/src/browser/boards/boards-auto-installer.ts +++ b/arduino-ide-extension/src/browser/boards/boards-auto-installer.ts @@ -240,7 +240,10 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution { this.boardsManagerFrontendContribution .openView({ reveal: true }) .then((widget) => - widget.refresh(candidate.name.toLocaleLowerCase()) + widget.refresh({ + query: candidate.name.toLocaleLowerCase(), + type: 'All', + }) ); }, }, diff --git a/arduino-ide-extension/src/browser/contributions/check-for-updates.ts b/arduino-ide-extension/src/browser/contributions/check-for-updates.ts index e2f10be82..077eb7663 100644 --- a/arduino-ide-extension/src/browser/contributions/check-for-updates.ts +++ b/arduino-ide-extension/src/browser/contributions/check-for-updates.ts @@ -105,29 +105,37 @@ export class CheckForUpdates extends Contribution { } private promptUpdateLibraries(items: LibraryPackage[]): void { - this.prompt( + this.prompt({ items, - this.libraryService, - this.librariesContribution, - UpdatesLibraries - ); + installable: this.libraryService, + viewContribution: this.librariesContribution, + message: UpdatesLibraries, + viewSearchOptions: { query: '', type: 'Updatable', topic: 'All' }, + }); } private promptUpdateBoards(items: BoardsPackage[]): void { - this.prompt( + this.prompt({ items, - this.boardsService, - this.boardsContribution, - UpdatesBoards - ); + installable: this.boardsService, + viewContribution: this.boardsContribution, + message: UpdatesBoards, + viewSearchOptions: { query: '', type: 'Updatable' }, + }); } - private prompt( - items: T[], - installable: Installable, - viewContribution: AbstractViewContribution>, - message: string - ): void { + private prompt< + T extends ArduinoComponent, + S extends Searchable.Options + >(options: { + items: T[]; + installable: Installable; + viewContribution: AbstractViewContribution>; + viewSearchOptions: S; + message: string; + }): void { + const { items, installable, viewContribution, message, viewSearchOptions } = + options; if (!items.length) { return; } @@ -139,7 +147,7 @@ export class CheckForUpdates extends Contribution { } else if (answer === InstallManually) { viewContribution .openView({ reveal: true }) - .then((widget) => widget.refresh(candidate.name.toLocaleLowerCase())); + .then((widget) => widget.refresh(viewSearchOptions)); } }); } diff --git a/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx b/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx index 6ecd53ce6..70d4321d2 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx @@ -34,7 +34,10 @@ export class FilterableListContainer< override componentDidMount(): void { this.search = debounce(this.search, 500); this.search(this.state.searchOptions); - this.props.filterTextChangeEvent(this.handlePropChange.bind(this)); + this.props.searchOptionsDidChange((newSearchOptions) => { + const { searchOptions } = this.state; + this.setSearchOptionsAndUpdate({ ...searchOptions, ...newSearchOptions }); + }); } override componentDidUpdate(): void { @@ -95,9 +98,13 @@ export class FilterableListContainer< ...this.state.searchOptions, [prop]: value, }; - this.setState({ searchOptions }, () => this.search(searchOptions)); + this.setSearchOptionsAndUpdate(searchOptions); }; + private setSearchOptionsAndUpdate(searchOptions: S) { + this.setState({ searchOptions }, () => this.search(searchOptions)); + } + protected search(searchOptions: S): void { const { searchable } = this.props; searchable @@ -175,7 +182,7 @@ export namespace FilterableListContainer { readonly itemRenderer: ListItemRenderer; readonly filterRenderer: FilterRenderer; readonly resolveFocus: (element: HTMLElement | undefined) => void; - readonly filterTextChangeEvent: Event; + readonly searchOptionsDidChange: Event | undefined>; readonly messageService: MessageService; readonly responseService: ResponseServiceClient; readonly install: ({ diff --git a/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx b/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx index 4a427b9df..3e388cce2 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx @@ -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(); - protected readonly filterTextChangeEmitter = new Emitter< - string | undefined + protected readonly searchOptionsChangeEmitter = new Emitter< + Partial | undefined >(); /** * Instead of running an `update` from the `postConstruct` `init` method, @@ -63,7 +62,7 @@ export abstract class ListWidget< this.addClass('arduino-list-widget'); this.node.tabIndex = 0; // To be able to set the focus on the widget. this.scrollOptions = undefined; - this.toDispose.push(this.filterTextChangeEmitter); + this.toDispose.push(this.searchOptionsChangeEmitter); } @postConstruct() @@ -142,7 +141,7 @@ export abstract class ListWidget< itemDeprecated={this.options.itemDeprecated} itemRenderer={this.options.itemRenderer} filterRenderer={this.options.filterRenderer} - filterTextChangeEvent={this.filterTextChangeEmitter.event} + searchOptionsDidChange={this.searchOptionsChangeEmitter.event} messageService={this.messageService} commandService={this.commandService} responseService={this.responseService} @@ -154,8 +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.filterTextChangeEmitter.fire(filterText); + refresh(searchOptions: Partial | undefined): void { + this.searchOptionsChangeEmitter.fire(searchOptions); } updateScrollBar(): void { diff --git a/arduino-ide-extension/src/common/protocol/library-service.ts b/arduino-ide-extension/src/common/protocol/library-service.ts index 2b2ec8c26..295213bb9 100644 --- a/arduino-ide-extension/src/common/protocol/library-service.ts +++ b/arduino-ide-extension/src/common/protocol/library-service.ts @@ -87,7 +87,7 @@ export namespace LibrarySearch { 'Signal Input/Output', 'Timing', 'Uncategorized', - ]; + ] as const; export type Topic = typeof TopicLiterals[number]; export const TopicLabels: Record = { All: All, From b3e20e4a84da1320c0fed7b34782ae1087802770 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Mon, 29 Aug 2022 16:21:53 +0200 Subject: [PATCH 16/22] align message. Signed-off-by: Akos Kitta --- .../src/browser/contributions/check-for-updates.ts | 10 +++++----- i18n/en.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/arduino-ide-extension/src/browser/contributions/check-for-updates.ts b/arduino-ide-extension/src/browser/contributions/check-for-updates.ts index 077eb7663..4a52462da 100644 --- a/arduino-ide-extension/src/browser/contributions/check-for-updates.ts +++ b/arduino-ide-extension/src/browser/contributions/check-for-updates.ts @@ -1,3 +1,4 @@ +import type { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution'; import { nls } from '@theia/core/lib/common/nls'; import { inject, injectable } from '@theia/core/shared/inversify'; import { InstallManually, Later } from '../../common/nls'; @@ -10,18 +11,17 @@ import { ResponseServiceClient, Searchable, } from '../../common/protocol'; -import { Contribution, CommandRegistry, Command } from './contribution'; import { Installable } from '../../common/protocol/installable'; import { ExecuteWithProgress } from '../../common/protocol/progressible'; -import { WindowServiceExt } from '../theia/core/window-service-ext'; import { BoardsListWidgetFrontendContribution } from '../boards/boards-widget-frontend-contribution'; import { LibraryListWidgetFrontendContribution } from '../library/library-widget-frontend-contribution'; -import { ListWidget } from '../widgets/component-list/list-widget'; -import { AbstractViewContribution } from '@theia/core/lib/browser'; +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', - 'No updates were found.' + 'There are no recent updates available.' ); const UpdatesBoards = nls.localize( 'arduino/checkForUpdates/updatedBoth', diff --git a/i18n/en.json b/i18n/en.json index 8b127e458..273ab8b43 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -66,7 +66,7 @@ "checkForUpdates": { "checkForUpdates": "Check for Arduino Updates", "installAll": "Install All", - "noUpdates": "No updates were found.", + "noUpdates": "There are no recent updates available.", "updatedBoth": "Updates are available for some of your libraries.", "updating": "Updating" }, From 36d8fe12277927a26a987c5a9c50867eb4a3c031 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Mon, 29 Aug 2022 16:23:51 +0200 Subject: [PATCH 17/22] removed comments. Signed-off-by: Akos Kitta --- .../contributions/check-for-updates.ts | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/arduino-ide-extension/src/browser/contributions/check-for-updates.ts b/arduino-ide-extension/src/browser/contributions/check-for-updates.ts index 4a52462da..d5f2c1f83 100644 --- a/arduino-ide-extension/src/browser/contributions/check-for-updates.ts +++ b/arduino-ide-extension/src/browser/contributions/check-for-updates.ts @@ -80,25 +80,6 @@ export class CheckForUpdates extends Contribution { ]); this.promptUpdateBoards(boardsPackages); this.promptUpdateLibraries(libraryPackages); - // const args = this.infoArgs(libraryPackages, boardsPackages); - // if (args) { - // const { message, actions } = args; - // this.messageService.info(message, ...actions).then((answer) => { - // if (answer === InstallAll) { - // const tasks = this.installAllTasks( - // libraryPackages, - // boardsPackages, - // answer - // ); - // return this.executeTasks(tasks); - // } - // // Install manually is not available if both boards and libraries can be updated. - // if (answer === InstallManually) { - - // } - // }); - // } else if (!silent) { - // } if (!libraryPackages.length && !boardsPackages.length && !silent) { this.messageService.info(NoUpdates); } From 337f48a3fb2eebeabd023e8299dd4c9b5ce4f40b Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Tue, 30 Aug 2022 09:21:03 +0200 Subject: [PATCH 18/22] API cleanup. Signed-off-by: Akos Kitta --- .../contributions/check-for-updates.ts | 79 +++++++++++-------- .../src/browser/style/list-widget.css | 1 - .../src/common/protocol/installable.ts | 5 -- .../src/node/boards-service-impl.ts | 4 - .../src/node/library-service-impl.ts | 4 - 5 files changed, 48 insertions(+), 45 deletions(-) diff --git a/arduino-ide-extension/src/browser/contributions/check-for-updates.ts b/arduino-ide-extension/src/browser/contributions/check-for-updates.ts index d5f2c1f83..023773dc2 100644 --- a/arduino-ide-extension/src/browser/contributions/check-for-updates.ts +++ b/arduino-ide-extension/src/browser/contributions/check-for-updates.ts @@ -23,11 +23,11 @@ const NoUpdates = nls.localize( 'arduino/checkForUpdates/noUpdates', 'There are no recent updates available.' ); -const UpdatesBoards = nls.localize( +const UpdateBoards = nls.localize( 'arduino/checkForUpdates/updatedBoth', 'Updates are available for some of your boards.' ); -const UpdatesLibraries = nls.localize( +const UpdateLibraries = nls.localize( 'arduino/checkForUpdates/updatedBoth', 'Updates are available for some of your libraries.' ); @@ -37,10 +37,12 @@ const InstallAll = nls.localize( ); interface Task { - run: () => Promise; - item: T; + readonly run: () => Promise; + readonly item: T; } +const Updatable = { type: 'Updatable' } as const; + @injectable() export class CheckForUpdates extends Contribution { @inject(WindowServiceExt) @@ -74,9 +76,9 @@ export class CheckForUpdates extends Contribution { } private async checkForUpdates(silent = true) { - const [libraryPackages, boardsPackages] = await Promise.all([ - this.libraryService.updateables(), - this.boardsService.updateables(), + const [boardsPackages, libraryPackages] = await Promise.all([ + this.boardsService.search(Updatable), + this.libraryService.search(Updatable), ]); this.promptUpdateBoards(boardsPackages); this.promptUpdateLibraries(libraryPackages); @@ -90,8 +92,8 @@ export class CheckForUpdates extends Contribution { items, installable: this.libraryService, viewContribution: this.librariesContribution, - message: UpdatesLibraries, - viewSearchOptions: { query: '', type: 'Updatable', topic: 'All' }, + viewSearchOptions: { query: '', topic: 'All', ...Updatable }, + message: UpdateLibraries, }); } @@ -100,8 +102,8 @@ export class CheckForUpdates extends Contribution { items, installable: this.boardsService, viewContribution: this.boardsContribution, - message: UpdatesBoards, - viewSearchOptions: { query: '', type: 'Updatable' }, + viewSearchOptions: { query: '', ...Updatable }, + message: UpdateBoards, }); } @@ -120,17 +122,20 @@ export class CheckForUpdates extends Contribution { if (!items.length) { return; } - const actions = [Later, InstallManually, InstallAll]; - this.messageService.info(message, ...actions).then((answer) => { - if (answer === InstallAll) { - const tasks = items.map((item) => this.installTask(item, installable)); - return this.executeTasks(tasks); - } else if (answer === InstallManually) { - viewContribution - .openView({ reveal: true }) - .then((widget) => widget.refresh(viewSearchOptions)); - } - }); + this.messageService + .info(message, Later, InstallManually, InstallAll) + .then((answer) => { + if (answer === InstallAll) { + const tasks = items.map((item) => + this.createInstallTask(item, installable) + ); + this.executeTasks(tasks); + } else if (answer === InstallManually) { + viewContribution + .openView({ reveal: true }) + .then((widget) => widget.refresh(viewSearchOptions)); + } + }); } private async executeTasks(tasks: Task[]): Promise { @@ -139,21 +144,33 @@ export class CheckForUpdates extends Contribution { nls.localize('arduino/checkForUpdates/updating', 'Updating'), this.messageService, async (progress) => { - const total = tasks.length; - let count = 0; - for (const { run, item } of tasks) { - progress.report({ - message: item.name, - }); - await run(); - progress.report({ work: { total, done: ++count } }); + try { + const total = tasks.length; + let count = 0; + for (const { run, item } of tasks) { + // progress.report({ + // message: item.name, + // }); + 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 installTask( + private createInstallTask( item: T, installable: Installable ): Task { diff --git a/arduino-ide-extension/src/browser/style/list-widget.css b/arduino-ide-extension/src/browser/style/list-widget.css index 03f938f84..c1e11bf1b 100644 --- a/arduino-ide-extension/src/browser/style/list-widget.css +++ b/arduino-ide-extension/src/browser/style/list-widget.css @@ -16,7 +16,6 @@ } .arduino-list-widget .filter-bar { - display: flex; margin: 0px 10px 5px 15px; } diff --git a/arduino-ide-extension/src/common/protocol/installable.ts b/arduino-ide-extension/src/common/protocol/installable.ts index 59ae96edd..096934ff5 100644 --- a/arduino-ide-extension/src/common/protocol/installable.ts +++ b/arduino-ide-extension/src/common/protocol/installable.ts @@ -20,11 +20,6 @@ export interface Installable { * Uninstalls the given component. It is a NOOP if not installed. */ uninstall(options: { item: T; progressId?: string }): Promise; - - /** - * Returns with a promise which resolves all component that is installed but has a more recent version available. - */ - updateables(): Promise; } export namespace Installable { export type Version = string; diff --git a/arduino-ide-extension/src/node/boards-service-impl.ts b/arduino-ide-extension/src/node/boards-service-impl.ts index f3fff536e..12c3fe354 100644 --- a/arduino-ide-extension/src/node/boards-service-impl.ts +++ b/arduino-ide-extension/src/node/boards-service-impl.ts @@ -190,10 +190,6 @@ export class BoardsServiceImpl ); } - updateables(): Promise { - return this.search({ type: 'Updatable' }); - } - async searchBoards({ query, }: { diff --git a/arduino-ide-extension/src/node/library-service-impl.ts b/arduino-ide-extension/src/node/library-service-impl.ts index d19073f22..073e4ae76 100644 --- a/arduino-ide-extension/src/node/library-service-impl.ts +++ b/arduino-ide-extension/src/node/library-service-impl.ts @@ -143,10 +143,6 @@ export class LibraryServiceImpl return (item: LibraryPackage) => item.category === topic; } - async updateables(): Promise { - return this.search({ type: 'Updatable' }); - } - async list({ fqbn, }: { From b3d1fe89c535ef7450ff209e1115eab77d67d526 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Tue, 30 Aug 2022 10:36:41 +0200 Subject: [PATCH 19/22] Updated message when running tasks. Signed-off-by: Akos Kitta --- .../contributions/check-for-updates.ts | 72 ++++++++++++------- i18n/en.json | 6 +- 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/arduino-ide-extension/src/browser/contributions/check-for-updates.ts b/arduino-ide-extension/src/browser/contributions/check-for-updates.ts index 023773dc2..d305f9db2 100644 --- a/arduino-ide-extension/src/browser/contributions/check-for-updates.ts +++ b/arduino-ide-extension/src/browser/contributions/check-for-updates.ts @@ -23,14 +23,22 @@ const NoUpdates = nls.localize( 'arduino/checkForUpdates/noUpdates', 'There are no recent updates available.' ); -const UpdateBoards = nls.localize( - 'arduino/checkForUpdates/updatedBoth', +const PromptUpdateBoards = nls.localize( + 'arduino/checkForUpdates/promptUpdateBoards', 'Updates are available for some of your boards.' ); -const UpdateLibraries = nls.localize( - 'arduino/checkForUpdates/updatedBoth', +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' @@ -47,12 +55,12 @@ const Updatable = { type: 'Updatable' } as const; export class CheckForUpdates extends Contribution { @inject(WindowServiceExt) private readonly windowService: WindowServiceExt; - @inject(LibraryService) - private readonly libraryService: LibraryService; - @inject(BoardsService) - private readonly boardsService: BoardsService; @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) @@ -87,23 +95,25 @@ export class CheckForUpdates extends Contribution { } } - private promptUpdateLibraries(items: LibraryPackage[]): void { + private promptUpdateBoards(items: BoardsPackage[]): void { this.prompt({ items, - installable: this.libraryService, - viewContribution: this.librariesContribution, - viewSearchOptions: { query: '', topic: 'All', ...Updatable }, - message: UpdateLibraries, + installable: this.boardsService, + viewContribution: this.boardsContribution, + viewSearchOptions: { query: '', ...Updatable }, + promptMessage: PromptUpdateBoards, + updatingMessage: UpdatingBoards, }); } - private promptUpdateBoards(items: BoardsPackage[]): void { + private promptUpdateLibraries(items: LibraryPackage[]): void { this.prompt({ items, - installable: this.boardsService, - viewContribution: this.boardsContribution, - viewSearchOptions: { query: '', ...Updatable }, - message: UpdateBoards, + installable: this.libraryService, + viewContribution: this.librariesContribution, + viewSearchOptions: { query: '', topic: 'All', ...Updatable }, + promptMessage: PromptUpdateLibraries, + updatingMessage: UpdatingLibraries, }); } @@ -115,10 +125,18 @@ export class CheckForUpdates extends Contribution { installable: Installable; viewContribution: AbstractViewContribution>; viewSearchOptions: S; - message: string; + promptMessage: string; + updatingMessage: string; }): void { - const { items, installable, viewContribution, message, viewSearchOptions } = - options; + const { + items, + installable, + viewContribution, + promptMessage: message, + viewSearchOptions, + updatingMessage, + } = options; + if (!items.length) { return; } @@ -129,7 +147,7 @@ export class CheckForUpdates extends Contribution { const tasks = items.map((item) => this.createInstallTask(item, installable) ); - this.executeTasks(tasks); + this.executeTasks(updatingMessage, tasks); } else if (answer === InstallManually) { viewContribution .openView({ reveal: true }) @@ -138,19 +156,19 @@ export class CheckForUpdates extends Contribution { }); } - private async executeTasks(tasks: Task[]): Promise { + private async executeTasks( + message: string, + tasks: Task[] + ): Promise { if (tasks.length) { return ExecuteWithProgress.withProgress( - nls.localize('arduino/checkForUpdates/updating', 'Updating'), + message, this.messageService, async (progress) => { try { const total = tasks.length; let count = 0; for (const { run, item } of tasks) { - // progress.report({ - // message: item.name, - // }); try { await run(); // runs update sequentially. // TODO: is parallel update desired? } catch (err) { diff --git a/i18n/en.json b/i18n/en.json index 273ab8b43..53c1cf835 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -67,8 +67,10 @@ "checkForUpdates": "Check for Arduino Updates", "installAll": "Install All", "noUpdates": "There are no recent updates available.", - "updatedBoth": "Updates are available for some of your libraries.", - "updating": "Updating" + "promptUpdateBoards": "Updates are available for some of your boards.", + "promptUpdateLibraries": "Updates are available for some of your libraries.", + "updatingBoards": "Updating boards...", + "updatingLibraries": "Updating libraries..." }, "cli-error-parser": { "keyboardError": "'Keyboard' not found. Does your sketch include the line '#include '?", From 05f340b8804967689bd1934bd36e3461a2f6c84f Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Tue, 30 Aug 2022 10:57:01 +0200 Subject: [PATCH 20/22] use flex display per filter Signed-off-by: Akos Kitta --- .../src/browser/style/list-widget.css | 10 ++++++++++ .../browser/widgets/component-list/filter-renderer.tsx | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/arduino-ide-extension/src/browser/style/list-widget.css b/arduino-ide-extension/src/browser/style/list-widget.css index c1e11bf1b..74bb63ffe 100644 --- a/arduino-ide-extension/src/browser/style/list-widget.css +++ b/arduino-ide-extension/src/browser/style/list-widget.css @@ -23,6 +23,16 @@ padding: 0px 5px 0px 0px; } +.arduino-list-widget .filter-bar .filter { + display: flex; + align-items: center; + flex-grow: 1; +} + +.arduino-list-widget .filter-bar .filter > * { + flex-grow: 1; +} + .filterable-list-container { display: flex; flex-direction: column; diff --git a/arduino-ide-extension/src/browser/widgets/component-list/filter-renderer.tsx b/arduino-ide-extension/src/browser/widgets/component-list/filter-renderer.tsx index 58c0a5244..9a4c41237 100644 --- a/arduino-ide-extension/src/browser/widgets/component-list/filter-renderer.tsx +++ b/arduino-ide-extension/src/browser/widgets/component-list/filter-renderer.tsx @@ -18,7 +18,7 @@ export abstract class FilterRenderer { {Object.entries(options) .filter(([prop]) => props.includes(prop as keyof S)) .map(([prop, value]) => ( -
+
{this.propertyLabel(prop as keyof S)}: