mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-30 01:25:38 -05:00
Refresh master with initial release/0.24 snapshot (#332)
* Initial port of release/0.24 source code * Fix additional headers * Fix a typo in launch.json
This commit is contained in:
36
src/sql/base/browser/ui/button/button.ts
Normal file
36
src/sql/base/browser/ui/button/button.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { Button as vsButton, IButtonOptions, IButtonStyles as vsIButtonStyles } from 'vs/base/browser/ui/button/button';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
|
||||
export interface IButtonStyles extends vsIButtonStyles {
|
||||
buttonFocusOutline?: Color;
|
||||
}
|
||||
|
||||
export class Button extends vsButton {
|
||||
private buttonFocusOutline: Color;
|
||||
|
||||
constructor(container: any, options?: IButtonOptions) {
|
||||
super(container, options);
|
||||
this.buttonFocusOutline = null;
|
||||
|
||||
this.$el.on(DOM.EventType.FOCUS, (e) => {
|
||||
this.$el.style('outline-color', this.buttonFocusOutline ? this.buttonFocusOutline.toString() : null);
|
||||
this.$el.style('outline-width', '1px');
|
||||
});
|
||||
}
|
||||
|
||||
public style(styles: IButtonStyles): void {
|
||||
super.style(styles);
|
||||
this.buttonFocusOutline = styles.buttonFocusOutline;
|
||||
}
|
||||
|
||||
public set title(value: string) {
|
||||
this.$el.title(value);
|
||||
}
|
||||
}
|
||||
@@ -2,53 +2,85 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import 'vs/css!sql/base/browser/ui/checkbox/media/checkbox';
|
||||
import { Checkbox as vsCheckbox, ICheckboxOpts, ICheckboxStyles } from 'vs/base/browser/ui/checkbox/checkbox';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
|
||||
const defaultOpts = {
|
||||
inputActiveOptionBorder: Color.fromHex('#007ACC'),
|
||||
actionClassName: ' sql-checkbox'
|
||||
};
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
|
||||
/**
|
||||
* Extends Checkbox to include Carbon checkbox icon and styling.
|
||||
*/
|
||||
export class Checkbox extends vsCheckbox {
|
||||
private _inputActiveOptionBorder: Color;
|
||||
export interface ICheckboxOptions {
|
||||
label: string;
|
||||
enabled?: boolean;
|
||||
checked?: boolean;
|
||||
onChange?: (val: boolean) => void;
|
||||
}
|
||||
|
||||
constructor(opts: ICheckboxOpts) {
|
||||
super({
|
||||
actionClassName: opts.actionClassName + defaultOpts.actionClassName,
|
||||
title: opts.title,
|
||||
isChecked: opts.isChecked,
|
||||
onChange: opts.onChange,
|
||||
onKeyDown: opts.onKeyDown,
|
||||
inputActiveOptionBorder: opts.inputActiveOptionBorder
|
||||
export class Checkbox extends Widget {
|
||||
private _el: HTMLInputElement;
|
||||
private _label: HTMLSpanElement;
|
||||
|
||||
private _onChange = new Emitter<boolean>();
|
||||
public readonly onChange: Event<boolean> = this._onChange.event;
|
||||
|
||||
constructor(container: HTMLElement, opts: ICheckboxOptions) {
|
||||
super();
|
||||
|
||||
this._el = document.createElement('input');
|
||||
this._el.type = 'checkbox';
|
||||
|
||||
this.onchange(this._el, e => {
|
||||
this._onChange.fire(this.checked);
|
||||
});
|
||||
this._inputActiveOptionBorder = opts.inputActiveOptionBorder ? opts.inputActiveOptionBorder : defaultOpts.inputActiveOptionBorder;
|
||||
|
||||
this.onkeydown(this._el, e => {
|
||||
if (e.equals(KeyCode.Enter)) {
|
||||
this.checked = !this.checked;
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
this._label = document.createElement('span');
|
||||
|
||||
this.label = opts.label;
|
||||
this.enabled = opts.enabled || true;
|
||||
this.checked = opts.checked || false;
|
||||
|
||||
if (opts.onChange) {
|
||||
this.onChange(opts.onChange);
|
||||
}
|
||||
|
||||
container.appendChild(this._el);
|
||||
container.appendChild(this._label);
|
||||
}
|
||||
|
||||
public enable(): void {
|
||||
super.enable();
|
||||
this.domNode.classList.remove('disabled');
|
||||
public set label(val: string) {
|
||||
this._label.innerText = val;
|
||||
}
|
||||
|
||||
public set enabled(val: boolean) {
|
||||
this._el.disabled = !val;
|
||||
}
|
||||
|
||||
public get enabled(): boolean {
|
||||
return !this._el.disabled;
|
||||
}
|
||||
|
||||
public set checked(val: boolean) {
|
||||
this._el.checked = val;
|
||||
}
|
||||
|
||||
public get checked(): boolean {
|
||||
return this._el.checked;
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this._el.focus();
|
||||
}
|
||||
|
||||
public disable(): void {
|
||||
super.disable();
|
||||
this.domNode.classList.add('disabled');
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
public style(styles: ICheckboxStyles): void {
|
||||
if (styles.inputActiveOptionBorder) {
|
||||
this._inputActiveOptionBorder = styles.inputActiveOptionBorder;
|
||||
}
|
||||
this.applyStyles();
|
||||
public enable(): void {
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
protected applyStyles(): void {
|
||||
if (this.domNode) {
|
||||
this.domNode.style.borderColor = this._inputActiveOptionBorder ? this._inputActiveOptionBorder.toString(): defaultOpts.inputActiveOptionBorder.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
|
||||
export interface ICheckboxOptions {
|
||||
label: string;
|
||||
enabled?: boolean;
|
||||
checked?: boolean;
|
||||
}
|
||||
|
||||
export class Checkbox extends Disposable {
|
||||
private _el: HTMLInputElement;
|
||||
private _label: HTMLSpanElement;
|
||||
|
||||
private _onChange = new Emitter<boolean>();
|
||||
public readonly onChange: Event<boolean> = this._onChange.event;
|
||||
|
||||
constructor(container: HTMLElement, opts: ICheckboxOptions) {
|
||||
super();
|
||||
|
||||
this._el = document.createElement('input');
|
||||
this._el.type = 'checkbox';
|
||||
|
||||
this._register(DOM.addDisposableListener(this._el, DOM.EventType.CHANGE, e => {
|
||||
this._onChange.fire(e);
|
||||
}));
|
||||
|
||||
this._register(DOM.addStandardDisposableListener(this._el, DOM.EventType.KEY_DOWN, (e: StandardKeyboardEvent) => {
|
||||
if (e.equals(KeyCode.Enter)) {
|
||||
this.checked = !this.checked;
|
||||
e.stopPropagation();
|
||||
}
|
||||
}));
|
||||
|
||||
this._label = document.createElement('span');
|
||||
|
||||
this.label = opts.label;
|
||||
this.enabled = opts.enabled;
|
||||
this.checked = opts.checked;
|
||||
|
||||
container.appendChild(this._el);
|
||||
container.appendChild(this._label);
|
||||
}
|
||||
|
||||
public set label(val: string) {
|
||||
this._label.innerText = val;
|
||||
}
|
||||
|
||||
public set enabled(val: boolean) {
|
||||
this._el.disabled = !val;
|
||||
}
|
||||
|
||||
public get enabled(): boolean {
|
||||
return !this._el.disabled;
|
||||
}
|
||||
|
||||
public set checked(val: boolean) {
|
||||
this._el.checked = val;
|
||||
}
|
||||
|
||||
public get checked(): boolean {
|
||||
return this._el.checked;
|
||||
}
|
||||
}
|
||||
@@ -11,13 +11,14 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
|
||||
import { EventType as GestureEventType } from 'vs/base/browser/touch';
|
||||
import { List } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
|
||||
import { Button } from 'sql/base/browser/ui/button/button';
|
||||
import { attachButtonStyler } from 'sql/common/theme/styler';
|
||||
|
||||
export interface IDropdownStyles {
|
||||
backgroundColor?: Color;
|
||||
foregroundColor?: Color;
|
||||
@@ -46,6 +47,14 @@ export class DropdownList extends Dropdown {
|
||||
this._action.run();
|
||||
this.hide();
|
||||
}));
|
||||
this.toDispose.push(DOM.addDisposableListener(button.getElement(), DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyCode.Enter)) {
|
||||
e.stopPropagation();
|
||||
this._action.run();
|
||||
this.hide();
|
||||
}
|
||||
}));
|
||||
attachButtonStyler(button, this._themeService);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,15 +6,15 @@
|
||||
import 'vs/css!./media/dropdownList';
|
||||
|
||||
import { ToggleDropdownAction } from './actions';
|
||||
import { DropdownDataSource, DropdownFilter, DropdownModel, DropdownRenderer, DropdownController } from './dropdownTree';
|
||||
|
||||
import { IContextViewProvider, ContextView } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { InputBox, IInputBoxStyles } from 'sql/base/browser/ui/inputBox/inputBox';
|
||||
import { IMessage, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { List, IListStyles } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { IDelegate, IRenderer } from 'vs/base/browser/ui/list/list';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
@@ -22,6 +22,7 @@ import * as nls from 'vs/nls';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
||||
|
||||
export interface IDropdownOptions extends IDropdownStyles {
|
||||
/**
|
||||
@@ -72,47 +73,20 @@ interface TableTemplate {
|
||||
label: HTMLElement;
|
||||
}
|
||||
|
||||
class Delegate implements IDelegate<ListResource> {
|
||||
getHeight = (): number => 22;
|
||||
|
||||
getTemplateId(element: ListResource): string {
|
||||
return 'string';
|
||||
}
|
||||
}
|
||||
|
||||
class Renderer implements IRenderer<ListResource, TableTemplate> {
|
||||
static TEMPLATE_ID = 'string';
|
||||
get templateId(): string { return Renderer.TEMPLATE_ID; }
|
||||
|
||||
renderTemplate(container: HTMLElement): TableTemplate {
|
||||
const row = $('div.list-row').style('height', '22px').style('padding-left', '5px').getHTMLElement();
|
||||
DOM.append(container, row);
|
||||
const label = $('span.label').style('margin', 'auto').getHTMLElement();
|
||||
DOM.append(row, label);
|
||||
|
||||
return { label };
|
||||
}
|
||||
|
||||
renderElement(resource: ListResource, index: number, template: TableTemplate): void {
|
||||
template.label.innerText = resource.label;
|
||||
}
|
||||
|
||||
disposeTemplate(template: TableTemplate): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
export class Dropdown extends Disposable {
|
||||
private $el: Builder;
|
||||
private $input: Builder;
|
||||
private $list: Builder;
|
||||
private $treeContainer: Builder;
|
||||
private _input: InputBox;
|
||||
private _list: List<ListResource>;
|
||||
private _values: string[];
|
||||
private _tree: Tree;
|
||||
private _options: IDropdownOptions;
|
||||
private _toggleAction: ToggleDropdownAction;
|
||||
// we have to create our own contextview since otherwise inputbox will override ours
|
||||
private _contextView: ContextView;
|
||||
private _dataSource = new DropdownDataSource();
|
||||
private _filter = new DropdownFilter();
|
||||
private _renderer = new DropdownRenderer();
|
||||
private _controller = new DropdownController();
|
||||
|
||||
private _onBlur = this._register(new Emitter<void>());
|
||||
public onBlur: Event<void> = this._onBlur.event;
|
||||
@@ -132,13 +106,16 @@ export class Dropdown extends Disposable {
|
||||
super();
|
||||
this._contextView = new ContextView(document.body);
|
||||
this._options = mixin(opt, defaults, false) as IDropdownOptions;
|
||||
this._values = this._options.values;
|
||||
this.$el = $('.dropdown').style('width', '100%').appendTo(container);
|
||||
|
||||
this.$input = $('.dropdown-input').style('width', '100%').appendTo(this.$el);
|
||||
this.$list = $('.dropdown-list');
|
||||
this.$treeContainer = $('.dropdown-tree');
|
||||
|
||||
this._toggleAction = new ToggleDropdownAction(() => this._showList());
|
||||
this._toggleAction = new ToggleDropdownAction(() => {
|
||||
this._showList();
|
||||
this._tree.DOMFocus();
|
||||
this._tree.focusFirst();
|
||||
});
|
||||
|
||||
this._input = new InputBox(this.$input.getHTMLElement(), contextViewService, {
|
||||
validationOptions: {
|
||||
@@ -154,12 +131,12 @@ export class Dropdown extends Disposable {
|
||||
}));
|
||||
|
||||
this._register(DOM.addDisposableListener(this._input.inputElement, DOM.EventType.BLUR, () => {
|
||||
if (!this._list.isDOMFocused) {
|
||||
if (!this._tree.isDOMFocused()) {
|
||||
this._onBlur.fire();
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(DOM.addStandardDisposableListener(this._input.inputElement, DOM.EventType.KEY_UP, (e: StandardKeyboardEvent) => {
|
||||
this._register(DOM.addStandardDisposableListener(this._input.inputElement, DOM.EventType.KEY_DOWN, (e: StandardKeyboardEvent) => {
|
||||
switch (e.keyCode) {
|
||||
case KeyCode.Enter:
|
||||
if (this._input.validate()) {
|
||||
@@ -168,7 +145,7 @@ export class Dropdown extends Disposable {
|
||||
e.stopPropagation();
|
||||
break;
|
||||
case KeyCode.Escape:
|
||||
if (this.$list.getHTMLElement().parentElement) {
|
||||
if (this.$treeContainer.getHTMLElement().parentElement) {
|
||||
this._input.validate();
|
||||
this._onBlur.fire();
|
||||
this._contextView.hide();
|
||||
@@ -182,44 +159,55 @@ export class Dropdown extends Disposable {
|
||||
e.stopPropagation();
|
||||
break;
|
||||
case KeyCode.DownArrow:
|
||||
if (!this.$list.getHTMLElement().parentElement) {
|
||||
if (!this.$treeContainer.getHTMLElement().parentElement) {
|
||||
this._showList();
|
||||
}
|
||||
this._list.getHTMLElement().focus();
|
||||
this._tree.DOMFocus();
|
||||
this._tree.focusFirst();
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
break;
|
||||
}
|
||||
}));
|
||||
|
||||
this._list = new List(this.$list.getHTMLElement(), new Delegate(), [new Renderer()]);
|
||||
if (this._values) {
|
||||
this._list.splice(0, this._list.length, this._values.map(i => { return { label: i }; }));
|
||||
let height = this._list.length * 22 > this._options.maxHeight ? this._options.maxHeight : this._list.length * 22;
|
||||
this.$list.style('height', height + 'px').style('width', DOM.getContentWidth(this.$input.getHTMLElement()) + 'px');
|
||||
}
|
||||
this._tree = new Tree(this.$treeContainer.getHTMLElement(), {
|
||||
dataSource: this._dataSource,
|
||||
filter: this._filter,
|
||||
renderer: this._renderer,
|
||||
controller: this._controller
|
||||
});
|
||||
|
||||
this._list.onSelectionChange(e => {
|
||||
if (e.elements.length === 1) {
|
||||
this.value = e.elements[0].label;
|
||||
this._onValueChange.fire(e.elements[0].label);
|
||||
this._contextView.hide();
|
||||
}
|
||||
this.values = this._options.values;
|
||||
|
||||
this._controller.onSelectionChange(e => {
|
||||
this.value = e.value;
|
||||
this._onValueChange.fire(e.value);
|
||||
this._input.focus();
|
||||
this._contextView.hide();
|
||||
});
|
||||
|
||||
this._input.onDidChange(e => {
|
||||
if (this._values) {
|
||||
this._list.splice(0, this._list.length, this._values.filter(i => i.includes(e)).map(i => { return { label: i }; }));
|
||||
let height = this._list.length * 22 > this._options.maxHeight ? this._options.maxHeight : this._list.length * 22;
|
||||
this.$list.style('height', height + 'px').style('width', DOM.getContentWidth(this.$input.getHTMLElement()) + 'px');
|
||||
this._list.layout(parseInt(this.$list.style('height')));
|
||||
if (this._dataSource.options) {
|
||||
this._filter.filterString = e;
|
||||
let filteredLength = this._dataSource.options.reduce((p, i) => {
|
||||
if (this._filter.isVisible(undefined, i)) {
|
||||
return p + 1;
|
||||
} else {
|
||||
return p;
|
||||
}
|
||||
}, 0);
|
||||
let height = filteredLength * this._renderer.getHeight(undefined, undefined) > this._options.maxHeight ? this._options.maxHeight : filteredLength * this._renderer.getHeight(undefined, undefined);
|
||||
this.$treeContainer.style('height', height + 'px').style('width', DOM.getContentWidth(this.$input.getHTMLElement()) + 'px');
|
||||
this._tree.layout(parseInt(this.$treeContainer.style('height')));
|
||||
this._tree.refresh();
|
||||
}
|
||||
});
|
||||
|
||||
this._register(this._contextView);
|
||||
this._register(this.$el);
|
||||
this._register(this.$input);
|
||||
this._register(this.$list);
|
||||
this._register(this._list);
|
||||
this._register(this.$treeContainer);
|
||||
this._register(this._tree);
|
||||
this._register(this._input);
|
||||
this._register(this._contextView);
|
||||
}
|
||||
@@ -230,12 +218,12 @@ export class Dropdown extends Disposable {
|
||||
this._contextView.show({
|
||||
getAnchor: () => this.$input.getHTMLElement(),
|
||||
render: container => {
|
||||
this.$list.appendTo(container);
|
||||
this._list.layout(parseInt(this.$list.style('height')));
|
||||
this.$treeContainer.appendTo(container);
|
||||
this._tree.layout(parseInt(this.$treeContainer.style('height')));
|
||||
return { dispose: () => { } };
|
||||
},
|
||||
onDOMEvent: (e, activeElement) => {
|
||||
if (!DOM.isAncestor(activeElement, this.$el.getHTMLElement())) {
|
||||
if (!DOM.isAncestor(activeElement, this.$el.getHTMLElement()) && !DOM.isAncestor(activeElement, this.$treeContainer.getHTMLElement())) {
|
||||
this._input.validate();
|
||||
this._onBlur.fire();
|
||||
this._contextView.hide();
|
||||
@@ -246,12 +234,15 @@ export class Dropdown extends Disposable {
|
||||
}
|
||||
|
||||
public set values(vals: string[]) {
|
||||
this._values = vals;
|
||||
this._list.splice(0, this._list.length, this._values.map(i => { return { label: i }; }));
|
||||
let height = this._list.length * 22 > this._options.maxHeight ? this._options.maxHeight : this._list.length * 22;
|
||||
this.$list.style('height', height + 'px').style('width', DOM.getContentWidth(this.$input.getHTMLElement()) + 'px');
|
||||
this._list.layout(parseInt(this.$list.style('height')));
|
||||
this._input.validate();
|
||||
if (vals) {
|
||||
this._filter.filterString = '';
|
||||
this._dataSource.options = vals.map(i => { return { value: i }; });
|
||||
let height = this._dataSource.options.length * 22 > this._options.maxHeight ? this._options.maxHeight : this._dataSource.options.length * 22;
|
||||
this.$treeContainer.style('height', height + 'px').style('width', DOM.getContentWidth(this.$input.getHTMLElement()) + 'px');
|
||||
this._tree.layout(parseInt(this.$treeContainer.style('height')));
|
||||
this._tree.setInput(new DropdownModel());
|
||||
this._input.validate();
|
||||
}
|
||||
}
|
||||
|
||||
public get value(): string {
|
||||
@@ -272,14 +263,14 @@ export class Dropdown extends Disposable {
|
||||
}
|
||||
|
||||
style(style: IListStyles & IInputBoxStyles & IDropdownStyles) {
|
||||
this._list.style(style);
|
||||
this._tree.style(style);
|
||||
this._input.style(style);
|
||||
this.$list.style('background-color', style.contextBackground.toString());
|
||||
this.$list.style('outline', `1px solid ${style.contextBorder || this._options.contextBorder}`);
|
||||
this.$treeContainer.style('background-color', style.contextBackground.toString());
|
||||
this.$treeContainer.style('outline', `1px solid ${style.contextBorder || this._options.contextBorder}`);
|
||||
}
|
||||
|
||||
private _inputValidator(value: string): IMessage {
|
||||
if (this._values && !this._values.includes(value)) {
|
||||
if (this._dataSource.options && !this._dataSource.options.find(i => i.value === value)) {
|
||||
if (this._options.strictSelection) {
|
||||
return {
|
||||
content: this._options.errorMessage,
|
||||
|
||||
121
src/sql/base/browser/ui/editableDropdown/dropdownTree.ts
Normal file
121
src/sql/base/browser/ui/editableDropdown/dropdownTree.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as tree from 'vs/base/parts/tree/browser/tree';
|
||||
import * as TreeDefaults from 'vs/base/parts/tree/browser/treeDefaults';
|
||||
import { Promise, TPromise } from 'vs/base/common/winjs.base';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { $ } from 'vs/base/browser/builder';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
|
||||
export interface Template {
|
||||
label: HTMLElement;
|
||||
}
|
||||
|
||||
export interface Resource {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class DropdownModel {
|
||||
public static ID = generateUuid();
|
||||
}
|
||||
|
||||
export class DropdownRenderer implements tree.IRenderer {
|
||||
public getHeight(tree: tree.ITree, element: Resource): number {
|
||||
return 22;
|
||||
}
|
||||
|
||||
public getTemplateId(tree: tree.ITree, element: Resource): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
public renderTemplate(tree: tree.ITree, templateId: string, container: HTMLElement) {
|
||||
const row = $('div.list-row').style('height', '22px').style('padding-left', '5px').getHTMLElement();
|
||||
DOM.append(container, row);
|
||||
const label = $('span.label').style('margin', 'auto').getHTMLElement();
|
||||
DOM.append(row, label);
|
||||
|
||||
return { label };
|
||||
}
|
||||
|
||||
public renderElement(tree: tree.ITree, element: Resource, templateId: string, templateData: Template): void {
|
||||
templateData.label.innerText = element.value;
|
||||
}
|
||||
|
||||
public disposeTemplate(tree: tree.ITree, templateId: string, templateData: Template): void {
|
||||
// no op
|
||||
}
|
||||
}
|
||||
|
||||
export class DropdownDataSource implements tree.IDataSource {
|
||||
public options: Array<Resource>;
|
||||
|
||||
public getId(tree: tree.ITree, element: Resource | DropdownModel): string {
|
||||
if (element instanceof DropdownModel) {
|
||||
return DropdownModel.ID;
|
||||
} else {
|
||||
return (element as Resource).value;
|
||||
}
|
||||
}
|
||||
|
||||
public hasChildren(tree: tree.ITree, element: Resource | DropdownModel): boolean {
|
||||
if (element instanceof DropdownModel) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public getChildren(tree: tree.ITree, element: Resource | DropdownModel): Promise<any, any> {
|
||||
if (element instanceof DropdownModel) {
|
||||
return TPromise.as(this.options);
|
||||
} else {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
public getParent(tree: tree.ITree, element: Resource | DropdownModel): Promise<any, any> {
|
||||
if (element instanceof DropdownModel) {
|
||||
return TPromise.as(undefined);
|
||||
} else {
|
||||
return TPromise.as(new DropdownModel());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class DropdownFilter extends TreeDefaults.DefaultFilter {
|
||||
public filterString: string;
|
||||
|
||||
public isVisible(tree: tree.ITree, element: Resource): boolean {
|
||||
return element.value.includes(this.filterString);
|
||||
}
|
||||
}
|
||||
|
||||
export class DropdownController extends TreeDefaults.DefaultController {
|
||||
private _onSelectionChange = new Emitter<Resource>();
|
||||
public readonly onSelectionChange: Event<Resource> = this._onSelectionChange.event;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
protected onLeftClick(tree: tree.ITree, element: any, eventish: TreeDefaults.ICancelableEvent, origin: string): boolean {
|
||||
let response = super.onLeftClick(tree, element, eventish, origin);
|
||||
if (response) {
|
||||
this._onSelectionChange.fire(tree.getSelection()[0]);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
protected onEnter(tree: tree.ITree, event: IKeyboardEvent): boolean {
|
||||
let response = super.onEnter(tree, event);
|
||||
if (response) {
|
||||
this._onSelectionChange.fire(tree.getSelection()[0]);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -21,3 +21,7 @@
|
||||
.dropdown .monaco-action-bar .action-item {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.dropdown-tree .list-row {
|
||||
margin-left: -33px;
|
||||
}
|
||||
|
||||
@@ -4,16 +4,15 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
|
||||
import { SelectBox } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import * as lifecycle from 'vs/base/common/lifecycle';
|
||||
import { SelectBox, ISelectBoxStyles } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { ISelectBoxStyles } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { IMessage, MessageType, defaultOpts } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { IContextViewProvider, AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { RenderOptions, renderFormattedText, renderText } from 'vs/base/browser/htmlContentRenderer';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
@@ -33,14 +32,12 @@ export interface IListBoxStyles {
|
||||
* Extends SelectBox to allow multiple selection and adding/remove items dynamically
|
||||
*/
|
||||
export class ListBox extends SelectBox {
|
||||
private _toDispose2: lifecycle.IDisposable[];
|
||||
private enabledSelectBackground: Color;
|
||||
private enabledSelectForeground: Color;
|
||||
private enabledSelectBorder: Color;
|
||||
private disabledSelectBackground: Color;
|
||||
private disabledSelectForeground: Color;
|
||||
private disabledSelectBorder: Color;
|
||||
private keyC = 33;
|
||||
|
||||
private inputValidationInfoBorder: Color;
|
||||
private inputValidationInfoBackground: Color;
|
||||
@@ -53,7 +50,7 @@ export class ListBox extends SelectBox {
|
||||
private contextViewProvider: IContextViewProvider;
|
||||
private isValid: boolean;
|
||||
|
||||
constructor(options: string[], selectedOption: string, contextViewProvider: IContextViewProvider) {
|
||||
constructor(options: string[], selectedOption: string, contextViewProvider: IContextViewProvider, private _clipboardService: IClipboardService) {
|
||||
super(options, 0);
|
||||
this.contextViewProvider = contextViewProvider;
|
||||
this.isValid = true;
|
||||
@@ -64,10 +61,7 @@ export class ListBox extends SelectBox {
|
||||
this.selectElement.style['width'] = 'inherit';
|
||||
this.selectElement.style['min-width'] = '100%';
|
||||
|
||||
this._toDispose2 = [];
|
||||
this._toDispose2.push(dom.addStandardDisposableListener(this.selectElement, 'keydown', (e) => {
|
||||
this.onKeyDown(e)
|
||||
}));
|
||||
this._register(dom.addStandardDisposableListener(this.selectElement, dom.EventType.KEY_DOWN, e => this.onKeyDown(e)));
|
||||
|
||||
this.enabledSelectBackground = this.selectBackground;
|
||||
this.enabledSelectForeground = this.selectForeground;
|
||||
@@ -88,11 +82,11 @@ export class ListBox extends SelectBox {
|
||||
}
|
||||
|
||||
public style(styles: IListBoxStyles): void {
|
||||
var superStyle: ISelectBoxStyles = {
|
||||
let superStyle: ISelectBoxStyles = {
|
||||
selectBackground: styles.selectBackground,
|
||||
selectForeground: styles.selectForeground,
|
||||
selectBorder: styles.selectBorder
|
||||
}
|
||||
};
|
||||
super.style(superStyle);
|
||||
this.enabledSelectBackground = this.selectBackground;
|
||||
this.enabledSelectForeground = this.selectForeground;
|
||||
@@ -123,8 +117,8 @@ export class ListBox extends SelectBox {
|
||||
}
|
||||
|
||||
public get selectedOptions(): string[] {
|
||||
var selected = [];
|
||||
for (var i = 0; i < this.selectElement.selectedOptions.length; i++ ) {
|
||||
let selected = [];
|
||||
for (let i = 0; i < this.selectElement.selectedOptions.length; i++) {
|
||||
selected.push(this.selectElement.selectedOptions[i].innerHTML);
|
||||
}
|
||||
return selected;
|
||||
@@ -136,13 +130,13 @@ export class ListBox extends SelectBox {
|
||||
|
||||
// Remove selected options
|
||||
public remove(): void {
|
||||
var indexes = [];
|
||||
for (var i = 0; i < this.selectElement.selectedOptions.length; i++ ) {
|
||||
let indexes = [];
|
||||
for (let i = 0; i < this.selectElement.selectedOptions.length; i++) {
|
||||
indexes.push(this.selectElement.selectedOptions[i].index);
|
||||
}
|
||||
indexes.sort((a, b) => b-a);
|
||||
indexes.sort((a, b) => b - a);
|
||||
|
||||
for (var i = 0; i < indexes.length; i++) {
|
||||
for (let i = 0; i < indexes.length; i++) {
|
||||
this.selectElement.remove(indexes[i]);
|
||||
this.options.splice(indexes[i], 1);
|
||||
}
|
||||
@@ -155,27 +149,22 @@ export class ListBox extends SelectBox {
|
||||
|
||||
// Allow copy to clipboard
|
||||
public onKeyDown(event: IKeyboardEvent): void {
|
||||
if (this.selectedOptions.length > 0)
|
||||
{
|
||||
var key = event.keyCode;
|
||||
var ctrlOrCmd = event.ctrlKey || event.metaKey;
|
||||
if (this.selectedOptions.length > 0) {
|
||||
let key = event.keyCode;
|
||||
let ctrlOrCmd = event.ctrlKey || event.metaKey;
|
||||
|
||||
if (ctrlOrCmd && key === this.keyC) {
|
||||
var textToCopy = this.selectedOptions[0];
|
||||
for (var i = 1; i < this.selectedOptions.length; i++) {
|
||||
textToCopy = textToCopy + ', ' + this.selectedOptions[i];
|
||||
}
|
||||
if (ctrlOrCmd && key === KeyCode.KEY_C) {
|
||||
let textToCopy = this.selectedOptions[0];
|
||||
for (let i = 1; i < this.selectedOptions.length; i++) {
|
||||
textToCopy = textToCopy + ', ' + this.selectedOptions[i];
|
||||
}
|
||||
|
||||
// Copy to clipboard
|
||||
WorkbenchUtils.executeCopy(textToCopy);
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copy to clipboard
|
||||
this._clipboardService.writeText(textToCopy);
|
||||
|
||||
public dispose(): void {
|
||||
this._toDispose2 = lifecycle.dispose(this._toDispose2);
|
||||
super.dispose();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enable(): void {
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
||||
import { Button } from 'sql/base/browser/ui/button/button';
|
||||
|
||||
import { Builder } from 'vs/base/browser/builder';
|
||||
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox';
|
||||
import * as data from 'data';
|
||||
import * as types from 'vs/base/common/types';
|
||||
|
||||
import * as data from 'data';
|
||||
|
||||
export function appendRow(container: Builder, label: string, labelClass: string, cellContainerClass: string): Builder {
|
||||
let cellContainer: Builder;
|
||||
container.element('tr', {}, (rowContainer) => {
|
||||
@@ -47,35 +47,11 @@ export function appendRowLink(container: Builder, label: string, labelClass: str
|
||||
return new Builder(rowButton.getElement());
|
||||
}
|
||||
|
||||
export function createCheckBox(container: Builder, label: string, checkboxClass: string, isChecked: boolean, onCheck?: (viaKeyboard: boolean) => void): Checkbox {
|
||||
let checkbox = new Checkbox({
|
||||
actionClassName: checkboxClass,
|
||||
title: label,
|
||||
isChecked: isChecked,
|
||||
onChange: (viaKeyboard) => {
|
||||
if (onCheck) {
|
||||
onCheck(viaKeyboard);
|
||||
}
|
||||
}
|
||||
});
|
||||
container.getHTMLElement().appendChild(checkbox.domNode);
|
||||
container.div({}, (labelContainer) => {
|
||||
labelContainer.innerHtml(label);
|
||||
});
|
||||
|
||||
return checkbox;
|
||||
}
|
||||
|
||||
export function appendInputSelectBox(container: Builder, selectBox: SelectBox): SelectBox {
|
||||
selectBox.render(container.getHTMLElement());
|
||||
return selectBox;
|
||||
}
|
||||
|
||||
export function isNullOrWhiteSpace(value: string): boolean {
|
||||
// returns true if the string is null or contains white space/tab chars only
|
||||
return !value || value.trim().length === 0;
|
||||
}
|
||||
|
||||
export function getBooleanValueFromStringOrBoolean(value: any): boolean {
|
||||
if (types.isBoolean(value)) {
|
||||
return value;
|
||||
|
||||
@@ -154,11 +154,7 @@
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.vs-dark.monaco-shell .modal.flyout-dialog .footer-button a.monaco-button.monaco-text-button {
|
||||
outline-color: #8e8c8c;
|
||||
}
|
||||
|
||||
.modal.flyout-dialog .footer-button a.monaco-button.monaco-text-button:focus {
|
||||
.vs .monaco-text-button:focus {
|
||||
outline-width: 1px;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,17 +7,17 @@ import 'vs/css!./media/modal';
|
||||
import { IThemable } from 'vs/platform/theme/common/styler';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { Builder, $, withElementById } from 'vs/base/browser/builder';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
import { Button } from 'sql/base/browser/ui/button/button';
|
||||
import * as TelemetryUtils from 'sql/common/telemetryUtilities';
|
||||
import * as TelemetryKeys from 'sql/common/telemetryKeys';
|
||||
|
||||
@@ -64,6 +64,12 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
private _errorMessage: Builder;
|
||||
private _spinnerElement: HTMLElement;
|
||||
private _errorIconElement: HTMLElement;
|
||||
|
||||
private _focusableElements: NodeListOf<Element>;
|
||||
private _firstFocusableElement: HTMLElement;
|
||||
private _lastFocusableElement: HTMLElement;
|
||||
private _focusedElementBeforeOpen: HTMLElement;
|
||||
|
||||
private _dialogForeground: Color;
|
||||
private _dialogBorder: Color;
|
||||
private _dialogHeaderAndFooterBackground: Color;
|
||||
@@ -102,6 +108,7 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
* Set the dialog to have wide layout dynamically.
|
||||
* Temporary solution to render file browser as wide or narrow layout.
|
||||
* This will be removed once backup dialog is changed to wide layout.
|
||||
* (hyoshi - 10/2/2017 tracked by https://github.com/Microsoft/carbon/issues/1836)
|
||||
*/
|
||||
public setWide(isWide: boolean): void {
|
||||
if (this._builder.hasClass('wide') && isWide === false) {
|
||||
@@ -244,12 +251,42 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
private handleBackwardTab(e: KeyboardEvent) {
|
||||
if (this._firstFocusableElement && this._lastFocusableElement && document.activeElement === this._firstFocusableElement) {
|
||||
e.preventDefault();
|
||||
this._lastFocusableElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private handleForwardTab(e: KeyboardEvent) {
|
||||
if (this._firstFocusableElement && this._lastFocusableElement && document.activeElement === this._lastFocusableElement) {
|
||||
e.preventDefault();
|
||||
this._firstFocusableElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set focusable elements in the modal dialog
|
||||
*/
|
||||
public setFocusableElements() {
|
||||
this._focusableElements = this._builder.getHTMLElement().querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]');
|
||||
if (this._focusableElements && this._focusableElements.length > 0) {
|
||||
this._firstFocusableElement = <HTMLElement>this._focusableElements[0];
|
||||
this._lastFocusableElement = <HTMLElement>this._focusableElements[this._focusableElements.length - 1];
|
||||
}
|
||||
|
||||
this._focusedElementBeforeOpen = <HTMLElement>document.activeElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the modal and attaches key listeners
|
||||
*/
|
||||
protected show() {
|
||||
this._modalShowingContext.get().push(this._staticKey);
|
||||
this._builder.appendTo(withElementById(this._partService.getWorkbenchElementId()).getHTMLElement().parentElement);
|
||||
|
||||
this.setFocusableElements();
|
||||
|
||||
this._keydownListener = DOM.addDisposableListener(document, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
let context = this._modalShowingContext.get();
|
||||
if (context[context.length - 1] === this._staticKey) {
|
||||
@@ -258,6 +295,10 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
this.onAccept(event);
|
||||
} else if (event.equals(KeyCode.Escape)) {
|
||||
this.onClose(event);
|
||||
} else if (event.equals(KeyMod.Shift | KeyCode.Tab)) {
|
||||
this.handleBackwardTab(e);
|
||||
} else if (event.equals(KeyCode.Tab)) {
|
||||
this.handleForwardTab(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -281,6 +322,9 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
this._footerButtons.forEach(button => button.applyStyles());
|
||||
this._modalShowingContext.get().pop();
|
||||
this._builder.offDOM();
|
||||
if (this._focusedElementBeforeOpen) {
|
||||
this._focusedElementBeforeOpen.focus();
|
||||
}
|
||||
this._keydownListener.dispose();
|
||||
this._resizeListener.dispose();
|
||||
TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.ModalDialogClosed, { name: this._name });
|
||||
|
||||
@@ -6,16 +6,15 @@
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/optionsDialog';
|
||||
|
||||
import { Button } from 'sql/base/browser/ui/button/button';
|
||||
import { FixedCollapsibleView } from 'sql/platform/views/fixedCollapsibleView';
|
||||
import * as DialogHelper from './dialogHelper';
|
||||
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
||||
import { IModalOptions, Modal } from './modal';
|
||||
import * as OptionsDialogHelper from './optionsDialogHelper';
|
||||
import { attachModalDialogStyler } from 'sql/common/theme/styler';
|
||||
import { attachButtonStyler, attachModalDialogStyler } from 'sql/common/theme/styler';
|
||||
|
||||
import * as data from 'data';
|
||||
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
@@ -27,13 +26,14 @@ import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/theme
|
||||
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import * as styler from 'vs/platform/theme/common/styler';
|
||||
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { SplitView, CollapsibleState } from 'vs/base/browser/ui/splitview/splitview';
|
||||
import { SplitView, CollapsibleState } from 'sql/base/browser/ui/splitview/splitview';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
|
||||
class CategoryView extends FixedCollapsibleView {
|
||||
export class CategoryView extends FixedCollapsibleView {
|
||||
private _treecontainer: HTMLElement;
|
||||
private _collapsed: CollapsibleState;
|
||||
|
||||
constructor(private viewTitle: string, private _bodyContainer: HTMLElement, collapsed: boolean, initialBodySize: number, headerSize: number) {
|
||||
super(
|
||||
initialBodySize,
|
||||
@@ -43,6 +43,7 @@ class CategoryView extends FixedCollapsibleView {
|
||||
initialState: collapsed ? CollapsibleState.COLLAPSED : CollapsibleState.EXPANDED,
|
||||
ariaHeaderLabel: viewTitle
|
||||
});
|
||||
this._collapsed = collapsed ? CollapsibleState.COLLAPSED : CollapsibleState.EXPANDED;
|
||||
}
|
||||
|
||||
public renderHeader(container: HTMLElement): void {
|
||||
@@ -54,6 +55,7 @@ class CategoryView extends FixedCollapsibleView {
|
||||
this._treecontainer = document.createElement('div');
|
||||
container.appendChild(this._treecontainer);
|
||||
this._treecontainer.appendChild(this._bodyContainer);
|
||||
this.changeState(this._collapsed);
|
||||
}
|
||||
|
||||
public layoutBody(size: number): void {
|
||||
@@ -102,13 +104,13 @@ export class OptionsDialog extends Modal {
|
||||
attachModalDialogStyler(this, this._themeService);
|
||||
if (this.backButton) {
|
||||
this.backButton.addListener('click', () => this.cancel());
|
||||
styler.attachButtonStyler(this.backButton, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND });
|
||||
attachButtonStyler(this.backButton, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND });
|
||||
}
|
||||
this._okButton = this.addFooterButton(this.okLabel, () => this.ok());
|
||||
this._closeButton = this.addFooterButton(this.cancelLabel, () => this.cancel());
|
||||
// Theme styler
|
||||
styler.attachButtonStyler(this._okButton, this._themeService);
|
||||
styler.attachButtonStyler(this._closeButton, this._themeService);
|
||||
attachButtonStyler(this._okButton, this._themeService);
|
||||
attachButtonStyler(this._closeButton, this._themeService);
|
||||
let self = this;
|
||||
this._register(self._themeService.onDidColorThemeChange(e => self.updateTheme(e)));
|
||||
self.updateTheme(self._themeService.getColorTheme());
|
||||
|
||||
@@ -6,11 +6,14 @@
|
||||
import { IThemable } from 'vs/platform/theme/common/styler';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { Dimension, Builder } from 'vs/base/browser/builder';
|
||||
import { addDisposableListener, EventType } from 'vs/base/browser/dom';
|
||||
import { IAction, IActionRunner, Action, IActionChangeEvent, ActionRunner } from 'vs/base/common/actions';
|
||||
import { Dimension, $, Builder } from 'vs/base/browser/builder';
|
||||
import { EventType } from 'vs/base/browser/dom';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IActionOptions, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import './panelStyles';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface IPanelStyles {
|
||||
|
||||
@@ -28,45 +31,41 @@ export interface IPanelTab {
|
||||
}
|
||||
|
||||
interface IInternalPanelTab extends IPanelTab {
|
||||
header: HTMLElement;
|
||||
label: HTMLElement;
|
||||
header: Builder;
|
||||
label: Builder;
|
||||
}
|
||||
|
||||
export type PanelTabIdentifier = string;
|
||||
|
||||
export class TabbedPanel implements IThemable {
|
||||
export class TabbedPanel extends Disposable implements IThemable {
|
||||
private _tabMap = new Map<PanelTabIdentifier, IInternalPanelTab>();
|
||||
private _shownTab: PanelTabIdentifier;
|
||||
public readonly headersize = 35;
|
||||
private _header: HTMLElement;
|
||||
private _tabList: HTMLElement;
|
||||
private $header: Builder;
|
||||
private $tabList: Builder;
|
||||
private $body: Builder;
|
||||
private $parent: Builder;
|
||||
private _actionbar: ActionBar;
|
||||
private _body: HTMLElement;
|
||||
private _currentDimensions: Dimension;
|
||||
private _collapsed = false;
|
||||
private _parent: HTMLElement;
|
||||
|
||||
private _onTabChange = new Emitter<PanelTabIdentifier>();
|
||||
public onTabChange: Event<PanelTabIdentifier> = this._onTabChange.event;
|
||||
|
||||
constructor(private container: HTMLElement) {
|
||||
this._parent = document.createElement('div');
|
||||
this._parent.className = 'tabbedPanel';
|
||||
container.appendChild(this._parent);
|
||||
this._header = document.createElement('div');
|
||||
this._header.className = 'composite title';
|
||||
this._tabList = document.createElement('div');
|
||||
this._tabList.className = 'tabList';
|
||||
this._tabList.style.height = this.headersize + 'px';
|
||||
this._header.appendChild(this._tabList);
|
||||
let actionbarcontainer = document.createElement('div');
|
||||
actionbarcontainer.className = 'title-actions';
|
||||
super();
|
||||
this.$parent = this._register($('.tabbedPanel'));
|
||||
this.$parent.appendTo(container);
|
||||
this.$header = $('.composite.title');
|
||||
this.$tabList = $('.tabList');
|
||||
this.$tabList.style('height', this.headersize + 'px');
|
||||
this.$header.append(this.$tabList);
|
||||
let actionbarcontainer = $('.title-actions');
|
||||
this._actionbar = new ActionBar(actionbarcontainer);
|
||||
this._header.appendChild(actionbarcontainer);
|
||||
this._parent.appendChild(this._header);
|
||||
this._body = document.createElement('div');
|
||||
this._body.className = 'tabBody';
|
||||
this._parent.appendChild(this._body);
|
||||
this.$header.append(actionbarcontainer);
|
||||
this.$parent.append(this.$header);
|
||||
this.$body = $('tabBody');
|
||||
this.$parent.append(this.$body);
|
||||
}
|
||||
|
||||
public pushTab(tab: IPanelTab): PanelTabIdentifier {
|
||||
@@ -88,14 +87,20 @@ export class TabbedPanel implements IThemable {
|
||||
}
|
||||
|
||||
private _createTab(tab: IInternalPanelTab): void {
|
||||
let tabElement = document.createElement('div');
|
||||
tabElement.className = 'tab';
|
||||
let tabLabel = document.createElement('a');
|
||||
tabLabel.className = 'tabLabel';
|
||||
tabLabel.innerText = tab.title;
|
||||
tabElement.appendChild(tabLabel);
|
||||
addDisposableListener(tabElement, EventType.CLICK, (e) => this.showTab(tab.identifier));
|
||||
this._tabList.appendChild(tabElement);
|
||||
let tabElement = $('.tab');
|
||||
tabElement.attr('tabindex', '0');
|
||||
let tabLabel = $('a.tabLabel');
|
||||
tabLabel.safeInnerHtml(tab.title);
|
||||
tabElement.append(tabLabel);
|
||||
tabElement.on(EventType.CLICK, e => this.showTab(tab.identifier));
|
||||
tabElement.on(EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyCode.Enter)) {
|
||||
this.showTab(tab.identifier);
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
});
|
||||
this.$tabList.append(tabElement);
|
||||
tab.header = tabElement;
|
||||
tab.label = tabLabel;
|
||||
}
|
||||
@@ -106,14 +111,14 @@ export class TabbedPanel implements IThemable {
|
||||
}
|
||||
|
||||
if (this._shownTab) {
|
||||
this._tabMap.get(this._shownTab).label.classList.remove('active');
|
||||
this._tabMap.get(this._shownTab).label.removeClass('active');
|
||||
}
|
||||
|
||||
this._shownTab = id;
|
||||
new Builder(this._body).empty();
|
||||
this.$body.clearChildren();
|
||||
let tab = this._tabMap.get(this._shownTab);
|
||||
tab.label.classList.add('active');
|
||||
tab.view.render(this._body);
|
||||
tab.label.addClass('active');
|
||||
tab.view.render(this.$body.getHTMLElement());
|
||||
this._onTabChange.fire(id);
|
||||
if (this._currentDimensions) {
|
||||
this._layoutCurrentTab(new Dimension(this._currentDimensions.width, this._currentDimensions.height - this.headersize));
|
||||
@@ -121,7 +126,7 @@ export class TabbedPanel implements IThemable {
|
||||
}
|
||||
|
||||
public removeTab(tab: PanelTabIdentifier) {
|
||||
this._tabMap.get(tab).header.remove();
|
||||
this._tabMap.get(tab).header.destroy();
|
||||
this._tabMap.delete(tab);
|
||||
}
|
||||
|
||||
@@ -131,9 +136,9 @@ export class TabbedPanel implements IThemable {
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
this._currentDimensions = dimension;
|
||||
this._header.style.width = dimension.width + 'px';
|
||||
this._body.style.width = dimension.width + 'px';
|
||||
this._body.style.height = (dimension.height - this.headersize) + 'px';
|
||||
this.$header.style('width', dimension.width + 'px');
|
||||
this.$body.style('width', dimension.width + 'px');
|
||||
this.$body.style('height', (dimension.height - this.headersize) + 'px');
|
||||
this._layoutCurrentTab(new Dimension(dimension.width, dimension.height - this.headersize));
|
||||
}
|
||||
|
||||
@@ -154,9 +159,9 @@ export class TabbedPanel implements IThemable {
|
||||
|
||||
this._collapsed = val === false ? false : true;
|
||||
if (this.collapsed) {
|
||||
this._body.remove();
|
||||
this.$body.offDOM();
|
||||
} else {
|
||||
this._parent.appendChild(this._body);
|
||||
this.$parent.append(this.$body);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e8e8e8" d="M6 4v8l4-4-4-4zm1 2.414l1.586 1.586-1.586 1.586v-3.172z"/></svg>
|
||||
|
After Width: | Height: | Size: 151 B |
1
src/sql/base/browser/ui/splitview/arrow-collapse.svg
Normal file
1
src/sql/base/browser/ui/splitview/arrow-collapse.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#646465" d="M6 4v8l4-4-4-4zm1 2.414l1.586 1.586-1.586 1.586v-3.172z"/></svg>
|
||||
|
After Width: | Height: | Size: 151 B |
1
src/sql/base/browser/ui/splitview/arrow-expand-dark.svg
Normal file
1
src/sql/base/browser/ui/splitview/arrow-expand-dark.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e8e8e8" d="M11 10.07h-5.656l5.656-5.656v5.656z"/></svg>
|
||||
|
After Width: | Height: | Size: 131 B |
1
src/sql/base/browser/ui/splitview/arrow-expand.svg
Normal file
1
src/sql/base/browser/ui/splitview/arrow-expand.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#646465" d="M11 10.07h-5.656l5.656-5.656v5.656z"/></svg>
|
||||
|
After Width: | Height: | Size: 131 B |
98
src/sql/base/browser/ui/splitview/splitview.css
Normal file
98
src/sql/base/browser/ui/splitview/splitview.css
Normal file
@@ -0,0 +1,98 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-split-view {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.monaco-split-view > .split-view-view {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-split-view.vertical > .split-view-view {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.monaco-split-view.horizontal > .split-view-view {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-split-view > .split-view-view > .header {
|
||||
position: relative;
|
||||
line-height: 22px;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
padding-left: 20px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.monaco-split-view > .split-view-view > .header.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Bold font style does not go well with CJK fonts */
|
||||
.monaco-split-view:lang(zh-Hans) > .split-view-view > .header,
|
||||
.monaco-split-view:lang(zh-Hant) > .split-view-view > .header,
|
||||
.monaco-split-view:lang(ja) > .split-view-view > .header,
|
||||
.monaco-split-view:lang(ko) > .split-view-view > .header { font-weight: normal; }
|
||||
|
||||
.monaco-split-view > .split-view-view > .header.collapsible {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.monaco-split-view > .split-view-view > .header.collapsible {
|
||||
background-image: url('arrow-collapse.svg');
|
||||
background-position: 2px center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.monaco-split-view > .split-view-view > .header.collapsible:not(.collapsed) {
|
||||
background-image: url('arrow-expand.svg');
|
||||
background-position: 2px center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-split-view > .split-view-view > .header.collapsible {
|
||||
background-image: url('arrow-collapse-dark.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-split-view > .split-view-view > .header.collapsible:not(.collapsed) {
|
||||
background-image: url('arrow-expand-dark.svg');
|
||||
background-position: 2px center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
/* Animation */
|
||||
|
||||
.monaco-split-view.animated > .split-view-view {
|
||||
transition-duration: 0.15s;
|
||||
-webkit-transition-duration: 0.15s;
|
||||
-moz-transition-duration: 0.15s;
|
||||
transition-timing-function: ease-out;
|
||||
-webkit-transition-timing-function: ease-out;
|
||||
-moz-transition-timing-function: ease-out;
|
||||
}
|
||||
|
||||
.monaco-split-view.vertical.animated > .split-view-view {
|
||||
transition-property: height;
|
||||
-webkit-transition-property: height;
|
||||
-moz-transition-property: height;
|
||||
}
|
||||
|
||||
.monaco-split-view.horizontal.animated > .split-view-view {
|
||||
transition-property: width;
|
||||
-webkit-transition-property: width;
|
||||
-moz-transition-property: width;
|
||||
}
|
||||
|
||||
.hc-black .split-view-view .action-label {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.hc-black .split-view-view > .header .action-label:before {
|
||||
top: 4px !important;
|
||||
}
|
||||
1043
src/sql/base/browser/ui/splitview/splitview.ts
Normal file
1043
src/sql/base/browser/ui/splitview/splitview.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,12 +2,12 @@
|
||||
|
||||
import 'vs/css!vs/base/browser/ui/checkbox/checkbox';
|
||||
|
||||
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import * as nls from 'vs/nls';
|
||||
import { ICheckboxStyles } from 'vs/base/browser/ui/checkbox/checkbox';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
|
||||
export interface ICheckboxSelectColumnOptions extends Slick.PluginOptions, ICheckboxStyles {
|
||||
columnId?: string;
|
||||
@@ -21,24 +21,23 @@ const defaultOptions: ICheckboxSelectColumnOptions = {
|
||||
columnId: '_checkbox_selector',
|
||||
cssClass: null,
|
||||
toolTip: nls.localize('selectDeselectAll', 'Select/Deselect All'),
|
||||
width: 30,
|
||||
inputActiveOptionBorder: Color.fromHex('#007ACC')
|
||||
width: 30
|
||||
};
|
||||
|
||||
const checkBoxTemplate = `<div style="display: flex; align-items: center; flex-direction: column">
|
||||
<div style="border-color: {0}" role="checkbox" aria-checked="{1}" aria-label="" class="custom-checkbox sql-checkbox {2}"></div>
|
||||
</div>`;
|
||||
const checkboxTemplate = `
|
||||
<div style="display: flex; align-items: center; flex-direction: column">
|
||||
<input type="checkbox" {0}>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export class CheckboxSelectColumn<T> implements Slick.Plugin<T> {
|
||||
private _options: ICheckboxSelectColumnOptions;
|
||||
private _grid: Slick.Grid<T>;
|
||||
private _handler = new Slick.EventHandler();
|
||||
private _selectedRowsLookup = {};
|
||||
private _checkboxTemplate: string;
|
||||
|
||||
constructor(options?: Slick.PluginOptions) {
|
||||
constructor(options?: ICheckboxSelectColumnOptions) {
|
||||
this._options = mixin(options, defaultOptions, false);
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
public init(grid: Slick.Grid<T>): void {
|
||||
@@ -74,11 +73,11 @@ export class CheckboxSelectColumn<T> implements Slick.Plugin<T> {
|
||||
if (!this._options.title) {
|
||||
if (selectedRows.length && selectedRows.length === this._grid.getDataLength()) {
|
||||
this._grid.updateColumnHeader(this._options.columnId,
|
||||
strings.format(this._checkboxTemplate, 'true', 'checked'),
|
||||
strings.format(checkboxTemplate, 'checked'),
|
||||
this._options.toolTip);
|
||||
} else {
|
||||
this._grid.updateColumnHeader(this._options.columnId,
|
||||
strings.format(this._checkboxTemplate, 'true', 'unchecked'),
|
||||
strings.format(checkboxTemplate, ''),
|
||||
this._options.toolTip);
|
||||
}
|
||||
}
|
||||
@@ -94,12 +93,22 @@ export class CheckboxSelectColumn<T> implements Slick.Plugin<T> {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
} else {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyCode.Enter)) {
|
||||
// clicking on a row select checkbox
|
||||
if (this._grid.getColumns()[args.cell].id === this._options.columnId) {
|
||||
this.toggleRowSelection(args.row);
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleClick(e: Event, args: Slick.OnClickEventArgs<T>): void {
|
||||
// clicking on a row select checkbox
|
||||
if (this._grid.getColumns()[args.cell].id === this._options.columnId && $(e.target).is('.custom-checkbox')) {
|
||||
if (this._grid.getColumns()[args.cell].id === this._options.columnId && $(e.target).is('input[type="checkbox"]')) {
|
||||
// if editing, try to commit
|
||||
if (this._grid.getEditorLock().isActive() && !this._grid.getEditorLock().commitCurrentEdit()) {
|
||||
e.preventDefault();
|
||||
@@ -122,7 +131,7 @@ export class CheckboxSelectColumn<T> implements Slick.Plugin<T> {
|
||||
}
|
||||
|
||||
private handleHeaderClick(e: Event, args: Slick.OnHeaderClickEventArgs<T>): void {
|
||||
if (!this._options.title && args.column.id === this._options.columnId && $(e.target).is('.custom-checkbox')) {
|
||||
if (!this._options.title && args.column.id === this._options.columnId && $(e.target).is('input[type="checkbox"]')) {
|
||||
// if editing, try to commit
|
||||
if (this._grid.getEditorLock().isActive() && !this._grid.getEditorLock().commitCurrentEdit()) {
|
||||
e.preventDefault();
|
||||
@@ -130,19 +139,19 @@ export class CheckboxSelectColumn<T> implements Slick.Plugin<T> {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($(e.target).is('.unchecked')) {
|
||||
if ($(e.target).is('input[checked]')) {
|
||||
let rows = [];
|
||||
for (let i = 0; i < this._grid.getDataLength(); i++) {
|
||||
rows.push(i);
|
||||
}
|
||||
this._grid.setSelectedRows(rows);
|
||||
this._grid.updateColumnHeader(this._options.columnId,
|
||||
strings.format(this._checkboxTemplate, 'true', 'checked'),
|
||||
strings.format(checkboxTemplate, 'checked'),
|
||||
this._options.toolTip);
|
||||
} else {
|
||||
this._grid.setSelectedRows([]);
|
||||
this._grid.updateColumnHeader(this._options.columnId,
|
||||
strings.format(this._checkboxTemplate, 'true', 'unchecked'), this._options.toolTip);
|
||||
strings.format(checkboxTemplate, ''), this._options.toolTip);
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
@@ -152,7 +161,7 @@ export class CheckboxSelectColumn<T> implements Slick.Plugin<T> {
|
||||
public getColumnDefinition(): Slick.Column<T> {
|
||||
return {
|
||||
id: this._options.columnId,
|
||||
name: this._options.title || strings.format(this._checkboxTemplate, 'true', 'unchecked'),
|
||||
name: this._options.title || strings.format(checkboxTemplate, ''),
|
||||
toolTip: this._options.toolTip,
|
||||
field: 'sel',
|
||||
width: this._options.width,
|
||||
@@ -166,24 +175,9 @@ export class CheckboxSelectColumn<T> implements Slick.Plugin<T> {
|
||||
private checkboxSelectionFormatter(row, cell, value, columnDef: Slick.Column<T>, dataContext): string {
|
||||
if (dataContext) {
|
||||
return this._selectedRowsLookup[row]
|
||||
? strings.format(this._checkboxTemplate, 'true', 'checked')
|
||||
: strings.format(this._checkboxTemplate, 'true', 'unchecked');
|
||||
? strings.format(checkboxTemplate, 'checked')
|
||||
: strings.format(checkboxTemplate, '');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public style(styles: ICheckboxStyles): void {
|
||||
if (styles.inputActiveOptionBorder) {
|
||||
this._options.inputActiveOptionBorder = styles.inputActiveOptionBorder;
|
||||
}
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
protected applyStyles(): void {
|
||||
this._checkboxTemplate = strings.format(checkBoxTemplate, this._options.inputActiveOptionBorder.toString(), '{0}', '{1}');
|
||||
if (this._grid) {
|
||||
this._grid.invalidateAllRows();
|
||||
this._grid.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,10 @@ export class Table<T extends Slick.SlickData> implements IThemable {
|
||||
this._grid.setColumns(columns);
|
||||
}
|
||||
|
||||
public get grid(): Slick.Grid<T> {
|
||||
return this._grid;
|
||||
}
|
||||
|
||||
setData(data: Array<T>);
|
||||
setData(data: TableDataView<T>);
|
||||
setData(data: Array<T> | TableDataView<T>) {
|
||||
@@ -204,11 +208,11 @@ export class Table<T extends Slick.SlickData> implements IThemable {
|
||||
}
|
||||
|
||||
if (styles.listFocusBackground) {
|
||||
content.push(`.monaco-table .${this._idPrefix} .slick-row .focused { background-color: ${styles.listFocusBackground}; }`);
|
||||
content.push(`.monaco-table .${this._idPrefix} .slick-row .active { background-color: ${styles.listFocusBackground}; }`);
|
||||
}
|
||||
|
||||
if (styles.listFocusForeground) {
|
||||
content.push(`.monaco-table .${this._idPrefix} .slick-row .focused { color: ${styles.listFocusForeground}; }`);
|
||||
content.push(`.monaco-table .${this._idPrefix} .slick-row .active { color: ${styles.listFocusForeground}; }`);
|
||||
}
|
||||
|
||||
if (styles.listActiveSelectionBackground) {
|
||||
|
||||
@@ -5,9 +5,12 @@
|
||||
|
||||
import { Table } from './table';
|
||||
import { TableDataView } from './tableDataView';
|
||||
|
||||
import { View, Orientation, AbstractCollapsibleView, HeaderView, IViewOptions, ICollapsibleViewOptions } from 'vs/base/browser/ui/splitview/splitview';
|
||||
import { View, Orientation, AbstractCollapsibleView, HeaderView, ICollapsibleViewOptions, IViewOptions, CollapsibleState } from 'sql/base/browser/ui/splitview/splitview';
|
||||
import { $ } from 'vs/base/browser/builder';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import * as lifecycle from 'vs/base/common/lifecycle';
|
||||
|
||||
export class TableBasicView<T> extends View {
|
||||
private _table: Table<T>;
|
||||
@@ -84,6 +87,7 @@ export class TableHeaderView<T> extends HeaderView {
|
||||
export class TableCollapsibleView<T> extends AbstractCollapsibleView {
|
||||
private _table: Table<T>;
|
||||
private _container: HTMLElement;
|
||||
private _headerTabListener: lifecycle.IDisposable;
|
||||
|
||||
constructor(
|
||||
private _viewTitle: string,
|
||||
@@ -98,6 +102,29 @@ export class TableCollapsibleView<T> extends AbstractCollapsibleView {
|
||||
this._table = new Table<T>(this._container, data, columns, tableOpts);
|
||||
}
|
||||
|
||||
public render(container: HTMLElement, orientation: Orientation): void {
|
||||
super.render(container, orientation);
|
||||
this._headerTabListener = DOM.addDisposableListener(this.header, DOM.EventType.KEY_DOWN, (e) => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyCode.Tab) && this.state === CollapsibleState.EXPANDED) {
|
||||
let element = this._table.getSelectedRows();
|
||||
if (!element || element.length === 0) {
|
||||
this._table.setSelectedRows([0]);
|
||||
this._table.setActiveCell(0, 1);
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this._headerTabListener) {
|
||||
this._headerTabListener.dispose();
|
||||
this._headerTabListener = null;
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public addContainerClass(className: string) {
|
||||
this._container.classList.add(className);
|
||||
}
|
||||
|
||||
@@ -63,6 +63,15 @@
|
||||
background-image: url('query-plan-inverse.svg');
|
||||
}
|
||||
|
||||
.vs .icon.actualQueryPlan {
|
||||
background-image: url('query-plan.svg');
|
||||
}
|
||||
|
||||
.vs-dark .icon.actualQueryPlan,
|
||||
.hc-black .icon.actualQueryPlan {
|
||||
background-image: url('query-plan-inverse.svg');
|
||||
}
|
||||
|
||||
.vs .icon.createInsight {
|
||||
background-image: url('create_insight.svg');
|
||||
}
|
||||
|
||||
26
src/sql/base/browser/ui/views/browser/media/views.css
Normal file
26
src/sql/base/browser/ui/views/browser/media/views.css
Normal file
@@ -0,0 +1,26 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.custom-view-tree-node-item {
|
||||
display: flex;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.custom-view-tree-node-item > .custom-view-tree-node-item-icon {
|
||||
background-size: 16px;
|
||||
background-position: left center;
|
||||
background-repeat: no-repeat;
|
||||
padding-right: 6px;
|
||||
width: 16px;
|
||||
height: 22px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.custom-view-tree-node-item > .custom-view-tree-node-item-label {
|
||||
flex: 1;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
303
src/sql/base/browser/ui/views/browser/views.ts
Normal file
303
src/sql/base/browser/ui/views/browser/views.ts
Normal file
@@ -0,0 +1,303 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IThemable } from 'vs/platform/theme/common/styler';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { $ } from 'vs/base/browser/builder';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IAction, IActionRunner } from 'vs/base/common/actions';
|
||||
import { IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { prepareActions } from 'vs/workbench/browser/actions';
|
||||
import { ITree } from 'vs/base/parts/tree/browser/tree';
|
||||
import { DelayedDragHandler } from 'vs/base/browser/dnd';
|
||||
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { AbstractCollapsibleView, CollapsibleState, IView as IBaseView, SplitView, ViewSizing } from 'sql/base/browser/ui/splitview/splitview';
|
||||
|
||||
export interface IViewOptions {
|
||||
|
||||
id: string;
|
||||
|
||||
name: string;
|
||||
|
||||
actionRunner: IActionRunner;
|
||||
|
||||
collapsed: boolean;
|
||||
|
||||
}
|
||||
|
||||
export interface IViewConstructorSignature {
|
||||
|
||||
new(initialSize: number, options: IViewOptions, ...services: { _serviceBrand: any; }[]): IView;
|
||||
|
||||
}
|
||||
|
||||
export interface IView extends IBaseView, IThemable {
|
||||
|
||||
id: string;
|
||||
|
||||
name: string;
|
||||
|
||||
getHeaderElement(): HTMLElement;
|
||||
|
||||
create(): TPromise<void>;
|
||||
|
||||
setVisible(visible: boolean): TPromise<void>;
|
||||
|
||||
isVisible(): boolean;
|
||||
|
||||
getActions(): IAction[];
|
||||
|
||||
getSecondaryActions(): IAction[];
|
||||
|
||||
getActionItem(action: IAction): IActionItem;
|
||||
|
||||
getActionsContext(): any;
|
||||
|
||||
showHeader(): boolean;
|
||||
|
||||
hideHeader(): boolean;
|
||||
|
||||
focusBody(): void;
|
||||
|
||||
isExpanded(): boolean;
|
||||
|
||||
expand(): void;
|
||||
|
||||
collapse(): void;
|
||||
|
||||
getOptimalWidth(): number;
|
||||
|
||||
shutdown(): void;
|
||||
}
|
||||
|
||||
export interface ICollapsibleViewOptions extends IViewOptions {
|
||||
|
||||
ariaHeaderLabel?: string;
|
||||
|
||||
sizing: ViewSizing;
|
||||
|
||||
initialBodySize?: number;
|
||||
|
||||
}
|
||||
|
||||
export abstract class CollapsibleView extends AbstractCollapsibleView implements IView {
|
||||
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
|
||||
protected treeContainer: HTMLElement;
|
||||
protected tree: ITree;
|
||||
protected toDispose: IDisposable[];
|
||||
protected toolBar: ToolBar;
|
||||
protected actionRunner: IActionRunner;
|
||||
protected isDisposed: boolean;
|
||||
|
||||
private _isVisible: boolean;
|
||||
|
||||
private dragHandler: DelayedDragHandler;
|
||||
|
||||
constructor(
|
||||
initialSize: number,
|
||||
options: ICollapsibleViewOptions,
|
||||
protected keybindingService: IKeybindingService,
|
||||
protected contextMenuService: IContextMenuService
|
||||
) {
|
||||
super(initialSize, {
|
||||
ariaHeaderLabel: options.ariaHeaderLabel,
|
||||
sizing: options.sizing,
|
||||
bodySize: options.initialBodySize ? options.initialBodySize : 4 * 22,
|
||||
initialState: options.collapsed ? CollapsibleState.COLLAPSED : CollapsibleState.EXPANDED,
|
||||
});
|
||||
|
||||
this.id = options.id;
|
||||
this.name = options.name;
|
||||
this.actionRunner = options.actionRunner;
|
||||
this.toDispose = [];
|
||||
}
|
||||
|
||||
protected changeState(state: CollapsibleState): void {
|
||||
this.updateTreeVisibility(this.tree, state === CollapsibleState.EXPANDED);
|
||||
|
||||
super.changeState(state);
|
||||
}
|
||||
|
||||
get draggableLabel(): string { return this.name; }
|
||||
|
||||
public create(): TPromise<void> {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
getHeaderElement(): HTMLElement {
|
||||
return this.header;
|
||||
}
|
||||
|
||||
public renderHeader(container: HTMLElement): void {
|
||||
|
||||
// Tool bar
|
||||
this.toolBar = new ToolBar($('div.actions').appendTo(container).getHTMLElement(), this.contextMenuService, {
|
||||
orientation: ActionsOrientation.HORIZONTAL,
|
||||
actionItemProvider: (action) => this.getActionItem(action),
|
||||
ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.name),
|
||||
getKeyBinding: (action) => this.keybindingService.lookupKeybinding(action.id)
|
||||
});
|
||||
this.toolBar.actionRunner = this.actionRunner;
|
||||
this.updateActions();
|
||||
|
||||
// Expand on drag over
|
||||
this.dragHandler = new DelayedDragHandler(container, () => {
|
||||
if (!this.isExpanded()) {
|
||||
this.expand();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected updateActions(): void {
|
||||
this.toolBar.setActions(prepareActions(this.getActions()), prepareActions(this.getSecondaryActions()))();
|
||||
this.toolBar.context = this.getActionsContext();
|
||||
}
|
||||
|
||||
protected renderViewTree(container: HTMLElement): HTMLElement {
|
||||
const treeContainer = document.createElement('div');
|
||||
container.appendChild(treeContainer);
|
||||
|
||||
return treeContainer;
|
||||
}
|
||||
|
||||
public getViewer(): ITree {
|
||||
return this.tree;
|
||||
}
|
||||
|
||||
public isVisible(): boolean {
|
||||
return this._isVisible;
|
||||
}
|
||||
|
||||
public setVisible(visible: boolean): TPromise<void> {
|
||||
if (this._isVisible !== visible) {
|
||||
this._isVisible = visible;
|
||||
this.updateTreeVisibility(this.tree, visible && this.state === CollapsibleState.EXPANDED);
|
||||
}
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public focusBody(): void {
|
||||
this.focusTree();
|
||||
}
|
||||
|
||||
protected reveal(element: any, relativeTop?: number): TPromise<void> {
|
||||
if (!this.tree) {
|
||||
return TPromise.as(null); // return early if viewlet has not yet been created
|
||||
}
|
||||
|
||||
return this.tree.reveal(element, relativeTop);
|
||||
}
|
||||
|
||||
public layoutBody(size: number): void {
|
||||
if (this.tree) {
|
||||
this.treeContainer.style.height = size + 'px';
|
||||
this.tree.layout(size);
|
||||
}
|
||||
}
|
||||
|
||||
public getActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
public getSecondaryActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
public getActionItem(action: IAction): IActionItem {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getActionsContext(): any {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
// Subclass to implement
|
||||
}
|
||||
|
||||
public getOptimalWidth(): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.isDisposed = true;
|
||||
this.treeContainer = null;
|
||||
|
||||
if (this.tree) {
|
||||
this.tree.dispose();
|
||||
}
|
||||
|
||||
if (this.dragHandler) {
|
||||
this.dragHandler.dispose();
|
||||
}
|
||||
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
|
||||
if (this.toolBar) {
|
||||
this.toolBar.dispose();
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private updateTreeVisibility(tree: ITree, isVisible: boolean): void {
|
||||
if (!tree) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isVisible) {
|
||||
$(tree.getHTMLElement()).show();
|
||||
} else {
|
||||
$(tree.getHTMLElement()).hide(); // make sure the tree goes out of the tabindex world by hiding it
|
||||
}
|
||||
|
||||
if (isVisible) {
|
||||
tree.onVisible();
|
||||
} else {
|
||||
tree.onHidden();
|
||||
}
|
||||
}
|
||||
|
||||
private focusTree(): void {
|
||||
if (!this.tree) {
|
||||
return; // return early if viewlet has not yet been created
|
||||
}
|
||||
|
||||
// Make sure the current selected element is revealed
|
||||
const selection = this.tree.getSelection();
|
||||
if (selection.length > 0) {
|
||||
this.reveal(selection[0], 0.5).done(null, errors.onUnexpectedError);
|
||||
}
|
||||
|
||||
// Pass Focus to Viewer
|
||||
this.tree.DOMFocus();
|
||||
}
|
||||
}
|
||||
|
||||
export interface IViewletViewOptions extends IViewOptions {
|
||||
|
||||
viewletSettings: object;
|
||||
|
||||
}
|
||||
|
||||
export interface IViewState {
|
||||
|
||||
collapsed: boolean;
|
||||
|
||||
size: number | undefined;
|
||||
|
||||
isHidden: boolean;
|
||||
|
||||
order: number;
|
||||
|
||||
}
|
||||
50
src/sql/base/browser/ui/views/common/views.ts
Normal file
50
src/sql/base/browser/ui/views/common/views.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import Event from 'vs/base/common/event';
|
||||
import { Command } from 'vs/editor/common/modes';
|
||||
|
||||
export type TreeViewItemHandleArg = {
|
||||
$treeViewId: string,
|
||||
$treeItemHandle: number
|
||||
};
|
||||
|
||||
export enum TreeItemCollapsibleState {
|
||||
None = 0,
|
||||
Collapsed = 1,
|
||||
Expanded = 2
|
||||
}
|
||||
|
||||
export interface ITreeItem {
|
||||
|
||||
handle: number;
|
||||
|
||||
label: string;
|
||||
|
||||
icon?: string;
|
||||
|
||||
iconDark?: string;
|
||||
|
||||
contextValue?: string;
|
||||
|
||||
command?: Command;
|
||||
|
||||
children?: ITreeItem[];
|
||||
|
||||
collapsibleState?: TreeItemCollapsibleState;
|
||||
}
|
||||
|
||||
export interface ITreeViewDataProvider {
|
||||
|
||||
onDidChange: Event<ITreeItem[] | undefined | null>;
|
||||
|
||||
onDispose: Event<void>;
|
||||
|
||||
getElements(): TPromise<ITreeItem[]>;
|
||||
|
||||
getChildren(element: ITreeItem): TPromise<ITreeItem[]>;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user