mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-25 20:26:38 +00:00
Made boards installable
This commit is contained in:
parent
201351fea8
commit
c48d80b137
@ -12,6 +12,10 @@ export class ComponentListItem extends React.Component<ComponentListItem.Props>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async install(item: ArduinoComponent) {
|
||||||
|
await this.props.install(item);
|
||||||
|
}
|
||||||
|
|
||||||
render(): React.ReactNode {
|
render(): React.ReactNode {
|
||||||
const { item } = this.props;
|
const { item } = this.props;
|
||||||
|
|
||||||
@ -27,7 +31,8 @@ export class ComponentListItem extends React.Component<ComponentListItem.Props>
|
|||||||
const description = !!item.description && <div className={style.DESCRIPTION_CLASS}>{item.description}</div>;
|
const description = !!item.description && <div className={style.DESCRIPTION_CLASS}>{item.description}</div>;
|
||||||
|
|
||||||
const moreInfo = !!item.moreInfoLink && <a href={item.moreInfoLink} onClick={this.onClick}>More info</a>;
|
const moreInfo = !!item.moreInfoLink && <a href={item.moreInfoLink} onClick={this.onClick}>More info</a>;
|
||||||
const install = item.installable && !item.installedVersion && <button className={style.INSTALL_BTN_CLASS}>INSTALL</button>;
|
const install = this.props.install && item.installable && !item.installedVersion &&
|
||||||
|
<button className={style.INSTALL_BTN_CLASS} onClick={this.install.bind(this, item)}>INSTALL</button>;
|
||||||
|
|
||||||
return <div className={[style.LIST_ITEM_CLASS, style.NO_SELECT_CLASS].join(' ')}>
|
return <div className={[style.LIST_ITEM_CLASS, style.NO_SELECT_CLASS].join(' ')}>
|
||||||
<div className={style.HEADER_CLASS}>
|
<div className={style.HEADER_CLASS}>
|
||||||
@ -52,6 +57,7 @@ export namespace ComponentListItem {
|
|||||||
export interface Props {
|
export interface Props {
|
||||||
readonly item: ArduinoComponent;
|
readonly item: ArduinoComponent;
|
||||||
readonly windowService: WindowService;
|
readonly windowService: WindowService;
|
||||||
|
readonly install: (comp: ArduinoComponent) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Styles {
|
export namespace Styles {
|
||||||
|
@ -7,7 +7,7 @@ export class ComponentList extends React.Component<ComponentList.Props> {
|
|||||||
|
|
||||||
render(): React.ReactNode {
|
render(): React.ReactNode {
|
||||||
return <div>
|
return <div>
|
||||||
{this.props.items.map(item => <ComponentListItem key={item.name} item={item} windowService={this.props.windowService}/>)}
|
{this.props.items.map(item => <ComponentListItem key={item.name} item={item} windowService={this.props.windowService} install={this.props.install} />)}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,6 +18,7 @@ export namespace ComponentList {
|
|||||||
export interface Props {
|
export interface Props {
|
||||||
readonly items: ArduinoComponent[];
|
readonly items: ArduinoComponent[];
|
||||||
readonly windowService: WindowService;
|
readonly windowService: WindowService;
|
||||||
|
readonly install: (comp: ArduinoComponent) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import { WindowService } from '@theia/core/lib/browser/window/window-service';
|
|||||||
import { ComponentList } from './component-list';
|
import { ComponentList } from './component-list';
|
||||||
import { SearchBar } from './search-bar';
|
import { SearchBar } from './search-bar';
|
||||||
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
|
||||||
|
import { InstallationProgressDialog } from '../installation-progress-dialog';
|
||||||
|
|
||||||
export class FilterableListContainer extends React.Component<FilterableListContainer.Props, FilterableListContainer.State> {
|
export class FilterableListContainer extends React.Component<FilterableListContainer.Props, FilterableListContainer.State> {
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ export class FilterableListContainer extends React.Component<FilterableListConta
|
|||||||
/>
|
/>
|
||||||
<ComponentList
|
<ComponentList
|
||||||
items={this.state.items}
|
items={this.state.items}
|
||||||
|
install={this.install.bind(this)}
|
||||||
windowService={this.props.windowService}
|
windowService={this.props.windowService}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -42,6 +44,18 @@ export class FilterableListContainer extends React.Component<FilterableListConta
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async install(comp: ArduinoComponent): Promise<void> {
|
||||||
|
const dialog = new InstallationProgressDialog(comp.name);
|
||||||
|
dialog.open();
|
||||||
|
try {
|
||||||
|
await this.props.service.install(comp);
|
||||||
|
const { items } = await this.props.service.search({ query: this.state.filterText });
|
||||||
|
this.setState({ items });
|
||||||
|
} finally {
|
||||||
|
dialog.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace FilterableListContainer {
|
export namespace FilterableListContainer {
|
||||||
@ -62,6 +76,7 @@ export namespace FilterableListContainer {
|
|||||||
|
|
||||||
export interface ComponentSource {
|
export interface ComponentSource {
|
||||||
search(req: { query: string }): Promise<{ items: ArduinoComponent[] }>
|
search(req: { query: string }): Promise<{ items: ArduinoComponent[] }>
|
||||||
|
install(board: ArduinoComponent): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
import { AbstractDialog } from "@theia/core/lib/browser";
|
||||||
|
|
||||||
|
|
||||||
|
export class InstallationProgressDialog extends AbstractDialog<string> {
|
||||||
|
readonly value: "does-not-matter";
|
||||||
|
|
||||||
|
constructor(componentName: string) {
|
||||||
|
super({ title: 'Installation in progress' });
|
||||||
|
this.contentNode.textContent = `Installing ${componentName}. Please wait.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,7 +5,9 @@ export const BoardsService = Symbol('BoardsService');
|
|||||||
export interface BoardsService {
|
export interface BoardsService {
|
||||||
connectedBoards(): Promise<{ boards: Board[], current?: Board }>;
|
connectedBoards(): Promise<{ boards: Board[], current?: Board }>;
|
||||||
search(options: { query?: string }): Promise<{ items: Board[] }>;
|
search(options: { query?: string }): Promise<{ items: Board[] }>;
|
||||||
|
install(board: Board): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Board extends ArduinoComponent {
|
export interface Board extends ArduinoComponent {
|
||||||
|
id: string;
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ export const LibraryServicePath = '/services/library-service';
|
|||||||
export const LibraryService = Symbol('LibraryService');
|
export const LibraryService = Symbol('LibraryService');
|
||||||
export interface LibraryService {
|
export interface LibraryService {
|
||||||
search(options: { query?: string }): Promise<{ items: Library[] }>;
|
search(options: { query?: string }): Promise<{ items: Library[] }>;
|
||||||
|
install(board: Library): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Library extends ArduinoComponent {
|
export interface Library extends ArduinoComponent {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { injectable, inject } from 'inversify';
|
import { injectable, inject } from 'inversify';
|
||||||
import { BoardsService, Board } from '../common/protocol/boards-service';
|
import { BoardsService, Board } from '../common/protocol/boards-service';
|
||||||
import { PlatformSearchReq, PlatformSearchResp } from './cli-protocol/core_pb';
|
import { PlatformSearchReq, PlatformSearchResp, PlatformInstallReq, PlatformInstallResp, PlatformListReq, PlatformListResp } from './cli-protocol/core_pb';
|
||||||
import { CoreClientProvider } from './core-client-provider';
|
import { CoreClientProvider } from './core-client-provider';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
@ -14,24 +14,76 @@ export class BoardsServiceImpl implements BoardsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async search(options: { query?: string }): Promise<{ items: Board[] }> {
|
async search(options: { query?: string }): Promise<{ items: Board[] }> {
|
||||||
let items: Board[] = [];
|
|
||||||
|
|
||||||
const { client, instance } = await this.coreClientProvider.getClient();
|
const { client, instance } = await this.coreClientProvider.getClient();
|
||||||
|
|
||||||
|
const installedPlatformsReq = new PlatformListReq();
|
||||||
|
installedPlatformsReq.setInstance(instance);
|
||||||
|
const installedPlatformsResp = await new Promise<PlatformListResp>((resolve, reject) =>
|
||||||
|
client.platformList(installedPlatformsReq, (err, resp) => (!!err ? reject : resolve)(!!err ? err : resp))
|
||||||
|
);
|
||||||
|
const installedPlatforms = installedPlatformsResp.getInstalledPlatformList();
|
||||||
|
console.info("Installed platforms", installedPlatforms);
|
||||||
|
|
||||||
const req = new PlatformSearchReq();
|
const req = new PlatformSearchReq();
|
||||||
req.setSearchArgs(options.query || "");
|
req.setSearchArgs(options.query || "");
|
||||||
req.setInstance(instance);
|
req.setInstance(instance);
|
||||||
const resp = await new Promise<PlatformSearchResp>((resolve, reject) => client.platformSearch(req, (err, resp) => (!!err ? reject : resolve)(!!err ? err : resp)));
|
const resp = await new Promise<PlatformSearchResp>((resolve, reject) => client.platformSearch(req, (err, resp) => (!!err ? reject : resolve)(!!err ? err : resp)));
|
||||||
items = resp.getSearchOutputList().map(o => <Board>{
|
|
||||||
name: o.getName(),
|
let items = resp.getSearchOutputList().map(o => {
|
||||||
author: "Someone",
|
let installedVersion: string | undefined;
|
||||||
availableVersions: [],
|
const matchingPlatform = installedPlatforms.find(ip => ip.getId().startsWith(`${o.getId()}@`));
|
||||||
description: "lorem ipsum sit dolor amet",
|
if (!!matchingPlatform) {
|
||||||
installable: false,
|
installedVersion = matchingPlatform.getInstalled();
|
||||||
summary: "has none"
|
}
|
||||||
|
|
||||||
|
const result: Board = {
|
||||||
|
id: o.getId(),
|
||||||
|
name: o.getName(),
|
||||||
|
author: "Someone",
|
||||||
|
availableVersions: [ o.getVersion() ],
|
||||||
|
description: "lorem ipsum sit dolor amet",
|
||||||
|
installable: true,
|
||||||
|
summary: "has none",
|
||||||
|
installedVersion,
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}).sort((a, b) => {
|
||||||
|
if (a.name < b.name) {
|
||||||
|
return -1;
|
||||||
|
} else if (a.name === b.name) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { items };
|
return { items };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async install(board: Board): Promise<void> {
|
||||||
|
const { client, instance } = await this.coreClientProvider.getClient();
|
||||||
|
|
||||||
|
const [ platform, boardName ] = board.id.split(":");
|
||||||
|
|
||||||
|
const req = new PlatformInstallReq();
|
||||||
|
req.setArchitecture(boardName);
|
||||||
|
req.setPlatformPackage(platform);
|
||||||
|
req.setVersion(board.availableVersions[0]);
|
||||||
|
req.setInstance(instance);
|
||||||
|
|
||||||
|
console.info("Starting board installation", board);
|
||||||
|
const resp = client.platformInstall(req);
|
||||||
|
resp.on('data', (r: PlatformInstallResp) => {
|
||||||
|
const prog = r.getProgress();
|
||||||
|
if (prog) {
|
||||||
|
console.info(`downloading ${prog.getFile()}: ${prog.getCompleted()}%`)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
resp.on('end', resolve);
|
||||||
|
resp.on('error', reject);
|
||||||
|
});
|
||||||
|
console.info("Board installation done", board);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ import { WorkspaceServiceExt } from '../browser/workspace-service-ext';
|
|||||||
import { FileSystem } from '@theia/filesystem/lib/common';
|
import { FileSystem } from '@theia/filesystem/lib/common';
|
||||||
import URI from '@theia/core/lib/common/uri';
|
import URI from '@theia/core/lib/common/uri';
|
||||||
import { CoreClientProvider, Client } from './core-client-provider';
|
import { CoreClientProvider, Client } from './core-client-provider';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class CoreClientProviderImpl implements CoreClientProvider {
|
export class CoreClientProviderImpl implements CoreClientProvider {
|
||||||
@ -44,14 +46,15 @@ export class CoreClientProviderImpl implements CoreClientProvider {
|
|||||||
|
|
||||||
console.info(` >>> Creating and caching a new client for ${rootUri}...`);
|
console.info(` >>> Creating and caching a new client for ${rootUri}...`);
|
||||||
const client = new ArduinoCoreClient('localhost:50051', grpc.credentials.createInsecure());
|
const client = new ArduinoCoreClient('localhost:50051', grpc.credentials.createInsecure());
|
||||||
|
|
||||||
const config = new Configuration();
|
const config = new Configuration();
|
||||||
const path = await this.fileSystem.getFsPath(rootUri);
|
const rootPath = await this.fileSystem.getFsPath(rootUri);
|
||||||
if (!path) {
|
if (!rootPath) {
|
||||||
throw new Error(`Could not resolve file-system path of URI: ${rootUri}.`);
|
throw new Error(`Could not resolve file-system path of URI: ${rootUri}.`);
|
||||||
}
|
}
|
||||||
config.setSketchbookdir(path);
|
config.setSketchbookdir(rootPath);
|
||||||
config.setDatadir(path);
|
config.setDatadir(rootPath);
|
||||||
config.setDownloadsdir(path);
|
config.setDownloadsdir(rootPath);
|
||||||
|
|
||||||
const initReq = new InitReq();
|
const initReq = new InitReq();
|
||||||
initReq.setConfiguration(config);
|
initReq.setConfiguration(config);
|
||||||
@ -60,19 +63,27 @@ export class CoreClientProviderImpl implements CoreClientProvider {
|
|||||||
if (!instance) {
|
if (!instance) {
|
||||||
throw new Error(`Could not retrieve instance from the initialize response.`);
|
throw new Error(`Could not retrieve instance from the initialize response.`);
|
||||||
}
|
}
|
||||||
const updateReq = new UpdateIndexReq();
|
|
||||||
updateReq.setInstance(instance);
|
// workaround to speed up startup on existing workspaces
|
||||||
const updateResp = client.updateIndex(updateReq);
|
if (!fs.existsSync(path.join(config.getDatadir(), "package_index.json"))) {
|
||||||
updateResp.on('data', (o: UpdateIndexResp) => {
|
const updateReq = new UpdateIndexReq();
|
||||||
const progress = o.getDownloadProgress();
|
updateReq.setInstance(instance);
|
||||||
if (progress) {
|
const updateResp = client.updateIndex(updateReq);
|
||||||
if (progress.getCompleted()) {
|
updateResp.on('data', (o: UpdateIndexResp) => {
|
||||||
console.log(`Download${progress.getFile() ? ` of ${progress.getFile()}` : ''} completed.`);
|
const progress = o.getDownloadProgress();
|
||||||
} else {
|
if (progress) {
|
||||||
console.log(`Downloading${progress.getFile() ? ` ${progress.getFile()}:` : ''} ${progress.getDownloaded()}.`);
|
if (progress.getCompleted()) {
|
||||||
|
console.log(`Download${progress.getFile() ? ` of ${progress.getFile()}` : ''} completed.`);
|
||||||
|
} else {
|
||||||
|
console.log(`Downloading${progress.getFile() ? ` ${progress.getFile()}:` : ''} ${progress.getDownloaded()}.`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
updateResp.on('error', reject);
|
||||||
|
updateResp.on('end', resolve);
|
||||||
|
});
|
||||||
|
}
|
||||||
// TODO: revisit this!!!
|
// TODO: revisit this!!!
|
||||||
// `updateResp.on('data'` is called only when running, for instance, `compile`. It does not run eagerly.
|
// `updateResp.on('data'` is called only when running, for instance, `compile`. It does not run eagerly.
|
||||||
// await new Promise<void>((resolve, reject) => {
|
// await new Promise<void>((resolve, reject) => {
|
||||||
|
@ -24,8 +24,9 @@ export class CoreServiceImpl implements CoreService {
|
|||||||
const { uri } = options;
|
const { uri } = options;
|
||||||
const sketchpath = await this.fileSystem.getFsPath(options.uri);
|
const sketchpath = await this.fileSystem.getFsPath(options.uri);
|
||||||
if (!sketchpath) {
|
if (!sketchpath) {
|
||||||
throw new Error(`Cannot resolve FS path for URI: ${uri}.`);
|
throw new Error(`Cannot resolve filesystem path for URI: ${uri}.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { client, instance } = await this.coreClientProvider.getClient(uri);
|
const { client, instance } = await this.coreClientProvider.getClient(uri);
|
||||||
// const boards = await this.boardsService.connectedBoards();
|
// const boards = await this.boardsService.connectedBoards();
|
||||||
// if (!boards.current) {
|
// if (!boards.current) {
|
||||||
@ -33,15 +34,6 @@ export class CoreServiceImpl implements CoreService {
|
|||||||
// }
|
// }
|
||||||
// https://github.com/cmaglie/arduino-cli/blob/bd5e78701e7546787649d3cca6b21c5d22d0e438/cli/compile/compile.go#L78-L88
|
// https://github.com/cmaglie/arduino-cli/blob/bd5e78701e7546787649d3cca6b21c5d22d0e438/cli/compile/compile.go#L78-L88
|
||||||
|
|
||||||
const installPlatformReq = new PlatformInstallReq();
|
|
||||||
installPlatformReq.setArchitecture('samd');
|
|
||||||
installPlatformReq.setVersion('1.6.0');
|
|
||||||
installPlatformReq.setInstance(instance);
|
|
||||||
const resp = client.platformInstall(installPlatformReq);
|
|
||||||
console.log(resp);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const installLibReq = new LibraryInstallReq();
|
const installLibReq = new LibraryInstallReq();
|
||||||
installLibReq.setInstance(instance);
|
installLibReq.setInstance(instance);
|
||||||
installLibReq.setName('arduino:samd');
|
installLibReq.setName('arduino:samd');
|
||||||
|
@ -43,4 +43,8 @@ export class LibraryServiceImpl implements LibraryService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async install(board: Library): Promise<void> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user