提交 1273a257 编写于 作者: I isidor

explorer: more async await

上级 aaa9fab2
......@@ -141,7 +141,7 @@ export class GlobalNewUntitledFileAction extends Action {
}
}
function deleteFiles(textFileService: ITextFileService, dialogService: IDialogService, configurationService: IConfigurationService, fileService: IFileService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise<void> {
async function deleteFiles(textFileService: ITextFileService, dialogService: IDialogService, configurationService: IConfigurationService, fileService: IFileService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise<void> {
let primaryButton: string;
if (useTrash) {
primaryButton = isWindows ? nls.localize('deleteButtonLabelRecycleBin', "&&Move to Recycle Bin") : nls.localize({ key: 'deleteButtonLabelTrash', comment: ['&& denotes a mnemonic'] }, "&&Move to Trash");
......@@ -152,7 +152,7 @@ function deleteFiles(textFileService: ITextFileService, dialogService: IDialogSe
const distinctElements = resources.distinctParents(elements, e => e.resource);
// Handle dirty
let confirmDirtyPromise: Promise<boolean> = Promise.resolve(true);
let confirmed = true;
const dirty = textFileService.getDirty().filter(d => distinctElements.some(e => resources.isEqualOrParent(d, e.resource)));
if (dirty.length) {
let message: string;
......@@ -168,114 +168,112 @@ function deleteFiles(textFileService: ITextFileService, dialogService: IDialogSe
message = nls.localize('dirtyMessageFileDelete', "You are deleting a file with unsaved changes. Do you want to continue?");
}
confirmDirtyPromise = dialogService.confirm({
const response = await dialogService.confirm({
message,
type: 'warning',
detail: nls.localize('dirtyWarning', "Your changes will be lost if you don't save them."),
primaryButton
}).then(res => {
if (!res.confirmed) {
return false;
}
skipConfirm = true; // since we already asked for confirmation
return textFileService.revertAll(dirty).then(() => true);
});
}
// Check if file is dirty in editor and save it to avoid data loss
return confirmDirtyPromise.then(confirmed => {
if (!confirmed) {
return undefined;
if (!response.confirmed) {
confirmed = false;
} else {
skipConfirm = true;
await textFileService.revertAll(dirty);
}
}
let confirmDeletePromise: Promise<IConfirmationResult>;
// Check if file is dirty in editor and save it to avoid data loss
if (!confirmed) {
return;
}
// Check if we need to ask for confirmation at all
if (skipConfirm || (useTrash && configurationService.getValue<boolean>(CONFIRM_DELETE_SETTING_KEY) === false)) {
confirmDeletePromise = Promise.resolve({ confirmed: true });
}
let confirmDeletePromise: Promise<IConfirmationResult>;
// Confirm for moving to trash
else if (useTrash) {
const message = getMoveToTrashMessage(distinctElements);
confirmDeletePromise = dialogService.confirm({
message,
detail: isWindows ? nls.localize('undoBin', "You can restore from the Recycle Bin.") : nls.localize('undoTrash', "You can restore from the Trash."),
primaryButton,
checkbox: {
label: nls.localize('doNotAskAgain', "Do not ask me again")
},
type: 'question'
});
}
// Check if we need to ask for confirmation at all
if (skipConfirm || (useTrash && configurationService.getValue<boolean>(CONFIRM_DELETE_SETTING_KEY) === false)) {
confirmDeletePromise = Promise.resolve({ confirmed: true });
}
// Confirm for deleting permanently
else {
const message = getDeleteMessage(distinctElements);
confirmDeletePromise = dialogService.confirm({
message,
detail: nls.localize('irreversible', "This action is irreversible!"),
primaryButton,
type: 'warning'
});
}
// Confirm for moving to trash
else if (useTrash) {
const message = getMoveToTrashMessage(distinctElements);
return confirmDeletePromise.then(confirmation => {
confirmDeletePromise = dialogService.confirm({
message,
detail: isWindows ? nls.localize('undoBin', "You can restore from the Recycle Bin.") : nls.localize('undoTrash', "You can restore from the Trash."),
primaryButton,
checkbox: {
label: nls.localize('doNotAskAgain', "Do not ask me again")
},
type: 'question'
});
}
// Check for confirmation checkbox
let updateConfirmSettingsPromise: Promise<void> = Promise.resolve(undefined);
if (confirmation.confirmed && confirmation.checkboxChecked === true) {
updateConfirmSettingsPromise = configurationService.updateValue(CONFIRM_DELETE_SETTING_KEY, false, ConfigurationTarget.USER);
}
// Confirm for deleting permanently
else {
const message = getDeleteMessage(distinctElements);
confirmDeletePromise = dialogService.confirm({
message,
detail: nls.localize('irreversible', "This action is irreversible!"),
primaryButton,
type: 'warning'
});
}
return updateConfirmSettingsPromise.then(() => {
return confirmDeletePromise.then(confirmation => {
// Check for confirmation
if (!confirmation.confirmed) {
return Promise.resolve(undefined);
}
// Check for confirmation checkbox
let updateConfirmSettingsPromise: Promise<void> = Promise.resolve(undefined);
if (confirmation.confirmed && confirmation.checkboxChecked === true) {
updateConfirmSettingsPromise = configurationService.updateValue(CONFIRM_DELETE_SETTING_KEY, false, ConfigurationTarget.USER);
}
// Call function
const servicePromise = Promise.all(distinctElements.map(e => fileService.del(e.resource, { useTrash: useTrash, recursive: true })))
.then(undefined, (error: any) => {
// Handle error to delete file(s) from a modal confirmation dialog
let errorMessage: string;
let detailMessage: string | undefined;
let primaryButton: string;
if (useTrash) {
errorMessage = isWindows ? nls.localize('binFailed', "Failed to delete using the Recycle Bin. Do you want to permanently delete instead?") : nls.localize('trashFailed', "Failed to delete using the Trash. Do you want to permanently delete instead?");
detailMessage = nls.localize('irreversible', "This action is irreversible!");
primaryButton = nls.localize({ key: 'deletePermanentlyButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Delete Permanently");
} else {
errorMessage = toErrorMessage(error, false);
primaryButton = nls.localize({ key: 'retryButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Retry");
}
return updateConfirmSettingsPromise.then(() => {
return dialogService.confirm({
message: errorMessage,
detail: detailMessage,
type: 'warning',
primaryButton
}).then(res => {
// Check for confirmation
if (!confirmation.confirmed) {
return Promise.resolve(undefined);
}
if (res.confirmed) {
if (useTrash) {
useTrash = false; // Delete Permanently
}
// Call function
const servicePromise = Promise.all(distinctElements.map(e => fileService.del(e.resource, { useTrash: useTrash, recursive: true })))
.then(undefined, (error: any) => {
// Handle error to delete file(s) from a modal confirmation dialog
let errorMessage: string;
let detailMessage: string | undefined;
let primaryButton: string;
if (useTrash) {
errorMessage = isWindows ? nls.localize('binFailed', "Failed to delete using the Recycle Bin. Do you want to permanently delete instead?") : nls.localize('trashFailed', "Failed to delete using the Trash. Do you want to permanently delete instead?");
detailMessage = nls.localize('irreversible', "This action is irreversible!");
primaryButton = nls.localize({ key: 'deletePermanentlyButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Delete Permanently");
} else {
errorMessage = toErrorMessage(error, false);
primaryButton = nls.localize({ key: 'retryButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Retry");
}
return dialogService.confirm({
message: errorMessage,
detail: detailMessage,
type: 'warning',
primaryButton
}).then(res => {
if (res.confirmed) {
if (useTrash) {
useTrash = false; // Delete Permanently
}
skipConfirm = true;
skipConfirm = true;
return deleteFiles(textFileService, dialogService, configurationService, fileService, elements, useTrash, skipConfirm);
}
return deleteFiles(textFileService, dialogService, configurationService, fileService, elements, useTrash, skipConfirm);
}
return Promise.resolve();
});
return Promise.resolve();
});
});
return servicePromise;
});
return servicePromise;
});
});
}
......@@ -453,7 +451,7 @@ export class GlobalCompareResourcesAction extends Action {
super(id, label);
}
run(): Promise<any> {
async run(): Promise<any> {
const activeInput = this.editorService.activeEditor;
const activeResource = activeInput ? activeInput.getResource() : undefined;
if (activeResource) {
......@@ -471,7 +469,7 @@ export class GlobalCompareResourcesAction extends Action {
override: this.editorService.openEditor({
leftResource: activeResource,
rightResource: resource
}).then(() => undefined)
})
};
}
......@@ -479,14 +477,11 @@ export class GlobalCompareResourcesAction extends Action {
});
// Bring up quick open
this.quickOpenService.show('', { autoFocus: { autoFocusSecondEntry: true } }).then(() => {
toDispose.dispose(); // make sure to unbind if quick open is closing
});
await this.quickOpenService.show('', { autoFocus: { autoFocusSecondEntry: true } });
toDispose.dispose(); // make sure to unbind if quick open is closing
} else {
this.notificationService.info(nls.localize('openFileToCompare', "Open a file first to compare it with another file."));
}
return Promise.resolve(true);
}
}
......@@ -562,11 +557,12 @@ export abstract class BaseSaveAllAction extends Action {
}
}
run(context?: any): Promise<boolean> {
return this.doRun(context).then(() => true, error => {
async run(context?: any): Promise<void> {
try {
await this.doRun(context);
} catch (error) {
onError(this.notificationService, error);
return false;
});
}
}
}
......@@ -682,13 +678,12 @@ export class CollapseExplorerView extends Action {
}));
}
run(): Promise<any> {
return this.viewletService.openViewlet(VIEWLET_ID).then((viewlet: ExplorerViewlet) => {
const explorerView = viewlet.getExplorerView();
if (explorerView) {
explorerView.collapseAll();
}
});
async run(): Promise<any> {
const explorerViewlet = await this.viewletService.openViewlet(VIEWLET_ID) as ExplorerViewlet;
const explorerView = explorerViewlet.getExplorerView();
if (explorerView) {
explorerView.collapseAll();
}
}
}
......@@ -710,10 +705,9 @@ export class RefreshExplorerView extends Action {
}));
}
run(): Promise<any> {
return this.viewletService.openViewlet(VIEWLET_ID).then(() =>
this.explorerService.refresh()
);
async run(): Promise<any> {
await this.viewletService.openViewlet(VIEWLET_ID);
this.explorerService.refresh();
}
}
......@@ -951,15 +945,15 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole
CommandsRegistry.registerCommand({
id: NEW_FILE_COMMAND_ID,
handler: (accessor) => {
openExplorerAndCreate(accessor, false).then(undefined, onUnexpectedError);
handler: async (accessor) => {
await openExplorerAndCreate(accessor, false);
}
});
CommandsRegistry.registerCommand({
id: NEW_FOLDER_COMMAND_ID,
handler: (accessor) => {
openExplorerAndCreate(accessor, true).then(undefined, onUnexpectedError);
handler: async (accessor) => {
await openExplorerAndCreate(accessor, true);
}
});
......
......@@ -315,7 +315,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
when: undefined,
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_D),
handler: (accessor, resource: URI | object) => {
handler: async (accessor, resource: URI | object) => {
const instantiationService = accessor.get(IInstantiationService);
const textModelService = accessor.get(ITextModelService);
const editorService = accessor.get(IEditorService);
......@@ -337,8 +337,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
const name = basename(uri);
const editorLabel = nls.localize('modifiedLabel', "{0} (in file) ↔ {1}", name, name);
TextFileContentProvider.open(uri, COMPARE_WITH_SAVED_SCHEMA, editorLabel, editorService).then(() => {
try {
await TextFileContentProvider.open(uri, COMPARE_WITH_SAVED_SCHEMA, editorLabel, editorService);
// Dispose once no more diff editor is opened with the scheme
if (registerEditorListener) {
providerDisposables.push(editorService.onDidVisibleEditorsChange(() => {
......@@ -347,12 +347,10 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
}
}));
}
}, error => {
} catch {
providerDisposables = dispose(providerDisposables);
});
}
}
return Promise.resolve(true);
}
});
......
......@@ -323,7 +323,7 @@ export class ExplorerView extends ViewletPanel {
const explorerNavigator = new TreeResourceNavigator2(this.tree);
this._register(explorerNavigator);
// Open when selecting via keyboard
this._register(explorerNavigator.onDidOpenResource(e => {
this._register(explorerNavigator.onDidOpenResource(async e => {
const selection = this.tree.getSelection();
// Do not react if the user is expanding selection via keyboard.
// Check if the item was previously also selected, if yes the user is simply expanding / collapsing current selection #66589.
......@@ -335,8 +335,7 @@ export class ExplorerView extends ViewletPanel {
return;
}
this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: 'workbench.files.openFile', from: 'explorer' });
this.editorService.openEditor({ resource: selection[0].resource, options: { preserveFocus: e.editorOptions.preserveFocus, pinned: e.editorOptions.pinned } }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP)
.then(undefined, onUnexpectedError);
await this.editorService.openEditor({ resource: selection[0].resource, options: { preserveFocus: e.editorOptions.preserveFocus, pinned: e.editorOptions.pinned } }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
}
}));
......
......@@ -36,8 +36,8 @@ import { IDragAndDropData, DataTransfers } from 'vs/base/browser/dnd';
import { Schemas } from 'vs/base/common/network';
import { DesktopDragAndDropData, ExternalElementsDragAndDropData, ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
import { isMacintosh } from 'vs/base/common/platform';
import { IDialogService, IConfirmationResult, IConfirmation, getConfirmMessage } from 'vs/platform/dialogs/common/dialogs';
import { ITextFileService, ITextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles';
import { IDialogService, IConfirmation, getConfirmMessage } from 'vs/platform/dialogs/common/dialogs';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';
import { URI } from 'vs/base/common/uri';
......@@ -648,87 +648,72 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
return undefined;
}
private addResources(target: ExplorerItem, resources: URI[]): Promise<any> {
private async addResources(target: ExplorerItem, resources: URI[]): Promise<any> {
if (resources && resources.length > 0) {
// Resolve target to check for name collisions and ask user
return this.fileService.resolve(target.resource).then(targetStat => {
// Check for name collisions
const targetNames = new Set<string>();
if (targetStat.children) {
const ignoreCase = hasToIgnoreCase(target.resource);
targetStat.children.forEach(child => {
targetNames.add(ignoreCase ? child.name : child.name.toLowerCase());
});
}
const targetStat = await this.fileService.resolve(target.resource);
// Check for name collisions
const targetNames = new Set<string>();
if (targetStat.children) {
const ignoreCase = hasToIgnoreCase(target.resource);
targetStat.children.forEach(child => {
targetNames.add(ignoreCase ? child.name : child.name.toLowerCase());
});
}
let overwritePromise: Promise<IConfirmationResult> = Promise.resolve({ confirmed: true });
if (resources.some(resource => {
return targetNames.has(!hasToIgnoreCase(resource) ? basename(resource) : basename(resource).toLowerCase());
})) {
const confirm: IConfirmation = {
message: localize('confirmOverwrite', "A file or folder with the same name already exists in the destination folder. Do you want to replace it?"),
detail: localize('irreversible', "This action is irreversible!"),
primaryButton: localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"),
type: 'warning'
};
const resourceExists = resources.some(resource => targetNames.has(!hasToIgnoreCase(resource) ? basename(resource) : basename(resource).toLowerCase()));
if (resourceExists) {
const confirm: IConfirmation = {
message: localize('confirmOverwrite', "A file or folder with the same name already exists in the destination folder. Do you want to replace it?"),
detail: localize('irreversible', "This action is irreversible!"),
primaryButton: localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"),
type: 'warning'
};
overwritePromise = this.dialogService.confirm(confirm);
const confirmationResult = await this.dialogService.confirm(confirm);
if (!confirmationResult.confirmed) {
return [];
}
}
return overwritePromise.then(res => {
if (!res.confirmed) {
return [];
// Run add in sequence
const addPromisesFactory: ITask<Promise<void>>[] = [];
resources.forEach(resource => {
addPromisesFactory.push(async () => {
const sourceFile = resource;
const targetFile = joinPath(target.resource, basename(sourceFile));
// if the target exists and is dirty, make sure to revert it. otherwise the dirty contents
// of the target file would replace the contents of the added file. since we already
// confirmed the overwrite before, this is OK.
if (this.textFileService.isDirty(targetFile)) {
await this.textFileService.revertAll([targetFile], { soft: true });
}
// Run add in sequence
const addPromisesFactory: ITask<Promise<void>>[] = [];
resources.forEach(resource => {
addPromisesFactory.push(() => {
const sourceFile = resource;
const targetFile = joinPath(target.resource, basename(sourceFile));
// if the target exists and is dirty, make sure to revert it. otherwise the dirty contents
// of the target file would replace the contents of the added file. since we already
// confirmed the overwrite before, this is OK.
let revertPromise: Promise<ITextFileOperationResult | null> = Promise.resolve(null);
if (this.textFileService.isDirty(targetFile)) {
revertPromise = this.textFileService.revertAll([targetFile], { soft: true });
}
return revertPromise.then(() => {
const copyTarget = joinPath(target.resource, basename(sourceFile));
return this.fileService.copy(sourceFile, copyTarget, true).then(stat => {
// if we only add one file, just open it directly
if (resources.length === 1 && !stat.isDirectory) {
this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } });
}
});
});
});
});
return sequence(addPromisesFactory);
const copyTarget = joinPath(target.resource, basename(sourceFile));
const stat = await this.fileService.copy(sourceFile, copyTarget, true);
// if we only add one file, just open it directly
if (resources.length === 1 && !stat.isDirectory) {
this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } });
}
});
});
}
return Promise.resolve(undefined);
await sequence(addPromisesFactory);
}
}
private handleExplorerDrop(data: IDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise<void> {
private async handleExplorerDrop(data: IDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise<void> {
const elementsData = (data as ElementsDragAndDropData<ExplorerItem>).elements;
const items = distinctParents(elementsData, s => s.resource);
const isCopy = (originalEvent.ctrlKey && !isMacintosh) || (originalEvent.altKey && isMacintosh);
let confirmPromise: Promise<IConfirmationResult>;
// Handle confirm setting
const confirmDragAndDrop = !isCopy && this.configurationService.getValue<boolean>(FileDragAndDrop.CONFIRM_DND_SETTING_KEY);
if (confirmDragAndDrop) {
confirmPromise = this.dialogService.confirm({
const confirmation = await this.dialogService.confirm({
message: items.length > 1 && items.every(s => s.isRoot) ? localize('confirmRootsMove', "Are you sure you want to change the order of multiple root folders in your workspace?")
: items.length > 1 ? getConfirmMessage(localize('confirmMultiMove', "Are you sure you want to move the following {0} files?", items.length), items.map(s => s.resource))
: items[0].isRoot ? localize('confirmRootMove', "Are you sure you want to change the order of root folder '{0}' in your workspace?", items[0].name)
......@@ -739,27 +724,19 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
type: 'question',
primaryButton: localize({ key: 'moveButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Move")
});
} else {
confirmPromise = Promise.resolve({ confirmed: true });
}
return confirmPromise.then(res => {
if (!confirmation.confirmed) {
return;
}
// Check for confirmation checkbox
let updateConfirmSettingsPromise: Promise<void> = Promise.resolve(undefined);
if (res.confirmed && res.checkboxChecked === true) {
updateConfirmSettingsPromise = this.configurationService.updateValue(FileDragAndDrop.CONFIRM_DND_SETTING_KEY, false, ConfigurationTarget.USER);
if (confirmation.checkboxChecked === true) {
await this.configurationService.updateValue(FileDragAndDrop.CONFIRM_DND_SETTING_KEY, false, ConfigurationTarget.USER);
}
}
return updateConfirmSettingsPromise.then(() => {
if (res.confirmed) {
const rootDropPromise = this.doHandleRootDrop(items.filter(s => s.isRoot), target);
return Promise.all(items.filter(s => !s.isRoot).map(source => this.doHandleExplorerDrop(source, target, isCopy)).concat(rootDropPromise)).then(() => undefined);
}
return Promise.resolve(undefined);
});
});
const rootDropPromise = this.doHandleRootDrop(items.filter(s => s.isRoot), target);
await Promise.all(items.filter(s => !s.isRoot).map(source => this.doHandleExplorerDrop(source, target, isCopy)).concat(rootDropPromise));
}
private doHandleRootDrop(roots: ExplorerItem[], target: ExplorerItem): Promise<void> {
......@@ -795,17 +772,16 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
return this.workspaceEditingService.updateFolders(0, workspaceCreationData.length, workspaceCreationData);
}
private doHandleExplorerDrop(source: ExplorerItem, target: ExplorerItem, isCopy: boolean): Promise<void> {
private async doHandleExplorerDrop(source: ExplorerItem, target: ExplorerItem, isCopy: boolean): Promise<void> {
// Reuse duplicate action if user copies
if (isCopy) {
const incrementalNaming = this.configurationService.getValue<IFilesConfiguration>().explorer.incrementalNaming;
return this.fileService.copy(source.resource, findValidPasteFileTarget(target, { resource: source.resource, isDirectory: source.isDirectory, allowOverwrite: false }, incrementalNaming)).then(stat => {
if (!stat.isDirectory) {
return this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } }).then(() => undefined);
}
const stat = await this.fileService.copy(source.resource, findValidPasteFileTarget(target, { resource: source.resource, isDirectory: source.isDirectory, allowOverwrite: false }, incrementalNaming));
if (!stat.isDirectory) {
await this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } });
}
return undefined;
});
return;
}
// Otherwise move
......@@ -815,8 +791,9 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
return Promise.resolve();
}
return this.textFileService.move(source.resource, targetResource).then(undefined, error => {
try {
await this.textFileService.move(source.resource, targetResource);
} catch (error) {
// Conflict
if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_MOVE_CONFLICT) {
const confirm: IConfirmation = {
......@@ -827,21 +804,19 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
};
// Move with overwrite if the user confirms
return this.dialogService.confirm(confirm).then(res => {
if (res.confirmed) {
return this.textFileService.move(source.resource, targetResource, true /* overwrite */).then(undefined, error => this.notificationService.error(error));
const { confirmed } = await this.dialogService.confirm(confirm);
if (confirmed) {
try {
await this.textFileService.move(source.resource, targetResource, true /* overwrite */);
} catch (error) {
this.notificationService.error(error);
}
return undefined;
});
}
}
// Any other error
else {
this.notificationService.error(error);
}
return undefined;
});
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册