提交 690007df 编写于 作者: E Eric Amodio 提交者: Eric Amodio

Closes #92135 - Adds view-level progress indicator

上级 89fe6d2b
......@@ -158,6 +158,10 @@ export abstract class CompositePart<T extends Composite> extends Part {
return composite;
}
protected getInstantiatedComposite(id: string) {
return this.instantiatedCompositeItems.get(id)?.composite;
}
protected createComposite(id: string, isActive?: boolean): Composite {
// Check if composite is already created
......
......@@ -434,6 +434,10 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
return this.getActiveComposite();
}
getInstantiatedPanel(id: string): IPanel | undefined {
return this.getInstantiatedComposite(id) as IPanel | undefined;
}
getLastActivePanelId(): string {
return this.getLastActiveCompositetId();
}
......
......@@ -225,6 +225,10 @@ export class SidebarPart extends CompositePart<Viewlet> implements IViewletServi
return <IViewlet>this.getActiveComposite();
}
getInstantiatedViewlet(id: string): IViewlet | undefined {
return this.getInstantiatedComposite(id) as IViewlet | undefined;
}
getLastActiveViewletId(): string {
return this.getLastActiveCompositetId();
}
......
......@@ -7,6 +7,10 @@
border-top: none !important; /* less clutter: do not show any border for first views in a pane */
}
.monaco-pane-view .pane > .pane-header {
position: relative;
}
.monaco-pane-view .pane > .pane-header > .actions.show {
display: initial;
}
......@@ -23,3 +27,11 @@
.monaco-pane-view .pane > .pane-header h3.title:first-child {
margin-left: 7px;
}
.monaco-pane-view .pane > .pane-header .monaco-progress-container {
position: absolute;
left: 0;
bottom: 0;
z-index: 5;
height: 2px;
}
......@@ -7,7 +7,7 @@ import 'vs/css!./media/paneviewlet';
import * as nls from 'vs/nls';
import { Event, Emitter } from 'vs/base/common/event';
import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry';
import { attachStyler, IColorMapping, attachButtonStyler, attachLinkStyler } from 'vs/platform/theme/common/styler';
import { attachStyler, IColorMapping, attachButtonStyler, attachLinkStyler, attachProgressBarStyler } from 'vs/platform/theme/common/styler';
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER, PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, addDisposableListener, removeClass, addClass } from 'vs/base/browser/dom';
import { IDisposable, combinedDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
......@@ -44,6 +44,9 @@ import { Button } from 'vs/base/browser/ui/button/button';
import { Link } from 'vs/platform/opener/browser/link';
import { LocalSelectionTransfer } from 'vs/workbench/browser/dnd';
import { Orientation } from 'vs/base/browser/ui/sash/sash';
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
import { ViewProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator';
import { IProgressIndicator } from 'vs/platform/progress/common/progress';
export interface IPaneColors extends IColorMapping {
dropBackground?: ColorIdentifier;
......@@ -181,6 +184,8 @@ export abstract class ViewPane extends Pane implements IView {
title: string;
private readonly menuActions: ViewMenuActions;
private progressBar!: ProgressBar;
private progressIndicator!: IProgressIndicator;
private toolbar?: ToolBar;
private readonly showActionsAlways: boolean = false;
......@@ -289,6 +294,13 @@ export abstract class ViewPane extends Pane implements IView {
const onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewPane.AlwaysShowActionsConfig));
this._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this));
this.updateActionsVisibility();
if (this.progressBar !== undefined) {
// Progress bar
this.progressBar = this._register(new ProgressBar(this.headerContainer));
this._register(attachProgressBarStyler(this.progressBar, this.themeService));
this.progressBar.hide();
}
}
protected renderTwisties(container: HTMLElement): void {
......@@ -320,6 +332,24 @@ export abstract class ViewPane extends Pane implements IView {
// noop
}
getProgressIndicator() {
if (!this.headerContainer) {
return undefined;
}
if (this.progressBar === undefined) {
// Progress bar
this.progressBar = this._register(new ProgressBar(this.headerContainer));
this._register(attachProgressBarStyler(this.progressBar, this.themeService));
this.progressBar.hide();
}
if (this.progressIndicator === undefined) {
this.progressIndicator = this.instantiationService.createInstance(ViewProgressIndicator, this, assertIsDefined(this.progressBar));
}
return this.progressIndicator;
}
protected getProgressLocation(): string {
return this.viewDescriptorService.getViewContainer(this.id)!.id;
}
......
......@@ -34,6 +34,7 @@ import { Viewlet, ViewletDescriptor, ViewletRegistry, Extensions as ViewletExten
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { URI } from 'vs/base/common/uri';
import { IProgressIndicator } from 'vs/platform/progress/common/progress';
export interface IViewState {
visibleGlobal: boolean | undefined;
......@@ -699,6 +700,27 @@ export class ViewsService extends Disposable implements IViewsService {
return null;
}
getProgressIndicator(id: string): IProgressIndicator | undefined {
const viewContainer = this.viewDescriptorService.getViewContainer(id);
if (viewContainer === null) {
return undefined;
}
const location = this.viewContainersRegistry.getViewContainerLocation(viewContainer);
let viewPaneContainer;
if (location === ViewContainerLocation.Sidebar) {
const viewlet = this.viewletService.getInstantiatedViewlet(viewContainer.id);
viewPaneContainer = viewlet?.getViewPaneContainer();
} else if (location === ViewContainerLocation.Panel) {
const panel = this.panelService.getInstantiatedPanel(viewContainer.id);
viewPaneContainer = (panel as IPaneComposite)?.getViewPaneContainer();
}
const view = viewPaneContainer?.getView(id);
return view?.getProgressIndicator();
}
private registerViewletOrPanel(viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation): void {
switch (viewContainerLocation) {
case ViewContainerLocation.Panel:
......
......@@ -20,6 +20,7 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { flatten, mergeSort } from 'vs/base/common/arrays';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { SetMap } from 'vs/base/common/collections';
import { IProgressIndicator } from 'vs/platform/progress/common/progress';
export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test';
......@@ -402,6 +403,7 @@ export interface IView {
setExpanded(expanded: boolean): boolean;
getProgressIndicator(): IProgressIndicator | undefined;
}
export interface IViewsViewlet extends IViewlet {
......@@ -426,6 +428,7 @@ export interface IViewsService {
closeView(id: string): void;
getProgressIndicator(id: string): IProgressIndicator | undefined;
}
/**
......
......@@ -35,6 +35,11 @@ export interface IPanelService {
*/
getActivePanel(): IPanel | undefined;
/**
* Returns an instantiated panel by id, if any.
*/
getInstantiatedPanel(id: string): IPanel | undefined;
/**
* Returns the panel by id.
*/
......
......@@ -10,6 +10,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IProgressRunner, IProgressIndicator, emptyProgressRunner } from 'vs/platform/progress/common/progress';
import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
export class ProgressBarIndicator extends Disposable implements IProgressIndicator {
......@@ -350,3 +351,160 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr
}
}
}
export class ViewProgressIndicator extends Disposable implements IProgressIndicator {
private progressState: ProgressIndicatorState.State = ProgressIndicatorState.None;
private isVisible: boolean;
constructor(
view: ViewPane,
private progressbar: ProgressBar,
) {
super();
this.progressbar = progressbar;
this._register(view.onDidChangeBodyVisibility(this.onVisible, this));
this.isVisible = view.isVisible();
}
onVisible(visible: boolean) {
this.isVisible = visible;
// Return early if progress state indicates that progress is done
if (!visible || this.progressState.type === ProgressIndicatorState.Done.type) {
this.progressbar.stop().hide();
return;
}
// Replay Infinite Progress from Promise
if (this.progressState.type === ProgressIndicatorState.Type.While) {
let delay: number | undefined;
if (this.progressState.whileDelay > 0) {
const remainingDelay = this.progressState.whileDelay - (Date.now() - this.progressState.whileStart);
if (remainingDelay > 0) {
delay = remainingDelay;
}
}
this.doShowWhile(delay);
}
// Replay Infinite Progress
else if (this.progressState.type === ProgressIndicatorState.Type.Infinite) {
this.progressbar.infinite().show();
}
// Replay Finite Progress (Total & Worked)
else if (this.progressState.type === ProgressIndicatorState.Type.Work) {
if (this.progressState.total) {
this.progressbar.total(this.progressState.total).show();
}
if (this.progressState.worked) {
this.progressbar.worked(this.progressState.worked).show();
}
}
}
show(infinite: true, delay?: number): IProgressRunner;
show(total: number, delay?: number): IProgressRunner;
show(infiniteOrTotal: true | number, delay?: number): IProgressRunner {
// Sort out Arguments
if (typeof infiniteOrTotal === 'boolean') {
this.progressState = ProgressIndicatorState.Infinite;
} else {
this.progressState = new ProgressIndicatorState.Work(infiniteOrTotal, undefined);
}
// Active: Show Progress
if (this.isVisible) {
// Infinite: Start Progressbar and Show after Delay
if (this.progressState.type === ProgressIndicatorState.Type.Infinite) {
this.progressbar.infinite().show(delay);
}
// Finite: Start Progressbar and Show after Delay
else if (this.progressState.type === ProgressIndicatorState.Type.Work && typeof this.progressState.total === 'number') {
this.progressbar.total(this.progressState.total).show(delay);
}
}
return {
total: (total: number) => {
this.progressState = new ProgressIndicatorState.Work(
total,
this.progressState.type === ProgressIndicatorState.Type.Work ? this.progressState.worked : undefined);
if (this.isVisible) {
this.progressbar.total(total);
}
},
worked: (worked: number) => {
// Verify first that we are either not active or the progressbar has a total set
if (!this.isVisible || this.progressbar.hasTotal()) {
this.progressState = new ProgressIndicatorState.Work(
this.progressState.type === ProgressIndicatorState.Type.Work ? this.progressState.total : undefined,
this.progressState.type === ProgressIndicatorState.Type.Work && typeof this.progressState.worked === 'number' ? this.progressState.worked + worked : worked);
if (this.isVisible) {
this.progressbar.worked(worked);
}
}
// Otherwise the progress bar does not support worked(), we fallback to infinite() progress
else {
this.progressState = ProgressIndicatorState.Infinite;
this.progressbar.infinite().show();
}
},
done: () => {
this.progressState = ProgressIndicatorState.Done;
this.progressbar.stop().hide();
}
};
}
async showWhile(promise: Promise<unknown>, delay?: number): Promise<void> {
// Join with existing running promise to ensure progress is accurate
if (this.progressState.type === ProgressIndicatorState.Type.While) {
promise = Promise.all([promise, this.progressState.whilePromise]);
}
// Keep Promise in State
this.progressState = new ProgressIndicatorState.While(promise, delay || 0, Date.now());
try {
this.doShowWhile(delay);
await promise;
} catch (error) {
// ignore
} finally {
// If this is not the last promise in the list of joined promises, skip this
if (this.progressState.type !== ProgressIndicatorState.Type.While || this.progressState.whilePromise === promise) {
// The while promise is either null or equal the promise we last hooked on
this.progressState = ProgressIndicatorState.None;
this.progressbar.stop().hide();
}
}
}
private doShowWhile(delay?: number): void {
// Show Progress when active
if (this.isVisible) {
this.progressbar.infinite().show(delay);
}
}
}
......@@ -25,6 +25,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { EventHelper } from 'vs/base/browser/dom';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { parseLinkedText } from 'vs/base/common/linkedText';
import { IViewsService, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views';
export class ProgressService extends Disposable implements IProgressService {
......@@ -33,6 +34,8 @@ export class ProgressService extends Disposable implements IProgressService {
constructor(
@IActivityService private readonly activityService: IActivityService,
@IViewletService private readonly viewletService: IViewletService,
@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,
@IViewsService private readonly viewsService: IViewsService,
@IPanelService private readonly panelService: IPanelService,
@INotificationService private readonly notificationService: INotificationService,
@IStatusbarService private readonly statusbarService: IStatusbarService,
......@@ -54,6 +57,10 @@ export class ProgressService extends Disposable implements IProgressService {
return this.withPanelProgress(location, task, { ...options, location });
}
if (this.viewsService.getProgressIndicator(location)) {
return this.withViewProgress(location, task, { ...options, location });
}
throw new Error(`Bad progress location: ${location}`);
}
......@@ -412,6 +419,57 @@ export class ProgressService extends Disposable implements IProgressService {
return promise;
}
private withViewProgress<P extends Promise<R>, R = unknown>(viewId: string, task: (progress: IProgress<IProgressStep>) => P, options: IProgressCompositeOptions): P {
// show in viewlet
const promise = this.withCompositeProgress(this.viewsService.getProgressIndicator(viewId), task, options);
const location = this.viewDescriptorService.getViewLocation(viewId);
if (location !== ViewContainerLocation.Sidebar) {
return promise;
}
const viewletId = this.viewDescriptorService.getViewContainer(viewId)?.id;
if (viewletId === undefined) {
return promise;
}
// show activity bar
let activityProgress: IDisposable;
let delayHandle: any = setTimeout(() => {
delayHandle = undefined;
const handle = this.activityService.showActivity(
viewletId,
new ProgressBadge(() => ''),
'progress-badge',
100
);
const startTimeVisible = Date.now();
const minTimeVisible = 300;
activityProgress = {
dispose() {
const d = Date.now() - startTimeVisible;
if (d < minTimeVisible) {
// should at least show for Nms
setTimeout(() => handle.dispose(), minTimeVisible - d);
} else {
// shown long enough
handle.dispose();
}
}
};
}, options.delay || 300);
promise.finally(() => {
clearTimeout(delayHandle);
dispose(activityProgress);
});
return promise;
}
private withPanelProgress<P extends Promise<R>, R = unknown>(panelid: string, task: (progress: IProgress<IProgressStep>) => P, options: IProgressCompositeOptions): P {
// show in panel
......
......@@ -30,6 +30,11 @@ export interface IViewletService {
*/
getActiveViewlet(): IViewlet | undefined;
/**
* Returns an instantiated viewlet by id, if any.
*/
getInstantiatedViewlet(id: string): IViewlet | undefined;
/**
* Returns the id of the default viewlet.
*/
......
......@@ -433,6 +433,7 @@ export class TestViewletService implements IViewletService {
getViewlets(): ViewletDescriptor[] { return []; }
getAllViewlets(): ViewletDescriptor[] { return []; }
getActiveViewlet(): IViewlet { return activeViewlet; }
getInstantiatedViewlet(): IViewlet | undefined { return undefined; }
getDefaultViewletId(): string { return 'workbench.view.explorer'; }
getViewlet(id: string): ViewletDescriptor | undefined { return undefined; }
getProgressIndicator(id: string) { return undefined; }
......@@ -451,7 +452,8 @@ export class TestPanelService implements IPanelService {
getPanel(id: string): any { return activeViewlet; }
getPanels() { return []; }
getPinnedPanels() { return []; }
getActivePanel(): IViewlet { return activeViewlet; }
getActivePanel(): IPanel { return activeViewlet; }
getInstantiatedPanel(): IPanel | undefined { return undefined; }
setPanelEnablement(id: string, enabled: boolean): void { }
dispose() { }
showActivity(panelId: string, badge: IBadge, clazz?: string): IDisposable { throw new Error('Method not implemented.'); }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册