提交 26ebe97d 编写于 作者: J Joao Moreno

data tree: first steps

上级 90a6b23b
......@@ -15,13 +15,13 @@ import { ITreeModel, ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { ISpliceable } from 'vs/base/common/sequence';
import { IIndexTreeModelOptions } from 'vs/base/browser/ui/tree/indexTreeModel';
function toTreeListOptions<T>(options?: IListOptions<T>): IListOptions<ITreeNode<T, any>> {
export function createComposedTreeListOptions<T, N extends { element: T }>(options?: IListOptions<T>): IListOptions<N> {
if (!options) {
return undefined;
}
let identityProvider: IIdentityProvider<ITreeNode<T, any>> | undefined = undefined;
let multipleSelectionController: IMultipleSelectionController<ITreeNode<T, any>> | undefined = undefined;
let identityProvider: IIdentityProvider<N> | undefined = undefined;
let multipleSelectionController: IMultipleSelectionController<N> | undefined = undefined;
if (options.identityProvider) {
identityProvider = el => options.identityProvider(el.element);
......@@ -45,15 +45,15 @@ function toTreeListOptions<T>(options?: IListOptions<T>): IListOptions<ITreeNode
};
}
class TreeDelegate<T> implements IVirtualDelegate<ITreeNode<T, any>> {
export class ComposedTreeDelegate<T, N extends { element: T }> implements IVirtualDelegate<N> {
constructor(private delegate: IVirtualDelegate<T>) { }
getHeight(element: ITreeNode<T, any>): number {
getHeight(element: N): number {
return this.delegate.getHeight(element.element);
}
getTemplateId(element: ITreeNode<T, any>): string {
getTemplateId(element: N): string {
return this.delegate.getTemplateId(element.element);
}
}
......@@ -149,13 +149,13 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
renderers: IRenderer<T, any>[],
options?: ITreeOptions<T, TFilterData>
) {
const treeDelegate = new TreeDelegate(delegate);
const treeDelegate = new ComposedTreeDelegate<T, ITreeNode<T, TFilterData>>(delegate);
const onDidChangeCollapseStateRelay = new Relay<ITreeNode<T, TFilterData>>();
const treeRenderers = renderers.map(r => new TreeRenderer(r, onDidChangeCollapseStateRelay.event));
const treeRenderers = renderers.map(r => new TreeRenderer<T, TFilterData, any>(r, onDidChangeCollapseStateRelay.event));
this.disposables.push(...treeRenderers);
this.view = new List(container, treeDelegate, treeRenderers, toTreeListOptions(options));
this.view = new List(container, treeDelegate, treeRenderers, createComposedTreeListOptions<T, ITreeNode<T, TFilterData>>(options));
this.model = this.createModel(this.view, options);
onDidChangeCollapseStateRelay.input = this.model.onDidChangeCollapseState;
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITreeOptions, ComposedTreeDelegate, createComposedTreeListOptions } from 'vs/base/browser/ui/tree/abstractTree';
import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
import { IVirtualDelegate, IRenderer } from 'vs/base/browser/ui/list/list';
import { ITreeElement } from 'vs/base/browser/ui/tree/tree';
export interface IDataTreeElement<T> {
readonly element: T;
readonly collapsible?: boolean;
readonly collapsed?: boolean;
}
export interface IDataSource<T extends NonNullable<any>> {
hasChildren(element: T | null): boolean;
getChildren(element: T | null): Thenable<IDataTreeElement<T>[]>;
}
enum DataTreeNodeState {
Idle,
Loading
}
interface IDataTreeNode<T extends NonNullable<any>> {
readonly element: T;
readonly parent: IDataTreeNode<T> | null;
state: DataTreeNodeState;
// promise: Thenable<any>;
}
interface IDataTreeListTemplateData<T> {
templateData: T;
}
class DataTreeRenderer<T, TTemplateData> implements IRenderer<IDataTreeNode<T>, IDataTreeListTemplateData<TTemplateData>> {
readonly templateId: string;
constructor(private renderer: IRenderer<T, TTemplateData>) {
this.templateId = renderer.templateId;
}
renderTemplate(container: HTMLElement): IDataTreeListTemplateData<TTemplateData> {
const templateData = this.renderer.renderTemplate(container);
return { templateData };
}
renderElement(node: IDataTreeNode<T>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>): void {
this.renderer.renderElement(node.element, index, templateData.templateData);
}
disposeElement(node: IDataTreeNode<T>): void {
// noop
}
disposeTemplate(templateData: IDataTreeListTemplateData<TTemplateData>): void {
this.renderer.disposeTemplate(templateData.templateData);
}
}
export class DataTree<T extends NonNullable<any>, TFilterData = void> {
private tree: ObjectTree<IDataTreeNode<T>, TFilterData>;
private root: IDataTreeNode<T>;
private nodes = new Map<T, IDataTreeNode<T>>();
constructor(
container: HTMLElement,
delegate: IVirtualDelegate<T>,
renderers: IRenderer<T, any>[],
private dataSource: IDataSource<T>,
options?: ITreeOptions<T, TFilterData>
) {
const treeDelegate = new ComposedTreeDelegate<T, IDataTreeNode<T>>(delegate);
const treeRenderers = renderers.map(r => new DataTreeRenderer(r));
const treeOptions = createComposedTreeListOptions<T, IDataTreeNode<T>>(options);
this.tree = new ObjectTree(container, treeDelegate, treeRenderers, treeOptions);
this.root = {
element: undefined, // TODO@joao
parent: null,
state: DataTreeNodeState.Idle,
};
this.nodes.set(null, this.root);
}
refresh(element: T | null, recursive = true): Thenable<void> {
const node: IDataTreeNode<T> = this.nodes.get(element);
if (typeof node === 'undefined') {
throw new Error(`Data tree node not found: ${element}`);
}
const hasChildren = this.dataSource.hasChildren(element);
if (!hasChildren) {
this.tree.setChildren(node === this.root ? null : node);
return Promise.resolve(null);
} else {
node.state = DataTreeNodeState.Loading;
return this.dataSource.getChildren(element)
.then(children => {
node.state = DataTreeNodeState.Idle;
const createTreeElement = (el: IDataTreeElement<T>): ITreeElement<IDataTreeNode<T>> => {
return {
element: {
element: el.element,
state: DataTreeNodeState.Idle,
parent: node
},
collapsible: el.collapsible,
collapsed: typeof el.collapsed === 'boolean' ? el.collapsed : true
};
};
const nodeChildren = children.map<ITreeElement<IDataTreeNode<T>>>(createTreeElement);
this.tree.setChildren(node === this.root ? null : node, nodeChildren);
}, err => {
node.state = DataTreeNodeState.Idle;
return Promise.reject(err);
});
}
}
}
\ No newline at end of file
......@@ -10,7 +10,7 @@ import { ISpliceable } from 'vs/base/common/sequence';
import { ITreeNode, ITreeModel, ITreeElement } from 'vs/base/browser/ui/tree/tree';
import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
export class ObjectTree<T, TFilterData = void> extends AbstractTree<T, TFilterData, ITreeNode<T, TFilterData>> {
export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends AbstractTree<T, TFilterData, ITreeNode<T, TFilterData>> {
protected model: ObjectTreeModel<T, TFilterData>;
......
......@@ -37,56 +37,118 @@
require.config({ baseUrl: '/static' });
require(['vs/base/browser/ui/tree/indexTree', 'vs/base/browser/ui/tree/tree', 'vs/base/common/iterator'], ({ IndexTree }, { TreeVisibility }, { iter }) => {
const delegate = {
getHeight() { return 22; },
getTemplateId() { return 'template'; }
};
const renderer = {
templateId: 'template',
renderTemplate(container) { return container; },
renderElement(element, index, container) {
container.textContent = element;
},
disposeTemplate() { }
};
const treeFilter = new class {
constructor() {
this.pattern = null;
let timeout;
filter.oninput = () => {
clearTimeout(timeout);
timeout = setTimeout(() => this.updatePattern(), 300);
};
}
updatePattern() {
if (!filter.value) {
require(['vs/base/browser/ui/tree/indexTree', 'vs/base/browser/ui/tree/dataTree', 'vs/base/browser/ui/tree/tree', 'vs/base/common/iterator'], ({ IndexTree }, { DataTree }, { TreeVisibility }, { iter }) => {
function createIndexTree() {
const delegate = {
getHeight() { return 22; },
getTemplateId() { return 'template'; }
};
const renderer = {
templateId: 'template',
renderTemplate(container) { return container; },
renderElement(element, index, container) {
container.textContent = element;
},
disposeTemplate() { }
};
const treeFilter = new class {
constructor() {
this.pattern = null;
} else {
this.pattern = new RegExp(filter.value, 'i');
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) : true) ? TreeVisibility.Visible : TreeVisibility.Recurse;
perf('refilter', () => tree.refilter());
}
filter(el) {
return (this.pattern ? this.pattern.test(el) : true) ? TreeVisibility.Visible : TreeVisibility.Recurse;
}
};
const tree = new IndexTree(container, delegate, [renderer], { filter: treeFilter });
return { tree, treeFilter };
}
function createDataTree() {
const delegate = {
getHeight() { return 22; },
getTemplateId() { return 'template'; }
};
const renderer = {
templateId: 'template',
renderTemplate(container) { return container; },
renderElement(element, index, container) { container.textContent = element.name; },
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 {
hasChildren(element) {
return element === null || element.type === 'dir';
}
getChildren(element) {
return new Promise((c, e) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', element ? `/api/readdir?path=${element.path}` : '/api/readdir');
xhr.send();
xhr.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
const els = JSON.parse(this.responseText).map(element => ({
element,
collapsible: element.type === 'dir'
}));
c(els);
}
};
});
}
}
};
const tree = new IndexTree(container, delegate, [renderer], { filter: treeFilter });
const tree = new DataTree(container, delegate, [renderer], dataSource, { filter: treeFilter });
function setModel(model) {
performance.mark('before splice');
const start = performance.now();
;
console.log('splice took', performance.now() - start);
performance.mark('after splice');
return { tree, treeFilter };
}
switch (location.search) {
case '?problems': {
const { tree, treeFilter } = createIndexTree();
const files = [];
for (let i = 0; i < 10000; i++) {
const errors = [];
......@@ -101,7 +163,16 @@
perf('splice', () => tree.splice([0], 0, files));
break;
}
case '?data': {
const { tree, treeFilter } = createDataTree();
tree.refresh(null);
break;
}
default:
const { tree, treeFilter } = createIndexTree();
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/ls?path=');
xhr.send();
......
......@@ -26,6 +26,29 @@ async function getTree(fsPath, level) {
return { element, collapsible: true, collapsed: false, children };
}
async function readdir(relativePath) {
const absolutePath = relativePath ? path.join(root, relativePath) : root;
const childNames = await fs.readdir(absolutePath);
const childStats = await Promise.all(childNames.map(async name => await fs.stat(path.join(absolutePath, name))));
const result = [];
for (let i = 0; i < childNames.length; i++) {
const name = childNames[i];
const path = relativePath ? `${relativePath}/${name}` : name;
const stat = childStats[i];
if (stat.isFile()) {
result.push({ type: 'file', name, path });
} else if (!stat.isDirectory() || name === '.git' || name === '.build') {
continue;
} else {
result.push({ type: 'dir', name, path });
}
}
return result;
}
app.use(serve('public'));
app.use(mount('/static', serve('../../out')));
app.use(_.get('/api/ls', async ctx => {
......@@ -33,7 +56,13 @@ app.use(_.get('/api/ls', async ctx => {
const absolutePath = path.join(root, relativePath);
ctx.body = await getTree(absolutePath, 0);
}))
}));
app.use(_.get('/api/readdir', async ctx => {
const relativePath = ctx.query.path;
ctx.body = await readdir(relativePath);
}));
app.listen(3000);
console.log('http://localhost:3000');
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册