Skip to content

Commit 057904d

Browse files
Akos Kittakittaakos
Akos Kitta
authored andcommitted
ATL-983: Propose installing the required libs.
Signed-off-by: Akos Kitta <[email protected]>
1 parent 0aef4b3 commit 057904d

File tree

11 files changed

+253
-103
lines changed

11 files changed

+253
-103
lines changed

arduino-ide-extension/package.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,11 @@
124124
],
125125
"arduino": {
126126
"cli": {
127-
"version": "0.16.0"
127+
"version": {
128+
"owner": "arduino",
129+
"repo": "arduino-cli",
130+
"commitish": "scerza/lib-install-deps"
131+
}
128132
}
129133
}
130134
}

arduino-ide-extension/src/browser/library/library-list-widget.ts

+110
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { injectable, postConstruct, inject } from 'inversify';
2+
import { Message } from '@phosphor/messaging';
3+
import { addEventListener } from '@theia/core/lib/browser/widgets/widget';
4+
import { AbstractDialog, DialogProps } from '@theia/core/lib/browser/dialogs';
25
import { LibraryPackage, LibraryService } from '../../common/protocol/library-service';
36
import { ListWidget } from '../widgets/component-list/list-widget';
7+
import { Installable } from '../../common/protocol';
48
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
59

610
@injectable()
@@ -33,4 +37,110 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
3337
]);
3438
}
3539

40+
protected async install({ item, version }: { item: LibraryPackage, version: Installable.Version }): Promise<void> {
41+
const dependencies = await this.service.listDependencies({ item, version, filterSelf: true });
42+
let installDependencies: boolean | undefined = undefined;
43+
if (dependencies.length) {
44+
const message = document.createElement('div');
45+
message.innerHTML = `The library <b>${item.name}:${version}</b> needs ${dependencies.length === 1 ? 'another dependency' : 'some other dependencies'} currently not installed:`;
46+
const listContainer = document.createElement('div');
47+
listContainer.style.maxHeight = '300px';
48+
listContainer.style.overflowY = 'auto';
49+
const list = document.createElement('ul');
50+
list.style.listStyleType = 'none';
51+
for (const { name } of dependencies) {
52+
const listItem = document.createElement('li');
53+
listItem.textContent = ` - ${name}`;
54+
listItem.style.fontWeight = 'bold';
55+
list.appendChild(listItem);
56+
}
57+
listContainer.appendChild(list);
58+
message.appendChild(listContainer);
59+
const question = document.createElement('div');
60+
question.textContent = `Would you like to install ${dependencies.length === 1 ? 'the missing dependency' : 'all the missing dependencies'}?`;
61+
message.appendChild(question);
62+
const result = await new MessageBoxDialog({
63+
title: `Dependencies for library ${item.name}:${version}`,
64+
message,
65+
buttons: [
66+
'Install all',
67+
`Install ${item.name} only`,
68+
'Cancel'
69+
],
70+
maxWidth: 740 // Aligned with `settings-dialog.css`.
71+
}).open();
72+
73+
if (result) {
74+
const { response } = result;
75+
if (response === 0) { // All
76+
installDependencies = true;
77+
} else if (response === 1) { // Current only
78+
installDependencies = false;
79+
}
80+
}
81+
}
82+
83+
if (typeof installDependencies === 'boolean') {
84+
return this.service.install({ item, version, installDependencies });
85+
}
86+
}
87+
88+
}
89+
90+
class MessageBoxDialog extends AbstractDialog<MessageBoxDialog.Result> {
91+
92+
protected response: number;
93+
94+
constructor(protected readonly options: MessageBoxDialog.Options) {
95+
super(options);
96+
this.contentNode.appendChild(this.createMessageNode(this.options.message));
97+
(options.buttons || ['OK']).forEach((text, index) => {
98+
const button = this.createButton(text);
99+
button.classList.add(index === 0 ? 'main' : 'secondary');
100+
this.controlPanel.appendChild(button);
101+
this.toDisposeOnDetach.push(addEventListener(button, 'click', () => {
102+
this.response = index;
103+
this.accept();
104+
}));
105+
});
106+
}
107+
108+
protected onCloseRequest(message: Message): void {
109+
super.onCloseRequest(message);
110+
this.accept();
111+
}
112+
113+
get value(): MessageBoxDialog.Result {
114+
return { response: this.response };
115+
}
116+
117+
protected createMessageNode(message: string | HTMLElement): HTMLElement {
118+
if (typeof message === 'string') {
119+
const messageNode = document.createElement('div');
120+
messageNode.textContent = message;
121+
return messageNode;
122+
}
123+
return message;
124+
}
125+
126+
protected handleEnter(event: KeyboardEvent): boolean | void {
127+
this.response = 0;
128+
super.handleEnter(event);
129+
}
130+
131+
}
132+
export namespace MessageBoxDialog {
133+
export interface Options extends DialogProps {
134+
/**
135+
* When empty, `['OK']` will be inferred.
136+
*/
137+
buttons?: string[];
138+
message: string | HTMLElement;
139+
}
140+
export interface Result {
141+
/**
142+
* The index of `buttons` that was clicked.
143+
*/
144+
readonly response: number;
145+
}
36146
}

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

+10-9
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,11 @@ export class FilterableListContainer<T extends ArduinoComponent> extends React.C
8181
}
8282

8383
protected async install(item: T, version: Installable.Version): Promise<void> {
84-
const { installable, searchable, itemLabel } = this.props;
84+
const { install, searchable, itemLabel } = this.props;
8585
const dialog = new InstallationProgressDialog(itemLabel(item), version);
86-
dialog.open();
8786
try {
88-
await installable.install({ item, version });
87+
dialog.open();
88+
await install({ item, version });
8989
const items = await searchable.search({ query: this.state.filterText });
9090
this.setState({ items: this.sort(items) });
9191
} finally {
@@ -94,20 +94,20 @@ export class FilterableListContainer<T extends ArduinoComponent> extends React.C
9494
}
9595

9696
protected async uninstall(item: T): Promise<void> {
97-
const uninstall = await new ConfirmDialog({
97+
const ok = await new ConfirmDialog({
9898
title: 'Uninstall',
9999
msg: `Do you want to uninstall ${item.name}?`,
100100
ok: 'Yes',
101101
cancel: 'No'
102102
}).open();
103-
if (!uninstall) {
103+
if (!ok) {
104104
return;
105105
}
106-
const { installable, searchable, itemLabel } = this.props;
106+
const { uninstall, searchable, itemLabel } = this.props;
107107
const dialog = new UninstallationProgressDialog(itemLabel(item));
108-
dialog.open();
109108
try {
110-
await installable.uninstall({ item });
109+
dialog.open();
110+
await uninstall({ item });
111111
const items = await searchable.search({ query: this.state.filterText });
112112
this.setState({ items: this.sort(items) });
113113
} finally {
@@ -121,13 +121,14 @@ export namespace FilterableListContainer {
121121

122122
export interface Props<T extends ArduinoComponent> {
123123
readonly container: ListWidget<T>;
124-
readonly installable: Installable<T>;
125124
readonly searchable: Searchable<T>;
126125
readonly itemLabel: (item: T) => string;
127126
readonly itemRenderer: ListItemRenderer<T>;
128127
readonly resolveContainer: (element: HTMLElement) => void;
129128
readonly resolveFocus: (element: HTMLElement | undefined) => void;
130129
readonly filterTextChangeEvent: Event<string | undefined>;
130+
readonly install: ({ item, version }: { item: T, version: Installable.Version }) => Promise<void>;
131+
readonly uninstall: ({ item }: { item: T }) => Promise<void>;
131132
}
132133

133134
export interface State<T> {

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

+10-1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ export abstract class ListWidget<T extends ArduinoComponent> extends ReactWidget
6767

6868
protected onFocusResolved = (element: HTMLElement | undefined) => {
6969
this.focusNode = element;
70+
};
71+
72+
protected async install({ item, version }: { item: T, version: Installable.Version }): Promise<void> {
73+
return this.options.installable.install({ item, version });
74+
}
75+
76+
protected async uninstall({ item }: { item: T }): Promise<void> {
77+
return this.options.installable.uninstall({ item });
7078
}
7179

7280
render(): React.ReactNode {
@@ -75,7 +83,8 @@ export abstract class ListWidget<T extends ArduinoComponent> extends ReactWidget
7583
resolveContainer={this.deferredContainer.resolve}
7684
resolveFocus={this.onFocusResolved}
7785
searchable={this.options.searchable}
78-
installable={this.options.installable}
86+
install={this.install.bind(this)}
87+
uninstall={this.uninstall.bind(this)}
7988
itemLabel={this.options.itemLabel}
8089
itemRenderer={this.options.itemRenderer}
8190
filterTextChangeEvent={this.filterTextChangeEmitter.event} />;

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

+15
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ export const LibraryServicePath = '/services/library-service';
66
export const LibraryService = Symbol('LibraryService');
77
export interface LibraryService extends Installable<LibraryPackage>, Searchable<LibraryPackage> {
88
list(options: LibraryService.List.Options): Promise<LibraryPackage[]>;
9+
/**
10+
* When `installDependencies` is not set, it is `true` by default. If you want to skip the installation of required dependencies, set it to `false`.
11+
*/
12+
install(options: { item: LibraryPackage, version?: Installable.Version, installDependencies?: boolean }): Promise<void>;
13+
/**
14+
* Set `filterSelf` to `true` if you want to avoid having `item` in the result set.
15+
* Note: as of today (22.02.2021), the CLI works like this: `./arduino-cli lib deps [email protected] ✕ Adaino 0.1.0 must be installed.`.
16+
*/
17+
listDependencies({ item, version, filterSelf }: { item: LibraryPackage, version: Installable.Version, filterSelf?: boolean }): Promise<LibraryDependency[]>;
918
}
1019

1120
export namespace LibraryService {
@@ -83,3 +92,9 @@ export namespace LibraryPackage {
8392
}
8493

8594
}
95+
96+
export interface LibraryDependency {
97+
readonly name: string;
98+
readonly requiredVersion: Installable.Version;
99+
readonly installedVersion: Installable.Version;
100+
}

arduino-ide-extension/src/node/boards-service-impl.ts

+2-24
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ import {
1111
PlatformListResp, Platform, PlatformUninstallResp, PlatformUninstallReq
1212
} from './cli-protocol/commands/core_pb';
1313
import { BoardDiscovery } from './board-discovery';
14-
import { CoreClientProvider } from './core-client-provider';
14+
import { CoreClientAware } from './core-client-provider';
1515
import { BoardDetailsReq, BoardDetailsResp } from './cli-protocol/commands/board_pb';
1616
import { ListProgrammersAvailableForUploadReq, ListProgrammersAvailableForUploadResp } from './cli-protocol/commands/upload_pb';
1717

1818
@injectable()
19-
export class BoardsServiceImpl implements BoardsService {
19+
export class BoardsServiceImpl extends CoreClientAware implements BoardsService {
2020

2121
@inject(ILogger)
2222
protected logger: ILogger;
@@ -25,9 +25,6 @@ export class BoardsServiceImpl implements BoardsService {
2525
@named('discovery')
2626
protected discoveryLogger: ILogger;
2727

28-
@inject(CoreClientProvider)
29-
protected readonly coreClientProvider: CoreClientProvider;
30-
3128
@inject(OutputService)
3229
protected readonly outputService: OutputService;
3330

@@ -49,25 +46,6 @@ export class BoardsServiceImpl implements BoardsService {
4946
return this.boardDiscovery.getAvailablePorts();
5047
}
5148

52-
private async coreClient(): Promise<CoreClientProvider.Client> {
53-
const coreClient = await new Promise<CoreClientProvider.Client>(async resolve => {
54-
const client = await this.coreClientProvider.client();
55-
if (client) {
56-
resolve(client);
57-
return;
58-
}
59-
const toDispose = this.coreClientProvider.onClientReady(async () => {
60-
const client = await this.coreClientProvider.client();
61-
if (client) {
62-
toDispose.dispose();
63-
resolve(client);
64-
return;
65-
}
66-
});
67-
});
68-
return coreClient;
69-
}
70-
7149
async getBoardDetails(options: { fqbn: string }): Promise<BoardDetails | undefined> {
7250
const coreClient = await this.coreClient();
7351
const { client, instance } = coreClient;

arduino-ide-extension/src/node/cli-protocol/commands/lib_pb.d.ts

+4
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ export class LibraryInstallReq extends jspb.Message {
7676
getVersion(): string;
7777
setVersion(value: string): LibraryInstallReq;
7878

79+
getNodeps(): boolean;
80+
setNodeps(value: boolean): LibraryInstallReq;
81+
7982

8083
serializeBinary(): Uint8Array;
8184
toObject(includeInstance?: boolean): LibraryInstallReq.AsObject;
@@ -92,6 +95,7 @@ export namespace LibraryInstallReq {
9295
instance?: commands_common_pb.Instance.AsObject,
9396
name: string,
9497
version: string,
98+
nodeps: boolean,
9599
}
96100
}
97101

arduino-ide-extension/src/node/cli-protocol/commands/lib_pb.js

+31-1
Original file line numberDiff line numberDiff line change
@@ -965,7 +965,8 @@ proto.cc.arduino.cli.commands.LibraryInstallReq.toObject = function(includeInsta
965965
var f, obj = {
966966
instance: (f = msg.getInstance()) && commands_common_pb.Instance.toObject(includeInstance, f),
967967
name: jspb.Message.getFieldWithDefault(msg, 2, ""),
968-
version: jspb.Message.getFieldWithDefault(msg, 3, "")
968+
version: jspb.Message.getFieldWithDefault(msg, 3, ""),
969+
nodeps: jspb.Message.getBooleanFieldWithDefault(msg, 4, false)
969970
};
970971

971972
if (includeInstance) {
@@ -1015,6 +1016,10 @@ proto.cc.arduino.cli.commands.LibraryInstallReq.deserializeBinaryFromReader = fu
10151016
var value = /** @type {string} */ (reader.readString());
10161017
msg.setVersion(value);
10171018
break;
1019+
case 4:
1020+
var value = /** @type {boolean} */ (reader.readBool());
1021+
msg.setNodeps(value);
1022+
break;
10181023
default:
10191024
reader.skipField();
10201025
break;
@@ -1066,6 +1071,13 @@ proto.cc.arduino.cli.commands.LibraryInstallReq.serializeBinaryToWriter = functi
10661071
f
10671072
);
10681073
}
1074+
f = message.getNodeps();
1075+
if (f) {
1076+
writer.writeBool(
1077+
4,
1078+
f
1079+
);
1080+
}
10691081
};
10701082

10711083

@@ -1142,6 +1154,24 @@ proto.cc.arduino.cli.commands.LibraryInstallReq.prototype.setVersion = function(
11421154
};
11431155

11441156

1157+
/**
1158+
* optional bool noDeps = 4;
1159+
* @return {boolean}
1160+
*/
1161+
proto.cc.arduino.cli.commands.LibraryInstallReq.prototype.getNodeps = function() {
1162+
return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 4, false));
1163+
};
1164+
1165+
1166+
/**
1167+
* @param {boolean} value
1168+
* @return {!proto.cc.arduino.cli.commands.LibraryInstallReq} returns this
1169+
*/
1170+
proto.cc.arduino.cli.commands.LibraryInstallReq.prototype.setNodeps = function(value) {
1171+
return jspb.Message.setProto3BooleanField(this, 4, value);
1172+
};
1173+
1174+
11451175

11461176

11471177

0 commit comments

Comments
 (0)