Skip to content

Commit fdda4a7

Browse files
author
Akos Kitta
committed
Initial support for updating/downgrading cores.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
1 parent 7077303 commit fdda4a7

27 files changed

+195
-65
lines changed

arduino-ide-extension/src/browser/boards/boards-auto-installer.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,17 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
4343
// tslint:disable-next-line:max-line-length
4444
this.messageService.info(`The \`"${candidate.name}"\` core has to be installed for the currently selected \`"${selectedBoard.name}"\` board. Do you want to install it now?`, 'Yes', 'Install Manually').then(async answer => {
4545
if (answer === 'Yes') {
46-
const dialog = new InstallationProgressDialog(candidate.name);
46+
const dialog = new InstallationProgressDialog(candidate.name, candidate.availableVersions[0]);
4747
dialog.open();
4848
try {
49-
await this.boardsService.install(candidate);
49+
await this.boardsService.install({ item: candidate });
5050
} finally {
5151
dialog.close();
5252
}
5353
}
5454
if (answer) {
5555
this.boardsManagerFrontendContribution.openView({ reveal: true }).then(widget => widget.refresh(candidate.name.toLocaleLowerCase()));
56-
}
56+
}
5757
});
5858
}
5959
})

arduino-ide-extension/src/browser/boards/boards-item-renderer.tsx

+29-6
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,18 @@ import * as React from 'react';
22
import { injectable } from 'inversify';
33
import { ListItemRenderer } from '../components/component-list/list-item-renderer';
44
import { BoardPackage } from '../../common/protocol/boards-service';
5+
import { Installable } from '../../common/protocol/installable';
6+
import { ComponentListItem } from '../components/component-list/component-list-item';
57

68
@injectable()
79
export class BoardItemRenderer extends ListItemRenderer<BoardPackage> {
810

9-
renderItem(item: BoardPackage, install: (item: BoardPackage) => Promise<void>): React.ReactNode {
11+
renderItem(
12+
input: ComponentListItem.State & { item: BoardPackage },
13+
install: (item: BoardPackage) => Promise<void>,
14+
onVersionChange: (version: Installable.Version) => void): React.ReactNode {
15+
16+
const { item } = input;
1017
const name = <span className='name'>{item.name}</span>;
1118
const author = <span className='author'>{item.author}</span>;
1219
const installedVersion = !!item.installedVersion && <div className='version-info'>
@@ -17,18 +24,34 @@ export class BoardItemRenderer extends ListItemRenderer<BoardPackage> {
1724
const summary = <div className='summary'>{item.summary}</div>;
1825
const description = <div className='summary'>{item.description}</div>;
1926

20-
const moreInfo = !!item.moreInfoLink && <a href={item.moreInfoLink} onClick={this.onClick}>More info</a>;
21-
const installButton = item.installable && !item.installedVersion &&
22-
<button className='install' onClick={install.bind(this, item)}>INSTALL</button>;
27+
const moreInfo = !!item.moreInfoLink && <a href={item.moreInfoLink} onClick={this.onMoreInfoClick}>More info</a>;
28+
const onClickInstall = () => install(item);
29+
const installButton = item.installable &&
30+
<button className='install' onClick={onClickInstall}>INSTALL</button>;
31+
32+
const onSelectChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
33+
const version = event.target.value;
34+
if (version) {
35+
onVersionChange(version);
36+
}
37+
}
2338

2439
const versions = (() => {
2540
const { availableVersions } = item;
26-
if (!!item.installedVersion || availableVersions.length === 0) {
41+
if (availableVersions.length === 0) {
2742
return undefined;
2843
} else if (availableVersions.length === 1) {
2944
return <label>{availableVersions[0]}</label>
3045
} else {
31-
return <select>{item.availableVersions.map(version => <option value={version} key={version}>{version}</option>)}</select>;
46+
return <select
47+
value={input.selectedVersion}
48+
onChange={onSelectChange}>
49+
{
50+
item.availableVersions
51+
.filter(version => version !== item.installedVersion) // Filter the version that is currently installed.
52+
.map(version => <option value={version} key={version}>{version}</option>)
53+
}
54+
</select>;
3255
}
3356
})();
3457

Original file line numberDiff line numberDiff line change
@@ -1,25 +1,45 @@
11
import * as React from 'react';
2+
import { Installable } from '../../../common/protocol/installable';
3+
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
24
import { ListItemRenderer } from './list-item-renderer';
35

4-
export class ComponentListItem<T> extends React.Component<ComponentListItem.Props<T>> {
6+
export class ComponentListItem<T extends ArduinoComponent> extends React.Component<ComponentListItem.Props<T>, ComponentListItem.State> {
7+
8+
constructor(props: ComponentListItem.Props<T>) {
9+
super(props);
10+
if (props.item.installable) {
11+
const version = props.item.availableVersions.filter(version => version !== props.item.installedVersion)[0];
12+
this.state = {
13+
selectedVersion: version
14+
};
15+
}
16+
}
517

618
protected async install(item: T): Promise<void> {
7-
await this.props.install(item);
19+
await this.props.install(item, this.state.selectedVersion);
20+
}
21+
22+
protected onVersionChange(version: Installable.Version) {
23+
this.setState({ selectedVersion: version });
824
}
925

1026
render(): React.ReactNode {
11-
const { item, itemRenderer, install } = this.props;
12-
return itemRenderer.renderItem(item, install.bind(this));
27+
const { item, itemRenderer } = this.props;
28+
return itemRenderer.renderItem(Object.assign(this.state, { item }), this.install.bind(this), this.onVersionChange.bind(this));
1329
}
1430

1531
}
1632

1733
export namespace ComponentListItem {
1834

19-
export interface Props<T> {
35+
export interface Props<T extends ArduinoComponent> {
2036
readonly item: T;
21-
readonly install: (item: T) => Promise<void>;
37+
readonly install: (item: T, version?: Installable.Version) => Promise<void>;
2238
readonly itemRenderer: ListItemRenderer<T>;
2339
}
2440

41+
export interface State {
42+
selectedVersion?: Installable.Version;
43+
}
44+
2545
}

arduino-ide-extension/src/browser/components/component-list/component-list.tsx

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import * as React from 'react';
2+
import { Installable } from '../../../common/protocol/installable';
3+
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
24
import { ComponentListItem } from './component-list-item';
35
import { ListItemRenderer } from './list-item-renderer';
46

5-
export class ComponentList<T> extends React.Component<ComponentList.Props<T>> {
7+
export class ComponentList<T extends ArduinoComponent> extends React.Component<ComponentList.Props<T>> {
68

79
protected container?: HTMLElement;
810

@@ -36,11 +38,11 @@ export class ComponentList<T> extends React.Component<ComponentList.Props<T>> {
3638

3739
export namespace ComponentList {
3840

39-
export interface Props<T> {
41+
export interface Props<T extends ArduinoComponent> {
4042
readonly items: T[];
4143
readonly itemLabel: (item: T) => string;
4244
readonly itemRenderer: ListItemRenderer<T>;
43-
readonly install: (item: T) => Promise<void>;
45+
readonly install: (item: T, version?: Installable.Version) => Promise<void>;
4446
readonly resolveContainer: (element: HTMLElement) => void;
4547
}
4648

arduino-ide-extension/src/browser/components/component-list/filterable-list-container.tsx

+6-5
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ import debounce = require('lodash.debounce');
33
import { Event } from '@theia/core/lib/common/event';
44
import { Searchable } from '../../../common/protocol/searchable';
55
import { Installable } from '../../../common/protocol/installable';
6+
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
67
import { InstallationProgressDialog } from '../installation-progress-dialog';
78
import { SearchBar } from './search-bar';
89
import { ListWidget } from './list-widget';
910
import { ComponentList } from './component-list';
1011
import { ListItemRenderer } from './list-item-renderer';
1112

12-
export class FilterableListContainer<T> extends React.Component<FilterableListContainer.Props<T>, FilterableListContainer.State<T>> {
13+
export class FilterableListContainer<T extends ArduinoComponent> extends React.Component<FilterableListContainer.Props<T>, FilterableListContainer.State<T>> {
1314

1415
constructor(props: Readonly<FilterableListContainer.Props<T>>) {
1516
super(props);
@@ -77,12 +78,12 @@ export class FilterableListContainer<T> extends React.Component<FilterableListCo
7778
return items.sort((left, right) => itemLabel(left).localeCompare(itemLabel(right)));
7879
}
7980

80-
protected async install(item: T): Promise<void> {
81+
protected async install(item: T, version: Installable.Version): Promise<void> {
8182
const { installable, searchable, itemLabel } = this.props;
82-
const dialog = new InstallationProgressDialog(itemLabel(item));
83+
const dialog = new InstallationProgressDialog(itemLabel(item), version);
8384
dialog.open();
8485
try {
85-
await installable.install(item);
86+
await installable.install({ item, version });
8687
const { items } = await searchable.search({ query: this.state.filterText });
8788
this.setState({ items: this.sort(items) });
8889
} finally {
@@ -94,7 +95,7 @@ export class FilterableListContainer<T> extends React.Component<FilterableListCo
9495

9596
export namespace FilterableListContainer {
9697

97-
export interface Props<T> {
98+
export interface Props<T extends ArduinoComponent> {
9899
readonly container: ListWidget<T>;
99100
readonly installable: Installable<T>;
100101
readonly searchable: Searchable<T>;
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
11
import * as React from 'react';
22
import { inject, injectable } from 'inversify';
33
import { WindowService } from '@theia/core/lib/browser/window/window-service';
4+
import { Installable } from '../../../common/protocol/installable';
5+
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
6+
import { ComponentListItem } from './component-list-item';
47

58
@injectable()
6-
export abstract class ListItemRenderer<T> {
9+
export abstract class ListItemRenderer<T extends ArduinoComponent> {
710

811
@inject(WindowService)
912
protected windowService: WindowService;
1013

11-
protected onClick = (event: React.SyntheticEvent<HTMLAnchorElement, Event>) => {
14+
protected onMoreInfoClick = (event: React.SyntheticEvent<HTMLAnchorElement, Event>) => {
1215
const { target } = event.nativeEvent;
1316
if (target instanceof HTMLAnchorElement) {
1417
this.windowService.openNewWindow(target.href, { external: true });
1518
event.nativeEvent.preventDefault();
1619
}
1720
}
1821

19-
abstract renderItem(item: T, install: (item: T) => Promise<void>): React.ReactNode;
22+
abstract renderItem(
23+
input: ComponentListItem.State & { item: T },
24+
install: (item: T) => Promise<void>,
25+
onVersionChange: (version: Installable.Version) => void
26+
): React.ReactNode;
2027

2128
}

arduino-ide-extension/src/browser/components/component-list/list-widget-frontend-contribution.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { injectable } from 'inversify';
22
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
33
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
4+
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
45
import { ListWidget } from './list-widget';
56

67
@injectable()
7-
export abstract class ListWidgetFrontendContribution<T> extends AbstractViewContribution<ListWidget<T>> implements FrontendApplicationContribution {
8+
export abstract class ListWidgetFrontendContribution<T extends ArduinoComponent> extends AbstractViewContribution<ListWidget<T>> implements FrontendApplicationContribution {
89

910
async initializeLayout(): Promise<void> {
1011
}

arduino-ide-extension/src/browser/components/component-list/list-widget.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ import { MaybePromise } from '@theia/core/lib/common/types';
77
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
88
import { Installable } from '../../../common/protocol/installable';
99
import { Searchable } from '../../../common/protocol/searchable';
10+
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
1011
import { FilterableListContainer } from './filterable-list-container';
1112
import { ListItemRenderer } from './list-item-renderer';
1213

1314
@injectable()
14-
export abstract class ListWidget<T> extends ReactWidget {
15+
export abstract class ListWidget<T extends ArduinoComponent> extends ReactWidget {
1516

1617
/**
1718
* Do not touch or use it. It is for setting the focus on the `input` after the widget activation.
@@ -78,7 +79,7 @@ export abstract class ListWidget<T> extends ReactWidget {
7879
}
7980

8081
export namespace ListWidget {
81-
export interface Options<T> {
82+
export interface Options<T extends ArduinoComponent> {
8283
readonly id: string;
8384
readonly label: string;
8485
readonly iconClass: string;

arduino-ide-extension/src/browser/components/installation-progress-dialog.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ export class InstallationProgressDialog extends AbstractDialog<undefined> {
44

55
readonly value = undefined;
66

7-
constructor(componentName: string) {
7+
constructor(componentName: string, version: string) {
88
super({ title: 'Installation in progress' });
9-
this.contentNode.textContent = `Installing ${componentName}. Please wait.`;
9+
this.contentNode.textContent = `Installing ${componentName} [${version}]. Please wait...`;
1010
}
1111

1212
}

arduino-ide-extension/src/browser/library/library-item-renderer.tsx

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
import * as React from 'react';
22
import { injectable } from 'inversify';
33
import { Library } from '../../common/protocol/library-service';
4+
import { Installable } from '../../common/protocol/installable';
45
import { ListItemRenderer } from '../components/component-list/list-item-renderer';
6+
import { ComponentListItem } from '../components/component-list/component-list-item';
57

68
@injectable()
79
export class LibraryItemRenderer extends ListItemRenderer<Library> {
810

9-
renderItem(item: Library, install: (item: Library) => Promise<void>): React.ReactNode {
11+
renderItem(
12+
input: ComponentListItem.State & { item: Library },
13+
install: (item: Library, version: Installable.Version) => Promise<void>): React.ReactNode {
14+
15+
const { item } = input;
1016
const name = <span className='name'>{item.name}</span>;
1117
const author = <span className='author'>by {item.author}</span>;
1218
const installedVersion = !!item.installedVersion && <div className='version-info'>
@@ -16,7 +22,7 @@ export class LibraryItemRenderer extends ListItemRenderer<Library> {
1622

1723
const summary = <div className='summary'>{item.summary}</div>;
1824

19-
const moreInfo = !!item.moreInfoLink && <a href={item.moreInfoLink} onClick={this.onClick}>More info</a>;
25+
const moreInfo = !!item.moreInfoLink && <a href={item.moreInfoLink} onClick={this.onMoreInfoClick}>More info</a>;
2026
const installButton = item.installable && !item.installedVersion &&
2127
<button className='install' onClick={install.bind(this, item)}>INSTALL</button>;
2228

Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Installable } from './installable';
12

23
export interface ArduinoComponent {
34
readonly name: string;
@@ -6,8 +7,8 @@ export interface ArduinoComponent {
67
readonly description: string;
78
readonly moreInfoLink?: string;
89

9-
readonly availableVersions: string[];
10+
readonly availableVersions: Installable.Version[];
1011
readonly installable: boolean;
1112

12-
readonly installedVersion?: string;
13+
readonly installedVersion?: Installable.Version;
1314
}

arduino-ide-extension/src/common/protocol/boards-service.ts

+6
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,12 @@ export interface BoardPackage extends ArduinoComponent {
170170
id: string;
171171
boards: Board[];
172172
}
173+
export namespace BoardPackage {
174+
/**
175+
* Most recent version comes first, then the previous versions. (`1.8.1`, `1.6.3`, `1.6.2`, `1.6.1` and so on.)
176+
*/
177+
export const VERSION_COMPARATOR = (left: string, right: string) => naturalCompare(right, left);
178+
}
173179

174180
export interface Board {
175181
name: string
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1-
export interface Installable<T> {
2-
install(item: T): Promise<void>;
3-
}
1+
import { ArduinoComponent } from './arduino-component';
2+
3+
export interface Installable<T extends ArduinoComponent> {
4+
/**
5+
* If `options.version` is specified, that will be installed. Otherwise, `item.availableVersions[0]`.
6+
*/
7+
install(options: { item: T, version?: Installable.Version }): Promise<void>;
8+
}
9+
export namespace Installable {
10+
export type Version = string;
11+
}

arduino-ide-extension/src/common/protocol/library-service.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,9 @@ import { ArduinoComponent } from './arduino-component';
55
export const LibraryServicePath = '/services/library-service';
66
export const LibraryService = Symbol('LibraryService');
77
export interface LibraryService extends Installable<Library>, Searchable<Library> {
8-
install(library: Library): Promise<void>;
8+
install(options: { item: Library, version?: Installable.Version }): Promise<void>;
99
}
1010

1111
export interface Library extends ArduinoComponent {
1212
readonly builtIn?: boolean;
1313
}
14-
15-
export namespace Library {
16-
// TODO: figure out whether we need a dedicated `version` type.
17-
export type Version = string;
18-
}

0 commit comments

Comments
 (0)