SQL Operations Studio Public Preview 1 (0.23) release source code

This commit is contained in:
Karl Burtram
2017-11-09 14:30:27 -08:00
parent b88ecb8d93
commit 3cdac41339
8829 changed files with 759707 additions and 286 deletions

View File

@@ -0,0 +1,58 @@
/*---------------------------------------------------------------------------------------------
* 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/media/icons/common-icons';
import 'vs/css!./media/breadcrumb';
import { Component, Inject, forwardRef, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
import { Router } from '@angular/router';
import { toDisposableSubscription } from 'sql/parts/common/rxjsUtils';
import { IBreadcrumbService, MenuItem } from './interfaces';
import { IDisposable } from 'vs/base/common/lifecycle';
@Component({
selector: 'breadcrumb',
template: `
<span style="display: flex; flex-flow: row; align-items: center; margin: 10px">
<ng-template ngFor let-item let-first="first" let-last="last" [ngForOf]="menuItems">
<span style="padding: 5px; display: flex; align-items: center">
<span *ngIf="item.icon" class="icon" style="display: inline-block; margin-right: 5px" [ngClass]="item.icon"></span>
<span *ngIf="first" style="font-weight: 200">{{item.label}}</span>
<span *ngIf="last" style="">{{item.label}}</span>
<a class="router-link" *ngIf="!last && !first" (click)="route(item.routerLink)" style=" font-weight: 200" >{{item.label}}</a>
</span>
<span *ngIf="!last" class="icon chevron-right"></span>
</ng-template>
</span>
`
})
export class BreadcrumbComponent implements OnInit, OnDestroy {
private menuItems: MenuItem[] = [];
private disposables: Array<IDisposable> = new Array();
constructor(
@Inject(forwardRef(() => IBreadcrumbService)) private _breadcrumbService: IBreadcrumbService,
@Inject(forwardRef(() => Router)) private _router: Router,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef
) { }
ngOnInit() {
this.disposables.push(toDisposableSubscription(this._breadcrumbService.breadcrumbItem.subscribe((item) => this.updateCrumb(item))));
}
ngOnDestroy() {
this.disposables.forEach(item => item.dispose());
}
private updateCrumb(items: MenuItem[]) {
this.menuItems = items;
this._changeRef.detectChanges();
}
public route(link: any[]): void {
this._router.navigate(link);
}
}

View File

@@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { InjectionToken, EventEmitter } from '@angular/core';
import { Subject } from 'rxjs/Subject';
export const IBreadcrumbService = new InjectionToken<IBreadcrumbService>('breadcrumbService');
export interface IBreadcrumbService {
breadcrumbItem: Subject<MenuItem[]>;
setBreadcrumbs(any): void;
}
export interface MenuItem {
label?: string;
icon?: string;
command?: (event?: any) => void;
url?: string;
routerLink?: any[];
eventEmitter?: EventEmitter<any>;
items?: MenuItem[];
expanded?: boolean;
disabled?: boolean;
visible?: boolean;
target?: string;
routerLinkActiveOptions?: any;
}

View File

@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.vs .chevron-right.icon {
background-image: url("chevron_right.svg");
}
.vs-dark .chevron-right.icon,
.hc-black .chevron-right.icon {
background-image: url("chevron_right_inverse.svg");
}
breadcrumb .icon {
background-size: 16px;
background-position: 50% 50%;
background-repeat: no-repeat;
padding: 8px;
}
breadcrumb .chevron-right.icon {
background-size: 16px;
background-position: 50% 50%;
background-repeat: no-repeat;
padding: 8px;
}
breadcrumb .router-link {
cursor: pointer;
}

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#231f20;}</style></defs><title>chevron_right</title><polygon class="cls-1" points="13.59 2.21 13.58 2.22 13.58 2.2 13.59 2.21"/><path d="M10.54,8l-7-7,1-1,8,8-8,8-1-1Z"/></svg>

After

Width:  |  Height:  |  Size: 294 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>chevron_right_inverse</title><path class="cls-1" d="M10.38,8l-7-7,1-1,8,8-8,8-1-1Z"/></svg>

After

Width:  |  Height:  |  Size: 237 B

View File

@@ -0,0 +1,54 @@
/*---------------------------------------------------------------------------------------------
* 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'
};
/**
* Extends Checkbox to include Carbon checkbox icon and styling.
*/
export class Checkbox extends vsCheckbox {
private _inputActiveOptionBorder: Color;
constructor(opts: ICheckboxOpts) {
super({
actionClassName: opts.actionClassName + defaultOpts.actionClassName,
title: opts.title,
isChecked: opts.isChecked,
onChange: opts.onChange,
onKeyDown: opts.onKeyDown,
inputActiveOptionBorder: opts.inputActiveOptionBorder
});
this._inputActiveOptionBorder = opts.inputActiveOptionBorder ? opts.inputActiveOptionBorder : defaultOpts.inputActiveOptionBorder;
}
public enable(): void {
super.enable();
this.domNode.classList.remove('disabled');
}
public disable(): void {
super.disable();
this.domNode.classList.add('disabled');
}
public style(styles: ICheckboxStyles): void {
if (styles.inputActiveOptionBorder) {
this._inputActiveOptionBorder = styles.inputActiveOptionBorder;
}
this.applyStyles();
}
protected applyStyles(): void {
if (this.domNode) {
this.domNode.style.borderColor = this._inputActiveOptionBorder ? this._inputActiveOptionBorder.toString(): defaultOpts.inputActiveOptionBorder.toString();
}
}
}

View File

@@ -0,0 +1,71 @@
/*---------------------------------------------------------------------------------------------
* 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;
}
}

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>check_16x16</title><path d="M16,3.16,5.48,13.69,0,8.2l.89-.89,4.6,4.59,9.63-9.62Z"/></svg>

After

Width:  |  Height:  |  Size: 190 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>check_inverse_16x16</title><path class="cls-1" d="M16,3.16,5.48,13.69,0,8.2l.89-.89,4.6,4.59,9.63-9.62Z"/></svg>

After

Width:  |  Height:  |  Size: 258 B

View File

@@ -0,0 +1,24 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.custom-checkbox.sql-checkbox {
margin-right: 5px;
margin-top: 2px;
width: 12px;
height: 12px;
}
.custom-checkbox.sql-checkbox.checked {
background: url('check.svg') center center no-repeat;
}
.vs-dark.monaco-shell .custom-checkbox.sql-checkbox.checked {
background: url('check_inverse.svg') center center no-repeat;
}
.custom-checkbox.sql-checkbox.disabled {
pointer-events: none;
opacity: 0.3;
}

View File

@@ -0,0 +1,146 @@
/*---------------------------------------------------------------------------------------------
* 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 'vs/css!sql/base/browser/ui/dropdownList/media/dropdownList';
import * as DOM from 'vs/base/browser/dom';
import { Dropdown, IDropdownOptions } from 'vs/base/browser/ui/dropdown/dropdown';
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';
export interface IDropdownStyles {
backgroundColor?: Color;
foregroundColor?: Color;
borderColor?: Color;
}
export class DropdownList extends Dropdown {
protected backgroundColor: Color;
protected foregroundColor: Color;
protected borderColor: Color;
constructor(
container: HTMLElement,
private _options: IDropdownOptions,
private _contentContainer: HTMLElement,
private _list: List<any>,
private _themeService: IThemeService,
private _action?: IAction,
) {
super(container, _options);
if (_action) {
let button = new Button(_contentContainer);
button.label = _action.label;
this.toDispose.push(DOM.addDisposableListener(button.getElement(), DOM.EventType.CLICK, () => {
this._action.run();
this.hide();
}));
attachButtonStyler(button, this._themeService);
}
DOM.append(this.element.getHTMLElement(), DOM.$('div.dropdown-icon'));
this.toDispose.push(this.element.on([DOM.EventType.CLICK, DOM.EventType.MOUSE_DOWN, GestureEventType.Tap], (e: Event) => {
DOM.EventHelper.stop(e, true); // prevent default click behaviour to trigger
}).on([DOM.EventType.MOUSE_DOWN, GestureEventType.Tap], (e: Event) => {
// We want to show the context menu on dropdown so that as a user you can press and hold the
// mouse button, make a choice of action in the menu and release the mouse to trigger that
// action.
// Due to some weird bugs though, we delay showing the menu to unwind event stack
// (see https://github.com/Microsoft/vscode/issues/27648)
setTimeout(() => this.show(), 100);
}).on([DOM.EventType.KEY_DOWN], (e: KeyboardEvent) => {
let event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.Enter)) {
e.stopPropagation();
setTimeout(() => {
this.show();
this._list.getHTMLElement().focus();
}, 100);
}
}));
this.toDispose.push(this._list.onSelectionChange(() => {
// focus on the dropdown label then hide the dropdown list
this.element.getHTMLElement().focus();
this.hide();
}));
this.element.getHTMLElement().setAttribute('tabindex', '0');
}
/**
* Render the dropdown contents
*/
protected renderContents(container: HTMLElement): IDisposable {
let div = DOM.append(container, this._contentContainer);
div.style.width = DOM.getTotalWidth(this.element.getHTMLElement()) + 'px';
return null;
}
/**
* Render the selected label of the dropdown
*/
public renderLabel(): void {
if (this._options.labelRenderer) {
this._options.labelRenderer(this.label.getHTMLElement());
}
}
protected onEvent(e: Event, activeElement: HTMLElement): void {
// If there is an event outside dropdown label and dropdown list, hide the dropdown list
if (!DOM.isAncestor(<HTMLElement>e.target, this.element.getHTMLElement()) && !DOM.isAncestor(<HTMLElement>e.target, this._contentContainer)) {
// focus on the dropdown label then hide the dropdown list
this.element.getHTMLElement().focus();
this.hide();
// If there is an keyboard event inside the list and key code is escape, hide the dropdown list
} else if (DOM.isAncestor(<HTMLElement>e.target, this._list.getHTMLElement()) && e instanceof KeyboardEvent) {
let event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.Escape)) {
// focus on the dropdown label then hide the dropdown list
this.element.getHTMLElement().focus();
this.hide();
e.stopPropagation();
}
}
}
public style(styles: IDropdownStyles): void {
this.backgroundColor = styles.backgroundColor;
this.foregroundColor = styles.foregroundColor;
this.borderColor = styles.borderColor;
this.applyStyles();
}
protected applyStyles(): void {
const background = this.backgroundColor ? this.backgroundColor.toString() : null;
const foreground = this.foregroundColor ? this.foregroundColor.toString() : null;
const border = this.borderColor ? this.borderColor.toString() : null;
this.applyStylesOnElement(this._contentContainer, background, foreground, border);
if (this.label) {
this.applyStylesOnElement(this.element.getHTMLElement(), background, foreground, border);
}
}
private applyStylesOnElement(element: HTMLElement, background: string, foreground: string, border: string): void {
if (element) {
element.style.backgroundColor = background;
element.style.color = foreground;
element.style.borderWidth = border ? '1px' : null;
element.style.borderStyle = border ? 'solid' : null;
element.style.borderColor = border;
}
}
}

View File

@@ -0,0 +1,34 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.dropdown {
width: 100%;
/* TODO: Determine a more permanent fix; vs/dropdown is overwriting this selector in packaged builds */
display: flex !important;
align-items: flex-start;
cursor: pointer;
}
.dropdown > .dropdown-label {
width: 100%;
}
.dropdown > .dropdown-label:not(:empty) {
padding: 0px;
}
.dropdown > .dropdown-icon {
flex: 0 0 15px;
height: 10px;
width: 10px;
content: url("dropdownarrow.svg");
align-self: center;
padding-right: 10px;
}
.vs-dark .dropdown > .dropdown-icon,
.hc-black .dropdown > .dropdown-icon {
content: url("dropdownarrow_inverse.svg");
}

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><defs><style>.cls-1{fill:#333;}</style></defs><title>dropdownarrow</title><path class="cls-1" d="M0,3H12L6,9Z"/></svg>

After

Width:  |  Height:  |  Size: 211 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><defs><style>.cls-1{fill:#fff;}</style></defs><title>dropdownarrow_inverse</title><path class="cls-1" d="M0,3H12L6,9Z"/></svg>

After

Width:  |  Height:  |  Size: 219 B

View File

@@ -0,0 +1,23 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Action } from 'vs/base/common/actions';
import { TPromise } from 'vs/base/common/winjs.base';
import * as nls from 'vs/nls';
export class ToggleDropdownAction extends Action {
private static readonly ID = 'dropdownAction.toggle';
private static readonly LABEL = nls.localize('dropdownAction.toggle', "Toggle dropdown");
private static readonly ICON = 'dropdown-arrow';
constructor(private _fn: () => any) {
super(ToggleDropdownAction.ID, ToggleDropdownAction.LABEL, ToggleDropdownAction.ICON);
}
public run(): TPromise<boolean> {
this._fn();
return TPromise.as(true);
}
}

View File

@@ -0,0 +1,307 @@
/*---------------------------------------------------------------------------------------------
* 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!./media/dropdownList';
import { ToggleDropdownAction } from './actions';
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 * 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';
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';
export interface IDropdownOptions extends IDropdownStyles {
/**
* Whether or not a options in the list must be selected or a "new" option can be set
*/
strictSelection?: boolean;
/**
* Maximum height of the dropdown, defaults to 500
*/
maxHeight?: number;
/**
* Initial values for the dropdown, can be set afterwards
*/
values?: string[];
/**
* Placeholder to use in the input
*/
placeholder?: string;
/**
* Warning message to show when the input is not part of the supplied list, only used if strictSelection = false
*/
warningMessage?: string;
/**
* Error Message to show if input is not part of the supplied list, only used if strictSelection = false
*/
errorMessage?: string;
}
export interface IDropdownStyles {
contextBackground?: Color;
contextBorder?: Color;
}
const errorMessage = nls.localize('editableDropdown.errorValidate', "Must be an option from the list");
const defaults: IDropdownOptions = {
strictSelection: true,
maxHeight: 300,
errorMessage: errorMessage,
contextBorder: Color.fromHex('#696969')
};
interface ListResource {
label: string;
}
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 _input: InputBox;
private _list: List<ListResource>;
private _values: string[];
private _options: IDropdownOptions;
private _toggleAction: ToggleDropdownAction;
// we have to create our own contextview since otherwise inputbox will override ours
private _contextView: ContextView;
private _onBlur = this._register(new Emitter<void>());
public onBlur: Event<void> = this._onBlur.event;
private _onValueChange = this._register(new Emitter<string>());
public onValueChange: Event<string> = this._onValueChange.event;
private _onFocus = this._register(new Emitter<void>());
public onFocus: Event<void> = this._onFocus.event;
constructor(
container: HTMLElement,
contextViewService: IContextViewProvider,
private _themeService: IThemeService,
opt?: IDropdownOptions
) {
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._toggleAction = new ToggleDropdownAction(() => this._showList());
this._input = new InputBox(this.$input.getHTMLElement(), contextViewService, {
validationOptions: {
showMessage: false,
validation: v => this._inputValidator(v)
},
placeholder: this._options.placeholder,
actions: [this._toggleAction]
});
this._register(DOM.addDisposableListener(this._input.inputElement, DOM.EventType.FOCUS, () => {
this._showList();
}));
this._register(DOM.addDisposableListener(this._input.inputElement, DOM.EventType.BLUR, () => {
if (!this._list.isDOMFocused) {
this._onBlur.fire();
}
}));
this._register(DOM.addStandardDisposableListener(this._input.inputElement, DOM.EventType.KEY_UP, (e: StandardKeyboardEvent) => {
switch (e.keyCode) {
case KeyCode.Enter:
if (this._input.validate()) {
this._onValueChange.fire(this._input.value);
}
e.stopPropagation();
break;
case KeyCode.Escape:
if (this.$list.getHTMLElement().parentElement) {
this._input.validate();
this._onBlur.fire();
this._contextView.hide();
e.stopPropagation();
}
break;
case KeyCode.Tab:
this._input.validate();
this._onBlur.fire();
this._contextView.hide();
e.stopPropagation();
break;
case KeyCode.DownArrow:
if (!this.$list.getHTMLElement().parentElement) {
this._showList();
}
this._list.getHTMLElement().focus();
e.stopPropagation();
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._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._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')));
}
});
this._register(this._contextView);
this._register(this.$el);
this._register(this.$input);
this._register(this.$list);
this._register(this._list);
this._register(this._input);
this._register(this._contextView);
}
private _showList(): void {
if (this._input.isEnabled) {
this._onFocus.fire();
this._contextView.show({
getAnchor: () => this.$input.getHTMLElement(),
render: container => {
this.$list.appendTo(container);
this._list.layout(parseInt(this.$list.style('height')));
return { dispose: () => { } };
},
onDOMEvent: (e, activeElement) => {
if (!DOM.isAncestor(activeElement, this.$el.getHTMLElement())) {
this._input.validate();
this._onBlur.fire();
this._contextView.hide();
}
}
});
}
}
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();
}
public get value(): string {
return this._input.value;
}
public set value(val: string) {
this._input.value = val;
}
public focus() {
this._input.focus();
}
public blur() {
this._input.blur();
this._contextView.hide();
}
style(style: IListStyles & IInputBoxStyles & IDropdownStyles) {
this._list.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}`);
}
private _inputValidator(value: string): IMessage {
if (this._values && !this._values.includes(value)) {
if (this._options.strictSelection) {
return {
content: this._options.errorMessage,
type: MessageType.ERROR
};
} else if (this._options.warningMessage) {
return {
content: this._options.warningMessage,
type: MessageType.WARNING
};
}
}
return undefined;
}
public set enabled(val: boolean) {
this._input.setEnabled(val);
this._toggleAction.enabled = val;
}
public get enabled(): boolean {
return this._input.isEnabled();
}
}

View File

@@ -0,0 +1,23 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.vs .dropdown-arrow.icon {
background-image: url("dropdownarrow.svg");
}
.vs-dark .dropdown-arrow.icon,
.hc-black .dropdown-arrow.icon {
background-image: url("dropdownarrow_inverse.svg");
}
.dropdown .monaco-action-bar .action-label.icon.dropdown-arrow {
padding: 0;
background-size: 10px;
background-position: 50%;
}
.dropdown .monaco-action-bar .action-item {
margin: 0;
}

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><defs><style>.cls-1{fill:#333;}</style></defs><title>dropdownarrow</title><path class="cls-1" d="M0,3H12L6,9Z"/></svg>

After

Width:  |  Height:  |  Size: 211 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><defs><style>.cls-1{fill:#fff;}</style></defs><title>dropdownarrow_inverse</title><path class="cls-1" d="M0,3H12L6,9Z"/></svg>

After

Width:  |  Height:  |  Size: 219 B

View File

@@ -0,0 +1,81 @@
/*---------------------------------------------------------------------------------------------
* 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 { InputBox as vsInputBox, IInputOptions, IInputBoxStyles as vsIInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox';
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { Color } from 'vs/base/common/color';
import Event, { Emitter } from 'vs/base/common/event';
export interface OnLoseFocusParams {
value: string;
hasChanged: boolean;
}
export interface IInputBoxStyles extends vsIInputBoxStyles {
disabledInputBackground?: Color;
disabledInputForeground?: Color;
}
export class InputBox extends vsInputBox {
private enabledInputBackground: Color;
private enabledInputForeground: Color;
private enabledInputBorder: Color;
private disabledInputBackground: Color;
private disabledInputForeground: Color;
private disabledInputBorder: Color;
private _lastLoseFocusValue: string;
private _onLoseFocus = this._register(new Emitter<OnLoseFocusParams>());
public onLoseFocus: Event<OnLoseFocusParams> = this._onLoseFocus.event;
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, options?: IInputOptions) {
super(container, contextViewProvider, options);
this.enabledInputBackground = this.inputBackground;
this.enabledInputForeground = this.inputForeground;
this.enabledInputBorder = this.inputBorder;
this.disabledInputBackground = Color.transparent;
this.disabledInputForeground = null;
this.disabledInputBorder = null;
this._lastLoseFocusValue = this.value;
let self = this;
this.onblur(this.inputElement, () => {
self._onLoseFocus.fire({ value: self.value, hasChanged: self._lastLoseFocusValue !== self.value });
self._lastLoseFocusValue = self.value;
});
}
public style(styles: IInputBoxStyles): void {
super.style(styles);
this.enabledInputBackground = this.inputBackground;
this.enabledInputForeground = this.inputForeground;
this.enabledInputBorder = this.inputBorder;
this.disabledInputBackground = styles.disabledInputBackground;
this.disabledInputForeground = styles.disabledInputForeground;
}
public enable(): void {
super.enable();
this.inputBackground = this.enabledInputBackground;
this.inputForeground = this.enabledInputForeground;
this.inputBorder = this.enabledInputBorder;
this.applyStyles();
}
public disable(): void {
super.disable();
this.inputBackground = this.disabledInputBackground;
this.inputForeground = this.disabledInputForeground;
this.inputBorder = this.disabledInputBorder;
this.applyStyles();
}
public isEnabled(): boolean {
return !this.inputElement.hasAttribute('disabled');
}
}

View File

@@ -0,0 +1,261 @@
/*---------------------------------------------------------------------------------------------
* 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 * 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 { 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 { 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';
const $ = dom.$;
export interface IListBoxStyles {
selectBackground?: Color;
selectForeground?: Color;
selectBorder?: Color;
inputValidationInfoBorder?: Color;
inputValidationInfoBackground?: Color;
inputValidationWarningBorder?: Color;
inputValidationWarningBackground?: Color;
inputValidationErrorBorder?: Color;
inputValidationErrorBackground?: Color;
}
/*
* 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;
private inputValidationWarningBorder: Color;
private inputValidationWarningBackground: Color;
private inputValidationErrorBorder: Color;
private inputValidationErrorBackground: Color;
private message: IMessage;
private contextViewProvider: IContextViewProvider;
private isValid: boolean;
constructor(options: string[], selectedOption: string, contextViewProvider: IContextViewProvider) {
super(options, 0);
this.contextViewProvider = contextViewProvider;
this.isValid = true;
this.selectElement.multiple = true;
this.selectElement.style['height'] = '80px';
// Set width style for horizontal scrollbar
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.enabledSelectBackground = this.selectBackground;
this.enabledSelectForeground = this.selectForeground;
this.enabledSelectBorder = this.selectBorder;
this.disabledSelectBackground = Color.transparent;
this.disabledSelectForeground = null;
this.disabledSelectBorder = null;
this.inputValidationInfoBorder = defaultOpts.inputValidationInfoBorder;
this.inputValidationInfoBackground = defaultOpts.inputValidationInfoBackground;
this.inputValidationWarningBorder = defaultOpts.inputValidationWarningBorder;
this.inputValidationWarningBackground = defaultOpts.inputValidationWarningBackground;
this.inputValidationErrorBorder = defaultOpts.inputValidationErrorBorder;
this.inputValidationErrorBackground = defaultOpts.inputValidationErrorBackground;
this.onblur(this.selectElement, () => this.onBlur());
this.onfocus(this.selectElement, () => this.onFocus());
}
public style(styles: IListBoxStyles): void {
var superStyle: ISelectBoxStyles = {
selectBackground: styles.selectBackground,
selectForeground: styles.selectForeground,
selectBorder: styles.selectBorder
}
super.style(superStyle);
this.enabledSelectBackground = this.selectBackground;
this.enabledSelectForeground = this.selectForeground;
this.enabledSelectBorder = this.selectBorder;
this.inputValidationInfoBackground = styles.inputValidationInfoBackground;
this.inputValidationInfoBorder = styles.inputValidationInfoBorder;
this.inputValidationWarningBackground = styles.inputValidationWarningBackground;
this.inputValidationWarningBorder = styles.inputValidationWarningBorder;
this.inputValidationErrorBackground = styles.inputValidationErrorBackground;
this.inputValidationErrorBorder = styles.inputValidationErrorBorder;
}
public setValidation(isValid: boolean, message?: IMessage): void {
this.isValid = isValid;
this.message = message;
if (this.isValid) {
this.selectElement.style.border = `1px solid ${this.selectBorder}`;
} else {
const styles = this.stylesForType(this.message.type);
this.selectElement.style.border = styles.border ? `1px solid ${styles.border}` : null;
}
}
public get isContentValid(): boolean {
return this.isValid;
}
public get selectedOptions(): string[] {
var selected = [];
for (var i = 0; i < this.selectElement.selectedOptions.length; i++ ) {
selected.push(this.selectElement.selectedOptions[i].innerHTML);
}
return selected;
}
public get count(): number {
return this.selectElement.options.length;
}
// Remove selected options
public remove(): void {
var indexes = [];
for (var i = 0; i < this.selectElement.selectedOptions.length; i++ ) {
indexes.push(this.selectElement.selectedOptions[i].index);
}
indexes.sort((a, b) => b-a);
for (var i = 0; i < indexes.length; i++) {
this.selectElement.remove(indexes[i]);
this.options.splice(indexes[i], 1);
}
}
public add(option: string): void {
this.selectElement.add(this.createOption(option));
this.options.push(option);
}
// 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 (ctrlOrCmd && key === this.keyC) {
var textToCopy = this.selectedOptions[0];
for (var i = 1; i < this.selectedOptions.length; i++) {
textToCopy = textToCopy + ', ' + this.selectedOptions[i];
}
// Copy to clipboard
WorkbenchUtils.executeCopy(textToCopy);
event.stopPropagation();
}
}
}
public dispose(): void {
this._toDispose2 = lifecycle.dispose(this._toDispose2);
super.dispose();
}
public enable(): void {
this.selectElement.disabled = false;
this.selectBackground = this.enabledSelectBackground;
this.selectForeground = this.enabledSelectForeground;
this.selectBorder = this.enabledSelectBorder;
this.applyStyles();
}
public disable(): void {
this.selectElement.disabled = true;
this.selectBackground = this.disabledSelectBackground;
this.selectForeground = this.disabledSelectForeground;
this.selectBorder = this.disabledSelectBorder;
this.applyStyles();
}
public onBlur(): void {
if (!this.isValid) {
this.contextViewProvider.hideContextView();
}
}
public onFocus(): void {
if (!this.isValid) {
this.showMessage();
}
}
public focus(): void {
this.selectElement.focus();
}
public showMessage(): void {
let div: HTMLElement;
let layout = () => div.style.width = dom.getTotalWidth(this.selectElement) + 'px';
this.contextViewProvider.showContextView({
getAnchor: () => this.selectElement,
anchorAlignment: AnchorAlignment.RIGHT,
render: (container: HTMLElement) => {
div = dom.append(container, $('.monaco-inputbox-container'));
layout();
const renderOptions: RenderOptions = {
inline: true,
className: 'monaco-inputbox-message'
};
let spanElement: HTMLElement = (this.message.formatContent
? renderFormattedText(this.message.content, renderOptions)
: renderText(this.message.content, renderOptions)) as any;
dom.addClass(spanElement, this.classForType(this.message.type));
const styles = this.stylesForType(this.message.type);
spanElement.style.backgroundColor = styles.background ? styles.background.toString() : null;
spanElement.style.border = styles.border ? `1px solid ${styles.border}` : null;
dom.append(div, spanElement);
return null;
},
layout: layout
});
}
private classForType(type: MessageType): string {
switch (type) {
case MessageType.INFO: return 'info';
case MessageType.WARNING: return 'warning';
default: return 'error';
}
}
private stylesForType(type: MessageType): { border: Color; background: Color } {
switch (type) {
case MessageType.INFO: return { border: this.inputValidationInfoBorder, background: this.inputValidationInfoBackground };
case MessageType.WARNING: return { border: this.inputValidationWarningBorder, background: this.inputValidationWarningBackground };
default: return { border: this.inputValidationErrorBorder, background: this.inputValidationErrorBackground };
}
}
}

View File

@@ -0,0 +1,106 @@
/*---------------------------------------------------------------------------------------------
* 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 { 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';
export function appendRow(container: Builder, label: string, labelClass: string, cellContainerClass: string): Builder {
let cellContainer: Builder;
container.element('tr', {}, (rowContainer) => {
rowContainer.element('td', { class: labelClass }, (labelCellContainer) => {
labelCellContainer.div({}, (labelContainer) => {
labelContainer.innerHtml(label);
});
});
rowContainer.element('td', { class: cellContainerClass }, (inputCellContainer) => {
cellContainer = inputCellContainer;
});
});
return cellContainer;
}
export function appendRowLink(container: Builder, label: string, labelClass: string, cellContainerClass: string): Builder {
let rowButton: Button;
container.element('tr', {}, (rowContainer) => {
rowContainer.element('td', { class: labelClass }, (labelCellContainer) => {
labelCellContainer.div({}, (labelContainer) => {
labelContainer.innerHtml(label);
});
});
rowContainer.element('td', { class: cellContainerClass }, (inputCellContainer) => {
inputCellContainer.element('div', {}, (rowContainer) => {
rowButton = new Button(rowContainer);
});
});
});
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;
} else if (types.isString(value)) {
return value.toLowerCase() === 'true';
}
return false;
}
export function getCategoryDisplayName(categories: data.CategoryValue[], categoryName: string) {
var displayName: string;
categories.forEach(c => {
if (c.name === categoryName) {
displayName = c.displayName;
}
});
return displayName;
}
export function getCategoryName(categories: data.CategoryValue[], categoryDisplayName: string) {
var categoryName: string;
categories.forEach(c => {
if (c.displayName === categoryDisplayName) {
categoryName = c.name;
}
});
return categoryName;
}

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#212121;}</style></defs><title>back_16x16</title><path class="cls-1" d="M16.15,8.5H2.1l6.15,6.15-.7.7L.19,8,7.55.65l.7.7L2.1,7.5h14Z"/></svg>

After

Width:  |  Height:  |  Size: 259 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>back_inverse_16x16</title><path class="cls-1" d="M16.15,8.5H2.1l6.15,6.15-.7.7L.19,8,7.55.65l.7.7L2.1,7.5h14Z"/></svg>

After

Width:  |  Height:  |  Size: 264 B

View File

@@ -0,0 +1,178 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-shell .modal {
background-color: rgba(204, 204, 204, 0.6);
right: 0;
left: 0;
top: 0;
bottom: 0;
position: absolute;
z-index: 500;
}
.monaco-shell .modal:not(.flyout-dialog) .modal-dialog {
margin: auto;
width: 640px;
height: 480px;
}
.modal .modal-header {
padding: 15px;
}
.modal .modal-footer {
padding: 15px;
}
.modal .icon.in-progress {
width: 25px;
height: 25px;
}
.monaco-shell .modal.flyout-dialog .modal-dialog {
margin: auto auto auto auto;
height: 100%;
width: 500px;
right: 0;
position: absolute;
overflow-y: hidden;
}
.monaco-shell .modal.flyout-dialog.wide .modal-dialog {
width: 1200px;
max-width: 95%;
min-width: 400px;
}
.monaco-shell .modal.flyout-dialog .modal-content {
height: 100%;
font-size: 11px;
}
.modal .modal-title {
font-size: 15px;
}
.modal .modal-title-icon {
width: 20px;
height: 20px;
float: left;
margin-right: 10px;
}
.monaco-shell .modal.flyout-dialog .modal-body {
height: calc(100% - 105px);
}
/* modal body for angular component dialog */
.monaco-shell .modal.flyout-dialog .angular-modal-body {
height: calc(100% - 90px);
}
/* Style for body and footer in modal dialog. This should be applied to dialog created with angular component. */
.monaco-shell .modal.flyout-dialog .modal-body-and-footer {
height: 100%;
}
/* modl body content style(excluding dialogErrorMessage section) for angulr component dialog */
.angular-modal-body-content {
overflow-x: hidden;
overflow-y: auto;
padding: 15px;
}
.modal.flyout-dialog .angular-form {
height: 100%;
}
.modal.flyout-dialog .dialog-label {
width: 100%;
padding-bottom: 5px;
}
/* Add space in the bottom to separate each input elements */
.modal.flyout-dialog .input-divider {
width: 100%;
padding-bottom: 20px;
}
.modal.flyout-dialog .input {
width: 100%;
border: none;
height: 25px;
padding-left: 4px;
}
.modal.flyout-dialog .modal-body {
overflow-y: auto;
}
.vs-dark.monaco-shell .modal.flyout-dialog .input {
background-color: #3C3C3C;
}
.vs-dark.monaco-shell .modal.flyout-dialog input:disabled {
background-color: #E1E1E1;
color: #3C3C3C;
}
.modal .select-box {
width: 100%;
height: 25px;
color: #6C6C6C;
font-size: 11px;
border: 1px solid transparent;
}
.modal.flyout-dialog .dialogErrorMessage {
overflow: hidden;
padding-left: 10px;
overflow-y: auto;
display: flex;
}
.modal .modal-footer {
display: flex;
}
.modal .modal-footer .left-footer {
display: flex;
flex: 1 1 auto;
justify-content: flex-start;
}
.modal .modal-footer .right-footer {
display: flex;
flex: 1 1 auto;
justify-content: flex-end;
}
.modal .footer-button a.monaco-button.monaco-text-button {
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 {
outline-width: 1px;
}
.modal .footer-button {
margin-right: 5px;
}
.modal .right-footer .footer-button:last-of-type {
margin-right: none;
}
.modal.flyout-dialog .icon.error {
float: left;
margin-right: 10px;
width: 20px;
height: 20px;
}

View File

@@ -0,0 +1,60 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.optionsDialog-label {
width: 120px;
padding-bottom: 5px;
}
.optionsDialog-input {
width: 200px;
padding-bottom: 5px;
padding-right: 7px;
}
.options-dialog .select-box {
width: 321px;
margin-left: 1px;
}
.optionsDialog-options-groups {
padding: 15px;
height: calc(100% - 150px);
overflow-y: auto;
}
.backButtonIcon {
content: url('back.svg');
width: 20px;
margin-right: 10px;
float: left;
cursor: pointer;
}
.vs-dark.monaco-shell .backButtonIcon {
content: url('back_inverse.svg');
}
.optionsDialog-description {
padding: 15px;
overflow-y: auto;
}
.optionsDialog-options {
height: calc(100% - 30px);
}
.optionsDialog-description-content {
padding: 10px;
}
.optionsDialog-table{
width:100%;
padding: 10px;
}
td.optionsDialog-input.select-container {
padding-right: 9px;
}

View File

@@ -0,0 +1,432 @@
/*---------------------------------------------------------------------------------------------
* 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/media/icons/common-icons';
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 { 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 * as TelemetryUtils from 'sql/common/telemetryUtilities';
import * as TelemetryKeys from 'sql/common/telemetryKeys';
export const MODAL_SHOWING_KEY = 'modalShowing';
export const MODAL_SHOWING_CONTEXT = new RawContextKey<Array<string>>(MODAL_SHOWING_KEY, []);
export interface IModalDialogStyles {
dialogForeground?: Color;
dialogBorder?: Color;
dialogHeaderAndFooterBackground?: Color;
dialogBodyBackground?: Color;
}
export interface IModalOptions {
isFlyout?: boolean;
isWide?: boolean;
isAngular?: boolean;
hasBackButton?: boolean;
hasTitleIcon?: boolean;
hasErrors?: boolean;
hasSpinner?: boolean;
}
// Needed for angular component dialogs to style modal footer
export class ModalFooterStyle {
public static backgroundColor;
public static borderTopWidth;
public static borderTopStyle;
public static borderTopColor;
}
const defaultOptions: IModalOptions = {
isFlyout: true,
isWide: false,
isAngular: false,
hasBackButton: false,
hasTitleIcon: false,
hasErrors: false,
hasSpinner: false
};
export abstract class Modal extends Disposable implements IThemable {
private _errorMessage: Builder;
private _spinnerElement: HTMLElement;
private _errorIconElement: HTMLElement;
private _dialogForeground: Color;
private _dialogBorder: Color;
private _dialogHeaderAndFooterBackground: Color;
private _dialogBodyBackground: Color;
private _modalDialog: Builder;
private _modalHeaderSection: Builder;
private _modalBodySection: HTMLElement;
private _modalFooterSection: Builder;
private _closeButtonInHeader: Builder;
private _builder: Builder;
private _footerBuilder: Builder;
private _modalTitle: Builder;
private _modalTitleIcon: HTMLElement;
private _leftFooter: Builder;
private _rightFooter: Builder;
private _footerButtons: Button[];
private _keydownListener: IDisposable;
private _resizeListener: IDisposable;
private _modalOptions: IModalOptions;
private _backButton: Button;
private _modalShowingContext: IContextKey<Array<string>>;
private readonly _staticKey: string;
/**
* Get the back button, only available after render and if the hasBackButton option is true
*/
protected get backButton(): Button {
return this._backButton;
}
/**
* 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.
*/
public setWide(isWide: boolean): void {
if (this._builder.hasClass('wide') && isWide === false) {
this._builder.removeClass('wide');
} else if (!this._builder.hasClass('wide') && isWide === true) {
this._builder.addClass('wide');
}
}
/**
* Constructor for modal
* @param _title Title of the modal, if undefined, the title section is not rendered
* @param _name Name of the modal, used for telemetry
* @param _partService
* @param options Modal options
*/
constructor(
private _title: string,
private _name: string,
private _partService: IPartService,
private _telemetryService: ITelemetryService,
private _contextKeyService: IContextKeyService,
options?: IModalOptions
) {
super();
this._modalOptions = options || Object.create(null);
mixin(this._modalOptions, defaultOptions, false);
this._staticKey = generateUuid();
this._modalShowingContext = MODAL_SHOWING_CONTEXT.bindTo(_contextKeyService);
this._footerButtons = [];
}
/**
* Build and render the modal, will call {@link Modal#renderBody}
*/
public render() {
let modalBodyClass = (this._modalOptions.isAngular === false ? 'modal-body' : 'modal-body-and-footer');
let parts: Array<HTMLElement> = [];
// This modal header section refers to the header of of the dialog
// will not be rendered if the title is passed in as undefined
if (this._title !== undefined) {
this._modalHeaderSection = $().div({ class: 'modal-header' }, (modalHeader) => {
if (this._modalOptions.hasBackButton) {
modalHeader.div({ class: 'modal-go-back' }, (cellContainer) => {
this._backButton = new Button(cellContainer);
this._backButton.icon = 'backButtonIcon';
});
}
if (this._modalOptions.hasTitleIcon) {
modalHeader.div({ class: 'modal-title-icon' }, (modalIcon) => {
this._modalTitleIcon = modalIcon.getHTMLElement();
});
}
modalHeader.div({ class: 'modal-title' }, (modalTitle) => {
this._modalTitle = modalTitle;
modalTitle.innerHtml(this._title);
});
});
parts.push(this._modalHeaderSection.getHTMLElement());
}
// This modal body section refers to the body of of the dialog
let body: Builder;
$().div({ class: modalBodyClass }, (builder) => {
body = builder;
});
this._modalBodySection = body.getHTMLElement();
parts.push(body.getHTMLElement());
this.renderBody(body.getHTMLElement());
if (this._modalOptions.isAngular === false && this._modalOptions.hasErrors) {
body.div({ class: 'dialogErrorMessage', id: 'dialogErrorMessage' }, (errorMessageContainer) => {
errorMessageContainer.div({ class: 'icon error' }, (iconContainer) => {
this._errorIconElement = iconContainer.getHTMLElement();
this._errorIconElement.style.visibility = 'hidden';
});
errorMessageContainer.div({ class: 'errorMessage' }, (messageContainer) => {
this._errorMessage = messageContainer;
});
});
}
// This modal footer section refers to the footer of of the dialog
if (this._modalOptions.isAngular === false) {
this._modalFooterSection = $().div({ class: 'modal-footer' }, (modelFooter) => {
if (this._modalOptions.hasSpinner) {
modelFooter.div({ 'class': 'icon in-progress' }, (spinnerContainer) => {
this._spinnerElement = spinnerContainer.getHTMLElement();
this._spinnerElement.style.visibility = 'hidden';
});
}
modelFooter.div({ 'class': 'left-footer' }, (leftFooter) => {
this._leftFooter = leftFooter;
});
modelFooter.div({ 'class': 'right-footer' }, (rightFooter) => {
this._rightFooter = rightFooter;
});
this._footerBuilder = modelFooter;
});
parts.push(this._modalFooterSection.getHTMLElement());
}
let builderClass = 'modal fade';
if (this._modalOptions.isFlyout) {
builderClass += ' flyout-dialog';
}
if (this._modalOptions.isWide) {
builderClass += ' wide';
}
// The builder builds the dialog. It append header, body and footer sections.
this._builder = $().div({ class: builderClass, 'role': 'dialog' }, (dialogContainer) => {
this._modalDialog = dialogContainer.div({ class: 'modal-dialog ', role: 'document' }, (modalDialog) => {
modalDialog.div({ class: 'modal-content' }, (modelContent) => {
parts.forEach((part) => {
modelContent.append(part);
});
});
});
});
}
/**
* Called for extended classes to render the body
* @param container The parent container to attach the rendered body to
*/
protected abstract renderBody(container: HTMLElement): void;
/**
* Overridable to change behavior of escape key
*/
protected onClose(e: StandardKeyboardEvent) {
this.hide();
}
/**
* Overridable to change behavior of enter key
*/
protected onAccept(e: StandardKeyboardEvent) {
this.hide();
}
/**
* 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._keydownListener = DOM.addDisposableListener(document, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
let context = this._modalShowingContext.get();
if (context[context.length - 1] === this._staticKey) {
let event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.Enter)) {
this.onAccept(event);
} else if (event.equals(KeyCode.Escape)) {
this.onClose(event);
}
}
});
this._resizeListener = DOM.addDisposableListener(window, DOM.EventType.RESIZE, (e: Event) => {
this.layout(DOM.getTotalHeight(this._builder.getHTMLElement()));
});
this.layout(DOM.getTotalHeight(this._builder.getHTMLElement()));
TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.ModalDialogOpened, { name: this._name });
}
/**
* Required to be implemented so that scrolling and other functions operate correctly. Should re-layout controls in the modal
*/
protected abstract layout(height?: number): void;
/**
* Hides the modal and removes key listeners
*/
protected hide() {
this._footerButtons.forEach(button => button.applyStyles());
this._modalShowingContext.get().pop();
this._builder.offDOM();
this._keydownListener.dispose();
this._resizeListener.dispose();
TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.ModalDialogClosed, { name: this._name });
}
/**
* Adds a button to the footer of the modal
* @param label Label to show on the button
* @param onSelect The callback to call when the button is selected
*/
protected addFooterButton(label: string, onSelect: () => void, orientation: 'left' | 'right' = 'right'): Button {
let footerButton = $('div.footer-button');
let button = new Button(footerButton);
button.label = label;
button.addListener('click', () => onSelect());
if (orientation === 'left') {
footerButton.appendTo(this._leftFooter);
} else {
footerButton.appendTo(this._rightFooter);
}
this._footerButtons.push(button);
return button;
}
/**
* Show an error in the error message element
* @param err Text to show in the error message
*/
protected setError(err: string) {
if (this._modalOptions.hasErrors) {
if (err === '') {
this._errorIconElement.style.visibility = 'hidden';
} else {
this._errorIconElement.style.visibility = 'visible';
}
this._errorMessage.innerHtml(err);
}
}
/**
* Show the spinner element that shows something is happening, hidden by default
*/
protected showSpinner(): void {
if (this._modalOptions.hasSpinner) {
this._spinnerElement.style.visibility = 'visible';
}
}
/**
* Hide the spinner element to show that something was happening, hidden by default
*/
protected hideSpinner(): void {
if (this._modalOptions.hasSpinner) {
this._spinnerElement.style.visibility = 'hidden';
}
}
/**
* Set spinner element to show or hide
*/
public set spinner(show: boolean) {
if (show) {
this.showSpinner();
} else {
this.hideSpinner();
}
}
/**
* Return background color of header and footer
*/
protected get headerAndFooterBackground(): string {
return this._dialogHeaderAndFooterBackground ? this._dialogHeaderAndFooterBackground.toString() : null;
}
/**
* Set the title of the modal
* @param title
*/
protected set title(title: string) {
if (this._title !== undefined) {
this._modalTitle.innerHtml(title);
}
}
/**
* Set the icon title class name
* @param iconClassName
*/
protected set titleIconClassName(iconClassName: string) {
if (this._modalTitleIcon) {
this._modalTitleIcon.className = 'modal-title-icon ' + iconClassName;
}
}
/**
* Called by the theme registry on theme change to style the component
*/
public style(styles: IModalDialogStyles): void {
this._dialogForeground = styles.dialogForeground;
this._dialogBorder = styles.dialogBorder;
this._dialogHeaderAndFooterBackground = styles.dialogHeaderAndFooterBackground;
this._dialogBodyBackground = styles.dialogBodyBackground;
this.applyStyles();
}
private applyStyles(): void {
const foreground = this._dialogForeground ? this._dialogForeground.toString() : null;
const border = this._dialogBorder ? this._dialogBorder.toString() : null;
const headerAndFooterBackground = this._dialogHeaderAndFooterBackground ? this._dialogHeaderAndFooterBackground.toString() : null;
const bodyBackground = this._dialogBodyBackground ? this._dialogBodyBackground.toString() : null;
ModalFooterStyle.backgroundColor = headerAndFooterBackground;
ModalFooterStyle.borderTopWidth = border ? '1px' : null;
ModalFooterStyle.borderTopStyle = border ? 'solid' : null;
ModalFooterStyle.borderTopColor = border;
if (this._closeButtonInHeader) {
this._closeButtonInHeader.style('color', foreground);
}
if (this._modalDialog) {
this._modalDialog.style('color', foreground);
this._modalDialog.style('border-width', border ? '1px' : null);
this._modalDialog.style('border-style', border ? 'solid' : null);
this._modalDialog.style('border-color', border);
}
if (this._modalHeaderSection) {
this._modalHeaderSection.style('background-color', headerAndFooterBackground);
this._modalHeaderSection.style('border-bottom-width', border ? '1px' : null);
this._modalHeaderSection.style('border-bottom-style', border ? 'solid' : null);
this._modalHeaderSection.style('border-bottom-color', border);
}
if (this._modalBodySection) {
this._modalBodySection.style.backgroundColor = bodyBackground;
}
if (this._modalFooterSection) {
this._modalFooterSection.style('background-color', ModalFooterStyle.backgroundColor);
this._modalFooterSection.style('border-top-width', ModalFooterStyle.borderTopWidth);
this._modalFooterSection.style('border-top-style', ModalFooterStyle.borderTopStyle);
this._modalFooterSection.style('border-top-color', ModalFooterStyle.borderTopColor);
}
}
public dispose() {
super.dispose();
this._keydownListener.dispose();
}
}

View File

@@ -0,0 +1,268 @@
/*---------------------------------------------------------------------------------------------
* 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 'vs/css!./media/optionsDialog';
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 * 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';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { localize } from 'vs/nls';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
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 { 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 {
private _treecontainer: HTMLElement;
constructor(private viewTitle: string, private _bodyContainer: HTMLElement, collapsed: boolean, initialBodySize: number, headerSize: number) {
super(
initialBodySize,
{
expandedBodySize: initialBodySize,
sizing: headerSize,
initialState: collapsed ? CollapsibleState.COLLAPSED : CollapsibleState.EXPANDED,
ariaHeaderLabel: viewTitle
});
}
public renderHeader(container: HTMLElement): void {
const titleDiv = $('div.title').appendTo(container);
$('span').text(this.viewTitle).appendTo(titleDiv);
}
public renderBody(container: HTMLElement): void {
this._treecontainer = document.createElement('div');
container.appendChild(this._treecontainer);
this._treecontainer.appendChild(this._bodyContainer);
}
public layoutBody(size: number): void {
this._treecontainer.style.height = size + 'px';
}
}
export class OptionsDialog extends Modal {
private _body: HTMLElement;
private _optionGroups: HTMLElement;
private _dividerBuilder: Builder;
private _okButton: Button;
private _closeButton: Button;
private _optionTitle: Builder;
private _optionDescription: Builder;
private _optionElements: { [optionName: string]: OptionsDialogHelper.IOptionElement } = {};
private _optionValues: { [optionName: string]: string };
private _optionRowSize = 31;
private _optionCategoryPadding = 30;
private _categoryHeaderSize = 22;
private _onOk = new Emitter<void>();
public onOk: Event<void> = this._onOk.event;
private _onCloseEvent = new Emitter<void>();
public onCloseEvent: Event<void> = this._onCloseEvent.event;
public okLabel: string = localize('ok', 'OK');
public cancelLabel: string = localize('cancel', 'Cancel');
constructor(
title: string,
name: string,
options: IModalOptions,
@IPartService partService: IPartService,
@IWorkbenchThemeService private _themeService: IWorkbenchThemeService,
@IContextViewService private _contextViewService: IContextViewService,
@ITelemetryService telemetryService: ITelemetryService,
@IContextKeyService contextKeyService: IContextKeyService
) {
super(title, name, partService, telemetryService, contextKeyService, options);
}
public render() {
super.render();
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 });
}
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);
let self = this;
this._register(self._themeService.onDidColorThemeChange(e => self.updateTheme(e)));
self.updateTheme(self._themeService.getColorTheme());
}
protected renderBody(container: HTMLElement) {
new Builder(container).div({ class: 'optionsDialog-options' }, (bodyBuilder) => {
this._body = bodyBuilder.getHTMLElement();
});
let builder = new Builder(this._body);
builder.div({ class: 'Connection-divider' }, (dividerContainer) => {
this._dividerBuilder = dividerContainer;
});
builder.div({ class: 'optionsDialog-description' }, (descriptionContainer) => {
descriptionContainer.div({ class: 'modal-title' }, (optionTitle) => {
this._optionTitle = optionTitle;
});
descriptionContainer.div({ class: 'optionsDialog-description-content' }, (optionDescription) => {
this._optionDescription = optionDescription;
});
});
}
// Update theming that is specific to options dialog flyout body
private updateTheme(theme: IColorTheme): void {
let borderColor = theme.getColor(contrastBorder);
let border = borderColor ? borderColor.toString() : null;
if (this._dividerBuilder) {
this._dividerBuilder.style('border-top-width', border ? '1px' : null);
this._dividerBuilder.style('border-top-style', border ? 'solid' : null);
this._dividerBuilder.style('border-top-color', border);
}
}
private onOptionLinkClicked(optionName: string): void {
var option = this._optionElements[optionName].option;
this._optionTitle.innerHtml(option.displayName);
this._optionDescription.innerHtml(option.description);
}
private fillInOptions(container: Builder, options: data.ServiceOption[]): void {
for (var i = 0; i < options.length; i++) {
var option: data.ServiceOption = options[i];
var rowContainer = DialogHelper.appendRow(container, option.displayName, 'optionsDialog-label', 'optionsDialog-input');
OptionsDialogHelper.createOptionElement(option, rowContainer, this._optionValues, this._optionElements, this._contextViewService, (name) => this.onOptionLinkClicked(name));
}
}
private registerStyling(): void {
// Theme styler
for (var optionName in this._optionElements) {
var widget: Widget = this._optionElements[optionName].optionWidget;
var option = this._optionElements[optionName].option;
switch (option.valueType) {
case OptionsDialogHelper.ServiceOptionType.category:
case OptionsDialogHelper.ServiceOptionType.boolean:
this._register(styler.attachSelectBoxStyler(<SelectBox>widget, this._themeService));
break;
case OptionsDialogHelper.ServiceOptionType.string:
case OptionsDialogHelper.ServiceOptionType.password:
case OptionsDialogHelper.ServiceOptionType.number:
this._register(styler.attachInputBoxStyler(<InputBox>widget, this._themeService));
}
}
}
public get optionValues(): { [name: string]: any } {
return this._optionValues;
}
public hideError() {
this.setError('');
}
public showError(err: string) {
this.setError(err);
}
/* Overwrite escape key behavior */
protected onClose() {
this.close();
}
/* Overwrite enter key behavior */
protected onAccept() {
this.ok();
}
public ok(): void {
if (OptionsDialogHelper.validateInputs(this._optionElements)) {
OptionsDialogHelper.updateOptions(this._optionValues, this._optionElements);
this._onOk.fire();
this.close();
}
}
public cancel() {
this.close();
}
public close() {
this._optionGroups.remove();
this.dispose();
this.hide();
this._onCloseEvent.fire();
}
public open(options: data.ServiceOption[], optionValues: { [name: string]: any }) {
this._optionValues = optionValues;
var firstOption: string;
var containerGroup: Builder;
var layoutSize = 0;
var optionsContentBuilder: Builder = $().div({ class: 'optionsDialog-options-groups' }, (container) => {
containerGroup = container;
this._optionGroups = container.getHTMLElement();
});
var splitview = new SplitView(containerGroup.getHTMLElement());
let categoryMap = OptionsDialogHelper.groupOptionsByCategory(options);
for (var category in categoryMap) {
var serviceOptions: data.ServiceOption[] = categoryMap[category];
var bodyContainer = $().element('table', { class: 'optionsDialog-table' }, (tableContainer: Builder) => {
this.fillInOptions(tableContainer, serviceOptions);
});
var viewSize = this._optionCategoryPadding + serviceOptions.length * this._optionRowSize;
layoutSize += (viewSize + this._categoryHeaderSize);
var categoryView = new CategoryView(category, bodyContainer.getHTMLElement(), false, viewSize, this._categoryHeaderSize);
splitview.addView(categoryView);
if (!firstOption) {
firstOption = serviceOptions[0].name;
}
}
splitview.layout(layoutSize);
let body = new Builder(this._body);
body.append(optionsContentBuilder.getHTMLElement(), 0);
this.show();
var firstOptionWidget = this._optionElements[firstOption].optionWidget;
this.registerStyling();
firstOptionWidget.focus();
}
protected layout(height?: number): void {
// Nothing currently laid out in this class
}
public dispose(): void {
super.dispose();
for (var optionName in this._optionElements) {
var widget: Widget = this._optionElements[optionName].optionWidget;
widget.dispose();
delete this._optionElements[optionName];
}
}
}

View File

@@ -0,0 +1,189 @@
/*---------------------------------------------------------------------------------------------
* 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 * as DialogHelper from './dialogHelper';
import { Builder } from 'vs/base/browser/builder';
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
import * as types from 'vs/base/common/types';
import data = require('data');
import { localize } from 'vs/nls';
export interface IOptionElement {
optionWidget: any;
option: data.ServiceOption;
optionValue: any;
}
export enum ServiceOptionType {
string = 0,
multistring = 1,
password = 2,
number = 3,
category = 4,
boolean = 5
}
export function createOptionElement(option: data.ServiceOption, rowContainer: Builder, options: { [name: string]: any },
optionsMap: { [optionName: string]: IOptionElement }, contextViewService: IContextViewService, onFocus: (name) => void): void {
let possibleInputs: string[] = [];
let optionValue = this.getOptionValueAndCategoryValues(option, options, possibleInputs);
let optionWidget: any;
let inputElement: HTMLElement;
let missingErrorMessage = localize('missingRequireField', ' is required.');
let invalidInputMessage = localize('invalidInput', 'Invalid input. Numeric value expected.');
switch (option.valueType) {
case ServiceOptionType.number:
optionWidget = new InputBox(rowContainer.getHTMLElement(), contextViewService, {
validationOptions: {
validation: (value: string) => {
if (!value && option.isRequired) {
return { type: MessageType.ERROR, content: option.displayName + missingErrorMessage };
} else if (!types.isNumber(Number(value))) {
return { type: MessageType.ERROR, content: invalidInputMessage };
} else {
return null;
}
}
}
});
optionWidget.value = optionValue;
inputElement = this.findElement(rowContainer, 'input');
break;
case ServiceOptionType.category:
case ServiceOptionType.boolean:
optionWidget = new SelectBox(possibleInputs, optionValue.toString());
DialogHelper.appendInputSelectBox(rowContainer, optionWidget);
inputElement = this.findElement(rowContainer, 'select-box');
break;
case ServiceOptionType.string:
case ServiceOptionType.password:
optionWidget = new InputBox(rowContainer.getHTMLElement(), contextViewService, {
validationOptions: {
validation: (value: string) => (!value && option.isRequired) ? ({ type: MessageType.ERROR, content: option.displayName + missingErrorMessage }) : null
}
});
optionWidget.value = optionValue;
if (option.valueType === ServiceOptionType.password) {
optionWidget.inputElement.type = 'password';
}
inputElement = this.findElement(rowContainer, 'input');
}
optionsMap[option.name] = { optionWidget: optionWidget, option: option, optionValue: optionValue };
inputElement.onfocus = () => onFocus(option.name);
}
export function getOptionValueAndCategoryValues(option: data.ServiceOption, options: { [optionName: string]: any }, possibleInputs: string[]): any {
var optionValue = option.defaultValue;
if (options[option.name]) {
// if the value type is boolean, the option value can be either boolean or string
if (option.valueType === ServiceOptionType.boolean) {
if (options[option.name] === true || options[option.name] === this.trueInputValue) {
optionValue = this.trueInputValue;
} else {
optionValue = this.falseInputValue;
}
} else {
optionValue = options[option.name];
}
}
if (option.valueType === ServiceOptionType.boolean || option.valueType === ServiceOptionType.category) {
// If the option is not required, the empty string should be add at the top of possible choices
if (!option.isRequired) {
possibleInputs.push('');
}
if (option.valueType === ServiceOptionType.boolean) {
possibleInputs.push(this.trueInputValue, this.falseInputValue);
} else {
option.categoryValues.map(c => possibleInputs.push(c.name));
}
// If the option value is not set and default value is null, the option value should be set to the first possible input.
if (optionValue === null || optionValue === undefined) {
optionValue = possibleInputs[0];
}
}
return optionValue;
}
export function validateInputs(optionsMap: { [optionName: string]: IOptionElement }): boolean {
let isValid = true;
let isFocused = false;
for (var optionName in optionsMap) {
var optionElement: IOptionElement = optionsMap[optionName];
var widget = optionElement.optionWidget;
var isInputBox = (optionElement.option.valueType === ServiceOptionType.string ||
optionElement.option.valueType === ServiceOptionType.password ||
optionElement.option.valueType === ServiceOptionType.number);
if (isInputBox) {
if (!widget.validate()) {
isValid = false;
if (!isFocused) {
isFocused = true;
widget.focus();
}
}
}
}
return isValid;
}
export function updateOptions(options: { [optionName: string]: any }, optionsMap: { [optionName: string]: IOptionElement }): void {
for (var optionName in optionsMap) {
var optionElement: IOptionElement = optionsMap[optionName];
if (optionElement.optionWidget.value !== options[optionName]) {
if (!optionElement.optionWidget.value && options[optionName]) {
delete options[optionName];
}
if (optionElement.optionWidget.value) {
if (optionElement.option.valueType === ServiceOptionType.boolean) {
options[optionName] = (optionElement.optionWidget.value === this.trueInputValue) ? true : false;
} else {
options[optionName] = optionElement.optionWidget.value;
}
}
optionElement.optionValue = options[optionName];
}
}
}
export let trueInputValue: string = 'True';
export let falseInputValue: string = 'False';
export function findElement(container: Builder, className: string): HTMLElement {
var elementBuilder: Builder = container;
while (elementBuilder.getHTMLElement()) {
var htmlElement = elementBuilder.getHTMLElement();
if (htmlElement.className === className) {
break;
}
elementBuilder = elementBuilder.child(0);
}
return elementBuilder.getHTMLElement();
}
export function groupOptionsByCategory(options: data.ServiceOption[]): { [category: string]: data.ServiceOption[] } {
var connectionOptionsMap: { [category: string]: data.ServiceOption[] } = {};
options.forEach(option => {
var groupName = option.groupName;
if (groupName === null || groupName === undefined) {
groupName = 'General';
}
if (!!connectionOptionsMap[groupName]) {
connectionOptionsMap[groupName].push(option);
} else {
connectionOptionsMap[groupName] = [option];
}
});
return connectionOptionsMap;
}

View File

@@ -0,0 +1,58 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.tabbedPanel {
border-top-color: rgba(128, 128, 128, 0.35);
border-top-width: 1;
border-top-style: solid;
box-sizing: border-box;
}
.tabbedPanel .composite.title {
display: flex;
}
.tabbedPanel .tabList {
display: flex;
margin: 0 auto;
padding: 0;
justify-content: flex-start;
flex-flow: row;
line-height: 35px;
}
.tabbedPanel .tabList > .tab {
cursor: pointer;
}
.tabbedPanel .tabList > .tab > .tabLabel {
text-transform: uppercase;
margin-left: 16px;
margin-right: 16px;
font-size: 11px;
padding-bottom: 4px;
}
.tabbedPanel .composite.title .title-actions .action-label {
display: block;
height: 35px;
line-height: 35px;
min-width: 28px;
background-size: 16px;
background-position: center center;
background-repeat: no-repeat;
}
.composite.title .title-actions {
flex: 1;
}
.tab > .tabLabel.active {
border-bottom: 1px solid;
}
.composite.title ~ tab.fullsize > :first-child {
height: calc(100% - 38px);
}

View 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.
*--------------------------------------------------------------------------------------------*/
import { Component, ContentChildren, QueryList, AfterContentInit, Inject, forwardRef, NgZone, OnInit, Input } from '@angular/core';
import { TabComponent } from './tab.component';
import './panelStyles';
import * as types from 'vs/base/common/types';
import { mixin } from 'vs/base/common/objects';
export interface IPanelOptions {
/**
* Whether or not to show the tabs if there is only one tab present
*/
showTabsWhenOne?: boolean;
}
const defaultOptions: IPanelOptions = {
showTabsWhenOne: true
};
@Component({
selector: 'panel',
template: `
<div class="tabbedPanel fullsize">
<div *ngIf="!options.showTabsWhenOne ? _tabs.length !== 1 : true" class="composite title">
<div class="tabList">
<div *ngFor="let tab of _tabs" class="tab" (click)="selectTab(tab)">
<a class="tabLabel" [class.active]="tab.active">
{{tab.title}}
</a>
</div>
</div>
<div class="title-actions">
</div>
</div>
<ng-content class="fullsize"></ng-content>
</div>
`
})
export class PanelComponent implements AfterContentInit, OnInit {
@Input() public options: IPanelOptions;
@ContentChildren(TabComponent) private _tabs: QueryList<TabComponent>;
private _activeTab: TabComponent;
constructor( @Inject(forwardRef(() => NgZone)) private _zone: NgZone) { }
ngOnInit(): void {
this.options = mixin(this.options || {}, defaultOptions, false);
}
ngAfterContentInit(): void {
if (this._tabs && this._tabs.length > 0) {
this._activeTab = this._tabs.first;
this._activeTab.active = true;
}
}
/**
* Select a tab based on index (unrecommended)
* @param index index of tab in the html
*/
selectTab(index: number)
/**
* Select a tab based on the identifier that was passed into the tab
* @param identifier specified identifer of the tab
*/
selectTab(identifier: string);
/**
* Select a tab directly if you have access to the object
* @param tab tab to navigate to
*/
selectTab(tab: TabComponent);
selectTab(input: TabComponent | number | string) {
if (this._tabs && this._tabs.length > 0) {
let tab: TabComponent;
if (input instanceof TabComponent) {
tab = input;
} else if (types.isNumber(input)) {
tab = this._tabs[input];
} else if (types.isString(input)) {
tab = this._tabs.find(i => i.identifier === input);
}
this._zone.run(() => {
if (this._activeTab) {
this._activeTab.active = false;
}
this._activeTab = tab;
this._activeTab.active = true;
});
}
}
}

View File

@@ -0,0 +1,16 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TabComponent } from './tab.component';
import { PanelComponent } from './panel.component';
@NgModule({
imports: [CommonModule],
exports: [TabComponent, PanelComponent],
declarations: [TabComponent, PanelComponent]
})
export class PanelModule { }

View File

@@ -0,0 +1,166 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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 { IActionOptions, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import './panelStyles';
export interface IPanelStyles {
}
export interface IPanelView {
render(container: HTMLElement): void;
layout(dimension: Dimension): void;
}
export interface IPanelTab {
title: string;
identifier: string;
view: IPanelView;
}
interface IInternalPanelTab extends IPanelTab {
header: HTMLElement;
label: HTMLElement;
}
export type PanelTabIdentifier = string;
export class TabbedPanel implements IThemable {
private _tabMap = new Map<PanelTabIdentifier, IInternalPanelTab>();
private _shownTab: PanelTabIdentifier;
public readonly headersize = 35;
private _header: HTMLElement;
private _tabList: HTMLElement;
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';
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);
}
public pushTab(tab: IPanelTab): PanelTabIdentifier {
let internalTab = objects.clone(tab) as IInternalPanelTab;
this._tabMap.set(tab.identifier, internalTab);
this._createTab(internalTab);
if (!this._shownTab) {
this.showTab(tab.identifier);
}
return tab.identifier as PanelTabIdentifier;
}
public pushAction(arg: IAction | IAction[], options: IActionOptions = {}): void {
this._actionbar.push(arg, options);
}
public set actionBarContext(context: any) {
this._actionbar.context = context;
}
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);
tab.header = tabElement;
tab.label = tabLabel;
}
public showTab(id: PanelTabIdentifier): void {
if (this._shownTab && this._shownTab === id) {
return;
}
if (this._shownTab) {
this._tabMap.get(this._shownTab).label.classList.remove('active');
}
this._shownTab = id;
new Builder(this._body).empty();
let tab = this._tabMap.get(this._shownTab);
tab.label.classList.add('active');
tab.view.render(this._body);
this._onTabChange.fire(id);
if (this._currentDimensions) {
this._layoutCurrentTab(new Dimension(this._currentDimensions.width, this._currentDimensions.height - this.headersize));
}
}
public removeTab(tab: PanelTabIdentifier) {
this._tabMap.get(tab).header.remove();
this._tabMap.delete(tab);
}
public style(styles: IPanelStyles): void {
}
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._layoutCurrentTab(new Dimension(dimension.width, dimension.height - this.headersize));
}
private _layoutCurrentTab(dimension: Dimension): void {
if (this._shownTab) {
this._tabMap.get(this._shownTab).view.layout(dimension);
}
}
public focus(): void {
}
public set collapsed(val: boolean) {
if (val === this._collapsed) {
return;
}
this._collapsed = val === false ? false : true;
if (this.collapsed) {
this._body.remove();
} else {
this._parent.appendChild(this._body);
}
}
public get collapsed(): boolean {
return this._collapsed;
}
}

View File

@@ -0,0 +1,70 @@
/*---------------------------------------------------------------------------------------------
* 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!./media/panel';
import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER } from 'vs/workbench/common/theme';
import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
// Title Active
const titleActive = theme.getColor(PANEL_ACTIVE_TITLE_FOREGROUND);
const titleActiveBorder = theme.getColor(PANEL_ACTIVE_TITLE_BORDER);
if (titleActive || titleActiveBorder) {
collector.addRule(`
.tabbedPanel > .title > .tabList > .tab:hover .tabLabel,
.tabbedPanel > .title > .tabList > .tab .tabLabel.active {
color: ${titleActive};
border-bottom-color: ${titleActiveBorder};
}
`);
}
// Title Inactive
const titleInactive = theme.getColor(PANEL_INACTIVE_TITLE_FOREGROUND);
if (titleInactive) {
collector.addRule(`
.tabbedPanel > .title > .tabList > .tab .tabLabel {
color: ${titleInactive};
}
`);
}
// Title focus
const focusBorderColor = theme.getColor(focusBorder);
if (focusBorderColor) {
collector.addRule(`
.tabbedPanel > .title > .tabList > .tab .tabLabel:focus {
color: ${titleActive};
border-bottom-color: ${focusBorderColor} !important;
border-bottom: 1px solid;
outline: none;
}
`);
}
// Styling with Outline color (e.g. high contrast theme)
const outline = theme.getColor(activeContrastBorder);
if (outline) {
const outline = theme.getColor(activeContrastBorder);
collector.addRule(`
.tabbedPanel > .title > .tabList > .tab .tabLabel.active,
.tabbedPanel > .title > .tabList > .tab .tabLabel:hover {
outline-color: ${outline};
outline-width: 1px;
outline-style: solid;
border-bottom: none;
padding-bottom: 0;
outline-offset: 3px;
}
.tabbedPanel > .title > .tabList > .tab .tabLabel:hover:not(.active) {
outline-style: dashed;
}
`);
}
});

View File

@@ -0,0 +1,35 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Component, Input, ContentChild } from '@angular/core';
export abstract class TabChild {
public abstract layout(): void;
}
@Component({
selector: 'tab',
template: `
<div *ngIf="active" class="fullsize">
<ng-content class="body fullsize"></ng-content>
</div>
`
})
export class TabComponent {
@ContentChild(TabChild) private _child: TabChild;
@Input() public title: string;
public _active = false;
@Input() public identifier: string;
public set active(val: boolean) {
this._active = val;
if (this.active && this._child) {
this._child.layout();
}
}
public get active(): boolean {
return this._active;
}
}

View File

@@ -0,0 +1,225 @@
/*---------------------------------------------------------------------------------------------
* 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 { SelectBox as vsSelectBox, ISelectBoxStyles as vsISelectBoxStyles } from 'vs/base/browser/ui/selectBox/selectBox';
import { Color } from 'vs/base/common/color';
import { IContextViewProvider, AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import * as dom from 'vs/base/browser/dom';
import { RenderOptions, renderFormattedText, renderText } from 'vs/base/browser/htmlContentRenderer';
import { IMessage, MessageType, defaultOpts } from 'vs/base/browser/ui/inputbox/inputBox';
import aria = require('vs/base/browser/ui/aria/aria');
import nls = require('vs/nls');
const $ = dom.$;
export interface ISelectBoxStyles extends vsISelectBoxStyles {
disabledSelectBackground?: Color,
disabledSelectForeground?: Color,
inputValidationInfoBorder?: Color;
inputValidationInfoBackground?: Color;
inputValidationWarningBorder?: Color;
inputValidationWarningBackground?: Color;
inputValidationErrorBorder?: Color;
inputValidationErrorBackground?: Color;
}
export class SelectBox extends vsSelectBox {
private _optionsDictionary;
private _dialogOptions: string[];
private _selectedOption: string;
private enabledSelectBackground: Color;
private enabledSelectForeground: Color;
private enabledSelectBorder: Color;
private disabledSelectBackground: Color;
private disabledSelectForeground: Color;
private disabledSelectBorder: Color;
private contextViewProvider: IContextViewProvider;
private message: IMessage;
private inputValidationInfoBorder: Color;
private inputValidationInfoBackground: Color;
private inputValidationWarningBorder: Color;
private inputValidationWarningBackground: Color;
private inputValidationErrorBorder: Color;
private inputValidationErrorBackground: Color;
private element: HTMLElement;
constructor(options: string[], selectedOption: string, container?: HTMLElement, contextViewProvider?: IContextViewProvider) {
super(options, 0);
this._optionsDictionary = new Array();
for (var i = 0; i < options.length; i++) {
this._optionsDictionary[options[i]] = i;
}
super.select(this._optionsDictionary[selectedOption]);
this._selectedOption = selectedOption;
this._dialogOptions = options;
this._register(this.onDidSelect(newInput => {
this._selectedOption = newInput.selected;
}));
this.enabledSelectBackground = this.selectBackground;
this.enabledSelectForeground = this.selectForeground;
this.enabledSelectBorder = this.selectBorder;
this.disabledSelectBackground = Color.transparent;
this.disabledSelectForeground = null;
this.disabledSelectBorder = null;
this.contextViewProvider = contextViewProvider;
if (container) {
this.element = dom.append(container, $('.monaco-selectbox.idle'));
}
}
public style(styles: ISelectBoxStyles): void {
super.style(styles);
this.enabledSelectBackground = this.selectBackground;
this.enabledSelectForeground = this.selectForeground;
this.enabledSelectBorder = this.selectBorder;
this.disabledSelectBackground = styles.disabledSelectBackground;
this.disabledSelectForeground = styles.disabledSelectForeground;
this.inputValidationInfoBorder = styles.inputValidationInfoBorder;
this.inputValidationInfoBackground = styles.inputValidationInfoBackground;
this.inputValidationWarningBorder = styles.inputValidationWarningBorder;
this.inputValidationWarningBackground = styles.inputValidationWarningBackground;
this.inputValidationErrorBorder = styles.inputValidationErrorBorder;
this.inputValidationErrorBackground = styles.inputValidationErrorBackground;
}
public selectWithOptionName(optionName: string): void {
if (this._optionsDictionary[optionName]) {
this.select(this._optionsDictionary[optionName]);
} else {
this.select(0);
}
}
public select(index: number): void {
super.select(index);
if (this._dialogOptions !== undefined) {
this._selectedOption = this._dialogOptions[index];
}
}
public setOptions(options: string[], selected?: number, disabled?: number): void {
this._optionsDictionary = [];
for (var i = 0; i < options.length; i++) {
this._optionsDictionary[options[i]] = i;
}
this._dialogOptions = options;
super.setOptions(options, selected, disabled);
}
public get value(): string {
return this._selectedOption;
}
public enable(): void {
this.selectElement.disabled = false;
this.selectBackground = this.enabledSelectBackground;
this.selectForeground = this.enabledSelectForeground;
this.selectBorder = this.enabledSelectBorder;
this.applyStyles();
}
public disable(): void {
this.selectElement.disabled = true;
this.selectBackground = this.disabledSelectBackground;
this.selectForeground = this.disabledSelectForeground;
this.selectBorder = this.disabledSelectBorder;
this.applyStyles();
}
public showMessage(message: IMessage): void {
this.message = message;
dom.removeClass(this.element, 'idle');
dom.removeClass(this.element, 'info');
dom.removeClass(this.element, 'warning');
dom.removeClass(this.element, 'error');
dom.addClass(this.element, this.classForType(message.type));
// ARIA Support
let alertText: string;
if (message.type === MessageType.ERROR) {
alertText = nls.localize('alertErrorMessage', "Error: {0}", message.content);
} else if (message.type === MessageType.WARNING) {
alertText = nls.localize('alertWarningMessage', "Warning: {0}", message.content);
} else {
alertText = nls.localize('alertInfoMessage', "Info: {0}", message.content);
}
aria.alert(alertText);
this._showMessage();
}
public _showMessage(): void {
if (this.message && this.contextViewProvider && this.element) {
let div: HTMLElement;
let layout = () => div.style.width = dom.getTotalWidth(this.selectElement) + 'px';
this.contextViewProvider.showContextView({
getAnchor: () => this.selectElement,
anchorAlignment: AnchorAlignment.RIGHT,
render: (container: HTMLElement) => {
div = dom.append(container, $('.monaco-inputbox-container'));
layout();
const renderOptions: RenderOptions = {
inline: true,
className: 'monaco-inputbox-message'
};
let spanElement: HTMLElement = (this.message.formatContent
? renderFormattedText(this.message.content, renderOptions)
: renderText(this.message.content, renderOptions)) as any;
dom.addClass(spanElement, this.classForType(this.message.type));
const styles = this.stylesForType(this.message.type);
spanElement.style.backgroundColor = styles.background ? styles.background.toString() : null;
spanElement.style.border = styles.border ? `1px solid ${styles.border}` : null;
dom.append(div, spanElement);
return null;
},
layout: layout
});
}
}
public hideMessage(): void {
this.message = null;
dom.removeClass(this.element, 'info');
dom.removeClass(this.element, 'warning');
dom.removeClass(this.element, 'error');
dom.addClass(this.element, 'idle');
this._hideMessage();
this.applyStyles();
}
private _hideMessage(): void {
if (this.contextViewProvider) {
this.contextViewProvider.hideContextView();
}
}
private classForType(type: MessageType): string {
switch (type) {
case MessageType.INFO: return 'info';
case MessageType.WARNING: return 'warning';
default: return 'error';
}
}
private stylesForType(type: MessageType): { border: Color; background: Color } {
switch (type) {
case MessageType.INFO: return { border: this.inputValidationInfoBorder, background: this.inputValidationInfoBackground };
case MessageType.WARNING: return { border: this.inputValidationWarningBorder, background: this.inputValidationWarningBackground };
default: return { border: this.inputValidationErrorBorder, background: this.inputValidationErrorBackground };
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 833 B

View File

@@ -0,0 +1,34 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-table * {
box-sizing: border-box;
}
.monaco-table {
width: 100%;
height: 100%;
}
.monaco-table > div {
width: 100%;
height: 100%;
}
.slick-sort-indicator-asc {
background: url('sort-asc.gif');
}
.slick-sort-indicator-desc {
background: url('sort-desc.gif');
}
.slick-sort-indicator {
display: inline-block;
width: 8px;
height: 5px;
margin-left: 4px;
margin-top: 6px;
}

View File

@@ -0,0 +1,137 @@
// Adapted from https://github.com/naresh-n/slickgrid-column-data-autosize/blob/master/src/slick.autocolumnsize.js
import { mixin, clone } from 'vs/base/common/objects';
export interface IAutoColumnSizeOptions extends Slick.PluginOptions {
maxWidth?: number;
}
const defaultOptions: IAutoColumnSizeOptions = {
maxWidth: 200
};
export class AutoColumnSize<T> implements Slick.Plugin<T> {
private _grid: Slick.Grid<T>;
private _$container: JQuery;
private _context: CanvasRenderingContext2D;
private _options: IAutoColumnSizeOptions;
constructor(options: IAutoColumnSizeOptions) {
this._options = mixin(options, defaultOptions, false);
}
public init(grid: Slick.Grid<T>) {
this._grid = grid;
this._$container = $(this._grid.getContainerNode());
this._$container.on('dblclick.autosize', '.slick-resizable-handle', e => this.reSizeColumn(e));
this._context = document.createElement('canvas').getContext('2d');
}
public destroy() {
this._$container.off();
}
private reSizeColumn(e: Event) {
let headerEl = $(e.currentTarget).closest('.slick-header-column');
let columnDef = headerEl.data('column');
if (!columnDef || !columnDef.resizable) {
return;
}
e.preventDefault();
e.stopPropagation();
let headerWidth = this.getElementWidth(headerEl[0]);
let colIndex = this._grid.getColumnIndex(columnDef.id);
let origCols = this._grid.getColumns();
let allColumns = clone(origCols);
allColumns.forEach((col, index) => {
col.formatter = origCols[index].formatter;
col.asyncPostRender = origCols[index].asyncPostRender;
});
let column = allColumns[colIndex];
let autoSizeWidth = Math.max(headerWidth, this.getMaxColumnTextWidth(columnDef, colIndex)) + 1;
if (autoSizeWidth !== column.width) {
allColumns[colIndex].width = autoSizeWidth;
this._grid.setColumns(allColumns);
this._grid.onColumnsResized.notify();
}
}
private getMaxColumnTextWidth(columnDef, colIndex: number): number {
let texts = [];
let rowEl = this.createRow(columnDef);
let data = this._grid.getData();
let viewPort = this._grid.getViewport();
let start = Math.max(0, viewPort.top + 1);
let end = Math.min(data.getLength(), viewPort.bottom);
for (let i = start; i < end; i++) {
texts.push(data.getItem(i)[columnDef.field]);
}
let template = this.getMaxTextTemplate(texts, columnDef, colIndex, data, rowEl);
let width = this.getTemplateWidth(rowEl, template);
this.deleteRow(rowEl);
return width;
}
private getTemplateWidth(rowEl: JQuery, template: JQuery | HTMLElement): number {
let cell = $(rowEl.find('.slick-cell'));
cell.append(template);
$(cell).find('*').css('position', 'relative');
return cell.outerWidth() + 1;
}
private getMaxTextTemplate(texts: string[], columnDef, colIndex: number, data, rowEl: JQuery): JQuery | HTMLElement {
let max = 0,
maxTemplate = null;
let formatFun = columnDef.formatter;
texts.forEach((text, index) => {
let template;
if (formatFun) {
template = $('<span>' + formatFun(index, colIndex, text, columnDef, data[index]) + '</span>');
text = template.text() || text;
}
let length = text ? this.getElementWidthUsingCanvas(rowEl, text) : 0;
if (length > max) {
max = length;
maxTemplate = template || text;
}
});
return maxTemplate;
}
private createRow(columnDef): JQuery {
let rowEl = $('<div class="slick-row"><div class="slick-cell"></div></div>');
rowEl.find('.slick-cell').css({
'visibility': 'hidden',
'text-overflow': 'initial',
'white-space': 'nowrap'
});
let gridCanvas = this._$container.find('.grid-canvas');
$(gridCanvas).append(rowEl);
return rowEl;
}
private deleteRow(rowEl: JQuery) {
$(rowEl).remove();
}
private getElementWidth(element: HTMLElement): number {
let width, clone = element.cloneNode(true) as HTMLElement;
clone.style.cssText = 'position: absolute; visibility: hidden;right: auto;text-overflow: initial;white-space: nowrap;';
element.parentNode.insertBefore(clone, element);
width = clone.offsetWidth;
clone.parentNode.removeChild(clone);
return width;
}
private getElementWidthUsingCanvas(element: JQuery, text: string): number {
this._context.font = element.css('font-size') + ' ' + element.css('font-family');
let metrics = this._context.measureText(text);
return metrics.width;
}
}

View File

@@ -0,0 +1,189 @@
// Adopted and converted to typescript from https://github.com/6pac/SlickGrid/blob/master/plugins/slick.checkboxselectcolumn.js
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';
export interface ICheckboxSelectColumnOptions extends Slick.PluginOptions, ICheckboxStyles {
columnId?: string;
cssClass?: string;
toolTip?: string;
width?: number;
title?: string;
}
const defaultOptions: ICheckboxSelectColumnOptions = {
columnId: '_checkbox_selector',
cssClass: null,
toolTip: nls.localize('selectDeselectAll', 'Select/Deselect All'),
width: 30,
inputActiveOptionBorder: Color.fromHex('#007ACC')
};
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>`;
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) {
this._options = mixin(options, defaultOptions, false);
this.applyStyles();
}
public init(grid: Slick.Grid<T>): void {
this._grid = grid;
this._handler
.subscribe(this._grid.onSelectedRowsChanged, (e, args) => this.handleSelectedRowsChanged(e, args))
.subscribe(this._grid.onClick, (e, args) => this.handleClick(e, args))
.subscribe(this._grid.onHeaderClick, (e, args) => this.handleHeaderClick(e, args))
.subscribe(this._grid.onKeyDown, (e, args) => this.handleKeyDown(e, args));
}
public destroy(): void {
this._handler.unsubscribeAll();
}
private handleSelectedRowsChanged(e: Event, args: Slick.OnSelectedRowsChangedEventArgs<T>): void {
let selectedRows = this._grid.getSelectedRows();
let lookup = {}, row, i;
for (i = 0; i < selectedRows.length; i++) {
row = selectedRows[i];
lookup[row] = true;
if (lookup[row] !== this._selectedRowsLookup[row]) {
this._grid.invalidateRow(row);
delete this._selectedRowsLookup[row];
}
}
for (i in this._selectedRowsLookup) {
this._grid.invalidateRow(i);
}
this._selectedRowsLookup = lookup;
this._grid.render();
if (!this._options.title) {
if (selectedRows.length && selectedRows.length === this._grid.getDataLength()) {
this._grid.updateColumnHeader(this._options.columnId,
strings.format(this._checkboxTemplate, 'true', 'checked'),
this._options.toolTip);
} else {
this._grid.updateColumnHeader(this._options.columnId,
strings.format(this._checkboxTemplate, 'true', 'unchecked'),
this._options.toolTip);
}
}
}
private handleKeyDown(e: KeyboardEvent, args: Slick.OnKeyDownEventArgs<T>): void {
if (e.which === 32) {
if (this._grid.getColumns()[args.cell].id === this._options.columnId) {
// if editing, try to commit
if (!this._grid.getEditorLock().isActive() || this._grid.getEditorLock().commitCurrentEdit()) {
this.toggleRowSelection(args.row);
}
e.preventDefault();
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 editing, try to commit
if (this._grid.getEditorLock().isActive() && !this._grid.getEditorLock().commitCurrentEdit()) {
e.preventDefault();
e.stopImmediatePropagation();
return;
}
this.toggleRowSelection(args.row);
e.stopPropagation();
e.stopImmediatePropagation();
}
}
private toggleRowSelection(row: number): void {
if (this._selectedRowsLookup[row]) {
this._grid.setSelectedRows(this._grid.getSelectedRows().filter(n => n !== row));
} else {
this._grid.setSelectedRows(this._grid.getSelectedRows().concat(row));
}
}
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 editing, try to commit
if (this._grid.getEditorLock().isActive() && !this._grid.getEditorLock().commitCurrentEdit()) {
e.preventDefault();
e.stopImmediatePropagation();
return;
}
if ($(e.target).is('.unchecked')) {
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'),
this._options.toolTip);
} else {
this._grid.setSelectedRows([]);
this._grid.updateColumnHeader(this._options.columnId,
strings.format(this._checkboxTemplate, 'true', 'unchecked'), this._options.toolTip);
e.stopPropagation();
e.stopImmediatePropagation();
}
}
}
public getColumnDefinition(): Slick.Column<T> {
return {
id: this._options.columnId,
name: this._options.title || strings.format(this._checkboxTemplate, 'true', 'unchecked'),
toolTip: this._options.toolTip,
field: 'sel',
width: this._options.width,
resizable: false,
sortable: false,
cssClass: this._options.cssClass,
formatter: (r, c, v, cd, dc) => this.checkboxSelectionFormatter(r, c, v, cd, dc)
};
}
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');
}
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();
}
}
}

View File

@@ -0,0 +1,364 @@
// Drag select selection model gist taken from https://gist.github.com/skoon/5312536
// heavily modified
import { clone } from 'vs/base/common/objects';
export class DragCellSelectionModel<T> implements Slick.SelectionModel<T, Array<Slick.Range>> {
private readonly keyColResizeIncr = 5;
private _grid: Slick.Grid<T>;
private _ranges: Array<Slick.Range> = [];
private _dragging = false;
private _handler = new Slick.EventHandler();
public onSelectedRangesChanged = new Slick.Event<Slick.Range[]>();
public init(grid: Slick.Grid<T>): void {
this._grid = grid;
this._handler.subscribe(this._grid.onActiveCellChanged, (e: Event, data: Slick.OnActiveCellChangedEventArgs<T>) => this.handleActiveCellChange(e, data));
this._handler.subscribe(this._grid.onKeyDown, (e: JQueryInputEventObject) => this.handleKeyDown(e));
this._handler.subscribe(this._grid.onClick, (e: MouseEvent) => this.handleClick(e));
this._handler.subscribe(this._grid.onDrag, (e: MouseEvent) => this.handleDrag(e));
this._handler.subscribe(this._grid.onDragInit, (e: MouseEvent) => this.handleDragInit(e));
this._handler.subscribe(this._grid.onDragStart, (e: MouseEvent) => this.handleDragStart(e));
this._handler.subscribe(this._grid.onDragEnd, (e: MouseEvent) => this.handleDragEnd(e));
this._handler.subscribe(this._grid.onHeaderClick, (e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) => this.handleHeaderClick(e, args));
}
public destroy(): void {
this._handler.unsubscribeAll();
}
private rangesToRows(ranges: Array<Slick.Range>): Array<number> {
let rows = [];
for (let i = 0; i < ranges.length; i++) {
for (let j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
rows.push(j);
}
}
return rows;
}
private rowsToRanges(rows: Array<number>): Array<Slick.Range> {
let ranges = [];
let lastCell = this._grid.getColumns().length - 1;
for (let i = 0; i < rows.length; i++) {
ranges.push(new Slick.Range(rows[i], 0, rows[i], lastCell));
}
return ranges;
}
public getSelectedRows(): Array<number> {
return this.rangesToRows(this._ranges);
}
public setSelectedRows(rows: Array<number>) {
this.setSelectedRanges(this.rowsToRanges(rows));
}
public setSelectedRanges(ranges: Array<Slick.Range>) {
this._ranges = ranges;
this.onSelectedRangesChanged.notify(this._ranges);
}
public getSelectedRanges(): Array<Slick.Range> {
return this._ranges;
}
private handleActiveCellChange(e: Event, data: Slick.OnActiveCellChangedEventArgs<T>) { }
private isNavigationKey(e: BaseJQueryEventObject) {
// Nave keys (home, end, arrows) are all in sequential order so use a
switch (e.which) {
case $.ui.keyCode.HOME:
case $.ui.keyCode.END:
case $.ui.keyCode.LEFT:
case $.ui.keyCode.UP:
case $.ui.keyCode.RIGHT:
case $.ui.keyCode.DOWN:
return true;
default:
return false;
}
}
private navigateLeft(e: JQueryInputEventObject, activeCell: Slick.Cell) {
if (activeCell.cell > 1) {
let isHome = e.which === $.ui.keyCode.HOME;
let newActiveCellColumn = isHome ? 1 : activeCell.cell - 1;
// Unsure why but for range, must record 1 index less than expected
let newRangeColumn = newActiveCellColumn - 1;
if (e.shiftKey) {
let last = this._ranges.pop();
// If we are on the rightmost edge of the range and we navigate left,
// we want to deselect the rightmost cell
if (last.fromCell <= newRangeColumn) { last.toCell -= 1; }
let fromRow = Math.min(activeCell.row, last.fromRow);
let fromCell = Math.min(newRangeColumn, last.fromCell);
let toRow = Math.max(activeCell.row, last.toRow);
let toCell = Math.max(newRangeColumn, last.toCell);
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
} else {
this._ranges = [new Slick.Range(activeCell.row, newRangeColumn, activeCell.row, newRangeColumn)];
}
this._grid.setActiveCell(activeCell.row, newActiveCellColumn);
this.setSelectedRanges(this._ranges);
}
}
private navigateRight(e: JQueryInputEventObject, activeCell: Slick.Cell) {
let columnLength = this._grid.getColumns().length;
if (activeCell.cell < columnLength) {
let isEnd = e.which === $.ui.keyCode.END;
let newActiveCellColumn = isEnd ? columnLength : activeCell.cell + 1;
// Unsure why but for range, must record 1 index less than expected
let newRangeColumn = newActiveCellColumn - 1;
if (e.shiftKey) {
let last = this._ranges.pop();
// If we are on the leftmost edge of the range and we navigate right,
// we want to deselect the leftmost cell
if (newRangeColumn <= last.toCell) { last.fromCell += 1; }
let fromRow = Math.min(activeCell.row, last.fromRow);
let fromCell = Math.min(newRangeColumn, last.fromCell);
let toRow = Math.max(activeCell.row, last.toRow);
let toCell = Math.max(newRangeColumn, last.toCell);
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
} else {
this._ranges = [new Slick.Range(activeCell.row, newRangeColumn, activeCell.row, newRangeColumn)];
}
this._grid.setActiveCell(activeCell.row, newActiveCellColumn);
this.setSelectedRanges(this._ranges);
}
}
private handleKeyDown(e: JQueryInputEventObject) {
let activeCell = this._grid.getActiveCell();
if (activeCell) {
// navigation keys
if (this.isNavigationKey(e)) {
e.stopImmediatePropagation();
if (e.ctrlKey || e.metaKey) {
let event = new CustomEvent('gridnav', {
detail: {
which: e.which,
ctrlKey: e.ctrlKey,
metaKey: e.metaKey,
shiftKey: e.shiftKey,
altKey: e.altKey
}
});
window.dispatchEvent(event);
return;
}
// end key
if (e.which === $.ui.keyCode.END) {
this.navigateRight(e, activeCell);
}
// home key
if (e.which === $.ui.keyCode.HOME) {
this.navigateLeft(e, activeCell);
}
// left arrow
if (e.which === $.ui.keyCode.LEFT) {
// column resize
if ((e.ctrlKey || e.metaKey) && e.shiftKey) {
let allColumns = clone(this._grid.getColumns());
allColumns[activeCell.cell - 1].width = allColumns[activeCell.cell - 1].width - this.keyColResizeIncr;
this._grid.setColumnWidths(allColumns);
} else {
this.navigateLeft(e, activeCell);
}
// up arrow
} else if (e.which === $.ui.keyCode.UP && activeCell.row > 0) {
if (e.shiftKey) {
let last = this._ranges.pop();
// If we are on the bottommost edge of the range and we navigate up,
// we want to deselect the bottommost row
let newRangeRow = activeCell.row - 1;
if (last.fromRow <= newRangeRow) { last.toRow -= 1; }
let fromRow = Math.min(activeCell.row - 1, last.fromRow);
let fromCell = Math.min(activeCell.cell - 1, last.fromCell);
let toRow = Math.max(newRangeRow, last.toRow);
let toCell = Math.max(activeCell.cell - 1, last.toCell);
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
} else {
this._ranges = [new Slick.Range(activeCell.row - 1, activeCell.cell - 1, activeCell.row - 1, activeCell.cell - 1)];
}
this._grid.setActiveCell(activeCell.row - 1, activeCell.cell);
this.setSelectedRanges(this._ranges);
// right arrow
} else if (e.which === $.ui.keyCode.RIGHT) {
// column resize
if ((e.ctrlKey || e.metaKey) && e.shiftKey) {
let allColumns = clone(this._grid.getColumns());
allColumns[activeCell.cell - 1].width = allColumns[activeCell.cell - 1].width + this.keyColResizeIncr;
this._grid.setColumnWidths(allColumns);
} else {
this.navigateRight(e, activeCell);
}
// down arrow
} else if (e.which === $.ui.keyCode.DOWN && activeCell.row < this._grid.getDataLength() - 1) {
if (e.shiftKey) {
let last = this._ranges.pop();
// If we are on the topmost edge of the range and we navigate down,
// we want to deselect the topmost row
let newRangeRow = activeCell.row + 1;
if (newRangeRow <= last.toRow) { last.fromRow += 1; }
let fromRow = Math.min(activeCell.row + 1, last.fromRow);
let fromCell = Math.min(activeCell.cell - 1, last.fromCell);
let toRow = Math.max(activeCell.row + 1, last.toRow);
let toCell = Math.max(activeCell.cell - 1, last.toCell);
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
} else {
this._ranges = [new Slick.Range(activeCell.row + 1, activeCell.cell - 1, activeCell.row + 1, activeCell.cell - 1)];
}
this._grid.setActiveCell(activeCell.row + 1, activeCell.cell);
this.setSelectedRanges(this._ranges);
}
}
}
}
private handleHeaderClick(e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) {
let columnIndex = this._grid.getColumnIndex(args.column.id);
if (e.ctrlKey || e.metaKey) {
this._ranges.push(new Slick.Range(0, columnIndex, this._grid.getDataLength() - 1, columnIndex));
this._grid.setActiveCell(0, columnIndex + 1);
} else if (e.shiftKey && this._ranges.length) {
let last = this._ranges.pop().fromCell;
let from = Math.min(columnIndex, last);
let to = Math.max(columnIndex, last);
this._ranges = [];
for (let i = from; i <= to; i++) {
if (i !== last) {
this._ranges.push(new Slick.Range(0, i, this._grid.getDataLength() - 1, i));
}
}
this._ranges.push(new Slick.Range(0, last, this._grid.getDataLength() - 1, last));
} else {
this._ranges = [new Slick.Range(0, columnIndex, this._grid.getDataLength() - 1, columnIndex)];
this._grid.resetActiveCell();
}
this.setSelectedRanges(this._ranges);
e.stopImmediatePropagation();
return true;
}
private handleClick(e: MouseEvent) {
let cell = this._grid.getCellFromEvent(e);
if (!cell || !this._grid.canCellBeActive(cell.row, cell.cell)) {
return false;
}
if (!e.ctrlKey && !e.shiftKey && !e.metaKey) {
if (cell.cell !== 0) {
this._ranges = [new Slick.Range(cell.row, cell.cell - 1, cell.row, cell.cell - 1)];
this.setSelectedRanges(this._ranges);
this._grid.setActiveCell(cell.row, cell.cell);
return true;
} else {
this._ranges = [new Slick.Range(cell.row, 0, cell.row, this._grid.getColumns().length - 1)];
this.setSelectedRanges(this._ranges);
this._grid.setActiveCell(cell.row, 1);
return true;
}
}
else if (this._grid.getOptions().multiSelect) {
if (e.ctrlKey || e.metaKey) {
if (cell.cell === 0) {
this._ranges.push(new Slick.Range(cell.row, 0, cell.row, this._grid.getColumns().length - 1));
this._grid.setActiveCell(cell.row, 1);
} else {
this._ranges.push(new Slick.Range(cell.row, cell.cell - 1, cell.row, cell.cell - 1));
this._grid.setActiveCell(cell.row, cell.cell);
}
} else if (this._ranges.length && e.shiftKey) {
let last = this._ranges.pop();
if (cell.cell === 0) {
let fromRow = Math.min(cell.row, last.fromRow);
let toRow = Math.max(cell.row, last.fromRow);
this._ranges = [new Slick.Range(fromRow, 0, toRow, this._grid.getColumns().length - 1)];
} else {
let fromRow = Math.min(cell.row, last.fromRow);
let fromCell = Math.min(cell.cell - 1, last.fromCell);
let toRow = Math.max(cell.row, last.toRow);
let toCell = Math.max(cell.cell - 1, last.toCell);
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
}
}
}
this.setSelectedRanges(this._ranges);
return true;
}
private handleDragInit(e: MouseEvent) {
e.stopImmediatePropagation();
}
private handleDragStart(e: MouseEvent) {
let cell = this._grid.getCellFromEvent(e);
e.stopImmediatePropagation();
this._dragging = true;
if (e.ctrlKey || e.metaKey) {
this._ranges.push(new Slick.Range(cell.row, cell.cell));
this._grid.setActiveCell(cell.row, cell.cell);
} else if (this._ranges.length && e.shiftKey) {
let last = this._ranges.pop();
let fromRow = Math.min(cell.row, last.fromRow);
let fromCell = Math.min(cell.cell - 1, last.fromCell);
let toRow = Math.max(cell.row, last.toRow);
let toCell = Math.max(cell.cell - 1, last.toCell);
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
} else {
this._ranges = [new Slick.Range(cell.row, cell.cell)];
this._grid.setActiveCell(cell.row, cell.cell);
}
this.setSelectedRanges(this._ranges);
}
private handleDrag(e: MouseEvent) {
if (this._dragging) {
let cell = this._grid.getCellFromEvent(e);
let activeCell = this._grid.getActiveCell();
if (!cell || !this._grid.canCellBeActive(cell.row, cell.cell)) {
return false;
}
this._ranges.pop();
if (activeCell.cell === 0) {
let lastCell = this._grid.getColumns().length - 1;
let firstRow = Math.min(cell.row, activeCell.row);
let lastRow = Math.max(cell.row, activeCell.row);
this._ranges.push(new Slick.Range(firstRow, 0, lastRow, lastCell));
} else {
let firstRow = Math.min(cell.row, activeCell.row);
let lastRow = Math.max(cell.row, activeCell.row);
let firstColumn = Math.min(cell.cell - 1, activeCell.cell - 1);
let lastColumn = Math.max(cell.cell - 1, activeCell.cell - 1);
this._ranges.push(new Slick.Range(firstRow, firstColumn, lastRow, lastColumn));
}
this.setSelectedRanges(this._ranges);
return true;
}
return false;
}
private handleDragEnd(e: MouseEvent) {
this._dragging = false;
}
}

View File

@@ -0,0 +1,164 @@
// Adopted and converted to typescript from https://github.com/6pac/SlickGrid/blob/master/plugins/slick.rowselectionmodel.js
// heavily modified
import { mixin } from 'vs/base/common/objects';
const defaultOptions: IRowSelectionModelOptions = {
selectActiveRow: true
};
export interface IRowSelectionModelOptions extends Slick.PluginOptions {
selectActiveRow?: boolean;
}
export class RowSelectionModel<T extends Slick.SlickData> implements Slick.SelectionModel<T, Array<Slick.Range>> {
private _options: IRowSelectionModelOptions;
private _grid: Slick.Grid<T>;
private _handler = new Slick.EventHandler();
private _ranges: Array<Slick.Range> = [];
public onSelectedRangesChanged = new Slick.Event<Array<Slick.Range>>();
constructor(options?: Slick.PluginOptions) {
this._options = mixin(options, defaultOptions, false);
}
public init(grid: Slick.Grid<T>) {
this._grid = grid;
this._handler
.subscribe(this._grid.onActiveCellChanged, (e, data) => this.handleActiveCellChange(e, data))
.subscribe(this._grid.onKeyDown, e => this.handleKeyDown(e))
.subscribe(this._grid.onClick, e => this.handleClick(e));
}
private rangesToRows(ranges: Slick.Range[]): number[] {
let rows = [];
for (let i = 0; i < ranges.length; i++) {
for (let j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
rows.push(j);
}
}
return rows;
}
private rowsToRanges(rows: number[]): Slick.Range[] {
let ranges = [];
let lastCell = this._grid.getColumns().length - 1;
for (let i = 0; i < rows.length; i++) {
ranges.push(new Slick.Range(rows[i], 0, rows[i], lastCell));
}
return ranges;
}
public getSelectedRows(): number[] {
return this.rangesToRows(this._ranges);
}
public setSelectedRows(rows: number[]): void {
this.setSelectedRanges(this.rowsToRanges(rows));
}
public setSelectedRanges(ranges: Slick.Range[]): void {
// simle check for: empty selection didn't change, prevent firing onSelectedRangesChanged
if ((!this._ranges || this._ranges.length === 0) && (!ranges || ranges.length === 0)) { return; }
this._ranges = ranges;
this.onSelectedRangesChanged.notify(this._ranges);
}
public getSelectedRanges(): Slick.Range[] {
return this._ranges;
}
private getRowsRange(from: number, to: number): number[] {
let i, rows = [];
for (i = from; i <= to; i++) {
rows.push(i);
}
for (i = to; i < from; i++) {
rows.push(i);
}
return rows;
}
private handleActiveCellChange(e: Event, data: Slick.OnActiveCellChangedEventArgs<T>): void {
if (this._options.selectActiveRow && data.row !== null) {
this.setSelectedRanges([new Slick.Range(data.row, 0, data.row, this._grid.getColumns().length - 1)]);
}
}
private handleKeyDown(e: KeyboardEvent): void {
let activeRow = this._grid.getActiveCell();
if (activeRow && e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey && (e.which === 38 || e.which === 40)) {
let selectedRows = this.getSelectedRows();
selectedRows.sort((x, y) => x - y);
if (!selectedRows.length) {
selectedRows = [activeRow.row];
}
let top = selectedRows[0];
let bottom = selectedRows[selectedRows.length - 1];
let active;
if (e.which === 40) {
active = activeRow.row < bottom || top === bottom ? ++bottom : ++top;
} else {
active = activeRow.row < bottom ? --bottom : --top;
}
if (active >= 0 && active < this._grid.getDataLength()) {
this._grid.scrollRowIntoView(active, undefined);
let tempRanges = this.rowsToRanges(this.getRowsRange(top, bottom));
this.setSelectedRanges(tempRanges);
}
e.preventDefault();
e.stopPropagation();
}
}
private handleClick(e: KeyboardEvent): boolean {
let cell = this._grid.getCellFromEvent(e);
if (!cell || !this._grid.canCellBeActive(cell.row, cell.cell)) {
return false;
}
if (!this._grid.getOptions().multiSelect || (
!e.ctrlKey && !e.shiftKey && !e.metaKey)) {
return false;
}
let selection = this.rangesToRows(this._ranges);
let idx = $.inArray(cell.row, selection);
if (idx === -1 && (e.ctrlKey || e.metaKey)) {
selection.push(cell.row);
this._grid.setActiveCell(cell.row, cell.cell);
} else if (idx !== -1 && (e.ctrlKey || e.metaKey)) {
selection = selection.filter(o => o !== cell.row);
this._grid.setActiveCell(cell.row, cell.cell);
} else if (selection.length && e.shiftKey) {
let last = selection.pop();
let from = Math.min(cell.row, last);
let to = Math.max(cell.row, last);
selection = [];
for (let i = from; i <= to; i++) {
if (i !== last) {
selection.push(i);
}
}
selection.push(last);
this._grid.setActiveCell(cell.row, cell.cell);
}
let tempRanges = this.rowsToRanges(selection);
this.setSelectedRanges(tempRanges);
e.stopImmediatePropagation();
return true;
}
public destroy() {
this._handler.unsubscribeAll();
}
}

View File

@@ -0,0 +1,273 @@
/*---------------------------------------------------------------------------------------------
* 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!./media/table';
import { TableDataView } from './tableDataView';
import { IThemable } from 'vs/platform/theme/common/styler';
import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
import * as DOM from 'vs/base/browser/dom';
import { Color } from 'vs/base/common/color';
import { mixin } from 'vs/base/common/objects';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Dimension } from 'vs/base/browser/builder';
import { Orientation } from 'vs/base/browser/ui/splitview/splitview';
export interface ITableStyles extends IListStyles {
tableHeaderBackground?: Color;
tableHeaderForeground?: Color;
}
function getDefaultOptions<T>(): Slick.GridOptions<T> {
return <Slick.GridOptions<T>>{
syncColumnCellResize: true,
enableColumnReorder: false
};
}
export class Table<T extends Slick.SlickData> implements IThemable {
private _grid: Slick.Grid<T>;
private _columns: Slick.Column<T>[];
private _data: TableDataView<T>;
private _styleElement: HTMLStyleElement;
private _idPrefix: string;
private _autoscroll: boolean;
private _onRowCountChangeListener: IDisposable;
private _container: HTMLElement;
private _tableContainer: HTMLElement;
constructor(parent: HTMLElement, data?: Array<T> | TableDataView<T>, columns?: Slick.Column<T>[], options?: Slick.GridOptions<T>) {
if (data instanceof TableDataView) {
this._data = data;
} else {
this._data = new TableDataView<T>(data);
}
if (columns) {
this._columns = columns;
} else {
this._columns = new Array<Slick.Column<T>>();
}
let newOptions = mixin(options || {}, getDefaultOptions<T>(), false);
this._container = document.createElement('div');
this._container.className = 'monaco-table';
parent.appendChild(this._container);
this._styleElement = DOM.createStyleSheet(this._container);
this._tableContainer = document.createElement('div');
this._container.appendChild(this._tableContainer);
this._styleElement = DOM.createStyleSheet(this._container);
this._grid = new Slick.Grid<T>(this._tableContainer, this._data, this._columns, newOptions);
this._idPrefix = this._tableContainer.classList[0];
this._onRowCountChangeListener = this._data.onRowCountChange(() => this._handleRowCountChange());
this._grid.onSort.subscribe((e, args) => {
this._data.sort(args);
this._grid.invalidate();
this._grid.render();
});
}
private _handleRowCountChange() {
this._grid.updateRowCount();
this._grid.render();
if (this._autoscroll) {
this._grid.scrollRowIntoView(this._data.getLength() - 1, false);
}
}
set columns(columns: Slick.Column<T>[]) {
this._grid.setColumns(columns);
}
setData(data: Array<T>);
setData(data: TableDataView<T>);
setData(data: Array<T> | TableDataView<T>) {
if (data instanceof TableDataView) {
this._data = data;
} else {
this._data = new TableDataView<T>(data);
}
this._onRowCountChangeListener.dispose();
this._grid.setData(this._data, true);
this._onRowCountChangeListener = this._data.onRowCountChange(() => this._handleRowCountChange());
}
get columns(): Slick.Column<T>[] {
return this._grid.getColumns();
}
setSelectedRows(rows: number[]) {
this._grid.setSelectedRows(rows);
}
getSelectedRows(): number[] {
return this._grid.getSelectedRows();
}
onSelectedRowsChanged(fn: (e: Slick.EventData, data: Slick.OnSelectedRowsChangedEventArgs<T>) => any): IDisposable
onSelectedRowsChanged(fn: (e: DOMEvent, data: Slick.OnSelectedRowsChangedEventArgs<T>) => any): IDisposable
onSelectedRowsChanged(fn: any): IDisposable {
this._grid.onSelectedRowsChanged.subscribe(fn);
return {
dispose() {
this._grid.onSelectedRowsChanged.unsubscribe(fn);
}
};
}
onContextMenu(fn: (e: Slick.EventData, data: Slick.OnContextMenuEventArgs<T>) => any): IDisposable;
onContextMenu(fn: (e: DOMEvent, data: Slick.OnContextMenuEventArgs<T>) => any): IDisposable;
onContextMenu(fn: any): IDisposable {
this._grid.onContextMenu.subscribe(fn);
return {
dispose() {
this._grid.onContextMenu.unsubscribe(fn);
}
};
}
getCellFromEvent(e: DOMEvent): Slick.Cell {
return this._grid.getCellFromEvent(e);
}
setSelectionModel(model: Slick.SelectionModel<T, Array<Slick.Range>>) {
this._grid.setSelectionModel(model);
}
focus(): void {
this._grid.focus();
}
setActiveCell(row: number, cell: number): void {
this._grid.setActiveCell(row, cell);
}
get activeCell(): Slick.Cell {
return this._grid.getActiveCell();
}
registerPlugin(plugin: Slick.Plugin<T>): void {
this._grid.registerPlugin(plugin);
}
/**
* This function needs to be called if the table is drawn off dom.
*/
resizeCanvas() {
this._grid.resizeCanvas();
}
layout(dimension: Dimension): void
layout(size: number, orientation: Orientation): void
layout(sizing: number | Dimension, orientation?: Orientation): void {
if (sizing instanceof Dimension) {
this._container.style.width = sizing.width + 'px';
this._container.style.height = sizing.height + 'px';
this._tableContainer.style.width = sizing.width + 'px';
this._tableContainer.style.height = sizing.height + 'px';
} else {
if (orientation === Orientation.HORIZONTAL) {
this._container.style.width = '100%';
this._container.style.height = sizing + 'px';
this._tableContainer.style.width = '100%';
this._tableContainer.style.height = sizing + 'px';
} else {
this._container.style.width = sizing + 'px';
this._container.style.height = '100%';
this._tableContainer.style.width = sizing + 'px';
this._tableContainer.style.height = '100%';
}
}
this.resizeCanvas();
}
autosizeColumns() {
this._grid.autosizeColumns();
}
set autoScroll(active: boolean) {
this._autoscroll = active;
}
style(styles: ITableStyles): void {
const content: string[] = [];
if (styles.tableHeaderBackground) {
content.push(`.monaco-table .${this._idPrefix} .slick-header .slick-header-column { background-color: ${styles.tableHeaderBackground}; }`);
}
if (styles.tableHeaderForeground) {
content.push(`.monaco-table .${this._idPrefix} .slick-header .slick-header-column { color: ${styles.tableHeaderForeground}; }`);
}
if (styles.listFocusBackground) {
content.push(`.monaco-table .${this._idPrefix} .slick-row .focused { background-color: ${styles.listFocusBackground}; }`);
}
if (styles.listFocusForeground) {
content.push(`.monaco-table .${this._idPrefix} .slick-row .focused { color: ${styles.listFocusForeground}; }`);
}
if (styles.listActiveSelectionBackground) {
content.push(`.monaco-table .${this._idPrefix} .slick-row .selected { background-color: ${styles.listActiveSelectionBackground}; }`);
content.push(`.monaco-table .${this._idPrefix} .slick-row .selected:hover { background-color: ${styles.listActiveSelectionBackground}; }`); // overwrite :hover style in this case!
}
if (styles.listActiveSelectionForeground) {
content.push(`.monaco-table .${this._idPrefix} .slick-row .selected { color: ${styles.listActiveSelectionForeground}; }`);
}
if (styles.listFocusAndSelectionBackground) {
content.push(`.monaco-table .${this._idPrefix} .slick-row .selected.active { background-color: ${styles.listFocusAndSelectionBackground}; }`);
}
if (styles.listFocusAndSelectionForeground) {
content.push(`.monaco-table .${this._idPrefix} .slick-row .selected.active { color: ${styles.listFocusAndSelectionForeground}; }`);
}
/* Commented out andresse 8/17/2017; keeping for reference as we iterate on the table styling */
// if (styles.listInactiveFocusBackground) {
// content.push(`.monaco-table .${this._idPrefix} .slick-row.focused { background-color: ${styles.listInactiveFocusBackground}; }`);
// content.push(`.monaco-table .${this._idPrefix} .slick-row.focused:hover { background-color: ${styles.listInactiveFocusBackground}; }`); // overwrite :hover style in this case!
// }
// if (styles.listInactiveSelectionBackground) {
// content.push(`.monaco-table .${this._idPrefix} .slick-row .selected { background-color: ${styles.listInactiveSelectionBackground}; }`);
// content.push(`.monaco-table .${this._idPrefix} .slick-row .selected:hover { background-color: ${styles.listInactiveSelectionBackground}; }`); // overwrite :hover style in this case!
// }
// if (styles.listInactiveSelectionForeground) {
// content.push(`.monaco-table .${this._idPrefix} .slick-row .selected { color: ${styles.listInactiveSelectionForeground}; }`);
// }
if (styles.listHoverBackground) {
content.push(`.monaco-table .${this._idPrefix} .slick-row:hover { background-color: ${styles.listHoverBackground}; }`);
}
if (styles.listHoverForeground) {
content.push(`.monaco-table .${this._idPrefix} .slick-row:hover { color: ${styles.listHoverForeground}; }`);
}
if (styles.listSelectionOutline) {
content.push(`.monaco-table .${this._idPrefix} .slick-row .selected { outline: 1px dotted ${styles.listSelectionOutline}; outline-offset: -1px; }`);
}
/* Commented out andresse 8/17/2017; keeping for reference as we iterate on the table styling */
// if (styles.listFocusOutline) {
// content.push(`.monaco-table .${this._idPrefix}:focus .slick-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }`);
// }
// if (styles.listInactiveFocusOutline) {
// content.push(`.monaco-table .${this._idPrefix} .slick-row.focused { outline: 1px dotted ${styles.listInactiveFocusOutline}; outline-offset: -1px; }`);
// }
if (styles.listHoverOutline) {
content.push(`.monaco-table .${this._idPrefix} .slick-row:hover { outline: 1px dashed ${styles.listHoverOutline}; outline-offset: -1px; }`);
}
this._styleElement.innerHTML = content.join('\n');
}
}

View File

@@ -0,0 +1,157 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Observable } from 'rxjs/Observable';
import { Observer } from 'rxjs/Observer';
import Event, { Emitter } from 'vs/base/common/event';
import { TPromise } from 'vs/base/common/winjs.base';
import * as types from 'vs/base/common/types';
export interface IFindPosition {
col: number;
row: number;
}
function defaultSort<T>(args: Slick.OnSortEventArgs<T>, data: Array<T>): Array<T> {
let field = args.sortCol.field;
let sign = args.sortAsc ? 1 : -1;
return data.sort((a, b) => (a[field] === b[field] ? 0 : (a[field] > b[field] ? 1 : -1)) * sign);
}
export class TableDataView<T extends Slick.SlickData> implements Slick.DataProvider<T> {
private _data: Array<T>;
private _findArray: Array<IFindPosition>;
private _findObs: Observable<IFindPosition>;
private _findIndex: number;
private _onRowCountChange = new Emitter<number>();
get onRowCountChange(): Event<number> { return this._onRowCountChange.event; }
private _onFindCountChange = new Emitter<number>();
get onFindCountChange(): Event<number> { return this._onFindCountChange.event; }
constructor(
data?: Array<T>,
private _findFn?: (val: T, exp: string) => Array<number>,
private _sortFn?: (args: Slick.OnSortEventArgs<T>, data: Array<T>) => Array<T>
) {
if (data) {
this._data = data;
} else {
this._data = new Array<T>();
}
if (!_sortFn) {
this._sortFn = defaultSort;
}
}
sort(args: Slick.OnSortEventArgs<T>) {
this._data = this._sortFn(args, this._data);
}
getLength(): number {
return this._data.length;
}
getItem(index: number): T {
return this._data[index];
}
push(items: Array<T>);
push(item: T);
push(input: T | Array<T>) {
if (Array.isArray(input)) {
this._data.push(...input);
} else {
this._data.push(input);
}
this._onRowCountChange.fire();
}
clear() {
this._data = new Array<T>();
this._onRowCountChange.fire();
}
find(exp: string): Thenable<IFindPosition> {
if (!this._findFn) {
return TPromise.wrapError(new Error('no find function provided'));
}
this._findArray = new Array<IFindPosition>();
this._findIndex = 0;
this._onFindCountChange.fire(this._findArray.length);
if (exp) {
this._findObs = Observable.create((observer: Observer<IFindPosition>) => {
this._data.forEach((item, i) => {
let result = this._findFn(item, exp);
if (result) {
result.forEach(pos => {
let index = { col: pos, row: i };
this._findArray.push(index);
observer.next(index);
this._onFindCountChange.fire(this._findArray.length);
});
}
});
});
return this._findObs.take(1).toPromise().then(() => {
return this._findArray[this._findIndex];
});
} else {
return TPromise.wrapError(new Error('no expression'));
}
}
clearFind() {
this._findArray = new Array<IFindPosition>();
this._findIndex = 0;
this._findObs = undefined;
this._onFindCountChange.fire(this._findArray.length);
}
findNext(): Thenable<IFindPosition> {
if (this._findArray && this._findArray.length !== 0) {
if (this._findIndex === this._findArray.length - 1) {
this._findIndex = 0;
} else {
++this._findIndex;
}
return TPromise.as(this._findArray[this._findIndex]);
} else {
return TPromise.wrapError(new Error('no search running'));
}
}
findPrevious(): Thenable<IFindPosition> {
if (this._findArray && this._findArray.length !== 0) {
if (this._findIndex === 0) {
this._findIndex = this._findArray.length - 1;
} else {
--this._findIndex;
}
return TPromise.as(this._findArray[this._findIndex]);
} else {
return TPromise.wrapError(new Error('no search running'));
}
}
get currentFindPosition(): Thenable<IFindPosition> {
if (this._findArray && this._findArray.length !== 0) {
return TPromise.as(this._findArray[this._findIndex]);
} else {
return TPromise.wrapError(new Error('no search running'));
}
}
/* 1 indexed */
get findPosition(): number {
return types.isUndefinedOrNull(this._findIndex) ? 0 : this._findIndex + 1;
}
get findCount(): number {
return types.isUndefinedOrNull(this._findArray) ? 0 : this._findArray.length;
}
}

View 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 { Table } from './table';
import { TableDataView } from './tableDataView';
import { View, Orientation, AbstractCollapsibleView, HeaderView, IViewOptions, ICollapsibleViewOptions } from 'vs/base/browser/ui/splitview/splitview';
import { $ } from 'vs/base/browser/builder';
export class TableBasicView<T> extends View {
private _table: Table<T>;
private _container: HTMLElement;
constructor(
viewOpts: IViewOptions,
data?: Array<T> | TableDataView<T>,
columns?: Slick.Column<T>[],
tableOpts?: Slick.GridOptions<T>
) {
super(undefined, viewOpts);
this._container = document.createElement('div');
this._container.className = 'table-view';
this._table = new Table<T>(this._container, data, columns, tableOpts);
}
public get table(): Table<T> {
return this._table;
}
render(container: HTMLElement, orientation: Orientation): void {
container.appendChild(this._container);
}
focus(): void {
this._table.focus();
}
layout(size: number, orientation: Orientation): void {
this._table.layout(size, orientation);
}
}
export class TableHeaderView<T> extends HeaderView {
private _table: Table<T>;
private _container: HTMLElement;
constructor(
private _viewTitle: string,
viewOpts: IViewOptions,
data?: Array<T> | TableDataView<T>,
columns?: Slick.Column<T>[],
tableOpts?: Slick.GridOptions<T>
) {
super(undefined, viewOpts);
this._container = document.createElement('div');
this._container.className = 'table-view';
this._table = new Table<T>(this._container, data, columns, tableOpts);
}
public get table(): Table<T> {
return this._table;
}
protected renderHeader(container: HTMLElement): void {
const titleDiv = $('div.title').appendTo(container);
$('span').text(this._viewTitle).appendTo(titleDiv);
}
protected renderBody(container: HTMLElement): void {
container.appendChild(this._container);
}
protected layoutBody(size: number): void {
this._table.layout(size, Orientation.HORIZONTAL);
}
focus(): void {
this._table.focus();
}
}
export class TableCollapsibleView<T> extends AbstractCollapsibleView {
private _table: Table<T>;
private _container: HTMLElement;
constructor(
private _viewTitle: string,
viewOpts: ICollapsibleViewOptions,
data?: Array<T> | TableDataView<T>,
columns?: Slick.Column<T>[],
tableOpts?: Slick.GridOptions<T>
) {
super(undefined, viewOpts);
this._container = document.createElement('div');
this._container.className = 'table-view';
this._table = new Table<T>(this._container, data, columns, tableOpts);
}
public addContainerClass(className: string) {
this._container.classList.add(className);
}
public get table(): Table<T> {
return this._table;
}
protected renderHeader(container: HTMLElement): void {
const titleDiv = $('div.title').appendTo(container);
$('span').text(this._viewTitle).appendTo(titleDiv);
}
protected renderBody(container: HTMLElement): void {
container.appendChild(this._container);
}
protected layoutBody(size: number): void {
this._table.layout(size, Orientation.HORIZONTAL);
}
}

View File

@@ -0,0 +1,381 @@
/*---------------------------------------------------------------------------------------------
* 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 'vs/css!vs/base/browser/ui/actionbar/actionbar';
import { Promise } from 'vs/base/common/winjs.base';
import { Builder, $ } from 'vs/base/browser/builder';
import { IAction, IActionRunner, ActionRunner } from 'vs/base/common/actions';
import { EventType as CommonEventType } from 'vs/base/common/events';
import { EventEmitter } from 'vs/base/common/eventEmitter';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import {
IActionBarOptions, ActionsOrientation, IActionItem,
IActionOptions, ActionItem, BaseActionItem
} from 'vs/base/browser/ui/actionbar/actionbar';
import * as lifecycle from 'vs/base/common/lifecycle';
import * as DOM from 'vs/base/browser/dom';
import * as types from 'vs/base/common/types';
let defaultOptions: IActionBarOptions = {
orientation: ActionsOrientation.HORIZONTAL,
context: null
};
/**
* Contains logic for displaying and handling callbacks for clickable icons. Based on
* ActionBar vs/base/browser/ui/actionbar/actionbar. This class was needed because we
* want the ability to display content other than Action icons in the QueryTaskbar.
*/
export class ActionBar extends EventEmitter implements IActionRunner {
private _options: IActionBarOptions;
private _actionRunner: IActionRunner;
private _context: any;
// Items
private _items: IActionItem[];
private _focusedItem: number;
private _focusTracker: DOM.IFocusTracker;
private _toDispose: lifecycle.IDisposable[];
// Elements
private _domNode: HTMLElement;
private _actionsList: HTMLElement;
constructor(container: HTMLElement | Builder, options: IActionBarOptions = defaultOptions) {
super();
this._options = options;
this._context = options.context;
this._toDispose = [];
this._actionRunner = this._options.actionRunner;
if (!this._actionRunner) {
this._actionRunner = new ActionRunner();
this._toDispose.push(this._actionRunner);
}
this._toDispose.push(this.addEmitter(this._actionRunner));
this._items = [];
this._focusedItem = undefined;
this._domNode = document.createElement('div');
this._domNode.className = 'monaco-action-bar';
if (options.animated !== false) {
DOM.addClass(this._domNode, 'animated');
}
let isVertical = this._options.orientation === ActionsOrientation.VERTICAL;
if (isVertical) {
this._domNode.className += ' vertical';
}
$(this._domNode).on(DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
let event = new StandardKeyboardEvent(e);
let eventHandled = true;
if (event.equals(isVertical ? KeyCode.UpArrow : KeyCode.LeftArrow)) {
this.focusPrevious();
} else if (event.equals(isVertical ? KeyCode.DownArrow : KeyCode.RightArrow)) {
this.focusNext();
} else if (event.equals(KeyCode.Escape)) {
this.cancel();
} else if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
// Nothing, just staying out of the else branch
} else {
eventHandled = false;
}
if (eventHandled) {
event.preventDefault();
event.stopPropagation();
}
});
// Prevent native context menu on actions
$(this._domNode).on(DOM.EventType.CONTEXT_MENU, (e: Event) => {
e.preventDefault();
e.stopPropagation();
});
$(this._domNode).on(DOM.EventType.KEY_UP, (e: KeyboardEvent) => {
let event = new StandardKeyboardEvent(e);
// Run action on Enter/Space
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
this.doTrigger(event);
event.preventDefault();
event.stopPropagation();
}
// Recompute focused item
else if (event.equals(KeyCode.Tab) || event.equals(KeyMod.Shift | KeyCode.Tab)) {
this.updateFocusedItem();
}
});
this._focusTracker = DOM.trackFocus(this._domNode);
this._focusTracker.addBlurListener(() => {
if (document.activeElement === this._domNode || !DOM.isAncestor(document.activeElement, this._domNode)) {
this.emit(DOM.EventType.BLUR, {});
this._focusedItem = undefined;
}
});
this._focusTracker.addFocusListener(() => this.updateFocusedItem());
this._actionsList = document.createElement('ul');
this._actionsList.className = 'actions-container';
this._actionsList.setAttribute('role', 'toolbar');
if (this._options.ariaLabel) {
this._actionsList.setAttribute('aria-label', this._options.ariaLabel);
}
this._domNode.appendChild(this._actionsList);
((container instanceof Builder) ? container.getHTMLElement() : container).appendChild(this._domNode);
}
public setAriaLabel(label: string): void {
if (label) {
this._actionsList.setAttribute('aria-label', label);
} else {
this._actionsList.removeAttribute('aria-label');
}
}
private updateFocusedItem(): void {
for (let i = 0; i < this._actionsList.children.length; i++) {
let elem = this._actionsList.children[i];
if (DOM.isAncestor(document.activeElement, elem)) {
this._focusedItem = i;
break;
}
}
}
public get context(): any {
return this._context;
}
public set context(context: any) {
this._context = context;
this._items.forEach(i => i.setActionContext(context));
}
public get actionRunner(): IActionRunner {
return this._actionRunner;
}
public set actionRunner(actionRunner: IActionRunner) {
if (actionRunner) {
this._actionRunner = actionRunner;
this._items.forEach(item => item.actionRunner = actionRunner);
}
}
public getContainer(): Builder {
return $(this._domNode);
}
/**
* Push an HTML Element onto the action bar UI in the position specified by options.
* Pushes to the last position if no options are provided.
*/
public pushElement(element: HTMLElement, options: IActionOptions = {}): void {
let index = types.isNumber(options.index) ? options.index : null;
if (index === null || index < 0 || index >= this._actionsList.children.length) {
this._actionsList.appendChild(element);
} else {
this._actionsList.insertBefore(element, this._actionsList.children[index++]);
}
}
/**
* Push an action onto the action bar UI in the position specified by options.
* Pushes to the last position if no options are provided.
*/
public pushAction(arg: IAction | IAction[], options: IActionOptions = {}): void {
const actions: IAction[] = !Array.isArray(arg) ? [arg] : arg;
let index = types.isNumber(options.index) ? options.index : null;
actions.forEach((action: IAction) => {
const actionItemElement = document.createElement('li');
actionItemElement.className = 'action-item';
actionItemElement.setAttribute('role', 'presentation');
let item: IActionItem = null;
if (this._options.actionItemProvider) {
item = this._options.actionItemProvider(action);
}
if (!item) {
item = new ActionItem(this.context, action, options);
}
item.actionRunner = this._actionRunner;
item.setActionContext(this.context);
this.addEmitter(item);
item.render(actionItemElement);
if (index === null || index < 0 || index >= this._actionsList.children.length) {
this._actionsList.appendChild(actionItemElement);
} else {
this._actionsList.insertBefore(actionItemElement, this._actionsList.children[index++]);
}
this._items.push(item);
});
}
public pull(index: number): void {
if (index >= 0 && index < this._items.length) {
this._items.splice(index, 1);
this._actionsList.removeChild(this._actionsList.childNodes[index]);
}
}
public clear(): void {
// Do not dispose action items if they were provided from outside
this._items = this._options.actionItemProvider ? [] : lifecycle.dispose(this._items);
$(this._actionsList).empty();
}
public length(): number {
return this._items.length;
}
public isEmpty(): boolean {
return this._items.length === 0;
}
public focus(selectFirst?: boolean): void {
if (selectFirst && typeof this._focusedItem === 'undefined') {
this._focusedItem = 0;
}
this.updateFocus();
}
private focusNext(): void {
if (typeof this._focusedItem === 'undefined') {
this._focusedItem = this._items.length - 1;
}
let startIndex = this._focusedItem;
let item: IActionItem;
do {
this._focusedItem = (this._focusedItem + 1) % this._items.length;
item = this._items[this._focusedItem];
} while (this._focusedItem !== startIndex && !item.isEnabled());
if (this._focusedItem === startIndex && !item.isEnabled()) {
this._focusedItem = undefined;
}
this.updateFocus();
}
private focusPrevious(): void {
if (typeof this._focusedItem === 'undefined') {
this._focusedItem = 0;
}
let startIndex = this._focusedItem;
let item: IActionItem;
do {
this._focusedItem = this._focusedItem - 1;
if (this._focusedItem < 0) {
this._focusedItem = this._items.length - 1;
}
item = this._items[this._focusedItem];
} while (this._focusedItem !== startIndex && !item.isEnabled());
if (this._focusedItem === startIndex && !item.isEnabled()) {
this._focusedItem = undefined;
}
this.updateFocus();
}
private updateFocus(): void {
if (typeof this._focusedItem === 'undefined') {
this._domNode.focus();
return;
}
for (let i = 0; i < this._items.length; i++) {
let item = this._items[i];
let actionItem = <any>item;
if (i === this._focusedItem) {
if (types.isFunction(actionItem.focus)) {
actionItem.focus();
}
} else {
if (types.isFunction(actionItem.blur)) {
actionItem.blur();
}
}
}
}
private doTrigger(event: StandardKeyboardEvent): void {
if (typeof this._focusedItem === 'undefined') {
return; //nothing to focus
}
// trigger action
let actionItem = this._items[this._focusedItem];
if (actionItem instanceof BaseActionItem) {
const context = (actionItem._context === null || actionItem._context === undefined) ? event : actionItem._context;
this.run(actionItem._action, context).done();
}
}
private cancel(): void {
if (document.activeElement instanceof HTMLElement) {
(<HTMLElement>document.activeElement).blur(); // remove focus from focussed action
}
this.emit(CommonEventType.CANCEL);
}
public run(action: IAction, context?: any): Promise {
return this._actionRunner.run(action, context);
}
public dispose(): void {
if (this._items !== null) {
lifecycle.dispose(this._items);
}
this._items = null;
if (this._focusTracker) {
this._focusTracker.dispose();
this._focusTracker = null;
}
this._toDispose = lifecycle.dispose(this._toDispose);
this.getContainer().destroy();
super.dispose();
}
}

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#212121;}</style></defs><title>change_connection</title><g id="path1"><path class="cls-1" d="M7.7,6.72h.83l0,2.85H7.68ZM5.51,8.61h.73v1.52A2,2,0,0,0,8,12.3h.19A2,2,0,0,0,10,10.16V8.64h.73v1.52a2.81,2.81,0,0,1-2.52,3H8A2.81,2.81,0,0,1,5.5,10.13ZM6.46,3.8A2.23,2.23,0,0,1,8,3.11h.19a2.81,2.81,0,0,1,2.47,3.06V7.7H10V6.17A2,2,0,0,0,8.24,4H8A2,2,0,0,0,6.26,6.14V7.67H5.52V6.14A3.33,3.33,0,0,1,6.46,3.8Z"/></g><path class="cls-1" d="M8,14.83a7,7,0,0,0,6.76-5l.93.29a7.75,7.75,0,0,1-1.14,2.32,8.11,8.11,0,0,1-4,3A7.88,7.88,0,0,1,8,15.8a8,8,0,0,1-3.9-1,8.14,8.14,0,0,1-1.63-1.2A7.82,7.82,0,0,1,1.18,12v1.85H.2V10H4.1v1H1.69a7.05,7.05,0,0,0,2.6,2.84,7.29,7.29,0,0,0,1.76.79A6.8,6.8,0,0,0,8,14.83ZM15.8,2.15V6H11.9v-1h2.41a7,7,0,0,0-1.12-1.61A7.08,7.08,0,0,0,11.7,2.24a7.28,7.28,0,0,0-1.76-.78A6.8,6.8,0,0,0,8,1.17a7,7,0,0,0-6.76,5L.31,5.91A7.79,7.79,0,0,1,1.44,3.59a8.08,8.08,0,0,1,4-3A7.85,7.85,0,0,1,8,.2a8,8,0,0,1,3.9,1,8.14,8.14,0,0,1,1.63,1.2A7.81,7.81,0,0,1,14.82,4V2.15Z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>change_connection_inverse</title><g id="path1"><path class="cls-1" d="M7.7,6.72h.83l0,2.85H7.68ZM5.51,8.61h.73v1.52A2,2,0,0,0,8,12.3h.19A2,2,0,0,0,10,10.16V8.64h.73v1.52a2.81,2.81,0,0,1-2.52,3H8A2.81,2.81,0,0,1,5.5,10.13ZM6.46,3.8A2.23,2.23,0,0,1,8,3.11h.19a2.81,2.81,0,0,1,2.47,3.06V7.7H10V6.17A2,2,0,0,0,8.24,4H8A2,2,0,0,0,6.26,6.14V7.67H5.52V6.14A3.33,3.33,0,0,1,6.46,3.8Z"/></g><path class="cls-1" d="M8,14.83a7,7,0,0,0,6.76-5l.93.29a7.75,7.75,0,0,1-1.14,2.32,8.11,8.11,0,0,1-4,3A7.88,7.88,0,0,1,8,15.8a8,8,0,0,1-3.9-1,8.14,8.14,0,0,1-1.63-1.2A7.82,7.82,0,0,1,1.18,12v1.85H.2V10H4.1v1H1.69a7.05,7.05,0,0,0,2.6,2.84,7.29,7.29,0,0,0,1.76.79A6.8,6.8,0,0,0,8,14.83ZM15.8,2.15V6H11.9v-1h2.41a7,7,0,0,0-1.12-1.61A7.08,7.08,0,0,0,11.7,2.24a7.28,7.28,0,0,0-1.76-.78A6.8,6.8,0,0,0,8,1.17a7,7,0,0,0-6.76,5L.31,5.91A7.79,7.79,0,0,1,1.44,3.59a8.08,8.08,0,0,1,4-3A7.85,7.85,0,0,1,8,.2a8,8,0,0,1,3.9,1,8.14,8.14,0,0,1,1.63,1.2A7.81,7.81,0,0,1,14.82,4V2.15Z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#212121;}</style></defs><title>connect</title><path id="path1" class="cls-1" d="M7.67,5.75H9l0,4.51H7.64Zm-3.47,3H5.36l0,2.41c0,1.88,1.23,3.42,2.76,3.43h.31c1.54,0,2.8-1.51,2.82-3.38l0-2.41h1.16l0,2.41c0,2.66-1.8,4.8-4,4.78H8.1c-2.18,0-3.93-2.19-3.91-4.84ZM5.7,1.12A3.53,3.53,0,0,1,8.22,0h.31c2.18,0,3.93,2.19,3.91,4.85l0,2.41H11.26l0-2.41c0-1.88-1.23-3.41-2.76-3.43H8.21C6.67,1.44,5.41,3,5.39,4.84l0,2.41H4.22l0-2.41A5.28,5.28,0,0,1,5.7,1.12Z"/></svg>

After

Width:  |  Height:  |  Size: 570 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>connect_inverse</title><path id="path1" class="cls-1" d="M7.67,5.75H9l0,4.51H7.64Zm-3.47,3H5.36l0,2.41c0,1.88,1.23,3.42,2.76,3.43h.31c1.54,0,2.8-1.51,2.82-3.38l0-2.41h1.16l0,2.41c0,2.66-1.8,4.8-4,4.78H8.1c-2.18,0-3.93-2.19-3.91-4.84ZM5.7,1.12A3.53,3.53,0,0,1,8.22,0h.31c2.18,0,3.93,2.19,3.91,4.85l0,2.41H11.26l0-2.41c0-1.88-1.23-3.41-2.76-3.43H8.21C6.67,1.44,5.41,3,5.39,4.84l0,2.41H4.22l0-2.41A5.28,5.28,0,0,1,5.7,1.12Z"/></svg>

After

Width:  |  Height:  |  Size: 575 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>copy_image_16x16</title><path d="M16,4.09v10H2v-2H0v-10H14v2Zm-15-1V7.38L3.5,4.89l4,4,2-2L13,10.38V3.09Zm7.29,8L3.5,6.3,1,8.81v2.29Zm6.71-6H14v7H3v1H15Zm-2.71,6L9.5,8.3,8.2,9.59l1.51,1.5Zm-.79-6a.51.51,0,1,1,.35-.15A.48.48,0,0,1,11.5,5.09Z"/></svg>

After

Width:  |  Height:  |  Size: 348 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>copy_image_inverse_16x16</title><path class="cls-1" d="M16,4.78v10H2v-2H0v-10H14v2Zm-15-1V8.07L3.5,5.58l4,4,2-2L13,11.07V3.78Zm7.29,8L3.5,7,1,9.49v2.29Zm6.71-6H14v7H3v1H15Zm-2.71,6L9.5,9l-1.3,1.3,1.51,1.5Zm-.79-6a.51.51,0,1,1,.35-.15A.48.48,0,0,1,11.5,5.78Z"/></svg>

After

Width:  |  Height:  |  Size: 412 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#212121;}</style></defs><title>disconnect</title><g id="path1"><path class="cls-1" d="M5.2,5.27V3a3,3,0,0,1,3-3,3,3,0,0,1,3,3V7h-1V3a2,2,0,0,0-4,0V5.74ZM5.2,13V9h1v4a2,2,0,1,0,4,0V10.72l1,.47V13a3,3,0,0,1-3,3A3,3,0,0,1,5.2,13Zm-2-6.54V5.35l4,1.86V5h2V8.15L13.14,10V11.1L9.2,9.26V11h-2V8.32Z"/></g></svg>

After

Width:  |  Height:  |  Size: 421 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>disconnect_inverse</title><g id="path1"><path class="cls-1" d="M5.2,5.27V3a3,3,0,0,1,3-3,3,3,0,0,1,3,3V7h-1V3a2,2,0,0,0-4,0V5.74ZM5.2,13V9h1v4a2,2,0,1,0,4,0V10.72l1,.47V13a3,3,0,0,1-3,3A3,3,0,0,1,5.2,13Zm-2-6.54V5.35l4,1.86V5h2V8.15L13.14,10V11.1L9.2,9.26V11h-2V8.32Z"/></g></svg>

After

Width:  |  Height:  |  Size: 426 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#262626"><circle cx="3.5" cy="7.5" r="2.5"/><circle cx="8.5" cy="7.5" r="2.5"/><circle cx="13.5" cy="7.5" r="2.5"/></g><g fill="#C5C5C5"><circle cx="3.5" cy="7.5" r="1.5"/><circle cx="8.5" cy="7.5" r="1.5"/><circle cx="13.5" cy="7.5" r="1.5"/></g></svg>

After

Width:  |  Height:  |  Size: 325 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#F6F6F6"><circle cx="3.5" cy="7.5" r="2.5"/><circle cx="8.5" cy="7.5" r="2.5"/><circle cx="13.5" cy="7.5" r="2.5"/></g><g fill="#424242"><circle cx="3.5" cy="7.5" r="1.5"/><circle cx="8.5" cy="7.5" r="1.5"/><circle cx="13.5" cy="7.5" r="1.5"/></g></svg>

After

Width:  |  Height:  |  Size: 325 B

View File

@@ -0,0 +1,91 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.vs .monaco-toolbar .action-label.toolbar-toggle-more {
background-image: url('ellipsis.svg');
}
.hc-black .monaco-toolbar .action-label.toolbar-toggle-more,
.vs-dark .monaco-toolbar .action-label.toolbar-toggle-more {
background-image: url('ellipsis-inverse.svg');
}
.vs .icon.start,
.vs-dark .icon.start,
.hc-black .icon.start {
background-image: url('start.svg');
}
.vs .icon.stop,
.vs-dark .icon.stop,
.hc-black .icon.stop {
background-image: url('stop.svg');
}
.vs .icon.disconnect {
background-image: url('disconnect.svg');
}
.vs-dark .icon.disconnect,
.hc-black .icon.disconnect {
background-image: url('disconnect_inverse.svg');
background-repeat: no-repeat;
}
.vs .icon.connect {
background-image: url('connect.svg');
}
.vs-dark .icon.connect,
.hc-black .icon.connect {
background-image: url('connect_inverse.svg');
background-repeat: no-repeat;
}
.vs .icon.changeConnection {
background-image: url('change_connection.svg');
}
.vs-dark .icon.changeConnection,
.hc-black .icon.changeConnection {
background-image: url('change_connection_inverse.svg');
background-repeat: no-repeat;
}
.vs .icon.estimatedQueryPlan {
background-image: url('query-plan.svg');
}
.vs-dark .icon.estimatedQueryPlan,
.hc-black .icon.estimatedQueryPlan {
background-image: url('query-plan-inverse.svg');
}
.vs .icon.createInsight {
background-image: url('create_insight.svg');
}
.vs-dark .icon.createInsight,
.hc-black .icon.createInsight {
background-image: url('create_insight_inverse.svg');
}
.vs .icon.copyImage {
background-image: url('copy_image.svg');
}
.vs-dark .icon.copyImage,
.hc-black .icon.copyImage {
background-image: url('copy_image_inverse.svg');
}
.vs .icon.saveAsImage {
background-image: url('save_as_image.svg');
}
.vs-dark .icon.saveAsImage,
.hc-black .icon.saveAsImage {
background-image: url('save_as_image_inverse.svg');
}

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>query_plan_inverse_16x16</title><path class="cls-1" d="M13.06,10.49H15v5H10.22v-5h1.89v-2H3.59v2H5.48v5H.75v-5h1.9v-3H7.38v-2H5.48v-5h4.74v5H8.33v2h4.74Zm-8.53,4v-3H1.69v3Zm1.9-13v3H9.27v-3Zm7.58,13v-3H11.17v3Z"/></svg>

After

Width:  |  Height:  |  Size: 365 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>query_plan_16x16</title><path d="M13.16,10.59h1.9v5H10.32v-5h1.89v-2H3.68v2H5.58v5H.84v-5h1.9v-3H7.47v-2H5.58v-5h4.74v5H8.42v2h4.74Zm-8.53,4v-3H1.79v3Zm1.9-13v3H9.37v-3Zm7.58,13v-3H11.26v3Z"/></svg>

After

Width:  |  Height:  |  Size: 298 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>save_as_image_16x16</title><path d="M12.22.54a.9.9,0,0,0-.47-.47A.94.94,0,0,0,11.41,0H.88A1,1,0,0,0,.54.07,1.07,1.07,0,0,0,.26.26.93.93,0,0,0,.07.54.93.93,0,0,0,0,.88v9.83l1.57,1.57H7.86v-.87H5.27V9.65H4.39v1.76H3.51V8.77H7.86V7.9H2.64v3.51h-.7L.88,10.34V.88h.88V6.14h8.77V.88h.88v7h.88v-7A.94.94,0,0,0,12.22.54ZM9.66,5.27h-7V.88h7Z"/><path d="M9,9h7v7H9Zm6.56,4.81V9.44H9.42v3.94l2-2.63,3.69,4.73-1.72-3.2.66-.66Z"/></svg>

After

Width:  |  Height:  |  Size: 523 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>save_as_image_inverse_16x16</title><path class="cls-1" d="M12.22.54a.9.9,0,0,0-.47-.47A.94.94,0,0,0,11.41,0H.88A1,1,0,0,0,.54.07,1.07,1.07,0,0,0,.26.26.93.93,0,0,0,.07.54.93.93,0,0,0,0,.88v9.83l1.57,1.57H7.86v-.87H5.27V9.65H4.39v1.76H3.51V8.77H7.86V7.9H2.64v3.51h-.7L.88,10.34V.88h.88V6.14h8.77V.88h.88v7h.88v-7A.94.94,0,0,0,12.22.54ZM9.66,5.27h-7V.88h7Z"/><path class="cls-1" d="M9,9h7v7H9Zm6.56,4.81V9.44H9.42v3.94l2-2.63,3.69,4.73-1.72-3.2.66-.66Z"/></svg>

After

Width:  |  Height:  |  Size: 605 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#3bb44a;}</style></defs><title>run</title><path class="cls-1" d="M3.24,0,14.61,8,3.24,16Zm2,12.07L11.13,8,5.24,3.88Z"/><path class="cls-1" d="M3.74,1l10,7-10,7Zm1,1.92V13.07L12,8Z"/></svg>

After

Width:  |  Height:  |  Size: 306 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#d02e00;}</style></defs><title>stop</title><path class="cls-1" d="M.5,15.3V.3h15v15Zm13-2V2.3H2.5v11Z"/><path class="cls-1" d="M1,.8H15v14H1Zm13,13V1.8H2v12Z"/></svg>

After

Width:  |  Height:  |  Size: 284 B

View File

@@ -0,0 +1,85 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* CSS Properties ported from taskbar.css */
.monaco-toolbar .dropdown > .dropdown-label:not(:empty):not(.tick) {
padding: 0;
}
.monaco-toolbar .toolbar-toggle-more {
display: inline-block;
padding: 0;
}
/* Taskbar */
.carbon-taskbar {
width: 100%;
position: relative;
}
.carbon-taskbar.monaco-toolbar .monaco-action-bar.animated .actions-container {
justify-content: flex-start;
padding-left: 15px;
flex-wrap: wrap;
}
.taskbar-progress {
padding-left: 5px;
padding-top: 5px;
width: 15px;
height: 15px;
}
.carbon-taskbar .monaco-action-bar li {
display: flex;
align-items: center;
}
.carbon-taskbar {
margin-top: 2px;
margin-bottom: 2px;
}
.carbon-taskbar .action-item {
margin-right: 5px;
}
.carbon-taskbar .action-label {
background-repeat: no-repeat;
background-position: 0% 50%;
padding-left: 15px;
}
.listDatabasesSelectBox {
padding-left: 2px;
min-width: 100px;
}
.carbon-taskbar.monaco-toolbar .monaco-action-bar.animated .actions-container .action-item
.configuration.select-container .select-box {
margin-top: 4px;
margin-top: 3px;
}
.taskbarSeparator {
width: 1px;
background-color:#555;
margin-right: 6px;
margin-top: 3px;
}
.taskbarTextSeparator {
margin-left: 3px;
margin-right: 3px;
margin-top: 4px;
}
/* Taskbar Icons */
.carbon-taskbar .icon {
background-size: 11px;
}

View File

@@ -0,0 +1,141 @@
/*---------------------------------------------------------------------------------------------
* 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 'vs/css!./media/taskbar';
import 'vs/css!./media/icons';
import 'vs/css!sql/media/icons/common-icons';
import { ActionBar } from './actionbar';
import { Builder, $ } from 'vs/base/browser/builder';
import { Action, IActionRunner, IAction } from 'vs/base/common/actions';
import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { IContextMenuProvider } from 'vs/base/browser/ui/dropdown/dropdown';
import { IToolBarOptions } from 'vs/base/browser/ui/toolbar/toolbar';
/**
* A wrapper for the different types of content a QueryTaskbar can display
*/
export interface ITaskbarContent {
// Display the element created by this IAction
action?: IAction;
// Display a pre-created element
element?: HTMLElement;
}
/**
* A widget that combines an action bar for actions. This class was needed because we
* want the ability to use the custom QueryActionBar in order to display other HTML
* in our taskbar. Based off import ToolBar from vs/base/browser/ui/toolbar/toolbar.
*/
export class Taskbar {
private options: IToolBarOptions;
private actionBar: ActionBar;
private lookupKeybindings: boolean;
constructor(container: HTMLElement, contextMenuProvider: IContextMenuProvider, options: IToolBarOptions = { orientation: ActionsOrientation.HORIZONTAL }) {
this.options = options;
this.lookupKeybindings = typeof this.options.getKeyBinding === 'function' && typeof this.options.getKeyBinding === 'function';
let element = document.createElement('div');
element.className = 'monaco-toolbar carbon-taskbar';
container.appendChild(element);
this.actionBar = new ActionBar($(element), {
orientation: options.orientation,
ariaLabel: options.ariaLabel,
actionItemProvider: (action: Action) => {
return options.actionItemProvider ? options.actionItemProvider(action) : null;
}
});
}
/**
* Creates an HTML vertical separator.
*/
public static createTaskbarSeparator(): HTMLElement {
let element = document.createElement('div');
element.className = 'taskbarSeparator';
element.innerHTML = ' ';
return element;
}
/**
* Creates an HTML spinner.
*/
public static createTaskbarSpinner(): HTMLElement {
let spinnerContainer = document.createElement('div');
spinnerContainer.className = 'taskbar-progress icon in-progress ';
spinnerContainer.style.visibility = 'hidden';
return spinnerContainer;
}
/**
* Creates an HTML text separator.
*/
public static createTaskbarText(inputText: string): HTMLElement {
let element = document.createElement('div');
element.className = 'taskbarTextSeparator';
element.innerHTML = inputText;
return element;
}
public set actionRunner(actionRunner: IActionRunner) {
this.actionBar.actionRunner = actionRunner;
}
public get actionRunner(): IActionRunner {
return this.actionBar.actionRunner;
}
public set context(context: any) {
this.actionBar.context = context;
}
public getContainer(): Builder {
return this.actionBar.getContainer();
}
public setAriaLabel(label: string): void {
this.actionBar.setAriaLabel(label);
}
/**
* Push HTMLElements and icons for IActions into the ActionBar UI. Push IActions into ActionBar's private collection.
*/
public setContent(content: ITaskbarContent[]): void {
let contentToSet: ITaskbarContent[] = content ? content.slice(0) : [];
this.actionBar.clear();
for (let item of contentToSet) {
if (item.action) {
this.actionBar.pushAction(item.action, { icon: true, label: true, keybinding: this.getKeybindingLabel(item.action) });
} else if (item.element) {
this.actionBar.pushElement(item.element);
}
}
}
private getKeybindingLabel(action: IAction): string {
const key = this.lookupKeybindings ? this.options.getKeyBinding(action) : void 0;
return key ? key.getLabel() : '';
}
public addAction(primaryAction: IAction): void {
this.actionBar.pushAction(primaryAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(primaryAction) });
}
public addElement(element: HTMLElement): void {
this.actionBar.pushElement(element);
}
public dispose(): void {
this.actionBar.dispose();
}
}