未验证 提交 47017a8e 编写于 作者: A Alex Ross 提交者: GitHub

Add custom button to quick pick for file picker (#72209)

上级 1bcc5e57
......@@ -162,6 +162,14 @@ export interface IQuickPick<T extends IQuickPickItem> extends IQuickInput {
readonly onDidAccept: Event<void>;
ok: boolean;
readonly onDidCustom: Event<void>;
customButton: boolean;
customLabel: string;
buttons: ReadonlyArray<IQuickInputButton>;
readonly onDidTriggerButton: Event<IQuickInputButton>;
......
......@@ -68,9 +68,11 @@ interface QuickInputUI {
visibleCount: CountBadge;
count: CountBadge;
message: HTMLElement;
customButton: Button;
progressBar: ProgressBar;
list: QuickInputList;
onDidAccept: Event<void>;
onDidCustom: Event<void>;
onDidTriggerButton: Event<IQuickInputButton>;
ignoreFocusOut: boolean;
keyMods: Writeable<IKeyMods>;
......@@ -92,6 +94,7 @@ type Visibilities = {
message?: boolean;
list?: boolean;
ok?: boolean;
customButton?: boolean;
};
class QuickInput implements IQuickInput {
......@@ -312,6 +315,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
private _placeholder: string;
private onDidChangeValueEmitter = new Emitter<string>();
private onDidAcceptEmitter = new Emitter<void>();
private onDidCustomEmitter = new Emitter<void>();
private _items: Array<T | IQuickPickSeparator> = [];
private itemsUpdated = false;
private _canSelectMany = false;
......@@ -331,6 +335,9 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
private _valueSelection: Readonly<[number, number]>;
private valueSelectionUpdated = true;
private _validationMessage: string;
private _ok: boolean;
private _customButton: boolean;
private _customButtonLabel: string;
quickNavigate: IQuickNavigateConfiguration;
......@@ -339,6 +346,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
this.disposables.push(
this.onDidChangeValueEmitter,
this.onDidAcceptEmitter,
this.onDidCustomEmitter,
this.onDidChangeActiveEmitter,
this.onDidChangeSelectionEmitter,
this.onDidTriggerItemButtonEmitter,
......@@ -367,6 +375,8 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
onDidAccept = this.onDidAcceptEmitter.event;
onDidCustom = this.onDidCustomEmitter.event;
get items() {
return this._items;
}
......@@ -463,6 +473,33 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
this.update();
}
get customButton() {
return this._customButton;
}
set customButton(showCustomButton: boolean) {
this._customButton = showCustomButton;
this.update();
}
get customLabel() {
return this._customButtonLabel;
}
set customLabel(label: string) {
this._customButtonLabel = label;
this.update();
}
get ok() {
return this._ok;
}
set ok(showOkButton: boolean) {
this._ok = showOkButton;
this.update();
}
public inputHasFocus(): boolean {
return this.visible ? this.ui.inputBox.hasFocus() : false;
}
......@@ -547,6 +584,9 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
}
this.onDidAcceptEmitter.fire(undefined);
}),
this.ui.onDidCustom(() => {
this.onDidCustomEmitter.fire(undefined);
}),
this.ui.list.onDidChangeFocus(focusedItems => {
if (this.activeItemsUpdated) {
return; // Expect another event.
......@@ -697,12 +737,13 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
this.ui.message.textContent = null;
this.ui.inputBox.showDecoration(Severity.Ignore);
}
this.ui.customButton.label = this.customLabel;
this.ui.list.matchOnDescription = this.matchOnDescription;
this.ui.list.matchOnDetail = this.matchOnDetail;
this.ui.list.matchOnLabel = this.matchOnLabel;
this.ui.setComboboxAccessibility(true);
this.ui.inputBox.setAttribute('aria-label', QuickPick.INPUT_BOX_ARIA_LABEL);
this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, checkAll: true, inputBox: true, visibleCount: true, count: true, ok: true, list: true, message: !!this.validationMessage } : { title: !!this.title || !!this.step, inputBox: true, visibleCount: true, list: true, message: !!this.validationMessage });
this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, checkAll: true, inputBox: true, visibleCount: true, count: true, ok: true, list: true, message: !!this.validationMessage } : { title: !!this.title || !!this.step, inputBox: true, visibleCount: true, list: true, message: !!this.validationMessage, customButton: this.customButton, ok: this.ok });
}
}
......@@ -848,6 +889,7 @@ export class QuickInputService extends Component implements IQuickInputService {
private countContainer: HTMLElement;
private okContainer: HTMLElement;
private ok: Button;
private customButtonContainer: HTMLElement;
private ui: QuickInputUI;
private comboboxAccessibility = false;
private enabled = true;
......@@ -855,6 +897,7 @@ export class QuickInputService extends Component implements IQuickInputService {
private inQuickOpenContext: IContextKey<boolean>;
private contexts: { [id: string]: IContextKey<boolean>; } = Object.create(null);
private onDidAcceptEmitter = this._register(new Emitter<void>());
private onDidCustomEmitter = this._register(new Emitter<void>());
private onDidTriggerButtonEmitter = this._register(new Emitter<IQuickInputButton>());
private keyMods: Writeable<IKeyMods> = { ctrlCmd: false, alt: false };
......@@ -1013,6 +1056,14 @@ export class QuickInputService extends Component implements IQuickInputService {
this.onDidAcceptEmitter.fire();
}));
this.customButtonContainer = dom.append(headerContainer, $('.quick-input-action'));
const customButton = new Button(this.customButtonContainer);
attachButtonStyler(customButton, this.themeService);
customButton.label = localize('custom', "Custom");
this._register(customButton.onDidClick(e => {
this.onDidCustomEmitter.fire();
}));
const message = dom.append(container, $(`#${this.idPrefix}message.quick-input-message`));
const progressBar = new ProgressBar(container);
......@@ -1098,9 +1149,11 @@ export class QuickInputService extends Component implements IQuickInputService {
visibleCount,
count,
message,
customButton,
progressBar,
list,
onDidAccept: this.onDidAcceptEmitter.event,
onDidCustom: this.onDidCustomEmitter.event,
onDidTriggerButton: this.onDidTriggerButtonEmitter.event,
ignoreFocusOut: false,
keyMods: this.keyMods,
......@@ -1330,6 +1383,7 @@ export class QuickInputService extends Component implements IQuickInputService {
this.visibleCountContainer.style.display = visibilities.visibleCount ? '' : 'none';
this.countContainer.style.display = visibilities.count ? '' : 'none';
this.okContainer.style.display = visibilities.ok ? '' : 'none';
this.customButtonContainer.style.display = visibilities.customButton ? '' : 'none';
this.ui.message.style.display = visibilities.message ? '' : 'none';
this.ui.list.display(!!visibilities.list);
this.ui.container.classList[visibilities.checkAll ? 'add' : 'remove']('show-checkboxes');
......
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-2 -2 16 16" enable-background="new -2 -2 16 16"><polygon fill="#C5C5C5" points="9,0 4.5,9 3,6 0,6 3,12 6,12 12,0"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{opacity:0;fill:#F6F6F6;} .icon-vs-fg{fill:#F0EFF1;} .icon-folder{fill:#656565;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 2.5v10c0 .827-.673 1.5-1.5 1.5h-11.996c-.827 0-1.5-.673-1.5-1.5v-8c0-.827.673-1.5 1.5-1.5h2.886l1-2h8.11c.827 0 1.5.673 1.5 1.5z" id="outline"/><path class="icon-folder" d="M14.5 2h-7.492l-1 2h-3.504c-.277 0-.5.224-.5.5v8c0 .276.223.5.5.5h11.996c.275 0 .5-.224.5-.5v-10c0-.276-.225-.5-.5-.5zm-.496 2h-6.496l.5-1h5.996v1z" id="iconBg"/><path class="icon-vs-fg" d="M14 3v1h-6.5l.5-1h6z" id="iconFg"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><polygon points="5.382,13 2.382,7 6.618,7 7,7.764 9.382,3 13.618,3 8.618,13" fill="#F6F6F6"/><path d="M12 4l-4 8h-2l-2-4h2l1 2 3-6h2z" fill="#424242"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{opacity:0;fill:#F6F6F6;} .icon-vs-fg{opacity:0;fill:#F0EFF1;} .icon-folder{fill:#C5C5C5;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 2.5v10c0 .827-.673 1.5-1.5 1.5h-11.996c-.827 0-1.5-.673-1.5-1.5v-8c0-.827.673-1.5 1.5-1.5h2.886l1-2h8.11c.827 0 1.5.673 1.5 1.5z" id="outline"/><path class="icon-folder" d="M14.5 2h-7.492l-1 2h-3.504c-.277 0-.5.224-.5.5v8c0 .276.223.5.5.5h11.996c.275 0 .5-.224.5-.5v-10c0-.276-.225-.5-.5-.5zm-.496 2h-6.496l.5-1h5.996v1z" id="iconBg"/><path class="icon-vs-fg" d="M14 3v1h-6.5l.5-1h6z" id="iconFg"/></svg>
\ No newline at end of file
......@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
import * as resources from 'vs/base/common/resources';
import * as objects from 'vs/base/common/objects';
import { IFileService, IFileStat, FileKind } from 'vs/platform/files/common/files';
import { IQuickInputService, IQuickPickItem, IQuickPick, IQuickInputButton } from 'vs/platform/quickinput/common/quickInput';
import { IQuickInputService, IQuickPickItem, IQuickPick } from 'vs/platform/quickinput/common/quickInput';
import { URI } from 'vs/base/common/uri';
import { isWindows } from 'vs/base/common/platform';
import { ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
......@@ -35,8 +35,6 @@ const INVALID_FILE_CHARS = isWindows ? /[\\/:\*\?"<>\|]/g : /[\\/]/g;
const WINDOWS_FORBIDDEN_NAMES = /^(con|prn|aux|clock\$|nul|lpt[0-9]|com[0-9])$/i;
export class RemoteFileDialog {
private acceptButton: IQuickInputButton;
private fallbackListItem: FileQuickPickItem | undefined;
private options: IOpenDialogOptions;
private currentFolder: URI;
private filePickBox: IQuickPick<FileQuickPickItem>;
......@@ -79,13 +77,6 @@ export class RemoteFileDialog {
return Promise.resolve(undefined);
}
this.options = newOptions;
const openFileString = nls.localize('remoteFileDialog.localFileFallback', '(Open Local File)');
const openFolderString = nls.localize('remoteFileDialog.localFolderFallback', '(Open Local Folder)');
const openFileFolderString = nls.localize('remoteFileDialog.localFileFolderFallback', '(Open Local File or Folder)');
let fallbackLabel = options.canSelectFiles ? (options.canSelectFolders ? openFileFolderString : openFileString) : openFolderString;
this.fallbackListItem = this.getFallbackFileSystem(fallbackLabel);
return this.pickResource();
}
......@@ -100,7 +91,6 @@ export class RemoteFileDialog {
this.options = newOptions;
this.options.canSelectFolders = true;
this.options.canSelectFiles = true;
this.fallbackListItem = this.getFallbackFileSystem(nls.localize('remoteFileDialog.localSaveFallback', '(Save Local File)'));
return new Promise<URI | undefined>((resolve) => {
this.pickResource(true).then(folderUri => {
......@@ -129,20 +119,13 @@ export class RemoteFileDialog {
private remoteUriFrom(path: string): URI {
path = path.replace(/\\/g, '/');
return resources.toLocalResource(URI.from({ scheme: this.scheme, path }), this.remoteAuthority);
return resources.toLocalResource(URI.from({ scheme: this.scheme, path }), this.scheme === Schemas.file ? undefined : this.remoteAuthority);
}
private getScheme(defaultUri: URI | undefined, available: string[] | undefined): string {
return defaultUri ? defaultUri.scheme : (available ? available[0] : Schemas.file);
}
private getFallbackFileSystem(label: string): FileQuickPickItem | undefined {
if (this.options && this.options.availableFileSystems && (this.options.availableFileSystems.length > 1)) {
return { label: label, uri: URI.from({ scheme: this.options.availableFileSystems[1] }), isFolder: true };
}
return undefined;
}
private async getUserHome(): Promise<URI> {
if (this.scheme !== Schemas.file) {
const env = await this.remoteAgentService.getEnvironment();
......@@ -182,30 +165,22 @@ export class RemoteFileDialog {
}
}
}
this.acceptButton = { iconPath: this.getDialogIcons('accept'), tooltip: this.options.title };
return new Promise<URI | undefined>(async (resolve) => {
this.filePickBox = this.quickInputService.createQuickPick<FileQuickPickItem>();
this.filePickBox.matchOnLabel = false;
this.filePickBox.autoFocusOnList = false;
this.filePickBox.ok = true;
if (this.options && this.options.availableFileSystems && (this.options.availableFileSystems.length > 1)) {
this.filePickBox.customButton = true;
this.filePickBox.customLabel = nls.localize('remoteFileDialog.local', 'Show Local');
}
let isResolving = false;
let isAcceptHandled = false;
this.currentFolder = homedir;
this.userEnteredPathSegment = '';
this.autoCompletePathSegment = '';
this.filePickBox.buttons = [this.acceptButton];
this.filePickBox.onDidTriggerButton(_ => {
// accept button
const resolveValue = this.addPostfix(this.remoteUriFrom(this.filePickBox.value));
this.validate(resolveValue).then(validated => {
if (validated) {
isResolving = true;
this.filePickBox.hide();
doResolve(this, resolveValue);
}
});
});
this.filePickBox.title = this.options.title;
this.filePickBox.value = this.pathFromUri(this.currentFolder);
......@@ -217,6 +192,28 @@ export class RemoteFileDialog {
dialog.filePickBox.dispose();
}
this.filePickBox.onDidCustom(() => {
if (isAcceptHandled || this.filePickBox.busy) {
return;
}
isAcceptHandled = true;
isResolving = true;
if (this.options.availableFileSystems && (this.options.availableFileSystems.length > 1)) {
this.options.availableFileSystems.shift();
}
this.options.defaultUri = undefined;
if (this.requiresTrailing) {
return this.fileDialogService.showSaveDialog(this.options).then(result => {
doResolve(this, result);
});
} else {
return this.fileDialogService.showOpenDialog(this.options).then(result => {
doResolve(this, result ? result[0] : undefined);
});
}
});
this.filePickBox.onDidAccept(_ => {
if (isAcceptHandled || this.filePickBox.busy) {
return;
......@@ -294,24 +291,6 @@ export class RemoteFileDialog {
}
private async onDidAccept(): Promise<URI | undefined> {
// Check if Open Local has been selected
const selectedItems: ReadonlyArray<FileQuickPickItem> = this.filePickBox.selectedItems;
if (selectedItems && (selectedItems.length > 0) && (selectedItems[0] === this.fallbackListItem)) {
if (this.options.availableFileSystems && (this.options.availableFileSystems.length > 1)) {
this.options.availableFileSystems.shift();
}
this.options.defaultUri = undefined;
if (this.requiresTrailing) {
return this.fileDialogService.showSaveDialog(this.options).then(result => {
return result;
});
} else {
return this.fileDialogService.showOpenDialog(this.options).then(result => {
return result ? result[0] : undefined;
});
}
}
let resolveValue: URI | undefined;
let navigateValue: URI | undefined;
const trimmedPickBoxValue = ((this.filePickBox.value.length > 1) && this.endsWithSlash(this.filePickBox.value)) ? this.filePickBox.value.substr(0, this.filePickBox.value.length - 1) : this.filePickBox.value;
......@@ -687,10 +666,6 @@ export class RemoteFileDialog {
if (backDir) {
sorted.unshift(backDir);
}
if (this.fallbackListItem) {
sorted.push(this.fallbackListItem);
}
return sorted;
}
......@@ -724,11 +699,4 @@ export class RemoteFileDialog {
return undefined;
}
}
private getDialogIcons(name: string): { light: URI, dark: URI } {
return {
dark: URI.parse(require.toUrl(`vs/workbench/services/dialogs/browser/media/dark/${name}.svg`)),
light: URI.parse(require.toUrl(`vs/workbench/services/dialogs/browser/media/light/${name}.svg`))
};
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册