mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-15 18:46:36 -05:00
Feature/tree component (#2077)
*added tree component to the model builder
This commit is contained in:
148
src/sql/parts/modelComponents/tree/tree.component.ts
Normal file
148
src/sql/parts/modelComponents/tree/tree.component.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
8
src/sql/parts/modelComponents/tree/treeComponent.css
Normal file
8
src/sql/parts/modelComponents/tree/treeComponent.css
Normal 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;
|
||||
}
|
||||
168
src/sql/parts/modelComponents/tree/treeComponentRenderer.ts
Normal file
168
src/sql/parts/modelComponents/tree/treeComponentRenderer.ts
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
57
src/sql/parts/modelComponents/tree/treeDataSource.ts
Normal file
57
src/sql/parts/modelComponents/tree/treeDataSource.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
39
src/sql/parts/modelComponents/tree/treeViewDataProvider.ts
Normal file
39
src/sql/parts/modelComponents/tree/treeViewDataProvider.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user