Feature/tree component (#2077)

*added tree component to the model builder
This commit is contained in:
Leila Lali
2018-08-02 10:50:05 -07:00
committed by GitHub
parent f995dea971
commit 0d043207b9
26 changed files with 1129 additions and 45 deletions

View File

@@ -57,7 +57,7 @@ import { OperatorsViewComponent } from 'sql/parts/jobManagement/views/operatorsV
import { ProxiesViewComponent } from 'sql/parts/jobManagement/views/proxiesView.component';
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox.component';
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox.component';
import { EditableDropDown } from 'sql/base/browser/ui/editableDropdown/editabledropdown.component';
import { EditableDropDown } from 'sql/base/browser/ui/editableDropdown/editableDropdown.component';
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox.component';
let baseComponents = [DashboardHomeContainer, DashboardComponent, DashboardWidgetWrapper, DashboardWebviewContainer,

View File

@@ -71,6 +71,12 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
abstract setLayout(layout: any): void;
public setDataProvider(handle: number, componentId: string, context: any): void {
}
public refreshDataProvider(item: any): void {
}
public setProperties(properties: { [key: string]: any; }): void {
if (!properties) {
this.properties = {};

View File

@@ -14,6 +14,7 @@ import DeclarativeTableComponent from './declarativeTable.component';
import ListBoxComponent from './listbox.component';
import ButtonComponent from './button.component';
import CheckBoxComponent from './checkbox.component';
import TreeComponent from './tree/tree.component';
import RadioButtonComponent from './radioButton.component';
import WebViewComponent from './webview.component';
import TableComponent from './table.component';
@@ -73,6 +74,9 @@ registerComponentType(TABLE_COMPONENT, ModelComponentTypes.Table, TableComponent
export const LOADING_COMPONENT = 'loading-component';
registerComponentType(LOADING_COMPONENT, ModelComponentTypes.LoadingComponent, LoadingComponent);
export const TREE_COMPONENT = 'tree-component';
registerComponentType(TREE_COMPONENT, ModelComponentTypes.TreeComponent, TreeComponent);
export const FILEBROWSERTREE_COMPONENT = 'filebrowsertree-component';
registerComponentType(FILEBROWSERTREE_COMPONENT, ModelComponentTypes.FileBrowserTree, FileBrowserTreeComponent);

View File

@@ -17,7 +17,7 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView
import { Event, Emitter } from 'vs/base/common/event';
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox.component';
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox.component';
import { EditableDropDown } from 'sql/base/browser/ui/editableDropdown/editabledropdown.component';
import { EditableDropDown } from 'sql/base/browser/ui/editableDropdown/editableDropdown.component';
import { ISelectData } from 'vs/base/browser/ui/selectBox/selectBox';
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox.component';
import * as nls from 'vs/nls';

View File

@@ -26,6 +26,8 @@ export interface IComponent {
enabled: boolean;
readonly valid?: boolean;
validate(): Thenable<boolean>;
setDataProvider(handle: number, componentId: string, context: any): void;
refreshDataProvider(item: any): void;
}
export const COMPONENT_CONFIG = new InjectionToken<IComponentConfig>('component_config');

View File

@@ -0,0 +1,148 @@
/*---------------------------------------------------------------------------------------------
* 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/parts/modelComponents/tree/treeComponent';
import 'vs/css!sql/media/icons/common-icons';
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef,
ViewChild, ElementRef, OnDestroy, AfterViewInit
} from '@angular/core';
import * as sqlops from 'sqlops';
import { ComponentBase } from 'sql/parts/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { TreeComponentRenderer } from 'sql/parts/modelComponents/tree/treeComponentRenderer';
import { TreeComponentDataSource } from 'sql/parts/modelComponents/tree/treeDataSource';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { attachListStyler } from 'vs/platform/theme/common/styler';
import { DefaultFilter, DefaultAccessibilityProvider, DefaultController } from 'vs/base/parts/tree/browser/treeDefaults';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITreeComponentItem, IModelViewTreeViewDataProvider } from 'sql/workbench/common/views';
import { TreeViewDataProvider } from './treeViewDataProvider';
class Root implements ITreeComponentItem {
label = 'root';
handle = '0';
parentHandle = null;
collapsibleState = 0;
children = void 0;
options = undefined;
}
@Component({
selector: 'modelview-tree',
template: `
<div #input style="width: 100%;height:100%"></div>
`
})
export default class TreeComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
private _tree: Tree;
private _treeRenderer: TreeComponentRenderer;
private _dataProvider: TreeViewDataProvider;
@ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef;
constructor(
@Inject(forwardRef(() => CommonServiceInterface)) private _commonService: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(IContextViewService) private contextViewService: IContextViewService,
@Inject(IInstantiationService) private _instantiationService: IInstantiationService) {
super(changeRef);
}
ngOnInit(): void {
this.baseInit();
}
ngAfterViewInit(): void {
if (this._inputContainer) {
this.createTreeControl();
}
}
ngOnDestroy(): void {
this.baseDestroy();
}
public setDataProvider(handle: number, componentId: string, context: any): any {
this._dataProvider = new TreeViewDataProvider(handle, componentId, context);
this.createTreeControl();
}
public refreshDataProvider(itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeComponentItem }): void {
if (this._dataProvider) {
this._dataProvider.refresh(itemsToRefreshByHandle);
}
if (this._tree) {
for (const item of Object.values(itemsToRefreshByHandle)) {
this._tree.refresh(<ITreeComponentItem>item);
}
}
}
private createTreeControl(): void {
if (!this._tree && this._dataProvider) {
const dataSource = this._instantiationService.createInstance(TreeComponentDataSource, this._dataProvider);
const renderer = this._instantiationService.createInstance(TreeComponentRenderer, this.themeService, { withCheckbox: this.withCheckbox });
this._treeRenderer = renderer;
const controller = new DefaultController();
const filter = new DefaultFilter();
const sorter = undefined;
const dnd = undefined;
const accessibilityProvider = new DefaultAccessibilityProvider();
this._tree = new Tree(this._inputContainer.nativeElement,
{ dataSource, renderer, controller, dnd, filter, sorter, accessibilityProvider },
{
indentPixels: 10,
twistiePixels: 20,
ariaLabel: 'Tree Node'
});
this._tree.setInput(new Root());
this._tree.domFocus();
this._register(this._tree);
this._register(attachListStyler(this._tree, this.themeService));
this._tree.refresh();
this.layout();
}
}
/// IComponent implementation
public layout(): void {
this._changeRef.detectChanges();
this.createTreeControl();
if (this._tree) {
this._tree.layout(this.convertSizeToNumber(this.width), this.convertSizeToNumber(this.height));
this._tree.refresh();
}
}
public setLayout(layout: any): void {
// TODO allow configuring the look and feel
this.layout();
}
public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties);
this._treeRenderer.options.withCheckbox = this.withCheckbox;
}
public get withCheckbox(): boolean {
return this.getPropertyOrDefault<sqlops.TreeProperties, boolean>((props) => props.withCheckbox, false);
}
public set withCheckbox(newValue: boolean) {
this.setPropertyFromUI<sqlops.TreeProperties, boolean>((properties, value) => { properties.withCheckbox = value; }, newValue);
}
}

View File

@@ -0,0 +1,8 @@
.tree-component-node-tile {
display: flex;
}
.tree-component-node-tile .model-view-tree-node-item-icon{
width: 15px;
height: 15px;
}

View File

@@ -0,0 +1,168 @@
/*---------------------------------------------------------------------------------------------
* 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/media/icons/common-icons';
import * as dom from 'vs/base/browser/dom';
import { localize } from 'vs/nls';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITree, IRenderer } from 'vs/base/parts/tree/browser/tree';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { LIGHT } from 'vs/platform/theme/common/themeService';
import { Disposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { ITreeComponentItem } from 'sql/workbench/common/views';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
export enum TreeCheckboxState {
Intermediate = 0,
Checked = 1,
Unchecked = 2
}
export class TreeDataTemplate extends Disposable {
root: HTMLElement;
label: HTMLSpanElement;
icon: HTMLElement;
private _checkbox: HTMLInputElement;
model: ITreeComponentItem;
private _onChange = new Emitter<boolean>();
public readonly onChange: Event<boolean> = this._onChange.event;
public set checkbox(input: HTMLInputElement) {
this._checkbox = input;
this.handleOnChange(this._checkbox, () => {
this._onChange.fire(this._checkbox.checked);
if (this.model && this.model.onCheckedChanged) {
this.model.onCheckedChanged(this._checkbox.checked);
}
});
}
public get checkboxState(): TreeCheckboxState {
if (this._checkbox.indeterminate) {
return TreeCheckboxState.Intermediate;
} else {
return this.checkbox.checked ? TreeCheckboxState.Checked : TreeCheckboxState.Unchecked;
}
}
public set checkboxState(value: TreeCheckboxState) {
if (this.checkboxState !== value) {
switch (value) {
case TreeCheckboxState.Checked:
this._checkbox.indeterminate = false;
this._checkbox.checked = true;
break;
case TreeCheckboxState.Unchecked:
this._checkbox.indeterminate = false;
this._checkbox.checked = false;
break;
case TreeCheckboxState.Intermediate:
this._checkbox.indeterminate = true;
break;
default:
break;
}
}
}
public get checkbox(): HTMLInputElement {
return this._checkbox;
}
protected handleOnChange(domNode: HTMLElement, listener: (e: Event<void>) => void): void {
this._register(dom.addDisposableListener(domNode, dom.EventType.CHANGE, listener));
}
}
/**
* Renders the tree items.
* Uses the dom template to render connection groups and connections.
*/
export class TreeComponentRenderer extends Disposable implements IRenderer {
public static DEFAULT_TEMPLATE = 'DEFAULT_TEMPLATE';
public static DEFAULT_HEIGHT = 20;
constructor(
private themeService: IWorkbenchThemeService,
public options?: { withCheckbox: boolean }
) {
super();
}
/**
* Returns the element's height in the tree, in pixels.
*/
public getHeight(tree: ITree, element: any): number {
return TreeComponentRenderer.DEFAULT_HEIGHT;
}
/**
* Returns a template ID for a given element.
*/
public getTemplateId(tree: ITree, element: any): string {
return TreeComponentRenderer.DEFAULT_TEMPLATE;
}
/**
* Render template in a dom element based on template id
*/
public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
if (templateId === TreeComponentRenderer.DEFAULT_TEMPLATE) {
const nodeTemplate: TreeDataTemplate = new TreeDataTemplate();
nodeTemplate.root = dom.append(container, dom.$('.tree-component-node-tile'));
nodeTemplate.icon = dom.append(nodeTemplate.root, dom.$('div.model-view-tree-node-item-icon'));
if (this.options && this.options.withCheckbox) {
let checkboxWrapper = dom.append(nodeTemplate.root, dom.$('div.checkboxWrapper'));
nodeTemplate.checkbox = dom.append(checkboxWrapper, dom.$<HTMLInputElement>('input.checkbox', { type: 'checkbox' }));
}
nodeTemplate.label = dom.append(nodeTemplate.root, dom.$('div.model-view-tree-node-item-label'));
return nodeTemplate;
}
}
/**
* Render a element, given an object bag returned by the template
*/
public renderElement(tree: ITree, element: ITreeComponentItem, templateId: string, templateData: TreeDataTemplate): void {
const icon = this.themeService.getTheme().type === LIGHT ? element.icon : element.iconDark;
templateData.icon.style.backgroundImage = icon ? `url('${icon}')` : '';
dom.toggleClass(templateData.icon, 'model-view-tree-node-item-icon', !!icon);
if (element && !templateData.model) {
templateData.model = element;
}
if (templateId === TreeComponentRenderer.DEFAULT_TEMPLATE) {
this.renderNode(element, templateData);
}
}
private renderNode(treeNode: ITreeComponentItem, templateData: TreeDataTemplate): void {
let label = treeNode.label;
templateData.label.textContent = label;
templateData.root.title = label;
templateData.checkboxState = this.getCheckboxState(treeNode);
}
private getCheckboxState(treeNode: ITreeComponentItem): TreeCheckboxState {
if (treeNode.checked === undefined) {
return TreeCheckboxState.Intermediate;
} else {
return treeNode.checked ? TreeCheckboxState.Checked : TreeCheckboxState.Unchecked;
}
}
public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
this.dispose();
// no op
}
}

View File

@@ -0,0 +1,57 @@
/*---------------------------------------------------------------------------------------------
* 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 { ITree, IDataSource } from 'vs/base/parts/tree/browser/tree';
import { TPromise } from 'vs/base/common/winjs.base';
import { IModelViewTreeViewDataProvider, ITreeComponentItem } from 'sql/workbench/common/views';
/**
* Implements the DataSource(that returns a parent/children of an element) for the recent connection tree
*/
export class TreeComponentDataSource implements IDataSource {
/**
*
*/
constructor(
private _dataProvider: IModelViewTreeViewDataProvider) {
}
/**
* Returns the unique identifier of the given element.
* No more than one element may use a given identifier.
*/
public getId(tree: ITree, node: ITreeComponentItem): string {
return node.handle;
}
/**
* Returns a boolean value indicating whether the element has children.
*/
public hasChildren(tree: ITree, node: ITreeComponentItem): boolean {
return this._dataProvider !== undefined;
}
/**
* Returns the element's children as an array in a promise.
*/
public getChildren(tree: ITree, node: ITreeComponentItem): TPromise<any> {
if (this._dataProvider) {
if (node && node.handle === '0') {
return this._dataProvider.getChildren(undefined);
} else {
return this._dataProvider.getChildren(node);
}
}
return TPromise.as([]);
}
public getParent(tree: ITree, node: any): TPromise<any> {
return TPromise.as(null);
}
}

View File

@@ -0,0 +1,39 @@
/*---------------------------------------------------------------------------------------------
* 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 { ExtHostModelViewTreeViewsShape, SqlExtHostContext } from 'sql/workbench/api/node/sqlExtHost.protocol';
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
import { IModelViewTreeViewDataProvider, ITreeComponentItem } from 'sql/workbench/common/views';
import { INotificationService } from 'vs/platform/notification/common/notification';
import * as vsTreeView from 'vs/workbench/api/electron-browser/mainThreadTreeViews';
export class TreeViewDataProvider extends vsTreeView.TreeViewDataProvider implements IModelViewTreeViewDataProvider {
constructor(handle: number, treeViewId: string,
context: IExtHostContext,
notificationService?: INotificationService
) {
super(`${handle}-${treeViewId}`, context.getProxy(SqlExtHostContext.ExtHostModelViewTreeViews), notificationService);
}
onNodeCheckedChanged(treeViewId: string, treeItemHandle?: string, checked?: boolean) {
(<ExtHostModelViewTreeViewsShape>this._proxy).$onNodeCheckedChanged(treeViewId, treeItemHandle, checked);
}
protected postGetChildren(elements: ITreeComponentItem[]): ITreeComponentItem[] {
const result = [];
if (elements) {
for (const element of elements) {
element.onCheckedChanged = (checked: boolean) => {
this.onNodeCheckedChanged(this.treeViewId, element.handle, checked);
};
this.itemsMap.set(element.handle, element);
result.push(element);
}
}
return result;
}
}

View File

@@ -10,7 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
import nls = require('vs/nls');
import * as sqlops from 'sqlops';
import { IModelStore, IComponentDescriptor, IComponent, IComponentEventArgs } from './interfaces';
import { IModelStore, IComponentDescriptor, IComponent } from './interfaces';
import { IItemConfig, ModelComponentTypes, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
import { IModelView, IModelViewEventArgs } from 'sql/services/model/modelViewService';
import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry';
@@ -97,6 +97,10 @@ export abstract class ViewBase extends AngularDisposable implements IModelView {
this.queueAction(componentId, (component) => component.setProperties(properties));
}
refreshDataProvider(componentId: string, item: any): void {
this.queueAction(componentId, (component) => component.refreshDataProvider(item));
}
private queueAction<T>(componentId: string, action: (component: IComponent) => T): void {
this.modelStore.eventuallyRunOnComponent(componentId, action).catch(err => {
// TODO add error handling
@@ -122,4 +126,8 @@ export abstract class ViewBase extends AngularDisposable implements IModelView {
public validate(componentId: string): Thenable<boolean> {
return new Promise(resolve => this.modelStore.eventuallyRunOnComponent(componentId, component => resolve(component.validate())));
}
public setDataProvider(handle: number, componentId: string, context: any): any {
return this.queueAction(componentId, (component) => component.setDataProvider(handle, componentId, context));
}
}

View File

@@ -22,7 +22,7 @@ import { IBootstrapParams, ISelector, providerIterator } from 'sql/services/boot
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox.component';
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox.component';
import { EditableDropDown } from 'sql/base/browser/ui/editableDropdown/editabledropdown.component';
import { EditableDropDown } from 'sql/base/browser/ui/editableDropdown/editableDropdown.component';
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox.component';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';

View File

@@ -25,6 +25,8 @@ export interface IModelView extends IView {
addToContainer(containerId: string, item: IItemConfig): void;
setLayout(componentId: string, layout: any): void;
setProperties(componentId: string, properties: { [key: string]: any }): void;
setDataProvider(handle: number, componentId: string, context: any): void;
refreshDataProvider(componentId: string, item: any): void;
registerEvent(componentId: string);
onEvent: Event<IModelViewEventArgs>;
validate(componentId: string): Thenable<boolean>;

View File

@@ -27,6 +27,7 @@ declare module 'sqlops' {
text(): ComponentBuilder<TextComponent>;
button(): ComponentBuilder<ButtonComponent>;
dropDown(): ComponentBuilder<DropDownComponent>;
tree<T>(): ComponentBuilder<TreeComponent<T>>;
listBox(): ComponentBuilder<ListBoxComponent>;
table(): ComponentBuilder<TableComponent>;
declarativeTable(): ComponentBuilder<DeclarativeTableComponent>;
@@ -39,6 +40,17 @@ declare module 'sqlops' {
fileBrowserTree(): ComponentBuilder<FileBrowserTreeComponent>;
}
export interface TreeComponentDataProvider<T> extends vscode.TreeDataProvider<T> {
getTreeItem(element: T): TreeComponentItem | Thenable<TreeComponentItem>;
onNodeCheckedChanged?(element: T, checked: boolean): void;
}
export class TreeComponentItem extends vscode.TreeItem {
checked?: boolean;
}
export interface ComponentBuilder<T extends Component> {
component(): T;
withProperties<U>(properties: U): ComponentBuilder<T>;
@@ -370,7 +382,7 @@ declare module 'sqlops' {
}
export interface TableColumn {
value: string
value: string;
}
export interface TableComponentProperties extends ComponentProperties {
@@ -389,6 +401,10 @@ declare module 'sqlops' {
label?: string;
}
export interface TreeProperties {
withCheckbox?: boolean;
}
export enum DeclarativeDataType {
string = 'string',
category = 'category',
@@ -514,6 +530,10 @@ declare module 'sqlops' {
onDidChange: vscode.Event<any>;
}
export interface TreeComponent<T> extends Component, TreeProperties {
registerDataProvider<T>(dataProvider: TreeComponentDataProvider<T>): any;
}
export interface WebViewComponent extends Component {
html: string;
message: any;
@@ -534,7 +554,7 @@ declare module 'sqlops' {
languageMode: string;
}
export interface ButtonComponent extends Component, ButtonProperties {
export interface ButtonComponent extends Component, ButtonProperties {
label: string;
iconPath: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri };
onDidClick: vscode.Event<any>;

View File

@@ -4,6 +4,8 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TreeItem } from 'vs/workbench/api/node/extHostTypes';
// SQL added extension host types
export enum ServiceOptionType {
string = 'string',
@@ -146,6 +148,7 @@ export enum ModelComponentTypes {
Group,
Toolbar,
LoadingComponent,
TreeComponent,
FileBrowserTree,
Editor
}
@@ -281,6 +284,11 @@ export enum CardType {
VerticalButton = 'VerticalButton',
Details = 'Details'
}
export class TreeComponentItem extends TreeItem {
checked?: boolean;
}
export class SqlThemeIcon {
static readonly Folder = new SqlThemeIcon('Folder');

View File

@@ -13,14 +13,18 @@ import * as nls from 'vs/nls';
import * as vscode from 'vscode';
import * as sqlops from 'sqlops';
import { SqlMainContext, ExtHostModelViewShape, MainThreadModelViewShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
import { IItemConfig, ModelComponentTypes, IComponentShape, IComponentEventArgs, ComponentEventType} from 'sql/workbench/api/common/sqlExtHostTypes';
import { SqlMainContext, ExtHostModelViewShape, MainThreadModelViewShape, ExtHostModelViewTreeViewsShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
import { IItemConfig, ModelComponentTypes, IComponentShape, IComponentEventArgs, ComponentEventType } from 'sql/workbench/api/common/sqlExtHostTypes';
class ModelBuilderImpl implements sqlops.ModelBuilder {
private nextComponentId: number;
private readonly _componentBuilders = new Map<string, ComponentBuilderImpl<any>>();
constructor(private readonly _proxy: MainThreadModelViewShape, private readonly _handle: number) {
constructor(
private readonly _proxy: MainThreadModelViewShape,
private readonly _handle: number,
private readonly _mainContext: IMainContext,
private readonly _extHostModelViewTree: ExtHostModelViewTreeViewsShape) {
this.nextComponentId = 0;
}
@@ -66,6 +70,13 @@ class ModelBuilderImpl implements sqlops.ModelBuilder {
return builder;
}
tree<T>(): sqlops.ComponentBuilder<sqlops.TreeComponent<T>> {
let id = this.getNextComponentId();
let builder: ComponentBuilderImpl<sqlops.TreeComponent<T>> = this.getComponentBuilder(new TreeComponentWrapper(this._extHostModelViewTree, this._proxy, this._handle, id), id);
this._componentBuilders.set(id, builder);
return builder;
}
inputBox(): sqlops.ComponentBuilder<sqlops.InputBoxComponent> {
let id = this.getNextComponentId();
let builder: ComponentBuilderImpl<sqlops.InputBoxComponent> = this.getComponentBuilder(new InputBoxWrapper(this._proxy, this._handle, id), id);
@@ -500,6 +511,11 @@ class ComponentWrapper implements sqlops.Component {
}
}
protected setDataProvider(): Thenable<void> {
return this._proxy.$setDataProvider(this._handle, this._id);
}
protected async setProperty(key: string, value: any): Promise<void> {
if (!this.properties[key] || this.properties[key] !== value) {
// Only notify the front end if a value has been updated
@@ -1038,6 +1054,28 @@ class FileBrowserTreeComponentWrapper extends ComponentWrapper implements sqlops
}
}
class TreeComponentWrapper<T> extends ComponentWrapper implements sqlops.TreeComponent<T> {
constructor(
private _extHostModelViewTree: ExtHostModelViewTreeViewsShape,
proxy: MainThreadModelViewShape, handle: number, id: string) {
super(proxy, handle, ModelComponentTypes.TreeComponent, id);
this.properties = {};
}
public registerDataProvider<T>(dataProvider: sqlops.TreeComponentDataProvider<T>): vscode.TreeView<T> {
this.setDataProvider();
return this._extHostModelViewTree.$createTreeView(this._handle, this.id, { treeDataProvider: dataProvider });
}
public get withCheckbox(): boolean {
return this.properties['withCheckbox'];
}
public set withCheckbox(v: boolean) {
this.setProperty('withCheckbox', v);
}
}
class ModelViewImpl implements sqlops.ModelView {
public onClosedEmitter = new Emitter<any>();
@@ -1051,9 +1089,11 @@ class ModelViewImpl implements sqlops.ModelView {
private readonly _proxy: MainThreadModelViewShape,
private readonly _handle: number,
private readonly _connection: sqlops.connection.Connection,
private readonly _serverInfo: sqlops.ServerInfo
private readonly _serverInfo: sqlops.ServerInfo,
private readonly mainContext: IMainContext,
private readonly _extHostModelViewTree: ExtHostModelViewTreeViewsShape
) {
this._modelBuilder = new ModelBuilderImpl(this._proxy, this._handle);
this._modelBuilder = new ModelBuilderImpl(this._proxy, this._handle, this.mainContext, this._extHostModelViewTree);
}
public get onClosed(): vscode.Event<any> {
@@ -1106,9 +1146,10 @@ export class ExtHostModelView implements ExtHostModelViewShape {
private readonly _handlers = new Map<string, (view: sqlops.ModelView) => void>();
constructor(
mainContext: IMainContext
private _mainContext: IMainContext,
private _extHostModelViewTree: ExtHostModelViewTreeViewsShape
) {
this._proxy = mainContext.getProxy(SqlMainContext.MainThreadModelView);
this._proxy = _mainContext.getProxy(SqlMainContext.MainThreadModelView);
}
$onClosed(handle: number): void {
@@ -1123,7 +1164,7 @@ export class ExtHostModelView implements ExtHostModelViewShape {
}
$registerWidget(handle: number, id: string, connection: sqlops.connection.Connection, serverInfo: sqlops.ServerInfo): void {
let view = new ModelViewImpl(this._proxy, handle, connection, serverInfo);
let view = new ModelViewImpl(this._proxy, handle, connection, serverInfo, this._mainContext, this._extHostModelViewTree);
this._modelViews.set(handle, view);
this._handlers.get(id)(view);
}

View File

@@ -0,0 +1,140 @@
/*---------------------------------------------------------------------------------------------
* 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 { localize } from 'vs/nls';
import * as vscode from 'vscode';
import { TPromise } from 'vs/base/common/winjs.base';
import { SqlMainContext, ExtHostModelViewTreeViewsShape, MainThreadModelViewShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
import { ITreeComponentItem } from 'sql/workbench/common/views';
import { CommandsConverter } from 'vs/workbench/api/node/extHostCommands';
import { asWinJsPromise } from 'vs/base/common/async';
import { IMainContext } from 'vs/workbench/api/node/extHost.protocol';
import * as sqlops from 'sqlops';
import * as vsTreeExt from 'vs/workbench/api/node/extHostTreeViews';
export class ExtHostModelViewTreeViews implements ExtHostModelViewTreeViewsShape {
private _proxy: MainThreadModelViewShape;
private treeViews: Map<string, ExtHostTreeView<any>> = new Map<string, ExtHostTreeView<any>>();
constructor(
private _mainContext: IMainContext
) {
this._proxy = this._mainContext.getProxy(SqlMainContext.MainThreadModelView);
}
$createTreeView<T>(handle: number, componentId: string, options: { treeDataProvider: sqlops.TreeComponentDataProvider<T> }): vscode.TreeView<T> {
if (!options || !options.treeDataProvider) {
throw new Error('Options with treeDataProvider is mandatory');
}
const treeView = this.createExtHostTreeViewer(handle, componentId, options.treeDataProvider);
return {
reveal: (element: T, options?: { select?: boolean }): Thenable<void> => {
return treeView.reveal(element, options);
},
dispose: () => {
this.treeViews.delete(componentId);
treeView.dispose();
}
};
}
$getChildren(treeViewId: string, treeItemHandle?: string): TPromise<ITreeComponentItem[]> {
const treeView = this.treeViews.get(treeViewId);
if (!treeView) {
return TPromise.wrapError<ITreeComponentItem[]>(new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId)));
}
return treeView.getChildren(treeItemHandle);
}
$onNodeCheckedChanged(treeViewId: string, treeItemHandle?: string, checked?: boolean): void {
const treeView = this.treeViews.get(treeViewId);
if (treeView) {
treeView.onNodeCheckedChanged(treeItemHandle, checked);
}
}
private createExtHostTreeViewer<T>(handle: number, id: string, dataProvider: sqlops.TreeComponentDataProvider<T>): ExtHostTreeView<T> {
const treeView = new ExtHostTreeView<T>(handle, id, dataProvider, this._proxy, undefined);
this.treeViews.set(`${handle}-${id}`, treeView);
return treeView;
}
}
export class ExtHostTreeView<T> extends vsTreeExt.ExtHostTreeView<T> {
constructor(private handle: number, private componentId: string, private componentDataProvider: sqlops.TreeComponentDataProvider<T>, private modelViewProxy: MainThreadModelViewShape, commands: CommandsConverter) {
super(componentId, componentDataProvider, undefined, commands);
}
onNodeCheckedChanged(parentHandle?: vsTreeExt.TreeItemHandle, checked?: boolean): void {
const parentElement = parentHandle ? this.getExtensionElement(parentHandle) : void 0;
if (parentHandle && !parentElement) {
console.error(`No tree item with id \'${parentHandle}\' found.`);
}
this.componentDataProvider.onNodeCheckedChanged(parentElement, checked);
}
reveal(element: T, options?: { select?: boolean }): TPromise<void> {
if (typeof this.componentDataProvider.getParent !== 'function') {
return TPromise.wrapError(new Error(`Required registered TreeDataProvider to implement 'getParent' method to access 'reveal' method`));
}
let i: void;
return this.resolveUnknownParentChain(element)
.then(parentChain => this.resolveTreeNode(element, parentChain[parentChain.length - 1])
.then(treeNode => i));
}
protected refresh(elements: T[]): void {
const hasRoot = elements.some(element => !element);
if (hasRoot) {
this.clearAll(); // clear cache
this.modelViewProxy.$refreshDataProvider(this.handle, this.componentId);
} else {
const handlesToRefresh = this.getHandlesToRefresh(elements);
if (handlesToRefresh.length) {
this.refreshHandles(handlesToRefresh);
}
}
}
protected refreshHandles(itemHandles: vsTreeExt.TreeItemHandle[]): TPromise<void> {
const itemsToRefresh: { [treeItemHandle: string]: ITreeComponentItem } = {};
return TPromise.join(itemHandles.map(treeItemHandle =>
this.refreshNode(treeItemHandle)
.then(node => {
if (node) {
itemsToRefresh[treeItemHandle] = node.item;
}
})))
.then(() => Object.keys(itemsToRefresh).length ? this.modelViewProxy.$refreshDataProvider(this.handle, this.componentId, itemsToRefresh) : null);
}
protected refreshNode(treeItemHandle: vsTreeExt.TreeItemHandle): TPromise<vsTreeExt.TreeNode> {
const extElement = this.getExtensionElement(treeItemHandle);
const existing = this.nodes.get(extElement);
//this.clearChildren(extElement); // clear children cache
return asWinJsPromise(() => this.componentDataProvider.getTreeItem(extElement))
.then(extTreeItem => {
if (extTreeItem) {
const newNode = this.createTreeNode(extElement, extTreeItem, existing.parent);
this.updateNodeCache(extElement, newNode, existing, existing.parent);
return newNode;
}
return null;
});
}
protected createTreeItem(element: T, extensionTreeItem: sqlops.TreeComponentItem, parent?: vsTreeExt.TreeNode): ITreeComponentItem {
let item = super.createTreeItem(element, extensionTreeItem, parent);
item = Object.assign({}, item, { checked: extensionTreeItem.checked });
return item;
}
}

View File

@@ -9,10 +9,9 @@ import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostC
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
import { Disposable } from 'vs/base/common/lifecycle';
import * as sqlops from 'sqlops';
import { IModelViewService } from 'sql/services/modelComponents/modelViewService';
import { IItemConfig, ModelComponentTypes, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
import { IItemConfig, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
import { IModelView } from 'sql/services/model/modelViewService';
@@ -22,15 +21,14 @@ export class MainThreadModelView extends Disposable implements MainThreadModelVi
private static _handlePool = 0;
private readonly _proxy: ExtHostModelViewShape;
private readonly _dialogs = new Map<number, IModelView>();
private knownWidgets = new Array<string>();
constructor(
context: IExtHostContext,
private _context: IExtHostContext,
@IModelViewService viewService: IModelViewService
) {
super();
this._proxy = context.getProxy(SqlExtHostContext.ExtHostModelView);
this._proxy = _context.getProxy(SqlExtHostContext.ExtHostModelView);
viewService.onRegisteredModelView(view => {
if (this.knownWidgets.includes(view.id)) {
let handle = MainThreadModelView._handlePool++;
@@ -79,6 +77,14 @@ export class MainThreadModelView extends Disposable implements MainThreadModelVi
});
}
$setDataProvider(handle: number, componentId: string): Thenable<void> {
return this.execModelViewAction(handle, (modelView) => modelView.setDataProvider(handle, componentId, this._context));
}
$refreshDataProvider(handle: number, componentId: string, item?: any): Thenable<void> {
return this.execModelViewAction(handle, (modelView) => modelView.refreshDataProvider(componentId, item));
}
$setProperties(handle: number, componentId: string, properties: { [key: string]: any; }): Thenable<void> {
return this.execModelViewAction(handle, (modelView) => modelView.setProperties(componentId, properties));
}

View File

@@ -33,6 +33,7 @@ import { ExtHostDashboard } from 'sql/workbench/api/node/extHostDashboard';
import { ExtHostObjectExplorer } from 'sql/workbench/api/node/extHostObjectExplorer';
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
import { ExtHostModelViewDialog } from 'sql/workbench/api/node/extHostModelViewDialog';
import { ExtHostModelViewTreeViews } from 'sql/workbench/api/node/extHostModelViewTree';
import { ExtHostQueryEditor } from 'sql/workbench/api/node/extHostQueryEditor';
import { ExtHostBackgroundTaskManagement } from './extHostBackgroundTaskManagement';
@@ -66,7 +67,8 @@ export function createApiFactory(
const extHostTasks = rpcProtocol.set(SqlExtHostContext.ExtHostTasks, new ExtHostTasks(rpcProtocol, logService));
const extHostBackgroundTaskManagement = rpcProtocol.set(SqlExtHostContext.ExtHostBackgroundTaskManagement, new ExtHostBackgroundTaskManagement(rpcProtocol));
const extHostWebviewWidgets = rpcProtocol.set(SqlExtHostContext.ExtHostDashboardWebviews, new ExtHostDashboardWebviews(rpcProtocol));
const extHostModelView = rpcProtocol.set(SqlExtHostContext.ExtHostModelView, new ExtHostModelView(rpcProtocol));
const extHostModelViewTree = rpcProtocol.set(SqlExtHostContext.ExtHostModelViewTreeViews, new ExtHostModelViewTreeViews(rpcProtocol));
const extHostModelView = rpcProtocol.set(SqlExtHostContext.ExtHostModelView, new ExtHostModelView(rpcProtocol, extHostModelViewTree));
const extHostDashboard = rpcProtocol.set(SqlExtHostContext.ExtHostDashboard, new ExtHostDashboard(rpcProtocol));
const extHostModelViewDialog = rpcProtocol.set(SqlExtHostContext.ExtHostModelViewDialog, new ExtHostModelViewDialog(rpcProtocol, extHostModelView, extHostBackgroundTaskManagement));
const extHostQueryEditor = rpcProtocol.set(SqlExtHostContext.ExtHostQueryEditor, new ExtHostQueryEditor(rpcProtocol));
@@ -415,7 +417,8 @@ export function createApiFactory(
ui: ui,
StatusIndicator: sqlExtHostTypes.StatusIndicator,
CardType: sqlExtHostTypes.CardType,
SqlThemeIcon: sqlExtHostTypes.SqlThemeIcon
SqlThemeIcon: sqlExtHostTypes.SqlThemeIcon,
TreeComponentItem: sqlExtHostTypes.TreeComponentItem
};
}
};

View File

@@ -16,6 +16,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import { ITreeComponentItem } from 'sql/workbench/common/views';
import { ITaskHandlerDescription } from 'sql/platform/tasks/common/tasks';
import {
IItemConfig, ModelComponentTypes, IComponentShape, IModelViewDialogDetails, IModelViewTabDetails, IModelViewButtonDetails,
@@ -531,6 +532,7 @@ export const SqlExtHostContext = {
ExtHostBackgroundTaskManagement: createExtId<ExtHostBackgroundTaskManagementShape>('ExtHostBackgroundTaskManagement'),
ExtHostDashboardWebviews: createExtId<ExtHostDashboardWebviewsShape>('ExtHostDashboardWebviews'),
ExtHostModelView: createExtId<ExtHostModelViewShape>('ExtHostModelView'),
ExtHostModelViewTreeViews: createExtId<ExtHostModelViewTreeViewsShape>('ExtHostModelViewTreeViews'),
ExtHostDashboard: createExtId<ExtHostDashboardShape>('ExtHostDashboard'),
ExtHostModelViewDialog: createExtId<ExtHostModelViewDialogShape>('ExtHostModelViewDialog'),
ExtHostQueryEditor: createExtId<ExtHostQueryEditorShape>('ExtHostQueryEditor')
@@ -590,6 +592,12 @@ export interface ExtHostModelViewShape {
$runCustomValidations(handle: number, id: string): Thenable<boolean>;
}
export interface ExtHostModelViewTreeViewsShape {
$getChildren(treeViewId: string, treeItemHandle?: string): TPromise<ITreeComponentItem[]>;
$createTreeView(handle: number, componentId: string, options: { treeDataProvider: vscode.TreeDataProvider<any> }): vscode.TreeView<any>;
$onNodeCheckedChanged(treeViewId: string, treeItemHandle?: string, checked?: boolean): void;
}
export interface ExtHostBackgroundTaskManagementShape {
$onTaskRegistered(operationId: string): void;
$onTaskCanceled(operationId: string): void;
@@ -611,6 +619,8 @@ export interface MainThreadModelViewShape extends IDisposable {
$setProperties(handle: number, componentId: string, properties: { [key: string]: any }): Thenable<void>;
$registerEvent(handle: number, componentId: string): Thenable<void>;
$validate(handle: number, componentId: string): Thenable<boolean>;
$setDataProvider(handle: number, componentId: string): Thenable<void>;
$refreshDataProvider(handle: number, componentId: string, item?: any): Thenable<void>;
}
export interface ExtHostObjectExplorerShape {

View File

@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TPromise } from 'vs/base/common/winjs.base';
import { Event } from 'vs/base/common/event';
import { ITreeViewDataProvider, ITreeItem } from 'vs/workbench/common/views';
export interface ITreeComponentItem extends ITreeItem {
checked?: boolean;
onCheckedChanged?: (checked: boolean) => void;
children?: ITreeComponentItem[];
}
export interface IModelViewTreeViewDataProvider extends ITreeViewDataProvider {
refresh(itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeComponentItem });
}
export interface IModelViewTreeViewDataProvider {
onDidChange: Event<ITreeComponentItem[] | undefined | null>;
onDispose: Event<void>;
getChildren(element?: ITreeComponentItem): TPromise<ITreeComponentItem[]>;
}