提交 7cd87957 编写于 作者: C Christof Marti

Input box and checkbox list (#45589)

上级 ea1e57b3
......@@ -12,6 +12,10 @@
margin-left: -300px;
}
.quick-input-box {
margin: 6px;
}
.quick-input-actions {
padding: 3px;
}
......
......@@ -10,10 +10,7 @@ import { Component } from 'vs/workbench/common/component';
import { IQuickInputService } from 'vs/platform/quickInput/common/quickInput';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { Dimension } from 'vs/base/browser/builder';
import { IDelegate, IRenderer } from 'vs/base/browser/ui/list/list';
import * as dom from 'vs/base/browser/dom';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { WorkbenchList } from 'vs/platform/list/browser/listService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { registerThemingParticipant, ITheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService';
import { buttonBackground, buttonForeground, contrastBorder, buttonHoverBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
......@@ -21,70 +18,11 @@ import { SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND } from 'vs/workbench/common/th
import { IPickOpenEntry, IPickOptions } from 'vs/platform/quickOpen/common/quickOpen';
import { TPromise } from 'vs/base/common/winjs.base';
import { CancellationToken } from 'vs/base/common/cancellation';
import { QuickInputCheckboxList } from './quickInputCheckboxList';
import { QuickInputBox } from './quickInputBox';
const $ = dom.$;
export interface ISelectedElement {
item: object;
label: string;
selected: boolean;
}
interface ISelectedElementTemplateData {
element: HTMLElement;
name: HTMLElement;
checkbox: HTMLInputElement;
context: ISelectedElement;
toDispose: IDisposable[];
}
class SelectedElementRenderer implements IRenderer<ISelectedElement, ISelectedElementTemplateData> {
static readonly ID = 'selectedelement';
get templateId() {
return SelectedElementRenderer.ID;
}
renderTemplate(container: HTMLElement): ISelectedElementTemplateData {
const data: ISelectedElementTemplateData = Object.create(null);
data.element = dom.append(container, $('.selected_element'));
data.checkbox = <HTMLInputElement>$('input');
data.checkbox.type = 'checkbox';
data.toDispose = [];
data.toDispose.push(dom.addStandardDisposableListener(data.checkbox, 'change', (e) => data.context.selected = !data.context.selected));
dom.append(data.element, data.checkbox);
data.name = dom.append(data.element, $('span.label'));
return data;
}
renderElement(element: ISelectedElement, index: number, data: ISelectedElementTemplateData): void {
data.context = element;
data.name.textContent = element.label;
data.element.title = data.name.textContent;
data.checkbox.checked = element.selected;
}
disposeTemplate(templateData: ISelectedElementTemplateData): void {
dispose(templateData.toDispose);
}
}
class SelectedElementDelegate implements IDelegate<ISelectedElement> {
getHeight(element: ISelectedElement): number {
return 22;
}
getTemplateId(element: ISelectedElement): string {
return SelectedElementRenderer.ID;
}
}
export class QuickInputService extends Component implements IQuickInputService {
public _serviceBrand: any;
......@@ -95,9 +33,9 @@ export class QuickInputService extends Component implements IQuickInputService {
private layoutDimensions: Dimension;
private container: HTMLElement;
private list: WorkbenchList<ISelectedElement>;
private inputBox: QuickInputBox;
private checkboxList: QuickInputCheckboxList;
private elements: ISelectedElement[] = [];
private resolve: (value?: object[] | Thenable<object[]>) => void;
constructor(
......@@ -117,12 +55,13 @@ export class QuickInputService extends Component implements IQuickInputService {
this.container = dom.append(workbench, $('.quick-input-widget'));
this.container.style.display = 'none';
const listContainer = dom.append(this.container, $('.quick-input-list'));
const delegate = new SelectedElementDelegate();
this.list = this.instantiationService.createInstance(WorkbenchList, listContainer, delegate, [new SelectedElementRenderer()], {
identityProvider: element => element.label,
multipleSelectionSupport: false
}) as WorkbenchList<ISelectedElement>;
this.inputBox = new QuickInputBox(this.container);
this.inputBox.style(this.themeService.getTheme());
this.inputBox.onInput(value => {
this.checkboxList.filter(value);
});
this.checkboxList = this.instantiationService.createInstance(QuickInputCheckboxList, this.container);
const buttonContainer = dom.append(this.container, $('.quick-input-actions'));
const cancel = dom.append(buttonContainer, $('button'));
......@@ -144,7 +83,7 @@ export class QuickInputService extends Component implements IQuickInputService {
private close(ok: boolean) {
if (ok) {
this.resolve(this.elements.filter(e => e.selected).map(e => e.item));
this.resolve(this.checkboxList.getSelectedElements());
} else {
this.resolve();
}
......@@ -154,18 +93,13 @@ export class QuickInputService extends Component implements IQuickInputService {
async pick<T extends IPickOpenEntry>(picks: TPromise<T[]>, options?: IPickOptions, token?: CancellationToken): TPromise<T[]> {
this.create();
this.inputBox.setPlaceholder(options.placeHolder || '');
// TODO: Progress indication.
this.elements = (await picks).map(item => ({
item,
label: item.label,
selected: !!item.selected
}));
this.list.splice(0, this.list.length, this.elements);
this.checkboxList.setElements(await picks);
this.container.style.display = null;
this.updateLayout();
this.list.focusFirst();
this.list.domFocus();
this.inputBox.setFocus();
return new TPromise<T[]>(resolve => this.resolve = resolve);
}
......@@ -185,9 +119,14 @@ export class QuickInputService extends Component implements IQuickInputService {
style.width = width + 'px';
style.marginLeft = '-' + (width / 2) + 'px';
this.list.layout();
this.inputBox.layout();
this.checkboxList.layout();
}
}
protected updateStyles() {
this.inputBox.style(this.themeService.getTheme());
}
}
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./quickInput';
import * as dom from 'vs/base/browser/dom';
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import * as nls from 'vs/nls';
import { inputBackground, inputForeground, inputBorder } from 'vs/platform/theme/common/colorRegistry';
import { ITheme } from 'vs/platform/theme/common/themeService';
import { IDisposable } from 'vs/base/common/lifecycle';
const $ = dom.$;
const DEFAULT_INPUT_ARIA_LABEL = nls.localize('quickInputBoxAriaLabel', "Type to narrow down results.");
export class QuickInputBox {
public container: HTMLElement;
private inputBox: InputBox;
constructor(
private parent: HTMLElement
) {
this.container = dom.append(this.parent, $('.quick-input-box'));
this.inputBox = new InputBox(this.container, null, {
ariaLabel: DEFAULT_INPUT_ARIA_LABEL
});
// ARIA
const inputElement = this.inputBox.inputElement;
inputElement.setAttribute('role', 'combobox');
inputElement.setAttribute('aria-haspopup', 'false');
inputElement.setAttribute('aria-autocomplete', 'list');
}
onInput(handler: (event: string) => void): IDisposable {
return this.inputBox.onDidChange(handler);
}
setPlaceholder(placeholder: string) {
this.inputBox.setPlaceHolder(placeholder);
}
setFocus(): void {
this.inputBox.focus();
}
layout(): void {
this.inputBox.layout();
}
style(theme: ITheme) {
this.inputBox.style({
inputForeground: theme.getColor(inputForeground),
inputBackground: theme.getColor(inputBackground),
inputBorder: theme.getColor(inputBorder)
});
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./quickInput';
import { IDelegate, IRenderer } from 'vs/base/browser/ui/list/list';
import * as dom from 'vs/base/browser/dom';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { WorkbenchList } from 'vs/platform/list/browser/listService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
import { IMatch } from 'vs/base/common/filters';
import { matchesFuzzyOcticonAware, parseOcticons } from 'vs/base/common/octicon';
import { compareAnything } from 'vs/base/common/comparers';
const $ = dom.$;
export interface ISelectedElement {
index: number;
item: object;
label: string;
shouldAlwaysShow?: boolean;
hidden?: boolean;
selected?: boolean;
labelHighlights?: IMatch[];
descriptionHighlights?: IMatch[];
detailHighlights?: IMatch[];
}
interface ISelectedElementTemplateData {
element: HTMLElement;
name: HTMLElement;
checkbox: HTMLInputElement;
context: ISelectedElement;
toDispose: IDisposable[];
}
class SelectedElementRenderer implements IRenderer<ISelectedElement, ISelectedElementTemplateData> {
static readonly ID = 'selectedelement';
get templateId() {
return SelectedElementRenderer.ID;
}
renderTemplate(container: HTMLElement): ISelectedElementTemplateData {
const data: ISelectedElementTemplateData = Object.create(null);
data.element = dom.append(container, $('.selected_element'));
data.checkbox = <HTMLInputElement>$('input');
data.checkbox.type = 'checkbox';
data.toDispose = [];
data.toDispose.push(dom.addStandardDisposableListener(data.checkbox, 'change', (e) => data.context.selected = !data.context.selected));
dom.append(data.element, data.checkbox);
data.name = dom.append(data.element, $('span.label'));
return data;
}
renderElement(element: ISelectedElement, index: number, data: ISelectedElementTemplateData): void {
data.context = element;
data.name.textContent = element.label;
data.element.title = data.name.textContent;
data.checkbox.checked = element.selected;
}
disposeTemplate(templateData: ISelectedElementTemplateData): void {
dispose(templateData.toDispose);
}
}
class SelectedElementDelegate implements IDelegate<ISelectedElement> {
getHeight(element: ISelectedElement): number {
return 22;
}
getTemplateId(element: ISelectedElement): string {
return SelectedElementRenderer.ID;
}
}
export class QuickInputCheckboxList {
container: HTMLElement;
private list: WorkbenchList<ISelectedElement>;
private elements: ISelectedElement[] = [];
constructor(
private parent: HTMLElement,
@IInstantiationService private instantiationService: IInstantiationService
) {
this.container = dom.append(this.parent, $('.quick-input-checkbox-list'));
const delegate = new SelectedElementDelegate();
this.list = this.instantiationService.createInstance(WorkbenchList, this.container, delegate, [new SelectedElementRenderer()], {
identityProvider: element => element.label,
multipleSelectionSupport: false
}) as WorkbenchList<ISelectedElement>;
}
setElements(elements: IPickOpenEntry[]): void {
this.elements = elements.map((item, index) => ({
index,
item,
label: item.label,
selected: !!item.selected
}));
this.list.splice(0, this.list.length, this.elements);
}
getSelectedElements() {
return this.elements.filter(e => e.selected)
.map(e => e.item);
}
setFocus(): void {
this.list.focusFirst();
this.list.domFocus();
}
layout(): void {
this.list.layout();
}
filter(query: string) {
query = query.trim();
// Reset filtering
if (!query) {
this.elements.forEach(element => {
element.labelHighlights = undefined;
element.descriptionHighlights = undefined;
element.detailHighlights = undefined;
element.hidden = false;
});
}
// Filter by value (since we support octicons, use octicon aware fuzzy matching)
else {
this.elements.forEach(element => {
const labelHighlights = matchesFuzzyOcticonAware(query, parseOcticons(element.label));
const descriptionHighlights = undefined; // TODO matchesFuzzyOcticonAware(query, parseOcticons(element.description));
const detailHighlights = undefined; // TODO matchesFuzzyOcticonAware(query, parseOcticons(element.detail));
if (element.shouldAlwaysShow || labelHighlights || descriptionHighlights || detailHighlights) {
element.labelHighlights = labelHighlights;
element.descriptionHighlights = descriptionHighlights;
element.detailHighlights = detailHighlights;
element.hidden = false;
} else {
element.labelHighlights = undefined;
element.descriptionHighlights = undefined;
element.detailHighlights = undefined;
element.hidden = true;
}
});
}
// Sort by value
const normalizedSearchValue = query.toLowerCase();
this.elements.sort((a, b) => {
if (!query) {
return a.index - b.index; // restore natural order
}
return compareEntries(a, b, normalizedSearchValue);
});
this.list.splice(0, this.list.length, this.elements.filter(element => !element.hidden));
this.list.layout();
if (query) {
this.list.focusFirst();
}
}
}
function compareEntries(elementA: ISelectedElement, elementB: ISelectedElement, lookFor: string): number {
const labelHighlightsA = elementA.labelHighlights || [];
const labelHighlightsB = elementB.labelHighlights || [];
if (labelHighlightsA.length && !labelHighlightsB.length) {
return -1;
}
if (!labelHighlightsA.length && labelHighlightsB.length) {
return 1;
}
return compareAnything(elementA.label, elementB.label, lookFor);
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册