SQL Operations Studio Public Preview 1 (0.23) release source code
58
src/sql/base/browser/ui/breadcrumb/breadcrumb.component.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
29
src/sql/base/browser/ui/breadcrumb/interfaces.ts
Normal 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;
|
||||
}
|
||||
32
src/sql/base/browser/ui/breadcrumb/media/breadcrumb.css
Normal 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;
|
||||
}
|
||||
@@ -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 |
@@ -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 |
54
src/sql/base/browser/ui/checkbox/checkbox.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
71
src/sql/base/browser/ui/checkbox/defaultCheckbox.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
1
src/sql/base/browser/ui/checkbox/media/check.svg
Normal 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 |
1
src/sql/base/browser/ui/checkbox/media/check_inverse.svg
Normal 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 |
24
src/sql/base/browser/ui/checkbox/media/checkbox.css
Normal 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;
|
||||
}
|
||||
146
src/sql/base/browser/ui/dropdownList/dropdownList.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/sql/base/browser/ui/dropdownList/media/dropdownList.css
Normal 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");
|
||||
}
|
||||
@@ -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 |
@@ -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 |
23
src/sql/base/browser/ui/editableDropdown/actions.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
307
src/sql/base/browser/ui/editableDropdown/dropdown.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 |
@@ -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 |
81
src/sql/base/browser/ui/inputBox/inputBox.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
261
src/sql/base/browser/ui/listBox/listBox.ts
Normal 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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
106
src/sql/base/browser/ui/modal/dialogHelper.ts
Normal 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;
|
||||
}
|
||||
1
src/sql/base/browser/ui/modal/media/back.svg
Normal 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 |
1
src/sql/base/browser/ui/modal/media/back_inverse.svg
Normal 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 |
178
src/sql/base/browser/ui/modal/media/modal.css
Normal 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;
|
||||
}
|
||||
60
src/sql/base/browser/ui/modal/media/optionsDialog.css
Normal 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;
|
||||
}
|
||||
432
src/sql/base/browser/ui/modal/modal.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
268
src/sql/base/browser/ui/modal/optionsDialog.ts
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
189
src/sql/base/browser/ui/modal/optionsDialogHelper.ts
Normal 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;
|
||||
}
|
||||
58
src/sql/base/browser/ui/panel/media/panel.css
Normal 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);
|
||||
}
|
||||
98
src/sql/base/browser/ui/panel/panel.component.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/sql/base/browser/ui/panel/panel.module.ts
Normal 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 { }
|
||||
166
src/sql/base/browser/ui/panel/panel.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
70
src/sql/base/browser/ui/panel/panelStyles.ts
Normal 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;
|
||||
}
|
||||
`);
|
||||
}
|
||||
});
|
||||
35
src/sql/base/browser/ui/panel/tab.component.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
225
src/sql/base/browser/ui/selectBox/selectBox.ts
Normal 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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
src/sql/base/browser/ui/table/media/sort-asc.gif
Normal file
|
After Width: | Height: | Size: 830 B |
BIN
src/sql/base/browser/ui/table/media/sort-desc.gif
Normal file
|
After Width: | Height: | Size: 833 B |
34
src/sql/base/browser/ui/table/media/table.css
Normal 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;
|
||||
}
|
||||
137
src/sql/base/browser/ui/table/plugins/autoSizeColumns.plugin.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
273
src/sql/base/browser/ui/table/table.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
157
src/sql/base/browser/ui/table/tableDataView.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
121
src/sql/base/browser/ui/table/tableView.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { 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);
|
||||
}
|
||||
}
|
||||
381
src/sql/base/browser/ui/taskbar/actionbar.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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 |
@@ -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 |
1
src/sql/base/browser/ui/taskbar/media/connect.svg
Normal 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 |
@@ -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 |
1
src/sql/base/browser/ui/taskbar/media/copy_image.svg
Normal 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 |
@@ -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 |
1
src/sql/base/browser/ui/taskbar/media/create_insight.svg
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
1
src/sql/base/browser/ui/taskbar/media/disconnect.svg
Normal 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 |
@@ -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 |
@@ -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 |
1
src/sql/base/browser/ui/taskbar/media/ellipsis.svg
Normal 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 |
91
src/sql/base/browser/ui/taskbar/media/icons.css
Normal 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');
|
||||
}
|
||||
@@ -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 |
1
src/sql/base/browser/ui/taskbar/media/query-plan.svg
Normal 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 |
1
src/sql/base/browser/ui/taskbar/media/save_as_image.svg
Normal 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 |
@@ -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 |
1
src/sql/base/browser/ui/taskbar/media/start.svg
Normal 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 |
1
src/sql/base/browser/ui/taskbar/media/stop.svg
Normal 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 |
85
src/sql/base/browser/ui/taskbar/media/taskbar.css
Normal 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;
|
||||
}
|
||||
141
src/sql/base/browser/ui/taskbar/taskbar.ts
Normal 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();
|
||||
}
|
||||
}
|
||||