Skip to content

Commit 671d2ea

Browse files
author
Alberto Iannaccone
authored
Show user fields dialog again if upload fails (#1415)
* Show user fields dialog again if upload fails * move user fields logic into own contribution * apply suggestions
1 parent 9a65ef6 commit 671d2ea

File tree

3 files changed

+180
-127
lines changed

3 files changed

+180
-127
lines changed

Diff for: arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts

+2
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ import { CheckForUpdates } from './contributions/check-for-updates';
337337
import { OutputEditorFactory } from './theia/output/output-editor-factory';
338338
import { StartupTaskProvider } from '../electron-common/startup-task';
339339
import { DeleteSketch } from './contributions/delete-sketch';
340+
import { UserFields } from './contributions/user-fields';
340341

341342
const registerArduinoThemes = () => {
342343
const themes: MonacoThemeJson[] = [
@@ -761,6 +762,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
761762
Contribution.configure(bind, OpenBoardsConfig);
762763
Contribution.configure(bind, SketchFilesTracker);
763764
Contribution.configure(bind, CheckForUpdates);
765+
Contribution.configure(bind, UserFields);
764766
Contribution.configure(bind, DeleteSketch);
765767

766768
bindContributionProvider(bind, StartupTaskProvider);

Diff for: arduino-ide-extension/src/browser/contributions/upload-sketch.ts

+28-127
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { inject, injectable } from '@theia/core/shared/inversify';
22
import { Emitter } from '@theia/core/lib/common/event';
3-
import { BoardUserField, CoreService, Port } from '../../common/protocol';
4-
import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
3+
import { CoreService, Port } from '../../common/protocol';
4+
import { ArduinoMenus } from '../menu/arduino-menus';
55
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
66
import {
77
Command,
@@ -11,96 +11,36 @@ import {
1111
TabBarToolbarRegistry,
1212
CoreServiceContribution,
1313
} from './contribution';
14-
import { UserFieldsDialog } from '../dialogs/user-fields/user-fields-dialog';
15-
import { deepClone, DisposableCollection, nls } from '@theia/core/lib/common';
14+
import { deepClone, nls } from '@theia/core/lib/common';
1615
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
1716
import type { VerifySketchParams } from './verify-sketch';
17+
import { UserFields } from './user-fields';
1818

1919
@injectable()
2020
export class UploadSketch extends CoreServiceContribution {
21-
@inject(MenuModelRegistry)
22-
private readonly menuRegistry: MenuModelRegistry;
23-
24-
@inject(UserFieldsDialog)
25-
private readonly userFieldsDialog: UserFieldsDialog;
26-
27-
private boardRequiresUserFields = false;
28-
private readonly cachedUserFields: Map<string, BoardUserField[]> = new Map();
29-
private readonly menuActionsDisposables = new DisposableCollection();
30-
3121
private readonly onDidChangeEmitter = new Emitter<void>();
3222
private readonly onDidChange = this.onDidChangeEmitter.event;
3323
private uploadInProgress = false;
3424

35-
protected override init(): void {
36-
super.init();
37-
this.boardsServiceProvider.onBoardsConfigChanged(async () => {
38-
const userFields =
39-
await this.boardsServiceProvider.selectedBoardUserFields();
40-
this.boardRequiresUserFields = userFields.length > 0;
41-
this.registerMenus(this.menuRegistry);
42-
});
43-
}
44-
45-
private selectedFqbnAddress(): string {
46-
const { boardsConfig } = this.boardsServiceProvider;
47-
const fqbn = boardsConfig.selectedBoard?.fqbn;
48-
if (!fqbn) {
49-
return '';
50-
}
51-
const address =
52-
boardsConfig.selectedBoard?.port?.address ||
53-
boardsConfig.selectedPort?.address;
54-
if (!address) {
55-
return '';
56-
}
57-
return fqbn + '|' + address;
58-
}
25+
@inject(UserFields)
26+
private readonly userFields: UserFields;
5927

6028
override registerCommands(registry: CommandRegistry): void {
6129
registry.registerCommand(UploadSketch.Commands.UPLOAD_SKETCH, {
6230
execute: async () => {
63-
const key = this.selectedFqbnAddress();
64-
if (
65-
this.boardRequiresUserFields &&
66-
key &&
67-
!this.cachedUserFields.has(key)
68-
) {
69-
// Deep clone the array of board fields to avoid editing the cached ones
70-
this.userFieldsDialog.value = (
71-
await this.boardsServiceProvider.selectedBoardUserFields()
72-
).map((f) => ({ ...f }));
73-
const result = await this.userFieldsDialog.open();
74-
if (!result) {
75-
return;
76-
}
77-
this.cachedUserFields.set(key, result);
31+
if (await this.userFields.checkUserFieldsDialog()) {
32+
this.uploadSketch();
7833
}
79-
this.uploadSketch();
8034
},
8135
isEnabled: () => !this.uploadInProgress,
8236
});
8337
registry.registerCommand(UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION, {
8438
execute: async () => {
85-
const key = this.selectedFqbnAddress();
86-
if (!key) {
87-
return;
88-
}
89-
90-
const cached = this.cachedUserFields.get(key);
91-
// Deep clone the array of board fields to avoid editing the cached ones
92-
this.userFieldsDialog.value = (
93-
cached ?? (await this.boardsServiceProvider.selectedBoardUserFields())
94-
).map((f) => ({ ...f }));
95-
96-
const result = await this.userFieldsDialog.open();
97-
if (!result) {
98-
return;
39+
if (await this.userFields.checkUserFieldsDialog(true)) {
40+
this.uploadSketch();
9941
}
100-
this.cachedUserFields.set(key, result);
101-
this.uploadSketch();
10242
},
103-
isEnabled: () => !this.uploadInProgress && this.boardRequiresUserFields,
43+
isEnabled: () => !this.uploadInProgress && this.userFields.isRequired(),
10444
});
10545
registry.registerCommand(
10646
UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER,
@@ -120,45 +60,20 @@ export class UploadSketch extends CoreServiceContribution {
12060
}
12161

12262
override registerMenus(registry: MenuModelRegistry): void {
123-
this.menuActionsDisposables.dispose();
124-
this.menuActionsDisposables.push(
125-
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
126-
commandId: UploadSketch.Commands.UPLOAD_SKETCH.id,
127-
label: nls.localize('arduino/sketch/upload', 'Upload'),
128-
order: '1',
129-
})
130-
);
131-
if (this.boardRequiresUserFields) {
132-
this.menuActionsDisposables.push(
133-
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
134-
commandId: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.id,
135-
label: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.label,
136-
order: '2',
137-
})
138-
);
139-
} else {
140-
this.menuActionsDisposables.push(
141-
registry.registerMenuNode(
142-
ArduinoMenus.SKETCH__MAIN_GROUP,
143-
new PlaceholderMenuNode(
144-
ArduinoMenus.SKETCH__MAIN_GROUP,
145-
// commandId: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.id,
146-
UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.label,
147-
{ order: '2' }
148-
)
149-
)
150-
);
151-
}
152-
this.menuActionsDisposables.push(
153-
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
154-
commandId: UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER.id,
155-
label: nls.localize(
156-
'arduino/sketch/uploadUsingProgrammer',
157-
'Upload Using Programmer'
158-
),
159-
order: '3',
160-
})
161-
);
63+
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
64+
commandId: UploadSketch.Commands.UPLOAD_SKETCH.id,
65+
label: nls.localize('arduino/sketch/upload', 'Upload'),
66+
order: '1',
67+
});
68+
69+
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
70+
commandId: UploadSketch.Commands.UPLOAD_SKETCH_USING_PROGRAMMER.id,
71+
label: nls.localize(
72+
'arduino/sketch/uploadUsingProgrammer',
73+
'Upload Using Programmer'
74+
),
75+
order: '3',
76+
});
16277
}
16378

16479
override registerKeybindings(registry: KeybindingRegistry): void {
@@ -215,18 +130,7 @@ export class UploadSketch extends CoreServiceContribution {
215130
return;
216131
}
217132

218-
// TODO: This does not belong here.
219-
// IDE2 should not do any preliminary checks but let the CLI fail and then toast a user consumable error message.
220-
if (
221-
uploadOptions.userFields.length === 0 &&
222-
this.boardRequiresUserFields
223-
) {
224-
this.messageService.error(
225-
nls.localize(
226-
'arduino/sketch/userFieldsNotFoundError',
227-
"Can't find user fields for connected board"
228-
)
229-
);
133+
if (!this.userFields.checkUserFieldsForUpload()) {
230134
return;
231135
}
232136

@@ -242,6 +146,7 @@ export class UploadSketch extends CoreServiceContribution {
242146
{ timeout: 3000 }
243147
);
244148
} catch (e) {
149+
this.userFields.notifyFailedWithError(e);
245150
this.handleError(e);
246151
} finally {
247152
this.uploadInProgress = false;
@@ -258,7 +163,7 @@ export class UploadSketch extends CoreServiceContribution {
258163
if (!CurrentSketch.isValid(sketch)) {
259164
return undefined;
260165
}
261-
const userFields = this.userFields();
166+
const userFields = this.userFields.getUserFields();
262167
const { boardsConfig } = this.boardsServiceProvider;
263168
const [fqbn, { selectedProgrammer: programmer }, verify, verbose] =
264169
await Promise.all([
@@ -301,10 +206,6 @@ export class UploadSketch extends CoreServiceContribution {
301206
return port;
302207
}
303208

304-
private userFields(): BoardUserField[] {
305-
return this.cachedUserFields.get(this.selectedFqbnAddress()) ?? [];
306-
}
307-
308209
/**
309210
* Converts the `VENDOR:ARCHITECTURE:BOARD_ID[:MENU_ID=OPTION_ID[,MENU2_ID=OPTION_ID ...]]` FQBN to
310211
* `VENDOR:ARCHITECTURE:BOARD_ID` format.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import { inject, injectable } from '@theia/core/shared/inversify';
2+
import { DisposableCollection, nls } from '@theia/core/lib/common';
3+
import { BoardUserField, CoreError } from '../../common/protocol';
4+
import { BoardsServiceProvider } from '../boards/boards-service-provider';
5+
import { UserFieldsDialog } from '../dialogs/user-fields/user-fields-dialog';
6+
import { ArduinoMenus, PlaceholderMenuNode } from '../menu/arduino-menus';
7+
import { MenuModelRegistry, Contribution } from './contribution';
8+
import { UploadSketch } from './upload-sketch';
9+
10+
@injectable()
11+
export class UserFields extends Contribution {
12+
private boardRequiresUserFields = false;
13+
private userFieldsSet = false;
14+
private readonly cachedUserFields: Map<string, BoardUserField[]> = new Map();
15+
private readonly menuActionsDisposables = new DisposableCollection();
16+
17+
@inject(UserFieldsDialog)
18+
private readonly userFieldsDialog: UserFieldsDialog;
19+
20+
@inject(BoardsServiceProvider)
21+
private readonly boardsServiceProvider: BoardsServiceProvider;
22+
23+
@inject(MenuModelRegistry)
24+
private readonly menuRegistry: MenuModelRegistry;
25+
26+
protected override init(): void {
27+
super.init();
28+
this.boardsServiceProvider.onBoardsConfigChanged(async () => {
29+
const userFields =
30+
await this.boardsServiceProvider.selectedBoardUserFields();
31+
this.boardRequiresUserFields = userFields.length > 0;
32+
this.registerMenus(this.menuRegistry);
33+
});
34+
}
35+
36+
override registerMenus(registry: MenuModelRegistry): void {
37+
this.menuActionsDisposables.dispose();
38+
if (this.boardRequiresUserFields) {
39+
this.menuActionsDisposables.push(
40+
registry.registerMenuAction(ArduinoMenus.SKETCH__MAIN_GROUP, {
41+
commandId: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.id,
42+
label: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.label,
43+
order: '2',
44+
})
45+
);
46+
} else {
47+
this.menuActionsDisposables.push(
48+
registry.registerMenuNode(
49+
ArduinoMenus.SKETCH__MAIN_GROUP,
50+
new PlaceholderMenuNode(
51+
ArduinoMenus.SKETCH__MAIN_GROUP,
52+
// commandId: UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.id,
53+
UploadSketch.Commands.UPLOAD_WITH_CONFIGURATION.label,
54+
{ order: '2' }
55+
)
56+
)
57+
);
58+
}
59+
}
60+
61+
private selectedFqbnAddress(): string | undefined {
62+
const { boardsConfig } = this.boardsServiceProvider;
63+
const fqbn = boardsConfig.selectedBoard?.fqbn;
64+
if (!fqbn) {
65+
return undefined;
66+
}
67+
const address =
68+
boardsConfig.selectedBoard?.port?.address ||
69+
boardsConfig.selectedPort?.address;
70+
if (!address) {
71+
return undefined;
72+
}
73+
return fqbn + '|' + address;
74+
}
75+
76+
private async showUserFieldsDialog(
77+
key: string
78+
): Promise<BoardUserField[] | undefined> {
79+
const cached = this.cachedUserFields.get(key);
80+
// Deep clone the array of board fields to avoid editing the cached ones
81+
this.userFieldsDialog.value = cached ? cached.slice() : await this.boardsServiceProvider.selectedBoardUserFields();
82+
const result = await this.userFieldsDialog.open();
83+
if (!result) {
84+
return;
85+
}
86+
87+
this.userFieldsSet = true;
88+
this.cachedUserFields.set(key, result);
89+
return result;
90+
}
91+
92+
async checkUserFieldsDialog(forceOpen = false): Promise<boolean> {
93+
const key = this.selectedFqbnAddress();
94+
if (!key) {
95+
return false;
96+
}
97+
/*
98+
If the board requires to be configured with user fields, we want
99+
to show the user fields dialog, but only if they weren't already
100+
filled in or if they were filled in, but the previous upload failed.
101+
*/
102+
if (
103+
!forceOpen &&
104+
(!this.boardRequiresUserFields ||
105+
(this.cachedUserFields.has(key) && this.userFieldsSet))
106+
) {
107+
return true;
108+
}
109+
const userFieldsFilledIn = Boolean(await this.showUserFieldsDialog(key));
110+
return userFieldsFilledIn;
111+
}
112+
113+
checkUserFieldsForUpload(): boolean {
114+
// TODO: This does not belong here.
115+
// IDE2 should not do any preliminary checks but let the CLI fail and then toast a user consumable error message.
116+
if (!this.boardRequiresUserFields || this.getUserFields().length > 0) {
117+
this.userFieldsSet = true;
118+
return true;
119+
}
120+
this.messageService.error(
121+
nls.localize(
122+
'arduino/sketch/userFieldsNotFoundError',
123+
"Can't find user fields for connected board"
124+
)
125+
);
126+
this.userFieldsSet = false;
127+
return false;
128+
}
129+
130+
getUserFields(): BoardUserField[] {
131+
const fqbnAddress = this.selectedFqbnAddress();
132+
if (!fqbnAddress) {
133+
return [];
134+
}
135+
return this.cachedUserFields.get(fqbnAddress) ?? [];
136+
}
137+
138+
isRequired(): boolean {
139+
return this.boardRequiresUserFields;
140+
}
141+
142+
notifyFailedWithError(e: Error): void {
143+
if (
144+
this.boardRequiresUserFields &&
145+
CoreError.UploadFailed.is(e)
146+
) {
147+
this.userFieldsSet = false;
148+
}
149+
}
150+
}

0 commit comments

Comments
 (0)