Skip to content

ATL-786: Progress indication for install. #305

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import { WorkspaceService } from './theia/workspace/workspace-service';
import { ArduinoToolbar } from './toolbar/arduino-toolbar';
import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
import { FileService } from '@theia/filesystem/lib/browser/file-service';
import { OutputService } from '../common/protocol/output-service';
import { ResponseService } from '../common/protocol/response-service';
import { ArduinoPreferences } from './arduino-preferences';
import { SketchesServiceClientImpl } from '../common/protocol/sketches-service-client-impl';
import { SaveAsSketch } from './contributions/save-as-sketch';
Expand Down Expand Up @@ -151,8 +151,8 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut
@inject(ExecutableService)
protected executableService: ExecutableService;

@inject(OutputService)
protected readonly outputService: OutputService;
@inject(ResponseService)
protected readonly responseService: ResponseService;

@inject(ArduinoPreferences)
protected readonly arduinoPreferences: ArduinoPreferences;
Expand Down
21 changes: 15 additions & 6 deletions arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ import { OutputChannelRegistryMainImpl as TheiaOutputChannelRegistryMainImpl, Ou
import { ExecutableService, ExecutableServicePath } from '../common/protocol';
import { MonacoTextModelService as TheiaMonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service';
import { MonacoTextModelService } from './theia/monaco/monaco-text-model-service';
import { OutputServiceImpl } from './output-service-impl';
import { OutputServicePath, OutputService } from '../common/protocol/output-service';
import { ResponseServiceImpl } from './response-service-impl';
import { ResponseServicePath, ResponseService } from '../common/protocol/response-service';
import { NotificationCenter } from './notification-center';
import { NotificationServicePath, NotificationServiceServer } from '../common/protocol';
import { About } from './contributions/about';
Expand Down Expand Up @@ -159,6 +159,10 @@ import { MonacoEditorProvider as TheiaMonacoEditorProvider } from '@theia/monaco
import { DebugEditorModel } from './theia/debug/debug-editor-model';
import { DebugEditorModelFactory } from '@theia/debug/lib/browser/editor/debug-editor-model';
import { StorageWrapper } from './storage-wrapper';
import { NotificationManager } from './theia/messages/notifications-manager';
import { NotificationManager as TheiaNotificationManager } from '@theia/messages/lib/browser/notifications-manager';
import { NotificationsRenderer as TheiaNotificationsRenderer } from '@theia/messages/lib/browser/notifications-renderer';
import { NotificationsRenderer } from './theia/messages/notifications-renderer';

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

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

bind(OutputServiceImpl).toSelf().inSingletonScope().onActivation(({ container }, outputService) => {
WebSocketConnectionProvider.createProxy(container, OutputServicePath, outputService);
return outputService;
bind(ResponseServiceImpl).toSelf().inSingletonScope().onActivation(({ container }, responseService) => {
WebSocketConnectionProvider.createProxy(container, ResponseServicePath, responseService);
return responseService;
});
bind(OutputService).toService(OutputServiceImpl);
bind(ResponseService).toService(ResponseServiceImpl);

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

bind(StorageWrapper).toSelf().inSingletonScope();
bind(CommandContribution).toService(StorageWrapper);

bind(NotificationManager).toSelf().inSingletonScope();
rebind(TheiaNotificationManager).toService(NotificationManager);
bind(NotificationsRenderer).toSelf().inSingletonScope();
rebind(TheiaNotificationsRenderer).toService(NotificationsRenderer);
});
20 changes: 12 additions & 8 deletions arduino-ide-extension/src/browser/boards/boards-auto-installer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { FrontendApplicationContribution } from '@theia/core/lib/browser/fronten
import { BoardsService, BoardsPackage } from '../../common/protocol/boards-service';
import { BoardsServiceProvider } from './boards-service-provider';
import { BoardsListWidgetFrontendContribution } from './boards-widget-frontend-contribution';
import { InstallationProgressDialog } from '../widgets/progress-dialog';
import { BoardsConfig } from './boards-config';
import { Installable } from '../../common/protocol';
import { ResponseServiceImpl } from '../response-service-impl';

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

@inject(ResponseServiceImpl)
protected readonly responseService: ResponseServiceImpl;

@inject(BoardsListWidgetFrontendContribution)
protected readonly boardsManagerFrontendContribution: BoardsListWidgetFrontendContribution;

Expand All @@ -42,13 +46,13 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
// tslint:disable-next-line:max-line-length
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 => {
if (answer === 'Yes') {
const dialog = new InstallationProgressDialog(candidate.name, candidate.availableVersions[0]);
dialog.open();
try {
await this.boardsService.install({ item: candidate });
} finally {
dialog.close();
}
await Installable.installWithProgress({
installable: this.boardsService,
item: candidate,
messageService: this.messageService,
responseService: this.responseService,
version: candidate.availableVersions[0]
});
}
if (answer) {
this.boardsManagerFrontendContribution.openView({ reveal: true }).then(widget => widget.refresh(candidate.name.toLocaleLowerCase()));
Expand Down
11 changes: 8 additions & 3 deletions arduino-ide-extension/src/browser/boards/boards-list-widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,14 @@ export class BoardsListWidget extends ListWidget<BoardsPackage> {
]);
}

async install({ item, version }: { item: BoardsPackage; version: string; }): Promise<void> {
await super.install({ item, version });
this.messageService.info(`Successfully installed platform ${item.name}:${version}.`, { timeout: 3000 });
protected async install({ item, progressId, version }: { item: BoardsPackage, progressId: string, version: string; }): Promise<void> {
await super.install({ item, progressId, version });
this.messageService.info(`Successfully installed platform ${item.name}:${version}`, { timeout: 3000 });
}

protected async uninstall({ item, progressId }: { item: BoardsPackage, progressId: string }): Promise<void> {
await super.uninstall({ item, progressId });
this.messageService.info(`Successfully uninstalled platform ${item.name}:${item.installedVersion}`, { timeout: 3000 });
}

}
26 changes: 15 additions & 11 deletions arduino-ide-extension/src/browser/contributions/add-zip-library.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { inject, injectable } from 'inversify';
import { remote } from 'electron';
import URI from '@theia/core/lib/common/uri';
import { ConfirmDialog } from '@theia/core/lib/browser/dialogs';
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
import { ArduinoMenus } from '../menu/arduino-menus';
import { ResponseServiceImpl } from '../response-service-impl';
import { Installable, LibraryService } from '../../common/protocol';
import { SketchContribution, Command, CommandRegistry, MenuModelRegistry } from './contribution';
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
import URI from '@theia/core/lib/common/uri';
import { InstallationProgressDialog } from '../widgets/progress-dialog';
import { LibraryService } from '../../common/protocol';
import { ConfirmDialog } from '@theia/core/lib/browser';

@injectable()
export class AddZipLibrary extends SketchContribution {

@inject(EnvVariablesServer)
protected readonly envVariableServer: EnvVariablesServer;

@inject(ResponseServiceImpl)
protected readonly responseService: ResponseServiceImpl;

@inject(LibraryService)
protected readonly libraryService: LibraryService;

Expand Down Expand Up @@ -69,11 +72,14 @@ export class AddZipLibrary extends SketchContribution {
}

private async doInstall(zipUri: string, overwrite?: boolean): Promise<void> {
const dialog = new InstallationProgressDialog('Installing library', 'zip');
try {
this.outputChannelManager.getChannel('Arduino').clear();
dialog.open();
await this.libraryService.installZip({ zipUri, overwrite });
await Installable.doWithProgress({
messageService: this.messageService,
progressText: `Processing ${new URI(zipUri).path.base}`,
responseService: this.responseService,
run: () => this.libraryService.installZip({ zipUri, overwrite })
});
this.messageService.info(`Successfully installed library from ${new URI(zipUri).path.base} archive`, { timeout: 3000 });
} catch (error) {
if (error instanceof Error) {
const match = error.message.match(/library (.*?) already installed/);
Expand All @@ -88,8 +94,6 @@ export class AddZipLibrary extends SketchContribution {
}
this.messageService.error(error.toString());
throw error;
} finally {
dialog.close();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"colors": {
"list.highlightForeground": "#005c5f",
"list.activeSelectionBackground": "#005c5f",
"progressBar.background": "#005c5f",
"editor.background": "#ffffff",
"editorCursor.foreground": "#434f54",
"editor.foreground": "#434f54",
Expand Down
11 changes: 8 additions & 3 deletions arduino-ide-extension/src/browser/library/library-list-widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
]);
}

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

if (typeof installDependencies === 'boolean') {
await this.service.install({ item, version, installDependencies });
this.messageService.info(`Successfully installed library ${item.name}:${version}.`, { timeout: 3000 });
await this.service.install({ item, version, progressId, installDependencies });
this.messageService.info(`Successfully installed library ${item.name}:${version}`, { timeout: 3000 });
}
}

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

}

class MessageBoxDialog extends AbstractDialog<MessageBoxDialog.Result> {
Expand Down
22 changes: 0 additions & 22 deletions arduino-ide-extension/src/browser/output-service-impl.ts

This file was deleted.

34 changes: 34 additions & 0 deletions arduino-ide-extension/src/browser/response-service-impl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { inject, injectable } from 'inversify';
import { Emitter } from '@theia/core/lib/common/event';
import { OutputContribution } from '@theia/output/lib/browser/output-contribution';
import { OutputChannelManager } from '@theia/output/lib/common/output-channel';
import { ResponseService, OutputMessage, ProgressMessage } from '../common/protocol/response-service';

@injectable()
export class ResponseServiceImpl implements ResponseService {

@inject(OutputContribution)
protected outputContribution: OutputContribution;

@inject(OutputChannelManager)
protected outputChannelManager: OutputChannelManager;

protected readonly progressDidChangeEmitter = new Emitter<ProgressMessage>();
readonly onProgressDidChange = this.progressDidChangeEmitter.event;

appendToOutput(message: OutputMessage): void {
const { chunk } = message;
const channel = this.outputChannelManager.getChannel('Arduino');
channel.show({ preserveFocus: true });
channel.append(chunk);
}

clearArduinoChannel(): void {
this.outputChannelManager.getChannel('Arduino').clear();
}

reportProgress(progress: ProgressMessage): void {
this.progressDidChangeEmitter.fire(progress);
}

}
17 changes: 17 additions & 0 deletions arduino-ide-extension/src/browser/style/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,20 @@ button.theia-button.secondary {
button.theia-button.main {
color: var(--theia-button-foreground);
}

/* To make the progress-bar slightly thicker, and use the color from the status bar */
.theia-progress-bar-container {
width: 100%;
height: 4px;
}

.theia-progress-bar {
height: 4px;
width: 3%;
animation: progress-animation 1.3s 0s infinite cubic-bezier(0.645, 0.045, 0.355, 1);
}

.theia-notification-item-progressbar {
height: 4px;
width: 66%;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as React from 'react';
import { NotificationComponent } from './notification-component';
import { NotificationCenterComponent as TheiaNotificationCenterComponent } from '@theia/messages/lib/browser/notification-center-component'

const PerfectScrollbar = require('react-perfect-scrollbar');

export class NotificationCenterComponent extends TheiaNotificationCenterComponent {

render(): React.ReactNode {
const empty = this.state.notifications.length === 0;
const title = empty ? 'NO NEW NOTIFICATIONS' : 'NOTIFICATIONS';
return (
<div className={`theia-notifications-container theia-notification-center ${this.state.visibilityState === 'center' ? 'open' : 'closed'}`}>
<div className='theia-notification-center-header'>
<div className='theia-notification-center-header-title'>{title}</div>
<div className='theia-notification-center-header-actions'>
<ul className='theia-notification-actions'>
<li className='collapse' title='Hide Notification Center' onClick={this.onHide} />
<li className='clear-all' title='Clear All' onClick={this.onClearAll} />
</ul>
</div>
</div>
<PerfectScrollbar className='theia-notification-list-scroll-container'>
<div className='theia-notification-list'>
{this.state.notifications.map(notification =>
<NotificationComponent key={notification.messageId} notification={notification} manager={this.props.manager} />
)}
</div>
</PerfectScrollbar>
</div>
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import * as React from 'react';
import { NotificationComponent as TheiaNotificationComponent } from '@theia/messages/lib/browser/notification-component'

export class NotificationComponent extends TheiaNotificationComponent {

render(): React.ReactNode {
const { messageId, message, type, collapsed, expandable, source, actions } = this.props.notification;
return (<div key={messageId} className='theia-notification-list-item'>
<div className={`theia-notification-list-item-content ${collapsed ? 'collapsed' : ''}`}>
<div className='theia-notification-list-item-content-main'>
<div className={`theia-notification-icon theia-notification-icon-${type}`} />
<div className='theia-notification-message'>
<span dangerouslySetInnerHTML={{ __html: message }} onClick={this.onMessageClick} />
</div>
<ul className='theia-notification-actions'>
{expandable && (
<li className={collapsed ? 'expand' : 'collapse'} title={collapsed ? 'Expand' : 'Collapse'}
data-message-id={messageId} onClick={this.onToggleExpansion} />
)}
{!this.isProgress && (<li className='clear' title='Clear' data-message-id={messageId} onClick={this.onClear} />)}
</ul>
</div>
{(source || !!actions.length) && (
<div className='theia-notification-list-item-content-bottom'>
<div className='theia-notification-source'>
{source && (<span>{source}</span>)}
</div>
<div className='theia-notification-buttons'>
{actions && actions.map((action, index) => (
<button key={messageId + `-action-${index}`} className='theia-button'
data-message-id={messageId} data-action={action}
onClick={this.onAction}>
{action}
</button>
))}
</div>
</div>
)}
</div>
{this.renderProgressBar()}
</div>);
}

private renderProgressBar(): React.ReactNode {
if (!this.isProgress) {
return undefined;
}
if (!Number.isNaN(this.props.notification.progress)) {
return <div className='theia-notification-item-progress'>
<div className='theia-notification-item-progressbar' style={{ width: `${this.props.notification.progress}%` }} />
</div>;
}
return <div className='theia-progress-bar-container'>
<div className='theia-progress-bar' />
</div>
}

private get isProgress(): boolean {
return typeof this.props.notification.progress === 'number';
}

}
Loading