Files
azuredatastudio/src/vs/workbench/services/dialogs/electron-browser/dialogs.ts
2018-04-11 15:47:34 -07:00

241 lines
7.9 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import nls = require('vs/nls');
import product from 'vs/platform/node/product';
import { TPromise } from 'vs/base/common/winjs.base';
import Severity from 'vs/base/common/severity';
import { isLinux, isMacintosh } from 'vs/base/common/platform';
import { Action } from 'vs/base/common/actions';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
import { IConfirmationService, IChoiceService, IConfirmation, IConfirmationResult, Choice } from 'vs/platform/dialogs/common/dialogs';
import { INotificationService, INotificationHandle, INotificationActions } from 'vs/platform/notification/common/notification';
import { once } from 'vs/base/common/event';
import URI from 'vs/base/common/uri';
import { basename } from 'vs/base/common/paths';
interface IMassagedMessageBoxOptions {
/**
* OS massaged message box options.
*/
options: Electron.MessageBoxOptions;
/**
* Since the massaged result of the message box options potentially
* changes the order of buttons, we have to keep a map of these
* changes so that we can still return the correct index to the caller.
*/
buttonIndexMap: number[];
}
export class DialogService implements IChoiceService, IConfirmationService {
public _serviceBrand: any;
constructor(
@IWindowService private windowService: IWindowService,
@INotificationService private notificationService: INotificationService
) {
}
public confirmWithCheckbox(confirmation: IConfirmation): TPromise<IConfirmationResult> {
const { options, buttonIndexMap } = this.massageMessageBoxOptions(this.getConfirmOptions(confirmation));
return this.windowService.showMessageBox(options).then(result => {
return {
confirmed: buttonIndexMap[result.button] === 0 ? true : false,
checkboxChecked: result.checkboxChecked
} as IConfirmationResult;
});
}
public confirm(confirmation: IConfirmation): TPromise<boolean> {
const { options, buttonIndexMap } = this.massageMessageBoxOptions(this.getConfirmOptions(confirmation));
return this.windowService.showMessageBox(options).then(result => buttonIndexMap[result.button] === 0 ? true : false);
}
private getConfirmOptions(confirmation: IConfirmation): Electron.MessageBoxOptions {
const buttons: string[] = [];
if (confirmation.primaryButton) {
buttons.push(confirmation.primaryButton);
} else {
buttons.push(nls.localize({ key: 'yesButton', comment: ['&& denotes a mnemonic'] }, "&&Yes"));
}
if (confirmation.secondaryButton) {
buttons.push(confirmation.secondaryButton);
} else if (typeof confirmation.secondaryButton === 'undefined') {
buttons.push(nls.localize('cancelButton', "Cancel"));
}
const opts: Electron.MessageBoxOptions = {
title: confirmation.title,
message: confirmation.message,
buttons,
defaultId: 0,
cancelId: 1
};
if (confirmation.detail) {
opts.detail = confirmation.detail;
}
if (confirmation.type) {
opts.type = confirmation.type;
}
if (confirmation.checkbox) {
opts.checkboxLabel = confirmation.checkbox.label;
opts.checkboxChecked = confirmation.checkbox.checked;
}
return opts;
}
public choose(severity: Severity, message: string, choices: Choice[], cancelId?: number, modal: boolean = false): TPromise<number> {
if (modal) {
return this.doChooseWithDialog(severity, message, choices, cancelId);
}
return this.doChooseWithNotification(severity, message, choices);
}
private doChooseWithDialog(severity: Severity, message: string, choices: Choice[], cancelId?: number): TPromise<number> {
const type: 'none' | 'info' | 'error' | 'question' | 'warning' = severity === Severity.Info ? 'question' : severity === Severity.Error ? 'error' : severity === Severity.Warning ? 'warning' : 'none';
const stringChoices: string[] = [];
choices.forEach(choice => {
if (typeof choice === 'string') {
stringChoices.push(choice);
} else {
stringChoices.push(choice.label);
}
});
const { options, buttonIndexMap } = this.massageMessageBoxOptions({ message, buttons: stringChoices, type, cancelId });
return this.windowService.showMessageBox(options).then(result => buttonIndexMap[result.button]);
}
private doChooseWithNotification(severity: Severity, message: string, choices: Choice[]): TPromise<number> {
let handle: INotificationHandle;
const promise = new TPromise<number>((c, e) => {
// Complete promise with index of action that was picked
const callback = (index: number, closeNotification: boolean) => () => {
c(index);
if (closeNotification) {
handle.close();
}
return TPromise.as(void 0);
};
// Convert choices into primary/secondary actions
const actions: INotificationActions = {
primary: [],
secondary: []
};
choices.forEach((choice, index) => {
let isPrimary = true;
let label: string;
let closeNotification = false;
if (typeof choice === 'string') {
label = choice;
} else {
isPrimary = false;
label = choice.label;
closeNotification = !choice.keepOpen;
}
const action = new Action(`workbench.dialog.choice.${index}`, label, null, true, callback(index, closeNotification));
if (isPrimary) {
actions.primary.push(action);
} else {
actions.secondary.push(action);
}
});
// Show notification with actions
handle = this.notificationService.notify({ severity, message, actions });
// Cancel promise when notification gets disposed
once(handle.onDidClose)(() => promise.cancel());
}, () => handle.close());
return promise;
}
private massageMessageBoxOptions(options: Electron.MessageBoxOptions): IMassagedMessageBoxOptions {
let buttonIndexMap = options.buttons.map((button, index) => index);
options.buttons = options.buttons.map(button => mnemonicButtonLabel(button));
// Linux: order of buttons is reverse
// macOS: also reverse, but the OS handles this for us!
if (isLinux) {
options.buttons = options.buttons.reverse();
buttonIndexMap = buttonIndexMap.reverse();
}
// Default Button
if (options.defaultId !== void 0) {
options.defaultId = buttonIndexMap[options.defaultId];
} else if (isLinux) {
options.defaultId = buttonIndexMap[0];
}
// Cancel Button
if (options.cancelId !== void 0) {
// macOS: the cancel button should always be to the left of the primary action
// if we see more than 2 buttons, move the cancel one to the left of the primary
if (isMacintosh && options.buttons.length > 2 && options.cancelId !== 1) {
const cancelButton = options.buttons[options.cancelId];
options.buttons.splice(options.cancelId, 1);
options.buttons.splice(1, 0, cancelButton);
const cancelButtonIndex = buttonIndexMap[options.cancelId];
buttonIndexMap.splice(cancelButtonIndex, 1);
buttonIndexMap.splice(1, 0, cancelButtonIndex);
}
options.cancelId = buttonIndexMap[options.cancelId];
}
options.noLink = true;
options.title = options.title || product.nameLong;
return { options, buttonIndexMap };
}
}
const MAX_CONFIRM_FILES = 10;
export function getConfirmMessage(start: string, resourcesToConfirm: URI[]): string {
const message = [start];
message.push('');
message.push(...resourcesToConfirm.slice(0, MAX_CONFIRM_FILES).map(r => basename(r.fsPath)));
if (resourcesToConfirm.length > MAX_CONFIRM_FILES) {
if (resourcesToConfirm.length - MAX_CONFIRM_FILES === 1) {
message.push(nls.localize('moreFile', "...1 additional file not shown"));
} else {
message.push(nls.localize('moreFiles', "...{0} additional files not shown", resourcesToConfirm.length - MAX_CONFIRM_FILES));
}
}
message.push('');
return message.join('\n');
}