ATL-786: Progress indication for install.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
Akos Kitta
2021-04-08 15:18:44 +02:00
committed by Akos Kitta
parent 8071298598
commit 9aff90b0af
30 changed files with 557 additions and 219 deletions

View File

@@ -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';
@@ -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;

View File

@@ -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';
@@ -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');
@@ -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);
@@ -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);
});

View File

@@ -1,11 +1,12 @@
import { injectable, inject } from 'inversify';
import { MessageService } from '@theia/core/lib/common/message-service';
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
import { BoardsService, BoardsPackage } from '../../common/protocol/boards-service';
import { BoardsService, BoardsPackage, Board } 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
@@ -23,9 +24,15 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
@inject(BoardsServiceProvider)
protected readonly boardsServiceClient: BoardsServiceProvider;
@inject(ResponseServiceImpl)
protected readonly responseService: ResponseServiceImpl;
@inject(BoardsListWidgetFrontendContribution)
protected readonly boardsManagerFrontendContribution: BoardsListWidgetFrontendContribution;
// Workaround for https://github.com/eclipse-theia/theia/issues/9349
protected notifications: Board[] = [];
onStart(): void {
this.boardsServiceClient.onBoardsConfigChanged(this.ensureCoreExists.bind(this));
this.ensureCoreExists(this.boardsServiceClient.boardsConfig);
@@ -33,22 +40,29 @@ export class BoardsAutoInstaller implements FrontendApplicationContribution {
protected ensureCoreExists(config: BoardsConfig.Config): void {
const { selectedBoard } = config;
if (selectedBoard) {
if (selectedBoard && !this.notifications.find(board => Board.sameAs(board, selectedBoard))) {
this.notifications.push(selectedBoard);
this.boardsService.search({}).then(packages => {
const candidates = packages
.filter(pkg => BoardsPackage.contains(selectedBoard, pkg))
.filter(({ installable, installedVersion }) => installable && !installedVersion);
for (const candidate of candidates) {
const candidate = candidates[0];
if (candidate) {
// 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 => {
const index = this.notifications.findIndex(board => Board.sameAs(board, selectedBoard));
if (index !== -1) {
this.notifications.splice(index, 1);
}
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]
});
return
}
if (answer) {
this.boardsManagerFrontendContribution.openView({ reveal: true }).then(widget => widget.refresh(candidate.name.toLocaleLowerCase()));

View File

@@ -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 });
}
}

View File

@@ -1,12 +1,12 @@
import { inject, injectable } from 'inversify';
import { remote } from 'electron';
import { ArduinoMenus } from '../menu/arduino-menus';
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';
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';
@injectable()
export class AddZipLibrary extends SketchContribution {
@@ -14,6 +14,9 @@ export class AddZipLibrary extends SketchContribution {
@inject(EnvVariablesServer)
protected readonly envVariableServer: EnvVariablesServer;
@inject(ResponseServiceImpl)
protected readonly responseService: ResponseServiceImpl;
@inject(LibraryService)
protected readonly libraryService: LibraryService;
@@ -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/);
@@ -88,8 +94,6 @@ export class AddZipLibrary extends SketchContribution {
}
this.messageService.error(error.toString());
throw error;
} finally {
dialog.close();
}
}

View File

@@ -82,6 +82,7 @@
"colors": {
"list.highlightForeground": "#005c5f",
"list.activeSelectionBackground": "#005c5f",
"progressBar.background": "#005c5f",
"editor.background": "#ffffff",
"editorCursor.foreground": "#434f54",
"editor.foreground": "#434f54",

View File

@@ -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) {
@@ -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> {

View File

@@ -1,22 +0,0 @@
import { inject, injectable } from 'inversify';
import { OutputContribution } from '@theia/output/lib/browser/output-contribution';
import { OutputChannelManager } from '@theia/output/lib/common/output-channel';
import { OutputService, OutputMessage } from '../common/protocol/output-service';
@injectable()
export class OutputServiceImpl implements OutputService {
@inject(OutputContribution)
protected outputContribution: OutputContribution;
@inject(OutputChannelManager)
protected outputChannelManager: OutputChannelManager;
append(message: OutputMessage): void {
const { chunk } = message;
const channel = this.outputChannelManager.getChannel('Arduino');
channel.show({ preserveFocus: true });
channel.append(chunk);
}
}

View File

@@ -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);
}
}

View File

@@ -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%;
}

View File

@@ -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>
);
}
}

View File

@@ -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';
}
}

View File

@@ -0,0 +1,17 @@
import * as React from 'react';
import { NotificationComponent } from './notification-component';
import { NotificationToastsComponent as TheiaNotificationToastsComponent } from '@theia/messages/lib/browser/notification-toasts-component'
export class NotificationToastsComponent extends TheiaNotificationToastsComponent {
render(): React.ReactNode {
return (
<div className={`theia-notifications-container theia-notification-toasts ${this.state.visibilityState === 'toasts' ? 'open' : 'closed'}`}>
<div className='theia-notification-list'>
{this.state.toasts.map(notification => <NotificationComponent key={notification.messageId} notification={notification} manager={this.props.manager} />)}
</div>
</div>
);
}
}

View File

@@ -0,0 +1,37 @@
import { injectable } from 'inversify';
import { CancellationToken } from '@theia/core/lib/common/cancellation';
import { ProgressMessage, ProgressUpdate } from '@theia/core/lib/common/message-service-protocol';
import { NotificationManager as TheiaNotificationManager } from '@theia/messages/lib/browser/notifications-manager';
@injectable()
export class NotificationManager extends TheiaNotificationManager {
async reportProgress(messageId: string, update: ProgressUpdate, originalMessage: ProgressMessage, cancellationToken: CancellationToken): Promise<void> {
const notification = this.find(messageId);
if (!notification) {
return;
}
if (cancellationToken.isCancellationRequested) {
this.clear(messageId);
} else {
notification.message = originalMessage.text && update.message ? `${originalMessage.text}: ${update.message}` :
originalMessage.text || update?.message || notification.message;
// Unlike in Theia, we allow resetting the progress monitor to NaN to enforce unknown progress.
const candidate = this.toPlainProgress(update);
notification.progress = typeof candidate === 'number' ? candidate : notification.progress;
}
this.fireUpdatedEvent();
}
protected toPlainProgress(update: ProgressUpdate): number | undefined {
if (!update.work) {
return undefined;
}
if (Number.isNaN(update.work.done) || Number.isNaN(update.work.total)) {
return Number.NaN; // This should trigger the unknown monitor.
}
return Math.min(update.work.done / update.work.total * 100, 100);
}
}

View File

@@ -0,0 +1,18 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { injectable } from 'inversify';
import { NotificationCenterComponent } from './notification-center-component';
import { NotificationToastsComponent } from './notification-toasts-component';
import { NotificationsRenderer as TheiaNotificationsRenderer } from '@theia/messages/lib/browser/notifications-renderer';
@injectable()
export class NotificationsRenderer extends TheiaNotificationsRenderer {
protected render(): void {
ReactDOM.render(<div>
<NotificationToastsComponent manager={this.manager} corePreferences={this.corePreferences} />
<NotificationCenterComponent manager={this.manager} />
</div>, this.container);
}
}

View File

@@ -3,16 +3,15 @@ import debounce = require('lodash.debounce');
import { Event } from '@theia/core/lib/common/event';
import { CommandService } from '@theia/core/lib/common/command';
import { MessageService } from '@theia/core/lib/common/message-service';
import { OutputCommands } from '@theia/output/lib/browser/output-commands';
import { ConfirmDialog } from '@theia/core/lib/browser/dialogs';
import { Searchable } from '../../../common/protocol/searchable';
import { Installable } from '../../../common/protocol/installable';
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
import { InstallationProgressDialog, UninstallationProgressDialog } from '../progress-dialog';
import { SearchBar } from './search-bar';
import { ListWidget } from './list-widget';
import { ComponentList } from './component-list';
import { ListItemRenderer } from './list-item-renderer';
import { ResponseServiceImpl } from '../../response-service-impl';
export class FilterableListContainer<T extends ArduinoComponent> extends React.Component<FilterableListContainer.Props<T>, FilterableListContainer.State<T>> {
@@ -84,20 +83,14 @@ export class FilterableListContainer<T extends ArduinoComponent> extends React.C
}
protected async install(item: T, version: Installable.Version): Promise<void> {
const { install, searchable, itemLabel } = this.props;
const dialog = new InstallationProgressDialog(itemLabel(item), version);
try {
dialog.open();
await this.clearArduinoChannel();
await install({ item, version });
const items = await searchable.search({ query: this.state.filterText });
this.setState({ items: this.sort(items) });
} catch (error) {
this.props.messageService.error(error instanceof Error ? error.message : String(error));
throw error;
} finally {
dialog.close();
}
const { install, searchable } = this.props;
await Installable.doWithProgress({
...this.props,
progressText: `Processing ${item.name}:${version}`,
run: ({ progressId }) => install({ item, progressId, version })
});
const items = await searchable.search({ query: this.state.filterText });
this.setState({ items: this.sort(items) });
}
protected async uninstall(item: T): Promise<void> {
@@ -110,21 +103,14 @@ export class FilterableListContainer<T extends ArduinoComponent> extends React.C
if (!ok) {
return;
}
const { uninstall, searchable, itemLabel } = this.props;
const dialog = new UninstallationProgressDialog(itemLabel(item));
try {
await this.clearArduinoChannel();
dialog.open();
await uninstall({ item });
const items = await searchable.search({ query: this.state.filterText });
this.setState({ items: this.sort(items) });
} finally {
dialog.close();
}
}
private async clearArduinoChannel(): Promise<void> {
return this.props.commandService.executeCommand(OutputCommands.CLEAR.id, { name: 'Arduino' });
const { uninstall, searchable } = this.props;
await Installable.doWithProgress({
...this.props,
progressText: `Processing ${item.name}${item.installedVersion ? `:${item.installedVersion}` : ''}`,
run: ({ progressId }) => uninstall({ item, progressId })
});
const items = await searchable.search({ query: this.state.filterText });
this.setState({ items: this.sort(items) });
}
}
@@ -139,9 +125,10 @@ export namespace FilterableListContainer {
readonly resolveContainer: (element: HTMLElement) => void;
readonly resolveFocus: (element: HTMLElement | undefined) => void;
readonly filterTextChangeEvent: Event<string | undefined>;
readonly install: ({ item, version }: { item: T, version: Installable.Version }) => Promise<void>;
readonly uninstall: ({ item }: { item: T }) => Promise<void>;
readonly messageService: MessageService;
readonly responseService: ResponseServiceImpl;
readonly install: ({ item, progressId, version }: { item: T, progressId: string, version: Installable.Version }) => Promise<void>;
readonly uninstall: ({ item, progressId }: { item: T, progressId: string }) => Promise<void>;
readonly commandService: CommandService;
}

View File

@@ -1,5 +1,6 @@
import * as React from 'react';
import { injectable, postConstruct, inject } from 'inversify';
import { Widget } from '@phosphor/widgets';
import { Message } from '@phosphor/messaging';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { Emitter } from '@theia/core/lib/common/event';
@@ -7,12 +8,11 @@ import { MaybePromise } from '@theia/core/lib/common/types';
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
import { CommandService } from '@theia/core/lib/common/command';
import { MessageService } from '@theia/core/lib/common/message-service';
import { Installable } from '../../../common/protocol/installable';
import { Searchable } from '../../../common/protocol/searchable';
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
import { Installable, Searchable, ArduinoComponent } from '../../../common/protocol';
import { FilterableListContainer } from './filterable-list-container';
import { ListItemRenderer } from './list-item-renderer';
import { NotificationCenter } from '../../notification-center';
import { ResponseServiceImpl } from '../../response-service-impl';
@injectable()
export abstract class ListWidget<T extends ArduinoComponent> extends ReactWidget {
@@ -23,6 +23,9 @@ export abstract class ListWidget<T extends ArduinoComponent> extends ReactWidget
@inject(CommandService)
protected readonly commandService: CommandService;
@inject(ResponseServiceImpl)
protected readonly responseService: ResponseServiceImpl;
@inject(NotificationCenter)
protected readonly notificationCenter: NotificationCenter;
@@ -63,26 +66,31 @@ export abstract class ListWidget<T extends ArduinoComponent> extends ReactWidget
return this.deferredContainer.promise;
}
protected onActivateRequest(msg: Message): void {
super.onActivateRequest(msg);
protected onActivateRequest(message: Message): void {
super.onActivateRequest(message);
(this.focusNode || this.node).focus();
}
protected onUpdateRequest(msg: Message): void {
super.onUpdateRequest(msg);
protected onUpdateRequest(message: Message): void {
super.onUpdateRequest(message);
this.render();
}
protected onResize(message: Widget.ResizeMessage): void {
super.onResize(message);
this.updateScrollBar();
}
protected onFocusResolved = (element: HTMLElement | undefined) => {
this.focusNode = element;
};
protected async install({ item, version }: { item: T, version: Installable.Version }): Promise<void> {
return this.options.installable.install({ item, version });
protected async install({ item, progressId, version }: { item: T, progressId: string, version: Installable.Version }): Promise<void> {
return this.options.installable.install({ item, progressId, version });
}
protected async uninstall({ item }: { item: T }): Promise<void> {
return this.options.installable.uninstall({ item });
protected async uninstall({ item, progressId }: { item: T, progressId: string }): Promise<void> {
return this.options.installable.uninstall({ item, progressId });
}
render(): React.ReactNode {
@@ -97,7 +105,8 @@ export abstract class ListWidget<T extends ArduinoComponent> extends ReactWidget
itemRenderer={this.options.itemRenderer}
filterTextChangeEvent={this.filterTextChangeEmitter.event}
messageService={this.messageService}
commandService={this.commandService} />;
commandService={this.commandService}
responseService={this.responseService} />;
}
/**

View File

@@ -1,23 +0,0 @@
import { AbstractDialog } from '@theia/core/lib/browser';
export class InstallationProgressDialog extends AbstractDialog<undefined> {
readonly value = undefined;
constructor(componentName: string, version: string) {
super({ title: 'Installation in progress' });
this.contentNode.textContent = `Installing ${componentName} [${version}]. Please wait...`;
}
}
export class UninstallationProgressDialog extends AbstractDialog<undefined> {
readonly value = undefined;
constructor(componentName: string) {
super({ title: 'Uninstallation in progress' });
this.contentNode.textContent = `Uninstalling ${componentName}. Please wait...`;
}
}