Skip to content

Commit 0f59310

Browse files
author
Akos Kitta
committed
ATL-786: Progress indication for install.
Signed-off-by: Akos Kitta <[email protected]>
1 parent 3fc1ffc commit 0f59310

29 files changed

+542
-212
lines changed

arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import { WorkspaceService } from './theia/workspace/workspace-service';
4444
import { ArduinoToolbar } from './toolbar/arduino-toolbar';
4545
import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
4646
import { FileService } from '@theia/filesystem/lib/browser/file-service';
47-
import { OutputService } from '../common/protocol/output-service';
47+
import { ResponseService } from '../common/protocol/response-service';
4848
import { ArduinoPreferences } from './arduino-preferences';
4949
import { SketchesServiceClientImpl } from '../common/protocol/sketches-service-client-impl';
5050
import { SaveAsSketch } from './contributions/save-as-sketch';
@@ -151,8 +151,8 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
151151
@inject(ExecutableService)
152152
protected executableService: ExecutableService;
153153

154-
@inject(OutputService)
155-
protected readonly outputService: OutputService;
154+
@inject(ResponseService)
155+
protected readonly responseService: ResponseService;
156156

157157
@inject(ArduinoPreferences)
158158
protected readonly arduinoPreferences: ArduinoPreferences;

arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts

+15-6
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ import { OutputChannelRegistryMainImpl as TheiaOutputChannelRegistryMainImpl, Ou
120120
import { ExecutableService, ExecutableServicePath } from '../common/protocol';
121121
import { MonacoTextModelService as TheiaMonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service';
122122
import { MonacoTextModelService } from './theia/monaco/monaco-text-model-service';
123-
import { OutputServiceImpl } from './output-service-impl';
124-
import { OutputServicePath, OutputService } from '../common/protocol/output-service';
123+
import { ResponseServiceImpl } from './response-service-impl';
124+
import { ResponseServicePath, ResponseService } from '../common/protocol/response-service';
125125
import { NotificationCenter } from './notification-center';
126126
import { NotificationServicePath, NotificationServiceServer } from '../common/protocol';
127127
import { About } from './contributions/about';
@@ -159,6 +159,10 @@ import { MonacoEditorProvider as TheiaMonacoEditorProvider } from '@theia/monaco
159159
import { DebugEditorModel } from './theia/debug/debug-editor-model';
160160
import { DebugEditorModelFactory } from '@theia/debug/lib/browser/editor/debug-editor-model';
161161
import { StorageWrapper } from './storage-wrapper';
162+
import { NotificationManager } from './theia/messages/notifications-manager';
163+
import { NotificationManager as TheiaNotificationManager } from '@theia/messages/lib/browser/notifications-manager';
164+
import { NotificationsRenderer as TheiaNotificationsRenderer } from '@theia/messages/lib/browser/notifications-renderer';
165+
import { NotificationsRenderer } from './theia/messages/notifications-renderer';
162166

163167
const ElementQueries = require('css-element-queries/src/ElementQueries');
164168

@@ -383,11 +387,11 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
383387
Contribution.configure(bind, ArchiveSketch);
384388
Contribution.configure(bind, AddZipLibrary);
385389

386-
bind(OutputServiceImpl).toSelf().inSingletonScope().onActivation(({ container }, outputService) => {
387-
WebSocketConnectionProvider.createProxy(container, OutputServicePath, outputService);
388-
return outputService;
390+
bind(ResponseServiceImpl).toSelf().inSingletonScope().onActivation(({ container }, responseService) => {
391+
WebSocketConnectionProvider.createProxy(container, ResponseServicePath, responseService);
392+
return responseService;
389393
});
390-
bind(OutputService).toService(OutputServiceImpl);
394+
bind(ResponseService).toService(ResponseServiceImpl);
391395

392396
bind(NotificationCenter).toSelf().inSingletonScope();
393397
bind(FrontendApplicationContribution).toService(NotificationCenter);
@@ -439,4 +443,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
439443

440444
bind(StorageWrapper).toSelf().inSingletonScope();
441445
bind(CommandContribution).toService(StorageWrapper);
446+
447+
bind(NotificationManager).toSelf().inSingletonScope();
448+
rebind(TheiaNotificationManager).toService(NotificationManager);
449+
bind(NotificationsRenderer).toSelf().inSingletonScope();
450+
rebind(TheiaNotificationsRenderer).toService(NotificationsRenderer);
442451
});

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

+12-8
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import { FrontendApplicationContribution } from '@theia/core/lib/browser/fronten
44
import { BoardsService, BoardsPackage } from '../../common/protocol/boards-service';
55
import { BoardsServiceProvider } from './boards-service-provider';
66
import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution';
7-
import { InstallationProgressDialog } from '../widgets/progress-dialog';
87
import { BoardsConfig } from './boards-config';
8+
import { Installable } from '../../common/protocol';
9+
import { ResponseServiceImpl } from '../response-service-impl';
910

1011
/**
1112
* Listens on `BoardsConfig.Config` changes, if a board is selected which does not
@@ -23,6 +24,9 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
2324
@inject(BoardsServiceProvider)
2425
protected readonly boardsServiceClient: BoardsServiceProvider;
2526

27+
@inject(ResponseServiceImpl)
28+
protected readonly responseService: ResponseServiceImpl;
29+
2630
@inject(BoardsListWidgetFrontendContribution)
2731
protected readonly boardsManagerFrontendContribution: BoardsListWidgetFrontendContribution;
2832

@@ -42,13 +46,13 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
4246
// tslint:disable-next-line:max-line-length
4347
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?`, 'Install Manually', 'Yes').then(async answer => {
4448
if (answer === 'Yes') {
45-
const dialog = new InstallationProgressDialog(candidate.name, candidate.availableVersions[0]);
46-
dialog.open();
47-
try {
48-
await this.boardsService.install({ item: candidate });
49-
} finally {
50-
dialog.close();
51-
}
49+
await Installable.installWithProgress({
50+
installable: this.boardsService,
51+
item: candidate,
52+
messageService: this.messageService,
53+
responseService: this.responseService,
54+
version: candidate.availableVersions[0]
55+
});
5256
}
5357
if (answer) {
5458
this.boardsManagerFrontendContribution.openView({ reveal: true }).then(widget => widget.refresh(candidate.name.toLocaleLowerCase()));

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

+8-3
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,14 @@ export class BoardsListWidget extends ListWidget<BoardsPackage> {
3333
]);
3434
}
3535

36-
async install({ item, version }: { item: BoardsPackage; version: string; }): Promise<void> {
37-
await super.install({ item, version });
38-
this.messageService.info(`Successfully installed platform ${item.name}:${version}.`, { timeout: 3000 });
36+
protected async install({ item, progressId, version }: { item: BoardsPackage, progressId: string, version: string; }): Promise<void> {
37+
await super.install({ item, progressId, version });
38+
this.messageService.info(`Successfully installed platform ${item.name}:${version}`, { timeout: 3000 });
39+
}
40+
41+
protected async uninstall({ item, progressId }: { item: BoardsPackage, progressId: string }): Promise<void> {
42+
await super.uninstall({ item, progressId });
43+
this.messageService.info(`Successfully uninstalled platform ${item.name}:${item.installedVersion}`, { timeout: 3000 });
3944
}
4045

4146
}

arduino-ide-extension/src/browser/contributions/add-zip-library.ts

+15-11
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
import { inject, injectable } from 'inversify';
22
import { remote } from 'electron';
3+
import URI from '@theia/core/lib/common/uri';
4+
import { ConfirmDialog } from '@theia/core/lib/browser/dialogs';
5+
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
36
import { ArduinoMenus } from '../menu/arduino-menus';
7+
import { ResponseServiceImpl } from '../response-service-impl';
8+
import { Installable, LibraryService } from '../../common/protocol';
49
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry } from './contribution';
5-
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
6-
import URI from '@theia/core/lib/common/uri';
7-
import { InstallationProgressDialog } from '../widgets/progress-dialog';
8-
import { LibraryService } from '../../common/protocol';
9-
import { ConfirmDialog } from '@theia/core/lib/browser';
1010

1111
@injectable()
1212
export class AddZipLibrary extends SketchContribution {
1313

1414
@inject(EnvVariablesServer)
1515
protected readonly envVariableServer: EnvVariablesServer;
1616

17+
@inject(ResponseServiceImpl)
18+
protected readonly responseService: ResponseServiceImpl;
19+
1720
@inject(LibraryService)
1821
protected readonly libraryService: LibraryService;
1922

@@ -69,11 +72,14 @@ export class AddZipLibrary extends SketchContribution {
6972
}
7073

7174
private async doInstall(zipUri: string, overwrite?: boolean): Promise<void> {
72-
const dialog = new InstallationProgressDialog('Installing library', 'zip');
7375
try {
74-
this.outputChannelManager.getChannel('Arduino').clear();
75-
dialog.open();
76-
await this.libraryService.installZip({ zipUri, overwrite });
76+
await Installable.doWithProgress({
77+
messageService: this.messageService,
78+
progressText: `Processing ${new URI(zipUri).path.base}`,
79+
responseService: this.responseService,
80+
run: () => this.libraryService.installZip({ zipUri, overwrite })
81+
});
82+
this.messageService.info(`Successfully installed library from ${new URI(zipUri).path.base} archive`, { timeout: 3000 });
7783
} catch (error) {
7884
if (error instanceof Error) {
7985
const match = error.message.match(/library (.*?) already installed/);
@@ -88,8 +94,6 @@ export class AddZipLibrary extends SketchContribution {
8894
}
8995
this.messageService.error(error.toString());
9096
throw error;
91-
} finally {
92-
dialog.close();
9397
}
9498
}
9599

arduino-ide-extension/src/browser/data/arduino.color-theme.json

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
"colors": {
8383
"list.highlightForeground": "#005c5f",
8484
"list.activeSelectionBackground": "#005c5f",
85+
"progressBar.background": "#005c5f",
8586
"editor.background": "#ffffff",
8687
"editorCursor.foreground": "#434f54",
8788
"editor.foreground": "#434f54",

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

+8-3
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
3737
]);
3838
}
3939

40-
protected async install({ item, version }: { item: LibraryPackage, version: Installable.Version }): Promise<void> {
40+
protected async install({ item, progressId, version }: { item: LibraryPackage, progressId: string, version: Installable.Version }): Promise<void> {
4141
const dependencies = await this.service.listDependencies({ item, version, filterSelf: true });
4242
let installDependencies: boolean | undefined = undefined;
4343
if (dependencies.length) {
@@ -84,11 +84,16 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
8484
}
8585

8686
if (typeof installDependencies === 'boolean') {
87-
await this.service.install({ item, version, installDependencies });
88-
this.messageService.info(`Successfully installed library ${item.name}:${version}.`, { timeout: 3000 });
87+
await this.service.install({ item, version, progressId, installDependencies });
88+
this.messageService.info(`Successfully installed library ${item.name}:${version}`, { timeout: 3000 });
8989
}
9090
}
9191

92+
protected async uninstall({ item, progressId }: { item: LibraryPackage, progressId: string }): Promise<void> {
93+
await super.uninstall({ item, progressId });
94+
this.messageService.info(`Successfully uninstalled library ${item.name}:${item.installedVersion}`, { timeout: 3000 });
95+
}
96+
9297
}
9398

9499
class MessageBoxDialog extends AbstractDialog<MessageBoxDialog.Result> {

arduino-ide-extension/src/browser/output-service-impl.ts

-22
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { inject, injectable } from 'inversify';
2+
import { Emitter } from '@theia/core/lib/common/event';
3+
import { OutputContribution } from '@theia/output/lib/browser/output-contribution';
4+
import { OutputChannelManager } from '@theia/output/lib/common/output-channel';
5+
import { ResponseService, OutputMessage, ProgressMessage } from '../common/protocol/response-service';
6+
7+
@injectable()
8+
export class ResponseServiceImpl implements ResponseService {
9+
10+
@inject(OutputContribution)
11+
protected outputContribution: OutputContribution;
12+
13+
@inject(OutputChannelManager)
14+
protected outputChannelManager: OutputChannelManager;
15+
16+
protected readonly progressDidChangeEmitter = new Emitter<ProgressMessage>();
17+
readonly onProgressDidChange = this.progressDidChangeEmitter.event;
18+
19+
appendToOutput(message: OutputMessage): void {
20+
const { chunk } = message;
21+
const channel = this.outputChannelManager.getChannel('Arduino');
22+
channel.show({ preserveFocus: true });
23+
channel.append(chunk);
24+
}
25+
26+
clearArduinoChannel(): void {
27+
this.outputChannelManager.getChannel('Arduino').clear();
28+
}
29+
30+
reportProgress(progress: ProgressMessage): void {
31+
this.progressDidChangeEmitter.fire(progress);
32+
}
33+
34+
}

arduino-ide-extension/src/browser/style/index.css

+17
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,20 @@ button.theia-button.secondary {
6767
button.theia-button.main {
6868
color: var(--theia-button-foreground);
6969
}
70+
71+
/* To make the progress-bar slightly thicker, and use the color from the status bar */
72+
.theia-progress-bar-container {
73+
width: 100%;
74+
height: 4px;
75+
}
76+
77+
.theia-progress-bar {
78+
height: 4px;
79+
width: 3%;
80+
animation: progress-animation 1.3s 0s infinite cubic-bezier(0.645, 0.045, 0.355, 1);
81+
}
82+
83+
.theia-notification-item-progressbar {
84+
height: 4px;
85+
width: 66%;
86+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import * as React from 'react';
2+
import { NotificationComponent } from './notification-component';
3+
import { NotificationCenterComponent as TheiaNotificationCenterComponent } from '@theia/messages/lib/browser/notification-center-component'
4+
5+
const PerfectScrollbar = require('react-perfect-scrollbar');
6+
7+
export class NotificationCenterComponent extends TheiaNotificationCenterComponent {
8+
9+
render(): React.ReactNode {
10+
const empty = this.state.notifications.length === 0;
11+
const title = empty ? 'NO NEW NOTIFICATIONS' : 'NOTIFICATIONS';
12+
return (
13+
<div className={`theia-notifications-container theia-notification-center ${this.state.visibilityState === 'center' ? 'open' : 'closed'}`}>
14+
<div className='theia-notification-center-header'>
15+
<div className='theia-notification-center-header-title'>{title}</div>
16+
<div className='theia-notification-center-header-actions'>
17+
<ul className='theia-notification-actions'>
18+
<li className='collapse' title='Hide Notification Center' onClick={this.onHide} />
19+
<li className='clear-all' title='Clear All' onClick={this.onClearAll} />
20+
</ul>
21+
</div>
22+
</div>
23+
<PerfectScrollbar className='theia-notification-list-scroll-container'>
24+
<div className='theia-notification-list'>
25+
{this.state.notifications.map(notification =>
26+
<NotificationComponent key={notification.messageId} notification={notification} manager={this.props.manager} />
27+
)}
28+
</div>
29+
</PerfectScrollbar>
30+
</div>
31+
);
32+
}
33+
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import * as React from 'react';
2+
import { NotificationComponent as TheiaNotificationComponent } from '@theia/messages/lib/browser/notification-component'
3+
4+
export class NotificationComponent extends TheiaNotificationComponent {
5+
6+
render(): React.ReactNode {
7+
const { messageId, message, type, collapsed, expandable, source, actions } = this.props.notification;
8+
return (<div key={messageId} className='theia-notification-list-item'>
9+
<div className={`theia-notification-list-item-content ${collapsed ? 'collapsed' : ''}`}>
10+
<div className='theia-notification-list-item-content-main'>
11+
<div className={`theia-notification-icon theia-notification-icon-${type}`} />
12+
<div className='theia-notification-message'>
13+
<span dangerouslySetInnerHTML={{ __html: message }} onClick={this.onMessageClick} />
14+
</div>
15+
<ul className='theia-notification-actions'>
16+
{expandable && (
17+
<li className={collapsed ? 'expand' : 'collapse'} title={collapsed ? 'Expand' : 'Collapse'}
18+
data-message-id={messageId} onClick={this.onToggleExpansion} />
19+
)}
20+
{!this.isProgress && (<li className='clear' title='Clear' data-message-id={messageId} onClick={this.onClear} />)}
21+
</ul>
22+
</div>
23+
{(source || !!actions.length) && (
24+
<div className='theia-notification-list-item-content-bottom'>
25+
<div className='theia-notification-source'>
26+
{source && (<span>{source}</span>)}
27+
</div>
28+
<div className='theia-notification-buttons'>
29+
{actions && actions.map((action, index) => (
30+
<button key={messageId + `-action-${index}`} className='theia-button'
31+
data-message-id={messageId} data-action={action}
32+
onClick={this.onAction}>
33+
{action}
34+
</button>
35+
))}
36+
</div>
37+
</div>
38+
)}
39+
</div>
40+
{this.renderProgressBar()}
41+
</div>);
42+
}
43+
44+
private renderProgressBar(): React.ReactNode {
45+
if (!this.isProgress) {
46+
return undefined;
47+
}
48+
if (!Number.isNaN(this.props.notification.progress)) {
49+
return <div className='theia-notification-item-progress'>
50+
<div className='theia-notification-item-progressbar' style={{ width: `${this.props.notification.progress}%` }} />
51+
</div>;
52+
}
53+
return <div className='theia-progress-bar-container'>
54+
<div className='theia-progress-bar' />
55+
</div>
56+
}
57+
58+
private get isProgress(): boolean {
59+
return typeof this.props.notification.progress === 'number';
60+
}
61+
62+
}

0 commit comments

Comments
 (0)