diff --git a/.travis.yml b/.travis.yml index 6842230848d65..5eca6d0833415 100644 --- a/.travis.yml +++ b/.travis.yml @@ -52,6 +52,7 @@ cache: - packages/preferences/node_modules - packages/preview/node_modules - packages/process/node_modules + - packages/progress-monitor/node_modules - packages/python/node_modules - packages/scm/node_modules - packages/search-in-workspace/node_modules diff --git a/examples/browser/package.json b/examples/browser/package.json index c44e0e0907fee..dc5a6259c528a 100644 --- a/examples/browser/package.json +++ b/examples/browser/package.json @@ -47,6 +47,7 @@ "@theia/preferences": "^0.7.0", "@theia/preview": "^0.7.0", "@theia/process": "^0.7.0", + "@theia/progress-monitor": "^0.7.0", "@theia/python": "^0.7.0", "@theia/scm": "^0.7.0", "@theia/search-in-workspace": "^0.7.0", diff --git a/examples/electron/package.json b/examples/electron/package.json index cac68f0c3de25..4f37328764210 100644 --- a/examples/electron/package.json +++ b/examples/electron/package.json @@ -46,6 +46,7 @@ "@theia/preview": "^0.7.0", "@theia/process": "^0.7.0", "@theia/python": "^0.7.0", + "@theia/progress-monitor": "^0.7.0", "@theia/search-in-workspace": "^0.7.0", "@theia/task": "^0.7.0", "@theia/terminal": "^0.7.0", diff --git a/packages/core/src/browser/status-bar/status-bar.tsx b/packages/core/src/browser/status-bar/status-bar.tsx index c88b350032681..12303008ce25a 100644 --- a/packages/core/src/browser/status-bar/status-bar.tsx +++ b/packages/core/src/browser/status-bar/status-bar.tsx @@ -65,6 +65,8 @@ export interface StatusBar { setColor(color?: string): Promise; setElement(id: string, entry: StatusBarEntry): Promise; removeElement(id: string): Promise; + hasElement(id: string): Promise; + getElementByClass(className: string): Promise>; } @injectable() @@ -95,12 +97,22 @@ export class StatusBarImpl extends ReactWidget implements StatusBar { this.update(); } + async hasElement(id: string): Promise { + await this.ready; + return this.entries.has(id); + } + async removeElement(id: string): Promise { await this.ready; this.entries.delete(id); this.update(); } + async getElementByClass(className: string) { + await this.ready; + return Object.assign({}, this.node.getElementsByClassName(className)); + } + async setBackgroundColor(color?: string): Promise { await this.ready; this.internalSetBackgroundColor(color); diff --git a/packages/git/package.json b/packages/git/package.json index 0b71f0fd4e412..846b2cf570b98 100644 --- a/packages/git/package.json +++ b/packages/git/package.json @@ -7,6 +7,7 @@ "@theia/editor": "^0.7.0", "@theia/filesystem": "^0.7.0", "@theia/languages": "^0.7.0", + "@theia/progress-monitor": "^0.7.0", "@theia/navigator": "^0.7.0", "@theia/scm": "^0.7.0", "@theia/workspace": "^0.7.0", diff --git a/packages/git/src/browser/git-quick-open-service.ts b/packages/git/src/browser/git-quick-open-service.ts index 973a97e33c1e6..29666d8897592 100644 --- a/packages/git/src/browser/git-quick-open-service.ts +++ b/packages/git/src/browser/git-quick-open-service.ts @@ -20,6 +20,7 @@ import { QuickOpenService, QuickOpenOptions } from '@theia/core/lib/browser/quic import { Git, Repository, Branch, BranchType, Tag, Remote, StashEntry } from '../common'; import { GitRepositoryProvider } from './git-repository-provider'; import { MessageService } from '@theia/core/lib/common/message-service'; +import { ProgressService } from '@theia/progress-monitor/lib/browser/progress-service'; import URI from '@theia/core/lib/common/uri'; import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; import { FileSystem } from '@theia/filesystem/lib/common'; @@ -45,6 +46,7 @@ export class GitQuickOpenService { @inject(GitRepositoryProvider) protected readonly repositoryProvider: GitRepositoryProvider, @inject(QuickOpenService) protected readonly quickOpenService: QuickOpenService, @inject(MessageService) protected readonly messageService: MessageService, + @inject(ProgressService) protected readonly progressService: ProgressService, @inject(WorkspaceService) protected readonly workspaceService: WorkspaceService, @inject(FileSystem) protected readonly fileSystem: FileSystem ) { } @@ -77,11 +79,27 @@ export class GitQuickOpenService { dynamicItems.push(new SingleStringInputOpenItem( `Clone the Git repository: ${lookFor}. ${suffix}`, async () => { + const progressReport = { + id: `git-clone-${lookFor}`, + task: 'Cloning git repositories', + location: lookFor, + totalWork: 1, + workDone: 0, + complete: false + }; + gitQuickOpenService.progressService.addOrUpdateContribution(progressReport); try { - await gitQuickOpenService.git.clone(lookFor, { localUri: await gitQuickOpenService.buildDefaultProjectPath(gitCloneLocalTargetFolder, lookFor) }); + const progressCallback = (progress: Git.CloneProgress) => { + progressReport.workDone = progress.value; + }; + const localUri = await gitQuickOpenService.buildDefaultProjectPath(gitCloneLocalTargetFolder, lookFor); + await gitQuickOpenService.git.clone(lookFor, { localUri }, progressCallback); + progressReport.workDone = progressReport.totalWork; } catch (error) { gitQuickOpenService.gitErrorHandler.handleError(error); } + progressReport.complete = true; + gitQuickOpenService.progressService.addOrUpdateContribution(progressReport); } )); } diff --git a/packages/git/src/common/git.ts b/packages/git/src/common/git.ts index b8e6cfefd0fcb..53264e8197706 100644 --- a/packages/git/src/common/git.ts +++ b/packages/git/src/common/git.ts @@ -33,6 +33,18 @@ export const Git = Symbol('Git'); export namespace Git { + export interface CloneProgress { + readonly kind: 'clone'; + /** + * The progress of the operation, represented as a fraction between 0 and 1. + */ + readonly value: number; + /** + * An informative text for user consumption indicating the current operation state. + */ + readonly title: string; + } + /** * Options for various Git commands. */ @@ -597,7 +609,7 @@ export interface Git extends Disposable { * @param remoteUrl the URL of the remote. * @param options the clone options. */ - clone(remoteUrl: string, options: Git.Options.Clone): Promise; + clone(remoteUrl: string, options: Git.Options.Clone, progressCallback?: (progress: Git.CloneProgress) => void): Promise; /** * Resolves to an array of repositories discovered in the workspace given with the workspace root URI. diff --git a/packages/git/src/node/dugite-git.ts b/packages/git/src/node/dugite-git.ts index b6ef4885970f4..9dca3dc801a85 100644 --- a/packages/git/src/node/dugite-git.ts +++ b/packages/git/src/node/dugite-git.ts @@ -333,11 +333,11 @@ export class DugiteGit implements Git { this.gitInit.dispose(); } - async clone(remoteUrl: string, options: Git.Options.Clone): Promise { + async clone(remoteUrl: string, options: Git.Options.Clone, progressCallback?: (progress: Git.CloneProgress) => void): Promise { await this.ready.promise; const { localUri, branch } = options; const [exec, env] = await Promise.all([this.execProvider.exec(), this.gitEnv.promise]); - await clone(remoteUrl, this.getFsPath(localUri), { branch }, { exec, env }); + await clone(remoteUrl, this.getFsPath(localUri), { branch }, { exec, env }, progressCallback); return { localUri }; } diff --git a/packages/java/package.json b/packages/java/package.json index 5b7931ee4b344..8f4b17dbae2d2 100644 --- a/packages/java/package.json +++ b/packages/java/package.json @@ -7,6 +7,7 @@ "@theia/editor": "^0.7.0", "@theia/languages": "^0.7.0", "@theia/monaco": "^0.7.0", + "@theia/progress-monitor": "^0.7.0", "@types/glob": "^5.0.30", "@types/tar": "4.0.0", "glob": "^7.1.2", diff --git a/packages/java/src/browser/java-client-contribution.ts b/packages/java/src/browser/java-client-contribution.ts index aac057fa99c1e..2dc8eeb8193ff 100644 --- a/packages/java/src/browser/java-client-contribution.ts +++ b/packages/java/src/browser/java-client-contribution.ts @@ -32,6 +32,8 @@ import { LanguageClientOptions } from '@theia/languages/lib/browser'; import { JAVA_LANGUAGE_ID, JAVA_LANGUAGE_NAME, JavaStartParams } from '../common'; +import { ProgressReportNotification } from './java-protocol'; +import { ProgressReport, ProgressService } from '@theia/progress-monitor/lib/browser'; import { ActionableNotification, ActionableMessage, @@ -60,7 +62,8 @@ export class JavaClientContribution extends BaseLanguageClientContribution { @inject(Window) protected readonly window: Window, @inject(CommandService) protected readonly commandService: CommandService, @inject(StatusBar) protected readonly statusBar: StatusBar, - @inject(SemanticHighlightingService) protected readonly semanticHighlightingService: SemanticHighlightingService + @inject(SemanticHighlightingService) protected readonly semanticHighlightingService: SemanticHighlightingService, + @inject(ProgressService) protected readonly progressMonitorService: ProgressService ) { super(workspace, languages, languageClientFactory); } @@ -81,6 +84,7 @@ export class JavaClientContribution extends BaseLanguageClientContribution { languageClient.onNotification(ActionableNotification.type, this.showActionableMessage.bind(this)); languageClient.onNotification(StatusNotification.type, this.showStatusMessage.bind(this)); languageClient.onRequest(ExecuteClientCommand.type, params => this.commandService.executeCommand(params.command, ...params.arguments)); + languageClient.onNotification(ProgressReportNotification.type, this.showProgressReportNotifications.bind(this)); super.onReady(languageClient); } @@ -107,6 +111,11 @@ export class JavaClientContribution extends BaseLanguageClientContribution { }, 500); } + protected showProgressReportNotifications(progressReport: ProgressReport) { + const javaProgressReport = Object.assign(progressReport, { location: 'Java' }); + this.progressMonitorService.addOrUpdateContribution(javaProgressReport); + } + protected showActionableMessage(message: ActionableMessage): void { const items = message.commands || []; this.window.showMessage(message.severity, message.message, ...items).then(command => { @@ -121,7 +130,8 @@ export class JavaClientContribution extends BaseLanguageClientContribution { const options = super.createOptions(); options.initializationOptions = { extendedClientCapabilities: { - classFileContentsSupport: true + classFileContentsSupport: true, + progressReportProvider: true } }; return options; diff --git a/packages/java/src/browser/java-protocol.ts b/packages/java/src/browser/java-protocol.ts index 9e332b10d8333..ddfe29cdbe50a 100644 --- a/packages/java/src/browser/java-protocol.ts +++ b/packages/java/src/browser/java-protocol.ts @@ -16,6 +16,7 @@ import { RequestType, NotificationType } from 'vscode-jsonrpc'; import { TextDocumentIdentifier, Command, MessageType, ExecuteCommandParams } from '@theia/languages/lib/browser'; +import { ProgressReport } from '@theia/progress-monitor/lib/browser'; export interface StatusReport { message: string; @@ -56,3 +57,7 @@ export namespace CompileWorkspaceRequest { export namespace ExecuteClientCommand { export const type = new RequestType('workspace/executeClientCommand'); } + +export namespace ProgressReportNotification { + export const type = new NotificationType('language/progressReport'); +} diff --git a/packages/progress-monitor/README.md b/packages/progress-monitor/README.md new file mode 100644 index 0000000000000..d93f96a14b161 --- /dev/null +++ b/packages/progress-monitor/README.md @@ -0,0 +1,10 @@ +# Theia - Progress Monitor Extension + +Contributes an progress monitor widget to Theia that allows users to show status of long running processes. +This status can come from either the backend i.e. language servers or from the front end. + +See [here](https://github.com/theia-ide/theia) for a detailed documentation on Theia. + +## License +- [Eclipse Public License 2.0](http://www.eclipse.org/legal/epl-2.0/) +- [δΈ€ (Secondary) GNU General Public License, version 2 with the GNU Classpath Exception](https://projects.eclipse.org/license/secondary-gpl-2.0-cp) diff --git a/packages/progress-monitor/compile.tsconfig.json b/packages/progress-monitor/compile.tsconfig.json new file mode 100644 index 0000000000000..18b90d15df3c5 --- /dev/null +++ b/packages/progress-monitor/compile.tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../configs/base.tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib" + }, + "include": [ + "src" + ] +} diff --git a/packages/progress-monitor/package.json b/packages/progress-monitor/package.json new file mode 100644 index 0000000000000..0089846fb7782 --- /dev/null +++ b/packages/progress-monitor/package.json @@ -0,0 +1,47 @@ +{ + "name": "@theia/progress-monitor", + "version": "0.7.0", + "description": "Theia - Progress Monitor", + "dependencies": { + "@theia/core": "^0.7.0", + "@theia/editor": "^0.7.0" + }, + "publishConfig": { + "access": "public" + }, + "theiaExtensions": [ + { + "frontend": "lib/browser/progress-monitor-frontend-module" + } + ], + "keywords": [ + "theia-extension" + ], + "license": "EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0", + "repository": { + "type": "git", + "url": "https://github.com/theia-ide/theia.git" + }, + "bugs": { + "url": "https://github.com/theia-ide/theia/issues" + }, + "homepage": "https://github.com/theia-ide/theia", + "files": [ + "lib", + "src" + ], + "scripts": { + "prepare": "yarn run clean && yarn run build", + "clean": "theiaext clean", + "build": "theiaext build", + "watch": "theiaext watch", + "test": "theiaext test", + "docs": "theiaext docs" + }, + "devDependencies": { + "@theia/ext-scripts": "^0.7.0" + }, + "nyc": { + "extends": "../../configs/nyc.json" + } +} diff --git a/packages/progress-monitor/src/browser/index.ts b/packages/progress-monitor/src/browser/index.ts new file mode 100644 index 0000000000000..c525854ddd35b --- /dev/null +++ b/packages/progress-monitor/src/browser/index.ts @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (C) 2018 Red Hat, Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +export * from './progress-service'; +export * from './progress-protocol'; diff --git a/packages/progress-monitor/src/browser/progress-dialog.ts b/packages/progress-monitor/src/browser/progress-dialog.ts new file mode 100644 index 0000000000000..105e4476c0944 --- /dev/null +++ b/packages/progress-monitor/src/browser/progress-dialog.ts @@ -0,0 +1,92 @@ +/******************************************************************************** + * Copyright (C) 2018 Red Hat, Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable, inject, postConstruct } from 'inversify'; +import { Message } from '@phosphor/messaging'; +import { Disposable } from '@theia/core'; +import { Widget, BaseWidget, Key, StatusBar } from '@theia/core/lib/browser'; +import { ProgressWidget } from './progress-widget'; + +@injectable() +export class ProgressDialog extends BaseWidget { + + protected contentNode: HTMLDivElement; + + protected isOpen: boolean = false; + + @inject(ProgressWidget) readonly widget: ProgressWidget; + @inject(StatusBar) protected readonly statusBar: StatusBar; + + constructor() { + super(); + this.contentNode = document.createElement('div'); + this.contentNode.classList.add(ProgressWidget.Styles.PROGRESS_MONITOR_CONTENT); + + this.node.classList.add(ProgressWidget.Styles.PROGRESS_WIDGET_DIALOG); + + this.node.appendChild(this.contentNode); + this.update(); + } + + @postConstruct() + protected init() { + this.toDispose.push(this.widget); + } + + protected onUpdateRequest(msg: Message): void { + super.onUpdateRequest(msg); + this.widget.update(); + } + + protected onAfterAttach(msg: Message): void { + super.onAfterAttach(msg); + Widget.attach(this.widget, this.contentNode); + this.toDisposeOnDetach.push(Disposable.create(() => { + if (this.widget && this.widget.isAttached) { + Widget.detach(this.widget); + } + })); + + this.addKeyListener(document.body, Key.ESCAPE, () => { + if (!this.isDisposed && this.isAttached) { + this.closeDialog(); + } + }, 'click'); + } + + closeDialog(): void { + if (this.isAttached) { + Widget.detach(this); + } + } + + protected onActivateRequest(msg: Message): void { + super.onActivateRequest(msg); + this.widget.activate(); + } + + async toggleOpen(): Promise { + if (this.isAttached) { + this.closeDialog(); + return; + } + const progressStatusBar = await this.statusBar.getElementByClass(ProgressWidget.Styles.PROGRESS_STATUS_BAR); + if (progressStatusBar[0] !== undefined) { + Widget.attach(this, progressStatusBar[0] as HTMLElement); + this.activate(); + } + } +} diff --git a/packages/progress-monitor/src/browser/progress-monitor-frontend-module.ts b/packages/progress-monitor/src/browser/progress-monitor-frontend-module.ts new file mode 100644 index 0000000000000..ec2182c5a7e69 --- /dev/null +++ b/packages/progress-monitor/src/browser/progress-monitor-frontend-module.ts @@ -0,0 +1,39 @@ +/******************************************************************************** + * Copyright (C) 2018 Red Hat, Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { ContainerModule, interfaces } from 'inversify'; +import { WidgetFactory, FrontendApplicationContribution } from '@theia/core/lib/browser'; +import { ProgressService } from './progress-service'; +import { ProgressDialog } from './progress-dialog'; +import { ProgressStatusBar } from './progress-status-bar'; +import { ProgressWidget } from './progress-widget'; + +export default new ContainerModule((bind: interfaces.Bind) => { + + bind(ProgressDialog).toSelf().inSingletonScope(); + + bind(ProgressStatusBar).toSelf().inSingletonScope(); + bind(FrontendApplicationContribution).toService(ProgressStatusBar); + + bind(ProgressService).toSelf().inSingletonScope(); + + bind(ProgressWidget).toSelf(); + bind(WidgetFactory).toDynamicValue(ctx => ({ + id: 'progress-monitor', + createWidget: () => ctx.container.get(ProgressWidget) + })); + +}); diff --git a/packages/progress-monitor/src/browser/progress-protocol.ts b/packages/progress-monitor/src/browser/progress-protocol.ts new file mode 100644 index 0000000000000..c171983d75b76 --- /dev/null +++ b/packages/progress-monitor/src/browser/progress-protocol.ts @@ -0,0 +1,24 @@ +/******************************************************************************** + * Copyright (C) 2018 Red Hat, Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +export interface ProgressReport { + readonly location: string; // Location denoting what part of theia this item is coming from + readonly id: string; + readonly task: string; + readonly workDone: number; + readonly totalWork: number; + readonly complete: boolean; +} diff --git a/packages/progress-monitor/src/browser/progress-service.ts b/packages/progress-monitor/src/browser/progress-service.ts new file mode 100644 index 0000000000000..e2afd96fd4207 --- /dev/null +++ b/packages/progress-monitor/src/browser/progress-service.ts @@ -0,0 +1,56 @@ +/******************************************************************************** + * Copyright (C) 2018 Red Hat, Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable } from 'inversify'; +import { Emitter, Event } from '@theia/core/lib/common'; +import { ProgressReport } from './progress-protocol'; + +const MIN_TIMEOUT = 2500; + +@injectable() +export class ProgressService { + + protected readonly onContributionChangedEmitter = new Emitter(); + public onContributionChanged: Event = this.onContributionChangedEmitter.event; + + private progressItemMap: Map = new Map(); + private progressDeletePromiseMap: Map> = new Map(); + + addOrUpdateContribution(progressContribution: ProgressReport): void { + this.updateOrAddProgress(progressContribution).then(() => { + this.onContributionChangedEmitter.fire(progressContribution); + }); + } + + private async updateOrAddProgress(progress: ProgressReport): Promise { + const currProgressItem = this.progressItemMap.get(progress.id); + if (currProgressItem && progress.complete) { + await this.progressDeletePromiseMap.get(progress.id); + this.progressItemMap.delete(progress.id); + return; + } + if (progress.complete) { + return; + } + this.progressItemMap.set(progress.id, progress); + this.progressDeletePromiseMap.set(progress.id, new Promise(resolve => setTimeout(resolve, MIN_TIMEOUT))); + } + + get progressItems(): Array { + return Array.from(this.progressItemMap.values()); + } + +} diff --git a/packages/progress-monitor/src/browser/progress-status-bar.ts b/packages/progress-monitor/src/browser/progress-status-bar.ts new file mode 100644 index 0000000000000..8cc026a82ebe2 --- /dev/null +++ b/packages/progress-monitor/src/browser/progress-status-bar.ts @@ -0,0 +1,84 @@ +/******************************************************************************** + * Copyright (C) 2018 Red Hat, Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable, inject, postConstruct } from 'inversify'; +import { StatusBar, StatusBarAlignment, StatusBarEntry, DefaultFrontendApplicationContribution } from '@theia/core/lib/browser'; +import { EditorManager } from '@theia/editor/lib/browser'; +import { ProgressDialog } from './progress-dialog'; +import { ProgressService } from './progress-service'; +import { ProgressWidget } from './progress-widget'; + +export const PROGRESS_MONITOR_WIDGET_KIND = 'progressMonitorView'; + +import '../../src/browser/style/index.css'; + +@injectable() +export class ProgressStatusBar extends DefaultFrontendApplicationContribution { + + private readonly progressNotificationName = 'progress-monitor-notification'; + private statusBarPromise: Promise = Promise.resolve(); + + @inject(StatusBar) protected readonly statusBar: StatusBar; + @inject(EditorManager) protected readonly editorManager: EditorManager; + @inject(ProgressDialog) protected readonly progressDialog: ProgressDialog; + @inject(ProgressService) protected readonly progressService: ProgressService; + + @postConstruct() + protected init() { + this.progressService.onContributionChanged(async () => { + if (this.progressService.progressItems.length > 0) { + this.statusBarPromise = this.addStatusBar(); + return; + } + if (await this.statusBar.hasElement(this.progressNotificationName)) { + this.progressDialog.closeDialog(); + this.statusBarPromise = this.statusBar.removeElement(this.progressNotificationName); + } + }); + } + + private async addStatusBar(): Promise { + const onclick = () => { + this.statusBarPromise.then(() => this.progressDialog.toggleOpen()); + }; + const statusEntry = { + alignment: StatusBarAlignment.RIGHT, + priority: 1, + text: this.calculatePercentageDoneOverall(), + onclick, + className: ProgressWidget.Styles.PROGRESS_STATUS_BAR + } as StatusBarEntry; + await this.statusBar.setElement(this.progressNotificationName, statusEntry); + } + + private calculatePercentageDoneOverall(): string { + let percentDone = 0; + const progressItems = this.progressService.progressItems; + Array.from(progressItems).map(p => { + if (p.totalWork > 0 && p.workDone > 0) { + percentDone += (p.workDone / p.totalWork); + } + }); + const percentDoneOverall = Math.round((percentDone / progressItems.length) * 100); + if (progressItems.length > 0) { + const firstProgressItem = progressItems[0]; + const progressItemText = firstProgressItem.task; + return `${percentDoneOverall}% ${progressItemText}`; + } + return `${percentDoneOverall}% Progress Overall`; + } + +} diff --git a/packages/progress-monitor/src/browser/progress-widget.tsx b/packages/progress-monitor/src/browser/progress-widget.tsx new file mode 100644 index 0000000000000..7c929c4e4c156 --- /dev/null +++ b/packages/progress-monitor/src/browser/progress-widget.tsx @@ -0,0 +1,95 @@ +/******************************************************************************** + * Copyright (C) 2018 Red Hat, Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable, inject, postConstruct } from 'inversify'; +import { Message } from '@phosphor/messaging'; +import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget'; +import * as React from 'react'; +import { ProgressService } from './progress-service'; +import { ProgressReport } from './progress-protocol'; + +@injectable() +export class ProgressWidget extends ReactWidget { + + static SEARCH_DELAY = 200; + + @inject(ProgressService) protected readonly progressService: ProgressService; + + @postConstruct() + protected init() { + this.node.classList.add(ProgressWidget.Styles.PROGRESS_WIDGET); + this.update(); + this.node.onclick = e => { + e.stopPropagation(); + }; + this.toDispose.push(this.progressService.onContributionChanged(() => this.update())); + } + + protected onActivateRequest(msg: Message) { + super.onActivateRequest(msg); + this.update(); + } + + protected render(): React.ReactNode { + return this.renderProgressList(); + } + + protected renderProgressList(): React.ReactNode { + const progressList: React.ReactNode[] = []; + this.progressService.progressItems.forEach(progressItem => { + const item = this.createProgressItem(progressItem); + progressList.push(item); + }); + + if (progressList.length === 0) { + return this.noProgressItemsFound(); + } + + return
{progressList}
; + } + + private createProgressItem(progressItem: ProgressReport): React.ReactNode { + return
+
+
{progressItem.location + ': ' + progressItem.task}
+
{(progressItem.workDone / progressItem.totalWork) * 100}
+
+
; + } + + private noProgressItemsFound(): React.ReactNode { + return
+
+

No Progress Items found

+
+
; + } + +} + +export namespace ProgressWidget { + export namespace Styles { + export const PROGRESS_MONITOR = 'theia-progress-monitor'; + export const PROGRESS_MONITOR_LEFT_CONTAINER = 'theia-progress-monitor-item-container-left'; + export const PROGRESS_MONITOR_RIGHT_CONTAINER = 'theia-progress-monitor-item-container-right'; + export const PROGRESS_MONITOR_CONTENT = 'theia-progress-content'; + export const PROGRESS_WIDGET_DIALOG = 'theia-progress-widget-dialog'; + export const PROGRESS_STATUS_BAR = 'theia-progress-status-bar'; + export const PROGRESS_WIDGET = 'theia-progress-widget'; + export const PROGRESS_MONITOR_LIST_CONTAINER = 'progress-monitor-list-container'; + export const PROGRESS_NO_ITEMS_FOUND = 'theia-no-items-found'; + } +} diff --git a/packages/progress-monitor/src/browser/style/index.css b/packages/progress-monitor/src/browser/style/index.css new file mode 100644 index 0000000000000..f4e9d61ca3500 --- /dev/null +++ b/packages/progress-monitor/src/browser/style/index.css @@ -0,0 +1,64 @@ +/******************************************************************************** + * Copyright (C) 2018 Red Hat, Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +.theia-progress-monitor { + padding-left: 15px; + padding-top: 15px; + padding-right: 15px; +} + +.theia-progress-monitor-item-container-left { + width: 90%; + text-overflow: ellipsis; + overflow: hidden; +} + +.theia-progress-monitor-item-container-right { + width: 10%; + text-align: right; +} + +.theia-progress-content { + z-index: 10000; + width: 300px; + height: 275px; + background-color: var(--theia-ui-dialog-color); + color: var(--theia-ui-dialog-font-color); +} + +.theia-progress-widget-dialog { + z-index: 1000000; + position: fixed; + bottom: var(--theia-statusBar-height); +} + +.theia-progress-status-bar { + flex-direction: row-reverse; +} + +.theia-progress-widget { + height: inherit; + z-index: 1000000; +} + +#progress-monitor-list-container { + overflow-y: scroll; + overflow-x: hidden; +} + +.theia-no-items-found { + justify-content: center; +} diff --git a/packages/progress-monitor/src/package.spec.ts b/packages/progress-monitor/src/package.spec.ts new file mode 100644 index 0000000000000..e757cb4b13c1e --- /dev/null +++ b/packages/progress-monitor/src/package.spec.ts @@ -0,0 +1,27 @@ +/******************************************************************************** + * Copyright (C) 2018 Red Hat, Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +/* note: this bogus test file is required so that + we are able to run mocha unit tests on this + package, without having any actual unit tests in it. + This way a coverage report will be generated, + showing 0% coverage, instead of no report. + This file can be removed once we have real unit + tests in place. */ + +describe('progress monitor package', () => { + it('support code coverage statistics', () => true); +}); diff --git a/tsconfig.json b/tsconfig.json index 89626631288c6..f3043f0573099 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -103,6 +103,9 @@ "@theia/bunyan/lib/*": [ "packages/bunyan/src/*" ], + "@theia/progress-monitor/lib/*": [ + "packages/progress-monitor/src/*" + ], "@theia/console/lib/*": [ "packages/console/src/*" ],