Generalized the list item renderers.

To support update/downgrade.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
Akos Kitta 2019-11-22 11:42:39 +01:00
parent 63cd2701b4
commit c3e2aa4feb
9 changed files with 84 additions and 156 deletions

View File

@ -52,8 +52,6 @@ import { ArduinoScmContribution } from './customization/arduino-scm-contribution
import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution'; import { SearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
import { ArduinoSearchInWorkspaceContribution } from './customization/arduino-search-in-workspace-contribution'; import { ArduinoSearchInWorkspaceContribution } from './customization/arduino-search-in-workspace-contribution';
import { LibraryListWidgetFrontendContribution } from './library/library-widget-frontend-contribution'; import { LibraryListWidgetFrontendContribution } from './library/library-widget-frontend-contribution';
import { LibraryItemRenderer } from './library/library-item-renderer';
import { BoardItemRenderer } from './boards/boards-item-renderer';
import { MonitorServiceClientImpl } from './monitor/monitor-service-client-impl'; import { MonitorServiceClientImpl } from './monitor/monitor-service-client-impl';
import { MonitorServicePath, MonitorService, MonitorServiceClient } from '../common/protocol/monitor-service'; import { MonitorServicePath, MonitorService, MonitorServiceClient } from '../common/protocol/monitor-service';
import { ConfigService, ConfigServicePath } from '../common/protocol/config-service'; import { ConfigService, ConfigServicePath } from '../common/protocol/config-service';
@ -72,6 +70,7 @@ import { AboutDialog } from '@theia/core/lib/browser/about-dialog';
import { ArduinoAboutDialog } from './customization/arduino-about-dialog'; import { ArduinoAboutDialog } from './customization/arduino-about-dialog';
import { ArduinoShellLayoutRestorer } from './shell/arduino-shell-layout-restorer'; import { ArduinoShellLayoutRestorer } from './shell/arduino-shell-layout-restorer';
import { EditorMode } from './editor-mode'; import { EditorMode } from './editor-mode';
import { ListItemRenderer } from './components/component-list/list-item-renderer';
const ElementQueries = require('css-element-queries/src/ElementQueries'); const ElementQueries = require('css-element-queries/src/ElementQueries');
export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => { export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => {
@ -92,9 +91,11 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
bind(LanguageGrammarDefinitionContribution).to(ArduinoLanguageGrammarContribution).inSingletonScope(); bind(LanguageGrammarDefinitionContribution).to(ArduinoLanguageGrammarContribution).inSingletonScope();
bind(LanguageClientContribution).to(ArduinoLanguageClientContribution).inSingletonScope(); bind(LanguageClientContribution).to(ArduinoLanguageClientContribution).inSingletonScope();
// Renderer for both the library and the core widgets.
bind(ListItemRenderer).toSelf().inSingletonScope();
// Library service // Library service
bind(LibraryService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, LibraryServicePath)).inSingletonScope(); bind(LibraryService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, LibraryServicePath)).inSingletonScope();
// Library list widget // Library list widget
bind(LibraryListWidget).toSelf(); bind(LibraryListWidget).toSelf();
bindViewContribution(bind, LibraryListWidgetFrontendContribution); bindViewContribution(bind, LibraryListWidgetFrontendContribution);
@ -103,7 +104,6 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
createWidget: () => context.container.get(LibraryListWidget) createWidget: () => context.container.get(LibraryListWidget)
})); }));
bind(FrontendApplicationContribution).toService(LibraryListWidgetFrontendContribution); bind(FrontendApplicationContribution).toService(LibraryListWidgetFrontendContribution);
bind(LibraryItemRenderer).toSelf().inSingletonScope();
// Sketch list service // Sketch list service
bind(SketchesService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, SketchesServicePath)).inSingletonScope(); bind(SketchesService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, SketchesServicePath)).inSingletonScope();
@ -137,7 +137,6 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
createWidget: () => context.container.get(BoardsListWidget) createWidget: () => context.container.get(BoardsListWidget)
})); }));
bind(FrontendApplicationContribution).toService(BoardsListWidgetFrontendContribution); bind(FrontendApplicationContribution).toService(BoardsListWidgetFrontendContribution);
bind(BoardItemRenderer).toSelf().inSingletonScope();
// Board select dialog // Board select dialog
bind(BoardsConfigDialogWidget).toSelf().inSingletonScope(); bind(BoardsConfigDialogWidget).toSelf().inSingletonScope();

View File

@ -1,77 +0,0 @@
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';
import { Installable } from '../../common/protocol/installable';
import { ComponentListItem } from '../components/component-list/component-list-item';
@injectable()
export class BoardItemRenderer extends ListItemRenderer<BoardPackage> {
renderItem(
input: ComponentListItem.State & { item: BoardPackage },
install: (item: BoardPackage) => Promise<void>,
onVersionChange: (version: Installable.Version) => void): React.ReactNode {
const { item } = input;
const name = <span className='name'>{item.name}</span>;
const author = <span className='author'>{item.author}</span>;
const installedVersion = !!item.installedVersion && <div className='version-info'>
<span className='version'>Version {item.installedVersion}</span>
<span className='installed'>INSTALLED</span>
</div>;
const summary = <div className='summary'>{item.summary}</div>;
const description = <div className='summary'>{item.description}</div>;
const moreInfo = !!item.moreInfoLink && <a href={item.moreInfoLink} onClick={this.onMoreInfoClick}>More info</a>;
const onClickInstall = () => install(item);
const installButton = item.installable &&
<button className='install' onClick={onClickInstall}>INSTALL</button>;
const onSelectChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
const version = event.target.value;
if (version) {
onVersionChange(version);
}
}
const versions = (() => {
const { availableVersions } = item;
if (availableVersions.length === 0) {
return undefined;
} else if (availableVersions.length === 1) {
return <label>{availableVersions[0]}</label>
} else {
return <select
value={input.selectedVersion}
onChange={onSelectChange}>
{
item.availableVersions
.filter(version => version !== item.installedVersion) // Filter the version that is currently installed.
.map(version => <option value={version} key={version}>{version}</option>)
}
</select>;
}
})();
return <div className='component-list-item noselect'>
<div className='header'>
<span>{name} by {author}</span>
{installedVersion}
</div>
<div className='content'>
{summary}
{description}
</div>
<div className='info'>
{moreInfo}
</div>
<div className='footer'>
{installButton}
{versions}
</div>
</div>;
}
}

View File

@ -1,7 +1,7 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from 'inversify';
import { BoardPackage, BoardsService } from '../../common/protocol/boards-service'; import { BoardPackage, BoardsService } from '../../common/protocol/boards-service';
import { ListWidget } from '../components/component-list/list-widget'; import { ListWidget } from '../components/component-list/list-widget';
import { BoardItemRenderer } from './boards-item-renderer'; import { ListItemRenderer } from '../components/component-list/list-item-renderer';
@injectable() @injectable()
export class BoardsListWidget extends ListWidget<BoardPackage> { export class BoardsListWidget extends ListWidget<BoardPackage> {
@ -11,7 +11,7 @@ export class BoardsListWidget extends ListWidget<BoardPackage> {
constructor( constructor(
@inject(BoardsService) protected service: BoardsService, @inject(BoardsService) protected service: BoardsService,
@inject(BoardItemRenderer) protected itemRenderer: BoardItemRenderer) { @inject(ListItemRenderer) protected itemRenderer: ListItemRenderer<BoardPackage>) {
super({ super({
id: BoardsListWidget.WIDGET_ID, id: BoardsListWidget.WIDGET_ID,

View File

@ -6,7 +6,7 @@ import { ArduinoComponent } from '../../../common/protocol/arduino-component';
import { ComponentListItem } from './component-list-item'; import { ComponentListItem } from './component-list-item';
@injectable() @injectable()
export abstract class ListItemRenderer<T extends ArduinoComponent> { export class ListItemRenderer<T extends ArduinoComponent> {
@inject(WindowService) @inject(WindowService)
protected windowService: WindowService; protected windowService: WindowService;
@ -19,10 +19,71 @@ export abstract class ListItemRenderer<T extends ArduinoComponent> {
} }
} }
abstract renderItem( renderItem(
input: ComponentListItem.State & { item: T }, input: ComponentListItem.State & { item: T },
install: (item: T) => Promise<void>, install: (item: T) => Promise<void>,
onVersionChange: (version: Installable.Version) => void onVersionChange: (version: Installable.Version) => void
): React.ReactNode; ): React.ReactNode {
const { item } = input;
const name = <span className='name'>{item.name}</span>;
const author = <span className='author'>{item.author}</span>;
const installedVersion = !!item.installedVersion && <div className='version-info'>
<span className='version'>Version {item.installedVersion}</span>
<span className='installed'>INSTALLED</span>
</div>;
const summary = <div className='summary'>{item.summary}</div>;
const description = <div className='summary'>{item.description}</div>;
const moreInfo = !!item.moreInfoLink && <a href={item.moreInfoLink} onClick={this.onMoreInfoClick}>More info</a>;
const onClickInstall = () => install(item);
const installButton = item.installable &&
<button className='install' onClick={onClickInstall}>INSTALL</button>;
const onSelectChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
const version = event.target.value;
if (version) {
onVersionChange(version);
}
}
const versions = (() => {
const { availableVersions } = item;
if (availableVersions.length === 0) {
return undefined;
} else if (availableVersions.length === 1) {
return <label>{availableVersions[0]}</label>
} else {
return <select
value={input.selectedVersion}
onChange={onSelectChange}>
{
item.availableVersions
.filter(version => version !== item.installedVersion) // Filter the version that is currently installed.
.map(version => <option value={version} key={version}>{version}</option>)
}
</select>;
}
})();
return <div className='component-list-item noselect'>
<div className='header'>
<span>{name} by {author}</span>
{installedVersion}
</div>
<div className='content'>
{summary}
{description}
</div>
<div className='info'>
{moreInfo}
</div>
<div className='footer'>
{installButton}
{versions}
</div>
</div>;
}
} }

View File

@ -1,58 +0,0 @@
import * as React from 'react';
import { injectable } from 'inversify';
import { Library } from '../../common/protocol/library-service';
import { Installable } from '../../common/protocol/installable';
import { ListItemRenderer } from '../components/component-list/list-item-renderer';
import { ComponentListItem } from '../components/component-list/component-list-item';
@injectable()
export class LibraryItemRenderer extends ListItemRenderer<Library> {
renderItem(
input: ComponentListItem.State & { item: Library },
install: (item: Library, version: Installable.Version) => Promise<void>): React.ReactNode {
const { item } = input;
const name = <span className='name'>{item.name}</span>;
const author = <span className='author'>by {item.author}</span>;
const installedVersion = !!item.installedVersion && <div className='version-info'>
<span className='version'>Version {item.installedVersion}</span>
<span className='installed'>INSTALLED</span>
</div>;
const summary = <div className='summary'>{item.summary}</div>;
const moreInfo = !!item.moreInfoLink && <a href={item.moreInfoLink} onClick={this.onMoreInfoClick}>More info</a>;
const installButton = item.installable && !item.installedVersion &&
<button className='install' onClick={install.bind(this, item)}>INSTALL</button>;
const versions = (() => {
const { availableVersions } = item;
if (!!item.installedVersion || availableVersions.length === 0) {
return undefined;
} else if (availableVersions.length === 1) {
return <label>{availableVersions[0]}</label>
} else {
return <select>{item.availableVersions.map(version => <option value={version} key={version}>{version}</option>)}</select>;
}
})();
return <div className='component-list-item noselect'>
<div className='header'>
<span>{name} {author}</span>
{installedVersion}
</div>
<div className='content'>
{summary}
</div>
<div className='info'>
{moreInfo}
</div>
<div className='footer'>
{installButton}
{versions}
</div>
</div>;
}
}

View File

@ -1,7 +1,7 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from 'inversify';
import { Library, LibraryService } from '../../common/protocol/library-service'; import { Library, LibraryService } from '../../common/protocol/library-service';
import { ListWidget } from '../components/component-list/list-widget'; import { ListWidget } from '../components/component-list/list-widget';
import { LibraryItemRenderer } from './library-item-renderer'; import { ListItemRenderer } from '../components/component-list/list-item-renderer';
@injectable() @injectable()
export class LibraryListWidget extends ListWidget<Library> { export class LibraryListWidget extends ListWidget<Library> {
@ -11,7 +11,7 @@ export class LibraryListWidget extends ListWidget<Library> {
constructor( constructor(
@inject(LibraryService) protected service: LibraryService, @inject(LibraryService) protected service: LibraryService,
@inject(LibraryItemRenderer) protected itemRenderer: LibraryItemRenderer) { @inject(ListItemRenderer) protected itemRenderer: ListItemRenderer<Library>) {
super({ super({
id: LibraryListWidget.WIDGET_ID, id: LibraryListWidget.WIDGET_ID,

View File

@ -1,8 +1,10 @@
import { injectable, inject } from 'inversify'; import { injectable, inject } from 'inversify';
import { Library, LibraryService } from '../common/protocol/library-service'; import { Library, LibraryService } from '../common/protocol/library-service';
import { CoreClientProvider } from './core-client-provider'; import { CoreClientProvider } from './core-client-provider';
import { LibrarySearchReq, LibrarySearchResp, LibraryListReq, LibraryListResp, LibraryRelease, import {
InstalledLibrary, LibraryInstallReq, LibraryInstallResp } from './cli-protocol/commands/lib_pb'; LibrarySearchReq, LibrarySearchResp, LibraryListReq, LibraryListResp, LibraryRelease,
InstalledLibrary, LibraryInstallReq, LibraryInstallResp
} from './cli-protocol/commands/lib_pb';
import { ToolOutputServiceServer } from '../common/protocol/tool-output-service'; import { ToolOutputServiceServer } from '../common/protocol/tool-output-service';
import { Installable } from '../common/protocol/installable'; import { Installable } from '../common/protocol/installable';
@ -44,6 +46,7 @@ export class LibraryServiceImpl implements LibraryService {
.filter(item => !!item.getLatest()) .filter(item => !!item.getLatest())
.slice(0, 50) .slice(0, 50)
.map(item => { .map(item => {
const availableVersions = item.getReleasesMap().getEntryList().map(([key, _]) => key);
let installedVersion: string | undefined; let installedVersion: string | undefined;
const installed = installedLibsIdx.get(item.getName()); const installed = installedLibsIdx.get(item.getName());
if (installed) { if (installed) {
@ -52,8 +55,8 @@ export class LibraryServiceImpl implements LibraryService {
return toLibrary({ return toLibrary({
name: item.getName(), name: item.getName(),
installable: true, installable: true,
installedVersion installedVersion,
}, item.getLatest()!) }, item.getLatest()!, availableVersions)
}) })
return { items }; return { items };
@ -88,14 +91,14 @@ export class LibraryServiceImpl implements LibraryService {
} }
function toLibrary(tpl: Partial<Library>, release: LibraryRelease): Library { function toLibrary(tpl: Partial<Library>, release: LibraryRelease, availableVersions: string[]): Library {
return { return {
name: "", name: "",
installable: false, installable: false,
...tpl, ...tpl,
author: release.getAuthor(), author: release.getAuthor(),
availableVersions: [release.getVersion()], availableVersions,
description: release.getSentence(), description: release.getSentence(),
moreInfoLink: release.getWebsite(), moreInfoLink: release.getWebsite(),
summary: release.getParagraph() summary: release.getParagraph()