AsyncDataTree<TInput...

related to #65091
This commit is contained in:
Joao Moreno
2018-12-17 15:42:26 +01:00
parent 8c09d4f476
commit 419d5bbaed
15 changed files with 211 additions and 203 deletions

View File

@@ -21,11 +21,11 @@ enum AsyncDataTreeNodeState {
Slow
}
interface IAsyncDataTreeNode<T extends NonNullable<any>> {
element: T | null;
readonly parent: IAsyncDataTreeNode<T> | null;
interface IAsyncDataTreeNode<TInput, T> {
element: TInput | T;
readonly parent: IAsyncDataTreeNode<TInput, T> | null;
readonly id?: string | null;
readonly children?: IAsyncDataTreeNode<T>[];
readonly children?: IAsyncDataTreeNode<TInput, T>[];
state: AsyncDataTreeNodeState;
}
@@ -33,9 +33,9 @@ interface IDataTreeListTemplateData<T> {
templateData: T;
}
class AsyncDataTreeNodeWrapper<T, TFilterData> implements ITreeNode<T, TFilterData> {
class AsyncDataTreeNodeWrapper<TInput, T, TFilterData> implements ITreeNode<TInput | T, TFilterData> {
get element(): T { return this.node.element!.element!; }
get element(): T { return this.node.element!.element as T; }
get parent(): ITreeNode<T, TFilterData> | undefined { return this.node.parent && new AsyncDataTreeNodeWrapper(this.node.parent); }
get children(): ITreeNode<T, TFilterData>[] { return this.node.children.map(node => new AsyncDataTreeNodeWrapper(node)); }
get depth(): number { return this.node.depth; }
@@ -44,18 +44,18 @@ class AsyncDataTreeNodeWrapper<T, TFilterData> implements ITreeNode<T, TFilterDa
get visible(): boolean { return this.node.visible; }
get filterData(): TFilterData | undefined { return this.node.filterData; }
constructor(private node: ITreeNode<IAsyncDataTreeNode<T> | null, TFilterData>) { }
constructor(private node: ITreeNode<IAsyncDataTreeNode<TInput, T> | null, TFilterData>) { }
}
class DataTreeRenderer<T, TFilterData, TTemplateData> implements ITreeRenderer<IAsyncDataTreeNode<T>, TFilterData, IDataTreeListTemplateData<TTemplateData>> {
class DataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ITreeRenderer<IAsyncDataTreeNode<TInput, T>, TFilterData, IDataTreeListTemplateData<TTemplateData>> {
readonly templateId: string;
private renderedNodes = new Map<IAsyncDataTreeNode<T>, IDataTreeListTemplateData<TTemplateData>>();
private renderedNodes = new Map<IAsyncDataTreeNode<TInput, T>, IDataTreeListTemplateData<TTemplateData>>();
private disposables: IDisposable[] = [];
constructor(
private renderer: ITreeRenderer<T, TFilterData, TTemplateData>,
readonly onDidChangeTwistieState: Event<IAsyncDataTreeNode<T>>
readonly onDidChangeTwistieState: Event<IAsyncDataTreeNode<TInput, T>>
) {
this.templateId = renderer.templateId;
}
@@ -65,16 +65,16 @@ class DataTreeRenderer<T, TFilterData, TTemplateData> implements ITreeRenderer<I
return { templateData };
}
renderElement(node: ITreeNode<IAsyncDataTreeNode<T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>): void {
renderElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>): void {
this.renderer.renderElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData);
}
renderTwistie(element: IAsyncDataTreeNode<T>, twistieElement: HTMLElement): boolean {
renderTwistie(element: IAsyncDataTreeNode<TInput, T>, twistieElement: HTMLElement): boolean {
toggleClass(twistieElement, 'loading', element.state === AsyncDataTreeNodeState.Slow);
return false;
}
disposeElement(node: ITreeNode<IAsyncDataTreeNode<T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>): void {
disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>): void {
if (this.renderer.disposeElement) {
this.renderer.disposeElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData);
}
@@ -90,24 +90,24 @@ class DataTreeRenderer<T, TFilterData, TTemplateData> implements ITreeRenderer<I
}
}
function asTreeEvent<T>(e: ITreeEvent<IAsyncDataTreeNode<T>>): ITreeEvent<T> {
function asTreeEvent<TInput, T>(e: ITreeEvent<IAsyncDataTreeNode<TInput, T>>): ITreeEvent<T> {
return {
browserEvent: e.browserEvent,
elements: e.elements.map(e => e.element!)
elements: e.elements.map(e => e.element as T)
};
}
function asTreeMouseEvent<T>(e: ITreeMouseEvent<IAsyncDataTreeNode<T>>): ITreeMouseEvent<T> {
function asTreeMouseEvent<TInput, T>(e: ITreeMouseEvent<IAsyncDataTreeNode<TInput, T>>): ITreeMouseEvent<T> {
return {
browserEvent: e.browserEvent,
element: e.element && e.element.element!
element: e.element && e.element.element as T
};
}
function asTreeContextMenuEvent<T>(e: ITreeContextMenuEvent<IAsyncDataTreeNode<T>>): ITreeContextMenuEvent<T> {
function asTreeContextMenuEvent<TInput, T>(e: ITreeContextMenuEvent<IAsyncDataTreeNode<TInput, T>>): ITreeContextMenuEvent<T> {
return {
browserEvent: e.browserEvent,
element: e.element && e.element.element!,
element: e.element && e.element.element as T,
anchor: e.anchor
};
}
@@ -122,12 +122,12 @@ export interface IChildrenResolutionEvent<T> {
readonly reason: ChildrenResolutionReason;
}
function asObjectTreeOptions<T, TFilterData>(options?: IAsyncDataTreeOptions<T, TFilterData>): IObjectTreeOptions<IAsyncDataTreeNode<T>, TFilterData> | undefined {
function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOptions<T, TFilterData>): IObjectTreeOptions<IAsyncDataTreeNode<TInput, T>, TFilterData> | undefined {
return options && {
...options,
identityProvider: options.identityProvider && {
getId(el) {
return options.identityProvider!.getId(el.element!);
return options.identityProvider!.getId(el.element as T);
}
},
multipleSelectionController: options.multipleSelectionController && {
@@ -140,28 +140,28 @@ function asObjectTreeOptions<T, TFilterData>(options?: IAsyncDataTreeOptions<T,
},
accessibilityProvider: options.accessibilityProvider && {
getAriaLabel(e) {
return options.accessibilityProvider!.getAriaLabel(e.element!);
return options.accessibilityProvider!.getAriaLabel(e.element as T);
}
},
filter: options.filter && {
filter(e, parentVisibility) {
return options.filter!.filter(e.element!, parentVisibility);
return options.filter!.filter(e.element as T, parentVisibility);
}
},
keyboardNavigationLabelProvider: options.keyboardNavigationLabelProvider && {
getKeyboardNavigationLabel(e) {
return options.keyboardNavigationLabelProvider!.getKeyboardNavigationLabel(e.element!);
return options.keyboardNavigationLabelProvider!.getKeyboardNavigationLabel(e.element as T);
}
},
sorter: options.sorter && {
compare(a, b) {
return options.sorter!.compare(a.element!, b.element!);
return options.sorter!.compare(a.element as T, b.element as T);
}
}
};
}
function asTreeElement<T>(node: IAsyncDataTreeNode<T>): ITreeElement<IAsyncDataTreeNode<T>> {
function asTreeElement<TInput, T>(node: IAsyncDataTreeNode<TInput, T>): ITreeElement<IAsyncDataTreeNode<TInput, T>> {
return {
element: node,
children: Iterator.map(Iterator.fromArray(node.children!), asTreeElement)
@@ -173,15 +173,15 @@ export interface IAsyncDataTreeOptions<T, TFilterData = void> extends IAbstractT
sorter?: ITreeSorter<T>;
}
export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> implements IDisposable {
export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable {
private readonly tree: ObjectTree<IAsyncDataTreeNode<T>, TFilterData>;
private readonly root: IAsyncDataTreeNode<T>;
private readonly nodes = new Map<T | null, IAsyncDataTreeNode<T>>();
private readonly refreshPromises = new Map<IAsyncDataTreeNode<T>, Promise<void>>();
private readonly tree: ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData>;
private readonly root: IAsyncDataTreeNode<TInput, T>;
private readonly nodes = new Map<null | T, IAsyncDataTreeNode<TInput, T>>();
private readonly refreshPromises = new Map<IAsyncDataTreeNode<TInput, T>, Promise<void>>();
private readonly identityProvider?: IIdentityProvider<T>;
private readonly _onDidChangeNodeState = new Emitter<IAsyncDataTreeNode<T>>();
private readonly _onDidChangeNodeState = new Emitter<IAsyncDataTreeNode<TInput, T>>();
protected readonly disposables: IDisposable[] = [];
@@ -204,20 +204,20 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
container: HTMLElement,
delegate: IListVirtualDelegate<T>,
renderers: ITreeRenderer<any /* TODO@joao */, TFilterData, any>[],
private dataSource: IAsyncDataSource<T>,
private dataSource: IAsyncDataSource<TInput, T>,
options?: IAsyncDataTreeOptions<T, TFilterData>
) {
this.identityProvider = options && options.identityProvider;
const objectTreeDelegate = new ComposedTreeDelegate<T | null, IAsyncDataTreeNode<T>>(delegate);
const objectTreeDelegate = new ComposedTreeDelegate<TInput | T, IAsyncDataTreeNode<TInput, T>>(delegate);
const objectTreeRenderers = renderers.map(r => new DataTreeRenderer(r, this._onDidChangeNodeState.event));
const objectTreeOptions = asObjectTreeOptions(options) || {};
const objectTreeOptions = asObjectTreeOptions<TInput, T, TFilterData>(options) || {};
objectTreeOptions.collapseByDefault = true;
this.tree = new ObjectTree(container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions);
this.root = {
element: null,
element: undefined!,
parent: null,
state: AsyncDataTreeNodeState.Uninitialized,
};
@@ -279,16 +279,34 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
// Data Tree
refresh(element: T | null, recursive = true): Promise<void> {
getInput(): TInput | undefined {
return this.root.element as TInput;
}
setInput(input: TInput | undefined): Promise<void> {
this.root.element = input!;
if (typeof input === 'undefined') {
return Promise.resolve();
}
return this.refresh(input);
}
refresh(element: TInput | T = this.root.element, recursive = true): Promise<void> {
if (typeof this.root.element === 'undefined') {
throw new Error('Tree input not set');
}
return this.refreshNode(this.getDataNode(element), recursive, ChildrenResolutionReason.Refresh);
}
// Tree
getNode(element: T | null): ITreeNode<T | null, TFilterData> {
getNode(element: TInput | T): ITreeNode<TInput | T, TFilterData> {
const dataNode = this.getDataNode(element);
const node = this.tree.getNode(dataNode === this.root ? null : dataNode);
return new AsyncDataTreeNodeWrapper<T | null, TFilterData>(node);
return new AsyncDataTreeNodeWrapper<TInput, T, TFilterData>(node);
}
collapse(element: T, recursive: boolean = false): boolean {
@@ -342,7 +360,7 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
getSelection(): T[] {
const nodes = this.tree.getSelection();
return nodes.map(n => n!.element!);
return nodes.map(n => n!.element as T);
}
setFocus(elements: T[], browserEvent?: UIEvent): void {
@@ -376,7 +394,7 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
getFocus(): T[] {
const nodes = this.tree.getFocus();
return nodes.map(n => n!.element!);
return nodes.map(n => n!.element as T);
}
open(elements: T[]): void {
@@ -394,21 +412,21 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
// Tree navigation
getParentElement(element: T): T | null {
getParentElement(element: T): TInput | T {
const node = this.tree.getParentElement(this.getDataNode(element));
return node && node.element;
return (node && node.element)!;
}
getFirstElementChild(element: T | null = null): T | null | undefined {
getFirstElementChild(element: TInput | T = this.root.element): TInput | T | undefined {
const dataNode = this.getDataNode(element);
const node = this.tree.getFirstElementChild(dataNode === this.root ? null : dataNode);
return node && node.element;
return (node && node.element)!;
}
getLastElementAncestor(element: T | null = null): T | null | undefined {
getLastElementAncestor(element: TInput | T = this.root.element): TInput | T | undefined {
const dataNode = this.getDataNode(element);
const node = this.tree.getLastElementAncestor(dataNode === this.root ? null : dataNode);
return node && node.element;
return (node && node.element)!;
}
// List
@@ -419,8 +437,8 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
// Implementation
private getDataNode(element: T | null): IAsyncDataTreeNode<T> {
const node: IAsyncDataTreeNode<T> = this.nodes.get(element);
private getDataNode(element: TInput | T): IAsyncDataTreeNode<TInput, T> {
const node: IAsyncDataTreeNode<TInput, T> = this.nodes.get((element === this.root.element ? null : element) as T);
if (typeof node === 'undefined') {
throw new Error(`Data tree node not found: ${element}`);
@@ -429,7 +447,7 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
return node;
}
private async refreshNode(node: IAsyncDataTreeNode<T>, recursive: boolean, reason: ChildrenResolutionReason): Promise<void> {
private async refreshNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, reason: ChildrenResolutionReason): Promise<void> {
await this._refreshNode(node, recursive, reason);
if (recursive && node.children) {
@@ -437,7 +455,7 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
}
}
private _refreshNode(node: IAsyncDataTreeNode<T>, recursive: boolean, reason: ChildrenResolutionReason): Promise<void> {
private _refreshNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, reason: ChildrenResolutionReason): Promise<void> {
let result = this.refreshPromises.get(node);
if (result) {
@@ -449,8 +467,8 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
return always(result, () => this.refreshPromises.delete(node));
}
private doRefresh(node: IAsyncDataTreeNode<T>, recursive: boolean, reason: ChildrenResolutionReason): Promise<void> {
const hasChildren = !!this.dataSource.hasChildren(node.element);
private doRefresh(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, reason: ChildrenResolutionReason): Promise<void> {
const hasChildren = !!this.dataSource.hasChildren(node.element!);
if (!hasChildren) {
this.setChildren(node, [], recursive);
@@ -468,14 +486,14 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
this._onDidChangeNodeState.fire(node);
}, _ => null);
return Promise.resolve(this.dataSource.getChildren(node.element))
return Promise.resolve(this.dataSource.getChildren(node.element!))
.then(children => {
slowTimeout.cancel();
node.state = AsyncDataTreeNodeState.Loaded;
this._onDidChangeNodeState.fire(node);
this.setChildren(node, children, recursive);
this._onDidResolveChildren.fire({ element: node.element, reason });
this._onDidResolveChildren.fire({ element: node.element as T, reason });
}, err => {
slowTimeout.cancel();
node.state = AsyncDataTreeNodeState.Uninitialized;
@@ -490,18 +508,18 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
}
}
private _onDidChangeCollapseState({ node, deep }: ICollapseStateChangeEvent<IAsyncDataTreeNode<T>, any>): void {
private _onDidChangeCollapseState({ node, deep }: ICollapseStateChangeEvent<IAsyncDataTreeNode<TInput, T>, any>): void {
if (!node.collapsed && node.element.state === AsyncDataTreeNodeState.Uninitialized) {
if (deep) {
this.collapse(node.element.element!);
this.collapse(node.element.element as T);
} else {
this.refreshNode(node.element, false, ChildrenResolutionReason.Expand);
}
}
}
private setChildren(node: IAsyncDataTreeNode<T>, childrenElements: T[], recursive: boolean): void {
const children = childrenElements.map<ITreeElement<IAsyncDataTreeNode<T>>>(element => {
private setChildren(node: IAsyncDataTreeNode<TInput, T>, childrenElements: T[], recursive: boolean): void {
const children = childrenElements.map<ITreeElement<IAsyncDataTreeNode<TInput, T>>>(element => {
if (!this.identityProvider) {
return {
element: {
@@ -514,7 +532,7 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
};
}
const nodeChildren = new Map<string, IAsyncDataTreeNode<T>>();
const nodeChildren = new Map<string, IAsyncDataTreeNode<TInput, T>>();
for (const child of node.children!) {
nodeChildren.set(child.id!, child);
@@ -556,7 +574,7 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
}
}
let children: Iterator<ITreeElement<IAsyncDataTreeNode<T>>> | undefined = undefined;
let children: Iterator<ITreeElement<IAsyncDataTreeNode<TInput, T>>> | undefined = undefined;
if (collapsible) {
children = Iterator.map(Iterator.fromArray(asyncDataTreeNode.children!), asTreeElement);
@@ -572,17 +590,17 @@ export class AsyncDataTree<T extends NonNullable<any>, TFilterData = void> imple
const insertedElements = new Set<T>();
const onDidCreateNode = (treeNode: ITreeNode<IAsyncDataTreeNode<T>, TFilterData>) => {
const onDidCreateNode = (treeNode: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>) => {
if (treeNode.element.element) {
insertedElements.add(treeNode.element.element);
this.nodes.set(treeNode.element.element, treeNode.element);
insertedElements.add(treeNode.element.element as T);
this.nodes.set(treeNode.element.element as T, treeNode.element);
}
};
const onDidDeleteNode = (treeNode: ITreeNode<IAsyncDataTreeNode<T>, TFilterData>) => {
const onDidDeleteNode = (treeNode: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>) => {
if (treeNode.element.element) {
if (!insertedElements.has(treeNode.element.element)) {
this.nodes.delete(treeNode.element.element);
if (!insertedElements.has(treeNode.element.element as T)) {
this.nodes.delete(treeNode.element.element as T);
}
}
};