mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-12 11:08:31 -05:00
This reverts commit d15a3fcc98.
This commit is contained in:
@@ -3,341 +3,294 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/progressService';
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IProgressService, IProgressOptions, IProgressStep, ProgressLocation, IProgress, emptyProgress, Progress, IProgressCompositeOptions, IProgressNotificationOptions } from 'vs/platform/progress/common/progress';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { StatusbarAlignment, IStatusbarService } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { ProgressBadge, IActivityService } from 'vs/workbench/services/activity/common/activity';
|
||||
import { INotificationService, Severity, INotificationHandle, INotificationActions } from 'vs/platform/notification/common/notification';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
|
||||
import { Dialog } from 'vs/base/browser/ui/dialog/dialog';
|
||||
import { attachDialogStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { EventHelper } from 'vs/base/browser/dom';
|
||||
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { IProgressService, IProgressRunner } from 'vs/platform/progress/common/progress';
|
||||
|
||||
export class ProgressService implements IProgressService {
|
||||
|
||||
_serviceBrand: ServiceIdentifier<IProgressService>;
|
||||
namespace ProgressState {
|
||||
export const enum Type {
|
||||
None,
|
||||
Done,
|
||||
Infinite,
|
||||
While,
|
||||
Work
|
||||
}
|
||||
|
||||
private readonly _stack: [IProgressOptions, Progress<IProgressStep>][] = [];
|
||||
private _globalStatusEntry: IDisposable;
|
||||
export const None = new class { readonly type = Type.None; };
|
||||
export const Done = new class { readonly type = Type.Done; };
|
||||
export const Infinite = new class { readonly type = Type.Infinite; };
|
||||
|
||||
export class While {
|
||||
public readonly type = Type.While;
|
||||
constructor(
|
||||
public readonly whilePromise: Promise<any>,
|
||||
public readonly whileStart: number,
|
||||
public readonly whileDelay: number,
|
||||
) { }
|
||||
}
|
||||
|
||||
export class Work {
|
||||
public readonly type = Type.Work;
|
||||
constructor(
|
||||
public readonly total: number | undefined,
|
||||
public readonly worked: number | undefined
|
||||
) { }
|
||||
}
|
||||
|
||||
export type State =
|
||||
typeof None
|
||||
| typeof Done
|
||||
| typeof Infinite
|
||||
| While
|
||||
| Work;
|
||||
}
|
||||
|
||||
export abstract class ScopedService extends Disposable {
|
||||
|
||||
constructor(private viewletService: IViewletService, private panelService: IPanelService, private scopeId: string) {
|
||||
super();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
registerListeners(): void {
|
||||
this._register(this.viewletService.onDidViewletOpen(viewlet => this.onScopeOpened(viewlet.getId())));
|
||||
this._register(this.panelService.onDidPanelOpen(({ panel }) => this.onScopeOpened(panel.getId())));
|
||||
|
||||
this._register(this.viewletService.onDidViewletClose(viewlet => this.onScopeClosed(viewlet.getId())));
|
||||
this._register(this.panelService.onDidPanelClose(panel => this.onScopeClosed(panel.getId())));
|
||||
}
|
||||
|
||||
private onScopeClosed(scopeId: string) {
|
||||
if (scopeId === this.scopeId) {
|
||||
this.onScopeDeactivated();
|
||||
}
|
||||
}
|
||||
|
||||
private onScopeOpened(scopeId: string) {
|
||||
if (scopeId === this.scopeId) {
|
||||
this.onScopeActivated();
|
||||
}
|
||||
}
|
||||
|
||||
abstract onScopeActivated(): void;
|
||||
|
||||
abstract onScopeDeactivated(): void;
|
||||
}
|
||||
|
||||
export class ScopedProgressService extends ScopedService implements IProgressService {
|
||||
_serviceBrand: any;
|
||||
private isActive: boolean;
|
||||
private progressbar: ProgressBar;
|
||||
private progressState: ProgressState.State = ProgressState.None;
|
||||
|
||||
constructor(
|
||||
@IActivityService private readonly _activityBar: IActivityService,
|
||||
@IViewletService private readonly _viewletService: IViewletService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@IStatusbarService private readonly _statusbarService: IStatusbarService,
|
||||
@ILayoutService private readonly _layoutService: ILayoutService,
|
||||
@IThemeService private readonly _themeService: IThemeService,
|
||||
@IKeybindingService private readonly _keybindingService: IKeybindingService
|
||||
) { }
|
||||
progressbar: ProgressBar,
|
||||
scopeId: string,
|
||||
isActive: boolean,
|
||||
@IViewletService viewletService: IViewletService,
|
||||
@IPanelService panelService: IPanelService
|
||||
) {
|
||||
super(viewletService, panelService, scopeId);
|
||||
|
||||
withProgress<R = unknown>(options: IProgressOptions, task: (progress: IProgress<IProgressStep>) => Promise<R>, onDidCancel?: () => void): Promise<R> {
|
||||
const { location } = options;
|
||||
if (typeof location === 'string') {
|
||||
const viewlet = this._viewletService.getViewlet(location);
|
||||
if (viewlet) {
|
||||
return this._withViewletProgress(location, task, { ...options, location });
|
||||
}
|
||||
this.progressbar = progressbar;
|
||||
this.isActive = isActive || types.isUndefinedOrNull(scopeId); // If service is unscoped, enable by default
|
||||
}
|
||||
|
||||
return Promise.reject(new Error(`Bad progress location: ${location}`));
|
||||
onScopeDeactivated(): void {
|
||||
this.isActive = false;
|
||||
}
|
||||
|
||||
onScopeActivated(): void {
|
||||
this.isActive = true;
|
||||
|
||||
// Return early if progress state indicates that progress is done
|
||||
if (this.progressState.type === ProgressState.Done.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (location) {
|
||||
case ProgressLocation.Notification:
|
||||
return this._withNotificationProgress({ ...options, location }, task, onDidCancel);
|
||||
case ProgressLocation.Window:
|
||||
return this._withWindowProgress(options, task);
|
||||
case ProgressLocation.Explorer:
|
||||
return this._withViewletProgress('workbench.view.explorer', task, { ...options, location });
|
||||
case ProgressLocation.Scm:
|
||||
return this._withViewletProgress('workbench.view.scm', task, { ...options, location });
|
||||
case ProgressLocation.Extensions:
|
||||
return this._withViewletProgress('workbench.view.extensions', task, { ...options, location });
|
||||
case ProgressLocation.Dialog:
|
||||
return this._withDialogProgress(options, task, onDidCancel);
|
||||
default:
|
||||
return Promise.reject(new Error(`Bad progress location: ${location}`));
|
||||
// Replay Infinite Progress from Promise
|
||||
if (this.progressState.type === ProgressState.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 === ProgressState.Type.Infinite) {
|
||||
this.progressbar.infinite().show();
|
||||
}
|
||||
|
||||
// Replay Finite Progress (Total & Worked)
|
||||
else if (this.progressState.type === ProgressState.Type.Work) {
|
||||
if (this.progressState.total) {
|
||||
this.progressbar.total(this.progressState.total).show();
|
||||
}
|
||||
|
||||
if (this.progressState.worked) {
|
||||
this.progressbar.worked(this.progressState.worked).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _withWindowProgress<R = unknown>(options: IProgressOptions, callback: (progress: IProgress<{ message?: string }>) => Promise<R>): Promise<R> {
|
||||
const task: [IProgressOptions, Progress<IProgressStep>] = [options, new Progress<IProgressStep>(() => this._updateWindowProgress())];
|
||||
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 = ProgressState.Infinite;
|
||||
} else {
|
||||
this.progressState = new ProgressState.Work(infiniteOrTotal, undefined);
|
||||
}
|
||||
|
||||
const promise = callback(task[1]);
|
||||
// Active: Show Progress
|
||||
if (this.isActive) {
|
||||
|
||||
let delayHandle: any = setTimeout(() => {
|
||||
delayHandle = undefined;
|
||||
this._stack.unshift(task);
|
||||
this._updateWindowProgress();
|
||||
|
||||
// show progress for at least 150ms
|
||||
Promise.all([
|
||||
timeout(150),
|
||||
promise
|
||||
]).finally(() => {
|
||||
const idx = this._stack.indexOf(task);
|
||||
this._stack.splice(idx, 1);
|
||||
this._updateWindowProgress();
|
||||
});
|
||||
}, 150);
|
||||
|
||||
// cancel delay if promise finishes below 150ms
|
||||
return promise.finally(() => clearTimeout(delayHandle));
|
||||
}
|
||||
|
||||
private _updateWindowProgress(idx: number = 0) {
|
||||
dispose(this._globalStatusEntry);
|
||||
|
||||
if (idx < this._stack.length) {
|
||||
const [options, progress] = this._stack[idx];
|
||||
|
||||
let progressTitle = options.title;
|
||||
let progressMessage = progress.value && progress.value.message;
|
||||
let text: string;
|
||||
let title: string;
|
||||
|
||||
if (progressTitle && progressMessage) {
|
||||
// <title>: <message>
|
||||
text = localize('progress.text2', "{0}: {1}", progressTitle, progressMessage);
|
||||
title = options.source ? localize('progress.title3', "[{0}] {1}: {2}", options.source, progressTitle, progressMessage) : text;
|
||||
|
||||
} else if (progressTitle) {
|
||||
// <title>
|
||||
text = progressTitle;
|
||||
title = options.source ? localize('progress.title2', "[{0}]: {1}", options.source, progressTitle) : text;
|
||||
|
||||
} else if (progressMessage) {
|
||||
// <message>
|
||||
text = progressMessage;
|
||||
title = options.source ? localize('progress.title2', "[{0}]: {1}", options.source, progressMessage) : text;
|
||||
|
||||
} else {
|
||||
// no title, no message -> no progress. try with next on stack
|
||||
this._updateWindowProgress(idx + 1);
|
||||
return;
|
||||
// Infinite: Start Progressbar and Show after Delay
|
||||
if (this.progressState.type === ProgressState.Type.Infinite) {
|
||||
this.progressbar.infinite().show(delay);
|
||||
}
|
||||
|
||||
this._globalStatusEntry = this._statusbarService.addEntry({
|
||||
text: `$(sync~spin) ${text}`,
|
||||
tooltip: title
|
||||
}, 'status.progress', localize('status.progress', "Progress Message"), StatusbarAlignment.LEFT);
|
||||
// Finite: Start Progressbar and Show after Delay
|
||||
else if (this.progressState.type === ProgressState.Type.Work && typeof this.progressState.total === 'number') {
|
||||
this.progressbar.total(this.progressState.total).show(delay);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
total: (total: number) => {
|
||||
this.progressState = new ProgressState.Work(
|
||||
total,
|
||||
this.progressState.type === ProgressState.Type.Work ? this.progressState.worked : undefined);
|
||||
|
||||
if (this.isActive) {
|
||||
this.progressbar.total(total);
|
||||
}
|
||||
},
|
||||
|
||||
worked: (worked: number) => {
|
||||
|
||||
// Verify first that we are either not active or the progressbar has a total set
|
||||
if (!this.isActive || this.progressbar.hasTotal()) {
|
||||
this.progressState = new ProgressState.Work(
|
||||
this.progressState.type === ProgressState.Type.Work ? this.progressState.total : undefined,
|
||||
this.progressState.type === ProgressState.Type.Work && typeof this.progressState.worked === 'number' ? this.progressState.worked + worked : worked);
|
||||
|
||||
if (this.isActive) {
|
||||
this.progressbar.worked(worked);
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise the progress bar does not support worked(), we fallback to infinite() progress
|
||||
else {
|
||||
this.progressState = ProgressState.Infinite;
|
||||
this.progressbar.infinite().show();
|
||||
}
|
||||
},
|
||||
|
||||
done: () => {
|
||||
this.progressState = ProgressState.Done;
|
||||
|
||||
if (this.isActive) {
|
||||
this.progressbar.stop().hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async showWhile(promise: Promise<any>, delay?: number): Promise<void> {
|
||||
|
||||
// Join with existing running promise to ensure progress is accurate
|
||||
if (this.progressState.type === ProgressState.Type.While) {
|
||||
promise = Promise.all([promise, this.progressState.whilePromise]);
|
||||
}
|
||||
|
||||
// Keep Promise in State
|
||||
this.progressState = new ProgressState.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 !== ProgressState.Type.While || this.progressState.whilePromise === promise) {
|
||||
|
||||
// The while promise is either null or equal the promise we last hooked on
|
||||
this.progressState = ProgressState.None;
|
||||
|
||||
if (this.isActive) {
|
||||
this.progressbar.stop().hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _withNotificationProgress<P extends Promise<R>, R = unknown>(options: IProgressNotificationOptions, callback: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: () => void): P {
|
||||
const toDispose = new DisposableStore();
|
||||
private doShowWhile(delay?: number): void {
|
||||
|
||||
const createNotification = (message: string | undefined, increment?: number): INotificationHandle | undefined => {
|
||||
if (!message) {
|
||||
return undefined; // we need a message at least
|
||||
}
|
||||
|
||||
const primaryActions = options.primaryActions ? Array.from(options.primaryActions) : [];
|
||||
const secondaryActions = options.secondaryActions ? Array.from(options.secondaryActions) : [];
|
||||
if (options.cancellable) {
|
||||
const cancelAction = new class extends Action {
|
||||
constructor() {
|
||||
super('progress.cancel', localize('cancel', "Cancel"), undefined, true);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
if (typeof onDidCancel === 'function') {
|
||||
onDidCancel();
|
||||
}
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
};
|
||||
toDispose.add(cancelAction);
|
||||
|
||||
primaryActions.push(cancelAction);
|
||||
}
|
||||
|
||||
const actions: INotificationActions = { primary: primaryActions, secondary: secondaryActions };
|
||||
const handle = this._notificationService.notify({
|
||||
severity: Severity.Info,
|
||||
message,
|
||||
source: options.source,
|
||||
actions
|
||||
});
|
||||
|
||||
updateProgress(handle, increment);
|
||||
|
||||
Event.once(handle.onDidClose)(() => {
|
||||
toDispose.dispose();
|
||||
});
|
||||
|
||||
return handle;
|
||||
};
|
||||
|
||||
const updateProgress = (notification: INotificationHandle, increment?: number): void => {
|
||||
if (typeof increment === 'number' && increment >= 0) {
|
||||
notification.progress.total(100); // always percentage based
|
||||
notification.progress.worked(increment);
|
||||
} else {
|
||||
notification.progress.infinite();
|
||||
}
|
||||
};
|
||||
|
||||
let handle: INotificationHandle | undefined;
|
||||
const updateNotification = (message?: string, increment?: number): void => {
|
||||
if (!handle) {
|
||||
handle = createNotification(message, increment);
|
||||
} else {
|
||||
if (typeof message === 'string') {
|
||||
let newMessage: string;
|
||||
if (typeof options.title === 'string') {
|
||||
newMessage = `${options.title}: ${message}`; // always prefix with overall title if we have it (https://github.com/Microsoft/vscode/issues/50932)
|
||||
} else {
|
||||
newMessage = message;
|
||||
}
|
||||
|
||||
handle.updateMessage(newMessage);
|
||||
}
|
||||
|
||||
if (typeof increment === 'number') {
|
||||
updateProgress(handle, increment);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Show initially
|
||||
updateNotification(options.title);
|
||||
|
||||
// Update based on progress
|
||||
const promise = callback({
|
||||
report: progress => {
|
||||
updateNotification(progress.message, progress.increment);
|
||||
}
|
||||
});
|
||||
|
||||
// Show progress for at least 800ms and then hide once done or canceled
|
||||
Promise.all([timeout(800), promise]).finally(() => {
|
||||
if (handle) {
|
||||
handle.close();
|
||||
}
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
private _withViewletProgress<P extends Promise<R>, R = unknown>(viewletId: string, task: (progress: IProgress<{ message?: string }>) => P, options: IProgressCompositeOptions): P {
|
||||
const promise = task(emptyProgress);
|
||||
|
||||
// show in viewlet
|
||||
const viewletProgress = this._viewletService.getProgressIndicator(viewletId);
|
||||
if (viewletProgress) {
|
||||
viewletProgress.showWhile(promise, options.delay);
|
||||
// Show Progress when active
|
||||
if (this.isActive) {
|
||||
this.progressbar.infinite().show(delay);
|
||||
}
|
||||
|
||||
// show activity bar
|
||||
let activityProgress: IDisposable;
|
||||
let delayHandle: any = setTimeout(() => {
|
||||
delayHandle = undefined;
|
||||
|
||||
const handle = this._activityBar.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 _withDialogProgress<P extends Promise<R>, R = unknown>(options: IProgressOptions, task: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: () => void): P {
|
||||
const disposables = new DisposableStore();
|
||||
const allowableCommands = [
|
||||
'workbench.action.quit',
|
||||
'workbench.action.reloadWindow'
|
||||
];
|
||||
|
||||
let dialog: Dialog;
|
||||
|
||||
const createDialog = (message: string) => {
|
||||
dialog = new Dialog(
|
||||
this._layoutService.container,
|
||||
message,
|
||||
[options.cancellable ? localize('cancel', "Cancel") : localize('dismiss', "Dismiss")],
|
||||
{
|
||||
type: 'pending',
|
||||
keyEventProcessor: (event: StandardKeyboardEvent) => {
|
||||
const resolved = this._keybindingService.softDispatch(event, this._layoutService.container);
|
||||
if (resolved && resolved.commandId) {
|
||||
if (allowableCommands.indexOf(resolved.commandId) === -1) {
|
||||
EventHelper.stop(event, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
disposables.add(dialog);
|
||||
disposables.add(attachDialogStyler(dialog, this._themeService));
|
||||
|
||||
dialog.show().then(() => {
|
||||
if (typeof onDidCancel === 'function') {
|
||||
onDidCancel();
|
||||
}
|
||||
|
||||
dispose(dialog);
|
||||
});
|
||||
|
||||
return dialog;
|
||||
};
|
||||
|
||||
const updateDialog = (message?: string) => {
|
||||
if (message && !dialog) {
|
||||
dialog = createDialog(message);
|
||||
} else if (message) {
|
||||
dialog.updateMessage(message);
|
||||
}
|
||||
};
|
||||
|
||||
const promise = task({
|
||||
report: progress => {
|
||||
updateDialog(progress.message);
|
||||
}
|
||||
});
|
||||
|
||||
promise.finally(() => {
|
||||
dispose(disposables);
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IProgressService, ProgressService, true);
|
||||
export class ProgressService implements IProgressService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
constructor(private progressbar: ProgressBar) { }
|
||||
|
||||
show(infinite: true, delay?: number): IProgressRunner;
|
||||
show(total: number, delay?: number): IProgressRunner;
|
||||
show(infiniteOrTotal: true | number, delay?: number): IProgressRunner {
|
||||
if (typeof infiniteOrTotal === 'boolean') {
|
||||
this.progressbar.infinite().show(delay);
|
||||
} else {
|
||||
this.progressbar.total(infiniteOrTotal).show(delay);
|
||||
}
|
||||
|
||||
return {
|
||||
total: (total: number) => {
|
||||
this.progressbar.total(total);
|
||||
},
|
||||
|
||||
worked: (worked: number) => {
|
||||
if (this.progressbar.hasTotal()) {
|
||||
this.progressbar.worked(worked);
|
||||
} else {
|
||||
this.progressbar.infinite().show();
|
||||
}
|
||||
},
|
||||
|
||||
done: () => {
|
||||
this.progressbar.stop().hide();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async showWhile(promise: Promise<any>, delay?: number): Promise<void> {
|
||||
try {
|
||||
this.progressbar.infinite().show(delay);
|
||||
|
||||
await promise;
|
||||
} catch (error) {
|
||||
// ignore
|
||||
} finally {
|
||||
this.progressbar.stop().hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user