diff --git a/src/vs/base/browser/ui/tree/dataTree.ts b/src/vs/base/browser/ui/tree/dataTree.ts new file mode 100644 index 0000000000000..6684ef46bba46 --- /dev/null +++ b/src/vs/base/browser/ui/tree/dataTree.ts @@ -0,0 +1,60 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree'; +import { ISpliceable } from 'vs/base/common/sequence'; +import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, IDataSource } from 'vs/base/browser/ui/tree/tree'; +import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel'; +import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { Iterator } from 'vs/base/common/iterator'; + +export interface IDataTreeOptions extends IAbstractTreeOptions { + sorter?: ITreeSorter; +} + +export class DataTree extends AbstractTree { + + protected model: ObjectTreeModel; + + private _input: TInput | undefined; + + get input(): TInput | undefined { + return this._input; + } + + set input(input: TInput | undefined) { + this._input = input; + this.refresh(input); + } + + constructor( + container: HTMLElement, + delegate: IListVirtualDelegate, + renderers: ITreeRenderer[], + private dataSource: IDataSource, + options: IDataTreeOptions = {} + ) { + super(container, delegate, renderers, options); + } + + refresh(element: TInput | T): void { + if (!this._input) { + throw new Error('Tree input not set'); + } + + this.model.setChildren((element === this.input ? null : element) as T, this.createIterator(element)); + } + + private createIterator(element: TInput | T): Iterator> { + return Iterator.map(Iterator.fromArray(this.dataSource.getChildren(element)), element => ({ + element, + children: this.createIterator(element) + })); + } + + protected createModel(view: ISpliceable>, options: IDataTreeOptions): ITreeModel { + return new ObjectTreeModel(view, options); + } +} \ No newline at end of file diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index ce23ee060bacc..1f2b347aab413 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -145,6 +145,10 @@ export interface ITreeNavigator { next(): T | null; } +export interface IDataSource { + getChildren(element: TInput | T): T[]; +} + export interface IAsyncDataSource> { hasChildren(element: T | null): boolean; getChildren(element: T | null): T[] | Promise; diff --git a/test/tree/public/index.html b/test/tree/public/index.html index 5ef47d5f729ad..ab846c91e15a7 100644 --- a/test/tree/public/index.html +++ b/test/tree/public/index.html @@ -44,7 +44,7 @@ require.config({ baseUrl: '/static' }); - require(['vs/base/browser/ui/tree/indexTree', 'vs/base/browser/ui/tree/asyncDataTree', 'vs/base/browser/ui/tree/tree', 'vs/base/common/iterator'], ({ IndexTree }, { AsyncDataTree }, { TreeVisibility }, { iter }) => { + require(['vs/base/browser/ui/tree/indexTree', 'vs/base/browser/ui/tree/asyncDataTree', 'vs/base/browser/ui/tree/dataTree', 'vs/base/browser/ui/tree/tree', 'vs/base/common/iterator'], ({ IndexTree }, { AsyncDataTree }, { DataTree }, { TreeVisibility }, { iter }) => { function createIndexTree(opts) { opts = opts || {}; @@ -182,6 +182,69 @@ return { tree, treeFilter }; } + function createDataTree() { + const delegate = { + getHeight() { return 22; }, + getTemplateId() { return 'template'; } + }; + + const renderer = { + templateId: 'template', + renderTemplate(container) { return container; }, + renderElement(node, index, container) { container.textContent = node.element.name; }, + disposeElement() { }, + disposeTemplate() { } + }; + + const treeFilter = new class { + constructor() { + this.pattern = null; + let timeout; + filter.oninput = () => { + clearTimeout(timeout); + timeout = setTimeout(() => this.updatePattern(), 300); + }; + } + + updatePattern() { + if (!filter.value) { + this.pattern = null; + } else { + this.pattern = new RegExp(filter.value, 'i'); + } + + perf('refilter', () => tree.refilter()); + } + filter(el) { + return (this.pattern ? this.pattern.test(el.name) : true) ? TreeVisibility.Visible : TreeVisibility.Recurse; + } + }; + + const dataSource = new class { + getChildren(element) { + return element.children || []; + } + }; + + const identityProvider = { + getId(node) { + return node.name; + } + }; + + const tree = new DataTree(container, delegate, [renderer], dataSource, { filter: treeFilter, identityProvider }); + + tree.input = { + children: [ + { name: 'A', children: [{ name: 'AA' }, { name: 'AB' }] }, + { name: 'B', children: [{ name: 'BA', children: [{ name: 'BAA' }] }, { name: 'BB' }] }, + { name: 'C' } + ] + }; + + return { tree, treeFilter }; + } + switch (location.search) { case '?problems': { const { tree, treeFilter } = createIndexTree(); @@ -216,6 +279,16 @@ break; } + case '?objectdata': { + const { tree, treeFilter } = createDataTree(); + + expandall.onclick = () => perf('expand all', () => tree.expandAll()); + collapseall.onclick = () => perf('collapse all', () => tree.collapseAll()); + renderwidth.onclick = () => perf('renderwidth', () => tree.layoutWidth(Math.random())); + refresh.onclick = () => perf('refresh', () => tree.refresh(null, true)); + + break; + } case '?height': { const { tree, treeFilter } = createIndexTree({ supportDynamicHeights: true });