diff --git a/arduino-ide-extension/src/browser/arduino-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-frontend-module.ts index 8e361456..2ea69840 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-frontend-module.ts @@ -13,7 +13,6 @@ import { ArduinoLanguageGrammarContribution } from './language/arduino-language- import { LibraryService, LibraryServicePath } from '../common/protocol/library-service'; import { BoardsService, BoardsServicePath, BoardsServiceClient } from '../common/protocol/boards-service'; import { SketchesService, SketchesServicePath } from '../common/protocol/sketches-service'; -import { LibraryListWidgetFrontendContribution } from './library/list-widget-frontend-contribution'; import { CoreService, CoreServicePath } from '../common/protocol/core-service'; import { BoardsListWidget } from './boards/boards-list-widget'; import { BoardsListWidgetFrontendContribution } from './boards/boards-widget-frontend-contribution'; @@ -52,6 +51,9 @@ import { ScmContribution } from '@theia/scm/lib/browser/scm-contribution'; import { SilentScmContribution } from './customization/silent-scm-contribution'; import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution'; import { SilentSearchInWorkspaceContribution } from './customization/silent-search-in-workspace-contribution'; +import { LibraryListWidgetFrontendContribution } from './library/library-widget-frontend-contribution'; +import { LibraryItemRenderer } from './library/library-item-renderer'; +import { BoardItemRenderer } from './boards/boards-item-renderer'; const ElementQueries = require('css-element-queries/src/ElementQueries'); if (!ARDUINO_PRO_MODE) { @@ -87,6 +89,7 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un createWidget: () => context.container.get(LibraryListWidget) })); bind(FrontendApplicationContribution).toService(LibraryListWidgetFrontendContribution); + bind(LibraryItemRenderer).toSelf().inSingletonScope(); // Sketch list service bind(SketchesService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, SketchesServicePath)).inSingletonScope(); @@ -113,6 +116,7 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un createWidget: () => context.container.get(BoardsListWidget) })); bind(FrontendApplicationContribution).toService(BoardsListWidgetFrontendContribution); + bind(BoardItemRenderer).toSelf().inSingletonScope(); // Board select dialog bind(BoardsConfigDialogWidget).toSelf().inSingletonScope(); diff --git a/arduino-ide-extension/src/browser/boards/boards-item-renderer.tsx b/arduino-ide-extension/src/browser/boards/boards-item-renderer.tsx new file mode 100644 index 00000000..0ba76b85 --- /dev/null +++ b/arduino-ide-extension/src/browser/boards/boards-item-renderer.tsx @@ -0,0 +1,38 @@ +import * as React from 'react'; +import { injectable } from 'inversify'; +import { ListItemRenderer } from '../components/component-list/list-item-renderer'; +import { BoardPackage } from '../../common/protocol/boards-service'; + +@injectable() +export class BoardItemRenderer extends ListItemRenderer { + + renderItem(item: BoardPackage, install: (item: BoardPackage) => Promise): React.ReactNode { + const name = {item.name}; + const author = {item.author}; + const installedVersion = !!item.installedVersion &&
+ Version {item.installedVersion} + INSTALLED +
; + + const summary =
{item.summary}
; + + const moreInfo = !!item.moreInfoLink && More info; + const installButton = item.installable && !item.installedVersion && + ; + + return
+
+ {name} by {author} + {installedVersion} +
+
+ {summary} +
+
+ {moreInfo} + {installButton} +
+
; + } + +} diff --git a/arduino-ide-extension/src/browser/boards/boards-list-widget.ts b/arduino-ide-extension/src/browser/boards/boards-list-widget.ts new file mode 100644 index 00000000..4f13c711 --- /dev/null +++ b/arduino-ide-extension/src/browser/boards/boards-list-widget.ts @@ -0,0 +1,27 @@ +import { inject, injectable } from 'inversify'; +import { BoardPackage, BoardsService } from '../../common/protocol/boards-service'; +import { ListWidget } from '../components/component-list/list-widget'; +import { BoardItemRenderer } from './boards-item-renderer'; + +@injectable() +export class BoardsListWidget extends ListWidget { + + static WIDGET_ID = 'boards-list-widget'; + static WIDGET_LABEL = 'Boards Manager'; + + constructor( + @inject(BoardsService) protected service: BoardsService, + @inject(BoardItemRenderer) protected itemRenderer: BoardItemRenderer) { + + super({ + id: BoardsListWidget.WIDGET_ID, + label: BoardsListWidget.WIDGET_LABEL, + iconClass: 'fa fa-microchip', + searchable: service, + installable: service, + itemLabel: (item: BoardPackage) => item.name, + itemRenderer + }); + } + +} diff --git a/arduino-ide-extension/src/browser/boards/boards-list-widget.tsx b/arduino-ide-extension/src/browser/boards/boards-list-widget.tsx deleted file mode 100644 index 5ef50d07..00000000 --- a/arduino-ide-extension/src/browser/boards/boards-list-widget.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { ListWidget } from './list-widget'; - -export class BoardsListWidget extends ListWidget { - - static WIDGET_ID = 'boards-list-widget'; - static WIDGET_LABEL = 'Boards Manager'; - - protected widgetProps(): ListWidget.Props { - return { - id: BoardsListWidget.WIDGET_ID, - title: BoardsListWidget.WIDGET_LABEL, - iconClass: 'fa fa-microchip' - } - } - -} 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 f7211947..d46b3f39 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,22 +1,12 @@ import { injectable } from 'inversify'; -import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application'; -import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution'; -import { ListWidget } from './list-widget'; -import { BoardsListWidget } from './boards-list-widget'; import { MenuModelRegistry } from '@theia/core'; +import { BoardsListWidget } from './boards-list-widget'; import { ArduinoMenus } from '../arduino-frontend-contribution'; +import { BoardPackage } from '../../common/protocol/boards-service'; +import { ListWidgetFrontendContribution } from '../components/component-list/list-widget-frontend-contribution'; @injectable() -export abstract class ListWidgetFrontendContribution extends AbstractViewContribution implements FrontendApplicationContribution { - - async initializeLayout(): Promise { - // await this.openView(); - } - -} - -@injectable() -export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution { +export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution { static readonly OPEN_MANAGER = `${BoardsListWidget.WIDGET_ID}:toggle`; diff --git a/arduino-ide-extension/src/browser/boards/list-widget.tsx b/arduino-ide-extension/src/browser/boards/list-widget.tsx deleted file mode 100644 index a0ad85ca..00000000 --- a/arduino-ide-extension/src/browser/boards/list-widget.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import * as React from 'react'; -import { inject, injectable, postConstruct } from 'inversify'; -import { Message } from '@phosphor/messaging'; -import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget'; -import { WindowService } from '@theia/core/lib/browser/window/window-service'; -import { BoardsService } from '../../common/protocol/boards-service'; -import { FilterableListContainer } from '../components/component-list/filterable-list-container'; -import { BoardsServiceClientImpl } from './boards-service-client-impl'; - -@injectable() -export abstract class ListWidget extends ReactWidget { - - @inject(BoardsService) - protected readonly boardsService: BoardsService; - - @inject(WindowService) - protected readonly windowService: WindowService; - - @inject(BoardsServiceClientImpl) - protected readonly boardsServiceClient: BoardsServiceClientImpl; - - constructor() { - super(); - const { id, title, iconClass } = this.widgetProps(); - this.id = id; - this.title.label = title; - this.title.caption = title; - this.title.iconClass = iconClass; - this.title.closable = true; - this.addClass(ListWidget.Styles.LIST_WIDGET_CLASS); - this.node.tabIndex = 0; // To be able to set the focus on the widget. - } - - protected abstract widgetProps(): ListWidget.Props; - - @postConstruct() - protected init(): void { - this.update(); - } - - protected onActivateRequest(msg: Message): void { - super.onActivateRequest(msg); - this.node.focus(); - this.render(); - } - - protected onUpdateRequest(msg: Message): void { - super.onUpdateRequest(msg); - this.render(); - } - - render(): React.ReactNode { - return ; - } - -} - -export namespace ListWidget { - - /** - * Props for customizing the abstract list widget. - */ - export interface Props { - readonly id: string; - readonly title: string; - readonly iconClass: string; - } - - export namespace Styles { - export const LIST_WIDGET_CLASS = 'arduino-list-widget' - } - -} diff --git a/arduino-ide-extension/src/browser/components/component-list/component-list-item.tsx b/arduino-ide-extension/src/browser/components/component-list/component-list-item.tsx index 6ac4b723..ea92dfc1 100644 --- a/arduino-ide-extension/src/browser/components/component-list/component-list-item.tsx +++ b/arduino-ide-extension/src/browser/components/component-list/component-list-item.tsx @@ -1,78 +1,25 @@ import * as React from 'react'; -import { WindowService } from '@theia/core/lib/browser/window/window-service'; -import { ArduinoComponent } from '../../../common/protocol/arduino-component'; +import { ListItemRenderer } from './list-item-renderer'; -export class ComponentListItem extends React.Component { +export class ComponentListItem extends React.Component> { - protected onClick = (event: React.SyntheticEvent) => { - const { target } = event.nativeEvent; - if (target instanceof HTMLAnchorElement) { - this.props.windowService.openNewWindow(target.href); - event.nativeEvent.preventDefault(); - } - } - - protected async install(item: ArduinoComponent): Promise { + protected async install(item: T): Promise { await this.props.install(item); } render(): React.ReactNode { - const { item } = this.props; - - const style = ComponentListItem.Styles; - const name = {item.name}; - const author = {item.author}; - const installedVersion = !!item.installedVersion &&
- Version {item.installedVersion} - INSTALLED -
; - - const summary =
{item.summary}
; - - const moreInfo = !!item.moreInfoLink && More info; - const install = this.props.install && item.installable && !item.installedVersion && - ; - - return
-
- {name} by {author} - {installedVersion} -
-
- {summary} -
-
- {moreInfo} - {install} -
-
; + const { item, itemRenderer, install } = this.props; + return itemRenderer.renderItem(item, install.bind(this)); } } export namespace ComponentListItem { - export interface Props { - readonly item: ArduinoComponent; - readonly windowService: WindowService; - readonly install: (comp: ArduinoComponent) => Promise; - } - - export namespace Styles { - export const LIST_ITEM_CLASS = 'component-list-item'; - export const HEADER_CLASS = 'header'; - export const VERSION_INFO_CLASS = 'version-info'; - export const CONTENT_CLASS = 'content'; - export const FOOTER_CLASS = 'footer'; - export const INSTALLED_CLASS = 'installed'; - export const NO_SELECT_CLASS = 'noselect'; - - export const NAME_CLASS = 'name'; - export const AUTHOR_CLASS = 'author'; - export const VERSION_CLASS = 'version'; - export const SUMMARY_CLASS = 'summary'; - export const DESCRIPTION_CLASS = 'description'; - export const INSTALL_BTN_CLASS = 'install'; + export interface Props { + readonly item: T; + readonly install: (item: T) => Promise; + readonly itemRenderer: ListItemRenderer; } } diff --git a/arduino-ide-extension/src/browser/components/component-list/component-list.tsx b/arduino-ide-extension/src/browser/components/component-list/component-list.tsx index 8a8c7574..0fe2b2c5 100644 --- a/arduino-ide-extension/src/browser/components/component-list/component-list.tsx +++ b/arduino-ide-extension/src/browser/components/component-list/component-list.tsx @@ -1,16 +1,15 @@ import * as React from 'react'; -import { WindowService } from '@theia/core/lib/browser/window/window-service'; import { ComponentListItem } from './component-list-item'; -import { ArduinoComponent } from '../../../common/protocol/arduino-component'; +import { ListItemRenderer } from './list-item-renderer'; -export class ComponentList extends React.Component { +export class ComponentList extends React.Component> { protected container?: HTMLElement; render(): React.ReactNode { return
this.container = element || undefined}> + ref={this.setRef}> {this.props.items.map(item => this.createItem(item))}
; } @@ -21,19 +20,28 @@ export class ComponentList extends React.Component { } } - protected createItem(item: ArduinoComponent): React.ReactNode { - return + protected setRef = (element: HTMLElement | null) => { + this.container = element || undefined; + } + + protected createItem(item: T): React.ReactNode { + return + key={this.props.itemLabel(item)} + item={item} + itemRenderer={this.props.itemRenderer} + install={this.props.install} /> } } export namespace ComponentList { - export interface Props { - readonly items: ArduinoComponent[]; - readonly windowService: WindowService; - readonly install: (comp: ArduinoComponent) => Promise; - readonly resolveContainer?: (element: HTMLElement) => void; + export interface Props { + readonly items: T[]; + readonly itemLabel: (item: T) => string; + readonly itemRenderer: ListItemRenderer; + readonly install: (item: T) => Promise; + readonly resolveContainer: (element: HTMLElement) => void; } } diff --git a/arduino-ide-extension/src/browser/components/component-list/filterable-list-container.tsx b/arduino-ide-extension/src/browser/components/component-list/filterable-list-container.tsx index af1cbf78..b7764764 100644 --- a/arduino-ide-extension/src/browser/components/component-list/filterable-list-container.tsx +++ b/arduino-ide-extension/src/browser/components/component-list/filterable-list-container.tsx @@ -1,23 +1,24 @@ import * as React from 'react'; -import { WindowService } from '@theia/core/lib/browser/window/window-service'; +import debounce = require('lodash.debounce'); +import { Searchable } from '../../../common/protocol/searchable'; +import { Installable } from '../../../common/protocol/installable'; +import { InstallationProgressDialog } from '../installation-progress-dialog'; import { SearchBar } from './search-bar'; import { ComponentList } from './component-list'; -import { LibraryService } from '../../../common/protocol/library-service'; -import { ArduinoComponent } from '../../../common/protocol/arduino-component'; -import { InstallationProgressDialog } from '../installation-progress-dialog'; +import { ListItemRenderer } from './list-item-renderer'; -export class FilterableListContainer extends React.Component { +export class FilterableListContainer extends React.Component, FilterableListContainer.State> { - constructor(props: Readonly) { + constructor(props: Readonly>) { super(props); this.state = { filterText: '', items: [] }; - this.handleFilterTextChange = this.handleFilterTextChange.bind(this); } componentWillMount(): void { + this.search = debounce(this.search, 500); this.handleFilterTextChange(''); } @@ -42,36 +43,43 @@ export class FilterableListContainer extends React.Component items={this.state.items} + itemLabel={itemLabel} + itemRenderer={itemRenderer} install={this.install.bind(this)} - windowService={this.props.windowService} - resolveContainer={this.props.resolveContainer} + resolveContainer={resolveContainer} /> } - private handleFilterTextChange(filterText: string): void { - const { props } = this.state; - this.props.service.search({ query: filterText, props }).then(result => { + protected handleFilterTextChange = (filterText: string) => { + this.setState({ filterText }); + this.search(filterText); + } + + protected search (query: string): void { + const { searchable } = this.props; + searchable.search({ query }).then(result => { const { items } = result; this.setState({ - filterText, items: this.sort(items) }); }); } - protected sort(items: ArduinoComponent[]): ArduinoComponent[] { - return items.sort((left, right) => left.name.localeCompare(right.name)); + protected sort(items: T[]): T[] { + const { itemLabel } = this.props; + return items.sort((left, right) => itemLabel(left).localeCompare(itemLabel(right))); } - protected async install(comp: ArduinoComponent): Promise { - const dialog = new InstallationProgressDialog(comp.name); + protected async install(item: T): Promise { + const { installable, searchable, itemLabel } = this.props; + const dialog = new InstallationProgressDialog(itemLabel(item)); dialog.open(); try { - await this.props.service.install(comp); - const { props } = this.state; - const { items } = await this.props.service.search({ query: this.state.filterText, props }); + await installable.install(item); + const { items } = await searchable.search({ query: this.state.filterText }); this.setState({ items: this.sort(items) }); } finally { dialog.close(); @@ -82,23 +90,18 @@ export class FilterableListContainer extends React.Component void; - readonly resolveFocus?: (element: HTMLElement | undefined) => void; + export interface Props { + readonly installable: Installable; + readonly searchable: Searchable; + readonly itemLabel: (item: T) => string; + readonly itemRenderer: ListItemRenderer; + readonly resolveContainer: (element: HTMLElement) => void; + readonly resolveFocus: (element: HTMLElement | undefined) => void; } - export interface State { + export interface State { filterText: string; - items: ArduinoComponent[]; - props?: LibraryService.Search.Props; - } - - export interface ComponentSource { - search(req: { query: string, props?: LibraryService.Search.Props }): Promise<{ items: ArduinoComponent[] }> - install(board: ArduinoComponent): Promise; + items: T[]; } } - diff --git a/arduino-ide-extension/src/browser/components/component-list/list-item-renderer.tsx b/arduino-ide-extension/src/browser/components/component-list/list-item-renderer.tsx new file mode 100644 index 00000000..e683275c --- /dev/null +++ b/arduino-ide-extension/src/browser/components/component-list/list-item-renderer.tsx @@ -0,0 +1,21 @@ +import * as React from 'react'; +import { inject, injectable } from 'inversify'; +import { WindowService } from '@theia/core/lib/browser/window/window-service'; + +@injectable() +export abstract class ListItemRenderer { + + @inject(WindowService) + protected windowService: WindowService; + + protected onClick = (event: React.SyntheticEvent) => { + const { target } = event.nativeEvent; + if (target instanceof HTMLAnchorElement) { + this.windowService.openNewWindow(target.href); + event.nativeEvent.preventDefault(); + } + } + + abstract renderItem(item: T, install: (item: T) => Promise): React.ReactNode; + +} \ No newline at end of file diff --git a/arduino-ide-extension/src/browser/components/component-list/list-widget-frontend-contribution.ts b/arduino-ide-extension/src/browser/components/component-list/list-widget-frontend-contribution.ts new file mode 100644 index 00000000..448f8070 --- /dev/null +++ b/arduino-ide-extension/src/browser/components/component-list/list-widget-frontend-contribution.ts @@ -0,0 +1,12 @@ +import { injectable } from 'inversify'; +import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application'; +import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution'; +import { ListWidget } from './list-widget'; + +@injectable() +export abstract class ListWidgetFrontendContribution extends AbstractViewContribution> implements FrontendApplicationContribution { + + async initializeLayout(): Promise { + } + +} \ No newline at end of file diff --git a/arduino-ide-extension/src/browser/library/library-list-widget.tsx b/arduino-ide-extension/src/browser/components/component-list/list-widget.tsx similarity index 59% rename from arduino-ide-extension/src/browser/library/library-list-widget.tsx rename to arduino-ide-extension/src/browser/components/component-list/list-widget.tsx index 00b74e8e..897a76ff 100644 --- a/arduino-ide-extension/src/browser/library/library-list-widget.tsx +++ b/arduino-ide-extension/src/browser/components/component-list/list-widget.tsx @@ -1,24 +1,16 @@ import * as React from 'react'; -import { inject, injectable, postConstruct } from 'inversify'; +import { injectable, postConstruct } from 'inversify'; import { Message } from '@phosphor/messaging'; import { Deferred } from '@theia/core/lib/common/promise-util'; import { MaybePromise } from '@theia/core/lib/common/types'; import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget'; -import { WindowService } from '@theia/core/lib/browser/window/window-service'; -import { LibraryFilterableListContainer } from './library-filterable-list-container'; -import { LibraryService } from '../../common/protocol/library-service'; +import { Installable } from '../../../common/protocol/installable'; +import { Searchable } from '../../../common/protocol/searchable'; +import { FilterableListContainer } from './filterable-list-container'; +import { ListItemRenderer } from './list-item-renderer'; @injectable() -export class LibraryListWidget extends ReactWidget { - - static WIDGET_ID = 'library-list-widget'; - static WIDGET_LABEL = 'Library Manager'; - - @inject(LibraryService) - protected readonly libraryService: LibraryService; - - @inject(WindowService) - protected readonly windowService: WindowService; +export abstract class ListWidget extends ReactWidget { /** * Do not touch or use it. It is for setting the focus on the `input` after the widget activation. @@ -26,12 +18,13 @@ export class LibraryListWidget extends ReactWidget { protected focusNode: HTMLElement | undefined; protected readonly deferredContainer = new Deferred(); - constructor() { + constructor(protected options: ListWidget.Options) { super(); - this.id = LibraryListWidget.WIDGET_ID - this.title.label = LibraryListWidget.WIDGET_LABEL; - this.title.caption = LibraryListWidget.WIDGET_LABEL - this.title.iconClass = 'library-tab-icon'; + const { id, label, iconClass } = options; + this.id = id; + this.title.label = label; + this.title.caption = label; + this.title.iconClass = iconClass this.title.closable = true; this.addClass('arduino-list-widget'); this.node.tabIndex = 0; // To be able to set the focus on the widget. @@ -64,25 +57,25 @@ export class LibraryListWidget extends ReactWidget { } render(): React.ReactNode { - return resolveContainer={this.deferredContainer.resolve} resolveFocus={this.onFocusResolved} - service={this.libraryService} - windowService={this.windowService} - />; + searchable={this.options.searchable} + installable={this.options.installable} + itemLabel={this.options.itemLabel} + itemRenderer={this.options.itemRenderer} />; } } export namespace ListWidget { - - /** - * Props for customizing the abstract list widget. - */ - export interface Props { + export interface Options { readonly id: string; - readonly title: string; + readonly label: string; readonly iconClass: string; + readonly installable: Installable; + readonly searchable: Searchable; + readonly itemLabel: (item: T) => string; + readonly itemRenderer: ListItemRenderer; } - } diff --git a/arduino-ide-extension/src/browser/components/installation-progress-dialog.tsx b/arduino-ide-extension/src/browser/components/installation-progress-dialog.tsx index 8d0be2de..aefa3940 100644 --- a/arduino-ide-extension/src/browser/components/installation-progress-dialog.tsx +++ b/arduino-ide-extension/src/browser/components/installation-progress-dialog.tsx @@ -1,12 +1,12 @@ -import { AbstractDialog } from "@theia/core/lib/browser"; +import { AbstractDialog } from '@theia/core/lib/browser'; +export class InstallationProgressDialog extends AbstractDialog { -export class InstallationProgressDialog extends AbstractDialog { - readonly value: "does-not-matter"; + readonly value = undefined; constructor(componentName: string) { super({ title: 'Installation in progress' }); this.contentNode.textContent = `Installing ${componentName}. Please wait.`; } -} \ No newline at end of file +} diff --git a/arduino-ide-extension/src/browser/library/library-component-list-item.tsx b/arduino-ide-extension/src/browser/library/library-component-list-item.tsx deleted file mode 100644 index 988ddf72..00000000 --- a/arduino-ide-extension/src/browser/library/library-component-list-item.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import * as React from 'react'; -import { ComponentListItem } from '../components/component-list/component-list-item'; - -export class LibraryComponentListItem extends ComponentListItem { - - render(): React.ReactNode { - const { item } = this.props; - - const name = {item.name}; - const author = by {item.author}; - const installedVersion = !!item.installedVersion &&
- Version {item.installedVersion} - INSTALLED -
; - - const summary =
{item.summary}
; - - const moreInfo = !!item.moreInfoLink && More info; - const install = this.props.install && item.installable && !item.installedVersion && - ; - const versions = (() => { - const { availableVersions } = item; - if (availableVersions.length === 0) { - return undefined; - } else if (availableVersions.length === 1) { - return - } else { - return ; - } - })(); - - return
-
- {name} {author} - {installedVersion} -
-
- {summary} -
-
- {moreInfo} -
-
- {install} - {versions} -
-
; - } - -} diff --git a/arduino-ide-extension/src/browser/library/library-component-list.tsx b/arduino-ide-extension/src/browser/library/library-component-list.tsx deleted file mode 100644 index 4cc67968..00000000 --- a/arduino-ide-extension/src/browser/library/library-component-list.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import * as React from 'react'; -import { ArduinoComponent } from '../../common/protocol/arduino-component'; -import { ComponentList } from '../components/component-list/component-list'; -import { LibraryComponentListItem } from './library-component-list-item'; - -export class LibraryComponentList extends ComponentList { - - createItem(item: ArduinoComponent): React.ReactNode { - return - } - -} diff --git a/arduino-ide-extension/src/browser/library/library-filterable-list-container.tsx b/arduino-ide-extension/src/browser/library/library-filterable-list-container.tsx deleted file mode 100644 index a532b45b..00000000 --- a/arduino-ide-extension/src/browser/library/library-filterable-list-container.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import * as React from 'react'; -import { FilterableListContainer } from '../components/component-list/filterable-list-container'; -import { LibraryComponentList } from './library-component-list'; - -export class LibraryFilterableListContainer extends FilterableListContainer { - - constructor(props: Readonly) { - super(props); - this.state = { - filterText: '', - items: [], - props: { - topic: this.topics[0], - type: this.types[0] - } - }; - } - - protected renderSearchFilter(): React.ReactNode { - const types = this.types.map(type => ); - let type = this.types[0]; - if (this.state.props) { - const currentType = this.types.find(t => t === this.state.props!.type) || this.types[0]; - if (currentType) { - type = currentType; - } - } - const topics = this.topics.map(topic => ); - let topic = this.topics[0]; - if (this.state.props) { - const currentTopic = this.topics.find(t => t === this.state.props!.topic) || this.topics[0]; - if (currentTopic) { - topic = currentTopic; - } - } - return
-
-
Type
{/** TODO: do `minWidth` better! */} - -
-
-
Topic
- -
-
- } - - protected onTypeChange = (event: React.ChangeEvent) => { - const type = event.target.value; - const props = { ...(this.state.props || {}), ...{ type } }; - this.setState({ - props - }); - } - - protected onTopicChange = (event: React.ChangeEvent) => { - const topic = event.target.value; - const props = { ...(this.state.props || {}), ...{ topic } }; - this.setState({ - props - }); - } - - protected renderComponentList(): React.ReactNode { - return - } - - private get topics(): string[] { - return [ - 'All', - 'Communication', - 'Data Processing', - 'Data Storage', - 'Device Control', - 'Display', - 'Other', - 'Sensor', - 'Signal Input/Output', - 'Timing', - 'Uncategorized' - ]; - } - - private get types(): string[] { - return [ - 'All', - 'Updatable', - 'Installed', - 'Arduino', - 'Partner', - 'Recommended', - 'Contributed', - 'Retired' - ]; - } - -} diff --git a/arduino-ide-extension/src/browser/library/library-item-renderer.tsx b/arduino-ide-extension/src/browser/library/library-item-renderer.tsx new file mode 100644 index 00000000..2e98480a --- /dev/null +++ b/arduino-ide-extension/src/browser/library/library-item-renderer.tsx @@ -0,0 +1,52 @@ +import * as React from 'react'; +import { injectable } from 'inversify'; +import { Library } from '../../common/protocol/library-service'; +import { ListItemRenderer } from '../components/component-list/list-item-renderer'; + +@injectable() +export class LibraryItemRenderer extends ListItemRenderer { + + renderItem(item: Library, install: (item: Library) => Promise): React.ReactNode { + const name = {item.name}; + const author = by {item.author}; + const installedVersion = !!item.installedVersion &&
+ Version {item.installedVersion} + INSTALLED +
; + + const summary =
{item.summary}
; + + const moreInfo = !!item.moreInfoLink && More info; + const installButton = item.installable && !item.installedVersion && + ; + + const versions = (() => { + const { availableVersions } = item; + if (availableVersions.length === 0) { + return undefined; + } else if (availableVersions.length === 1) { + return + } else { + return ; + } + })(); + + return
+
+ {name} {author} + {installedVersion} +
+
+ {summary} +
+
+ {moreInfo} +
+
+ {installButton} + {versions} +
+
; + } + +} diff --git a/arduino-ide-extension/src/browser/library/library-list-widget.ts b/arduino-ide-extension/src/browser/library/library-list-widget.ts new file mode 100644 index 00000000..83bd8bcc --- /dev/null +++ b/arduino-ide-extension/src/browser/library/library-list-widget.ts @@ -0,0 +1,27 @@ +import { inject, injectable } from 'inversify'; +import { Library, LibraryService } from '../../common/protocol/library-service'; +import { ListWidget } from '../components/component-list/list-widget'; +import { LibraryItemRenderer } from './library-item-renderer'; + +@injectable() +export class LibraryListWidget extends ListWidget { + + static WIDGET_ID = 'library-list-widget'; + static WIDGET_LABEL = 'Library Manager'; + + constructor( + @inject(LibraryService) protected service: LibraryService, + @inject(LibraryItemRenderer) protected itemRenderer: LibraryItemRenderer) { + + super({ + id: LibraryListWidget.WIDGET_ID, + label: LibraryListWidget.WIDGET_LABEL, + iconClass: 'library-tab-icon', + searchable: service, + installable: service, + itemLabel: (item: Library) => item.name, + itemRenderer + }); + } + +} diff --git a/arduino-ide-extension/src/browser/library/list-widget-frontend-contribution.ts b/arduino-ide-extension/src/browser/library/library-widget-frontend-contribution.ts similarity index 100% rename from arduino-ide-extension/src/browser/library/list-widget-frontend-contribution.ts rename to arduino-ide-extension/src/browser/library/library-widget-frontend-contribution.ts diff --git a/arduino-ide-extension/src/common/protocol/boards-service.ts b/arduino-ide-extension/src/common/protocol/boards-service.ts index 5b781d81..edc2158a 100644 --- a/arduino-ide-extension/src/common/protocol/boards-service.ts +++ b/arduino-ide-extension/src/common/protocol/boards-service.ts @@ -1,5 +1,7 @@ -import { ArduinoComponent } from "./arduino-component"; -import { JsonRpcServer } from "@theia/core"; +import { JsonRpcServer } from '@theia/core'; +import { Searchable } from './searchable'; +import { Installable } from './installable'; +import { ArduinoComponent } from './arduino-component'; export interface AttachedBoardsChangeEvent { readonly oldState: Readonly<{ boards: Board[] }>; @@ -18,10 +20,8 @@ export interface BoardsServiceClient { export const BoardsServicePath = '/services/boards-service'; export const BoardsService = Symbol('BoardsService'); -export interface BoardsService extends JsonRpcServer { +export interface BoardsService extends Installable, Searchable, JsonRpcServer { getAttachedBoards(): Promise<{ boards: Board[] }>; - search(options: { query?: string }): Promise<{ items: BoardPackage[] }>; - install(item: BoardPackage): Promise; } export interface BoardPackage extends ArduinoComponent { diff --git a/arduino-ide-extension/src/common/protocol/installable.ts b/arduino-ide-extension/src/common/protocol/installable.ts new file mode 100644 index 00000000..f9d1887f --- /dev/null +++ b/arduino-ide-extension/src/common/protocol/installable.ts @@ -0,0 +1,3 @@ +export interface Installable { + install(item: T): Promise; +} \ No newline at end of file diff --git a/arduino-ide-extension/src/common/protocol/library-service.ts b/arduino-ide-extension/src/common/protocol/library-service.ts index f8689d5a..67f463f7 100644 --- a/arduino-ide-extension/src/common/protocol/library-service.ts +++ b/arduino-ide-extension/src/common/protocol/library-service.ts @@ -1,20 +1,13 @@ -import { ArduinoComponent } from "./arduino-component"; +import { Searchable } from './searchable'; +import { Installable } from './installable'; +import { ArduinoComponent } from './arduino-component'; export const LibraryServicePath = '/services/library-service'; export const LibraryService = Symbol('LibraryService'); -export interface LibraryService { - search(options: { query?: string, props?: LibraryService.Search.Props }): Promise<{ items: Library[] }>; +export interface LibraryService extends Installable, Searchable { install(library: Library): Promise; } -export namespace LibraryService { - export namespace Search { - export interface Props { - [key: string]: string | undefined; - } - } -} - export interface Library extends ArduinoComponent { readonly builtIn?: boolean; } diff --git a/arduino-ide-extension/src/common/protocol/searchable.ts b/arduino-ide-extension/src/common/protocol/searchable.ts new file mode 100644 index 00000000..f4e996b9 --- /dev/null +++ b/arduino-ide-extension/src/common/protocol/searchable.ts @@ -0,0 +1,11 @@ +export interface Searchable { + search(options: Searchable.Options): Promise<{ items: T[] }>; +} +export namespace Searchable { + export interface Options { + /** + * Defaults to empty an empty string. + */ + readonly query?: string; + } +} \ No newline at end of file diff --git a/arduino-ide-extension/src/node/library-service-impl.ts b/arduino-ide-extension/src/node/library-service-impl.ts index f149ae64..93a9cc42 100644 --- a/arduino-ide-extension/src/node/library-service-impl.ts +++ b/arduino-ide-extension/src/node/library-service-impl.ts @@ -14,7 +14,7 @@ export class LibraryServiceImpl implements LibraryService { @inject(ToolOutputServiceServer) protected readonly toolOutputService: ToolOutputServiceServer; - async search(options: { query?: string, props: LibraryService.Search.Props }): Promise<{ items: Library[] }> { + async search(options: { query?: string }): Promise<{ items: Library[] }> { const coreClient = await this.coreClientProvider.getClient(); if (!coreClient) { return { items: [] };