Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 7f2b849

Browse files
#854 fix platform installation only offered if port is selected (#1130)
* ensure desired prompts shown + refactor * pr review changes
1 parent 0ce065e commit 7f2b849

File tree

2 files changed

+259
-97
lines changed

2 files changed

+259
-97
lines changed

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

Lines changed: 232 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,37 @@ import {
55
BoardsService,
66
BoardsPackage,
77
Board,
8+
Port,
89
} from '../../common/protocol/boards-service';
910
import { BoardsServiceProvider } from './boards-service-provider';
10-
import { BoardsConfig } from './boards-config';
1111
import { Installable, ResponseServiceArduino } from '../../common/protocol';
1212
import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution';
1313
import { nls } from '@theia/core/lib/common';
14+
import { NotificationCenter } from '../notification-center';
15+
16+
interface AutoInstallPromptAction {
17+
// isAcceptance, whether or not the action indicates acceptance of auto-install proposal
18+
isAcceptance?: boolean;
19+
key: string;
20+
handler: (...args: unknown[]) => unknown;
21+
}
22+
23+
type AutoInstallPromptActions = AutoInstallPromptAction[];
1424

1525
/**
1626
* Listens on `BoardsConfig.Config` changes, if a board is selected which does not
1727
* have the corresponding core installed, it proposes the user to install the core.
1828
*/
29+
30+
// * Cases in which we do not show the auto-install prompt:
31+
// 1. When a related platform is already installed
32+
// 2. When a prompt is already showing in the UI
33+
// 3. When a board is unplugged
1934
@injectable()
2035
export class BoardsAutoInstaller implements FrontendApplicationContribution {
36+
@inject(NotificationCenter)
37+
private readonly notificationCenter: NotificationCenter;
38+
2139
@inject(MessageService)
2240
protected readonly messageService: MessageService;
2341

@@ -36,97 +54,228 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
3654
// Workaround for https://github.com/eclipse-theia/theia/issues/9349
3755
protected notifications: Board[] = [];
3856

57+
// * "refusal" meaning a "prompt action" not accepting the auto-install offer ("X" or "install manually")
58+
// we can use "portSelectedOnLastRefusal" to deduce when a board is unplugged after a user has "refused"
59+
// an auto-install prompt. Important to know as we do not want "an unplug" to trigger a "refused" prompt
60+
// showing again
61+
private portSelectedOnLastRefusal: Port | undefined;
62+
private lastRefusedPackageId: string | undefined;
63+
3964
onStart(): void {
40-
this.boardsServiceClient.onBoardsConfigChanged(
41-
this.ensureCoreExists.bind(this)
42-
);
43-
this.ensureCoreExists(this.boardsServiceClient.boardsConfig);
44-
}
65+
const setEventListeners = () => {
66+
this.boardsServiceClient.onBoardsConfigChanged((config) => {
67+
const { selectedBoard, selectedPort } = config;
68+
69+
const boardWasUnplugged =
70+
!selectedPort && this.portSelectedOnLastRefusal;
71+
72+
this.clearLastRefusedPromptInfo();
4573

46-
protected ensureCoreExists(config: BoardsConfig.Config): void {
47-
const { selectedBoard, selectedPort } = config;
48-
if (
49-
selectedBoard &&
50-
selectedPort &&
51-
!this.notifications.find((board) => Board.sameAs(board, selectedBoard))
52-
) {
53-
this.notifications.push(selectedBoard);
54-
this.boardsService.search({}).then((packages) => {
55-
// filter packagesForBoard selecting matches from the cli (installed packages)
56-
// and matches based on the board name
57-
// NOTE: this ensures the Deprecated & new packages are all in the array
58-
// so that we can check if any of the valid packages is already installed
59-
const packagesForBoard = packages.filter(
60-
(pkg) =>
61-
BoardsPackage.contains(selectedBoard, pkg) ||
62-
pkg.boards.some((board) => board.name === selectedBoard.name)
63-
);
64-
65-
// check if one of the packages for the board is already installed. if so, no hint
6674
if (
67-
packagesForBoard.some(({ installedVersion }) => !!installedVersion)
75+
boardWasUnplugged ||
76+
!selectedBoard ||
77+
this.promptAlreadyShowingForBoard(selectedBoard)
6878
) {
6979
return;
7080
}
7181

72-
// filter the installable (not installed) packages,
73-
// CLI returns the packages already sorted with the deprecated ones at the end of the list
74-
// in order to ensure the new ones are preferred
75-
const candidates = packagesForBoard.filter(
76-
({ installable, installedVersion }) =>
77-
installable && !installedVersion
78-
);
79-
80-
const candidate = candidates[0];
81-
if (candidate) {
82-
const version = candidate.availableVersions[0]
83-
? `[v ${candidate.availableVersions[0]}]`
84-
: '';
85-
const yes = nls.localize('vscode/extensionsUtils/yes', 'Yes');
86-
const manualInstall = nls.localize(
87-
'arduino/board/installManually',
88-
'Install Manually'
89-
);
90-
// tslint:disable-next-line:max-line-length
91-
this.messageService
92-
.info(
93-
nls.localize(
94-
'arduino/board/installNow',
95-
'The "{0} {1}" core has to be installed for the currently selected "{2}" board. Do you want to install it now?',
96-
candidate.name,
97-
version,
98-
selectedBoard.name
99-
),
100-
manualInstall,
101-
yes
102-
)
103-
.then(async (answer) => {
104-
const index = this.notifications.findIndex((board) =>
105-
Board.sameAs(board, selectedBoard)
106-
);
107-
if (index !== -1) {
108-
this.notifications.splice(index, 1);
109-
}
110-
if (answer === yes) {
111-
await Installable.installWithProgress({
112-
installable: this.boardsService,
113-
item: candidate,
114-
messageService: this.messageService,
115-
responseService: this.responseService,
116-
version: candidate.availableVersions[0],
117-
});
118-
return;
119-
}
120-
if (answer === manualInstall) {
121-
this.boardsManagerFrontendContribution
122-
.openView({ reveal: true })
123-
.then((widget) =>
124-
widget.refresh(candidate.name.toLocaleLowerCase())
125-
);
126-
}
127-
});
82+
this.ensureCoreExists(selectedBoard, selectedPort);
83+
});
84+
85+
// we "clearRefusedPackageInfo" if a "refused" package is eventually
86+
// installed, though this is not strictly necessary. It's more of a
87+
// cleanup, to ensure the related variables are representative of
88+
// current state.
89+
this.notificationCenter.onPlatformInstalled((installed) => {
90+
if (this.lastRefusedPackageId === installed.item.id) {
91+
this.clearLastRefusedPromptInfo();
12892
}
12993
});
94+
};
95+
96+
// we should invoke this.ensureCoreExists only once we're sure
97+
// everything has been reconciled
98+
this.boardsServiceClient.reconciled.then(() => {
99+
const { selectedBoard, selectedPort } =
100+
this.boardsServiceClient.boardsConfig;
101+
102+
if (selectedBoard) {
103+
this.ensureCoreExists(selectedBoard, selectedPort);
104+
}
105+
106+
setEventListeners();
107+
});
108+
}
109+
110+
private removeNotificationByBoard(selectedBoard: Board): void {
111+
const index = this.notifications.findIndex((notification) =>
112+
Board.sameAs(notification, selectedBoard)
113+
);
114+
if (index !== -1) {
115+
this.notifications.splice(index, 1);
116+
}
117+
}
118+
119+
private clearLastRefusedPromptInfo(): void {
120+
this.lastRefusedPackageId = undefined;
121+
this.portSelectedOnLastRefusal = undefined;
122+
}
123+
124+
private setLastRefusedPromptInfo(
125+
packageId: string,
126+
selectedPort?: Port
127+
): void {
128+
this.lastRefusedPackageId = packageId;
129+
this.portSelectedOnLastRefusal = selectedPort;
130+
}
131+
132+
private promptAlreadyShowingForBoard(board: Board): boolean {
133+
return Boolean(
134+
this.notifications.find((notification) =>
135+
Board.sameAs(notification, board)
136+
)
137+
);
138+
}
139+
140+
protected ensureCoreExists(selectedBoard: Board, selectedPort?: Port): void {
141+
this.notifications.push(selectedBoard);
142+
this.boardsService.search({}).then((packages) => {
143+
const candidate = this.getInstallCandidate(packages, selectedBoard);
144+
145+
if (candidate) {
146+
this.showAutoInstallPrompt(candidate, selectedBoard, selectedPort);
147+
} else {
148+
this.removeNotificationByBoard(selectedBoard);
149+
}
150+
});
151+
}
152+
153+
private getInstallCandidate(
154+
packages: BoardsPackage[],
155+
selectedBoard: Board
156+
): BoardsPackage | undefined {
157+
// filter packagesForBoard selecting matches from the cli (installed packages)
158+
// and matches based on the board name
159+
// NOTE: this ensures the Deprecated & new packages are all in the array
160+
// so that we can check if any of the valid packages is already installed
161+
const packagesForBoard = packages.filter(
162+
(pkg) =>
163+
BoardsPackage.contains(selectedBoard, pkg) ||
164+
pkg.boards.some((board) => board.name === selectedBoard.name)
165+
);
166+
167+
// check if one of the packages for the board is already installed. if so, no hint
168+
if (packagesForBoard.some(({ installedVersion }) => !!installedVersion)) {
169+
return;
130170
}
171+
172+
// filter the installable (not installed) packages,
173+
// CLI returns the packages already sorted with the deprecated ones at the end of the list
174+
// in order to ensure the new ones are preferred
175+
const candidates = packagesForBoard.filter(
176+
({ installable, installedVersion }) => installable && !installedVersion
177+
);
178+
179+
return candidates[0];
180+
}
181+
182+
private showAutoInstallPrompt(
183+
candidate: BoardsPackage,
184+
selectedBoard: Board,
185+
selectedPort?: Port
186+
): void {
187+
const candidateName = candidate.name;
188+
const version = candidate.availableVersions[0]
189+
? `[v ${candidate.availableVersions[0]}]`
190+
: '';
191+
192+
const info = this.generatePromptInfoText(
193+
candidateName,
194+
version,
195+
selectedBoard.name
196+
);
197+
198+
const actions = this.createPromptActions(candidate);
199+
200+
const onRefuse = () => {
201+
this.setLastRefusedPromptInfo(candidate.id, selectedPort);
202+
};
203+
const handleAction = this.createOnAnswerHandler(actions, onRefuse);
204+
205+
const onAnswer = (answer: string) => {
206+
this.removeNotificationByBoard(selectedBoard);
207+
208+
handleAction(answer);
209+
};
210+
211+
this.messageService
212+
.info(info, ...actions.map((action) => action.key))
213+
.then(onAnswer);
214+
}
215+
216+
private generatePromptInfoText(
217+
candidateName: string,
218+
version: string,
219+
boardName: string
220+
): string {
221+
return nls.localize(
222+
'arduino/board/installNow',
223+
'The "{0} {1}" core has to be installed for the currently selected "{2}" board. Do you want to install it now?',
224+
candidateName,
225+
version,
226+
boardName
227+
);
228+
}
229+
230+
private createPromptActions(
231+
candidate: BoardsPackage
232+
): AutoInstallPromptActions {
233+
const yes = nls.localize('vscode/extensionsUtils/yes', 'Yes');
234+
const manualInstall = nls.localize(
235+
'arduino/board/installManually',
236+
'Install Manually'
237+
);
238+
239+
const actions: AutoInstallPromptActions = [
240+
{
241+
isAcceptance: true,
242+
key: yes,
243+
handler: () => {
244+
return Installable.installWithProgress({
245+
installable: this.boardsService,
246+
item: candidate,
247+
messageService: this.messageService,
248+
responseService: this.responseService,
249+
version: candidate.availableVersions[0],
250+
});
251+
},
252+
},
253+
{
254+
key: manualInstall,
255+
handler: () => {
256+
this.boardsManagerFrontendContribution
257+
.openView({ reveal: true })
258+
.then((widget) =>
259+
widget.refresh(candidate.name.toLocaleLowerCase())
260+
);
261+
},
262+
},
263+
];
264+
265+
return actions;
266+
}
267+
268+
private createOnAnswerHandler(
269+
actions: AutoInstallPromptActions,
270+
onRefuse?: () => void
271+
): (answer: string) => void {
272+
return (answer) => {
273+
const actionToHandle = actions.find((action) => action.key === answer);
274+
actionToHandle?.handler();
275+
276+
if (!actionToHandle?.isAcceptance && onRefuse) {
277+
onRefuse();
278+
}
279+
};
131280
}
132281
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /